From 681ba22ed11f63f7bfce0bd39894db4e371c981d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 23 Nov 2024 11:52:05 +0100 Subject: [PATCH] all: add '#pragma target' directive (#10) Here I add support for a new directive #pragma target for configuring the target instruction set. Opcode names are resolved against the instruction set, and choosing a target also configures use of PUSH0. --- README.md | 45 ++- asm/compiler.go | 118 ++++-- asm/compiler_eval.go | 24 +- asm/compiler_expand.go | 48 ++- asm/compiler_prog.go | 12 +- asm/error.go | 14 +- asm/testdata/compiler-tests.yaml | 114 +++++- asm/testdata/known-bytecode.yaml | 4 +- cmd/geas/geas.go | 4 +- example/4788asm.eas | 1 + example/4788asm_ctor.eas | 4 +- example/erc20/erc20.eas | 2 + example/erc20/erc20_ctor.eas | 2 + internal/ast/ast.go | 14 + internal/ast/lexer.go | 6 + internal/ast/parse.go | 24 +- internal/ast/tokentype_string.go | 7 +- internal/evm/forkdefs.go | 247 ++++++++++++ internal/evm/instruction_set.go | 200 ++++++++++ internal/evm/instruction_set_test.go | 141 +++++++ internal/evm/opcodes.go | 564 --------------------------- internal/evm/ops.go | 189 +++++++++ 22 files changed, 1135 insertions(+), 649 deletions(-) create mode 100644 internal/evm/forkdefs.go create mode 100644 internal/evm/instruction_set.go create mode 100644 internal/evm/instruction_set_test.go delete mode 100644 internal/evm/opcodes.go create mode 100644 internal/evm/ops.go diff --git a/README.md b/README.md index 5e8b67b..3ce1ea7 100644 --- a/README.md +++ b/README.md @@ -242,10 +242,50 @@ main.eas: push 2 %StoreSum ;; calling global macro defined in lib.evm +### Configuring the target instruction set + +The EVM is a changing environment. Opcodes may be added (and sometimes removed) as new +versions of the EVM are released in protocol forks. Geas is aware of EVM forks and their +respective instruction sets. + +Geas always operates on a specific EVM instruction set. It targets the latest known eth +mainnet fork by default, i.e. all opcodes available in that fork can be used, and opcodes +that have been removed in any prior fork cannot. + +Use the `#pragma target` directive to change the target instruction set. The basic syntax is + + #pragma target "name" + +where `name` is a lower-case execution-layer fork name like `homestead`, `berlin`, or `prague`. + +Here is an example. This contract uses the CHAINID instruction to check if it is running +on mainnet, and destroys itself otherwise. CHAINID became available in the "istanbul" +fork, and SELFDESTRUCT was removed in a later revision of the EVM, so this program is only +applicable to a certain range of past EVM versions. + + #pragma target "berlin" + + chainid ; [id] + push 1 ; [1, id] + eq ; [id = 1] + jumpi @mainnet ; [] + push 0x0 ; [zeroaddr] + selfdestruct ; [] + mainnet: + +Note that declaring the target instruction set using `#pragma target` will not prevent the +output bytecode from running on a different EVM version, since it is just a compiler +setting. The example program above will start behaving differently from its intended +version on EVM version "cancun", because SELFDESTRUCT was turned into SENDALL in that +fork. It may even stop working entirely in a later fork. + +`#pragma target` can only appear in the program once. It cannot be placed in an include +file. You have to put the directive in the main program file. + ### #assemble When writing contract constructors and advanced CALL scenarios, it can be necessary to -include subprogram bytecode as-is. The `#assemble` directive can do this for you. +include subprogram bytecode as-is. The `#assemble` directive does this for you. Using `#assemble` runs the assembler on the specified file, and includes the resulting bytecode into the current program. Labels of the subprogram will start at offset zero. @@ -261,5 +301,8 @@ Unlike with `#include`, global definitions of the subprogram are not imported. #assemble "subprogram.eas" .end +If a target instruction set is configured with `#pragma target`, it will also be used for +assembling the subprogram. However, the subprogram file can override the instruction set +using its own `#pragma target` directive. [^1]: Under no circumstances must it be called the geth assembler. diff --git a/asm/compiler.go b/asm/compiler.go index 8567619..542c3fa 100644 --- a/asm/compiler.go +++ b/asm/compiler.go @@ -27,6 +27,7 @@ import ( "strings" "github.com/fjl/geas/internal/ast" + "github.com/fjl/geas/internal/evm" ) // Compiler performs the assembling. @@ -35,7 +36,7 @@ type Compiler struct { lexDebug bool maxIncDepth int maxErrors int - usePush0 bool + defaultFork string globals *globalScope errors []error @@ -51,7 +52,7 @@ func NewCompiler(fsys fs.FS) *Compiler { includes: make(map[*ast.IncludeSt]*ast.Document), maxIncDepth: 128, maxErrors: 10, - usePush0: true, + defaultFork: evm.LatestFork, } } @@ -60,10 +61,9 @@ func (c *Compiler) SetDebugLexer(on bool) { c.lexDebug = on } -// SetUsePush0 enables/disables use of the PUSH0 instruction. -// It's on by default. -func (c *Compiler) SetUsePush0(on bool) { - c.usePush0 = on +// SetDefaultFork sets the EVM instruction set used by default. +func (c *Compiler) SetDefaultFork(f string) { + c.defaultFork = f } // SetDebugLexer enables/disables printing of the token stream to stdout. @@ -135,10 +135,6 @@ func (c *Compiler) addErrors(errs []error) { // compile is the toplevel entry point into the compiler. func (c *Compiler) compile(doc *ast.Document) (output []byte) { - prevGlobals := c.globals - c.globals = newGlobalScope() - defer func() { c.globals = prevGlobals }() - defer func() { panicking := recover() if panicking != nil && panicking != errCancelCompilation { @@ -146,11 +142,19 @@ func (c *Compiler) compile(doc *ast.Document) (output []byte) { } }() + c.globals = newGlobalScope() + prog := newCompilerProg(doc) + // First, load all #include files and register their definitions. - c.processIncludes(doc, nil) + // This also configures the instruction set if specified by a #pragma. + c.processIncludes(doc, prog, nil) + + // Choose latest eth mainnet instruction set if not configured. + if prog.evm == nil { + prog.evm = evm.FindInstructionSet(c.defaultFork) + } // Next, the AST document tree is expanded into a flat list of instructions. - prog := newCompilerProg(doc) c.expand(doc, prog) if prog.cur != prog.toplevel { panic("section stack was not unwound by expansion") @@ -184,38 +188,53 @@ func (c *Compiler) compile(doc *ast.Document) (output []byte) { } // processIncludes reads all #included documents. -func (c *Compiler) processIncludes(doc *ast.Document, stack []ast.Statement) { +func (c *Compiler) processIncludes(doc *ast.Document, prog *compilerProg, stack []ast.Statement) { errs := c.globals.registerDefinitions(doc) c.addErrors(errs) var list []*ast.IncludeSt - for _, inst := range doc.Statements { - inc, ok := inst.(*ast.IncludeSt) - if !ok { - continue - } - file, err := resolveRelative(doc.File, inc.Filename) - if err != nil { - c.addError(inst, err) - continue - } - incdoc := c.parseIncludeFile(file, inc, len(stack)+1) - if incdoc == nil { - continue // there were parse errors + for _, st := range doc.Statements { + switch st := st.(type) { + case *ast.IncludeSt: + file, err := resolveRelative(doc.File, st.Filename) + if err != nil { + c.addError(st, err) + continue + } + incdoc := c.parseIncludeFile(file, st, len(stack)+1) + if incdoc != nil { + c.includes[st] = incdoc + list = append(list, st) + } + + case *ast.PragmaSt: + switch st.Option { + case "target": + if len(stack) != 0 { + c.addError(st, ecPragmaTargetInIncludeFile) + } + if prog.evm != nil { + c.addError(st, ecPragmaTargetConflict) + } + prog.evm = evm.FindInstructionSet(st.Value) + if prog.evm == nil { + c.addError(st, fmt.Errorf("%w %q", ecPragmaTargetUnknown, st.Value)) + } + default: + c.addError(st, fmt.Errorf("%w %s", ecUnknownPragma, st.Option)) + } } - c.includes[inc] = incdoc - list = append(list, inc) } // Process includes in macros. for _, m := range doc.InstrMacros() { - c.processIncludes(m.Body, append(stack, m)) + c.processIncludes(m.Body, prog, append(stack, m)) } // Recurse. for _, inst := range list { incdoc := c.includes[inst] - c.processIncludes(incdoc, append(stack, inst)) + c.processIncludes(incdoc, prog, append(stack, inst)) } } @@ -264,19 +283,48 @@ func (c *Compiler) generateOutput(prog *compilerProg) []byte { if len(c.errors) > 0 { return nil } + var output []byte for _, inst := range prog.iterInstructions() { if len(output) != inst.pc { panic(fmt.Sprintf("BUG: instruction pc=%d, but output has size %d", inst.pc, len(output))) } - if inst.op != "" { - opcode, ok := inst.opcode() - if !ok { + + switch { + case isPush(inst.op): + if inst.pushSize > 32 { + panic("BUG: pushSize > 32") + } + if len(inst.data) > inst.pushSize { + panic(fmt.Sprintf("BUG: push inst.data %d > inst.pushSize %d", len(inst.data), inst.pushSize)) + } + + // resolve the op + var op *evm.Op + if inst.op == "PUSH" { + op = prog.evm.PushBySize(inst.pushSize) + } else { + op = prog.evm.OpByName(inst.op) + } + if op == nil { + panic(fmt.Sprintf("BUG: opcode for %q (size %d) not found", inst.op, inst.pushSize)) + } + + // Add opcode and data padding to output. + output = append(output, op.Code) + if len(inst.data) < inst.pushSize { + output = append(output, make([]byte, inst.pushSize-len(inst.data))...) + } + + case inst.op != "": + op := prog.evm.OpByName(inst.op) + if op == nil { c.addError(inst.ast, fmt.Errorf("%w %s", ecUnknownOpcode, inst.op)) - continue } - output = append(output, byte(opcode)) + output = append(output, op.Code) } + + // Instruction data is always added to output. output = append(output, inst.data...) } return output diff --git a/asm/compiler_eval.go b/asm/compiler_eval.go index ac34610..659c031 100644 --- a/asm/compiler_eval.go +++ b/asm/compiler_eval.go @@ -48,7 +48,7 @@ func (c *Compiler) assignInitialPushSizes(e *evaluator, prog *compilerProg) { c.addError(inst.ast, err) continue } - if err := c.assignPushArg(inst, v, true); err != nil { + if err := prog.assignPushArg(inst, v, true); err != nil { c.addError(inst.ast, err) continue } @@ -91,7 +91,7 @@ func (c *Compiler) assignArgs(e *evaluator, prog *compilerProg) (inst *instructi if err != nil { return inst, err } - if err := c.assignPushArg(inst, v, false); err != nil { + if err := prog.assignPushArg(inst, v, false); err != nil { return inst, err } } @@ -103,7 +103,7 @@ func (c *Compiler) assignArgs(e *evaluator, prog *compilerProg) (inst *instructi // // If setSize is true, the pushSize of variable-size "PUSH" instructions will be assigned // based on the value. -func (c *Compiler) assignPushArg(inst *instruction, v *big.Int, setSize bool) error { +func (prog *compilerProg) assignPushArg(inst *instruction, v *big.Int, setSize bool) error { if v.Sign() < 0 { return ecNegativeResult } @@ -115,7 +115,7 @@ func (c *Compiler) assignPushArg(inst *instruction, v *big.Int, setSize bool) er _, hasExplicitSize := inst.explicitPushSize() if setSize && !hasExplicitSize { - inst.pushSize = c.autoPushSize(b) + inst.pushSize = prog.autoPushSize(b) } if len(b) > inst.pushSize { if !hasExplicitSize { @@ -124,22 +124,18 @@ func (c *Compiler) assignPushArg(inst *instruction, v *big.Int, setSize bool) er return ecFixedSizePushOverflow } - // Store data padded. - inst.data = make([]byte, inst.pushSize) - copy(inst.data[len(inst.data)-len(b):], b) + // Store data. Note there is no padding applied here. + // Padding will be added at the bytecode output stage. + inst.data = b return nil } -func (c *Compiler) autoPushSize(value []byte) int { +func (prog *compilerProg) autoPushSize(value []byte) int { if len(value) > 32 { panic("value too big") } - if len(value) == 0 { - if c.usePush0 { - return 0 - } else { - return 1 - } + if len(value) == 0 && !prog.evm.SupportsPush0() { + return 1 } return len(value) } diff --git a/asm/compiler_expand.go b/asm/compiler_expand.go index 4b7d28c..0738974 100644 --- a/asm/compiler_expand.go +++ b/asm/compiler_expand.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/fjl/geas/internal/ast" + "github.com/fjl/geas/internal/evm" ) // expand appends a list of AST instructions to the program. @@ -63,13 +64,7 @@ func (op opcodeStatement) expand(c *Compiler, doc *ast.Document, prog *compilerP inst := newInstruction(op, opcode) switch { - case isPush(opcode): - if opcode == "PUSH0" { - if op.Arg != nil { - return ecPushzeroWithArgument - } - break - } + case isPush(opcode) && opcode != "PUSH0": if op.Arg == nil { return ecPushWithoutArgument } @@ -78,6 +73,9 @@ func (op opcodeStatement) expand(c *Compiler, doc *ast.Document, prog *compilerP if err := c.validateJumpArg(doc, op.Arg); err != nil { return err } + if _, err := prog.resolveOp(opcode); err != nil { + return err + } // 'JUMP @label' instructions turn into 'PUSH @label' + 'JUMP'. if op.Arg != nil { push := newInstruction(op, "PUSH") @@ -85,10 +83,13 @@ func (op opcodeStatement) expand(c *Compiler, doc *ast.Document, prog *compilerP } default: - if _, ok := inst.opcode(); !ok { - return fmt.Errorf("%w %s", ecUnknownOpcode, inst.op) + if _, err := prog.resolveOp(opcode); err != nil { + return err } if op.Arg != nil { + if opcode == "PUSH0" { + return ecPushzeroWithArgument + } return ecUnexpectedArgument } } @@ -97,6 +98,27 @@ func (op opcodeStatement) expand(c *Compiler, doc *ast.Document, prog *compilerP return nil } +// resolveOp resolves an opcode name. +func (prog *compilerProg) resolveOp(op string) (*evm.Op, error) { + if op := prog.evm.OpByName(op); op != nil { + return op, nil + } + remFork := prog.evm.ForkWhereOpRemoved(op) + if remFork != "" { + return nil, fmt.Errorf("%w %s (target = %q; removed in fork %q)", ecUnknownOpcode, op, prog.evm.Name(), remFork) + } + addedForks := evm.ForksWhereOpAdded(op) + if len(addedForks) > 0 { + list := strings.Join(addedForks, ", ") + fork := "fork" + if len(addedForks) > 1 { + fork += "s" + } + return nil, fmt.Errorf("%w %s (target = %q; added in %s %q)", ecUnknownOpcode, op, prog.evm.Name(), fork, list) + } + return nil, fmt.Errorf("%w %s", ecUnknownOpcode, op) +} + // validateJumpArg checks that argument to JUMP is a defined label. func (c *Compiler) validateJumpArg(doc *ast.Document, arg ast.Expr) error { if arg == nil { @@ -216,13 +238,17 @@ func (inst assembleStatement) expand(c *Compiler, doc *ast.Document, prog *compi subc := NewCompiler(c.fsys) subc.SetIncludeDepthLimit(c.maxIncDepth) subc.SetMaxErrors(math.MaxInt) + subc.SetDefaultFork(prog.evm.Name()) file, err := resolveRelative(doc.File, inst.Filename) if err != nil { return err } - bytecode := c.CompileFile(file) - if len(c.Errors()) > 0 { + bytecode := subc.CompileFile(file) + errs := subc.Errors() + if len(errs) > 0 { + reportedErrs := errs[:min(max(c.maxErrors, 1), len(errs))] + c.errors = append(c.errors, reportedErrs...) return nil } datainst := &instruction{data: bytecode} diff --git a/asm/compiler_prog.go b/asm/compiler_prog.go index 72b09c6..795afce 100644 --- a/asm/compiler_prog.go +++ b/asm/compiler_prog.go @@ -29,6 +29,7 @@ import ( type compilerProg struct { toplevel *compilerSection cur *compilerSection + evm *evm.InstructionSet } // compilerSection is a section of the output program. @@ -161,14 +162,3 @@ func (inst *instruction) pushArg() ast.Expr { } return nil } - -// opcode returns the EVM opcode of the instruction. -func (inst *instruction) opcode() (evm.OpCode, bool) { - if isPush(inst.op) { - if inst.pushSize > 32 { - panic("BUG: pushSize > 32") - } - return evm.PUSH1 + evm.OpCode(inst.pushSize-1), true - } - return evm.OpByName(inst.op) -} diff --git a/asm/error.go b/asm/error.go index ae57221..0812c5e 100644 --- a/asm/error.go +++ b/asm/error.go @@ -55,6 +55,10 @@ const ( ecNegativeResult ecIncludeNoFS ecIncludeDepthLimit + ecUnknownPragma + ecPragmaTargetInIncludeFile + ecPragmaTargetConflict + ecPragmaTargetUnknown ) func (e compilerError) Error() string { @@ -78,7 +82,7 @@ func (e compilerError) Error() string { case ecJumpToUndefinedLabel: return "JUMP to undefined label" case ecUnknownOpcode: - return "unknown opcode" + return "unknown op" case ecUndefinedVariable: return "undefined macro parameter" case ecUndefinedMacro: @@ -97,6 +101,14 @@ func (e compilerError) Error() string { return "#include not allowed" case ecIncludeDepthLimit: return "#include depth limit reached" + case ecUnknownPragma: + return "unknown #pragma" + case ecPragmaTargetInIncludeFile: + return "#pragma target cannot be used in #include'd files" + case ecPragmaTargetConflict: + return "duplicate '#pragma target ...' directive" + case ecPragmaTargetUnknown: + return "unknown #pragma target" default: return fmt.Sprintf("invalid error %d", e) } diff --git a/asm/testdata/compiler-tests.yaml b/asm/testdata/compiler-tests.yaml index f8fc961..c4ced18 100644 --- a/asm/testdata/compiler-tests.yaml +++ b/asm/testdata/compiler-tests.yaml @@ -48,6 +48,32 @@ assemble-directive-globals-not-shared: errors: - "file.eas:1: undefined macro Global" +assemble-directive-fork-inherit: + input: + code: | + #pragma target "berlin" + push 1 + #assemble "file.eas" + files: + file.eas: | + random + output: + errors: + - 'file.eas:1: unknown op RANDOM (target = "berlin"; added in fork "paris")' + +assemble-directive-fork-override: + input: + code: | + #pragma target "berlin" + push 1 + #assemble "file.eas" + files: + file.eas: | + #pragma target "paris" + random + output: + bytecode: '600144' + comments-on-label: input: code: | @@ -327,6 +353,17 @@ include-basic: output: bytecode: "6001 6001 01 6002 00" +include-pragma-fork-disallowed: + input: + code: | + #include "a.evm" + files: + a.evm: | + #pragma target "berlin" + output: + errors: + - "a.evm:1: #pragma target cannot be used in #include'd files" + include-depth-limit: input: code: '#include "a.evm"' @@ -528,13 +565,32 @@ instr-macro-variable-shadow: output: bytecode: "6001 6002" -opcode-bad-with-args: +opcode-unknown-in-fork: + input: + code: | + #pragma target "berlin" + basefee + output: + errors: + - ':2: unknown op BASEFEE (target = "berlin"; added in fork "london")' + +opcode-removed-in-fork: + input: + code: | + #pragma target "shanghai" + difficulty + output: + errors: + - ':2: unknown op DIFFICULTY (target = "shanghai"; removed in fork "paris")' + +opcode-unknown-with-args: input: code: | + #pragma target "cancun" myop(foo) output: errors: - - ':1: unknown opcode MYOP' + - ':2: unknown op MYOP' opcode-known-with-args: input: @@ -691,6 +747,24 @@ push0-implicit: output: bytecode: "5f" +# this checks that the compiler won't emit PUSH0 if the fork doesn't support it. +push0-implicit-not-in-fork: + input: + code: | + #pragma target "frontier" + push 0 + output: + bytecode: "6000" + +push0-explicit-not-in-fork: + input: + code: | + #pragma target "berlin" + push0 + output: + errors: + - ':2: unknown op PUSH0 (target = "berlin"; added in fork "shanghai")' + unicode-ident: input: code: | @@ -698,3 +772,39 @@ unicode-ident: PUSH @läbel output: bytecode: '5b6000' + +pragma-fork-dup: + input: + code: | + #pragma target "berlin" + #pragma target "berlin" + output: + errors: + - ":2: duplicate '#pragma target ...' directive" + +pragma-fork-dup-2: + input: + code: | + #pragma target "berlin" + #pragma target "cancun" + output: + errors: + - ":2: duplicate '#pragma target ...' directive" + +pragma-fork-bad-syntax: + input: + code: | + #pragma target = "berlin" + #pragma target cancun + output: + errors: + - ":1: unexpected = after #pragma target" + - ":2: #pragma option value must be string or number literal" + +pragma-unknown: + input: + code: | + #pragma something 1 + output: + errors: + - ':1: unknown #pragma something' diff --git a/asm/testdata/known-bytecode.yaml b/asm/testdata/known-bytecode.yaml index 1f234e6..74a1b6b 100644 --- a/asm/testdata/known-bytecode.yaml +++ b/asm/testdata/known-bytecode.yaml @@ -1,4 +1,4 @@ 4788asm: 3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762016da0810690815414603c575f5ffd5b62016da001545f5260205ff35b5f5ffd5b62016da042064281555f359062016da0015500 4788asm_ctor: 60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762016da0810690815414603c575f5ffd5b62016da001545f5260205ff35b5f5ffd5b62016da042064281555f359062016da0015500 -erc20: 365f80375f5160e01c806323b872dd146058578063095ea7b31460c1578063a9059cbb1461010a57806370a0823114610154578063dd62ed3e1461015f578063313ce5671461016c57806318160ddd1461016c575b5f5ffd5b604060042080546044518181116054576004355410605457604435900390556004358054604435809103909155602435805490910190556024356004356044355f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa3005b60245160045160245233600452604060042080549091019055600435336024355f527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3005b3354602451818111605457900333556004518054602451019055600435336024355f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa3005b60205f600451548152f35b6040600420545f5260205ff35b -erc20_ctor: 5861271033556012803803919082908239f3365f80375f5160e01c806323b872dd146058578063095ea7b31460c1578063a9059cbb1461010a57806370a0823114610154578063dd62ed3e1461015f578063313ce5671461016c57806318160ddd1461016c575b5f5ffd5b604060042080546044518181116054576004355410605457604435900390556004358054604435809103909155602435805490910190556024356004356044355f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa3005b60245160045160245233600452604060042080549091019055600435336024355f527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3005b3354602451818111605457900333556004518054602451019055600435336024355f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa3005b60205f600451548152f35b6040600420545f5260205ff35b +erc20: 366000803760005160e01c806323b872dd14605c578063095ea7b31460c7578063a9059cbb1461011257806370a082311461015e578063dd62ed3e1461016a578063313ce5671461017957806318160ddd14610179575b60006000fd5b604060042080546044518181116056576004355410605657604435900390556004358054604435809103909155602435805490910190556024356004356044356000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3005b60245160045160245233600452604060042080549091019055600435336024356000527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560206000a3005b3354602451818111605657900333556004518054602451019055600435336024356000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3005b60206000600451548152f35b60406004205460005260206000f35b +erc20_ctor: 5861271033556012803803919082908239f3366000803760005160e01c806323b872dd14605c578063095ea7b31460c7578063a9059cbb1461011257806370a082311461015e578063dd62ed3e1461016a578063313ce5671461017957806318160ddd14610179575b60006000fd5b604060042080546044518181116056576004355410605657604435900390556004358054604435809103909155602435805490910190556024356004356044356000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3005b60245160045160245233600452604060042080549091019055600435336024356000527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560206000a3005b3354602451818111605657900333556004518054602451019055600435336024356000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3005b60206000600451548152f35b60406004205460005260206000f35b diff --git a/cmd/geas/geas.go b/cmd/geas/geas.go index 32442e7..8ea4a23 100644 --- a/cmd/geas/geas.go +++ b/cmd/geas/geas.go @@ -42,9 +42,11 @@ func main() { default: exit(fmt.Errorf("too many arguments")) } + if *noPush0 { + exit(fmt.Errorf("option -no-push0 is not supported anymore")) + } c := asm.NewCompiler(os.DirFS(".")) - c.SetUsePush0(!*noPush0) bin := c.CompileFile(file) if len(c.Errors()) > 0 { for _, err := range c.Errors() { diff --git a/example/4788asm.eas b/example/4788asm.eas index 60b708d..75865cb 100644 --- a/example/4788asm.eas +++ b/example/4788asm.eas @@ -22,6 +22,7 @@ ;;; buflen to the timestamp's index in the first ring buffer. The sum will be ;;; the storage slot in the second ring buffer where it is stored. +#pragma target "cancun" ;;; ----------------------------------------------------------------------------- ;;; MACROS ---------------------------------------------------------------------- diff --git a/example/4788asm_ctor.eas b/example/4788asm_ctor.eas index 61fb8de..f7a7e11 100644 --- a/example/4788asm_ctor.eas +++ b/example/4788asm_ctor.eas @@ -5,7 +5,9 @@ ;;; /_/ /_/\____/\____/\__,_/____/_/ /_/ /_/ ;;; ;;; constructor code - + +#pragma target "cancun" + push @.end - @.start ; [size] dup1 ; [size, size] push @.start ; [start, size, size] diff --git a/example/erc20/erc20.eas b/example/erc20/erc20.eas index d40c6c2..9266ade 100644 --- a/example/erc20/erc20.eas +++ b/example/erc20/erc20.eas @@ -7,6 +7,8 @@ ;;; balance(address) => 0x000000000000000000000000 || address ;;; allowance(owner, spender) => keccak(owner || spender) +#pragma target "constantinople" + #define %match(candidate, label) { ; [selector] dup1 ; [selector, selector] push $candidate ; [candidate, selector, selector] diff --git a/example/erc20/erc20_ctor.eas b/example/erc20/erc20_ctor.eas index 913f7dc..1137f1d 100644 --- a/example/erc20/erc20_ctor.eas +++ b/example/erc20/erc20_ctor.eas @@ -1,6 +1,8 @@ ;;; ERC20 - constructor ;;; +#pragma target "constantinople" + pc ; [0] ;; give deployer initial supply diff --git a/internal/ast/ast.go b/internal/ast/ast.go index b6b542a..648ee68 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -167,6 +167,12 @@ type ( Src *Document Filename string } + + PragmaSt struct { + pos Position + Option string + Value string + } ) // definitions @@ -242,6 +248,14 @@ func (inst *AssembleSt) Description() string { return fmt.Sprintf("#assemble %q", inst.Filename) } +func (inst *PragmaSt) Position() Position { + return inst.pos +} + +func (inst *PragmaSt) Description() string { + return fmt.Sprintf("#pragma %s %q", inst.Option, inst.Value) +} + func (inst *OpcodeSt) Position() Position { return Position{File: inst.Src.File, Line: inst.tok.line} } diff --git a/internal/ast/lexer.go b/internal/ast/lexer.go index 23ef59e..220340e 100644 --- a/internal/ast/lexer.go +++ b/internal/ast/lexer.go @@ -69,6 +69,7 @@ const ( instMacroIdent // %macro openBrace // { closeBrace // } + equals // = arith // +, -, *, /, ... (see arith.go) ) @@ -232,6 +233,10 @@ func lexNext(l *lexer) stateFn { case r == '#': return lexPreprocessor + case r == '=': + l.emit(equals) + return lexNext + // numbers and identifiers: case unicode.IsDigit(r): @@ -241,6 +246,7 @@ func lexNext(l *lexer) stateFn { return lexIdentifier // arithmetic: + case r == '<': return lexLshift diff --git a/internal/ast/parse.go b/internal/ast/parse.go index 8d7edf5..b233231 100644 --- a/internal/ast/parse.go +++ b/internal/ast/parse.go @@ -189,13 +189,12 @@ func parseDirective(p *Parser, tok token) { p.throwError(tok, "nested macro definitions are not allowed") } parseMacroDef(p) - case "#include": parseInclude(p, tok) - case "#assemble": parseAssemble(p, tok) - + case "#pragma": + parsePragma(p, tok) default: p.throwError(tok, "unknown compiler directive %q", tok.text) } @@ -316,6 +315,25 @@ func parseAssemble(p *Parser, d token) { } } +func parsePragma(p *Parser, d token) { + instr := &PragmaSt{pos: Position{p.doc.File, d.line}} + switch tok := p.next(); tok.typ { + case identifier: + instr.Option = tok.text + switch v := p.next(); v.typ { + case stringLiteral, numberLiteral: + instr.Value = v.text + case equals: + p.throwError(tok, "unexpected = after #pragma %s", instr.Option) + default: + p.throwError(tok, "#pragma option value must be string or number literal") + } + p.doc.Statements = append(p.doc.Statements, instr) + default: + p.throwError(tok, "expected option name following #pragma") + } +} + func parseInstruction(p *Parser, tok token) { opcode := &OpcodeSt{Op: tok.text, Src: p.doc, tok: tok} size, isPush := parsePushSize(tok.text) diff --git a/internal/ast/tokentype_string.go b/internal/ast/tokentype_string.go index 78e502e..cb65aa1 100644 --- a/internal/ast/tokentype_string.go +++ b/internal/ast/tokentype_string.go @@ -28,12 +28,13 @@ func _() { _ = x[instMacroIdent-17] _ = x[openBrace-18] _ = x[closeBrace-19] - _ = x[arith-20] + _ = x[equals-20] + _ = x[arith-21] } -const _tokenType_name = "eoflineStartlineEndinvalidTokenidentifierdottedIdentifiervariableIdentifierlabelRefdottedLabelReflabeldottedLabelnumberLiteralstringLiteralopenParencloseParencommadirectiveinstMacroIdentopenBracecloseBracearith" +const _tokenType_name = "eoflineStartlineEndinvalidTokenidentifierdottedIdentifiervariableIdentifierlabelRefdottedLabelReflabeldottedLabelnumberLiteralstringLiteralopenParencloseParencommadirectiveinstMacroIdentopenBracecloseBraceequalsarith" -var _tokenType_index = [...]uint8{0, 3, 12, 19, 31, 41, 57, 75, 83, 97, 102, 113, 126, 139, 148, 158, 163, 172, 186, 195, 205, 210} +var _tokenType_index = [...]uint8{0, 3, 12, 19, 31, 41, 57, 75, 83, 97, 102, 113, 126, 139, 148, 158, 163, 172, 186, 195, 205, 211, 216} func (i tokenType) String() string { if i >= tokenType(len(_tokenType_index)-1) { diff --git a/internal/evm/forkdefs.go b/internal/evm/forkdefs.go new file mode 100644 index 0000000..d0972c3 --- /dev/null +++ b/internal/evm/forkdefs.go @@ -0,0 +1,247 @@ +package evm + +var LatestFork = "cancun" + +var forkReg = map[string]*InstructionSetDef{ + "frontier": { + Names: []string{"frontier"}, + Added: []*Op{ + opm["STOP"], + opm["ADD"], + opm["MUL"], + opm["SUB"], + opm["DIV"], + opm["SDIV"], + opm["MOD"], + opm["SMOD"], + opm["ADDMOD"], + opm["MULMOD"], + opm["EXP"], + opm["SIGNEXTEND"], + opm["LT"], + opm["GT"], + opm["SLT"], + opm["SGT"], + opm["EQ"], + opm["ISZERO"], + opm["AND"], + opm["XOR"], + opm["OR"], + opm["NOT"], + opm["BYTE"], + opm["KECCAK256"], + opm["ADDRESS"], + opm["BALANCE"], + opm["ORIGIN"], + opm["CALLER"], + opm["CALLVALUE"], + opm["CALLDATALOAD"], + opm["CALLDATASIZE"], + opm["CALLDATACOPY"], + opm["CODESIZE"], + opm["CODECOPY"], + opm["GASPRICE"], + opm["EXTCODESIZE"], + opm["EXTCODECOPY"], + opm["BLOCKHASH"], + opm["COINBASE"], + opm["TIMESTAMP"], + opm["NUMBER"], + opm["DIFFICULTY"], + opm["GASLIMIT"], + opm["POP"], + opm["MLOAD"], + opm["MSTORE"], + opm["MSTORE8"], + opm["SLOAD"], + opm["SSTORE"], + opm["JUMP"], + opm["JUMPI"], + opm["PC"], + opm["MSIZE"], + opm["GAS"], + opm["JUMPDEST"], + opm["PUSH1"], + opm["PUSH2"], + opm["PUSH3"], + opm["PUSH4"], + opm["PUSH5"], + opm["PUSH6"], + opm["PUSH7"], + opm["PUSH8"], + opm["PUSH9"], + opm["PUSH10"], + opm["PUSH11"], + opm["PUSH12"], + opm["PUSH13"], + opm["PUSH14"], + opm["PUSH15"], + opm["PUSH16"], + opm["PUSH17"], + opm["PUSH18"], + opm["PUSH19"], + opm["PUSH20"], + opm["PUSH21"], + opm["PUSH22"], + opm["PUSH23"], + opm["PUSH24"], + opm["PUSH25"], + opm["PUSH26"], + opm["PUSH27"], + opm["PUSH28"], + opm["PUSH29"], + opm["PUSH30"], + opm["PUSH31"], + opm["PUSH32"], + opm["DUP1"], + opm["DUP2"], + opm["DUP3"], + opm["DUP4"], + opm["DUP5"], + opm["DUP6"], + opm["DUP7"], + opm["DUP8"], + opm["DUP9"], + opm["DUP10"], + opm["DUP11"], + opm["DUP12"], + opm["DUP13"], + opm["DUP14"], + opm["DUP15"], + opm["DUP16"], + opm["SWAP1"], + opm["SWAP2"], + opm["SWAP3"], + opm["SWAP4"], + opm["SWAP5"], + opm["SWAP6"], + opm["SWAP7"], + opm["SWAP8"], + opm["SWAP9"], + opm["SWAP10"], + opm["SWAP11"], + opm["SWAP12"], + opm["SWAP13"], + opm["SWAP14"], + opm["SWAP15"], + opm["SWAP16"], + opm["LOG0"], + opm["LOG1"], + opm["LOG2"], + opm["LOG3"], + opm["LOG4"], + opm["CREATE"], + opm["CALL"], + opm["CALLCODE"], + opm["RETURN"], + opm["SELFDESTRUCT"], + }, + }, + + "homestead": { + Names: []string{"homestead"}, + Parent: "frontier", + Added: []*Op{ + opm["DELEGATECALL"], + }, + }, + + "tangerinewhistle": { + Names: []string{"tangerinewhistle", "eip150"}, + Parent: "homestead", + }, + + "spuriousdragon": { + Names: []string{"spuriousdragon", "eip158"}, + Parent: "tangerinewhistle", + }, + + "byzantium": { + Names: []string{"byzantium"}, + Parent: "spuriousdragon", + Added: []*Op{ + opm["STATICCALL"], + opm["RETURNDATASIZE"], + opm["RETURNDATACOPY"], + opm["REVERT"], + }, + }, + + "petersburg": { + Names: []string{"petersburg"}, + Parent: "byzantium", + }, + + "constantinople": { + Names: []string{"constantinople"}, + Parent: "petersburg", + Added: []*Op{ + opm["SHL"], + opm["SHR"], + opm["SAR"], + opm["EXTCODEHASH"], + opm["CREATE2"], + }, + }, + + "istanbul": { + Names: []string{"istanbul"}, + Parent: "constantinople", + Added: []*Op{ + opm["CHAINID"], + opm["SELFBALANCE"], + }, + }, + + "berlin": { + Names: []string{"berlin"}, + Parent: "istanbul", + }, + + "london": { + Names: []string{"london"}, + Parent: "berlin", + Added: []*Op{ + opm["BASEFEE"], + }, + }, + + "paris": { + Names: []string{"paris", "merge"}, + Parent: "istanbul", + Added: []*Op{ + opm["RANDOM"], + }, + Removed: []*Op{ + opm["DIFFICULTY"], + }, + }, + + "shanghai": { + Names: []string{"shanghai"}, + Parent: "paris", + Added: []*Op{ + opm["PUSH0"], + }, + }, + + "cancun": { + Names: []string{"cancun"}, + Parent: "shanghai", + Added: []*Op{ + opm["BLOBHASH"], + opm["TSTORE"], + opm["TLOAD"], + opm["MCOPY"], + opm["SENDALL"], + }, + Removed: []*Op{ + opm["SELFDESTRUCT"], + }, + }, + + "prague": { + Names: []string{"prague"}, + Parent: "cancun", + }, +} diff --git a/internal/evm/instruction_set.go b/internal/evm/instruction_set.go new file mode 100644 index 0000000..b3b6aca --- /dev/null +++ b/internal/evm/instruction_set.go @@ -0,0 +1,200 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package evm + +import ( + "fmt" + "maps" + "slices" + "strconv" + "strings" +) + +// InstructionSetDef is the definition of an EVM instruction set. +type InstructionSetDef struct { + Names []string // all names of this instruction set + Parent string + Added []*Op + Removed []*Op +} + +// Name returns the canonical name. +func (def *InstructionSetDef) Name() string { + return def.Names[0] +} + +// InstructionSet is an EVM instruction set. +type InstructionSet struct { + name string + byName map[string]*Op + byCode map[byte]*Op + opRemoved map[string]string // forks where op was last removed +} + +// FindInstructionSet resolves a fork name to a set of opcodes. +func FindInstructionSet(name string) *InstructionSet { + name = strings.ToLower(name) + def, ok := forkReg[name] + if !ok { + return nil + } + is := &InstructionSet{ + name: def.Name(), + byName: make(map[string]*Op), + byCode: make(map[byte]*Op), + opRemoved: make(map[string]string), + } + if err := is.resolveDefs(def); err != nil { + panic(err) + } + return is +} + +// Name returns the canonical instruction set name. +func (is *InstructionSet) Name() string { + return is.name +} + +// SupportsPush0 reports whether the instruction set includes the PUSH0 instruction. +func (is *InstructionSet) SupportsPush0() bool { + return is.byName["PUSH0"] != nil +} + +// OpByName resolves an opcode by its name. +// Name has to be all uppercase. +func (is *InstructionSet) OpByName(opname string) *Op { + return is.byName[opname] +} + +// PushBySize resolves a push op by its size. +func (is *InstructionSet) PushBySize(size int) *Op { + buf := []byte{'P', 'U', 'S', 'H', 0, 0} + name := strconv.AppendInt(buf[:4], int64(size), 10) + return is.byName[string(name)] +} + +// OpByCode resolves an opcode by its code. +func (is *InstructionSet) OpByCode(code byte) *Op { + return is.byCode[code] +} + +// ForkWhereOpRemoved returns the fork where a given op was removed from the instruction +// set. This is intended to be called when op is known to not exist. Note this will return +// an empty string in several cases: +// +// - op is invalid +// - op is valid, but does not appear in lineage of instruction set +// - op is valid and exists in instruction set +func (is *InstructionSet) ForkWhereOpRemoved(op string) string { + return is.opRemoved[op] +} + +// lineage computes the definition chain of an instruction set. +func (def *InstructionSetDef) lineage() ([]*InstructionSetDef, error) { + var visited = make(set[*InstructionSetDef]) + var lin []*InstructionSetDef + for { + if visited.includes(def) { + return nil, fmt.Errorf("instruction set parent cycle: %s <- %s", lin[len(lin)-1].Name(), def.Name()) + } + visited.add(def) + lin = append(lin, def) + + if def.Parent == "" { + break + } + parent, ok := forkReg[def.Parent] + if !ok { + return nil, fmt.Errorf("instruction set %s has unknown parent %s", def.Name(), def.Parent) + } + def = parent + } + slices.Reverse(lin) + return lin, nil +} + +// resolveDefs computes the full opcode set of a fork from its lineage. +func (is *InstructionSet) resolveDefs(toplevel *InstructionSetDef) error { + lineage, err := toplevel.lineage() + if err != nil { + return err + } + + for _, def := range lineage { + for _, op := range def.Removed { + if _, ok := is.byName[op.Name]; !ok { + return fmt.Errorf("removed op %s does not exist in fork %s", op.Name, def.Name()) + } + if _, ok := is.byCode[op.Code]; !ok { + return fmt.Errorf("removed opcode %d (%s) does not exist in fork %s", op.Code, op.Name, def.Name()) + } + delete(is.byName, op.Name) + delete(is.byCode, op.Code) + is.opRemoved[op.Name] = def.Name() + } + for _, op := range def.Added { + _, nameDefined := is.byName[op.Name] + if nameDefined { + return fmt.Errorf("instruction %s added multiple times", op.Name) + } + is.byName[op.Name] = op + _, codeDefined := is.byCode[op.Code] + if codeDefined { + return fmt.Errorf("opcode %v added multiple times (adding %s, existing def %s)", op.Code, op.Name, is.byCode[op.Code].Name) + } + is.byCode[op.Code] = op + delete(is.opRemoved, op.Name) + } + } + return nil +} + +// opAddedInForkMap contains all ops and the forks they were added in. +var opAddedInForkMap = computeOpAddedInFork() + +func computeOpAddedInFork() map[string][]string { + m := make(map[string][]string) + for _, def := range forkReg { + for _, op := range def.Added { + m[op.Name] = append(m[op.Name], def.Name()) + } + } + return m +} + +// ForksWhereOpAdded returns the fork names where a given op is added. +// If this returns nil, op is invalid. +func ForksWhereOpAdded(op string) []string { + return opAddedInForkMap[op] +} + +// set is a wrapper over map. +// I don't want to depend on a set library just for this. +type set[X comparable] map[X]struct{} + +func (s set[X]) add(k X) { + s[k] = struct{}{} +} + +func (s set[X]) includes(k X) bool { + _, ok := s[k] + return ok +} + +func (s set[X]) members() []X { + return slices.Collect(maps.Keys(s)) +} diff --git a/internal/evm/instruction_set_test.go b/internal/evm/instruction_set_test.go new file mode 100644 index 0000000..b53c236 --- /dev/null +++ b/internal/evm/instruction_set_test.go @@ -0,0 +1,141 @@ +package evm + +import ( + "maps" + "slices" + "strings" + "testing" +) + +func TestOps(t *testing.T) { + // Check op all names are uppercase. + for _, op := range oplist { + if op.Name != strings.ToUpper(op.Name) { + t.Fatalf("op %s name is not all-uppercase", op.Name) + } + } + + // Check all ops are used in a fork. + // First compute set of used op names. + defnames := slices.Sorted(maps.Keys(forkReg)) + used := make(set[string], len(oplist)) + for _, name := range defnames { + for _, op := range forkReg[name].Added { + used.add(op.Name) + } + } + usedopnames := used.members() + slices.Sort(usedopnames) + // Now compute sorted list of all ops. + allopnames := make([]string, len(oplist)) + for i, op := range oplist { + allopnames[i] = op.Name + } + slices.Sort(allopnames) + // Compare. + d := diff(allopnames, usedopnames) + if len(d) > 0 { + t.Error("unused ops:", d) + } + if len(usedopnames) > len(allopnames) { + t.Error("forkdefs uses ops which are not in oplist") + } +} + +func TestForkDefs(t *testing.T) { + defnames := slices.Sorted(maps.Keys(forkReg)) + + // Check canon name is listed first in def.Names. + for _, name := range defnames { + def := forkReg[name] + if len(def.Names) == 0 { + t.Fatalf("instruction set %q has no Names", name) + } + if def.Names[0] != name { + t.Fatalf("canon name of instruction set %q not listed first in def.Names", name) + } + } + + // Check lineage works. + for _, name := range defnames { + def := forkReg[name] + _, err := def.lineage() + if err != nil { + t.Errorf("problem in lineage() of %q: %v", name, err) + } + } +} + +// In this test, we just check for a few known ops. +func TestForkOps(t *testing.T) { + is := FindInstructionSet("cancun") + + { + op := is.OpByName("ADD") + if op.Name != "ADD" { + t.Fatal("wrong op name:", op.Name) + } + if op.Code != 0x01 { + t.Fatal("wrong op code:", op.Code) + } + if op2 := is.OpByCode(0x01); op2 != op { + t.Fatal("reverse lookup returned incorrect op", op2) + } + } + { + op := is.OpByName("SHR") + if op.Name != "SHR" { + t.Fatal("wrong op name:", op.Name) + } + if op.Code != 0x1c { + t.Fatal("wrong op code:", op.Code) + } + if op2 := is.OpByCode(0x1c); op2 != op { + t.Fatal("reverse lookup returned incorrect op", op2) + } + } + { + op := is.OpByName("RANDOM") + if op.Name != "RANDOM" { + t.Fatal("wrong op name:", op.Name) + } + if op.Code != 0x44 { + t.Fatal("wrong op code:", op.Code) + } + if op2 := is.OpByCode(0x44); op2 != op { + t.Fatal("reverse lookup returned incorrect op", op2) + } + } + { + op := is.OpByName("DIFFICULTY") + if op != nil { + t.Fatal("DIFFICULTY op found even though it was removed") + } + rf := is.ForkWhereOpRemoved("DIFFICULTY") + if rf != "paris" { + t.Fatalf("ForkWhereOpRemoved(DIFFICULTY) -> %s != %s", rf, "paris") + } + } +} + +func TestForksWhereOpAdded(t *testing.T) { + f := ForksWhereOpAdded("BASEFEE") + if !slices.Equal(f, []string{"london"}) { + t.Fatalf("wrong list for BASEFEE: %v", f) + } +} + +// diff returns the elements of a which are not in b. +func diff[X comparable](a, b []X) []X { + set := make(set[X], len(b)) + for _, x := range b { + set.add(x) + } + var diff []X + for _, x := range a { + if !set.includes(x) { + diff = append(diff, x) + } + } + return diff +} diff --git a/internal/evm/opcodes.go b/internal/evm/opcodes.go deleted file mode 100644 index 45aaf3f..0000000 --- a/internal/evm/opcodes.go +++ /dev/null @@ -1,564 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package evm - -import ( - "fmt" -) - -// OpCode is an EVM opcode -type OpCode byte - -// IsPush specifies if an opcode is a PUSH opcode. -func (op OpCode) IsPush() bool { - return PUSH1 <= op && op <= PUSH32 -} - -// 0x0 range - arithmetic ops. -const ( - STOP OpCode = 0x0 - ADD OpCode = 0x1 - MUL OpCode = 0x2 - SUB OpCode = 0x3 - DIV OpCode = 0x4 - SDIV OpCode = 0x5 - MOD OpCode = 0x6 - SMOD OpCode = 0x7 - ADDMOD OpCode = 0x8 - MULMOD OpCode = 0x9 - EXP OpCode = 0xa - SIGNEXTEND OpCode = 0xb -) - -// 0x10 range - comparison ops. -const ( - LT OpCode = 0x10 - GT OpCode = 0x11 - SLT OpCode = 0x12 - SGT OpCode = 0x13 - EQ OpCode = 0x14 - ISZERO OpCode = 0x15 - AND OpCode = 0x16 - OR OpCode = 0x17 - XOR OpCode = 0x18 - NOT OpCode = 0x19 - BYTE OpCode = 0x1a - SHL OpCode = 0x1b - SHR OpCode = 0x1c - SAR OpCode = 0x1d -) - -// 0x20 range - crypto. -const ( - KECCAK256 OpCode = 0x20 -) - -// 0x30 range - closure state. -const ( - ADDRESS OpCode = 0x30 - BALANCE OpCode = 0x31 - ORIGIN OpCode = 0x32 - CALLER OpCode = 0x33 - CALLVALUE OpCode = 0x34 - CALLDATALOAD OpCode = 0x35 - CALLDATASIZE OpCode = 0x36 - CALLDATACOPY OpCode = 0x37 - CODESIZE OpCode = 0x38 - CODECOPY OpCode = 0x39 - GASPRICE OpCode = 0x3a - EXTCODESIZE OpCode = 0x3b - EXTCODECOPY OpCode = 0x3c - RETURNDATASIZE OpCode = 0x3d - RETURNDATACOPY OpCode = 0x3e - EXTCODEHASH OpCode = 0x3f -) - -// 0x40 range - block operations. -const ( - BLOCKHASH OpCode = 0x40 - COINBASE OpCode = 0x41 - TIMESTAMP OpCode = 0x42 - NUMBER OpCode = 0x43 - DIFFICULTY OpCode = 0x44 - RANDOM OpCode = 0x44 // Same as DIFFICULTY - PREVRANDAO OpCode = 0x44 // Same as DIFFICULTY - GASLIMIT OpCode = 0x45 - CHAINID OpCode = 0x46 - SELFBALANCE OpCode = 0x47 - BASEFEE OpCode = 0x48 - BLOBHASH OpCode = 0x49 -) - -// 0x50 range - 'storage' and execution. -const ( - POP OpCode = 0x50 - MLOAD OpCode = 0x51 - MSTORE OpCode = 0x52 - MSTORE8 OpCode = 0x53 - SLOAD OpCode = 0x54 - SSTORE OpCode = 0x55 - JUMP OpCode = 0x56 - JUMPI OpCode = 0x57 - PC OpCode = 0x58 - MSIZE OpCode = 0x59 - GAS OpCode = 0x5a - JUMPDEST OpCode = 0x5b - TLOAD OpCode = 0x5c - TSTORE OpCode = 0x5d - MCOPY OpCode = 0x5e - PUSH0 OpCode = 0x5f -) - -// 0x60 range - pushes. -const ( - PUSH1 OpCode = 0x60 + iota - PUSH2 - PUSH3 - PUSH4 - PUSH5 - PUSH6 - PUSH7 - PUSH8 - PUSH9 - PUSH10 - PUSH11 - PUSH12 - PUSH13 - PUSH14 - PUSH15 - PUSH16 - PUSH17 - PUSH18 - PUSH19 - PUSH20 - PUSH21 - PUSH22 - PUSH23 - PUSH24 - PUSH25 - PUSH26 - PUSH27 - PUSH28 - PUSH29 - PUSH30 - PUSH31 - PUSH32 -) - -// 0x80 range - dups. -const ( - DUP1 = 0x80 + iota - DUP2 - DUP3 - DUP4 - DUP5 - DUP6 - DUP7 - DUP8 - DUP9 - DUP10 - DUP11 - DUP12 - DUP13 - DUP14 - DUP15 - DUP16 -) - -// 0x90 range - swaps. -const ( - SWAP1 = 0x90 + iota - SWAP2 - SWAP3 - SWAP4 - SWAP5 - SWAP6 - SWAP7 - SWAP8 - SWAP9 - SWAP10 - SWAP11 - SWAP12 - SWAP13 - SWAP14 - SWAP15 - SWAP16 -) - -// 0xa0 range - logging ops. -const ( - LOG0 OpCode = 0xa0 + iota - LOG1 - LOG2 - LOG3 - LOG4 -) - -// 0xf0 range - closures. -const ( - CREATE OpCode = 0xf0 - CALL OpCode = 0xf1 - CALLCODE OpCode = 0xf2 - RETURN OpCode = 0xf3 - DELEGATECALL OpCode = 0xf4 - CREATE2 OpCode = 0xf5 - - STATICCALL OpCode = 0xfa - REVERT OpCode = 0xfd - INVALID OpCode = 0xfe - SELFDESTRUCT OpCode = 0xff -) - -// Since the opcodes aren't all in order we can't use a regular slice. -var opCodeToString = map[OpCode]string{ - // 0x0 range - arithmetic ops. - STOP: "STOP", - ADD: "ADD", - MUL: "MUL", - SUB: "SUB", - DIV: "DIV", - SDIV: "SDIV", - MOD: "MOD", - SMOD: "SMOD", - EXP: "EXP", - NOT: "NOT", - LT: "LT", - GT: "GT", - SLT: "SLT", - SGT: "SGT", - EQ: "EQ", - ISZERO: "ISZERO", - SIGNEXTEND: "SIGNEXTEND", - - // 0x10 range - bit ops. - AND: "AND", - OR: "OR", - XOR: "XOR", - BYTE: "BYTE", - SHL: "SHL", - SHR: "SHR", - SAR: "SAR", - ADDMOD: "ADDMOD", - MULMOD: "MULMOD", - - // 0x20 range - crypto. - KECCAK256: "KECCAK256", - - // 0x30 range - closure state. - ADDRESS: "ADDRESS", - BALANCE: "BALANCE", - ORIGIN: "ORIGIN", - CALLER: "CALLER", - CALLVALUE: "CALLVALUE", - CALLDATALOAD: "CALLDATALOAD", - CALLDATASIZE: "CALLDATASIZE", - CALLDATACOPY: "CALLDATACOPY", - CODESIZE: "CODESIZE", - CODECOPY: "CODECOPY", - GASPRICE: "GASPRICE", - EXTCODESIZE: "EXTCODESIZE", - EXTCODECOPY: "EXTCODECOPY", - RETURNDATASIZE: "RETURNDATASIZE", - RETURNDATACOPY: "RETURNDATACOPY", - EXTCODEHASH: "EXTCODEHASH", - - // 0x40 range - block operations. - BLOCKHASH: "BLOCKHASH", - COINBASE: "COINBASE", - TIMESTAMP: "TIMESTAMP", - NUMBER: "NUMBER", - DIFFICULTY: "DIFFICULTY", // TODO (MariusVanDerWijden) rename to PREVRANDAO post merge - GASLIMIT: "GASLIMIT", - CHAINID: "CHAINID", - SELFBALANCE: "SELFBALANCE", - BASEFEE: "BASEFEE", - BLOBHASH: "BLOBHASH", - - // 0x50 range - 'storage' and execution. - POP: "POP", - MLOAD: "MLOAD", - MSTORE: "MSTORE", - MSTORE8: "MSTORE8", - SLOAD: "SLOAD", - SSTORE: "SSTORE", - JUMP: "JUMP", - JUMPI: "JUMPI", - PC: "PC", - MSIZE: "MSIZE", - GAS: "GAS", - JUMPDEST: "JUMPDEST", - TLOAD: "TLOAD", - TSTORE: "TSTORE", - MCOPY: "MCOPY", - PUSH0: "PUSH0", - - // 0x60 range - pushes. - PUSH1: "PUSH1", - PUSH2: "PUSH2", - PUSH3: "PUSH3", - PUSH4: "PUSH4", - PUSH5: "PUSH5", - PUSH6: "PUSH6", - PUSH7: "PUSH7", - PUSH8: "PUSH8", - PUSH9: "PUSH9", - PUSH10: "PUSH10", - PUSH11: "PUSH11", - PUSH12: "PUSH12", - PUSH13: "PUSH13", - PUSH14: "PUSH14", - PUSH15: "PUSH15", - PUSH16: "PUSH16", - PUSH17: "PUSH17", - PUSH18: "PUSH18", - PUSH19: "PUSH19", - PUSH20: "PUSH20", - PUSH21: "PUSH21", - PUSH22: "PUSH22", - PUSH23: "PUSH23", - PUSH24: "PUSH24", - PUSH25: "PUSH25", - PUSH26: "PUSH26", - PUSH27: "PUSH27", - PUSH28: "PUSH28", - PUSH29: "PUSH29", - PUSH30: "PUSH30", - PUSH31: "PUSH31", - PUSH32: "PUSH32", - - // 0x80 - dups. - DUP1: "DUP1", - DUP2: "DUP2", - DUP3: "DUP3", - DUP4: "DUP4", - DUP5: "DUP5", - DUP6: "DUP6", - DUP7: "DUP7", - DUP8: "DUP8", - DUP9: "DUP9", - DUP10: "DUP10", - DUP11: "DUP11", - DUP12: "DUP12", - DUP13: "DUP13", - DUP14: "DUP14", - DUP15: "DUP15", - DUP16: "DUP16", - - // 0x90 - swaps. - SWAP1: "SWAP1", - SWAP2: "SWAP2", - SWAP3: "SWAP3", - SWAP4: "SWAP4", - SWAP5: "SWAP5", - SWAP6: "SWAP6", - SWAP7: "SWAP7", - SWAP8: "SWAP8", - SWAP9: "SWAP9", - SWAP10: "SWAP10", - SWAP11: "SWAP11", - SWAP12: "SWAP12", - SWAP13: "SWAP13", - SWAP14: "SWAP14", - SWAP15: "SWAP15", - SWAP16: "SWAP16", - - // 0xa0 range - logging ops. - LOG0: "LOG0", - LOG1: "LOG1", - LOG2: "LOG2", - LOG3: "LOG3", - LOG4: "LOG4", - - // 0xf0 range - closures. - CREATE: "CREATE", - CALL: "CALL", - RETURN: "RETURN", - CALLCODE: "CALLCODE", - DELEGATECALL: "DELEGATECALL", - CREATE2: "CREATE2", - STATICCALL: "STATICCALL", - REVERT: "REVERT", - INVALID: "INVALID", - SELFDESTRUCT: "SELFDESTRUCT", -} - -func (op OpCode) String() string { - str := opCodeToString[op] - if len(str) == 0 { - return fmt.Sprintf("opcode %#x not defined", int(op)) - } - - return str -} - -var stringToOp = map[string]OpCode{ - "STOP": STOP, - "ADD": ADD, - "MUL": MUL, - "SUB": SUB, - "DIV": DIV, - "SDIV": SDIV, - "MOD": MOD, - "SMOD": SMOD, - "EXP": EXP, - "NOT": NOT, - "LT": LT, - "GT": GT, - "SLT": SLT, - "SGT": SGT, - "EQ": EQ, - "ISZERO": ISZERO, - "SIGNEXTEND": SIGNEXTEND, - "AND": AND, - "OR": OR, - "XOR": XOR, - "BYTE": BYTE, - "SHL": SHL, - "SHR": SHR, - "SAR": SAR, - "ADDMOD": ADDMOD, - "MULMOD": MULMOD, - "KECCAK256": KECCAK256, - "ADDRESS": ADDRESS, - "BALANCE": BALANCE, - "ORIGIN": ORIGIN, - "CALLER": CALLER, - "CALLVALUE": CALLVALUE, - "CALLDATALOAD": CALLDATALOAD, - "CALLDATASIZE": CALLDATASIZE, - "CALLDATACOPY": CALLDATACOPY, - "CHAINID": CHAINID, - "BASEFEE": BASEFEE, - "BLOBHASH": BLOBHASH, - "DELEGATECALL": DELEGATECALL, - "STATICCALL": STATICCALL, - "CODESIZE": CODESIZE, - "CODECOPY": CODECOPY, - "GASPRICE": GASPRICE, - "EXTCODESIZE": EXTCODESIZE, - "EXTCODECOPY": EXTCODECOPY, - "RETURNDATASIZE": RETURNDATASIZE, - "RETURNDATACOPY": RETURNDATACOPY, - "EXTCODEHASH": EXTCODEHASH, - "BLOCKHASH": BLOCKHASH, - "COINBASE": COINBASE, - "TIMESTAMP": TIMESTAMP, - "NUMBER": NUMBER, - "DIFFICULTY": DIFFICULTY, - "GASLIMIT": GASLIMIT, - "SELFBALANCE": SELFBALANCE, - "POP": POP, - "MLOAD": MLOAD, - "MSTORE": MSTORE, - "MSTORE8": MSTORE8, - "SLOAD": SLOAD, - "SSTORE": SSTORE, - "JUMP": JUMP, - "JUMPI": JUMPI, - "PC": PC, - "MSIZE": MSIZE, - "GAS": GAS, - "JUMPDEST": JUMPDEST, - "TLOAD": TLOAD, - "TSTORE": TSTORE, - "MCOPY": MCOPY, - "PUSH0": PUSH0, - "PUSH1": PUSH1, - "PUSH2": PUSH2, - "PUSH3": PUSH3, - "PUSH4": PUSH4, - "PUSH5": PUSH5, - "PUSH6": PUSH6, - "PUSH7": PUSH7, - "PUSH8": PUSH8, - "PUSH9": PUSH9, - "PUSH10": PUSH10, - "PUSH11": PUSH11, - "PUSH12": PUSH12, - "PUSH13": PUSH13, - "PUSH14": PUSH14, - "PUSH15": PUSH15, - "PUSH16": PUSH16, - "PUSH17": PUSH17, - "PUSH18": PUSH18, - "PUSH19": PUSH19, - "PUSH20": PUSH20, - "PUSH21": PUSH21, - "PUSH22": PUSH22, - "PUSH23": PUSH23, - "PUSH24": PUSH24, - "PUSH25": PUSH25, - "PUSH26": PUSH26, - "PUSH27": PUSH27, - "PUSH28": PUSH28, - "PUSH29": PUSH29, - "PUSH30": PUSH30, - "PUSH31": PUSH31, - "PUSH32": PUSH32, - "DUP1": DUP1, - "DUP2": DUP2, - "DUP3": DUP3, - "DUP4": DUP4, - "DUP5": DUP5, - "DUP6": DUP6, - "DUP7": DUP7, - "DUP8": DUP8, - "DUP9": DUP9, - "DUP10": DUP10, - "DUP11": DUP11, - "DUP12": DUP12, - "DUP13": DUP13, - "DUP14": DUP14, - "DUP15": DUP15, - "DUP16": DUP16, - "SWAP1": SWAP1, - "SWAP2": SWAP2, - "SWAP3": SWAP3, - "SWAP4": SWAP4, - "SWAP5": SWAP5, - "SWAP6": SWAP6, - "SWAP7": SWAP7, - "SWAP8": SWAP8, - "SWAP9": SWAP9, - "SWAP10": SWAP10, - "SWAP11": SWAP11, - "SWAP12": SWAP12, - "SWAP13": SWAP13, - "SWAP14": SWAP14, - "SWAP15": SWAP15, - "SWAP16": SWAP16, - "LOG0": LOG0, - "LOG1": LOG1, - "LOG2": LOG2, - "LOG3": LOG3, - "LOG4": LOG4, - "CREATE": CREATE, - "CREATE2": CREATE2, - "CALL": CALL, - "RETURN": RETURN, - "CALLCODE": CALLCODE, - "REVERT": REVERT, - "INVALID": INVALID, - "SELFDESTRUCT": SELFDESTRUCT, -} - -// Lookup finds the opcode whose name is stored in `str`. -func OpByName(str string) (OpCode, bool) { - op, ok := stringToOp[str] - return op, ok -} - diff --git a/internal/evm/ops.go b/internal/evm/ops.go new file mode 100644 index 0000000..ed23761 --- /dev/null +++ b/internal/evm/ops.go @@ -0,0 +1,189 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package evm + +// Op is an EVM opcode. +type Op struct { + Name string + Code byte +} + +// This is the list of all opcodes. +var oplist = []*Op{ + {Name: "STOP", Code: 0x0}, + {Name: "ADD", Code: 0x1}, + {Name: "MUL", Code: 0x2}, + {Name: "SUB", Code: 0x3}, + {Name: "DIV", Code: 0x4}, + {Name: "SDIV", Code: 0x5}, + {Name: "MOD", Code: 0x6}, + {Name: "SMOD", Code: 0x7}, + {Name: "ADDMOD", Code: 0x8}, + {Name: "MULMOD", Code: 0x9}, + {Name: "EXP", Code: 0xa}, + {Name: "SIGNEXTEND", Code: 0xb}, + {Name: "LT", Code: 0x10}, + {Name: "GT", Code: 0x11}, + {Name: "SLT", Code: 0x12}, + {Name: "SGT", Code: 0x13}, + {Name: "EQ", Code: 0x14}, + {Name: "ISZERO", Code: 0x15}, + {Name: "AND", Code: 0x16}, + {Name: "OR", Code: 0x17}, + {Name: "XOR", Code: 0x18}, + {Name: "NOT", Code: 0x19}, + {Name: "BYTE", Code: 0x1a}, + {Name: "SHL", Code: 0x1b}, + {Name: "SHR", Code: 0x1c}, + {Name: "SAR", Code: 0x1d}, + {Name: "KECCAK256", Code: 0x20}, + {Name: "ADDRESS", Code: 0x30}, + {Name: "BALANCE", Code: 0x31}, + {Name: "ORIGIN", Code: 0x32}, + {Name: "CALLER", Code: 0x33}, + {Name: "CALLVALUE", Code: 0x34}, + {Name: "CALLDATALOAD", Code: 0x35}, + {Name: "CALLDATASIZE", Code: 0x36}, + {Name: "CALLDATACOPY", Code: 0x37}, + {Name: "CODESIZE", Code: 0x38}, + {Name: "CODECOPY", Code: 0x39}, + {Name: "GASPRICE", Code: 0x3a}, + {Name: "EXTCODESIZE", Code: 0x3b}, + {Name: "EXTCODECOPY", Code: 0x3c}, + {Name: "RETURNDATASIZE", Code: 0x3d}, + {Name: "RETURNDATACOPY", Code: 0x3e}, + {Name: "EXTCODEHASH", Code: 0x3f}, + {Name: "BLOCKHASH", Code: 0x40}, + {Name: "COINBASE", Code: 0x41}, + {Name: "TIMESTAMP", Code: 0x42}, + {Name: "NUMBER", Code: 0x43}, + {Name: "DIFFICULTY", Code: 0x44}, + {Name: "RANDOM", Code: 0x44}, + {Name: "GASLIMIT", Code: 0x45}, + {Name: "CHAINID", Code: 0x46}, + {Name: "SELFBALANCE", Code: 0x47}, + {Name: "BASEFEE", Code: 0x48}, + {Name: "BLOBHASH", Code: 0x49}, + {Name: "POP", Code: 0x50}, + {Name: "MLOAD", Code: 0x51}, + {Name: "MSTORE", Code: 0x52}, + {Name: "MSTORE8", Code: 0x53}, + {Name: "SLOAD", Code: 0x54}, + {Name: "SSTORE", Code: 0x55}, + {Name: "JUMP", Code: 0x56}, + {Name: "JUMPI", Code: 0x57}, + {Name: "PC", Code: 0x58}, + {Name: "MSIZE", Code: 0x59}, + {Name: "GAS", Code: 0x5a}, + {Name: "JUMPDEST", Code: 0x5b}, + {Name: "TLOAD", Code: 0x5c}, + {Name: "TSTORE", Code: 0x5d}, + {Name: "MCOPY", Code: 0x5e}, + {Name: "PUSH0", Code: 0x5f}, + {Name: "PUSH1", Code: 0x60}, + {Name: "PUSH2", Code: 0x61}, + {Name: "PUSH3", Code: 0x62}, + {Name: "PUSH4", Code: 0x63}, + {Name: "PUSH5", Code: 0x64}, + {Name: "PUSH6", Code: 0x65}, + {Name: "PUSH7", Code: 0x66}, + {Name: "PUSH8", Code: 0x67}, + {Name: "PUSH9", Code: 0x68}, + {Name: "PUSH10", Code: 0x69}, + {Name: "PUSH11", Code: 0x6a}, + {Name: "PUSH12", Code: 0x6b}, + {Name: "PUSH13", Code: 0x6c}, + {Name: "PUSH14", Code: 0x6d}, + {Name: "PUSH15", Code: 0x6e}, + {Name: "PUSH16", Code: 0x6f}, + {Name: "PUSH17", Code: 0x70}, + {Name: "PUSH18", Code: 0x71}, + {Name: "PUSH19", Code: 0x72}, + {Name: "PUSH20", Code: 0x73}, + {Name: "PUSH21", Code: 0x74}, + {Name: "PUSH22", Code: 0x75}, + {Name: "PUSH23", Code: 0x76}, + {Name: "PUSH24", Code: 0x77}, + {Name: "PUSH25", Code: 0x78}, + {Name: "PUSH26", Code: 0x79}, + {Name: "PUSH27", Code: 0x7a}, + {Name: "PUSH28", Code: 0x7b}, + {Name: "PUSH29", Code: 0x7c}, + {Name: "PUSH30", Code: 0x7d}, + {Name: "PUSH31", Code: 0x7e}, + {Name: "PUSH32", Code: 0x7f}, + {Name: "DUP1", Code: 0x80}, + {Name: "DUP2", Code: 0x81}, + {Name: "DUP3", Code: 0x82}, + {Name: "DUP4", Code: 0x83}, + {Name: "DUP5", Code: 0x84}, + {Name: "DUP6", Code: 0x85}, + {Name: "DUP7", Code: 0x86}, + {Name: "DUP8", Code: 0x87}, + {Name: "DUP9", Code: 0x88}, + {Name: "DUP10", Code: 0x89}, + {Name: "DUP11", Code: 0x8a}, + {Name: "DUP12", Code: 0x8b}, + {Name: "DUP13", Code: 0x8c}, + {Name: "DUP14", Code: 0x8d}, + {Name: "DUP15", Code: 0x8e}, + {Name: "DUP16", Code: 0x8f}, + {Name: "SWAP1", Code: 0x90}, + {Name: "SWAP2", Code: 0x91}, + {Name: "SWAP3", Code: 0x92}, + {Name: "SWAP4", Code: 0x93}, + {Name: "SWAP5", Code: 0x94}, + {Name: "SWAP6", Code: 0x95}, + {Name: "SWAP7", Code: 0x96}, + {Name: "SWAP8", Code: 0x97}, + {Name: "SWAP9", Code: 0x98}, + {Name: "SWAP10", Code: 0x99}, + {Name: "SWAP11", Code: 0x9a}, + {Name: "SWAP12", Code: 0x9b}, + {Name: "SWAP13", Code: 0x9c}, + {Name: "SWAP14", Code: 0x9d}, + {Name: "SWAP15", Code: 0x9e}, + {Name: "SWAP16", Code: 0x9f}, + {Name: "LOG0", Code: 0xa0}, + {Name: "LOG1", Code: 0xa1}, + {Name: "LOG2", Code: 0xa2}, + {Name: "LOG3", Code: 0xa3}, + {Name: "LOG4", Code: 0xa4}, + {Name: "CREATE", Code: 0xf0}, + {Name: "CALL", Code: 0xf1}, + {Name: "CALLCODE", Code: 0xf2}, + {Name: "RETURN", Code: 0xf3}, + {Name: "DELEGATECALL", Code: 0xf4}, + {Name: "CREATE2", Code: 0xf5}, + {Name: "STATICCALL", Code: 0xfa}, + {Name: "REVERT", Code: 0xfd}, + {Name: "SELFDESTRUCT", Code: 0xff}, + {Name: "SENDALL", Code: 0xff}, +} + +var opm = computeOpsMap() + +func computeOpsMap() map[string]*Op { + m := make(map[string]*Op, len(oplist)) + for _, op := range oplist { + if m[op.Name] != nil { + panic("duplicate op " + op.Name) + } + m[op.Name] = op + } + return m +}