Skip to content

Commit

Permalink
Merge branch 'main' into write-your-own-vm-basic-types
Browse files Browse the repository at this point in the history
  • Loading branch information
fmoletta committed Jul 31, 2023
2 parents 7850e11 + d9bbbd1 commit cec8581
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 71 deletions.
52 changes: 20 additions & 32 deletions pkg/vm/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,58 +6,42 @@ 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.
if addr.segmentIndex < 0 {
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.
Expand All @@ -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
}
164 changes: 148 additions & 16 deletions pkg/vm/memory/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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")
}
}
29 changes: 10 additions & 19 deletions pkg/vm/memory/relocatable.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
27 changes: 26 additions & 1 deletion pkg/vm/memory/segments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions pkg/vm/run_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit cec8581

Please sign in to comment.