diff --git a/pkg/vm/memory/memory.go b/pkg/vm/memory/memory.go index fba6a050..ea7485a9 100644 --- a/pkg/vm/memory/memory.go +++ b/pkg/vm/memory/memory.go @@ -6,17 +6,17 @@ import ( // Memory represents the Cairo VM's memory. type Memory struct { - data [][]MaybeRelocatable + data map[Relocatable]MaybeRelocatable + num_segments uint } -func NewMemory(data [][]MaybeRelocatable) *Memory { - return &Memory{data} +func NewMemory() *Memory { + data := make(map[Relocatable]MaybeRelocatable) + return &Memory{data, 0} } // Inserts a value in some memory address, given by a Relocatable value. -func (m *Memory) Insert(addr *Relocatable, val *MaybeRelocatable) error { - addr_idx, addr_offset := addr.into_indexes() - +func (m *Memory) Insert(addr Relocatable, val *MaybeRelocatable) error { // FIXME: There should be a special handling if the key // segment index is negative. This is an edge // case, so for now let's raise an error. @@ -24,40 +24,24 @@ func (m *Memory) Insert(addr *Relocatable, val *MaybeRelocatable) error { return errors.New("Segment index of key is negative - unimplemented") } - segment := &m.data[addr_idx] - segment_len := len(*segment) - - // When the offset of the insertion address is greater than the max - // offset of the segment, memory cells are filled with `nil` in the - // intermediate values, if any. So if segment has length 2 (last idx is 1) - // and we want to insert something at index 4, index 2 and 3 will be filled - // with `nil`, and index 4 will have the desired value. - if segment_len <= int(addr_offset) { - new_segment_len := addr_offset + 1 - for i := segment_len; i < int(new_segment_len); i++ { - *segment = append(*segment, MaybeRelocatable{nil}) - } + // Check that insertions are preformed within the memory bounds + if addr.segmentIndex >= int(m.num_segments) { + return errors.New("Error: Inserting into a non allocated segment") } - // At this point, something exists at the `addr_offset` for sure. - // Check that the value at that offset is `nil` and if it is, then - // swap that `nil` with the desired value. - if (*segment)[addr_offset].is_nil() { - (*segment)[addr_offset] = *val - // If there wasn't `nil`, then we are trying to overwrite in that - // address. If the value we are trying to insert is not the same as - // the one that was already in that location, raise an error. - } else if (*segment)[addr_offset] != *val { + // Check for possible overwrites + prev_elem, ok := m.data[addr] + if ok && prev_elem != *val { return errors.New("Memory is write-once, cannot overwrite memory value") } + m.data[addr] = *val + return nil } // Gets some value stored in the memory address `addr`. -func (m *Memory) Get(addr *Relocatable) (*MaybeRelocatable, error) { - addr_idx, addr_offset := addr.into_indexes() - +func (m *Memory) Get(addr Relocatable) (*MaybeRelocatable, error) { // FIXME: There should be a special handling if the key // segment index is negative. This is an edge // case, so for now let's raise an error. @@ -70,7 +54,11 @@ func (m *Memory) Get(addr *Relocatable) (*MaybeRelocatable, error) { // check if the value is a `Relocatable` with a negative // segment index. Again, these are edge cases so not important // right now. See cairo-vm code for details. - value := m.data[addr_idx][addr_offset] + value, ok := m.data[addr] + + if !ok { + return nil, errors.New("Memory Get: Value not found") + } return &value, nil } diff --git a/pkg/vm/memory/memory_test.go b/pkg/vm/memory/memory_test.go index 96eadd71..15a94edd 100644 --- a/pkg/vm/memory/memory_test.go +++ b/pkg/vm/memory/memory_test.go @@ -8,9 +8,10 @@ import ( ) func TestMemoryInsert(t *testing.T) { - // Instantiate memory with 3 empty segments - data := make([][]memory.MaybeRelocatable, 3) - mem := memory.NewMemory(data) + mem_manager := memory.NewMemorySegmentManager() + mem_manager.AddSegment() + mem_manager.AddSegment() + mem := &mem_manager.Memory // Instantiate the address where we want to insert and the value. // We will insert the value Int(5) in segment 1, offset 0 @@ -36,9 +37,10 @@ func TestMemoryInsert(t *testing.T) { } func TestMemoryInsertWithHoles(t *testing.T) { - // Instantiate memory with 3 empty segments - data := make([][]memory.MaybeRelocatable, 3) - mem := memory.NewMemory(data) + mem_manager := memory.NewMemorySegmentManager() + mem_manager.AddSegment() + mem_manager.AddSegment() + mem := &mem_manager.Memory // Instantiate the address where we want to insert and the value. // We will insert the MaybeRelocatable Int(7) in segment 1, offset 2 @@ -61,25 +63,155 @@ func TestMemoryInsertWithHoles(t *testing.T) { if !reflect.DeepEqual(res_val, val) { t.Errorf("Inserted value and original value are not the same") } +} + +func TestMemoryInsertOverWriteSameValue(t *testing.T) { + mem_manager := memory.NewMemorySegmentManager() + mem := &mem_manager.Memory - // Since we inserted in segment 1, offset 2 in an empty memory, now - // the values in segment 1, offset 0 and 1 should be `nil` (memory holes) - hole1_addr := memory.NewRelocatable(1, 0) - hole2_addr := memory.NewRelocatable(1, 1) + // We will insert the MaybeRelocatable Int(7) in segment 0, offset 0 + key := mem_manager.AddSegment() + val := memory.NewMaybeRelocatableInt(7) - hole1, err := mem.Get(hole1_addr) + // Make the insertion + err := mem.Insert(key, val) + if err != nil { + t.Errorf("Insert error in test: %s", err) + } + + // Insert the same value again and check it doesn't fail + err2 := mem.Insert(key, val) + if err2 != nil { + t.Errorf("Insert error in test: %s", err) + } +} + +func TestMemoryInsertOverWriteValue(t *testing.T) { + mem_manager := memory.NewMemorySegmentManager() + mem := &mem_manager.Memory + + // We will insert the MaybeRelocatable Int(7) in segment 0, offset 0 + key := mem_manager.AddSegment() + val := memory.NewMaybeRelocatableInt(7) + + // Make the insertion + err := mem.Insert(key, val) + if err != nil { + t.Errorf("Insert error in test: %s", err) + } + + // Insert another value into the same address and check that it fails + val2 := memory.NewMaybeRelocatableInt(8) + err2 := mem.Insert(key, val2) + if err2 == nil { + t.Errorf("Overwritting memory value should fail") + } +} + +func TestMemoryInsertUnallocatedSegment(t *testing.T) { + mem_manager := memory.NewMemorySegmentManager() + mem := &mem_manager.Memory + + // Instantiate the address where we want to insert and the value. + // We will insert the value Int(5) in segment 1, offset 0 + key := memory.NewRelocatable(1, 0) + val := memory.NewMaybeRelocatableInt(5) + + // Make the insertion + err := mem.Insert(key, val) + if err == nil { + t.Errorf("Insertion on unallocated segment should fail") + } +} + +func TestMemorySegmentsLoadDataUnallocatedSegment(t *testing.T) { + mem_manager := memory.NewMemorySegmentManager() + + ptr := memory.NewRelocatable(1, 0) + data := []memory.MaybeRelocatable{*memory.NewMaybeRelocatableInt(5)} + + // Load Data + _, err := mem_manager.LoadData(ptr, &data) + if err == nil { + t.Errorf("Insertion on unallocated segment should fail") + } +} + +func TestMemorySegmentsLoadDataOneElement(t *testing.T) { + mem_manager := memory.NewMemorySegmentManager() + mem_manager.AddSegment() + + ptr := memory.NewRelocatable(0, 0) + val := memory.NewMaybeRelocatableInt(5) + data := []memory.MaybeRelocatable{*val} + + // Load Data + end_ptr, err := mem_manager.LoadData(ptr, &data) + if err != nil { + t.Errorf("LoadData error in test: %s", err) + } + + // Check returned ptr + expected_end_ptr := memory.NewRelocatable(0, 1) + if !reflect.DeepEqual(end_ptr, expected_end_ptr) { + t.Errorf("LoadData returned wrong ptr") + } + + // Check inserted value + res_val, err := mem_manager.Memory.Get(ptr) if err != nil { t.Errorf("Get error in test: %s", err) } - hole2, err := mem.Get(hole2_addr) + // Check that the original and the retrieved values are the same + if !reflect.DeepEqual(res_val, val) { + t.Errorf("Inserted value and original value are not the same") + } +} + +func TestMemorySegmentsLoadDataTwoElements(t *testing.T) { + mem_manager := memory.NewMemorySegmentManager() + mem_manager.AddSegment() + + ptr := memory.NewRelocatable(0, 0) + val := memory.NewMaybeRelocatableInt(5) + val2 := memory.NewMaybeRelocatableInt(5) + data := []memory.MaybeRelocatable{*val, *val2} + + // Load Data + end_ptr, err := mem_manager.LoadData(ptr, &data) + if err != nil { + t.Errorf("LoadData error in test: %s", err) + } + + // Check returned ptr + expected_end_ptr := memory.NewRelocatable(0, 2) + if !reflect.DeepEqual(end_ptr, expected_end_ptr) { + t.Errorf("LoadData returned wrong ptr") + } + + // Check inserted values + + // val + res_val, err := mem_manager.Memory.Get(ptr) + if err != nil { + t.Errorf("Get error in test: %s", err) + } + + // Check that the original and the retrieved values are the same + if !reflect.DeepEqual(res_val, val) { + t.Errorf("Inserted value and original value are not the same") + } + + //val2 + ptr2 := memory.NewRelocatable(0, 1) + res_val2, err := mem_manager.Memory.Get(ptr2) if err != nil { t.Errorf("Get error in test: %s", err) } - // Check that we got the holes from memory - expected_hole := memory.NewMaybeRelocatableNil() - if !reflect.DeepEqual(hole1, expected_hole) || !reflect.DeepEqual(hole2, expected_hole) { - t.Errorf("Expected nil value but got another") + // Check that the original and the retrieved values are the same + if !reflect.DeepEqual(res_val2, val2) { + t.Errorf("Inserted value and original value are not the same") } } diff --git a/pkg/vm/memory/relocatable.go b/pkg/vm/memory/relocatable.go index 8a6654a1..19d1c8ab 100644 --- a/pkg/vm/memory/relocatable.go +++ b/pkg/vm/memory/relocatable.go @@ -11,19 +11,8 @@ type Relocatable struct { // Creates a new Relocatable struct with the specified segment index // and offset. -func NewRelocatable(segment_idx int, offset uint) *Relocatable { - return &Relocatable{segment_idx, offset} -} - -// Get the the indexes of the Relocatable struct. -// Returns a tuple with both values (segment_index, offset) -func (r *Relocatable) into_indexes() (uint, uint) { - if r.segmentIndex < 0 { - corrected_segment_idx := uint(-(r.segmentIndex + 1)) - return corrected_segment_idx, r.offset - } - - return uint(r.segmentIndex), r.offset +func NewRelocatable(segment_idx int, offset uint) Relocatable { + return Relocatable{segment_idx, offset} } // Int in the Cairo VM represents a value in memory that @@ -47,12 +36,14 @@ func NewMaybeRelocatableInt(felt uint) *MaybeRelocatable { return &MaybeRelocatable{inner: Int{felt}} } -// Creates a new MaybeRelocatable with a `nil` inner value -func NewMaybeRelocatableNil() *MaybeRelocatable { - return &MaybeRelocatable{inner: nil} +// If m is Int, returns the inner value + true, if not, returns zero + false +func (m *MaybeRelocatable) GetInt() (Int, bool) { + int, is_type := m.inner.(Int) + return int, is_type } -// Checks if inner value of MaybeRelocatable is `nil` -func (m *MaybeRelocatable) is_nil() bool { - return m.inner == nil +// If m is Relocatable, returns the inner value + true, if not, returns zero + false +func (m *MaybeRelocatable) GetRelocatable() (Relocatable, bool) { + rel, is_type := m.inner.(Relocatable) + return rel, is_type } diff --git a/pkg/vm/memory/segments.go b/pkg/vm/memory/segments.go index 3fc1c025..d9b4f4d6 100644 --- a/pkg/vm/memory/segments.go +++ b/pkg/vm/memory/segments.go @@ -5,5 +5,30 @@ package memory // the memory at the end of the VM run. type MemorySegmentManager struct { segmentSizes map[uint]uint - memory Memory + Memory Memory +} + +func NewMemorySegmentManager() *MemorySegmentManager { + memory := NewMemory() + return &MemorySegmentManager{make(map[uint]uint), *memory} +} + +// Adds a memory segment and returns the first address of the new segment +func (m *MemorySegmentManager) AddSegment() Relocatable { + ptr := Relocatable{int(m.Memory.num_segments), 0} + m.Memory.num_segments += 1 + return ptr +} + +// Writes data into the memory from address ptr and returns the first address after the data. +// If any insertion fails, returns (0,0) and the memory insertion error +func (m *MemorySegmentManager) LoadData(ptr Relocatable, data *[]MaybeRelocatable) (Relocatable, error) { + for _, val := range *data { + err := m.Memory.Insert(ptr, &val) + if err != nil { + return Relocatable{0, 0}, err + } + ptr.offset += 1 + } + return ptr, nil } diff --git a/pkg/vm/run_context.go b/pkg/vm/run_context.go index 769e25e1..6ec0deaf 100644 --- a/pkg/vm/run_context.go +++ b/pkg/vm/run_context.go @@ -5,7 +5,7 @@ import "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" // RunContext containts the register states of the // Cairo VM. type RunContext struct { - pc memory.Relocatable - ap uint - fp uint + Pc memory.Relocatable + Ap memory.Relocatable + Fp memory.Relocatable }