Commit 4584c976 authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Modularize mipsevm packages (#11135)

* cannon: Isolate test utilities, rework types to avoid circular dependencies

* cannon: Rollback changes to MIPSEVM.EncodePreimageOracleInput

* cannon: Move open_mips_tests to tests subpackage

* cannon: Update some paths to open_mips_tests

* cannon: Collect oracle utils, move generic tests to tests package

* cannon: Move common state constants and utilities to core pkg

* cannon: Move state.go and state_mt.go into impls package

* cannon: Create a memory subpackage

* cannon: Create exec pkg for mips logic

* cannon: Wrap remaining core files in isolated pkgs

* cannon: Create a few more subpackages

* cannon: Rename state_mt to state

* cannon: Move hex utility

* cannon: Pull loading functionality out of patch.go

* cannon: Remove 'MT' prefixes on multi_threaded/state.go symbols

* cannon: Add compile-time PreimageOracle check back in

* cannon: Undo excessive nesting in core package

* cannon: Reorganize single- and multithreaded pkgs

* cannon: Rename test_util pkg to testutil

* cannon: Move instrumented_state logic into singlethreaded pkg

* cannon: Fix test paths

* cannon: Move core pkgs back to root mipsevm pkg

* cannon: Flatten util pkg back into main mipsevm pkg

* cannon: Cleanup - don't use vmstate for mipsevm pkg

* cannon: Add godoc for FPVMState.GetRegisters()

* cannon: Cleanup - fix pkg name, fn visibility
parent f5221f4d
...@@ -162,11 +162,11 @@ jobs: ...@@ -162,11 +162,11 @@ jobs:
steps: steps:
- checkout - checkout
- check-changed: - check-changed:
patterns: cannon/mipsevm/open_mips_tests/test patterns: cannon/mipsevm/tests/open_mips_tests/test
- run: - run:
name: Build MIPS test vectors name: Build MIPS test vectors
command: python3 maketests.py && git diff --exit-code command: python3 maketests.py && git diff --exit-code
working_directory: cannon/mipsevm/open_mips_tests working_directory: cannon/mipsevm/tests/open_mips_tests
pnpm-monorepo: pnpm-monorepo:
docker: docker:
......
...@@ -6,7 +6,8 @@ import ( ...@@ -6,7 +6,8 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil"
) )
...@@ -46,16 +47,16 @@ func LoadELF(ctx *cli.Context) error { ...@@ -46,16 +47,16 @@ func LoadELF(ctx *cli.Context) error {
if elfProgram.Machine != elf.EM_MIPS { if elfProgram.Machine != elf.EM_MIPS {
return fmt.Errorf("ELF is not big-endian MIPS R3000, but got %q", elfProgram.Machine.String()) return fmt.Errorf("ELF is not big-endian MIPS R3000, but got %q", elfProgram.Machine.String())
} }
state, err := mipsevm.LoadELF(elfProgram, mipsevm.CreateInitialState) state, err := program.LoadELF(elfProgram, singlethreaded.CreateInitialState)
if err != nil { if err != nil {
return fmt.Errorf("failed to load ELF data into VM state: %w", err) return fmt.Errorf("failed to load ELF data into VM state: %w", err)
} }
for _, typ := range ctx.StringSlice(LoadELFPatchFlag.Name) { for _, typ := range ctx.StringSlice(LoadELFPatchFlag.Name) {
switch typ { switch typ {
case "stack": case "stack":
err = mipsevm.PatchStack(state) err = program.PatchStack(state)
case "go": case "go":
err = mipsevm.PatchGo(elfProgram, state) err = program.PatchGo(elfProgram, state)
default: default:
return fmt.Errorf("unrecognized form of patching: %q", typ) return fmt.Errorf("unrecognized form of patching: %q", typ)
} }
...@@ -63,14 +64,14 @@ func LoadELF(ctx *cli.Context) error { ...@@ -63,14 +64,14 @@ func LoadELF(ctx *cli.Context) error {
return fmt.Errorf("failed to apply patch %s: %w", typ, err) return fmt.Errorf("failed to apply patch %s: %w", typ, err)
} }
} }
meta, err := mipsevm.MakeMetadata(elfProgram) meta, err := program.MakeMetadata(elfProgram)
if err != nil { if err != nil {
return fmt.Errorf("failed to compute program metadata: %w", err) return fmt.Errorf("failed to compute program metadata: %w", err)
} }
if err := jsonutil.WriteJSON[*mipsevm.Metadata](ctx.Path(LoadELFMetaFlag.Name), meta, OutFilePerm); err != nil { if err := jsonutil.WriteJSON[*program.Metadata](ctx.Path(LoadELFMetaFlag.Name), meta, OutFilePerm); err != nil {
return fmt.Errorf("failed to output metadata: %w", err) return fmt.Errorf("failed to output metadata: %w", err)
} }
return jsonutil.WriteJSON[*mipsevm.State](ctx.Path(LoadELFOutFlag.Name), state, OutFilePerm) return jsonutil.WriteJSON[*singlethreaded.State](ctx.Path(LoadELFOutFlag.Name), state, OutFilePerm)
} }
var LoadELFCommand = &cli.Command{ var LoadELFCommand = &cli.Command{
......
...@@ -13,11 +13,12 @@ import ( ...@@ -13,11 +13,12 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
"github.com/pkg/profile" "github.com/pkg/profile"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
preimage "github.com/ethereum-optimism/optimism/op-preimage" preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil"
) )
...@@ -350,12 +351,12 @@ func Run(ctx *cli.Context) error { ...@@ -350,12 +351,12 @@ func Run(ctx *cli.Context) error {
snapshotAt := ctx.Generic(RunSnapshotAtFlag.Name).(*StepMatcherFlag).Matcher() snapshotAt := ctx.Generic(RunSnapshotAtFlag.Name).(*StepMatcherFlag).Matcher()
infoAt := ctx.Generic(RunInfoAtFlag.Name).(*StepMatcherFlag).Matcher() infoAt := ctx.Generic(RunInfoAtFlag.Name).(*StepMatcherFlag).Matcher()
var meta *mipsevm.Metadata var meta *program.Metadata
if metaPath := ctx.Path(RunMetaFlag.Name); metaPath == "" { if metaPath := ctx.Path(RunMetaFlag.Name); metaPath == "" {
l.Info("no metadata file specified, defaulting to empty metadata") l.Info("no metadata file specified, defaulting to empty metadata")
meta = &mipsevm.Metadata{Symbols: nil} // provide empty metadata by default meta = &program.Metadata{Symbols: nil} // provide empty metadata by default
} else { } else {
if m, err := jsonutil.LoadJSON[mipsevm.Metadata](metaPath); err != nil { if m, err := jsonutil.LoadJSON[program.Metadata](metaPath); err != nil {
return fmt.Errorf("failed to load metadata: %w", err) return fmt.Errorf("failed to load metadata: %w", err)
} else { } else {
meta = m meta = m
...@@ -365,7 +366,7 @@ func Run(ctx *cli.Context) error { ...@@ -365,7 +366,7 @@ func Run(ctx *cli.Context) error {
var vm mipsevm.FPVM var vm mipsevm.FPVM
var debugProgram bool var debugProgram bool
if vmType == cannonVMType { if vmType == cannonVMType {
cannon, err := mipsevm.NewInstrumentedStateFromFile(ctx.Path(RunInputFlag.Name), po, outLog, errLog) cannon, err := singlethreaded.NewInstrumentedStateFromFile(ctx.Path(RunInputFlag.Name), po, outLog, errLog)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -4,9 +4,10 @@ import ( ...@@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
) )
var ( var (
...@@ -26,7 +27,7 @@ var ( ...@@ -26,7 +27,7 @@ var (
func Witness(ctx *cli.Context) error { func Witness(ctx *cli.Context) error {
input := ctx.Path(WitnessInputFlag.Name) input := ctx.Path(WitnessInputFlag.Name)
output := ctx.Path(WitnessOutputFlag.Name) output := ctx.Path(WitnessOutputFlag.Name)
state, err := jsonutil.LoadJSON[mipsevm.State](input) state, err := jsonutil.LoadJSON[singlethreaded.State](input)
if err != nil { if err != nil {
return fmt.Errorf("invalid input state (%v): %w", input, err) return fmt.Errorf("invalid input state (%v): %w", input, err)
} }
......
package mipsevm
type DebugInfo struct {
Pages int `json:"pages"`
NumPreimageRequests int `json:"num_preimage_requests"`
TotalPreimageSize int `json:"total_preimage_size"`
}
package mipsevm package exec
import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type StackTracker interface { type StackTracker interface {
pushStack(target uint32) PushStack(target uint32)
popStack() PopStack()
} }
func getInstructionDetails(pc uint32, memory *Memory) (insn, opcode, fun uint32) { func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc) insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits opcode = insn >> 26 // First 6-bits
fun = insn & 0x3f // Last 6-bits fun = insn & 0x3f // Last 6-bits
...@@ -13,7 +18,7 @@ func getInstructionDetails(pc uint32, memory *Memory) (insn, opcode, fun uint32) ...@@ -13,7 +18,7 @@ func getInstructionDetails(pc uint32, memory *Memory) (insn, opcode, fun uint32)
return insn, opcode, fun return insn, opcode, fun
} }
func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) error { func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker memory.MemTracker, stackTracker StackTracker) error {
// j-type j/jal // j-type j/jal
if opcode == 2 || opcode == 3 { if opcode == 2 || opcode == 3 {
linkReg := uint32(0) linkReg := uint32(0)
...@@ -22,8 +27,8 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -22,8 +27,8 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
} }
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
stackTracker.pushStack(target) stackTracker.PushStack(target)
return handleJump(cpu, registers, linkReg, target) return HandleJump(cpu, registers, linkReg, target)
} }
// register fetch // register fetch
...@@ -46,7 +51,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -46,7 +51,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
rt = insn & 0xFFFF rt = insn & 0xFFFF
} else { } else {
// SignExtImm // SignExtImm
rt = signExtend(insn&0xFFFF, 16) rt = SignExtend(insn&0xFFFF, 16)
} }
} else if opcode >= 0x28 || opcode == 0x22 || opcode == 0x26 { } else if opcode >= 0x28 || opcode == 0x22 || opcode == 0x26 {
// store rt value with store // store rt value with store
...@@ -57,7 +62,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -57,7 +62,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
} }
if (opcode >= 4 && opcode < 8) || opcode == 1 { if (opcode >= 4 && opcode < 8) || opcode == 1 {
return handleBranch(cpu, registers, opcode, insn, rtReg, rs) return HandleBranch(cpu, registers, opcode, insn, rtReg, rs)
} }
storeAddr := uint32(0xFF_FF_FF_FF) storeAddr := uint32(0xFF_FF_FF_FF)
...@@ -66,7 +71,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -66,7 +71,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
mem := uint32(0) mem := uint32(0)
if opcode >= 0x20 { if opcode >= 0x20 {
// M[R[rs]+SignExtImm] // M[R[rs]+SignExtImm]
rs += signExtend(insn&0xFFFF, 16) rs += SignExtend(insn&0xFFFF, 16)
addr := rs & 0xFFFFFFFC addr := rs & 0xFFFFFFFC
memTracker(addr) memTracker(addr)
mem = memory.GetMemory(addr) mem = memory.GetMemory(addr)
...@@ -79,7 +84,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -79,7 +84,7 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
} }
// ALU // ALU
val := executeMipsInstruction(insn, opcode, fun, rs, rt, mem) val := ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem)
if opcode == 0 && fun >= 8 && fun < 0x1c { if opcode == 0 && fun >= 8 && fun < 0x1c {
if fun == 8 || fun == 9 { // jr/jalr if fun == 8 || fun == 9 { // jr/jalr
...@@ -87,21 +92,21 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -87,21 +92,21 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
if fun == 9 { if fun == 9 {
linkReg = rdReg linkReg = rdReg
} }
stackTracker.popStack() stackTracker.PopStack()
return handleJump(cpu, registers, linkReg, rs) return HandleJump(cpu, registers, linkReg, rs)
} }
if fun == 0xa { // movz if fun == 0xa { // movz
return handleRd(cpu, registers, rdReg, rs, rt == 0) return HandleRd(cpu, registers, rdReg, rs, rt == 0)
} }
if fun == 0xb { // movn if fun == 0xb { // movn
return handleRd(cpu, registers, rdReg, rs, rt != 0) return HandleRd(cpu, registers, rdReg, rs, rt != 0)
} }
// lo and hi registers // lo and hi registers
// can write back // can write back
if fun >= 0x10 && fun < 0x1c { if fun >= 0x10 && fun < 0x1c {
return handleHiLo(cpu, registers, fun, rs, rt, rdReg) return HandleHiLo(cpu, registers, fun, rs, rt, rdReg)
} }
} }
...@@ -117,10 +122,10 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor ...@@ -117,10 +122,10 @@ func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memor
} }
// write back the value to destination register // write back the value to destination register
return handleRd(cpu, registers, rdReg, val, true) return HandleRd(cpu, registers, rdReg, val, true)
} }
func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
if opcode == 0 || (opcode >= 8 && opcode < 0xF) { if opcode == 0 || (opcode >= 8 && opcode < 0xF) {
// transform ArithLogI to SPECIAL // transform ArithLogI to SPECIAL
switch opcode { switch opcode {
...@@ -147,13 +152,13 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { ...@@ -147,13 +152,13 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
return rt >> ((insn >> 6) & 0x1F) return rt >> ((insn >> 6) & 0x1F)
case 0x03: // sra case 0x03: // sra
shamt := (insn >> 6) & 0x1F shamt := (insn >> 6) & 0x1F
return signExtend(rt>>shamt, 32-shamt) return SignExtend(rt>>shamt, 32-shamt)
case 0x04: // sllv case 0x04: // sllv
return rt << (rs & 0x1F) return rt << (rs & 0x1F)
case 0x06: // srlv case 0x06: // srlv
return rt >> (rs & 0x1F) return rt >> (rs & 0x1F)
case 0x07: // srav case 0x07: // srav
return signExtend(rt>>rs, 32-rs) return SignExtend(rt>>rs, 32-rs)
// functs in range [0x8, 0x1b] are handled specially by other functions // functs in range [0x8, 0x1b] are handled specially by other functions
case 0x08: // jr case 0x08: // jr
return rs return rs
...@@ -234,9 +239,9 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { ...@@ -234,9 +239,9 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
case 0x0F: // lui case 0x0F: // lui
return rt << 16 return rt << 16
case 0x20: // lb case 0x20: // lb
return signExtend((mem>>(24-(rs&3)*8))&0xFF, 8) return SignExtend((mem>>(24-(rs&3)*8))&0xFF, 8)
case 0x21: // lh case 0x21: // lh
return signExtend((mem>>(16-(rs&2)*8))&0xFFFF, 16) return SignExtend((mem>>(16-(rs&2)*8))&0xFFFF, 16)
case 0x22: // lwl case 0x22: // lwl
val := mem << ((rs & 3) * 8) val := mem << ((rs & 3) * 8)
mask := uint32(0xFFFFFFFF) << ((rs & 3) * 8) mask := uint32(0xFFFFFFFF) << ((rs & 3) * 8)
...@@ -280,7 +285,7 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 { ...@@ -280,7 +285,7 @@ func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
panic("invalid instruction") panic("invalid instruction")
} }
func signExtend(dat uint32, idx uint32) uint32 { func SignExtend(dat uint32, idx uint32) uint32 {
isSigned := (dat >> (idx - 1)) != 0 isSigned := (dat >> (idx - 1)) != 0
signed := ((uint32(1) << (32 - idx)) - 1) << idx signed := ((uint32(1) << (32 - idx)) - 1) << idx
mask := (uint32(1) << idx) - 1 mask := (uint32(1) << idx) - 1
...@@ -291,7 +296,7 @@ func signExtend(dat uint32, idx uint32) uint32 { ...@@ -291,7 +296,7 @@ func signExtend(dat uint32, idx uint32) uint32 {
} }
} }
func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error { func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error {
if cpu.NextPC != cpu.PC+4 { if cpu.NextPC != cpu.PC+4 {
panic("branch in delay slot") panic("branch in delay slot")
} }
...@@ -318,14 +323,14 @@ func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn ui ...@@ -318,14 +323,14 @@ func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn ui
prevPC := cpu.PC prevPC := cpu.PC
cpu.PC = cpu.NextPC // execute the delay slot first cpu.PC = cpu.NextPC // execute the delay slot first
if shouldBranch { if shouldBranch {
cpu.NextPC = prevPC + 4 + (signExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to. cpu.NextPC = prevPC + 4 + (SignExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to.
} else { } else {
cpu.NextPC = cpu.NextPC + 4 // branch not taken cpu.NextPC = cpu.NextPC + 4 // branch not taken
} }
return nil return nil
} }
func handleHiLo(cpu *CpuScalars, registers *[32]uint32, fun uint32, rs uint32, rt uint32, storeReg uint32) error { func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]uint32, fun uint32, rs uint32, rt uint32, storeReg uint32) error {
val := uint32(0) val := uint32(0)
switch fun { switch fun {
case 0x10: // mfhi case 0x10: // mfhi
...@@ -361,7 +366,7 @@ func handleHiLo(cpu *CpuScalars, registers *[32]uint32, fun uint32, rs uint32, r ...@@ -361,7 +366,7 @@ func handleHiLo(cpu *CpuScalars, registers *[32]uint32, fun uint32, rs uint32, r
return nil return nil
} }
func handleJump(cpu *CpuScalars, registers *[32]uint32, linkReg uint32, dest uint32) error { func HandleJump(cpu *mipsevm.CpuScalars, registers *[32]uint32, linkReg uint32, dest uint32) error {
if cpu.NextPC != cpu.PC+4 { if cpu.NextPC != cpu.PC+4 {
panic("jump in delay slot") panic("jump in delay slot")
} }
...@@ -374,7 +379,7 @@ func handleJump(cpu *CpuScalars, registers *[32]uint32, linkReg uint32, dest uin ...@@ -374,7 +379,7 @@ func handleJump(cpu *CpuScalars, registers *[32]uint32, linkReg uint32, dest uin
return nil return nil
} }
func handleRd(cpu *CpuScalars, registers *[32]uint32, storeReg uint32, val uint32, conditional bool) error { func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, val uint32, conditional bool) error {
if storeReg >= 32 { if storeReg >= 32 {
panic("invalid register") panic("invalid register")
} }
......
package mipsevm package exec
import ( import (
"encoding/binary" "encoding/binary"
...@@ -6,26 +6,29 @@ import ( ...@@ -6,26 +6,29 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
) )
const ( const (
sysMmap = 4090 SysMmap = 4090
sysBrk = 4045 SysBrk = 4045
sysClone = 4120 SysClone = 4120
sysExitGroup = 4246 SysExitGroup = 4246
sysRead = 4003 SysRead = 4003
sysWrite = 4004 SysWrite = 4004
sysFcntl = 4055 SysFcntl = 4055
) )
const ( const (
fdStdin = 0 FdStdin = 0
fdStdout = 1 FdStdout = 1
fdStderr = 2 FdStderr = 2
fdHintRead = 3 FdHintRead = 3
fdHintWrite = 4 FdHintWrite = 4
fdPreimageRead = 5 FdPreimageRead = 5
fdPreimageWrite = 6 FdPreimageWrite = 6
) )
const ( const (
...@@ -35,7 +38,7 @@ const ( ...@@ -35,7 +38,7 @@ const (
type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32)
func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) { func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
syscallNum = registers[2] // v0 syscallNum = registers[2] // v0
a0 = registers[4] a0 = registers[4]
...@@ -45,13 +48,13 @@ func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) { ...@@ -45,13 +48,13 @@ func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
return syscallNum, a0, a1, a2 return syscallNum, a0, a1, a2
} }
func handleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
v1 = uint32(0) v1 = uint32(0)
newHeap = heap newHeap = heap
sz := a1 sz := a1
if sz&PageAddrMask != 0 { // adjust size to align with page size if sz&memory.PageAddrMask != 0 { // adjust size to align with page size
sz += PageSize - (sz & PageAddrMask) sz += memory.PageSize - (sz & memory.PageAddrMask)
} }
if a0 == 0 { if a0 == 0 {
v0 = heap v0 = heap
...@@ -65,7 +68,7 @@ func handleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { ...@@ -65,7 +68,7 @@ func handleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
return v0, v1, newHeap return v0, v1, newHeap
} }
func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32) { func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker memory.MemTracker) (v0, v1, newPreimageOffset uint32) {
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code // returns: v0 = read, v1 = err code
v0 = uint32(0) v0 = uint32(0)
...@@ -73,9 +76,9 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -73,9 +76,9 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
newPreimageOffset = preimageOffset newPreimageOffset = preimageOffset
switch a0 { switch a0 {
case fdStdin: case FdStdin:
// leave v0 and v1 zero: read nothing, no error // leave v0 and v1 zero: read nothing, no error
case fdPreimageRead: // pre-image oracle case FdPreimageRead: // pre-image oracle
effAddr := a1 & 0xFFffFFfc effAddr := a1 & 0xFFffFFfc
memTracker(effAddr) memTracker(effAddr)
mem := memory.GetMemory(effAddr) mem := memory.GetMemory(effAddr)
...@@ -96,7 +99,7 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -96,7 +99,7 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
newPreimageOffset += datLen newPreimageOffset += datLen
v0 = datLen v0 = datLen
//fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem) //fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem)
case fdHintRead: // hint response case FdHintRead: // hint response
// don't actually read into memory, just say we read it all, we ignore the result anyway // don't actually read into memory, just say we read it all, we ignore the result anyway
v0 = a2 v0 = a2
default: default:
...@@ -107,7 +110,7 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -107,7 +110,7 @@ func handleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
return v0, v1, newPreimageOffset return v0, v1, newPreimageOffset
} }
func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle PreimageOracle, memory *Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) { func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker memory.MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code // returns: v0 = written, v1 = err code
v1 = uint32(0) v1 = uint32(0)
...@@ -116,13 +119,13 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b ...@@ -116,13 +119,13 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
newPreimageOffset = preimageOffset newPreimageOffset = preimageOffset
switch a0 { switch a0 {
case fdStdout: case FdStdout:
_, _ = io.Copy(stdOut, memory.ReadMemoryRange(a1, a2)) _, _ = io.Copy(stdOut, memory.ReadMemoryRange(a1, a2))
v0 = a2 v0 = a2
case fdStderr: case FdStderr:
_, _ = io.Copy(stdErr, memory.ReadMemoryRange(a1, a2)) _, _ = io.Copy(stdErr, memory.ReadMemoryRange(a1, a2))
v0 = a2 v0 = a2
case fdHintWrite: case FdHintWrite:
hintData, _ := io.ReadAll(memory.ReadMemoryRange(a1, a2)) hintData, _ := io.ReadAll(memory.ReadMemoryRange(a1, a2))
lastHint = append(lastHint, hintData...) lastHint = append(lastHint, hintData...)
for len(lastHint) >= 4 { // process while there is enough data to check if there are any hints for len(lastHint) >= 4 { // process while there is enough data to check if there are any hints
...@@ -137,7 +140,7 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b ...@@ -137,7 +140,7 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
} }
newLastHint = lastHint newLastHint = lastHint
v0 = a2 v0 = a2
case fdPreimageWrite: case FdPreimageWrite:
effAddr := a1 & 0xFFffFFfc effAddr := a1 & 0xFFffFFfc
memTracker(effAddr) memTracker(effAddr)
mem := memory.GetMemory(effAddr) mem := memory.GetMemory(effAddr)
...@@ -163,15 +166,15 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b ...@@ -163,15 +166,15 @@ func handleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
return v0, v1, newLastHint, newPreimageKey, newPreimageOffset return v0, v1, newLastHint, newPreimageKey, newPreimageOffset
} }
func handleSysFcntl(a0, a1 uint32) (v0, v1 uint32) { func HandleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
// args: a0 = fd, a1 = cmd // args: a0 = fd, a1 = cmd
v1 = uint32(0) v1 = uint32(0)
if a1 == 3 { // F_GETFL: get file descriptor flags if a1 == 3 { // F_GETFL: get file descriptor flags
switch a0 { switch a0 {
case fdStdin, fdPreimageRead, fdHintRead: case FdStdin, FdPreimageRead, FdHintRead:
v0 = 0 // O_RDONLY v0 = 0 // O_RDONLY
case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite: case FdStdout, FdStderr, FdPreimageWrite, FdHintWrite:
v0 = 1 // O_WRONLY v0 = 1 // O_WRONLY
default: default:
v0 = 0xFFffFFff v0 = 0xFFffFFff
...@@ -185,7 +188,7 @@ func handleSysFcntl(a0, a1 uint32) (v0, v1 uint32) { ...@@ -185,7 +188,7 @@ func handleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
return v0, v1 return v0, v1
} }
func handleSyscallUpdates(cpu *CpuScalars, registers *[32]uint32, v0, v1 uint32) { func HandleSyscallUpdates(cpu *mipsevm.CpuScalars, registers *[32]uint32, v0, v1 uint32) {
registers[2] = v0 registers[2] = v0
registers[7] = v1 registers[7] = v1
......
package mipsevm
import "fmt"
// HexU32 to lazy-format integer attributes for logging
type HexU32 uint32
func (v HexU32) String() string {
return fmt.Sprintf("%08x", uint32(v))
}
func (v HexU32) MarshalText() ([]byte, error) {
return []byte(v.String()), nil
}
package mipsevm package mipsevm
import "github.com/ethereum/go-ethereum/common" import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type FPVMState interface { type FPVMState interface {
GetMemory() *Memory GetMemory() *memory.Memory
// GetPC returns the currently executing program counter // GetPC returns the currently executing program counter
GetPC() uint32 GetPC() uint32
// GetRegisters returns the currently active registers
GetRegisters() *[32]uint32
// GetStep returns the current VM step // GetStep returns the current VM step
GetStep() uint64 GetStep() uint64
......
package mipsevm package memory
import ( import (
"encoding/binary" "encoding/binary"
...@@ -335,3 +335,5 @@ func (m *Memory) Usage() string { ...@@ -335,3 +335,5 @@ func (m *Memory) Usage() string {
// KiB, MiB, GiB, TiB, ... // KiB, MiB, GiB, TiB, ...
return fmt.Sprintf("%.1f %ciB", float64(total)/float64(div), "KMGTPE"[exp]) return fmt.Sprintf("%.1f %ciB", float64(total)/float64(div), "KMGTPE"[exp])
} }
type MemTracker func(addr uint32)
package mipsevm package memory
import ( import (
"bytes" "bytes"
......
package mipsevm package memory
import ( import (
"bytes" "bytes"
......
package mipsevm package memory
import ( import (
"testing" "testing"
......
package mipsevm package multithreaded
import ( import (
"encoding/binary" "encoding/binary"
...@@ -7,6 +7,9 @@ import ( ...@@ -7,6 +7,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
) )
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object // SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
...@@ -28,7 +31,7 @@ type ThreadState struct { ...@@ -28,7 +31,7 @@ type ThreadState struct {
FutexAddr uint32 `json:"futexAddr"` FutexAddr uint32 `json:"futexAddr"`
FutexVal uint32 `json:"futexVal"` FutexVal uint32 `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"` FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu CpuScalars `json:"cpu"` Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]uint32 `json:"registers"` Registers [32]uint32 `json:"registers"`
} }
...@@ -37,7 +40,7 @@ func (t *ThreadState) serializeThread() []byte { ...@@ -37,7 +40,7 @@ func (t *ThreadState) serializeThread() []byte {
out = binary.BigEndian.AppendUint32(out, t.ThreadId) out = binary.BigEndian.AppendUint32(out, t.ThreadId)
out = append(out, t.ExitCode) out = append(out, t.ExitCode)
out = AppendBoolToWitness(out, t.Exited) out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = binary.BigEndian.AppendUint32(out, t.FutexAddr) out = binary.BigEndian.AppendUint32(out, t.FutexAddr)
out = binary.BigEndian.AppendUint32(out, t.FutexVal) out = binary.BigEndian.AppendUint32(out, t.FutexVal)
out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep) out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep)
...@@ -64,26 +67,26 @@ func computeThreadRoot(prevStackRoot common.Hash, threadToPush *ThreadState) com ...@@ -64,26 +67,26 @@ func computeThreadRoot(prevStackRoot common.Hash, threadToPush *ThreadState) com
return crypto.Keccak256Hash(hashData) return crypto.Keccak256Hash(hashData)
} }
// MT_STATE_WITNESS_SIZE is the size of the state witness encoding in bytes. // STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const MT_STATE_WITNESS_SIZE = 163 const STATE_WITNESS_SIZE = 163
const ( const (
MEMROOT_MT_WITNESS_OFFSET = 0 MEMROOT_WITNESS_OFFSET = 0
PREIMAGE_KEY_MT_WITNESS_OFFSET = MEMROOT_MT_WITNESS_OFFSET + 32 PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32
PREIMAGE_OFFSET_MT_WITNESS_OFFSET = PREIMAGE_KEY_MT_WITNESS_OFFSET + 32 PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32
HEAP_MT_WITNESS_OFFSET = PREIMAGE_OFFSET_MT_WITNESS_OFFSET + 4 HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + 4
EXITCODE_MT_WITNESS_OFFSET = HEAP_MT_WITNESS_OFFSET + 4 EXITCODE_WITNESS_OFFSET = HEAP_WITNESS_OFFSET + 4
EXITED_MT_WITNESS_OFFSET = EXITCODE_MT_WITNESS_OFFSET + 1 EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_MT_WITNESS_OFFSET = EXITED_MT_WITNESS_OFFSET + 1 STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_MT_WITNESS_OFFSET = STEP_MT_WITNESS_OFFSET + 8 STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
WAKEUP_MT_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_MT_WITNESS_OFFSET + 8 WAKEUP_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_MT_WITNESS_OFFSET = WAKEUP_MT_WITNESS_OFFSET + 4 TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + 4
LEFT_THREADS_ROOT_MT_WITNESS_OFFSET = TRAVERSE_RIGHT_MT_WITNESS_OFFSET + 1 LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_MT_WITNESS_OFFSET = LEFT_THREADS_ROOT_MT_WITNESS_OFFSET + 32 RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32
THREAD_ID_MT_WITNESS_OFFSET = RIGHT_THREADS_ROOT_MT_WITNESS_OFFSET + 32 THREAD_ID_WITNESS_OFFSET = RIGHT_THREADS_ROOT_WITNESS_OFFSET + 32
) )
type MTState struct { type State struct {
Memory *Memory `json:"memory"` Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"` PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
...@@ -113,13 +116,13 @@ type MTState struct { ...@@ -113,13 +116,13 @@ type MTState struct {
LastHint hexutil.Bytes `json:"lastHint,omitempty"` LastHint hexutil.Bytes `json:"lastHint,omitempty"`
} }
func CreateEmptyMTState() *MTState { func CreateEmptyState() *State {
initThreadId := uint32(0) initThreadId := uint32(0)
initThread := ThreadState{ initThread := ThreadState{
ThreadId: initThreadId, ThreadId: initThreadId,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
Cpu: CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: 0, PC: 0,
NextPC: 0, NextPC: 0,
LO: 0, LO: 0,
...@@ -131,8 +134,8 @@ func CreateEmptyMTState() *MTState { ...@@ -131,8 +134,8 @@ func CreateEmptyMTState() *MTState {
Registers: [32]uint32{}, Registers: [32]uint32{},
} }
return &MTState{ return &State{
Memory: NewMemory(), Memory: memory.NewMemory(),
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -145,8 +148,8 @@ func CreateEmptyMTState() *MTState { ...@@ -145,8 +148,8 @@ func CreateEmptyMTState() *MTState {
} }
} }
func CreateInitialMTState(pc, heapStart uint32) *MTState { func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyMTState() state := CreateEmptyState()
currentThread := state.getCurrentThread() currentThread := state.getCurrentThread()
currentThread.Cpu.PC = pc currentThread.Cpu.PC = pc
currentThread.Cpu.NextPC = pc + 4 currentThread.Cpu.NextPC = pc + 4
...@@ -155,7 +158,7 @@ func CreateInitialMTState(pc, heapStart uint32) *MTState { ...@@ -155,7 +158,7 @@ func CreateInitialMTState(pc, heapStart uint32) *MTState {
return state return state
} }
func (s *MTState) getCurrentThread() *ThreadState { func (s *State) getCurrentThread() *ThreadState {
activeStack := s.getActiveThreadStack() activeStack := s.getActiveThreadStack()
activeStackSize := len(activeStack) activeStackSize := len(activeStack)
...@@ -166,9 +169,7 @@ func (s *MTState) getCurrentThread() *ThreadState { ...@@ -166,9 +169,7 @@ func (s *MTState) getCurrentThread() *ThreadState {
return &activeStack[activeStackSize-1] return &activeStack[activeStackSize-1]
} }
type ThreadMutator func(thread *ThreadState) func (s *State) getActiveThreadStack() []ThreadState {
func (s *MTState) getActiveThreadStack() []ThreadState {
var activeStack []ThreadState var activeStack []ThreadState
if s.TraverseRight { if s.TraverseRight {
activeStack = s.RightThreadStack activeStack = s.RightThreadStack
...@@ -179,15 +180,15 @@ func (s *MTState) getActiveThreadStack() []ThreadState { ...@@ -179,15 +180,15 @@ func (s *MTState) getActiveThreadStack() []ThreadState {
return activeStack return activeStack
} }
func (s *MTState) getRightThreadStackRoot() common.Hash { func (s *State) getRightThreadStackRoot() common.Hash {
return s.calculateThreadStackRoot(s.RightThreadStack) return s.calculateThreadStackRoot(s.RightThreadStack)
} }
func (s *MTState) getLeftThreadStackRoot() common.Hash { func (s *State) getLeftThreadStackRoot() common.Hash {
return s.calculateThreadStackRoot(s.LeftThreadStack) return s.calculateThreadStackRoot(s.LeftThreadStack)
} }
func (s *MTState) calculateThreadStackRoot(stack []ThreadState) common.Hash { func (s *State) calculateThreadStackRoot(stack []ThreadState) common.Hash {
curRoot := EmptyThreadsRoot curRoot := EmptyThreadsRoot
for _, thread := range stack { for _, thread := range stack {
curRoot = computeThreadRoot(curRoot, &thread) curRoot = computeThreadRoot(curRoot, &thread)
...@@ -196,44 +197,49 @@ func (s *MTState) calculateThreadStackRoot(stack []ThreadState) common.Hash { ...@@ -196,44 +197,49 @@ func (s *MTState) calculateThreadStackRoot(stack []ThreadState) common.Hash {
return curRoot return curRoot
} }
func (s *MTState) PreemptThread() { func (s *State) PreemptThread() {
// TODO(CP-903) // TODO(CP-903)
panic("Not Implemented") panic("Not Implemented")
} }
func (s *MTState) PushThread(thread *ThreadState) { func (s *State) PushThread(thread *ThreadState) {
// TODO(CP-903) // TODO(CP-903)
panic("Not Implemented") panic("Not Implemented")
} }
func (s *MTState) GetPC() uint32 { func (s *State) GetPC() uint32 {
activeThread := s.getCurrentThread() activeThread := s.getCurrentThread()
return activeThread.Cpu.PC return activeThread.Cpu.PC
} }
func (s *MTState) GetExitCode() uint8 { return s.ExitCode } func (s *State) GetRegisters() *[32]uint32 {
activeThread := s.getCurrentThread()
return &activeThread.Registers
}
func (s *State) GetExitCode() uint8 { return s.ExitCode }
func (s *MTState) GetExited() bool { return s.Exited } func (s *State) GetExited() bool { return s.Exited }
func (s *MTState) GetStep() uint64 { return s.Step } func (s *State) GetStep() uint64 { return s.Step }
func (s *MTState) VMStatus() uint8 { func (s *State) VMStatus() uint8 {
return vmStatus(s.Exited, s.ExitCode) return mipsevm.VmStatus(s.Exited, s.ExitCode)
} }
func (s *MTState) GetMemory() *Memory { func (s *State) GetMemory() *memory.Memory {
return s.Memory return s.Memory
} }
func (s *MTState) EncodeWitness() ([]byte, common.Hash) { func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, MT_STATE_WITNESS_SIZE) out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot() memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...) out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...) out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset) out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Heap) out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode) out = append(out, s.ExitCode)
out = AppendBoolToWitness(out, s.Exited) out = mipsevm.AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step) out = binary.BigEndian.AppendUint64(out, s.Step)
out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch) out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch)
...@@ -241,31 +247,31 @@ func (s *MTState) EncodeWitness() ([]byte, common.Hash) { ...@@ -241,31 +247,31 @@ func (s *MTState) EncodeWitness() ([]byte, common.Hash) {
leftStackRoot := s.getLeftThreadStackRoot() leftStackRoot := s.getLeftThreadStackRoot()
rightStackRoot := s.getRightThreadStackRoot() rightStackRoot := s.getRightThreadStackRoot()
out = AppendBoolToWitness(out, s.TraverseRight) out = mipsevm.AppendBoolToWitness(out, s.TraverseRight)
out = append(out, (leftStackRoot)[:]...) out = append(out, (leftStackRoot)[:]...)
out = append(out, (rightStackRoot)[:]...) out = append(out, (rightStackRoot)[:]...)
out = binary.BigEndian.AppendUint32(out, s.NextThreadId) out = binary.BigEndian.AppendUint32(out, s.NextThreadId)
return out, mtStateHashFromWitness(out) return out, stateHashFromWitness(out)
} }
type MTStateWitness []byte type StateWitness []byte
func (sw MTStateWitness) StateHash() (common.Hash, error) { func (sw StateWitness) StateHash() (common.Hash, error) {
if len(sw) != MT_STATE_WITNESS_SIZE { if len(sw) != STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), MT_STATE_WITNESS_SIZE) return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE)
} }
return mtStateHashFromWitness(sw), nil return stateHashFromWitness(sw), nil
} }
func mtStateHashFromWitness(sw []byte) common.Hash { func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != MT_STATE_WITNESS_SIZE { if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length") panic("Invalid witness length")
} }
hash := crypto.Keccak256Hash(sw) hash := crypto.Keccak256Hash(sw)
exitCode := sw[EXITCODE_MT_WITNESS_OFFSET] exitCode := sw[EXITCODE_WITNESS_OFFSET]
exited := sw[EXITED_MT_WITNESS_OFFSET] exited := sw[EXITED_WITNESS_OFFSET]
status := vmStatus(exited == 1, exitCode) status := mipsevm.VmStatus(exited == 1, exitCode)
hash[0] = status hash[0] = status
return hash return hash
} }
package mipsevm package multithreaded
import ( import (
"debug/elf" "debug/elf"
...@@ -7,9 +7,12 @@ import ( ...@@ -7,9 +7,12 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
) )
func setWitnessField(witness MTStateWitness, fieldOffset int, fieldData []byte) { func setWitnessField(witness StateWitness, fieldOffset int, fieldData []byte) {
start := fieldOffset start := fieldOffset
end := fieldOffset + len(fieldData) end := fieldOffset + len(fieldData)
copy(witness[start:end], fieldData) copy(witness[start:end], fieldData)
...@@ -17,7 +20,7 @@ func setWitnessField(witness MTStateWitness, fieldOffset int, fieldData []byte) ...@@ -17,7 +20,7 @@ func setWitnessField(witness MTStateWitness, fieldOffset int, fieldData []byte)
// Run through all permutations of `exited` / `exitCode` and ensure that the // Run through all permutations of `exited` / `exitCode` and ensure that the
// correct witness, state hash, and VM Status is produced. // correct witness, state hash, and VM Status is produced.
func TestMTState_EncodeWitness(t *testing.T) { func TestState_EncodeWitness(t *testing.T) {
cases := []struct { cases := []struct {
exited bool exited bool
exitCode uint8 exitCode uint8
...@@ -38,7 +41,7 @@ func TestMTState_EncodeWitness(t *testing.T) { ...@@ -38,7 +41,7 @@ func TestMTState_EncodeWitness(t *testing.T) {
step := uint64(33) step := uint64(33)
stepsSinceContextSwitch := uint64(123) stepsSinceContextSwitch := uint64(123)
for _, c := range cases { for _, c := range cases {
state := CreateEmptyMTState() state := CreateEmptyState()
state.Exited = c.exited state.Exited = c.exited
state.ExitCode = c.exitCode state.ExitCode = c.exitCode
state.PreimageKey = preimageKey state.PreimageKey = preimageKey
...@@ -52,38 +55,38 @@ func TestMTState_EncodeWitness(t *testing.T) { ...@@ -52,38 +55,38 @@ func TestMTState_EncodeWitness(t *testing.T) {
rightStackRoot := EmptyThreadsRoot rightStackRoot := EmptyThreadsRoot
// Set up expected witness // Set up expected witness
expectedWitness := make(MTStateWitness, MT_STATE_WITNESS_SIZE) expectedWitness := make(StateWitness, STATE_WITNESS_SIZE)
setWitnessField(expectedWitness, MEMROOT_MT_WITNESS_OFFSET, memRoot[:]) setWitnessField(expectedWitness, MEMROOT_WITNESS_OFFSET, memRoot[:])
setWitnessField(expectedWitness, PREIMAGE_KEY_MT_WITNESS_OFFSET, preimageKey[:]) setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_MT_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)}) setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)})
setWitnessField(expectedWitness, HEAP_MT_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)}) setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)})
setWitnessField(expectedWitness, EXITCODE_MT_WITNESS_OFFSET, []byte{c.exitCode}) setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode})
if c.exited { if c.exited {
setWitnessField(expectedWitness, EXITED_MT_WITNESS_OFFSET, []byte{1}) setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1})
} }
setWitnessField(expectedWitness, STEP_MT_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)}) setWitnessField(expectedWitness, STEP_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)})
setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_MT_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)}) setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)})
setWitnessField(expectedWitness, WAKEUP_MT_WITNESS_OFFSET, []byte{0xFF, 0xFF, 0xFF, 0xFF}) setWitnessField(expectedWitness, WAKEUP_WITNESS_OFFSET, []byte{0xFF, 0xFF, 0xFF, 0xFF})
setWitnessField(expectedWitness, TRAVERSE_RIGHT_MT_WITNESS_OFFSET, []byte{0}) setWitnessField(expectedWitness, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_MT_WITNESS_OFFSET, leftStackRoot[:]) setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_MT_WITNESS_OFFSET, rightStackRoot[:]) setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:])
setWitnessField(expectedWitness, THREAD_ID_MT_WITNESS_OFFSET, []byte{0, 0, 0, 1}) setWitnessField(expectedWitness, THREAD_ID_WITNESS_OFFSET, []byte{0, 0, 0, 1})
// Validate witness // Validate witness
actualWitness, actualStateHash := state.EncodeWitness() actualWitness, actualStateHash := state.EncodeWitness()
require.Equal(t, len(actualWitness), MT_STATE_WITNESS_SIZE, "Incorrect witness size") require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size")
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness") require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness")
// Validate witness hash // Validate witness hash
expectedStateHash := crypto.Keccak256Hash(actualWitness) expectedStateHash := crypto.Keccak256Hash(actualWitness)
expectedStateHash[0] = vmStatus(c.exited, c.exitCode) expectedStateHash[0] = mipsevm.VmStatus(c.exited, c.exitCode)
require.Equal(t, expectedStateHash, actualStateHash, "Incorrect state hash") require.Equal(t, expectedStateHash, actualStateHash, "Incorrect state hash")
} }
} }
func TestMTState_JSONCodec(t *testing.T) { func TestState_JSONCodec(t *testing.T) {
elfProgram, err := elf.Open("../example/bin/hello.elf") elfProgram, err := elf.Open("../../example/bin/hello.elf")
require.NoError(t, err, "open ELF file") require.NoError(t, err, "open ELF file")
state, err := LoadELF(elfProgram, CreateInitialMTState) state, err := program.LoadELF(elfProgram, CreateInitialState)
require.NoError(t, err, "load ELF into state") require.NoError(t, err, "load ELF into state")
// Set a few additional fields // Set a few additional fields
state.PreimageKey = crypto.Keccak256Hash([]byte{1, 2, 3, 4}) state.PreimageKey = crypto.Keccak256Hash([]byte{1, 2, 3, 4})
...@@ -98,7 +101,7 @@ func TestMTState_JSONCodec(t *testing.T) { ...@@ -98,7 +101,7 @@ func TestMTState_JSONCodec(t *testing.T) {
stateJSON, err := json.Marshal(state) stateJSON, err := json.Marshal(state)
require.NoError(t, err) require.NoError(t, err)
var newState *MTState var newState *State
err = json.Unmarshal(stateJSON, &newState) err = json.Unmarshal(stateJSON, &newState)
require.NoError(t, err) require.NoError(t, err)
...@@ -118,7 +121,7 @@ func TestMTState_JSONCodec(t *testing.T) { ...@@ -118,7 +121,7 @@ func TestMTState_JSONCodec(t *testing.T) {
require.Equal(t, state.LastHint, newState.LastHint) require.Equal(t, state.LastHint, newState.LastHint)
} }
func TestMTState_EmptyThreadsRoot(t *testing.T) { func TestState_EmptyThreadsRoot(t *testing.T) {
data := [64]byte{} data := [64]byte{}
expectedEmptyRoot := crypto.Keccak256Hash(data[:]) expectedEmptyRoot := crypto.Keccak256Hash(data[:])
......
package mipsevm
type PreimageOracle interface {
Hint(v []byte)
GetPreimage(k [32]byte) []byte
}
package program
import (
"bytes"
"debug/elf"
"fmt"
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
)
const HEAP_START = 0x05000000
type CreateFPVMState[T mipsevm.FPVMState] func(pc, heapStart uint32) T
func LoadELF[T mipsevm.FPVMState](f *elf.File, initState CreateFPVMState[T]) (T, error) {
var empty T
s := initState(uint32(f.Entry), HEAP_START)
for i, prog := range f.Progs {
if prog.Type == 0x70000003 { // MIPS_ABIFLAGS
continue
}
r := io.Reader(io.NewSectionReader(prog, 0, int64(prog.Filesz)))
if prog.Filesz != prog.Memsz {
if prog.Type == elf.PT_LOAD {
if prog.Filesz < prog.Memsz {
r = io.MultiReader(r, bytes.NewReader(make([]byte, prog.Memsz-prog.Filesz)))
} else {
return empty, fmt.Errorf("invalid PT_LOAD program segment %d, file size (%d) > mem size (%d)", i, prog.Filesz, prog.Memsz)
}
} else {
return empty, fmt.Errorf("program segment %d has different file size (%d) than mem size (%d): filling for non PT_LOAD segments is not supported", i, prog.Filesz, prog.Memsz)
}
}
if prog.Vaddr+prog.Memsz >= uint64(1<<32) {
return empty, fmt.Errorf("program %d out of 32-bit mem range: %x - %x (size: %x)", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if prog.Vaddr+prog.Memsz >= HEAP_START {
return empty, fmt.Errorf("program %d overlaps with heap: %x - %x (size: %x). The heap start offset must be reconfigured", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if err := s.GetMemory().SetMemoryRange(uint32(prog.Vaddr), r); err != nil {
return empty, fmt.Errorf("failed to read program segment %d: %w", i, err)
}
}
return s, nil
}
package mipsevm package program
import ( import (
"debug/elf" "debug/elf"
...@@ -64,14 +64,3 @@ func (m *Metadata) SymbolMatcher(name string) func(addr uint32) bool { ...@@ -64,14 +64,3 @@ func (m *Metadata) SymbolMatcher(name string) func(addr uint32) bool {
return false return false
} }
} }
// HexU32 to lazy-format integer attributes for logging
type HexU32 uint32
func (v HexU32) String() string {
return fmt.Sprintf("%08x", uint32(v))
}
func (v HexU32) MarshalText() ([]byte, error) {
return []byte(v.String()), nil
}
package mipsevm package program
import ( import (
"bytes" "bytes"
"debug/elf" "debug/elf"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
)
const HEAP_START = 0x05000000
type CreateFPVMState[T FPVMState] func(pc, heapStart uint32) T
func LoadELF[T FPVMState](f *elf.File, initState CreateFPVMState[T]) (T, error) { "github.com/ethereum-optimism/optimism/cannon/mipsevm"
var empty T "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
s := initState(uint32(f.Entry), HEAP_START) )
for i, prog := range f.Progs {
if prog.Type == 0x70000003 { // MIPS_ABIFLAGS
continue
}
r := io.Reader(io.NewSectionReader(prog, 0, int64(prog.Filesz)))
if prog.Filesz != prog.Memsz {
if prog.Type == elf.PT_LOAD {
if prog.Filesz < prog.Memsz {
r = io.MultiReader(r, bytes.NewReader(make([]byte, prog.Memsz-prog.Filesz)))
} else {
return empty, fmt.Errorf("invalid PT_LOAD program segment %d, file size (%d) > mem size (%d)", i, prog.Filesz, prog.Memsz)
}
} else {
return empty, fmt.Errorf("program segment %d has different file size (%d) than mem size (%d): filling for non PT_LOAD segments is not supported", i, prog.Filesz, prog.Memsz)
}
}
if prog.Vaddr+prog.Memsz >= uint64(1<<32) {
return empty, fmt.Errorf("program %d out of 32-bit mem range: %x - %x (size: %x)", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if prog.Vaddr+prog.Memsz >= HEAP_START {
return empty, fmt.Errorf("program %d overlaps with heap: %x - %x (size: %x). The heap start offset must be reconfigured", i, prog.Vaddr, prog.Vaddr+prog.Memsz, prog.Memsz)
}
if err := s.GetMemory().SetMemoryRange(uint32(prog.Vaddr), r); err != nil {
return empty, fmt.Errorf("failed to read program segment %d: %w", i, err)
}
}
return s, nil
}
func PatchGo(f *elf.File, st *State) error { func PatchGo(f *elf.File, st mipsevm.FPVMState) error {
symbols, err := f.Symbols() symbols, err := f.Symbols()
if err != nil { if err != nil {
return fmt.Errorf("failed to read symbols data, cannot patch program: %w", err) return fmt.Errorf("failed to read symbols data, cannot patch program: %w", err)
...@@ -77,14 +39,14 @@ func PatchGo(f *elf.File, st *State) error { ...@@ -77,14 +39,14 @@ func PatchGo(f *elf.File, st *State) error {
// MIPS32 patch: ret (pseudo instruction) // MIPS32 patch: ret (pseudo instruction)
// 03e00008 = jr $ra = ret (pseudo instruction) // 03e00008 = jr $ra = ret (pseudo instruction)
// 00000000 = nop (executes with delay-slot, but does nothing) // 00000000 = nop (executes with delay-slot, but does nothing)
if err := st.Memory.SetMemoryRange(uint32(s.Value), bytes.NewReader([]byte{ if err := st.GetMemory().SetMemoryRange(uint32(s.Value), bytes.NewReader([]byte{
0x03, 0xe0, 0x00, 0x08, 0x03, 0xe0, 0x00, 0x08,
0, 0, 0, 0, 0, 0, 0, 0,
})); err != nil { })); err != nil {
return fmt.Errorf("failed to patch Go runtime.gcenable: %w", err) return fmt.Errorf("failed to patch Go runtime.gcenable: %w", err)
} }
case "runtime.MemProfileRate": case "runtime.MemProfileRate":
if err := st.Memory.SetMemoryRange(uint32(s.Value), bytes.NewReader(make([]byte, 4))); err != nil { // disable mem profiling, to avoid a lot of unnecessary floating point ops if err := st.GetMemory().SetMemoryRange(uint32(s.Value), bytes.NewReader(make([]byte, 4))); err != nil { // disable mem profiling, to avoid a lot of unnecessary floating point ops
return err return err
} }
} }
...@@ -92,19 +54,19 @@ func PatchGo(f *elf.File, st *State) error { ...@@ -92,19 +54,19 @@ func PatchGo(f *elf.File, st *State) error {
return nil return nil
} }
func PatchStack(st *State) error { func PatchStack(st mipsevm.FPVMState) error {
// setup stack pointer // setup stack pointer
sp := uint32(0x7f_ff_d0_00) sp := uint32(0x7f_ff_d0_00)
// allocate 1 page for the initial stack data, and 16KB = 4 pages for the stack to grow // allocate 1 page for the initial stack data, and 16KB = 4 pages for the stack to grow
if err := st.Memory.SetMemoryRange(sp-4*PageSize, bytes.NewReader(make([]byte, 5*PageSize))); err != nil { if err := st.GetMemory().SetMemoryRange(sp-4*memory.PageSize, bytes.NewReader(make([]byte, 5*memory.PageSize))); err != nil {
return fmt.Errorf("failed to allocate page for stack content") return fmt.Errorf("failed to allocate page for stack content")
} }
st.Registers[29] = sp st.GetRegisters()[29] = sp
storeMem := func(addr uint32, v uint32) { storeMem := func(addr uint32, v uint32) {
var dat [4]byte var dat [4]byte
binary.BigEndian.PutUint32(dat[:], v) binary.BigEndian.PutUint32(dat[:], v)
_ = st.Memory.SetMemoryRange(addr, bytes.NewReader(dat[:])) _ = st.GetMemory().SetMemoryRange(addr, bytes.NewReader(dat[:]))
} }
// init argc, argv, aux on stack // init argc, argv, aux on stack
...@@ -117,7 +79,7 @@ func PatchStack(st *State) error { ...@@ -117,7 +79,7 @@ func PatchStack(st *State) error {
storeMem(sp+4*7, sp+4*9) // auxv[3] = address of 16 bytes containing random value storeMem(sp+4*7, sp+4*9) // auxv[3] = address of 16 bytes containing random value
storeMem(sp+4*8, 0) // auxv[term] = 0 storeMem(sp+4*8, 0) // auxv[term] = 0
_ = st.Memory.SetMemoryRange(sp+4*9, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness" _ = st.GetMemory().SetMemoryRange(sp+4*9, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness"
return nil return nil
} }
package mipsevm package singlethreaded
import ( import (
"errors" "errors"
"io" "io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/op-service/jsonutil" "github.com/ethereum-optimism/optimism/op-service/jsonutil"
) )
type PreimageOracle interface {
Hint(v []byte)
GetPreimage(k [32]byte) []byte
}
type Debug struct { type Debug struct {
stack []uint32 stack []uint32
caller []uint32 caller []uint32
meta *Metadata meta *program.Metadata
} }
type InstrumentedState struct { type InstrumentedState struct {
...@@ -41,7 +38,7 @@ type InstrumentedState struct { ...@@ -41,7 +38,7 @@ type InstrumentedState struct {
debugEnabled bool debugEnabled bool
} }
func NewInstrumentedState(state *State, po PreimageOracle, stdOut, stdErr io.Writer) *InstrumentedState { func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer) *InstrumentedState {
return &InstrumentedState{ return &InstrumentedState{
state: state, state: state,
stdOut: stdOut, stdOut: stdOut,
...@@ -50,7 +47,7 @@ func NewInstrumentedState(state *State, po PreimageOracle, stdOut, stdErr io.Wri ...@@ -50,7 +47,7 @@ func NewInstrumentedState(state *State, po PreimageOracle, stdOut, stdErr io.Wri
} }
} }
func NewInstrumentedStateFromFile(stateFile string, po PreimageOracle, stdOut, stdErr io.Writer) (*InstrumentedState, error) { func NewInstrumentedStateFromFile(stateFile string, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer) (*InstrumentedState, error) {
state, err := jsonutil.LoadJSON[State](stateFile) state, err := jsonutil.LoadJSON[State](stateFile)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -63,7 +60,7 @@ func NewInstrumentedStateFromFile(stateFile string, po PreimageOracle, stdOut, s ...@@ -63,7 +60,7 @@ func NewInstrumentedStateFromFile(stateFile string, po PreimageOracle, stdOut, s
}, nil }, nil
} }
func (m *InstrumentedState) InitDebug(meta *Metadata) error { func (m *InstrumentedState) InitDebug(meta *program.Metadata) error {
if meta == nil { if meta == nil {
return errors.New("metadata is nil") return errors.New("metadata is nil")
} }
...@@ -72,7 +69,7 @@ func (m *InstrumentedState) InitDebug(meta *Metadata) error { ...@@ -72,7 +69,7 @@ func (m *InstrumentedState) InitDebug(meta *Metadata) error {
return nil return nil
} }
func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) { func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err error) {
m.memProofEnabled = proof m.memProofEnabled = proof
m.lastMemAccess = ^uint32(0) m.lastMemAccess = ^uint32(0)
m.lastPreimageOffset = ^uint32(0) m.lastPreimageOffset = ^uint32(0)
...@@ -80,7 +77,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) { ...@@ -80,7 +77,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) {
if proof { if proof {
insnProof := m.state.Memory.MerkleProof(m.state.Cpu.PC) insnProof := m.state.Memory.MerkleProof(m.state.Cpu.PC)
encodedWitness, stateHash := m.state.EncodeWitness() encodedWitness, stateHash := m.state.EncodeWitness()
wit = &StepWitness{ wit = &mipsevm.StepWitness{
State: encodedWitness, State: encodedWitness,
StateHash: stateHash, StateHash: stateHash,
ProofData: insnProof[:], ProofData: insnProof[:],
...@@ -106,26 +103,20 @@ func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) { ...@@ -106,26 +103,20 @@ func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) {
return m.lastPreimageKey, m.lastPreimage, m.lastPreimageOffset return m.lastPreimageKey, m.lastPreimage, m.lastPreimageOffset
} }
func (m *InstrumentedState) GetState() FPVMState { func (m *InstrumentedState) GetState() mipsevm.FPVMState {
return m.state return m.state
} }
func (m *InstrumentedState) GetDebugInfo() *DebugInfo { func (m *InstrumentedState) GetDebugInfo() *mipsevm.DebugInfo {
return &DebugInfo{ return &mipsevm.DebugInfo{
Pages: m.state.Memory.PageCount(), Pages: m.state.Memory.PageCount(),
NumPreimageRequests: m.preimageOracle.numPreimageRequests, NumPreimageRequests: m.preimageOracle.numPreimageRequests,
TotalPreimageSize: m.preimageOracle.totalPreimageSize, TotalPreimageSize: m.preimageOracle.totalPreimageSize,
} }
} }
type DebugInfo struct {
Pages int `json:"pages"`
NumPreimageRequests int `json:"num_preimage_requests"`
TotalPreimageSize int `json:"total_preimage_size"`
}
type trackingOracle struct { type trackingOracle struct {
po PreimageOracle po mipsevm.PreimageOracle
totalPreimageSize int totalPreimageSize int
numPreimageRequests int numPreimageRequests int
} }
......
package singlethreaded
import (
"bytes"
"io"
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func TestState(t *testing.T) {
testFiles, err := os.ReadDir("../tests/open_mips_tests/test/bin")
require.NoError(t, err)
for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) {
oracle := testutil.SelectOracleFixture(t, f.Name())
// Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin"
// TODO: currently tests are compiled as flat binary objects
// We can use more standard tooling to compile them to ELF files and get remove maketests.py
fn := path.Join("../tests/open_mips_tests/test/bin", f.Name())
//elfProgram, err := elf.Open()
//require.NoError(t, err, "must load test ELF binary")
//state, err := LoadELF(elfProgram)
//require.NoError(t, err, "must load ELF into state")
programMem, err := os.ReadFile(fn)
require.NoError(t, err)
state := &State{Cpu: mipsevm.CpuScalars{PC: 0, NextPC: 4}, Memory: memory.NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state")
// set the return address ($ra) to jump into when test completes
state.Registers[31] = testutil.EndAddr
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ {
if us.state.Cpu.PC == testutil.EndAddr {
break
}
if exitGroup && us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
if exitGroup {
require.NotEqual(t, uint32(testutil.EndAddr), us.state.Cpu.PC, "must not reach end")
require.True(t, us.state.Exited, "must set exited state")
require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1")
} else {
require.Equal(t, uint32(testutil.EndAddr), us.state.Cpu.PC, "must reach end")
done, result := state.Memory.GetMemory(testutil.BaseAddrEnd+4), state.Memory.GetMemory(testutil.BaseAddrEnd+8)
// inspect test result
require.Equal(t, done, uint32(1), "must be done")
require.Equal(t, result, uint32(1), "must have success result")
}
})
}
}
func TestHello(t *testing.T) {
state := testutil.LoadELFProgram(t, "../../example/bin/hello.elf", CreateInitialState)
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 400_000; i++ {
if us.state.Exited {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, "hello world!\n", stdOutBuf.String(), "stdout says hello")
require.Equal(t, "", stdErrBuf.String(), "stderr silent")
}
func TestClaim(t *testing.T) {
state := testutil.LoadELFProgram(t, "../../example/bin/claim.elf", CreateInitialState)
oracle, expectedStdOut, expectedStdErr := testutil.ClaimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
for i := 0; i < 2000_000; i++ {
if us.GetState().GetExited() {
break
}
_, err := us.Step(false)
require.NoError(t, err)
}
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Equal(t, expectedStdOut, stdOutBuf.String(), "stdout")
require.Equal(t, expectedStdErr, stdErrBuf.String(), "stderr")
}
func TestAlloc(t *testing.T) {
t.Skip("TODO(client-pod#906): Currently fails on Single threaded Cannon. Re-enable for the MT FPVM")
state := testutil.LoadELFProgram(t, "../example/bin/alloc.elf", CreateInitialState)
const numAllocs = 100 // where each alloc is a 32 MiB chunk
oracle := testutil.AllocOracle(t, numAllocs)
// completes in ~870 M steps
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 20_000_000_000; i++ {
if us.GetState().GetExited() {
break
}
_, err := us.Step(false)
require.NoError(t, err)
if state.Step%10_000_000 == 0 {
t.Logf("Completed %d steps", state.Step)
}
}
t.Logf("Completed in %d steps", state.Step)
require.True(t, state.Exited, "must complete program")
require.Equal(t, uint8(0), state.ExitCode, "exit with 0")
require.Less(t, state.Memory.PageCount()*memory.PageSize, 1*1024*1024*1024, "must not allocate more than 1 GiB")
}
package mipsevm package singlethreaded
import ( import (
"encoding/binary" "encoding/binary"
...@@ -6,9 +6,9 @@ import ( ...@@ -6,9 +6,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
)
type MemTracker func(addr uint32) "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) { func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage preimage := m.lastPreimage
...@@ -37,46 +37,46 @@ func (m *InstrumentedState) trackMemAccess(effAddr uint32) { ...@@ -37,46 +37,46 @@ func (m *InstrumentedState) trackMemAccess(effAddr uint32) {
} }
func (m *InstrumentedState) handleSyscall() error { func (m *InstrumentedState) handleSyscall() error {
syscallNum, a0, a1, a2 := getSyscallArgs(&m.state.Registers) syscallNum, a0, a1, a2 := exec.GetSyscallArgs(&m.state.Registers)
v0 := uint32(0) v0 := uint32(0)
v1 := uint32(0) v1 := uint32(0)
//fmt.Printf("syscall: %d\n", syscallNum) //fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum { switch syscallNum {
case sysMmap: case exec.SysMmap:
var newHeap uint32 var newHeap uint32
v0, v1, newHeap = handleSysMmap(a0, a1, m.state.Heap) v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap)
m.state.Heap = newHeap m.state.Heap = newHeap
case sysBrk: case exec.SysBrk:
v0 = 0x40000000 v0 = 0x40000000
case sysClone: // clone (not supported) case exec.SysClone: // clone (not supported)
v0 = 1 v0 = 1
case sysExitGroup: case exec.SysExitGroup:
m.state.Exited = true m.state.Exited = true
m.state.ExitCode = uint8(a0) m.state.ExitCode = uint8(a0)
return nil return nil
case sysRead: case exec.SysRead:
var newPreimageOffset uint32 var newPreimageOffset uint32
v0, v1, newPreimageOffset = handleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.readPreimage, m.state.Memory, m.trackMemAccess) v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.readPreimage, m.state.Memory, m.trackMemAccess)
m.state.PreimageOffset = newPreimageOffset m.state.PreimageOffset = newPreimageOffset
case sysWrite: case exec.SysWrite:
var newLastHint hexutil.Bytes var newLastHint hexutil.Bytes
var newPreimageKey common.Hash var newPreimageKey common.Hash
var newPreimageOffset uint32 var newPreimageOffset uint32
v0, v1, newLastHint, newPreimageKey, newPreimageOffset = handleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.trackMemAccess, m.stdOut, m.stdErr) v0, v1, newLastHint, newPreimageKey, newPreimageOffset = exec.HandleSysWrite(a0, a1, a2, m.state.LastHint, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.trackMemAccess, m.stdOut, m.stdErr)
m.state.LastHint = newLastHint m.state.LastHint = newLastHint
m.state.PreimageKey = newPreimageKey m.state.PreimageKey = newPreimageKey
m.state.PreimageOffset = newPreimageOffset m.state.PreimageOffset = newPreimageOffset
case sysFcntl: case exec.SysFcntl:
v0, v1 = handleSysFcntl(a0, a1) v0, v1 = exec.HandleSysFcntl(a0, a1)
} }
handleSyscallUpdates(&m.state.Cpu, &m.state.Registers, v0, v1) exec.HandleSyscallUpdates(&m.state.Cpu, &m.state.Registers, v0, v1)
return nil return nil
} }
func (m *InstrumentedState) pushStack(target uint32) { func (m *InstrumentedState) PushStack(target uint32) {
if !m.debugEnabled { if !m.debugEnabled {
return return
} }
...@@ -84,7 +84,7 @@ func (m *InstrumentedState) pushStack(target uint32) { ...@@ -84,7 +84,7 @@ func (m *InstrumentedState) pushStack(target uint32) {
m.debug.caller = append(m.debug.caller, m.state.Cpu.PC) m.debug.caller = append(m.debug.caller, m.state.Cpu.PC)
} }
func (m *InstrumentedState) popStack() { func (m *InstrumentedState) PopStack() {
if !m.debugEnabled { if !m.debugEnabled {
return return
} }
...@@ -125,7 +125,7 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -125,7 +125,7 @@ func (m *InstrumentedState) mipsStep() error {
} }
m.state.Step += 1 m.state.Step += 1
// instruction fetch // instruction fetch
insn, opcode, fun := getInstructionDetails(m.state.Cpu.PC, m.state.Memory) insn, opcode, fun := exec.GetInstructionDetails(m.state.Cpu.PC, m.state.Memory)
// Handle syscall separately // Handle syscall separately
// syscall (can read and write) // syscall (can read and write)
...@@ -134,5 +134,5 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -134,5 +134,5 @@ func (m *InstrumentedState) mipsStep() error {
} }
// Exec the rest of the step logic // Exec the rest of the step logic
return execMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m) return exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m)
} }
package singlethreaded
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 226
type State struct {
Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
Cpu mipsevm.CpuScalars `json:"cpu"`
Heap uint32 `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
// It is used to remember the last pre-image hint,
// so a VM can start from any state without fetching prior pre-images,
// and instead just repeat the last hint on setup,
// to make sure pre-image requests can be served.
// The first 4 bytes are a uin32 length prefix.
// Warning: the hint MAY NOT BE COMPLETE. I.e. this is buffered,
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func CreateEmptyState() *State {
return &State{
Cpu: mipsevm.CpuScalars{
PC: 0,
NextPC: 0,
LO: 0,
HI: 0,
},
Heap: 0,
Registers: [32]uint32{},
Memory: memory.NewMemory(),
ExitCode: 0,
Exited: false,
Step: 0,
}
}
func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyState()
state.Cpu.PC = pc
state.Cpu.NextPC = pc + 4
state.Heap = heapStart
return state
}
type stateMarshaling struct {
Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"`
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
Heap uint32 `json:"heap"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func (s *State) MarshalJSON() ([]byte, error) { // nosemgrep
sm := &stateMarshaling{
Memory: s.Memory,
PreimageKey: s.PreimageKey,
PreimageOffset: s.PreimageOffset,
PC: s.Cpu.PC,
NextPC: s.Cpu.NextPC,
LO: s.Cpu.LO,
HI: s.Cpu.HI,
Heap: s.Heap,
ExitCode: s.ExitCode,
Exited: s.Exited,
Step: s.Step,
Registers: s.Registers,
LastHint: s.LastHint,
}
return json.Marshal(sm)
}
func (s *State) UnmarshalJSON(data []byte) error {
sm := new(stateMarshaling)
if err := json.Unmarshal(data, sm); err != nil {
return err
}
s.Memory = sm.Memory
s.PreimageKey = sm.PreimageKey
s.PreimageOffset = sm.PreimageOffset
s.Cpu.PC = sm.PC
s.Cpu.NextPC = sm.NextPC
s.Cpu.LO = sm.LO
s.Cpu.HI = sm.HI
s.Heap = sm.Heap
s.ExitCode = sm.ExitCode
s.Exited = sm.Exited
s.Step = sm.Step
s.Registers = sm.Registers
s.LastHint = sm.LastHint
return nil
}
func (s *State) GetPC() uint32 { return s.Cpu.PC }
func (s *State) GetRegisters() *[32]uint32 { return &s.Registers }
func (s *State) GetExitCode() uint8 { return s.ExitCode }
func (s *State) GetExited() bool { return s.Exited }
func (s *State) GetStep() uint64 { return s.Step }
func (s *State) VMStatus() uint8 {
return mipsevm.VmStatus(s.Exited, s.ExitCode)
}
func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Cpu.PC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.NextPC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.LO)
out = binary.BigEndian.AppendUint32(out, s.Cpu.HI)
out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode)
out = mipsevm.AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step)
for _, r := range s.Registers {
out = binary.BigEndian.AppendUint32(out, r)
}
return out, stateHashFromWitness(out)
}
type StateWitness []byte
func (sw StateWitness) StateHash() (common.Hash, error) {
if len(sw) != STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE)
}
return stateHashFromWitness(sw), nil
}
func GetStateHashFn() mipsevm.HashFn {
return func(sw []byte) (common.Hash, error) {
return StateWitness(sw).StateHash()
}
}
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
}
hash := crypto.Keccak256Hash(sw)
offset := 32*2 + 4*6
exitCode := sw[offset]
exited := sw[offset+1]
status := mipsevm.VmStatus(exited == 1, exitCode)
hash[0] = status
return hash
}
package singlethreaded
import (
"debug/elf"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
// Run through all permutations of `exited` / `exitCode` and ensure that the
// correct witness, state hash, and VM Status is produced.
func TestStateHash(t *testing.T) {
cases := []struct {
exited bool
exitCode uint8
}{
{exited: false, exitCode: 0},
{exited: false, exitCode: 1},
{exited: false, exitCode: 2},
{exited: false, exitCode: 3},
{exited: true, exitCode: 0},
{exited: true, exitCode: 1},
{exited: true, exitCode: 2},
{exited: true, exitCode: 3},
}
exitedOffset := 32*2 + 4*6
for _, c := range cases {
state := &State{
Memory: memory.NewMemory(),
Exited: c.exited,
ExitCode: c.exitCode,
}
actualWitness, actualStateHash := state.EncodeWitness()
require.Equal(t, len(actualWitness), STATE_WITNESS_SIZE, "Incorrect witness size")
expectedWitness := make(StateWitness, 226)
memRoot := state.Memory.MerkleRoot()
copy(expectedWitness[:32], memRoot[:])
expectedWitness[exitedOffset] = c.exitCode
var exited uint8
if c.exited {
exited = 1
}
expectedWitness[exitedOffset+1] = uint8(exited)
require.EqualValues(t, expectedWitness[:], actualWitness[:], "Incorrect witness")
expectedStateHash := crypto.Keccak256Hash(actualWitness)
expectedStateHash[0] = mipsevm.VmStatus(c.exited, c.exitCode)
require.Equal(t, expectedStateHash, actualStateHash, "Incorrect state hash")
}
}
func TestStateJSONCodec(t *testing.T) {
elfProgram, err := elf.Open("../../example/bin/hello.elf")
require.NoError(t, err, "open ELF file")
state, err := program.LoadELF(elfProgram, CreateInitialState)
require.NoError(t, err, "load ELF into state")
stateJSON, err := state.MarshalJSON()
require.NoError(t, err)
newState := new(State)
require.NoError(t, newState.UnmarshalJSON(stateJSON))
require.Equal(t, state.PreimageKey, newState.PreimageKey)
require.Equal(t, state.PreimageOffset, newState.PreimageOffset)
require.Equal(t, state.Cpu, newState.Cpu)
require.Equal(t, state.Heap, newState.Heap)
require.Equal(t, state.ExitCode, newState.ExitCode)
require.Equal(t, state.Exited, newState.Exited)
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Registers, newState.Registers)
require.Equal(t, state.Step, newState.Step)
}
package mipsevm package mipsevm
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 226
type CpuScalars struct { type CpuScalars struct {
PC uint32 `json:"pc"` PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"` NextPC uint32 `json:"nextPC"`
...@@ -20,154 +7,6 @@ type CpuScalars struct { ...@@ -20,154 +7,6 @@ type CpuScalars struct {
HI uint32 `json:"hi"` HI uint32 `json:"hi"`
} }
type State struct {
Memory *Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
Cpu CpuScalars `json:"cpu"`
Heap uint32 `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
// It is used to remember the last pre-image hint,
// so a VM can start from any state without fetching prior pre-images,
// and instead just repeat the last hint on setup,
// to make sure pre-image requests can be served.
// The first 4 bytes are a uin32 length prefix.
// Warning: the hint MAY NOT BE COMPLETE. I.e. this is buffered,
// and should only be read when len(LastHint) > 4 && uint32(LastHint[:4]) <= len(LastHint[4:])
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func CreateEmptyState() *State {
return &State{
Cpu: CpuScalars{
PC: 0,
NextPC: 0,
LO: 0,
HI: 0,
},
Heap: 0,
Registers: [32]uint32{},
Memory: NewMemory(),
ExitCode: 0,
Exited: false,
Step: 0,
}
}
func CreateInitialState(pc, heapStart uint32) *State {
state := CreateEmptyState()
state.Cpu.PC = pc
state.Cpu.NextPC = pc + 4
state.Heap = heapStart
return state
}
type stateMarshaling struct {
Memory *Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"`
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
Heap uint32 `json:"heap"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
func (s *State) MarshalJSON() ([]byte, error) { // nosemgrep
sm := &stateMarshaling{
Memory: s.Memory,
PreimageKey: s.PreimageKey,
PreimageOffset: s.PreimageOffset,
PC: s.Cpu.PC,
NextPC: s.Cpu.NextPC,
LO: s.Cpu.LO,
HI: s.Cpu.HI,
Heap: s.Heap,
ExitCode: s.ExitCode,
Exited: s.Exited,
Step: s.Step,
Registers: s.Registers,
LastHint: s.LastHint,
}
return json.Marshal(sm)
}
func (s *State) UnmarshalJSON(data []byte) error {
sm := new(stateMarshaling)
if err := json.Unmarshal(data, sm); err != nil {
return err
}
s.Memory = sm.Memory
s.PreimageKey = sm.PreimageKey
s.PreimageOffset = sm.PreimageOffset
s.Cpu.PC = sm.PC
s.Cpu.NextPC = sm.NextPC
s.Cpu.LO = sm.LO
s.Cpu.HI = sm.HI
s.Heap = sm.Heap
s.ExitCode = sm.ExitCode
s.Exited = sm.Exited
s.Step = sm.Step
s.Registers = sm.Registers
s.LastHint = sm.LastHint
return nil
}
func (s *State) GetPC() uint32 { return s.Cpu.PC }
func (s *State) GetExitCode() uint8 { return s.ExitCode }
func (s *State) GetExited() bool { return s.Exited }
func (s *State) GetStep() uint64 { return s.Step }
func (s *State) VMStatus() uint8 {
return vmStatus(s.Exited, s.ExitCode)
}
func (s *State) GetMemory() *Memory {
return s.Memory
}
func (s *State) EncodeWitness() ([]byte, common.Hash) {
out := make([]byte, 0, STATE_WITNESS_SIZE)
memRoot := s.Memory.MerkleRoot()
out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Cpu.PC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.NextPC)
out = binary.BigEndian.AppendUint32(out, s.Cpu.LO)
out = binary.BigEndian.AppendUint32(out, s.Cpu.HI)
out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode)
out = AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step)
for _, r := range s.Registers {
out = binary.BigEndian.AppendUint32(out, r)
}
return out, stateHashFromWitness(out)
}
type StateWitness []byte
const ( const (
VMStatusValid = 0 VMStatusValid = 0
VMStatusInvalid = 1 VMStatusInvalid = 1
...@@ -175,27 +14,7 @@ const ( ...@@ -175,27 +14,7 @@ const (
VMStatusUnfinished = 3 VMStatusUnfinished = 3
) )
func (sw StateWitness) StateHash() (common.Hash, error) { func VmStatus(exited bool, exitCode uint8) uint8 {
if len(sw) != STATE_WITNESS_SIZE {
return common.Hash{}, fmt.Errorf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE)
}
return stateHashFromWitness(sw), nil
}
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
}
hash := crypto.Keccak256Hash(sw)
offset := 32*2 + 4*6
exitCode := sw[offset]
exited := sw[offset+1]
status := vmStatus(exited == 1, exitCode)
hash[0] = status
return hash
}
func vmStatus(exited bool, exitCode uint8) uint8 {
if !exited { if !exited {
return VMStatusUnfinished return VMStatusUnfinished
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Tests from https://github.com/grantae/OpenMIPS/tree/d606b35e9d5260aef20de2a58660c8303a681e9c/software/test/macro/tests Tests from https://github.com/grantae/OpenMIPS/tree/d606b35e9d5260aef20de2a58660c8303a681e9c/software/test/macro/tests
OpenMIPS is licensed LGPLv3 (as seen in the root of the repository), see [`LICENSE`](./LICENSE) file. OpenMIPS is licensed LGPLv3 (as seen in the root of the repository), see [`LICENSE`](LICENSE) file.
Note that some build-system files from 2014/2015 in that repository by the same author are marked as BSD licensed, Note that some build-system files from 2014/2015 in that repository by the same author are marked as BSD licensed,
but the build-system is not used here. but the build-system is not used here.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment