use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use crate::{
arg_parser::without_dependency_flags,
compiler_wrapper::CompilerKind,
config::try_rllvm_config,
constants::FAT_LTO_SECTION_NAME,
error::Error,
lto::{is_save_temps_artifact, is_saved_module, marker_source},
utils::{execute_command_for_status, link_bitcode_files, recorded_bitcode_filepath},
};
pub(crate) fn inject_marker(
object: &Path,
bitcode: &Path,
compile_args: &[String],
compiler: &Path,
kind: CompilerKind,
) -> Result<(), Error> {
let workspace = tempfile::tempdir()?;
let marker = build_marker_module(
workspace.path(),
object,
bitcode,
compile_args,
compiler,
kind,
)?;
let config = try_rllvm_config()?;
let status = Command::new(config.llvm_link_filepath())
.arg(object)
.arg(&marker)
.arg("-o")
.arg(object)
.status()?;
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to merge the LTO marker into {object:?}: exit_status={status}"
)));
}
Ok(())
}
fn build_marker_module(
workspace: &Path,
object: &Path,
bitcode: &Path,
compile_args: &[String],
compiler: &Path,
kind: CompilerKind,
) -> Result<PathBuf, Error> {
let extension = match kind {
CompilerKind::Clang => "c",
CompilerKind::ClangXX => "cpp",
};
let source = workspace.join(format!("rllvm_marker.{extension}"));
let marker = workspace.join("rllvm_marker.bc");
fs::write(&source, marker_source(&recorded_bitcode_filepath(bitcode)?))?;
let status = Command::new(compiler)
.args(without_dependency_flags(compile_args))
.args(["-emit-llvm", "-c", "-o"])
.arg(&marker)
.arg(&source)
.status()?;
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to compile the LTO marker for {object:?}: exit_status={status}. \
On a target that is neither ELF nor Mach-O, set lto_mode = \"skip\"."
)));
}
Ok(marker)
}
pub(crate) fn inject_marker_into_fat_object(
object: &Path,
bitcode: &Path,
compile_args: &[String],
compiler: &Path,
kind: CompilerKind,
) -> Result<(), Error> {
let workspace = tempfile::tempdir()?;
let marker = build_marker_module(
workspace.path(),
object,
bitcode,
compile_args,
compiler,
kind,
)?;
let config = try_rllvm_config()?;
let Some(objcopy_filepath) = config.llvm_objcopy_filepath() else {
tracing::warn!(
"No llvm-objcopy configured, so the bitcode half of the fat LTO object \
{object:?} records no path. An LTO link through GNU ld will extract nothing \
from it; set llvm_objcopy_filepath."
);
return Ok(());
};
let extracted = workspace.path().join("fat_lto.bc");
let merged = workspace.path().join("fat_lto_merged.bc");
let status = execute_command_for_status(
objcopy_filepath,
&[
format!(
"--dump-section={FAT_LTO_SECTION_NAME}={}",
extracted.display()
),
object.to_string_lossy().into_owned(),
workspace
.path()
.join("discarded.o")
.to_string_lossy()
.into_owned(),
],
)?;
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to read {FAT_LTO_SECTION_NAME} from the fat LTO object {object:?}: \
exit_status={status}"
)));
}
let status = Command::new(config.llvm_link_filepath())
.arg(&extracted)
.arg(&marker)
.arg("-o")
.arg(&merged)
.status()?;
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to merge the LTO marker into {FAT_LTO_SECTION_NAME} of {object:?}: \
exit_status={status}"
)));
}
let status = execute_command_for_status(
objcopy_filepath,
&[
format!(
"--update-section={FAT_LTO_SECTION_NAME}={}",
merged.display()
),
object.to_string_lossy().into_owned(),
],
)?;
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to write {FAT_LTO_SECTION_NAME} back to the fat LTO object {object:?}: \
exit_status={status}"
)));
}
Ok(())
}
pub(crate) fn collect_saved_module(output: &Path, cleanup: bool) -> Result<PathBuf, Error> {
let darwin = cfg!(target_vendor = "apple");
let dir = match output.parent() {
Some(parent) if !parent.as_os_str().is_empty() => parent,
_ => Path::new("."),
};
let output_name = output.file_name().unwrap_or_default().to_string_lossy();
let destination = PathBuf::from(format!("{}.rllvm.bc", output.display()));
let mut saved = vec![];
let mut litter = vec![];
for entry in fs::read_dir(dir)? {
let entry = entry?;
let name = entry.file_name().to_string_lossy().into_owned();
if is_saved_module(&output_name, &name, darwin) {
saved.push(entry.path());
} else if cleanup && is_save_temps_artifact(&output_name, &name) {
litter.push(entry.path());
}
}
saved.sort();
match saved.len() {
0 => {
for path in litter {
let _ = fs::remove_file(path);
}
return Err(Error::MissingFile(format!(
"The LTO link produced no merged module for {output:?}. Expected {}. \
The link's inputs are probably not LTO bitcode: `-flto` in LDFLAGS alone \
is not enough if the objects were compiled without it. {output:?} now \
records {destination:?}, which does not exist, so `rllvm-get-bc` will \
fail on it.",
if darwin {
format!("{output_name}.lto.opt.bc")
} else {
format!("{output_name}.*.precodegen.bc")
}
)));
}
1 => fs::rename(&saved[0], &destination)?,
_ => {
let code = link_bitcode_files(&saved, destination.clone())?;
if code != Some(0) {
return Err(Error::ExecutionFailure(format!(
"Failed to merge {} save-temps partitions into {destination:?}: exit_status={code:?}",
saved.len()
)));
}
for module in &saved {
let _ = fs::remove_file(module);
}
}
}
for path in litter {
let _ = fs::remove_file(path);
}
tracing::info!("Collected the LTO merged module: {:?}", destination);
Ok(destination)
}