/* * 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 errors; 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::cfg_target_feature; 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::Session; use rustc_session::config::{OptLevel, OutputFilenames}; 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 String>(pub F); impl String> Drop for PrintOnPanic { 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>>>, } 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, } 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 { Box::new(codegen_crate(self.clone(), tcx)) } fn join_codegen( &self, ongoing_codegen: Box, sess: &Session, _outputs: &OutputFilenames, crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap) { ongoing_codegen .downcast::>() .expect("Expected GccCodegenBackend's OngoingCodegen, found Box") .join(sess, crate_info) } fn target_config(&self, sess: &Session) -> TargetConfig { target_config(sess, &self.target_info) } fn fallback_intrinsics(&self) -> Vec { 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, ) -> (ModuleCodegen, 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, /// 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, } 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 { // FIXME(antoyo): set opt level. Arc::new(|_, _| ()) } fn optimize_and_codegen_fat_lto( sess: &Session, cgcx: &CodegenContext, shared_emitter: &SharedEmitter, _tm_factory: TargetMachineFactoryFn, // FIXME(bjorn3): Limit LTO exports to these symbols _exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> 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>, ) -> (Vec>, Vec) { unreachable!() } fn optimize( _cgcx: &CodegenContext, _prof: &SelfProfilerRef, _shared_emitter: &SharedEmitter, module: &mut ModuleCodegen, 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, _thin: ThinModule, ) -> CompiledModule { unreachable!() } fn codegen( cgcx: &CodegenContext, prof: &SelfProfilerRef, shared_emitter: &SharedEmitter, module: ModuleCodegen, 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 { #[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) -> 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 (unstable_target_features, target_features) = cfg_target_feature( 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 { target_features, unstable_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, } }