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:
authored and GitHub committed 2026-06-17 12:35:16 +02:00
1 parent d178110a81
commit 98d818f958
183 files changed
+49503 -561

No files matched your search

+17
View File
@@ -52,6 +52,23 @@ pub const STANDARD_JSON_NO_EVM_CODEGEN_COMPLEX_PATH: &str =
/// These contracts are similar to ones used in an example project.
pub const STANDARD_JSON_PVM_CODEGEN_ALL_WILDCARD_PATH: &str =
"src/tests/data/standard_json/pvm_codegen_all_wildcard.json";
/// A standard JSON fixture enabling the newyork pipeline via `settings.polkavm.newyork`.
///
/// Shares the same source as [`STANDARD_JSON_NEWYORK_DISABLED_PATH`] so the two
/// can be compared to confirm the input field selects a different pipeline.
pub const STANDARD_JSON_NEWYORK_ENABLED_PATH: &str =
"src/tests/data/standard_json/newyork_enabled.json";
/// The same source as [`STANDARD_JSON_NEWYORK_ENABLED_PATH`] without the
/// `settings.polkavm.newyork` field, so it compiles through the stock pipeline.
pub const STANDARD_JSON_NEWYORK_DISABLED_PATH: &str =
"src/tests/data/standard_json/newyork_disabled.json";
/// A `"language": "Yul"` standard JSON fixture enabling newyork via `settings.polkavm.newyork`.
pub const STANDARD_JSON_YUL_NEWYORK_ENABLED_PATH: &str =
"src/tests/data/standard_json/yul_newyork_enabled.json";
/// The same Yul source as [`STANDARD_JSON_YUL_NEWYORK_ENABLED_PATH`] without the
/// `settings.polkavm.newyork` field, so it compiles through the stock pipeline.
pub const STANDARD_JSON_YUL_NEWYORK_DISABLED_PATH: &str =
"src/tests/data/standard_json/yul_newyork_disabled.json";
/// The standard JSON PVM codegen for all files on a per-file basis test fixture path.
///
/// These contracts are similar to ones used in an example project.
+29 -1
View File
@@ -71,7 +71,19 @@ pub(crate) mod version;
/// The rayon worker stack size.
pub const RAYON_WORKER_STACK_SIZE: usize = 64 * 1024 * 1024;
/// The name of the IR pipeline, used for the `resolc_pipeline` standard JSON output field.
pub fn pipeline_name(use_newyork: bool) -> &'static str {
if use_newyork {
"newyork"
} else {
"yul"
}
}
/// Runs the Yul mode.
///
/// When `use_newyork` is `true`, each Yul source is routed through the newyork IR
/// pipeline instead of the direct Yul-to-LLVM path.
pub fn yul<T: Compiler>(
solc: &T,
input_files: &[PathBuf],
@@ -82,13 +94,15 @@ pub fn yul<T: Compiler>(
debug_config: DebugConfig,
llvm_arguments: &[String],
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
use_newyork: bool,
) -> anyhow::Result<Build> {
let libraries = SolcStandardJsonInputSettingsLibraries::try_from(libraries)?;
let solc_output = solc.validate_yul_paths(input_files, libraries.clone(), messages)?;
solc_output.exit_on_error();
let linker_symbols = libraries.as_linker_symbols()?;
let project = Project::try_from_yul_paths(input_files, None, libraries, &debug_config)?;
let project =
Project::try_from_yul_paths(input_files, None, libraries, &debug_config, use_newyork)?;
let mut build = project.compile(
messages,
optimizer_settings,
@@ -107,6 +121,8 @@ pub fn yul<T: Compiler>(
}
/// Runs the standard output mode.
///
/// See [`yul`] for the meaning of `use_newyork`.
pub fn standard_output<T: Compiler>(
solc: &T,
input_files: &[PathBuf],
@@ -124,6 +140,7 @@ pub fn standard_output<T: Compiler>(
debug_config: DebugConfig,
llvm_arguments: Vec<String>,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
use_newyork: bool,
) -> anyhow::Result<Build> {
let solc_version = solc.version()?;
let mut solc_input = SolcStandardJsonInput::try_from_solidity_paths(
@@ -163,6 +180,7 @@ pub fn standard_output<T: Compiler>(
solc_input.settings.libraries,
&solc_version,
&debug_config,
use_newyork,
)?;
solc_output.take_and_write_warnings();
solc_output.check_errors()?;
@@ -186,6 +204,8 @@ pub fn standard_output<T: Compiler>(
}
/// Runs the standard JSON mode.
///
/// See [`yul`] for the meaning of `use_newyork`.
pub fn standard_json<T: Compiler>(
solc: &T,
metadata_hash: MetadataHash,
@@ -196,6 +216,7 @@ pub fn standard_json<T: Compiler>(
allow_paths: Option<String>,
mut debug_config: DebugConfig,
detect_missing_libraries: bool,
use_newyork: bool,
) -> anyhow::Result<()> {
let solc_version = solc.version()?;
let mut solc_input = SolcStandardJsonInput::try_from(json_path.as_deref())?;
@@ -206,6 +227,7 @@ pub fn standard_json<T: Compiler>(
let optimizer_settings = OptimizerSettings::try_from_cli(solc_input.settings.optimizer.mode)?;
let detect_missing_libraries =
solc_input.settings.detect_missing_libraries || detect_missing_libraries;
let use_newyork = use_newyork || solc_input.settings.polkavm.newyork.unwrap_or(false);
debug_config.emit_debug_info = solc_input
.settings
.polkavm
@@ -226,6 +248,7 @@ pub fn standard_json<T: Compiler>(
include_paths,
allow_paths,
)?;
solc_output.resolc_pipeline = Some(pipeline_name(use_newyork).to_owned());
if language == SolcStandardJsonInputLanguage::Yul {
let solc_output = solc.validate_yul_standard_json(&mut solc_input, messages)?;
@@ -239,6 +262,7 @@ pub fn standard_json<T: Compiler>(
solc_input.settings.libraries,
&solc_version,
&debug_config,
use_newyork,
)?;
if solc_output.has_errors() {
@@ -274,6 +298,8 @@ pub fn standard_json<T: Compiler>(
}
/// Runs the combined JSON mode.
///
/// See [`yul`] for the meaning of `use_newyork`.
pub fn combined_json<T: Compiler>(
solc: &T,
paths: &[PathBuf],
@@ -294,6 +320,7 @@ pub fn combined_json<T: Compiler>(
overwrite: bool,
llvm_arguments: Vec<String>,
memory_config: SolcStandardJsonInputSettingsPolkaVMMemory,
use_newyork: bool,
) -> anyhow::Result<()> {
let selectors = CombinedJsonSelector::from_cli(format.as_str())
.into_iter()
@@ -342,6 +369,7 @@ pub fn combined_json<T: Compiler>(
debug_config,
llvm_arguments,
memory_config,
use_newyork,
)?
.write_to_combined_json(&mut combined_json)?;
@@ -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(())
}
}
+61 -14
View File
@@ -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)
}
+42 -9
View File
@@ -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)))
}
}
+15 -2
View File
@@ -57,11 +57,21 @@ pub struct Arguments {
#[arg(long = "overwrite")]
pub overwrite: bool,
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Set the LLVM optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size.
/// Controls the LLVM optimization level only; independent of `--newyork`,
/// which selects the IR lowering pipeline.
#[arg(short = 'O', long = "optimization", default_value = "z")]
pub optimization: char,
/// Route Yul lowering through the experimental newyork IR pipeline.
///
/// Orthogonal to the input mode: composes with `--yul`, `--standard-json`,
/// `--combined-json`, and the default Solidity mode. Equivalent to setting the
/// `settings.polkavm.newyork` field in standard JSON input. Off by default.
#[arg(long = "newyork")]
pub newyork: bool,
/// Disable the `solc` optimizer.
/// Use it if your project uses the `MSIZE` instruction, or in other cases.
/// Beware that it will prevent libraries from being inlined.
@@ -252,7 +262,10 @@ impl Arguments {
let acceptable_count = 1 + self.standard_json.is_some() as usize;
if modes > acceptable_count {
messages.push(SolcStandardJsonOutputError::new_error(
"Only one modes is allowed at the same time: Yul, LLVM IR, PolkaVM assembly, combined JSON, standard JSON.",None,None));
"Only one mode is allowed at the same time: Yul, combined JSON, standard JSON, link.",
None,
None,
));
}
if self.yul && !self.libraries.is_empty() {
+14 -4
View File
@@ -111,6 +111,8 @@ fn main_inner(
initialize_llvm(
PolkaVMTarget::PVM,
resolc::DEFAULT_EXECUTABLE_NAME,
input.optimizer_settings.level_middle_end_size,
input.contract.ir.is_newyork(),
&input.llvm_arguments,
);
@@ -124,9 +126,15 @@ fn main_inner(
}
}
let mut optimizer_settings = OptimizerSettings::try_from_cli(arguments.optimization)?;
optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each;
optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging;
initialize_llvm(
PolkaVMTarget::PVM,
resolc::DEFAULT_EXECUTABLE_NAME,
optimizer_settings.level_middle_end_size,
arguments.newyork,
&arguments.llvm_arguments,
);
@@ -167,15 +175,13 @@ fn main_inner(
None => None,
};
let mut optimizer_settings = OptimizerSettings::try_from_cli(arguments.optimization)?;
optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each;
optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging;
let memory_config = SolcStandardJsonInputSettingsPolkaVMMemory::new(
Some(arguments.heap_size),
Some(arguments.stack_size),
);
let use_newyork = arguments.newyork;
let build = if arguments.yul {
resolc::yul(
&solc,
@@ -187,6 +193,7 @@ fn main_inner(
debug_config,
&arguments.llvm_arguments,
memory_config,
use_newyork,
)
} else if let Some(standard_json) = arguments.standard_json {
resolc::standard_json(
@@ -199,6 +206,7 @@ fn main_inner(
arguments.allow_paths,
debug_config,
arguments.detect_missing_libraries,
use_newyork,
)?;
return Ok(());
} else if let Some(format) = arguments.combined_json {
@@ -222,6 +230,7 @@ fn main_inner(
arguments.overwrite,
arguments.llvm_arguments,
memory_config,
use_newyork,
)?;
return Ok(());
} else if arguments.link {
@@ -244,6 +253,7 @@ fn main_inner(
debug_config,
arguments.llvm_arguments,
memory_config,
use_newyork,
)
}?;
+63 -8
View File
@@ -10,6 +10,7 @@ use revive_common::MetadataHash;
use revive_llvm_context::initialize_llvm;
use revive_llvm_context::DebugConfig;
use revive_llvm_context::OptimizerSettings;
use revive_llvm_context::OptimizerSettingsSizeLevel;
use revive_llvm_context::PolkaVMTarget;
use revive_solc_json_interface::standard_json::output::contract::evm::bytecode::Bytecode;
use revive_solc_json_interface::standard_json::output::contract::evm::bytecode::DeployedBytecode;
@@ -50,6 +51,8 @@ struct CachedBlob {
solidity: String,
/// The optimization level.
opt: String,
/// Whether newyork IR pipeline is enabled.
use_newyork: bool,
}
/// Builds the Solidity project and returns the standard JSON output.
@@ -77,7 +80,13 @@ pub fn build_solidity_with_options(
) -> anyhow::Result<SolcStandardJsonOutput> {
check_dependencies();
inkwell::support::enable_llvm_pretty_stack_trace();
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
initialize_llvm(
PolkaVMTarget::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
optimizer_settings.level_middle_end_size,
false,
&[],
);
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let solc_version = solc.version()?;
@@ -108,6 +117,7 @@ pub fn build_solidity_with_options(
if output.has_errors() {
return Ok(output);
}
let use_newyork = cfg!(feature = "newyork");
let debug_config = DebugConfig::new(None, optimizer_settings.middle_end_as_string() != "z");
let linker_symbols = libraries.as_linker_symbols()?;
let build = Project::try_from_standard_json_output(
@@ -115,6 +125,7 @@ pub fn build_solidity_with_options(
libraries,
&solc_version,
&debug_config,
use_newyork,
)?
.compile(
&mut vec![],
@@ -143,7 +154,13 @@ pub fn build_solidity_with_options_evm(
) -> anyhow::Result<BTreeMap<String, (Bytecode, DeployedBytecode)>> {
check_dependencies();
inkwell::support::enable_llvm_pretty_stack_trace();
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
initialize_llvm(
PolkaVMTarget::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
OptimizerSettingsSizeLevel::Zero,
false,
&[],
);
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let mut input = SolcStandardJsonInput::try_from_solidity_sources(
None,
@@ -198,7 +215,13 @@ pub fn build_solidity_and_detect_missing_libraries<T: ToString>(
);
inkwell::support::enable_llvm_pretty_stack_trace();
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
initialize_llvm(
PolkaVMTarget::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
OptimizerSettingsSizeLevel::Zero,
false,
&[],
);
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let solc_version = solc.version()?;
let mut input = SolcStandardJsonInput::try_from_solidity_sources(
@@ -225,6 +248,7 @@ pub fn build_solidity_and_detect_missing_libraries<T: ToString>(
libraries,
&solc_version,
&DEBUG_CONFIG,
false,
)?;
let missing_libraries = project.get_missing_libraries(&deployed_libraries);
@@ -239,7 +263,14 @@ pub fn build_yul<T: ToString + Display>(
) -> anyhow::Result<BTreeMap<String, Vec<u8>>> {
check_dependencies();
inkwell::support::enable_llvm_pretty_stack_trace();
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
let optimizer_settings = OptimizerSettings::size();
initialize_llvm(
PolkaVMTarget::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
optimizer_settings.level_middle_end_size,
false,
&[],
);
let mut build = Project::try_from_yul_sources(
sources
@@ -254,10 +285,11 @@ pub fn build_yul<T: ToString + Display>(
Default::default(),
None,
&DEBUG_CONFIG,
false,
)?
.compile(
&mut vec![],
OptimizerSettings::size(),
optimizer_settings,
MetadataHash::Keccak256,
&DEBUG_CONFIG,
Default::default(),
@@ -285,7 +317,14 @@ pub fn build_yul_standard_json(
) -> anyhow::Result<SolcStandardJsonOutput> {
check_dependencies();
inkwell::support::enable_llvm_pretty_stack_trace();
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
let optimizer_settings = OptimizerSettings::try_from_cli(solc_input.settings.optimizer.mode)?;
initialize_llvm(
PolkaVMTarget::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
optimizer_settings.level_middle_end_size,
false,
&[],
);
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
let mut output = solc.validate_yul_standard_json(&mut solc_input, &mut vec![])?;
@@ -297,10 +336,11 @@ pub fn build_yul_standard_json(
Default::default(),
Some(&mut output),
&DEBUG_CONFIG,
false,
)?
.compile(
&mut vec![],
OptimizerSettings::try_from_cli(solc_input.settings.optimizer.mode)?,
optimizer_settings,
MetadataHash::Keccak256,
&DEBUG_CONFIG,
Default::default(),
@@ -335,11 +375,14 @@ pub fn compile_blob_with_options(
optimizer_settings: OptimizerSettings,
libraries: SolcStandardJsonInputSettingsLibraries,
) -> Vec<u8> {
let use_newyork = cfg!(feature = "newyork");
let id = CachedBlob {
contract_name: contract_name.to_owned(),
opt: optimizer_settings.middle_end_as_string(),
solc_optimizer_enabled,
solidity: source_code.to_owned(),
use_newyork,
};
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
@@ -446,6 +489,7 @@ fn compile_evm(
solidity: source_code.to_owned(),
solc_optimizer_enabled,
opt: String::new(),
use_newyork: false, // EVM compilation never uses newyork
};
let cache = if runtime {
@@ -498,11 +542,13 @@ pub fn compile_yul_blob_with_options(
yul_source: &str,
optimizer_settings: OptimizerSettings,
) -> Vec<u8> {
let use_newyork = cfg!(feature = "newyork");
let id = CachedBlob {
contract_name: contract_name.to_owned(),
opt: optimizer_settings.middle_end_as_string(),
solc_optimizer_enabled: false,
solidity: yul_source.to_owned(),
use_newyork,
};
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
return blob.clone();
@@ -510,7 +556,13 @@ pub fn compile_yul_blob_with_options(
check_dependencies();
inkwell::support::enable_llvm_pretty_stack_trace();
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
initialize_llvm(
PolkaVMTarget::PVM,
crate::DEFAULT_EXECUTABLE_NAME,
optimizer_settings.level_middle_end_size,
false,
&[],
);
let path = format!("{contract_name}.yul");
let mut build = Project::try_from_yul_sources(
@@ -521,6 +573,7 @@ pub fn compile_yul_blob_with_options(
Default::default(),
None,
&DEBUG_CONFIG,
use_newyork,
)
.expect("yul project should build")
.compile(
@@ -563,6 +616,7 @@ pub fn compile_yul_evm_deploy_code(contract_name: &str, yul_source: &str) -> Vec
opt: String::new(),
solc_optimizer_enabled: false,
solidity: yul_source.to_owned(),
use_newyork: false,
};
if let Some(blob) = EVM_BLOB_CACHE.lock().unwrap().get(&id) {
return blob.clone();
@@ -626,6 +680,7 @@ pub fn compile_to_yul(
solc_optimizer_enabled,
solidity: source_code.to_owned(),
opt: optimizer.mode.into(),
use_newyork: false, // Yul IR cache doesn't use newyork
};
if let Some(yul) = YUL_IR_CACHE.lock().unwrap().get(&id) {
+129 -1
View File
@@ -8,9 +8,11 @@ use revive_solc_json_interface::{
use crate::cli_utils::{
assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input,
execute_solc_with_stdin_input, STANDARD_JSON_ALL_OUTPUTS_PATH, STANDARD_JSON_CONTRACTS_PATH,
STANDARD_JSON_NEWYORK_DISABLED_PATH, STANDARD_JSON_NEWYORK_ENABLED_PATH,
STANDARD_JSON_NO_EVM_CODEGEN_COMPLEX_PATH, STANDARD_JSON_NO_EVM_CODEGEN_PATH,
STANDARD_JSON_NO_PVM_CODEGEN_PER_FILE_PATH, STANDARD_JSON_PVM_CODEGEN_ALL_WILDCARD_PATH,
STANDARD_JSON_PVM_CODEGEN_ONE_FILE_PATH, STANDARD_JSON_PVM_CODEGEN_PER_FILE_PATH,
STANDARD_JSON_YUL_NEWYORK_DISABLED_PATH, STANDARD_JSON_YUL_NEWYORK_ENABLED_PATH,
STANDARD_JSON_YUL_NO_PVM_CODEGEN_PATH, STANDARD_JSON_YUL_PVM_CODEGEN_PATH,
};
@@ -187,7 +189,23 @@ fn assert_standard_json_errors_contain(output: &SolcStandardJsonOutput, error_me
/// Asserts that the standard JSON output has no errors with severity `error`.
fn assert_no_errors(output: &SolcStandardJsonOutput) {
assert!(!output.errors.iter().any(|error| error.is_error()));
let mut has_errors = false;
for error in output.errors.iter().filter(|error| error.is_error()) {
eprintln!(
"ERROR in {}: {}",
error
.source_location
.as_ref()
.map(|l| l.file.as_str())
.unwrap_or("unknown"),
error.message,
);
has_errors = true;
}
assert!(
!has_errors,
"the standard JSON output should not contain errors"
);
}
/// Converts valid JSON text to `SolcStandardJsonOutput`.
@@ -547,3 +565,113 @@ fn invalid_extra_arguments() {
assert_standard_json_errors_contain(&output, case.error_message);
}
}
/// Extracts the hex PVM bytecode object of `path`/`name` from a standard JSON output.
fn bytecode_object(output: &SolcStandardJsonOutput, path: &str, name: &str) -> String {
let contract = output
.contracts
.get(path)
.and_then(|file| file.get(name))
.unwrap_or_else(|| panic!("the contract `{name}` in `{path}` should exist"));
serde_json::to_value(contract)
.unwrap()
.get("evm")
.and_then(|evm| evm.get("bytecode"))
.and_then(|bytecode| bytecode.get("object"))
.and_then(|object| object.as_str())
.expect("the bytecode object should be present")
.to_owned()
}
/// The `settings.polkavm.newyork` standard JSON input field selects the newyork
/// pipeline: for the same source it yields different bytecode than the stock
/// pipeline, and the field is off by default (the disabled fixture omits it).
#[test]
fn pvm_codegen_newyork_input_setting() {
let enabled =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NEWYORK_ENABLED_PATH);
assert_command_success(
&enabled,
"the newyork-enabled standard JSON input should build",
);
let disabled =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NEWYORK_DISABLED_PATH);
assert_command_success(&disabled, "the stock standard JSON input should build");
let enabled_output = to_solc_standard_json_output(&enabled.stdout);
let disabled_output = to_solc_standard_json_output(&disabled.stdout);
assert_no_errors(&enabled_output);
assert_no_errors(&disabled_output);
assert_ne!(
bytecode_object(&enabled_output, "C.sol", "C"),
bytecode_object(&disabled_output, "C.sol", "C"),
"settings.polkavm.newyork should select a different pipeline than the default"
);
}
/// The `--newyork` CLI flag selects the newyork pipeline for standard JSON input
/// and produces the same bytecode as the `settings.polkavm.newyork` input field.
#[test]
fn pvm_codegen_newyork_cli_flag() {
let stock =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NEWYORK_DISABLED_PATH);
assert_command_success(&stock, "the stock standard JSON input should build");
let flagged = execute_resolc_with_stdin_input(
&[JSON_OPTION, "--newyork"],
STANDARD_JSON_NEWYORK_DISABLED_PATH,
);
assert_command_success(&flagged, "the --newyork flag should build the stock input");
let stock_output = to_solc_standard_json_output(&stock.stdout);
let flagged_output = to_solc_standard_json_output(&flagged.stdout);
assert_no_errors(&stock_output);
assert_no_errors(&flagged_output);
let stock_bytecode = bytecode_object(&stock_output, "C.sol", "C");
let flagged_bytecode = bytecode_object(&flagged_output, "C.sol", "C");
assert_ne!(
stock_bytecode, flagged_bytecode,
"--newyork should select a different pipeline than the default"
);
let via_field =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NEWYORK_ENABLED_PATH);
assert_command_success(
&via_field,
"the newyork-enabled standard JSON input should build",
);
let via_field_output = to_solc_standard_json_output(&via_field.stdout);
assert_no_errors(&via_field_output);
assert_eq!(
flagged_bytecode,
bytecode_object(&via_field_output, "C.sol", "C"),
"--newyork and settings.polkavm.newyork should select the same pipeline"
);
}
/// `settings.polkavm.newyork` also selects the newyork pipeline for `"language": "Yul"`
/// standard JSON input: the same Yul source yields different bytecode than the stock pipeline.
#[test]
fn pvm_codegen_newyork_yul_input() {
let enabled =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_YUL_NEWYORK_ENABLED_PATH);
assert_command_success(
&enabled,
"the newyork-enabled Yul standard JSON input should build",
);
let disabled =
execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_YUL_NEWYORK_DISABLED_PATH);
assert_command_success(&disabled, "the stock Yul standard JSON input should build");
let enabled_output = to_solc_standard_json_output(&enabled.stdout);
let disabled_output = to_solc_standard_json_output(&disabled.stdout);
assert_no_errors(&enabled_output);
assert_no_errors(&disabled_output);
assert_ne!(
bytecode_object(&enabled_output, "Test", "Test"),
bytecode_object(&disabled_output, "Test", "Test"),
"settings.polkavm.newyork should select a different pipeline for Yul input"
);
}
@@ -0,0 +1,12 @@
{
"language": "Solidity",
"sources": {
"C.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\ncontract C { function f(uint256 a, uint256 b) public pure returns (uint256) { unchecked { return a + b * 3; } } }"
}
},
"settings": {
"optimizer": { "enabled": true, "mode": "z" },
"outputSelection": { "*": { "*": ["evm.bytecode"] } }
}
}
@@ -0,0 +1,13 @@
{
"language": "Solidity",
"sources": {
"C.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\ncontract C { function f(uint256 a, uint256 b) public pure returns (uint256) { unchecked { return a + b * 3; } } }"
}
},
"settings": {
"optimizer": { "enabled": true, "mode": "z" },
"outputSelection": { "*": { "*": ["evm.bytecode"] } },
"polkavm": { "newyork": true }
}
}
@@ -0,0 +1,12 @@
{
"language": "Yul",
"sources": {
"Test": {
"content": "object \"Test\" { code { let s := datasize(\"Test_deployed\") codecopy(0, dataoffset(\"Test_deployed\"), s) return(0, s) } object \"Test_deployed\" { code { let x := calldataload(0) let r := 0 for { let i := 0 } lt(i, 5) { i := add(i, 1) } { r := add(r, mul(x, i)) } mstore(0, r) return(0, 32) } } }"
}
},
"settings": {
"optimizer": { "enabled": true },
"outputSelection": { "*": { "*": ["evm.bytecode"] } }
}
}
@@ -0,0 +1,13 @@
{
"language": "Yul",
"sources": {
"Test": {
"content": "object \"Test\" { code { let s := datasize(\"Test_deployed\") codecopy(0, dataoffset(\"Test_deployed\"), s) return(0, s) } object \"Test_deployed\" { code { let x := calldataload(0) let r := 0 for { let i := 0 } lt(i, 5) { i := add(i, 1) } { r := add(r, mul(x, i)) } mstore(0, r) return(0, 32) } } }"
}
},
"settings": {
"optimizer": { "enabled": true },
"outputSelection": { "*": { "*": ["evm.bytecode"] } },
"polkavm": { "newyork": true }
}
}
@@ -5,8 +5,7 @@ use crate::test_utils::build_yul;
/// Reproducer from GH-474: duplicate `f` across switch cases.
#[test]
fn duplicate_function_names_in_switch_cases() {
let code = r#"
object "Test" {
let code = r#"object "Test" {
code {
{
let size := datasize("Test_deployed")