Closed llvmbot closed 2 years ago
mentioned in issue llvm/llvm-bugzilla-archive#46848
mentioned in issue llvm/llvm-project#44998
Created llvm/llvm-bugzilla-archive#46848 to track the remaining cleanup work. Closing this issue.
Vedant, while I am trying to land the narrow fix, could you outline the more involved real fix of getting rid of the duplicate DBG_VALUEs for COPYs?
Especially the part about:
try to make up for any loss in location coverage by making generic improvements to LiveDebugValues.
I saw that you pinged me on https://reviews.llvm.org/D80684. Would removing EntryValueCopyBackupKind from EntryDebugValues be the only required fix?
Also, how could I test that the eventual fix doesn't loose location coverage? Do you have specific test cases in mind?
Sorry for the delayed response.
What I had in mind was deleting "LiveInMap" from SelectionDAGISel::runOnMachineFunction (and everything that uses it). I tried this on a lark before commenting on 2020-05-01 and found that this resulted in ~15 or so test failures during check-llvm. Some of these are tests that are being overly strict about what they match -- these can be fixed up by mechanically updating the check lines. But others represent real regressions in location coverage.
AIUI, it should be the job of LiveDebugValues::transferRegisterCopy to track when a copy introduces a backup location for a variable, /not/ the job of the instruction selector. So (and I'm hand-waving a bit here), if we can fix the tests that regress by making transferRegisterCopy more aggressive, that could be a nice, general improvement.
My impression goes in the same way. I think the LiveDebugValues::transferRegisterCopy() should track all these copies.
As for measuring changes in location coverage, you can compile a stage2 clang binary with vs. without your changes, and use
llvm-dwarfdump --statistics
to collect location coverage stats.Btw, my understanding of Djordje's comment in https://reviews.llvm.org/D80684 is that EntryValueCopyBackupKind is a hack invented to work around SelectionDAG's insertion of DBG_VALUEs after copies in the entry block. We might be able to delete EntryValueCopyBackupKind after changing SelectionDAG, but not before. Yes. That piece of code from the SelectionDAG violates the premise (before LiveDebugValues) that each DBG_VALUE represents assignment of a variable (and changing of its value potentially); so the EntryValueCopyBackupKind is used to track these copies from SelectionDAG.
Btw, my understanding of Djordje's comment in https://reviews.llvm.org/D80684 is that EntryValueCopyBackupKind is a hack invented to work around SelectionDAG's insertion of DBG_VALUEs after copies in the entry block. We might be able to delete EntryValueCopyBackupKind after changing SelectionDAG, but not before.
Vedant, while I am trying to land the narrow fix, could you outline the more involved real fix of getting rid of the duplicate DBG_VALUEs for COPYs?
Especially the part about:
try to make up for any loss in location coverage by making generic improvements to LiveDebugValues.
I saw that you pinged me on https://reviews.llvm.org/D80684. Would removing EntryValueCopyBackupKind from EntryDebugValues be the only required fix?
Also, how could I test that the eventual fix doesn't loose location coverage? Do you have specific test cases in mind?
Sorry for the delayed response.
What I had in mind was deleting "LiveInMap" from SelectionDAGISel::runOnMachineFunction (and everything that uses it). I tried this on a lark before commenting on 2020-05-01 and found that this resulted in ~15 or so test failures during check-llvm. Some of these are tests that are being overly strict about what they match -- these can be fixed up by mechanically updating the check lines. But others represent real regressions in location coverage.
AIUI, it should be the job of LiveDebugValues::transferRegisterCopy to track when a copy introduces a backup location for a variable, /not/ the job of the instruction selector. So (and I'm hand-waving a bit here), if we can fix the tests that regress by making transferRegisterCopy more aggressive, that could be a nice, general improvement.
As for measuring changes in location coverage, you can compile a stage2 clang binary with vs. without your changes, and use llvm-dwarfdump --statistics
to collect location coverage stats.
Vedant, while I am trying to land the narrow fix, could you outline the more involved real fix of getting rid of the duplicate DBG_VALUEs for COPYs?
Especially the part about:
try to make up for any loss in location coverage by making generic improvements to LiveDebugValues.
I saw that you pinged me on https://reviews.llvm.org/D80684. Would removing EntryValueCopyBackupKind from EntryDebugValues be the only required fix?
Also, how could I test that the eventual fix doesn't loose location coverage? Do you have specific test cases in mind?
Vedant, sorry, this took a bit, but here is the change: https://reviews.llvm.org/D80819
I just took your patch and added a test for it. No other tests seem to have been affected by the change.
Could you have a look? Thanks!
@Tobias Ah, I didn't mean to hijack this thread. If you're still able to drive this forward, I think that'd be great. (Unfortunately I'm not sure I have the bandwidth to do so.)
To summarize things a bit, I see at least two possible solutions, and I suspect folks would be fine with either:
A narrow fix in SelectionDAG to add in the missing width check (more or less the patch I attached, plus maybe fixing up a few tests).
A more involved / long-term fix where we remove the part of SelectionDAG that tries to place DBG_VALUEs after copies, adjust the ~15 or so tests that fail as a result, and then try to make up for any loss in location coverage by making generic improvements to LiveDebugValues.
@Vedant: Would you like to take over this bug, as you clearly know more about the required fix than I do? Alternatively, I could try to start with your patch and see which tests fail...
Djordje wrote:
I remember we discussed about removing this when we were implementing the debug entry values support within LiveDebugValues pass. Only deleting that piece of code causes some tests failures and we did not take an in-depth analysis back then.
Ah yeah, I remember, at the time I reckoned the physreg argument location needed to be specified differently to the vreg location for some reason. I think the explanation above is probably closer to the truth,
Yes, I think the LiveDebugValues pass can handle the copies properly. Having that implemented, we can simplify the support for entry values within that pass. These extra DBG_VALUEs (that comes from the SelcetionDAG) before LiveDebugValues volatile the assumption "every dbg_value indicates new value within the MBB for the variable", so we needed to handle it as well.
Removing some of those workarounds would be great, they make up a large portion of LiveDebugValues.
Sorry, I have missed this, because I have changed my email address.
@Jeremy
I remember we discussed about removing this when we were implementing the debug entry values support within LiveDebugValues pass. Only deleting that piece of code causes some tests failures and we did not take an in-depth analysis back then.
@Vedant
If we can get away with deleting that whole chunk of SelectionDAG, and have a later pass do it right, that seems like the best outcome.
Yes, I think the LiveDebugValues pass can handle the copies properly. Having that implemented, we can simplify the support for entry values within that pass. These extra DBG_VALUEs (that comes from the SelcetionDAG) before LiveDebugValues volatile the assumption "every dbg_value indicates new value within the MBB for the variable", so we needed to handle it as well.
Add the missing width check to SelectionDAG Thanks for finding this Jeremy! I've attached a patch which adds the missing width check to SelectionDAG. This seems to 'fix' the problem :)
Looking through the radar history from [2] (rdar://8412415), I see that the logic to add DBG_VALUEs after copies was added to SelectionDAG in an attempt to address an optimized code debugging regression. The kernel engineers who filed the radar (circa the iOS 4.1 release.. so a lot has changed since then!) didn't seem to think the change was particularly effective.
If we can get away with deleting that whole chunk of SelectionDAG, and have a later pass do it right, that seems like the best outcome.
"but make the argument value available in the rest of the function in a vreg," if there was more than one block, of course.
Hi Tobias, thanks for digging into this and all the reproducers!,
The X86DAGToDAGISel pass seems to be creating the DBG_VALUES. And it seems to produce too many COPY operations:
NB: I think (90% confidence) isel deliberately avoids optimising the number of COPYs created, and instead lets the register allocate handle that,
Vedant wrote:
What I'd love to understand is why that
DEBUG_VALUE: fn1:z <- $edi
gets created in the first place. That just seems wrong. If we can prevent that from happening, we don't need to have later passes clean things up ex post facto
It looks like those DBG_VALUEs, and in fact all the DBG_VALUEs of the argument value, originate from here [0]. dbg.value intrinsics for arguments get picked out of SelectionDAGBuilder in EmitFuncArgumentDbgValue, joined up with the ABIs argument location, and then inserted by [0]. The loop on line 614 is what's generating additional DBG_VALUEs for copies.
Exactly why this code path exists is a bit of a mystery to me; I would imagine it's something to do with past limitations on tracking variable locations. Without this code, SelectionDAG would attach the argument dbg.value to a CopyFromReg(rdi), but make the argument value available in the rest of the function in a vreg, via something like this pseudo-dag: t1 = CopyFromReg(rdi) t2, ch = CopyToReg(t1, %0) If %0 wasn't allocated to $rdi, the variable location would have to follow the copy to reach the rest of the function; and this might have been a serious problem in the past. The commit messages of [1, 2], which add large portions of the code at [0] appear to have that as a motivation.
Today, LiveDebugValues can /probably/ propagate the variable location through such a COPY, although there are various circumstances where it won't.
[0] https://github.com/llvm/llvm-project/blob/446082b99f0bd9786d24edd3cd598dc4e5f64371/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp#L572 [1] https://reviews.llvm.org/rG1b08572a665402ce57771507b3bcbe235ff1ba82 [2] https://reviews.llvm.org/rG99ff76212a0b38ceae924b3a739a8cc6a132624a
Filed llvm/llvm-project#44998 regarding the lldb bugs.
Ok sounds good.
Will look into the following then (probably in this order):
1) Why X86DAGToDAGISel associates the 32bit DBG_VALUE with "z" (DBG_VALUE %1:gr32, $noreg, !"z", ...) 2) Find a way to remove the DBG_VALUEs when the COPYs are folded (in RegisterCoalescer and VirtRegMap) 3) Why lldb shows the wrong values for DWARF4
What I'd love to understand is why that
DEBUG_VALUE: fn1:z <- $edi
gets created in > the first place. That just seems wrong. If we can prevent that from happening, we don't need to have later passes clean things up ex post factoAs far as I understand it, this DEBUG_VALUE is created like this:
Fn1 takes a long, and the result of the multiplication is stored in an int variable. So X86DAGToDAGISel emits a COPY that uses a register subindex for 32bits, which through the following passes (Simple Register Coalescing, Virtual Register Rewriter) gets inlined into the multiplication as $edi (see the IR dumps above).
So do you refer to the fact that X86DAGToDAGISel emits these extra COPYs? I.e.: DBG_VALUE $rdi, $noreg, !"z", ... %0:gr64 = COPY $rdi
DBG_VALUE %0:gr64, $noreg, !"z", ... %1:gr32 = COPY %0.sub_32bit:gr64
DBG_VALUE %1:gr32, $noreg, !"z", ...
Yes: to be clear, the COPY instructions are fine, but the DBG_VALUE emitted for the copy is bogus (DBG_VALUE %1:gr32, $noreg, !"z", ...).
It's not always true that "z" is equal to the lower 32-bits of "z".
There are probably good reasons for emitting a DBG_VALUE after each COPY; maybe this helps with location availability when the source register gets killed but the copied register stays alive. But I think whatever logic is inserting those DBG_VALUEs is missing a width check.
Btw, if I change the simplified example to use long
in all places, there is no problem either.
What I'd love to understand is why that
DEBUG_VALUE: fn1:z <- $edi
gets created in > the first place. That just seems wrong. If we can prevent that from happening, we don't need to have later passes clean things up ex post facto
As far as I understand it, this DEBUG_VALUE is created like this:
Fn1 takes a long, and the result of the multiplication is stored in an int variable. So X86DAGToDAGISel emits a COPY that uses a register subindex for 32bits, which through the following passes (Simple Register Coalescing, Virtual Register Rewriter) gets inlined into the multiplication as $edi (see the IR dumps above).
So do you refer to the fact that X86DAGToDAGISel emits these extra COPYs? I.e.: DBG_VALUE $rdi, $noreg, !"z", ... %0:gr64 = COPY $rdi
DBG_VALUE %0:gr64, $noreg, !"z", ... %1:gr32 = COPY %0.sub_32bit:gr64
DBG_VALUE %1:gr32, $noreg, !"z", ... %2:gr32 = IMUL32rr %1:gr32(tied-def 0), %1:gr32, ...
While debugging, I also found the following problems in lldb:
1) lldb with dwarf4 uses DW_OP_GNU_entry_value, but doesn't save the value at the beginning of the function
Using clang -g produces dwarf4 (with DW_OP_GNU_entry_value instead of DW_OP_entry_value). With the example above that uses
int
and produces an entry_value, lldb says "z=4" when breaking at thereturn
stmt, although z had the initial value of "2" when the function was called.
Thanks for finding this! lldb doesn't support the GNU opcodes yet, but it definitely shouldn't print the wrong value. Mind filing a separate PR for lldb?
2) lldb with dwarf5 does not seem to respect DW_OP_entry_value at all.
When using clang -gdwarf-5, lldb says "z=
" when breaking at the return
stmt for the example withint
and the example withlong
.
Ouch. Well, I'm not sure how complete lldb's support for dwarf5 is. I'll ask around.
Given this example, and the problem analysis in Comment 9, I see the following possible solutions:
Solution 1) Create a pass that combines sibling DBG_VALUEs into one Use isParameter from the first one, and everything else from the last one.
Solution 2) Make LiveDebugValues.cpp deal with multiple DBG_VALUE before an instruction: Use isParameter from the first one, and everything else from the last one.
Solution 3) Change RegisterCoalescer and VirtRegMap to also remove the corresponding DBG_VALUEs.
Options (1) and (2) seem very narrow, imho neither address the root cause. Istm that "DEBUG_VALUE: fn1:z <- $edi" doesn't make any sense: consider what happens if the caller passes in z = (1 << 33).
(3) sounds good, we should have those utilities clean up dead DBG_VALUEs. Doing just (3) would "fix" the user experience bug, and clean up the MIR stream, but imho this is still just patching over the problem.
What I'd love to understand is why that DEBUG_VALUE: fn1:z <- $edi
gets created in the first place. That just seems wrong. If we can prevent that from happening, we don't need to have later passes clean things up ex post facto
I would tend to solution 1), but am open for other ideas (and maybe I am going in the wrong direction).
@Vedant Kumar, @Djordje Todorovic, @Eric Christopher: WDYT?
Given this example, and the problem analysis in Comment 9, I see the following possible solutions:
Solution 1) Create a pass that combines sibling DBG_VALUEs into one Use isParameter from the first one, and everything else from the last one.
Solution 2) Make LiveDebugValues.cpp deal with multiple DBG_VALUE before an instruction: Use isParameter from the first one, and everything else from the last one.
Solution 3) Change RegisterCoalescer and VirtRegMap to also remove the corresponding DBG_VALUEs.
The "Simple Register Coalescing" pass removes one COPY, but leaves the DBG_VALUE:
Function Live Ins: $rdi in %0
0B bb.0 (%ir-block.1): liveins: $rdi DBG_VALUE $rdi, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 16B %4:gr64_with_sub_8bit = COPY $rdi DBG_VALUE %4:gr64_with_sub_8bit, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 DBG_VALUE %4.sub_32bit:gr64_with_sub_8bit, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 64B %4.sub_32bit:gr64_with_sub_8bit = IMUL32rr %4.sub_32bit:gr64_with_sub_8bit(tied-def 0), %4.sub_32bit:gr64_with_sub_8bit, implicit-def dead $eflags, debug-location !16; test.cc:3:17 ...
And the "Virtual Register Rewriter" pass seems to remove the other COPY, and also leaves the DBG_VALUE as is:
Function Live Ins: $rdi
0B bb.0 (%ir-block.1): liveins: $rdi DBG_VALUE $rdi, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 DBG_VALUE $edi, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 64B renamable $edi = IMUL32rr renamable $edi(tied-def 0), renamable $edi, implicit-def dead $eflags, implicit killed $rdi, implicit-def $rdi, debug-location !16; test.cc:3:17 ...
The X86DAGToDAGISel pass seems to be creating the DBG_VALUES. And it seems to produce too many COPY operations:
IR dumps right after X86DAGToDAGISel with the simplified example above:
Function Live Ins: $rdi in %0
bb.0 (%ir-block.1): liveins: $rdi DBG_VALUE $rdi, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 %0:gr64 = COPY $rdi DBG_VALUE %0:gr64, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 %1:gr32 = COPY %0.sub_32bit:gr64, debug-location !16; test.cc:3:17 DBG_VALUE %1:gr32, $noreg, !"z", !DIExpression(), debug-location !15; test.cc:0 line no:2 %2:gr32 = IMUL32rr %1:gr32(tied-def 0), %1:gr32, implicit-def dead $eflags, debug-location !16; test.cc:3:17 DBG_VALUE %2:gr32, $noreg, !"l", !DIExpression(), debug-location !15; test.cc:0 line no:3 %3:gr32 = ADD32ri8 %2:gr32(tied-def 0), 1, implicit-def dead $eflags, debug-location !18; test.cc:4:16 $eax = COPY %3:gr32, debug-location !19; test.cc:4:7 RET 0, $eax, debug-location !19; test.cc:4:7
Function Live Ins: $edi in %0
bb.0 (%ir-block.1): liveins: $edi DBG_VALUE $edi, $noreg, !"z", !DIExpression(), debug-location !14; test.cc:0 line no:2 %0:gr32 = COPY $edi DBG_VALUE %0:gr32, $noreg, !"z", !DIExpression(), debug-location !14; test.cc:0 line no:2 %1:gr32 = nsw IMUL32rr %0:gr32(tied-def 0), %0:gr32, implicit-def dead $eflags, debug-location !15; test.cc:3:17 DBG_VALUE %1:gr32, $noreg, !"l", !DIExpression(), debug-location !14; test.cc:0 line no:3 %2:gr32 = nuw nsw ADD32ri8 %1:gr32(tied-def 0), 1, implicit-def dead $eflags, debug-location !16; test.cc:4:16 $eax = COPY %2:gr32, debug-location !17; test.cc:4:7 RET 0, $eax, debug-location !17; test.cc:4:7
While debugging, I also found the following problems in lldb:
1) lldb with dwarf4 uses DW_OP_GNU_entry_value, but doesn't save the value at the beginning of the function
Using clang -g produces dwarf4 (with DW_OP_GNU_entry_value instead of DW_OP_entry_value). With the example above that uses int
and produces an entry_value, lldb says "z=4" when breaking at the return
stmt, although z had the initial value of "2" when the function was called.
2) lldb with dwarf5 does not seem to respect DW_OP_entry_value at all.
When using clang -gdwarf-5, lldb says "z=return
stmt for the example with int
and the example with long
.
Ok, here is a simpler example to show the problem:
attribute((noinline)) int fn1 (long z) { int l = z * z; return l+1; }
int main(void) { return fn1(2); }
fn1(long):
#DEBUG_VALUE: fn1:z <- $edi
imul edi, edi
#DEBUG_VALUE: fn1:q <- $edi
lea eax, [rdi + 1]
ret
0x00000037: DW_TAG_formal_parameter DW_AT_location (indexed (0x0) loclist = 0x00000014: [0x0000000000401110, 0x0000000000401113): DW_OP_reg5 RDI) DW_AT_name ("z") DW_AT_type (0x0000006a "long int")
int
argument instead of long
When using an int
argument instead of long
(https://godbolt.org/z/-6Q6V8), this leads to the following:
fn1(int):
# kill: def $edi killed $edi def $rdi
#DEBUG_VALUE: fn1:z <- $edi
imul edi, edi
#DEBUG_VALUE: fn1:q <- $edi
lea eax, [rdi + 1]
ret
0x00000037: DW_TAG_formal_parameter DW_AT_location (indexed (0x0) loclist = 0x00000014: [0x0000000000401110, 0x0000000000401113): DW_OP_reg5 RDI [0x0000000000401113, 0x0000000000401117): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value) DW_AT_name ("z")
call_site_parameters are only a partial workaround for this - they work in this case because the parameters are constants, if a caller didn't have constant parameters & didn't have any other reason to save the values aside before calling the function, they would not help recover the value & the bug remains/becomes visible
Just to loop back here - this ^ was a mistake. I either got bugs jumbled up or just misread this one - ah, yeah, I read Zach's comment:
What might be possible though is to extend the variable for as long as possible until its location is reused for something else. I haven’t seen the generated code for this example, but I expect at least in this case that that would end up being the entire function.
& assumed we weren't already doing that, according to Jeremy's comment:
ISel has picked a two-address multiply for the first instruction, meaning edx / 'z' gets overwritten. AFAIK, this is totally fine, and there's no real way of recovering the argument here.
(DW_OP_entry_value would help of course, alternately fake.use: https://reviews.llvm.org/D41043 although that's invasive).
Which I agree with - so, yeah, fixing the entry_value part seems like the only issue here (yeah, fake.use is a whole other thing, suitable to an -Og mode, etc - but much bigger project than "Why does this test case work for GCC and not Clang")
Carry on!
(oh, re: "It's probably a bug that we have so many duplicate DBG_VALUEs. " - yeah, if anyone gets an opportunity to investigate that, we have seen some compile time performance degredation likely due to something like this... )
It's probably a bug that we have so many duplicate DBG_VALUEs. But the thing that's breaking the user experience is that the duplicate DBG_VALUEs don't all agree on the width of "z".
I think the ones that refer to $edx are wrong.
That's it! I changed the program to use int
instead of long int
and it works!
The DWARF DIE for the function parameter looks like this then (somehow clang decided to swap y and z in the multiplication if I do this too):
0x00000054: DW_TAG_formal_parameter DW_AT_location (0x00000000: [0x0000000000401110, 0x0000000000401113): DW_OP_reg4 RSI [0x0000000000401113, 0x0000000000401117): DW_OP_GNU_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value) DW_AT_name ("y")
The LiveDebugValues pass, which is responsible for creating entry value locations, is giving up too early.
Before LiveDebugValues, the MIR for fn1 starts with this:
bb.0.entry:
liveins: $rdi, $rdx, $rsi
DBG_VALUE $rdi, $noreg, !​14, !DIExpression(), debug-location !​19
DBG_VALUE $edi, $noreg, !​14, !DIExpression(), debug-location !​19
DBG_VALUE $rsi, $noreg, !​15, !DIExpression(), debug-location !​19
DBG_VALUE $rsi, $noreg, !​15, !DIExpression(), debug-location !​19
DBG_VALUE $esi, $noreg, !​15, !DIExpression(), debug-location !​19
DBG_VALUE $rdx, $noreg, !​16, !DIExpression(), debug-location !​19
DBG_VALUE $rdx, $noreg, !​16, !DIExpression(), debug-location !​19
DBG_VALUE $edx, $noreg, !​16, !DIExpression(), debug-location !​19
...
LDV takes the first DBG_VALUE for "z" and stashes a backup entry value location for it:
Creating the backup entry location: DBG_VALUE $rdx, $noreg, !"z", !DIExpression(), debug-location !​19; fn.c:0 line no:2
But then it immediately finds another and mistakes it for a clobber of the parameter:
Deleting a DBG entry value because of: DBG_VALUE $edx, $noreg, !"z", !DIExpression(), debug-location !​19; fn.c:0 line no:2
It's probably a bug that we have so many duplicate DBG_VALUEs. But the thing that's breaking the user experience is that the duplicate DBG_VALUEs don't all agree on the width of "z".
I think the ones that refer to $edx are wrong.
The llvm dwarf output is actually from an older clang version. With the current master, the problem still exists. However, the DWARF output does contain the call sites:
0x0000002e: DW_TAG_subprogram DW_AT_APPLE_omit_frame_ptr (true) DW_AT_frame_base (DW_OP_reg7 RSP) DW_AT_call_all_calls (true) DW_AT_name ("fn1") DW_AT_APPLE_optimized (true)
0x0000004b: DW_TAG_formal_parameter DW_AT_location (DW_OP_reg5 RDI) DW_AT_name ("x")
0x00000058: DW_TAG_formal_parameter DW_AT_location (DW_OP_reg4 RSI) DW_AT_name ("y")
0x00000065: DW_TAG_formal_parameter DW_AT_location (0x00000000: [0x0000000000401110, 0x0000000000401113): DW_OP_reg1 RDX) DW_AT_name ("z")
0x0000008f: DW_TAG_subprogram DW_AT_APPLE_omit_frame_ptr (true) DW_AT_frame_base (DW_OP_reg7 RSP) DW_AT_call_all_calls (true) DW_AT_name ("main") DW_AT_APPLE_optimized (true)
0x000000a8: DW_TAG_call_site DW_AT_call_origin (0x0000002e) DW_AT_call_tail_call (true) DW_AT_call_pc (0x000000000040112f)
0x000000b5: DW_TAG_call_site_parameter DW_AT_location (DW_OP_reg1 RDX) DW_AT_call_value (DW_OP_constu 0x22)
0x000000bb: DW_TAG_call_site_parameter DW_AT_location (DW_OP_reg4 RSI) DW_AT_call_value (DW_OP_lit23)
0x000000c0: DW_TAG_call_site_parameter DW_AT_location (DW_OP_reg5 RDI) DW_AT_call_value (DW_OP_lit14)
call_site_parameters are only a partial workaround for this - they work in this case because the parameters are constants, if a caller didn't have constant parameters & didn't have any other reason to save the values aside before calling the function, they would not help recover the value & the bug remains/becomes visible
Happy to chat further about how this all works - maybe video chat if that's helpful - just ping me :)
Note that i also tried with -gdwarf-5 and lldb with the same results.
Interestingly, this works with gcc (the dexter test above passes with gcc).
Breakpoint with gcc + gdb at the return stmt of fn1 Breakpoint 1, fn1 (x=14, y=23, z=34) at test.cc:5 5 return l + q;
fn1(long, long, long): imul esi, edx lea eax, [rsi+rdi*2] ret
0x00000029: DW_TAG_subprogram DW_AT_name ("main") DW_AT_GNU_all_call_sites (true)
0x0000004b: DW_TAG_GNU_call_site DW_AT_GNU_tail_call (true)
0x00000058: DW_TAG_GNU_call_site_parameter DW_AT_location (DW_OP_reg5 RDI) DW_AT_GNU_call_site_value (DW_OP_breg5 RDI+0)
0x0000005e: DW_TAG_GNU_call_site_parameter DW_AT_location (DW_OP_reg4 RSI) DW_AT_GNU_call_site_value (DW_OP_lit23)
0x00000063: DW_TAG_GNU_call_site_parameter DW_AT_location (DW_OP_reg1 RDX) DW_AT_GNU_call_site_value (DW_OP_breg1 RDX+0)
0x00000072: DW_TAG_subprogram DW_AT_name ("fn1") DW_AT_GNU_all_call_sites (true)
0x00000098: DW_TAG_formal_parameter DW_AT_name ("x") DW_AT_location (DW_OP_reg5 RDI)
0x000000a4: DW_TAG_formal_parameter DW_AT_name ("y") DW_AT_location (0x00000004: [0x0000000000001150, 0x0000000000001153): DW_OP_reg4 RSI [0x0000000000001153, 0x0000000000001157): DW_OP_GNU_entry_value(DW_OP_reg4 RSI), DW_OP_stack_value) DW_AT_unknown_2137 (0x00000000)
0x000000b6: DW_TAG_formal_parameter DW_AT_name ("z") DW_AT_location (DW_OP_reg1 RDX)
Breakpoint with clang + gdb at the return stmt of fn1:
Breakpoint 1, fn1 (x=14, y=23, z=
fn1(long, long, long): imul edx, esi lea eax, [rdx + 2*rdi] ret
0x0000002a: DW_TAG_subprogram DW_AT_name ("fn1") DW_AT_external (true)
0x00000047: DW_TAG_formal_parameter DW_AT_location (0x00000000: [0x0000000000401110, 0x0000000000401117): DW_OP_reg5 RDI) DW_AT_name ("x")
0x00000056: DW_TAG_formal_parameter DW_AT_location (0x00000023: [0x0000000000401110, 0x0000000000401117): DW_OP_reg4 RSI) DW_AT_name ("y")
0x00000065: DW_TAG_formal_parameter DW_AT_location (0x00000046: [0x0000000000401110, 0x0000000000401113): DW_OP_reg1 RDX) DW_AT_name ("z")
0x0000008f: DW_TAG_subprogram DW_AT_name ("main")
Here is a dexter test that reproduces the problem. It passes with -O0, but fails with -O3:
// REQUIRES: lldb
// UNSUPPORTED: system-windows
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' --cflags "-O3 -glldb" -- %s
__attribute__((noinline))
int fn1 (long int x, long int y, long int z) {
int l = x * 2;
int q = y * z; // DexLabel('alive')
return l + q; // DexLabel('dead')
}
int main(void) {
return fn1(14, 23, 34);
}
// DexExpectWatchValue('z', '34', on_line='alive')
// DexExpectWatchValue('z', '34', on_line='dead')
With llvm/clang r354569 I get the following codegen for fn1:
--------8<--------
0000000000400480
ISel has picked a two-address multiply for the first instruction, meaning edx / 'z' gets overwritten. AFAIK, this is totally fine, and there's no real way of recovering the argument here.
(DW_OP_entry_value would help of course, alternately fake.use: https://reviews.llvm.org/D41043 although that's invasive).
If we’re just asking about user expectation, then the end of the scope (which in this case is the whole function). But I’ll grant that this isn’t always possible.
What might be possible though is to extend the variable for as long as possible until its location is reused for something else. I haven’t seen the generated code for this example, but I expect at least in this case that that would end up being the entire function.
So, the lifetime of z has ended at that point, how long would you expect the lifetime to be extended for this?
assigned to @tbosch
Extended Description
Same test case as before:
attribute((noinline)) int fn1 (long int x, long int y, long int z) { int l = x 2; int q = y z; return l + q; }
attribute((noinline)) long int fn2 (long int a, long int b, long int c) { long int q = 2 * a; return fn1 (5, 6, 7); }
int main(void) { return fn2(14, 23, 34); }
(built with -O3 -g). lldb session:
Process 30254 stopped
(lldb) frame var z (long) z = 7 (lldb) n Process 30254 stopped
8 attribute((noinline)) long int (lldb) frame var z (long) z =