#7732·sway

Release constant folding removes arithmetic operations that must clear $of

Author: DicksonWu654Created Sep 11, 2026Updated Sep 13, 2026

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 + 0
  • x * 1
  • x - 0
  • x / 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:

sway
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 1

The 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

bash
forc 0.72.1
sway 0.72.1
commit dad95cc42b0383b4e3bacdeda9766565aa584a92