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

cannon: Extract handleBranch (#10822)

* cannon: Extract handleBranch, outputState

* cannon: Define MIPSInstructions.sol as a library so we can set visibility modifiers

* cannon: Flatten cpu fields in sol files

* cannon: Rework sol cpu field handling

* cannon: Update MIPS contract version

* cannon: Run snapshots, semver lock

* cannon: Update go logic to match sol

* cannon: Move outputState() back into MIPS.sol

* cannon: Mark pure functions

* cannon: Run semver-lock

* cannon: Update state.json format in test_data

* cannon: Fix variable names to match style guide

* cannon: Undo comment formatting change

* cannon: Style fix - use named parameters

* cannon: Run semver-lock
parent aff6e893
...@@ -380,16 +380,16 @@ func Run(ctx *cli.Context) error { ...@@ -380,16 +380,16 @@ func Run(ctx *cli.Context) error {
delta := time.Since(start) delta := time.Since(start)
l.Info("processing", l.Info("processing",
"step", step, "step", step,
"pc", mipsevm.HexU32(state.PC), "pc", mipsevm.HexU32(state.Cpu.PC),
"insn", mipsevm.HexU32(state.Memory.GetMemory(state.PC)), "insn", mipsevm.HexU32(state.Memory.GetMemory(state.Cpu.PC)),
"ips", float64(step-startStep)/(float64(delta)/float64(time.Second)), "ips", float64(step-startStep)/(float64(delta)/float64(time.Second)),
"pages", state.Memory.PageCount(), "pages", state.Memory.PageCount(),
"mem", state.Memory.Usage(), "mem", state.Memory.Usage(),
"name", meta.LookupSymbol(state.PC), "name", meta.LookupSymbol(state.Cpu.PC),
) )
} }
if sleepCheck(state.PC) { // don't loop forever when we get stuck because of an unexpected bad program if sleepCheck(state.Cpu.PC) { // 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) return fmt.Errorf("got stuck in Go sleep at step %d", step)
} }
...@@ -411,7 +411,7 @@ func Run(ctx *cli.Context) error { ...@@ -411,7 +411,7 @@ func Run(ctx *cli.Context) error {
} }
witness, err := stepFn(true) witness, err := stepFn(true)
if err != nil { if err != nil {
return fmt.Errorf("failed at proof-gen step %d (PC: %08x): %w", step, state.PC, err) return fmt.Errorf("failed at proof-gen step %d (PC: %08x): %w", step, state.Cpu.PC, err)
} }
postStateHash, err := state.EncodeWitness().StateHash() postStateHash, err := state.EncodeWitness().StateHash()
if err != nil { if err != nil {
...@@ -435,7 +435,7 @@ func Run(ctx *cli.Context) error { ...@@ -435,7 +435,7 @@ func Run(ctx *cli.Context) error {
} else { } else {
_, err = stepFn(false) _, err = stepFn(false)
if err != nil { if err != nil {
return fmt.Errorf("failed at step %d (PC: %08x): %w", step, state.PC, err) return fmt.Errorf("failed at step %d (PC: %08x): %w", step, state.Cpu.PC, err)
} }
} }
......
...@@ -172,7 +172,7 @@ func TestEVM(t *testing.T) { ...@@ -172,7 +172,7 @@ func TestEVM(t *testing.T) {
fn := path.Join("open_mips_tests/test/bin", f.Name()) fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn) programMem, err := os.ReadFile(fn)
require.NoError(t, err) require.NoError(t, err)
state := &State{PC: 0, NextPC: 4, Memory: NewMemory()} state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem)) err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state") require.NoError(t, err, "load program into state")
...@@ -182,14 +182,14 @@ func TestEVM(t *testing.T) { ...@@ -182,14 +182,14 @@ func TestEVM(t *testing.T) {
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr) goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
if goState.state.PC == endAddr { if goState.state.Cpu.PC == endAddr {
break break
} }
if exitGroup && goState.state.Exited { if exitGroup && goState.state.Exited {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.Cpu.PC)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)
stepWitness, err := goState.Step(true) stepWitness, err := goState.Step(true)
require.NoError(t, err) require.NoError(t, err)
...@@ -201,11 +201,11 @@ func TestEVM(t *testing.T) { ...@@ -201,11 +201,11 @@ func TestEVM(t *testing.T) {
"mipsevm produced different state than EVM at step %d", state.Step) "mipsevm produced different state than EVM at step %d", state.Step)
} }
if exitGroup { if exitGroup {
require.NotEqual(t, uint32(endAddr), goState.state.PC, "must not reach end") require.NotEqual(t, uint32(endAddr), goState.state.Cpu.PC, "must not reach end")
require.True(t, goState.state.Exited, "must set exited state") require.True(t, goState.state.Exited, "must set exited state")
require.Equal(t, uint8(1), goState.state.ExitCode, "must exit with 1") require.Equal(t, uint8(1), goState.state.ExitCode, "must exit with 1")
} else { } else {
require.Equal(t, uint32(endAddr), state.PC, "must reach end") require.Equal(t, uint32(endAddr), state.Cpu.PC, "must reach end")
// inspect test result // inspect test result
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8) done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
require.Equal(t, done, uint32(1), "must be done") require.Equal(t, done, uint32(1), "must be done")
...@@ -233,7 +233,7 @@ func TestEVMSingleStep(t *testing.T) { ...@@ -233,7 +233,7 @@ func TestEVMSingleStep(t *testing.T) {
for _, tt := range cases { for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
state := &State{PC: tt.pc, NextPC: tt.nextPC, Memory: NewMemory()} state := &State{Cpu: CpuScalars{PC: tt.pc, NextPC: tt.nextPC}, Memory: NewMemory()}
state.Memory.SetMemory(tt.pc, tt.insn) state.Memory.SetMemory(tt.pc, tt.insn)
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr) us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
...@@ -401,7 +401,7 @@ func TestEVMSysWriteHint(t *testing.T) { ...@@ -401,7 +401,7 @@ func TestEVMSysWriteHint(t *testing.T) {
for _, tt := range cases { for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
oracle := hintTrackingOracle{} oracle := hintTrackingOracle{}
state := &State{PC: 0, NextPC: 4, Memory: NewMemory()} state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}
state.LastHint = tt.lastHint state.LastHint = tt.lastHint
state.Registers[2] = sysWrite state.Registers[2] = sysWrite
...@@ -448,8 +448,8 @@ func TestEVMFault(t *testing.T) { ...@@ -448,8 +448,8 @@ func TestEVMFault(t *testing.T) {
for _, tt := range cases { for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
state := &State{PC: 0, NextPC: tt.nextPC, Memory: NewMemory()} state := &State{Cpu: CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: NewMemory()}
initialState := &State{PC: 0, NextPC: tt.nextPC, Memory: state.Memory} initialState := &State{Cpu: CpuScalars{PC: 0, NextPC: tt.nextPC}, Memory: state.Memory}
state.Memory.SetMemory(0, tt.insn) state.Memory.SetMemory(0, tt.insn)
// set the return address ($ra) to jump into when test completes // set the return address ($ra) to jump into when test completes
...@@ -496,9 +496,9 @@ func TestHelloEVM(t *testing.T) { ...@@ -496,9 +496,9 @@ func TestHelloEVM(t *testing.T) {
if goState.state.Exited { if goState.state.Exited {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.Cpu.PC)
if i%1000 == 0 { // avoid spamming test logs, we are executing many steps if i%1000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)
} }
evm := NewMIPSEVM(contracts, addrs) evm := NewMIPSEVM(contracts, addrs)
...@@ -548,9 +548,9 @@ func TestClaimEVM(t *testing.T) { ...@@ -548,9 +548,9 @@ func TestClaimEVM(t *testing.T) {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.Cpu.PC)
if i%1000 == 0 { // avoid spamming test logs, we are executing many steps if i%1000 == 0 { // avoid spamming test logs, we are executing many steps
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.Cpu.PC, insn)
} }
stepWitness, err := goState.Step(true) stepWitness, err := goState.Step(true)
......
...@@ -21,10 +21,12 @@ func FuzzStateSyscallBrk(f *testing.F) { ...@@ -21,10 +21,12 @@ func FuzzStateSyscallBrk(f *testing.F) {
pc = pc & 0xFF_FF_FF_FC // align PC pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4 nextPC := pc + 4
state := &State{ state := &State{
Cpu: CpuScalars{
PC: pc, PC: pc,
NextPC: nextPC, NextPC: nextPC,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -44,10 +46,10 @@ func FuzzStateSyscallBrk(f *testing.F) { ...@@ -44,10 +46,10 @@ func FuzzStateSyscallBrk(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.PC) require.Equal(t, pc+4, state.Cpu.PC)
require.Equal(t, nextPC+4, state.NextPC) require.Equal(t, nextPC+4, state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
...@@ -71,10 +73,12 @@ func FuzzStateSyscallClone(f *testing.F) { ...@@ -71,10 +73,12 @@ func FuzzStateSyscallClone(f *testing.F) {
pc = pc & 0xFF_FF_FF_FC // align PC pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4 nextPC := pc + 4
state := &State{ state := &State{
Cpu: CpuScalars{
PC: pc, PC: pc,
NextPC: nextPC, NextPC: nextPC,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -93,10 +97,10 @@ func FuzzStateSyscallClone(f *testing.F) { ...@@ -93,10 +97,10 @@ func FuzzStateSyscallClone(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.PC) require.Equal(t, pc+4, state.Cpu.PC)
require.Equal(t, nextPC+4, state.NextPC) require.Equal(t, nextPC+4, state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
...@@ -118,10 +122,12 @@ func FuzzStateSyscallMmap(f *testing.F) { ...@@ -118,10 +122,12 @@ func FuzzStateSyscallMmap(f *testing.F) {
contracts, addrs := testContractsSetup(f) contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) { f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) {
state := &State{ state := &State{
Cpu: CpuScalars{
PC: 0, PC: 0,
NextPC: 4, NextPC: 4,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: heap, Heap: heap,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -139,10 +145,10 @@ func FuzzStateSyscallMmap(f *testing.F) { ...@@ -139,10 +145,10 @@ func FuzzStateSyscallMmap(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC) require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.NextPC) require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
...@@ -179,10 +185,12 @@ func FuzzStateSyscallExitGroup(f *testing.F) { ...@@ -179,10 +185,12 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
pc = pc & 0xFF_FF_FF_FC // align PC pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4 nextPC := pc + 4
state := &State{ state := &State{
Cpu: CpuScalars{
PC: pc, PC: pc,
NextPC: nextPC, NextPC: nextPC,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -200,10 +208,10 @@ func FuzzStateSyscallExitGroup(f *testing.F) { ...@@ -200,10 +208,10 @@ func FuzzStateSyscallExitGroup(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc, state.PC) require.Equal(t, pc, state.Cpu.PC)
require.Equal(t, nextPC, state.NextPC) require.Equal(t, nextPC, state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(exitCode), state.ExitCode) require.Equal(t, uint8(exitCode), state.ExitCode)
require.Equal(t, true, state.Exited) require.Equal(t, true, state.Exited)
...@@ -225,10 +233,12 @@ func FuzzStateSyscallFnctl(f *testing.F) { ...@@ -225,10 +233,12 @@ func FuzzStateSyscallFnctl(f *testing.F) {
contracts, addrs := testContractsSetup(f) contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) { f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) {
state := &State{ state := &State{
Cpu: CpuScalars{
PC: 0, PC: 0,
NextPC: 4, NextPC: 4,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -246,10 +256,10 @@ func FuzzStateSyscallFnctl(f *testing.F) { ...@@ -246,10 +256,10 @@ func FuzzStateSyscallFnctl(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC) require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.NextPC) require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
...@@ -289,10 +299,12 @@ func FuzzStateHintRead(f *testing.F) { ...@@ -289,10 +299,12 @@ func FuzzStateHintRead(f *testing.F) {
f.Fuzz(func(t *testing.T, addr uint32, count uint32) { f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world") preimageData := []byte("hello world")
state := &State{ state := &State{
Cpu: CpuScalars{
PC: 0, PC: 0,
NextPC: 4, NextPC: 4,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -314,10 +326,10 @@ func FuzzStateHintRead(f *testing.F) { ...@@ -314,10 +326,10 @@ func FuzzStateHintRead(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC) require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.NextPC) require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
...@@ -342,10 +354,12 @@ func FuzzStatePreimageRead(f *testing.F) { ...@@ -342,10 +354,12 @@ func FuzzStatePreimageRead(f *testing.F) {
t.SkipNow() t.SkipNow()
} }
state := &State{ state := &State{
Cpu: CpuScalars{
PC: 0, PC: 0,
NextPC: 4, NextPC: 4,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -372,10 +386,10 @@ func FuzzStatePreimageRead(f *testing.F) { ...@@ -372,10 +386,10 @@ func FuzzStatePreimageRead(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.True(t, stepWitness.HasPreimage()) require.True(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC) require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.NextPC) require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
...@@ -403,10 +417,12 @@ func FuzzStateHintWrite(f *testing.F) { ...@@ -403,10 +417,12 @@ func FuzzStateHintWrite(f *testing.F) {
f.Fuzz(func(t *testing.T, addr uint32, count uint32, randSeed int64) { f.Fuzz(func(t *testing.T, addr uint32, count uint32, randSeed int64) {
preimageData := []byte("hello world") preimageData := []byte("hello world")
state := &State{ state := &State{
Cpu: CpuScalars{
PC: 0, PC: 0,
NextPC: 4, NextPC: 4,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -436,10 +452,10 @@ func FuzzStateHintWrite(f *testing.F) { ...@@ -436,10 +452,10 @@ func FuzzStateHintWrite(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC) require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.NextPC) require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
...@@ -461,10 +477,12 @@ func FuzzStatePreimageWrite(f *testing.F) { ...@@ -461,10 +477,12 @@ func FuzzStatePreimageWrite(f *testing.F) {
f.Fuzz(func(t *testing.T, addr uint32, count uint32) { f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world") preimageData := []byte("hello world")
state := &State{ state := &State{
Cpu: CpuScalars{
PC: 0, PC: 0,
NextPC: 4, NextPC: 4,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
...@@ -489,10 +507,10 @@ func FuzzStatePreimageWrite(f *testing.F) { ...@@ -489,10 +507,10 @@ func FuzzStatePreimageWrite(f *testing.F) {
require.NoError(t, err) require.NoError(t, err)
require.False(t, stepWitness.HasPreimage()) require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC) require.Equal(t, uint32(4), state.Cpu.PC)
require.Equal(t, uint32(8), state.NextPC) require.Equal(t, uint32(8), state.Cpu.NextPC)
require.Equal(t, uint32(0), state.LO) require.Equal(t, uint32(0), state.Cpu.LO)
require.Equal(t, uint32(0), state.HI) require.Equal(t, uint32(0), state.Cpu.HI)
require.Equal(t, uint32(0), state.Heap) require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode) require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited) require.Equal(t, false, state.Exited)
......
...@@ -78,7 +78,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) { ...@@ -78,7 +78,7 @@ func (m *InstrumentedState) Step(proof bool) (wit *StepWitness, err error) {
m.lastPreimageOffset = ^uint32(0) m.lastPreimageOffset = ^uint32(0)
if proof { if proof {
insnProof := m.state.Memory.MerkleProof(m.state.PC) insnProof := m.state.Memory.MerkleProof(m.state.Cpu.PC)
wit = &StepWitness{ wit = &StepWitness{
State: m.state.EncodeWitness(), State: m.state.EncodeWitness(),
MemProof: insnProof[:], MemProof: insnProof[:],
......
...@@ -174,8 +174,8 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -174,8 +174,8 @@ func (m *InstrumentedState) handleSyscall() error {
m.state.Registers[2] = v0 m.state.Registers[2] = v0
m.state.Registers[7] = v1 m.state.Registers[7] = v1
m.state.PC = m.state.NextPC m.state.Cpu.PC = m.state.Cpu.NextPC
m.state.NextPC = m.state.NextPC + 4 m.state.Cpu.NextPC = m.state.Cpu.NextPC + 4
return nil return nil
} }
...@@ -184,7 +184,7 @@ func (m *InstrumentedState) pushStack(target uint32) { ...@@ -184,7 +184,7 @@ func (m *InstrumentedState) pushStack(target uint32) {
return return
} }
m.debug.stack = append(m.debug.stack, target) m.debug.stack = append(m.debug.stack, target)
m.debug.caller = append(m.debug.caller, m.state.PC) m.debug.caller = append(m.debug.caller, m.state.Cpu.PC)
} }
func (m *InstrumentedState) popStack() { func (m *InstrumentedState) popStack() {
...@@ -192,7 +192,7 @@ func (m *InstrumentedState) popStack() { ...@@ -192,7 +192,7 @@ func (m *InstrumentedState) popStack() {
return return
} }
if len(m.debug.stack) != 0 { if len(m.debug.stack) != 0 {
fn := m.debug.meta.LookupSymbol(m.state.PC) fn := m.debug.meta.LookupSymbol(m.state.Cpu.PC)
topFn := m.debug.meta.LookupSymbol(m.debug.stack[len(m.debug.stack)-1]) topFn := m.debug.meta.LookupSymbol(m.debug.stack[len(m.debug.stack)-1])
if fn != topFn { if fn != topFn {
// most likely the function was inlined. Snap back to the last return. // most likely the function was inlined. Snap back to the last return.
...@@ -209,12 +209,12 @@ func (m *InstrumentedState) popStack() { ...@@ -209,12 +209,12 @@ func (m *InstrumentedState) popStack() {
m.debug.caller = m.debug.caller[:len(m.debug.caller)-1] m.debug.caller = m.debug.caller[:len(m.debug.caller)-1]
} }
} else { } else {
fmt.Printf("ERROR: stack underflow at pc=%x. step=%d\n", m.state.PC, m.state.Step) fmt.Printf("ERROR: stack underflow at pc=%x. step=%d\n", m.state.Cpu.PC, m.state.Step)
} }
} }
func (m *InstrumentedState) Traceback() { func (m *InstrumentedState) Traceback() {
fmt.Printf("traceback at pc=%x. step=%d\n", m.state.PC, m.state.Step) fmt.Printf("traceback at pc=%x. step=%d\n", m.state.Cpu.PC, m.state.Step)
for i := len(m.debug.stack) - 1; i >= 0; i-- { for i := len(m.debug.stack) - 1; i >= 0; i-- {
s := m.debug.stack[i] s := m.debug.stack[i]
idx := len(m.debug.stack) - i - 1 idx := len(m.debug.stack) - i - 1
...@@ -222,83 +222,49 @@ func (m *InstrumentedState) Traceback() { ...@@ -222,83 +222,49 @@ func (m *InstrumentedState) Traceback() {
} }
} }
func (m *InstrumentedState) handleBranch(opcode uint32, insn uint32, rtReg uint32, rs uint32) error {
if m.state.NextPC != m.state.PC+4 {
panic("branch in delay slot")
}
shouldBranch := false
if opcode == 4 || opcode == 5 { // beq/bne
rt := m.state.Registers[rtReg]
shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5)
} else if opcode == 6 {
shouldBranch = int32(rs) <= 0 // blez
} else if opcode == 7 {
shouldBranch = int32(rs) > 0 // bgtz
} else if opcode == 1 {
// regimm
rtv := (insn >> 16) & 0x1F
if rtv == 0 { // bltz
shouldBranch = int32(rs) < 0
}
if rtv == 1 { // bgez
shouldBranch = int32(rs) >= 0
}
}
prevPC := m.state.PC
m.state.PC = m.state.NextPC // execute the delay slot first
if shouldBranch {
m.state.NextPC = prevPC + 4 + (signExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to.
} else {
m.state.NextPC = m.state.NextPC + 4 // branch not taken
}
return nil
}
func (m *InstrumentedState) handleHiLo(fun uint32, rs uint32, rt uint32, storeReg uint32) error { func (m *InstrumentedState) handleHiLo(fun uint32, rs uint32, rt uint32, storeReg uint32) error {
val := uint32(0) val := uint32(0)
switch fun { switch fun {
case 0x10: // mfhi case 0x10: // mfhi
val = m.state.HI val = m.state.Cpu.HI
case 0x11: // mthi case 0x11: // mthi
m.state.HI = rs m.state.Cpu.HI = rs
case 0x12: // mflo case 0x12: // mflo
val = m.state.LO val = m.state.Cpu.LO
case 0x13: // mtlo case 0x13: // mtlo
m.state.LO = rs m.state.Cpu.LO = rs
case 0x18: // mult case 0x18: // mult
acc := uint64(int64(int32(rs)) * int64(int32(rt))) acc := uint64(int64(int32(rs)) * int64(int32(rt)))
m.state.HI = uint32(acc >> 32) m.state.Cpu.HI = uint32(acc >> 32)
m.state.LO = uint32(acc) m.state.Cpu.LO = uint32(acc)
case 0x19: // multu case 0x19: // multu
acc := uint64(uint64(rs) * uint64(rt)) acc := uint64(uint64(rs) * uint64(rt))
m.state.HI = uint32(acc >> 32) m.state.Cpu.HI = uint32(acc >> 32)
m.state.LO = uint32(acc) m.state.Cpu.LO = uint32(acc)
case 0x1a: // div case 0x1a: // div
m.state.HI = uint32(int32(rs) % int32(rt)) m.state.Cpu.HI = uint32(int32(rs) % int32(rt))
m.state.LO = uint32(int32(rs) / int32(rt)) m.state.Cpu.LO = uint32(int32(rs) / int32(rt))
case 0x1b: // divu case 0x1b: // divu
m.state.HI = rs % rt m.state.Cpu.HI = rs % rt
m.state.LO = rs / rt m.state.Cpu.LO = rs / rt
} }
if storeReg != 0 { if storeReg != 0 {
m.state.Registers[storeReg] = val m.state.Registers[storeReg] = val
} }
m.state.PC = m.state.NextPC m.state.Cpu.PC = m.state.Cpu.NextPC
m.state.NextPC = m.state.NextPC + 4 m.state.Cpu.NextPC = m.state.Cpu.NextPC + 4
return nil return nil
} }
func (m *InstrumentedState) handleJump(linkReg uint32, dest uint32) error { func (m *InstrumentedState) handleJump(linkReg uint32, dest uint32) error {
if m.state.NextPC != m.state.PC+4 { if m.state.Cpu.NextPC != m.state.Cpu.PC+4 {
panic("jump in delay slot") panic("jump in delay slot")
} }
prevPC := m.state.PC prevPC := m.state.Cpu.PC
m.state.PC = m.state.NextPC m.state.Cpu.PC = m.state.Cpu.NextPC
m.state.NextPC = dest m.state.Cpu.NextPC = dest
if linkReg != 0 { if linkReg != 0 {
m.state.Registers[linkReg] = prevPC + 8 // set the link-register to the instr after the delay slot instruction. m.state.Registers[linkReg] = prevPC + 8 // set the link-register to the instr after the delay slot instruction.
} }
...@@ -312,8 +278,8 @@ func (m *InstrumentedState) handleRd(storeReg uint32, val uint32, conditional bo ...@@ -312,8 +278,8 @@ func (m *InstrumentedState) handleRd(storeReg uint32, val uint32, conditional bo
if storeReg != 0 && conditional { if storeReg != 0 && conditional {
m.state.Registers[storeReg] = val m.state.Registers[storeReg] = val
} }
m.state.PC = m.state.NextPC m.state.Cpu.PC = m.state.Cpu.NextPC
m.state.NextPC = m.state.NextPC + 4 m.state.Cpu.NextPC = m.state.Cpu.NextPC + 4
return nil return nil
} }
...@@ -323,7 +289,7 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -323,7 +289,7 @@ func (m *InstrumentedState) mipsStep() error {
} }
m.state.Step += 1 m.state.Step += 1
// instruction fetch // instruction fetch
insn := m.state.Memory.GetMemory(m.state.PC) insn := m.state.Memory.GetMemory(m.state.Cpu.PC)
opcode := insn >> 26 // 6-bits opcode := insn >> 26 // 6-bits
// j-type j/jal // j-type j/jal
...@@ -333,7 +299,7 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -333,7 +299,7 @@ func (m *InstrumentedState) mipsStep() error {
linkReg = 31 linkReg = 31
} }
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (m.state.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2) target := (m.state.Cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
m.pushStack(target) m.pushStack(target)
return m.handleJump(linkReg, target) return m.handleJump(linkReg, target)
} }
...@@ -369,7 +335,7 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -369,7 +335,7 @@ func (m *InstrumentedState) mipsStep() error {
} }
if (opcode >= 4 && opcode < 8) || opcode == 1 { if (opcode >= 4 && opcode < 8) || opcode == 1 {
return m.handleBranch(opcode, insn, rtReg, rs) return handleBranch(&m.state.Cpu, &m.state.Registers, opcode, insn, rtReg, rs)
} }
storeAddr := uint32(0xFF_FF_FF_FF) storeAddr := uint32(0xFF_FF_FF_FF)
......
...@@ -174,3 +174,37 @@ func signExtend(dat uint32, idx uint32) uint32 { ...@@ -174,3 +174,37 @@ func signExtend(dat uint32, idx uint32) uint32 {
return dat & mask return dat & mask
} }
} }
func handleBranch(cpu *CpuScalars, registers *[32]uint32, opcode uint32, insn uint32, rtReg uint32, rs uint32) error {
if cpu.NextPC != cpu.PC+4 {
panic("branch in delay slot")
}
shouldBranch := false
if opcode == 4 || opcode == 5 { // beq/bne
rt := registers[rtReg]
shouldBranch = (rs == rt && opcode == 4) || (rs != rt && opcode == 5)
} else if opcode == 6 {
shouldBranch = int32(rs) <= 0 // blez
} else if opcode == 7 {
shouldBranch = int32(rs) > 0 // bgtz
} else if opcode == 1 {
// regimm
rtv := (insn >> 16) & 0x1F
if rtv == 0 { // bltz
shouldBranch = int32(rs) < 0
}
if rtv == 1 { // bgez
shouldBranch = int32(rs) >= 0
}
}
prevPC := cpu.PC
cpu.PC = cpu.NextPC // execute the delay slot first
if shouldBranch {
cpu.NextPC = prevPC + 4 + (signExtend(insn&0xFFFF, 16) << 2) // then continue with the instruction the branch jumps to.
} else {
cpu.NextPC = cpu.NextPC + 4 // branch not taken
}
return nil
}
...@@ -12,10 +12,12 @@ const HEAP_START = 0x05000000 ...@@ -12,10 +12,12 @@ const HEAP_START = 0x05000000
func LoadELF(f *elf.File) (*State, error) { func LoadELF(f *elf.File) (*State, error) {
s := &State{ s := &State{
Cpu: CpuScalars{
PC: uint32(f.Entry), PC: uint32(f.Entry),
NextPC: uint32(f.Entry + 4), NextPC: uint32(f.Entry + 4),
HI: 0,
LO: 0, LO: 0,
HI: 0,
},
Heap: HEAP_START, Heap: HEAP_START,
Registers: [32]uint32{}, Registers: [32]uint32{},
Memory: NewMemory(), Memory: NewMemory(),
......
...@@ -12,16 +12,21 @@ import ( ...@@ -12,16 +12,21 @@ import (
// StateWitnessSize is the size of the state witness encoding in bytes. // StateWitnessSize is the size of the state witness encoding in bytes.
var StateWitnessSize = 226 var StateWitnessSize = 226
type CpuScalars struct {
PC uint32 `json:"pc"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
}
type State struct { type State struct {
Memory *Memory `json:"memory"` Memory *Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"` PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix PreimageOffset uint32 `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
PC uint32 `json:"pc"` Cpu CpuScalars `json:"cpu"`
NextPC uint32 `json:"nextPC"`
LO uint32 `json:"lo"`
HI uint32 `json:"hi"`
Heap uint32 `json:"heap"` // to handle mmap growth Heap uint32 `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"` ExitCode uint8 `json:"exit"`
...@@ -54,10 +59,10 @@ func (s *State) EncodeWitness() StateWitness { ...@@ -54,10 +59,10 @@ func (s *State) EncodeWitness() StateWitness {
out = append(out, memRoot[:]...) out = append(out, memRoot[:]...)
out = append(out, s.PreimageKey[:]...) out = append(out, s.PreimageKey[:]...)
out = binary.BigEndian.AppendUint32(out, s.PreimageOffset) out = binary.BigEndian.AppendUint32(out, s.PreimageOffset)
out = binary.BigEndian.AppendUint32(out, s.PC) out = binary.BigEndian.AppendUint32(out, s.Cpu.PC)
out = binary.BigEndian.AppendUint32(out, s.NextPC) out = binary.BigEndian.AppendUint32(out, s.Cpu.NextPC)
out = binary.BigEndian.AppendUint32(out, s.LO) out = binary.BigEndian.AppendUint32(out, s.Cpu.LO)
out = binary.BigEndian.AppendUint32(out, s.HI) out = binary.BigEndian.AppendUint32(out, s.Cpu.HI)
out = binary.BigEndian.AppendUint32(out, s.Heap) out = binary.BigEndian.AppendUint32(out, s.Heap)
out = append(out, s.ExitCode) out = append(out, s.ExitCode)
if s.Exited { if s.Exited {
......
...@@ -44,7 +44,7 @@ func TestState(t *testing.T) { ...@@ -44,7 +44,7 @@ func TestState(t *testing.T) {
//require.NoError(t, err, "must load ELF into state") //require.NoError(t, err, "must load ELF into state")
programMem, err := os.ReadFile(fn) programMem, err := os.ReadFile(fn)
require.NoError(t, err) require.NoError(t, err)
state := &State{PC: 0, NextPC: 4, Memory: NewMemory()} state := &State{Cpu: CpuScalars{PC: 0, NextPC: 4}, Memory: NewMemory()}
err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem)) err = state.Memory.SetMemoryRange(0, bytes.NewReader(programMem))
require.NoError(t, err, "load program into state") require.NoError(t, err, "load program into state")
...@@ -54,7 +54,7 @@ func TestState(t *testing.T) { ...@@ -54,7 +54,7 @@ func TestState(t *testing.T) {
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr) us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
if us.state.PC == endAddr { if us.state.Cpu.PC == endAddr {
break break
} }
if exitGroup && us.state.Exited { if exitGroup && us.state.Exited {
...@@ -65,11 +65,11 @@ func TestState(t *testing.T) { ...@@ -65,11 +65,11 @@ func TestState(t *testing.T) {
} }
if exitGroup { if exitGroup {
require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end") require.NotEqual(t, uint32(endAddr), us.state.Cpu.PC, "must not reach end")
require.True(t, us.state.Exited, "must set exited state") require.True(t, us.state.Exited, "must set exited state")
require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1") require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1")
} else { } else {
require.Equal(t, uint32(endAddr), us.state.PC, "must reach end") require.Equal(t, uint32(endAddr), us.state.Cpu.PC, "must reach end")
done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8) done, result := state.Memory.GetMemory(baseAddrEnd+4), state.Memory.GetMemory(baseAddrEnd+8)
// inspect test result // inspect test result
require.Equal(t, done, uint32(1), "must be done") require.Equal(t, done, uint32(1), "must be done")
......
...@@ -44,10 +44,12 @@ func TestAbsolutePreStateCommitment(t *testing.T) { ...@@ -44,10 +44,12 @@ func TestAbsolutePreStateCommitment(t *testing.T) {
Memory: mipsevm.NewMemory(), Memory: mipsevm.NewMemory(),
PreimageKey: common.HexToHash("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), PreimageKey: common.HexToHash("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
PreimageOffset: 0, PreimageOffset: 0,
Cpu: mipsevm.CpuScalars{
PC: 0, PC: 0,
NextPC: 1, NextPC: 1,
LO: 0, LO: 0,
HI: 0, HI: 0,
},
Heap: 0, Heap: 0,
ExitCode: 0, ExitCode: 0,
Exited: false, Exited: false,
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
"memory": [], "memory": [],
"preimageKey": "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "preimageKey": "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"preimageOffset": 0, "preimageOffset": 0,
"cpu": {
"pc": 0, "pc": 0,
"nextPC": 1, "nextPC": 1,
"lo": 0, "lo": 0,
"hi": 0, "hi": 0
},
"heap": 0, "heap": 0,
"exit": 0, "exit": 0,
"exited": false, "exited": false,
......
...@@ -124,8 +124,8 @@ ...@@ -124,8 +124,8 @@
"sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f" "sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0x1c5dbe83af31e70feb906e2bda2bb1d78d3d15012ec6b11ba5643785657af2a6", "initCodeHash": "0xf0fb656774ff761e2fd9adc523d4ee3dfad6fab63a37d4177543cfc98708b1be",
"sourceCodeHash": "0x9bdc97ff4e51fdec7c3e2113d5b60cd64eeb121a51122bea972789d4a5ac3dfa" "sourceCodeHash": "0xe6a817aac69c149c24a9ab820853aae3c189eba337ab2e5c75a7cf458b45e308"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090", "initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090",
......
...@@ -4,7 +4,8 @@ pragma solidity 0.8.15; ...@@ -4,7 +4,8 @@ pragma solidity 0.8.15;
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol"; import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol";
import { PreimageKeyLib } from "./PreimageKeyLib.sol"; import { PreimageKeyLib } from "./PreimageKeyLib.sol";
import "src/cannon/libraries/MIPSInstructions.sol" as ins; import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
/// @title MIPS /// @title MIPS
/// @notice The MIPS contract emulates a single MIPS instruction. /// @notice The MIPS contract emulates a single MIPS instruction.
...@@ -45,7 +46,7 @@ contract MIPS is ISemver { ...@@ -45,7 +46,7 @@ contract MIPS is ISemver {
/// @notice The semantic version of the MIPS contract. /// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.0.1 /// @custom:semver 1.0.1
string public constant version = "1.1.0-beta.1"; string public constant version = "1.1.0-beta.2";
uint32 internal constant FD_STDIN = 0; uint32 internal constant FD_STDIN = 0;
uint32 internal constant FD_STDOUT = 1; uint32 internal constant FD_STDOUT = 1;
...@@ -301,70 +302,6 @@ contract MIPS is ISemver { ...@@ -301,70 +302,6 @@ contract MIPS is ISemver {
} }
} }
/// @notice Handles a branch instruction, updating the MIPS state PC where needed.
/// @param _opcode The opcode of the branch instruction.
/// @param _insn The instruction to be executed.
/// @param _rtReg The register to be used for the branch.
/// @param _rs The register to be compared with the branch register.
/// @return out_ The hashed MIPS state.
function handleBranch(uint32 _opcode, uint32 _insn, uint32 _rtReg, uint32 _rs) internal returns (bytes32 out_) {
unchecked {
// Load state from memory
State memory state;
assembly {
state := 0x80
}
bool shouldBranch = false;
if (state.nextPC != state.pc + 4) {
revert("branch in delay slot");
}
// beq/bne: Branch on equal / not equal
if (_opcode == 4 || _opcode == 5) {
uint32 rt = state.registers[_rtReg];
shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5);
}
// blez: Branches if instruction is less than or equal to zero
else if (_opcode == 6) {
shouldBranch = int32(_rs) <= 0;
}
// bgtz: Branches if instruction is greater than zero
else if (_opcode == 7) {
shouldBranch = int32(_rs) > 0;
}
// bltz/bgez: Branch on less than zero / greater than or equal to zero
else if (_opcode == 1) {
// regimm
uint32 rtv = ((_insn >> 16) & 0x1F);
if (rtv == 0) {
shouldBranch = int32(_rs) < 0;
}
if (rtv == 1) {
shouldBranch = int32(_rs) >= 0;
}
}
// Update the state's previous PC
uint32 prevPC = state.pc;
// Execute the delay slot first
state.pc = state.nextPC;
// If we should branch, update the PC to the branch target
// Otherwise, proceed to the next instruction
if (shouldBranch) {
state.nextPC = prevPC + 4 + (ins.signExtend(_insn & 0xFFFF, 16) << 2);
} else {
state.nextPC = state.nextPC + 4;
}
// Return the hash of the resulting state
out_ = outputState();
}
}
/// @notice Handles HI and LO register instructions. /// @notice Handles HI and LO register instructions.
/// @param _func The function code of the instruction. /// @param _func The function code of the instruction.
/// @param _rs The value of the RS register. /// @param _rs The value of the RS register.
...@@ -730,7 +667,19 @@ contract MIPS is ISemver { ...@@ -730,7 +667,19 @@ contract MIPS is ISemver {
} }
if ((opcode >= 4 && opcode < 8) || opcode == 1) { if ((opcode >= 4 && opcode < 8) || opcode == 1) {
return handleBranch(opcode, insn, rtReg, rs); st.CpuScalars memory cpu = getCpuScalars(state);
ins.handleBranch({
_cpu: cpu,
_registers: state.registers,
_opcode: opcode,
_insn: insn,
_rtReg: rtReg,
_rs: rs
});
setStateCpuScalars(state, cpu);
return outputState();
} }
uint32 storeAddr = 0xFF_FF_FF_FF; uint32 storeAddr = 0xFF_FF_FF_FF;
...@@ -796,4 +745,15 @@ contract MIPS is ISemver { ...@@ -796,4 +745,15 @@ contract MIPS is ISemver {
return handleRd(rdReg, val, true); return handleRd(rdReg, val, true);
} }
} }
function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) {
return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi });
}
function setStateCpuScalars(State memory _state, st.CpuScalars memory _cpu) internal pure {
_state.pc = _cpu.pc;
_state.nextPC = _cpu.nextPC;
_state.lo = _cpu.lo;
_state.hi = _cpu.hi;
}
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/// @notice Execute an instruction. import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
function executeMipsInstruction(uint32 insn, uint32 rs, uint32 rt, uint32 mem) pure returns (uint32 out) {
library MIPSInstructions {
/// @notice Execute an instruction.
function executeMipsInstruction(
uint32 _insn,
uint32 _rs,
uint32 _rt,
uint32 _mem
)
internal
pure
returns (uint32 out_)
{
unchecked { unchecked {
uint32 opcode = insn >> 26; // 6-bits uint32 opcode = _insn >> 26; // 6-bits
if (opcode == 0 || (opcode >= 8 && opcode < 0xF)) { if (opcode == 0 || (opcode >= 8 && opcode < 0xF)) {
uint32 func = insn & 0x3f; // 6-bits uint32 func = _insn & 0x3f; // 6-bits
assembly { assembly {
// transform ArithLogI to SPECIAL // transform ArithLogI to SPECIAL
switch opcode switch opcode
...@@ -29,237 +41,304 @@ function executeMipsInstruction(uint32 insn, uint32 rs, uint32 rt, uint32 mem) p ...@@ -29,237 +41,304 @@ function executeMipsInstruction(uint32 insn, uint32 rs, uint32 rt, uint32 mem) p
// sll // sll
if (func == 0x00) { if (func == 0x00) {
return rt << ((insn >> 6) & 0x1F); return _rt << ((_insn >> 6) & 0x1F);
} }
// srl // srl
else if (func == 0x02) { else if (func == 0x02) {
return rt >> ((insn >> 6) & 0x1F); return _rt >> ((_insn >> 6) & 0x1F);
} }
// sra // sra
else if (func == 0x03) { else if (func == 0x03) {
uint32 shamt = (insn >> 6) & 0x1F; uint32 shamt = (_insn >> 6) & 0x1F;
return signExtend(rt >> shamt, 32 - shamt); return signExtend(_rt >> shamt, 32 - shamt);
} }
// sllv // sllv
else if (func == 0x04) { else if (func == 0x04) {
return rt << (rs & 0x1F); return _rt << (_rs & 0x1F);
} }
// srlv // srlv
else if (func == 0x6) { else if (func == 0x6) {
return rt >> (rs & 0x1F); return _rt >> (_rs & 0x1F);
} }
// srav // srav
else if (func == 0x07) { else if (func == 0x07) {
return signExtend(rt >> rs, 32 - rs); return signExtend(_rt >> _rs, 32 - _rs);
} }
// functs in range [0x8, 0x1b] are handled specially by other functions // functs in range [0x8, 0x1b] are handled specially by other functions
// Explicitly enumerate each funct in range to reduce code diff against Go Vm // Explicitly enumerate each funct in range to reduce code diff against Go Vm
// jr // jr
else if (func == 0x08) { else if (func == 0x08) {
return rs; return _rs;
} }
// jalr // jalr
else if (func == 0x09) { else if (func == 0x09) {
return rs; return _rs;
} }
// movz // movz
else if (func == 0x0a) { else if (func == 0x0a) {
return rs; return _rs;
} }
// movn // movn
else if (func == 0x0b) { else if (func == 0x0b) {
return rs; return _rs;
} }
// syscall // syscall
else if (func == 0x0c) { else if (func == 0x0c) {
return rs; return _rs;
} }
// 0x0d - break not supported // 0x0d - break not supported
// sync // sync
else if (func == 0x0f) { else if (func == 0x0f) {
return rs; return _rs;
} }
// mfhi // mfhi
else if (func == 0x10) { else if (func == 0x10) {
return rs; return _rs;
} }
// mthi // mthi
else if (func == 0x11) { else if (func == 0x11) {
return rs; return _rs;
} }
// mflo // mflo
else if (func == 0x12) { else if (func == 0x12) {
return rs; return _rs;
} }
// mtlo // mtlo
else if (func == 0x13) { else if (func == 0x13) {
return rs; return _rs;
} }
// mult // mult
else if (func == 0x18) { else if (func == 0x18) {
return rs; return _rs;
} }
// multu // multu
else if (func == 0x19) { else if (func == 0x19) {
return rs; return _rs;
} }
// div // div
else if (func == 0x1a) { else if (func == 0x1a) {
return rs; return _rs;
} }
// divu // divu
else if (func == 0x1b) { else if (func == 0x1b) {
return rs; return _rs;
} }
// The rest includes transformed R-type arith imm instructions // The rest includes transformed R-type arith imm instructions
// add // add
else if (func == 0x20) { else if (func == 0x20) {
return (rs + rt); return (_rs + _rt);
} }
// addu // addu
else if (func == 0x21) { else if (func == 0x21) {
return (rs + rt); return (_rs + _rt);
} }
// sub // sub
else if (func == 0x22) { else if (func == 0x22) {
return (rs - rt); return (_rs - _rt);
} }
// subu // subu
else if (func == 0x23) { else if (func == 0x23) {
return (rs - rt); return (_rs - _rt);
} }
// and // and
else if (func == 0x24) { else if (func == 0x24) {
return (rs & rt); return (_rs & _rt);
} }
// or // or
else if (func == 0x25) { else if (func == 0x25) {
return (rs | rt); return (_rs | _rt);
} }
// xor // xor
else if (func == 0x26) { else if (func == 0x26) {
return (rs ^ rt); return (_rs ^ _rt);
} }
// nor // nor
else if (func == 0x27) { else if (func == 0x27) {
return ~(rs | rt); return ~(_rs | _rt);
} }
// slti // slti
else if (func == 0x2a) { else if (func == 0x2a) {
return int32(rs) < int32(rt) ? 1 : 0; return int32(_rs) < int32(_rt) ? 1 : 0;
} }
// sltiu // sltiu
else if (func == 0x2b) { else if (func == 0x2b) {
return rs < rt ? 1 : 0; return _rs < _rt ? 1 : 0;
} else { } else {
revert("invalid instruction"); revert("invalid instruction");
} }
} else { } else {
// SPECIAL2 // SPECIAL2
if (opcode == 0x1C) { if (opcode == 0x1C) {
uint32 func = insn & 0x3f; // 6-bits uint32 func = _insn & 0x3f; // 6-bits
// mul // mul
if (func == 0x2) { if (func == 0x2) {
return uint32(int32(rs) * int32(rt)); return uint32(int32(_rs) * int32(_rt));
} }
// clz, clo // clz, clo
else if (func == 0x20 || func == 0x21) { else if (func == 0x20 || func == 0x21) {
if (func == 0x20) { if (func == 0x20) {
rs = ~rs; _rs = ~_rs;
} }
uint32 i = 0; uint32 i = 0;
while (rs & 0x80000000 != 0) { while (_rs & 0x80000000 != 0) {
i++; i++;
rs <<= 1; _rs <<= 1;
} }
return i; return i;
} }
} }
// lui // lui
else if (opcode == 0x0F) { else if (opcode == 0x0F) {
return rt << 16; return _rt << 16;
} }
// lb // lb
else if (opcode == 0x20) { else if (opcode == 0x20) {
return signExtend((mem >> (24 - (rs & 3) * 8)) & 0xFF, 8); return signExtend((_mem >> (24 - (_rs & 3) * 8)) & 0xFF, 8);
} }
// lh // lh
else if (opcode == 0x21) { else if (opcode == 0x21) {
return signExtend((mem >> (16 - (rs & 2) * 8)) & 0xFFFF, 16); return signExtend((_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF, 16);
} }
// lwl // lwl
else if (opcode == 0x22) { else if (opcode == 0x22) {
uint32 val = mem << ((rs & 3) * 8); uint32 val = _mem << ((_rs & 3) * 8);
uint32 mask = uint32(0xFFFFFFFF) << ((rs & 3) * 8); uint32 mask = uint32(0xFFFFFFFF) << ((_rs & 3) * 8);
return (rt & ~mask) | val; return (_rt & ~mask) | val;
} }
// lw // lw
else if (opcode == 0x23) { else if (opcode == 0x23) {
return mem; return _mem;
} }
// lbu // lbu
else if (opcode == 0x24) { else if (opcode == 0x24) {
return (mem >> (24 - (rs & 3) * 8)) & 0xFF; return (_mem >> (24 - (_rs & 3) * 8)) & 0xFF;
} }
// lhu // lhu
else if (opcode == 0x25) { else if (opcode == 0x25) {
return (mem >> (16 - (rs & 2) * 8)) & 0xFFFF; return (_mem >> (16 - (_rs & 2) * 8)) & 0xFFFF;
} }
// lwr // lwr
else if (opcode == 0x26) { else if (opcode == 0x26) {
uint32 val = mem >> (24 - (rs & 3) * 8); uint32 val = _mem >> (24 - (_rs & 3) * 8);
uint32 mask = uint32(0xFFFFFFFF) >> (24 - (rs & 3) * 8); uint32 mask = uint32(0xFFFFFFFF) >> (24 - (_rs & 3) * 8);
return (rt & ~mask) | val; return (_rt & ~mask) | val;
} }
// sb // sb
else if (opcode == 0x28) { else if (opcode == 0x28) {
uint32 val = (rt & 0xFF) << (24 - (rs & 3) * 8); uint32 val = (_rt & 0xFF) << (24 - (_rs & 3) * 8);
uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (rs & 3) * 8)); uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (_rs & 3) * 8));
return (mem & mask) | val; return (_mem & mask) | val;
} }
// sh // sh
else if (opcode == 0x29) { else if (opcode == 0x29) {
uint32 val = (rt & 0xFFFF) << (16 - (rs & 2) * 8); uint32 val = (_rt & 0xFFFF) << (16 - (_rs & 2) * 8);
uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (rs & 2) * 8)); uint32 mask = 0xFFFFFFFF ^ uint32(0xFFFF << (16 - (_rs & 2) * 8));
return (mem & mask) | val; return (_mem & mask) | val;
} }
// swl // swl
else if (opcode == 0x2a) { else if (opcode == 0x2a) {
uint32 val = rt >> ((rs & 3) * 8); uint32 val = _rt >> ((_rs & 3) * 8);
uint32 mask = uint32(0xFFFFFFFF) >> ((rs & 3) * 8); uint32 mask = uint32(0xFFFFFFFF) >> ((_rs & 3) * 8);
return (mem & ~mask) | val; return (_mem & ~mask) | val;
} }
// sw // sw
else if (opcode == 0x2b) { else if (opcode == 0x2b) {
return rt; return _rt;
} }
// swr // swr
else if (opcode == 0x2e) { else if (opcode == 0x2e) {
uint32 val = rt << (24 - (rs & 3) * 8); uint32 val = _rt << (24 - (_rs & 3) * 8);
uint32 mask = uint32(0xFFFFFFFF) << (24 - (rs & 3) * 8); uint32 mask = uint32(0xFFFFFFFF) << (24 - (_rs & 3) * 8);
return (mem & ~mask) | val; return (_mem & ~mask) | val;
} }
// ll // ll
else if (opcode == 0x30) { else if (opcode == 0x30) {
return mem; return _mem;
} }
// sc // sc
else if (opcode == 0x38) { else if (opcode == 0x38) {
return rt; return _rt;
} else { } else {
revert("invalid instruction"); revert("invalid instruction");
} }
} }
revert("invalid instruction"); revert("invalid instruction");
} }
} }
/// @notice Extends the value leftwards with its most significant bit (sign extension). /// @notice Extends the value leftwards with its most significant bit (sign extension).
function signExtend(uint32 _dat, uint32 _idx) pure returns (uint32 out_) { function signExtend(uint32 _dat, uint32 _idx) internal pure returns (uint32 out_) {
unchecked { unchecked {
bool isSigned = (_dat >> (_idx - 1)) != 0; bool isSigned = (_dat >> (_idx - 1)) != 0;
uint256 signed = ((1 << (32 - _idx)) - 1) << _idx; uint256 signed = ((1 << (32 - _idx)) - 1) << _idx;
uint256 mask = (1 << _idx) - 1; uint256 mask = (1 << _idx) - 1;
return uint32(_dat & mask | (isSigned ? signed : 0)); return uint32(_dat & mask | (isSigned ? signed : 0));
} }
}
/// @notice Handles a branch instruction, updating the MIPS state PC where needed.
/// @param _cpu Holds the state of cpu scalars pc, nextPC, hi, lo.
/// @param _registers Holds the current state of the cpu registers.
/// @param _opcode The opcode of the branch instruction.
/// @param _insn The instruction to be executed.
/// @param _rtReg The register to be used for the branch.
/// @param _rs The register to be compared with the branch register.
function handleBranch(
st.CpuScalars memory _cpu,
uint32[32] memory _registers,
uint32 _opcode,
uint32 _insn,
uint32 _rtReg,
uint32 _rs
)
internal
pure
{
unchecked {
bool shouldBranch = false;
if (_cpu.nextPC != _cpu.pc + 4) {
revert("branch in delay slot");
}
// beq/bne: Branch on equal / not equal
if (_opcode == 4 || _opcode == 5) {
uint32 rt = _registers[_rtReg];
shouldBranch = (_rs == rt && _opcode == 4) || (_rs != rt && _opcode == 5);
}
// blez: Branches if instruction is less than or equal to zero
else if (_opcode == 6) {
shouldBranch = int32(_rs) <= 0;
}
// bgtz: Branches if instruction is greater than zero
else if (_opcode == 7) {
shouldBranch = int32(_rs) > 0;
}
// bltz/bgez: Branch on less than zero / greater than or equal to zero
else if (_opcode == 1) {
// regimm
uint32 rtv = ((_insn >> 16) & 0x1F);
if (rtv == 0) {
shouldBranch = int32(_rs) < 0;
}
if (rtv == 1) {
shouldBranch = int32(_rs) >= 0;
}
}
// Update the state's previous PC
uint32 prevPC = _cpu.pc;
// Execute the delay slot first
_cpu.pc = _cpu.nextPC;
// If we should branch, update the PC to the branch target
// Otherwise, proceed to the next instruction
if (shouldBranch) {
_cpu.nextPC = prevPC + 4 + (signExtend(_insn & 0xFFFF, 16) << 2);
} else {
_cpu.nextPC = _cpu.nextPC + 4;
}
}
}
} }
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
library MIPSState {
struct CpuScalars {
uint32 pc;
uint32 nextPC;
uint32 lo;
uint32 hi;
}
}
...@@ -25,7 +25,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode { ...@@ -25,7 +25,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode {
address internal constant l1ERC721BridgeProxyAddress = 0xD31598c909d9C935a9e35bA70d9a3DD47d4D5865; address internal constant l1ERC721BridgeProxyAddress = 0xD31598c909d9C935a9e35bA70d9a3DD47d4D5865;
address internal constant l1StandardBridgeAddress = 0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b; address internal constant l1StandardBridgeAddress = 0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b;
address internal constant l1StandardBridgeProxyAddress = 0xDeF3bca8c80064589E6787477FFa7Dd616B5574F; address internal constant l1StandardBridgeProxyAddress = 0xDeF3bca8c80064589E6787477FFa7Dd616B5574F;
address internal constant mipsAddress = 0x1C0e3B8e58dd91536Caf37a6009536255A7816a6; address internal constant mipsAddress = 0x477508A9612B67709Caf812D4356d531ba6a471d;
address internal constant optimismPortal2Address = 0xfcbb237388CaF5b08175C9927a37aB6450acd535; address internal constant optimismPortal2Address = 0xfcbb237388CaF5b08175C9927a37aB6450acd535;
address internal constant optimismPortalProxyAddress = 0x978e3286EB805934215a88694d80b09aDed68D90; address internal constant optimismPortalProxyAddress = 0x978e3286EB805934215a88694d80b09aDed68D90;
address internal constant preimageOracleAddress = 0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75; address internal constant preimageOracleAddress = 0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75;
......
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