diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da33b4..1559f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` - Support for solc v0.8.29 - Decouples the solc JSON-input-output type definitions from the Solidity fronted and expose them via a dedicated crate. - `--supported-solc-versions` for `resolc` binary to return a `semver` range of supported `solc` versions. +- Support for passing LLVM command line options via the prcoess input or providing one or more `--llvm-arg='..'` resolc CLI flag. This allows more fine-grained control over the LLVM backend configuration. ### Changed - Runner `resolc` using webkit is no longer supported. diff --git a/Cargo.lock b/Cargo.lock index 17e7a4a..87524ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8368,6 +8368,7 @@ dependencies = [ "hex", "inkwell", "itertools 0.14.0", + "libc", "num", "polkavm-common 0.21.0", "polkavm-disassembler", diff --git a/crates/llvm-context/Cargo.toml b/crates/llvm-context/Cargo.toml index 28d564f..c3bdf71 100644 --- a/crates/llvm-context/Cargo.toml +++ b/crates/llvm-context/Cargo.toml @@ -22,6 +22,7 @@ num = { workspace = true } hex = { workspace = true } sha3 = { workspace = true } inkwell = { workspace = true } +libc = { workspace = true } polkavm-disassembler = { workspace = true } polkavm-common = { workspace = true } diff --git a/crates/llvm-context/src/lib.rs b/crates/llvm-context/src/lib.rs index 01b4080..181c870 100644 --- a/crates/llvm-context/src/lib.rs +++ b/crates/llvm-context/src/lib.rs @@ -1,9 +1,7 @@ //! The LLVM context library. -pub(crate) mod debug_config; -pub(crate) mod optimizer; -pub(crate) mod polkavm; -pub(crate) mod target_machine; +use std::ffi::CString; +use std::sync::OnceLock; pub use self::debug_config::ir_type::IRType as DebugConfigIR; pub use self::debug_config::DebugConfig; @@ -74,9 +72,41 @@ pub use self::polkavm::WriteLLVM as PolkaVMWriteLLVM; pub use self::target_machine::target::Target; pub use self::target_machine::TargetMachine; -/// Initializes the target machine. -pub fn initialize_target(target: Target) { +pub(crate) mod debug_config; +pub(crate) mod optimizer; +pub(crate) mod polkavm; +pub(crate) mod target_machine; + +static DID_INITIALIZE: OnceLock<()> = OnceLock::new(); + +/// Initializes the LLVM compiler backend. +/// +/// This is a no-op if called subsequentially. +/// +/// `llvm_arguments` are passed as-is to the LLVM CL options parser. +pub fn initialize_llvm(target: Target, name: &str, llvm_arguments: &[String]) { + let Ok(_) = DID_INITIALIZE.set(()) else { + return; // Tests don't go through a recursive process + }; + + let argv = [name.to_string()] + .iter() + .chain(llvm_arguments) + .map(|arg| CString::new(arg.as_bytes()).unwrap()) + .collect::>(); + let argv: Vec<*const libc::c_char> = argv.iter().map(|arg| arg.as_ptr()).collect(); + let overview = CString::new("").unwrap(); + unsafe { + inkwell::llvm_sys::support::LLVMParseCommandLineOptions( + argv.len() as i32, + argv.as_ptr(), + overview.as_ptr(), + ); + } + + inkwell::support::enable_llvm_pretty_stack_trace(); + match target { - Target::PVM => self::polkavm::initialize_target(), + Target::PVM => inkwell::targets::Target::initialize_riscv(&Default::default()), } } diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index d521196..e533b9c 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -83,6 +83,8 @@ where current_function: Option>>>, /// The loop context stack. loop_stack: Vec>, + /// The extra LLVM arguments that were used during target initialization. + llvm_arguments: &'ctx [String], /// The project dependency manager. It can be any entity implementing the trait. /// The manager is used to get information about contracts and their dependencies during @@ -223,6 +225,7 @@ where dependency_manager: Option, include_metadata_hash: bool, debug_config: DebugConfig, + llvm_arguments: &'ctx [String], ) -> Self { Self::set_data_layout(llvm, &module); Self::link_stdlib_module(llvm, &module); @@ -250,6 +253,7 @@ where functions: HashMap::with_capacity(Self::FUNCTIONS_HASHMAP_INITIAL_CAPACITY), current_function: None, loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), + llvm_arguments, dependency_manager, include_metadata_hash, @@ -639,6 +643,7 @@ where self.optimizer.settings().to_owned(), self.include_metadata_hash, self.debug_config.clone(), + self.llvm_arguments, ) }) } diff --git a/crates/llvm-context/src/polkavm/context/tests.rs b/crates/llvm-context/src/polkavm/context/tests.rs index 3e40481..73a2f72 100644 --- a/crates/llvm-context/src/polkavm/context/tests.rs +++ b/crates/llvm-context/src/polkavm/context/tests.rs @@ -10,12 +10,20 @@ pub fn create_context( llvm: &inkwell::context::Context, optimizer_settings: OptimizerSettings, ) -> Context { - crate::polkavm::initialize_target(); + crate::initialize_llvm(crate::Target::PVM, "resolc", Default::default()); let module = llvm.create_module("test"); let optimizer = Optimizer::new(optimizer_settings); - Context::::new(llvm, module, optimizer, None, true, Default::default()) + Context::::new( + llvm, + module, + optimizer, + None, + true, + Default::default(), + Default::default(), + ) } #[test] diff --git a/crates/llvm-context/src/polkavm/mod.rs b/crates/llvm-context/src/polkavm/mod.rs index f486d4a..f2ad22e 100644 --- a/crates/llvm-context/src/polkavm/mod.rs +++ b/crates/llvm-context/src/polkavm/mod.rs @@ -17,11 +17,6 @@ use sha3::Digest; use self::context::build::Build; use self::context::Context; -/// Initializes the PolkaVM target machine. -pub fn initialize_target() { - inkwell::targets::Target::initialize_riscv(&Default::default()); -} - /// Builds PolkaVM assembly text. pub fn build_assembly_text( contract_path: &str, @@ -94,6 +89,7 @@ pub trait Dependency { optimizer_settings: OptimizerSettings, include_metadata_hash: bool, debug_config: DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result; /// Resolves a full contract path. @@ -114,6 +110,7 @@ impl Dependency for DummyDependency { _optimizer_settings: OptimizerSettings, _include_metadata_hash: bool, _debug_config: DebugConfig, + _llvm_arguments: &[String], ) -> anyhow::Result { Ok(String::new()) } diff --git a/crates/solidity/src/lib.rs b/crates/solidity/src/lib.rs index 94169c1..2c5ff10 100644 --- a/crates/solidity/src/lib.rs +++ b/crates/solidity/src/lib.rs @@ -54,6 +54,7 @@ pub fn yul( optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result { let path = match input_files.len() { 1 => input_files.first().expect("Always exists"), @@ -74,7 +75,12 @@ pub fn yul( let solc_validator = Some(&*solc); let project = Project::try_from_yul_path(path, solc_validator)?; - let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; + let build = project.compile( + optimizer_settings, + include_metadata_hash, + debug_config, + llvm_arguments, + )?; Ok(build) } @@ -85,6 +91,7 @@ pub fn llvm_ir( optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result { let path = match input_files.len() { 1 => input_files.first().expect("Always exists"), @@ -97,7 +104,12 @@ pub fn llvm_ir( let project = Project::try_from_llvm_ir_path(path)?; - let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; + let build = project.compile( + optimizer_settings, + include_metadata_hash, + debug_config, + llvm_arguments, + )?; Ok(build) } @@ -118,6 +130,7 @@ pub fn standard_output( remappings: Option>, suppressed_warnings: Option>, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result { let solc_version = solc.version()?; @@ -171,7 +184,12 @@ pub fn standard_output( &debug_config, )?; - let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; + let build = project.compile( + optimizer_settings, + include_metadata_hash, + debug_config, + llvm_arguments, + )?; Ok(build) } @@ -184,6 +202,7 @@ pub fn standard_json( include_paths: Vec, allow_paths: Option, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result<()> { let solc_version = solc.version()?; @@ -226,7 +245,12 @@ pub fn standard_json( let missing_libraries = project.get_missing_libraries(); missing_libraries.write_to_standard_json(&mut solc_output, &solc_version)?; } else { - let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; + let build = project.compile( + optimizer_settings, + include_metadata_hash, + debug_config, + llvm_arguments, + )?; build.write_to_standard_json(&mut solc_output, &solc_version)?; } serde_json::to_writer(std::io::stdout(), &solc_output)?; @@ -252,6 +276,7 @@ pub fn combined_json( debug_config: revive_llvm_context::DebugConfig, output_directory: Option, overwrite: bool, + llvm_arguments: &[String], ) -> anyhow::Result<()> { let build = standard_output( input_files, @@ -267,6 +292,7 @@ pub fn combined_json( remappings, suppressed_warnings, debug_config, + llvm_arguments, )?; let mut combined_json = solc.combined_json(input_files, format.as_str())?; diff --git a/crates/solidity/src/process/input.rs b/crates/solidity/src/process/input.rs index e80290e..b2e3825 100644 --- a/crates/solidity/src/process/input.rs +++ b/crates/solidity/src/process/input.rs @@ -20,6 +20,8 @@ pub struct Input { pub optimizer_settings: revive_llvm_context::OptimizerSettings, /// The debug output config. pub debug_config: revive_llvm_context::DebugConfig, + /// The extra LLVM arguments give used for manual control. + pub llvm_arguments: Vec, } impl Input { @@ -30,6 +32,7 @@ impl Input { include_metadata_hash: bool, optimizer_settings: revive_llvm_context::OptimizerSettings, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: Vec, ) -> Self { Self { contract, @@ -37,6 +40,7 @@ impl Input { include_metadata_hash, optimizer_settings, debug_config, + llvm_arguments, } } } diff --git a/crates/solidity/src/process/mod.rs b/crates/solidity/src/process/mod.rs index 883349f..815a8fd 100644 --- a/crates/solidity/src/process/mod.rs +++ b/crates/solidity/src/process/mod.rs @@ -37,11 +37,19 @@ pub trait Process { } let input: Input = revive_common::deserialize_from_slice(buffer.as_slice())?; + + revive_llvm_context::initialize_llvm( + revive_llvm_context::Target::PVM, + crate::DEFAULT_EXECUTABLE_NAME, + &input.llvm_arguments, + ); + let result = input.contract.compile( input.project, input.optimizer_settings, input.include_metadata_hash, input.debug_config, + &input.llvm_arguments, ); match result { diff --git a/crates/solidity/src/project/contract/metadata.rs b/crates/solidity/src/project/contract/metadata.rs index c7bc4ea..41b0b81 100644 --- a/crates/solidity/src/project/contract/metadata.rs +++ b/crates/solidity/src/project/contract/metadata.rs @@ -18,6 +18,8 @@ pub struct Metadata { pub revive_version: String, /// The PolkaVM compiler optimizer settings. pub optimizer_settings: revive_llvm_context::OptimizerSettings, + /// The extra LLVM arguments give used for manual control. + pub llvm_arguments: Vec, } impl Metadata { @@ -27,6 +29,7 @@ impl Metadata { solc_version: String, revive_pallet_version: Option, optimizer_settings: revive_llvm_context::OptimizerSettings, + llvm_arguments: Vec, ) -> Self { Self { solc_metadata, @@ -34,6 +37,7 @@ impl Metadata { revive_pallet_version, revive_version: ResolcVersion::default().long, optimizer_settings, + llvm_arguments, } } } diff --git a/crates/solidity/src/project/contract/mod.rs b/crates/solidity/src/project/contract/mod.rs index 8b6e958..6e2f8cb 100644 --- a/crates/solidity/src/project/contract/mod.rs +++ b/crates/solidity/src/project/contract/mod.rs @@ -77,6 +77,7 @@ impl Contract { optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result { let llvm = inkwell::context::Context::create(); let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings); @@ -89,6 +90,7 @@ impl Contract { version.long.clone(), version.l2_revision.clone(), optimizer.settings().to_owned(), + llvm_arguments.to_vec(), ); let metadata_json = serde_json::to_value(&metadata).expect("Always valid"); let metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]> = if include_metadata_hash @@ -120,6 +122,7 @@ impl Contract { Some(project), include_metadata_hash, debug_config, + llvm_arguments, ); context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default()); match self.ir { diff --git a/crates/solidity/src/project/mod.rs b/crates/solidity/src/project/mod.rs index 1d43583..e92c22a 100644 --- a/crates/solidity/src/project/mod.rs +++ b/crates/solidity/src/project/mod.rs @@ -66,6 +66,7 @@ impl Project { optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result { let project = self.clone(); #[cfg(feature = "parallel")] @@ -81,6 +82,7 @@ impl Project { include_metadata_hash, optimizer_settings.clone(), debug_config.clone(), + llvm_arguments.to_vec(), ); let process_output = { #[cfg(target_os = "emscripten")] @@ -316,6 +318,7 @@ impl revive_llvm_context::PolkaVMDependency for Project { optimizer_settings: revive_llvm_context::OptimizerSettings, include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, + llvm_arguments: &[String], ) -> anyhow::Result { let contract_path = project.resolve_path(identifier)?; let contract = project @@ -335,6 +338,7 @@ impl revive_llvm_context::PolkaVMDependency for Project { optimizer_settings, include_metadata_hash, debug_config, + llvm_arguments, ) .map_err(|error| { anyhow::anyhow!( diff --git a/crates/solidity/src/resolc/arguments.rs b/crates/solidity/src/resolc/arguments.rs index c777b6b..b9cafae 100644 --- a/crates/solidity/src/resolc/arguments.rs +++ b/crates/solidity/src/resolc/arguments.rs @@ -166,6 +166,10 @@ pub struct Arguments { #[cfg(debug_assertions)] #[arg(long = "recursive-process-input")] pub recursive_process_input: Option, + + #[arg(long = "llvm-arg")] + /// These are passed to LLVM as the command line to allow manual control. + pub llvm_arguments: Vec, } impl Arguments { diff --git a/crates/solidity/src/resolc/main.rs b/crates/solidity/src/resolc/main.rs index 632346c..92af664 100644 --- a/crates/solidity/src/resolc/main.rs +++ b/crates/solidity/src/resolc/main.rs @@ -64,8 +64,6 @@ fn main_inner() -> anyhow::Result<()> { .stack_size(RAYON_WORKER_STACK_SIZE) .build_global() .expect("Thread pool configuration failure"); - inkwell::support::enable_llvm_pretty_stack_trace(); - revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); // TODO: pass from CLI if arguments.recursive_process { #[cfg(debug_assertions)] @@ -157,6 +155,7 @@ fn main_inner() -> anyhow::Result<()> { optimizer_settings, include_metadata_hash, debug_config, + &arguments.llvm_arguments, ) } else if arguments.llvm_ir { revive_solidity::llvm_ir( @@ -164,6 +163,7 @@ fn main_inner() -> anyhow::Result<()> { optimizer_settings, include_metadata_hash, debug_config, + &arguments.llvm_arguments, ) } else if arguments.standard_json { revive_solidity::standard_json( @@ -173,6 +173,7 @@ fn main_inner() -> anyhow::Result<()> { arguments.include_paths, arguments.allow_paths, debug_config, + &arguments.llvm_arguments, )?; return Ok(()); } else if let Some(format) = arguments.combined_json { @@ -193,6 +194,7 @@ fn main_inner() -> anyhow::Result<()> { debug_config, arguments.output_directory, arguments.overwrite, + &arguments.llvm_arguments, )?; return Ok(()); } else { @@ -210,6 +212,7 @@ fn main_inner() -> anyhow::Result<()> { remappings, suppressed_warnings, debug_config, + &arguments.llvm_arguments, ) }?; diff --git a/crates/solidity/src/test_utils.rs b/crates/solidity/src/test_utils.rs index 4879f30..ef1caff 100644 --- a/crates/solidity/src/test_utils.rs +++ b/crates/solidity/src/test_utils.rs @@ -73,7 +73,11 @@ pub fn build_solidity_with_options( check_dependencies(); inkwell::support::enable_llvm_pretty_stack_trace(); - revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); + revive_llvm_context::initialize_llvm( + revive_llvm_context::Target::PVM, + crate::DEFAULT_EXECUTABLE_NAME, + &[], + ); let _ = crate::process::native_process::EXECUTABLE .set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); @@ -106,7 +110,8 @@ pub fn build_solidity_with_options( &DEBUG_CONFIG, )?; - let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?; + let build: crate::Build = + project.compile(optimizer_settings, false, DEBUG_CONFIG, Default::default())?; build.write_to_standard_json(&mut output, &solc_version)?; Ok(output) @@ -122,7 +127,11 @@ pub fn build_solidity_with_options_evm( check_dependencies(); inkwell::support::enable_llvm_pretty_stack_trace(); - revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); + revive_llvm_context::initialize_llvm( + revive_llvm_context::Target::PVM, + crate::DEFAULT_EXECUTABLE_NAME, + &[], + ); let _ = crate::process::native_process::EXECUTABLE .set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); @@ -174,7 +183,11 @@ pub fn build_solidity_and_detect_missing_libraries( check_dependencies(); inkwell::support::enable_llvm_pretty_stack_trace(); - revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); + revive_llvm_context::initialize_llvm( + revive_llvm_context::Target::PVM, + crate::DEFAULT_EXECUTABLE_NAME, + &[], + ); let _ = crate::process::native_process::EXECUTABLE .set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); @@ -213,7 +226,11 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> { check_dependencies(); inkwell::support::enable_llvm_pretty_stack_trace(); - revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); + revive_llvm_context::initialize_llvm( + revive_llvm_context::Target::PVM, + crate::DEFAULT_EXECUTABLE_NAME, + &[], + ); let optimizer_settings = revive_llvm_context::OptimizerSettings::none(); let project = Project::try_from_yul_string::( @@ -221,7 +238,7 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> { source_code, None, )?; - let _build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?; + let _build = project.compile(optimizer_settings, false, DEBUG_CONFIG, Default::default())?; Ok(()) }