|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +published: true |
| 4 | +date: 2023-08-25 12:34:56 |
| 5 | +title: What's new in Swift debugging on the 5.9 branch? |
| 6 | +author: [adrian-prantl, augustonoronha, kastiglione] |
| 7 | +--- |
| 8 | + |
| 9 | +## What’s new in Swift debugging on the 5.9 branch? |
| 10 | + |
| 11 | +On the Swift 5.9 branch, we introduced a couple of new features to [LLDB](https://lldb.llvm.org/ "LLDB project home page") and the Swift compiler to improve the debugging experience. In this post, we are highlighting three changes that we expect to have the most noticeable impact on Swift debugging workflows. |
| 12 | + |
| 13 | + |
| 14 | +### Faster `p` and `po` |
| 15 | + |
| 16 | +The `p` and `po` command aliases have been redefined to the new `dwim-print` command. The `dwim-print` command prints values using the most user-friendly implementation. "DWIM" is an acronym for "Do What I Mean". Specifically, when printing variables, `dwim-print` will use the same implementation as `frame variable` or `v` instead of the more expensive expression evaluator. |
| 17 | + |
| 18 | +By default, the output of `p` no longer includes a persistent result variable, such as `$0`, or `$R0`. In addition to the overhead incurred by persisting the result, persistent result variables retain any objects they contain, which can be an unexpected side effect for the program execution. Users who want persistent results on occasion, can use `expression` (or a unique prefix such as `expr`) directly instead of `p`. To enable persistent results every time, the `p` alias can be redefined in the `~/.lldbinit` file: |
| 19 | + |
| 20 | +``` |
| 21 | +command unalias p |
| 22 | +command alias p dwim-print --persistent-result on -- |
| 23 | +``` |
| 24 | + |
| 25 | +The `dwim-print` command also gives `po` new functionality. The `po` command can now print Swift objects by their *address*. When running `po <object-address>`, LLDB's embedded Swift compiler will automatically evaluate the expression `unsafeBitCast(<object-address>, to: AnyObject.self)` under the hood to produce the expected result. |
| 26 | + |
| 27 | +See [Introduce dwim-print command](https://reviews.llvm.org/D138315 "LLVM review") and [Change dwim-print to default to disabled persistent results](https://reviews.llvm.org/D145609 "LLVM review") for the patches that introduced these changes. |
| 28 | + |
| 29 | + |
| 30 | +### Using generic type parameters in expressions |
| 31 | + |
| 32 | +LLDB now supports referring to generic type parameters in expression evaluation. For example, given the following code: |
| 33 | + |
| 34 | +```swift |
| 35 | +func use<T>(_ t: T) { |
| 36 | + print(t) // break here |
| 37 | +} |
| 38 | + |
| 39 | +use(5) |
| 40 | +use("Hello!”) |
| 41 | +``` |
| 42 | +
|
| 43 | +Running `po T.self`, when stopped in `use`, will print `Int` when coming in through the first call, and `String` in the second. This can be especially useful in combination with conditional breakpoints to stop only when a generic function is instantiated with a certain concrete type. For example, adding the following expression as the condition to a breakpoint inside `use` will only stop when the variable `t` is a `String`: `T.self == String.self`. |
| 44 | +
|
| 45 | +More details about the implementation of this feature can be found in the [LLDB PR](https://github.com/apple/llvm-project/pull/5715) introducing it. |
| 46 | +
|
| 47 | +
|
| 48 | +### More precise scope information in the Swift compiler |
| 49 | +
|
| 50 | +The Swift compiler now emits more precise lexical scopes in the debug information. Scope information allows a debugger to distinguish between the different variables that are all called `x` in the following example: |
| 51 | +
|
| 52 | +```swift |
| 53 | +func f(x: AnyObject?) { |
| 54 | + // function parameter `x: AnyObject?` |
| 55 | + guard let x else {} |
| 56 | + // local variable `x: AnyObject`, which shadows the function argument `x` |
| 57 | + ... |
| 58 | +} |
| 59 | +``` |
| 60 | +
|
| 61 | +In fact, the Swift language's scoping rules allow some astonishing things to be done with variable bindings: |
| 62 | +
|
| 63 | +```swift |
| 64 | +enum E<T> { |
| 65 | +case A(T) |
| 66 | +case B(T) |
| 67 | +case C(String) |
| 68 | +case D(T, T, T) |
| 69 | +} |
| 70 | +
|
| 71 | +func f<T>(_ e: E<T>) -> String { |
| 72 | + switch e { |
| 73 | + case .A(let a), .B(let a): return "One variable \(a): T in scope" |
| 74 | + case .C(let a): return "One variable \(a): String in scope" |
| 75 | + case .D(let a, _, let c): return "One \(a): T and one \(c): T in scope" |
| 76 | + default: return "Only the function argument e is in scope" |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | +
|
| 81 | +All of these can be correctly disambiguated by LLDB thanks to lexical scope debug information. |
| 82 | +
|
| 83 | +The Swift compiler now uses more accurate ASTScope information to generate the lexical scope hierarchy in the debug information, which results in some behavior changes in the debugger. In the example below, the local variable `a` is not yet in scope at the call site of `getInt()` and will only be available after it has been assigned: |
| 84 | +
|
| 85 | +``` |
| 86 | + 1 func getInt() -> Int { return 42 } |
| 87 | + 2 |
| 88 | + 3 func f() { |
| 89 | + 4 let a = getInt() |
| 90 | + ^ |
| 91 | + 5 print(a) |
| 92 | + 6 } |
| 93 | +
|
| 94 | +(lldb) p a |
| 95 | +error: <EXPR>:3:1: error: cannot find 'a' in scope |
| 96 | +
|
| 97 | +(lldb) n |
| 98 | + 3 func f() { |
| 99 | + 4 let a = getInt() |
| 100 | + 5 print(a) |
| 101 | + ^ |
| 102 | + 6 } |
| 103 | +
|
| 104 | +(lldb) p a |
| 105 | +42 |
| 106 | +``` |
| 107 | +
|
| 108 | +With the debug information produced by previous versions of the Swift compiler, the debugger might have displayed uninitialized memory as the contents of `a` at the call site of `getInt()`. In Swift 5.9 the variable `a` only becomes visible after it has been initialized. |
| 109 | +
|
| 110 | +For more details, see the [pull request](https://github.com/apple/swift/pull/64941) that introduced this change. |
| 111 | +
|
| 112 | +
|
| 113 | +### Getting involved |
| 114 | +
|
| 115 | +If you want to learn more about Swift debugging and LLDB, provide feedback, or want to get started with improving the tooling, debug information, or the debugger itself, come join us in the [LLDB section](https://forums.swift.org/c/development/lldb/13) of the Swift development forums! |
0 commit comments