From 56d430e411ff227b3ef18713c9a4e1783e9d3ee5 Mon Sep 17 00:00:00 2001 From: jmjac <23382625+jmjac@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:36:23 +0000 Subject: [PATCH] Remove memory manager (#109) * Remove memory manager * Add test for segmentsOffsets and clean up * Remove memory field * Fix linter errors * refactor --- pkg/hintrunner/hint_test.go | 10 +- pkg/hintrunner/hintrunner_test.go | 4 +- pkg/hintrunner/operand_test.go | 14 +- pkg/hintrunner/testutils.go | 12 +- pkg/runners/zero/zero.go | 63 +++---- pkg/runners/zero/zero_test.go | 6 +- pkg/vm/memory/memory.go | 18 ++ pkg/vm/memory/memory_manager.go | 61 ------- pkg/vm/memory/memory_manager_test.go | 150 ---------------- pkg/vm/memory/memory_test.go | 34 ++++ pkg/vm/vm.go | 30 ++++ pkg/vm/vm_test.go | 247 +++++++++++++++++++++------ 12 files changed, 328 insertions(+), 321 deletions(-) delete mode 100644 pkg/vm/memory/memory_manager.go delete mode 100644 pkg/vm/memory/memory_manager_test.go diff --git a/pkg/hintrunner/hint_test.go b/pkg/hintrunner/hint_test.go index 7da34a905..521832619 100644 --- a/pkg/hintrunner/hint_test.go +++ b/pkg/hintrunner/hint_test.go @@ -11,7 +11,7 @@ import ( ) func TestAllocSegment(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 3 vm.Context.Fp = 0 @@ -42,7 +42,7 @@ func TestAllocSegment(t *testing.T) { } func TestTestLessThanTrue(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 0 vm.Context.Fp = 0 writeTo(vm, VM.ExecutionSegment, 0, memory.MemoryValueFromInt(23)) @@ -79,7 +79,7 @@ func TestTestLessThanFalse(t *testing.T) { for _, tc := range testCases { t.Run(tc.expectedMsg, func(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 0 vm.Context.Fp = 0 writeTo(vm, VM.ExecutionSegment, 0, memory.MemoryValueFromInt(17)) @@ -118,7 +118,7 @@ func TestTestLessThanOrEqTrue(t *testing.T) { for _, tc := range testCases { t.Run(tc.expectedMsg, func(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 0 vm.Context.Fp = 0 writeTo(vm, VM.ExecutionSegment, 0, memory.MemoryValueFromInt(23)) @@ -147,7 +147,7 @@ func TestTestLessThanOrEqTrue(t *testing.T) { } func TestTestLessThanOrEqFalse(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 0 vm.Context.Fp = 0 writeTo(vm, VM.ExecutionSegment, 0, memory.MemoryValueFromInt(17)) diff --git a/pkg/hintrunner/hintrunner_test.go b/pkg/hintrunner/hintrunner_test.go index 179f88e41..f71ab7698 100644 --- a/pkg/hintrunner/hintrunner_test.go +++ b/pkg/hintrunner/hintrunner_test.go @@ -9,7 +9,7 @@ import ( ) func TestExistingHint(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 3 var ap ApCellRef = 5 @@ -33,7 +33,7 @@ func TestExistingHint(t *testing.T) { } func TestNoHint(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 3 var ap ApCellRef = 5 diff --git a/pkg/hintrunner/operand_test.go b/pkg/hintrunner/operand_test.go index 818234e93..9a93d4bdb 100644 --- a/pkg/hintrunner/operand_test.go +++ b/pkg/hintrunner/operand_test.go @@ -10,7 +10,7 @@ import ( ) func TestGetAp(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 writeTo(vm, VM.ExecutionSegment, vm.Context.Ap+7, memory.MemoryValueFromInt(11)) @@ -26,7 +26,7 @@ func TestGetAp(t *testing.T) { } func TestGetFp(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Fp = 15 writeTo(vm, VM.ExecutionSegment, vm.Context.Fp-7, memory.MemoryValueFromInt(11)) @@ -41,7 +41,7 @@ func TestGetFp(t *testing.T) { } func TestResolveDeref(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 writeTo(vm, VM.ExecutionSegment, vm.Context.Ap+7, memory.MemoryValueFromInt(11)) @@ -55,7 +55,7 @@ func TestResolveDeref(t *testing.T) { } func TestResolveDoubleDerefPositiveOffset(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 writeTo( vm, @@ -77,7 +77,7 @@ func TestResolveDoubleDerefPositiveOffset(t *testing.T) { } func TestResolveDoubleDerefNegativeOffset(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 writeTo( vm, @@ -110,7 +110,7 @@ func TestResolveImmediate(t *testing.T) { } func TestResolveAddOp(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Set the information used by the lhs vm.Context.Fp = 0 vm.Context.Ap = 5 @@ -147,7 +147,7 @@ func TestResolveAddOp(t *testing.T) { } func TestResolveMulOp(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Set the information used by the lhs vm.Context.Fp = 0 vm.Context.Ap = 5 diff --git a/pkg/hintrunner/testutils.go b/pkg/hintrunner/testutils.go index 45fc177db..420507296 100644 --- a/pkg/hintrunner/testutils.go +++ b/pkg/hintrunner/testutils.go @@ -6,16 +6,16 @@ import ( "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" ) -func defaultVirtualMachine() (*vm.VirtualMachine, *memory.MemoryManager) { - manager := memory.CreateMemoryManager() - manager.Memory.AllocateEmptySegment() - manager.Memory.AllocateEmptySegment() +func defaultVirtualMachine() *vm.VirtualMachine { + memory := memory.InitializeEmptyMemory() + memory.AllocateEmptySegment() + memory.AllocateEmptySegment() - vm, err := vm.NewVirtualMachine(vm.Context{}, manager.Memory, vm.VirtualMachineConfig{}) + vm, err := vm.NewVirtualMachine(vm.Context{}, memory, vm.VirtualMachineConfig{}) if err != nil { panic(err) } - return vm, manager + return vm } func writeTo(vm *VM.VirtualMachine, segment uint64, offset uint64, val memory.MemoryValue) { diff --git a/pkg/runners/zero/zero.go b/pkg/runners/zero/zero.go index 562226702..0d1db3099 100644 --- a/pkg/runners/zero/zero.go +++ b/pkg/runners/zero/zero.go @@ -14,10 +14,9 @@ import ( type ZeroRunner struct { // core components - program *Program - vm *vm.VirtualMachine - hintrunner hintrunner.HintRunner - memoryManager *memory.MemoryManager + program *Program + vm *vm.VirtualMachine + hintrunner hintrunner.HintRunner // config proofmode bool maxsteps uint64 @@ -27,22 +26,14 @@ type ZeroRunner struct { // Creates a new Runner of a Cairo Zero program func NewRunner(program *Program, proofmode bool, maxsteps uint64) (*ZeroRunner, error) { - memoryManager := memory.CreateMemoryManager() - _, err := memoryManager.Memory.AllocateSegment(program.Bytecode) // ProgramSegment - if err != nil { - return nil, err - } - memoryManager.Memory.AllocateEmptySegment() // ExecutionSegment - // todo(rodro): given the program get the appropiate hints hintrunner := hintrunner.NewHintRunner(make(map[uint64]hintrunner.Hinter)) return &ZeroRunner{ - program: program, - hintrunner: hintrunner, - memoryManager: memoryManager, - proofmode: proofmode, - maxsteps: maxsteps, + program: program, + hintrunner: hintrunner, + proofmode: proofmode, + maxsteps: maxsteps, }, nil } @@ -74,6 +65,13 @@ func (runner *ZeroRunner) Run() error { } func (runner *ZeroRunner) InitializeMainEntrypoint() (memory.MemoryAddress, error) { + mem := memory.InitializeEmptyMemory() + _, err := mem.AllocateSegment(runner.program.Bytecode) // ProgramSegment + if err != nil { + return memory.UnknownAddress, err + } + + mem.AllocateEmptySegment() // ExecutionSegment if runner.proofmode { initialPCOffset, ok := runner.program.Labels["__start__"] if !ok { @@ -92,18 +90,18 @@ func (runner *ZeroRunner) InitializeMainEntrypoint() (memory.MemoryAddress, erro return memory.MemoryAddress{SegmentIndex: vm.ProgramSegment, Offset: endPcOffset}, runner.initializeVm(&memory.MemoryAddress{ SegmentIndex: vm.ProgramSegment, Offset: initialPCOffset, - }, stack) + }, stack, mem) } - returnFp := memory.MemoryValueFromSegmentAndOffset( - runner.memory().AllocateEmptySegment(), + mem.AllocateEmptySegment(), 0, ) - return runner.InitializeEntrypoint("main", nil, &returnFp) + + return runner.InitializeEntrypoint("main", nil, &returnFp, mem) } func (runner *ZeroRunner) InitializeEntrypoint( - funcName string, arguments []*f.Element, returnFp *memory.MemoryValue, + funcName string, arguments []*f.Element, returnFp *memory.MemoryValue, mem *memory.Memory, ) (memory.MemoryAddress, error) { initialPCOffset, ok := runner.program.Entrypoints[funcName] if !ok { @@ -115,18 +113,21 @@ func (runner *ZeroRunner) InitializeEntrypoint( stack = append(stack, memory.MemoryValueFromFieldElement(arguments[i])) } end := memory.MemoryAddress{ - SegmentIndex: uint64(runner.memory().AllocateEmptySegment()), + SegmentIndex: uint64(mem.AllocateEmptySegment()), Offset: 0, } + stack = append(stack, *returnFp, memory.MemoryValueFromMemoryAddress(&end)) return end, runner.initializeVm(&memory.MemoryAddress{ SegmentIndex: vm.ProgramSegment, Offset: initialPCOffset, - }, stack) + }, stack, mem) } -func (runner *ZeroRunner) initializeVm(initialPC *memory.MemoryAddress, stack []memory.MemoryValue) error { - executionSegment := runner.segments()[vm.ExecutionSegment] +func (runner *ZeroRunner) initializeVm(initialPC *memory.MemoryAddress, stack []memory.MemoryValue, memory *memory.Memory) error { + //Initialize memory + + executionSegment := memory.Segments[vm.ExecutionSegment] offset := executionSegment.Len() for idx := range stack { if err := executionSegment.Write(offset+uint64(idx), &stack[idx]); err != nil { @@ -140,7 +141,7 @@ func (runner *ZeroRunner) initializeVm(initialPC *memory.MemoryAddress, stack [] Pc: *initialPC, Ap: offset + uint64(len(stack)), Fp: offset + uint64(len(stack)), - }, runner.memoryManager.Memory, vm.VirtualMachineConfig{ProofMode: runner.proofmode}) + }, memory, vm.VirtualMachineConfig{ProofMode: runner.proofmode}) return err } @@ -191,15 +192,7 @@ func (runner *ZeroRunner) BuildProof() ([]byte, []byte, error) { return nil, nil, err } - return EncodeTrace(relocatedTrace), EncodeMemory(runner.memoryManager.RelocateMemory()), nil -} - -func (runner *ZeroRunner) memory() *memory.Memory { - return runner.memoryManager.Memory -} - -func (runner *ZeroRunner) segments() []*memory.Segment { - return runner.memoryManager.Memory.Segments + return EncodeTrace(relocatedTrace), EncodeMemory(runner.vm.RelocateMemory()), nil } func (runner *ZeroRunner) pc() memory.MemoryAddress { diff --git a/pkg/runners/zero/zero_test.go b/pkg/runners/zero/zero_test.go index a61c3d722..68d143744 100644 --- a/pkg/runners/zero/zero_test.go +++ b/pkg/runners/zero/zero_test.go @@ -36,7 +36,7 @@ func TestSimpleProgram(t *testing.T) { err = runner.RunUntilPc(&endPc) require.NoError(t, err) - executionSegment := runner.segments()[vm.ExecutionSegment] + executionSegment := runner.vm.Memory.Segments[vm.ExecutionSegment] assert.Equal( t, @@ -81,7 +81,7 @@ func TestStepLimitExceeded(t *testing.T) { err = runner.RunUntilPc(&endPc) require.ErrorContains(t, err, "step limit exceeded") - executionSegment := runner.segments()[vm.ExecutionSegment] + executionSegment := runner.vm.Memory.Segments[vm.ExecutionSegment] assert.Equal( t, @@ -133,7 +133,7 @@ func TestStepLimitExceededProofMode(t *testing.T) { err = runner.Run() require.ErrorContains(t, err, "step limit exceeded") - executionSegment := runner.segments()[vm.ExecutionSegment] + executionSegment := runner.vm.Memory.Segments[vm.ExecutionSegment] assert.Equal( t, diff --git a/pkg/vm/memory/memory.go b/pkg/vm/memory/memory.go index c88a69502..972548564 100644 --- a/pkg/vm/memory/memory.go +++ b/pkg/vm/memory/memory.go @@ -257,3 +257,21 @@ func (memory *Memory) Peek(segmentIndex uint64, offset uint64) (MemoryValue, err func (memory *Memory) PeekFromAddress(address *MemoryAddress) (MemoryValue, error) { return memory.Peek(address.SegmentIndex, address.Offset) } + +// It returns all segment offsets and max memory used +func (memory *Memory) RelocationOffsets() ([]uint64, uint64) { + // Prover expects maxMemoryUsed to start at one + var maxMemoryUsed uint64 = 1 + + // segmentsOffsets[0] = 1 + // segmentsOffsets[1] = 1 + len(segment[0]) + // segmentsOffsets[N] = 1 + len(segment[n-1]) + sum of segements[n-1-i] for i in [1, n-1] + segmentsOffsets := make([]uint64, uint64(len(memory.Segments))+1) + segmentsOffsets[0] = 1 + for i, segment := range memory.Segments { + segmentLength := segment.Len() + maxMemoryUsed += segmentLength + segmentsOffsets[i+1] = segmentsOffsets[i] + segmentLength + } + return segmentsOffsets, maxMemoryUsed +} diff --git a/pkg/vm/memory/memory_manager.go b/pkg/vm/memory/memory_manager.go deleted file mode 100644 index cb342dafd..000000000 --- a/pkg/vm/memory/memory_manager.go +++ /dev/null @@ -1,61 +0,0 @@ -package memory - -import ( - f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" -) - -type MemoryManager struct { - Memory *Memory -} - -// Creates a new memory manager -func CreateMemoryManager() *MemoryManager { - memory := InitializeEmptyMemory() - - return &MemoryManager{ - Memory: memory, - } -} - -// It returns all segments in memory but relocated as a single segment -// Each element is a pointer to a field element, if the cell was not accessed, -// nil is stored instead -func (mm *MemoryManager) RelocateMemory() []*f.Element { - // this begins at one, because the prover expects for max memory used to - var maxMemoryUsed uint64 = 1 - - // segmentsOffsets[0] = 1 - // segmentsOffsets[1] = 1 + len(segment[0]) - // segmentsOffsets[N] = 1 + len(segment[n-1]) + sum of segements[n-1-i] for i in [1, n-1] - segmentsOffsets := make([]uint64, uint64(len(mm.Memory.Segments))+1) - segmentsOffsets[0] = 1 - for i, segment := range mm.Memory.Segments { - segmentLength := segment.Len() - maxMemoryUsed += segmentLength - segmentsOffsets[i+1] = segmentsOffsets[i] + segmentLength - } - - // the prover expect first element of the relocated memory to start at index 1, - // this way we fill relocatedMemory starting from zero, but the actual value - // returned has nil as its first element. - relocatedMemory := make([]*f.Element, maxMemoryUsed) - for i, segment := range mm.Memory.Segments { - // fmt.Printf("s: %s", segment) - for j := uint64(0); j < segment.Len(); j++ { - cell := segment.Data[j] - if !cell.Known() { - continue - } - - var felt *f.Element - if cell.IsAddress() { - felt = cell.addrUnsafe().Relocate(segmentsOffsets) - } else { - felt = &cell.felt - } - - relocatedMemory[segmentsOffsets[i]+j] = felt - } - } - return relocatedMemory -} diff --git a/pkg/vm/memory/memory_manager_test.go b/pkg/vm/memory/memory_manager_test.go deleted file mode 100644 index cb06383b0..000000000 --- a/pkg/vm/memory/memory_manager_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package memory - -import ( - "testing" - - f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" - "github.com/stretchr/testify/require" -) - -func TestMemoryRelocationWithFelt(t *testing.T) { - // segment 0: [2, -, -, 3] - // segment 3: [5, -, 7, -, 11, 13] - // relocated: [-, 2, -, -, 3, 5, -, 7, -, 11, 13] - - manager := CreateMemoryManager() - updateMemoryWithValues( - manager.Memory, - []memoryWrite{ - // segment zero - {0, 0, uint64(2)}, - {0, 3, uint64(3)}, - // segment three - {3, 0, uint64(5)}, - {3, 2, uint64(7)}, - {3, 4, uint64(11)}, - {3, 5, uint64(13)}, - }, - ) - - res := manager.RelocateMemory() - - expected := []*f.Element{ - nil, - // segment zero - new(f.Element).SetUint64(2), - nil, - nil, - new(f.Element).SetUint64(3), - // segment three - new(f.Element).SetUint64(5), - nil, - new(f.Element).SetUint64(7), - nil, - new(f.Element).SetUint64(11), - new(f.Element).SetUint64(13), - } - - require.Equal(t, len(expected), len(res)) - require.Equal(t, expected, res) -} - -func TestMemoryRelocationWithAddress(t *testing.T) { - // segment 0: [-, 1, -, 1:5] (4) - // segment 1: [1, 4:3, 7, -, -, 13] (10) - // segment 2: [0:1] (11) - // segment 3: [2:0] (12) - // segment 4: [0:0, 1:1, 1:5, 15] (16) - // relocated: [ - // dummy: -, - // zero: -, 1, -, 10, - // one: 1, 16, 7, -, -, 13, - // two: 2, - // three: 11, - // four: 1, 6, 10, 15, - // ] - - manager := CreateMemoryManager() - updateMemoryWithValues( - manager.Memory, - []memoryWrite{ - // segment zero - {0, 1, uint64(1)}, - {0, 3, &MemoryAddress{1, 5}}, - // segment one - {1, 0, uint64(1)}, - {1, 1, &MemoryAddress{4, 3}}, - {1, 2, uint64(7)}, - {1, 5, uint64(13)}, - // segment two - {2, 0, &MemoryAddress{0, 1}}, - // segment three - {3, 0, &MemoryAddress{2, 0}}, - // segment four - {4, 0, &MemoryAddress{0, 0}}, - {4, 1, &MemoryAddress{1, 1}}, - {4, 2, &MemoryAddress{1, 5}}, - {4, 3, uint64(15)}, - }, - ) - - res := manager.RelocateMemory() - - expected := []*f.Element{ - nil, - // segment zero - nil, - new(f.Element).SetUint64(1), - nil, - new(f.Element).SetUint64(10), - // segment one - new(f.Element).SetUint64(1), - new(f.Element).SetUint64(16), - new(f.Element).SetUint64(7), - nil, - nil, - new(f.Element).SetUint64(13), - // segment two - new(f.Element).SetUint64(2), - // segment three - new(f.Element).SetUint64(11), - // segment 4 - new(f.Element).SetUint64(1), - new(f.Element).SetUint64(6), - new(f.Element).SetUint64(10), - new(f.Element).SetUint64(15), - } - - require.Equal(t, len(expected), len(res)) - require.Equal(t, expected, res) -} - -type memoryWrite struct { - SegmentIndex uint64 - Offset uint64 - Value any -} - -func updateMemoryWithValues(memory *Memory, valuesToWrite []memoryWrite) { - var max_segment uint64 = 0 - for _, toWrite := range valuesToWrite { - // wrap any inside a memory value - val, err := MemoryValueFromAny(toWrite.Value) - if err != nil { - panic(err) - } - - // if the destination segment does not exist, create it - for toWrite.SegmentIndex >= max_segment { - max_segment += 1 - memory.AllocateEmptySegment() - } - - // write the memory val - err = memory.Write(toWrite.SegmentIndex, toWrite.Offset, &val) - if err != nil { - panic(err) - } - - } -} diff --git a/pkg/vm/memory/memory_test.go b/pkg/vm/memory/memory_test.go index 728709779..cb3fd86be 100644 --- a/pkg/vm/memory/memory_test.go +++ b/pkg/vm/memory/memory_test.go @@ -214,6 +214,40 @@ func TestSegmentBuiltin(t *testing.T) { }) } +func TestRelocationOffsets(t *testing.T) { + memory := InitializeEmptyMemory() + memory.AllocateEmptySegment() //Program + memory.AllocateEmptySegment() //Execution + memory.AllocateEmptySegment() + + err := memory.Segments[1].Write(0, memoryValuePointerFromInt(1)) + assert.NoError(t, err) + err = memory.Segments[1].Write(1, memoryValuePointerFromInt(2)) + assert.NoError(t, err) + err = memory.Segments[1].Write(2, memoryValuePointerFromInt(3)) + assert.NoError(t, err) + err = memory.Segments[1].Write(3, memoryValuePointerFromInt(4)) + assert.NoError(t, err) + + err = memory.Segments[2].Write(0, memoryValuePointerFromInt(5)) + assert.NoError(t, err) + err = memory.Segments[2].Write(1, memoryValuePointerFromInt(6)) + assert.NoError(t, err) + + // segmentsOffsets[0] = 1 + // segmentsOffsets[1] = 1 + // segmentsOffsets[2] = 1+4 + // segmentsOffsets[3] = 1+4+2 + expected_offsets := []uint64{1, 1, 5, 7} + + offsets, memoryUsed := memory.RelocationOffsets() + for i, v := range offsets { + assert.Equal(t, expected_offsets[i], v) + + } + assert.Equal(t, memoryUsed, uint64(7)) +} + // compares the memory value match an expected value at the given segment and offset func noErrorAndEqualSegmentRead(t *testing.T, s *Segment, offset uint64, expected MemoryValue) { v, err := s.Read(offset) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 0bca7002e..3d3dc5a79 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -512,3 +512,33 @@ func (vm *VirtualMachine) relocateTrace() []Trace { } return relocatedTrace } + +// It returns all segments in memory but relocated as a single segment +// Each element is a pointer to a field element, if the cell was not accessed, +// nil is stored instead +func (vm *VirtualMachine) RelocateMemory() []*f.Element { + segmentsOffsets, maxMemoryUsed := vm.Memory.RelocationOffsets() + // the prover expect first element of the relocated memory to start at index 1, + // this way we fill relocatedMemory starting from zero, but the actual value + // returned has nil as its first element. + relocatedMemory := make([]*f.Element, maxMemoryUsed) + for i, segment := range vm.Memory.Segments { + for j := uint64(0); j < segment.Len(); j++ { + cell := segment.Data[j] + if !cell.Known() { + continue + } + + var felt *f.Element + if cell.IsAddress() { + addr, _ := cell.MemoryAddress() + felt = addr.Relocate(segmentsOffsets) + } else { + felt, _ = cell.FieldElement() + } + + relocatedMemory[segmentsOffsets[i]+j] = felt + } + } + return relocatedMemory +} diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 92a93fdca..937eb54d4 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -8,11 +8,12 @@ import ( "github.com/stretchr/testify/require" assembler "github.com/NethermindEth/cairo-vm-go/pkg/assembler" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" mem "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" ) func TestGetCellApDst(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const offDest = 15 @@ -35,7 +36,7 @@ func TestGetCellApDst(t *testing.T) { } func TestGetCellFpDst(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -62,7 +63,7 @@ func TestGetCellFpDst(t *testing.T) { } func TestGetCellApDstWithDifferentOffsets(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() offsets := []int{-10, -5, 0, 5, 10} for _, offset := range offsets { @@ -87,7 +88,7 @@ func TestGetCellApDstWithDifferentOffsets(t *testing.T) { } func TestGetCellDstApNegativeOffset(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() const ( offDest = -2 @@ -112,7 +113,7 @@ func TestGetCellDstApNegativeOffset(t *testing.T) { } func TestGetCellDstFpNegativeOffset(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() const ( offDest = -19 @@ -137,7 +138,7 @@ func TestGetCellDstFpNegativeOffset(t *testing.T) { } func TestGetApCellOp0(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -163,7 +164,7 @@ func TestGetApCellOp0(t *testing.T) { func TestGetApCellOp0NegOff(t *testing.T) { // Op0 & Ap & Negative case - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -189,7 +190,7 @@ func TestGetApCellOp0NegOff(t *testing.T) { func TestGetFpCellOp0(t *testing.T) { // Op0 & Fp & Positive case - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -215,7 +216,7 @@ func TestGetFpCellOp0(t *testing.T) { func TestGetFpCellOp0NegOff(t *testing.T) { // Op0 & Fp & Negative case - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const ( @@ -240,7 +241,7 @@ func TestGetFpCellOp0NegOff(t *testing.T) { } func TestGetImmCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachineWithBytecode( + vm := defaultVirtualMachineWithBytecode( []*f.Element{ newElementPtr(0), // dummy newElementPtr(0), // dummy @@ -267,7 +268,7 @@ func TestGetImmCellOp1(t *testing.T) { } func TestGetOp0PosCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachineWithBytecode( + vm := defaultVirtualMachineWithBytecode( []*f.Element{ newElementPtr(0), // dummy newElementPtr(0), // dummy @@ -295,7 +296,7 @@ func TestGetOp0PosCellOp1(t *testing.T) { } func TestGetOp0NegCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachineWithBytecode( + vm := defaultVirtualMachineWithBytecode( []*f.Element{ newElementPtr(0), // dummy newElementPtr(0), // dummy @@ -323,7 +324,7 @@ func TestGetOp0NegCellOp1(t *testing.T) { } func TestGetFpPosCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const offOp1 = 2 // target relative to Fp @@ -345,7 +346,7 @@ func TestGetFpPosCellOp1(t *testing.T) { } func TestGetFpNegCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values const offOp1 = -2 // target relative to Fp @@ -367,7 +368,7 @@ func TestGetFpNegCellOp1(t *testing.T) { } func TestGetApPosCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values vm.Context.Ap = 3 // "allocation pointer" @@ -388,7 +389,7 @@ func TestGetApPosCellOp1(t *testing.T) { } func TestGetApNegCellOp1(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() // Prepare vm with dummy values vm.Context.Ap = 3 // "allocation pointer" @@ -409,7 +410,7 @@ func TestGetApNegCellOp1(t *testing.T) { } func TestInferOperandSub(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{ Opcode: assembler.OpCodeAssertEq, Res: assembler.AddOperands, @@ -431,7 +432,7 @@ func TestInferOperandSub(t *testing.T) { } func TestInferResOp1(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{ Opcode: assembler.OpCodeAssertEq, Res: assembler.Op1, @@ -452,7 +453,7 @@ func TestInferResOp1(t *testing.T) { } func TestComputeResUnconstrained(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.Unconstrained} res, err := vm.computeRes(&instruction, nil, nil) require.NoError(t, err) @@ -460,7 +461,7 @@ func TestComputeResUnconstrained(t *testing.T) { } func TestComputeResOp1(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.Op1} writeToDataSegment(vm, 3, mem.MemoryValueFromInt(15)) @@ -474,7 +475,7 @@ func TestComputeResOp1(t *testing.T) { } func TestComputeAddResAddrToFelt(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.AddOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromSegmentAndOffset(2, 10)) @@ -488,7 +489,7 @@ func TestComputeAddResAddrToFelt(t *testing.T) { } func TestComputeAddResFeltToAddr(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.AddOperands} op0Addr := writeToDataSegment(vm, 2, mem.MemoryValueFromInt(8)) @@ -501,7 +502,7 @@ func TestComputeAddResFeltToAddr(t *testing.T) { } func TestComputeAddResBothAddrs(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.AddOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromSegmentAndOffset(2, 10)) @@ -512,7 +513,7 @@ func TestComputeAddResBothAddrs(t *testing.T) { } func TestComputeAddResBothFelts(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.AddOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromInt(10)) @@ -527,7 +528,7 @@ func TestComputeAddResBothFelts(t *testing.T) { // Felt should be Positive or Negative. Thus four test cases func TestComputeMulResPosToPosFelt(t *testing.T) { //Positive Felt to Positive Felt compute - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromInt(10)) @@ -540,7 +541,7 @@ func TestComputeMulResPosToPosFelt(t *testing.T) { } func TestComputeMulResNegToPosFelts(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} //Negative to Positive op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromInt(-10)) @@ -553,7 +554,7 @@ func TestComputeMulResNegToPosFelts(t *testing.T) { } func TestComputeMulResPosToNegFelt(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} //Positive to Negative op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromInt(10)) @@ -566,7 +567,7 @@ func TestComputeMulResPosToNegFelt(t *testing.T) { } func TestComputeMulResNegToNegFelt(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} //Netagive to Negative op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromInt(-10)) @@ -581,7 +582,7 @@ func TestComputeMulResNegToNegFelt(t *testing.T) { // Multiplication does not involve addresses // three failing cases func TestComputeMulResAddrToFelt(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromSegmentAndOffset(2, 10)) @@ -592,7 +593,7 @@ func TestComputeMulResAddrToFelt(t *testing.T) { } func TestComputeMulResFeltToAddr(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromInt(10)) @@ -603,7 +604,7 @@ func TestComputeMulResFeltToAddr(t *testing.T) { } func TestComputeMulResBothAddrs(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() instruction := assembler.Instruction{Res: assembler.MulOperands} op0Addr := writeToDataSegment(vm, 3, mem.MemoryValueFromSegmentAndOffset(2, 10)) @@ -614,7 +615,7 @@ func TestComputeMulResBothAddrs(t *testing.T) { } func TestOpcodeAssertionAssertEq(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() dstAddr := mem.MemoryAddress{SegmentIndex: ExecutionSegment, Offset: 0} instruction := assembler.Instruction{ @@ -631,7 +632,7 @@ func TestOpcodeAssertionAssertEq(t *testing.T) { } func TestUpdatePcNextInstr(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Pc = mem.MemoryAddress{SegmentIndex: 0, Offset: 3} instruction := assembler.Instruction{ @@ -645,7 +646,7 @@ func TestUpdatePcNextInstr(t *testing.T) { } func TestUpdatePcNextInstrImm(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Pc = mem.MemoryAddress{SegmentIndex: 0, Offset: 3} instruction := assembler.Instruction{ @@ -659,7 +660,7 @@ func TestUpdatePcNextInstrImm(t *testing.T) { } func TestUpdatePcJump(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Pc = mem.MemoryAddress{SegmentIndex: 0, Offset: 3} jumpAddr := uint64(10) @@ -675,7 +676,7 @@ func TestUpdatePcJump(t *testing.T) { } func TestUpdatePcJumpRel(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Pc = mem.MemoryAddress{SegmentIndex: 0, Offset: 3} relAddr := uint64(10) @@ -691,7 +692,7 @@ func TestUpdatePcJumpRel(t *testing.T) { } func TestUpdatePcJnz(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() relAddr := uint64(10) writeToDataSegment(vm, 0, mem.MemoryValueFromInt(10)) //dstCell writeToDataSegment(vm, 1, mem.MemoryValueFromInt(relAddr)) //op1Cell @@ -711,7 +712,7 @@ func TestUpdatePcJnz(t *testing.T) { } func TestUpdatePcJnzDstZero(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() writeToDataSegment(vm, 0, mem.MemoryValueFromInt(0)) //dstCell dstAddr := mem.MemoryAddress{SegmentIndex: ExecutionSegment, Offset: 0} @@ -728,7 +729,7 @@ func TestUpdatePcJnzDstZero(t *testing.T) { } func TestUpdatePcJnzDstZeroImm(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() writeToDataSegment(vm, 0, mem.MemoryValueFromInt(0)) //dstCell dstAddr := mem.MemoryAddress{SegmentIndex: ExecutionSegment, Offset: 0} @@ -745,7 +746,7 @@ func TestUpdatePcJnzDstZeroImm(t *testing.T) { } func TestUpdateApSameAp(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 instruction := assembler.Instruction{ @@ -759,7 +760,7 @@ func TestUpdateApSameAp(t *testing.T) { } func TestUpdateApAddImmPos(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 instruction := assembler.Instruction{ @@ -775,7 +776,7 @@ func TestUpdateApAddImmPos(t *testing.T) { } func TestUpdateApAddImmNeg(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 10 instruction := assembler.Instruction{ @@ -791,7 +792,7 @@ func TestUpdateApAddImmNeg(t *testing.T) { } func TestUpdateApAddOne(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 instruction := assembler.Instruction{ @@ -805,7 +806,7 @@ func TestUpdateApAddOne(t *testing.T) { } func TestUpdateApAddTwo(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Ap = 5 instruction := assembler.Instruction{ @@ -819,7 +820,7 @@ func TestUpdateApAddTwo(t *testing.T) { } func TestUpdateFp(t *testing.T) { - vm, _ := defaultVirtualMachine() + vm := defaultVirtualMachine() vm.Context.Fp = 5 instruction := assembler.Instruction{ @@ -842,24 +843,24 @@ func writeToDataSegment(vm *VirtualMachine, index uint64, value mem.MemoryValue) } } -func defaultVirtualMachine() (*VirtualMachine, *mem.MemoryManager) { +func defaultVirtualMachine() *VirtualMachine { return defaultVirtualMachineWithBytecode(nil) } -func defaultVirtualMachineWithBytecode(bytecode []*f.Element) (*VirtualMachine, *mem.MemoryManager) { - manager := mem.CreateMemoryManager() - _, err := manager.Memory.AllocateSegment(bytecode) +func defaultVirtualMachineWithBytecode(bytecode []*f.Element) *VirtualMachine { + memory := memory.InitializeEmptyMemory() + _, err := memory.AllocateSegment(bytecode) if err != nil { panic(err) } - manager.Memory.AllocateEmptySegment() + memory.AllocateEmptySegment() - vm, err := NewVirtualMachine(Context{}, manager.Memory, VirtualMachineConfig{}) + vm, err := NewVirtualMachine(Context{}, memory, VirtualMachineConfig{}) if err != nil { panic(err) } - return vm, manager + return vm } // create a pointer to an Element @@ -867,3 +868,145 @@ func newElementPtr(val uint64) *f.Element { element := f.NewElement(val) return &element } + +func TestMemoryRelocationWithFelt(t *testing.T) { + // segment 0: [2, -, -, 3] + // segment 3: [5, -, 7, -, 11, 13] + // relocated: [-, 2, -, -, 3, 5, -, 7, -, 11, 13] + vm := defaultVirtualMachine() + + updateMemoryWithValues( + vm.Memory, + []memoryWrite{ + // segment zero + {0, 0, uint64(2)}, + {0, 3, uint64(3)}, + // segment three + {3, 0, uint64(5)}, + {3, 2, uint64(7)}, + {3, 4, uint64(11)}, + {3, 5, uint64(13)}, + }, + ) + + res := vm.RelocateMemory() + + expected := []*f.Element{ + nil, + // segment zero + new(f.Element).SetUint64(2), + nil, + nil, + new(f.Element).SetUint64(3), + // segment three + new(f.Element).SetUint64(5), + nil, + new(f.Element).SetUint64(7), + nil, + new(f.Element).SetUint64(11), + new(f.Element).SetUint64(13), + } + + require.Equal(t, len(expected), len(res)) + require.Equal(t, expected, res) +} + +func TestMemoryRelocationWithAddress(t *testing.T) { + // segment 0: [-, 1, -, 1:5] (4) + // segment 1: [1, 4:3, 7, -, -, 13] (10) + // segment 2: [0:1] (11) + // segment 3: [2:0] (12) + // segment 4: [0:0, 1:1, 1:5, 15] (16) + // relocated: [ + // dummy: -, + // zero: -, 1, -, 10, + // one: 1, 16, 7, -, -, 13, + // two: 2, + // three: 11, + // four: 1, 6, 10, 15, + // ] + + vm := defaultVirtualMachine() + updateMemoryWithValues( + vm.Memory, + []memoryWrite{ + // segment zero + {0, 1, uint64(1)}, + {0, 3, &mem.MemoryAddress{SegmentIndex: 1, Offset: 5}}, + // segment one + {1, 0, uint64(1)}, + {1, 1, &mem.MemoryAddress{SegmentIndex: 4, Offset: 3}}, + {1, 2, uint64(7)}, + {1, 5, uint64(13)}, + // segment two + {2, 0, &mem.MemoryAddress{SegmentIndex: 0, Offset: 1}}, + // segment three + {3, 0, &mem.MemoryAddress{SegmentIndex: 2, Offset: 0}}, + // segment four + {4, 0, &mem.MemoryAddress{SegmentIndex: 0, Offset: 0}}, + {4, 1, &mem.MemoryAddress{SegmentIndex: 1, Offset: 1}}, + {4, 2, &mem.MemoryAddress{SegmentIndex: 1, Offset: 5}}, + {4, 3, uint64(15)}, + }, + ) + + res := vm.RelocateMemory() + + expected := []*f.Element{ + nil, + // segment zero + nil, + new(f.Element).SetUint64(1), + nil, + new(f.Element).SetUint64(10), + // segment one + new(f.Element).SetUint64(1), + new(f.Element).SetUint64(16), + new(f.Element).SetUint64(7), + nil, + nil, + new(f.Element).SetUint64(13), + // segment two + new(f.Element).SetUint64(2), + // segment three + new(f.Element).SetUint64(11), + // segment 4 + new(f.Element).SetUint64(1), + new(f.Element).SetUint64(6), + new(f.Element).SetUint64(10), + new(f.Element).SetUint64(15), + } + + require.Equal(t, len(expected), len(res)) + require.Equal(t, expected, res) +} + +type memoryWrite struct { + SegmentIndex uint64 + Offset uint64 + Value any +} + +func updateMemoryWithValues(memory *mem.Memory, valuesToWrite []memoryWrite) { + var max_segment uint64 = 0 + for _, toWrite := range valuesToWrite { + // wrap any inside a memory value + val, err := mem.MemoryValueFromAny(toWrite.Value) + if err != nil { + panic(err) + } + + // if the destination segment does not exist, create it + for toWrite.SegmentIndex >= max_segment { + max_segment += 1 + memory.AllocateEmptySegment() + } + + // write the memory val + err = memory.Write(toWrite.SegmentIndex, toWrite.Offset, &val) + if err != nil { + panic(err) + } + + } +}