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

cannon: Simplify futex handling (#13754)

* cannon: Simplify futex handling

* cannon: Cut envar and go ahead and implement new futex behavior directly

* cannon: Cut now unused onWaitComplete() method

* cannon: Update single-threaded mips call to getSyscallArgs

* cannon: Update contracts to match go vm

* cannon: Update solidity tests

* cannon: Reconcile MIPS.sol version with latest release (1.2.1)

* cannon: Update differential tests

* cannon: Cut wakeup field

* cannon: Remove thread futex fields

* cannon: Fix const name

* cannon: Add new cannon state versions

* cannon: Cut wakeup stats

* cannon: Fix ParseStateVersion(), add test

* cannon: Reenable evm validation in tests

* cannon: Temporarily use ad hoc cannon release

* cannon: Run semver lock

* cannon: Fix semver comment, run semver-lock

* cannon: Cut unused constants

* cannon: Use latest cannon release in op-stack-go dockerfile

---------
Co-authored-by: default avatarinphi <mlaw2501@gmail.com>
parent ef6ef6fd
......@@ -152,14 +152,14 @@ cannon-prestate: op-program cannon ## Generates prestate using cannon and op-pro
mv op-program/bin/0.json op-program/bin/prestate-proof.json
.PHONY: cannon-prestate
cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the multithreaded64-2 cannon format
./cannon/bin/cannon load-elf --type multithreaded64-2 --path op-program/bin/op-program-client64.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json
cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the latest 64-bit multithreaded cannon format
./cannon/bin/cannon load-elf --type multithreaded64-3 --path op-program/bin/op-program-client64.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json
./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json --proof-fmt 'op-program/bin/%d-mt.json' --output ""
mv op-program/bin/0-mt.json op-program/bin/prestate-proof-mt.json
.PHONY: cannon-prestate-mt
cannon-prestate-interop: op-program cannon ## Generates interop prestate using cannon and op-program in the multithreaded64-2 cannon format
./cannon/bin/cannon load-elf --type multithreaded64-2 --path op-program/bin/op-program-client-interop.elf --out op-program/bin/prestate-interop.bin.gz --meta op-program/bin/meta-interop.json
cannon-prestate-interop: op-program cannon ## Generates interop prestate using cannon and op-program in the latest 64-bit multithreaded cannon format
./cannon/bin/cannon load-elf --type multithreaded64-3 --path op-program/bin/op-program-client-interop.elf --out op-program/bin/prestate-interop.bin.gz --meta op-program/bin/meta-interop.json
./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate-interop.bin.gz --meta op-program/bin/meta-interop.json --proof-fmt 'op-program/bin/%d-interop.json' --output ""
mv op-program/bin/0-interop.json op-program/bin/prestate-proof-interop.json
.PHONY: cannon-prestate-interop
......
......@@ -31,13 +31,14 @@ cannon64-impl:
# It should build the individual versions of cannons and copy them into place in hte multicannon/embeds directory
# Ideally, preserve backwards compatibility with this behaviour but if it needs to change, build-legacy-cannons.sh will
# need to be updated to account for different behaviours in different versions.
# Each embed is suffixed with the latest `StateVersion` number corresponding to the target VM and architecture.
cannon-embeds: cannon32-impl cannon64-impl
# singlethreaded-v2
# 32-bit singlethreaded vm
@cp bin/cannon32-impl ./multicannon/embeds/cannon-2
# multithreaded
@cp bin/cannon32-impl ./multicannon/embeds/cannon-1
# 64-bit multithreaded v2
@cp bin/cannon64-impl ./multicannon/embeds/cannon-4
# 32-bit multithreaded vm
@cp bin/cannon32-impl ./multicannon/embeds/cannon-5
# 64-bit multithreaded vm
@cp bin/cannon64-impl ./multicannon/embeds/cannon-6
cannon: cannon-embeds
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/
......
......@@ -80,7 +80,7 @@ func LoadELF(ctx *cli.Context) error {
}
return program.PatchStack(state)
}
case versions.VersionMultiThreaded, versions.VersionMultiThreaded64_v2:
case versions.VersionMultiThreaded_v2, versions.VersionMultiThreaded64_v3:
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, multithreaded.CreateInitialState)
}
......
......@@ -14,6 +14,5 @@ type DebugInfo struct {
MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"`
ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"`
ForcedPreemptionCount uint64 `json:"forced_preemption_count"`
FailedWakeupCount uint64 `json:"failed_wakeup_count"`
IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"`
}
......@@ -23,7 +23,6 @@ func TestDebugInfo_Serialization(t *testing.T) {
MaxStepsBetweenLLAndSC: 7,
ReservationInvalidationCount: 8,
ForcedPreemptionCount: 9,
FailedWakeupCount: 10,
IdleStepCountThread0: math.MaxUint64,
}
......
......@@ -42,11 +42,8 @@ const (
// SysFutex-related constants
const (
FutexWaitPrivate = 128
FutexWakePrivate = 129
FutexTimeoutSteps = 10_000
FutexNoTimeout = ^uint64(0)
FutexEmptyAddr = ^Word(0)
FutexWaitPrivate = 128
FutexWakePrivate = 129
)
// SysClone flags
......@@ -99,15 +96,14 @@ const (
ClockGettimeMonotonicFlag = 1
)
func GetSyscallArgs(registers *[32]Word) (syscallNum, a0, a1, a2, a3 Word) {
func GetSyscallArgs(registers *[32]Word) (syscallNum, a0, a1, a2 Word) {
syscallNum = registers[register.RegSyscallNum] // v0
a0 = registers[register.RegSyscallParam1]
a1 = registers[register.RegSyscallParam2]
a2 = registers[register.RegSyscallParam3]
a3 = registers[register.RegSyscallParam4]
return syscallNum, a0, a1, a2, a3
return syscallNum, a0, a1, a2
}
func HandleSysMmap(a0, a1, heap Word) (v0, v1, newHeap Word) {
......
......@@ -20,7 +20,7 @@ type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error {
thread := m.state.GetCurrentThread()
syscallNum, a0, a1, a2, a3 := exec.GetSyscallArgs(m.state.GetRegistersRef())
syscallNum, a0, a1, a2 := exec.GetSyscallArgs(m.state.GetRegistersRef())
v0 := Word(0)
v1 := Word(0)
......@@ -43,12 +43,9 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = m.state.NextThreadId
v1 = 0
newThread := &ThreadState{
ThreadId: m.state.NextThreadId,
ExitCode: 0,
Exited: false,
FutexAddr: exec.FutexEmptyAddr,
FutexVal: 0,
FutexTimeoutStep: 0,
ThreadId: m.state.NextThreadId,
ExitCode: 0,
Exited: false,
Cpu: mipsevm.CpuScalars{
PC: thread.Cpu.NextPC,
NextPC: thread.Cpu.NextPC + 4,
......@@ -119,37 +116,18 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN
} else {
thread.FutexAddr = effFutexAddr
thread.FutexVal = targetVal
if a3 == 0 {
thread.FutexTimeoutStep = exec.FutexNoTimeout
} else {
thread.FutexTimeoutStep = m.state.Step + exec.FutexTimeoutSteps
}
// Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete`
m.syscallYield(thread)
return nil
}
case exec.FutexWakePrivate:
// Trigger a wakeup traversal
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
v1 = 0
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
m.state.TraverseRight = len(m.state.LeftThreadStack) == 0
m.statsTracker.trackWakeupTraversalStart()
m.syscallYield(thread)
return nil
default:
v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL
}
case arch.SysSchedYield, arch.SysNanosleep:
v0 = 0
v1 = 0
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
m.syscallYield(thread)
return nil
case arch.SysOpen:
v0 = exec.SysErrorSignal
......@@ -225,6 +203,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
}
func (m *InstrumentedState) syscallYield(thread *ThreadState) {
v0 := Word(0)
v1 := Word(0)
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
}
func (m *InstrumentedState) mipsStep() error {
err := m.doMipsStep()
if err != nil {
......@@ -249,56 +234,12 @@ func (m *InstrumentedState) doMipsStep() error {
m.state.Step += 1
thread := m.state.GetCurrentThread()
// During wakeup traversal, search for the first thread blocked on the wakeup address.
// Don't allow regular execution until we have found such a thread or else we have visited all threads.
if m.state.Wakeup != exec.FutexEmptyAddr {
// We are currently performing a wakeup traversal
if m.state.Wakeup == thread.FutexAddr {
// We found a target thread, resume normal execution and process this thread
m.state.Wakeup = exec.FutexEmptyAddr
} else {
// This is not the thread we're looking for, move on
traversingRight := m.state.TraverseRight
changedDirections := m.preemptThread(thread)
if traversingRight && changedDirections {
// We started the wakeup traversal walking left and we've now walked all the way right
// We have therefore visited all threads and can resume normal thread execution
m.state.Wakeup = exec.FutexEmptyAddr
}
}
return nil
}
if thread.Exited {
m.popThread()
m.stackTracker.DropThread(thread.ThreadId)
return nil
}
// check if thread is blocked on a futex
if thread.FutexAddr != exec.FutexEmptyAddr {
// if set, then check futex
// check timeout first
if m.state.Step > thread.FutexTimeoutStep {
// timeout! Allow execution
m.onWaitComplete(thread, true)
return nil
} else {
futexVal := m.getFutexValue(thread.FutexAddr)
if thread.FutexVal == futexVal {
// still got expected value, continue sleeping, try next thread.
m.preemptThread(thread)
m.statsTracker.trackWakeupFail()
return nil
} else {
// wake thread up, the value at its address changed!
// Userspace can turn thread back to sleep if it was too sporadic.
m.onWaitComplete(thread, false)
return nil
}
}
}
if m.state.StepsSinceLastContextSwitch >= exec.SchedQuantum {
// Force a context switch as this thread has been active too long
if m.state.ThreadCount() > 1 {
......@@ -412,24 +353,6 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
return exec.HandleRd(m.state.getCpuRef(), m.state.GetRegistersRef(), rtReg, retVal, true)
}
func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) {
// Note: no need to reset m.state.Wakeup. If we're here, the Wakeup field has already been reset
// Clear the futex state
thread.FutexAddr = exec.FutexEmptyAddr
thread.FutexVal = 0
thread.FutexTimeoutStep = 0
// Complete the FUTEX_WAIT syscall
v0 := Word(0)
v1 := Word(0)
if isTimedOut {
v0 = exec.SysErrorSignal
v1 = exec.MipsETIMEDOUT
}
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.statsTracker.trackWakeup()
}
func (m *InstrumentedState) preemptThread(thread *ThreadState) bool {
// Pop thread from the current stack and push to the other stack
if m.state.TraverseRight {
......
......@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"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/op-service/serialize"
)
......@@ -30,13 +29,12 @@ const (
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
WAKEUP_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + arch.WordSizeBytes
TRAVERSE_RIGHT_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32
THREAD_ID_WITNESS_OFFSET = RIGHT_THREADS_ROOT_WITNESS_OFFSET + 32
// 172 and 196 bytes for 32 and 64-bit respectively
// 168 and 188 bytes for 32 and 64-bit respectively
STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes
)
......@@ -64,7 +62,6 @@ type State struct {
Step uint64
StepsSinceLastContextSwitch uint64
Wakeup Word
TraverseRight bool
LeftThreadStack []*ThreadState
......@@ -89,7 +86,6 @@ func CreateEmptyState() *State {
ExitCode: 0,
Exited: false,
Step: 0,
Wakeup: exec.FutexEmptyAddr,
TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{},
......@@ -215,7 +211,6 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = binary.BigEndian.AppendUint64(out, s.Step)
out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch)
out = arch.ByteOrderWord.AppendWord(out, s.Wakeup)
leftStackRoot := s.getLeftThreadStackRoot()
rightStackRoot := s.getRightThreadStackRoot()
......@@ -262,7 +257,6 @@ func (s *State) ThreadCount() int {
// Exited uint8 - 0 for false, 1 for true
// Step uint64
// StepsSinceLastContextSwitch uint64
// Wakeup Word
// TraverseRight uint8 - 0 for false, 1 for true
// NextThreadId Word
// len(LeftThreadStack) Word
......@@ -307,9 +301,6 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.StepsSinceLastContextSwitch); err != nil {
return err
}
if err := bout.WriteUInt(s.Wakeup); err != nil {
return err
}
if err := bout.WriteBool(s.TraverseRight); err != nil {
return err
}
......@@ -376,9 +367,6 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.StepsSinceLastContextSwitch); err != nil {
return err
}
if err := bin.ReadUInt(&s.Wakeup); err != nil {
return err
}
if err := bin.ReadBool(&s.TraverseRight); err != nil {
return err
}
......
......@@ -14,7 +14,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"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/program"
)
......@@ -85,7 +84,6 @@ func TestState_EncodeWitness(t *testing.T) {
}
setWitnessField(expectedWitness, STEP_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)})
setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)})
setWitnessWord(expectedWitness, WAKEUP_WITNESS_OFFSET, ^arch.Word(0))
setWitnessField(expectedWitness, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:])
......@@ -132,7 +130,6 @@ func TestState_JSONCodec(t *testing.T) {
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Step, newState.Step)
require.Equal(t, state.StepsSinceLastContextSwitch, newState.StepsSinceLastContextSwitch)
require.Equal(t, state.Wakeup, newState.Wakeup)
require.Equal(t, state.TraverseRight, newState.TraverseRight)
require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack)
require.Equal(t, state.RightThreadStack, newState.RightThreadStack)
......@@ -170,7 +167,6 @@ func TestState_Binary(t *testing.T) {
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Step, newState.Step)
require.Equal(t, state.StepsSinceLastContextSwitch, newState.StepsSinceLastContextSwitch)
require.Equal(t, state.Wakeup, newState.Wakeup)
require.Equal(t, state.TraverseRight, newState.TraverseRight)
require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack)
require.Equal(t, state.RightThreadStack, newState.RightThreadStack)
......@@ -196,16 +192,12 @@ func TestSerializeStateRoundTrip(t *testing.T) {
Exited: true,
Step: 0xdeadbeef,
StepsSinceLastContextSwitch: 334,
Wakeup: 42,
TraverseRight: true,
LeftThreadStack: []*ThreadState{
{
ThreadId: 45,
ExitCode: 46,
Exited: true,
FutexAddr: 47,
FutexVal: 48,
FutexTimeoutStep: 49,
ThreadId: 45,
ExitCode: 46,
Exited: true,
Cpu: mipsevm.CpuScalars{
PC: 0xFF,
NextPC: 0xFF + 4,
......@@ -223,12 +215,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
},
},
{
ThreadId: 55,
ExitCode: 56,
Exited: false,
FutexAddr: 57,
FutexVal: 58,
FutexTimeoutStep: 59,
ThreadId: 55,
ExitCode: 56,
Exited: false,
Cpu: mipsevm.CpuScalars{
PC: 0xEE,
NextPC: 0xEE + 4,
......@@ -243,12 +232,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
},
RightThreadStack: []*ThreadState{
{
ThreadId: 65,
ExitCode: 66,
Exited: false,
FutexAddr: 67,
FutexVal: 68,
FutexTimeoutStep: 69,
ThreadId: 65,
ExitCode: 66,
Exited: false,
Cpu: mipsevm.CpuScalars{
PC: 0xdd,
NextPC: 0xdd + 4,
......@@ -260,12 +246,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
},
},
{
ThreadId: 75,
ExitCode: 76,
Exited: true,
FutexAddr: 77,
FutexVal: 78,
FutexTimeoutStep: 79,
ThreadId: 75,
ExitCode: 76,
Exited: true,
Cpu: mipsevm.CpuScalars{
PC: 0xcc,
NextPC: 0xcc + 4,
......@@ -360,20 +343,16 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) {
func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
cases := []struct {
name string
wakeupAddr Word
traverseRight bool
}{
{"traverse left during wakeup traversal", Word(99), false},
{"traverse left during normal traversal", exec.FutexEmptyAddr, false},
{"traverse right during wakeup traversal", Word(99), true},
{"traverse right during normal traversal", exec.FutexEmptyAddr, true},
{"traverse left", false},
{"traverse right", true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
// Set up invalid state where the active stack is empty
state := CreateEmptyState()
state.Wakeup = c.wakeupAddr
state.TraverseRight = c.traverseRight
if c.traverseRight {
state.LeftThreadStack = []*ThreadState{CreateEmptyThread()}
......@@ -389,17 +368,17 @@ func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
}
func TestStateWitnessSize(t *testing.T) {
expectedWitnessSize := 172
expectedWitnessSize := 168
if !arch.IsMips32 {
expectedWitnessSize = 196
expectedWitnessSize = 188
}
require.Equal(t, expectedWitnessSize, STATE_WITNESS_SIZE)
}
func TestThreadStateWitnessSize(t *testing.T) {
expectedWitnessSize := 166
expectedWitnessSize := 150
if !arch.IsMips32 {
expectedWitnessSize = 318
expectedWitnessSize = 298
}
require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE)
}
......@@ -13,9 +13,6 @@ type StatsTracker interface {
trackSCFailure(threadId Word, step uint64)
trackReservationInvalidation()
trackForcedPreemption()
trackWakeupTraversalStart()
trackWakeup()
trackWakeupFail()
trackThreadActivated(tid Word, step uint64)
populateDebugInfo(debugInfo *mipsevm.DebugInfo)
}
......@@ -32,9 +29,6 @@ func (s *noopStatsTracker) trackSCSuccess(threadId Word, step uint64) {}
func (s *noopStatsTracker) trackSCFailure(threadId Word, step uint64) {}
func (s *noopStatsTracker) trackReservationInvalidation() {}
func (s *noopStatsTracker) trackForcedPreemption() {}
func (s *noopStatsTracker) trackWakeupTraversalStart() {}
func (s *noopStatsTracker) trackWakeup() {}
func (s *noopStatsTracker) trackWakeupFail() {}
func (s *noopStatsTracker) trackThreadActivated(tid Word, step uint64) {}
func (s *noopStatsTracker) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {}
......@@ -44,7 +38,6 @@ var _ StatsTracker = (*noopStatsTracker)(nil)
type statsTrackerImpl struct {
// State
lastLLStepByThread *lru.LRU[Word, uint64]
isWakeupTraversal bool
activeThreadId Word
lastActiveStepThread0 uint64
// Stats
......@@ -54,7 +47,6 @@ type statsTrackerImpl struct {
// Tracks RMW reservation invalidation due to reserved memory being accessed outside of the RMW sequence
reservationInvalidationCount uint64
forcedPreemptionCount uint64
failedWakeupCount uint64
idleStepCountThread0 uint64
}
......@@ -64,7 +56,6 @@ func (s *statsTrackerImpl) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {
debugInfo.MaxStepsBetweenLLAndSC = s.maxStepsBetweenLLAndSC
debugInfo.ReservationInvalidationCount = s.reservationInvalidationCount
debugInfo.ForcedPreemptionCount = s.forcedPreemptionCount
debugInfo.FailedWakeupCount = s.failedWakeupCount
debugInfo.IdleStepCountThread0 = s.idleStepCountThread0
}
......@@ -102,21 +93,6 @@ func (s *statsTrackerImpl) trackForcedPreemption() {
s.forcedPreemptionCount += 1
}
func (s *statsTrackerImpl) trackWakeupTraversalStart() {
s.isWakeupTraversal = true
}
func (s *statsTrackerImpl) trackWakeup() {
s.isWakeupTraversal = false
}
func (s *statsTrackerImpl) trackWakeupFail() {
if s.isWakeupTraversal {
s.failedWakeupCount += 1
}
s.isWakeupTraversal = false
}
func (s *statsTrackerImpl) trackThreadActivated(tid Word, step uint64) {
if s.activeThreadId == Word(0) && tid != Word(0) {
// Thread 0 has been deactivated, start tracking to capture idle steps
......
......@@ -69,26 +69,6 @@ func TestStatsTracker(t *testing.T) {
operations: []Operation{forcePreempt(), forcePreempt()},
expected: &mipsevm.DebugInfo{ForcedPreemptionCount: 2},
},
{
name: "Successful wakeup traversal",
operations: []Operation{wakeupTraversal(), wakeup(), sleep(), wakeup()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 0},
},
{
name: "Failed wakeup traversal",
operations: []Operation{wakeupTraversal(), sleep(), wakeup(), sleep()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 1},
},
{
name: "Multiple failed wakeup traversals",
operations: []Operation{wakeupTraversal(), sleep(), wakeupTraversal(), sleep(), wakeupTraversal(), wakeup()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 2},
},
{
name: "Wakeups and sleeps outside of wakeup traversal",
operations: []Operation{sleep(), wakeup(), wakeup(), sleep()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 0},
},
{
name: "Preempt thread 0 for thread 0",
operations: []Operation{activateThread(0, 10), activateThread(0, 20), activateThread(0, 21)},
......@@ -148,24 +128,6 @@ func forcePreempt() Operation {
}
}
func wakeupTraversal() Operation {
return func(tracker StatsTracker) {
tracker.trackWakeupTraversalStart()
}
}
func wakeup() Operation {
return func(tracker StatsTracker) {
tracker.trackWakeup()
}
}
func sleep() Operation {
return func(tracker StatsTracker) {
tracker.trackWakeupFail()
}
}
func activateThread(tid Word, step uint64) Operation {
return func(tracker StatsTracker) {
tracker.trackThreadActivated(tid, step)
......
......@@ -31,7 +31,6 @@ type ExpectedMTState struct {
expectedMemory *memory.Memory
// Threading-related expectations
StepsSinceLastContextSwitch uint64
Wakeup arch.Word
TraverseRight bool
NextThreadId arch.Word
ThreadCount int
......@@ -44,18 +43,15 @@ type ExpectedMTState struct {
}
type ExpectedThreadState struct {
ThreadId arch.Word
ExitCode uint8
Exited bool
FutexAddr arch.Word
FutexVal uint32
FutexTimeoutStep uint64
PC arch.Word
NextPC arch.Word
HI arch.Word
LO arch.Word
Registers [32]arch.Word
Dropped bool
ThreadId arch.Word
ExitCode uint8
Exited bool
PC arch.Word
NextPC arch.Word
HI arch.Word
LO arch.Word
Registers [32]arch.Word
Dropped bool
}
func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
......@@ -81,7 +77,6 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup,
TraverseRight: fromState.TraverseRight,
NextThreadId: fromState.NextThreadId,
ThreadCount: fromState.ThreadCount(),
......@@ -98,18 +93,15 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
func newExpectedThreadState(fromThread *multithreaded.ThreadState) *ExpectedThreadState {
return &ExpectedThreadState{
ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited,
FutexAddr: fromThread.FutexAddr,
FutexVal: fromThread.FutexVal,
FutexTimeoutStep: fromThread.FutexTimeoutStep,
PC: fromThread.Cpu.PC,
NextPC: fromThread.Cpu.NextPC,
HI: fromThread.Cpu.HI,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited,
PC: fromThread.Cpu.PC,
NextPC: fromThread.Cpu.NextPC,
HI: fromThread.Cpu.HI,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
}
}
......@@ -193,7 +185,6 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade
require.Equalf(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), "Expect memory root = %v", e.MemoryRoot)
// Thread-related global fields
require.Equalf(t, e.StepsSinceLastContextSwitch, actualState.StepsSinceLastContextSwitch, "Expect StepsSinceLastContextSwitch = %v", e.StepsSinceLastContextSwitch)
require.Equalf(t, e.Wakeup, actualState.Wakeup, "Expect Wakeup = %v", e.Wakeup)
require.Equalf(t, e.TraverseRight, actualState.TraverseRight, "Expect TraverseRight = %v", e.TraverseRight)
require.Equalf(t, e.NextThreadId, actualState.NextThreadId, "Expect NextThreadId = %v", e.NextThreadId)
require.Equalf(t, e.ThreadCount, actualState.ThreadCount(), "Expect thread count = %v", e.ThreadCount)
......@@ -229,7 +220,4 @@ func (e *ExpectedMTState) validateThread(t require.TestingT, et *ExpectedThreadS
require.Equalf(t, et.Registers, actual.Registers, "Expect registers to match (%v)", threadInfo)
require.Equalf(t, et.ExitCode, actual.ExitCode, "Expect exitCode = %v (%v)", et.ExitCode, threadInfo)
require.Equalf(t, et.Exited, actual.Exited, "Expect exited = %v (%v)", et.Exited, threadInfo)
require.Equalf(t, et.FutexAddr, actual.FutexAddr, "Expect futexAddr = %v (%v)", et.FutexAddr, threadInfo)
require.Equalf(t, et.FutexVal, actual.FutexVal, "Expect futexVal = %v (%v)", et.FutexVal, threadInfo)
require.Equalf(t, et.FutexTimeoutStep, actual.FutexTimeoutStep, "Expect futexTimeoutStep = %v (%v)", et.FutexTimeoutStep, threadInfo)
}
......@@ -38,7 +38,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "LastHint", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LastHint = []byte{7, 8, 9, 10} }},
{name: "MemoryRoot", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.MemoryRoot = emptyHash }},
{name: "StepsSinceLastContextSwitch", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.StepsSinceLastContextSwitch += 1 }},
{name: "Wakeup", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Wakeup += 1 }},
{name: "TraverseRight", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.TraverseRight = !e.TraverseRight }},
{name: "NextThreadId", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.NextThreadId += 1 }},
{name: "ThreadCount", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ThreadCount += 1 }},
......@@ -60,15 +59,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "Active thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].Exited = !st.GetCurrentThread().Exited
}},
{name: "Active thread futexAddr", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexAddr += 1
}},
{name: "Active thread futexVal", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexVal += 1
}},
{name: "Active thread FutexTimeoutStep", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexTimeoutStep += 1
}},
{name: "Active thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].PC += 1
}},
......@@ -96,15 +86,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "Inactive thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].Exited = !FindNextThread(st).Exited
}},
{name: "Inactive thread futexAddr", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexAddr += 1
}},
{name: "Inactive thread futexVal", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexVal += 1
}},
{name: "Inactive thread FutexTimeoutStep", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexTimeoutStep += 1
}},
{name: "Inactive thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].PC += 1
}},
......
......@@ -9,21 +9,17 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
const (
THREAD_ID_STATE_WITNESS_OFFSET = 0
THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes
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 + 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)
THREAD_ID_STATE_WITNESS_OFFSET = 0
THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1
THREAD_CPU_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1
THREAD_REGISTERS_WITNESS_OFFSET = THREAD_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
// 166 and 318 bytes for 32 and 64-bit respectively
// 150 and 298 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.
......@@ -37,14 +33,11 @@ const (
var EmptyThreadsRoot common.Hash = common.HexToHash("0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5")
type ThreadState struct {
ThreadId Word `json:"threadId"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
FutexAddr Word `json:"futexAddr"`
FutexVal uint32 `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"`
ThreadId Word `json:"threadId"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"`
}
func CreateEmptyThread() *ThreadState {
......@@ -59,10 +52,7 @@ func CreateEmptyThread() *ThreadState {
LO: 0,
HI: 0,
},
FutexAddr: exec.FutexEmptyAddr,
FutexVal: 0,
FutexTimeoutStep: 0,
Registers: [32]Word{},
Registers: [32]Word{},
}
}
......@@ -72,9 +62,6 @@ func (t *ThreadState) serializeThread() []byte {
out = arch.ByteOrderWord.AppendWord(out, t.ThreadId)
out = append(out, t.ExitCode)
out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = arch.ByteOrderWord.AppendWord(out, t.FutexAddr)
out = binary.BigEndian.AppendUint32(out, t.FutexVal)
out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.PC)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.NextPC)
......@@ -107,15 +94,6 @@ func (t *ThreadState) Deserialize(in io.Reader) error {
return err
}
t.Exited = exited != 0
if err := binary.Read(in, binary.BigEndian, &t.FutexAddr); err != nil {
return err
}
if err := binary.Read(in, binary.BigEndian, &t.FutexVal); err != nil {
return err
}
if err := binary.Read(in, binary.BigEndian, &t.FutexTimeoutStep); err != nil {
return err
}
if err := binary.Read(in, binary.BigEndian, &t.Cpu.PC); err != nil {
return err
}
......
......@@ -25,5 +25,4 @@ const (
RegSyscallParam1 = RegA0
RegSyscallParam2 = RegA1
RegSyscallParam3 = RegA2
RegSyscallParam4 = RegA3
)
......@@ -13,7 +13,7 @@ import (
type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error {
syscallNum, a0, a1, a2, _ := exec.GetSyscallArgs(&m.state.Registers)
syscallNum, a0, a1, a2 := exec.GetSyscallArgs(&m.state.Registers)
v0 := Word(0)
v1 := Word(0)
......
......@@ -964,7 +964,7 @@ func TestEVM_HelloProgram(t *testing.T) {
break
}
insn := testutil.GetInstruction(state.GetMemory(), state.GetPC())
if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps
if i%100_000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}
......@@ -1012,7 +1012,7 @@ func TestEVM_ClaimProgram(t *testing.T) {
}
insn := testutil.GetInstruction(state.GetMemory(), state.GetPC())
if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps
if i%1_000_000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}
......@@ -1020,6 +1020,7 @@ func TestEVM_ClaimProgram(t *testing.T) {
require.NoError(t, err)
validator.ValidateEVM(t, stepWitness, curStep, goVm)
}
t.Logf("Completed in %d steps", state.GetStep())
require.True(t, state.GetExited(), "must complete program")
require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0")
......
......@@ -486,14 +486,13 @@ func TestEVM_PopExitedThread(t *testing.T) {
func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
// Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit
cases := []struct {
name string
addressParam uint64
effAddr uint64
targetValue uint32
actualValue uint32
timeout uint64
shouldFail bool
shouldSetTimeout bool
name string
addressParam uint64
effAddr uint64
targetValue uint32
actualValue uint32
timeout uint64
shouldFail 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_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},
......@@ -502,8 +501,8 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
{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: "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},
{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},
{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},
}
......@@ -525,20 +524,17 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
// Setup expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
expected.StepsSinceLastContextSwitch += 1
expected.ActiveThread().PC = state.GetCpu().NextPC
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
if c.shouldFail {
expected.ActiveThread().PC = state.GetCpu().NextPC
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
expected.StepsSinceLastContextSwitch += 1
expected.ActiveThread().Registers[2] = exec.SysErrorSignal
expected.ActiveThread().Registers[7] = exec.MipsEAGAIN
} else {
// PC and return registers should not update on success, updates happen when wait completes
expected.ActiveThread().FutexAddr = Word(c.effAddr)
expected.ActiveThread().FutexVal = c.targetValue
expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout
if c.shouldSetTimeout {
expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1
}
// Return empty result and preempt thread
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
expected.ExpectPreemption(state)
}
// State transition
......@@ -561,7 +557,6 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
activeThreadCount int
inactiveThreadCount int
traverseRight 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, 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},
......@@ -574,10 +569,10 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
{name: "Traverse right, 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: true},
{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_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},
{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},
{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},
{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},
{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},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
......@@ -595,14 +590,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)
expected.ExpectPreemption(state)
expected.TraverseRight = c.expectTraverseRight
if c.traverseRight != c.expectTraverseRight {
// If we preempt the current thread and then switch directions, the same
// thread will remain active
expected.ActiveThreadId = state.GetCurrentThread().ThreadId
}
// State transition
stepWitness, err := goVm.Step(true)
......@@ -1013,96 +1001,6 @@ func TestEVM_EmptyThreadStacks(t *testing.T) {
}
}
func TestEVM_NormalTraversalStep_HandleWaitingThread(t *testing.T) {
cases := []struct {
name string
step uint64
activeStackSize int
otherStackSize int
futexAddr 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, 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 {
for i, traverseRight := range []bool{true, false} {
testName := fmt.Sprintf("%v (traverseRight=%v)", c.name, traverseRight)
t.Run(testName, func(t *testing.T) {
// Sanity check
if !c.shouldWakeup && c.shouldTimeout {
require.Fail(t, "Invalid test case - cannot expect a timeout with no wakeup")
}
goVm, state, contracts := setup(t, i, nil)
mttestutil.SetupThreads(int64(i*101), state, traverseRight, c.activeStackSize, c.otherStackSize)
state.Step = c.step
activeThread := state.GetCurrentThread()
activeThread.FutexAddr = c.futexAddr
activeThread.FutexVal = c.targetValue
activeThread.FutexTimeoutStep = c.timeoutStep
testutil.RandomizeWordAndSetUint32(state.GetMemory(), c.effAddr, c.actualValue, int64(i+11))
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
if c.shouldWakeup {
expected.ActiveThread().FutexAddr = exec.FutexEmptyAddr
expected.ActiveThread().FutexVal = 0
expected.ActiveThread().FutexTimeoutStep = 0
// PC and return registers are updated onWaitComplete
expected.ActiveThread().PC = state.GetCpu().NextPC
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
if c.shouldTimeout {
expected.ActiveThread().Registers[2] = exec.SysErrorSignal
expected.ActiveThread().Registers[7] = exec.MipsETIMEDOUT
} else {
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
}
} else {
expected.ExpectPreemption(state)
}
// State transition
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, c.step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
}
func TestEVM_NormalTraversal_Full(t *testing.T) {
cases := []struct {
name string
......@@ -1120,21 +1018,20 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
// Setup
goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0)
// Put threads into a waiting state so that we just traverse through them
for _, thread := range mttestutil.GetAllThreads(state) {
thread.FutexAddr = 0x04
thread.FutexTimeoutStep = exec.FutexNoTimeout
}
step := state.Step
initialState := mttestutil.NewExpectedMTState(state)
// Loop through all the threads to get back to the starting state
iterations := c.threadCount * 2
for i := 0; i < iterations; i++ {
// Set up thread to yield
testutil.StoreInstruction(state.Memory, state.GetPC(), syscallInsn)
state.GetRegistersRef()[2] = Word(arch.SysSchedYield)
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
expected.ExpectStep()
expected.ExpectPreemption(state)
// State transition
......@@ -1147,218 +1044,11 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
}
// We should be back to the original state with only a few modifications
initialState.Step += uint64(iterations)
initialState.StepsSinceLastContextSwitch = 0
initialState.Validate(t, state)
})
}
}
}
func TestEVM_WakeupTraversalStep(t *testing.T) {
addr := Word(0x1234)
wakeupVal := uint32(0x999)
cases := []struct {
name string
wakeupAddr Word
futexAddr Word
targetVal uint32
traverseRight bool
activeStackSize int
otherStackSize int
shouldClearWakeup bool
shouldPreempt bool
}{
{name: "Matching addr, not wakeable, first thread", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 3, otherStackSize: 0, shouldClearWakeup: true},
{name: "Matching addr, wakeable, first thread", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal + 1, traverseRight: false, activeStackSize: 3, otherStackSize: 0, shouldClearWakeup: true},
{name: "Matching addr, not wakeable, last thread", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldClearWakeup: true},
{name: "Matching addr, wakeable, last thread", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal + 1, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldClearWakeup: true},
{name: "Matching addr, not wakeable, intermediate thread", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: false, activeStackSize: 2, otherStackSize: 2, shouldClearWakeup: true},
{name: "Matching addr, wakeable, intermediate thread", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal + 1, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldClearWakeup: true},
{name: "Mismatched addr, last thread", wakeupAddr: addr, futexAddr: addr + 4, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldPreempt: true, shouldClearWakeup: true},
{name: "Mismatched addr", wakeupAddr: addr, futexAddr: addr + 4, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldPreempt: true},
{name: "Mismatched addr", wakeupAddr: addr, futexAddr: addr + 4, traverseRight: false, activeStackSize: 2, otherStackSize: 0, shouldPreempt: true},
{name: "Mismatched addr", wakeupAddr: addr, futexAddr: addr + 4, traverseRight: false, activeStackSize: 1, otherStackSize: 0, shouldPreempt: true},
{name: "Non-waiting thread", wakeupAddr: addr, futexAddr: exec.FutexEmptyAddr, traverseRight: false, activeStackSize: 1, otherStackSize: 0, shouldPreempt: true},
{name: "Non-waiting thread", wakeupAddr: addr, futexAddr: exec.FutexEmptyAddr, traverseRight: true, activeStackSize: 2, otherStackSize: 1, shouldPreempt: true},
{name: "Non-waiting thread, last thread", wakeupAddr: addr, futexAddr: exec.FutexEmptyAddr, traverseRight: true, activeStackSize: 1, otherStackSize: 1, shouldPreempt: true, shouldClearWakeup: true},
// Check behavior of unaligned addresses - should be the same as aligned addresses (no memory access)
{name: "Matching addr, unaligned", wakeupAddr: addr + 1, futexAddr: addr + 1, targetVal: wakeupVal, traverseRight: false, activeStackSize: 3, otherStackSize: 0, shouldClearWakeup: true},
{name: "Mismatched addr, last thread, wakeup unaligned", wakeupAddr: addr + 1, futexAddr: addr + 4, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldPreempt: true, shouldClearWakeup: true},
{name: "Mismatched addr, last thread, futex unaligned", wakeupAddr: addr, futexAddr: addr + 5, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldPreempt: true, shouldClearWakeup: true},
{name: "Mismatched addr, last thread, wake & futex unaligned", wakeupAddr: addr + 1, futexAddr: addr + 5, traverseRight: true, activeStackSize: 1, otherStackSize: 2, shouldPreempt: true, shouldClearWakeup: true},
{name: "Mismatched addr, wakeup unaligned", wakeupAddr: addr + 3, futexAddr: addr + 4, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldPreempt: true},
{name: "Mismatched addr, futex unaligned", wakeupAddr: addr, futexAddr: addr + 6, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldPreempt: true},
{name: "Mismatched addr, wakeup & futex unaligned", wakeupAddr: addr + 2, futexAddr: addr + 6, traverseRight: true, activeStackSize: 2, otherStackSize: 2, shouldPreempt: true},
{name: "Non-waiting thread, last thread, unaligned wakeup", wakeupAddr: addr + 3, futexAddr: exec.FutexEmptyAddr, traverseRight: true, activeStackSize: 1, otherStackSize: 1, shouldPreempt: true, shouldClearWakeup: true},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*2000, nil)
mttestutil.SetupThreads(int64(i*101), state, c.traverseRight, c.activeStackSize, c.otherStackSize)
step := state.Step
state.Wakeup = c.wakeupAddr
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
activeThread.FutexTimeoutStep = exec.FutexNoTimeout
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
if c.shouldClearWakeup {
expected.Wakeup = exec.FutexEmptyAddr
}
if c.shouldPreempt {
// Just preempt the current thread
expected.ExpectPreemption(state)
}
// State transition
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
func TestEVM_WakeupTraversal_Full(t *testing.T) {
cases := []struct {
name string
threadCount int
}{
{"1 thread", 1},
{"2 threads", 2},
{"3 threads", 3},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
// Setup
goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, false, c.threadCount, 0)
state.Wakeup = 0x08
step := state.Step
initialState := mttestutil.NewExpectedMTState(state)
// Loop through all the threads to get back to the starting state
iterations := c.threadCount * 2
for i := 0; i < iterations; i++ {
// Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
expected.ExpectPreemption(state)
// State transition
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
// We should clear the wakeup on the last step
if i == iterations-1 {
expected.Wakeup = exec.FutexEmptyAddr
}
// Validate post-state
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
}
// We should be back to the original state with only a few modifications
initialState.Step += uint64(iterations)
initialState.StepsSinceLastContextSwitch = 0
initialState.Wakeup = exec.FutexEmptyAddr
initialState.Validate(t, state)
})
}
}
func TestEVM_WakeupTraversal_WithExitedThreads(t *testing.T) {
addr := Word(0x1230)
wakeupVal := uint32(0x999)
cases := []struct {
name string
wakeupAddr Word
futexAddr Word
targetVal uint32
traverseRight bool
activeStackSize int
otherStackSize int
exitedThreadIdx []int
shouldClearWakeup bool
shouldPreempt bool
activeThreadFutexAddr 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},
{name: "Exited threads, no matching futex", wakeupAddr: addr, futexAddr: addr + 4, targetVal: wakeupVal, traverseRight: false, activeStackSize: 2, otherStackSize: 1, exitedThreadIdx: []int{}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 24, activeThreadFutexVal: wakeupVal + 4},
{name: "Matching addr, not wakeable, with exited threads", wakeupAddr: addr, futexAddr: addr, targetVal: wakeupVal, traverseRight: true, activeStackSize: 3, otherStackSize: 0, exitedThreadIdx: []int{1}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 32, activeThreadFutexVal: wakeupVal + 5},
{name: "Non-waiting threads with exited threads", wakeupAddr: addr, futexAddr: exec.FutexEmptyAddr, targetVal: 0, traverseRight: false, activeStackSize: 2, otherStackSize: 1, exitedThreadIdx: []int{}, shouldClearWakeup: false, shouldPreempt: true, activeThreadFutexAddr: addr + 40, activeThreadFutexVal: wakeupVal + 6},
}
for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
goVm, state, contracts := setup(t, i*1000, nil)
mttestutil.SetupThreads(int64(i*5000), state, c.traverseRight, c.activeStackSize, c.otherStackSize)
step := state.Step
state.Wakeup = c.wakeupAddr
effWakeupAddr := ^Word(3) & c.wakeupAddr
testutil.RandomizeWordAndSetUint32(state.GetMemory(), effWakeupAddr, wakeupVal, int64(i+1111))
threads := mttestutil.GetAllThreads(state)
for idx, thread := range threads {
if slices.Contains(c.exitedThreadIdx, idx) {
thread.Exited = true
} else {
thread.FutexAddr = c.futexAddr
thread.FutexVal = c.targetVal
thread.FutexTimeoutStep = exec.FutexNoTimeout
}
}
activeThread := state.GetCurrentThread()
activeThread.Exited = true
activeThread.FutexAddr = c.activeThreadFutexAddr
activeThread.FutexVal = c.activeThreadFutexVal
activeThread.FutexTimeoutStep = exec.FutexNoTimeout
expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1
if c.shouldClearWakeup {
expected.Wakeup = exec.FutexEmptyAddr
}
if c.shouldPreempt {
// Just preempt the current thread
expected.ExpectPreemption(state)
}
// State transition
var err error
var stepWitness *mipsevm.StepWitness
stepWitness, err = goVm.Step(true)
require.NoError(t, err)
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts)
})
}
}
func TestEVM_SchedQuantumThreshold(t *testing.T) {
cases := []struct {
name string
......
......@@ -27,7 +27,7 @@ func DetectVersion(path string) (StateVersion, error) {
}
switch ver {
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2:
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3:
return ver, nil
default:
return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver)
......
......@@ -64,9 +64,9 @@ func TestDetectVersion_singleThreadedBinary(t *testing.T) {
}
func TestDetectVersion_multiThreadedBinary(t *testing.T) {
targetVersion := VersionMultiThreaded
targetVersion := VersionMultiThreaded_v2
if !arch.IsMips32 {
targetVersion = VersionMultiThreaded64_v2
targetVersion = VersionMultiThreaded64_v3
}
state, err := NewFromState(multithreaded.CreateEmptyState())
......
......@@ -19,12 +19,19 @@ type StateVersion uint8
const (
// VersionSingleThreaded is the version of the Cannon STF found in op-contracts/v1.6.0 - https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.6.0/packages/contracts-bedrock/src/cannon/MIPS.sol
VersionSingleThreaded StateVersion = iota
// VersionMultiThreaded is the original implementation of 32-bit multithreaded cannon, tagged at cannon/v1.3.0
VersionMultiThreaded
// VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall
// This is the latest 32-bit single-threaded vm
VersionSingleThreaded2
// VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0
VersionMultiThreaded64
// VersionMultiThreaded64_v2 includes an audit fix to ensure futex values are always 32-bit, tagged at cannon/v1.3.0
VersionMultiThreaded64_v2
// VersionMultiThreaded_v2 is the latest 32-bit multithreaded vm
VersionMultiThreaded_v2
// VersionMultiThreaded64_v3 is the latest 64-bit multithreaded vm
VersionMultiThreaded64_v3
)
var (
......@@ -33,7 +40,7 @@ var (
ErrUnsupportedMipsArch = errors.New("mips architecture is not supported")
)
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2}
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3}
func LoadStateFromFile(path string) (*VersionedState, error) {
if !serialize.IsBinaryFile(path) {
......@@ -60,12 +67,12 @@ func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) {
case *multithreaded.State:
if arch.IsMips32 {
return &VersionedState{
Version: VersionMultiThreaded,
Version: VersionMultiThreaded_v2,
FPVMState: state,
}, nil
} else {
return &VersionedState{
Version: VersionMultiThreaded64_v2,
Version: VersionMultiThreaded64_v3,
FPVMState: state,
}, nil
}
......@@ -106,7 +113,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
}
s.FPVMState = state
return nil
case VersionMultiThreaded:
case VersionMultiThreaded_v2:
if !arch.IsMips32 {
return ErrUnsupportedMipsArch
}
......@@ -116,7 +123,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
}
s.FPVMState = state
return nil
case VersionMultiThreaded64_v2:
case VersionMultiThreaded64_v3:
if arch.IsMips32 {
return ErrUnsupportedMipsArch
}
......@@ -155,6 +162,10 @@ func (s StateVersion) String() string {
return "multithreaded64"
case VersionMultiThreaded64_v2:
return "multithreaded64-2"
case VersionMultiThreaded_v2:
return "multithreaded-2"
case VersionMultiThreaded64_v3:
return "multithreaded64-3"
default:
return "unknown"
}
......@@ -172,6 +183,10 @@ func ParseStateVersion(ver string) (StateVersion, error) {
return VersionMultiThreaded64, nil
case "multithreaded64-2":
return VersionMultiThreaded64_v2, nil
case "multithreaded-2":
return VersionMultiThreaded_v2, nil
case "multithreaded64-3":
return VersionMultiThreaded64_v3, nil
default:
return StateVersion(0), errors.New("unknown state version")
}
......
......@@ -15,16 +15,16 @@ import (
)
func TestNewFromState(t *testing.T) {
t.Run("multithreaded64-2", func(t *testing.T) {
t.Run("multithreaded64-latestVersion", func(t *testing.T) {
actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState)
require.Equal(t, VersionMultiThreaded64_v2, actual.Version)
require.Equal(t, VersionMultiThreaded64_v3, actual.Version)
})
}
func TestLoadStateFromFile(t *testing.T) {
t.Run("Multithreaded64_v2_FromBinary", func(t *testing.T) {
t.Run("Multithreaded64_latestVersion_FromBinary", func(t *testing.T) {
expected, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
......@@ -40,7 +40,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
version StateVersion
createState func() mipsevm.FPVMState
}{
{VersionMultiThreaded64_v2, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
{VersionMultiThreaded64_v3, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
}
for _, test := range tests {
test := test
......
......@@ -16,18 +16,18 @@ import (
)
func TestNewFromState(t *testing.T) {
t.Run("singlethreaded-2", func(t *testing.T) {
t.Run("singlethreaded-latestVersion", func(t *testing.T) {
actual, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &singlethreaded.State{}, actual.FPVMState)
require.Equal(t, VersionSingleThreaded2, actual.Version)
})
t.Run("multithreaded", func(t *testing.T) {
t.Run("multithreaded-latestVersion", func(t *testing.T) {
actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState)
require.Equal(t, VersionMultiThreaded, actual.Version)
require.Equal(t, VersionMultiThreaded_v2, actual.Version)
})
}
......@@ -59,7 +59,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
createState func() mipsevm.FPVMState
}{
{VersionSingleThreaded2, func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }},
{VersionMultiThreaded, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
{VersionMultiThreaded_v2, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
}
for _, test := range tests {
test := test
......@@ -75,6 +75,16 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
}
}
func TestParseStateVersion(t *testing.T) {
for _, version := range StateVersionTypes {
t.Run(version.String(), func(t *testing.T) {
result, err := ParseStateVersion(version.String())
require.NoError(t, err)
require.Equal(t, version, result)
})
}
}
func writeToFile(t *testing.T, filename string, data serialize.Serializable) string {
dir := t.TempDir()
path := filepath.Join(dir, filename)
......
......@@ -198,7 +198,6 @@ func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64
e.metrics.RecordMaxStepsBetweenLLAndSC(uint64(info.MaxStepsBetweenLLAndSC))
e.metrics.RecordReservationInvalidationCount(uint64(info.ReservationInvalidationCount))
e.metrics.RecordForcedPreemptionCount(uint64(info.ForcedPreemptionCount))
e.metrics.RecordFailedWakeupCount(uint64(info.FailedWakeupCount))
e.metrics.RecordIdleStepCountThread0(uint64(info.IdleStepCountThread0))
}
}
......@@ -214,6 +213,5 @@ type debugInfo struct {
MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"`
ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"`
ForcedPreemptionCount uint64 `json:"forced_preemption_count"`
FailedWakeupCount uint64 `json:"failed_wakeup_count"`
IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"`
}
......@@ -53,7 +53,6 @@ func TestGenerateProof(t *testing.T) {
MaxStepsBetweenLLAndSC: 56,
ReservationInvalidationCount: 78,
ForcedPreemptionCount: 910,
FailedWakeupCount: 1112,
IdleStepCountThread0: 1314,
}
......@@ -191,7 +190,6 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev
require.Equal(t, expected.MaxStepsBetweenLLAndSC, m.maxStepsBetweenLLAndSC)
require.Equal(t, expected.ReservationInvalidationCount, m.reservationInvalidations)
require.Equal(t, expected.ForcedPreemptionCount, m.forcedPreemptions)
require.Equal(t, expected.FailedWakeupCount, m.failedWakeup)
require.Equal(t, expected.IdleStepCountThread0, m.idleStepsThread0)
} else {
// If debugInfo is disabled, json file should not be written and metrics should be zeroed out
......@@ -202,7 +200,6 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev
require.Equal(t, uint64(0), m.maxStepsBetweenLLAndSC)
require.Equal(t, uint64(0), m.reservationInvalidations)
require.Equal(t, uint64(0), m.forcedPreemptions)
require.Equal(t, uint64(0), m.failedWakeup)
require.Equal(t, uint64(0), m.idleStepsThread0)
}
}
......@@ -220,7 +217,6 @@ type capturingVmMetrics struct {
maxStepsBetweenLLAndSC uint64
reservationInvalidations uint64
forcedPreemptions uint64
failedWakeup uint64
idleStepsThread0 uint64
}
......@@ -256,10 +252,6 @@ func (c *capturingVmMetrics) RecordForcedPreemptionCount(val uint64) {
c.forcedPreemptions = val
}
func (c *capturingVmMetrics) RecordFailedWakeupCount(val uint64) {
c.failedWakeup = val
}
func (c *capturingVmMetrics) RecordIdleStepCountThread0(val uint64) {
c.idleStepsThread0 = val
}
......
......@@ -17,7 +17,6 @@ type VmMetricer interface {
RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64)
RecordVmReservationInvalidationCount(vmType string, val uint64)
RecordVmForcedPreemptionCount(vmType string, val uint64)
RecordVmFailedWakeupCount(vmType string, val uint64)
RecordVmIdleStepCountThread0(vmType string, val uint64)
}
......@@ -31,7 +30,6 @@ type TypedVmMetricer interface {
RecordMaxStepsBetweenLLAndSC(val uint64)
RecordReservationInvalidationCount(val uint64)
RecordForcedPreemptionCount(val uint64)
RecordFailedWakeupCount(val uint64)
RecordIdleStepCountThread0(val uint64)
}
......@@ -44,7 +42,6 @@ type VmMetrics struct {
vmMaxStepsBetweenLLAndSC *prometheus.GaugeVec
vmReservationInvalidations *prometheus.GaugeVec
vmForcedPreemptions *prometheus.GaugeVec
vmFailedWakeup *prometheus.GaugeVec
vmIdleStepsThread0 *prometheus.GaugeVec
}
......@@ -82,10 +79,6 @@ func (m *VmMetrics) RecordVmForcedPreemptionCount(vmType string, val uint64) {
m.vmForcedPreemptions.WithLabelValues(vmType).Set(float64(val))
}
func (m *VmMetrics) RecordVmFailedWakeupCount(vmType string, val uint64) {
m.vmFailedWakeup.WithLabelValues(vmType).Set(float64(val))
}
func (m *VmMetrics) RecordVmIdleStepCountThread0(vmType string, val uint64) {
m.vmIdleStepsThread0.WithLabelValues(vmType).Set(float64(val))
}
......@@ -137,11 +130,6 @@ func NewVmMetrics(namespace string, factory metrics.Factory) *VmMetrics {
Name: "vm_forced_preemptions",
Help: "Number of forced preemptions during vm run",
}, []string{"vm"}),
vmFailedWakeup: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "vm_failed_wakeup",
Help: "Number of failed wakesups during vm run",
}, []string{"vm"}),
vmIdleStepsThread0: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "vm_idle_steps_thread0",
......@@ -162,7 +150,6 @@ func (n NoopVmMetrics) RecordVmRmwFailCount(vmType string, val uint64)
func (n NoopVmMetrics) RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmReservationInvalidationCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmForcedPreemptionCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmFailedWakeupCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmIdleStepCountThread0(vmType string, val uint64) {}
type typedVmMetricsImpl struct {
......@@ -204,10 +191,6 @@ func (m *typedVmMetricsImpl) RecordForcedPreemptionCount(val uint64) {
m.m.RecordVmForcedPreemptionCount(m.vmType, val)
}
func (m *typedVmMetricsImpl) RecordFailedWakeupCount(val uint64) {
m.m.RecordVmFailedWakeupCount(m.vmType, val)
}
func (m *typedVmMetricsImpl) RecordIdleStepCountThread0(val uint64) {
m.m.RecordVmIdleStepCountThread0(m.vmType, val)
}
......
......@@ -34,9 +34,9 @@ RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-pro
# Run the op-program-client.elf binary directly through cannon's load-elf subcommand.
RUN /app/cannon/bin/cannon load-elf --type singlethreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.bin.gz --meta "/app/op-program/bin/meta.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "/app/op-program/bin/meta-mt.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-2 --path /app/op-program/bin/op-program-client64.elf --out /app/op-program/bin/prestate-mt64.bin.gz --meta "/app/op-program/bin/meta-mt64.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-2 --path /app/op-program/bin/op-program-client-interop.elf --out /app/op-program/bin/prestate-interop.bin.gz --meta "/app/op-program/bin/meta-interop.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "/app/op-program/bin/meta-mt.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-3 --path /app/op-program/bin/op-program-client64.elf --out /app/op-program/bin/prestate-mt64.bin.gz --meta "/app/op-program/bin/meta-mt64.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-3 --path /app/op-program/bin/op-program-client-interop.elf --out /app/op-program/bin/prestate-interop.bin.gz --meta "/app/op-program/bin/meta-interop.json"
# Generate the prestate proof containing the absolute pre-state hash.
RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output ""
......
......@@ -65,12 +65,15 @@ ARG TARGETARCH
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.0.0 AS cannon-builder-v1-0-0
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.2.0 AS cannon-builder-v1-2-0
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.3.0 AS cannon-builder-v1-3-0
FROM --platform=$BUILDPLATFORM builder AS cannon-builder
ARG CANNON_VERSION=v0.0.0
# Copy cannon binaries from previous versions
COPY --from=cannon-builder-v1-0-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-0
COPY --from=cannon-builder-v1-2-0 /usr/local/bin/cannon-3 ./cannon/multicannon/embeds/cannon-3
COPY --from=cannon-builder-v1-3-0 /usr/local/bin/cannon-1 ./cannon/multicannon/embeds/cannon-1
COPY --from=cannon-builder-v1-3-0 /usr/local/bin/cannon-4 ./cannon/multicannon/embeds/cannon-4
# Build current binaries
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \
GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION"
......
......@@ -11,9 +11,6 @@ interface IMIPS2 is ISemver {
uint32 threadID;
uint8 exitCode;
bool exited;
uint32 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint32 pc;
uint32 nextPC;
uint32 lo;
......@@ -33,7 +30,6 @@ interface IMIPS2 is ISemver {
bool exited;
uint64 step;
uint64 stepsSinceLastContextSwitch;
uint32 wakeup;
bool traverseRight;
bytes32 leftThreadStack;
bytes32 rightThreadStack;
......
......@@ -140,16 +140,16 @@
"sourceCodeHash": "0xe9964aa66db1dfc86772958b4c9276697e67f7055529a43e6a49a055009bc995"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0xc10654f0e6498f424f7a5095bac36005dc7062d3813cc8f805a15005fc37406b",
"sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af"
"initCodeHash": "0x7d0fc7c7b51b74fa2611aaa8cc1a5967e2e48f0726ea894eb2c43f36b0ff2ab7",
"sourceCodeHash": "0xc18f51210c9d75f9bc33e55f879e8f0ab2514924718022264c0a2993134821e0"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0x70ecbb2327fadd6205312aa93279a6340242dfeb44a00b98caa7b56688c46bdc",
"sourceCodeHash": "0x84506539c40b72b1d40c9d07418650b30c27d2b219b40e55f61edcd31365157f"
"initCodeHash": "0x4e1dbd0a6ac84873622af9234aca42e6e7b2bfda1186bbfd3ff83081f141ef86",
"sourceCodeHash": "0x62c820b22c72399efd7688dcf713c34a6ee6821835ec66d5e7b98f33bbbfb209"
},
"src/cannon/MIPS64.sol": {
"initCodeHash": "0xa2a42c50d2fac71d93e44ad4871e5d838f1c630b9d1abc4c89971d36b0ae44bb",
"sourceCodeHash": "0xdb771f1b92c7612b120e0bce31967f0c8a7ce332dbb426bc9cfc52b47be21c4d"
"initCodeHash": "0xd15808bd3a9f0779dfc662dd75fb11fe5f2ff15c3a6e9f699d05dad49e064afb",
"sourceCodeHash": "0x84609ac875a282e8a0675bcd8558635dcd7d054cc23395691d87922a15a815ba"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x17d3b3df1aaaf7a705b8d48de8a05e6511b910fdafdbe5eb7f7f95ec944fba9a",
......
......@@ -47,8 +47,8 @@ contract MIPS is ISemver {
}
/// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.2.1-beta.10
string public constant version = "1.2.1-beta.10";
/// @custom:semver 1.3.0-beta.1
string public constant version = "1.3.0-beta.1";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -151,7 +151,7 @@ contract MIPS is ISemver {
}
// Load the syscall numbers and args from the registers
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2,) = sys.getSyscallArgs(state.registers);
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2) = sys.getSyscallArgs(state.registers);
uint32 v0 = 0;
uint32 v1 = 0;
......
......@@ -20,16 +20,13 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
/// It differs from MIPS.sol in that it supports multi-threading.
contract MIPS2 is ISemver {
/// @notice The thread context.
/// Total state size: 4 + 1 + 1 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 32 * 4 = 166 bytes
/// Total state size: 4 + 1 + 1 + 4 + 4 + 4 + 4 + 32 * 4 = 150 bytes
struct ThreadState {
// metadata
uint32 threadID;
uint8 exitCode;
bool exited;
// state
uint32 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint32 pc;
uint32 nextPC;
uint32 lo;
......@@ -37,11 +34,13 @@ contract MIPS2 is ISemver {
uint32[32] registers;
}
uint32 internal constant PACKED_THREAD_STATE_SIZE = 150;
uint8 internal constant LL_STATUS_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE = 1;
/// @notice Stores the VM state.
/// Total state size: 32 + 32 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 172 bytes
/// Total state size: 32 + 32 + 4 + 4 + 1 + 4 + 4 + 1 + 1 + 8 + 8 + 1 + 32 + 32 + 4 = 168 bytes
/// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State {
bytes32 memRoot;
......@@ -55,7 +54,6 @@ contract MIPS2 is ISemver {
bool exited;
uint64 step;
uint64 stepsSinceLastContextSwitch;
uint32 wakeup;
bool traverseRight;
bytes32 leftThreadStack;
bytes32 rightThreadStack;
......@@ -63,8 +61,8 @@ contract MIPS2 is ISemver {
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.27
string public constant version = "1.0.0-beta.27";
/// @custom:semver 1.0.0-beta.28
string public constant version = "1.0.0-beta.28";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -73,7 +71,7 @@ contract MIPS2 is ISemver {
uint256 internal constant THREAD_PROOF_OFFSET = 356;
// The offset of the start of proof calldata (_memProof.offset) in the step() function
uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + 166 + 32;
uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32;
// The empty thread root - keccak256(bytes32(0) ++ bytes32(0))
bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5";
......@@ -82,7 +80,7 @@ contract MIPS2 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step
uint256 internal constant TC_MEM_OFFSET = 0x280;
uint256 internal constant TC_MEM_OFFSET = 0x260;
/// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) {
......@@ -149,10 +147,13 @@ contract MIPS2 is ISemver {
}
if iszero(eq(thread, TC_MEM_OFFSET)) {
// expected thread mem offset check
// STATE_MEM_OFFSET = 0x80 = 128
// 32 bytes per state field = 32 * 15 = 480
// TC_MEM_OFFSET = 480 + 128 = 608 = 0x260
revert(0, 0)
}
if iszero(eq(mload(0x40), shl(5, 63))) {
// 4 + 16 state slots + 43 thread slots = 63 expected memory check
if iszero(eq(mload(0x40), shl(5, 59))) {
// 4 + 15 state slots + 40 thread slots = 59 expected memory check
revert(0, 0)
}
if iszero(eq(_stateData.offset, 132)) {
......@@ -160,7 +161,10 @@ contract MIPS2 is ISemver {
revert(0, 0)
}
if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) {
// _stateData.offset+192+32=356 expected thread proof offset
// _stateData.offset = 132
// stateData.length = ceil(stateSize / 32) * 32 = 6 * 32 = 192
// _proof size prefix = 32
// expected thread proof offset equals the sum of the above is 356
revert(0, 0)
}
......@@ -187,7 +191,6 @@ contract MIPS2 is ISemver {
exited := mload(sub(m, 32))
c, m := putField(c, m, 8) // step
c, m := putField(c, m, 8) // stepsSinceLastContextSwitch
c, m := putField(c, m, 4) // wakeup
c, m := putField(c, m, 1) // traverseRight
c, m := putField(c, m, 32) // leftThreadStack
c, m := putField(c, m, 32) // rightThreadStack
......@@ -212,52 +215,11 @@ contract MIPS2 is ISemver {
setThreadStateFromCalldata(thread);
validateCalldataThreadWitness(state, thread);
// Search for the first thread blocked by the wakeup call, if wakeup is set
// Don't allow regular execution until we resolved if we have woken up any thread.
if (state.wakeup != sys.FUTEX_EMPTY_ADDR) {
if (state.wakeup == thread.futexAddr) {
// completed wake traversal
// resume execution on woken up thread
state.wakeup = sys.FUTEX_EMPTY_ADDR;
return outputState();
} else {
bool traversingRight = state.traverseRight;
bool changedDirections = preemptThread(state, thread);
if (traversingRight && changedDirections) {
// then we've completed wake traversal
// resume thread execution
state.wakeup = sys.FUTEX_EMPTY_ADDR;
}
return outputState();
}
}
if (thread.exited) {
popThread(state);
return outputState();
}
// check if thread is blocked on a futex
if (thread.futexAddr != sys.FUTEX_EMPTY_ADDR) {
// if set, then check futex
// check timeout first
if (state.step > thread.futexTimeoutStep) {
// timeout! Allow execution
return onWaitComplete(thread, true);
} else {
uint32 futexVal = getFutexValue(thread.futexAddr);
if (thread.futexVal == futexVal) {
// still got expected value, continue sleeping, try next thread.
preemptThread(state, thread);
return outputState();
} else {
// wake thread up, the value at its address changed!
// Userspace can turn thread back to sleep if it was too sporadic.
return onWaitComplete(thread, false);
}
}
}
if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) {
preemptThread(state, thread);
return outputState();
......@@ -394,7 +356,7 @@ contract MIPS2 is ISemver {
}
// Load the syscall numbers and args from the registers
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2, uint32 a3) = sys.getSyscallArgs(thread.registers);
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2) = sys.getSyscallArgs(thread.registers);
// Syscalls that are unimplemented but known return with v0=0 and v1=0
uint32 v0 = 0;
uint32 v1 = 0;
......@@ -416,9 +378,6 @@ contract MIPS2 is ISemver {
newThread.threadID = state.nextThreadID;
newThread.exitCode = 0;
newThread.exited = false;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4;
newThread.lo = thread.lo;
......@@ -494,39 +453,16 @@ contract MIPS2 is ISemver {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN;
} else {
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();
return outputState();
return syscallYield(state, thread);
}
} 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 = 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;
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
state.traverseRight = state.leftThreadStack == EMPTY_THREAD_ROOT;
return outputState();
return syscallYield(state, thread);
} else {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL;
}
} else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) {
v0 = 0;
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
return outputState();
return syscallYield(state, thread);
} else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF;
......@@ -650,6 +586,17 @@ contract MIPS2 is ISemver {
}
}
function syscallYield(State memory _state, ThreadState memory _thread) internal returns (bytes32 out_) {
uint32 v0 = 0;
uint32 v1 = 0;
st.CpuScalars memory cpu = getCpuScalars(_thread);
sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1);
setStateCpuScalars(_thread, cpu);
preemptThread(_state, _thread);
return outputState();
}
function execSysRead(
State memory _state,
sys.SysReadParams memory _args
......@@ -699,7 +646,6 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // step
from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch
from, to := copyMem(from, to, 4) // wakeup
from, to := copyMem(from, to, 1) // traverseRight
from, to := copyMem(from, to, 32) // leftThreadStack
from, to := copyMem(from, to, 32) // rightThreadStack
......@@ -750,26 +696,6 @@ contract MIPS2 is ISemver {
}
}
/// @notice Completes the FUTEX_WAIT syscall.
function onWaitComplete(ThreadState memory _thread, bool _isTimedOut) internal returns (bytes32 out_) {
// Note: no need to reset State.wakeup. If we're here, the wakeup field has already been reset
// Clear the futex state
_thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
_thread.futexVal = 0;
_thread.futexTimeoutStep = 0;
// Complete the FUTEX_WAIT syscall
uint32 v0 = _isTimedOut ? sys.SYS_ERROR_SIGNAL : 0;
// set errno
uint32 v1 = _isTimedOut ? sys.ETIMEDOUT : 0;
st.CpuScalars memory cpu = getCpuScalars(_thread);
sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1);
setStateCpuScalars(_thread, cpu);
updateCurrentThreadRoot();
out_ = outputState();
}
/// @notice Preempts the current thread for another and updates the VM state.
/// It reads the inner thread root from calldata to update the current thread stack root.
function preemptThread(
......@@ -855,9 +781,6 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 4) // threadID
from, to := copyMem(from, to, 1) // exitCode
from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 4) // futexAddr
from, to := copyMem(from, to, 4) // futexVal
from, to := copyMem(from, to, 8) // futexTimeoutStep
from, to := copyMem(from, to, 4) // pc
from, to := copyMem(from, to, 4) // nextPC
from, to := copyMem(from, to, 4) // lo
......@@ -899,7 +822,7 @@ contract MIPS2 is ISemver {
s := calldatasize()
}
// verify we have enough calldata
require(s >= (THREAD_PROOF_OFFSET + 166), "insufficient calldata for thread witness");
require(s >= (THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE), "insufficient calldata for thread witness");
unchecked {
assembly {
......@@ -916,9 +839,6 @@ contract MIPS2 is ISemver {
c, m := putField(c, m, 4) // threadID
c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited
c, m := putField(c, m, 4) // futexAddr
c, m := putField(c, m, 4) // futexVal
c, m := putField(c, m, 8) // futexTimeoutStep
c, m := putField(c, m, 4) // pc
c, m := putField(c, m, 4) // nextPC
c, m := putField(c, m, 4) // lo
......@@ -935,10 +855,10 @@ contract MIPS2 is ISemver {
uint256 s = 0;
assembly {
s := calldatasize()
innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, 166))
innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, PACKED_THREAD_STATE_SIZE))
}
// verify we have enough calldata
require(s >= (THREAD_PROOF_OFFSET + 198), "insufficient calldata for thread witness"); // 166 + 32
require(s >= (THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32), "insufficient calldata for thread witness");
}
/// @notice Loads a 32-bit futex value at _vAddr
......
......@@ -21,16 +21,13 @@ 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 + 4 + 8 + 8 + 8 + 8 + 8 + 32 * 8 = 318 bytes
/// Total state size: 8 + 1 + 1 + 8 + 8 + 8 + 8 + 32 * 8 = 298 bytes
struct ThreadState {
// metadata
uint64 threadID;
uint8 exitCode;
bool exited;
// state
uint64 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint64 pc;
uint64 nextPC;
uint64 lo;
......@@ -38,14 +35,14 @@ contract MIPS64 is ISemver {
uint64[32] registers;
}
uint32 internal constant PACKED_THREAD_STATE_SIZE = 318;
uint32 internal constant PACKED_THREAD_STATE_SIZE = 298;
uint8 internal constant LL_STATUS_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1;
uint8 internal constant LL_STATUS_ACTIVE_64_BIT = 0x2;
/// @notice Stores the VM state.
/// Total state size: 32 + 32 + 8 + 8 + 1 + 8 + 8 + 1 + 1 + 8 + 8 + 8 + 1 + 32 + 32 + 8 = 196 bytes
/// Total state size: 32 + 32 + 8 + 8 + 1 + 8 + 8 + 1 + 1 + 8 + 8 + 1 + 32 + 32 + 8 = 188 bytes
/// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State {
bytes32 memRoot;
......@@ -59,7 +56,6 @@ contract MIPS64 is ISemver {
bool exited;
uint64 step;
uint64 stepsSinceLastContextSwitch;
uint64 wakeup;
bool traverseRight;
bytes32 leftThreadStack;
bytes32 rightThreadStack;
......@@ -67,14 +63,14 @@ contract MIPS64 is ISemver {
}
/// @notice The semantic version of the MIPS64 contract.
/// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.9";
/// @custom:semver 1.0.0-beta.10
string public constant version = "1.0.0-beta.10";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
// The offset of the start of proof calldata (_threadWitness.offset) in the step() function
uint256 internal constant THREAD_PROOF_OFFSET = 388;
uint256 internal constant THREAD_PROOF_OFFSET = 356;
// The offset of the start of proof calldata (_memProof.offset) in the step() function
uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32;
......@@ -86,7 +82,7 @@ contract MIPS64 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step
uint256 internal constant TC_MEM_OFFSET = 0x280;
uint256 internal constant TC_MEM_OFFSET = 0x260;
/// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) {
......@@ -153,10 +149,13 @@ contract MIPS64 is ISemver {
}
if iszero(eq(thread, TC_MEM_OFFSET)) {
// expected thread mem offset check
// STATE_MEM_OFFSET = 0x80 = 128
// 32 bytes per state field = 32 * 15 = 480
// TC_MEM_OFFSET = 480 + 128 = 608 = 0x260
revert(0, 0)
}
if iszero(eq(mload(0x40), shl(5, 63))) {
// 4 + 16 state slots + 43 thread slots = 63 expected memory check
if iszero(eq(mload(0x40), shl(5, 59))) {
// 4 + 15 state slots + 40 thread slots = 59 expected memory check
revert(0, 0)
}
if iszero(eq(_stateData.offset, 132)) {
......@@ -165,10 +164,9 @@ contract MIPS64 is ISemver {
}
if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) {
// _stateData.offset = 132
// stateData.length = 196
// 32-byte align padding = 28
// stateData.length = ceil(stateSize / 32) * 32 = 6 * 32 = 192
// _proof size prefix = 32
// expected thread proof offset equals the sum of the above is 388
// expected thread proof offset equals the sum of the above is 356
revert(0, 0)
}
......@@ -195,7 +193,6 @@ contract MIPS64 is ISemver {
exited := mload(sub(m, 32))
c, m := putField(c, m, 8) // step
c, m := putField(c, m, 8) // stepsSinceLastContextSwitch
c, m := putField(c, m, 8) // wakeup
c, m := putField(c, m, 1) // traverseRight
c, m := putField(c, m, 32) // leftThreadStack
c, m := putField(c, m, 32) // rightThreadStack
......@@ -220,52 +217,11 @@ contract MIPS64 is ISemver {
setThreadStateFromCalldata(thread);
validateCalldataThreadWitness(state, thread);
// Search for the first thread blocked by the wakeup call, if wakeup is set
// Don't allow regular execution until we resolved if we have woken up any thread.
if (state.wakeup != sys.FUTEX_EMPTY_ADDR) {
if (state.wakeup == thread.futexAddr) {
// completed wake traversal
// resume execution on woken up thread
state.wakeup = sys.FUTEX_EMPTY_ADDR;
return outputState();
} else {
bool traversingRight = state.traverseRight;
bool changedDirections = preemptThread(state, thread);
if (traversingRight && changedDirections) {
// then we've completed wake traversal
// resume thread execution
state.wakeup = sys.FUTEX_EMPTY_ADDR;
}
return outputState();
}
}
if (thread.exited) {
popThread(state);
return outputState();
}
// check if thread is blocked on a futex
if (thread.futexAddr != sys.FUTEX_EMPTY_ADDR) {
// if set, then check futex
// check timeout first
if (state.step > thread.futexTimeoutStep) {
// timeout! Allow execution
return onWaitComplete(thread, true);
} else {
uint32 futexVal = getFutexValue(thread.futexAddr);
if (thread.futexVal == futexVal) {
// still got expected value, continue sleeping, try next thread.
preemptThread(state, thread);
return outputState();
} else {
// wake thread up, the value at its address changed!
// Userspace can turn thread back to sleep if it was too sporadic.
return onWaitComplete(thread, false);
}
}
}
if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) {
preemptThread(state, thread);
return outputState();
......@@ -434,7 +390,7 @@ contract MIPS64 is ISemver {
}
// Load the syscall numbers and args from the registers
(uint64 syscall_no, uint64 a0, uint64 a1, uint64 a2, uint64 a3) = sys.getSyscallArgs(thread.registers);
(uint64 syscall_no, uint64 a0, uint64 a1, uint64 a2) = sys.getSyscallArgs(thread.registers);
// Syscalls that are unimplemented but known return with v0=0 and v1=0
uint64 v0 = 0;
uint64 v1 = 0;
......@@ -456,9 +412,6 @@ contract MIPS64 is ISemver {
newThread.threadID = state.nextThreadID;
newThread.exitCode = 0;
newThread.exited = false;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4;
newThread.lo = thread.lo;
......@@ -535,39 +488,16 @@ contract MIPS64 is ISemver {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN;
} else {
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();
return outputState();
return syscallYield(state, thread);
}
} 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 = 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;
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
state.traverseRight = state.leftThreadStack == EMPTY_THREAD_ROOT;
return outputState();
return syscallYield(state, thread);
} else {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL;
}
} else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) {
v0 = 0;
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
return outputState();
return syscallYield(state, thread);
} else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF;
......@@ -687,6 +617,17 @@ contract MIPS64 is ISemver {
}
}
function syscallYield(State memory _state, ThreadState memory _thread) internal returns (bytes32 out_) {
uint64 v0 = 0;
uint64 v1 = 0;
st.CpuScalars memory cpu = getCpuScalars(_thread);
sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1);
setStateCpuScalars(_thread, cpu);
preemptThread(_state, _thread);
return outputState();
}
function execSysRead(
State memory _state,
sys.SysReadParams memory _args
......@@ -736,7 +677,6 @@ contract MIPS64 is ISemver {
from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // step
from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch
from, to := copyMem(from, to, 8) // wakeup
from, to := copyMem(from, to, 1) // traverseRight
from, to := copyMem(from, to, 32) // leftThreadStack
from, to := copyMem(from, to, 32) // rightThreadStack
......@@ -787,26 +727,6 @@ contract MIPS64 is ISemver {
}
}
/// @notice Completes the FUTEX_WAIT syscall.
function onWaitComplete(ThreadState memory _thread, bool _isTimedOut) internal returns (bytes32 out_) {
// Note: no need to reset State.wakeup. If we're here, the wakeup field has already been reset
// Clear the futex state
_thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
_thread.futexVal = 0;
_thread.futexTimeoutStep = 0;
// Complete the FUTEX_WAIT syscall
uint64 v0 = _isTimedOut ? sys.SYS_ERROR_SIGNAL : 0;
// set errno
uint64 v1 = _isTimedOut ? sys.ETIMEDOUT : 0;
st.CpuScalars memory cpu = getCpuScalars(_thread);
sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1);
setStateCpuScalars(_thread, cpu);
updateCurrentThreadRoot();
out_ = outputState();
}
/// @notice Preempts the current thread for another and updates the VM state.
/// It reads the inner thread root from calldata to update the current thread stack root.
function preemptThread(
......@@ -892,9 +812,6 @@ contract MIPS64 is ISemver {
from, to := copyMem(from, to, 8) // threadID
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, 4) // futexVal
from, to := copyMem(from, to, 8) // futexTimeoutStep
from, to := copyMem(from, to, 8) // pc
from, to := copyMem(from, to, 8) // nextPC
from, to := copyMem(from, to, 8) // lo
......@@ -955,9 +872,6 @@ contract MIPS64 is ISemver {
c, m := putField(c, m, 8) // threadID
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, 4) // futexVal
c, m := putField(c, m, 8) // futexTimeoutStep
c, m := putField(c, m, 8) // pc
c, m := putField(c, m, 8) // nextPC
c, m := putField(c, m, 8) // lo
......
......@@ -119,9 +119,6 @@ library MIPS64Syscalls {
uint64 internal constant FUTEX_WAIT_PRIVATE = 128;
uint64 internal constant FUTEX_WAKE_PRIVATE = 129;
uint64 internal constant FUTEX_TIMEOUT_STEPS = 10000;
uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max;
uint64 internal constant FUTEX_EMPTY_ADDR = U64_MASK;
uint64 internal constant SCHED_QUANTUM = 100_000;
uint64 internal constant HZ = 10_000_000;
......@@ -168,7 +165,6 @@ library MIPS64Syscalls {
uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0;
uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1;
uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2;
uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3;
// Constants copied from MIPS64Arch for use in Yul
uint64 internal constant WORD_SIZE_BYTES = 8;
......@@ -180,11 +176,10 @@ library MIPS64Syscalls {
/// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second argument available to the syscall operation.
/// @return a2_ The third argument available to the syscall operation.
/// @return a3_ The fourth argument available to the syscall operation.
function getSyscallArgs(uint64[32] memory _registers)
internal
pure
returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_, uint64 a3_)
returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_)
{
unchecked {
sysCallNum_ = _registers[REG_SYSCALL_NUM];
......@@ -192,9 +187,8 @@ library MIPS64Syscalls {
a0_ = _registers[REG_SYSCALL_PARAM1];
a1_ = _registers[REG_SYSCALL_PARAM2];
a2_ = _registers[REG_SYSCALL_PARAM3];
a3_ = _registers[REG_SYSCALL_PARAM4];
return (sysCallNum_, a0_, a1_, a2_, a3_);
return (sysCallNum_, a0_, a1_, a2_);
}
}
......
......@@ -115,9 +115,6 @@ library MIPSSyscalls {
uint32 internal constant FUTEX_WAIT_PRIVATE = 128;
uint32 internal constant FUTEX_WAKE_PRIVATE = 129;
uint32 internal constant FUTEX_TIMEOUT_STEPS = 10000;
uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max;
uint32 internal constant FUTEX_EMPTY_ADDR = 0xFF_FF_FF_FF;
uint32 internal constant SCHED_QUANTUM = 100_000;
uint32 internal constant HZ = 10_000_000;
......@@ -165,7 +162,6 @@ library MIPSSyscalls {
uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0;
uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1;
uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2;
uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3;
/// @notice Extract syscall num and arguments from registers.
/// @param _registers The cpu registers.
......@@ -173,11 +169,10 @@ library MIPSSyscalls {
/// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second argument available to the syscall operation.
/// @return a2_ The third argument available to the syscall operation.
/// @return a3_ The fourth argument available to the syscall operation.
function getSyscallArgs(uint32[32] memory _registers)
internal
pure
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_, uint32 a3_)
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_)
{
unchecked {
sysCallNum_ = _registers[REG_SYSCALL_NUM];
......@@ -185,9 +180,8 @@ library MIPSSyscalls {
a0_ = _registers[REG_SYSCALL_PARAM1];
a1_ = _registers[REG_SYSCALL_PARAM2];
a2_ = _registers[REG_SYSCALL_PARAM3];
a3_ = _registers[REG_SYSCALL_PARAM4];
return (sysCallNum_, a0_, a1_, a2_, a3_);
return (sysCallNum_, a0_, a1_, a2_);
}
}
......
......@@ -157,13 +157,13 @@ contract MIPS2_Test is CommonTest {
/// @notice Used to debug step() behavior given a specific input.
/// This is useful to more easily debug non-forge tests.
/// For example, in cannon/mipsevm/evm_test.go step input can be pulled here:
/// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80
/// For example, in cannon/mipsevm/testutil/mips.go step input can be pulled here:
/// https://github.com/ethereum-optimism/optimism/blob/efcaa2ded4ee4d6c76331314ab2da0366972aa0a/cannon/mipsevm/testutil/mips.go#L104-L104
function test_step_debug_succeeds() external {
bytes memory input =
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acab5a39c6f974b22302e96dcdef1815483eaf580639bb1ee7ac98267afac2bf1ac041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81fa7c3d90b000000000000000000000078fc2ffac2fd940100000000000080c8ffffffff006504aeffb6e08baf3f85da5476a9160fa8f9f188a722fdd29268b0cbaf596736ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000f1f85ff4f1f85ff8506d442dbb3938f83eb60825a7ecbff2000010185e1a31f600050f0000000064a7c3d90be5acea102ad7bda149e0bfd0e7111c77d98b335645e665389becadf140ef999cc64fbd7f04799e85c97dadc5cca510bd5b3d97166d1aec28829f3dd43d8cf1f9358e4103b16d09d466e2c7c048ea3ba1aef3141e700270581aa0b75b50e34fc926bb2d83ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a8b2b243c8ff90766c3f413a78ce5dc5176b0aa029576b87025dbeaf6a54020af2c041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81f956bdecf000000000000000000000078fc2ffac2fd9401000000000000dfc800c39478fcda196ca0fced6b42ecb09452580e4b553f4ba3e23d60de73779e6d4a3d718f9eeedd979b6295aabc5adf08862b09cce94bb10865cc78cbd362c2fba7000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b360000000100000000000000000004a0b25df715ec361e43da99550d2e51b81e754db19d40d6d1ed7acb9d6cccd60534c6870936427cee207c51f09ea9c6dcdcbae4865e5f5e026159b4819f295228e910f1366e21ba060337c8018933ad52e51feb4a82d0cc8d289256f48dcf2f1a7806a3958755a4cdf0c5a9dfbe8f1cbf5942f5e7928760894b15d3c5c5a01b075b5c96d4526829d9f5c9a3af93f534a428a7f4b89f96be325b55f40dd7a26b5d441a4101a75595a30a000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
(bool success, bytes memory retVal) = address(mips).call(input);
bytes memory expectedRetVal = hex"0335fe4205f8443eefa7ac4541197874224df35e8536158c2fc2d5c8c2d2adb4";
bytes memory expectedRetVal = hex"0334289a94004544cab7d6b90238581ca3e082e097d90deb6251da612df2530f";
assertTrue(success);
assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned");
......@@ -179,9 +179,6 @@ contract MIPS2_Test is CommonTest {
threadID: 0,
exitCode: 0,
exited: false,
futexAddr: sys.FUTEX_EMPTY_ADDR,
futexVal: 0,
futexTimeoutStep: 0,
pc: 4,
nextPC: 8,
lo: 0,
......@@ -204,7 +201,6 @@ contract MIPS2_Test is CommonTest {
exited: false,
step: 1,
stepsSinceLastContextSwitch: 1,
wakeup: sys.FUTEX_EMPTY_ADDR,
traverseRight: false,
leftThreadStack: threadRoot,
rightThreadStack: EMPTY_THREAD_ROOT,
......@@ -349,9 +345,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory newThread = copyThread(thread);
newThread.threadID = 1;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4;
newThread.registers[2] = 0;
......@@ -400,44 +393,17 @@ contract MIPS2_Test is CommonTest {
/// @dev Static unit test asserting successful futex wait syscall behavior with a timeout argument
function test_syscallFutexWaitTimeout_succeeds() public {
uint32 futexAddr = 0x1000;
uint32 futexVal = 0xAA_AA_AA_AA;
uint32 timeout = 1;
uint32 insn = 0x0000000c; // syscall
(IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) =
constructMIPSState(0, insn, futexAddr, futexVal);
thread.registers[2] = sys.SYS_FUTEX;
thread.registers[A0_REG] = futexAddr;
thread.registers[A1_REG] = sys.FUTEX_WAIT_PRIVATE;
thread.registers[A2_REG] = futexVal;
thread.registers[A3_REG] = timeout;
threading.createThread();
threading.replaceCurrent(thread);
bytes memory threadWitness = threading.witness();
finalizeThreadingState(threading, state);
// FUTEX_WAIT
IMIPS2.ThreadState memory expectThread = copyThread(thread);
expectThread.futexAddr = futexAddr;
expectThread.futexVal = futexVal;
expectThread.futexTimeoutStep = state.step + 1 + sys.FUTEX_TIMEOUT_STEPS;
threading.replaceCurrent(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1;
finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
syscallFutexWaitTest(1);
}
/// @dev Static unit test asserting successful futex wait syscall behavior with a zero timeout argument
function test_syscallFutexWaitNoTimeout_succeeds() public {
syscallFutexWaitTest(0);
}
function syscallFutexWaitTest(uint32 timeout) private {
uint32 futexAddr = 0x1000;
uint32 futexVal = 0xAA_AA_AA_AA;
uint32 timeout = 0;
uint32 insn = 0x0000000c; // syscall
(IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) =
......@@ -452,16 +418,20 @@ contract MIPS2_Test is CommonTest {
bytes memory threadWitness = threading.witness();
finalizeThreadingState(threading, state);
// FUTEX_WAIT
// FUTEX_WAIT should return empty values and preempt thread
IMIPS2.ThreadState memory expectThread = copyThread(thread);
expectThread.futexAddr = futexAddr;
expectThread.futexVal = futexVal;
expectThread.futexTimeoutStep = sys.FUTEX_NO_TIMEOUT;
threading.replaceCurrent(expectThread);
expectThread.registers[2] = 0;
expectThread.registers[7] = 0;
expectThread.pc = thread.nextPC;
expectThread.nextPC = thread.nextPC + 4;
// Preempt thread
threading.left().pop();
threading.right().push(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1;
expect.stepsSinceLastContextSwitch = 0;
expect.traverseRight = true;
finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
......@@ -490,7 +460,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory expectThread = copyThread(thread);
expectThread.pc = thread.nextPC;
expectThread.nextPC = thread.nextPC + 4;
expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
expectThread.registers[2] = sys.SYS_ERROR_SIGNAL;
expectThread.registers[7] = sys.EAGAIN; // errno
threading.replaceCurrent(expectThread);
......@@ -530,7 +499,6 @@ contract MIPS2_Test is CommonTest {
threading.right().push(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.wakeup = futexAddr;
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0;
expect.traverseRight = true;
......@@ -546,13 +514,11 @@ contract MIPS2_Test is CommonTest {
uint8 exitCode = 4;
IMIPS2.ThreadState memory threadA = threading.createThread();
threadA.futexAddr = sys.FUTEX_EMPTY_ADDR;
threadA.pc = 0x1000;
threadA.nextPC = 0x1004;
threading.replaceCurrent(threadA);
IMIPS2.ThreadState memory threadB = threading.createThread();
threadB.futexAddr = sys.FUTEX_EMPTY_ADDR;
threadB.pc = 0x100;
threadB.nextPC = 0x104;
threadB.registers[2] = sys.SYS_EXIT;
......@@ -565,7 +531,6 @@ contract MIPS2_Test is CommonTest {
(state.memRoot, memProof) = ffi.getCannonMemoryProof(threadB.pc, insn, 0, 0);
state.step = 20;
state.stepsSinceLastContextSwitch = 10;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, state);
// state updates
......@@ -588,7 +553,6 @@ contract MIPS2_Test is CommonTest {
uint8 exitCode = 4;
IMIPS2.ThreadState memory thread = threading.createThread();
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
thread.pc = 0x1000;
thread.nextPC = 0x1004;
thread.registers[2] = sys.SYS_EXIT;
......@@ -601,7 +565,6 @@ contract MIPS2_Test is CommonTest {
(state.memRoot, memProof) = ffi.getCannonMemoryProof(thread.pc, insn, 0, 0);
state.step = 20;
state.stepsSinceLastContextSwitch = 10;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, state);
// state updates
......@@ -830,13 +793,10 @@ contract MIPS2_Test is CommonTest {
function test_threadQuantumSchedule_succeeds() public {
IMIPS2.ThreadState memory threadA = threading.createThread();
threadA.threadID = 0;
threadA.futexAddr = sys.FUTEX_EMPTY_ADDR;
threading.replaceCurrent(threadA);
IMIPS2.ThreadState memory threadB = threading.createThread();
threadB.futexAddr = sys.FUTEX_EMPTY_ADDR;
threading.replaceCurrent(threadB);
IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = sys.SCHED_QUANTUM;
finalizeThreadingState(threading, state);
bytes memory threadWitness = threading.witness();
......@@ -855,10 +815,9 @@ contract MIPS2_Test is CommonTest {
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev Static unit test asserting thread left traversal without wakeups
/// @dev Static unit test asserting thread left traversal
function test_threadTraverseLeft_succeeds() public {
IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.step = 10;
state.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, state);
......@@ -873,7 +832,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory thread = threading.createThread();
thread.pc = pc;
thread.nextPC = pc + 4;
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
thread.registers[2] = sys.SYS_NANOSLEEP;
threading.replaceCurrent(thread);
}
......@@ -905,12 +863,11 @@ contract MIPS2_Test is CommonTest {
}
}
/// @dev Static unit test asserting thread right traversal without wakeups
/// @dev Static unit test asserting thread right traversal
function test_threadTraverseRight_succeeds() public {
threading.setTraverseRight(true);
IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.step = 10;
state.stepsSinceLastContextSwitch = 0;
state.traverseRight = true;
......@@ -926,7 +883,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory thread = threading.createThread();
thread.pc = pc;
thread.nextPC = pc + 4;
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
thread.registers[2] = sys.SYS_NANOSLEEP;
threading.replaceCurrent(thread);
}
......@@ -957,334 +913,6 @@ contract MIPS2_Test is CommonTest {
}
}
/// @dev static unit test asserting state transition of a spurious wakeup
function test_wakeupPreemptsThread_succeeds() public {
threading.createThread();
threading.createThread();
IMIPS2.ThreadState memory threadB = threading.current();
threadB.futexAddr = 0xdead;
threading.replaceCurrent(threadB);
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
state.wakeup = 0xabba;
finalizeThreadingState(threading, state);
// Preempt the current thread on spurious wakeup
threading.left().pop();
threading.right().push(threadB);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, expect);
bytes memory memProof;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev Static unit test asserting successful wakeup traversal when no threads are ready to wake
function test_threadWakeupFullTraversalNoWakeup_succeeds() public {
IMIPS2.State memory state;
state.wakeup = 0x1000;
state.step = 10;
state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state);
// Create a few threads that are not waiting to wake
for (uint256 i = 0; i < 3; i++) {
IMIPS2.ThreadState memory thread = threading.createThread();
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
threading.replaceCurrent(thread);
}
finalizeThreadingState(threading, state);
// Traverse left
for (uint256 i = 0; i < 3; i++) {
IMIPS2.ThreadState memory currentThread = threading.current();
bytes memory memProof;
(state.memRoot, memProof) = ffi.getCannonMemoryProof(currentThread.pc, 0);
bytes memory threadWitness = threading.witness();
// We should preempt the current thread
threading.left().pop();
threading.right().push(currentThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, expect);
expect.traverseRight = i == 2;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
state = expect;
}
// Traverse right
threading.setTraverseRight(true);
for (uint256 i = 0; i < 3; i++) {
IMIPS2.ThreadState memory currentThread = threading.current();
bytes memory memProof;
(state.memRoot, memProof) = ffi.getCannonMemoryProof(currentThread.pc, 0);
bytes memory threadWitness = threading.witness();
// We should preempt the current thread
threading.right().pop();
threading.left().push(currentThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, expect);
expect.traverseRight = true;
if (i == 2) {
// When we reach the last thread, we should clear the wakeup and resume normal execution
expect.traverseRight = false;
expect.wakeup = sys.FUTEX_EMPTY_ADDR;
}
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
state = expect;
}
}
/// @dev static unit test asserting state transition of an empty right thread stack
/// This occurs during wakeup traversal
function test_wakeup_traversalEnds_succeeds() public {
threading.setTraverseRight(true);
IMIPS2.ThreadState memory thread = threading.createThread();
IMIPS2.State memory state;
state.traverseRight = true;
state.wakeup = 0x1000;
state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state);
bytes memory threadWitness = threading.witness();
// state changes
threading.right().pop();
threading.left().push(thread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
// Note that this does not change. The next thread scheduled (on the left stack) was the last thread on the
// right stack.
expect.stepsSinceLastContextSwitch = 0;
expect.wakeup = sys.FUTEX_EMPTY_ADDR;
expect.traverseRight = false;
finalizeThreadingState(threading, expect);
bytes memory memProof; // unused
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev static unit test asserting state transition of completion of a timed-out futex waiter
function test_futexTimeoutCompletion_succeeds() public {
threading.createThread();
threading.createThread();
IMIPS2.ThreadState memory threadB = threading.current();
threadB.futexAddr = 0x1000;
threadB.futexVal = 0xdead;
threadB.futexTimeoutStep = 10;
threading.replaceCurrent(threadB);
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.step = 10;
state.stepsSinceLastContextSwitch = 10; // must be unchanged
finalizeThreadingState(threading, state);
// Resume the current blocked thread on futex timeout
IMIPS2.ThreadState memory expectThread = copyThread(threadB);
expectThread.pc = threadB.nextPC;
expectThread.nextPC = threadB.nextPC + 4;
expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
expectThread.futexVal = 0x0;
expectThread.futexTimeoutStep = 0;
expectThread.registers[2] = sys.SYS_ERROR_SIGNAL;
expectThread.registers[7] = sys.ETIMEDOUT;
threading.replaceCurrent(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, expect);
bytes memory memProof;
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev Static unit test asserting wakeup where the current thread is ready to be woken up
function testFuzz_wakeupWaiter_succeeds(
uint32 _wakeup,
uint32 _futexVal,
uint32 _futexTimeoutStep,
bool _threadExited
)
public
{
_wakeup = uint32(_bound(_wakeup, 0, sys.FUTEX_EMPTY_ADDR - 1));
threading.createThread();
threading.createThread();
IMIPS2.ThreadState memory threadB = threading.current();
threadB.futexAddr = _wakeup;
threadB.futexVal = _futexVal;
threadB.futexTimeoutStep = _futexTimeoutStep;
// A thread exit cannot interrupt wakeup traversal. thread.exited during wakeup is technically not a valid
// state.
// But we fuzz this anyways to ensure the VM only traverses threads during wakeup
threadB.exited = _threadExited;
threadB.exitCode = _threadExited ? 1 : 0;
threading.replaceCurrent(threadB);
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
bytes memory memProof; // unused
state.wakeup = _wakeup;
state.step = 10;
state.stepsSinceLastContextSwitch = 20; // must be unchanged
finalizeThreadingState(threading, state);
// Resume the current thread that is blocked
IMIPS2.ThreadState memory expectThread = copyThread(threadB);
// no changes on thread since we're in wakeup traversal
threading.replaceCurrent(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev Static unit test asserting wakeup where the current thread isn't ready
function testFuzz_wakeupNonWaiter_succeeds(
uint32 _wakeup,
uint32 _futexAddr,
uint32 _futexVal,
uint32 _futexTimeoutStep,
bool _threadExited
)
public
{
// -2 incase _wakeup == _futexAddr and _wakeup needs to be incremented
_wakeup = uint32(_bound(_wakeup, 0, sys.FUTEX_EMPTY_ADDR - 2));
if (_wakeup == _futexAddr) _wakeup++;
threading.createThread();
threading.createThread();
IMIPS2.ThreadState memory threadB = threading.current();
threadB.futexAddr = _futexAddr;
threadB.futexVal = _futexVal;
threadB.futexTimeoutStep = _futexTimeoutStep;
threadB.exited = _threadExited;
threadB.exitCode = _threadExited ? 1 : 0;
threading.replaceCurrent(threadB);
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
bytes memory memProof; // unused
state.wakeup = _wakeup;
state.step = 10;
state.stepsSinceLastContextSwitch = 20;
finalizeThreadingState(threading, state);
// state changes
IMIPS2.ThreadState memory expectThread = copyThread(threadB);
// thread internal state is unchanged since we're in wakeup traversal
threading.replaceCurrent(expectThread);
threading.left().pop();
threading.right().push(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev static unit test asserting state transition of completion of a non-timed-out futex waiter
function test_futexNoTimeoutCompletion_succeeds() public {
threading.createThread();
threading.createThread();
IMIPS2.ThreadState memory threadB = threading.current();
threadB.futexAddr = 0x1000;
threadB.futexVal = 0xdead;
threadB.futexTimeoutStep = 100;
threading.replaceCurrent(threadB);
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
bytes memory memProof;
(state.memRoot, memProof) = ffi.getCannonMemoryProof(0, 0, threadB.futexAddr, threadB.futexVal + 1);
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.step = 10;
state.stepsSinceLastContextSwitch = 10; // must be unchanged
finalizeThreadingState(threading, state);
// Resume the current thread that is blocked
IMIPS2.ThreadState memory expectThread = copyThread(threadB);
expectThread.pc = threadB.nextPC;
expectThread.nextPC = threadB.nextPC + 4;
expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
expectThread.futexVal = 0x0;
expectThread.futexTimeoutStep = 0;
expectThread.registers[2] = 0;
expectThread.registers[7] = 0; // errno
threading.replaceCurrent(expectThread);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev static unit test asserting state transition of futex waiter that isn't ready
function test_futexNoTimeoutPreemptsThread_succeeds() public {
threading.createThread();
threading.createThread();
IMIPS2.ThreadState memory threadB = threading.current();
threadB.futexAddr = 0x1000;
threadB.futexVal = 0xdead;
threadB.futexTimeoutStep = sys.FUTEX_NO_TIMEOUT;
threading.replaceCurrent(threadB);
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
bytes memory memProof;
(state.memRoot, memProof) = ffi.getCannonMemoryProof(0, 0, threadB.futexAddr, threadB.futexVal);
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state);
// Expect the thread to be moved from the left to right stack
threading.left().pop();
threading.right().push(threadB);
IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
assertEq(postState, outputState(expect), "unexpected post state");
}
/// @dev Static unit test asserting VM behavior when the current thread has exited
function test_threadExit_succeeds() public {
threading.createThread();
......@@ -1295,7 +923,6 @@ contract MIPS2_Test is CommonTest {
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state);
......@@ -1323,7 +950,6 @@ contract MIPS2_Test is CommonTest {
bytes memory threadWitness = threading.witness();
IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state);
......@@ -2708,10 +2334,8 @@ contract MIPS2_Test is CommonTest {
{
(state_.memRoot, proof_) = ffi.getCannonMemoryProof(pc, insn, addr, val);
state_.nextThreadID = 1;
state_.wakeup = sys.FUTEX_EMPTY_ADDR;
thread_.pc = pc;
thread_.nextPC = pc + 4;
thread_.futexAddr = sys.FUTEX_EMPTY_ADDR;
state_.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(thread_))));
state_.rightThreadStack = EMPTY_THREAD_ROOT;
}
......@@ -2779,9 +2403,8 @@ contract MIPS2_Test is CommonTest {
bytes memory b = abi.encodePacked(
_state.llOwnerThread, _state.exitCode, _state.exited, _state.step, _state.stepsSinceLastContextSwitch
);
bytes memory c = abi.encodePacked(
_state.wakeup, _state.traverseRight, _state.leftThreadStack, _state.rightThreadStack, _state.nextThreadID
);
bytes memory c =
abi.encodePacked(_state.traverseRight, _state.leftThreadStack, _state.rightThreadStack, _state.nextThreadID);
return abi.encodePacked(a, b, c);
}
......@@ -2846,9 +2469,6 @@ function encodeThread(IMIPS2.ThreadState memory _thread) pure returns (bytes mem
_thread.threadID,
_thread.exitCode,
_thread.exited,
_thread.futexAddr,
_thread.futexVal,
_thread.futexTimeoutStep,
_thread.pc,
_thread.nextPC,
_thread.lo,
......
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