diff --git a/src/borrow-check.md b/src/borrow-check.md index 3e4e370153..a46cb5f958 100644 --- a/src/borrow-check.md +++ b/src/borrow-check.md @@ -1,22 +1,21 @@ # MIR borrow check -The borrow check is Rust's "secret sauce" – it is tasked with -enforcing a number of properties: +The borrow check is Rust's "secret sauce" – it is tasked with enforcing a number of properties: - That all variables are initialized before they are used. - That you can't move the same value twice. - That you can't move a value while it is borrowed. -- That you can't access a place while it is mutably borrowed (except through - the reference). +- That you can't access a place while it is mutably borrowed (except through the reference). - That you can't mutate a place while it is immutably borrowed. - etc -The borrow checker operates on the MIR. An older implementation operated on the -HIR. Doing borrow checking on MIR has several advantages: +The borrow checker operates on the MIR. +An older implementation operated on the HIR. +Doing borrow checking on MIR has several advantages: - The MIR is *far* less complex than the HIR; the radical desugaring - helps prevent bugs in the borrow checker. (If you're curious, you - can see + helps prevent bugs in the borrow checker. + (If you're curious, you can see [a list of bugs that the MIR-based borrow checker fixes here][47366].) - Even more importantly, using the MIR enables ["non-lexical lifetimes"][nll], which are regions derived from the control-flow graph. @@ -26,35 +25,32 @@ HIR. Doing borrow checking on MIR has several advantages: ## Major phases of the borrow checker -The borrow checker source is found in -[the `rustc_borrowck` crate][b_c]. The main entry point is -the [`mir_borrowck`] query. +The borrow checker source is found in [the `rustc_borrowck` crate][b_c]. +The main entry point is the [`mir_borrowck`] query. [b_c]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/index.html [`mir_borrowck`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/fn.mir_borrowck.html -- We first create a **local copy** of the MIR. In the coming steps, +- We first create a **local copy** of the MIR. + In the coming steps, we will modify this copy in place to modify the types and things to include references to the new regions that we are computing. - We then invoke [`replace_regions_in_mir`] to modify our local MIR. Among other things, this function will replace all of the [regions](./appendix/glossary.md#region) in the MIR with fresh [inference variables](./appendix/glossary.md#inf-var). -- Next, we perform a number of - [dataflow analyses](./appendix/background.md#dataflow) that +- Next, we perform a number of [dataflow analyses](./appendix/background.md#dataflow) that compute what data is moved and when. - We then do a [second type check](borrow-check/type-check.md) across the MIR: - the purpose of this type check is to determine all of the constraints between - different regions. + the purpose of this type check is to determine all of the constraints between different regions. - Next, we do [region inference](borrow-check/region-inference.md), which computes the values of each region — basically, the points in the control-flow graph where each lifetime must be valid according to the constraints we collected. - At this point, we can compute the "borrows in scope" at each point. -- Finally, we do a second walk over the MIR, looking at the actions it - does and reporting errors. For example, if we see a statement like - `*a + 1`, then we would check that the variable `a` is initialized - and that it is not mutably borrowed, as either of those would - require an error to be reported. Doing this check requires the results of all - the previous analyses. +- Finally, we do a second walk over the MIR, looking at the actions it does and reporting errors. + For example, if we see a statement like `*a + 1`, + we would check that the variable `a` is initialized and that it is not mutably borrowed, + as either of those would require an error to be reported. + Doing this check requires the results of all the previous analyses. [`replace_regions_in_mir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/nll/fn.replace_regions_in_mir.html diff --git a/src/borrow-check/debugging.md b/src/borrow-check/debugging.md index dac7821a57..306b0056a6 100644 --- a/src/borrow-check/debugging.md +++ b/src/borrow-check/debugging.md @@ -6,21 +6,22 @@ With `-Z dump-mir-graphviz=yes`, you will also get Graphviz files for the outlives constraints of the MIR bodies you asked for, as well as the strongly connected components (SCCs) on them. -They are available as -`mir_dump/rs-file-name.function-name.-------.nll.0.regioncx.all.dot` and -`mir_dump/rs-file-name.function-name.-------.nll.0.regioncx.scc.dot` respectively. For both -graphs, named region variables will be shown with their external name (such as `'static`) -shown in parenthesis. For region inference variables in universes other than the root universe, -they will be shown as `/U13` (for universe 13). In the region graph, edges are labelled with -the MIR location where the relationship is required to hold, or `All` if the constraint should -always be true. +They are available as `mir_dump/rs-file-name.function-name.-------.nll.0.regioncx.all.dot` and +`mir_dump/rs-file-name.function-name.-------.nll.0.regioncx.scc.dot` respectively. +For both graphs, named region variables will be shown with their external name (such as `'static`) +shown in parenthesis. +For region inference variables in universes other than the root universe, +they will be shown as `/U13` (for universe 13). +In the region graph, +edges are labelled with the MIR location where the relationship is required to hold, +or `All` if the constraint should always be true. ![A graph showing a small number of strongly connected components on the region- outlives-graph above](../img/scc-graphviz.png) -**Note:** There are implicit edges from `'static` to every region, but those are not -rendered -in the region graph to avoid clutter. They _do_ however show up in the SCC graph. +**Note:** There are implicit edges from `'static` to every region, but those are not rendered +in the region graph to avoid clutter. +They _do_ however show up in the SCC graph. This is why there are outgoing edges from `SCC(5)` in the SCC graph that do not seem to have corresponding edges in the region outlives graph above. diff --git a/src/compiler-debugging.md b/src/compiler-debugging.md index 8bc4dbbd84..000ed93966 100644 --- a/src/compiler-debugging.md +++ b/src/compiler-debugging.md @@ -4,8 +4,7 @@ This chapter contains a few tips to debug the compiler. These tips aim to be useful no matter what you are working on. Some of the other chapters have advice about specific parts of the compiler (e.g. the [Queries Debugging and -Testing chapter](./incrcomp-debugging.md) or the [LLVM Debugging -chapter](./backend/debugging.md)). +Testing chapter](./incrcomp-debugging.md) or the [LLVM Debugging chapter](./backend/debugging.md)). ## Configuring the compiler @@ -317,8 +316,8 @@ $ firefox maybe_init_suffix.pdf # Or your favorite pdf viewer Graphviz also comes with a preprocessor program, [`unflatten`](https://graphviz.org/docs/cli/unflatten/), that -sometimes helps making the outputs look less oddly spread out. It reads -a dot file and outputs another dot file, so you can use it in a pipe, +sometimes helps making the outputs look less oddly spread out. +It reads a dot file and outputs another dot file, so you can use it in a pipe, e.g: ``` $ unflatten mir_dump/*.foo.-------.nll.0.regioncx.all.dot | dot -Tpdf -o foo-outlives.pdf diff --git a/src/debuginfo/lldb-visualizers.md b/src/debuginfo/lldb-visualizers.md index 9e6b89758c..eca949f5c3 100644 --- a/src/debuginfo/lldb-visualizers.md +++ b/src/debuginfo/lldb-visualizers.md @@ -116,8 +116,7 @@ The bool returned from this function is somewhat complicated, see: [`update` caching](#update-caching) below for more info. When in doubt, return `False`/`None`. As of Nov 2025, -none of the visualizers return `True`, but that may change as the debug info -test suite is improved. +none of the visualizers return `True`, but that may change as the debug info test suite is improved. #### `update` caching @@ -348,8 +347,9 @@ The category we use will be called `Rust`. description, list of arguments, and examples. In the past, we used `command source ...`, which executes a series of CLI commands from the -file `lldb_commands` to add providers. This file was somewhat unwieldy, and has been supplanted by -the Python API equivalent outlined below. +file `lldb_commands` to add providers. +This file was somewhat unwieldy, +and has been supplanted by the Python API equivalent outlined below. ## `__lldb_init_module` diff --git a/src/debuginfo/testing.md b/src/debuginfo/testing.md index c6181d3950..bcbe960034 100644 --- a/src/debuginfo/testing.md +++ b/src/debuginfo/testing.md @@ -15,9 +15,10 @@ Debug info tests check a few important things: * Do our visualizers work the way we expect? The first question is typically answered by `tests/codegen-llvm`, but debug info generation is often -tested incidentally, rather than deliberately. At time of writing, there is a much larger focus on -the latter two questions, and those will be covered in detail here. The tests that answer those -questions live in `tests/debuginfo`, which is executed by `compiletest`. +tested incidentally, rather than deliberately. +As of Jul 2026, there is a much larger focus on the latter two questions, +and those will be covered in detail here. +The tests that answer those questions live in `tests/debuginfo`, which is executed by `compiletest`. For much of the test suite's lifespan, debuggers were discovered automatically, and tests were tests were comprised of `$DEBUGGER-command` and `$DEBUGGER-check` directives (i.e. raw string @@ -26,10 +27,11 @@ a nightmare and lead to a [litany of issues](https://github.com/rust-lang/rust/i To help remedy this: 1. [`tests/debuginfo` is now opt-in](https://github.com/rust-lang/rust/pull/159455) for GDB and LLDB. -2. a new directive was added: `$DEBUGGER-repr`. This directive dispatches to custom logic that polls -the debugger for additional information that isn't visible in the printed output. It also -automatically separates output by target, allowing the tests to be run on different platforms -without conflicts. +2. a new directive was added: `$DEBUGGER-repr`. + This directive dispatches to custom logic that polls + the debugger for additional information that isn't visible in the printed output. + It also automatically separates output by target, + allowing the tests to be run on different platforms without conflicts. # The `repr` directive @@ -49,30 +51,33 @@ When the commands are passed to the debugger, our test framework intercepts `rep and runs special logic on them, testing against data stored in `tests/debuginfo//input/_input/.json`. -"Target groups" cover the set of targets where we cannot guarantee identical output. Those targets - are defined by the +"Target groups" cover the set of targets where we cannot guarantee identical output. +Those targets are defined by the [`Target` enum in `common.py`](https://github.com/rust-lang/rust/blob/bf9944f0b8006b152ef4d5f408ae75a0dde3d044/src/etc/lldb_batchmode/common.py#L54). -At time of writing, this list includes `non_windows`, `windows_gnu`, and `windows_msvc`. It is -intentionally kept as short as possible, since each target is a new set of test data that must be -updated when changes are made. There is still not a perfect solution for how tests can be +As of Jul 2026, this list includes `non_windows`, `windows_gnu`, and `windows_msvc`. +It is intentionally kept as short as possible, +since each target is a new set of test data that must be updated when changes are made. +There is still not a perfect solution for how tests can be `--bless`-ed by contributors who do not have access to all of the targets. The input data can be automatically updated for expected changes by adding `--bless` to the test -invocation (e.g. `./x test tests/debuginfo/basic-types/main.rs --bless`). `--bless` updates the -in-memory representation, tests against it, and if no errors occur, saves the data back to the -target file (or creates a new file if necessary). +invocation (e.g. `./x test tests/debuginfo/basic-types/main.rs --bless`). +`--bless` updates the in-memory representation, tests against it, +and if no errors occur, saves the data back to the target file (or creates a new file if necessary). The schema of the input data is defined by the classes in [`common.py`](https://github.com/rust-lang/rust/blob/be3d26db984c6f96335faca1f254dc04873cb1c1/src/etc/lldb_batchmode/common.py). -The top-level container is `TargetData`. This -schema is identical for all debuggers. +The top-level container is `TargetData`. +This schema is identical for all debuggers. ## Converting existing tests Nearly any time a variable is tested, the `repr` directive should be preferred over `command` + -`check`. At time of writing, only a single test has been converted over, but more will follow as -part of the test rewrite mentioned above. Thankfully, the conversion process is fairly easy. For -a given check: +`check`. +As of Jul 2026, only a single test has been converted over, but more will follow as +part of the test rewrite mentioned above. +Thankfully, the conversion process is fairly easy. +For a given check: ``` //@ lldb-command:v foo @@ -85,8 +90,8 @@ The equivalent `repr` test is: //@ lldb-repr:foo ``` -Once all `command` + `checks` are converted to `repr`, run the tests with the `--bless` option. If -you have access to additional targets, `--bless` the data for the remainder of the target groups +Once all `command` + `checks` are converted to `repr`, run the tests with the `--bless` option. +If you have access to additional targets, `--bless` the data for the remainder of the target groups as well (e.g. if you are on a Windows machine, bless once for `x86_64-pc-windows-msvc`, once for `x86_64-pc-windows-gnu`, and use WSL to bless for `x86_64-unknown-linux-gnu`). @@ -96,10 +101,13 @@ as well (e.g. if you are on a Windows machine, bless once for `x86_64-pc-windows ### Ser/De `TargetData` is converted to a dictionary with `dataclasses.asdict`, and is serialized with Python's -built-in JSON library. When testing, the data is read into a `dict`, converted to a `TargetData`, -and stored in the top level `INPUT_DATA` variable. The current deserialization logic should be -resilient to changes in the schema, but requires that all fields contain ONLY types that can be -directly serialized/deserialized by `json.dumps`. The acceptable types are those that make up +built-in JSON library. +When testing, the data is read into a `dict`, converted to a `TargetData`, +and stored in the top level `INPUT_DATA` variable. +The current deserialization logic should be resilient to changes in the schema, +but requires that all fields contain ONLY types that can be +directly serialized/deserialized by `json.dumps`. +The acceptable types are those that make up [`common.JsonType`](https://github.com/rust-lang/rust/blob/bf9944f0b8006b152ef4d5f408ae75a0dde3d044/src/etc/lldb_batchmode/common.py#L17) Since the serialization/deserialization is decoupled from the debugger logic, we can easily switch @@ -109,8 +117,8 @@ The conversion logic from the debugger's internal representation to our schema c `from_$DEBUGGER.py`. Once imported, `common` automatically deserializes any existing input data and [stores it in the -global variable `INPUT_DATA`](https://github.com/rust-lang/rust/blob/bf9944f0b8006b152ef4d5f408ae75a0dde3d044/src/etc/lldb_batchmode/common.py#L523). This -data is what we test against. +global variable `INPUT_DATA`](https://github.com/rust-lang/rust/blob/bf9944f0b8006b152ef4d5f408ae75a0dde3d044/src/etc/lldb_batchmode/common.py#L523). +This data is what we test against. > [!NOTE] > Special care was taken to prevent `lldb_batchmode` from importing `common` unless a `repr` command @@ -122,52 +130,60 @@ data is what we test against. Since type information is unique and unchanging once the debug session has begun, types are only stored once at the top level, and are referred to by name everywhere else. -Pointer values change from run to run. To prevent mismatches, pointer variables do not store their -value. This is equivalent to the wildcard `[...]` used in `-check` directives. +Pointer values change from run to run. +To prevent mismatches, pointer variables do not store their value. +This is equivalent to the wildcard `[...]` used in `-check` directives. -`BlessMetadata` is included in `TargetData`, but is not tested against. It exists solely as a record -of how the test data was generated, to help in diagnosing issues that may occur due to Python or the -debugger changing versions. +`BlessMetadata` is included in `TargetData`, but is not tested against. +It exists solely as a record of how the test data was generated, +to help in diagnosing issues that may occur due to Python or the debugger changing versions. ### Entry point and `--bless` Upon encountering a `repr` pseudo-command, `lldb_batchmode.main` dispatches to -`check_$DEBUGGER.check()`. If the `--bless` option was specified, the variable is converted from -the in-memory representation to our equivalent schema class. This includes the variable's type, -visualizers, children, the children's types, etc. Once inserted into `TargetData`, the variable is -tested against the data that was just saved to `TargetData`. +`check_$DEBUGGER.check()`. +If the `--bless` option was specified, the variable is converted from +the in-memory representation to our equivalent schema class. +This includes the variable's type, +visualizers, children, the children's types, etc. +Once inserted into `TargetData`, +the variable is tested against the data that was just saved to `TargetData`. If no exception or errors occurred and the `--bless` option was specified, `INPUT_DATA` is written -to the appropriate file just before `lldb_batchmode` exits. If errors occur, `INPUT_DATA` is simply -discarded. +to the appropriate file just before `lldb_batchmode` exits. +If errors occur, `INPUT_DATA` is simply discarded. -Currently, the `repr` pseudo-command is checked for directly. GDB and LLDB both support creating -custom CLI commands via Python code. In the future, `repr` may be implemented as a CLI command for -one or both debuggers. +Currently, the `repr` pseudo-command is checked for directly. +GDB and LLDB both support creating custom CLI commands via Python code. +In the future, `repr` may be implemented as a CLI command for one or both debuggers. ### Check logic `check_$DEBUGGER.check` converts the debugger's variable object into a `Variable` object and -compares the two. If any mismatches are found, further processing is done to report errors in a more -helpful manner. This means that errors are encountered and reported immediately, which has a number -of advantages. Most importantly, since the debugger state has not changed since the failure, and we +compares the two. +If any mismatches are found, further processing is done to report errors in a more helpful manner. +This means that errors are encountered and reported immediately, which has a number of advantages. +Most importantly, since the debugger state has not changed since the failure, and we still have access to the debugger's variable object, we can poll the debugger for more information to provide more useful error messages. For example, LLDB can be a bit coy when it comes to reporting errors that occur within -synthetic/summary provider calls. This is especially true when running the command within another +synthetic/summary provider calls. +This is especially true when running the command within another command, as the tests do by calling `script import lldb_batchmode; lldb_batchmode.main()` and executing commands in that context. When we encounter an error, we can import the appropriate summary provider, pass the variable object -to it, and print the exception ourselves. We can also inspect the synthetic provider class to make +to it, and print the exception ourselves. +We can also inspect the synthetic provider class to make sure it implements all the mandatory functions. -Errors *do not* immediately end the test. This is especially important now that a `--bless` option has been added. +Errors *do not* immediately end the test. +This is especially important now that a `--bless` option has been added. `--bless` updates all of the input data, so we need to print all of the errors so the reader can -make an informed decision about whether or not there are further changes that need to be made. We -absolutely do not want people accidentally blessing bad data purely because the 1st error happened -to be an expected change. +make an informed decision about whether or not there are further changes that need to be made. +We absolutely do not want people accidentally blessing bad data +purely because the first error happened to be an expected change. Errors are printed directly to `stdout` to appear as visible output from the `repr` pseudo command. There are [several error helper functions](https://github.com/rust-lang/rust/blob/e7b595554e664e6bd281c8cf881093d6c71bc0e1/src/etc/lldb_batchmode/common.py#L35-L51) @@ -183,13 +199,13 @@ If no errors occurred for a given variable, `$VAR_NAME ok` is printed to `stdout to match against. Before `lldb_batchmode` exits, one last check is done to ensure that all the types and variables -that were present in `INPUT_DATA` have been checked against. If this check fails, the script reports -the untested types/variables and exits with an error code. +that were present in `INPUT_DATA` have been checked against. +If this check fails, the script reports the untested types/variables and exits with an error code. # LLDB versioning -Apple distributes a fork of LLDB with Xcode that contains Swift support. This fork of LLDB does not -use the same versioning scheme as LLVM's LLDB: +Apple distributes a fork of LLDB with Xcode that contains Swift support. +This fork of LLDB does not use the same versioning scheme as LLVM's LLDB: ``` # Apple: @@ -201,13 +217,16 @@ lldb version 22.1.2 (https://github.com/llvm/llvm-project revision 1ab49a973e210 ``` It does not appear that the Apple LLDB's version is derived from LLVM's version, so we cannot easily -or automatically convert between the two. Luckily, we can still check the base LLVM version manually -by checking the appropraite release branch in the Swift LLVM repo. For our example above, no branch -exists for `Swift 6.2.3`, but there is one for `6.2.2`. The LLVM version is located in +or automatically convert between the two. +Luckily, we can still check the base LLVM version manually +by checking the appropriate release branch in the Swift LLVM repo. +For our example above, no branch exists for `Swift 6.2.3`, but there is one for `6.2.2`. +The LLVM version is located in [`llvm/utils/gn/secondary/llvm/version.gni`](https://github.com/swiftlang/llvm-project/blob/swift/release/6.2.2/llvm/utils/gn/secondary/llvm/version.gni). As we can see from that example, the Apple LLDB version above corresponds to (roughly) LLVM LLDB `19.1.5`. This can be useful when diagnosing or writing new tests, as it allows us to get a better idea of -what features are available in the Apple LLDB used in CI. For example, LLDB 19 was the first version -to support Type Recognizer functions, so we can assume our example Apple LLDB supports them. +what features are available in the Apple LLDB used in CI. +For example, LLDB 19 was the first version to support Type Recognizer functions, +so we can assume our example Apple LLDB supports them.