diff --git a/changelog/dmd.standalone-attribute.dd b/changelog/dmd.standalone-attribute.dd new file mode 100644 index 000000000000..fcd59216e79a --- /dev/null +++ b/changelog/dmd.standalone-attribute.dd @@ -0,0 +1,32 @@ +Added `@standalone` for module constructors + +When two modules import each other and both have module constructors, +druntime would throw an error because it can't determine which to run first. + +This could be circumvented by using `pragma(crt_constructor)` instead, but in C runtime constructors, druntime isn't initialized. +Therefore the Garbage Collector can't be used in such constructors. + +`@standalone` is a new attribute that can be used to mark module constructors that run after druntime has been initialized, +but do not depend on any other module constructors being run before it, so it will not cause a cyclic dependency error. +It must be imported from `core.attribute`. + +The compiler doesn't verify that the module constructor truly doesn't depend on other variables being initialized, so it must be enforced manually. +Because of this, they must be marked `@system` or `@trusted`. + +--- +import core.attribute : standalone; + +immutable int* x; + +@standalone @system shared static this() +{ + x = new int(10); +} + +void main() +{ + assert(*x == 10); +} +--- + +If possible, prefer to solve cyclic dependency errors by putting the offending module constructors into their own smaller modules instead of using `@standalone`. diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index baabe930c685..f9cc9092fb74 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -1574,3 +1574,28 @@ int foreachUdaNoSemantic(Dsymbol sym, int delegate(Expression) dg) return 0; } + + +/** + * Returns: true if the given expression is an enum from `core.attribute` named `id` + */ +bool isEnumAttribute(Expression e, Identifier id) +{ + import dmd.attrib : isCoreUda; + import dmd.id : Id; + + // Logic based on dmd.objc.Supported.declaredAsOptionalCount + auto typeExp = e.isTypeExp; + if (!typeExp) + return false; + + auto typeEnum = typeExp.type.isTypeEnum(); + if (!typeEnum) + return false; + + if (isCoreUda(typeEnum.sym, id)) + return true; + + return false; +} + diff --git a/compiler/src/dmd/declaration.h b/compiler/src/dmd/declaration.h index 71f2baa525f2..3a92a1e191c4 100644 --- a/compiler/src/dmd/declaration.h +++ b/compiler/src/dmd/declaration.h @@ -857,6 +857,7 @@ class StaticCtorDeclaration : public FuncDeclaration class SharedStaticCtorDeclaration final : public StaticCtorDeclaration { public: + bool standalone; SharedStaticCtorDeclaration *syntaxCopy(Dsymbol *) override; SharedStaticCtorDeclaration *isSharedStaticCtorDeclaration() override { return this; } diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index f5b2b6c38c24..363a5fee5b60 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -4385,6 +4385,24 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor m.needmoduleinfo = 1; //printf("module1 %s needs moduleinfo\n", m.toChars()); } + + foreachUda(scd, sc, (Expression e) { + import dmd.attrib : isEnumAttribute; + if (!isEnumAttribute(e, Id.udaStandalone)) + return 0; + + if (auto sharedCtor = scd.isSharedStaticCtorDeclaration()) + { + auto trust = sharedCtor.type.isTypeFunction().trust; + if (trust != TRUST.system && trust != TRUST.trusted) + error(e.loc, "a module constructor using `@%s` must be `@system` or `@trusted`", Id.udaStandalone.toChars()); + sharedCtor.standalone = true; + } + else + .error(e.loc, "`@%s` can only be used on shared static constructors", Id.udaStandalone.toChars()); + + return 1; + }); } override void visit(StaticDtorDeclaration sdd) diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 95a5e64e6cbc..4c973a4d3c43 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -2803,6 +2803,7 @@ class StaticCtorDeclaration : public FuncDeclaration class SharedStaticCtorDeclaration final : public StaticCtorDeclaration { public: + bool standalone; SharedStaticCtorDeclaration* syntaxCopy(Dsymbol* s) override; SharedStaticCtorDeclaration* isSharedStaticCtorDeclaration() override; void accept(Visitor* v) override; @@ -8933,6 +8934,7 @@ struct Id final static Identifier* udaSelector; static Identifier* udaOptional; static Identifier* udaMustUse; + static Identifier* udaStandalone; static Identifier* TRUE; static Identifier* FALSE; static Identifier* ImportC; diff --git a/compiler/src/dmd/func.d b/compiler/src/dmd/func.d index 72cbe967e1fd..d2f941836f66 100644 --- a/compiler/src/dmd/func.d +++ b/compiler/src/dmd/func.d @@ -4239,6 +4239,9 @@ extern (C++) class StaticCtorDeclaration : FuncDeclaration */ extern (C++) final class SharedStaticCtorDeclaration : StaticCtorDeclaration { + /// Exclude this constructor from cyclic dependency check + bool standalone; + extern (D) this(const ref Loc loc, const ref Loc endloc, StorageClass stc) { super(loc, endloc, "_sharedStaticCtor", stc); diff --git a/compiler/src/dmd/glue.d b/compiler/src/dmd/glue.d index bdb36e706b0e..a31ea6de0699 100644 --- a/compiler/src/dmd/glue.d +++ b/compiler/src/dmd/glue.d @@ -160,12 +160,13 @@ struct Glue elem *eictor; Symbol *ictorlocalgot; - symbols sctors; + symbols sctors; // static constructorss StaticDtorDeclarations ectorgates; symbols sdtors; symbols stests; - symbols ssharedctors; + symbols ssharedctors; // shared static constructors + symbols sisharedctors; // standalone shared static constructors SharedStaticDtorDeclarations esharedctorgates; symbols sshareddtors; @@ -592,7 +593,7 @@ private void genObjFile(Module m, bool multiobj) // If coverage / static constructor / destructor / unittest calls if (glue.eictor || glue.sctors.length || glue.ectorgates.length || glue.sdtors.length || - glue.ssharedctors.length || glue.esharedctorgates.length || glue.sshareddtors.length || glue.stests.length) + glue.ssharedctors.length || glue.esharedctorgates.length || glue.sshareddtors.length || glue.stests.length || glue.sisharedctors.length) { if (glue.eictor) { @@ -609,6 +610,10 @@ private void genObjFile(Module m, bool multiobj) m.sctor = callFuncsAndGates(m, glue.sctors[], glue.ectorgates[], "__modctor"); m.sdtor = callFuncsAndGates(m, glue.sdtors[], null, "__moddtor"); + if (m.sictor) + glue.sisharedctors.push(m.sictor); + m.sictor = callFuncsAndGates(m, glue.sisharedctors[], null, "__modsharedictor"); + m.ssharedctor = callFuncsAndGates(m, glue.ssharedctors[], cast(StaticDtorDeclaration[])glue.esharedctorgates[], "__modsharedctor"); m.sshareddtor = callFuncsAndGates(m, glue.sshareddtors[], null, "__modshareddtor"); m.stest = callFuncsAndGates(m, glue.stests[], null, "__modtest"); @@ -1149,9 +1154,12 @@ void FuncDeclaration_toObjFile(FuncDeclaration fd, bool multiobj) insertFinallyBlockCalls(f.Fstartblock); // If static constructor - if (fd.isSharedStaticCtorDeclaration()) // must come first because it derives from StaticCtorDeclaration + if (auto sctor = fd.isSharedStaticCtorDeclaration()) // must come first because it derives from StaticCtorDeclaration { - glue.ssharedctors.push(s); + if (sctor.standalone) + glue.sisharedctors.push(s); + else + glue.ssharedctors.push(s); } else if (fd.isStaticCtorDeclaration()) { diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index 43b2e5f5a50e..ae9fc677e776 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -514,6 +514,7 @@ immutable Msgtable[] msgtable = { "udaSelector", "selector" }, { "udaOptional", "optional"}, { "udaMustUse", "mustuse" }, + { "udaStandalone", "standalone" }, // C names, for undefined identifier error messages { "NULL" }, diff --git a/compiler/src/dmd/mustuse.d b/compiler/src/dmd/mustuse.d index 844f719e0b81..334ac0a03cf8 100644 --- a/compiler/src/dmd/mustuse.d +++ b/compiler/src/dmd/mustuse.d @@ -226,20 +226,7 @@ private bool hasMustUseAttribute(Dsymbol sym, Scope* sc) */ private bool isMustUseAttribute(Expression e) { - import dmd.attrib : isCoreUda; + import dmd.attrib : isEnumAttribute; import dmd.id : Id; - - // Logic based on dmd.objc.Supported.declaredAsOptionalCount - auto typeExp = e.isTypeExp; - if (!typeExp) - return false; - - auto typeEnum = typeExp.type.isTypeEnum(); - if (!typeEnum) - return false; - - if (isCoreUda(typeEnum.sym, Id.udaMustUse)) - return true; - - return false; + return isEnumAttribute(e, Id.udaMustUse); } diff --git a/compiler/test/fail_compilation/standalone_modctor.d b/compiler/test/fail_compilation/standalone_modctor.d new file mode 100644 index 000000000000..cb36ed6b790d --- /dev/null +++ b/compiler/test/fail_compilation/standalone_modctor.d @@ -0,0 +1,15 @@ +/** +TEST_OUTPUT: +--- +fail_compilation/standalone_modctor.d(11): Error: `@standalone` can only be used on shared static constructors +fail_compilation/standalone_modctor.d(12): Error: a module constructor using `@standalone` must be `@system` or `@trusted` +fail_compilation/standalone_modctor.d(13): Error: a module constructor using `@standalone` must be `@system` or `@trusted` +--- +*/ +import core.attribute : standalone; + +@standalone static this() {} +@standalone shared static this() {} +@standalone shared static this() @safe {} +@standalone shared static this() @trusted {} +@standalone shared static this() @system {} diff --git a/compiler/test/runnable/imports/standalone_b.d b/compiler/test/runnable/imports/standalone_b.d new file mode 100644 index 000000000000..bc5500b7b8c0 --- /dev/null +++ b/compiler/test/runnable/imports/standalone_b.d @@ -0,0 +1,11 @@ +module standalone_b; + +import standalone_modctor; +import core.attribute : standalone; + +immutable int* y; + +@standalone @system shared static this() +{ + y = new int(2); +} diff --git a/compiler/test/runnable/standalone_modctor.d b/compiler/test/runnable/standalone_modctor.d new file mode 100644 index 000000000000..265440768369 --- /dev/null +++ b/compiler/test/runnable/standalone_modctor.d @@ -0,0 +1,19 @@ +// REQUIRED_ARGS: -Irunnable/imports +// EXTRA_SOURCES: imports/standalone_b.d +// PERMUTE_ARGS: -cov + +import standalone_b; +import core.attribute : standalone; + +immutable int* x; + +@standalone @system shared static this() +{ + x = new int(1); +} + +void main() +{ + assert(*x == 1); + assert(*y == 2); +} diff --git a/druntime/src/core/attribute.d b/druntime/src/core/attribute.d index c2a7c334eb77..c270445fd522 100644 --- a/druntime/src/core/attribute.d +++ b/druntime/src/core/attribute.d @@ -290,3 +290,14 @@ version (UdaGNUAbiTag) struct gnuAbiTag * --- */ enum mustuse; + +/** + * Use this attribute to indicate that a shared module constructor does not depend on any + * other module constructor being run first. This avoids errors on cyclic module constructors. + * + * However, it is now up to the user to enforce safety. + * The module constructor must be marked `@system` as a result. + * + * This is only allowed on `shared` static constructors, not thread-local module constructors. + */ +enum standalone;