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 ( ...@@ -5,6 +5,11 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
) )
const (
OpLoadLinked = 0x30
OpStoreConditional = 0x38
)
func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) { func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc) insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits opcode = insn >> 26 // First 6-bits
...@@ -13,7 +18,7 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun ...@@ -13,7 +18,7 @@ func GetInstructionDetails(pc uint32, memory *memory.Memory) (insn, opcode, fun
return 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 // j-type j/jal
if opcode == 2 || opcode == 3 { if opcode == 2 || opcode == 3 {
linkReg := uint32(0) linkReg := uint32(0)
...@@ -23,7 +28,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -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 // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
stackTracker.PushStack(cpu.PC, target) stackTracker.PushStack(cpu.PC, target)
return HandleJump(cpu, registers, linkReg, target) err = HandleJump(cpu, registers, linkReg, target)
return
} }
// register fetch // register fetch
...@@ -57,7 +63,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -57,7 +63,8 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
} }
if (opcode >= 4 && opcode < 8) || opcode == 1 { 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) storeAddr := uint32(0xFF_FF_FF_FF)
...@@ -70,7 +77,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -70,7 +77,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
addr := rs & 0xFFFFFFFC addr := rs & 0xFFFFFFFC
memTracker.TrackMemAccess(addr) memTracker.TrackMemAccess(addr)
mem = memory.GetMemory(addr) mem = memory.GetMemory(addr)
if opcode >= 0x28 && opcode != 0x30 { if opcode >= 0x28 {
// store // store
storeAddr = addr storeAddr = addr
// store opcodes don't write back to a register // store opcodes don't write back to a register
...@@ -90,36 +97,42 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor ...@@ -90,36 +97,42 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
} else { } else {
stackTracker.PopStack() stackTracker.PopStack()
} }
return HandleJump(cpu, registers, linkReg, rs) err = HandleJump(cpu, registers, linkReg, rs)
return
} }
if fun == 0xa { // movz 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 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 // lo and hi registers
// can write back // can write back
if fun >= 0x10 && fun < 0x1c { if fun >= 0x10 && fun < 0x1c {
return HandleHiLo(cpu, registers, fun, rs, rt, rdReg) 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 // write memory
if storeAddr != 0xFF_FF_FF_FF { if storeAddr != 0xFF_FF_FF_FF {
memTracker.TrackMemAccess(storeAddr) memTracker.TrackMemAccess(storeAddr)
memory.SetMemory(storeAddr, val) memory.SetMemory(storeAddr, val)
memUpdated = true
memAddr = storeAddr
} }
// write back the value to destination register // 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 { func ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
...@@ -272,10 +285,6 @@ 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) val := rt << (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8) mask := uint32(0xFFFFFFFF) << (24 - (rs&3)*8)
return (mem & ^mask) | val return (mem & ^mask) | val
case 0x30: // ll
return mem
case 0x38: // sc
return rt
default: default:
panic("invalid instruction") panic("invalid instruction")
} }
...@@ -382,6 +391,7 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, v ...@@ -382,6 +391,7 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]uint32, storeReg uint32, v
panic("invalid register") panic("invalid register")
} }
if storeReg != 0 && conditional { if storeReg != 0 && conditional {
// Register 0 is a special register that always holds a value of 0
registers[storeReg] = val registers[storeReg] = val
} }
cpu.PC = cpu.NextPC cpu.PC = cpu.NextPC
......
...@@ -187,7 +187,7 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) { ...@@ -187,7 +187,7 @@ func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
return v0, v1, newHeap return v0, v1, newHeap
} }
func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint32, preimageReader PreimageReader, memory *memory.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 // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code // returns: v0 = read, v1 = err code
v0 = uint32(0) v0 = uint32(0)
...@@ -215,6 +215,8 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -215,6 +215,8 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
binary.BigEndian.PutUint32(outMem[:], mem) binary.BigEndian.PutUint32(outMem[:], mem)
copy(outMem[alignment:], dat[:datLen]) copy(outMem[alignment:], dat[:datLen])
memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:])) memory.SetMemory(effAddr, binary.BigEndian.Uint32(outMem[:]))
memUpdated = true
memAddr = effAddr
newPreimageOffset += datLen newPreimageOffset += datLen
v0 = datLen v0 = datLen
//fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem) //fmt.Printf("read %d pre-image bytes, new offset: %d, eff addr: %08x mem: %08x\n", datLen, m.state.PreimageOffset, effAddr, outMem)
...@@ -226,7 +228,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3 ...@@ -226,7 +228,7 @@ func HandleSysRead(a0, a1, a2 uint32, preimageKey [32]byte, preimageOffset uint3
v1 = MipsEBADF 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) { 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 { ...@@ -75,8 +75,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil return nil
case exec.SysRead: case exec.SysRead:
var newPreimageOffset uint32 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 m.state.PreimageOffset = newPreimageOffset
if memUpdated {
m.handleMemoryUpdate(memAddr)
}
case exec.SysWrite: case exec.SysWrite:
var newLastHint hexutil.Bytes var newLastHint hexutil.Bytes
var newPreimageKey common.Hash var newPreimageKey common.Hash
...@@ -158,8 +163,10 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -158,8 +163,10 @@ func (m *InstrumentedState) handleSyscall() error {
effAddr := a1 & 0xFFffFFfc effAddr := a1 & 0xFFffFFfc
m.memoryTracker.TrackMemAccess(effAddr) m.memoryTracker.TrackMemAccess(effAddr)
m.state.Memory.SetMemory(effAddr, secs) m.state.Memory.SetMemory(effAddr, secs)
m.handleMemoryUpdate(effAddr)
m.memoryTracker.TrackMemAccess2(effAddr + 4) m.memoryTracker.TrackMemAccess2(effAddr + 4)
m.state.Memory.SetMemory(effAddr+4, nsecs) m.state.Memory.SetMemory(effAddr+4, nsecs)
m.handleMemoryUpdate(effAddr + 4)
default: default:
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL v1 = exec.MipsEINVAL
...@@ -286,8 +293,71 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -286,8 +293,71 @@ func (m *InstrumentedState) mipsStep() error {
return m.handleSyscall() 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 // 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) { func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) {
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/ethereum-optimism/optimism/cannon/serialize"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
...@@ -14,16 +13,20 @@ import ( ...@@ -14,16 +13,20 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "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. // STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
const STATE_WITNESS_SIZE = 163 const STATE_WITNESS_SIZE = 172
const ( const (
MEMROOT_WITNESS_OFFSET = 0 MEMROOT_WITNESS_OFFSET = 0
PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32 PREIMAGE_KEY_WITNESS_OFFSET = MEMROOT_WITNESS_OFFSET + 32
PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32 PREIMAGE_OFFSET_WITNESS_OFFSET = PREIMAGE_KEY_WITNESS_OFFSET + 32
HEAP_WITNESS_OFFSET = PREIMAGE_OFFSET_WITNESS_OFFSET + 4 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 EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1 STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8 STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
...@@ -40,7 +43,10 @@ type State struct { ...@@ -40,7 +43,10 @@ type State struct {
PreimageKey common.Hash PreimageKey common.Hash
PreimageOffset uint32 // note that the offset includes the 8-byte length prefix 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 ExitCode uint8
Exited bool Exited bool
...@@ -64,16 +70,19 @@ func CreateEmptyState() *State { ...@@ -64,16 +70,19 @@ func CreateEmptyState() *State {
initThread := CreateEmptyThread() initThread := CreateEmptyThread()
return &State{ return &State{
Memory: memory.NewMemory(), Memory: memory.NewMemory(),
Heap: 0, Heap: 0,
ExitCode: 0, LLReservationActive: false,
Exited: false, LLAddress: 0,
Step: 0, LLOwnerThread: 0,
Wakeup: exec.FutexEmptyAddr, ExitCode: 0,
TraverseRight: false, Exited: false,
LeftThreadStack: []*ThreadState{initThread}, Step: 0,
RightThreadStack: []*ThreadState{}, Wakeup: exec.FutexEmptyAddr,
NextThreadId: initThread.ThreadId + 1, TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{},
NextThreadId: initThread.ThreadId + 1,
} }
} }
...@@ -187,6 +196,9 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) { ...@@ -187,6 +196,9 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = append(out, s.PreimageKey[:]...) out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset) out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.Heap) out = binary.BigEndian.AppendUint32(out, s.Heap)
out = 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 = append(out, s.ExitCode)
out = mipsevm.AppendBoolToWitness(out, s.Exited) out = mipsevm.AppendBoolToWitness(out, s.Exited)
...@@ -264,6 +276,15 @@ func (s *State) Serialize(out io.Writer) error { ...@@ -264,6 +276,15 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.Heap); err != nil { if err := bout.WriteUInt(s.Heap); err != nil {
return err 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 { if err := bout.WriteUInt(s.ExitCode); err != nil {
return err return err
} }
...@@ -324,6 +345,15 @@ func (s *State) Deserialize(in io.Reader) error { ...@@ -324,6 +345,15 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.Heap); err != nil { if err := bin.ReadUInt(&s.Heap); err != nil {
return err 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 { if err := bin.ReadUInt(&s.ExitCode); err != nil {
return err return err
} }
......
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
...@@ -15,6 +14,7 @@ import ( ...@@ -15,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "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/program"
) )
...@@ -42,6 +42,8 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -42,6 +42,8 @@ func TestState_EncodeWitness(t *testing.T) {
} }
heap := uint32(12) heap := uint32(12)
llAddress := uint32(55)
llThreadOwner := uint32(99)
preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4}) preimageKey := crypto.Keccak256Hash([]byte{1, 2, 3, 4})
preimageOffset := uint32(24) preimageOffset := uint32(24)
step := uint64(33) step := uint64(33)
...@@ -53,6 +55,9 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -53,6 +55,9 @@ func TestState_EncodeWitness(t *testing.T) {
state.PreimageKey = preimageKey state.PreimageKey = preimageKey
state.PreimageOffset = preimageOffset state.PreimageOffset = preimageOffset
state.Heap = heap state.Heap = heap
state.LLReservationActive = true
state.LLAddress = llAddress
state.LLOwnerThread = llThreadOwner
state.Step = step state.Step = step
state.StepsSinceLastContextSwitch = stepsSinceContextSwitch state.StepsSinceLastContextSwitch = stepsSinceContextSwitch
...@@ -66,6 +71,9 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -66,6 +71,9 @@ func TestState_EncodeWitness(t *testing.T) {
setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:]) setWitnessField(expectedWitness, PREIMAGE_KEY_WITNESS_OFFSET, preimageKey[:])
setWitnessField(expectedWitness, PREIMAGE_OFFSET_WITNESS_OFFSET, []byte{0, 0, 0, byte(preimageOffset)}) 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, 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}) setWitnessField(expectedWitness, EXITCODE_WITNESS_OFFSET, []byte{c.exitCode})
if c.exited { if c.exited {
setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1}) setWitnessField(expectedWitness, EXITED_WITNESS_OFFSET, []byte{1})
...@@ -176,6 +184,9 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -176,6 +184,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
PreimageKey: common.Hash{0xFF}, PreimageKey: common.Hash{0xFF},
PreimageOffset: 5, PreimageOffset: 5,
Heap: 0xc0ffee, Heap: 0xc0ffee,
LLReservationActive: true,
LLAddress: 0x12345678,
LLOwnerThread: 0x02,
ExitCode: 1, ExitCode: 1,
Exited: true, Exited: true,
Step: 0xdeadbeef, Step: 0xdeadbeef,
......
...@@ -14,15 +14,18 @@ import ( ...@@ -14,15 +14,18 @@ import (
// ExpectedMTState is a test utility that basically stores a copy of a state that can be explicitly mutated // 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) // to define an expected post-state. The post-state is then validated with ExpectedMTState.Validate(t, postState)
type ExpectedMTState struct { type ExpectedMTState struct {
PreimageKey common.Hash PreimageKey common.Hash
PreimageOffset uint32 PreimageOffset uint32
Heap uint32 Heap uint32
ExitCode uint8 LLReservationActive bool
Exited bool LLAddress uint32
Step uint64 LLOwnerThread uint32
LastHint hexutil.Bytes ExitCode uint8
MemoryRoot common.Hash Exited bool
expectedMemory *memory.Memory Step uint64
LastHint hexutil.Bytes
MemoryRoot common.Hash
expectedMemory *memory.Memory
// Threading-related expectations // Threading-related expectations
StepsSinceLastContextSwitch uint64 StepsSinceLastContextSwitch uint64
Wakeup uint32 Wakeup uint32
...@@ -62,14 +65,17 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { ...@@ -62,14 +65,17 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
return &ExpectedMTState{ return &ExpectedMTState{
// General Fields // General Fields
PreimageKey: fromState.GetPreimageKey(), PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(), PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(), Heap: fromState.GetHeap(),
ExitCode: fromState.GetExitCode(), LLReservationActive: fromState.LLReservationActive,
Exited: fromState.GetExited(), LLAddress: fromState.LLAddress,
Step: fromState.GetStep(), LLOwnerThread: fromState.LLOwnerThread,
LastHint: fromState.GetLastHint(), ExitCode: fromState.GetExitCode(),
MemoryRoot: fromState.GetMemory().MerkleRoot(), Exited: fromState.GetExited(),
Step: fromState.GetStep(),
LastHint: fromState.GetLastHint(),
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields // Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch, StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup, Wakeup: fromState.Wakeup,
...@@ -119,7 +125,7 @@ func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) { ...@@ -119,7 +125,7 @@ func (e *ExpectedMTState) ExpectMemoryWrite(addr uint32, val uint32) {
func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) { func (e *ExpectedMTState) ExpectMemoryWriteMultiple(addr uint32, val uint32, addr2 uint32, val2 uint32) {
e.expectedMemory.SetMemory(addr, val) e.expectedMemory.SetMemory(addr, val)
e.expectedMemory.SetMemory(addr2, val) e.expectedMemory.SetMemory(addr2, val2)
e.MemoryRoot = e.expectedMemory.MerkleRoot() e.MemoryRoot = e.expectedMemory.MerkleRoot()
} }
...@@ -168,6 +174,9 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade ...@@ -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.PreimageKey, actualState.GetPreimageKey(), "Expect preimageKey = %v", e.PreimageKey)
require.Equalf(t, e.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset) 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.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.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.Exited, actualState.GetExited(), "Expect exited = %v", e.Exited)
require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step) require.Equalf(t, e.Step, actualState.GetStep(), "Expect step = %d", e.Step)
......
...@@ -28,6 +28,9 @@ func TestValidate_shouldCatchMutations(t *testing.T) { ...@@ -28,6 +28,9 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }}, {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: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }},
{name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 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: "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: "Exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Exited = !e.Exited }},
{name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }}, {name: "Step", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Step += 1 }},
......
package testutil package testutil
import ( import (
"math"
"math/rand" "math/rand"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -28,11 +29,19 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) { ...@@ -28,11 +29,19 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
m.state.PreimageKey = testutil.RandHash(r) m.state.PreimageKey = testutil.RandHash(r)
m.state.PreimageOffset = r.Uint32() m.state.PreimageOffset = r.Uint32()
m.state.Heap = r.Uint32()
m.state.Step = step m.state.Step = step
m.state.LastHint = testutil.RandHint(r) m.state.LastHint = testutil.RandHint(r)
m.state.StepsSinceLastContextSwitch = uint64(r.Intn(exec.SchedQuantum)) 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 // Randomize threads
activeStackThreads := r.Intn(2) + 1 activeStackThreads := r.Intn(2) + 1
inactiveStackThreads := r.Intn(3) inactiveStackThreads := r.Intn(3)
......
package singlethreaded package singlethreaded
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -30,7 +32,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -30,7 +32,7 @@ func (m *InstrumentedState) handleSyscall() error {
return nil return nil
case exec.SysRead: case exec.SysRead:
var newPreimageOffset uint32 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 m.state.PreimageOffset = newPreimageOffset
case exec.SysWrite: case exec.SysWrite:
var newLastHint hexutil.Bytes var newLastHint hexutil.Bytes
...@@ -62,6 +64,37 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -62,6 +64,37 @@ func (m *InstrumentedState) mipsStep() error {
return m.handleSyscall() 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 // 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 ...@@ -2,7 +2,6 @@ package tests
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"os" "os"
...@@ -13,14 +12,12 @@ import ( ...@@ -13,14 +12,12 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
) )
func TestEVM(t *testing.T) { func TestEVM(t *testing.T) {
...@@ -230,85 +227,6 @@ func TestEVM_MMap(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) { func TestEVMSysWriteHint(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
......
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 @@ ...@@ -140,12 +140,12 @@
"sourceCodeHash": "0x3f7bd622a788b8d00fe1631b14b761488eedccf56879f7ea2d610dd5ce81efbe" "sourceCodeHash": "0x3f7bd622a788b8d00fe1631b14b761488eedccf56879f7ea2d610dd5ce81efbe"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0x6add59adb849ec02e13b33df7efd439ca80f6a8ceefdf69ebcb0963c0167da23", "initCodeHash": "0x4043f262804931bbbbecff64f87f2d0bdc4554b4d0a8b22df8fff940e8d239bf",
"sourceCodeHash": "0xee1aef5a502f9491b7b83dab46ea2f0fc286f87ace31edcc1367c840d462bdfe" "sourceCodeHash": "0xba4674e1846afbbc708877332a38dfabd4b8d1e48ce07d8ebf0a45c9f27f16b0"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0xeab5f44d7fa7af1072f500c754bb55aa92409a79b2765a66efed47461c0c4049", "initCodeHash": "0xdaed5d70cc84a53f224c28f24f8eef26d5d53dfba9fdc4f1b28c3b231b974e53",
"sourceCodeHash": "0xcf180ff2cab8d0a34f3bb19d7145f5bf7f67dd379722ece765d09b38dcfed2f6" "sourceCodeHash": "0x4026eb7ae7b303ec4c3c2880e14e260dbcfc0b4290459bcd22994cfed8655f80"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b", "initCodeHash": "0x801e52f9c8439fcf7089575fa93272dfb874641dbfc7d82f36d979c987271c0b",
......
...@@ -69,5 +69,10 @@ ...@@ -69,5 +69,10 @@
"inputs": [], "inputs": [],
"name": "InvalidMemoryProof", "name": "InvalidMemoryProof",
"type": "error" "type": "error"
},
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -75,6 +75,11 @@ ...@@ -75,6 +75,11 @@
"name": "InvalidMemoryProof", "name": "InvalidMemoryProof",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "InvalidRMWInstruction",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "InvalidSecondMemoryProof", "name": "InvalidSecondMemoryProof",
......
...@@ -8,6 +8,7 @@ import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.s ...@@ -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 { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol"; import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol"; import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";
import { InvalidRMWInstruction } from "src/cannon/libraries/CannonErrors.sol";
/// @title MIPS /// @title MIPS
/// @notice The MIPS contract emulates a single MIPS instruction. /// @notice The MIPS contract emulates a single MIPS instruction.
...@@ -44,8 +45,8 @@ contract MIPS is ISemver { ...@@ -44,8 +45,8 @@ contract MIPS is ISemver {
} }
/// @notice The semantic version of the MIPS contract. /// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.1.1-beta.3 /// @custom:semver 1.1.1-beta.4
string public constant version = "1.1.1-beta.3"; string public constant version = "1.1.1-beta.4";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -178,7 +179,7 @@ contract MIPS is ISemver { ...@@ -178,7 +179,7 @@ contract MIPS is ISemver {
proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
memRoot: state.memRoot 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) { } else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0, _a0: a0,
...@@ -291,23 +292,59 @@ contract MIPS is ISemver { ...@@ -291,23 +292,59 @@ contract MIPS is ISemver {
return handleSyscall(_localContext); 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 // Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(state); st.CpuScalars memory cpu = getCpuScalars(state);
(state.memRoot) = ins.execMipsCoreStepLogic({ ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
_cpu: cpu, cpu: cpu,
_registers: state.registers, registers: state.registers,
_memRoot: state.memRoot, memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1), memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
_insn: insn, insn: insn,
_opcode: opcode, opcode: opcode,
_fun: fun fun: fun
}); });
(state.memRoot,,) = ins.execMipsCoreStepLogic(coreStepArgs);
setStateCpuScalars(state, cpu); setStateCpuScalars(state, cpu);
return outputState(); 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) { 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 }); 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"; ...@@ -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 { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol"; import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol";
import { VMStatuses } from "src/dispute/lib/Types.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 /// @title MIPS2
/// @notice The MIPS2 contract emulates a single MIPS instruction. /// @notice The MIPS2 contract emulates a single MIPS instruction.
...@@ -33,13 +35,16 @@ contract MIPS2 is ISemver { ...@@ -33,13 +35,16 @@ contract MIPS2 is ISemver {
} }
/// @notice Stores the VM state. /// @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. /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State { struct State {
bytes32 memRoot; bytes32 memRoot;
bytes32 preimageKey; bytes32 preimageKey;
uint32 preimageOffset; uint32 preimageOffset;
uint32 heap; uint32 heap;
bool llReservationActive;
uint32 llAddress;
uint32 llOwnerThread;
uint8 exitCode; uint8 exitCode;
bool exited; bool exited;
uint64 step; uint64 step;
...@@ -52,8 +57,8 @@ contract MIPS2 is ISemver { ...@@ -52,8 +57,8 @@ contract MIPS2 is ISemver {
} }
/// @notice The semantic version of the MIPS2 contract. /// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.8 /// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.8"; string public constant version = "1.0.0-beta.9";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -71,7 +76,7 @@ contract MIPS2 is ISemver { ...@@ -71,7 +76,7 @@ contract MIPS2 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80; uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step // 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. /// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) { constructor(IPreimageOracle _oracle) {
...@@ -108,8 +113,8 @@ contract MIPS2 is ISemver { ...@@ -108,8 +113,8 @@ contract MIPS2 is ISemver {
// expected thread mem offset check // expected thread mem offset check
revert(0, 0) revert(0, 0)
} }
if iszero(eq(mload(0x40), shl(5, 60))) { if iszero(eq(mload(0x40), shl(5, 63))) {
// 4 + 13 state slots + 43 thread slots = 60 expected memory check // 4 + 16 state slots + 43 thread slots = 63 expected memory check
revert(0, 0) revert(0, 0)
} }
if iszero(eq(_stateData.offset, 132)) { if iszero(eq(_stateData.offset, 132)) {
...@@ -136,6 +141,9 @@ contract MIPS2 is ISemver { ...@@ -136,6 +141,9 @@ contract MIPS2 is ISemver {
c, m := putField(c, m, 32) // preimageKey c, m := putField(c, m, 32) // preimageKey
c, m := putField(c, m, 4) // preimageOffset c, m := putField(c, m, 4) // preimageOffset
c, m := putField(c, m, 4) // heap 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) // exitCode
c, m := putField(c, m, 1) // exited c, m := putField(c, m, 1) // exited
exited := mload(sub(m, 32)) exited := mload(sub(m, 32))
...@@ -228,19 +236,95 @@ contract MIPS2 is ISemver { ...@@ -228,19 +236,95 @@ contract MIPS2 is ISemver {
return handleSyscall(_localContext); 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 // Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(thread); st.CpuScalars memory cpu = getCpuScalars(thread);
(state.memRoot) = ins.execMipsCoreStepLogic({ ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
_cpu: cpu, cpu: cpu,
_registers: thread.registers, registers: thread.registers,
_memRoot: state.memRoot, memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
_insn: insn, insn: insn,
_opcode: opcode, opcode: opcode,
_fun: fun fun: fun
}); });
bool memUpdated;
uint32 memAddr;
(state.memRoot, memUpdated, memAddr) = ins.execMipsCoreStepLogic(coreStepArgs);
setStateCpuScalars(thread, cpu); setStateCpuScalars(thread, cpu);
updateCurrentThreadRoot(); 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(); return outputState();
} }
} }
...@@ -319,7 +403,8 @@ contract MIPS2 is ISemver { ...@@ -319,7 +403,8 @@ contract MIPS2 is ISemver {
proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
memRoot: state.memRoot 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) { } else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({ (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0, _a0: a0,
...@@ -412,6 +497,7 @@ contract MIPS2 is ISemver { ...@@ -412,6 +497,7 @@ contract MIPS2 is ISemver {
// Recompute the new root after updating effAddr // Recompute the new root after updating effAddr
state.memRoot = state.memRoot =
MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs); MIPSMemory.writeMem(effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1), secs);
handleMemoryUpdate(state, effAddr);
// Verify the second memory proof against the newly computed root // Verify the second memory proof against the newly computed root
if ( if (
!MIPSMemory.isValidProof( !MIPSMemory.isValidProof(
...@@ -422,6 +508,7 @@ contract MIPS2 is ISemver { ...@@ -422,6 +508,7 @@ contract MIPS2 is ISemver {
} }
state.memRoot = state.memRoot =
MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs); MIPSMemory.writeMem(effAddr + 4, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 2), nsecs);
handleMemoryUpdate(state, effAddr + 4);
} else { } else {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL; v1 = sys.EINVAL;
...@@ -502,6 +589,22 @@ contract MIPS2 is ISemver { ...@@ -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. /// @notice Computes the hash of the MIPS state.
/// @return out_ The hashed MIPS state. /// @return out_ The hashed MIPS state.
function outputState() internal returns (bytes32 out_) { function outputState() internal returns (bytes32 out_) {
...@@ -526,6 +629,9 @@ contract MIPS2 is ISemver { ...@@ -526,6 +629,9 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 32) // preimageKey from, to := copyMem(from, to, 32) // preimageKey
from, to := copyMem(from, to, 4) // preimageOffset from, to := copyMem(from, to, 4) // preimageOffset
from, to := copyMem(from, to, 4) // heap 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) let exitCode := mload(from)
from, to := copyMem(from, to, 1) // exitCode from, to := copyMem(from, to, 1) // exitCode
exited := mload(from) exited := mload(from)
......
...@@ -8,6 +8,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; ...@@ -8,6 +8,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @notice Interface for the MIPS contract. /// @notice Interface for the MIPS contract.
interface IMIPS is ISemver { interface IMIPS is ISemver {
error InvalidMemoryProof(); error InvalidMemoryProof();
error InvalidRMWInstruction();
function oracle() external view returns (IPreimageOracle oracle_); function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
......
...@@ -10,6 +10,7 @@ interface IMIPS2 is ISemver { ...@@ -10,6 +10,7 @@ interface IMIPS2 is ISemver {
error InvalidExitedValue(); error InvalidExitedValue();
error InvalidMemoryProof(); error InvalidMemoryProof();
error InvalidSecondMemoryProof(); error InvalidSecondMemoryProof();
error InvalidRMWInstruction();
function oracle() external view returns (IPreimageOracle oracle_); function oracle() external view returns (IPreimageOracle oracle_);
function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32); function step(bytes memory _stateData, bytes memory _proof, bytes32 _localContext) external returns (bytes32);
......
...@@ -60,3 +60,6 @@ error InvalidMemoryProof(); ...@@ -60,3 +60,6 @@ error InvalidMemoryProof();
/// @notice Thrown when the second memory location is invalid /// @notice Thrown when the second memory location is invalid
error InvalidSecondMemoryProof(); error InvalidSecondMemoryProof();
/// @notice Thrown when an RMW instruction is expected, but a different instruction is provided.
error InvalidRMWInstruction();
...@@ -202,13 +202,22 @@ library MIPSSyscalls { ...@@ -202,13 +202,22 @@ library MIPSSyscalls {
function handleSysRead(SysReadParams memory _args) function handleSysRead(SysReadParams memory _args)
internal internal
view view
returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_) returns (
uint32 v0_,
uint32 v1_,
uint32 newPreimageOffset_,
bytes32 newMemRoot_,
bool memUpdated_,
uint32 memAddr_
)
{ {
unchecked { unchecked {
v0_ = uint32(0); v0_ = uint32(0);
v1_ = uint32(0); v1_ = uint32(0);
newMemRoot_ = _args.memRoot; newMemRoot_ = _args.memRoot;
newPreimageOffset_ = _args.preimageOffset; newPreimageOffset_ = _args.preimageOffset;
memUpdated_ = false;
memAddr_ = 0;
// args: _a0 = fd, _a1 = addr, _a2 = count // args: _a0 = fd, _a1 = addr, _a2 = count
// returns: v0_ = read, v1_ = err code // returns: v0_ = read, v1_ = err code
...@@ -217,9 +226,10 @@ library MIPSSyscalls { ...@@ -217,9 +226,10 @@ library MIPSSyscalls {
} }
// pre-image oracle read // pre-image oracle read
else if (_args.a0 == FD_PREIMAGE_READ) { else if (_args.a0 == FD_PREIMAGE_READ) {
uint32 effAddr = _args.a1 & 0xFFffFFfc;
// verify proof is correct, and get the existing memory. // verify proof is correct, and get the existing memory.
// mask the addr to align it to 4 bytes // 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 the preimage key is a local key, localize it in the context of the caller.
if (uint8(_args.preimageKey[0]) == 1) { if (uint8(_args.preimageKey[0]) == 1) {
_args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext); _args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext);
...@@ -246,7 +256,9 @@ library MIPSSyscalls { ...@@ -246,7 +256,9 @@ library MIPSSyscalls {
} }
// Write memory back // 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); newPreimageOffset_ += uint32(datLen);
v0_ = uint32(datLen); v0_ = uint32(datLen);
} }
...@@ -260,7 +272,7 @@ library MIPSSyscalls { ...@@ -260,7 +272,7 @@ library MIPSSyscalls {
v1_ = EBADF; v1_ = EBADF;
} }
return (v0_, v1_, newPreimageOffset_, newMemRoot_); return (v0_, v1_, newPreimageOffset_, newMemRoot_, memUpdated_, memAddr_);
} }
} }
......
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