LJ 32763fedf2 Fix Windows bytecode divergence during LLVM LSR (#570)
## Description

Our differential bytecode comparison tests detected a bytecode mismatch
between the Linux/macOS builds and the Windows build for a
[contract](https://github.com/paritytech/resolc-compiler-tests/blob/4647018d64eab6ccff30d22c753f02b90e9fc7e3/fixtures/solidity/simple/internal_function_pointers/sort/bubble.sol)
implementing recursive bubble sort, specifically with these opt
settings:
* Solc optimizer disabled
* Newyork enabled
* `-Oz`

This PR applies an [LLVM
patch](https://github.com/llvm/llvm-project/pull/190587) which fixes the
divergence for contracts with loops that index array elements at
constant offsets from a Solidity `uint8` loop variable, observed with
the above opt settings.

## Cause

The first divergence occurs in an LLVM Loop Strength Reduction (LSR)
decision, on `bubbleSort`'s loop (`for (uint8 i = 0; i < ARRAY_SIZE -
start - 1; i++)` with `array[i]`/`array[i+1]` accesses):

```diff
-  %lsr.iv = phi i32 [ 0, %for_cond.preheader153 ], [ %lsr.iv.next, %if_join64 ]   ; Linux/macOS
+  %lsr.iv = phi i32 [ 1, %for_cond.preheader153 ], [ %lsr.iv.next, %if_join64 ]   ; Windows
+  %3 = add i32 %lsr.iv, -1                                                        ; Windows
 ...
-  %3 = add i32 %lsr.iv, 1                                                         ; Linux/macOS
 ...
   %lsr.iv.next = add nuw nsw i32 %lsr.iv, 1   ; identical on both platforms: step is +1 either way
```

In LLVM's `{Start,+,Step}` recurrence notation (value starts at `Start`,
changes by `Step` per loop iteration), the three constants in the diff
represent: recurrence *start*, use-site *offset*, and recurrence *step*.
Only the first two differ between the platforms:

**Linux/macOS** (the `array[i]` calls take `%lsr.iv` directly; the
`array[i+1]` calls take `%3`):

| Line | Constant | Role |
|---|---|---|
| `%lsr.iv = phi i32 [ 0, … ]` | `0` | **Start**: the register's value
on loop entry (`i`) (holds 0, 1, 2, …) |
| `%3 = add i32 %lsr.iv, 1` | `+1` | **Use-site offset**: derives the
*other* index (`i + 1`) from the register each iteration. (No register
is dedicated to `%3`) |
| `%lsr.iv.next = add … , 1` | `+1` | **Step**: how much the register
advances each iteration |

**Windows** (mirrored: the `array[i+1]` calls take `%lsr.iv` directly;
the `array[i]` calls take `%3`):

| Line | Constant | Role |
|---|---|---|
| `%lsr.iv = phi i32 [ 1, … ]` | `1` | **Start**: the register's value
on loop entry (`i + 1`) (holds 1, 2, 3, …) |
| `%3 = add i32 %lsr.iv, -1` | `-1` | **Use-site offset**: derives the
*other* index (`i`) from the register each iteration. (No register is
dedicated to `%3`) |
| `%lsr.iv.next = add … , 1` | `+1` | **Step**: identical |

The two solutions are mirror images with exactly equal cost, because
`getSetupCost()` (the patched function) rated *any* constant induction
variable (IV) start equally (a 0-start and a 1-start IV cost the same).
LSR keeps the first solution it enumerates, and the enumeration order
derives from walking SSA use-lists, whose in-memory order differed
between our Linux/macOS and Windows builds of LLVM.

Thus both platforms compute the indices `i` and `i + 1` each iteration
(functionally identical), but the LSR decision of which of the two
recurrences becomes the loop-carried value (the phi) differs, which
affects register allocation.

> [!NOTE]
> The equal cost tie can only arise if LSR processes the loop at all,
and LSR only forms uses for induction variables of a 32-bit or 64-bit
integer in our case.

## Partial solution

The [upstream fix](https://github.com/llvm/llvm-project/pull/190587)
updates `getSetupCost()` to return `0` for constant IV starts whose
materialization is `TCC_Free` (which immediate `0` on RISC-V is), which
is now cheaper than non-zero starts, resulting in the same LSR decision
(no tie to decide on) on Windows as well.

As zero-start IVs are most common, this fix addresses the most common
scenarios, but it is still only a partial fix as it specifically solves
only the zero-start case. (I'm currently writing up another issue
regarding this.)

## Verification

I created a draft LLVM release that was built with the patch applied,
then reran all workflows using the draft release, including temporarily
testing more opt settings for the bytecode comparisons which ran
successfully (no mismatches) with the following `retester` modes (see
[this
run](https://github.com/paritytech/revive/actions/runs/28970814016/job/85982029937)):
* `NY M0 S+`
* `NY M0 S-`
* `NY M3 S+`
* `NY M3 S-`
* `NY Mz S+`
* `NY Mz S-`
* `Y M0 S+`
* `Y M0 S-`
* `Y M3 S+`
* `Y M3 S-`
* `Y Mz S+`
* `Y Mz S-`

All E2E tests also ran as it does today (allowing all Yul and newyork IR
`retester` modes), completing successfully.
2026-07-09 13:17:28 +02:00
2026-02-23 15:48:07 -05:00
2026-06-19 11:50:41 +02:00
2025-05-08 12:36:11 +02:00
2026-07-02 10:22:32 +02:00
2025-01-13 15:58:27 +01:00
2026-04-28 14:28:14 -04:00
2025-05-08 12:36:11 +02:00
2024-03-12 12:06:02 +01:00
2024-03-12 12:06:02 +01:00
2026-06-17 12:35:16 +02:00
2026-06-19 11:50:41 +02:00
2026-04-28 14:28:14 -04:00
2025-01-13 15:58:27 +01:00

CI Docs

revive

Yul recompiler to LLVM, targetting RISC-V on PolkaVM.

Check the docs or visit contracts.polkadot.io to learn more about revive and contracts on Polkadot!

Discussion around the development is hosted on the Polkadot Forum.

Warning

Solidity on PVM is running on the pallet-revive runtime. This introduces observable semantic differences in comparison with the EVM.

Study the differences section carefully. Ignoring these differences may lead to defunct contracts.

Notable examples:

  • The 63/64 gas rule isn't implemented in the pallet (introduces potential DoS vector when calling other contracts)
  • Contract instantiation works differently (by hash instead of by code)
  • The gas model implemented by pallet-revive differs from Ethereum
  • The heap size is fixed instead of gas-metered and there's a fixed amount of stack size (contracts working fine on EVM may trap on PVM)

Installation

Building Solidity contracts for PolkaVM requires installing the following two compilers:

resolc binary releases

resolc is distributed as a standalone binary (with solc as the only external dependency). Please download one of our binary releases for your target platform and mind the platform specific instructions below. We also provide nightly builds.

MacOS users

MacOS users need to clear the downloaded attribute from the binary and set the executable flag.

xattr -rc resolc-universal-apple-darwin
chmod +x resolc-universal-apple-darwin
Linux users

Linux users need to set the executable flag.

chmod +x resolc-x86_64-unknown-linux-musl

resolc NPM package

We distribute the revive compiler as node.js module and hardhat plugin.

Note: The solc dependency is bundled via NPM packaging and defaults to the latest supported version.

Building from source

Building revive requires a stable Rust installation and a C++ toolchain for building LLVM on your system.

LLVM

revive depends on a custom build of LLVM v18.1.8 with the RISC-V embedded target, including the compiler-rt builtins. You can either download a build from our releases (recommended for older hardware) or build it from source.

Download from our LLVM releases

Download the latest LLVM build from our releases.

MacOS users need to clear the downloaded attribute from all binaries after extracting the archive:

xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/*

After extracting the archive, point $LLVM_SYS_221_PREFIX to it:

export LLVM_SYS_221_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final
Building from source

The Makefile provides a shortcut target to obtain a compatible LLVM build, using the provided revive-llvm utility. Once installed, point $LLVM_SYS_221_PREFIX to the installation afterwards:

make install-llvm
export LLVM_SYS_221_PREFIX=${PWD}/target-llvm/gnu/target-final

The resolc Solidity frontend

To build the resolc Solidity frontend executable, make sure you have obtained a compatible LLVM build and did export the LLVM_SYS_221_PREFIX environment variable pointing to it (see above).

To install the resolc Solidity frontend executable:

make install-bin
resolc --version

Development

Please consult the Developer Guide to learn more about how contribute to the project.

Acknowledgements

The revive compiler project, after some early experiments with EVM bytecode translations, decided to fork the era-compiler framework. Frontend, code generator and some supporting libraries are based of ZKSync zksolc. I'd like to express my gratitude and thank the original authors for providing a useable code base under a generous license.

S
Description
No description provided
Readme
29 MiB
1 Watchers 0 Forks
Languages
Rust 89.2%
Solidity 4.4%
Yul 3.4%
JavaScript 1%
Python 0.4%
Other 1.5%