Per-hardfork instruction tables, built at compile time, and the programs decoded from them.
A table is a 256-element tuple indexed by opcode byte. Each entry is an
instruction {opcode, static_gas, min_depth, max_depth, arg}:
opcode— the opcodeEEVM.Interpreterdispatches onstatic_gas— the cost the interpreter charges before dispatch, fromEEVM.Gas.Staticfor that hardforkmin_depth/max_depth— the stack depths the opcode can run at: it pops the Registry's:inputs, and must not leave more than 1024 itemsarg—nilin the table; a decoded program puts PUSH data and jump targets here
Bytes the hardfork does not define — unassigned bytes, and opcodes whose
activating EIP (the Registry's :eip field) is not yet enabled — get the
INVALID (0xFE) entry, opcode included, so they halt exactly like INVALID
even when their module implements them on later forks.
EEVM.Interpreter.MachineState.Env resolves the table once per frame from
the active hardfork, and decode/3 turns a contract's code into a program
of these instructions at most once per transaction, so the per-opcode path
never checks which fork is active or decodes a byte. Until code has run long
enough to repay decoding, fetch/3 reads its instructions one at a time
and jumpdests/1 checks its jumps (#210).
Summary
Types
An opcode, or one of the fused PUSH+JUMP(I) instructions of a program.
A decoded program: elem(program, pc) is the instruction at pc,
:push_data inside PUSH data, and :end past the end of the code.
Functions
Decodes code into a program for table, with one entry per code byte.
Reads the instruction at pc straight from code, as decode/3 would
without tracing? but with nothing fused, or :end past the end of the code.
Returns the instruction table for spec_id.
Returns the JUMPDEST map of code: one byte per code byte, 1 where a
JUMPDEST opcode starts and 0 elsewhere, including PUSH data (Yellow Paper
§9.4.3).
Types
@type instruction() :: {opcode(), non_neg_integer(), non_neg_integer(), non_neg_integer(), term()}
@type opcode() :: non_neg_integer() | :jump_to | :jumpi_to
An opcode, or one of the fused PUSH+JUMP(I) instructions of a program.
@type program() :: tuple()
A decoded program: elem(program, pc) is the instruction at pc,
:push_data inside PUSH data, and :end past the end of the code.
@type t() :: tuple()
Functions
Decodes code into a program for table, with one entry per code byte.
Opcodes are the table's own instructions, so most entries share them. A
PUSH carries its value in arg, and its data bytes are :push_data, so a
jump can land only on a real JUMPDEST (Yellow Paper §9.4.3). A PUSH cut
short by the end of the code reads the missing bytes as zeros.
Without tracing?, a PUSH directly followed by JUMP or JUMPI becomes one
instruction, :jump_to or :jumpi_to, which charges both opcodes' gas and
never puts the destination on the stack: Solidity emits that pair for nearly
every branch and internal call. Its arg keeps the PUSH, which the
interpreter runs on its own when the pair cannot run whole, so a failure is
reported at the opcode that fails.
With tracing?, nothing is fused, so the tracer sees every opcode, and
each instruction is wrapped as {:trace, byte, instruction}: the
interpreter records a trace step for the wrapper, then runs the
instruction. Untraced programs carry no tracing cost at all.
iex> alias EEVM.Interpreter.InstructionTable
iex> table = InstructionTable.for_spec(:prague)
iex> code = <<0x60, 0x03, 0x56, 0x5B>>
iex> program = InstructionTable.decode(code, table, false)
iex> elem(program, 0)
{:jump_to, 11, 0, 1023, {{0x60, 3, 0, 1023, 3}, 3}}
iex> {elem(program, 1), elem(program, 4)}
{:push_data, :end}
iex> InstructionTable.decode(code, table, true) |> elem(0)
{:trace, 0x60, {0x60, 3, 0, 1023, 3}}
@spec fetch(binary(), non_neg_integer(), t()) :: instruction() | :end
Reads the instruction at pc straight from code, as decode/3 would
without tracing? but with nothing fused, or :end past the end of the code.
This runs code that has not been decoded yet, one instruction at a time.
pc must be an instruction's position, not PUSH data.
iex> alias EEVM.Interpreter.InstructionTable
iex> table = InstructionTable.for_spec(:prague)
iex> InstructionTable.fetch(<<0x61, 0x01>>, 0, table)
{0x61, 3, 0, 1023, 0x0100}
iex> InstructionTable.fetch(<<0x61, 0x01>>, 3, table)
:end
@spec for_spec(EEVM.HardforkConfig.spec_id()) :: t()
Returns the instruction table for spec_id.
Returns the JUMPDEST map of code: one byte per code byte, 1 where a
JUMPDEST opcode starts and 0 elsewhere, including PUSH data (Yellow Paper
§9.4.3).
Jumps in code that has not been decoded yet are checked against it. One
byte per position rather than one bit keeps each check a single
:binary.at/2.
iex> EEVM.Interpreter.InstructionTable.jumpdests(<<0x60, 0x5B, 0x5B>>)
<<0, 0, 1>>