| use std::ffi::{CStr, CString}; | |
| use std::io::{self, Write}; | |
| use std::path::{Path, PathBuf}; | |
| use std::sync::Arc; | |
| use std::{fs, slice, str}; | |
| use libc::{c_char, c_int, c_void, size_t}; | |
| use rustc_codegen_ssa::back::link::ensure_removed; | |
| use rustc_codegen_ssa::back::versioned_llvm_target; | |
| use rustc_codegen_ssa::back::write::{ | |
| BitcodeSection, CodegenContext, EmitObj, InlineAsmError, ModuleConfig, SharedEmitter, | |
| TargetMachineFactoryConfig, TargetMachineFactoryFn, | |
| }; | |
| use rustc_codegen_ssa::base::wants_wasm_eh; | |
| use rustc_codegen_ssa::common::TypeKind; | |
| use rustc_codegen_ssa::traits::*; | |
| use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind}; | |
| use rustc_data_structures::profiling::SelfProfilerRef; | |
| use rustc_data_structures::small_c_str::SmallCStr; | |
| use rustc_errors::{DiagCtxt, DiagCtxtHandle, Level}; | |
| use rustc_fs_util::{link_or_copy, path_to_c_string}; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_session::Session; | |
| use rustc_session::config::{self, Lto, OutputType, Passes, SplitDwarfKind, SwitchWithOptPath}; | |
| use rustc_span::{BytePos, InnerSpan, Pos, RemapPathScopeComponents, SpanData, SyntaxContext}; | |
| use rustc_target::spec::{CodeModel, FloatAbi, RelocModel, SanitizerSet, SplitDebuginfo, TlsModel}; | |
| use tracing::{debug, trace}; | |
| use crate::back::lto::{Buffer, ModuleBuffer}; | |
| use crate::back::owned_target_machine::OwnedTargetMachine; | |
| use crate::back::profiling::{ | |
| LlvmSelfProfiler, selfprofile_after_pass_callback, selfprofile_before_pass_callback, | |
| }; | |
| use crate::builder::SBuilder; | |
| use crate::builder::gpu_offload::scalar_width; | |
| use crate::common::AsCCharPtr; | |
| use crate::diagnostics::{ | |
| CopyBitcode, FromLlvmDiag, FromLlvmOptimizationDiag, LlvmError, ParseTargetMachineConfig, | |
| UnsupportedCompression, WithLlvmError, WriteBytecode, | |
| }; | |
| use crate::llvm::diagnostic::OptimizationDiagnosticKind::*; | |
| use crate::llvm::{self, DiagnosticInfo}; | |
| use crate::type_::llvm_type_ptr; | |
| use crate::{LlvmCodegenBackend, ModuleLlvm, SimpleCx, attributes, base, common, llvm_util}; | |
| pub(crate) fn llvm_err<'a>(dcx: DiagCtxtHandle<'_>, err: LlvmError<'a>) -> ! { | |
| match llvm::last_error() { | |
| Some(llvm_err) => dcx.emit_fatal(WithLlvmError(err, llvm_err)), | |
| None => dcx.emit_fatal(err), | |
| } | |
| } | |
| fn write_output_file<'ll>( | |
| dcx: DiagCtxtHandle<'_>, | |
| target: &'ll llvm::TargetMachine, | |
| no_builtins: bool, | |
| m: &'ll llvm::Module, | |
| output: &Path, | |
| dwo_output: Option<&Path>, | |
| file_type: llvm::FileType, | |
| self_profiler_ref: &SelfProfilerRef, | |
| verify_llvm_ir: bool, | |
| ) { | |
| debug!("write_output_file output={:?} dwo_output={:?}", output, dwo_output); | |
| let output_c = path_to_c_string(output); | |
| let dwo_output_c; | |
| let dwo_output_ptr = if let Some(dwo_output) = dwo_output { | |
| dwo_output_c = path_to_c_string(dwo_output); | |
| dwo_output_c.as_ptr() | |
| } else { | |
| std::ptr::null() | |
| }; | |
| let result = unsafe { | |
| let pm = llvm::LLVMCreatePassManager(); | |
| llvm::LLVMAddAnalysisPasses(target, pm); | |
| llvm::LLVMRustAddLibraryInfo(target, pm, m, no_builtins); | |
| llvm::LLVMRustWriteOutputFile( | |
| target, | |
| pm, | |
| m, | |
| output_c.as_ptr(), | |
| dwo_output_ptr, | |
| file_type, | |
| verify_llvm_ir, | |
| ) | |
| }; | |
| // Record artifact sizes for self-profiling | |
| if result == llvm::LLVMRustResult::Success { | |
| let artifact_kind = match file_type { | |
| llvm::FileType::ObjectFile => "object_file", | |
| llvm::FileType::AssemblyFile => "assembly_file", | |
| }; | |
| record_artifact_size(self_profiler_ref, artifact_kind, output); | |
| if let Some(dwo_file) = dwo_output { | |
| record_artifact_size(self_profiler_ref, "dwo_file", dwo_file); | |
| } | |
| } | |
| result.into_result().unwrap_or_else(|()| llvm_err(dcx, LlvmError::WriteOutput { path: output })) | |
| } | |
| /// If `for_cfg` is `true` then we are creating this machine for the purpose of populating | |
| /// [`rustc_codegen_ssa::TargetConfig`] based on what LLVM actually enables in this configuration. | |
| /// `-Ctarget-feature` should be ignored in that case since it is already processed separately. | |
| pub(crate) fn create_informational_target_machine( | |
| sess: &Session, | |
| for_cfg: bool, | |
| ) -> OwnedTargetMachine { | |
| let config = TargetMachineFactoryConfig { split_dwarf_file: None, output_obj_file: None }; | |
| // Can't use query system here quite yet because this function is invoked before the query | |
| // system/tcx is set up. | |
| let features = llvm_util::global_llvm_features(sess, for_cfg); | |
| target_machine_factory(sess, config::OptLevel::No, &features)(sess.dcx(), config) | |
| } | |
| pub(crate) fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> OwnedTargetMachine { | |
| let split_dwarf_file = if tcx.sess.target_can_use_split_dwarf() { | |
| tcx.output_filenames(()).split_dwarf_path( | |
| tcx.sess.split_debuginfo(), | |
| tcx.sess.opts.unstable_opts.split_dwarf_kind, | |
| mod_name, | |
| ) | |
| } else { | |
| None | |
| }; | |
| let output_obj_file = | |
| Some(tcx.output_filenames(()).temp_path_for_cgu(OutputType::Object, mod_name)); | |
| let config = TargetMachineFactoryConfig { split_dwarf_file, output_obj_file }; | |
| target_machine_factory( | |
| tcx.sess, | |
| tcx.backend_optimization_level(()), | |
| tcx.global_backend_features(()), | |
| )(tcx.dcx(), config) | |
| } | |
| fn to_llvm_opt_settings(cfg: config::OptLevel) -> (llvm::CodeGenOptLevel, llvm::CodeGenOptSize) { | |
| use self::config::OptLevel::*; | |
| match cfg { | |
| No => (llvm::CodeGenOptLevel::None, llvm::CodeGenOptSizeNone), | |
| Less => (llvm::CodeGenOptLevel::Less, llvm::CodeGenOptSizeNone), | |
| More => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeNone), | |
| Aggressive => (llvm::CodeGenOptLevel::Aggressive, llvm::CodeGenOptSizeNone), | |
| Size => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeDefault), | |
| SizeMin => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeAggressive), | |
| } | |
| } | |
| fn to_pass_builder_opt_level(cfg: config::OptLevel) -> llvm::PassBuilderOptLevel { | |
| use config::OptLevel::*; | |
| match cfg { | |
| No => llvm::PassBuilderOptLevel::O0, | |
| Less => llvm::PassBuilderOptLevel::O1, | |
| More => llvm::PassBuilderOptLevel::O2, | |
| Aggressive => llvm::PassBuilderOptLevel::O3, | |
| Size => llvm::PassBuilderOptLevel::Os, | |
| SizeMin => llvm::PassBuilderOptLevel::Oz, | |
| } | |
| } | |
| fn to_llvm_relocation_model(relocation_model: RelocModel) -> llvm::RelocModel { | |
| match relocation_model { | |
| RelocModel::Static => llvm::RelocModel::Static, | |
| // LLVM doesn't have a PIE relocation model, it represents PIE as PIC with an extra | |
| // attribute. | |
| RelocModel::Pic | RelocModel::Pie => llvm::RelocModel::PIC, | |
| RelocModel::DynamicNoPic => llvm::RelocModel::DynamicNoPic, | |
| RelocModel::Ropi => llvm::RelocModel::ROPI, | |
| RelocModel::Rwpi => llvm::RelocModel::RWPI, | |
| RelocModel::RopiRwpi => llvm::RelocModel::ROPI_RWPI, | |
| } | |
| } | |
| pub(crate) fn to_llvm_code_model(code_model: Option<CodeModel>) -> llvm::CodeModel { | |
| match code_model { | |
| Some(CodeModel::Tiny) => llvm::CodeModel::Tiny, | |
| Some(CodeModel::Small) => llvm::CodeModel::Small, | |
| Some(CodeModel::Kernel) => llvm::CodeModel::Kernel, | |
| Some(CodeModel::Medium) => llvm::CodeModel::Medium, | |
| Some(CodeModel::Large) => llvm::CodeModel::Large, | |
| None => llvm::CodeModel::None, | |
| } | |
| } | |
| fn to_llvm_float_abi(float_abi: Option<FloatAbi>) -> llvm::FloatAbi { | |
| match float_abi { | |
| None => llvm::FloatAbi::Default, | |
| Some(FloatAbi::Soft) => llvm::FloatAbi::Soft, | |
| Some(FloatAbi::Hard) => llvm::FloatAbi::Hard, | |
| } | |
| } | |
| pub(crate) fn target_machine_factory( | |
| sess: &Session, | |
| optlvl: config::OptLevel, | |
| target_features: &[String], | |
| ) -> TargetMachineFactoryFn<LlvmCodegenBackend> { | |
| // Self-profile timer for creating a _factory_. | |
| let _prof_timer = sess.prof.generic_activity("target_machine_factory"); | |
| let reloc_model = to_llvm_relocation_model(sess.relocation_model()); | |
| let (opt_level, _) = to_llvm_opt_settings(optlvl); | |
| let float_abi = to_llvm_float_abi(sess.target.llvm_floatabi); | |
| let ffunction_sections = | |
| sess.opts.unstable_opts.function_sections.unwrap_or(sess.target.function_sections); | |
| let fdata_sections = ffunction_sections; | |
| let funique_section_names = !sess.opts.unstable_opts.no_unique_section_names; | |
| let code_model = to_llvm_code_model(sess.code_model()); | |
| // This is used to set cfg_has_threads, so all logic must be in this method. | |
| let singlethread = sess.target.singlethread(&sess.internal_target_features); | |
| let triple = SmallCStr::new(&versioned_llvm_target(sess)); | |
| let cpu = SmallCStr::new(llvm_util::target_cpu(sess)); | |
| let features = CString::new(target_features.join(",")).unwrap(); | |
| let abi = SmallCStr::new(sess.target.llvm_abiname.desc()); | |
| let trap_unreachable = | |
| sess.opts.unstable_opts.trap_unreachable.unwrap_or(sess.target.trap_unreachable); | |
| let emit_stack_size_section = sess.opts.unstable_opts.emit_stack_sizes; | |
| let verbose_asm = sess.opts.unstable_opts.verbose_asm; | |
| let relax_elf_relocations = | |
| sess.opts.unstable_opts.relax_elf_relocations.unwrap_or(sess.target.relax_elf_relocations); | |
| let use_init_array = | |
| !sess.opts.unstable_opts.use_ctors_section.unwrap_or(sess.target.use_ctors_section); | |
| let path_mapping = sess.source_map().path_mapping().clone(); | |
| let working_dir = sess.source_map().working_dir().clone(); | |
| let use_emulated_tls = matches!(sess.tls_model(), TlsModel::Emulated); | |
| let debuginfo_compression = match sess.opts.unstable_opts.debuginfo_compression { | |
| config::DebugInfoCompression::None => llvm::CompressionKind::None, | |
| config::DebugInfoCompression::Zlib => { | |
| if llvm::LLVMRustLLVMHasZlibCompression() { | |
| llvm::CompressionKind::Zlib | |
| } else { | |
| sess.dcx().emit_warn(UnsupportedCompression { algorithm: "zlib" }); | |
| llvm::CompressionKind::None | |
| } | |
| } | |
| config::DebugInfoCompression::Zstd => { | |
| if llvm::LLVMRustLLVMHasZstdCompression() { | |
| llvm::CompressionKind::Zstd | |
| } else { | |
| sess.dcx().emit_warn(UnsupportedCompression { algorithm: "zstd" }); | |
| llvm::CompressionKind::None | |
| } | |
| } | |
| }; | |
| let use_wasm_eh = wants_wasm_eh(sess); | |
| let large_data_threshold = sess.opts.unstable_opts.large_data_threshold.unwrap_or(0); | |
| let prof = SelfProfilerRef::clone(&sess.prof); | |
| Arc::new(move |dcx: DiagCtxtHandle<'_>, config: TargetMachineFactoryConfig| { | |
| // Self-profile timer for invoking a factory to create a target machine. | |
| let _prof_timer = prof.generic_activity("target_machine_factory_inner"); | |
| let path_to_cstring_helper = |path: Option<PathBuf>| -> CString { | |
| let path = path.unwrap_or_default(); | |
| let path = path_mapping | |
| .to_real_filename(&working_dir, path) | |
| .path(RemapPathScopeComponents::DEBUGINFO) | |
| .to_string_lossy() | |
| .into_owned(); | |
| CString::new(path).unwrap() | |
| }; | |
| let split_dwarf_file = path_to_cstring_helper(config.split_dwarf_file); | |
| let output_obj_file = path_to_cstring_helper(config.output_obj_file); | |
| OwnedTargetMachine::new( | |
| &triple, | |
| &cpu, | |
| &features, | |
| &abi, | |
| code_model, | |
| reloc_model, | |
| opt_level, | |
| float_abi, | |
| ffunction_sections, | |
| fdata_sections, | |
| funique_section_names, | |
| trap_unreachable, | |
| singlethread, | |
| verbose_asm, | |
| emit_stack_size_section, | |
| relax_elf_relocations, | |
| use_init_array, | |
| &split_dwarf_file, | |
| &output_obj_file, | |
| debuginfo_compression, | |
| use_emulated_tls, | |
| use_wasm_eh, | |
| large_data_threshold, | |
| ) | |
| .unwrap_or_else(|err| dcx.emit_fatal(ParseTargetMachineConfig(err))) | |
| }) | |
| } | |
| pub(crate) fn save_temp_bitcode( | |
| cgcx: &CodegenContext, | |
| module: &ModuleCodegen<ModuleLlvm>, | |
| name: &str, | |
| ) { | |
| if !cgcx.save_temps { | |
| return; | |
| } | |
| let ext = format!("{name}.bc"); | |
| let path = cgcx.output_filenames.temp_path_ext_for_cgu(&ext, &module.name); | |
| write_bitcode_to_file(&module.module_llvm, &path) | |
| } | |
| fn write_bitcode_to_file(module: &ModuleLlvm, path: &Path) { | |
| unsafe { | |
| let path = path_to_c_string(&path); | |
| let llmod = module.llmod(); | |
| llvm::LLVMWriteBitcodeToFile(llmod, path.as_ptr()); | |
| } | |
| } | |
| /// In what context is a diagnostic handler being attached to a codegen unit? | |
| pub(crate) enum CodegenDiagnosticsStage { | |
| /// Prelink optimization stage. | |
| Opt, | |
| /// LTO/ThinLTO postlink optimization stage. | |
| LTO, | |
| /// Code generation. | |
| Codegen, | |
| } | |
| pub(crate) struct DiagnosticHandlers<'a> { | |
| data: *mut (&'a CodegenContext, &'a SharedEmitter), | |
| llcx: &'a llvm::Context, | |
| old_handler: Option<&'a llvm::DiagnosticHandler>, | |
| } | |
| impl<'a> DiagnosticHandlers<'a> { | |
| pub(crate) fn new( | |
| cgcx: &'a CodegenContext, | |
| shared_emitter: &'a SharedEmitter, | |
| llcx: &'a llvm::Context, | |
| module: &ModuleCodegen<ModuleLlvm>, | |
| stage: CodegenDiagnosticsStage, | |
| ) -> Self { | |
| let remark_passes_all: bool; | |
| let remark_passes: Vec<CString>; | |
| match &cgcx.remark { | |
| Passes::All => { | |
| remark_passes_all = true; | |
| remark_passes = Vec::new(); | |
| } | |
| Passes::Some(passes) => { | |
| remark_passes_all = false; | |
| remark_passes = | |
| passes.iter().map(|name| CString::new(name.as_str()).unwrap()).collect(); | |
| } | |
| }; | |
| let remark_passes: Vec<*const c_char> = | |
| remark_passes.iter().map(|name: &CString| name.as_ptr()).collect(); | |
| let remark_file = cgcx | |
| .remark_dir | |
| .as_ref() | |
| // Use the .opt.yaml file suffix, which is supported by LLVM's opt-viewer. | |
| .map(|dir| { | |
| let stage_suffix = match stage { | |
| CodegenDiagnosticsStage::Codegen => "codegen", | |
| CodegenDiagnosticsStage::Opt => "opt", | |
| CodegenDiagnosticsStage::LTO => "lto", | |
| }; | |
| dir.join(format!("{}.{stage_suffix}.opt.yaml", module.name)) | |
| }) | |
| .and_then(|dir| dir.to_str().and_then(|p| CString::new(p).ok())); | |
| let pgo_available = cgcx.module_config.pgo_use.is_some(); | |
| let data = Box::into_raw(Box::new((cgcx, shared_emitter))); | |
| unsafe { | |
| let old_handler = llvm::LLVMRustContextGetDiagnosticHandler(llcx); | |
| llvm::LLVMRustContextConfigureDiagnosticHandler( | |
| llcx, | |
| diagnostic_handler, | |
| data.cast(), | |
| remark_passes_all, | |
| remark_passes.as_ptr(), | |
| remark_passes.len(), | |
| // The `as_ref()` is important here, otherwise the `CString` will be dropped | |
| // too soon! | |
| remark_file.as_ref().map(|dir| dir.as_ptr()).unwrap_or(std::ptr::null()), | |
| pgo_available, | |
| ); | |
| DiagnosticHandlers { data, llcx, old_handler } | |
| } | |
| } | |
| } | |
| impl<'a> Drop for DiagnosticHandlers<'a> { | |
| fn drop(&mut self) { | |
| unsafe { | |
| llvm::LLVMRustContextSetDiagnosticHandler(self.llcx, self.old_handler); | |
| drop(Box::from_raw(self.data)); | |
| } | |
| } | |
| } | |
| fn report_inline_asm( | |
| cgcx: &CodegenContext, | |
| msg: String, | |
| level: llvm::DiagnosticLevel, | |
| cookie: u64, | |
| source: Option<(String, Vec<InnerSpan>)>, | |
| ) -> InlineAsmError { | |
| // In LTO build we may get srcloc values from other crates which are invalid | |
| // since they use a different source map. To be safe we just suppress these | |
| // in LTO builds. | |
| let span = if cookie == 0 || matches!(cgcx.lto, Lto::Fat | Lto::Thin) { | |
| SpanData::default() | |
| } else { | |
| SpanData { | |
| lo: BytePos::from_u32(cookie as u32), | |
| hi: BytePos::from_u32((cookie >> 32) as u32), | |
| ctxt: SyntaxContext::root(), | |
| parent: None, | |
| } | |
| }; | |
| let level = match level { | |
| llvm::DiagnosticLevel::Error => Level::Error, | |
| llvm::DiagnosticLevel::Warning => Level::Warning, | |
| llvm::DiagnosticLevel::Note | llvm::DiagnosticLevel::Remark => Level::Note, | |
| }; | |
| let msg = msg.trim_prefix("error: ").to_string(); | |
| InlineAsmError { span, msg, level, source } | |
| } | |
| unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void) { | |
| if user.is_null() { | |
| return; | |
| } | |
| let (cgcx, shared_emitter) = unsafe { *(user as *const (&CodegenContext, &SharedEmitter)) }; | |
| let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); | |
| let dcx = dcx.handle(); | |
| match unsafe { llvm::diagnostic::Diagnostic::unpack(info) } { | |
| llvm::diagnostic::InlineAsm(inline) => { | |
| // FIXME use dcx | |
| shared_emitter.inline_asm_error(report_inline_asm( | |
| cgcx, | |
| inline.message, | |
| inline.level, | |
| inline.cookie, | |
| inline.source, | |
| )); | |
| } | |
| llvm::diagnostic::Optimization(opt) => { | |
| dcx.emit_note(FromLlvmOptimizationDiag { | |
| filename: &opt.filename, | |
| line: opt.line, | |
| column: opt.column, | |
| pass_name: &opt.pass_name, | |
| kind: match opt.kind { | |
| OptimizationRemark => "success", | |
| OptimizationMissed | OptimizationFailure => "missed", | |
| OptimizationAnalysis | |
| | OptimizationAnalysisFPCommute | |
| | OptimizationAnalysisAliasing => "analysis", | |
| OptimizationRemarkOther => "other", | |
| }, | |
| message: &opt.message, | |
| }); | |
| } | |
| llvm::diagnostic::PGO(diagnostic_ref) | llvm::diagnostic::Linker(diagnostic_ref) => { | |
| let message = llvm::build_string(|s| unsafe { | |
| llvm::LLVMRustWriteDiagnosticInfoToString(diagnostic_ref, s) | |
| }) | |
| .expect("non-UTF8 diagnostic"); | |
| dcx.emit_warn(FromLlvmDiag { message }); | |
| } | |
| llvm::diagnostic::Unsupported(diagnostic_ref) => { | |
| let message = llvm::build_string(|s| unsafe { | |
| llvm::LLVMRustWriteDiagnosticInfoToString(diagnostic_ref, s) | |
| }) | |
| .expect("non-UTF8 diagnostic"); | |
| dcx.emit_err(FromLlvmDiag { message }); | |
| } | |
| llvm::diagnostic::UnknownDiagnostic(..) => {} | |
| } | |
| } | |
| fn get_pgo_gen_path(config: &ModuleConfig) -> Option<CString> { | |
| match config.pgo_gen { | |
| SwitchWithOptPath::Enabled(ref opt_dir_path) => { | |
| let path = if let Some(dir_path) = opt_dir_path { | |
| dir_path.join("default_%m.profraw") | |
| } else { | |
| PathBuf::from("default_%m.profraw") | |
| }; | |
| Some(CString::new(format!("{}", path.display())).unwrap()) | |
| } | |
| SwitchWithOptPath::Disabled => None, | |
| } | |
| } | |
| fn get_pgo_use_path(config: &ModuleConfig) -> Option<CString> { | |
| config | |
| .pgo_use | |
| .as_ref() | |
| .map(|path_buf| CString::new(path_buf.to_string_lossy().as_bytes()).unwrap()) | |
| } | |
| fn get_pgo_sample_use_path(config: &ModuleConfig) -> Option<CString> { | |
| config | |
| .pgo_sample_use | |
| .as_ref() | |
| .map(|path_buf| CString::new(path_buf.to_string_lossy().as_bytes()).unwrap()) | |
| } | |
| fn get_instr_profile_output_path(config: &ModuleConfig) -> Option<CString> { | |
| config.instrument_coverage.then(|| c"default_%m_%p.profraw".to_owned()) | |
| } | |
| // PreAD will run llvm opts but disable size increasing opts (vectorization, loop unrolling) | |
| // DuringAD is the same as above, but also runs the enzyme opt and autodiff passes. | |
| // PostAD will run all opts, including size increasing opts. | |
| #[derive(Debug, Eq, PartialEq)] | |
| pub(crate) enum AutodiffStage { | |
| PreAD, | |
| DuringAD, | |
| PostAD, | |
| } | |
| pub(crate) unsafe fn llvm_optimize( | |
| cgcx: &CodegenContext, | |
| prof: &SelfProfilerRef, | |
| dcx: DiagCtxtHandle<'_>, | |
| module: &ModuleCodegen<ModuleLlvm>, | |
| thin_lto_buffer: Option<&mut Option<Buffer>>, | |
| thin_lto_summary_buffer: Option<&mut Option<Buffer>>, | |
| config: &ModuleConfig, | |
| opt_level: config::OptLevel, | |
| opt_stage: llvm::OptStage, | |
| autodiff_stage: AutodiffStage, | |
| ) { | |
| // Enzyme: | |
| // The whole point of compiler based AD is to differentiate optimized IR instead of unoptimized | |
| // source code. However, benchmarks show that optimizations increasing the code size | |
| // tend to reduce AD performance. Therefore deactivate them before AD, then differentiate the code | |
| // and finally re-optimize the module, now with all optimizations available. | |
| // FIXME(ZuseZ4): In a future update we could figure out how to only optimize individual functions getting | |
| // differentiated. | |
| let consider_ad = config.autodiff.contains(&config::AutoDiff::Enable); | |
| let run_enzyme = autodiff_stage == AutodiffStage::DuringAD; | |
| let print_before_enzyme = config.autodiff.contains(&config::AutoDiff::PrintModBefore); | |
| let print_after_enzyme = config.autodiff.contains(&config::AutoDiff::PrintModAfter); | |
| let print_passes = config.autodiff.contains(&config::AutoDiff::PrintPasses); | |
| let passes_after_enzyme = if autodiff_stage == AutodiffStage::PostAD { | |
| config.autodiff_post_passes.as_deref() | |
| } else { | |
| None | |
| }; | |
| let passes_after_enzyme_ptr = | |
| passes_after_enzyme.map_or(std::ptr::null(), |s| s.as_c_char_ptr()); | |
| let passes_after_enzyme_len = passes_after_enzyme.map_or(0, |s| s.len()); | |
| let merge_functions; | |
| let unroll_loops; | |
| let vectorize_slp; | |
| let vectorize_loop; | |
| // When we build rustc with enzyme/autodiff support, we want to postpone size-increasing | |
| // optimizations until after differentiation. Our pipeline is thus: (opt + enzyme), (full opt). | |
| // We therefore have two calls to llvm_optimize, if autodiff is used. | |
| // | |
| // We also must disable merge_functions, since autodiff placeholder/dummy bodies tend to be | |
| // identical. We run opts before AD, so there is a chance that LLVM will merge our dummies. | |
| // In that case, we lack some dummy bodies and can't replace them with the real AD code anymore. | |
| // We then would need to abort compilation. This was especially common in test cases. | |
| if consider_ad && autodiff_stage != AutodiffStage::PostAD { | |
| merge_functions = false; | |
| unroll_loops = false; | |
| vectorize_slp = false; | |
| vectorize_loop = false; | |
| } else { | |
| unroll_loops = | |
| opt_level != config::OptLevel::Size && opt_level != config::OptLevel::SizeMin; | |
| merge_functions = config.merge_functions; | |
| vectorize_slp = config.vectorize_slp; | |
| vectorize_loop = config.vectorize_loop; | |
| } | |
| trace!(?unroll_loops, ?vectorize_slp, ?vectorize_loop, ?run_enzyme); | |
| if thin_lto_buffer.is_some() { | |
| assert!( | |
| matches!( | |
| opt_stage, | |
| llvm::OptStage::PreLinkNoLTO | |
| | llvm::OptStage::PreLinkFatLTO | |
| | llvm::OptStage::PreLinkThinLTO | |
| ), | |
| "the bitcode for LTO can only be obtained at the pre-link stage" | |
| ); | |
| } | |
| let pgo_gen_path = get_pgo_gen_path(config); | |
| let pgo_use_path = get_pgo_use_path(config); | |
| let pgo_sample_use_path = get_pgo_sample_use_path(config); | |
| let is_lto = opt_stage == llvm::OptStage::ThinLTO || opt_stage == llvm::OptStage::FatLTO; | |
| let is_final_stage = | |
| !matches!(opt_stage, llvm::OptStage::PreLinkFatLTO | llvm::OptStage::PreLinkThinLTO); | |
| let instr_profile_output_path = get_instr_profile_output_path(config); | |
| let sanitize_dataflow_abilist: Vec<_> = config | |
| .sanitizer_dataflow_abilist | |
| .iter() | |
| .map(|file| CString::new(file.as_str()).unwrap()) | |
| .collect(); | |
| let sanitize_dataflow_abilist_ptrs: Vec<_> = | |
| sanitize_dataflow_abilist.iter().map(|file| file.as_ptr()).collect(); | |
| // Sanitizer instrumentation is only inserted during the pre-link optimization stage. | |
| let sanitizer_options = if !is_lto { | |
| Some(llvm::SanitizerOptions { | |
| sanitize_address: config.sanitizer.contains(SanitizerSet::ADDRESS), | |
| sanitize_address_recover: config.sanitizer_recover.contains(SanitizerSet::ADDRESS), | |
| sanitize_cfi: config.sanitizer.contains(SanitizerSet::CFI), | |
| sanitize_dataflow: config.sanitizer.contains(SanitizerSet::DATAFLOW), | |
| sanitize_dataflow_abilist: sanitize_dataflow_abilist_ptrs.as_ptr(), | |
| sanitize_dataflow_abilist_len: sanitize_dataflow_abilist_ptrs.len(), | |
| sanitize_kcfi: config.sanitizer.contains(SanitizerSet::KCFI), | |
| sanitize_memory: config.sanitizer.contains(SanitizerSet::MEMORY), | |
| sanitize_memory_recover: config.sanitizer_recover.contains(SanitizerSet::MEMORY), | |
| sanitize_memory_track_origins: config.sanitizer_memory_track_origins as c_int, | |
| sanitize_realtime: config.sanitizer.contains(SanitizerSet::REALTIME), | |
| sanitize_thread: config.sanitizer.contains(SanitizerSet::THREAD), | |
| sanitize_hwaddress: config.sanitizer.contains(SanitizerSet::HWADDRESS), | |
| sanitize_hwaddress_recover: config.sanitizer_recover.contains(SanitizerSet::HWADDRESS), | |
| sanitize_kernel_address: config.sanitizer.contains(SanitizerSet::KERNELADDRESS), | |
| sanitize_kernel_address_recover: config | |
| .sanitizer_recover | |
| .contains(SanitizerSet::KERNELADDRESS), | |
| sanitize_kernel_hwaddress: config.sanitizer.contains(SanitizerSet::KERNELHWADDRESS), | |
| sanitize_kernel_hwaddress_recover: config | |
| .sanitizer_recover | |
| .contains(SanitizerSet::KERNELHWADDRESS), | |
| }) | |
| } else { | |
| None | |
| }; | |
| fn handle_offload<'ll>(cx: &'ll SimpleCx<'_>, old_fn: &llvm::Value) { | |
| let old_fn_ty = cx.get_type_of_global(old_fn); | |
| let old_param_types = cx.func_params_types(old_fn_ty); | |
| let old_param_count = old_param_types.len(); | |
| if old_param_count == 0 { | |
| return; | |
| } | |
| let first_param = llvm::get_param(old_fn, 0); | |
| let c_name = llvm::get_value_name(first_param); | |
| let first_arg_name = str::from_utf8(&c_name).unwrap(); | |
| // We might call llvm_optimize (and thus this code) multiple times on the same IR, | |
| // but we shouldn't add this helper ptr multiple times. | |
| // FIXME(offload): This could break if the user calls his first argument `dyn_ptr`. | |
| if first_arg_name == "dyn_ptr" { | |
| return; | |
| } | |
| // Create the new parameter list, with ptr as the first argument | |
| let mut new_param_types = Vec::with_capacity(old_param_count as usize + 1); | |
| new_param_types.push(cx.type_ptr()); | |
| // This relies on undocumented LLVM knowledge that scalars must be passed as i64 | |
| for &old_ty in &old_param_types { | |
| let new_ty = match cx.type_kind(old_ty) { | |
| TypeKind::Half | TypeKind::Float | TypeKind::Double | TypeKind::Integer => { | |
| cx.type_i64() | |
| } | |
| _ => old_ty, | |
| }; | |
| new_param_types.push(new_ty); | |
| } | |
| // Create the new function type | |
| let ret_ty = unsafe { llvm::LLVMGetReturnType(old_fn_ty) }; | |
| let new_fn_ty = cx.type_func(&new_param_types, ret_ty); | |
| // Create the new function, with a temporary .offload name to avoid a name collision. | |
| let old_fn_name = String::from_utf8(llvm::get_value_name(old_fn)).unwrap(); | |
| let new_fn_name = format!("{}.offload", &old_fn_name); | |
| let new_fn = cx.add_func(&new_fn_name, new_fn_ty); | |
| let a0 = llvm::get_param(new_fn, 0); | |
| llvm::set_value_name(a0, CString::new("dyn_ptr").unwrap().as_bytes()); | |
| let bb = SBuilder::append_block(cx, new_fn, "entry"); | |
| let mut builder = SBuilder::build(cx, bb); | |
| let mut old_args_rebuilt = Vec::with_capacity(old_param_types.len()); | |
| for (i, &old_ty) in old_param_types.iter().enumerate() { | |
| let new_arg = llvm::get_param(new_fn, (i + 1) as u32); | |
| let rebuilt = match cx.type_kind(old_ty) { | |
| TypeKind::Half | TypeKind::Float | TypeKind::Double | TypeKind::Integer => { | |
| let num_bits = scalar_width(cx, old_ty); | |
| let trunc = builder.trunc(new_arg, cx.type_ix(num_bits)); | |
| builder.bitcast(trunc, old_ty) | |
| } | |
| _ => new_arg, | |
| }; | |
| old_args_rebuilt.push(rebuilt); | |
| } | |
| builder.ret_void(); | |
| // Here we map the old arguments to the new arguments, with an offset of 1 to make sure | |
| // that we don't use the newly added `%dyn_ptr`. | |
| unsafe { | |
| llvm::RustOffloadWrapper::get_instance().llvm_rust_offload_wrapper( | |
| old_fn, | |
| new_fn, | |
| old_args_rebuilt.as_slice(), | |
| ); | |
| } | |
| llvm::set_linkage(new_fn, llvm::get_linkage(old_fn)); | |
| llvm::set_visibility(new_fn, llvm::get_visibility(old_fn)); | |
| // Replace all uses of old_fn with new_fn (RAUW) | |
| unsafe { | |
| llvm::LLVMReplaceAllUsesWith(old_fn, new_fn); | |
| } | |
| let name = llvm::get_value_name(old_fn); | |
| unsafe { | |
| llvm::LLVMDeleteFunction(old_fn); | |
| } | |
| // Now we can re-use the old name, without name collision. | |
| llvm::set_value_name(new_fn, &name); | |
| } | |
| if cgcx.target_is_like_gpu | |
| && config.offload.iter().any(|o| matches!(o, config::Offload::Device(_))) | |
| { | |
| let cx = | |
| SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); | |
| for func in cx.get_functions() { | |
| let offload_kernel = "offload-kernel"; | |
| if attributes::has_string_attr(func, offload_kernel) { | |
| handle_offload(&cx, func); | |
| } | |
| attributes::remove_string_attr_from_llfn(func, offload_kernel); | |
| } | |
| } | |
| let mut llvm_profiler = prof | |
| .llvm_recording_enabled() | |
| .then(|| LlvmSelfProfiler::new(prof.get_self_profiler().unwrap())); | |
| let llvm_selfprofiler = | |
| llvm_profiler.as_mut().map(|s| s as *mut _ as *mut c_void).unwrap_or(std::ptr::null_mut()); | |
| let extra_passes = if !is_lto { config.passes.join(",") } else { "".to_string() }; | |
| let llvm_plugins = config.llvm_plugins.join(","); | |
| let enzyme_fn = if consider_ad { | |
| let wrapper = llvm::EnzymeWrapper::get_instance(); | |
| wrapper.registerEnzymeAndPassPipeline | |
| } else { | |
| std::ptr::null() | |
| }; | |
| let result = unsafe { | |
| llvm::LLVMRustOptimize( | |
| module.module_llvm.llmod(), | |
| &*module.module_llvm.tm.raw(), | |
| to_pass_builder_opt_level(opt_level), | |
| opt_stage, | |
| cgcx.use_linker_plugin_lto, | |
| config.no_prepopulate_passes, | |
| config.verify_llvm_ir, | |
| config.lint_llvm_ir, | |
| thin_lto_buffer, | |
| thin_lto_summary_buffer, | |
| merge_functions, | |
| unroll_loops, | |
| vectorize_slp, | |
| vectorize_loop, | |
| config.no_builtins, | |
| config.emit_lifetime_markers, | |
| enzyme_fn, | |
| print_before_enzyme, | |
| print_after_enzyme, | |
| print_passes, | |
| sanitizer_options.as_ref(), | |
| pgo_gen_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), | |
| pgo_use_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), | |
| config.instrument_coverage, | |
| instr_profile_output_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), | |
| pgo_sample_use_path.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), | |
| config.debug_info_for_profiling, | |
| llvm_selfprofiler, | |
| selfprofile_before_pass_callback, | |
| selfprofile_after_pass_callback, | |
| passes_after_enzyme_ptr, | |
| passes_after_enzyme_len, | |
| extra_passes.as_c_char_ptr(), | |
| extra_passes.len(), | |
| llvm_plugins.as_c_char_ptr(), | |
| llvm_plugins.len(), | |
| ) | |
| }; | |
| if cgcx.target_is_like_gpu | |
| && config.offload.iter().any(|o| matches!(o, config::Offload::Device(_))) | |
| { | |
| let device_path = cgcx.output_filenames.path(OutputType::Object); | |
| let device_dir = device_path.parent().unwrap(); | |
| let device_out = device_dir.join("device.bin"); | |
| let device_out_c = path_to_c_string(device_out.as_path()); | |
| // 1) Bundle device module into offload image device.bin (device TM) | |
| let ok = unsafe { | |
| llvm::RustOffloadWrapper::get_instance().llvm_rust_bundle_images( | |
| module.module_llvm.llmod(), | |
| module.module_llvm.tm.raw(), | |
| device_out_c.as_c_str(), | |
| ) | |
| }; | |
| if !ok || !device_out.exists() { | |
| dcx.emit_err(crate::diagnostics::OffloadBundleImagesFailed); | |
| } | |
| } | |
| // This assumes that we previously compiled our kernels for a gpu target, which created a | |
| // `device.bin` artifact. The user is supposed to provide us with a path to this artifact, we | |
| // don't need any other artifacts from the previous run. We will embed this artifact into our | |
| // LLVM-IR host module, to create a `host.o` ObjectFile, which we will write to disk. | |
| // The last, not yet automated steps uses the `clang-linker-wrapper` to process `host.o`. | |
| if !cgcx.target_is_like_gpu && is_final_stage { | |
| if let Some(device_path) = config | |
| .offload | |
| .iter() | |
| .find_map(|o| if let config::Offload::Host(path) = o { Some(path) } else { None }) | |
| { | |
| let device_pathbuf = PathBuf::from(device_path); | |
| if device_pathbuf.is_relative() { | |
| dcx.emit_err(crate::diagnostics::OffloadWithoutAbsPath); | |
| } else if device_pathbuf | |
| .file_name() | |
| .and_then(|n| n.to_str()) | |
| .is_some_and(|n| n != "device.bin") | |
| { | |
| dcx.emit_err(crate::diagnostics::OffloadWrongFileName); | |
| } else if !device_pathbuf.exists() { | |
| dcx.emit_err(crate::diagnostics::OffloadNonexistingPath); | |
| } | |
| let host_path = cgcx.output_filenames.path(OutputType::Object); | |
| let host_dir = host_path.parent().unwrap(); | |
| let out_obj = host_dir.join("host.o"); | |
| let device_bin_c = path_to_c_string(device_pathbuf.as_path()); | |
| // 2) Finalize host: lib.bc + device.bin -> host.o (host TM) | |
| // We create a full clone of our LLVM host module, since we will embed the device IR | |
| // into it, and this might break caching or incremental compilation otherwise. | |
| let ok = unsafe { | |
| llvm::RustOffloadWrapper::get_instance().llvm_rust_offload_embed_buffer_in_module( | |
| module.module_llvm.llmod(), | |
| device_bin_c.as_c_str(), | |
| ) | |
| }; | |
| if !ok { | |
| dcx.emit_err(crate::diagnostics::OffloadEmbedFailed); | |
| } | |
| write_output_file( | |
| dcx, | |
| module.module_llvm.tm.raw(), | |
| config.no_builtins, | |
| module.module_llvm.llmod(), | |
| &out_obj, | |
| None, | |
| llvm::FileType::ObjectFile, | |
| prof, | |
| true, | |
| ); | |
| // We ignore cgcx.save_temps here and unconditionally always keep our `device.bin` artifact. | |
| // Otherwise, recompiling the host code would fail since we deleted that device artifact | |
| // in the previous host compilation, which would be confusing at best. | |
| let ok = unsafe { | |
| llvm::RustOffloadWrapper::get_instance().llvm_rust_offload_wrap_images( | |
| module.module_llvm.llmod(), | |
| device_bin_c.as_c_str(), | |
| ) | |
| }; | |
| if !ok { | |
| dcx.emit_err(crate::diagnostics::OffloadWrapImagesFailed); | |
| } | |
| } | |
| } | |
| result.into_result().unwrap_or_else(|()| llvm_err(dcx, LlvmError::RunLlvmPasses)) | |
| } | |
| // Unsafe due to LLVM calls. | |
| pub(crate) fn optimize( | |
| cgcx: &CodegenContext, | |
| prof: &SelfProfilerRef, | |
| shared_emitter: &SharedEmitter, | |
| module: &mut ModuleCodegen<ModuleLlvm>, | |
| config: &ModuleConfig, | |
| ) { | |
| let _timer = prof.generic_activity_with_arg("LLVM_module_optimize", &*module.name); | |
| let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); | |
| let dcx = dcx.handle(); | |
| let llcx = &*module.module_llvm.llcx; | |
| let _handlers = | |
| DiagnosticHandlers::new(cgcx, shared_emitter, llcx, module, CodegenDiagnosticsStage::Opt); | |
| if module.kind == ModuleKind::Regular { | |
| save_temp_bitcode(cgcx, module, "no-opt"); | |
| } | |
| // FIXME(ZuseZ4): support SanitizeHWAddress and prevent illegal/unsupported opts | |
| if let Some(opt_level) = config.opt_level { | |
| let opt_stage = match cgcx.lto { | |
| Lto::Fat => llvm::OptStage::PreLinkFatLTO, | |
| Lto::Thin | Lto::ThinLocal => llvm::OptStage::PreLinkThinLTO, | |
| _ if cgcx.use_linker_plugin_lto => llvm::OptStage::PreLinkThinLTO, | |
| _ => llvm::OptStage::PreLinkNoLTO, | |
| }; | |
| // If we know that we will later run AD, then we disable vectorization and loop unrolling. | |
| // Otherwise we pretend AD is already done and run the normal opt pipeline (=PostAD). | |
| let consider_ad = config.autodiff.contains(&config::AutoDiff::Enable); | |
| let autodiff_stage = if consider_ad { AutodiffStage::PreAD } else { AutodiffStage::PostAD }; | |
| // The embedded bitcode is used to run LTO/ThinLTO. | |
| // The bitcode obtained during the `codegen` phase is no longer suitable for performing LTO. | |
| // It may have undergone LTO due to ThinLocal, so we need to obtain the embedded bitcode at | |
| // this point. | |
| let (mut thin_lto_buffer, mut thin_lto_summary_buffer) = if (module.kind | |
| == ModuleKind::Regular | |
| && config.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full)) | |
| || config.emit_thin_lto_summary | |
| { | |
| (Some(None), config.emit_thin_lto_summary.then_some(None)) | |
| } else { | |
| (None, None) | |
| }; | |
| unsafe { | |
| llvm_optimize( | |
| cgcx, | |
| prof, | |
| dcx, | |
| module, | |
| thin_lto_buffer.as_mut(), | |
| thin_lto_summary_buffer.as_mut(), | |
| config, | |
| opt_level, | |
| opt_stage, | |
| autodiff_stage, | |
| ) | |
| }; | |
| if let Some(thin_lto_buffer) = thin_lto_buffer { | |
| let thin_lto_buffer = thin_lto_buffer.unwrap(); | |
| module.thin_lto_buffer = Some(thin_lto_buffer.data().to_vec()); | |
| let bc_summary_out = | |
| cgcx.output_filenames.temp_path_for_cgu(OutputType::ThinLinkBitcode, &module.name); | |
| if let Some(thin_lto_summary_buffer) = thin_lto_summary_buffer | |
| && let Some(thin_link_bitcode_filename) = bc_summary_out.file_name() | |
| { | |
| let thin_lto_summary_buffer = thin_lto_summary_buffer.unwrap(); | |
| let summary_data = thin_lto_summary_buffer.data(); | |
| prof.artifact_size( | |
| "llvm_bitcode_summary", | |
| thin_link_bitcode_filename.to_string_lossy(), | |
| summary_data.len() as u64, | |
| ); | |
| let _timer = prof.generic_activity_with_arg( | |
| "LLVM_module_codegen_emit_bitcode_summary", | |
| &*module.name, | |
| ); | |
| if let Err(err) = fs::write(&bc_summary_out, summary_data) { | |
| dcx.emit_err(WriteBytecode { path: &bc_summary_out, err }); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| pub(crate) fn codegen( | |
| cgcx: &CodegenContext, | |
| prof: &SelfProfilerRef, | |
| shared_emitter: &SharedEmitter, | |
| module: ModuleCodegen<ModuleLlvm>, | |
| config: &ModuleConfig, | |
| ) -> CompiledModule { | |
| let _timer = prof.generic_activity_with_arg("LLVM_module_codegen", &*module.name); | |
| let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); | |
| let dcx = dcx.handle(); | |
| { | |
| let llmod = module.module_llvm.llmod(); | |
| let llcx = &*module.module_llvm.llcx; | |
| let tm = &*module.module_llvm.tm; | |
| let _handlers = DiagnosticHandlers::new( | |
| cgcx, | |
| shared_emitter, | |
| llcx, | |
| &module, | |
| CodegenDiagnosticsStage::Codegen, | |
| ); | |
| if cgcx.msvc_imps_needed { | |
| create_msvc_imps(cgcx, llcx, llmod); | |
| } | |
| // Note that if object files are just LLVM bitcode we write bitcode, | |
| // copy it to the .o file, and delete the bitcode if it wasn't | |
| // otherwise requested. | |
| let bc_out = cgcx.output_filenames.temp_path_for_cgu(OutputType::Bitcode, &module.name); | |
| let obj_out = cgcx.output_filenames.temp_path_for_cgu(OutputType::Object, &module.name); | |
| if config.bitcode_needed() { | |
| if config.emit_bc || config.emit_obj == EmitObj::Bitcode { | |
| let thin = { | |
| let _timer = prof.generic_activity_with_arg( | |
| "LLVM_module_codegen_make_bitcode", | |
| &*module.name, | |
| ); | |
| ModuleBuffer::new(llmod, cgcx.lto != Lto::Fat) | |
| }; | |
| let data = thin.data(); | |
| let _timer = prof | |
| .generic_activity_with_arg("LLVM_module_codegen_emit_bitcode", &*module.name); | |
| if let Some(bitcode_filename) = bc_out.file_name() { | |
| prof.artifact_size( | |
| "llvm_bitcode", | |
| bitcode_filename.to_string_lossy(), | |
| data.len() as u64, | |
| ); | |
| } | |
| if let Err(err) = fs::write(&bc_out, data) { | |
| dcx.emit_err(WriteBytecode { path: &bc_out, err }); | |
| } | |
| } | |
| if config.embed_bitcode() && module.kind == ModuleKind::Regular { | |
| let _timer = prof | |
| .generic_activity_with_arg("LLVM_module_codegen_embed_bitcode", &*module.name); | |
| let thin_bc = | |
| module.thin_lto_buffer.as_deref().expect("cannot find embedded bitcode"); | |
| embed_bitcode(cgcx, llcx, llmod, &thin_bc); | |
| } | |
| } | |
| if config.emit_ir { | |
| let _timer = | |
| prof.generic_activity_with_arg("LLVM_module_codegen_emit_ir", &*module.name); | |
| let out = | |
| cgcx.output_filenames.temp_path_for_cgu(OutputType::LlvmAssembly, &module.name); | |
| let out_c = path_to_c_string(&out); | |
| extern "C" fn demangle_callback( | |
| input_ptr: *const c_char, | |
| input_len: size_t, | |
| output_ptr: *mut c_char, | |
| output_len: size_t, | |
| ) -> size_t { | |
| let input = | |
| unsafe { slice::from_raw_parts(input_ptr as *const u8, input_len as usize) }; | |
| let Ok(input) = str::from_utf8(input) else { return 0 }; | |
| let output = unsafe { | |
| slice::from_raw_parts_mut(output_ptr as *mut u8, output_len as usize) | |
| }; | |
| let mut cursor = io::Cursor::new(output); | |
| let Ok(demangled) = rustc_demangle::try_demangle(input) else { return 0 }; | |
| if write!(cursor, "{demangled:#}").is_err() { | |
| // Possible only if provided buffer is not big enough | |
| return 0; | |
| } | |
| cursor.position() as size_t | |
| } | |
| let result = | |
| unsafe { llvm::LLVMRustPrintModule(llmod, out_c.as_ptr(), demangle_callback) }; | |
| if result == llvm::LLVMRustResult::Success { | |
| record_artifact_size(prof, "llvm_ir", &out); | |
| } | |
| result | |
| .into_result() | |
| .unwrap_or_else(|()| llvm_err(dcx, LlvmError::WriteIr { path: &out })); | |
| } | |
| if config.emit_asm { | |
| let _timer = | |
| prof.generic_activity_with_arg("LLVM_module_codegen_emit_asm", &*module.name); | |
| let path = cgcx.output_filenames.temp_path_for_cgu(OutputType::Assembly, &module.name); | |
| // We can't use the same module for asm and object code output, | |
| // because that triggers various errors like invalid IR or broken | |
| // binaries. So we must clone the module to produce the asm output | |
| // if we are also producing object code. | |
| let llmod = if let EmitObj::ObjectCode(_) = config.emit_obj { | |
| llvm::LLVMCloneModule(llmod) | |
| } else { | |
| llmod | |
| }; | |
| write_output_file( | |
| dcx, | |
| tm.raw(), | |
| config.no_builtins, | |
| llmod, | |
| &path, | |
| None, | |
| llvm::FileType::AssemblyFile, | |
| prof, | |
| config.verify_llvm_ir, | |
| ); | |
| } | |
| match config.emit_obj { | |
| EmitObj::ObjectCode(_) => { | |
| let _timer = | |
| prof.generic_activity_with_arg("LLVM_module_codegen_emit_obj", &*module.name); | |
| let dwo_out = cgcx.output_filenames.temp_path_dwo_for_cgu(&module.name); | |
| let dwo_out = match (cgcx.split_debuginfo, cgcx.split_dwarf_kind) { | |
| // Don't change how DWARF is emitted when disabled. | |
| (SplitDebuginfo::Off, _) => None, | |
| // Don't provide a DWARF object path if split debuginfo is enabled but this is | |
| // a platform that doesn't support Split DWARF. | |
| _ if !cgcx.target_can_use_split_dwarf => None, | |
| // Don't provide a DWARF object path in single mode, sections will be written | |
| // into the object as normal but ignored by linker. | |
| (_, SplitDwarfKind::Single) => None, | |
| // Emit (a subset of the) DWARF into a separate dwarf object file in split | |
| // mode. | |
| (_, SplitDwarfKind::Split) => Some(dwo_out.as_path()), | |
| }; | |
| write_output_file( | |
| dcx, | |
| tm.raw(), | |
| config.no_builtins, | |
| llmod, | |
| &obj_out, | |
| dwo_out, | |
| llvm::FileType::ObjectFile, | |
| prof, | |
| config.verify_llvm_ir, | |
| ); | |
| } | |
| EmitObj::Bitcode => { | |
| debug!("copying bitcode {:?} to obj {:?}", bc_out, obj_out); | |
| if let Err(err) = link_or_copy(&bc_out, &obj_out) { | |
| dcx.emit_err(CopyBitcode { err }); | |
| } | |
| if !config.emit_bc { | |
| debug!("removing_bitcode {:?}", bc_out); | |
| ensure_removed(dcx, &bc_out); | |
| } | |
| } | |
| EmitObj::None => {} | |
| } | |
| record_llvm_cgu_instructions_stats(prof, &module.name, llmod); | |
| } | |
| // `.dwo` files are only emitted if: | |
| // | |
| // - Object files are being emitted (i.e. bitcode only or metadata only compilations will not | |
| // produce dwarf objects, even if otherwise enabled) | |
| // - Target supports Split DWARF | |
| // - Split debuginfo is enabled | |
| // - Split DWARF kind is `split` (i.e. debuginfo is split into `.dwo` files, not different | |
| // sections in the `.o` files). | |
| let dwarf_object_emitted = matches!(config.emit_obj, EmitObj::ObjectCode(_)) | |
| && cgcx.target_can_use_split_dwarf | |
| && cgcx.split_debuginfo != SplitDebuginfo::Off | |
| && cgcx.split_dwarf_kind == SplitDwarfKind::Split; | |
| module.into_compiled_module( | |
| config.emit_obj != EmitObj::None, | |
| dwarf_object_emitted, | |
| config.emit_bc, | |
| config.emit_asm, | |
| config.emit_ir, | |
| &cgcx.output_filenames, | |
| ) | |
| } | |
| fn create_section_with_flags_asm(section_name: &str, section_flags: &str, data: &[u8]) -> Vec<u8> { | |
| let mut asm = format!(".section {section_name},\"{section_flags}\"\n").into_bytes(); | |
| asm.extend_from_slice(b".ascii \""); | |
| asm.reserve(data.len()); | |
| for &byte in data { | |
| if byte == b'\\' || byte == b'"' { | |
| asm.push(b'\\'); | |
| asm.push(byte); | |
| } else if byte < 0x20 || byte >= 0x80 { | |
| // Avoid non UTF-8 inline assembly. Use octal escape sequence, because it is fixed | |
| // width, while hex escapes will consume following characters. | |
| asm.push(b'\\'); | |
| asm.push(b'0' + ((byte >> 6) & 0x7)); | |
| asm.push(b'0' + ((byte >> 3) & 0x7)); | |
| asm.push(b'0' + ((byte >> 0) & 0x7)); | |
| } else { | |
| asm.push(byte); | |
| } | |
| } | |
| asm.extend_from_slice(b"\"\n"); | |
| asm | |
| } | |
| pub(crate) fn bitcode_section_name(cgcx: &CodegenContext) -> &'static CStr { | |
| if cgcx.target_is_like_darwin { | |
| c"__LLVM,__bitcode" | |
| } else if cgcx.target_is_like_aix { | |
| c".ipa" | |
| } else { | |
| c".llvmbc" | |
| } | |
| } | |
| /// Embed the bitcode of an LLVM module for LTO in the LLVM module itself. | |
| fn embed_bitcode( | |
| cgcx: &CodegenContext, | |
| llcx: &llvm::Context, | |
| llmod: &llvm::Module, | |
| bitcode: &[u8], | |
| ) { | |
| // We're adding custom sections to the output object file, but we definitely | |
| // do not want these custom sections to make their way into the final linked | |
| // executable. The purpose of these custom sections is for tooling | |
| // surrounding object files to work with the LLVM IR, if necessary. For | |
| // example rustc's own LTO will look for LLVM IR inside of the object file | |
| // in these sections by default. | |
| // | |
| // To handle this is a bit different depending on the object file format | |
| // used by the backend, broken down into a few different categories: | |
| // | |
| // * Mach-O - this is for macOS. Inspecting the source code for the native | |
| // linker here shows that the `.llvmbc` and `.llvmcmd` sections are | |
| // automatically skipped by the linker. In that case there's nothing extra | |
| // that we need to do here. We do need to make sure that the | |
| // `__LLVM,__cmdline` section exists even though it is empty as otherwise | |
| // ld64 rejects the object file. | |
| // | |
| // * Wasm - the native LLD linker is hard-coded to skip `.llvmbc` and | |
| // `.llvmcmd` sections, so there's nothing extra we need to do. | |
| // | |
| // * COFF - if we don't do anything the linker will by default copy all | |
| // these sections to the output artifact, not what we want! To subvert | |
| // this we want to flag the sections we inserted here as | |
| // `IMAGE_SCN_LNK_REMOVE`. | |
| // | |
| // * ELF - this is very similar to COFF above. One difference is that these | |
| // sections are removed from the output linked artifact when | |
| // `--gc-sections` is passed, which we pass by default. If that flag isn't | |
| // passed though then these sections will show up in the final output. | |
| // Additionally the flag that we need to set here is `SHF_EXCLUDE`. | |
| // | |
| // * XCOFF - AIX linker ignores content in .ipa and .info if no auxiliary | |
| // symbol associated with these sections. | |
| // | |
| // Unfortunately, LLVM provides no way to set custom section flags. For ELF | |
| // and COFF we emit the sections using module level inline assembly for that | |
| // reason (see issue #90326 for historical background). | |
| if cgcx.target_is_like_darwin | |
| || cgcx.target_is_like_aix | |
| || cgcx.target_arch == "wasm32" | |
| || cgcx.target_arch == "wasm64" | |
| { | |
| // We don't need custom section flags, create LLVM globals. | |
| let llconst = common::bytes_in_context(llcx, bitcode); | |
| let llglobal = llvm::add_global(llmod, common::val_ty(llconst), c"rustc.embedded.module"); | |
| llvm::set_initializer(llglobal, llconst); | |
| llvm::set_section(llglobal, bitcode_section_name(cgcx)); | |
| llvm::set_linkage(llglobal, llvm::Linkage::PrivateLinkage); | |
| llvm::LLVMSetGlobalConstant(llglobal, llvm::TRUE); | |
| let llconst = common::bytes_in_context(llcx, &[]); | |
| let llglobal = llvm::add_global(llmod, common::val_ty(llconst), c"rustc.embedded.cmdline"); | |
| llvm::set_initializer(llglobal, llconst); | |
| let section = if cgcx.target_is_like_darwin { | |
| c"__LLVM,__cmdline" | |
| } else if cgcx.target_is_like_aix { | |
| c".info" | |
| } else { | |
| c".llvmcmd" | |
| }; | |
| llvm::set_section(llglobal, section); | |
| llvm::set_linkage(llglobal, llvm::Linkage::PrivateLinkage); | |
| } else { | |
| // We need custom section flags, so emit module-level inline assembly. | |
| let section_flags = if cgcx.is_pe_coff { "n" } else { "e" }; | |
| let asm = create_section_with_flags_asm(".llvmbc", section_flags, bitcode); | |
| llvm::append_module_inline_asm(llmod, &asm, "", ""); | |
| let asm = create_section_with_flags_asm(".llvmcmd", section_flags, &[]); | |
| llvm::append_module_inline_asm(llmod, &asm, "", ""); | |
| } | |
| } | |
| // Create a `__imp_<symbol> = &symbol` global for each externally visible | |
| // static data symbol, including aliases to static data. | |
| // This is required to satisfy `dllimport` references to static data in .rlibs | |
| // when using MSVC linker. We do this only for data, as linker can fix up | |
| // code references on its own. | |
| // See #26591, #27438 | |
| fn create_msvc_imps(cgcx: &CodegenContext, llcx: &llvm::Context, llmod: &llvm::Module) { | |
| if !cgcx.msvc_imps_needed { | |
| return; | |
| } | |
| // The x86 ABI seems to require that leading underscores are added to symbol | |
| // names, so we need an extra underscore on x86. There's also a leading | |
| // '\x01' here which disables LLVM's symbol mangling (e.g., no extra | |
| // underscores added in front). | |
| let prefix: &[u8] = if cgcx.target_arch == "x86" { b"\x01__imp__" } else { b"\x01__imp_" }; | |
| let ptr_ty = llvm_type_ptr(llcx); | |
| let symbols = std::iter::chain( | |
| base::iter_globals(llmod), | |
| base::iter_global_aliases(llmod).filter(|&val| { | |
| llvm::LLVMGetTypeKind(unsafe { llvm::LLVMGlobalGetValueType(val) }).to_rust() | |
| != llvm::TypeKind::Function | |
| }), | |
| ) | |
| .map(|val| (val, llvm::get_linkage(val))) | |
| .filter(|&(val, linkage)| { | |
| matches!(linkage, llvm::Linkage::ExternalLinkage | llvm::Linkage::WeakAnyLinkage) | |
| && !llvm::is_declaration(val) | |
| }) | |
| .collect::<Vec<_>>(); | |
| for (val, linkage) in symbols { | |
| let name = llvm::get_value_name(val); | |
| // Exclude some symbols that we know are not Rust symbols. | |
| if ignored(&name) { | |
| continue; | |
| } | |
| let mut imp_name = prefix.to_vec(); | |
| imp_name.extend(name); | |
| let imp_name = CString::new(imp_name).unwrap(); | |
| let imp = llvm::add_global(llmod, ptr_ty, &imp_name); | |
| llvm::set_initializer(imp, val); | |
| llvm::set_linkage(imp, linkage); | |
| } | |
| // Use this function to exclude certain symbols from `__imp` generation. | |
| fn ignored(symbol_name: &[u8]) -> bool { | |
| // These are symbols generated by LLVM's profiling instrumentation | |
| symbol_name.starts_with(b"__llvm_profile_") | |
| } | |
| } | |
| fn record_artifact_size( | |
| self_profiler_ref: &SelfProfilerRef, | |
| artifact_kind: &'static str, | |
| path: &Path, | |
| ) { | |
| // Don't stat the file if we are not going to record its size. | |
| if !self_profiler_ref.enabled() { | |
| return; | |
| } | |
| if let Some(artifact_name) = path.file_name() { | |
| let file_size = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0); | |
| self_profiler_ref.artifact_size(artifact_kind, artifact_name.to_string_lossy(), file_size); | |
| } | |
| } | |
| fn record_llvm_cgu_instructions_stats(prof: &SelfProfilerRef, name: &str, llmod: &llvm::Module) { | |
| if !prof.enabled() { | |
| return; | |
| } | |
| let total = unsafe { llvm::LLVMRustModuleInstructionStats(llmod) }; | |
| prof.artifact_size("cgu_instructions", name, total); | |
| } |