use std::{
collections::HashSet,
ffi::OsStr,
path::{Path, PathBuf},
};
use crate::{
arg_parser::{CompileMode, CompilerArgsInfo},
cache,
config::try_rllvm_config,
constants::DEFAULT_LINK_OUTPUT_FILENAME,
diagnostics::print_warning,
error::Error,
utils::{embed_bitcode_filepath_to_object_file, execute_command_for_status},
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum CompilerKind {
#[default]
Clang,
ClangXX,
}
pub trait CompilerWrapper {
fn name(&self) -> &str;
fn wrapped_compiler(&self) -> &Path;
fn compiler_kind(&self) -> &CompilerKind;
fn parse_args<S>(&mut self, args: &[S]) -> Result<&'_ mut Self, Error>
where
S: AsRef<str>;
fn args(&self) -> &CompilerArgsInfo;
fn command(&self) -> Result<Vec<String>, Error> {
let args_info = self.args();
let compiler_filepath = self.wrapped_compiler();
let mut args = vec![compiler_filepath.to_string_lossy().into_owned()];
if args_info.input_files().is_empty() && !args_info.link_args().is_empty() {
if args_info.is_lto() {
if let Some(lto_ldflags) = try_rllvm_config()?.lto_ldflags() {
args.extend(lto_ldflags.iter().cloned());
}
}
}
args.extend(args_info.input_args().iter().cloned());
if !args_info.forbidden_flags().is_empty() {
let forbidden_flags_set: HashSet<String> =
HashSet::from_iter(args_info.forbidden_flags().iter().cloned());
let mut removed_flags: Vec<&str> =
forbidden_flags_set.iter().map(String::as_str).collect();
removed_flags.sort_unstable();
let message = format!(
"Removed the following flag(s) from the compilation, as they are incompatible with bitcode generation: {}",
removed_flags.join(", ")
);
print_warning(&message);
args.retain(|x| !forbidden_flags_set.contains(x));
}
Ok(args)
}
fn silence(&mut self, value: bool) -> &'_ mut Self;
fn is_silent(&self) -> bool;
fn run(&mut self) -> Result<Option<i32>, Error> {
if let Some(code) = self.build_target()?
&& code != 0
{
return Ok(Some(code));
}
if self.args().is_bitcode_generation_skipped()? {
return Ok(Some(0));
}
self.generate_bitcode_files_and_embed_filepaths()
}
fn execute_command<S>(&self, args: &[S], mode: CompileMode) -> Result<Option<i32>, Error>
where
S: AsRef<OsStr> + std::fmt::Debug,
{
if !self.is_silent() {
tracing::debug!("[{:?}] args={:?}", mode, args);
}
if args.is_empty() {
return Err(Error::InvalidArguments(
"The number of arguments cannot be 0".into(),
));
}
let status = execute_command_for_status(args[0].as_ref(), &args[1..])?;
if !self.is_silent() {
tracing::debug!("[{:?}] exit_status={}", mode, status);
}
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to execute the command: args={:?}, exit_status={}",
args, status
)));
}
Ok(status.code())
}
fn build_target(&self) -> Result<Option<i32>, Error> {
let args = self.command()?;
let mode = self.args().mode();
self.execute_command(&args, mode)
}
fn generate_bitcode_files_and_embed_filepaths(&self) -> Result<Option<i32>, Error> {
let config = try_rllvm_config()?;
let is_compile_only = self.args().is_compile_only();
let artifact_filepaths = self.args().artifact_filepaths()?;
let caching_enabled = cache::is_cache_enabled(config.cache_enabled());
let cache_directory = if caching_enabled {
match cache::cache_dir(config.cache_dir().map(|p| p.as_path())) {
Ok(dir) => Some(dir),
Err(err) => {
tracing::warn!(
"Failed to initialize cache directory, caching disabled: {}",
err
);
None
}
}
} else {
None
};
let mut object_filepaths = vec![];
for (src_filepath, object_filepath, bitcode_filepath) in artifact_filepaths {
if !is_compile_only {
self.build_object_file(&src_filepath, &object_filepath)?;
object_filepaths.push(object_filepath.clone());
}
let src_bitcode_filepath = if src_filepath.extension().is_some_and(|x| x == "bc") {
src_filepath
} else if let Some(ref cache_dir) = cache_directory {
let cache_key = cache::compute_cache_key(
&src_filepath,
self.args().compile_args(),
config.bitcode_generation_flags(),
)?;
if let Some(cached_path) = cache::cache_lookup(cache_dir, &src_filepath, cache_key)
{
std::fs::copy(&cached_path, &bitcode_filepath).map_err(|err| {
tracing::error!(
"Failed to copy cached bitcode {:?} to {:?}: {}",
cached_path,
bitcode_filepath,
err
);
err
})?;
bitcode_filepath
} else {
if let Some(code) =
self.generate_bitcode_file(&src_filepath, &bitcode_filepath)?
&& code != 0
{
return Ok(Some(code));
}
if let Err(err) =
cache::cache_store(cache_dir, &src_filepath, cache_key, &bitcode_filepath)
{
tracing::warn!("Failed to store bitcode in cache: {}", err);
}
bitcode_filepath
}
} else {
if let Some(code) = self.generate_bitcode_file(&src_filepath, &bitcode_filepath)?
&& code != 0
{
return Ok(Some(code));
}
bitcode_filepath
};
embed_bitcode_filepath_to_object_file(&src_bitcode_filepath, &object_filepath, None)?;
}
if cache_directory.is_some() {
cache::log_cache_stats();
}
if is_compile_only || object_filepaths.is_empty() {
return Ok(Some(0));
}
let output_filename = match self.args().output_filename() {
"" => DEFAULT_LINK_OUTPUT_FILENAME,
name => name,
};
let output_filepath = PathBuf::from(output_filename).canonicalize()?;
self.link_object_files(&object_filepaths, output_filepath)
}
fn generate_bitcode_file<P>(
&self,
src_filepath: P,
bitcode_filepath: P,
) -> Result<Option<i32>, Error>
where
P: AsRef<Path>,
{
let src_filepath = src_filepath.as_ref();
let bitcode_filepath = bitcode_filepath.as_ref();
let compiler_filepath = self.wrapped_compiler();
let mut args = vec![compiler_filepath.to_string_lossy().into_owned()];
args.extend(self.args().compile_args().iter().cloned());
if let Some(bitcode_generation_flags) = try_rllvm_config()?.bitcode_generation_flags() {
args.extend(bitcode_generation_flags.iter().cloned());
}
args.extend_from_slice(&[
"-emit-llvm".to_string(),
"-c".to_string(),
"-o".to_string(),
bitcode_filepath.to_string_lossy().into_owned(),
src_filepath.to_string_lossy().into_owned(),
]);
let mode = CompileMode::BitcodeGeneration;
self.execute_command(&args, mode)
}
fn build_object_file<P>(
&self,
src_filepath: P,
object_filepath: P,
) -> Result<Option<i32>, Error>
where
P: AsRef<Path>,
{
let src_filepath = src_filepath.as_ref();
let object_filepath = object_filepath.as_ref();
let wrapped_compiler = self.wrapped_compiler();
let mut args = vec![wrapped_compiler.to_string_lossy().into_owned()];
args.extend(self.args().compile_args().iter().cloned());
args.extend_from_slice(&[
"-c".to_string(),
"-o".to_string(),
object_filepath.to_string_lossy().into_owned(),
src_filepath.to_string_lossy().into_owned(),
]);
let mode = CompileMode::Compiling;
self.execute_command(&args, mode)
}
fn link_object_files<P>(
&self,
object_filepaths: &[P],
output_filepath: P,
) -> Result<Option<i32>, Error>
where
P: AsRef<Path>,
{
let output_filepath = output_filepath.as_ref();
let wrapped_compiler = self.wrapped_compiler();
let mut args = vec![wrapped_compiler.to_string_lossy().into_owned()];
if self.args().is_lto() {
if let Some(lto_ldflags) = try_rllvm_config()?.lto_ldflags() {
args.extend(lto_ldflags.iter().cloned());
}
}
args.extend(self.args().link_args().iter().cloned());
args.extend_from_slice(&[
"-o".to_string(),
output_filepath.to_string_lossy().into_owned(),
]);
args.extend(
object_filepaths
.iter()
.map(|x| x.as_ref().to_string_lossy().into_owned()),
);
let mode = CompileMode::Linking;
self.execute_command(&args, mode)
}
}
pub trait CompilerWrapperBuilder {
type OutputType;
fn build(&self) -> Result<Self::OutputType, Error>;
#[must_use]
fn name(self, name: &str) -> Self;
#[must_use]
fn compiler_kind(self, compiler_kind: CompilerKind) -> Self;
fn wrapped_compiler<P>(self, wrapped_compiler: P) -> Self
where
P: AsRef<Path>;
fn silence(self, value: bool) -> Self;
}