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,6 +40,14 @@ const ( ...@@ -40,6 +40,14 @@ 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
...@@ -47,7 +55,7 @@ type State struct { ...@@ -47,7 +55,7 @@ type State struct {
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
...@@ -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) {
......
//go:build cannon64
// +build cannon64
// These tests target architectures that are 64-bit or larger
package tests
import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded"
mttestutil "github.com/ethereum-optimism/optimism/cannon/mipsevm/multithreaded/testutil"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
)
func TestEVM_MT64_LL(t *testing.T) {
var tracer *tracing.Hooks
memVal := Word(0x11223344_55667788)
memValNeg := Word(0xF1223344_F5667788)
cases := []struct {
name string
base Word
offset int
addr Word
memVal Word
retReg int
retVal Word
}{
{name: "8-byte-aligned addr", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retVal: 0x11223344, retReg: 5},
{name: "8-byte-aligned addr, neg value", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memValNeg, retVal: 0xFFFFFFFF_F1223344, retReg: 5},
{name: "8-byte-aligned addr, extra bits", base: 0x01, offset: 0x0109, addr: 0x010A, memVal: memVal, retVal: 0x11223344, retReg: 5},
{name: "8-byte-aligned addr, addr signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, memVal: memVal, retVal: 0x11223344, retReg: 5},
{name: "8-byte-aligned addr, addr signed extended w overflow", base: 0x1000_0001, offset: 0xFF07, addr: 0x0000_0000_0FFF_FF08, memVal: memVal, retVal: 0x11223344, retReg: 5},
{name: "4-byte-aligned addr", base: 0x01, offset: 0x0103, addr: 0x0104, memVal: memVal, retVal: 0x55667788, retReg: 5},
{name: "4-byte-aligned addr, neg value", base: 0x01, offset: 0x0104, addr: 0x0105, memVal: memValNeg, retVal: 0xFFFFFFFF_F5667788, retReg: 5},
{name: "4-byte-aligned addr, extra bits", base: 0x01, offset: 0x0105, addr: 0x0106, memVal: memVal, retVal: 0x55667788, retReg: 5},
{name: "4-byte-aligned addr, addr signed extended", base: 0x01, offset: 0xFF33, addr: 0xFFFF_FFFF_FFFF_FF34, memVal: memVal, retVal: 0x55667788, retReg: 5},
{name: "4-byte-aligned addr, addr signed extended w overflow", base: 0x1000_0001, offset: 0xFF03, addr: 0x0000_0000_0FFF_FF04, memVal: memVal, retVal: 0x55667788, retReg: 5},
{name: "Return register set to 0", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retVal: 0x11223344, retReg: 0},
}
for i, c := range cases {
for _, withExistingReservation := range []bool{true, false} {
tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation)
t.Run(tName, func(t *testing.T) {
effAddr := arch.AddressMask & c.addr
retReg := c.retReg
baseReg := 6
insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40))
step := state.GetStep()
// Set up state
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetMemory().SetWord(effAddr, c.memVal)
state.GetRegistersRef()[baseReg] = c.base
if withExistingReservation {
state.LLReservationStatus = multithreaded.LLStatusActive32bit
state.LLAddress = c.addr + 1
state.LLOwnerThread = 123
} else {
state.LLReservationStatus = multithreaded.LLStatusNone
state.LLAddress = 0
state.LLOwnerThread = 0
}
// Set up expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.LLReservationStatus = multithreaded.LLStatusActive32bit
expected.LLAddress = c.addr
expected.LLOwnerThread = state.GetCurrentThread().ThreadId
if retReg != 0 {
expected.ActiveThread().Registers[retReg] = c.retVal
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT64_SC(t *testing.T) {
var tracer *tracing.Hooks
llVariations := []struct {
name string
llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool
matchAddr bool
shouldSucceed bool
}{
{name: "should succeed", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: true},
{name: "mismatch addr", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: true, shouldSucceed: false},
{name: "mismatched thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: false, shouldSucceed: false},
{name: "mismatched reservation status", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: true, shouldSucceed: false},
{name: "no active reservation", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchAddr: true, shouldSucceed: false},
}
cases := []struct {
name string
base Word
offset int
addr Word
value Word
expectedMemVal Word
rtReg int
threadId Word
}{
{name: "8-byte-aligned addr", base: 0x01, offset: 0x0137, addr: 0x0138, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4},
{name: "8-byte-aligned addr, extra bits", base: 0x01, offset: 0x0138, addr: 0x0139, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4},
{name: "8-byte-aligned addr, signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4},
{name: "8-byte-aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF37, addr: 0x0FFF_FF38, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 4},
{name: "4-byte-aligned addr", base: 0x01, offset: 0x0133, addr: 0x0134, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4},
{name: "4-byte-aligned addr, extra bits", base: 0x01, offset: 0x0134, addr: 0x0135, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4},
{name: "4-byte-aligned addr, signed extended", base: 0x01, offset: 0xFF33, addr: 0xFFFF_FFFF_FFFF_FF34, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4},
{name: "4-byte-aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF33, addr: 0x0FFF_FF34, value: 0xABCD, expectedMemVal: 0x_0000_0000_0000_ABCD, rtReg: 5, threadId: 4},
{name: "Return register set to 0", base: 0x01, offset: 0x0138, addr: 0x0139, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 0, threadId: 4},
{name: "Zero valued ll args", base: 0x0, offset: 0x0, value: 0xABCD, expectedMemVal: 0xABCD_0000_0000, rtReg: 5, threadId: 0},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
effAddr := arch.AddressMask & c.addr
// Setup
rtReg := c.rtReg
baseReg := 6
insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40))
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread Word
if v.matchAddr {
llAddress = c.addr
} else {
llAddress = c.addr + 1
}
if v.matchThreadId {
llOwnerThread = c.threadId
} else {
llOwnerThread = c.threadId + 1
}
// Setup state
state.GetCurrentThread().ThreadId = c.threadId
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value
state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
var retVal Word
if v.shouldSucceed {
retVal = 1
expected.ExpectMemoryWordWrite(effAddr, c.expectedMemVal)
expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0
expected.LLOwnerThread = 0
} else {
retVal = 0
}
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = retVal
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT64_LLD(t *testing.T) {
var tracer *tracing.Hooks
memVal := Word(0x11223344_55667788)
memValNeg := Word(0xF1223344_F5667788)
cases := []struct {
name string
base Word
offset int
addr Word
memVal Word
retReg int
}{
{name: "Aligned addr", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retReg: 5},
{name: "Aligned addr, neg value", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memValNeg, retReg: 5},
{name: "Unaligned addr, offset=1", base: 0x01, offset: 0x0100, addr: 0x0101, memVal: memVal, retReg: 5},
{name: "Unaligned addr, offset=2", base: 0x02, offset: 0x0100, addr: 0x0102, memVal: memVal, retReg: 5},
{name: "Unaligned addr, offset=3", base: 0x03, offset: 0x0100, addr: 0x0103, memVal: memVal, retReg: 5},
{name: "Unaligned addr, offset=4", base: 0x04, offset: 0x0100, addr: 0x0104, memVal: memVal, retReg: 5},
{name: "Unaligned addr, offset=5", base: 0x05, offset: 0x0100, addr: 0x0105, memVal: memVal, retReg: 5},
{name: "Unaligned addr, offset=6", base: 0x06, offset: 0x0100, addr: 0x0106, memVal: memVal, retReg: 5},
{name: "Unaligned addr, offset=7", base: 0x07, offset: 0x0100, addr: 0x0107, memVal: memVal, retReg: 5},
{name: "Aligned addr, signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, memVal: memVal, retReg: 5},
{name: "Aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF07, addr: 0x0000_0000_0FFF_FF08, memVal: memVal, retReg: 5},
{name: "Return register set to 0", base: 0x01, offset: 0x0107, addr: 0x0108, memVal: memVal, retReg: 0},
}
for i, c := range cases {
for _, withExistingReservation := range []bool{true, false} {
tName := fmt.Sprintf("%v (withExistingReservation = %v)", c.name, withExistingReservation)
t.Run(tName, func(t *testing.T) {
effAddr := arch.AddressMask & c.addr
retReg := c.retReg
baseReg := 6
insn := uint32((0b11_0100 << 26) | (baseReg & 0x1F << 21) | (retReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40))
step := state.GetStep()
// Set up state
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetMemory().SetWord(effAddr, c.memVal)
state.GetRegistersRef()[baseReg] = c.base
if withExistingReservation {
state.LLReservationStatus = multithreaded.LLStatusActive64bit
state.LLAddress = c.addr + 1
state.LLOwnerThread = 123
} else {
state.LLReservationStatus = multithreaded.LLStatusNone
state.LLAddress = 0
state.LLOwnerThread = 0
}
// Set up expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.LLReservationStatus = multithreaded.LLStatusActive64bit
expected.LLAddress = c.addr
expected.LLOwnerThread = state.GetCurrentThread().ThreadId
if retReg != 0 {
expected.ActiveThread().Registers[retReg] = c.memVal
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
func TestEVM_MT64_SCD(t *testing.T) {
var tracer *tracing.Hooks
value := Word(0x11223344_55667788)
llVariations := []struct {
name string
llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool
matchAddr bool
shouldSucceed bool
}{
{name: "should succeed", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: true, shouldSucceed: true},
{name: "mismatch addr", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, matchAddr: true, shouldSucceed: false},
{name: "mismatched thread", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, matchAddr: false, shouldSucceed: false},
{name: "mismatched status", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: false},
{name: "no active reservation", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchAddr: true, shouldSucceed: false},
}
cases := []struct {
name string
base Word
offset int
addr Word
rtReg int
threadId Word
}{
{name: "Aligned addr", base: 0x01, offset: 0x0137, addr: 0x0138, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=1", base: 0x01, offset: 0x0100, addr: 0x0101, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=2", base: 0x02, offset: 0x0100, addr: 0x0102, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=3", base: 0x03, offset: 0x0100, addr: 0x0103, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=4", base: 0x04, offset: 0x0100, addr: 0x0104, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=5", base: 0x05, offset: 0x0100, addr: 0x0105, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=6", base: 0x06, offset: 0x0100, addr: 0x0106, rtReg: 5, threadId: 4},
{name: "Unaligned addr, offset=7", base: 0x07, offset: 0x0100, addr: 0x0107, rtReg: 5, threadId: 4},
{name: "Aligned addr, signed extended", base: 0x01, offset: 0xFF37, addr: 0xFFFF_FFFF_FFFF_FF38, rtReg: 5, threadId: 4},
{name: "Aligned addr, signed extended w overflow", base: 0x1000_0001, offset: 0xFF37, addr: 0x0FFF_FF38, rtReg: 5, threadId: 4},
{name: "Return register set to 0", base: 0x01, offset: 0x0138, addr: 0x0139, rtReg: 0, threadId: 4},
{name: "Zero valued ll args", base: 0x0, offset: 0x0, rtReg: 5, threadId: 0},
}
for i, c := range cases {
for _, v := range llVariations {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
effAddr := arch.AddressMask & c.addr
// Setup
rtReg := c.rtReg
baseReg := 6
insn := uint32((0b11_1100 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40))
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread Word
if v.matchAddr {
llAddress = c.addr
} else {
llAddress = c.addr + 1
}
if v.matchThreadId {
llOwnerThread = c.threadId
} else {
llOwnerThread = c.threadId + 1
}
// Setup state
state.GetCurrentThread().ThreadId = c.threadId
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = value
state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
var retVal Word
if v.shouldSucceed {
retVal = 1
expected.ExpectMemoryWordWrite(effAddr, value)
expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0
expected.LLOwnerThread = 0
} else {
retVal = 0
}
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = retVal
}
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts, tracer)
})
}
}
}
// These tests target architectures that are 64-bit or larger
package tests package tests
import ( import (
...@@ -27,19 +28,28 @@ type Word = arch.Word ...@@ -27,19 +28,28 @@ type Word = arch.Word
func TestEVM_MT_LL(t *testing.T) { func TestEVM_MT_LL(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
// Set up some test values that will be reused
posValue := uint64(0xAAAA_BBBB_1122_3344)
posValueRet := uint64(0x1122_3344)
negValue := uint64(0x1111_1111_8877_6655)
negRetValue := uint64(0xFFFF_FFFF_8877_6655) // Sign extended version of negValue
// Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit
cases := []struct { cases := []struct {
name string name string
base Word base uint64
offset int offset int
value Word expectedAddr uint64
effAddr Word memValue uint64
retVal uint64
rtReg int rtReg int
}{ }{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5}, {name: "Aligned addr", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, memValue: posValue, retVal: posValueRet, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5}, {name: "Aligned addr, negative value", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, memValue: negValue, retVal: negRetValue, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5}, {name: "Aligned addr, addr signed extended", base: 0x01, offset: 0xFF33, expectedAddr: 0xFFFF_FFFF_FFFF_FF34, memValue: posValue, retVal: posValueRet, 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: "Unaligned addr", base: 0xFF12_0001, offset: 0x3405, expectedAddr: 0xFF12_3406, memValue: posValue, retVal: posValueRet, rtReg: 5},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0}, {name: "Unaligned addr, addr sign extended w overflow", base: 0xFF12_0001, offset: 0x8405, expectedAddr: 0xFF11_8406, memValue: posValue, retVal: posValueRet, rtReg: 5},
{name: "Return register set to 0", base: 0xFF12_0001, offset: 0x7404, expectedAddr: 0xFF12_7405, memValue: posValue, retVal: 0, rtReg: 0},
} }
for i, c := range cases { for i, c := range cases {
for _, withExistingReservation := range []bool{true, false} { for _, withExistingReservation := range []bool{true, false} {
...@@ -47,23 +57,20 @@ func TestEVM_MT_LL(t *testing.T) { ...@@ -47,23 +57,20 @@ func TestEVM_MT_LL(t *testing.T) {
t.Run(tName, func(t *testing.T) { t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg rtReg := c.rtReg
baseReg := 6 baseReg := 6
pc := Word(0x44)
insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) insn := uint32((0b11_0000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil) goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x40))
step := state.GetStep() step := state.GetStep()
// Set up state // Set up state
state.GetCurrentThread().Cpu.PC = pc testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), c.memValue)
state.GetCurrentThread().Cpu.NextPC = pc + 4 state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetMemory().SetUint32(pc, insn) state.GetRegistersRef()[baseReg] = Word(c.base)
state.GetMemory().SetWord(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
if withExistingReservation { if withExistingReservation {
state.LLReservationActive = true state.LLReservationStatus = multithreaded.LLStatusActive32bit
state.LLAddress = c.effAddr + Word(4) state.LLAddress = Word(c.expectedAddr + 1)
state.LLOwnerThread = 123 state.LLOwnerThread = 123
} else { } else {
state.LLReservationActive = false state.LLReservationStatus = multithreaded.LLStatusNone
state.LLAddress = 0 state.LLAddress = 0
state.LLOwnerThread = 0 state.LLOwnerThread = 0
} }
...@@ -71,11 +78,11 @@ func TestEVM_MT_LL(t *testing.T) { ...@@ -71,11 +78,11 @@ func TestEVM_MT_LL(t *testing.T) {
// Set up expectations // Set up expectations
expected := mttestutil.NewExpectedMTState(state) expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep() expected.ExpectStep()
expected.LLReservationActive = true expected.LLReservationStatus = multithreaded.LLStatusActive32bit
expected.LLAddress = c.effAddr expected.LLAddress = Word(c.expectedAddr)
expected.LLOwnerThread = state.GetCurrentThread().ThreadId expected.LLOwnerThread = state.GetCurrentThread().ThreadId
if rtReg != 0 { if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = c.value expected.ActiveThread().Registers[rtReg] = Word(c.retVal)
} }
stepWitness, err := goVm.Step(true) stepWitness, err := goVm.Step(true)
...@@ -92,35 +99,39 @@ func TestEVM_MT_LL(t *testing.T) { ...@@ -92,35 +99,39 @@ func TestEVM_MT_LL(t *testing.T) {
func TestEVM_MT_SC(t *testing.T) { func TestEVM_MT_SC(t *testing.T) {
var tracer *tracing.Hooks var tracer *tracing.Hooks
// Set up some test values that will be reused
memValue := uint64(0x1122_3344_5566_7788)
llVariations := []struct { llVariations := []struct {
name string name string
llReservationActive bool llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool matchThreadId bool
matchEffAddr bool matchAddr bool
shouldSucceed bool shouldSucceed bool
}{ }{
{name: "should succeed", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldSucceed: true}, {name: "should succeed", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: true},
{name: "mismatch addr", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldSucceed: false}, {name: "mismatch thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: true, shouldSucceed: false},
{name: "mismatched thread", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldSucceed: false}, {name: "mismatched addr", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldSucceed: false}, {name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: false, shouldSucceed: false},
{name: "no active reservation", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldSucceed: false}, {name: "mismatched status", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchAddr: true, shouldSucceed: false},
{name: "no active reservation", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchAddr: true, shouldSucceed: false},
} }
// Note: Some parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit
cases := []struct { cases := []struct {
name string name string
base Word base Word
offset int offset int
value Word expectedAddr uint64
effAddr Word storeValue uint32
rtReg int rtReg int
threadId Word threadId Word
}{ }{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5, threadId: 4}, {name: "Aligned addr", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5, threadId: 4}, {name: "Aligned addr, signed extended", base: 0x01, offset: 0xFF33, expectedAddr: 0xFFFF_FFFF_FFFF_FF34, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5, threadId: 4}, {name: "Unaligned addr", base: 0xFF12_0001, offset: 0x3404, expectedAddr: 0xFF12_3405, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5, threadId: 4}, {name: "Unaligned addr, sign extended w overflow", base: 0xFF12_0001, offset: 0x8404, expectedAddr: 0xFF_11_8405, storeValue: 0xAABB_CCDD, rtReg: 5, threadId: 4},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0, threadId: 4}, {name: "Return register set to 0", base: 0xFF12_0001, offset: 0x7403, expectedAddr: 0xFF12_7404, storeValue: 0xAABB_CCDD, rtReg: 0, threadId: 4},
{name: "Zero valued ll args", base: 0x00_00_00_00, offset: 0x0, value: 0xABCD, effAddr: 0x00_00_00_00, rtReg: 5, threadId: 0},
} }
for i, c := range cases { for i, c := range cases {
for _, v := range llVariations { for _, v := range llVariations {
...@@ -128,18 +139,17 @@ func TestEVM_MT_SC(t *testing.T) { ...@@ -128,18 +139,17 @@ func TestEVM_MT_SC(t *testing.T) {
t.Run(tName, func(t *testing.T) { t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg rtReg := c.rtReg
baseReg := 6 baseReg := 6
pc := Word(0x44)
insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) insn := uint32((0b11_1000 << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil) goVm, state, contracts := setup(t, i, nil)
mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1) mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40))
step := state.GetStep() step := state.GetStep()
// Define LL-related params // Define LL-related params
var llAddress, llOwnerThread Word var llAddress, llOwnerThread Word
if v.matchEffAddr { if v.matchAddr {
llAddress = c.effAddr llAddress = Word(c.expectedAddr)
} else { } else {
llAddress = c.effAddr + 4 llAddress = Word(c.expectedAddr) + 1
} }
if v.matchThreadId { if v.matchThreadId {
llOwnerThread = c.threadId llOwnerThread = c.threadId
...@@ -148,13 +158,12 @@ func TestEVM_MT_SC(t *testing.T) { ...@@ -148,13 +158,12 @@ func TestEVM_MT_SC(t *testing.T) {
} }
// Setup state // Setup state
testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), memValue)
state.GetCurrentThread().ThreadId = c.threadId state.GetCurrentThread().ThreadId = c.threadId
state.GetCurrentThread().Cpu.PC = pc state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetUint32(pc, insn)
state.GetRegistersRef()[baseReg] = c.base state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value state.GetRegistersRef()[rtReg] = Word(c.storeValue)
state.LLReservationActive = v.llReservationActive state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread state.LLOwnerThread = llOwnerThread
...@@ -164,8 +173,8 @@ func TestEVM_MT_SC(t *testing.T) { ...@@ -164,8 +173,8 @@ func TestEVM_MT_SC(t *testing.T) {
var retVal Word var retVal Word
if v.shouldSucceed { if v.shouldSucceed {
retVal = 1 retVal = 1
expected.ExpectMemoryWordWrite(c.effAddr, c.value) expected.ExpectMemoryWriteUint32(t, Word(c.expectedAddr), c.storeValue)
expected.LLReservationActive = false expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0 expected.LLAddress = 0
expected.LLOwnerThread = 0 expected.LLOwnerThread = 0
} else { } else {
...@@ -195,17 +204,18 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) { ...@@ -195,17 +204,18 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
llVariations := []struct { llVariations := []struct {
name string name string
llReservationActive bool llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool matchThreadId bool
matchEffAddr bool effAddrOffset Word
shouldClearReservation bool shouldClearReservation bool
}{ }{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false},
} }
cases := []struct { cases := []struct {
...@@ -246,16 +256,10 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) { ...@@ -246,16 +256,10 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
step := state.GetStep() step := state.GetStep()
// Define LL-related params // Define LL-related params
var llAddress, llOwnerThread Word llAddress := effAddr + v.effAddrOffset
if v.matchEffAddr { llOwnerThread := state.GetCurrentThread().ThreadId
llAddress = effAddr if !v.matchThreadId {
} else { llOwnerThread += 1
llAddress = effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
} }
// Set up state // Set up state
...@@ -266,7 +270,7 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) { ...@@ -266,7 +270,7 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
state.GetRegistersRef()[5] = c.addr state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count state.GetRegistersRef()[6] = c.count
state.GetMemory().SetUint32(state.GetPC(), syscallInsn) state.GetMemory().SetUint32(state.GetPC(), syscallInsn)
state.LLReservationActive = v.llReservationActive state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread state.LLOwnerThread = llOwnerThread
state.GetMemory().SetUint32(effAddr, c.prestateMem) state.GetMemory().SetUint32(effAddr, c.prestateMem)
...@@ -277,9 +281,9 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) { ...@@ -277,9 +281,9 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
expected.ActiveThread().Registers[2] = c.writeLen expected.ActiveThread().Registers[2] = c.writeLen
expected.ActiveThread().Registers[7] = 0 // no error expected.ActiveThread().Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem) expected.ExpectMemoryWriteUint32(t, effAddr, c.postateMem)
if v.shouldClearReservation { if v.shouldClearReservation {
expected.LLReservationActive = false expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0 expected.LLAddress = 0
expected.LLOwnerThread = 0 expected.LLOwnerThread = 0
} }
...@@ -305,20 +309,22 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) { ...@@ -305,20 +309,22 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
llVariations := []struct { llVariations := []struct {
name string name string
llReservationActive bool llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool matchThreadId bool
matchEffAddr bool effAddrOffset Word
shouldClearReservation bool shouldClearReservation bool
}{ }{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, {name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, shouldClearReservation: true},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation, diff thread, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, effAddrOffset: 8, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false},
} }
pc := Word(0x04)
rt := Word(0x12_34_56_78) rt := Word(0x12_34_56_78)
baseReg := 5 baseReg := 5
rtReg := 6 rtReg := 6
...@@ -342,39 +348,31 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) { ...@@ -342,39 +348,31 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
tName := fmt.Sprintf("%v (%v)", c.name, v.name) tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) { t.Run(tName, func(t *testing.T) {
insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset)) insn := uint32((c.opcode << 26) | (baseReg & 0x1F << 21) | (rtReg & 0x1F << 16) | (0xFFFF & c.offset))
goVm, state, contracts := setup(t, i, nil) goVm, state, contracts := setup(t, i, nil, testutil.WithPCAndNextPC(0x08))
step := state.GetStep() step := state.GetStep()
// Define LL-related params // Define LL-related params
var llAddress, llOwnerThread Word llAddress := c.effAddr + v.effAddrOffset
if v.matchEffAddr { llOwnerThread := state.GetCurrentThread().ThreadId
llAddress = c.effAddr if !v.matchThreadId {
} else { llOwnerThread += 1
llAddress = c.effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
} }
// Setup state // Setup state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetRegistersRef()[rtReg] = rt state.GetRegistersRef()[rtReg] = rt
state.GetRegistersRef()[baseReg] = c.base state.GetRegistersRef()[baseReg] = c.base
state.GetMemory().SetUint32(state.GetPC(), insn) state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetMemory().SetUint32(c.effAddr, c.preMem) state.GetMemory().SetUint32(c.effAddr, c.preMem)
state.LLReservationActive = v.llReservationActive state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread state.LLOwnerThread = llOwnerThread
// Setup expectations // Setup expectations
expected := mttestutil.NewExpectedMTState(state) expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep() expected.ExpectStep()
expected.ExpectMemoryWrite(c.effAddr, c.postMem) expected.ExpectMemoryWriteUint32(t, c.effAddr, c.postMem)
if v.shouldClearReservation { if v.shouldClearReservation {
expected.LLReservationActive = false expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0 expected.LLAddress = 0
expected.LLOwnerThread = 0 expected.LLOwnerThread = 0
} }
...@@ -980,21 +978,23 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { ...@@ -980,21 +978,23 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) {
llVariations := []struct { llVariations := []struct {
name string name string
llReservationActive bool llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool matchThreadId bool
matchEffAddr bool matchEffAddr bool
matchEffAddr2 bool matchEffAddr2 bool
shouldClearReservation bool shouldClearReservation bool
}{ }{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, 2nd word", llReservationActive: true, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, {name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true}, {name: "matching reservation, 2nd word", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "matching reservation, diff thread, 2nd word", llReservationActive: true, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true}, {name: "matching reservation, 2nd word, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, {name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false}, {name: "matching reservation, diff thread, 2nd word", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true}, {name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr2", llReservationActive: false, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true}, {name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false}, {name: "no reservation, matching addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, matching addr2", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationStatus: multithreaded.LLStatusNone, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
} }
cases := []struct { cases := []struct {
...@@ -1033,7 +1033,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { ...@@ -1033,7 +1033,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) {
state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = clkid // a0 state.GetRegistersRef()[4] = clkid // a0
state.GetRegistersRef()[5] = c.timespecAddr // a1 state.GetRegistersRef()[5] = c.timespecAddr // a1
state.LLReservationActive = v.llReservationActive state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread state.LLOwnerThread = llOwnerThread
...@@ -1050,7 +1050,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) { ...@@ -1050,7 +1050,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) {
expected.ExpectMemoryWordWrite(effAddr, secs) expected.ExpectMemoryWordWrite(effAddr, secs)
expected.ExpectMemoryWordWrite(effAddr2, nsecs) expected.ExpectMemoryWordWrite(effAddr2, nsecs)
if v.shouldClearReservation { if v.shouldClearReservation {
expected.LLReservationActive = false expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0 expected.LLAddress = 0
expected.LLOwnerThread = 0 expected.LLOwnerThread = 0
} }
...@@ -1541,9 +1541,10 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) { ...@@ -1541,9 +1541,10 @@ func TestEVM_SchedQuantumThreshold(t *testing.T) {
} }
} }
func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) { func setup(t require.TestingT, randomSeed int, preimageOracle mipsevm.PreimageOracle, opts ...testutil.StateOption) (mipsevm.FPVM, *multithreaded.State, *testutil.ContractMetadata) {
v := GetMultiThreadedTestCase(t) v := GetMultiThreadedTestCase(t)
vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(randomSeed))) allOpts := append([]testutil.StateOption{testutil.WithRandomization(int64(randomSeed))}, opts...)
vm := v.VMFactory(preimageOracle, os.Stdout, os.Stderr, testutil.CreateLogger(), allOpts...)
state := mttestutil.GetMtState(t, vm) state := mttestutil.GetMtState(t, vm)
return vm, state, v.Contracts return vm, state, v.Contracts
......
// 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