diff --git a/cannon/mipsevm/arch/arch32.go b/cannon/mipsevm/arch/arch32.go index 2d16ed3b84737..cf384710985ed 100644 --- a/cannon/mipsevm/arch/arch32.go +++ b/cannon/mipsevm/arch/arch32.go @@ -17,10 +17,12 @@ const ( WordSize = 32 ExtMask = 0x3 - HeapStart = 0x05_00_00_00 - HeapEnd = 0x60_00_00_00 - ProgramBreak = 0x40_00_00_00 - HighMemoryStart = 0x7f_ff_d0_00 + Limit = 0xff_ff_ff_ff + ProgramHeapStart = 0x00_c0_00_00 + HeapStart = 0x05_00_00_00 + HeapEnd = 0x60_00_00_00 + ProgramBreak = 0x40_00_00_00 + HighMemoryStart = 0x7f_ff_d0_00 ) // 32-bit Syscall codes diff --git a/cannon/mipsevm/arch/arch64.go b/cannon/mipsevm/arch/arch64.go index 9c70fb7ee8336..bd9da0338f5cd 100644 --- a/cannon/mipsevm/arch/arch64.go +++ b/cannon/mipsevm/arch/arch64.go @@ -18,11 +18,12 @@ const ( ExtMask = 0x7 // Ensure virtual address is limited to 48-bits as many user programs assume such to implement packed pointers - // limit 0x00_00_FF_FF_FF_FF_FF_FF - HeapStart = 0x00_00_10_00_00_00_00_00 - HeapEnd = 0x00_00_60_00_00_00_00_00 - ProgramBreak = 0x00_00_40_00_00_00_00_00 - HighMemoryStart = 0x00_00_7F_FF_FF_FF_F0_00 + Limit = 0x00_00_FF_FF_FF_FF_FF_FF + ProgramHeapStart = 0x00_00_00_c0_00_00_00_00 + HeapStart = 0x00_00_10_00_00_00_00_00 + HeapEnd = 0x00_00_60_00_00_00_00_00 + ProgramBreak = 0x00_00_40_00_00_00_00_00 + HighMemoryStart = 0x00_00_7F_FF_FF_FF_F0_00 ) // MIPS64 syscall table - https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/arch/mips/kernel/syscalls/syscall_n64.tbl. Generate the syscall numbers using the Makefile in that directory. diff --git a/cannon/mipsevm/memory/binary_tree.go b/cannon/mipsevm/memory/binary_tree.go index 9ad49262a14cc..edb48788155fb 100644 --- a/cannon/mipsevm/memory/binary_tree.go +++ b/cannon/mipsevm/memory/binary_tree.go @@ -2,6 +2,8 @@ package memory import ( "math/bits" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" ) // BinaryTreeIndex is a representation of the state of the memory in a binary merkle tree. @@ -15,10 +17,23 @@ type BinaryTreeIndex struct { func NewBinaryTreeMemory() *Memory { pages := make(map[Word]*CachedPage) index := NewBinaryTreeIndex(pages) + + indexedRegions := make([]MappedMemoryRegion, 2) + indexedRegions[0] = MappedMemoryRegion{ + start_addr: 0, + end_addr: min(arch.ProgramHeapStart, 1<<31), + Data: make([]byte, 1<<31), + } + indexedRegions[1] = MappedMemoryRegion{ + start_addr: arch.ProgramHeapStart, + end_addr: min(arch.HeapStart, arch.ProgramHeapStart+(1<<31)), + Data: make([]byte, 1<<31), + } return &Memory{ - merkleIndex: index, - pageTable: pages, - lastPageKeys: [2]Word{^Word(0), ^Word(0)}, // default to invalid keys, to not match any pages + merkleIndex: index, + pageTable: pages, + lastPageKeys: [2]Word{^Word(0), ^Word(0)}, // default to invalid keys, to not match any pages + MappedRegions: indexedRegions, } } @@ -83,7 +98,8 @@ func (m *BinaryTreeIndex) MerkleizeSubtree(gindex uint64) [32]byte { func (m *BinaryTreeIndex) MerkleProof(addr Word) (out [MemProofSize]byte) { proof := m.traverseBranch(1, addr, 0) // encode the proof - for i := 0; i < MemProofLeafCount; i++ { + _ = proof[MemProofLeafCount-1] + for i := 0; i <= MemProofLeafCount-1; i++ { copy(out[i*32:(i+1)*32], proof[i][:]) } return out diff --git a/cannon/mipsevm/memory/memory.go b/cannon/mipsevm/memory/memory.go index fe29db30acb55..b71a2634acda9 100644 --- a/cannon/mipsevm/memory/memory.go +++ b/cannon/mipsevm/memory/memory.go @@ -27,6 +27,27 @@ const ( type Word = arch.Word +type MappedMemoryRegion struct { + start_addr Word + end_addr Word + Data []byte +} + +func (m *MappedMemoryRegion) AddrInRegion(addr Word) bool { + return addr >= m.start_addr && addr < m.end_addr +} + +func (m *MappedMemoryRegion) PageIndexInRegion(pageIndex Word) bool { + return pageIndex >= m.start_addr>>PageAddrSize && pageIndex < m.end_addr>>PageAddrSize +} + +func (m *MappedMemoryRegion) AccessWordBytes(addr Word) ([]byte, bool) { + if m.AddrInRegion(addr) { + return m.Data[addr : addr+arch.WordSizeBytes : addr+arch.WordSizeBytes], true + } + return nil, false +} + type Memory struct { merkleIndex PageIndex // Note: since we don't de-alloc Pages, we don't do ref-counting. @@ -38,6 +59,8 @@ type Memory struct { // this prevents map lookups each instruction lastPageKeys [2]Word lastPage [2]*CachedPage + + MappedRegions []MappedMemoryRegion } type PageIndex interface { @@ -54,6 +77,45 @@ func NewMemory() *Memory { return NewBinaryTreeMemory() } +// start end size gap +func (m *Memory) GetAllocatedRanges() [][4]Word { + var ranges [][4]Word + if len(m.pageTable) == 0 { + return ranges + } + + // Extract and sort page addresses + keys := make([]Word, 0, len(m.pageTable)) + for key := range m.pageTable { + keys = append(keys, key) + } + sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) + + // Find contiguous ranges and gaps + start := keys[0] + prev := start + var lastEnd Word = start - 1 + + for i := 1; i < len(keys); i++ { + if keys[i] != prev+1 { + gap := start - lastEnd - 1 // Gap is calculated from end of prev range to start of new one + ranges = append(ranges, [4]Word{start, prev, prev - start + 1, gap}) + lastEnd = prev + start = keys[i] + } + prev = keys[i] + } + + // Append last range + gap := start - lastEnd - 1 + ranges = append(ranges, [4]Word{start, prev, prev - start + 1, gap}) + for i := 0; i < len(ranges); i++ { + ranges[i][0] <<= PageAddrSize + ranges[i][1] <<= PageAddrSize + } + return ranges +} + func (m *Memory) MerkleRoot() [32]byte { return m.MerkleizeSubtree(1) } @@ -66,7 +128,7 @@ func (m *Memory) PageCount() int { return len(m.pageTable) } -func (m *Memory) ForEachPage(fn func(pageIndex Word, page *Page) error) error { +func (m *Memory) ForEachPage(fn func(pageIndex Word, page Page) error) error { for pageIndex, cachedPage := range m.pageTable { if err := fn(pageIndex, cachedPage.Data); err != nil { return err @@ -74,7 +136,6 @@ func (m *Memory) ForEachPage(fn func(pageIndex Word, page *Page) error) error { } return nil } - func (m *Memory) MerkleizeSubtree(gindex uint64) [32]byte { return m.merkleIndex.MerkleizeSubtree(gindex) } @@ -145,6 +206,7 @@ func (m *Memory) SetWord(addr Word, v Word) { m.merkleIndex.Invalidate(addr) // invalidate this branch of memory, now that the value changed } } + arch.ByteOrderWord.PutWord(p.Data[pageAddr:pageAddr+arch.WordSizeBytes], v) } @@ -155,7 +217,15 @@ func (m *Memory) GetWord(addr Word) Word { if addr&arch.ExtMask != 0 { panic(fmt.Errorf("unaligned memory access: %x", addr)) } + for _, region := range m.MappedRegions { + if ok := region.AddrInRegion(addr); ok { + offset := addr - region.start_addr + return arch.ByteOrderWord.Word(region.Data[offset : offset+arch.WordSizeBytes : offset+arch.WordSizeBytes]) + } + } + pageIndex := addr >> PageAddrSize + p, ok := m.PageLookup(pageIndex) if !ok { return 0 @@ -165,7 +235,21 @@ func (m *Memory) GetWord(addr Word) Word { } func (m *Memory) AllocPage(pageIndex Word) *CachedPage { - p := &CachedPage{Data: new(Page)} + p := new(CachedPage) + for _, region := range m.MappedRegions { + if region.PageIndexInRegion(pageIndex) { + currLen := len(region.Data) + indexAdjusted := pageIndex - region.start_addr>>PageAddrSize + if indexAdjusted*PageSize >= Word(currLen) { + region.Data = region.Data[:(pageIndex+1)*PageSize] + } + p.Data = region.Data[indexAdjusted*PageSize : (indexAdjusted+1)*PageSize : (indexAdjusted+1)*PageSize] + break + } + } + if p.Data == nil { + p.Data = make(Page, PageSize) + } m.pageTable[pageIndex] = p m.merkleIndex.AddPage(pageIndex) return p @@ -237,8 +321,9 @@ func (m *Memory) Copy() *Memory { } for k, page := range m.pageTable { - data := new(Page) - *data = *page.Data + data := make(Page, PageSize) + // *data = *page.Data + copy(data, page.Data) out.AllocPage(k).Data = data } return out @@ -287,20 +372,23 @@ func (m *Memory) Deserialize(in io.Reader) error { return err } } + return nil } type pageEntry struct { - Index Word `json:"index"` - Data *Page `json:"data"` + Index Word `json:"index"` + Data *[PageSize]byte `json:"data"` } func (m *Memory) MarshalJSON() ([]byte, error) { // nosemgrep pages := make([]pageEntry, 0, len(m.pageTable)) for k, p := range m.pageTable { + data := new([PageSize]byte) + copy(data[:], p.Data) pages = append(pages, pageEntry{ Index: k, - Data: p.Data, + Data: data, }) } sort.Slice(pages, func(i, j int) bool { @@ -318,7 +406,8 @@ func (m *Memory) UnmarshalJSON(data []byte) error { if _, ok := m.pageTable[p.Index]; ok { return fmt.Errorf("cannot load duplicate page, entry %d, page index %d", i, p.Index) } - m.AllocPage(p.Index).Data = p.Data + page := m.AllocPage(p.Index) + copy(page.Data, p.Data[:]) } return nil } diff --git a/cannon/mipsevm/memory/page.go b/cannon/mipsevm/memory/page.go index 2d99b7ce273be..0f715ee3d96c9 100644 --- a/cannon/mipsevm/memory/page.go +++ b/cannon/mipsevm/memory/page.go @@ -18,9 +18,9 @@ var zlibWriterPool = sync.Pool{ }, } -type Page [PageSize]byte +type Page []byte -func (p *Page) MarshalJSON() ([]byte, error) { // nosemgrep +func (p Page) MarshalJSON() ([]byte, error) { // nosemgrep var out bytes.Buffer w := zlibWriterPool.Get().(*zlib.Writer) defer zlibWriterPool.Put(w) @@ -34,7 +34,7 @@ func (p *Page) MarshalJSON() ([]byte, error) { // nosemgrep return json.Marshal(out.Bytes()) } -func (p *Page) UnmarshalJSON(dat []byte) error { +func (p Page) UnmarshalJSON(dat []byte) error { // Strip off the `"` characters at the start & end. dat = dat[1 : len(dat)-1] // Decode b64 then decompress @@ -52,7 +52,7 @@ func (p *Page) UnmarshalJSON(dat []byte) error { } } -func (p *Page) UnmarshalText(dat []byte) error { +func (p Page) UnmarshalText(dat []byte) error { if len(dat) != PageSize*2 { return fmt.Errorf("expected %d hex chars, but got %d", PageSize*2, len(dat)) } @@ -61,7 +61,7 @@ func (p *Page) UnmarshalText(dat []byte) error { } type CachedPage struct { - Data *Page + Data Page // intermediate nodes only Cache [PageSize / 32][32]byte // bit set to 1 if the intermediate node is valid diff --git a/cannon/mipsevm/memory/page_test.go b/cannon/mipsevm/memory/page_test.go index e7a8167a9df49..81a0a612bd580 100644 --- a/cannon/mipsevm/memory/page_test.go +++ b/cannon/mipsevm/memory/page_test.go @@ -8,7 +8,7 @@ import ( ) func TestCachedPage(t *testing.T) { - p := &CachedPage{Data: new(Page)} + p := &CachedPage{Data: make(Page, PageSize, PageSize)} p.Data[42] = 0xab gindex := ((uint64(1) << PageAddrSize) | 42) >> 5 diff --git a/cannon/mipsevm/multithreaded/instrumented.go b/cannon/mipsevm/multithreaded/instrumented.go index 5295d74811616..9d4df29b4d2f2 100644 --- a/cannon/mipsevm/multithreaded/instrumented.go +++ b/cannon/mipsevm/multithreaded/instrumented.go @@ -11,6 +11,9 @@ import ( "github.com/ethereum-optimism/optimism/cannon/mipsevm/exec" ) +type InstructionDetails struct { + insn, opcode, fun uint32 +} type InstrumentedState struct { state *State @@ -24,11 +27,18 @@ type InstrumentedState struct { preimageOracle *exec.TrackingPreimageOracleReader meta mipsevm.Metadata + + cached_decode []InstructionDetails } var _ mipsevm.FPVM = (*InstrumentedState)(nil) func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdErr io.Writer, log log.Logger, meta mipsevm.Metadata) *InstrumentedState { + cached_decode := make([]InstructionDetails, 0) + for pc := Word(0); pc < Word(len(state.Memory.MappedRegions[0].Data)); pc += 4 { + insn, opcode, fun := exec.GetInstructionDetails(pc, state.Memory) + cached_decode = append(cached_decode, InstructionDetails{insn, opcode, fun}) + } return &InstrumentedState{ state: state, log: log, @@ -39,6 +49,7 @@ func NewInstrumentedState(state *State, po mipsevm.PreimageOracle, stdOut, stdEr statsTracker: NoopStatsTracker(), preimageOracle: exec.NewTrackingPreimageOracleReader(po), meta: meta, + cached_decode: cached_decode, } } diff --git a/cannon/mipsevm/multithreaded/mips.go b/cannon/mipsevm/multithreaded/mips.go index cc3bdf2c52bf5..0c9cbf173c110 100644 --- a/cannon/mipsevm/multithreaded/mips.go +++ b/cannon/mipsevm/multithreaded/mips.go @@ -255,8 +255,8 @@ func (m *InstrumentedState) doMipsStep() error { } m.state.StepsSinceLastContextSwitch += 1 - //instruction fetch - insn, opcode, fun := exec.GetInstructionDetails(m.state.GetPC(), m.state.Memory) + insn_detail := m.cached_decode[m.state.GetPC()/4] + insn, opcode, fun := insn_detail.insn, insn_detail.opcode, insn_detail.fun // Handle syscall separately // syscall (can read and write)