diff --git a/changelog/scope-array-on-stack.dd b/changelog/scope-array-on-stack.dd new file mode 100644 index 000000000000..851a2cfc5147 --- /dev/null +++ b/changelog/scope-array-on-stack.dd @@ -0,0 +1,26 @@ +Array literals assigned to `scope` array variables are now allocated on the stack + +Formerly, they were always allocated with the Garbage Collector, making it unavailable in `@nogc` or `-betterC` code. +This led to frequent use of the following workaround: + +--- +void main() @nogc +{ + int[3] buffer = [10, 20, 30]; + int[] arr = buffer[]; +} +--- + +This can now be written in a single line: + +--- +void main() @nogc +{ + scope int[] arr = [10, 20, 30]; +} +--- + +This only applies to array elements that are Plain Old Data, proper destruction cannot be ensured when the array is not managed by the GC. + +Note: checking that a `scope` variable actually doesn't escape is only done in `@safe` code. +In a `@system` function, it's your own risk when you corrupt memory by returning a `scope int[]`. diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 40c1f55e211e..e2d7c41cd5e9 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -1059,6 +1059,12 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor if (f.tookAddressOf) f.tookAddressOf--; } + else if (auto ale = ex.isArrayLiteralExp()) + { + // or an array literal assigned to a `scope` variable + if (!dsym.type.nextOf().needsDestruction()) + ale.onstack = true; + } } Expression exp = ei.exp; diff --git a/compiler/src/dmd/e2ir.d b/compiler/src/dmd/e2ir.d index 5ddcd40e4d38..5ea0136780a5 100644 --- a/compiler/src/dmd/e2ir.d +++ b/compiler/src/dmd/e2ir.d @@ -3868,7 +3868,7 @@ elem* toElem(Expression e, IRState *irs) elem *e; if (dim > 0) { - if (tb.ty == Tsarray || + if (ale.onstack || tb.ty == Tsarray || irs.Cfile && tb.ty == Tpointer) { Symbol *stmp = null; diff --git a/compiler/src/dmd/expression.d b/compiler/src/dmd/expression.d index 42b4dd45c09c..21f5cc76a4b6 100644 --- a/compiler/src/dmd/expression.d +++ b/compiler/src/dmd/expression.d @@ -2949,7 +2949,7 @@ extern (C++) final class ArrayLiteralExp : Expression Expressions* elements; OwnedBy ownedByCtfe = OwnedBy.code; - + bool onstack = false; extern (D) this(const ref Loc loc, Type type, Expressions* elements) { diff --git a/compiler/src/dmd/expression.h b/compiler/src/dmd/expression.h index c9e397871068..79bc5288577a 100644 --- a/compiler/src/dmd/expression.h +++ b/compiler/src/dmd/expression.h @@ -419,6 +419,7 @@ class ArrayLiteralExp final : public Expression Expression *basis; Expressions *elements; OwnedBy ownedByCtfe; + bool onstack; static ArrayLiteralExp *create(const Loc &loc, Expressions *elements); static void emplace(UnionExp *pue, const Loc &loc, Expressions *elements); diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index d568de1d4578..88abb141f398 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -6701,7 +6701,7 @@ struct UnionExp final char complexexp[80LLU]; char symoffexp[72LLU]; char stringexp[60LLU]; - char arrayliteralexp[57LLU]; + char arrayliteralexp[58LLU]; char assocarrayliteralexp[57LLU]; char structliteralexp[95LLU]; char compoundliteralexp[48LLU]; @@ -6896,6 +6896,7 @@ class ArrayLiteralExp final : public Expression Expression* basis; Array* elements; OwnedBy ownedByCtfe; + bool onstack; static ArrayLiteralExp* create(const Loc& loc, Array* elements); static void emplace(UnionExp* pue, const Loc& loc, Array* elements); ArrayLiteralExp* syntaxCopy() override; diff --git a/compiler/src/dmd/nogc.d b/compiler/src/dmd/nogc.d index 78575c2684b0..7ddeeecae9b6 100644 --- a/compiler/src/dmd/nogc.d +++ b/compiler/src/dmd/nogc.d @@ -99,7 +99,7 @@ public: override void visit(ArrayLiteralExp e) { - if (e.type.ty != Tarray || !e.elements || !e.elements.dim) + if (e.type.ty != Tarray || !e.elements || !e.elements.dim || e.onstack) return; if (f.setGC()) { diff --git a/compiler/test/runnable/test20734.d b/compiler/test/runnable/test20734.d index b3c5916ada50..bd012bba19e3 100644 --- a/compiler/test/runnable/test20734.d +++ b/compiler/test/runnable/test20734.d @@ -17,6 +17,14 @@ extern(C) int main() nothrow @nogc @safe takeScopeSlice([ S(1), S(2) ]); // @nogc => no GC allocation (() @trusted { assert(numDtor == 2); })(); // stack-allocated array literal properly destructed assert23100([]); + + // https://issues.dlang.org/show_bug.cgi?id=22306 + // scope array variable should be stack allocated + scope int[] sa = [10, 20]; + assert(sa[0] == 10); + assert(sa[1] == 20); + assert(sa.length == 2); + return 0; }