From 512a7e5989c9c21e254ac71db2379a6bee002937 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Tue, 24 Mar 2026 21:28:31 +0800 Subject: [PATCH 1/9] perf(evm): add frontend peephole identities --- .../evm_frontend/evm_mir_compiler.cpp | 54 ++++++--- src/compiler/evm_frontend/evm_mir_compiler.h | 105 +++++++++++++++++- 2 files changed, 144 insertions(+), 15 deletions(-) diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index ee077b5c0..fa748f4c3 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -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(); @@ -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); @@ -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( false, Predicate, ResultType, OrResult, Zero); @@ -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(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 ==================== @@ -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(false, MirI64Type, Base[I]); + Result[I] = protectUnsafeValue(LocalResult, MirI64Type); + } + return Result; + } + + if (Opnd.isDeferredZeroTest()) { + return handleCompareEQZ(Opnd.getDeferredBaseComponents(), &Ctx.I64Type, + Opnd.isDeferredZeroTestNegated()); + } + if (Opnd.isU256MultiComponent()) { U256Inst Instrs = Opnd.getU256Components(); if (Instrs[0] != nullptr) { @@ -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) { diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index c6b6406bb..5ce767555 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -119,6 +119,8 @@ class EVMMirBuilder final { class Operand { public: + enum class DeferredKind : uint8_t { NONE, BITWISE_NOT, ZERO_TEST }; + Operand() = default; Operand(MInstruction *Instr, EVMType Type) : Instr(Instr), Type(Type) {} Operand(Variable *Var, EVMType Type) : Var(Var), Type(Type) {} @@ -138,21 +140,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.DeferredBaseComponents = BaseComponents; + return Result; + } + + static Operand createDeferredZeroTest(U256Inst BaseComponents, + bool IsNegated) { + Operand Result; + Result.Type = EVMType::UINT256; + Result.DeferredValueKind = DeferredKind::ZERO_TEST; + Result.DeferredBaseComponents = BaseComponents; + Result.DeferredZeroTestNegated = IsNegated; + 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; + } + bool isDeferredZeroTestNegated() const { + ZEN_ASSERT(DeferredValueKind == DeferredKind::ZERO_TEST && + "Not a deferred zero-test value"); + return DeferredZeroTestNegated; + } const U256Inst &getU256Components() const { ZEN_ASSERT(IsU256MultiComponent && "Not a multi-component U256"); @@ -166,6 +213,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 DeferredBaseComponents; + } constexpr bool isReg() { return false; } constexpr bool isTempReg() { return true; } @@ -180,8 +232,11 @@ class EVMMirBuilder final { U256Inst U256Components = {}; U256Var U256VarComponents = {}; U256Value ConstValue = {}; + U256Inst DeferredBaseComponents = {}; bool IsConstant = false; bool IsU256MultiComponent = false; + DeferredKind DeferredValueKind = DeferredKind::NONE; + bool DeferredZeroTestNegated = false; }; bool compile(CompilerContext *Context); @@ -257,6 +312,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(); @@ -357,6 +427,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()); @@ -440,6 +518,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()) { @@ -772,7 +872,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); From fe4f1e9c86d76079ca8ff87e693b2b6360261596 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Wed, 25 Mar 2026 16:46:44 +0800 Subject: [PATCH 2/9] perf(compiler): add EVM peepholes and branch shaping --- docs/evm-next-optimization-prompt.md | 125 +++++++++++++++++++ src/compiler/evm_frontend/evm_mir_compiler.h | 7 +- src/compiler/target/x86/x86_cg_peephole.cpp | 33 ++++- src/compiler/target/x86/x86lowering.cpp | 48 ++++++- 4 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 docs/evm-next-optimization-prompt.md diff --git a/docs/evm-next-optimization-prompt.md b/docs/evm-next-optimization-prompt.md new file mode 100644 index 000000000..07933673e --- /dev/null +++ b/docs/evm-next-optimization-prompt.md @@ -0,0 +1,125 @@ +# Next Optimization Prompt + +## Goal + +Continue optimizing DTVM's EVM multipass JIT after the current peephole and +short-diamond branch-lowering work. + +The current branch already: + +- implements EVM frontend peephole rules in + `src/compiler/evm_frontend/evm_mir_compiler.h` and + `src/compiler/evm_frontend/evm_mir_compiler.cpp` +- adds an x86 compare-chain peephole in + `src/compiler/target/x86/x86_cg_peephole.cpp` +- adds a short-diamond `br_if` fallthrough optimization in + `src/compiler/target/x86/x86lowering.cpp` + +These changes materially improve `jump_around`, `memory_grow_*`, and the full +`external/total/(main|micro)` geomean, but some jump-dense microcases still +have room for improvement. + +## Current Performance Snapshot + +Against the same-session `fix2` baseline: + +- full `external/total/(main|micro)` geomean: about `-11.4%` +- full total sum: about `-3.8%` +- `jump_around/empty`: about `-48%` +- `JUMPDEST_n0/empty`: about `-18.6%` vs `fix2`, but still about `+4.5%` worse + than the previous movzx-aware peephole-only variant +- `loop_with_many_jumpdests/empty`: about `+2.2%` worse than the previous + movzx-aware peephole-only variant + +Latest benchmark JSON: + +- `/tmp/dtvm_shortdiamond_full.json` +- comparison baseline: `/tmp/dtvm_peephole_fix2_sameenv_full.json` +- previous best before short-diamond shaping: + `/tmp/dtvm_peephole_movzx_peephole_full.json` + +## Hypothesis + +The remaining opportunity is no longer in compare materialization removal +itself. The next likely bottleneck is CFG layout for jump-heavy bytecode: + +- block order may still force avoidable taken branches +- `JUMPDEST`-heavy code may want a different fallthrough policy than generic + `br_if` +- there may be more cases where conditional inversion can remove a trailing + `jmp` without hurting other benchmarks + +## Task + +Investigate and optimize the remaining jump-dense regressions, especially: + +- `external/total/micro/JUMPDEST_n0/empty` +- `external/total/micro/loop_with_many_jumpdests/empty` + +Prefer solutions that preserve or improve: + +- `jump_around/empty` +- `memory_grow_mload/by32` +- `memory_grow_mstore/by16` +- full-suite geomean + +## Constraints + +- Work in a new worktree, not on `main` +- Do not revert existing frontend peephole work +- Keep gas semantics unchanged +- Preserve `evmStateTests` correctness +- Use `tools/format.sh format` and `tools/format.sh check` + +## Suggested Investigation Plan + +1. Inspect JIT logs and final assembly for: + - `JUMPDEST_n0` + - `loop_with_many_jumpdests` + - `jump_around` +2. Compare the current short-diamond branch-lowering variant against the + previous movzx-aware peephole-only variant. +3. Focus on: + - block ordering + - fallthrough direction + - removable `jcc + jmp` tails + - whether `optimizeBranchInBlockEnd()` can safely do more +4. Only keep a change if it improves the problematic jump-heavy cases without + giving back the large gains already achieved on `jump_around` and + `memory_grow_*`. + +## Validation + +Build: + +```bash +cmake -B build_perf -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DZEN_ENABLE_MULTIPASS_JIT=ON -DZEN_ENABLE_EVM=ON \ + -DZEN_ENABLE_SPEC_TEST=ON -G Ninja +cmake --build build_perf -j +``` + +Hotspot benchmark: + +```bash +/home/abmcar/evmone-for-test-mulx-adx/build/bin/evmone-bench \ + "/home/abmcar/DTVM-evm-peephole-rules/build_perf/lib/libdtvmapi.so,mode=multipass,enable_gas_metering=true" \ + /home/abmcar/evmone-for-test-mulx-adx/test/evm-benchmarks/benchmarks \ + --benchmark_filter='^external/total/micro/(JUMPDEST_n0/empty|jump_around/empty|loop_with_many_jumpdests/empty|memory_grow_mload/(nogrow|by1|by16|by32)|memory_grow_mstore/(nogrow|by1|by16|by32)|signextend/(zero|one))$' +``` + +Full sweep: + +```bash +/home/abmcar/evmone-for-test-mulx-adx/build/bin/evmone-bench \ + "/home/abmcar/DTVM-evm-peephole-rules/build_perf/lib/libdtvmapi.so,mode=multipass,enable_gas_metering=true" \ + /home/abmcar/evmone-for-test-mulx-adx/test/evm-benchmarks/benchmarks \ + --benchmark_filter='^external/total/(main|micro)/' \ + --benchmark_repetitions=1 +``` + +Correctness: + +```bash +./build/evmStateTests +``` diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index 5ce767555..d27b5888a 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -144,7 +144,7 @@ class EVMMirBuilder final { Operand Result; Result.Type = EVMType::UINT256; Result.DeferredValueKind = DeferredKind::BITWISE_NOT; - Result.DeferredBaseComponents = BaseComponents; + Result.U256Components = BaseComponents; return Result; } @@ -153,7 +153,7 @@ class EVMMirBuilder final { Operand Result; Result.Type = EVMType::UINT256; Result.DeferredValueKind = DeferredKind::ZERO_TEST; - Result.DeferredBaseComponents = BaseComponents; + Result.U256Components = BaseComponents; Result.DeferredZeroTestNegated = IsNegated; return Result; } @@ -216,7 +216,7 @@ class EVMMirBuilder final { const U256Inst &getDeferredBaseComponents() const { ZEN_ASSERT(DeferredValueKind != DeferredKind::NONE && "Not a deferred value"); - return DeferredBaseComponents; + return U256Components; } constexpr bool isReg() { return false; } @@ -232,7 +232,6 @@ class EVMMirBuilder final { U256Inst U256Components = {}; U256Var U256VarComponents = {}; U256Value ConstValue = {}; - U256Inst DeferredBaseComponents = {}; bool IsConstant = false; bool IsU256MultiComponent = false; DeferredKind DeferredValueKind = DeferredKind::NONE; diff --git a/src/compiler/target/x86/x86_cg_peephole.cpp b/src/compiler/target/x86/x86_cg_peephole.cpp index 3a91408cf..6685a2f56 100644 --- a/src/compiler/target/x86/x86_cg_peephole.cpp +++ b/src/compiler/target/x86/x86_cg_peephole.cpp @@ -29,7 +29,7 @@ void X86CgPeephole::peepholeOptimize(CgBasicBlock &MBB, void X86CgPeephole::optimizeCmp(CgBasicBlock &MBB, CgBasicBlock::iterator &MII) { auto MIE = MBB.end(); - // cmp/test -> setcc cond -> test -> jne + // cmp/test -> setcc cond -> [movzx] -> test -> jne // optimized to: cmp/test -> jcc cond auto LocalMII = MII; LocalMII++; @@ -42,12 +42,28 @@ void X86CgPeephole::optimizeCmp(CgBasicBlock &MBB, if (!Op1.isReg()) return; auto CC = Inst1.getOperand(1).getImm(); + unsigned TestReg = Op1.getReg(); + CgInstruction *MovzxInst = nullptr; LocalMII++; if (LocalMII == MIE) return; auto &Inst2 = *LocalMII; - switch (Inst2.getOpcode()) { + if (Inst2.getOpcode() == X86::MOVZX32rr8) { + const auto &MovzxDst = Inst2.getOperand(0); + const auto &MovzxSrc = Inst2.getOperand(1); + if (!MovzxDst.isReg() || !MovzxSrc.isReg() || + MovzxSrc.getReg() != Op1.getReg()) + return; + TestReg = MovzxDst.getReg(); + MovzxInst = &Inst2; + LocalMII++; + if (LocalMII == MIE) + return; + } + + auto &TestInst = *LocalMII; + switch (TestInst.getOpcode()) { case X86::TEST8rr: case X86::TEST16rr: case X86::TEST32rr: @@ -56,8 +72,10 @@ void X86CgPeephole::optimizeCmp(CgBasicBlock &MBB, default: return; } - const auto &Op2 = Inst2.getOperand(0); - if (!Op2.isReg() || Op2.getReg() != Op1.getReg()) + const auto &TestOp0 = TestInst.getOperand(0); + const auto &TestOp1 = TestInst.getOperand(1); + if (!TestOp0.isReg() || !TestOp1.isReg() || TestOp0.getReg() != TestReg || + TestOp1.getReg() != TestReg) return; LocalMII++; @@ -70,7 +88,10 @@ void X86CgPeephole::optimizeCmp(CgBasicBlock &MBB, return; // TODO, other optimization, use opposite condition code Inst1.eraseFromParent(); - Inst2.eraseFromParent(); + if (MovzxInst != nullptr) { + MovzxInst->eraseFromParent(); + } + TestInst.eraseFromParent(); Inst3.getOperand(1).setImm(CC); } } // namespace COMPILER @@ -85,4 +106,4 @@ void X86CgPeephole::optimizeBranchInBlockEnd(CgBasicBlock &MBB, // remove the unconditional branch MI.eraseFromParent(); } -} \ No newline at end of file +} diff --git a/src/compiler/target/x86/x86lowering.cpp b/src/compiler/target/x86/x86lowering.cpp index e5f7ac834..b1102fd77 100644 --- a/src/compiler/target/x86/x86lowering.cpp +++ b/src/compiler/target/x86/x86lowering.cpp @@ -1520,7 +1520,35 @@ void X86CgLowering::lowerBrStmt(const BrInstruction &Inst) { fastEmitBranch(TargetMBB); } -// TODO: optimize if the condition is comparison +static bool isShortDiamondTrueFallthrough(const BrIfInstruction &Inst) { + if (!Inst.hasFalseBlock()) { + return false; + } + + const auto *CurrentBB = Inst.getBasicBlock(); + const auto *TrueBB = Inst.getTrueBlock(); + const auto *FalseBB = Inst.getFalseBlock(); + if (CurrentBB->getIdx() + 1 != TrueBB->getIdx() || + TrueBB->getIdx() + 1 != FalseBB->getIdx()) { + return false; + } + + auto PredRange = TrueBB->predecessors(); + auto PredIt = PredRange.begin(); + if (PredIt == PredRange.end() || *PredIt != CurrentBB) { + return false; + } + ++PredIt; + if (PredIt != PredRange.end() || TrueBB->empty()) { + return false; + } + + auto *MutableTrueBB = const_cast(TrueBB); + const auto *Terminator = *std::prev(MutableTrueBB->end()); + const auto *BrInst = dyn_cast(Terminator); + return BrInst != nullptr && BrInst->getTargetBlock() == FalseBB; +} + void X86CgLowering::lowerBrIfStmt(const BrIfInstruction &Inst) { const MInstruction *Operand = Inst.getOperand<0>(); CgRegister OperandReg = lowerExpr(*Operand); @@ -1538,15 +1566,25 @@ void X86CgLowering::lowerBrIfStmt(const BrIfInstruction &Inst) { } fastEmitNoDefInst_rr(TESTOpc, OperandReg, OperandReg); - // Jump to the true basic block if the operand is not zero CgBasicBlock *TrueMBB = getOrCreateCgBB(Inst.getTrueBlock()); - fastEmitCondBranch(TrueMBB, X86::CondCode::COND_NE); - if (Inst.hasFalseBlock()) { - // Jump to the false basic block if the operand is zero CgBasicBlock *FalseMBB = getOrCreateCgBB(Inst.getFalseBlock()); + if (isShortDiamondTrueFallthrough(Inst)) { + // Prefer falling through to the next laid-out block and branch away on + // the zero case when the MIR already forms a short diamond that rejoins + // immediately after the true block. + fastEmitCondBranch(FalseMBB, X86::CondCode::COND_E); + CurBB->addSuccessorWithoutProb(TrueMBB); + return; + } + + // Jump to the true basic block if the operand is not zero. + fastEmitCondBranch(TrueMBB, X86::CondCode::COND_NE); + // Jump to the false basic block if the operand is zero. fastEmitBranch(FalseMBB); } else { + // Jump to the true basic block if the operand is not zero. + fastEmitCondBranch(TrueMBB, X86::CondCode::COND_NE); startNewBlockAfterBranch(); } } From 43374e9220600f31d8cc40f5cec6a5be2be69b4b Mon Sep 17 00:00:00 2001 From: Abmcar Date: Fri, 27 Mar 2026 17:22:51 +0800 Subject: [PATCH 3/9] chore(docs): remove next optimization prompt --- docs/evm-next-optimization-prompt.md | 125 --------------------------- 1 file changed, 125 deletions(-) delete mode 100644 docs/evm-next-optimization-prompt.md diff --git a/docs/evm-next-optimization-prompt.md b/docs/evm-next-optimization-prompt.md deleted file mode 100644 index 07933673e..000000000 --- a/docs/evm-next-optimization-prompt.md +++ /dev/null @@ -1,125 +0,0 @@ -# Next Optimization Prompt - -## Goal - -Continue optimizing DTVM's EVM multipass JIT after the current peephole and -short-diamond branch-lowering work. - -The current branch already: - -- implements EVM frontend peephole rules in - `src/compiler/evm_frontend/evm_mir_compiler.h` and - `src/compiler/evm_frontend/evm_mir_compiler.cpp` -- adds an x86 compare-chain peephole in - `src/compiler/target/x86/x86_cg_peephole.cpp` -- adds a short-diamond `br_if` fallthrough optimization in - `src/compiler/target/x86/x86lowering.cpp` - -These changes materially improve `jump_around`, `memory_grow_*`, and the full -`external/total/(main|micro)` geomean, but some jump-dense microcases still -have room for improvement. - -## Current Performance Snapshot - -Against the same-session `fix2` baseline: - -- full `external/total/(main|micro)` geomean: about `-11.4%` -- full total sum: about `-3.8%` -- `jump_around/empty`: about `-48%` -- `JUMPDEST_n0/empty`: about `-18.6%` vs `fix2`, but still about `+4.5%` worse - than the previous movzx-aware peephole-only variant -- `loop_with_many_jumpdests/empty`: about `+2.2%` worse than the previous - movzx-aware peephole-only variant - -Latest benchmark JSON: - -- `/tmp/dtvm_shortdiamond_full.json` -- comparison baseline: `/tmp/dtvm_peephole_fix2_sameenv_full.json` -- previous best before short-diamond shaping: - `/tmp/dtvm_peephole_movzx_peephole_full.json` - -## Hypothesis - -The remaining opportunity is no longer in compare materialization removal -itself. The next likely bottleneck is CFG layout for jump-heavy bytecode: - -- block order may still force avoidable taken branches -- `JUMPDEST`-heavy code may want a different fallthrough policy than generic - `br_if` -- there may be more cases where conditional inversion can remove a trailing - `jmp` without hurting other benchmarks - -## Task - -Investigate and optimize the remaining jump-dense regressions, especially: - -- `external/total/micro/JUMPDEST_n0/empty` -- `external/total/micro/loop_with_many_jumpdests/empty` - -Prefer solutions that preserve or improve: - -- `jump_around/empty` -- `memory_grow_mload/by32` -- `memory_grow_mstore/by16` -- full-suite geomean - -## Constraints - -- Work in a new worktree, not on `main` -- Do not revert existing frontend peephole work -- Keep gas semantics unchanged -- Preserve `evmStateTests` correctness -- Use `tools/format.sh format` and `tools/format.sh check` - -## Suggested Investigation Plan - -1. Inspect JIT logs and final assembly for: - - `JUMPDEST_n0` - - `loop_with_many_jumpdests` - - `jump_around` -2. Compare the current short-diamond branch-lowering variant against the - previous movzx-aware peephole-only variant. -3. Focus on: - - block ordering - - fallthrough direction - - removable `jcc + jmp` tails - - whether `optimizeBranchInBlockEnd()` can safely do more -4. Only keep a change if it improves the problematic jump-heavy cases without - giving back the large gains already achieved on `jump_around` and - `memory_grow_*`. - -## Validation - -Build: - -```bash -cmake -B build_perf -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DZEN_ENABLE_MULTIPASS_JIT=ON -DZEN_ENABLE_EVM=ON \ - -DZEN_ENABLE_SPEC_TEST=ON -G Ninja -cmake --build build_perf -j -``` - -Hotspot benchmark: - -```bash -/home/abmcar/evmone-for-test-mulx-adx/build/bin/evmone-bench \ - "/home/abmcar/DTVM-evm-peephole-rules/build_perf/lib/libdtvmapi.so,mode=multipass,enable_gas_metering=true" \ - /home/abmcar/evmone-for-test-mulx-adx/test/evm-benchmarks/benchmarks \ - --benchmark_filter='^external/total/micro/(JUMPDEST_n0/empty|jump_around/empty|loop_with_many_jumpdests/empty|memory_grow_mload/(nogrow|by1|by16|by32)|memory_grow_mstore/(nogrow|by1|by16|by32)|signextend/(zero|one))$' -``` - -Full sweep: - -```bash -/home/abmcar/evmone-for-test-mulx-adx/build/bin/evmone-bench \ - "/home/abmcar/DTVM-evm-peephole-rules/build_perf/lib/libdtvmapi.so,mode=multipass,enable_gas_metering=true" \ - /home/abmcar/evmone-for-test-mulx-adx/test/evm-benchmarks/benchmarks \ - --benchmark_filter='^external/total/(main|micro)/' \ - --benchmark_repetitions=1 -``` - -Correctness: - -```bash -./build/evmStateTests -``` From cfc9d844dea4fd829fd8a01a4d48d015919ad555 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Fri, 27 Mar 2026 17:49:35 +0800 Subject: [PATCH 4/9] chore(ci): rerun workflows From f4a9eb2dadd122c94ffd9925f9239f3f0bdf7362 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Sun, 29 Mar 2026 12:46:53 +0800 Subject: [PATCH 5/9] chore(ci): rerun workflows From 14cc393b9960d0345e4bb0dad3a4bb3aa6b8cbd9 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Thu, 2 Apr 2026 14:06:45 +0800 Subject: [PATCH 6/9] refactor(compiler): add const iterators to MBasicBlock and remove const_cast Add begin()/end() const overloads to MBasicBlock so read-only queries can iterate statements without casting away const. Remove the const_cast in isShortDiamondTrueFallthrough() that was only needed because const iterators were missing. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/compiler/mir/basic_block.h | 2 ++ src/compiler/target/x86/x86lowering.cpp | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/mir/basic_block.h b/src/compiler/mir/basic_block.h index 68ae0e295..dae86342c 100644 --- a/src/compiler/mir/basic_block.h +++ b/src/compiler/mir/basic_block.h @@ -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) { diff --git a/src/compiler/target/x86/x86lowering.cpp b/src/compiler/target/x86/x86lowering.cpp index b1102fd77..a40109730 100644 --- a/src/compiler/target/x86/x86lowering.cpp +++ b/src/compiler/target/x86/x86lowering.cpp @@ -1543,8 +1543,7 @@ static bool isShortDiamondTrueFallthrough(const BrIfInstruction &Inst) { return false; } - auto *MutableTrueBB = const_cast(TrueBB); - const auto *Terminator = *std::prev(MutableTrueBB->end()); + const auto *Terminator = *std::prev(TrueBB->end()); const auto *BrInst = dyn_cast(Terminator); return BrInst != nullptr && BrInst->getTargetBlock() == FalseBB; } From 003c61388f78803efb3339e026f05d4166b116ef Mon Sep 17 00:00:00 2001 From: Abmcar Date: Thu, 2 Apr 2026 15:08:13 +0800 Subject: [PATCH 7/9] fix(compiler): add liveness check before erasing SETCC/MOVZX in peephole The compare-chain peephole (cmp->setcc->[movzx]->test->jne => cmp->jcc) was erasing SETCC/MOVZX instructions without checking if their virtual registers had other uses. The lowering cache (_expr_reg_map) can share these registers across consumers, so erasing them could leave dangling references. Add hasOneNonDBGUse guards to bail out when the registers are still live. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/compiler/target/x86/x86_cg_peephole.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/compiler/target/x86/x86_cg_peephole.cpp b/src/compiler/target/x86/x86_cg_peephole.cpp index 6685a2f56..bf7cb500c 100644 --- a/src/compiler/target/x86/x86_cg_peephole.cpp +++ b/src/compiler/target/x86/x86_cg_peephole.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "compiler/target/x86/x86_cg_peephole.h" +#include "compiler/cgir/pass/cg_register_info.h" #include "compiler/llvm-prebuild/Target/X86/X86Subtarget.h" #include "compiler/target/x86/x86_constants.h" @@ -87,6 +88,15 @@ void X86CgPeephole::optimizeCmp(CgBasicBlock &MBB, if (Inst3.getOperand(1).getImm() != X86::CondCode::COND_NE) return; // TODO, other optimization, use opposite condition code + // Ensure the SETCC/MOVZX registers have no uses beyond this chain. + // The lowering cache (_expr_reg_map) may share these virtual registers + // with other consumers; erasing them would leave dangling references. + const auto &RegInfo = MBB.getParent()->getRegInfo(); + if (!RegInfo.hasOneNonDBGUse(Op1.getReg())) + return; + if (MovzxInst != nullptr && !RegInfo.hasOneNonDBGUse(TestReg)) + return; + Inst1.eraseFromParent(); if (MovzxInst != nullptr) { MovzxInst->eraseFromParent(); From b386f9c32e9b8f475d758c8c4bfebfee926ddb88 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Thu, 2 Apr 2026 15:24:42 +0800 Subject: [PATCH 8/9] refactor(compiler): fold DeferredZeroTestNegated bool into DeferredKind enum Replace the separate bool field with ZERO_TEST_EQ/ZERO_TEST_NE enum variants, eliminating a redundant state that could silently be inert when DeferredValueKind was BITWISE_NOT. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/compiler/evm_frontend/evm_mir_compiler.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index d27b5888a..34b88c1e9 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -119,7 +119,12 @@ class EVMMirBuilder final { class Operand { public: - enum class DeferredKind : uint8_t { NONE, BITWISE_NOT, ZERO_TEST }; + 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) {} @@ -152,9 +157,9 @@ class EVMMirBuilder final { bool IsNegated) { Operand Result; Result.Type = EVMType::UINT256; - Result.DeferredValueKind = DeferredKind::ZERO_TEST; + Result.DeferredValueKind = + IsNegated ? DeferredKind::ZERO_TEST_NE : DeferredKind::ZERO_TEST_EQ; Result.U256Components = BaseComponents; - Result.DeferredZeroTestNegated = IsNegated; return Result; } @@ -193,12 +198,12 @@ class EVMMirBuilder final { return DeferredValueKind == DeferredKind::BITWISE_NOT; } bool isDeferredZeroTest() const { - return DeferredValueKind == DeferredKind::ZERO_TEST; + return DeferredValueKind == DeferredKind::ZERO_TEST_EQ || + DeferredValueKind == DeferredKind::ZERO_TEST_NE; } bool isDeferredZeroTestNegated() const { - ZEN_ASSERT(DeferredValueKind == DeferredKind::ZERO_TEST && - "Not a deferred zero-test value"); - return DeferredZeroTestNegated; + ZEN_ASSERT(isDeferredZeroTest() && "Not a deferred zero-test value"); + return DeferredValueKind == DeferredKind::ZERO_TEST_NE; } const U256Inst &getU256Components() const { @@ -235,7 +240,6 @@ class EVMMirBuilder final { bool IsConstant = false; bool IsU256MultiComponent = false; DeferredKind DeferredValueKind = DeferredKind::NONE; - bool DeferredZeroTestNegated = false; }; bool compile(CompilerContext *Context); From e39b95fa969e047e6cd6d176ad48532b87adf588 Mon Sep 17 00:00:00 2001 From: Abmcar Date: Wed, 8 Apr 2026 19:46:48 +0800 Subject: [PATCH 9/9] docs(compiler): add change document for EVM peepholes and branch shaping Co-Authored-By: Claude Opus 4.6 (1M context) --- .../README.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/changes/2026-03-27-evm-peepholes-branch-shaping/README.md diff --git a/docs/changes/2026-03-27-evm-peepholes-branch-shaping/README.md b/docs/changes/2026-03-27-evm-peepholes-branch-shaping/README.md new file mode 100644 index 000000000..666abb1ea --- /dev/null +++ b/docs/changes/2026-03-27-evm-peepholes-branch-shaping/README.md @@ -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