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 ...@@ -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 mv op-program/bin/0.json op-program/bin/prestate-proof.json
.PHONY: cannon-prestate .PHONY: cannon-prestate
cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the multithreaded64-2 cannon format 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-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/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 "" ./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 mv op-program/bin/0-mt.json op-program/bin/prestate-proof-mt.json
.PHONY: cannon-prestate-mt .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-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-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/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 "" ./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 mv op-program/bin/0-interop.json op-program/bin/prestate-proof-interop.json
.PHONY: cannon-prestate-interop .PHONY: cannon-prestate-interop
......
...@@ -31,13 +31,14 @@ cannon64-impl: ...@@ -31,13 +31,14 @@ cannon64-impl:
# It should build the individual versions of cannons and copy them into place in hte multicannon/embeds directory # 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 # 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. # 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 cannon-embeds: cannon32-impl cannon64-impl
# singlethreaded-v2 # 32-bit singlethreaded vm
@cp bin/cannon32-impl ./multicannon/embeds/cannon-2 @cp bin/cannon32-impl ./multicannon/embeds/cannon-2
# multithreaded # 32-bit multithreaded vm
@cp bin/cannon32-impl ./multicannon/embeds/cannon-1 @cp bin/cannon32-impl ./multicannon/embeds/cannon-5
# 64-bit multithreaded v2 # 64-bit multithreaded vm
@cp bin/cannon64-impl ./multicannon/embeds/cannon-4 @cp bin/cannon64-impl ./multicannon/embeds/cannon-6
cannon: cannon-embeds cannon: cannon-embeds
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/ 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 { ...@@ -80,7 +80,7 @@ func LoadELF(ctx *cli.Context) error {
} }
return program.PatchStack(state) 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) { createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, multithreaded.CreateInitialState) return program.LoadELF(f, multithreaded.CreateInitialState)
} }
......
...@@ -14,6 +14,5 @@ type DebugInfo struct { ...@@ -14,6 +14,5 @@ type DebugInfo struct {
MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"` MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"`
ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"` ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"`
ForcedPreemptionCount uint64 `json:"forced_preemption_count"` ForcedPreemptionCount uint64 `json:"forced_preemption_count"`
FailedWakeupCount uint64 `json:"failed_wakeup_count"`
IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"` IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"`
} }
...@@ -23,7 +23,6 @@ func TestDebugInfo_Serialization(t *testing.T) { ...@@ -23,7 +23,6 @@ func TestDebugInfo_Serialization(t *testing.T) {
MaxStepsBetweenLLAndSC: 7, MaxStepsBetweenLLAndSC: 7,
ReservationInvalidationCount: 8, ReservationInvalidationCount: 8,
ForcedPreemptionCount: 9, ForcedPreemptionCount: 9,
FailedWakeupCount: 10,
IdleStepCountThread0: math.MaxUint64, IdleStepCountThread0: math.MaxUint64,
} }
......
...@@ -42,11 +42,8 @@ const ( ...@@ -42,11 +42,8 @@ const (
// SysFutex-related constants // SysFutex-related constants
const ( const (
FutexWaitPrivate = 128 FutexWaitPrivate = 128
FutexWakePrivate = 129 FutexWakePrivate = 129
FutexTimeoutSteps = 10_000
FutexNoTimeout = ^uint64(0)
FutexEmptyAddr = ^Word(0)
) )
// SysClone flags // SysClone flags
...@@ -99,15 +96,14 @@ const ( ...@@ -99,15 +96,14 @@ const (
ClockGettimeMonotonicFlag = 1 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 syscallNum = registers[register.RegSyscallNum] // v0
a0 = registers[register.RegSyscallParam1] a0 = registers[register.RegSyscallParam1]
a1 = registers[register.RegSyscallParam2] a1 = registers[register.RegSyscallParam2]
a2 = registers[register.RegSyscallParam3] 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) { func HandleSysMmap(a0, a1, heap Word) (v0, v1, newHeap Word) {
......
...@@ -20,7 +20,7 @@ type Word = arch.Word ...@@ -20,7 +20,7 @@ type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error { func (m *InstrumentedState) handleSyscall() error {
thread := m.state.GetCurrentThread() 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) v0 := Word(0)
v1 := Word(0) v1 := Word(0)
...@@ -43,12 +43,9 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -43,12 +43,9 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = m.state.NextThreadId v0 = m.state.NextThreadId
v1 = 0 v1 = 0
newThread := &ThreadState{ newThread := &ThreadState{
ThreadId: m.state.NextThreadId, ThreadId: m.state.NextThreadId,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
FutexAddr: exec.FutexEmptyAddr,
FutexVal: 0,
FutexTimeoutStep: 0,
Cpu: mipsevm.CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: thread.Cpu.NextPC, PC: thread.Cpu.NextPC,
NextPC: thread.Cpu.NextPC + 4, NextPC: thread.Cpu.NextPC + 4,
...@@ -119,37 +116,18 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -119,37 +116,18 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN v1 = exec.MipsEAGAIN
} else { } else {
thread.FutexAddr = effFutexAddr m.syscallYield(thread)
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`
return nil return nil
} }
case exec.FutexWakePrivate: case exec.FutexWakePrivate:
// Trigger a wakeup traversal m.syscallYield(thread)
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()
return nil return nil
default: default:
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL v1 = exec.MipsEINVAL
} }
case arch.SysSchedYield, arch.SysNanosleep: case arch.SysSchedYield, arch.SysNanosleep:
v0 = 0 m.syscallYield(thread)
v1 = 0
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
return nil return nil
case arch.SysOpen: case arch.SysOpen:
v0 = exec.SysErrorSignal v0 = exec.SysErrorSignal
...@@ -225,6 +203,13 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -225,6 +203,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil 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 { func (m *InstrumentedState) mipsStep() error {
err := m.doMipsStep() err := m.doMipsStep()
if err != nil { if err != nil {
...@@ -249,56 +234,12 @@ func (m *InstrumentedState) doMipsStep() error { ...@@ -249,56 +234,12 @@ func (m *InstrumentedState) doMipsStep() error {
m.state.Step += 1 m.state.Step += 1
thread := m.state.GetCurrentThread() 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 { if thread.Exited {
m.popThread() m.popThread()
m.stackTracker.DropThread(thread.ThreadId) m.stackTracker.DropThread(thread.ThreadId)
return nil 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 { if m.state.StepsSinceLastContextSwitch >= exec.SchedQuantum {
// Force a context switch as this thread has been active too long // Force a context switch as this thread has been active too long
if m.state.ThreadCount() > 1 { if m.state.ThreadCount() > 1 {
...@@ -412,24 +353,6 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error { ...@@ -412,24 +353,6 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
return exec.HandleRd(m.state.getCpuRef(), m.state.GetRegistersRef(), rtReg, retVal, true) 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 { func (m *InstrumentedState) preemptThread(thread *ThreadState) bool {
// Pop thread from the current stack and push to the other stack // Pop thread from the current stack and push to the other stack
if m.state.TraverseRight { if m.state.TraverseRight {
......
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/op-service/serialize" "github.com/ethereum-optimism/optimism/op-service/serialize"
) )
...@@ -30,13 +29,12 @@ const ( ...@@ -30,13 +29,12 @@ const (
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1 EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1 STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8 STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
WAKEUP_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8 TRAVERSE_RIGHT_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + arch.WordSizeBytes
LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1 LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32 RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32
THREAD_ID_WITNESS_OFFSET = RIGHT_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 STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes
) )
...@@ -64,7 +62,6 @@ type State struct { ...@@ -64,7 +62,6 @@ type State struct {
Step uint64 Step uint64
StepsSinceLastContextSwitch uint64 StepsSinceLastContextSwitch uint64
Wakeup Word
TraverseRight bool TraverseRight bool
LeftThreadStack []*ThreadState LeftThreadStack []*ThreadState
...@@ -89,7 +86,6 @@ func CreateEmptyState() *State { ...@@ -89,7 +86,6 @@ func CreateEmptyState() *State {
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
Step: 0, Step: 0,
Wakeup: exec.FutexEmptyAddr,
TraverseRight: false, TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread}, LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{}, RightThreadStack: []*ThreadState{},
...@@ -215,7 +211,6 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) { ...@@ -215,7 +211,6 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = binary.BigEndian.AppendUint64(out, s.Step) out = binary.BigEndian.AppendUint64(out, s.Step)
out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch) out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch)
out = arch.ByteOrderWord.AppendWord(out, s.Wakeup)
leftStackRoot := s.getLeftThreadStackRoot() leftStackRoot := s.getLeftThreadStackRoot()
rightStackRoot := s.getRightThreadStackRoot() rightStackRoot := s.getRightThreadStackRoot()
...@@ -262,7 +257,6 @@ func (s *State) ThreadCount() int { ...@@ -262,7 +257,6 @@ func (s *State) ThreadCount() int {
// Exited uint8 - 0 for false, 1 for true // Exited uint8 - 0 for false, 1 for true
// Step uint64 // Step uint64
// StepsSinceLastContextSwitch uint64 // StepsSinceLastContextSwitch uint64
// Wakeup Word
// TraverseRight uint8 - 0 for false, 1 for true // TraverseRight uint8 - 0 for false, 1 for true
// NextThreadId Word // NextThreadId Word
// len(LeftThreadStack) Word // len(LeftThreadStack) Word
...@@ -307,9 +301,6 @@ func (s *State) Serialize(out io.Writer) error { ...@@ -307,9 +301,6 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.StepsSinceLastContextSwitch); err != nil { if err := bout.WriteUInt(s.StepsSinceLastContextSwitch); err != nil {
return err return err
} }
if err := bout.WriteUInt(s.Wakeup); err != nil {
return err
}
if err := bout.WriteBool(s.TraverseRight); err != nil { if err := bout.WriteBool(s.TraverseRight); err != nil {
return err return err
} }
...@@ -376,9 +367,6 @@ func (s *State) Deserialize(in io.Reader) error { ...@@ -376,9 +367,6 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.StepsSinceLastContextSwitch); err != nil { if err := bin.ReadUInt(&s.StepsSinceLastContextSwitch); err != nil {
return err return err
} }
if err := bin.ReadUInt(&s.Wakeup); err != nil {
return err
}
if err := bin.ReadBool(&s.TraverseRight); err != nil { if err := bin.ReadBool(&s.TraverseRight); err != nil {
return err return err
} }
......
...@@ -14,7 +14,6 @@ import ( ...@@ -14,7 +14,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory" "github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program" "github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
) )
...@@ -85,7 +84,6 @@ func TestState_EncodeWitness(t *testing.T) { ...@@ -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, 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)}) 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, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:]) setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:]) setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:])
...@@ -132,7 +130,6 @@ func TestState_JSONCodec(t *testing.T) { ...@@ -132,7 +130,6 @@ func TestState_JSONCodec(t *testing.T) {
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot()) require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Step, newState.Step) require.Equal(t, state.Step, newState.Step)
require.Equal(t, state.StepsSinceLastContextSwitch, newState.StepsSinceLastContextSwitch) 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.TraverseRight, newState.TraverseRight)
require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack) require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack)
require.Equal(t, state.RightThreadStack, newState.RightThreadStack) require.Equal(t, state.RightThreadStack, newState.RightThreadStack)
...@@ -170,7 +167,6 @@ func TestState_Binary(t *testing.T) { ...@@ -170,7 +167,6 @@ func TestState_Binary(t *testing.T) {
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot()) require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Step, newState.Step) require.Equal(t, state.Step, newState.Step)
require.Equal(t, state.StepsSinceLastContextSwitch, newState.StepsSinceLastContextSwitch) 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.TraverseRight, newState.TraverseRight)
require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack) require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack)
require.Equal(t, state.RightThreadStack, newState.RightThreadStack) require.Equal(t, state.RightThreadStack, newState.RightThreadStack)
...@@ -196,16 +192,12 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -196,16 +192,12 @@ func TestSerializeStateRoundTrip(t *testing.T) {
Exited: true, Exited: true,
Step: 0xdeadbeef, Step: 0xdeadbeef,
StepsSinceLastContextSwitch: 334, StepsSinceLastContextSwitch: 334,
Wakeup: 42,
TraverseRight: true, TraverseRight: true,
LeftThreadStack: []*ThreadState{ LeftThreadStack: []*ThreadState{
{ {
ThreadId: 45, ThreadId: 45,
ExitCode: 46, ExitCode: 46,
Exited: true, Exited: true,
FutexAddr: 47,
FutexVal: 48,
FutexTimeoutStep: 49,
Cpu: mipsevm.CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: 0xFF, PC: 0xFF,
NextPC: 0xFF + 4, NextPC: 0xFF + 4,
...@@ -223,12 +215,9 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -223,12 +215,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
}, },
}, },
{ {
ThreadId: 55, ThreadId: 55,
ExitCode: 56, ExitCode: 56,
Exited: false, Exited: false,
FutexAddr: 57,
FutexVal: 58,
FutexTimeoutStep: 59,
Cpu: mipsevm.CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: 0xEE, PC: 0xEE,
NextPC: 0xEE + 4, NextPC: 0xEE + 4,
...@@ -243,12 +232,9 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -243,12 +232,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
}, },
RightThreadStack: []*ThreadState{ RightThreadStack: []*ThreadState{
{ {
ThreadId: 65, ThreadId: 65,
ExitCode: 66, ExitCode: 66,
Exited: false, Exited: false,
FutexAddr: 67,
FutexVal: 68,
FutexTimeoutStep: 69,
Cpu: mipsevm.CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: 0xdd, PC: 0xdd,
NextPC: 0xdd + 4, NextPC: 0xdd + 4,
...@@ -260,12 +246,9 @@ func TestSerializeStateRoundTrip(t *testing.T) { ...@@ -260,12 +246,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
}, },
}, },
{ {
ThreadId: 75, ThreadId: 75,
ExitCode: 76, ExitCode: 76,
Exited: true, Exited: true,
FutexAddr: 77,
FutexVal: 78,
FutexTimeoutStep: 79,
Cpu: mipsevm.CpuScalars{ Cpu: mipsevm.CpuScalars{
PC: 0xcc, PC: 0xcc,
NextPC: 0xcc + 4, NextPC: 0xcc + 4,
...@@ -360,20 +343,16 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) { ...@@ -360,20 +343,16 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) {
func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) { func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
wakeupAddr Word
traverseRight bool traverseRight bool
}{ }{
{"traverse left during wakeup traversal", Word(99), false}, {"traverse left", false},
{"traverse left during normal traversal", exec.FutexEmptyAddr, false}, {"traverse right", true},
{"traverse right during wakeup traversal", Word(99), true},
{"traverse right during normal traversal", exec.FutexEmptyAddr, true},
} }
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
// Set up invalid state where the active stack is empty // Set up invalid state where the active stack is empty
state := CreateEmptyState() state := CreateEmptyState()
state.Wakeup = c.wakeupAddr
state.TraverseRight = c.traverseRight state.TraverseRight = c.traverseRight
if c.traverseRight { if c.traverseRight {
state.LeftThreadStack = []*ThreadState{CreateEmptyThread()} state.LeftThreadStack = []*ThreadState{CreateEmptyThread()}
...@@ -389,17 +368,17 @@ func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) { ...@@ -389,17 +368,17 @@ func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
} }
func TestStateWitnessSize(t *testing.T) { func TestStateWitnessSize(t *testing.T) {
expectedWitnessSize := 172 expectedWitnessSize := 168
if !arch.IsMips32 { if !arch.IsMips32 {
expectedWitnessSize = 196 expectedWitnessSize = 188
} }
require.Equal(t, expectedWitnessSize, STATE_WITNESS_SIZE) require.Equal(t, expectedWitnessSize, STATE_WITNESS_SIZE)
} }
func TestThreadStateWitnessSize(t *testing.T) { func TestThreadStateWitnessSize(t *testing.T) {
expectedWitnessSize := 166 expectedWitnessSize := 150
if !arch.IsMips32 { if !arch.IsMips32 {
expectedWitnessSize = 318 expectedWitnessSize = 298
} }
require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE) require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE)
} }
...@@ -13,9 +13,6 @@ type StatsTracker interface { ...@@ -13,9 +13,6 @@ type StatsTracker interface {
trackSCFailure(threadId Word, step uint64) trackSCFailure(threadId Word, step uint64)
trackReservationInvalidation() trackReservationInvalidation()
trackForcedPreemption() trackForcedPreemption()
trackWakeupTraversalStart()
trackWakeup()
trackWakeupFail()
trackThreadActivated(tid Word, step uint64) trackThreadActivated(tid Word, step uint64)
populateDebugInfo(debugInfo *mipsevm.DebugInfo) populateDebugInfo(debugInfo *mipsevm.DebugInfo)
} }
...@@ -32,9 +29,6 @@ func (s *noopStatsTracker) trackSCSuccess(threadId Word, step uint64) {} ...@@ -32,9 +29,6 @@ func (s *noopStatsTracker) trackSCSuccess(threadId Word, step uint64) {}
func (s *noopStatsTracker) trackSCFailure(threadId Word, step uint64) {} func (s *noopStatsTracker) trackSCFailure(threadId Word, step uint64) {}
func (s *noopStatsTracker) trackReservationInvalidation() {} func (s *noopStatsTracker) trackReservationInvalidation() {}
func (s *noopStatsTracker) trackForcedPreemption() {} 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) trackThreadActivated(tid Word, step uint64) {}
func (s *noopStatsTracker) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {} func (s *noopStatsTracker) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {}
...@@ -44,7 +38,6 @@ var _ StatsTracker = (*noopStatsTracker)(nil) ...@@ -44,7 +38,6 @@ var _ StatsTracker = (*noopStatsTracker)(nil)
type statsTrackerImpl struct { type statsTrackerImpl struct {
// State // State
lastLLStepByThread *lru.LRU[Word, uint64] lastLLStepByThread *lru.LRU[Word, uint64]
isWakeupTraversal bool
activeThreadId Word activeThreadId Word
lastActiveStepThread0 uint64 lastActiveStepThread0 uint64
// Stats // Stats
...@@ -54,7 +47,6 @@ type statsTrackerImpl struct { ...@@ -54,7 +47,6 @@ type statsTrackerImpl struct {
// Tracks RMW reservation invalidation due to reserved memory being accessed outside of the RMW sequence // Tracks RMW reservation invalidation due to reserved memory being accessed outside of the RMW sequence
reservationInvalidationCount uint64 reservationInvalidationCount uint64
forcedPreemptionCount uint64 forcedPreemptionCount uint64
failedWakeupCount uint64
idleStepCountThread0 uint64 idleStepCountThread0 uint64
} }
...@@ -64,7 +56,6 @@ func (s *statsTrackerImpl) populateDebugInfo(debugInfo *mipsevm.DebugInfo) { ...@@ -64,7 +56,6 @@ func (s *statsTrackerImpl) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {
debugInfo.MaxStepsBetweenLLAndSC = s.maxStepsBetweenLLAndSC debugInfo.MaxStepsBetweenLLAndSC = s.maxStepsBetweenLLAndSC
debugInfo.ReservationInvalidationCount = s.reservationInvalidationCount debugInfo.ReservationInvalidationCount = s.reservationInvalidationCount
debugInfo.ForcedPreemptionCount = s.forcedPreemptionCount debugInfo.ForcedPreemptionCount = s.forcedPreemptionCount
debugInfo.FailedWakeupCount = s.failedWakeupCount
debugInfo.IdleStepCountThread0 = s.idleStepCountThread0 debugInfo.IdleStepCountThread0 = s.idleStepCountThread0
} }
...@@ -102,21 +93,6 @@ func (s *statsTrackerImpl) trackForcedPreemption() { ...@@ -102,21 +93,6 @@ func (s *statsTrackerImpl) trackForcedPreemption() {
s.forcedPreemptionCount += 1 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) { func (s *statsTrackerImpl) trackThreadActivated(tid Word, step uint64) {
if s.activeThreadId == Word(0) && tid != Word(0) { if s.activeThreadId == Word(0) && tid != Word(0) {
// Thread 0 has been deactivated, start tracking to capture idle steps // Thread 0 has been deactivated, start tracking to capture idle steps
......
...@@ -69,26 +69,6 @@ func TestStatsTracker(t *testing.T) { ...@@ -69,26 +69,6 @@ func TestStatsTracker(t *testing.T) {
operations: []Operation{forcePreempt(), forcePreempt()}, operations: []Operation{forcePreempt(), forcePreempt()},
expected: &mipsevm.DebugInfo{ForcedPreemptionCount: 2}, 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", name: "Preempt thread 0 for thread 0",
operations: []Operation{activateThread(0, 10), activateThread(0, 20), activateThread(0, 21)}, operations: []Operation{activateThread(0, 10), activateThread(0, 20), activateThread(0, 21)},
...@@ -148,24 +128,6 @@ func forcePreempt() Operation { ...@@ -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 { func activateThread(tid Word, step uint64) Operation {
return func(tracker StatsTracker) { return func(tracker StatsTracker) {
tracker.trackThreadActivated(tid, step) tracker.trackThreadActivated(tid, step)
......
...@@ -31,7 +31,6 @@ type ExpectedMTState struct { ...@@ -31,7 +31,6 @@ type ExpectedMTState struct {
expectedMemory *memory.Memory expectedMemory *memory.Memory
// Threading-related expectations // Threading-related expectations
StepsSinceLastContextSwitch uint64 StepsSinceLastContextSwitch uint64
Wakeup arch.Word
TraverseRight bool TraverseRight bool
NextThreadId arch.Word NextThreadId arch.Word
ThreadCount int ThreadCount int
...@@ -44,18 +43,15 @@ type ExpectedMTState struct { ...@@ -44,18 +43,15 @@ type ExpectedMTState struct {
} }
type ExpectedThreadState struct { type ExpectedThreadState struct {
ThreadId arch.Word ThreadId arch.Word
ExitCode uint8 ExitCode uint8
Exited bool Exited bool
FutexAddr arch.Word PC arch.Word
FutexVal uint32 NextPC arch.Word
FutexTimeoutStep uint64 HI arch.Word
PC arch.Word LO arch.Word
NextPC arch.Word Registers [32]arch.Word
HI arch.Word Dropped bool
LO arch.Word
Registers [32]arch.Word
Dropped bool
} }
func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
...@@ -81,7 +77,6 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { ...@@ -81,7 +77,6 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
MemoryRoot: fromState.GetMemory().MerkleRoot(), MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields // Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch, StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup,
TraverseRight: fromState.TraverseRight, TraverseRight: fromState.TraverseRight,
NextThreadId: fromState.NextThreadId, NextThreadId: fromState.NextThreadId,
ThreadCount: fromState.ThreadCount(), ThreadCount: fromState.ThreadCount(),
...@@ -98,18 +93,15 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState { ...@@ -98,18 +93,15 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
func newExpectedThreadState(fromThread *multithreaded.ThreadState) *ExpectedThreadState { func newExpectedThreadState(fromThread *multithreaded.ThreadState) *ExpectedThreadState {
return &ExpectedThreadState{ return &ExpectedThreadState{
ThreadId: fromThread.ThreadId, ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode, ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited, Exited: fromThread.Exited,
FutexAddr: fromThread.FutexAddr, PC: fromThread.Cpu.PC,
FutexVal: fromThread.FutexVal, NextPC: fromThread.Cpu.NextPC,
FutexTimeoutStep: fromThread.FutexTimeoutStep, HI: fromThread.Cpu.HI,
PC: fromThread.Cpu.PC, LO: fromThread.Cpu.LO,
NextPC: fromThread.Cpu.NextPC, Registers: fromThread.Registers,
HI: fromThread.Cpu.HI, Dropped: false,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
} }
} }
...@@ -193,7 +185,6 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade ...@@ -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) require.Equalf(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), "Expect memory root = %v", e.MemoryRoot)
// Thread-related global fields // Thread-related global fields
require.Equalf(t, e.StepsSinceLastContextSwitch, actualState.StepsSinceLastContextSwitch, "Expect StepsSinceLastContextSwitch = %v", e.StepsSinceLastContextSwitch) 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.TraverseRight, actualState.TraverseRight, "Expect TraverseRight = %v", e.TraverseRight)
require.Equalf(t, e.NextThreadId, actualState.NextThreadId, "Expect NextThreadId = %v", e.NextThreadId) 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) 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 ...@@ -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.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.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.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) { ...@@ -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: "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: "MemoryRoot", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.MemoryRoot = emptyHash }},
{name: "StepsSinceLastContextSwitch", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.StepsSinceLastContextSwitch += 1 }}, {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: "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: "NextThreadId", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.NextThreadId += 1 }},
{name: "ThreadCount", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ThreadCount += 1 }}, {name: "ThreadCount", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ThreadCount += 1 }},
...@@ -60,15 +59,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) { ...@@ -60,15 +59,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "Active thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { {name: "Active thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].Exited = !st.GetCurrentThread().Exited 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) { {name: "Active thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].PC += 1 e.threadExpectations[st.GetCurrentThread().ThreadId].PC += 1
}}, }},
...@@ -96,15 +86,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) { ...@@ -96,15 +86,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "Inactive thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) { {name: "Inactive thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].Exited = !FindNextThread(st).Exited 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) { {name: "Inactive thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].PC += 1 e.threadExpectations[FindNextThread(st).ThreadId].PC += 1
}}, }},
......
...@@ -9,21 +9,17 @@ import ( ...@@ -9,21 +9,17 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
) )
const ( const (
THREAD_ID_STATE_WITNESS_OFFSET = 0 THREAD_ID_STATE_WITNESS_OFFSET = 0
THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1 THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1
THREAD_FUTEX_ADDR_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1 THREAD_CPU_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1
THREAD_FUTEX_VAL_WITNESS_OFFSET = THREAD_FUTEX_ADDR_WITNESS_OFFSET + arch.WordSizeBytes THREAD_REGISTERS_WITNESS_OFFSET = THREAD_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET = THREAD_FUTEX_VAL_WITNESS_OFFSET + 4
THREAD_FUTEX_CPU_WITNESS_OFFSET = THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET + 8
THREAD_REGISTERS_WITNESS_OFFSET = THREAD_FUTEX_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object // 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) SERIALIZED_THREAD_SIZE = THREAD_REGISTERS_WITNESS_OFFSET + (32 * arch.WordSizeBytes)
// THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes. // THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes.
...@@ -37,14 +33,11 @@ const ( ...@@ -37,14 +33,11 @@ const (
var EmptyThreadsRoot common.Hash = common.HexToHash("0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5") var EmptyThreadsRoot common.Hash = common.HexToHash("0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5")
type ThreadState struct { type ThreadState struct {
ThreadId Word `json:"threadId"` ThreadId Word `json:"threadId"`
ExitCode uint8 `json:"exit"` ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"` Exited bool `json:"exited"`
FutexAddr Word `json:"futexAddr"` Cpu mipsevm.CpuScalars `json:"cpu"`
FutexVal uint32 `json:"futexVal"` Registers [32]Word `json:"registers"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"`
} }
func CreateEmptyThread() *ThreadState { func CreateEmptyThread() *ThreadState {
...@@ -59,10 +52,7 @@ func CreateEmptyThread() *ThreadState { ...@@ -59,10 +52,7 @@ func CreateEmptyThread() *ThreadState {
LO: 0, LO: 0,
HI: 0, HI: 0,
}, },
FutexAddr: exec.FutexEmptyAddr, Registers: [32]Word{},
FutexVal: 0,
FutexTimeoutStep: 0,
Registers: [32]Word{},
} }
} }
...@@ -72,9 +62,6 @@ func (t *ThreadState) serializeThread() []byte { ...@@ -72,9 +62,6 @@ func (t *ThreadState) serializeThread() []byte {
out = arch.ByteOrderWord.AppendWord(out, t.ThreadId) out = arch.ByteOrderWord.AppendWord(out, t.ThreadId)
out = append(out, t.ExitCode) out = append(out, t.ExitCode)
out = mipsevm.AppendBoolToWitness(out, t.Exited) out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = arch.ByteOrderWord.AppendWord(out, t.FutexAddr)
out = 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.PC)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.NextPC) out = arch.ByteOrderWord.AppendWord(out, t.Cpu.NextPC)
...@@ -107,15 +94,6 @@ func (t *ThreadState) Deserialize(in io.Reader) error { ...@@ -107,15 +94,6 @@ func (t *ThreadState) Deserialize(in io.Reader) error {
return err return err
} }
t.Exited = exited != 0 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 { if err := binary.Read(in, binary.BigEndian, &t.Cpu.PC); err != nil {
return err return err
} }
......
...@@ -25,5 +25,4 @@ const ( ...@@ -25,5 +25,4 @@ const (
RegSyscallParam1 = RegA0 RegSyscallParam1 = RegA0
RegSyscallParam2 = RegA1 RegSyscallParam2 = RegA1
RegSyscallParam3 = RegA2 RegSyscallParam3 = RegA2
RegSyscallParam4 = RegA3
) )
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
type Word = arch.Word type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error { 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) v0 := Word(0)
v1 := Word(0) v1 := Word(0)
......
...@@ -964,7 +964,7 @@ func TestEVM_HelloProgram(t *testing.T) { ...@@ -964,7 +964,7 @@ func TestEVM_HelloProgram(t *testing.T) {
break break
} }
insn := testutil.GetInstruction(state.GetMemory(), state.GetPC()) 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) 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) { ...@@ -1012,7 +1012,7 @@ func TestEVM_ClaimProgram(t *testing.T) {
} }
insn := testutil.GetInstruction(state.GetMemory(), state.GetPC()) 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) 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) { ...@@ -1020,6 +1020,7 @@ func TestEVM_ClaimProgram(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
validator.ValidateEVM(t, stepWitness, curStep, goVm) validator.ValidateEVM(t, stepWitness, curStep, goVm)
} }
t.Logf("Completed in %d steps", state.GetStep())
require.True(t, state.GetExited(), "must complete program") require.True(t, state.GetExited(), "must complete program")
require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0") require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0")
......
...@@ -486,14 +486,13 @@ func TestEVM_PopExitedThread(t *testing.T) { ...@@ -486,14 +486,13 @@ func TestEVM_PopExitedThread(t *testing.T) {
func TestEVM_SysFutex_WaitPrivate(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 // Note: parameters are written as 64-bit values. For 32-bit architectures, these values are downcast to 32-bit
cases := []struct { cases := []struct {
name string name string
addressParam uint64 addressParam uint64
effAddr uint64 effAddr uint64
targetValue uint32 targetValue uint32
actualValue uint32 actualValue uint32
timeout uint64 timeout uint64
shouldFail bool shouldFail bool
shouldSetTimeout bool
}{ }{
{name: "successful wait, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_01}, {name: "successful wait, no timeout", addressParam: 0xFF_FF_FF_FF_FF_FF_12_38, effAddr: 0xFF_FF_FF_FF_FF_FF_12_38, targetValue: 0xFF_FF_FF_01, actualValue: 0xFF_FF_FF_01},
{name: "successful wait, no timeout, unaligned addr #1", addressParam: 0xFF_FF_FF_FF_FF_FF_12_33, effAddr: 0xFF_FF_FF_FF_FF_FF_12_30, targetValue: 0x01, actualValue: 0x01}, {name: "successful wait, no timeout, unaligned addr #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) { ...@@ -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: "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", 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: "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", 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, 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},
{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", 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}, {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) { ...@@ -525,20 +524,17 @@ func TestEVM_SysFutex_WaitPrivate(t *testing.T) {
// Setup expectations // Setup expectations
expected := mttestutil.NewExpectedMTState(state) expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1 expected.Step += 1
expected.StepsSinceLastContextSwitch += 1 expected.ActiveThread().PC = state.GetCpu().NextPC
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
if c.shouldFail { if c.shouldFail {
expected.ActiveThread().PC = state.GetCpu().NextPC expected.StepsSinceLastContextSwitch += 1
expected.ActiveThread().NextPC = state.GetCpu().NextPC + 4
expected.ActiveThread().Registers[2] = exec.SysErrorSignal expected.ActiveThread().Registers[2] = exec.SysErrorSignal
expected.ActiveThread().Registers[7] = exec.MipsEAGAIN expected.ActiveThread().Registers[7] = exec.MipsEAGAIN
} else { } else {
// PC and return registers should not update on success, updates happen when wait completes // Return empty result and preempt thread
expected.ActiveThread().FutexAddr = Word(c.effAddr) expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().FutexVal = c.targetValue expected.ActiveThread().Registers[7] = 0
expected.ActiveThread().FutexTimeoutStep = exec.FutexNoTimeout expected.ExpectPreemption(state)
if c.shouldSetTimeout {
expected.ActiveThread().FutexTimeoutStep = step + exec.FutexTimeoutSteps + 1
}
} }
// State transition // State transition
...@@ -561,7 +557,6 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { ...@@ -561,7 +557,6 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
activeThreadCount int activeThreadCount int
inactiveThreadCount int inactiveThreadCount int
traverseRight bool 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", 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}, {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) { ...@@ -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 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", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false},
{name: "Traverse left, unaliagned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false}, {name: "Traverse left, unaliagned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 2, inactiveThreadCount: 1, traverseRight: false},
{name: "Traverse left, switch directions", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false, expectTraverseRight: true}, {name: "Traverse left, switch directions", addressParam: 0xFF_FF_FF_FF_FF_FF_67_88, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 1, traverseRight: false},
{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, 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, 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},
{name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false, expectTraverseRight: true}, {name: "Traverse left, single thread, unaligned", addressParam: 0xFF_FF_FF_FF_FF_FF_67_89, effAddr: 0xFF_FF_FF_FF_FF_FF_67_88, activeThreadCount: 1, inactiveThreadCount: 0, traverseRight: false},
} }
for i, c := range cases { for i, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
...@@ -595,14 +590,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) { ...@@ -595,14 +590,7 @@ func TestEVM_SysFutex_WakePrivate(t *testing.T) {
expected.ExpectStep() expected.ExpectStep()
expected.ActiveThread().Registers[2] = 0 expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0 expected.ActiveThread().Registers[7] = 0
expected.Wakeup = Word(c.effAddr)
expected.ExpectPreemption(state) 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 // State transition
stepWitness, err := goVm.Step(true) stepWitness, err := goVm.Step(true)
...@@ -1013,96 +1001,6 @@ func TestEVM_EmptyThreadStacks(t *testing.T) { ...@@ -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) { func TestEVM_NormalTraversal_Full(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
...@@ -1120,21 +1018,20 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { ...@@ -1120,21 +1018,20 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
// Setup // Setup
goVm, state, contracts := setup(t, i*789, nil) goVm, state, contracts := setup(t, i*789, nil)
mttestutil.SetupThreads(int64(i*2947), state, traverseRight, c.threadCount, 0) 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 step := state.Step
initialState := mttestutil.NewExpectedMTState(state)
// Loop through all the threads to get back to the starting state // Loop through all the threads to get back to the starting state
iterations := c.threadCount * 2 iterations := c.threadCount * 2
for i := 0; i < iterations; i++ { 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 // Set up post-state expectations
expected := mttestutil.NewExpectedMTState(state) expected := mttestutil.NewExpectedMTState(state)
expected.Step += 1 expected.ActiveThread().Registers[2] = 0
expected.ActiveThread().Registers[7] = 0
expected.ExpectStep()
expected.ExpectPreemption(state) expected.ExpectPreemption(state)
// State transition // State transition
...@@ -1147,218 +1044,11 @@ func TestEVM_NormalTraversal_Full(t *testing.T) { ...@@ -1147,218 +1044,11 @@ func TestEVM_NormalTraversal_Full(t *testing.T) {
expected.Validate(t, state) expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, multithreaded.GetStateHashFn(), contracts) 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) { func TestEVM_SchedQuantumThreshold(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
......
...@@ -27,7 +27,7 @@ func DetectVersion(path string) (StateVersion, error) { ...@@ -27,7 +27,7 @@ func DetectVersion(path string) (StateVersion, error) {
} }
switch ver { switch ver {
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2: case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3:
return ver, nil return ver, nil
default: default:
return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver) return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver)
......
...@@ -64,9 +64,9 @@ func TestDetectVersion_singleThreadedBinary(t *testing.T) { ...@@ -64,9 +64,9 @@ func TestDetectVersion_singleThreadedBinary(t *testing.T) {
} }
func TestDetectVersion_multiThreadedBinary(t *testing.T) { func TestDetectVersion_multiThreadedBinary(t *testing.T) {
targetVersion := VersionMultiThreaded targetVersion := VersionMultiThreaded_v2
if !arch.IsMips32 { if !arch.IsMips32 {
targetVersion = VersionMultiThreaded64_v2 targetVersion = VersionMultiThreaded64_v3
} }
state, err := NewFromState(multithreaded.CreateEmptyState()) state, err := NewFromState(multithreaded.CreateEmptyState())
......
...@@ -19,12 +19,19 @@ type StateVersion uint8 ...@@ -19,12 +19,19 @@ type StateVersion uint8
const ( 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 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 VersionSingleThreaded StateVersion = iota
// VersionMultiThreaded is the original implementation of 32-bit multithreaded cannon, tagged at cannon/v1.3.0
VersionMultiThreaded VersionMultiThreaded
// VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall // 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 VersionSingleThreaded2
// VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0 // VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0
VersionMultiThreaded64 VersionMultiThreaded64
// VersionMultiThreaded64_v2 includes an audit fix to ensure futex values are always 32-bit, tagged at cannon/v1.3.0
VersionMultiThreaded64_v2 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 ( var (
...@@ -33,7 +40,7 @@ var ( ...@@ -33,7 +40,7 @@ var (
ErrUnsupportedMipsArch = errors.New("mips architecture is not supported") 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) { func LoadStateFromFile(path string) (*VersionedState, error) {
if !serialize.IsBinaryFile(path) { if !serialize.IsBinaryFile(path) {
...@@ -60,12 +67,12 @@ func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) { ...@@ -60,12 +67,12 @@ func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) {
case *multithreaded.State: case *multithreaded.State:
if arch.IsMips32 { if arch.IsMips32 {
return &VersionedState{ return &VersionedState{
Version: VersionMultiThreaded, Version: VersionMultiThreaded_v2,
FPVMState: state, FPVMState: state,
}, nil }, nil
} else { } else {
return &VersionedState{ return &VersionedState{
Version: VersionMultiThreaded64_v2, Version: VersionMultiThreaded64_v3,
FPVMState: state, FPVMState: state,
}, nil }, nil
} }
...@@ -106,7 +113,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { ...@@ -106,7 +113,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
} }
s.FPVMState = state s.FPVMState = state
return nil return nil
case VersionMultiThreaded: case VersionMultiThreaded_v2:
if !arch.IsMips32 { if !arch.IsMips32 {
return ErrUnsupportedMipsArch return ErrUnsupportedMipsArch
} }
...@@ -116,7 +123,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { ...@@ -116,7 +123,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
} }
s.FPVMState = state s.FPVMState = state
return nil return nil
case VersionMultiThreaded64_v2: case VersionMultiThreaded64_v3:
if arch.IsMips32 { if arch.IsMips32 {
return ErrUnsupportedMipsArch return ErrUnsupportedMipsArch
} }
...@@ -155,6 +162,10 @@ func (s StateVersion) String() string { ...@@ -155,6 +162,10 @@ func (s StateVersion) String() string {
return "multithreaded64" return "multithreaded64"
case VersionMultiThreaded64_v2: case VersionMultiThreaded64_v2:
return "multithreaded64-2" return "multithreaded64-2"
case VersionMultiThreaded_v2:
return "multithreaded-2"
case VersionMultiThreaded64_v3:
return "multithreaded64-3"
default: default:
return "unknown" return "unknown"
} }
...@@ -172,6 +183,10 @@ func ParseStateVersion(ver string) (StateVersion, error) { ...@@ -172,6 +183,10 @@ func ParseStateVersion(ver string) (StateVersion, error) {
return VersionMultiThreaded64, nil return VersionMultiThreaded64, nil
case "multithreaded64-2": case "multithreaded64-2":
return VersionMultiThreaded64_v2, nil return VersionMultiThreaded64_v2, nil
case "multithreaded-2":
return VersionMultiThreaded_v2, nil
case "multithreaded64-3":
return VersionMultiThreaded64_v3, nil
default: default:
return StateVersion(0), errors.New("unknown state version") return StateVersion(0), errors.New("unknown state version")
} }
......
...@@ -15,16 +15,16 @@ import ( ...@@ -15,16 +15,16 @@ import (
) )
func TestNewFromState(t *testing.T) { 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()) actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err) require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState) 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) { 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()) expected, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err) require.NoError(t, err)
...@@ -40,7 +40,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { ...@@ -40,7 +40,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
version StateVersion version StateVersion
createState func() mipsevm.FPVMState createState func() mipsevm.FPVMState
}{ }{
{VersionMultiThreaded64_v2, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, {VersionMultiThreaded64_v3, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
......
...@@ -16,18 +16,18 @@ import ( ...@@ -16,18 +16,18 @@ import (
) )
func TestNewFromState(t *testing.T) { 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()) actual, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err) require.NoError(t, err)
require.IsType(t, &singlethreaded.State{}, actual.FPVMState) require.IsType(t, &singlethreaded.State{}, actual.FPVMState)
require.Equal(t, VersionSingleThreaded2, actual.Version) 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()) actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err) require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState) 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) { ...@@ -59,7 +59,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
createState func() mipsevm.FPVMState createState func() mipsevm.FPVMState
}{ }{
{VersionSingleThreaded2, func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }}, {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 { for _, test := range tests {
test := test test := test
...@@ -75,6 +75,16 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { ...@@ -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 { func writeToFile(t *testing.T, filename string, data serialize.Serializable) string {
dir := t.TempDir() dir := t.TempDir()
path := filepath.Join(dir, filename) path := filepath.Join(dir, filename)
......
...@@ -198,7 +198,6 @@ func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64 ...@@ -198,7 +198,6 @@ func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64
e.metrics.RecordMaxStepsBetweenLLAndSC(uint64(info.MaxStepsBetweenLLAndSC)) e.metrics.RecordMaxStepsBetweenLLAndSC(uint64(info.MaxStepsBetweenLLAndSC))
e.metrics.RecordReservationInvalidationCount(uint64(info.ReservationInvalidationCount)) e.metrics.RecordReservationInvalidationCount(uint64(info.ReservationInvalidationCount))
e.metrics.RecordForcedPreemptionCount(uint64(info.ForcedPreemptionCount)) e.metrics.RecordForcedPreemptionCount(uint64(info.ForcedPreemptionCount))
e.metrics.RecordFailedWakeupCount(uint64(info.FailedWakeupCount))
e.metrics.RecordIdleStepCountThread0(uint64(info.IdleStepCountThread0)) e.metrics.RecordIdleStepCountThread0(uint64(info.IdleStepCountThread0))
} }
} }
...@@ -214,6 +213,5 @@ type debugInfo struct { ...@@ -214,6 +213,5 @@ type debugInfo struct {
MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"` MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"`
ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"` ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"`
ForcedPreemptionCount uint64 `json:"forced_preemption_count"` ForcedPreemptionCount uint64 `json:"forced_preemption_count"`
FailedWakeupCount uint64 `json:"failed_wakeup_count"`
IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"` IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"`
} }
...@@ -53,7 +53,6 @@ func TestGenerateProof(t *testing.T) { ...@@ -53,7 +53,6 @@ func TestGenerateProof(t *testing.T) {
MaxStepsBetweenLLAndSC: 56, MaxStepsBetweenLLAndSC: 56,
ReservationInvalidationCount: 78, ReservationInvalidationCount: 78,
ForcedPreemptionCount: 910, ForcedPreemptionCount: 910,
FailedWakeupCount: 1112,
IdleStepCountThread0: 1314, IdleStepCountThread0: 1314,
} }
...@@ -191,7 +190,6 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev ...@@ -191,7 +190,6 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev
require.Equal(t, expected.MaxStepsBetweenLLAndSC, m.maxStepsBetweenLLAndSC) require.Equal(t, expected.MaxStepsBetweenLLAndSC, m.maxStepsBetweenLLAndSC)
require.Equal(t, expected.ReservationInvalidationCount, m.reservationInvalidations) require.Equal(t, expected.ReservationInvalidationCount, m.reservationInvalidations)
require.Equal(t, expected.ForcedPreemptionCount, m.forcedPreemptions) require.Equal(t, expected.ForcedPreemptionCount, m.forcedPreemptions)
require.Equal(t, expected.FailedWakeupCount, m.failedWakeup)
require.Equal(t, expected.IdleStepCountThread0, m.idleStepsThread0) require.Equal(t, expected.IdleStepCountThread0, m.idleStepsThread0)
} else { } else {
// If debugInfo is disabled, json file should not be written and metrics should be zeroed out // 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 ...@@ -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.maxStepsBetweenLLAndSC)
require.Equal(t, uint64(0), m.reservationInvalidations) require.Equal(t, uint64(0), m.reservationInvalidations)
require.Equal(t, uint64(0), m.forcedPreemptions) require.Equal(t, uint64(0), m.forcedPreemptions)
require.Equal(t, uint64(0), m.failedWakeup)
require.Equal(t, uint64(0), m.idleStepsThread0) require.Equal(t, uint64(0), m.idleStepsThread0)
} }
} }
...@@ -220,7 +217,6 @@ type capturingVmMetrics struct { ...@@ -220,7 +217,6 @@ type capturingVmMetrics struct {
maxStepsBetweenLLAndSC uint64 maxStepsBetweenLLAndSC uint64
reservationInvalidations uint64 reservationInvalidations uint64
forcedPreemptions uint64 forcedPreemptions uint64
failedWakeup uint64
idleStepsThread0 uint64 idleStepsThread0 uint64
} }
...@@ -256,10 +252,6 @@ func (c *capturingVmMetrics) RecordForcedPreemptionCount(val uint64) { ...@@ -256,10 +252,6 @@ func (c *capturingVmMetrics) RecordForcedPreemptionCount(val uint64) {
c.forcedPreemptions = val c.forcedPreemptions = val
} }
func (c *capturingVmMetrics) RecordFailedWakeupCount(val uint64) {
c.failedWakeup = val
}
func (c *capturingVmMetrics) RecordIdleStepCountThread0(val uint64) { func (c *capturingVmMetrics) RecordIdleStepCountThread0(val uint64) {
c.idleStepsThread0 = val c.idleStepsThread0 = val
} }
......
...@@ -17,7 +17,6 @@ type VmMetricer interface { ...@@ -17,7 +17,6 @@ type VmMetricer interface {
RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64) RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64)
RecordVmReservationInvalidationCount(vmType string, val uint64) RecordVmReservationInvalidationCount(vmType string, val uint64)
RecordVmForcedPreemptionCount(vmType string, val uint64) RecordVmForcedPreemptionCount(vmType string, val uint64)
RecordVmFailedWakeupCount(vmType string, val uint64)
RecordVmIdleStepCountThread0(vmType string, val uint64) RecordVmIdleStepCountThread0(vmType string, val uint64)
} }
...@@ -31,7 +30,6 @@ type TypedVmMetricer interface { ...@@ -31,7 +30,6 @@ type TypedVmMetricer interface {
RecordMaxStepsBetweenLLAndSC(val uint64) RecordMaxStepsBetweenLLAndSC(val uint64)
RecordReservationInvalidationCount(val uint64) RecordReservationInvalidationCount(val uint64)
RecordForcedPreemptionCount(val uint64) RecordForcedPreemptionCount(val uint64)
RecordFailedWakeupCount(val uint64)
RecordIdleStepCountThread0(val uint64) RecordIdleStepCountThread0(val uint64)
} }
...@@ -44,7 +42,6 @@ type VmMetrics struct { ...@@ -44,7 +42,6 @@ type VmMetrics struct {
vmMaxStepsBetweenLLAndSC *prometheus.GaugeVec vmMaxStepsBetweenLLAndSC *prometheus.GaugeVec
vmReservationInvalidations *prometheus.GaugeVec vmReservationInvalidations *prometheus.GaugeVec
vmForcedPreemptions *prometheus.GaugeVec vmForcedPreemptions *prometheus.GaugeVec
vmFailedWakeup *prometheus.GaugeVec
vmIdleStepsThread0 *prometheus.GaugeVec vmIdleStepsThread0 *prometheus.GaugeVec
} }
...@@ -82,10 +79,6 @@ func (m *VmMetrics) RecordVmForcedPreemptionCount(vmType string, val uint64) { ...@@ -82,10 +79,6 @@ func (m *VmMetrics) RecordVmForcedPreemptionCount(vmType string, val uint64) {
m.vmForcedPreemptions.WithLabelValues(vmType).Set(float64(val)) 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) { func (m *VmMetrics) RecordVmIdleStepCountThread0(vmType string, val uint64) {
m.vmIdleStepsThread0.WithLabelValues(vmType).Set(float64(val)) m.vmIdleStepsThread0.WithLabelValues(vmType).Set(float64(val))
} }
...@@ -137,11 +130,6 @@ func NewVmMetrics(namespace string, factory metrics.Factory) *VmMetrics { ...@@ -137,11 +130,6 @@ func NewVmMetrics(namespace string, factory metrics.Factory) *VmMetrics {
Name: "vm_forced_preemptions", Name: "vm_forced_preemptions",
Help: "Number of forced preemptions during vm run", Help: "Number of forced preemptions during vm run",
}, []string{"vm"}), }, []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{ vmIdleStepsThread0: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace, Namespace: namespace,
Name: "vm_idle_steps_thread0", Name: "vm_idle_steps_thread0",
...@@ -162,7 +150,6 @@ func (n NoopVmMetrics) RecordVmRmwFailCount(vmType string, val uint64) ...@@ -162,7 +150,6 @@ func (n NoopVmMetrics) RecordVmRmwFailCount(vmType string, val uint64)
func (n NoopVmMetrics) RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64) {} func (n NoopVmMetrics) RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmReservationInvalidationCount(vmType string, val uint64) {} func (n NoopVmMetrics) RecordVmReservationInvalidationCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmForcedPreemptionCount(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) {} func (n NoopVmMetrics) RecordVmIdleStepCountThread0(vmType string, val uint64) {}
type typedVmMetricsImpl struct { type typedVmMetricsImpl struct {
...@@ -204,10 +191,6 @@ func (m *typedVmMetricsImpl) RecordForcedPreemptionCount(val uint64) { ...@@ -204,10 +191,6 @@ func (m *typedVmMetricsImpl) RecordForcedPreemptionCount(val uint64) {
m.m.RecordVmForcedPreemptionCount(m.vmType, val) m.m.RecordVmForcedPreemptionCount(m.vmType, val)
} }
func (m *typedVmMetricsImpl) RecordFailedWakeupCount(val uint64) {
m.m.RecordVmFailedWakeupCount(m.vmType, val)
}
func (m *typedVmMetricsImpl) RecordIdleStepCountThread0(val uint64) { func (m *typedVmMetricsImpl) RecordIdleStepCountThread0(val uint64) {
m.m.RecordVmIdleStepCountThread0(m.vmType, val) 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 ...@@ -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 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 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 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-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-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-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 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. # 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 "" 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 ...@@ -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.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.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 FROM --platform=$BUILDPLATFORM builder AS cannon-builder
ARG CANNON_VERSION=v0.0.0 ARG CANNON_VERSION=v0.0.0
# Copy cannon binaries from previous versions # 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-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-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 # Build current binaries
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \ 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" GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION"
......
...@@ -11,9 +11,6 @@ interface IMIPS2 is ISemver { ...@@ -11,9 +11,6 @@ interface IMIPS2 is ISemver {
uint32 threadID; uint32 threadID;
uint8 exitCode; uint8 exitCode;
bool exited; bool exited;
uint32 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint32 pc; uint32 pc;
uint32 nextPC; uint32 nextPC;
uint32 lo; uint32 lo;
...@@ -33,7 +30,6 @@ interface IMIPS2 is ISemver { ...@@ -33,7 +30,6 @@ interface IMIPS2 is ISemver {
bool exited; bool exited;
uint64 step; uint64 step;
uint64 stepsSinceLastContextSwitch; uint64 stepsSinceLastContextSwitch;
uint32 wakeup;
bool traverseRight; bool traverseRight;
bytes32 leftThreadStack; bytes32 leftThreadStack;
bytes32 rightThreadStack; bytes32 rightThreadStack;
......
...@@ -140,16 +140,16 @@ ...@@ -140,16 +140,16 @@
"sourceCodeHash": "0xe9964aa66db1dfc86772958b4c9276697e67f7055529a43e6a49a055009bc995" "sourceCodeHash": "0xe9964aa66db1dfc86772958b4c9276697e67f7055529a43e6a49a055009bc995"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0xc10654f0e6498f424f7a5095bac36005dc7062d3813cc8f805a15005fc37406b", "initCodeHash": "0x7d0fc7c7b51b74fa2611aaa8cc1a5967e2e48f0726ea894eb2c43f36b0ff2ab7",
"sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af" "sourceCodeHash": "0xc18f51210c9d75f9bc33e55f879e8f0ab2514924718022264c0a2993134821e0"
}, },
"src/cannon/MIPS2.sol": { "src/cannon/MIPS2.sol": {
"initCodeHash": "0x70ecbb2327fadd6205312aa93279a6340242dfeb44a00b98caa7b56688c46bdc", "initCodeHash": "0x4e1dbd0a6ac84873622af9234aca42e6e7b2bfda1186bbfd3ff83081f141ef86",
"sourceCodeHash": "0x84506539c40b72b1d40c9d07418650b30c27d2b219b40e55f61edcd31365157f" "sourceCodeHash": "0x62c820b22c72399efd7688dcf713c34a6ee6821835ec66d5e7b98f33bbbfb209"
}, },
"src/cannon/MIPS64.sol": { "src/cannon/MIPS64.sol": {
"initCodeHash": "0xa2a42c50d2fac71d93e44ad4871e5d838f1c630b9d1abc4c89971d36b0ae44bb", "initCodeHash": "0xd15808bd3a9f0779dfc662dd75fb11fe5f2ff15c3a6e9f699d05dad49e064afb",
"sourceCodeHash": "0xdb771f1b92c7612b120e0bce31967f0c8a7ce332dbb426bc9cfc52b47be21c4d" "sourceCodeHash": "0x84609ac875a282e8a0675bcd8558635dcd7d054cc23395691d87922a15a815ba"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x17d3b3df1aaaf7a705b8d48de8a05e6511b910fdafdbe5eb7f7f95ec944fba9a", "initCodeHash": "0x17d3b3df1aaaf7a705b8d48de8a05e6511b910fdafdbe5eb7f7f95ec944fba9a",
......
...@@ -47,8 +47,8 @@ contract MIPS is ISemver { ...@@ -47,8 +47,8 @@ contract MIPS is ISemver {
} }
/// @notice The semantic version of the MIPS contract. /// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.2.1-beta.10 /// @custom:semver 1.3.0-beta.1
string public constant version = "1.2.1-beta.10"; string public constant version = "1.3.0-beta.1";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -151,7 +151,7 @@ contract MIPS is ISemver { ...@@ -151,7 +151,7 @@ contract MIPS is ISemver {
} }
// Load the syscall numbers and args from the registers // 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 v0 = 0;
uint32 v1 = 0; uint32 v1 = 0;
......
...@@ -20,16 +20,13 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; ...@@ -20,16 +20,13 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
/// It differs from MIPS.sol in that it supports multi-threading. /// It differs from MIPS.sol in that it supports multi-threading.
contract MIPS2 is ISemver { contract MIPS2 is ISemver {
/// @notice The thread context. /// @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 { struct ThreadState {
// metadata // metadata
uint32 threadID; uint32 threadID;
uint8 exitCode; uint8 exitCode;
bool exited; bool exited;
// state // state
uint32 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint32 pc; uint32 pc;
uint32 nextPC; uint32 nextPC;
uint32 lo; uint32 lo;
...@@ -37,11 +34,13 @@ contract MIPS2 is ISemver { ...@@ -37,11 +34,13 @@ contract MIPS2 is ISemver {
uint32[32] registers; uint32[32] registers;
} }
uint32 internal constant PACKED_THREAD_STATE_SIZE = 150;
uint8 internal constant LL_STATUS_NONE = 0; uint8 internal constant LL_STATUS_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE = 1; uint8 internal constant LL_STATUS_ACTIVE = 1;
/// @notice Stores the VM state. /// @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. /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State { struct State {
bytes32 memRoot; bytes32 memRoot;
...@@ -55,7 +54,6 @@ contract MIPS2 is ISemver { ...@@ -55,7 +54,6 @@ contract MIPS2 is ISemver {
bool exited; bool exited;
uint64 step; uint64 step;
uint64 stepsSinceLastContextSwitch; uint64 stepsSinceLastContextSwitch;
uint32 wakeup;
bool traverseRight; bool traverseRight;
bytes32 leftThreadStack; bytes32 leftThreadStack;
bytes32 rightThreadStack; bytes32 rightThreadStack;
...@@ -63,8 +61,8 @@ contract MIPS2 is ISemver { ...@@ -63,8 +61,8 @@ contract MIPS2 is ISemver {
} }
/// @notice The semantic version of the MIPS2 contract. /// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 1.0.0-beta.27 /// @custom:semver 1.0.0-beta.28
string public constant version = "1.0.0-beta.27"; string public constant version = "1.0.0-beta.28";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -73,7 +71,7 @@ contract MIPS2 is ISemver { ...@@ -73,7 +71,7 @@ contract MIPS2 is ISemver {
uint256 internal constant THREAD_PROOF_OFFSET = 356; uint256 internal constant THREAD_PROOF_OFFSET = 356;
// The offset of the start of proof calldata (_memProof.offset) in the step() function // 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)) // The empty thread root - keccak256(bytes32(0) ++ bytes32(0))
bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5";
...@@ -82,7 +80,7 @@ contract MIPS2 is ISemver { ...@@ -82,7 +80,7 @@ contract MIPS2 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80; uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step // 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. /// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) { constructor(IPreimageOracle _oracle) {
...@@ -149,10 +147,13 @@ contract MIPS2 is ISemver { ...@@ -149,10 +147,13 @@ contract MIPS2 is ISemver {
} }
if iszero(eq(thread, TC_MEM_OFFSET)) { if iszero(eq(thread, TC_MEM_OFFSET)) {
// expected thread mem offset check // 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) revert(0, 0)
} }
if iszero(eq(mload(0x40), shl(5, 63))) { if iszero(eq(mload(0x40), shl(5, 59))) {
// 4 + 16 state slots + 43 thread slots = 63 expected memory check // 4 + 15 state slots + 40 thread slots = 59 expected memory check
revert(0, 0) revert(0, 0)
} }
if iszero(eq(_stateData.offset, 132)) { if iszero(eq(_stateData.offset, 132)) {
...@@ -160,7 +161,10 @@ contract MIPS2 is ISemver { ...@@ -160,7 +161,10 @@ contract MIPS2 is ISemver {
revert(0, 0) revert(0, 0)
} }
if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) { 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) revert(0, 0)
} }
...@@ -187,7 +191,6 @@ contract MIPS2 is ISemver { ...@@ -187,7 +191,6 @@ contract MIPS2 is ISemver {
exited := mload(sub(m, 32)) exited := mload(sub(m, 32))
c, m := putField(c, m, 8) // step c, m := putField(c, m, 8) // step
c, m := putField(c, m, 8) // stepsSinceLastContextSwitch 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, 1) // traverseRight
c, m := putField(c, m, 32) // leftThreadStack c, m := putField(c, m, 32) // leftThreadStack
c, m := putField(c, m, 32) // rightThreadStack c, m := putField(c, m, 32) // rightThreadStack
...@@ -212,52 +215,11 @@ contract MIPS2 is ISemver { ...@@ -212,52 +215,11 @@ contract MIPS2 is ISemver {
setThreadStateFromCalldata(thread); setThreadStateFromCalldata(thread);
validateCalldataThreadWitness(state, 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) { if (thread.exited) {
popThread(state); popThread(state);
return outputState(); 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) { if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) {
preemptThread(state, thread); preemptThread(state, thread);
return outputState(); return outputState();
...@@ -394,7 +356,7 @@ contract MIPS2 is ISemver { ...@@ -394,7 +356,7 @@ contract MIPS2 is ISemver {
} }
// Load the syscall numbers and args from the registers // 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 // Syscalls that are unimplemented but known return with v0=0 and v1=0
uint32 v0 = 0; uint32 v0 = 0;
uint32 v1 = 0; uint32 v1 = 0;
...@@ -416,9 +378,6 @@ contract MIPS2 is ISemver { ...@@ -416,9 +378,6 @@ contract MIPS2 is ISemver {
newThread.threadID = state.nextThreadID; newThread.threadID = state.nextThreadID;
newThread.exitCode = 0; newThread.exitCode = 0;
newThread.exited = false; newThread.exited = false;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC; newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4; newThread.nextPC = thread.nextPC + 4;
newThread.lo = thread.lo; newThread.lo = thread.lo;
...@@ -494,39 +453,16 @@ contract MIPS2 is ISemver { ...@@ -494,39 +453,16 @@ contract MIPS2 is ISemver {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN; v1 = sys.EAGAIN;
} else { } else {
thread.futexAddr = effFutexAddr; return syscallYield(state, thread);
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();
} }
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) { } else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup return syscallYield(state, thread);
// 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();
} else { } else {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL; v1 = sys.EINVAL;
} }
} else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) { } else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) {
v0 = 0; return syscallYield(state, thread);
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
return outputState();
} else if (syscall_no == sys.SYS_OPEN) { } else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF; v1 = sys.EBADF;
...@@ -650,6 +586,17 @@ contract MIPS2 is ISemver { ...@@ -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( function execSysRead(
State memory _state, State memory _state,
sys.SysReadParams memory _args sys.SysReadParams memory _args
...@@ -699,7 +646,6 @@ contract MIPS2 is ISemver { ...@@ -699,7 +646,6 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 1) // exited from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // step from, to := copyMem(from, to, 8) // step
from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch 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, 1) // traverseRight
from, to := copyMem(from, to, 32) // leftThreadStack from, to := copyMem(from, to, 32) // leftThreadStack
from, to := copyMem(from, to, 32) // rightThreadStack from, to := copyMem(from, to, 32) // rightThreadStack
...@@ -750,26 +696,6 @@ contract MIPS2 is ISemver { ...@@ -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. /// @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. /// It reads the inner thread root from calldata to update the current thread stack root.
function preemptThread( function preemptThread(
...@@ -855,9 +781,6 @@ contract MIPS2 is ISemver { ...@@ -855,9 +781,6 @@ contract MIPS2 is ISemver {
from, to := copyMem(from, to, 4) // threadID from, to := copyMem(from, to, 4) // threadID
from, to := copyMem(from, to, 1) // exitCode from, to := copyMem(from, to, 1) // exitCode
from, to := copyMem(from, to, 1) // exited from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 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) // pc
from, to := copyMem(from, to, 4) // nextPC from, to := copyMem(from, to, 4) // nextPC
from, to := copyMem(from, to, 4) // lo from, to := copyMem(from, to, 4) // lo
...@@ -899,7 +822,7 @@ contract MIPS2 is ISemver { ...@@ -899,7 +822,7 @@ contract MIPS2 is ISemver {
s := calldatasize() s := calldatasize()
} }
// verify we have enough calldata // 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 { unchecked {
assembly { assembly {
...@@ -916,9 +839,6 @@ contract MIPS2 is ISemver { ...@@ -916,9 +839,6 @@ contract MIPS2 is ISemver {
c, m := putField(c, m, 4) // threadID c, m := putField(c, m, 4) // threadID
c, m := putField(c, m, 1) // exitCode c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited c, m := putField(c, m, 1) // exited
c, m := putField(c, m, 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) // pc
c, m := putField(c, m, 4) // nextPC c, m := putField(c, m, 4) // nextPC
c, m := putField(c, m, 4) // lo c, m := putField(c, m, 4) // lo
...@@ -935,10 +855,10 @@ contract MIPS2 is ISemver { ...@@ -935,10 +855,10 @@ contract MIPS2 is ISemver {
uint256 s = 0; uint256 s = 0;
assembly { assembly {
s := calldatasize() s := calldatasize()
innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, 166)) innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, PACKED_THREAD_STATE_SIZE))
} }
// verify we have enough calldata // verify we have enough calldata
require(s >= (THREAD_PROOF_OFFSET + 198), "insufficient calldata for thread witness"); // 166 + 32 require(s >= (THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32), "insufficient calldata for thread witness");
} }
/// @notice Loads a 32-bit futex value at _vAddr /// @notice Loads a 32-bit futex value at _vAddr
......
...@@ -21,16 +21,13 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; ...@@ -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. /// It differs from MIPS.sol in that it supports MIPS64 instructions and multi-tasking.
contract MIPS64 is ISemver { contract MIPS64 is ISemver {
/// @notice The thread context. /// @notice The thread context.
/// Total state size: 8 + 1 + 1 + 8 + 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 { struct ThreadState {
// metadata // metadata
uint64 threadID; uint64 threadID;
uint8 exitCode; uint8 exitCode;
bool exited; bool exited;
// state // state
uint64 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint64 pc; uint64 pc;
uint64 nextPC; uint64 nextPC;
uint64 lo; uint64 lo;
...@@ -38,14 +35,14 @@ contract MIPS64 is ISemver { ...@@ -38,14 +35,14 @@ contract MIPS64 is ISemver {
uint64[32] registers; 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_NONE = 0;
uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1; uint8 internal constant LL_STATUS_ACTIVE_32_BIT = 0x1;
uint8 internal constant LL_STATUS_ACTIVE_64_BIT = 0x2; uint8 internal constant LL_STATUS_ACTIVE_64_BIT = 0x2;
/// @notice Stores the VM state. /// @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. /// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State { struct State {
bytes32 memRoot; bytes32 memRoot;
...@@ -59,7 +56,6 @@ contract MIPS64 is ISemver { ...@@ -59,7 +56,6 @@ contract MIPS64 is ISemver {
bool exited; bool exited;
uint64 step; uint64 step;
uint64 stepsSinceLastContextSwitch; uint64 stepsSinceLastContextSwitch;
uint64 wakeup;
bool traverseRight; bool traverseRight;
bytes32 leftThreadStack; bytes32 leftThreadStack;
bytes32 rightThreadStack; bytes32 rightThreadStack;
...@@ -67,14 +63,14 @@ contract MIPS64 is ISemver { ...@@ -67,14 +63,14 @@ contract MIPS64 is ISemver {
} }
/// @notice The semantic version of the MIPS64 contract. /// @notice The semantic version of the MIPS64 contract.
/// @custom:semver 1.0.0-beta.9 /// @custom:semver 1.0.0-beta.10
string public constant version = "1.0.0-beta.9"; string public constant version = "1.0.0-beta.10";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
// The offset of the start of proof calldata (_threadWitness.offset) in the step() function // 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 // 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; uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + PACKED_THREAD_STATE_SIZE + 32;
...@@ -86,7 +82,7 @@ contract MIPS64 is ISemver { ...@@ -86,7 +82,7 @@ contract MIPS64 is ISemver {
uint256 internal constant STATE_MEM_OFFSET = 0x80; uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step // 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. /// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) { constructor(IPreimageOracle _oracle) {
...@@ -153,10 +149,13 @@ contract MIPS64 is ISemver { ...@@ -153,10 +149,13 @@ contract MIPS64 is ISemver {
} }
if iszero(eq(thread, TC_MEM_OFFSET)) { if iszero(eq(thread, TC_MEM_OFFSET)) {
// expected thread mem offset check // 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) revert(0, 0)
} }
if iszero(eq(mload(0x40), shl(5, 63))) { if iszero(eq(mload(0x40), shl(5, 59))) {
// 4 + 16 state slots + 43 thread slots = 63 expected memory check // 4 + 15 state slots + 40 thread slots = 59 expected memory check
revert(0, 0) revert(0, 0)
} }
if iszero(eq(_stateData.offset, 132)) { if iszero(eq(_stateData.offset, 132)) {
...@@ -165,10 +164,9 @@ contract MIPS64 is ISemver { ...@@ -165,10 +164,9 @@ contract MIPS64 is ISemver {
} }
if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) { if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) {
// _stateData.offset = 132 // _stateData.offset = 132
// stateData.length = 196 // stateData.length = ceil(stateSize / 32) * 32 = 6 * 32 = 192
// 32-byte align padding = 28
// _proof size prefix = 32 // _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) revert(0, 0)
} }
...@@ -195,7 +193,6 @@ contract MIPS64 is ISemver { ...@@ -195,7 +193,6 @@ contract MIPS64 is ISemver {
exited := mload(sub(m, 32)) exited := mload(sub(m, 32))
c, m := putField(c, m, 8) // step c, m := putField(c, m, 8) // step
c, m := putField(c, m, 8) // stepsSinceLastContextSwitch 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, 1) // traverseRight
c, m := putField(c, m, 32) // leftThreadStack c, m := putField(c, m, 32) // leftThreadStack
c, m := putField(c, m, 32) // rightThreadStack c, m := putField(c, m, 32) // rightThreadStack
...@@ -220,52 +217,11 @@ contract MIPS64 is ISemver { ...@@ -220,52 +217,11 @@ contract MIPS64 is ISemver {
setThreadStateFromCalldata(thread); setThreadStateFromCalldata(thread);
validateCalldataThreadWitness(state, 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) { if (thread.exited) {
popThread(state); popThread(state);
return outputState(); 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) { if (state.stepsSinceLastContextSwitch >= sys.SCHED_QUANTUM) {
preemptThread(state, thread); preemptThread(state, thread);
return outputState(); return outputState();
...@@ -434,7 +390,7 @@ contract MIPS64 is ISemver { ...@@ -434,7 +390,7 @@ contract MIPS64 is ISemver {
} }
// Load the syscall numbers and args from the registers // 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 // Syscalls that are unimplemented but known return with v0=0 and v1=0
uint64 v0 = 0; uint64 v0 = 0;
uint64 v1 = 0; uint64 v1 = 0;
...@@ -456,9 +412,6 @@ contract MIPS64 is ISemver { ...@@ -456,9 +412,6 @@ contract MIPS64 is ISemver {
newThread.threadID = state.nextThreadID; newThread.threadID = state.nextThreadID;
newThread.exitCode = 0; newThread.exitCode = 0;
newThread.exited = false; newThread.exited = false;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC; newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4; newThread.nextPC = thread.nextPC + 4;
newThread.lo = thread.lo; newThread.lo = thread.lo;
...@@ -535,39 +488,16 @@ contract MIPS64 is ISemver { ...@@ -535,39 +488,16 @@ contract MIPS64 is ISemver {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN; v1 = sys.EAGAIN;
} else { } else {
thread.futexAddr = effFutexAddr; return syscallYield(state, thread);
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();
} }
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) { } else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup return syscallYield(state, thread);
// 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();
} else { } else {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL; v1 = sys.EINVAL;
} }
} else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) { } else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) {
v0 = 0; return syscallYield(state, thread);
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
return outputState();
} else if (syscall_no == sys.SYS_OPEN) { } else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL; v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF; v1 = sys.EBADF;
...@@ -687,6 +617,17 @@ contract MIPS64 is ISemver { ...@@ -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( function execSysRead(
State memory _state, State memory _state,
sys.SysReadParams memory _args sys.SysReadParams memory _args
...@@ -736,7 +677,6 @@ contract MIPS64 is ISemver { ...@@ -736,7 +677,6 @@ contract MIPS64 is ISemver {
from, to := copyMem(from, to, 1) // exited from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // step from, to := copyMem(from, to, 8) // step
from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch 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, 1) // traverseRight
from, to := copyMem(from, to, 32) // leftThreadStack from, to := copyMem(from, to, 32) // leftThreadStack
from, to := copyMem(from, to, 32) // rightThreadStack from, to := copyMem(from, to, 32) // rightThreadStack
...@@ -787,26 +727,6 @@ contract MIPS64 is ISemver { ...@@ -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. /// @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. /// It reads the inner thread root from calldata to update the current thread stack root.
function preemptThread( function preemptThread(
...@@ -892,9 +812,6 @@ contract MIPS64 is ISemver { ...@@ -892,9 +812,6 @@ contract MIPS64 is ISemver {
from, to := copyMem(from, to, 8) // threadID from, to := copyMem(from, to, 8) // threadID
from, to := copyMem(from, to, 1) // exitCode from, to := copyMem(from, to, 1) // exitCode
from, to := copyMem(from, to, 1) // exited from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // futexAddr
from, to := copyMem(from, to, 4) // futexVal
from, to := copyMem(from, to, 8) // futexTimeoutStep
from, to := copyMem(from, to, 8) // pc from, to := copyMem(from, to, 8) // pc
from, to := copyMem(from, to, 8) // nextPC from, to := copyMem(from, to, 8) // nextPC
from, to := copyMem(from, to, 8) // lo from, to := copyMem(from, to, 8) // lo
...@@ -955,9 +872,6 @@ contract MIPS64 is ISemver { ...@@ -955,9 +872,6 @@ contract MIPS64 is ISemver {
c, m := putField(c, m, 8) // threadID c, m := putField(c, m, 8) // threadID
c, m := putField(c, m, 1) // exitCode c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited c, m := putField(c, m, 1) // exited
c, m := putField(c, m, 8) // futexAddr
c, m := putField(c, m, 4) // futexVal
c, m := putField(c, m, 8) // futexTimeoutStep
c, m := putField(c, m, 8) // pc c, m := putField(c, m, 8) // pc
c, m := putField(c, m, 8) // nextPC c, m := putField(c, m, 8) // nextPC
c, m := putField(c, m, 8) // lo c, m := putField(c, m, 8) // lo
......
...@@ -119,9 +119,6 @@ library MIPS64Syscalls { ...@@ -119,9 +119,6 @@ library MIPS64Syscalls {
uint64 internal constant FUTEX_WAIT_PRIVATE = 128; uint64 internal constant FUTEX_WAIT_PRIVATE = 128;
uint64 internal constant FUTEX_WAKE_PRIVATE = 129; 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 SCHED_QUANTUM = 100_000;
uint64 internal constant HZ = 10_000_000; uint64 internal constant HZ = 10_000_000;
...@@ -168,7 +165,6 @@ library MIPS64Syscalls { ...@@ -168,7 +165,6 @@ library MIPS64Syscalls {
uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0; uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0;
uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1; uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1;
uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2; uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2;
uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3;
// Constants copied from MIPS64Arch for use in Yul // Constants copied from MIPS64Arch for use in Yul
uint64 internal constant WORD_SIZE_BYTES = 8; uint64 internal constant WORD_SIZE_BYTES = 8;
...@@ -180,11 +176,10 @@ library MIPS64Syscalls { ...@@ -180,11 +176,10 @@ library MIPS64Syscalls {
/// @return a0_ The first argument available to the syscall operation. /// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second 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 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) function getSyscallArgs(uint64[32] memory _registers)
internal internal
pure pure
returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_, uint64 a3_) returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_)
{ {
unchecked { unchecked {
sysCallNum_ = _registers[REG_SYSCALL_NUM]; sysCallNum_ = _registers[REG_SYSCALL_NUM];
...@@ -192,9 +187,8 @@ library MIPS64Syscalls { ...@@ -192,9 +187,8 @@ library MIPS64Syscalls {
a0_ = _registers[REG_SYSCALL_PARAM1]; a0_ = _registers[REG_SYSCALL_PARAM1];
a1_ = _registers[REG_SYSCALL_PARAM2]; a1_ = _registers[REG_SYSCALL_PARAM2];
a2_ = _registers[REG_SYSCALL_PARAM3]; 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 { ...@@ -115,9 +115,6 @@ library MIPSSyscalls {
uint32 internal constant FUTEX_WAIT_PRIVATE = 128; uint32 internal constant FUTEX_WAIT_PRIVATE = 128;
uint32 internal constant FUTEX_WAKE_PRIVATE = 129; 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 SCHED_QUANTUM = 100_000;
uint32 internal constant HZ = 10_000_000; uint32 internal constant HZ = 10_000_000;
...@@ -165,7 +162,6 @@ library MIPSSyscalls { ...@@ -165,7 +162,6 @@ library MIPSSyscalls {
uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0; uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0;
uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1; uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1;
uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2; uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2;
uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3;
/// @notice Extract syscall num and arguments from registers. /// @notice Extract syscall num and arguments from registers.
/// @param _registers The cpu registers. /// @param _registers The cpu registers.
...@@ -173,11 +169,10 @@ library MIPSSyscalls { ...@@ -173,11 +169,10 @@ library MIPSSyscalls {
/// @return a0_ The first argument available to the syscall operation. /// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second 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 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) function getSyscallArgs(uint32[32] memory _registers)
internal internal
pure pure
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_, uint32 a3_) returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_)
{ {
unchecked { unchecked {
sysCallNum_ = _registers[REG_SYSCALL_NUM]; sysCallNum_ = _registers[REG_SYSCALL_NUM];
...@@ -185,9 +180,8 @@ library MIPSSyscalls { ...@@ -185,9 +180,8 @@ library MIPSSyscalls {
a0_ = _registers[REG_SYSCALL_PARAM1]; a0_ = _registers[REG_SYSCALL_PARAM1];
a1_ = _registers[REG_SYSCALL_PARAM2]; a1_ = _registers[REG_SYSCALL_PARAM2];
a2_ = _registers[REG_SYSCALL_PARAM3]; 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 { ...@@ -157,13 +157,13 @@ contract MIPS2_Test is CommonTest {
/// @notice Used to debug step() behavior given a specific input. /// @notice Used to debug step() behavior given a specific input.
/// This is useful to more easily debug non-forge tests. /// This is useful to more easily debug non-forge tests.
/// For example, in cannon/mipsevm/evm_test.go step input can be pulled here: /// For example, in cannon/mipsevm/testutil/mips.go step input can be pulled here:
/// https://github.com/ethereum-optimism/optimism/blob/1f64dd6db5561f3bb76ed1d1ffdaff0cde9b7c4b/cannon/mipsevm/evm_test.go#L80-L80 /// https://github.com/ethereum-optimism/optimism/blob/efcaa2ded4ee4d6c76331314ab2da0366972aa0a/cannon/mipsevm/testutil/mips.go#L104-L104
function test_step_debug_succeeds() external { function test_step_debug_succeeds() external {
bytes memory input = bytes memory input =
hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acab5a39c6f974b22302e96dcdef1815483eaf580639bb1ee7ac98267afac2bf1ac041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81fa7c3d90b000000000000000000000078fc2ffac2fd940100000000000080c8ffffffff006504aeffb6e08baf3f85da5476a9160fa8f9f188a722fdd29268b0cbaf596736ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007c6000000000000ffffffff000000000000000000000000f1f85ff4f1f85ff8506d442dbb3938f83eb60825a7ecbff2000010185e1a31f600050f0000000064a7c3d90be5acea102ad7bda149e0bfd0e7111c77d98b335645e665389becadf140ef999cc64fbd7f04799e85c97dadc5cca510bd5b3d97166d1aec28829f3dd43d8cf1f9358e4103b16d09d466e2c7c048ea3ba1aef3141e700270581aa0b75b50e34fc926bb2d83ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb500000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; hex"e14ced3200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a8b2b243c8ff90766c3f413a78ce5dc5176b0aa029576b87025dbeaf6a54020af2c041d3ff12045b73c86e4ff95ff662a5eee82abdf44a2d0b75fb180daf48a79e3143a81f956bdecf000000000000000000000078fc2ffac2fd9401000000000000dfc800c39478fcda196ca0fced6b42ecb09452580e4b553f4ba3e23d60de73779e6d4a3d718f9eeedd979b6295aabc5adf08862b09cce94bb10865cc78cbd362c2fba7000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b360000000100000000000000000004a0b25df715ec361e43da99550d2e51b81e754db19d40d6d1ed7acb9d6cccd60534c6870936427cee207c51f09ea9c6dcdcbae4865e5f5e026159b4819f295228e910f1366e21ba060337c8018933ad52e51feb4a82d0cc8d289256f48dcf2f1a7806a3958755a4cdf0c5a9dfbe8f1cbf5942f5e7928760894b15d3c5c5a01b075b5c96d4526829d9f5c9a3af93f534a428a7f4b89f96be325b55f40dd7a26b5d441a4101a75595a30a000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
(bool success, bytes memory retVal) = address(mips).call(input); (bool success, bytes memory retVal) = address(mips).call(input);
bytes memory expectedRetVal = hex"0335fe4205f8443eefa7ac4541197874224df35e8536158c2fc2d5c8c2d2adb4"; bytes memory expectedRetVal = hex"0334289a94004544cab7d6b90238581ca3e082e097d90deb6251da612df2530f";
assertTrue(success); assertTrue(success);
assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned"); assertEq(retVal.length, 32, "Expect a bytes32 hash of the post-state to be returned");
...@@ -179,9 +179,6 @@ contract MIPS2_Test is CommonTest { ...@@ -179,9 +179,6 @@ contract MIPS2_Test is CommonTest {
threadID: 0, threadID: 0,
exitCode: 0, exitCode: 0,
exited: false, exited: false,
futexAddr: sys.FUTEX_EMPTY_ADDR,
futexVal: 0,
futexTimeoutStep: 0,
pc: 4, pc: 4,
nextPC: 8, nextPC: 8,
lo: 0, lo: 0,
...@@ -204,7 +201,6 @@ contract MIPS2_Test is CommonTest { ...@@ -204,7 +201,6 @@ contract MIPS2_Test is CommonTest {
exited: false, exited: false,
step: 1, step: 1,
stepsSinceLastContextSwitch: 1, stepsSinceLastContextSwitch: 1,
wakeup: sys.FUTEX_EMPTY_ADDR,
traverseRight: false, traverseRight: false,
leftThreadStack: threadRoot, leftThreadStack: threadRoot,
rightThreadStack: EMPTY_THREAD_ROOT, rightThreadStack: EMPTY_THREAD_ROOT,
...@@ -349,9 +345,6 @@ contract MIPS2_Test is CommonTest { ...@@ -349,9 +345,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory newThread = copyThread(thread); IMIPS2.ThreadState memory newThread = copyThread(thread);
newThread.threadID = 1; newThread.threadID = 1;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC; newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4; newThread.nextPC = thread.nextPC + 4;
newThread.registers[2] = 0; newThread.registers[2] = 0;
...@@ -400,44 +393,17 @@ contract MIPS2_Test is CommonTest { ...@@ -400,44 +393,17 @@ contract MIPS2_Test is CommonTest {
/// @dev Static unit test asserting successful futex wait syscall behavior with a timeout argument /// @dev Static unit test asserting successful futex wait syscall behavior with a timeout argument
function test_syscallFutexWaitTimeout_succeeds() public { function test_syscallFutexWaitTimeout_succeeds() public {
uint32 futexAddr = 0x1000; syscallFutexWaitTest(1);
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");
} }
/// @dev Static unit test asserting successful futex wait syscall behavior with a zero timeout argument /// @dev Static unit test asserting successful futex wait syscall behavior with a zero timeout argument
function test_syscallFutexWaitNoTimeout_succeeds() public { function test_syscallFutexWaitNoTimeout_succeeds() public {
syscallFutexWaitTest(0);
}
function syscallFutexWaitTest(uint32 timeout) private {
uint32 futexAddr = 0x1000; uint32 futexAddr = 0x1000;
uint32 futexVal = 0xAA_AA_AA_AA; uint32 futexVal = 0xAA_AA_AA_AA;
uint32 timeout = 0;
uint32 insn = 0x0000000c; // syscall uint32 insn = 0x0000000c; // syscall
(IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) = (IMIPS2.State memory state, IMIPS2.ThreadState memory thread, bytes memory memProof) =
...@@ -452,16 +418,20 @@ contract MIPS2_Test is CommonTest { ...@@ -452,16 +418,20 @@ contract MIPS2_Test is CommonTest {
bytes memory threadWitness = threading.witness(); bytes memory threadWitness = threading.witness();
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
// FUTEX_WAIT // FUTEX_WAIT should return empty values and preempt thread
IMIPS2.ThreadState memory expectThread = copyThread(thread); IMIPS2.ThreadState memory expectThread = copyThread(thread);
expectThread.futexAddr = futexAddr; expectThread.registers[2] = 0;
expectThread.futexVal = futexVal; expectThread.registers[7] = 0;
expectThread.futexTimeoutStep = sys.FUTEX_NO_TIMEOUT; expectThread.pc = thread.nextPC;
threading.replaceCurrent(expectThread); expectThread.nextPC = thread.nextPC + 4;
// Preempt thread
threading.left().pop();
threading.right().push(expectThread);
IMIPS2.State memory expect = copyState(state); IMIPS2.State memory expect = copyState(state);
expect.step = state.step + 1; expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = state.stepsSinceLastContextSwitch + 1; expect.stepsSinceLastContextSwitch = 0;
expect.traverseRight = true;
finalizeThreadingState(threading, expect); finalizeThreadingState(threading, expect);
bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0); bytes32 postState = mips.step(encodeState(state), bytes.concat(threadWitness, memProof), 0);
...@@ -490,7 +460,6 @@ contract MIPS2_Test is CommonTest { ...@@ -490,7 +460,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory expectThread = copyThread(thread); IMIPS2.ThreadState memory expectThread = copyThread(thread);
expectThread.pc = thread.nextPC; expectThread.pc = thread.nextPC;
expectThread.nextPC = thread.nextPC + 4; expectThread.nextPC = thread.nextPC + 4;
expectThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
expectThread.registers[2] = sys.SYS_ERROR_SIGNAL; expectThread.registers[2] = sys.SYS_ERROR_SIGNAL;
expectThread.registers[7] = sys.EAGAIN; // errno expectThread.registers[7] = sys.EAGAIN; // errno
threading.replaceCurrent(expectThread); threading.replaceCurrent(expectThread);
...@@ -530,7 +499,6 @@ contract MIPS2_Test is CommonTest { ...@@ -530,7 +499,6 @@ contract MIPS2_Test is CommonTest {
threading.right().push(expectThread); threading.right().push(expectThread);
IMIPS2.State memory expect = copyState(state); IMIPS2.State memory expect = copyState(state);
expect.wakeup = futexAddr;
expect.step = state.step + 1; expect.step = state.step + 1;
expect.stepsSinceLastContextSwitch = 0; expect.stepsSinceLastContextSwitch = 0;
expect.traverseRight = true; expect.traverseRight = true;
...@@ -546,13 +514,11 @@ contract MIPS2_Test is CommonTest { ...@@ -546,13 +514,11 @@ contract MIPS2_Test is CommonTest {
uint8 exitCode = 4; uint8 exitCode = 4;
IMIPS2.ThreadState memory threadA = threading.createThread(); IMIPS2.ThreadState memory threadA = threading.createThread();
threadA.futexAddr = sys.FUTEX_EMPTY_ADDR;
threadA.pc = 0x1000; threadA.pc = 0x1000;
threadA.nextPC = 0x1004; threadA.nextPC = 0x1004;
threading.replaceCurrent(threadA); threading.replaceCurrent(threadA);
IMIPS2.ThreadState memory threadB = threading.createThread(); IMIPS2.ThreadState memory threadB = threading.createThread();
threadB.futexAddr = sys.FUTEX_EMPTY_ADDR;
threadB.pc = 0x100; threadB.pc = 0x100;
threadB.nextPC = 0x104; threadB.nextPC = 0x104;
threadB.registers[2] = sys.SYS_EXIT; threadB.registers[2] = sys.SYS_EXIT;
...@@ -565,7 +531,6 @@ contract MIPS2_Test is CommonTest { ...@@ -565,7 +531,6 @@ contract MIPS2_Test is CommonTest {
(state.memRoot, memProof) = ffi.getCannonMemoryProof(threadB.pc, insn, 0, 0); (state.memRoot, memProof) = ffi.getCannonMemoryProof(threadB.pc, insn, 0, 0);
state.step = 20; state.step = 20;
state.stepsSinceLastContextSwitch = 10; state.stepsSinceLastContextSwitch = 10;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
// state updates // state updates
...@@ -588,7 +553,6 @@ contract MIPS2_Test is CommonTest { ...@@ -588,7 +553,6 @@ contract MIPS2_Test is CommonTest {
uint8 exitCode = 4; uint8 exitCode = 4;
IMIPS2.ThreadState memory thread = threading.createThread(); IMIPS2.ThreadState memory thread = threading.createThread();
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
thread.pc = 0x1000; thread.pc = 0x1000;
thread.nextPC = 0x1004; thread.nextPC = 0x1004;
thread.registers[2] = sys.SYS_EXIT; thread.registers[2] = sys.SYS_EXIT;
...@@ -601,7 +565,6 @@ contract MIPS2_Test is CommonTest { ...@@ -601,7 +565,6 @@ contract MIPS2_Test is CommonTest {
(state.memRoot, memProof) = ffi.getCannonMemoryProof(thread.pc, insn, 0, 0); (state.memRoot, memProof) = ffi.getCannonMemoryProof(thread.pc, insn, 0, 0);
state.step = 20; state.step = 20;
state.stepsSinceLastContextSwitch = 10; state.stepsSinceLastContextSwitch = 10;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
// state updates // state updates
...@@ -830,13 +793,10 @@ contract MIPS2_Test is CommonTest { ...@@ -830,13 +793,10 @@ contract MIPS2_Test is CommonTest {
function test_threadQuantumSchedule_succeeds() public { function test_threadQuantumSchedule_succeeds() public {
IMIPS2.ThreadState memory threadA = threading.createThread(); IMIPS2.ThreadState memory threadA = threading.createThread();
threadA.threadID = 0; threadA.threadID = 0;
threadA.futexAddr = sys.FUTEX_EMPTY_ADDR;
threading.replaceCurrent(threadA); threading.replaceCurrent(threadA);
IMIPS2.ThreadState memory threadB = threading.createThread(); IMIPS2.ThreadState memory threadB = threading.createThread();
threadB.futexAddr = sys.FUTEX_EMPTY_ADDR;
threading.replaceCurrent(threadB); threading.replaceCurrent(threadB);
IMIPS2.State memory state; IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = sys.SCHED_QUANTUM; state.stepsSinceLastContextSwitch = sys.SCHED_QUANTUM;
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
bytes memory threadWitness = threading.witness(); bytes memory threadWitness = threading.witness();
...@@ -855,10 +815,9 @@ contract MIPS2_Test is CommonTest { ...@@ -855,10 +815,9 @@ contract MIPS2_Test is CommonTest {
assertEq(postState, outputState(expect), "unexpected post state"); 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 { function test_threadTraverseLeft_succeeds() public {
IMIPS2.State memory state; IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.step = 10; state.step = 10;
state.stepsSinceLastContextSwitch = 0; state.stepsSinceLastContextSwitch = 0;
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
...@@ -873,7 +832,6 @@ contract MIPS2_Test is CommonTest { ...@@ -873,7 +832,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory thread = threading.createThread(); IMIPS2.ThreadState memory thread = threading.createThread();
thread.pc = pc; thread.pc = pc;
thread.nextPC = pc + 4; thread.nextPC = pc + 4;
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
thread.registers[2] = sys.SYS_NANOSLEEP; thread.registers[2] = sys.SYS_NANOSLEEP;
threading.replaceCurrent(thread); threading.replaceCurrent(thread);
} }
...@@ -905,12 +863,11 @@ contract MIPS2_Test is CommonTest { ...@@ -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 { function test_threadTraverseRight_succeeds() public {
threading.setTraverseRight(true); threading.setTraverseRight(true);
IMIPS2.State memory state; IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.step = 10; state.step = 10;
state.stepsSinceLastContextSwitch = 0; state.stepsSinceLastContextSwitch = 0;
state.traverseRight = true; state.traverseRight = true;
...@@ -926,7 +883,6 @@ contract MIPS2_Test is CommonTest { ...@@ -926,7 +883,6 @@ contract MIPS2_Test is CommonTest {
IMIPS2.ThreadState memory thread = threading.createThread(); IMIPS2.ThreadState memory thread = threading.createThread();
thread.pc = pc; thread.pc = pc;
thread.nextPC = pc + 4; thread.nextPC = pc + 4;
thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
thread.registers[2] = sys.SYS_NANOSLEEP; thread.registers[2] = sys.SYS_NANOSLEEP;
threading.replaceCurrent(thread); threading.replaceCurrent(thread);
} }
...@@ -957,334 +913,6 @@ contract MIPS2_Test is CommonTest { ...@@ -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 /// @dev Static unit test asserting VM behavior when the current thread has exited
function test_threadExit_succeeds() public { function test_threadExit_succeeds() public {
threading.createThread(); threading.createThread();
...@@ -1295,7 +923,6 @@ contract MIPS2_Test is CommonTest { ...@@ -1295,7 +923,6 @@ contract MIPS2_Test is CommonTest {
bytes memory threadWitness = threading.witness(); bytes memory threadWitness = threading.witness();
IMIPS2.State memory state; IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = 10; state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
...@@ -1323,7 +950,6 @@ contract MIPS2_Test is CommonTest { ...@@ -1323,7 +950,6 @@ contract MIPS2_Test is CommonTest {
bytes memory threadWitness = threading.witness(); bytes memory threadWitness = threading.witness();
IMIPS2.State memory state; IMIPS2.State memory state;
state.wakeup = sys.FUTEX_EMPTY_ADDR;
state.stepsSinceLastContextSwitch = 10; state.stepsSinceLastContextSwitch = 10;
finalizeThreadingState(threading, state); finalizeThreadingState(threading, state);
...@@ -2708,10 +2334,8 @@ contract MIPS2_Test is CommonTest { ...@@ -2708,10 +2334,8 @@ contract MIPS2_Test is CommonTest {
{ {
(state_.memRoot, proof_) = ffi.getCannonMemoryProof(pc, insn, addr, val); (state_.memRoot, proof_) = ffi.getCannonMemoryProof(pc, insn, addr, val);
state_.nextThreadID = 1; state_.nextThreadID = 1;
state_.wakeup = sys.FUTEX_EMPTY_ADDR;
thread_.pc = pc; thread_.pc = pc;
thread_.nextPC = pc + 4; thread_.nextPC = pc + 4;
thread_.futexAddr = sys.FUTEX_EMPTY_ADDR;
state_.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(thread_)))); state_.leftThreadStack = keccak256(abi.encodePacked(EMPTY_THREAD_ROOT, keccak256(encodeThread(thread_))));
state_.rightThreadStack = EMPTY_THREAD_ROOT; state_.rightThreadStack = EMPTY_THREAD_ROOT;
} }
...@@ -2779,9 +2403,8 @@ contract MIPS2_Test is CommonTest { ...@@ -2779,9 +2403,8 @@ contract MIPS2_Test is CommonTest {
bytes memory b = abi.encodePacked( bytes memory b = abi.encodePacked(
_state.llOwnerThread, _state.exitCode, _state.exited, _state.step, _state.stepsSinceLastContextSwitch _state.llOwnerThread, _state.exitCode, _state.exited, _state.step, _state.stepsSinceLastContextSwitch
); );
bytes memory c = abi.encodePacked( bytes memory c =
_state.wakeup, _state.traverseRight, _state.leftThreadStack, _state.rightThreadStack, _state.nextThreadID abi.encodePacked(_state.traverseRight, _state.leftThreadStack, _state.rightThreadStack, _state.nextThreadID);
);
return abi.encodePacked(a, b, c); return abi.encodePacked(a, b, c);
} }
...@@ -2846,9 +2469,6 @@ function encodeThread(IMIPS2.ThreadState memory _thread) pure returns (bytes mem ...@@ -2846,9 +2469,6 @@ function encodeThread(IMIPS2.ThreadState memory _thread) pure returns (bytes mem
_thread.threadID, _thread.threadID,
_thread.exitCode, _thread.exitCode,
_thread.exited, _thread.exited,
_thread.futexAddr,
_thread.futexVal,
_thread.futexTimeoutStep,
_thread.pc, _thread.pc,
_thread.nextPC, _thread.nextPC,
_thread.lo, _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