Skip to content

Commit

Permalink
cannon: 64-bit Refactor (#12029)
Browse files Browse the repository at this point in the history
* cannon: 64-bit Refactor

Refactor Cannon codebase to support both 32-bit and 64-bit MIPS emulation
while reusing code as much as possible.

* fix 64-bit test compilation errors

* review comments

* more review comments

* fcntl syscall err for 64-bit

* simplify pad check

* cannon: sc must store lsb 32 on 64-bits

* lint

* fix sc 64-bit logic

* add TODO state test
  • Loading branch information
Inphi authored Oct 1, 2024
1 parent fed6f35 commit a2653a3
Show file tree
Hide file tree
Showing 56 changed files with 1,181 additions and 666 deletions.
34 changes: 26 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ jobs:
description: Whether to notify on failure
type: boolean
default: false
mips64:
type: boolean
default: false
resource_class: xlarge
steps:
- checkout
Expand All @@ -184,14 +187,29 @@ jobs:
command: |
make lint
working_directory: cannon
- run:
name: Cannon Go tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
mkdir -p /testlogs
gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \
-- -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
working_directory: cannon
- when:
condition:
not: <<parameters.mips64>>
steps:
- run:
name: Cannon Go 32-bit tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
mkdir -p /testlogs
gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \
-- -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
working_directory: cannon
- when:
condition: <<parameters.mips64>>
steps:
- run:
name: Cannon Go 64-bit tests
command: |
export SKIP_SLOW_TESTS=<<parameters.skip_slow_tests>>
mkdir -p /testlogs
gotestsum --format=testname --junitfile=/tmp/test-results/cannon.xml --jsonfile=/testlogs/log.json \
-- --tags=cannon64 -parallel=8 -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage.out ./...
working_directory: cannon
- run:
name: upload Cannon coverage
command: codecov --verbose --clean --flags cannon-go-tests
Expand Down
24 changes: 16 additions & 8 deletions cannon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,25 @@ endif

.DEFAULT_GOAL := cannon

cannon-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon-impl .
cannon32-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon32 -v $(LDFLAGS) -o ./bin/cannon32-impl .

cannon-embeds: cannon-impl
@cp bin/cannon-impl ./multicannon/embeds/cannon-2
@cp bin/cannon-impl ./multicannon/embeds/cannon-1
cannon64-impl:
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build --tags=cannon64 -v $(LDFLAGS) -o ./bin/cannon64-impl .

cannon-embeds: cannon32-impl cannon64-impl
# singlethreaded-v2
@cp bin/cannon32-impl ./multicannon/embeds/cannon-2
# multithreaded
@cp bin/cannon32-impl ./multicannon/embeds/cannon-1
# 64-bit multithreaded
@cp bin/cannon64-impl ./multicannon/embeds/cannon-3

cannon: cannon-embeds
env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/

clean:
rm -rf bin
rm -rf bin multicannon/embeds/cannon*

elf:
make -C ./testdata/example elf
Expand Down Expand Up @@ -84,9 +91,10 @@ fuzz:
go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests

.PHONY: \
cannon \
cannon-impl \
cannon32-impl \
cannon64-impl \
cannon-embeds \
cannon \
clean \
test \
lint \
Expand Down
4 changes: 2 additions & 2 deletions cannon/cmd/load_elf.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var (
}
LoadELFPathFlag = &cli.PathFlag{
Name: "path",
Usage: "Path to 32-bit big-endian MIPS ELF file",
Usage: "Path to 32/64-bit big-endian MIPS ELF file",
TakesFile: true,
Required: true,
}
Expand Down Expand Up @@ -80,7 +80,7 @@ func LoadELF(ctx *cli.Context) error {
}
return program.PatchStack(state)
}
case versions.VersionMultiThreaded:
case versions.VersionMultiThreaded, versions.VersionMultiThreaded64:
createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) {
return program.LoadELF(f, multithreaded.CreateInitialState)
}
Expand Down
20 changes: 10 additions & 10 deletions cannon/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import (
"strings"
"time"

"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/arch"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/versions"
"github.com/ethereum-optimism/optimism/cannon/serialize"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/pkg/profile"
"github.com/urfave/cli/v2"

"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/cannon/mipsevm/program"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-service/jsonutil"
)

var (
Expand Down Expand Up @@ -128,7 +128,7 @@ type Proof struct {

OracleKey hexutil.Bytes `json:"oracle-key,omitempty"`
OracleValue hexutil.Bytes `json:"oracle-value,omitempty"`
OracleOffset uint32 `json:"oracle-offset,omitempty"`
OracleOffset arch.Word `json:"oracle-offset,omitempty"`
}

type rawHint string
Expand Down Expand Up @@ -288,7 +288,7 @@ func Run(ctx *cli.Context) error {

stopAtAnyPreimage := false
var stopAtPreimageKeyPrefix []byte
stopAtPreimageOffset := uint32(0)
stopAtPreimageOffset := arch.Word(0)
if ctx.IsSet(RunStopAtPreimageFlag.Name) {
val := ctx.String(RunStopAtPreimageFlag.Name)
parts := strings.Split(val, "@")
Expand All @@ -297,11 +297,11 @@ func Run(ctx *cli.Context) error {
}
stopAtPreimageKeyPrefix = common.FromHex(parts[0])
if len(parts) == 2 {
x, err := strconv.ParseUint(parts[1], 10, 32)
x, err := strconv.ParseUint(parts[1], 10, arch.WordSizeBytes)
if err != nil {
return fmt.Errorf("invalid preimage offset: %w", err)
}
stopAtPreimageOffset = uint32(x)
stopAtPreimageOffset = arch.Word(x)
}
} else {
switch ctx.String(RunStopAtPreimageTypeFlag.Name) {
Expand Down Expand Up @@ -463,7 +463,7 @@ func Run(ctx *cli.Context) error {
}

lastPreimageKey, lastPreimageValue, lastPreimageOffset := vm.LastPreimage()
if lastPreimageOffset != ^uint32(0) {
if lastPreimageOffset != ^arch.Word(0) {
if stopAtAnyPreimage {
l.Info("Stopping at preimage read")
break
Expand Down
2 changes: 1 addition & 1 deletion cannon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

func main() {
app := cli.NewApp()
app.Name = "cannon"
app.Name = os.Args[0]
app.Usage = "MIPS Fault Proof tool"
app.Description = "MIPS Fault Proof tool"
app.Commands = []*cli.Command{
Expand Down
48 changes: 48 additions & 0 deletions cannon/mipsevm/arch/arch32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//go:build !cannon64
// +build !cannon64

package arch

import "encoding/binary"

type (
// Word differs from the tradditional meaning in MIPS. The type represents the *maximum* architecture specific access length and value sizes.
Word = uint32
// SignedInteger specifies the maximum signed integer type used for arithmetic.
SignedInteger = int32
)

const (
IsMips32 = true
WordSize = 32
WordSizeBytes = WordSize >> 3
PageAddrSize = 12
PageKeySize = WordSize - PageAddrSize

MemProofLeafCount = 28
MemProofSize = MemProofLeafCount * 32

AddressMask = 0xFFffFFfc
ExtMask = 0x3

HeapStart = 0x05_00_00_00
HeapEnd = 0x60_00_00_00
ProgramBreak = 0x40_00_00_00
HighMemoryStart = 0x7f_ff_d0_00
)

var ByteOrderWord = byteOrder32{}

type byteOrder32 struct{}

func (bo byteOrder32) Word(b []byte) Word {
return binary.BigEndian.Uint32(b)
}

func (bo byteOrder32) AppendWord(b []byte, v uint32) []byte {
return binary.BigEndian.AppendUint32(b, v)
}

func (bo byteOrder32) PutWord(b []byte, v uint32) {
binary.BigEndian.PutUint32(b, v)
}
48 changes: 48 additions & 0 deletions cannon/mipsevm/arch/arch64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//go:build cannon64
// +build cannon64

package arch

import "encoding/binary"

type (
// Word differs from the tradditional meaning in MIPS. The type represents the *maximum* architecture specific access length and value sizes
Word = uint64
// SignedInteger specifies the maximum signed integer type used for arithmetic.
SignedInteger = int64
)

const (
IsMips32 = false
WordSize = 64
WordSizeBytes = WordSize >> 3
PageAddrSize = 12
PageKeySize = WordSize - PageAddrSize

MemProofLeafCount = 60
MemProofSize = MemProofLeafCount * 32

AddressMask = 0xFFFFFFFFFFFFFFF8
ExtMask = 0x7

HeapStart = 0x10_00_00_00_00_00_00_00
HeapEnd = 0x60_00_00_00_00_00_00_00
ProgramBreak = 0x40_00_00_00_00_00_00_00
HighMemoryStart = 0x7F_FF_FF_FF_D0_00_00_00
)

var ByteOrderWord = byteOrder64{}

type byteOrder64 struct{}

func (bo byteOrder64) Word(b []byte) Word {
return binary.BigEndian.Uint64(b)
}

func (bo byteOrder64) AppendWord(b []byte, v uint64) []byte {
return binary.BigEndian.AppendUint64(b, v)
}

func (bo byteOrder64) PutWord(b []byte, v uint64) {
binary.BigEndian.PutUint64(b, v)
}
7 changes: 7 additions & 0 deletions cannon/mipsevm/arch/byteorder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package arch

type ByteOrder interface {
Word([]byte) Word
AppendWord([]byte, Word) []byte
PutWord([]byte, Word)
}
12 changes: 6 additions & 6 deletions cannon/mipsevm/exec/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
)

type MemTracker interface {
TrackMemAccess(addr uint32)
TrackMemAccess(addr Word)
}

type MemoryTrackerImpl struct {
memory *memory.Memory
lastMemAccess uint32
lastMemAccess Word
memProofEnabled bool
// proof of first unique memory access
memProof [memory.MEM_PROOF_SIZE]byte
Expand All @@ -24,9 +24,9 @@ func NewMemoryTracker(memory *memory.Memory) *MemoryTrackerImpl {
return &MemoryTrackerImpl{memory: memory}
}

func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) {
func (m *MemoryTrackerImpl) TrackMemAccess(effAddr Word) {
if m.memProofEnabled && m.lastMemAccess != effAddr {
if m.lastMemAccess != ^uint32(0) {
if m.lastMemAccess != ^Word(0) {
panic(fmt.Errorf("unexpected different mem access at %08x, already have access at %08x buffered", effAddr, m.lastMemAccess))
}
m.lastMemAccess = effAddr
Expand All @@ -36,7 +36,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess(effAddr uint32) {

// 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 uint32) {
func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr Word) {
if m.memProofEnabled && m.lastMemAccess+4 != effAddr {
panic(fmt.Errorf("unexpected disjointed mem access at %08x, last memory access is at %08x buffered", effAddr, m.lastMemAccess))
}
Expand All @@ -46,7 +46,7 @@ func (m *MemoryTrackerImpl) TrackMemAccess2(effAddr uint32) {

func (m *MemoryTrackerImpl) Reset(enableProof bool) {
m.memProofEnabled = enableProof
m.lastMemAccess = ^uint32(0)
m.lastMemAccess = ^Word(0)
}

func (m *MemoryTrackerImpl) MemProof() [memory.MEM_PROOF_SIZE]byte {
Expand Down
Loading

0 comments on commit a2653a3

Please sign in to comment.