resolc v1.4.0
Supported `polkadot-sdk` rev: `2604.2.0`
### Added
- Support for solc v0.8.36 and the "Amsterdam" EVM version.
### Fixed
- Restored the top-level `revive_version` field in `--standard-json`
output and the `resolc_version` field in `--combined-json` output, both
of which stopped being populated in `v0.4.0`.
[#557](https://github.com/paritytech/revive/pull/557)
- ICE triggered by `--disable-solc-optimizer --newyork` where inlined
loops could emit an unterminated basic block in the inlined entrypoint.
[#561](https://github.com/paritytech/revive/pull/561)
- `--newyork`: dead-store elimination could drop a store whose memory
expansion an intervening `msize()` observes, so `msize()` read a stale
(unexpanded) value.
[#565](https://github.com/paritytech/revive/pull/565)
- `--newyork`: dead-store elimination could drop a store that a later
overlapping unaligned `mload` reads, returning zeroed memory instead of
the stored bytes. [#563](https://github.com/paritytech/revive/pull/563)
- `--newyork`: an unaligned full-word `mload`/`mstore` overlapping a
word kept native (little-endian) by the heap optimization returned
byte-swapped bytes.
[#562](https://github.com/paritytech/revive/pull/562)
- `--newyork`: a `calldatacopy`/`codecopy`/`returndatacopy` to a dynamic
destination that could overwrite the free-memory-pointer word truncated
a subsequent `mload(0x40)` to `0`.
[#564](https://github.com/paritytech/revive/pull/564)
- `--newyork`: a full-width value stored to the free-memory-pointer slot
(e.g. a function parameter `>= 2^64`) was narrowed to 64 bits, inserting
a checked truncation that consumed all gas.
[#567](https://github.com/paritytech/revive/pull/567)
- `--newyork`: an unaligned `mstore` corrupting the free-memory-pointer
word let a later store through the corrupted pointer silently succeed
where EVM runs out of gas.
[#566](https://github.com/paritytech/revive/pull/566)
- Fixed a bytecode divergence (yet functionally equivalent) between the
resolc Linux/macOS builds and the Windows build for contracts with loops
indexing array elements at constant offsets from a `uint8` loop variable
(e.g. `array[i]`/`array[i + 1]`), compiled with `--newyork` at `-Oz`
with the solc optimizer disabled.
[#570](https://github.com/paritytech/revive/pull/570)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Bug found by @jubnzv:
`--newyork`: an unaligned `mstore` corrupting the free-memory-pointer
word let a later store through the corrupted pointer silently succeed
where EVM runs out of gas.
---------
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug found by @jubnzv:
`--newyork`: a full-width value stored to the free-memory-pointer slot
(e.g. a function parameter `>= 2^64`) was narrowed to 64 bits, inserting
a checked truncation that consumed all gas.
---------
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug found by @jubnzv:
`--newyork`: dead-store elimination could drop a store whose memory
expansion an intervening `msize()` observes, so `msize()` read a stale
(unexpanded) value.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug found by @jubnzv:
`--newyork`: a `calldatacopy`/`codecopy`/`returndatacopy` to a dynamic
destination that could overwrite the free-memory-pointer word truncated
a subsequent `mload(0x40)` to `0`.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Agent <agent@local>
Another bug found by @jubnzv:
`--newyork`: dead-store elimination could drop a store that a later
overlapping unaligned `mload` reads, returning zeroed memory instead of
the stored bytes.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Another bug found by @jubnzv:
`--newyork`: an unaligned full-word `mload`/`mstore` overlapping a word
kept native (little-endian) by the heap optimization returned
byte-swapped bytes.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Description
The resolc long version stopped being populated on the standard JSON and
combined JSON output. This PR restores these fields and populates them.
## v1.3.0
Supported `polkadot-sdk` rev: `2604.2.0`
### Added
- The newyork IR code size optimization pipeline, selectable via the
`--newyork` CLI flag or the `settings.polkavm.newyork` standard JSON
input field.
This can reduce code size by up to 50%. Considered a new and
experimental feature, switched off by default.
[#519](https://github.com/paritytech/revive/pull/519)
### Changed
- The latest supported `polkadot-sdk` version is now `2604.2.0`
Signed-off-by: xermicus <cyrill@parity.io>
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>
Supported `polkadot-sdk` rev: `2603`
### Changed
- Updated `LLVM` from `21.1.8` to LLVM `22.1.4`
### Added
- Support for solc v0.8.35.
### Fixed
- A constant folding bug causing incorrect translation of a signed
remainder edge case. The affected pattern (Solidity % or Yul smod):
- SREM over the two constant operands `type(int256).min` and `-1`.
- At least one of the two constants needs folding (i.e. not just
`type(int256).min % -1`),
and is written specifically such that solc doesn't fold but LLVM does,
resulting in UB (signed-overflow).
- Fix `mulmod` returning out-of-range result for large moduli.
---------
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Existing test coverage didn't catch a signed remainder overflow
semantics edge case behavior where at least one the operators is being
constant folded by LLVM instead of solc. This PR fixes the SREM
translation bug and adds missing test coverage for division ops with
constants.
Closes#524
---------
Signed-off-by: xermicus <cyrill@parity.io>
- Support solc v0.8.35
- The new `erc7201` is comptime (already translated to a constant in
Yul)
Didn't spot any relevant breakage or addition in the release notes so
should be no-brainer.
---------
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This appears to be NFC change as new pass manager handles
--passes=default<O0> correctly. Still must be useful as a safeguard and
possibly for the old pass manager that is still used by backend.
```
Contract Without (bytes) With noinline+optnone (bytes)
─────────────────────────────── ───────────────── ────────────────────────────
contract.sol:C 1,266 1,266
Fibonacci:Iterative 2,506 2,506
Storage 3,671 3,671
Fibonacci:Recursive 3,794 3,794
Computation 5,776 5,776
Fibonacci:Binet 8,786 8,786
large_div_rem 10,705 10,705
DivisionArithmetics 14,831 14,831
ERC20 32,288 32,288
ERC20Tester 34,589 34,589
```
## Description
We previously had a workaround to strip `minsize` attributes for
functions with 256-bit integer division due to a crash in the LLVM
backend. We now [patch](https://github.com/paritytech/revive/pull/500)
LLVM with the fix, allowing us to use `minsize` with correct backend
optimization, making the workaround unnecessary.
## Description
Applies LLVM patches that have not been backported to the major version
used in our submodule:
* [LLVM PR #163679](https://github.com/llvm/llvm-project/pull/163679)
* *"[RISCV] Allow large div peephole optimization for minsize"*
* Fixes peephole optimization of large integers when `minsize` function
attribute is set.
* (Our revive codebase used a workaround that can be removed once this
500 PR is merged and we publish a new LLVM release.)
* [LLVM PR #190219](https://github.com/llvm/llvm-project/pull/190219)
* *"[TargetLowering] Speculative fix for a non-determinism issue between
different compilers"*
* Operand ordering of `mul i256` were different depending on the target
ABI when building LLVM, producing different RISC-V and PVM bytecode for
our Windows vs macOS/linux builds.
* The bytecode divergence (yet functionally equivalent) affected
Solidity contracts containing a value [multiplied by
itself](https://github.com/llvm/llvm-project/issues/190148) (e.g. via
explicit squaring or exponentiation), manifesting primarily when using
optimization level `-O0`.
## Description
Switches the Windows LLVM build from Visual Studio to `clang-cl` +
`lld-link` (Clang's MSVC-compatible drivers) with Ninja.
This is in order to better match the Clang toolchain used on our Linux
and macOS variants, while keeping the MSVC ABI.
It also speeds up the build time by at least 2x on Windows.
In YUL it's possible to declare closure with a same name, but in
different scope, which previously resulted to func name conflict during
LLVM IR construction.
To solve this, track YUL scope when building LLVM IR and a special
counter that increments every time function with the same name exists in
LLVM module.
There's still assert if somehow within the same scope there 2 functions
with the same name, which should be forbidden by Yul
Fixes: #474
---------
Co-authored-by: LJ <81748770+elle-j@users.noreply.github.com>
Emscripten 4.0.12 introduced breaking change by changing module factory
to always return promise even if `WASM_ASYNC_COMPILATION=0`. To overcome this, build emscripten compiler without MODULARIZE with custom export of `createRevive`.
- Recursive processes spawn the currently running executable by default,
removing the annoyance of mixing different compiler builds during
contract compilation.
- Remove the unused `once_cell` dependency.
Signed-off-by: xermicus <bigcyrill@hotmail.com>
As a drive-by fix, the get-llvm action now ignores LLVM releases marked
as pre-release.
---------
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
In `--yul` mode, resolc now properly exits when solc reports validation
errors (e.g., invalid hex literals, variable redeclarations), matching
`--standard-json behavior.`
`runs_without_input_file` was adapted because it was relying on it where
the new behavior correctly matches solc --strict-assembly.
Closes#477
Co-authored-by: minimax-m2.5 <neo@zion>
---------
Signed-off-by: xermicus <bigcyrill@hotmail.com>
This marks a milestone: Smart contracts on Polkadot Asset Hub are live
and we release the first compiler version!
Signed-off-by: xermicus <bigcyrill@hotmail.com>
# Description
Compilation times for resolc were significantly slower than solc when:
* Multiple files were included in the JSON input file via
`--standard-json`
* And none of the files requested code generation
* (If only a subset of files requested code generation, both resolc and
solc were very slow)
Reason:
* Resolc unconditionally generated bytecode for all contracts, even if
not requested.
* When compiling to PolkaVM bytecode, we need to pass additional flags
(e.g. `irOptimized`) to solc. If these flags were not explicitly
requested, resolc also added these to the requested output since PolkaVM
bytecode was always generated.
## Breaking Changes
The following changes are breaking, but fix the semantics to match its
intended behavior:
* Compiling with `--standard-json` now compiles to PolkaVM bytecode
***only*** for the contracts explicitly requested in the
`outputSelection`.
* Compiling with `--standard-json` now outputs ***only*** the explicitly
requested output (e.g. `evm.assembly`, `evm.bytecode`, `ast`, etc.).
```jsonc
// Example:
{
"settings": {
// ...
"outputSelection": {
"src/common/GasService.sol": {
// Contracts in this file will generate bytecode.
// Only these fields of the JSON output selection will be in the `contracts` output.
"*": ["abi", "evm.methodIdentifiers", "metadata", "evm.bytecode"],
// Only this field of the JSON output selection will be in the `sources` output.
"": ["ast"]
},
"src/common/Gateway.sol": {
// No contracts in this file will generate bytecode.
"*": ["abi", "evm.methodIdentifiers", "metadata"],
// No `ast` will be in the `sources` output (only the automatically added `id`,
// similar to solc as this is not a configurable output selection).
"": []
},
}
}
}
```
## Performance Improvements
When e.g. the standard JSON input file includes multiple files and
requests output for every contract as per this setting:
```jsonc
{
"settings": {
// ...
"optimizer": { "enabled": true, "runs": 1 },
"viaIR": true,
"outputSelection": {
"src/common/GasService.sol": {
"*": ["abi", "evm.methodIdentifiers", "metadata", "evm.bytecode"],
},
// Rest of files..
}
}
}
```
The differences in compilation times depending on whether `evm.bytecode`
is selected in every file, one file, or no files can be seen below:
| Solc Optimizer (runs=1) | ViaIR | Request `abi`,
`evm.methodIdentifiers`, `metadata` | Request `evm.bytecode` | resolc
before | resolc after | solc v0.8.33 |
|:-----------------------:|:-----:|:---------:|:---------:|:-------------:|:------------:|:----------:|
| ✅ | ✅ | All files | All files | `~14.5s` | `~14.5s` | `~13.5s` |
| ✅ | ✅ | All files | One file | `~14.5s` | `~450ms` | `~13.5s` |
| ✅ | ✅ | All files | No files | `~14.5s` | `~390ms` | `~350ms` |
**Observations:**
* `resolc before` is roughly the same no matter the request
* This is due to unconditionally generating bytecode
* When not requesting bytecode for any contracts:
* `resolc after` now compiles in ~390ms (vs ~14.5s), similar to `solc`
* Neither `solc` nor `resolc after` generates bytecode when none of the
contracts request it
* When requesting bytecode for all contracts in _only one_ file:
* `resolc after` now compiles in ~450ms (vs ~14.5s)
* `solc` still compiles in the same time as when requesting bytecode for
all contracts in all files
* This is something they have tried to improve (see
[Solidity#15373](https://github.com/argotorg/solidity/issues/15373) and
[Solidity#15615](https://github.com/argotorg/solidity/issues/15615)),
and the intended behavior seems to now be that bytecode should only be
generated when explicitly requested for each contract.
* Even though PRs have merged to try to fix it, their issue seems to
persist as of v0.8.33.
# Open Questions
* Foundry:
* [x] Pruning of the AST:
* Just saw [this past
commit](https://github.com/paritytech/revive/pull/385) to not prune the
AST for Foundry compatibility.
* Can Foundry not explicitly request `"": "ast"`?
* **Answer:**
* Pruning the AST when not requested should be fine, as long as Foundry
explicitly requests it when needed.
* Will be tested by Foundry maintainer.
# Review Notes
Changes in `docs/` (~25 files) can be skipped as they are automatically
updated after adding content to `book/` and rebuilding it.
# Resolved Issues
Closes#326
---------
Co-authored-by: xermicus <cyrill@parity.io>
Differentially tests the behavior of deleting a struct with a member
that do not occupy the full 32-byte slot. From [this
issue](https://github.com/paritytech/contract-issues/issues/118) it was
unclear whether the `delete` was expected to reset only the bytes
occupied or the entire slot. The current behavior of resetting the
entire slot is expected.
- Remove the superfluous byte swap in the `linkersymbol` builtin
translation.
- Support testing the `linkersymbol` builtin translation in the runner
specs.
---------
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
## Description
Adds default values to the compiler arguments below when parsed by
`clap`. This does not change previous user-facing behavior as these
defaults were implemented using different logic, but improves the UX and
the visibility of the exact defaults used.
**Example output from `resolc --help`:**
(Only affected arguments are shown here, and some of their descriptions
have been omitted here.)
```
Options:
-O, --optimization <OPTIMIZATION>
Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. Use `3` for best performance and `z` for minimal size
[default: z]
--metadata-hash <METADATA_HASH>
Set the metadata hash type. Available types: `none`, `ipfs`, `keccak256`
[default: keccak256]
--heap-size <HEAP_SIZE>
The emulated EVM linear heap memory static buffer size in bytes.
...
[default: 131072]
--stack-size <STACK_SIZE>
The contracts total stack size in bytes.
...
[default: 131072]
```
## Resolved Issues
Closes#446
The OOB logic is implemented in the runtime but truncation circumvented
it.
Closes#263
---------
Signed-off-by: xermicus <cyrill@parity.io>
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Added 2 options to control expected result to `revive-runner`:
```
--verify-failure <VERIFY_FAILURE>
The expected call result of the contract execution
Possible values:
- instantiate: The contract instantiation must fail
- call: The contract execution must fail
--verify-call-gas-consumed <VERIFY_CALL_GAS_CONSUMED>
The expected amount of consumed gas by the contract execution
```
---------
Co-authored-by: xermicus <bigcyrill@hotmail.com>
Fixes https://github.com/paritytech/contract-issues/issues/149
A few test cases from v2-periphery-polkadot are hitting the PVM stack
limit when the contracts are compiled using resolc version 0.5.0. The
same tests pass if the contracts are compiled with a resolc version that
includes commit:
https://github.com/paritytech/revive/commit/878ca91689b99220f7ffd40df760613b7314de86.
After a brief discussion with @xermicus, we propose to set the default
PolkaVM stack and heap size values to 128KB, since we have already
encountered tests that hit the stack size limit and, without real world
data, it is difficult to determine the optimal values.