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

cannon: Simplify futex handling (#13754)

* cannon: Simplify futex handling

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

* cannon: Cut now unused onWaitComplete() method

* cannon: Update single-threaded mips call to getSyscallArgs

* cannon: Update contracts to match go vm

* cannon: Update solidity tests

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

* cannon: Update differential tests

* cannon: Cut wakeup field

* cannon: Remove thread futex fields

* cannon: Fix const name

* cannon: Add new cannon state versions

* cannon: Cut wakeup stats

* cannon: Fix ParseStateVersion(), add test

* cannon: Reenable evm validation in tests

* cannon: Temporarily use ad hoc cannon release

* cannon: Run semver lock

* cannon: Fix semver comment, run semver-lock

* cannon: Cut unused constants

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

---------
Co-authored-by: default avatarinphi <mlaw2501@gmail.com>
parent ef6ef6fd
......@@ -152,14 +152,14 @@ cannon-prestate: op-program cannon ## Generates prestate using cannon and op-pro
mv op-program/bin/0.json op-program/bin/prestate-proof.json
.PHONY: cannon-prestate
cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the multithreaded64-2 cannon format
./cannon/bin/cannon load-elf --type multithreaded64-2 --path op-program/bin/op-program-client64.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json
cannon-prestate-mt: op-program cannon ## Generates prestate using cannon and op-program in the latest 64-bit multithreaded cannon format
./cannon/bin/cannon load-elf --type multithreaded64-3 --path op-program/bin/op-program-client64.elf --out op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json
./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate-mt.bin.gz --meta op-program/bin/meta-mt.json --proof-fmt 'op-program/bin/%d-mt.json' --output ""
mv op-program/bin/0-mt.json op-program/bin/prestate-proof-mt.json
.PHONY: cannon-prestate-mt
cannon-prestate-interop: op-program cannon ## Generates interop prestate using cannon and op-program in the multithreaded64-2 cannon format
./cannon/bin/cannon load-elf --type multithreaded64-2 --path op-program/bin/op-program-client-interop.elf --out op-program/bin/prestate-interop.bin.gz --meta op-program/bin/meta-interop.json
cannon-prestate-interop: op-program cannon ## Generates interop prestate using cannon and op-program in the latest 64-bit multithreaded cannon format
./cannon/bin/cannon load-elf --type multithreaded64-3 --path op-program/bin/op-program-client-interop.elf --out op-program/bin/prestate-interop.bin.gz --meta op-program/bin/meta-interop.json
./cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input op-program/bin/prestate-interop.bin.gz --meta op-program/bin/meta-interop.json --proof-fmt 'op-program/bin/%d-interop.json' --output ""
mv op-program/bin/0-interop.json op-program/bin/prestate-proof-interop.json
.PHONY: cannon-prestate-interop
......
......@@ -31,13 +31,14 @@ cannon64-impl:
# It should build the individual versions of cannons and copy them into place in hte multicannon/embeds directory
# Ideally, preserve backwards compatibility with this behaviour but if it needs to change, build-legacy-cannons.sh will
# need to be updated to account for different behaviours in different versions.
# Each embed is suffixed with the latest `StateVersion` number corresponding to the target VM and architecture.
cannon-embeds: cannon32-impl cannon64-impl
# singlethreaded-v2
# 32-bit singlethreaded vm
@cp bin/cannon32-impl ./multicannon/embeds/cannon-2
# multithreaded
@cp bin/cannon32-impl ./multicannon/embeds/cannon-1
# 64-bit multithreaded v2
@cp bin/cannon64-impl ./multicannon/embeds/cannon-4
# 32-bit multithreaded vm
@cp bin/cannon32-impl ./multicannon/embeds/cannon-5
# 64-bit multithreaded vm
@cp bin/cannon64-impl ./multicannon/embeds/cannon-6
cannon: cannon-embeds
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/
......
......@@ -80,7 +80,7 @@ func LoadELF(ctx *cli.Context) error {
}
return program.PatchStack(state)
}
case versions.VersionMultiThreaded, versions.VersionMultiThreaded64_v2:
case versions.VersionMultiThreaded_v2, versions.VersionMultiThreaded64_v3:
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, multithreaded.CreateInitialState)
}
......
......@@ -14,6 +14,5 @@ type DebugInfo struct {
MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"`
ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"`
ForcedPreemptionCount uint64 `json:"forced_preemption_count"`
FailedWakeupCount uint64 `json:"failed_wakeup_count"`
IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"`
}
......@@ -23,7 +23,6 @@ func TestDebugInfo_Serialization(t *testing.T) {
MaxStepsBetweenLLAndSC: 7,
ReservationInvalidationCount: 8,
ForcedPreemptionCount: 9,
FailedWakeupCount: 10,
IdleStepCountThread0: math.MaxUint64,
}
......
......@@ -42,11 +42,8 @@ const (
// SysFutex-related constants
const (
FutexWaitPrivate = 128
FutexWakePrivate = 129
FutexTimeoutSteps = 10_000
FutexNoTimeout = ^uint64(0)
FutexEmptyAddr = ^Word(0)
FutexWaitPrivate = 128
FutexWakePrivate = 129
)
// SysClone flags
......@@ -99,15 +96,14 @@ const (
ClockGettimeMonotonicFlag = 1
)
func GetSyscallArgs(registers *[32]Word) (syscallNum, a0, a1, a2, a3 Word) {
func GetSyscallArgs(registers *[32]Word) (syscallNum, a0, a1, a2 Word) {
syscallNum = registers[register.RegSyscallNum] // v0
a0 = registers[register.RegSyscallParam1]
a1 = registers[register.RegSyscallParam2]
a2 = registers[register.RegSyscallParam3]
a3 = registers[register.RegSyscallParam4]
return syscallNum, a0, a1, a2, a3
return syscallNum, a0, a1, a2
}
func HandleSysMmap(a0, a1, heap Word) (v0, v1, newHeap Word) {
......
......@@ -20,7 +20,7 @@ type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error {
thread := m.state.GetCurrentThread()
syscallNum, a0, a1, a2, a3 := exec.GetSyscallArgs(m.state.GetRegistersRef())
syscallNum, a0, a1, a2 := exec.GetSyscallArgs(m.state.GetRegistersRef())
v0 := Word(0)
v1 := Word(0)
......@@ -43,12 +43,9 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = m.state.NextThreadId
v1 = 0
newThread := &ThreadState{
ThreadId: m.state.NextThreadId,
ExitCode: 0,
Exited: false,
FutexAddr: exec.FutexEmptyAddr,
FutexVal: 0,
FutexTimeoutStep: 0,
ThreadId: m.state.NextThreadId,
ExitCode: 0,
Exited: false,
Cpu: mipsevm.CpuScalars{
PC: thread.Cpu.NextPC,
NextPC: thread.Cpu.NextPC + 4,
......@@ -119,37 +116,18 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = exec.SysErrorSignal
v1 = exec.MipsEAGAIN
} else {
thread.FutexAddr = effFutexAddr
thread.FutexVal = targetVal
if a3 == 0 {
thread.FutexTimeoutStep = exec.FutexNoTimeout
} else {
thread.FutexTimeoutStep = m.state.Step + exec.FutexTimeoutSteps
}
// Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete`
m.syscallYield(thread)
return nil
}
case exec.FutexWakePrivate:
// Trigger a wakeup traversal
m.state.Wakeup = effFutexAddr
// Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees.
// The woken up thread should indicate this in userspace.
v0 = 0
v1 = 0
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
m.state.TraverseRight = len(m.state.LeftThreadStack) == 0
m.statsTracker.trackWakeupTraversalStart()
m.syscallYield(thread)
return nil
default:
v0 = exec.SysErrorSignal
v1 = exec.MipsEINVAL
}
case arch.SysSchedYield, arch.SysNanosleep:
v0 = 0
v1 = 0
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
m.syscallYield(thread)
return nil
case arch.SysOpen:
v0 = exec.SysErrorSignal
......@@ -225,6 +203,13 @@ func (m *InstrumentedState) handleSyscall() error {
return nil
}
func (m *InstrumentedState) syscallYield(thread *ThreadState) {
v0 := Word(0)
v1 := Word(0)
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.preemptThread(thread)
}
func (m *InstrumentedState) mipsStep() error {
err := m.doMipsStep()
if err != nil {
......@@ -249,56 +234,12 @@ func (m *InstrumentedState) doMipsStep() error {
m.state.Step += 1
thread := m.state.GetCurrentThread()
// During wakeup traversal, search for the first thread blocked on the wakeup address.
// Don't allow regular execution until we have found such a thread or else we have visited all threads.
if m.state.Wakeup != exec.FutexEmptyAddr {
// We are currently performing a wakeup traversal
if m.state.Wakeup == thread.FutexAddr {
// We found a target thread, resume normal execution and process this thread
m.state.Wakeup = exec.FutexEmptyAddr
} else {
// This is not the thread we're looking for, move on
traversingRight := m.state.TraverseRight
changedDirections := m.preemptThread(thread)
if traversingRight && changedDirections {
// We started the wakeup traversal walking left and we've now walked all the way right
// We have therefore visited all threads and can resume normal thread execution
m.state.Wakeup = exec.FutexEmptyAddr
}
}
return nil
}
if thread.Exited {
m.popThread()
m.stackTracker.DropThread(thread.ThreadId)
return nil
}
// check if thread is blocked on a futex
if thread.FutexAddr != exec.FutexEmptyAddr {
// if set, then check futex
// check timeout first
if m.state.Step > thread.FutexTimeoutStep {
// timeout! Allow execution
m.onWaitComplete(thread, true)
return nil
} else {
futexVal := m.getFutexValue(thread.FutexAddr)
if thread.FutexVal == futexVal {
// still got expected value, continue sleeping, try next thread.
m.preemptThread(thread)
m.statsTracker.trackWakeupFail()
return nil
} else {
// wake thread up, the value at its address changed!
// Userspace can turn thread back to sleep if it was too sporadic.
m.onWaitComplete(thread, false)
return nil
}
}
}
if m.state.StepsSinceLastContextSwitch >= exec.SchedQuantum {
// Force a context switch as this thread has been active too long
if m.state.ThreadCount() > 1 {
......@@ -412,24 +353,6 @@ func (m *InstrumentedState) handleRMWOps(insn, opcode uint32) error {
return exec.HandleRd(m.state.getCpuRef(), m.state.GetRegistersRef(), rtReg, retVal, true)
}
func (m *InstrumentedState) onWaitComplete(thread *ThreadState, isTimedOut bool) {
// Note: no need to reset m.state.Wakeup. If we're here, the Wakeup field has already been reset
// Clear the futex state
thread.FutexAddr = exec.FutexEmptyAddr
thread.FutexVal = 0
thread.FutexTimeoutStep = 0
// Complete the FUTEX_WAIT syscall
v0 := Word(0)
v1 := Word(0)
if isTimedOut {
v0 = exec.SysErrorSignal
v1 = exec.MipsETIMEDOUT
}
exec.HandleSyscallUpdates(&thread.Cpu, &thread.Registers, v0, v1)
m.statsTracker.trackWakeup()
}
func (m *InstrumentedState) preemptThread(thread *ThreadState) bool {
// Pop thread from the current stack and push to the other stack
if m.state.TraverseRight {
......
......@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/op-service/serialize"
)
......@@ -30,13 +29,12 @@ const (
EXITED_WITNESS_OFFSET = EXITCODE_WITNESS_OFFSET + 1
STEP_WITNESS_OFFSET = EXITED_WITNESS_OFFSET + 1
STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET = STEP_WITNESS_OFFSET + 8
WAKEUP_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
TRAVERSE_RIGHT_WITNESS_OFFSET = WAKEUP_WITNESS_OFFSET + arch.WordSizeBytes
TRAVERSE_RIGHT_WITNESS_OFFSET = STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET + 8
LEFT_THREADS_ROOT_WITNESS_OFFSET = TRAVERSE_RIGHT_WITNESS_OFFSET + 1
RIGHT_THREADS_ROOT_WITNESS_OFFSET = LEFT_THREADS_ROOT_WITNESS_OFFSET + 32
THREAD_ID_WITNESS_OFFSET = RIGHT_THREADS_ROOT_WITNESS_OFFSET + 32
// 172 and 196 bytes for 32 and 64-bit respectively
// 168 and 188 bytes for 32 and 64-bit respectively
STATE_WITNESS_SIZE = THREAD_ID_WITNESS_OFFSET + arch.WordSizeBytes
)
......@@ -64,7 +62,6 @@ type State struct {
Step uint64
StepsSinceLastContextSwitch uint64
Wakeup Word
TraverseRight bool
LeftThreadStack []*ThreadState
......@@ -89,7 +86,6 @@ func CreateEmptyState() *State {
ExitCode: 0,
Exited: false,
Step: 0,
Wakeup: exec.FutexEmptyAddr,
TraverseRight: false,
LeftThreadStack: []*ThreadState{initThread},
RightThreadStack: []*ThreadState{},
......@@ -215,7 +211,6 @@ func (s *State) EncodeWitness() ([]byte, common.Hash) {
out = binary.BigEndian.AppendUint64(out, s.Step)
out = binary.BigEndian.AppendUint64(out, s.StepsSinceLastContextSwitch)
out = arch.ByteOrderWord.AppendWord(out, s.Wakeup)
leftStackRoot := s.getLeftThreadStackRoot()
rightStackRoot := s.getRightThreadStackRoot()
......@@ -262,7 +257,6 @@ func (s *State) ThreadCount() int {
// Exited uint8 - 0 for false, 1 for true
// Step uint64
// StepsSinceLastContextSwitch uint64
// Wakeup Word
// TraverseRight uint8 - 0 for false, 1 for true
// NextThreadId Word
// len(LeftThreadStack) Word
......@@ -307,9 +301,6 @@ func (s *State) Serialize(out io.Writer) error {
if err := bout.WriteUInt(s.StepsSinceLastContextSwitch); err != nil {
return err
}
if err := bout.WriteUInt(s.Wakeup); err != nil {
return err
}
if err := bout.WriteBool(s.TraverseRight); err != nil {
return err
}
......@@ -376,9 +367,6 @@ func (s *State) Deserialize(in io.Reader) error {
if err := bin.ReadUInt(&s.StepsSinceLastContextSwitch); err != nil {
return err
}
if err := bin.ReadUInt(&s.Wakeup); err != nil {
return err
}
if err := bin.ReadBool(&s.TraverseRight); err != nil {
return err
}
......
......@@ -14,7 +14,6 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
)
......@@ -85,7 +84,6 @@ func TestState_EncodeWitness(t *testing.T) {
}
setWitnessField(expectedWitness, STEP_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(step)})
setWitnessField(expectedWitness, STEPS_SINCE_CONTEXT_SWITCH_WITNESS_OFFSET, []byte{0, 0, 0, 0, 0, 0, 0, byte(stepsSinceContextSwitch)})
setWitnessWord(expectedWitness, WAKEUP_WITNESS_OFFSET, ^arch.Word(0))
setWitnessField(expectedWitness, TRAVERSE_RIGHT_WITNESS_OFFSET, []byte{0})
setWitnessField(expectedWitness, LEFT_THREADS_ROOT_WITNESS_OFFSET, leftStackRoot[:])
setWitnessField(expectedWitness, RIGHT_THREADS_ROOT_WITNESS_OFFSET, rightStackRoot[:])
......@@ -132,7 +130,6 @@ func TestState_JSONCodec(t *testing.T) {
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Step, newState.Step)
require.Equal(t, state.StepsSinceLastContextSwitch, newState.StepsSinceLastContextSwitch)
require.Equal(t, state.Wakeup, newState.Wakeup)
require.Equal(t, state.TraverseRight, newState.TraverseRight)
require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack)
require.Equal(t, state.RightThreadStack, newState.RightThreadStack)
......@@ -170,7 +167,6 @@ func TestState_Binary(t *testing.T) {
require.Equal(t, state.Memory.MerkleRoot(), newState.Memory.MerkleRoot())
require.Equal(t, state.Step, newState.Step)
require.Equal(t, state.StepsSinceLastContextSwitch, newState.StepsSinceLastContextSwitch)
require.Equal(t, state.Wakeup, newState.Wakeup)
require.Equal(t, state.TraverseRight, newState.TraverseRight)
require.Equal(t, state.LeftThreadStack, newState.LeftThreadStack)
require.Equal(t, state.RightThreadStack, newState.RightThreadStack)
......@@ -196,16 +192,12 @@ func TestSerializeStateRoundTrip(t *testing.T) {
Exited: true,
Step: 0xdeadbeef,
StepsSinceLastContextSwitch: 334,
Wakeup: 42,
TraverseRight: true,
LeftThreadStack: []*ThreadState{
{
ThreadId: 45,
ExitCode: 46,
Exited: true,
FutexAddr: 47,
FutexVal: 48,
FutexTimeoutStep: 49,
ThreadId: 45,
ExitCode: 46,
Exited: true,
Cpu: mipsevm.CpuScalars{
PC: 0xFF,
NextPC: 0xFF + 4,
......@@ -223,12 +215,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
},
},
{
ThreadId: 55,
ExitCode: 56,
Exited: false,
FutexAddr: 57,
FutexVal: 58,
FutexTimeoutStep: 59,
ThreadId: 55,
ExitCode: 56,
Exited: false,
Cpu: mipsevm.CpuScalars{
PC: 0xEE,
NextPC: 0xEE + 4,
......@@ -243,12 +232,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
},
RightThreadStack: []*ThreadState{
{
ThreadId: 65,
ExitCode: 66,
Exited: false,
FutexAddr: 67,
FutexVal: 68,
FutexTimeoutStep: 69,
ThreadId: 65,
ExitCode: 66,
Exited: false,
Cpu: mipsevm.CpuScalars{
PC: 0xdd,
NextPC: 0xdd + 4,
......@@ -260,12 +246,9 @@ func TestSerializeStateRoundTrip(t *testing.T) {
},
},
{
ThreadId: 75,
ExitCode: 76,
Exited: true,
FutexAddr: 77,
FutexVal: 78,
FutexTimeoutStep: 79,
ThreadId: 75,
ExitCode: 76,
Exited: true,
Cpu: mipsevm.CpuScalars{
PC: 0xcc,
NextPC: 0xcc + 4,
......@@ -360,20 +343,16 @@ func TestState_EncodeThreadProof_MultipleThreads(t *testing.T) {
func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
cases := []struct {
name string
wakeupAddr Word
traverseRight bool
}{
{"traverse left during wakeup traversal", Word(99), false},
{"traverse left during normal traversal", exec.FutexEmptyAddr, false},
{"traverse right during wakeup traversal", Word(99), true},
{"traverse right during normal traversal", exec.FutexEmptyAddr, true},
{"traverse left", false},
{"traverse right", true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
// Set up invalid state where the active stack is empty
state := CreateEmptyState()
state.Wakeup = c.wakeupAddr
state.TraverseRight = c.traverseRight
if c.traverseRight {
state.LeftThreadStack = []*ThreadState{CreateEmptyThread()}
......@@ -389,17 +368,17 @@ func TestState_EncodeThreadProof_EmptyThreadStackPanic(t *testing.T) {
}
func TestStateWitnessSize(t *testing.T) {
expectedWitnessSize := 172
expectedWitnessSize := 168
if !arch.IsMips32 {
expectedWitnessSize = 196
expectedWitnessSize = 188
}
require.Equal(t, expectedWitnessSize, STATE_WITNESS_SIZE)
}
func TestThreadStateWitnessSize(t *testing.T) {
expectedWitnessSize := 166
expectedWitnessSize := 150
if !arch.IsMips32 {
expectedWitnessSize = 318
expectedWitnessSize = 298
}
require.Equal(t, expectedWitnessSize, SERIALIZED_THREAD_SIZE)
}
......@@ -13,9 +13,6 @@ type StatsTracker interface {
trackSCFailure(threadId Word, step uint64)
trackReservationInvalidation()
trackForcedPreemption()
trackWakeupTraversalStart()
trackWakeup()
trackWakeupFail()
trackThreadActivated(tid Word, step uint64)
populateDebugInfo(debugInfo *mipsevm.DebugInfo)
}
......@@ -32,9 +29,6 @@ func (s *noopStatsTracker) trackSCSuccess(threadId Word, step uint64) {}
func (s *noopStatsTracker) trackSCFailure(threadId Word, step uint64) {}
func (s *noopStatsTracker) trackReservationInvalidation() {}
func (s *noopStatsTracker) trackForcedPreemption() {}
func (s *noopStatsTracker) trackWakeupTraversalStart() {}
func (s *noopStatsTracker) trackWakeup() {}
func (s *noopStatsTracker) trackWakeupFail() {}
func (s *noopStatsTracker) trackThreadActivated(tid Word, step uint64) {}
func (s *noopStatsTracker) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {}
......@@ -44,7 +38,6 @@ var _ StatsTracker = (*noopStatsTracker)(nil)
type statsTrackerImpl struct {
// State
lastLLStepByThread *lru.LRU[Word, uint64]
isWakeupTraversal bool
activeThreadId Word
lastActiveStepThread0 uint64
// Stats
......@@ -54,7 +47,6 @@ type statsTrackerImpl struct {
// Tracks RMW reservation invalidation due to reserved memory being accessed outside of the RMW sequence
reservationInvalidationCount uint64
forcedPreemptionCount uint64
failedWakeupCount uint64
idleStepCountThread0 uint64
}
......@@ -64,7 +56,6 @@ func (s *statsTrackerImpl) populateDebugInfo(debugInfo *mipsevm.DebugInfo) {
debugInfo.MaxStepsBetweenLLAndSC = s.maxStepsBetweenLLAndSC
debugInfo.ReservationInvalidationCount = s.reservationInvalidationCount
debugInfo.ForcedPreemptionCount = s.forcedPreemptionCount
debugInfo.FailedWakeupCount = s.failedWakeupCount
debugInfo.IdleStepCountThread0 = s.idleStepCountThread0
}
......@@ -102,21 +93,6 @@ func (s *statsTrackerImpl) trackForcedPreemption() {
s.forcedPreemptionCount += 1
}
func (s *statsTrackerImpl) trackWakeupTraversalStart() {
s.isWakeupTraversal = true
}
func (s *statsTrackerImpl) trackWakeup() {
s.isWakeupTraversal = false
}
func (s *statsTrackerImpl) trackWakeupFail() {
if s.isWakeupTraversal {
s.failedWakeupCount += 1
}
s.isWakeupTraversal = false
}
func (s *statsTrackerImpl) trackThreadActivated(tid Word, step uint64) {
if s.activeThreadId == Word(0) && tid != Word(0) {
// Thread 0 has been deactivated, start tracking to capture idle steps
......
......@@ -69,26 +69,6 @@ func TestStatsTracker(t *testing.T) {
operations: []Operation{forcePreempt(), forcePreempt()},
expected: &mipsevm.DebugInfo{ForcedPreemptionCount: 2},
},
{
name: "Successful wakeup traversal",
operations: []Operation{wakeupTraversal(), wakeup(), sleep(), wakeup()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 0},
},
{
name: "Failed wakeup traversal",
operations: []Operation{wakeupTraversal(), sleep(), wakeup(), sleep()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 1},
},
{
name: "Multiple failed wakeup traversals",
operations: []Operation{wakeupTraversal(), sleep(), wakeupTraversal(), sleep(), wakeupTraversal(), wakeup()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 2},
},
{
name: "Wakeups and sleeps outside of wakeup traversal",
operations: []Operation{sleep(), wakeup(), wakeup(), sleep()},
expected: &mipsevm.DebugInfo{FailedWakeupCount: 0},
},
{
name: "Preempt thread 0 for thread 0",
operations: []Operation{activateThread(0, 10), activateThread(0, 20), activateThread(0, 21)},
......@@ -148,24 +128,6 @@ func forcePreempt() Operation {
}
}
func wakeupTraversal() Operation {
return func(tracker StatsTracker) {
tracker.trackWakeupTraversalStart()
}
}
func wakeup() Operation {
return func(tracker StatsTracker) {
tracker.trackWakeup()
}
}
func sleep() Operation {
return func(tracker StatsTracker) {
tracker.trackWakeupFail()
}
}
func activateThread(tid Word, step uint64) Operation {
return func(tracker StatsTracker) {
tracker.trackThreadActivated(tid, step)
......
......@@ -31,7 +31,6 @@ type ExpectedMTState struct {
expectedMemory *memory.Memory
// Threading-related expectations
StepsSinceLastContextSwitch uint64
Wakeup arch.Word
TraverseRight bool
NextThreadId arch.Word
ThreadCount int
......@@ -44,18 +43,15 @@ type ExpectedMTState struct {
}
type ExpectedThreadState struct {
ThreadId arch.Word
ExitCode uint8
Exited bool
FutexAddr arch.Word
FutexVal uint32
FutexTimeoutStep uint64
PC arch.Word
NextPC arch.Word
HI arch.Word
LO arch.Word
Registers [32]arch.Word
Dropped bool
ThreadId arch.Word
ExitCode uint8
Exited bool
PC arch.Word
NextPC arch.Word
HI arch.Word
LO arch.Word
Registers [32]arch.Word
Dropped bool
}
func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
......@@ -81,7 +77,6 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
MemoryRoot: fromState.GetMemory().MerkleRoot(),
// Thread-related global fields
StepsSinceLastContextSwitch: fromState.StepsSinceLastContextSwitch,
Wakeup: fromState.Wakeup,
TraverseRight: fromState.TraverseRight,
NextThreadId: fromState.NextThreadId,
ThreadCount: fromState.ThreadCount(),
......@@ -98,18 +93,15 @@ func NewExpectedMTState(fromState *multithreaded.State) *ExpectedMTState {
func newExpectedThreadState(fromThread *multithreaded.ThreadState) *ExpectedThreadState {
return &ExpectedThreadState{
ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited,
FutexAddr: fromThread.FutexAddr,
FutexVal: fromThread.FutexVal,
FutexTimeoutStep: fromThread.FutexTimeoutStep,
PC: fromThread.Cpu.PC,
NextPC: fromThread.Cpu.NextPC,
HI: fromThread.Cpu.HI,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
ThreadId: fromThread.ThreadId,
ExitCode: fromThread.ExitCode,
Exited: fromThread.Exited,
PC: fromThread.Cpu.PC,
NextPC: fromThread.Cpu.NextPC,
HI: fromThread.Cpu.HI,
LO: fromThread.Cpu.LO,
Registers: fromThread.Registers,
Dropped: false,
}
}
......@@ -193,7 +185,6 @@ func (e *ExpectedMTState) Validate(t require.TestingT, actualState *multithreade
require.Equalf(t, e.MemoryRoot, common.Hash(actualState.GetMemory().MerkleRoot()), "Expect memory root = %v", e.MemoryRoot)
// Thread-related global fields
require.Equalf(t, e.StepsSinceLastContextSwitch, actualState.StepsSinceLastContextSwitch, "Expect StepsSinceLastContextSwitch = %v", e.StepsSinceLastContextSwitch)
require.Equalf(t, e.Wakeup, actualState.Wakeup, "Expect Wakeup = %v", e.Wakeup)
require.Equalf(t, e.TraverseRight, actualState.TraverseRight, "Expect TraverseRight = %v", e.TraverseRight)
require.Equalf(t, e.NextThreadId, actualState.NextThreadId, "Expect NextThreadId = %v", e.NextThreadId)
require.Equalf(t, e.ThreadCount, actualState.ThreadCount(), "Expect thread count = %v", e.ThreadCount)
......@@ -229,7 +220,4 @@ func (e *ExpectedMTState) validateThread(t require.TestingT, et *ExpectedThreadS
require.Equalf(t, et.Registers, actual.Registers, "Expect registers to match (%v)", threadInfo)
require.Equalf(t, et.ExitCode, actual.ExitCode, "Expect exitCode = %v (%v)", et.ExitCode, threadInfo)
require.Equalf(t, et.Exited, actual.Exited, "Expect exited = %v (%v)", et.Exited, threadInfo)
require.Equalf(t, et.FutexAddr, actual.FutexAddr, "Expect futexAddr = %v (%v)", et.FutexAddr, threadInfo)
require.Equalf(t, et.FutexVal, actual.FutexVal, "Expect futexVal = %v (%v)", et.FutexVal, threadInfo)
require.Equalf(t, et.FutexTimeoutStep, actual.FutexTimeoutStep, "Expect futexTimeoutStep = %v (%v)", et.FutexTimeoutStep, threadInfo)
}
......@@ -38,7 +38,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "LastHint", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.LastHint = []byte{7, 8, 9, 10} }},
{name: "MemoryRoot", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.MemoryRoot = emptyHash }},
{name: "StepsSinceLastContextSwitch", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.StepsSinceLastContextSwitch += 1 }},
{name: "Wakeup", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.Wakeup += 1 }},
{name: "TraverseRight", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.TraverseRight = !e.TraverseRight }},
{name: "NextThreadId", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.NextThreadId += 1 }},
{name: "ThreadCount", mut: func(e *ExpectedMTState, st *multithreaded.State) { e.ThreadCount += 1 }},
......@@ -60,15 +59,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "Active thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].Exited = !st.GetCurrentThread().Exited
}},
{name: "Active thread futexAddr", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexAddr += 1
}},
{name: "Active thread futexVal", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexVal += 1
}},
{name: "Active thread FutexTimeoutStep", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].FutexTimeoutStep += 1
}},
{name: "Active thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[st.GetCurrentThread().ThreadId].PC += 1
}},
......@@ -96,15 +86,6 @@ func TestValidate_shouldCatchMutations(t *testing.T) {
{name: "Inactive thread exited", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].Exited = !FindNextThread(st).Exited
}},
{name: "Inactive thread futexAddr", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexAddr += 1
}},
{name: "Inactive thread futexVal", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexVal += 1
}},
{name: "Inactive thread FutexTimeoutStep", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].FutexTimeoutStep += 1
}},
{name: "Inactive thread PC", mut: func(e *ExpectedMTState, st *multithreaded.State) {
e.threadExpectations[FindNextThread(st).ThreadId].PC += 1
}},
......
......@@ -9,21 +9,17 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/exec"
)
const (
THREAD_ID_STATE_WITNESS_OFFSET = 0
THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1
THREAD_FUTEX_ADDR_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1
THREAD_FUTEX_VAL_WITNESS_OFFSET = THREAD_FUTEX_ADDR_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET = THREAD_FUTEX_VAL_WITNESS_OFFSET + 4
THREAD_FUTEX_CPU_WITNESS_OFFSET = THREAD_FUTEX_TIMEOUT_STEP_WITNESS_OFFSET + 8
THREAD_REGISTERS_WITNESS_OFFSET = THREAD_FUTEX_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
THREAD_ID_STATE_WITNESS_OFFSET = 0
THREAD_EXIT_CODE_WITNESS_OFFSET = THREAD_ID_STATE_WITNESS_OFFSET + arch.WordSizeBytes
THREAD_EXITED_WITNESS_OFFSET = THREAD_EXIT_CODE_WITNESS_OFFSET + 1
THREAD_CPU_WITNESS_OFFSET = THREAD_EXITED_WITNESS_OFFSET + 1
THREAD_REGISTERS_WITNESS_OFFSET = THREAD_CPU_WITNESS_OFFSET + (4 * arch.WordSizeBytes)
// SERIALIZED_THREAD_SIZE is the size of a serialized ThreadState object
// 166 and 318 bytes for 32 and 64-bit respectively
// 150 and 298 bytes for 32 and 64-bit respectively
SERIALIZED_THREAD_SIZE = THREAD_REGISTERS_WITNESS_OFFSET + (32 * arch.WordSizeBytes)
// THREAD_WITNESS_SIZE is the size of a thread witness encoded in bytes.
......@@ -37,14 +33,11 @@ const (
var EmptyThreadsRoot common.Hash = common.HexToHash("0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5")
type ThreadState struct {
ThreadId Word `json:"threadId"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
FutexAddr Word `json:"futexAddr"`
FutexVal uint32 `json:"futexVal"`
FutexTimeoutStep uint64 `json:"futexTimeoutStep"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"`
ThreadId Word `json:"threadId"`
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Cpu mipsevm.CpuScalars `json:"cpu"`
Registers [32]Word `json:"registers"`
}
func CreateEmptyThread() *ThreadState {
......@@ -59,10 +52,7 @@ func CreateEmptyThread() *ThreadState {
LO: 0,
HI: 0,
},
FutexAddr: exec.FutexEmptyAddr,
FutexVal: 0,
FutexTimeoutStep: 0,
Registers: [32]Word{},
Registers: [32]Word{},
}
}
......@@ -72,9 +62,6 @@ func (t *ThreadState) serializeThread() []byte {
out = arch.ByteOrderWord.AppendWord(out, t.ThreadId)
out = append(out, t.ExitCode)
out = mipsevm.AppendBoolToWitness(out, t.Exited)
out = arch.ByteOrderWord.AppendWord(out, t.FutexAddr)
out = binary.BigEndian.AppendUint32(out, t.FutexVal)
out = binary.BigEndian.AppendUint64(out, t.FutexTimeoutStep)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.PC)
out = arch.ByteOrderWord.AppendWord(out, t.Cpu.NextPC)
......@@ -107,15 +94,6 @@ func (t *ThreadState) Deserialize(in io.Reader) error {
return err
}
t.Exited = exited != 0
if err := binary.Read(in, binary.BigEndian, &t.FutexAddr); err != nil {
return err
}
if err := binary.Read(in, binary.BigEndian, &t.FutexVal); err != nil {
return err
}
if err := binary.Read(in, binary.BigEndian, &t.FutexTimeoutStep); err != nil {
return err
}
if err := binary.Read(in, binary.BigEndian, &t.Cpu.PC); err != nil {
return err
}
......
......@@ -25,5 +25,4 @@ const (
RegSyscallParam1 = RegA0
RegSyscallParam2 = RegA1
RegSyscallParam3 = RegA2
RegSyscallParam4 = RegA3
)
......@@ -13,7 +13,7 @@ import (
type Word = arch.Word
func (m *InstrumentedState) handleSyscall() error {
syscallNum, a0, a1, a2, _ := exec.GetSyscallArgs(&m.state.Registers)
syscallNum, a0, a1, a2 := exec.GetSyscallArgs(&m.state.Registers)
v0 := Word(0)
v1 := Word(0)
......
......@@ -964,7 +964,7 @@ func TestEVM_HelloProgram(t *testing.T) {
break
}
insn := testutil.GetInstruction(state.GetMemory(), state.GetPC())
if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps
if i%100_000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}
......@@ -1012,7 +1012,7 @@ func TestEVM_ClaimProgram(t *testing.T) {
}
insn := testutil.GetInstruction(state.GetMemory(), state.GetPC())
if i%10_000 == 0 { // avoid spamming test logs, we are executing many steps
if i%1_000_000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.GetStep(), state.GetPC(), insn)
}
......@@ -1020,6 +1020,7 @@ func TestEVM_ClaimProgram(t *testing.T) {
require.NoError(t, err)
validator.ValidateEVM(t, stepWitness, curStep, goVm)
}
t.Logf("Completed in %d steps", state.GetStep())
require.True(t, state.GetExited(), "must complete program")
require.Equal(t, uint8(0), state.GetExitCode(), "exit with 0")
......
......@@ -27,7 +27,7 @@ func DetectVersion(path string) (StateVersion, error) {
}
switch ver {
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2:
case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3:
return ver, nil
default:
return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver)
......
......@@ -64,9 +64,9 @@ func TestDetectVersion_singleThreadedBinary(t *testing.T) {
}
func TestDetectVersion_multiThreadedBinary(t *testing.T) {
targetVersion := VersionMultiThreaded
targetVersion := VersionMultiThreaded_v2
if !arch.IsMips32 {
targetVersion = VersionMultiThreaded64_v2
targetVersion = VersionMultiThreaded64_v3
}
state, err := NewFromState(multithreaded.CreateEmptyState())
......
......@@ -19,12 +19,19 @@ type StateVersion uint8
const (
// VersionSingleThreaded is the version of the Cannon STF found in op-contracts/v1.6.0 - https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.6.0/packages/contracts-bedrock/src/cannon/MIPS.sol
VersionSingleThreaded StateVersion = iota
// VersionMultiThreaded is the original implementation of 32-bit multithreaded cannon, tagged at cannon/v1.3.0
VersionMultiThreaded
// VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall
// This is the latest 32-bit single-threaded vm
VersionSingleThreaded2
// VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0
VersionMultiThreaded64
// VersionMultiThreaded64_v2 includes an audit fix to ensure futex values are always 32-bit, tagged at cannon/v1.3.0
VersionMultiThreaded64_v2
// VersionMultiThreaded_v2 is the latest 32-bit multithreaded vm
VersionMultiThreaded_v2
// VersionMultiThreaded64_v3 is the latest 64-bit multithreaded vm
VersionMultiThreaded64_v3
)
var (
......@@ -33,7 +40,7 @@ var (
ErrUnsupportedMipsArch = errors.New("mips architecture is not supported")
)
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2}
var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3}
func LoadStateFromFile(path string) (*VersionedState, error) {
if !serialize.IsBinaryFile(path) {
......@@ -60,12 +67,12 @@ func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) {
case *multithreaded.State:
if arch.IsMips32 {
return &VersionedState{
Version: VersionMultiThreaded,
Version: VersionMultiThreaded_v2,
FPVMState: state,
}, nil
} else {
return &VersionedState{
Version: VersionMultiThreaded64_v2,
Version: VersionMultiThreaded64_v3,
FPVMState: state,
}, nil
}
......@@ -106,7 +113,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
}
s.FPVMState = state
return nil
case VersionMultiThreaded:
case VersionMultiThreaded_v2:
if !arch.IsMips32 {
return ErrUnsupportedMipsArch
}
......@@ -116,7 +123,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error {
}
s.FPVMState = state
return nil
case VersionMultiThreaded64_v2:
case VersionMultiThreaded64_v3:
if arch.IsMips32 {
return ErrUnsupportedMipsArch
}
......@@ -155,6 +162,10 @@ func (s StateVersion) String() string {
return "multithreaded64"
case VersionMultiThreaded64_v2:
return "multithreaded64-2"
case VersionMultiThreaded_v2:
return "multithreaded-2"
case VersionMultiThreaded64_v3:
return "multithreaded64-3"
default:
return "unknown"
}
......@@ -172,6 +183,10 @@ func ParseStateVersion(ver string) (StateVersion, error) {
return VersionMultiThreaded64, nil
case "multithreaded64-2":
return VersionMultiThreaded64_v2, nil
case "multithreaded-2":
return VersionMultiThreaded_v2, nil
case "multithreaded64-3":
return VersionMultiThreaded64_v3, nil
default:
return StateVersion(0), errors.New("unknown state version")
}
......
......@@ -15,16 +15,16 @@ import (
)
func TestNewFromState(t *testing.T) {
t.Run("multithreaded64-2", func(t *testing.T) {
t.Run("multithreaded64-latestVersion", func(t *testing.T) {
actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState)
require.Equal(t, VersionMultiThreaded64_v2, actual.Version)
require.Equal(t, VersionMultiThreaded64_v3, actual.Version)
})
}
func TestLoadStateFromFile(t *testing.T) {
t.Run("Multithreaded64_v2_FromBinary", func(t *testing.T) {
t.Run("Multithreaded64_latestVersion_FromBinary", func(t *testing.T) {
expected, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
......@@ -40,7 +40,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
version StateVersion
createState func() mipsevm.FPVMState
}{
{VersionMultiThreaded64_v2, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
{VersionMultiThreaded64_v3, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
}
for _, test := range tests {
test := test
......
......@@ -16,18 +16,18 @@ import (
)
func TestNewFromState(t *testing.T) {
t.Run("singlethreaded-2", func(t *testing.T) {
t.Run("singlethreaded-latestVersion", func(t *testing.T) {
actual, err := NewFromState(singlethreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &singlethreaded.State{}, actual.FPVMState)
require.Equal(t, VersionSingleThreaded2, actual.Version)
})
t.Run("multithreaded", func(t *testing.T) {
t.Run("multithreaded-latestVersion", func(t *testing.T) {
actual, err := NewFromState(multithreaded.CreateEmptyState())
require.NoError(t, err)
require.IsType(t, &multithreaded.State{}, actual.FPVMState)
require.Equal(t, VersionMultiThreaded, actual.Version)
require.Equal(t, VersionMultiThreaded_v2, actual.Version)
})
}
......@@ -59,7 +59,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
createState func() mipsevm.FPVMState
}{
{VersionSingleThreaded2, func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }},
{VersionMultiThreaded, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
{VersionMultiThreaded_v2, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }},
}
for _, test := range tests {
test := test
......@@ -75,6 +75,16 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) {
}
}
func TestParseStateVersion(t *testing.T) {
for _, version := range StateVersionTypes {
t.Run(version.String(), func(t *testing.T) {
result, err := ParseStateVersion(version.String())
require.NoError(t, err)
require.Equal(t, version, result)
})
}
}
func writeToFile(t *testing.T, filename string, data serialize.Serializable) string {
dir := t.TempDir()
path := filepath.Join(dir, filename)
......
......@@ -198,7 +198,6 @@ func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64
e.metrics.RecordMaxStepsBetweenLLAndSC(uint64(info.MaxStepsBetweenLLAndSC))
e.metrics.RecordReservationInvalidationCount(uint64(info.ReservationInvalidationCount))
e.metrics.RecordForcedPreemptionCount(uint64(info.ForcedPreemptionCount))
e.metrics.RecordFailedWakeupCount(uint64(info.FailedWakeupCount))
e.metrics.RecordIdleStepCountThread0(uint64(info.IdleStepCountThread0))
}
}
......@@ -214,6 +213,5 @@ type debugInfo struct {
MaxStepsBetweenLLAndSC uint64 `json:"max_steps_between_ll_and_sc"`
ReservationInvalidationCount uint64 `json:"reservation_invalidation_count"`
ForcedPreemptionCount uint64 `json:"forced_preemption_count"`
FailedWakeupCount uint64 `json:"failed_wakeup_count"`
IdleStepCountThread0 uint64 `json:"idle_step_count_thread_0"`
}
......@@ -53,7 +53,6 @@ func TestGenerateProof(t *testing.T) {
MaxStepsBetweenLLAndSC: 56,
ReservationInvalidationCount: 78,
ForcedPreemptionCount: 910,
FailedWakeupCount: 1112,
IdleStepCountThread0: 1314,
}
......@@ -191,7 +190,6 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev
require.Equal(t, expected.MaxStepsBetweenLLAndSC, m.maxStepsBetweenLLAndSC)
require.Equal(t, expected.ReservationInvalidationCount, m.reservationInvalidations)
require.Equal(t, expected.ForcedPreemptionCount, m.forcedPreemptions)
require.Equal(t, expected.FailedWakeupCount, m.failedWakeup)
require.Equal(t, expected.IdleStepCountThread0, m.idleStepsThread0)
} else {
// If debugInfo is disabled, json file should not be written and metrics should be zeroed out
......@@ -202,7 +200,6 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev
require.Equal(t, uint64(0), m.maxStepsBetweenLLAndSC)
require.Equal(t, uint64(0), m.reservationInvalidations)
require.Equal(t, uint64(0), m.forcedPreemptions)
require.Equal(t, uint64(0), m.failedWakeup)
require.Equal(t, uint64(0), m.idleStepsThread0)
}
}
......@@ -220,7 +217,6 @@ type capturingVmMetrics struct {
maxStepsBetweenLLAndSC uint64
reservationInvalidations uint64
forcedPreemptions uint64
failedWakeup uint64
idleStepsThread0 uint64
}
......@@ -256,10 +252,6 @@ func (c *capturingVmMetrics) RecordForcedPreemptionCount(val uint64) {
c.forcedPreemptions = val
}
func (c *capturingVmMetrics) RecordFailedWakeupCount(val uint64) {
c.failedWakeup = val
}
func (c *capturingVmMetrics) RecordIdleStepCountThread0(val uint64) {
c.idleStepsThread0 = val
}
......
......@@ -17,7 +17,6 @@ type VmMetricer interface {
RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64)
RecordVmReservationInvalidationCount(vmType string, val uint64)
RecordVmForcedPreemptionCount(vmType string, val uint64)
RecordVmFailedWakeupCount(vmType string, val uint64)
RecordVmIdleStepCountThread0(vmType string, val uint64)
}
......@@ -31,7 +30,6 @@ type TypedVmMetricer interface {
RecordMaxStepsBetweenLLAndSC(val uint64)
RecordReservationInvalidationCount(val uint64)
RecordForcedPreemptionCount(val uint64)
RecordFailedWakeupCount(val uint64)
RecordIdleStepCountThread0(val uint64)
}
......@@ -44,7 +42,6 @@ type VmMetrics struct {
vmMaxStepsBetweenLLAndSC *prometheus.GaugeVec
vmReservationInvalidations *prometheus.GaugeVec
vmForcedPreemptions *prometheus.GaugeVec
vmFailedWakeup *prometheus.GaugeVec
vmIdleStepsThread0 *prometheus.GaugeVec
}
......@@ -82,10 +79,6 @@ func (m *VmMetrics) RecordVmForcedPreemptionCount(vmType string, val uint64) {
m.vmForcedPreemptions.WithLabelValues(vmType).Set(float64(val))
}
func (m *VmMetrics) RecordVmFailedWakeupCount(vmType string, val uint64) {
m.vmFailedWakeup.WithLabelValues(vmType).Set(float64(val))
}
func (m *VmMetrics) RecordVmIdleStepCountThread0(vmType string, val uint64) {
m.vmIdleStepsThread0.WithLabelValues(vmType).Set(float64(val))
}
......@@ -137,11 +130,6 @@ func NewVmMetrics(namespace string, factory metrics.Factory) *VmMetrics {
Name: "vm_forced_preemptions",
Help: "Number of forced preemptions during vm run",
}, []string{"vm"}),
vmFailedWakeup: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "vm_failed_wakeup",
Help: "Number of failed wakesups during vm run",
}, []string{"vm"}),
vmIdleStepsThread0: factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Name: "vm_idle_steps_thread0",
......@@ -162,7 +150,6 @@ func (n NoopVmMetrics) RecordVmRmwFailCount(vmType string, val uint64)
func (n NoopVmMetrics) RecordVmMaxStepsBetweenLLAndSC(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmReservationInvalidationCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmForcedPreemptionCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmFailedWakeupCount(vmType string, val uint64) {}
func (n NoopVmMetrics) RecordVmIdleStepCountThread0(vmType string, val uint64) {}
type typedVmMetricsImpl struct {
......@@ -204,10 +191,6 @@ func (m *typedVmMetricsImpl) RecordForcedPreemptionCount(val uint64) {
m.m.RecordVmForcedPreemptionCount(m.vmType, val)
}
func (m *typedVmMetricsImpl) RecordFailedWakeupCount(val uint64) {
m.m.RecordVmFailedWakeupCount(m.vmType, val)
}
func (m *typedVmMetricsImpl) RecordIdleStepCountThread0(val uint64) {
m.m.RecordVmIdleStepCountThread0(m.vmType, val)
}
......
......@@ -34,9 +34,9 @@ RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-pro
# Run the op-program-client.elf binary directly through cannon's load-elf subcommand.
RUN /app/cannon/bin/cannon load-elf --type singlethreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.bin.gz --meta "/app/op-program/bin/meta.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "/app/op-program/bin/meta-mt.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-2 --path /app/op-program/bin/op-program-client64.elf --out /app/op-program/bin/prestate-mt64.bin.gz --meta "/app/op-program/bin/meta-mt64.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-2 --path /app/op-program/bin/op-program-client-interop.elf --out /app/op-program/bin/prestate-interop.bin.gz --meta "/app/op-program/bin/meta-interop.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate-mt.bin.gz --meta "/app/op-program/bin/meta-mt.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-3 --path /app/op-program/bin/op-program-client64.elf --out /app/op-program/bin/prestate-mt64.bin.gz --meta "/app/op-program/bin/meta-mt64.json"
RUN /app/cannon/bin/cannon load-elf --type multithreaded64-3 --path /app/op-program/bin/op-program-client-interop.elf --out /app/op-program/bin/prestate-interop.bin.gz --meta "/app/op-program/bin/meta-interop.json"
# Generate the prestate proof containing the absolute pre-state hash.
RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output ""
......
......@@ -65,12 +65,15 @@ ARG TARGETARCH
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.0.0 AS cannon-builder-v1-0-0
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.2.0 AS cannon-builder-v1-2-0
FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon:v1.3.0 AS cannon-builder-v1-3-0
FROM --platform=$BUILDPLATFORM builder AS cannon-builder
ARG CANNON_VERSION=v0.0.0
# Copy cannon binaries from previous versions
COPY --from=cannon-builder-v1-0-0 /usr/local/bin/cannon ./cannon/multicannon/embeds/cannon-0
COPY --from=cannon-builder-v1-2-0 /usr/local/bin/cannon-3 ./cannon/multicannon/embeds/cannon-3
COPY --from=cannon-builder-v1-3-0 /usr/local/bin/cannon-1 ./cannon/multicannon/embeds/cannon-1
COPY --from=cannon-builder-v1-3-0 /usr/local/bin/cannon-4 ./cannon/multicannon/embeds/cannon-4
# Build current binaries
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \
GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION"
......
......@@ -11,9 +11,6 @@ interface IMIPS2 is ISemver {
uint32 threadID;
uint8 exitCode;
bool exited;
uint32 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint32 pc;
uint32 nextPC;
uint32 lo;
......@@ -33,7 +30,6 @@ interface IMIPS2 is ISemver {
bool exited;
uint64 step;
uint64 stepsSinceLastContextSwitch;
uint32 wakeup;
bool traverseRight;
bytes32 leftThreadStack;
bytes32 rightThreadStack;
......
......@@ -140,16 +140,16 @@
"sourceCodeHash": "0xe9964aa66db1dfc86772958b4c9276697e67f7055529a43e6a49a055009bc995"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0xc10654f0e6498f424f7a5095bac36005dc7062d3813cc8f805a15005fc37406b",
"sourceCodeHash": "0x6c45dd23cb0d6f9bf4f84855ad0caf70e53dee3fe6c41454f7bf8df52ec3a9af"
"initCodeHash": "0x7d0fc7c7b51b74fa2611aaa8cc1a5967e2e48f0726ea894eb2c43f36b0ff2ab7",
"sourceCodeHash": "0xc18f51210c9d75f9bc33e55f879e8f0ab2514924718022264c0a2993134821e0"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0x70ecbb2327fadd6205312aa93279a6340242dfeb44a00b98caa7b56688c46bdc",
"sourceCodeHash": "0x84506539c40b72b1d40c9d07418650b30c27d2b219b40e55f61edcd31365157f"
"initCodeHash": "0x4e1dbd0a6ac84873622af9234aca42e6e7b2bfda1186bbfd3ff83081f141ef86",
"sourceCodeHash": "0x62c820b22c72399efd7688dcf713c34a6ee6821835ec66d5e7b98f33bbbfb209"
},
"src/cannon/MIPS64.sol": {
"initCodeHash": "0xa2a42c50d2fac71d93e44ad4871e5d838f1c630b9d1abc4c89971d36b0ae44bb",
"sourceCodeHash": "0xdb771f1b92c7612b120e0bce31967f0c8a7ce332dbb426bc9cfc52b47be21c4d"
"initCodeHash": "0xd15808bd3a9f0779dfc662dd75fb11fe5f2ff15c3a6e9f699d05dad49e064afb",
"sourceCodeHash": "0x84609ac875a282e8a0675bcd8558635dcd7d054cc23395691d87922a15a815ba"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0x17d3b3df1aaaf7a705b8d48de8a05e6511b910fdafdbe5eb7f7f95ec944fba9a",
......
......@@ -47,8 +47,8 @@ contract MIPS is ISemver {
}
/// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.2.1-beta.10
string public constant version = "1.2.1-beta.10";
/// @custom:semver 1.3.0-beta.1
string public constant version = "1.3.0-beta.1";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
......@@ -151,7 +151,7 @@ contract MIPS is ISemver {
}
// Load the syscall numbers and args from the registers
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2,) = sys.getSyscallArgs(state.registers);
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2) = sys.getSyscallArgs(state.registers);
uint32 v0 = 0;
uint32 v1 = 0;
......
......@@ -119,9 +119,6 @@ library MIPS64Syscalls {
uint64 internal constant FUTEX_WAIT_PRIVATE = 128;
uint64 internal constant FUTEX_WAKE_PRIVATE = 129;
uint64 internal constant FUTEX_TIMEOUT_STEPS = 10000;
uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max;
uint64 internal constant FUTEX_EMPTY_ADDR = U64_MASK;
uint64 internal constant SCHED_QUANTUM = 100_000;
uint64 internal constant HZ = 10_000_000;
......@@ -168,7 +165,6 @@ library MIPS64Syscalls {
uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0;
uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1;
uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2;
uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3;
// Constants copied from MIPS64Arch for use in Yul
uint64 internal constant WORD_SIZE_BYTES = 8;
......@@ -180,11 +176,10 @@ library MIPS64Syscalls {
/// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second argument available to the syscall operation.
/// @return a2_ The third argument available to the syscall operation.
/// @return a3_ The fourth argument available to the syscall operation.
function getSyscallArgs(uint64[32] memory _registers)
internal
pure
returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_, uint64 a3_)
returns (uint64 sysCallNum_, uint64 a0_, uint64 a1_, uint64 a2_)
{
unchecked {
sysCallNum_ = _registers[REG_SYSCALL_NUM];
......@@ -192,9 +187,8 @@ library MIPS64Syscalls {
a0_ = _registers[REG_SYSCALL_PARAM1];
a1_ = _registers[REG_SYSCALL_PARAM2];
a2_ = _registers[REG_SYSCALL_PARAM3];
a3_ = _registers[REG_SYSCALL_PARAM4];
return (sysCallNum_, a0_, a1_, a2_, a3_);
return (sysCallNum_, a0_, a1_, a2_);
}
}
......
......@@ -115,9 +115,6 @@ library MIPSSyscalls {
uint32 internal constant FUTEX_WAIT_PRIVATE = 128;
uint32 internal constant FUTEX_WAKE_PRIVATE = 129;
uint32 internal constant FUTEX_TIMEOUT_STEPS = 10000;
uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max;
uint32 internal constant FUTEX_EMPTY_ADDR = 0xFF_FF_FF_FF;
uint32 internal constant SCHED_QUANTUM = 100_000;
uint32 internal constant HZ = 10_000_000;
......@@ -165,7 +162,6 @@ library MIPSSyscalls {
uint32 internal constant REG_SYSCALL_PARAM1 = REG_A0;
uint32 internal constant REG_SYSCALL_PARAM2 = REG_A1;
uint32 internal constant REG_SYSCALL_PARAM3 = REG_A2;
uint32 internal constant REG_SYSCALL_PARAM4 = REG_A3;
/// @notice Extract syscall num and arguments from registers.
/// @param _registers The cpu registers.
......@@ -173,11 +169,10 @@ library MIPSSyscalls {
/// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second argument available to the syscall operation.
/// @return a2_ The third argument available to the syscall operation.
/// @return a3_ The fourth argument available to the syscall operation.
function getSyscallArgs(uint32[32] memory _registers)
internal
pure
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_, uint32 a3_)
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_)
{
unchecked {
sysCallNum_ = _registers[REG_SYSCALL_NUM];
......@@ -185,9 +180,8 @@ library MIPSSyscalls {
a0_ = _registers[REG_SYSCALL_PARAM1];
a1_ = _registers[REG_SYSCALL_PARAM2];
a2_ = _registers[REG_SYSCALL_PARAM3];
a3_ = _registers[REG_SYSCALL_PARAM4];
return (sysCallNum_, a0_, a1_, a2_, a3_);
return (sysCallNum_, a0_, a1_, a2_);
}
}
......
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