Add @standalone attribute for module constructors (#15537)

* Add `@__standalone` attribute

* Remove underscores from __standalone
This commit is contained in:
Dennis 2023-12-22 20:03:03 +01:00 committed by GitHub
parent d05cf6e9b0
commit 14ecea9ea7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 156 additions and 20 deletions

View file

@ -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`.

View file

@ -1296,3 +1296,27 @@ 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;
}

View file

@ -843,6 +843,7 @@ public:
class SharedStaticCtorDeclaration final : public StaticCtorDeclaration
{
public:
bool standalone;
SharedStaticCtorDeclaration *syntaxCopy(Dsymbol *) override;
SharedStaticCtorDeclaration *isSharedStaticCtorDeclaration() override { return this; }

View file

@ -4592,6 +4592,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)

View file

@ -4005,6 +4005,7 @@ public:
class SharedStaticCtorDeclaration final : public StaticCtorDeclaration
{
public:
bool standalone;
SharedStaticCtorDeclaration* syntaxCopy(Dsymbol* s) override;
SharedStaticCtorDeclaration* isSharedStaticCtorDeclaration() override;
void accept(Visitor* v) override;
@ -8825,6 +8826,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;

View file

@ -4253,6 +4253,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);

View file

@ -203,12 +203,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;
@ -637,7 +638,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)
{
@ -654,6 +655,13 @@ 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 (glue.sisharedctors.length > 0)
{
if (m.sictor)
glue.sisharedctors.shift(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");
@ -1198,8 +1206,11 @@ public 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
{
if (sctor.standalone)
glue.sisharedctors.push(s);
else
glue.ssharedctors.push(s);
}
else if (fd.isStaticCtorDeclaration())

View file

@ -521,6 +521,7 @@ immutable Msgtable[] msgtable =
{ "udaSelector", "selector" },
{ "udaOptional", "optional"},
{ "udaMustUse", "mustuse" },
{ "udaStandalone", "standalone" },
// C names, for undefined identifier error messages
{ "NULL" },

View file

@ -222,20 +222,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);
}

View file

@ -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 {}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -290,3 +290,15 @@ 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.
* Prefer to refactor the module constructor causing the cycle so it's in its own module if possible.
*
* This is only allowed on `shared` static constructors, not thread-local module constructors.
*/
enum standalone;