From 210bd18fe313bc07a84d4fdeb5eba9fcaebbd890 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 14 Feb 2021 03:16:11 +0100 Subject: [PATCH] implement decref, fuse RC operations --- compiler/builtins/bitcode/src/dict.zig | 340 +++++++++++++++++-------- compiler/builtins/bitcode/src/main.zig | 2 + compiler/builtins/src/bitcode.rs | 2 + compiler/can/src/builtins.rs | 1 - compiler/gen/src/llvm/build_dict.rs | 108 +++++++- compiler/gen/tests/gen_dict.rs | 84 ++++++ compiler/gen/tests/helpers/eval.rs | 2 +- compiler/mono/src/borrow.rs | 5 +- 8 files changed, 422 insertions(+), 122 deletions(-) diff --git a/compiler/builtins/bitcode/src/dict.zig b/compiler/builtins/bitcode/src/dict.zig index 23feb26e18..d83b3eac14 100644 --- a/compiler/builtins/bitcode/src/dict.zig +++ b/compiler/builtins/bitcode/src/dict.zig @@ -3,6 +3,7 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const mem = std.mem; const Allocator = mem.Allocator; +const assert = std.debug.assert; const level_size = 32; @@ -48,7 +49,7 @@ fn total_slots_at_level(input: usize) usize { return slots; } -fn slots_at_level(input: usize) usize { +fn capacityOfLevel(input: usize) usize { if (input == 0) { return 0; } @@ -118,60 +119,18 @@ pub const RocDict = extern struct { pub fn allocate( allocator: *Allocator, - result_in_place: InPlace, number_of_levels: usize, number_of_entries: usize, - alignment: usize, + alignment: Alignment, key_size: usize, value_size: usize, ) RocDict { const number_of_slots = total_slots_at_level(number_of_levels); - const first_slot = switch (alignment) { - 8 => blk: { - const slot_size = slotSize(key_size, value_size); - - const length = @sizeOf(usize) + (number_of_slots * slot_size); - - var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable; - - var as_usize_array = @ptrCast([*]usize, new_bytes); - if (result_in_place == InPlace.InPlace) { - as_usize_array[0] = @intCast(usize, number_of_slots); - } else { - as_usize_array[0] = REFCOUNT_ONE; - } - - var as_u8_array = @ptrCast([*]u8, new_bytes); - const first_slot = as_u8_array + @sizeOf(usize); - - break :blk first_slot; - }, - 16 => blk: { - const slot_size = slotSize(key_size, value_size); - - const length = 2 * @sizeOf(usize) + (number_of_slots * slot_size); - - var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable; - - var as_usize_array = @ptrCast([*]usize, new_bytes); - if (result_in_place == InPlace.InPlace) { - as_usize_array[0] = 0; - as_usize_array[1] = @intCast(usize, number_of_slots); - } else { - as_usize_array[0] = 0; - as_usize_array[1] = REFCOUNT_ONE; - } - - var as_u8_array = @ptrCast([*]u8, new_bytes); - const first_slot = as_u8_array + 2 * @sizeOf(usize); - - break :blk first_slot; - }, - else => unreachable, - }; + const slot_size = slotSize(key_size, value_size); + const data_bytes = number_of_slots * slot_size; return RocDict{ - .dict_bytes = first_slot, + .dict_bytes = allocateWithRefcount(allocator, alignment, data_bytes), .number_of_levels = number_of_levels, .dict_entries_len = number_of_entries, }; @@ -180,7 +139,7 @@ pub const RocDict = extern struct { pub fn reallocate( self: RocDict, allocator: *Allocator, - alignment: usize, + alignment: Alignment, key_width: usize, value_width: usize, ) RocDict { @@ -189,23 +148,10 @@ pub const RocDict = extern struct { const old_capacity = self.capacity(); const new_capacity = total_slots_at_level(new_level); + const delta_capacity = new_capacity - old_capacity; - const first_slot = switch (alignment) { - 8 => blk: { - const length = @sizeOf(usize) + (new_capacity * slot_size); - - var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable; - - var as_usize_array = @ptrCast([*]usize, new_bytes); - as_usize_array[0] = REFCOUNT_ONE; - - var as_u8_array = @ptrCast([*]u8, new_bytes); - const first_slot = as_u8_array + @sizeOf(usize); - - break :blk first_slot; - }, - else => unreachable, - }; + const data_bytes = new_capacity * slot_size; + const first_slot = allocateWithRefcount(allocator, alignment, data_bytes); // transfer the memory @@ -215,28 +161,47 @@ pub const RocDict = extern struct { if (old_capacity > 0) { var source_offset: usize = 0; var dest_offset: usize = 0; - @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width); - source_offset += old_capacity * key_width; - dest_offset += old_capacity * key_width + (new_capacity * key_width); - @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width); + if (alignment.keyFirst()) { + // copy keys + @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width); - source_offset += old_capacity * value_width; - dest_offset += old_capacity * value_width + (new_capacity * value_width); + // copy values + source_offset = old_capacity * key_width; + dest_offset = new_capacity * key_width; + @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width); + } else { + // copy values + @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width); + + // copy keys + source_offset = old_capacity * value_width; + dest_offset = new_capacity * value_width; + @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width); + } + + // copy slots + source_offset = old_capacity * (key_width + value_width); + dest_offset = new_capacity * (key_width + value_width); @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * @sizeOf(Slot)); } var i: usize = 0; - const first_new_slot_value = dest_ptr + old_capacity * slot_size + new_capacity * (key_width + value_width); + const first_new_slot_value = dest_ptr + old_capacity * slot_size + delta_capacity * (key_width + value_width); while (i < (new_capacity - old_capacity)) : (i += 1) { (first_new_slot_value)[i] = @enumToInt(Slot.Empty); } - return RocDict{ + const result = RocDict{ .dict_bytes = first_slot, .number_of_levels = self.number_of_levels + 1, .dict_entries_len = self.dict_entries_len, }; + + // NOTE we fuse an increment of all keys/values with a decrement of the input dict + decref(allocator, alignment, self.dict_bytes, self.capacity() * slotSize(key_width, value_width)); + + return result; } pub fn asU8ptr(self: RocDict) [*]u8 { @@ -270,7 +235,7 @@ pub const RocDict = extern struct { return total_slots_at_level(self.number_of_levels); } - pub fn makeUnique(self: RocDict, allocator: *Allocator, in_place: InPlace, alignment: Alignment, key_width: usize, value_width: usize, inc_key: Inc, inc_value: Inc) RocDict { + pub fn makeUnique(self: RocDict, allocator: *Allocator, alignment: Alignment, key_width: usize, value_width: usize) RocDict { if (self.isEmpty()) { return self; } @@ -281,28 +246,21 @@ pub const RocDict = extern struct { // unfortunately, we have to clone - var new_dict = RocDict.allocate(allocator, in_place, 8, self.dict_entries_len, alignment.toUsize(), key_width, value_width); + const in_place = InPlace.Clone; + var new_dict = RocDict.allocate(allocator, self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width); var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes); var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes); - const number_of_bytes = 8 * (@sizeOf(Slot) + key_width + value_width); + const number_of_bytes = self.capacity() * (@sizeOf(Slot) + key_width + value_width); @memcpy(new_bytes, old_bytes, number_of_bytes); // we copied potentially-refcounted values; make sure to increment const size = new_dict.capacity(); var i: usize = 0; - i = 0; - while (i < size) : (i += 1) { - switch (new_dict.getSlot(i, key_width, value_width)) { - Slot.Filled => { - inc_key(new_dict.getKey(i, alignment, key_width, value_width)); - inc_value(new_dict.getValue(i, alignment, key_width, value_width)); - }, - else => {}, - } - } + // NOTE we fuse an increment of all keys/values with a decrement of the input dict + decref(allocator, alignment, self.dict_bytes, self.capacity() * slotSize(key_width, value_width)); return new_dict; } @@ -412,7 +370,7 @@ pub const RocDict = extern struct { // hash the key, and modulo by the maximum size // (so we get an in-bounds index) const hash = hash_fn(seed, key); - const index = hash % current_level_size; + const index = capacityOfLevel(current_level - 1) + (hash % current_level_size); switch (self.getSlot(index, key_width, value_width)) { Slot.Empty, Slot.PreviouslyFilled => { @@ -461,20 +419,10 @@ const Inc = fn (?[*]u8) callconv(.C) void; const Dec = fn (?[*]u8) callconv(.C) void; // Dict.insert : Dict k v, k, v -> Dict k v -pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, dec_key: Dec, inc_value: Inc, dec_value: Dec, output: *RocDict) callconv(.C) void { +pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void { var seed: u64 = INITIAL_SEED; - var result: RocDict = blk: { - if (input.isEmpty()) { - break :blk input; - } else { - const in_place = InPlace.Clone; - - var temp = input.makeUnique(std.heap.c_allocator, in_place, alignment, key_width, value_width, inc_key, inc_value); - - break :blk temp; - } - }; + var result = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); var current_level: usize = 1; var current_level_size: usize = 8; @@ -482,11 +430,12 @@ pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: while (true) { if (current_level > result.number_of_levels) { - result = result.reallocate(std.heap.c_allocator, alignment.toUsize(), key_width, value_width); + result = result.reallocate(std.heap.c_allocator, alignment, key_width, value_width); } const hash = hash_fn(seed, key); - const index = hash % current_level_size; + const index = capacityOfLevel(current_level - 1) + (hash % current_level_size); + assert(index < result.capacity()); switch (result.getSlot(index, key_width, value_width)) { Slot.Empty, Slot.PreviouslyFilled => { @@ -528,11 +477,8 @@ pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: output.* = result; } -// { ptr, length, level: u8 } -// [ key1 .. key8, value1, ... - // Dict.remove : Dict k v, k -> Dict k v -pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, dec_key: Dec, inc_value: Inc, dec_value: Dec, output: *RocDict) callconv(.C) void { +pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void { switch (input.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) { MaybeIndex.not_found => { // the key was not found; we're done @@ -540,8 +486,7 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width: return; }, MaybeIndex.index => |index| { - // TODO make sure input is unique (or duplicate otherwise) - var dict = input; + var dict = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); dict.setSlot(index, key_width, value_width, Slot.PreviouslyFilled); const old_key = dict.getKey(index, alignment, key_width, value_width); @@ -600,6 +545,189 @@ pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_w } } +pub const RocList = extern struct { + bytes: ?[*]u8, + length: usize, +}; + +pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_key: Inc, output: *RocList) callconv(.C) void { + const size = dict.capacity(); + + var length: usize = 0; + var i: usize = 0; + while (i < size) : (i += 1) { + switch (dict.getSlot(i, key_width, value_width)) { + Slot.Filled => { + length += 1; + }, + else => {}, + } + } + + if (length == 0) { + output.* = RocList{ .bytes = null, .length = 0 }; + return; + } + + const data_bytes = length * key_width; + var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes); + + var offset = blk: { + if (alignment.keyFirst()) { + break :blk 0; + } else { + break :blk (dict.capacity() * value_width); + } + }; + + i = 0; + var copied: usize = 0; + while (i < size) : (i += 1) { + switch (dict.getSlot(i, key_width, value_width)) { + Slot.Filled => { + const key = dict.getKey(i, alignment, key_width, value_width); + inc_key(key); + + const key_cast = @ptrCast([*]const u8, key); + @memcpy(ptr + (copied * key_width), key_cast, key_width); + copied += 1; + }, + else => {}, + } + } + + output.* = RocList{ .bytes = ptr, .length = length }; +} + +pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_value: Inc, output: *RocList) callconv(.C) void { + const size = dict.capacity(); + + var length: usize = 0; + var i: usize = 0; + while (i < size) : (i += 1) { + switch (dict.getSlot(i, key_width, value_width)) { + Slot.Filled => { + length += 1; + }, + else => {}, + } + } + + if (length == 0) { + output.* = RocList{ .bytes = null, .length = 0 }; + return; + } + + const data_bytes = length * value_width; + var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes); + + var offset = blk: { + if (alignment.keyFirst()) { + break :blk (dict.capacity() * key_width); + } else { + break :blk 0; + } + }; + + i = 0; + var copied: usize = 0; + while (i < size) : (i += 1) { + switch (dict.getSlot(i, key_width, value_width)) { + Slot.Filled => { + const value = dict.getValue(i, alignment, key_width, value_width); + inc_value(value); + + const value_cast = @ptrCast([*]const u8, value); + @memcpy(ptr + (copied * value_width), value_cast, value_width); + copied += 1; + }, + else => {}, + } + } + + output.* = RocList{ .bytes = ptr, .length = length }; +} + +fn decref( + allocator: *Allocator, + alignment: Alignment, + bytes_or_null: ?[*]u8, + data_bytes: usize, +) void { + var bytes = bytes_or_null orelse return; + + const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes)); + + const refcount = (usizes - 1)[0]; + const refcount_isize = @bitCast(isize, refcount); + + switch (alignment.toUsize()) { + 8 => { + if (refcount == REFCOUNT_ONE) { + allocator.free((bytes - 8)[0 .. 8 + data_bytes]); + } else if (refcount_isize < 0) { + (usizes - 1)[0] = refcount + 1; + } + }, + 16 => { + if (refcount == REFCOUNT_ONE) { + allocator.free((bytes - 16)[0 .. 16 + data_bytes]); + } else if (refcount_isize < 0) { + (usizes - 1)[0] = refcount + 1; + } + }, + else => unreachable, + } +} + +fn allocateWithRefcount( + allocator: *Allocator, + alignment: Alignment, + data_bytes: usize, +) [*]u8 { + comptime const result_in_place = InPlace.Clone; + + switch (alignment.toUsize()) { + 8 => { + const length = @sizeOf(usize) + data_bytes; + + var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable; + + var as_usize_array = @ptrCast([*]usize, new_bytes); + if (result_in_place == InPlace.InPlace) { + as_usize_array[0] = @intCast(usize, number_of_slots); + } else { + as_usize_array[0] = REFCOUNT_ONE; + } + + var as_u8_array = @ptrCast([*]u8, new_bytes); + const first_slot = as_u8_array + @sizeOf(usize); + + return first_slot; + }, + 16 => { + const length = 2 * @sizeOf(usize) + data_bytes; + + var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable; + + var as_usize_array = @ptrCast([*]usize, new_bytes); + if (result_in_place == InPlace.InPlace) { + as_usize_array[0] = 0; + as_usize_array[1] = @intCast(usize, number_of_slots); + } else { + as_usize_array[0] = 0; + as_usize_array[1] = REFCOUNT_ONE; + } + + var as_u8_array = @ptrCast([*]u8, new_bytes); + const first_slot = as_u8_array + 2 * @sizeOf(usize); + + return first_slot; + }, + else => unreachable, + } +} + test "RocDict.init() contains nothing" { const key_size = @sizeOf(usize); const value_size = @sizeOf(usize); diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index d2ede712d7..7bad53bdfa 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -14,6 +14,8 @@ comptime { exportDictFn(dict.dictContains, "contains"); exportDictFn(dict.dictGet, "get"); exportDictFn(dict.elementsRc, "elementsRc"); + exportDictFn(dict.dictKeys, "keys"); + exportDictFn(dict.dictValues, "values"); exportDictFn(hash.wyhash, "hash"); exportDictFn(hash.wyhash_rocstr, "hash_str"); diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index cfed50779b..176ff235a2 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -44,3 +44,5 @@ pub const DICT_REMOVE: &str = "roc_builtins.dict.remove"; pub const DICT_CONTAINS: &str = "roc_builtins.dict.contains"; pub const DICT_GET: &str = "roc_builtins.dict.get"; pub const DICT_ELEMENTS_RC: &str = "roc_builtins.dict.elementsRc"; +pub const DICT_KEYS: &str = "roc_builtins.dict.keys"; +pub const DICT_VALUES: &str = "roc_builtins.dict.values"; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index c6b1820a16..eefb0cc746 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1990,7 +1990,6 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def { let arg_key = Symbol::ARG_2; let temp_record = Symbol::ARG_3; - let temp_flag = Symbol::ARG_4; let bool_var = var_store.fresh(); let flag_var = var_store.fresh(); diff --git a/compiler/gen/src/llvm/build_dict.rs b/compiler/gen/src/llvm/build_dict.rs index 5c06cba419..e5cfa5be89 100644 --- a/compiler/gen/src/llvm/build_dict.rs +++ b/compiler/gen/src/llvm/build_dict.rs @@ -3,7 +3,7 @@ use crate::llvm::build::{ call_bitcode_fn, call_void_bitcode_fn, complex_bitcast, load_symbol, load_symbol_and_layout, set_name, Env, Scope, }; -use crate::llvm::convert::{as_const_zero, basic_type_from_layout}; +use crate::llvm::convert::{as_const_zero, basic_type_from_layout, collection}; use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout, Mode}; use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::types::BasicType; @@ -128,10 +128,7 @@ pub fn dict_insert<'a, 'ctx, 'env>( let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); - let inc_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Inc(1)); let dec_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Dec); - - let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Inc(1)); let dec_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Dec); call_void_bitcode_fn( @@ -145,9 +142,7 @@ pub fn dict_insert<'a, 'ctx, 'env>( value_width.into(), hash_fn.as_global_value().as_pointer_value().into(), eq_fn.as_global_value().as_pointer_value().into(), - inc_key_fn.as_global_value().as_pointer_value().into(), dec_key_fn.as_global_value().as_pointer_value().into(), - inc_value_fn.as_global_value().as_pointer_value().into(), dec_value_fn.as_global_value().as_pointer_value().into(), result_ptr.into(), ], @@ -200,10 +195,7 @@ pub fn dict_remove<'a, 'ctx, 'env>( let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); - let inc_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Inc(1)); let dec_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Dec); - - let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Inc(1)); let dec_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Dec); call_void_bitcode_fn( @@ -216,9 +208,7 @@ pub fn dict_remove<'a, 'ctx, 'env>( value_width.into(), hash_fn.as_global_value().as_pointer_value().into(), eq_fn.as_global_value().as_pointer_value().into(), - inc_key_fn.as_global_value().as_pointer_value().into(), dec_key_fn.as_global_value().as_pointer_value().into(), - inc_value_fn.as_global_value().as_pointer_value().into(), dec_value_fn.as_global_value().as_pointer_value().into(), result_ptr.into(), ], @@ -451,7 +441,53 @@ pub fn dict_keys<'a, 'ctx, 'env>( key_layout: &Layout<'a>, value_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - todo!() + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + let zig_list_type = env.module.get_struct_type("dict.RocList").unwrap(); + + let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr"); + env.builder + .build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value())); + + let key_width = env + .ptr_int() + .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + + let value_width = env + .ptr_int() + .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment_iv = env.context.i8_type().const_int(alignment as u64, false); + + let inc_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Inc(1)); + + let list_ptr = builder.build_alloca(zig_list_type, "list_ptr"); + + call_void_bitcode_fn( + env, + &[ + dict_ptr.into(), + alignment_iv.into(), + key_width.into(), + value_width.into(), + inc_key_fn.as_global_value().as_pointer_value().into(), + list_ptr.into(), + ], + &bitcode::DICT_KEYS, + ); + + let list_ptr = env + .builder + .build_bitcast( + list_ptr, + collection(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic), + "to_roc_list", + ) + .into_pointer_value(); + + env.builder.build_load(list_ptr, "load_keys_list") } #[allow(clippy::too_many_arguments)] @@ -462,7 +498,53 @@ pub fn dict_values<'a, 'ctx, 'env>( key_layout: &Layout<'a>, value_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - todo!() + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + let zig_list_type = env.module.get_struct_type("dict.RocList").unwrap(); + + let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr"); + env.builder + .build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value())); + + let key_width = env + .ptr_int() + .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + + let value_width = env + .ptr_int() + .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment_iv = env.context.i8_type().const_int(alignment as u64, false); + + let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Inc(1)); + + let list_ptr = builder.build_alloca(zig_list_type, "list_ptr"); + + call_void_bitcode_fn( + env, + &[ + dict_ptr.into(), + alignment_iv.into(), + key_width.into(), + value_width.into(), + inc_value_fn.as_global_value().as_pointer_value().into(), + list_ptr.into(), + ], + &bitcode::DICT_VALUES, + ); + + let list_ptr = env + .builder + .build_bitcast( + list_ptr, + collection(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic), + "to_roc_list", + ) + .into_pointer_value(); + + env.builder.build_load(list_ptr, "load_keys_list") } fn build_hash_wrapper<'a, 'ctx, 'env>( diff --git a/compiler/gen/tests/gen_dict.rs b/compiler/gen/tests/gen_dict.rs index 12ee27b19d..effb623c98 100644 --- a/compiler/gen/tests/gen_dict.rs +++ b/compiler/gen/tests/gen_dict.rs @@ -149,4 +149,88 @@ mod gen_dict { f64 ); } + + #[test] + fn keys() { + assert_evals_to!( + indoc!( + r#" + myDict : Dict I64 I64 + myDict = + Dict.empty + |> Dict.insert 0 100 + |> Dict.insert 1 100 + |> Dict.insert 2 100 + + + Dict.keys myDict + "# + ), + &[0, 1, 2], + &[i64] + ); + } + + #[test] + fn values() { + assert_evals_to!( + indoc!( + r#" + myDict : Dict I64 I64 + myDict = + Dict.empty + |> Dict.insert 0 100 + |> Dict.insert 1 200 + |> Dict.insert 2 300 + + + Dict.values myDict + "# + ), + &[100, 200, 300], + &[i64] + ); + } + + #[test] + fn from_list_with_fold() { + assert_evals_to!( + indoc!( + r#" + myDict : Dict I64 I64 + myDict = + [1,2,3] + |> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty + + Dict.values myDict + "# + ), + &[2, 3, 1], + &[i64] + ); + + assert_evals_to!( + indoc!( + r#" + range : I64, I64, List I64-> List I64 + range = \low, high, accum -> + if low < high then + range (low + 1) high (List.append accum low) + else + accum + + myDict : Dict I64 I64 + 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 + + Dict.values myDict + |> List.len + "# + ), + 25, + i64 + ); + } } diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 61c48332a4..e6f3f962df 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -275,7 +275,7 @@ pub fn helper<'a>( mode, ); - // fn_val.print_to_stderr(); + fn_val.print_to_stderr(); // module.print_to_stderr(); panic!( diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 2a1ecf05d8..6740206ab4 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -578,7 +578,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListSingle => arena.alloc_slice_copy(&[irrelevant]), ListRepeat => arena.alloc_slice_copy(&[irrelevant, irrelevant]), ListReverse => arena.alloc_slice_copy(&[owned]), - ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListPrepend => arena.alloc_slice_copy(&[owned, owned]), StrJoinWith => arena.alloc_slice_copy(&[irrelevant, irrelevant]), ListJoin => arena.alloc_slice_copy(&[irrelevant]), @@ -589,6 +588,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListWalkBackwards => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), ListSum => arena.alloc_slice_copy(&[borrowed]), + // TODO when we have lists with capacity (if ever) + // List.append should own its first argument + ListAppend => arena.alloc_slice_copy(&[borrowed, owned]), + Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd