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 {
return nil
case arch.SysFutex:
// 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 {
case exec.FutexWaitPrivate:
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetWord(effAddr)
if mem != a2 {
futexVal := m.getFutexValue(effFutexAddr)
targetVal := uint32(a2)
if futexVal != targetVal {
v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN
} else {
thread.FutexAddr = effAddr
thread.FutexVal = a2
thread.FutexAddr = effFutexAddr
thread.FutexVal = targetVal
if a3 == 0 {
thread.FutexTimeoutStep = exec.FutexNoTimeout
} else {
......@@ -130,7 +131,7 @@ func (m *InstrumentedState) handleSyscall() error {
}
case exec.FutexWakePrivate:
// 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.
// The woken up thread should indicate this in userspace.
v0 = 0
......@@ -283,10 +284,8 @@ func (m *InstrumentedState) doMipsStep() error {
m.onWaitComplete(thread, true)
return nil
} else {
effAddr := thread.FutexAddr & arch.AddressMask
m.memoryTracker.TrackMemAccess(effAddr)
mem := m.state.Memory.GetWord(effAddr)
if thread.FutexVal == mem {
futexVal := m.getFutexValue(thread.FutexAddr)
if thread.FutexVal == futexVal {
// still got expected value, continue sleeping, try next thread.
m.preemptThread(thread)
m.statsTracker.trackWakeupFail()
......@@ -488,3 +487,8 @@ func (m *InstrumentedState) popThread() {
func (m *InstrumentedState) lastThreadRemaining() bool {
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) {
func TestThreadStateWitnessSize(t *testing.T) {
expectedWitnessSize := 166
if !arch.IsMips32 {
expectedWitnessSize = 322
expectedWitnessSize = 318
}
require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE)
}
......@@ -48,7 +48,7 @@ type ExpectedThreadState struct {
ExitCode uint8
Exited bool
FutexAddr arch.Word
FutexVal arch.Word
FutexVal uint32
FutexTimeoutStep uint64
PC arch.Word
NextPC arch.Word
......
......@@ -18,12 +18,12 @@ const (
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_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_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_REGISTERS_WITNESS_OFFSET = THREAD_FUTEX_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
// 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)
// THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes.
......@@ -41,7 +41,7 @@ type ThreadState struct {
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
FutexAddr Word `json:"futexAddr"`
FutexVal Word `json:"futexVal"`
FutexVal uint32 `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"`
......@@ -73,7 +73,7 @@ func (t *ThreadState) serializeThread() []byte {
out = append(out, t.ExitCode)
out = mipsevm.AppendBoolToWitness(out, t.Exited)
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 = arch.ByteOrderWord.AppendWord(out, t.Cpu.PC)
......
......@@ -489,32 +489,37 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
name string
addressParam uint64
effAddr uint64
targetValue uint64
actualValue uint64
targetValue uint32
actualValue uint32
timeout uint64
shouldFail 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, 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: "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: "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 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 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 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 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, 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 #1", addressParam: 0xFF_FF_FF_FF_FF_FF_12_33, effAddr: 0xFF_FF_FF_FF_FF_FF_12_30, targetValue: 0x01, actualValue: 0x01},
{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: "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, 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: "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, 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: "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 {
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()
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()[4] = Word(c.addressParam)
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)
// Setup expectations
......@@ -529,7 +534,7 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
} else {
// PC and return registers should not update on success, updates happen when wait completes
expected.ActiveThread().FutexAddr = Word(c.effAddr)
expected.ActiveThread().FutexVal = Word(c.targetValue)
expected.ActiveThread().FutexVal = c.targetValue
expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout
if c.shouldSetTimeout {
expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1
......@@ -559,7 +564,10 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
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, 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, 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},
......@@ -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, 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, 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, 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) {
expected.ExpectStep()
expected.ActiveThread().Registers[2] = 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.TraverseRight = c.expectTraverseRight
if c.traverseRight != c.expectTraverseRight {
......@@ -1012,26 +1020,33 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
activeStackSize int
otherStackSize int
futexAddr Word
targetValue Word
actualValue Word
effAddr Word
targetValue uint32
actualValue uint32
timeoutStep uint64
shouldWakeup 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 #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 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, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x101, 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, with timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{name: "Preempt, with timeout, unaligned", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x101, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{name: "Wakeup, no timeout #1", step: 100, activeStackSize: 1, otherStackSize: 0, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup, no timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{name: "Wakeup, no timeout, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x102, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: true},
{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 with timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, targetValue: 0x02, actualValue: 0x02, timeoutStep: 100, shouldWakeup: true, shouldTimeout: 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 with timeout, unaligned", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x103, targetValue: 0x02, actualValue: 0x02, timeoutStep: 50, shouldWakeup: true, shouldTimeout: true},
{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, effAddr: 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 #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x101, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{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, no timeout, unaligned #3", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x109, effAddr: 0x108, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, no timeout, unaligned #4", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x10F, effAddr: 0x10C, targetValue: 0x01, actualValue: 0x01, timeoutStep: exec.FutexNoTimeout},
{name: "Preempt, with timeout #1", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 101},
{name: "Preempt, with timeout #2", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{name: "Preempt, with timeout, unaligned", step: 100, activeStackSize: 1, otherStackSize: 1, futexAddr: 0x101, effAddr: 0x100, targetValue: 0x01, actualValue: 0x01, timeoutStep: 150},
{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, no timeout #2", step: 100, activeStackSize: 2, otherStackSize: 1, futexAddr: 0x100, effAddr: 0x100, targetValue: 0x01, actualValue: 0x02, timeoutStep: exec.FutexNoTimeout, shouldWakeup: 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, 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 {
......@@ -1042,7 +1057,6 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
if !c.shouldWakeup && c.shouldTimeout {
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)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step
......@@ -1051,7 +1065,7 @@ func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetValue
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
expected := mttestutil.NewExpectedMTState(state)
......@@ -1145,12 +1159,12 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
func TestEVM_WakeupTraversalStep(t *testing.T) {
addr := Word(0x1234)
wakeupVal := Word(0x999)
wakeupVal := uint32(0x999)
cases := []struct {
name string
wakeupAddr Word
futexAddr Word
targetVal Word
targetVal uint32
traverseRight bool
activeStackSize int
otherStackSize int
......@@ -1188,7 +1202,8 @@ func TestEVM_WakeupTraversalStep(t *testing.T) {
step := state.Step
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.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetVal
......@@ -1271,13 +1286,13 @@ func TestEVM_WakeupTraversal_Full(t *testing.T) {
}
func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) {
addr := Word(0x1234)
wakeupVal := Word(0x999)
addr := Word(0x1230)
wakeupVal := uint32(0x999)
cases := []struct {
name string
wakeupAddr Word
futexAddr Word
targetVal Word
targetVal uint32
traverseRight bool
activeStackSize int
otherStackSize int
......@@ -1285,7 +1300,7 @@ func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) {
shouldClearWakeup bool
shouldPreempt bool
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: "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) {
step := state.Step
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)
for idx, thread := range threads {
......
......@@ -4,11 +4,13 @@ package testutil
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)
......@@ -42,6 +44,20 @@ func SetMemoryUint64(t require.TestingT, mem *memory.Memory, addr Word, value ui
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.
// Useful for avoiding Go compiler warnings for literals that don't fit in a signed type
func ToSignedInteger(x Word) arch.SignedInteger {
......
......@@ -140,12 +140,12 @@
"sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0x4971f62a6aecf91bd795fa44b5ce3cb77a987719af4f351d4aec5b6c3bf81387",
"sourceCodeHash": "0x8da8be0b7d60af0eb11bd58653f1854d56a8f0616f3aeaeba7ab9ec340d02ac7"
"initCodeHash": "0x70ecbb2327fadd6205312aa93279a6340242dfeb44a00b98caa7b56688c46bdc",
"sourceCodeHash": "0x84506539c40b72b1d40c9d07418650b30c27d2b219b40e55f61edcd31365157f"
},
"src/cannon/MIPS64.sol": {
"initCodeHash": "0xdef0bd64af2541644e1fba56cb443650749dee201a38053c02464584f2e806a9",
"sourceCodeHash": "0x16b46d17dff5b772675bdd280dddea482e85c707a8779694f10cc78b05de871d"
"initCodeHash": "0xa2a42c50d2fac71d93e44ad4871e5d838f1c630b9d1abc4c89971d36b0ae44bb",
"sourceCodeHash": "0xdb771f1b92c7612b120e0bce31967f0c8a7ce332dbb426bc9cfc52b47be21c4d"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xf08736a5af9277a4f3498dfee84a40c9b05f1a2ba3177459bebe2b0b54f99343",
......
......@@ -63,8 +63,8 @@ contract MIPS2 is ISemver {
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.26
string public constant version = "1.0.0-beta.26";
/// @custom:semver 1.0.0-beta.27
string public constant version = "1.0.0-beta.27";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -245,10 +245,8 @@ contract MIPS2 is ISemver {
// timeout! Allow execution
return onWaitComplete(thread, true);
} else {
uint32 mem = MIPSMemory.readMem(
state.memRoot, thread.futexAddr & 0xFFffFFfc, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
if (thread.futexVal == mem) {
uint32 futexVal = getFutexValue(thread.futexAddr);
if (thread.futexVal == futexVal) {
// still got expected value, continue sleeping, try next thread.
preemptThread(state, thread);
return outputState();
......@@ -488,16 +486,16 @@ contract MIPS2 is ISemver {
return outputState();
} else if (syscall_no == sys.SYS_FUTEX) {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout
uint32 effAddr = a0 & 0xFFffFFfc;
uint32 effFutexAddr = a0 & 0xFFffFFfc;
if (a1 == sys.FUTEX_WAIT_PRIVATE) {
uint32 mem =
MIPSMemory.readMem(state.memRoot, effAddr, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1));
if (mem != a2) {
uint32 futexVal = getFutexValue(effFutexAddr);
uint32 targetValue = a2;
if (futexVal != targetValue) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN;
} else {
thread.futexAddr = effAddr;
thread.futexVal = a2;
thread.futexAddr = effFutexAddr;
thread.futexVal = targetValue;
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`
updateCurrentThreadRoot();
......@@ -506,7 +504,7 @@ contract MIPS2 is ISemver {
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup
// 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.
// The woken up thread should indicate this in userspace.
v0 = 0;
......@@ -942,4 +940,16 @@ contract MIPS2 is ISemver {
// verify we have enough calldata
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";
/// It differs from MIPS.sol in that it supports MIPS64 instructions and multi-tasking.
contract MIPS64 is ISemver {
/// @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 {
// metadata
uint64 threadID;
......@@ -29,7 +29,7 @@ contract MIPS64 is ISemver {
bool exited;
// state
uint64 futexAddr;
uint64 futexVal;
uint32 futexVal;
uint64 futexTimeoutStep;
uint64 pc;
uint64 nextPC;
......@@ -38,7 +38,7 @@ contract MIPS64 is ISemver {
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_ACTIVE_32_BIT = 0x1;
......@@ -67,8 +67,8 @@ contract MIPS64 is ISemver {
}
/// @notice The semantic version of the MIPS64 contract.
/// @custom:semver 1.0.0-beta.8
string public constant version = "1.0.0-beta.8";
/// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.9";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -253,12 +253,8 @@ contract MIPS64 is ISemver {
// timeout! Allow execution
return onWaitComplete(thread, true);
} else {
uint64 mem = MIPS64Memory.readMem(
state.memRoot,
thread.futexAddr & arch.ADDRESS_MASK,
MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
if (thread.futexVal == mem) {
uint32 futexVal = getFutexValue(thread.futexAddr);
if (thread.futexVal == futexVal) {
// still got expected value, continue sleeping, try next thread.
preemptThread(state, thread);
return outputState();
......@@ -530,17 +526,17 @@ contract MIPS64 is ISemver {
return outputState();
} else if (syscall_no == sys.SYS_FUTEX) {
// 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) {
uint64 mem = MIPS64Memory.readMem(
state.memRoot, effAddr, MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
if (mem != a2) {
uint32 futexVal = getFutexValue(effFutexAddr);
uint32 targetVal = uint32(a2);
if (futexVal != targetVal) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN;
} else {
thread.futexAddr = effAddr;
thread.futexVal = a2;
thread.futexAddr = effFutexAddr;
thread.futexVal = targetVal;
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`
updateCurrentThreadRoot();
......@@ -549,7 +545,7 @@ contract MIPS64 is ISemver {
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup
// 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.
// The woken up thread should indicate this in userspace.
v0 = 0;
......@@ -897,7 +893,7 @@ contract MIPS64 is ISemver {
from, to := copyMem(from, to, 1) // exitCode
from, to := copyMem(from, to, 1) // exited
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) // pc
from, to := copyMem(from, to, 8) // nextPC
......@@ -960,7 +956,7 @@ contract MIPS64 is ISemver {
c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited
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) // pc
c, m := putField(c, m, 8) // nextPC
......@@ -986,4 +982,15 @@ contract MIPS64 is ISemver {
"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