mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
core/vm, core/vm/gen: verify directCold opcodes are fork-stable
This commit is contained in:
parent
72d0907790
commit
7e2d97ce5f
2 changed files with 88 additions and 22 deletions
|
|
@ -518,6 +518,12 @@ func (g *generator) deriveMeta(forks []vm.GenFork) {
|
|||
for code := 0x80; code <= 0x8f; code++ { // DUP1-16
|
||||
g.checkStable(byte(code), "makeDup", forks)
|
||||
}
|
||||
// directCold opcodes bake their static gas and stack bounds the same way, so
|
||||
// they must be fork-stable too. Dynamic gas is allowed (it is charged through
|
||||
// the named gas function, not baked).
|
||||
for code := range directCold {
|
||||
g.checkColdStable(code, forks)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) checkStable(code byte, what string, forks []vm.GenFork) {
|
||||
|
|
@ -530,12 +536,47 @@ func (g *generator) checkStable(code byte, what string, forks []vm.GenFork) {
|
|||
if !o.Defined {
|
||||
continue
|
||||
}
|
||||
if o.ConstantGas != m.constGas || o.MinStack != m.minStack || o.MaxStack != m.maxStack || o.HasDynamicGas {
|
||||
if o.ConstantGas != m.constGas || o.MinStack != m.minStack || o.MaxStack != m.maxStack || o.DynamicGasFn != "" {
|
||||
fatalf("opcode %#x (%s) is not fork-stable (fork %s): cannot inline", code, what, fork.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkColdStable verifies a directCold opcode is safe to direct-call. Its static
|
||||
// gas and stack bounds must be the same across every fork it appears in (they are
|
||||
// baked as constants), and its handler, gas and memory functions must be the same
|
||||
// across those forks too (they are called by name, so a fork that swapped one
|
||||
// would otherwise be missed). Unlike checkStable it allows dynamic gas, which
|
||||
// directCold ops carry by definition. It does not check the directCold map's names
|
||||
// against the table, which the differential test covers.
|
||||
func (g *generator) checkColdStable(code byte, forks []vm.GenFork) {
|
||||
m := g.meta[code]
|
||||
if !m.defined {
|
||||
fatalf("opcode %#x (directCold) is never defined", code)
|
||||
}
|
||||
var exec, dyn, mem string
|
||||
seen := false
|
||||
for _, fork := range forks {
|
||||
o := fork.Ops[code]
|
||||
if !o.Defined {
|
||||
continue
|
||||
}
|
||||
if o.ConstantGas != m.constGas || o.MinStack != m.minStack || o.MaxStack != m.maxStack {
|
||||
fatalf("opcode %#x (%s) is in directCold but not fork-stable (fork %s): static gas or stack bounds vary, cannot bake", code, m.name, fork.Name)
|
||||
}
|
||||
// Handler, gas and memory functions must match across forks too, or
|
||||
// direct-calling them by name would skip a fork that swapped one. Names
|
||||
// come from FuncForPC via vm.GenForks (aliases resolve to the underlying
|
||||
// func, still stable across forks).
|
||||
if !seen {
|
||||
exec, dyn, mem, seen = o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, true
|
||||
} else if o.ExecuteFn != exec || o.DynamicGasFn != dyn || o.MemorySizeFn != mem {
|
||||
fatalf("opcode %#x (%s) is in directCold but its functions vary by fork (fork %s): got %s/%s/%s, want %s/%s/%s, cannot direct-call",
|
||||
code, m.name, fork.Name, o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, exec, dyn, mem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Case emission
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -18,27 +18,35 @@ package vm
|
|||
|
||||
//go:generate go run ./gen
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This file exposes the interpreter's opcode metadata to the code generator in
|
||||
// core/vm/gen. It is not used at runtime. It exists so the generator can derive
|
||||
// the per-opcode spec (static gas, stack bounds, the fork an opcode first
|
||||
// appears in, and whether it carries dynamic gas or memory sizing) from the
|
||||
// existing per-fork instruction sets, rather than restating that metadata.
|
||||
// appears in, and the FuncForPC names of its handler/gas/memory functions) from
|
||||
// the existing per-fork instruction sets, rather than restating that metadata.
|
||||
//
|
||||
// The fork-varying dynamic-gas / memory-size / execute *functions* are not
|
||||
// surfaced here: several are closures (gasCall, the memoryCopierGas family,
|
||||
// makeGasLog) that cannot be recovered by name. The generated switch instead
|
||||
// reaches those volatile opcodes through the active per-fork JumpTable at
|
||||
// runtime (see interp_gen.go), so they need no generator-side restatement.
|
||||
// The function names let the generator confirm the directCold ops are
|
||||
// fork-invariant. The fork-varying gas/execute functions themselves are still
|
||||
// reached through the active per-fork JumpTable at runtime (see interp_gen.go),
|
||||
// not emitted by name: several are closures (gasCall, the memoryCopierGas
|
||||
// family, makeGasLog) that FuncForPC reports only as anonymous labels, so they
|
||||
// could not be called by name in any case.
|
||||
|
||||
// GenOp is the generator-facing scalar metadata for one opcode slot in one fork.
|
||||
type GenOp struct {
|
||||
Name string // opcode mnemonic, e.g. "ADD" (valid only if Defined)
|
||||
Defined bool // false if the slot is undefined/invalid in this fork
|
||||
ConstantGas uint64
|
||||
MinStack int
|
||||
MaxStack int
|
||||
HasDynamicGas bool
|
||||
HasMemorySize bool
|
||||
Name string // opcode mnemonic, e.g. "ADD" (valid only if Defined)
|
||||
Defined bool // false if the slot is undefined/invalid in this fork
|
||||
ConstantGas uint64
|
||||
MinStack int
|
||||
MaxStack int
|
||||
ExecuteFn string // FuncForPC name of op.execute
|
||||
DynamicGasFn string // FuncForPC name of op.dynamicGas, "" if nil
|
||||
MemorySizeFn string // FuncForPC name of op.memorySize, "" if nil
|
||||
}
|
||||
|
||||
// GenFork bundles a fork's name, the params.Rules bool field that activates it
|
||||
|
|
@ -79,6 +87,22 @@ var genForkOrder = []struct {
|
|||
{"Amsterdam", "IsAmsterdam", &amsterdamInstructionSet},
|
||||
}
|
||||
|
||||
// genFnName returns the short FuncForPC name of a jump-table function value
|
||||
// (e.g. "gasKeccak256"), or "" if nil. An aliased var resolves to the underlying
|
||||
// function (gasMLoad reports "pureMemoryGascost"), which is still stable across
|
||||
// forks and so serves the directCold fork-invariance check.
|
||||
func genFnName(fn any) string {
|
||||
v := reflect.ValueOf(fn)
|
||||
if !v.IsValid() || v.IsNil() {
|
||||
return ""
|
||||
}
|
||||
full := runtime.FuncForPC(v.Pointer()).Name()
|
||||
if i := strings.LastIndex(full, "."); i >= 0 {
|
||||
return full[i+1:]
|
||||
}
|
||||
return full
|
||||
}
|
||||
|
||||
// GenForks returns per-fork opcode metadata for the interpreter code generator
|
||||
// (core/vm/gen). It is exported solely for that purpose.
|
||||
func GenForks() []GenFork {
|
||||
|
|
@ -91,13 +115,14 @@ func GenForks() []GenFork {
|
|||
continue
|
||||
}
|
||||
gf.Ops[code] = GenOp{
|
||||
Name: OpCode(code).String(),
|
||||
Defined: true,
|
||||
ConstantGas: op.constantGas,
|
||||
MinStack: op.minStack,
|
||||
MaxStack: op.maxStack,
|
||||
HasDynamicGas: op.dynamicGas != nil,
|
||||
HasMemorySize: op.memorySize != nil,
|
||||
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),
|
||||
}
|
||||
}
|
||||
out[i] = gf
|
||||
|
|
|
|||
Loading…
Reference in a new issue