This adds a clang option to disable inline line tables. When it is used,
the inliner uses the call site as the location of the inlined function instead of
marking it as an inline location with the function location.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
- Build Status
Buildable 39468 Build 39486: arc lint + arc unit
Event Timeline
+ other debug info people
llvm/docs/LangRef.rst | ||
---|---|---|
1444 | This is a string attribute, so it should have quotes around it. | |
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
1439 | Let's check hasFnAttribute out of the loop so we aren't doing string hash lookups in a loop. | |
1443–1456 | Let's actually try to reuse this !CalleeHasDebugInfo code path when this function attribute is present. They should do the same thing. |
As per the bug - I'm not super inclined towards a function attribute here (& even if it's going to be an inliner rather than debug info generation/backend thing (which I'm more on the fence about - think I'm happy with either direction there, really) - I'd still be inclined towards it living in the DICompileUnit with other debug info things, rather than a function attribute). But happy to hear what other folks (especially the usual debug info cabal - @aprantl @probinson @JDevlieghere etc) think.
Looks good, although I'm not familiar enough with frontend things to approve IMO.
Using a function attribute seems fine and appropriate too -- although CU flags is another thing I'm unfamiliar with, so I can't really offer an opinion.
I don't think the implementation is correct, see inline comments.
clang/include/clang/Driver/Options.td | ||
---|---|---|
1963 | As a DWARF person, this option name is a little confusing since in DWARF inline info is part of debug info, not the line table, but few end-users would actually know. I would probably have called it -gno-inline-info or -gno-inlined-functions. I don't have strong feelings about it though. | |
llvm/docs/LangRef.rst | ||
1445 | Same comment for the attribute. | |
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
1444 | This will probably cause some IR Verifier failures and very confusing debug info when inlining dbg.value intrinsics. The correct thing to do here is probably to assign line 0 to the inlined instructions and remove all debug info intrinsics. Otherwise the inlined variables will show up in the parent frame, which will screw up debugging. |
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
---|---|---|
1444 | cf. getMergedLocation() for how to do this. |
clang/include/clang/Driver/Options.td | ||
---|---|---|
1963 | The other two options we have that control this stuff are -gmlt / -gline-tables-only. gmlt stands for "g minimal line tables". So, our command line interface talks about "line tables" already, and IMO we should stick with it, even if it's not really a table after all. And, technically, this option will greatly affect the .debug_line section. The inlined source locations are normally present in .debug_line, and this change suppresses them. Instead, the debugger will appear to be stopped at the inlined call site. | |
llvm/docs/LangRef.rst | ||
1445 | Maybe we could use more precise wording rather than talking about tables. Maybe we should describe what happens from the user perspective, something like: When this attribute is present and set to true, the inliner will discard source locations while inlining code into the current function. Instead, the source location of the call site will be used for all inlined code. Breakpoints set on code that was inlined into the current function will not fire during the execution of the inlined call sites. If the debugger stops inside an inlined call site, it will appear to be stopped at the outermost inlined call site. | |
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
1443–1456 | This suggestion makes less sense in light of the need to remove variable information. Use your best judgement. | |
1444 | Ah, yes, we should erase all debug info for inlined variables while inlining in this mode. |
-Remove intrinsics debug info
-Add inliner test
-Add to function attribute description
I guess the commit message shouldn't say "[CodeView] Add option to disable inline line tables." It's really an option for all debug info. You could put "[DebugInfo]" on there, or just drop the tag.
I would still prefer no-inline-info or no-inline-debuginfo over no-inline-linetables and a line 0 location for the inlined instructions. Other than that the patch is now safe.
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
---|---|---|
1431 | I still think an artificial (line 0) location would be less misleading for debuggers, profilers, and optimization remarks. |
Based on what we learned in https://llvm.org/PR43530, I think we still want to use the location of the call site and not line zero. :(
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
---|---|---|
1431 | That will cause problems for us in practice. There's discussion about this in D68747. Since that change, we treat line zero the same as "no location". If there are no locations in a basic block, then the whole block inherits the line number from the block layout predecessor, which could be unrelated. Keeping the inlined call site location gives us the highest likelihood that "step over" will stop at the next statement. Widely applying line zero to entire basic blocks will put us in that situation more often. We could certainly write a pass to backfill better source locations, but it seems preferable to not put ourselves in that position in the first place. However, the effect you mention on profilers and optimization remarks is real and concerning. Users should have the power to work around it by removing the flag that applies this attribute, which makes me feel like we should go forward with this as is. If this develops into a real usability problem, we can leave the attribute as is and move the implementation into the backend. |
- Fixes for DbgInfoIntrinsic type and change test cmd
Reverted the line 0 change - I wasn't sure if it would be an issue since
the debugger doesn't step through those lines.
llvm/lib/Transforms/Utils/InlineFunction.cpp | ||
---|---|---|
1428 | Will this work if the dbg.value is the first instruction of a basic block? I'd expect eraseFromParent to return a new iterator pointing to FI->begin(), then operator-- to back up to "before begin", which would probably crash or assert. This would make a good test case and shouldn't be too hard. You can try inlining foo in this example: void bar(); int foo(bool cond, int x) { if (cond) { x = 42; // should set up a dbg.value at BB start bar(); // block select formation } return x; } |
Since that change, we treat line zero the same as "no location". If there are no locations in a basic block, then the whole block inherits the line number from the block layout predecessor, which could be unrelated. Keeping the inlined call site location gives us the highest likelihood that "step over" will stop at the next statement.
Who is "we" in this context? The CodeView backend?
As far as DWARF is concerned (and LLVM mostly inherits the DWARF semantics) line 0 is well-defined and means compiler-generated code or otherwise no unambiguous source location. DWARF-based debuggers know to skip over instructions with line 0.
Is the problem that CodeView doesn't have this concept, or does the Windows debugger no know how to deal with it (or both)?
I'm feeling rather strongly that that LLVM should not be emitting wrong debug info to work around bugs in a debugger. I understand that sometimes this isn't possible because we don't control the consumers. The correct thing to do here is to guard the workaround by a debugger tuning flag. For DWARF, we do want line 0 here.
(+1 to all that, FWIW)
Though I think in this case since it's got to be handled during the transformation (rather than as an after the fact choice at debug-info-emission time) it might not be practical to guard by a debugger tuning flag. It could/should be guarded though, but may just have to be guarded by the format (not that we have any other debuggers consuming CodeView anyway, so I think it's sufficient here).
Yes, the CodeView backend, sorry for the ambiguity.
As far as DWARF is concerned (and LLVM mostly inherits the DWARF semantics) line 0 is well-defined and means compiler-generated code or otherwise no unambiguous source location. DWARF-based debuggers know to skip over instructions with line 0.
Is the problem that CodeView doesn't have this concept, or does the Windows debugger no know how to deal with it (or both)?
Visual Studio in particular has been reported to have problems with line zero. It seems to treat it as some kind of error condition (no line available), and kicks the user over to the assembly view.
I'm feeling rather strongly that that LLVM should not be emitting wrong debug info to work around bugs in a debugger. I understand that sometimes this isn't possible because we don't control the consumers. The correct thing to do here is to guard the workaround by a debugger tuning flag. For DWARF, we do want line 0 here.
I don't think we want to emit line zero here. The use case for this flag is to allow the user to ask the compiler to attribute code from inlined call sites to the call site itself. Maybe the user doesn't want to see the details of push_back.
I actually went ahead and experimented with how gdb handles line zero. I compiled the following program like so:
$ cat t.cpp volatile int x; static inline void foo() { ++x; ++x; } int main() { ++x; foo(); ++x; foo(); ++x; return x; } $ clang -g -O2 t.cpp -S -emit-llvm -o t.ll $ sed -e 's/DILocation.line:.*column:.*, scope\(.*inlinedAt: .*\))/DILocation(line: 0, column: 0, scope\1)/' -i t.ll $ clang -g t.ll -o t.exe $ gdb --args t.exe ...
When I step through main with s in gdb, it stops on the ++x lines, and skips the foo() lines completely. I don't think that's the desired behavior, the desired behavior is to treat the body of foo as a single line.
To give more context, back in 2013, @probinson asked if we should add a similar feature here:
http://lists.llvm.org/pipermail/cfe-dev/2013-November/033765.html
This was around the time that inlinedAt locations were being sorted out, I think. His proposed name for this flag was -gno-inlined-scopes. I believe nothing ever came of that discussion, and we continued on our way until today in 2019, when one of our users requested a similar feature. I vaguely recalled the discussion from 2013, but I had forgotten the details, so I figured that it might be a generally useful feature that others would appreciate. So, I suggested that @akhuang add a real flag for it, and that it should work for DWARF and CodeView. That's part of why I figured it would be good to implement it in the inliner, so we don't have to do the work twice.
Given the behavior of gdb shown above, I don't think either set of users, Chromium developers or Sony licensees, have a use case for a flag that applies line zero to inlined functions. I don't think that's what they are asking for. Paul outlined what users actually asked for back then here:
http://lists.llvm.org/pipermail/cfe-dev/2013-November/033782.html
I think users are asking for a flag that attributes the code that the inliner clones to the call site. And, I can't see any reason not to give it to them. Does that seem reasonable?
Apologies for missing this until now. Our email system keeps dropping stuff sent by Phabricator.
FTR, since @rnk has mentioned my years-ago writings, what Sony has internally nowadays is a little different than what I said back then. We have an option spelled -gno-inlined-scopes which is slightly tricky to describe precisely, but the intent is that for debug-info purposes, certain functions appear to be empty. That is, the declaration is still emitted (which is different from nodebug) but the generated IR has no source locations.
We implement this in CodeGenFunction::GenerateCode, rather than down in LLVM. We found that the in-LLVM implementation would affect exactly those functions that were actually inlined by the optimizer, rather than those that had some kind of "inline me" indication in the source. This caught too many cases where they compiler inlined a function because of a cost heuristic rather than a source indication, and made the set of functions with no source locations somewhat unpredictable.
What is an "inline me" indication? The always_inline attribute, the inline keyword, or a class method whose definition is in-class.
Which functions are affected? Everything with always_inline regardless of optimization level, and also anything to which optimization applies and is marked inline or is defined in-class. ("Optimization applies" means compiling at -O1 or higher, and the function does not have OptimizeNoneAttr.)
This might all sound overly complicated, but after (I think) three attempts, it appears to filter out the set of functions that can be considered too small to bother with, and/or well-debugged and unlikely to be the cause of a problem, and of course everything in STL which by nature pretty much all has to have in-class definitions. It's a heuristic that is (at least to an extent) under the control of the programmer, and is a rule based strictly on source annotations and command-line options, rather than arbitrary choices made by the compiler (which possibly vary from build to build).
I haven't taken the time to read through all the prior comments and re-read the PR etc, but I did want to report what it is that we actually do now, and which seems to keep the users-who-care happy enough over the past few years.
Thanks for the info. That's interesting, and in the end I suppose it's pretty different from the behavior we had in mind for this flag.
I chatted offline with @dblaikie and he suggested perhaps it would be better to motivate this flag as one of the many existing knobs we have for controlling the volume of debug info produced by the debugger. We already have two major examples of this:
- -gline-tables-only / -gmlt / -g1
- -flimit-debug / -fno-standalone-debug
This flag exists to give the user the ability to produce even less debug info, if that debug info seems to be putting pressure on the tools downstream: the linker or the debugger. We are motivated by one tool in particular at the moment, but if we're going to take the time to add a knob, we might as well make it work for DWARF. If the user cares enough to find this flag, it seems more user friendly to make it behave the same rather than making it format-dependent.
@aprantl hit accept on a previous diff, but Amy changed the line zero behavior after that, so I just wanted to reconfirm that the current behavior is OK.
I agree that it would make sense to have a -ginline-info-threshold=<#insns> or -gno-small-inline-functions with a hardcoded threshold to implement the feature Paul described, and this patch seems to be a step in that direction, with the threshold being hardcoded to 0.
We are motivated by one tool in particular at the moment, but if we're going to take the time to add a knob, we might as well make it work for DWARF.
Here you got me confused: When I read "we might as well make it work for DWARF", I read that as "we should emit the inlined instructions with line 0 under a DWARF debugger tuning". But that reading seems to to contradict your next sentence:
If the user cares enough to find this flag, it seems more user friendly to make it behave the same rather than making it format-dependent.
Can you clarify?
OK. :)
We are motivated by one tool in particular at the moment, but if we're going to take the time to add a knob, we might as well make it work for DWARF.
Here you got me confused: When I read "we might as well make it work for DWARF", I read that as "we should emit the inlined instructions with line 0 under a DWARF debugger tuning". But that reading seems to to contradict your next sentence:
If the user cares enough to find this flag, it seems more user friendly to make it behave the same rather than making it format-dependent.
Can you clarify?
If we use line zero for DWARF, gdb will not behave in the way documented by the function attribute in LangRef. I was the one who suggested the wording there, so maybe we could come up with new wording that describes what the user should expect in the debugger when using line zero. However, given the behavior I show below, I have a hard time imagining the use case for it.
I applied the version of this patch that uses getMergedLocation, compiled this program, and ran it under gdb:
volatile int x; static inline void foo() { ++x; *(volatile int*)0 = 42; // crash ++x; } int main() { ++x; // line 8 foo(); // line 9 ++x; return x; }
If we apply line zero, the debugger stops on line 8:
Program received signal SIGSEGV, Segmentation fault. 0x000000000040111e in main () at t.cpp:8 8 ++x; (gdb) bt #0 0x000000000040111e in main () at t.cpp:8
The inline frame is gone, as expected for this flag, but the current location does not reflect the site of the call to foo. So, if we want it to behave as documented, we have to put the call site location on some instructions.
Alternatively, if I arrange things like this, the crash is attributed to line return x, which is completely unrelated to the inline call site:
static inline void foo() { ++x; if (x) { *(volatile int*)0 = 42; // crash __builtin_unreachable(); } ++x; }
This means that if line zero is used, the source location shown in the debugger becomes sensitive to code layout, which is arbitrary.
These experiments are convincing me that, in general, line zero isn't that helpful for DWARF consumers. If the goal is to get smooth stepping, we may want to refocus on getting reliable is_stmt bits in the line table.
These experiments are convincing me that, in general, line zero isn't that helpful for DWARF consumers. If the goal is to get smooth stepping, we may want to refocus on getting reliable is_stmt bits in the line table.
If you mean, it's not useful for identifying the call site as the implicit source for the inlined function, well, yeah. Line 0 means "there is no useful source location to attach to this instruction" and it's not what you want here. Based solely on the description of /Zo- in the Microsoft docs, I'd guess it behaves more like Sony's original implementation: Instead of attaching the call-site location using InlinedAt, just replace the original source location with the call-site location.
Adrian's point that line 0 would be less misleading for profilers etc is true, but as a couple of Dev Meeting discussions suggested, there is no one solution that will please all consumers (unless we invent a more complicated line table that provides everyone with the answers they want). My thinking is that if the user *asked* to suppress inlined scopes, then profiling is not their major concern, and there's no benefit to using line 0 here.
I didn't realize that GDB also had problems; I thought that this was a problem that only affected Windows debuggers.
I applied the version of this patch that uses getMergedLocation, compiled this program, and ran it under gdb:
volatile int x; static inline void foo() { ++x; *(volatile int*)0 = 42; // crash ++x; } int main() { ++x; // line 8 foo(); // line 9 ++x; return x; }If we apply line zero, the debugger stops on line 8:
Program received signal SIGSEGV, Segmentation fault. 0x000000000040111e in main () at t.cpp:8 8 ++x; (gdb) bt #0 0x000000000040111e in main () at t.cpp:8The inline frame is gone, as expected for this flag, but the current location does not reflect the site of the call to foo. So, if we want it to behave as documented, we have to put the call site location on some instructions.
Alternatively, if I arrange things like this, the crash is attributed to line return x, which is completely unrelated to the inline call site:
static inline void foo() { ++x; if (x) { *(volatile int*)0 = 42; // crash __builtin_unreachable(); } ++x; }This means that if line zero is used, the source location shown in the debugger becomes sensitive to code layout, which is arbitrary.
These experiments are convincing me that, in general, line zero isn't that helpful for DWARF consumers. If the goal is to get smooth stepping, we may want to refocus on getting reliable is_stmt bits in the line table.
The Swift compiler is far more aggressive in using line 0 than Clang, and consequently LLDB is much better at handling line 0 than even GDB, and that can skew my perception :-)
Give how popular GDB is, I don't want to intentionally break compatibility with it, so I think this patch is okay. If we wanted we can put an if-debugger-tuning-is-LLDB-getMergedLocation condition in. Otherwise documenting that this is necessary for compatibility with popular debuggers, seems fine to me, too.
I don't think the behavior Reid described would be a "problem" - it seems to me like the only behavior the debugger could provide if those instructions are attributed to line zero.
I applied the version of this patch that uses getMergedLocation, compiled this program, and ran it under gdb:
volatile int x; static inline void foo() { ++x; *(volatile int*)0 = 42; // crash ++x; } int main() { ++x; // line 8 foo(); // line 9 ++x; return x; }If we apply line zero, the debugger stops on line 8:
Program received signal SIGSEGV, Segmentation fault. 0x000000000040111e in main () at t.cpp:8 8 ++x; (gdb) bt #0 0x000000000040111e in main () at t.cpp:8The inline frame is gone, as expected for this flag, but the current location does not reflect the site of the call to foo. So, if we want it to behave as documented, we have to put the call site location on some instructions.
Alternatively, if I arrange things like this, the crash is attributed to line return x, which is completely unrelated to the inline call site:
static inline void foo() { ++x; if (x) { *(volatile int*)0 = 42; // crash __builtin_unreachable(); } ++x; }This means that if line zero is used, the source location shown in the debugger becomes sensitive to code layout, which is arbitrary.
These experiments are convincing me that, in general, line zero isn't that helpful for DWARF consumers. If the goal is to get smooth stepping, we may want to refocus on getting reliable is_stmt bits in the line table.
The Swift compiler is far more aggressive in using line 0 than Clang, and consequently LLDB is much better at handling line 0 than even GDB, and that can skew my perception :-)
What behavior does LLDB have in the example Reid gave?
Give how popular GDB is, I don't want to intentionally break compatibility with it, so I think this patch is okay. If we wanted we can put an if-debugger-tuning-is-LLDB-getMergedLocation condition in. Otherwise documenting that this is necessary for compatibility with popular debuggers, seems fine to me, too.
Seems like this is good to be committed then. And it sounds like implementing more thresholds would be useful to do in the future.
As a DWARF person, this option name is a little confusing since in DWARF inline info is part of debug info, not the line table, but few end-users would actually know. I would probably have called it -gno-inline-info or -gno-inlined-functions. I don't have strong feelings about it though.