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 return prior, ok
} }
// chargeRegularOnly deducts a regular-only cost. // ChargeRegularOnly deducts a regular-only cost.
func (g *GasBudget) chargeRegularOnly(r uint64) error { func (g *GasBudget) ChargeRegularOnly(r uint64) error {
if g.RegularGas < r { if g.RegularGas < r {
return ErrOutOfGas 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 // parseHandlers parses instructions.go, eips.go, stack.go, gascosts.go and
// interpreter.go. It returns the top-level opXxx handlers by name, the // interpreter.go. It returns the top-level opXxx handlers by name, the
// //gen:inline *Stack helper methods, and the gas/memory helper functions // //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). // spliced into the generated dispatch (all by name).
func parseHandlers(vmDir string) (fset *token.FileSet, opcodeHandlers, stackHelpers, gasHelpers map[string]*ast.FuncDecl) { func parseHandlers(vmDir string) (fset *token.FileSet, opcodeHandlers, stackHelpers, gasHelpers map[string]*ast.FuncDecl) {
fset = token.NewFileSet() fset = token.NewFileSet()
@ -132,7 +132,7 @@ func parseHandlers(vmDir string) (fset *token.FileSet, opcodeHandlers, stackHelp
continue continue
} }
switch { 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 gasHelpers[fn.Name.Name] = fn
case fn.Recv == nil: // top-level opXxx handler case fn.Recv == nil: // top-level opXxx handler
opcodeHandlers[fn.Name.Name] = fn 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 // 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 // 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 // to contract.Gas and the method's single uint64 parameter to amount, substituted
// textually on word boundaries (which cannot touch fields like RegularGas). Its // 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 // is dropped so the opcode falls through to its remaining steps (see
// rewriteGasReturns). // rewriteGasReturns).
func (g *generator) generateStaticGas(amount any) string { func (g *generator) generateStaticGas(amount any) string {
fn := g.gasHelpers["chargeRegularOnly"] fn := g.gasHelpers["ChargeRegularOnly"]
if fn == nil { if fn == nil {
fatalf("no chargeRegularOnly gas helper to inline") fatalf("no ChargeRegularOnly gas helper to inline")
} }
names := paramNames(fn) names := paramNames(fn)
if len(names) != 1 { 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 := g.renderAst(fn.Body.List)
src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas") src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas")

View file

@ -22,6 +22,8 @@ import (
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
"github.com/ethereum/go-ethereum/params"
) )
// This file exposes the interpreter's opcode metadata to the code generator in // This file exposes the interpreter's opcode metadata to the code generator in
@ -56,35 +58,13 @@ type GenFork struct {
Ops [256]GenOp Ops [256]GenOp
} }
// genForkOrder is the canonical fork progression for code generation, oldest to // codegenSkippedForks are forks in geth's fork schedule that the generator does
// newest, each paired with the params.Rules field that activates it. // 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),
// Petersburg is omitted: it shares Constantinople's opcode set and only changes // which the generated switch picks up from the active table at runtime. Emitting
// SSTORE dynamic gas, which flows through the shared gas function. Verkle/UBT is // a lane for it would trip the generator's fork-stability check (an inlined op's
// omitted: over its Shanghai base it adds no new opcodes (it only swaps gas and // execute function is not allowed to vary by fork).
// execute on existing opcodes), which the generated switch picks up from the var codegenSkippedForks = map[string]bool{"IsUBT": true}
// 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},
}
// genFnName returns the FuncForPC name of a jump-table function value with the // 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 // package path stripped (e.g. "gasKeccak256"), or "" if nil. An aliased var
@ -108,13 +88,39 @@ func genFnName(fn any) string {
} }
// GenForks returns per-fork opcode metadata for the interpreter code generator // 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 { func GenForks() []GenFork {
out := make([]GenFork, len(genForkOrder)) // Frontier is always active and carries no rule gate.
for i, f := range genForkOrder { frontier, _ := LookupInstructionSet(params.Rules{})
gf := GenFork{Name: f.name, RuleField: f.rule} 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
}
// 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 { for code := range 256 {
op := f.set[code] op := set[code]
if op == nil || op.undefined { if op == nil || op.undefined {
continue continue
} }
@ -129,7 +135,28 @@ func GenForks() []GenFork {
MemorySizeFn: genFnName(op.memorySize), MemorySizeFn: genFnName(op.memorySize),
} }
} }
out[i] = gf return gf
} }
return out
// 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} return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
} }
// for tracing: this gas consumption event is emitted below in the debug section. // 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 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 // A regular-only deduction when there is no state gas, otherwise the full
// multidimensional charge through the reservoir. // multidimensional charge through the reservoir.
if dynamicCost.StateGas == 0 { 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 return dynamicCost, cerr
} }
} else if !contract.Gas.charge(dynamicCost) { } else if !contract.Gas.charge(dynamicCost) {

View file

@ -601,7 +601,7 @@ mainLoop:
break mainLoop break mainLoop
} }
if dynamicCost.StateGas == 0 { 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 res, err = nil, cerr
break mainLoop break mainLoop
} }
@ -664,7 +664,7 @@ mainLoop:
break mainLoop break mainLoop
} }
if dynamicCost.StateGas == 0 { 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 res, err = nil, cerr
break mainLoop break mainLoop
} }
@ -712,7 +712,7 @@ mainLoop:
break mainLoop break mainLoop
} }
if dynamicCost.StateGas == 0 { 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 res, err = nil, cerr
break mainLoop break mainLoop
} }
@ -760,7 +760,7 @@ mainLoop:
break mainLoop break mainLoop
} }
if dynamicCost.StateGas == 0 { 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 res, err = nil, cerr
break mainLoop break mainLoop
} }