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
|
for code := 0x80; code <= 0x8f; code++ { // DUP1-16
|
||||||
g.checkStable(byte(code), "makeDup", forks)
|
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) {
|
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 {
|
if !o.Defined {
|
||||||
continue
|
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)
|
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
|
// Case emission
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -18,27 +18,35 @@ package vm
|
||||||
|
|
||||||
//go:generate go run ./gen
|
//go:generate go run ./gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// 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
|
||||||
// core/vm/gen. It is not used at runtime. It exists so the generator can derive
|
// 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
|
// 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
|
// appears in, and the FuncForPC names of its handler/gas/memory functions) from
|
||||||
// existing per-fork instruction sets, rather than restating that metadata.
|
// the existing per-fork instruction sets, rather than restating that metadata.
|
||||||
//
|
//
|
||||||
// The fork-varying dynamic-gas / memory-size / execute *functions* are not
|
// The function names let the generator confirm the directCold ops are
|
||||||
// surfaced here: several are closures (gasCall, the memoryCopierGas family,
|
// fork-invariant. The fork-varying gas/execute functions themselves are still
|
||||||
// makeGasLog) that cannot be recovered by name. The generated switch instead
|
// reached through the active per-fork JumpTable at runtime (see interp_gen.go),
|
||||||
// reaches those volatile opcodes through the active per-fork JumpTable at
|
// not emitted by name: several are closures (gasCall, the memoryCopierGas
|
||||||
// runtime (see interp_gen.go), so they need no generator-side restatement.
|
// 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.
|
// GenOp is the generator-facing scalar metadata for one opcode slot in one fork.
|
||||||
type GenOp struct {
|
type GenOp struct {
|
||||||
Name string // opcode mnemonic, e.g. "ADD" (valid only if Defined)
|
Name string // opcode mnemonic, e.g. "ADD" (valid only if Defined)
|
||||||
Defined bool // false if the slot is undefined/invalid in this fork
|
Defined bool // false if the slot is undefined/invalid in this fork
|
||||||
ConstantGas uint64
|
ConstantGas uint64
|
||||||
MinStack int
|
MinStack int
|
||||||
MaxStack int
|
MaxStack int
|
||||||
HasDynamicGas bool
|
ExecuteFn string // FuncForPC name of op.execute
|
||||||
HasMemorySize bool
|
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
|
// GenFork bundles a fork's name, the params.Rules bool field that activates it
|
||||||
|
|
@ -79,6 +87,22 @@ var genForkOrder = []struct {
|
||||||
{"Amsterdam", "IsAmsterdam", &amsterdamInstructionSet},
|
{"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
|
// GenForks returns per-fork opcode metadata for the interpreter code generator
|
||||||
// (core/vm/gen). It is exported solely for that purpose.
|
// (core/vm/gen). It is exported solely for that purpose.
|
||||||
func GenForks() []GenFork {
|
func GenForks() []GenFork {
|
||||||
|
|
@ -91,13 +115,14 @@ func GenForks() []GenFork {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gf.Ops[code] = GenOp{
|
gf.Ops[code] = GenOp{
|
||||||
Name: OpCode(code).String(),
|
Name: OpCode(code).String(),
|
||||||
Defined: true,
|
Defined: true,
|
||||||
ConstantGas: op.constantGas,
|
ConstantGas: op.constantGas,
|
||||||
MinStack: op.minStack,
|
MinStack: op.minStack,
|
||||||
MaxStack: op.maxStack,
|
MaxStack: op.maxStack,
|
||||||
HasDynamicGas: op.dynamicGas != nil,
|
ExecuteFn: genFnName(op.execute),
|
||||||
HasMemorySize: op.memorySize != nil,
|
DynamicGasFn: genFnName(op.dynamicGas),
|
||||||
|
MemorySizeFn: genFnName(op.memorySize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out[i] = gf
|
out[i] = gf
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue