diff options
Diffstat (limited to 'compiler/rustc_codegen_cranelift/src/driver/aot.rs')
| -rw-r--r-- | compiler/rustc_codegen_cranelift/src/driver/aot.rs | 450 | 
1 files changed, 450 insertions, 0 deletions
| diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs new file mode 100644 index 00000000000..ff0b994c9a9 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -0,0 +1,450 @@ +//! The AOT driver uses [`cranelift_object`] to write object files suitable for linking into a +//! standalone executable. + +use std::path::PathBuf; + +use rustc_codegen_ssa::back::linker::LinkerInfo; +use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, ModuleKind}; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; +use rustc_middle::middle::cstore::EncodedMetadata; +use rustc_middle::mir::mono::CodegenUnit; +use rustc_session::cgu_reuse_tracker::CguReuse; +use rustc_session::config::{DebugInfo, OutputType}; + +use cranelift_object::{ObjectModule, ObjectProduct}; + +use crate::prelude::*; + +use crate::backend::AddConstructor; + +fn new_module(tcx: TyCtxt<'_>, name: String) -> ObjectModule { + let module = crate::backend::make_module(tcx.sess, name); + assert_eq!(pointer_ty(tcx), module.target_config().pointer_type()); + module +} + +struct ModuleCodegenResult(CompiledModule, Option<(WorkProductId, WorkProduct)>); + +impl<HCX> HashStable<HCX> for ModuleCodegenResult { + fn hash_stable(&self, _: &mut HCX, _: &mut StableHasher) { + // do nothing + } +} + +fn emit_module( + tcx: TyCtxt<'_>, + name: String, + kind: ModuleKind, + module: ObjectModule, + debug: Option<DebugContext<'_>>, + unwind_context: UnwindContext<'_>, + map_product: impl FnOnce(ObjectProduct) -> ObjectProduct, +) -> ModuleCodegenResult { + let mut product = module.finish(); + + if let Some(mut debug) = debug { + debug.emit(&mut product); + } + + unwind_context.emit(&mut product); + + let product = map_product(product); + + let tmp_file = tcx + .output_filenames(LOCAL_CRATE) + .temp_path(OutputType::Object, Some(&name)); + let obj = product.object.write().unwrap(); + if let Err(err) = std::fs::write(&tmp_file, obj) { + tcx.sess + .fatal(&format!("error writing object file: {}", err)); + } + + let work_product = if std::env::var("CG_CLIF_INCR_CACHE_DISABLED").is_ok() { + None + } else { + rustc_incremental::copy_cgu_workproduct_to_incr_comp_cache_dir( + tcx.sess, + &name, + &Some(tmp_file.clone()), + ) + }; + + ModuleCodegenResult( + CompiledModule { + name, + kind, + object: Some(tmp_file), + bytecode: None, + }, + work_product, + ) +} + +fn reuse_workproduct_for_cgu( + tcx: TyCtxt<'_>, + cgu: &CodegenUnit<'_>, + work_products: &mut FxHashMap<WorkProductId, WorkProduct>, +) -> CompiledModule { + let incr_comp_session_dir = tcx.sess.incr_comp_session_dir(); + let mut object = None; + let work_product = cgu.work_product(tcx); + if let Some(saved_file) = &work_product.saved_file { + let obj_out = tcx + .output_filenames(LOCAL_CRATE) + .temp_path(OutputType::Object, Some(&cgu.name().as_str())); + object = Some(obj_out.clone()); + let source_file = rustc_incremental::in_incr_comp_dir(&incr_comp_session_dir, &saved_file); + if let Err(err) = rustc_fs_util::link_or_copy(&source_file, &obj_out) { + tcx.sess.err(&format!( + "unable to copy {} to {}: {}", + source_file.display(), + obj_out.display(), + err + )); + } + } + + work_products.insert(cgu.work_product_id(), work_product); + + CompiledModule { + name: cgu.name().to_string(), + kind: ModuleKind::Regular, + object, + bytecode: None, + } +} + +fn module_codegen(tcx: TyCtxt<'_>, cgu_name: rustc_span::Symbol) -> ModuleCodegenResult { + let cgu = tcx.codegen_unit(cgu_name); + let mono_items = cgu.items_in_deterministic_order(tcx); + + let mut module = new_module(tcx, cgu_name.as_str().to_string()); + + // Initialize the global atomic mutex using a constructor for proc-macros. + // FIXME implement atomic instructions in Cranelift. + let mut init_atomics_mutex_from_constructor = None; + if tcx + .sess + .crate_types() + .contains(&rustc_session::config::CrateType::ProcMacro) + { + if mono_items.iter().any(|(mono_item, _)| match mono_item { + rustc_middle::mir::mono::MonoItem::Static(def_id) => tcx + .symbol_name(Instance::mono(tcx, *def_id)) + .name + .contains("__rustc_proc_macro_decls_"), + _ => false, + }) { + init_atomics_mutex_from_constructor = + Some(crate::atomic_shim::init_global_lock_constructor( + &mut module, + &format!("{}_init_atomics_mutex", cgu_name.as_str()), + )); + } + } + + let mut cx = crate::CodegenCx::new(tcx, module, tcx.sess.opts.debuginfo != DebugInfo::None); + super::codegen_mono_items(&mut cx, mono_items); + let (mut module, global_asm, debug, mut unwind_context) = + tcx.sess.time("finalize CodegenCx", || cx.finalize()); + crate::main_shim::maybe_create_entry_wrapper(tcx, &mut module, &mut unwind_context, false); + + let codegen_result = emit_module( + tcx, + cgu.name().as_str().to_string(), + ModuleKind::Regular, + module, + debug, + unwind_context, + |mut product| { + if let Some(func_id) = init_atomics_mutex_from_constructor { + product.add_constructor(func_id); + } + + product + }, + ); + + codegen_global_asm(tcx, &cgu.name().as_str(), &global_asm); + + codegen_result +} + +pub(super) fn run_aot( + tcx: TyCtxt<'_>, + metadata: EncodedMetadata, + need_metadata_module: bool, +) -> Box<(CodegenResults, FxHashMap<WorkProductId, WorkProduct>)> { + let mut work_products = FxHashMap::default(); + + let cgus = if tcx.sess.opts.output_types.should_codegen() { + tcx.collect_and_partition_mono_items(LOCAL_CRATE).1 + } else { + // If only `--emit metadata` is used, we shouldn't perform any codegen. + // Also `tcx.collect_and_partition_mono_items` may panic in that case. + &[] + }; + + if tcx.dep_graph.is_fully_enabled() { + for cgu in &*cgus { + tcx.ensure().codegen_unit(cgu.name()); + } + } + + let modules = super::time(tcx, "codegen mono items", || { + cgus.iter() + .map(|cgu| { + let cgu_reuse = determine_cgu_reuse(tcx, cgu); + tcx.sess + .cgu_reuse_tracker + .set_actual_reuse(&cgu.name().as_str(), cgu_reuse); + + match cgu_reuse { + _ if std::env::var("CG_CLIF_INCR_CACHE_DISABLED").is_ok() => {} + CguReuse::No => {} + CguReuse::PreLto => { + return reuse_workproduct_for_cgu(tcx, &*cgu, &mut work_products); + } + CguReuse::PostLto => unreachable!(), + } + + let dep_node = cgu.codegen_dep_node(tcx); + let (ModuleCodegenResult(module, work_product), _) = tcx.dep_graph.with_task( + dep_node, + tcx, + cgu.name(), + module_codegen, + rustc_middle::dep_graph::hash_result, + ); + + if let Some((id, product)) = work_product { + work_products.insert(id, product); + } + + module + }) + .collect::<Vec<_>>() + }); + + tcx.sess.abort_if_errors(); + + let mut allocator_module = new_module(tcx, "allocator_shim".to_string()); + let mut allocator_unwind_context = UnwindContext::new(tcx, allocator_module.isa()); + let created_alloc_shim = + crate::allocator::codegen(tcx, &mut allocator_module, &mut allocator_unwind_context); + + let allocator_module = if created_alloc_shim { + let ModuleCodegenResult(module, work_product) = emit_module( + tcx, + "allocator_shim".to_string(), + ModuleKind::Allocator, + allocator_module, + None, + allocator_unwind_context, + |product| product, + ); + if let Some((id, product)) = work_product { + work_products.insert(id, product); + } + Some(module) + } else { + None + }; + + rustc_incremental::assert_dep_graph(tcx); + rustc_incremental::save_dep_graph(tcx); + + let metadata_module = if need_metadata_module { + let _timer = tcx.prof.generic_activity("codegen crate metadata"); + let (metadata_cgu_name, tmp_file) = tcx.sess.time("write compressed metadata", || { + use rustc_middle::mir::mono::CodegenUnitNameBuilder; + + let cgu_name_builder = &mut CodegenUnitNameBuilder::new(tcx); + let metadata_cgu_name = cgu_name_builder + .build_cgu_name(LOCAL_CRATE, &["crate"], Some("metadata")) + .as_str() + .to_string(); + + let tmp_file = tcx + .output_filenames(LOCAL_CRATE) + .temp_path(OutputType::Metadata, Some(&metadata_cgu_name)); + + let obj = crate::backend::with_object(tcx.sess, &metadata_cgu_name, |object| { + crate::metadata::write_metadata(tcx, object); + }); + + if let Err(err) = std::fs::write(&tmp_file, obj) { + tcx.sess + .fatal(&format!("error writing metadata object file: {}", err)); + } + + (metadata_cgu_name, tmp_file) + }); + + Some(CompiledModule { + name: metadata_cgu_name, + kind: ModuleKind::Metadata, + object: Some(tmp_file), + bytecode: None, + }) + } else { + None + }; + + if tcx.sess.opts.output_types.should_codegen() { + rustc_incremental::assert_module_sources::assert_module_sources(tcx); + } + + Box::new(( + CodegenResults { + crate_name: tcx.crate_name(LOCAL_CRATE), + modules, + allocator_module, + metadata_module, + metadata, + windows_subsystem: None, // Windows is not yet supported + linker_info: LinkerInfo::new(tcx), + crate_info: CrateInfo::new(tcx), + }, + work_products, + )) +} + +fn codegen_global_asm(tcx: TyCtxt<'_>, cgu_name: &str, global_asm: &str) { + use std::io::Write; + use std::process::{Command, Stdio}; + + if global_asm.is_empty() { + return; + } + + if cfg!(not(feature = "inline_asm")) + || tcx.sess.target.options.is_like_osx + || tcx.sess.target.options.is_like_windows + { + if global_asm.contains("__rust_probestack") { + return; + } + + // FIXME fix linker error on macOS + if cfg!(not(feature = "inline_asm")) { + tcx.sess.fatal( + "asm! and global_asm! support is disabled while compiling rustc_codegen_cranelift", + ); + } else { + tcx.sess + .fatal("asm! and global_asm! are not yet supported on macOS and Windows"); + } + } + + let assembler = crate::toolchain::get_toolchain_binary(tcx.sess, "as"); + let linker = crate::toolchain::get_toolchain_binary(tcx.sess, "ld"); + + // Remove all LLVM style comments + let global_asm = global_asm + .lines() + .map(|line| { + if let Some(index) = line.find("//") { + &line[0..index] + } else { + line + } + }) + .collect::<Vec<_>>() + .join("\n"); + + let output_object_file = tcx + .output_filenames(LOCAL_CRATE) + .temp_path(OutputType::Object, Some(cgu_name)); + + // Assemble `global_asm` + let global_asm_object_file = add_file_stem_postfix(output_object_file.clone(), ".asm"); + let mut child = Command::new(assembler) + .arg("-o") + .arg(&global_asm_object_file) + .stdin(Stdio::piped()) + .spawn() + .expect("Failed to spawn `as`."); + child + .stdin + .take() + .unwrap() + .write_all(global_asm.as_bytes()) + .unwrap(); + let status = child.wait().expect("Failed to wait for `as`."); + if !status.success() { + tcx.sess + .fatal(&format!("Failed to assemble `{}`", global_asm)); + } + + // Link the global asm and main object file together + let main_object_file = add_file_stem_postfix(output_object_file.clone(), ".main"); + std::fs::rename(&output_object_file, &main_object_file).unwrap(); + let status = Command::new(linker) + .arg("-r") // Create a new object file + .arg("-o") + .arg(output_object_file) + .arg(&main_object_file) + .arg(&global_asm_object_file) + .status() + .unwrap(); + if !status.success() { + tcx.sess.fatal(&format!( + "Failed to link `{}` and `{}` together", + main_object_file.display(), + global_asm_object_file.display(), + )); + } + + std::fs::remove_file(global_asm_object_file).unwrap(); + std::fs::remove_file(main_object_file).unwrap(); +} + +fn add_file_stem_postfix(mut path: PathBuf, postfix: &str) -> PathBuf { + let mut new_filename = path.file_stem().unwrap().to_owned(); + new_filename.push(postfix); + if let Some(extension) = path.extension() { + new_filename.push("."); + new_filename.push(extension); + } + path.set_file_name(new_filename); + path +} + +// Adapted from https://github.com/rust-lang/rust/blob/303d8aff6092709edd4dbd35b1c88e9aa40bf6d8/src/librustc_codegen_ssa/base.rs#L922-L953 +fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse { + if !tcx.dep_graph.is_fully_enabled() { + return CguReuse::No; + } + + let work_product_id = &cgu.work_product_id(); + if tcx + .dep_graph + .previous_work_product(work_product_id) + .is_none() + { + // We don't have anything cached for this CGU. This can happen + // if the CGU did not exist in the previous session. + return CguReuse::No; + } + + // Try to mark the CGU as green. If it we can do so, it means that nothing + // affecting the LLVM module has changed and we can re-use a cached version. + // If we compile with any kind of LTO, this means we can re-use the bitcode + // of the Pre-LTO stage (possibly also the Post-LTO version but we'll only + // know that later). If we are not doing LTO, there is only one optimized + // version of each module, so we re-use that. + let dep_node = cgu.codegen_dep_node(tcx); + assert!( + !tcx.dep_graph.dep_node_exists(&dep_node), + "CompileCodegenUnit dep-node for CGU `{}` already exists before marking.", + cgu.name() + ); + + if tcx.dep_graph.try_mark_green(tcx, &dep_node).is_some() { + CguReuse::PreLto + } else { + CguReuse::No + } +} | 
