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)
......
......@@ -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