SignedRemainder lowers Solidity signed % (EVM SMOD) to a bare LLVM srem via build_int_signed_rem, guarded only against a zero divisor by wrapped_division. For divisor -1 it emits srem x, -1, which is UB in LLVM when x == INT256_MIN and constant-folds to poison.
MRE:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
contract C {
function f(int256 x) external pure returns (int256) {
// INT_MIN % -1 == 0, so this is the identity `return x;`
unchecked { return (type(int256).min % ((int256(-1) << 58) >> 58)) ^ x; }
}
}
Reproduce: compile with resolc -O3 --bin mre.sol and call f(5) on PolkaVM/pallet-revive — it returns 0. The same source compiled with solc --bin-runtime --optimize and run on go-ethereum evm returns 5.
EVM: f(5) = 0x...05 (correct)
PolkaVM: f(5) = 0x...00 (whole function collapsed to 0)
Expected behavior: f(x) == x for all x (EVM SMOD(INT_MIN, -1) = 0).
Notes:
- Only signed
% (srem); / (sdiv) is guarded and correct.
- Runtime
x % y with (INT_MIN, -1) is correct — the bare srem lowers to RISC-V rem, which defines INT_MIN rem -1 = 0; the bug is only LLVM's constant-fold of srem(constINT_MIN, const-1), so the dividend must also be compile-time constant.
- The divisor must be a
-1 that solc does not pre-fold to a literal (here (int256(-1) << 58) >> 58); writing int256(-1) directly makes solc fold INT_MIN % -1 → 0 itself and the bug disappears.
- Silent: no diagnostic; PolkaVM bytecode returns the wrong constant.
Git commit: 7fe8d41 (revive)
Build: resolc release, -O3; solc 0.8.35-develop; go-ethereum evm 1.17.2
SignedRemainderlowers Solidity signed%(EVMSMOD) to a bare LLVMsremviabuild_int_signed_rem, guarded only against a zero divisor bywrapped_division. For divisor-1it emitssrem x, -1, which is UB in LLVM whenx == INT256_MINand constant-folds to poison.MRE:
Reproduce: compile with
resolc -O3 --bin mre.soland callf(5)on PolkaVM/pallet-revive — it returns0. The same source compiled withsolc --bin-runtime --optimizeand run on go-ethereumevmreturns5.Expected behavior:
f(x) == xfor allx(EVMSMOD(INT_MIN, -1) = 0).Notes:
%(srem);/(sdiv) is guarded and correct.x % ywith(INT_MIN, -1)is correct — the baresremlowers to RISC-Vrem, which definesINT_MIN rem -1 = 0; the bug is only LLVM's constant-fold ofsrem(constINT_MIN, const-1), so the dividend must also be compile-time constant.-1that solc does not pre-fold to a literal (here(int256(-1) << 58) >> 58); writingint256(-1)directly makes solc foldINT_MIN % -1 → 0itself and the bug disappears.Git commit: 7fe8d41 (revive)
Build: resolc release, -O3; solc 0.8.35-develop; go-ethereum evm 1.17.2