-
Notifications
You must be signed in to change notification settings - Fork 25
fix(evm): preserve gas cost in cyclic chunks #305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -365,6 +365,75 @@ static bool bitsetTest(const std::vector<uint64_t> &Bits, size_t Index) { | |||||
| return (Bits[Index / 64] & (uint64_t{1} << (Index % 64))) != 0; | ||||||
| } | ||||||
|
|
||||||
| static std::vector<uint8_t> | ||||||
| computeInCycle(const std::vector<GasBlock> &Blocks) { | ||||||
| const size_t NumBlocks = Blocks.size(); | ||||||
| std::vector<int32_t> Index(NumBlocks, -1); | ||||||
| std::vector<int32_t> Lowlink(NumBlocks, 0); | ||||||
| std::vector<uint8_t> OnStack(NumBlocks, 0); | ||||||
| std::vector<uint32_t> Stack; | ||||||
| std::vector<uint8_t> InCycle(NumBlocks, 0); | ||||||
| int32_t CurIndex = 0; | ||||||
|
|
||||||
| std::function<void(uint32_t)> StrongConnect = [&](uint32_t Node) { | ||||||
| Index[Node] = CurIndex; | ||||||
| Lowlink[Node] = CurIndex; | ||||||
| ++CurIndex; | ||||||
| Stack.push_back(Node); | ||||||
| OnStack[Node] = 1; | ||||||
|
|
||||||
| for (uint32_t Succ : Blocks[Node].Succs) { | ||||||
| if (Succ >= NumBlocks) { | ||||||
| continue; | ||||||
| } | ||||||
| if (Index[Succ] < 0) { | ||||||
| StrongConnect(Succ); | ||||||
| Lowlink[Node] = std::min(Lowlink[Node], Lowlink[Succ]); | ||||||
| } else if (OnStack[Succ] != 0) { | ||||||
| Lowlink[Node] = std::min(Lowlink[Node], Index[Succ]); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (Lowlink[Node] != Index[Node]) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| std::vector<uint32_t> Scc; | ||||||
| while (!Stack.empty()) { | ||||||
| const uint32_t Top = Stack.back(); | ||||||
| Stack.pop_back(); | ||||||
| OnStack[Top] = 0; | ||||||
| Scc.push_back(Top); | ||||||
| if (Top == Node) { | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (Scc.size() > 1) { | ||||||
| for (uint32_t Id : Scc) { | ||||||
| InCycle[Id] = 1; | ||||||
| } | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| const uint32_t Only = Scc.front(); | ||||||
| for (uint32_t Succ : Blocks[Only].Succs) { | ||||||
| if (Succ == Only) { | ||||||
| InCycle[Only] = 1; | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| for (uint32_t Node = 0; Node < NumBlocks; ++Node) { | ||||||
| if (Index[Node] < 0) { | ||||||
| StrongConnect(Node); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return InCycle; | ||||||
| } | ||||||
|
|
||||||
| static bool bitsetEqual(const std::vector<uint64_t> &A, | ||||||
| const std::vector<uint64_t> &B) { | ||||||
| return A == B; | ||||||
|
|
@@ -871,6 +940,7 @@ static bool buildGasChunksSPP(const zen::common::Byte *Code, size_t CodeSize, | |||||
| for (size_t Index = 0; Index < RevTopo.size(); ++Index) { | ||||||
| RevTopoIndex[RevTopo[Index]] = Index; | ||||||
| } | ||||||
| const std::vector<uint8_t> InCycle = computeInCycle(Blocks); | ||||||
|
|
||||||
| std::vector<LoopInfo> Loops; | ||||||
| std::vector<int32_t> LoopOf; | ||||||
|
|
@@ -885,9 +955,19 @@ static bool buildGasChunksSPP(const zen::common::Byte *Code, size_t CodeSize, | |||||
| Metering[Id] = Blocks[Id].Cost; | ||||||
| } | ||||||
|
|
||||||
| std::vector<uint64_t> NonCycleMask(bitsetWordCount(Blocks.size()), 0); | ||||||
| for (size_t Id = 0; Id < Blocks.size(); ++Id) { | ||||||
| if (Id < InCycle.size() && InCycle[Id] == 0) { | ||||||
|
||||||
| bitsetSet(NonCycleMask, Id); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (!UseLinearSPP) { | ||||||
| for (uint32_t NodeId : RevTopo) { | ||||||
| lemma614Update(NodeId, Blocks, &BackEdges, nullptr, Metering); | ||||||
| if (NodeId < InCycle.size() && InCycle[NodeId] != 0) { | ||||||
|
||||||
| if (NodeId < InCycle.size() && InCycle[NodeId] != 0) { | |
| if (InCycle[NodeId] != 0) { |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code now uses two different mechanisms to identify loops/cycles: (1) dominator-based back edges via BackEdges, and (2) SCC-based cycle detection via InCycle. These serve different purposes but may produce conflicting results.
Specifically, BackEdges identifies loop back edges (edges where target dominates source), while InCycle identifies all nodes in any strongly connected component. A node can be in a cycle without having any back edges (e.g., in an irreducible loop), and a node with back edges might not be in the SCC identified as a cycle.
This dual approach could lead to inconsistent treatment of loop nodes. Consider documenting why both mechanisms are needed and how they interact, or simplifying to use a single, consistent approach to loop identification.
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AllowedMask parameter has been changed from &Loops[LoopId].NodeMask to &NonCycleMask, which fundamentally alters the algorithm's behavior.
The original code restricted updates to successors within the specific loop being processed (via Loops[LoopId].NodeMask). The new code only restricts to non-cyclic successors globally (via NonCycleMask), which means it will incorrectly consider successors outside the current loop during loop-specific processing.
For example, if a non-cyclic node N in loop L1 has a successor S that is non-cyclic and in loop L2 (or not in any loop), the update will now incorrectly include S in the minimum cost calculation. This breaks the loop-local invariant that the SPP algorithm depends on.
The correct approach would be to compute an intersection of both masks, or to restructure the logic to maintain the loop-specific constraint while also excluding cyclic nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive lambda
StrongConnectcan cause stack overflow for deeply nested or large graphs due to unbounded recursion depth. For EVM bytecode with complex control flow graphs containing thousands of blocks, this could be problematic.Consider converting this to an iterative implementation using an explicit stack to avoid potential stack overflow issues, or document the assumption about maximum CFG depth if that's acceptable for this use case.