diff --git a/Cargo.toml b/Cargo.toml index 4c86d70..2a42417 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,15 +16,14 @@ repository = "https://github.com/h1994st/rllvm" version = "0.1.1" [dependencies] -clap = {version = "~4.4.11", features = ["derive"]} -confy = "~0.5" -lazy_static = "~1.4" -log = "~0.4.20" -object = {version = "~0.32", features = ["all"]} -regex = "~1.10" -serde = {version = "~1.0.193", features = ["derive"]} -simple_logger = "~4.3.0" -which = "~5.0.0" +clap = {version = "~4.5.23", features = ["derive"]} +confy = "~0.6.1" +log = "~0.4.22" +object = {version = "~0.36.5", features = ["all"]} +regex = "~1.11.1" +serde = {version = "~1.0.216", features = ["derive"]} +simple_logger = "~5.0.0" +which = "~7.0.0" [target.'cfg(target_vendor = "apple")'.dependencies] glob = "~0.3.1" diff --git a/src/arg_parser.rs b/src/arg_parser.rs index ed03c38..5e08e79 100644 --- a/src/arg_parser.rs +++ b/src/arg_parser.rs @@ -1,16 +1,13 @@ //! Command-line argument parser -use std::path::PathBuf; - -use lazy_static::lazy_static; -use regex::Regex; - use crate::{ - config::RLLVM_CONFIG, - constants::{ARG_EXACT_MATCH_MAP, ARG_PATTERNS}, + config::rllvm_config, + constants::{arg_exact_match_map, arg_patterns}, error::Error, utils::*, }; +use regex::Regex; +use std::{path::PathBuf, sync::OnceLock}; /// Compile mode #[derive(Debug)] @@ -93,10 +90,9 @@ impl CompilerArgsInfo { self.input_files.push(flag.as_ref().to_string()); // Assembly files - lazy_static! { - static ref RE: Regex = Regex::new(r"\.(s|S)$").unwrap(); - } - if RE.is_match(flag.as_ref()) { + static RE: OnceLock = OnceLock::new(); + let re = RE.get_or_init(|| Regex::new(r"\.(s|S)$").unwrap()); + if re.is_match(flag.as_ref()) { self.is_assembly = true; } @@ -313,7 +309,7 @@ impl CompilerArgsInfo { let mut offset = 1; // Try to match the flag exactly - if let Some(arg_info) = ARG_EXACT_MATCH_MAP.get(arg.as_str()) { + if let Some(arg_info) = arg_exact_match_map().get(arg.as_str()) { // Consume more parameters offset += self.consume_params(i, arg.to_string(), arg_info, &args); } else if arg == "-Wl,--start-group" { @@ -335,7 +331,7 @@ impl CompilerArgsInfo { } else { // Try to match a pattern let mut matched = false; - for arg_pattern in ARG_PATTERNS.iter() { + for arg_pattern in arg_patterns().iter() { let pattern = &arg_pattern.pattern; let arg_info = &arg_pattern.arg_info; if pattern.is_match(arg.as_str()) { @@ -435,7 +431,7 @@ impl CompilerArgsInfo { let conditions = [ ( - RLLVM_CONFIG.is_configure_only(), + rllvm_config().is_configure_only(), "we are in configure-only mode", ), ( @@ -506,7 +502,7 @@ impl CompilerArgsInfo { derive_object_and_bitcode_filepath(&src_filepath, self.is_compile_only)?; // Update the bitcode filepath, if the bitcode store path is provided - if let Some(bitcode_store_path) = RLLVM_CONFIG.bitcode_store_path() { + if let Some(bitcode_store_path) = rllvm_config().bitcode_store_path() { if bitcode_store_path.exists() { // Obtain a new bitcode filename based on the hash of the source filepath if bitcode_filepath.file_name().is_some() { @@ -536,3 +532,45 @@ impl CompilerArgsInfo { Ok(artifacts) } } + +#[cfg(test)] +mod tests { + use super::CompilerArgsInfo; + + fn test_parsing(input: &str, check_func: F) + where + F: Fn(&CompilerArgsInfo) -> bool, + { + let mut args_info = CompilerArgsInfo::default(); + let args: Vec<&str> = input.split_ascii_whitespace().collect(); + let ret = args_info.parse_args(&args); + assert!(ret.is_ok()); + assert!(check_func(ret.unwrap())); + } + + fn test_parsing_lto_internal(input: &str) { + test_parsing(input, |args| args.is_lto()); + } + + #[test] + fn test_parsing_lto() { + let input = r#"-pthread -c -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -flto -g -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Wstrict-prototypes -Werror=implicit-function-declaration -fprofile-instr-use=code.profclangd -I./Include/internal -I. -I./Include -D_FORTIFY_SOURCE=2 -D_FORTIFY_SOURCE=2 -fPIC -DPy_BUILD_CORE -DSOABI='"cpython-38-x86_64-linux-gnu"' -o Python/dynload_shlib.o ./Python/dynload_shlib.c"#; + test_parsing_lto_internal(input); + + let input = r#"-pthread -c -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -flto=thin -g -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Wstrict-prototypes -Werror=implicit-function-declaration -fprofile-instr-use=code.profclangd -I./Include/internal -I. -I./Include -D_FORTIFY_SOURCE=2 -D_FORTIFY_SOURCE=2 -fPIC -DPy_BUILD_CORE -DSOABI='"cpython-38-x86_64-linux-gnu"' -o Python/dynload_shlib.o ./Python/dynload_shlib.c"#; + test_parsing_lto_internal(input); + } + + fn test_parsing_link_args_internal(input: &str, expected: usize) { + test_parsing(input, |args| args.link_args().len() == expected); + } + + #[test] + fn test_parsing_link_args() { + let input = r#"-Wl,--fatal-warnings -Wl,--build-id=sha1 -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,-z,defs -Wl,--as-needed -fuse-ld=lld -Wl,--icf=all -Wl,--color-diagnostics -flto=thin -Wl,--thinlto-jobs=8 -Wl,--thinlto-cache-dir=thinlto-cache -Wl,--thinlto-cache-policy,cache_size=10\%:cache_size_bytes=10g:cache_size_files=100000 -Wl,--lto-O0 -fwhole-program-vtables -Wl,--no-call-graph-profile-sort -m64 -Wl,-O2 -Wl,--gc-sections -Wl,--gdb-index -rdynamic -fsanitize=cfi-vcall -fsanitize=cfi-icall -pie -Wl,--disable-new-dtags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now -o "./brotli" -Wl,--start-group @"./brotli.rsp" -Wl,--end-group -latomic -ldl -lpthread -lrt"#; + test_parsing_link_args_internal(input, 32); + + let input = r#"1.c 2.c 3.c 4.c 5.c -Wl,--start-group 7.o 8.o 9.o -Wl,--end-group 10.c 11.c 12.c 13.c"#; + test_parsing_link_args_internal(input, 5); + } +} diff --git a/src/bin/rllvm_cc.rs b/src/bin/rllvm_cc.rs index e51e7ff..207400c 100644 --- a/src/bin/rllvm_cc.rs +++ b/src/bin/rllvm_cc.rs @@ -2,7 +2,7 @@ use std::env; use rllvm::{ compiler_wrapper::{llvm::ClangWrapper, CompilerKind, CompilerWrapper}, - config::RLLVM_CONFIG, + config::rllvm_config, error::Error, }; use simple_logger::SimpleLogger; @@ -13,7 +13,7 @@ pub fn rllvm_main(name: &str, compiler_kind: CompilerKind) -> Result<(), Error> let args = &args[1..]; // Set log level - let log_level = RLLVM_CONFIG.log_level().to_level_filter(); + let log_level = rllvm_config().log_level().to_level_filter(); SimpleLogger::new() .with_level(log_level) .init() diff --git a/src/compiler_wrapper/llvm/clang_wrapper.rs b/src/compiler_wrapper/llvm/clang_wrapper.rs index 5b1b0e6..eb3b88e 100644 --- a/src/compiler_wrapper/llvm/clang_wrapper.rs +++ b/src/compiler_wrapper/llvm/clang_wrapper.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use crate::{ - arg_parser::CompilerArgsInfo, compiler_wrapper::*, config::RLLVM_CONFIG, error::Error, + arg_parser::CompilerArgsInfo, compiler_wrapper::*, config::rllvm_config, error::Error, }; #[derive(Debug)] @@ -22,8 +22,8 @@ impl ClangWrapper { pub fn new(name: &str, compiler_kind: CompilerKind) -> Self { // Obtain the compiler path from the configuration let compiler_path = match compiler_kind { - CompilerKind::Clang => RLLVM_CONFIG.clang_filepath(), - CompilerKind::ClangXX => RLLVM_CONFIG.clangxx_filepath(), + CompilerKind::Clang => rllvm_config().clang_filepath(), + CompilerKind::ClangXX => rllvm_config().clangxx_filepath(), }; Self { diff --git a/src/compiler_wrapper/wrapper.rs b/src/compiler_wrapper/wrapper.rs index c9878ee..3892b8f 100644 --- a/src/compiler_wrapper/wrapper.rs +++ b/src/compiler_wrapper/wrapper.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ arg_parser::{CompileMode, CompilerArgsInfo}, - config::RLLVM_CONFIG, + config::rllvm_config, error::Error, utils::{embed_bitcode_filepath_to_object_file, execute_command_for_status}, }; @@ -53,7 +53,7 @@ pub trait CompilerWrapper { // Linking if args_info.is_lto() { // Add LTO LDFLAGS - if let Some(lto_ldflags) = RLLVM_CONFIG.lto_ldflags() { + if let Some(lto_ldflags) = rllvm_config().lto_ldflags() { args.extend(lto_ldflags.iter().cloned()); } } @@ -182,7 +182,7 @@ pub trait CompilerWrapper { let mut args = vec![String::from(compiler_filepath.to_string_lossy())]; args.extend(self.args().compile_args().iter().cloned()); // Add bitcode generation flags - if let Some(bitcode_generation_flags) = RLLVM_CONFIG.bitcode_generation_flags() { + if let Some(bitcode_generation_flags) = rllvm_config().bitcode_generation_flags() { args.extend(bitcode_generation_flags.iter().cloned()); } args.extend_from_slice(&[ @@ -239,7 +239,7 @@ pub trait CompilerWrapper { let mut args = vec![String::from(wrapped_compiler.to_string_lossy())]; if self.args().is_lto() { // Add LTO LDFLAGS - if let Some(lto_ldflags) = RLLVM_CONFIG.lto_ldflags() { + if let Some(lto_ldflags) = rllvm_config().lto_ldflags() { args.extend(lto_ldflags.iter().cloned()); } } diff --git a/src/config.rs b/src/config.rs index 9381daf..11130e0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,9 @@ use std::{ env, fs, path::{Path, PathBuf}, + sync::OnceLock, }; -use lazy_static::lazy_static; use log::Level; use serde::{Deserialize, Serialize}; @@ -14,8 +14,16 @@ use crate::{ utils::{execute_llvm_config, find_llvm_config}, }; -lazy_static! { - pub static ref RLLVM_CONFIG: RLLVMConfig = RLLVMConfig::new(); +#[cfg(not(test))] +pub fn rllvm_config() -> &'static RLLVMConfig { + static RLLVM_CONFIG: OnceLock = OnceLock::new(); + RLLVM_CONFIG.get_or_init(|| RLLVMConfig::new()) +} + +#[cfg(test)] +pub fn rllvm_config() -> &'static RLLVMConfig { + static RLLVM_CONFIG: OnceLock = OnceLock::new(); + RLLVM_CONFIG.get_or_init(|| RLLVMConfig::default()) } #[derive(Serialize, Deserialize, Debug)] diff --git a/src/constants.rs b/src/constants.rs index adee68c..5a7bcdc 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,8 +1,6 @@ //! Constants used by the argument parser -use std::collections::HashMap; - -use lazy_static::lazy_static; +use std::{collections::HashMap, sync::OnceLock}; use crate::arg_parser::{ArgInfo, ArgPatternInfo, CompilerArgsInfo}; @@ -28,8 +26,10 @@ pub const LLVM_VERSION_MAX: u32 = 33; #[cfg(not(target_vendor = "apple"))] pub const LLVM_VERSION_MIN: u32 = 6; -lazy_static! { - pub static ref ARG_EXACT_MATCH_MAP: CallbackMap = { +pub fn arg_exact_match_map() -> &'static CallbackMap { + static ARG_EXACT_MATCH_MAP: OnceLock = OnceLock::new(); + + ARG_EXACT_MATCH_MAP.get_or_init(|| { let mut m = HashMap::new(); m.insert("/dev/null", ArgInfo::new(0, CompilerArgsInfo::input_file)); @@ -416,71 +416,77 @@ lazy_static! { ); m - }; - pub static ref ARG_PATTERNS: PatternCallbackVec = vec![ - ArgPatternInfo::new(r"^-MF.*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-MJ.*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-MQ.*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-MT.*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-Wl,.+$", 0, CompilerArgsInfo::link_unary), - ArgPatternInfo::new(r"^-W[^l].*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-W[l][^,].*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-(l|L).+$", 0, CompilerArgsInfo::link_unary), - ArgPatternInfo::new(r"^-I.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-D.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-B.+$", 0, CompilerArgsInfo::compile_link_unary), - ArgPatternInfo::new(r"^-isystem.+$", 0, CompilerArgsInfo::compile_link_unary), - ArgPatternInfo::new(r"^-U.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-fsanitize=.+$", 0, CompilerArgsInfo::compile_link_unary), - ArgPatternInfo::new(r"^-fuse-ld=.+$", 0, CompilerArgsInfo::link_unary), - ArgPatternInfo::new(r"^-flto=.+$", 0, CompilerArgsInfo::lto), - ArgPatternInfo::new(r"^-f.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-rtlib=.+$", 0, CompilerArgsInfo::link_unary), - ArgPatternInfo::new(r"^-std=.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-stdlib=.+$", 0, CompilerArgsInfo::compile_link_unary), - ArgPatternInfo::new(r"^-mtune=.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^--sysroot=.+$", 0, CompilerArgsInfo::compile_link_unary), - ArgPatternInfo::new(r"^-print-.*$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new( - r"^-mmacosx-version-min=.+$", - 0, - CompilerArgsInfo::compile_link_unary - ), - ArgPatternInfo::new( - r"^-mstack-alignment=.+$", - 0, - CompilerArgsInfo::compile_unary - ), - ArgPatternInfo::new(r"^-march=.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-mregparm=.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new(r"^-mcmodel=.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new( - r"^-mpreferred-stack-boundary=.+$", - 0, - CompilerArgsInfo::compile_unary - ), - ArgPatternInfo::new( - r"^-mindirect-branch=.+$", - 0, - CompilerArgsInfo::compile_unary - ), - ArgPatternInfo::new(r"^--param=.+$", 0, CompilerArgsInfo::compile_unary), - ArgPatternInfo::new( - r"^.+\.(c|cc|cpp|C|cxx|i|s|S|bc)$", - 0, - CompilerArgsInfo::input_file - ), - ArgPatternInfo::new( - r"^.+\.([fF](|[0-9][0-9]|or|OR|pp|PP))$", - 0, - CompilerArgsInfo::input_file - ), - ArgPatternInfo::new( - r"^.+\.(o|lo|So|so|po|a|dylib|pico|nossppico)$", - 0, - CompilerArgsInfo::object_file - ), - ArgPatternInfo::new(r"^.+\.dylib(\.\d)+$", 0, CompilerArgsInfo::object_file), - ArgPatternInfo::new(r"^.+\.(So|so)(\.\d)+$", 0, CompilerArgsInfo::object_file), - ]; + }) +} + +pub fn arg_patterns() -> &'static PatternCallbackVec { + static ARG_PATTERNS: OnceLock = OnceLock::new(); + ARG_PATTERNS.get_or_init(|| { + vec![ + ArgPatternInfo::new(r"^-MF.*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-MJ.*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-MQ.*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-MT.*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-Wl,.+$", 0, CompilerArgsInfo::link_unary), + ArgPatternInfo::new(r"^-W[^l].*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-W[l][^,].*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-(l|L).+$", 0, CompilerArgsInfo::link_unary), + ArgPatternInfo::new(r"^-I.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-D.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-B.+$", 0, CompilerArgsInfo::compile_link_unary), + ArgPatternInfo::new(r"^-isystem.+$", 0, CompilerArgsInfo::compile_link_unary), + ArgPatternInfo::new(r"^-U.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-fsanitize=.+$", 0, CompilerArgsInfo::compile_link_unary), + ArgPatternInfo::new(r"^-fuse-ld=.+$", 0, CompilerArgsInfo::link_unary), + ArgPatternInfo::new(r"^-flto=.+$", 0, CompilerArgsInfo::lto), + ArgPatternInfo::new(r"^-f.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-rtlib=.+$", 0, CompilerArgsInfo::link_unary), + ArgPatternInfo::new(r"^-std=.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-stdlib=.+$", 0, CompilerArgsInfo::compile_link_unary), + ArgPatternInfo::new(r"^-mtune=.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^--sysroot=.+$", 0, CompilerArgsInfo::compile_link_unary), + ArgPatternInfo::new(r"^-print-.*$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new( + r"^-mmacosx-version-min=.+$", + 0, + CompilerArgsInfo::compile_link_unary, + ), + ArgPatternInfo::new( + r"^-mstack-alignment=.+$", + 0, + CompilerArgsInfo::compile_unary, + ), + ArgPatternInfo::new(r"^-march=.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-mregparm=.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new(r"^-mcmodel=.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new( + r"^-mpreferred-stack-boundary=.+$", + 0, + CompilerArgsInfo::compile_unary, + ), + ArgPatternInfo::new( + r"^-mindirect-branch=.+$", + 0, + CompilerArgsInfo::compile_unary, + ), + ArgPatternInfo::new(r"^--param=.+$", 0, CompilerArgsInfo::compile_unary), + ArgPatternInfo::new( + r"^.+\.(c|cc|cpp|C|cxx|i|s|S|bc)$", + 0, + CompilerArgsInfo::input_file, + ), + ArgPatternInfo::new( + r"^.+\.([fF](|[0-9][0-9]|or|OR|pp|PP))$", + 0, + CompilerArgsInfo::input_file, + ), + ArgPatternInfo::new( + r"^.+\.(o|lo|So|so|po|a|dylib|pico|nossppico)$", + 0, + CompilerArgsInfo::object_file, + ), + ArgPatternInfo::new(r"^.+\.dylib(\.\d)+$", 0, CompilerArgsInfo::object_file), + ArgPatternInfo::new(r"^.+\.(So|so)(\.\d)+$", 0, CompilerArgsInfo::object_file), + ] + }) } diff --git a/src/utils/file_utils.rs b/src/utils/file_utils.rs index b4d35ef..6b6ae2c 100644 --- a/src/utils/file_utils.rs +++ b/src/utils/file_utils.rs @@ -9,7 +9,7 @@ use std::{ use object::{ write, BinaryFormat, File, Object, ObjectComdat, ObjectKind, ObjectSection, ObjectSymbol, - RelocationTarget, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolSection, + RelocationTarget, SectionFlags, SectionKind, SymbolFlags, SymbolSection, }; use crate::{ @@ -150,10 +150,6 @@ fn copy_object_file(in_object: File) -> Result { // Symbols let mut out_symbols = HashMap::new(); for in_symbol in in_object.symbols() { - if in_symbol.kind() == SymbolKind::Null { - continue; - } - let (section, value) = match in_symbol.section() { SymbolSection::None => (write::SymbolSection::None, in_symbol.address()), SymbolSection::Undefined => (write::SymbolSection::Undefined, in_symbol.address()), @@ -250,11 +246,9 @@ fn copy_object_file(in_object: File) -> Result { }; let out_relocation = write::Relocation { offset, - size: in_relocation.size(), - kind: in_relocation.kind(), - encoding: in_relocation.encoding(), symbol, addend: in_relocation.addend(), + flags: in_relocation.flags(), }; out_object.add_relocation(out_section, out_relocation)?; } @@ -342,3 +336,59 @@ pub fn extract_bitcode_filepaths_from_parsed_objects( Ok(bitcode_filepaths) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::test_case; + use std::{ + fs, + path::{Path, PathBuf}, + }; + + #[test] + fn test_path_injection_and_extraction() { + let bitcode_filepath = Path::new("/tmp/hello.bc"); + let object_filepath = Path::new(test_case!("hello.o")); + + let output_object_filepath = Path::new("/tmp/hello.new.o"); + + // Embed bitcode filepath + let ret = embed_bitcode_filepath_to_object_file( + bitcode_filepath, + object_filepath, + Some(output_object_filepath), + ); + assert!(ret.is_ok()); + + // Extract embedded filepaths + let embedded_filepaths = extract_bitcode_filepaths_from_object_file(output_object_filepath) + .expect("Failed to extract embedded filepaths"); + assert!(!embedded_filepaths.is_empty()); + + let embedded_filepath = embedded_filepaths[0].clone(); + let expected_filepath = PathBuf::from(bitcode_filepath); + println!("{:?}", embedded_filepath); + assert_eq!(embedded_filepath, expected_filepath); + + // Clean + fs::remove_file(output_object_filepath).expect("Failed to delete the output object file"); + } + + #[test] + fn test_paths_extraction() { + let object_filepath = Path::new(test_case!("foo_bar_baz.dylib")); + + let embedded_filepaths = extract_bitcode_filepaths_from_object_file(object_filepath) + .expect("Failed to extract embedded filepaths"); + assert_eq!(embedded_filepaths.len(), 3); + + let expected_filepaths = vec![ + PathBuf::from("/tmp/bar.bc"), + PathBuf::from("/tmp/baz.bc"), + PathBuf::from("/tmp/foo.bc"), + ]; + println!("{:?}", embedded_filepaths); + assert_eq!(embedded_filepaths, expected_filepaths) + } +} diff --git a/src/utils/llvm_utils.rs b/src/utils/llvm_utils.rs index 50964c3..12b9480 100644 --- a/src/utils/llvm_utils.rs +++ b/src/utils/llvm_utils.rs @@ -12,7 +12,7 @@ use which::which; #[cfg(not(target_vendor = "apple"))] use crate::constants::{LLVM_VERSION_MAX, LLVM_VERSION_MIN}; use crate::utils::{execute_command_for_status, execute_command_for_stdout_string}; -use crate::{config::RLLVM_CONFIG, error::Error}; +use crate::{config::rllvm_config, error::Error}; pub fn execute_llvm_ar(llvm_ar_filepath: P, args: &[S]) -> Result where @@ -116,7 +116,7 @@ where let mut args = vec![]; // Link arguments - if let Some(llvm_link_flags) = RLLVM_CONFIG.llvm_link_flags() { + if let Some(llvm_link_flags) = rllvm_config().llvm_link_flags() { args.extend(llvm_link_flags.iter().cloned()); } // Output @@ -131,7 +131,8 @@ where .map(|x| String::from(x.as_ref().to_string_lossy())), ); - execute_command_for_status(RLLVM_CONFIG.llvm_link_filepath(), &args).map(|status| status.code()) + execute_command_for_status(rllvm_config().llvm_link_filepath(), &args) + .map(|status| status.code()) } /// Archive given bitcode files into one archive file @@ -159,5 +160,154 @@ where .map(|x| String::from(x.as_ref().to_string_lossy())), ); - execute_command_for_status(RLLVM_CONFIG.llvm_ar_filepath(), &args).map(|status| status.code()) + execute_command_for_status(rllvm_config().llvm_ar_filepath(), &args).map(|status| status.code()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + compiler_wrapper::{llvm::ClangWrapper, CompilerKind, CompilerWrapper}, + utils::test_case, + }; + use std::{ + fs, + path::{Path, PathBuf}, + }; + + #[test] + fn test_find_llvm_config() { + assert!(find_llvm_config().map_or(false, |llvm_config_path| { + println!("llvm_config_path={:?}", llvm_config_path); + llvm_config_path.exists() + && llvm_config_path.is_file() + && llvm_config_path + .file_name() + .unwrap() + .to_string_lossy() + .starts_with("llvm-config") + })); + } + + fn build_bitcode_files(label: &str) -> bool { + let bitcode_filepaths = [ + PathBuf::from(format!("/tmp/{}_bar.bc", label)), + PathBuf::from(format!("/tmp/{}_baz.bc", label)), + PathBuf::from(format!("/tmp/{}_foo.bc", label)), + ]; + + let input_args = [ + [ + "-c", + "-emit-llvm", + "-o", + bitcode_filepaths[0].to_str().unwrap(), + test_case!("bar.c"), + ], + [ + "-c", + "-emit-llvm", + "-o", + bitcode_filepaths[1].to_str().unwrap(), + test_case!("baz.c"), + ], + [ + "-c", + "-emit-llvm", + "-o", + bitcode_filepaths[2].to_str().unwrap(), + test_case!("foo.c"), + ], + ]; + + input_args.iter().all(|args| { + let mut cc = ClangWrapper::new("rllvm", CompilerKind::Clang); + cc.parse_args(args) + .unwrap() + .run() + .unwrap() + .map_or(false, |code| code == 0) + }) + } + + #[test] + fn test_link_bitcode_files() { + // Prepare input bitcode files + assert!(build_bitcode_files("link")); + + let bitcode_filepaths = [ + Path::new("/tmp/link_bar.bc"), + Path::new("/tmp/link_baz.bc"), + Path::new("/tmp/link_foo.bc"), + ]; + + let output_filepath = Path::new("/tmp/foo_bar_baz.bc"); + + assert!( + link_bitcode_files(&bitcode_filepaths, output_filepath).map_or_else( + |err| { + println!("Failed to link bitcode files: {:?}", err); + false + }, + |code| { code.map_or(false, |code| code == 0) } + ) + ); + + // Check if the output file is successfully created + assert!(output_filepath.exists() && output_filepath.is_file()); + + // Clean + fs::remove_file(output_filepath).expect("Failed to delete the output bitcode file"); + bitcode_filepaths.iter().for_each(|&bitcode_filepath| { + fs::remove_file(bitcode_filepath).expect("Failed to delete the input bitcode file") + }); + } + + #[test] + fn test_archive_bitcode_files() { + // Prepare input bitcode files + assert!(build_bitcode_files("archive")); + + let bitcode_filepaths = [ + Path::new("/tmp/archive_bar.bc"), + Path::new("/tmp/archive_baz.bc"), + Path::new("/tmp/archive_foo.bc"), + ]; + + let output_filepath = Path::new("/tmp/foo_bar_baz.bca"); + + assert!( + archive_bitcode_files(&bitcode_filepaths, output_filepath).map_or_else( + |err| { + println!("Failed to archive bitcode files: {:?}", err); + false + }, + |code| { code.map_or(false, |code| code == 0) } + ) + ); + + // Check if the output file is successfully created + assert!(output_filepath.exists() && output_filepath.is_file()); + + // Check the type of the output archive + let output_data = fs::read(&output_filepath).expect("Failed to read the output file"); + assert!( + object::read::archive::ArchiveFile::parse(&*output_data).map_or_else( + |err| { + println!("Failed to parse the output file: {:?}", err); + false + }, + |output_archive_file| { + println!("Output archive file kind: {:?}", output_archive_file.kind()); + true + }, + ) + ); + + // Clean + fs::remove_file(output_filepath).expect("Failed to delete the output bitcode file"); + bitcode_filepaths.iter().for_each(|&bitcode_filepath| { + fs::remove_file(bitcode_filepath).expect("Failed to delete the input bitcode file") + }); + } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0c729b2..cf12019 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -15,3 +15,8 @@ pub use llvm_utils::*; /// Filepath-related utility functions mod path_utils; pub use path_utils::*; + +#[cfg(test)] +mod test_utils; +#[cfg(test)] +pub(crate) use test_utils::*; diff --git a/src/utils/path_utils.rs b/src/utils/path_utils.rs index dbc3c6a..9e21ac3 100644 --- a/src/utils/path_utils.rs +++ b/src/utils/path_utils.rs @@ -79,3 +79,42 @@ where filepath.hash(&mut hasher); hasher.finish() } + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + + #[test] + fn test_derive_object_and_bitcode_filepath() { + let test_inputs = [ + ( + Path::new("/tmp/foo.c"), + false, + (Path::new("/tmp/.foo.o"), Path::new("/tmp/.foo.o.bc")), + ), + ( + Path::new("/tmp/foo.c"), + true, + (Path::new("/tmp/foo.c.o"), Path::new("/tmp/.foo.o.bc")), + ), + ]; + + assert!(test_inputs.iter().all( + |&( + src_filepath, + is_compile_only, + (expected_object_filepath, expected_bitcode_filepath), + )| { + derive_object_and_bitcode_filepath(src_filepath, is_compile_only).map_or( + false, + |(object_filepath, bitcode_filepath)| { + object_filepath == expected_object_filepath + && bitcode_filepath == expected_bitcode_filepath + }, + ) + }, + )); + } +} diff --git a/src/utils/test_utils.rs b/src/utils/test_utils.rs new file mode 100644 index 0000000..bec469b --- /dev/null +++ b/src/utils/test_utils.rs @@ -0,0 +1,6 @@ +macro_rules! test_case { + ($fname:expr) => { + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data/", $fname) + }; +} +pub(crate) use test_case; diff --git a/tests/test_arg_parser.rs b/tests/test_arg_parser.rs deleted file mode 100644 index b7dc9e5..0000000 --- a/tests/test_arg_parser.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[cfg(test)] -mod tests { - use rllvm::arg_parser::CompilerArgsInfo; - - fn test_parsing(input: &str, check_func: F) - where - F: Fn(&CompilerArgsInfo) -> bool, - { - let mut args_info = CompilerArgsInfo::default(); - let args: Vec<&str> = input.split_ascii_whitespace().collect(); - let ret = args_info.parse_args(&args); - assert!(ret.is_ok()); - assert!(check_func(ret.unwrap())); - } - - fn test_parsing_lto_internal(input: &str) { - test_parsing(input, |args| args.is_lto()); - } - - #[test] - fn test_parsing_lto() { - let input = r#"-pthread -c -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -flto -g -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Wstrict-prototypes -Werror=implicit-function-declaration -fprofile-instr-use=code.profclangd -I./Include/internal -I. -I./Include -D_FORTIFY_SOURCE=2 -D_FORTIFY_SOURCE=2 -fPIC -DPy_BUILD_CORE -DSOABI='"cpython-38-x86_64-linux-gnu"' -o Python/dynload_shlib.o ./Python/dynload_shlib.c"#; - test_parsing_lto_internal(input); - - let input = r#"-pthread -c -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -g -fdebug-prefix-map=/home/legend/makepkgs/python/src=/usr/src/debug -fno-semantic-interposition -flto=thin -g -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Wstrict-prototypes -Werror=implicit-function-declaration -fprofile-instr-use=code.profclangd -I./Include/internal -I. -I./Include -D_FORTIFY_SOURCE=2 -D_FORTIFY_SOURCE=2 -fPIC -DPy_BUILD_CORE -DSOABI='"cpython-38-x86_64-linux-gnu"' -o Python/dynload_shlib.o ./Python/dynload_shlib.c"#; - test_parsing_lto_internal(input); - } - - fn test_parsing_link_args_internal(input: &str, expected: usize) { - test_parsing(input, |args| args.link_args().len() == expected); - } - - #[test] - fn test_parsing_link_args() { - let input = r#"-Wl,--fatal-warnings -Wl,--build-id=sha1 -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,-z,defs -Wl,--as-needed -fuse-ld=lld -Wl,--icf=all -Wl,--color-diagnostics -flto=thin -Wl,--thinlto-jobs=8 -Wl,--thinlto-cache-dir=thinlto-cache -Wl,--thinlto-cache-policy,cache_size=10\%:cache_size_bytes=10g:cache_size_files=100000 -Wl,--lto-O0 -fwhole-program-vtables -Wl,--no-call-graph-profile-sort -m64 -Wl,-O2 -Wl,--gc-sections -Wl,--gdb-index -rdynamic -fsanitize=cfi-vcall -fsanitize=cfi-icall -pie -Wl,--disable-new-dtags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now -o "./brotli" -Wl,--start-group @"./brotli.rsp" -Wl,--end-group -latomic -ldl -lpthread -lrt"#; - test_parsing_link_args_internal(input, 32); - - let input = r#"1.c 2.c 3.c 4.c 5.c -Wl,--start-group 7.o 8.o 9.o -Wl,--end-group 10.c 11.c 12.c 13.c"#; - test_parsing_link_args_internal(input, 5); - } -} diff --git a/tests/test_file_utils.rs b/tests/test_file_utils.rs deleted file mode 100644 index 77036c5..0000000 --- a/tests/test_file_utils.rs +++ /dev/null @@ -1,55 +0,0 @@ -#[cfg(test)] -mod tests { - use std::{ - fs, - path::{Path, PathBuf}, - }; - - use rllvm::utils::*; - - #[test] - fn test_path_injection_and_extraction() { - let bitcode_filepath = Path::new("/tmp/hello.bc"); - let object_filepath = Path::new("tests/data/hello.o"); - - let output_object_filepath = Path::new("/tmp/hello.new.o"); - - // Embed bitcode filepath - let ret = embed_bitcode_filepath_to_object_file( - bitcode_filepath, - object_filepath, - Some(output_object_filepath), - ); - assert!(ret.is_ok()); - - // Extract embedded filepaths - let embedded_filepaths = extract_bitcode_filepaths_from_object_file(output_object_filepath) - .expect("Failed to extract embedded filepaths"); - assert!(!embedded_filepaths.is_empty()); - - let embedded_filepath = embedded_filepaths[0].clone(); - let expected_filepath = PathBuf::from(bitcode_filepath); - println!("{:?}", embedded_filepath); - assert_eq!(embedded_filepath, expected_filepath); - - // Clean - fs::remove_file(output_object_filepath).expect("Failed to delete the output object file"); - } - - #[test] - fn test_paths_extraction() { - let object_filepath = Path::new("tests/data/foo_bar_baz.dylib"); - - let embedded_filepaths = extract_bitcode_filepaths_from_object_file(object_filepath) - .expect("Failed to extract embedded filepaths"); - assert_eq!(embedded_filepaths.len(), 3); - - let expected_filepaths = vec![ - PathBuf::from("/tmp/bar.bc"), - PathBuf::from("/tmp/baz.bc"), - PathBuf::from("/tmp/foo.bc"), - ]; - println!("{:?}", embedded_filepaths); - assert_eq!(embedded_filepaths, expected_filepaths) - } -} diff --git a/tests/test_llvm_utils.rs b/tests/test_llvm_utils.rs deleted file mode 100644 index ea0b2b4..0000000 --- a/tests/test_llvm_utils.rs +++ /dev/null @@ -1,148 +0,0 @@ -#[cfg(test)] -mod tests { - use std::{ - fs, - path::{Path, PathBuf}, - }; - - use rllvm::{ - compiler_wrapper::{llvm::ClangWrapper, CompilerKind, CompilerWrapper}, - utils::*, - }; - - #[test] - fn test_find_llvm_config() { - assert!(find_llvm_config().map_or(false, |llvm_config_path| { - println!("llvm_config_path={:?}", llvm_config_path); - llvm_config_path.exists() - && llvm_config_path.is_file() - && llvm_config_path - .file_name() - .unwrap() - .to_string_lossy() - .starts_with("llvm-config") - })); - } - - fn build_bitcode_files(label: &str) -> bool { - let bitcode_filepaths = [ - PathBuf::from(format!("/tmp/{}_bar.bc", label)), - PathBuf::from(format!("/tmp/{}_baz.bc", label)), - PathBuf::from(format!("/tmp/{}_foo.bc", label)), - ]; - - let input_args = [ - [ - "-c", - "-emit-llvm", - "-o", - bitcode_filepaths[0].to_str().unwrap(), - "tests/data/bar.c", - ], - [ - "-c", - "-emit-llvm", - "-o", - bitcode_filepaths[1].to_str().unwrap(), - "tests/data/baz.c", - ], - [ - "-c", - "-emit-llvm", - "-o", - bitcode_filepaths[2].to_str().unwrap(), - "tests/data/foo.c", - ], - ]; - - input_args.iter().all(|args| { - let mut cc = ClangWrapper::new("rllvm", CompilerKind::Clang); - cc.parse_args(args) - .unwrap() - .run() - .unwrap() - .map_or(false, |code| code == 0) - }) - } - - #[test] - fn test_link_bitcode_files() { - // Prepare input bitcode files - assert!(build_bitcode_files("link")); - - let bitcode_filepaths = [ - Path::new("/tmp/link_bar.bc"), - Path::new("/tmp/link_baz.bc"), - Path::new("/tmp/link_foo.bc"), - ]; - - let output_filepath = Path::new("/tmp/foo_bar_baz.bc"); - - assert!( - link_bitcode_files(&bitcode_filepaths, output_filepath).map_or_else( - |err| { - println!("Failed to link bitcode files: {:?}", err); - false - }, - |code| { code.map_or(false, |code| code == 0) } - ) - ); - - // Check if the output file is successfully created - assert!(output_filepath.exists() && output_filepath.is_file()); - - // Clean - fs::remove_file(output_filepath).expect("Failed to delete the output bitcode file"); - bitcode_filepaths.iter().for_each(|&bitcode_filepath| { - fs::remove_file(bitcode_filepath).expect("Failed to delete the input bitcode file") - }); - } - - #[test] - fn test_archive_bitcode_files() { - // Prepare input bitcode files - assert!(build_bitcode_files("archive")); - - let bitcode_filepaths = [ - Path::new("/tmp/archive_bar.bc"), - Path::new("/tmp/archive_baz.bc"), - Path::new("/tmp/archive_foo.bc"), - ]; - - let output_filepath = Path::new("/tmp/foo_bar_baz.bca"); - - assert!( - archive_bitcode_files(&bitcode_filepaths, output_filepath).map_or_else( - |err| { - println!("Failed to archive bitcode files: {:?}", err); - false - }, - |code| { code.map_or(false, |code| code == 0) } - ) - ); - - // Check if the output file is successfully created - assert!(output_filepath.exists() && output_filepath.is_file()); - - // Check the type of the output archive - let output_data = fs::read(&output_filepath).expect("Failed to read the output file"); - assert!( - object::read::archive::ArchiveFile::parse(&*output_data).map_or_else( - |err| { - println!("Failed to parse the output file: {:?}", err); - false - }, - |output_archive_file| { - println!("Output archive file kind: {:?}", output_archive_file.kind()); - true - }, - ) - ); - - // Clean - fs::remove_file(output_filepath).expect("Failed to delete the output bitcode file"); - bitcode_filepaths.iter().for_each(|&bitcode_filepath| { - fs::remove_file(bitcode_filepath).expect("Failed to delete the input bitcode file") - }); - } -} diff --git a/tests/test_path_utils.rs b/tests/test_path_utils.rs deleted file mode 100644 index 7849c17..0000000 --- a/tests/test_path_utils.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[cfg(test)] -mod tests { - use std::path::Path; - - use rllvm::utils::*; - - #[test] - fn test_derive_object_and_bitcode_filepath() { - let test_inputs = [ - ( - Path::new("/tmp/foo.c"), - false, - (Path::new("/tmp/.foo.o"), Path::new("/tmp/.foo.o.bc")), - ), - ( - Path::new("/tmp/foo.c"), - true, - (Path::new("/tmp/foo.c.o"), Path::new("/tmp/.foo.o.bc")), - ), - ]; - - assert!(test_inputs.iter().all( - |&( - src_filepath, - is_compile_only, - (expected_object_filepath, expected_bitcode_filepath), - )| { - derive_object_and_bitcode_filepath(src_filepath, is_compile_only).map_or( - false, - |(object_filepath, bitcode_filepath)| { - object_filepath == expected_object_filepath - && bitcode_filepath == expected_bitcode_filepath - }, - ) - }, - )); - } -}