From b9beeae97c1c1baa53645a2857d3db3fe66f89ca Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sat, 10 Jun 2023 19:44:28 -0400 Subject: [PATCH 1/3] Increase the precision of the exportable MIR check --- compiler/rustc_metadata/src/rmeta/encoder.rs | 7 ++ compiler/rustc_middle/src/ty/context.rs | 3 + compiler/rustc_mir_transform/src/inline.rs | 93 +++++++++++++++++-- .../items-within-generic-items.rs | 1 + tests/codegen/call-metadata.rs | 1 + tests/codegen/cold-call-declare-and-call.rs | 1 + tests/codegen/drop.rs | 1 + tests/codegen/personality_lifetimes.rs | 1 + tests/codegen/target-cpu-on-functions.rs | 3 +- tests/codegen/tune-cpu-on-functions.rs | 3 +- tests/incremental/hashes/let_expressions.rs | 4 +- ...in.DestinationPropagation.panic-abort.diff | 16 +--- ...n.DestinationPropagation.panic-unwind.diff | 16 +--- ...issue_106141.outer.Inline.panic-abort.diff | 32 ++----- ...ssue_106141.outer.Inline.panic-unwind.diff | 32 ++----- .../issue_78442.bar.Inline.panic-abort.diff | 34 +++---- .../issue_78442.bar.Inline.panic-unwind.diff | 54 +++++------ ...ivate_helper.outer.Inline.panic-abort.diff | 18 ++++ ...vate_helper.outer.Inline.panic-unwind.diff | 18 ++++ tests/mir-opt/inline/private_helper.rs | 13 +++ ...ans.outer.PreCodegen.after.panic-abort.mir | 13 +-- ...ns.outer.PreCodegen.after.panic-unwind.mir | 13 +-- ...hile_loop.PreCodegen.after.panic-abort.mir | 34 ++----- ...ile_loop.PreCodegen.after.panic-unwind.mir | 34 ++----- tests/ui/mir/auxiliary/tricky_mir.rs | 48 ++++++++++ tests/ui/mir/private-const.rs | 9 ++ tests/ui/mir/private-fn-ptr.rs | 9 ++ tests/ui/mir/private-static.rs | 9 ++ 28 files changed, 315 insertions(+), 205 deletions(-) create mode 100644 tests/mir-opt/inline/private_helper.outer.Inline.panic-abort.diff create mode 100644 tests/mir-opt/inline/private_helper.outer.Inline.panic-unwind.diff create mode 100644 tests/mir-opt/inline/private_helper.rs create mode 100644 tests/ui/mir/auxiliary/tricky_mir.rs create mode 100644 tests/ui/mir/private-const.rs create mode 100644 tests/ui/mir/private-fn-ptr.rs create mode 100644 tests/ui/mir/private-static.rs diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 9c3b8780d97aa..a1fd690dbd137 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1554,6 +1554,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.tables.unused_generic_params.set(def_id.local_def_index, unused); } + // Encode promoted_mir for all functions that we inlined + let prev_len = tcx.inlined_internal_defs.lock().len(); + for def_id in tcx.inlined_internal_defs.lock().clone() { + record!(self.tables.promoted_mir[def_id.to_def_id()] <- tcx.promoted_mir(def_id)); + } + assert_eq!(tcx.inlined_internal_defs.lock().len(), prev_len); + // Encode all the deduced parameter attributes for everything that has MIR, even for items // that can't be inlined. But don't if we aren't optimizing in non-incremental mode, to // save the query traffic. diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 449129b84188b..904d872014343 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -558,6 +558,8 @@ pub struct GlobalCtxt<'tcx> { /// Stores memory for globals (statics/consts). pub(crate) alloc_map: Lock>, + + pub inlined_internal_defs: Lock>, } impl<'tcx> GlobalCtxt<'tcx> { @@ -706,6 +708,7 @@ impl<'tcx> TyCtxt<'tcx> { new_solver_evaluation_cache: Default::default(), data_layout, alloc_map: Lock::new(interpret::AllocMap::new()), + inlined_internal_defs: Default::default(), } } diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index b28fed7159f1e..cb2f99b556df1 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -146,6 +146,12 @@ impl<'tcx> Inliner<'tcx> { debug!("inlined {}", callsite.callee); self.changed = true; + if !matches!(callsite.callee.def, InstanceDef::CloneShim(..)) { + if let Some(def_id) = callsite.callee.def_id().as_local() { + self.tcx.inlined_internal_defs.lock().insert(def_id); + } + } + self.history.push(callsite.callee.def_id()); self.process_blocks(caller_body, new_blocks); self.history.pop(); @@ -184,6 +190,7 @@ impl<'tcx> Inliner<'tcx> { self.check_mir_is_available(caller_body, &callsite.callee)?; let callee_body = try_instance_mir(self.tcx, callsite.callee.def)?; + self.check_mir_is_exportable(&callsite.callee, callee_attrs, callee_body)?; self.check_mir_body(callsite, callee_body, callee_attrs)?; if !self.tcx.consider_optimizing(|| { @@ -353,6 +360,44 @@ impl<'tcx> Inliner<'tcx> { None } + fn check_mir_is_exportable( + &self, + callee: &Instance<'tcx>, + callee_attrs: &CodegenFnAttrs, + callee_body: &Body<'tcx>, + ) -> Result<(), &'static str> { + // If the callee is not local, then it must be exportable because we passed the check for + // if MIR is available. + if !callee.def_id().is_local() { + return Ok(()); + } + if self.tcx.is_constructor(callee.def_id()) { + return Ok(()); + } + if callee_attrs.requests_inline() { + return Ok(()); + } + let is_generic = callee.substs.non_erasable_generics().next().is_some(); + if is_generic { + return Ok(()); + } + + // So now we are trying to inline a function from another crate which is not inline and is + // not a generic. This might work. But if this pulls in a symbol from the other crater, we + // will fail to link. + // So this is our heuritic for handling this: + // If this function has any calls in it, that might reference an internal symbol. + // If this function contains a pointer constant, that might be a reference to an internal + // static. + // So if either of those conditions are met, we cannot inline this. + if !contains_pointer_constant(callee_body) { + debug!("Has no pointer constants, must be exportable"); + return Ok(()); + } + + Err("not exported") + } + /// Returns an error if inlining is not possible based on codegen attributes alone. A success /// indicates that inlining decision should be based on other criteria. fn check_codegen_attributes( @@ -364,16 +409,6 @@ impl<'tcx> Inliner<'tcx> { return Err("never inline hint"); } - // Only inline local functions if they would be eligible for cross-crate - // inlining. This is to ensure that the final crate doesn't have MIR that - // reference unexported symbols - if callsite.callee.def_id().is_local() { - let is_generic = callsite.callee.substs.non_erasable_generics().next().is_some(); - if !is_generic && !callee_attrs.requests_inline() { - return Err("not exported"); - } - } - if callsite.fn_sig.c_variadic() { return Err("C variadic"); } @@ -1148,3 +1183,41 @@ fn try_instance_mir<'tcx>( _ => Ok(tcx.instance_mir(instance)), } } + +fn contains_pointer_constant(body: &Body<'_>) -> bool { + let mut finder = ConstFinder { found: false }; + finder.visit_body(body); + finder.found +} + +struct ConstFinder { + found: bool, +} + +impl Visitor<'_> for ConstFinder { + fn visit_constant(&mut self, constant: &Constant<'_>, location: Location) { + debug!("Visiting constant: {:?}", constant.literal); + if let ConstantKind::Unevaluated(..) = constant.literal { + self.found = true; + return; + } + + if let ConstantKind::Val(val, ty) = constant.literal { + ty::tls::with(|tcx| { + let val = tcx.lift(val).unwrap(); + if let ConstValue::Scalar(Scalar::Ptr(ptr, _size)) = val { + if let Some(GlobalAlloc::Static(_)) = + tcx.try_get_global_alloc(ptr.into_parts().0) + { + self.found = true; + } + } + }); + + if ty.is_fn() { + self.found = true; + } + } + self.super_constant(constant, location); + } +} diff --git a/tests/codegen-units/item-collection/items-within-generic-items.rs b/tests/codegen-units/item-collection/items-within-generic-items.rs index d37d7f7d9b2b3..da584c39769dd 100644 --- a/tests/codegen-units/item-collection/items-within-generic-items.rs +++ b/tests/codegen-units/item-collection/items-within-generic-items.rs @@ -3,6 +3,7 @@ #![deny(dead_code)] #![feature(start)] +#[inline(never)] fn generic_fn(a: T) -> (T, i32) { //~ MONO_ITEM fn generic_fn::nested_fn fn nested_fn(a: i32) -> i32 { diff --git a/tests/codegen/call-metadata.rs b/tests/codegen/call-metadata.rs index 07cc0c963717e..e610e12bfccf4 100644 --- a/tests/codegen/call-metadata.rs +++ b/tests/codegen/call-metadata.rs @@ -12,6 +12,7 @@ pub fn test() { } #[no_mangle] +#[inline(never)] fn some_true() -> Option { Some(true) } diff --git a/tests/codegen/cold-call-declare-and-call.rs b/tests/codegen/cold-call-declare-and-call.rs index 71d49478bfc74..ec3dcbbe8e10c 100644 --- a/tests/codegen/cold-call-declare-and-call.rs +++ b/tests/codegen/cold-call-declare-and-call.rs @@ -9,6 +9,7 @@ // CHECK: call coldcc void @this_should_never_happen(i16 #[no_mangle] +#[inline(never)] pub extern "rust-cold" fn this_should_never_happen(x: u16) {} pub fn do_things(x: u16) { diff --git a/tests/codegen/drop.rs b/tests/codegen/drop.rs index 3615ef47b531b..75d3b2d91bae5 100644 --- a/tests/codegen/drop.rs +++ b/tests/codegen/drop.rs @@ -11,6 +11,7 @@ impl Drop for SomeUniqueName { } } +#[inline(never)] pub fn possibly_unwinding() { } diff --git a/tests/codegen/personality_lifetimes.rs b/tests/codegen/personality_lifetimes.rs index 9ff7a9b3e8879..187f191442e98 100644 --- a/tests/codegen/personality_lifetimes.rs +++ b/tests/codegen/personality_lifetimes.rs @@ -13,6 +13,7 @@ impl Drop for S { } } +#[inline(never)] fn might_unwind() { } diff --git a/tests/codegen/target-cpu-on-functions.rs b/tests/codegen/target-cpu-on-functions.rs index c043eceb5cd12..d5250f22cca94 100644 --- a/tests/codegen/target-cpu-on-functions.rs +++ b/tests/codegen/target-cpu-on-functions.rs @@ -15,7 +15,8 @@ pub extern "C" fn exported() { // CHECK-LABEL: ; target_cpu_on_functions::not_exported // CHECK-NEXT: ; Function Attrs: -// CHECK-NEXT: define {{.*}}() {{.*}} #0 +// CHECK-NEXT: define {{.*}}() {{.*}} #1 +#[inline(never)] fn not_exported() {} // CHECK: attributes #0 = {{.*}} "target-cpu"="{{.*}}" diff --git a/tests/codegen/tune-cpu-on-functions.rs b/tests/codegen/tune-cpu-on-functions.rs index ed8dc0e938377..6ba0f6a87ce36 100644 --- a/tests/codegen/tune-cpu-on-functions.rs +++ b/tests/codegen/tune-cpu-on-functions.rs @@ -15,7 +15,8 @@ pub extern fn exported() { // CHECK-LABEL: ; tune_cpu_on_functions::not_exported // CHECK-NEXT: ; Function Attrs: -// CHECK-NEXT: define {{.*}}() {{.*}} #0 +// CHECK-NEXT: define {{.*}}() {{.*}} #1 +#[inline(never)] fn not_exported() {} // CHECK: attributes #0 = {{.*}} "tune-cpu"="{{.*}}" diff --git a/tests/incremental/hashes/let_expressions.rs b/tests/incremental/hashes/let_expressions.rs index a835b8eef8ce7..b16ac40aa5b61 100644 --- a/tests/incremental/hashes/let_expressions.rs +++ b/tests/incremental/hashes/let_expressions.rs @@ -142,9 +142,9 @@ pub fn add_ref_in_pattern() { } #[cfg(not(any(cfail1,cfail4)))] -#[rustc_clean(cfg="cfail2", except="hir_owner_nodes,typeck,optimized_mir")] +#[rustc_clean(cfg="cfail2", except="hir_owner_nodes,typeck,optimized_mir,promoted_mir")] #[rustc_clean(cfg="cfail3")] -#[rustc_clean(cfg="cfail5", except="hir_owner_nodes,typeck,optimized_mir")] +#[rustc_clean(cfg="cfail5", except="hir_owner_nodes,typeck,optimized_mir,promoted_mir")] #[rustc_clean(cfg="cfail6")] pub fn add_ref_in_pattern() { let (ref _a, _b) = (1u8, 'y'); diff --git a/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-abort.diff b/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-abort.diff index 459a9c442b3e9..c3d35900bd8d4 100644 --- a/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-abort.diff +++ b/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-abort.diff @@ -4,27 +4,19 @@ fn main() -> () { let mut _0: (); let _1: main::Un; - let mut _2: u32; - let mut _3: u32; scope 1 { debug un => _1; scope 2 { } - scope 3 (inlined std::mem::drop::) { - debug _x => _3; + scope 4 (inlined std::mem::drop::) { + debug _x => const 1_u32; } } + scope 3 (inlined val) { + } bb0: { StorageLive(_1); - StorageLive(_2); - _2 = val() -> [return: bb1, unwind unreachable]; - } - - bb1: { - StorageDead(_2); - StorageLive(_3); - StorageDead(_3); StorageDead(_1); return; } diff --git a/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-unwind.diff b/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-unwind.diff index 8d1297d029963..c3d35900bd8d4 100644 --- a/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-unwind.diff +++ b/tests/mir-opt/dest-prop/union.main.DestinationPropagation.panic-unwind.diff @@ -4,27 +4,19 @@ fn main() -> () { let mut _0: (); let _1: main::Un; - let mut _2: u32; - let mut _3: u32; scope 1 { debug un => _1; scope 2 { } - scope 3 (inlined std::mem::drop::) { - debug _x => _3; + scope 4 (inlined std::mem::drop::) { + debug _x => const 1_u32; } } + scope 3 (inlined val) { + } bb0: { StorageLive(_1); - StorageLive(_2); - _2 = val() -> bb1; - } - - bb1: { - StorageDead(_2); - StorageLive(_3); - StorageDead(_3); StorageDead(_1); return; } diff --git a/tests/mir-opt/inline/issue_106141.outer.Inline.panic-abort.diff b/tests/mir-opt/inline/issue_106141.outer.Inline.panic-abort.diff index 3d0c10725275d..cbb466b9a845c 100644 --- a/tests/mir-opt/inline/issue_106141.outer.Inline.panic-abort.diff +++ b/tests/mir-opt/inline/issue_106141.outer.Inline.panic-abort.diff @@ -4,13 +4,13 @@ fn outer() -> usize { let mut _0: usize; + scope 1 (inlined inner) { -+ let mut _1: &[bool; 1]; -+ let mut _2: bool; -+ let mut _3: bool; + scope 2 { + debug buffer => const _; ++ let _1: usize; + scope 3 { -+ debug index => _0; ++ debug index => _1; ++ } ++ scope 4 (inlined index) { + } + } + } @@ -18,30 +18,12 @@ bb0: { - _0 = inner() -> [return: bb1, unwind unreachable]; + StorageLive(_1); -+ _1 = const _; -+ _0 = index() -> [return: bb1, unwind unreachable]; ++ goto -> bb1; } bb1: { -+ StorageLive(_3); -+ _2 = Lt(_0, const 1_usize); -+ assert(move _2, "index out of bounds: the length is {} but the index is {}", const 1_usize, _0) -> [success: bb2, unwind unreachable]; -+ } -+ -+ bb2: { -+ _3 = (*_1)[_0]; -+ switchInt(move _3) -> [0: bb3, otherwise: bb4]; -+ } -+ -+ bb3: { -+ _0 = const 0_usize; -+ goto -> bb4; -+ } -+ -+ bb4: { -+ StorageDead(_3); -+ StorageDead(_1); - return; +- return; ++ goto -> bb1; } } diff --git a/tests/mir-opt/inline/issue_106141.outer.Inline.panic-unwind.diff b/tests/mir-opt/inline/issue_106141.outer.Inline.panic-unwind.diff index 1e407f07d0969..180c40fb18b31 100644 --- a/tests/mir-opt/inline/issue_106141.outer.Inline.panic-unwind.diff +++ b/tests/mir-opt/inline/issue_106141.outer.Inline.panic-unwind.diff @@ -4,13 +4,13 @@ fn outer() -> usize { let mut _0: usize; + scope 1 (inlined inner) { -+ let mut _1: &[bool; 1]; -+ let mut _2: bool; -+ let mut _3: bool; + scope 2 { + debug buffer => const _; ++ let _1: usize; + scope 3 { -+ debug index => _0; ++ debug index => _1; ++ } ++ scope 4 (inlined index) { + } + } + } @@ -18,30 +18,12 @@ bb0: { - _0 = inner() -> bb1; + StorageLive(_1); -+ _1 = const _; -+ _0 = index() -> bb1; ++ goto -> bb1; } bb1: { -+ StorageLive(_3); -+ _2 = Lt(_0, const 1_usize); -+ assert(move _2, "index out of bounds: the length is {} but the index is {}", const 1_usize, _0) -> bb2; -+ } -+ -+ bb2: { -+ _3 = (*_1)[_0]; -+ switchInt(move _3) -> [0: bb3, otherwise: bb4]; -+ } -+ -+ bb3: { -+ _0 = const 0_usize; -+ goto -> bb4; -+ } -+ -+ bb4: { -+ StorageDead(_3); -+ StorageDead(_1); - return; +- return; ++ goto -> bb1; } } diff --git a/tests/mir-opt/inline/issue_78442.bar.Inline.panic-abort.diff b/tests/mir-opt/inline/issue_78442.bar.Inline.panic-abort.diff index b86eb5f35c95e..14a0c639c3abd 100644 --- a/tests/mir-opt/inline/issue_78442.bar.Inline.panic-abort.diff +++ b/tests/mir-opt/inline/issue_78442.bar.Inline.panic-abort.diff @@ -8,40 +8,40 @@ let mut _3: &fn() {foo}; let _4: fn() {foo}; let mut _5: (); -+ scope 1 (inlined >::call - shim(fn() {foo})) { ++ scope 1 (inlined hide_foo) { ++ } ++ scope 2 (inlined >::call - shim(fn() {foo})) { ++ scope 3 (inlined foo) { ++ } + } bb0: { StorageLive(_2); StorageLive(_3); StorageLive(_4); - _4 = hide_foo() -> [return: bb1, unwind unreachable]; - } - - bb1: { +- _4 = hide_foo() -> [return: bb1, unwind unreachable]; +- } +- +- bb1: { _3 = &_4; StorageLive(_5); _5 = (); - _2 = >::call(move _3, move _5) -> [return: bb2, unwind unreachable]; -+ _2 = move (*_3)() -> [return: bb3, unwind unreachable]; - } - - bb2: { -+ return; -+ } -+ -+ bb3: { +- } +- +- bb2: { StorageDead(_5); StorageDead(_3); StorageDead(_4); StorageDead(_2); _0 = const (); - drop(_1) -> [return: bb3, unwind unreachable]; -- } -- ++ drop(_1) -> [return: bb1, unwind unreachable]; + } + - bb3: { -- return; -+ drop(_1) -> [return: bb2, unwind unreachable]; ++ bb1: { + return; } } diff --git a/tests/mir-opt/inline/issue_78442.bar.Inline.panic-unwind.diff b/tests/mir-opt/inline/issue_78442.bar.Inline.panic-unwind.diff index c67babba23e80..13f119cac45c3 100644 --- a/tests/mir-opt/inline/issue_78442.bar.Inline.panic-unwind.diff +++ b/tests/mir-opt/inline/issue_78442.bar.Inline.panic-unwind.diff @@ -8,7 +8,11 @@ let mut _3: &fn() {foo}; let _4: fn() {foo}; let mut _5: (); -+ scope 1 (inlined >::call - shim(fn() {foo})) { ++ scope 1 (inlined hide_foo) { ++ } ++ scope 2 (inlined >::call - shim(fn() {foo})) { ++ scope 3 (inlined foo) { ++ } + } bb0: { @@ -16,47 +20,37 @@ StorageLive(_3); StorageLive(_4); - _4 = hide_foo() -> [return: bb1, unwind: bb4]; -+ _4 = hide_foo() -> [return: bb1, unwind: bb3]; - } - - bb1: { +- } +- +- bb1: { _3 = &_4; StorageLive(_5); _5 = (); - _2 = >::call(move _3, move _5) -> [return: bb2, unwind: bb4]; -+ _2 = move (*_3)() -> [return: bb5, unwind: bb3]; - } - - bb2: { -- StorageDead(_5); -- StorageDead(_3); -- StorageDead(_4); -- StorageDead(_2); -- _0 = const (); +- } +- +- bb2: { + StorageDead(_5); + StorageDead(_3); + StorageDead(_4); + StorageDead(_2); + _0 = const (); - drop(_1) -> [return: bb3, unwind: bb5]; -+ return; ++ drop(_1) -> [return: bb1, unwind: bb2]; } - bb3: { -- return; -+ bb3 (cleanup): { -+ drop(_1) -> [return: bb4, unwind terminate]; ++ bb1: { + return; } - bb4 (cleanup): { +- bb4 (cleanup): { - drop(_1) -> [return: bb5, unwind terminate]; -+ resume; - } - +- } +- - bb5 (cleanup): { -- resume; -+ bb5: { -+ StorageDead(_5); -+ StorageDead(_3); -+ StorageDead(_4); -+ StorageDead(_2); -+ _0 = const (); -+ drop(_1) -> [return: bb2, unwind: bb4]; ++ bb2 (cleanup): { + resume; } } diff --git a/tests/mir-opt/inline/private_helper.outer.Inline.panic-abort.diff b/tests/mir-opt/inline/private_helper.outer.Inline.panic-abort.diff new file mode 100644 index 0000000000000..1fca82c155426 --- /dev/null +++ b/tests/mir-opt/inline/private_helper.outer.Inline.panic-abort.diff @@ -0,0 +1,18 @@ +- // MIR for `outer` before Inline ++ // MIR for `outer` after Inline + + fn outer() -> u8 { + let mut _0: u8; +- +- bb0: { +- _0 = helper() -> [return: bb1, unwind unreachable]; ++ scope 1 (inlined helper) { + } + +- bb1: { ++ bb0: { ++ _0 = const 123_u8; + return; + } + } + diff --git a/tests/mir-opt/inline/private_helper.outer.Inline.panic-unwind.diff b/tests/mir-opt/inline/private_helper.outer.Inline.panic-unwind.diff new file mode 100644 index 0000000000000..6454ad3bc10f2 --- /dev/null +++ b/tests/mir-opt/inline/private_helper.outer.Inline.panic-unwind.diff @@ -0,0 +1,18 @@ +- // MIR for `outer` before Inline ++ // MIR for `outer` after Inline + + fn outer() -> u8 { + let mut _0: u8; +- +- bb0: { +- _0 = helper() -> bb1; ++ scope 1 (inlined helper) { + } + +- bb1: { ++ bb0: { ++ _0 = const 123_u8; + return; + } + } + diff --git a/tests/mir-opt/inline/private_helper.rs b/tests/mir-opt/inline/private_helper.rs new file mode 100644 index 0000000000000..65fa01dee2640 --- /dev/null +++ b/tests/mir-opt/inline/private_helper.rs @@ -0,0 +1,13 @@ +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +// compile-flags: -Zmir-opt-level=2 -Zinline-mir + +#![crate_type = "lib"] + +// EMIT_MIR private_helper.outer.Inline.diff +pub fn outer() -> u8 { + helper() +} + +fn helper() -> u8 { + 123 +} diff --git a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir index b9329520bab7c..fcbe745f21e00 100644 --- a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-abort.mir @@ -3,17 +3,12 @@ fn outer(_1: u8) -> u8 { debug v => _1; // in scope 0 at $DIR/spans.rs:10:14: 10:15 let mut _0: u8; // return place in scope 0 at $DIR/spans.rs:10:24: 10:26 - let _2: &u8; // in scope 0 at $DIR/spans.rs:11:11: 11:13 - - bb0: { - _2 = &_1; // scope 0 at $DIR/spans.rs:11:11: 11:13 - _0 = inner(_2) -> [return: bb1, unwind unreachable]; // scope 0 at $DIR/spans.rs:11:5: 11:14 - // mir::Constant - // + span: $DIR/spans.rs:11:5: 11:10 - // + literal: Const { ty: for<'a> fn(&'a u8) -> u8 {inner}, val: Value() } + scope 1 (inlined inner) { // at $DIR/spans.rs:11:5: 11:14 + debug x => &_1; // in scope 1 at $DIR/spans.rs:14:14: 14:15 } - bb1: { + bb0: { + _0 = _1; // scope 1 at $DIR/spans.rs:15:5: 15:7 return; // scope 0 at $DIR/spans.rs:12:2: 12:2 } } diff --git a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir index 52e85809735e5..fcbe745f21e00 100644 --- a/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/spans.outer.PreCodegen.after.panic-unwind.mir @@ -3,17 +3,12 @@ fn outer(_1: u8) -> u8 { debug v => _1; // in scope 0 at $DIR/spans.rs:10:14: 10:15 let mut _0: u8; // return place in scope 0 at $DIR/spans.rs:10:24: 10:26 - let _2: &u8; // in scope 0 at $DIR/spans.rs:11:11: 11:13 - - bb0: { - _2 = &_1; // scope 0 at $DIR/spans.rs:11:11: 11:13 - _0 = inner(_2) -> bb1; // scope 0 at $DIR/spans.rs:11:5: 11:14 - // mir::Constant - // + span: $DIR/spans.rs:11:5: 11:10 - // + literal: Const { ty: for<'a> fn(&'a u8) -> u8 {inner}, val: Value() } + scope 1 (inlined inner) { // at $DIR/spans.rs:11:5: 11:14 + debug x => &_1; // in scope 1 at $DIR/spans.rs:14:14: 14:15 } - bb1: { + bb0: { + _0 = _1; // scope 1 at $DIR/spans.rs:15:5: 15:7 return; // scope 0 at $DIR/spans.rs:12:2: 12:2 } } diff --git a/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-abort.mir index c04fdeb637d88..e6ce33ed682f6 100644 --- a/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-abort.mir @@ -3,44 +3,26 @@ fn while_loop(_1: bool) -> () { debug c => _1; let mut _0: (); - let mut _2: bool; - let mut _3: bool; + scope 1 (inlined get_bool) { + debug c => _1; + } + scope 2 (inlined get_bool) { + debug c => _1; + } bb0: { goto -> bb1; } bb1: { - StorageLive(_2); - _2 = get_bool(_1) -> [return: bb2, unwind unreachable]; + switchInt(_1) -> [0: bb3, otherwise: bb2]; } bb2: { - switchInt(move _2) -> [0: bb7, otherwise: bb3]; + switchInt(_1) -> [0: bb1, otherwise: bb3]; } bb3: { - StorageLive(_3); - _3 = get_bool(_1) -> [return: bb4, unwind unreachable]; - } - - bb4: { - switchInt(move _3) -> [0: bb5, otherwise: bb6]; - } - - bb5: { - StorageDead(_3); - StorageDead(_2); - goto -> bb1; - } - - bb6: { - StorageDead(_3); - goto -> bb7; - } - - bb7: { - StorageDead(_2); return; } } diff --git a/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-unwind.mir index ec4782e5f3c7b..e6ce33ed682f6 100644 --- a/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/while_storage.while_loop.PreCodegen.after.panic-unwind.mir @@ -3,44 +3,26 @@ fn while_loop(_1: bool) -> () { debug c => _1; let mut _0: (); - let mut _2: bool; - let mut _3: bool; + scope 1 (inlined get_bool) { + debug c => _1; + } + scope 2 (inlined get_bool) { + debug c => _1; + } bb0: { goto -> bb1; } bb1: { - StorageLive(_2); - _2 = get_bool(_1) -> bb2; + switchInt(_1) -> [0: bb3, otherwise: bb2]; } bb2: { - switchInt(move _2) -> [0: bb7, otherwise: bb3]; + switchInt(_1) -> [0: bb1, otherwise: bb3]; } bb3: { - StorageLive(_3); - _3 = get_bool(_1) -> bb4; - } - - bb4: { - switchInt(move _3) -> [0: bb5, otherwise: bb6]; - } - - bb5: { - StorageDead(_3); - StorageDead(_2); - goto -> bb1; - } - - bb6: { - StorageDead(_3); - goto -> bb7; - } - - bb7: { - StorageDead(_2); return; } } diff --git a/tests/ui/mir/auxiliary/tricky_mir.rs b/tests/ui/mir/auxiliary/tricky_mir.rs new file mode 100644 index 0000000000000..86caffb7cdb74 --- /dev/null +++ b/tests/ui/mir/auxiliary/tricky_mir.rs @@ -0,0 +1,48 @@ +// compile-flags: -Zmir-opt-level=2 -Zinline-mir + +#![crate_type = "lib"] + +// get_static is a good inlining candidate. +// But it calls a private function that is a good inlining candidate, which also accesses a private +// static. +// If we naivelly inline all of this away in MIR, we will reference a private static from this +// crate while compiling another crate, which will produce a linker error. + +#[inline] +pub fn get_static() -> &'static u8 { + get_static_impl() +} + +fn get_static_impl() -> &'static u8 { + static THING: u8 = 0; + &THING +} + +// This is the same as the get_static test, except that here we reference a local function. If we +// mishandle this we will ICE or get a linker error because the private non-exported function will +// get pulled into the other crate. + +#[inline] +pub fn get_fn_ptr() -> u8 { + wrapper()() +} + +fn wrapper() -> fn() -> u8 { + inner +} + +fn inner() -> u8 { + 123 +} + +// Same as the get_static test, but with a promoted const. In this case, we can just generate MIR +// for all promoted consts, and permit the inlining to happen. + +#[inline] +pub fn get_promoted_const() -> &'static u8 { + inner_promoted_const() +} + +fn inner_promoted_const() -> &'static u8 { + &0 +} diff --git a/tests/ui/mir/private-const.rs b/tests/ui/mir/private-const.rs new file mode 100644 index 0000000000000..1d756319fb8ed --- /dev/null +++ b/tests/ui/mir/private-const.rs @@ -0,0 +1,9 @@ +// run-pass +// compile-flags: -Zmir-opt-level=2 -Zinline-mir +// aux-build:tricky_mir.rs + +extern crate tricky_mir; + +fn main() { + println!("{:?}", tricky_mir::get_promoted_const()); +} diff --git a/tests/ui/mir/private-fn-ptr.rs b/tests/ui/mir/private-fn-ptr.rs new file mode 100644 index 0000000000000..90890bd3ee381 --- /dev/null +++ b/tests/ui/mir/private-fn-ptr.rs @@ -0,0 +1,9 @@ +// run-pass +// compile-flags: -Zmir-opt-level=2 -Zinline-mir +// aux-build:tricky_mir.rs + +extern crate tricky_mir; + +fn main() { + println!("{}", tricky_mir::get_fn_ptr()); +} diff --git a/tests/ui/mir/private-static.rs b/tests/ui/mir/private-static.rs new file mode 100644 index 0000000000000..5ba9a3dc2344f --- /dev/null +++ b/tests/ui/mir/private-static.rs @@ -0,0 +1,9 @@ +// run-pass +// compile-flags: -Zmir-opt-level=2 -Zinline-mir +// aux-build:tricky_mir.rs + +extern crate tricky_mir; + +fn main() { + println!("{}", *tricky_mir::get_static()); +} From 433b1bb7785f64010b589e9e646524d0b45b08c4 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sat, 17 Jun 2023 16:42:38 -0400 Subject: [PATCH 2/3] Allow inlining of non-exportable MIR into non-exported functions --- compiler/rustc_mir_transform/src/inline.rs | 53 ++++++++++++------- ...ssue_66971.main.ConstProp.panic-abort.diff | 35 ++++++++++-- ...sue_66971.main.ConstProp.panic-unwind.diff | 35 ++++++++++-- ...ssue_67019.main.ConstProp.panic-abort.diff | 36 +++++++++---- ...sue_67019.main.ConstProp.panic-unwind.diff | 36 +++++++++---- 5 files changed, 145 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index cb2f99b556df1..e5243a5264bc1 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -5,6 +5,7 @@ use rustc_hir::def_id::DefId; use rustc_index::bit_set::BitSet; use rustc_index::Idx; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; +use rustc_middle::mir::interpret::{ConstValue, GlobalAlloc, Scalar}; use rustc_middle::mir::visit::*; use rustc_middle::mir::*; use rustc_middle::ty::TypeVisitableExt; @@ -87,12 +88,19 @@ fn inline<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { let param_env = tcx.param_env_reveal_all_normalized(def_id); + let codegen_fn_attrs = tcx.codegen_fn_attrs(def_id); + + let is_exported = tcx.is_constructor(def_id.into()) + || tcx.generics_of(def_id).requires_monomorphization(tcx) + || codegen_fn_attrs.requests_inline(); + let mut this = Inliner { tcx, param_env, - codegen_fn_attrs: tcx.codegen_fn_attrs(def_id), + codegen_fn_attrs, history: Vec::new(), changed: false, + is_exported, }; let blocks = START_BLOCK..body.basic_blocks.next_index(); this.process_blocks(body, blocks); @@ -112,6 +120,7 @@ struct Inliner<'tcx> { history: Vec, /// Indicates that the caller body has been modified. changed: bool, + is_exported: bool, } impl<'tcx> Inliner<'tcx> { @@ -190,7 +199,9 @@ impl<'tcx> Inliner<'tcx> { self.check_mir_is_available(caller_body, &callsite.callee)?; let callee_body = try_instance_mir(self.tcx, callsite.callee.def)?; - self.check_mir_is_exportable(&callsite.callee, callee_attrs, callee_body)?; + if self.is_exported { + self.check_mir_is_exportable(&callsite.callee, callee_attrs, callee_body)?; + } self.check_mir_body(callsite, callee_body, callee_attrs)?; if !self.tcx.consider_optimizing(|| { @@ -382,15 +393,19 @@ impl<'tcx> Inliner<'tcx> { return Ok(()); } - // So now we are trying to inline a function from another crate which is not inline and is - // not a generic. This might work. But if this pulls in a symbol from the other crater, we - // will fail to link. - // So this is our heuritic for handling this: + // The general situation we need to avoid is this: + // * We inline a non-exported function into an exported function + // * The non-exported function pulls a symbol previously determined to be internal into an + // exported function + // * Later, we inline the exported function (which is now not exportable) into another crate + // * Linker error (or ICE) + // + // So this is our heuristic for preventing that: // If this function has any calls in it, that might reference an internal symbol. // If this function contains a pointer constant, that might be a reference to an internal // static. // So if either of those conditions are met, we cannot inline this. - if !contains_pointer_constant(callee_body) { + if !contains_pointer_constant(self.tcx, callee_body) { debug!("Has no pointer constants, must be exportable"); return Ok(()); } @@ -1184,17 +1199,18 @@ fn try_instance_mir<'tcx>( } } -fn contains_pointer_constant(body: &Body<'_>) -> bool { - let mut finder = ConstFinder { found: false }; +fn contains_pointer_constant<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { + let mut finder = ConstFinder { tcx, found: false }; finder.visit_body(body); finder.found } -struct ConstFinder { +struct ConstFinder<'tcx> { + tcx: TyCtxt<'tcx>, found: bool, } -impl Visitor<'_> for ConstFinder { +impl<'tcx> Visitor<'_> for ConstFinder<'tcx> { fn visit_constant(&mut self, constant: &Constant<'_>, location: Location) { debug!("Visiting constant: {:?}", constant.literal); if let ConstantKind::Unevaluated(..) = constant.literal { @@ -1203,16 +1219,13 @@ impl Visitor<'_> for ConstFinder { } if let ConstantKind::Val(val, ty) = constant.literal { - ty::tls::with(|tcx| { - let val = tcx.lift(val).unwrap(); - if let ConstValue::Scalar(Scalar::Ptr(ptr, _size)) = val { - if let Some(GlobalAlloc::Static(_)) = - tcx.try_get_global_alloc(ptr.into_parts().0) - { - self.found = true; - } + if let ConstValue::Scalar(Scalar::Ptr(ptr, _size)) = val { + if let Some(GlobalAlloc::Static(_)) = + self.tcx.try_get_global_alloc(ptr.into_parts().0) + { + self.found = true; } - }); + } if ty.is_fn() { self.found = true; diff --git a/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-abort.diff b/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-abort.diff index 516f13586d3ff..759d89b2a7650 100644 --- a/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-abort.diff +++ b/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-abort.diff @@ -3,17 +3,42 @@ fn main() -> () { let mut _0: (); - let _1: (); - let mut _2: ((), u8, u8); + let mut _5: (); + let mut _6: u8; + let mut _7: u8; + scope 1 (inlined encode) { + debug this => ((), u8, u8){ .0 => _5, .1 => _6, .2 => _7, }; + let mut _1: bool; + let mut _2: bool; + let mut _3: u8; + let mut _4: !; + } bb0: { + StorageLive(_5); + StorageLive(_6); + _5 = const (); + _6 = const 0_u8; + _7 = const 0_u8; + StorageLive(_1); StorageLive(_2); - _2 = (const (), const 0_u8, const 0_u8); - _1 = encode(move _2) -> [return: bb1, unwind unreachable]; +- _2 = Eq(_7, const 0_u8); +- _1 = Not(move _2); ++ _2 = const true; ++ _1 = const false; + StorageDead(_2); +- switchInt(move _1) -> [0: bb2, otherwise: bb1]; ++ switchInt(const false) -> [0: bb2, otherwise: bb1]; } bb1: { - StorageDead(_2); + _4 = core::panicking::panic(const "assertion failed: this.2 == 0") -> unwind unreachable; + } + + bb2: { + StorageDead(_1); + StorageDead(_5); + StorageDead(_6); return; } } diff --git a/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-unwind.diff b/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-unwind.diff index e83c18735b612..066262a4ec812 100644 --- a/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-unwind.diff +++ b/tests/mir-opt/const_prop/issue_66971.main.ConstProp.panic-unwind.diff @@ -3,17 +3,42 @@ fn main() -> () { let mut _0: (); - let _1: (); - let mut _2: ((), u8, u8); + let mut _5: (); + let mut _6: u8; + let mut _7: u8; + scope 1 (inlined encode) { + debug this => ((), u8, u8){ .0 => _5, .1 => _6, .2 => _7, }; + let mut _1: bool; + let mut _2: bool; + let mut _3: u8; + let mut _4: !; + } bb0: { + StorageLive(_5); + StorageLive(_6); + _5 = const (); + _6 = const 0_u8; + _7 = const 0_u8; + StorageLive(_1); StorageLive(_2); - _2 = (const (), const 0_u8, const 0_u8); - _1 = encode(move _2) -> bb1; +- _2 = Eq(_7, const 0_u8); +- _1 = Not(move _2); ++ _2 = const true; ++ _1 = const false; + StorageDead(_2); +- switchInt(move _1) -> [0: bb2, otherwise: bb1]; ++ switchInt(const false) -> [0: bb2, otherwise: bb1]; } bb1: { - StorageDead(_2); + _4 = core::panicking::panic(const "assertion failed: this.2 == 0"); + } + + bb2: { + StorageDead(_1); + StorageDead(_5); + StorageDead(_6); return; } } diff --git a/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-abort.diff b/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-abort.diff index 96b4093726c88..ad6361a59d694 100644 --- a/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-abort.diff +++ b/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-abort.diff @@ -3,22 +3,38 @@ fn main() -> () { let mut _0: (); - let _1: (); - let mut _2: ((u8, u8),); - let mut _3: (u8, u8); + let mut _5: u8; + let mut _6: u8; + let mut _7: u8; + let mut _8: u8; + scope 1 (inlined test) { + debug this => ((u8, u8),){ .0.0 => _7, .0.1 => _8, }; + let mut _1: bool; + let mut _2: bool; + let mut _3: u8; + let mut _4: !; + } bb0: { + _7 = const 1_u8; + _8 = const 2_u8; + StorageLive(_1); StorageLive(_2); - StorageLive(_3); -- _3 = (const 1_u8, const 2_u8); -+ _3 = const (1_u8, 2_u8); - _2 = (move _3,); - StorageDead(_3); - _1 = test(move _2) -> [return: bb1, unwind unreachable]; +- _2 = Eq(_7, const 1_u8); +- _1 = Not(move _2); ++ _2 = const true; ++ _1 = const false; + StorageDead(_2); +- switchInt(move _1) -> [0: bb2, otherwise: bb1]; ++ switchInt(const false) -> [0: bb2, otherwise: bb1]; } bb1: { - StorageDead(_2); + _4 = core::panicking::panic(const "assertion failed: (this.0).0 == 1") -> unwind unreachable; + } + + bb2: { + StorageDead(_1); return; } } diff --git a/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-unwind.diff b/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-unwind.diff index cbbc2582973a7..42249cb4f06ad 100644 --- a/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-unwind.diff +++ b/tests/mir-opt/const_prop/issue_67019.main.ConstProp.panic-unwind.diff @@ -3,22 +3,38 @@ fn main() -> () { let mut _0: (); - let _1: (); - let mut _2: ((u8, u8),); - let mut _3: (u8, u8); + let mut _5: u8; + let mut _6: u8; + let mut _7: u8; + let mut _8: u8; + scope 1 (inlined test) { + debug this => ((u8, u8),){ .0.0 => _7, .0.1 => _8, }; + let mut _1: bool; + let mut _2: bool; + let mut _3: u8; + let mut _4: !; + } bb0: { + _7 = const 1_u8; + _8 = const 2_u8; + StorageLive(_1); StorageLive(_2); - StorageLive(_3); -- _3 = (const 1_u8, const 2_u8); -+ _3 = const (1_u8, 2_u8); - _2 = (move _3,); - StorageDead(_3); - _1 = test(move _2) -> bb1; +- _2 = Eq(_7, const 1_u8); +- _1 = Not(move _2); ++ _2 = const true; ++ _1 = const false; + StorageDead(_2); +- switchInt(move _1) -> [0: bb2, otherwise: bb1]; ++ switchInt(const false) -> [0: bb2, otherwise: bb1]; } bb1: { - StorageDead(_2); + _4 = core::panicking::panic(const "assertion failed: (this.0).0 == 1"); + } + + bb2: { + StorageDead(_1); return; } } From f40a8681d4835c5c4e3205eeada8d0e17c252321 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Wed, 21 Jun 2023 22:16:23 -0400 Subject: [PATCH 3/3] Improve comments --- compiler/rustc_mir_transform/src/inline.rs | 23 +++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index e5243a5264bc1..94a903851baca 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -399,13 +399,8 @@ impl<'tcx> Inliner<'tcx> { // exported function // * Later, we inline the exported function (which is now not exportable) into another crate // * Linker error (or ICE) - // - // So this is our heuristic for preventing that: - // If this function has any calls in it, that might reference an internal symbol. - // If this function contains a pointer constant, that might be a reference to an internal - // static. - // So if either of those conditions are met, we cannot inline this. - if !contains_pointer_constant(self.tcx, callee_body) { + // So we search the callee body for anything that might cause problems if exported. + if !may_contain_unexportable_constant(self.tcx, callee_body) { debug!("Has no pointer constants, must be exportable"); return Ok(()); } @@ -1199,26 +1194,29 @@ fn try_instance_mir<'tcx>( } } -fn contains_pointer_constant<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { - let mut finder = ConstFinder { tcx, found: false }; +fn may_contain_unexportable_constant<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { + let mut finder = UnexportableConstFinder { tcx, found: false }; finder.visit_body(body); finder.found } -struct ConstFinder<'tcx> { +struct UnexportableConstFinder<'tcx> { tcx: TyCtxt<'tcx>, found: bool, } -impl<'tcx> Visitor<'_> for ConstFinder<'tcx> { +impl<'tcx> Visitor<'_> for UnexportableConstFinder<'tcx> { fn visit_constant(&mut self, constant: &Constant<'_>, location: Location) { debug!("Visiting constant: {:?}", constant.literal); + // Unevaluated constants could be anything. Ouch. if let ConstantKind::Unevaluated(..) = constant.literal { self.found = true; return; } if let ConstantKind::Val(val, ty) = constant.literal { + // Constants which contain a pointer to a static are mentions of a static. Since + // statics can be private, exporting this MIR may cause a linker error. if let ConstValue::Scalar(Scalar::Ptr(ptr, _size)) = val { if let Some(GlobalAlloc::Static(_)) = self.tcx.try_get_global_alloc(ptr.into_parts().0) @@ -1227,10 +1225,13 @@ impl<'tcx> Visitor<'_> for ConstFinder<'tcx> { } } + // fn constants are mentions of other functions; if those functions are not exportable, + // exporting this function would break compilation. if ty.is_fn() { self.found = true; } } + self.super_constant(constant, location); } }