mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 03:10:48 +00:00
core/vm, core/vm/gen: fix generated EIP-8037 state-gas charging
This commit is contained in:
parent
5d1ff24f54
commit
2e0035a63a
5 changed files with 564 additions and 186 deletions
|
|
@ -84,15 +84,14 @@ func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
||||||
return prior, ok
|
return prior, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChargeRegularOnly deducts a regular-only cost. It's always preferred for
|
// chargeRegularOnly deducts a regular-only cost.
|
||||||
// performance consideration if the opcode doesn't have any state cost.
|
func (g *GasBudget) chargeRegularOnly(r uint64) error {
|
||||||
func (g *GasBudget) ChargeRegularOnly(r uint64) bool {
|
|
||||||
if g.RegularGas < r {
|
if g.RegularGas < r {
|
||||||
return false
|
return ErrOutOfGas
|
||||||
}
|
}
|
||||||
g.RegularGas -= r
|
g.RegularGas -= r
|
||||||
g.UsedRegularGas += r
|
g.UsedRegularGas += r
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanAfford reports whether the running budget can cover the given cost vector
|
// CanAfford reports whether the running budget can cover the given cost vector
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
// derived from the per-fork instruction tables via vm.GenForks.
|
// derived from the per-fork instruction tables via vm.GenForks.
|
||||||
//
|
//
|
||||||
// - calls the fork-invariant ops (KECCAK256 / MLOAD / MSTORE / MSTORE8,
|
// - calls the fork-invariant ops (KECCAK256 / MLOAD / MSTORE / MSTORE8,
|
||||||
// see directCall) directly by name, skipping the table's function
|
// see directCallOps) directly by name, skipping the table's function
|
||||||
// pointers, which Go cannot inline through.
|
// pointers, which Go cannot inline through.
|
||||||
//
|
//
|
||||||
// - dispatches everything fork-varying (CALL / CREATE / SSTORE / SLOAD / LOG /
|
// - dispatches everything fork-varying (CALL / CREATE / SSTORE / SLOAD / LOG /
|
||||||
|
|
@ -57,14 +57,14 @@ import (
|
||||||
|
|
||||||
const stackLimit = 1024 // params.StackLimit
|
const stackLimit = 1024 // params.StackLimit
|
||||||
|
|
||||||
// inlineHandler maps an opcode byte to the handler whose body is spliced inline
|
// inlineOps maps an opcode byte to the handler whose body is spliced inline
|
||||||
// for that opcode. These are the hot, fork-stable opcodes with no dynamic gas.
|
// for that opcode. These are the hot, fork-stable opcodes with no dynamic gas.
|
||||||
// The value is usually an opXxx handler, but PUSH3-PUSH32 and DUP1-DUP16 are
|
// The value is usually an opXxx handler, but PUSH3-PUSH32 and DUP1-DUP16 are
|
||||||
// factory-built (one shared makePush / makeDup each), so their value is the
|
// factory-built (one shared makePush / makeDup each), so their value is the
|
||||||
// factory name and emitOpBody splices the factory body with the per-opcode size.
|
// factory name and emitOpBody splices the factory body with the per-opcode size.
|
||||||
// Opcodes not listed here (or in directCall) fall through to the default case,
|
// Opcodes not listed here (or in directCallOps) fall through to the default case,
|
||||||
// which dispatches via the per-fork table.
|
// which dispatches via the per-fork table.
|
||||||
var inlineHandler = func() map[byte]string {
|
var inlineOps = func() map[byte]string {
|
||||||
m := map[byte]string{
|
m := map[byte]string{
|
||||||
0x01: "opAdd", 0x02: "opMul", 0x03: "opSub", 0x04: "opDiv", 0x05: "opSdiv",
|
0x01: "opAdd", 0x02: "opMul", 0x03: "opSub", 0x04: "opDiv", 0x05: "opSdiv",
|
||||||
0x06: "opMod", 0x07: "opSmod", 0x08: "opAddmod", 0x09: "opMulmod", 0x0b: "opSignExtend",
|
0x06: "opMod", 0x07: "opSmod", 0x08: "opAddmod", 0x09: "opMulmod", 0x0b: "opSignExtend",
|
||||||
|
|
@ -87,7 +87,7 @@ var inlineHandler = func() map[byte]string {
|
||||||
return m
|
return m
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// directCall lists the opcodes (dynamic gas, not inlined) whose handler,
|
// directCallOps lists the opcodes (dynamic gas, not inlined) whose handler,
|
||||||
// dynamic-gas, and memory-size functions are the same across every fork
|
// dynamic-gas, and memory-size functions are the same across every fork
|
||||||
// (verified: untouched by any enableXxx). They are emitted as direct calls to
|
// (verified: untouched by any enableXxx). They are emitted as direct calls to
|
||||||
// those functions by name instead of the indirect operation.* pointer calls
|
// those functions by name instead of the indirect operation.* pointer calls
|
||||||
|
|
@ -102,7 +102,7 @@ var inlineHandler = func() map[byte]string {
|
||||||
// Limited to the memory/hash ops that appear in hot loops. Adding more (e.g.
|
// Limited to the memory/hash ops that appear in hot loops. Adding more (e.g.
|
||||||
// CALLDATACOPY/RETURN, typically once per call) grows the generated function
|
// CALLDATACOPY/RETURN, typically once per call) grows the generated function
|
||||||
// and regresses tiny benchmarks through code layout, for negligible gain.
|
// and regresses tiny benchmarks through code layout, for negligible gain.
|
||||||
var directCall = map[byte][3]string{
|
var directCallOps = map[byte][3]string{
|
||||||
0x20: {"opKeccak256", "gasKeccak256", "memoryKeccak256"}, // KECCAK256
|
0x20: {"opKeccak256", "gasKeccak256", "memoryKeccak256"}, // KECCAK256
|
||||||
0x51: {"opMload", "gasMLoad", "memoryMLoad"}, // MLOAD
|
0x51: {"opMload", "gasMLoad", "memoryMLoad"}, // MLOAD
|
||||||
0x52: {"opMstore", "gasMStore", "memoryMStore"}, // MSTORE
|
0x52: {"opMstore", "gasMStore", "memoryMStore"}, // MSTORE
|
||||||
|
|
@ -113,7 +113,7 @@ var directCall = map[byte][3]string{
|
||||||
type opSpec struct {
|
type opSpec struct {
|
||||||
defined bool
|
defined bool
|
||||||
name string // opcode mnemonic, e.g. "ADD"
|
name string // opcode mnemonic, e.g. "ADD"
|
||||||
introF string // params.Rules field activating it, empty for Frontier (always on)
|
fork string // params.Rules field activating it, empty for Frontier (always on)
|
||||||
constGas uint64
|
constGas uint64
|
||||||
minStack int
|
minStack int
|
||||||
maxStack int
|
maxStack int
|
||||||
|
|
@ -123,6 +123,7 @@ type generator struct {
|
||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
handlers map[string]*ast.FuncDecl // opXxx handlers from instructions.go and eips.go
|
handlers map[string]*ast.FuncDecl // opXxx handlers from instructions.go and eips.go
|
||||||
stackHelpers map[string]*ast.FuncDecl // (s *Stack) helpers from stack.go, spliced inline
|
stackHelpers map[string]*ast.FuncDecl // (s *Stack) helpers from stack.go, spliced inline
|
||||||
|
gasHelpers map[string]*ast.FuncDecl // (g *GasBudget) charge methods from gascosts.go, spliced by name
|
||||||
specs [256]opSpec
|
specs [256]opSpec
|
||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
@ -138,14 +139,16 @@ func (g *generator) p(format string, args ...any) {
|
||||||
// Handler parsing + body splicing
|
// Handler parsing + body splicing
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// parseHandlers parses instructions.go, eips.go and stack.go. It returns the
|
// parseHandlers parses instructions.go, eips.go, stack.go and gascosts.go. It
|
||||||
// top-level opXxx handlers by name, and separately the *Stack helper methods by
|
// returns the top-level opXxx handlers by name, the //gen:inline *Stack helper
|
||||||
// name (the inliner splices the latter into the former).
|
// methods by name (spliced into handler bodies), and the *GasBudget charge
|
||||||
func parseHandlers(vmDir string) (fset *token.FileSet, handlers, stackHelpers map[string]*ast.FuncDecl) {
|
// methods by name (spliced directly at gas steps).
|
||||||
|
func parseHandlers(vmDir string) (fset *token.FileSet, handlers, stackHelpers, gasHelpers map[string]*ast.FuncDecl) {
|
||||||
fset = token.NewFileSet()
|
fset = token.NewFileSet()
|
||||||
handlers = map[string]*ast.FuncDecl{}
|
handlers = map[string]*ast.FuncDecl{}
|
||||||
stackHelpers = map[string]*ast.FuncDecl{}
|
stackHelpers = map[string]*ast.FuncDecl{}
|
||||||
for _, name := range []string{"instructions.go", "eips.go", "stack.go"} {
|
gasHelpers = map[string]*ast.FuncDecl{}
|
||||||
|
for _, name := range []string{"instructions.go", "eips.go", "stack.go", "gascosts.go"} {
|
||||||
path := filepath.Join(vmDir, name)
|
path := filepath.Join(vmDir, name)
|
||||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -159,25 +162,31 @@ func parseHandlers(vmDir string) (fset *token.FileSet, handlers, stackHelpers ma
|
||||||
switch {
|
switch {
|
||||||
case fn.Recv == nil: // top-level opXxx handler
|
case fn.Recv == nil: // top-level opXxx handler
|
||||||
handlers[fn.Name.Name] = fn
|
handlers[fn.Name.Name] = fn
|
||||||
case isStackMethod(fn) && hasInlineMarker(fn): // (s *Stack) helper tagged //gen:inline
|
case methodReceiver(fn) == "Stack" && hasInlineMarker(fn): // (s *Stack) helper tagged //gen:inline
|
||||||
stackHelpers[fn.Name.Name] = fn
|
stackHelpers[fn.Name.Name] = fn
|
||||||
|
case methodReceiver(fn) == "GasBudget": // (g *GasBudget) charge method, spliced by name at gas steps
|
||||||
|
gasHelpers[fn.Name.Name] = fn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fset, handlers, stackHelpers
|
return fset, handlers, stackHelpers, gasHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
// isStackMethod reports whether fn is a method on *Stack.
|
// methodReceiver returns the receiver type name of a pointer-receiver method
|
||||||
func isStackMethod(fn *ast.FuncDecl) bool {
|
// (e.g. "Stack" for (s *Stack)), or "" if fn is not such a method.
|
||||||
|
func methodReceiver(fn *ast.FuncDecl) string {
|
||||||
if fn.Recv == nil || len(fn.Recv.List) != 1 {
|
if fn.Recv == nil || len(fn.Recv.List) != 1 {
|
||||||
return false
|
return ""
|
||||||
}
|
}
|
||||||
star, ok := fn.Recv.List[0].Type.(*ast.StarExpr)
|
star, ok := fn.Recv.List[0].Type.(*ast.StarExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return ""
|
||||||
}
|
}
|
||||||
id, ok := star.X.(*ast.Ident)
|
id, ok := star.X.(*ast.Ident)
|
||||||
return ok && id.Name == "Stack"
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return id.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasInlineMarker reports whether fn is tagged //gen:inline, which marks a stack
|
// hasInlineMarker reports whether fn is tagged //gen:inline, which marks a stack
|
||||||
|
|
@ -194,25 +203,25 @@ func hasInlineMarker(fn *ast.FuncDecl) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var returnRe = regexp.MustCompile(`^(\s*)return\s+([^,]+),\s*(.+)$`)
|
var opcodeReturnRe = regexp.MustCompile(`^(\s*)return\s+([^,]+),\s*(.+)$`)
|
||||||
|
|
||||||
// inlineBody returns a named handler's body, rewritten so it can be spliced
|
// inlineOpcodeBody returns a named handler's body, rewritten so it can be spliced
|
||||||
// into the dispatch loop (see rewriteSplicedBody). The caller emits it with p.
|
// into the dispatch loop (see rewriteOpcodeReturns). The caller emits it with p.
|
||||||
func (g *generator) inlineBody(handler string) string {
|
func (g *generator) inlineOpcodeBody(handler string) string {
|
||||||
fn := g.handlers[handler]
|
fn := g.handlers[handler]
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
fatalf("no handler %q to inline", handler)
|
fatalf("no handler %q to inline", handler)
|
||||||
}
|
}
|
||||||
return g.rewriteSplicedBody(g.inlineStackHelpers(fn.Body.List, nil))
|
return g.rewriteOpcodeReturns(g.inlineStackHelpers(fn.Body.List, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// inlineFactoryBody splices the body of the executionFunc closure that a make*
|
// inlineOpcodeFactoryBody splices the body of the executionFunc closure that a make*
|
||||||
// factory returns, substituting the factory's parameters with the per-opcode
|
// factory returns, substituting the factory's parameters with the per-opcode
|
||||||
// constants in args (positional, matching the factory signature). This lets
|
// constants in args (positional, matching the factory signature). This lets
|
||||||
// closure-built handlers (makePush, makeDup) be derived from their single
|
// closure-built handlers (makePush, makeDup) be derived from their single
|
||||||
// definition rather than restated in the generator. The caller emits the
|
// definition rather than restated in the generator. The caller emits the
|
||||||
// result with p.
|
// result with p.
|
||||||
func (g *generator) inlineFactoryBody(factory string, args ...int) string {
|
func (g *generator) inlineOpcodeFactoryBody(factory string, args ...int) string {
|
||||||
fn := g.handlers[factory]
|
fn := g.handlers[factory]
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
fatalf("no factory %q to inline", factory)
|
fatalf("no factory %q to inline", factory)
|
||||||
|
|
@ -227,7 +236,7 @@ func (g *generator) inlineFactoryBody(factory string, args ...int) string {
|
||||||
for i, nm := range names {
|
for i, nm := range names {
|
||||||
params[nm] = args[i]
|
params[nm] = args[i]
|
||||||
}
|
}
|
||||||
return g.rewriteSplicedBody(g.inlineStackHelpers(lit.Body.List, params))
|
return g.rewriteOpcodeReturns(g.inlineStackHelpers(lit.Body.List, params))
|
||||||
}
|
}
|
||||||
|
|
||||||
// factoryClosure returns the executionFunc literal that a make* factory's body
|
// factoryClosure returns the executionFunc literal that a make* factory's body
|
||||||
|
|
@ -262,17 +271,17 @@ func (g *generator) renderAst(stmts []ast.Stmt) string {
|
||||||
return raw.String()
|
return raw.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewriteSplicedBody rewrites a printed handler body so it runs inside the
|
// rewriteOpcodeReturns rewrites a printed handler body so it runs inside the
|
||||||
// dispatch loop: the `*pc` dereference becomes the loop's `pc` local, and each
|
// dispatch loop: the `*pc` dereference becomes the loop's `pc` local, and each
|
||||||
// `return r0, r1` becomes loop control flow. Success (r1 == nil) advances pc
|
// `return r0, r1` becomes loop control flow. Success (r1 == nil) advances pc
|
||||||
// and continues, an error sets err and breaks. (Stack helpers were already
|
// and continues, an error sets err and breaks. (Stack helpers were already
|
||||||
// inlined by inlineStackHelpers before the body was printed.)
|
// inlined by inlineStackHelpers before the body was printed.)
|
||||||
func (g *generator) rewriteSplicedBody(src string) string {
|
func (g *generator) rewriteOpcodeReturns(src string) string {
|
||||||
src = strings.ReplaceAll(src, "*pc", "pc")
|
src = strings.ReplaceAll(src, "*pc", "pc")
|
||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
for _, line := range strings.Split(src, "\n") {
|
for _, line := range strings.Split(src, "\n") {
|
||||||
if m := returnRe.FindStringSubmatch(line); m != nil {
|
if m := opcodeReturnRe.FindStringSubmatch(line); m != nil {
|
||||||
indent, r0, r1 := m[1], strings.TrimSpace(m[2]), strings.TrimSpace(m[3])
|
indent, r0, r1 := m[1], strings.TrimSpace(m[2]), strings.TrimSpace(m[3])
|
||||||
// The error and halt path must overwrite res and err. Otherwise a
|
// The error and halt path must overwrite res and err. Otherwise a
|
||||||
// halting inlined op (JUMPI on an invalid jump, say) returns stale
|
// halting inlined op (JUMPI on an invalid jump, say) returns stale
|
||||||
|
|
@ -297,6 +306,50 @@ func (g *generator) rewriteSplicedBody(src string) string {
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gasReturnRe = regexp.MustCompile(`^(\s*)return\s+(\S.*)$`)
|
||||||
|
|
||||||
|
// inlineGasBody splices a (g *GasBudget) charge method's body at a gas step,
|
||||||
|
// mapping the receiver to contract.Gas and the method's single uint64 parameter
|
||||||
|
// to the per-opcode gas constant. It is the gas-step analog of inlineOpcodeBody: the
|
||||||
|
// method's `return <err>` becomes the loop's out-of-gas exit and its trailing
|
||||||
|
// `return nil` is dropped so the opcode falls through to its remaining steps (see
|
||||||
|
// rewriteGasReturns). The receiver and parameter are substituted textually on
|
||||||
|
// word boundaries, which cannot touch fields like RegularGas.
|
||||||
|
func (g *generator) inlineGasBody(name string, arg int) string {
|
||||||
|
fn := g.gasHelpers[name]
|
||||||
|
if fn == nil {
|
||||||
|
fatalf("no gas helper %q to inline", name)
|
||||||
|
}
|
||||||
|
names := paramNames(fn)
|
||||||
|
if len(names) != 1 {
|
||||||
|
fatalf("gas helper %q takes %d params, want 1", name, len(names))
|
||||||
|
}
|
||||||
|
src := g.renderAst(fn.Body.List)
|
||||||
|
src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas")
|
||||||
|
src = substParams(src, map[string]int{names[0]: arg})
|
||||||
|
return g.rewriteGasReturns(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteGasReturns rewrites a spliced charge body so it runs as a gas step in
|
||||||
|
// the dispatch loop: a `return <err>` becomes the out-of-gas break, and the
|
||||||
|
// trailing `return nil` (success) is dropped so the opcode continues.
|
||||||
|
func (g *generator) rewriteGasReturns(src string) string {
|
||||||
|
var out bytes.Buffer
|
||||||
|
for _, line := range strings.Split(src, "\n") {
|
||||||
|
if m := gasReturnRe.FindStringSubmatch(line); m != nil {
|
||||||
|
indent, val := m[1], strings.TrimSpace(m[2])
|
||||||
|
if val == "nil" {
|
||||||
|
continue // success: fall through to the rest of the op
|
||||||
|
}
|
||||||
|
out.WriteString(indent + "res, err = nil, " + val + "\n")
|
||||||
|
out.WriteString(indent + "break mainLoop\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out.WriteString(line + "\n")
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
// stackCall is a matched call to a tagged helper.
|
// stackCall is a matched call to a tagged helper.
|
||||||
type stackCall struct {
|
type stackCall struct {
|
||||||
helper string // helper method name
|
helper string // helper method name
|
||||||
|
|
@ -506,7 +559,7 @@ func (g *generator) deriveSpecs(forks []vm.GenFork) {
|
||||||
g.specs[code] = opSpec{
|
g.specs[code] = opSpec{
|
||||||
defined: true,
|
defined: true,
|
||||||
name: o.Name,
|
name: o.Name,
|
||||||
introF: fork.RuleField,
|
fork: fork.RuleField,
|
||||||
constGas: o.ConstantGas,
|
constGas: o.ConstantGas,
|
||||||
minStack: o.MinStack,
|
minStack: o.MinStack,
|
||||||
maxStack: o.MaxStack,
|
maxStack: o.MaxStack,
|
||||||
|
|
@ -517,13 +570,13 @@ func (g *generator) deriveSpecs(forks []vm.GenFork) {
|
||||||
// Sanity: every inlined opcode must be defined and have fork-stable static
|
// Sanity: every inlined opcode must be defined and have fork-stable static
|
||||||
// gas / stack bounds across all forks where it appears (that is what makes
|
// gas / stack bounds across all forks where it appears (that is what makes
|
||||||
// it safe to bake as a constant). Bail loudly otherwise.
|
// it safe to bake as a constant). Bail loudly otherwise.
|
||||||
for code, handler := range inlineHandler {
|
for code, handler := range inlineOps {
|
||||||
g.checkStable(code, handler, forks)
|
g.checkStable(code, handler, forks)
|
||||||
}
|
}
|
||||||
// directCall opcodes bake their static gas and stack bounds the same way, so
|
// directCallOps 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
|
// they must be fork-stable too. Dynamic gas is allowed (it is charged through
|
||||||
// the named gas function, not baked).
|
// the named gas function, not baked).
|
||||||
for code := range directCall {
|
for code := range directCallOps {
|
||||||
g.checkDirectCallStable(code, forks)
|
g.checkDirectCallStable(code, forks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -544,17 +597,17 @@ func (g *generator) checkStable(code byte, what string, forks []vm.GenFork) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDirectCallStable verifies a directCall opcode is safe to direct-call. Its static
|
// checkDirectCallStable verifies a directCallOps opcode is safe to direct-call. Its static
|
||||||
// gas and stack bounds must be the same across every fork it appears in (they are
|
// 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
|
// 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
|
// 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
|
// would otherwise be missed). Unlike checkStable it allows dynamic gas, which
|
||||||
// directCall ops carry by definition. It does not check the directCall map's names
|
// directCallOps ops carry by definition. It does not check the directCallOps map's names
|
||||||
// against the table, which the differential test covers.
|
// against the table, which the differential test covers.
|
||||||
func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
|
func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
|
||||||
spec := g.specs[code]
|
spec := g.specs[code]
|
||||||
if !spec.defined {
|
if !spec.defined {
|
||||||
fatalf("opcode %#x (directCall) is never defined", code)
|
fatalf("opcode %#x (directCallOps) is never defined", code)
|
||||||
}
|
}
|
||||||
var exec, dyn, mem string
|
var exec, dyn, mem string
|
||||||
seen := false
|
seen := false
|
||||||
|
|
@ -564,7 +617,7 @@ func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if o.ConstantGas != spec.constGas || o.MinStack != spec.minStack || o.MaxStack != spec.maxStack {
|
if o.ConstantGas != spec.constGas || o.MinStack != spec.minStack || o.MaxStack != spec.maxStack {
|
||||||
fatalf("opcode %#x (%s) is in directCall but not fork-stable (fork %s): static gas or stack bounds vary, cannot bake", code, spec.name, fork.Name)
|
fatalf("opcode %#x (%s) is in directCallOps but not fork-stable (fork %s): static gas or stack bounds vary, cannot bake", code, spec.name, fork.Name)
|
||||||
}
|
}
|
||||||
// Handler, gas and memory functions must match across forks too, or
|
// Handler, gas and memory functions must match across forks too, or
|
||||||
// direct-calling them by name would skip a fork that swapped one. Names
|
// direct-calling them by name would skip a fork that swapped one. Names
|
||||||
|
|
@ -573,7 +626,7 @@ func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
|
||||||
if !seen {
|
if !seen {
|
||||||
exec, dyn, mem, seen = o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, true
|
exec, dyn, mem, seen = o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, true
|
||||||
} else if o.ExecuteFn != exec || o.DynamicGasFn != dyn || o.MemorySizeFn != mem {
|
} else if o.ExecuteFn != exec || o.DynamicGasFn != dyn || o.MemorySizeFn != mem {
|
||||||
fatalf("opcode %#x (%s) is in directCall but its functions vary by fork (fork %s): got %s/%s/%s, want %s/%s/%s, cannot direct-call",
|
fatalf("opcode %#x (%s) is in directCallOps but its functions vary by fork (fork %s): got %s/%s/%s, want %s/%s/%s, cannot direct-call",
|
||||||
code, spec.name, fork.Name, o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, exec, dyn, mem)
|
code, spec.name, fork.Name, o.ExecuteFn, o.DynamicGasFn, o.MemorySizeFn, exec, dyn, mem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -616,12 +669,7 @@ func (g *generator) emitGasCheck(spec opSpec) {
|
||||||
if spec.constGas == 0 {
|
if spec.constGas == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.p(`
|
g.p("%s", g.inlineGasBody("chargeRegularOnly", int(spec.constGas)))
|
||||||
if contract.Gas.RegularGas < %d {
|
|
||||||
return nil, ErrOutOfGas
|
|
||||||
}
|
|
||||||
contract.Gas.RegularGas -= %d
|
|
||||||
`, spec.constGas, spec.constGas)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitOpBody emits the stack/gas guards and the opcode body (the portion that runs
|
// emitOpBody emits the stack/gas guards and the opcode body (the portion that runs
|
||||||
|
|
@ -647,27 +695,27 @@ func (g *generator) emitOpBody(code byte) {
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch h := inlineHandler[code]; h {
|
switch h := inlineOps[code]; h {
|
||||||
case "makePush": // PUSH3-PUSH32: splice makePush(size, size)
|
case "makePush": // PUSH3-PUSH32: splice makePush(size, size)
|
||||||
n := int(code) - 0x5f
|
n := int(code) - 0x5f
|
||||||
g.p("%s", g.inlineFactoryBody("makePush", n, n))
|
g.p("%s", g.inlineOpcodeFactoryBody("makePush", n, n))
|
||||||
case "makeDup": // DUP1-DUP16: splice makeDup(n)
|
case "makeDup": // DUP1-DUP16: splice makeDup(n)
|
||||||
g.p("%s", g.inlineFactoryBody("makeDup", int(code)-0x7f))
|
g.p("%s", g.inlineOpcodeFactoryBody("makeDup", int(code)-0x7f))
|
||||||
default: // the rest: splice the opXxx handler body
|
default: // the rest: splice the opXxx handler body
|
||||||
g.p("%s", g.inlineBody(h))
|
g.p("%s", g.inlineOpcodeBody(h))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generator) emitInlineOp(code byte) {
|
func (g *generator) emitInlineOp(code byte) {
|
||||||
spec := g.specs[code]
|
spec := g.specs[code]
|
||||||
g.p("case %s:\n", spec.name)
|
g.p("case %s:\n", spec.name)
|
||||||
if spec.introF == "" {
|
if spec.fork == "" {
|
||||||
g.emitOpBody(code)
|
g.emitOpBody(code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Fork-gated: run the inlined body only when the opcode is active for the
|
// Fork-gated: run the inlined body only when the opcode is active for the
|
||||||
// current fork. Otherwise mirror the legacy loop's undefined-opcode handling.
|
// current fork. Otherwise mirror the legacy loop's undefined-opcode handling.
|
||||||
g.p("if rules.%s {\n", spec.introF)
|
g.p("if rules.%s {\n", spec.fork)
|
||||||
g.emitOpBody(code)
|
g.emitOpBody(code)
|
||||||
g.p("}\n")
|
g.p("}\n")
|
||||||
g.p(`
|
g.p(`
|
||||||
|
|
@ -679,10 +727,10 @@ func (g *generator) emitInlineOp(code byte) {
|
||||||
// emitDirectCallOp emits an opcode case identical to the default case, except
|
// emitDirectCallOp emits an opcode case identical to the default case, except
|
||||||
// the handler, dynamic-gas, and memory-size functions are called by name
|
// the handler, dynamic-gas, and memory-size functions are called by name
|
||||||
// rather than through the indirect operation.* table pointers. Valid only for
|
// rather than through the indirect operation.* table pointers. Valid only for
|
||||||
// fork-invariant ops (see directCall).
|
// fork-invariant ops (see directCallOps).
|
||||||
func (g *generator) emitDirectCallOp(code byte) {
|
func (g *generator) emitDirectCallOp(code byte) {
|
||||||
spec := g.specs[code]
|
spec := g.specs[code]
|
||||||
fns := directCall[code]
|
fns := directCallOps[code]
|
||||||
g.p("case %s:\n", spec.name)
|
g.p("case %s:\n", spec.name)
|
||||||
g.emitStackChecks(spec)
|
g.emitStackChecks(spec)
|
||||||
g.emitGasCheck(spec)
|
g.emitGasCheck(spec)
|
||||||
|
|
@ -706,10 +754,13 @@ func (g *generator) emitDirectCallOp(code byte) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err)
|
return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err)
|
||||||
}
|
}
|
||||||
if contract.Gas.RegularGas < dynamicCost.RegularGas {
|
if dynamicCost.StateGas == 0 {
|
||||||
|
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if !contract.Gas.charge(dynamicCost) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
contract.Gas.RegularGas -= dynamicCost.RegularGas
|
|
||||||
if memorySize > 0 {
|
if memorySize > 0 {
|
||||||
mem.Resize(memorySize)
|
mem.Resize(memorySize)
|
||||||
}
|
}
|
||||||
|
|
@ -735,10 +786,9 @@ func (g *generator) emitDefault() {
|
||||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||||
}
|
}
|
||||||
cost := operation.constantGas
|
cost := operation.constantGas
|
||||||
if contract.Gas.RegularGas < cost {
|
if err := contract.Gas.chargeRegularOnly(cost); err != nil {
|
||||||
return nil, ErrOutOfGas
|
return nil, err
|
||||||
}
|
}
|
||||||
contract.Gas.RegularGas -= cost
|
|
||||||
var memorySize uint64
|
var memorySize uint64
|
||||||
if operation.dynamicGas != nil {
|
if operation.dynamicGas != nil {
|
||||||
if operation.memorySize != nil {
|
if operation.memorySize != nil {
|
||||||
|
|
@ -755,10 +805,13 @@ func (g *generator) emitDefault() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err)
|
return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err)
|
||||||
}
|
}
|
||||||
if contract.Gas.RegularGas < dynamicCost.RegularGas {
|
if dynamicCost.StateGas == 0 {
|
||||||
|
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if !contract.Gas.charge(dynamicCost) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
contract.Gas.RegularGas -= dynamicCost.RegularGas
|
|
||||||
}
|
}
|
||||||
if memorySize > 0 {
|
if memorySize > 0 {
|
||||||
mem.Resize(memorySize)
|
mem.Resize(memorySize)
|
||||||
|
|
@ -829,9 +882,9 @@ func (g *generator) emitFile() {
|
||||||
// Inlined cases, in opcode order for readability.
|
// Inlined cases, in opcode order for readability.
|
||||||
for code := 0; code < 256; code++ {
|
for code := 0; code < 256; code++ {
|
||||||
b := byte(code)
|
b := byte(code)
|
||||||
if _, named := inlineHandler[b]; named {
|
if _, named := inlineOps[b]; named {
|
||||||
g.emitInlineOp(b)
|
g.emitInlineOp(b)
|
||||||
} else if _, dc := directCall[b]; dc {
|
} else if _, dc := directCallOps[b]; dc {
|
||||||
g.emitDirectCallOp(b)
|
g.emitDirectCallOp(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -857,8 +910,8 @@ func main() {
|
||||||
}
|
}
|
||||||
vmDir := filepath.Dir(filepath.Dir(self)) // .../core/vm/gen -> .../core/vm
|
vmDir := filepath.Dir(filepath.Dir(self)) // .../core/vm/gen -> .../core/vm
|
||||||
|
|
||||||
fset, handlers, stackHelpers := parseHandlers(vmDir)
|
fset, handlers, stackHelpers, gasHelpers := parseHandlers(vmDir)
|
||||||
g := &generator{fset: fset, handlers: handlers, stackHelpers: stackHelpers, buf: new(bytes.Buffer)}
|
g := &generator{fset: fset, handlers: handlers, stackHelpers: stackHelpers, gasHelpers: gasHelpers, buf: new(bytes.Buffer)}
|
||||||
g.deriveSpecs(vm.GenForks())
|
g.deriveSpecs(vm.GenForks())
|
||||||
g.emitFile()
|
g.emitFile()
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -103,6 +103,14 @@ var diffForks = func() []struct {
|
||||||
preCon.ArrowGlacierBlock = nil
|
preCon.ArrowGlacierBlock = nil
|
||||||
preCon.GrayGlacierBlock = nil
|
preCon.GrayGlacierBlock = nil
|
||||||
|
|
||||||
|
// amsterdam: Merged plus the Amsterdam (EIP-8037) timestamp, so the diff test
|
||||||
|
// exercises the multidimensional gas accounting (regular + state gas). Without
|
||||||
|
// this lane a state-gas charging divergence between the two interpreters would
|
||||||
|
// go unnoticed.
|
||||||
|
ams := *params.MergedTestChainConfig
|
||||||
|
amsTime := uint64(0)
|
||||||
|
ams.AmsterdamTime = &amsTime
|
||||||
|
|
||||||
return []struct {
|
return []struct {
|
||||||
name string
|
name string
|
||||||
cfg *params.ChainConfig
|
cfg *params.ChainConfig
|
||||||
|
|
@ -112,6 +120,7 @@ var diffForks = func() []struct {
|
||||||
{"Byzantium", &preCon, false},
|
{"Byzantium", &preCon, false},
|
||||||
{"London", params.TestChainConfig, false},
|
{"London", params.TestChainConfig, false},
|
||||||
{"Merged", params.MergedTestChainConfig, true},
|
{"Merged", params.MergedTestChainConfig, true},
|
||||||
|
{"Amsterdam", &ams, true},
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -295,6 +304,9 @@ func diffBlockCtx(merged bool) BlockContext {
|
||||||
GasLimit: 30_000_000,
|
GasLimit: 30_000_000,
|
||||||
BaseFee: big.NewInt(7),
|
BaseFee: big.NewInt(7),
|
||||||
BlobBaseFee: big.NewInt(3),
|
BlobBaseFee: big.NewInt(3),
|
||||||
|
// Price state gas as mainnet does (see core.NewEVMBlockContext), so the
|
||||||
|
// Amsterdam diff lane exercises the EIP-8037 state-gas charging path.
|
||||||
|
CostPerStateByte: params.CostPerStateByte,
|
||||||
}
|
}
|
||||||
if merged {
|
if merged {
|
||||||
h := common.HexToHash("0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")
|
h := common.HexToHash("0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20")
|
||||||
|
|
|
||||||
|
|
@ -217,8 +217,8 @@ 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 !contract.Gas.ChargeRegularOnly(cost) {
|
if err := contract.Gas.chargeRegularOnly(cost); err != nil {
|
||||||
return nil, ErrOutOfGas
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||||
|
|
@ -248,8 +248,8 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
|
||||||
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
||||||
}
|
}
|
||||||
if dynamicCost.StateGas == 0 {
|
if dynamicCost.StateGas == 0 {
|
||||||
if !contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas) {
|
if err := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); err != nil {
|
||||||
return nil, ErrOutOfGas
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if !contract.Gas.charge(dynamicCost) {
|
} else if !contract.Gas.charge(dynamicCost) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue