diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go
index 9111bdf856..b73732daa3 100644
--- a/core/vm/gascosts.go
+++ b/core/vm/gascosts.go
@@ -94,19 +94,6 @@ func (g *GasBudget) chargeRegularOnly(r uint64) error {
return nil
}
-// chargeDynamic charges a dynamic gas cost: a regular-only deduction when there
-// is no state gas, otherwise the full multidimensional charge through the
-// reservoir.
-func (g *GasBudget) chargeDynamic(cost GasCosts) error {
- if cost.StateGas == 0 {
- return g.chargeRegularOnly(cost.RegularGas)
- }
- if !g.charge(cost) {
- return ErrOutOfGas
- }
- return nil
-}
-
// CanAfford reports whether the running budget can cover the given cost vector
// without going out of gas.
func (g GasBudget) CanAfford(cost GasCosts) bool {
diff --git a/core/vm/gen/main.go b/core/vm/gen/main.go
index 56d9d18c38..3f59e6f151 100644
--- a/core/vm/gen/main.go
+++ b/core/vm/gen/main.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-// Command gen generates core/vm/interp_gen.go, the EVM interpreter's untraced
+// Command gen generates core/vm/interpreter_gen.go, the EVM interpreter's untraced
// fast-path dispatch. The output is a switch over the opcode byte that:
//
// - inlines the hot, fork-stable opcodes (arithmetic / comparison / bitwise /
@@ -33,7 +33,7 @@
// logic stays shared rather than restated.
//
// The generated file is committed and a CI test asserts it matches `go generate`
-// output. Do not hand-edit interp_gen.go.
+// output. Do not hand-edit interpreter_gen.go.
//
// Usage: go generate ./core/vm/...
package main
@@ -61,7 +61,7 @@ const stackLimit = 1024 // params.StackLimit
// 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
// 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 emitInlineOp splices the factory body with the per-opcode size.
// Opcodes not listed here (or in directCallOps) fall through to the default case,
// which dispatches via the per-fork table.
var inlineOps = func() map[byte]string {
@@ -92,16 +92,16 @@ var inlineOps = func() map[byte]string {
// those functions by name instead of the indirect operation.* pointer calls
// in the default case.
var directCallOps = map[byte][3]string{
- 0x20: {"opKeccak256", "gasKeccak256", "memoryKeccak256"}, // KECCAK256
- 0x51: {"opMload", "gasMLoad", "memoryMLoad"}, // MLOAD
- 0x52: {"opMstore", "gasMStore", "memoryMStore"}, // MSTORE
- 0x53: {"opMstore8", "gasMStore8", "memoryMStore8"}, // MSTORE8
+ 0x20: {"opKeccak256", "gasKeccak256", "memoryKeccak256"},
+ 0x51: {"opMload", "gasMLoad", "memoryMLoad"},
+ 0x52: {"opMstore", "gasMStore", "memoryMStore"},
+ 0x53: {"opMstore8", "gasMStore8", "memoryMStore8"},
}
// opSpec holds the per-opcode constants the generator bakes (gas, stack bounds, intro fork), derived from the per-fork tables.
type opSpec struct {
defined bool
- name string // opcode mnemonic, e.g. "ADD"
+ name string
fork string
constGas uint64
minStack int
@@ -109,12 +109,12 @@ type opSpec struct {
}
type generator struct {
- fset *token.FileSet
- 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
- gasHelpers map[string]*ast.FuncDecl // (g *GasBudget) charge methods from gascosts.go, spliced by name
- specs [256]opSpec
- buf *bytes.Buffer
+ fset *token.FileSet
+ opcodeHandlers map[string]*ast.FuncDecl
+ stackHelpers map[string]*ast.FuncDecl
+ gasHelpers map[string]*ast.FuncDecl
+ specs [256]opSpec
+ buf *bytes.Buffer
}
// p is the writer of the generated file. Every line of output is appended
@@ -124,20 +124,17 @@ func (g *generator) p(format string, args ...any) {
fmt.Fprintf(g.buf, format, args...)
}
-// ---------------------------------------------------------------------------
-// Handler parsing + body splicing
-// ---------------------------------------------------------------------------
-
-// parseHandlers parses instructions.go, eips.go, stack.go and gascosts.go. It
-// returns the top-level opXxx handlers by name, the //gen:inline *Stack helper
-// methods by name (spliced into handler bodies), and the *GasBudget charge
-// methods by name (spliced directly at gas steps).
-func parseHandlers(vmDir string) (fset *token.FileSet, handlers, stackHelpers, gasHelpers map[string]*ast.FuncDecl) {
+// 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
+// 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()
- handlers = map[string]*ast.FuncDecl{}
+ opcodeHandlers = map[string]*ast.FuncDecl{}
stackHelpers = map[string]*ast.FuncDecl{}
gasHelpers = map[string]*ast.FuncDecl{}
- for _, name := range []string{"instructions.go", "eips.go", "stack.go", "gascosts.go"} {
+ for _, name := range []string{"instructions.go", "eips.go", "stack.go", "gascosts.go", "interpreter.go"} {
path := filepath.Join(vmDir, name)
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
@@ -149,16 +146,16 @@ func parseHandlers(vmDir string) (fset *token.FileSet, handlers, stackHelpers, g
continue
}
switch {
+ 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
- handlers[fn.Name.Name] = fn
+ opcodeHandlers[fn.Name.Name] = fn
case methodReceiver(fn) == "Stack" && hasInlineMarker(fn): // (s *Stack) helper tagged //gen:inline
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, gasHelpers
+ return fset, opcodeHandlers, stackHelpers, gasHelpers
}
// methodReceiver returns the receiver type name of a pointer-receiver method
@@ -194,24 +191,24 @@ func hasInlineMarker(fn *ast.FuncDecl) bool {
var opcodeReturnRe = regexp.MustCompile(`^(\s*)return\s+([^,]+),\s*(.+)$`)
-// inlineOpcodeBody returns a named handler's body, rewritten so it can be spliced
+// spliceOpcodeBody returns a named handler's body, rewritten so it can be spliced
// into the dispatch loop (see rewriteOpcodeReturns). The caller emits it with p.
-func (g *generator) inlineOpcodeBody(handler string) string {
- fn := g.handlers[handler]
+func (g *generator) spliceOpcodeBody(handler string) string {
+ fn := g.opcodeHandlers[handler]
if fn == nil {
fatalf("no handler %q to inline", handler)
}
return g.rewriteOpcodeReturns(g.inlineStackHelpers(fn.Body.List, nil))
}
-// inlineOpcodeFactoryBody splices the body of the executionFunc closure that a make*
+// spliceOpcodeFactoryBody splices the body of the executionFunc closure that a make*
// factory returns, substituting the factory's parameters with the per-opcode
// constants in args (positional, matching the factory signature). This lets
// closure-built handlers (makePush, makeDup) be derived from their single
// definition rather than restated in the generator. The caller emits the
// result with p.
-func (g *generator) inlineOpcodeFactoryBody(factory string, args ...int) string {
- fn := g.handlers[factory]
+func (g *generator) spliceOpcodeFactoryBody(factory string, args ...int) string {
+ fn := g.opcodeHandlers[factory]
if fn == nil {
fatalf("no factory %q to inline", factory)
}
@@ -288,28 +285,6 @@ func (g *generator) rewriteOpcodeReturns(src string) 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 ` 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, amount string) 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 = regexp.MustCompile(`\b`+names[0]+`\b`).ReplaceAllString(src, amount)
- return g.rewriteGasReturns(src)
-}
-
// rewriteGasReturns rewrites a spliced charge body so it runs as a gas step in
// the dispatch loop: a `return ` becomes the out-of-gas break, and the
// trailing `return nil` (success) is dropped so the opcode continues.
@@ -330,6 +305,30 @@ func (g *generator) rewriteGasReturns(src string) string {
return out.String()
}
+// rewriteStepReturns rewrites a spliced gas-step body's (value, error) returns so
+// it runs inline in the dispatch loop: a non-nil error becomes the out-of-gas
+// break; on success the value is assigned to target (or dropped when target is
+// empty) and the op falls through.
+func (g *generator) rewriteStepReturns(src, target string) string {
+ var out bytes.Buffer
+ for _, line := range strings.Split(src, "\n") {
+ if m := opcodeReturnRe.FindStringSubmatch(line); m != nil {
+ indent, val, errVal := m[1], strings.TrimSpace(m[2]), strings.TrimSpace(m[3])
+ if errVal == "nil" {
+ if target != "" {
+ out.WriteString(indent + target + " = " + val + "\n")
+ }
+ continue
+ }
+ out.WriteString(indent + "res, err = nil, " + errVal + "\n")
+ out.WriteString(indent + "break mainLoop\n")
+ continue
+ }
+ out.WriteString(line + "\n")
+ }
+ return out.String()
+}
+
// stackCall is a matched call to a tagged helper.
type stackCall struct {
helper string // helper method name
@@ -525,10 +524,10 @@ func renderInlineExpr(expr ast.Expr, subst map[string]string) string {
return ""
}
-// ---------------------------------------------------------------------------
-// Spec derivation (from the per-fork tables, via vm.GenForks)
-// ---------------------------------------------------------------------------
-
+// deriveSpecs records each opcode's bakeable constants (name, intro fork, static
+// gas, stack bounds) from the first fork that defines it, then checks that the
+// opcodes chosen for inlining and direct-calling are safe to bake by verifying
+// their constants are fork-stable (see checkStable and checkDirectCallStable).
func (g *generator) deriveSpecs(forks []vm.GenFork) {
for code := 0; code < 256; code++ {
for _, fork := range forks {
@@ -547,12 +546,13 @@ func (g *generator) deriveSpecs(forks []vm.GenFork) {
break // first fork that defines it wins (its intro fork)
}
}
- // 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
- // it safe to bake as a constant). Bail loudly otherwise.
+
+ // Every inlined opcode must be defined and have fork-stable static
+ // gas / stack bounds across all forks where it appears. Bail loudly otherwise.
for code, handler := range inlineOps {
g.checkStable(code, handler, forks)
}
+
// 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
// the named gas function, not baked).
@@ -561,6 +561,11 @@ func (g *generator) deriveSpecs(forks []vm.GenFork) {
}
}
+// checkStable verifies an opcode selected for inlining is safe to inline: it must
+// be defined, its static gas and stack bounds must be the same across every fork
+// it appears in (they are baked as constants), and it must have no dynamic gas,
+// since an inlined op charges only the baked static gas. It bails loudly
+// otherwise. `what` names the handler for the error message.
func (g *generator) checkStable(code byte, what string, forks []vm.GenFork) {
spec := g.specs[code]
if !spec.defined {
@@ -612,55 +617,74 @@ func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
}
}
-// ---------------------------------------------------------------------------
-// Case emission
-// ---------------------------------------------------------------------------
-
-// emitStackChecks emits the underflow/overflow guards, mirroring the legacy
-// loop's order (stack validated before gas). minExpr/maxExpr are the stack-bound
-// expressions (baked constants on the inlined/direct paths, operation.minStack/
-// operation.maxStack in the table path) and under/over select which guards to
-// emit; the baked paths omit a guard whose bound is trivial.
-func (g *generator) emitStackChecks(minExpr, maxExpr string, under, over bool) {
+// generateStackChecks returns the underflow/overflow guards, mirroring the legacy
+// loop's order (stack validated before gas). minExpr and maxExpr are the
+// stack-bound expressions (baked constants on the inlined/direct paths,
+// operation.minStack/maxStack in the table path). under and over select which
+// guards to emit, so the baked paths can omit a guard whose bound is trivial.
+func (g *generator) generateStackChecks(minExpr, maxExpr any, under, over bool) string {
switch {
case under && over:
- g.p(`
- if sLen := stack.len(); sLen < %s {
- return nil, &ErrStackUnderflow{stackLen: sLen, required: %s}
- } else if sLen > %s {
- return nil, &ErrStackOverflow{stackLen: sLen, limit: %s}
- }
- `, minExpr, minExpr, maxExpr, maxExpr)
+ return fmt.Sprintf(`if sLen := stack.len(); sLen < %v {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: %v}
+} else if sLen > %v {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: %v}
+}
+`, minExpr, minExpr, maxExpr, maxExpr)
case under:
- g.p(`
- if sLen := stack.len(); sLen < %s {
- return nil, &ErrStackUnderflow{stackLen: sLen, required: %s}
- }
- `, minExpr, minExpr)
+ return fmt.Sprintf(`if sLen := stack.len(); sLen < %v {
+ return nil, &ErrStackUnderflow{stackLen: sLen, required: %v}
+}
+`, minExpr, minExpr)
case over:
- g.p(`
- if sLen := stack.len(); sLen > %s {
- return nil, &ErrStackOverflow{stackLen: sLen, limit: %s}
- }
- `, maxExpr, maxExpr)
+ return fmt.Sprintf(`if sLen := stack.len(); sLen > %v {
+ return nil, &ErrStackOverflow{stackLen: sLen, limit: %v}
+}
+`, maxExpr, maxExpr)
}
+ return ""
}
-// emitStaticGas charges static gas by splicing the chargeRegularOnly body inline
-// (call-free) for amount: a baked constant on the inlined and direct-call paths,
-// operation.constantGas in the table path. It is the static-gas counterpart to
-// emitDynamicGas.
-func (g *generator) emitStaticGas(amount string) {
- g.p("%s", g.inlineGasBody("chargeRegularOnly", amount))
+// generateStaticGas returns the static-gas charge, spliced call-free from the
+// chargeRegularOnly body for amount: a baked 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
+// `return ` 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).
+func (g *generator) generateStaticGas(amount any) string {
+ fn := g.gasHelpers["chargeRegularOnly"]
+ if fn == nil {
+ fatalf("no chargeRegularOnly gas helper to inline")
+ }
+ names := paramNames(fn)
+ if len(names) != 1 {
+ 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")
+ src = regexp.MustCompile(`\b`+names[0]+`\b`).ReplaceAllString(src, fmt.Sprint(amount))
+ return g.rewriteGasReturns(src)
}
-// emitOpBody emits the stack/gas guards and the opcode body (the portion that runs
-// when the opcode is active for the current fork).
-func (g *generator) emitOpBody(code byte) {
+// emitInlineOp emits an inlined opcode case: the stack and gas guards followed by
+// the spliced opcode body. A fork-introduced opcode wraps that body in a fork gate
+// so it runs only when the opcode is active for the current fork, otherwise the
+// case mirrors the legacy loop's undefined-opcode handling.
+func (g *generator) emitInlineOp(code byte) {
spec := g.specs[code]
- g.emitStackChecks(fmt.Sprint(spec.minStack), fmt.Sprint(spec.maxStack), spec.minStack > 0, spec.maxStack < stackLimit)
+ g.p("case %s:\n", spec.name)
+ if spec.fork != "" {
+ g.p("if rules.%s {\n", spec.fork)
+ }
+
+ // stack bounds check
+ g.p("%s", g.generateStackChecks(spec.minStack, spec.maxStack, spec.minStack > 0, spec.maxStack < stackLimit))
+
+ // static gas
if spec.constGas != 0 {
- g.emitStaticGas(fmt.Sprint(spec.constGas))
+ g.p("%s", g.generateStaticGas(spec.constGas))
}
// PUSH1-PUSH32 swap their execute function under EIP-4762 (verkle) to charge
@@ -679,53 +703,26 @@ func (g *generator) emitOpBody(code byte) {
`)
}
+ // opcode body
switch h := inlineOps[code]; h {
case "makePush": // PUSH3-PUSH32: splice makePush(size, size)
n := int(code) - 0x5f
- g.p("%s", g.inlineOpcodeFactoryBody("makePush", n, n))
+ g.p("%s", g.spliceOpcodeFactoryBody("makePush", n, n))
case "makeDup": // DUP1-DUP16: splice makeDup(n)
- g.p("%s", g.inlineOpcodeFactoryBody("makeDup", int(code)-0x7f))
+ g.p("%s", g.spliceOpcodeFactoryBody("makeDup", int(code)-0x7f))
default: // the rest: splice the opXxx handler body
- g.p("%s", g.inlineOpcodeBody(h))
+ g.p("%s", g.spliceOpcodeBody(h))
}
-}
-func (g *generator) emitInlineOp(code byte) {
- spec := g.specs[code]
- g.p("case %s:\n", spec.name)
- if spec.fork == "" {
- g.emitOpBody(code)
- return
+ // If opcode is inactive for this fork, then close the gate
+ // and fall back to the legacy loop's undefined-opcode handling.
+ if spec.fork != "" {
+ g.p(`
+ }
+ res, err = opUndefined(&pc, evm, scope)
+ break mainLoop
+ `)
}
- // 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.
- g.p("if rules.%s {\n", spec.fork)
- g.emitOpBody(code)
- g.p("}\n")
- g.p(`
- res, err = opUndefined(&pc, evm, scope)
- break mainLoop
- `)
-}
-
-// emitDynamicGas emits the dynamic-gas computation and charge shared by the
-// direct-call and default cases. gasFn is the dynamic-gas function to invoke:
-// a name like "gasKeccak256" for a direct call, or "operation.dynamicGas" for
-// the table case. A computation error is wrapped as ErrOutOfGas, then the cost
-// is charged through GasBudget.chargeDynamic. The doubled %%w/%%v are not
-// generator verbs: Fprintf collapses each %% to one %, leaving a literal
-// fmt.Errorf("%w: %v", ...) in the generated code.
-func (g *generator) emitDynamicGas(gasFn string) {
- g.p(`
- var dynamicCost GasCosts
- dynamicCost, err = %s(evm, contract, stack, mem, memorySize)
- if err != nil {
- return nil, fmt.Errorf("%%w: %%v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
- `, gasFn)
}
// emitDirectCallOp emits an opcode case identical to the default case, except
@@ -736,77 +733,112 @@ func (g *generator) emitDirectCallOp(code byte) {
spec := g.specs[code]
fns := directCallOps[code]
g.p("case %s:\n", spec.name)
- g.emitStackChecks(fmt.Sprint(spec.minStack), fmt.Sprint(spec.maxStack), spec.minStack > 0, spec.maxStack < stackLimit)
+
+ // stack bounds check
+ g.p("%s", g.generateStackChecks(spec.minStack, spec.maxStack, spec.minStack > 0, spec.maxStack < stackLimit))
+
+ // static gas
if spec.constGas != 0 {
- g.emitStaticGas(fmt.Sprint(spec.constGas))
+ g.p("%s", g.generateStaticGas(spec.constGas))
}
- // fns[2], fns[1], fns[0] are the memory-size, dynamic-gas and handler names.
- g.p(`
- var memorySize uint64
- {
- memSize, overflow := %s(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
- }
- }
- `, fns[2])
- g.emitDynamicGas(fns[1])
+
+ // dynamic gas
+ g.p("\nvar memorySize uint64\n")
+
+ // Splice computeMemorySize's body, rewriting its operation.memorySize lookup to
+ // the opcode's memory-size function and its returns for the dispatch loop.
+ memSizeFn := g.gasHelpers["computeMemorySize"]
+ if memSizeFn == nil {
+ fatalf("no computeMemorySize gas helper to inline")
+ }
+ memSizeSrc := g.renderAst(memSizeFn.Body.List)
+ memSizeSrc = strings.ReplaceAll(memSizeSrc, "operation.memorySize", fns[2])
+ g.p("%s", g.rewriteStepReturns(memSizeSrc, "memorySize"))
+
+ // Splice chargeDynamicGas's body the same way, rewriting operation.dynamicGas to
+ // the opcode's gas function.
+ dynGasFn := g.gasHelpers["chargeDynamicGas"]
+ if dynGasFn == nil {
+ fatalf("no chargeDynamicGas gas helper to inline")
+ }
+ dynGasSrc := g.renderAst(dynGasFn.Body.List)
+ dynGasSrc = strings.ReplaceAll(dynGasSrc, "operation.dynamicGas", fns[1])
+ g.p("%s", g.rewriteStepReturns(dynGasSrc, ""))
+
+ // resize memory
g.p(`
if memorySize > 0 {
mem.Resize(memorySize)
}
+ `)
+
+ // call the opcode handler
+ g.p(`
res, err = %s(&pc, evm, scope)
if err != nil {
break mainLoop
}
+ `, fns[0])
+
+ // advance to the next opcode
+ g.p(`
pc++
continue mainLoop
- `, fns[0])
+ `)
}
+// emitDefault emits the switch's default case: every opcode not inlined or
+// direct-called (the fork-varying ops such as CALL, CREATE, SSTORE, SLOAD, LOG
+// and the COPY family) is dispatched through the active per-fork table, exactly
+// as the legacy loop did, so their volatile gas and opcode logic stays shared
+// rather than restated here.
func (g *generator) emitDefault() {
g.p(`
default:
operation := table[op]
`)
- g.emitStackChecks("operation.minStack", "operation.maxStack", true, true)
- g.emitStaticGas("operation.constantGas")
+ // stack bounds check
+ g.p("%s", g.generateStackChecks("operation.minStack", "operation.maxStack", true, true))
+
+ // static gas
+ g.p("%s", g.generateStaticGas("operation.constantGas"))
+
+ // dynamic gas
g.p(`
var memorySize uint64
- if operation.dynamicGas != nil {
- if operation.memorySize != nil {
- memSize, overflow := operation.memorySize(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
- }
- }
- `)
- g.emitDynamicGas("operation.dynamicGas")
- g.p(`
+ if memorySize, _, err = contract.meterDynamicGas(operation, evm, stack, mem); err != nil {
+ return nil, err
}
+ `)
+
+ // resize memory
+ g.p(`
if memorySize > 0 {
mem.Resize(memorySize)
}
+ `)
+
+ // call the opcode handler
+ g.p(`
res, err = operation.execute(&pc, evm, scope)
if err != nil {
break mainLoop
}
+ `)
+
+ // advance to the next opcode
+ g.p(`
pc++
continue mainLoop
`)
}
-// ---------------------------------------------------------------------------
-// File emission
-// ---------------------------------------------------------------------------
-
-func (g *generator) emitFile() {
+// createFile emits the whole generated file into g.buf: the header, package and
+// imports, then the execUntraced function (its locals and dispatch loop, the
+// verkle code-chunk gas, and a switch with one case per opcode built by the
+// emit* helpers). main formats the buffer and writes it to interpreter_gen.go.
+func (g *generator) createFile() {
+ // file header, package clause, and imports
g.p(`
// Code generated by core/vm/gen; DO NOT EDIT.
@@ -821,6 +853,7 @@ func (g *generator) emitFile() {
`)
+ // execUntraced: doc comment, loop-local declarations, and the dispatch loop
g.p(`
// execUntraced is the generated, tracing-free interpreter fast path. Hot,
// fork-stable opcodes are inlined with their static gas and stack bounds baked
@@ -845,18 +878,22 @@ func (g *generator) emitFile() {
_ = table
mainLoop:
for {
- if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
- contractAddr := contract.Address()
- consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
- contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
- if consumed < wanted {
- return nil, ErrOutOfGas
- }
- }
+ `)
+
+ // verkle code-chunk gas, spliced from chargeVerkleCodeChunkGas
+ ccgFn := g.gasHelpers["chargeVerkleCodeChunkGas"]
+ if ccgFn == nil {
+ fatalf("no chargeVerkleCodeChunkGas gas helper to inline")
+ }
+ g.p("%s", g.rewriteGasReturns(g.renderAst(ccgFn.Body.List)))
+
+ // fetch the opcode and open the dispatch switch
+ g.p(`
op := contract.GetOp(pc)
switch op {
`)
- // Inlined cases, in opcode order for readability.
+
+ // one case per inlined or direct-call opcode, in opcode order
for code := 0; code < 256; code++ {
b := byte(code)
if _, named := inlineOps[b]; named {
@@ -865,15 +902,21 @@ func (g *generator) emitFile() {
g.emitDirectCallOp(b)
}
}
- g.emitDefault()
- g.p("}\n") // switch
- g.p("}\n") // for
- g.p("if err == errStopToken {\nerr = nil\n}\n")
- g.p("return res, err\n")
- g.p("}\n") // func
-}
-// ---------------------------------------------------------------------------
+ // the default case: fork-varying ops via the per-fork table
+ g.emitDefault()
+
+ // close the switch and loop, clear the stop token, and return
+ g.p(`
+ }
+ }
+ if err == errStopToken {
+ err = nil
+ }
+ return res, err
+ }
+ `)
+}
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...)
@@ -887,21 +930,21 @@ func main() {
}
vmDir := filepath.Dir(filepath.Dir(self)) // .../core/vm/gen -> .../core/vm
- fset, handlers, stackHelpers, gasHelpers := parseHandlers(vmDir)
- g := &generator{fset: fset, handlers: handlers, stackHelpers: stackHelpers, gasHelpers: gasHelpers, buf: new(bytes.Buffer)}
+ fset, opcodeHandlers, stackHelpers, gasHelpers := parseHandlers(vmDir)
+ g := &generator{fset: fset, opcodeHandlers: opcodeHandlers, stackHelpers: stackHelpers, gasHelpers: gasHelpers, buf: new(bytes.Buffer)}
g.deriveSpecs(vm.GenForks())
- g.emitFile()
+ g.createFile()
formatted, err := format.Source(g.buf.Bytes())
if err != nil {
- dbg := filepath.Join(vmDir, "interp_gen.go.broken")
+ dbg := filepath.Join(vmDir, "interpreter_gen.go.broken")
os.WriteFile(dbg, g.buf.Bytes(), 0644)
fatalf("gofmt failed (%v); wrote unformatted output to %s", err, dbg)
}
- // INTERP_GEN_OUT lets the CI-match test (interp_gen_test.go) regenerate to a
+ // INTERPRETER_GEN_OUT lets the CI-match test (interpreter_gen_test.go) regenerate to a
// temporary file and diff it against the committed one, without clobbering it.
- out := filepath.Join(vmDir, "interp_gen.go")
- if env := os.Getenv("INTERP_GEN_OUT"); env != "" {
+ out := filepath.Join(vmDir, "interpreter_gen.go")
+ if env := os.Getenv("INTERPRETER_GEN_OUT"); env != "" {
out = env
}
if err := os.WriteFile(out, formatted, 0644); err != nil {
diff --git a/core/vm/genspec.go b/core/vm/genspec.go
index 21fb1e72e5..89729554db 100644
--- a/core/vm/genspec.go
+++ b/core/vm/genspec.go
@@ -32,7 +32,7 @@ import (
//
// 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),
+// reached through the active per-fork JumpTable at runtime (see interpreter_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.
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 56d38c0bbd..86589428a6 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -193,16 +193,8 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas.RegularGas
}
-
- if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
- // if the PC ends up in a new "chunk" of verkleized code, charge the
- // associated costs.
- contractAddr := contract.Address()
- consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
- contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
- if consumed < wanted {
- return nil, ErrOutOfGas
- }
+ if err = contract.chargeVerkleCodeChunkGas(evm, pc, isEIP4762); err != nil {
+ return nil, err
}
// Get the operation from the jump table and validate the stack to ensure there are
@@ -221,35 +213,15 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
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. Size the
+ // memory and charge dynamic gas here.
var memorySize uint64
- if operation.dynamicGas != nil {
- // calculate the new memory size and expand the memory to fit
- // the operation
- // Memory check needs to be done prior to evaluating the dynamic gas portion,
- // to detect calculation overflows
- if operation.memorySize != nil {
- memSize, overflow := operation.memorySize(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- // memory is expanded in words of 32 bytes. Gas
- // is also calculated in words.
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
- }
- }
- // Consume the gas and return an error if not enough gas is available.
- // cost is explicitly set so that the capture state defer method can get the proper cost
- var dynamicCost GasCosts
- dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
- cost += dynamicCost.RegularGas // for tracing
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
+ var dynamicCost GasCosts
+ memorySize, dynamicCost, err = contract.meterDynamicGas(operation, evm, stack, mem)
+ // cost is explicitly set so that the capture state defer method can get the proper cost
+ cost += dynamicCost.RegularGas // for tracing
+ if err != nil {
+ return nil, err
}
// Do tracing before potential memory expansion
@@ -284,3 +256,79 @@ func (evm *EVM) execTraced(scope *ScopeContext) (ret []byte, err error) {
return res, err
}
+
+// computeMemorySize runs an operation's memorySize function and word-aligns the
+// result, guarding overflow. The traced loop and the default case call it, and the
+// direct-call ops splice it, substituting the opcode's memory-size function for
+// operation.memorySize.
+func computeMemorySize(operation *operation, stack *Stack) (uint64, error) {
+ memSize, overflow := operation.memorySize(stack)
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ // memory is expanded in words of 32 bytes. Gas is also calculated in words.
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ return size, nil
+}
+
+// chargeDynamicGas runs an operation's dynamicGas function, treats a computation
+// failure as out of gas, and charges the cost. It returns the cost so the traced
+// loop can report it. The traced loop and the default case call it, and the
+// direct-call ops splice it, substituting the opcode's gas function for
+// operation.dynamicGas.
+func (contract *Contract) chargeDynamicGas(operation *operation, evm *EVM, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
+ dynamicCost, gerr := operation.dynamicGas(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ return dynamicCost, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ }
+ // 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 {
+ return dynamicCost, cerr
+ }
+ } else if !contract.Gas.charge(dynamicCost) {
+ return dynamicCost, ErrOutOfGas
+ }
+ return dynamicCost, nil
+}
+
+// meterDynamicGas sizes the memory an operation needs and charges its dynamic
+// gas, returning the size to expand to and the charged cost (which the traced
+// loop reports). The caller expands the memory afterward. computeMemorySize and
+// chargeDynamicGas do the work, guarded here for the generic table paths. The
+// direct-call ops splice those two by name and skip the guards.
+func (contract *Contract) meterDynamicGas(operation *operation, evm *EVM, stack *Stack, mem *Memory) (memorySize uint64, dynamicCost GasCosts, err error) {
+ if operation.dynamicGas != nil {
+ if operation.memorySize != nil {
+ if memorySize, err = computeMemorySize(operation, stack); err != nil {
+ return memorySize, dynamicCost, err
+ }
+ }
+ if dynamicCost, err = contract.chargeDynamicGas(operation, evm, stack, mem, memorySize); err != nil {
+ return memorySize, dynamicCost, err
+ }
+ }
+ return memorySize, dynamicCost, nil
+}
+
+// chargeVerkleCodeChunkGas charges EIP-4762 (verkle) witness gas for the code chunk the
+// pc sits in. It is a no-op outside verkle and for deployment or system calls.
+// isEIP4762 is passed in so the caller can hoist evm.chainRules.IsEIP4762 out of
+// the loop. The traced loop calls it, the generated loop splices its body.
+func (contract *Contract) chargeVerkleCodeChunkGas(evm *EVM, pc uint64, isEIP4762 bool) error {
+ // if the PC ends up in a new "chunk" of verkleized code, charge the
+ // associated costs.
+ if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
+ contractAddr := contract.Address()
+ consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
+ contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
+ if consumed < wanted {
+ return ErrOutOfGas
+ }
+ }
+ return nil
+}
diff --git a/core/vm/interp_gen.go b/core/vm/interpreter_gen.go
similarity index 95%
rename from core/vm/interp_gen.go
rename to core/vm/interpreter_gen.go
index fa7679f471..252b69784f 100644
--- a/core/vm/interp_gen.go
+++ b/core/vm/interpreter_gen.go
@@ -37,9 +37,11 @@ mainLoop:
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted {
- return nil, ErrOutOfGas
+ res, err = nil, ErrOutOfGas
+ break mainLoop
}
}
+
op := contract.GetOp(pc)
switch op {
case ADD:
@@ -581,23 +583,33 @@ mainLoop:
contract.Gas.UsedRegularGas += 30
var memorySize uint64
- {
- memSize, overflow := memoryKeccak256(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
+ memSize, overflow := memoryKeccak256(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := gasKeccak256(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
}
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
}
- var dynamicCost GasCosts
- dynamicCost, err = gasKeccak256(evm, contract, stack, mem, memorySize)
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
+
if memorySize > 0 {
mem.Resize(memorySize)
}
@@ -634,23 +646,33 @@ mainLoop:
contract.Gas.UsedRegularGas += 3
var memorySize uint64
- {
- memSize, overflow := memoryMLoad(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
+ memSize, overflow := memoryMLoad(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := gasMLoad(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
}
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
}
- var dynamicCost GasCosts
- dynamicCost, err = gasMLoad(evm, contract, stack, mem, memorySize)
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
+
if memorySize > 0 {
mem.Resize(memorySize)
}
@@ -672,23 +694,33 @@ mainLoop:
contract.Gas.UsedRegularGas += 3
var memorySize uint64
- {
- memSize, overflow := memoryMStore(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
+ memSize, overflow := memoryMStore(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := gasMStore(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
}
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
}
- var dynamicCost GasCosts
- dynamicCost, err = gasMStore(evm, contract, stack, mem, memorySize)
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
+
if memorySize > 0 {
mem.Resize(memorySize)
}
@@ -710,23 +742,33 @@ mainLoop:
contract.Gas.UsedRegularGas += 3
var memorySize uint64
- {
- memSize, overflow := memoryMStore8(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
+ memSize, overflow := memoryMStore8(stack)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ size, overflow := math.SafeMul(toWordSize(memSize), 32)
+ if overflow {
+ res, err = nil, ErrGasUintOverflow
+ break mainLoop
+ }
+ memorySize = size
+
+ dynamicCost, gerr := gasMStore8(evm, contract, stack, mem, memorySize)
+ if gerr != nil {
+ res, err = nil, fmt.Errorf("%w: %v", ErrOutOfGas, gerr)
+ break mainLoop
+ }
+ if dynamicCost.StateGas == 0 {
+ if cerr := contract.Gas.chargeRegularOnly(dynamicCost.RegularGas); cerr != nil {
+ res, err = nil, cerr
+ break mainLoop
}
+ } else if !contract.Gas.charge(dynamicCost) {
+ res, err = nil, ErrOutOfGas
+ break mainLoop
}
- var dynamicCost GasCosts
- dynamicCost, err = gasMStore8(evm, contract, stack, mem, memorySize)
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
+
if memorySize > 0 {
mem.Resize(memorySize)
}
@@ -2533,24 +2575,8 @@ mainLoop:
contract.Gas.UsedRegularGas += operation.constantGas
var memorySize uint64
- if operation.dynamicGas != nil {
- if operation.memorySize != nil {
- memSize, overflow := operation.memorySize(stack)
- if overflow {
- return nil, ErrGasUintOverflow
- }
- if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
- return nil, ErrGasUintOverflow
- }
- }
- var dynamicCost GasCosts
- dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
- if err != nil {
- return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
- }
- if err := contract.Gas.chargeDynamic(dynamicCost); err != nil {
- return nil, err
- }
+ if memorySize, _, err = contract.meterDynamicGas(operation, evm, stack, mem); err != nil {
+ return nil, err
}
if memorySize > 0 {
mem.Resize(memorySize)
diff --git a/core/vm/interp_gen_test.go b/core/vm/interpreter_gen_test.go
similarity index 95%
rename from core/vm/interp_gen_test.go
rename to core/vm/interpreter_gen_test.go
index 5ca2039e31..786dd14338 100644
--- a/core/vm/interp_gen_test.go
+++ b/core/vm/interpreter_gen_test.go
@@ -16,7 +16,7 @@
package vm
-// Tests for the generated interpreter dispatch (interp_gen.go): that the
+// Tests for the generated interpreter dispatch (interpreter_gen.go): that the
// committed file is up to date, that it behaves identically to the table loop,
// and that the fast path keeps its cheap stack helpers inlined.
@@ -41,7 +41,7 @@ import (
"github.com/holiman/uint256"
)
-// TestGeneratedDispatchUpToDate asserts that the committed interp_gen.go matches
+// TestGeneratedDispatchUpToDate asserts that the committed interpreter_gen.go matches
// what `go generate` (core/vm/gen) produces from the current opcode/gas/fork
// definitions. It is the CI guard against hand-edits to the generated file and
// against the generator drifting from the committed output.
@@ -49,9 +49,9 @@ func TestGeneratedDispatchUpToDate(t *testing.T) {
if testing.Short() {
t.Skip("skipping generator round-trip in -short mode")
}
- tmp := filepath.Join(t.TempDir(), "interp_gen.go")
+ tmp := filepath.Join(t.TempDir(), "interpreter_gen.go")
cmd := exec.Command("go", "run", "./gen")
- cmd.Env = append(os.Environ(), "INTERP_GEN_OUT="+tmp)
+ cmd.Env = append(os.Environ(), "INTERPRETER_GEN_OUT="+tmp)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("running generator: %v\n%s", err, out)
}
@@ -59,12 +59,12 @@ func TestGeneratedDispatchUpToDate(t *testing.T) {
if err != nil {
t.Fatalf("reading regenerated output: %v", err)
}
- want, err := os.ReadFile("interp_gen.go")
+ want, err := os.ReadFile("interpreter_gen.go")
if err != nil {
- t.Fatalf("reading committed interp_gen.go: %v", err)
+ t.Fatalf("reading committed interpreter_gen.go: %v", err)
}
if string(got) != string(want) {
- t.Fatalf("interp_gen.go is out of date; run `go generate ./core/vm/...` and commit the result")
+ t.Fatalf("interpreter_gen.go is out of date; run `go generate ./core/vm/...` and commit the result")
}
}
@@ -444,7 +444,7 @@ func markedHelpers(t *testing.T) map[string]bool {
}
// TestGeneratedFastPathHelpersExpanded asserts the generator spliced every
-// //gen:inline helper inline, so none survives as a real call in interp_gen.go.
+// //gen:inline helper inline, so none survives as a real call in interpreter_gen.go.
// Those helpers exceed the compiler's inline budget for a function as large as
// execUntraced, so a missed splice would silently drop the inlining the fast
// path exists for. It is the expand-side counterpart to
@@ -452,10 +452,10 @@ func markedHelpers(t *testing.T) map[string]bool {
// the fast path makes no real stack-helper call, the costly ones by splicing,
// the cheap ones by compiler inlining.
func TestGeneratedFastPathHelpersExpanded(t *testing.T) {
- calls := countStackCalls(t, "interp_gen.go")
+ calls := countStackCalls(t, "interpreter_gen.go")
for h := range markedHelpers(t) {
if n := calls[h]; n != 0 {
- t.Errorf("(*Stack).%s is //gen:inline but has %d residual call(s) in interp_gen.go, expected 0.\n"+
+ t.Errorf("(*Stack).%s is //gen:inline but has %d residual call(s) in interpreter_gen.go, expected 0.\n"+
"The generator did not splice it. Check it is still in inlinable shape (core/vm/gen).", h, n)
}
}
@@ -463,7 +463,7 @@ func TestGeneratedFastPathHelpersExpanded(t *testing.T) {
// TestGeneratedFastPathHelpersInlined recompiles this package with the
// compiler's inlining diagnostics on and fails if any *Stack helper call that
-// survives into interp_gen.go was not inlined. Every survivor must be a cheap
+// survives into interpreter_gen.go was not inlined. Every survivor must be a cheap
// helper (len, pop1, peek, drop) the compiler inlines into execUntraced; the
// //gen:inline helpers are spliced away and owned by the Expanded test. The
// cheap ones inline today with margin except pop1, at cost 18 against Go's
@@ -483,21 +483,21 @@ func TestGeneratedFastPathHelpersInlined(t *testing.T) {
t.Fatalf("compiling with inlining diagnostics: %v\n%s", err, out)
}
diag := string(out)
- if !strings.Contains(diag, "interp_gen.go") {
- t.Fatalf("captured no interp_gen.go diagnostics, the -m build produced nothing to check:\n%s", diag)
+ if !strings.Contains(diag, "interpreter_gen.go") {
+ t.Fatalf("captured no interpreter_gen.go diagnostics, the -m build produced nothing to check:\n%s", diag)
}
// Every surviving stack-helper call (i.e. not a //gen:inline target) must be
// inlined by the compiler.
marked := markedHelpers(t)
- for h, n := range countStackCalls(t, "interp_gen.go") {
+ for h, n := range countStackCalls(t, "interpreter_gen.go") {
if marked[h] {
continue // spliced away, owned by TestGeneratedFastPathHelpersExpanded
}
- inlinedRe := regexp.MustCompile(`interp_gen\.go.*inlining call to \(\*Stack\)\.` + regexp.QuoteMeta(h) + `\b`)
+ inlinedRe := regexp.MustCompile(`interpreter_gen\.go.*inlining call to \(\*Stack\)\.` + regexp.QuoteMeta(h) + `\b`)
inlined := len(inlinedRe.FindAllString(diag, -1))
if inlined != n {
- t.Errorf("(*Stack).%s: %d call site(s) in interp_gen.go, %d inlined into execUntraced.\n"+
+ t.Errorf("(*Stack).%s: %d call site(s) in interpreter_gen.go, %d inlined into execUntraced.\n"+
"The compiler stopped inlining it, so the fast path now pays a real call. Shrink the\n"+
"body to fit the inline budget, or tag it //gen:inline in stack.go to splice it instead.", h, n, inlined)
continue