From f98c15121d69b51d0013f43f1c0edf796698d122 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 8 Aug 2025 17:43:12 +0100 Subject: [PATCH] Add ability for compiler to opt out if it can't work with some Mode/version --- crates/compiler/src/lib.rs | 17 +++-- crates/compiler/src/revive_resolc.rs | 17 ++++- crates/compiler/src/solc.rs | 17 ++++- crates/core/src/main.rs | 108 +++++++++++++++++---------- crates/format/src/mode.rs | 10 +-- 5 files changed, 115 insertions(+), 54 deletions(-) diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 4a6835c..c3a6fb8 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -20,7 +20,7 @@ use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; // Re-export this as it's a part of the compiler interface. -pub use revive_dt_format::mode::ModeOptimizerSetting; +pub use revive_dt_format::mode::{Mode, ModeOptimizerSetting, ModePipeline}; pub mod revive_js; pub mod revive_resolc; @@ -46,13 +46,20 @@ pub trait SolidityCompiler { ) -> impl Future>; fn version(&self) -> anyhow::Result; + + /// Does the compiler support the provided mode and version settings? + fn supports_mode( + compiler_version: &Version, + optimize_setting: ModeOptimizerSetting, + pipeline: ModePipeline, + ) -> bool; } /// The generic compilation input configuration. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CompilerInput { + pub pipeline: Option, pub optimization: Option, - pub via_ir: Option, pub evm_version: Option, pub allow_paths: Vec, pub base_path: Option, @@ -87,8 +94,8 @@ where pub fn new() -> Self { Self { input: CompilerInput { + pipeline: Default::default(), optimization: Default::default(), - via_ir: Default::default(), evm_version: Default::default(), allow_paths: Default::default(), base_path: Default::default(), @@ -104,8 +111,8 @@ where self } - pub fn with_via_ir(mut self, value: impl Into>) -> Self { - self.input.via_ir = value.into(); + pub fn with_pipeline(mut self, value: impl Into>) -> Self { + self.input.pipeline = value.into(); self } diff --git a/crates/compiler/src/revive_resolc.rs b/crates/compiler/src/revive_resolc.rs index d5abf71..bdb88af 100644 --- a/crates/compiler/src/revive_resolc.rs +++ b/crates/compiler/src/revive_resolc.rs @@ -14,7 +14,7 @@ use revive_solc_json_interface::{ SolcStandardJsonOutput, }; -use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, SolidityCompiler}; +use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler}; use alloy::json_abi::JsonAbi; use anyhow::Context; @@ -39,9 +39,9 @@ impl SolidityCompiler for Resolc { async fn build( &self, CompilerInput { + // Ignored as we only support one pipeline (Y) + pipeline, optimization, - // Ignored and not honored since this is required for the resolc compilation. - via_ir: _via_ir, evm_version, allow_paths, base_path, @@ -231,6 +231,17 @@ impl SolidityCompiler for Resolc { Version::parse(version_string).map_err(Into::into) } + + fn supports_mode( + compiler_version: &Version, + _optimize_setting: ModeOptimizerSetting, + pipeline: ModePipeline, + ) -> bool { + // We only support the Y (IE compile via Yul IR) mode here, which also means that we can + // only use solc version 0.8.13 and above. We must always compile via Yul IR as resolc + // needs this to translate to LLVM IR and then RISCV. + pipeline == ModePipeline::Y && compiler_version >= &Version::new(0, 8, 13) + } } #[cfg(test)] diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index 4312bee..ba80458 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -10,7 +10,7 @@ use revive_dt_common::types::VersionOrRequirement; use revive_dt_config::Arguments; use revive_dt_solc_binaries::download_solc; -use crate::{CompilerInput, CompilerOutput, SolidityCompiler}; +use crate::{CompilerInput, CompilerOutput, ModeOptimizerSetting, ModePipeline, SolidityCompiler}; use anyhow::Context; use foundry_compilers_artifacts::{ @@ -35,8 +35,8 @@ impl SolidityCompiler for Solc { async fn build( &self, CompilerInput { + pipeline, optimization, - via_ir, evm_version, allow_paths, base_path, @@ -70,7 +70,7 @@ impl SolidityCompiler for Solc { .map(|item| item.to_string()), ), evm_version: evm_version.map(|version| version.to_string().parse().unwrap()), - via_ir, + via_ir: pipeline.map(|p| p.via_yul_ir()), libraries: Libraries { libs: libraries .into_iter() @@ -212,6 +212,17 @@ impl SolidityCompiler for Solc { Version::parse(version_string).map_err(Into::into) } + + fn supports_mode( + compiler_version: &Version, + _optimize_setting: ModeOptimizerSetting, + pipeline: ModePipeline, + ) -> bool { + // solc 0.8.13 and above supports --via-ir, and less than that does not. Thus, we support mode E + // (ie no Yul IR) in either case, but only support Y (via Yul IR) if the compiler is new enough. + pipeline == ModePipeline::E + || (pipeline == ModePipeline::Y && compiler_version >= &Version::new(0, 8, 13)) + } } #[cfg(test)] diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 50daf01..67193ac 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -324,7 +324,10 @@ where .await; let mut metadata_case_status = metadata_case_status.write().await; match result { - Ok(inputs_executed) => { + Ok(None) => { + tracing::info!("Execution skipped"); + } + Ok(Some(inputs_executed)) => { tracing::info!(inputs_executed, "Execution succeeded"); metadata_case_status .entry((metadata_file_path.clone(), solc_mode)) @@ -362,14 +365,14 @@ async fn handle_case_driver<'a, L, F>( leader_node: &L::Blockchain, follower_node: &F::Blockchain, _: Span, -) -> anyhow::Result +) -> anyhow::Result> where L: Platform, F: Platform, L::Blockchain: revive_dt_node::Node + Send + Sync + 'static, F::Blockchain: revive_dt_node::Node + Send + Sync + 'static, { - let leader_pre_link_contracts = get_or_build_contracts::( + let Some(leader_pre_link_contracts) = get_or_build_contracts::( metadata, metadata_file_path, mode.clone(), @@ -377,8 +380,11 @@ where compilation_cache.clone(), &HashMap::new(), ) - .await?; - let follower_pre_link_contracts = get_or_build_contracts::( + .await? + else { + return Ok(None); + }; + let Some(follower_pre_link_contracts) = get_or_build_contracts::( metadata, metadata_file_path, mode.clone(), @@ -386,7 +392,10 @@ where compilation_cache.clone(), &HashMap::new(), ) - .await?; + .await? + else { + return Ok(None); + }; let mut leader_deployed_libraries = HashMap::new(); let mut follower_deployed_libraries = HashMap::new(); @@ -537,7 +546,7 @@ where cache.remove(&follower_key); } - let leader_post_link_contracts = get_or_build_contracts::( + let Some(leader_post_link_contracts) = get_or_build_contracts::( metadata, metadata_file_path, mode.clone(), @@ -545,8 +554,11 @@ where compilation_cache.clone(), &leader_deployed_libraries, ) - .await?; - let follower_post_link_contracts = get_or_build_contracts::( + .await? + else { + return Ok(None); + }; + let Some(follower_post_link_contracts) = get_or_build_contracts::( metadata, metadata_file_path, mode.clone(), @@ -554,7 +566,10 @@ where compilation_cache, &follower_deployed_libraries, ) - .await?; + .await? + else { + return Ok(None); + }; (leader_post_link_contracts, follower_post_link_contracts) } else { @@ -581,7 +596,7 @@ where leader_state, follower_state, ); - driver.execute().await + driver.execute().await.map(Some) } async fn get_or_build_contracts<'a, P: Platform>( @@ -591,29 +606,32 @@ async fn get_or_build_contracts<'a, P: Platform>( config: &Arguments, compilation_cache: CompilationCache<'a>, deployed_libraries: &HashMap, -) -> anyhow::Result> { +) -> anyhow::Result>> { let key = (metadata_file_path, mode.clone(), P::config_id()); if let Some(compilation_artifact) = compilation_cache.read().await.get(&key).cloned() { let mut compilation_artifact = compilation_artifact.lock().await; match *compilation_artifact { Some(ref compiled_contracts) => { tracing::debug!(?key, "Compiled contracts cache hit"); - return Ok(compiled_contracts.clone()); + return Ok(Some(compiled_contracts.clone())); } None => { tracing::debug!(?key, "Compiled contracts cache miss"); - let compiled_contracts = Arc::new( - compile_contracts::

( - metadata, - metadata_file_path, - &mode, - config, - deployed_libraries, - ) - .await?, - ); + let Some(compiled_contracts) = compile_contracts::

( + metadata, + metadata_file_path, + &mode, + config, + deployed_libraries, + ) + .await? + else { + return Ok(None); + }; + let compiled_contracts = Arc::new(compiled_contracts); + *compilation_artifact = Some(compiled_contracts.clone()); - return Ok(compiled_contracts.clone()); + return Ok(Some(compiled_contracts.clone())); } } }; @@ -626,18 +644,22 @@ async fn get_or_build_contracts<'a, P: Platform>( mutex }; let mut compilation_artifact = mutex.lock().await; - let compiled_contracts = Arc::new( - compile_contracts::

( - metadata, - metadata_file_path, - &mode, - config, - deployed_libraries, - ) - .await?, - ); + + let Some(compiled_contracts) = compile_contracts::

( + metadata, + metadata_file_path, + &mode, + config, + deployed_libraries, + ) + .await? + else { + return Ok(None); + }; + let compiled_contracts = Arc::new(compiled_contracts); + *compilation_artifact = Some(compiled_contracts.clone()); - Ok(compiled_contracts.clone()) + Ok(Some(compiled_contracts.clone())) } async fn compile_contracts( @@ -646,12 +668,22 @@ async fn compile_contracts( mode: &Mode, config: &Arguments, deployed_libraries: &HashMap, -) -> anyhow::Result<(Version, CompilerOutput)> { +) -> anyhow::Result> { let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone()); let compiler_path = P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?; let compiler_version = P::Compiler::new(compiler_path.clone()).version()?; + if !P::Compiler::supports_mode(&compiler_version, mode.optimize_setting, mode.pipeline) { + tracing::info!( + %compiler_version, + metadata_file_path = %metadata_file_path.display(), + mode = ?mode, + "Skipping compilation: compiler does not support this mode" + ); + return Ok(None); + } + tracing::info!( %compiler_version, metadata_file_path = %metadata_file_path.display(), @@ -662,7 +694,7 @@ async fn compile_contracts( let compiler = Compiler::::new() .with_allow_path(metadata.directory()?) .with_optimization(mode.optimize_setting) - .with_via_ir(mode.via_yul_ir()); + .with_pipeline(mode.pipeline); let mut compiler = metadata .files_to_compile()? .try_fold(compiler, |compiler, path| compiler.with_source(&path))?; @@ -688,7 +720,7 @@ async fn compile_contracts( let compiler_output = compiler.try_build(compiler_path).await?; - Ok((compiler_version, compiler_output)) + Ok(Some((compiler_version, compiler_output))) } async fn execute_corpus( diff --git a/crates/format/src/mode.rs b/crates/format/src/mode.rs index 91a8062..41289d2 100644 --- a/crates/format/src/mode.rs +++ b/crates/format/src/mode.rs @@ -61,11 +61,6 @@ impl Mode { None => default.into(), } } - - /// Should we go via Yul IR? - pub fn via_yul_ir(&self) -> bool { - self.pipeline == ModePipeline::Y - } } /// This represents a mode that has been parsed from test metadata. @@ -281,6 +276,11 @@ impl Display for ModePipeline { } impl ModePipeline { + /// Should we go via Yul IR? + pub fn via_yul_ir(&self) -> bool { + matches!(self, ModePipeline::Y) + } + /// An iterator over the available pipelines that we'd like to test, /// when an explicit pipeline was not specified. pub fn test_cases() -> impl Iterator + Clone {