Skip to content

Commit

Permalink
mt-cannon: Implement mips logic (ethereum-optimism#11188)
Browse files Browse the repository at this point in the history
* cannon: Copy over singlethreaded impls as a starting point

* cannon: Update mips property access to work with MTState

* cannon: Add new syscall constants

* mt-cannon: Implement clone syscall

* mt-cannon: Implement remaining new syscalls

* mt-cannon: Implement thread traversal changes to mipsStep()

* mt-cannon: Add logger, log when max steps reached

* mt-cannon: Implement onWaitComplete()

* mt-cannon: Implement thread manipulation methods

Also, use slices of pointers for the thread stacks

* mt-cannon: Move thread traversal fns to mips.go

* mt-cannon: Fix issue where wakeup traversal never stops

* mt-cannon: Fix issue where we can end up popping an empty stack

* mt-cannon: Move thread definitions to new thread.go file

* cannon: Add compile-time type checks for FPVM(State) impls

* mt-cannon: Add new threaded StackTracker

* mt-cannon: Update proof generation to include thread proof

* mt-cannon: Move FPVM compile-time type check

* cannon: Run common vm tests across all FPVM impls

* cannon: Cut OpenMIPS clone test

* cannon: Cleanup - fix some discrepancies, clarify constant

* cannon: Disable mem profiling in op-program instead of patch.go

* cannon: Consolidate calls to program.PatchGo

* cannon: Disable program.PatchGo in MTCannon tests

* mt-cannon: Add multithreaded program test

* cannon: Only run sleep check for single-threaded cannon

* op-program: Update profiling before dependency init fns are called

* mt-cannon: Track stack on thread clone, handled popped threads

* mt-cannon: Panic if unrecognized syscall is executed

* mt-cannon: Panic if unexpected flags are passed to SysClone

* mt-cannon: Add some tests for EncodeThreadProof()

* mt-cannon: Add some more tests around threadProof edge cases

* mt-cannon: Minimize logging

* cannon: Update go version in cannon/example/multithreaded/go.mod

Co-authored-by: Inphi <[email protected]>

* mt-cannon: Rework clone behavior based on feedback

* mt-cannon: Rework wakeup logic

* mt-cannon: Cleanup - simplify clone, refine logging

* Revert "cannon: Cut OpenMIPS clone test"

This reverts commit d876d6a.

* mt-cannon: Skip open-mips clone test add todos

* mt-cannon: Handle munmap syscall

* mt-cannon: Exit if the last thread exits

* cannon: Clarify skip comment

* cannon: Add some todos

* cannon: Add guard around logging

---------

Co-authored-by: Inphi <[email protected]>
  • Loading branch information
mbaxter and Inphi authored Jul 30, 2024
1 parent beb5d87 commit 05a9c47
Show file tree
Hide file tree
Showing 27 changed files with 1,268 additions and 293 deletions.
12 changes: 5 additions & 7 deletions cannon/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func Run(ctx *cli.Context) error {
var vm mipsevm.FPVM
var debugProgram bool
if vmType == cannonVMType {
cannon, err := singlethreaded.NewInstrumentedStateFromFile(ctx.Path(RunInputFlag.Name), po, outLog, errLog)
cannon, err := singlethreaded.NewInstrumentedStateFromFile(ctx.Path(RunInputFlag.Name), po, outLog, errLog, meta)
if err != nil {
return err
}
Expand All @@ -375,7 +375,7 @@ func Run(ctx *cli.Context) error {
if metaPath := ctx.Path(RunMetaFlag.Name); metaPath == "" {
return fmt.Errorf("cannot enable debug mode without a metadata file")
}
if err := cannon.InitDebug(meta); err != nil {
if err := cannon.InitDebug(); err != nil {
return fmt.Errorf("failed to initialize debug mode: %w", err)
}
}
Expand All @@ -397,9 +397,6 @@ func Run(ctx *cli.Context) error {
state := vm.GetState()
startStep := state.GetStep()

// avoid symbol lookups every instruction by preparing a matcher func
sleepCheck := meta.SymbolMatcher("runtime.notesleep")

for !state.GetExited() {
step := state.GetStep()
if step%100 == 0 { // don't do the ctx err check (includes lock) too often
Expand All @@ -421,8 +418,9 @@ func Run(ctx *cli.Context) error {
)
}

if sleepCheck(state.GetPC()) { // don't loop forever when we get stuck because of an unexpected bad program
return fmt.Errorf("got stuck in Go sleep at step %d", step)
if vm.CheckInfiniteLoop() {
// don't loop forever when we get stuck because of an unexpected bad program
return fmt.Errorf("detected an infinite loop at step %d", step)
}

if stopAt(state) {
Expand Down
3 changes: 3 additions & 0 deletions cannon/example/multithreaded/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module multithreaded

go 1.21
46 changes: 46 additions & 0 deletions cannon/example/multithreaded/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"fmt"
"os"
"runtime"
"sync"
"sync/atomic"
)

func main() {
// try some concurrency!
var wg sync.WaitGroup
wg.Add(2)
var x atomic.Int32
go func() {
x.Add(2)
wg.Done()
}()
go func() {
x.Add(40)
wg.Done()
}()
wg.Wait()
fmt.Printf("waitgroup result: %d\n", x.Load())

// channels
a := make(chan int, 1)
b := make(chan int)
c := make(chan int)
go func() {
t0 := <-a
b <- t0
}()
go func() {
t1 := <-b
c <- t1
}()
a <- 1234
out := <-c
fmt.Printf("channels result: %d\n", out)

// try a GC! (the runtime might not have run one yet)
runtime.GC()
_, _ = os.Stdout.Write([]byte("GC complete!\n"))
}
2 changes: 1 addition & 1 deletion cannon/mipsevm/exec/mips_instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]uint32, memor
}
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
stackTracker.PushStack(target)
stackTracker.PushStack(cpu.PC, target)
return HandleJump(cpu, registers, linkReg, target)
}

Expand Down
122 changes: 111 additions & 11 deletions cannon/mipsevm/exec/mips_syscalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,65 @@ import (
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)

// Syscall codes
const (
SysMmap = 4090
SysBrk = 4045
SysClone = 4120
SysExitGroup = 4246
SysRead = 4003
SysWrite = 4004
SysFcntl = 4055
SysMmap = 4090
SysMunmap = 4091
SysBrk = 4045
SysClone = 4120
SysExitGroup = 4246
SysRead = 4003
SysWrite = 4004
SysFcntl = 4055
SysExit = 4001
SysSchedYield = 4162
SysGetTID = 4222
SysFutex = 4238
SysOpen = 4005
SysNanosleep = 4166
)

// Noop Syscall codes
const (
SysGetAffinity = 4240
SysMadvise = 4218
SysRtSigprocmask = 4195
SysSigaltstack = 4206
SysRtSigaction = 4194
SysPrlimit64 = 4338
SysClose = 4006
SysPread64 = 4200
SysFstat64 = 4215
SysOpenAt = 4288
SysReadlink = 4085
SysReadlinkAt = 4298
SysIoctl = 4054
SysEpollCreate1 = 4326
SysPipe2 = 4328
SysEpollCtl = 4249
SysEpollPwait = 4313
SysGetRandom = 4353
SysUname = 4122
SysStat64 = 4213
SysGetuid = 4024
SysGetgid = 4047
SysLlseek = 4140
SysMinCore = 4217
SysTgkill = 4266
)

// Profiling-related syscalls
// Should be able to ignore if we patch out prometheus calls and disable memprofiling
// TODO(cp-903) - Update patching for mt-cannon so that these can be ignored
const (
SysSetITimer = 4104
SysTimerCreate = 4257
SysTimerSetTime = 4258
SysTimerDelete = 4261
SysClockGetTime = 4263
)

// File descriptors
const (
FdStdin = 0
FdStdout = 1
Expand All @@ -31,19 +80,70 @@ const (
FdPreimageWrite = 6
)

// Errors
const (
SysErrorSignal = ^uint32(0)
MipsEBADF = 0x9
MipsEINVAL = 0x16
MipsEAGAIN = 0xb
MipsETIMEDOUT = 0x91
)

// SysFutex-related constants
const (
FutexWaitPrivate = 128
FutexWakePrivate = 129
FutexTimeoutSteps = 10_000
FutexNoTimeout = ^uint64(0)
FutexEmptyAddr = ^uint32(0)
)

// SysClone flags
// Handling is meant to support go runtime use cases
// Pulled from: https://github.com/golang/go/blob/d8392e69973a64d96534d544d1f8ac2defc1bc64/src/runtime/os_linux.go#L124-L158
const (
CloneVm = 0x100
CloneFs = 0x200
CloneFiles = 0x400
CloneSighand = 0x800
ClonePtrace = 0x2000
CloneVfork = 0x4000
CloneParent = 0x8000
CloneThread = 0x10000
CloneNewns = 0x20000
CloneSysvsem = 0x40000
CloneSettls = 0x80000
CloneParentSettid = 0x100000
CloneChildCleartid = 0x200000
CloneUntraced = 0x800000
CloneChildSettid = 0x1000000
CloneStopped = 0x2000000
CloneNewuts = 0x4000000
CloneNewipc = 0x8000000

ValidCloneFlags = CloneVm |
CloneFs |
CloneFiles |
CloneSighand |
CloneSysvsem |
CloneThread
)

// Other constants
const (
MipsEBADF = 0x9
MipsEINVAL = 0x16
SchedQuantum = 100_000
BrkStart = 0x40000000
)

func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
func GetSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2, a3 uint32) {
syscallNum = registers[2] // v0

a0 = registers[4]
a1 = registers[5]
a2 = registers[6]
a3 = registers[7]

return syscallNum, a0, a1, a2
return syscallNum, a0, a1, a2, a3
}

func HandleSysMmap(a0, a1, heap uint32) (v0, v1, newHeap uint32) {
Expand Down
15 changes: 10 additions & 5 deletions cannon/mipsevm/exec/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

type StackTracker interface {
PushStack(target uint32)
PushStack(caller uint32, target uint32)
PopStack()
}

Expand All @@ -20,7 +20,7 @@ type TraceableStackTracker interface {

type NoopStackTracker struct{}

func (n *NoopStackTracker) PushStack(target uint32) {}
func (n *NoopStackTracker) PushStack(caller uint32, target uint32) {}

func (n *NoopStackTracker) PopStack() {}

Expand All @@ -38,12 +38,17 @@ func NewStackTracker(state mipsevm.FPVMState, meta *program.Metadata) (*StackTra
if meta == nil {
return nil, errors.New("metadata is nil")
}
return &StackTrackerImpl{state: state}, nil
return NewStackTrackerUnsafe(state, meta), nil
}

func (s *StackTrackerImpl) PushStack(target uint32) {
// NewStackTrackerUnsafe creates a new TraceableStackTracker without verifying meta is not nil
func NewStackTrackerUnsafe(state mipsevm.FPVMState, meta *program.Metadata) *StackTrackerImpl {
return &StackTrackerImpl{state: state, meta: meta}
}

func (s *StackTrackerImpl) PushStack(caller uint32, target uint32) {
s.caller = append(s.caller, caller)
s.stack = append(s.stack, target)
s.caller = append(s.caller, s.state.GetPC())
}

func (s *StackTrackerImpl) PopStack() {
Expand Down
3 changes: 3 additions & 0 deletions cannon/mipsevm/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type FPVM interface {
// Step executes a single instruction and returns the witness for the step
Step(includeProof bool) (*StepWitness, error)

// CheckInfiniteLoop returns true if the vm is stuck in an infinite loop
CheckInfiniteLoop() bool

// LastPreimage returns the last preimage accessed by the VM
LastPreimage() (preimageKey [32]byte, preimage []byte, preimageOffset uint32)

Expand Down
Loading

0 comments on commit 05a9c47

Please sign in to comment.