diff --git a/cannon/mipsevm/README.md b/cannon/mipsevm/README.md index f01aa61510e54..41124ecbf9d1a 100644 --- a/cannon/mipsevm/README.md +++ b/cannon/mipsevm/README.md @@ -19,6 +19,8 @@ Supported 63 instructions: | `Conditional Branch` | `bne` | Branch on not equal. | | `Logical` | `clo` | Count leading ones. | | `Logical` | `clz` | Count leading zeros. | +| `Logical` | `dclo` | Count Leading Ones in Doubleword. | +| `Logical` | `dclz` | Count Leading Zeros in Doubleword. | | `Arithmetic` | `div` | Divide. | | `Arithmetic` | `divu` | Divide unsigned. | | `Unconditional Jump` | `j` | Jump. | diff --git a/cannon/mipsevm/exec/mips_instructions.go b/cannon/mipsevm/exec/mips_instructions.go index bbb9e8b208770..8f0d42e09858d 100644 --- a/cannon/mipsevm/exec/mips_instructions.go +++ b/cannon/mipsevm/exec/mips_instructions.go @@ -38,7 +38,7 @@ func GetInstructionDetails(pc Word, memory *memory.Memory) (insn, opcode, fun ui // ExecMipsCoreStepLogic executes a MIPS instruction that isn't a syscall nor a RMW operation // If a store operation occurred, then it returns the effective address of the store memory location. -func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker) (memUpdated bool, effMemAddr Word, err error) { +func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory *memory.Memory, insn, opcode, fun uint32, memTracker MemTracker, stackTracker StackTracker, features mipsevm.FeatureToggles) (memUpdated bool, effMemAddr Word, err error) { // j-type j/jal if opcode == 2 || opcode == 3 { linkReg := Word(0) @@ -117,7 +117,7 @@ func ExecMipsCoreStepLogic(cpu *mipsevm.CpuScalars, registers *[32]Word, memory } // ALU - val := ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem) + val := ExecuteMipsInstruction(insn, opcode, fun, rs, rt, mem, features) funSel := uint32(0x1c) if !arch.IsMips32 { @@ -182,7 +182,7 @@ func assertMips64Fun(fun uint32) { } } -func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem Word) Word { +func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem Word, features mipsevm.FeatureToggles) Word { if opcode == 0 || (opcode >= 8 && opcode < 0xF) || (!arch.IsMips32 && (opcode == 0x18 || opcode == 0x19)) { // transform ArithLogI to SPECIAL switch opcode { @@ -338,10 +338,10 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem switch opcode { // SPECIAL2 case 0x1C: - switch fun { - case 0x2: // mul + switch { + case fun == 0x2: // mul return SignExtend(Word(int32(rs)*int32(rt)), 32) - case 0x20, 0x21: // clz, clo + case fun == 0x20 || fun == 0x21: // clz, clo if fun == 0x20 { rs = ^rs } @@ -350,6 +350,16 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem rs <<= 1 } return Word(i) + case features.SupportDclzDclo && (fun == 0x24 || fun == 0x25): // dclz, dclo + assertMips64Fun(insn) + if fun == 0x24 { + rs = ^rs + } + i := uint32(0) + for ; uint64(rs)&0x80000000_00000000 != 0; i++ { + rs <<= 1 + } + return Word(i) } case 0x0F: // lui return SignExtend(rt<<16, 32) diff --git a/cannon/mipsevm/iface.go b/cannon/mipsevm/iface.go index ad06cdceb6dc2..ec9e4dcb03be5 100644 --- a/cannon/mipsevm/iface.go +++ b/cannon/mipsevm/iface.go @@ -75,6 +75,7 @@ type Metadata interface { // version can then be supported via multicannon pulling in a specific build and support for it dropped in latest code. type FeatureToggles struct { SupportNoopSysEventFd2 bool + SupportDclzDclo bool } type FPVM interface { diff --git a/cannon/mipsevm/multithreaded/mips.go b/cannon/mipsevm/multithreaded/mips.go index 332a0abe40148..22078d3bc8fa5 100644 --- a/cannon/mipsevm/multithreaded/mips.go +++ b/cannon/mipsevm/multithreaded/mips.go @@ -284,7 +284,7 @@ func (m *InstrumentedState) doMipsStep() error { } // Exec the rest of the step logic - memUpdated, effMemAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker) + memUpdated, effMemAddr, err := exec.ExecMipsCoreStepLogic(m.state.getCpuRef(), m.state.GetRegistersRef(), m.state.Memory, insn, opcode, fun, m.memoryTracker, m.stackTracker, m.features) if err != nil { return err } diff --git a/cannon/mipsevm/tests/evm_common64_test.go b/cannon/mipsevm/tests/evm_common64_test.go index 0bf411b3a051d..fe48ac95501a1 100644 --- a/cannon/mipsevm/tests/evm_common64_test.go +++ b/cannon/mipsevm/tests/evm_common64_test.go @@ -3,9 +3,12 @@ package tests import ( "fmt" "os" + "slices" "testing" + "github.com/ethereum-optimism/optimism/cannon/mipsevm" "github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil" + "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" "github.com/stretchr/testify/require" ) @@ -508,3 +511,77 @@ func TestEVM_SingleStep_Branch64(t *testing.T) { testBranch(t, cases) } + +func TestEVM_SingleStep_DCloDClz64(t *testing.T) { + rsReg := uint32(7) + rdReg := uint32(8) + cases := []struct { + name string + rs Word + funct uint32 + expectedResult Word + }{ + // dclo + {name: "dclo", rs: Word(0xFF_FF_FF_FF_FF_FF_FF_FF), expectedResult: Word(64), funct: 0b10_0101}, + {name: "dclo", rs: Word(0xFF_FF_FF_FF_FF_FF_FF_FE), expectedResult: Word(63), funct: 0b10_0101}, + {name: "dclo", rs: Word(0xFF_FF_FF_FF_00_00_00_00), expectedResult: Word(32), funct: 0b10_0101}, + {name: "dclo", rs: Word(0x80_00_00_00_00_00_00_00), expectedResult: Word(1), funct: 0b10_0101}, + {name: "dclo", rs: Word(0x0), expectedResult: Word(0), funct: 0b10_0101}, + // dclz + {name: "dclz", rs: Word(0x0), expectedResult: Word(64), funct: 0b10_0100}, + {name: "dclz", rs: Word(0x1), expectedResult: Word(63), funct: 0b10_0100}, + {name: "dclz", rs: Word(0x10_00_00_00), expectedResult: Word(35), funct: 0b10_0100}, + {name: "dclz", rs: Word(0x80_00_00_00), expectedResult: Word(32), funct: 0b10_0100}, + {name: "dclz", rs: Word(0x80_00_00_00_00_00_00_00), expectedResult: Word(0), funct: 0b10_0100}, + } + + vmVersions := GetMipsVersionTestCases(t) + require.True(t, slices.ContainsFunc(vmVersions, func(v VersionedVMTestCase) bool { + features := versions.FeaturesForVersion(v.Version) + return features.SupportDclzDclo + }), "dclz/dclo feature not tested") + require.True(t, slices.ContainsFunc(vmVersions, func(v VersionedVMTestCase) bool { + features := versions.FeaturesForVersion(v.Version) + return !features.SupportDclzDclo + }), "dclz/dclo backwards compatibility feature not tested") + + for _, v := range vmVersions { + for i, tt := range cases { + testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) + t.Run(testName, func(t *testing.T) { + // Set up state + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i))) + state := goVm.GetState() + insn := 0b01_1100<<26 | rsReg<<21 | rdReg<<11 | tt.funct + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), insn) + state.GetRegistersRef()[rsReg] = tt.rs + step := state.GetStep() + + features := versions.FeaturesForVersion(v.Version) + if features.SupportDclzDclo { + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + expected.Registers[rdReg] = tt.expectedResult + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + } else { + assertUnsupportedInstruction(t, v, insn, goVm) + } + }) + } + } +} + +func assertUnsupportedInstruction(t *testing.T, versionedTestCase VersionedVMTestCase, insn uint32, goVm mipsevm.FPVM) { + state := goVm.GetState() + proofData := versionedTestCase.ProofGenerator(t, goVm.GetState()) + goPanicMsg := fmt.Sprintf("invalid instruction: %x", insn) + require.PanicsWithValue(t, goPanicMsg, func() { + _, _ = goVm.Step( + false) + }) + errMsg := testutil.CreateErrorStringMatcher("invalid instruction") + testutil.AssertEVMReverts(t, state, versionedTestCase.Contracts, nil, proofData, errMsg) +} diff --git a/cannon/mipsevm/versions/state.go b/cannon/mipsevm/versions/state.go index e2fba6825b063..41313c17eec42 100644 --- a/cannon/mipsevm/versions/state.go +++ b/cannon/mipsevm/versions/state.go @@ -63,6 +63,7 @@ func FeaturesForVersion(version StateVersion) mipsevm.FeatureToggles { // Set any required feature toggles based on the state version here. if version >= VersionMultiThreaded64_v4 { features.SupportNoopSysEventFd2 = true + features.SupportDclzDclo = true } return features } diff --git a/cannon/mipsevm/versions/version.go b/cannon/mipsevm/versions/version.go index 046d96b98aa5e..88f0ec5ce892e 100644 --- a/cannon/mipsevm/versions/version.go +++ b/cannon/mipsevm/versions/version.go @@ -23,7 +23,7 @@ const ( VersionMultiThreaded_v2 // VersionMultiThreaded64_v3 includes futex handling simplification VersionMultiThreaded64_v3 - // VersionMultiThreaded64_v4 is the latest 64-bit multithreaded vm, includes support for new syscall eventfd2 + // VersionMultiThreaded64_v4 is the latest 64-bit multithreaded vm, includes support for new syscall eventfd2 and dclo/dclz instructions VersionMultiThreaded64_v4 ) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index b32ec7bfcab9c..d71f223f7d4c3 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -132,8 +132,8 @@ "sourceCodeHash": "0x734a6b2aa6406bc145d848ad6071d3af1d40852aeb8f4b2f6f51beaad476e2d3" }, "src/cannon/MIPS64.sol:MIPS64": { - "initCodeHash": "0xd38d92ed0ddeba1ecb7ae65dd4142a91b51c24c15f0e449f3c711e86b97db1ab", - "sourceCodeHash": "0x9c01b1c4611583819dbc4dfe529fc1d6ca5885620e89cc58668e0606d5bf959a" + "initCodeHash": "0xcbe1c834c7f1c954ccad3e613f440fed6732dccc0a8786dac8d831752d5613f3", + "sourceCodeHash": "0x59352159a7c46f8cf9a408b20f90e6802ad78ee65bbc215cb825ecfef7beebc1" }, "src/cannon/PreimageOracle.sol:PreimageOracle": { "initCodeHash": "0x6af5b0e83b455aab8d0946c160a4dc049a4e03be69f8a2a9e87b574f27b25a66", diff --git a/packages/contracts-bedrock/src/cannon/MIPS64.sol b/packages/contracts-bedrock/src/cannon/MIPS64.sol index 44e5bb842bf47..13739f3fb6343 100644 --- a/packages/contracts-bedrock/src/cannon/MIPS64.sol +++ b/packages/contracts-bedrock/src/cannon/MIPS64.sol @@ -66,8 +66,8 @@ contract MIPS64 is ISemver { } /// @notice The semantic version of the MIPS64 contract. - /// @custom:semver 1.2.0 - string public constant version = "1.2.0"; + /// @custom:semver 1.2.1 + string public constant version = "1.2.1"; /// @notice The preimage oracle contract. IPreimageOracle internal immutable ORACLE; @@ -273,7 +273,8 @@ contract MIPS64 is ISemver { memProofOffset: MIPS64Memory.memoryProofOffset(MEM_PROOF_OFFSET, 1), insn: insn, opcode: opcode, - fun: fun + fun: fun, + stateVersion: STATE_VERSION }); bool memUpdated; uint64 effMemAddr; @@ -622,7 +623,7 @@ contract MIPS64 is ISemver { } else if (syscall_no == sys.SYS_LSEEK) { // ignored } else if (syscall_no == sys.SYS_EVENTFD2) { - if (STATE_VERSION < 7) { + if (!st.featuresForVersion(STATE_VERSION).supportNoopSysEventFd2) { revert("MIPS64: unimplemented syscall"); } } else { diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol index c2e323832ff5c..2c0f3acfa3c4f 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64Instructions.sol @@ -20,7 +20,7 @@ library MIPS64Instructions { error InvalidPC(); struct CoreStepLogicParams { - /// @param opcode The opcode value parsed from insn_. + /// @param opcode The opcode value parsed from insn. st.CpuScalars cpu; /// @param registers The CPU registers. uint64[32] registers; @@ -28,12 +28,31 @@ library MIPS64Instructions { bytes32 memRoot; /// @param memProofOffset The offset in calldata specify where the memory merkle proof is located. uint256 memProofOffset; - /// @param insn The current 32-bit instruction at the pc. + /// @param insn The current MIPS instruction at the pc. uint32 insn; /// @param cpu The CPU scalar fields. uint32 opcode; - /// @param fun The function value parsed from insn_. + /// @param fun The function value parsed from insn. uint32 fun; + /// @param stateVersion The state version. + uint256 stateVersion; + } + + struct ExecuteMipsInstructionParams { + /// @param insn The current MIPS instruction at the pc. + uint32 insn; + /// @param opcode The opcode value parsed from insn. + uint32 opcode; + /// @param fun The function value parsed from insn. + uint32 fun; + /// @param rs The source register 1 value. + uint64 rs; + /// @param rt The source register 2 value. + uint64 rt; + /// @param mem The value fetched from memory for the current instruction. + uint64 mem; + /// @param stateVersion The state version. + uint256 stateVersion; } /// @param _pc The program counter. @@ -82,7 +101,7 @@ library MIPS64Instructions { if (_args.opcode == 2 || _args.opcode == 3) { // Take top 4 bits of the next PC (its 256 MB region), and concatenate with the 26-bit offset uint64 target = (_args.cpu.nextPC & signExtend(0xF0000000, 32)) | uint64((_args.insn & 0x03FFFFFF) << 2); - handleJump(_args.cpu, _args.registers, _args.opcode == 2 ? 0 : REG_RA, target); + handleJump(_args, _args.opcode == 2 ? 0 : REG_RA, target); return (newMemRoot_, memUpdated_, effMemAddr_); } @@ -156,13 +175,22 @@ library MIPS64Instructions { // ALU // Note: swr outputs more than 8 bytes without the u64_mask - uint64 val = executeMipsInstruction(_args.insn, _args.opcode, _args.fun, rs, rt, mem) & U64_MASK; + ExecuteMipsInstructionParams memory params = ExecuteMipsInstructionParams({ + insn: _args.insn, + opcode: _args.opcode, + fun: _args.fun, + rs: rs, + rt: rt, + mem: mem, + stateVersion: _args.stateVersion + }); + uint64 val = executeMipsInstruction(params) & U64_MASK; uint64 funSel = 0x20; if (_args.opcode == 0 && _args.fun >= 8 && _args.fun < funSel) { if (_args.fun == 8 || _args.fun == 9) { // jr/jalr - handleJump(_args.cpu, _args.registers, _args.fun == 8 ? 0 : rdReg, rs); + handleJump(_args, _args.fun == 8 ? 0 : rdReg, rs); return (newMemRoot_, memUpdated_, effMemAddr_); } @@ -213,376 +241,384 @@ library MIPS64Instructions { } /// @notice Execute an instruction. - function executeMipsInstruction( - uint32 _insn, - uint32 _opcode, - uint32 _fun, - uint64 _rs, - uint64 _rt, - uint64 _mem - ) - internal - pure - returns (uint64 out_) - { + function executeMipsInstruction(ExecuteMipsInstructionParams memory _args) internal pure returns (uint64 out_) { + uint32 insn = _args.insn; + uint32 opcode = _args.opcode; + uint32 fun = _args.fun; + uint64 rs = _args.rs; + uint64 rt = _args.rt; + uint64 mem = _args.mem; + uint256 stateVersion = _args.stateVersion; unchecked { - if (_opcode == 0 || (_opcode >= 8 && _opcode < 0xF) || _opcode == 0x18 || _opcode == 0x19) { + if (opcode == 0 || (opcode >= 8 && opcode < 0xF) || opcode == 0x18 || opcode == 0x19) { assembly { // transform ArithLogI to SPECIAL - switch _opcode + switch opcode // addi - case 0x8 { _fun := 0x20 } + case 0x8 { fun := 0x20 } // addiu - case 0x9 { _fun := 0x21 } + case 0x9 { fun := 0x21 } // stli - case 0xA { _fun := 0x2A } + case 0xA { fun := 0x2A } // sltiu - case 0xB { _fun := 0x2B } + case 0xB { fun := 0x2B } // andi - case 0xC { _fun := 0x24 } + case 0xC { fun := 0x24 } // ori - case 0xD { _fun := 0x25 } + case 0xD { fun := 0x25 } // xori - case 0xE { _fun := 0x26 } + case 0xE { fun := 0x26 } // daddi - case 0x18 { _fun := 0x2C } + case 0x18 { fun := 0x2C } // daddiu - case 0x19 { _fun := 0x2D } + case 0x19 { fun := 0x2D } } // sll - if (_fun == 0x00) { - uint32 shiftAmt = (_insn >> 6) & 0x1F; - return signExtend((_rt << shiftAmt) & U32_MASK, 32); + if (fun == 0x00) { + uint32 shiftAmt = (insn >> 6) & 0x1F; + return signExtend((rt << shiftAmt) & U32_MASK, 32); } // srl - else if (_fun == 0x02) { - return signExtend((_rt & U32_MASK) >> ((_insn >> 6) & 0x1F), 32); + else if (fun == 0x02) { + return signExtend((rt & U32_MASK) >> ((insn >> 6) & 0x1F), 32); } // sra - else if (_fun == 0x03) { - uint32 shamt = (_insn >> 6) & 0x1F; - return signExtend((_rt & U32_MASK) >> shamt, 32 - shamt); + else if (fun == 0x03) { + uint32 shamt = (insn >> 6) & 0x1F; + return signExtend((rt & U32_MASK) >> shamt, 32 - shamt); } // sllv - else if (_fun == 0x04) { - uint64 shiftAmt = _rs & 0x1F; - return signExtend((_rt << shiftAmt) & U32_MASK, 32); + else if (fun == 0x04) { + uint64 shiftAmt = rs & 0x1F; + return signExtend((rt << shiftAmt) & U32_MASK, 32); } // srlv - else if (_fun == 0x6) { - return signExtend((_rt & U32_MASK) >> (_rs & 0x1F), 32); + else if (fun == 0x6) { + return signExtend((rt & U32_MASK) >> (rs & 0x1F), 32); } // srav - else if (_fun == 0x07) { + else if (fun == 0x07) { // shamt here is different than the typical shamt which comes from the // instruction itself, here it comes from the rs register - uint64 shamt = _rs & 0x1F; - return signExtend((_rt & U32_MASK) >> shamt, 32 - shamt); + uint64 shamt = rs & 0x1F; + return signExtend((rt & U32_MASK) >> shamt, 32 - shamt); } // functs in range [0x8, 0x1b] are handled specially by other functions // Explicitly enumerate each funct in range to reduce code diff against Go Vm // jr - else if (_fun == 0x08) { - return _rs; + else if (fun == 0x08) { + return rs; } // jalr - else if (_fun == 0x09) { - return _rs; + else if (fun == 0x09) { + return rs; } // movz - else if (_fun == 0x0a) { - return _rs; + else if (fun == 0x0a) { + return rs; } // movn - else if (_fun == 0x0b) { - return _rs; + else if (fun == 0x0b) { + return rs; } // syscall - else if (_fun == 0x0c) { - return _rs; + else if (fun == 0x0c) { + return rs; } // 0x0d - break not supported // sync - else if (_fun == 0x0f) { - return _rs; + else if (fun == 0x0f) { + return rs; } // mfhi - else if (_fun == 0x10) { - return _rs; + else if (fun == 0x10) { + return rs; } // mthi - else if (_fun == 0x11) { - return _rs; + else if (fun == 0x11) { + return rs; } // mflo - else if (_fun == 0x12) { - return _rs; + else if (fun == 0x12) { + return rs; } // mtlo - else if (_fun == 0x13) { - return _rs; + else if (fun == 0x13) { + return rs; } // dsllv - else if (_fun == 0x14) { - return _rt; + else if (fun == 0x14) { + return rt; } // dsrlv - else if (_fun == 0x16) { - return _rt; + else if (fun == 0x16) { + return rt; } // dsrav - else if (_fun == 0x17) { - return _rt; + else if (fun == 0x17) { + return rt; } // mult - else if (_fun == 0x18) { - return _rs; + else if (fun == 0x18) { + return rs; } // multu - else if (_fun == 0x19) { - return _rs; + else if (fun == 0x19) { + return rs; } // div - else if (_fun == 0x1a) { - return _rs; + else if (fun == 0x1a) { + return rs; } // divu - else if (_fun == 0x1b) { - return _rs; + else if (fun == 0x1b) { + return rs; } // dmult - else if (_fun == 0x1c) { - return _rs; + else if (fun == 0x1c) { + return rs; } // dmultu - else if (_fun == 0x1d) { - return _rs; + else if (fun == 0x1d) { + return rs; } // ddiv - else if (_fun == 0x1e) { - return _rs; + else if (fun == 0x1e) { + return rs; } // ddivu - else if (_fun == 0x1f) { - return _rs; + else if (fun == 0x1f) { + return rs; } // The rest includes transformed R-type arith imm instructions // add - else if (_fun == 0x20) { - return signExtend(uint64(uint32(_rs) + uint32(_rt)), 32); + else if (fun == 0x20) { + return signExtend(uint64(uint32(rs) + uint32(rt)), 32); } // addu - else if (_fun == 0x21) { - return signExtend(uint64(uint32(_rs) + uint32(_rt)), 32); + else if (fun == 0x21) { + return signExtend(uint64(uint32(rs) + uint32(rt)), 32); } // sub - else if (_fun == 0x22) { - return signExtend(uint64(uint32(_rs) - uint32(_rt)), 32); + else if (fun == 0x22) { + return signExtend(uint64(uint32(rs) - uint32(rt)), 32); } // subu - else if (_fun == 0x23) { - return signExtend(uint64(uint32(_rs) - uint32(_rt)), 32); + else if (fun == 0x23) { + return signExtend(uint64(uint32(rs) - uint32(rt)), 32); } // and - else if (_fun == 0x24) { - return (_rs & _rt); + else if (fun == 0x24) { + return (rs & rt); } // or - else if (_fun == 0x25) { - return (_rs | _rt); + else if (fun == 0x25) { + return (rs | rt); } // xor - else if (_fun == 0x26) { - return (_rs ^ _rt); + else if (fun == 0x26) { + return (rs ^ rt); } // nor - else if (_fun == 0x27) { - return ~(_rs | _rt); + else if (fun == 0x27) { + return ~(rs | rt); } // slti - else if (_fun == 0x2a) { - return int64(_rs) < int64(_rt) ? 1 : 0; + else if (fun == 0x2a) { + return int64(rs) < int64(rt) ? 1 : 0; } // sltiu - else if (_fun == 0x2b) { - return _rs < _rt ? 1 : 0; + else if (fun == 0x2b) { + return rs < rt ? 1 : 0; } // dadd - else if (_fun == 0x2c) { - return (_rs + _rt); + else if (fun == 0x2c) { + return (rs + rt); } // daddu - else if (_fun == 0x2d) { - return (_rs + _rt); + else if (fun == 0x2d) { + return (rs + rt); } // dsub - else if (_fun == 0x2e) { - return (_rs - _rt); + else if (fun == 0x2e) { + return (rs - rt); } // dsubu - else if (_fun == 0x2f) { - return (_rs - _rt); + else if (fun == 0x2f) { + return (rs - rt); } // dsll - else if (_fun == 0x38) { - return _rt << ((_insn >> 6) & 0x1f); + else if (fun == 0x38) { + return rt << ((insn >> 6) & 0x1f); } // dsrl - else if (_fun == 0x3A) { - return _rt >> ((_insn >> 6) & 0x1f); + else if (fun == 0x3A) { + return rt >> ((insn >> 6) & 0x1f); } // dsra - else if (_fun == 0x3B) { - return uint64(int64(_rt) >> ((_insn >> 6) & 0x1f)); + else if (fun == 0x3B) { + return uint64(int64(rt) >> ((insn >> 6) & 0x1f)); } // dsll32 - else if (_fun == 0x3c) { - return _rt << (((_insn >> 6) & 0x1f) + 32); + else if (fun == 0x3c) { + return rt << (((insn >> 6) & 0x1f) + 32); } // dsrl32 - else if (_fun == 0x3e) { - return _rt >> (((_insn >> 6) & 0x1f) + 32); + else if (fun == 0x3e) { + return rt >> (((insn >> 6) & 0x1f) + 32); } // dsra32 - else if (_fun == 0x3f) { - return uint64(int64(_rt) >> (((_insn >> 6) & 0x1f) + 32)); + else if (fun == 0x3f) { + return uint64(int64(rt) >> (((insn >> 6) & 0x1f) + 32)); } else { revert("MIPS64: invalid instruction"); } } else { // SPECIAL2 - if (_opcode == 0x1C) { + if (opcode == 0x1C) { // mul - if (_fun == 0x2) { - return signExtend(uint32(int32(uint32(_rs)) * int32(uint32(_rt))), 32); + if (fun == 0x2) { + return signExtend(uint32(int32(uint32(rs)) * int32(uint32(rt))), 32); } // clz, clo - else if (_fun == 0x20 || _fun == 0x21) { - if (_fun == 0x20) { - _rs = ~_rs; + else if (fun == 0x20 || fun == 0x21) { + if (fun == 0x20) { + rs = ~rs; } uint32 i = 0; - while (_rs & 0x80000000 != 0) { + while (rs & 0x80000000 != 0) { i++; - _rs <<= 1; + rs <<= 1; + } + return i; + } + // dclz, dclo + else if (st.featuresForVersion(stateVersion).supportDclzDclo && (fun == 0x24 || fun == 0x25)) { + if (fun == 0x24) { + rs = ~rs; + } + uint32 i = 0; + while (rs & 0x80000000_00000000 != 0) { + i++; + rs <<= 1; } return i; } } // lui - else if (_opcode == 0x0F) { - return signExtend(_rt << 16, 32); + else if (opcode == 0x0F) { + return signExtend(rt << 16, 32); } // lb - else if (_opcode == 0x20) { - return selectSubWord(_rs, _mem, 1, true); + else if (opcode == 0x20) { + return selectSubWord(rs, mem, 1, true); } // lh - else if (_opcode == 0x21) { - return selectSubWord(_rs, _mem, 2, true); + else if (opcode == 0x21) { + return selectSubWord(rs, mem, 2, true); } // lwl - else if (_opcode == 0x22) { - uint32 w = uint32(selectSubWord(_rs, _mem, 4, false)); - uint32 val = w << uint32((_rs & 3) * 8); - uint64 mask = uint64(U32_MASK << uint32((_rs & 3) * 8)); - return signExtend(((_rt & ~mask) | uint64(val)) & U32_MASK, 32); + else if (opcode == 0x22) { + uint32 w = uint32(selectSubWord(rs, mem, 4, false)); + uint32 val = w << uint32((rs & 3) * 8); + uint64 mask = uint64(U32_MASK << uint32((rs & 3) * 8)); + return signExtend(((rt & ~mask) | uint64(val)) & U32_MASK, 32); } // lw - else if (_opcode == 0x23) { - return selectSubWord(_rs, _mem, 4, true); + else if (opcode == 0x23) { + return selectSubWord(rs, mem, 4, true); } // lbu - else if (_opcode == 0x24) { - return selectSubWord(_rs, _mem, 1, false); + else if (opcode == 0x24) { + return selectSubWord(rs, mem, 1, false); } // lhu - else if (_opcode == 0x25) { - return selectSubWord(_rs, _mem, 2, false); + else if (opcode == 0x25) { + return selectSubWord(rs, mem, 2, false); } // lwr - else if (_opcode == 0x26) { - uint32 w = uint32(selectSubWord(_rs, _mem, 4, false)); - uint32 val = w >> (24 - (_rs & 3) * 8); - uint32 mask = U32_MASK >> (24 - (_rs & 3) * 8); - uint64 lwrResult = (uint32(_rt) & ~mask) | val; - if (_rs & 3 == 3) { + else if (opcode == 0x26) { + uint32 w = uint32(selectSubWord(rs, mem, 4, false)); + uint32 val = w >> (24 - (rs & 3) * 8); + uint32 mask = U32_MASK >> (24 - (rs & 3) * 8); + uint64 lwrResult = (uint32(rt) & ~mask) | val; + if (rs & 3 == 3) { // loaded bit 31 return signExtend(uint64(lwrResult), 32); } else { // NOTE: cannon64 implementation specific: We leave the upper word untouched uint64 rtMask = 0xFF_FF_FF_FF_00_00_00_00; - return ((_rt & rtMask) | uint64(lwrResult)); + return ((rt & rtMask) | uint64(lwrResult)); } } // sb - else if (_opcode == 0x28) { - return updateSubWord(_rs, _mem, 1, _rt); + else if (opcode == 0x28) { + return updateSubWord(rs, mem, 1, rt); } // sh - else if (_opcode == 0x29) { - return updateSubWord(_rs, _mem, 2, _rt); + else if (opcode == 0x29) { + return updateSubWord(rs, mem, 2, rt); } // swl - else if (_opcode == 0x2a) { - uint64 sr = (_rs & 3) << 3; - uint64 val = ((_rt & U32_MASK) >> sr) << (32 - ((_rs & 0x4) << 3)); - uint64 mask = (uint64(U32_MASK) >> sr) << (32 - ((_rs & 0x4) << 3)); - return (_mem & ~mask) | val; + else if (opcode == 0x2a) { + uint64 sr = (rs & 3) << 3; + uint64 val = ((rt & U32_MASK) >> sr) << (32 - ((rs & 0x4) << 3)); + uint64 mask = (uint64(U32_MASK) >> sr) << (32 - ((rs & 0x4) << 3)); + return (mem & ~mask) | val; } // sw - else if (_opcode == 0x2b) { - return updateSubWord(_rs, _mem, 4, _rt); + else if (opcode == 0x2b) { + return updateSubWord(rs, mem, 4, rt); } // swr - else if (_opcode == 0x2e) { - uint32 w = uint32(selectSubWord(_rs, _mem, 4, false)); - uint64 val = _rt << (24 - (_rs & 3) * 8); - uint64 mask = U32_MASK << uint32(24 - (_rs & 3) * 8); + else if (opcode == 0x2e) { + uint32 w = uint32(selectSubWord(rs, mem, 4, false)); + uint64 val = rt << (24 - (rs & 3) * 8); + uint64 mask = U32_MASK << uint32(24 - (rs & 3) * 8); uint64 swrResult = (w & ~mask) | uint32(val); - return updateSubWord(_rs, _mem, 4, swrResult); + return updateSubWord(rs, mem, 4, swrResult); } // MIPS64 // ldl - else if (_opcode == 0x1a) { - uint64 sl = (_rs & 0x7) << 3; - uint64 val = _mem << sl; + else if (opcode == 0x1a) { + uint64 sl = (rs & 0x7) << 3; + uint64 val = mem << sl; uint64 mask = U64_MASK << sl; - return (val | (_rt & ~mask)); + return (val | (rt & ~mask)); } // ldr - else if (_opcode == 0x1b) { - uint64 sr = 56 - ((_rs & 0x7) << 3); - uint64 val = _mem >> sr; + else if (opcode == 0x1b) { + uint64 sr = 56 - ((rs & 0x7) << 3); + uint64 val = mem >> sr; uint64 mask = U64_MASK << (64 - sr); - return (val | (_rt & mask)); + return (val | (rt & mask)); } // lwu - else if (_opcode == 0x27) { - return ((_mem >> (32 - ((_rs & 0x4) << 3))) & U32_MASK); + else if (opcode == 0x27) { + return ((mem >> (32 - ((rs & 0x4) << 3))) & U32_MASK); } // sdl - else if (_opcode == 0x2c) { - uint64 sr = (_rs & 0x7) << 3; - uint64 val = _rt >> sr; + else if (opcode == 0x2c) { + uint64 sr = (rs & 0x7) << 3; + uint64 val = rt >> sr; uint64 mask = U64_MASK >> sr; - return (val | (_mem & ~mask)); + return (val | (mem & ~mask)); } // sdr - else if (_opcode == 0x2d) { - uint64 sl = 56 - ((_rs & 0x7) << 3); - uint64 val = _rt << sl; + else if (opcode == 0x2d) { + uint64 sl = 56 - ((rs & 0x7) << 3); + uint64 val = rt << sl; uint64 mask = U64_MASK << sl; - return (val | (_mem & ~mask)); + return (val | (mem & ~mask)); } // ld - else if (_opcode == 0x37) { - return _mem; + else if (opcode == 0x37) { + return mem; } // sd - else if (_opcode == 0x3F) { - return _rt; + else if (opcode == 0x3F) { + return rt; } else { revert("MIPS64: invalid instruction"); } @@ -800,32 +836,25 @@ library MIPS64Instructions { } /// @notice Handles a jump 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. + /// @dev The _cpuAndRegisters is stored in memory to avoid stack limit issues. + /// @param _cpuAndRegisters Holds the state of cpu scalars (pc, nextPC, hi, lo) and the current state of the cpu + /// registers. /// @param _linkReg The register to store the link to the instruction after the delay slot instruction. /// @param _dest The destination to jump to. - function handleJump( - st.CpuScalars memory _cpu, - uint64[32] memory _registers, - uint64 _linkReg, - uint64 _dest - ) - internal - pure - { + function handleJump(CoreStepLogicParams memory _cpuAndRegisters, uint64 _linkReg, uint64 _dest) internal pure { unchecked { - if (_cpu.nextPC != _cpu.pc + 4) { + if (_cpuAndRegisters.cpu.nextPC != _cpuAndRegisters.cpu.pc + 4) { revert("MIPS64: jump in delay slot"); } // Update the next PC to the jump destination. - uint64 prevPC = _cpu.pc; - _cpu.pc = _cpu.nextPC; - _cpu.nextPC = _dest; + uint64 prevPC = _cpuAndRegisters.cpu.pc; + _cpuAndRegisters.cpu.pc = _cpuAndRegisters.cpu.nextPC; + _cpuAndRegisters.cpu.nextPC = _dest; // Update the link-register to the instruction after the delay slot instruction. if (_linkReg != 0) { - _registers[_linkReg] = prevPC + 8; + _cpuAndRegisters.registers[_linkReg] = prevPC + 8; } } } diff --git a/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol b/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol index c7102dea0fdd7..a9de00a320772 100644 --- a/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol +++ b/packages/contracts-bedrock/src/cannon/libraries/MIPS64State.sol @@ -12,9 +12,21 @@ library MIPS64State { uint64 hi; } + struct Features { + bool supportNoopSysEventFd2; + bool supportDclzDclo; + } + function assertExitedIsValid(uint32 _exited) internal pure { if (_exited > 1) { revert InvalidExitedValue(); } } + + function featuresForVersion(uint256 _version) internal pure returns (Features memory features_) { + if (_version >= 7) { + features_.supportNoopSysEventFd2 = true; + features_.supportDclzDclo = true; + } + } }