-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DwarfCompileUnit::createAndAddScopeChildren
gobbles stack
#92724
Comments
@llvm/issue-subscribers-debuginfo Author: Jubilee (workingjubilee)
By far the most common stack overflow bug report nowadays for rustc is rustc dying in LLVM while emitting debuginfo. Lately `DwarfCompileUnit::createAndAddScopeChildren` has seemed to acquire the power of recursing on itself and destroying the stack essentially without assistance. People are setting hundreds of megabytes for `RUST_MIN_STACK` to get their code to compile. That's... closer to our resident set size in most executions?
Unlike other reports like #76920 this has been happening with our stock rustc distribution, as far as I can tell, and our bundled LLVM. |
Some reproducer(s) and, if it's a regression, an example of this not being a problem on earlier versions, would be helpful. |
Textual LLVMIR from a... I can't call it "minimal", but it's a reproducer: createandaddsigsev.ll It's hard to tell whether this is a "regression" or not, honestly. I hope the reason this is the most common segfault lately is because we've won the game of whack-a-mole with the others. |
Might end up leaving this to someone else to look at, but I got a flame graph just cpu-based, didn't look at the memory usage and I didn't see a deep stack related to I guess maybe the deep recursion happened too quick/didn't get sampled. valgrind detects the recursion and diagnoses I guess this is some deep inlining or many nested lexical scopes? (I haven't looked at the IR to try to find the shape of it... ) - generally LLVM won't scale well with very large functions, though I guess a lot of inlining doesn't necessarily mean a large function and deeply inlined code only really impacts DWARF handling - outside of DWARF, everything related to inlined code just "goes away"... |
Hmm, in a non-optimized build of the compiler it's stack-overflowing in I'm guessing this is some deeply recursive code? |
Hmm, nope, all these nestings appear to be lexical blocks... What is this code? We /might/ be able to make LLVM work better with it, but it seems pretty weird/not sure we'd want to do a lot of work to support it without understanding it better... |
Yeah, I mean, I wouldn't want you to do much more than the standard trick... I'd PR it myself except my C++ skills are bad enough I'd probably fuck it up somehow!... of hoisting recursion into iteration inside relevant functions, by having an inner function return the state of the "recursion" and then repeatedly calling that inner function to drive the "recursion" forward.
A Rust stack frame! ...in real programs, instead of this somewhat decontextualized example, this is "someone asked for full debuginfo and then declares MANY variables". The problem is that the breaking points get reached in programs people actually want to build rather than purely hypothetical ones. And not, like... absurd code like entire gate-by-gate CPU simulators, though I have gotten a report of that, but "ordinary" things like cryptography algorithms. My understanding is that yes, inlining does play some part in it. But otherwise this is just a somewhat overly literalist interpretation of Rust. Now, it might be that there's a better way to do this, but we started doing it this specific way because we had issues with getting values to be emitted into debuginfo in a way we can easily predict. Otherwise we might do something else. It seems quite a bit has changed over time with LLVM's debug info handling, so if you have any recommendations as to how to reduce the frequency of this occurring on our side but which might require being aware of the last... 4 years of LLVM goings-on, I'm interested! |
Declaring a lot of variables shouldn't result in this sort of debug info - unless it's being generated somewhat esoterically. lexical_blocks are for literal language-level lexical blocks like anything with "{}" in C++, roughly. Is rust using a separate lexical_block for every variable lifetime? in C++, for instance, this code would have one lexical block:
Expressing the name overriding. One of the problems with this is that, at the call to But if rust is putting in new artificial scopes for every variable to avoid the "we're in the lexical scope, but the variable doesn't exist yet" problem - I'd /highly/ encourage not doing that, and accepting the less-than-ideal behavior that happens. Introducing a scope for every variable is likely not going to scale well with many pieces of the infrastructure here (compiler, object files, debuggers, symbolizers, etc) Perhaps you could show me a smaller example of rust with a few variables, the source code, the LLVM IR, and the resulting dwarf (llvm-dwarfdump to dump it)? |
I was curious about this, so I tried a small Rust example on CE: #[no_mangle]
pub fn square(num: i32) -> i32 {
let x = num;
let y = num;
x + y
} There are indeed separate !15 = !DILocalVariable(name: "x", scope: !16, file: !8, line: 3, type: !12, align: 4)
!16 = distinct !DILexicalBlock(scope: !7, file: !8, line: 3, column: 5)
!17 = !DILocalVariable(name: "y", scope: !18, file: !8, line: 4, type: !12, align: 4)
!18 = distinct !DILexicalBlock(scope: !16, file: !8, line: 4, column: 5)
Rust does seem to do this, but they are also not alone... Swift makes the same choice, as described in this 2023 blog post from @adrian-prantl and others. |
Yes. Rust supports shadowing, so it's possible... even common, in some styles of code I've seen... to have like 6 different declarations of a variable named I don't know of another way to model that so a debugger can understand except by saying "fuck it, new scope time". |
Huh, fascinating - thanks for the link. So, yeah, that does mean that the DWARF's going to grow, and recursion's going to get pretty deep even with a linear sequence of local variables. I guess this isn't a problem you've had, @adrian-prantl ? Just haven't hit large enough inputs/enough local variables for it to be a problem? |
One quirk that I have been noticing regarding Rust usage is that it has become an attractive target for code generators. And many of the breaking inputs (that I've gotten reports of) include partially generated or fully generated code, often extracted from a Rocq proof. And I mean, I can tell the person who "only" needed to double rustc's stack size "you'll have to try harder", but for the person who had to double it 4~5 times, well...
Would that address this issue mentioned in the Swift post while allowing us to bring things down to smaller numbers of lexical blocks?
|
Potentially so (though there may be some limitation I'm missing with such an approach)... For future archaeologists, the attribute is |
Yeah - |
cc @khuey this might be a vaguely interesting thread if you aren't already aware of it. |
By far the most common stack overflow bug report nowadays for rustc is rustc dying in LLVM while emitting debuginfo. Lately
DwarfCompileUnit::createAndAddScopeChildren
has seemed to acquire the power of recursing on itself and destroying the stack essentially without assistance. People are setting hundreds of megabytes forRUST_MIN_STACK
to get their code to compile. That's... closer to our resident set size in most executions?Unlike other reports like #76920 this has been happening with our stock rustc distribution, as far as I can tell, and our bundled LLVM.
The text was updated successfully, but these errors were encountered: