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

cannon: Use 32-bit futex values (#13453)

* cannon: Update futex-related tests - use 32-bit futex values

* cannon: Update go vm implementation - use 32-bit futex values

* cannon: Update sol 64-bit vm implementation - use 32-bit futex values

* cannon: Update sol 32-bit vm implementation - use 32-bit futex values

* cannon: Update MIPS64 version

* cannon: Update MIPS2 version

* cannon: Run semver lock

* cannon: Update semver comments to match actual version

* cannon: Run semver-lock

* cannon: Randomize futex value in register when testing

* cannon: Tweak comment

* cannon: Ignore the upper bytes in the futex value register

* cannon: Make FutexVal thread field 32-bit

* cannon: Run semver-lock

* cannon: Fix some inconsistencies, run semver-lock

* cannon: Rename testutil method
parent bf7e95ce
...@@ -109,17 +109,18 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -109,17 +109,18 @@ func (m *InstrumentedState) handleSyscall() error {
return nil return nil
case arch.SysFutex: case arch.SysFutex:
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout // args: a0 = addr, a1 = op, a2 = val, a3 = timeout
effAddr := a0 & arch.AddressMask // Futex value is 32-bit, so clear the lower 2 bits to get an effective address targeting a 4-byte value
effFutexAddr := a0 & ^Word(0x3)
switch a1 { switch a1 {
case exec.FutexWaitPrivate: case exec.FutexWaitPrivate:
m.memoryTracker.TrackMemAccess(effAddr) futexVal := m.getFutexValue(effFutexAddr)
mem := m.state.Memory.GetWord(effAddr) targetVal := uint32(a2)
if mem != a2 { if futexVal != targetVal {
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN v1 = exec.MipsEAGAIN
} else { } else {
thread.FutexAddr = effAddr thread.FutexAddr = effFutexAddr
thread.FutexVal = a2 thread.FutexVal = targetVal
if a3 == 0 { if a3 == 0 {
thread.FutexTimeoutStep = exec.FutexNoTimeout thread.FutexTimeoutStep = exec.FutexNoTimeout
} else { } else {
...@@ -130,7 +131,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -130,7 +131,7 @@ func (m *InstrumentedState) handleSyscall() error {
} }
case exec.FutexWakePrivate: case exec.FutexWakePrivate:
// Trigger a wakeup traversal // Trigger a wakeup traversal
m.state.Wakeup = effAddr m.state.Wakeup = effFutexAddr
// Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees. // Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees.
// The woken up thread should indicate this in userspace. // The woken up thread should indicate this in userspace.
v0 = 0 v0 = 0
...@@ -283,10 +284,8 @@ func (m *InstrumentedState) doMipsStep() error { ...@@ -283,10 +284,8 @@ func (m *InstrumentedState) doMipsStep() error {
m.onWaitComplete(thread, true) m.onWaitComplete(thread, true)
return nil return nil
} else { } else {
effAddr := thread.FutexAddr & arch.AddressMask futexVal := m.getFutexValue(thread.FutexAddr)
m.memoryTracker.TrackMemAccess(effAddr) if thread.FutexVal == futexVal {
mem := m.state.Memory.GetWord(effAddr)
if thread.FutexVal == mem {
// still got expected value, continue sleeping, try next thread. // still got expected value, continue sleeping, try next thread.
m.preemptThread(thread) m.preemptThread(thread)
m.statsTracker.trackWakeupFail() m.statsTracker.trackWakeupFail()
...@@ -488,3 +487,8 @@ func (m *InstrumentedState) popThread() { ...@@ -488,3 +487,8 @@ func (m *InstrumentedState) popThread() {
func (m *InstrumentedState) lastThreadRemaining() bool { func (m *InstrumentedState) lastThreadRemaining() bool {
return m.state.ThreadCount() == 1 return m.state.ThreadCount() == 1
} }
func (m *InstrumentedState) getFutexValue(vAddr Word) uint32 {
subword := exec.LoadSubWord(m.state.GetMemory(), vAddr, Word(4), false, m.memoryTracker)
return uint32(subword)
}
...@@ -399,7 +399,7 @@ func TestStateWitnessSize(t *testing.T) { ...@@ -399,7 +399,7 @@ func TestStateWitnessSize(t *testing.T) {
func TestThreadStateWitnessSize(t *testing.T) { func TestThreadStateWitnessSize(t *testing.T) {
expectedWitnessSize := 166 expectedWitnessSize := 166
if !arch.IsMips32 { if !arch.IsMips32 {
expectedWitnessSize = 322 expectedWitnessSize = 318
} }
require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE) require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE)
} }
...@@ -48,7 +48,7 @@ type ExpectedThreadState struct { ...@@ -48,7 +48,7 @@ type ExpectedThreadState struct {
ExitCode uint8 ExitCode uint8
Exited bool Exited bool
FutexAddr arch.Word FutexAddr arch.Word
FutexVal arch.Word FutexVal uint32
FutexTimeoutStep uint64 FutexTimeoutStep uint64
PC arch.Word PC arch.Word
NextPC arch.Word NextPC arch.Word
......
...@@ -18,12 +18,12 @@ const ( ...@@ -18,12 +18,12 @@ const (
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1 THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1
THREAD_FUTEX_ADDR_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1 THREAD_FUTEX_ADDR_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1
THREAD_FUTEX_VAL_WITNESS_OFFSET = THREAD_FUTEX_ADDR_WITNESS_OFFSET + arch.WordSizeBytes THREAD_FUTEX_VAL_WITNESS_OFFSET = THREAD_FUTEX_ADDR_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET = THREAD_FUTEX_VAL_WITNESS_OFFSET + arch.WordSizeBytes THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET = THREAD_FUTEX_VAL_WITNESS_OFFSET + 4
THREAD_FUTEX_CPU_WITNESS_OFFSET = THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET + 8 THREAD_FUTEX_CPU_WITNESS_OFFSET = THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET + 8
THREAD_REGISTERS_WITNESS_OFFSET = THREAD_FUTEX_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes) THREAD_REGISTERS_WITNESS_OFFSET = THREAD_FUTEX_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object // SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
// 166 and 322 bytes for 32 and 64-bit respectively // 166 and 318 bytes for 32 and 64-bit respectively
SERIALIZED_THREAD_SIZE = THREAD_REGISTERS_WITNESS_OFFSET + (32 * arch.WordSizeBytes) SERIALIZED_THREAD_SIZE = THREAD_REGISTERS_WITNESS_OFFSET + (32 * arch.WordSizeBytes)
// THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes. // THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes.
...@@ -41,7 +41,7 @@ type ThreadState struct { ...@@ -41,7 +41,7 @@ type ThreadState struct {
ExitCode uint8 `json:"exit"` ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"` Exited bool `json:"exited"`
FutexAddr Word `json:"futexAddr"` FutexAddr Word `json:"futexAddr"`
FutexVal Word `json:"futexVal"` FutexVal uint32 `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"` FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu mipsevm.CpuScalars `json:"cpu"` Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"` Registers [32]Word `json:"registers"`
...@@ -73,7 +73,7 @@ func (t *ThreadState) serializeThread() []byte { ...@@ -73,7 +73,7 @@ func (t *ThreadState) serializeThread() []byte {
out = append(out, t.ExitCode) out = append(out, t.ExitCode)
out = mipsevm.AppendBoolToWitness(out, t.Exited) out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = arch.ByteOrderWord.AppendWord(out, t.FutexAddr) out = arch.ByteOrderWord.AppendWord(out, t.FutexAddr)
out = arch.ByteOrderWord.AppendWord(out, t.FutexVal) out = binary.BigEndian.AppendUint32(out, t.FutexVal)
out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep) out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.PC) out = arch.ByteOrderWord.AppendWord(out, t.Cpu.PC)
......
...@@ -489,32 +489,37 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { ...@@ -489,32 +489,37 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
name string name string
addressParam uint64 addressParam uint64
effAddr uint64 effAddr uint64
targetValue uint64 targetValue uint32
actualValue uint64 actualValue uint32
timeout uint64 timeout uint64
shouldFail bool shouldFail bool
shouldSetTimeout bool shouldSetTimeout bool
}{ }{
{name: "successful wait, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01}, {name: "successful wait, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_01},
{name: "successful wait, no timeout, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_12_39, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01}, {name: "successful wait, no timeout, unaligned addr #1", addressParam: 0xFF_FF_FF_FF_FF_FF_12_33, effAddr: 0xFF_FF_FF_FF_FF_FF_12_30, targetValue: 0x01, actualValue: 0x01},
{name: "memory mismatch, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, shouldFail: true}, {name: "successful wait, no timeout, unaligned addr #2", addressParam: 0xFF_FF_FF_FF_FF_FF_12_37, effAddr: 0xFF_FF_FF_FF_FF_FF_12_34, targetValue: 0x01, actualValue: 0x01},
{name: "memory mismatch, no timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_03, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, shouldFail: true}, {name: "successful wait, no timeout, unaligned addr #3", addressParam: 0xFF_FF_FF_FF_FF_FF_12_3A, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0x01, actualValue: 0x01},
{name: "successful wait w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true}, {name: "successful wait, no timeout, unaligned addr #4", addressParam: 0xFF_FF_FF_FF_FF_FF_12_3F, effAddr: 0xFF_FF_FF_FF_FF_FF_12_3C, targetValue: 0x01, actualValue: 0x01},
{name: "successful wait w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_32, effAddr: 0xFF_FF_FF_FF_FF_FF_12_30, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true}, {name: "memory mismatch, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_02, shouldFail: true},
{name: "memory mismatch w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, timeout: 2000000, shouldFail: true}, {name: "memory mismatch, no timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_05, effAddr: 0xFF_FF_FF_FF_FF_FF_12_04, targetValue: 0x01, actualValue: 0x02, shouldFail: true},
{name: "memory mismatch w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_0F, effAddr: 0xFF_FF_FF_FF_FF_FF_12_10, targetValue: 0xFF_FF_FF_FF_FF_FF_FF_01, actualValue: 0xFF_FF_FF_FF_FF_FF_FF_02, timeout: 2000000, shouldFail: true}, {name: "successful wait w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true},
{name: "successful wait w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_37, effAddr: 0xFF_FF_FF_FF_FF_FF_12_34, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_01, timeout: 1000000, shouldSetTimeout: true},
{name: "memory mismatch w timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_00, effAddr: 0xFF_FF_FF_FF_FF_FF_12_00, targetValue: 0xFF_FF_FF_F8, actualValue: 0xF8, timeout: 2000000, shouldFail: true},
{name: "memory mismatch w timeout, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_12_0F, effAddr: 0xFF_FF_FF_FF_FF_FF_12_0C, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_02, timeout: 2000000, shouldFail: true},
} }
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1234, nil) rand := testutil.NewRandHelper(int64(i * 33))
goVm, state, contracts := setup(t, i*1234, nil, testutil.WithPCAndNextPC(0x04))
step := state.GetStep() step := state.GetStep()
testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn) testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn)
state.Memory.SetWord(Word(c.effAddr), Word(c.actualValue)) testutil.RandomizeWordAndSetUint32(state.GetMemory(), Word(c.effAddr), c.actualValue, int64(i+22))
state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number state.GetRegistersRef()[2] = arch.SysFutex // Set syscall number
state.GetRegistersRef()[4] = Word(c.addressParam) state.GetRegistersRef()[4] = Word(c.addressParam)
state.GetRegistersRef()[5] = exec.FutexWaitPrivate state.GetRegistersRef()[5] = exec.FutexWaitPrivate
state.GetRegistersRef()[6] = Word(c.targetValue) // Randomize upper bytes of futex target
state.GetRegistersRef()[6] = (rand.Word() & ^Word(0xFF_FF_FF_FF)) | Word(c.targetValue)
state.GetRegistersRef()[7] = Word(c.timeout) state.GetRegistersRef()[7] = Word(c.timeout)
// Setup expectations // Setup expectations
...@@ -529,7 +534,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) { ...@@ -529,7 +534,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
} else { } else {
// PC and return registers should not update on success, updates happen when wait completes // PC and return registers should not update on success, updates happen when wait completes
expected.ActiveThread().FutexAddr = Word(c.effAddr) expected.ActiveThread().FutexAddr = Word(c.effAddr)
expected.ActiveThread().FutexVal = Word(c.targetValue) expected.ActiveThread().FutexVal = c.targetValue
expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout
if c.shouldSetTimeout { if c.shouldSetTimeout {
expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1 expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1
...@@ -559,7 +564,10 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { ...@@ -559,7 +564,10 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
expectTraverseRight bool expectTraverseRight bool
}{ }{
{name: "Traverse right", addressParam: 0xFF_FF_FF_FF_FF_FF_67_00, effAddr: 0xFF_FF_FF_FF_FF_FF_67_00, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, {name: "Traverse right", addressParam: 0xFF_FF_FF_FF_FF_FF_67_00, effAddr: 0xFF_FF_FF_FF_FF_FF_67_00, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true},
{name: "Traverse right, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true}, {name: "Traverse right, unaligned addr #1", addressParam: 0xFF_FF_FF_FF_FF_FF_67_83, effAddr: 0xFF_FF_FF_FF_FF_FF_67_80, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true},
{name: "Traverse right, unaligned addr #2", addressParam: 0xFF_FF_FF_FF_FF_FF_67_87, effAddr: 0xFF_FF_FF_FF_FF_FF_67_84, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true},
{name: "Traverse right, unaligned addr #3", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true},
{name: "Traverse right, unaligned addr #4", addressParam: 0xFF_FF_FF_FF_FF_FF_67_8F, effAddr: 0xFF_FF_FF_FF_FF_FF_67_8C, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: true},
{name: "Traverse right, no left threads", addressParam: 0xFF_FF_FF_FF_FF_FF_67_84, effAddr: 0xFF_FF_FF_FF_FF_FF_67_84, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, {name: "Traverse right, no left threads", addressParam: 0xFF_FF_FF_FF_FF_FF_67_84, effAddr: 0xFF_FF_FF_FF_FF_FF_67_84, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true},
{name: "Traverse right, no left threads, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_8E, effAddr: 0xFF_FF_FF_FF_FF_FF_67_8C, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true}, {name: "Traverse right, no left threads, unaligned addr", addressParam: 0xFF_FF_FF_FF_FF_FF_67_8E, effAddr: 0xFF_FF_FF_FF_FF_FF_67_8C, activeThreadCount: 2, inactiveThreadCount: 0, traverseRight: true},
{name: "Traverse right, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true}, {name: "Traverse right, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: true},
...@@ -567,7 +575,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { ...@@ -567,7 +575,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
{name: "Traverse left", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, {name: "Traverse left", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false},
{name: "Traverse left, unaliagned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, {name: "Traverse left, unaliagned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false},
{name: "Traverse left, switch directions", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, {name: "Traverse left, switch directions", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true},
{name: "Traverse left, switch directions, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, {name: "Traverse left, switch directions, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_8F, effAddr: 0xFF_FF_FF_FF_FF_FF_67_8C, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true},
{name: "Traverse left, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, {name: "Traverse left, single thread", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true},
{name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, {name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true},
} }
...@@ -587,7 +595,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { ...@@ -587,7 +595,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
expected.ExpectStep() expected.ExpectStep()
expected.ActiveThread().Registers[2] = 0 expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0 expected.ActiveThread().Registers[7] = 0
expected.Wakeup = Word(c.effAddr) & arch.AddressMask // aligned for 32 and 64-bit compatibility expected.Wakeup = Word(c.effAddr)
expected.ExpectPreemption(state) expected.ExpectPreemption(state)
expected.TraverseRight = c.expectTraverseRight expected.TraverseRight = c.expectTraverseRight
if c.traverseRight != c.expectTraverseRight { if c.traverseRight != c.expectTraverseRight {
...@@ -1012,26 +1020,33 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { ...@@ -1012,26 +1020,33 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
activeStackSize int activeStackSize int
otherStackSize int otherStackSize int
futexAddr Word futexAddr Word
targetValue Word effAddr Word
actualValue Word targetValue uint32
actualValue uint32
timeoutStep uint64 timeoutStep uint64
shouldWakeup bool shouldWakeup bool
shouldTimeout bool shouldTimeout bool
}{ }{
{name: "Preempt, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout}, {name: "Preempt, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, no timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout}, {name: "Preempt, no timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, no timeout #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout}, {name: "Preempt, no timeout #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, no timeout, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x101, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout}, {name: "Preempt, no timeout, unaligned #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x101, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 101}, {name: "Preempt, no timeout, unaligned #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x107, effAddr: 0x104, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, with timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150}, {name: "Preempt, no timeout, unaligned #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x109, effAddr: 0x108, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, with timeout, unaligned", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x101, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150}, {name: "Preempt, no timeout, unaligned #4", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x10F, effAddr: 0x10C, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Wakeup, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true}, {name: "Preempt, with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 101},
{name: "Wakeup, no timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true}, {name: "Preempt, with timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{name: "Wakeup, no timeout, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x102, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true}, {name: "Preempt, with timeout, unaligned", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x101, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{name: "Wakeup with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: true}, {name: "Wakeup, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup with timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: true}, {name: "Wakeup, no timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup with timeout #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 50, shouldWakeup: true, shouldTimeout: true}, {name: "Wakeup, no timeout, unaligned #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x102, effAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup with timeout, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x103, targetValue: 0x02, actualValue: 0x02, timeoutStep: 50, shouldWakeup: true, shouldTimeout: true}, {name: "Wakeup, no timeout, unaligned #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x105, effAddr: 0x104, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup, no timeout, unaligned #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x10B, effAddr: 0x108, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup, no timeout, unaligned #4", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x10E, effAddr: 0x10C, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: true},
{name: "Wakeup with timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: true},
{name: "Wakeup with timeout #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 50, shouldWakeup: true, shouldTimeout: true},
{name: "Wakeup with timeout, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x103, effAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 50, shouldWakeup: true, shouldTimeout: true},
} }
for _, c := range cases { for _, c := range cases {
...@@ -1042,7 +1057,6 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { ...@@ -1042,7 +1057,6 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
if !c.shouldWakeup && c.shouldTimeout { if !c.shouldWakeup && c.shouldTimeout {
require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup") require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup")
} }
effAddr := c.futexAddr & arch.AddressMask
goVm, state, contracts := setup(t, i, nil) goVm, state, contracts := setup(t, i, nil)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize) mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step state.Step = c.step
...@@ -1051,7 +1065,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) { ...@@ -1051,7 +1065,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
activeThread.FutexAddr = c.futexAddr activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetValue activeThread.FutexVal = c.targetValue
activeThread.FutexTimeoutStep = c.timeoutStep activeThread.FutexTimeoutStep = c.timeoutStep
state.GetMemory().SetWord(effAddr, c.actualValue) testutil.RandomizeWordAndSetUint32(state.GetMemory(), c.effAddr, c.actualValue, int64(i+11))
// Set up post-state expectations // Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state) expected := mttestutil.NewExpectedMTState(state)
...@@ -1145,12 +1159,12 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { ...@@ -1145,12 +1159,12 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
func TestEVM_WakeupTraversalStep(t *testing.T) { func TestEVM_WakeupTraversalStep(t *testing.T) {
addr := Word(0x1234) addr := Word(0x1234)
wakeupVal := Word(0x999) wakeupVal := uint32(0x999)
cases := []struct { cases := []struct {
name string name string
wakeupAddr Word wakeupAddr Word
futexAddr Word futexAddr Word
targetVal Word targetVal uint32
traverseRight bool traverseRight bool
activeStackSize int activeStackSize int
otherStackSize int otherStackSize int
...@@ -1188,7 +1202,8 @@ func TestEVM_WakeupTraversalStep(t *testing.T) { ...@@ -1188,7 +1202,8 @@ func TestEVM_WakeupTraversalStep(t *testing.T) {
step := state.Step step := state.Step
state.Wakeup = c.wakeupAddr state.Wakeup = c.wakeupAddr
state.GetMemory().SetWord(c.wakeupAddr&arch.AddressMask, wakeupVal) effWakeupAddr := ^Word(3) & c.wakeupAddr
testutil.RandomizeWordAndSetUint32(state.GetMemory(), effWakeupAddr, wakeupVal, int64(i+1000))
activeThread := state.GetCurrentThread() activeThread := state.GetCurrentThread()
activeThread.FutexAddr = c.futexAddr activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetVal activeThread.FutexVal = c.targetVal
...@@ -1271,13 +1286,13 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) { ...@@ -1271,13 +1286,13 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) {
} }
func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) { func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) {
addr := Word(0x1234) addr := Word(0x1230)
wakeupVal := Word(0x999) wakeupVal := uint32(0x999)
cases := []struct { cases := []struct {
name string name string
wakeupAddr Word wakeupAddr Word
futexAddr Word futexAddr Word
targetVal Word targetVal uint32
traverseRight bool traverseRight bool
activeStackSize int activeStackSize int
otherStackSize int otherStackSize int
...@@ -1285,7 +1300,7 @@ func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) { ...@@ -1285,7 +1300,7 @@ func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) {
shouldClearWakeup bool shouldClearWakeup bool
shouldPreempt bool shouldPreempt bool
activeThreadFutexAddr Word activeThreadFutexAddr Word
activeThreadFutexVal Word activeThreadFutexVal uint32
}{ }{
{name: "Wakeable thread exists among exited threads", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal + 1, traverseRight: false, activeStackSize: 3, otherStackSize: 1, exitedThreadIdx: []int{2}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 8, activeThreadFutexVal: wakeupVal + 2}, {name: "Wakeable thread exists among exited threads", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal + 1, traverseRight: false, activeStackSize: 3, otherStackSize: 1, exitedThreadIdx: []int{2}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 8, activeThreadFutexVal: wakeupVal + 2},
{name: "All threads exited", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 3, otherStackSize: 0, exitedThreadIdx: []int{1, 2}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 16, activeThreadFutexVal: wakeupVal + 3}, {name: "All threads exited", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 3, otherStackSize: 0, exitedThreadIdx: []int{1, 2}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 16, activeThreadFutexVal: wakeupVal + 3},
...@@ -1301,7 +1316,8 @@ func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) { ...@@ -1301,7 +1316,8 @@ func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) {
step := state.Step step := state.Step
state.Wakeup = c.wakeupAddr state.Wakeup = c.wakeupAddr
state.GetMemory().SetWord(c.wakeupAddr&arch.AddressMask, wakeupVal) effWakeupAddr := ^Word(3) & c.wakeupAddr
testutil.RandomizeWordAndSetUint32(state.GetMemory(), effWakeupAddr, wakeupVal, int64(i+1111))
threads := mttestutil.GetAllThreads(state) threads := mttestutil.GetAllThreads(state)
for idx, thread := range threads { for idx, thread := range threads {
......
...@@ -4,11 +4,13 @@ package testutil ...@@ -4,11 +4,13 @@ package testutil
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
) )
...@@ -42,6 +44,20 @@ func SetMemoryUint64(t require.TestingT, mem *memory.Memory, addr Word, value ui ...@@ -42,6 +44,20 @@ func SetMemoryUint64(t require.TestingT, mem *memory.Memory, addr Word, value ui
require.Equal(t, Word(value), actual) require.Equal(t, Word(value), actual)
} }
// RandomizeWordAndSetUint32 writes a uint32 value and randomizes the rest of the Word containing the uint32 in memory
func RandomizeWordAndSetUint32(mem *memory.Memory, addr Word, val uint32, randomizeWordSeed int64) {
if addr&0x3 != 0 {
panic(fmt.Errorf("unaligned memory access: %x", addr))
}
// Randomize the Word containing the target uint32 - only makes a difference for 64-bit architectures
rand := NewRandHelper(randomizeWordSeed)
wordAddr := addr & arch.AddressMask
mem.SetWord(wordAddr, rand.Word())
exec.StoreSubWord(mem, addr, 4, Word(val), new(exec.NoopMemoryTracker))
}
// ToSignedInteger converts the unsigend Word to a SignedInteger. // ToSignedInteger converts the unsigend Word to a SignedInteger.
// Useful for avoiding Go compiler warnings for literals that don't fit in a signed type // Useful for avoiding Go compiler warnings for literals that don't fit in a signed type
func ToSignedInteger(x Word) arch.SignedInteger { func ToSignedInteger(x Word) arch.SignedInteger {
......
...@@ -140,12 +140,12 @@ ...@@ -140,12 +140,12 @@
"sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af" "sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0x4971f62a6aecf91bd795fa44b5ce3cb77a987719af4f351d4aec5b6c3bf81387", "initCodeHash": "0x70ecbb2327fadd6205312aa93279a6340242dfeb44a00b98caa7b56688c46bdc",
"sourceCodeHash": "0x8da8be0b7d60af0eb11bd58653f1854d56a8f0616f3aeaeba7ab9ec340d02ac7" "sourceCodeHash": "0x84506539c40b72b1d40c9d07418650b30c27d2b219b40e55f61edcd31365157f"
}, },
"src/cannon/MIPS64.sol": { "src/cannon/MIPS64.sol": {
"initCodeHash": "0xdef0bd64af2541644e1fba56cb443650749dee201a38053c02464584f2e806a9", "initCodeHash": "0xa2a42c50d2fac71d93e44ad4871e5d838f1c630b9d1abc4c89971d36b0ae44bb",
"sourceCodeHash": "0x16b46d17dff5b772675bdd280dddea482e85c707a8779694f10cc78b05de871d" "sourceCodeHash": "0xdb771f1b92c7612b120e0bce31967f0c8a7ce332dbb426bc9cfc52b47be21c4d"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xf08736a5af9277a4f3498dfee84a40c9b05f1a2ba3177459bebe2b0b54f99343", "initCodeHash": "0xf08736a5af9277a4f3498dfee84a40c9b05f1a2ba3177459bebe2b0b54f99343",
......
...@@ -63,8 +63,8 @@ contract MIPS2 is ISemver { ...@@ -63,8 +63,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.26 /// @custom:semver 1.0.0-beta.27
string public constant version = "1.0.0-beta.26"; string public constant version = "1.0.0-beta.27";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -245,10 +245,8 @@ contract MIPS2 is ISemver { ...@@ -245,10 +245,8 @@ contract MIPS2 is ISemver {
// timeout! Allow execution // timeout! Allow execution
return onWaitComplete(thread, true); return onWaitComplete(thread, true);
} else { } else {
uint32 mem = MIPSMemory.readMem( uint32 futexVal = getFutexValue(thread.futexAddr);
state.memRoot, thread.futexAddr & 0xFFffFFfc, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1) if (thread.futexVal == futexVal) {
);
if (thread.futexVal == mem) {
// still got expected value, continue sleeping, try next thread. // still got expected value, continue sleeping, try next thread.
preemptThread(state, thread); preemptThread(state, thread);
return outputState(); return outputState();
...@@ -488,16 +486,16 @@ contract MIPS2 is ISemver { ...@@ -488,16 +486,16 @@ contract MIPS2 is ISemver {
return outputState(); return outputState();
} else if (syscall_no == sys.SYS_FUTEX) { } else if (syscall_no == sys.SYS_FUTEX) {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout // args: a0 = addr, a1 = op, a2 = val, a3 = timeout
uint32 effAddr = a0 & 0xFFffFFfc; uint32 effFutexAddr = a0 & 0xFFffFFfc;
if (a1 == sys.FUTEX_WAIT_PRIVATE) { if (a1 == sys.FUTEX_WAIT_PRIVATE) {
uint32 mem = uint32 futexVal = getFutexValue(effFutexAddr);
MIPSMemory.readMem(state.memRoot, effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1)); uint32 targetValue = a2;
if (mem != a2) { if (futexVal != targetValue) {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN; v1 = sys.EAGAIN;
} else { } else {
thread.futexAddr = effAddr; thread.futexAddr = effFutexAddr;
thread.futexVal = a2; thread.futexVal = targetValue;
thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS; thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS;
// Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete` // Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete`
updateCurrentThreadRoot(); updateCurrentThreadRoot();
...@@ -506,7 +504,7 @@ contract MIPS2 is ISemver { ...@@ -506,7 +504,7 @@ contract MIPS2 is ISemver {
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) { } else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup // Trigger thread traversal starting from the left stack until we find one waiting on the wakeup
// address // address
state.wakeup = effAddr; state.wakeup = effFutexAddr;
// Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees. // Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees.
// The woken up thread should indicate this in userspace. // The woken up thread should indicate this in userspace.
v0 = 0; v0 = 0;
...@@ -942,4 +940,16 @@ contract MIPS2 is ISemver { ...@@ -942,4 +940,16 @@ contract MIPS2 is ISemver {
// verify we have enough calldata // verify we have enough calldata
require(s >= (THREAD_PROOF_OFFSET + 198), "insufficient calldata for thread witness"); // 166 + 32 require(s >= (THREAD_PROOF_OFFSET + 198), "insufficient calldata for thread witness"); // 166 + 32
} }
/// @notice Loads a 32-bit futex value at _vAddr
function getFutexValue(uint32 _vAddr) internal pure returns (uint32 out_) {
State memory state;
assembly {
state := STATE_MEM_OFFSET
}
uint32 effAddr = _vAddr & 0xFFffFFfc;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1);
return MIPSMemory.readMem(state.memRoot, effAddr, memProofOffset);
}
} }
...@@ -21,7 +21,7 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; ...@@ -21,7 +21,7 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
/// It differs from MIPS.sol in that it supports MIPS64 instructions and multi-tasking. /// It differs from MIPS.sol in that it supports MIPS64 instructions and multi-tasking.
contract MIPS64 is ISemver { contract MIPS64 is ISemver {
/// @notice The thread context. /// @notice The thread context.
/// Total state size: 8 + 1 + 1 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 32 * 8 = 322 bytes /// Total state size: 8 + 1 + 1 + 8 + 4 + 8 + 8 + 8 + 8 + 8 + 32 * 8 = 318 bytes
struct ThreadState { struct ThreadState {
// metadata // metadata
uint64 threadID; uint64 threadID;
...@@ -29,7 +29,7 @@ contract MIPS64 is ISemver { ...@@ -29,7 +29,7 @@ contract MIPS64 is ISemver {
bool exited; bool exited;
// state // state
uint64 futexAddr; uint64 futexAddr;
uint64 futexVal; uint32 futexVal;
uint64 futexTimeoutStep; uint64 futexTimeoutStep;
uint64 pc; uint64 pc;
uint64 nextPC; uint64 nextPC;
...@@ -38,7 +38,7 @@ contract MIPS64 is ISemver { ...@@ -38,7 +38,7 @@ contract MIPS64 is ISemver {
uint64[32] registers; uint64[32] registers;
} }
uint32 internal constant PACKED_THREAD_STATE_SIZE = 322; uint32 internal constant PACKED_THREAD_STATE_SIZE = 318;
uint8 internal constant LL_STATUS_NONE = 0; uint8 internal constant LL_STATUS_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1; uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1;
...@@ -67,8 +67,8 @@ contract MIPS64 is ISemver { ...@@ -67,8 +67,8 @@ contract MIPS64 is ISemver {
} }
/// @notice The semantic version of the MIPS64 contract. /// @notice The semantic version of the MIPS64 contract.
/// @custom:semver 1.0.0-beta.8 /// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.8"; string public constant version = "1.0.0-beta.9";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -253,12 +253,8 @@ contract MIPS64 is ISemver { ...@@ -253,12 +253,8 @@ contract MIPS64 is ISemver {
// timeout! Allow execution // timeout! Allow execution
return onWaitComplete(thread, true); return onWaitComplete(thread, true);
} else { } else {
uint64 mem = MIPS64Memory.readMem( uint32 futexVal = getFutexValue(thread.futexAddr);
state.memRoot, if (thread.futexVal == futexVal) {
thread.futexAddr & arch.ADDRESS_MASK,
MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
if (thread.futexVal == mem) {
// still got expected value, continue sleeping, try next thread. // still got expected value, continue sleeping, try next thread.
preemptThread(state, thread); preemptThread(state, thread);
return outputState(); return outputState();
...@@ -530,17 +526,17 @@ contract MIPS64 is ISemver { ...@@ -530,17 +526,17 @@ contract MIPS64 is ISemver {
return outputState(); return outputState();
} else if (syscall_no == sys.SYS_FUTEX) { } else if (syscall_no == sys.SYS_FUTEX) {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout // args: a0 = addr, a1 = op, a2 = val, a3 = timeout
uint64 effAddr = a0 & arch.ADDRESS_MASK; // Futex value is 32-bit, so clear the lower 2 bits to get an effective address targeting a 4-byte value
uint64 effFutexAddr = a0 & 0xFFFFFFFFFFFFFFFC;
if (a1 == sys.FUTEX_WAIT_PRIVATE) { if (a1 == sys.FUTEX_WAIT_PRIVATE) {
uint64 mem = MIPS64Memory.readMem( uint32 futexVal = getFutexValue(effFutexAddr);
state.memRoot, effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1) uint32 targetVal = uint32(a2);
); if (futexVal != targetVal) {
if (mem != a2) {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN; v1 = sys.EAGAIN;
} else { } else {
thread.futexAddr = effAddr; thread.futexAddr = effFutexAddr;
thread.futexVal = a2; thread.futexVal = targetVal;
thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS; thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS;
// Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete` // Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete`
updateCurrentThreadRoot(); updateCurrentThreadRoot();
...@@ -549,7 +545,7 @@ contract MIPS64 is ISemver { ...@@ -549,7 +545,7 @@ contract MIPS64 is ISemver {
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) { } else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup // Trigger thread traversal starting from the left stack until we find one waiting on the wakeup
// address // address
state.wakeup = effAddr; state.wakeup = effFutexAddr;
// Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees. // Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees.
// The woken up thread should indicate this in userspace. // The woken up thread should indicate this in userspace.
v0 = 0; v0 = 0;
...@@ -897,7 +893,7 @@ contract MIPS64 is ISemver { ...@@ -897,7 +893,7 @@ contract MIPS64 is ISemver {
from, to := copyMem(from, to, 1) // exitCode from, to := copyMem(from, to, 1) // exitCode
from, to := copyMem(from, to, 1) // exited from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // futexAddr from, to := copyMem(from, to, 8) // futexAddr
from, to := copyMem(from, to, 8) // futexVal from, to := copyMem(from, to, 4) // futexVal
from, to := copyMem(from, to, 8) // futexTimeoutStep from, to := copyMem(from, to, 8) // futexTimeoutStep
from, to := copyMem(from, to, 8) // pc from, to := copyMem(from, to, 8) // pc
from, to := copyMem(from, to, 8) // nextPC from, to := copyMem(from, to, 8) // nextPC
...@@ -960,7 +956,7 @@ contract MIPS64 is ISemver { ...@@ -960,7 +956,7 @@ contract MIPS64 is ISemver {
c, m := putField(c, m, 1) // exitCode c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited c, m := putField(c, m, 1) // exited
c, m := putField(c, m, 8) // futexAddr c, m := putField(c, m, 8) // futexAddr
c, m := putField(c, m, 8) // futexVal c, m := putField(c, m, 4) // futexVal
c, m := putField(c, m, 8) // futexTimeoutStep c, m := putField(c, m, 8) // futexTimeoutStep
c, m := putField(c, m, 8) // pc c, m := putField(c, m, 8) // pc
c, m := putField(c, m, 8) // nextPC c, m := putField(c, m, 8) // nextPC
...@@ -986,4 +982,15 @@ contract MIPS64 is ISemver { ...@@ -986,4 +982,15 @@ contract MIPS64 is ISemver {
"MIPS64: insufficient calldata for thread witness" "MIPS64: insufficient calldata for thread witness"
); );
} }
/// @notice Loads a 32-bit futex value at _vAddr
function getFutexValue(uint64 _vAddr) internal pure returns (uint32 out_) {
State memory state;
assembly {
state := STATE_MEM_OFFSET
}
uint64 subword = loadSubWord(state, _vAddr, 4, false);
return uint32(subword);
}
} }
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