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"
"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();
......@@ -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_);
}
}
......
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