Skip to content
28 changes: 28 additions & 0 deletions docs/changes/2026-03-27-evm-peepholes-branch-shaping/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Change: EVM peepholes and branch shaping

- **Status**: Implemented
- **Date**: 2026-03-27
- **Tier**: Light

## Overview

Add EVM frontend peepholes for zero/one/all-ones identities in ADD, SUB, MUL, AND, OR, XOR. Defer NOT and zero-test materialization so ISZERO chains can collapse without building redundant 256-bit MIR values. Improve x86 lowering and CG peepholes for compare chains and short-diamond branches.

## Motivation

The EVM frontend mechanically expands all operations to full 256-bit MIR regardless of operand values. Identity operations (add 0, mul 1, and all-ones, etc.) generate unnecessary instructions that are trivially eliminable at the frontend level.

## Impact

- Modules: `docs/modules/compiler/` (EVM frontend + x86 lowering)
- 5 files changed in `src/compiler/evm_frontend/` and `src/compiler/target/x86/`
- Benchmark geomean: +11.4%, total time reduction ~3.8%
- Notable: jump_around/empty ~48% faster
- 1798/1798 state tests pass

## Checklist

- [x] Implementation complete
- [x] Tests added/updated
- [ ] Module specs in `docs/modules/` updated (if affected)
- [x] Build and tests pass
54 changes: 41 additions & 13 deletions src/compiler/evm_frontend/evm_mir_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1742,6 +1742,18 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleMul(Operand MultiplicandOp,
return Operand(intxToU256Value(A * B));
}

if (MultiplicandOp.isZeroConstant() || MultiplierOp.isZeroConstant()) {
return Operand(U256Value{0, 0, 0, 0});
}

if (MultiplicandOp.isOneConstant()) {
return MultiplierOp;
}

if (MultiplierOp.isOneConstant()) {
return MultiplicandOp;
}

// Phase 4: u64 fast path - one operand fits in u64 (4x1 multiplication)
bool AIsU64 = MultiplicandOp.isConstU64();
bool BIsU64 = MultiplierOp.isConstU64();
Expand Down Expand Up @@ -2387,7 +2399,8 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleExp(Operand BaseOp,
}

EVMMirBuilder::U256Inst EVMMirBuilder::handleCompareEQZ(const U256Inst &LHS,
MType *ResultType) {
MType *ResultType,
bool IsNegated) {
U256Inst Result = {};
MType *MirI64Type =
EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64);
Expand All @@ -2405,7 +2418,8 @@ EVMMirBuilder::U256Inst EVMMirBuilder::handleCompareEQZ(const U256Inst &LHS,

// Final result is 1 if all are zero, 0 otherwise
MInstruction *Zero = createIntConstInstruction(MirI64Type, 0);
auto Predicate = CmpInstruction::Predicate::ICMP_EQ;
auto Predicate = IsNegated ? CmpInstruction::Predicate::ICMP_NE
: CmpInstruction::Predicate::ICMP_EQ;
MInstruction *CmpResult = createInstruction<CmpInstruction>(
false, Predicate, ResultType, OrResult, Zero);

Expand Down Expand Up @@ -2529,19 +2543,11 @@ typename EVMMirBuilder::Operand EVMMirBuilder::handleNot(const Operand &LHSOp) {
return Operand(U256Value{~V[0], ~V[1], ~V[2], ~V[3]});
}

U256Inst Result = {};
U256Inst LHS = extractU256Operand(LHSOp);

MType *MirI64Type =
EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64);

for (size_t I = 0; I < EVM_ELEMENTS_COUNT; ++I) {
MInstruction *LocalResult =
createInstruction<NotInstruction>(false, MirI64Type, LHS[I]);
Result[I] = protectUnsafeValue(LocalResult, MirI64Type);
if (LHSOp.isDeferredBitwiseNot()) {
return Operand(LHSOp.getDeferredBaseComponents(), EVMType::UINT256);
}

return Operand(Result, EVMType::UINT256);
return Operand::createDeferredBitwiseNot(extractU256Operand(LHSOp));
}

// ==================== u64 Fast Path Helpers ====================
Expand Down Expand Up @@ -4493,6 +4499,23 @@ EVMMirBuilder::U256Inst EVMMirBuilder::extractU256Operand(const Operand &Opnd) {
return Result;
}

if (Opnd.isDeferredBitwiseNot()) {
const U256Inst &Base = Opnd.getDeferredBaseComponents();
MType *MirI64Type =
EVMFrontendContext::getMIRTypeFromEVMType(EVMType::UINT64);
for (size_t I = 0; I < EVM_ELEMENTS_COUNT; ++I) {
MInstruction *LocalResult =
createInstruction<NotInstruction>(false, MirI64Type, Base[I]);
Result[I] = protectUnsafeValue(LocalResult, MirI64Type);
}
return Result;
}

if (Opnd.isDeferredZeroTest()) {
return handleCompareEQZ(Opnd.getDeferredBaseComponents(), &Ctx.I64Type,
Opnd.isDeferredZeroTestNegated());
}
Comment thread
abmcar marked this conversation as resolved.

if (Opnd.isU256MultiComponent()) {
U256Inst Instrs = Opnd.getU256Components();
if (Instrs[0] != nullptr) {
Expand Down Expand Up @@ -5095,6 +5118,11 @@ EVMMirBuilder::convertOperandToUNInstruction(const Operand &Param) {
for (size_t I = 0; I < N; ++I) {
Result[I] = Components[I];
}
} else if (Param.isDeferredValue()) {
auto Components = extractU256Operand(Param);
for (size_t I = 0; I < N; ++I) {
Result[I] = Components[I];
}
} else if (Param.isU256MultiComponent()) {
auto Components = Param.getU256Components();
if (Components[0] != nullptr) {
Expand Down
108 changes: 106 additions & 2 deletions src/compiler/evm_frontend/evm_mir_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ class EVMMirBuilder final {

class Operand {
public:
enum class DeferredKind : uint8_t {
NONE,
BITWISE_NOT,
ZERO_TEST_EQ,
ZERO_TEST_NE
};

Operand() = default;
Operand(MInstruction *Instr, EVMType Type) : Instr(Instr), Type(Type) {}
Operand(Variable *Var, EVMType Type) : Var(Var), Type(Type) {}
Expand All @@ -138,21 +145,66 @@ class EVMMirBuilder final {
Operand(const U256Value &ConstValue)
: Type(EVMType::UINT256), ConstValue(ConstValue), IsConstant(true) {}

static Operand createDeferredBitwiseNot(U256Inst BaseComponents) {
Operand Result;
Result.Type = EVMType::UINT256;
Result.DeferredValueKind = DeferredKind::BITWISE_NOT;
Result.U256Components = BaseComponents;
return Result;
}

static Operand createDeferredZeroTest(U256Inst BaseComponents,
bool IsNegated) {
Operand Result;
Result.Type = EVMType::UINT256;
Result.DeferredValueKind =
IsNegated ? DeferredKind::ZERO_TEST_NE : DeferredKind::ZERO_TEST_EQ;
Result.U256Components = BaseComponents;
return Result;
}

MInstruction *getInstr() const { return Instr; }
Variable *getVar() const { return Var; }
EVMType getType() const { return Type; }

bool isEmpty() const {
return !Instr && !Var && !IsU256MultiComponent && !IsConstant &&
Type == EVMType::VOID;
DeferredValueKind == DeferredKind::NONE && Type == EVMType::VOID;
}

bool isU256MultiComponent() const { return IsU256MultiComponent; }
bool isConstant() const { return IsConstant; }
bool isZeroConstant() const {
return IsConstant && ConstValue[0] == 0 && ConstValue[1] == 0 &&
ConstValue[2] == 0 && ConstValue[3] == 0;
}
bool isOneConstant() const {
return IsConstant && ConstValue[0] == 1 && ConstValue[1] == 0 &&
ConstValue[2] == 0 && ConstValue[3] == 0;
}
bool isAllOnesConstant() const {
return IsConstant && ConstValue[0] == UINT64_MAX &&
ConstValue[1] == UINT64_MAX && ConstValue[2] == UINT64_MAX &&
ConstValue[3] == UINT64_MAX;
}
bool isConstU64() const {
return IsConstant && ConstValue[1] == 0 && ConstValue[2] == 0 &&
ConstValue[3] == 0;
}
bool isDeferredValue() const {
return DeferredValueKind != DeferredKind::NONE;
}
bool isDeferredBitwiseNot() const {
return DeferredValueKind == DeferredKind::BITWISE_NOT;
}
bool isDeferredZeroTest() const {
return DeferredValueKind == DeferredKind::ZERO_TEST_EQ ||
DeferredValueKind == DeferredKind::ZERO_TEST_NE;
}
bool isDeferredZeroTestNegated() const {
ZEN_ASSERT(isDeferredZeroTest() && "Not a deferred zero-test value");
return DeferredValueKind == DeferredKind::ZERO_TEST_NE;
}

const U256Inst &getU256Components() const {
ZEN_ASSERT(IsU256MultiComponent && "Not a multi-component U256");
Expand All @@ -166,6 +218,11 @@ class EVMMirBuilder final {
ZEN_ASSERT(IsConstant && "Not a constant value");
return ConstValue;
}
const U256Inst &getDeferredBaseComponents() const {
ZEN_ASSERT(DeferredValueKind != DeferredKind::NONE &&
"Not a deferred value");
return U256Components;
}

constexpr bool isReg() { return false; }
constexpr bool isTempReg() { return true; }
Expand All @@ -182,6 +239,7 @@ class EVMMirBuilder final {
U256Value ConstValue = {};
bool IsConstant = false;
bool IsU256MultiComponent = false;
DeferredKind DeferredValueKind = DeferredKind::NONE;
};

bool compile(CompilerContext *Context);
Expand Down Expand Up @@ -257,6 +315,21 @@ class EVMMirBuilder final {
return Operand(intxToU256Value(Res));
}

if constexpr (Operator == BinaryOperator::BO_ADD) {
if (LHSOp.isZeroConstant()) {
return RHSOp;
}
if (RHSOp.isZeroConstant()) {
return LHSOp;
}
}

if constexpr (Operator == BinaryOperator::BO_SUB) {
if (RHSOp.isZeroConstant()) {
return LHSOp;
}
}

// Phase 2: u64 fast path for ADD - share zero const for upper RHS limbs
if constexpr (Operator == BinaryOperator::BO_ADD) {
bool LHSIsU64 = LHSOp.isConstU64();
Expand Down Expand Up @@ -357,6 +430,14 @@ class EVMMirBuilder final {
uint64_t R = (V[0] == 0 && V[1] == 0 && V[2] == 0 && V[3] == 0) ? 1 : 0;
return Operand(U256Value{R, 0, 0, 0});
}

if (LHSOp.isDeferredZeroTest()) {
return Operand::createDeferredZeroTest(
LHSOp.getDeferredBaseComponents(),
!LHSOp.isDeferredZeroTestNegated());
}

return Operand::createDeferredZeroTest(extractU256Operand(LHSOp), false);
} else {
if (LHSOp.isConstant() && RHSOp.isConstant()) {
intx::uint256 L = u256ValueToIntx(LHSOp.getConstValue());
Expand Down Expand Up @@ -440,6 +521,28 @@ class EVMMirBuilder final {
return Operand(Res);
}

if constexpr (Operator == BinaryOperator::BO_AND) {
if (LHSOp.isZeroConstant() || RHSOp.isZeroConstant()) {
return Operand(U256Value{0, 0, 0, 0});
}
if (LHSOp.isAllOnesConstant()) {
return RHSOp;
}
if (RHSOp.isAllOnesConstant()) {
return LHSOp;
}
}

if constexpr (Operator == BinaryOperator::BO_OR ||
Operator == BinaryOperator::BO_XOR) {
if (LHSOp.isZeroConstant()) {
return RHSOp;
}
if (RHSOp.isZeroConstant()) {
return LHSOp;
}
}

// Phase 1: u64 fast path for AND - upper limbs are annihilated to 0
if constexpr (Operator == BinaryOperator::BO_AND) {
if (LHSOp.isConstU64() || RHSOp.isConstU64()) {
Expand Down Expand Up @@ -772,7 +875,8 @@ class EVMMirBuilder final {
}
}

U256Inst handleCompareEQZ(const U256Inst &LHS, MType *ResultType);
U256Inst handleCompareEQZ(const U256Inst &LHS, MType *ResultType,
bool IsNegated = false);

U256Inst handleCompareEQ(const U256Inst &LHS, const U256Inst &RHS,
MType *ResultType);
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/mir/basic_block.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class MBasicBlock : public ContextObject {

auto begin() { return Statements.begin(); }
auto end() { return Statements.end(); }
auto begin() const { return Statements.begin(); }
auto end() const { return Statements.end(); }
bool empty() const { return Statements.empty(); }

void addStatement(MInstruction *Inst) {
Expand Down
Loading
Loading