core/vm: fixes due to rebase and removed hardcoded fork schedule in genspec

This commit is contained in:
jonny rhea 2026-07-17 14:09:55 -05:00
parent efb58fc526
commit 008e27ae8a
5 changed files with 90 additions and 63 deletions

View file

@ -84,8 +84,8 @@ func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
return prior, ok
}
// chargeRegularOnly deducts a regular-only cost.
func (g *GasBudget) chargeRegularOnly(r uint64) error {
// ChargeRegularOnly deducts a regular-only cost.
func (g *GasBudget) ChargeRegularOnly(r uint64) error {
if g.RegularGas < r {
return ErrOutOfGas
}

View file

@ -113,7 +113,7 @@ func (g *generator) p(format string, args ...any) {
// parseHandlers parses instructions.go, eips.go, stack.go, gascosts.go and
// interpreter.go. It returns the top-level opXxx handlers by name, the
// //gen:inline *Stack helper methods, and the gas/memory helper functions
// (chargeRegularOnly, computeMemorySize, chargeDynamicGas) whose bodies are
// (ChargeRegularOnly, computeMemorySize, chargeDynamicGas) whose bodies are
// spliced into the generated dispatch (all by name).
func parseHandlers(vmDir string) (fset *token.FileSet, opcodeHandlers, stackHelpers, gasHelpers map[string]*ast.FuncDecl) {
fset = token.NewFileSet()
@ -132,7 +132,7 @@ func parseHandlers(vmDir string) (fset *token.FileSet, opcodeHandlers, stackHelp
continue
}
switch {
case fn.Name.Name == "chargeRegularOnly" || fn.Name.Name == "computeMemorySize" || fn.Name.Name == "chargeDynamicGas" || fn.Name.Name == "chargeVerkleCodeChunkGas": // spliced gas/memory helpers
case fn.Name.Name == "ChargeRegularOnly" || fn.Name.Name == "computeMemorySize" || fn.Name.Name == "chargeDynamicGas" || fn.Name.Name == "chargeVerkleCodeChunkGas": // spliced gas/memory helpers
gasHelpers[fn.Name.Name] = fn
case fn.Recv == nil: // top-level opXxx handler
opcodeHandlers[fn.Name.Name] = fn
@ -627,7 +627,7 @@ func (g *generator) generateStackChecks(minExpr, maxExpr any, under, over bool)
}
// generateStaticGas returns the static-gas charge, spliced call-free from the
// chargeRegularOnly body for amount: a constant on the inlined and
// ChargeRegularOnly body for amount: a constant on the inlined and
// direct-call paths, operation.constantGas in the table path. The receiver maps
// to contract.Gas and the method's single uint64 parameter to amount, substituted
// textually on word boundaries (which cannot touch fields like RegularGas). Its
@ -635,13 +635,13 @@ func (g *generator) generateStackChecks(minExpr, maxExpr any, under, over bool)
// is dropped so the opcode falls through to its remaining steps (see
// rewriteGasReturns).
func (g *generator) generateStaticGas(amount any) string {
fn := g.gasHelpers["chargeRegularOnly"]
fn := g.gasHelpers["ChargeRegularOnly"]
if fn == nil {
fatalf("no chargeRegularOnly gas helper to inline")
fatalf("no ChargeRegularOnly gas helper to inline")
}
names := paramNames(fn)
if len(names) != 1 {
fatalf("chargeRegularOnly takes %d params, want 1", len(names))
fatalf("ChargeRegularOnly takes %d params, want 1", len(names))
}
src := g.renderAst(fn.Body.List)
src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas")

View file

@ -22,6 +22,8 @@ import (
"reflect"
"runtime"
"strings"
"github.com/ethereum/go-ethereum/params"
)
// This file exposes the interpreter's opcode metadata to the code generator in
@ -56,35 +58,13 @@ type GenFork struct {
Ops [256]GenOp
}
// genForkOrder is the canonical fork progression for code generation, oldest to
// newest, each paired with the params.Rules field that activates it.
//
// Petersburg is omitted: it shares Constantinople's opcode set and only changes
// SSTORE dynamic gas, which flows through the shared gas function. Verkle/UBT is
// omitted: over its Shanghai base it adds no new opcodes (it only swaps gas and
// execute on existing opcodes), which the generated switch picks up from the
// active table at runtime.
var genForkOrder = []struct {
name string
rule string
set *JumpTable
}{
{"Frontier", "", &frontierInstructionSet},
{"Homestead", "IsHomestead", &homesteadInstructionSet},
{"TangerineWhistle", "IsEIP150", &tangerineWhistleInstructionSet},
{"SpuriousDragon", "IsEIP158", &spuriousDragonInstructionSet},
{"Byzantium", "IsByzantium", &byzantiumInstructionSet},
{"Constantinople", "IsConstantinople", &constantinopleInstructionSet},
{"Istanbul", "IsIstanbul", &istanbulInstructionSet},
{"Berlin", "IsBerlin", &berlinInstructionSet},
{"London", "IsLondon", &londonInstructionSet},
{"Merge", "IsMerge", &mergeInstructionSet},
{"Shanghai", "IsShanghai", &shanghaiInstructionSet},
{"Cancun", "IsCancun", &cancunInstructionSet},
{"Prague", "IsPrague", &pragueInstructionSet},
{"Osaka", "IsOsaka", &osakaInstructionSet},
{"Amsterdam", "IsAmsterdam", &amsterdamInstructionSet},
}
// codegenSkippedForks are forks in geth's fork schedule that the generator does
// not give a lane. Verkle/UBT is the only one: over its Shanghai base it adds no
// opcodes, it only swaps gas and execute functions on existing ones (enable4762),
// which the generated switch picks up from the active table at runtime. Emitting
// a lane for it would trip the generator's fork-stability check (an inlined op's
// execute function is not allowed to vary by fork).
var codegenSkippedForks = map[string]bool{"IsUBT": true}
// genFnName returns the FuncForPC name of a jump-table function value with the
// package path stripped (e.g. "gasKeccak256"), or "" if nil. An aliased var
@ -108,28 +88,75 @@ func genFnName(fn any) string {
}
// GenForks returns per-fork opcode metadata for the interpreter code generator
// (core/vm/gen). It is exported solely for that purpose.
// (core/vm/gen), one entry per fork that changes the opcode table, oldest to
// newest. It derives the progression from params.Rules and LookupInstructionSet
// (in params.Rules declaration order, which is chronological) so new forks are
// picked up without restating a list here.
func GenForks() []GenFork {
out := make([]GenFork, len(genForkOrder))
for i, f := range genForkOrder {
gf := GenFork{Name: f.name, RuleField: f.rule}
for code := range 256 {
op := f.set[code]
if op == nil || op.undefined {
continue
}
gf.Ops[code] = GenOp{
Name: OpCode(code).String(),
Defined: true,
ConstantGas: op.constantGas,
MinStack: op.minStack,
MaxStack: op.maxStack,
ExecuteFn: genFnName(op.execute),
DynamicGasFn: genFnName(op.dynamicGas),
MemorySizeFn: genFnName(op.memorySize),
}
// Frontier is always active and carries no rule gate.
frontier, _ := LookupInstructionSet(params.Rules{})
out := []GenFork{genFork("Frontier", "", &frontier)}
rt := reflect.TypeOf(params.Rules{})
for i := range rt.NumField() {
field := rt.Field(i)
if field.Type.Kind() != reflect.Bool || codegenSkippedForks[field.Name] {
continue
}
out[i] = gf
// Activate only this field so the fork resolves to the table it gates.
var rules params.Rules
reflect.ValueOf(&rules).Elem().Field(i).SetBool(true)
set, _ := LookupInstructionSet(rules)
if sameOps(&set, &frontier) {
continue // this rule does not change the opcode table
}
out = append(out, genFork(strings.TrimPrefix(field.Name, "Is"), field.Name, &set))
}
return out
}
// genFork extracts the generator-facing per-opcode metadata from one fork's
// instruction set.
func genFork(name, rule string, set *JumpTable) GenFork {
gf := GenFork{Name: name, RuleField: rule}
for code := range 256 {
op := set[code]
if op == nil || op.undefined {
continue
}
gf.Ops[code] = GenOp{
Name: OpCode(code).String(),
Defined: true,
ConstantGas: op.constantGas,
MinStack: op.minStack,
MaxStack: op.maxStack,
ExecuteFn: genFnName(op.execute),
DynamicGasFn: genFnName(op.dynamicGas),
MemorySizeFn: genFnName(op.memorySize),
}
}
return gf
}
// sameOps reports whether two instruction sets carry identical per-opcode static
// metadata: which slots are defined, and their static gas and stack bounds. It
// is used to tell whether a fork actually changes the opcode table. Handler,
// dynamic-gas and memory-size functions are ignored (they cannot be compared for
// equality and are reached through the table at runtime).
func sameOps(a, b *JumpTable) bool {
for code := range 256 {
oa, ob := a[code], b[code]
undefA := oa == nil || oa.undefined
undefB := ob == nil || ob.undefined
if undefA != undefB {
return false
}
if undefA {
continue
}
if oa.constantGas != ob.constantGas || oa.minStack != ob.minStack || oa.maxStack != ob.maxStack {
return false
}
}
return true
}

View file

@ -209,7 +209,7 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
// for tracing: this gas consumption event is emitted below in the debug section.
if err := contract.Gas.chargeRegularOnly(cost); err != nil {
if err := contract.Gas.ChargeRegularOnly(cost); err != nil {
return nil, err
}
@ -287,7 +287,7 @@ func (contract *Contract) chargeDynamicGas(operation *operation, evm *EVM, stack
// A regular-only deduction when there is no state gas, otherwise the full
// multidimensional charge through the reservoir.
if dynamicCost.StateGas == 0 {
if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
return dynamicCost, cerr
}
} else if !contract.Gas.charge(dynamicCost) {

View file

@ -601,7 +601,7 @@ mainLoop:
break mainLoop
}
if dynamicCost.StateGas == 0 {
if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
res, err = nil, cerr
break mainLoop
}
@ -664,7 +664,7 @@ mainLoop:
break mainLoop
}
if dynamicCost.StateGas == 0 {
if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
res, err = nil, cerr
break mainLoop
}
@ -712,7 +712,7 @@ mainLoop:
break mainLoop
}
if dynamicCost.StateGas == 0 {
if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
res, err = nil, cerr
break mainLoop
}
@ -760,7 +760,7 @@ mainLoop:
break mainLoop
}
if dynamicCost.StateGas == 0 {
if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
if cerr := contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
res, err = nil, cerr
break mainLoop
}