diff --git a/changelog/dmd.linkerlist.dd b/changelog/dmd.linkerlist.dd new file mode 100644 index 000000000000..cdd814a233fc --- /dev/null +++ b/changelog/dmd.linkerlist.dd @@ -0,0 +1,34 @@ +Linker lists are now supported + +A linker list is a list of symbols that a linker puts together into a section, allowing for each disparate module to provide entries within their object files. +They cannot be exported or dllimport, you must register their contents by use of a module constructor (can be C). + +To iterate them, you may foreach over them at runtime. + +To append to them, you may do so from within a CTFE executed function. + +```d +import core.attribute; + +__gshared __linkerlist!int myInts; + +static assert(() { myInts ~= 42; return true; }()); + +void main() { + foreach(v; myInts) + assert(v == 42); +} +``` + +Each declaration for the linker list, must be per executable or shared library. +The declaration may trigger symbol emittance. + +If the type is ``const`` or ``immutable`` then the list entries will be marked as read only. + +An entry when emitted will go into the object file, associated with the module that initiated the function. +It is recommended therefore that the function should always be templated. +If the template is associated with a module that is not being compiled (imported), it will not be emitted into an object file. +Note: that this module does NOT refer to the source file that contains the template. + +If you are already using linker lists using the section attribute, it is strongly recommended to swap to compiler backed linker lists. +They are platform, linker, and compiler specific abstractions that are not portable. diff --git a/compiler/src/dmd/declaration.d b/compiler/src/dmd/declaration.d index e1a4f9bf656c..1dda49b08105 100644 --- a/compiler/src/dmd/declaration.d +++ b/compiler/src/dmd/declaration.d @@ -427,15 +427,17 @@ extern (C++) final class OverDeclaration : Declaration extern (C++) class VarDeclaration : Declaration { Initializer _init; - FuncDeclarations nestedrefs; // referenced by these lexically nested functions - TupleDeclaration aliasTuple; // when `this` is really a tuple of declarations - VarDeclaration lastVar; // Linked list of variables for goto-skips-init detection - Expression edtor; // if !=null, does the destruction of the variable - IntRange* range; // if !=null, the variable is known to be within the range + FuncDeclarations nestedrefs; // referenced by these lexically nested functions + TupleDeclaration aliasTuple; // when `this` is really a tuple of declarations + VarDeclaration lastVar; // Linked list of variables for goto-skips-init detection + Expression edtor; // if !=null, does the destruction of the variable + IntRange* range; // if !=null, the variable is known to be within the range - uint endlinnum; // line number of end of scope that this var lives in + VarDeclaration entryForLinkerList; // if !=null, the linker list we're appending to + + uint endlinnum; // line number of end of scope that this var lives in uint offset; - uint sequenceNumber; // order the variables are declared + uint sequenceNumber; // order the variables are declared structalign_t alignment; // When interpreting, these point to the value (NULL if value not determinable) @@ -469,6 +471,8 @@ extern (C++) class VarDeclaration : Declaration bool dllExport; /// __declspec(dllexport) mixin VarDeclarationExtra; bool systemInferred; /// @system was inferred from initializer + + bool isLinkerListDeclaration; /// This variable declares a linker list } import dmd.common.bitfields : generateBitFields; diff --git a/compiler/src/dmd/declaration.h b/compiler/src/dmd/declaration.h index 759e533543d6..8003f51e73ea 100644 --- a/compiler/src/dmd/declaration.h +++ b/compiler/src/dmd/declaration.h @@ -236,6 +236,7 @@ class VarDeclaration : public Declaration VarDeclaration *lastVar; // Linked list of variables for goto-skips-init detection Expression *edtor; // if !=NULL, does the destruction of the variable IntRange *range; // if !NULL, the variable is known to be within the range + VarDeclaration* entryForLinkerList; unsigned endlinnum; // line number of end of scope that this var lives in unsigned offset; @@ -282,6 +283,8 @@ class VarDeclaration : public Declaration #endif bool systemInferred() const; bool systemInferred(bool v); + bool isLinkerListDeclaration() const; + bool isLinkerListDeclaration(bool v); static VarDeclaration *create(Loc loc, Type *t, Identifier *id, Initializer *init, StorageClass storage_class = STCundefined); VarDeclaration *syntaxCopy(Dsymbol *) override; const char *kind() const override; diff --git a/compiler/src/dmd/dinterpret.d b/compiler/src/dmd/dinterpret.d index 65b24a0a0c0a..4f128776d5d8 100644 --- a/compiler/src/dmd/dinterpret.d +++ b/compiler/src/dmd/dinterpret.d @@ -4652,6 +4652,47 @@ public: Expression pthis = null; FuncDeclaration fd = null; + if (e.f !is null && e.f.ident is Id.opOpAssign) + { + DotVarExp dve = e.e1.isDotVarExp(); + VarExp ve; + VarDeclaration vd, linkerListEntry; + + if (dve !is null && + (ve = dve.e1.isVarExp()) !is null && + (vd = ve.var.isVarDeclaration()) !is null && + vd.isLinkerListDeclaration) + { + if (istate.fd is null) + { + error(e.loc, "cannot append to a linker list without being in a function"); + result = CTFEExp.cantexp; + } + else + { + auto root = istate.fd.getModule; + // If we don't have a root module, to put it in we'll ignore it. + // We ignore it because it may have already been emitted in a different compilation step. + + if (root.isRoot) + { + auto initExp = interpret((*e.arguments)[0], istate).copyRegionExp; + auto init = new ExpInitializer(e.loc, initExp); + linkerListEntry = new VarDeclaration(e.loc, null, Identifier.generateIdWithLoc("_d_linkerlistentry", e.loc), init); + + linkerListEntry.entryForLinkerList = vd; + linkerListEntry.visibility.kind = Visibility.Kind.private_; + linkerListEntry.storage_class |= STC.gshared; + + root.members.push(linkerListEntry); + linkerListEntry.dsymbolSemantic(root._scope); + } + } + + return; + } + } + Expression ecall = interpretRegion(e.e1, istate); if (exceptionOrCant(ecall)) return; diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 6108fcd84152..584be308d1db 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -2592,6 +2592,26 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor // Flag variable as error to avoid invalid error messages due to unknown size dsym.type = Type.terror; } + else if (ts.sym.ident is Id.__linkerlist) + { + // Okay we've got a linker list + + if (dsym.visibility.kind == Visibility.Kind.export_) + { + .error(dsym.loc, "Linker lists cannot be exported"); + dsym.type = Type.terror; + } + else if (dsym.storage_class & STC.extern_) + { + .error(dsym.loc, "Linker lists cannot be `extern` they must live at declaration site"); + dsym.type = Type.terror; + } + else + { + dsym.isLinkerListDeclaration = true; + dsym._linkage = LINK.system; // set mangling to system, as we can end up going too big with D. + } + } } if ((dsym.storage_class & STC.auto_) && !inferred && !(dsym.storage_class & STC.autoref)) .error(dsym.loc, "%s `%s` - storage class `auto` has no effect if type is not inferred, did you mean `scope`?", dsym.kind, dsym.toPrettyChars); diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 8e8e2fe76f8e..82358dc84b7d 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -6598,6 +6598,7 @@ class VarDeclaration : public Declaration VarDeclaration* lastVar; Expression* edtor; IntRange* range; + VarDeclaration* entryForLinkerList; uint32_t endlinnum; uint32_t offset; uint32_t sequenceNumber; @@ -6639,6 +6640,8 @@ class VarDeclaration : public Declaration bool inAlignSection(bool v); bool systemInferred() const; bool systemInferred(bool v); + bool isLinkerListDeclaration() const; + bool isLinkerListDeclaration(bool v); private: uint32_t bitFields; public: @@ -8668,6 +8671,7 @@ struct Id final static Identifier* classDelete; static Identifier* apply; static Identifier* applyReverse; + static Identifier* linkerlistApply; static Identifier* Fempty; static Identifier* Ffront; static Identifier* Fback; diff --git a/compiler/src/dmd/glue/e2ir.d b/compiler/src/dmd/glue/e2ir.d index 330a8505c6d8..8f60a17c385a 100644 --- a/compiler/src/dmd/glue/e2ir.d +++ b/compiler/src/dmd/glue/e2ir.d @@ -21,6 +21,7 @@ import dmd.root.ctfloat; import dmd.root.rmem; import dmd.rootobject; import dmd.root.stringtable; +import dmd.common.outbuffer; import dmd.glue; import dmd.glue.objc; @@ -56,6 +57,8 @@ import dmd.id; import dmd.init; import dmd.location; import dmd.mtype; +import dmd.mangle : mangleToBuffer; +import dmd.opover : search_function; import dmd.printast; import dmd.sideeffect; import dmd.statement; @@ -5408,7 +5411,7 @@ elem* callfunc(Loc loc, version (none) { printf("callfunc(directcall = %d, tret = '%s', ec = %p, fd = %p, op = %d)\n", - directcall, tret.toChars(), ec, fd); + directcall, tret.toChars(), ec, fd, op); printf("ec: "); elem_print(ec); if (fd) printf("fd = '%s', vtblIndex = %d, isVirtual() = %d\n", fd.toChars(), fd.vtblIndex, fd.isVirtual()); @@ -5521,7 +5524,7 @@ elem* callfunc(Loc loc, /* Convert arguments[] to elems[] in left-to-right order */ - const n = arguments.length; + const n = arguments.length + (fd !is null && fd.ident is Id.linkerlistApply); debug elem*[2] elems_array = void; else @@ -5538,8 +5541,65 @@ elem* callfunc(Loc loc, const int j = tf.isDstyleVariadic(); const osx_aapcs64 = irs.target.isAArch64 && irs.target.os == Target.OS.OSX; + void linkerlistArg(Expression arg) + { + // We are swapping a __linkerlist!T global variable with its respective start+stop symbols as arguments. + // The transformation takes place at offset 1 of the elems array, and will result in three arguments total. + + VarExp ve = arg.isVarExp; + assert(ve !is null); + VarDeclaration vd = ve.var.isVarDeclaration; + assert(vd !is null); + const canBeReadOnly = !vd.type.isMutable; + + elem* genStartEndExtern(string namePrefix, string nameSuffix, bool noUnderscore = false) + { + OutBuffer nameOB; + nameOB.writestring(namePrefix); + mangleToBuffer(vd, nameOB); + nameOB.writestring(nameSuffix); + + Symbol* s = symbol_calloc(nameOB.extractSlice); + s.Sclass = SC.extern_; + s.Sfl = FL.extern_; + s.Stype = type_fake(TYnptr); // always a pointer in size, regardless of linker list type. + + if (noUnderscore) + s.Sflags |= SFLnounderscore; + + return el_ptr(s); + } + + switch (config.objfmt) + { + case OBJ_MACH: + elems[1] = genStartEndExtern(canBeReadOnly ? "section$start$__TEXT$" : "section$start$__DATA$", "", true); + elems[2] = genStartEndExtern(canBeReadOnly ? "section$end$__TEXT$" : "section$end$__DATA$", "", true); + break; + + case OBJ_ELF: + elems[1] = genStartEndExtern("__start_", ""); + elems[2] = genStartEndExtern("__stop_", ""); + break; + case OBJ_MSCOFF: + elems[1] = genStartEndExtern("", "_start"); + elems[2] = genStartEndExtern("", "_end"); + break; + + default: + assert(0); + } + } + foreach (const i, arg; *arguments) { + if (i == 1 && fd !is null && fd.ident is Id.linkerlistApply) + { + // do not generate symbol for the linker list global + linkerlistArg(arg); + break; // This completes the argument list. + } + elem* ea = toElem(arg, irs); Parameter param = null; @@ -6555,8 +6615,10 @@ elem* toElemCall(CallExp ce, ref IRState irs, elem* ehidden = null) if (ce.e1.op == EXP.dotVariable && t1.ty != Tdelegate) { DotVarExp dve = cast(DotVarExp)ce.e1; + FuncDeclaration linkerListApply; fd = dve.var.isFuncDeclaration(); + ectype = dve.e1.type.toBasetype(); if (auto sle = dve.e1.isStructLiteralExp()) { @@ -6565,6 +6627,17 @@ elem* toElemCall(CallExp ce, ref IRState irs, elem* ehidden = null) sle.type.size() <= 8) // more efficient than fPIC sle.useStaticInit = false; // don't modify initializer, so make copy } + else if (fd.ident is Id.apply) + { + if (auto ts = ectype.isTypeStruct) + { + if (ts.sym.ident is Id.__linkerlist) + { + Dsymbol applyImpl = search_function(ts.sym, Id.linkerlistApply); + linkerListApply = applyImpl !is null ? applyImpl.isFuncDeclaration : null; + } + } + } // Special cases for constructor RVO if (ehidden && fd && fd.isCtorDeclaration()) @@ -6584,11 +6657,22 @@ elem* toElemCall(CallExp ce, ref IRState irs, elem* ehidden = null) ehidden = null; } + else if (linkerListApply !is null) + { + // Point ec to the implementation function and update the function/type to match. + // Append to the arguments list the global variable that contains the linker list. + + ec = el_ptr(toSymbol(linkerListApply)); + ectype = linkerListApply.type; + t1 = linkerListApply.type; + fd = linkerListApply; + + assert(ce.arguments !is null && ce.arguments.length == 1); + ce.arguments.push(dve.e1); + } else ec = toElem(dve.e1, irs); - ectype = dve.e1.type.toBasetype(); - /* Recognize: * [1] ce: ((S __ctmp = initializer),__ctmp).ctor(args) * where the left of the . was turned into [2] or [3] for EH_DWARF: diff --git a/compiler/src/dmd/glue/tocsym.d b/compiler/src/dmd/glue/tocsym.d index 8a8d93e4e360..322222e1e2bc 100644 --- a/compiler/src/dmd/glue/tocsym.d +++ b/compiler/src/dmd/glue/tocsym.d @@ -139,6 +139,11 @@ Symbol* toSymbol(Dsymbol s) fprintf(stderr, "VarDeclaration.toSymbol(%s) needThis kind: %s\n", vd.toPrettyChars(), vd.kind()); assert(!vd.needThis()); + // foreach is already handled before this, so anything thats hitting it is an error case. + // No need to quit after error, at worse this will result in a linker error. + if (vd.isLinkerListDeclaration) + error(vd.loc, "Linker list declarations cannot be accessed except for iteration via foreach statement or appended during CTFE"); + import dmd.common.outbuffer : OutBuffer; OutBuffer buf; bool isNRVO = false; diff --git a/compiler/src/dmd/glue/todt.d b/compiler/src/dmd/glue/todt.d index ed2f8b58f40d..d499008e6741 100644 --- a/compiler/src/dmd/glue/todt.d +++ b/compiler/src/dmd/glue/todt.d @@ -1486,7 +1486,7 @@ private extern (C++) class TypeInfoDtVisitor : Visitor auto tc = d.tinfo.isTypeStruct(); StructDeclaration sd = tc.sym; - if (!sd.members) + if (!sd.members || sd.ident is Id.__linkerlist) return; if (sd.semanticRun < PASS.semantic3done) diff --git a/compiler/src/dmd/glue/toobj.d b/compiler/src/dmd/glue/toobj.d index 54bb9089642a..eb4c29bef6c4 100644 --- a/compiler/src/dmd/glue/toobj.d +++ b/compiler/src/dmd/glue/toobj.d @@ -57,6 +57,7 @@ import dmd.hdrgen; import dmd.id; import dmd.init; import dmd.location; +import dmd.mangle : mangleToBuffer; import dmd.mtype; import dmd.nspace; import dmd.statement; @@ -466,6 +467,101 @@ void toObjFile(Dsymbol ds, bool multiobj) return; } + if (vd.isLinkerListDeclaration) + { + const canBeReadOnly = !vd.type.isMutable; + const isAlignedTo8 = (config.exe & EX_64) > 0; + + switch (config.objfmt) + { + case OBJ_MACH: + // Mach-O uses start/end symbols that are extern, we declare them on usage. + // We don't have to do anything at the linker link global variable declaration. + // But we do have to double check that the mangling won't end up exceeding 16 characters. + OutBuffer name; + mangleToBuffer(vd, name); + + if (name.length > 16) + { + .error(vd.loc, "Section name length for Mach-O cannot exceed 16 characters but is: `%s`", name.peekChars); + return; + } + break; + + case OBJ_ELF: + // ELF uses start/end symbols that are extern, we declare them on usage. + // We don't have to do anything at the linker link global variable declaration. + break; + + case OBJ_MSCOFF: + import dmd.backend.mscoff; + // we need to generate two new variables based upon how we create the entries. + + char* startSectionNamePtr, endSectionNamePtr; + + { + OutBuffer startSectionName, endSectionName; + + startSectionName.writestring("."); + mangleToBuffer(vd, startSectionName); + startSectionName.writestring("$A"); + + endSectionName.writestring(startSectionName[]); + endSectionName.peekSlice[$-1] = 'Z'; + + startSectionNamePtr = startSectionName.extractChars; + endSectionNamePtr = endSectionName.extractChars; + } + + // Windows will not dead strip symbols, as long as start/end are used. + + void genStartEndWindows(string name, char* sectionNamePtr) + { + int sectionId = Obj.getsegment( + sectionNamePtr, + + // flags + IMAGE_SCN_ALIGN_4BYTES << isAlignedTo8 // 4-8 byte alignment + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_CNT_INITIALIZED_DATA + | (canBeReadOnly ? 0 : IMAGE_SCN_MEM_WRITE) + ); + + OutBuffer nameOB; + mangleToBuffer(vd, nameOB); + nameOB.writestring(name); + + Symbol* s = symbol_calloc(nameOB.extractSlice); + s.Sclass = SC.global; + s.Sseg = sectionId; + s.Salignment = 4 + (isAlignedTo8 * 4); // 4-8 byte alignment + + s.lposscopestart = toSrcpos(vd.loc); + s.lnoscopeend = vd.endlinnum; + + s.Stype = type_fake(TYnptr); + s.Stype.Tcount++; + + auto dtbWrapper = DtBuilder(0); + dtbWrapper.size(0); + s.Sdt = dtbWrapper.finish(); + + if (canBeReadOnly) + out_readonly(s); + outdata(s); + } + + genStartEndWindows("_start", startSectionNamePtr); + genStartEndWindows("_end", endSectionNamePtr); + break; + + default: + assert(0); + } + + return; + } + // Check to see if we're doing special section mangling, these are not regular variables. // Do not prepend an underscore to the name, it won't work. if (config.objfmt == OBJ_MACH && vd.mangleOverride.length > 8 && vd.mangleOverride[0 .. 8] == "section$") @@ -490,6 +586,10 @@ void toObjFile(Dsymbol ds, bool multiobj) Dsymbol parent = vd.toParent(); s.Sclass = SC.global; + // This symbol for whatever reason is known to be a bit internal, + // and isn't allowed to be exported. + bool noExportInternal; + { string userDefinedSection; @@ -585,6 +685,121 @@ void toObjFile(Dsymbol ds, bool multiobj) } } + if (vd.entryForLinkerList !is null) + { + // We're going into a linker list, so we need to configure the section + // A lot of this logic is copied into opApply emittance for linker lists in e2ir, + // make sure to keep them in sync. + + const canBeReadOnly = !vd.type.isMutable; + const isAlignedTo8 = (config.exe & EX_64) > 0; + + OutBuffer nameOB; + mangleToBuffer(vd.entryForLinkerList, nameOB); + + // Note: there is not meant to be a limit to the number of linker list entries, + // but there may be due to bad compiler implementation details; + + int sectionId = -1; + const(char)* sectionName; + + switch (config.objfmt) + { + case OBJ_MACH: + import dmd.backend.mach; + + sectionName = nameOB.extractChars; + + // name does not start with a _ + //s.Sflags |= SFLnounderscore; + + sectionId = Obj.getsegment( + sectionName, + canBeReadOnly ? "__TEXT" : "__DATA", + 2^(2 + isAlignedTo8), + + S_ATTR_NO_DEAD_STRIP | + S_REGULAR // flags + ); + break; + case OBJ_ELF: + import dmd.backend.elfobj; + import dmd.backend.melf; + + sectionName = nameOB.extractChars; + + sectionId = Obj.getsegment( + sectionName, + null, // suffix + SHT_PROGBITS, // type + + SHF_GNU_RETAIN | + SHF_ALLOC | (canBeReadOnly ? 0 : SHF_WRITE), // flags + + isAlignedTo8 ? 8 : 4 // align + ); + break; + case OBJ_MSCOFF: + import dmd.backend.mscoff; + + nameOB.prependstring("."); // + nameOB.writestring("$N"); // right into the middle of the section + sectionName = nameOB.extractChars; + + // Windows will not dead strip symbols, as long as start/end are used. + + sectionId = Obj.getsegment( + sectionName, + + // flags + IMAGE_SCN_ALIGN_4BYTES << isAlignedTo8 // 4-8 byte alignment + | IMAGE_SCN_MEM_READ + | IMAGE_SCN_CNT_INITIALIZED_DATA + | (canBeReadOnly ? 0 : IMAGE_SCN_MEM_WRITE) + ); + break; + default: + break; + } + + if (vd.type.isTypePointer || vd.type.isTypeClass) + { + // ok we've got a pointer, we can slap the section directly into our variable. + // Does not support slices (dynamic arrays). + s.Sseg = sectionId; + s.Salignment = 4 + (isAlignedTo8 * 4); // 4-8 byte alignment + } + else + { + // Okay so we need to generate a symbol that wraps this one. + // Then we'll have it store a pointer to this var, and that'll go into the section. + + mangleToBuffer(vd, nameOB); + nameOB.writestring("_wrap"); + + Symbol* sWrapper = symbol_calloc(nameOB.extractSlice); + sWrapper.Sclass = SC.global; + sWrapper.Sseg = sectionId; + sWrapper.Salignment = 4 + (isAlignedTo8 * 4); // 4-8 byte alignment + + sWrapper.lposscopestart = toSrcpos(vd.loc); + sWrapper.lnoscopeend = vd.endlinnum; + + sWrapper.Stype = type_fake(TYnptr); + sWrapper.Stype.Tcount++; + + auto dtbWrapper = DtBuilder(0); + dtbWrapper.xoff(s, 0); + sWrapper.Sdt = dtbWrapper.finish(); + + if (canBeReadOnly) + out_readonly(sWrapper); + outdata(sWrapper); + } + + noExportInternal = true; + } + /* Make C static functions SCstatic */ if (vd.storage_class & STC.static_ && vd.isCsymbol()) @@ -654,7 +869,7 @@ void toObjFile(Dsymbol ds, bool multiobj) outdata(s); if (vd.type.isMutable() || !vd._init) write_pointers(vd.type, s, 0); - if (vd.isExport() || driverParams.exportVisibility == ExpVis.public_) + if (!noExportInternal && (vd.isExport() || driverParams.exportVisibility == ExpVis.public_)) objmod.export_symbol(s, 0); } diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index 2b814a8074d8..a9324094bd60 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -252,6 +252,10 @@ immutable Msgtable[] msgtable = { "apply", "opApply" }, { "applyReverse", "opApplyReverse" }, + // For linker list + { "__linkerlist" }, + { "linkerlistApply", "_d_linkerlistApply" }, + // Ranges { "Fempty", "empty" }, { "Ffront", "front" }, diff --git a/compiler/test/runnable/extra-files/linkerlistdef.d b/compiler/test/runnable/extra-files/linkerlistdef.d new file mode 100644 index 000000000000..9df397d0f69e --- /dev/null +++ b/compiler/test/runnable/extra-files/linkerlistdef.d @@ -0,0 +1,28 @@ +module linkerlistdef; +import core.attribute; + +__linkerlist!int myInts; +__linkerlist!(int[3]) my3Ints; +__linkerlist!Object myObjects; +__linkerlist!I myInterfaces; +__linkerlist!S myStructs1; +__linkerlist!(S*) myStructs2; +__linkerlist!string myStrings; + +struct S { + int val; +} + +interface I { +} + +class CI : I { +} + +static assert(() { myInts ~= 15; return true; }()); +static assert(() { my3Ints ~= [15, 16, 17]; return true; }()); +static assert(() { myObjects ~= new Object; return true; }()); +static assert(() { myInterfaces ~= new CI; return true; }()); +static assert(() { myStructs1 ~= S(18); return true; }()); +static assert(() { myStructs2 ~= new S(19); return true; }()); +static assert(() { myStrings ~= __FUNCTION__; return true; }()); diff --git a/compiler/test/runnable/linkerlist.d b/compiler/test/runnable/linkerlist.d new file mode 100644 index 000000000000..cff3653c153c --- /dev/null +++ b/compiler/test/runnable/linkerlist.d @@ -0,0 +1,57 @@ +// EXTRA_SOURCES: extra-files/linkerlistdef.d +module linkerlist; +import linkerlistdef; + +static assert(() { myInts ~= 5; return true; }()); +static assert(() { my3Ints ~= [5, 6, 7]; return true; }()); +static assert(() { myObjects ~= new Object; return true; }()); +static assert(() { myInterfaces ~= new CI; return true; }()); +static assert(() { myStructs1 ~= S(9); return true; }()); +static assert(() { myStructs2 ~= new S(10); return true; }()); +static assert(() { myStrings ~= __FUNCTION__; return true; }()); + +void main() { + int count; + + foreach(ref v; myInts) { + count++; + v = typeof(v).init; + } + assert(count == 2); + + foreach(ref v; my3Ints) { + count++; + v = typeof(v).init; + } + assert(count == 4); + + foreach(ref v; myObjects) { + count++; + v = typeof(v).init; + } + assert(count == 6); + + foreach(ref v; myInterfaces) { + count++; + v = typeof(v).init; + } + assert(count == 8); + + foreach(ref v; myStructs1) { + count++; + v = typeof(v).init; + } + assert(count == 10); + + foreach(ref v; myStructs2) { + count++; + v = typeof(v).init; + } + assert(count == 12); + + foreach(ref v; myStrings) { + count++; + v = typeof(v).init; + } + assert(count == 14); +} diff --git a/druntime/src/core/attribute.d b/druntime/src/core/attribute.d index 5e9a97ff9876..058828a02a89 100644 --- a/druntime/src/core/attribute.d +++ b/druntime/src/core/attribute.d @@ -346,3 +346,67 @@ enum mustuse; * This is only allowed on `shared` static constructors, not thread-local module constructors. */ enum standalone; + +/** +* Provides a global to the binary linker list that may be iterated at runtime, and appended to during CTFE. +* +* It will go into read only memory if the type is `const` or `immutable`. +* +* It will never be exported or made dllimport. +* +* Always prefer pointers as your list type, the compiler will automatically add an intermediary symbol to make it a pointer. +* +* Do not make assumptions regarding the location of the pointer in memory, it may not be contiguous between entries. +* +* Examples: +* --- + * __linkerlist!int myInts; + * static assert(() { myInts ~= 1; return true; }()); + * --- +*/ +struct __linkerlist(Type) +{ + /// Callable at runtime, not during compile time. + int opApply(scope int delegate(ref Type) @system) @system; + /// Callable during CTFE, not available at runtime. + void opOpAssign(string op : "~")(Type) @safe nothrow @nogc pure; + +private: + static int _d_linkerlistApply(scope int delegate(ref Type) @system del, void* start, void* end) @system + { + static if (is(Type : U*, U) || is(Type == class) || is(Type == interface)) + alias Type2 = Type*; + else + alias Type2 = Type**; + + Type2 start2 = cast(Type2)start, end2 = cast(Type2)end; + + // Because of the way sections work for MSVC $A will not have a valid value in it, so skip it. + version(Windows) + start2++; + + int result; + + // Note: A pointer in the list could be null, + // this can happen on Windows due to MSVC linker adding padding. + // https://devblogs.microsoft.com/oldnewthing/20190114-00/?p=100695 + + while(result == 0 && start2 < end2) + { + static if (is(Type* == Type2)) + { + if (*start2 !is null) + result = del(*start2); + } + else + { + if (*start2 !is null) + result = del(**start2); + } + + start2++; + } + + return result; + } +}