Skip to content

Commit

Permalink
cannon: Implement 64-bit Solidity VM (#12665)
Browse files Browse the repository at this point in the history
* cannon: Implement MIPS64Memory.sol

* cannon: Implement 64-bit Solidity VM

- Implements 64-bit Cannon (with multithreading) in MIPS64.sol
- Re-enable differential testing for 64-bit VMs

* review comments

* check pc for 4-byte alignment

* gofmt

* update snapshot

* address nits; add more add/sub/mult overflow tests

* diff test misaligned instruction

* fix mul[t] MIPS64.sol emulation

* diff fuzz mul operations

* fix addiu test case

* fix GetInstruction return value type
  • Loading branch information
Inphi authored Oct 30, 2024
1 parent d6bda03 commit 96ef7bb
Show file tree
Hide file tree
Showing 23 changed files with 2,825 additions and 201 deletions.
9 changes: 8 additions & 1 deletion cannon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ sanitize-program:
contract:
cd ../packages/contracts-bedrock && forge build

test: elf contract
test: elf contract test64
go test -v ./...

test64: elf contract
go test -tags=cannon64 -run '(TestEVM.*64|TestHelloEVM|TestClaimEVM)' ./mipsevm/tests

diff-%-cannon: cannon elf
$$OTHER_CANNON load-elf --type $* --path ./testdata/example/bin/hello.elf --out ./bin/prestate-other.bin.gz --meta ""
./bin/cannon load-elf --type $* --path ./testdata/example/bin/hello.elf --out ./bin/prestate.bin.gz --meta ""
Expand Down Expand Up @@ -96,6 +99,10 @@ fuzz:
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneST ./mipsevm/tests
# Multi-threaded tests
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests
# 64-bit tests - increased fuzztime for a larger input space
go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMulOp ./mipsevm/tests
go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMultOp ./mipsevm/tests
go test $(FUZZLDFLAGS) -tags=cannon64 -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStateConsistencyMultuOp ./mipsevm/tests

.PHONY: \
cannon32-impl \
Expand Down
3 changes: 2 additions & 1 deletion cannon/mipsevm/exec/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exec
import (
"fmt"

"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
)

Expand Down Expand Up @@ -37,7 +38,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr Word) {
// TrackMemAccess2 creates a proof for a memory access following a call to TrackMemAccess
// This is used to generate proofs for contiguous memory accesses within the same step
func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr Word) {
if m.memProofEnabled && m.lastMemAccess+4 != effAddr {
if m.memProofEnabled && m.lastMemAccess+arch.WordSizeBytes != effAddr {
panic(fmt.Errorf("unexpected disjointed mem access at %08x, last memory access is at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
Expand Down
6 changes: 3 additions & 3 deletions cannon/mipsevm/exec/mips_instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func ExecuteMipsInstruction(insn uint32, opcode uint32, fun uint32, rs, rt, mem
w := uint32(SelectSubWord(rs, mem, 4, false))
val := w >> (24 - (rs&3)*8)
mask := uint32(0xFFFFFFFF) >> (24 - (rs&3)*8)
lwrResult := ((uint32(rt) & ^mask) | val) & 0xFFFFFFFF
lwrResult := (uint32(rt) & ^mask) | val
if rs&3 == 3 { // loaded bit 31
return SignExtend(Word(lwrResult), 32)
} else {
Expand Down Expand Up @@ -539,13 +539,13 @@ func HandleHiLo(cpu *mipsevm.CpuScalars, registers *[32]Word, fun uint32, rs Wor
cpu.HI = SignExtend(Word(acc>>32), 32)
cpu.LO = SignExtend(Word(uint32(acc)), 32)
case 0x1a: // div
if rt == 0 {
if uint32(rt) == 0 {
panic("instruction divide by zero")
}
cpu.HI = SignExtend(Word(int32(rs)%int32(rt)), 32)
cpu.LO = SignExtend(Word(int32(rs)/int32(rt)), 32)
case 0x1b: // divu
if rt == 0 {
if uint32(rt) == 0 {
panic("instruction divide by zero")
}
cpu.HI = SignExtend(Word(uint32(rs)%uint32(rt)), 32)
Expand Down
36 changes: 23 additions & 13 deletions cannon/mipsevm/tests/evm_common64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/testutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -134,12 +133,12 @@ func TestEVMSingleStep_Operators64(t *testing.T) {

// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}

func TestEVMSingleStep_Shift(t *testing.T) {
func TestEVMSingleStep_Shift64(t *testing.T) {
cases := []struct {
name string
rd Word
Expand Down Expand Up @@ -190,7 +189,8 @@ func TestEVMSingleStep_Shift(t *testing.T) {
for i, tt := range cases {
testName := fmt.Sprintf("%v %v", v.Name, tt.name)
t.Run(testName, func(t *testing.T) {
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(0))
pc := Word(0x0)
goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(pc))
state := goVm.GetState()
var insn uint32
var rtReg uint32
Expand All @@ -200,7 +200,7 @@ func TestEVMSingleStep_Shift(t *testing.T) {
insn = rtReg<<16 | rdReg<<11 | tt.sa<<6 | tt.funct
state.GetRegistersRef()[rdReg] = tt.rd
state.GetRegistersRef()[rtReg] = tt.rt
testutil.StoreInstruction(state.GetMemory(), 0, insn)
testutil.StoreInstruction(state.GetMemory(), pc, insn)
step := state.GetStep()

// Setup expectations
Expand All @@ -213,7 +213,7 @@ func TestEVMSingleStep_Shift(t *testing.T) {

// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
Expand Down Expand Up @@ -455,12 +455,12 @@ func TestEVMSingleStep_LoadStore64(t *testing.T) {

// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}

func TestEVMSingleStep_DivMult(t *testing.T) {
func TestEVMSingleStep_DivMult64(t *testing.T) {
cases := []struct {
name string
rs Word
Expand All @@ -470,6 +470,14 @@ func TestEVMSingleStep_DivMult(t *testing.T) {
expectHi Word
expectPanic string
}{
// TODO(#12598): Fix 32-bit tests and remove these
{name: "mult", funct: uint32(0x18), rs: Word(0x0F_FF_00_00), rt: Word(100), expectHi: Word(0x6), expectLo: Word(0x3F_9C_00_00)},
{name: "mult", funct: uint32(0x18), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), expectHi: Word(0x0), expectLo: Word(0x1)},
{name: "mult", funct: uint32(0x18), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_DD), expectHi: Word(0xE), expectLo: Word(0xFF_FF_FF_FF_FC_FC_FD_27)},
{name: "multu", funct: uint32(0x19), rs: Word(0x0F_FF_00_00), rt: Word(100), expectHi: Word(0x6), expectLo: Word(0x3F_9C_00_00)},
{name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_FF), rt: Word(0xFF_FF_FF_FF), expectHi: Word(0xFF_FF_FF_FF_FF_FF_FF_FE), expectLo: Word(0x1)},
{name: "multu", funct: uint32(0x19), rs: Word(0xFF_FF_FF_D3), rt: Word(0xAA_BB_CC_BE), expectHi: Word(0xFF_FF_FF_FF_AA_BB_CC_9F), expectLo: Word(0xFF_FF_FF_FF_FC_FD_02_9A)},

// dmult s1, s2
// expected hi,lo were verified using qemu-mips
{name: "dmult 0", funct: 0x1c, rs: 0, rt: 0, expectLo: 0, expectHi: 0},
Expand Down Expand Up @@ -530,6 +538,10 @@ func TestEVMSingleStep_DivMult(t *testing.T) {
{name: "ddivu", funct: 0x1f, rs: ^Word(0), rt: ^Word(0), expectLo: 1, expectHi: 0},
{name: "ddivu", funct: 0x1f, rs: ^Word(0), rt: 2, expectLo: 0x7F_FF_FF_FF_FF_FF_FF_FF, expectHi: 1},
{name: "ddivu", funct: 0x1f, rs: 0x7F_FF_FF_FF_00_00_00_00, rt: ^Word(0), expectLo: 0, expectHi: 0x7F_FF_FF_FF_00_00_00_00},

// a couple div/divu 64-bit edge cases
{name: "div lower word zero", funct: 0x1a, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, expectPanic: "instruction divide by zero"},
{name: "divu lower word zero", funct: 0x1b, rs: 1, rt: 0xFF_FF_FF_FF_00_00_00_00, expectPanic: "instruction divide by zero"},
}

v := GetMultiThreadedTestCase(t)
Expand Down Expand Up @@ -560,15 +572,13 @@ func TestEVMSingleStep_DivMult(t *testing.T) {
stepWitness, err := goVm.Step(true)
require.NoError(t, err)
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, nil)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
}
})
}
}

func TestEVMSingleStepBranch64(t *testing.T) {
var tracer *tracing.Hooks

func TestEVMSingleStep_Branch64(t *testing.T) {
versions := GetMipsVersionTestCases(t)
cases := []struct {
name string
Expand Down Expand Up @@ -651,7 +661,7 @@ func TestEVMSingleStepBranch64(t *testing.T) {

// Check expectations
expected.Validate(t, state)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts, tracer)
testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts)
})
}
}
Expand Down
Loading

0 comments on commit 96ef7bb

Please sign in to comment.