Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions cannon/mipsevm/exec/mips_instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions cannon/mipsevm/exec/mips_syscalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand Down
74 changes: 72 additions & 2 deletions cannon/mipsevm/multithreaded/mips.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
mbaxter marked this conversation as resolved.
Show resolved Hide resolved
case exec.SysWrite:
var newLastHint hexutil.Bytes
var newPreimageKey common.Hash
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
58 changes: 44 additions & 14 deletions cannon/mipsevm/multithreaded/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
Loading