Release constant folding removes arithmetic operations that must clear $of
Release constant folding removes arithmetic operations that must clear $of
Related Component
compiler
Problem
In Sway v0.72.1 (dad95cc42b0383b4e3bacdeda9766565aa584a92), release constant folding removes four value-identity operations:
x + 0x * 1x - 0x / 1
Although these expressions preserve their ordinary result, the corresponding FuelVM arithmetic instructions also update the special arithmetic status registers. In particular, a successful operation clears $of. Removing the instruction can therefore leave a stale overflow value visible through std::registers::overflow().
The same contract tests pass in a debug build and fail in a release build. In each failing release call, the returned arithmetic value is correct but overflow() returns 1 where execution of the source operation requires 0.
The fold is implemented in sway-ir/src/optimize/constants.rs, in remove_useless_binary_op(). It models only the result value of BinaryOp, not its FuelVM status-register effects.
Steps
Save this as a contract and run forc test --test-threads 1, followed by forc test --release --test-threads 1:
contract;
use std::{
flags::{disable_panic_on_overflow, set_flags},
registers::overflow,
};
abi Probe {
fn add_identity(a: u64, b: u64, x: u64) -> (u64, u64);
fn mul_identity(a: u64, b: u64, x: u64) -> (u64, u64);
fn sub_identity(a: u64, b: u64, x: u64) -> (u64, u64);
fn div_identity(a: u64, b: u64, x: u64) -> (u64, u64);
}
impl Probe for Contract {
fn add_identity(a: u64, b: u64, x: u64) -> (u64, u64) {
let old = disable_panic_on_overflow();
let _overflowing = a + b;
let value = x + 0;
let status = overflow();
set_flags(old);
(value, status)
}
fn mul_identity(a: u64, b: u64, x: u64) -> (u64, u64) {
let old = disable_panic_on_overflow();
let _overflowing = a + b;
let value = x * 1;
let status = overflow();
set_flags(old);
(value, status)
}
fn sub_identity(a: u64, b: u64, x: u64) -> (u64, u64) {
let old = disable_panic_on_overflow();
let _overflowing = a + b;
let value = x - 0;
let status = overflow();
set_flags(old);
(value, status)
}
fn div_identity(a: u64, b: u64, x: u64) -> (u64, u64) {
let old = disable_panic_on_overflow();
let _overflowing = a + b;
let value = x / 1;
let status = overflow();
set_flags(old);
(value, status)
}
}
#[test]
fn identities_clear_overflow() {
let p = abi(Probe, CONTRACT_ID);
assert(p.add_identity(u64::max(), 1, 7) == (7, 0));
assert(p.mul_identity(u64::max(), 1, 7) == (7, 0));
assert(p.sub_identity(u64::max(), 1, 7) == (7, 0));
assert(p.div_identity(u64::max(), 1, 7) == (7, 0));
}Observed:
debug: test passes
release: test fails because each status value is 1The release IR no longer contains the four identity operations.
Possible Solution(s)
The optimizer needs an effect model for arithmetic status. Until that exists, a conservative fix is to stop deleting these identity operations when their special-register effects may be observed. Regression tests should cover all four identities after an overflow, in both build profiles.
Notes
The overflow panic is deliberately disabled so execution reaches the identity operation. The issue is the stale status left by removal of a later, successful arithmetic instruction, not the wrapped value of the earlier operation.
Installed components
forc 0.72.1
sway 0.72.1
commit dad95cc42b0383b4e3bacdeda9766565aa584a92Source: FuelLabs/sway