diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_codegen_ssa/src/back/symbol_export.rs | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs new file mode 100644 index 000000000..e6b605575 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -0,0 +1,590 @@ +use std::collections::hash_map::Entry::*; + +use rustc_ast::expand::allocator::ALLOCATOR_METHODS; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE}; +use rustc_hir::Node; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::middle::exported_symbols::{ + metadata_symbol_name, ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, +}; +use rustc_middle::ty::query::{ExternProviders, Providers}; +use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; +use rustc_middle::ty::Instance; +use rustc_middle::ty::{self, SymbolName, TyCtxt}; +use rustc_session::config::CrateType; +use rustc_target::spec::SanitizerSet; + +pub fn threshold(tcx: TyCtxt<'_>) -> SymbolExportLevel { + crates_export_threshold(&tcx.sess.crate_types()) +} + +fn crate_export_threshold(crate_type: CrateType) -> SymbolExportLevel { + match crate_type { + CrateType::Executable | CrateType::Staticlib | CrateType::ProcMacro | CrateType::Cdylib => { + SymbolExportLevel::C + } + CrateType::Rlib | CrateType::Dylib => SymbolExportLevel::Rust, + } +} + +pub fn crates_export_threshold(crate_types: &[CrateType]) -> SymbolExportLevel { + if crate_types + .iter() + .any(|&crate_type| crate_export_threshold(crate_type) == SymbolExportLevel::Rust) + { + SymbolExportLevel::Rust + } else { + SymbolExportLevel::C + } +} + +fn reachable_non_generics_provider(tcx: TyCtxt<'_>, cnum: CrateNum) -> DefIdMap<SymbolExportInfo> { + assert_eq!(cnum, LOCAL_CRATE); + + if !tcx.sess.opts.output_types.should_codegen() { + return Default::default(); + } + + // Check to see if this crate is a "special runtime crate". These + // crates, implementation details of the standard library, typically + // have a bunch of `pub extern` and `#[no_mangle]` functions as the + // ABI between them. We don't want their symbols to have a `C` + // export level, however, as they're just implementation details. + // Down below we'll hardwire all of the symbols to the `Rust` export + // level instead. + let special_runtime_crate = + tcx.is_panic_runtime(LOCAL_CRATE) || tcx.is_compiler_builtins(LOCAL_CRATE); + + let mut reachable_non_generics: DefIdMap<_> = tcx + .reachable_set(()) + .iter() + .filter_map(|&def_id| { + // We want to ignore some FFI functions that are not exposed from + // this crate. Reachable FFI functions can be lumped into two + // categories: + // + // 1. Those that are included statically via a static library + // 2. Those included otherwise (e.g., dynamically or via a framework) + // + // Although our LLVM module is not literally emitting code for the + // statically included symbols, it's an export of our library which + // needs to be passed on to the linker and encoded in the metadata. + // + // As a result, if this id is an FFI item (foreign item) then we only + // let it through if it's included statically. + match tcx.hir().get_by_def_id(def_id) { + Node::ForeignItem(..) => { + tcx.is_statically_included_foreign_item(def_id).then_some(def_id) + } + + // Only consider nodes that actually have exported symbols. + Node::Item(&hir::Item { + kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn(..), + .. + }) + | Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(..), .. }) => { + let generics = tcx.generics_of(def_id); + if !generics.requires_monomorphization(tcx) + // Functions marked with #[inline] are codegened with "internal" + // linkage and are not exported unless marked with an extern + // indicator + && (!Instance::mono(tcx, def_id.to_def_id()).def.generates_cgu_internal_copy(tcx) + || tcx.codegen_fn_attrs(def_id.to_def_id()).contains_extern_indicator()) + { + Some(def_id) + } else { + None + } + } + + _ => None, + } + }) + .map(|def_id| { + let (export_level, used) = if special_runtime_crate { + let name = tcx.symbol_name(Instance::mono(tcx, def_id.to_def_id())).name; + // We won't link right if these symbols are stripped during LTO. + let used = match name { + "rust_eh_personality" + | "rust_eh_register_frames" + | "rust_eh_unregister_frames" => true, + _ => false, + }; + (SymbolExportLevel::Rust, used) + } else { + (symbol_export_level(tcx, def_id.to_def_id()), false) + }; + let codegen_attrs = tcx.codegen_fn_attrs(def_id.to_def_id()); + debug!( + "EXPORTED SYMBOL (local): {} ({:?})", + tcx.symbol_name(Instance::mono(tcx, def_id.to_def_id())), + export_level + ); + (def_id.to_def_id(), SymbolExportInfo { + level: export_level, + kind: if tcx.is_static(def_id.to_def_id()) { + if codegen_attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL) { + SymbolExportKind::Tls + } else { + SymbolExportKind::Data + } + } else { + SymbolExportKind::Text + }, + used: codegen_attrs.flags.contains(CodegenFnAttrFlags::USED) + || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) || used, + }) + }) + .collect(); + + if let Some(id) = tcx.proc_macro_decls_static(()) { + reachable_non_generics.insert( + id.to_def_id(), + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Data, + used: false, + }, + ); + } + + reachable_non_generics +} + +fn is_reachable_non_generic_provider_local(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + let export_threshold = threshold(tcx); + + if let Some(&info) = tcx.reachable_non_generics(def_id.krate).get(&def_id) { + info.level.is_below_threshold(export_threshold) + } else { + false + } +} + +fn is_reachable_non_generic_provider_extern(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + tcx.reachable_non_generics(def_id.krate).contains_key(&def_id) +} + +fn exported_symbols_provider_local<'tcx>( + tcx: TyCtxt<'tcx>, + cnum: CrateNum, +) -> &'tcx [(ExportedSymbol<'tcx>, SymbolExportInfo)] { + assert_eq!(cnum, LOCAL_CRATE); + + if !tcx.sess.opts.output_types.should_codegen() { + return &[]; + } + + let mut symbols: Vec<_> = tcx + .reachable_non_generics(LOCAL_CRATE) + .iter() + .map(|(&def_id, &info)| (ExportedSymbol::NonGeneric(def_id), info)) + .collect(); + + if tcx.entry_fn(()).is_some() { + let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, "main")); + + symbols.push(( + exported_symbol, + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Text, + used: false, + }, + )); + } + + if tcx.allocator_kind(()).is_some() { + for method in ALLOCATOR_METHODS { + let symbol_name = format!("__rust_{}", method.name); + let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, &symbol_name)); + + symbols.push(( + exported_symbol, + SymbolExportInfo { + level: SymbolExportLevel::Rust, + kind: SymbolExportKind::Text, + used: false, + }, + )); + } + } + + if tcx.sess.instrument_coverage() || tcx.sess.opts.cg.profile_generate.enabled() { + // These are weak symbols that point to the profile version and the + // profile name, which need to be treated as exported so LTO doesn't nix + // them. + const PROFILER_WEAK_SYMBOLS: [&str; 2] = + ["__llvm_profile_raw_version", "__llvm_profile_filename"]; + + symbols.extend(PROFILER_WEAK_SYMBOLS.iter().map(|sym| { + let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, sym)); + ( + exported_symbol, + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Data, + used: false, + }, + ) + })); + } + + if tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) { + let mut msan_weak_symbols = Vec::new(); + + // Similar to profiling, preserve weak msan symbol during LTO. + if tcx.sess.opts.unstable_opts.sanitizer_recover.contains(SanitizerSet::MEMORY) { + msan_weak_symbols.push("__msan_keep_going"); + } + + if tcx.sess.opts.unstable_opts.sanitizer_memory_track_origins != 0 { + msan_weak_symbols.push("__msan_track_origins"); + } + + symbols.extend(msan_weak_symbols.into_iter().map(|sym| { + let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, sym)); + ( + exported_symbol, + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Data, + used: false, + }, + ) + })); + } + + if tcx.sess.crate_types().contains(&CrateType::Dylib) + || tcx.sess.crate_types().contains(&CrateType::ProcMacro) + { + let symbol_name = metadata_symbol_name(tcx); + let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, &symbol_name)); + + symbols.push(( + exported_symbol, + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Data, + used: true, + }, + )); + } + + if tcx.sess.opts.share_generics() && tcx.local_crate_exports_generics() { + use rustc_middle::mir::mono::{Linkage, MonoItem, Visibility}; + use rustc_middle::ty::InstanceDef; + + // Normally, we require that shared monomorphizations are not hidden, + // because if we want to re-use a monomorphization from a Rust dylib, it + // needs to be exported. + // However, on platforms that don't allow for Rust dylibs, having + // external linkage is enough for monomorphization to be linked to. + let need_visibility = tcx.sess.target.dynamic_linking && !tcx.sess.target.only_cdylib; + + let (_, cgus) = tcx.collect_and_partition_mono_items(()); + + for (mono_item, &(linkage, visibility)) in cgus.iter().flat_map(|cgu| cgu.items().iter()) { + if linkage != Linkage::External { + // We can only re-use things with external linkage, otherwise + // we'll get a linker error + continue; + } + + if need_visibility && visibility == Visibility::Hidden { + // If we potentially share things from Rust dylibs, they must + // not be hidden + continue; + } + + match *mono_item { + MonoItem::Fn(Instance { def: InstanceDef::Item(def), substs }) => { + if substs.non_erasable_generics().next().is_some() { + let symbol = ExportedSymbol::Generic(def.did, substs); + symbols.push(( + symbol, + SymbolExportInfo { + level: SymbolExportLevel::Rust, + kind: SymbolExportKind::Text, + used: false, + }, + )); + } + } + MonoItem::Fn(Instance { def: InstanceDef::DropGlue(_, Some(ty)), substs }) => { + // A little sanity-check + debug_assert_eq!( + substs.non_erasable_generics().next(), + Some(GenericArgKind::Type(ty)) + ); + symbols.push(( + ExportedSymbol::DropGlue(ty), + SymbolExportInfo { + level: SymbolExportLevel::Rust, + kind: SymbolExportKind::Text, + used: false, + }, + )); + } + _ => { + // Any other symbols don't qualify for sharing + } + } + } + } + + // Sort so we get a stable incr. comp. hash. + symbols.sort_by_cached_key(|s| s.0.symbol_name_for_local_instance(tcx)); + + tcx.arena.alloc_from_iter(symbols) +} + +fn upstream_monomorphizations_provider( + tcx: TyCtxt<'_>, + (): (), +) -> DefIdMap<FxHashMap<SubstsRef<'_>, CrateNum>> { + let cnums = tcx.crates(()); + + let mut instances: DefIdMap<FxHashMap<_, _>> = Default::default(); + + let drop_in_place_fn_def_id = tcx.lang_items().drop_in_place_fn(); + + for &cnum in cnums.iter() { + for (exported_symbol, _) in tcx.exported_symbols(cnum).iter() { + let (def_id, substs) = match *exported_symbol { + ExportedSymbol::Generic(def_id, substs) => (def_id, substs), + ExportedSymbol::DropGlue(ty) => { + if let Some(drop_in_place_fn_def_id) = drop_in_place_fn_def_id { + (drop_in_place_fn_def_id, tcx.intern_substs(&[ty.into()])) + } else { + // `drop_in_place` in place does not exist, don't try + // to use it. + continue; + } + } + ExportedSymbol::NonGeneric(..) | ExportedSymbol::NoDefId(..) => { + // These are no monomorphizations + continue; + } + }; + + let substs_map = instances.entry(def_id).or_default(); + + match substs_map.entry(substs) { + Occupied(mut e) => { + // If there are multiple monomorphizations available, + // we select one deterministically. + let other_cnum = *e.get(); + if tcx.stable_crate_id(other_cnum) > tcx.stable_crate_id(cnum) { + e.insert(cnum); + } + } + Vacant(e) => { + e.insert(cnum); + } + } + } + } + + instances +} + +fn upstream_monomorphizations_for_provider( + tcx: TyCtxt<'_>, + def_id: DefId, +) -> Option<&FxHashMap<SubstsRef<'_>, CrateNum>> { + debug_assert!(!def_id.is_local()); + tcx.upstream_monomorphizations(()).get(&def_id) +} + +fn upstream_drop_glue_for_provider<'tcx>( + tcx: TyCtxt<'tcx>, + substs: SubstsRef<'tcx>, +) -> Option<CrateNum> { + if let Some(def_id) = tcx.lang_items().drop_in_place_fn() { + tcx.upstream_monomorphizations_for(def_id).and_then(|monos| monos.get(&substs).cloned()) + } else { + None + } +} + +fn is_unreachable_local_definition_provider(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + !tcx.reachable_set(()).contains(&def_id) +} + +pub fn provide(providers: &mut Providers) { + providers.reachable_non_generics = reachable_non_generics_provider; + providers.is_reachable_non_generic = is_reachable_non_generic_provider_local; + providers.exported_symbols = exported_symbols_provider_local; + providers.upstream_monomorphizations = upstream_monomorphizations_provider; + providers.is_unreachable_local_definition = is_unreachable_local_definition_provider; + providers.upstream_drop_glue_for = upstream_drop_glue_for_provider; + providers.wasm_import_module_map = wasm_import_module_map; +} + +pub fn provide_extern(providers: &mut ExternProviders) { + providers.is_reachable_non_generic = is_reachable_non_generic_provider_extern; + providers.upstream_monomorphizations_for = upstream_monomorphizations_for_provider; +} + +fn symbol_export_level(tcx: TyCtxt<'_>, sym_def_id: DefId) -> SymbolExportLevel { + // We export anything that's not mangled at the "C" layer as it probably has + // to do with ABI concerns. We do not, however, apply such treatment to + // special symbols in the standard library for various plumbing between + // core/std/allocators/etc. For example symbols used to hook up allocation + // are not considered for export + let codegen_fn_attrs = tcx.codegen_fn_attrs(sym_def_id); + let is_extern = codegen_fn_attrs.contains_extern_indicator(); + let std_internal = + codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); + + if is_extern && !std_internal { + let target = &tcx.sess.target.llvm_target; + // WebAssembly cannot export data symbols, so reduce their export level + if target.contains("emscripten") { + if let Some(Node::Item(&hir::Item { kind: hir::ItemKind::Static(..), .. })) = + tcx.hir().get_if_local(sym_def_id) + { + return SymbolExportLevel::Rust; + } + } + + SymbolExportLevel::C + } else { + SymbolExportLevel::Rust + } +} + +/// This is the symbol name of the given instance instantiated in a specific crate. +pub fn symbol_name_for_instance_in_crate<'tcx>( + tcx: TyCtxt<'tcx>, + symbol: ExportedSymbol<'tcx>, + instantiating_crate: CrateNum, +) -> String { + // If this is something instantiated in the local crate then we might + // already have cached the name as a query result. + if instantiating_crate == LOCAL_CRATE { + return symbol.symbol_name_for_local_instance(tcx).to_string(); + } + + // This is something instantiated in an upstream crate, so we have to use + // the slower (because uncached) version of computing the symbol name. + match symbol { + ExportedSymbol::NonGeneric(def_id) => { + rustc_symbol_mangling::symbol_name_for_instance_in_crate( + tcx, + Instance::mono(tcx, def_id), + instantiating_crate, + ) + } + ExportedSymbol::Generic(def_id, substs) => { + rustc_symbol_mangling::symbol_name_for_instance_in_crate( + tcx, + Instance::new(def_id, substs), + instantiating_crate, + ) + } + ExportedSymbol::DropGlue(ty) => rustc_symbol_mangling::symbol_name_for_instance_in_crate( + tcx, + Instance::resolve_drop_in_place(tcx, ty), + instantiating_crate, + ), + ExportedSymbol::NoDefId(symbol_name) => symbol_name.to_string(), + } +} + +/// This is the symbol name of the given instance as seen by the linker. +/// +/// On 32-bit Windows symbols are decorated according to their calling conventions. +pub fn linking_symbol_name_for_instance_in_crate<'tcx>( + tcx: TyCtxt<'tcx>, + symbol: ExportedSymbol<'tcx>, + instantiating_crate: CrateNum, +) -> String { + use rustc_target::abi::call::Conv; + + let mut undecorated = symbol_name_for_instance_in_crate(tcx, symbol, instantiating_crate); + + let target = &tcx.sess.target; + if !target.is_like_windows { + // Mach-O has a global "_" suffix and `object` crate will handle it. + // ELF does not have any symbol decorations. + return undecorated; + } + + let x86 = match &target.arch[..] { + "x86" => true, + "x86_64" => false, + // Only x86/64 use symbol decorations. + _ => return undecorated, + }; + + let instance = match symbol { + ExportedSymbol::NonGeneric(def_id) | ExportedSymbol::Generic(def_id, _) + if tcx.is_static(def_id) => + { + None + } + ExportedSymbol::NonGeneric(def_id) => Some(Instance::mono(tcx, def_id)), + ExportedSymbol::Generic(def_id, substs) => Some(Instance::new(def_id, substs)), + // DropGlue always use the Rust calling convention and thus follow the target's default + // symbol decoration scheme. + ExportedSymbol::DropGlue(..) => None, + // NoDefId always follow the target's default symbol decoration scheme. + ExportedSymbol::NoDefId(..) => None, + }; + + let (conv, args) = instance + .map(|i| { + tcx.fn_abi_of_instance(ty::ParamEnv::reveal_all().and((i, ty::List::empty()))) + .unwrap_or_else(|_| bug!("fn_abi_of_instance({i:?}) failed")) + }) + .map(|fnabi| (fnabi.conv, &fnabi.args[..])) + .unwrap_or((Conv::Rust, &[])); + + // Decorate symbols with prefices, suffices and total number of bytes of arguments. + // Reference: https://docs.microsoft.com/en-us/cpp/build/reference/decorated-names?view=msvc-170 + let (prefix, suffix) = match conv { + Conv::X86Fastcall => ("@", "@"), + Conv::X86Stdcall => ("_", "@"), + Conv::X86VectorCall => ("", "@@"), + _ => { + if x86 { + undecorated.insert(0, '_'); + } + return undecorated; + } + }; + + let args_in_bytes: u64 = args + .iter() + .map(|abi| abi.layout.size.bytes().next_multiple_of(target.pointer_width as u64 / 8)) + .sum(); + format!("{prefix}{undecorated}{suffix}{args_in_bytes}") +} + +fn wasm_import_module_map(tcx: TyCtxt<'_>, cnum: CrateNum) -> FxHashMap<DefId, String> { + // Build up a map from DefId to a `NativeLib` structure, where + // `NativeLib` internally contains information about + // `#[link(wasm_import_module = "...")]` for example. + let native_libs = tcx.native_libraries(cnum); + + let def_id_to_native_lib = native_libs + .iter() + .filter_map(|lib| lib.foreign_module.map(|id| (id, lib))) + .collect::<FxHashMap<_, _>>(); + + let mut ret = FxHashMap::default(); + for (def_id, lib) in tcx.foreign_modules(cnum).iter() { + let module = def_id_to_native_lib.get(&def_id).and_then(|s| s.wasm_import_module); + let Some(module) = module else { continue }; + ret.extend(lib.foreign_items.iter().map(|id| { + assert_eq!(id.krate, cnum); + (*id, module.to_string()) + })); + } + + ret +} |