From 7da92b65a7d2a2c0823b3532e4055c721e264df7 Mon Sep 17 00:00:00 2001 From: Inphi Date: Tue, 1 Oct 2024 14:37:18 -0400 Subject: [PATCH] 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 --- .circleci/config.yml | 34 +- cannon/Makefile | 24 +- cannon/cmd/load_elf.go | 4 +- cannon/cmd/run.go | 20 +- cannon/main.go | 2 +- cannon/mipsevm/arch/arch32.go | 48 +++ cannon/mipsevm/arch/arch64.go | 48 +++ cannon/mipsevm/arch/byteorder.go | 7 + cannon/mipsevm/exec/memory.go | 12 +- cannon/mipsevm/exec/mips_instructions.go | 335 +++++++++++++----- cannon/mipsevm/exec/mips_syscalls.go | 85 +++-- cannon/mipsevm/exec/preimage.go | 16 +- cannon/mipsevm/exec/stack.go | 10 +- cannon/mipsevm/iface.go | 21 +- cannon/mipsevm/memory/memory.go | 140 +++++--- cannon/mipsevm/memory/memory_test.go | 4 +- cannon/mipsevm/memory/page.go | 2 +- cannon/mipsevm/memory/page_test.go | 6 +- cannon/mipsevm/multithreaded/instrumented.go | 7 +- .../multithreaded/instrumented_test.go | 1 - cannon/mipsevm/multithreaded/mips.go | 68 ++-- cannon/mipsevm/multithreaded/stack.go | 12 +- cannon/mipsevm/multithreaded/state.go | 76 ++-- cannon/mipsevm/multithreaded/state_test.go | 49 ++- .../multithreaded/testutil/expectations.go | 48 +-- .../testutil/expectations_test.go | 5 +- .../multithreaded/testutil/mutators.go | 21 +- .../mipsevm/multithreaded/testutil/thread.go | 11 +- cannon/mipsevm/multithreaded/thread.go | 59 +-- cannon/mipsevm/program/load.go | 16 +- cannon/mipsevm/program/metadata.go | 12 +- cannon/mipsevm/program/patch.go | 67 ++-- cannon/mipsevm/singlethreaded/instrumented.go | 8 +- cannon/mipsevm/singlethreaded/mips.go | 26 +- cannon/mipsevm/singlethreaded/state.go | 64 ++-- cannon/mipsevm/singlethreaded/state_test.go | 3 +- .../mipsevm/singlethreaded/testutil/state.go | 21 +- cannon/mipsevm/state.go | 10 +- cannon/mipsevm/tests/evm_common_test.go | 81 ++--- .../mipsevm/tests/evm_multithreaded_test.go | 135 +++---- .../mipsevm/tests/evm_singlethreaded_test.go | 31 +- cannon/mipsevm/tests/fuzz_evm_common_test.go | 39 +- .../tests/fuzz_evm_multithreaded_test.go | 4 +- cannon/mipsevm/testutil/mips.go | 7 +- cannon/mipsevm/testutil/rand.go | 19 +- cannon/mipsevm/testutil/state.go | 47 +-- cannon/mipsevm/testutil/vmtests.go | 7 +- cannon/mipsevm/versions/detect.go | 2 +- cannon/mipsevm/versions/detect_test.go | 3 + cannon/mipsevm/versions/state.go | 50 ++- cannon/mipsevm/versions/state_test.go | 4 + cannon/mipsevm/witness.go | 7 +- cannon/multicannon/exec.go | 5 +- cannon/multicannon/run.go | 1 - go.mod | 1 + go.sum | 2 + 56 files changed, 1181 insertions(+), 666 deletions(-) create mode 100644 cannon/mipsevm/arch/arch32.go create mode 100644 cannon/mipsevm/arch/arch64.go create mode 100644 cannon/mipsevm/arch/byteorder.go diff --git a/.circleci/config.yml b/.circleci/config.yml index c4da7cb22ebb5..736d615bc058b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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=<> - 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: <> + steps: + - run: + name: Cannon Go 32-bit tests + command: | + export 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: <> + steps: + - run: + name: Cannon Go 64-bit tests + command: | + export 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 diff --git a/cannon/Makefile b/cannon/Makefile index 408700613d3d9..5376b1b62086d 100644 --- a/cannon/Makefile +++ b/cannon/Makefile @@ -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 \ diff --git a/cannon/cmd/load_elf.go b/cannon/cmd/load_elf.go index c76626941e9bc..7609a3b7091dd 100644 --- a/cannon/cmd/load_elf.go +++ b/cannon/cmd/load_elf.go @@ -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) } diff --git a/cannon/cmd/run.go b/cannon/cmd/run.go index 6d536eeeceac4..b9854082c5a98 100644 --- a/cannon/cmd/run.go +++ b/cannon/cmd/run.go @@ -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 diff --git a/cannon/main.go b/cannon/main.go index 176ce315708f0..015aea6317f35 100644 --- a/cannon/main.go +++ b/cannon/main.go @@ -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{ diff --git a/cannon/mipsevm/arch/arch32.go b/cannon/mipsevm/arch/arch32.go new file mode 100644 index 0000000000000..98a22c8382bc1 --- /dev/null +++ b/cannon/mipsevm/arch/arch32.go @@ -0,0 +1,48 @@ +//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) +} diff --git a/cannon/mipsevm/arch/arch64.go b/cannon/mipsevm/arch/arch64.go new file mode 100644 index 0000000000000..e01b44c50bab4 --- /dev/null +++ b/cannon/mipsevm/arch/arch64.go @@ -0,0 +1,48 @@ +//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) +} diff --git a/cannon/mipsevm/arch/byteorder.go b/cannon/mipsevm/arch/byteorder.go new file mode 100644 index 0000000000000..a633d6858864f --- /dev/null +++ b/cannon/mipsevm/arch/byteorder.go @@ -0,0 +1,7 @@ +package arch + +type ByteOrder interface { + Word([]byte) Word + AppendWord([]byte, Word) []byte + PutWord([]byte, Word) +} diff --git a/cannon/mipsevm/exec/memory.go b/cannon/mipsevm/exec/memory.go index 3dea28dce29bd..2a0afcbdea3f1 100644 --- a/cannon/mipsevm/exec/memory.go +++ b/cannon/mipsevm/exec/memory.go @@ -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 { diff --git a/cannon/mipsevm/exec/mips_instructions.go b/cannon/mipsevm/exec/mips_instructions.go index aec14192df93d..326a3d4f504f9 100644 --- a/cannon/mipsevm/exec/mips_instructions.go +++ b/cannon/mipsevm/exec/mips_instructions.go @@ -1,16 +1,24 @@ 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<> ((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") } diff --git a/cannon/mipsevm/exec/mips_syscalls.go b/cannon/mipsevm/exec/mips_syscalls.go index 57df29d760ac2..abb186266b71a 100644 --- a/cannon/mipsevm/exec/mips_syscalls.go +++ b/cannon/mipsevm/exec/mips_syscalls.go @@ -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 diff --git a/cannon/mipsevm/exec/preimage.go b/cannon/mipsevm/exec/preimage.go index 15c1f98e95304..17f6d4e0f6f20 100644 --- a/cannon/mipsevm/exec/preimage.go +++ b/cannon/mipsevm/exec/preimage.go @@ -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 } diff --git a/cannon/mipsevm/exec/stack.go b/cannon/mipsevm/exec/stack.go index 06e919c0352fa..5f96afe0416b0 100644 --- a/cannon/mipsevm/exec/stack.go +++ b/cannon/mipsevm/exec/stack.go @@ -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) } diff --git a/cannon/mipsevm/iface.go b/cannon/mipsevm/iface.go index 8e8d758e90488..1b3b4efaf84a8 100644 --- a/cannon/mipsevm/iface.go +++ b/cannon/mipsevm/iface.go @@ -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 } diff --git a/cannon/mipsevm/memory/memory.go b/cannon/mipsevm/memory/memory.go index ea5c279763b32..596e202940652 100644 --- a/cannon/mipsevm/memory/memory.go +++ b/cannon/mipsevm/memory/memory.go @@ -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} } diff --git a/cannon/mipsevm/memory/memory_test.go b/cannon/mipsevm/memory/memory_test.go index 5f3f9301e5525..fac076c90e19f 100644 --- a/cannon/mipsevm/memory/memory_test.go +++ b/cannon/mipsevm/memory/memory_test.go @@ -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") diff --git a/cannon/mipsevm/memory/page.go b/cannon/mipsevm/memory/page.go index d3c3096b418ec..defcc10b603cd 100644 --- a/cannon/mipsevm/memory/page.go +++ b/cannon/mipsevm/memory/page.go @@ -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") } diff --git a/cannon/mipsevm/memory/page_test.go b/cannon/mipsevm/memory/page_test.go index c2960421b6700..e7a8167a9df49 100644 --- a/cannon/mipsevm/memory/page_test.go +++ b/cannon/mipsevm/memory/page_test.go @@ -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") diff --git a/cannon/mipsevm/multithreaded/instrumented.go b/cannon/mipsevm/multithreaded/instrumented.go index ac76d6cdb532e..db61fd1207e87 100644 --- a/cannon/mipsevm/multithreaded/instrumented.go +++ b/cannon/mipsevm/multithreaded/instrumented.go @@ -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 "" } diff --git a/cannon/mipsevm/multithreaded/instrumented_test.go b/cannon/mipsevm/multithreaded/instrumented_test.go index 20ce2b9cc0b02..f0b005257f7c2 100644 --- a/cannon/mipsevm/multithreaded/instrumented_test.go +++ b/cannon/mipsevm/multithreaded/instrumented_test.go @@ -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") } diff --git a/cannon/mipsevm/multithreaded/mips.go b/cannon/mipsevm/multithreaded/mips.go index b06ad3917724d..43abdbf57157e 100644 --- a/cannon/mipsevm/multithreaded/mips.go +++ b/cannon/mipsevm/multithreaded/mips.go @@ -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 diff --git a/cannon/mipsevm/multithreaded/stack.go b/cannon/mipsevm/multithreaded/stack.go index 4fc32c221ee87..099dc7351323a 100644 --- a/cannon/mipsevm/multithreaded/stack.go +++ b/cannon/mipsevm/multithreaded/stack.go @@ -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) } diff --git a/cannon/mipsevm/multithreaded/state.go b/cannon/mipsevm/multithreaded/state.go index 7b4d545396a94..f88b5fb0186c4 100644 --- a/cannon/mipsevm/multithreaded/state.go +++ b/cannon/mipsevm/multithreaded/state.go @@ -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] diff --git a/cannon/mipsevm/multithreaded/state_test.go b/cannon/mipsevm/multithreaded/state_test.go index 6d776632bf0f0..0beddb75b026e 100644 --- a/cannon/mipsevm/multithreaded/state_test.go +++ b/cannon/mipsevm/multithreaded/state_test.go @@ -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) +} diff --git a/cannon/mipsevm/multithreaded/testutil/expectations.go b/cannon/mipsevm/multithreaded/testutil/expectations.go index 559ed2de8c4fd..05dfdb4474a6c 100644 --- a/cannon/mipsevm/multithreaded/testutil/expectations.go +++ b/cannon/mipsevm/multithreaded/testutil/expectations.go @@ -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] } diff --git a/cannon/mipsevm/multithreaded/testutil/expectations_test.go b/cannon/mipsevm/multithreaded/testutil/expectations_test.go index a40e15e0f8d57..a17534fd5eeab 100644 --- a/cannon/mipsevm/multithreaded/testutil/expectations_test.go +++ b/cannon/mipsevm/multithreaded/testutil/expectations_test.go @@ -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 diff --git a/cannon/mipsevm/multithreaded/testutil/mutators.go b/cannon/mipsevm/multithreaded/testutil/mutators.go index a44ba23a4fac2..62e22c237c8db 100644 --- a/cannon/mipsevm/multithreaded/testutil/mutators.go +++ b/cannon/mipsevm/multithreaded/testutil/mutators.go @@ -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 } diff --git a/cannon/mipsevm/multithreaded/testutil/thread.go b/cannon/mipsevm/multithreaded/testutil/thread.go index f5b1d29a8dd60..6cbd3752c613d 100644 --- a/cannon/mipsevm/multithreaded/testutil/thread.go +++ b/cannon/mipsevm/multithreaded/testutil/thread.go @@ -1,6 +1,7 @@ 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 diff --git a/cannon/mipsevm/multithreaded/thread.go b/cannon/mipsevm/multithreaded/thread.go index f811a52be4676..fbb49856e3998 100644 --- a/cannon/mipsevm/multithreaded/thread.go +++ b/cannon/mipsevm/multithreaded/thread.go @@ -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 diff --git a/cannon/mipsevm/program/load.go b/cannon/mipsevm/program/load.go index 5ff0b4098bc8b..3cbba07d2bcda 100644 --- a/cannon/mipsevm/program/load.go +++ b/cannon/mipsevm/program/load.go @@ -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) } } diff --git a/cannon/mipsevm/program/metadata.go b/cannon/mipsevm/program/metadata.go index fb34da7694c78..ab1aea0842d00 100644 --- a/cannon/mipsevm/program/metadata.go +++ b/cannon/mipsevm/program/metadata.go @@ -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 } } diff --git a/cannon/mipsevm/program/patch.go b/cannon/mipsevm/program/patch.go index e8e2e3ebc0855..603bb41086ac1 100644 --- a/cannon/mipsevm/program/patch.go +++ b/cannon/mipsevm/program/patch.go @@ -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)...) +} diff --git a/cannon/mipsevm/singlethreaded/instrumented.go b/cannon/mipsevm/singlethreaded/instrumented.go index 800cc1a92f7a7..7757ae390d6f2 100644 --- a/cannon/mipsevm/singlethreaded/instrumented.go +++ b/cannon/mipsevm/singlethreaded/instrumented.go @@ -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 "" } diff --git a/cannon/mipsevm/singlethreaded/mips.go b/cannon/mipsevm/singlethreaded/mips.go index a88d0c66b0e61..afef3fb415868 100644 --- a/cannon/mipsevm/singlethreaded/mips.go +++ b/cannon/mipsevm/singlethreaded/mips.go @@ -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)) diff --git a/cannon/mipsevm/singlethreaded/state.go b/cannon/mipsevm/singlethreaded/state.go index b7320131fb975..741f7f66bb097 100644 --- a/cannon/mipsevm/singlethreaded/state.go +++ b/cannon/mipsevm/singlethreaded/state.go @@ -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) diff --git a/cannon/mipsevm/singlethreaded/state_test.go b/cannon/mipsevm/singlethreaded/state_test.go index c3dfd5cd41af6..e0639c3dbeece 100644 --- a/cannon/mipsevm/singlethreaded/state_test.go +++ b/cannon/mipsevm/singlethreaded/state_test.go @@ -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, diff --git a/cannon/mipsevm/singlethreaded/testutil/state.go b/cannon/mipsevm/singlethreaded/testutil/state.go index 079827500e45a..b7203ead20b12 100644 --- a/cannon/mipsevm/singlethreaded/testutil/state.go +++ b/cannon/mipsevm/singlethreaded/testutil/state.go @@ -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 } diff --git a/cannon/mipsevm/state.go b/cannon/mipsevm/state.go index 8ed6f265c8941..731562f4fae2e 100644 --- a/cannon/mipsevm/state.go +++ b/cannon/mipsevm/state.go @@ -1,10 +1,12 @@ 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 ( diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index ad93014450dd1..b0068cb993ba9 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -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}, diff --git a/cannon/mipsevm/tests/evm_multithreaded_test.go b/cannon/mipsevm/tests/evm_multithreaded_test.go index a26ebe96eb372..d0da7910fa0ac 100644 --- a/cannon/mipsevm/tests/evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/evm_multithreaded_test.go @@ -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 diff --git a/cannon/mipsevm/tests/evm_singlethreaded_test.go b/cannon/mipsevm/tests/evm_singlethreaded_test.go index 32cad32cc00e2..73613d3590f49 100644 --- a/cannon/mipsevm/tests/evm_singlethreaded_test.go +++ b/cannon/mipsevm/tests/evm_singlethreaded_test.go @@ -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)) diff --git a/cannon/mipsevm/tests/fuzz_evm_common_test.go b/cannon/mipsevm/tests/fuzz_evm_common_test.go index 15b29a2b9e50b..712b7d4875d30 100644 --- a/cannon/mipsevm/tests/fuzz_evm_common_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_common_test.go @@ -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 diff --git a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go index 828f9c5587399..c646589344212 100644 --- a/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go +++ b/cannon/mipsevm/tests/fuzz_evm_multithreaded_test.go @@ -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) } } diff --git a/cannon/mipsevm/testutil/mips.go b/cannon/mipsevm/testutil/mips.go index 33ada41869d76..50d0ac48a6083 100644 --- a/cannon/mipsevm/testutil/mips.go +++ b/cannon/mipsevm/testutil/mips.go @@ -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) diff --git a/cannon/mipsevm/testutil/rand.go b/cannon/mipsevm/testutil/rand.go index 96ff0eb6318b9..da0b6d113b8b1 100644 --- a/cannon/mipsevm/testutil/rand.go +++ b/cannon/mipsevm/testutil/rand.go @@ -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 { diff --git a/cannon/mipsevm/testutil/state.go b/cannon/mipsevm/testutil/state.go index 86d5cfb2b6adf..4513d1424692c 100644 --- a/cannon/mipsevm/testutil/state.go +++ b/cannon/mipsevm/testutil/state.go @@ -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() } diff --git a/cannon/mipsevm/testutil/vmtests.go b/cannon/mipsevm/testutil/vmtests.go index 0c5c325a8b553..559db317cef0f 100644 --- a/cannon/mipsevm/testutil/vmtests.go +++ b/cannon/mipsevm/testutil/vmtests.go @@ -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") diff --git a/cannon/mipsevm/versions/detect.go b/cannon/mipsevm/versions/detect.go index cb1efcc06eb35..1f1f4147d6952 100644 --- a/cannon/mipsevm/versions/detect.go +++ b/cannon/mipsevm/versions/detect.go @@ -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) diff --git a/cannon/mipsevm/versions/detect_test.go b/cannon/mipsevm/versions/detect_test.go index 993fb4adcb819..be849269fff9b 100644 --- a/cannon/mipsevm/versions/detect_test.go +++ b/cannon/mipsevm/versions/detect_test.go @@ -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") }) diff --git a/cannon/mipsevm/versions/state.go b/cannon/mipsevm/versions/state.go index 97fceadd43e45..c33c5d4d756c3 100644 --- a/cannon/mipsevm/versions/state.go +++ b/cannon/mipsevm/versions/state.go @@ -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") } diff --git a/cannon/mipsevm/versions/state_test.go b/cannon/mipsevm/versions/state_test.go index 8740d51d2929c..27892c7c05521 100644 --- a/cannon/mipsevm/versions/state_test.go +++ b/cannon/mipsevm/versions/state_test.go @@ -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 diff --git a/cannon/mipsevm/witness.go b/cannon/mipsevm/witness.go index b7bf38fa528e2..6807bc91c2f6d 100644 --- a/cannon/mipsevm/witness.go +++ b/cannon/mipsevm/witness.go @@ -1,6 +1,9 @@ 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 { diff --git a/cannon/multicannon/exec.go b/cannon/multicannon/exec.go index cc06cad9a685b..982b83c556925 100644 --- a/cannon/multicannon/exec.go +++ b/cannon/multicannon/exec.go @@ -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") } diff --git a/cannon/multicannon/run.go b/cannon/multicannon/run.go index 7139436899ab3..fabd4d71df387 100644 --- a/cannon/multicannon/run.go +++ b/cannon/multicannon/run.go @@ -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 diff --git a/go.mod b/go.mod index 92a75cf442fa4..37c8a09cf77ff 100644 --- a/go.mod +++ b/go.mod @@ -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 ( diff --git a/go.sum b/go.sum index 5cd91613853fd..6c07be129d857 100644 --- a/go.sum +++ b/go.sum @@ -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=