Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dmd/glue/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,7 @@ private void obj_start(ref OutBuffer objbuf, const(char)* srcfile)
//printf("obj_start()\n");

bzeroSymbol = null;
resetCtfeSymbolCache();
rtlsym_reset();
clearStringTab();

Expand Down
25 changes: 25 additions & 0 deletions compiler/src/dmd/glue/tocsym.d
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,29 @@ Symbol* toInitializer(EnumDeclaration ed)
/* CTFE stuff */
/*****************************************************/

/* A CTFE-evaluated literal (struct literal or class instance) that is baked into
* static data gets a backing LOCAL symbol (named "internal"), cached on the literal's
* AST node (`sle.sym` / `cre.value.origin.sym`). The AST is shared across all modules
* compiled in a single invocation, so without intervention that cache leaks the symbol
* created for the first object module into later ones — which then emit only an
* undefined reference to a LOCAL symbol they never define (link error:
* `undefined reference to 'internal'`).
*
* Track every literal that cached a symbol in the current object module and clear those
* caches at each object-module boundary (resetCtfeSymbolCache, called from obj_start), so
* each object module re-creates and emits its own self-contained local copy. This matches
* the array-literal path (DtBuilder.dtoff) and the already-accepted separate-compilation
* behavior, while keeping intra-module dedup intact.
*/
private __gshared Array!StructLiteralExp ctfeSymbolLiterals;

void resetCtfeSymbolCache()
{
foreach (sle; ctfeSymbolLiterals[])
sle.sym = null;
ctfeSymbolLiterals.setDim(0);
}

Symbol* toSymbol(StructLiteralExp sle)
{
//printf("toSymbol() %p.sym: %p\n", sle, sle.sym);
Expand All @@ -793,6 +816,7 @@ Symbol* toSymbol(StructLiteralExp sle)
s.Sflags |= SFLnodebug;
s.Stype = t;
sle.sym = s;
ctfeSymbolLiterals.push(sle);
auto dtb = DtBuilder(0);
Expression_toDt(sle, dtb);
s.Sdt = dtb.finish();
Expand All @@ -814,6 +838,7 @@ Symbol* toSymbol(ClassReferenceExp cre)
s.Stype = t;
cre.value.sym = s;
cre.value.origin.sym = s;
ctfeSymbolLiterals.push(cre.value.origin);
auto dtb = DtBuilder(0);
ClassReferenceExp_toInstanceDt(cre, dtb);
s.Sdt = dtb.finish();
Expand Down
8 changes: 8 additions & 0 deletions compiler/test/dshell/extra-files/issue19439.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module issue19439;
import issue19439b;

void main()
{
auto b = new B();
assert(b.obj !is null);
}
15 changes: 15 additions & 0 deletions compiler/test/dshell/extra-files/issue20439.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module issue20439;
import issue20439a;

// This module independently bakes the `.init` images of S and T (and the CTFE instances
// embedded in them). Before the fix, the backing `internal` symbols were cached on the
// shared AST nodes and emitted only into the first object module, so this module's object
// file held only undefined references to symbols it never defines.
__gshared S s;
__gshared T t;

void main()
{
assert(s.c !is null && s.c.x == 42);
assert(t.p !is null && t.p.y == 7);
}
4 changes: 4 additions & 0 deletions compiler/test/dshell/imports/issue19439b.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module issue19439b;
import issue19439c;

class B : C { }
4 changes: 4 additions & 0 deletions compiler/test/dshell/imports/issue19439c.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module issue19439c;

static this() { }
class C { Object obj = new Object; }
11 changes: 11 additions & 0 deletions compiler/test/dshell/imports/issue20439a.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module issue20439a;

// Reduced, Phobos-free form of the bug. The original trigger was `SysTime.max`, which
// bakes a CTFE-allocated immutable class instance (its time zone) into a struct's
// `.init`; a plain `new C` reproduces the same toSymbol(ClassReferenceExp) path. The
// `new Inner` field additionally covers the toSymbol(StructLiteralExp) path.
class C { int x = 42; }
struct S { C c = new C; }

struct Inner { int y = 7; }
struct T { Inner* p = new Inner; }
20 changes: 20 additions & 0 deletions compiler/test/dshell/issue19439.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// https://github.com/dlang/dmd/issues/19439
//
// The `-lib`/multiobj variant of #20439: a CTFE `new Object` baked into a class's `.init`
// is emitted with one object module per symbol, so the class init image and its "internal"
// backing symbol can land in different archive members. The symbol was cached on the shared
// AST node and emitted (locally) into only one member, leaving the member that holds
// `C.__init` with an undefined reference:
// `lib(c.o):(.data._D...1C6__initZ+0x10): undefined reference to 'internal'`.
import dshell;

int main()
{
// class C (with the CTFE field) and derived class B live in separate modules, so their
// codegen lands in separate archive members.
run("$DMD -m$MODEL -lib -of$OUTPUT_BASE/issue19439$LIBEXT $IMPORT_FILES/issue19439b.d $IMPORT_FILES/issue19439c.d");
run("$DMD -m$MODEL -I$IMPORT_FILES -of$OUTPUT_BASE/issue19439$EXE $EXTRA_FILES/issue19439.d $OUTPUT_BASE/issue19439$LIBEXT");
run("$OUTPUT_BASE/issue19439$EXE");

return 0;
}
24 changes: 24 additions & 0 deletions compiler/test/dshell/issue20439.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// https://github.com/dlang/dmd/issues/20439
//
// A CTFE class/struct instance baked into a struct's `.init` needs a local backing symbol
// ("internal") in *every* object module that emits the init image. The symbol was cached on
// the shared AST node, so compiling two modules in one invocation (`dmd -c a.d b.d`) leaked
// the first object module's local symbol into the second, which then emitted only an
// undefined reference to a symbol it never defines:
// `b.o:(.data.rel.ro+0x8): undefined reference to 'internal'`.
//
// The issue used `SysTime.max`; reduced here to a plain CTFE instance to avoid importing
// Phobos (the test suite must not), exercising both toSymbol overloads (class + struct).
import dshell;

int main()
{
// One invocation, two source files, separate object files (-c): issue20439a defines the
// types (and emits the .init image + its local `internal`), issue20439 bakes __gshared
// instances of them (and must define its own `internal`, not reference the other's).
run("$DMD -m$MODEL -od$OUTPUT_BASE -I$IMPORT_FILES -c $IMPORT_FILES/issue20439a.d $EXTRA_FILES/issue20439.d");
run("$DMD -m$MODEL -of$OUTPUT_BASE/issue20439$EXE $OUTPUT_BASE/issue20439a$OBJ $OUTPUT_BASE/issue20439$OBJ");
run("$OUTPUT_BASE/issue20439$EXE");

return 0;
}
Loading