Commit cc0959d8 authored by mbaxter's avatar mbaxter Committed by GitHub

cannon: Rework RMW ops for 64-bit compatibility (#12419)

* cannon: Use 4-byte alignment for memory.SetUint32

* cannon: For now, bypass EVM check for 64-bit tests

* cannon: Rework rmw ops for Cannon64 compatibility

TODO: Fix MIPS 64-bit tests

* cannon: Add some comments

* cannon: Make TestEVM_MT_LL 64-bit compatible

* cannon: Make SC tests 64-bit compatible

* cannon: Clean up unused test param

* cannon: Add 64-bit tests of ll/sc ops, fix sign extension

* cannon: Add tests for lld, scd ops

* cannon: Rework test utils

* cannon: Update state field LLReservationActive to LLReservationStatus

Use the status field to track what type of reservation was made.

* cannon: Update MIPS2 version, run semver lock

* cannon: Rename go var to match solidity

* cannon: Tweak test descriptions

* Revert "cannon: Use 4-byte alignment for memory.SetUint32"

This reverts commit ce9abdddd66a8567eb000e0b18a161762d86b374.

* cannon: Rework 64-bit compatibility in tests

Also, clean-up pc setting

* cannon: Add test cases that clear unaligned LLAddress reservation

* Fix testcase descriptions
Co-authored-by: default avatarInphi <mlaw2501@gmail.com>

---------
Co-authored-by: default avatarInphi <mlaw2501@gmail.com>
parent 527bf6cd
...@@ -569,3 +569,47 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]Word, storeReg Word, val W ...@@ -569,3 +569,47 @@ func HandleRd(cpu *mipsevm.CpuScalars, registers *[32]Word, storeReg Word, val W
cpu.NextPC = cpu.NextPC + 4 cpu.NextPC = cpu.NextPC + 4
return nil return nil
} }
func LoadSubWord(memory *memory.Memory, addr Word, byteLength Word, signExtend bool, memoryTracker MemTracker) Word {
// Pull data from memory
effAddr := (addr) & arch.AddressMask
memoryTracker.TrackMemAccess(effAddr)
mem := memory.GetWord(effAddr)
// Extract a sub-word based on the low-order bits in addr
dataMask, bitOffset, bitLength := calculateSubWordMaskAndOffset(addr, byteLength)
retVal := (mem >> bitOffset) & dataMask
if signExtend {
retVal = SignExtend(retVal, bitLength)
}
return retVal
}
func StoreSubWord(memory *memory.Memory, addr Word, byteLength Word, value Word, memoryTracker MemTracker) {
// Pull data from memory
effAddr := (addr) & arch.AddressMask
memoryTracker.TrackMemAccess(effAddr)
mem := memory.GetWord(effAddr)
// Modify isolated sub-word within mem
dataMask, bitOffset, _ := calculateSubWordMaskAndOffset(addr, byteLength)
subWordValue := dataMask & value
memUpdateMask := dataMask << bitOffset
newMemVal := subWordValue<<bitOffset | (^memUpdateMask)&mem
memory.SetWord(effAddr, newMemVal)
}
func calculateSubWordMaskAndOffset(addr Word, byteLength Word) (dataMask, bitOffset, bitLength Word) {
bitLength = byteLength << 3
dataMask = ^Word(0) >> (arch.WordSize - bitLength)
// Figure out sub-word index based on the low-order bits in addr
byteIndexMask := addr & arch.ExtMask & ^(byteLength - 1)
maxByteShift := arch.WordSizeBytes - byteLength
byteIndex := addr & byteIndexMask
bitOffset = (maxByteShift - byteIndex) << 3
return dataMask, bitOffset, bitLength
}
// These tests target architectures that are 32-bit or larger
package exec
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
// TestLoadSubWord_32bits validates LoadSubWord with 32-bit offsets (up to 3 bytes)
func TestLoadSubWord_32bits(t *testing.T) {
cases := []struct {
name string
byteLength Word
addr uint32
memVal uint32
signExtend bool
shouldSignExtend bool
expectedValue uint32
}{
{name: "32-bit", byteLength: 4, addr: 0xFF00_0000, memVal: 0x1234_5678, expectedValue: 0x1234_5678},
{name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0001, memVal: 0x1234_5678, expectedValue: 0x1234_5678},
{name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0002, memVal: 0x1234_5678, expectedValue: 0x1234_5678},
{name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0003, memVal: 0x1234_5678, expectedValue: 0x1234_5678},
{name: "16-bit, offset=0", byteLength: 2, addr: 0x00, memVal: 0x1234_5678, expectedValue: 0x1234},
{name: "16-bit, offset=0, extra bit set", byteLength: 2, addr: 0x01, memVal: 0x1234_5678, expectedValue: 0x1234},
{name: "16-bit, offset=2", byteLength: 2, addr: 0x02, memVal: 0x1234_5678, expectedValue: 0x5678},
{name: "16-bit, offset=2, extra bit set", byteLength: 2, addr: 0x03, memVal: 0x1234_5678, expectedValue: 0x5678},
{name: "16-bit, sign extend positive val", byteLength: 2, addr: 0x02, memVal: 0x1234_5678, expectedValue: 0x5678, signExtend: true, shouldSignExtend: false},
{name: "16-bit, sign extend negative val", byteLength: 2, addr: 0x02, memVal: 0x1234_F678, expectedValue: 0xFFFF_F678, signExtend: true, shouldSignExtend: true},
{name: "16-bit, do not sign extend negative val", byteLength: 2, addr: 0x02, memVal: 0x1234_F678, expectedValue: 0xF678, signExtend: false},
{name: "8-bit, offset=0", byteLength: 1, addr: 0x1230, memVal: 0x1234_5678, expectedValue: 0x12},
{name: "8-bit, offset=1", byteLength: 1, addr: 0x1231, memVal: 0x1234_5678, expectedValue: 0x34},
{name: "8-bit, offset=2", byteLength: 1, addr: 0x1232, memVal: 0x1234_5678, expectedValue: 0x56},
{name: "8-bit, offset=3", byteLength: 1, addr: 0x1233, memVal: 0x1234_5678, expectedValue: 0x78},
{name: "8-bit, sign extend positive", byteLength: 1, addr: 0x1233, memVal: 0x1234_5678, expectedValue: 0x78, signExtend: true, shouldSignExtend: false},
{name: "8-bit, sign extend negative", byteLength: 1, addr: 0x1233, memVal: 0x1234_5688, expectedValue: 0xFFFF_FF88, signExtend: true, shouldSignExtend: true},
{name: "8-bit, do not sign extend neg value", byteLength: 1, addr: 0x1233, memVal: 0x1234_5688, expectedValue: 0x88, signExtend: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
mem := memory.NewMemory()
memTracker := NewMemoryTracker(mem)
effAddr := Word(c.addr) & arch.AddressMask
// Shift memval for consistency across architectures
memVal := Word(c.memVal) << (arch.WordSize - 32)
mem.SetWord(effAddr, memVal)
retVal := LoadSubWord(mem, Word(c.addr), c.byteLength, c.signExtend, memTracker)
// If sign extending, make sure retVal is consistent across architectures
expected := Word(c.expectedValue)
if c.shouldSignExtend {
signedBits := ^Word(0xFFFF_FFFF)
expected = expected | signedBits
}
require.Equal(t, expected, retVal)
})
}
}
// TestStoreSubWord_32bits validates LoadSubWord with 32-bit offsets (up to 3 bytes)
func TestStoreSubWord_32bits(t *testing.T) {
memVal := 0xFFFF_FFFF
value := 0x1234_5678
cases := []struct {
name string
byteLength Word
addr uint32
expectedValue uint32
}{
{name: "32-bit", byteLength: 4, addr: 0xFF00_0000, expectedValue: 0x1234_5678},
{name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0001, expectedValue: 0x1234_5678},
{name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0002, expectedValue: 0x1234_5678},
{name: "32-bit, extra bits", byteLength: 4, addr: 0xFF00_0003, expectedValue: 0x1234_5678},
{name: "16-bit, subword offset=0", byteLength: 2, addr: 0x00, expectedValue: 0x5678_FFFF},
{name: "16-bit, subword offset=0, extra bit set", byteLength: 2, addr: 0x01, expectedValue: 0x5678_FFFF},
{name: "16-bit, subword offset=2", byteLength: 2, addr: 0x02, expectedValue: 0xFFFF_5678},
{name: "16-bit, subword offset=2, extra bit set", byteLength: 2, addr: 0x03, expectedValue: 0xFFFF_5678},
{name: "8-bit, offset=0", byteLength: 1, addr: 0x1230, expectedValue: 0x78FF_FFFF},
{name: "8-bit, offset=1", byteLength: 1, addr: 0x1231, expectedValue: 0xFF78_FFFF},
{name: "8-bit, offset=2", byteLength: 1, addr: 0x1232, expectedValue: 0xFFFF_78FF},
{name: "8-bit, offset=3", byteLength: 1, addr: 0x1233, expectedValue: 0xFFFF_FF78},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
mem := memory.NewMemory()
memTracker := NewMemoryTracker(mem)
effAddr := Word(c.addr) & arch.AddressMask
// Shift memval for consistency across architectures
memVal := Word(memVal) << (arch.WordSize - 32)
mem.SetWord(effAddr, memVal)
StoreSubWord(mem, Word(c.addr), c.byteLength, Word(value), memTracker)
newMemVal := mem.GetWord(effAddr)
// Make sure expectation is consistent across architectures
expected := Word(c.expectedValue) << (arch.WordSize - 32)
require.Equal(t, expected, newMemVal)
})
}
}
//go:build cannon64
// +build cannon64
// These tests target architectures that are 64-bit or larger
package exec
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
// TestLoadSubWord_64bits extends TestLoadSubWord_32bits by testing up to 64-bits (7 byte) offsets
func TestLoadSubWord_64bits(t *testing.T) {
memVal := uint64(0x1234_5678_9876_5432)
cases := []struct {
name string
byteLength Word
addr uint64
memVal uint64
signExtend bool
expectedValue uint64
}{
{name: "64-bit", byteLength: 8, addr: 0xFF00_0000, memVal: 0x8234_5678_9876_5432, expectedValue: 0x8234_5678_9876_5432},
{name: "64-bit w sign extension", byteLength: 8, addr: 0xFF00_0000, memVal: 0x8234_5678_9876_5432, expectedValue: 0x8234_5678_9876_5432, signExtend: true},
{name: "32-bit, offset=0", byteLength: 4, addr: 0xFF00_0000, memVal: memVal, expectedValue: 0x1234_5678},
{name: "32-bit, offset=0, extra bits", byteLength: 4, addr: 0xFF00_0001, memVal: memVal, expectedValue: 0x1234_5678},
{name: "32-bit, offset=0, extra bits", byteLength: 4, addr: 0xFF00_0002, memVal: memVal, expectedValue: 0x1234_5678},
{name: "32-bit, offset=0, extra bits", byteLength: 4, addr: 0xFF00_0003, memVal: memVal, expectedValue: 0x1234_5678},
{name: "32-bit, offset=4", byteLength: 4, addr: 0xFF00_0004, memVal: memVal, expectedValue: 0x9876_5432},
{name: "32-bit, offset=4, extra bits", byteLength: 4, addr: 0xFF00_0005, memVal: memVal, expectedValue: 0x9876_5432},
{name: "32-bit, offset=4, extra bits", byteLength: 4, addr: 0xFF00_0006, memVal: memVal, expectedValue: 0x9876_5432},
{name: "32-bit, offset=4, extra bits", byteLength: 4, addr: 0xFF00_0007, memVal: memVal, expectedValue: 0x9876_5432},
{name: "32-bit, sign extend negative", byteLength: 4, addr: 0xFF00_0006, memVal: 0x1234_5678_F1E2_A1B1, expectedValue: 0xFFFF_FFFF_F1E2_A1B1, signExtend: true},
{name: "32-bit, sign extend positive", byteLength: 4, addr: 0xFF00_0007, memVal: 0x1234_5678_7876_5432, expectedValue: 0x7876_5432, signExtend: true},
{name: "16-bit, subword offset=4", byteLength: 2, addr: 0x04, memVal: memVal, expectedValue: 0x9876},
{name: "16-bit, subword offset=4, extra bit set", byteLength: 2, addr: 0x05, memVal: memVal, expectedValue: 0x9876},
{name: "16-bit, subword offset=6", byteLength: 2, addr: 0x06, memVal: memVal, expectedValue: 0x5432},
{name: "16-bit, subword offset=6, extra bit set", byteLength: 2, addr: 0x07, memVal: memVal, expectedValue: 0x5432},
{name: "16-bit, sign extend negative val", byteLength: 2, addr: 0x04, memVal: 0x1234_5678_8BEE_CCDD, expectedValue: 0xFFFF_FFFF_FFFF_8BEE, signExtend: true},
{name: "16-bit, sign extend positive val", byteLength: 2, addr: 0x04, memVal: 0x1234_5678_7876_5432, expectedValue: 0x7876, signExtend: true},
{name: "8-bit, offset=4", byteLength: 1, addr: 0x1234, memVal: memVal, expectedValue: 0x98},
{name: "8-bit, offset=5", byteLength: 1, addr: 0x1235, memVal: memVal, expectedValue: 0x76},
{name: "8-bit, offset=6", byteLength: 1, addr: 0x1236, memVal: memVal, expectedValue: 0x54},
{name: "8-bit, offset=7", byteLength: 1, addr: 0x1237, memVal: memVal, expectedValue: 0x32},
{name: "8-bit, sign extend positive", byteLength: 1, addr: 0x1237, memVal: memVal, expectedValue: 0x32, signExtend: true},
{name: "8-bit, sign extend negative", byteLength: 1, addr: 0x1237, memVal: 0x1234_5678_8764_4381, expectedValue: 0xFFFF_FFFF_FFFF_FF81, signExtend: true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
mem := memory.NewMemory()
memTracker := NewMemoryTracker(mem)
effAddr := Word(c.addr) & arch.AddressMask
mem.SetWord(effAddr, c.memVal)
retVal := LoadSubWord(mem, Word(c.addr), c.byteLength, c.signExtend, memTracker)
require.Equal(t, c.expectedValue, retVal)
})
}
}
// TestStoreSubWord_64bits extends TestStoreSubWord_32bits by testing up to 64-bits (7 byte) offsets
func TestStoreSubWord_64bits(t *testing.T) {
memVal := uint64(0xFFFF_FFFF_FFFF_FFFF)
value := uint64(0x1234_5678_9876_5432)
cases := []struct {
name string
byteLength Word
addr uint64
expectedValue uint64
}{
{name: "64-bit", byteLength: 8, addr: 0xFF00_0000, expectedValue: value},
{name: "32-bit, offset 0", byteLength: 4, addr: 0xFF00_0000, expectedValue: 0x9876_5432_FFFF_FFFF},
{name: "32-bit, offset 0, extra addr bits", byteLength: 4, addr: 0xFF00_0001, expectedValue: 0x9876_5432_FFFF_FFFF},
{name: "32-bit, offset 0, extra addr bits", byteLength: 4, addr: 0xFF00_0002, expectedValue: 0x9876_5432_FFFF_FFFF},
{name: "32-bit, offset 0, extra addr bits", byteLength: 4, addr: 0xFF00_0003, expectedValue: 0x9876_5432_FFFF_FFFF},
{name: "32-bit, offset 4", byteLength: 4, addr: 0xFF00_0004, expectedValue: 0xFFFF_FFFF_9876_5432},
{name: "32-bit, offset 4, extra addr bits", byteLength: 4, addr: 0xFF00_0005, expectedValue: 0xFFFF_FFFF_9876_5432},
{name: "32-bit, offset 4, extra addr bits", byteLength: 4, addr: 0xFF00_0006, expectedValue: 0xFFFF_FFFF_9876_5432},
{name: "32-bit, offset 4, extra addr bits", byteLength: 4, addr: 0xFF00_0007, expectedValue: 0xFFFF_FFFF_9876_5432},
{name: "16-bit, offset=4", byteLength: 2, addr: 0x04, expectedValue: 0xFFFF_FFFF_5432_FFFF},
{name: "16-bit, offset=4, extra bit set", byteLength: 2, addr: 0x05, expectedValue: 0xFFFF_FFFF_5432_FFFF},
{name: "16-bit, offset=6", byteLength: 2, addr: 0x06, expectedValue: 0xFFFF_FFFF_FFFF_5432},
{name: "16-bit, offset=6, extra bit set", byteLength: 2, addr: 0x07, expectedValue: 0xFFFF_FFFF_FFFF_5432},
{name: "8-bit, offset=4", byteLength: 1, addr: 0x1234, expectedValue: 0xFFFF_FFFF_32FF_FFFF},
{name: "8-bit, offset=5", byteLength: 1, addr: 0x1235, expectedValue: 0xFFFF_FFFF_FF32_FFFF},
{name: "8-bit, offset=6", byteLength: 1, addr: 0x1236, expectedValue: 0xFFFF_FFFF_FFFF_32FF},
{name: "8-bit, offset=7", byteLength: 1, addr: 0x1237, expectedValue: 0xFFFF_FFFF_FFFF_FF32},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
mem := memory.NewMemory()
memTracker := NewMemoryTracker(mem)
effAddr := Word(c.addr) & arch.AddressMask
mem.SetWord(effAddr, memVal)
StoreSubWord(mem, Word(c.addr), c.byteLength, Word(value), memTracker)
newMemVal := mem.GetWord(effAddr)
require.Equal(t, c.expectedValue, newMemVal)
})
}
}
...@@ -325,14 +325,14 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -325,14 +325,14 @@ func (m *InstrumentedState) mipsStep() error {
} }
func (m *InstrumentedState) handleMemoryUpdate(memAddr Word) { func (m *InstrumentedState) handleMemoryUpdate(memAddr Word) {
if memAddr == m.state.LLAddress { if memAddr == (arch.AddressMask & m.state.LLAddress) {
// Reserved address was modified, clear the reservation // Reserved address was modified, clear the reservation
m.clearLLMemoryReservation() m.clearLLMemoryReservation()
} }
} }
func (m *InstrumentedState) clearLLMemoryReservation() { func (m *InstrumentedState) clearLLMemoryReservation() {
m.state.LLReservationActive = false m.state.LLReservationStatus = LLStatusNone
m.state.LLAddress = 0 m.state.LLAddress = 0
m.state.LLOwnerThread = 0 m.state.LLOwnerThread = 0
} }
...@@ -343,36 +343,40 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error { ...@@ -343,36 +343,40 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
base := m.state.GetRegistersRef()[baseReg] base := m.state.GetRegistersRef()[baseReg]
rtReg := Word((insn >> 16) & 0x1F) rtReg := Word((insn >> 16) & 0x1F)
offset := exec.SignExtendImmediate(insn) offset := exec.SignExtendImmediate(insn)
addr := base + offset
effAddr := (base + offset) & arch.AddressMask // Determine some opcode-specific parameters
m.memoryTracker.TrackMemAccess(effAddr) targetStatus := LLStatusActive32bit
mem := m.state.Memory.GetWord(effAddr) byteLength := Word(4)
if opcode == exec.OpLoadLinked64 || opcode == exec.OpStoreConditional64 {
// Use 64-bit params
targetStatus = LLStatusActive64bit
byteLength = Word(8)
}
var retVal Word var retVal Word
threadId := m.state.GetCurrentThread().ThreadId threadId := m.state.GetCurrentThread().ThreadId
if opcode == exec.OpLoadLinked || opcode == exec.OpLoadLinked64 { switch opcode {
retVal = mem case exec.OpLoadLinked, exec.OpLoadLinked64:
m.state.LLReservationActive = true retVal = exec.LoadSubWord(m.state.GetMemory(), addr, byteLength, true, m.memoryTracker)
m.state.LLAddress = effAddr
m.state.LLReservationStatus = targetStatus
m.state.LLAddress = addr
m.state.LLOwnerThread = threadId m.state.LLOwnerThread = threadId
} else if opcode == exec.OpStoreConditional || opcode == exec.OpStoreConditional64 { case exec.OpStoreConditional, exec.OpStoreConditional64:
// TODO(#12205): Determine bits affected by coherence stores on 64-bits if m.state.LLReservationStatus == targetStatus && m.state.LLOwnerThread == threadId && m.state.LLAddress == addr {
// 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 // Complete atomic update: set memory and return 1 for success
m.clearLLMemoryReservation() m.clearLLMemoryReservation()
rt := m.state.GetRegistersRef()[rtReg]
if opcode == exec.OpStoreConditional { val := m.state.GetRegistersRef()[rtReg]
m.state.Memory.SetUint32(effAddr, uint32(rt)) exec.StoreSubWord(m.state.GetMemory(), addr, byteLength, val, m.memoryTracker)
} else {
m.state.Memory.SetWord(effAddr, rt)
}
retVal = 1 retVal = 1
} else { } else {
// Atomic update failed, return 0 for failure // Atomic update failed, return 0 for failure
retVal = 0 retVal = 0
} }
} else { default:
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode)) panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
} }
......
...@@ -40,16 +40,24 @@ const ( ...@@ -40,16 +40,24 @@ const (
STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes
) )
type LLReservationStatus uint8
const (
LLStatusNone LLReservationStatus = 0x0
LLStatusActive32bit LLReservationStatus = 0x1
LLStatusActive64bit LLReservationStatus = 0x2
)
type State struct { type State struct {
Memory *memory.Memory Memory *memory.Memory
PreimageKey common.Hash PreimageKey common.Hash
PreimageOffset Word // note that the offset includes the 8-byte length prefix PreimageOffset Word // note that the offset includes the 8-byte length prefix
Heap Word // to handle mmap growth Heap Word // to handle mmap growth
LLReservationActive bool // Whether there is an active memory reservation initiated via the LL (load linked) op LLReservationStatus LLReservationStatus // Determines whether there is an active memory reservation, and what type
LLAddress Word // The "linked" memory address reserved via the LL (load linked) op LLAddress Word // The "linked" memory address reserved via the LL (load linked) op
LLOwnerThread Word // The id of the thread that holds the reservation on LLAddress LLOwnerThread Word // The id of the thread that holds the reservation on LLAddress
ExitCode uint8 ExitCode uint8
Exited bool Exited bool
...@@ -75,7 +83,7 @@ func CreateEmptyState() *State { ...@@ -75,7 +83,7 @@ func CreateEmptyState() *State {
return &State{ return &State{
Memory: memory.NewMemory(), Memory: memory.NewMemory(),
Heap: 0, Heap: 0,
LLReservationActive: false, LLReservationStatus: LLStatusNone,
LLAddress: 0, LLAddress: 0,
LLOwnerThread: 0, LLOwnerThread: 0,
ExitCode: 0, ExitCode: 0,
...@@ -199,7 +207,7 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) { ...@@ -199,7 +207,7 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = append(out, s.PreimageKey[:]...) out = append(out, s.PreimageKey[:]...)
out = arch.ByteOrderWord.AppendWord(out, s.PreimageOffset) out = arch.ByteOrderWord.AppendWord(out, s.PreimageOffset)
out = arch.ByteOrderWord.AppendWord(out, s.Heap) out = arch.ByteOrderWord.AppendWord(out, s.Heap)
out = mipsevm.AppendBoolToWitness(out, s.LLReservationActive) out = append(out, byte(s.LLReservationStatus))
out = arch.ByteOrderWord.AppendWord(out, s.LLAddress) out = arch.ByteOrderWord.AppendWord(out, s.LLAddress)
out = arch.ByteOrderWord.AppendWord(out, s.LLOwnerThread) out = arch.ByteOrderWord.AppendWord(out, s.LLOwnerThread)
out = append(out, s.ExitCode) out = append(out, s.ExitCode)
...@@ -278,7 +286,7 @@ func (s *State) Serialize(out io.Writer) error { ...@@ -278,7 +286,7 @@ 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 { if err := bout.WriteUInt(s.LLReservationStatus); err != nil {
return err return err
} }
if err := bout.WriteUInt(s.LLAddress); err != nil { if err := bout.WriteUInt(s.LLAddress); err != nil {
...@@ -347,7 +355,7 @@ func (s *State) Deserialize(in io.Reader) error { ...@@ -347,7 +355,7 @@ 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 { if err := bin.ReadUInt(&s.LLReservationStatus); err != nil {
return err return err
} }
if err := bin.ReadUInt(&s.LLAddress); err != nil { if err := bin.ReadUInt(&s.LLAddress); err != nil {
......
...@@ -56,7 +56,7 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -56,7 +56,7 @@ 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.LLReservationStatus = LLStatusActive32bit
state.LLAddress = llAddress state.LLAddress = llAddress
state.LLOwnerThread = llThreadOwner state.LLOwnerThread = llThreadOwner
state.Step = step state.Step = step
...@@ -185,7 +185,7 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -185,7 +185,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
PreimageKey: common.Hash{0xFF}, PreimageKey: common.Hash{0xFF},
PreimageOffset: 5, PreimageOffset: 5,
Heap: 0xc0ffee, Heap: 0xc0ffee,
LLReservationActive: true, LLReservationStatus: LLStatusActive64bit,
LLAddress: 0x12345678, LLAddress: 0x12345678,
LLOwnerThread: 0x02, LLOwnerThread: 0x02,
ExitCode: 1, ExitCode: 1,
......
package testutil package testutil
import ( import (
"bytes"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -10,6 +11,7 @@ import ( ...@@ -10,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded" "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
) )
// 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
...@@ -18,7 +20,7 @@ type ExpectedMTState struct { ...@@ -18,7 +20,7 @@ type ExpectedMTState struct {
PreimageKey common.Hash PreimageKey common.Hash
PreimageOffset arch.Word PreimageOffset arch.Word
Heap arch.Word Heap arch.Word
LLReservationActive bool LLReservationStatus multithreaded.LLReservationStatus
LLAddress arch.Word LLAddress arch.Word
LLOwnerThread arch.Word LLOwnerThread arch.Word
ExitCode uint8 ExitCode uint8
...@@ -69,7 +71,7 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { ...@@ -69,7 +71,7 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
PreimageKey: fromState.GetPreimageKey(), PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(), PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(), Heap: fromState.GetHeap(),
LLReservationActive: fromState.LLReservationActive, LLReservationStatus: fromState.LLReservationStatus,
LLAddress: fromState.LLAddress, LLAddress: fromState.LLAddress,
LLOwnerThread: fromState.LLOwnerThread, LLOwnerThread: fromState.LLOwnerThread,
ExitCode: fromState.GetExitCode(), ExitCode: fromState.GetExitCode(),
...@@ -119,8 +121,15 @@ func (e *ExpectedMTState) ExpectStep() { ...@@ -119,8 +121,15 @@ func (e *ExpectedMTState) ExpectStep() {
e.StepsSinceLastContextSwitch += 1 e.StepsSinceLastContextSwitch += 1
} }
func (e *ExpectedMTState) ExpectMemoryWrite(addr arch.Word, val uint32) { func (e *ExpectedMTState) ExpectMemoryWriteUint32(t require.TestingT, addr arch.Word, val uint32) {
e.expectedMemory.SetUint32(addr, val) // Align address to 4-byte boundaries
addr = addr & ^arch.Word(3)
// Set 4 bytes at addr
data := testutil.Uint32ToBytes(val)
err := e.expectedMemory.SetMemoryRange(addr, bytes.NewReader(data))
require.NoError(t, err)
e.MemoryRoot = e.expectedMemory.MerkleRoot() e.MemoryRoot = e.expectedMemory.MerkleRoot()
} }
...@@ -180,7 +189,7 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade ...@@ -180,7 +189,7 @@ 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.LLReservationStatus, actualState.LLReservationStatus, "Expect LLReservationStatus = %v", e.LLReservationStatus)
require.Equalf(t, e.LLAddress, actualState.LLAddress, "Expect LLAddress = 0x%x", e.LLAddress) 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.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)
......
...@@ -29,7 +29,7 @@ func TestValidate_shouldCatchMutations(t *testing.T) { ...@@ -29,7 +29,7 @@ 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: "LLReservationStatus", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLReservationStatus = e.LLReservationStatus + 1 }},
{name: "LLAddress", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLAddress += 1 }}, {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: "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 }},
......
...@@ -36,8 +36,8 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) { ...@@ -36,8 +36,8 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
// Randomize memory-related fields // Randomize memory-related fields
halfMemory := math.MaxUint32 / 2 halfMemory := math.MaxUint32 / 2
m.state.Heap = arch.Word(r.Intn(halfMemory) + halfMemory) m.state.Heap = arch.Word(r.Intn(halfMemory) + halfMemory)
m.state.LLReservationActive = r.Intn(2) == 1 m.state.LLReservationStatus = multithreaded.LLReservationStatus(r.Intn(3))
if m.state.LLReservationActive { if m.state.LLReservationStatus != multithreaded.LLStatusNone {
m.state.LLAddress = arch.Word(r.Intn(halfMemory)) m.state.LLAddress = arch.Word(r.Intn(halfMemory))
m.state.LLOwnerThread = arch.Word(r.Intn(10)) m.state.LLOwnerThread = arch.Word(r.Intn(10))
} }
......
...@@ -21,7 +21,7 @@ func RandomThread(randSeed int64) *multithreaded.ThreadState { ...@@ -21,7 +21,7 @@ func RandomThread(randSeed int64) *multithreaded.ThreadState {
return thread return thread
} }
func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRight bool) { func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRight bool, opts ...testutil.StateOption) {
singleThread := RandomThread(int64(randSeed)) singleThread := RandomThread(int64(randSeed))
state.NextThreadId = singleThread.ThreadId + 1 state.NextThreadId = singleThread.ThreadId + 1
...@@ -33,6 +33,11 @@ func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRi ...@@ -33,6 +33,11 @@ func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRi
state.RightThreadStack = []*multithreaded.ThreadState{} state.RightThreadStack = []*multithreaded.ThreadState{}
state.LeftThreadStack = []*multithreaded.ThreadState{singleThread} state.LeftThreadStack = []*multithreaded.ThreadState{singleThread}
} }
mutator := NewStateMutatorMultiThreaded(state)
for _, opt := range opts {
opt(mutator)
}
} }
func SetupThreads(randomSeed int64, state *multithreaded.State, traverseRight bool, activeStackSize, otherStackSize int) { func SetupThreads(randomSeed int64, state *multithreaded.State, traverseRight bool, activeStackSize, otherStackSize int) {
......
This diff is collapsed.
// This file contains utils for setting up forward-compatible tests for 32- and 64-bit MIPS VMs
package testutil
import (
"bytes"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
type Word = arch.Word
// SetMemoryUint64 sets 8 bytes of memory (1 or 2 Words depending on architecture) and enforces the use of addresses
// that are compatible across 32- and 64-bit architectures
func SetMemoryUint64(t require.TestingT, mem *memory.Memory, addr Word, value uint64) {
// We are setting 8 bytes of data, so mask addr to align with 8-byte boundaries in memory
addrMask := ^Word(0) & ^Word(7)
targetAddr := addr & addrMask
data := Uint64ToBytes(value)
err := mem.SetMemoryRange(targetAddr, bytes.NewReader(data))
require.NoError(t, err)
// Sanity check
if addr&0x04 != 0x04 {
// In order to write tests that run seamlessly across both 32- and 64-bit architectures,
// we need to use a memory address that is a multiple of 4, but not a multiple of 8.
// This allows us to expect a consistent value when getting a 32-bit memory value at the given address.
// For example, if memory contains [0x00: 0x1111_2222, 0x04: 0x3333_4444]:
// - the 64-bit MIPS VM will get effAddr 0x00, pulling the rightmost (lower-order) 32-bit value
// - the 32-bit MIPS VM will get effAddr 0x04, pulling the same 32-bit value
t.Errorf("Invalid address used to set uint64 memory value: %016x", addr)
t.FailNow()
}
// Give the above addr check, memory access should return the same value across architectures
effAddr := addr & arch.AddressMask
actual := mem.GetWord(effAddr)
require.Equal(t, Word(value), actual)
}
package testutil
import "encoding/binary"
func Uint32ToBytes(val uint32) []byte {
data := make([]byte, 4)
binary.BigEndian.PutUint32(data, val)
return data
}
func Uint64ToBytes(val uint64) []byte {
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, val)
return data
}
...@@ -172,6 +172,12 @@ func LogStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) { ...@@ -172,6 +172,12 @@ func LogStepFailureAtCleanup(t *testing.T, mipsEvm *MIPSEVM) {
// ValidateEVM runs a single evm step and validates against an FPVM poststate // ValidateEVM runs a single evm step and validates against an FPVM poststate
func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, tracer *tracing.Hooks) { func ValidateEVM(t *testing.T, stepWitness *mipsevm.StepWitness, step uint64, goVm mipsevm.FPVM, hashFn mipsevm.HashFn, contracts *ContractMetadata, tracer *tracing.Hooks) {
if !arch.IsMips32 {
// TODO(#12250) Re-enable EVM validation once 64-bit MIPS contracts are completed
t.Logf("WARNING: Skipping EVM validation for 64-bit MIPS")
return
}
evm := NewMIPSEVM(contracts) evm := NewMIPSEVM(contracts)
evm.SetTracer(tracer) evm.SetTracer(tracer)
LogStepFailureAtCleanup(t, evm) LogStepFailureAtCleanup(t, evm)
......
...@@ -119,7 +119,7 @@ func WithRandomization(seed int64) StateOption { ...@@ -119,7 +119,7 @@ func WithRandomization(seed int64) StateOption {
func AlignPC(pc arch.Word) arch.Word { func AlignPC(pc arch.Word) arch.Word {
// Memory-align random pc and leave room for nextPC // Memory-align random pc and leave room for nextPC
pc = pc & arch.AddressMask // Align address pc = pc & arch.AddressMask // Align address
if pc >= arch.AddressMask && arch.IsMips32 { if pc >= arch.AddressMask {
// Leave room to set and then increment nextPC // Leave room to set and then increment nextPC
pc = arch.AddressMask - 8 pc = arch.AddressMask - 8
} }
......
...@@ -144,8 +144,8 @@ ...@@ -144,8 +144,8 @@
"sourceCodeHash": "0xaf7416f27db1b393092f51d290a29293184105bc5f0d89cd6048f687cebc7d69" "sourceCodeHash": "0xaf7416f27db1b393092f51d290a29293184105bc5f0d89cd6048f687cebc7d69"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0x9ba94a69090a8c89786cdb2a5980deba4b5b16bbf5909f8275e090dbcd65e5c3", "initCodeHash": "0xd04c55d731400f777f4bb7c6520943e0f350868122bf992377ee3262cda8ee90",
"sourceCodeHash": "0x3859b4bf63f485800b0eb6ffb83a79c8d134f7e4cbbe93fbc72cc2ccd4f91b82" "sourceCodeHash": "0x0fd936b1b09a5c3cb4e7ae71c294f168ce9a57a173bcd56b9f20383624de296e"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x64ea814bf9769257c91da57928675d3f8462374b0c23bdf860ccfc79f41f7801", "initCodeHash": "0x64ea814bf9769257c91da57928675d3f8462374b0c23bdf860ccfc79f41f7801",
......
...@@ -34,6 +34,9 @@ contract MIPS2 is ISemver { ...@@ -34,6 +34,9 @@ contract MIPS2 is ISemver {
uint32[32] registers; uint32[32] registers;
} }
uint8 internal constant LL_STATUS_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE = 1;
/// @notice Stores the VM state. /// @notice Stores the VM state.
/// Total state size: 32 + 32 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 172 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.
...@@ -42,7 +45,7 @@ contract MIPS2 is ISemver { ...@@ -42,7 +45,7 @@ contract MIPS2 is ISemver {
bytes32 preimageKey; bytes32 preimageKey;
uint32 preimageOffset; uint32 preimageOffset;
uint32 heap; uint32 heap;
bool llReservationActive; uint8 llReservationStatus;
uint32 llAddress; uint32 llAddress;
uint32 llOwnerThread; uint32 llOwnerThread;
uint8 exitCode; uint8 exitCode;
...@@ -57,8 +60,8 @@ contract MIPS2 is ISemver { ...@@ -57,8 +60,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.15 /// @custom:semver 1.0.0-beta.16
string public constant version = "1.0.0-beta.15"; string public constant version = "1.0.0-beta.16";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -141,7 +144,7 @@ contract MIPS2 is ISemver { ...@@ -141,7 +144,7 @@ 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, 1) // llReservationStatus
c, m := putField(c, m, 4) // llAddress c, m := putField(c, m, 4) // llAddress
c, m := putField(c, m, 4) // llOwnerThread c, m := putField(c, m, 4) // llOwnerThread
c, m := putField(c, m, 1) // exitCode c, m := putField(c, m, 1) // exitCode
...@@ -269,14 +272,14 @@ contract MIPS2 is ISemver { ...@@ -269,14 +272,14 @@ contract MIPS2 is ISemver {
} }
function handleMemoryUpdate(State memory _state, uint32 _memAddr) internal pure { function handleMemoryUpdate(State memory _state, uint32 _memAddr) internal pure {
if (_memAddr == _state.llAddress) { if (_memAddr == (0xFFFFFFFC & _state.llAddress)) {
// Reserved address was modified, clear the reservation // Reserved address was modified, clear the reservation
clearLLMemoryReservation(_state); clearLLMemoryReservation(_state);
} }
} }
function clearLLMemoryReservation(State memory _state) internal pure { function clearLLMemoryReservation(State memory _state) internal pure {
_state.llReservationActive = false; _state.llReservationStatus = LL_STATUS_NONE;
_state.llAddress = 0; _state.llAddress = 0;
_state.llOwnerThread = 0; _state.llOwnerThread = 0;
} }
...@@ -295,25 +298,28 @@ contract MIPS2 is ISemver { ...@@ -295,25 +298,28 @@ contract MIPS2 is ISemver {
uint32 base = _thread.registers[baseReg]; uint32 base = _thread.registers[baseReg];
uint32 rtReg = (_insn >> 16) & 0x1F; uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 offset = ins.signExtendImmediate(_insn); uint32 offset = ins.signExtendImmediate(_insn);
uint32 addr = base + offset;
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 retVal = 0;
uint32 threadId = _thread.threadID; uint32 threadId = _thread.threadID;
if (_opcode == ins.OP_LOAD_LINKED) { if (_opcode == ins.OP_LOAD_LINKED) {
retVal = mem; retVal = loadWord(_state, addr);
_state.llReservationActive = true;
_state.llAddress = effAddr; _state.llReservationStatus = LL_STATUS_ACTIVE;
_state.llAddress = addr;
_state.llOwnerThread = threadId; _state.llOwnerThread = threadId;
} else if (_opcode == ins.OP_STORE_CONDITIONAL) { } else if (_opcode == ins.OP_STORE_CONDITIONAL) {
// Check if our memory reservation is still intact // Check if our memory reservation is still intact
if (_state.llReservationActive && _state.llOwnerThread == threadId && _state.llAddress == effAddr) { if (
_state.llReservationStatus == LL_STATUS_ACTIVE && _state.llOwnerThread == threadId
&& _state.llAddress == addr
) {
// Complete atomic update: set memory and return 1 for success // Complete atomic update: set memory and return 1 for success
clearLLMemoryReservation(_state); clearLLMemoryReservation(_state);
uint32 val = _thread.registers[rtReg]; uint32 val = _thread.registers[rtReg];
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val); storeWord(_state, addr, val);
retVal = 1; retVal = 1;
} else { } else {
// Atomic update failed, return 0 for failure // Atomic update failed, return 0 for failure
...@@ -332,6 +338,18 @@ contract MIPS2 is ISemver { ...@@ -332,6 +338,18 @@ contract MIPS2 is ISemver {
} }
} }
function loadWord(State memory _state, uint32 _addr) internal pure returns (uint32 val_) {
uint32 effAddr = _addr & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1);
val_ = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);
}
function storeWord(State memory _state, uint32 _addr, uint32 _val) internal pure {
uint32 effAddr = _addr & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1);
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, _val);
}
function handleSyscall(bytes32 _localContext) internal returns (bytes32 out_) { function handleSyscall(bytes32 _localContext) internal returns (bytes32 out_) {
unchecked { unchecked {
// Load state from memory offsets to reduce stack pressure // Load state from memory offsets to reduce stack pressure
...@@ -632,7 +650,7 @@ contract MIPS2 is ISemver { ...@@ -632,7 +650,7 @@ 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, 1) // llReservationStatus
from, to := copyMem(from, to, 4) // llAddress from, to := copyMem(from, to, 4) // llAddress
from, to := copyMem(from, to, 4) // llOwnerThread from, to := copyMem(from, to, 4) // llOwnerThread
let exitCode := mload(from) let exitCode := mload(from)
......
...@@ -185,7 +185,7 @@ contract MIPS2_Test is CommonTest { ...@@ -185,7 +185,7 @@ contract MIPS2_Test is CommonTest {
preimageKey: bytes32(0), preimageKey: bytes32(0),
preimageOffset: 0, preimageOffset: 0,
heap: 0, heap: 0,
llReservationActive: false, llReservationStatus: 0,
llAddress: 0, llAddress: 0,
llOwnerThread: 0, llOwnerThread: 0,
exitCode: 0, exitCode: 0,
...@@ -2017,7 +2017,7 @@ contract MIPS2_Test is CommonTest { ...@@ -2017,7 +2017,7 @@ contract MIPS2_Test is CommonTest {
updateThreadStacks(state, thread); updateThreadStacks(state, thread);
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal); MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal);
expect.llReservationActive = true; expect.llReservationStatus = 1;
expect.llAddress = effAddr; expect.llAddress = effAddr;
expect.llOwnerThread = thread.threadID; expect.llOwnerThread = thread.threadID;
...@@ -2034,7 +2034,7 @@ contract MIPS2_Test is CommonTest { ...@@ -2034,7 +2034,7 @@ contract MIPS2_Test is CommonTest {
(MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) = (MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, effAddr, 0); constructMIPSState(0, insn, effAddr, 0);
state.llReservationActive = true; state.llReservationStatus = 1;
state.llAddress = effAddr; state.llAddress = effAddr;
state.llOwnerThread = thread.threadID; state.llOwnerThread = thread.threadID;
thread.registers[8] = writeMemVal; thread.registers[8] = writeMemVal;
...@@ -2044,7 +2044,7 @@ contract MIPS2_Test is CommonTest { ...@@ -2044,7 +2044,7 @@ contract MIPS2_Test is CommonTest {
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1); MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1);
(expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal); (expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal);
expect.llReservationActive = false; expect.llReservationStatus = 0;
expect.llAddress = 0; expect.llAddress = 0;
expect.llOwnerThread = 0; expect.llOwnerThread = 0;
...@@ -2764,7 +2764,7 @@ contract MIPS2_Test is CommonTest { ...@@ -2764,7 +2764,7 @@ contract MIPS2_Test is CommonTest {
_state.preimageKey, _state.preimageKey,
_state.preimageOffset, _state.preimageOffset,
_state.heap, _state.heap,
_state.llReservationActive, _state.llReservationStatus,
_state.llAddress, _state.llAddress,
_state.llOwnerThread, _state.llOwnerThread,
_state.exitCode, _state.exitCode,
......
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