From 02e161f839f81a7c488d5e51a6b49c108023f86a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 14 Feb 2021 15:49:40 +0100 Subject: [PATCH] add dict tests with string keys/values --- compiler/builtins/bitcode/src/dict.zig | 82 ++++++++---------------- compiler/gen/tests/gen_dict.rs | 88 ++++++++++++++++++++++++-- roc_std/src/lib.rs | 4 ++ 3 files changed, 112 insertions(+), 62 deletions(-) diff --git a/compiler/builtins/bitcode/src/dict.zig b/compiler/builtins/bitcode/src/dict.zig index e0790b1d2d..da55ad6437 100644 --- a/compiler/builtins/bitcode/src/dict.zig +++ b/compiler/builtins/bitcode/src/dict.zig @@ -5,8 +5,6 @@ const mem = std.mem; const Allocator = mem.Allocator; const assert = std.debug.assert; -const level_size = 32; - const INITIAL_SEED = 0xc70f6907; const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize); const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE); @@ -31,10 +29,11 @@ const MaybeIndex = union(MaybeIndexTag) { }; fn nextSeed(seed: u64) u64 { + // TODO is this a valid way to get a new seed? are there better ways? return seed + 1; } -fn total_slots_at_level(input: usize) usize { +fn totalCapacityAtLevel(input: usize) usize { if (input == 0) { return 0; } @@ -106,17 +105,6 @@ pub const RocDict = extern struct { }; } - pub fn deinit(self: RocDict, allocator: *Allocator, key_size: usize, value_size: usize) void { - if (!self.isEmpty()) { - const slot_size = slotSize(key_size, value_size); - - const dict_bytes_ptr: [*]u8 = self.dict_bytes orelse unreachable; - - const dict_bytes: []u8 = dict_bytes_ptr[0..(self.number_of_levels)]; - allocator.free(dict_bytes); - } - } - pub fn allocate( allocator: *Allocator, number_of_levels: usize, @@ -125,7 +113,7 @@ pub const RocDict = extern struct { key_size: usize, value_size: usize, ) RocDict { - const number_of_slots = total_slots_at_level(number_of_levels); + const number_of_slots = totalCapacityAtLevel(number_of_levels); const slot_size = slotSize(key_size, value_size); const data_bytes = number_of_slots * slot_size; @@ -147,7 +135,7 @@ pub const RocDict = extern struct { const slot_size = slotSize(key_width, value_width); const old_capacity = self.capacity(); - const new_capacity = total_slots_at_level(new_level); + const new_capacity = totalCapacityAtLevel(new_level); const delta_capacity = new_capacity - old_capacity; const data_bytes = new_capacity * slot_size; @@ -155,8 +143,7 @@ pub const RocDict = extern struct { // transfer the memory - if (old_capacity > 0) { - const source_ptr = self.dict_bytes orelse unreachable; + if (self.dict_bytes) |source_ptr| { const dest_ptr = first_slot; var source_offset: usize = 0; @@ -208,10 +195,6 @@ pub const RocDict = extern struct { return @ptrCast([*]u8, self.dict_bytes); } - pub fn contains(self: RocDict, key_size: usize, key_ptr: *const c_void, hash_code: u64) bool { - return false; - } - pub fn len(self: RocDict) usize { return self.dict_entries_len; } @@ -221,17 +204,18 @@ pub const RocDict = extern struct { } pub fn isUnique(self: RocDict) bool { + // the empty dict is unique (in the sense that copying it will not leak memory) if (self.isEmpty()) { return true; } + // otherwise, check if the refcount is one const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.dict_bytes)); - return (ptr - 1)[0] == REFCOUNT_ONE; } pub fn capacity(self: RocDict) usize { - return total_slots_at_level(self.number_of_levels); + return totalCapacityAtLevel(self.number_of_levels); } pub fn makeUnique(self: RocDict, allocator: *Allocator, alignment: Alignment, key_width: usize, value_width: usize) RocDict { @@ -264,21 +248,15 @@ pub const RocDict = extern struct { fn getSlot(self: *const RocDict, index: usize, key_width: usize, value_width: usize) Slot { const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot); - if (self.dict_bytes) |u8_ptr| { - return @intToEnum(Slot, u8_ptr[offset]); - } else { - unreachable; - } + const ptr = self.dict_bytes orelse unreachable; + return @intToEnum(Slot, ptr[offset]); } fn setSlot(self: *RocDict, index: usize, key_width: usize, value_width: usize, slot: Slot) void { const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot); - if (self.dict_bytes) |u8_ptr| { - u8_ptr[offset] = @enumToInt(slot); - } else { - unreachable; - } + const ptr = self.dict_bytes orelse unreachable; + ptr[offset] = @enumToInt(slot); } fn setKey(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void { @@ -290,12 +268,11 @@ pub const RocDict = extern struct { } }; - if (self.dict_bytes) |u8_ptr| { - const source = data orelse unreachable; - @memcpy(u8_ptr + offset, source, key_width); - } else { - unreachable; - } + const ptr = self.dict_bytes orelse unreachable; + + const source = data orelse unreachable; + const dest = ptr + offset; + @memcpy(dest, source, key_width); } fn getKey(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque { @@ -307,11 +284,8 @@ pub const RocDict = extern struct { } }; - if (self.dict_bytes) |u8_ptr| { - return u8_ptr + offset; - } else { - unreachable; - } + const ptr = self.dict_bytes orelse unreachable; + return ptr + offset; } fn setValue(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void { @@ -323,12 +297,11 @@ pub const RocDict = extern struct { } }; - if (self.dict_bytes) |u8_ptr| { - const source = data orelse unreachable; - @memcpy(u8_ptr + offset, source, key_width); - } else { - unreachable; - } + const ptr = self.dict_bytes orelse unreachable; + + const source = data orelse unreachable; + const dest = ptr + offset; + @memcpy(dest, source, value_width); } fn getValue(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque { @@ -340,11 +313,8 @@ pub const RocDict = extern struct { } }; - if (self.dict_bytes) |u8_ptr| { - return u8_ptr + offset; - } else { - unreachable; - } + const ptr = self.dict_bytes orelse unreachable; + return ptr + offset; } fn findIndex(self: *const RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) MaybeIndex { diff --git a/compiler/gen/tests/gen_dict.rs b/compiler/gen/tests/gen_dict.rs index effb623c98..11e2bcc66a 100644 --- a/compiler/gen/tests/gen_dict.rs +++ b/compiler/gen/tests/gen_dict.rs @@ -13,6 +13,8 @@ mod helpers; #[cfg(test)] mod gen_dict { + use roc_std::RocStr; + #[test] fn dict_empty_len() { assert_evals_to!( @@ -156,7 +158,7 @@ mod gen_dict { indoc!( r#" myDict : Dict I64 I64 - myDict = + myDict = Dict.empty |> Dict.insert 0 100 |> Dict.insert 1 100 @@ -177,7 +179,7 @@ mod gen_dict { indoc!( r#" myDict : Dict I64 I64 - myDict = + myDict = Dict.empty |> Dict.insert 0 100 |> Dict.insert 1 200 @@ -198,9 +200,9 @@ mod gen_dict { indoc!( r#" myDict : Dict I64 I64 - myDict = + myDict = [1,2,3] - |> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty + |> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty Dict.values myDict "# @@ -220,10 +222,10 @@ mod gen_dict { accum myDict : Dict I64 I64 - myDict = + myDict = # 25 elements (8 + 16 + 1) is guaranteed to overflow/reallocate at least twice range 0 25 [] - |> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty + |> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty Dict.values myDict |> List.len @@ -233,4 +235,78 @@ mod gen_dict { i64 ); } + + #[test] + fn small_str_keys() { + assert_evals_to!( + indoc!( + r#" + myDict : Dict Str I64 + myDict = + Dict.empty + |> Dict.insert "a" 100 + |> Dict.insert "b" 100 + |> Dict.insert "c" 100 + + + Dict.keys myDict + "# + ), + &[ + RocStr::from_str("c"), + RocStr::from_str("a"), + RocStr::from_str("b"), + ], + &[RocStr] + ); + } + + #[test] + fn big_str_keys() { + assert_evals_to!( + indoc!( + r#" + myDict : Dict Str I64 + myDict = + Dict.empty + |> Dict.insert "Leverage agile frameworks to provide a robust" 100 + |> Dict.insert "synopsis for high level overviews. Iterative approaches" 200 + |> Dict.insert "to corporate strategy foster collaborative thinking to" 300 + + + Dict.keys myDict + "# + ), + &[ + RocStr::from_str("Leverage agile frameworks to provide a robust"), + RocStr::from_str("to corporate strategy foster collaborative thinking to"), + RocStr::from_str("synopsis for high level overviews. Iterative approaches"), + ], + &[RocStr] + ); + } + + #[test] + fn big_str_values() { + assert_evals_to!( + indoc!( + r#" + myDict : Dict I64 Str + myDict = + Dict.empty + |> Dict.insert 100 "Leverage agile frameworks to provide a robust" + |> Dict.insert 200 "synopsis for high level overviews. Iterative approaches" + |> Dict.insert 300 "to corporate strategy foster collaborative thinking to" + + Dict.values myDict + "# + ), + &[ + RocStr::from_str("Leverage agile frameworks to provide a robust"), + RocStr::from_str("to corporate strategy foster collaborative thinking to"), + RocStr::from_str("synopsis for high level overviews. Iterative approaches"), + ], + &[RocStr] + ); + } } diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index f6a3e38b42..1d256ceb77 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -401,6 +401,10 @@ impl RocStr { Self::from_slice_with_capacity_str(slice, slice.len()) } + pub fn from_str(slice: &str) -> RocStr { + Self::from_slice_with_capacity_str(slice.as_bytes(), slice.len()) + } + pub fn as_slice(&self) -> &[u8] { if self.is_small_str() { unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) }