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

cannon: Extract MIPS step helper functions (#11017)

* 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
parent 1f64dd6d
...@@ -8,6 +8,8 @@ import ( ...@@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "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) { func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage preimage := m.lastPreimage
if key != m.lastPreimageKey { if key != m.lastPreimageKey {
...@@ -123,117 +125,14 @@ func (m *InstrumentedState) mipsStep() error { ...@@ -123,117 +125,14 @@ func (m *InstrumentedState) mipsStep() error {
} }
m.state.Step += 1 m.state.Step += 1
// instruction fetch // instruction fetch
insn := m.state.Memory.GetMemory(m.state.Cpu.PC) insn, opcode, fun := getInstructionDetails(m.state.Cpu.PC, m.state.Memory)
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
}
// write memory // Handle syscall separately
if storeAddr != 0xFF_FF_FF_FF { // syscall (can read and write)
m.trackMemAccess(storeAddr) if opcode == 0 && fun == 0xC {
m.state.Memory.SetMemory(storeAddr, val) return m.handleSyscall()
} }
// write back the value to destination register // Exec the rest of the step logic
return handleRd(&m.state.Cpu, &m.state.Registers, rdReg, val, true) return execMipsCoreStepLogic(&m.state.Cpu, &m.state.Registers, m.state.Memory, insn, opcode, fun, m.trackMemAccess, m)
} }
package mipsevm package mipsevm
func executeMipsInstruction(insn uint32, rs uint32, rt uint32, mem uint32) uint32 { type StackTracker interface {
opcode := insn >> 26 // 6-bits 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) { if opcode == 0 || (opcode >= 8 && opcode < 0xF) {
fun := insn & 0x3f // 6-bits
// transform ArithLogI to SPECIAL // transform ArithLogI to SPECIAL
switch opcode { switch opcode {
case 8: case 8:
...@@ -101,7 +218,6 @@ func executeMipsInstruction(insn uint32, rs uint32, rt uint32, mem uint32) uint3 ...@@ -101,7 +218,6 @@ func executeMipsInstruction(insn uint32, rs uint32, rt uint32, mem uint32) uint3
switch opcode { switch opcode {
// SPECIAL2 // SPECIAL2
case 0x1C: case 0x1C:
fun := insn & 0x3f // 6-bits
switch fun { switch fun {
case 0x2: // mul case 0x2: // mul
return uint32(int32(rs) * int32(rt)) return uint32(int32(rs) * int32(rt))
......
...@@ -34,7 +34,6 @@ const ( ...@@ -34,7 +34,6 @@ const (
) )
type PreimageReader func(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) 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) { func getSyscallArgs(registers *[32]uint32) (syscallNum, a0, a1, a2 uint32) {
syscallNum = registers[2] // v0 syscallNum = registers[2] // v0
......
...@@ -124,8 +124,8 @@ ...@@ -124,8 +124,8 @@
"sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f" "sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f"
}, },
"src/cannon/MIPS.sol": { "src/cannon/MIPS.sol": {
"initCodeHash": "0xe9183ee3b69d9ec9594d6b3923d78c86c996cd738ccbc09675bb281284c060af", "initCodeHash": "0xe2cfbfa5d8587a6c3cf52686e29fb0d07e2764af0ef728825529f42ebdeacb5d",
"sourceCodeHash": "0x7c2eab73da8b2eeadba30eadb39f20e91307bc29218938fadfc5f73fadcf13bc" "sourceCodeHash": "0x231f42a05f0c8e5784eb112518afca0bb16a3689f317ce021b8390a0aa70377b"
}, },
"src/cannon/PreimageOracle.sol": { "src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090", "initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090",
......
...@@ -48,7 +48,7 @@ contract MIPS is ISemver { ...@@ -48,7 +48,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.4"; string public constant version = "1.1.0-beta.5";
/// @notice The preimage oracle contract. /// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE; IPreimageOracle internal immutable ORACLE;
...@@ -262,180 +262,32 @@ contract MIPS is ISemver { ...@@ -262,180 +262,32 @@ contract MIPS is ISemver {
// instruction fetch // instruction fetch
uint256 insnProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 0); uint256 insnProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 0);
uint32 insn = MIPSMemory.readMem(state.memRoot, state.pc, insnProofOffset); (uint32 insn, uint32 opcode, uint32 fun) =
uint32 opcode = insn >> 26; // 6-bits ins.getInstructionDetails(state.pc, state.memRoot, insnProofOffset);
// j-type j/jal
if (opcode == 2 || opcode == 3) {
// Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset
uint32 target = (state.nextPC & 0xF0000000) | (insn & 0x03FFFFFF) << 2;
return handleJumpAndReturnOutput(state, opcode == 2 ? 0 : 31, target);
}
// register fetch
uint32 rs; // source register 1 value
uint32 rt; // source register 2 / temp value
uint32 rtReg = (insn >> 16) & 0x1F;
// R-type or I-type (stores rt)
rs = state.registers[(insn >> 21) & 0x1F];
uint32 rdReg = rtReg;
if (opcode == 0 || opcode == 0x1c) {
// R-type (stores rd)
rt = 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 = ins.signExtend(insn & 0xFFFF, 16);
}
} else if (opcode >= 0x28 || opcode == 0x22 || opcode == 0x26) {
// store rt value with store
rt = state.registers[rtReg];
// store actual rt with lwl and lwr
rdReg = rtReg;
}
if ((opcode >= 4 && opcode < 8) || opcode == 1) {
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;
// memory fetch (all I-type)
// we do the load for stores also
uint32 mem;
if (opcode >= 0x20) {
// M[R[rs]+SignExtImm]
rs += ins.signExtend(insn & 0xFFFF, 16);
uint32 addr = rs & 0xFFFFFFFC;
uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1);
mem = MIPSMemory.readMem(state.memRoot, addr, memProofOffset);
if (opcode >= 0x28 && opcode != 0x30) {
// store
storeAddr = addr;
// store opcodes don't write back to a register
rdReg = 0;
}
}
// ALU
// Note: swr outputs more than 4 bytes without the mask 0xffFFffFF
uint32 val = ins.executeMipsInstruction(insn, rs, rt, mem) & 0xffFFffFF;
uint32 func = insn & 0x3f; // 6-bits
if (opcode == 0 && func >= 8 && func < 0x1c) {
if (func == 8 || func == 9) {
// jr/jalr
return handleJumpAndReturnOutput(state, func == 8 ? 0 : rdReg, rs);
}
if (func == 0xa) { // Handle syscall separately
// movz // syscall (can read and write)
return handleRdAndReturnOutput(state, rdReg, rs, rt == 0); if (opcode == 0 && fun == 0xC) {
} return handleSyscall(_localContext);
if (func == 0xb) {
// movn
return handleRdAndReturnOutput(state, rdReg, rs, rt != 0);
}
// syscall (can read and write)
if (func == 0xC) {
return handleSyscall(_localContext);
}
// lo and hi registers
// can write back
if (func >= 0x10 && func < 0x1c) {
st.CpuScalars memory cpu = getCpuScalars(state);
ins.handleHiLo({
_cpu: cpu,
_registers: state.registers,
_func: func,
_rs: rs,
_rt: rt,
_storeReg: rdReg
});
setStateCpuScalars(state, cpu);
return outputState();
}
}
// stupid sc, write a 1 to rt
if (opcode == 0x38 && rtReg != 0) {
state.registers[rtReg] = 1;
} }
// write memory // Exec the rest of the step logic
if (storeAddr != 0xFF_FF_FF_FF) { st.CpuScalars memory cpu = getCpuScalars(state);
uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1); (state.memRoot) = ins.execMipsCoreStepLogic({
state.memRoot = MIPSMemory.writeMem(storeAddr, memProofOffset, val); _cpu: cpu,
} _registers: state.registers,
_memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
_insn: insn,
_opcode: opcode,
_fun: fun
});
setStateCpuScalars(state, cpu);
// write back the value to destination register return outputState();
return handleRdAndReturnOutput(state, rdReg, val, true);
} }
} }
function handleJumpAndReturnOutput(
State memory _state,
uint32 _linkReg,
uint32 _dest
)
internal
returns (bytes32 out_)
{
st.CpuScalars memory cpu = getCpuScalars(_state);
ins.handleJump({ _cpu: cpu, _registers: _state.registers, _linkReg: _linkReg, _dest: _dest });
setStateCpuScalars(_state, cpu);
return outputState();
}
function handleRdAndReturnOutput(
State memory _state,
uint32 _storeReg,
uint32 _val,
bool _conditional
)
internal
returns (bytes32 out_)
{
st.CpuScalars memory cpu = getCpuScalars(_state);
ins.handleRd({
_cpu: cpu,
_registers: _state.registers,
_storeReg: _storeReg,
_val: _val,
_conditional: _conditional
});
setStateCpuScalars(_state, cpu);
return outputState();
}
function getCpuScalars(State memory _state) internal pure returns (st.CpuScalars memory) { 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 }); return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi });
} }
......
...@@ -93,8 +93,8 @@ library MIPSMemory { ...@@ -93,8 +93,8 @@ library MIPSMemory {
newMemRoot_ := node newMemRoot_ := node
} }
return newMemRoot_;
} }
return newMemRoot_;
} }
/// @notice Computes the offset of a memory proof in the calldata. /// @notice Computes the offset of a memory proof in the calldata.
...@@ -114,11 +114,13 @@ library MIPSMemory { ...@@ -114,11 +114,13 @@ library MIPSMemory {
/// @notice Validates that enough calldata is available to hold a full memory proof at the given offset /// @notice Validates that enough calldata is available to hold a full memory proof at the given offset
/// @param _proofStartOffset The index of the first byte of the target memory proof in calldata /// @param _proofStartOffset The index of the first byte of the target memory proof in calldata
function validateMemoryProofAvailability(uint256 _proofStartOffset) internal pure { function validateMemoryProofAvailability(uint256 _proofStartOffset) internal pure {
uint256 s = 0; unchecked {
assembly { uint256 s = 0;
s := calldatasize() assembly {
s := calldatasize()
}
// A memory proof consists of 28 bytes32 values - verify we have enough calldata
require(s >= (_proofStartOffset + 28 * 32), "check that there is enough calldata");
} }
// A memory proof consists of 28 bytes32 values - verify we have enough calldata
require(s >= (_proofStartOffset + 28 * 32), "check that there is enough calldata");
} }
} }
...@@ -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 = 0x28bF1582225713139c0E898326Db808B6484cFd4; address internal constant mipsAddress = 0xcdAdd729ca2319E8955240bDb61A6A6A956A7664;
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