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
- run:
name: Cannon Go 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:
not: <<parameters.mips64>>
steps:
- run:
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 {
......
package exec
import (
"fmt"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
// TODO(#12205): MIPS64 port. Replace with a custom library
u128 "lukechampine.com/uint128"
)
const (
OpLoadLinked = 0x30
OpStoreConditional = 0x38
OpLoadLinked = 0x30
OpStoreConditional = 0x38
OpLoadLinked64 = 0x34
OpStoreConditional64 = 0x3c
)
func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
func GetInstructionDetails(pc Word, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits
fun = insn & 0x3f // Last 6-bits
......@@ -18,47 +26,53 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun
return insn, opcode, fun
}
func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) (memUpdated bool, memAddr uint32, err error) {
func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) (memUpdated bool, memAddr Word, err error) {
// j-type j/jal
if opcode == 2 || opcode == 3 {
linkReg := uint32(0)
linkReg := Word(0)
if opcode == 3 {
linkReg = 31
}
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
// Take the top bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & SignExtend(0xF0000000, 32)) | Word((insn&0x03FFFFFF)<<2)
stackTracker.PushStack(cpu.PC, target)
err = HandleJump(cpu, registers, linkReg, target)
return
}
// register fetch
rs := uint32(0) // source register 1 value
rt := uint32(0) // source register 2 / temp value
rtReg := (insn >> 16) & 0x1F
rs := Word(0) // source register 1 value
rt := Word(0) // source register 2 / temp value
rtReg := Word((insn >> 16) & 0x1F)
// R-type or I-type (stores rt)
rs = registers[(insn>>21)&0x1F]
rdReg := rtReg
if opcode == 0 || opcode == 0x1c {
if opcode == 0x27 || opcode == 0x1A || opcode == 0x1B { // 64-bit opcodes lwu, ldl, ldr
assertMips64(insn)
// store rt value with store
rt = registers[rtReg]
// store actual rt with lwu, ldl and ldr
rdReg = rtReg
} else if opcode == 0 || opcode == 0x1c {
// R-type (stores rd)
rt = registers[rtReg]
rdReg = (insn >> 11) & 0x1F
rdReg = Word((insn >> 11) & 0x1F)
} else if opcode < 0x20 {
// rt is SignExtImm
// don't sign extend for andi, ori, xori
if opcode == 0xC || opcode == 0xD || opcode == 0xe {
// ZeroExtImm
rt = insn & 0xFFFF
rt = Word(insn & 0xFFFF)
} else {
// SignExtImm
rt = SignExtend(insn&0xFFFF, 16)
rt = SignExtendImmediate(insn)
}
} else if opcode >= 0x28 || opcode == 0x22 || opcode == 0x26 {
// store rt value with store
rt = registers[rtReg]
// store actual rt with lwl and lwr
// store actual rt with lwl, ldl, and lwr
rdReg = rtReg
}
......@@ -67,30 +81,39 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
return
}
storeAddr := uint32(0xFF_FF_FF_FF)
storeAddr := ^Word(0)
// memory fetch (all I-type)
// we do the load for stores also
mem := uint32(0)
mem := Word(0)
if opcode >= 0x20 {
// M[R[rs]+SignExtImm]
rs += SignExtend(insn&0xFFFF, 16)
addr := rs & 0xFFFFFFFC
rs += SignExtendImmediate(insn)
addr := rs & arch.AddressMask
memTracker.TrackMemAccess(addr)
mem = memory.GetMemory(addr)
mem = memory.GetWord(addr)
if opcode >= 0x28 {
// store
storeAddr = addr
// store opcodes don't write back to a register
rdReg = 0
// store for 32-bit
// for 64-bit: ld (0x37) is the only non-store opcode >= 0x28
// SAFETY: On 32-bit mode, 0x37 will be considered an invalid opcode by ExecuteMipsInstruction
if arch.IsMips32 || opcode != 0x37 {
// store
storeAddr = addr
// store opcodes don't write back to a register
rdReg = 0
}
}
}
// ALU
val := ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem)
if opcode == 0 && fun >= 8 && fun < 0x1c {
funSel := uint32(0x1c)
if !arch.IsMips32 {
funSel = 0x20
}
if opcode == 0 && fun >= 8 && fun < funSel {
if fun == 8 || fun == 9 { // jr/jalr
linkReg := uint32(0)
linkReg := Word(0)
if fun == 9 {
linkReg = rdReg
stackTracker.PushStack(cpu.PC, rs)
......@@ -112,16 +135,16 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
// lo and hi registers
// can write back
if fun >= 0x10 && fun < 0x1c {
if fun >= 0x10 && fun < funSel {
err = HandleHiLo(cpu, registers, fun, rs, rt, rdReg)
return
}
}
// write memory
if storeAddr != 0xFF_FF_FF_FF {
if storeAddr != ^Word(0) {
memTracker.TrackMemAccess(storeAddr)
memory.SetMemory(storeAddr, val)
memory.SetWord(storeAddr, val)
memUpdated = true
memAddr = storeAddr
}
......@@ -131,12 +154,24 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
return
}
func SignExtendImmediate(insn uint32) uint32 {
return SignExtend(insn&0xFFFF, 16)
func SignExtendImmediate(insn uint32) Word {
return SignExtend(Word(insn&0xFFFF), 16)
}
func assertMips64(insn uint32) {
if arch.IsMips32 {
panic(fmt.Sprintf("invalid instruction: %x", insn))
}
}
func assertMips64Fun(fun uint32) {
if arch.IsMips32 {
panic(fmt.Sprintf("invalid instruction func: %x", fun))
}
}
func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
if opcode == 0 || (opcode >= 8 && opcode < 0xF) {
func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem Word) Word {
if opcode == 0 || (opcode >= 8 && opcode < 0xF) || (!arch.IsMips32 && (opcode == 0x18 || opcode == 0x19)) {
// transform ArithLogI to SPECIAL
switch opcode {
case 8:
......@@ -153,24 +188,28 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
fun = 0x25 // ori
case 0xE:
fun = 0x26 // xori
case 0x18:
fun = 0x2c // daddi
case 0x19:
fun = 0x2d // daddiu
}
switch fun {
case 0x00: // sll
return rt << ((insn >> 6) & 0x1F)
return SignExtend((rt&0xFFFFFFFF)<<((insn>>6)&0x1F), 32)
case 0x02: // srl
return rt >> ((insn >> 6) & 0x1F)
return SignExtend((rt&0xFFFFFFFF)>>((insn>>6)&0x1F), 32)
case 0x03: // sra
shamt := (insn >> 6) & 0x1F
return SignExtend(rt>>shamt, 32-shamt)
shamt := Word((insn >> 6) & 0x1F)
return SignExtend((rt&0xFFFFFFFF)>>shamt, 32-shamt)
case 0x04: // sllv
return rt << (rs & 0x1F)
return SignExtend((rt&0xFFFFFFFF)<<(rs&0x1F), 32)
case 0x06: // srlv
return rt >> (rs & 0x1F)
return SignExtend((rt&0xFFFFFFFF)>>(rs&0x1F), 32)
case 0x07: // srav
shamt := rs & 0x1F
return SignExtend(rt>>shamt, 32-shamt)
// functs in range [0x8, 0x1b] are handled specially by other functions
shamt := Word(rs & 0x1F)
return SignExtend((rt&0xFFFFFFFF)>>shamt, 32-shamt)
// functs in range [0x8, 0x1b] for 32-bit and [0x8, 0x1f] for 64-bit are handled specially by other functions
case 0x08: // jr
return rs
case 0x09: // jalr
......@@ -192,6 +231,15 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
return rs
case 0x13: // mtlo
return rs
case 0x14: // dsllv
assertMips64(insn)
return rt
case 0x16: // dsrlv
assertMips64(insn)
return rt
case 0x17: // dsrav
assertMips64(insn)
return rt
case 0x18: // mult
return rs
case 0x19: // multu
......@@ -200,15 +248,27 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
return rs
case 0x1b: // divu
return rs
case 0x1C: // dmult
assertMips64(insn)
return rs
case 0x1D: // dmultu
assertMips64(insn)
return rs
case 0x1E: // ddiv
assertMips64(insn)
return rs
case 0x1F: // ddivu
assertMips64(insn)
return rs
// The rest includes transformed R-type arith imm instructions
case 0x20: // add
return rs + rt
return SignExtend(Word(int32(rs)+int32(rt)), 32)
case 0x21: // addu
return rs + rt
return SignExtend(Word(uint32(rs)+uint32(rt)), 32)
case 0x22: // sub
return rs - rt
return SignExtend(Word(int32(rs)-int32(rt)), 32)
case 0x23: // subu
return rs - rt
return SignExtend(Word(uint32(rs)-uint32(rt)), 32)
case 0x24: // and
return rs & rt
case 0x25: // or
......@@ -218,7 +278,7 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
case 0x27: // nor
return ^(rs | rt)
case 0x2a: // slti
if int32(rs) < int32(rt) {
if arch.SignedInteger(rs) < arch.SignedInteger(rt) {
return 1
}
return 0
......@@ -227,8 +287,38 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
return 1
}
return 0
case 0x2c: // dadd
assertMips64(insn)
return rs + rt
case 0x2d: // daddu
assertMips64(insn)
return rs + rt
case 0x2e: // dsub
assertMips64(insn)
return rs - rt
case 0x2f: // dsubu
assertMips64(insn)
return rs - rt
case 0x38: // dsll
assertMips64(insn)
return rt << ((insn >> 6) & 0x1f)
case 0x3A: // dsrl
assertMips64(insn)
return rt >> ((insn >> 6) & 0x1f)
case 0x3B: // dsra
assertMips64(insn)
return Word(int64(rt) >> ((insn >> 6) & 0x1f))
case 0x3C: // dsll32
assertMips64(insn)
return rt << (((insn >> 6) & 0x1f) + 32)
case 0x3E: // dsll32
assertMips64(insn)
return rt >> (((insn >> 6) & 0x1f) + 32)
case 0x3F: // dsll32
assertMips64(insn)
return Word(int64(rt) >> (((insn >> 6) & 0x1f) + 32))
default:
panic("invalid instruction")
panic(fmt.Sprintf("invalid instruction: %x", insn))
}
} else {
switch opcode {
......@@ -236,7 +326,7 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
case 0x1C:
switch fun {
case 0x2: // mul
return uint32(int32(rs) * int32(rt))
return SignExtend(Word(int32(rs)*int32(rt)), 32)
case 0x20, 0x21: // clz, clo
if fun == 0x20 {
rs = ^rs
......@@ -245,45 +335,98 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
for ; rs&0x80000000 != 0; i++ {
rs <<= 1
}
return i
return Word(i)
}
case 0x0F: // lui
return rt << 16
return SignExtend(rt<<16, 32)
case 0x20: // lb
return SignExtend((mem>>(24-(rs&3)*8))&0xFF, 8)
msb := uint32(arch.WordSize - 8) // 24 for 32-bit and 56 for 64-bit
return SignExtend((mem>>(msb-uint32(rs&arch.ExtMask)*8))&0xFF, 8)
case 0x21: // lh
return SignExtend((mem>>(16-(rs&2)*8))&0xFFFF, 16)
msb := uint32(arch.WordSize - 16) // 16 for 32-bit and 48 for 64-bit
mask := Word(arch.ExtMask - 1)
return SignExtend((mem>>(msb-uint32(rs&mask)*8))&0xFFFF, 16)
case 0x22: // lwl
val := mem << ((rs & 3) * 8)
mask := uint32(0xFFFFFFFF) << ((rs & 3) * 8)
return (rt & ^mask) | val
mask := Word(uint32(0xFFFFFFFF) << ((rs & 3) * 8))
return SignExtend(((rt & ^mask)|val)&0xFFFFFFFF, 32)
case 0x23: // lw
// TODO(#12205): port to MIPS64
return mem
//return SignExtend((mem>>(32-((rs&0x4)<<3)))&0xFFFFFFFF, 32)
case 0x24: // lbu
return (mem >> (24 - (rs&3)*8)) & 0xFF
msb := uint32(arch.WordSize - 8) // 24 for 32-bit and 56 for 64-bit
return (mem >> (msb - uint32(rs&arch.ExtMask)*8)) & 0xFF
case 0x25: // lhu
return (mem >> (16 - (rs&2)*8)) & 0xFFFF
msb := uint32(arch.WordSize - 16) // 16 for 32-bit and 48 for 64-bit
mask := Word(arch.ExtMask - 1)
return (mem >> (msb - uint32(rs&mask)*8)) & 0xFFFF
case 0x26: // lwr
val := mem >> (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) >> (24 - (rs&3)*8)
return (rt & ^mask) | val
mask := Word(uint32(0xFFFFFFFF) >> (24 - (rs&3)*8))
return SignExtend(((rt & ^mask)|val)&0xFFFFFFFF, 32)
case 0x28: // sb
val := (rt & 0xFF) << (24 - (rs&3)*8)
mask := 0xFFFFFFFF ^ uint32(0xFF<<(24-(rs&3)*8))
msb := uint32(arch.WordSize - 8) // 24 for 32-bit and 56 for 64-bit
val := (rt & 0xFF) << (msb - uint32(rs&arch.ExtMask)*8)
mask := ^Word(0) ^ Word(0xFF<<(msb-uint32(rs&arch.ExtMask)*8))
return (mem & mask) | val
case 0x29: // sh
val := (rt & 0xFFFF) << (16 - (rs&2)*8)
mask := 0xFFFFFFFF ^ uint32(0xFFFF<<(16-(rs&2)*8))
msb := uint32(arch.WordSize - 16) // 16 for 32-bit and 48 for 64-bit
rsMask := Word(arch.ExtMask - 1) // 2 for 32-bit and 6 for 64-bit
sl := msb - uint32(rs&rsMask)*8
val := (rt & 0xFFFF) << sl
mask := ^Word(0) ^ Word(0xFFFF<<sl)
return (mem & mask) | val
case 0x2a: // swl
// TODO(#12205): port to MIPS64
val := rt >> ((rs & 3) * 8)
mask := uint32(0xFFFFFFFF) >> ((rs & 3) * 8)
return (mem & ^mask) | val
return (mem & Word(^mask)) | val
case 0x2b: // sw
// TODO(#12205): port to MIPS64
return rt
case 0x2e: // swr
// TODO(#12205): port to MIPS64
val := rt << (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8)
return (mem & Word(^mask)) | val
// MIPS64
case 0x1A: // ldl
assertMips64(insn)
sl := (rs & 0x7) << 3
val := mem << sl
mask := ^Word(0) << sl
return val | (rt & ^mask)
case 0x1B: // ldr
assertMips64(insn)
sr := 56 - ((rs & 0x7) << 3)
val := mem >> sr
mask := ^Word(0) << (64 - sr)
return val | (rt & mask)
case 0x27: // lwu
assertMips64(insn)
return (mem >> (32 - ((rs & 0x4) << 3))) & 0xFFFFFFFF
case 0x2C: // sdl
assertMips64(insn)
sr := (rs & 0x7) << 3
val := rt >> sr
mask := ^Word(0) >> sr
return val | (mem & ^mask)
case 0x2D: // sdr
assertMips64(insn)
sl := 56 - ((rs & 0x7) << 3)
val := rt << sl
mask := ^Word(0) << sl
return val | (mem & ^mask)
case 0x37: // ld
assertMips64(insn)
return mem
case 0x3F: // sd
assertMips64(insn)
sl := (rs & 0x7) << 3
val := rt << sl
mask := ^Word(0) << sl
return (mem & ^mask) | val
default:
panic("invalid instruction")
......@@ -292,10 +435,10 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
panic("invalid instruction")
}
func SignExtend(dat uint32, idx uint32) uint32 {
func SignExtend(dat Word, idx Word) Word {
isSigned := (dat >> (idx - 1)) != 0
signed := ((uint32(1) << (32 - idx)) - 1) << idx
mask := (uint32(1) << idx) - 1
signed := ((Word(1) << (arch.WordSize - idx)) - 1) << idx
mask := (Word(1) << idx) - 1
if isSigned {
return dat&mask | signed
} else {
......@@ -303,7 +446,7 @@ func SignExtend(dat uint32, idx uint32) uint32 {
}
}
func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error {
func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]Word, opcode uint32, insn uint32, rtReg Word, rs Word) error {
if cpu.NextPC != cpu.PC+4 {
panic("branch in delay slot")
}
......@@ -313,9 +456,9 @@ func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]uint32, opcode uint32,
rt := registers[rtReg]
shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5)
} else if opcode == 6 {
shouldBranch = int32(rs) <= 0 // blez
shouldBranch = arch.SignedInteger(rs) <= 0 // blez
} else if opcode == 7 {
shouldBranch = int32(rs) > 0 // bgtz
shouldBranch = arch.SignedInteger(rs) > 0 // bgtz
} else if opcode == 1 {
// regimm
rtv := (insn >> 16) & 0x1F
......@@ -330,15 +473,15 @@ func HandleBranch(cpu *mipsevm.CpuScalars, registers *[32]uint32, opcode uint32,
prevPC := cpu.PC
cpu.PC = cpu.NextPC // execute the delay slot first
if shouldBranch {
cpu.NextPC = prevPC + 4 + (SignExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to.
cpu.NextPC = prevPC + 4 + (SignExtend(Word(insn&0xFFFF), 16) << 2) // then continue with the instruction the branch jumps to.
} else {
cpu.NextPC = cpu.NextPC + 4 // branch not taken
}
return nil
}
func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]uint32, fun uint32, rs uint32, rt uint32, storeReg uint32) error {
val := uint32(0)
func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]Word, fun uint32, rs Word, rt Word, storeReg Word) error {
val := Word(0)
switch fun {
case 0x10: // mfhi
val = cpu.HI
......@@ -350,16 +493,44 @@ func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]uint32, fun uint32, rs u
cpu.LO = rs
case 0x18: // mult
acc := uint64(int64(int32(rs)) * int64(int32(rt)))
cpu.HI = uint32(acc >> 32)
cpu.LO = uint32(acc)
cpu.HI = SignExtend(Word(acc>>32), 32)
cpu.LO = SignExtend(Word(uint32(acc)), 32)
case 0x19: // multu
acc := uint64(uint64(rs) * uint64(rt))
cpu.HI = uint32(acc >> 32)
cpu.LO = uint32(acc)
acc := uint64(uint32(rs)) * uint64(uint32(rt))
cpu.HI = SignExtend(Word(acc>>32), 32)
cpu.LO = SignExtend(Word(uint32(acc)), 32)
case 0x1a: // div
cpu.HI = uint32(int32(rs) % int32(rt))
cpu.LO = uint32(int32(rs) / int32(rt))
cpu.HI = SignExtend(Word(int32(rs)%int32(rt)), 32)
cpu.LO = SignExtend(Word(int32(rs)/int32(rt)), 32)
case 0x1b: // divu
cpu.HI = SignExtend(Word(uint32(rs)%uint32(rt)), 32)
cpu.LO = SignExtend(Word(uint32(rs)/uint32(rt)), 32)
case 0x14: // dsllv
assertMips64Fun(fun)
val = rt << (rs & 0x3F)
case 0x16: // dsrlv
assertMips64Fun(fun)
val = rt >> (rs & 0x3F)
case 0x17: // dsrav
assertMips64Fun(fun)
val = Word(int64(rt) >> (rs & 0x3F))
case 0x1c: // dmult
// TODO(#12205): port to MIPS64. Is signed multiply needed for dmult
assertMips64Fun(fun)
acc := u128.From64(uint64(rs)).Mul(u128.From64(uint64(rt)))
cpu.HI = Word(acc.Hi)
cpu.LO = Word(acc.Lo)
case 0x1d: // dmultu
assertMips64Fun(fun)
acc := u128.From64(uint64(rs)).Mul(u128.From64(uint64(rt)))
cpu.HI = Word(acc.Hi)
cpu.LO = Word(acc.Lo)
case 0x1e: // ddiv
assertMips64Fun(fun)
cpu.HI = Word(int64(rs) % int64(rt))
cpu.LO = Word(int64(rs) / int64(rt))
case 0x1f: // ddivu
assertMips64Fun(fun)
cpu.HI = rs % rt
cpu.LO = rs / rt
}
......@@ -373,7 +544,7 @@ func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]uint32, fun uint32, rs u
return nil
}
func HandleJump(cpu *mipsevm.CpuScalars, registers *[32]uint32, linkReg uint32, dest uint32) error {
func HandleJump(cpu *mipsevm.CpuScalars, registers *[32]Word, linkReg Word, dest Word) error {
if cpu.NextPC != cpu.PC+4 {
panic("jump in delay slot")
}
......@@ -386,7 +557,7 @@ func HandleJump(cpu *mipsevm.CpuScalars, registers *[32]uint32, linkReg uint32,
return nil
}
func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, val uint32, conditional bool) error {
func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]Word, storeReg Word, val Word, conditional bool) error {
if storeReg >= 32 {
panic("invalid register")
}
......
......@@ -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
}
......@@ -9,21 +9,25 @@ import (
"slices"
"sort"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum/go-ethereum/crypto"
"golang.org/x/exp/maps"
)
// Note: 2**12 = 4 KiB, the min phys page size in the Go runtime.
const (
PageAddrSize = 12
PageKeySize = 32 - PageAddrSize
PageSize = 1 << PageAddrSize
PageAddrMask = PageSize - 1
MaxPageCount = 1 << PageKeySize
PageKeyMask = MaxPageCount - 1
PageAddrSize = arch.PageAddrSize
PageKeySize = arch.PageKeySize
PageSize = 1 << PageAddrSize
PageAddrMask = PageSize - 1
MaxPageCount = 1 << PageKeySize
PageKeyMask = MaxPageCount - 1
MemProofLeafCount = arch.MemProofLeafCount
)
const MEM_PROOF_SIZE = 28 * 32
const MEM_PROOF_SIZE = arch.MemProofSize
type Word = arch.Word
func HashPair(left, right [32]byte) [32]byte {
out := crypto.Keccak256Hash(left[:], right[:])
......@@ -45,22 +49,22 @@ type Memory struct {
nodes map[uint64]*[32]byte
// pageIndex -> cached page
pages map[uint32]*CachedPage
pages map[Word]*CachedPage
// Note: since we don't de-alloc pages, we don't do ref-counting.
// Once a page exists, it doesn't leave memory
// two caches: we often read instructions from one page, and do memory things with another page.
// this prevents map lookups each instruction
lastPageKeys [2]uint32
lastPageKeys [2]Word
lastPage [2]*CachedPage
}
func NewMemory() *Memory {
return &Memory{
nodes: make(map[uint64]*[32]byte),
pages: make(map[uint32]*CachedPage),
lastPageKeys: [2]uint32{^uint32(0), ^uint32(0)}, // default to invalid keys, to not match any pages
pages: make(map[Word]*CachedPage),
lastPageKeys: [2]Word{^Word(0), ^Word(0)}, // default to invalid keys, to not match any pages
}
}
......@@ -68,7 +72,7 @@ func (m *Memory) PageCount() int {
return len(m.pages)
}
func (m *Memory) ForEachPage(fn func(pageIndex uint32, page *Page) error) error {
func (m *Memory) ForEachPage(fn func(pageIndex Word, page *Page) error) error {
for pageIndex, cachedPage := range m.pages {
if err := fn(pageIndex, cachedPage.Data); err != nil {
return err
......@@ -77,16 +81,16 @@ func (m *Memory) ForEachPage(fn func(pageIndex uint32, page *Page) error) error
return nil
}
func (m *Memory) Invalidate(addr uint32) {
// addr must be aligned to 4 bytes
if addr&0x3 != 0 {
func (m *Memory) invalidate(addr Word) {
// addr must be aligned
if addr&arch.ExtMask != 0 {
panic(fmt.Errorf("unaligned memory access: %x", addr))
}
// find page, and invalidate addr within it
if p, ok := m.pageLookup(addr >> PageAddrSize); ok {
prevValid := p.Ok[1]
p.Invalidate(addr & PageAddrMask)
p.invalidate(addr & PageAddrMask)
if !prevValid { // if the page was already invalid before, then nodes to mem-root will also still be.
return
}
......@@ -105,23 +109,23 @@ func (m *Memory) Invalidate(addr uint32) {
func (m *Memory) MerkleizeSubtree(gindex uint64) [32]byte {
l := uint64(bits.Len64(gindex))
if l > 28 {
if l > MemProofLeafCount {
panic("gindex too deep")
}
if l > PageKeySize {
depthIntoPage := l - 1 - PageKeySize
pageIndex := (gindex >> depthIntoPage) & PageKeyMask
if p, ok := m.pages[uint32(pageIndex)]; ok {
if p, ok := m.pages[Word(pageIndex)]; ok {
pageGindex := (1 << depthIntoPage) | (gindex & ((1 << depthIntoPage) - 1))
return p.MerkleizeSubtree(pageGindex)
} else {
return zeroHashes[28-l] // page does not exist
return zeroHashes[MemProofLeafCount-l] // page does not exist
}
}
n, ok := m.nodes[gindex]
if !ok {
// if the node doesn't exist, the whole sub-tree is zeroed
return zeroHashes[28-l]
return zeroHashes[MemProofLeafCount-l]
}
if n != nil {
return *n
......@@ -133,16 +137,16 @@ func (m *Memory) MerkleizeSubtree(gindex uint64) [32]byte {
return r
}
func (m *Memory) MerkleProof(addr uint32) (out [MEM_PROOF_SIZE]byte) {
func (m *Memory) MerkleProof(addr Word) (out [MEM_PROOF_SIZE]byte) {
proof := m.traverseBranch(1, addr, 0)
// encode the proof
for i := 0; i < 28; i++ {
for i := 0; i < MemProofLeafCount; i++ {
copy(out[i*32:(i+1)*32], proof[i][:])
}
return out
}
func (m *Memory) traverseBranch(parent uint64, addr uint32, depth uint8) (proof [][32]byte) {
func (m *Memory) traverseBranch(parent uint64, addr Word, depth uint8) (proof [][32]byte) {
if depth == 32-5 {
proof = make([][32]byte, 0, 32-5+1)
proof = append(proof, m.MerkleizeSubtree(parent))
......@@ -166,7 +170,7 @@ func (m *Memory) MerkleRoot() [32]byte {
return m.MerkleizeSubtree(1)
}
func (m *Memory) pageLookup(pageIndex uint32) (*CachedPage, bool) {
func (m *Memory) pageLookup(pageIndex Word) (*CachedPage, bool) {
// hit caches
if pageIndex == m.lastPageKeys[0] {
return m.lastPage[0], true
......@@ -187,9 +191,9 @@ func (m *Memory) pageLookup(pageIndex uint32) (*CachedPage, bool) {
return p, ok
}
func (m *Memory) SetMemory(addr uint32, v uint32) {
func (m *Memory) SetMemory(addr Word, v uint32) {
// addr must be aligned to 4 bytes
if addr&0x3 != 0 {
if addr&arch.ExtMask != 0 {
panic(fmt.Errorf("unaligned memory access: %x", addr))
}
......@@ -201,14 +205,35 @@ func (m *Memory) SetMemory(addr uint32, v uint32) {
// Go may mmap relatively large ranges, but we only allocate the pages just in time.
p = m.AllocPage(pageIndex)
} else {
m.Invalidate(addr) // invalidate this branch of memory, now that the value changed
m.invalidate(addr) // invalidate this branch of memory, now that the value changed
}
binary.BigEndian.PutUint32(p.Data[pageAddr:pageAddr+4], v)
}
func (m *Memory) GetMemory(addr uint32) uint32 {
// SetWord stores [arch.Word] sized values at the specified address
func (m *Memory) SetWord(addr Word, v Word) {
// addr must be aligned to WordSizeBytes bytes
if addr&arch.ExtMask != 0 {
panic(fmt.Errorf("unaligned memory access: %x", addr))
}
pageIndex := addr >> PageAddrSize
pageAddr := addr & PageAddrMask
p, ok := m.pageLookup(pageIndex)
if !ok {
// allocate the page if we have not already.
// Go may mmap relatively large ranges, but we only allocate the pages just in time.
p = m.AllocPage(pageIndex)
} else {
m.invalidate(addr) // invalidate this branch of memory, now that the value changed
}
arch.ByteOrderWord.PutWord(p.Data[pageAddr:pageAddr+arch.WordSizeBytes], v)
}
// GetMemory reads the 32-bit value located at the specified address.
func (m *Memory) GetMemory(addr Word) uint32 {
// addr must be aligned to 4 bytes
if addr&0x3 != 0 {
if addr&arch.ExtMask != 0 {
panic(fmt.Errorf("unaligned memory access: %x", addr))
}
p, ok := m.pageLookup(addr >> PageAddrSize)
......@@ -219,7 +244,22 @@ func (m *Memory) GetMemory(addr uint32) uint32 {
return binary.BigEndian.Uint32(p.Data[pageAddr : pageAddr+4])
}
func (m *Memory) AllocPage(pageIndex uint32) *CachedPage {
// GetWord reads the maximum sized value, [arch.Word], located at the specified address.
// Note: Also known by the MIPS64 specification as a "double-word" memory access.
func (m *Memory) GetWord(addr Word) Word {
// addr must be word aligned
if addr&arch.ExtMask != 0 {
panic(fmt.Errorf("unaligned memory access: %x", addr))
}
p, ok := m.pageLookup(addr >> PageAddrSize)
if !ok {
return 0
}
pageAddr := addr & PageAddrMask
return arch.ByteOrderWord.Word(p.Data[pageAddr : pageAddr+arch.WordSizeBytes])
}
func (m *Memory) AllocPage(pageIndex Word) *CachedPage {
p := &CachedPage{Data: new(Page)}
m.pages[pageIndex] = p
// make nodes to root
......@@ -232,8 +272,8 @@ func (m *Memory) AllocPage(pageIndex uint32) *CachedPage {
}
type pageEntry struct {
Index uint32 `json:"index"`
Data *Page `json:"data"`
Index Word `json:"index"`
Data *Page `json:"data"`
}
func (m *Memory) MarshalJSON() ([]byte, error) { // nosemgrep
......@@ -256,8 +296,8 @@ func (m *Memory) UnmarshalJSON(data []byte) error {
return err
}
m.nodes = make(map[uint64]*[32]byte)
m.pages = make(map[uint32]*CachedPage)
m.lastPageKeys = [2]uint32{^uint32(0), ^uint32(0)}
m.pages = make(map[Word]*CachedPage)
m.lastPageKeys = [2]Word{^Word(0), ^Word(0)}
m.lastPage = [2]*CachedPage{nil, nil}
for i, p := range pages {
if _, ok := m.pages[p.Index]; ok {
......@@ -268,7 +308,7 @@ func (m *Memory) UnmarshalJSON(data []byte) error {
return nil
}
func (m *Memory) SetMemoryRange(addr uint32, r io.Reader) error {
func (m *Memory) SetMemoryRange(addr Word, r io.Reader) error {
for {
pageIndex := addr >> PageAddrSize
pageAddr := addr & PageAddrMask
......@@ -284,7 +324,7 @@ func (m *Memory) SetMemoryRange(addr uint32, r io.Reader) error {
}
return err
}
addr += uint32(n)
addr += Word(n)
}
}
......@@ -292,13 +332,13 @@ func (m *Memory) SetMemoryRange(addr uint32, r io.Reader) error {
// The format is a simple concatenation of fields, with prefixed item count for repeating items and using big endian
// encoding for numbers.
//
// len(PageCount) uint32
// len(PageCount) Word
// For each page (order is arbitrary):
//
// page index uint32
// page index Word
// page Data [PageSize]byte
func (m *Memory) Serialize(out io.Writer) error {
if err := binary.Write(out, binary.BigEndian, uint32(m.PageCount())); err != nil {
if err := binary.Write(out, binary.BigEndian, Word(m.PageCount())); err != nil {
return err
}
indexes := maps.Keys(m.pages)
......@@ -317,12 +357,12 @@ func (m *Memory) Serialize(out io.Writer) error {
}
func (m *Memory) Deserialize(in io.Reader) error {
var pageCount uint32
var pageCount Word
if err := binary.Read(in, binary.BigEndian, &pageCount); err != nil {
return err
}
for i := uint32(0); i < pageCount; i++ {
var pageIndex uint32
for i := Word(0); i < pageCount; i++ {
var pageIndex Word
if err := binary.Read(in, binary.BigEndian, &pageIndex); err != nil {
return err
}
......@@ -337,8 +377,8 @@ func (m *Memory) Deserialize(in io.Reader) error {
func (m *Memory) Copy() *Memory {
out := NewMemory()
out.nodes = make(map[uint64]*[32]byte)
out.pages = make(map[uint32]*CachedPage)
out.lastPageKeys = [2]uint32{^uint32(0), ^uint32(0)}
out.pages = make(map[Word]*CachedPage)
out.lastPageKeys = [2]Word{^Word(0), ^Word(0)}
out.lastPage = [2]*CachedPage{nil, nil}
for k, page := range m.pages {
data := new(Page)
......@@ -350,8 +390,8 @@ func (m *Memory) Copy() *Memory {
type memReader struct {
m *Memory
addr uint32
count uint32
addr Word
count Word
}
func (r *memReader) Read(dest []byte) (n int, err error) {
......@@ -365,7 +405,7 @@ func (r *memReader) Read(dest []byte) (n int, err error) {
pageIndex := r.addr >> PageAddrSize
start := r.addr & PageAddrMask
end := uint32(PageSize)
end := Word(PageSize)
if pageIndex == (endAddr >> PageAddrSize) {
end = endAddr & PageAddrMask
......@@ -376,12 +416,12 @@ func (r *memReader) Read(dest []byte) (n int, err error) {
} else {
n = copy(dest, make([]byte, end-start)) // default to zeroes
}
r.addr += uint32(n)
r.count -= uint32(n)
r.addr += Word(n)
r.count -= Word(n)
return n, nil
}
func (m *Memory) ReadMemoryRange(addr uint32, count uint32) io.Reader {
func (m *Memory) ReadMemoryRange(addr Word, count Word) io.Reader {
return &memReader{m: m, addr: addr, count: count}
}
......
......@@ -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
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
Heap Word // to handle mmap growth
LLReservationActive bool // Whether there is an active memory reservation initiated via the LL (load linked) op
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 (
......
......@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"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"
......@@ -98,13 +99,13 @@ func TestEVM(t *testing.T) {
"mipsevm produced different state than EVM at step %d", state.GetStep())
}
if exitGroup {
require.NotEqual(t, uint32(testutil.EndAddr), goVm.GetState().GetPC(), "must not reach end")
require.NotEqual(t, arch.Word(testutil.EndAddr), goVm.GetState().GetPC(), "must not reach end")
require.True(t, goVm.GetState().GetExited(), "must set exited state")
require.Equal(t, uint8(1), goVm.GetState().GetExitCode(), "must exit with 1")
} else if expectPanic {
require.NotEqual(t, uint32(testutil.EndAddr), state.GetPC(), "must not reach end")
require.NotEqual(t, arch.Word(testutil.EndAddr), state.GetPC(), "must not reach end")
} else {
require.Equal(t, uint32(testutil.EndAddr), state.GetPC(), "must reach end")
require.Equal(t, arch.Word(testutil.EndAddr), state.GetPC(), "must reach end")
// inspect test result
done, result := state.GetMemory().GetMemory(testutil.BaseAddrEnd+4), state.GetMemory().GetMemory(testutil.BaseAddrEnd+8)
require.Equal(t, done, uint32(1), "must be done")
......@@ -121,10 +122,10 @@ func TestEVMSingleStep_Jump(t *testing.T) {
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
pc uint32
nextPC uint32
pc arch.Word
nextPC arch.Word
insn uint32
expectNextPC uint32
expectNextPC arch.Word
expectLink bool
}{
{name: "j MSB set target", pc: 0, nextPC: 4, insn: 0x0A_00_00_02, expectNextPC: 0x08_00_00_08}, // j 0x02_00_00_02
......@@ -169,29 +170,29 @@ func TestEVMSingleStep_Operators(t *testing.T) {
cases := []struct {
name string
isImm bool
rs uint32
rt uint32
rs Word
rt Word
imm uint16
funct uint32
opcode uint32
expectRes uint32
expectRes Word
}{
{name: "add", funct: 0x20, isImm: false, rs: uint32(12), rt: uint32(20), expectRes: uint32(32)}, // add t0, s1, s2
{name: "addu", funct: 0x21, isImm: false, rs: uint32(12), rt: uint32(20), expectRes: uint32(32)}, // addu t0, s1, s2
{name: "addi", opcode: 0x8, isImm: true, rs: uint32(4), rt: uint32(1), imm: uint16(40), expectRes: uint32(44)}, // addi t0, s1, 40
{name: "addi sign", opcode: 0x8, isImm: true, rs: uint32(2), rt: uint32(1), imm: uint16(0xfffe), expectRes: uint32(0)}, // addi t0, s1, -2
{name: "addiu", opcode: 0x9, isImm: true, rs: uint32(4), rt: uint32(1), imm: uint16(40), expectRes: uint32(44)}, // addiu t0, s1, 40
{name: "sub", funct: 0x22, isImm: false, rs: uint32(20), rt: uint32(12), expectRes: uint32(8)}, // sub t0, s1, s2
{name: "subu", funct: 0x23, isImm: false, rs: uint32(20), rt: uint32(12), expectRes: uint32(8)}, // subu t0, s1, s2
{name: "and", funct: 0x24, isImm: false, rs: uint32(1200), rt: uint32(490), expectRes: uint32(160)}, // and t0, s1, s2
{name: "andi", opcode: 0xc, isImm: true, rs: uint32(4), rt: uint32(1), imm: uint16(40), expectRes: uint32(0)}, // andi t0, s1, 40
{name: "or", funct: 0x25, isImm: false, rs: uint32(1200), rt: uint32(490), expectRes: uint32(1530)}, // or t0, s1, s2
{name: "ori", opcode: 0xd, isImm: true, rs: uint32(4), rt: uint32(1), imm: uint16(40), expectRes: uint32(44)}, // ori t0, s1, 40
{name: "xor", funct: 0x26, isImm: false, rs: uint32(1200), rt: uint32(490), expectRes: uint32(1370)}, // xor t0, s1, s2
{name: "xori", opcode: 0xe, isImm: true, rs: uint32(4), rt: uint32(1), imm: uint16(40), expectRes: uint32(44)}, // xori t0, s1, 40
{name: "nor", funct: 0x27, isImm: false, rs: uint32(1200), rt: uint32(490), expectRes: uint32(4294965765)}, // nor t0, s1, s2
{name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: uint32(5), expectRes: uint32(1)}, // slt t0, s1, s2
{name: "sltu", funct: 0x2b, isImm: false, rs: uint32(1200), rt: uint32(490), expectRes: uint32(0)}, // sltu t0, s1, s2
{name: "add", funct: 0x20, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // add t0, s1, s2
{name: "addu", funct: 0x21, isImm: false, rs: Word(12), rt: Word(20), expectRes: Word(32)}, // addu t0, s1, s2
{name: "addi", opcode: 0x8, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // addi t0, s1, 40
{name: "addi sign", opcode: 0x8, isImm: true, rs: Word(2), rt: Word(1), imm: uint16(0xfffe), expectRes: Word(0)}, // addi t0, s1, -2
{name: "addiu", opcode: 0x9, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // addiu t0, s1, 40
{name: "sub", funct: 0x22, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // sub t0, s1, s2
{name: "subu", funct: 0x23, isImm: false, rs: Word(20), rt: Word(12), expectRes: Word(8)}, // subu t0, s1, s2
{name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2
{name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40
{name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2
{name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40
{name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2
{name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40
{name: "nor", funct: 0x27, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(4294965765)}, // nor t0, s1, s2
{name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2
{name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2
}
for _, v := range versions {
......@@ -237,17 +238,17 @@ func TestEVM_MMap(t *testing.T) {
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
heap uint32
address uint32
size uint32
heap arch.Word
address arch.Word
size arch.Word
shouldFail bool
expectedHeap uint32
expectedHeap arch.Word
}{
{name: "Increment heap by max value", heap: program.HEAP_START, address: 0, size: ^uint32(0), shouldFail: true},
{name: "Increment heap to 0", heap: program.HEAP_START, address: 0, size: ^uint32(0) - program.HEAP_START + 1, shouldFail: true},
{name: "Increment heap to previous page", heap: program.HEAP_START, address: 0, size: ^uint32(0) - program.HEAP_START - memory.PageSize + 1, shouldFail: true},
{name: "Increment max page size", heap: program.HEAP_START, address: 0, size: ^uint32(0) & ^uint32(memory.PageAddrMask), shouldFail: true},
{name: "Increment max page size from 0", heap: 0, address: 0, size: ^uint32(0) & ^uint32(memory.PageAddrMask), shouldFail: true},
{name: "Increment heap by max value", heap: program.HEAP_START, address: 0, size: ^arch.Word(0), shouldFail: true},
{name: "Increment heap to 0", heap: program.HEAP_START, address: 0, size: ^arch.Word(0) - program.HEAP_START + 1, shouldFail: true},
{name: "Increment heap to previous page", heap: program.HEAP_START, address: 0, size: ^arch.Word(0) - program.HEAP_START - memory.PageSize + 1, shouldFail: true},
{name: "Increment max page size", heap: program.HEAP_START, address: 0, size: ^arch.Word(0) & ^arch.Word(memory.PageAddrMask), shouldFail: true},
{name: "Increment max page size from 0", heap: 0, address: 0, size: ^arch.Word(0) & ^arch.Word(memory.PageAddrMask), shouldFail: true},
{name: "Increment heap at limit", heap: program.HEAP_END, address: 0, size: 1, shouldFail: true},
{name: "Increment heap to limit", heap: program.HEAP_END - memory.PageSize, address: 0, size: 1, shouldFail: false, expectedHeap: program.HEAP_END},
{name: "Increment heap within limit", heap: program.HEAP_END - 2*memory.PageSize, address: 0, size: 1, shouldFail: false, expectedHeap: program.HEAP_END - memory.PageSize},
......@@ -464,10 +465,10 @@ func TestEVMSysWriteHint(t *testing.T) {
state := goVm.GetState()
state.GetRegistersRef()[2] = exec.SysWrite
state.GetRegistersRef()[4] = exec.FdHintWrite
state.GetRegistersRef()[5] = uint32(tt.memOffset)
state.GetRegistersRef()[6] = uint32(tt.bytesToWrite)
state.GetRegistersRef()[5] = arch.Word(tt.memOffset)
state.GetRegistersRef()[6] = arch.Word(tt.bytesToWrite)
err := state.GetMemory().SetMemoryRange(uint32(tt.memOffset), bytes.NewReader(tt.hintData))
err := state.GetMemory().SetMemoryRange(arch.Word(tt.memOffset), bytes.NewReader(tt.hintData))
require.NoError(t, err)
state.GetMemory().SetMemory(state.GetPC(), insn)
step := state.GetStep()
......@@ -477,8 +478,8 @@ func TestEVMSysWriteHint(t *testing.T) {
expected.PC = state.GetCpu().NextPC
expected.NextPC = state.GetCpu().NextPC + 4
expected.LastHint = tt.expectedLastHint
expected.Registers[2] = uint32(tt.bytesToWrite) // Return count of bytes written
expected.Registers[7] = 0 // no Error
expected.Registers[2] = arch.Word(tt.bytesToWrite) // Return count of bytes written
expected.Registers[7] = 0 // no Error
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
......@@ -497,7 +498,7 @@ func TestEVMFault(t *testing.T) {
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
nextPC uint32
nextPC arch.Word
insn uint32
}{
{"illegal instruction", 0, 0xFF_FF_FF_FF},
......
......@@ -14,6 +14,7 @@ import (
"golang.org/x/exp/maps"
"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/multithreaded"
mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil"
......@@ -21,15 +22,17 @@ import (
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
type Word = arch.Word
func TestEVM_MT_LL(t *testing.T) {
var tracer *tracing.Hooks
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},
......@@ -44,7 +47,7 @@ func TestEVM_MT_LL(t *testing.T) {
t.Run(tName, 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, state, contracts := setup(t, i, nil)
step := state.GetStep()
......@@ -53,11 +56,11 @@ func TestEVM_MT_LL(t *testing.T) {
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetMemory(pc, insn)
state.GetMemory().SetMemory(c.effAddr, c.value)
state.GetMemory().SetWord(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
if withExistingReservation {
state.LLReservationActive = true
state.LLAddress = c.effAddr + uint32(4)
state.LLAddress = c.effAddr + Word(4)
state.LLOwnerThread = 123
} else {
state.LLReservationActive = false
......@@ -105,12 +108,12 @@ func TestEVM_MT_SC(t *testing.T) {
cases := []struct {
name string
base uint32
base Word
offset int
value uint32
effAddr uint32
value Word
effAddr Word
rtReg int
threadId uint32
threadId Word
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5, threadId: 4},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5, threadId: 4},
......@@ -125,14 +128,14 @@ func TestEVM_MT_SC(t *testing.T) {
t.Run(tName, 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, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1)
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = c.effAddr
} else {
......@@ -158,10 +161,10 @@ func TestEVM_MT_SC(t *testing.T) {
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
var retVal uint32
var retVal Word
if v.shouldSucceed {
retVal = 1
expected.ExpectMemoryWrite(c.effAddr, c.value)
expected.ExpectMemoryWordWrite(c.effAddr, c.value)
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
......@@ -207,10 +210,10 @@ func TestEVM_MT_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
......@@ -236,14 +239,14 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, 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, state, contracts := setup(t, i, oracle)
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = effAddr
} else {
......@@ -315,16 +318,16 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
pc := uint32(0x04)
rt := uint32(0x12_34_56_78)
pc := Word(0x04)
rt := Word(0x12_34_56_78)
baseReg := 5
rtReg := 6
cases := []struct {
name string
opcode int
offset int
base uint32
effAddr uint32
base Word
effAddr Word
preMem uint32
postMem uint32
}{
......@@ -343,7 +346,7 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = c.effAddr
} else {
......@@ -393,13 +396,13 @@ func TestEVM_SysClone_FlagHandling(t *testing.T) {
cases := []struct {
name string
flags uint32
flags Word
valid bool
}{
{"the supported flags bitmask", exec.ValidCloneFlags, true},
{"no flags", 0, false},
{"all flags", ^uint32(0), false},
{"all unsupported flags", ^uint32(exec.ValidCloneFlags), false},
{"all flags", ^Word(0), false},
{"all unsupported flags", ^Word(exec.ValidCloneFlags), false},
{"a few supported flags", exec.CloneFs | exec.CloneSysvsem, false},
{"one supported flag", exec.CloneFs, false},
{"mixed supported and unsupported flags", exec.CloneFs | exec.CloneParentSettid, false},
......@@ -459,7 +462,7 @@ func TestEVM_SysClone_Successful(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
stackPtr := uint32(100)
stackPtr := Word(100)
goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*333, state, c.traverseRight)
......@@ -470,7 +473,7 @@ func TestEVM_SysClone_Successful(t *testing.T) {
step := state.GetStep()
// Sanity-check assumptions
require.Equal(t, uint32(1), state.NextThreadId)
require.Equal(t, Word(1), state.NextThreadId)
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
......@@ -514,7 +517,7 @@ func TestEVM_SysGetTID(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
threadId uint32
threadId Word
}{
{"zero", 0},
{"non-zero", 11},
......@@ -570,8 +573,8 @@ func TestEVM_SysExit(t *testing.T) {
mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysExit // Set syscall number
state.GetRegistersRef()[4] = uint32(exitCode) // The first argument (exit code)
state.GetRegistersRef()[2] = exec.SysExit // Set syscall number
state.GetRegistersRef()[4] = Word(exitCode) // The first argument (exit code)
step := state.Step
// Set up expectations
......@@ -654,11 +657,11 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
addressParam uint32
effAddr uint32
targetValue uint32
actualValue uint32
timeout uint32
addressParam Word
effAddr Word
targetValue Word
actualValue Word
timeout Word
shouldFail bool
shouldSetTimeout bool
}{
......@@ -678,7 +681,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.Memory.SetMemory(c.effAddr, c.actualValue)
state.Memory.SetWord(c.effAddr, c.actualValue)
state.GetRegistersRef()[2] = exec.SysFutex // Set syscall number
state.GetRegistersRef()[4] = c.addressParam
state.GetRegistersRef()[5] = exec.FutexWaitPrivate
......@@ -721,8 +724,8 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
addressParam uint32
effAddr uint32
addressParam Word
effAddr Word
activeThreadCount int
inactiveThreadCount int
traverseRight bool
......@@ -800,7 +803,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) {
const FUTEX_CMP_REQUEUE_PI = 12
const FUTEX_LOCK_PI2 = 13
unsupportedFutexOps := map[string]uint32{
unsupportedFutexOps := map[string]Word{
"FUTEX_WAIT": FUTEX_WAIT,
"FUTEX_WAKE": FUTEX_WAKE,
"FUTEX_FD": FUTEX_FD,
......@@ -889,7 +892,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) {
mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = syscallNum // Set syscall number
state.GetRegistersRef()[2] = Word(syscallNum) // Set syscall number
step := state.Step
// Set up post-state expectations
......@@ -972,7 +975,7 @@ func TestEVM_SysClockGettimeRealtime(t *testing.T) {
testEVM_SysClockGettime(t, exec.ClockGettimeRealtimeFlag)
}
func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
func testEVM_SysClockGettime(t *testing.T, clkid Word) {
var tracer *tracing.Hooks
llVariations := []struct {
......@@ -996,7 +999,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
cases := []struct {
name string
timespecAddr uint32
timespecAddr Word
}{
{"aligned timespec address", 0x1000},
{"unaligned timespec address", 0x1003},
......@@ -1007,12 +1010,12 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
t.Run(tName, func(t *testing.T) {
goVm, state, contracts := setup(t, 2101, nil)
mttestutil.InitializeSingleThread(2101+i, state, i%2 == 1)
effAddr := c.timespecAddr & 0xFFffFFfc
effAddr := c.timespecAddr & arch.AddressMask
effAddr2 := effAddr + 4
step := state.Step
// Define LL-related params
var llAddress, llOwnerThread uint32
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = effAddr
} else if v.matchEffAddr2 {
......@@ -1039,13 +1042,13 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
next := state.Step + 1
var secs, nsecs uint32
var secs, nsecs Word
if clkid == exec.ClockGettimeMonotonicFlag {
secs = uint32(next / exec.HZ)
nsecs = uint32((next % exec.HZ) * (1_000_000_000 / exec.HZ))
secs = Word(next / exec.HZ)
nsecs = Word((next % exec.HZ) * (1_000_000_000 / exec.HZ))
}
expected.ExpectMemoryWrite(effAddr, secs)
expected.ExpectMemoryWrite(effAddr2, nsecs)
expected.ExpectMemoryWordWrite(effAddr, secs)
expected.ExpectMemoryWordWrite(effAddr2, nsecs)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
......@@ -1069,7 +1072,7 @@ func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 2101, nil)
timespecAddr := uint32(0x1000)
timespecAddr := Word(0x1000)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = 0xDEAD // a0 - invalid clockid
......@@ -1131,7 +1134,7 @@ func TestEVM_NoopSyscall(t *testing.T) {
goVm, state, contracts := setup(t, int(noopVal), nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = noopVal // Set syscall number
state.GetRegistersRef()[2] = Word(noopVal) // Set syscall number
step := state.Step
// Set up post-state expectations
......@@ -1178,7 +1181,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) {
goVm, state, contracts := setup(t, i*3434, nil)
// Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = syscallNum
state.GetRegistersRef()[2] = Word(syscallNum)
// Set up post-state expectations
require.Panics(t, func() { _, _ = goVm.Step(true) })
......@@ -1194,9 +1197,9 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
step uint64
activeStackSize int
otherStackSize int
futexAddr uint32
targetValue uint32
actualValue uint32
futexAddr Word
targetValue Word
actualValue Word
timeoutStep uint64
shouldWakeup bool
shouldTimeout bool
......@@ -1225,7 +1228,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
if !c.shouldWakeup && c.shouldTimeout {
require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup")
}
effAddr := c.futexAddr & 0xFF_FF_FF_Fc
effAddr := c.futexAddr & arch.AddressMask
goVm, state, contracts := setup(t, i, nil)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step
......@@ -1234,7 +1237,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetValue
activeThread.FutexTimeoutStep = c.timeoutStep
state.GetMemory().SetMemory(effAddr, c.actualValue)
state.GetMemory().SetWord(effAddr, c.actualValue)
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
......@@ -1328,14 +1331,14 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
}
func TestEVM_WakeupTraversalStep(t *testing.T) {
addr := uint32(0x1234)
wakeupVal := uint32(0x999)
addr := Word(0x1234)
wakeupVal := Word(0x999)
var tracer *tracing.Hooks
cases := []struct {
name string
wakeupAddr uint32
futexAddr uint32
targetVal uint32
wakeupAddr Word
futexAddr Word
targetVal Word
traverseRight bool
activeStackSize int
otherStackSize int
......@@ -1373,7 +1376,7 @@ func TestEVM_WakeupTraversalStep(t *testing.T) {
step := state.Step
state.Wakeup = c.wakeupAddr
state.GetMemory().SetMemory(c.wakeupAddr&0xFF_FF_FF_FC, wakeupVal)
state.GetMemory().SetWord(c.wakeupAddr&arch.AddressMask, wakeupVal)
activeThread := state.GetCurrentThread()
activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetVal
......
......@@ -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")
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:
return &VersionedState{
Version: VersionMultiThreaded,
FPVMState: state,
}, nil
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