Commit 4af1fe6e authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Implement thread-safe ll and sc operations (#11906)

* cannon: Extract RMW op handling from shared logic

* cannon: Add more test cases of LL/SC behavior

* cannon: Cut ll/sc-related logic from common mips code

* cannon: Setup mips helpers to return memory modification info

* cannon: Add new fields to track reserved memory

* cannon: Add a boolean field indicating whether an ll reservation is active

* cannon: Implement ll/sc for MTCannon

* cannon: Add modified sysRead tests for MTCannon

* cannon: Test store operations cleare memory reservations

* cannon: Update SysClockGettime to clear ll memory reservation

* cannon: Fix slither warning - initialize variable

* cannon: Bump MIP2.sol version

* cannon: Run semver, snapshot tasks

* cannon: Cut unused log msg

* cannon: Add new error to MIPS interfaces

* cannon: Cut stale TODO
parent ac443ef0
......@@ -5,6 +5,11 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
const (
OpLoadLinked = 0x30
OpStoreConditional = 0x38
)
func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits
......@@ -13,7 +18,7 @@ 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) error {
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) {
// j-type j/jal
if opcode == 2 || opcode == 3 {
linkReg := uint32(0)
......@@ -23,7 +28,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
// 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)
stackTracker.PushStack(cpu.PC, target)
return HandleJump(cpu, registers, linkReg, target)
err = HandleJump(cpu, registers, linkReg, target)
return
}
// register fetch
......@@ -57,7 +63,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
}
if (opcode >= 4 && opcode < 8) || opcode == 1 {
return HandleBranch(cpu, registers, opcode, insn, rtReg, rs)
err = HandleBranch(cpu, registers, opcode, insn, rtReg, rs)
return
}
storeAddr := uint32(0xFF_FF_FF_FF)
......@@ -70,7 +77,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
addr := rs & 0xFFFFFFFC
memTracker.TrackMemAccess(addr)
mem = memory.GetMemory(addr)
if opcode >= 0x28 && opcode != 0x30 {
if opcode >= 0x28 {
// store
storeAddr = addr
// store opcodes don't write back to a register
......@@ -90,36 +97,42 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
} else {
stackTracker.PopStack()
}
return HandleJump(cpu, registers, linkReg, rs)
err = HandleJump(cpu, registers, linkReg, rs)
return
}
if fun == 0xa { // movz
return HandleRd(cpu, registers, rdReg, rs, rt == 0)
err = HandleRd(cpu, registers, rdReg, rs, rt == 0)
return
}
if fun == 0xb { // movn
return HandleRd(cpu, registers, rdReg, rs, rt != 0)
err = HandleRd(cpu, registers, rdReg, rs, rt != 0)
return
}
// lo and hi registers
// can write back
if fun >= 0x10 && fun < 0x1c {
return HandleHiLo(cpu, registers, fun, rs, rt, rdReg)
err = HandleHiLo(cpu, registers, fun, rs, rt, rdReg)
return
}
}
// store conditional, write a 1 to rt
if opcode == 0x38 && rtReg != 0 {
registers[rtReg] = 1
}
// write memory
if storeAddr != 0xFF_FF_FF_FF {
memTracker.TrackMemAccess(storeAddr)
memory.SetMemory(storeAddr, val)
memUpdated = true
memAddr = storeAddr
}
// write back the value to destination register
return HandleRd(cpu, registers, rdReg, val, true)
err = HandleRd(cpu, registers, rdReg, val, true)
return
}
func SignExtendImmediate(insn uint32) uint32 {
return SignExtend(insn&0xFFFF, 16)
}
func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
......@@ -272,10 +285,6 @@ func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
val := rt << (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8)
return (mem & ^mask) | val
case 0x30: // ll
return mem
case 0x38: // sc
return rt
default:
panic("invalid instruction")
}
......@@ -382,6 +391,7 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, v
panic("invalid register")
}
if storeReg != 0 && conditional {
// Register 0 is a special register that always holds a value of 0
registers[storeReg] = val
}
cpu.PC = cpu.NextPC
......
......@@ -187,7 +187,7 @@ 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) {
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) {
// args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code
v0 = uint32(0)
......@@ -215,6 +215,8 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
binary.BigEndian.PutUint32(outMem[:], mem)
copy(outMem[alignment:], dat[:datLen])
memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:]))
memUpdated = true
memAddr = effAddr
newPreimageOffset += datLen
v0 = datLen
//fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem)
......@@ -226,7 +228,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
v1 = MipsEBADF
}
return v0, v1, newPreimageOffset
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) {
......
......@@ -75,8 +75,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
case exec.SysRead:
var newPreimageOffset uint32
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
var memUpdated bool
var memAddr uint32
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 {
m.handleMemoryUpdate(memAddr)
}
case exec.SysWrite:
var newLastHint hexutil.Bytes
var newPreimageKey common.Hash
......@@ -158,8 +163,10 @@ func (m *InstrumentedState) handleSyscall() error {
effAddr := a1 & 0xFFffFFfc
m.memoryTracker.TrackMemAccess(effAddr)
m.state.Memory.SetMemory(effAddr, secs)
m.handleMemoryUpdate(effAddr)
m.memoryTracker.TrackMemAccess2(effAddr + 4)
m.state.Memory.SetMemory(effAddr+4, nsecs)
m.handleMemoryUpdate(effAddr + 4)
default:
v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL
......@@ -286,8 +293,71 @@ func (m *InstrumentedState) mipsStep() error {
return m.handleSyscall()
}
// Handle RMW (read-modify-write) ops
if opcode == exec.OpLoadLinked || opcode == exec.OpStoreConditional {
return m.handleRMWOps(insn, opcode)
}
// Exec the rest of the step logic
return exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
memUpdated, memAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
if err != nil {
return err
}
if memUpdated {
m.handleMemoryUpdate(memAddr)
}
return nil
}
func (m *InstrumentedState) handleMemoryUpdate(memAddr uint32) {
if memAddr == m.state.LLAddress {
// Reserved address was modified, clear the reservation
m.clearLLMemoryReservation()
}
}
func (m *InstrumentedState) clearLLMemoryReservation() {
m.state.LLReservationActive = false
m.state.LLAddress = 0
m.state.LLOwnerThread = 0
}
// handleRMWOps handles LL and SC operations which provide the primitives to implement read-modify-write operations
func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
baseReg := (insn >> 21) & 0x1F
base := m.state.GetRegistersRef()[baseReg]
rtReg := (insn >> 16) & 0x1F
offset := exec.SignExtendImmediate(insn)
effAddr := (base + offset) & 0xFFFFFFFC
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
var retVal uint32
threadId := m.state.GetCurrentThread().ThreadId
if opcode == exec.OpLoadLinked {
retVal = mem
m.state.LLReservationActive = true
m.state.LLAddress = effAddr
m.state.LLOwnerThread = threadId
} else if opcode == exec.OpStoreConditional {
// 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)
retVal = 1
} else {
// Atomic update failed, return 0 for failure
retVal = 0
}
} else {
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
}
return exec.HandleRd(m.state.getCpuRef(), m.state.GetRegistersRef(), rtReg, retVal, true)
}
func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) {
......
......@@ -5,7 +5,6 @@ import (
"fmt"
"io"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
......@@ -14,16 +13,20 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"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 = 163
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
EXITCODE_WITNESS_OFFSET = HEAP_WITNESS_OFFSET + 4
LL_RESERVATION_ACTIVE_OFFSET = HEAP_WITNESS_OFFSET + 4
LL_ADDRESS_OFFSET = LL_RESERVATION_ACTIVE_OFFSET + 1
LL_OWNER_THREAD_OFFSET = LL_ADDRESS_OFFSET + 4
EXITCODE_WITNESS_OFFSET = LL_OWNER_THREAD_OFFSET + 4
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
......@@ -40,7 +43,10 @@ type State struct {
PreimageKey common.Hash
PreimageOffset uint32 // note that the offset includes the 8-byte length prefix
Heap uint32 // to handle mmap growth
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
ExitCode uint8
Exited bool
......@@ -64,16 +70,19 @@ func CreateEmptyState() *State {
initThread := CreateEmptyThread()
return &State{
Memory: memory.NewMemory(),
Heap: 0,
ExitCode: 0,
Exited: false,
Step: 0,
Wakeup: exec.FutexEmptyAddr,
TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{},
NextThreadId: initThread.ThreadId + 1,
Memory: memory.NewMemory(),
Heap: 0,
LLReservationActive: false,
LLAddress: 0,
LLOwnerThread: 0,
ExitCode: 0,
Exited: false,
Step: 0,
Wakeup: exec.FutexEmptyAddr,
TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{},
NextThreadId: initThread.ThreadId + 1,
}
}
......@@ -187,6 +196,9 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Heap)
out = mipsevm.AppendBoolToWitness(out, s.LLReservationActive)
out = binary.BigEndian.AppendUint32(out, s.LLAddress)
out = binary.BigEndian.AppendUint32(out, s.LLOwnerThread)
out = append(out, s.ExitCode)
out = mipsevm.AppendBoolToWitness(out, s.Exited)
......@@ -264,6 +276,15 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.Heap); err != nil {
return err
}
if err := bout.WriteBool(s.LLReservationActive); err != nil {
return err
}
if err := bout.WriteUInt(s.LLAddress); err != nil {
return err
}
if err := bout.WriteUInt(s.LLOwnerThread); err != nil {
return err
}
if err := bout.WriteUInt(s.ExitCode); err != nil {
return err
}
......@@ -324,6 +345,15 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.Heap); err != nil {
return err
}
if err := bin.ReadBool(&s.LLReservationActive); err != nil {
return err
}
if err := bin.ReadUInt(&s.LLAddress); err != nil {
return err
}
if err := bin.ReadUInt(&s.LLOwnerThread); err != nil {
return err
}
if err := bin.ReadUInt(&s.ExitCode); err != nil {
return err
}
......
......@@ -6,7 +6,6 @@ import (
"encoding/json"
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
......@@ -15,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
......@@ -42,6 +42,8 @@ func TestState_EncodeWitness(t *testing.T) {
}
heap := uint32(12)
llAddress := uint32(55)
llThreadOwner := uint32(99)
preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4})
preimageOffset := uint32(24)
step := uint64(33)
......@@ -53,6 +55,9 @@ func TestState_EncodeWitness(t *testing.T) {
state.PreimageKey = preimageKey
state.PreimageOffset = preimageOffset
state.Heap = heap
state.LLReservationActive = true
state.LLAddress = llAddress
state.LLOwnerThread = llThreadOwner
state.Step = step
state.StepsSinceLastContextSwitch = stepsSinceContextSwitch
......@@ -66,6 +71,9 @@ func TestState_EncodeWitness(t *testing.T) {
setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)})
setWitnessField(expectedWitness, HEAP_WITNESS_OFFSET, []byte{0, 0, 0, byte(heap)})
setWitnessField(expectedWitness, LL_RESERVATION_ACTIVE_OFFSET, []byte{1})
setWitnessField(expectedWitness, LL_ADDRESS_OFFSET, []byte{0, 0, 0, byte(llAddress)})
setWitnessField(expectedWitness, LL_OWNER_THREAD_OFFSET, []byte{0, 0, 0, byte(llThreadOwner)})
setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode})
if c.exited {
setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1})
......@@ -176,6 +184,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
PreimageKey: common.Hash{0xFF},
PreimageOffset: 5,
Heap: 0xc0ffee,
LLReservationActive: true,
LLAddress: 0x12345678,
LLOwnerThread: 0x02,
ExitCode: 1,
Exited: true,
Step: 0xdeadbeef,
......
......@@ -14,15 +14,18 @@ import (
// ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated
// 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
ExitCode uint8
Exited bool
Step uint64
LastHint hexutil.Bytes
MemoryRoot common.Hash
expectedMemory *memory.Memory
PreimageKey common.Hash
PreimageOffset uint32
Heap uint32
LLReservationActive bool
LLAddress uint32
LLOwnerThread uint32
ExitCode uint8
Exited bool
Step uint64
LastHint hexutil.Bytes
MemoryRoot common.Hash
expectedMemory *memory.Memory
// Threading-related expectations
StepsSinceLastContextSwitch uint64
Wakeup uint32
......@@ -62,14 +65,17 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
return &ExpectedMTState{
// General Fields
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(),
ExitCode: fromState.GetExitCode(),
Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(),
LLReservationActive: fromState.LLReservationActive,
LLAddress: fromState.LLAddress,
LLOwnerThread: fromState.LLOwnerThread,
ExitCode: fromState.GetExitCode(),
Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup,
......@@ -119,7 +125,7 @@ func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) {
func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) {
e.expectedMemory.SetMemory(addr, val)
e.expectedMemory.SetMemory(addr2, val)
e.expectedMemory.SetMemory(addr2, val2)
e.MemoryRoot = e.expectedMemory.MerkleRoot()
}
......@@ -168,6 +174,9 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade
require.Equalf(t, e.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey)
require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset)
require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap)
require.Equalf(t, e.LLReservationActive, actualState.LLReservationActive, "Expect LLReservationActive = %v", e.LLReservationActive)
require.Equalf(t, e.LLAddress, actualState.LLAddress, "Expect LLAddress = 0x%x", e.LLAddress)
require.Equalf(t, e.LLOwnerThread, actualState.LLOwnerThread, "Expect LLOwnerThread = %v", e.LLOwnerThread)
require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode)
require.Equalf(t, e.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited)
require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step)
......
......@@ -28,6 +28,9 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }},
{name: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }},
{name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 1 }},
{name: "LLReservationActive", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLReservationActive = !e.LLReservationActive }},
{name: "LLAddress", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLAddress += 1 }},
{name: "LLOwnerThread", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLOwnerThread += 1 }},
{name: "ExitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ExitCode += 1 }},
{name: "Exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Exited = !e.Exited }},
{name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }},
......
package testutil
import (
"math"
"math/rand"
"github.com/ethereum/go-ethereum/common"
......@@ -28,11 +29,19 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
m.state.PreimageKey = testutil.RandHash(r)
m.state.PreimageOffset = r.Uint32()
m.state.Heap = r.Uint32()
m.state.Step = step
m.state.LastHint = testutil.RandHint(r)
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.LLReservationActive = r.Intn(2) == 1
if m.state.LLReservationActive {
m.state.LLAddress = uint32(r.Intn(halfMemory))
m.state.LLOwnerThread = uint32(r.Intn(10))
}
// Randomize threads
activeStackThreads := r.Intn(2) + 1
inactiveStackThreads := r.Intn(3)
......
package singlethreaded
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -30,7 +32,7 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
case exec.SysRead:
var newPreimageOffset uint32
v0, v1, newPreimageOffset = exec.HandleSysRead(a0, a1, a2, m.state.PreimageKey, m.state.PreimageOffset, m.preimageOracle, m.state.Memory, m.memoryTracker)
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
......@@ -62,6 +64,37 @@ func (m *InstrumentedState) mipsStep() error {
return m.handleSyscall()
}
// Handle RMW (read-modify-write) ops
if opcode == exec.OpLoadLinked || opcode == exec.OpStoreConditional {
return m.handleRMWOps(insn, opcode)
}
// Exec the rest of the step logic
return exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
_, _, err := exec.ExecMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker)
return err
}
// handleRMWOps handles LL and SC operations which provide the primitives to implement read-modify-write operations
func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
baseReg := (insn >> 21) & 0x1F
base := m.state.Registers[baseReg]
rtReg := (insn >> 16) & 0x1F
offset := exec.SignExtendImmediate(insn)
effAddr := (base + offset) & 0xFFFFFFFC
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetMemory(effAddr)
var retVal uint32
if opcode == exec.OpLoadLinked {
retVal = mem
} else if opcode == exec.OpStoreConditional {
rt := m.state.Registers[rtReg]
m.state.Memory.SetMemory(effAddr, rt)
retVal = 1 // 1 for success
} else {
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
}
return exec.HandleRd(&m.state.Cpu, &m.state.Registers, rtReg, retVal, true)
}
......@@ -2,7 +2,6 @@ package tests
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"os"
......@@ -13,14 +12,12 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
func TestEVM(t *testing.T) {
......@@ -230,85 +227,6 @@ func TestEVM_MMap(t *testing.T) {
}
}
func TestEVM_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
prestateMem uint32
postateMem uint32
shouldPanic bool
}{
{name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF},
{name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF},
{name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF},
{name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_FF_FF},
{name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_34_56_FF},
{name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_12_FF},
{name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_98_76},
{name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_12},
{name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF},
{name: "Count greater than 4", addr: 0x00_00_FF_00, count: 15, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "Count greater than 4, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "Offset at last byte", addr: 0x00_00_FF_00, count: 4, writeLen: 1, preimageOffset: 15, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x32_FF_FF_FF},
{name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
{name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
}
for i, c := range cases {
for _, v := range versions {
tName := fmt.Sprintf("%v (%v)", c.name, v.Name)
t.Run(tName, func(t *testing.T) {
effAddr := 0xFFffFFfc & 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))
state := goVm.GetState()
step := state.GetStep()
// Set up state
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.GetMemory().SetMemory(effAddr, c.prestateMem)
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
expected.Registers[2] = c.writeLen
expected.Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts, tracer)
} else {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
}
})
}
}
}
func TestEVMSysWriteHint(t *testing.T) {
var tracer *tracing.Hooks
......
package tests
import (
"encoding/binary"
"fmt"
"os"
"slices"
......@@ -8,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
......@@ -16,8 +18,375 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
func TestEVM_MT_LL(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0},
}
for i, c := range cases {
for _, withExistingReservation := range []bool{true, false} {
tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation)
t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(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()
// Set up state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetMemory(pc, insn)
state.GetMemory().SetMemory(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
if withExistingReservation {
state.LLReservationActive = true
state.LLAddress = c.effAddr + uint32(4)
state.LLOwnerThread = 123
} else {
state.LLReservationActive = false
state.LLAddress = 0
state.LLOwnerThread = 0
}
// Set up expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.LLReservationActive = true
expected.LLAddress = c.effAddr
expected.LLOwnerThread = state.GetCurrentThread().ThreadId
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = c.value
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT_SC(t *testing.T) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
shouldSucceed bool
}{
{name: "should succeed", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldSucceed: true},
{name: "mismatch addr", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldSucceed: false},
{name: "mismatched thread", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldSucceed: false},
{name: "no active reservation", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldSucceed: false},
}
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
threadId uint32
}{
{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},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5, threadId: 4},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5, threadId: 4},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0, threadId: 4},
{name: "Zero valued ll args", base: 0x00_00_00_00, offset: 0x0, value: 0xABCD, effAddr: 0x00_00_00_00, rtReg: 5, threadId: 0},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(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
if v.matchEffAddr {
llAddress = c.effAddr
} else {
llAddress = c.effAddr + 4
}
if v.matchThreadId {
llOwnerThread = c.threadId
} else {
llOwnerThread = c.threadId + 1
}
// Setup state
state.GetCurrentThread().ThreadId = c.threadId
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetMemory(pc, insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
var retVal uint32
if v.shouldSucceed {
retVal = 1
expected.ExpectMemoryWrite(c.effAddr, c.value)
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
} else {
retVal = 0
}
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = retVal
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
prestateMem uint32
postateMem uint32
shouldPanic bool
}{
{name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF},
{name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF},
{name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF},
{name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_FF_FF},
{name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_34_56_FF},
{name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_12_FF},
{name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_98_76},
{name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_12},
{name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF},
{name: "Count greater than 4", addr: 0x00_00_FF_00, count: 15, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "Count greater than 4, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "Offset at last byte", addr: 0x00_00_FF_00, count: 4, writeLen: 1, preimageOffset: 15, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x32_FF_FF_FF},
{name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
{name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
effAddr := 0xFFffFFfc & 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
if v.matchEffAddr {
llAddress = effAddr
} else {
llAddress = effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
}
// Set up state
state.PreimageKey = preimageKey
state.PreimageOffset = c.preimageOffset
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
state.GetMemory().SetMemory(effAddr, c.prestateMem)
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = c.writeLen
expected.ActiveThread().Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, contracts, tracer)
} else {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
}
})
}
}
}
func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
pc := uint32(0x04)
rt := uint32(0x12_34_56_78)
baseReg := 5
rtReg := 6
cases := []struct {
name string
opcode int
offset int
base uint32
effAddr uint32
preMem uint32
postMem uint32
}{
{name: "Store byte", opcode: 0b10_1000, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF},
{name: "Store halfword", opcode: 0b10_1001, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x56_78_FF_FF},
{name: "Store word left", opcode: 0b10_1010, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78},
{name: "Store word", opcode: 0b10_1011, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x12_34_56_78},
{name: "Store word right", opcode: 0b10_1110, base: 0xFF_00_00_04, offset: 0xFF_00_00_08, effAddr: 0xFF_00_00_0C, preMem: 0xFF_FF_FF_FF, postMem: 0x78_FF_FF_FF},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil)
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread uint32
if v.matchEffAddr {
llAddress = c.effAddr
} else {
llAddress = c.effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
}
// Setup state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetRegistersRef()[rtReg] = rt
state.GetRegistersRef()[baseReg] = c.base
state.GetMemory().SetMemory(state.GetPC(), insn)
state.GetMemory().SetMemory(c.effAddr, c.preMem)
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ExpectMemoryWrite(c.effAddr, c.postMem)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_SysClone_FlagHandling(t *testing.T) {
contracts := testutil.TestContractsSetup(t, testutil.MipsMultithreaded)
var tracer *tracing.Hooks
......@@ -92,7 +461,7 @@ func TestEVM_SysClone_Successful(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
stackPtr := uint32(100)
goVm, state, contracts := setup(t, i)
goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*333, state, c.traverseRight)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClone // the syscall number
......@@ -153,7 +522,7 @@ func TestEVM_SysGetTID(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789)
goVm, state, contracts := setup(t, i*789, nil)
mttestutil.InitializeSingleThread(i*789, state, false)
state.GetCurrentThread().ThreadId = c.threadId
......@@ -197,7 +566,7 @@ func TestEVM_SysExit(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
exitCode := uint8(3)
goVm, state, contracts := setup(t, i*133)
goVm, state, contracts := setup(t, i*133, nil)
mttestutil.SetupThreads(int64(i*1111), state, i%2 == 0, c.threadCount, 0)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
......@@ -245,7 +614,7 @@ func TestEVM_PopExitedThread(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*133)
goVm, state, contracts := setup(t, i*133, nil)
mttestutil.SetupThreads(int64(i*222), state, c.traverseRight, c.activeStackThreadCount, 1)
step := state.Step
......@@ -300,7 +669,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1234)
goVm, state, contracts := setup(t, i*1234, nil)
step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn)
......@@ -363,7 +732,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1122)
goVm, state, contracts := setup(t, i*1122, nil)
mttestutil.SetupThreads(int64(i*2244), state, c.traverseRight, c.activeThreadCount, c.inactiveThreadCount)
step := state.Step
......@@ -449,7 +818,7 @@ func TestEVM_SysFutex_UnsupportedOp(t *testing.T) {
for name, op := range unsupportedFutexOps {
t.Run(name, func(t *testing.T) {
goVm, state, contracts := setup(t, int(op))
goVm, state, contracts := setup(t, int(op), nil)
step := state.GetStep()
state.Memory.SetMemory(state.GetPC(), syscallInsn)
......@@ -504,7 +873,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) {
for _, traverseRight := range []bool{true, false} {
testName := fmt.Sprintf("%v: %v (traverseRight = %v)", syscallName, c.name, traverseRight)
t.Run(testName, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789)
goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*3259), state, traverseRight, c.activeThreads, c.inactiveThreads)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
......@@ -535,7 +904,7 @@ func runPreemptSyscall(t *testing.T, syscallName string, syscallNum uint32) {
func TestEVM_SysOpen(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 5512)
goVm, state, contracts := setup(t, 5512, nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysOpen // Set syscall number
......@@ -560,7 +929,7 @@ func TestEVM_SysOpen(t *testing.T) {
func TestEVM_SysGetPID(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 1929)
goVm, state, contracts := setup(t, 1929, nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysGetpid // Set syscall number
......@@ -594,6 +963,25 @@ func TestEVM_SysClockGettimeRealtime(t *testing.T) {
func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationActive bool
matchThreadId bool
matchEffAddr bool
matchEffAddr2 bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, 2nd word", llReservationActive: true, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread, 2nd word", llReservationActive: true, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, matching addr2", llReservationActive: false, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
}
cases := []struct {
name string
timespecAddr uint32
......@@ -601,45 +989,73 @@ func testEVM_SysClockGettime(t *testing.T, clkid uint32) {
{"aligned timespec address", 0x1000},
{"unaligned timespec address", 0x1003},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, 2101)
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
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
effAddr2 := effAddr + 4
step := state.Step
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = clkid // a0
state.GetRegistersRef()[5] = c.timespecAddr // a1
step := state.Step
// Define LL-related params
var llAddress, llOwnerThread uint32
if v.matchEffAddr {
llAddress = effAddr
} else if v.matchEffAddr2 {
llAddress = effAddr2
} else {
llAddress = effAddr2 + 8
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
}
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
next := state.Step + 1
var secs, nsecs uint32
if clkid == exec.ClockGettimeMonotonicFlag {
secs = uint32(next / exec.HZ)
nsecs = uint32((next % exec.HZ) * (1_000_000_000 / exec.HZ))
}
effAddr := c.timespecAddr & 0xFFffFFfc
expected.ExpectMemoryWrite(effAddr, secs)
expected.ExpectMemoryWrite(effAddr+4, nsecs)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = clkid // a0
state.GetRegistersRef()[5] = c.timespecAddr // a1
state.LLReservationActive = v.llReservationActive
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
next := state.Step + 1
var secs, nsecs uint32
if clkid == exec.ClockGettimeMonotonicFlag {
secs = uint32(next / exec.HZ)
nsecs = uint32((next % exec.HZ) * (1_000_000_000 / exec.HZ))
}
expected.ExpectMemoryWrite(effAddr, secs)
expected.ExpectMemoryWrite(effAddr2, nsecs)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_SysClockGettimeNonMonotonic(t *testing.T) {
var tracer *tracing.Hooks
goVm, state, contracts := setup(t, 2101)
goVm, state, contracts := setup(t, 2101, nil)
timespecAddr := uint32(0x1000)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
......@@ -700,7 +1116,7 @@ func TestEVM_NoopSyscall(t *testing.T) {
var tracer *tracing.Hooks
for noopName, noopVal := range NoopSyscalls {
t.Run(noopName, func(t *testing.T) {
goVm, state, contracts := setup(t, int(noopVal))
goVm, state, contracts := setup(t, int(noopVal), nil)
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = noopVal // Set syscall number
......@@ -747,7 +1163,7 @@ func TestEVM_UnsupportedSyscall(t *testing.T) {
syscallNum := syscallNum
t.Run(testName, func(t *testing.T) {
t.Parallel()
goVm, state, contracts := setup(t, i*3434)
goVm, state, contracts := setup(t, i*3434, nil)
// Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = syscallNum
......@@ -794,7 +1210,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup")
}
goVm, state, contracts := setup(t, i)
goVm, state, contracts := setup(t, i, nil)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step
......@@ -856,7 +1272,7 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
testName := fmt.Sprintf("%v (traverseRight = %v)", c.name, traverseRight)
t.Run(testName, func(t *testing.T) {
// Setup
goVm, state, contracts := setup(t, i*789)
goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0)
// Put threads into a waiting state so that we just traverse through them
for _, thread := range mttestutil.GetAllThreads(state) {
......@@ -926,7 +1342,7 @@ func TestEVM_WakeupTraversalStep(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*2000)
goVm, state, contracts := setup(t, i*2000, nil)
mttestutil.SetupThreads(int64(i*101), state, c.traverseRight, c.activeStackSize, c.otherStackSize)
step := state.Step
......@@ -974,7 +1390,7 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
// Setup
goVm, state, contracts := setup(t, i*789)
goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, false, c.threadCount, 0)
state.Wakeup = 0x08
step := state.Step
......@@ -1028,7 +1444,7 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) {
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*789)
goVm, state, contracts := setup(t, i*789, nil)
// Setup basic getThreadId syscall instruction
state.Memory.SetMemory(state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = exec.SysGetTID // Set syscall number
......@@ -1060,9 +1476,9 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) {
}
}
func setup(t require.TestingT, randomSeed int) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) {
func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) {
v := GetMultiThreadedTestCase(t)
vm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed)))
vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed)))
state := mttestutil.GetMtState(t, vm)
return vm, state, v.Contracts
......
package tests
import (
"encoding/binary"
"os"
"testing"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
)
func TestEVM_LL(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 0},
}
v := GetSingleThreadedTestCase(t)
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(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.GetRegistersRef()[baseReg] = c.base
step := state.GetStep()
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = pc + 4
expected.NextPC = pc + 8
if rtReg != 0 {
expected.Registers[rtReg] = c.value
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
})
}
}
func TestEVM_SC(t *testing.T) {
var tracer *tracing.Hooks
cases := []struct {
name string
base uint32
offset int
value uint32
effAddr uint32
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0},
}
v := GetSingleThreadedTestCase(t)
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := uint32(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()
state.GetMemory().SetMemory(pc, insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value
step := state.GetStep()
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.Step += 1
expected.PC = pc + 4
expected.NextPC = pc + 8
expectedMemory := memory.NewMemory()
expectedMemory.SetMemory(pc, insn)
expectedMemory.SetMemory(c.effAddr, c.value)
expected.MemoryRoot = expectedMemory.MerkleRoot()
if rtReg != 0 {
expected.Registers[rtReg] = 1 // 1 for success
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
})
}
}
func TestEVM_SysRead_Preimage(t *testing.T) {
var tracer *tracing.Hooks
preimageValue := make([]byte, 0, 8)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x12_34_56_78)
preimageValue = binary.BigEndian.AppendUint32(preimageValue, 0x98_76_54_32)
v := GetSingleThreadedTestCase(t)
cases := []struct {
name string
addr uint32
count uint32
writeLen uint32
preimageOffset uint32
prestateMem uint32
postateMem uint32
shouldPanic bool
}{
{name: "Aligned addr, write 1 byte", addr: 0x00_00_FF_00, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_FF_FF_FF},
{name: "Aligned addr, write 2 byte", addr: 0x00_00_FF_00, count: 2, writeLen: 2, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_FF_FF},
{name: "Aligned addr, write 3 byte", addr: 0x00_00_FF_00, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_FF},
{name: "Aligned addr, write 4 byte", addr: 0x00_00_FF_00, count: 4, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "1-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_01, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_FF_FF},
{name: "1-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_01, count: 2, writeLen: 2, preimageOffset: 9, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_34_56_FF},
{name: "1-byte misaligned addr, write 3 byte", addr: 0x00_00_FF_01, count: 3, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "2-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_02, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_12_FF},
{name: "2-byte misaligned addr, write 2 byte", addr: 0x00_00_FF_02, count: 2, writeLen: 2, preimageOffset: 12, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_98_76},
{name: "3-byte misaligned addr, write 1 byte", addr: 0x00_00_FF_03, count: 1, writeLen: 1, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_12},
{name: "Count of 0", addr: 0x00_00_FF_03, count: 0, writeLen: 0, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF},
{name: "Count greater than 4", addr: 0x00_00_FF_00, count: 15, writeLen: 4, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x12_34_56_78},
{name: "Count greater than 4, unaligned", addr: 0x00_00_FF_01, count: 15, writeLen: 3, preimageOffset: 8, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_12_34_56},
{name: "Offset at last byte", addr: 0x00_00_FF_00, count: 4, writeLen: 1, preimageOffset: 15, prestateMem: 0xFF_FF_FF_FF, postateMem: 0x32_FF_FF_FF},
{name: "Offset just out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 16, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
{name: "Offset out of bounds", addr: 0x00_00_FF_00, count: 4, writeLen: 0, preimageOffset: 17, prestateMem: 0xFF_FF_FF_FF, postateMem: 0xFF_FF_FF_FF, shouldPanic: true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
effAddr := 0xFFffFFfc & 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))
state := goVm.GetState()
step := state.GetStep()
// Set up state
state.GetRegistersRef()[2] = exec.SysRead
state.GetRegistersRef()[4] = exec.FdPreimageRead
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetMemory(state.GetPC(), syscallInsn)
state.GetMemory().SetMemory(effAddr, c.prestateMem)
// Setup expectations
expected := testutil.NewExpectedState(state)
expected.ExpectStep()
expected.Registers[2] = c.writeLen
expected.Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
if c.shouldPanic {
require.Panics(t, func() { _, _ = goVm.Step(true) })
testutil.AssertPreimageOracleReverts(t, preimageKey, preimageValue, c.preimageOffset, v.Contracts, tracer)
} else {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
}
})
}
}
......@@ -140,12 +140,12 @@
"sourceCodeHash": "0x3f7bd622a788b8d00fe1631b14b761488eedccf56879f7ea2d610dd5ce81efbe"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0x6add59adb849ec02e13b33df7efd439ca80f6a8ceefdf69ebcb0963c0167da23",
"sourceCodeHash": "0xee1aef5a502f9491b7b83dab46ea2f0fc286f87ace31edcc1367c840d462bdfe"
"initCodeHash": "0x4043f262804931bbbbecff64f87f2d0bdc4554b4d0a8b22df8fff940e8d239bf",
"sourceCodeHash": "0xba4674e1846afbbc708877332a38dfabd4b8d1e48ce07d8ebf0a45c9f27f16b0"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0xeab5f44d7fa7af1072f500c754bb55aa92409a79b2765a66efed47461c0c4049",
"sourceCodeHash": "0xcf180ff2cab8d0a34f3bb19d7145f5bf7f67dd379722ece765d09b38dcfed2f6"
"initCodeHash": "0xdaed5d70cc84a53f224c28f24f8eef26d5d53dfba9fdc4f1b28c3b231b974e53",
"sourceCodeHash": "0x4026eb7ae7b303ec4c3c2880e14e260dbcfc0b4290459bcd22994cfed8655f80"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b",
......
......@@ -69,5 +69,10 @@
"inputs": [],
"name": "InvalidMemoryProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
}
]
\ No newline at end of file
......@@ -75,6 +75,11 @@
"name": "InvalidMemoryProof",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSecondMemoryProof",
......
......@@ -8,6 +8,7 @@ import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.s
import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";
import { InvalidRMWInstruction } from "src/cannon/libraries/CannonErrors.sol";
/// @title MIPS
/// @notice The MIPS contract emulates a single MIPS instruction.
......@@ -44,8 +45,8 @@ contract MIPS is ISemver {
}
/// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.1.1-beta.3
string public constant version = "1.1.1-beta.3";
/// @custom:semver 1.1.1-beta.4
string public constant version = "1.1.1-beta.4";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -178,7 +179,7 @@ contract MIPS is ISemver {
proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
memRoot: state.memRoot
});
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args);
(v0, v1, state.preimageOffset, state.memRoot,,) = sys.handleSysRead(args);
} else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0,
......@@ -291,23 +292,59 @@ contract MIPS is ISemver {
return handleSyscall(_localContext);
}
// Handle RMW (read-modify-write) ops
if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) {
return handleRMWOps(state, insn, opcode);
}
// Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(state);
(state.memRoot) = ins.execMipsCoreStepLogic({
_cpu: cpu,
_registers: state.registers,
_memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
_insn: insn,
_opcode: opcode,
_fun: fun
ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
cpu: cpu,
registers: state.registers,
memRoot: state.memRoot,
memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
insn: insn,
opcode: opcode,
fun: fun
});
(state.memRoot,,) = ins.execMipsCoreStepLogic(coreStepArgs);
setStateCpuScalars(state, cpu);
return outputState();
}
}
function handleRMWOps(State memory _state, uint32 _insn, uint32 _opcode) internal returns (bytes32) {
unchecked {
uint32 baseReg = (_insn >> 21) & 0x1F;
uint32 base = _state.registers[baseReg];
uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 offset = ins.signExtendImmediate(_insn);
uint32 effAddr = (base + offset) & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1);
uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);
uint32 retVal;
if (_opcode == ins.OP_LOAD_LINKED) {
retVal = mem;
} else if (_opcode == ins.OP_STORE_CONDITIONAL) {
uint32 val = _state.registers[rtReg];
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val);
retVal = 1; // 1 for success
} else {
revert InvalidRMWInstruction();
}
st.CpuScalars memory cpu = getCpuScalars(_state);
ins.handleRd(cpu, _state.registers, rtReg, retVal, true);
setStateCpuScalars(_state, cpu);
return outputState();
}
}
function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) {
return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi });
}
......
......@@ -8,7 +8,9 @@ import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol";
import { VMStatuses } from "src/dispute/lib/Types.sol";
import { InvalidMemoryProof, InvalidSecondMemoryProof } from "src/cannon/libraries/CannonErrors.sol";
import {
InvalidMemoryProof, InvalidRMWInstruction, InvalidSecondMemoryProof
} from "src/cannon/libraries/CannonErrors.sol";
/// @title MIPS2
/// @notice The MIPS2 contract emulates a single MIPS instruction.
......@@ -33,13 +35,16 @@ contract MIPS2 is ISemver {
}
/// @notice Stores the VM state.
/// Total state size: 32 + 32 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 163 bytes
/// Total state size: 32 + 32 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 172 bytes
/// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State {
bytes32 memRoot;
bytes32 preimageKey;
uint32 preimageOffset;
uint32 heap;
bool llReservationActive;
uint32 llAddress;
uint32 llOwnerThread;
uint8 exitCode;
bool exited;
uint64 step;
......@@ -52,8 +57,8 @@ contract MIPS2 is ISemver {
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.8
string public constant version = "1.0.0-beta.8";
/// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.9";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -71,7 +76,7 @@ contract MIPS2 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step
uint256 internal constant TC_MEM_OFFSET = 0x220;
uint256 internal constant TC_MEM_OFFSET = 0x280;
/// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) {
......@@ -108,8 +113,8 @@ contract MIPS2 is ISemver {
// expected thread mem offset check
revert(0, 0)
}
if iszero(eq(mload(0x40), shl(5, 60))) {
// 4 + 13 state slots + 43 thread slots = 60 expected memory check
if iszero(eq(mload(0x40), shl(5, 63))) {
// 4 + 16 state slots + 43 thread slots = 63 expected memory check
revert(0, 0)
}
if iszero(eq(_stateData.offset, 132)) {
......@@ -136,6 +141,9 @@ contract MIPS2 is ISemver {
c, m := putField(c, m, 32) // preimageKey
c, m := putField(c, m, 4) // preimageOffset
c, m := putField(c, m, 4) // heap
c, m := putField(c, m, 1) // llReservationActive
c, m := putField(c, m, 4) // llAddress
c, m := putField(c, m, 4) // llOwnerThread
c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited
exited := mload(sub(m, 32))
......@@ -228,19 +236,95 @@ contract MIPS2 is ISemver {
return handleSyscall(_localContext);
}
// Handle RMW (read-modify-write) ops
if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) {
return handleRMWOps(state, thread, insn, opcode);
}
// Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(thread);
(state.memRoot) = ins.execMipsCoreStepLogic({
_cpu: cpu,
_registers: thread.registers,
_memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
_insn: insn,
_opcode: opcode,
_fun: fun
ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
cpu: cpu,
registers: thread.registers,
memRoot: state.memRoot,
memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
insn: insn,
opcode: opcode,
fun: fun
});
bool memUpdated;
uint32 memAddr;
(state.memRoot, memUpdated, memAddr) = ins.execMipsCoreStepLogic(coreStepArgs);
setStateCpuScalars(thread, cpu);
updateCurrentThreadRoot();
if (memUpdated) {
handleMemoryUpdate(state, memAddr);
}
return outputState();
}
}
function handleMemoryUpdate(State memory _state, uint32 _memAddr) internal pure {
if (_memAddr == _state.llAddress) {
// Reserved address was modified, clear the reservation
clearLLMemoryReservation(_state);
}
}
function clearLLMemoryReservation(State memory _state) internal pure {
_state.llReservationActive = false;
_state.llAddress = 0;
_state.llOwnerThread = 0;
}
function handleRMWOps(
State memory _state,
ThreadState memory _thread,
uint32 _insn,
uint32 _opcode
)
internal
returns (bytes32)
{
unchecked {
uint32 baseReg = (_insn >> 21) & 0x1F;
uint32 base = _thread.registers[baseReg];
uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 offset = ins.signExtendImmediate(_insn);
uint32 effAddr = (base + offset) & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1);
uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);
uint32 retVal = 0;
uint32 threadId = _thread.threadID;
if (_opcode == ins.OP_LOAD_LINKED) {
retVal = mem;
_state.llReservationActive = true;
_state.llAddress = effAddr;
_state.llOwnerThread = threadId;
} else if (_opcode == ins.OP_STORE_CONDITIONAL) {
// Check if our memory reservation is still intact
if (_state.llReservationActive && _state.llOwnerThread == threadId && _state.llAddress == effAddr) {
// Complete atomic update: set memory and return 1 for success
clearLLMemoryReservation(_state);
uint32 val = _thread.registers[rtReg];
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val);
retVal = 1;
} else {
// Atomic update failed, return 0 for failure
retVal = 0;
}
} else {
revert InvalidRMWInstruction();
}
st.CpuScalars memory cpu = getCpuScalars(_thread);
ins.handleRd(cpu, _thread.registers, rtReg, retVal, true);
setStateCpuScalars(_thread, cpu);
updateCurrentThreadRoot();
return outputState();
}
}
......@@ -319,7 +403,8 @@ contract MIPS2 is ISemver {
proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
memRoot: state.memRoot
});
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args);
// Encapsulate execution to avoid stack-too-deep error
(v0, v1) = execSysRead(state, args);
} else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0,
......@@ -412,6 +497,7 @@ contract MIPS2 is ISemver {
// Recompute the new root after updating effAddr
state.memRoot =
MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs);
handleMemoryUpdate(state, effAddr);
// Verify the second memory proof against the newly computed root
if (
!MIPSMemory.isValidProof(
......@@ -422,6 +508,7 @@ contract MIPS2 is ISemver {
}
state.memRoot =
MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs);
handleMemoryUpdate(state, effAddr + 4);
} else {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL;
......@@ -502,6 +589,22 @@ contract MIPS2 is ISemver {
}
}
function execSysRead(
State memory _state,
sys.SysReadParams memory _args
)
internal
view
returns (uint32 v0, uint32 v1)
{
bool memUpdated;
uint32 memAddr;
(v0, v1, _state.preimageOffset, _state.memRoot, memUpdated, memAddr) = sys.handleSysRead(_args);
if (memUpdated) {
handleMemoryUpdate(_state, memAddr);
}
}
/// @notice Computes the hash of the MIPS state.
/// @return out_ The hashed MIPS state.
function outputState() internal returns (bytes32 out_) {
......@@ -526,6 +629,9 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 32) // preimageKey
from, to := copyMem(from, to, 4) // preimageOffset
from, to := copyMem(from, to, 4) // heap
from, to := copyMem(from, to, 1) // llReservationActive
from, to := copyMem(from, to, 4) // llAddress
from, to := copyMem(from, to, 4) // llOwnerThread
let exitCode := mload(from)
from, to := copyMem(from, to, 1) // exitCode
exited := mload(from)
......
......@@ -8,6 +8,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @notice Interface for the MIPS contract.
interface IMIPS is ISemver {
error InvalidMemoryProof();
error InvalidRMWInstruction();
function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
......
......@@ -10,6 +10,7 @@ interface IMIPS2 is ISemver {
error InvalidExitedValue();
error InvalidMemoryProof();
error InvalidSecondMemoryProof();
error InvalidRMWInstruction();
function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
......
......@@ -60,3 +60,6 @@ error InvalidMemoryProof();
/// @notice Thrown when the second memory location is invalid
error InvalidSecondMemoryProof();
/// @notice Thrown when an RMW instruction is expected, but a different instruction is provided.
error InvalidRMWInstruction();
......@@ -5,6 +5,26 @@ import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
library MIPSInstructions {
uint32 internal constant OP_LOAD_LINKED = 0x30;
uint32 internal constant OP_STORE_CONDITIONAL = 0x38;
struct CoreStepLogicParams {
/// @param opcode The opcode value parsed from insn_.
st.CpuScalars cpu;
/// @param registers The CPU registers.
uint32[32] registers;
/// @param memRoot The current merkle root of the memory.
bytes32 memRoot;
/// @param memProofOffset The offset in calldata specify where the memory merkle proof is located.
uint256 memProofOffset;
/// @param insn The current 32-bit instruction at the pc.
uint32 insn;
/// @param cpu The CPU scalar fields.
uint32 opcode;
/// @param fun The function value parsed from insn_.
uint32 fun;
}
/// @param _pc The program counter.
/// @param _memRoot The current memory root.
/// @param _insnProofOffset The calldata offset of the memory proof for the current instruction.
......@@ -30,91 +50,80 @@ library MIPSInstructions {
}
/// @notice Execute core MIPS step logic.
/// @notice _cpu The CPU scalar fields.
/// @notice _registers The CPU registers.
/// @notice _memRoot The current merkle root of the memory.
/// @notice _memProofOffset The offset in calldata specify where the memory merkle proof is located.
/// @param _insn The current 32-bit instruction at the pc.
/// @param _opcode The opcode value parsed from insn_.
/// @param _fun The function value parsed from insn_.
/// @return newMemRoot_ The updated merkle root of memory after any modifications, may be unchanged.
function execMipsCoreStepLogic(
st.CpuScalars memory _cpu,
uint32[32] memory _registers,
bytes32 _memRoot,
uint256 _memProofOffset,
uint32 _insn,
uint32 _opcode,
uint32 _fun
)
/// @return memUpdated_ True if memory was modified.
/// @return memAddr_ Holds the memory address that was updated if memUpdated_ is true.
function execMipsCoreStepLogic(CoreStepLogicParams memory _args)
internal
pure
returns (bytes32 newMemRoot_)
returns (bytes32 newMemRoot_, bool memUpdated_, uint32 memAddr_)
{
unchecked {
newMemRoot_ = _memRoot;
newMemRoot_ = _args.memRoot;
memUpdated_ = false;
memAddr_ = 0;
// j-type j/jal
if (_opcode == 2 || _opcode == 3) {
if (_args.opcode == 2 || _args.opcode == 3) {
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
uint32 target = (_cpu.nextPC & 0xF0000000) | (_insn & 0x03FFFFFF) << 2;
handleJump(_cpu, _registers, _opcode == 2 ? 0 : 31, target);
return newMemRoot_;
uint32 target = (_args.cpu.nextPC & 0xF0000000) | (_args.insn & 0x03FFFFFF) << 2;
handleJump(_args.cpu, _args.registers, _args.opcode == 2 ? 0 : 31, target);
return (newMemRoot_, memUpdated_, memAddr_);
}
// register fetch
uint32 rs = 0; // source register 1 value
uint32 rt = 0; // source register 2 / temp value
uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 rtReg = (_args.insn >> 16) & 0x1F;
// R-type or I-type (stores rt)
rs = _registers[(_insn >> 21) & 0x1F];
rs = _args.registers[(_args.insn >> 21) & 0x1F];
uint32 rdReg = rtReg;
if (_opcode == 0 || _opcode == 0x1c) {
if (_args.opcode == 0 || _args.opcode == 0x1c) {
// R-type (stores rd)
rt = _registers[rtReg];
rdReg = (_insn >> 11) & 0x1F;
} else if (_opcode < 0x20) {
rt = _args.registers[rtReg];
rdReg = (_args.insn >> 11) & 0x1F;
} else if (_args.opcode < 0x20) {
// rt is SignExtImm
// don't sign extend for andi, ori, xori
if (_opcode == 0xC || _opcode == 0xD || _opcode == 0xe) {
if (_args.opcode == 0xC || _args.opcode == 0xD || _args.opcode == 0xe) {
// ZeroExtImm
rt = _insn & 0xFFFF;
rt = _args.insn & 0xFFFF;
} else {
// SignExtImm
rt = signExtend(_insn & 0xFFFF, 16);
rt = signExtend(_args.insn & 0xFFFF, 16);
}
} else if (_opcode >= 0x28 || _opcode == 0x22 || _opcode == 0x26) {
} else if (_args.opcode >= 0x28 || _args.opcode == 0x22 || _args.opcode == 0x26) {
// store rt value with store
rt = _registers[rtReg];
rt = _args.registers[rtReg];
// store actual rt with lwl and lwr
rdReg = rtReg;
}
if ((_opcode >= 4 && _opcode < 8) || _opcode == 1) {
if ((_args.opcode >= 4 && _args.opcode < 8) || _args.opcode == 1) {
handleBranch({
_cpu: _cpu,
_registers: _registers,
_opcode: _opcode,
_insn: _insn,
_cpu: _args.cpu,
_registers: _args.registers,
_opcode: _args.opcode,
_insn: _args.insn,
_rtReg: rtReg,
_rs: rs
});
return newMemRoot_;
return (newMemRoot_, memUpdated_, memAddr_);
}
uint32 storeAddr = 0xFF_FF_FF_FF;
// memory fetch (all I-type)
// we do the load for stores also
uint32 mem = 0;
if (_opcode >= 0x20) {
if (_args.opcode >= 0x20) {
// M[R[rs]+SignExtImm]
rs += signExtend(_insn & 0xFFFF, 16);
rs += signExtend(_args.insn & 0xFFFF, 16);
uint32 addr = rs & 0xFFFFFFFC;
mem = MIPSMemory.readMem(_memRoot, addr, _memProofOffset);
if (_opcode >= 0x28 && _opcode != 0x30) {
mem = MIPSMemory.readMem(_args.memRoot, addr, _args.memProofOffset);
if (_args.opcode >= 0x28) {
// store
storeAddr = addr;
// store opcodes don't write back to a register
......@@ -124,49 +133,58 @@ library MIPSInstructions {
// ALU
// Note: swr outputs more than 4 bytes without the mask 0xffFFffFF
uint32 val = executeMipsInstruction(_insn, _opcode, _fun, rs, rt, mem) & 0xffFFffFF;
uint32 val = executeMipsInstruction(_args.insn, _args.opcode, _args.fun, rs, rt, mem) & 0xffFFffFF;
if (_opcode == 0 && _fun >= 8 && _fun < 0x1c) {
if (_fun == 8 || _fun == 9) {
if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < 0x1c) {
if (_args.fun == 8 || _args.fun == 9) {
// jr/jalr
handleJump(_cpu, _registers, _fun == 8 ? 0 : rdReg, rs);
return newMemRoot_;
handleJump(_args.cpu, _args.registers, _args.fun == 8 ? 0 : rdReg, rs);
return (newMemRoot_, memUpdated_, memAddr_);
}
if (_fun == 0xa) {
if (_args.fun == 0xa) {
// movz
handleRd(_cpu, _registers, rdReg, rs, rt == 0);
return newMemRoot_;
handleRd(_args.cpu, _args.registers, rdReg, rs, rt == 0);
return (newMemRoot_, memUpdated_, memAddr_);
}
if (_fun == 0xb) {
if (_args.fun == 0xb) {
// movn
handleRd(_cpu, _registers, rdReg, rs, rt != 0);
return newMemRoot_;
handleRd(_args.cpu, _args.registers, rdReg, rs, rt != 0);
return (newMemRoot_, memUpdated_, memAddr_);
}
// lo and hi registers
// can write back
if (_fun >= 0x10 && _fun < 0x1c) {
handleHiLo({ _cpu: _cpu, _registers: _registers, _fun: _fun, _rs: rs, _rt: rt, _storeReg: rdReg });
return newMemRoot_;
if (_args.fun >= 0x10 && _args.fun < 0x1c) {
handleHiLo({
_cpu: _args.cpu,
_registers: _args.registers,
_fun: _args.fun,
_rs: rs,
_rt: rt,
_storeReg: rdReg
});
return (newMemRoot_, memUpdated_, memAddr_);
}
}
// stupid sc, write a 1 to rt
if (_opcode == 0x38 && rtReg != 0) {
_registers[rtReg] = 1;
}
// write memory
if (storeAddr != 0xFF_FF_FF_FF) {
newMemRoot_ = MIPSMemory.writeMem(storeAddr, _memProofOffset, val);
newMemRoot_ = MIPSMemory.writeMem(storeAddr, _args.memProofOffset, val);
memUpdated_ = true;
memAddr_ = storeAddr;
}
// write back the value to destination register
handleRd(_cpu, _registers, rdReg, val, true);
handleRd(_args.cpu, _args.registers, rdReg, val, true);
return newMemRoot_;
return (newMemRoot_, memUpdated_, memAddr_);
}
}
function signExtendImmediate(uint32 _insn) internal pure returns (uint32 offset_) {
unchecked {
return signExtend(_insn & 0xFFFF, 16);
}
}
......@@ -417,14 +435,6 @@ library MIPSInstructions {
uint32 val = _rt << (24 - (_rs & 3) * 8);
uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8);
return (_mem & ~mask) | val;
}
// ll
else if (_opcode == 0x30) {
return _mem;
}
// sc
else if (_opcode == 0x38) {
return _rt;
} else {
revert("invalid instruction");
}
......
......@@ -202,13 +202,22 @@ library MIPSSyscalls {
function handleSysRead(SysReadParams memory _args)
internal
view
returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_)
returns (
uint32 v0_,
uint32 v1_,
uint32 newPreimageOffset_,
bytes32 newMemRoot_,
bool memUpdated_,
uint32 memAddr_
)
{
unchecked {
v0_ = uint32(0);
v1_ = uint32(0);
newMemRoot_ = _args.memRoot;
newPreimageOffset_ = _args.preimageOffset;
memUpdated_ = false;
memAddr_ = 0;
// args: _a0 = fd, _a1 = addr, _a2 = count
// returns: v0_ = read, v1_ = err code
......@@ -217,9 +226,10 @@ library MIPSSyscalls {
}
// pre-image oracle read
else if (_args.a0 == FD_PREIMAGE_READ) {
uint32 effAddr = _args.a1 & 0xFFffFFfc;
// verify proof is correct, and get the existing memory.
// mask the addr to align it to 4 bytes
uint32 mem = MIPSMemory.readMem(_args.memRoot, _args.a1 & 0xFFffFFfc, _args.proofOffset);
uint32 mem = MIPSMemory.readMem(_args.memRoot, effAddr, _args.proofOffset);
// If the preimage key is a local key, localize it in the context of the caller.
if (uint8(_args.preimageKey[0]) == 1) {
_args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext);
......@@ -246,7 +256,9 @@ library MIPSSyscalls {
}
// Write memory back
newMemRoot_ = MIPSMemory.writeMem(_args.a1 & 0xFFffFFfc, _args.proofOffset, mem);
newMemRoot_ = MIPSMemory.writeMem(effAddr, _args.proofOffset, mem);
memUpdated_ = true;
memAddr_ = effAddr;
newPreimageOffset_ += uint32(datLen);
v0_ = uint32(datLen);
}
......@@ -260,7 +272,7 @@ library MIPSSyscalls {
v1_ = EBADF;
}
return (v0_, v1_, newPreimageOffset_, newMemRoot_);
return (v0_, v1_, newPreimageOffset_, newMemRoot_, memUpdated_, memAddr_);
}
}
......
......@@ -141,9 +141,9 @@ contract MIPS2_Test is CommonTest {
/// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80
function test_mips2_step_debug_succeeds() external {
bytes memory input =
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a3df82bcbdf27955e04d467b84d94d0b4662c88a70264d7ea31325bc8d826681ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000affffffff00cbf05eda4a03d05cc6a14cff1cf2f955bfb253097c296ea96032da307da4f353ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000000000280000002c00000000000000000000000000000000000000010000000000000000000000000000000000000000fffffffd00000003000000000000000000000000000000000000000000000000bffffff00000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7ef00d0ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5ae020008ae11000403e0000800000000000000000000000000000000000000003c10bfff3610fff0341100013c08ffff3508fffd34090003010950212d420001ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d4e545be579dc7118fc02cd7b19b704e4710a81bce0cb48bb7e289e403e7c969a00000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d6a3e23902bafb21ac312e717f7942f8fd8ae795f67c918083442c2ab253cc66e0000000000000000000000000000000000000000000000000000";
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acab5a39c6f974b22302e96dcdef1815483eaf580639bb1ee7ac98267afac2bf1ac041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81fa7c3d90b000000000000000000000078fc2ffac2fd940100000000000080c8ffffffff006504aeffb6e08baf3f85da5476a9160fa8f9f188a722fdd29268b0cbaf596736ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000f1f85ff4f1f85ff8506d442dbb3938f83eb60825a7ecbff2000010185e1a31f600050f0000000064a7c3d90be5acea102ad7bda149e0bfd0e7111c77d98b335645e665389becadf140ef999cc64fbd7f04799e85c97dadc5cca510bd5b3d97166d1aec28829f3dd43d8cf1f9358e4103b16d09d466e2c7c048ea3ba1aef3141e700270581aa0b75b50e34fc926bb2d83ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
(bool success, bytes memory retVal) = address(mips).call(input);
bytes memory expectedRetVal = hex"03fc952a0bd8aabc407669b857af995eab91ce55c404d8b32eaf8b941a48188c";
bytes memory expectedRetVal = hex"0335fe4205f8443eefa7ac4541197874224df35e8536158c2fc2d5c8c2d2adb4";
assertTrue(success);
assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned");
......@@ -177,6 +177,9 @@ contract MIPS2_Test is CommonTest {
preimageKey: bytes32(0),
preimageOffset: 0,
heap: 0,
llReservationActive: false,
llAddress: 0,
llOwnerThread: 0,
exitCode: 0,
exited: false,
step: 1,
......@@ -211,7 +214,7 @@ contract MIPS2_Test is CommonTest {
assembly {
// Manipulate state data
// Push offset by an additional 32 bytes (0x20) to account for length prefix
mstore8(add(add(stateData, 0x20), 73), exited)
mstore8(add(add(stateData, 0x20), 82), exited)
}
// Call the step function and expect a revert.
......@@ -1992,36 +1995,50 @@ contract MIPS2_Test is CommonTest {
}
function test_ll_succeeds() public {
uint32 t1 = 0x100;
uint32 val = 0x12_23_45_67;
uint32 insn = encodeitype(0x30, 0x9, 0x8, 0x4); // ll $t0, 4($t1)
uint32 base = 0x100;
uint32 memVal = 0x12_23_45_67;
uint16 offset = 0x4;
uint32 effAddr = base + offset;
uint32 insn = encodeitype(0x30, 0x9, 0x8, offset); // ll baseReg, rtReg, offset
(MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, t1 + 4, val);
thread.registers[8] = 0; // t0
thread.registers[9] = t1;
constructMIPSState(0, insn, effAddr, memVal);
thread.registers[8] = 0; // rtReg
thread.registers[9] = base;
bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT);
updateThreadStacks(state, thread);
thread.registers[8] = val; // t0
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]);
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal);
expect.llReservationActive = true;
expect.llAddress = effAddr;
expect.llOwnerThread = thread.threadID;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
function test_sc_succeeds() public {
uint32 t1 = 0x100;
uint32 insn = encodeitype(0x38, 0x9, 0x8, 0x4); // sc $t0, 4($t1)
uint32 base = 0x100;
uint16 offset = 0x4;
uint32 effAddr = base + offset;
uint32 writeMemVal = 0xaa_bb_cc_dd;
uint32 insn = encodeitype(0x38, 0x9, 0x8, offset); // ll baseReg, rtReg, offset
(MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, t1 + 4, 0);
thread.registers[8] = 0xaa_bb_cc_dd; // t0
thread.registers[9] = t1;
constructMIPSState(0, insn, effAddr, 0);
state.llReservationActive = true;
state.llAddress = effAddr;
state.llOwnerThread = thread.threadID;
thread.registers[8] = writeMemVal;
thread.registers[9] = base;
bytes memory threadWitness = abi.encodePacked(encodeThread(thread), EMPTY_THREAD_ROOT);
updateThreadStacks(state, thread);
thread.registers[8] = 0x1; // t0
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, /* t0 */ thread.registers[8]);
(expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, t1 + 4, 0xaa_bb_cc_dd);
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1);
(expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal);
expect.llReservationActive = false;
expect.llAddress = 0;
expect.llOwnerThread = 0;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
......@@ -2528,23 +2545,33 @@ contract MIPS2_Test is CommonTest {
}
function encodeState(MIPS2.State memory _state) internal pure returns (bytes memory) {
// Split up encoding to get around stack-too-deep error
return abi.encodePacked(encodeStateA(_state), encodeStateB(_state));
}
function encodeStateA(MIPS2.State memory _state) internal pure returns (bytes memory) {
return abi.encodePacked(
_state.memRoot,
_state.preimageKey,
_state.preimageOffset,
_state.heap,
_state.llReservationActive,
_state.llAddress,
_state.llOwnerThread,
_state.exitCode,
_state.exited,
_state.step,
_state.stepsSinceLastContextSwitch,
_state.wakeup,
_state.traverseRight,
_state.leftThreadStack,
_state.rightThreadStack,
_state.nextThreadID
_state.leftThreadStack
);
}
function encodeStateB(MIPS2.State memory _state) internal pure returns (bytes memory) {
return abi.encodePacked(_state.rightThreadStack, _state.nextThreadID);
}
function copyState(MIPS2.State memory _state) internal pure returns (MIPS2.State memory out_) {
bytes memory data = abi.encode(_state);
return abi.decode(data, (MIPS2.State));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment