Bug 23676 – Static foreach hangs compilation for some time

Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2023-02-07T15:57:31Z
Last change time
2023-02-22T09:26:16Z
Keywords
performance, pull
Assigned to
No Owner
Creator
Bradley Chatha

Comments

Comment #0 by bradley — 2023-02-07T15:57:31Z
The main, (mostly) reduced code is here: https://gist.github.com/BradleyChatha/873077996b36c9f575fa2410e03705d5 DMD version: DMD64 D Compiler v2.101.2 LDC2 version (has same issue): LDC - the LLVM D compiler (1.30.0): based on DMD v2.100.1 and LLVM 14.0.6 built with LDC - the LLVM D compiler (1.28.1) Default target: arm64-apple-darwin21.6.0 Host CPU: cyclone http://dlang.org - http://wiki.dlang.org/LDC Main issue: If you don't wrap the body of the `static if` on Line 70 inside curly braces, the compiler begins to error out with the following: ``` to_report.d(85): Error: `@safe` function `lexer.Lexer.nextVaryingLengthToken!(safePeek, Operator).nextVaryingLengthToken` cannot call `@system` function `lexer.Lexer.nextVaryingLengthToken!(safePeek, Operator).nextVaryingLengthToken.tryLexLongerOperators!(noreturn).tryLexLongerOperators` to_report.d(58): `lexer.Lexer.nextVaryingLengthToken!(safePeek, Operator).nextVaryingLengthToken.tryLexLongerOperators!(noreturn).tryLexLongerOperators` is declared here ``` It then proceeds to hang and use max out its CPU usage for a few minutes, before exiting. However, if you wrap the static if's body in braces, it will succesfully compile without the weird performance issues. Side issue: I'm very uncertain about the cause of the hang; if you delete any field within the `Token.Type` enum, the hang reduces to a couple of seconds, instead of minutes.
Comment #1 by razvan.nitu1305 — 2023-02-07T17:44:52Z
This is a serious issue. Bumping severity to "major"
Comment #2 by bradley — 2023-02-07T19:12:13Z
Even more strangely: If you delete any line within the body of the case statement that is guarded by the fault `static if`, compilation succeeds. What a very strange edge case this is hitting.
Comment #3 by bradley — 2023-02-07T19:13:53Z
Marking the nested function as @safe prevents the error message; allows the compilation to succeed, however the compiler does still hang for a while before it finishes.
Comment #4 by razvan.nitu1305 — 2023-02-10T12:05:54Z
I manually reduced it to get rid of phobos and other unrelated features: module lexer; enum Type { operatorSlash, operatorSlashEqual, operatorAnd, operatorAndEqual, operatorAndAnd, operatorQuestion, operatorDollar, operatorEqual, operatorEqualEqual, operatorStar, operatorStartEqual, } struct Lexer { size_t _index; @safe: void nextOperator() { const couldLex = nextVaryingLengthToken(); } // Complex reading // bool nextVaryingLengthToken() { alias TokenTypes = __traits(allMembers, Type); bool tryLexLongerOperators(alias TokenType)() { static foreach(type; TokenTypes) { _index++; if(tryLexLongerOperators!type) return true; return true; } } return tryLexLongerOperators!noreturn; } } My hunch is that `static foreach` ends up generating too many recursive calls to tryLexLongerOperators, but I don't know exactly what causes the hang.
Comment #5 by b2.temp — 2023-02-11T02:58:21Z
The `static foreach` is not great (btw you can drop `static`) but the real culprit is the access to the struct member `_index` from the nested functions. That would be a problem of counter performance caused by the creation of closures. kcachegrind shows that the costy thing would be a cycle between `FuncDeclaration.needsClosure` and checkEscapingSiblings.
Comment #6 by bradley — 2023-02-14T17:40:46Z
Yeah, it does appear to be exponential. If you change the static foreach to include a loop index; and then guard its body under a `static if(i < 1)`, the slowdown appears to start at i < 8. So in other words, my code is bad and I should feel bad ;) (or more seriously it could be a combination of bad code and a bad pathway in the frontend). Curiously, `i < 1` allows the function to compile perfectly fine however `i < 2` makes you run into the `@safe function can't call @system function` error. Is that a seperate attribute inference bug or am I missing something, because you can easily add @safe onto the nested function to solve that problem.
Comment #7 by bradley — 2023-02-14T17:43:16Z
Though that also still doesn't explain the strange behaviour where using a bracefull body works fine, without causing the hang or the inference error, and using a braceless body causes the errors and the hang.
Comment #8 by dkorpel — 2023-02-15T15:58:58Z
Reduced a bit further: ``` void f() { int i; bool g(int I)() { static foreach(j; 0..11) { i++; return g!j(); } } g!0; } ``` The 11 is the number of enum members of Type. The amount of calls to `checkEscapingSiblings` grows exponentially based on the loop length, which is why compilation hangs.
Comment #9 by dlang-bot — 2023-02-15T16:58:38Z
@dkorpel created dlang/dmd pull request #14886 "Fix 23676 - Static foreach hangs compilation for some time" fixing this issue: - Fix 23676 - Static foreach hangs compilation for some time https://github.com/dlang/dmd/pull/14886
Comment #10 by dlang-bot — 2023-02-22T09:26:16Z
dlang/dmd pull request #14886 "Fix 23676 - Static foreach hangs compilation for some time" was merged into master: - f0b5ce49c72caa6c9393195b46adf32dd503cd92 by Dennis Korpel: Fix 23676 - Static foreach hangs compilation for some time https://github.com/dlang/dmd/pull/14886