mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-11-09 12:17:12 +00:00
The new Yul optimizer toolkit (#519)
This PR introduces powerful code size optimizations. This is achieved with an additional IR layer in between Yul and LLVM IR. It works quite well (see below for a comparison) because it captures optimization gains that neither solc nor LLVM can realize. The feature is gated behind `--newyork` or a corresponding standard JSON input setting and **off by default**. ### How it works We implemented an SSA IR modelled after or inspired by the [MLIR SCF dialect](https://mlir.llvm.org/docs/Dialects/SCFDialect/). Yul is translated into this IR and the IR implements mainly the following optimizations: - **Type narrowing**: narrows 256-bit values to I1..I160 where provable (bidirectional + interprocedural), so LLVM emits native 32/64-bit ops instead of software 256-bit arithmetic. - **FMP range proof**: exposes the `mload(0x40) < heap_size` bound to LLVM IPSCCP, eliminating trailing overflow checks on FMP-derived offsets. Single largest code-size contributor. - **Heap analysis**: skips big-endian byte-swaps where native little-endian layout is safe. - **mem_opt / keccak fusion+folding**, **compound outlining** (mapping sload/sstore), **guard narrowing**, **inlining** tuned for PolkaVM, and **exact + fuzzy function dedup**. ### Results The optimizations implemented with the newyork IR bring substantial code size improvements. - Integration contracts: approximately **-20% to -50%** code size. - OpenZeppelin Wizard corpus: around **-47%** code size. This table shows the code size improvements on various OpenZeppelin ERC implementations, taken from their [contracts wizard](https://wizard.openzeppelin.com/), with almost all features enabled. | Contract | Default: (bytes) | With `--newyork`: (bytes) | Reduction | |---|---:|---:|---:| | erc1155 | 59,206 | 30,649 | -48.2% | | erc20 | 81,697 | 42,893 | -47.5% | | erc721 | 92,052 | 49,423 | -46.3% | | oz_gov | 141,861 | 81,159 | -42.8% | | oz_rwa | 77,274 | 37,975 | -50.9% | | oz_simple_erc20 | 29,950 | 16,554 | -44.7% | | oz_stable | 82,117 | 39,032 | -52.5% | | **Total** | **564,157** | **297,685** | **-47.2%** | We believe this to be a reasonably representative dataset and conclude that the newyork IR achieves drastic code size reductions on real world dApp implementations. ### Methodology, security and runtime performance Since the main contributors to these improvements are the FMP range proofs and the type narrowing, we don't expect big runtime performance regressions (it may even benefit runtime performance in some cases). The PR also includes docs (`book/src/developer_guide/newyork_{optimizer,ir}.md`) and many new integration tests, including regression tests for known FMP soundness traps. The PR was authored collaboratively and reviewed extensively by the `revive` maintainers, as well as Claude Opus, Claude Fable, Qwen, Minimax and Deepseek LLMs, over a span of many months. The `newyork` compiler pipeline introduces no new regressions over our integration and differential test suites (containing over 30000 test cases). This was achieved by careful manual reviews and many LLM bughunt loops. Additionally, a final security review by Anthropic's Fable 5 LLM found no remaining soundness issues. Nonetheless, like any brand new software, please consider this an experimental feature upon release. --------- Signed-off-by: xermicus <bigcyrill@hotmail.com> Signed-off-by: xermicus <cyrill@parity.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: neo <me@90f9.org> Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com>
This commit is contained in:
183 files changed
+49503
-561
No files matched your search
@@ -5,8 +5,10 @@ use std::collections::BTreeSet;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::newyork::NewYork;
|
||||
use self::yul::Yul;
|
||||
|
||||
pub mod newyork;
|
||||
pub mod yul;
|
||||
|
||||
/// The contract source code.
|
||||
@@ -15,13 +17,23 @@ pub mod yul;
|
||||
pub enum IR {
|
||||
/// The Yul source code.
|
||||
Yul(Yul),
|
||||
/// The newyork IR (via Yul translation).
|
||||
NewYork(NewYork),
|
||||
}
|
||||
|
||||
impl IR {
|
||||
/// Whether this contract is lowered through the newyork IR pipeline.
|
||||
pub fn is_newyork(&self) -> bool {
|
||||
matches!(self, Self::NewYork(_))
|
||||
}
|
||||
|
||||
/// Drains the list of factory dependencies.
|
||||
pub fn drain_factory_dependencies(&mut self) -> BTreeSet<String> {
|
||||
match self {
|
||||
IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(),
|
||||
IR::NewYork(ref mut newyork) => {
|
||||
newyork.yul_object.factory_dependencies.drain().collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +41,7 @@ impl IR {
|
||||
pub fn get_missing_libraries(&self) -> BTreeSet<String> {
|
||||
match self {
|
||||
Self::Yul(inner) => inner.get_missing_libraries(),
|
||||
Self::NewYork(inner) => inner.get_missing_libraries(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,3 +51,9 @@ impl From<Yul> for IR {
|
||||
Self::Yul(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NewYork> for IR {
|
||||
fn from(inner: NewYork) -> Self {
|
||||
Self::NewYork(inner)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
//! The contract compiled via newyork IR.
|
||||
//!
|
||||
//! This module integrates the newyork IR pipeline:
|
||||
//! 1. Parse Yul source to Yul AST
|
||||
//! 2. Translate Yul AST to newyork IR
|
||||
//! 3. Run heap optimization analysis
|
||||
//! 4. Generate LLVM IR from newyork IR
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use inkwell::debug_info::AsDIScope;
|
||||
use revive_llvm_context::PolkaVMCodeType;
|
||||
use revive_newyork::{LlvmCodegen, TranslationResult};
|
||||
use revive_yul::lexer::Lexer;
|
||||
use revive_yul::parser::statement::object::Object as YulObject;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The contract compiled via newyork IR.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct NewYork {
|
||||
/// The Yul AST object (source).
|
||||
pub yul_object: YulObject,
|
||||
}
|
||||
|
||||
impl NewYork {
|
||||
/// Transforms the `solc` standard JSON output contract into a Yul object
|
||||
/// for subsequent translation to newyork IR.
|
||||
pub fn try_from_source(source_code: &str) -> anyhow::Result<Option<Self>> {
|
||||
if source_code.is_empty() {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut lexer = Lexer::new(source_code.to_owned());
|
||||
let object = YulObject::parse(&mut lexer, None)
|
||||
.map_err(|error| anyhow::anyhow!("Yul parsing: {error:?}"))?;
|
||||
|
||||
Ok(Some(Self { yul_object: object }))
|
||||
}
|
||||
|
||||
/// Get the list of missing deployable libraries.
|
||||
pub fn get_missing_libraries(&self) -> BTreeSet<String> {
|
||||
self.yul_object.get_missing_libraries()
|
||||
}
|
||||
|
||||
/// Translate the Yul AST to newyork IR with heap optimization analysis.
|
||||
///
|
||||
/// `capture_ir_snapshot` requests the mid-pipeline IR snapshot, which is dumped via
|
||||
/// [`revive_llvm_context::DebugConfig`] when a debug output directory is configured.
|
||||
fn translate_to_ir(&self, capture_ir_snapshot: bool) -> anyhow::Result<TranslationResult> {
|
||||
revive_newyork::translate_yul_object(&self.yul_object, capture_ir_snapshot)
|
||||
.map_err(|e| anyhow::anyhow!("newyork IR translation: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Code-size threshold for emitting the outlined single-word keccak256 helper.
|
||||
///
|
||||
/// The helper body costs ~150 bytes; each call site it replaces saves ~20 bytes through
|
||||
/// deduplication. Fewer than this many sites and the helper costs more than it returns.
|
||||
const KECCAK_SINGLE_THRESHOLD: usize = 8;
|
||||
|
||||
/// Heap-operation threshold above which `__sbrk_internal` is marked NoInline.
|
||||
///
|
||||
/// `__sbrk_internal` has five basic blocks of bounds-checking; inlining it at too many
|
||||
/// sites bloats the binary beyond the call-overhead savings on PolkaVM.
|
||||
const SBRK_NOINLINE_THRESHOLD: usize = 30;
|
||||
|
||||
impl revive_llvm_context::PolkaVMWriteLLVM for NewYork {
|
||||
fn declare(&mut self, context: &mut revive_llvm_context::PolkaVMContext) -> anyhow::Result<()> {
|
||||
self.yul_object.declare(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMKeccak256TwoWordsFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMCallValueFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMCallValueNonzeroFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMCallDataLoadFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMCallerFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMRevertEmptyFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMRevertFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMRevertPanicFunction.declare(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext) -> anyhow::Result<()> {
|
||||
let translation_result =
|
||||
self.translate_to_ir(context.debug_config().output_directory.is_some())?;
|
||||
let ir_object = translation_result.object;
|
||||
let heap_opt = translation_result.heap_opt;
|
||||
let type_info = translation_result.type_info;
|
||||
let mem_opt = translation_result.mem_opt;
|
||||
let ir_snapshot = translation_result.ir_snapshot;
|
||||
|
||||
let inline_decisions: std::collections::BTreeMap<u32, revive_newyork::InlineDecision> =
|
||||
translation_result
|
||||
.inline_results
|
||||
.decisions
|
||||
.into_iter()
|
||||
.map(|(fid, decision)| (fid.0, decision))
|
||||
.collect();
|
||||
|
||||
let heap_op_count = ir_object.count_heap_operations();
|
||||
let keccak_single_count = ir_object.count_keccak256_single();
|
||||
let has_keccak_single = keccak_single_count >= KECCAK_SINGLE_THRESHOLD;
|
||||
let use_native_heap = heap_opt.all_native();
|
||||
|
||||
// Dump the newyork debug artifacts into the debug output directory, when configured.
|
||||
// The final IR is annotated with the inferred type widths so the narrow pass's effect is
|
||||
// visible; the snapshot is the IR before the late passes.
|
||||
context.debug_config().dump_newyork(
|
||||
None,
|
||||
&revive_newyork::print_object_with_types(&ir_object, &type_info),
|
||||
)?;
|
||||
if let Some(snapshot) = ir_snapshot.as_ref() {
|
||||
context
|
||||
.debug_config()
|
||||
.dump_newyork(Some("snapshot"), snapshot)?;
|
||||
}
|
||||
context.debug_config().dump_newyork(
|
||||
Some("heap"),
|
||||
&format!(
|
||||
"all_native={}, total={}, unknown={}, tainted={}, escaping={}, native_regions={:?}, native_offsets={:?}, dynamic_escapes={}, dynamic_accesses={}",
|
||||
use_native_heap,
|
||||
heap_opt.total_accesses,
|
||||
heap_opt.unknown_accesses,
|
||||
heap_opt.tainted_count,
|
||||
heap_opt.escaping_count,
|
||||
heap_opt.native_safe_regions,
|
||||
heap_opt.native_safe_offsets,
|
||||
heap_opt.has_dynamic_escapes,
|
||||
heap_opt.has_dynamic_accesses,
|
||||
),
|
||||
)?;
|
||||
context.debug_config().dump_newyork(
|
||||
Some("mem"),
|
||||
&format!(
|
||||
"loads_eliminated={}, stores_eliminated={}, values_tracked={}, keccak_pairs_fused={}, keccak_singles_fused={}, fmp_loads_eliminated={}",
|
||||
mem_opt.loads_eliminated,
|
||||
mem_opt.stores_eliminated,
|
||||
mem_opt.values_tracked,
|
||||
mem_opt.keccak_pairs_fused,
|
||||
mem_opt.keccak_singles_fused,
|
||||
mem_opt.fmp_loads_eliminated,
|
||||
),
|
||||
)?;
|
||||
|
||||
if let Some(debug_info) = context.debug_info() {
|
||||
let di_builder = debug_info.builder();
|
||||
let object_name: &str = self.yul_object.identifier.as_str();
|
||||
let di_parent_scope = debug_info
|
||||
.top_scope()
|
||||
.expect("expected an existing debug-info scope");
|
||||
let object_scope = di_builder.create_namespace(di_parent_scope, object_name, true);
|
||||
context.push_debug_scope(object_scope.as_debug_info_scope());
|
||||
}
|
||||
|
||||
context.set_debug_location(
|
||||
self.yul_object.location.line,
|
||||
self.yul_object.location.column,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// `NewYork` always wraps the top-level (deploy) object; the runtime
|
||||
// `_deployed` object is emitted as a subobject by `generate_object`,
|
||||
// which sets the runtime code type itself.
|
||||
assert!(
|
||||
!self.yul_object.identifier.ends_with("_deployed"),
|
||||
"ICE: newyork into_llvm expected the top-level object, got `{}`",
|
||||
self.yul_object.identifier,
|
||||
);
|
||||
|
||||
context.set_code_type(PolkaVMCodeType::Deploy);
|
||||
|
||||
revive_llvm_context::PolkaVMEntryFunction::default().into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMLoadImmutableDataFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMStoreImmutableDataFunction.into_llvm(context)?;
|
||||
|
||||
if use_native_heap {
|
||||
revive_llvm_context::PolkaVMLoadHeapWordNativeFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMStoreHeapWordNativeFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMLoadHeapWordNativeFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMStoreHeapWordNativeFunction.into_llvm(context)?;
|
||||
}
|
||||
revive_llvm_context::PolkaVMLoadHeapWordFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMStoreHeapWordFunction.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMLoadStorageWordFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMStoreStorageWordFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMLoadTransientStorageWordFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMStoreTransientStorageWordFunction.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMWordToPointerFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMExitFunction.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<0>.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<1>.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<2>.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<3>.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMEventLogFunction::<4>.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMDivisionFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMSignedDivisionFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?;
|
||||
|
||||
revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMKeccak256TwoWordsFunction.into_llvm(context)?;
|
||||
if has_keccak_single {
|
||||
revive_llvm_context::PolkaVMKeccak256OneWordFunction.declare(context)?;
|
||||
revive_llvm_context::PolkaVMKeccak256OneWordFunction.into_llvm(context)?;
|
||||
}
|
||||
revive_llvm_context::PolkaVMCallValueFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMCallValueNonzeroFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMCallDataLoadFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMCallerFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMRevertEmptyFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMRevertFunction.into_llvm(context)?;
|
||||
revive_llvm_context::PolkaVMRevertPanicFunction.into_llvm(context)?;
|
||||
|
||||
if heap_op_count > SBRK_NOINLINE_THRESHOLD {
|
||||
if let Some(sbrk_func) = context.get_function("__sbrk_internal", false) {
|
||||
revive_llvm_context::PolkaVMFunction::set_attributes(
|
||||
context.llvm(),
|
||||
sbrk_func.borrow().declaration(),
|
||||
&[revive_llvm_context::PolkaVMAttribute::NoInline],
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut codegen = LlvmCodegen::new(heap_opt, type_info, inline_decisions);
|
||||
codegen
|
||||
.generate_object(&ir_object, context)
|
||||
.map_err(|e| anyhow::anyhow!("newyork LLVM codegen: {e}"))?;
|
||||
|
||||
context.set_debug_location(
|
||||
self.yul_object.location.line,
|
||||
self.yul_object.location.column,
|
||||
None,
|
||||
)?;
|
||||
|
||||
context.pop_debug_scope();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ impl Contract {
|
||||
pub fn object_identifier(&self) -> &str {
|
||||
match self.ir {
|
||||
IR::Yul(ref yul) => yul.object.identifier.as_str(),
|
||||
IR::NewYork(ref newyork) => newyork.yul_object.identifier.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,21 +91,30 @@ impl Contract {
|
||||
};
|
||||
debug_config.set_contract_path(&self.identifier.full_path);
|
||||
|
||||
let full_path = self.identifier.full_path.as_str();
|
||||
let build = match self.ir {
|
||||
IR::Yul(mut yul) => {
|
||||
let module = llvm.create_module(self.identifier.full_path.as_str());
|
||||
let mut context =
|
||||
PolkaVMContext::new(&llvm, module, optimizer, debug_config, memory_config);
|
||||
context.set_solidity_data(PolkaVMContextSolidityData::default());
|
||||
let yul_data = PolkaVMContextYulData::new(identifier_paths);
|
||||
context.set_yul_data(yul_data);
|
||||
|
||||
yul.declare(&mut context)?;
|
||||
yul.into_llvm(&mut context)
|
||||
.map_err(|error| anyhow::anyhow!("LLVM IR generator: {error}"))?;
|
||||
|
||||
context.build(self.identifier.full_path.as_str(), metadata_bytes)?
|
||||
}
|
||||
IR::Yul(yul) => compile_ir(
|
||||
yul,
|
||||
&llvm,
|
||||
optimizer,
|
||||
false,
|
||||
debug_config,
|
||||
memory_config,
|
||||
identifier_paths,
|
||||
full_path,
|
||||
metadata_bytes,
|
||||
)?,
|
||||
IR::NewYork(newyork) => compile_ir(
|
||||
newyork,
|
||||
&llvm,
|
||||
optimizer,
|
||||
true,
|
||||
debug_config,
|
||||
memory_config,
|
||||
identifier_paths,
|
||||
full_path,
|
||||
metadata_bytes,
|
||||
)?,
|
||||
};
|
||||
|
||||
Ok(ContractBuild::new(
|
||||
@@ -126,3 +136,40 @@ impl Contract {
|
||||
.collect::<BTreeSet<String>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Lowers an IR object to an LLVM module and links it into a PolkaVM build.
|
||||
///
|
||||
/// Shared by the `Yul` and `NewYork` arms of [`Contract::compile`], which differ
|
||||
/// only in whether the newyork optimizer pipeline is enabled and in the error
|
||||
/// label attached to a codegen failure.
|
||||
fn compile_ir<T: PolkaVMWriteLLVM>(
|
||||
mut ir: T,
|
||||
llvm: &inkwell::context::Context,
|
||||
mut optimizer: Optimizer,
|
||||
use_newyork: bool,
|
||||
debug_config: DebugConfig,
|
||||
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
|
||||
identifier_paths: BTreeMap<String, String>,
|
||||
full_path: &str,
|
||||
metadata_bytes: Option<Keccak256>,
|
||||
) -> anyhow::Result<revive_llvm_context::PolkaVMBuild> {
|
||||
if use_newyork {
|
||||
optimizer.enable_newyork_pipeline();
|
||||
}
|
||||
|
||||
let module = llvm.create_module(full_path);
|
||||
let mut context = PolkaVMContext::new(llvm, module, optimizer, debug_config, memory_config);
|
||||
context.set_solidity_data(PolkaVMContextSolidityData::default());
|
||||
context.set_yul_data(PolkaVMContextYulData::new(identifier_paths));
|
||||
|
||||
ir.declare(&mut context)?;
|
||||
let generator_error_label = if use_newyork {
|
||||
"NewYork IR generator"
|
||||
} else {
|
||||
"LLVM IR generator"
|
||||
};
|
||||
ir.into_llvm(&mut context)
|
||||
.map_err(|error| anyhow::anyhow!("{generator_error_label}: {error}"))?;
|
||||
|
||||
context.build(full_path, metadata_bytes)
|
||||
}
|
||||
@@ -27,6 +27,7 @@ use crate::build::Build;
|
||||
use crate::missing_libraries::MissingLibraries;
|
||||
use crate::process::input::Input as ProcessInput;
|
||||
use crate::process::Process;
|
||||
use crate::project::contract::ir::newyork::NewYork;
|
||||
use crate::project::contract::ir::yul::Yul;
|
||||
use crate::project::contract::ir::IR;
|
||||
use crate::project::contract::Contract;
|
||||
@@ -142,11 +143,16 @@ impl Project {
|
||||
}
|
||||
|
||||
/// Parses the Yul source code file and returns the source data.
|
||||
///
|
||||
/// When `use_newyork` is `true`, each source is parsed into the `IR::NewYork`
|
||||
/// variant; otherwise into `IR::Yul`. The downstream compile pipeline dispatches
|
||||
/// on the IR variant.
|
||||
pub fn try_from_yul_paths(
|
||||
paths: &[PathBuf],
|
||||
solc_output: Option<&mut SolcStandardJsonOutput>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
debug_config: &DebugConfig,
|
||||
use_newyork: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let sources = paths
|
||||
.iter()
|
||||
@@ -155,15 +161,18 @@ impl Project {
|
||||
(path.to_string_lossy().to_string(), source)
|
||||
})
|
||||
.collect::<BTreeMap<String, SolcStandardJsonInputSource>>();
|
||||
Self::try_from_yul_sources(sources, libraries, solc_output, debug_config)
|
||||
Self::try_from_yul_sources(sources, libraries, solc_output, debug_config, use_newyork)
|
||||
}
|
||||
|
||||
/// Parses the test Yul source code string and returns the source data.
|
||||
///
|
||||
/// See [`Self::try_from_yul_paths`] for the meaning of `use_newyork`.
|
||||
pub fn try_from_yul_sources(
|
||||
sources: BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
mut solc_output: Option<&mut SolcStandardJsonOutput>,
|
||||
debug_config: &DebugConfig,
|
||||
use_newyork: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = sources.into_par_iter();
|
||||
@@ -176,11 +185,11 @@ impl Project {
|
||||
Ok(()) => source.take_content().expect("Always exists"),
|
||||
Err(error) => return Some((path, Err(error))),
|
||||
};
|
||||
let ir = match Yul::try_from_source(&source_code) {
|
||||
Ok(ir) => ir?,
|
||||
let (ir, object_identifier) = match parse_ir(&source_code, use_newyork) {
|
||||
Ok(Some(parsed)) => parsed,
|
||||
Ok(None) => return None,
|
||||
Err(error) => return Some((path, Err(error))),
|
||||
};
|
||||
let object_identifier = ir.object.identifier.clone();
|
||||
let name = ContractIdentifier::new(path.clone(), Some(object_identifier));
|
||||
let full_path = name.full_path.clone();
|
||||
if let Err(error) = debug_config.dump_yul(&name.full_path, &source_code) {
|
||||
@@ -189,7 +198,7 @@ impl Project {
|
||||
let source_metadata = serde_json::json!({
|
||||
"source_hash": Keccak256::from_slice(source_code.as_bytes()).to_string()
|
||||
});
|
||||
let contract = Contract::new(name, ir.into(), source_metadata);
|
||||
let contract = Contract::new(name, ir, source_metadata);
|
||||
Some((full_path, Ok(contract)))
|
||||
})
|
||||
.collect::<BTreeMap<String, anyhow::Result<Contract>>>();
|
||||
@@ -210,11 +219,15 @@ impl Project {
|
||||
}
|
||||
|
||||
/// Converts the `solc` JSON output into a convenient project.
|
||||
///
|
||||
/// When `use_newyork` is `true`, each contract's optimized Yul IR is routed through
|
||||
/// the newyork IR pipeline; otherwise through the direct Yul-to-LLVM path.
|
||||
pub fn try_from_standard_json_output(
|
||||
solc_output: &mut SolcStandardJsonOutput,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
solc_version: &SolcVersion,
|
||||
debug_config: &DebugConfig,
|
||||
use_newyork: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut input_contracts = Vec::with_capacity(solc_output.contracts.len());
|
||||
for (path, file) in solc_output.contracts.iter() {
|
||||
@@ -231,10 +244,9 @@ impl Project {
|
||||
|
||||
let results = iter
|
||||
.filter_map(|(name, contract)| {
|
||||
let ir = match Yul::try_from_source(&contract.ir_optimized)
|
||||
.map(|yul| yul.map(IR::from))
|
||||
{
|
||||
Ok(ir) => ir?,
|
||||
let ir = match parse_ir(&contract.ir_optimized, use_newyork) {
|
||||
Ok(Some((ir, _))) => ir,
|
||||
Ok(None) => return None,
|
||||
Err(error) => return Some((name.full_path, Err(error))),
|
||||
};
|
||||
if let Err(error) = debug_config.dump_yul(&name.full_path, &contract.ir_optimized) {
|
||||
@@ -261,3 +273,24 @@ impl Project {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a Yul source string into either the `IR::Yul` or `IR::NewYork` variant,
|
||||
/// depending on `use_newyork`. Returns `Ok(None)` for empty sources.
|
||||
///
|
||||
/// Yields the parsed IR together with the Yul object identifier (used for the
|
||||
/// `ContractIdentifier`).
|
||||
fn parse_ir(source_code: &str, use_newyork: bool) -> anyhow::Result<Option<(IR, String)>> {
|
||||
if use_newyork {
|
||||
let Some(ir) = NewYork::try_from_source(source_code)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let identifier = ir.yul_object.identifier.clone();
|
||||
Ok(Some((IR::from(ir), identifier)))
|
||||
} else {
|
||||
let Some(ir) = Yul::try_from_source(source_code)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let identifier = ir.object.identifier.clone();
|
||||
Ok(Some((IR::from(ir), identifier)))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user