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
cpu.NextPC = cpu.NextPC + 4
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 {
}
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
m.clearLLMemoryReservation()
}
}
func (m *InstrumentedState) clearLLMemoryReservation() {
m.state.LLReservationActive = false
m.state.LLReservationStatus = LLStatusNone
m.state.LLAddress = 0
m.state.LLOwnerThread = 0
}
......@@ -343,36 +343,40 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
base := m.state.GetRegistersRef()[baseReg]
rtReg := Word((insn >> 16) & 0x1F)
offset := exec.SignExtendImmediate(insn)
addr := base + offset
effAddr := (base + offset) & arch.AddressMask
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetWord(effAddr)
// Determine some opcode-specific parameters
targetStatus := LLStatusActive32bit
byteLength := Word(4)
if opcode == exec.OpLoadLinked64 || opcode == exec.OpStoreConditional64 {
// Use 64-bit params
targetStatus = LLStatusActive64bit
byteLength = Word(8)
}
var retVal Word
threadId := m.state.GetCurrentThread().ThreadId
if opcode == exec.OpLoadLinked || opcode == exec.OpLoadLinked64 {
retVal = mem
m.state.LLReservationActive = true
m.state.LLAddress = effAddr
switch opcode {
case exec.OpLoadLinked, exec.OpLoadLinked64:
retVal = exec.LoadSubWord(m.state.GetMemory(), addr, byteLength, true, m.memoryTracker)
m.state.LLReservationStatus = targetStatus
m.state.LLAddress = addr
m.state.LLOwnerThread = threadId
} else if opcode == exec.OpStoreConditional || opcode == exec.OpStoreConditional64 {
// TODO(#12205): Determine bits affected by coherence stores on 64-bits
// Check if our memory reservation is still intact
if m.state.LLReservationActive && m.state.LLOwnerThread == threadId && m.state.LLAddress == effAddr {
case exec.OpStoreConditional, exec.OpStoreConditional64:
if m.state.LLReservationStatus == targetStatus && m.state.LLOwnerThread == threadId && m.state.LLAddress == addr {
// Complete atomic update: set memory and return 1 for success
m.clearLLMemoryReservation()
rt := m.state.GetRegistersRef()[rtReg]
if opcode == exec.OpStoreConditional {
m.state.Memory.SetUint32(effAddr, uint32(rt))
} else {
m.state.Memory.SetWord(effAddr, rt)
}
val := m.state.GetRegistersRef()[rtReg]
exec.StoreSubWord(m.state.GetMemory(), addr, byteLength, val, m.memoryTracker)
retVal = 1
} else {
// Atomic update failed, return 0 for failure
retVal = 0
}
} else {
default:
panic(fmt.Sprintf("Invalid instruction passed to handleRMWOps (opcode %08x)", opcode))
}
......
......@@ -40,16 +40,24 @@ const (
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 {
Memory *memory.Memory
PreimageKey common.Hash
PreimageOffset Word // note that the offset includes the 8-byte length prefix
Heap Word // to handle mmap growth
LLReservationActive bool // Whether there is an active memory reservation initiated 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
Heap Word // to handle mmap growth
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
LLOwnerThread Word // The id of the thread that holds the reservation on LLAddress
ExitCode uint8
Exited bool
......@@ -75,7 +83,7 @@ func CreateEmptyState() *State {
return &State{
Memory: memory.NewMemory(),
Heap: 0,
LLReservationActive: false,
LLReservationStatus: LLStatusNone,
LLAddress: 0,
LLOwnerThread: 0,
ExitCode: 0,
......@@ -199,7 +207,7 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = append(out, s.PreimageKey[:]...)
out = arch.ByteOrderWord.AppendWord(out, s.PreimageOffset)
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.LLOwnerThread)
out = append(out, s.ExitCode)
......@@ -278,7 +286,7 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.Heap); err != nil {
return err
}
if err := bout.WriteBool(s.LLReservationActive); err != nil {
if err := bout.WriteUInt(s.LLReservationStatus); err != nil {
return err
}
if err := bout.WriteUInt(s.LLAddress); err != nil {
......@@ -347,7 +355,7 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.Heap); err != nil {
return err
}
if err := bin.ReadBool(&s.LLReservationActive); err != nil {
if err := bin.ReadUInt(&s.LLReservationStatus); err != nil {
return err
}
if err := bin.ReadUInt(&s.LLAddress); err != nil {
......
......@@ -56,7 +56,7 @@ func TestState_EncodeWitness(t *testing.T) {
state.PreimageKey = preimageKey
state.PreimageOffset = preimageOffset
state.Heap = heap
state.LLReservationActive = true
state.LLReservationStatus = LLStatusActive32bit
state.LLAddress = llAddress
state.LLOwnerThread = llThreadOwner
state.Step = step
......@@ -185,7 +185,7 @@ func TestSerializeStateRoundTrip(t *testing.T) {
PreimageKey: common.Hash{0xFF},
PreimageOffset: 5,
Heap: 0xc0ffee,
LLReservationActive: true,
LLReservationStatus: LLStatusActive64bit,
LLAddress: 0x12345678,
LLOwnerThread: 0x02,
ExitCode: 1,
......
package testutil
import (
"bytes"
"fmt"
"github.com/ethereum/go-ethereum/common"
......@@ -10,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"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
......@@ -18,7 +20,7 @@ type ExpectedMTState struct {
PreimageKey common.Hash
PreimageOffset arch.Word
Heap arch.Word
LLReservationActive bool
LLReservationStatus multithreaded.LLReservationStatus
LLAddress arch.Word
LLOwnerThread arch.Word
ExitCode uint8
......@@ -69,7 +71,7 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
PreimageKey: fromState.GetPreimageKey(),
PreimageOffset: fromState.GetPreimageOffset(),
Heap: fromState.GetHeap(),
LLReservationActive: fromState.LLReservationActive,
LLReservationStatus: fromState.LLReservationStatus,
LLAddress: fromState.LLAddress,
LLOwnerThread: fromState.LLOwnerThread,
ExitCode: fromState.GetExitCode(),
......@@ -119,8 +121,15 @@ func (e *ExpectedMTState) ExpectStep() {
e.StepsSinceLastContextSwitch += 1
}
func (e *ExpectedMTState) ExpectMemoryWrite(addr arch.Word, val uint32) {
e.expectedMemory.SetUint32(addr, val)
func (e *ExpectedMTState) ExpectMemoryWriteUint32(t require.TestingT, addr arch.Word, val uint32) {
// 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()
}
......@@ -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.PreimageOffset, actualState.GetPreimageOffset(), "Expect preimageOffset = %v", e.PreimageOffset)
require.Equalf(t, e.Heap, actualState.GetHeap(), "Expect heap = 0x%x", e.Heap)
require.Equalf(t, e.LLReservationActive, actualState.LLReservationActive, "Expect LLReservationActive = %v", e.LLReservationActive)
require.Equalf(t, e.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.LLOwnerThread, actualState.LLOwnerThread, "Expect LLOwnerThread = %v", e.LLOwnerThread)
require.Equalf(t, e.ExitCode, actualState.GetExitCode(), "Expect exitCode = 0x%x", e.ExitCode)
......
......@@ -29,7 +29,7 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "PreimageKey", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageKey = emptyHash }},
{name: "PreimageOffset", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.PreimageOffset += 1 }},
{name: "Heap", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Heap += 1 }},
{name: "LLReservationActive", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLReservationActive = !e.LLReservationActive }},
{name: "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: "LLOwnerThread", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LLOwnerThread += 1 }},
{name: "ExitCode", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ExitCode += 1 }},
......
......@@ -36,8 +36,8 @@ func (m *StateMutatorMultiThreaded) Randomize(randSeed int64) {
// Randomize memory-related fields
halfMemory := math.MaxUint32 / 2
m.state.Heap = arch.Word(r.Intn(halfMemory) + halfMemory)
m.state.LLReservationActive = r.Intn(2) == 1
if m.state.LLReservationActive {
m.state.LLReservationStatus = multithreaded.LLReservationStatus(r.Intn(3))
if m.state.LLReservationStatus != multithreaded.LLStatusNone {
m.state.LLAddress = arch.Word(r.Intn(halfMemory))
m.state.LLOwnerThread = arch.Word(r.Intn(10))
}
......
......@@ -21,7 +21,7 @@ func RandomThread(randSeed int64) *multithreaded.ThreadState {
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))
state.NextThreadId = singleThread.ThreadId + 1
......@@ -33,6 +33,11 @@ func InitializeSingleThread(randSeed int, state *multithreaded.State, traverseRi
state.RightThreadStack = []*multithreaded.ThreadState{}
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) {
......
//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
import (
......@@ -27,19 +28,28 @@ type Word = arch.Word
func TestEVM_MT_LL(t *testing.T) {
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 {
name string
base Word
offset int
value Word
effAddr Word
rtReg int
name string
base uint64
offset int
expectedAddr uint64
memValue uint64
retVal uint64
rtReg int
}{
{name: "Aligned effAddr", base: 0x00_00_00_01, offset: 0x0133, value: 0xABCD, effAddr: 0x00_00_01_34, rtReg: 5},
{name: "Aligned effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, rtReg: 5},
{name: "Unaligned effAddr", base: 0xFF_12_00_01, offset: 0x3401, value: 0xABCD, effAddr: 0xFF_12_34_00, rtReg: 5},
{name: "Unaligned effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 5},
{name: "Return register set to 0", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, rtReg: 0},
{name: "Aligned addr", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, memValue: posValue, retVal: posValueRet, rtReg: 5},
{name: "Aligned addr, negative value", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, memValue: negValue, retVal: negRetValue, 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 addr", base: 0xFF12_0001, offset: 0x3405, expectedAddr: 0xFF12_3406, memValue: posValue, retVal: posValueRet, rtReg: 5},
{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 _, withExistingReservation := range []bool{true, false} {
......@@ -47,23 +57,20 @@ func TestEVM_MT_LL(t *testing.T) {
t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := Word(0x44)
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()
// Set up state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetUint32(pc, insn)
state.GetMemory().SetWord(c.effAddr, c.value)
state.GetRegistersRef()[baseReg] = c.base
testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), c.memValue)
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetRegistersRef()[baseReg] = Word(c.base)
if withExistingReservation {
state.LLReservationActive = true
state.LLAddress = c.effAddr + Word(4)
state.LLReservationStatus = multithreaded.LLStatusActive32bit
state.LLAddress = Word(c.expectedAddr + 1)
state.LLOwnerThread = 123
} else {
state.LLReservationActive = false
state.LLReservationStatus = multithreaded.LLStatusNone
state.LLAddress = 0
state.LLOwnerThread = 0
}
......@@ -71,11 +78,11 @@ func TestEVM_MT_LL(t *testing.T) {
// Set up expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.LLReservationActive = true
expected.LLAddress = c.effAddr
expected.LLReservationStatus = multithreaded.LLStatusActive32bit
expected.LLAddress = Word(c.expectedAddr)
expected.LLOwnerThread = state.GetCurrentThread().ThreadId
if rtReg != 0 {
expected.ActiveThread().Registers[rtReg] = c.value
expected.ActiveThread().Registers[rtReg] = Word(c.retVal)
}
stepWitness, err := goVm.Step(true)
......@@ -92,35 +99,39 @@ func TestEVM_MT_LL(t *testing.T) {
func TestEVM_MT_SC(t *testing.T) {
var tracer *tracing.Hooks
// Set up some test values that will be reused
memValue := uint64(0x1122_3344_5566_7788)
llVariations := []struct {
name string
llReservationActive bool
llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool
matchEffAddr bool
matchAddr bool
shouldSucceed bool
}{
{name: "should succeed", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldSucceed: true},
{name: "mismatch addr", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldSucceed: false},
{name: "mismatched thread", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldSucceed: false},
{name: "no active reservation", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldSucceed: false},
{name: "should succeed", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: true, shouldSucceed: true},
{name: "mismatch thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: true, shouldSucceed: false},
{name: "mismatched addr", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchAddr: false, shouldSucceed: false},
{name: "mismatched addr & thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchAddr: false, 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 {
name string
base Word
offset int
value Word
effAddr Word
rtReg int
threadId Word
name string
base Word
offset int
expectedAddr uint64
storeValue uint32
rtReg int
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 effAddr, signed extended", base: 0x00_00_00_01, offset: 0xFF33, value: 0xABCD, effAddr: 0xFF_FF_FF_34, 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 effAddr, sign extended w overflow", base: 0xFF_12_00_01, offset: 0x8401, value: 0xABCD, effAddr: 0xFF_11_84_00, 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: "Zero valued ll args", base: 0x00_00_00_00, offset: 0x0, value: 0xABCD, effAddr: 0x00_00_00_00, rtReg: 5, threadId: 0},
{name: "Aligned addr", base: 0x01, offset: 0x0133, expectedAddr: 0x0134, storeValue: 0xAABB_CCDD, 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 addr", base: 0xFF12_0001, offset: 0x3404, expectedAddr: 0xFF12_3405, storeValue: 0xAABB_CCDD, 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: 0xFF12_0001, offset: 0x7403, expectedAddr: 0xFF12_7404, storeValue: 0xAABB_CCDD, rtReg: 0, threadId: 4},
}
for i, c := range cases {
for _, v := range llVariations {
......@@ -128,18 +139,17 @@ func TestEVM_MT_SC(t *testing.T) {
t.Run(tName, func(t *testing.T) {
rtReg := c.rtReg
baseReg := 6
pc := Word(0x44)
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)
mttestutil.InitializeSingleThread(i*23456, state, i%2 == 1, testutil.WithPCAndNextPC(0x40))
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = c.effAddr
if v.matchAddr {
llAddress = Word(c.expectedAddr)
} else {
llAddress = c.effAddr + 4
llAddress = Word(c.expectedAddr) + 1
}
if v.matchThreadId {
llOwnerThread = c.threadId
......@@ -148,13 +158,12 @@ func TestEVM_MT_SC(t *testing.T) {
}
// Setup state
testutil.SetMemoryUint64(t, state.GetMemory(), Word(c.expectedAddr), memValue)
state.GetCurrentThread().ThreadId = c.threadId
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetMemory().SetUint32(pc, insn)
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetRegistersRef()[baseReg] = c.base
state.GetRegistersRef()[rtReg] = c.value
state.LLReservationActive = v.llReservationActive
state.GetRegistersRef()[rtReg] = Word(c.storeValue)
state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
......@@ -164,8 +173,8 @@ func TestEVM_MT_SC(t *testing.T) {
var retVal Word
if v.shouldSucceed {
retVal = 1
expected.ExpectMemoryWordWrite(c.effAddr, c.value)
expected.LLReservationActive = false
expected.ExpectMemoryWriteUint32(t, Word(c.expectedAddr), c.storeValue)
expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0
expected.LLOwnerThread = 0
} else {
......@@ -195,17 +204,18 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
llVariations := []struct {
name string
llReservationActive bool
llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool
matchEffAddr bool
effAddrOffset Word
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true},
{name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, effAddrOffset: 8, shouldClearReservation: false},
{name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive64bit, 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},
}
cases := []struct {
......@@ -246,16 +256,10 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
step := state.GetStep()
// Define LL-related params
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = effAddr
} else {
llAddress = effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
llAddress := effAddr + v.effAddrOffset
llOwnerThread := state.GetCurrentThread().ThreadId
if !v.matchThreadId {
llOwnerThread += 1
}
// Set up state
......@@ -266,7 +270,7 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
state.GetRegistersRef()[5] = c.addr
state.GetRegistersRef()[6] = c.count
state.GetMemory().SetUint32(state.GetPC(), syscallInsn)
state.LLReservationActive = v.llReservationActive
state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
state.GetMemory().SetUint32(effAddr, c.prestateMem)
......@@ -277,9 +281,9 @@ func TestEVM_MT_SysRead_Preimage(t *testing.T) {
expected.ActiveThread().Registers[2] = c.writeLen
expected.ActiveThread().Registers[7] = 0 // no error
expected.PreimageOffset += c.writeLen
expected.ExpectMemoryWrite(effAddr, c.postateMem)
expected.ExpectMemoryWriteUint32(t, effAddr, c.postateMem)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
......@@ -305,20 +309,22 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
llVariations := []struct {
name string
llReservationActive bool
llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool
matchEffAddr bool
effAddrOffset Word
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, unaligned", llReservationStatus: multithreaded.LLStatusActive32bit, effAddrOffset: 1, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, shouldClearReservation: true},
{name: "matching reservation, diff thread, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: false, shouldClearReservation: true},
{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)
baseReg := 5
rtReg := 6
......@@ -342,39 +348,31 @@ func TestEVM_MT_StoreOpsClearMemReservation(t *testing.T) {
tName := fmt.Sprintf("%v (%v)", c.name, v.name)
t.Run(tName, func(t *testing.T) {
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()
// Define LL-related params
var llAddress, llOwnerThread Word
if v.matchEffAddr {
llAddress = c.effAddr
} else {
llAddress = c.effAddr + 4
}
if v.matchThreadId {
llOwnerThread = state.GetCurrentThread().ThreadId
} else {
llOwnerThread = state.GetCurrentThread().ThreadId + 1
llAddress := c.effAddr + v.effAddrOffset
llOwnerThread := state.GetCurrentThread().ThreadId
if !v.matchThreadId {
llOwnerThread += 1
}
// Setup state
state.GetCurrentThread().Cpu.PC = pc
state.GetCurrentThread().Cpu.NextPC = pc + 4
state.GetRegistersRef()[rtReg] = rt
state.GetRegistersRef()[baseReg] = c.base
state.GetMemory().SetUint32(state.GetPC(), insn)
state.GetMemory().SetUint32(c.effAddr, c.preMem)
state.LLReservationActive = v.llReservationActive
state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.ExpectStep()
expected.ExpectMemoryWrite(c.effAddr, c.postMem)
expected.ExpectMemoryWriteUint32(t, c.effAddr, c.postMem)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
......@@ -980,21 +978,23 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) {
llVariations := []struct {
name string
llReservationActive bool
llReservationStatus multithreaded.LLReservationStatus
matchThreadId bool
matchEffAddr bool
matchEffAddr2 bool
shouldClearReservation bool
}{
{name: "matching reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, 2nd word", llReservationActive: true, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread, 2nd word", llReservationActive: true, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationActive: true, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationActive: true, matchThreadId: false, matchEffAddr: false, shouldClearReservation: false},
{name: "no reservation, matching addr", llReservationActive: false, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "no reservation, matching addr2", llReservationActive: false, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "no reservation, mismatched addr", llReservationActive: false, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "matching reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, 2nd word", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "matching reservation, 2nd word, 64-bit", llReservationStatus: multithreaded.LLStatusActive64bit, matchThreadId: true, matchEffAddr2: true, shouldClearReservation: true},
{name: "matching reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr: true, shouldClearReservation: true},
{name: "matching reservation, diff thread, 2nd word", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, matchEffAddr2: true, shouldClearReservation: true},
{name: "mismatched reservation", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: true, matchEffAddr: false, shouldClearReservation: false},
{name: "mismatched reservation, diff thread", llReservationStatus: multithreaded.LLStatusActive32bit, matchThreadId: false, 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 {
......@@ -1033,7 +1033,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) {
state.GetRegistersRef()[2] = arch.SysClockGetTime // Set syscall number
state.GetRegistersRef()[4] = clkid // a0
state.GetRegistersRef()[5] = c.timespecAddr // a1
state.LLReservationActive = v.llReservationActive
state.LLReservationStatus = v.llReservationStatus
state.LLAddress = llAddress
state.LLOwnerThread = llOwnerThread
......@@ -1050,7 +1050,7 @@ func testEVM_SysClockGettime(t *testing.T, clkid Word) {
expected.ExpectMemoryWordWrite(effAddr, secs)
expected.ExpectMemoryWordWrite(effAddr2, nsecs)
if v.shouldClearReservation {
expected.LLReservationActive = false
expected.LLReservationStatus = multithreaded.LLStatusNone
expected.LLAddress = 0
expected.LLOwnerThread = 0
}
......@@ -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)
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)
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) {
// 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) {
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.SetTracer(tracer)
LogStepFailureAtCleanup(t, evm)
......
......@@ -119,7 +119,7 @@ func WithRandomization(seed int64) StateOption {
func AlignPC(pc arch.Word) arch.Word {
// Memory-align random pc and leave room for nextPC
pc = pc & arch.AddressMask // Align address
if pc >= arch.AddressMask && arch.IsMips32 {
if pc >= arch.AddressMask {
// Leave room to set and then increment nextPC
pc = arch.AddressMask - 8
}
......
......@@ -144,8 +144,8 @@
"sourceCodeHash": "0xaf7416f27db1b393092f51d290a29293184105bc5f0d89cd6048f687cebc7d69"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0x9ba94a69090a8c89786cdb2a5980deba4b5b16bbf5909f8275e090dbcd65e5c3",
"sourceCodeHash": "0x3859b4bf63f485800b0eb6ffb83a79c8d134f7e4cbbe93fbc72cc2ccd4f91b82"
"initCodeHash": "0xd04c55d731400f777f4bb7c6520943e0f350868122bf992377ee3262cda8ee90",
"sourceCodeHash": "0x0fd936b1b09a5c3cb4e7ae71c294f168ce9a57a173bcd56b9f20383624de296e"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x64ea814bf9769257c91da57928675d3f8462374b0c23bdf860ccfc79f41f7801",
......
......@@ -34,6 +34,9 @@ contract MIPS2 is ISemver {
uint32[32] registers;
}
uint8 internal constant LL_STATUS_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE = 1;
/// @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
/// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
......@@ -42,7 +45,7 @@ contract MIPS2 is ISemver {
bytes32 preimageKey;
uint32 preimageOffset;
uint32 heap;
bool llReservationActive;
uint8 llReservationStatus;
uint32 llAddress;
uint32 llOwnerThread;
uint8 exitCode;
......@@ -57,8 +60,8 @@ contract MIPS2 is ISemver {
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.15
string public constant version = "1.0.0-beta.15";
/// @custom:semver 1.0.0-beta.16
string public constant version = "1.0.0-beta.16";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -141,7 +144,7 @@ contract MIPS2 is ISemver {
c, m := putField(c, m, 32) // preimageKey
c, m := putField(c, m, 4) // preimageOffset
c, m := putField(c, m, 4) // heap
c, m := putField(c, m, 1) // llReservationActive
c, m := putField(c, m, 1) // llReservationStatus
c, m := putField(c, m, 4) // llAddress
c, m := putField(c, m, 4) // llOwnerThread
c, m := putField(c, m, 1) // exitCode
......@@ -269,14 +272,14 @@ contract MIPS2 is ISemver {
}
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
clearLLMemoryReservation(_state);
}
}
function clearLLMemoryReservation(State memory _state) internal pure {
_state.llReservationActive = false;
_state.llReservationStatus = LL_STATUS_NONE;
_state.llAddress = 0;
_state.llOwnerThread = 0;
}
......@@ -295,25 +298,28 @@ contract MIPS2 is ISemver {
uint32 base = _thread.registers[baseReg];
uint32 rtReg = (_insn >> 16) & 0x1F;
uint32 offset = ins.signExtendImmediate(_insn);
uint32 effAddr = (base + offset) & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1);
uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);
uint32 addr = base + offset;
uint32 retVal = 0;
uint32 threadId = _thread.threadID;
if (_opcode == ins.OP_LOAD_LINKED) {
retVal = mem;
_state.llReservationActive = true;
_state.llAddress = effAddr;
retVal = loadWord(_state, addr);
_state.llReservationStatus = LL_STATUS_ACTIVE;
_state.llAddress = addr;
_state.llOwnerThread = threadId;
} else if (_opcode == ins.OP_STORE_CONDITIONAL) {
// Check if our memory reservation is still intact
if (_state.llReservationActive && _state.llOwnerThread == threadId && _state.llAddress == effAddr) {
if (
_state.llReservationStatus == LL_STATUS_ACTIVE && _state.llOwnerThread == threadId
&& _state.llAddress == addr
) {
// Complete atomic update: set memory and return 1 for success
clearLLMemoryReservation(_state);
uint32 val = _thread.registers[rtReg];
_state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val);
storeWord(_state, addr, val);
retVal = 1;
} else {
// Atomic update failed, return 0 for failure
......@@ -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_) {
unchecked {
// Load state from memory offsets to reduce stack pressure
......@@ -632,7 +650,7 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 32) // preimageKey
from, to := copyMem(from, to, 4) // preimageOffset
from, to := copyMem(from, to, 4) // heap
from, to := copyMem(from, to, 1) // llReservationActive
from, to := copyMem(from, to, 1) // llReservationStatus
from, to := copyMem(from, to, 4) // llAddress
from, to := copyMem(from, to, 4) // llOwnerThread
let exitCode := mload(from)
......
......@@ -185,7 +185,7 @@ contract MIPS2_Test is CommonTest {
preimageKey: bytes32(0),
preimageOffset: 0,
heap: 0,
llReservationActive: false,
llReservationStatus: 0,
llAddress: 0,
llOwnerThread: 0,
exitCode: 0,
......@@ -2017,7 +2017,7 @@ contract MIPS2_Test is CommonTest {
updateThreadStacks(state, thread);
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, memVal);
expect.llReservationActive = true;
expect.llReservationStatus = 1;
expect.llAddress = effAddr;
expect.llOwnerThread = thread.threadID;
......@@ -2034,7 +2034,7 @@ contract MIPS2_Test is CommonTest {
(MIPS2.State memory state, MIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, effAddr, 0);
state.llReservationActive = true;
state.llReservationStatus = 1;
state.llAddress = effAddr;
state.llOwnerThread = thread.threadID;
thread.registers[8] = writeMemVal;
......@@ -2044,7 +2044,7 @@ contract MIPS2_Test is CommonTest {
MIPS2.State memory expect = arithmeticPostState(state, thread, 8, 0x1);
(expect.memRoot,) = ffi.getCannonMemoryProof(0, insn, effAddr, writeMemVal);
expect.llReservationActive = false;
expect.llReservationStatus = 0;
expect.llAddress = 0;
expect.llOwnerThread = 0;
......@@ -2764,7 +2764,7 @@ contract MIPS2_Test is CommonTest {
_state.preimageKey,
_state.preimageOffset,
_state.heap,
_state.llReservationActive,
_state.llReservationStatus,
_state.llAddress,
_state.llOwnerThread,
_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