From a7915a3a7d1df22a66b2a4d122536452053d5e63 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Tue, 14 Jan 2020 21:38:29 -0800 Subject: [PATCH 1/2] Implicit Caller Location section. --- src/SUMMARY.md | 1 + src/codegen/implicit-caller-location.md | 280 ++++++++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/codegen/implicit-caller-location.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 722bcedc6..b0bf1966d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -109,6 +109,7 @@ - [Updating LLVM](./backend/updating-llvm.md) - [Debugging LLVM](./backend/debugging.md) - [Backend Agnostic Codegen](./backend/backend-agnostic.md) + - [Implicit Caller Location](./codegen/implicit-caller-location.md) - [Profile-guided Optimization](./profile-guided-optimization.md) - [Sanitizers Support](./sanitizers.md) - [Debugging Support in Rust Compiler](./debugging-support-in-rustc.md) diff --git a/src/codegen/implicit-caller-location.md b/src/codegen/implicit-caller-location.md new file mode 100644 index 000000000..4cb3f583d --- /dev/null +++ b/src/codegen/implicit-caller-location.md @@ -0,0 +1,280 @@ +# Implicit Caller Location + +Approved in [RFC 2091], this feature enables the accurate reporting of caller location during panics +initiated from functions like `Option::unwrap`, `Result::expect`, and `Index::index`. This feature +adds the [`#[track_caller]`][attr-reference] attribute for functions, the +[`caller_location`][intrinsic] intrinsic, and the stabilization-friendly +[`core::panic::Location::caller`][wrapper] wrapper. + +## Motivating Example + +Take this example program: + +```rust +fn main() { + let foo: Option<()> = None; + foo.unwrap(); // this should produce a useful panic message! +} +``` + +Prior to Rust 1.42, panics like this `unwrap()` printed a location in libcore: + +``` +$ rustc +1.41.0 example.rs; example.exe +thread 'main' panicked at 'called `Option::unwrap()` on a `None` value',...core\macros\mod.rs:15:40 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. +``` + +As of 1.42, we get a much more helpful message: + +``` +$ rustc +1.42.0 example.rs; example.exe +thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', example.rs:3:5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +These error messages are achieved through a combination of changes to `panic!` internals to make use +of `core::panic::Location::caller` and a number of `#[track_caller]` annotations in the standard +library which propagate caller information. + +## Reading Caller Location + +Previously, `panic!` made use of the `file!()`, `line!()`, and `column!()` macros to construct a +[`Location`] pointing to where the panic occurred. These macros couldn't be given an overridden +location, so functions which intentionally invoked `panic!` couldn't provide their own location, +hiding the actual source of error. + +Internally, `panic!()` now calls [`core::panic::Location::caller()`][wrapper] to find out where it +was expanded. This function is itself annotated with `#[track_caller]` and wraps the +[`caller_location`][intrinsic] compiler intrinsic implemented by rustc. This intrinsic is easiest +explained in terms of how it works in a `const` context. + +## Caller Location in `const` + +There are two main phases to returning the caller location in a const context: walking up the stack +to find the right location and allocating a const value to return. + +### Finding the right `Location` + +In a const context we "walk up the stack" from where the intrinsic is invoked, stopping when we +reach the first function call in the stack which does *not* have the attribute. This walk is in +[`InterpCx::find_closest_untracked_caller_location()`][const-find-closest]. + +Starting at the bottom, we iterate up over stack [`Frame`][const-frame]s in the +[`InterpCx::stack`][const-stack], calling +[`InstanceDef::requires_caller_location`][requires-location] on the +[`Instance`s from each `Frame`][frame-instance]. We stop once we find one that returns `false` and +return the span of the *previous* frame which was the "topmost" tracked function. + +### Allocating a static `Location` + +Once we have a `Span`, we need to allocate static memory for the `Location`, which is performed by +the [`TyCtxt::const_caller_location()`][const-location-query] query. Internally this calls +[`InterpCx::alloc_caller_location()`][alloc-location] and results in a unique +[memory kind][location-memory-kind] (`MemoryKind::CallerLocation`). The SSA codegen backend is able +to emit code for these same values, and we use this code there as well. + +Once our `Location` has been allocated in static memory, our intrinsic returns a reference to it. + +## Generating code for `#[track_caller]` callees + +To generate efficient code for a tracked function and its callers, we need to provide the same +behavior from the intrinsic's point of view without having a stack to walk up at runtime. We invert +the approach: as we grow the stack down we pass an additional argument to calls of tracked functions +rather than walking up the stack when the intrinsic is called. That additional argument can be +returned wherever the caller location is queried. + +The argument we append is of type `&'static core::panic::Location<'staic>`. A reference was chosen +to avoid unnecessary copying because a pointer is a third the size of +`std::mem::size_of::() == 24` at time of writing. + +When generating a call to a function which is tracked, we pass the location argument the value of +[`FunctionCx::get_caller_location`][fcx-get]. + +If the calling function is tracked, `get_caller_location` returns the local in +[`FunctionCx::caller_location`][fcx-location] which was populated by the current caller's caller. +In these cases the intrinsic "returns" a reference which was actually provided in an argument to its +caller. + +If the calling function is not tracked, `get_caller_location` allocates a `Location` static from +the current `Span` and returns a reference to that. + +We more efficiently achieve the same behavior as a loop starting from the bottom by passing a single +`&Location` value through the `caller_location` fields of multiple `FunctionCx`s as we grow the +stack downward. + +### Codegen examples + +What does this transformation look like in practice? Take this example which uses the new feature: + +```rust +#![feature(track_caller)] +use std::panic::Location; + +#[track_caller] +fn print_caller() { + println!("called from {}", Location::caller()); +} + +fn main() { + print_caller(); +} +``` + +Here `print_caller()` appears to take no arguments, but we compile it to something like this: + +```rust +#![feature(panic_internals)] +use std::panic::Location; + +fn print_caller(caller: &Location) { + println!("called from {}", caller); +} + +fn main() { + print_caller(&Location::internal_constructor(file!(), line!(), column!())); +} +``` + +### Dynamic Dispatch + +In codegen contexts we have to modify the callee ABI to pass this information down the stack, but +the attribute expressly does *not* modify the type of the function. The ABI change must be +transparent to type checking and remain sound in all uses. + +Direct calls to tracked functions will always know the full codegen flags for the callee and can +generate appropriate code. Indirect callers won't have this information and it's not encoded in +the type of the function pointer they call, so we generate a [`ReifyShim`] around the function +whenever taking a pointer to it. This shim isn't able to report the actual location of the indirect +call (the function's definition site is reported instead), but it prevents miscompilation and is +probably the best we can do without modifying fully-stabilized type signatures. + +> *Note:* We always emit a [`ReifyShim`] when taking a pointer to a tracked function. While the +> constraint here is imposed by codegen contexts, we don't know during MIR construction of the shim +> whether we'll be called in a const context (safe to ignore shim) or in a codegen context (unsafe +> to ignore shim). Even if we did know, the results from const and codegen contexts must agree. + +## The Attribute + +The `#[track_caller]` attribute is checked alongside other codegen attributes to ensure the +function: + +* has the `"Rust"` ABI (as opposed to e.g., `"C"`) +* is not a foreign import (e.g., in an `extern {...}` block) +* is not a closure +* is not `#[naked]` + +If the use is valid, we set [`CodegenFnAttrsFlags::TRACK_CALLER`][attrs-flags]. This flag influences +the return value of [`InstanceDef::requires_caller_location`][requires-location] which is in turn +used in both const and codegen contexts to ensure correct propagation. + +### Traits + +When applied to trait method implementations, the attribute works as it does for regular functions. + +When applied to a trait method prototype, the attribute applies to all implementations of the +method. When applied to a default trait method implementation, the attribute takes effect on +that implementation *and* any overrides. + +Examples: + +```rust +#![feature(track_caller)] + +macro_rules! assert_tracked { + () => {{ + let location = std::panic::Location::caller(); + assert_eq!(location.file(), file!()); + assert_ne!(location.line(), line!(), "line should be outside this fn"); + println!("called at {}", location); + }}; +} + +trait TrackedFourWays { + /// All implementations inherit `#[track_caller]`. + #[track_caller] + fn blanket_tracked(); + + /// Implementors can annotate themselves. + fn local_tracked(); + + /// This implementation is tracked (overrides are too). + #[track_caller] + fn default_tracked() { + assert_tracked!(); + } + + /// Overrides of this implementation are tracked (it is too). + #[track_caller] + fn default_tracked_to_override() { + assert_tracked!(); + } +} + +/// This impl uses the default impl for `default_tracked` and provides its own for +/// `default_tracked_to_override`. +impl TrackedFourWays for () { + fn blanket_tracked() { + assert_tracked!(); + } + + #[track_caller] + fn local_tracked() { + assert_tracked!(); + } + + fn default_tracked_to_override() { + assert_tracked!(); + } +} + +fn main() { + <() as TrackedFourWays>::blanket_tracked(); + <() as TrackedFourWays>::default_tracked(); + <() as TrackedFourWays>::default_tracked_to_override(); + <() as TrackedFourWays>::local_tracked(); +} +``` + +## Background/History + +Broadly speaking, this feature's goal is to improve common Rust error messages without breaking +stability guarantees, requiring modifications to end-user source, relying on platform-specific +debug-info, or preventing user-defined types from having the same error-reporting benefits. + +Improving the output of these panics has been a goal of proposals since at least mid-2016 (see +[non-viable alternatives] in the approved RFC for details). It took two more years until RFC 2091 +was approved, much of its [rationale] for this feature's design having been discovered through the +discussion around several earlier proposals. + +The design in the original RFC limited itself to implementations that could be done inside the +compiler at the time without significant refactoring. However in the year and a half between the +approval of the RFC and the actual implementation work, a [revised design] was proposed and written +up on the tracking issue. During the course of implementing that, it was also discovered that an +implementation was possible without modifying the number of arguments in a function's MIR, which +would simplify later stages and unlock use in traits. + +Because the RFC's implementation strategy could not readily support traits, the semantics were not +originally specified. They have since been implemented following the path which seemed most correct +to the author and reviewers. + +[RFC 2091]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md +[attr-reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-track_caller-attribute +[intrinsic]: https://doc.rust-lang.org/nightly/core/intrinsics/fn.caller_location.html +[wrapper]: https://doc.rust-lang.org/nightly/core/panic/struct.Location.html#method.caller +[non-viable alternatives]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md#non-viable-alternatives +[rationale]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md#rationale +[revised design]: https://github.com/rust-lang/rust/issues/47809#issuecomment-443538059 +[attrs-flags]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/middle/codegen_fn_attrs/struct.CodegenFnAttrFlags.html#associatedconstant.TRACK_CALLER +[`ReifyShim`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/enum.InstanceDef.html#variant.ReifyShim +[`Location`]: https://doc.rust-lang.org/core/panic/struct.Location.html +[const-find-closest]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.InterpCx.html#method.find_closest_untracked_caller_location +[requires-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/enum.InstanceDef.html#method.requires_caller_location +[alloc-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.InterpCx.html#method.alloc_caller_location +[fcx-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/struct.FunctionCx.html#structfield.caller_location +[const-location-query]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/struct.TyCtxt.html#method.const_caller_location +[location-memory-kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/enum.MemoryKind.html#variant.CallerLocation +[const-frame]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.Frame.html +[const-stack]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.InterpCx.html#structfield.stack +[fcx-get]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/struct.FunctionCx.html#method.get_caller_location +[frame-instance]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.Frame.html#structfield.instance From f32f2d54af67e68febc964725d49cf9b0918b9c2 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Tue, 31 Mar 2020 19:21:37 -0700 Subject: [PATCH 2/2] fix broken links --- src/codegen/implicit-caller-location.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/codegen/implicit-caller-location.md b/src/codegen/implicit-caller-location.md index 4cb3f583d..5f11dad30 100644 --- a/src/codegen/implicit-caller-location.md +++ b/src/codegen/implicit-caller-location.md @@ -265,14 +265,14 @@ to the author and reviewers. [non-viable alternatives]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md#non-viable-alternatives [rationale]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md#rationale [revised design]: https://github.com/rust-lang/rust/issues/47809#issuecomment-443538059 -[attrs-flags]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/middle/codegen_fn_attrs/struct.CodegenFnAttrFlags.html#associatedconstant.TRACK_CALLER -[`ReifyShim`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/enum.InstanceDef.html#variant.ReifyShim +[attrs-flags]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/middle/codegen_fn_attrs/struct.CodegenFnAttrFlags.html#associatedconstant.TRACK_CALLER +[`ReifyShim`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.InstanceDef.html#variant.ReifyShim [`Location`]: https://doc.rust-lang.org/core/panic/struct.Location.html [const-find-closest]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.InterpCx.html#method.find_closest_untracked_caller_location -[requires-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/enum.InstanceDef.html#method.requires_caller_location +[requires-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.InstanceDef.html#method.requires_caller_location [alloc-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.InterpCx.html#method.alloc_caller_location [fcx-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/struct.FunctionCx.html#structfield.caller_location -[const-location-query]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/struct.TyCtxt.html#method.const_caller_location +[const-location-query]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.const_caller_location [location-memory-kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/enum.MemoryKind.html#variant.CallerLocation [const-frame]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.Frame.html [const-stack]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir/interpret/struct.InterpCx.html#structfield.stack