Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Supported `polkadot-sdk` rev: `2604.2.0`
### 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)

## v1.3.0

Expand Down
173 changes: 173 additions & 0 deletions crates/integration/contracts/FoldedGuardZext.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/// ICE PoC (paritytech/revive#560): the one-armed `if` lowering in `to_llvm`
/// built the conditional branch first and materialized the fall-through
/// `inputs` afterwards, with the builder still positioned at the end of the
/// now-terminated entry block. That is invisible while every input is already
/// word-typed (no instruction is emitted), but a narrow input — here the raw
/// `gt` comparison result flowing out of the inlined `fun_h0` — needs a `zext`
/// to reach the join phi, and that `zext` landed after the terminator:
/// "Basic Block in function 'fun_run_runtime' does not have terminator!".
///
/// The shape needs all of:
/// - `fun_h0`'s `if 0x01 { ... leave }`: the constant-true guard folds, so
/// the `leave`-carried return value copy-propagates to the bare `gt`
/// (an i1 value that is neither a constant nor a word-typed if-output);
/// - `fun_h1(acc, acc)`: equal arguments fold `iszero(lt(a, b))` to
/// constant true, collapsing the leave-`if` so the `leave`-elimination
/// guard around the remainder keeps the raw `gt` as its fall-through
/// input with a constant-false condition;
/// - the rotated `for` loop (condition `1`, `break` in the body — solc's
/// unoptimized lowering) before that guard, which keeps the simplifier
/// from folding the constant-condition `if` away;
/// - `fun_run` staying above the never-inline size threshold (the verbose
/// copy chains are load-bearing): fully inlined into the entry block,
/// the guard folds and the shape disappears.
///
/// This is solc's `--no-optimize-yul` output for issue #560's contract with
/// the selector dispatch and revert-helper indirections trimmed. `run(s)`
/// never assigns its unnamed return value, so every call must return 0.
object "FoldedGuardZext" {
code {
let size := datasize("FoldedGuardZext_deployed")
codecopy(0, dataoffset("FoldedGuardZext_deployed"), size)
return(0, size)
}
object "FoldedGuardZext_deployed" {
code {
{
mstore(64, memoryguard(0x80))
external_fun_run()
revert(0, 0)
}
function allocate_unbounded() -> memPtr
{ memPtr := mload(64) }
function cleanup_uint256(value) -> cleaned
{ cleaned := value }
function validator_revert_uint256(value)
{
if iszero(eq(value, cleanup_uint256(value))) { revert(0, 0) }
}
function abi_decode_uint256(offset, end) -> value
{
value := calldataload(offset)
validator_revert_uint256(value)
}
function abi_decode_tuple_uint256(headStart, dataEnd) -> value0
{
if slt(sub(dataEnd, headStart), 32)
{
revert(0, 0)
}
let offset := 0
value0 := abi_decode_uint256(add(headStart, offset), dataEnd)
}
function abi_encode_uint256_to_uint256(value, pos)
{
mstore(pos, cleanup_uint256(value))
}
function abi_encode_uint256(headStart, value0) -> tail
{
tail := add(headStart, 32)
abi_encode_uint256_to_uint256(value0, add(headStart, 0))
}
function external_fun_run()
{
if callvalue()
{
revert(0, 0)
}
let param := abi_decode_tuple_uint256(4, calldatasize())
let ret := fun_run(param)
let memPos := allocate_unbounded()
let memEnd := abi_encode_uint256(memPos, ret)
return(memPos, sub(memEnd, memPos))
}
function zero_value_for_split_uint256() -> ret
{ ret := 0 }
function fun_run(var_s) -> var
{
let zero_uint256 := zero_value_for_split_uint256()
var := zero_uint256
let _1 := var_s
let expr := _1
let var_acc := expr
let _2 := var_acc
let expr_1 := _2
let _3 := var_acc
let expr_2 := _3
let expr_3 := fun_h0(expr_1, expr_2)
var_acc := expr_3
let _4 := var_acc
let expr_4 := _4
let _5 := var_acc
let expr_5 := _5
let expr_6 := fun_h1(expr_4, expr_5)
var_acc := expr_6
}
function fun_h0(var_a, var_b) -> var_r
{
let zero_t_uint256 := zero_value_for_split_uint256()
var_r := zero_t_uint256
let expr := 0x01
if expr
{
var_r := gt(var_r, var_b)
let _1 := var_r
let expr_1 := _1
var_r := expr_1
leave
}
}
function cleanup_t_rational_by(value) -> cleaned
{ cleaned := value }
function identity(value) -> ret
{ ret := value }
function convert_t_rational_by_to_t_uint256(value) -> converted
{
converted := cleanup_uint256(identity(cleanup_t_rational_by(value)))
}
function increment_wrapping_uint256(value) -> ret
{
ret := cleanup_uint256(add(value, 1))
}
function cleanup_rational_by(value) -> cleaned
{ cleaned := value }
function convert_rational_by_to_uint256(value) -> converted
{
converted := cleanup_uint256(identity(cleanup_rational_by(value)))
}
function fun_h1(var_a, var_b) -> var_r
{
let zero_uint256 := zero_value_for_split_uint256()
var_r := zero_uint256
let expr := 0x00
let var_i := convert_t_rational_by_to_t_uint256(expr)
for { }
1
{
let _1 := var_i
let _2 := increment_wrapping_uint256(_1)
var_i := _2
}
{
let _3 := var_i
let expr_1 := _3
let expr_2 := 0x01
let expr_3 := lt(cleanup_uint256(expr_1), convert_rational_by_to_uint256(expr_2))
if iszero(expr_3) { break }
}
let _4 := var_a
let expr_4 := _4
let _5 := var_b
let expr_5 := _5
let expr_6 := iszero(lt(cleanup_uint256(expr_4), cleanup_uint256(expr_5)))
if expr_6
{
let _6 := var_a
let expr_7 := _6
var_r := expr_7
leave
}
}
}
}
}
24 changes: 24 additions & 0 deletions crates/integration/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2844,6 +2844,30 @@ fn switch_callvalue_cse_dangling() {
run_differential(actions);
}

/// The one-armed `if` lowering must materialize its fall-through `inputs`
/// before the conditional branch terminates the entry block: a narrow (i1) input needs a `zext` to
/// reach the join phi, and emitting it after the branch leaves the block without a trailing
/// terminator ("Basic Block ... does not have terminator! label %for_join"). Compiling at all
/// exercises the fix; `run(s)` never assigns its return value, so every call returns 0. See
/// FoldedGuardZext.yul (paritytech/revive#560).
#[test]
fn folded_guard_narrow_if_input_zext() {
let mut actions = instantiate_yul("contracts/FoldedGuardZext.yul", "FoldedGuardZext");
for s in [U256::ZERO, U256::from(7u64), U256::MAX] {
let mut data = vec![0xa4, 0x44, 0xf5, 0xe9];
data.extend_from_slice(&s.to_be_bytes::<32>());
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data,
});
}
run_differential(actions);
}

/// The panic-pattern outliner must not collapse a window that contains a
/// side-effecting call. `inner()` reverts with 0xdeadbeef before the panic revert; dropping it and
/// emitting PanicRevert would revert with the wrong payload. EVM reverts with 0xdeadbeef; the bug
Expand Down
3 changes: 1 addition & 2 deletions crates/newyork/src/to_llvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4714,8 +4714,6 @@ impl<'ctx> LlvmCodegen<'ctx> {
}
} else {
let entry_block = context.basic_block();
context.build_conditional_branch(condition_bool, then_block, join_block)?;

let mut else_yields = Vec::new();
for (index, input_value) in inputs.iter().enumerate() {
else_yields.push(self.translate_value_as_word(
Expand All @@ -4724,6 +4722,7 @@ impl<'ctx> LlvmCodegen<'ctx> {
&format!("input_yield_{}", index),
)?);
}
context.build_conditional_branch(condition_bool, then_block, join_block)?;
phi_incoming.push((else_yields, entry_block));

context.set_basic_block(then_block);
Expand Down
4 changes: 4 additions & 0 deletions crates/resolc/src/cli_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ pub const SOLIDITY_DEPENDENCY_CONTRACT_PATH: &str = "src/tests/data/solidity/dep
/// The simple Solidity contract containing i256 divisions and remains
/// that should be compiled correctly.
pub const SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH: &str = "src/tests/data/solidity/large_div_rem.sol";
/// The verbatim reproducer from paritytech/revive#560: a folded guard plus an
/// inlined for-loop that crashed the newyork pipeline under `--disable-solc-optimizer`.
pub const SOLIDITY_FOLDED_GUARD_INLINED_LOOP_PATH: &str =
"src/tests/data/solidity/folded_guard_inlined_loop.sol";

/// The simple YUL contract test fixture path.
pub const YUL_CONTRACT_PATH: &str = "src/tests/data/yul/contract.yul";
Expand Down
19 changes: 17 additions & 2 deletions crates/resolc/src/tests/cli/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

use crate::{
cli_utils::{
absolute_path, assert_command_success, execute_command, CommandResult, ResolcOptSettings,
SolcOptSettings, SOLIDITY_CONTRACT_PATH, STANDARD_JSON_CONTRACTS_PATH,
absolute_path, assert_command_success, execute_command, execute_resolc, CommandResult,
ResolcOptSettings, SolcOptSettings, SOLIDITY_CONTRACT_PATH,
SOLIDITY_FOLDED_GUARD_INLINED_LOOP_PATH, STANDARD_JSON_CONTRACTS_PATH,
YUL_MEMSET_CONTRACT_PATH,
},
SolcCompiler,
Expand Down Expand Up @@ -165,6 +166,20 @@ fn compiles_yul_to_binary_blob() {
assert_evm_blob_from_yul(&solc_result);
}

/// Compile regression for [paritytech/revive#560](https://github.com/paritytech/revive/issues/560):
/// the verbatim Solidity from the issue crashed the newyork pipeline under
/// `--disable-solc-optimizer` with "Basic Block in function 'fun_run_runtime' does
/// not have terminator! label %for_join" (the one-armed `if` lowering emitted the
/// fall-through input `zext` after the branch terminator).
#[test]
fn compiles_newyork_folded_guard_inlined_loop() {
let path = absolute_path(SOLIDITY_FOLDED_GUARD_INLINED_LOOP_PATH);
let arguments = &[&path, "--newyork", "--disable-solc-optimizer", "--bin"];

let result = execute_resolc(arguments);
assert_pvm_blob(&result);
}

/// This test mimics the command used in the `resolc` benchmarks when compiling Solidity via standard JSON input.
#[test]
fn compiles_json_to_binary_blob() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma solidity ^0.8.0;
contract C {
function h0(uint256 a, uint256 b) internal returns (uint256 r) {
if (true) { assembly { r := gt(r, b) } return r; }
}
function h1(uint256 a, uint256 b) internal returns (uint256 r) {
for (uint256 i = 0; i < 1; i++) {}
if (a >= b) { return a; }
}
function run(uint256 s) external returns (uint256) {
uint256 acc = s;
acc = h0(acc, acc);
acc = h1(acc, acc);
}
}
Loading