Skip to content

Commit

Permalink
cannon: Extract MIPS step helper functions (#11017)
Browse files Browse the repository at this point in the history
* cannon: Extract step helpers

* cannon: Wrap step helper logic in unchecked

* cannon: Use consistent var name between solidity and go (fun)

* cannon: Dedupe `opcode` and `fun` calculations

* cannon: Bump MIPS.sol version

* cannon: Run semver-lock, snapshots

* cannon: Address slither warnings

* cannon: Make sure all lib functions are unchecked
  • Loading branch information
mbaxter authored Jun 27, 2024
1 parent 1f64dd6 commit cce7f9c
Show file tree
Hide file tree
Showing 10 changed files with 532 additions and 488 deletions.
119 changes: 9 additions & 110 deletions cannon/mipsevm/mips.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)

type MemTracker func(addr uint32)

func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage
if key != m.lastPreimageKey {
Expand Down Expand Up @@ -123,117 +125,14 @@ func (m *InstrumentedState) mipsStep() error {
}
m.state.Step += 1
// instruction fetch
insn := m.state.Memory.GetMemory(m.state.Cpu.PC)
opcode := insn >> 26 // 6-bits

// j-type j/jal
if opcode == 2 || opcode == 3 {
linkReg := uint32(0)
if opcode == 3 {
linkReg = 31
}
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
target := (m.state.Cpu.NextPC & 0xF0000000) | ((insn & 0x03FFFFFF) << 2)
m.pushStack(target)
return handleJump(&m.state.Cpu, &m.state.Registers, linkReg, target)
}

// register fetch
rs := uint32(0) // source register 1 value
rt := uint32(0) // source register 2 / temp value
rtReg := (insn >> 16) & 0x1F

// R-type or I-type (stores rt)
rs = m.state.Registers[(insn>>21)&0x1F]
rdReg := rtReg
if opcode == 0 || opcode == 0x1c {
// R-type (stores rd)
rt = m.state.Registers[rtReg]
rdReg = (insn >> 11) & 0x1F
} else if opcode < 0x20 {
// rt is SignExtImm
// don't sign extend for andi, ori, xori
if opcode == 0xC || opcode == 0xD || opcode == 0xe {
// ZeroExtImm
rt = insn & 0xFFFF
} else {
// SignExtImm
rt = signExtend(insn&0xFFFF, 16)
}
} else if opcode >= 0x28 || opcode == 0x22 || opcode == 0x26 {
// store rt value with store
rt = m.state.Registers[rtReg]

// store actual rt with lwl and lwr
rdReg = rtReg
}

if (opcode >= 4 && opcode < 8) || opcode == 1 {
return handleBranch(&m.state.Cpu, &m.state.Registers, opcode, insn, rtReg, rs)
}

storeAddr := uint32(0xFF_FF_FF_FF)
// memory fetch (all I-type)
// we do the load for stores also
mem := uint32(0)
if opcode >= 0x20 {
// M[R[rs]+SignExtImm]
rs += signExtend(insn&0xFFFF, 16)
addr := rs & 0xFFFFFFFC
m.trackMemAccess(addr)
mem = m.state.Memory.GetMemory(addr)
if opcode >= 0x28 && opcode != 0x30 {
// store
storeAddr = addr
// store opcodes don't write back to a register
rdReg = 0
}
}

// ALU
val := executeMipsInstruction(insn, rs, rt, mem)

fun := insn & 0x3f // 6-bits
if opcode == 0 && fun >= 8 && fun < 0x1c {
if fun == 8 || fun == 9 { // jr/jalr
linkReg := uint32(0)
if fun == 9 {
linkReg = rdReg
}
m.popStack()
return handleJump(&m.state.Cpu, &m.state.Registers, linkReg, rs)
}

if fun == 0xa { // movz
return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, rs, rt == 0)
}
if fun == 0xb { // movn
return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, rs, rt != 0)
}

// syscall (can read and write)
if fun == 0xC {
return m.handleSyscall()
}

// lo and hi registers
// can write back
if fun >= 0x10 && fun < 0x1c {
return handleHiLo(&m.state.Cpu, &m.state.Registers, fun, rs, rt, rdReg)
}
}

// stupid sc, write a 1 to rt
if opcode == 0x38 && rtReg != 0 {
m.state.Registers[rtReg] = 1
}
insn, opcode, fun := getInstructionDetails(m.state.Cpu.PC, m.state.Memory)

// write memory
if storeAddr != 0xFF_FF_FF_FF {
m.trackMemAccess(storeAddr)
m.state.Memory.SetMemory(storeAddr, val)
// Handle syscall separately
// syscall (can read and write)
if opcode == 0 && fun == 0xC {
return m.handleSyscall()
}

// write back the value to destination register
return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, val, true)
// Exec the rest of the step logic
return execMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m)
}
124 changes: 120 additions & 4 deletions cannon/mipsevm/mips_instructions.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,127 @@
package mipsevm

func executeMipsInstruction(insn uint32, rs uint32, rt uint32, mem uint32) uint32 {
opcode := insn >> 26 // 6-bits
type StackTracker interface {
pushStack(target uint32)
popStack()
}

func getInstructionDetails(pc uint32, memory *Memory) (insn, opcode, fun uint32) {
insn = memory.GetMemory(pc)
opcode = insn >> 26 // First 6-bits
fun = insn & 0x3f // Last 6-bits

return insn, opcode, fun
}

func execMipsCoreStepLogic(cpu *CpuScalars, registers *[32]uint32, memory *Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) error {
// j-type j/jal
if opcode == 2 || opcode == 3 {
linkReg := uint32(0)
if opcode == 3 {
linkReg = 31
}
// 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)
return handleJump(cpu, registers, linkReg, target)
}

// register fetch
rs := uint32(0) // source register 1 value
rt := uint32(0) // source register 2 / temp value
rtReg := (insn >> 16) & 0x1F

// R-type or I-type (stores rt)
rs = registers[(insn>>21)&0x1F]
rdReg := rtReg
if opcode == 0 || opcode == 0x1c {
// R-type (stores rd)
rt = registers[rtReg]
rdReg = (insn >> 11) & 0x1F
} else if opcode < 0x20 {
// rt is SignExtImm
// don't sign extend for andi, ori, xori
if opcode == 0xC || opcode == 0xD || opcode == 0xe {
// ZeroExtImm
rt = insn & 0xFFFF
} else {
// SignExtImm
rt = signExtend(insn&0xFFFF, 16)
}
} else if opcode >= 0x28 || opcode == 0x22 || opcode == 0x26 {
// store rt value with store
rt = registers[rtReg]

// store actual rt with lwl and lwr
rdReg = rtReg
}

if (opcode >= 4 && opcode < 8) || opcode == 1 {
return handleBranch(cpu, registers, opcode, insn, rtReg, rs)
}

storeAddr := uint32(0xFF_FF_FF_FF)
// memory fetch (all I-type)
// we do the load for stores also
mem := uint32(0)
if opcode >= 0x20 {
// M[R[rs]+SignExtImm]
rs += signExtend(insn&0xFFFF, 16)
addr := rs & 0xFFFFFFFC
memTracker(addr)
mem = memory.GetMemory(addr)
if opcode >= 0x28 && opcode != 0x30 {
// store
storeAddr = addr
// store opcodes don't write back to a register
rdReg = 0
}
}

// ALU
val := executeMipsInstruction(insn, opcode, fun, rs, rt, mem)

if opcode == 0 && fun >= 8 && fun < 0x1c {
if fun == 8 || fun == 9 { // jr/jalr
linkReg := uint32(0)
if fun == 9 {
linkReg = rdReg
}
stackTracker.popStack()
return handleJump(cpu, registers, linkReg, rs)
}

if fun == 0xa { // movz
return handleRd(cpu, registers, rdReg, rs, rt == 0)
}
if fun == 0xb { // movn
return handleRd(cpu, registers, rdReg, rs, rt != 0)
}

// lo and hi registers
// can write back
if fun >= 0x10 && fun < 0x1c {
return handleHiLo(cpu, registers, fun, rs, rt, rdReg)
}
}

// store conditional, write a 1 to rt
if opcode == 0x38 && rtReg != 0 {
registers[rtReg] = 1
}

// write memory
if storeAddr != 0xFF_FF_FF_FF {
memTracker(storeAddr)
memory.SetMemory(storeAddr, val)
}

// write back the value to destination register
return handleRd(cpu, registers, rdReg, val, true)
}

func executeMipsInstruction(insn, opcode, fun, rs, rt, mem uint32) uint32 {
if opcode == 0 || (opcode >= 8 && opcode < 0xF) {
fun := insn & 0x3f // 6-bits
// transform ArithLogI to SPECIAL
switch opcode {
case 8:
Expand Down Expand Up @@ -101,7 +218,6 @@ func executeMipsInstruction(insn uint32, rs uint32, rt uint32, mem uint32) uint3
switch opcode {
// SPECIAL2
case 0x1C:
fun := insn & 0x3f // 6-bits
switch fun {
case 0x2: // mul
return uint32(int32(rs) * int32(rt))
Expand Down
1 change: 0 additions & 1 deletion cannon/mipsevm/mips_syscalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const (
)

type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32)
type MemTracker func(addr uint32)

func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
syscallNum = registers[2] // v0
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@
"sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0xe9183ee3b69d9ec9594d6b3923d78c86c996cd738ccbc09675bb281284c060af",
"sourceCodeHash": "0x7c2eab73da8b2eeadba30eadb39f20e91307bc29218938fadfc5f73fadcf13bc"
"initCodeHash": "0xe2cfbfa5d8587a6c3cf52686e29fb0d07e2764af0ef728825529f42ebdeacb5d",
"sourceCodeHash": "0x231f42a05f0c8e5784eb112518afca0bb16a3689f317ce021b8390a0aa70377b"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090",
Expand Down
Loading

0 comments on commit cce7f9c

Please sign in to comment.