Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions changelog/dslices-cpp-mangling.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
Added support for D arrays in C++ functions

D arrays don't have any corresponding type in C++. This change adds support for
mangling a D array as a templated struct with the special name: `__dslice`.
Under the hood, a D array is represented as a struct, containing the length of
the array and a pointer to the data:

---
struct DArray(T)
{
size_t length;
T* ptr;
}
---

Any D array in the form of, `T[]`, when used in a C++ function, will be mangled
as the following struct:

---
struct __dslice(T);
---

For example, an array of ints, `int[]`, will be mangled as `__dslice!int` when
used in a C++ function. This allows to declare C++ functions that accepts D
arrays and can be accessed to C++ if the right struct declaration is present
on the C++ side.

On the D side:

---
extern(C++) void foo(const(char)[] str);

void main()
{
foo("bar");
}
---

On the C++ side:

$(CPPCODE
#include <stdio.h>

template<typename T> struct __dslice
{
size_t length;
T* ptr;
};

void foo(__dslice<const char> array)
{
printf("%.*s\n", (int) array.length, array.ptr);
}
)

Since the `__dslice` struct is ABI compatible with D arrays, it works perfectly
fine to pass a D array to a C++ function taking a `__dslice`.
7 changes: 6 additions & 1 deletion src/dmd/cppmangle.d
Original file line number Diff line number Diff line change
Expand Up @@ -1910,7 +1910,12 @@ extern(C++):

override void visit(Type t)
{
error(t);
auto typeForMangling = t.typeForMangling(LINK.cpp);

if (typeForMangling is t)
error(t);
else
typeForMangling.accept(this);
}

void visit(Tuple t)
Expand Down
9 changes: 7 additions & 2 deletions src/dmd/cppmanglewin.d
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ public:

override void visit(Type type)
{
auto typeForMangling = type.typeForMangling(LINK.cpp);

if (typeForMangling !is type)
return typeForMangling.accept(this);

if (checkImmutableShared(type))
return;

Expand Down Expand Up @@ -1036,7 +1041,7 @@ private:
// ::= $0<encoded integral number>
//printf("mangleIdent('%s')\n", sym.toChars());
Dsymbol p = sym;
if (p.toParent() && p.toParent().isTemplateInstance())
if (p && p.toParent() && p.toParent().isTemplateInstance())
Comment thread
thewilsonator marked this conversation as resolved.
{
p = p.toParent();
}
Expand All @@ -1047,7 +1052,7 @@ private:
for (auto ns = p.cppnamespace; ns !is null; ns = ns.cppnamespace)
mangleName(ns, dont_use_back_reference);
p = p.toParent();
if (p.toParent() && p.toParent().isTemplateInstance())
if (p && p.toParent() && p.toParent().isTemplateInstance())
{
p = p.toParent();
}
Expand Down
5 changes: 5 additions & 0 deletions src/dmd/dmangle.d
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,11 @@ extern (C++) const(char)* mangleExact(FuncDeclaration fd)

extern (C++) void mangleToBuffer(Type t, OutBuffer* buf)
{
auto typeForMangling = t.typeForMangling(LINK.d);

if (t !is typeForMangling)
return mangleToBuffer(typeForMangling, buf);

if (t.deco)
buf.writestring(t.deco);
else
Expand Down
1 change: 1 addition & 0 deletions src/dmd/id.d
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ immutable Msgtable[] msgtable =
{ "basic_ostream" },
{ "basic_iostream" },
{ "char_traits" },
{ "__dslice" },

// Compiler recognized UDA's
{ "udaSelector", "selector" },
Expand Down
103 changes: 103 additions & 0 deletions src/dmd/mtype.d
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,24 @@ extern (C++) abstract class Type : ASTNode
return DYNCAST.type;
}

/**
* Returns the type the receiver should be treated as during mangling.
*
* This allows for a type to be treated as a different type during mangling.
* This is useful, for example, when interfacing with C++, for D types that
* don't have a corresponding C++ type. This can allow `int[]` to be
* mangled as `__dslice!int` during C++ mangling.
*
* Params:
* linkage = the type of mangling that is requested
*
* Returns: the type the receiver should be treated as during mangling
*/
Type typeForMangling(LINK linkage)
{
return this;
}

/*******************************
* Covariant means that 'this' can substitute for 't',
* i.e. a pure function is a match for an impure type.
Expand Down Expand Up @@ -3720,6 +3738,9 @@ extern (C++) final class TypeSArray : TypeArray
*/
extern (C++) final class TypeDArray : TypeArray
{
/// Mangle D array as this type when mangling for C++.
private Type typeForCppMangling;

extern (D) this(Type t)
{
super(Tarray, t);
Expand All @@ -3744,6 +3765,88 @@ extern (C++) final class TypeDArray : TypeArray
return t;
}

override Type typeForMangling(LINK linkage)
{
/**
* Returns a template declaration corresponding to the following code:
*
* ---
* template __dslice(T) {}
* ---
*
* Returns: a template declaration corresponding to the above code
*
* See_Also: `typeForCppMangling`
*/
static TemplateDeclaration dsliceTemplateDeclaration()
{
__gshared TemplateDeclaration td;

if (td)
return td;

auto ttp = new TemplateTypeParameter(Loc.initial, Id.p, null, null);
auto parameters = new TemplateParameters(ttp);

return td = new TemplateDeclaration(Loc.initial, Id.__dslice, parameters,
null, null);
}

/**
* Returns a template instantiation of the template declaration returned
* by `dsliceTemplateDeclaration`.
*
* The template is instantiated with the element type of this array. For
* an array of ints it would correspond to the following D code:
* `__dslice!int`.
*
* Returns: a template instance
*
* See_Also: `dsliceTemplateDeclaration`
* See_Also: `typeForCppMangling`
*/
TemplateInstance dsliceTemplateInstance()
{
auto tiargs = new Objects(next);
auto ti = new TemplateInstance(Loc.initial, Id.__dslice, tiargs);
ti.tempdecl = dsliceTemplateDeclaration();

return ti;
}

/**
* Returns the type that this array type should be treated as when it's
* mangled as a C++ type.
*
* D arrays don't have any corresponding type in C++. Instead we mangle
* it as a templated struct with the name `__dslice`, i.e.
* `struct __dslice(T)`, where `T` is the element type of the array. For
* an array of ints it would be mangled as the following type
* `__dslice!int`.
*
* Returns: the type that this should be treated as when mangling as a
* C++ type
*
* See_Also: `dsliceTemplateInstance`
*/
Type typeForCppMangling()
{
__gshared Type[char*] cachedTypes;
auto elementType = next;

if (auto type = elementType.deco in cachedTypes)
return *type;

auto sd = new StructDeclaration(Loc.initial, Id.__dslice, false);
sd.parent = dsliceTemplateInstance();

auto type = new TypeStruct(sd).typeSemantic(Loc.initial, null);
return cachedTypes[elementType.deco] = type;
}

return linkage == LINK.cpp ? typeForCppMangling() : this;
}

override d_uns64 size(const ref Loc loc) const
{
//printf("TypeDArray::size()\n");
Expand Down
6 changes: 6 additions & 0 deletions src/dmd/mtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ class Type : public ASTNode
virtual Type *syntaxCopy();
bool equals(const RootObject *o) const;
bool equivalent(Type *t);
virtual Type* typeForMangling(LINK linkage);
// kludge for template.isType()
DYNCAST dyncast() const { return DYNCAST_TYPE; }
int covariant(Type *t, StorageClass *pstc = NULL, bool fix17349 = true);
Expand Down Expand Up @@ -453,9 +454,14 @@ class TypeSArray : public TypeArray
// Dynamic array, no dimension
class TypeDArray : public TypeArray
{
private:
Type* typeForCppMangling;

public:

const char *kind();
Type *syntaxCopy();
Type* typeForMangling(LINK linkage);
d_uns64 size(const Loc &loc) /*const*/;
unsigned alignsize() /*const*/;
bool isString();
Expand Down
15 changes: 15 additions & 0 deletions src/dmd/root/array.d
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ public:

@disable this(this);

/**
* Convenience constructor to add the given elements to this array.
*
* Params:
* elements = elements to add to this array
*/
extern(D) this(T[] elements ...) nothrow
{
if (elements.length > 0)
reserve(elements.length);

foreach (e ; elements)
push(e);
}

~this() pure nothrow
{
debug (stomp) memset(data.ptr, 0xFF, data.length);
Expand Down
12 changes: 12 additions & 0 deletions src/dmd/root/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,15 @@ struct Array
}
};

// This is the type that the D compiler will mangle D slices as when mangling
// for C++. This type is ABI compatible with native D slices.
Copy link
Copy Markdown
Contributor

@kinke kinke Jul 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ABI compatibility actually tested somewhere? I seriously doubt it is on Win64 for example. LDC on Win64 returns and passes slices (and delegates IIRC) as pairs (in 2 registers if available), whereas a 128-bit struct is passed/returned indirectly by value (hidden ref), and there is no way for a struct to emulate that behaviour.

[Btw, the __dslice ctors below make MSVC++ treat this as a non-POD and return it via hidden pointer, but that's irrelevant here, as it's > 64 bits anyway.]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ T[] ABI need not be the same as the D T[] ABI, but I take your point. I don't expect this to be used straight away in DMD because that will require an upgrade of the minimum DMDFE required to build, and there has to be at least one release that can build it w/o using it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently the same for DMD. DMD 2.081.0, -m64 on Windows, just checking the return value:

const(char)[] getNativeSlice() { return "lala"[0..3]; }

struct Slice(T)
{
    size_t length;
    T* ptr;
}

extern(C++) Slice!(const(char)) getSlice() { return Slice!(const(char))(3, "lala".ptr); }

=>

; native slice returned in RAX and RDX
_D5slice14getNativeSliceFZAxa:
  0000000000000000: 55                 push        rbp
  0000000000000001: 48 8B EC           mov         rbp,rsp
  0000000000000004: 48 8D 15 00 00 00  lea         rdx,[__a3_6c616c]
                    00
  000000000000000B: B8 03 00 00 00     mov         eax,3
  0000000000000010: 5D                 pop         rbp
  0000000000000011: C3                 ret

; pointer to pre-allocated return value passed in RCX
?getSlice@@YA?AU?$Slice@D@@XZ (struct Slice<char> __cdecl getSlice(void)):
  0000000000000000: 55                 push        rbp
  0000000000000001: 48 8B EC           mov         rbp,rsp
  0000000000000004: 48 C7 01 03 00 00  mov         qword ptr [rcx],3
                    00
  000000000000000B: 48 8D 05 00 00 00  lea         rax,[_TMP0]
                    00
  0000000000000012: 48 89 41 08        mov         qword ptr [rcx+8],rax
  0000000000000016: 48 89 C8           mov         rax,rcx
  0000000000000019: 5D                 pop         rbp
  000000000000001A: C3                 ret

Copy link
Copy Markdown
Contributor Author

@jacob-carlborg jacob-carlborg Jul 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ABI compatibility actually tested somewhere?

No.

LDC on Win64 returns and passes slices (and delegates IIRC) as pairs (in 2 registers if available), whereas a 128-bit struct is passed/returned indirectly by value (hidden ref), and there is no way for a struct to emulate that behavior.

That's unfortunate.

template <typename T> struct __dslice
{
d_size_t length;
T* ptr;

__dslice() : length(0), ptr(NULL) {}
__dslice(size_t length, T* ptr) : length(length), ptr(ptr) {}
};

#define DSlice __dslice
34 changes: 34 additions & 0 deletions test/compilable/cppmangle.d
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,40 @@ else version(Posix)
static assert(test19278_var.mangleof == "_ZN5hello5world13test19278_varE");
}

/*****************************************/
// https://issues.dlang.org/show_bug.cgi?id=18716
// D slices as parameters in extern(C++) functions

struct Test18716 {}
struct __dslice(T) {}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests needs to be in runnable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ABI doesn't match, so there's no point, at least not yet.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is exactly that the ABI does not match.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following. This whole PR is moot as long as the ABI problem exists.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I was just saying that it's not obvious, since there are tests, and the PR is green.
But IMO, this PR should ultimately be accepted, but it looks like you fear it to be vetoed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It should, but Walter hasn't responded to the reasoning why his concerns are unfounded.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not going to spend time on moving the tests if/until I fix the ABI issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Walter did already vetoed it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But not sensibly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#11073 contains a runnable_cxx test (if someone wants to use it)


extern(C++) void test18716a(char[]);
extern(C++) void test18716b(__dslice!char);
extern(C++) void test18716c(const(char)[]);
extern(C++) void test18716d(Test18716[]);

void test18716()
{
test18716a("foo".dup);
test18716c("foo");
test18716d([Test18716()]);
Comment thread
thewilsonator marked this conversation as resolved.
}

version (Posix)
{
static assert(test18716a.mangleof == "_Z10test18716a8__dsliceIcE");
static assert(test18716b.mangleof == "_Z10test18716b8__dsliceIcE");
static assert(test18716c.mangleof == "_Z10test18716c8__dsliceIKcE");
static assert(test18716d.mangleof == "_Z10test18716d8__dsliceI9Test18716E");
}

else version (Windows)
{
static assert(test18716a.mangleof == "?test18716a@@YAXU?$__dslice@D@@@Z");
static assert(test18716b.mangleof == "?test18716b@@YAXU?$__dslice@D@@@Z");
static assert(test18716c.mangleof == "?test18716c@@YAXU?$__dslice@$$CBD@@@Z");
static assert(test18716d.mangleof == "?test18716d@@YAXU?$__dslice@UTest18716@@@@@Z");
}
/**************************************/
// https://issues.dlang.org/show_bug.cgi?id=18958
// Issue 18958 - extern(C++) wchar, dchar mangling not correct
Expand Down