Wrap gas counter (gas_left) in a strong Gas type#1564
Draft
chfast wants to merge 1 commit into
Draft
Conversation
gas_left is a bare int64_t threaded through the whole interpreter, so it can be silently confused with unrelated integers (stack values, memory sizes, EVMC's own int64 gas fields). Wrap it in evmone::Gas: conversions to/from int64_t are explicit, so gas only crosses into raw int64_t at the EVMC boundary (message.gas, evmc::Result.gas_left, the GAS opcode push, tracing) through deliberate static_cast. The gas-domain operators (-=, +=, binary -, +, /, <=>) accept raw int64_t cost operands, so instruction bodies are unchanged and the change stays mechanical: Result::gas_left, check_requirements, every core impl, grow_memory/check_memory, dispatch[_cgoto] and AdvancedExecutionState::gas_left. Generated code is not identical to the int64_t version (measured on the clang assertions build, x86-64): - baseline_execution.cpp.o gains 127 static instructions. The gas check (gas_left -= cost) < 0 lowers to `subq; jl` (signed-less, reusing the subtraction's flags) instead of `subq; js` (sign bit) -- same instruction count; in cmov contexts the spaceship form even drops a testq (`subq; cmovge` vs `testq; cmovns`). - Hot path is zero-cost. Deterministic instruction counts (google benchmark --benchmark_perf_counters=INSTRUCTIONS) over single-opcode loops running millions of iterations stay within +0.003%: ADD +0.003%, MUL +0.001%, SUB/LT/GT/ISZERO/NOT/SIGNEXTEND/JUMPDEST +0.002..0.007%. - A fixed ~255 extra instructions per execute() call remain (setup and teardown inlining churn from the boundary casts and the Gas dispatch return value): +0.003% on normal workloads, up to +0.9% only on trivially short executions (synth/loop_v1: 27544 vs 27289 instructions). 1115/1115 unit tests pass (baseline, advanced, cgoto).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
gas_left is a bare int64_t threaded through the whole interpreter, so it can be silently confused with unrelated integers (stack values, memory sizes, EVMC's own int64 gas fields). Wrap it in evmone::Gas: conversions to/from int64_t are explicit, so gas only crosses into raw int64_t at the EVMC boundary (message.gas, evmc::Result.gas_left, the GAS opcode push, tracing) through deliberate static_cast. The gas-domain operators (-=, +=, binary -, +, /, <=>) accept raw int64_t cost operands, so instruction bodies are unchanged and the change stays mechanical: Result::gas_left, check_requirements, every core impl, grow_memory/check_memory, dispatch[_cgoto] and
AdvancedExecutionState::gas_left.
Generated code is not identical to the int64_t version (measured on the clang assertions build, x86-64):
baseline_execution.cpp.o gains 127 static instructions. The gas check (gas_left -= cost) < 0 lowers to
subq; jl(signed-less, reusing the subtraction's flags) instead ofsubq; js(sign bit) -- same instruction count; in cmov contexts the spaceship form even drops a testq (subq; cmovgevstestq; cmovns).Hot path is zero-cost. Deterministic instruction counts (google benchmark --benchmark_perf_counters=INSTRUCTIONS) over single-opcode loops running millions of iterations stay within +0.003%: ADD +0.003%, MUL +0.001%, SUB/LT/GT/ISZERO/NOT/SIGNEXTEND/JUMPDEST +0.002..0.007%.
A fixed ~255 extra instructions per execute() call remain (setup and teardown inlining churn from the boundary casts and the Gas dispatch return value): +0.003% on normal workloads, up to +0.9% only on trivially short executions (synth/loop_v1: 27544 vs 27289 instructions).
1115/1115 unit tests pass (baseline, advanced, cgoto).