From a09987f5ac6c274dad37fbe89beca3613bbf7e6a Mon Sep 17 00:00:00 2001 From: "daniel.vladco" Date: Tue, 28 Jan 2025 15:37:19 +0200 Subject: [PATCH] refactor: memory layout clean and v0.5.4 compliant --- internal/polkavm/abi.go | 149 -------- internal/polkavm/abi_test.go | 115 ------ internal/polkavm/common.go | 145 ++++---- .../host_call/accumulate_functions_test.go | 38 +- .../host_call/general_functions_test.go | 176 +++++----- .../polkavm/host_call/refine_functions.go | 17 +- .../host_call/refine_functions_test.go | 329 +++++++++--------- internal/polkavm/initialisation.go | 133 +++++++ internal/polkavm/interpreter/instance.go | 17 +- .../polkavm/interpreter/interpreter_test.go | 65 ++-- internal/polkavm/interpreter/mutator.go | 35 +- internal/polkavm/interpreter/utils.go | 20 +- internal/polkavm/program.go | 322 +++-------------- internal/polkavm/program_test.go | 132 ------- .../testdata/example-hello-world.polkavm | Bin 93 -> 0 bytes tests/integration/pvm_integration_test.go | 29 +- 16 files changed, 611 insertions(+), 1111 deletions(-) delete mode 100644 internal/polkavm/abi.go delete mode 100644 internal/polkavm/abi_test.go create mode 100644 internal/polkavm/initialisation.go delete mode 100644 internal/polkavm/program_test.go delete mode 100644 internal/polkavm/testdata/example-hello-world.polkavm diff --git a/internal/polkavm/abi.go b/internal/polkavm/abi.go deleted file mode 100644 index 89b68f8e..00000000 --- a/internal/polkavm/abi.go +++ /dev/null @@ -1,149 +0,0 @@ -// Constants, memory map and align to next page implementation used as reference are taken from: -// https://github.com/koute/polkavm/blob/e905e981c756bc9a27a5780341f24c7bdabae568/crates/polkavm-common/src/abi.rs -// We are using the same ABI as koute's PVM implementation in order for us to be able to -// run and test the programs designed specifically for koute's PVM implementation. -// However, this is the only thing that these two PVMs have in common. - -package polkavm - -import ( - "errors" - "fmt" - "math" -) - -const ( - AddressSpaceSize = 0x100000000 // 2^32 - VmMaxPageSize = 0x10000 // The maximum page size of the VM. - VMPageSize = uint32(1 << 12) // ZP: 4096 (2^12) The pvm memory page size. - VmAddressReturnToHost = 0xffff0000 // The address which, when jumped to, will return to the host. - VmAddressSpaceTop = AddressSpaceSize - VmMaxPageSize // The address at which the program's stackData starts inside of the VM. - VmAddressSpaceBottom = VmMaxPageSize // The bottom of the accessible address space inside the VM (ZQ?) - VMMaxPageIndex = AddressSpaceSize / uint64(VMPageSize) // 2^32 / ZP = 1 << 20 -) - -var ( - ErrPageValueTooLarge = errors.New("page value too large") -) - -func NewMemoryMap(roDataSize, rwDataSize, stackSize, argsDataSize uint32) (*MemoryMap, error) { - roDataAddressSpace, err := AlignToNextPage(roDataSize) - if err != nil { - return nil, fmt.Errorf("failed to instantiate memory map ro data address space: %w", err) - } - - roDataSize, err = AlignToNextPage(roDataSize) - if err != nil { - return nil, fmt.Errorf("failed to instantiate memory map ro data size: %w", err) - } - - rwDataAddressSpace, err := AlignToNextPage(rwDataSize) - if err != nil { - return nil, fmt.Errorf("failed to instantiate memory map rw data address space: %w", err) - } - - originalRwDataSize := rwDataSize - rwDataSize, err = AlignToNextPage(rwDataSize) - if err != nil { - return nil, fmt.Errorf("failed to instantiate memory map rw data size: %w", err) - } - - stackAddressSpace, err := AlignToNextPage(stackSize) - if err != nil { - return nil, fmt.Errorf("failed to instantiate memory map stackData address space: %w", err) - } - - stackSize, err = AlignToNextPage(stackSize) - if err != nil { - return nil, fmt.Errorf("failed to instantiate memory map stackData size: %w", err) - } - argsDataAddressSpace, err := AlignToNextPage(argsDataSize) - if err != nil { - return nil, fmt.Errorf("the size of the arguments data is too big %w", err) - } - - argsDataSize, err = AlignToNextPage(argsDataSize) - if err != nil { - return nil, fmt.Errorf("the size of the arguments data is too big %w", err) - } - var addressLow uint32 - addressLow += VmAddressSpaceBottom - addressLow += roDataAddressSpace - addressLow += VmMaxPageSize - - rwDataAddress := addressLow - heapBase := addressLow + originalRwDataSize - addressLow += rwDataAddressSpace - heapSlack := addressLow - heapBase - addressLow += VmMaxPageSize - - var addressHigh uint32 = VmAddressSpaceTop - addressHigh -= argsDataAddressSpace - argsDataAddress := addressHigh - addressHigh -= VmMaxPageSize - stackAddressHigh := addressHigh - addressHigh -= stackAddressSpace - - if addressLow > addressHigh { - return nil, fmt.Errorf("maximum memory size exceeded") - } - - maxHeapSize := addressHigh - addressLow + heapSlack - - return &MemoryMap{ - RODataSize: roDataSize, - RWDataAddress: rwDataAddress, - RWDataSize: rwDataSize, - StackAddressHigh: stackAddressHigh, - StackAddressLow: stackAddressHigh - stackSize, - StackSize: stackSize, - HeapBase: heapBase, - MaxHeapSize: maxHeapSize, - RODataAddress: VmMaxPageSize, - ArgsDataAddress: argsDataAddress, - ArgsDataSize: argsDataSize, - }, nil -} - -type MemoryMap struct { - RODataSize uint32 - RWDataSize uint32 - StackSize uint32 - HeapBase uint32 - MaxHeapSize uint32 - StackAddressHigh uint32 - RODataAddress uint32 - StackAddressLow uint32 - RWDataAddress uint32 - ArgsDataAddress uint32 - ArgsDataSize uint32 -} - -func (mm MemoryMap) NewMemory(rwData, roData, argsData []byte) Memory { - m := Memory{ - data: []*memorySegment{ - {mm.ArgsDataAddress, mm.ArgsDataAddress + mm.ArgsDataSize, ReadOnly, copySized(argsData, mm.ArgsDataSize)}, - {mm.StackAddressLow, mm.StackAddressLow + mm.StackSize, ReadWrite, make([]byte, mm.StackSize)}, - {mm.RWDataAddress, mm.RWDataAddress + mm.RWDataSize, ReadWrite, copySized(rwData, mm.RWDataSize)}, - {mm.RODataAddress, mm.RODataAddress + mm.RODataSize, ReadOnly, copySized(roData, mm.RODataSize)}, - }, - } - return m -} - -func copySized(data []byte, size uint32) []byte { - dst := make([]byte, size) - copy(dst, data) - return dst -} - -func AlignToNextPage(value uint32) (uint32, error) { - if value&(VMPageSize-1) == 0 { - return value, nil - } - if value <= (math.MaxUint32 - VmMaxPageSize) { - return (value + VMPageSize) & ^(VMPageSize - 1), nil - } - - return 0, ErrPageValueTooLarge -} diff --git a/internal/polkavm/abi_test.go b/internal/polkavm/abi_test.go deleted file mode 100644 index ecd3d78d..00000000 --- a/internal/polkavm/abi_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package polkavm - -import ( - "math" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_memoryMap(t *testing.T) { - maxSize := uint32(AddressSpaceSize - uint64(VmMaxPageSize)*5) - tests := []struct { - expectError bool - roDataSize, rwDataSize, stackSize uint32 - expectedRODataAddress, expectedRODataSize, expectedRWDataSize, expectedRWDataAddress, - expectedStackSize, expectedStackAddressHigh, expectedStackAddressLow, expectedHeapBase uint32 - expectedMaxHeapSize uint64 - }{{ - roDataSize: 1, rwDataSize: 1, stackSize: 1, - expectedRODataAddress: 0x10000, - expectedRODataSize: 0x1000, - expectedRWDataSize: 0x1000, - expectedStackSize: 0x1000, - expectedRWDataAddress: VmAddressSpaceBottom + 0x1000 + VmMaxPageSize, - expectedStackAddressHigh: VmAddressSpaceTop - VmMaxPageSize, - expectedStackAddressLow: VmAddressSpaceTop - VmMaxPageSize - 0x1000, - expectedHeapBase: VmAddressSpaceBottom + 0x1000 + VmMaxPageSize + 1, - expectedMaxHeapSize: func() uint64 { - addressLow := VmAddressSpaceBottom + uint32(0x1000) + VmMaxPageSize + uint32(0x1000) + VmMaxPageSize - heapSlack := uint32(0x1000) - 1 - addressHigh := VmAddressSpaceTop - VmMaxPageSize - uint32(0x1000) - - return uint64(addressHigh - addressLow + heapSlack) - }(), - }, { - roDataSize: maxSize, rwDataSize: 0, stackSize: 0, - expectedRODataAddress: 0x10000, - expectedRODataSize: maxSize, - expectedRWDataAddress: 0x10000 + VmMaxPageSize + maxSize, - expectedRWDataSize: 0, - expectedStackAddressHigh: VmAddressSpaceTop - VmMaxPageSize, - expectedStackAddressLow: VmAddressSpaceTop - VmMaxPageSize, - expectedStackSize: 0, - expectedHeapBase: 0x10000 + VmMaxPageSize + maxSize, - expectedMaxHeapSize: 0, - }, { - roDataSize: maxSize + 1, rwDataSize: 0, stackSize: 0, - expectError: true, - }, { - roDataSize: maxSize, rwDataSize: 1, stackSize: 0, - expectError: true, - }, { - roDataSize: maxSize, rwDataSize: 0, stackSize: 1, - expectError: true, - }, { - roDataSize: 0, rwDataSize: maxSize, stackSize: 0, - expectedRODataAddress: VmMaxPageSize, - expectedRODataSize: 0, - expectedRWDataAddress: VmMaxPageSize * 2, - expectedRWDataSize: maxSize, - expectedStackAddressHigh: VmAddressSpaceTop - VmMaxPageSize, - expectedStackAddressLow: VmAddressSpaceTop - VmMaxPageSize, - expectedStackSize: 0, - expectedHeapBase: VmMaxPageSize*2 + maxSize, - expectedMaxHeapSize: 0, - }, { - roDataSize: 0, rwDataSize: 0, stackSize: maxSize, - expectedRODataAddress: VmMaxPageSize, - expectedRODataSize: 0, - expectedRWDataAddress: VmMaxPageSize * 2, - expectedRWDataSize: 0, - expectedStackAddressHigh: VmAddressSpaceTop - VmMaxPageSize, - expectedStackAddressLow: VmAddressSpaceTop - VmMaxPageSize - maxSize, - expectedStackSize: maxSize, - expectedHeapBase: VmMaxPageSize * 2, - expectedMaxHeapSize: 0, - }} - for _, tc := range tests { - t.Run("", func(t *testing.T) { - m, err := NewMemoryMap(tc.roDataSize, tc.rwDataSize, tc.stackSize, 0) - if err != nil { - if tc.expectError { - return - } - t.Fatal(err) - } - assert.Equal(t, tc.expectedRODataAddress, m.RODataAddress) - assert.Equal(t, tc.expectedRODataSize, m.RODataSize) - assert.Equal(t, tc.expectedRWDataAddress, m.RWDataAddress) - assert.Equal(t, tc.expectedStackSize, m.StackSize) - assert.Equal(t, tc.expectedStackAddressHigh, m.StackAddressHigh) - assert.Equal(t, tc.expectedStackAddressLow, m.StackAddressLow) - assert.Equal(t, tc.expectedHeapBase, m.HeapBase) - assert.Equal(t, tc.expectedMaxHeapSize, uint64(m.MaxHeapSize)) - }) - } -} - -func Test_alignToNextPageUint32(t *testing.T) { - v, _ := AlignToNextPage(0) - assert.Equal(t, uint32(0), v) - v, _ = AlignToNextPage(1) - assert.Equal(t, uint32(4096), v) - v, _ = AlignToNextPage(4095) - assert.Equal(t, uint32(4096), v) - v, _ = AlignToNextPage(uint32(4096)) - assert.Equal(t, uint32(4096), v) - v, _ = AlignToNextPage(4097) - assert.Equal(t, uint32(8192), v) - var maxVal uint32 = math.MaxUint32 + 1 - 4096 - v, _ = AlignToNextPage(maxVal) - assert.Equal(t, maxVal, v) - _, err := AlignToNextPage(maxVal + 1) - assert.Error(t, err) -} diff --git a/internal/polkavm/common.go b/internal/polkavm/common.go index 41f7749c..8fa45fde 100644 --- a/internal/polkavm/common.go +++ b/internal/polkavm/common.go @@ -16,103 +16,124 @@ const ( ReadWrite // W (Read-Write) ) -// Memory Equation: 34 (M) +// Memory M ≡ (V ∈ Y_(2^32), A ∈ ⟦{W, R, ∅}⟧p) (eq. 4.24) +// for practical reasons we define each memory segment separately +// so we don't have to allocate [2^32]byte unnecessarily type Memory struct { - data []*memorySegment // data (V ∈ Y, A ∈ ⟦{W, R, ∅}) + ro memorySegment + rw memorySegment + stack memorySegment + args memorySegment } type memorySegment struct { - start, end uint32 - access MemoryAccess - data []byte + address uint32 + data []byte + access MemoryAccess } // Read reads from the set of readable indices (Vμ) func (m *Memory) Read(address uint32, data []byte) error { - memSeg := m.inRange(address) - if memSeg == nil || memSeg.access == Inaccessible { + var memoryData []byte + var access MemoryAccess + if address >= m.stack.address && address+uint32(len(data)) <= m.stack.address+uint32(len(m.stack.data)) { + memoryData = m.stack.data[address-m.stack.address : address-m.stack.address+uint32(len(data))] + access = m.stack.access + } else if address >= m.rw.address && address+uint32(len(data)) <= m.rw.address+uint32(len(m.rw.data)) { + memoryData = m.rw.data[address-m.rw.address : address-m.rw.address+uint32(len(data))] + access = m.rw.access + } else if address >= m.ro.address && address+uint32(len(data)) <= m.ro.address+uint32(len(m.ro.data)) { + memoryData = m.ro.data[address-m.ro.address : address-m.ro.address+uint32(len(data))] + access = m.ro.access + } else if address >= m.args.address && address+uint32(len(data)) <= m.args.address+uint32(len(m.args.data)) { + memoryData = m.args.data[address-m.args.address : address-m.args.address+uint32(len(data))] + access = m.args.access + } else { return &ErrPageFault{Reason: "inaccessible memory", Address: address} } - - offset := int(address - memSeg.start) - offsetEnd := offset + len(data) - if offsetEnd > len(memSeg.data) { - return &ErrPageFault{Reason: "memory exceeds page size, growing memory not supported", Address: address} + if access == Inaccessible { + return &ErrPageFault{Reason: "inaccessible memory", Address: address} } - copy(data, memSeg.data[offset:offsetEnd]) + copy(data, memoryData) return nil } -func (m *Memory) inRange(address uint32) *memorySegment { - for _, r := range m.data { - if address >= r.start && address <= r.end { - return r - } +// Write writes to the set of writeable indices (Vμ*) +func (m *Memory) Write(address uint32, data []byte) error { + var memoryData []byte + var access MemoryAccess + if address >= m.stack.address && address+uint32(len(data)) <= m.stack.address+uint32(len(m.stack.data)) { + memoryData = m.stack.data[address-m.stack.address : address-m.stack.address+uint32(len(data))] + access = m.stack.access + } else if address >= m.rw.address && address+uint32(len(data)) <= m.rw.address+uint32(len(m.rw.data)) { + memoryData = m.rw.data[address-m.rw.address : address-m.rw.address+uint32(len(data))] + access = m.rw.access + } else if address >= m.ro.address && address+uint32(len(data)) <= m.ro.address+uint32(len(m.ro.data)) { + memoryData = m.ro.data[address-m.ro.address : address-m.ro.address+uint32(len(data))] + access = m.ro.access + } else if address >= m.args.address && address+uint32(len(data)) <= m.args.address+uint32(len(m.args.data)) { + memoryData = m.args.data[address-m.args.address : address-m.args.address+uint32(len(data))] + access = m.args.access + } else { + return &ErrPageFault{Reason: "inaccessible memory", Address: address} } - return nil -} - -func (m *Memory) Sbrk(heapTop uint32) error { - if heapTop > m.data[2].end { - nextPage, err := AlignToNextPage(heapTop) - if err != nil { - return err - } - - m.data[2].end += nextPage + if access != ReadWrite { + return &ErrPageFault{Reason: "memory at address is not writeable", Address: address} } + copy(memoryData, data) return nil } -// Write writes to the set of writeable indices (Vμ*) -func (m *Memory) Write(address uint32, data []byte) error { - memSeg := m.inRange(address) - if memSeg == nil { - return &ErrPageFault{Reason: "address not in a valid range", Address: address} +func (m *Memory) Sbrk(size uint32) (uint32, error) { + currentHeapPointer := m.rw.address + uint32(len(m.rw.data)) // h + if size == 0 { + return currentHeapPointer, nil } - if memSeg.access != ReadWrite { - return &ErrPageFault{ - Reason: "memory at address is not writeable", - Address: address, - } + newHeapPointer := currentHeapPointer + size + if newHeapPointer >= m.stack.address { // where the next memory segment begins + return 0, &ErrPageFault{Reason: "allocation failed heap pointer exceeds maximum allowed", Address: newHeapPointer} } - offset := int(address - memSeg.start) - offsetEnd := offset + len(data) - if offsetEnd > len(memSeg.data) { - return &ErrPageFault{Reason: "memory exceeds page size, growing memory not supported", Address: address} + if newHeapPointer > currentHeapPointer { + m.rw.data = make([]byte, alignToPage(newHeapPointer)) } - copy(memSeg.data[offset:offsetEnd], data) - return nil + + return m.rw.address + uint32(len(m.rw.data)), nil } // SetAccess updates the access mode func (m *Memory) SetAccess(pageIndex uint32, access MemoryAccess) error { - pageStart := pageIndex * VMPageSize - pageEnd := pageStart + VMPageSize - 1 + address := pageIndex * PageSize - for _, seg := range m.data { - if seg.start <= pageStart && seg.end >= pageEnd { - seg.access = access - return nil - } + if address >= m.stack.address && address <= m.stack.address+uint32(len(m.stack.data)) { + m.stack.access = access + return nil + } else if address >= m.rw.address && address <= m.rw.address+uint32(len(m.rw.data)) { + m.rw.access = access + return nil + } else if address >= m.ro.address && address <= m.ro.address+uint32(len(m.ro.data)) { + m.ro.access = access + return nil + } else if address >= m.args.address && address <= m.args.address+uint32(len(m.args.data)) { + m.args.access = access + return nil } - return &ErrPageFault{ - Reason: "page out of valid range", - Address: pageStart, - } + return &ErrPageFault{Reason: "page out of valid range", Address: address} } func (m *Memory) GetAccess(pageIndex uint32) MemoryAccess { - pageStart := pageIndex * VMPageSize - pageEnd := pageStart + VMPageSize - 1 + address := pageIndex * PageSize - for _, seg := range m.data { - if seg.start <= pageStart && seg.end >= pageEnd { - return seg.access - } + if address >= m.stack.address && address <= m.stack.address+uint32(len(m.stack.data)) { + return m.stack.access + } else if address >= m.rw.address && address <= m.rw.address+uint32(len(m.rw.data)) { + return m.rw.access + } else if address >= m.ro.address && address <= m.ro.address+uint32(len(m.ro.data)) { + return m.ro.access + } else if address >= m.args.address && address <= m.args.address+uint32(len(m.args.data)) { + return m.args.access } return Inaccessible diff --git a/internal/polkavm/host_call/accumulate_functions_test.go b/internal/polkavm/host_call/accumulate_functions_test.go index a179f5ed..ae1cadc4 100644 --- a/internal/polkavm/host_call/accumulate_functions_test.go +++ b/internal/polkavm/host_call/accumulate_functions_test.go @@ -22,12 +22,17 @@ import ( func TestAccumulate(t *testing.T) { pp := &Program{ - RODataSize: 0, - RWDataSize: 256, - StackSize: 512, - Instructions: []Instruction{ - {Opcode: Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: JumpIndirect, Imm: []uint32{0}, Reg: []Reg{RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: ProgramMemorySizes{ + RODataSize: 0, + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 200, + }, + CodeAndJumpTable: CodeAndJumpTable{ + Instructions: []Instruction{ + {Opcode: Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: JumpIndirect, Imm: []uint32{0}, Reg: []Reg{RA}, Offset: 1, Length: 2}, + }, }, } @@ -888,22 +893,18 @@ func TestAccumulate(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - memoryMap, err := NewMemoryMap(0, 0, 1<<19, 0) - require.NoError(t, err) - - mem := memoryMap.NewMemory(nil, nil, nil) - initialRegs := Registers{ - RA: VmAddressReturnToHost, - SP: uint64(memoryMap.StackAddressHigh), + mem, initialRegs, err := InitializeStandardProgram(pp, nil) + if err != nil { + t.Fatal(err) } - stackAddress := memoryMap.StackAddressLow + rwAddress := RWAddressBase for addrReg, v := range tc.alloc { require.Greater(t, addrReg, S1) - err = mem.Write(stackAddress, v) + err = mem.Write(rwAddress, v) require.NoError(t, err) - initialRegs[addrReg] = uint64(stackAddress) - stackAddress = stackAddress + uint32(len(v)) + initialRegs[addrReg] = uint64(rwAddress) + rwAddress = rwAddress + uint32(len(v)) } for i, v := range tc.initialRegs { initialRegs[i] = v @@ -914,8 +915,7 @@ func TestAccumulate(t *testing.T) { return gasCounter, regs, mem, x, nil } gasRemaining, regs, _, ctxPair, err := interpreter.InvokeHostCall( - pp, memoryMap, - 0, tc.initialGas, initialRegs, mem, + pp, 0, tc.initialGas, initialRegs, mem, hostCall, AccumulateContextPair{ RegularCtx: tc.X, ExceptionalCtx: tc.Y, diff --git a/internal/polkavm/host_call/general_functions_test.go b/internal/polkavm/host_call/general_functions_test.go index 0f3a29f8..72721878 100644 --- a/internal/polkavm/host_call/general_functions_test.go +++ b/internal/polkavm/host_call/general_functions_test.go @@ -17,20 +17,20 @@ import ( func TestGasRemaining(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + InitialHeapPages: 100, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 0, 4096, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) - mem := memoryMap.NewMemory(nil, nil, nil) - - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - } initialGas := uint64(100) hostCall := func(hostCall uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mem polkavm.Memory, x struct{}) (polkavm.Gas, polkavm.Registers, polkavm.Memory, struct{}, error) { gasCounter, regs, err = host_call.GasRemaining(gasCounter, regs) @@ -38,7 +38,7 @@ func TestGasRemaining(t *testing.T) { return gasCounter, regs, mem, struct{}{}, nil } - gas, regs, _, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, struct{}{}) + gas, regs, _, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, struct{}{}) require.ErrorIs(t, err, polkavm.ErrHalt) expectedGas := polkavm.Gas(initialGas) - host_call.GasRemainingCost - polkavm.GasCosts[polkavm.Ecalli] @@ -49,32 +49,30 @@ func TestGasRemaining(t *testing.T) { func TestLookup(t *testing.T) { pp := &polkavm.Program{ - RODataSize: 0, - RWDataSize: 256, - StackSize: 512, - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RODataSize: 0, + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 100, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, - Imports: []string{"lookup"}, - Exports: []polkavm.ProgramExport{{TargetCodeOffset: 0, Symbol: "test_lookup"}}, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) - require.NoError(t, err) t.Run("service_not_found", func(t *testing.T) { - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - } - mem := memoryMap.NewMemory(nil, nil, nil) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) + require.NoError(t, err) initialGas := uint64(100) hostCall := func(hostCall uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mem polkavm.Memory, x service.ServiceAccount) (polkavm.Gas, polkavm.Registers, polkavm.Memory, service.ServiceAccount, error) { gasCounter, regs, mem, err = host_call.Lookup(gasCounter, regs, mem, service.ServiceAccount{}, 1, make(service.ServiceState)) require.NoError(t, err) return gasCounter, regs, mem, x, nil } - gasRemaining, regs, _, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) + gasRemaining, regs, _, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) require.ErrorIs(t, err, polkavm.ErrHalt) assert.Equal(t, uint64(host_call.NONE), regs[polkavm.A0]) @@ -82,26 +80,23 @@ func TestLookup(t *testing.T) { }) t.Run("successful_key_lookup", func(t *testing.T) { - mem := memoryMap.NewMemory(nil, nil, nil) initialGas := uint64(100) serviceId := block.ServiceId(1) val := []byte("value to store") - ho := memoryMap.RWDataAddress - bo := memoryMap.RWDataAddress + 100 + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) + require.NoError(t, err) + ho := polkavm.RWAddressBase + bo := polkavm.RWAddressBase + 100 dataToHash := make([]byte, 32) copy(dataToHash, "hash") hash := crypto.HashData(dataToHash) - err := mem.Write(ho, dataToHash) + err = mem.Write(ho, dataToHash) require.NoError(t, err) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(serviceId), - polkavm.A1: uint64(ho), - polkavm.A2: uint64(bo), - polkavm.A3: 32, - } + initialRegs[polkavm.A0] = uint64(serviceId) + initialRegs[polkavm.A1] = uint64(ho) + initialRegs[polkavm.A2] = uint64(bo) + initialRegs[polkavm.A3] = 32 sa := service.ServiceAccount{ Storage: map[crypto.Hash][]byte{ hash: val, @@ -116,7 +111,7 @@ func TestLookup(t *testing.T) { require.NoError(t, err) return gasCounter, regs, mem, x, nil } - gasRemaining, regs, mem, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, sa) + gasRemaining, regs, mem, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, sa) require.ErrorIs(t, err, polkavm.ErrHalt) actualValue := make([]byte, len(val)) @@ -131,13 +126,20 @@ func TestLookup(t *testing.T) { func TestRead(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) serviceId := block.ServiceId(1) @@ -164,20 +166,15 @@ func TestRead(t *testing.T) { initialGas := uint64(100) - ko := memoryMap.RWDataAddress - bo := memoryMap.RWDataAddress + 100 + ko := polkavm.RWAddressBase + bo := polkavm.RWAddressBase + 100 kz := uint32(len(keyData)) bz := uint32(32) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(serviceId), - polkavm.A1: uint64(ko), - polkavm.A2: uint64(kz), - polkavm.A3: uint64(bo), - polkavm.A4: uint64(bz), - } - mem := memoryMap.NewMemory(nil, nil, nil) + initialRegs[polkavm.A0] = uint64(serviceId) + initialRegs[polkavm.A1] = uint64(ko) + initialRegs[polkavm.A2] = uint64(kz) + initialRegs[polkavm.A3] = uint64(bo) + initialRegs[polkavm.A4] = uint64(bz) err = mem.Write(ko, keyData) require.NoError(t, err) @@ -186,7 +183,7 @@ func TestRead(t *testing.T) { require.NoError(t, err) return gasCounter, regs, mem, x, nil } - gasRemaining, regs, mem, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, sa) + gasRemaining, regs, mem, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, sa) require.ErrorIs(t, err, polkavm.ErrHalt) actualValue := make([]byte, len(value)) @@ -202,13 +199,20 @@ func TestRead(t *testing.T) { func TestWrite(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) serviceId := block.ServiceId(1) @@ -230,21 +234,16 @@ func TestWrite(t *testing.T) { initialGas := uint64(100) - ko := memoryMap.RWDataAddress + ko := polkavm.RWAddressBase kz := uint32(len(keyData)) - vo := memoryMap.RWDataAddress + 100 + vo := polkavm.RWAddressBase + 100 vz := uint32(len(value)) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(ko), - polkavm.A1: uint64(kz), - polkavm.A2: uint64(vo), - polkavm.A3: uint64(vz), - } - mem := memoryMap.NewMemory(nil, nil, nil) + initialRegs[polkavm.A0] = uint64(ko) + initialRegs[polkavm.A1] = uint64(kz) + initialRegs[polkavm.A2] = uint64(vo) + initialRegs[polkavm.A3] = uint64(vz) err = mem.Write(ko, keyData) require.NoError(t, err) err = mem.Write(vo, value) @@ -254,7 +253,7 @@ func TestWrite(t *testing.T) { require.NoError(t, err) return gasCounter, regs, mem, a, nil } - gasRemaining, regs, _, sa, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, sa) + gasRemaining, regs, _, sa, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, sa) require.ErrorIs(t, err, polkavm.ErrHalt) actualValue := make([]byte, len(value)) @@ -279,18 +278,20 @@ func TestWrite(t *testing.T) { func TestInfo(t *testing.T) { pp := &polkavm.Program{ - RODataSize: 0, - RWDataSize: 256, - StackSize: 512, - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, - Imports: []string{"info"}, - Exports: []polkavm.ProgramExport{{TargetCodeOffset: 0, Symbol: "test_info"}}, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) serviceId := block.ServiceId(1) @@ -313,20 +314,15 @@ func TestInfo(t *testing.T) { initialGas := uint64(100) - omega1 := memoryMap.RWDataAddress - mem := memoryMap.NewMemory(nil, nil, nil) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(serviceId), - polkavm.A1: uint64(omega1), - } + omega1 := polkavm.RWAddressBase + initialRegs[polkavm.A0] = uint64(serviceId) + initialRegs[polkavm.A1] = uint64(omega1) hostCall := func(hostCall uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mem polkavm.Memory, x service.ServiceAccount) (polkavm.Gas, polkavm.Registers, polkavm.Memory, service.ServiceAccount, error) { gasCounter, regs, mem, err = host_call.Info(gasCounter, regs, mem, serviceId, serviceState) require.NoError(t, err) return gasCounter, regs, mem, x, nil } - gasRemaining, regs, _, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, sampleAccount) + gasRemaining, regs, _, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, sampleAccount) require.ErrorIs(t, err, polkavm.ErrHalt) require.Equal(t, uint64(host_call.OK), regs[polkavm.A0]) diff --git a/internal/polkavm/host_call/refine_functions.go b/internal/polkavm/host_call/refine_functions.go index d6e3f2cf..8a5480c2 100644 --- a/internal/polkavm/host_call/refine_functions.go +++ b/internal/polkavm/host_call/refine_functions.go @@ -3,7 +3,6 @@ package host_call import ( "bytes" "errors" - "log" "math" "github.com/eigerco/strawberry/internal/block" @@ -286,7 +285,7 @@ func Zero( n, p, c := regs[A0], regs[A1], regs[A2] // p < 16 ∨ p + c ≥ 2^32 / ZP - if p < 16 || p+c >= VMMaxPageIndex { + if p < 16 || p+c >= MaxPageIndex { return gas, withCode(regs, OOB), mem, ctxPair, nil } @@ -303,8 +302,8 @@ func Zero( } // (u′V)pZP..+cZP = [0, 0, ...] - start := uint32(pageIndex * uint64(VMPageSize)) - zeroBuf := make([]byte, VMPageSize) + start := uint32(pageIndex * uint64(PageSize)) + zeroBuf := make([]byte, PageSize) if err := u.Ram.Write(start, zeroBuf); err != nil { return gas, withCode(regs, OOB), mem, ctxPair, nil } @@ -345,8 +344,8 @@ func Void( } // (u′V)pZP..+cZP = [0, 0, ...] - start := uint32(pageIndex * uint64(VMPageSize)) - zeroBuf := make([]byte, VMPageSize) + start := uint32(pageIndex * uint64(PageSize)) + zeroBuf := make([]byte, PageSize) if err := u.Ram.Write(start, zeroBuf); err != nil { return gas, withCode(regs, OOB), mem, ctxPair, nil } @@ -411,13 +410,11 @@ func Invoke( // we only parse the code and jump table as we are not expected to invoke a full program program := &Program{} - if err := ParseCodeAndJumpTable(uint32(len(pvm.Code)), NewReader(bytes.NewReader(pvm.Code)), program); err != nil { + if err := ParseCodeAndJumpTable(uint32(len(pvm.Code)), NewReader(bytes.NewReader(pvm.Code)), &program.CodeAndJumpTable); err != nil { return gas, withCode(regs, PANIC), mem, ctxPair, nil } - log.Println("invokeGas", invokeGas) - log.Println("invokeRegs", invokeRegs) - resultInstr, resultGas, resultRegs, resultMem, hostCall, invokeErr := interpreter.Invoke(program, nil, pvm.InstructionCounter, invokeGas, invokeRegs, pvm.Ram) + resultInstr, resultGas, resultRegs, resultMem, hostCall, invokeErr := interpreter.Invoke(program, pvm.InstructionCounter, invokeGas, invokeRegs, pvm.Ram) if bb, err := jam.Marshal([14]uint64(append([]uint64{uint64(resultGas)}, resultRegs[:]...))); err != nil { return gas, withCode(regs, OOB), mem, ctxPair, nil // (OOB, ω8, μ, m) diff --git a/internal/polkavm/host_call/refine_functions_test.go b/internal/polkavm/host_call/refine_functions_test.go index b31126f2..714dc095 100644 --- a/internal/polkavm/host_call/refine_functions_test.go +++ b/internal/polkavm/host_call/refine_functions_test.go @@ -22,13 +22,20 @@ var initialGas = uint64(100) func TestHistoricalLookup(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) serviceId := block.ServiceId(1) @@ -59,20 +66,15 @@ func TestHistoricalLookup(t *testing.T) { hashData := make([]byte, 32) copy(hashData, preimage) - ho := memoryMap.RWDataAddress - bo := memoryMap.RWDataAddress + 100 + ho := polkavm.RWAddressBase + bo := polkavm.RWAddressBase + 100 bz := uint32(64) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(serviceId), - polkavm.A1: uint64(ho), - polkavm.A2: uint64(bo), - polkavm.A3: uint64(bz), - } + initialRegs[polkavm.A0] = uint64(serviceId) + initialRegs[polkavm.A1] = uint64(ho) + initialRegs[polkavm.A2] = uint64(bo) + initialRegs[polkavm.A3] = uint64(bz) - mem := memoryMap.NewMemory(nil, nil, nil) err = mem.Write(ho, hashData) require.NoError(t, err) @@ -95,7 +97,7 @@ func TestHistoricalLookup(t *testing.T) { return gasCounterOut, regsOut, memOut, x, err } - gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, sa) + gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, sa) require.ErrorIs(t, err, polkavm.ErrHalt) actualValue := make([]byte, len(preimage)) @@ -111,13 +113,20 @@ func TestHistoricalLookup(t *testing.T) { func TestImport(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) segmentData := [common.SizeOfSegment]byte{} @@ -126,18 +135,12 @@ func TestImport(t *testing.T) { } importedSegments := []polkavm.Segment{segmentData} - bo := memoryMap.RWDataAddress + 100 + bo := polkavm.RWAddressBase + 100 bz := uint32(50) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(0), - polkavm.A1: uint64(bo), - polkavm.A2: uint64(bz), - } - - mem := memoryMap.NewMemory(nil, nil, nil) + initialRegs[polkavm.A0] = uint64(0) + initialRegs[polkavm.A1] = uint64(bo) + initialRegs[polkavm.A2] = uint64(bz) hostCall := func(hostCall uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mem polkavm.Memory, x service.ServiceAccount) (polkavm.Gas, polkavm.Registers, polkavm.Memory, service.ServiceAccount, error) { gasCounterOut, regsOut, memOut, _, err := host_call.Import( @@ -151,7 +154,7 @@ func TestImport(t *testing.T) { return gasCounterOut, regsOut, memOut, x, err } - gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) + gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) require.ErrorIs(t, err, polkavm.ErrHalt) actualValue := make([]byte, bz) @@ -172,30 +175,32 @@ func TestImport(t *testing.T) { func TestExport(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) dataToExport := []byte("export_data") - p := memoryMap.RWDataAddress + p := polkavm.RWAddressBase - mem := memoryMap.NewMemory(nil, nil, nil) err = mem.Write(p, dataToExport) require.NoError(t, err) exportOffset := uint64(10) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(p), - polkavm.A1: uint64(len(dataToExport)), - } + initialRegs[polkavm.A0] = uint64(p) + initialRegs[polkavm.A1] = uint64(len(dataToExport)) ctxPair := polkavm.RefineContextPair{ Segments: []polkavm.Segment{}, @@ -215,7 +220,7 @@ func TestExport(t *testing.T) { return gasCounterOut, regsOut, memOut, x, err } - gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) + gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) require.ErrorIs(t, err, polkavm.ErrHalt) // We expect ω7 = ς + |e| = 10 + 1 = 11 @@ -233,31 +238,33 @@ func TestExport(t *testing.T) { func TestMachine(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) dataToMachine := []byte("machine_code") - po := memoryMap.RWDataAddress + po := polkavm.RWAddressBase pz := len(dataToMachine) i := uint64(42) - mem := memoryMap.NewMemory(nil, nil, nil) err = mem.Write(po, dataToMachine) require.NoError(t, err) - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: uint64(po), - polkavm.A1: uint64(pz), - polkavm.A2: i, - } + initialRegs[polkavm.A0] = uint64(po) + initialRegs[polkavm.A1] = uint64(pz) + initialRegs[polkavm.A2] = i ctxPair := polkavm.RefineContextPair{ IntegratedPVMMap: make(map[uint64]polkavm.IntegratedPVM), @@ -276,7 +283,7 @@ func TestMachine(t *testing.T) { return gasCounterOut, regsOut, memOut, x, err } - gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) + gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) require.ErrorIs(t, err, polkavm.ErrHalt) assert.Equal(t, uint64(0), regsOut[polkavm.A0]) @@ -294,24 +301,29 @@ func TestMachine(t *testing.T) { func TestPeek(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) n := uint64(0) - o := memoryMap.RWDataAddress + 100 + o := polkavm.RWAddressBase + 100 z := uint64(1) uData := []byte("data_for_peek") - mem := memoryMap.NewMemory(nil, nil, nil) - - uDataBase := memoryMap.RWDataAddress + uDataBase := polkavm.RWAddressBase require.True(t, uDataBase+uint32(len(uData)) < math.MaxUint32) err = mem.Write(uDataBase, uData) @@ -332,14 +344,10 @@ func TestPeek(t *testing.T) { Segments: []polkavm.Segment{}, } - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: n, - polkavm.A1: uint64(o), - polkavm.A2: s, - polkavm.A3: z, - } + initialRegs[polkavm.A0] = n + initialRegs[polkavm.A1] = uint64(o) + initialRegs[polkavm.A2] = s + initialRegs[polkavm.A3] = z hostCall := func(hc uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mm polkavm.Memory, x service.ServiceAccount) (polkavm.Gas, polkavm.Registers, polkavm.Memory, service.ServiceAccount, error) { gasCounterOut, regsOut, memOut, ctxOut, err := host_call.Peek( @@ -353,7 +361,7 @@ func TestPeek(t *testing.T) { return gasCounterOut, regsOut, memOut, x, err } - gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) + gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) require.ErrorIs(t, err, polkavm.ErrHalt) assert.Equal(t, uint64(host_call.OK), regsOut[polkavm.A0]) @@ -373,22 +381,27 @@ func TestPeek(t *testing.T) { func TestPoke(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) n := uint64(0) - s := uint64(memoryMap.RWDataAddress) + 100 - o := uint64(memoryMap.RWDataAddress) + 200 + s := uint64(polkavm.RWAddressBase) + 100 + o := uint64(polkavm.RWAddressBase) + 200 z := uint64(4) - mem := memoryMap.NewMemory(nil, nil, nil) - sourceData := []byte("data_for_poke") err = mem.Write(uint32(s), sourceData) @@ -407,14 +420,10 @@ func TestPoke(t *testing.T) { Segments: []polkavm.Segment{}, } - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: n, - polkavm.A1: s, - polkavm.A2: o, - polkavm.A3: z, - } + initialRegs[polkavm.A0] = n + initialRegs[polkavm.A1] = s + initialRegs[polkavm.A2] = o + initialRegs[polkavm.A3] = z hostCall := func(hc uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mm polkavm.Memory, x service.ServiceAccount) (polkavm.Gas, polkavm.Registers, polkavm.Memory, service.ServiceAccount, error) { gasCounterOut, regsOut, memOut, ctxOut, err := host_call.Poke( @@ -428,7 +437,7 @@ func TestPoke(t *testing.T) { return gasCounterOut, regsOut, memOut, x, err } - gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(pp, memoryMap, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) + gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall(pp, 0, initialGas, initialRegs, mem, hostCall, service.ServiceAccount{}) require.ErrorIs(t, err, polkavm.ErrHalt) assert.Equal(t, uint64(host_call.OK), regsOut[polkavm.A0]) @@ -446,40 +455,43 @@ func TestPoke(t *testing.T) { func TestZero(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 128*1024, 0, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) + require.NoError(t, err) + innerMem, _, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) n := uint64(0) p := uint64(32) c := uint64(2) // zero out pages #32 & #33 - startAddr := p * uint64(polkavm.VMPageSize) - endAddr := (p + c) * uint64(polkavm.VMPageSize) + startAddr := p * uint64(polkavm.PageSize) + endAddr := (p + c) * uint64(polkavm.PageSize) - mem := memoryMap.NewMemory(nil, nil, nil) for addr := startAddr; addr < endAddr; addr++ { err := mem.Write(uint32(addr), []byte{0xFF}) require.NoError(t, err) } - u := polkavm.IntegratedPVM{Ram: mem} ctxPair := polkavm.RefineContextPair{ - IntegratedPVMMap: map[uint64]polkavm.IntegratedPVM{n: u}, + IntegratedPVMMap: map[uint64]polkavm.IntegratedPVM{n: {Ram: innerMem}}, } - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: n, - polkavm.A1: p, - polkavm.A2: c, - } + initialRegs[polkavm.A0] = n + initialRegs[polkavm.A1] = p + initialRegs[polkavm.A2] = c hostCallFn := func( hc uint32, @@ -501,9 +513,8 @@ func TestZero(t *testing.T) { return gasOut, regsOut, memOut, acc, err } - gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall( + gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall( pp, - memoryMap, 0, initialGas, initialRegs, @@ -512,11 +523,12 @@ func TestZero(t *testing.T) { service.ServiceAccount{}, ) require.ErrorIs(t, err, polkavm.ErrHalt) - assert.Equal(t, uint64(host_call.OK), regsOut[polkavm.A0]) + require.Equal(t, uint64(host_call.OK), regsOut[polkavm.A0]) for addr := startAddr; addr < endAddr; addr++ { b := make([]byte, 1) - err = memOut.Read(uint32(addr), b) + innerPVMRam := ctxPair.IntegratedPVMMap[n].Ram + err = innerPVMRam.Read(uint32(addr), b) require.NoError(t, err) assert.Equal(t, byte(0), b[0]) } @@ -530,38 +542,40 @@ func TestZero(t *testing.T) { func TestVoid(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 100, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 128*1024, 0, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) + require.NoError(t, err) + innerMem, _, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) p := uint64(32) c := uint64(2) - mem := memoryMap.NewMemory(nil, nil, nil) - for pageIndex := p; pageIndex < p+c; pageIndex++ { access := mem.GetAccess(uint32(pageIndex)) assert.Equal(t, polkavm.ReadWrite, access) } n := uint64(0) - u := polkavm.IntegratedPVM{Ram: mem} ctxPair := polkavm.RefineContextPair{ - IntegratedPVMMap: map[uint64]polkavm.IntegratedPVM{n: u}, + IntegratedPVMMap: map[uint64]polkavm.IntegratedPVM{n: {Ram: innerMem}}, } - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: n, - polkavm.A1: p, - polkavm.A2: c, - } + initialRegs[polkavm.A0] = n + initialRegs[polkavm.A1] = p + initialRegs[polkavm.A2] = c hostCall := func(hc uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mm polkavm.Memory, x service.ServiceAccount, @@ -578,9 +592,8 @@ func TestVoid(t *testing.T) { return gasOut, regsOut, memOut, x, err } - gasRemaining, regsOut, memOut, _, err := interpreter.InvokeHostCall( + gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall( pp, - memoryMap, 0, initialGas, initialRegs, @@ -590,10 +603,11 @@ func TestVoid(t *testing.T) { ) require.ErrorIs(t, err, polkavm.ErrHalt) - assert.Equal(t, uint64(host_call.OK), regsOut[polkavm.A0]) + require.Equal(t, uint64(host_call.OK), regsOut[polkavm.A0]) for pageIndex := p; pageIndex < p+c; pageIndex++ { - access := memOut.GetAccess(uint32(pageIndex)) + innerPVMRam := ctxPair.IntegratedPVMMap[n].Ram + access := innerPVMRam.GetAccess(uint32(pageIndex)) assert.Equal(t, polkavm.Inaccessible, access) } @@ -606,17 +620,22 @@ func TestVoid(t *testing.T) { func TestInvoke(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 128*1024, 0, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) - mem := memoryMap.NewMemory(nil, nil, nil) - bb, err := jam.Marshal([14]uint64{ 10000, // gas 0, // regs @@ -635,7 +654,7 @@ func TestInvoke(t *testing.T) { }) require.NoError(t, err) - addr := memoryMap.RWDataAddress + addr := polkavm.RWAddressBase if err := mem.Write(addr, bb); err != nil { t.Fatal(err) } @@ -650,12 +669,8 @@ func TestInvoke(t *testing.T) { }}, } - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: pvmKey, - polkavm.A1: uint64(addr), - } + initialRegs[polkavm.A0] = pvmKey + initialRegs[polkavm.A1] = uint64(addr) hostCall := func(hc uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mm polkavm.Memory, x struct{}, @@ -668,7 +683,6 @@ func TestInvoke(t *testing.T) { gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall( pp, - memoryMap, 0, initialGas, initialRegs, @@ -703,17 +717,22 @@ var addInstrProgram = []byte{0, 0, 3, 190, 135, 9, 1} // copied from the future func TestExpunge(t *testing.T) { pp := &polkavm.Program{ - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RWDataSize: 256, + StackSize: 512, + InitialHeapPages: 10, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 0, Length: 1}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 1, Length: 2}, + }, }, } - memoryMap, err := polkavm.NewMemoryMap(0, 256, 512, 0) + mem, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) - mem := memoryMap.NewMemory(nil, nil, nil) - n, ic := uint64(7), uint32(42) ctxPair := polkavm.RefineContextPair{ @@ -725,16 +744,11 @@ func TestExpunge(t *testing.T) { InstructionCounter: ic, } - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: n, - } + initialRegs[polkavm.A0] = n hostCallFn := func(hc uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mm polkavm.Memory, x service.ServiceAccount, ) (polkavm.Gas, polkavm.Registers, polkavm.Memory, service.ServiceAccount, error) { - gasOut, regsOut, memOut, ctxOut, err := host_call.Expunge( gasCounter, regs, @@ -748,7 +762,6 @@ func TestExpunge(t *testing.T) { gasRemaining, regsOut, _, _, err := interpreter.InvokeHostCall( pp, - memoryMap, 0, 100, initialRegs, diff --git a/internal/polkavm/initialisation.go b/internal/polkavm/initialisation.go new file mode 100644 index 00000000..4480504c --- /dev/null +++ b/internal/polkavm/initialisation.go @@ -0,0 +1,133 @@ +package polkavm + +import ( + "errors" +) + +const ( + AddressSpaceSize = 1 << 32 + DynamicAddressAlignment uint32 = 2 // Z_A = 2: The pvm dynamic address alignment factor. See equation A.15. + InputDataSize = 1 << 24 // Z_I: The standard pvm program initialization input data size (equation A.7) + MemoryZoneSize = 1 << 16 // Z_Z: The standard pvm program initialization zone size (section A.7) + PageSize = 1 << 12 // Z_P: The pvm memory page size (equation 4.25) + MaxPageIndex = AddressSpaceSize / PageSize // p = 2^32 / Z_P = 1 << 20 + AddressReturnToHost = AddressSpaceSize - MemoryZoneSize + StackAddressHigh = AddressSpaceSize - 2*MemoryZoneSize - InputDataSize // 2^32 − 2Z_Z − Z_I + ArgsAddressLow = AddressSpaceSize - MemoryZoneSize - InputDataSize // 2^32 − Z_Z − Z_I + RWAddressBase uint32 = 2 * MemoryZoneSize +) + +var ( + ErrMemoryLayoutOverflowsAddressSpace = errors.New("memory layout overflows address space") +) + +// InitializeStandardProgram (A.7) +func InitializeStandardProgram(program *Program, argsData []byte) (Memory, Registers, error) { + ram, err := InitializeMemory(program.ROData, program.RWData, argsData, program.ProgramMemorySizes.StackSize, program.ProgramMemorySizes.InitialHeapPages) + if err != nil { + return Memory{}, Registers{}, err + } + regs := InitializeRegisters(len(argsData)) + return ram, regs, nil +} + +// InitializeMemory (eq. A.36) +func InitializeMemory(roData, rwData, argsData []byte, stackSize uint32, initialPages uint16) (Memory, error) { + // 5Z_Z + Z(|o|) + Z(|w| + zZ_P) + Z(s) + Z_I ≤ 2^32 (eq. A.35) + if 5*MemoryZoneSize+ + int(alignToZone(uint32(len(rwData))+uint32(initialPages)*PageSize))+ + int(alignToZone(stackSize))+ + InputDataSize > AddressSpaceSize { + return Memory{}, ErrMemoryLayoutOverflowsAddressSpace + } + stackSizeAligned := alignToPage(stackSize) // P(s) + return Memory{ + // if Z_Z ≤ i < Z_Z + |o| + // if Z_Z + |o| ≤ i < Z_Z + P(|o|) + ro: memorySegment{ + address: MemoryZoneSize, // Z_Z + access: ReadOnly, // A: R + data: copySized(roData, alignToPage(uint32(len(roData)))), // V: o_(i−Z_Z) + }, + // if 2Z_Z + Z(|o|) ≤ i < 2Z_Z + Z(|o|) + |w| + // if 2Z_Z + Z(|o|) + |w| ≤ i < 2Z_Z + Z(|o|) + P(|w|) + zZ_P + rw: memorySegment{ + address: 2*MemoryZoneSize + alignToZone(uint32(len(roData))), // 2Z_Z + Z(|o|) + access: ReadWrite, // A: W + data: copySized(rwData, alignToPage(uint32(len(rwData)))+uint32(initialPages)*PageSize), // V: w_(i−(2Z_Z +Z(|o|))) + }, + // if 2^32 − 2Z_Z − Z_I − P(s) ≤ i < 2^32 − 2Z_Z − Z_I + stack: memorySegment{ + address: StackAddressHigh - stackSizeAligned, // 2^32 − 2Z_Z − Z_I − P(s) + access: ReadWrite, + data: make([]byte, stackSizeAligned), + }, + // if 2^32 − Z_Z − Z_I ≤ i < 2^32 − Z_Z − Z_I + |a| + // if 2^32 − Z_Z − Z_I + |a| ≤ i < 2^32 − Z_Z − Z_I + P(|a|) + args: memorySegment{ + address: ArgsAddressLow, + access: ReadOnly, + data: copySized(argsData, alignToPage(uint32(len(argsData)))), + }, + }, nil +} + +func InitializeCustomMemory(roAddr, rwAddr, stackAddr, argsAddr, roSize, rwSize, stackSize, argsSize uint32) Memory { + return Memory{ + ro: memorySegment{ + address: roAddr, + access: ReadOnly, + data: make([]byte, roSize), + }, + rw: memorySegment{ + address: rwAddr, + access: ReadWrite, + data: make([]byte, rwSize), + }, + stack: memorySegment{ + address: stackAddr, + access: ReadWrite, + data: make([]byte, stackSize), + }, + args: memorySegment{ + address: argsAddr, + access: ReadOnly, + data: make([]byte, argsSize), + }, + } +} + +// InitializeRegisters (eq. A.37) +func InitializeRegisters(argsLen int) Registers { + return Registers{ + RA: AddressReturnToHost, // 2^32 − 2^16 if i = 0 + SP: StackAddressHigh, // 2^32 − 2Z_Z − Z_I if i = 1 + A0: ArgsAddressLow, // 2^32 − Z_Z − Z_I if i = 7 + A1: uint64(argsLen), // |a| if i = 8 + // 0 otherwise + } +} + +func copySized(data []byte, size uint32) []byte { + dst := make([]byte, size) + copy(dst, data) + return dst +} + +// alignToPage let P(x ∈ N) ≡ ZP⌈x/Z_P⌉ (eq. A.34) +func alignToPage(value uint32) uint32 { + if value&(PageSize-1) == 0 { + return value + } + + return (value + PageSize) & ^(uint32(PageSize) - 1) +} + +// alignToPage let P(x ∈ N) ≡ ZP⌈x/Z_P⌉ (eq. A.34) +func alignToZone(value uint32) uint32 { + if value&(MemoryZoneSize-1) == 0 { + return value + } + + return (value + MemoryZoneSize) & ^(uint32(MemoryZoneSize) - 1) +} diff --git a/internal/polkavm/interpreter/instance.go b/internal/polkavm/interpreter/instance.go index cddc6c37..64c4aa3b 100644 --- a/internal/polkavm/interpreter/instance.go +++ b/internal/polkavm/interpreter/instance.go @@ -4,16 +4,6 @@ import ( "github.com/eigerco/strawberry/internal/polkavm" ) -// InitRegs Equation 270 v0.4.5: standard program initialization, registers -func InitRegs(args []byte) polkavm.Registers { - regs := polkavm.Registers{} - regs[polkavm.RA] = 1<<32 - 1<<16 - regs[polkavm.SP] = 1<<32 - 2*(1<<16) - 2<<24 - regs[polkavm.A0] = 1<<32 - 1<<16 - 2<<24 - regs[polkavm.A1] = uint64(len(args)) - return regs -} - func Instantiate(instructionOffset uint32, gasLimit polkavm.Gas, regs polkavm.Registers, memory polkavm.Memory) *instance { return &instance{ memory: memory, @@ -25,8 +15,7 @@ func Instantiate(instructionOffset uint32, gasLimit polkavm.Gas, regs polkavm.Re } type instance struct { - memory polkavm.Memory // The memory sequence; a member of the set M (μ) - heapSize uint32 + memory polkavm.Memory // The memory sequence; a member of the set M (μ) regs polkavm.Registers // The registers (ω) instructionOffset uint32 // The instruction counter (ı) instructionLength uint32 @@ -42,9 +31,9 @@ func (i *instance) startBasicBlock(program *polkavm.Program) { i.instructionIndex = compiledOffset } else { i.instructionIndex = len(i.instructions) - for index, instr := range program.Instructions { + for index, instr := range program.CodeAndJumpTable.Instructions { if instr.Offset == i.instructionOffset { - i.addInstructionsForBlock(program.Instructions[index:]) + i.addInstructionsForBlock(program.CodeAndJumpTable.Instructions[index:]) break } } diff --git a/internal/polkavm/interpreter/interpreter_test.go b/internal/polkavm/interpreter/interpreter_test.go index 21525d80..fdb1979d 100644 --- a/internal/polkavm/interpreter/interpreter_test.go +++ b/internal/polkavm/interpreter/interpreter_test.go @@ -1,7 +1,6 @@ package interpreter import ( - "slices" "testing" "github.com/stretchr/testify/assert" @@ -12,58 +11,48 @@ import ( func TestInstance_Execute(t *testing.T) { pp := &polkavm.Program{ - RODataSize: 0, - RWDataSize: 0, - StackSize: 4096, - Instructions: []polkavm.Instruction{ - {Opcode: polkavm.AddImm32, Imm: []uint32{4294967288}, Reg: []polkavm.Reg{polkavm.SP, polkavm.SP}, Offset: 0, Length: 3}, - {Opcode: polkavm.StoreIndirectU32, Imm: []uint32{4}, Reg: []polkavm.Reg{polkavm.RA, polkavm.SP}, Offset: 3, Length: 3}, - {Opcode: polkavm.StoreIndirectU32, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.S0, polkavm.SP}, Offset: 6, Length: 2}, - {Opcode: polkavm.Add32, Reg: []polkavm.Reg{polkavm.S0, polkavm.A1, polkavm.A0}, Offset: 8, Length: 3}, - {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 11, Length: 1}, - {Opcode: polkavm.Add32, Imm: nil, Reg: []polkavm.Reg{polkavm.A0, polkavm.A0, polkavm.S0}, Offset: 12, Length: 3}, - {Opcode: polkavm.LoadIndirectU32, Imm: []uint32{4}, Reg: []polkavm.Reg{polkavm.RA, polkavm.SP}, Offset: 15, Length: 3}, - {Opcode: polkavm.LoadIndirectU32, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.S0, polkavm.SP}, Offset: 18, Length: 2}, - {Opcode: polkavm.AddImm32, Imm: []uint32{8}, Reg: []polkavm.Reg{polkavm.SP, polkavm.SP}, Offset: 20, Length: 3}, - {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 23, Length: 2}, + ProgramMemorySizes: polkavm.ProgramMemorySizes{ + RODataSize: 0, + RWDataSize: 0, + StackSize: 4096, + }, + CodeAndJumpTable: polkavm.CodeAndJumpTable{ + Instructions: []polkavm.Instruction{ + {Opcode: polkavm.AddImm32, Imm: []uint32{4294967288}, Reg: []polkavm.Reg{polkavm.SP, polkavm.SP}, Offset: 0, Length: 3}, + {Opcode: polkavm.StoreIndirectU32, Imm: []uint32{4}, Reg: []polkavm.Reg{polkavm.RA, polkavm.SP}, Offset: 3, Length: 3}, + {Opcode: polkavm.StoreIndirectU32, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.S0, polkavm.SP}, Offset: 6, Length: 2}, + {Opcode: polkavm.Add32, Reg: []polkavm.Reg{polkavm.S0, polkavm.A1, polkavm.A0}, Offset: 8, Length: 3}, + {Opcode: polkavm.Ecalli, Imm: []uint32{0}, Offset: 11, Length: 1}, + {Opcode: polkavm.Add32, Imm: nil, Reg: []polkavm.Reg{polkavm.A0, polkavm.A0, polkavm.S0}, Offset: 12, Length: 3}, + {Opcode: polkavm.LoadIndirectU32, Imm: []uint32{4}, Reg: []polkavm.Reg{polkavm.RA, polkavm.SP}, Offset: 15, Length: 3}, + {Opcode: polkavm.LoadIndirectU32, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.S0, polkavm.SP}, Offset: 18, Length: 2}, + {Opcode: polkavm.AddImm32, Imm: []uint32{8}, Reg: []polkavm.Reg{polkavm.SP, polkavm.SP}, Offset: 20, Length: 3}, + {Opcode: polkavm.JumpIndirect, Imm: []uint32{0}, Reg: []polkavm.Reg{polkavm.RA}, Offset: 23, Length: 2}, + }, }, - Imports: []string{"get_third_number"}, - Exports: []polkavm.ProgramExport{{TargetCodeOffset: 0, Symbol: "add_numbers"}}, } - memoryMap, err := polkavm.NewMemoryMap(pp.RODataSize, pp.RWDataSize, pp.StackSize, 0) + memory, initialRegs, err := polkavm.InitializeStandardProgram(pp, nil) require.NoError(t, err) - memory := memoryMap.NewMemory(pp.RWData, pp.ROData, nil) - - // find add numbers export - i := slices.IndexFunc(pp.Exports, func(e polkavm.ProgramExport) bool { return e.Symbol == "add_numbers" }) - require.True(t, i >= 0) - entryPoint := pp.Exports[i].TargetCodeOffset - initialRegs := polkavm.Registers{ - polkavm.RA: polkavm.VmAddressReturnToHost, - polkavm.SP: uint64(memoryMap.StackAddressHigh), - polkavm.A0: 1, - polkavm.A1: 10, - } + initialRegs[polkavm.A0] = 1 + initialRegs[polkavm.A1] = 10 hostCall := func(hostCall uint32, gasCounter polkavm.Gas, regs polkavm.Registers, mem polkavm.Memory, x nothing) (polkavm.Gas, polkavm.Registers, polkavm.Memory, nothing, error) { - if pp.Imports[hostCall] == "get_third_number" { - regs1 := getThirdNumber(regs) - return gasCounter, regs1, mem, struct{}{}, nil - } - return gasCounter, regs, mem, struct{}{}, nil + assert.Equal(t, uint32(0), hostCall) + regs1 := getThirdNumber(regs) + return gasCounter, regs1, mem, struct{}{}, nil } t.Run("1 + 10 + 100 = 111", func(t *testing.T) { gasLimit := uint64(1000) - gas, regs, _, _, err := InvokeHostCall(pp, memoryMap, entryPoint, gasLimit, initialRegs, memory, hostCall, nothing{}) + gas, regs, _, _, err := InvokeHostCall(pp, 0, gasLimit, initialRegs, memory, hostCall, nothing{}) require.ErrorIs(t, err, polkavm.ErrHalt) assert.Equal(t, uint64(111), regs[polkavm.A0]) - assert.Equal(t, polkavm.Gas(gasLimit)-polkavm.Gas(len(pp.Instructions)), gas) + assert.Equal(t, polkavm.Gas(gasLimit)-polkavm.Gas(len(pp.CodeAndJumpTable.Instructions)), gas) }) t.Run("not enough gas", func(t *testing.T) { - _, _, _, _, err := InvokeHostCall(pp, memoryMap, entryPoint, 9, initialRegs, memory, hostCall, nothing{}) + _, _, _, _, err := InvokeHostCall(pp, 0, 9, initialRegs, memory, hostCall, nothing{}) require.ErrorIs(t, err, polkavm.ErrOutOfGas) }) } diff --git a/internal/polkavm/interpreter/mutator.go b/internal/polkavm/interpreter/mutator.go index cfdd15ba..2ec0c54f 100644 --- a/internal/polkavm/interpreter/mutator.go +++ b/internal/polkavm/interpreter/mutator.go @@ -12,19 +12,17 @@ import ( var _ polkavm.Mutator = &Mutator{} -func NewMutator(i *instance, program *polkavm.Program, memoryMap *polkavm.MemoryMap) *Mutator { +func NewMutator(i *instance, program *polkavm.Program) *Mutator { v := &Mutator{ - instance: i, - memoryMap: memoryMap, - program: program, + instance: i, + program: program, } return v } type Mutator struct { - instance *instance - program *polkavm.Program - memoryMap *polkavm.MemoryMap + instance *instance + program *polkavm.Program } func (m *Mutator) branch(condition bool, target uint32) { @@ -83,9 +81,9 @@ func store[T number](m *Mutator, src T, base polkavm.Reg, offset uint32) error { return nil } -// djump Equation 249 v0.4.5 +// djump (eq. A.14) func (m *Mutator) djump(target uint32) error { - if target == polkavm.VmAddressReturnToHost { + if target == polkavm.AddressReturnToHost { return polkavm.ErrHalt } instructionOffset := m.program.JumpTableGetByAddress(target) @@ -130,23 +128,10 @@ func (m *Mutator) Fallthrough() { } func (m *Mutator) Sbrk(dst polkavm.Reg, sizeReg polkavm.Reg) error { size := m.get32(sizeReg) - if size == 0 { - // The guest wants to know the current heap pointer. - m.setNext32(dst, m.instance.heapSize) - return nil - } - - newHeapSize := m.instance.heapSize + size - if newHeapSize > m.memoryMap.MaxHeapSize { - return polkavm.ErrPanicf("max heap size exceeded") - } - - m.instance.heapSize = newHeapSize - heapTop := m.memoryMap.HeapBase + newHeapSize - if err := m.instance.memory.Sbrk(heapTop); err != nil { - return polkavm.ErrPanicf(err.Error()) + heapTop, err := m.instance.memory.Sbrk(size) + if err != nil { + return err } - m.setNext32(dst, heapTop) return nil } diff --git a/internal/polkavm/interpreter/utils.go b/internal/polkavm/interpreter/utils.go index 9eff5674..0c1c58e2 100644 --- a/internal/polkavm/interpreter/utils.go +++ b/internal/polkavm/interpreter/utils.go @@ -1,7 +1,6 @@ package interpreter import ( - "bytes" "errors" "slices" @@ -16,16 +15,15 @@ import ( // - ErrPanic (☇) // - ErrPageFault (F) func InvokeWholeProgram[X any](p []byte, entryPoint uint32, gas uint64, args []byte, hostFunc polkavm.HostCall[X], x X) (polkavm.Gas, []byte, X, error) { - program, err := polkavm.ParseBlob(polkavm.NewReader(bytes.NewReader(p))) + program, err := polkavm.ParseBlob(p) if err != nil { return 0, nil, x, polkavm.ErrPanicf(err.Error()) } - memMap, err := polkavm.NewMemoryMap(program.RODataSize, program.RWDataSize, program.StackSize, uint32(len(args))) + ram, regs, err := polkavm.InitializeStandardProgram(program, args) if err != nil { return 0, nil, x, polkavm.ErrPanicf(err.Error()) } - memory := memMap.NewMemory(program.RWData, program.ROData, args) - gasRemaining, regs, memory1, x1, err := InvokeHostCall(program, memMap, entryPoint, gas, InitRegs(args), memory, hostFunc, x) + gasRemaining, regs, memory1, x1, err := InvokeHostCall(program, entryPoint, gas, regs, ram, hostFunc, x) if err != nil { return 0, nil, x, err } @@ -40,7 +38,7 @@ func InvokeWholeProgram[X any](p []byte, entryPoint uint32, gas uint64, args []b // InvokeHostCall host call invocation (ΨH) func InvokeHostCall[X any]( - program *polkavm.Program, memMap *polkavm.MemoryMap, + program *polkavm.Program, instructionCounter uint32, initialGas uint64, regs polkavm.Registers, mem polkavm.Memory, hostCall polkavm.HostCall[X], x X, ) (polkavm.Gas, polkavm.Registers, polkavm.Memory, X, error) { @@ -50,19 +48,19 @@ func InvokeHostCall[X any]( gas = polkavm.Gas(initialGas) ) for { - instructionCounter, gas, regs, mem, hostCallIndex, err = Invoke(program, memMap, instructionCounter, gas, regs, mem) + instructionCounter, gas, regs, mem, hostCallIndex, err = Invoke(program, instructionCounter, gas, regs, mem) if err != nil && errors.Is(err, polkavm.ErrHostCall) { gas, regs, mem, x, err = hostCall(hostCallIndex, gas, regs, mem, x) if err != nil { return gas, regs, mem, x, err } - index := slices.IndexFunc(program.Instructions, func(i polkavm.Instruction) bool { + index := slices.IndexFunc(program.CodeAndJumpTable.Instructions, func(i polkavm.Instruction) bool { return i.Offset == instructionCounter }) if index < 0 { return gas, regs, mem, x, polkavm.ErrPanicf("no instructions for offset") } - instructionCounter += program.Instructions[index].Length + instructionCounter += program.CodeAndJumpTable.Instructions[index].Length continue } @@ -74,11 +72,11 @@ func InvokeHostCall[X any]( // Invoke basic definition (Ψ) func Invoke( - program *polkavm.Program, memMap *polkavm.MemoryMap, + program *polkavm.Program, instructionCounter uint32, gas polkavm.Gas, regs polkavm.Registers, mem polkavm.Memory, ) (uint32, polkavm.Gas, polkavm.Registers, polkavm.Memory, uint32, error) { i := Instantiate(instructionCounter, gas, regs, mem) - m := NewMutator(i, program, memMap) + m := NewMutator(i, program) m.instance.startBasicBlock(m.program) for { // single-step invocation (Ψ1) diff --git a/internal/polkavm/program.go b/internal/polkavm/program.go index ffa32cec..dae5fde0 100644 --- a/internal/polkavm/program.go +++ b/internal/polkavm/program.go @@ -4,273 +4,85 @@ import ( "bytes" "encoding/binary" "fmt" - "github.com/eigerco/strawberry/pkg/serialization/codec/jam" "io" "log" "math/bits" -) -// BlobMagic The magic bytes with which every program blob must start with. -var BlobMagic = [4]byte{'P', 'V', 'M', 0} - -// program blob sections -const ( - SectionMemoryConfig byte = 1 - SectionROData byte = 2 - SectionRWData byte = 3 - SectionImports byte = 4 - SectionExports byte = 5 - SectionCodeAndJumpTable byte = 6 - SectionOptDebugStrings byte = 128 - SectionOptDebugLinePrograms byte = 129 - SectionOptDebugLineProgramRanges byte = 130 - SectionEndOfFile byte = 0 - - BlobLenSize = 8 // for 64 bit blobs - BlobVersionV1x32 byte = 0 - BlobVersionV1x64 byte = 1 - - VersionDebugLineProgramV1 byte = 1 - - VmMaximumJumpTableEntries uint32 = 16 * 1024 * 1024 - VmMaximumImportCount uint32 = 1024 // The maximum number of functions the program can import. - VmMaximumCodeSize uint32 = 32 * 1024 * 1024 - VmCodeAddressAlignment uint32 = 2 - BitmaskMax = 24 + "github.com/eigerco/strawberry/pkg/serialization/codec/jam" ) +const BitmaskMax = 24 + +type ProgramMemorySizes struct { + RODataSize uint32 `jam:"length=3"` + RWDataSize uint32 `jam:"length=3"` + InitialHeapPages uint16 `jam:"length=2"` + StackSize uint32 `jam:"length=3"` +} +type CodeAndJumpTable struct { + JumpTable []uint32 + Instructions []Instruction +} + type Program struct { - Is64Bit bool - RODataSize uint32 - RWDataSize uint32 - StackSize uint32 - ROData []byte - RWData []byte - JumpTable []uint32 - Instructions []Instruction - Imports []string - Exports []ProgramExport - DebugStrings []byte - DebugLineProgramRanges []byte - DebugLinePrograms []byte + ProgramMemorySizes ProgramMemorySizes + ROData []byte + RWData []byte + CodeAndJumpTable CodeAndJumpTable } func (p *Program) JumpTableGetByAddress(address uint32) *uint32 { - if address&(VmCodeAddressAlignment-1) != 0 || address == 0 { + if address&(DynamicAddressAlignment-1) != 0 || address == 0 { return nil } - instructionOffset := p.JumpTable[((address - VmCodeAddressAlignment) / VmCodeAddressAlignment)] + instructionOffset := p.CodeAndJumpTable.JumpTable[((address - DynamicAddressAlignment) / DynamicAddressAlignment)] return &instructionOffset } -type ProgramExport struct { - TargetCodeOffset uint32 - Symbol string -} - -func ParseBlob(r *Reader) (pp *Program, err error) { - magic := make([]byte, len(BlobMagic)) - if _, err = r.Read(magic); err != nil { +// ParseBlob let E3(|o|) ⌢ E3(|w|) ⌢ E2(z) ⌢ E3(s) ⌢ o ⌢ w ⌢ E4(|c|) ⌢ c = p (eq. A.32) +func ParseBlob(data []byte) (*Program, error) { + memorySizes := ProgramMemorySizes{} + if err := jam.Unmarshal(data[:11], &memorySizes); err != nil { return nil, err } - if !bytes.Equal(magic, BlobMagic[:]) { - return pp, fmt.Errorf("blob doesn't start with the expected magic bytes") - } - blobVersion, err := r.ReadByte() - if err != nil { + program := &Program{ProgramMemorySizes: memorySizes} + if err := jam.Unmarshal(data[11:memorySizes.RODataSize], program.ROData); err != nil { return nil, err } - if blobVersion != BlobVersionV1x32 && blobVersion != BlobVersionV1x64 { - return pp, fmt.Errorf("unsupported version: %d", blobVersion) + if int(memorySizes.RODataSize) != len(program.ROData) { + return nil, fmt.Errorf("ro data size mismatch") } - - blobLenBytes := make([]byte, 8) - _, err = r.Read(blobLenBytes) - if err != nil { - return nil, fmt.Errorf("failed to read blob length: %w", err) + if int(memorySizes.RWDataSize) != len(program.RWData) { + return nil, fmt.Errorf("rw data size mismatch") } - blobLen := binary.LittleEndian.Uint64(blobLenBytes) - - if blobLen != uint64(r.Len()) { - return pp, fmt.Errorf("blob size doesn't match the blob length metadata") - } - pp = &Program{Is64Bit: blobVersion == BlobVersionV1x64} - section, err := r.ReadByte() - if err != nil { + if err := jam.Unmarshal(data[memorySizes.RODataSize:memorySizes.RWDataSize], program.RWData); err != nil { return nil, err } - if section == SectionMemoryConfig { - if section, err = parseMemoryConfig(r, pp); err != nil { - return nil, err - } - } - if section == SectionROData { - if pp.ROData, err = r.ReadWithLength(); err != nil { - return nil, err - } - if section, err = r.ReadByte(); err != nil { - return nil, err - } - } - if section == SectionRWData { - if pp.RWData, err = r.ReadWithLength(); err != nil { - return nil, err - } - if section, err = r.ReadByte(); err != nil { - return nil, err - } - } - if section == SectionImports { - if section, err = parseImports(r, pp); err != nil { - return nil, err - } - } - - if section == SectionExports { - if section, err = parseExports(r, pp); err != nil { - return nil, err - } - } - if section == SectionCodeAndJumpTable { - secLen, err := r.ReadVarint() - if err != nil { - return nil, err - } - if err = ParseCodeAndJumpTable(secLen, r, pp); err != nil { - return nil, err - } - if section, err = r.ReadByte(); err != nil { - return nil, err - } - } - if section == SectionOptDebugStrings { - if pp.DebugStrings, err = r.ReadWithLength(); err != nil { - return nil, err - } - if section, err = r.ReadByte(); err != nil { - return nil, err - } - } - if section == SectionOptDebugLinePrograms { - if pp.DebugLinePrograms, err = r.ReadWithLength(); err != nil { - return nil, err - } - if section, err = r.ReadByte(); err != nil { - return nil, err - } - } - if section == SectionOptDebugLineProgramRanges { - if pp.DebugLineProgramRanges, err = r.ReadWithLength(); err != nil { - return nil, err - } - if section, err = r.ReadByte(); err != nil { - return nil, err - } - } - - for (section & 0b10000000) != 0 { - // We don't know this section, but it's optional, so just skip it. - log.Printf("Skipping unsupported optional section: %v", section) - sectionLength, err := r.ReadVarint() - if err != nil { - return nil, err - } - discardBytes := make([]byte, sectionLength) - _, err = r.Read(discardBytes) - if err != nil { - return nil, err - } - section, err = r.ReadByte() - if err != nil { - return nil, err - } - } - if section != SectionEndOfFile { - return nil, fmt.Errorf("unexpected section: %v", section) - } - return pp, nil -} - -func parseMemoryConfig(r *Reader, p *Program) (byte, error) { - secLen, err := r.ReadVarint() - if err != nil { - return 0, err - } - pos := r.Position() - - if p.RODataSize, err = r.ReadVarint(); err != nil { - return 0, err - } - if p.RWDataSize, err = r.ReadVarint(); err != nil { - return 0, err - } - if p.StackSize, err = r.ReadVarint(); err != nil { - return 0, err - } - if pos+int64(secLen) != r.Position() { - return 0, fmt.Errorf("the memory config section contains more data than expected %v %v", pos+int64(secLen), r.Position()) - } - - return r.ReadByte() -} - -func parseImports(r *Reader, p *Program) (byte, error) { - secLen, err := r.ReadVarint() - if err != nil { - return 0, err - } - posStart := r.Position() - importCount, err := r.ReadVarint() - if err != nil { - return 0, err - } - if importCount > VmMaximumImportCount { - return 0, fmt.Errorf("too many imports") + var codeSize uint32 + if err := jam.Unmarshal(data[memorySizes.RWDataSize:memorySizes.RWDataSize+4], &codeSize); err != nil { + return nil, err } - //TODO check for underflow and overflow? - importOffsetsSize := importCount * 4 - importOffsets := make([]byte, importOffsetsSize) - _, err = r.Read(importOffsets) - if err != nil { - return 0, err + if len(data[memorySizes.RWDataSize+4:]) != int(codeSize) { + return nil, fmt.Errorf("code size mismatch") } - //TODO check for underflow? - importSymbolsSize := secLen - uint32(r.Position()-posStart) - importSymbols := make([]byte, importSymbolsSize) - _, err = r.Read(importSymbols) - if err != nil { - return 0, err - } - - if len(importOffsets)%4 != 0 { - return 0, fmt.Errorf("invalid import offsets data: %d", len(importOffsets)) - } - var offsets []uint32 - for i := 0; i < len(importOffsets); i += 4 { - offsets = append(offsets, binary.BigEndian.Uint32(importOffsets[i:i+4])) - } - for i := 0; i < len(offsets); i += 2 { - if i+1 == len(offsets) { - p.Imports = append(p.Imports, string(importSymbols[offsets[i]:])) - continue - } - p.Imports = append(p.Imports, string(importSymbols[offsets[i]:offsets[i+1]])) + if err := ParseCodeAndJumpTable( + codeSize, + NewReader(bytes.NewReader(data[memorySizes.RWDataSize+4:])), + &program.CodeAndJumpTable); err != nil { + return nil, err } - - return r.ReadByte() + return program, nil } -func ParseCodeAndJumpTable(secLen uint32, r *Reader, p *Program) error { +// ParseCodeAndJumpTable p = E(|j|) ⌢ E1(z) ⌢ E(|c|) ⌢ E_z(j) ⌢ E(c) ⌢ E(k), |k| = |c| (part of eq. A.1) +func ParseCodeAndJumpTable(secLen uint32, r *Reader, codeAndJumpTable *CodeAndJumpTable) error { initialPosition := r.Position() jumpTableEntryCount, err := r.ReadVarint() if err != nil { return err } - if jumpTableEntryCount > VmMaximumJumpTableEntries { - return fmt.Errorf("the jump table section is too long") - } jumpTableEntrySize, err := r.ReadByte() if err != nil { return err @@ -279,9 +91,6 @@ func ParseCodeAndJumpTable(secLen uint32, r *Reader, p *Program) error { if err != nil { return err } - if codeLength > VmMaximumCodeSize { - return fmt.Errorf("the code section is too long") - } if jumpTableEntrySize > 4 { return fmt.Errorf("invalid jump table entry size") } @@ -296,15 +105,15 @@ func ParseCodeAndJumpTable(secLen uint32, r *Reader, p *Program) error { for i := 0; i < len(jumpTable); i += int(jumpTableEntrySize) { switch jumpTableEntrySize { case 1: - p.JumpTable = append(p.JumpTable, uint32(jumpTable[i])) + codeAndJumpTable.JumpTable = append(codeAndJumpTable.JumpTable, uint32(jumpTable[i])) case 2: - p.JumpTable = append(p.JumpTable, uint32(binary.BigEndian.Uint16(jumpTable[i:i+2]))) + codeAndJumpTable.JumpTable = append(codeAndJumpTable.JumpTable, uint32(binary.BigEndian.Uint16(jumpTable[i:i+2]))) case 3: - p.JumpTable = append(p.JumpTable, binary.BigEndian.Uint32( + codeAndJumpTable.JumpTable = append(codeAndJumpTable.JumpTable, binary.BigEndian.Uint32( []byte{jumpTable[i], jumpTable[i+1], jumpTable[i+2], 0}, )) case 4: - p.JumpTable = append(p.JumpTable, binary.BigEndian.Uint32(jumpTable[i:i+int(jumpTableEntrySize)])) + codeAndJumpTable.JumpTable = append(codeAndJumpTable.JumpTable, binary.BigEndian.Uint32(jumpTable[i:i+int(jumpTableEntrySize)])) default: panic("unreachable") } @@ -335,7 +144,7 @@ func ParseCodeAndJumpTable(secLen uint32, r *Reader, p *Program) error { if err != nil { return err } - p.Instructions = append(p.Instructions, instr) + codeAndJumpTable.Instructions = append(codeAndJumpTable.Instructions, instr) offset = nextOffset } return nil @@ -413,39 +222,6 @@ func parseBitmask(bitmask []byte, offset int) (int, int) { return offset, argsLength } -func parseExports(r *Reader, p *Program) (byte, error) { - var secLen uint32 - secLen, err := r.ReadVarint() - if err != nil { - return 0, err - } - initialPosition := r.Position() - nr, err := r.ReadVarint() - if err != nil { - return 0, err - } - for i := 0; i < int(nr); i++ { - targetCodeOffset, err := r.ReadVarint() - if err != nil { - return 0, err - } - symbol, err := r.ReadWithLength() - if err != nil { - return 0, err - } - - p.Exports = append(p.Exports, ProgramExport{ - TargetCodeOffset: targetCodeOffset, - Symbol: string(symbol), - }) - } - - if initialPosition+int64(secLen) != r.Position() { - return 0, fmt.Errorf("invalid exports section Length: %v", secLen) - } - return r.ReadByte() -} - func NewReader(r io.ReadSeeker) *Reader { return &Reader{r} } type Reader struct{ io.ReadSeeker } diff --git a/internal/polkavm/program_test.go b/internal/polkavm/program_test.go deleted file mode 100644 index 64d49bb9..00000000 --- a/internal/polkavm/program_test.go +++ /dev/null @@ -1,132 +0,0 @@ -//go:build integration - -package polkavm - -import ( - "embed" - "io" - "testing" - - "github.com/stretchr/testify/assert" -) - -//go:embed testdata -var fs embed.FS - -func Test_ParseBlob(t *testing.T) { - f, err := fs.Open("testdata/example-hello-world.polkavm") - if err != nil { - t.Fatal(err) - } - defer f.Close() - pp, err := ParseBlob(NewReader(f.(io.ReadSeeker))) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, uint32(0), pp.RODataSize) - assert.Equal(t, uint32(0), pp.RWDataSize) - assert.Equal(t, uint32(4096), pp.StackSize) - if assert.Equal(t, 10, len(pp.Instructions)) { - assert.Equal(t, Instruction{ - Opcode: AddImm32, - Imm: []uint32{4294967288}, - Reg: []Reg{SP, SP}, - Offset: 0, Length: 3, - }, pp.Instructions[0]) - - assert.Equal(t, Instruction{ - Opcode: StoreIndirectU32, - Imm: []uint32{4}, - Reg: []Reg{RA, SP}, - Offset: 3, - Length: 3, - }, pp.Instructions[1]) - assert.Equal(t, Instruction{ - Opcode: StoreIndirectU32, - Imm: []uint32{0}, - Reg: []Reg{S0, SP}, - Offset: 6, - Length: 2, - }, pp.Instructions[2]) - assert.Equal(t, Instruction{ - Opcode: Add32, - Reg: []Reg{S0, A1, A0}, - Offset: 8, - Length: 3, - }, pp.Instructions[3]) - assert.Equal(t, Instruction{ - Opcode: Ecalli, - Imm: []uint32{0}, - Offset: 11, - Length: 1, - }, pp.Instructions[4]) - assert.Equal(t, Instruction{ - Opcode: Add32, - Imm: nil, - Reg: []Reg{A0, A0, S0}, - Offset: 12, - Length: 3, - }, pp.Instructions[5]) - assert.Equal(t, Instruction{ - Opcode: LoadIndirectU32, - Imm: []uint32{4}, - Reg: []Reg{RA, SP}, - Offset: 15, - Length: 3, - }, pp.Instructions[6]) - assert.Equal(t, Instruction{ - Opcode: LoadIndirectU32, - Imm: []uint32{0}, - Reg: []Reg{S0, SP}, - Offset: 18, - Length: 2, - }, pp.Instructions[7]) - assert.Equal(t, Instruction{ - Opcode: AddImm32, - Imm: []uint32{8}, - Reg: []Reg{SP, SP}, - Offset: 20, - Length: 3, - }, pp.Instructions[8]) - assert.Equal(t, Instruction{ - Opcode: JumpIndirect, - Imm: []uint32{0}, - Reg: []Reg{RA}, - Offset: 23, - Length: 2, - }, pp.Instructions[9]) - } - assert.Equal(t, []string{"get_third_number"}, pp.Imports) - assert.Equal(t, []ProgramExport{{0, "add_numbers"}}, pp.Exports) -} - -func Test_parseBitmaskFast(t *testing.T) { - table := []struct { - bitmask []byte - offset, nextOffset, skip int - }{ - {[]byte{0b00000011, 0, 0, 0}, 0, 1, 0}, - {[]byte{0b00000101, 0, 0, 0}, 0, 2, 1}, - {[]byte{0b10000001, 0, 0, 0}, 0, 7, 6}, - {[]byte{0b00000001, 1, 0, 0}, 0, 8, 7}, - {[]byte{0b00000001, 1 << 7, 0, 0}, 0, 15, 14}, - {[]byte{0b00000001, 0, 1, 0}, 0, 16, 15}, - {[]byte{0b00000001, 0, 1 << 7, 0}, 0, 23, 22}, - {[]byte{0b00000001, 0, 0, 1}, 0, 24, 23}, - - {[]byte{0b11000000, 0, 0, 0, 0}, 6, 7, 0}, - {[]byte{0b01000000, 1, 0, 0, 0}, 6, 8, 1}, - - {[]byte{0b10000000, 1, 0, 0, 0}, 7, 8, 0}, - {[]byte{0b10000000, 1 << 1, 0, 0, 0}, 7, 9, 1}, - - {[]byte{0, 0, 0, 0, 0b00000001}, 0, 25, 24}, - {[]byte{0, 0, 0, 0, 0b00000001}, 6, 31, 24}, - {[]byte{0, 0, 0, 0, 0b00000001}, 7, 32, 24}, - } - for i, tc := range table { - nextOffset, skip := parseBitmask(tc.bitmask, tc.offset) - assert.Equal(t, tc.nextOffset, nextOffset, "index: %d", i) - assert.Equal(t, tc.skip, skip, "index: %d", i) - } -} diff --git a/internal/polkavm/testdata/example-hello-world.polkavm b/internal/polkavm/testdata/example-hello-world.polkavm deleted file mode 100644 index 34544e0baaf1c7c6fff2b463c11470933f48ad68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmWFt^JQR+Wq<%i76yh13@oCIAZ~hUNqk90W>HFfUTJPpY7r|RBLjD03XEIKrog}; j$t3uLS%8IEl%s;xk0YF&QGkU}lu3|7n89=A6b1$WKUNXP diff --git a/tests/integration/pvm_integration_test.go b/tests/integration/pvm_integration_test.go index 9b211792..fc0ccd02 100644 --- a/tests/integration/pvm_integration_test.go +++ b/tests/integration/pvm_integration_test.go @@ -65,13 +65,12 @@ func Test_Vectors(t *testing.T) { t.Fatal(file.Name(), err) } - pp := &polkavm.Program{} - if err := polkavm.ParseCodeAndJumpTable(uint32(len(tc.Program)), polkavm.NewReader(bytes.NewReader(tc.Program)), pp); err != nil { + pp := polkavm.CodeAndJumpTable{} + if err := polkavm.ParseCodeAndJumpTable(uint32(len(tc.Program)), polkavm.NewReader(bytes.NewReader(tc.Program)), &pp); err != nil { t.Fatal(err) } - mm := getMemoryMap(tc.InitialPageMap) - mem := mm.NewMemory(nil, nil, nil) + mem := getMemoryMap(tc.InitialPageMap) for _, initialMem := range tc.InitialMemory { err := mem.Write(initialMem.Address, initialMem.Contents) @@ -79,7 +78,7 @@ func Test_Vectors(t *testing.T) { t.Fatal(err) } } - instructionCounter, gas, regs, mem, _, err := interpreter.Invoke(pp, mm, tc.InitialPc, tc.InitialGas, tc.InitialRegs, mem) + instructionCounter, gas, regs, mem, _, err := interpreter.Invoke(&polkavm.Program{CodeAndJumpTable: pp}, tc.InitialPc, tc.InitialGas, tc.InitialRegs, mem) assert.Equal(t, int(tc.ExpectedPc), int(instructionCounter)) for i := range regs { assert.Equal(t, uint32(tc.ExpectedRegs[i]), uint32(regs[i])) // TODO temp fix @@ -98,21 +97,21 @@ func Test_Vectors(t *testing.T) { } } -func getMemoryMap(pageMap []Page) *polkavm.MemoryMap { - mm := &polkavm.MemoryMap{ArgsDataAddress: 1<<32 - 1} +func getMemoryMap(pageMap []Page) polkavm.Memory { + var roAddr, rwAddr, stackAddr, roSize, rwSize, stackSize uint32 for _, page := range pageMap { if !page.IsWritable { - mm.RODataAddress = page.Address - mm.RODataSize = page.Length - } else if page.IsWritable && mm.StackAddressLow == 0 { - mm.StackAddressLow = page.Address - mm.StackSize = page.Length + roAddr = page.Address + roSize = page.Length + } else if page.IsWritable && stackAddr == 0 { + stackAddr = page.Address + stackSize = page.Length } else { - mm.RWDataAddress = page.Address - mm.RWDataSize = page.Length + rwAddr = page.Address + rwSize = page.Length } } - return mm + return polkavm.InitializeCustomMemory(roAddr, rwAddr, stackAddr, 1<<32-1, roSize, rwSize, stackSize, 0) } func error2status(err error) string {