mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 19:30:44 +00:00
core/vm, core/vm/gen: build the default case from the shared stack and gas emitters
This commit is contained in:
parent
bfa0bfdc7c
commit
834f7017aa
2 changed files with 48 additions and 70 deletions
|
|
@ -73,10 +73,6 @@ var inlineOps = func() map[byte]string {
|
||||||
0x1b: "opSHL", 0x1c: "opSHR", 0x1d: "opSAR", 0x1e: "opCLZ",
|
0x1b: "opSHL", 0x1c: "opSHR", 0x1d: "opSAR", 0x1e: "opCLZ",
|
||||||
0x50: "opPop", 0x56: "opJump", 0x57: "opJumpi", 0x58: "opPc", 0x59: "opMsize", 0x5b: "opJumpdest",
|
0x50: "opPop", 0x56: "opJump", 0x57: "opJumpi", 0x58: "opPc", 0x59: "opMsize", 0x5b: "opJumpdest",
|
||||||
0x5f: "opPush0", 0x60: "opPush1", 0x61: "opPush2",
|
0x5f: "opPush0", 0x60: "opPush1", 0x61: "opPush2",
|
||||||
0x90: "opSwap1", 0x91: "opSwap2", 0x92: "opSwap3", 0x93: "opSwap4",
|
|
||||||
0x94: "opSwap5", 0x95: "opSwap6", 0x96: "opSwap7", 0x97: "opSwap8",
|
|
||||||
0x98: "opSwap9", 0x99: "opSwap10", 0x9a: "opSwap11", 0x9b: "opSwap12",
|
|
||||||
0x9c: "opSwap13", 0x9d: "opSwap14", 0x9e: "opSwap15", 0x9f: "opSwap16",
|
|
||||||
}
|
}
|
||||||
for code := 0x62; code <= 0x7f; code++ { // PUSH3-PUSH32
|
for code := 0x62; code <= 0x7f; code++ { // PUSH3-PUSH32
|
||||||
m[byte(code)] = "makePush"
|
m[byte(code)] = "makePush"
|
||||||
|
|
@ -84,6 +80,9 @@ var inlineOps = func() map[byte]string {
|
||||||
for code := 0x80; code <= 0x8f; code++ { // DUP1-DUP16
|
for code := 0x80; code <= 0x8f; code++ { // DUP1-DUP16
|
||||||
m[byte(code)] = "makeDup"
|
m[byte(code)] = "makeDup"
|
||||||
}
|
}
|
||||||
|
for code := 0x90; code <= 0x9f; code++ { // SWAP1-SWAP16
|
||||||
|
m[byte(code)] = fmt.Sprintf("opSwap%d", code-0x8f)
|
||||||
|
}
|
||||||
return m
|
return m
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -91,17 +90,7 @@ var inlineOps = func() map[byte]string {
|
||||||
// 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
|
||||||
// in the default case. Go inlines the plain functions, and the var-aliased gas
|
// in the default case.
|
||||||
// funcs (gasMLoad and friends alias pureMemoryGascost) at least skip the
|
|
||||||
// table load. Measured at ~3.4% on snailtracer (p=0.000). Fork-varying ops
|
|
||||||
// (CALL/SSTORE/SLOAD and friends) do not qualify and stay on the per-fork
|
|
||||||
// table in the default case.
|
|
||||||
//
|
|
||||||
// opcode → {handler, dynamicGas, memorySize}
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// and regresses tiny benchmarks through code layout, for negligible gain.
|
|
||||||
var directCallOps = 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
|
||||||
|
|
@ -113,7 +102,7 @@ var directCallOps = 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"
|
||||||
fork string // params.Rules field activating it, empty for Frontier (always on)
|
fork string
|
||||||
constGas uint64
|
constGas uint64
|
||||||
minStack int
|
minStack int
|
||||||
maxStack int
|
maxStack int
|
||||||
|
|
@ -283,15 +272,6 @@ func (g *generator) rewriteOpcodeReturns(src string) string {
|
||||||
for _, line := range strings.Split(src, "\n") {
|
for _, line := range strings.Split(src, "\n") {
|
||||||
if m := opcodeReturnRe.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
|
|
||||||
// halting inlined op (JUMPI on an invalid jump, say) returns stale
|
|
||||||
// res from an earlier res-setting op such as a DELEGATECALL. The
|
|
||||||
// fuzzer caught exactly that bug. The success path advances pc and
|
|
||||||
// continues without writing res or err: stale res on a continuing
|
|
||||||
// op is always overwritten by whichever op ends the run, and err
|
|
||||||
// stays nil through normal iteration. Keeping these stores off the
|
|
||||||
// success path avoids growing every inlined case, which regresses
|
|
||||||
// tiny, layout-sensitive benchmarks.
|
|
||||||
if r1 == "nil" {
|
if r1 == "nil" {
|
||||||
out.WriteString(indent + "pc++\n")
|
out.WriteString(indent + "pc++\n")
|
||||||
out.WriteString(indent + "continue mainLoop\n")
|
out.WriteString(indent + "continue mainLoop\n")
|
||||||
|
|
@ -315,7 +295,7 @@ var gasReturnRe = regexp.MustCompile(`^(\s*)return\s+(\S.*)$`)
|
||||||
// `return nil` is dropped so the opcode falls through to its remaining steps (see
|
// `return nil` is dropped so the opcode falls through to its remaining steps (see
|
||||||
// rewriteGasReturns). The receiver and parameter are substituted textually on
|
// rewriteGasReturns). The receiver and parameter are substituted textually on
|
||||||
// word boundaries, which cannot touch fields like RegularGas.
|
// word boundaries, which cannot touch fields like RegularGas.
|
||||||
func (g *generator) inlineGasBody(name string, arg int) string {
|
func (g *generator) inlineGasBody(name, amount string) string {
|
||||||
fn := g.gasHelpers[name]
|
fn := g.gasHelpers[name]
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
fatalf("no gas helper %q to inline", name)
|
fatalf("no gas helper %q to inline", name)
|
||||||
|
|
@ -326,7 +306,7 @@ func (g *generator) inlineGasBody(name string, arg int) string {
|
||||||
}
|
}
|
||||||
src := g.renderAst(fn.Body.List)
|
src := g.renderAst(fn.Body.List)
|
||||||
src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas")
|
src = regexp.MustCompile(`\b`+recvName(fn)+`\b`).ReplaceAllString(src, "contract.Gas")
|
||||||
src = substParams(src, map[string]int{names[0]: arg})
|
src = regexp.MustCompile(`\b`+names[0]+`\b`).ReplaceAllString(src, amount)
|
||||||
return g.rewriteGasReturns(src)
|
return g.rewriteGasReturns(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -636,51 +616,52 @@ func (g *generator) checkDirectCallStable(code byte, forks []vm.GenFork) {
|
||||||
// Case emission
|
// Case emission
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// emitStackChecks emits the underflow/overflow guards for a baked opcode,
|
// emitStackChecks emits the underflow/overflow guards, mirroring the legacy
|
||||||
// mirroring the legacy loop's order (stack validated before gas).
|
// loop's order (stack validated before gas). minExpr/maxExpr are the stack-bound
|
||||||
func (g *generator) emitStackChecks(spec opSpec) {
|
// expressions (baked constants on the inlined/direct paths, operation.minStack/
|
||||||
under := spec.minStack > 0
|
// operation.maxStack in the table path) and under/over select which guards to
|
||||||
over := spec.maxStack < stackLimit
|
// emit; the baked paths omit a guard whose bound is trivial.
|
||||||
|
func (g *generator) emitStackChecks(minExpr, maxExpr string, under, over bool) {
|
||||||
switch {
|
switch {
|
||||||
case under && over:
|
case under && over:
|
||||||
g.p(`
|
g.p(`
|
||||||
if sLen := stack.len(); sLen < %d {
|
if sLen := stack.len(); sLen < %s {
|
||||||
return nil, &ErrStackUnderflow{stackLen: sLen, required: %d}
|
return nil, &ErrStackUnderflow{stackLen: sLen, required: %s}
|
||||||
} else if sLen > %d {
|
} else if sLen > %s {
|
||||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: %d}
|
return nil, &ErrStackOverflow{stackLen: sLen, limit: %s}
|
||||||
}
|
}
|
||||||
`, spec.minStack, spec.minStack, spec.maxStack, spec.maxStack)
|
`, minExpr, minExpr, maxExpr, maxExpr)
|
||||||
case under:
|
case under:
|
||||||
g.p(`
|
g.p(`
|
||||||
if sLen := stack.len(); sLen < %d {
|
if sLen := stack.len(); sLen < %s {
|
||||||
return nil, &ErrStackUnderflow{stackLen: sLen, required: %d}
|
return nil, &ErrStackUnderflow{stackLen: sLen, required: %s}
|
||||||
}
|
}
|
||||||
`, spec.minStack, spec.minStack)
|
`, minExpr, minExpr)
|
||||||
case over:
|
case over:
|
||||||
g.p(`
|
g.p(`
|
||||||
if sLen := stack.len(); sLen > %d {
|
if sLen := stack.len(); sLen > %s {
|
||||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: %d}
|
return nil, &ErrStackOverflow{stackLen: sLen, limit: %s}
|
||||||
}
|
}
|
||||||
`, spec.maxStack, spec.maxStack)
|
`, maxExpr, maxExpr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitStaticGas charges an opcode's constant gas by splicing the chargeRegularOnly
|
// emitStaticGas charges static gas by splicing the chargeRegularOnly body inline
|
||||||
// body inline (call-free, for the hot path); a zero constant emits nothing. It is
|
// (call-free) for amount: a baked constant on the inlined and direct-call paths,
|
||||||
// the static-gas counterpart to emitDynamicGas.
|
// operation.constantGas in the table path. It is the static-gas counterpart to
|
||||||
func (g *generator) emitStaticGas(spec opSpec) {
|
// emitDynamicGas.
|
||||||
if spec.constGas == 0 {
|
func (g *generator) emitStaticGas(amount string) {
|
||||||
return
|
g.p("%s", g.inlineGasBody("chargeRegularOnly", amount))
|
||||||
}
|
|
||||||
g.p("%s", g.inlineGasBody("chargeRegularOnly", int(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
|
||||||
// when the opcode is active for the current fork).
|
// when the opcode is active for the current fork).
|
||||||
func (g *generator) emitOpBody(code byte) {
|
func (g *generator) emitOpBody(code byte) {
|
||||||
spec := g.specs[code]
|
spec := g.specs[code]
|
||||||
g.emitStackChecks(spec)
|
g.emitStackChecks(fmt.Sprint(spec.minStack), fmt.Sprint(spec.maxStack), spec.minStack > 0, spec.maxStack < stackLimit)
|
||||||
g.emitStaticGas(spec)
|
if spec.constGas != 0 {
|
||||||
|
g.emitStaticGas(fmt.Sprint(spec.constGas))
|
||||||
|
}
|
||||||
|
|
||||||
// PUSH1-PUSH32 swap their execute function under EIP-4762 (verkle) to charge
|
// PUSH1-PUSH32 swap their execute function under EIP-4762 (verkle) to charge
|
||||||
// code-chunk gas on the immediate bytes. Defer to the table handler there.
|
// code-chunk gas on the immediate bytes. Defer to the table handler there.
|
||||||
|
|
@ -755,8 +736,10 @@ func (g *generator) emitDirectCallOp(code byte) {
|
||||||
spec := g.specs[code]
|
spec := g.specs[code]
|
||||||
fns := directCallOps[code]
|
fns := directCallOps[code]
|
||||||
g.p("case %s:\n", spec.name)
|
g.p("case %s:\n", spec.name)
|
||||||
g.emitStackChecks(spec)
|
g.emitStackChecks(fmt.Sprint(spec.minStack), fmt.Sprint(spec.maxStack), spec.minStack > 0, spec.maxStack < stackLimit)
|
||||||
g.emitStaticGas(spec)
|
if spec.constGas != 0 {
|
||||||
|
g.emitStaticGas(fmt.Sprint(spec.constGas))
|
||||||
|
}
|
||||||
// fns[2], fns[1], fns[0] are the memory-size, dynamic-gas and handler names.
|
// fns[2], fns[1], fns[0] are the memory-size, dynamic-gas and handler names.
|
||||||
g.p(`
|
g.p(`
|
||||||
var memorySize uint64
|
var memorySize uint64
|
||||||
|
|
@ -785,21 +768,13 @@ func (g *generator) emitDirectCallOp(code byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generator) emitDefault() {
|
func (g *generator) emitDefault() {
|
||||||
// The doubled %%w and %%v below are not generator verbs: Fprintf collapses
|
|
||||||
// each %% to a single %, leaving a literal fmt.Errorf("%w: %v", ...) in the
|
|
||||||
// generated default case.
|
|
||||||
g.p(`
|
g.p(`
|
||||||
default:
|
default:
|
||||||
operation := table[op]
|
operation := table[op]
|
||||||
if sLen := stack.len(); sLen < operation.minStack {
|
`)
|
||||||
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
|
g.emitStackChecks("operation.minStack", "operation.maxStack", true, true)
|
||||||
} else if sLen > operation.maxStack {
|
g.emitStaticGas("operation.constantGas")
|
||||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
g.p(`
|
||||||
}
|
|
||||||
cost := operation.constantGas
|
|
||||||
if err := contract.Gas.chargeRegularOnly(cost); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var memorySize uint64
|
var memorySize uint64
|
||||||
if operation.dynamicGas != nil {
|
if operation.dynamicGas != nil {
|
||||||
if operation.memorySize != nil {
|
if operation.memorySize != nil {
|
||||||
|
|
|
||||||
|
|
@ -2525,10 +2525,13 @@ mainLoop:
|
||||||
} else if sLen > operation.maxStack {
|
} else if sLen > operation.maxStack {
|
||||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||||
}
|
}
|
||||||
cost := operation.constantGas
|
if contract.Gas.RegularGas < operation.constantGas {
|
||||||
if err := contract.Gas.chargeRegularOnly(cost); err != nil {
|
res, err = nil, ErrOutOfGas
|
||||||
return nil, err
|
break mainLoop
|
||||||
}
|
}
|
||||||
|
contract.Gas.RegularGas -= operation.constantGas
|
||||||
|
contract.Gas.UsedRegularGas += operation.constantGas
|
||||||
|
|
||||||
var memorySize uint64
|
var memorySize uint64
|
||||||
if operation.dynamicGas != nil {
|
if operation.dynamicGas != nil {
|
||||||
if operation.memorySize != nil {
|
if operation.memorySize != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue