swift: LLDB missing variables in certain case
For some certain swift code, like:
// Dog.swift
class Dog: NSObject {
var wang : Bool?
}
// main.swift
let wang = Dog().wang ?? false
if wang { //Break here
print("this line is never reached") // <--
}
When we debug and stop at the Break here
line, the debugger doesn’t show the value of wang
.
There are also some details behavior:
- If we use that
wang
variable later (except theif
expression), likeprint(wang)
. The issue disappears - If we define class
Dog
inside the same filemain.swift
(not another fileDog.swift
. The issue disappears
We have tried to find out reasons and have one guess: During SIL Canonical stage, the Swift compiler may probably do some optimizations to such unused variables, which leads to the loss of debug value. For example:
%30 = load %8 : $*Bool, loc "/Users/ccc/Desktop/PHITest/PHITest/ViewController.swift":18:27, scope 2 // users: %89, %31, %33
%33 = struct_extract %30 : $Bool, #Bool._value, loc "/Users/ccc/Desktop/PHITest/PHITest/ViewController.swift":20:9, scope 3 // user: %34
This SIL might be optimized as:
%31 = load %30 : $*Builtin.Int1, loc "/Users/ccc/Desktop/PHITest/PHITest/ViewController.swift":18:27, scope 2 // user: %33
Maybe this process leads to the loss of debug value of wang
which cause the missing display of value of wang
in LLDB?
Steps to reproduce
The demo which can trigger this bug is attached in this PHITest.zip file.
By setting breakpoints at if(wang)
in ViewController.swift and debug this project you can see the lldb debugger doesn’t show the value of wang
.
Expected behavior
The LLDB debugger should show the value of wang
, which should be false
.
Environment
- Swift compiler version info: Swift 5.7
- Xcode version info: Xcode 14.0/14.1
- Deployment target: iOS15+ (reproducible in lower firmware as well)
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 15 (7 by maintainers)
Commits related to this issue
- fix the bug reported in:https://github.com/apple/swift/issues/62241 — committed to Alkaid-10/swift by deleted user 2 years ago
- At -Onone preserve debug info after splitting loads Load splitting converts an aggregate load into a set of subobject loads. This is required at -Onone for exclusivity diagnostics. We cannot preserv... — committed to atrick/swift by atrick 2 years ago
- At -Onone preserve debug info after splitting loads Load splitting converts an aggregate load into a set of subobject loads. This is required at -Onone for exclusivity diagnostics. We cannot preserv... — committed to atrick/swift by atrick 2 years ago
- At -Onone preserve debug info after splitting loads Load splitting converts an aggregate load into a set of subobject loads. This is required at -Onone for exclusivity diagnostics. We cannot preserv... — committed to ando-huang/swift by atrick 2 years ago
- At -Onone preserve debug info after splitting loads Load splitting converts an aggregate load into a set of subobject loads. This is required at -Onone for exclusivity diagnostics. We cannot preserv... — committed to tbkka/swift by atrick 2 years ago
- Enable -sil-load-splitting-debug-info by default. Fixes a know issue in which load splitting drops debug info. This was fixed circa swift 5.8 using the LLVM's debug fragments feature. The fix was, ho... — committed to atrick/swift by atrick a year ago
- Enable salvageDebugInfo in splitAggregateLoad Fixes a know issue in which load splitting drops debug info. After splitting the load, invoke the improved salvageDebugInfo, which now creates a new deb... — committed to atrick/swift by atrick a year ago
- Call salvageLoadDebugInfo from splitAggregateLoad Fixes a know issue in which load splitting drops debug info. After splitting the load, create a new debug_value instruction for the loaded memory lo... — committed to atrick/swift by atrick a year ago
- Call salvageLoadDebugInfo from splitAggregateLoad Fixes a know issue in which load splitting drops debug info. After splitting the load, create a new debug_value instruction for the loaded memory lo... — committed to atrick/swift by atrick a year ago
- Call salvageLoadDebugInfo from splitAggregateLoad Fixes a know issue in which load splitting drops debug info. After splitting the load, create a new debug_value instruction for the loaded memory lo... — committed to meg-gupta/swift by atrick a year ago
- Call salvageLoadDebugInfo from splitAggregateLoad Fixes a know issue in which load splitting drops debug info. After splitting the load, create a new debug_value instruction for the loaded memory lo... — committed to NuriAmari/swift by atrick a year ago
The exclusivity checker verifies that the same variable is not accessed at the same time that it is being modified using a different name (for example via closure captures). People expect to be able to access two fields of the same struct simultaneously though:
foo(&s.a, &s.b)
Initially, that looks illegal to the checker. Diagnosing that correctly as valid code requires load splitting. The way that we hide the original load from the checker is by wrapping it in an unsafe access scopeThank you for the report and for the investigation everyone.
I’ve reduced this to the following example:
It does look like the compiler is dropping debug info for
wang
, there’s no mentions ofwang
in thedwarfdump
output for the example, but if we add a usage ofwang
after the if statement (likeprint(wang)
), then it shows up as:Here’s a PR the fix I suggested. It needed to be extended to handle general problem, and there were a lot of difficult details to handle.
PR: https://github.com/apple/swift/pull/62672
Thanks for debugging and reducing!
I’m not sure of the status of LLVM’s “debug fragmants” feature. Regardless, I don’t think we want to rely on that feature for debug (Onone) builds.
The split-loads pass “lowers” the load of Bool into a load of Bool._value. We need to run this pass at Onone to avoid breaking source. We can’t move the debug_value to the new load because it’s the wrong type, and only a “fragment” of the original value.
As a very crude hack, we could simply move the debug_value from the loaded value to its address. I’m afraid in some cases that could report an incorrect debug value.
A better hack is probably the one I outlined in the FIXME. Keep both the original and the new lowered load but “hide” the original one from the exclusivity checker.
I can try to implement that very soon, like next week. If someone else wants to go for it, I can review it.
Reopening because the fix was reverted.
It would be surprising if it was since we are seeing this issue in debug mode. Although some mandatory SIL passes runs in that case, but I’m no expert so it could be…
Here are some interesting thing I was able to narrow down from this issue: It looks like is related to operator
??
, in fact to if m is a result of a call to any generic functionAlso ok if
unwrap
is not-generic or we define a non-generic??
operator specialization forBool
. So lldb compiler seems to be having trouble with getting information from generic context, but from that point on wasn’t able to debug further.I’m not familiar enough with lldb to affirm anything, so it is possible. But my guess is that the error
error: expression failed to parse
is something that indicates an issue with lldb compiler because logs aren’t even able to show a type checked AST for lookup the variable, because something is failing when trying to parse source in context, so no sure how could that be related to missing/wrong debug info, but again not an expert in lldb or anything…cc @adrian-prantl @augusto2112