Suport passing arbitrary llvm arguments (#271)

- 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.
- Make LLVM initialization idempotent.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
xermicus
2025-04-03 13:21:00 +02:00
committed by GitHub
parent 8a98346a9c
commit 87c1d7a8be
16 changed files with 142 additions and 26 deletions
+1
View File
@@ -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.
Generated
+1
View File
@@ -8368,6 +8368,7 @@ dependencies = [
"hex",
"inkwell",
"itertools 0.14.0",
"libc",
"num",
"polkavm-common 0.21.0",
"polkavm-disassembler",
+1
View File
@@ -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 }
+37 -7
View File
@@ -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::<Vec<_>>();
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()),
}
}
@@ -83,6 +83,8 @@ where
current_function: Option<Rc<RefCell<Function<'ctx>>>>,
/// The loop context stack.
loop_stack: Vec<Loop<'ctx>>,
/// 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<D>,
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,
)
})
}
@@ -10,12 +10,20 @@ pub fn create_context(
llvm: &inkwell::context::Context,
optimizer_settings: OptimizerSettings,
) -> Context<DummyDependency> {
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::<DummyDependency>::new(llvm, module, optimizer, None, true, Default::default())
Context::<DummyDependency>::new(
llvm,
module,
optimizer,
None,
true,
Default::default(),
Default::default(),
)
}
#[test]
+2 -5
View File
@@ -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<String>;
/// 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<String> {
Ok(String::new())
}
+30 -4
View File
@@ -54,6 +54,7 @@ pub fn yul<T: Compiler>(
optimizer_settings: revive_llvm_context::OptimizerSettings,
include_metadata_hash: bool,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
) -> anyhow::Result<Build> {
let path = match input_files.len() {
1 => input_files.first().expect("Always exists"),
@@ -74,7 +75,12 @@ pub fn yul<T: Compiler>(
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<Build> {
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<T: Compiler>(
remappings: Option<BTreeSet<String>>,
suppressed_warnings: Option<Vec<ResolcWarning>>,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
) -> anyhow::Result<Build> {
let solc_version = solc.version()?;
@@ -171,7 +184,12 @@ pub fn standard_output<T: Compiler>(
&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<T: Compiler>(
include_paths: Vec<String>,
allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig,
llvm_arguments: &[String],
) -> anyhow::Result<()> {
let solc_version = solc.version()?;
@@ -226,7 +245,12 @@ pub fn standard_json<T: Compiler>(
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<T: Compiler>(
debug_config: revive_llvm_context::DebugConfig,
output_directory: Option<PathBuf>,
overwrite: bool,
llvm_arguments: &[String],
) -> anyhow::Result<()> {
let build = standard_output(
input_files,
@@ -267,6 +292,7 @@ pub fn combined_json<T: Compiler>(
remappings,
suppressed_warnings,
debug_config,
llvm_arguments,
)?;
let mut combined_json = solc.combined_json(input_files, format.as_str())?;
+4
View File
@@ -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<String>,
}
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<String>,
) -> Self {
Self {
contract,
@@ -37,6 +40,7 @@ impl Input {
include_metadata_hash,
optimizer_settings,
debug_config,
llvm_arguments,
}
}
}
+8
View File
@@ -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 {
@@ -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<String>,
}
impl Metadata {
@@ -27,6 +29,7 @@ impl Metadata {
solc_version: String,
revive_pallet_version: Option<semver::Version>,
optimizer_settings: revive_llvm_context::OptimizerSettings,
llvm_arguments: Vec<String>,
) -> Self {
Self {
solc_metadata,
@@ -34,6 +37,7 @@ impl Metadata {
revive_pallet_version,
revive_version: ResolcVersion::default().long,
optimizer_settings,
llvm_arguments,
}
}
}
@@ -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<ContractBuild> {
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 {
+4
View File
@@ -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<Build> {
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<String> {
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!(
+4
View File
@@ -166,6 +166,10 @@ pub struct Arguments {
#[cfg(debug_assertions)]
#[arg(long = "recursive-process-input")]
pub recursive_process_input: Option<String>,
#[arg(long = "llvm-arg")]
/// These are passed to LLVM as the command line to allow manual control.
pub llvm_arguments: Vec<String>,
}
impl Arguments {
+5 -2
View File
@@ -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,
)
}?;
+23 -6
View File
@@ -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::<SolcCompiler>(
@@ -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(())
}