| /* | |
| * FIXME(antoyo): implement equality in libgccjit based on https://zpz.github.io/blog/overloading-equality-operator-in-cpp-class-hierarchy/ (for type equality?) | |
| * For Thin LTO, this might be helpful: | |
| // cspell:disable-next-line | |
| * In gcc 4.6 -fwhopr was removed and became default with -flto. The non-whopr path can still be executed via -flto-partition=none. | |
| * Or the new incremental LTO (https://www.phoronix.com/news/GCC-Incremental-LTO-Patches)? | |
| * | |
| * Maybe some missing optimizations enabled by rustc's LTO is in there: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html | |
| // cspell:disable-next-line | |
| * Like -fipa-icf (should be already enabled) and maybe -fdevirtualize-at-ltrans. | |
| * FIXME: disable debug info always being emitted. Perhaps this slows down things? | |
| * | |
| * FIXME(antoyo): remove the patches. | |
| */ | |
| #![feature(rustc_private)] | |
| #![recursion_limit = "256"] | |
| #![warn(rust_2018_idioms)] | |
| #![warn(unused_lifetimes)] | |
| #![deny(clippy::pattern_type_mismatch)] | |
| #![expect(clippy::uninlined_format_args)] | |
| #![allow(clippy::collapsible_match)] | |
| // The rustc crates we need | |
| extern crate rustc_abi; | |
| extern crate rustc_apfloat; | |
| extern crate rustc_ast; | |
| extern crate rustc_codegen_ssa; | |
| extern crate rustc_data_structures; | |
| extern crate rustc_errors; | |
| extern crate rustc_fs_util; | |
| extern crate rustc_hir; | |
| #[cfg(feature = "master")] | |
| extern crate rustc_interface; | |
| extern crate rustc_log; | |
| extern crate rustc_macros; | |
| extern crate rustc_middle; | |
| extern crate rustc_session; | |
| extern crate rustc_span; | |
| extern crate rustc_symbol_mangling; | |
| extern crate rustc_target; | |
| extern crate rustc_type_ir; | |
| // This prevents duplicating functions and statics that are already part of the host rustc process. | |
| #[expect(unused_extern_crates)] | |
| extern crate rustc_driver; | |
| mod abi; | |
| mod allocator; | |
| mod asm; | |
| mod attributes; | |
| mod back; | |
| mod base; | |
| mod builder; | |
| mod callee; | |
| mod common; | |
| mod consts; | |
| mod context; | |
| mod coverageinfo; | |
| mod debuginfo; | |
| mod declare; | |
| mod diagnostics; | |
| mod gcc_util; | |
| mod int; | |
| mod intrinsic; | |
| mod mono_item; | |
| mod type_; | |
| mod type_of; | |
| use std::any::Any; | |
| use std::ffi::CString; | |
| use std::fmt::Debug; | |
| use std::fs; | |
| use std::ops::Deref; | |
| use std::path::{Path, PathBuf}; | |
| use std::sync::atomic::{AtomicBool, Ordering}; | |
| use std::sync::{Arc, Mutex}; | |
| use gccjit::{CType, Context, OptimizationLevel}; | |
| #[cfg(feature = "master")] | |
| use gccjit::{TargetInfo, Version}; | |
| use rustc_ast::expand::allocator::AllocatorMethod; | |
| use rustc_codegen_ssa::back::lto::ThinModule; | |
| use rustc_codegen_ssa::back::write::{ | |
| CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter, TargetMachineFactoryFn, ThinLtoInput, | |
| }; | |
| use rustc_codegen_ssa::base::codegen_crate; | |
| use rustc_codegen_ssa::target_features::internal_target_features; | |
| use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, WriteBackendMethods}; | |
| use rustc_codegen_ssa::{CompiledModule, CompiledModules, CrateInfo, ModuleCodegen, TargetConfig}; | |
| use rustc_data_structures::profiling::SelfProfilerRef; | |
| use rustc_data_structures::sync::IntoDynSyncSend; | |
| use rustc_errors::{DiagCtxt, DiagCtxtHandle}; | |
| use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; | |
| use rustc_middle::ty::TyCtxt; | |
| use rustc_middle::util::Providers; | |
| use rustc_session::config::{OptLevel, OutputFilenames}; | |
| use rustc_session::{IncrCompSession, Session}; | |
| use rustc_span::{Symbol, sym}; | |
| use rustc_target::spec::{Arch, RelocModel}; | |
| use tempfile::TempDir; | |
| use crate::back::lto::ModuleBuffer; | |
| use crate::gcc_util::{target_cpu, to_gcc_features}; | |
| pub struct PrintOnPanic<F: Fn() -> String>(pub F); | |
| impl<F: Fn() -> String> Drop for PrintOnPanic<F> { | |
| fn drop(&mut self) { | |
| if ::std::thread::panicking() { | |
| println!("{}", (self.0)()); | |
| } | |
| } | |
| } | |
| #[cfg(not(feature = "master"))] | |
| #[derive(Debug)] | |
| pub struct TargetInfo { | |
| supports_128bit_integers: AtomicBool, | |
| } | |
| #[cfg(not(feature = "master"))] | |
| impl TargetInfo { | |
| fn cpu_supports(&self, _feature: &str) -> bool { | |
| false | |
| } | |
| fn supports_target_dependent_type(&self, typ: CType) -> bool { | |
| match typ { | |
| CType::UInt128t | CType::Int128t => { | |
| if self.supports_128bit_integers.load(Ordering::SeqCst) { | |
| return true; | |
| } | |
| } | |
| _ => (), | |
| } | |
| false | |
| } | |
| } | |
| #[derive(Clone)] | |
| pub struct LockedTargetInfo { | |
| info: Arc<Mutex<IntoDynSyncSend<Option<TargetInfo>>>>, | |
| } | |
| impl Debug for LockedTargetInfo { | |
| fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| self.info.lock().expect("lock").fmt(formatter) | |
| } | |
| } | |
| impl LockedTargetInfo { | |
| fn cpu_supports(&self, feature: &str) -> bool { | |
| self.info | |
| .lock() | |
| .expect("lock") | |
| .as_ref() | |
| .expect("target info not initialized") | |
| .cpu_supports(feature) | |
| } | |
| fn supports_target_dependent_type(&self, typ: CType) -> bool { | |
| self.info | |
| .lock() | |
| .expect("lock") | |
| .as_ref() | |
| .expect("target info not initialized") | |
| .supports_target_dependent_type(typ) | |
| } | |
| } | |
| #[derive(Clone)] | |
| pub struct GccCodegenBackend { | |
| target_info: LockedTargetInfo, | |
| lto_supported: Arc<AtomicBool>, | |
| } | |
| fn load_libgccjit_if_needed(libgccjit_target_lib_file: &Path) { | |
| if gccjit::is_loaded() { | |
| // Do not load a libgccjit second time. | |
| return; | |
| } | |
| let path = libgccjit_target_lib_file.to_str().expect("libgccjit path"); | |
| let string = CString::new(path).expect("string to libgccjit path"); | |
| if let Err(error) = gccjit::load(&string) { | |
| panic!("Cannot load libgccjit.so: {}", error); | |
| } | |
| } | |
| impl CodegenBackend for GccCodegenBackend { | |
| fn name(&self) -> &'static str { | |
| "gcc" | |
| } | |
| fn init(&self, sess: &Session) { | |
| fn file_path(sysroot_path: &Path, sess: &Session) -> PathBuf { | |
| let rustlib_path = | |
| rustc_target::relative_target_rustlib_path(sysroot_path, &sess.host.llvm_target); | |
| sysroot_path | |
| .join(rustlib_path) | |
| .join("codegen-backends") | |
| .join("lib") | |
| .join(sess.target.llvm_target.as_ref()) | |
| .join("libgccjit.so") | |
| } | |
| // We use all_paths() instead of only path() in case the path specified by --sysroot is | |
| // invalid. | |
| // This is the case for instance in Rust for Linux where they specify --sysroot=/dev/null. | |
| for path in sess.opts.sysroot.all_paths() { | |
| let libgccjit_target_lib_file = file_path(path, sess); | |
| if let Ok(true) = fs::exists(&libgccjit_target_lib_file) { | |
| load_libgccjit_if_needed(&libgccjit_target_lib_file); | |
| break; | |
| } | |
| } | |
| if !gccjit::is_loaded() { | |
| let mut paths = vec![]; | |
| for path in sess.opts.sysroot.all_paths() { | |
| let libgccjit_target_lib_file = file_path(path, sess); | |
| paths.push(libgccjit_target_lib_file); | |
| } | |
| panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); | |
| } | |
| #[cfg(feature = "master")] | |
| { | |
| gccjit::set_lang_name(c"GNU Rust"); | |
| let target_cpu = target_cpu(sess); | |
| // Get the second TargetInfo with the correct CPU features by setting the arch. | |
| let context = Context::default(); | |
| if target_cpu != "generic" { | |
| context.add_command_line_option(format!("-march={}", target_cpu)); | |
| } | |
| *self.target_info.info.lock().expect("lock") = | |
| IntoDynSyncSend(Some(context.get_target_info())); | |
| } | |
| #[cfg(feature = "master")] | |
| { | |
| let lto_supported = gccjit::is_lto_supported(); | |
| self.lto_supported.store(lto_supported, Ordering::SeqCst); | |
| gccjit::set_global_personality_function_name(b"rust_eh_personality\0"); | |
| } | |
| #[cfg(not(feature = "master"))] | |
| { | |
| let temp_dir = TempDir::new().expect("cannot create temporary directory"); | |
| let temp_file = temp_dir.keep().join("result.asm"); | |
| let check_context = Context::default(); | |
| check_context.set_print_errors_to_stderr(false); | |
| let _int128_ty = check_context.new_c_type(CType::UInt128t); | |
| // NOTE: we cannot just call compile() as this would require other files than libgccjit.so. | |
| check_context.compile_to_file( | |
| gccjit::OutputKind::Assembler, | |
| temp_file.to_str().expect("path to str"), | |
| ); | |
| self.target_info | |
| .info | |
| .lock() | |
| .expect("lock") | |
| .0 | |
| .as_ref() | |
| .expect("target info not initialized") | |
| .supports_128bit_integers | |
| .store(check_context.get_last_error() == Ok(None), Ordering::SeqCst); | |
| } | |
| } | |
| fn thin_lto_supported(&self) -> bool { | |
| false | |
| } | |
| fn provide(&self, providers: &mut Providers) { | |
| providers.queries.global_backend_features = | |
| |tcx, ()| gcc_util::global_gcc_features(tcx.sess) | |
| } | |
| fn target_cpu(&self, sess: &Session) -> String { | |
| target_cpu(sess).to_owned() | |
| } | |
| fn codegen_crate(&self, tcx: TyCtxt<'_>) -> Box<dyn Any> { | |
| Box::new(codegen_crate(self.clone(), tcx)) | |
| } | |
| fn join_codegen( | |
| &self, | |
| ongoing_codegen: Box<dyn Any>, | |
| sess: &Session, | |
| incr_comp_session: Option<&IncrCompSession>, | |
| _outputs: &OutputFilenames, | |
| crate_info: &CrateInfo, | |
| ) -> (CompiledModules, WorkProductMap) { | |
| ongoing_codegen | |
| .downcast::<rustc_codegen_ssa::back::write::OngoingCodegen<GccCodegenBackend>>() | |
| .expect("Expected GccCodegenBackend's OngoingCodegen, found Box<Any>") | |
| .join(sess, incr_comp_session, crate_info) | |
| } | |
| fn target_config(&self, sess: &Session) -> TargetConfig { | |
| target_config(sess, &self.target_info) | |
| } | |
| fn fallback_intrinsics(&self) -> Vec<Symbol> { | |
| vec![sym::type_id_eq] | |
| } | |
| } | |
| fn new_context<'gcc, 'tcx>(tcx: TyCtxt<'tcx>) -> Context<'gcc> { | |
| let context = Context::default(); | |
| if matches!(tcx.sess.target.arch, Arch::X86 | Arch::X86_64) { | |
| context.add_command_line_option("-masm=intel"); | |
| } | |
| #[cfg(feature = "master")] | |
| { | |
| context.set_special_chars_allowed_in_func_names("$.*"); | |
| let version = Version::get(); | |
| let version = format!("{}.{}.{}", version.major, version.minor, version.patch); | |
| context.set_output_ident(&format!( | |
| "rustc version {} with libgccjit {}", | |
| rustc_interface::util::rustc_version_str().unwrap_or("unknown version"), | |
| version, | |
| )); | |
| } | |
| // FIXME(antoyo): check if this should only be added when using -Cforce-unwind-tables=n. | |
| context.add_command_line_option("-fno-asynchronous-unwind-tables"); | |
| context | |
| } | |
| impl ExtraBackendMethods for GccCodegenBackend { | |
| type Module = GccContext; | |
| fn codegen_allocator( | |
| &self, | |
| tcx: TyCtxt<'_>, | |
| module_name: &str, | |
| methods: &[AllocatorMethod], | |
| ) -> Self::Module { | |
| let lto_supported = self.lto_supported.load(Ordering::SeqCst); | |
| let mut mods = GccContext { | |
| context: Arc::new(SyncContext::new(new_context(tcx))), | |
| relocation_model: tcx.sess.relocation_model(), | |
| lto_mode: LtoMode::None, | |
| lto_supported, | |
| temp_dir: None, | |
| }; | |
| unsafe { | |
| allocator::codegen(tcx, &mut mods, module_name, methods); | |
| } | |
| mods | |
| } | |
| fn compile_codegen_unit( | |
| &self, | |
| tcx: TyCtxt<'_>, | |
| cgu_name: Symbol, | |
| _bitcode_needed: bool, | |
| ) -> (ModuleCodegen<Self::Module>, u64) { | |
| base::compile_codegen_unit( | |
| tcx, | |
| cgu_name, | |
| self.target_info.clone(), | |
| self.lto_supported.load(Ordering::SeqCst), | |
| ) | |
| } | |
| } | |
| #[derive(Clone, Copy, PartialEq)] | |
| pub enum LtoMode { | |
| None, | |
| Thin, | |
| Fat, | |
| } | |
| pub struct GccContext { | |
| context: Arc<SyncContext>, | |
| /// This field is needed in order to be able to set the flag -fPIC when necessary when doing | |
| /// LTO. | |
| relocation_model: RelocModel, | |
| lto_mode: LtoMode, | |
| lto_supported: bool, | |
| // Temporary directory used by LTO. We keep it here so that it's not removed before linking. | |
| temp_dir: Option<TempDir>, | |
| } | |
| struct SyncContext { | |
| context: Context<'static>, | |
| } | |
| impl SyncContext { | |
| fn new(context: Context<'static>) -> Self { | |
| Self { context } | |
| } | |
| } | |
| impl Deref for SyncContext { | |
| type Target = Context<'static>; | |
| fn deref(&self) -> &Self::Target { | |
| &self.context | |
| } | |
| } | |
| unsafe impl Send for SyncContext {} | |
| // FIXME(antoyo): that shouldn't be Sync. Parallel compilation is currently disabled with "CodegenBackend::supports_parallel()". | |
| unsafe impl Sync for SyncContext {} | |
| impl WriteBackendMethods for GccCodegenBackend { | |
| type Module = GccContext; | |
| type TargetMachine = (); | |
| type ModuleBuffer = ModuleBuffer; | |
| type ThinData = (); | |
| fn supports_parallel(&self) -> bool { | |
| false | |
| } | |
| fn target_machine_factory( | |
| &self, | |
| _sess: &Session, | |
| _opt_level: OptLevel, | |
| _features: &[String], | |
| ) -> TargetMachineFactoryFn<Self> { | |
| // FIXME(antoyo): set opt level. | |
| Arc::new(|_, _| ()) | |
| } | |
| fn optimize_and_codegen_fat_lto( | |
| sess: &Session, | |
| cgcx: &CodegenContext, | |
| shared_emitter: &SharedEmitter, | |
| _tm_factory: TargetMachineFactoryFn<Self>, | |
| // FIXME(bjorn3): Limit LTO exports to these symbols | |
| _exported_symbols_for_lto: &[String], | |
| each_linked_rlib_for_lto: &[PathBuf], | |
| modules: Vec<FatLtoInput<Self>>, | |
| ) -> CompiledModule { | |
| back::lto::run_fat(cgcx, &sess.prof, shared_emitter, each_linked_rlib_for_lto, modules) | |
| } | |
| fn run_thin_lto( | |
| _cgcx: &CodegenContext, | |
| _prof: &SelfProfilerRef, | |
| _dcx: DiagCtxtHandle<'_>, | |
| // FIXME(bjorn3): Limit LTO exports to these symbols | |
| _exported_symbols_for_lto: &[String], | |
| _each_linked_rlib_for_lto: &[PathBuf], | |
| _modules: Vec<ThinLtoInput<Self>>, | |
| ) -> (Vec<ThinModule<Self>>, Vec<WorkProduct>) { | |
| unreachable!() | |
| } | |
| fn optimize( | |
| _cgcx: &CodegenContext, | |
| _prof: &SelfProfilerRef, | |
| _shared_emitter: &SharedEmitter, | |
| module: &mut ModuleCodegen<Self::Module>, | |
| config: &ModuleConfig, | |
| ) { | |
| module.module_llvm.context.set_optimization_level(to_gcc_opt_level(config.opt_level)); | |
| } | |
| fn optimize_and_codegen_thin( | |
| _cgcx: &CodegenContext, | |
| _prof: &SelfProfilerRef, | |
| _shared_emitter: &SharedEmitter, | |
| _tm_factory: TargetMachineFactoryFn<Self>, | |
| _thin: ThinModule<Self>, | |
| ) -> CompiledModule { | |
| unreachable!() | |
| } | |
| fn codegen( | |
| cgcx: &CodegenContext, | |
| prof: &SelfProfilerRef, | |
| shared_emitter: &SharedEmitter, | |
| module: ModuleCodegen<Self::Module>, | |
| config: &ModuleConfig, | |
| ) -> CompiledModule { | |
| let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); | |
| let dcx = dcx.handle(); | |
| back::write::codegen(cgcx, prof, dcx, module, config) | |
| } | |
| fn serialize_module(_module: Self::Module, _is_thin: bool) -> Self::ModuleBuffer { | |
| unimplemented!(); | |
| } | |
| } | |
| /// This is the entrypoint for a hot plugged rustc_codegen_gccjit | |
| #[unsafe(no_mangle)] | |
| pub fn __rustc_codegen_backend() -> Box<dyn CodegenBackend> { | |
| #[cfg(feature = "master")] | |
| let info = { | |
| // Check whether the target supports 128-bit integers, and sized floating point types (like | |
| // Float16). | |
| Arc::new(Mutex::new(IntoDynSyncSend(None))) | |
| }; | |
| #[cfg(not(feature = "master"))] | |
| let info = Arc::new(Mutex::new(IntoDynSyncSend(Some(TargetInfo { | |
| supports_128bit_integers: AtomicBool::new(false), | |
| })))); | |
| Box::new(GccCodegenBackend { | |
| lto_supported: Arc::new(AtomicBool::new(false)), | |
| target_info: LockedTargetInfo { info }, | |
| }) | |
| } | |
| fn to_gcc_opt_level(optlevel: Option<OptLevel>) -> OptimizationLevel { | |
| match optlevel { | |
| None => OptimizationLevel::None, | |
| Some(level) => match level { | |
| OptLevel::No => OptimizationLevel::None, | |
| OptLevel::Less => OptimizationLevel::Limited, | |
| OptLevel::More => OptimizationLevel::Standard, | |
| OptLevel::Aggressive => OptimizationLevel::Aggressive, | |
| OptLevel::Size | OptLevel::SizeMin => OptimizationLevel::Limited, | |
| }, | |
| } | |
| } | |
| /// Returns the features that should be set in `cfg(target_feature)`. | |
| fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig { | |
| let internal_target_features = internal_target_features( | |
| sess, | |
| |feature| to_gcc_features(sess, feature), | |
| |feature| { | |
| // FIXME: we disable Neon for now since we don't support the LLVM intrinsics for it. | |
| if feature == "neon" { | |
| return false; | |
| } | |
| target_info.cpu_supports(feature) | |
| // cSpell:disable | |
| /* | |
| adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma, | |
| avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq, | |
| bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm, | |
| sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves | |
| */ | |
| // cSpell:enable | |
| }, | |
| ); | |
| let has_reliable_f16 = target_info.supports_target_dependent_type(CType::Float16); | |
| let has_reliable_f128 = target_info.supports_target_dependent_type(CType::Float128); | |
| TargetConfig { | |
| internal_target_features, | |
| // There are no known bugs with GCC support for f16 or f128 | |
| has_reliable_f16, | |
| has_reliable_f16_math: has_reliable_f16, | |
| has_reliable_f128, | |
| has_reliable_f128_math: has_reliable_f128, | |
| } | |
| } |