rust/compiler/rustc_codegen_ssa/src/back/write.rs

1992 lines
82 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::link::{self, ensure_removed};
use super::linker::LinkerInfo;
use super::lto::{self, SerializedModule};
use super::symbol_export::symbol_name_for_instance_in_crate;
use crate::{
CachedModuleCodegen, CodegenResults, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind,
};
use crate::traits::*;
use jobserver::{Acquired, Client};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_data_structures::profiling::TimingGuard;
use rustc_data_structures::profiling::VerboseTimingGuard;
use rustc_data_structures::sync::Lrc;
use rustc_errors::emitter::Emitter;
use rustc_errors::{DiagnosticId, FatalError, Handler, Level};
use rustc_fs_util::link_or_copy;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_incremental::{
copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir, in_incr_comp_dir_sess,
};
use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
use rustc_middle::middle::cstore::EncodedMetadata;
use rustc_middle::middle::exported_symbols::SymbolExportLevel;
use rustc_middle::ty::TyCtxt;
use rustc_session::cgu_reuse_tracker::CguReuseTracker;
use rustc_session::config::{self, CrateType, Lto, OutputFilenames, OutputType};
use rustc_session::config::{Passes, SanitizerSet, SwitchWithOptPath};
use rustc_session::Session;
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::{BytePos, FileName, InnerSpan, Pos, Span};
use rustc_target::spec::{MergeFunctions, PanicStrategy};
use std::any::Any;
use std::fs;
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use std::str;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;
const PRE_LTO_BC_EXT: &str = "pre-lto.bc";
/// What kind of object file to emit.
#[derive(Clone, Copy, PartialEq)]
pub enum EmitObj {
// No object file.
None,
// Just uncompressed llvm bitcode. Provides easy compatibility with
// emscripten's ecc compiler, when used as the linker.
Bitcode,
// Object code, possibly augmented with a bitcode section.
ObjectCode(BitcodeSection),
}
/// What kind of llvm bitcode section to embed in an object file.
#[derive(Clone, Copy, PartialEq)]
pub enum BitcodeSection {
// No bitcode section.
None,
// A full, uncompressed bitcode section.
Full,
}
/// Module-specific configuration for `optimize_and_codegen`.
pub struct ModuleConfig {
/// Names of additional optimization passes to run.
pub passes: Vec<String>,
/// Some(level) to optimize at a certain level, or None to run
/// absolutely no optimizations (used for the metadata module).
pub opt_level: Option<config::OptLevel>,
/// Some(level) to optimize binary size, or None to not affect program size.
pub opt_size: Option<config::OptLevel>,
pub pgo_gen: SwitchWithOptPath,
pub pgo_use: Option<PathBuf>,
pub sanitizer: SanitizerSet,
pub sanitizer_recover: SanitizerSet,
pub sanitizer_memory_track_origins: usize,
// Flags indicating which outputs to produce.
pub emit_pre_lto_bc: bool,
pub emit_no_opt_bc: bool,
pub emit_bc: bool,
pub emit_ir: bool,
pub emit_asm: bool,
pub emit_obj: EmitObj,
pub bc_cmdline: String,
// Miscellaneous flags. These are mostly copied from command-line
// options.
pub verify_llvm_ir: bool,
pub no_prepopulate_passes: bool,
pub no_builtins: bool,
pub time_module: bool,
pub vectorize_loop: bool,
pub vectorize_slp: bool,
pub merge_functions: bool,
pub inline_threshold: Option<usize>,
pub new_llvm_pass_manager: bool,
pub emit_lifetime_markers: bool,
}
impl ModuleConfig {
fn new(
kind: ModuleKind,
sess: &Session,
no_builtins: bool,
is_compiler_builtins: bool,
) -> ModuleConfig {
// If it's a regular module, use `$regular`, otherwise use `$other`.
// `$regular` and `$other` are evaluated lazily.
macro_rules! if_regular {
($regular: expr, $other: expr) => {
if let ModuleKind::Regular = kind { $regular } else { $other }
};
}
let opt_level_and_size = if_regular!(Some(sess.opts.optimize), None);
let save_temps = sess.opts.cg.save_temps;
let should_emit_obj = sess.opts.output_types.contains_key(&OutputType::Exe)
|| match kind {
ModuleKind::Regular => sess.opts.output_types.contains_key(&OutputType::Object),
ModuleKind::Allocator => false,
ModuleKind::Metadata => sess.opts.output_types.contains_key(&OutputType::Metadata),
};
let emit_obj = if !should_emit_obj {
EmitObj::None
} else if sess.target.obj_is_bitcode
|| (sess.opts.cg.linker_plugin_lto.enabled() && !no_builtins)
{
// This case is selected if the target uses objects as bitcode, or
// if linker plugin LTO is enabled. In the linker plugin LTO case
// the assumption is that the final link-step will read the bitcode
// and convert it to object code. This may be done by either the
// native linker or rustc itself.
//
// Note, however, that the linker-plugin-lto requested here is
// explicitly ignored for `#![no_builtins]` crates. These crates are
// specifically ignored by rustc's LTO passes and wouldn't work if
// loaded into the linker. These crates define symbols that LLVM
// lowers intrinsics to, and these symbol dependencies aren't known
// until after codegen. As a result any crate marked
// `#![no_builtins]` is assumed to not participate in LTO and
// instead goes on to generate object code.
EmitObj::Bitcode
} else if need_bitcode_in_object(sess) {
EmitObj::ObjectCode(BitcodeSection::Full)
} else {
EmitObj::ObjectCode(BitcodeSection::None)
};
ModuleConfig {
passes: if_regular!(
{
let mut passes = sess.opts.cg.passes.clone();
// compiler_builtins overrides the codegen-units settings,
// which is incompatible with -Zprofile which requires that
// only a single codegen unit is used per crate.
if sess.opts.debugging_opts.profile && !is_compiler_builtins {
passes.push("insert-gcov-profiling".to_owned());
}
// The rustc option `-Zinstrument_coverage` injects intrinsic calls to
// `llvm.instrprof.increment()`, which requires the LLVM `instrprof` pass.
if sess.opts.debugging_opts.instrument_coverage {
passes.push("instrprof".to_owned());
}
passes
},
vec![]
),
opt_level: opt_level_and_size,
opt_size: opt_level_and_size,
pgo_gen: if_regular!(
sess.opts.cg.profile_generate.clone(),
SwitchWithOptPath::Disabled
),
pgo_use: if_regular!(sess.opts.cg.profile_use.clone(), None),
sanitizer: if_regular!(sess.opts.debugging_opts.sanitizer, SanitizerSet::empty()),
sanitizer_recover: if_regular!(
sess.opts.debugging_opts.sanitizer_recover,
SanitizerSet::empty()
),
sanitizer_memory_track_origins: if_regular!(
sess.opts.debugging_opts.sanitizer_memory_track_origins,
0
),
emit_pre_lto_bc: if_regular!(
save_temps || need_pre_lto_bitcode_for_incr_comp(sess),
false
),
emit_no_opt_bc: if_regular!(save_temps, false),
emit_bc: if_regular!(
save_temps || sess.opts.output_types.contains_key(&OutputType::Bitcode),
save_temps
),
emit_ir: if_regular!(
sess.opts.output_types.contains_key(&OutputType::LlvmAssembly),
false
),
emit_asm: if_regular!(
sess.opts.output_types.contains_key(&OutputType::Assembly),
false
),
emit_obj,
bc_cmdline: sess.target.bitcode_llvm_cmdline.clone(),
verify_llvm_ir: sess.verify_llvm_ir(),
no_prepopulate_passes: sess.opts.cg.no_prepopulate_passes,
no_builtins: no_builtins || sess.target.no_builtins,
// Exclude metadata and allocator modules from time_passes output,
// since they throw off the "LLVM passes" measurement.
time_module: if_regular!(true, false),
// Copy what clang does by turning on loop vectorization at O2 and
// slp vectorization at O3.
vectorize_loop: !sess.opts.cg.no_vectorize_loops
&& (sess.opts.optimize == config::OptLevel::Default
|| sess.opts.optimize == config::OptLevel::Aggressive),
vectorize_slp: !sess.opts.cg.no_vectorize_slp
&& sess.opts.optimize == config::OptLevel::Aggressive,
// Some targets (namely, NVPTX) interact badly with the
// MergeFunctions pass. This is because MergeFunctions can generate
// new function calls which may interfere with the target calling
// convention; e.g. for the NVPTX target, PTX kernels should not
// call other PTX kernels. MergeFunctions can also be configured to
// generate aliases instead, but aliases are not supported by some
// backends (again, NVPTX). Therefore, allow targets to opt out of
// the MergeFunctions pass, but otherwise keep the pass enabled (at
// O2 and O3) since it can be useful for reducing code size.
merge_functions: match sess
.opts
.debugging_opts
.merge_functions
.unwrap_or(sess.target.merge_functions)
{
MergeFunctions::Disabled => false,
MergeFunctions::Trampolines | MergeFunctions::Aliases => {
sess.opts.optimize == config::OptLevel::Default
|| sess.opts.optimize == config::OptLevel::Aggressive
}
},
inline_threshold: sess.opts.cg.inline_threshold,
new_llvm_pass_manager: sess.opts.debugging_opts.new_llvm_pass_manager,
emit_lifetime_markers: sess.emit_lifetime_markers(),
}
}
pub fn bitcode_needed(&self) -> bool {
self.emit_bc
|| self.emit_obj == EmitObj::Bitcode
|| self.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full)
}
}
/// Configuration passed to the function returned by the `target_machine_factory`.
pub struct TargetMachineFactoryConfig {
/// Split DWARF is enabled in LLVM by checking that `TM.MCOptions.SplitDwarfFile` isn't empty,
/// so the path to the dwarf object has to be provided when we create the target machine.
/// This can be ignored by backends which do not need it for their Split DWARF support.
pub split_dwarf_file: Option<PathBuf>,
}
impl TargetMachineFactoryConfig {
pub fn new(
cgcx: &CodegenContext<impl WriteBackendMethods>,
module_name: &str,
) -> TargetMachineFactoryConfig {
let split_dwarf_file = if cgcx.target_can_use_split_dwarf {
cgcx.output_filenames.split_dwarf_path(cgcx.split_debuginfo, Some(module_name))
} else {
None
};
TargetMachineFactoryConfig { split_dwarf_file }
}
}
pub type TargetMachineFactoryFn<B> = Arc<
dyn Fn(TargetMachineFactoryConfig) -> Result<<B as WriteBackendMethods>::TargetMachine, String>
+ Send
+ Sync,
>;
pub type ExportedSymbols = FxHashMap<CrateNum, Arc<Vec<(String, SymbolExportLevel)>>>;
/// Additional resources used by optimize_and_codegen (not module specific)
#[derive(Clone)]
pub struct CodegenContext<B: WriteBackendMethods> {
// Resources needed when running LTO
pub backend: B,
pub prof: SelfProfilerRef,
pub lto: Lto,
pub no_landing_pads: bool,
pub save_temps: bool,
pub fewer_names: bool,
pub exported_symbols: Option<Arc<ExportedSymbols>>,
pub opts: Arc<config::Options>,
pub crate_types: Vec<CrateType>,
pub each_linked_rlib_for_lto: Vec<(CrateNum, PathBuf)>,
pub output_filenames: Arc<OutputFilenames>,
pub regular_module_config: Arc<ModuleConfig>,
pub metadata_module_config: Arc<ModuleConfig>,
pub allocator_module_config: Arc<ModuleConfig>,
pub tm_factory: TargetMachineFactoryFn<B>,
pub msvc_imps_needed: bool,
pub is_pe_coff: bool,
pub target_can_use_split_dwarf: bool,
pub target_pointer_width: u32,
pub target_arch: String,
pub debuginfo: config::DebugInfo,
pub split_debuginfo: rustc_target::spec::SplitDebuginfo,
// Number of cgus excluding the allocator/metadata modules
pub total_cgus: usize,
// Handler to use for diagnostics produced during codegen.
pub diag_emitter: SharedEmitter,
// LLVM optimizations for which we want to print remarks.
pub remark: Passes,
// Worker thread number
pub worker: usize,
// The incremental compilation session directory, or None if we are not
// compiling incrementally
pub incr_comp_session_dir: Option<PathBuf>,
// Used to update CGU re-use information during the thinlto phase.
pub cgu_reuse_tracker: CguReuseTracker,
// Channel back to the main control thread to send messages to
pub coordinator_send: Sender<Box<dyn Any + Send>>,
}
impl<B: WriteBackendMethods> CodegenContext<B> {
pub fn create_diag_handler(&self) -> Handler {
Handler::with_emitter(true, None, Box::new(self.diag_emitter.clone()))
}
pub fn config(&self, kind: ModuleKind) -> &ModuleConfig {
match kind {
ModuleKind::Regular => &self.regular_module_config,
ModuleKind::Metadata => &self.metadata_module_config,
ModuleKind::Allocator => &self.allocator_module_config,
}
}
}
fn generate_lto_work<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
needs_fat_lto: Vec<FatLTOInput<B>>,
needs_thin_lto: Vec<(String, B::ThinBuffer)>,
import_only_modules: Vec<(SerializedModule<B::ModuleBuffer>, WorkProduct)>,
) -> Vec<(WorkItem<B>, u64)> {
let _prof_timer = cgcx.prof.generic_activity("codegen_generate_lto_work");
let (lto_modules, copy_jobs) = if !needs_fat_lto.is_empty() {
assert!(needs_thin_lto.is_empty());
let lto_module =
B::run_fat_lto(cgcx, needs_fat_lto, import_only_modules).unwrap_or_else(|e| e.raise());
(vec![lto_module], vec![])
} else {
assert!(needs_fat_lto.is_empty());
B::run_thin_lto(cgcx, needs_thin_lto, import_only_modules).unwrap_or_else(|e| e.raise())
};
lto_modules
.into_iter()
.map(|module| {
let cost = module.cost();
(WorkItem::LTO(module), cost)
})
.chain(copy_jobs.into_iter().map(|wp| {
(
WorkItem::CopyPostLtoArtifacts(CachedModuleCodegen {
name: wp.cgu_name.clone(),
source: wp,
}),
0,
)
}))
.collect()
}
pub struct CompiledModules {
pub modules: Vec<CompiledModule>,
pub metadata_module: Option<CompiledModule>,
pub allocator_module: Option<CompiledModule>,
}
fn need_bitcode_in_object(sess: &Session) -> bool {
let requested_for_rlib = sess.opts.cg.embed_bitcode
&& sess.crate_types().contains(&CrateType::Rlib)
&& sess.opts.output_types.contains_key(&OutputType::Exe);
let forced_by_target = sess.target.forces_embed_bitcode;
requested_for_rlib || forced_by_target
}
fn need_pre_lto_bitcode_for_incr_comp(sess: &Session) -> bool {
if sess.opts.incremental.is_none() {
return false;
}
match sess.lto() {
Lto::No => false,
Lto::Fat | Lto::Thin | Lto::ThinLocal => true,
}
}
pub fn start_async_codegen<B: ExtraBackendMethods>(
backend: B,
tcx: TyCtxt<'_>,
metadata: EncodedMetadata,
total_cgus: usize,
) -> OngoingCodegen<B> {
let (coordinator_send, coordinator_receive) = channel();
let sess = tcx.sess;
let crate_name = tcx.crate_name(LOCAL_CRATE);
let crate_attrs = tcx.hir().attrs(rustc_hir::CRATE_HIR_ID);
let no_builtins = tcx.sess.contains_name(crate_attrs, sym::no_builtins);
let is_compiler_builtins = tcx.sess.contains_name(crate_attrs, sym::compiler_builtins);
let subsystem = tcx.sess.first_attr_value_str_by_name(crate_attrs, sym::windows_subsystem);
let windows_subsystem = subsystem.map(|subsystem| {
if subsystem != sym::windows && subsystem != sym::console {
tcx.sess.fatal(&format!(
"invalid windows subsystem `{}`, only \
`windows` and `console` are allowed",
subsystem
));
}
subsystem.to_string()
});
let linker_info = LinkerInfo::new(tcx);
let crate_info = CrateInfo::new(tcx);
let regular_config =
ModuleConfig::new(ModuleKind::Regular, sess, no_builtins, is_compiler_builtins);
let metadata_config =
ModuleConfig::new(ModuleKind::Metadata, sess, no_builtins, is_compiler_builtins);
let allocator_config =
ModuleConfig::new(ModuleKind::Allocator, sess, no_builtins, is_compiler_builtins);
let (shared_emitter, shared_emitter_main) = SharedEmitter::new();
let (codegen_worker_send, codegen_worker_receive) = channel();
let coordinator_thread = start_executing_work(
backend.clone(),
tcx,
&crate_info,
shared_emitter,
codegen_worker_send,
coordinator_receive,
total_cgus,
sess.jobserver.clone(),
Arc::new(regular_config),
Arc::new(metadata_config),
Arc::new(allocator_config),
coordinator_send.clone(),
);
OngoingCodegen {
backend,
crate_name,
metadata,
windows_subsystem,
linker_info,
crate_info,
coordinator_send,
codegen_worker_receive,
shared_emitter_main,
future: coordinator_thread,
output_filenames: tcx.output_filenames(LOCAL_CRATE),
}
}
fn copy_all_cgu_workproducts_to_incr_comp_cache_dir(
sess: &Session,
compiled_modules: &CompiledModules,
) -> FxHashMap<WorkProductId, WorkProduct> {
let mut work_products = FxHashMap::default();
if sess.opts.incremental.is_none() {
return work_products;
}
let _timer = sess.timer("copy_all_cgu_workproducts_to_incr_comp_cache_dir");
for module in compiled_modules.modules.iter().filter(|m| m.kind == ModuleKind::Regular) {
let path = module.object.as_ref().cloned();
if let Some((id, product)) =
copy_cgu_workproduct_to_incr_comp_cache_dir(sess, &module.name, &path)
{
work_products.insert(id, product);
}
}
work_products
}
fn produce_final_output_artifacts(
sess: &Session,
compiled_modules: &CompiledModules,
crate_output: &OutputFilenames,
) {
let mut user_wants_bitcode = false;
let mut user_wants_objects = false;
// Produce final compile outputs.
let copy_gracefully = |from: &Path, to: &Path| {
if let Err(e) = fs::copy(from, to) {
sess.err(&format!("could not copy {:?} to {:?}: {}", from, to, e));
}
};
let copy_if_one_unit = |output_type: OutputType, keep_numbered: bool| {
if compiled_modules.modules.len() == 1 {
// 1) Only one codegen unit. In this case it's no difficulty
// to copy `foo.0.x` to `foo.x`.
let module_name = Some(&compiled_modules.modules[0].name[..]);
let path = crate_output.temp_path(output_type, module_name);
copy_gracefully(&path, &crate_output.path(output_type));
if !sess.opts.cg.save_temps && !keep_numbered {
// The user just wants `foo.x`, not `foo.#module-name#.x`.
ensure_removed(sess.diagnostic(), &path);
}
} else {
let ext = crate_output
.temp_path(output_type, None)
.extension()
.unwrap()
.to_str()
.unwrap()
.to_owned();
if crate_output.outputs.contains_key(&output_type) {
// 2) Multiple codegen units, with `--emit foo=some_name`. We have
// no good solution for this case, so warn the user.
sess.warn(&format!(
"ignoring emit path because multiple .{} files \
were produced",
ext
));
} else if crate_output.single_output_file.is_some() {
// 3) Multiple codegen units, with `-o some_name`. We have
// no good solution for this case, so warn the user.
sess.warn(&format!(
"ignoring -o because multiple .{} files \
were produced",
ext
));
} else {
// 4) Multiple codegen units, but no explicit name. We
// just leave the `foo.0.x` files in place.
// (We don't have to do any work in this case.)
}
}
};
// Flag to indicate whether the user explicitly requested bitcode.
// Otherwise, we produced it only as a temporary output, and will need
// to get rid of it.
for output_type in crate_output.outputs.keys() {
match *output_type {
OutputType::Bitcode => {
user_wants_bitcode = true;
// Copy to .bc, but always keep the .0.bc. There is a later
// check to figure out if we should delete .0.bc files, or keep
// them for making an rlib.
copy_if_one_unit(OutputType::Bitcode, true);
}
OutputType::LlvmAssembly => {
copy_if_one_unit(OutputType::LlvmAssembly, false);
}
OutputType::Assembly => {
copy_if_one_unit(OutputType::Assembly, false);
}
OutputType::Object => {
user_wants_objects = true;
copy_if_one_unit(OutputType::Object, true);
}
OutputType::Mir | OutputType::Metadata | OutputType::Exe | OutputType::DepInfo => {}
}
}
// Clean up unwanted temporary files.
// We create the following files by default:
// - #crate#.#module-name#.bc
// - #crate#.#module-name#.o
// - #crate#.crate.metadata.bc
// - #crate#.crate.metadata.o
// - #crate#.o (linked from crate.##.o)
// - #crate#.bc (copied from crate.##.bc)
// We may create additional files if requested by the user (through
// `-C save-temps` or `--emit=` flags).
if !sess.opts.cg.save_temps {
// Remove the temporary .#module-name#.o objects. If the user didn't
// explicitly request bitcode (with --emit=bc), and the bitcode is not
// needed for building an rlib, then we must remove .#module-name#.bc as
// well.
// Specific rules for keeping .#module-name#.bc:
// - If the user requested bitcode (`user_wants_bitcode`), and
// codegen_units > 1, then keep it.
// - If the user requested bitcode but codegen_units == 1, then we
// can toss .#module-name#.bc because we copied it to .bc earlier.
// - If we're not building an rlib and the user didn't request
// bitcode, then delete .#module-name#.bc.
// If you change how this works, also update back::link::link_rlib,
// where .#module-name#.bc files are (maybe) deleted after making an
// rlib.
let needs_crate_object = crate_output.outputs.contains_key(&OutputType::Exe);
let keep_numbered_bitcode = user_wants_bitcode && sess.codegen_units() > 1;
let keep_numbered_objects =
needs_crate_object || (user_wants_objects && sess.codegen_units() > 1);
for module in compiled_modules.modules.iter() {
if let Some(ref path) = module.object {
if !keep_numbered_objects {
ensure_removed(sess.diagnostic(), path);
}
}
if let Some(ref path) = module.dwarf_object {
if !keep_numbered_objects {
ensure_removed(sess.diagnostic(), path);
}
}
if let Some(ref path) = module.bytecode {
if !keep_numbered_bitcode {
ensure_removed(sess.diagnostic(), path);
}
}
}
if !user_wants_bitcode {
if let Some(ref metadata_module) = compiled_modules.metadata_module {
if let Some(ref path) = metadata_module.bytecode {
ensure_removed(sess.diagnostic(), &path);
}
}
if let Some(ref allocator_module) = compiled_modules.allocator_module {
if let Some(ref path) = allocator_module.bytecode {
ensure_removed(sess.diagnostic(), path);
}
}
}
}
// We leave the following files around by default:
// - #crate#.o
// - #crate#.crate.metadata.o
// - #crate#.bc
// These are used in linking steps and will be cleaned up afterward.
}
pub enum WorkItem<B: WriteBackendMethods> {
/// Optimize a newly codegened, totally unoptimized module.
Optimize(ModuleCodegen<B::Module>),
/// Copy the post-LTO artifacts from the incremental cache to the output
/// directory.
CopyPostLtoArtifacts(CachedModuleCodegen),
/// Performs (Thin)LTO on the given module.
LTO(lto::LtoModuleCodegen<B>),
}
impl<B: WriteBackendMethods> WorkItem<B> {
pub fn module_kind(&self) -> ModuleKind {
match *self {
WorkItem::Optimize(ref m) => m.kind,
WorkItem::CopyPostLtoArtifacts(_) | WorkItem::LTO(_) => ModuleKind::Regular,
}
}
fn start_profiling<'a>(&self, cgcx: &'a CodegenContext<B>) -> TimingGuard<'a> {
match *self {
WorkItem::Optimize(ref m) => {
cgcx.prof.generic_activity_with_arg("codegen_module_optimize", &m.name[..])
}
WorkItem::CopyPostLtoArtifacts(ref m) => cgcx
.prof
.generic_activity_with_arg("codegen_copy_artifacts_from_incr_cache", &m.name[..]),
WorkItem::LTO(ref m) => {
cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", m.name())
}
}
}
/// Generate a short description of this work item suitable for use as a thread name.
fn short_description(&self) -> String {
// `pthread_setname()` on *nix is limited to 15 characters and longer names are ignored.
// Use very short descriptions in this case to maximize the space available for the module name.
// Windows does not have that limitation so use slightly more descriptive names there.
match self {
WorkItem::Optimize(m) => {
#[cfg(windows)]
return format!("optimize module {}", m.name);
#[cfg(not(windows))]
return format!("opt {}", m.name);
}
WorkItem::CopyPostLtoArtifacts(m) => {
#[cfg(windows)]
return format!("copy LTO artifacts for {}", m.name);
#[cfg(not(windows))]
return format!("copy {}", m.name);
}
WorkItem::LTO(m) => {
#[cfg(windows)]
return format!("LTO module {}", m.name());
#[cfg(not(windows))]
return format!("LTO {}", m.name());
}
}
}
}
enum WorkItemResult<B: WriteBackendMethods> {
Compiled(CompiledModule),
NeedsLink(ModuleCodegen<B::Module>),
NeedsFatLTO(FatLTOInput<B>),
NeedsThinLTO(String, B::ThinBuffer),
}
pub enum FatLTOInput<B: WriteBackendMethods> {
Serialized { name: String, buffer: B::ModuleBuffer },
InMemory(ModuleCodegen<B::Module>),
}
fn execute_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
work_item: WorkItem<B>,
) -> Result<WorkItemResult<B>, FatalError> {
let module_config = cgcx.config(work_item.module_kind());
match work_item {
WorkItem::Optimize(module) => execute_optimize_work_item(cgcx, module, module_config),
WorkItem::CopyPostLtoArtifacts(module) => {
Ok(execute_copy_from_cache_work_item(cgcx, module, module_config))
}
WorkItem::LTO(module) => execute_lto_work_item(cgcx, module, module_config),
}
}
// Actual LTO type we end up choosing based on multiple factors.
pub enum ComputedLtoType {
No,
Thin,
Fat,
}
pub fn compute_per_cgu_lto_type(
sess_lto: &Lto,
opts: &config::Options,
sess_crate_types: &[CrateType],
module_kind: ModuleKind,
) -> ComputedLtoType {
// Metadata modules never participate in LTO regardless of the lto
// settings.
if module_kind == ModuleKind::Metadata {
return ComputedLtoType::No;
}
// If the linker does LTO, we don't have to do it. Note that we
// keep doing full LTO, if it is requested, as not to break the
// assumption that the output will be a single module.
let linker_does_lto = opts.cg.linker_plugin_lto.enabled();
// When we're automatically doing ThinLTO for multi-codegen-unit
// builds we don't actually want to LTO the allocator modules if
// it shows up. This is due to various linker shenanigans that
// we'll encounter later.
let is_allocator = module_kind == ModuleKind::Allocator;
// We ignore a request for full crate grath LTO if the cate type
// is only an rlib, as there is no full crate graph to process,
// that'll happen later.
//
// This use case currently comes up primarily for targets that
// require LTO so the request for LTO is always unconditionally
// passed down to the backend, but we don't actually want to do
// anything about it yet until we've got a final product.
let is_rlib = sess_crate_types.len() == 1 && sess_crate_types[0] == CrateType::Rlib;
match sess_lto {
Lto::ThinLocal if !linker_does_lto && !is_allocator => ComputedLtoType::Thin,
Lto::Thin if !linker_does_lto && !is_rlib => ComputedLtoType::Thin,
Lto::Fat if !is_rlib => ComputedLtoType::Fat,
_ => ComputedLtoType::No,
}
}
fn execute_optimize_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
module: ModuleCodegen<B::Module>,
module_config: &ModuleConfig,
) -> Result<WorkItemResult<B>, FatalError> {
let diag_handler = cgcx.create_diag_handler();
unsafe {
B::optimize(cgcx, &diag_handler, &module, module_config)?;
}
// After we've done the initial round of optimizations we need to
// decide whether to synchronously codegen this module or ship it
// back to the coordinator thread for further LTO processing (which
// has to wait for all the initial modules to be optimized).
let lto_type = compute_per_cgu_lto_type(&cgcx.lto, &cgcx.opts, &cgcx.crate_types, module.kind);
// If we're doing some form of incremental LTO then we need to be sure to
// save our module to disk first.
let bitcode = if cgcx.config(module.kind).emit_pre_lto_bc {
let filename = pre_lto_bitcode_filename(&module.name);
cgcx.incr_comp_session_dir.as_ref().map(|path| path.join(&filename))
} else {
None
};
match lto_type {
ComputedLtoType::No => finish_intra_module_work(cgcx, module, module_config),
ComputedLtoType::Thin => {
let (name, thin_buffer) = B::prepare_thin(module);
if let Some(path) = bitcode {
fs::write(&path, thin_buffer.data()).unwrap_or_else(|e| {
panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e);
});
}
Ok(WorkItemResult::NeedsThinLTO(name, thin_buffer))
}
ComputedLtoType::Fat => match bitcode {
Some(path) => {
let (name, buffer) = B::serialize_module(module);
fs::write(&path, buffer.data()).unwrap_or_else(|e| {
panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e);
});
Ok(WorkItemResult::NeedsFatLTO(FatLTOInput::Serialized { name, buffer }))
}
None => Ok(WorkItemResult::NeedsFatLTO(FatLTOInput::InMemory(module))),
},
}
}
fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
module: CachedModuleCodegen,
module_config: &ModuleConfig,
) -> WorkItemResult<B> {
let incr_comp_session_dir = cgcx.incr_comp_session_dir.as_ref().unwrap();
let mut object = None;
if let Some(saved_file) = module.source.saved_file {
let obj_out = cgcx.output_filenames.temp_path(OutputType::Object, Some(&module.name));
object = Some(obj_out.clone());
let source_file = in_incr_comp_dir(&incr_comp_session_dir, &saved_file);
debug!(
"copying pre-existing module `{}` from {:?} to {}",
module.name,
source_file,
obj_out.display()
);
if let Err(err) = link_or_copy(&source_file, &obj_out) {
let diag_handler = cgcx.create_diag_handler();
diag_handler.err(&format!(
"unable to copy {} to {}: {}",
source_file.display(),
obj_out.display(),
err
));
}
}
assert_eq!(object.is_some(), module_config.emit_obj != EmitObj::None);
WorkItemResult::Compiled(CompiledModule {
name: module.name,
kind: ModuleKind::Regular,
object,
dwarf_object: None,
bytecode: None,
})
}
fn execute_lto_work_item<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
mut module: lto::LtoModuleCodegen<B>,
module_config: &ModuleConfig,
) -> Result<WorkItemResult<B>, FatalError> {
let module = unsafe { module.optimize(cgcx)? };
finish_intra_module_work(cgcx, module, module_config)
}
fn finish_intra_module_work<B: ExtraBackendMethods>(
cgcx: &CodegenContext<B>,
module: ModuleCodegen<B::Module>,
module_config: &ModuleConfig,
) -> Result<WorkItemResult<B>, FatalError> {
let diag_handler = cgcx.create_diag_handler();
if !cgcx.opts.debugging_opts.combine_cgu
|| module.kind == ModuleKind::Metadata
|| module.kind == ModuleKind::Allocator
{
let module = unsafe { B::codegen(cgcx, &diag_handler, module, module_config)? };
Ok(WorkItemResult::Compiled(module))
} else {
Ok(WorkItemResult::NeedsLink(module))
}
}
pub enum Message<B: WriteBackendMethods> {
Token(io::Result<Acquired>),
NeedsFatLTO {
result: FatLTOInput<B>,
worker_id: usize,
},
NeedsThinLTO {
name: String,
thin_buffer: B::ThinBuffer,
worker_id: usize,
},
NeedsLink {
module: ModuleCodegen<B::Module>,
worker_id: usize,
},
Done {
result: Result<CompiledModule, Option<WorkerFatalError>>,
worker_id: usize,
},
CodegenDone {
llvm_work_item: WorkItem<B>,
cost: u64,
},
AddImportOnlyModule {
module_data: SerializedModule<B::ModuleBuffer>,
work_product: WorkProduct,
},
CodegenComplete,
CodegenItem,
CodegenAborted,
}
struct Diagnostic {
msg: String,
code: Option<DiagnosticId>,
lvl: Level,
}
#[derive(PartialEq, Clone, Copy, Debug)]
enum MainThreadWorkerState {
Idle,
Codegenning,
LLVMing,
}
fn start_executing_work<B: ExtraBackendMethods>(
backend: B,
tcx: TyCtxt<'_>,
crate_info: &CrateInfo,
shared_emitter: SharedEmitter,
codegen_worker_send: Sender<Message<B>>,
coordinator_receive: Receiver<Box<dyn Any + Send>>,
total_cgus: usize,
jobserver: Client,
regular_config: Arc<ModuleConfig>,
metadata_config: Arc<ModuleConfig>,
allocator_config: Arc<ModuleConfig>,
tx_to_llvm_workers: Sender<Box<dyn Any + Send>>,
) -> thread::JoinHandle<Result<CompiledModules, ()>> {
let coordinator_send = tx_to_llvm_workers;
let sess = tcx.sess;
// Compute the set of symbols we need to retain when doing LTO (if we need to)
let exported_symbols = {
let mut exported_symbols = FxHashMap::default();
let copy_symbols = |cnum| {
let symbols = tcx
.exported_symbols(cnum)
.iter()
.map(|&(s, lvl)| (symbol_name_for_instance_in_crate(tcx, s, cnum), lvl))
.collect();
Arc::new(symbols)
};
match sess.lto() {
Lto::No => None,
Lto::ThinLocal => {
exported_symbols.insert(LOCAL_CRATE, copy_symbols(LOCAL_CRATE));
Some(Arc::new(exported_symbols))
}
Lto::Fat | Lto::Thin => {
exported_symbols.insert(LOCAL_CRATE, copy_symbols(LOCAL_CRATE));
for &cnum in tcx.crates().iter() {
exported_symbols.insert(cnum, copy_symbols(cnum));
}
Some(Arc::new(exported_symbols))
}
}
};
// First up, convert our jobserver into a helper thread so we can use normal
// mpsc channels to manage our messages and such.
// After we've requested tokens then we'll, when we can,
// get tokens on `coordinator_receive` which will
// get managed in the main loop below.
let coordinator_send2 = coordinator_send.clone();
let helper = jobserver
.into_helper_thread(move |token| {
drop(coordinator_send2.send(Box::new(Message::Token::<B>(token))));
})
.expect("failed to spawn helper thread");
let mut each_linked_rlib_for_lto = Vec::new();
drop(link::each_linked_rlib(crate_info, &mut |cnum, path| {
if link::ignored_for_lto(sess, crate_info, cnum) {
return;
}
each_linked_rlib_for_lto.push((cnum, path.to_path_buf()));
}));
let ol = if tcx.sess.opts.debugging_opts.no_codegen
|| !tcx.sess.opts.output_types.should_codegen()
{
// If we know that we wont be doing codegen, create target machines without optimisation.
config::OptLevel::No
} else {
tcx.backend_optimization_level(LOCAL_CRATE)
};
let cgcx = CodegenContext::<B> {
backend: backend.clone(),
crate_types: sess.crate_types().to_vec(),
each_linked_rlib_for_lto,
lto: sess.lto(),
no_landing_pads: sess.panic_strategy() == PanicStrategy::Abort,
fewer_names: sess.fewer_names(),
save_temps: sess.opts.cg.save_temps,
opts: Arc::new(sess.opts.clone()),
prof: sess.prof.clone(),
exported_symbols,
remark: sess.opts.cg.remark.clone(),
worker: 0,
incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()),
cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(),
coordinator_send,
diag_emitter: shared_emitter.clone(),
output_filenames: tcx.output_filenames(LOCAL_CRATE),
regular_module_config: regular_config,
metadata_module_config: metadata_config,
allocator_module_config: allocator_config,
tm_factory: backend.target_machine_factory(tcx.sess, ol),
total_cgus,
msvc_imps_needed: msvc_imps_needed(tcx),
is_pe_coff: tcx.sess.target.is_like_windows,
target_can_use_split_dwarf: tcx.sess.target_can_use_split_dwarf(),
target_pointer_width: tcx.sess.target.pointer_width,
target_arch: tcx.sess.target.arch.clone(),
debuginfo: tcx.sess.opts.debuginfo,
split_debuginfo: tcx.sess.split_debuginfo(),
};
// This is the "main loop" of parallel work happening for parallel codegen.
// It's here that we manage parallelism, schedule work, and work with
// messages coming from clients.
//
// There are a few environmental pre-conditions that shape how the system
// is set up:
//
// - Error reporting only can happen on the main thread because that's the
// only place where we have access to the compiler `Session`.
// - LLVM work can be done on any thread.
// - Codegen can only happen on the main thread.
// - Each thread doing substantial work most be in possession of a `Token`
// from the `Jobserver`.
// - The compiler process always holds one `Token`. Any additional `Tokens`
// have to be requested from the `Jobserver`.
//
// Error Reporting
// ===============
// The error reporting restriction is handled separately from the rest: We
// set up a `SharedEmitter` the holds an open channel to the main thread.
// When an error occurs on any thread, the shared emitter will send the
// error message to the receiver main thread (`SharedEmitterMain`). The
// main thread will periodically query this error message queue and emit
// any error messages it has received. It might even abort compilation if
// has received a fatal error. In this case we rely on all other threads
// being torn down automatically with the main thread.
// Since the main thread will often be busy doing codegen work, error
// reporting will be somewhat delayed, since the message queue can only be
// checked in between to work packages.
//
// Work Processing Infrastructure
// ==============================
// The work processing infrastructure knows three major actors:
//
// - the coordinator thread,
// - the main thread, and
// - LLVM worker threads
//
// The coordinator thread is running a message loop. It instructs the main
// thread about what work to do when, and it will spawn off LLVM worker
// threads as open LLVM WorkItems become available.
//
// The job of the main thread is to codegen CGUs into LLVM work package
// (since the main thread is the only thread that can do this). The main
// thread will block until it receives a message from the coordinator, upon
// which it will codegen one CGU, send it to the coordinator and block
// again. This way the coordinator can control what the main thread is
// doing.
//
// The coordinator keeps a queue of LLVM WorkItems, and when a `Token` is
// available, it will spawn off a new LLVM worker thread and let it process
// that a WorkItem. When a LLVM worker thread is done with its WorkItem,
// it will just shut down, which also frees all resources associated with
// the given LLVM module, and sends a message to the coordinator that the
// has been completed.
//
// Work Scheduling
// ===============
// The scheduler's goal is to minimize the time it takes to complete all
// work there is, however, we also want to keep memory consumption low
// if possible. These two goals are at odds with each other: If memory
// consumption were not an issue, we could just let the main thread produce
// LLVM WorkItems at full speed, assuring maximal utilization of
// Tokens/LLVM worker threads. However, since codegen usual is faster
// than LLVM processing, the queue of LLVM WorkItems would fill up and each
// WorkItem potentially holds on to a substantial amount of memory.
//
// So the actual goal is to always produce just enough LLVM WorkItems as
// not to starve our LLVM worker threads. That means, once we have enough
// WorkItems in our queue, we can block the main thread, so it does not
// produce more until we need them.
//
// Doing LLVM Work on the Main Thread
// ----------------------------------
// Since the main thread owns the compiler processes implicit `Token`, it is
// wasteful to keep it blocked without doing any work. Therefore, what we do
// in this case is: We spawn off an additional LLVM worker thread that helps
// reduce the queue. The work it is doing corresponds to the implicit
// `Token`. The coordinator will mark the main thread as being busy with
// LLVM work. (The actual work happens on another OS thread but we just care
// about `Tokens`, not actual threads).
//
// When any LLVM worker thread finishes while the main thread is marked as
// "busy with LLVM work", we can do a little switcheroo: We give the Token
// of the just finished thread to the LLVM worker thread that is working on
// behalf of the main thread's implicit Token, thus freeing up the main
// thread again. The coordinator can then again decide what the main thread
// should do. This allows the coordinator to make decisions at more points
// in time.
//
// Striking a Balance between Throughput and Memory Consumption
// ------------------------------------------------------------
// Since our two goals, (1) use as many Tokens as possible and (2) keep
// memory consumption as low as possible, are in conflict with each other,
// we have to find a trade off between them. Right now, the goal is to keep
// all workers busy, which means that no worker should find the queue empty
// when it is ready to start.
// How do we do achieve this? Good question :) We actually never know how
// many `Tokens` are potentially available so it's hard to say how much to
// fill up the queue before switching the main thread to LLVM work. Also we
// currently don't have a means to estimate how long a running LLVM worker
// will still be busy with it's current WorkItem. However, we know the
// maximal count of available Tokens that makes sense (=the number of CPU
// cores), so we can take a conservative guess. The heuristic we use here
// is implemented in the `queue_full_enough()` function.
//
// Some Background on Jobservers
// -----------------------------
// It's worth also touching on the management of parallelism here. We don't
// want to just spawn a thread per work item because while that's optimal
// parallelism it may overload a system with too many threads or violate our
// configuration for the maximum amount of cpu to use for this process. To
// manage this we use the `jobserver` crate.
//
// Job servers are an artifact of GNU make and are used to manage
// parallelism between processes. A jobserver is a glorified IPC semaphore
// basically. Whenever we want to run some work we acquire the semaphore,
// and whenever we're done with that work we release the semaphore. In this
// manner we can ensure that the maximum number of parallel workers is
// capped at any one point in time.
//
// LTO and the coordinator thread
// ------------------------------
//
// The final job the coordinator thread is responsible for is managing LTO
// and how that works. When LTO is requested what we'll to is collect all
// optimized LLVM modules into a local vector on the coordinator. Once all
// modules have been codegened and optimized we hand this to the `lto`
// module for further optimization. The `lto` module will return back a list
// of more modules to work on, which the coordinator will continue to spawn
// work for.
//
// Each LLVM module is automatically sent back to the coordinator for LTO if
// necessary. There's already optimizations in place to avoid sending work
// back to the coordinator if LTO isn't requested.
return thread::spawn(move || {
let mut worker_id_counter = 0;
let mut free_worker_ids = Vec::new();
let mut get_worker_id = |free_worker_ids: &mut Vec<usize>| {
if let Some(id) = free_worker_ids.pop() {
id
} else {
let id = worker_id_counter;
worker_id_counter += 1;
id
}
};
// This is where we collect codegen units that have gone all the way
// through codegen and LLVM.
let mut compiled_modules = vec![];
let mut compiled_metadata_module = None;
let mut compiled_allocator_module = None;
let mut needs_link = Vec::new();
let mut needs_fat_lto = Vec::new();
let mut needs_thin_lto = Vec::new();
let mut lto_import_only_modules = Vec::new();
let mut started_lto = false;
let mut codegen_aborted = false;
// This flag tracks whether all items have gone through codegens
let mut codegen_done = false;
// This is the queue of LLVM work items that still need processing.
let mut work_items = Vec::<(WorkItem<B>, u64)>::new();
// This are the Jobserver Tokens we currently hold. Does not include
// the implicit Token the compiler process owns no matter what.
let mut tokens = Vec::new();
let mut main_thread_worker_state = MainThreadWorkerState::Idle;
let mut running = 0;
let prof = &cgcx.prof;
let mut llvm_start_time: Option<VerboseTimingGuard<'_>> = None;
// Run the message loop while there's still anything that needs message
// processing. Note that as soon as codegen is aborted we simply want to
// wait for all existing work to finish, so many of the conditions here
// only apply if codegen hasn't been aborted as they represent pending
// work to be done.
while !codegen_done
|| running > 0
|| (!codegen_aborted
&& !(work_items.is_empty()
&& needs_fat_lto.is_empty()
&& needs_thin_lto.is_empty()
&& lto_import_only_modules.is_empty()
&& main_thread_worker_state == MainThreadWorkerState::Idle))
{
// While there are still CGUs to be codegened, the coordinator has
// to decide how to utilize the compiler processes implicit Token:
// For codegenning more CGU or for running them through LLVM.
if !codegen_done {
if main_thread_worker_state == MainThreadWorkerState::Idle {
// Compute the number of workers that will be running once we've taken as many
// items from the work queue as we can, plus one for the main thread. It's not
// critically important that we use this instead of just `running`, but it
// prevents the `queue_full_enough` heuristic from fluctuating just because a
// worker finished up and we decreased the `running` count, even though we're
// just going to increase it right after this when we put a new worker to work.
let extra_tokens = tokens.len().checked_sub(running).unwrap();
let additional_running = std::cmp::min(extra_tokens, work_items.len());
let anticipated_running = running + additional_running + 1;
if !queue_full_enough(work_items.len(), anticipated_running) {
// The queue is not full enough, codegen more items:
if codegen_worker_send.send(Message::CodegenItem).is_err() {
panic!("Could not send Message::CodegenItem to main thread")
}
main_thread_worker_state = MainThreadWorkerState::Codegenning;
} else {
// The queue is full enough to not let the worker
// threads starve. Use the implicit Token to do some
// LLVM work too.
let (item, _) =
work_items.pop().expect("queue empty - queue_full_enough() broken?");
let cgcx = CodegenContext {
worker: get_worker_id(&mut free_worker_ids),
..cgcx.clone()
};
maybe_start_llvm_timer(
prof,
cgcx.config(item.module_kind()),
&mut llvm_start_time,
);
main_thread_worker_state = MainThreadWorkerState::LLVMing;
spawn_work(cgcx, item);
}
}
} else if codegen_aborted {
// don't queue up any more work if codegen was aborted, we're
// just waiting for our existing children to finish
} else {
// If we've finished everything related to normal codegen
// then it must be the case that we've got some LTO work to do.
// Perform the serial work here of figuring out what we're
// going to LTO and then push a bunch of work items onto our
// queue to do LTO
if work_items.is_empty()
&& running == 0
&& main_thread_worker_state == MainThreadWorkerState::Idle
{
assert!(!started_lto);
started_lto = true;
let needs_fat_lto = mem::take(&mut needs_fat_lto);
let needs_thin_lto = mem::take(&mut needs_thin_lto);
let import_only_modules = mem::take(&mut lto_import_only_modules);
for (work, cost) in
generate_lto_work(&cgcx, needs_fat_lto, needs_thin_lto, import_only_modules)
{
let insertion_index = work_items
.binary_search_by_key(&cost, |&(_, cost)| cost)
.unwrap_or_else(|e| e);
work_items.insert(insertion_index, (work, cost));
if !cgcx.opts.debugging_opts.no_parallel_llvm {
helper.request_token();
}
}
}
// In this branch, we know that everything has been codegened,
// so it's just a matter of determining whether the implicit
// Token is free to use for LLVM work.
match main_thread_worker_state {
MainThreadWorkerState::Idle => {
if let Some((item, _)) = work_items.pop() {
let cgcx = CodegenContext {
worker: get_worker_id(&mut free_worker_ids),
..cgcx.clone()
};
maybe_start_llvm_timer(
prof,
cgcx.config(item.module_kind()),
&mut llvm_start_time,
);
main_thread_worker_state = MainThreadWorkerState::LLVMing;
spawn_work(cgcx, item);
} else {
// There is no unstarted work, so let the main thread
// take over for a running worker. Otherwise the
// implicit token would just go to waste.
// We reduce the `running` counter by one. The
// `tokens.truncate()` below will take care of
// giving the Token back.
debug_assert!(running > 0);
running -= 1;
main_thread_worker_state = MainThreadWorkerState::LLVMing;
}
}
MainThreadWorkerState::Codegenning => bug!(
"codegen worker should not be codegenning after \
codegen was already completed"
),
MainThreadWorkerState::LLVMing => {
// Already making good use of that token
}
}
}
// Spin up what work we can, only doing this while we've got available
// parallelism slots and work left to spawn.
while !codegen_aborted && !work_items.is_empty() && running < tokens.len() {
let (item, _) = work_items.pop().unwrap();
maybe_start_llvm_timer(prof, cgcx.config(item.module_kind()), &mut llvm_start_time);
let cgcx =
CodegenContext { worker: get_worker_id(&mut free_worker_ids), ..cgcx.clone() };
spawn_work(cgcx, item);
running += 1;
}
// Relinquish accidentally acquired extra tokens
tokens.truncate(running);
// If a thread exits successfully then we drop a token associated
// with that worker and update our `running` count. We may later
// re-acquire a token to continue running more work. We may also not
// actually drop a token here if the worker was running with an
// "ephemeral token"
let mut free_worker = |worker_id| {
if main_thread_worker_state == MainThreadWorkerState::LLVMing {
main_thread_worker_state = MainThreadWorkerState::Idle;
} else {
running -= 1;
}
free_worker_ids.push(worker_id);
};
let msg = coordinator_receive.recv().unwrap();
match *msg.downcast::<Message<B>>().ok().unwrap() {
// Save the token locally and the next turn of the loop will use
// this to spawn a new unit of work, or it may get dropped
// immediately if we have no more work to spawn.
Message::Token(token) => {
match token {
Ok(token) => {
tokens.push(token);
if main_thread_worker_state == MainThreadWorkerState::LLVMing {
// If the main thread token is used for LLVM work
// at the moment, we turn that thread into a regular
// LLVM worker thread, so the main thread is free
// to react to codegen demand.
main_thread_worker_state = MainThreadWorkerState::Idle;
running += 1;
}
}
Err(e) => {
let msg = &format!("failed to acquire jobserver token: {}", e);
shared_emitter.fatal(msg);
// Exit the coordinator thread
panic!("{}", msg)
}
}
}
Message::CodegenDone { llvm_work_item, cost } => {
// We keep the queue sorted by estimated processing cost,
// so that more expensive items are processed earlier. This
// is good for throughput as it gives the main thread more
// time to fill up the queue and it avoids scheduling
// expensive items to the end.
// Note, however, that this is not ideal for memory
// consumption, as LLVM module sizes are not evenly
// distributed.
let insertion_index = work_items.binary_search_by_key(&cost, |&(_, cost)| cost);
let insertion_index = match insertion_index {
Ok(idx) | Err(idx) => idx,
};
work_items.insert(insertion_index, (llvm_work_item, cost));
if !cgcx.opts.debugging_opts.no_parallel_llvm {
helper.request_token();
}
assert!(!codegen_aborted);
assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning);
main_thread_worker_state = MainThreadWorkerState::Idle;
}
Message::CodegenComplete => {
codegen_done = true;
assert!(!codegen_aborted);
assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning);
main_thread_worker_state = MainThreadWorkerState::Idle;
}
// If codegen is aborted that means translation was aborted due
// to some normal-ish compiler error. In this situation we want
// to exit as soon as possible, but we want to make sure all
// existing work has finished. Flag codegen as being done, and
// then conditions above will ensure no more work is spawned but
// we'll keep executing this loop until `running` hits 0.
Message::CodegenAborted => {
assert!(!codegen_aborted);
codegen_done = true;
codegen_aborted = true;
assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning);
}
Message::Done { result: Ok(compiled_module), worker_id } => {
free_worker(worker_id);
match compiled_module.kind {
ModuleKind::Regular => {
compiled_modules.push(compiled_module);
}
ModuleKind::Metadata => {
assert!(compiled_metadata_module.is_none());
compiled_metadata_module = Some(compiled_module);
}
ModuleKind::Allocator => {
assert!(compiled_allocator_module.is_none());
compiled_allocator_module = Some(compiled_module);
}
}
}
Message::NeedsLink { module, worker_id } => {
free_worker(worker_id);
needs_link.push(module);
}
Message::NeedsFatLTO { result, worker_id } => {
assert!(!started_lto);
free_worker(worker_id);
needs_fat_lto.push(result);
}
Message::NeedsThinLTO { name, thin_buffer, worker_id } => {
assert!(!started_lto);
free_worker(worker_id);
needs_thin_lto.push((name, thin_buffer));
}
Message::AddImportOnlyModule { module_data, work_product } => {
assert!(!started_lto);
assert!(!codegen_done);
assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning);
lto_import_only_modules.push((module_data, work_product));
main_thread_worker_state = MainThreadWorkerState::Idle;
}
// If the thread failed that means it panicked, so we abort immediately.
Message::Done { result: Err(None), worker_id: _ } => {
bug!("worker thread panicked");
}
Message::Done { result: Err(Some(WorkerFatalError)), worker_id: _ } => {
return Err(());
}
Message::CodegenItem => bug!("the coordinator should not receive codegen requests"),
}
}
let needs_link = mem::take(&mut needs_link);
if !needs_link.is_empty() {
assert!(compiled_modules.is_empty());
let diag_handler = cgcx.create_diag_handler();
let module = B::run_link(&cgcx, &diag_handler, needs_link).map_err(|_| ())?;
let module = unsafe {
B::codegen(&cgcx, &diag_handler, module, cgcx.config(ModuleKind::Regular))
.map_err(|_| ())?
};
compiled_modules.push(module);
}
// Drop to print timings
drop(llvm_start_time);
// Regardless of what order these modules completed in, report them to
// the backend in the same order every time to ensure that we're handing
// out deterministic results.
compiled_modules.sort_by(|a, b| a.name.cmp(&b.name));
Ok(CompiledModules {
modules: compiled_modules,
metadata_module: compiled_metadata_module,
allocator_module: compiled_allocator_module,
})
});
// A heuristic that determines if we have enough LLVM WorkItems in the
// queue so that the main thread can do LLVM work instead of codegen
fn queue_full_enough(items_in_queue: usize, workers_running: usize) -> bool {
// This heuristic scales ahead-of-time codegen according to available
// concurrency, as measured by `workers_running`. The idea is that the
// more concurrency we have available, the more demand there will be for
// work items, and the fuller the queue should be kept to meet demand.
// An important property of this approach is that we codegen ahead of
// time only as much as necessary, so as to keep fewer LLVM modules in
// memory at once, thereby reducing memory consumption.
//
// When the number of workers running is less than the max concurrency
// available to us, this heuristic can cause us to instruct the main
// thread to work on an LLVM item (that is, tell it to "LLVM") instead
// of codegen, even though it seems like it *should* be codegenning so
// that we can create more work items and spawn more LLVM workers.
//
// But this is not a problem. When the main thread is told to LLVM,
// according to this heuristic and how work is scheduled, there is
// always at least one item in the queue, and therefore at least one
// pending jobserver token request. If there *is* more concurrency
// available, we will immediately receive a token, which will upgrade
// the main thread's LLVM worker to a real one (conceptually), and free
// up the main thread to codegen if necessary. On the other hand, if
// there isn't more concurrency, then the main thread working on an LLVM
// item is appropriate, as long as the queue is full enough for demand.
//
// Speaking of which, how full should we keep the queue? Probably less
// full than you'd think. A lot has to go wrong for the queue not to be
// full enough and for that to have a negative effect on compile times.
//
// Workers are unlikely to finish at exactly the same time, so when one
// finishes and takes another work item off the queue, we often have
// ample time to codegen at that point before the next worker finishes.
// But suppose that codegen takes so long that the workers exhaust the
// queue, and we have one or more workers that have nothing to work on.
// Well, it might not be so bad. Of all the LLVM modules we create and
// optimize, one has to finish last. It's not necessarily the case that
// by losing some concurrency for a moment, we delay the point at which
// that last LLVM module is finished and the rest of compilation can
// proceed. Also, when we can't take advantage of some concurrency, we
// give tokens back to the job server. That enables some other rustc to
// potentially make use of the available concurrency. That could even
// *decrease* overall compile time if we're lucky. But yes, if no other
// rustc can make use of the concurrency, then we've squandered it.
//
// However, keeping the queue full is also beneficial when we have a
// surge in available concurrency. Then items can be taken from the
// queue immediately, without having to wait for codegen.
//
// So, the heuristic below tries to keep one item in the queue for every
// four running workers. Based on limited benchmarking, this appears to
// be more than sufficient to avoid increasing compilation times.
let quarter_of_workers = workers_running - 3 * workers_running / 4;
items_in_queue > 0 && items_in_queue >= quarter_of_workers
}
fn maybe_start_llvm_timer<'a>(
prof: &'a SelfProfilerRef,
config: &ModuleConfig,
llvm_start_time: &mut Option<VerboseTimingGuard<'a>>,
) {
if config.time_module && llvm_start_time.is_none() {
*llvm_start_time = Some(prof.extra_verbose_generic_activity("LLVM_passes", "crate"));
}
}
}
/// `FatalError` is explicitly not `Send`.
#[must_use]
pub struct WorkerFatalError;
fn spawn_work<B: ExtraBackendMethods>(cgcx: CodegenContext<B>, work: WorkItem<B>) {
let builder = thread::Builder::new().name(work.short_description());
builder
.spawn(move || {
// Set up a destructor which will fire off a message that we're done as
// we exit.
struct Bomb<B: ExtraBackendMethods> {
coordinator_send: Sender<Box<dyn Any + Send>>,
result: Option<Result<WorkItemResult<B>, FatalError>>,
worker_id: usize,
}
impl<B: ExtraBackendMethods> Drop for Bomb<B> {
fn drop(&mut self) {
let worker_id = self.worker_id;
let msg = match self.result.take() {
Some(Ok(WorkItemResult::Compiled(m))) => {
Message::Done::<B> { result: Ok(m), worker_id }
}
Some(Ok(WorkItemResult::NeedsLink(m))) => {
Message::NeedsLink::<B> { module: m, worker_id }
}
Some(Ok(WorkItemResult::NeedsFatLTO(m))) => {
Message::NeedsFatLTO::<B> { result: m, worker_id }
}
Some(Ok(WorkItemResult::NeedsThinLTO(name, thin_buffer))) => {
Message::NeedsThinLTO::<B> { name, thin_buffer, worker_id }
}
Some(Err(FatalError)) => {
Message::Done::<B> { result: Err(Some(WorkerFatalError)), worker_id }
}
None => Message::Done::<B> { result: Err(None), worker_id },
};
drop(self.coordinator_send.send(Box::new(msg)));
}
}
let mut bomb = Bomb::<B> {
coordinator_send: cgcx.coordinator_send.clone(),
result: None,
worker_id: cgcx.worker,
};
// Execute the work itself, and if it finishes successfully then flag
// ourselves as a success as well.
//
// Note that we ignore any `FatalError` coming out of `execute_work_item`,
// as a diagnostic was already sent off to the main thread - just
// surface that there was an error in this worker.
bomb.result = {
let _prof_timer = work.start_profiling(&cgcx);
Some(execute_work_item(&cgcx, work))
};
})
.expect("failed to spawn thread");
}
enum SharedEmitterMessage {
Diagnostic(Diagnostic),
InlineAsmError(u32, String, Level, Option<(String, Vec<InnerSpan>)>),
AbortIfErrors,
Fatal(String),
}
#[derive(Clone)]
pub struct SharedEmitter {
sender: Sender<SharedEmitterMessage>,
}
pub struct SharedEmitterMain {
receiver: Receiver<SharedEmitterMessage>,
}
impl SharedEmitter {
pub fn new() -> (SharedEmitter, SharedEmitterMain) {
let (sender, receiver) = channel();
(SharedEmitter { sender }, SharedEmitterMain { receiver })
}
pub fn inline_asm_error(
&self,
cookie: u32,
msg: String,
level: Level,
source: Option<(String, Vec<InnerSpan>)>,
) {
drop(self.sender.send(SharedEmitterMessage::InlineAsmError(cookie, msg, level, source)));
}
pub fn fatal(&self, msg: &str) {
drop(self.sender.send(SharedEmitterMessage::Fatal(msg.to_string())));
}
}
impl Emitter for SharedEmitter {
fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) {
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
msg: diag.message(),
code: diag.code.clone(),
lvl: diag.level,
})));
for child in &diag.children {
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
msg: child.message(),
code: None,
lvl: child.level,
})));
}
drop(self.sender.send(SharedEmitterMessage::AbortIfErrors));
}
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
None
}
}
impl SharedEmitterMain {
pub fn check(&self, sess: &Session, blocking: bool) {
loop {
let message = if blocking {
match self.receiver.recv() {
Ok(message) => Ok(message),
Err(_) => Err(()),
}
} else {
match self.receiver.try_recv() {
Ok(message) => Ok(message),
Err(_) => Err(()),
}
};
match message {
Ok(SharedEmitterMessage::Diagnostic(diag)) => {
let handler = sess.diagnostic();
let mut d = rustc_errors::Diagnostic::new(diag.lvl, &diag.msg);
if let Some(code) = diag.code {
d.code(code);
}
handler.emit_diagnostic(&d);
}
Ok(SharedEmitterMessage::InlineAsmError(cookie, msg, level, source)) => {
let msg = msg.strip_prefix("error: ").unwrap_or(&msg);
let mut err = match level {
Level::Error => sess.struct_err(&msg),
Level::Warning => sess.struct_warn(&msg),
Level::Note => sess.struct_note_without_error(&msg),
_ => bug!("Invalid inline asm diagnostic level"),
};
// If the cookie is 0 then we don't have span information.
if cookie != 0 {
let pos = BytePos::from_u32(cookie);
let span = Span::with_root_ctxt(pos, pos);
err.set_span(span);
};
// Point to the generated assembly if it is available.
if let Some((buffer, spans)) = source {
let source = sess
.source_map()
.new_source_file(FileName::inline_asm_source_code(&buffer), buffer);
let source_span = Span::with_root_ctxt(source.start_pos, source.end_pos);
let spans: Vec<_> =
spans.iter().map(|sp| source_span.from_inner(*sp)).collect();
err.span_note(spans, "instantiated into assembly here");
}
err.emit();
}
Ok(SharedEmitterMessage::AbortIfErrors) => {
sess.abort_if_errors();
}
Ok(SharedEmitterMessage::Fatal(msg)) => {
sess.fatal(&msg);
}
Err(_) => {
break;
}
}
}
}
}
pub struct OngoingCodegen<B: ExtraBackendMethods> {
pub backend: B,
pub crate_name: Symbol,
pub metadata: EncodedMetadata,
pub windows_subsystem: Option<String>,
pub linker_info: LinkerInfo,
pub crate_info: CrateInfo,
pub coordinator_send: Sender<Box<dyn Any + Send>>,
pub codegen_worker_receive: Receiver<Message<B>>,
pub shared_emitter_main: SharedEmitterMain,
pub future: thread::JoinHandle<Result<CompiledModules, ()>>,
pub output_filenames: Arc<OutputFilenames>,
}
impl<B: ExtraBackendMethods> OngoingCodegen<B> {
pub fn join(self, sess: &Session) -> (CodegenResults, FxHashMap<WorkProductId, WorkProduct>) {
let _timer = sess.timer("finish_ongoing_codegen");
self.shared_emitter_main.check(sess, true);
let future = self.future;
let compiled_modules = sess.time("join_worker_thread", || match future.join() {
Ok(Ok(compiled_modules)) => compiled_modules,
Ok(Err(())) => {
sess.abort_if_errors();
panic!("expected abort due to worker thread errors")
}
Err(_) => {
bug!("panic during codegen/LLVM phase");
}
});
sess.cgu_reuse_tracker.check_expected_reuse(sess.diagnostic());
sess.abort_if_errors();
let work_products =
copy_all_cgu_workproducts_to_incr_comp_cache_dir(sess, &compiled_modules);
produce_final_output_artifacts(sess, &compiled_modules, &self.output_filenames);
// FIXME: time_llvm_passes support - does this use a global context or
// something?
if sess.codegen_units() == 1 && sess.time_llvm_passes() {
self.backend.print_pass_timings()
}
(
CodegenResults {
crate_name: self.crate_name,
metadata: self.metadata,
windows_subsystem: self.windows_subsystem,
linker_info: self.linker_info,
crate_info: self.crate_info,
modules: compiled_modules.modules,
allocator_module: compiled_modules.allocator_module,
metadata_module: compiled_modules.metadata_module,
},
work_products,
)
}
pub fn submit_pre_codegened_module_to_llvm(
&self,
tcx: TyCtxt<'_>,
module: ModuleCodegen<B::Module>,
) {
self.wait_for_signal_to_codegen_item();
self.check_for_errors(tcx.sess);
// These are generally cheap and won't throw off scheduling.
let cost = 0;
submit_codegened_module_to_llvm(&self.backend, &self.coordinator_send, module, cost);
}
pub fn codegen_finished(&self, tcx: TyCtxt<'_>) {
self.wait_for_signal_to_codegen_item();
self.check_for_errors(tcx.sess);
drop(self.coordinator_send.send(Box::new(Message::CodegenComplete::<B>)));
}
/// Consumes this context indicating that codegen was entirely aborted, and
/// we need to exit as quickly as possible.
///
/// This method blocks the current thread until all worker threads have
/// finished, and all worker threads should have exited or be real close to
/// exiting at this point.
pub fn codegen_aborted(self) {
// Signal to the coordinator it should spawn no more work and start
// shutdown.
drop(self.coordinator_send.send(Box::new(Message::CodegenAborted::<B>)));
drop(self.future.join());
}
pub fn check_for_errors(&self, sess: &Session) {
self.shared_emitter_main.check(sess, false);
}
pub fn wait_for_signal_to_codegen_item(&self) {
match self.codegen_worker_receive.recv() {
Ok(Message::CodegenItem) => {
// Nothing to do
}
Ok(_) => panic!("unexpected message"),
Err(_) => {
// One of the LLVM threads must have panicked, fall through so
// error handling can be reached.
}
}
}
}
pub fn submit_codegened_module_to_llvm<B: ExtraBackendMethods>(
_backend: &B,
tx_to_llvm_workers: &Sender<Box<dyn Any + Send>>,
module: ModuleCodegen<B::Module>,
cost: u64,
) {
let llvm_work_item = WorkItem::Optimize(module);
drop(tx_to_llvm_workers.send(Box::new(Message::CodegenDone::<B> { llvm_work_item, cost })));
}
pub fn submit_post_lto_module_to_llvm<B: ExtraBackendMethods>(
_backend: &B,
tx_to_llvm_workers: &Sender<Box<dyn Any + Send>>,
module: CachedModuleCodegen,
) {
let llvm_work_item = WorkItem::CopyPostLtoArtifacts(module);
drop(tx_to_llvm_workers.send(Box::new(Message::CodegenDone::<B> { llvm_work_item, cost: 0 })));
}
pub fn submit_pre_lto_module_to_llvm<B: ExtraBackendMethods>(
_backend: &B,
tcx: TyCtxt<'_>,
tx_to_llvm_workers: &Sender<Box<dyn Any + Send>>,
module: CachedModuleCodegen,
) {
let filename = pre_lto_bitcode_filename(&module.name);
let bc_path = in_incr_comp_dir_sess(tcx.sess, &filename);
let file = fs::File::open(&bc_path)
.unwrap_or_else(|e| panic!("failed to open bitcode file `{}`: {}", bc_path.display(), e));
let mmap = unsafe {
memmap2::Mmap::map(&file).unwrap_or_else(|e| {
panic!("failed to mmap bitcode file `{}`: {}", bc_path.display(), e)
})
};
// Schedule the module to be loaded
drop(tx_to_llvm_workers.send(Box::new(Message::AddImportOnlyModule::<B> {
module_data: SerializedModule::FromUncompressedFile(mmap),
work_product: module.source,
})));
}
pub fn pre_lto_bitcode_filename(module_name: &str) -> String {
format!("{}.{}", module_name, PRE_LTO_BC_EXT)
}
fn msvc_imps_needed(tcx: TyCtxt<'_>) -> bool {
// This should never be true (because it's not supported). If it is true,
// something is wrong with commandline arg validation.
assert!(
!(tcx.sess.opts.cg.linker_plugin_lto.enabled()
&& tcx.sess.target.is_like_windows
&& tcx.sess.opts.cg.prefer_dynamic)
);
tcx.sess.target.is_like_windows &&
tcx.sess.crate_types().iter().any(|ct| *ct == CrateType::Rlib) &&
// ThinLTO can't handle this workaround in all cases, so we don't
// emit the `__imp_` symbols. Instead we make them unnecessary by disallowing
// dynamic linking when linker plugin LTO is enabled.
!tcx.sess.opts.cg.linker_plugin_lto.enabled()
}