From 8c86b56dd44ecf88eceeac2518afbf7300c097ca Mon Sep 17 00:00:00 2001 From: Jacob Carlborg Date: Tue, 3 Apr 2018 13:43:38 +0200 Subject: [PATCH 1/3] Fix issue 18716: type `const(char)[]` can not be mapped to C++ 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`. --- changelog/dslices-cpp-mangling.dd | 57 +++++++++++++++++ src/dmd/cppmangle.d | 7 +- src/dmd/cppmanglewin.d | 9 ++- src/dmd/dmangle.d | 5 ++ src/dmd/id.d | 1 + src/dmd/mtype.d | 103 ++++++++++++++++++++++++++++++ src/dmd/mtype.h | 6 ++ src/dmd/root/array.d | 15 +++++ src/dmd/root/array.h | 12 ++++ test/compilable/cppmangle.d | 35 ++++++++++ 10 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 changelog/dslices-cpp-mangling.dd diff --git a/changelog/dslices-cpp-mangling.dd b/changelog/dslices-cpp-mangling.dd new file mode 100644 index 000000000000..e4193524b25b --- /dev/null +++ b/changelog/dslices-cpp-mangling.dd @@ -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`. diff --git a/src/dmd/cppmangle.d b/src/dmd/cppmangle.d index 423816a04d13..dbf567b6f0be 100644 --- a/src/dmd/cppmangle.d +++ b/src/dmd/cppmangle.d @@ -1970,7 +1970,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) diff --git a/src/dmd/cppmanglewin.d b/src/dmd/cppmanglewin.d index f20751259568..a8666a54fc5c 100644 --- a/src/dmd/cppmanglewin.d +++ b/src/dmd/cppmanglewin.d @@ -125,6 +125,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; @@ -1037,7 +1042,7 @@ private: // ::= $0 //printf("mangleIdent('%s')\n", sym.toChars()); Dsymbol p = sym; - if (p.toParent() && p.toParent().isTemplateInstance()) + if (p && p.toParent() && p.toParent().isTemplateInstance()) { p = p.toParent(); } @@ -1048,7 +1053,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(); } diff --git a/src/dmd/dmangle.d b/src/dmd/dmangle.d index 990eb49c314b..20122ecb3832 100644 --- a/src/dmd/dmangle.d +++ b/src/dmd/dmangle.d @@ -1181,6 +1181,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 diff --git a/src/dmd/id.d b/src/dmd/id.d index 8f3872ed99ad..f8b4098ebf7e 100644 --- a/src/dmd/id.d +++ b/src/dmd/id.d @@ -461,6 +461,7 @@ immutable Msgtable[] msgtable = { "basic_ostream" }, { "basic_iostream" }, { "char_traits" }, + { "__dslice" }, // Compiler recognized UDA's { "udaGNUAbiTag", "gnuAbiTag" }, diff --git a/src/dmd/mtype.d b/src/dmd/mtype.d index f0a511a3c543..06f6f3a9c3d9 100644 --- a/src/dmd/mtype.d +++ b/src/dmd/mtype.d @@ -547,6 +547,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. @@ -3728,6 +3746,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); @@ -3752,6 +3773,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"); diff --git a/src/dmd/mtype.h b/src/dmd/mtype.h index a1fef30bee1c..cb54ce5d62a7 100644 --- a/src/dmd/mtype.h +++ b/src/dmd/mtype.h @@ -226,6 +226,7 @@ class Type : public ASTNode bool equivalent(Type *t); // kludge for template.isType() DYNCAST dyncast() const { return DYNCAST_TYPE; } + virtual Type* typeForMangling(LINK linkage); int covariant(Type *t, StorageClass *pstc = NULL, bool fix17349 = true); const char *toChars() const; char *toPrettyChars(bool QualifyTypes = false); @@ -456,9 +457,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(); diff --git a/src/dmd/root/array.d b/src/dmd/root/array.d index b6ac8e8ff3c5..c8a31e60d466 100644 --- a/src/dmd/root/array.d +++ b/src/dmd/root/array.d @@ -49,6 +49,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); diff --git a/src/dmd/root/array.h b/src/dmd/root/array.h index 2a1c71d2622d..18c788dead3d 100644 --- a/src/dmd/root/array.h +++ b/src/dmd/root/array.h @@ -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. +template 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 diff --git a/test/compilable/cppmangle.d b/test/compilable/cppmangle.d index b9c4ea5921c0..999e90aa2a9e 100644 --- a/test/compilable/cppmangle.d +++ b/test/compilable/cppmangle.d @@ -1279,3 +1279,38 @@ version (Win64) extern(C++) extern(C++, struct) class DefaultClass20700_2 {} static assert(test20700_4.mangleof == `?test20700_4@@YAXPEAU?$TStruct20700_2@PEAUDefaultClass20700_2@@VDefaultStruct20700_2@@@@@Z`); } + +/*****************************************/ +// https://issues.dlang.org/show_bug.cgi?id=18716 +// D slices as parameters in extern(C++) functions + +struct Test18716 {} +struct __dslice(T) {} + +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()]); +} + +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"); +} From a7bba403b6c2c5fa3e8813f158d3bcd2f4f70f65 Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Tue, 28 Apr 2020 21:29:30 +0200 Subject: [PATCH 2/3] Fix unittest --- src/dmd/root/array.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dmd/root/array.d b/src/dmd/root/array.d index c8a31e60d466..daffc9ead665 100644 --- a/src/dmd/root/array.d +++ b/src/dmd/root/array.d @@ -398,7 +398,7 @@ unittest unittest { - auto arrayA = Array!int(0); + auto arrayA = Array!int(size_t(0)); int[3] buf = [10, 15, 20]; arrayA.pushSlice(buf); assert(arrayA[] == buf[]); From ffc8bce7e873d7571522ab25eef489067854de4a Mon Sep 17 00:00:00 2001 From: MoonlightSentinel Date: Tue, 28 Apr 2020 23:54:22 +0200 Subject: [PATCH 3/3] Runnable test --- test/runnable_cxx/extra-files/slices.cpp | 66 +++++++++++++++++++++++ test/runnable_cxx/slices.d | 69 ++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 test/runnable_cxx/extra-files/slices.cpp create mode 100644 test/runnable_cxx/slices.d diff --git a/test/runnable_cxx/extra-files/slices.cpp b/test/runnable_cxx/extra-files/slices.cpp new file mode 100644 index 000000000000..eba92fade1ec --- /dev/null +++ b/test/runnable_cxx/extra-files/slices.cpp @@ -0,0 +1,66 @@ +#include "array.h" +#include + +void ints(DSlice values) +{ + assert(values.length == 1); + assert(values.ptr[0] == 1); +} + +void cints(DSlice a) +{ + assert(a.length == 2); + assert(a.ptr[0] == 2); + assert(a.ptr[1] == 3); +} + +void ccints(const DSlice a) +{ + assert(a.length == 3); + assert(a.ptr[0] == 4); + assert(a.ptr[1] == 5); + assert(a.ptr[2] == 6); +} + +DSlice wrap(int* ptr, int size) +{ + return DSlice(size, ptr); +} + +void paddedInts(signed char d1, DSlice a, short d2, DSlice b) +{ + assert(d1 == 33); + + assert(a.length == 1); + assert(*a.ptr == 44); + + assert(d2 == 55); + + assert(b.length == 1); + assert(*b.ptr == 66); +} + +DSlice passthrough(DSlice values) +{ + assert(values.length == 5); + return values; +} + +DSlice& passthroughRef(DSlice& values) +{ + assert(values.length == 5); + return values; +} + +struct S +{ + int a, b; +}; + +void structs(DSlice values) +{ + assert(values.length == 1); + assert(values.ptr->a == 1); + assert(values.ptr->b == 2); +} + diff --git a/test/runnable_cxx/slices.d b/test/runnable_cxx/slices.d new file mode 100644 index 000000000000..29079ddae8fe --- /dev/null +++ b/test/runnable_cxx/slices.d @@ -0,0 +1,69 @@ +/* +REQUIRED_ARGS: -extern-std=c++11 +EXTRA_CPP_SOURCES: slices.cpp +CXXFLAGS(windows): /I..\src\dmd\root +CXXFLAGS(osx linux freebsd openbsd netbsd dragonflybsd solaris): -std=c++11 -I../src/dmd/root +*/ + +void main() +{ + ints([1]); + cints([2, 3]); + ccints([4, 5, 6]); + + paddedInts(33, [44], 55, [66]); + + char[] arr = cast(char[]) "Hello"; + version (Windows) {} + else + { + int[] values = [1]; + int[] ret = wrap(values.ptr, cast(int) values.length); + assert(ret is values); + + assert(passthrough(arr) == arr); + } + + assert(passthroughRef(arr) is arr); + + structs([S(1, 2)]); +} + +extern (C++): + +// Check correct handling of qualifiers +void ints(int[]); +void cints(const(int)[]); +void ccints(const int[]); + +// Break "accidental" ABI compatibility +void paddedInts(byte, int[], short, int[]); + +// Check D -> C++ -> D works as well +version (Windows) +{ + /* + Wrong return type mangling on windows: + + E.g. + Expected: ?passthrough@@YAU?$__dslice@D@@U1@@Z + Actual: ?passthrough@@YA?AU?$__dslice@D@@U1@@Z + + Probably (related to) https://issues.dlang.org/show_bug.cgi?id=20679 + */ +} +else +{ + int[] wrap(int*, int); + char[] passthrough(char[]); +} + +ref char[] passthroughRef(ref char[]); + +// Complex types should be supported as well +struct S +{ + int a, b; +} + +void structs(S[]);