Commit a2653a38 authored by Inphi's avatar Inphi Committed by GitHub

cannon: 64-bit Refactor (#12029)

* cannon: 64-bit Refactor

Refactor Cannon codebase to support both 32-bit and 64-bit MIPS emulation
while reusing code as much as possible.

* fix 64-bit test compilation errors

* review comments

* more review comments

* fcntl syscall err for 64-bit

* simplify pad check

* cannon: sc must store lsb 32 on 64-bits

* lint

* fix sc 64-bit logic

* add TODO state test
parent fed6f354
......@@ -162,6 +162,9 @@ jobs:
description: Whether to notify on failure
type: boolean
default: false
mips64:
type: boolean
default: false
resource_class: xlarge
steps:
- checkout
......@@ -184,14 +187,29 @@ jobs:
command: |
make lint
working_directory: cannon
- when:
condition:
not: <<parameters.mips64>>
steps:
- run:
name: Cannon Go tests
name: Cannon Go 32-bit tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
mkdir -p /testlogs
gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \
-- -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
working_directory: cannon
- when:
condition: <<parameters.mips64>>
steps:
- run:
name: Cannon Go 64-bit tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
mkdir -p /testlogs
gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \
-- --tags=cannon64 -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
working_directory: cannon
- run:
name: upload Cannon coverage
command: codecov --verbose --clean --flags cannon-go-tests
......
......@@ -15,18 +15,25 @@ endif
.DEFAULT_GOAL := cannon
cannon-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon-impl .
cannon32-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon32 -v $(LDFLAGS) -o ./bin/cannon32-impl .
cannon-embeds: cannon-impl
@cp bin/cannon-impl ./multicannon/embeds/cannon-2
@cp bin/cannon-impl ./multicannon/embeds/cannon-1
cannon64-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon64 -v $(LDFLAGS) -o ./bin/cannon64-impl .
cannon-embeds: cannon32-impl cannon64-impl
# singlethreaded-v2
@cp bin/cannon32-impl ./multicannon/embeds/cannon-2
# multithreaded
@cp bin/cannon32-impl ./multicannon/embeds/cannon-1
# 64-bit multithreaded
@cp bin/cannon64-impl ./multicannon/embeds/cannon-3
cannon: cannon-embeds
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/
clean:
rm -rf bin
rm -rf bin multicannon/embeds/cannon*
elf:
make -C ./testdata/example elf
......@@ -84,9 +91,10 @@ fuzz:
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests
.PHONY: \
cannon \
cannon-impl \
cannon32-impl \
cannon64-impl \
cannon-embeds \
cannon \
clean \
test \
lint \
......
......@@ -25,7 +25,7 @@ var (
}
LoadELFPathFlag = &cli.PathFlag{
Name: "path",
Usage: "Path to 32-bit big-endian MIPS ELF file",
Usage: "Path to 32/64-bit big-endian MIPS ELF file",
TakesFile: true,
Required: true,
}
......@@ -80,7 +80,7 @@ func LoadELF(ctx *cli.Context) error {
}
return program.PatchStack(state)
}
case versions.VersionMultiThreaded:
case versions.VersionMultiThreaded, versions.VersionMultiThreaded64:
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, multithreaded.CreateInitialState)
}
......
......@@ -11,19 +11,19 @@ import (
"strings"
"time"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
"github.com/ethereum-optimism/optimism/cannon/serialize"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/pkg/profile"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)
var (
......@@ -128,7 +128,7 @@ type Proof struct {
OracleKey hexutil.Bytes `json:"oracle-key,omitempty"`
OracleValue hexutil.Bytes `json:"oracle-value,omitempty"`
OracleOffset uint32 `json:"oracle-offset,omitempty"`
OracleOffset arch.Word `json:"oracle-offset,omitempty"`
}
type rawHint string
......@@ -288,7 +288,7 @@ func Run(ctx *cli.Context) error {
stopAtAnyPreimage := false
var stopAtPreimageKeyPrefix []byte
stopAtPreimageOffset := uint32(0)
stopAtPreimageOffset := arch.Word(0)
if ctx.IsSet(RunStopAtPreimageFlag.Name) {
val := ctx.String(RunStopAtPreimageFlag.Name)
parts := strings.Split(val, "@")
......@@ -297,11 +297,11 @@ func Run(ctx *cli.Context) error {
}
stopAtPreimageKeyPrefix = common.FromHex(parts[0])
if len(parts) == 2 {
x, err := strconv.ParseUint(parts[1], 10, 32)
x, err := strconv.ParseUint(parts[1], 10, arch.WordSizeBytes)
if err != nil {
return fmt.Errorf("invalid preimage offset: %w", err)
}
stopAtPreimageOffset = uint32(x)
stopAtPreimageOffset = arch.Word(x)
}
} else {
switch ctx.String(RunStopAtPreimageTypeFlag.Name) {
......@@ -463,7 +463,7 @@ func Run(ctx *cli.Context) error {
}
lastPreimageKey, lastPreimageValue, lastPreimageOffset := vm.LastPreimage()
if lastPreimageOffset != ^uint32(0) {
if lastPreimageOffset != ^arch.Word(0) {
if stopAtAnyPreimage {
l.Info("Stopping at preimage read")
break
......
......@@ -14,7 +14,7 @@ import (
func main() {
app := cli.NewApp()
app.Name = "cannon"
app.Name = os.Args[0]
app.Usage = "MIPS Fault Proof tool"
app.Description = "MIPS Fault Proof tool"
app.Commands = []*cli.Command{
......
//go:build !cannon64
// +build !cannon64
package arch
import "encoding/binary"
type (
// Word differs from the tradditional meaning in MIPS. The type represents the *maximum* architecture specific access length and value sizes.
Word = uint32
// SignedInteger specifies the maximum signed integer type used for arithmetic.
SignedInteger = int32
)
const (
IsMips32 = true
WordSize = 32
WordSizeBytes = WordSize >> 3
PageAddrSize = 12
PageKeySize = WordSize - PageAddrSize
MemProofLeafCount = 28
MemProofSize = MemProofLeafCount * 32
AddressMask = 0xFFffFFfc
ExtMask = 0x3
HeapStart = 0x05_00_00_00
HeapEnd = 0x60_00_00_00
ProgramBreak = 0x40_00_00_00
HighMemoryStart = 0x7f_ff_d0_00
)
var ByteOrderWord = byteOrder32{}
type byteOrder32 struct{}
func (bo byteOrder32) Word(b []byte) Word {
return binary.BigEndian.Uint32(b)
}
func (bo byteOrder32) AppendWord(b []byte, v uint32) []byte {
return binary.BigEndian.AppendUint32(b, v)
}
func (bo byteOrder32) PutWord(b []byte, v uint32) {
binary.BigEndian.PutUint32(b, v)
}
//go:build cannon64
// +build cannon64
package arch
import "encoding/binary"
type (
// Word differs from the tradditional meaning in MIPS. The type represents the *maximum* architecture specific access length and value sizes
Word = uint64
// SignedInteger specifies the maximum signed integer type used for arithmetic.
SignedInteger = int64
)
const (
IsMips32 = false
WordSize = 64
WordSizeBytes = WordSize >> 3
PageAddrSize = 12
PageKeySize = WordSize - PageAddrSize
MemProofLeafCount = 60
MemProofSize = MemProofLeafCount * 32
AddressMask = 0xFFFFFFFFFFFFFFF8
ExtMask = 0x7
HeapStart = 0x10_00_00_00_00_00_00_00
HeapEnd = 0x60_00_00_00_00_00_00_00
ProgramBreak = 0x40_00_00_00_00_00_00_00
HighMemoryStart = 0x7F_FF_FF_FF_D0_00_00_00
)
var ByteOrderWord = byteOrder64{}
type byteOrder64 struct{}
func (bo byteOrder64) Word(b []byte) Word {
return binary.BigEndian.Uint64(b)
}
func (bo byteOrder64) AppendWord(b []byte, v uint64) []byte {
return binary.BigEndian.AppendUint64(b, v)
}
func (bo byteOrder64) PutWord(b []byte, v uint64) {
binary.BigEndian.PutUint64(b, v)
}
package arch
type ByteOrder interface {
Word([]byte) Word
AppendWord([]byte, Word) []byte
PutWord([]byte, Word)
}
......@@ -7,12 +7,12 @@ import (
)
type MemTracker interface {
TrackMemAccess(addr uint32)
TrackMemAccess(addr Word)
}
type MemoryTrackerImpl struct {
memory *memory.Memory
lastMemAccess uint32
lastMemAccess Word
memProofEnabled bool
// proof of first unique memory access
memProof [memory.MEM_PROOF_SIZE]byte
......@@ -24,9 +24,9 @@ func NewMemoryTracker(memory *memory.Memory) *MemoryTrackerImpl {
return &MemoryTrackerImpl{memory: memory}
}
func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) {
func (m *MemoryTrackerImpl) TrackMemAccess(effAddr Word) {
if m.memProofEnabled && m.lastMemAccess != effAddr {
if m.lastMemAccess != ^uint32(0) {
if m.lastMemAccess != ^Word(0) {
panic(fmt.Errorf("unexpected different mem access at %08x, already have access at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
......@@ -36,7 +36,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) {
// TrackMemAccess2 creates a proof for a memory access following a call to TrackMemAccess
// This is used to generate proofs for contiguous memory accesses within the same step
func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr uint32) {
func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr Word) {
if m.memProofEnabled && m.lastMemAccess+4 != effAddr {
panic(fmt.Errorf("unexpected disjointed mem access at %08x, last memory access is at %08x buffered", effAddr, m.lastMemAccess))
}
......@@ -46,7 +46,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr uint32) {
func (m *MemoryTrackerImpl) Reset(enableProof bool) {
m.memProofEnabled = enableProof
m.lastMemAccess = ^uint32(0)
m.lastMemAccess = ^Word(0)
}
func (m *MemoryTrackerImpl) MemProof() [memory.MEM_PROOF_SIZE]byte {
......
This diff is collapsed.
......@@ -8,10 +8,18 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
type Word = arch.Word
const (
AddressMask = arch.AddressMask
)
// TODO(#12205): redefine syscalls for MIPS64
// Syscall codes
const (
SysMmap = 4090
......@@ -79,7 +87,7 @@ const (
// Errors
const (
SysErrorSignal = ^uint32(0)
SysErrorSignal = ^Word(0)
MipsEBADF = 0x9
MipsEINVAL = 0x16
MipsEAGAIN = 0xb
......@@ -92,7 +100,7 @@ const (
FutexWakePrivate = 129
FutexTimeoutSteps = 10_000
FutexNoTimeout = ^uint64(0)
FutexEmptyAddr = ^uint32(0)
FutexEmptyAddr = ^Word(0)
)
// SysClone flags
......@@ -145,7 +153,7 @@ const (
ClockGettimeMonotonicFlag = 1
)
func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) {
func GetSyscallArgs(registers *[32]Word) (syscallNum, a0, a1, a2, a3 Word) {
syscallNum = registers[2] // v0
a0 = registers[4]
......@@ -156,8 +164,8 @@ func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) {
return syscallNum, a0, a1, a2, a3
}
func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
v1 = uint32(0)
func HandleSysMmap(a0, a1, heap Word) (v0, v1, newHeap Word) {
v1 = Word(0)
newHeap = heap
sz := a1
......@@ -182,34 +190,41 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
return v0, v1, newHeap
}
func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.Memory, memTracker MemTracker) (v0, v1, newPreimageOffset uint32, memUpdated bool, memAddr uint32) {
func HandleSysRead(
a0, a1, a2 Word,
preimageKey [32]byte,
preimageOffset Word,
preimageReader PreimageReader,
memory *memory.Memory,
memTracker MemTracker,
) (v0, v1, newPreimageOffset Word, memUpdated bool, memAddr Word) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code
v0 = uint32(0)
v1 = uint32(0)
v0 = Word(0)
v1 = Word(0)
newPreimageOffset = preimageOffset
switch a0 {
case FdStdin:
// leave v0 and v1 zero: read nothing, no error
case FdPreimageRead: // pre-image oracle
effAddr := a1 & 0xFFffFFfc
effAddr := a1 & AddressMask
memTracker.TrackMemAccess(effAddr)
mem := memory.GetMemory(effAddr)
mem := memory.GetWord(effAddr)
dat, datLen := preimageReader.ReadPreimage(preimageKey, preimageOffset)
//fmt.Printf("reading pre-image data: addr: %08x, offset: %d, datLen: %d, data: %x, key: %s count: %d\n", a1, preimageOffset, datLen, dat[:datLen], preimageKey, a2)
alignment := a1 & 3
space := 4 - alignment
alignment := a1 & arch.ExtMask
space := arch.WordSizeBytes - alignment
if space < datLen {
datLen = space
}
if a2 < datLen {
datLen = a2
}
var outMem [4]byte
binary.BigEndian.PutUint32(outMem[:], mem)
var outMem [arch.WordSizeBytes]byte
arch.ByteOrderWord.PutWord(outMem[:], mem)
copy(outMem[alignment:], dat[:datLen])
memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:]))
memory.SetWord(effAddr, arch.ByteOrderWord.Word(outMem[:]))
memUpdated = true
memAddr = effAddr
newPreimageOffset += datLen
......@@ -219,17 +234,25 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
// don't actually read into memory, just say we read it all, we ignore the result anyway
v0 = a2
default:
v0 = 0xFFffFFff
v0 = ^Word(0)
v1 = MipsEBADF
}
return v0, v1, newPreimageOffset, memUpdated, memAddr
}
func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]byte, preimageOffset uint32, oracle mipsevm.PreimageOracle, memory *memory.Memory, memTracker MemTracker, stdOut, stdErr io.Writer) (v0, v1 uint32, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset uint32) {
func HandleSysWrite(a0, a1, a2 Word,
lastHint hexutil.Bytes,
preimageKey [32]byte,
preimageOffset Word,
oracle mipsevm.PreimageOracle,
memory *memory.Memory,
memTracker MemTracker,
stdOut, stdErr io.Writer,
) (v0, v1 Word, newLastHint hexutil.Bytes, newPreimageKey common.Hash, newPreimageOffset Word) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code
v1 = uint32(0)
v1 = Word(0)
newLastHint = lastHint
newPreimageKey = preimageKey
newPreimageOffset = preimageOffset
......@@ -257,41 +280,41 @@ func HandleSysWrite(a0, a1, a2 uint32, lastHint hexutil.Bytes, preimageKey [32]b
newLastHint = lastHint
v0 = a2
case FdPreimageWrite:
effAddr := a1 & 0xFFffFFfc
effAddr := a1 & arch.AddressMask
memTracker.TrackMemAccess(effAddr)
mem := memory.GetMemory(effAddr)
mem := memory.GetWord(effAddr)
key := preimageKey
alignment := a1 & 3
space := 4 - alignment
alignment := a1 & arch.ExtMask
space := arch.WordSizeBytes - alignment
if space < a2 {
a2 = space
}
copy(key[:], key[a2:])
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], mem)
var tmp [arch.WordSizeBytes]byte
arch.ByteOrderWord.PutWord(tmp[:], mem)
copy(key[32-a2:], tmp[alignment:])
newPreimageKey = key
newPreimageOffset = 0
//fmt.Printf("updating pre-image key: %s\n", m.state.PreimageKey)
v0 = a2
default:
v0 = 0xFFffFFff
v0 = ^Word(0)
v1 = MipsEBADF
}
return v0, v1, newLastHint, newPreimageKey, newPreimageOffset
}
func HandleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
func HandleSysFcntl(a0, a1 Word) (v0, v1 Word) {
// args: a0 = fd, a1 = cmd
v1 = uint32(0)
v1 = Word(0)
if a1 == 1 { // F_GETFD: get file descriptor flags
switch a0 {
case FdStdin, FdStdout, FdStderr, FdPreimageRead, FdHintRead, FdPreimageWrite, FdHintWrite:
v0 = 0 // No flags set
default:
v0 = 0xFFffFFff
v0 = ^Word(0)
v1 = MipsEBADF
}
} else if a1 == 3 { // F_GETFL: get file status flags
......@@ -301,18 +324,18 @@ func HandleSysFcntl(a0, a1 uint32) (v0, v1 uint32) {
case FdStdout, FdStderr, FdPreimageWrite, FdHintWrite:
v0 = 1 // O_WRONLY
default:
v0 = 0xFFffFFff
v0 = ^Word(0)
v1 = MipsEBADF
}
} else {
v0 = 0xFFffFFff
v0 = ^Word(0)
v1 = MipsEINVAL // cmd not recognized by this kernel
}
return v0, v1
}
func HandleSyscallUpdates(cpu *mipsevm.CpuScalars, registers *[32]uint32, v0, v1 uint32) {
func HandleSyscallUpdates(cpu *mipsevm.CpuScalars, registers *[32]Word, v0, v1 Word) {
registers[2] = v0
registers[7] = v1
......
......@@ -7,7 +7,7 @@ import (
)
type PreimageReader interface {
ReadPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32)
ReadPreimage(key [32]byte, offset Word) (dat [32]byte, datLen Word)
}
// TrackingPreimageOracleReader wraps around a PreimageOracle, implements the PreimageOracle interface, and adds tracking functionality.
......@@ -22,8 +22,8 @@ type TrackingPreimageOracleReader struct {
lastPreimage []byte
// key for above preimage
lastPreimageKey [32]byte
// offset we last read from, or max uint32 if nothing is read this step
lastPreimageOffset uint32
// offset we last read from, or max Word if nothing is read this step
lastPreimageOffset Word
}
func NewTrackingPreimageOracleReader(po mipsevm.PreimageOracle) *TrackingPreimageOracleReader {
......@@ -31,7 +31,7 @@ func NewTrackingPreimageOracleReader(po mipsevm.PreimageOracle) *TrackingPreimag
}
func (p *TrackingPreimageOracleReader) Reset() {
p.lastPreimageOffset = ^uint32(0)
p.lastPreimageOffset = ^Word(0)
}
func (p *TrackingPreimageOracleReader) Hint(v []byte) {
......@@ -45,7 +45,7 @@ func (p *TrackingPreimageOracleReader) GetPreimage(k [32]byte) []byte {
return preimage
}
func (p *TrackingPreimageOracleReader) ReadPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
func (p *TrackingPreimageOracleReader) ReadPreimage(key [32]byte, offset Word) (dat [32]byte, datLen Word) {
preimage := p.lastPreimage
if key != p.lastPreimageKey {
p.lastPreimageKey = key
......@@ -57,14 +57,14 @@ func (p *TrackingPreimageOracleReader) ReadPreimage(key [32]byte, offset uint32)
p.lastPreimage = preimage
}
p.lastPreimageOffset = offset
if offset >= uint32(len(preimage)) {
if offset >= Word(len(preimage)) {
panic("Preimage offset out-of-bounds")
}
datLen = uint32(copy(dat[:], preimage[offset:]))
datLen = Word(copy(dat[:], preimage[offset:]))
return
}
func (p *TrackingPreimageOracleReader) LastPreimage() ([32]byte, []byte, uint32) {
func (p *TrackingPreimageOracleReader) LastPreimage() ([32]byte, []byte, Word) {
return p.lastPreimageKey, p.lastPreimage, p.lastPreimageOffset
}
......
......@@ -8,7 +8,7 @@ import (
)
type StackTracker interface {
PushStack(caller uint32, target uint32)
PushStack(caller Word, target Word)
PopStack()
}
......@@ -19,7 +19,7 @@ type TraceableStackTracker interface {
type NoopStackTracker struct{}
func (n *NoopStackTracker) PushStack(caller uint32, target uint32) {}
func (n *NoopStackTracker) PushStack(caller Word, target Word) {}
func (n *NoopStackTracker) PopStack() {}
......@@ -28,8 +28,8 @@ func (n *NoopStackTracker) Traceback() {}
type StackTrackerImpl struct {
state mipsevm.FPVMState
stack []uint32
caller []uint32
stack []Word
caller []Word
meta mipsevm.Metadata
}
......@@ -45,7 +45,7 @@ func NewStackTrackerUnsafe(state mipsevm.FPVMState, meta mipsevm.Metadata) *Stac
return &StackTrackerImpl{state: state, meta: meta}
}
func (s *StackTrackerImpl) PushStack(caller uint32, target uint32) {
func (s *StackTrackerImpl) PushStack(caller Word, target Word) {
s.caller = append(s.caller, caller)
s.stack = append(s.stack, target)
}
......
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
......@@ -17,22 +18,22 @@ type FPVMState interface {
GetMemory() *memory.Memory
// GetHeap returns the current memory address at the top of the heap
GetHeap() uint32
GetHeap() arch.Word
// GetPreimageKey returns the most recently accessed preimage key
GetPreimageKey() common.Hash
// GetPreimageOffset returns the current offset into the current preimage
GetPreimageOffset() uint32
GetPreimageOffset() arch.Word
// GetPC returns the currently executing program counter
GetPC() uint32
GetPC() arch.Word
// GetCpu returns the currently active cpu scalars, including the program counter
GetCpu() CpuScalars
// GetRegistersRef returns a pointer to the currently active registers
GetRegistersRef() *[32]uint32
GetRegistersRef() *[32]arch.Word
// GetStep returns the current VM step
GetStep() uint64
......@@ -48,9 +49,9 @@ type FPVMState interface {
// 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 uint32 length prefix.
// The first 4 bytes are a Word 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:])
// and should only be read when len(LastHint) > 4 && Word(LastHint[:4]) <= len(LastHint[4:])
GetLastHint() hexutil.Bytes
// EncodeWitness returns the witness for the current state and the state hash
......@@ -60,10 +61,10 @@ type FPVMState interface {
CreateVM(logger log.Logger, po PreimageOracle, stdOut, stdErr io.Writer, meta Metadata) FPVM
}
type SymbolMatcher func(addr uint32) bool
type SymbolMatcher func(addr arch.Word) bool
type Metadata interface {
LookupSymbol(addr uint32) string
LookupSymbol(addr arch.Word) string
CreateSymbolMatcher(name string) SymbolMatcher
}
......@@ -78,7 +79,7 @@ type FPVM interface {
CheckInfiniteLoop() bool
// LastPreimage returns the last preimage accessed by the VM
LastPreimage() (preimageKey [32]byte, preimage []byte, preimageOffset uint32)
LastPreimage() (preimageKey [32]byte, preimage []byte, preimageOffset arch.Word)
// Traceback prints a traceback of the program to the console
Traceback()
......@@ -91,5 +92,5 @@ type FPVM interface {
// LookupSymbol returns the symbol located at the specified address.
// May return an empty string if there's no symbol table available.
LookupSymbol(addr uint32) string
LookupSymbol(addr arch.Word) string
}
This diff is collapsed.
......@@ -118,7 +118,7 @@ func TestMemoryReadWrite(t *testing.T) {
_, err := rand.Read(data[:])
require.NoError(t, err)
require.NoError(t, m.SetMemoryRange(0, bytes.NewReader(data)))
for _, i := range []uint32{0, 4, 1000, 20_000 - 4} {
for _, i := range []Word{0, 4, 1000, 20_000 - 4} {
v := m.GetMemory(i)
expected := binary.BigEndian.Uint32(data[i : i+4])
require.Equalf(t, expected, v, "read at %d", i)
......@@ -129,7 +129,7 @@ func TestMemoryReadWrite(t *testing.T) {
m := NewMemory()
data := []byte(strings.Repeat("under the big bright yellow sun ", 40))
require.NoError(t, m.SetMemoryRange(0x1337, bytes.NewReader(data)))
res, err := io.ReadAll(m.ReadMemoryRange(0x1337-10, uint32(len(data)+20)))
res, err := io.ReadAll(m.ReadMemoryRange(0x1337-10, Word(len(data)+20)))
require.NoError(t, err)
require.Equal(t, make([]byte, 10), res[:10], "empty start")
require.Equal(t, data, res[10:len(res)-10], "result")
......
......@@ -70,7 +70,7 @@ type CachedPage struct {
Ok [PageSize / 32]bool
}
func (p *CachedPage) Invalidate(pageAddr uint32) {
func (p *CachedPage) invalidate(pageAddr Word) {
if pageAddr >= PageSize {
panic("invalid page addr")
}
......
......@@ -29,16 +29,16 @@ func TestCachedPage(t *testing.T) {
post := p.MerkleRoot()
require.Equal(t, pre, post, "no change expected until cache is invalidated")
p.Invalidate(42)
p.invalidate(42)
post2 := p.MerkleRoot()
require.NotEqual(t, post, post2, "change after cache invalidation")
p.Data[2000] = 0xef
p.Invalidate(42)
p.invalidate(42)
post3 := p.MerkleRoot()
require.Equal(t, post2, post3, "local invalidation is not global invalidation")
p.Invalidate(2000)
p.invalidate(2000)
post4 := p.MerkleRoot()
require.NotEqual(t, post3, post4, "can see the change now")
......
......@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
......@@ -77,7 +78,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err erro
wit.ProofData = append(wit.ProofData, memProof[:]...)
wit.ProofData = append(wit.ProofData, memProof2[:]...)
lastPreimageKey, lastPreimage, lastPreimageOffset := m.preimageOracle.LastPreimage()
if lastPreimageOffset != ^uint32(0) {
if lastPreimageOffset != ^arch.Word(0) {
wit.PreimageOffset = lastPreimageOffset
wit.PreimageKey = lastPreimageKey
wit.PreimageValue = lastPreimage
......@@ -90,7 +91,7 @@ func (m *InstrumentedState) CheckInfiniteLoop() bool {
return false
}
func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) {
func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, arch.Word) {
return m.preimageOracle.LastPreimage()
}
......@@ -111,7 +112,7 @@ func (m *InstrumentedState) Traceback() {
m.stackTracker.Traceback()
}
func (m *InstrumentedState) LookupSymbol(addr uint32) string {
func (m *InstrumentedState) LookupSymbol(addr arch.Word) string {
if m.meta == nil {
return ""
}
......
......@@ -20,7 +20,6 @@ func vmFactory(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer
func TestInstrumentedState_OpenMips(t *testing.T) {
t.Parallel()
// TODO: Add mt-specific tests here
testutil.RunVMTests_OpenMips(t, CreateEmptyState, vmFactory, "clone.bin")
}
......
......@@ -9,21 +9,24 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error {
thread := m.state.GetCurrentThread()
syscallNum, a0, a1, a2, a3 := exec.GetSyscallArgs(m.state.GetRegistersRef())
v0 := uint32(0)
v1 := uint32(0)
v0 := Word(0)
v1 := Word(0)
//fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum {
case exec.SysMmap:
var newHeap uint32
var newHeap Word
v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap)
m.state.Heap = newHeap
case exec.SysBrk:
......@@ -74,9 +77,9 @@ func (m *InstrumentedState) handleSyscall() error {
m.state.ExitCode = uint8(a0)
return nil
case exec.SysRead:
var newPreimageOffset uint32
var newPreimageOffset Word
var memUpdated bool
var memAddr uint32
var memAddr Word
v0, v1, newPreimageOffset, memUpdated, memAddr = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
m.state.PreimageOffset = newPreimageOffset
if memUpdated {
......@@ -85,7 +88,7 @@ func (m *InstrumentedState) handleSyscall() error {
case exec.SysWrite:
var newLastHint hexutil.Bytes
var newPreimageKey common.Hash
var newPreimageOffset uint32
var newPreimageOffset Word
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.memoryTracker, m.stdOut, m.stdErr)
m.state.LastHint = newLastHint
m.state.PreimageKey = newPreimageKey
......@@ -105,11 +108,11 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
case exec.SysFutex:
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout
effAddr := a0 & 0xFFffFFfc
effAddr := a0 & arch.AddressMask
switch a1 {
case exec.FutexWaitPrivate:
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
mem := m.state.Memory.GetWord(effAddr)
if mem != a2 {
v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN
......@@ -153,20 +156,20 @@ func (m *InstrumentedState) handleSyscall() error {
switch a0 {
case exec.ClockGettimeRealtimeFlag, exec.ClockGettimeMonotonicFlag:
v0, v1 = 0, 0
var secs, nsecs uint32
var secs, nsecs Word
if a0 == exec.ClockGettimeMonotonicFlag {
// monotonic clock_gettime is used by Go guest programs for goroutine scheduling and to implement
// `time.Sleep` (and other sleep related operations).
secs = uint32(m.state.Step / exec.HZ)
nsecs = uint32((m.state.Step % exec.HZ) * (1_000_000_000 / exec.HZ))
secs = Word(m.state.Step / exec.HZ)
nsecs = Word((m.state.Step % exec.HZ) * (1_000_000_000 / exec.HZ))
} // else realtime set to Unix Epoch
effAddr := a1 & 0xFFffFFfc
effAddr := a1 & arch.AddressMask
m.memoryTracker.TrackMemAccess(effAddr)
m.state.Memory.SetMemory(effAddr, secs)
m.state.Memory.SetWord(effAddr, secs)
m.handleMemoryUpdate(effAddr)
m.memoryTracker.TrackMemAccess2(effAddr + 4)
m.state.Memory.SetMemory(effAddr+4, nsecs)
m.state.Memory.SetWord(effAddr+4, nsecs)
m.handleMemoryUpdate(effAddr + 4)
default:
v0 = exec.SysErrorSignal
......@@ -182,6 +185,8 @@ func (m *InstrumentedState) handleSyscall() error {
case exec.SysSigaltstack:
case exec.SysRtSigaction:
case exec.SysPrlimit64:
// TODO(#12205): may be needed for 64-bit Cannon
// case exec.SysGetRtLimit:
case exec.SysClose:
case exec.SysPread64:
case exec.SysFstat64:
......@@ -256,9 +261,9 @@ func (m *InstrumentedState) mipsStep() error {
m.onWaitComplete(thread, true)
return nil
} else {
effAddr := thread.FutexAddr & 0xFFffFFfc
effAddr := thread.FutexAddr & arch.AddressMask
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
mem := m.state.Memory.GetWord(effAddr)
if thread.FutexVal == mem {
// still got expected value, continue sleeping, try next thread.
m.preemptThread(thread)
......@@ -299,6 +304,12 @@ func (m *InstrumentedState) mipsStep() error {
if opcode == exec.OpLoadLinked || opcode == exec.OpStoreConditional {
return m.handleRMWOps(insn, opcode)
}
if opcode == exec.OpLoadLinked64 || opcode == exec.OpStoreConditional64 {
if arch.IsMips32 {
panic(fmt.Sprintf("invalid instruction: %x", insn))
}
return m.handleRMWOps(insn, opcode)
}
// Exec the rest of the step logic
memUpdated, memAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
......@@ -312,7 +323,7 @@ func (m *InstrumentedState) mipsStep() error {
return nil
}
func (m *InstrumentedState) handleMemoryUpdate(memAddr uint32) {
func (m *InstrumentedState) handleMemoryUpdate(memAddr Word) {
if memAddr == m.state.LLAddress {
// Reserved address was modified, clear the reservation
m.clearLLMemoryReservation()
......@@ -329,27 +340,32 @@ func (m *InstrumentedState) clearLLMemoryReservation() {
func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
baseReg := (insn >> 21) & 0x1F
base := m.state.GetRegistersRef()[baseReg]
rtReg := (insn >> 16) & 0x1F
rtReg := Word((insn >> 16) & 0x1F)
offset := exec.SignExtendImmediate(insn)
effAddr := (base + offset) & 0xFFFFFFFC
effAddr := (base + offset) & arch.AddressMask
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
mem := m.state.Memory.GetWord(effAddr)
var retVal uint32
var retVal Word
threadId := m.state.GetCurrentThread().ThreadId
if opcode == exec.OpLoadLinked {
if opcode == exec.OpLoadLinked || opcode == exec.OpLoadLinked64 {
retVal = mem
m.state.LLReservationActive = true
m.state.LLAddress = effAddr
m.state.LLOwnerThread = threadId
} else if opcode == exec.OpStoreConditional {
} else if opcode == exec.OpStoreConditional || opcode == exec.OpStoreConditional64 {
// TODO(#12205): Determine bits affected by coherence stores on 64-bits
// Check if our memory reservation is still intact
if m.state.LLReservationActive && m.state.LLOwnerThread == threadId && m.state.LLAddress == effAddr {
// Complete atomic update: set memory and return 1 for success
m.clearLLMemoryReservation()
rt := m.state.GetRegistersRef()[rtReg]
m.state.Memory.SetMemory(effAddr, rt)
if opcode == exec.OpStoreConditional {
m.state.Memory.SetMemory(effAddr, uint32(rt))
} else {
m.state.Memory.SetWord(effAddr, rt)
}
retVal = 1
} else {
// Atomic update failed, return 0 for failure
......@@ -370,8 +386,8 @@ func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool)
thread.FutexTimeoutStep = 0
// Complete the FUTEX_WAIT syscall
v0 := uint32(0)
v1 := uint32(0)
v0 := Word(0)
v1 := Word(0)
if isTimedOut {
v0 = exec.SysErrorSignal
v1 = exec.MipsETIMEDOUT
......
......@@ -9,7 +9,7 @@ import (
type ThreadedStackTracker interface {
exec.TraceableStackTracker
DropThread(threadId uint32)
DropThread(threadId Word)
}
type NoopThreadedStackTracker struct {
......@@ -18,12 +18,12 @@ type NoopThreadedStackTracker struct {
var _ ThreadedStackTracker = (*ThreadedStackTrackerImpl)(nil)
func (n *NoopThreadedStackTracker) DropThread(threadId uint32) {}
func (n *NoopThreadedStackTracker) DropThread(threadId Word) {}
type ThreadedStackTrackerImpl struct {
meta mipsevm.Metadata
state *State
trackersByThreadId map[uint32]exec.TraceableStackTracker
trackersByThreadId map[Word]exec.TraceableStackTracker
}
var _ ThreadedStackTracker = (*ThreadedStackTrackerImpl)(nil)
......@@ -36,11 +36,11 @@ func NewThreadedStackTracker(state *State, meta mipsevm.Metadata) (*ThreadedStac
return &ThreadedStackTrackerImpl{
state: state,
meta: meta,
trackersByThreadId: make(map[uint32]exec.TraceableStackTracker),
trackersByThreadId: make(map[Word]exec.TraceableStackTracker),
}, nil
}
func (t *ThreadedStackTrackerImpl) PushStack(caller uint32, target uint32) {
func (t *ThreadedStackTrackerImpl) PushStack(caller Word, target Word) {
t.getCurrentTracker().PushStack(caller, target)
}
......@@ -62,6 +62,6 @@ func (t *ThreadedStackTrackerImpl) getCurrentTracker() exec.TraceableStackTracke
return tracker
}
func (t *ThreadedStackTrackerImpl) DropThread(threadId uint32) {
func (t *ThreadedStackTrackerImpl) DropThread(threadId Word) {
delete(t.trackersByThreadId, threadId)
}
......@@ -11,54 +11,57 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/serialize"
)
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 172
const (
MEMROOT_WITNESS_OFFSET = 0
PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32
PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32
HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + 4
LL_RESERVATION_ACTIVE_OFFSET = HEAP_WITNESS_OFFSET + 4
HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + arch.WordSizeBytes
LL_RESERVATION_ACTIVE_OFFSET = HEAP_WITNESS_OFFSET + arch.WordSizeBytes
LL_ADDRESS_OFFSET = LL_RESERVATION_ACTIVE_OFFSET + 1
LL_OWNER_THREAD_OFFSET = LL_ADDRESS_OFFSET + 4
EXITCODE_WITNESS_OFFSET = LL_OWNER_THREAD_OFFSET + 4
LL_OWNER_THREAD_OFFSET = LL_ADDRESS_OFFSET + arch.WordSizeBytes
EXITCODE_WITNESS_OFFSET = LL_OWNER_THREAD_OFFSET + arch.WordSizeBytes
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
WAKEUP_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + 4
TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + arch.WordSizeBytes
LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32
THREAD_ID_WITNESS_OFFSET = RIGHT_THREADS_ROOT_WITNESS_OFFSET + 32
// 172 and 196 bytes for 32 and 64-bit respectively
STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes
)
type State struct {
Memory *memory.Memory
PreimageKey common.Hash
PreimageOffset uint32 // note that the offset includes the 8-byte length prefix
PreimageOffset Word // note that the offset includes the 8-byte length prefix
Heap uint32 // to handle mmap growth
Heap Word // to handle mmap growth
LLReservationActive bool // Whether there is an active memory reservation initiated via the LL (load linked) op
LLAddress uint32 // The "linked" memory address reserved via the LL (load linked) op
LLOwnerThread uint32 // The id of the thread that holds the reservation on LLAddress
LLAddress Word // The "linked" memory address reserved via the LL (load linked) op
LLOwnerThread Word // The id of the thread that holds the reservation on LLAddress
ExitCode uint8
Exited bool
Step uint64
StepsSinceLastContextSwitch uint64
Wakeup uint32
Wakeup Word
TraverseRight bool
LeftThreadStack []*ThreadState
RightThreadStack []*ThreadState
NextThreadId uint32
NextThreadId Word
// LastHint is optional metadata, and not part of the VM state itself.
LastHint hexutil.Bytes
......@@ -86,7 +89,7 @@ func CreateEmptyState() *State {
}
}
func CreateInitialState(pc, heapStart uint32) *State {
func CreateInitialState(pc, heapStart Word) *State {
state := CreateEmptyState()
currentThread := state.GetCurrentThread()
currentThread.Cpu.PC = pc
......@@ -97,6 +100,7 @@ func CreateInitialState(pc, heapStart uint32) *State {
}
func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) mipsevm.FPVM {
logger.Info("Using cannon multithreaded VM", "is32", arch.IsMips32)
return NewInstrumentedState(s, po, stdOut, stdErr, logger, meta)
}
......@@ -139,7 +143,7 @@ func (s *State) calculateThreadStackRoot(stack []*ThreadState) common.Hash {
return curRoot
}
func (s *State) GetPC() uint32 {
func (s *State) GetPC() Word {
activeThread := s.GetCurrentThread()
return activeThread.Cpu.PC
}
......@@ -153,7 +157,7 @@ func (s *State) getCpuRef() *mipsevm.CpuScalars {
return &s.GetCurrentThread().Cpu
}
func (s *State) GetRegistersRef() *[32]uint32 {
func (s *State) GetRegistersRef() *[32]Word {
activeThread := s.GetCurrentThread()
return &activeThread.Registers
}
......@@ -176,7 +180,7 @@ func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *State) GetHeap() uint32 {
func (s *State) GetHeap() Word {
return s.Heap
}
......@@ -184,7 +188,7 @@ func (s *State) GetPreimageKey() common.Hash {
return s.PreimageKey
}
func (s *State) GetPreimageOffset() uint32 {
func (s *State) GetPreimageOffset() Word {
return s.PreimageOffset
}
......@@ -193,24 +197,24 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
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.Heap)
out = arch.ByteOrderWord.AppendWord(out, s.PreimageOffset)
out = arch.ByteOrderWord.AppendWord(out, s.Heap)
out = mipsevm.AppendBoolToWitness(out, s.LLReservationActive)
out = binary.BigEndian.AppendUint32(out, s.LLAddress)
out = binary.BigEndian.AppendUint32(out, s.LLOwnerThread)
out = arch.ByteOrderWord.AppendWord(out, s.LLAddress)
out = arch.ByteOrderWord.AppendWord(out, s.LLOwnerThread)
out = append(out, s.ExitCode)
out = mipsevm.AppendBoolToWitness(out, s.Exited)
out = binary.BigEndian.AppendUint64(out, s.Step)
out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch)
out = binary.BigEndian.AppendUint32(out, s.Wakeup)
out = arch.ByteOrderWord.AppendWord(out, s.Wakeup)
leftStackRoot := s.getLeftThreadStackRoot()
rightStackRoot := s.getRightThreadStackRoot()
out = mipsevm.AppendBoolToWitness(out, s.TraverseRight)
out = append(out, (leftStackRoot)[:]...)
out = append(out, (rightStackRoot)[:]...)
out = binary.BigEndian.AppendUint32(out, s.NextThreadId)
out = arch.ByteOrderWord.AppendWord(out, s.NextThreadId)
return out, stateHashFromWitness(out)
}
......@@ -245,20 +249,20 @@ func (s *State) ThreadCount() int {
// StateVersion uint8(1)
// Memory As per Memory.Serialize
// PreimageKey [32]byte
// PreimageOffset uint32
// Heap uint32
// PreimageOffset Word
// Heap Word
// ExitCode uint8
// Exited uint8 - 0 for false, 1 for true
// Step uint64
// StepsSinceLastContextSwitch uint64
// Wakeup uint32
// Wakeup Word
// TraverseRight uint8 - 0 for false, 1 for true
// NextThreadId uint32
// len(LeftThreadStack) uint32
// NextThreadId Word
// len(LeftThreadStack) Word
// LeftThreadStack entries as per ThreadState.Serialize
// len(RightThreadStack) uint32
// len(RightThreadStack) Word
// RightThreadStack entries as per ThreadState.Serialize
// len(LastHint) uint32 (0 when LastHint is nil)
// len(LastHint) Word (0 when LastHint is nil)
// LastHint []byte
func (s *State) Serialize(out io.Writer) error {
bout := serialize.NewBinaryWriter(out)
......@@ -306,7 +310,7 @@ func (s *State) Serialize(out io.Writer) error {
return err
}
if err := bout.WriteUInt(uint32(len(s.LeftThreadStack))); err != nil {
if err := bout.WriteUInt(Word(len(s.LeftThreadStack))); err != nil {
return err
}
for _, stack := range s.LeftThreadStack {
......@@ -314,7 +318,7 @@ func (s *State) Serialize(out io.Writer) error {
return err
}
}
if err := bout.WriteUInt(uint32(len(s.RightThreadStack))); err != nil {
if err := bout.WriteUInt(Word(len(s.RightThreadStack))); err != nil {
return err
}
for _, stack := range s.RightThreadStack {
......@@ -376,7 +380,7 @@ func (s *State) Deserialize(in io.Reader) error {
return err
}
var leftThreadStackSize uint32
var leftThreadStackSize Word
if err := bin.ReadUInt(&leftThreadStackSize); err != nil {
return err
}
......@@ -388,7 +392,7 @@ func (s *State) Deserialize(in io.Reader) error {
}
}
var rightThreadStackSize uint32
var rightThreadStackSize Word
if err := bin.ReadUInt(&rightThreadStackSize); err != nil {
return err
}
......@@ -423,7 +427,7 @@ func GetStateHashFn() mipsevm.HashFn {
func stateHashFromWitness(sw []byte) common.Hash {
if len(sw) != STATE_WITNESS_SIZE {
panic("Invalid witness length")
panic(fmt.Sprintf("Invalid witness length. Got %d, expected %d", len(sw), STATE_WITNESS_SIZE))
}
hash := crypto.Keccak256Hash(sw)
exitCode := sw[EXITCODE_WITNESS_OFFSET]
......
......@@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
......@@ -41,11 +42,11 @@ func TestState_EncodeWitness(t *testing.T) {
{exited: true, exitCode: 3},
}
heap := uint32(12)
llAddress := uint32(55)
llThreadOwner := uint32(99)
heap := Word(12)
llAddress := Word(55)
llThreadOwner := Word(99)
preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4})
preimageOffset := uint32(24)
preimageOffset := Word(24)
step := uint64(33)
stepsSinceContextSwitch := uint64(123)
for _, c := range cases {
......@@ -207,7 +208,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
LO: 0xbeef,
HI: 0xbabe,
},
Registers: [32]uint32{
Registers: [32]Word{
0xdeadbeef,
0xdeadbeef,
0xc0ffee,
......@@ -230,7 +231,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
LO: 0xeeef,
HI: 0xeabe,
},
Registers: [32]uint32{
Registers: [32]Word{
0xabcdef,
0x123456,
},
......@@ -250,7 +251,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
LO: 0xdeef,
HI: 0xdabe,
},
Registers: [32]uint32{
Registers: [32]Word{
0x654321,
},
},
......@@ -267,7 +268,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
LO: 0xceef,
HI: 0xcabe,
},
Registers: [32]uint32{
Registers: [32]Word{
0x987653,
0xfedbca,
},
......@@ -302,7 +303,7 @@ func TestState_EncodeThreadProof_SingleThread(t *testing.T) {
activeThread.Cpu.HI = 11
activeThread.Cpu.LO = 22
for i := 0; i < 32; i++ {
activeThread.Registers[i] = uint32(i)
activeThread.Registers[i] = Word(i)
}
expectedProof := append([]byte{}, activeThread.serializeThread()[:]...)
......@@ -324,12 +325,12 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) {
// Set some fields on our threads
for i := 0; i < 3; i++ {
curThread := state.LeftThreadStack[i]
curThread.Cpu.PC = uint32(4 * i)
curThread.Cpu.PC = Word(4 * i)
curThread.Cpu.NextPC = curThread.Cpu.PC + 4
curThread.Cpu.HI = uint32(11 + i)
curThread.Cpu.LO = uint32(22 + i)
curThread.Cpu.HI = Word(11 + i)
curThread.Cpu.LO = Word(22 + i)
for j := 0; j < 32; j++ {
curThread.Registers[j] = uint32(j + i)
curThread.Registers[j] = Word(j + i)
}
}
......@@ -355,12 +356,12 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) {
func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
cases := []struct {
name string
wakeupAddr uint32
wakeupAddr Word
traverseRight bool
}{
{"traverse left during wakeup traversal", uint32(99), false},
{"traverse left during wakeup traversal", Word(99), false},
{"traverse left during normal traversal", exec.FutexEmptyAddr, false},
{"traverse right during wakeup traversal", uint32(99), true},
{"traverse right during wakeup traversal", Word(99), true},
{"traverse right during normal traversal", exec.FutexEmptyAddr, true},
}
......@@ -382,3 +383,19 @@ func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
})
}
}
func TestStateWitnessSize(t *testing.T) {
expectedWitnessSize := 172
if !arch.IsMips32 {
expectedWitnessSize = 196
}
require.Equal(t, expectedWitnessSize, STATE_WITNESS_SIZE)
}
func TestThreadStateWitnessSize(t *testing.T) {
expectedWitnessSize := 166
if !arch.IsMips32 {
expectedWitnessSize = 322
}
require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE)
}
......@@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
)
......@@ -15,11 +16,11 @@ import (
// to define an expected post-state. The post-state is then validated with ExpectedMTState.Validate(t, postState)
type ExpectedMTState struct {
PreimageKey common.Hash
PreimageOffset uint32
Heap uint32
PreimageOffset arch.Word
Heap arch.Word
LLReservationActive bool
LLAddress uint32
LLOwnerThread uint32
LLAddress arch.Word
LLOwnerThread arch.Word
ExitCode uint8
Exited bool
Step uint64
......@@ -28,37 +29,37 @@ type ExpectedMTState struct {
expectedMemory *memory.Memory
// Threading-related expectations
StepsSinceLastContextSwitch uint64
Wakeup uint32
Wakeup arch.Word
TraverseRight bool
NextThreadId uint32
NextThreadId arch.Word
ThreadCount int
RightStackSize int
LeftStackSize int
prestateActiveThreadId uint32
prestateActiveThreadId arch.Word
prestateActiveThreadOrig ExpectedThreadState // Cached for internal use
ActiveThreadId uint32
threadExpectations map[uint32]*ExpectedThreadState
ActiveThreadId arch.Word
threadExpectations map[arch.Word]*ExpectedThreadState
}
type ExpectedThreadState struct {
ThreadId uint32
ThreadId arch.Word
ExitCode uint8
Exited bool
FutexAddr uint32
FutexVal uint32
FutexAddr arch.Word
FutexVal arch.Word
FutexTimeoutStep uint64
PC uint32
NextPC uint32
HI uint32
LO uint32
Registers [32]uint32
PC arch.Word
NextPC arch.Word
HI arch.Word
LO arch.Word
Registers [32]arch.Word
Dropped bool
}
func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
currentThread := fromState.GetCurrentThread()
expectedThreads := make(map[uint32]*ExpectedThreadState)
expectedThreads := make(map[arch.Word]*ExpectedThreadState)
for _, t := range GetAllThreads(fromState) {
expectedThreads[t.ThreadId] = newExpectedThreadState(t)
}
......@@ -118,12 +119,17 @@ func (e *ExpectedMTState) ExpectStep() {
e.StepsSinceLastContextSwitch += 1
}
func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) {
func (e *ExpectedMTState) ExpectMemoryWrite(addr arch.Word, val uint32) {
e.expectedMemory.SetMemory(addr, val)
e.MemoryRoot = e.expectedMemory.MerkleRoot()
}
func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) {
func (e *ExpectedMTState) ExpectMemoryWordWrite(addr arch.Word, val arch.Word) {
e.expectedMemory.SetWord(addr, val)
e.MemoryRoot = e.expectedMemory.MerkleRoot()
}
func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr arch.Word, val uint32, addr2 arch.Word, val2 uint32) {
e.expectedMemory.SetMemory(addr, val)
e.expectedMemory.SetMemory(addr2, val2)
e.MemoryRoot = e.expectedMemory.MerkleRoot()
......@@ -166,7 +172,7 @@ func (e *ExpectedMTState) PrestateActiveThread() *ExpectedThreadState {
return e.threadExpectations[e.prestateActiveThreadId]
}
func (e *ExpectedMTState) Thread(threadId uint32) *ExpectedThreadState {
func (e *ExpectedMTState) Thread(threadId arch.Word) *ExpectedThreadState {
return e.threadExpectations[threadId]
}
......
......@@ -7,6 +7,7 @@ import (
//"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
)
......@@ -45,10 +46,10 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "LeftStackSize", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LeftStackSize += 1 }},
{name: "ActiveThreadId", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ActiveThreadId += 1 }},
{name: "Empty thread expectations", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations = map[uint32]*ExpectedThreadState{}
e.threadExpectations = map[arch.Word]*ExpectedThreadState{}
}},
{name: "Mismatched thread expectations", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations = map[uint32]*ExpectedThreadState{someThread.ThreadId: newExpectedThreadState(someThread)}
e.threadExpectations = map[arch.Word]*ExpectedThreadState{someThread.ThreadId: newExpectedThreadState(someThread)}
}},
{name: "Active threadId", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].ThreadId += 1
......
......@@ -6,6 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
......@@ -27,18 +28,18 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
step := r.RandStep()
m.state.PreimageKey = r.RandHash()
m.state.PreimageOffset = r.Uint32()
m.state.PreimageOffset = r.Word()
m.state.Step = step
m.state.LastHint = r.RandHint()
m.state.StepsSinceLastContextSwitch = uint64(r.Intn(exec.SchedQuantum))
// Randomize memory-related fields
halfMemory := math.MaxUint32 / 2
m.state.Heap = uint32(r.Intn(halfMemory) + halfMemory)
m.state.Heap = arch.Word(r.Intn(halfMemory) + halfMemory)
m.state.LLReservationActive = r.Intn(2) == 1
if m.state.LLReservationActive {
m.state.LLAddress = uint32(r.Intn(halfMemory))
m.state.LLOwnerThread = uint32(r.Intn(10))
m.state.LLAddress = arch.Word(r.Intn(halfMemory))
m.state.LLOwnerThread = arch.Word(r.Intn(10))
}
// Randomize threads
......@@ -48,11 +49,11 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
SetupThreads(randSeed+1, m.state, traverseRight, activeStackThreads, inactiveStackThreads)
}
func (m *StateMutatorMultiThreaded) SetHI(val uint32) {
func (m *StateMutatorMultiThreaded) SetHI(val arch.Word) {
m.state.GetCurrentThread().Cpu.HI = val
}
func (m *StateMutatorMultiThreaded) SetLO(val uint32) {
func (m *StateMutatorMultiThreaded) SetLO(val arch.Word) {
m.state.GetCurrentThread().Cpu.LO = val
}
......@@ -64,16 +65,16 @@ func (m *StateMutatorMultiThreaded) SetExited(val bool) {
m.state.Exited = val
}
func (m *StateMutatorMultiThreaded) SetPC(val uint32) {
func (m *StateMutatorMultiThreaded) SetPC(val arch.Word) {
thread := m.state.GetCurrentThread()
thread.Cpu.PC = val
}
func (m *StateMutatorMultiThreaded) SetHeap(val uint32) {
func (m *StateMutatorMultiThreaded) SetHeap(val arch.Word) {
m.state.Heap = val
}
func (m *StateMutatorMultiThreaded) SetNextPC(val uint32) {
func (m *StateMutatorMultiThreaded) SetNextPC(val arch.Word) {
thread := m.state.GetCurrentThread()
thread.Cpu.NextPC = val
}
......@@ -86,7 +87,7 @@ func (m *StateMutatorMultiThreaded) SetPreimageKey(val common.Hash) {
m.state.PreimageKey = val
}
func (m *StateMutatorMultiThreaded) SetPreimageOffset(val uint32) {
func (m *StateMutatorMultiThreaded) SetPreimageOffset(val arch.Word) {
m.state.PreimageOffset = val
}
......
package testutil
import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
......@@ -14,8 +15,8 @@ func RandomThread(randSeed int64) *multithreaded.ThreadState {
thread.Registers = *r.RandRegisters()
thread.Cpu.PC = pc
thread.Cpu.NextPC = pc + 4
thread.Cpu.HI = r.Uint32()
thread.Cpu.LO = r.Uint32()
thread.Cpu.HI = r.Word()
thread.Cpu.LO = r.Word()
return thread
}
......@@ -37,7 +38,7 @@ func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRi
func SetupThreads(randomSeed int64, state *multithreaded.State, traverseRight bool, activeStackSize, otherStackSize int) {
var activeStack, otherStack []*multithreaded.ThreadState
tid := uint32(0)
tid := arch.Word(0)
for i := 0; i < activeStackSize; i++ {
thread := RandomThread(randomSeed + int64(i))
thread.ThreadId = tid
......@@ -129,13 +130,13 @@ func FindNextThreadFiltered(state *multithreaded.State, filter ThreadFilter) *mu
return nil
}
func FindNextThreadExcluding(state *multithreaded.State, threadId uint32) *multithreaded.ThreadState {
func FindNextThreadExcluding(state *multithreaded.State, threadId arch.Word) *multithreaded.ThreadState {
return FindNextThreadFiltered(state, func(t *multithreaded.ThreadState) bool {
return t.ThreadId != threadId
})
}
func FindThread(state *multithreaded.State, threadId uint32) *multithreaded.ThreadState {
func FindThread(state *multithreaded.State, threadId arch.Word) *multithreaded.ThreadState {
for _, t := range GetAllThreads(state) {
if t.ThreadId == threadId {
return t
......
......@@ -8,34 +8,47 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
const SERIALIZED_THREAD_SIZE = 166
// THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes.
//
// It consists of the active thread serialized and concatenated with the
// 32 byte hash onion of the active thread stack without the active thread
const THREAD_WITNESS_SIZE = SERIALIZED_THREAD_SIZE + 32
const (
THREAD_ID_STATE_WITNESS_OFFSET = 0
THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1
THREAD_FUTEX_ADDR_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1
THREAD_FUTEX_VAL_WITNESS_OFFSET = THREAD_FUTEX_ADDR_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET = THREAD_FUTEX_VAL_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_FUTEX_CPU_WITNESS_OFFSET = THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET + 8
THREAD_REGISTERS_WITNESS_OFFSET = THREAD_FUTEX_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
// 166 and 322 bytes for 32 and 64-bit respectively
SERIALIZED_THREAD_SIZE = THREAD_REGISTERS_WITNESS_OFFSET + (32 * arch.WordSizeBytes)
// THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes.
//
// It consists of the active thread serialized and concatenated with the
// 32 byte hash onion of the active thread stack without the active thread
THREAD_WITNESS_SIZE = SERIALIZED_THREAD_SIZE + 32
)
// The empty thread root - keccak256(bytes32(0) ++ bytes32(0))
var EmptyThreadsRoot common.Hash = common.HexToHash("0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5")
type ThreadState struct {
ThreadId uint32 `json:"threadId"`
ThreadId Word `json:"threadId"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
FutexAddr uint32 `json:"futexAddr"`
FutexVal uint32 `json:"futexVal"`
FutexAddr Word `json:"futexAddr"`
FutexVal Word `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]uint32 `json:"registers"`
Registers [32]Word `json:"registers"`
}
func CreateEmptyThread() *ThreadState {
initThreadId := uint32(0)
initThreadId := Word(0)
return &ThreadState{
ThreadId: initThreadId,
ExitCode: 0,
......@@ -49,27 +62,27 @@ func CreateEmptyThread() *ThreadState {
FutexAddr: exec.FutexEmptyAddr,
FutexVal: 0,
FutexTimeoutStep: 0,
Registers: [32]uint32{},
Registers: [32]Word{},
}
}
func (t *ThreadState) serializeThread() []byte {
out := make([]byte, 0, SERIALIZED_THREAD_SIZE)
out = binary.BigEndian.AppendUint32(out, t.ThreadId)
out = arch.ByteOrderWord.AppendWord(out, t.ThreadId)
out = append(out, t.ExitCode)
out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = binary.BigEndian.AppendUint32(out, t.FutexAddr)
out = binary.BigEndian.AppendUint32(out, t.FutexVal)
out = arch.ByteOrderWord.AppendWord(out, t.FutexAddr)
out = arch.ByteOrderWord.AppendWord(out, t.FutexVal)
out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep)
out = binary.BigEndian.AppendUint32(out, t.Cpu.PC)
out = binary.BigEndian.AppendUint32(out, t.Cpu.NextPC)
out = binary.BigEndian.AppendUint32(out, t.Cpu.LO)
out = binary.BigEndian.AppendUint32(out, t.Cpu.HI)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.PC)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.NextPC)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.LO)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.HI)
for _, r := range t.Registers {
out = binary.BigEndian.AppendUint32(out, r)
out = arch.ByteOrderWord.AppendWord(out, r)
}
return out
......@@ -115,7 +128,7 @@ func (t *ThreadState) Deserialize(in io.Reader) error {
if err := binary.Read(in, binary.BigEndian, &t.Cpu.HI); err != nil {
return err
}
// Read the registers as big endian uint32s
// Read the registers as big endian Words
for i := range t.Registers {
if err := binary.Read(in, binary.BigEndian, &t.Registers[i]); err != nil {
return err
......
......@@ -7,19 +7,22 @@ import (
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
)
const (
HEAP_START = 0x05_00_00_00
HEAP_END = 0x60_00_00_00
PROGRAM_BREAK = 0x40_00_00_00
HEAP_START = arch.HeapStart
HEAP_END = arch.HeapEnd
PROGRAM_BREAK = arch.ProgramBreak
)
type CreateInitialFPVMState[T mipsevm.FPVMState] func(pc, heapStart uint32) T
type Word = arch.Word
type CreateInitialFPVMState[T mipsevm.FPVMState] func(pc, heapStart Word) T
func LoadELF[T mipsevm.FPVMState](f *elf.File, initState CreateInitialFPVMState[T]) (T, error) {
var empty T
s := initState(uint32(f.Entry), HEAP_START)
s := initState(Word(f.Entry), HEAP_START)
for i, prog := range f.Progs {
if prog.Type == 0x70000003 { // MIPS_ABIFLAGS
......@@ -39,13 +42,14 @@ func LoadELF[T mipsevm.FPVMState](f *elf.File, initState CreateInitialFPVMState[
}
}
// TODO(#12205)
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 {
if err := s.GetMemory().SetMemoryRange(Word(prog.Vaddr), r); err != nil {
return empty, fmt.Errorf("failed to read program segment %d: %w", i, err)
}
}
......
......@@ -10,8 +10,8 @@ import (
type Symbol struct {
Name string `json:"name"`
Start uint32 `json:"start"`
Size uint32 `json:"size"`
Start Word `json:"start"`
Size Word `json:"size"`
}
type Metadata struct {
......@@ -31,12 +31,12 @@ func MakeMetadata(elfProgram *elf.File) (*Metadata, error) {
})
out := &Metadata{Symbols: make([]Symbol, len(syms))}
for i, s := range syms {
out.Symbols[i] = Symbol{Name: s.Name, Start: uint32(s.Value), Size: uint32(s.Size)}
out.Symbols[i] = Symbol{Name: s.Name, Start: Word(s.Value), Size: Word(s.Size)}
}
return out, nil
}
func (m *Metadata) LookupSymbol(addr uint32) string {
func (m *Metadata) LookupSymbol(addr Word) string {
if len(m.Symbols) == 0 {
return "!unknown"
}
......@@ -59,12 +59,12 @@ func (m *Metadata) CreateSymbolMatcher(name string) mipsevm.SymbolMatcher {
if s.Name == name {
start := s.Start
end := s.Start + s.Size
return func(addr uint32) bool {
return func(addr Word) bool {
return addr >= start && addr < end
}
}
}
return func(addr uint32) bool {
return func(addr Word) bool {
return false
}
}
......@@ -3,14 +3,16 @@ package program
import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
const WordSizeBytes = arch.WordSizeBytes
// PatchGoGC patches out garbage-collection-related symbols to disable garbage collection
// and improves performance by patching out floating-point-related symbols
func PatchGoGC(f *elf.File, st mipsevm.FPVMState) error {
......@@ -39,10 +41,10 @@ func PatchGoGC(f *elf.File, st mipsevm.FPVMState) error {
"flag.init",
// We need to patch this out, we don't pass float64nan because we don't support floats
"runtime.check":
// MIPS32 patch: ret (pseudo instruction)
// MIPSx patch: ret (pseudo instruction)
// 03e00008 = jr $ra = ret (pseudo instruction)
// 00000000 = nop (executes with delay-slot, but does nothing)
if err := st.GetMemory().SetMemoryRange(uint32(s.Value), bytes.NewReader([]byte{
if err := st.GetMemory().SetMemoryRange(Word(s.Value), bytes.NewReader([]byte{
0x03, 0xe0, 0x00, 0x08,
0, 0, 0, 0,
})); err != nil {
......@@ -56,41 +58,54 @@ func PatchGoGC(f *elf.File, st mipsevm.FPVMState) error {
// PatchStack sets up the program's initial stack frame and stack pointer
func PatchStack(st mipsevm.FPVMState) error {
// setup stack pointer
sp := uint32(0x7f_ff_d0_00)
sp := Word(arch.HighMemoryStart)
// allocate 1 page for the initial stack data, and 16KB = 4 pages for the stack to grow
if err := st.GetMemory().SetMemoryRange(sp-4*memory.PageSize, bytes.NewReader(make([]byte, 5*memory.PageSize))); err != nil {
return errors.New("failed to allocate page for stack content")
}
st.GetRegistersRef()[29] = sp
storeMem := func(addr uint32, v uint32) {
var dat [4]byte
binary.BigEndian.PutUint32(dat[:], v)
storeMem := func(addr Word, v Word) {
var dat [WordSizeBytes]byte
arch.ByteOrderWord.PutWord(dat[:], v)
_ = st.GetMemory().SetMemoryRange(addr, bytes.NewReader(dat[:]))
}
// init argc, argv, aux on stack
storeMem(sp+4*0, 1) // argc = 1 (argument count)
storeMem(sp+4*1, sp+4*21) // argv[0]
storeMem(sp+4*2, 0) // argv[1] = terminating
storeMem(sp+4*3, sp+4*14) // envp[0] = x (offset to first env var)
storeMem(sp+4*4, 0) // envp[1] = terminating
storeMem(sp+4*5, 6) // auxv[0] = _AT_PAGESZ = 6 (key)
storeMem(sp+4*6, 4096) // auxv[1] = page size of 4 KiB (value) - (== minPhysPageSize)
storeMem(sp+4*7, 25) // auxv[2] = AT_RANDOM
storeMem(sp+4*8, sp+4*10) // auxv[3] = address of 16 bytes containing random value
storeMem(sp+4*9, 0) // auxv[term] = 0
auxv3Offset := sp + WordSizeBytes*10
randomness := []byte("4;byfairdiceroll")
randomness = pad(randomness)
_ = st.GetMemory().SetMemoryRange(auxv3Offset, bytes.NewReader(randomness))
_ = st.GetMemory().SetMemoryRange(sp+4*10, bytes.NewReader([]byte("4;byfairdiceroll"))) // 16 bytes of "randomness"
envp0Offset := auxv3Offset + Word(len(randomness))
envar := append([]byte("GODEBUG=memprofilerate=0"), 0x0)
envar = pad(envar)
_ = st.GetMemory().SetMemoryRange(envp0Offset, bytes.NewReader(envar))
// append 4 extra zero bytes to end at 4-byte alignment
envar := append([]byte("GODEBUG=memprofilerate=0"), 0x0, 0x0, 0x0, 0x0)
_ = st.GetMemory().SetMemoryRange(sp+4*14, bytes.NewReader(envar))
argv0Offset := envp0Offset + Word(len(envar))
programName := append([]byte("op-program"), 0x0)
programName = pad(programName)
_ = st.GetMemory().SetMemoryRange(argv0Offset, bytes.NewReader(programName))
// 24 bytes for GODEBUG=memprofilerate=0 + 4 null bytes
// Then append program name + 2 null bytes for 4-byte alignment
programName := append([]byte("op-program"), 0x0, 0x0)
_ = st.GetMemory().SetMemoryRange(sp+4*21, bytes.NewReader(programName))
// init argc, argv, aux on stack
storeMem(sp+WordSizeBytes*0, 1) // argc = 1 (argument count)
storeMem(sp+WordSizeBytes*1, argv0Offset) // argv[0]
storeMem(sp+WordSizeBytes*2, 0) // argv[1] = terminating
storeMem(sp+WordSizeBytes*3, envp0Offset) // envp[0] = x (offset to first env var)
storeMem(sp+WordSizeBytes*4, 0) // envp[1] = terminating
storeMem(sp+WordSizeBytes*5, 6) // auxv[0] = _AT_PAGESZ = 6 (key)
storeMem(sp+WordSizeBytes*6, 4096) // auxv[1] = page size of 4 KiB (value) - (== minPhysPageSize)
storeMem(sp+WordSizeBytes*7, 25) // auxv[2] = AT_RANDOM
storeMem(sp+WordSizeBytes*8, auxv3Offset) // auxv[3] = address of 16 bytes containing random value
storeMem(sp+WordSizeBytes*9, 0) // auxv[term] = 0
return nil
}
// pad adds appropriate padding to buf to end at Word alignment
func pad(buf []byte) []byte {
if len(buf)%WordSizeBytes == 0 {
return buf
}
bytesToAlignment := WordSizeBytes - len(buf)%WordSizeBytes
return append(buf, make([]byte, bytesToAlignment)...)
}
......@@ -28,7 +28,7 @@ var _ mipsevm.FPVM = (*InstrumentedState)(nil)
func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, meta mipsevm.Metadata) *InstrumentedState {
var sleepCheck mipsevm.SymbolMatcher
if meta == nil {
sleepCheck = func(addr uint32) bool { return false }
sleepCheck = func(addr Word) bool { return false }
} else {
sleepCheck = meta.CreateSymbolMatcher("runtime.notesleep")
}
......@@ -75,7 +75,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *mipsevm.StepWitness, err erro
memProof := m.memoryTracker.MemProof()
wit.ProofData = append(wit.ProofData, memProof[:]...)
lastPreimageKey, lastPreimage, lastPreimageOffset := m.preimageOracle.LastPreimage()
if lastPreimageOffset != ^uint32(0) {
if lastPreimageOffset != ^Word(0) {
wit.PreimageOffset = lastPreimageOffset
wit.PreimageKey = lastPreimageKey
wit.PreimageValue = lastPreimage
......@@ -88,7 +88,7 @@ func (m *InstrumentedState) CheckInfiniteLoop() bool {
return m.sleepCheck(m.state.GetPC())
}
func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, uint32) {
func (m *InstrumentedState) LastPreimage() ([32]byte, []byte, Word) {
return m.preimageOracle.LastPreimage()
}
......@@ -109,7 +109,7 @@ func (m *InstrumentedState) Traceback() {
m.stackTracker.Traceback()
}
func (m *InstrumentedState) LookupSymbol(addr uint32) string {
func (m *InstrumentedState) LookupSymbol(addr Word) string {
if m.meta == nil {
return ""
}
......
......@@ -6,24 +6,26 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error {
syscallNum, a0, a1, a2, _ := exec.GetSyscallArgs(&m.state.Registers)
v0 := uint32(0)
v1 := uint32(0)
v0 := Word(0)
v1 := Word(0)
//fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum {
case exec.SysMmap:
var newHeap uint32
var newHeap Word
v0, v1, newHeap = exec.HandleSysMmap(a0, a1, m.state.Heap)
m.state.Heap = newHeap
case exec.SysBrk:
v0 = program.PROGRAM_BREAK
v0 = arch.ProgramBreak
case exec.SysClone: // clone (not supported)
v0 = 1
case exec.SysExitGroup:
......@@ -31,13 +33,13 @@ func (m *InstrumentedState) handleSyscall() error {
m.state.ExitCode = uint8(a0)
return nil
case exec.SysRead:
var newPreimageOffset uint32
var newPreimageOffset Word
v0, v1, newPreimageOffset, _, _ = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
m.state.PreimageOffset = newPreimageOffset
case exec.SysWrite:
var newLastHint hexutil.Bytes
var newPreimageKey common.Hash
var newPreimageOffset uint32
var newPreimageOffset Word
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.memoryTracker, m.stdOut, m.stdErr)
m.state.LastHint = newLastHint
m.state.PreimageKey = newPreimageKey
......@@ -78,19 +80,19 @@ func (m *InstrumentedState) mipsStep() error {
func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
baseReg := (insn >> 21) & 0x1F
base := m.state.Registers[baseReg]
rtReg := (insn >> 16) & 0x1F
rtReg := Word((insn >> 16) & 0x1F)
offset := exec.SignExtendImmediate(insn)
effAddr := (base + offset) & 0xFFFFFFFC
effAddr := (base + offset) & arch.AddressMask
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
mem := m.state.Memory.GetWord(effAddr)
var retVal uint32
var retVal Word
if opcode == exec.OpLoadLinked {
retVal = mem
} else if opcode == exec.OpStoreConditional {
rt := m.state.Registers[rtReg]
m.state.Memory.SetMemory(effAddr, rt)
m.state.Memory.SetWord(effAddr, rt)
retVal = 1 // 1 for success
} else {
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
......
......@@ -13,28 +13,30 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
// ignoring 64-bit STATE_WITNESS_SIZE as it's not supported for singlethreaded
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
PreimageOffset Word `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
Heap Word `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
Registers [32]Word `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
......@@ -51,7 +53,7 @@ func CreateEmptyState() *State {
HI: 0,
},
Heap: 0,
Registers: [32]uint32{},
Registers: [32]Word{},
Memory: memory.NewMemory(),
ExitCode: 0,
Exited: false,
......@@ -59,7 +61,7 @@ func CreateEmptyState() *State {
}
}
func CreateInitialState(pc, heapStart uint32) *State {
func CreateInitialState(pc, heapStart Word) *State {
state := CreateEmptyState()
state.Cpu.PC = pc
state.Cpu.NextPC = pc + 4
......@@ -75,16 +77,16 @@ func (s *State) CreateVM(logger log.Logger, po mipsevm.PreimageOracle, stdOut, s
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"`
PreimageOffset Word `json:"preimageOffset"`
PC Word `json:"pc"`
NextPC Word `json:"nextPC"`
LO Word `json:"lo"`
HI Word `json:"hi"`
Heap Word `json:"heap"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]uint32 `json:"registers"`
Registers [32]Word `json:"registers"`
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
......@@ -128,11 +130,11 @@ func (s *State) UnmarshalJSON(data []byte) error {
return nil
}
func (s *State) GetPC() uint32 { return s.Cpu.PC }
func (s *State) GetPC() Word { return s.Cpu.PC }
func (s *State) GetCpu() mipsevm.CpuScalars { return s.Cpu }
func (s *State) GetRegistersRef() *[32]uint32 { return &s.Registers }
func (s *State) GetRegistersRef() *[32]Word { return &s.Registers }
func (s *State) GetExitCode() uint8 { return s.ExitCode }
......@@ -152,7 +154,7 @@ func (s *State) GetMemory() *memory.Memory {
return s.Memory
}
func (s *State) GetHeap() uint32 {
func (s *State) GetHeap() Word {
return s.Heap
}
......@@ -160,7 +162,7 @@ func (s *State) GetPreimageKey() common.Hash {
return s.PreimageKey
}
func (s *State) GetPreimageOffset() uint32 {
func (s *State) GetPreimageOffset() Word {
return s.PreimageOffset
}
......@@ -169,17 +171,17 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
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 = arch.ByteOrderWord.AppendWord(out, s.PreimageOffset)
out = arch.ByteOrderWord.AppendWord(out, s.Cpu.PC)
out = arch.ByteOrderWord.AppendWord(out, s.Cpu.NextPC)
out = arch.ByteOrderWord.AppendWord(out, s.Cpu.LO)
out = arch.ByteOrderWord.AppendWord(out, s.Cpu.HI)
out = arch.ByteOrderWord.AppendWord(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)
out = arch.ByteOrderWord.AppendWord(out, r)
}
return out, stateHashFromWitness(out)
}
......@@ -191,17 +193,17 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
// StateVersion uint8(0)
// Memory As per Memory.Serialize
// PreimageKey [32]byte
// PreimageOffset uint32
// Cpu.PC uint32
// Cpu.NextPC uint32
// Cpu.LO uint32
// Cpu.HI uint32
// Heap uint32
// PreimageOffset Word
// Cpu.PC Word
// Cpu.NextPC Word
// Cpu.LO Word
// Cpu.HI Word
// Heap Word
// ExitCode uint8
// Exited uint8 - 0 for false, 1 for true
// Step uint64
// Registers [32]uint32
// len(LastHint) uint32 (0 when LastHint is nil)
// Registers [32]Word
// len(LastHint) Word (0 when LastHint is nil)
// LastHint []byte
func (s *State) Serialize(out io.Writer) error {
bout := serialize.NewBinaryWriter(out)
......
......@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
......@@ -128,7 +129,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
ExitCode: 1,
Exited: true,
Step: 0xdeadbeef,
Registers: [32]uint32{
Registers: [32]arch.Word{
0xdeadbeef,
0xdeadbeef,
0xc0ffee,
......
......@@ -4,6 +4,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
......@@ -19,12 +20,12 @@ func (m *StateMutatorSingleThreaded) Randomize(randSeed int64) {
step := r.RandStep()
m.state.PreimageKey = r.RandHash()
m.state.PreimageOffset = r.Uint32()
m.state.PreimageOffset = r.Word()
m.state.Cpu.PC = pc
m.state.Cpu.NextPC = pc + 4
m.state.Cpu.HI = r.Uint32()
m.state.Cpu.LO = r.Uint32()
m.state.Heap = r.Uint32()
m.state.Cpu.HI = r.Word()
m.state.Cpu.LO = r.Word()
m.state.Heap = r.Word()
m.state.Step = step
m.state.LastHint = r.RandHint()
m.state.Registers = *r.RandRegisters()
......@@ -36,23 +37,23 @@ func NewStateMutatorSingleThreaded(state *singlethreaded.State) testutil.StateMu
return &StateMutatorSingleThreaded{state: state}
}
func (m *StateMutatorSingleThreaded) SetPC(val uint32) {
func (m *StateMutatorSingleThreaded) SetPC(val arch.Word) {
m.state.Cpu.PC = val
}
func (m *StateMutatorSingleThreaded) SetNextPC(val uint32) {
func (m *StateMutatorSingleThreaded) SetNextPC(val arch.Word) {
m.state.Cpu.NextPC = val
}
func (m *StateMutatorSingleThreaded) SetHI(val uint32) {
func (m *StateMutatorSingleThreaded) SetHI(val arch.Word) {
m.state.Cpu.HI = val
}
func (m *StateMutatorSingleThreaded) SetLO(val uint32) {
func (m *StateMutatorSingleThreaded) SetLO(val arch.Word) {
m.state.Cpu.LO = val
}
func (m *StateMutatorSingleThreaded) SetHeap(val uint32) {
func (m *StateMutatorSingleThreaded) SetHeap(val arch.Word) {
m.state.Heap = val
}
......@@ -72,7 +73,7 @@ func (m *StateMutatorSingleThreaded) SetPreimageKey(val common.Hash) {
m.state.PreimageKey = val
}
func (m *StateMutatorSingleThreaded) SetPreimageOffset(val uint32) {
func (m *StateMutatorSingleThreaded) SetPreimageOffset(val arch.Word) {
m.state.PreimageOffset = val
}
......
package mipsevm
import "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
type CpuScalars struct {
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
PC arch.Word `json:"pc"`
NextPC arch.Word `json:"nextPC"`
LO arch.Word `json:"lo"`
HI arch.Word `json:"hi"`
}
const (
......
This diff is collapsed.
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
......@@ -20,10 +21,10 @@ func TestEVM_LL(t *testing.T) {
cases := []struct {
name string
base uint32
base Word
offset int
value uint32
effAddr uint32
value Word
effAddr Word
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
......@@ -37,12 +38,12 @@ func TestEVM_LL(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(0x44)
pc := Word(0x44)
insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(pc), testutil.WithNextPC(pc+4))
state := goVm.GetState()
state.GetMemory().SetMemory(pc, insn)
state.GetMemory().SetMemory(c.effAddr, c.value)
state.GetMemory().SetWord(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
step := state.GetStep()
......@@ -70,10 +71,10 @@ func TestEVM_SC(t *testing.T) {
cases := []struct {
name string
base uint32
base Word
offset int
value uint32
effAddr uint32
value Word
effAddr Word
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
......@@ -87,7 +88,7 @@ func TestEVM_SC(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(0x44)
pc := Word(0x44)
insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(pc), testutil.WithNextPC(pc+4))
state := goVm.GetState()
......@@ -103,7 +104,7 @@ func TestEVM_SC(t *testing.T) {
expected.NextPC = pc + 8
expectedMemory := memory.NewMemory()
expectedMemory.SetMemory(pc, insn)
expectedMemory.SetMemory(c.effAddr, c.value)
expectedMemory.SetWord(c.effAddr, c.value)
expected.MemoryRoot = expectedMemory.MerkleRoot()
if rtReg != 0 {
expected.Registers[rtReg] = 1 // 1 for success
......@@ -130,10 +131,10 @@ func TestEVM_SysRead_Preimage(t *testing.T) {
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
addr Word
count Word
writeLen Word
preimageOffset Word
prestateMem uint32
postateMem uint32
shouldPanic bool
......@@ -157,7 +158,7 @@ func TestEVM_SysRead_Preimage(t *testing.T) {
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
effAddr := 0xFFffFFfc & c.addr
effAddr := arch.AddressMask & c.addr
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey()
oracle := testutil.StaticOracle(t, preimageValue)
goVm := v.VMFactory(oracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPreimageKey(preimageKey), testutil.WithPreimageOffset(c.preimageOffset))
......
......@@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
......@@ -50,13 +51,13 @@ func FuzzStateSyscallBrk(f *testing.F) {
func FuzzStateSyscallMmap(f *testing.F) {
// Add special cases for large memory allocation
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END), int64(1))
f.Add(uint32(0), uint32(1<<31), uint32(program.HEAP_START), int64(2))
f.Add(Word(0), Word(0x1000), Word(program.HEAP_END), int64(1))
f.Add(Word(0), Word(1<<31), Word(program.HEAP_START), int64(2))
// Check edge case - just within bounds
f.Add(uint32(0), uint32(0x1000), uint32(program.HEAP_END-4096), int64(3))
f.Add(Word(0), Word(0x1000), Word(program.HEAP_END-4096), int64(3))
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32, seed int64) {
f.Fuzz(func(t *testing.T, addr Word, siz Word, heap Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
......@@ -112,7 +113,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
testutil.WithRandomization(seed))
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysExitGroup
state.GetRegistersRef()[4] = uint32(exitCode)
state.GetRegistersRef()[4] = Word(exitCode)
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
step := state.GetStep()
......@@ -134,7 +135,7 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
func FuzzStateSyscallFcntl(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32, seed int64) {
f.Fuzz(func(t *testing.T, fd Word, cmd Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(),
......@@ -190,7 +191,7 @@ func FuzzStateSyscallFcntl(f *testing.F) {
func FuzzStateHintRead(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, seed int64) {
f.Fuzz(func(t *testing.T, addr Word, count Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
preimageData := []byte("hello world")
......@@ -227,15 +228,15 @@ func FuzzStateHintRead(f *testing.F) {
func FuzzStatePreimageRead(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, pc uint32, count uint32, preimageOffset uint32, seed int64) {
f.Fuzz(func(t *testing.T, addr arch.Word, pc arch.Word, count arch.Word, preimageOffset arch.Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
effAddr := addr & 0xFF_FF_FF_FC
pc = pc & 0xFF_FF_FF_FC
effAddr := addr & arch.AddressMask
pc = pc & arch.AddressMask
preexistingMemoryVal := [4]byte{0xFF, 0xFF, 0xFF, 0xFF}
preimageValue := []byte("hello world")
preimageData := testutil.AddPreimageLengthPrefix(preimageValue)
if preimageOffset >= uint32(len(preimageData)) || pc == effAddr {
if preimageOffset >= Word(len(preimageData)) || pc == effAddr {
t.SkipNow()
}
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageValue)).PreimageKey()
......@@ -252,13 +253,13 @@ func FuzzStatePreimageRead(f *testing.F) {
state.GetMemory().SetMemory(effAddr, binary.BigEndian.Uint32(preexistingMemoryVal[:]))
step := state.GetStep()
alignment := addr & 3
alignment := addr & arch.ExtMask
writeLen := 4 - alignment
if count < writeLen {
writeLen = count
}
// Cap write length to remaining bytes of the preimage
preimageDataLen := uint32(len(preimageData))
preimageDataLen := Word(len(preimageData))
if preimageOffset+writeLen > preimageDataLen {
writeLen = preimageDataLen - preimageOffset
}
......@@ -290,11 +291,11 @@ func FuzzStatePreimageRead(f *testing.F) {
func FuzzStateHintWrite(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, hint1, hint2, hint3 []byte, randSeed int64) {
f.Fuzz(func(t *testing.T, addr Word, count Word, hint1, hint2, hint3 []byte, randSeed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
// Make sure pc does not overlap with hint data in memory
pc := uint32(0)
pc := Word(0)
if addr <= 8 {
addr += 8
}
......@@ -372,15 +373,15 @@ func FuzzStateHintWrite(f *testing.F) {
func FuzzStatePreimageWrite(f *testing.F) {
versions := GetMipsVersionTestCases(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, seed int64) {
f.Fuzz(func(t *testing.T, addr arch.Word, count arch.Word, seed int64) {
for _, v := range versions {
t.Run(v.Name, func(t *testing.T) {
// Make sure pc does not overlap with preimage data in memory
pc := uint32(0)
pc := Word(0)
if addr <= 8 {
addr += 8
}
effAddr := addr & 0xFF_FF_FF_FC
effAddr := addr & arch.AddressMask
preexistingMemoryVal := [4]byte{0x12, 0x34, 0x56, 0x78}
preimageData := []byte("hello world")
preimageKey := preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey()
......@@ -398,7 +399,7 @@ func FuzzStatePreimageWrite(f *testing.F) {
step := state.GetStep()
expectBytesWritten := count
alignment := addr & 0x3
alignment := addr & arch.ExtMask
sz := 4 - alignment
if sz < expectBytesWritten {
expectBytesWritten = sz
......
......@@ -14,13 +14,13 @@ import (
func FuzzStateSyscallCloneMT(f *testing.F) {
v := GetMultiThreadedTestCase(f)
f.Fuzz(func(t *testing.T, nextThreadId, stackPtr uint32, seed int64) {
f.Fuzz(func(t *testing.T, nextThreadId, stackPtr Word, seed int64) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(seed))
state := mttestutil.GetMtState(t, goVm)
// Update existing threads to avoid collision with nextThreadId
if mttestutil.FindThread(state, nextThreadId) != nil {
for i, t := range mttestutil.GetAllThreads(state) {
t.ThreadId = nextThreadId - uint32(i+1)
t.ThreadId = nextThreadId - Word(i+1)
}
}
......
......@@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
......@@ -97,7 +98,7 @@ func EncodeStepInput(t *testing.T, wit *mipsevm.StepWitness, localContext mipsev
return input
}
func (m *MIPSEVM) encodePreimageOracleInput(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset uint32, localContext mipsevm.LocalContext) ([]byte, error) {
func (m *MIPSEVM) encodePreimageOracleInput(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, localContext mipsevm.LocalContext) ([]byte, error) {
if preimageKey == ([32]byte{}) {
return nil, errors.New("cannot encode pre-image oracle input, witness has no pre-image to proof")
}
......@@ -151,7 +152,7 @@ func (m *MIPSEVM) encodePreimageOracleInput(t *testing.T, preimageKey [32]byte,
}
}
func (m *MIPSEVM) assertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset uint32) {
func (m *MIPSEVM) assertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word) {
poInput, err := m.encodePreimageOracleInput(t, preimageKey, preimageValue, preimageOffset, mipsevm.LocalContext{})
require.NoError(t, err, "encode preimage oracle input")
_, _, evmErr := m.env.Call(m.sender, m.addrs.Oracle, poInput, m.startingGas, common.U2560)
......@@ -200,7 +201,7 @@ func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *Contract
require.Equal(t, 0, len(logs))
}
func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset uint32, contracts *ContractMetadata, tracer *tracing.Hooks) {
func AssertPreimageOracleReverts(t *testing.T, preimageKey [32]byte, preimageValue []byte, preimageOffset arch.Word, contracts *ContractMetadata, tracer *tracing.Hooks) {
evm := NewMIPSEVM(contracts)
evm.SetTracer(tracer)
LogStepFailureAtCleanup(t, evm)
......
......@@ -4,6 +4,7 @@ import (
"encoding/binary"
"math/rand"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
......@@ -21,6 +22,14 @@ func (h *RandHelper) Uint32() uint32 {
return h.r.Uint32()
}
func (h *RandHelper) Word() arch.Word {
if arch.IsMips32 {
return arch.Word(h.r.Uint32())
} else {
return arch.Word(h.r.Uint64())
}
}
func (h *RandHelper) Fraction() float64 {
return h.r.Float64()
}
......@@ -57,10 +66,10 @@ func (h *RandHelper) RandHint() []byte {
return bytes
}
func (h *RandHelper) RandRegisters() *[32]uint32 {
registers := new([32]uint32)
func (h *RandHelper) RandRegisters() *[32]arch.Word {
registers := new([32]arch.Word)
for i := 0; i < 32; i++ {
registers[i] = h.r.Uint32()
registers[i] = h.Word()
}
return registers
}
......@@ -73,8 +82,8 @@ func (h *RandHelper) RandomBytes(t require.TestingT, length int) []byte {
return randBytes
}
func (h *RandHelper) RandPC() uint32 {
return AlignPC(h.r.Uint32())
func (h *RandHelper) RandPC() arch.Word {
return AlignPC(h.Word())
}
func (h *RandHelper) RandStep() uint64 {
......
......@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
......@@ -33,12 +34,12 @@ func AddPreimageLengthPrefix(data []byte) []byte {
type StateMutator interface {
SetPreimageKey(val common.Hash)
SetPreimageOffset(val uint32)
SetPC(val uint32)
SetNextPC(val uint32)
SetHI(val uint32)
SetLO(val uint32)
SetHeap(addr uint32)
SetPreimageOffset(val arch.Word)
SetPC(val arch.Word)
SetNextPC(val arch.Word)
SetHI(val arch.Word)
SetLO(val arch.Word)
SetHeap(addr arch.Word)
SetExitCode(val uint8)
SetExited(val bool)
SetStep(val uint64)
......@@ -48,26 +49,26 @@ type StateMutator interface {
type StateOption func(state StateMutator)
func WithPC(pc uint32) StateOption {
func WithPC(pc arch.Word) StateOption {
return func(state StateMutator) {
state.SetPC(pc)
}
}
func WithNextPC(nextPC uint32) StateOption {
func WithNextPC(nextPC arch.Word) StateOption {
return func(state StateMutator) {
state.SetNextPC(nextPC)
}
}
func WithPCAndNextPC(pc uint32) StateOption {
func WithPCAndNextPC(pc arch.Word) StateOption {
return func(state StateMutator) {
state.SetPC(pc)
state.SetNextPC(pc + 4)
}
}
func WithHeap(addr uint32) StateOption {
func WithHeap(addr arch.Word) StateOption {
return func(state StateMutator) {
state.SetHeap(addr)
}
......@@ -85,7 +86,7 @@ func WithPreimageKey(key common.Hash) StateOption {
}
}
func WithPreimageOffset(offset uint32) StateOption {
func WithPreimageOffset(offset arch.Word) StateOption {
return func(state StateMutator) {
state.SetPreimageOffset(offset)
}
......@@ -103,12 +104,12 @@ func WithRandomization(seed int64) StateOption {
}
}
func AlignPC(pc uint32) uint32 {
func AlignPC(pc arch.Word) arch.Word {
// Memory-align random pc and leave room for nextPC
pc = pc & 0xFF_FF_FF_FC // Align address
if pc >= 0xFF_FF_FF_FC {
pc = pc & arch.AddressMask // Align address
if pc >= arch.AddressMask && arch.IsMips32 {
// Leave room to set and then increment nextPC
pc = 0xFF_FF_FF_FC - 8
pc = arch.AddressMask - 8
}
return pc
}
......@@ -123,17 +124,17 @@ func BoundStep(step uint64) uint64 {
type ExpectedState struct {
PreimageKey common.Hash
PreimageOffset uint32
PC uint32
NextPC uint32
HI uint32
LO uint32
Heap uint32
PreimageOffset arch.Word
PC arch.Word
NextPC arch.Word
HI arch.Word
LO arch.Word
Heap arch.Word
ExitCode uint8
Exited bool
Step uint64
LastHint hexutil.Bytes
Registers [32]uint32
Registers [32]arch.Word
MemoryRoot common.Hash
expectedMemory *memory.Memory
}
......@@ -164,7 +165,7 @@ func (e *ExpectedState) ExpectStep() {
e.NextPC += 4
}
func (e *ExpectedState) ExpectMemoryWrite(addr uint32, val uint32) {
func (e *ExpectedState) ExpectMemoryWrite(addr arch.Word, val uint32) {
e.expectedMemory.SetMemory(addr, val)
e.MemoryRoot = e.expectedMemory.MerkleRoot()
}
......
......@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
......@@ -76,13 +77,13 @@ func RunVMTests_OpenMips[T mipsevm.FPVMState](t *testing.T, stateFactory StateFa
}
if exitGroup {
require.NotEqual(t, uint32(EndAddr), us.GetState().GetPC(), "must not reach end")
require.NotEqual(t, arch.Word(EndAddr), us.GetState().GetPC(), "must not reach end")
require.True(t, us.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), us.GetState().GetExitCode(), "must exit with 1")
} else if expectPanic {
require.NotEqual(t, uint32(EndAddr), us.GetState().GetPC(), "must not reach end")
require.NotEqual(t, arch.Word(EndAddr), us.GetState().GetPC(), "must not reach end")
} else {
require.Equal(t, uint32(EndAddr), us.GetState().GetPC(), "must reach end")
require.Equal(t, arch.Word(EndAddr), us.GetState().GetPC(), "must reach end")
done, result := state.GetMemory().GetMemory(BaseAddrEnd+4), state.GetMemory().GetMemory(BaseAddrEnd+8)
// inspect test result
require.Equal(t, done, uint32(1), "must be done")
......
......@@ -27,7 +27,7 @@ func DetectVersion(path string) (StateVersion, error) {
}
switch ver {
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2:
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64:
return ver, nil
default:
return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver)
......
......@@ -34,6 +34,9 @@ func TestDetectVersion(t *testing.T) {
// Iterate all known versions to ensure we have a test case to detect every state version
for _, version := range StateVersionTypes {
version := version
if version == VersionMultiThreaded64 {
t.Skip("TODO(#12205)")
}
t.Run(version.String(), func(t *testing.T) {
testDetection(t, version, ".bin.gz")
})
......
......@@ -7,6 +7,7 @@ import (
"io"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
"github.com/ethereum-optimism/optimism/cannon/serialize"
......@@ -21,14 +22,16 @@ const (
VersionMultiThreaded
// VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall
VersionSingleThreaded2
VersionMultiThreaded64
)
var (
ErrUnknownVersion = errors.New("unknown version")
ErrJsonNotSupported = errors.New("json not supported")
ErrUnsupportedMipsArch = errors.New("mips architecture is not supported")
)
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2}
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64}
func LoadStateFromFile(path string) (*VersionedState, error) {
if !serialize.IsBinaryFile(path) {
......@@ -45,15 +48,25 @@ func LoadStateFromFile(path string) (*VersionedState, error) {
func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) {
switch state := state.(type) {
case *singlethreaded.State:
if !arch.IsMips32 {
return nil, ErrUnsupportedMipsArch
}
return &VersionedState{
Version: VersionSingleThreaded2,
FPVMState: state,
}, nil
case *multithreaded.State:
if arch.IsMips32 {
return &VersionedState{
Version: VersionMultiThreaded,
FPVMState: state,
}, nil
} else {
return &VersionedState{
Version: VersionMultiThreaded64,
FPVMState: state,
}, nil
}
default:
return nil, fmt.Errorf("%w: %T", ErrUnknownVersion, state)
}
......@@ -82,6 +95,9 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
switch s.Version {
case VersionSingleThreaded2:
if !arch.IsMips32 {
return ErrUnsupportedMipsArch
}
state := &singlethreaded.State{}
if err := state.Deserialize(in); err != nil {
return err
......@@ -89,6 +105,19 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
s.FPVMState = state
return nil
case VersionMultiThreaded:
if !arch.IsMips32 {
return ErrUnsupportedMipsArch
}
state := &multithreaded.State{}
if err := state.Deserialize(in); err != nil {
return err
}
s.FPVMState = state
return nil
case VersionMultiThreaded64:
if arch.IsMips32 {
return ErrUnsupportedMipsArch
}
state := &multithreaded.State{}
if err := state.Deserialize(in); err != nil {
return err
......@@ -106,6 +135,9 @@ func (s *VersionedState) MarshalJSON() ([]byte, error) {
if s.Version != VersionSingleThreaded {
return nil, fmt.Errorf("%w for type %T", ErrJsonNotSupported, s.FPVMState)
}
if !arch.IsMips32 {
return nil, ErrUnsupportedMipsArch
}
return json.Marshal(s.FPVMState)
}
......@@ -117,6 +149,8 @@ func (s StateVersion) String() string {
return "multithreaded"
case VersionSingleThreaded2:
return "singlethreaded-2"
case VersionMultiThreaded64:
return "multithreaded64"
default:
return "unknown"
}
......@@ -130,6 +164,8 @@ func ParseStateVersion(ver string) (StateVersion, error) {
return VersionMultiThreaded, nil
case "singlethreaded-2":
return VersionSingleThreaded2, nil
case "multithreaded64":
return VersionMultiThreaded64, nil
default:
return StateVersion(0), errors.New("unknown state version")
}
......
......@@ -49,6 +49,10 @@ func TestLoadStateFromFile(t *testing.T) {
})
}
func TestLoadStateFromFile64(t *testing.T) {
t.Skip("TODO(#12205): Test asserting that cannon64 fails to decode a 32-bit state")
}
func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
tests := []struct {
version StateVersion
......
package mipsevm
import "github.com/ethereum/go-ethereum/common"
import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum/go-ethereum/common"
)
type LocalContext common.Hash
......@@ -13,7 +16,7 @@ type StepWitness struct {
PreimageKey [32]byte // zeroed when no pre-image is accessed
PreimageValue []byte // including the 8-byte length prefix
PreimageOffset uint32
PreimageOffset arch.Word
}
func (wit *StepWitness) HasPreimage() bool {
......
......@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
)
......@@ -20,9 +21,7 @@ var vmFS embed.FS
const baseDir = "embeds"
func ExecuteCannon(ctx context.Context, args []string, ver versions.StateVersion) error {
switch ver {
case versions.VersionSingleThreaded, versions.VersionSingleThreaded2, versions.VersionMultiThreaded:
default:
if !slices.Contains(versions.StateVersionTypes, ver) {
return errors.New("unsupported version")
}
......
......@@ -10,7 +10,6 @@ import (
)
func Run(ctx *cli.Context) error {
fmt.Printf("args %v\n", os.Args[:])
if len(os.Args) == 3 && os.Args[2] == "--help" {
if err := list(); err != nil {
return err
......
......@@ -49,6 +49,7 @@ require (
golang.org/x/sync v0.8.0
golang.org/x/term v0.24.0
golang.org/x/time v0.6.0
lukechampine.com/uint128 v1.3.0
)
require (
......
......@@ -1098,6 +1098,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
......
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