Commit d837696f authored by Inphi's avatar Inphi Committed by GitHub

MIPS2.sol MT-FPVM Implementation (#11036)

* cannon: MIPS2 MT-FPVM contract

Add a smart contract implementing the multi-threaded Cannon

* Update packages/contracts-bedrock/src/cannon/libraries/MIPSSyscalls.sol
Co-authored-by: default avatarmbaxter <meredith@oplabs.co>

* cannon: Use common constant for BRK_START

* cannon: Define new constant FUTEX_EMPTY_ADDR

* cannon: Add SYS_ERROR_SIGNAL constant, fix futex wait ret val

* dedup syscall handling; rename timeout

* fix sys_clone bug

* use handler functions in onWaitComplete

* fix nits

* fix ETIMEDOUT constant

* remove leftover console import

* traverse right if left is empty on futex_wake syscall

* Update packages/contracts-bedrock/test/cannon/MIPS2.t.sol
Co-authored-by: default avatarmbaxter <meredith@oplabs.co>

* fix traverseRight updates at popThread

* exit syscall is exit_group if last thread

* simplify wakeup logic; traverse fully before any other operation

* remove dup logic for wakeup traversal end

* fuzz thread.exited in wakeup tests

* update semver-lock; abi snapshots

* implement unused syscalls

* rebase; fix clone args

* update semver-lock

* handle munmap

* add comment on unimplemented syscalls

* add mising snapshots

---------
Co-authored-by: default avatarmbaxter <meredith@oplabs.co>
parent 55b3e492
......@@ -124,8 +124,12 @@
"sourceCodeHash": "0x3ff4a3f21202478935412d47fd5ef7f94a170402ddc50e5c062013ce5544c83f"
},
"src/cannon/MIPS.sol": {
"initCodeHash": "0xe2cfbfa5d8587a6c3cf52686e29fb0d07e2764af0ef728825529f42ebdeacb5d",
"sourceCodeHash": "0x231f42a05f0c8e5784eb112518afca0bb16a3689f317ce021b8390a0aa70377b"
"initCodeHash": "0x644a4167210bee38884419c2af618f3cf9533f959bda41aad3cfed4b6f325b1b",
"sourceCodeHash": "0xe28a16aad04ebcadba631a1920cac6e492c18f0d866836610be22779f3f04bfc"
},
"src/cannon/MIPS2.sol": {
"initCodeHash": "0xdcd3bd4bbf8c119ca2d8a435da322ecc5c55a9e2a308789064bf19105933aa6c",
"sourceCodeHash": "0xa1406c94c785b094432aed8af2e1c5b42644e2a0878d48f89b488138e079ee66"
},
"src/cannon/PreimageOracle.sol": {
"initCodeHash": "0xe5db668fe41436f53995e910488c7c140766ba8745e19743773ebab508efd090",
......
......@@ -10,19 +10,6 @@
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BRK_START",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "oracle",
......
[
{
"inputs": [
{
"internalType": "contract IPreimageOracle",
"name": "_oracle",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_stateData",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "_localContext",
"type": "bytes32"
}
],
"name": "step",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -43,9 +43,6 @@ contract MIPS is ISemver {
uint32[32] registers;
}
/// @notice Start of the data segment.
uint32 public constant BRK_START = 0x40000000;
/// @notice The semantic version of the MIPS contract.
/// @custom:semver 1.0.1
string public constant version = "1.1.0-beta.5";
......@@ -143,7 +140,7 @@ contract MIPS is ISemver {
}
// Load the syscall numbers and args from the registers
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2) = sys.getSyscallArgs(state.registers);
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2,) = sys.getSyscallArgs(state.registers);
uint32 v0 = 0;
uint32 v1 = 0;
......@@ -152,7 +149,7 @@ contract MIPS is ISemver {
(v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap);
} else if (syscall_no == sys.SYS_BRK) {
// brk: Returns a fixed address for the program break at 0x40000000
v0 = BRK_START;
v0 = sys.BRK_START;
} else if (syscall_no == sys.SYS_CLONE) {
// clone (not supported) returns 1
v0 = 1;
......@@ -162,17 +159,18 @@ contract MIPS is ISemver {
state.exitCode = uint8(a0);
return outputState();
} else if (syscall_no == sys.SYS_READ) {
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead({
_a0: a0,
_a1: a1,
_a2: a2,
_preimageKey: state.preimageKey,
_preimageOffset: state.preimageOffset,
_localContext: _localContext,
_oracle: ORACLE,
_proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
_memRoot: state.memRoot
sys.SysReadParams memory args = sys.SysReadParams({
a0: a0,
a1: a1,
a2: a2,
preimageKey: state.preimageKey,
preimageOffset: state.preimageOffset,
localContext: _localContext,
oracle: ORACLE,
proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
memRoot: state.memRoot
});
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args);
} else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0,
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ISemver } from "src/universal/ISemver.sol";
import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol";
import { MIPSMemory } from "src/cannon/libraries/MIPSMemory.sol";
import { MIPSSyscalls as sys } from "src/cannon/libraries/MIPSSyscalls.sol";
import { MIPSState as st } from "src/cannon/libraries/MIPSState.sol";
import { MIPSInstructions as ins } from "src/cannon/libraries/MIPSInstructions.sol";
/// @title MIPS2
/// @notice The MIPS2 contract emulates a single MIPS instruction.
/// It differs from MIPS.sol in that it supports multi-threading.
contract MIPS2 is ISemver {
/// @notice The thread context.
/// Total state size: 4 + 1 + 1 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 32 * 4 = 166 bytes
struct ThreadState {
// metadata
uint32 threadID;
uint8 exitCode;
bool exited;
// state
uint32 futexAddr;
uint32 futexVal;
uint64 futexTimeoutStep;
uint32 pc;
uint32 nextPC;
uint32 lo;
uint32 hi;
uint32[32] registers;
}
/// @notice Stores the VM state.
/// Total state size: 32 + 32 + 4 + 4 + 1 + 1 + 8 + 8 + 4 + 1 + 32 + 32 + 4 = 163 bytes
/// If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
struct State {
bytes32 memRoot;
bytes32 preimageKey;
uint32 preimageOffset;
uint32 heap;
uint8 exitCode;
bool exited;
uint64 step;
uint64 stepsSinceLastContextSwitch;
uint32 wakeup;
bool traverseRight;
bytes32 leftThreadStack;
bytes32 rightThreadStack;
uint32 nextThreadID;
}
/// @notice The semantic version of the MIPS2 contract.
/// @custom:semver 0.0.1-beta
string public constant version = "0.0.1-beta";
/// @notice The preimage oracle contract.
IPreimageOracle internal immutable ORACLE;
// The offset of the start of proof calldata (_threadWitness.offset) in the step() function
uint256 internal constant THREAD_PROOF_OFFSET = 356;
// The offset of the start of proof calldata (_memProof.offset) in the step() function
uint256 internal constant MEM_PROOF_OFFSET = THREAD_PROOF_OFFSET + 166 + 32;
// The empty thread root - keccak256(bytes32(0) ++ bytes32(0))
bytes32 internal constant EMPTY_THREAD_ROOT = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5";
// State memory offset allocated during step
uint256 internal constant STATE_MEM_OFFSET = 0x80;
// ThreadState memory offset allocated during step
uint256 internal constant TC_MEM_OFFSET = 0x220;
// VM Status Panic exit code
uint8 internal constant VM_STATUS_PANIC = 0x3;
/// @param _oracle The address of the preimage oracle contract.
constructor(IPreimageOracle _oracle) {
ORACLE = _oracle;
}
/// @notice Executes a single step of the multi-threaded vm.
/// Will revert if any required input state is missing.
/// @param _stateData The encoded state witness data.
/// @param _proof The encoded proof data: <<thread_context, inner_root>, <memory proof>.
/// Contains the thread context witness and the memory proof data for leaves within the MIPS VM's
/// memory.
/// The thread context witness is a packed tuple of the thread context and the immediate inner root of
/// the current thread stack.
/// @param _localContext The local key context for the preimage oracle. Optional, can be set as a constant
/// if the caller only requires one set of local keys.
function step(bytes calldata _stateData, bytes calldata _proof, bytes32 _localContext) public returns (bytes32) {
unchecked {
State memory state;
ThreadState memory thread;
assembly {
if iszero(eq(state, STATE_MEM_OFFSET)) {
// expected state mem offset check
revert(0, 0)
}
if iszero(eq(thread, TC_MEM_OFFSET)) {
// expected thread mem offset check
revert(0, 0)
}
if iszero(eq(mload(0x40), shl(5, 60))) {
// 4 + 13 state slots + 43 thread slots = 60 expected memory check
revert(0, 0)
}
if iszero(eq(_stateData.offset, 132)) {
// 32*4+4=132 expected state data offset
revert(0, 0)
}
if iszero(eq(_proof.offset, THREAD_PROOF_OFFSET)) {
// _stateData.offset+192+32=356 expected thread proof offset
revert(0, 0)
}
function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut {
// calldata is packed, thus starting left-aligned, shift-right to pad and right-align
let w := shr(shl(3, sub(32, size)), calldataload(callOffset))
mstore(memOffset, w)
callOffsetOut := add(callOffset, size)
memOffsetOut := add(memOffset, 32)
}
// Unpack state from calldata into memory
let c := _stateData.offset // calldata offset
let m := STATE_MEM_OFFSET // mem offset
c, m := putField(c, m, 32) // memRoot
c, m := putField(c, m, 32) // preimageKey
c, m := putField(c, m, 4) // preimageOffset
c, m := putField(c, m, 4) // heap
c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited
c, m := putField(c, m, 8) // step
c, m := putField(c, m, 8) // stepsSinceLastContextSwitch
c, m := putField(c, m, 4) // wakeup
c, m := putField(c, m, 1) // traverseRight
c, m := putField(c, m, 32) // leftThreadStack
c, m := putField(c, m, 32) // rightThreadStack
c, m := putField(c, m, 4) // nextThreadID
}
if (state.exited) {
// thread state is unchanged
return outputState();
}
if (state.leftThreadStack == EMPTY_THREAD_ROOT && state.rightThreadStack == EMPTY_THREAD_ROOT) {
revert("MIPS2: illegal vm state");
}
state.step += 1;
setThreadStateFromCalldata(thread);
validateCalldataThreadWitness(state, thread);
// Search for the first thread blocked by the wakeup call, if wakeup is set
// Don't allow regular execution until we resolved if we have woken up any thread.
if (state.wakeup != sys.FUTEX_EMPTY_ADDR) {
if (state.wakeup == thread.futexAddr) {
// completed wake traverssal
// resume execution on woken up thread
state.wakeup = sys.FUTEX_EMPTY_ADDR;
return outputState();
} else {
bool traversingRight = state.traverseRight;
bool changedDirections = preemptThread(state, thread);
if (traversingRight && changedDirections) {
// then we've completed wake traversal
// resume thread execution
state.wakeup = sys.FUTEX_EMPTY_ADDR;
}
return outputState();
}
}
if (thread.exited) {
popThread(state);
return outputState();
}
// check if thread is blocked on a futex
if (thread.futexAddr != sys.FUTEX_EMPTY_ADDR) {
// if set, then check futex
// check timeout first
if (state.step > thread.futexTimeoutStep) {
// timeout! Allow execution
return onWaitComplete(state, thread, true);
} else {
uint32 mem = MIPSMemory.readMem(
state.memRoot, thread.futexAddr & 0xFFffFFfc, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
if (thread.futexVal == mem) {
// still got expected value, continue sleeping, try next thread.
preemptThread(state, thread);
return outputState();
} else {
// wake thread up, the value at its address changed!
// Userspace can turn thread back to sleep if it was too sporadic.
return onWaitComplete(state, thread, false);
}
}
}
if (state.stepsSinceLastContextSwitch == sys.SCHED_QUANTUM) {
preemptThread(state, thread);
return outputState();
}
state.stepsSinceLastContextSwitch += 1;
// instruction fetch
uint256 insnProofOffset = MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 0);
(uint32 insn, uint32 opcode, uint32 fun) =
ins.getInstructionDetails(thread.pc, state.memRoot, insnProofOffset);
// Handle syscall separately
// syscall (can read and write)
if (opcode == 0 && fun == 0xC) {
return handleSyscall(_localContext);
}
// Exec the rest of the step logic
st.CpuScalars memory cpu = getCpuScalars(thread);
(state.memRoot) = ins.execMipsCoreStepLogic({
_cpu: cpu,
_registers: thread.registers,
_memRoot: state.memRoot,
_memProofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
_insn: insn,
_opcode: opcode,
_fun: fun
});
setStateCpuScalars(thread, cpu);
updateCurrentThreadRoot();
return outputState();
}
}
function handleSyscall(bytes32 _localContext) internal returns (bytes32 out_) {
unchecked {
// Load state from memory offsets to reduce stack pressure
State memory state;
ThreadState memory thread;
assembly {
state := STATE_MEM_OFFSET
thread := TC_MEM_OFFSET
}
// Load the syscall numbers and args from the registers
(uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2, uint32 a3) = sys.getSyscallArgs(thread.registers);
// Syscalls that are unimplemented but known return with v0=0 and v1=0
uint32 v0 = 0;
uint32 v1 = 0;
if (syscall_no == sys.SYS_MMAP) {
(v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap);
} else if (syscall_no == sys.SYS_BRK) {
// brk: Returns a fixed address for the program break at 0x40000000
v0 = sys.BRK_START;
} else if (syscall_no == sys.SYS_CLONE) {
if (sys.VALID_SYS_CLONE_FLAGS != a0) {
state.exited = true;
state.exitCode = VM_STATUS_PANIC;
return outputState();
}
v0 = state.nextThreadID;
v1 = 0;
ThreadState memory newThread;
newThread.threadID = state.nextThreadID;
newThread.exitCode = 0;
newThread.exited = false;
newThread.futexAddr = sys.FUTEX_EMPTY_ADDR;
newThread.futexVal = 0;
newThread.futexTimeoutStep = 0;
newThread.pc = thread.nextPC;
newThread.nextPC = thread.nextPC + 4;
newThread.lo = thread.lo;
newThread.hi = thread.hi;
for (uint256 i; i < 32; i++) {
newThread.registers[i] = thread.registers[i];
}
newThread.registers[29] = a1; // set stack pointer
// the child will perceive a 0 value as returned value instead, and no error
newThread.registers[2] = 0;
newThread.registers[7] = 0;
state.nextThreadID++;
// Preempt this thread for the new one. But not before updating PCs
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
updateCurrentThreadRoot();
pushThread(state, newThread);
return outputState();
} else if (syscall_no == sys.SYS_EXIT_GROUP) {
// exit group: Sets the Exited and ExitCode states to true and argument 0.
state.exited = true;
state.exitCode = uint8(a0);
updateCurrentThreadRoot();
return outputState();
} else if (syscall_no == sys.SYS_READ) {
sys.SysReadParams memory args = sys.SysReadParams({
a0: a0,
a1: a1,
a2: a2,
preimageKey: state.preimageKey,
preimageOffset: state.preimageOffset,
localContext: _localContext,
oracle: ORACLE,
proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
memRoot: state.memRoot
});
(v0, v1, state.preimageOffset, state.memRoot) = sys.handleSysRead(args);
} else if (syscall_no == sys.SYS_WRITE) {
(v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
_a0: a0,
_a1: a1,
_a2: a2,
_preimageKey: state.preimageKey,
_preimageOffset: state.preimageOffset,
_proofOffset: MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1),
_memRoot: state.memRoot
});
} else if (syscall_no == sys.SYS_FCNTL) {
(v0, v1) = sys.handleSysFcntl(a0, a1);
} else if (syscall_no == sys.SYS_GETTID) {
v0 = thread.threadID;
v1 = 0;
} else if (syscall_no == sys.SYS_EXIT) {
thread.exited = true;
thread.exitCode = uint8(a0);
if (lastThreadRemaining(state)) {
state.exited = true;
state.exitCode = uint8(a0);
}
updateCurrentThreadRoot();
return outputState();
} else if (syscall_no == sys.SYS_FUTEX) {
// args: a0 = addr, a1 = op, a2 = val, a3 = timeout
if (a1 == sys.FUTEX_WAIT_PRIVATE) {
thread.futexAddr = a0;
uint32 mem = MIPSMemory.readMem(
state.memRoot, a0 & 0xFFffFFfc, MIPSMemory.memoryProofOffset(MEM_PROOF_OFFSET, 1)
);
if (mem != a2) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EAGAIN;
} else {
thread.futexVal = a2;
thread.futexTimeoutStep = a3 == 0 ? sys.FUTEX_NO_TIMEOUT : state.step + sys.FUTEX_TIMEOUT_STEPS;
// Leave cpu scalars as-is. This instruction will be completed by `onWaitComplete`
updateCurrentThreadRoot();
return outputState();
}
} else if (a1 == sys.FUTEX_WAKE_PRIVATE) {
// Trigger thread traversal starting from the left stack until we find one waiting on the wakeup
// address
state.wakeup = a0;
// Don't indicate to the program that we've woken up a waiting thread, as there are no guarantees.
// The woken up thread should indicate this in userspace.
v0 = 0;
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
state.traverseRight = state.leftThreadStack == EMPTY_THREAD_ROOT;
return outputState();
} else {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EINVAL;
}
} else if (syscall_no == sys.SYS_SCHED_YIELD || syscall_no == sys.SYS_NANOSLEEP) {
v0 = 0;
v1 = 0;
st.CpuScalars memory cpu0 = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu0, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu0);
preemptThread(state, thread);
return outputState();
} else if (syscall_no == sys.SYS_OPEN) {
v0 = sys.SYS_ERROR_SIGNAL;
v1 = sys.EBADF;
} else if (syscall_no == sys.SYS_CLOCK_GETTIME) {
// ignored
} else if (syscall_no == sys.SYS_GET_AFFINITY) {
// ignored
} else if (syscall_no == sys.SYS_MADVISE) {
// ignored
} else if (syscall_no == sys.SYS_RTSIGPROCMASK) {
// ignored
} else if (syscall_no == sys.SYS_SIGALTSTACK) {
// ignored
} else if (syscall_no == sys.SYS_RTSIGACTION) {
// ignored
} else if (syscall_no == sys.SYS_PRLIMIT64) {
// ignored
} else if (syscall_no == sys.SYS_CLOSE) {
// ignored
} else if (syscall_no == sys.SYS_PREAD64) {
// ignored
} else if (syscall_no == sys.SYS_FSTAT64) {
// ignored
} else if (syscall_no == sys.SYS_OPENAT) {
// ignored
} else if (syscall_no == sys.SYS_READLINK) {
// ignored
} else if (syscall_no == sys.SYS_READLINKAT) {
// ignored
} else if (syscall_no == sys.SYS_IOCTL) {
// ignored
} else if (syscall_no == sys.SYS_EPOLLCREATE1) {
// ignored
} else if (syscall_no == sys.SYS_PIPE2) {
// ignored
} else if (syscall_no == sys.SYS_EPOLLCTL) {
// ignored
} else if (syscall_no == sys.SYS_EPOLLPWAIT) {
// ignored
} else if (syscall_no == sys.SYS_GETRANDOM) {
// ignored
} else if (syscall_no == sys.SYS_UNAME) {
// ignored
} else if (syscall_no == sys.SYS_STAT64) {
// ignored
} else if (syscall_no == sys.SYS_GETUID) {
// ignored
} else if (syscall_no == sys.SYS_GETGID) {
// ignored
} else if (syscall_no == sys.SYS_LLSEEK) {
// ignored
} else if (syscall_no == sys.SYS_MINCORE) {
// ignored
} else if (syscall_no == sys.SYS_TGKILL) {
// ignored
} else if (syscall_no == sys.SYS_SETITIMER) {
// ignored
} else if (syscall_no == sys.SYS_TIMERCREATE) {
// ignored
} else if (syscall_no == sys.SYS_TIMERSETTIME) {
// ignored
} else if (syscall_no == sys.SYS_TIMERDELETE) {
// ignored
} else if (syscall_no == sys.SYS_CLOCKGETTIME) {
// ignored
} else if (syscall_no == sys.SYS_MUNMAP) {
// ignored
} else {
revert("MIPS2: unimplemented syscall");
}
st.CpuScalars memory cpu = getCpuScalars(thread);
sys.handleSyscallUpdates(cpu, thread.registers, v0, v1);
setStateCpuScalars(thread, cpu);
updateCurrentThreadRoot();
out_ = outputState();
}
}
/// @notice Computes the hash of the MIPS state.
/// @return out_ The hashed MIPS state.
function outputState() internal returns (bytes32 out_) {
assembly {
// copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data
function copyMem(from, to, size) -> fromOut, toOut {
mstore(to, mload(add(from, sub(32, size))))
fromOut := add(from, 32)
toOut := add(to, size)
}
// From points to the MIPS State
let from := STATE_MEM_OFFSET
// Copy to the free memory pointer
let start := mload(0x40)
let to := start
// Copy state to free memory
from, to := copyMem(from, to, 32) // memRoot
from, to := copyMem(from, to, 32) // preimageKey
from, to := copyMem(from, to, 4) // preimageOffset
from, to := copyMem(from, to, 4) // heap
let exitCode := mload(from)
from, to := copyMem(from, to, 1) // exitCode
let exited := mload(from)
from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 8) // step
from, to := copyMem(from, to, 8) // stepsSinceLastContextSwitch
from, to := copyMem(from, to, 4) // wakeup
from, to := copyMem(from, to, 1) // traverseRight
from, to := copyMem(from, to, 32) // leftThreadStack
from, to := copyMem(from, to, 32) // rightThreadStack
from, to := copyMem(from, to, 4) // nextThreadID
// Clean up end of memory
mstore(to, 0)
// Log the resulting MIPS state, for debugging
log0(start, sub(to, start))
// Determine the VM status
let status := 0
switch exited
case 1 {
switch exitCode
// VMStatusValid
case 0 { status := 0 }
// VMStatusInvalid
case 1 { status := 1 }
// VMStatusPanic
default { status := 2 }
}
// VMStatusUnfinished
default { status := 3 }
// Compute the hash of the resulting MIPS state and set the status byte
out_ := keccak256(start, sub(to, start))
out_ := or(and(not(shl(248, 0xFF)), out_), shl(248, status))
}
}
/// @notice Updates the current thread stack root via inner thread root in calldata
function updateCurrentThreadRoot() internal pure {
State memory state;
ThreadState memory thread;
assembly {
state := STATE_MEM_OFFSET
thread := TC_MEM_OFFSET
}
bytes32 updatedRoot = computeThreadRoot(loadCalldataInnerThreadRoot(), thread);
if (state.traverseRight) {
state.rightThreadStack = updatedRoot;
} else {
state.leftThreadStack = updatedRoot;
}
}
/// @notice Completes the FUTEX_WAIT syscall.
function onWaitComplete(
State memory _state,
ThreadState memory _thread,
bool _isTimedOut
)
internal
returns (bytes32 out_)
{
// Clear the futex state
_thread.futexAddr = sys.FUTEX_EMPTY_ADDR;
_thread.futexVal = 0;
_thread.futexTimeoutStep = 0;
// Complete the FUTEX_WAIT syscall
uint32 v0 = _isTimedOut ? sys.SYS_ERROR_SIGNAL : 0;
// set errno
uint32 v1 = _isTimedOut ? sys.ETIMEDOUT : 0;
st.CpuScalars memory cpu = getCpuScalars(_thread);
sys.handleSyscallUpdates(cpu, _thread.registers, v0, v1);
setStateCpuScalars(_thread, cpu);
_state.wakeup = sys.FUTEX_EMPTY_ADDR;
updateCurrentThreadRoot();
out_ = outputState();
}
/// @notice Preempts the current thread for another and updates the VM state.
/// It reads the inner thread root from calldata to update the current thread stack root.
function preemptThread(
State memory _state,
ThreadState memory _thread
)
internal
pure
returns (bool _changedDirections)
{
// pop thread from the current stack and push to the other stack
if (_state.traverseRight) {
require(_state.rightThreadStack != EMPTY_THREAD_ROOT, "empty right thread stack");
_state.rightThreadStack = loadCalldataInnerThreadRoot();
_state.leftThreadStack = computeThreadRoot(_state.leftThreadStack, _thread);
} else {
require(_state.leftThreadStack != EMPTY_THREAD_ROOT, "empty left thread stack");
_state.leftThreadStack = loadCalldataInnerThreadRoot();
_state.rightThreadStack = computeThreadRoot(_state.rightThreadStack, _thread);
}
bytes32 current = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack;
if (current == EMPTY_THREAD_ROOT) {
_state.traverseRight = !_state.traverseRight;
_changedDirections = true;
}
_state.stepsSinceLastContextSwitch = 0;
}
/// @notice Pushes a thread to the current thread stack.
function pushThread(State memory _state, ThreadState memory _thread) internal pure {
if (_state.traverseRight) {
_state.rightThreadStack = computeThreadRoot(_state.rightThreadStack, _thread);
} else {
_state.leftThreadStack = computeThreadRoot(_state.leftThreadStack, _thread);
}
_state.stepsSinceLastContextSwitch = 0;
}
/// @notice Removes the current thread from the stack.
function popThread(State memory _state) internal pure {
if (_state.traverseRight) {
_state.rightThreadStack = loadCalldataInnerThreadRoot();
} else {
_state.leftThreadStack = loadCalldataInnerThreadRoot();
}
bytes32 current = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack;
if (current == EMPTY_THREAD_ROOT) {
_state.traverseRight = !_state.traverseRight;
}
_state.stepsSinceLastContextSwitch = 0;
}
/// @notice Returns true if the number of threads is 1
function lastThreadRemaining(State memory _state) internal pure returns (bool out_) {
bytes32 inactiveStack = _state.traverseRight ? _state.leftThreadStack : _state.rightThreadStack;
bool currentStackIsAlmostEmpty = loadCalldataInnerThreadRoot() == EMPTY_THREAD_ROOT;
return inactiveStack == EMPTY_THREAD_ROOT && currentStackIsAlmostEmpty;
}
function computeThreadRoot(bytes32 _currentRoot, ThreadState memory _thread) internal pure returns (bytes32 _out) {
// w_i = hash(w_0 ++ hash(thread))
bytes32 threadRoot = outputThreadState(_thread);
_out = keccak256(abi.encodePacked(_currentRoot, threadRoot));
}
function outputThreadState(ThreadState memory _thread) internal pure returns (bytes32 out_) {
assembly {
// copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data
function copyMem(from, to, size) -> fromOut, toOut {
mstore(to, mload(add(from, sub(32, size))))
fromOut := add(from, 32)
toOut := add(to, size)
}
// From points to the ThreadState
let from := _thread
// Copy to the free memory pointer
let start := mload(0x40)
let to := start
// Copy state to free memory
from, to := copyMem(from, to, 4) // threadID
from, to := copyMem(from, to, 1) // exitCode
from, to := copyMem(from, to, 1) // exited
from, to := copyMem(from, to, 4) // futexAddr
from, to := copyMem(from, to, 4) // futexVal
from, to := copyMem(from, to, 8) // futexTimeoutStep
from, to := copyMem(from, to, 4) // pc
from, to := copyMem(from, to, 4) // nextPC
from, to := copyMem(from, to, 4) // lo
from, to := copyMem(from, to, 4) // hi
from := mload(from) // offset to registers
// Copy registers
for { let i := 0 } lt(i, 32) { i := add(i, 1) } { from, to := copyMem(from, to, 4) }
// Clean up end of memory
mstore(to, 0)
// Compute the hash of the resulting ThreadState
out_ := keccak256(start, sub(to, start))
}
}
function getCpuScalars(ThreadState memory _tc) internal pure returns (st.CpuScalars memory cpu_) {
cpu_ = st.CpuScalars({ pc: _tc.pc, nextPC: _tc.nextPC, lo: _tc.lo, hi: _tc.hi });
}
function setStateCpuScalars(ThreadState memory _tc, st.CpuScalars memory _cpu) internal pure {
_tc.pc = _cpu.pc;
_tc.nextPC = _cpu.nextPC;
_tc.lo = _cpu.lo;
_tc.hi = _cpu.hi;
}
/// @notice Validates the thread witness in calldata against the current thread.
function validateCalldataThreadWitness(State memory _state, ThreadState memory _thread) internal pure {
bytes32 witnessRoot = computeThreadRoot(loadCalldataInnerThreadRoot(), _thread);
bytes32 expectedRoot = _state.traverseRight ? _state.rightThreadStack : _state.leftThreadStack;
require(expectedRoot == witnessRoot, "invalid thread witness");
}
/// @notice Sets the thread context from calldata.
function setThreadStateFromCalldata(ThreadState memory _thread) internal pure {
uint256 s = 0;
assembly {
s := calldatasize()
}
// verify we have enough calldata
require(s >= (THREAD_PROOF_OFFSET + 166), "insufficient calldata for thread witness");
unchecked {
assembly {
function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut {
// calldata is packed, thus starting left-aligned, shift-right to pad and right-align
let w := shr(shl(3, sub(32, size)), calldataload(callOffset))
mstore(memOffset, w)
callOffsetOut := add(callOffset, size)
memOffsetOut := add(memOffset, 32)
}
let c := THREAD_PROOF_OFFSET
let m := _thread
c, m := putField(c, m, 4) // threadID
c, m := putField(c, m, 1) // exitCode
c, m := putField(c, m, 1) // exited
c, m := putField(c, m, 4) // futexAddr
c, m := putField(c, m, 4) // futexVal
c, m := putField(c, m, 8) // futexTimeoutStep
c, m := putField(c, m, 4) // pc
c, m := putField(c, m, 4) // nextPC
c, m := putField(c, m, 4) // lo
c, m := putField(c, m, 4) // hi
m := mload(m) // offset to registers
// Unpack register calldata into memory
for { let i := 0 } lt(i, 32) { i := add(i, 1) } { c, m := putField(c, m, 4) }
}
}
}
/// @notice Loads the inner root for the current thread hash onion from calldata.
function loadCalldataInnerThreadRoot() internal pure returns (bytes32 innerThreadRoot_) {
uint256 s = 0;
assembly {
s := calldatasize()
innerThreadRoot_ := calldataload(add(THREAD_PROOF_OFFSET, 166))
}
// verify we have enough calldata
require(s >= (THREAD_PROOF_OFFSET + 198), "insufficient calldata for thread witness"); // 166 + 32
}
}
......@@ -7,6 +7,27 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
import { PreimageKeyLib } from "src/cannon/PreimageKeyLib.sol";
library MIPSSyscalls {
struct SysReadParams {
/// @param _a0 The file descriptor.
uint32 a0;
/// @param _a1 The memory location where data should be read to.
uint32 a1;
/// @param _a2 The number of bytes to read from the file
uint32 a2;
/// @param _preimageKey The key of the preimage to read.
bytes32 preimageKey;
/// @param _preimageOffset The offset of the preimage to read.
uint32 preimageOffset;
/// @param _localContext The local context for the preimage key.
bytes32 localContext;
/// @param _oracle The address of the preimage oracle.
IPreimageOracle oracle;
/// @param _proofOffset The offset of the memory proof in calldata.
uint256 proofOffset;
/// @param _memRoot The current memory root.
bytes32 memRoot;
}
uint32 internal constant SYS_MMAP = 4090;
uint32 internal constant SYS_BRK = 4045;
uint32 internal constant SYS_CLONE = 4120;
......@@ -14,6 +35,47 @@ library MIPSSyscalls {
uint32 internal constant SYS_READ = 4003;
uint32 internal constant SYS_WRITE = 4004;
uint32 internal constant SYS_FCNTL = 4055;
uint32 internal constant SYS_EXIT = 4001;
uint32 internal constant SYS_SCHED_YIELD = 4162;
uint32 internal constant SYS_GETTID = 4222;
uint32 internal constant SYS_FUTEX = 4238;
uint32 internal constant SYS_OPEN = 4005;
uint32 internal constant SYS_NANOSLEEP = 4166;
// unused syscalls
uint32 internal constant SYS_CLOCK_GETTIME = 4263;
uint32 internal constant SYS_GET_AFFINITY = 4240;
uint32 internal constant SYS_GETAFFINITY = 4240;
uint32 internal constant SYS_MADVISE = 4218;
uint32 internal constant SYS_RTSIGPROCMASK = 4195;
uint32 internal constant SYS_SIGALTSTACK = 4206;
uint32 internal constant SYS_RTSIGACTION = 4194;
uint32 internal constant SYS_PRLIMIT64 = 4338;
uint32 internal constant SYS_CLOSE = 4006;
uint32 internal constant SYS_PREAD64 = 4200;
uint32 internal constant SYS_FSTAT64 = 4215;
uint32 internal constant SYS_OPENAT = 4288;
uint32 internal constant SYS_READLINK = 4085;
uint32 internal constant SYS_READLINKAT = 4298;
uint32 internal constant SYS_IOCTL = 4054;
uint32 internal constant SYS_EPOLLCREATE1 = 4326;
uint32 internal constant SYS_PIPE2 = 4328;
uint32 internal constant SYS_EPOLLCTL = 4249;
uint32 internal constant SYS_EPOLLPWAIT = 4313;
uint32 internal constant SYS_GETRANDOM = 4353;
uint32 internal constant SYS_UNAME = 4122;
uint32 internal constant SYS_STAT64 = 4213;
uint32 internal constant SYS_GETUID = 4024;
uint32 internal constant SYS_GETGID = 4047;
uint32 internal constant SYS_LLSEEK = 4140;
uint32 internal constant SYS_MINCORE = 4217;
uint32 internal constant SYS_TGKILL = 4266;
// profiling-related syscalls - ignored
uint32 internal constant SYS_SETITIMER = 4104;
uint32 internal constant SYS_TIMERCREATE = 4257;
uint32 internal constant SYS_TIMERSETTIME = 4258;
uint32 internal constant SYS_TIMERDELETE = 4261;
uint32 internal constant SYS_CLOCKGETTIME = 4263;
uint32 internal constant SYS_MUNMAP = 4091;
uint32 internal constant FD_STDIN = 0;
uint32 internal constant FD_STDOUT = 1;
......@@ -23,8 +85,43 @@ library MIPSSyscalls {
uint32 internal constant FD_PREIMAGE_READ = 5;
uint32 internal constant FD_PREIMAGE_WRITE = 6;
uint32 internal constant SYS_ERROR_SIGNAL = 0xFF_FF_FF_FF;
uint32 internal constant EBADF = 0x9;
uint32 internal constant EINVAL = 0x16;
uint32 internal constant EAGAIN = 0xb;
uint32 internal constant ETIMEDOUT = 0x91;
uint32 internal constant FUTEX_WAIT_PRIVATE = 128;
uint32 internal constant FUTEX_WAKE_PRIVATE = 129;
uint32 internal constant FUTEX_TIMEOUT_STEPS = 10000;
uint64 internal constant FUTEX_NO_TIMEOUT = type(uint64).max;
uint32 internal constant FUTEX_EMPTY_ADDR = 0xFF_FF_FF_FF;
uint32 internal constant SCHED_QUANTUM = 100_000;
/// @notice Start of the data segment.
uint32 public constant BRK_START = 0x40000000;
// SYS_CLONE flags
uint32 internal constant CLONE_VM = 0x100;
uint32 internal constant CLONE_FS = 0x200;
uint32 internal constant CLONE_FILES = 0x400;
uint32 internal constant CLONE_SIGHAND = 0x800;
uint32 internal constant CLONE_PTRACE = 0x2000;
uint32 internal constant CLONE_VFORK = 0x4000;
uint32 internal constant CLONE_PARENT = 0x8000;
uint32 internal constant CLONE_THREAD = 0x10000;
uint32 internal constant CLONE_NEWNS = 0x20000;
uint32 internal constant CLONE_SYSVSEM = 0x40000;
uint32 internal constant CLONE_SETTLS = 0x80000;
uint32 internal constant CLONE_PARENTSETTID = 0x100000;
uint32 internal constant CLONE_CHILDCLEARTID = 0x200000;
uint32 internal constant CLONE_UNTRACED = 0x800000;
uint32 internal constant CLONE_CHILDSETTID = 0x1000000;
uint32 internal constant CLONE_STOPPED = 0x2000000;
uint32 internal constant CLONE_NEWUTS = 0x4000000;
uint32 internal constant CLONE_NEWIPC = 0x8000000;
uint32 internal constant VALID_SYS_CLONE_FLAGS =
CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD;
/// @notice Extract syscall num and arguments from registers.
/// @param _registers The cpu registers.
......@@ -32,10 +129,11 @@ library MIPSSyscalls {
/// @return a0_ The first argument available to the syscall operation.
/// @return a1_ The second argument available to the syscall operation.
/// @return a2_ The third argument available to the syscall operation.
/// @return a3_ The fourth argument available to the syscall operation.
function getSyscallArgs(uint32[32] memory _registers)
internal
pure
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_)
returns (uint32 sysCallNum_, uint32 a0_, uint32 a1_, uint32 a2_, uint32 a3_)
{
unchecked {
sysCallNum_ = _registers[2];
......@@ -43,8 +141,9 @@ library MIPSSyscalls {
a0_ = _registers[4];
a1_ = _registers[5];
a2_ = _registers[6];
a3_ = _registers[7];
return (sysCallNum_, a0_, a1_, a2_);
return (sysCallNum_, a0_, a1_, a2_, a3_);
}
}
......@@ -85,30 +184,12 @@ library MIPSSyscalls {
}
/// @notice Like a Linux read syscall. Splits unaligned reads into aligned reads.
/// @param _a0 The file descriptor.
/// @param _a1 The memory location where data should be read to.
/// @param _a2 The number of bytes to read from the file
/// @param _preimageKey The key of the preimage to read.
/// @param _preimageOffset The offset of the preimage to read.
/// @param _localContext The local context for the preimage key.
/// @param _oracle The address of the preimage oracle.
/// @param _proofOffset The offset of the memory proof in calldata.
/// @param _memRoot The current memory root.
/// Args are provided as a struct to reduce stack pressure.
/// @return v0_ The number of bytes read, -1 on error.
/// @return v1_ The error code, 0 if there is no error.
/// @return newPreimageOffset_ The new value for the preimage offset.
/// @return newMemRoot_ The new memory root.
function handleSysRead(
uint32 _a0,
uint32 _a1,
uint32 _a2,
bytes32 _preimageKey,
uint32 _preimageOffset,
bytes32 _localContext,
IPreimageOracle _oracle,
uint256 _proofOffset,
bytes32 _memRoot
)
function handleSysRead(SysReadParams memory _args)
internal
view
returns (uint32 v0_, uint32 v1_, uint32 newPreimageOffset_, bytes32 newMemRoot_)
......@@ -116,32 +197,34 @@ library MIPSSyscalls {
unchecked {
v0_ = uint32(0);
v1_ = uint32(0);
newMemRoot_ = _memRoot;
newPreimageOffset_ = _preimageOffset;
newMemRoot_ = _args.memRoot;
newPreimageOffset_ = _args.preimageOffset;
// args: _a0 = fd, _a1 = addr, _a2 = count
// returns: v0_ = read, v1_ = err code
if (_a0 == FD_STDIN) {
if (_args.a0 == FD_STDIN) {
// Leave v0_ and v1_ zero: read nothing, no error
}
// pre-image oracle read
else if (_a0 == FD_PREIMAGE_READ) {
else if (_args.a0 == FD_PREIMAGE_READ) {
// verify proof is correct, and get the existing memory.
// mask the addr to align it to 4 bytes
uint32 mem = MIPSMemory.readMem(_memRoot, _a1 & 0xFFffFFfc, _proofOffset);
uint32 mem = MIPSMemory.readMem(_args.memRoot, _args.a1 & 0xFFffFFfc, _args.proofOffset);
// If the preimage key is a local key, localize it in the context of the caller.
if (uint8(_preimageKey[0]) == 1) {
_preimageKey = PreimageKeyLib.localize(_preimageKey, _localContext);
if (uint8(_args.preimageKey[0]) == 1) {
_args.preimageKey = PreimageKeyLib.localize(_args.preimageKey, _args.localContext);
}
(bytes32 dat, uint256 datLen) = _oracle.readPreimage(_preimageKey, _preimageOffset);
(bytes32 dat, uint256 datLen) = _args.oracle.readPreimage(_args.preimageKey, _args.preimageOffset);
// Transform data for writing to memory
// We use assembly for more precise ops, and no var count limit
uint32 a1 = _args.a1;
uint32 a2 = _args.a2;
assembly {
let alignment := and(_a1, 3) // the read might not start at an aligned address
let alignment := and(a1, 3) // the read might not start at an aligned address
let space := sub(4, alignment) // remaining space in memory word
if lt(space, datLen) { datLen := space } // if less space than data, shorten data
if lt(_a2, datLen) { datLen := _a2 } // if requested to read less, read less
if lt(a2, datLen) { datLen := a2 } // if requested to read less, read less
dat := shr(sub(256, mul(datLen, 8)), dat) // right-align data
dat := shl(mul(sub(sub(4, datLen), alignment), 8), dat) // position data to insert into memory
// word
......@@ -153,15 +236,15 @@ library MIPSSyscalls {
}
// Write memory back
newMemRoot_ = MIPSMemory.writeMem(_a1 & 0xFFffFFfc, _proofOffset, mem);
newMemRoot_ = MIPSMemory.writeMem(_args.a1 & 0xFFffFFfc, _args.proofOffset, mem);
newPreimageOffset_ += uint32(datLen);
v0_ = uint32(datLen);
}
// hint response
else if (_a0 == FD_HINT_READ) {
else if (_args.a0 == FD_HINT_READ) {
// Don't read into memory, just say we read it all
// The result is ignored anyway
v0_ = _a2;
v0_ = _args.a2;
} else {
v0_ = 0xFFffFFff;
v1_ = EBADF;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -27,7 +27,7 @@ contract DeploymentSummary is DeploymentSummaryCode {
address internal constant l1StandardBridgeProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0xD31598c909d9C935a9e35bA70d9a3DD47d4D5865;
address internal constant mipsAddress = 0xcdAdd729ca2319E8955240bDb61A6A6A956A7664;
address internal constant mipsAddress = 0x390B62da67a702949505A34881926D5e3Be54B66;
address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567;
address internal constant optimismMintableERC20FactoryProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -27,7 +27,7 @@ contract DeploymentSummaryFaultProofs is DeploymentSummaryFaultProofsCode {
address internal constant l1StandardBridgeProxyAddress = 0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4;
address internal constant l2OutputOracleAddress = 0x19652082F846171168Daf378C4fD3ee85a0D4A60;
address internal constant l2OutputOracleProxyAddress = 0xD31598c909d9C935a9e35bA70d9a3DD47d4D5865;
address internal constant mipsAddress = 0xcdAdd729ca2319E8955240bDb61A6A6A956A7664;
address internal constant mipsAddress = 0x390B62da67a702949505A34881926D5e3Be54B66;
address internal constant optimismMintableERC20FactoryAddress = 0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567;
address internal constant optimismMintableERC20FactoryProxyAddress = 0x20A42a5a785622c6Ba2576B2D6e924aA82BFA11D;
address internal constant optimismPortalAddress = 0xbdD90485FCbcac869D5b5752179815a3103d8131;
......
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