Skip to content

Commit

Permalink
Memcpy hints (#230)
Browse files Browse the repository at this point in the history
* add is_positive felt method

* implement memcpy_continue_copying hint

* fix test

* fix hint

* add failing test

* add hint

* add test to memcpy_continue_copying hint

* improve imports in hints

* abstract error

* add aux utils methods

* change from MaybeRelocatable to Felt when returning n identifier

* add tests

* add continue loop hint

* use utils method for adding segments

* fix memcpy_continue_coding hint code

Co-authored-by: Juan-M-V <[email protected]>

* improve returned value in memset_step_loop method

Co-authored-by: fmoletta <[email protected]>

* delete GetRef method fromexecScopes

* Revert "delete GetRef method fromexecScopes"

This reverts commit 9efe91f.

* Revert "Revert "delete GetRef method fromexecScopes""

This reverts commit ad0c563.

* improve error message

* improve tests

* add integration tests

* Merge branch 'main' of github.com:lambdaclass/cairo-vm.go into memcpy-hints

---------

Co-authored-by: Juan-M-V <[email protected]>
Co-authored-by: fmoletta <[email protected]>
  • Loading branch information
3 people authored Sep 20, 2023
1 parent 6f45961 commit 64dcc86
Show file tree
Hide file tree
Showing 17 changed files with 496 additions and 49 deletions.
21 changes: 21 additions & 0 deletions cairo_programs/memcpy_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.memcpy import memcpy
from starkware.cairo.common.registers import get_fp_and_pc

func main() {
alloc_locals;
let (__fp__, _) = get_fp_and_pc();
local numbers: (felt, felt, felt) = (1, 2, 3);
let dest: felt* = alloc();
memcpy(dst=dest, src=&numbers, len=3);
assert numbers[0] = dest[0];
assert numbers[1] = dest[1];
assert numbers[2] = dest[2];
return ();
}
30 changes: 30 additions & 0 deletions cairo_programs/memset.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.memset import memset
from starkware.cairo.common.bool import TRUE, FALSE

func check_array(array: felt*, value: felt, array_length: felt, iterator: felt) -> (r: felt) {
if (iterator == array_length) {
return (TRUE,);
}
if (array[iterator] != value) {
return (FALSE,);
}
return check_array(array, value, array_length, iterator + 1);
}

func main() {
alloc_locals;
let (local strings: felt*) = alloc();
memset(strings, 'Lambda', 20);
let check_string: felt = check_array(strings, 'Lambda', 20, 0);
assert check_string = TRUE;
assert strings[20] = 'can insert new value';
let numbers: felt* = alloc();
memset(numbers, 10, 100);
let check_string: felt = check_array(numbers, 10, 100, 0);
assert check_string = TRUE;
assert numbers[100] = 11;
return ();
}
1 change: 1 addition & 0 deletions pkg/hints/hint_codes/memcpy_hint_codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ const ADD_SEGMENT = "memory[ap] = segments.add()"
const VM_EXIT_SCOPE = "vm_exit_scope()"
const VM_ENTER_SCOPE = "vm_enter_scope()"
const MEMCPY_ENTER_SCOPE = "vm_enter_scope({'n': ids.len})"
const MEMCPY_CONTINUE_COPYING = "n -= 1\nids.continue_copying = 1 if n > 0 else 0"
6 changes: 6 additions & 0 deletions pkg/hints/hint_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ func (p *CairoVmHintProcessor) ExecuteHint(vm *vm.VirtualMachine, hintData *any,
return sqrt(data.Ids, vm)
case MEMCPY_ENTER_SCOPE:
return memcpy_enter_scope(data.Ids, vm, execScopes)
case MEMSET_ENTER_SCOPE:
return memset_enter_scope(data.Ids, vm, execScopes)
case MEMCPY_CONTINUE_COPYING:
return memset_step_loop(data.Ids, vm, execScopes, "continue_copying")
case MEMSET_CONTINUE_LOOP:
return memset_step_loop(data.Ids, vm, execScopes, "continue_loop")
case VM_ENTER_SCOPE:
return vm_enter_scope(execScopes)
case ASSERT_LE_FELT:
Expand Down
6 changes: 5 additions & 1 deletion pkg/hints/hint_utils/ids_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func ErrUnknownIdentifier(name string) error {
return ErrIdsManager(errors.Errorf("Unknow identifier %s", name))
}

func ErrIdentifierNotFelt(name string) error {
return ErrIdsManager(errors.Errorf("Identifier %s is not a Felt", name))
}

func NewIdsManager(references map[string]HintReference, hintApTracking parser.ApTrackingData, accessibleScopes []string) IdsManager {
return IdsManager{
References: references,
Expand Down Expand Up @@ -67,7 +71,7 @@ func (ids *IdsManager) GetFelt(name string, vm *VirtualMachine) (lambdaworks.Fel
}
felt, is_felt := val.GetFelt()
if !is_felt {
return lambdaworks.Felt{}, errors.Errorf("Identifier %s is not a Felt", name)
return lambdaworks.Felt{}, ErrIdentifierNotFelt(name)
}
return felt, nil
}
Expand Down
43 changes: 39 additions & 4 deletions pkg/hints/memcpy_hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package hints

import (
. "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils"
"github.com/lambdaclass/cairo-vm.go/pkg/types"
. "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks"
. "github.com/lambdaclass/cairo-vm.go/pkg/types"
. "github.com/lambdaclass/cairo-vm.go/pkg/vm"
. "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory"
)
Expand All @@ -15,13 +16,13 @@ func add_segment(vm *VirtualMachine) error {

// Implements hint:
// %{ vm_exit_scope() %}
func vm_exit_scope(executionScopes *types.ExecutionScopes) error {
func vm_exit_scope(executionScopes *ExecutionScopes) error {
return executionScopes.ExitScope()
}

// Implements hint:
// %{ vm_enter_scope({'n': ids.len}) %}
func memcpy_enter_scope(ids IdsManager, vm *VirtualMachine, execScopes *types.ExecutionScopes) error {
func memcpy_enter_scope(ids IdsManager, vm *VirtualMachine, execScopes *ExecutionScopes) error {
len, err := ids.GetFelt("len", vm)
if err != nil {
return err
Expand All @@ -31,8 +32,42 @@ func memcpy_enter_scope(ids IdsManager, vm *VirtualMachine, execScopes *types.Ex
return nil
}

/*
Implements hint:
%{
n -= 1
ids.`i_name` = 1 if n > 0 else 0
%}
*/
func memset_step_loop(ids IdsManager, vm *VirtualMachine, execScoes *ExecutionScopes, i_name string) error {
// get `n` variable from vm scope
n, err := execScoes.Get("n")
if err != nil {
return err
}
// this variable will hold the value of `n - 1`
newN, ok := n.(Felt)
if !ok {
return ConversionError(n, "felt")
}
newN = newN.Sub(FeltOne())
execScoes.AssignOrUpdateVariable("n", newN)

// if `newN` is positive, insert 1 in the address of `continue_loop`
// else, insert 0
var flag *MaybeRelocatable
if newN.IsPositive() {
flag = NewMaybeRelocatableFelt(FeltOne())
} else {
flag = NewMaybeRelocatableFelt(FeltZero())
}
return ids.Insert(i_name, flag, vm)
}

// Implements hint: vm_enter_scope()
func vm_enter_scope(executionScopes *types.ExecutionScopes) error {
func vm_enter_scope(executionScopes *ExecutionScopes) error {
executionScopes.EnterScope(make(map[string]interface{}))
return nil
}
138 changes: 128 additions & 10 deletions pkg/hints/memcpy_hints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
. "github.com/lambdaclass/cairo-vm.go/pkg/hints"
. "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_codes"
. "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils"
"github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks"
. "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks"
. "github.com/lambdaclass/cairo-vm.go/pkg/types"
. "github.com/lambdaclass/cairo-vm.go/pkg/utils"
. "github.com/lambdaclass/cairo-vm.go/pkg/vm"
. "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory"
)

func AddSegmentHintOk(t *testing.T) {
func TestAddSegmentHintOk(t *testing.T) {
vm := NewVirtualMachine()
initial_segments := vm.Segments.Memory.NumSegments()
hintProcessor := CairoVmHintProcessor{}
Expand Down Expand Up @@ -51,7 +51,7 @@ func TestExitScopeHintValid(t *testing.T) {

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err != nil {
t.Errorf("TestExitScopeHintValid failed with error %s", err)
t.Errorf("failed with error %s", err)
}

}
Expand All @@ -77,7 +77,7 @@ func TestExitScopeHintInvalid(t *testing.T) {

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err.Error() != ErrCannotExitMainScop.Error() {
t.Errorf("TestExitScopeHintInvalid should fail with error %s", ErrCannotExitMainScop)
t.Errorf("should fail with error %s", ErrCannotExitMainScop)
}

}
Expand All @@ -101,14 +101,14 @@ func TestMemcpyEnterScopeHintValid(t *testing.T) {
executionScopes := NewExecutionScopes()
err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err != nil {
t.Errorf("TestMemcpyEnterScopeHintValid failed with error %s", err)
t.Errorf("failed with error %s", err)
}
res, err := executionScopes.Get("n")
if err != nil {
t.Errorf("TestMemcpyEnterScopeHintValid failed with error %s", err)
t.Errorf("failed with error %s", err)
}
if res.(lambdaworks.Felt) != lambdaworks.FeltFromDecString("45") {
t.Errorf("TestMemcpyEnterScopeHintValid failed, expected len: %d, got: %d", lambdaworks.FeltFromDecString("45"), res.(lambdaworks.Felt))
if res.(Felt) != FeltFromDecString("45") {
t.Errorf("failed, expected len: %d, got: %d", FeltFromDecString("45"), res.(Felt))
}
}

Expand All @@ -130,7 +130,7 @@ func TestMemcpyEnterScopeHintInvalid(t *testing.T) {
executionScopes := NewExecutionScopes()
err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err.Error() != ErrUnknownIdentifier("len").Error() {
t.Errorf("TestMemcpyEnterScopeHintInvalid should fail with error %s", ErrUnknownIdentifier("len"))
t.Errorf("should fail with error %s", ErrUnknownIdentifier("len"))
}
}

Expand All @@ -153,6 +153,124 @@ func TestEnterScope(t *testing.T) {

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err != nil {
t.Errorf("TestEnterScopeHint failed with error %s", err)
t.Errorf("failed with error %s", err)
}
}

func TestMemcpyContinueCopyingValid1(t *testing.T) {
vm := NewVirtualMachine()
vm.Segments = AddNSegments(vm.Segments, 2)
vm.RunContext.Fp = NewRelocatable(1, 2)
executionScopes := NewExecutionScopesWithInitValue("n", FeltOne())

idsManager := SetupIdsForTest(
map[string][]*MaybeRelocatable{
"continue_copying": nil,
},
vm,
)
hintProcessor := CairoVmHintProcessor{}
hintData := any(HintData{
Ids: idsManager,
Code: MEMCPY_CONTINUE_COPYING,
})

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err != nil {
t.Errorf("failed with error %s", err)
}

val, err := idsManager.GetFelt("continue_copying", vm)
if err != nil {
t.Errorf("failed with error %s", err)
}
if val != FeltZero() {
t.Errorf("failed, expected val: %d, got: %d", FeltZero(), val)
}
}

func TestMemcpyContinueCopyingValidNeg1(t *testing.T) {
vm := NewVirtualMachine()
vm.Segments = AddNSegments(vm.Segments, 2)
vm.RunContext.Fp = NewRelocatable(1, 2)
executionScopes := NewExecutionScopesWithInitValue("n", FeltFromDecString("-1"))

idsManager := SetupIdsForTest(
map[string][]*MaybeRelocatable{
"continue_copying": nil,
},
vm,
)
hintProcessor := CairoVmHintProcessor{}
hintData := any(HintData{
Ids: idsManager,
Code: MEMCPY_CONTINUE_COPYING,
})

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err != nil {
t.Errorf("failed with error %s", err)
}

val, err := idsManager.GetFelt("continue_copying", vm)
if err != nil {
t.Errorf("failed with error %s", err)
}
if val != FeltOne() {
t.Errorf("failed, expected val: %d, got: %d", FeltOne(), val)
}
}

func TestMemcpyContinueCopyingVarNotInScope(t *testing.T) {
vm := NewVirtualMachine()
vm.Segments.AddSegment()
vm.RunContext.Fp = NewRelocatable(3, 0)
vm.Segments.Memory.Insert(NewRelocatable(0, 2), NewMaybeRelocatableFelt(FeltFromUint64(5)))

executionScopes := NewExecutionScopes()

idsManager := SetupIdsForTest(
map[string][]*MaybeRelocatable{
"continue_copying": nil,
},
vm,
)
hintProcessor := CairoVmHintProcessor{}
hintData := any(HintData{
Ids: idsManager,
Code: MEMCPY_CONTINUE_COPYING,
})

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
if err.Error() != ErrVariableNotInScope("n").Error() {
t.Errorf("should fail with error %s", ErrVariableNotInScope("n"))
}
}

func TestMemcpyContinueCopyingInsertError(t *testing.T) {
vm := NewVirtualMachine()
vm.Segments = AddNSegments(vm.Segments, 2)
executionScopes := NewExecutionScopes()

scope := make(map[string]interface{})
scope["n"] = FeltOne()
executionScopes.EnterScope(scope)

idsManager := SetupIdsForTest(
map[string][]*MaybeRelocatable{
"continue_copying": {NewMaybeRelocatableFelt(FeltFromUint64(5))},
},
vm,
)
hintProcessor := CairoVmHintProcessor{}
hintData := any(HintData{
Ids: idsManager,
Code: MEMCPY_CONTINUE_COPYING,
})

err := hintProcessor.ExecuteHint(vm, &hintData, nil, executionScopes)
expected := ErrMemoryWriteOnce(NewRelocatable(0, 0), *NewMaybeRelocatableFeltFromUint64(5), *NewMaybeRelocatableFeltFromUint64(0))
if err.Error() != expected.Error() {
t.Errorf("should fail with error %s", expected)
}
}
4 changes: 4 additions & 0 deletions pkg/hints/memset_hint_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package hints

const MEMSET_ENTER_SCOPE = "vm_enter_scope({'n': ids.n})"
const MEMSET_CONTINUE_LOOP = "n -= 1\nids.continue_loop = 1 if n > 0 else 0"
18 changes: 18 additions & 0 deletions pkg/hints/memset_hints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hints

import (
. "github.com/lambdaclass/cairo-vm.go/pkg/hints/hint_utils"
. "github.com/lambdaclass/cairo-vm.go/pkg/types"
. "github.com/lambdaclass/cairo-vm.go/pkg/vm"
)

// Implements hint:
// %{ vm_enter_scope({'n': ids.n}) %}
func memset_enter_scope(ids IdsManager, vm *VirtualMachine, execScopes *ExecutionScopes) error {
n, err := ids.GetFelt("n", vm)
if err != nil {
return err
}
execScopes.EnterScope(map[string]interface{}{"n": n})
return nil
}
Loading

0 comments on commit 64dcc86

Please sign in to comment.