Bug 23112 – code passes @nogc, allocates anyway

Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2022-05-16T07:54:43Z
Last change time
2022-08-08T17:22:13Z
Keywords
betterC, pull, safe
Assigned to
No Owner
Creator
Susan
See also
https://issues.dlang.org/show_bug.cgi?id=23160, https://issues.dlang.org/show_bug.cgi?id=14730, https://issues.dlang.org/show_bug.cgi?id=23287

Comments

Comment #0 by su+dlangissues — 2022-05-16T07:54:43Z
the following code allocates, bypassing @nogc and -vgc. struct Forward(alias F) { auto opCall(Ts...)(Ts args) { return F(args); } } auto bar(T)(T a) @nogc nothrow @safe { auto f() { return a; } Forward!f val; return val; }
Comment #1 by dkorpel — 2022-05-16T11:31:18Z
An uninstantiated template generates no code, so here's the test modified a bit: ``` struct Forward(alias F) { auto call()() { return F(); } } auto bar(int a) @nogc nothrow @safe { auto f() { return a; } return Forward!f(); } extern(C) void main() @nogc { assert(bar(3).call() == 3); } ``` Compile with -betterC and you get: undefined reference to '_d_allocmemory'
Comment #2 by bugzilla — 2022-06-05T03:24:09Z
(In reply to Dennis from comment #1) > auto call()() Rewriting as: > auto call() and it gives the error: test2.d(8): Error: function `test2.bar` is `@nogc` yet allocates closures with the GC test2.d(10): test2.bar.f closes over variable a at test2.d(8) which is what we expect. The trouble with the template version is the semantic3() of bar() should run the semantic3() of Forward.call, which is necessary to discover that bar() requires a closure. But it does not do the semantic3() of Forward.call until after semantic3() for bar() has completed.
Comment #3 by bugzilla — 2022-06-05T04:31:19Z
Some further study. The semantic analysis for call()() cannot be done during the semantic analysis for bar() or Forward, because the template arguments to call()() are as yet unknown. We only find out the arguments when main() takes the return value (an instance of Forward) and calls it. To fix this, the argument `f` supplied to the parameter `alias F` of `Forward` must be regarded as escaping when the instance of `Forward` is returned from `bar()`. Not doing this is a major hole in the @safe system.
Comment #4 by bugzilla — 2022-06-05T04:56:52Z
A simpler version of this is filed as https://issues.dlang.org/show_bug.cgi?id=23160 which should be fixed first.
Comment #5 by dlang-bot — 2022-06-05T07:43:13Z
@WalterBright created dlang/dmd pull request #14183 "fix Issue 23112 - code passes @nogc, allocates anyway" fixing this issue: - fix Issue 23112 - code passes @nogc, allocates anyway https://github.com/dlang/dmd/pull/14183
Comment #6 by bugzilla — 2022-06-05T19:19:48Z
Regarding `f` as escaping produces far too many false positives. 1. semantic3 (the function body) analysis is done for a function. No gc use is found, and no escapes of an aliased reference are found, so the function is inferred @nogc. This also enables some NRVO optimizations. 2. code is generated that depends on the @nogc inferred attribute of the function, and/or does NRVO. 3. code is generated via another template getting expanded that escapes the alias reference. 4. this gets detected by the code the decides whether to generate a closure or not, and now (2) is invalid, but it's too late.
Comment #7 by dlang-bot — 2022-07-22T00:55:21Z
dlang/dmd pull request #14183 "fix Issue 23112 - code passes @nogc, allocates anyway" was merged into master: - 7797df8b69ef91210753293060ca9454f078d35d by Walter Bright: fix Issue 23112 - code passes @nogc, allocates anyway https://github.com/dlang/dmd/pull/14183