Skip to content

Commit

Permalink
Add @standalone attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
dkorpel committed Aug 15, 2023
1 parent 5fb9de2 commit 0407048
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 20 deletions.
32 changes: 32 additions & 0 deletions changelog/dmd.standalone-attribute.dd
Original file line number Diff line number Diff line change
@@ -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`.
25 changes: 25 additions & 0 deletions compiler/src/dmd/attrib.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

1 change: 1 addition & 0 deletions compiler/src/dmd/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
18 changes: 18 additions & 0 deletions compiler/src/dmd/dsymbolsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dmd/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dmd/func.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 13 additions & 5 deletions compiler/src/dmd/glue.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
{
Expand All @@ -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");
Expand Down Expand Up @@ -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())
{
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dmd/id.d
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ immutable Msgtable[] msgtable =
{ "udaSelector", "selector" },
{ "udaOptional", "optional"},
{ "udaMustUse", "mustuse" },
{ "udaStandalone", "standalone" },

// C names, for undefined identifier error messages
{ "NULL" },
Expand Down
17 changes: 2 additions & 15 deletions compiler/src/dmd/mustuse.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
15 changes: 15 additions & 0 deletions compiler/test/fail_compilation/standalone_modctor.d
Original file line number Diff line number Diff line change
@@ -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 {}
11 changes: 11 additions & 0 deletions compiler/test/runnable/imports/standalone_b.d
Original file line number Diff line number Diff line change
@@ -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);
}
19 changes: 19 additions & 0 deletions compiler/test/runnable/standalone_modctor.d
Original file line number Diff line number Diff line change
@@ -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);
}
11 changes: 11 additions & 0 deletions druntime/src/core/attribute.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit 0407048

Please sign in to comment.