aboutsummaryrefslogtreecommitdiff
path: root/gcc/d/dmd/cppmangle.d
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/d/dmd/cppmangle.d')
-rw-r--r--gcc/d/dmd/cppmangle.d2540
1 files changed, 2540 insertions, 0 deletions
diff --git a/gcc/d/dmd/cppmangle.d b/gcc/d/dmd/cppmangle.d
new file mode 100644
index 0000000..0381f9a
--- /dev/null
+++ b/gcc/d/dmd/cppmangle.d
@@ -0,0 +1,2540 @@
+/**
+ * Do mangling for C++ linkage.
+ *
+ * This is the POSIX side of the implementation.
+ * It exports two functions to C++, `toCppMangleItanium` and `cppTypeInfoMangleItanium`.
+ *
+ * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
+ * Authors: Walter Bright, http://www.digitalmars.com
+ * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
+ * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/cppmangle.d, _cppmangle.d)
+ * Documentation: https://dlang.org/phobos/dmd_cppmangle.html
+ * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/cppmangle.d
+ *
+ * References:
+ * Follows Itanium C++ ABI 1.86 section 5.1
+ * http://refspecs.linux-foundation.org/cxxabi-1.86.html#mangling
+ * which is where the grammar comments come from.
+ *
+ * Bugs:
+ * https://issues.dlang.org/query.cgi
+ * enter `C++, mangling` as the keywords.
+ */
+
+module dmd.cppmangle;
+
+import core.stdc.string;
+import core.stdc.stdio;
+
+import dmd.arraytypes;
+import dmd.astenums;
+import dmd.attrib;
+import dmd.declaration;
+import dmd.dsymbol;
+import dmd.dtemplate;
+import dmd.errors;
+import dmd.expression;
+import dmd.func;
+import dmd.globals;
+import dmd.id;
+import dmd.identifier;
+import dmd.mtype;
+import dmd.nspace;
+import dmd.root.array;
+import dmd.root.outbuffer;
+import dmd.root.rootobject;
+import dmd.root.string;
+import dmd.target;
+import dmd.tokens;
+import dmd.typesem;
+import dmd.visitor;
+
+
+// helper to check if an identifier is a C++ operator
+enum CppOperator { Cast, Assign, Eq, Index, Call, Unary, Binary, OpAssign, Unknown }
+package CppOperator isCppOperator(Identifier id)
+{
+ __gshared const(Identifier)[] operators = null;
+ if (!operators)
+ operators = [Id._cast, Id.assign, Id.eq, Id.index, Id.call, Id.opUnary, Id.opBinary, Id.opOpAssign];
+ foreach (i, op; operators)
+ {
+ if (op == id)
+ return cast(CppOperator)i;
+ }
+ return CppOperator.Unknown;
+}
+
+///
+extern(C++) const(char)* toCppMangleItanium(Dsymbol s)
+{
+ //printf("toCppMangleItanium(%s)\n", s.toChars());
+ OutBuffer buf;
+ scope CppMangleVisitor v = new CppMangleVisitor(&buf, s.loc);
+ v.mangleOf(s);
+ return buf.extractChars();
+}
+
+///
+extern(C++) const(char)* cppTypeInfoMangleItanium(Dsymbol s)
+{
+ //printf("cppTypeInfoMangle(%s)\n", s.toChars());
+ OutBuffer buf;
+ buf.writestring("_ZTI"); // "TI" means typeinfo structure
+ scope CppMangleVisitor v = new CppMangleVisitor(&buf, s.loc);
+ v.cpp_mangle_name(s, false);
+ return buf.extractChars();
+}
+
+///
+extern(C++) const(char)* cppThunkMangleItanium(FuncDeclaration fd, int offset)
+{
+ //printf("cppThunkMangleItanium(%s)\n", fd.toChars());
+ OutBuffer buf;
+ buf.printf("_ZThn%u_", offset); // "Th" means thunk, "n%u" is the call offset
+ scope CppMangleVisitor v = new CppMangleVisitor(&buf, fd.loc);
+ v.mangle_function_encoding(fd);
+ return buf.extractChars();
+}
+
+/******************************
+ * Determine if sym is the 'primary' destructor, that is,
+ * the most-aggregate destructor (the one that is defined as __xdtor)
+ * Params:
+ * sym = Dsymbol
+ * Returns:
+ * true if sym is the primary destructor for an aggregate
+ */
+bool isPrimaryDtor(const Dsymbol sym)
+{
+ const dtor = sym.isDtorDeclaration();
+ if (!dtor)
+ return false;
+ const ad = dtor.isMember();
+ assert(ad);
+ return dtor == ad.primaryDtor;
+}
+
+/// Context used when processing pre-semantic AST
+private struct Context
+{
+ /// Template instance of the function being mangled
+ TemplateInstance ti;
+ /// Function declaration we're mangling
+ FuncDeclaration fd;
+ /// Current type / expression being processed (semantically analyzed)
+ RootObject res;
+
+ @disable ref Context opAssign(ref Context other);
+ @disable ref Context opAssign(Context other);
+
+ /**
+ * Helper function to track `res`
+ *
+ * Params:
+ * next = Value to set `this.res` to.
+ * If `this.res` is `null`, the expression is not evalutated.
+ * This allow this code to be used even when no context is needed.
+ *
+ * Returns:
+ * The previous state of this `Context` object
+ */
+ private Context push(lazy RootObject next)
+ {
+ auto r = this.res;
+ if (r !is null)
+ this.res = next;
+ return Context(this.ti, this.fd, r);
+ }
+
+ /**
+ * Reset the context to a previous one, making any adjustment necessary
+ */
+ private void pop(ref Context prev)
+ {
+ this.res = prev.res;
+ }
+}
+
+private final class CppMangleVisitor : Visitor
+{
+ /// Context used when processing pre-semantic AST
+ private Context context;
+
+ ABITagContainer abiTags; /// Container for already-written ABI tags
+ Objects components; /// array of components available for substitution
+ OutBuffer* buf; /// append the mangling to buf[]
+ Loc loc; /// location for use in error messages
+
+ /**
+ * Constructor
+ *
+ * Params:
+ * buf = `OutBuffer` to write the mangling to
+ * loc = `Loc` of the symbol being mangled
+ */
+ this(OutBuffer* buf, Loc loc)
+ {
+ this.buf = buf;
+ this.loc = loc;
+ }
+
+ /*****
+ * Entry point. Append mangling to buf[]
+ * Params:
+ * s = symbol to mangle
+ */
+ void mangleOf(Dsymbol s)
+ {
+ if (VarDeclaration vd = s.isVarDeclaration())
+ {
+ mangle_variable(vd, vd.cppnamespace !is null);
+ }
+ else if (FuncDeclaration fd = s.isFuncDeclaration())
+ {
+ mangle_function(fd);
+ }
+ else
+ {
+ assert(0);
+ }
+ }
+
+ /**
+ * Mangle the return type of a function
+ *
+ * This is called on a templated function type.
+ * Context is set to the `FuncDeclaration`.
+ *
+ * Params:
+ * preSemantic = the `FuncDeclaration`'s `originalType`
+ */
+ void mangleReturnType(TypeFunction preSemantic)
+ {
+ auto tf = cast(TypeFunction)this.context.res.asFuncDecl().type;
+ Type rt = preSemantic.nextOf();
+ if (tf.isref)
+ rt = rt.referenceTo();
+ auto prev = this.context.push(tf.nextOf());
+ scope (exit) this.context.pop(prev);
+ this.headOfType(rt);
+ }
+
+ /**
+ * Write a seq-id from an index number, excluding the terminating '_'
+ *
+ * Params:
+ * idx = the index in a substitution list.
+ * Note that index 0 has no value, and `S0_` would be the
+ * substitution at index 1 in the list.
+ *
+ * See-Also:
+ * https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.seq-id
+ */
+ private void writeSequenceFromIndex(size_t idx)
+ {
+ if (idx)
+ {
+ void write_seq_id(size_t i)
+ {
+ if (i >= 36)
+ {
+ write_seq_id(i / 36);
+ i %= 36;
+ }
+ i += (i < 10) ? '0' : 'A' - 10;
+ buf.writeByte(cast(char)i);
+ }
+
+ write_seq_id(idx - 1);
+ }
+ }
+
+ /**
+ * Attempt to perform substitution on `p`
+ *
+ * If `p` already appeared in the mangling, it is stored as
+ * a 'part', and short references in the form of `SX_` can be used.
+ * Note that `p` can be anything: template declaration, struct declaration,
+ * class declaration, namespace...
+ *
+ * Params:
+ * p = The object to attempt to substitute
+ * nested = Whether or not `p` is to be considered nested.
+ * When `true`, `N` will be prepended before the substitution.
+ *
+ * Returns:
+ * Whether `p` already appeared in the mangling,
+ * and substitution has been written to `this.buf`.
+ */
+ bool substitute(RootObject p, bool nested = false)
+ {
+ //printf("substitute %s\n", p ? p.toChars() : null);
+ auto i = find(p);
+ if (i < 0)
+ return false;
+
+ //printf("\tmatch\n");
+ /* Sequence is S_, S0_, .., S9_, SA_, ..., SZ_, S10_, ...
+ */
+ if (nested)
+ buf.writeByte('N');
+ buf.writeByte('S');
+ writeSequenceFromIndex(i);
+ buf.writeByte('_');
+ return true;
+ }
+
+ /******
+ * See if `p` exists in components[]
+ *
+ * Note that components can contain `null` entries,
+ * as the index used in mangling is based on the index in the array.
+ *
+ * If called with an object whose dynamic type is `Nspace`,
+ * calls the `find(Nspace)` overload.
+ *
+ * Returns:
+ * index if found, -1 if not
+ */
+ int find(RootObject p)
+ {
+ //printf("find %p %d %s\n", p, p.dyncast(), p ? p.toChars() : null);
+ scope v = new ComponentVisitor(p);
+ foreach (i, component; components)
+ {
+ if (component)
+ component.visitObject(v);
+ if (v.result)
+ return cast(int)i;
+ }
+ return -1;
+ }
+
+ /*********************
+ * Append p to components[]
+ */
+ void append(RootObject p)
+ {
+ //printf("append %p %d %s\n", p, p.dyncast(), p ? p.toChars() : "null");
+ components.push(p);
+ }
+
+ /**
+ * Write an identifier preceded by its length
+ *
+ * Params:
+ * ident = `Identifier` to write to `this.buf`
+ */
+ void writeIdentifier(const ref Identifier ident)
+ {
+ const name = ident.toString();
+ this.buf.print(name.length);
+ this.buf.writestring(name);
+ }
+
+ /**
+ * Insert the leftover ABI tags to the buffer
+ *
+ * This inset ABI tags that hasn't already been written
+ * after the mangled name of the function.
+ * For more details, see the `abiTags` variable.
+ *
+ * Params:
+ * off = Offset to insert at
+ * fd = Type of the function to mangle the return type of
+ */
+ void writeRemainingTags(size_t off, TypeFunction tf)
+ {
+ scope remainingVisitor = new LeftoverVisitor(&this.abiTags.written);
+ tf.next.accept(remainingVisitor);
+ OutBuffer b2;
+ foreach (se; remainingVisitor.toWrite)
+ {
+ auto tag = se.peekString();
+ // We can only insert a slice, and each insert is a memmove,
+ // so use a temporary buffer to keep it efficient.
+ b2.reset();
+ b2.writestring("B");
+ b2.print(tag.length);
+ b2.writestring(tag);
+ this.buf.insert(off, b2[]);
+ off += b2.length;
+ }
+ }
+
+ /************************
+ * Determine if symbol is indeed the global ::std namespace.
+ * Params:
+ * s = symbol to check
+ * Returns:
+ * true if it is ::std
+ */
+ static bool isStd(Dsymbol s)
+ {
+ if (!s)
+ return false;
+
+ if (auto cnd = s.isCPPNamespaceDeclaration())
+ return isStd(cnd);
+
+ return (s.ident == Id.std && // the right name
+ s.isNspace() && // g++ disallows global "std" for other than a namespace
+ !getQualifier(s)); // at global level
+ }
+
+ /// Ditto
+ static bool isStd(CPPNamespaceDeclaration s)
+ {
+ return s && s.cppnamespace is null && s.ident == Id.std;
+ }
+
+ /************************
+ * Determine if type is a C++ fundamental type.
+ * Params:
+ * t = type to check
+ * Returns:
+ * true if it is a fundamental type
+ */
+ static bool isFundamentalType(Type t)
+ {
+ // First check the target whether some specific ABI is being followed.
+ bool isFundamental = void;
+ if (target.cpp.fundamentalType(t, isFundamental))
+ return isFundamental;
+
+ if (auto te = t.isTypeEnum())
+ {
+ // Peel off enum type from special types.
+ if (te.sym.isSpecial())
+ t = te.memType();
+ }
+
+ // Fundamental arithmetic types:
+ // 1. integral types: bool, char, int, ...
+ // 2. floating point types: float, double, real
+ // 3. void
+ // 4. null pointer: std::nullptr_t (since C++11)
+ if (t.ty == Tvoid || t.ty == Tbool)
+ return true;
+ else if (t.ty == Tnull && global.params.cplusplus >= CppStdRevision.cpp11)
+ return true;
+ else
+ return t.isTypeBasic() && (t.isintegral() || t.isreal());
+ }
+
+ /******************************
+ * Write the mangled representation of a template argument.
+ * Params:
+ * ti = the template instance
+ * arg = the template argument index
+ */
+ void template_arg(TemplateInstance ti, size_t arg)
+ {
+ TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration();
+ assert(td);
+ TemplateParameter tp = (*td.parameters)[arg];
+ RootObject o = (*ti.tiargs)[arg];
+
+ auto prev = this.context.push({
+ TemplateInstance parentti;
+ if (this.context.res.dyncast() == DYNCAST.dsymbol)
+ parentti = this.context.res.asFuncDecl().parent.isTemplateInstance();
+ else
+ parentti = this.context.res.asType().toDsymbol(null).parent.isTemplateInstance();
+ return (*parentti.tiargs)[arg];
+ }());
+ scope (exit) this.context.pop(prev);
+
+ if (tp.isTemplateTypeParameter())
+ {
+ Type t = isType(o);
+ assert(t);
+ t.accept(this);
+ }
+ else if (TemplateValueParameter tv = tp.isTemplateValueParameter())
+ {
+ // <expr-primary> ::= L <type> <value number> E # integer literal
+ if (tv.valType.isintegral())
+ {
+ Expression e = isExpression(o);
+ assert(e);
+ buf.writeByte('L');
+ tv.valType.accept(this);
+ auto val = e.toUInteger();
+ if (!tv.valType.isunsigned() && cast(sinteger_t)val < 0)
+ {
+ val = -val;
+ buf.writeByte('n');
+ }
+ buf.print(val);
+ buf.writeByte('E');
+ }
+ else
+ {
+ ti.error("Internal Compiler Error: C++ `%s` template value parameter is not supported", tv.valType.toChars());
+ fatal();
+ }
+ }
+ else if (tp.isTemplateAliasParameter())
+ {
+ // Passing a function as alias parameter is the same as passing
+ // `&function`
+ Dsymbol d = isDsymbol(o);
+ Expression e = isExpression(o);
+ if (d && d.isFuncDeclaration())
+ {
+ // X .. E => template parameter is an expression
+ // 'ad' => unary operator ('&')
+ // L .. E => is a <expr-primary>
+ buf.writestring("XadL");
+ mangle_function(d.isFuncDeclaration());
+ buf.writestring("EE");
+ }
+ else if (e && e.op == TOK.variable && (cast(VarExp)e).var.isVarDeclaration())
+ {
+ VarDeclaration vd = (cast(VarExp)e).var.isVarDeclaration();
+ buf.writeByte('L');
+ mangle_variable(vd, true);
+ buf.writeByte('E');
+ }
+ else if (d && d.isTemplateDeclaration() && d.isTemplateDeclaration().onemember)
+ {
+ if (!substitute(d))
+ {
+ cpp_mangle_name(d, false);
+ }
+ }
+ else
+ {
+ ti.error("Internal Compiler Error: C++ `%s` template alias parameter is not supported", o.toChars());
+ fatal();
+ }
+ }
+ else if (tp.isTemplateThisParameter())
+ {
+ ti.error("Internal Compiler Error: C++ `%s` template this parameter is not supported", o.toChars());
+ fatal();
+ }
+ else
+ {
+ assert(0);
+ }
+ }
+
+ /******************************
+ * Write the mangled representation of the template arguments.
+ * Params:
+ * ti = the template instance
+ * firstArg = index of the first template argument to mangle
+ * (used for operator overloading)
+ * Returns:
+ * true if any arguments were written
+ */
+ bool template_args(TemplateInstance ti, int firstArg = 0)
+ {
+ /* <template-args> ::= I <template-arg>+ E
+ */
+ if (!ti || ti.tiargs.dim <= firstArg) // could happen if std::basic_string is not a template
+ return false;
+ buf.writeByte('I');
+ foreach (i; firstArg .. ti.tiargs.dim)
+ {
+ TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration();
+ assert(td);
+ TemplateParameter tp = (*td.parameters)[i];
+
+ /*
+ * <template-arg> ::= <type> # type or template
+ * ::= X <expression> E # expression
+ * ::= <expr-primary> # simple expressions
+ * ::= J <template-arg>* E # argument pack
+ *
+ * Reference: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.template-arg
+ */
+ if (TemplateTupleParameter tt = tp.isTemplateTupleParameter())
+ {
+ buf.writeByte('J'); // argument pack
+
+ // mangle the rest of the arguments as types
+ foreach (j; i .. (*ti.tiargs).dim)
+ {
+ Type t = isType((*ti.tiargs)[j]);
+ assert(t);
+ t.accept(this);
+ }
+
+ buf.writeByte('E');
+ break;
+ }
+
+ template_arg(ti, i);
+ }
+ buf.writeByte('E');
+ return true;
+ }
+
+ /**
+ * Write the symbol `p` if not null, then execute the delegate
+ *
+ * Params:
+ * p = Symbol to write
+ * dg = Delegate to execute
+ */
+ void writeChained(Dsymbol p, scope void delegate() dg)
+ {
+ if (p && !p.isModule())
+ {
+ buf.writestring("N");
+ source_name(p, true);
+ dg();
+ buf.writestring("E");
+ }
+ else
+ dg();
+ }
+
+ /**
+ * Write the name of `s` to the buffer
+ *
+ * Params:
+ * s = Symbol to write the name of
+ * haveNE = Whether `N..E` is already part of the mangling
+ * Because `Nspace` and `CPPNamespaceAttribute` can be
+ * mixed, this is a mandatory hack.
+ */
+ void source_name(Dsymbol s, bool haveNE = false)
+ {
+ version (none)
+ {
+ printf("source_name(%s)\n", s.toChars());
+ auto sl = this.buf.peekSlice();
+ assert(sl.length == 0 || haveNE || s.cppnamespace is null || sl != "_ZN");
+ }
+ auto ti = s.isTemplateInstance();
+
+ if (!ti)
+ {
+ auto ag = s.isAggregateDeclaration();
+ const ident = (ag && ag.mangleOverride) ? ag.mangleOverride.id : s.ident;
+ this.writeNamespace(s.cppnamespace, () {
+ this.writeIdentifier(ident);
+ this.abiTags.writeSymbol(s, this);
+ },
+ haveNE);
+ return;
+ }
+
+ bool needsTa = false;
+
+ // https://issues.dlang.org/show_bug.cgi?id=20413
+ // N..E is not needed when substituting members of the std namespace.
+ // This is observed in the GCC and Clang implementations.
+ // The Itanium specification is not clear enough on this specific case.
+ // References:
+ // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.name
+ // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-compression
+ Dsymbol q = getQualifier(ti.tempdecl);
+ Dsymbol ns = ti.tempdecl.cppnamespace;
+ const inStd = ns && isStd(ns) || q && isStd(q);
+ const isNested = !inStd && (ns || q);
+
+ if (substitute(ti.tempdecl, !haveNE && isNested))
+ {
+ template_args(ti);
+ if (!haveNE && isNested)
+ buf.writeByte('E');
+ return;
+ }
+ else if (this.writeStdSubstitution(ti, needsTa))
+ {
+ this.abiTags.writeSymbol(ti, this);
+ if (needsTa)
+ template_args(ti);
+ return;
+ }
+
+ auto ag = ti.aliasdecl ? ti.aliasdecl.isAggregateDeclaration() : null;
+ if (ag && ag.mangleOverride)
+ {
+ this.writeNamespace(
+ ti.toAlias().cppnamespace, () {
+ this.writeIdentifier(ag.mangleOverride.id);
+ if (ag.mangleOverride.agg && ag.mangleOverride.agg.isInstantiated())
+ {
+ auto to = ag.mangleOverride.agg.isInstantiated();
+ append(to);
+ this.abiTags.writeSymbol(to.tempdecl, this);
+ template_args(to);
+ }
+ }, haveNE);
+ }
+ else
+ {
+ this.writeNamespace(
+ s.cppnamespace, () {
+ this.writeIdentifier(ti.tempdecl.toAlias().ident);
+ append(ti.tempdecl);
+ this.abiTags.writeSymbol(ti.tempdecl, this);
+ template_args(ti);
+ }, haveNE);
+ }
+ }
+
+ /********
+ * See if s is actually an instance of a template
+ * Params:
+ * s = symbol
+ * Returns:
+ * if s is instance of a template, return the instance, otherwise return s
+ */
+ static Dsymbol getInstance(Dsymbol s)
+ {
+ Dsymbol p = s.toParent();
+ if (p)
+ {
+ if (TemplateInstance ti = p.isTemplateInstance())
+ return ti;
+ }
+ return s;
+ }
+
+ /// Get the namespace of a template instance
+ CPPNamespaceDeclaration getTiNamespace(TemplateInstance ti)
+ {
+ // If we receive a pre-semantic `TemplateInstance`,
+ // `cppnamespace` is always `null`
+ return ti.tempdecl ? ti.cppnamespace
+ : this.context.res.asType().toDsymbol(null).cppnamespace;
+ }
+
+ /********
+ * Get qualifier for `s`, meaning the symbol
+ * that s is in the symbol table of.
+ * The module does not count as a qualifier, because C++
+ * does not have modules.
+ * Params:
+ * s = symbol that may have a qualifier
+ * s is rewritten to be TemplateInstance if s is one
+ * Returns:
+ * qualifier, null if none
+ */
+ static Dsymbol getQualifier(Dsymbol s)
+ {
+ Dsymbol p = s.toParent();
+ return (p && !p.isModule()) ? p : null;
+ }
+
+ // Detect type char
+ static bool isChar(RootObject o)
+ {
+ Type t = isType(o);
+ return (t && t.equals(Type.tchar));
+ }
+
+ // Detect type ::std::char_traits<char>
+ bool isChar_traits_char(RootObject o)
+ {
+ return isIdent_char(Id.char_traits, o);
+ }
+
+ // Detect type ::std::allocator<char>
+ bool isAllocator_char(RootObject o)
+ {
+ return isIdent_char(Id.allocator, o);
+ }
+
+ // Detect type ::std::ident<char>
+ bool isIdent_char(Identifier ident, RootObject o)
+ {
+ Type t = isType(o);
+ if (!t || t.ty != Tstruct)
+ return false;
+ Dsymbol s = (cast(TypeStruct)t).toDsymbol(null);
+ if (s.ident != ident)
+ return false;
+ Dsymbol p = s.toParent();
+ if (!p)
+ return false;
+ TemplateInstance ti = p.isTemplateInstance();
+ if (!ti)
+ return false;
+ Dsymbol q = getQualifier(ti);
+ const bool inStd = isStd(q) || isStd(this.getTiNamespace(ti));
+ return inStd && ti.tiargs.dim == 1 && isChar((*ti.tiargs)[0]);
+ }
+
+ /***
+ * Detect template args <char, ::std::char_traits<char>>
+ * and write st if found.
+ * Returns:
+ * true if found
+ */
+ bool char_std_char_traits_char(TemplateInstance ti, string st)
+ {
+ if (ti.tiargs.dim == 2 &&
+ isChar((*ti.tiargs)[0]) &&
+ isChar_traits_char((*ti.tiargs)[1]))
+ {
+ buf.writestring(st.ptr);
+ return true;
+ }
+ return false;
+ }
+
+
+ void prefix_name(Dsymbol s)
+ {
+ //printf("prefix_name(%s)\n", s.toChars());
+ if (substitute(s))
+ return;
+ if (isStd(s))
+ return buf.writestring("St");
+
+ auto si = getInstance(s);
+ Dsymbol p = getQualifier(si);
+ if (p)
+ {
+ if (isStd(p))
+ {
+ bool needsTa;
+ auto ti = si.isTemplateInstance();
+ if (this.writeStdSubstitution(ti, needsTa))
+ {
+ this.abiTags.writeSymbol(ti, this);
+ if (needsTa)
+ {
+ template_args(ti);
+ append(ti);
+ }
+ return;
+ }
+ buf.writestring("St");
+ }
+ else
+ prefix_name(p);
+ }
+ source_name(si, true);
+ if (!isStd(si))
+ /* Do this after the source_name() call to keep components[]
+ * in the right order.
+ * https://issues.dlang.org/show_bug.cgi?id=17947
+ */
+ append(si);
+ }
+
+ /**
+ * Write common substitution for standard types, such as std::allocator
+ *
+ * This function assumes that the symbol `ti` is in the namespace `std`.
+ *
+ * Params:
+ * ti = Template instance to consider
+ * needsTa = If this function returns `true`, this value indicates
+ * if additional template argument mangling is needed
+ *
+ * Returns:
+ * `true` if a special std symbol was found
+ */
+ bool writeStdSubstitution(TemplateInstance ti, out bool needsTa)
+ {
+ if (!ti)
+ return false;
+ if (!isStd(this.getTiNamespace(ti)) && !isStd(getQualifier(ti)))
+ return false;
+
+ if (ti.name == Id.allocator)
+ {
+ buf.writestring("Sa");
+ needsTa = true;
+ return true;
+ }
+ if (ti.name == Id.basic_string)
+ {
+ // ::std::basic_string<char, ::std::char_traits<char>, ::std::allocator<char>>
+ if (ti.tiargs.dim == 3 &&
+ isChar((*ti.tiargs)[0]) &&
+ isChar_traits_char((*ti.tiargs)[1]) &&
+ isAllocator_char((*ti.tiargs)[2]))
+
+ {
+ buf.writestring("Ss");
+ return true;
+ }
+ buf.writestring("Sb"); // ::std::basic_string
+ needsTa = true;
+ return true;
+ }
+
+ // ::std::basic_istream<char, ::std::char_traits<char>>
+ if (ti.name == Id.basic_istream &&
+ char_std_char_traits_char(ti, "Si"))
+ return true;
+
+ // ::std::basic_ostream<char, ::std::char_traits<char>>
+ if (ti.name == Id.basic_ostream &&
+ char_std_char_traits_char(ti, "So"))
+ return true;
+
+ // ::std::basic_iostream<char, ::std::char_traits<char>>
+ if (ti.name == Id.basic_iostream &&
+ char_std_char_traits_char(ti, "Sd"))
+ return true;
+
+ return false;
+ }
+
+ void cpp_mangle_name(Dsymbol s, bool qualified)
+ {
+ //printf("cpp_mangle_name(%s, %d)\n", s.toChars(), qualified);
+ Dsymbol p = s.toParent();
+ Dsymbol se = s;
+ bool write_prefix = true;
+ if (p && p.isTemplateInstance())
+ {
+ se = p;
+ if (find(p.isTemplateInstance().tempdecl) >= 0)
+ write_prefix = false;
+ p = p.toParent();
+ }
+ if (!p || p.isModule())
+ {
+ source_name(se, false);
+ append(s);
+ return;
+ }
+
+ if (!isStd(p) || qualified)
+ {
+ buf.writeByte('N');
+ if (write_prefix)
+ {
+ if (isStd(p))
+ buf.writestring("St");
+ else
+ prefix_name(p);
+ }
+ source_name(se, true);
+ buf.writeByte('E');
+ append(s);
+ return;
+ }
+ /* The N..E is not required if:
+ * 1. the parent is 'std'
+ * 2. 'std' is the initial qualifier
+ * 3. there is no CV-qualifier or a ref-qualifier for a member function
+ * ABI 5.1.8
+ */
+ TemplateInstance ti = se.isTemplateInstance();
+ if (s.ident == Id.allocator)
+ {
+ buf.writestring("Sa"); // "Sa" is short for ::std::allocator
+ template_args(ti);
+ }
+ else if (s.ident == Id.basic_string)
+ {
+ // ::std::basic_string<char, ::std::char_traits<char>, ::std::allocator<char>>
+ if (ti.tiargs.dim == 3 &&
+ isChar((*ti.tiargs)[0]) &&
+ isChar_traits_char((*ti.tiargs)[1]) &&
+ isAllocator_char((*ti.tiargs)[2]))
+ {
+ buf.writestring("Ss");
+ return;
+ }
+ buf.writestring("Sb"); // ::std::basic_string
+ template_args(ti);
+ }
+ else
+ {
+ // ::std::basic_istream<char, ::std::char_traits<char>>
+ if (s.ident == Id.basic_istream)
+ {
+ if (char_std_char_traits_char(ti, "Si"))
+ return;
+ }
+ else if (s.ident == Id.basic_ostream)
+ {
+ if (char_std_char_traits_char(ti, "So"))
+ return;
+ }
+ else if (s.ident == Id.basic_iostream)
+ {
+ if (char_std_char_traits_char(ti, "Sd"))
+ return;
+ }
+ buf.writestring("St");
+ source_name(se, true);
+ }
+ append(s);
+ }
+
+ /**
+ * Write CV-qualifiers to the buffer
+ *
+ * CV-qualifiers are 'r': restrict (unused in D), 'V': volatile, 'K': const
+ *
+ * See_Also:
+ * https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.CV-qualifiers
+ */
+ void CV_qualifiers(const Type t)
+ {
+ if (t.isConst())
+ buf.writeByte('K');
+ }
+
+ /**
+ * Mangles a variable
+ *
+ * Params:
+ * d = Variable declaration to mangle
+ * isNested = Whether this variable is nested, e.g. a template parameter
+ * or within a namespace
+ */
+ void mangle_variable(VarDeclaration d, bool isNested)
+ {
+ // fake mangling for fields to fix https://issues.dlang.org/show_bug.cgi?id=16525
+ if (!(d.storage_class & (STC.extern_ | STC.field | STC.gshared)))
+ {
+ d.error("Internal Compiler Error: C++ static non-`__gshared` non-`extern` variables not supported");
+ fatal();
+ }
+ Dsymbol p = d.toParent();
+ if (p && !p.isModule()) //for example: char Namespace1::beta[6] should be mangled as "_ZN10Namespace14betaE"
+ {
+ buf.writestring("_ZN");
+ prefix_name(p);
+ source_name(d, true);
+ buf.writeByte('E');
+ }
+ else if (isNested)
+ {
+ buf.writestring("_Z");
+ source_name(d, false);
+ }
+ else
+ {
+ if (auto varTags = ABITagContainer.forSymbol(d))
+ {
+ buf.writestring("_Z");
+ source_name(d, false);
+ return;
+ }
+ if (auto typeTags = ABITagContainer.forSymbol(d.type.toDsymbol(null)))
+ {
+ buf.writestring("_Z");
+ source_name(d, false);
+ this.abiTags.write(*this.buf, typeTags);
+ return;
+ }
+ //char beta[6] should mangle as "beta"
+ buf.writestring(d.ident.toString());
+ }
+ }
+
+ void mangle_function(FuncDeclaration d)
+ {
+ //printf("mangle_function(%s)\n", d.toChars());
+ /*
+ * <mangled-name> ::= _Z <encoding>
+ */
+ buf.writestring("_Z");
+ this.mangle_function_encoding(d);
+ }
+
+ void mangle_function_encoding(FuncDeclaration d)
+ {
+ //printf("mangle_function_encoding(%s)\n", d.toChars());
+ /*
+ * <encoding> ::= <function name> <bare-function-type>
+ * ::= <data name>
+ * ::= <special-name>
+ */
+ TypeFunction tf = cast(TypeFunction)d.type;
+
+ if (TemplateDeclaration ftd = getFuncTemplateDecl(d))
+ {
+ /* It's an instance of a function template
+ */
+ TemplateInstance ti = d.parent.isTemplateInstance();
+ assert(ti);
+ this.mangleTemplatedFunction(d, tf, ftd, ti);
+ return;
+ }
+
+ Dsymbol p = d.toParent();
+ if (p && !p.isModule() && tf.linkage == LINK.cpp)
+ {
+ this.mangleNestedFuncPrefix(tf, p);
+
+ if (auto ctor = d.isCtorDeclaration())
+ buf.writestring(ctor.isCpCtor ? "C2" : "C1");
+ else if (d.isPrimaryDtor())
+ buf.writestring("D1");
+ else if (d.ident && d.ident == Id.assign)
+ buf.writestring("aS");
+ else if (d.ident && d.ident == Id.eq)
+ buf.writestring("eq");
+ else if (d.ident && d.ident == Id.index)
+ buf.writestring("ix");
+ else if (d.ident && d.ident == Id.call)
+ buf.writestring("cl");
+ else
+ source_name(d, true);
+ buf.writeByte('E');
+ }
+ else
+ {
+ source_name(d, false);
+ }
+
+ // Save offset for potentially writing tags
+ const size_t off = this.buf.length();
+
+ // Template args accept extern "C" symbols with special mangling
+ if (tf.linkage == LINK.cpp)
+ mangleFunctionParameters(tf.parameterList);
+
+ if (!tf.next.isTypeBasic())
+ this.writeRemainingTags(off, tf);
+ }
+
+ /**
+ * Recursively mangles a non-scoped namespace
+ *
+ * Parameters:
+ * ns = Namespace to mangle
+ * dg = A delegate to write the identifier in this namespace
+ * haveNE = When `false` (the default), surround the namespace / dg
+ * call with nested name qualifier (`N..E`).
+ * Otherwise, they are already present (e.g. `Nspace` was used).
+ */
+ void writeNamespace(CPPNamespaceDeclaration ns, scope void delegate() dg,
+ bool haveNE = false)
+ {
+ void runDg () { if (dg !is null) dg(); }
+
+ if (ns is null || ns.ident is null)
+ return runDg();
+
+ if (isStd(ns))
+ {
+ if (!substitute(ns))
+ buf.writestring("St");
+ runDg();
+ }
+ else if (dg !is null)
+ {
+ if (!haveNE)
+ buf.writestring("N");
+ if (!substitute(ns))
+ {
+ this.writeNamespace(ns.cppnamespace, null);
+ this.writeIdentifier(ns.ident);
+ append(ns);
+ }
+ dg();
+ if (!haveNE)
+ buf.writestring("E");
+ }
+ else if (!substitute(ns))
+ {
+ this.writeNamespace(ns.cppnamespace, null);
+ this.writeIdentifier(ns.ident);
+ append(ns);
+ }
+ }
+
+ /**
+ * Mangles a function template to C++
+ *
+ * Params:
+ * d = Function declaration
+ * tf = Function type (casted d.type)
+ * ftd = Template declaration (ti.templdecl)
+ * ti = Template instance (d.parent)
+ */
+ void mangleTemplatedFunction(FuncDeclaration d, TypeFunction tf,
+ TemplateDeclaration ftd, TemplateInstance ti)
+ {
+ Dsymbol p = ti.toParent();
+ // Check if this function is *not* nested
+ if (!p || p.isModule() || tf.linkage != LINK.cpp)
+ {
+ this.context.ti = ti;
+ this.context.fd = d;
+ this.context.res = d;
+ TypeFunction preSemantic = cast(TypeFunction)d.originalType;
+ auto nspace = ti.toParent();
+ if (nspace && nspace.isNspace())
+ this.writeChained(ti.toParent(), () => source_name(ti, true));
+ else
+ source_name(ti, false);
+ this.mangleReturnType(preSemantic);
+ this.mangleFunctionParameters(ParameterList(preSemantic.parameterList.parameters, tf.parameterList.varargs));
+ return;
+ }
+
+ // It's a nested function (e.g. a member of an aggregate)
+ this.mangleNestedFuncPrefix(tf, p);
+
+ if (d.isCtorDeclaration())
+ {
+ buf.writestring("C1");
+ mangleFunctionParameters(tf.parameterList);
+ return;
+ }
+ else if (d.isPrimaryDtor())
+ {
+ buf.writestring("D1");
+ mangleFunctionParameters(tf.parameterList);
+ return;
+ }
+
+ int firstTemplateArg = 0;
+ bool appendReturnType = true;
+ bool isConvertFunc = false;
+ string symName;
+
+ // test for special symbols
+ CppOperator whichOp = isCppOperator(ti.name);
+ final switch (whichOp)
+ {
+ case CppOperator.Unknown:
+ break;
+ case CppOperator.Cast:
+ symName = "cv";
+ firstTemplateArg = 1;
+ isConvertFunc = true;
+ appendReturnType = false;
+ break;
+ case CppOperator.Assign:
+ symName = "aS";
+ break;
+ case CppOperator.Eq:
+ symName = "eq";
+ break;
+ case CppOperator.Index:
+ symName = "ix";
+ break;
+ case CppOperator.Call:
+ symName = "cl";
+ break;
+ case CppOperator.Unary:
+ case CppOperator.Binary:
+ case CppOperator.OpAssign:
+ TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration();
+ assert(td);
+ assert(ti.tiargs.dim >= 1);
+ TemplateParameter tp = (*td.parameters)[0];
+ TemplateValueParameter tv = tp.isTemplateValueParameter();
+ if (!tv || !tv.valType.isString())
+ break; // expecting a string argument to operators!
+ Expression exp = (*ti.tiargs)[0].isExpression();
+ StringExp str = exp.toStringExp();
+ switch (whichOp)
+ {
+ case CppOperator.Unary:
+ switch (str.peekString())
+ {
+ case "*": symName = "de"; goto continue_template;
+ case "++": symName = "pp"; goto continue_template;
+ case "--": symName = "mm"; goto continue_template;
+ case "-": symName = "ng"; goto continue_template;
+ case "+": symName = "ps"; goto continue_template;
+ case "~": symName = "co"; goto continue_template;
+ default: break;
+ }
+ break;
+ case CppOperator.Binary:
+ switch (str.peekString())
+ {
+ case ">>": symName = "rs"; goto continue_template;
+ case "<<": symName = "ls"; goto continue_template;
+ case "*": symName = "ml"; goto continue_template;
+ case "-": symName = "mi"; goto continue_template;
+ case "+": symName = "pl"; goto continue_template;
+ case "&": symName = "an"; goto continue_template;
+ case "/": symName = "dv"; goto continue_template;
+ case "%": symName = "rm"; goto continue_template;
+ case "^": symName = "eo"; goto continue_template;
+ case "|": symName = "or"; goto continue_template;
+ default: break;
+ }
+ break;
+ case CppOperator.OpAssign:
+ switch (str.peekString())
+ {
+ case "*": symName = "mL"; goto continue_template;
+ case "+": symName = "pL"; goto continue_template;
+ case "-": symName = "mI"; goto continue_template;
+ case "/": symName = "dV"; goto continue_template;
+ case "%": symName = "rM"; goto continue_template;
+ case ">>": symName = "rS"; goto continue_template;
+ case "<<": symName = "lS"; goto continue_template;
+ case "&": symName = "aN"; goto continue_template;
+ case "|": symName = "oR"; goto continue_template;
+ case "^": symName = "eO"; goto continue_template;
+ default: break;
+ }
+ break;
+ default:
+ assert(0);
+ continue_template:
+ firstTemplateArg = 1;
+ break;
+ }
+ break;
+ }
+ if (symName.length == 0)
+ source_name(ti, true);
+ else
+ {
+ buf.writestring(symName);
+ if (isConvertFunc)
+ template_arg(ti, 0);
+ appendReturnType = template_args(ti, firstTemplateArg) && appendReturnType;
+ }
+ buf.writeByte('E');
+ if (appendReturnType)
+ headOfType(tf.nextOf()); // mangle return type
+ mangleFunctionParameters(tf.parameterList);
+ }
+
+ /**
+ * Mangle the parameters of a function
+ *
+ * For templated functions, `context.res` is set to the `FuncDeclaration`
+ *
+ * Params:
+ * parameters = Array of `Parameter` to mangle
+ * varargs = if != 0, this function has varargs parameters
+ */
+ void mangleFunctionParameters(ParameterList parameterList)
+ {
+ int numparams = 0;
+
+ foreach (n, fparam; parameterList)
+ {
+ Type t = target.cpp.parameterType(fparam);
+ if (t.ty == Tsarray)
+ {
+ // Static arrays in D are passed by value; no counterpart in C++
+ .error(loc, "Internal Compiler Error: unable to pass static array `%s` to extern(C++) function, use pointer instead",
+ t.toChars());
+ fatal();
+ }
+ auto prev = this.context.push({
+ TypeFunction tf;
+ if (isDsymbol(this.context.res))
+ tf = cast(TypeFunction)this.context.res.asFuncDecl().type;
+ else
+ tf = this.context.res.asType().isTypeFunction();
+ assert(tf);
+ return (*tf.parameterList.parameters)[n].type;
+ }());
+ scope (exit) this.context.pop(prev);
+
+ if (this.context.ti && global.params.cplusplus >= CppStdRevision.cpp11)
+ handleParamPack(t, this.context.ti.tempdecl.isTemplateDeclaration().parameters);
+
+ headOfType(t);
+ ++numparams;
+ }
+
+ if (parameterList.varargs == VarArg.variadic)
+ buf.writeByte('z');
+ else if (!numparams)
+ buf.writeByte('v'); // encode (void) parameters
+ }
+
+ /****** The rest is type mangling ************/
+
+ void error(Type t)
+ {
+ const(char)* p;
+ if (t.isImmutable())
+ p = "`immutable` ";
+ else if (t.isShared())
+ p = "`shared` ";
+ else
+ p = "";
+ .error(loc, "Internal Compiler Error: %stype `%s` cannot be mapped to C++\n", p, t.toChars());
+ fatal(); //Fatal, because this error should be handled in frontend
+ }
+
+ /****************************
+ * Mangle a type,
+ * treating it as a Head followed by a Tail.
+ * Params:
+ * t = Head of a type
+ */
+ void headOfType(Type t)
+ {
+ if (t.ty == Tclass)
+ {
+ mangleTypeClass(cast(TypeClass)t, true);
+ }
+ else
+ {
+ // For value types, strip const/immutable/shared from the head of the type
+ auto prev = this.context.push(this.context.res.asType().mutableOf().unSharedOf());
+ scope (exit) this.context.pop(prev);
+ t.mutableOf().unSharedOf().accept(this);
+ }
+ }
+
+ /******
+ * Write out 1 or 2 character basic type mangling.
+ * Handle const and substitutions.
+ * Params:
+ * t = type to mangle
+ * p = if not 0, then character prefix
+ * c = mangling character
+ */
+ void writeBasicType(Type t, char p, char c)
+ {
+ // Only do substitutions for non-fundamental types.
+ if (!isFundamentalType(t) || t.isConst())
+ {
+ if (substitute(t))
+ return;
+ else
+ append(t);
+ }
+ CV_qualifiers(t);
+ if (p)
+ buf.writeByte(p);
+ buf.writeByte(c);
+ }
+
+
+ /****************
+ * Write structs and enums.
+ * Params:
+ * t = TypeStruct or TypeEnum
+ */
+ void doSymbol(Type t)
+ {
+ if (substitute(t))
+ return;
+ CV_qualifiers(t);
+
+ // Handle any target-specific struct types.
+ if (auto tm = target.cpp.typeMangle(t))
+ {
+ buf.writestring(tm);
+ }
+ else
+ {
+ Dsymbol s = t.toDsymbol(null);
+ Dsymbol p = s.toParent();
+ if (p && p.isTemplateInstance())
+ {
+ /* https://issues.dlang.org/show_bug.cgi?id=17947
+ * Substitute the template instance symbol, not the struct/enum symbol
+ */
+ if (substitute(p))
+ return;
+ }
+ if (!substitute(s))
+ cpp_mangle_name(s, false);
+ }
+ if (t.isConst())
+ append(t);
+ }
+
+
+
+ /************************
+ * Mangle a class type.
+ * If it's the head, treat the initial pointer as a value type.
+ * Params:
+ * t = class type
+ * head = true for head of a type
+ */
+ void mangleTypeClass(TypeClass t, bool head)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ /* Mangle as a <pointer to><struct>
+ */
+ if (substitute(t))
+ return;
+ if (!head)
+ CV_qualifiers(t);
+ buf.writeByte('P');
+
+ CV_qualifiers(t);
+
+ {
+ Dsymbol s = t.toDsymbol(null);
+ Dsymbol p = s.toParent();
+ if (p && p.isTemplateInstance())
+ {
+ /* https://issues.dlang.org/show_bug.cgi?id=17947
+ * Substitute the template instance symbol, not the class symbol
+ */
+ if (substitute(p))
+ return;
+ }
+ }
+
+ if (!substitute(t.sym))
+ {
+ cpp_mangle_name(t.sym, false);
+ }
+ if (t.isConst())
+ append(null); // C++ would have an extra type here
+ append(t);
+ }
+
+ /**
+ * Mangle the prefix of a nested (e.g. member) function
+ *
+ * Params:
+ * tf = Type of the nested function
+ * parent = Parent in which the function is nested
+ */
+ void mangleNestedFuncPrefix(TypeFunction tf, Dsymbol parent)
+ {
+ /* <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E
+ * ::= N [<CV-qualifiers>] <template-prefix> <template-args> E
+ */
+ buf.writeByte('N');
+ CV_qualifiers(tf);
+
+ /* <prefix> ::= <prefix> <unqualified-name>
+ * ::= <template-prefix> <template-args>
+ * ::= <template-param>
+ * ::= # empty
+ * ::= <substitution>
+ * ::= <prefix> <data-member-prefix>
+ */
+ prefix_name(parent);
+ }
+
+ /**
+ * Write `Dp` (C++11 function parameter pack prefix) if 't' is a TemplateSequenceParameter (T...).
+ *
+ * Params:
+ * t = Parameter type
+ * params = Template parameters of the function
+ */
+ private void handleParamPack(Type t, TemplateParameters* params)
+ {
+ if (t.isTypeReference())
+ t = t.nextOf();
+ auto ti = t.isTypeIdentifier();
+ if (!ti)
+ return;
+
+ auto idx = templateParamIndex(ti.ident, params);
+ if (idx < params.length && (*params)[idx].isTemplateTupleParameter())
+ buf.writestring("Dp");
+ }
+
+ /**
+ * Helper function to write a `T..._` template index.
+ *
+ * Params:
+ * idx = Index of `param` in the template argument list
+ * param = Template parameter to mangle
+ */
+ private void writeTemplateArgIndex(size_t idx, TemplateParameter param)
+ {
+ // expressions are mangled in <X..E>
+ if (param.isTemplateValueParameter())
+ buf.writeByte('X');
+ buf.writeByte('T');
+ writeSequenceFromIndex(idx);
+ buf.writeByte('_');
+ if (param.isTemplateValueParameter())
+ buf.writeByte('E');
+ }
+
+ /**
+ * Given an array of template parameters and an identifier,
+ * returns the index of the identifier in that array.
+ *
+ * Params:
+ * ident = Identifier for which substitution is attempted
+ * (e.g. `void func(T)(T param)` => `T` from `T param`)
+ * params = `TemplateParameters` of the enclosing symbol
+ * (in the previous example, `func`'s template parameters)
+ *
+ * Returns:
+ * The index of the identifier match in `params`,
+ * or `params.length` if there wasn't any match.
+ */
+ private static size_t templateParamIndex(
+ const ref Identifier ident, TemplateParameters* params)
+ {
+ foreach (idx, param; *params)
+ if (param.ident == ident)
+ return idx;
+ return params.length;
+ }
+
+ /**
+ * Given a template instance `t`, write its qualified name
+ * without the template parameter list
+ *
+ * Params:
+ * t = Post-parsing `TemplateInstance` pointing to the symbol
+ * to mangle (one level deep)
+ * dg = Delegate to execute after writing the qualified symbol
+ *
+ */
+ private void writeQualified(TemplateInstance t, scope void delegate() dg)
+ {
+ auto type = isType(this.context.res);
+ if (!type)
+ {
+ this.writeIdentifier(t.name);
+ return dg();
+ }
+ auto sym1 = type.toDsymbol(null);
+ if (!sym1)
+ {
+ this.writeIdentifier(t.name);
+ return dg();
+ }
+ // Get the template instance
+ auto sym = getQualifier(sym1);
+ auto sym2 = getQualifier(sym);
+ if (sym2 && isStd(sym2)) // Nspace path
+ {
+ bool unused;
+ assert(sym.isTemplateInstance());
+ if (this.writeStdSubstitution(sym.isTemplateInstance(), unused))
+ return dg();
+ // std names don't require `N..E`
+ buf.writestring("St");
+ this.writeIdentifier(t.name);
+ this.append(t);
+ return dg();
+ }
+ else if (sym2)
+ {
+ buf.writestring("N");
+ if (!this.substitute(sym2))
+ sym2.accept(this);
+ }
+ this.writeNamespace(
+ sym1.cppnamespace, () {
+ this.writeIdentifier(t.name);
+ this.append(t);
+ dg();
+ });
+ if (sym2)
+ buf.writestring("E");
+ }
+
+extern(C++):
+
+ alias visit = Visitor.visit;
+
+ override void visit(TypeNull t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ writeBasicType(t, 'D', 'n');
+ }
+
+ override void visit(TypeNoreturn t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ writeBasicType(t, 0, 'v'); // mangle like `void`
+ }
+
+ override void visit(TypeBasic t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ // Handle any target-specific basic types.
+ if (auto tm = target.cpp.typeMangle(t))
+ {
+ // Only do substitutions for non-fundamental types.
+ if (!isFundamentalType(t) || t.isConst())
+ {
+ if (substitute(t))
+ return;
+ else
+ append(t);
+ }
+ CV_qualifiers(t);
+ buf.writestring(tm);
+ return;
+ }
+
+ /* <builtin-type>:
+ * v void
+ * w wchar_t
+ * b bool
+ * c char
+ * a signed char
+ * h unsigned char
+ * s short
+ * t unsigned short
+ * i int
+ * j unsigned int
+ * l long
+ * m unsigned long
+ * x long long, __int64
+ * y unsigned long long, __int64
+ * n __int128
+ * o unsigned __int128
+ * f float
+ * d double
+ * e long double, __float80
+ * g __float128
+ * z ellipsis
+ * Dd 64 bit IEEE 754r decimal floating point
+ * De 128 bit IEEE 754r decimal floating point
+ * Df 32 bit IEEE 754r decimal floating point
+ * Dh 16 bit IEEE 754r half-precision floating point
+ * Di char32_t
+ * Ds char16_t
+ * u <source-name> # vendor extended type
+ */
+ char c;
+ char p = 0;
+ switch (t.ty)
+ {
+ case Tvoid: c = 'v'; break;
+ case Tint8: c = 'a'; break;
+ case Tuns8: c = 'h'; break;
+ case Tint16: c = 's'; break;
+ case Tuns16: c = 't'; break;
+ case Tint32: c = 'i'; break;
+ case Tuns32: c = 'j'; break;
+ case Tfloat32: c = 'f'; break;
+ case Tint64:
+ c = target.c.longsize == 8 ? 'l' : 'x';
+ break;
+ case Tuns64:
+ c = target.c.longsize == 8 ? 'm' : 'y';
+ break;
+ case Tint128: c = 'n'; break;
+ case Tuns128: c = 'o'; break;
+ case Tfloat64: c = 'd'; break;
+ case Tfloat80: c = 'e'; break;
+ case Tbool: c = 'b'; break;
+ case Tchar: c = 'c'; break;
+ case Twchar: p = 'D'; c = 's'; break; // since C++11
+ case Tdchar: p = 'D'; c = 'i'; break; // since C++11
+ case Timaginary32: p = 'G'; c = 'f'; break; // 'G' means imaginary
+ case Timaginary64: p = 'G'; c = 'd'; break;
+ case Timaginary80: p = 'G'; c = 'e'; break;
+ case Tcomplex32: p = 'C'; c = 'f'; break; // 'C' means complex
+ case Tcomplex64: p = 'C'; c = 'd'; break;
+ case Tcomplex80: p = 'C'; c = 'e'; break;
+
+ default:
+ return error(t);
+ }
+ writeBasicType(t, p, c);
+ }
+
+ override void visit(TypeVector t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ if (substitute(t))
+ return;
+ append(t);
+ CV_qualifiers(t);
+
+ // Handle any target-specific vector types.
+ if (auto tm = target.cpp.typeMangle(t))
+ {
+ buf.writestring(tm);
+ }
+ else
+ {
+ assert(t.basetype && t.basetype.ty == Tsarray);
+ auto tsa = t.basetype.isTypeSArray();
+ assert(tsa.dim);
+ buf.writestring("Dv"); // -- Gnu ABI v.4
+ buf.print(tsa.dim.toInteger());
+ buf.writeByte('_');
+ t.basetype.nextOf().accept(this);
+ }
+ }
+
+ override void visit(TypeSArray t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ if (!substitute(t))
+ append(t);
+ CV_qualifiers(t);
+ buf.writeByte('A');
+ buf.print(t.dim ? t.dim.toInteger() : 0);
+ buf.writeByte('_');
+ t.next.accept(this);
+ }
+
+ override void visit(TypePointer t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ // Check for const - Since we cannot represent C++'s `char* const`,
+ // and `const char* const` (a.k.a `const(char*)` in D) is mangled
+ // the same as `const char*` (`const(char)*` in D), we need to add
+ // an extra `K` if `nextOf()` is `const`, before substitution
+ CV_qualifiers(t);
+ if (substitute(t))
+ return;
+ buf.writeByte('P');
+ auto prev = this.context.push(this.context.res.asType().nextOf());
+ scope (exit) this.context.pop(prev);
+ t.next.accept(this);
+ append(t);
+ }
+
+ override void visit(TypeReference t)
+ {
+ if (substitute(t))
+ return;
+ buf.writeByte('R');
+ CV_qualifiers(t.nextOf());
+ headOfType(t.nextOf());
+ if (t.nextOf().isConst())
+ append(t.nextOf());
+ append(t);
+ }
+
+ override void visit(TypeFunction t)
+ {
+ /*
+ * <function-type> ::= F [Y] <bare-function-type> E
+ * <bare-function-type> ::= <signature type>+
+ * # types are possible return type, then parameter types
+ */
+ /* ABI says:
+ "The type of a non-static member function is considered to be different,
+ for the purposes of substitution, from the type of a namespace-scope or
+ static member function whose type appears similar. The types of two
+ non-static member functions are considered to be different, for the
+ purposes of substitution, if the functions are members of different
+ classes. In other words, for the purposes of substitution, the class of
+ which the function is a member is considered part of the type of
+ function."
+
+ BUG: Right now, types of functions are never merged, so our simplistic
+ component matcher always finds them to be different.
+ We should use Type.equals on these, and use different
+ TypeFunctions for non-static member functions, and non-static
+ member functions of different classes.
+ */
+ if (substitute(t))
+ return;
+ buf.writeByte('F');
+ if (t.linkage == LINK.c)
+ buf.writeByte('Y');
+ Type tn = t.next;
+ if (t.isref)
+ tn = tn.referenceTo();
+ tn.accept(this);
+ mangleFunctionParameters(t.parameterList);
+ buf.writeByte('E');
+ append(t);
+ }
+
+ override void visit(TypeStruct t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+ //printf("TypeStruct %s\n", t.toChars());
+ doSymbol(t);
+ }
+
+ override void visit(TypeEnum t)
+ {
+ if (t.isImmutable() || t.isShared())
+ return error(t);
+
+ /* __c_(u)long(long) and others get special mangling
+ */
+ const id = t.sym.ident;
+ //printf("enum id = '%s'\n", id.toChars());
+ if (id == Id.__c_long)
+ return writeBasicType(t, 0, 'l');
+ else if (id == Id.__c_ulong)
+ return writeBasicType(t, 0, 'm');
+ else if (id == Id.__c_wchar_t)
+ return writeBasicType(t, 0, 'w');
+ else if (id == Id.__c_longlong)
+ return writeBasicType(t, 0, 'x');
+ else if (id == Id.__c_ulonglong)
+ return writeBasicType(t, 0, 'y');
+ else if (id == Id.__c_complex_float)
+ return writeBasicType(t, 'C', 'f');
+ else if (id == Id.__c_complex_double)
+ return writeBasicType(t, 'C', 'd');
+ else if (id == Id.__c_complex_real)
+ return writeBasicType(t, 'C', 'e');
+
+ doSymbol(t);
+ }
+
+ override void visit(TypeClass t)
+ {
+ mangleTypeClass(t, false);
+ }
+
+ /**
+ * Performs template parameter substitution
+ *
+ * Mangling is performed on a copy of the post-parsing AST before
+ * any semantic pass is run.
+ * There is no easy way to link a type to the template parameters
+ * once semantic has run, because:
+ * - the `TemplateInstance` installs aliases in its scope to its params
+ * - `AliasDeclaration`s are resolved in many places
+ * - semantic passes are destructive, so the `TypeIdentifier` gets lost
+ *
+ * As a result, the best approach with the current architecture is to:
+ * - Run the visitor on the `originalType` of the function,
+ * looking up any `TypeIdentifier` at the template scope when found.
+ * - Fallback to the post-semantic `TypeFunction` when the identifier is
+ * not a template parameter.
+ */
+ override void visit(TypeIdentifier t)
+ {
+ auto decl = cast(TemplateDeclaration)this.context.ti.tempdecl;
+ assert(decl.parameters !is null);
+ auto idx = templateParamIndex(t.ident, decl.parameters);
+ // If not found, default to the post-semantic type
+ if (idx >= decl.parameters.length)
+ return this.context.res.visitObject(this);
+
+ auto param = (*decl.parameters)[idx];
+ if (auto type = this.context.res.isType())
+ CV_qualifiers(type);
+ // Otherwise, attempt substitution (`S_` takes precedence on `T_`)
+ if (this.substitute(param))
+ return;
+
+ // If substitution failed, write `TX_` where `X` is the index
+ this.writeTemplateArgIndex(idx, param);
+ this.append(param);
+ // Write the ABI tags, if any
+ if (auto sym = this.context.res.isDsymbol())
+ this.abiTags.writeSymbol(sym, this);
+ }
+
+ /// Ditto
+ override void visit(TypeInstance t)
+ {
+ assert(t.tempinst !is null);
+ t.tempinst.accept(this);
+ }
+
+ /**
+ * Mangles a `TemplateInstance`
+ *
+ * A `TemplateInstance` can be found either in the parameter,
+ * or the return value.
+ * Arguments to the template instance needs to be mangled but the template
+ * can be partially substituted, so for example the following:
+ * `Container!(T, Val) func16479_12 (alias Container, T, int Val) ()`
+ * will mangle the return value part to "T_IT0_XT1_EE"
+ */
+ override void visit(TemplateInstance t)
+ {
+ // Template names are substituted, but args still need to be written
+ void writeArgs ()
+ {
+ buf.writeByte('I');
+ // When visiting the arguments, the context will be set to the
+ // resolved type
+ auto analyzed_ti = this.context.res.asType().toDsymbol(null).isInstantiated();
+ auto prev = this.context;
+ scope (exit) this.context.pop(prev);
+ foreach (idx, RootObject o; *t.tiargs)
+ {
+ this.context.res = (*analyzed_ti.tiargs)[idx];
+ o.visitObject(this);
+ }
+ if (analyzed_ti.tiargs.dim > t.tiargs.dim)
+ {
+ // If the resolved AST has more args than the parse one,
+ // we have default arguments
+ auto oparams = (cast(TemplateDeclaration)analyzed_ti.tempdecl).origParameters;
+ foreach (idx, arg; (*oparams)[t.tiargs.dim .. $])
+ {
+ this.context.res = (*analyzed_ti.tiargs)[idx + t.tiargs.dim];
+
+ if (auto ttp = arg.isTemplateTypeParameter())
+ ttp.defaultType.accept(this);
+ else if (auto tvp = arg.isTemplateValueParameter())
+ tvp.defaultValue.accept(this);
+ else if (auto tvp = arg.isTemplateThisParameter())
+ tvp.defaultType.accept(this);
+ else if (auto tvp = arg.isTemplateAliasParameter())
+ tvp.defaultAlias.visitObject(this);
+ else
+ assert(0, arg.toString());
+ }
+ }
+ buf.writeByte('E');
+ }
+
+ // `name` is used, not `ident`
+ assert(t.name !is null);
+ assert(t.tiargs !is null);
+
+ bool needsTa;
+ auto decl = cast(TemplateDeclaration)this.context.ti.tempdecl;
+ // Attempt to substitute the template itself
+ auto idx = templateParamIndex(t.name, decl.parameters);
+ if (idx < decl.parameters.length)
+ {
+ auto param = (*decl.parameters)[idx];
+ if (auto type = t.getType())
+ CV_qualifiers(type);
+ if (this.substitute(param))
+ return;
+ this.writeTemplateArgIndex(idx, param);
+ this.append(param);
+ writeArgs();
+ }
+ else if (this.writeStdSubstitution(t, needsTa))
+ {
+ if (needsTa)
+ writeArgs();
+ }
+ else if (!this.substitute(t))
+ this.writeQualified(t, &writeArgs);
+ }
+
+ /// Ditto
+ override void visit(IntegerExp t)
+ {
+ this.buf.writeByte('L');
+ t.type.accept(this);
+ this.buf.print(t.getInteger());
+ this.buf.writeByte('E');
+ }
+
+ override void visit(Nspace t)
+ {
+ if (auto p = getQualifier(t))
+ p.accept(this);
+
+ if (isStd(t))
+ buf.writestring("St");
+ else
+ {
+ this.writeIdentifier(t.ident);
+ this.append(t);
+ }
+ }
+
+ override void visit(Type t)
+ {
+ error(t);
+ }
+
+ void visit(Tuple t)
+ {
+ assert(0);
+ }
+}
+
+/// Helper code to visit `RootObject`, as it doesn't define `accept`,
+/// only its direct subtypes do.
+private void visitObject(V : Visitor)(RootObject o, V this_)
+{
+ assert(o !is null);
+ if (Type ta = isType(o))
+ ta.accept(this_);
+ else if (Expression ea = isExpression(o))
+ ea.accept(this_);
+ else if (Dsymbol sa = isDsymbol(o))
+ sa.accept(this_);
+ else if (TemplateParameter t = isTemplateParameter(o))
+ t.accept(this_);
+ else if (Tuple t = isTuple(o))
+ // `Tuple` inherits `RootObject` and does not define accept
+ // For this reason, this uses static dispatch on the visitor
+ this_.visit(t);
+ else
+ assert(0, o.toString());
+}
+
+/// Helper function to safely get a type out of a `RootObject`
+private Type asType(RootObject o)
+{
+ Type ta = isType(o);
+ // When called with context.res as argument, it can be `FuncDeclaration`
+ if (!ta && o.asFuncDecl())
+ ta = (cast(FuncDeclaration)o).type;
+ assert(ta !is null, o.toString());
+ return ta;
+}
+
+/// Helper function to safely get a `FuncDeclaration` out of a `RootObject`
+private FuncDeclaration asFuncDecl(RootObject o)
+{
+ Dsymbol d = isDsymbol(o);
+ assert(d !is null);
+ auto fd = d.isFuncDeclaration();
+ assert(fd !is null);
+ return fd;
+}
+
+/// Helper class to compare entries in components
+private extern(C++) final class ComponentVisitor : Visitor
+{
+ /// Only one of the following is not `null`, it's always
+ /// the most specialized type, set from the ctor
+ private Nspace namespace;
+
+ /// Ditto
+ private CPPNamespaceDeclaration namespace2;
+
+ /// Ditto
+ private TypePointer tpointer;
+
+ /// Ditto
+ private TypeReference tref;
+
+ /// Ditto
+ private TypeIdentifier tident;
+
+ /// Least specialized type
+ private RootObject object;
+
+ /// Set to the result of the comparison
+ private bool result;
+
+ public this(RootObject base)
+ {
+ switch (base.dyncast())
+ {
+ case DYNCAST.dsymbol:
+ if (auto ns = (cast(Dsymbol)base).isNspace())
+ this.namespace = ns;
+ else if (auto ns = (cast(Dsymbol)base).isCPPNamespaceDeclaration())
+ this.namespace2 = ns;
+ else
+ goto default;
+ break;
+
+ case DYNCAST.type:
+ auto t = cast(Type)base;
+ if (t.ty == Tpointer)
+ this.tpointer = cast(TypePointer)t;
+ else if (t.ty == Treference)
+ this.tref = cast(TypeReference)t;
+ else if (t.ty == Tident)
+ this.tident = cast(TypeIdentifier)t;
+ else
+ goto default;
+ break;
+
+ // Note: ABI tags are also handled here (they are TupleExp of StringExp)
+ default:
+ this.object = base;
+ }
+ }
+
+ /// Introduce base class overloads
+ alias visit = Visitor.visit;
+
+ /// Least specialized overload of each direct child of `RootObject`
+ public override void visit(Dsymbol o)
+ {
+ this.result = this.object && this.object == o;
+ }
+
+ /// Ditto
+ public override void visit(Expression o)
+ {
+ this.result = this.object && this.object == o;
+ }
+
+ /// Ditto
+ public void visit(Tuple o)
+ {
+ this.result = this.object && this.object == o;
+ }
+
+ /// Ditto
+ public override void visit(Type o)
+ {
+ this.result = this.object && this.object == o;
+ }
+
+ /// Ditto
+ public override void visit(TemplateParameter o)
+ {
+ this.result = this.object && this.object == o;
+ }
+
+ /**
+ * This overload handles composed types including template parameters
+ *
+ * Components for substitutions include "next" type.
+ * For example, if `ref T` is present, `ref T` and `T` will be present
+ * in the substitution array.
+ * But since we don't have the final/merged type, we cannot rely on
+ * object comparison, and need to recurse instead.
+ */
+ public override void visit(TypeReference o)
+ {
+ if (!this.tref)
+ return;
+ if (this.tref == o)
+ this.result = true;
+ else
+ {
+ // It might be a reference to a template parameter that we already
+ // saw, so we need to recurse
+ scope v = new ComponentVisitor(this.tref.next);
+ o.next.visitObject(v);
+ this.result = v.result;
+ }
+ }
+
+ /// Ditto
+ public override void visit(TypePointer o)
+ {
+ if (!this.tpointer)
+ return;
+ if (this.tpointer == o)
+ this.result = true;
+ else
+ {
+ // It might be a pointer to a template parameter that we already
+ // saw, so we need to recurse
+ scope v = new ComponentVisitor(this.tpointer.next);
+ o.next.visitObject(v);
+ this.result = v.result;
+ }
+ }
+
+ /// Ditto
+ public override void visit(TypeIdentifier o)
+ {
+ /// Since we know they are at the same level, scope resolution will
+ /// give us the same symbol, thus we can just compare ident.
+ this.result = (this.tident && (this.tident.ident == o.ident));
+ }
+
+ /**
+ * Overload which accepts a Namespace
+ *
+ * It is very common for large C++ projects to have multiple files sharing
+ * the same `namespace`. If any D project adopts the same approach
+ * (e.g. separating data structures from functions), it will lead to two
+ * `Nspace` objects being instantiated, with different addresses.
+ * At the same time, we cannot compare just any Dsymbol via identifier,
+ * because it messes with templates.
+ *
+ * See_Also:
+ * https://issues.dlang.org/show_bug.cgi?id=18922
+ *
+ * Params:
+ * ns = C++ namespace to do substitution for
+ */
+ public override void visit(Nspace ns)
+ {
+ this.result = isNamespaceEqual(this.namespace, ns)
+ || isNamespaceEqual(this.namespace2, ns);
+ }
+
+ /// Ditto
+ public override void visit(CPPNamespaceDeclaration ns)
+ {
+ this.result = isNamespaceEqual(this.namespace, ns)
+ || isNamespaceEqual(this.namespace2, ns);
+ }
+}
+
+/// Transitional functions for `CPPNamespaceDeclaration` / `Nspace`
+/// Remove when `Nspace` is removed.
+private bool isNamespaceEqual (Nspace a, Nspace b)
+{
+ if (a is null || b is null)
+ return false;
+ return a.equals(b);
+}
+
+/// Ditto
+private bool isNamespaceEqual (Nspace a, CPPNamespaceDeclaration b)
+{
+ return isNamespaceEqual(b, a);
+}
+
+/// Ditto
+private bool isNamespaceEqual (CPPNamespaceDeclaration a, Nspace b, size_t idx = 0)
+{
+ if ((a is null) != (b is null))
+ return false;
+ if (!a.ident.equals(b.ident))
+ return false;
+
+ // We need to see if there's more ident enclosing
+ if (auto pb = b.toParent().isNspace())
+ return isNamespaceEqual(a.cppnamespace, pb);
+ else
+ return a.cppnamespace is null;
+}
+
+/// Returns:
+/// Whether two `CPPNamespaceDeclaration` are equals
+private bool isNamespaceEqual (CPPNamespaceDeclaration a, CPPNamespaceDeclaration b)
+{
+ if (a is null || b is null)
+ return false;
+
+ if ((a.cppnamespace is null) != (b.cppnamespace is null))
+ return false;
+ if (a.ident != b.ident)
+ return false;
+ return a.cppnamespace is null ? true : isNamespaceEqual(a.cppnamespace, b.cppnamespace);
+}
+
+/**
+ * A container for ABI tags
+ *
+ * At its hearth, there is a sorted array of ABI tags having been written
+ * already. ABI tags can be present on parameters, template parameters,
+ * return value, and varaible. ABI tags for a given type needs to be written
+ * sorted. When a function returns a type that has ABI tags, only the tags that
+ * haven't been printed as part of the mangling (e.g. arguments) are written
+ * directly after the function name.
+ *
+ * This means that:
+ * ---
+ * /++ C++ type definitions:
+ * struct [[gnu::abi_tag("tag1")]] Struct1 {};
+ * struct [[gnu::abi_tag("tag2")]] Struct2 {};
+ * // Can also be: "tag2", "tag1", since tags are sorted.
+ * struct [[gnu::abi_tag("tag1", "tag2")]] Struct3 {};
+ * +/
+ * // Functions definitions:
+ * Struct3 func1 (Struct1);
+ * Struct3 func2 (Struct2);
+ * Struct3 func3 (Struct2, Struct1);
+ * ---
+ * Will be respectively pseudo-mangled (part of interest between stars) as:
+ * "_Z4 func1 *B4tag2* ParamsMangling" (ParamsMangling includes tag1),
+ * "_Z4 func2 *B4tag1* ParamsMangling" (ParamsMangling includes tag2),
+ * "_Z4 func2 *B4tag1* ParamsMangling" (ParamsMangling includes both).
+ *
+ * This is why why need to keep a list of tags that were written,
+ * and insert the missing one after parameter mangling has been written.
+ * Since there's a lot of operations that are not easily doable in DMD
+ * (since we can't use Phobos), this special container is implemented.
+ */
+private struct ABITagContainer
+{
+ private Array!StringExp written;
+
+ static ArrayLiteralExp forSymbol (Dsymbol s)
+ {
+ if (!s)
+ return null;
+ // If this is a template instance, we want the declaration,
+ // as that's where the UDAs are
+ if (auto ti = s.isTemplateInstance())
+ s = ti.tempdecl;
+ if (!s.userAttribDecl || !s.userAttribDecl.atts)
+ return null;
+
+ foreach (exp; *s.userAttribDecl.atts)
+ {
+ if (UserAttributeDeclaration.isGNUABITag(exp))
+ return (*exp.isStructLiteralExp().elements)[0]
+ .isArrayLiteralExp();
+ }
+ return null;
+ }
+
+ void writeSymbol(Dsymbol s, CppMangleVisitor self)
+ {
+ auto tale = forSymbol(s);
+ if (!tale) return;
+ if (self.substitute(tale))
+ return;
+ this.write(*self.buf, tale);
+ }
+
+ /**
+ * Write an ArrayLiteralExp (expected to be an ABI tag) to the buffer
+ *
+ * Params:
+ * buf = Buffer to write mangling to
+ * ale = GNU ABI tag array literal expression, semantically analyzed
+ */
+ void write (ref OutBuffer buf, ArrayLiteralExp ale, bool skipKnown = false)
+ {
+ void writeElem (StringExp exp)
+ {
+ const tag = exp.peekString();
+ buf.writestring("B");
+ buf.print(tag.length);
+ buf.writestring(tag);
+ }
+
+ bool match;
+ foreach (exp; *ale.elements)
+ {
+ auto elem = exp.toStringExp();
+ auto idx = closestIndex(this.written[], elem, match);
+ if (!match)
+ {
+ writeElem(elem);
+ this.written.insert(idx, elem);
+ }
+ else if (!skipKnown)
+ writeElem(elem);
+ }
+ }
+}
+
+/**
+ * Returns the closest index to to `exp` in `slice`
+ *
+ * Performs a binary search on `slice` (assumes `slice` is sorted),
+ * and returns either `exp`'s index in `slice` if `exact` is `true`,
+ * or the index at which `exp` can be inserted in `slice` if `exact is `false`.
+ * Inserting `exp` at the return value will keep the array sorted.
+ *
+ * Params:
+ * slice = The sorted slice to search into
+ * exp = The string expression to search for
+ * exact = If `true` on return, `exp` was found in `slice`
+ *
+ * Returns:
+ * Either the index to insert `exp` at (if `exact == false`),
+ * or the index of `exp` in `slice`.
+ */
+private size_t closestIndex (const(StringExp)[] slice, StringExp exp, out bool exact)
+{
+ if (!slice.length) return 0;
+
+ const StringExp* first = slice.ptr;
+ while (true)
+ {
+ int res = dstrcmp(exp.peekString(), slice[$ / 2].peekString());
+ if (res == 0)
+ {
+ exact = true;
+ return (&slice[$/2] - first);
+ }
+
+ if (slice.length == 1)
+ return (slice.ptr - first) + (res > 0);
+ slice = slice[(res > 0 ? $ / 2 : 0) .. (res > 0 ? $ : $ / 2)];
+ }
+}
+
+//
+unittest
+{
+ bool match;
+ auto s1 = new StringExp(Loc.initial, "Amande");
+ auto s2 = new StringExp(Loc.initial, "Baguette");
+ auto s3 = new StringExp(Loc.initial, "Croissant");
+ auto s4 = new StringExp(Loc.initial, "Framboises");
+ auto s5 = new StringExp(Loc.initial, "Proscuitto");
+
+ // Found, odd size
+ assert(closestIndex([s1, s2, s3, s4, s5], s1, match) == 0 && match);
+ assert(closestIndex([s1, s2, s3, s4, s5], s2, match) == 1 && match);
+ assert(closestIndex([s1, s2, s3, s4, s5], s3, match) == 2 && match);
+ assert(closestIndex([s1, s2, s3, s4, s5], s4, match) == 3 && match);
+ assert(closestIndex([s1, s2, s3, s4, s5], s5, match) == 4 && match);
+
+ // Not found, even size
+ assert(closestIndex([s2, s3, s4, s5], s1, match) == 0 && !match);
+ assert(closestIndex([s1, s3, s4, s5], s2, match) == 1 && !match);
+ assert(closestIndex([s1, s2, s4, s5], s3, match) == 2 && !match);
+ assert(closestIndex([s1, s2, s3, s5], s4, match) == 3 && !match);
+ assert(closestIndex([s1, s2, s3, s4], s5, match) == 4 && !match);
+
+ // Found, even size
+ assert(closestIndex([s1, s2, s3, s4], s1, match) == 0 && match);
+ assert(closestIndex([s1, s2, s3, s4], s2, match) == 1 && match);
+ assert(closestIndex([s1, s2, s3, s4], s3, match) == 2 && match);
+ assert(closestIndex([s1, s2, s3, s4], s4, match) == 3 && match);
+ assert(closestIndex([s1, s3, s4, s5], s5, match) == 3 && match);
+
+ // Not found, odd size
+ assert(closestIndex([s2, s4, s5], s1, match) == 0 && !match);
+ assert(closestIndex([s1, s4, s5], s2, match) == 1 && !match);
+ assert(closestIndex([s1, s2, s4], s3, match) == 2 && !match);
+ assert(closestIndex([s1, s3, s5], s4, match) == 2 && !match);
+ assert(closestIndex([s1, s2, s4], s5, match) == 3 && !match);
+}
+
+/**
+ * Visits the return type of a function and writes leftover ABI tags
+ */
+extern(C++) private final class LeftoverVisitor : Visitor
+{
+ /// List of tags to write
+ private Array!StringExp toWrite;
+ /// List of tags to ignore
+ private const(Array!StringExp)* ignore;
+
+ ///
+ public this(const(Array!StringExp)* previous)
+ {
+ this.ignore = previous;
+ }
+
+ /// Reintroduce base class overloads
+ public alias visit = Visitor.visit;
+
+ /// Least specialized overload of each direct child of `RootObject`
+ public override void visit(Dsymbol o)
+ {
+ auto ale = ABITagContainer.forSymbol(o);
+ if (!ale) return;
+
+ bool match;
+ foreach (elem; *ale.elements)
+ {
+ auto se = elem.toStringExp();
+ closestIndex((*this.ignore)[], se, match);
+ if (match) continue;
+ auto idx = closestIndex(this.toWrite[], se, match);
+ if (!match)
+ this.toWrite.insert(idx, se);
+ }
+ }
+
+ /// Ditto
+ public override void visit(Type o)
+ {
+ if (auto sym = o.toDsymbol(null))
+ sym.accept(this);
+ }
+
+ /// Composite type
+ public override void visit(TypePointer o)
+ {
+ o.next.accept(this);
+ }
+
+ public override void visit(TypeReference o)
+ {
+ o.next.accept(this);
+ }
+}