From 6af30ec720c3a8f074b61c9003b7e63f78f148a0 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 1 Nov 2023 20:16:11 +0000 Subject: [PATCH 1/9] Remove a false statement from Unsize docs, add a test --- library/core/src/marker.rs | 1 - .../unsize-coerce-multiple-adt-params.rs | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/ui/unsized/unsize-coerce-multiple-adt-params.rs diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index d396a707b55da..39e4abb10e926 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -158,7 +158,6 @@ pub trait Sized { /// - Types implementing a trait `Trait` also implement `Unsize`. /// - Structs `Foo<..., T, ...>` implement `Unsize>` if all of these conditions /// are met: -/// - `T: Unsize`. /// - Only the last field of `Foo` has a type involving `T`. /// - `Bar: Unsize>`, where `Bar` stands for the actual type of that last field. /// diff --git a/tests/ui/unsized/unsize-coerce-multiple-adt-params.rs b/tests/ui/unsized/unsize-coerce-multiple-adt-params.rs new file mode 100644 index 0000000000000..eba341ff2847f --- /dev/null +++ b/tests/ui/unsized/unsize-coerce-multiple-adt-params.rs @@ -0,0 +1,29 @@ +// check-pass + +struct Foo +where + (T, U): Trait, +{ + f: <(T, U) as Trait>::Assoc, +} + +trait Trait { + type Assoc: ?Sized; +} + +struct Count; + +impl Trait for (i32, Count) { + type Assoc = [(); N]; +} + +impl<'a> Trait for (u32, ()) { + type Assoc = [()]; +} + +// Test that we can unsize several trait params in creative ways. +fn unsize(x: &Foo>) -> &Foo { + x +} + +fn main() {} From 1221b7b652a9859340fad0cd6db78c7493f3847f Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 1 Nov 2023 20:16:24 +0000 Subject: [PATCH 2/9] Rework unsize documentation --- library/core/src/marker.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 39e4abb10e926..13437d8f96120 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -155,11 +155,18 @@ pub trait Sized { /// Those implementations are: /// /// - Arrays `[T; N]` implement `Unsize<[T]>`. -/// - Types implementing a trait `Trait` also implement `Unsize`. -/// - Structs `Foo<..., T, ...>` implement `Unsize>` if all of these conditions -/// are met: -/// - Only the last field of `Foo` has a type involving `T`. -/// - `Bar: Unsize>`, where `Bar` stands for the actual type of that last field. +/// - A type implements `Unsize` if all of these conditions are met: +/// - The type implements `Trait`. +/// - `Trait` is object safe. +/// - The type is sized. +/// - The type outlives `'a`. +/// - Structs `Foo<..., T1, ..., Tn, ...>` implement `Unsize>` +/// where any number of (type and const) parameters may be changed if all of these conditions +/// are met: +/// - Only the last field of `Foo` has a type involving the parameters `T1`, ..., `Tn`. +/// - All other parameters of the struct are equal. +/// - `Field: Unsize>`, where `Field<...>` stands for the actual +/// type of the struct's last field. /// /// `Unsize` is used along with [`ops::CoerceUnsized`] to allow /// "user-defined" containers such as [`Rc`] to contain dynamically-sized From a9e1e43b561aa1c3d5500b5b8b82c6e3ba05cc6c Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 2 Nov 2023 17:40:40 +1100 Subject: [PATCH 3/9] Add UI tests for values accepted by `-C symbol-mangling-version` --- src/tools/tidy/src/ui_tests.rs | 2 +- tests/ui/symbol-mangling-version/bad-value.bad.stderr | 2 ++ tests/ui/symbol-mangling-version/bad-value.blank.stderr | 2 ++ tests/ui/symbol-mangling-version/bad-value.no-value.stderr | 2 ++ tests/ui/symbol-mangling-version/bad-value.rs | 6 ++++++ tests/ui/symbol-mangling-version/stable.rs | 5 +++++ tests/ui/symbol-mangling-version/unstable.legacy.stderr | 2 ++ tests/ui/symbol-mangling-version/unstable.rs | 6 ++++++ 8 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/ui/symbol-mangling-version/bad-value.bad.stderr create mode 100644 tests/ui/symbol-mangling-version/bad-value.blank.stderr create mode 100644 tests/ui/symbol-mangling-version/bad-value.no-value.stderr create mode 100644 tests/ui/symbol-mangling-version/bad-value.rs create mode 100644 tests/ui/symbol-mangling-version/stable.rs create mode 100644 tests/ui/symbol-mangling-version/unstable.legacy.stderr create mode 100644 tests/ui/symbol-mangling-version/unstable.rs diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index dcf218bc18954..7e24793adee0e 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -11,7 +11,7 @@ use std::path::{Path, PathBuf}; const ENTRY_LIMIT: usize = 900; // FIXME: The following limits should be reduced eventually. const ISSUES_ENTRY_LIMIT: usize = 1854; -const ROOT_ENTRY_LIMIT: usize = 866; +const ROOT_ENTRY_LIMIT: usize = 867; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ "rs", // test source files diff --git a/tests/ui/symbol-mangling-version/bad-value.bad.stderr b/tests/ui/symbol-mangling-version/bad-value.bad.stderr new file mode 100644 index 0000000000000..c36c73c6069f1 --- /dev/null +++ b/tests/ui/symbol-mangling-version/bad-value.bad.stderr @@ -0,0 +1,2 @@ +error: incorrect value `bad-value` for codegen option `symbol-mangling-version` - either `legacy` or `v0` (RFC 2603) was expected + diff --git a/tests/ui/symbol-mangling-version/bad-value.blank.stderr b/tests/ui/symbol-mangling-version/bad-value.blank.stderr new file mode 100644 index 0000000000000..0e70af5b8ffbe --- /dev/null +++ b/tests/ui/symbol-mangling-version/bad-value.blank.stderr @@ -0,0 +1,2 @@ +error: incorrect value `` for codegen option `symbol-mangling-version` - either `legacy` or `v0` (RFC 2603) was expected + diff --git a/tests/ui/symbol-mangling-version/bad-value.no-value.stderr b/tests/ui/symbol-mangling-version/bad-value.no-value.stderr new file mode 100644 index 0000000000000..77013b72b6c1d --- /dev/null +++ b/tests/ui/symbol-mangling-version/bad-value.no-value.stderr @@ -0,0 +1,2 @@ +error: codegen option `symbol-mangling-version` requires either `legacy` or `v0` (RFC 2603) (C symbol-mangling-version=) + diff --git a/tests/ui/symbol-mangling-version/bad-value.rs b/tests/ui/symbol-mangling-version/bad-value.rs new file mode 100644 index 0000000000000..7623857d49e81 --- /dev/null +++ b/tests/ui/symbol-mangling-version/bad-value.rs @@ -0,0 +1,6 @@ +// revisions: no-value blank bad +// [no-value] compile-flags: -Csymbol-mangling-version +// [blank] compile-flags: -Csymbol-mangling-version= +// [bad] compile-flags: -Csymbol-mangling-version=bad-value + +fn main() {} diff --git a/tests/ui/symbol-mangling-version/stable.rs b/tests/ui/symbol-mangling-version/stable.rs new file mode 100644 index 0000000000000..dac9bb18d1c3f --- /dev/null +++ b/tests/ui/symbol-mangling-version/stable.rs @@ -0,0 +1,5 @@ +// check-pass +// revisions: v0 +// [v0] compile-flags: -Csymbol-mangling-version=v0 + +fn main() {} diff --git a/tests/ui/symbol-mangling-version/unstable.legacy.stderr b/tests/ui/symbol-mangling-version/unstable.legacy.stderr new file mode 100644 index 0000000000000..c5b359b41bd9d --- /dev/null +++ b/tests/ui/symbol-mangling-version/unstable.legacy.stderr @@ -0,0 +1,2 @@ +error: `-C symbol-mangling-version=legacy` requires `-Z unstable-options` + diff --git a/tests/ui/symbol-mangling-version/unstable.rs b/tests/ui/symbol-mangling-version/unstable.rs new file mode 100644 index 0000000000000..df87a39cdfbd1 --- /dev/null +++ b/tests/ui/symbol-mangling-version/unstable.rs @@ -0,0 +1,6 @@ +// revisions: legacy legacy-ok +// [legacy] compile-flags: -Csymbol-mangling-version=legacy +// [legacy-ok] check-pass +// [legacy-ok] compile-flags: -Zunstable-options -Csymbol-mangling-version=legacy + +fn main() {} From 76103a8f6e838ca5bd9cf16b6236519c7c78e81d Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 2 Nov 2023 17:34:05 +1100 Subject: [PATCH 4/9] Remove support for alias `-Z symbol-mangling-version` --- compiler/rustc_interface/src/tests.rs | 1 - compiler/rustc_session/src/config.rs | 33 +++++++------------ compiler/rustc_session/src/options.rs | 3 -- .../crate-hash-rustc-version/Makefile | 2 +- 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 594283168c98d..8e66083a390fc 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -817,7 +817,6 @@ fn test_unstable_options_tracking_hash() { tracked!(split_lto_unit, Some(true)); tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1)); tracked!(stack_protector, StackProtector::All); - tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0)); tracked!(teach, true); tracked!(thinlto, Some(true)); tracked!(thir_unsafeck, true); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index a8ebab4ae333c..715a7f9cb631b 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2674,28 +2674,19 @@ pub fn build_session_options( ); } - // Handle both `-Z symbol-mangling-version` and `-C symbol-mangling-version`; the latter takes - // precedence. - match (cg.symbol_mangling_version, unstable_opts.symbol_mangling_version) { - (Some(smv_c), Some(smv_z)) if smv_c != smv_z => { - handler.early_error( - "incompatible values passed for `-C symbol-mangling-version` \ - and `-Z symbol-mangling-version`", - ); - } - (Some(SymbolManglingVersion::V0), _) => {} - (Some(_), _) if !unstable_opts.unstable_options => { - handler - .early_error("`-C symbol-mangling-version=legacy` requires `-Z unstable-options`"); - } - (None, None) => {} - (None, smv) => { - handler.early_warn( - "`-Z symbol-mangling-version` is deprecated; use `-C symbol-mangling-version`", - ); - cg.symbol_mangling_version = smv; + // Check for unstable values of `-C symbol-mangling-version`. + // This is what prevents them from being used on stable compilers. + match cg.symbol_mangling_version { + // Stable values: + None | Some(SymbolManglingVersion::V0) => {} + // Unstable values: + Some(SymbolManglingVersion::Legacy) => { + if !unstable_opts.unstable_options { + handler.early_error( + "`-C symbol-mangling-version=legacy` requires `-Z unstable-options`", + ); + } } - _ => {} } // Check for unstable values of `-C instrument-coverage`. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 30c8b9d67002c..8669c37e35a4e 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1823,9 +1823,6 @@ written to standard error output)"), "control if mem::uninitialized and mem::zeroed panic on more UB"), strip: Strip = (Strip::None, parse_strip, [UNTRACKED], "tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"), - symbol_mangling_version: Option = (None, - parse_symbol_mangling_version, [TRACKED], - "which mangling version to use for symbol names ('legacy' (default) or 'v0')"), #[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")] teach: bool = (false, parse_bool, [TRACKED], "show extended diagnostic help (default: no)"), diff --git a/tests/run-make/crate-hash-rustc-version/Makefile b/tests/run-make/crate-hash-rustc-version/Makefile index f1d2a36041045..6bf504bf01be5 100644 --- a/tests/run-make/crate-hash-rustc-version/Makefile +++ b/tests/run-make/crate-hash-rustc-version/Makefile @@ -4,7 +4,7 @@ include ../tools.mk # Ensure that crates compiled with different rustc versions cannot # be dynamically linked. -FLAGS := -Cprefer-dynamic -Zsymbol-mangling-version=v0 +FLAGS := -Cprefer-dynamic -Csymbol-mangling-version=v0 UNAME := $(shell uname) ifeq ($(UNAME),Linux) EXT=".so" From 15ae59ba037721d9af593965d954dbed26a68690 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 30 Oct 2023 14:23:35 +0100 Subject: [PATCH 5/9] use global cache when computing proof trees --- compiler/rustc_middle/src/arena.rs | 1 + .../rustc_middle/src/traits/solve/cache.rs | 47 +++++-- .../rustc_middle/src/traits/solve/inspect.rs | 10 +- .../src/traits/solve/inspect/format.rs | 9 +- compiler/rustc_session/src/options.rs | 2 - .../src/solve/eval_ctxt/mod.rs | 16 +-- .../src/solve/inspect/analyse.rs | 12 +- .../src/solve/inspect/build.rs | 121 +++++++++--------- .../rustc_trait_selection/src/solve/mod.rs | 4 +- .../src/solve/search_graph.rs | 38 ++++-- .../src/traits/error_reporting/mod.rs | 4 +- 11 files changed, 138 insertions(+), 126 deletions(-) diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index 1d573a746b918..dd761b4e31203 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -69,6 +69,7 @@ macro_rules! arena_types { [] dtorck_constraint: rustc_middle::traits::query::DropckConstraint<'tcx>, [] candidate_step: rustc_middle::traits::query::CandidateStep<'tcx>, [] autoderef_bad_ty: rustc_middle::traits::query::MethodAutoderefBadTy<'tcx>, + [] canonical_goal_evaluation: rustc_middle::traits::solve::inspect::GoalEvaluationStep<'tcx>, [] query_region_constraints: rustc_middle::infer::canonical::QueryRegionConstraints<'tcx>, [] type_op_subtype: rustc_middle::infer::canonical::Canonical<'tcx, diff --git a/compiler/rustc_middle/src/traits/solve/cache.rs b/compiler/rustc_middle/src/traits/solve/cache.rs index 9898b0019bba2..e9e9cc418a6d6 100644 --- a/compiler/rustc_middle/src/traits/solve/cache.rs +++ b/compiler/rustc_middle/src/traits/solve/cache.rs @@ -1,4 +1,4 @@ -use super::{CanonicalInput, QueryResult}; +use super::{inspect, CanonicalInput, QueryResult}; use crate::ty::TyCtxt; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lock; @@ -14,8 +14,10 @@ pub struct EvaluationCache<'tcx> { map: Lock, CacheEntry<'tcx>>>, } +#[derive(PartialEq, Eq)] pub struct CacheData<'tcx> { pub result: QueryResult<'tcx>, + pub proof_tree: Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]>, pub reached_depth: usize, pub encountered_overflow: bool, } @@ -24,22 +26,33 @@ impl<'tcx> EvaluationCache<'tcx> { /// Insert a final result into the global cache. pub fn insert( &self, + tcx: TyCtxt<'tcx>, key: CanonicalInput<'tcx>, + proof_tree: Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]>, reached_depth: usize, - did_overflow: bool, + encountered_overflow: bool, cycle_participants: FxHashSet>, dep_node: DepNodeIndex, result: QueryResult<'tcx>, ) { let mut map = self.map.borrow_mut(); let entry = map.entry(key).or_default(); - let data = WithDepNode::new(dep_node, result); + let data = WithDepNode::new(dep_node, QueryData { result, proof_tree }); entry.cycle_participants.extend(cycle_participants); - if did_overflow { + if encountered_overflow { entry.with_overflow.insert(reached_depth, data); } else { entry.success = Some(Success { data, reached_depth }); } + + if cfg!(debug_assertions) { + drop(map); + if Some(CacheData { result, proof_tree, reached_depth, encountered_overflow }) + != self.get(tcx, key, |_| false, Limit(reached_depth)) + { + bug!("unable to retrieve inserted element from cache: {key:?}"); + } + } } /// Try to fetch a cached result, checking the recursion limit @@ -62,27 +75,39 @@ impl<'tcx> EvaluationCache<'tcx> { if let Some(ref success) = entry.success { if available_depth.value_within_limit(success.reached_depth) { + let QueryData { result, proof_tree } = success.data.get(tcx); return Some(CacheData { - result: success.data.get(tcx), + result, + proof_tree, reached_depth: success.reached_depth, encountered_overflow: false, }); } } - entry.with_overflow.get(&available_depth.0).map(|e| CacheData { - result: e.get(tcx), - reached_depth: available_depth.0, - encountered_overflow: true, + entry.with_overflow.get(&available_depth.0).map(|e| { + let QueryData { result, proof_tree } = e.get(tcx); + CacheData { + result, + proof_tree, + reached_depth: available_depth.0, + encountered_overflow: true, + } }) } } struct Success<'tcx> { - data: WithDepNode>, + data: WithDepNode>, reached_depth: usize, } +#[derive(Clone, Copy)] +pub struct QueryData<'tcx> { + pub result: QueryResult<'tcx>, + pub proof_tree: Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]>, +} + /// The cache entry for a goal `CanonicalInput`. /// /// This contains results whose computation never hit the @@ -96,5 +121,5 @@ struct CacheEntry<'tcx> { /// See the doc comment of `StackEntry::cycle_participants` for more /// details. cycle_participants: FxHashSet>, - with_overflow: FxHashMap>>, + with_overflow: FxHashMap>>, } diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs index e7e40bee6b9de..a5916c4ab8552 100644 --- a/compiler/rustc_middle/src/traits/solve/inspect.rs +++ b/compiler/rustc_middle/src/traits/solve/inspect.rs @@ -42,12 +42,6 @@ pub struct State<'tcx, T> { pub type CanonicalState<'tcx, T> = Canonical<'tcx, State<'tcx, T>>; -#[derive(Debug, Eq, PartialEq)] -pub enum CacheHit { - Provisional, - Global, -} - /// When evaluating the root goals we also store the /// original values for the `CanonicalVarValues` of the /// canonicalized goal. We use this to map any [CanonicalState] @@ -78,8 +72,8 @@ pub struct CanonicalGoalEvaluation<'tcx> { #[derive(Eq, PartialEq)] pub enum CanonicalGoalEvaluationKind<'tcx> { Overflow, - CacheHit(CacheHit), - Uncached { revisions: Vec> }, + CycleInStack, + Evaluation { revisions: &'tcx [GoalEvaluationStep<'tcx>] }, } impl Debug for GoalEvaluation<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs index 5733be00adfae..4b73d8e41a150 100644 --- a/compiler/rustc_middle/src/traits/solve/inspect/format.rs +++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs @@ -74,13 +74,10 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> { CanonicalGoalEvaluationKind::Overflow => { writeln!(self.f, "OVERFLOW: {:?}", eval.result) } - CanonicalGoalEvaluationKind::CacheHit(CacheHit::Global) => { - writeln!(self.f, "GLOBAL CACHE HIT: {:?}", eval.result) + CanonicalGoalEvaluationKind::CycleInStack => { + writeln!(self.f, "CYCLE IN STACK: {:?}", eval.result) } - CanonicalGoalEvaluationKind::CacheHit(CacheHit::Provisional) => { - writeln!(self.f, "PROVISIONAL CACHE HIT: {:?}", eval.result) - } - CanonicalGoalEvaluationKind::Uncached { revisions } => { + CanonicalGoalEvaluationKind::Evaluation { revisions } => { for (n, step) in revisions.iter().enumerate() { writeln!(self.f, "REVISION {n}")?; self.nested(|this| this.format_evaluation_step(step))?; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 30c8b9d67002c..25a4547362c9c 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1529,8 +1529,6 @@ options! { dump_solver_proof_tree: DumpSolverProofTree = (DumpSolverProofTree::Never, parse_dump_solver_proof_tree, [UNTRACKED], "dump a proof tree for every goal evaluated by the new trait solver. If the flag is specified without any options after it then it defaults to `always`. If the flag is not specified at all it defaults to `on-request`."), - dump_solver_proof_tree_use_cache: Option = (None, parse_opt_bool, [UNTRACKED], - "determines whether dumped proof trees use the global cache"), dwarf_version: Option = (None, parse_opt_number, [TRACKED], "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), dylib_lto: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs index 066129d8e4731..70235b710e2b4 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs @@ -119,25 +119,11 @@ impl NestedGoals<'_> { #[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)] pub enum GenerateProofTree { - Yes(UseGlobalCache), + Yes, IfEnabled, Never, } -#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)] -pub enum UseGlobalCache { - Yes, - No, -} -impl UseGlobalCache { - pub fn from_bool(use_cache: bool) -> Self { - match use_cache { - true => UseGlobalCache::Yes, - false => UseGlobalCache::No, - } - } -} - pub trait InferCtxtEvalExt<'tcx> { /// Evaluates a goal from **outside** of the trait solver. /// diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index 15c8d9e5bcb02..69bfdd4688cab 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -17,7 +17,7 @@ use rustc_middle::traits::solve::{Certainty, Goal}; use rustc_middle::ty; use crate::solve::inspect::ProofTreeBuilder; -use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache}; +use crate::solve::{GenerateProofTree, InferCtxtEvalExt}; pub struct InspectGoal<'a, 'tcx> { infcx: &'a InferCtxt<'tcx>, @@ -82,8 +82,7 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { } for &goal in &instantiated_goals { - let (_, proof_tree) = - infcx.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)); + let (_, proof_tree) = infcx.evaluate_root_goal(goal, GenerateProofTree::Yes); let proof_tree = proof_tree.unwrap(); visitor.visit_goal(&InspectGoal::new( infcx, @@ -169,11 +168,11 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { let mut candidates = vec![]; let last_eval_step = match self.evaluation.evaluation.kind { inspect::CanonicalGoalEvaluationKind::Overflow - | inspect::CanonicalGoalEvaluationKind::CacheHit(_) => { + | inspect::CanonicalGoalEvaluationKind::CycleInStack => { warn!("unexpected root evaluation: {:?}", self.evaluation); return vec![]; } - inspect::CanonicalGoalEvaluationKind::Uncached { ref revisions } => { + inspect::CanonicalGoalEvaluationKind::Evaluation { ref revisions } => { if let Some(last) = revisions.last() { last } else { @@ -227,8 +226,7 @@ impl<'tcx> ProofTreeInferCtxtExt<'tcx> for InferCtxt<'tcx> { goal: Goal<'tcx, ty::Predicate<'tcx>>, visitor: &mut V, ) -> ControlFlow { - let (_, proof_tree) = - self.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)); + let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes); let proof_tree = proof_tree.unwrap(); visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree)) } diff --git a/compiler/rustc_trait_selection/src/solve/inspect/build.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs index 2eba98b02e907..088455b38cbbc 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/build.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs @@ -3,6 +3,8 @@ //! This code is *a bit* of a mess and can hopefully be //! mostly ignored. For a general overview of how it works, //! see the comment on [ProofTreeBuilder]. +use std::mem; + use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{ CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult, @@ -10,7 +12,6 @@ use rustc_middle::traits::solve::{ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::DumpSolverProofTree; -use crate::solve::eval_ctxt::UseGlobalCache; use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree}; /// The core data structure when building proof trees. @@ -34,12 +35,7 @@ use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree}; /// is called to recursively convert the whole structure to a /// finished proof tree. pub(in crate::solve) struct ProofTreeBuilder<'tcx> { - state: Option>>, -} - -struct BuilderData<'tcx> { - tree: DebugSolver<'tcx>, - use_global_cache: UseGlobalCache, + state: Option>>, } /// The current state of the proof tree builder, at most places @@ -118,36 +114,46 @@ pub(in crate::solve) enum WipGoalEvaluationKind<'tcx> { Nested { is_normalizes_to_hack: IsNormalizesToHack }, } -#[derive(Eq, PartialEq, Debug)] -pub(in crate::solve) enum WipCanonicalGoalEvaluationKind { +#[derive(Eq, PartialEq)] +pub(in crate::solve) enum WipCanonicalGoalEvaluationKind<'tcx> { Overflow, - CacheHit(inspect::CacheHit), + CycleInStack, + Interned { revisions: &'tcx [inspect::GoalEvaluationStep<'tcx>] }, +} + +impl std::fmt::Debug for WipCanonicalGoalEvaluationKind<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Overflow => write!(f, "Overflow"), + Self::CycleInStack => write!(f, "CycleInStack"), + Self::Interned { revisions: _ } => f.debug_struct("Interned").finish_non_exhaustive(), + } + } } #[derive(Eq, PartialEq, Debug)] struct WipCanonicalGoalEvaluation<'tcx> { goal: CanonicalInput<'tcx>, - kind: Option, + kind: Option>, + /// Only used for uncached goals. After we finished evaluating + /// the goal, this is interned and moved into `kind`. revisions: Vec>, result: Option>, } impl<'tcx> WipCanonicalGoalEvaluation<'tcx> { fn finalize(self) -> inspect::CanonicalGoalEvaluation<'tcx> { - let kind = match self.kind { - Some(WipCanonicalGoalEvaluationKind::Overflow) => { + assert!(self.revisions.is_empty()); + let kind = match self.kind.unwrap() { + WipCanonicalGoalEvaluationKind::Overflow => { inspect::CanonicalGoalEvaluationKind::Overflow } - Some(WipCanonicalGoalEvaluationKind::CacheHit(hit)) => { - inspect::CanonicalGoalEvaluationKind::CacheHit(hit) + WipCanonicalGoalEvaluationKind::CycleInStack => { + inspect::CanonicalGoalEvaluationKind::CycleInStack + } + WipCanonicalGoalEvaluationKind::Interned { revisions } => { + inspect::CanonicalGoalEvaluationKind::Evaluation { revisions } } - None => inspect::CanonicalGoalEvaluationKind::Uncached { - revisions: self - .revisions - .into_iter() - .map(WipGoalEvaluationStep::finalize) - .collect(), - }, }; inspect::CanonicalGoalEvaluation { goal: self.goal, kind, result: self.result.unwrap() } @@ -226,33 +232,20 @@ impl<'tcx> WipProbeStep<'tcx> { } impl<'tcx> ProofTreeBuilder<'tcx> { - fn new( - state: impl Into>, - use_global_cache: UseGlobalCache, - ) -> ProofTreeBuilder<'tcx> { - ProofTreeBuilder { - state: Some(Box::new(BuilderData { tree: state.into(), use_global_cache })), - } + fn new(state: impl Into>) -> ProofTreeBuilder<'tcx> { + ProofTreeBuilder { state: Some(Box::new(state.into())) } } fn nested>>(&self, state: impl FnOnce() -> T) -> Self { - match &self.state { - Some(prev_state) => Self { - state: Some(Box::new(BuilderData { - tree: state().into(), - use_global_cache: prev_state.use_global_cache, - })), - }, - None => Self { state: None }, - } + ProofTreeBuilder { state: self.state.as_ref().map(|_| Box::new(state().into())) } } fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> { - self.state.as_mut().map(|boxed| &mut boxed.tree) + self.state.as_deref_mut() } pub fn finalize(self) -> Option> { - match self.state?.tree { + match *self.state? { DebugSolver::GoalEvaluation(wip_goal_evaluation) => { Some(wip_goal_evaluation.finalize()) } @@ -260,13 +253,6 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } } - pub fn use_global_cache(&self) -> bool { - self.state - .as_ref() - .map(|state| matches!(state.use_global_cache, UseGlobalCache::Yes)) - .unwrap_or(true) - } - pub fn new_maybe_root( tcx: TyCtxt<'tcx>, generate_proof_tree: GenerateProofTree, @@ -276,10 +262,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { GenerateProofTree::IfEnabled => { let opts = &tcx.sess.opts.unstable_opts; match opts.dump_solver_proof_tree { - DumpSolverProofTree::Always => { - let use_cache = opts.dump_solver_proof_tree_use_cache.unwrap_or(true); - ProofTreeBuilder::new_root(UseGlobalCache::from_bool(use_cache)) - } + DumpSolverProofTree::Always => ProofTreeBuilder::new_root(), // `OnError` is handled by reevaluating goals in error // reporting with `GenerateProofTree::Yes`. DumpSolverProofTree::OnError | DumpSolverProofTree::Never => { @@ -287,12 +270,12 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } } } - GenerateProofTree::Yes(use_cache) => ProofTreeBuilder::new_root(use_cache), + GenerateProofTree::Yes => ProofTreeBuilder::new_root(), } } - pub fn new_root(use_global_cache: UseGlobalCache) -> ProofTreeBuilder<'tcx> { - ProofTreeBuilder::new(DebugSolver::Root, use_global_cache) + pub fn new_root() -> ProofTreeBuilder<'tcx> { + ProofTreeBuilder::new(DebugSolver::Root) } pub fn new_noop() -> ProofTreeBuilder<'tcx> { @@ -336,9 +319,27 @@ impl<'tcx> ProofTreeBuilder<'tcx> { }) } + pub fn finalize_evaluation( + &mut self, + tcx: TyCtxt<'tcx>, + ) -> Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]> { + self.as_mut().map(|this| match this { + DebugSolver::CanonicalGoalEvaluation(evaluation) => { + let revisions = mem::take(&mut evaluation.revisions) + .into_iter() + .map(WipGoalEvaluationStep::finalize); + let revisions = &*tcx.arena.alloc_from_iter(revisions); + let kind = WipCanonicalGoalEvaluationKind::Interned { revisions }; + assert_eq!(evaluation.kind.replace(kind), None); + revisions + } + _ => unreachable!(), + }) + } + pub fn canonical_goal_evaluation(&mut self, canonical_goal_evaluation: ProofTreeBuilder<'tcx>) { if let Some(this) = self.as_mut() { - match (this, canonical_goal_evaluation.state.unwrap().tree) { + match (this, *canonical_goal_evaluation.state.unwrap()) { ( DebugSolver::GoalEvaluation(goal_evaluation), DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation), @@ -348,7 +349,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } } - pub fn goal_evaluation_kind(&mut self, kind: WipCanonicalGoalEvaluationKind) { + pub fn goal_evaluation_kind(&mut self, kind: WipCanonicalGoalEvaluationKind<'tcx>) { if let Some(this) = self.as_mut() { match this { DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation) => { @@ -372,7 +373,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) { if let Some(this) = self.as_mut() { - match (this, goal_evaluation.state.unwrap().tree) { + match (this, *goal_evaluation.state.unwrap()) { ( DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation { evaluations, .. @@ -396,7 +397,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } pub fn goal_evaluation_step(&mut self, goal_evaluation_step: ProofTreeBuilder<'tcx>) { if let Some(this) = self.as_mut() { - match (this, goal_evaluation_step.state.unwrap().tree) { + match (this, *goal_evaluation_step.state.unwrap()) { ( DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluations), DebugSolver::GoalEvaluationStep(goal_evaluation_step), @@ -444,7 +445,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { pub fn finish_probe(&mut self, probe: ProofTreeBuilder<'tcx>) { if let Some(this) = self.as_mut() { - match (this, probe.state.unwrap().tree) { + match (this, *probe.state.unwrap()) { ( DebugSolver::Probe(WipProbe { steps, .. }) | DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { @@ -486,7 +487,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> { pub fn added_goals_evaluation(&mut self, added_goals_evaluation: ProofTreeBuilder<'tcx>) { if let Some(this) = self.as_mut() { - match (this, added_goals_evaluation.state.unwrap().tree) { + match (this, *added_goals_evaluation.state.unwrap()) { ( DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { evaluation: WipProbe { steps, .. }, diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index d45fe102805ad..dba5369fa0feb 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -38,9 +38,7 @@ mod project_goals; mod search_graph; mod trait_goals; -pub use eval_ctxt::{ - EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt, UseGlobalCache, -}; +pub use eval_ctxt::{EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt}; pub use fulfill::FulfillmentCtxt; pub(crate) use normalize::{deeply_normalize, deeply_normalize_with_skipped_universes}; diff --git a/compiler/rustc_trait_selection/src/solve/search_graph.rs b/compiler/rustc_trait_selection/src/solve/search_graph.rs index 33513f6bd439d..7ffa1d7d31936 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph.rs @@ -6,7 +6,6 @@ use rustc_data_structures::fx::FxHashSet; use rustc_index::Idx; use rustc_index::IndexVec; use rustc_middle::dep_graph::dep_kinds; -use rustc_middle::traits::solve::inspect::CacheHit; use rustc_middle::traits::solve::CacheData; use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult}; use rustc_middle::ty::TyCtxt; @@ -191,8 +190,8 @@ impl<'tcx> SearchGraph<'tcx> { }; // Try to fetch the goal from the global cache. - if inspect.use_global_cache() { - if let Some(CacheData { result, reached_depth, encountered_overflow }) = + 'global: { + let Some(CacheData { result, proof_tree, reached_depth, encountered_overflow }) = self.global_cache(tcx).get( tcx, input, @@ -201,13 +200,26 @@ impl<'tcx> SearchGraph<'tcx> { }, available_depth, ) - { - inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CacheHit( - CacheHit::Global, - )); - self.on_cache_hit(reached_depth, encountered_overflow); - return result; + else { + break 'global; + }; + + // If we're building a proof tree and the current cache entry does not + // contain a proof tree, we do not use the entry but instead recompute + // the goal. We simply overwrite the existing entry once we're done, + // caching the proof tree. + if !inspect.is_noop() { + if let Some(revisions) = proof_tree { + inspect.goal_evaluation_kind( + inspect::WipCanonicalGoalEvaluationKind::Interned { revisions }, + ); + } else { + break 'global; + } } + + self.on_cache_hit(reached_depth, encountered_overflow); + return result; } // Check whether we're in a cycle. @@ -238,9 +250,7 @@ impl<'tcx> SearchGraph<'tcx> { // Finally we can return either the provisional response for that goal if we have a // coinductive cycle or an ambiguous result if the cycle is inductive. Entry::Occupied(entry) => { - inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CacheHit( - CacheHit::Provisional, - )); + inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CycleInStack); let stack_depth = *entry.get(); debug!("encountered cycle with depth {stack_depth:?}"); @@ -329,6 +339,8 @@ impl<'tcx> SearchGraph<'tcx> { (current_entry, result) }); + let proof_tree = inspect.finalize_evaluation(tcx); + // We're now done with this goal. In case this goal is involved in a larger cycle // do not remove it from the provisional cache and update its provisional result. // We only add the root of cycles to the global cache. @@ -346,7 +358,9 @@ impl<'tcx> SearchGraph<'tcx> { // more details. let reached_depth = final_entry.reached_depth.as_usize() - self.stack.len(); self.global_cache(tcx).insert( + tcx, input, + proof_tree, reached_depth, final_entry.encountered_overflow, final_entry.cycle_participants, diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 79b09db2268b6..0796cb57d979c 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -8,7 +8,7 @@ mod type_err_ctxt_ext; use super::{Obligation, ObligationCause, ObligationCauseCode, PredicateObligation}; use crate::infer::InferCtxt; -use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache}; +use crate::solve::{GenerateProofTree, InferCtxtEvalExt}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::Visitor; @@ -173,7 +173,7 @@ pub fn dump_proof_tree<'tcx>(o: &Obligation<'tcx, ty::Predicate<'tcx>>, infcx: & infcx.probe(|_| { let goal = Goal { predicate: o.predicate, param_env: o.param_env }; let tree = infcx - .evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)) + .evaluate_root_goal(goal, GenerateProofTree::Yes) .1 .expect("proof tree should have been generated"); let mut lock = std::io::stdout().lock(); From 9bfd1c4bdb5a8c4ee86c9d4ef4d4b17fa9694b99 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Thu, 2 Nov 2023 05:25:57 -0700 Subject: [PATCH 6/9] Expand mem::offset_of! docs Makes progress on #106655 --- library/core/src/mem/mod.rs | 64 +++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 8792134cdc862..05f039fdc986c 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1294,13 +1294,64 @@ impl SizedTypeProperties for T {} /// /// Structs, enums, unions and tuples are supported. /// -/// Nested field accesses may be used, but not array indexes like in `C`'s `offsetof`. +/// Nested field accesses may be used, but not array indexes. /// /// Enum variants may be traversed as if they were fields. Variants themselves do /// not have an offset. /// +/// Visibility is respected - all types and fields must be visible to the call site: +/// +/// ``` +/// #![feature(offset_of)] +/// +/// mod nested { +/// #[repr(C)] +/// pub struct Struct { +/// private: u8, +/// } +/// } +/// +/// // assert_eq!(mem::offset_of!(nested::Struct, private), 0); +/// // ^^^ error[E0616]: field `private` of struct `Struct` is private +/// ``` +/// /// Note that type layout is, in general, [subject to change and -/// platform-specific](https://doc.rust-lang.org/reference/type-layout.html). +/// platform-specific](https://doc.rust-lang.org/reference/type-layout.html). If +/// layout stability is required, consider using an [explicit `repr` attribute]. +/// +/// Rust guarantees that the offset of a given field within a given type will not +/// change over the lifetime of the program. However, two different compilations of +/// the same program may result in different layouts. Also, even within a single +/// program execution, no guarantees are made about types which are *similar* but +/// not *identical*, e.g.: +/// +/// ``` +/// #![feature(offset_of)] +/// +/// use std::mem; +/// struct Wrapper(T, U); +/// +/// type A = Wrapper; +/// type B = Wrapper; +/// +/// // Not necessarily identical even though `u8` and `i8` have the same layout! +/// // assert!(mem::offset_of!(A, 1), mem::offset_of!(B, 1)); +/// +/// #[repr(transparent)] +/// struct U8(u8); +/// +/// type C = Wrapper; +/// +/// // Not necessarily identical even though `u8` and `U8` have the same layout! +/// // assert!(mem::offset_of!(A, 1), mem::offset_of!(C, 1)); +/// +/// struct Empty(PhantomData); +/// +/// // Not necessarily identical even though `PhantomData` always has the same layout! +/// // assert!(mem::offset_of!(Empty, 0), mem::offset_of!(Empty, 0)); +/// ``` +/// +/// [explicit `repr` attribute]: https://doc.rust-lang.org/reference/type-layout.html#representations /// /// # Examples /// @@ -1329,6 +1380,15 @@ impl SizedTypeProperties for T {} /// /// assert_eq!(mem::offset_of!(NestedA, b.0), 0); /// +/// #[repr(u8)] +/// enum Enum { +/// A(u8, u16), +/// B { one: u8, two: u16 }, +/// } +/// +/// assert_eq!(mem::offset_of!(Enum, A.0), 1); +/// assert_eq!(mem::offset_of!(Enum, B.two), 2); +/// /// # #[cfg(not(bootstrap))] /// assert_eq!(mem::offset_of!(Option<&u8>, Some.0), 0); /// ``` From ec7996df7c4225162f407098beef213ce53b1c76 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Thu, 2 Nov 2023 05:35:08 -0700 Subject: [PATCH 7/9] Remove trailing space --- library/core/src/mem/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 05f039fdc986c..cbbf23e289bba 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1310,7 +1310,7 @@ impl SizedTypeProperties for T {} /// private: u8, /// } /// } -/// +/// /// // assert_eq!(mem::offset_of!(nested::Struct, private), 0); /// // ^^^ error[E0616]: field `private` of struct `Struct` is private /// ``` From 9cf1a485998401cde281b5922dcfced238d31b94 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Thu, 2 Nov 2023 05:57:31 -0700 Subject: [PATCH 8/9] Update mod.rs --- library/core/src/mem/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index cbbf23e289bba..70d74b5c459df 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1328,7 +1328,6 @@ impl SizedTypeProperties for T {} /// ``` /// #![feature(offset_of)] /// -/// use std::mem; /// struct Wrapper(T, U); /// /// type A = Wrapper; @@ -1345,7 +1344,7 @@ impl SizedTypeProperties for T {} /// // Not necessarily identical even though `u8` and `U8` have the same layout! /// // assert!(mem::offset_of!(A, 1), mem::offset_of!(C, 1)); /// -/// struct Empty(PhantomData); +/// struct Empty(core::marker::PhantomData); /// /// // Not necessarily identical even though `PhantomData` always has the same layout! /// // assert!(mem::offset_of!(Empty, 0), mem::offset_of!(Empty, 0)); From 2cc92e666faacbeeaaf5b6e92b0a07566ac4ad69 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Thu, 2 Nov 2023 06:45:32 -0700 Subject: [PATCH 9/9] Update mod.rs --- library/core/src/mem/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 70d74b5c459df..c88d71f8e5487 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1385,7 +1385,9 @@ impl SizedTypeProperties for T {} /// B { one: u8, two: u16 }, /// } /// +/// # #[cfg(not(bootstrap))] /// assert_eq!(mem::offset_of!(Enum, A.0), 1); +/// # #[cfg(not(bootstrap))] /// assert_eq!(mem::offset_of!(Enum, B.two), 2); /// /// # #[cfg(not(bootstrap))]