Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DictWrite hint #364

Merged
merged 47 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
593ff1b
Add basic skeleton
har777 Apr 22, 2024
88028ac
Implement DefaultDictNew
har777 Apr 22, 2024
076f7b9
Add tests
har777 Apr 22, 2024
99eada8
Implement DefaultRead
har777 Apr 22, 2024
93b9c52
Implement DictWrite
har777 Apr 22, 2024
1a6212c
Add simple integration test
har777 Apr 25, 2024
66c21d2
Merge branch 'defaultdictnew_hint' into dictread_hint
har777 Apr 25, 2024
2b3aff8
Update dict integration test
har777 Apr 25, 2024
69e9838
Merge branch 'dictread_hint' into dictwrite_hint
har777 Apr 25, 2024
1a071b2
Update dict integration test
har777 Apr 25, 2024
d60bf5f
Fix imports
har777 Apr 25, 2024
b5f9d28
Merge branch 'dictread_hint' into dictwrite_hint
har777 Apr 25, 2024
414772c
Fix imports
har777 Apr 25, 2024
1db67f0
Merge main
har777 May 13, 2024
667b45a
Add comments + minor changes
har777 May 13, 2024
a261b72
Merge defaultdictnew_hint
har777 May 13, 2024
31b2bc4
Merge dictread_hint
har777 May 13, 2024
92a609d
Remove unnecessary ctx init
har777 May 13, 2024
980f4ab
Add comment
har777 May 13, 2024
4adb057
Add comment
har777 May 13, 2024
a07a75d
Clean up dict integration test
har777 May 14, 2024
957676f
Clean up dict integration test
har777 May 14, 2024
7d9815e
Clean up dict integration test
har777 May 14, 2024
03910ad
Treat dicts in zero hints differently
har777 May 15, 2024
4decc42
Remove accidental newline
har777 May 15, 2024
e04aa54
Merge defaultdictnew_hint + update implementation
har777 May 15, 2024
cefb3f5
Fix ignoring err message
har777 May 15, 2024
efc3129
Remove some unnecessary code from tests
har777 May 15, 2024
9f08d23
Merge defaultread_hint + update implementation
har777 May 15, 2024
159674a
Merge main
har777 May 16, 2024
ff554ad
Fix typo
har777 May 16, 2024
ee997e4
Fix typo
har777 May 16, 2024
00494b4
Improved comments
har777 May 16, 2024
0088b58
Merge main
har777 May 16, 2024
2bccb0e
Add credit comment
har777 May 16, 2024
ca98db3
Merge dictread_hint
har777 May 16, 2024
faeacd5
Add better comments
har777 May 16, 2024
216ed2d
Merge main
har777 May 20, 2024
6f4bc71
Fix freeOffset bug + add tests
har777 May 20, 2024
3adece9
Fix tests
har777 May 21, 2024
ed75416
Add and use zeroDictInScopeEquals test util
har777 May 21, 2024
5911929
Merge branch 'main' into dictwrite_hint
TAdev0 Jun 5, 2024
0c1b794
Merge branch 'main' into dictwrite_hint
MaksymMalicki Jun 7, 2024
951eda1
Fix method usage
har777 Jun 7, 2024
58e12a7
Comment out integration test
har777 Jun 7, 2024
ce43643
Enable dict integration test
har777 Jun 7, 2024
54ed888
Merge branch 'main' into dictwrite_hint
har777 Jun 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions integration_tests/cairo_files/dict.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from starkware.cairo.common.default_dict import default_dict_new
from starkware.cairo.common.dict import dict_read, dict_write
from starkware.cairo.common.dict_access import DictAccess

func test_default_dict() {
alloc_locals;
let (local my_dict: DictAccess*) = default_dict_new(123);

return ();
}

func test_read() {
alloc_locals;
let (local my_dict: DictAccess*) = default_dict_new(123);

let (local val1: felt) = dict_read{dict_ptr=my_dict}(key=1);
assert val1 = 123;

let (local val2: felt) = dict_read{dict_ptr=my_dict}(key=2);
assert val2 = 123;

return ();
}

func test_write() {
alloc_locals;
let (local my_dict: DictAccess*) = default_dict_new(123);

let (local val1: felt) = dict_read{dict_ptr=my_dict}(key=1);
assert val1 = 123;

let (local val2: felt) = dict_read{dict_ptr=my_dict}(key=2);
assert val2 = 123;

dict_write{dict_ptr=my_dict}(key=1, new_value=512);
let (local val3: felt) = dict_read{dict_ptr=my_dict}(key=1);
assert val3 = 512;

let (local val4: felt) = dict_read{dict_ptr=my_dict}(key=2);
assert val4 = 123;

dict_write{dict_ptr=my_dict}(key=1, new_value=1024);
let (local val5: felt) = dict_read{dict_ptr=my_dict}(key=1);
assert val5 = 1024;

let (local val6: felt) = dict_read{dict_ptr=my_dict}(key=2);
assert val6 = 123;

dict_write{dict_ptr=my_dict}(key=1, new_value=888);
dict_write{dict_ptr=my_dict}(key=2, new_value=999);
let (local val7: felt) = dict_read{dict_ptr=my_dict}(key=1);
assert val7 = 888;
let (local val8: felt) = dict_read{dict_ptr=my_dict}(key=2);
assert val8 = 999;
let (local val9: felt) = dict_read{dict_ptr=my_dict}(key=3);
assert val9 = 123;

return ();
}

func main() {
test_default_dict();
test_read();
test_write();

return ();
}
9 changes: 9 additions & 0 deletions pkg/hintrunner/hinter/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,12 @@ func (sm *ScopeManager) getCurrentScope() (*map[string]any, error) {
}
return &sm.scopes[len(sm.scopes)-1], nil
}

func (sm *ScopeManager) GetZeroDictionaryManager() (ZeroDictionaryManager, bool) {
dictionaryManagerValue, err := sm.GetVariableValue("__dict_manager")
if err != nil {
return ZeroDictionaryManager{}, false
}
dictionaryManager, ok := dictionaryManagerValue.(ZeroDictionaryManager)
return dictionaryManager, ok
}
116 changes: 116 additions & 0 deletions pkg/hintrunner/hinter/zero_dict.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package hinter

import (
"fmt"

VM "github.com/NethermindEth/cairo-vm-go/pkg/vm"
mem "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory"
f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

// Used to keep track of all dictionaries data
type ZeroDictionary struct {
// The data contained on a dictionary
data map[f.Element]mem.MemoryValue
// Default value for key not present in the dictionary
defaultValue mem.MemoryValue
// first free offset in memory segment of dictionary
freeOffset uint64
}

// Gets the memory value at certain key
func (d *ZeroDictionary) At(key f.Element) (mem.MemoryValue, error) {
if value, ok := d.data[key]; ok {
return value, nil
}
if d.defaultValue != mem.UnknownValue {
return d.defaultValue, nil
}
return mem.UnknownValue, fmt.Errorf("no value for key: %v", key)
}

// Given a key and a value, it sets the value at the given key
func (d *ZeroDictionary) Set(key f.Element, value mem.MemoryValue) {
d.data[key] = value
}

// Given a incrementBy value, it increments the freeOffset field of dictionary by it
func (d *ZeroDictionary) IncrementFreeOffset(freeOffset uint64) {
d.freeOffset += freeOffset
}

// Used to manage dictionaries creation
type ZeroDictionaryManager struct {
// a map that links a segment index to a dictionary
dictionaries map[uint64]ZeroDictionary
}

func NewZeroDictionaryManager() ZeroDictionaryManager {
return ZeroDictionaryManager{
dictionaries: make(map[uint64]ZeroDictionary),
}
}

// It creates a new segment which will hold dictionary values. It links this
// segment with the current dictionary and returns the address that points
// to the start of this segment
func (dm *ZeroDictionaryManager) NewDictionary(vm *VM.VirtualMachine) mem.MemoryAddress {
newDictAddr := vm.Memory.AllocateEmptySegment()
dm.dictionaries[newDictAddr.SegmentIndex] = ZeroDictionary{
data: make(map[f.Element]mem.MemoryValue),
defaultValue: mem.UnknownValue,
freeOffset: 0,
}
return newDictAddr
}

// It creates a new segment which will hold dictionary values. It links this
// segment with the current dictionary and returns the address that points
// to the start of this segment. If key not present in the dictionary during
// querying the defaultValue will be returned instead.
func (dm *ZeroDictionaryManager) NewDefaultDictionary(vm *VM.VirtualMachine, defaultValue mem.MemoryValue) mem.MemoryAddress {
newDefaultDictAddr := vm.Memory.AllocateEmptySegment()
dm.dictionaries[newDefaultDictAddr.SegmentIndex] = ZeroDictionary{
data: make(map[f.Element]mem.MemoryValue),
defaultValue: defaultValue,
freeOffset: 0,
}
return newDefaultDictAddr
}

// Given a memory address, it looks for the right dictionary using the segment index. If no
// segment is associated with the given segment index, it errors
func (dm *ZeroDictionaryManager) GetDictionary(dictAddr mem.MemoryAddress) (ZeroDictionary, error) {
dict, ok := dm.dictionaries[dictAddr.SegmentIndex]
if ok {
return dict, nil
}
return ZeroDictionary{}, fmt.Errorf("no dictionary at address: %s", dictAddr)
}

// Given a memory address and a key it returns the value held at that position. The address is used
// to locate the correct dictionary and the key to index on it
func (dm *ZeroDictionaryManager) At(dictAddr mem.MemoryAddress, key f.Element) (mem.MemoryValue, error) {
if dict, ok := dm.dictionaries[dictAddr.SegmentIndex]; ok {
return dict.At(key)
}
return mem.UnknownValue, fmt.Errorf("no dictionary at address: %s", dictAddr)
}

// Given a memory address,a key and a value it stores the value at the correct position.
func (dm *ZeroDictionaryManager) Set(dictAddr mem.MemoryAddress, key f.Element, value mem.MemoryValue) error {
if dict, ok := dm.dictionaries[dictAddr.SegmentIndex]; ok {
dict.Set(key, value)
return nil
}
return fmt.Errorf("no dictionary at address: %s", dictAddr)
}

// Given a memory address and a incrementBy, it increments the freeOffset field of dictionary by it.
func (dm *ZeroDictionaryManager) IncrementFreeOffset(dictAddr mem.MemoryAddress, freeOffset uint64) error {
if dict, ok := dm.dictionaries[dictAddr.SegmentIndex]; ok {
dict.IncrementFreeOffset(freeOffset)
return nil
}
return fmt.Errorf("no dictionary at address: %s", dictAddr)
}
3 changes: 3 additions & 0 deletions pkg/hintrunner/zero/hintcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ const (
keccakWriteArgs string = "segments.write_arg(ids.inputs, [ids.low % 2 ** 64, ids.low // 2 ** 64])\nsegments.write_arg(ids.inputs + 2, [ids.high % 2 ** 64, ids.high // 2 ** 64])"

// ------ Dictionaries hints related code ------
defaultDictNewCode string = "if '__dict_manager' not in globals():\n from starkware.cairo.common.dict import DictManager\n __dict_manager = DictManager()\n\nmemory[ap] = __dict_manager.new_default_dict(segments, ids.default_value)"
dictReadCode string = "dict_tracker = __dict_manager.get_tracker(ids.dict_ptr)\ndict_tracker.current_ptr += ids.DictAccess.SIZE\nids.value = dict_tracker.data[ids.key]"
dictWriteCode string = "dict_tracker = __dict_manager.get_tracker(ids.dict_ptr)\ndict_tracker.current_ptr += ids.DictAccess.SIZE\nids.dict_ptr.prev_value = dict_tracker.data[ids.key]\ndict_tracker.data[ids.key] = ids.new_value"
squashDictInnerAssertLenKeys string = "assert len(keys) == 0"
squashDictInnerNextKey string = "assert len(keys) > 0, 'No keys left but remaining_accesses > 0.'\nids.next_key = key = keys.pop()"

Expand Down
6 changes: 6 additions & 0 deletions pkg/hintrunner/zero/zerohint.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ func GetHintFromCode(program *zero.ZeroProgram, rawHint zero.Hint, hintPC uint64
case usortBodyCode:
return createUsortBodyHinter(resolver)
// Dictionaries hints
case defaultDictNewCode:
return createDefaultDictNewHinter(resolver)
case dictReadCode:
return createDictReadHinter(resolver)
case dictWriteCode:
return createDictWriteHinter(resolver)
case squashDictInnerAssertLenKeys:
return createSquashDictInnerAssertLenKeysHinter()
case squashDictInnerNextKey:
Expand Down
Loading
Loading