Auto merge of #122580 - saethlin:compiler-builtins-can-panic, r=pnkfelix

"Handle" calls to upstream monomorphizations in compiler_builtins

This is pretty cooked, but I think it works.

compiler-builtins has a long-standing problem that at link time, its rlib cannot contain any calls to `core`. And yet, in codegen we _love_ inserting calls to symbols in `core`, generally from various panic entrypoints.

I intend this PR to attack that problem as completely as possible. When we generate a function call, we now check if we are generating a function call from `compiler_builtins` and whether the callee is a function which was not lowered in the current crate, meaning we will have to link to it.

If those conditions are met, actually generating the call is asking for a linker error. So we don't. If the callee diverges, we lower to an abort with the same behavior as `core::intrinsics::abort`. If the callee does not diverge, we produce an error. This means that compiler-builtins can contain panics, but they'll SIGILL instead of panicking. I made non-diverging calls a compile error because I'm guessing that they'd mostly get into compiler-builtins by someone making a mistake while working on the crate, and compile errors are better than linker errors. We could turn such calls into aborts as well if that's preferred.
This commit is contained in:
bors 2024-03-22 16:55:11 +00:00
commit b3df0d7e5e
17 changed files with 284 additions and 29 deletions

View File

@ -214,7 +214,7 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9792d37ca5173d7e7f4fe453739a0671d0557915a030a383d6b866476bbc3e71" checksum = "9792d37ca5173d7e7f4fe453739a0671d0557915a030a383d6b866476bbc3e71"
dependencies = [ dependencies = [
"object", "object 0.32.2",
] ]
[[package]] [[package]]
@ -281,7 +281,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"miniz_oxide", "miniz_oxide",
"object", "object 0.32.2",
"rustc-demangle", "rustc-demangle",
] ]
@ -2636,10 +2636,21 @@ dependencies = [
"memchr", "memchr",
"rustc-std-workspace-alloc", "rustc-std-workspace-alloc",
"rustc-std-workspace-core", "rustc-std-workspace-core",
"ruzstd", "ruzstd 0.5.0",
"wasmparser", "wasmparser",
] ]
[[package]]
name = "object"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7090bae93f8585aad99e595b7073c5de9ba89fbd6b4e9f0cdd7a10177273ac8"
dependencies = [
"flate2",
"memchr",
"ruzstd 0.6.0",
]
[[package]] [[package]]
name = "odht" name = "odht"
version = "0.3.1" version = "0.3.1"
@ -3323,6 +3334,7 @@ dependencies = [
name = "run_make_support" name = "run_make_support"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"object 0.34.0",
"wasmparser", "wasmparser",
] ]
@ -3634,7 +3646,7 @@ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"libc", "libc",
"measureme", "measureme",
"object", "object 0.32.2",
"rustc-demangle", "rustc-demangle",
"rustc_ast", "rustc_ast",
"rustc_attr", "rustc_attr",
@ -3670,7 +3682,7 @@ dependencies = [
"itertools 0.12.1", "itertools 0.12.1",
"jobserver", "jobserver",
"libc", "libc",
"object", "object 0.32.2",
"pathdiff", "pathdiff",
"regex", "regex",
"rustc_arena", "rustc_arena",
@ -3686,6 +3698,7 @@ dependencies = [
"rustc_macros", "rustc_macros",
"rustc_metadata", "rustc_metadata",
"rustc_middle", "rustc_middle",
"rustc_monomorphize",
"rustc_query_system", "rustc_query_system",
"rustc_serialize", "rustc_serialize",
"rustc_session", "rustc_session",
@ -4630,7 +4643,7 @@ name = "rustc_target"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"object", "object 0.32.2",
"rustc_abi", "rustc_abi",
"rustc_data_structures", "rustc_data_structures",
"rustc_feature", "rustc_feature",
@ -4897,6 +4910,17 @@ dependencies = [
"twox-hash", "twox-hash",
] ]
[[package]]
name = "ruzstd"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b"
dependencies = [
"byteorder",
"derive_more",
"twox-hash",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
@ -5200,7 +5224,7 @@ dependencies = [
"hermit-abi", "hermit-abi",
"libc", "libc",
"miniz_oxide", "miniz_oxide",
"object", "object 0.32.2",
"panic_abort", "panic_abort",
"panic_unwind", "panic_unwind",
"profiler_builtins", "profiler_builtins",
@ -5517,7 +5541,7 @@ checksum = "4db52ee8fec06e119b692ef3dd2c4cf621a99204c1b8c47407870ed050305b9b"
dependencies = [ dependencies = [
"gimli", "gimli",
"hashbrown", "hashbrown",
"object", "object 0.32.2",
"tracing", "tracing",
] ]

View File

@ -66,15 +66,6 @@ exclude = [
] ]
[profile.release.package.compiler_builtins] [profile.release.package.compiler_builtins]
# The compiler-builtins crate cannot reference libcore, and its own CI will
# verify that this is the case. This requires, however, that the crate is built
# without overflow checks and debug assertions. Forcefully disable debug
# assertions and overflow checks here which should ensure that even if these
# assertions are enabled for libstd we won't enable them for compiler_builtins
# which should ensure we still link everything correctly.
debug-assertions = false
overflow-checks = false
# For compiler-builtins we always use a high number of codegen units. # For compiler-builtins we always use a high number of codegen units.
# The goal here is to place every single intrinsic into its own object # The goal here is to place every single intrinsic into its own object
# file to avoid symbol clashes with the system libgcc if possible. Note # file to avoid symbol clashes with the system libgcc if possible. Note

View File

@ -8,8 +8,11 @@ use std::borrow::Cow;
use cranelift_codegen::ir::SigRef; use cranelift_codegen::ir::SigRef;
use cranelift_module::ModuleError; use cranelift_module::ModuleError;
use rustc_codegen_ssa::errors::CompilerBuiltinsCannotCall;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::ty::layout::FnAbiOf; use rustc_middle::ty::layout::FnAbiOf;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_monomorphize::is_call_from_compiler_builtins_to_upstream_monomorphization;
use rustc_session::Session; use rustc_session::Session;
use rustc_span::source_map::Spanned; use rustc_span::source_map::Spanned;
use rustc_target::abi::call::{Conv, FnAbi}; use rustc_target::abi::call::{Conv, FnAbi};
@ -372,6 +375,17 @@ pub(crate) fn codegen_terminator_call<'tcx>(
ty::Instance::expect_resolve(fx.tcx, ty::ParamEnv::reveal_all(), def_id, fn_args) ty::Instance::expect_resolve(fx.tcx, ty::ParamEnv::reveal_all(), def_id, fn_args)
.polymorphize(fx.tcx); .polymorphize(fx.tcx);
if is_call_from_compiler_builtins_to_upstream_monomorphization(fx.tcx, instance) {
if target.is_some() {
let caller = with_no_trimmed_paths!(fx.tcx.def_path_str(fx.instance.def_id()));
let callee = with_no_trimmed_paths!(fx.tcx.def_path_str(def_id));
fx.tcx.dcx().emit_err(CompilerBuiltinsCannotCall { caller, callee });
} else {
fx.bcx.ins().trap(TrapCode::User(0));
return;
}
}
if fx.tcx.symbol_name(instance).name.starts_with("llvm.") { if fx.tcx.symbol_name(instance).name.starts_with("llvm.") {
crate::intrinsics::codegen_llvm_intrinsic_call( crate::intrinsics::codegen_llvm_intrinsic_call(
fx, fx,

View File

@ -8,6 +8,7 @@ use rustc_index::IndexVec;
use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::layout::FnAbiOf; use rustc_middle::ty::layout::FnAbiOf;
use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_monomorphize::is_call_from_compiler_builtins_to_upstream_monomorphization;
use crate::constant::ConstantCx; use crate::constant::ConstantCx;
use crate::debuginfo::FunctionDebugContext; use crate::debuginfo::FunctionDebugContext;
@ -999,6 +1000,12 @@ fn codegen_panic_inner<'tcx>(
let def_id = fx.tcx.require_lang_item(lang_item, span); let def_id = fx.tcx.require_lang_item(lang_item, span);
let instance = Instance::mono(fx.tcx, def_id).polymorphize(fx.tcx); let instance = Instance::mono(fx.tcx, def_id).polymorphize(fx.tcx);
if is_call_from_compiler_builtins_to_upstream_monomorphization(fx.tcx, instance) {
fx.bcx.ins().trap(TrapCode::User(0));
return;
}
let symbol_name = fx.tcx.symbol_name(instance).name; let symbol_name = fx.tcx.symbol_name(instance).name;
fx.lib_call( fx.lib_call(

View File

@ -21,6 +21,7 @@ extern crate rustc_hir;
extern crate rustc_incremental; extern crate rustc_incremental;
extern crate rustc_index; extern crate rustc_index;
extern crate rustc_metadata; extern crate rustc_metadata;
extern crate rustc_monomorphize;
extern crate rustc_session; extern crate rustc_session;
extern crate rustc_span; extern crate rustc_span;
extern crate rustc_target; extern crate rustc_target;

View File

@ -25,6 +25,7 @@ rustc_index = { path = "../rustc_index" }
rustc_macros = { path = "../rustc_macros" } rustc_macros = { path = "../rustc_macros" }
rustc_metadata = { path = "../rustc_metadata" } rustc_metadata = { path = "../rustc_metadata" }
rustc_middle = { path = "../rustc_middle" } rustc_middle = { path = "../rustc_middle" }
rustc_monomorphize = { path = "../rustc_monomorphize" }
rustc_query_system = { path = "../rustc_query_system" } rustc_query_system = { path = "../rustc_query_system" }
rustc_serialize = { path = "../rustc_serialize" } rustc_serialize = { path = "../rustc_serialize" }
rustc_session = { path = "../rustc_session" } rustc_session = { path = "../rustc_session" }

View File

@ -16,6 +16,9 @@ codegen_ssa_cgu_not_recorded =
codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option.
codegen_ssa_compiler_builtins_cannot_call =
`compiler_builtins` cannot call functions through upstream monomorphizations; encountered invalid call from `{$caller}` to `{$callee}`
codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error} codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error}
codegen_ssa_copy_path_buf = unable to copy {$source_file} to {$output_path}: {$error} codegen_ssa_copy_path_buf = unable to copy {$source_file} to {$output_path}: {$error}

View File

@ -2,6 +2,7 @@
use rustc_hir::LangItem; use rustc_hir::LangItem;
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::ty::Instance;
use rustc_middle::ty::{self, layout::TyAndLayout, Ty, TyCtxt}; use rustc_middle::ty::{self, layout::TyAndLayout, Ty, TyCtxt};
use rustc_span::Span; use rustc_span::Span;
@ -120,11 +121,11 @@ pub fn build_langcall<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
bx: &Bx, bx: &Bx,
span: Option<Span>, span: Option<Span>,
li: LangItem, li: LangItem,
) -> (Bx::FnAbiOfResult, Bx::Value) { ) -> (Bx::FnAbiOfResult, Bx::Value, Instance<'tcx>) {
let tcx = bx.tcx(); let tcx = bx.tcx();
let def_id = tcx.require_lang_item(li, span); let def_id = tcx.require_lang_item(li, span);
let instance = ty::Instance::mono(tcx, def_id); let instance = ty::Instance::mono(tcx, def_id);
(bx.fn_abi_of_instance(instance, ty::List::empty()), bx.get_fn_addr(instance)) (bx.fn_abi_of_instance(instance, ty::List::empty()), bx.get_fn_addr(instance), instance)
} }
// To avoid UB from LLVM, these two functions mask RHS with an // To avoid UB from LLVM, these two functions mask RHS with an

View File

@ -1030,3 +1030,10 @@ pub struct FailedToGetLayout<'tcx> {
pub struct ErrorCreatingRemarkDir { pub struct ErrorCreatingRemarkDir {
pub error: std::io::Error, pub error: std::io::Error,
} }
#[derive(Diagnostic)]
#[diag(codegen_ssa_compiler_builtins_cannot_call)]
pub struct CompilerBuiltinsCannotCall {
pub caller: String,
pub callee: String,
}

View File

@ -5,6 +5,7 @@ use super::{CachedLlbb, FunctionCx, LocalRef};
use crate::base; use crate::base;
use crate::common::{self, IntPredicate}; use crate::common::{self, IntPredicate};
use crate::errors::CompilerBuiltinsCannotCall;
use crate::meth; use crate::meth;
use crate::traits::*; use crate::traits::*;
use crate::MemFlags; use crate::MemFlags;
@ -16,6 +17,7 @@ use rustc_middle::mir::{self, AssertKind, BasicBlock, SwitchTargets, UnwindTermi
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement}; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::ty::{self, Instance, Ty};
use rustc_monomorphize::is_call_from_compiler_builtins_to_upstream_monomorphization;
use rustc_session::config::OptLevel; use rustc_session::config::OptLevel;
use rustc_span::{source_map::Spanned, sym, Span}; use rustc_span::{source_map::Spanned, sym, Span};
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode, Reg}; use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode, Reg};
@ -157,8 +159,28 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
destination: Option<(ReturnDest<'tcx, Bx::Value>, mir::BasicBlock)>, destination: Option<(ReturnDest<'tcx, Bx::Value>, mir::BasicBlock)>,
mut unwind: mir::UnwindAction, mut unwind: mir::UnwindAction,
copied_constant_arguments: &[PlaceRef<'tcx, <Bx as BackendTypes>::Value>], copied_constant_arguments: &[PlaceRef<'tcx, <Bx as BackendTypes>::Value>],
instance: Option<Instance<'tcx>>,
mergeable_succ: bool, mergeable_succ: bool,
) -> MergingSucc { ) -> MergingSucc {
let tcx = bx.tcx();
if let Some(instance) = instance {
if is_call_from_compiler_builtins_to_upstream_monomorphization(tcx, instance) {
if destination.is_some() {
let caller = with_no_trimmed_paths!(tcx.def_path_str(fx.instance.def_id()));
let callee = with_no_trimmed_paths!(tcx.def_path_str(instance.def_id()));
tcx.dcx().emit_err(CompilerBuiltinsCannotCall { caller, callee });
} else {
info!(
"compiler_builtins call to diverging function {:?} replaced with abort",
instance.def_id()
);
bx.abort();
bx.unreachable();
return MergingSucc::False;
}
}
}
// If there is a cleanup block and the function we're calling can unwind, then // If there is a cleanup block and the function we're calling can unwind, then
// do an invoke, otherwise do a call. // do an invoke, otherwise do a call.
let fn_ty = bx.fn_decl_backend_type(fn_abi); let fn_ty = bx.fn_decl_backend_type(fn_abi);
@ -480,6 +502,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let ty = location.ty(self.mir, bx.tcx()).ty; let ty = location.ty(self.mir, bx.tcx()).ty;
let ty = self.monomorphize(ty); let ty = self.monomorphize(ty);
let drop_fn = Instance::resolve_drop_in_place(bx.tcx(), ty); let drop_fn = Instance::resolve_drop_in_place(bx.tcx(), ty);
let instance = drop_fn.clone();
if let ty::InstanceDef::DropGlue(_, None) = drop_fn.def { if let ty::InstanceDef::DropGlue(_, None) = drop_fn.def {
// we don't actually need to drop anything. // we don't actually need to drop anything.
@ -582,6 +605,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
Some((ReturnDest::Nothing, target)), Some((ReturnDest::Nothing, target)),
unwind, unwind,
&[], &[],
Some(instance),
mergeable_succ, mergeable_succ,
) )
} }
@ -658,10 +682,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
} }
}; };
let (fn_abi, llfn) = common::build_langcall(bx, Some(span), lang_item); let (fn_abi, llfn, instance) = common::build_langcall(bx, Some(span), lang_item);
// Codegen the actual panic invoke/call. // Codegen the actual panic invoke/call.
let merging_succ = helper.do_call(self, bx, fn_abi, llfn, &args, None, unwind, &[], false); let merging_succ =
helper.do_call(self, bx, fn_abi, llfn, &args, None, unwind, &[], Some(instance), false);
assert_eq!(merging_succ, MergingSucc::False); assert_eq!(merging_succ, MergingSucc::False);
MergingSucc::False MergingSucc::False
} }
@ -677,7 +702,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
self.set_debug_loc(bx, terminator.source_info); self.set_debug_loc(bx, terminator.source_info);
// Obtain the panic entry point. // Obtain the panic entry point.
let (fn_abi, llfn) = common::build_langcall(bx, Some(span), reason.lang_item()); let (fn_abi, llfn, instance) = common::build_langcall(bx, Some(span), reason.lang_item());
// Codegen the actual panic invoke/call. // Codegen the actual panic invoke/call.
let merging_succ = helper.do_call( let merging_succ = helper.do_call(
@ -689,6 +714,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
None, None,
mir::UnwindAction::Unreachable, mir::UnwindAction::Unreachable,
&[], &[],
Some(instance),
false, false,
); );
assert_eq!(merging_succ, MergingSucc::False); assert_eq!(merging_succ, MergingSucc::False);
@ -738,7 +764,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let msg = bx.const_str(&msg_str); let msg = bx.const_str(&msg_str);
// Obtain the panic entry point. // Obtain the panic entry point.
let (fn_abi, llfn) = let (fn_abi, llfn, instance) =
common::build_langcall(bx, Some(source_info.span), LangItem::PanicNounwind); common::build_langcall(bx, Some(source_info.span), LangItem::PanicNounwind);
// Codegen the actual panic invoke/call. // Codegen the actual panic invoke/call.
@ -751,6 +777,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
target.as_ref().map(|bb| (ReturnDest::Nothing, *bb)), target.as_ref().map(|bb| (ReturnDest::Nothing, *bb)),
unwind, unwind,
&[], &[],
Some(instance),
mergeable_succ, mergeable_succ,
) )
} else { } else {
@ -798,6 +825,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
ty::FnPtr(_) => (None, Some(callee.immediate())), ty::FnPtr(_) => (None, Some(callee.immediate())),
_ => bug!("{} is not callable", callee.layout.ty), _ => bug!("{} is not callable", callee.layout.ty),
}; };
let def = instance.map(|i| i.def); let def = instance.map(|i| i.def);
if let Some(ty::InstanceDef::DropGlue(_, None)) = def { if let Some(ty::InstanceDef::DropGlue(_, None)) = def {
@ -1106,6 +1134,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
destination, destination,
unwind, unwind,
&copied_constant_arguments, &copied_constant_arguments,
instance,
mergeable_succ, mergeable_succ,
) )
} }
@ -1664,11 +1693,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
self.set_debug_loc(&mut bx, mir::SourceInfo::outermost(self.mir.span)); self.set_debug_loc(&mut bx, mir::SourceInfo::outermost(self.mir.span));
let (fn_abi, fn_ptr) = common::build_langcall(&bx, None, reason.lang_item()); let (fn_abi, fn_ptr, instance) = common::build_langcall(&bx, None, reason.lang_item());
let fn_ty = bx.fn_decl_backend_type(fn_abi); if is_call_from_compiler_builtins_to_upstream_monomorphization(bx.tcx(), instance) {
bx.abort();
} else {
let fn_ty = bx.fn_decl_backend_type(fn_abi);
let llret = bx.call(fn_ty, None, Some(fn_abi), fn_ptr, &[], funclet.as_ref()); let llret = bx.call(fn_ty, None, Some(fn_abi), fn_ptr, &[], funclet.as_ref());
bx.apply_attrs_to_cleanup_callsite(llret); bx.apply_attrs_to_cleanup_callsite(llret);
}
bx.unreachable(); bx.unreachable();

View File

@ -62,7 +62,8 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
let msg = bx.const_str(&msg_str); let msg = bx.const_str(&msg_str);
// Obtain the panic entry point. // Obtain the panic entry point.
let (fn_abi, llfn) = common::build_langcall(bx, None, LangItem::PanicNounwind); let (fn_abi, llfn, _instance) =
common::build_langcall(bx, None, LangItem::PanicNounwind);
// Generate the call. // Generate the call.
// Cannot use `do_call` since we don't have a MIR terminator so we can't create a `TerminationCodegenHelper`. // Cannot use `do_call` since we don't have a MIR terminator so we can't create a `TerminationCodegenHelper`.

View File

@ -10,6 +10,7 @@ use rustc_hir::lang_items::LangItem;
use rustc_index::bit_set::FiniteBitSet; use rustc_index::bit_set::FiniteBitSet;
use rustc_macros::HashStable; use rustc_macros::HashStable;
use rustc_middle::ty::normalize_erasing_regions::NormalizationError; use rustc_middle::ty::normalize_erasing_regions::NormalizationError;
use rustc_span::def_id::LOCAL_CRATE;
use rustc_span::Symbol; use rustc_span::Symbol;
use std::assert_matches::assert_matches; use std::assert_matches::assert_matches;
@ -172,6 +173,11 @@ impl<'tcx> Instance<'tcx> {
// If this a non-generic instance, it cannot be a shared monomorphization. // If this a non-generic instance, it cannot be a shared monomorphization.
self.args.non_erasable_generics(tcx, self.def_id()).next()?; self.args.non_erasable_generics(tcx, self.def_id()).next()?;
// compiler_builtins cannot use upstream monomorphizations.
if tcx.is_compiler_builtins(LOCAL_CRATE) {
return None;
}
match self.def { match self.def {
InstanceDef::Item(def) => tcx InstanceDef::Item(def) => tcx
.upstream_monomorphizations_for(def) .upstream_monomorphizations_for(def)

View File

@ -1107,7 +1107,7 @@ fn visit_instance_use<'tcx>(
/// Returns `true` if we should codegen an instance in the local crate, or returns `false` if we /// Returns `true` if we should codegen an instance in the local crate, or returns `false` if we
/// can just link to the upstream crate and therefore don't need a mono item. /// can just link to the upstream crate and therefore don't need a mono item.
fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) -> bool { pub(crate) fn should_codegen_locally<'tcx>(tcx: TyCtxt<'tcx>, instance: &Instance<'tcx>) -> bool {
let Some(def_id) = instance.def.def_id_if_not_guaranteed_local_codegen() else { let Some(def_id) = instance.def.def_id_if_not_guaranteed_local_codegen() else {
return true; return true;
}; };

View File

@ -11,7 +11,10 @@ use rustc_hir::lang_items::LangItem;
use rustc_middle::query::{Providers, TyCtxtAt}; use rustc_middle::query::{Providers, TyCtxtAt};
use rustc_middle::traits; use rustc_middle::traits;
use rustc_middle::ty::adjustment::CustomCoerceUnsized; use rustc_middle::ty::adjustment::CustomCoerceUnsized;
use rustc_middle::ty::Instance;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_span::def_id::LOCAL_CRATE;
use rustc_span::ErrorGuaranteed; use rustc_span::ErrorGuaranteed;
mod collector; mod collector;
@ -20,6 +23,8 @@ mod partitioning;
mod polymorphize; mod polymorphize;
mod util; mod util;
use collector::should_codegen_locally;
rustc_fluent_macro::fluent_messages! { "../messages.ftl" } rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
fn custom_coerce_unsize_info<'tcx>( fn custom_coerce_unsize_info<'tcx>(
@ -45,6 +50,23 @@ fn custom_coerce_unsize_info<'tcx>(
} }
} }
/// Returns whether a call from the current crate to the [`Instance`] would produce a call
/// from `compiler_builtins` to a symbol the linker must resolve.
///
/// Such calls from `compiler_bultins` are effectively impossible for the linker to handle. Some
/// linkers will optimize such that dead calls to unresolved symbols are not an error, but this is
/// not guaranteed. So we used this function in codegen backends to ensure we do not generate any
/// unlinkable calls.
pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>(
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
) -> bool {
!instance.def_id().is_local()
&& tcx.is_compiler_builtins(LOCAL_CRATE)
&& tcx.codegen_fn_attrs(instance.def_id()).link_name.is_none()
&& !should_codegen_locally(tcx, &instance)
}
pub fn provide(providers: &mut Providers) { pub fn provide(providers: &mut Providers) {
partitioning::provide(providers); partitioning::provide(providers);
polymorphize::provide(providers); polymorphize::provide(providers);

View File

@ -4,4 +4,5 @@ version = "0.0.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
object = "0.34.0"
wasmparser = "0.118.2" wasmparser = "0.118.2"

View File

@ -2,6 +2,7 @@ use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Output}; use std::process::{Command, Output};
pub use object;
pub use wasmparser; pub use wasmparser;
pub fn out_dir() -> PathBuf { pub fn out_dir() -> PathBuf {

View File

@ -0,0 +1,142 @@
//! The compiler_builtins library is special. It can call functions in core, but it must not
//! require linkage against a build of core. If it ever does, building the standard library *may*
//! result in linker errors, depending on whether the linker in use applies optimizations first or
//! resolves symbols first. So the portable and safe approach is to forbid such a linkage
//! requirement entirely.
//!
//! In addition, whether compiler_builtins requires linkage against core can depend on optimization
//! settings. Turning off optimizations and enabling debug assertions tends to produce the most
//! dependence on core that is possible, so that is the configuration we test here.
#![deny(warnings)]
extern crate run_make_support;
use run_make_support::object;
use run_make_support::object::read::archive::ArchiveFile;
use run_make_support::object::read::Object;
use run_make_support::object::ObjectSection;
use run_make_support::object::ObjectSymbol;
use run_make_support::object::RelocationTarget;
use run_make_support::out_dir;
use std::collections::HashSet;
const MANIFEST: &str = r#"
[package]
name = "scratch"
version = "0.1.0"
edition = "2021"
[lib]
path = "lib.rs""#;
fn main() {
let target_dir = out_dir().join("target");
let target = std::env::var("TARGET").unwrap();
if target.starts_with("wasm") || target.starts_with("nvptx") {
// wasm and nvptx targets don't produce rlib files that object can parse.
return;
}
println!("Testing compiler_builtins for {}", target);
// Set up the tiniest Cargo project: An empty no_std library. Just enough to run -Zbuild-std.
let manifest_path = out_dir().join("Cargo.toml");
std::fs::write(&manifest_path, MANIFEST.as_bytes()).unwrap();
std::fs::write(out_dir().join("lib.rs"), b"#![no_std]").unwrap();
let path = std::env::var("PATH").unwrap();
let rustc = std::env::var("RUSTC").unwrap();
let bootstrap_cargo = std::env::var("BOOTSTRAP_CARGO").unwrap();
let status = std::process::Command::new(bootstrap_cargo)
.args([
"build",
"--manifest-path",
manifest_path.to_str().unwrap(),
"-Zbuild-std=core",
"--target",
&target,
])
.env_clear()
.env("PATH", path)
.env("RUSTC", rustc)
.env("RUSTFLAGS", "-Copt-level=0 -Cdebug-assertions=yes")
.env("CARGO_TARGET_DIR", &target_dir)
.env("RUSTC_BOOTSTRAP", "1")
.status()
.unwrap();
assert!(status.success());
let rlibs_path = target_dir.join(target).join("debug").join("deps");
let compiler_builtins_rlib = std::fs::read_dir(rlibs_path)
.unwrap()
.find_map(|e| {
let path = e.unwrap().path();
let file_name = path.file_name().unwrap().to_str().unwrap();
if file_name.starts_with("libcompiler_builtins") && file_name.ends_with(".rlib") {
Some(path)
} else {
None
}
})
.unwrap();
// rlib files are archives, where the archive members each a CGU, and we also have one called
// lib.rmeta which is the encoded metadata. Each of the CGUs is an object file.
let data = std::fs::read(compiler_builtins_rlib).unwrap();
let mut defined_symbols = HashSet::new();
let mut undefined_relocations = HashSet::new();
let archive = ArchiveFile::parse(&*data).unwrap();
for member in archive.members() {
let member = member.unwrap();
if member.name() == b"lib.rmeta" {
continue;
}
let data = member.data(&*data).unwrap();
let object = object::File::parse(&*data).unwrap();
// Record all defined symbols in this CGU.
for symbol in object.symbols() {
if !symbol.is_undefined() {
let name = symbol.name().unwrap();
defined_symbols.insert(name);
}
}
// Find any relocations against undefined symbols. Calls within this CGU are relocations
// against a defined symbol.
for (_offset, relocation) in object.sections().flat_map(|section| section.relocations()) {
let RelocationTarget::Symbol(symbol_index) = relocation.target() else {
continue;
};
let symbol = object.symbol_by_index(symbol_index).unwrap();
if symbol.is_undefined() {
let name = symbol.name().unwrap();
undefined_relocations.insert(name);
}
}
}
// We can have symbols in the compiler_builtins rlib that are actually from core, if they were
// monomorphized in the compiler_builtins crate. This is totally fine, because though the call
// is to a function in core, it's resolved internally.
//
// It is normal to have relocations against symbols not defined in the rlib for things like
// unwinding, or math functions provided the target's platform libraries. Finding these is not
// a problem, we want to specifically ban relocations against core which are not resolved
// internally.
undefined_relocations
.retain(|symbol| !defined_symbols.contains(symbol) && symbol.contains("core"));
if !undefined_relocations.is_empty() {
panic!(
"compiler_builtins must not link against core, but it does. \n\
These symbols may be undefined in a debug build of compiler_builtins:\n\
{:?}",
undefined_relocations
);
}
}