diff --git a/src/action/evm_bytecode_visitor.h b/src/action/evm_bytecode_visitor.h index c6d96573..0757fd46 100644 --- a/src/action/evm_bytecode_visitor.h +++ b/src/action/evm_bytecode_visitor.h @@ -575,17 +575,17 @@ template class EVMByteCodeVisitor { } case OP_JUMPDEST: { - // Skip to the last consecutive JUMPDEST if there are multiple - uint64_t PrevPC = PC; + // Consecutive JUMPDEST opcodes share one body BB in multipass. + // Charge all skipped metering points before jumping to the shared + // destination at the end of the run. + uint64_t RunStartPC = PC; while (Ip < IpEnd && static_cast(*Ip) == OP_JUMPDEST) { Ip++; PC++; } - if (PC > PrevPC) { - // Meter gas for the skipped JUMPDESTs - Builder.meterGas(PC - PrevPC); + if (PC > RunStartPC) { + Builder.meterOpcodeRange(RunStartPC, PC); } - // Now process the last JUMPDEST (current position after skipping) handleEndBlock(); Builder.handleJumpDest(PC); Builder.meterOpcode(Opcode, PC); diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index 205b75d1..6971118d 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -9,7 +9,6 @@ #include "runtime/evm_instance.h" #include "utils/hash_utils.h" #include -#include #ifdef ZEN_ENABLE_EVM_GAS_REGISTER #include "compiler/llvm-prebuild/Target/X86/X86Subtarget.h" @@ -21,6 +20,7 @@ namespace COMPILER { constexpr uint64_t HashMultiplier = 0x9E3779B97F4A7C15ULL; constexpr uint64_t MinHashSize = 5; constexpr uint64_t MaxHashSize = 1024; +constexpr uint32_t InvalidJumpDestRunPC = 0xFFFFFFFFu; zen::common::EVMU256Type *EVMFrontendContext::getEVMU256Type() { static zen::common::EVMU256Type U256Type; @@ -257,7 +257,6 @@ void EVMMirBuilder::initEVM(CompilerContext *Context) { evmc_get_instruction_names_table(zen::evm::DEFAULT_REVISION); } - createJumpTable(); ReturnBB = createBasicBlock(); loadEVMInstanceAttr(); @@ -269,6 +268,8 @@ void EVMMirBuilder::initEVM(CompilerContext *Context) { initGasRegister(); #endif + createJumpTable(); + #ifdef ZEN_ENABLE_LINUX_PERF CurBB->setSourceOffset(1); CurBB->setSourceName("MAIN_ENTRY"); @@ -404,6 +405,56 @@ void EVMMirBuilder::meterOpcode(evmc_opcode Opcode, uint64_t PC) { meterGas(static_cast(Metrics.gas_cost)); } +void EVMMirBuilder::meterOpcodeRange(uint64_t StartPC, + uint64_t EndPCExclusive) { + if (!Ctx.isGasMeteringEnabled() || StartPC >= EndPCExclusive) { + return; + } + + // Fast path for merged consecutive JUMPDEST runs: the skipped cost for + // [StartPC, EndPCExclusive) is precomputed once when building the jump table. + if (StartPC < JumpDestRunLastPC.size()) { + const uint32_t RunLastPC = JumpDestRunLastPC[static_cast(StartPC)]; + if (RunLastPC != InvalidJumpDestRunPC && + static_cast(RunLastPC) == EndPCExclusive) { + meterGas(JumpDestRunSkipCost[static_cast(StartPC)]); + return; + } + } + + const auto *EvmCtx = static_cast(&Ctx); + const Byte *Bytecode = EvmCtx->getBytecode(); + if (!Bytecode) { + return; + } + + const uint64_t CodeSize = static_cast(EvmCtx->getBytecodeSize()); + if (StartPC >= CodeSize) { + return; + } + EndPCExclusive = std::min(EndPCExclusive, CodeSize); + + uint64_t TotalCost = 0; + for (uint64_t PC = StartPC; PC < EndPCExclusive; ++PC) { + uint64_t Cost = 0; + if (GasChunkEnd && GasChunkCost && PC < GasChunkSize && + GasChunkEnd[PC] > PC) { + Cost = GasChunkCost[PC]; + } else { + const uint8_t Opcode = static_cast(Bytecode[PC]); + Cost = static_cast(InstructionMetrics[Opcode].gas_cost); + } + + if (UINT64_MAX - TotalCost < Cost) { + TotalCost = UINT64_MAX; + break; + } + TotalCost += Cost; + } + + meterGas(TotalCost); +} + bool EVMMirBuilder::isOpcodeDefined(evmc_opcode Opcode) const { const uint8_t Index = static_cast(Opcode); if (InstructionNames && InstructionNames[Index] != nullptr) { @@ -880,30 +931,97 @@ void EVMMirBuilder::createJumpTable() { const Byte *Bytecode = EvmCtx->getBytecode(); size_t BytecodeSize = EvmCtx->getBytecodeSize(); - uint64_t ConsecutiveStart = 0; - bool InConsecutive = false; + JumpDestTable.clear(); + JumpDestBodyTable.clear(); + JumpHashTable.clear(); + JumpHashReverse.clear(); + HashMask = 0; + if (Ctx.isGasMeteringEnabled()) { + JumpDestRunLastPC.assign(BytecodeSize, InvalidJumpDestRunPC); + JumpDestRunSkipCost.assign(BytecodeSize, 0); + } else { + JumpDestRunLastPC.clear(); + JumpDestRunSkipCost.clear(); + } + + MBasicBlock *SavedInsertBB = CurBB; for (size_t PC = 0; PC < BytecodeSize; ++PC) { if (Bytecode[PC] == static_cast(evmc_opcode::OP_JUMPDEST)) { - if (JumpDestTable.count(PC - 1) > 0) { - // Reuse BB for consecutive JUMPDESTs - JumpDestTable[PC] = JumpDestTable[PC - 1]; - // We are in a consecutive sequence, continue tracking - if (!InConsecutive) { - ConsecutiveStart = PC - 1; - InConsecutive = true; + const size_t RangeStart = PC; + while (PC + 1 < BytecodeSize && + Bytecode[PC + 1] == static_cast(evmc_opcode::OP_JUMPDEST)) { + ++PC; + } + const size_t RangeEnd = PC; + + // Share one canonical execution block for the whole run. + MBasicBlock *BodyBB = createBasicBlock(); + BodyBB->setJumpDestBB(true); + + for (size_t DestPC = RangeStart; DestPC <= RangeEnd; ++DestPC) { + JumpDestBodyTable[DestPC] = BodyBB; + } + + if (!Ctx.isGasMeteringEnabled() || RangeStart == RangeEnd) { + for (size_t DestPC = RangeStart; DestPC <= RangeEnd; ++DestPC) { + JumpDestTable[DestPC] = BodyBB; } - continue; + } else { + // For merged runs, materialize per-target entry thunks that charge the + // exact skipped metering before entering the shared body. + // + // NOTE: We may create O(n) thunks for a run of length n. Avoid an + // O(n^2) compile-time cost by precomputing the suffix sums of skipped + // metering once for the run. + const size_t SkipCount = RangeEnd - RangeStart; // exclude RangeEnd + const uint64_t JumpDestBaseCost = static_cast( + InstructionMetrics[static_cast(evmc_opcode::OP_JUMPDEST)] + .gas_cost); + std::vector SkipCostByOffset(SkipCount, 0); + if (SkipCount > 0) { + uint64_t Running = 0; + for (size_t Offset = SkipCount; Offset > 0; --Offset) { + const size_t Pc = RangeStart + (Offset - 1); + uint64_t Cost = 0; + if (GasChunkEnd && GasChunkCost && Pc < GasChunkSize && + GasChunkEnd[Pc] > Pc) { + Cost = GasChunkCost[Pc]; + } else { + // All bytes in the run are JUMPDEST opcode bytes (PUSH payload is + // skipped in the scan above), so the fallback is a constant. + Cost = JumpDestBaseCost; + } + + if (UINT64_MAX - Running < Cost) { + Running = UINT64_MAX; + } else { + Running += Cost; + } + SkipCostByOffset[Pc - RangeStart] = Running; + } + } + + // Cache the total skipped cost at run start so the linear decode path + // can reuse it without re-scanning the same consecutive JUMPDEST range. + if (RangeStart < JumpDestRunLastPC.size()) { + JumpDestRunLastPC[RangeStart] = static_cast(RangeEnd); + JumpDestRunSkipCost[RangeStart] = SkipCostByOffset[0]; + } + + for (size_t DestPC = RangeStart; DestPC < RangeEnd; ++DestPC) { + MBasicBlock *EntryBB = createBasicBlock(); + EntryBB->setJumpDestBB(true); + JumpDestTable[DestPC] = EntryBB; + + setInsertBlock(EntryBB); + meterGas(SkipCostByOffset[DestPC - RangeStart]); + createInstruction(true, Ctx, BodyBB); + addSuccessor(BodyBB); + } + JumpDestTable[RangeEnd] = BodyBB; } - MBasicBlock *DestBB = createBasicBlock(); - DestBB->setJumpDestBB(true); - JumpDestTable[PC] = DestBB; } else { - // End of consecutive sequence if we were in one - if (InConsecutive) { - ConsecutiveJumpDests.push_back({ConsecutiveStart, PC - 1}); - InConsecutive = false; - } if (static_cast(evmc_opcode::OP_PUSH0) <= Bytecode[PC] && Bytecode[PC] <= static_cast(evmc_opcode::OP_PUSH32)) { uint8_t PushSize = static_cast(Bytecode[PC]) + 1 - @@ -912,10 +1030,8 @@ void EVMMirBuilder::createJumpTable() { } } } - // Handle case where consecutive sequence extends to end of bytecode - if (InConsecutive) { - ConsecutiveJumpDests.push_back({ConsecutiveStart, BytecodeSize - 1}); - } + + setInsertBlock(SavedInsertBB); // If the size of JumpDests is greater than MinHashSize, create a hash table // which calculates the hash of DestPC and use it as the index to jump @@ -923,7 +1039,6 @@ void EVMMirBuilder::createJumpTable() { uint64_t HashSize = std::min(nextPowerOfTwo(JumpDestTable.size()), MaxHashSize); HashMask = HashSize - 1; - std::vector> HashDests(HashSize); for (const auto &[DestPC, DestBB] : JumpDestTable) { // HashIndex(a) = (a * HashMultiplier) & (size - 1) uint64_t Index = (DestPC * HashMultiplier) & HashMask; @@ -936,13 +1051,6 @@ void EVMMirBuilder::createJumpTable() { void EVMMirBuilder::implementConstantJump(uint64_t ConstDest, MBasicBlock *FailureBB) { if (JumpDestTable.count(ConstDest)) { - // Check if ConstDest falls within a consecutive JUMPDEST range - for (const auto &[StartPC, EndPC] : ConsecutiveJumpDests) { - if (ConstDest >= StartPC && ConstDest < EndPC) { - meterGas(EndPC - ConstDest); - break; - } - } createInstruction(true, Ctx, JumpDestTable[ConstDest]); addSuccessor(JumpDestTable[ConstDest]); } else { @@ -960,55 +1068,6 @@ void EVMMirBuilder::implementIndirectJump(MInstruction *JumpTarget, } HasIndirectJump = true; - // Check if JumpTarget falls within any consecutive JUMPDEST range - // and charge gas for skipped JUMPDESTs - if (!ConsecutiveJumpDests.empty() && Ctx.isGasMeteringEnabled()) { - MType *I64Type = &Ctx.I64Type; - MBasicBlock *FinalBB = createBasicBlock(); - - for (const auto &[StartPC, EndPC] : ConsecutiveJumpDests) { - MBasicBlock *NextCheckBB = createBasicBlock(); - MBasicBlock *ChargeGasBB = createBasicBlock(); - - // Check if JumpTarget >= StartPC - MInstruction *StartPCConst = createIntConstInstruction(I64Type, StartPC); - MInstruction *IsGEStart = createInstruction( - false, CmpInstruction::Predicate::ICMP_UGE, I64Type, JumpTarget, - StartPCConst); - - // Check if JumpTarget < EndPC - MInstruction *EndPCConst = createIntConstInstruction(I64Type, EndPC); - MInstruction *IsLTEnd = createInstruction( - false, CmpInstruction::Predicate::ICMP_ULT, I64Type, JumpTarget, - EndPCConst); - - // Combine: JumpTarget >= StartPC && JumpTarget < EndPC - MInstruction *InRange = createInstruction( - false, OP_and, I64Type, IsGEStart, IsLTEnd); - - createInstruction(true, Ctx, InRange, ChargeGasBB, - NextCheckBB); - addSuccessor(ChargeGasBB); - addSuccessor(NextCheckBB); - - // ChargeGasBB: charge gas for (EndPC - JumpTarget) - setInsertBlock(ChargeGasBB); - MInstruction *GasCost = createInstruction( - false, OP_sub, I64Type, EndPCConst, JumpTarget); - chargeDynamicGasIR(GasCost); - createInstruction(true, Ctx, FinalBB); - addSuccessor(FinalBB); - - // Continue to next check - setInsertBlock(NextCheckBB); - } - - // After all checks, branch to FinalBB - createInstruction(true, Ctx, FinalBB); - addSuccessor(FinalBB); - setInsertBlock(FinalBB); - } - MBasicBlock *TargetBB = getOrCreateIndirectJumpBB(); createInstruction(true, &(Ctx.VoidType), JumpTarget, JumpTargetVar->getVarIdx()); @@ -1166,7 +1225,9 @@ void EVMMirBuilder::handleJumpI(Operand Dest, Operand Cond) { } void EVMMirBuilder::handleJumpDest(const uint64_t &PC) { - MBasicBlock *DestBB = JumpDestTable.at(PC); + auto BodyIt = JumpDestBodyTable.find(PC); + ZEN_ASSERT(BodyIt != JumpDestBodyTable.end() && "JUMPDEST body not found"); + MBasicBlock *DestBB = BodyIt->second; // Only add successor if the current BB is not ExceptionSetBB, bool IsExceptionSetBB = false; for (auto &[EC, BB] : CurFunc->getExceptionSetBBs()) { diff --git a/src/compiler/evm_frontend/evm_mir_compiler.h b/src/compiler/evm_frontend/evm_mir_compiler.h index 0503cac5..61d8fdbc 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.h +++ b/src/compiler/evm_frontend/evm_mir_compiler.h @@ -186,6 +186,7 @@ class EVMMirBuilder final { void finalizeEVMBase(); void meterOpcode(evmc_opcode Opcode, uint64_t PC); + void meterOpcodeRange(uint64_t StartPC, uint64_t EndPCExclusive); bool isOpcodeDefined(evmc_opcode Opcode) const; void meterGas(uint64_t GasCost); @@ -627,10 +628,16 @@ class EVMMirBuilder final { // Jump table for dynamic jumps bool HasIndirectJump = false; + // Entry blocks for jump targets (may be tiny thunks for shared JUMPDEST + // bodies). std::map JumpDestTable; - // Consecutive JUMPDEST ranges: {start_pc, end_pc} - std::vector> ConsecutiveJumpDests; - MBasicBlock *DefaultJumpBB = nullptr; // For invalid jump destinations + // Canonical execution blocks for JUMPDEST opcodes in linear decode. + std::map JumpDestBodyTable; + // Cached skipped-metering for merged consecutive JUMPDEST runs. + // Cache it so meterOpcodeRange(S, E) doesn't have to re-scan the same run. + std::vector JumpDestRunLastPC; // [S] = E, else invalid sentinel + std::vector JumpDestRunSkipCost; // [S] = sum cost for [S, E) + MBasicBlock *DefaultJumpBB = nullptr; // For invalid jump destinations std::map> JumpHashTable; std::map> JumpHashReverse;