Skip to content

The new Yul optimizer toolkit#519

Draft
xermicus wants to merge 213 commits into
mainfrom
cl/newyork
Draft

The new Yul optimizer toolkit#519
xermicus wants to merge 213 commits into
mainfrom
cl/newyork

Conversation

@xermicus
Copy link
Copy Markdown
Member

No description provided.

xermicus and others added 30 commits February 2, 2026 21:28
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Implement type-inference-driven value narrowing in newyork codegen:

1. Let-binding narrowing: i256 values with inferred width ≤I64 and
   unsigned are truncated to i64 at Let binding sites, enabling native
   RISC-V arithmetic downstream.

2. Arithmetic dispatch: Add/Mul check inferred result width. If result
   fits in i64, use ensure_min_width for native ops. Sub always uses
   i256 to handle negative results correctly.

3. Pointer-site narrowing: Memory offsets/lengths narrowed from i256
   to i64 at use sites (mstore, mload, codecopy, etc.).

4. Fix condition_stmts forward inference: The forward type inference
   pass was not visiting Let statements inside for-loop condition
   blocks, causing large constants (e.g., type(int256).min) to retain
   default I1 width and be incorrectly truncated.

5. Fix inliner for large contracts: Single-call functions larger than
   40 IR nodes are now deferred to LLVM's inliner instead of being
   inlined at IR level. This prevents creating monolithic dispatcher
   functions that cause regressions on OpenZeppelin contracts.

Results: OZ ERC20 -3.5%, OZ ERC721 -3.9%, small benchmarks -1.5% to -3.1%.
All 62 integration tests, 5851 retester tests, and make test pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a truncate-and-extend pattern after loading the free memory pointer
(mload(64)) to prove to LLVM that the value fits in 64 bits. This
enables LLVM to eliminate downstream overflow checks and simplify
surrounding arithmetic, yielding significant code size reductions.

Also adds revert(0,K) block deduplication using shared basic blocks
keyed by constant revert length.

Results vs standard pipeline:
- Integration ERC20: -10.8% (was -5.1%)
- Integration SHA1: -7.9% (was -1.4%)
- OZ ERC20: -11.7% (74,508 vs 84,364 bytes)
- OZ ERC721: -9.4% (84,950 vs 93,730 bytes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Key changes:
- FMP range proof changed from 64-bit to 32-bit to match XLEN,
  eliminating 60 more overflow trap sites in OZ ERC20
- Added apply_range_proof() helper for reusable range proofs
- Added range proofs for timestamp (64), number (64), chainid (64), basefee (128)
- Documented which builtins already have implicit range proofs
- Memory region annotation in simplify pass using constant tracking
- Updated IR_PLAN.md with optimization analysis and failed experiments

Results: ERC20 -12.6%, Flipper -10.8%, Computation -10.5% vs standard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cture

Conditional sbrk NoInline: mark __sbrk_internal as NoInline only for
contracts with >20 heap operations. ERC20 saves 359 bytes while small
contracts remain unaffected.

Interprocedural parameter narrowing: infrastructure to narrow function
parameter types from I256 based on body-internal usage analysis.
Currently no parameters meet the strict criteria but the code path is
ready for broader narrowing rules.

ERC20: 14287 bytes (-14.7% vs standard 16757)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The try_narrow_let_binding was overriding the 32-bit FMP range proof
(trunc i256 → i32; zext i32 → i256) with a weaker 64-bit proof
(trunc i256 → i64). Now detects zext source width and preserves
tighter existing proofs. Result: 17 fewer overflow checks in LLVM IR,
288 fewer IR lines, though PVM blob size unchanged (PolkaVM already
handles dead overflow blocks efficiently).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major newyork IR optimizations:

1. Keccak256Pair IR node: fuses mstore(0,w0)+mstore(32,w1)+keccak256(0,64)
   into a single __revive_keccak256_two_words call. 13 sites in ERC20,
   saving ~529 bytes (-3.7%).

2. FMP constant propagation: replaces mload(0x40) with known constant
   when the free memory pointer hasn't been modified. Handles
   Statement::Block wrapping, Statement::Expr void calls, and builds
   transitive FMP-writer closure via call graph analysis.

3. Interprocedural parameter narrowing: propagates caller argument
   widths to callee parameters in the type inference forward pass.
   Narrows function signatures (e.g., assert_helper(i32),
   finalize_allocation(i64, i256)).

4. Conditional sbrk NoInline: only outline __sbrk_internal when the
   contract has >20 heap operations (saves ~359 bytes on ERC20 without
   regressing small contracts).

5. Load-after-store forwarding in mem_opt: when mload matches a tracked
   mstore to the same offset, forward the stored value directly.

6. Memory region annotation in simplify pass: tag MLoad/MStore with
   FreePointerSlot/Scratch/Dynamic regions from constant offset analysis.

Code size improvements (newyork vs previous):
- ERC20:    14287 → 13051 (-8.7%)
- SHA1:      6748 →  5930 (-12.1%)
- Computation: 1654 → 1577 (-4.7%)
- DivisionArithmetics: 13501 → 13243 (-1.9%)

All 62 integration tests pass with RESOLC_USE_NEWYORK=1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two new code size optimizations:

1. PanicRevert IR node: detects the Solidity panic pattern
   (mstore(0, 0x4e487b71...) + mstore(4, code) + revert(0, 0x24))
   and replaces it with a PanicRevert { code } statement that lowers
   to a shared helper function. Each panic code (0x11 overflow,
   0x22 encoding, 0x41 memory, etc.) gets its own shared block,
   deduplicating the 3-statement pattern across many call sites.

2. Shared return blocks: when multiple return(offset, length)
   statements have identical constant arguments, they branch to a
   single shared LLVM basic block instead of each generating their
   own exit sequence (sbrk + ptrtoint + seal_return).

Code size improvements:
- ERC20:              13051 → 12681 (-2.8%)
- DivisionArithmetics: 13243 → 12950 (-2.2%)

All 62 integration tests pass with RESOLC_USE_NEWYORK=1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Common subexpression elimination for pure environment reads
   (calldatasize, callvalue, caller, origin, address). After
   inlining, multiple switch cases redundantly call these builtins.
   CSE replaces 2nd+ occurrences with references to the first
   binding, enabling LLVM to eliminate redundant loads.

2. Storage load/store by-value functions: pass key and value as
   i256 directly instead of through alloca+pointer. Eliminates
   2x alloca + 2x store overhead per storage operation.

3. Outlined caller_word function: moves the syscall + bswap + zext
   sequence into a shared function to avoid code duplication.

4. Fix clippy: remove useless .into() on BasicValueEnum arguments.

Code size improvements:
- ERC20:    12681 → 12386 (-2.3%)
- Flipper:   1507 →  1493 (-0.9%)

All 62 integration tests pass with RESOLC_USE_NEWYORK=1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
neo and others added 7 commits May 21, 2026 11:31
Co-authored-by: xermicus <bigcyrill@hotmail.com>
Resolves conflicts after LLVM 21 → 22 upgrade and inkwell 0.8 → 0.9
landed on main:

* Cargo.toml: keep bumped versions from main; preserve revive-newyork.
* docs/: regenerated with mdbook against cl/newyork sources.
* function/mod.rs: keep ours' NoFree+NoUnwind PVM target attributes;
  keep main's is_middle_end_enabled gate around NoInline/OptimizeNone.
* context/mod.rs: drop narrow_divrem_instructions and
  strip_minsize_for_divrem — both were LLVM 21 backend workarounds
  removed by main in #501 once the upstream patch landed; LLVM 22
  bundles the fix.
* heap.rs: port inkwell 0.8 → 0.9 API breaks (custom_width_int_type
  now takes NonZeroU32 and returns Result).
* codesize.json: kept ours (LLVM 22 bumps integration sizes ~5–60%;
  update intentionally in a follow-up).

Verified: RESOLC_USE_NEWYORK=1 make test green (workspace + book).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a per-operation reference for the newyork IR to the compiler book
for easily being able to view printed syntax, operand and result types,
examples, etc. for any operation. Using easily readable syntax notation.
The new page is partly inspired by [MLIR's
docs](https://mlir.llvm.org/docs/Dialects/SCFDialect/).

<details>
  <summary>Example entry</summary>

### `and`

(`Expression::Binary` with `BinaryOperation::And`)

#### Description

Bitwise AND. The common idiom for type narrowing: a constant mask on the
right lets forward analysis pick up a tight result width.

#### Syntax

```text
and($lhs[: <type>], $rhs[: <type>])
```

#### Example

```text
let v2 := and(v0, v1)
let v3 := and(v0, 0xff)     // type inference narrows result to i8
```

#### Operands

| Name | Type | Notes |
|---|---|---|
| `lhs` | `i256` | — |
| `rhs` | `i256` | — |

#### Result and purity

| Result | Purity |
|---|---|
| `min(width(lhs), width(rhs))` — AND can only clear bits, so the result
fits in the narrower operand | Pure |

#### Annotations

None.

</details>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
* Drop the -O0 `optnone`/`noinline` gate from `set_default_attributes`
  (main #502). It pinned both attributes on every function when the
  middle end is off; with newyork-emitted IR (many small helpers
  expecting to be inlined) the backend's fast-isel produced PVM basic
  blocks over the 1000-instruction `BasicBlockTooLarge` limit. #502
  itself reported the change as NFC on main, so the safeguard was
  inert there but actively harmful here.

* Restore `narrow_divrem_instructions` after the LLVM pipeline.
  `strip_minsize_for_divrem` stays dropped — LLVM 22 carries the patch
  that made it unnecessary — but the narrowing safety net still helps
  i256 div/rem expand into compact backend code.

* In `LlvmCodegen::set_inline_attributes`, early-return when the
  middle end is disabled and route the attribute write through
  `PolkaVMFunction::set_attributes(force = true)`. The raw
  `add_attribute` call stacked `alwaysinline` on top of the
  forced `noinline`/`optnone`, which the LLVM IR verifier rejected
  during library post-link compilation.

`RESOLC_USE_NEWYORK=1 retester.sh`: 34888 passed, 6 failed, 144
ignored. The six failures are `blockhash(0xFFFF…)` at S- across all
Y M{0,1,2,3,s,z} — a pre-existing newyork edge case masked at S+ by
solc's optimizer folding the call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-existing newyork edge case: `blockhash(0xFFFF…)` reverts instead
of returning 0. solc's optimizer constant-folds the call to 0 at S+,
so the failure is only visible across Y M{0,1,2,3,s,z} at S-.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Comment thread crates/newyork/src/lib.rs Outdated
Comment thread crates/newyork/src/printer.rs
Comment thread crates/newyork/src/printer.rs
Comment thread crates/newyork/src/printer.rs
Comment thread crates/newyork/src/printer.rs Outdated
Comment thread crates/newyork/src/printer.rs Outdated
xermicus and others added 22 commits May 26, 2026 08:06
- Validation errors should lead to ICE instead of just a warning
- Fix validation false positives

Co-authored-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
…rence

Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Co-authored-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Co-authored-by: xermicus <bigcyrill@hotmail.com>
Reviewed-on: https://forgejo.lab/neo/revive/pulls/8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants