diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 3b7b6b58f2..e97c015d09 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -81,12 +81,22 @@ pub fn gen_from_mono_module( // module.strip_debug_info(); // mark our zig-defined builtins as internal + use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::module::Linkage; + + let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); + debug_assert!(kind_id > 0); + let attr = context.create_enum_attribute(kind_id, 1); + for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); if name.starts_with("roc_builtins") { function.set_linkage(Linkage::Internal); } + + if name.starts_with("roc_builtins.dict") || name.starts_with("dict.RocDict") { + function.add_attribute(AttributeLoc::Function, attr); + } } let builder = context.create_builder(); @@ -143,7 +153,7 @@ pub fn gen_from_mono_module( if fn_val.verify(true) { fpm.run_on(&fn_val); } else { - // fn_val.print_to_stderr(); + fn_val.print_to_stderr(); // env.module.print_to_stderr(); // NOTE: If this fails, uncomment the above println to debug. panic!( diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index bda2a7043e..05189d7190 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -4,6 +4,7 @@ const mem = std.mem; const Builder = std.build.Builder; pub fn build(b: *Builder) void { + // b.setPreferredReleaseMode(builtin.Mode.Debug); b.setPreferredReleaseMode(builtin.Mode.ReleaseFast); const mode = b.standardReleaseOptions(); @@ -27,6 +28,7 @@ pub fn build(b: *Builder) void { obj.strip = true; obj.emit_llvm_ir = true; obj.emit_bin = false; + obj.bundle_compiler_rt = true; const ir = b.step("ir", "Build LLVM ir"); ir.dependOn(&obj.step); diff --git a/compiler/builtins/bitcode/src/dict.zig b/compiler/builtins/bitcode/src/dict.zig index 2d2c2230e0..4580e04176 100644 --- a/compiler/builtins/bitcode/src/dict.zig +++ b/compiler/builtins/bitcode/src/dict.zig @@ -3,8 +3,11 @@ 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; +const INITIAL_SEED = 0xc70f6907; +const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize); +const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE); const InPlace = packed enum(u8) { InPlace, @@ -17,82 +20,179 @@ const Slot = packed enum(u8) { PreviouslyFilled, }; +const MaybeIndexTag = enum { + index, not_found +}; + +const MaybeIndex = union(MaybeIndexTag) { + index: usize, not_found: void +}; + +fn nextSeed(seed: u64) u64 { + // TODO is this a valid way to get a new seed? are there better ways? + return seed + 1; +} + +fn totalCapacityAtLevel(input: usize) usize { + if (input == 0) { + return 0; + } + + var n = input; + var slots: usize = 8; + + while (n > 1) : (n -= 1) { + slots = slots * 2 + slots; + } + + return slots; +} + +fn capacityOfLevel(input: usize) usize { + if (input == 0) { + return 0; + } + + var n = input; + var slots: usize = 8; + + while (n > 1) : (n -= 1) { + slots = slots * 2; + } + + return slots; +} + +// aligmnent of elements. The number (16 or 8) indicates the maximum +// alignment of the key and value. The tag furthermore indicates +// which has the biggest aligmnent. If both are the same, we put +// the key first +const Alignment = packed enum(u8) { + Align16KeyFirst, + Align16ValueFirst, + Align8KeyFirst, + Align8ValueFirst, + + fn toUsize(self: Alignment) usize { + switch (self) { + .Align16KeyFirst => return 16, + .Align16ValueFirst => return 16, + .Align8KeyFirst => return 8, + .Align8ValueFirst => return 8, + } + } + + fn keyFirst(self: Alignment) bool { + switch (self) { + .Align16KeyFirst => return true, + .Align16ValueFirst => return false, + .Align8KeyFirst => return true, + .Align8ValueFirst => return false, + } + } +}; + pub const RocDict = extern struct { dict_bytes: ?[*]u8, - dict_slot_len: usize, dict_entries_len: usize, + number_of_levels: usize, pub fn empty() RocDict { return RocDict{ .dict_entries_len = 0, - .dict_slot_len = 0, + .number_of_levels = 0, .dict_bytes = null, }; } - pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, number_of_slots: usize, number_of_entries: usize, key_size: usize, value_size: usize) RocDict { - var result = RocDict.allocate( - allocator, - InPlace.Clone, - number_of_slots, - number_of_entries, - key_size, - value_size, - ); - - @memcpy(result.asU8ptr(), bytes_ptr, number_of_slots); - - return result; - } - - 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.dict_slot_len)]; - allocator.free(dict_bytes); - } - } - pub fn allocate( allocator: *Allocator, - result_in_place: InPlace, - number_of_slots: usize, + number_of_levels: usize, number_of_entries: usize, + alignment: Alignment, key_size: usize, value_size: usize, ) RocDict { + const number_of_slots = totalCapacityAtLevel(number_of_levels); const slot_size = slotSize(key_size, value_size); - - const length = @sizeOf(usize) + (number_of_slots * slot_size); - - var new_bytes: []usize = allocator.alloc(usize, length) catch unreachable; - - if (result_in_place == InPlace.InPlace) { - new_bytes[0] = @intCast(usize, number_of_slots); - } else { - const v: isize = std.math.minInt(isize); - new_bytes[0] = @bitCast(usize, v); - } - - var first_slot = @ptrCast([*]align(@alignOf(usize)) u8, new_bytes); - first_slot += @sizeOf(usize); + const data_bytes = number_of_slots * slot_size; return RocDict{ - .dict_bytes = first_slot, - .dict_slot_len = number_of_slots, + .dict_bytes = allocateWithRefcount(allocator, alignment, data_bytes), + .number_of_levels = number_of_levels, .dict_entries_len = number_of_entries, }; } - pub fn asU8ptr(self: RocDict) [*]u8 { - return @ptrCast([*]u8, self.dict_bytes); + pub fn reallocate( + self: RocDict, + allocator: *Allocator, + alignment: Alignment, + key_width: usize, + value_width: usize, + ) RocDict { + const new_level = self.number_of_levels + 1; + const slot_size = slotSize(key_width, value_width); + + const old_capacity = self.capacity(); + const new_capacity = totalCapacityAtLevel(new_level); + const delta_capacity = new_capacity - old_capacity; + + const data_bytes = new_capacity * slot_size; + const first_slot = allocateWithRefcount(allocator, alignment, data_bytes); + + // transfer the memory + + if (self.dict_bytes) |source_ptr| { + const dest_ptr = first_slot; + + var source_offset: usize = 0; + var dest_offset: usize = 0; + + if (alignment.keyFirst()) { + // copy keys + @memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_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 = first_slot + 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); + } + + 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 contains(self: RocDict, key_size: usize, key_ptr: *const c_void, hash_code: u64) bool { - return false; + pub fn asU8ptr(self: RocDict) [*]u8 { + return @ptrCast([*]u8, self.dict_bytes); } pub fn len(self: RocDict) usize { @@ -103,16 +203,163 @@ pub const RocDict = extern struct { return self.len() == 0; } - pub fn clone(self: RocDict, allocator: *Allocator, in_place: InPlace, key_size: usize, value_size: usize) RocDict { - var new_dict = RocDict.init(allocator, self.dict_slot_len, self.dict_entries_len, key_size, value_size); + 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 totalCapacityAtLevel(self.number_of_levels); + } + + pub fn makeUnique(self: RocDict, allocator: *Allocator, alignment: Alignment, key_width: usize, value_width: usize) RocDict { + if (self.isEmpty()) { + return self; + } + + if (self.isUnique()) { + return self; + } + + // unfortunately, we have to clone + + 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); - @memcpy(new_bytes, old_bytes, self.dict_slot_len); + const number_of_bytes = self.capacity() * (@sizeOf(Slot) + key_width + value_width); + @memcpy(new_bytes, old_bytes, number_of_bytes); + + // NOTE we fuse an increment of all keys/values with a decrement of the input dict + const data_bytes = self.capacity() * slotSize(key_width, value_width); + decref(allocator, alignment, self.dict_bytes, data_bytes); return new_dict; } + + 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); + + 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); + + 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 { + const offset = blk: { + if (alignment.keyFirst()) { + break :blk (index * key_width); + } else { + break :blk (self.capacity() * value_width) + (index * key_width); + } + }; + + 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 { + const offset = blk: { + if (alignment.keyFirst()) { + break :blk (index * key_width); + } else { + break :blk (self.capacity() * value_width) + (index * key_width); + } + }; + + 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 { + const offset = blk: { + if (alignment.keyFirst()) { + break :blk (self.capacity() * key_width) + (index * value_width); + } else { + break :blk (index * value_width); + } + }; + + 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 { + const offset = blk: { + if (alignment.keyFirst()) { + break :blk (self.capacity() * key_width) + (index * value_width); + } else { + break :blk (index * value_width); + } + }; + + 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 { + if (self.isEmpty()) { + return MaybeIndex.not_found; + } + + var seed: u64 = INITIAL_SEED; + + var current_level: usize = 1; + var current_level_size: usize = 8; + var next_level_size: usize = 2 * current_level_size; + + while (true) { + if (current_level > self.number_of_levels) { + return MaybeIndex.not_found; + } + + // hash the key, and modulo by the maximum size + // (so we get an in-bounds index) + const hash = hash_fn(seed, key); + const index = capacityOfLevel(current_level - 1) + (hash % current_level_size); + + switch (self.getSlot(index, key_width, value_width)) { + Slot.Empty, Slot.PreviouslyFilled => { + return MaybeIndex.not_found; + }, + Slot.Filled => { + // is this the same key, or a new key? + const current_key = self.getKey(index, alignment, key_width, value_width); + + if (is_eq(key, current_key)) { + return MaybeIndex{ .index = index }; + } else { + current_level += 1; + current_level_size *= 2; + next_level_size *= 2; + + seed = nextSeed(seed); + continue; + } + }, + } + } + } }; // Dict.empty @@ -129,11 +376,327 @@ pub fn dictLen(dict: RocDict) callconv(.C) usize { return dict.dict_entries_len; } -test "RocDict.init() contains nothing" { - const key_size = @sizeOf(usize); - const value_size = @sizeOf(usize); +// commonly used type aliases +const Opaque = ?[*]u8; +const HashFn = fn (u64, ?[*]u8) callconv(.C) u64; +const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; - const dict = dictEmpty(); +const Inc = fn (?[*]u8) callconv(.C) void; +const Dec = fn (?[*]u8) callconv(.C) void; - expectEqual(false, dict.contains(4, @ptrCast(*const c_void, &""), 9)); +// 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, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void { + var seed: u64 = INITIAL_SEED; + + var result = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); + + var current_level: usize = 1; + var current_level_size: usize = 8; + var next_level_size: usize = 2 * current_level_size; + + while (true) { + if (current_level > result.number_of_levels) { + result = result.reallocate(std.heap.c_allocator, alignment, key_width, value_width); + } + + const hash = hash_fn(seed, key); + 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 => { + result.setSlot(index, key_width, value_width, Slot.Filled); + result.setKey(index, alignment, key_width, value_width, key); + result.setValue(index, alignment, key_width, value_width, value); + + result.dict_entries_len += 1; + break; + }, + Slot.Filled => { + // is this the same key, or a new key? + const current_key = result.getKey(index, alignment, key_width, value_width); + + if (is_eq(key, current_key)) { + // we will override the old value, but first have to decrement its refcount + const current_value = result.getValue(index, alignment, key_width, value_width); + dec_value(current_value); + + // we must consume the key argument! + dec_key(key); + + result.setValue(index, alignment, key_width, value_width, value); + break; + } else { + seed = nextSeed(seed); + + current_level += 1; + current_level_size *= 2; + next_level_size *= 2; + + continue; + } + }, + } + } + + // write result into pointer + output.* = result; +} + +// 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, 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 + output.* = input; + return; + }, + MaybeIndex.index => |index| { + var dict = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width); + + assert(index < dict.capacity()); + + dict.setSlot(index, key_width, value_width, Slot.PreviouslyFilled); + const old_key = dict.getKey(index, alignment, key_width, value_width); + const old_value = dict.getValue(index, alignment, key_width, value_width); + + dec_key(old_key); + dec_value(old_value); + dict.dict_entries_len -= 1; + + // if the dict is now completely empty, free its allocation + if (dict.dict_entries_len == 0) { + const data_bytes = dict.capacity() * slotSize(key_width, value_width); + decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes); + } + + output.* = dict; + }, + } +} + +// Dict.contains : Dict k v, k -> Bool +pub fn dictContains(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) callconv(.C) bool { + switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) { + MaybeIndex.not_found => { + return false; + }, + MaybeIndex.index => |_| { + return true; + }, + } +} + +// Dict.get : Dict k v, k -> { flag: bool, value: Opaque } +pub fn dictGet(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_value: Inc) callconv(.C) extern struct { value: Opaque, flag: bool } { + switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) { + MaybeIndex.not_found => { + return .{ .flag = false, .value = null }; + }, + MaybeIndex.index => |index| { + var value = dict.getValue(index, alignment, key_width, value_width); + inc_value(value); + return .{ .flag = true, .value = value }; + }, + } +} + +// Dict.elementsRc +// increment or decrement all dict elements (but not the dict's allocation itself) +pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, modify_key: Inc, modify_value: Inc) callconv(.C) void { + const size = dict.capacity(); + + var i: usize = 0; + while (i < size) : (i += 1) { + switch (dict.getSlot(i, key_width, value_width)) { + Slot.Filled => { + modify_key(dict.getKey(i, alignment, key_width, value_width)); + modify_value(dict.getValue(i, alignment, key_width, value_width)); + }, + else => {}, + } + } +} + +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, + } } diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index ef4e733d06..7bad53bdfa 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -9,6 +9,14 @@ const hash = @import("hash.zig"); comptime { exportDictFn(dict.dictLen, "len"); exportDictFn(dict.dictEmpty, "empty"); + exportDictFn(dict.dictInsert, "insert"); + exportDictFn(dict.dictRemove, "remove"); + 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 f549c572e1..176ff235a2 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -39,3 +39,10 @@ pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; pub const DICT_LEN: &str = "roc_builtins.dict.len"; pub const DICT_EMPTY: &str = "roc_builtins.dict.empty"; +pub const DICT_INSERT: &str = "roc_builtins.dict.insert"; +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/builtins/src/std.rs b/compiler/builtins/src/std.rs index 180448a8cd..7d66ed2d6c 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -770,6 +770,7 @@ pub fn types() -> MutMap { ), ); + // Dict.insert : Dict k v, k, v -> Dict k v add_type( Symbol::DICT_INSERT, top_level_function( @@ -782,6 +783,42 @@ pub fn types() -> MutMap { ), ); + // Dict.remove : Dict k v, k -> Dict k v + add_type( + Symbol::DICT_REMOVE, + top_level_function( + vec![dict_type(flex(TVAR1), flex(TVAR2)), flex(TVAR1)], + Box::new(dict_type(flex(TVAR1), flex(TVAR2))), + ), + ); + + // Dict.contains : Dict k v, k -> Bool + add_type( + Symbol::DICT_CONTAINS, + top_level_function( + vec![dict_type(flex(TVAR1), flex(TVAR2)), flex(TVAR1)], + Box::new(bool_type()), + ), + ); + + // Dict.keys : Dict k v -> List k + add_type( + Symbol::DICT_KEYS, + top_level_function( + vec![dict_type(flex(TVAR1), flex(TVAR2))], + Box::new(list_type(flex(TVAR1))), + ), + ); + + // Dict.values : Dict k v -> List v + add_type( + Symbol::DICT_VALUES, + top_level_function( + vec![dict_type(flex(TVAR1), flex(TVAR2))], + Box::new(list_type(flex(TVAR2))), + ), + ); + // Set module // empty : Set a diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 020c119f19..c62d0a37c9 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -83,6 +83,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option DICT_LEN => dict_len, DICT_EMPTY => dict_empty, DICT_INSERT => dict_insert, + DICT_REMOVE => dict_remove, + DICT_GET => dict_get, + DICT_CONTAINS => dict_contains, + DICT_KEYS => dict_keys, + DICT_VALUES => dict_values, NUM_ADD => num_add, NUM_ADD_CHECKED => num_add_checked, NUM_ADD_WRAP => num_add_wrap, @@ -182,6 +187,11 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::DICT_LEN => dict_len, Symbol::DICT_EMPTY => dict_empty, Symbol::DICT_INSERT => dict_insert, + Symbol::DICT_REMOVE => dict_remove, + Symbol::DICT_GET => dict_get, + Symbol::DICT_CONTAINS => dict_contains, + Symbol::DICT_KEYS => dict_keys, + Symbol::DICT_VALUES => dict_values, Symbol::NUM_ADD => num_add, Symbol::NUM_ADD_CHECKED => num_add_checked, Symbol::NUM_ADD_WRAP => num_add_wrap, @@ -1927,6 +1937,174 @@ fn dict_insert(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Dict.remove : Dict k v, k -> Dict k v +fn dict_remove(symbol: Symbol, var_store: &mut VarStore) -> Def { + let dict_var = var_store.fresh(); + let key_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::DictRemove, + args: vec![ + (dict_var, Var(Symbol::ARG_1)), + (key_var, Var(Symbol::ARG_2)), + ], + ret_var: dict_var, + }; + + defn( + symbol, + vec![(dict_var, Symbol::ARG_1), (key_var, Symbol::ARG_2)], + var_store, + body, + dict_var, + ) +} + +/// Dict.contains : Dict k v, k -> Bool +fn dict_contains(symbol: Symbol, var_store: &mut VarStore) -> Def { + let dict_var = var_store.fresh(); + let key_var = var_store.fresh(); + let bool_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::DictContains, + args: vec![ + (dict_var, Var(Symbol::ARG_1)), + (key_var, Var(Symbol::ARG_2)), + ], + ret_var: bool_var, + }; + + defn( + symbol, + vec![(dict_var, Symbol::ARG_1), (key_var, Symbol::ARG_2)], + var_store, + body, + bool_var, + ) +} + +/// Dict.get : Dict k v, k -> Result v [ KeyNotFound ]* +fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_dict = Symbol::ARG_1; + let arg_key = Symbol::ARG_2; + + let temp_record = Symbol::ARG_3; + + let bool_var = var_store.fresh(); + let flag_var = var_store.fresh(); + let key_var = var_store.fresh(); + let dict_var = var_store.fresh(); + let value_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let temp_record_var = var_store.fresh(); + let ext_var1 = var_store.fresh(); + let ext_var2 = var_store.fresh(); + + // NOTE DictGetUnsafe returns a { flag: Bool, value: v } + // when the flag is True, the value is found and defined; + // otherwise it is not and `Dict.get` should return `Err ...` + let def_body = RunLowLevel { + op: LowLevel::DictGetUnsafe, + args: vec![(dict_var, Var(arg_dict)), (key_var, Var(arg_key))], + ret_var: temp_record_var, + }; + + let def = Def { + annotation: None, + expr_var: temp_record_var, + loc_expr: Located::at_zero(def_body), + loc_pattern: Located::at_zero(Pattern::Identifier(temp_record)), + pattern_vars: Default::default(), + }; + + let get_value = Access { + record_var: temp_record_var, + ext_var: ext_var1, + field_var: value_var, + loc_expr: Box::new(no_region(Var(temp_record))), + field: "value".into(), + }; + + let get_flag = Access { + record_var: temp_record_var, + ext_var: ext_var2, + field_var: flag_var, + loc_expr: Box::new(no_region(Var(temp_record))), + field: "zflag".into(), + }; + + let make_ok = tag("Ok", vec![get_value], var_store); + + let make_err = tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ); + + let inspect = If { + cond_var: bool_var, + branch_var: ret_var, + branches: vec![( + // if-condition + no_region(get_flag), + no_region(make_ok), + )], + final_else: Box::new(no_region(make_err)), + }; + + let body = LetNonRec(Box::new(def), Box::new(no_region(inspect)), ret_var); + + defn( + symbol, + vec![(dict_var, Symbol::ARG_1), (key_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + +/// Dict.keys : Dict k v -> List k +fn dict_keys(symbol: Symbol, var_store: &mut VarStore) -> Def { + let dict_var = var_store.fresh(); + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::DictKeys, + args: vec![(dict_var, Var(Symbol::ARG_1))], + ret_var: list_var, + }; + + defn( + symbol, + vec![(dict_var, Symbol::ARG_1)], + var_store, + body, + list_var, + ) +} + +/// Dict.values : Dict k v -> List v +fn dict_values(symbol: Symbol, var_store: &mut VarStore) -> Def { + let dict_var = var_store.fresh(); + let list_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::DictValues, + args: vec![(dict_var, Var(Symbol::ARG_1))], + ret_var: list_var, + }; + + defn( + symbol, + vec![(dict_var, Symbol::ARG_1)], + var_store, + body, + list_var, + ) +} + /// Num.rem : Int, Int -> Result Int [ DivByZero ]* fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 30dcc86ba9..7a12157460 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1,4 +1,6 @@ -use crate::llvm::build_dict::{dict_empty, dict_insert, dict_len}; +use crate::llvm::build_dict::{ + dict_contains, dict_empty, dict_get, dict_insert, dict_keys, dict_len, dict_remove, dict_values, +}; use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, @@ -55,6 +57,30 @@ const PRINT_FN_VERIFICATION_OUTPUT: bool = true; #[cfg(not(debug_assertions))] const PRINT_FN_VERIFICATION_OUTPUT: bool = false; +#[macro_export] +macro_rules! debug_info_init { + ($env:expr, $function_value:expr) => {{ + use inkwell::debug_info::AsDIScope; + + let func_scope = $function_value.get_subprogram().unwrap(); + let lexical_block = $env.dibuilder.create_lexical_block( + /* scope */ func_scope.as_debug_info_scope(), + /* file */ $env.compile_unit.get_file(), + /* line_no */ 0, + /* column_no */ 0, + ); + + let loc = $env.dibuilder.create_debug_location( + $env.context, + /* line */ 0, + /* column */ 0, + /* current_scope */ lexical_block.as_debug_info_scope(), + /* inlined_at */ None, + ); + $env.builder.set_current_debug_location(&$env.context, loc); + }}; +} + #[derive(Debug, Clone, Copy)] pub enum OptLevel { Normal, @@ -402,13 +428,15 @@ pub fn construct_optimization_passes<'a>( let mpm = PassManager::create(()); let fpm = PassManager::create(module); + // remove unused global values (e.g. those defined by zig, but unused in user code) + mpm.add_global_dce_pass(); + + mpm.add_always_inliner_pass(); + // tail-call elimination is always on fpm.add_instruction_combining_pass(); fpm.add_tail_call_elimination_pass(); - // remove unused global values (e.g. those defined by zig, but unused in user code) - mpm.add_global_dce_pass(); - let pmb = PassManagerBuilder::create(); match opt_level { OptLevel::Normal => { @@ -2690,22 +2718,7 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = c_function.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - env.context, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(env.context, loc); + debug_info_init!(env, c_function); // drop the final argument, which is the pointer we write the result into let args = c_function.get_params(); @@ -2747,22 +2760,7 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = size_function.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - env.context, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(env.context, loc); + debug_info_init!(env, size_function); let size: BasicValueEnum = return_type.size_of().unwrap().into(); builder.build_return(Some(&size)); @@ -2974,22 +2972,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( let basic_block = context.append_basic_block(wrapper_function, "entry"); builder.position_at_end(basic_block); - let func_scope = wrapper_function.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - env.context, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(env.context, loc); + debug_info_init!(env, wrapper_function); let result = invoke_and_catch( env, @@ -3389,23 +3372,7 @@ pub fn build_proc<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = fn_val.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - context, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - - builder.set_current_debug_location(&context, loc); + debug_info_init!(env, fn_val); // Add args to scope for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) { @@ -4038,7 +4005,90 @@ fn run_low_level<'a, 'ctx, 'env>( let (dict, _) = load_symbol_and_layout(scope, &args[0]); let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); let (value, value_layout) = load_symbol_and_layout(scope, &args[2]); - dict_insert(env, scope, dict, key, key_layout, value, value_layout) + dict_insert(env, layout_ids, dict, key, key_layout, value, value_layout) + } + DictRemove => { + debug_assert_eq!(args.len(), 2); + + let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); + let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); + + match dict_layout { + Layout::Builtin(Builtin::EmptyDict) => { + // no elements, so nothing to remove + dict + } + Layout::Builtin(Builtin::Dict(_, value_layout)) => { + dict_remove(env, layout_ids, dict, key, key_layout, value_layout) + } + _ => unreachable!("invalid dict layout"), + } + } + DictContains => { + debug_assert_eq!(args.len(), 2); + + let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); + let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); + + match dict_layout { + Layout::Builtin(Builtin::EmptyDict) => { + // no elements, so `key` is not in here + env.context.bool_type().const_zero().into() + } + Layout::Builtin(Builtin::Dict(_, value_layout)) => { + dict_contains(env, layout_ids, dict, key, key_layout, value_layout) + } + _ => unreachable!("invalid dict layout"), + } + } + DictGetUnsafe => { + debug_assert_eq!(args.len(), 2); + + let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); + let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); + + match dict_layout { + Layout::Builtin(Builtin::EmptyDict) => { + unreachable!("we can't make up a layout for the return value"); + // in other words, make sure to check whether the dict is empty first + } + Layout::Builtin(Builtin::Dict(_, value_layout)) => { + dict_get(env, layout_ids, dict, key, key_layout, value_layout) + } + _ => unreachable!("invalid dict layout"), + } + } + DictKeys => { + debug_assert_eq!(args.len(), 1); + + let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); + + match dict_layout { + Layout::Builtin(Builtin::EmptyDict) => { + // no elements, so `key` is not in here + panic!("key type unknown") + } + Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { + dict_keys(env, layout_ids, dict, key_layout, value_layout) + } + _ => unreachable!("invalid dict layout"), + } + } + DictValues => { + debug_assert_eq!(args.len(), 1); + + let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); + + match dict_layout { + Layout::Builtin(Builtin::EmptyDict) => { + // no elements, so `key` is not in here + panic!("key type unknown") + } + Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { + dict_values(env, layout_ids, dict, key_layout, value_layout) + } + _ => unreachable!("invalid dict layout"), + } } } } diff --git a/compiler/gen/src/llvm/build_dict.rs b/compiler/gen/src/llvm/build_dict.rs index 99f5f40208..3b8cd5feed 100644 --- a/compiler/gen/src/llvm/build_dict.rs +++ b/compiler/gen/src/llvm/build_dict.rs @@ -1,14 +1,46 @@ +use crate::debug_info_init; use crate::llvm::build::{ call_bitcode_fn, call_void_bitcode_fn, complex_bitcast, load_symbol, load_symbol_and_layout, - Env, Scope, + set_name, Env, Scope, }; -use crate::llvm::convert::collection; -use inkwell::types::BasicTypeEnum; -use inkwell::values::{BasicValueEnum, IntValue, StructValue}; +use crate::llvm::convert::{self, 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; +use inkwell::values::{BasicValueEnum, FunctionValue, StructValue}; use inkwell::AddressSpace; use roc_builtins::bitcode; use roc_module::symbol::Symbol; -use roc_mono::layout::{Builtin, Layout}; +use roc_mono::layout::{Builtin, Layout, LayoutIds}; + +#[repr(u8)] +enum Alignment { + Align16KeyFirst = 0, + Align16ValueFirst = 1, + Align8KeyFirst = 2, + Align8ValueFirst = 3, +} + +impl Alignment { + fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment { + let key_align = key.alignment_bytes(ptr_bytes); + let value_align = value.alignment_bytes(ptr_bytes); + + if key_align >= value_align { + match key_align.max(value_align) { + 8 => Alignment::Align8KeyFirst, + 16 => Alignment::Align16KeyFirst, + _ => unreachable!(), + } + } else { + match key_align.max(value_align) { + 8 => Alignment::Align8ValueFirst, + 16 => Alignment::Align16ValueFirst, + _ => unreachable!(), + } + } + } +} pub fn dict_len<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -21,9 +53,15 @@ pub fn dict_len<'a, 'ctx, 'env>( match dict_layout { Layout::Builtin(Builtin::Dict(_, _)) => { - let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol); + // let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol); + let dict_as_zig_dict = dict_symbol_to_zig_dict(env, scope, dict_symbol); - call_bitcode_fn(env, &[dict_as_int.into()], &bitcode::DICT_LEN) + let dict_ptr = env + .builder + .build_alloca(dict_as_zig_dict.get_type(), "dict_ptr"); + env.builder.build_store(dict_ptr, dict_as_zig_dict); + + call_bitcode_fn(env, &[dict_ptr.into()], &bitcode::DICT_LEN) } Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(), _ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout), @@ -50,61 +88,730 @@ pub fn dict_empty<'a, 'ctx, 'env>( zig_dict_to_struct(env, result).into() } +#[allow(clippy::too_many_arguments)] pub fn dict_insert<'a, 'ctx, 'env>( - _env: &Env<'a, 'ctx, 'env>, - _scope: &Scope<'a, 'ctx>, - _dict: BasicValueEnum<'ctx>, - _key: BasicValueEnum<'ctx>, - _key_layout: &Layout<'a>, - _value: BasicValueEnum<'ctx>, - _value_layout: &Layout<'a>, + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value: BasicValueEnum<'ctx>, + value_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - todo!() + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr"); + let key_ptr = builder.build_alloca(key.get_type(), "key_ptr"); + let value_ptr = builder.build_alloca(value.get_type(), "value_ptr"); + + env.builder + .build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value())); + env.builder.build_store(key_ptr, key); + env.builder.build_store(value_ptr, 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 result_ptr = builder.build_alloca(zig_dict_type, "result_ptr"); + + 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout); + let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); + + let dec_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Dec); + let dec_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Dec); + + call_void_bitcode_fn( + env, + &[ + dict_ptr.into(), + alignment_iv.into(), + env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), + key_width.into(), + env.builder.build_bitcast(value_ptr, u8_ptr, "to_u8_ptr"), + value_width.into(), + hash_fn.as_global_value().as_pointer_value().into(), + eq_fn.as_global_value().as_pointer_value().into(), + dec_key_fn.as_global_value().as_pointer_value().into(), + dec_value_fn.as_global_value().as_pointer_value().into(), + result_ptr.into(), + ], + &bitcode::DICT_INSERT, + ); + + let result_ptr = env + .builder + .build_bitcast( + result_ptr, + convert::dict(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic), + "to_roc_dict", + ) + .into_pointer_value(); + + env.builder.build_load(result_ptr, "load_result") } -fn dict_symbol_to_i128<'a, 'ctx, 'env>( +#[allow(clippy::too_many_arguments)] +pub fn dict_remove<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr"); + let key_ptr = builder.build_alloca(key.get_type(), "key_ptr"); + + env.builder + .build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value())); + env.builder.build_store(key_ptr, key); + + 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 result_ptr = builder.build_alloca(zig_dict_type, "result_ptr"); + + 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout); + let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); + + let dec_key_fn = build_rc_wrapper(env, layout_ids, key_layout, Mode::Dec); + let dec_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Dec); + + call_void_bitcode_fn( + env, + &[ + dict_ptr.into(), + alignment_iv.into(), + env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), + key_width.into(), + value_width.into(), + hash_fn.as_global_value().as_pointer_value().into(), + eq_fn.as_global_value().as_pointer_value().into(), + dec_key_fn.as_global_value().as_pointer_value().into(), + dec_value_fn.as_global_value().as_pointer_value().into(), + result_ptr.into(), + ], + &bitcode::DICT_REMOVE, + ); + + let result_ptr = env + .builder + .build_bitcast( + result_ptr, + convert::dict(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic), + "to_roc_dict", + ) + .into_pointer_value(); + + env.builder.build_load(result_ptr, "load_result") +} + +#[allow(clippy::too_many_arguments)] +pub fn dict_contains<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr"); + let key_ptr = builder.build_alloca(key.get_type(), "key_ptr"); + + env.builder + .build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value())); + env.builder.build_store(key_ptr, key); + + 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout); + let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); + + call_bitcode_fn( + env, + &[ + dict_ptr.into(), + alignment_iv.into(), + env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), + key_width.into(), + value_width.into(), + hash_fn.as_global_value().as_pointer_value().into(), + eq_fn.as_global_value().as_pointer_value().into(), + ], + &bitcode::DICT_CONTAINS, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn dict_get<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr"); + let key_ptr = builder.build_alloca(key.get_type(), "key_ptr"); + + env.builder + .build_store(dict_ptr, struct_to_zig_dict(env, dict.into_struct_value())); + env.builder.build_store(key_ptr, key); + + 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 hash_fn = build_hash_wrapper(env, layout_ids, key_layout); + let eq_fn = build_eq_wrapper(env, layout_ids, key_layout); + + let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, Mode::Inc(1)); + + // { flag: bool, value: *const u8 } + let result = call_bitcode_fn( + env, + &[ + dict_ptr.into(), + alignment_iv.into(), + env.builder.build_bitcast(key_ptr, u8_ptr, "to_u8_ptr"), + key_width.into(), + value_width.into(), + hash_fn.as_global_value().as_pointer_value().into(), + eq_fn.as_global_value().as_pointer_value().into(), + inc_value_fn.as_global_value().as_pointer_value().into(), + ], + &bitcode::DICT_GET, + ) + .into_struct_value(); + + let flag = env + .builder + .build_extract_value(result, 1, "get_flag") + .unwrap() + .into_int_value(); + + let value_u8_ptr = env + .builder + .build_extract_value(result, 0, "get_value_ptr") + .unwrap() + .into_pointer_value(); + + let start_block = env.builder.get_insert_block().unwrap(); + let parent = start_block.get_parent().unwrap(); + + let if_not_null = env.context.append_basic_block(parent, "if_not_null"); + let done_block = env.context.append_basic_block(parent, "done"); + + let value_bt = basic_type_from_layout(env.arena, env.context, value_layout, env.ptr_bytes); + let default = as_const_zero(&value_bt); + + env.builder + .build_conditional_branch(flag, if_not_null, done_block); + + env.builder.position_at_end(if_not_null); + let value_ptr = env + .builder + .build_bitcast( + value_u8_ptr, + value_bt.ptr_type(AddressSpace::Generic), + "from_opaque", + ) + .into_pointer_value(); + let loaded = env.builder.build_load(value_ptr, "load_value"); + env.builder.build_unconditional_branch(done_block); + + env.builder.position_at_end(done_block); + let result_phi = env.builder.build_phi(value_bt, "result"); + + result_phi.add_incoming(&[(&default, start_block), (&loaded, if_not_null)]); + + let value = result_phi.as_basic_value(); + + let result = env + .context + .struct_type(&[value_bt, env.context.bool_type().into()], false) + .const_zero(); + + let result = env + .builder + .build_insert_value(result, flag, 1, "insert_flag") + .unwrap(); + + env.builder + .build_insert_value(result, value, 0, "insert_value") + .unwrap() + .into_struct_value() + .into() +} + +#[allow(clippy::too_many_arguments)] +pub fn dict_elements_rc<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, + rc_operation: Mode, +) { + let builder = env.builder; + + let zig_dict_type = env.module.get_struct_type("dict.RocDict").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, rc_operation); + let inc_value_fn = build_rc_wrapper(env, layout_ids, value_layout, rc_operation); + + 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(), + inc_value_fn.as_global_value().as_pointer_value().into(), + ], + &bitcode::DICT_ELEMENTS_RC, + ); +} + +#[allow(clippy::too_many_arguments)] +pub fn dict_keys<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + 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)] +pub fn dict_values<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + dict: BasicValueEnum<'ctx>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + 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>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + layout: &Layout<'a>, +) -> FunctionValue<'ctx> { + let block = env.builder.get_insert_block().expect("to be in a function"); + let di_location = env.builder.get_current_debug_location().unwrap(); + + let symbol = Symbol::GENERIC_HASH_REF; + let fn_name = layout_ids + .get(symbol, &layout) + .to_symbol_string(symbol, &env.interns); + + let function_value = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let seed_type = env.context.i64_type(); + let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let function_value = crate::llvm::refcounting::build_header_help( + env, + &fn_name, + seed_type.into(), + &[seed_type.into(), arg_type.into()], + ); + + let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); + debug_assert!(kind_id > 0); + let attr = env.context.create_enum_attribute(kind_id, 1); + function_value.add_attribute(AttributeLoc::Function, attr); + + let entry = env.context.append_basic_block(function_value, "entry"); + env.builder.position_at_end(entry); + + debug_info_init!(env, function_value); + + let mut it = function_value.get_param_iter(); + let seed_arg = it.next().unwrap().into_int_value(); + let value_ptr = it.next().unwrap().into_pointer_value(); + + set_name(seed_arg.into(), Symbol::ARG_1.ident_string(&env.interns)); + set_name(value_ptr.into(), Symbol::ARG_2.ident_string(&env.interns)); + + let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes) + .ptr_type(AddressSpace::Generic); + + let value_cast = env + .builder + .build_bitcast(value_ptr, value_type, "load_opaque") + .into_pointer_value(); + + let val_arg = env.builder.build_load(value_cast, "load_opaque"); + + let result = + crate::llvm::build_hash::generic_hash(env, layout_ids, seed_arg, val_arg, layout); + + env.builder.build_return(Some(&result)); + + function_value + } + }; + + env.builder.position_at_end(block); + env.builder + .set_current_debug_location(env.context, di_location); + + function_value +} + +fn build_eq_wrapper<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + layout: &Layout<'a>, +) -> FunctionValue<'ctx> { + let block = env.builder.get_insert_block().expect("to be in a function"); + let di_location = env.builder.get_current_debug_location().unwrap(); + + let symbol = Symbol::GENERIC_EQ_REF; + let fn_name = layout_ids + .get(symbol, &layout) + .to_symbol_string(symbol, &env.interns); + + let function_value = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let function_value = crate::llvm::refcounting::build_header_help( + env, + &fn_name, + env.context.bool_type().into(), + &[arg_type.into(), arg_type.into()], + ); + + let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); + debug_assert!(kind_id > 0); + let attr = env.context.create_enum_attribute(kind_id, 1); + function_value.add_attribute(AttributeLoc::Function, attr); + + let entry = env.context.append_basic_block(function_value, "entry"); + env.builder.position_at_end(entry); + + debug_info_init!(env, function_value); + + let mut it = function_value.get_param_iter(); + let value_ptr1 = it.next().unwrap().into_pointer_value(); + let value_ptr2 = it.next().unwrap().into_pointer_value(); + + set_name(value_ptr1.into(), Symbol::ARG_1.ident_string(&env.interns)); + set_name(value_ptr2.into(), Symbol::ARG_2.ident_string(&env.interns)); + + let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes) + .ptr_type(AddressSpace::Generic); + + let value_cast1 = env + .builder + .build_bitcast(value_ptr1, value_type, "load_opaque") + .into_pointer_value(); + + let value_cast2 = env + .builder + .build_bitcast(value_ptr2, value_type, "load_opaque") + .into_pointer_value(); + + let value1 = env.builder.build_load(value_cast1, "load_opaque"); + let value2 = env.builder.build_load(value_cast2, "load_opaque"); + + let result = + crate::llvm::compare::generic_eq(env, layout_ids, value1, value2, layout, layout); + + env.builder.build_return(Some(&result)); + + function_value + } + }; + + env.builder.position_at_end(block); + env.builder + .set_current_debug_location(env.context, di_location); + + function_value +} + +fn build_rc_wrapper<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + layout: &Layout<'a>, + rc_operation: Mode, +) -> FunctionValue<'ctx> { + let block = env.builder.get_insert_block().expect("to be in a function"); + let di_location = env.builder.get_current_debug_location().unwrap(); + + let symbol = Symbol::GENERIC_RC_REF; + let fn_name = layout_ids + .get(symbol, &layout) + .to_symbol_string(symbol, &env.interns); + + let fn_name = match rc_operation { + Mode::Inc(n) => format!("{}_inc_{}", fn_name, n), + Mode::Dec => format!("{}_dec", fn_name), + }; + + let function_value = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let function_value = crate::llvm::refcounting::build_header_help( + env, + &fn_name, + env.context.void_type().into(), + &[arg_type.into()], + ); + + let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); + debug_assert!(kind_id > 0); + let attr = env.context.create_enum_attribute(kind_id, 1); + function_value.add_attribute(AttributeLoc::Function, attr); + + let entry = env.context.append_basic_block(function_value, "entry"); + env.builder.position_at_end(entry); + + debug_info_init!(env, function_value); + + let mut it = function_value.get_param_iter(); + let value_ptr = it.next().unwrap().into_pointer_value(); + + set_name(value_ptr.into(), Symbol::ARG_1.ident_string(&env.interns)); + + let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes) + .ptr_type(AddressSpace::Generic); + + let value_cast = env + .builder + .build_bitcast(value_ptr, value_type, "load_opaque") + .into_pointer_value(); + + let value = env.builder.build_load(value_cast, "load_opaque"); + + match rc_operation { + Mode::Inc(n) => { + increment_refcount_layout(env, function_value, layout_ids, n, value, layout); + } + Mode::Dec => { + decrement_refcount_layout(env, function_value, layout_ids, value, layout); + } + } + + env.builder.build_return(None); + + function_value + } + }; + + env.builder.position_at_end(block); + env.builder + .set_current_debug_location(env.context, di_location); + + function_value +} + +fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, symbol: Symbol, -) -> IntValue<'ctx> { +) -> StructValue<'ctx> { let dict = load_symbol(scope, &symbol); - let i128_type = env.context.i128_type().into(); + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); - complex_bitcast(&env.builder, dict, i128_type, "dict_to_i128").into_int_value() + complex_bitcast(&env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict") + .into_struct_value() } fn zig_dict_to_struct<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, zig_dict: StructValue<'ctx>, ) -> StructValue<'ctx> { - let builder = env.builder; - - // get the RocStr type defined by zig - let zig_str_type = env.module.get_struct_type("dict.RocDict").unwrap(); - - let ret_type = BasicTypeEnum::StructType(collection(env.context, env.ptr_bytes)); - - // a roundabout way of casting (LLVM does not accept a standard bitcast) - let allocation = builder.build_alloca(zig_str_type, "zig_result"); - - builder.build_store(allocation, zig_dict); - - let ptr3 = builder - .build_bitcast( - allocation, - env.context.i128_type().ptr_type(AddressSpace::Generic), - "cast", - ) - .into_pointer_value(); - - let ptr4 = builder - .build_bitcast( - ptr3, - ret_type.into_struct_type().ptr_type(AddressSpace::Generic), - "cast", - ) - .into_pointer_value(); - - builder.build_load(ptr4, "load").into_struct_value() + complex_bitcast( + env.builder, + zig_dict.into(), + crate::llvm::convert::dict(env.context, env.ptr_bytes).into(), + "to_zig_dict", + ) + .into_struct_value() +} + +fn struct_to_zig_dict<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + struct_dict: StructValue<'ctx>, +) -> StructValue<'ctx> { + // get the RocStr type defined by zig + let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); + + complex_bitcast( + env.builder, + struct_dict.into(), + zig_dict_type.into(), + "to_zig_dict", + ) + .into_struct_value() } diff --git a/compiler/gen/src/llvm/build_hash.rs b/compiler/gen/src/llvm/build_hash.rs index 4c212911e5..a5445e1a39 100644 --- a/compiler/gen/src/llvm/build_hash.rs +++ b/compiler/gen/src/llvm/build_hash.rs @@ -1,3 +1,4 @@ +use crate::debug_info_init; use crate::llvm::build::Env; use crate::llvm::build::{ call_bitcode_fn, cast_block_of_memory_to_tag, complex_bitcast, set_name, FAST_CALL_CONV, @@ -236,28 +237,8 @@ fn build_hash_struct_help<'a, 'ctx, 'env>( field_layouts: &[Layout<'a>], ) { let ctx = env.context; - let builder = env.builder; - { - use inkwell::debug_info::AsDIScope; - - let func_scope = parent.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - ctx, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&ctx, loc); - } + debug_info_init!(env, parent); // Add args to scope let mut it = parent.get_param_iter(); @@ -404,28 +385,8 @@ fn build_hash_tag_help<'a, 'ctx, 'env>( union_layout: &UnionLayout<'a>, ) { let ctx = env.context; - let builder = env.builder; - { - use inkwell::debug_info::AsDIScope; - - let func_scope = parent.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - ctx, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&ctx, loc); - } + debug_info_init!(env, parent); // Add args to scope let mut it = parent.get_param_iter(); @@ -707,28 +668,8 @@ fn build_hash_list_help<'a, 'ctx, 'env>( element_layout: &Layout<'a>, ) { let ctx = env.context; - let builder = env.builder; - { - use inkwell::debug_info::AsDIScope; - - let func_scope = parent.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - ctx, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&ctx, loc); - } + debug_info_init!(env, parent); // Add args to scope let mut it = parent.get_param_iter(); diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 5687e32b52..f5a319aaf8 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -3,6 +3,7 @@ use bumpalo::Bump; use inkwell::context::Context; use inkwell::types::BasicTypeEnum::{self, *}; use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType}; +use inkwell::values::BasicValueEnum; use inkwell::AddressSpace; use roc_mono::layout::{Builtin, Layout, UnionLayout}; @@ -48,6 +49,18 @@ pub fn get_array_type<'ctx>(bt_enum: &BasicTypeEnum<'ctx>, size: u32) -> ArrayTy } } +/// TODO could this be added to Inkwell itself as a method on BasicValueEnum? +pub fn as_const_zero<'ctx>(bt_enum: &BasicTypeEnum<'ctx>) -> BasicValueEnum<'ctx> { + match bt_enum { + ArrayType(typ) => typ.const_zero().into(), + IntType(typ) => typ.const_zero().into(), + FloatType(typ) => typ.const_zero().into(), + PointerType(typ) => typ.const_zero().into(), + StructType(typ) => typ.const_zero().into(), + VectorType(typ) => typ.const_zero().into(), + } +} + fn basic_type_from_function_layout<'ctx>( arena: &Bump, context: &'ctx Context, @@ -185,7 +198,7 @@ pub fn basic_type_from_builtin<'ctx>( Float64 => context.f64_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(), Float16 => context.f16_type().as_basic_type_enum(), - Dict(_, _) | EmptyDict => collection(context, ptr_bytes).into(), + Dict(_, _) | EmptyDict => dict(context, ptr_bytes).into(), Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(), EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)), @@ -260,6 +273,16 @@ pub fn collection(ctx: &Context, ptr_bytes: u32) -> StructType<'_> { ctx.struct_type(&[u8_ptr.into(), usize_type.into()], false) } +pub fn dict(ctx: &Context, ptr_bytes: u32) -> StructType<'_> { + let usize_type = ptr_int(ctx, ptr_bytes); + let u8_ptr = ctx.i8_type().ptr_type(AddressSpace::Generic); + + ctx.struct_type( + &[u8_ptr.into(), usize_type.into(), usize_type.into()], + false, + ) +} + pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { match ptr_bytes { 1 => ctx.i8_type(), diff --git a/compiler/gen/src/llvm/refcounting.rs b/compiler/gen/src/llvm/refcounting.rs index a7d07c3ca1..c472f5ab11 100644 --- a/compiler/gen/src/llvm/refcounting.rs +++ b/compiler/gen/src/llvm/refcounting.rs @@ -1,3 +1,4 @@ +use crate::debug_info_init; use crate::llvm::build::{ cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, @@ -8,7 +9,6 @@ use crate::llvm::convert::{ }; use bumpalo::collections::Vec; use inkwell::context::Context; -use inkwell::debug_info::AsDIScope; use inkwell::module::Linkage; use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; @@ -192,23 +192,7 @@ impl<'ctx> PointerToRefcount<'ctx> { let entry = ctx.append_basic_block(parent, "entry"); builder.position_at_end(entry); - let subprogram = parent.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ subprogram.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - &ctx, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - - env.builder.set_current_debug_location(&ctx, loc); + debug_info_init!(env, parent); let refcount_ptr = { let raw_refcount_ptr = parent.get_nth_param(0).unwrap(); @@ -390,12 +374,18 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>( todo!(); } Dict(key_layout, value_layout) => { - if key_layout.contains_refcounted() || value_layout.contains_refcounted() { - // TODO decrement all values - } - - todo!(); + let wrapper_struct = value.into_struct_value(); + modify_refcount_dict( + env, + layout_ids, + mode, + layout, + key_layout, + value_layout, + wrapper_struct, + ); } + Str => { let wrapper_struct = value.into_struct_value(); modify_refcount_str(env, layout_ids, mode, layout, wrapper_struct); @@ -574,22 +564,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = fn_val.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - ctx, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&ctx, loc); + debug_info_init!(env, fn_val); // Add args to scope let arg_symbol = Symbol::ARG_1; @@ -681,22 +656,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = fn_val.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - ctx, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&ctx, loc); + debug_info_init!(env, fn_val); // Add args to scope let arg_symbol = Symbol::ARG_1; @@ -740,6 +700,131 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>( builder.build_return(None); } +fn modify_refcount_dict<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + mode: Mode, + layout: &Layout<'a>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, + original_wrapper: StructValue<'ctx>, +) { + let block = env.builder.get_insert_block().expect("to be in a function"); + let di_location = env.builder.get_current_debug_location().unwrap(); + + let (call_name, symbol) = match mode { + Mode::Inc(_) => ("increment_str", Symbol::INC), + Mode::Dec => ("decrement_str", Symbol::DEC), + }; + + let fn_name = layout_ids + .get(symbol, &layout) + .to_symbol_string(symbol, &env.interns); + + let function = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); + let function_value = build_header(env, basic_type, mode, &fn_name); + + modify_refcount_dict_help( + env, + layout_ids, + mode, + layout, + key_layout, + value_layout, + function_value, + ); + + function_value + } + }; + + env.builder.position_at_end(block); + env.builder + .set_current_debug_location(env.context, di_location); + + call_help(env, function, mode, original_wrapper.into(), call_name); +} + +fn modify_refcount_dict_help<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + mode: Mode, + layout: &Layout<'a>, + key_layout: &Layout<'a>, + value_layout: &Layout<'a>, + fn_val: FunctionValue<'ctx>, +) { + let builder = env.builder; + let ctx = env.context; + + // Add a basic block for the entry point + let entry = ctx.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, fn_val); + + // Add args to scope + let arg_symbol = Symbol::ARG_1; + let arg_val = fn_val.get_param_iter().next().unwrap(); + + set_name(arg_val, arg_symbol.ident_string(&env.interns)); + + let parent = fn_val; + + let wrapper_struct = arg_val.into_struct_value(); + + let len = builder + .build_extract_value(wrapper_struct, 1, "read_dict_len") + .unwrap() + .into_int_value(); + + // the block we'll always jump to when we're done + let cont_block = ctx.append_basic_block(parent, "modify_rc_str_cont"); + let modification_block = ctx.append_basic_block(parent, "modify_rc"); + + let is_non_empty = builder.build_int_compare( + IntPredicate::SGT, + len, + ptr_int(ctx, env.ptr_bytes).const_zero(), + "is_non_empty", + ); + + builder.build_conditional_branch(is_non_empty, modification_block, cont_block); + builder.position_at_end(modification_block); + + if key_layout.contains_refcounted() || value_layout.contains_refcounted() { + crate::llvm::build_dict::dict_elements_rc( + env, + layout_ids, + wrapper_struct.into(), + key_layout, + value_layout, + mode, + ); + } + + let data_ptr = env + .builder + .build_extract_value(wrapper_struct, 0, "get_data_ptr") + .unwrap() + .into_pointer_value(); + + let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, data_ptr); + let call_mode = mode_to_call_mode(fn_val, mode); + refcount_ptr.modify(call_mode, layout, env); + + builder.build_unconditional_branch(cont_block); + + builder.position_at_end(cont_block); + + // this function returns void + builder.build_return(None); +} + /// Build an increment or decrement function for a specific layout fn build_header<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -793,7 +878,7 @@ pub fn build_header_help<'a, 'ctx, 'env>( } #[derive(Clone, Copy)] -enum Mode { +pub enum Mode { Inc(u64), Dec, } @@ -867,22 +952,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = fn_val.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - context, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&context, loc); + debug_info_init!(env, fn_val); // Add args to scope let arg_symbol = Symbol::ARG_1; @@ -938,8 +1008,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>( // next, make a jump table for all possible values of the tag_id let mut cases = Vec::with_capacity_in(tags.len(), env.arena); - builder.set_current_debug_location(&context, loc); - for (tag_id, field_layouts) in tags.iter().enumerate() { // if none of the fields are or contain anything refcounted, just move on if !field_layouts @@ -1172,22 +1240,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( builder.position_at_end(entry); - let func_scope = fn_val.get_subprogram().unwrap(); - let lexical_block = env.dibuilder.create_lexical_block( - /* scope */ func_scope.as_debug_info_scope(), - /* file */ env.compile_unit.get_file(), - /* line_no */ 0, - /* column_no */ 0, - ); - - let loc = env.dibuilder.create_debug_location( - context, - /* line */ 0, - /* column */ 0, - /* current_scope */ lexical_block.as_debug_info_scope(), - /* inlined_at */ None, - ); - builder.set_current_debug_location(&context, loc); + debug_info_init!(env, fn_val); // Add args to scope let arg_symbol = Symbol::ARG_1; diff --git a/compiler/gen/tests/gen_dict.rs b/compiler/gen/tests/gen_dict.rs index 2dc74a1022..b837a886c0 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!( @@ -25,4 +27,282 @@ mod gen_dict { usize ); } + + #[test] + fn dict_insert_empty() { + assert_evals_to!( + indoc!( + r#" + Dict.insert Dict.empty 42 32 + |> Dict.len + "# + ), + 1, + usize + ); + } + + #[test] + fn dict_empty_contains() { + assert_evals_to!( + indoc!( + r#" + empty : Dict I64 F64 + empty = Dict.empty + + Dict.contains empty 42 + "# + ), + false, + bool + ); + } + + #[test] + fn dict_nonempty_contains() { + assert_evals_to!( + indoc!( + r#" + empty : Dict I64 F64 + empty = Dict.insert Dict.empty 42 3.14 + + Dict.contains empty 42 + "# + ), + true, + bool + ); + } + + #[test] + fn dict_empty_remove() { + assert_evals_to!( + indoc!( + r#" + empty : Dict I64 F64 + empty = Dict.empty + + empty + |> Dict.remove 42 + |> Dict.len + "# + ), + 0, + i64 + ); + } + + #[test] + fn dict_nonempty_remove() { + assert_evals_to!( + indoc!( + r#" + empty : Dict I64 F64 + empty = Dict.insert Dict.empty 42 3.14 + + empty + |> Dict.remove 42 + |> Dict.len + "# + ), + 0, + i64 + ); + } + + #[test] + fn dict_nonempty_get() { + assert_evals_to!( + indoc!( + r#" + empty : Dict I64 F64 + empty = Dict.insert Dict.empty 42 3.14 + + withDefault = \x, def -> + when x is + Ok v -> v + Err _ -> def + + empty + |> Dict.insert 42 3.14 + |> Dict.get 42 + |> withDefault 0 + "# + ), + 3.14, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + withDefault = \x, def -> + when x is + Ok v -> v + Err _ -> def + + Dict.empty + |> Dict.insert 42 3.14 + |> Dict.get 43 + |> withDefault 0 + "# + ), + 0.0, + 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 + ); + } + + #[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("c"), RocStr::from("a"), RocStr::from("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("Leverage agile frameworks to provide a robust"), + RocStr::from("to corporate strategy foster collaborative thinking to"), + RocStr::from("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("Leverage agile frameworks to provide a robust"), + RocStr::from("to corporate strategy foster collaborative thinking to"), + RocStr::from("synopsis for high level overviews. Iterative approaches"), + ], + &[RocStr] + ); + } } diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index bcb00458bc..e6f3f962df 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -193,12 +193,22 @@ pub fn helper<'a>( let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module); // mark our zig-defined builtins as internal + use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::module::Linkage; + + let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); + debug_assert!(kind_id > 0); + let attr = context.create_enum_attribute(kind_id, 1); + for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); if name.starts_with("roc_builtins") { function.set_linkage(Linkage::Internal); } + + if name.starts_with("roc_builtins.dict") { + function.add_attribute(AttributeLoc::Function, attr); + } } // Compile and add all the Procs before adding main @@ -265,8 +275,8 @@ pub fn helper<'a>( mode, ); - // fn_val.print_to_stderr(); - module.print_to_stderr(); + fn_val.print_to_stderr(); + // module.print_to_stderr(); panic!( "The preceding code was from {:?}, which failed LLVM verification in {} build.", diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 7107a4ce13..251d3aba44 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -267,7 +267,6 @@ impl Assembler for AArch64Assembler { ); } } - #[inline(always)] fn add_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, @@ -277,7 +276,6 @@ impl Assembler for AArch64Assembler { ) { add_reg64_reg64_reg64(buf, dst, src1, src2); } - #[inline(always)] fn add_freg64_freg64_freg64( _buf: &mut Vec<'_, u8>, @@ -293,6 +291,20 @@ impl Assembler for AArch64Assembler { unimplemented!("calling functions literal not yet implemented for AArch64"); } + #[inline(always)] + fn jmp_imm32(_buf: &mut Vec<'_, u8>, _offset: i32) -> usize { + unimplemented!("jump instructions not yet implemented for AArch64"); + } + #[inline(always)] + fn jne_reg64_imm64_imm32( + _buf: &mut Vec<'_, u8>, + _reg: AArch64GeneralReg, + _imm: u64, + _offset: i32, + ) -> usize { + unimplemented!("jump not equal instructions not yet implemented for AArch64"); + } + #[inline(always)] fn mov_freg64_imm64( _buf: &mut Vec<'_, u8>, @@ -302,7 +314,6 @@ impl Assembler for AArch64Assembler { ) { unimplemented!("loading float literal not yet implemented for AArch64"); } - #[inline(always)] fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, imm: i64) { let mut remaining = imm as u64; @@ -320,12 +331,10 @@ impl Assembler for AArch64Assembler { movk_reg64_imm16(buf, dst, remaining as u16, 3); } } - #[inline(always)] fn mov_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { unimplemented!("moving data between float registers not yet implemented for AArch64"); } - #[inline(always)] fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) { mov_reg64_reg64(buf, dst, src); @@ -414,7 +423,6 @@ impl Assembler for AArch64Assembler { ); } } - #[inline(always)] fn sub_reg64_reg64_reg64( _buf: &mut Vec<'_, u8>, @@ -434,6 +442,7 @@ impl Assembler for AArch64Assembler { ) { unimplemented!("registers equality not implemented yet for AArch64"); } + #[inline(always)] fn ret(buf: &mut Vec<'_, u8>) { ret_reg64(buf, AArch64GeneralReg::LR) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index f5f3f63e2c..b734f5450f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -2,7 +2,7 @@ use crate::{Backend, Env, Relocation}; use bumpalo::collections::Vec; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; -use roc_mono::ir::{Literal, Stmt}; +use roc_mono::ir::{BranchInfo, Literal, Stmt}; use roc_mono::layout::{Builtin, Layout}; use std::marker::PhantomData; use target_lexicon::Triple; @@ -71,6 +71,7 @@ pub trait CallConv { /// dst should always come before sources. pub trait Assembler { fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); + fn add_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32); fn add_freg64_freg64_freg64( buf: &mut Vec<'_, u8>, @@ -84,7 +85,24 @@ pub trait Assembler { src1: GeneralReg, src2: GeneralReg, ); + fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String); + + // Jumps by an offset of offset bytes unconditionally. + // It should always generate the same number of bytes to enable replacement if offset changes. + // It returns the base offset to calculate the jump from (generally the instruction after the jump). + fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize; + + // Jumps by an offset of offset bytes if reg is not equal to imm. + // It should always generate the same number of bytes to enable replacement if offset changes. + // It returns the base offset to calculate the jump from (generally the instruction after the jump). + fn jne_reg64_imm64_imm32( + buf: &mut Vec<'_, u8>, + reg: GeneralReg, + imm: u64, + offset: i32, + ) -> usize; + fn mov_freg64_imm64( buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, @@ -113,12 +131,14 @@ pub trait Assembler { src1: GeneralReg, src2: GeneralReg, ); + fn eq_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, src2: GeneralReg, ); + fn ret(buf: &mut Vec<'_, u8>); } @@ -372,6 +392,75 @@ impl< } } + fn build_switch( + &mut self, + cond_symbol: &Symbol, + _cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations. + branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], + default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), + _ret_layout: &Layout<'a>, + ) -> Result<(), String> { + // Switches are a little complex due to keeping track of jumps. + // In general I am trying to not have to loop over things multiple times or waste memory. + // The basic plan is to make jumps to nowhere and then correct them once we know the correct address. + let cond_reg = self.load_to_general_reg(cond_symbol)?; + + let mut ret_jumps = bumpalo::vec![in self.env.arena]; + let mut tmp = bumpalo::vec![in self.env.arena]; + for (val, branch_info, stmt) in branches.iter() { + tmp.clear(); + if let BranchInfo::None = branch_info { + // Create jump to next branch if not cond_sym not equal to value. + // Since we don't know the offset yet, set it to 0 and overwrite later. + let jne_location = self.buf.len(); + let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); + + // Build all statements in this branch. + self.build_stmt(stmt)?; + + // Build unconditional jump to the end of this switch. + // Since we don't know the offset yet, set it to 0 and overwrite later. + let jmp_location = self.buf.len(); + let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0); + ret_jumps.push((jmp_location, jmp_offset)); + + // Overwite the original jne with the correct offset. + let end_offset = self.buf.len(); + let jne_offset = end_offset - start_offset; + ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32); + for (i, byte) in tmp.iter().enumerate() { + self.buf[jne_location + i] = *byte; + } + } else { + return Err(format!( + "branch info, {:?}, is not yet implemented in switch statemens", + branch_info + )); + } + } + let (branch_info, stmt) = default_branch; + if let BranchInfo::None = branch_info { + self.build_stmt(stmt)?; + + // Update all return jumps to jump past the default case. + let ret_offset = self.buf.len(); + for (jmp_location, start_offset) in ret_jumps.into_iter() { + tmp.clear(); + let jmp_offset = ret_offset - start_offset; + ASM::jmp_imm32(&mut tmp, jmp_offset as i32); + for (i, byte) in tmp.iter().enumerate() { + self.buf[jmp_location + i] = *byte; + } + } + Ok(()) + } else { + Err(format!( + "branch info, {:?}, is not yet implemented in switch statemens", + branch_info + )) + } + } + fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> { let dst_reg = self.claim_general_reg(dst)?; let src_reg = self.load_to_general_reg(src)?; diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 1e06840d6f..eaf4b25005 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -738,6 +738,7 @@ impl Assembler for X86_64Assembler { neg_reg64(buf, dst); cmovl_reg64_reg64(buf, dst, src); } + #[inline(always)] fn add_reg64_reg64_imm32( buf: &mut Vec<'_, u8>, @@ -784,6 +785,7 @@ impl Assembler for X86_64Assembler { addsd_freg64_freg64(buf, dst, src2); } } + #[inline(always)] fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String) { buf.extend(&[0xE8, 0x00, 0x00, 0x00, 0x00]); @@ -792,6 +794,30 @@ impl Assembler for X86_64Assembler { name: fn_name, }); } + + #[inline(always)] + fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize { + jmp_imm32(buf, offset); + buf.len() + } + #[inline(always)] + fn jne_reg64_imm64_imm32( + buf: &mut Vec<'_, u8>, + reg: X86_64GeneralReg, + imm: u64, + offset: i32, + ) -> usize { + buf.reserve(13); + if imm > i32::MAX as u64 { + unimplemented!( + "comparison with values greater than i32::max not yet implemented for jne" + ); + } + cmp_reg64_imm32(buf, reg, imm as i32); + jne_imm32(buf, offset); + buf.len() + } + #[inline(always)] fn mov_freg64_imm64( buf: &mut Vec<'_, u8>, @@ -853,6 +879,7 @@ impl Assembler for X86_64Assembler { fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { mov_stack32_reg64(buf, offset, src); } + #[inline(always)] fn sub_reg64_reg64_imm32( buf: &mut Vec<'_, u8>, @@ -936,6 +963,20 @@ const fn add_reg_extension(reg: X86_64GeneralReg, byte: u8) -> u8 { } } +#[inline(always)] +fn binop_reg64_reg64( + op_code: u8, + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(dst, REX_W); + let rex = add_reg_extension(src, rex); + let dst_mod = dst as u8 % 8; + let src_mod = (src as u8 % 8) << 3; + buf.extend(&[rex, op_code, 0xC0 + dst_mod + src_mod]); +} + // Below here are the functions for all of the assembly instructions. // Their names are based on the instruction and operators combined. // You should call `buf.reserve()` if you push or extend more than once. @@ -952,19 +993,6 @@ fn add_reg64_imm32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i32) { buf.extend(&imm.to_le_bytes()); } -fn binop_reg64_reg64( - op_code: u8, - buf: &mut Vec<'_, u8>, - dst: X86_64GeneralReg, - src: X86_64GeneralReg, -) { - let rex = add_rm_extension(dst, REX_W); - let rex = add_reg_extension(src, rex); - let dst_mod = dst as u8 % 8; - let src_mod = (src as u8 % 8) << 3; - buf.extend(&[rex, op_code, 0xC0 + dst_mod + src_mod]); -} - /// `ADD r/m64,r64` -> Add r64 to r/m64. #[inline(always)] fn add_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { @@ -991,22 +1019,12 @@ fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl } } -/// `SUB r/m64,r64` -> Sub r64 to r/m64. +/// r/m64 AND imm8 (sign-extended). #[inline(always)] -fn sub_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - binop_reg64_reg64(0x29, buf, dst, src); -} - -/// `CMP r/m64,r64` -> Compare r64 to r/m64. -#[inline(always)] -fn cmp_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - binop_reg64_reg64(0x39, buf, dst, src); -} - -/// `XOR r/m64,r64` -> Xor r64 to r/m64. -#[inline(always)] -fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - binop_reg64_reg64(0x31, buf, dst, src); +fn and_reg64_imm8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i8) { + let rex = add_rm_extension(dst, REX_W); + let dst_mod = dst as u8 % 8; + buf.extend(&[rex, 0x83, 0xE0 + dst_mod, imm as u8]); } /// `CMOVL r64,r/m64` -> Move if less (SF≠ OF). @@ -1019,6 +1037,39 @@ fn cmovl_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Ge buf.extend(&[rex, 0x0F, 0x4C, 0xC0 + dst_mod + src_mod]); } +/// `CMP r/m64,i32` -> Compare i32 to r/m64. +#[inline(always)] +fn cmp_reg64_imm32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i32) { + let rex = add_rm_extension(dst, REX_W); + let dst_mod = dst as u8 % 8; + buf.reserve(7); + buf.extend(&[rex, 0x81, 0xF8 + dst_mod]); + buf.extend(&imm.to_le_bytes()); +} + +/// `CMP r/m64,r64` -> Compare r64 to r/m64. +#[inline(always)] +fn cmp_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + binop_reg64_reg64(0x39, buf, dst, src); +} + +/// Jump near, relative, RIP = RIP + 32-bit displacement sign extended to 64-bits. +#[inline(always)] +fn jmp_imm32(buf: &mut Vec<'_, u8>, imm: i32) { + buf.reserve(5); + buf.push(0xE9); + buf.extend(&imm.to_le_bytes()); +} + +/// Jump near if not equal (ZF=0). +#[inline(always)] +fn jne_imm32(buf: &mut Vec<'_, u8>, imm: i32) { + buf.reserve(6); + buf.push(0x0F); + buf.push(0x85); + buf.extend(&imm.to_le_bytes()); +} + /// `MOV r/m64, imm32` -> Move imm32 sign extended to 64-bits to r/m64. #[inline(always)] fn mov_reg64_imm32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i32) { @@ -1124,6 +1175,7 @@ fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl } // `MOVSD xmm, m64` -> Load scalar double-precision floating-point value from m64 to xmm register. +#[inline(always)] fn movsd_freg64_rip_offset32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: u32) { let dst_mod = dst as u8 % 8; if dst as u8 > 7 { @@ -1150,10 +1202,6 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { // XOR needs 3 bytes, actual SETE instruction need 3 or 4 bytes buf.reserve(7); - // We reset reg to 0 because the SETE instruction only applies - // to the lower bits of the register - xor_reg64_reg64(buf, reg, reg); - // Actually apply the SETE instruction let reg_mod = reg as u8 % 8; use X86_64GeneralReg::*; @@ -1164,6 +1212,10 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { buf.extend(&[REX + 1, 0x0F, 0x94, 0xC0 + reg_mod]) } } + + // We and reg with 1 because the SETE instruction only applies + // to the lower bits of the register + and_reg64_imm8(buf, reg, 1); } /// `RET` -> Near return to calling procedure. @@ -1183,6 +1235,12 @@ fn sub_reg64_imm32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i32) { buf.extend(&imm.to_le_bytes()); } +/// `SUB r/m64,r64` -> Sub r64 to r/m64. +#[inline(always)] +fn sub_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + binop_reg64_reg64(0x29, buf, dst, src); +} + /// `POP r64` -> Pop top of stack into r64; increment stack pointer. Cannot encode 32-bit operand size. #[inline(always)] fn pop_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { @@ -1207,6 +1265,13 @@ fn push_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { } } +/// `XOR r/m64,r64` -> Xor r64 to r/m64. +#[inline(always)] +#[allow(dead_code)] +fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + binop_reg64_reg64(0x31, buf, dst, src); +} + // When writing tests, it is a good idea to test both a number and unnumbered register. // This is because R8-R15 often have special instruction prefixes. #[cfg(test)] @@ -1343,6 +1408,39 @@ mod tests { } } + #[test] + fn test_cmp_reg64_imm32() { + let arena = bumpalo::Bump::new(); + let mut buf = bumpalo::vec![in &arena]; + for (dst, expected) in &[ + (X86_64GeneralReg::RAX, [0x48, 0x81, 0xF8]), + (X86_64GeneralReg::R15, [0x49, 0x81, 0xFF]), + ] { + buf.clear(); + cmp_reg64_imm32(&mut buf, *dst, TEST_I32); + assert_eq!(expected, &buf[..3]); + assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]); + } + } + + #[test] + fn test_jmp_imm32() { + let arena = bumpalo::Bump::new(); + let mut buf = bumpalo::vec![in &arena]; + jmp_imm32(&mut buf, TEST_I32); + assert_eq!(0xE9, buf[0]); + assert_eq!(TEST_I32.to_le_bytes(), &buf[1..]); + } + + #[test] + fn test_jne_imm32() { + let arena = bumpalo::Bump::new(); + let mut buf = bumpalo::vec![in &arena]; + jne_imm32(&mut buf, TEST_I32); + assert_eq!([0x0F, 0x85], &buf[..2]); + assert_eq!(TEST_I32.to_le_bytes(), &buf[2..]); + } + #[test] fn test_mov_reg64_imm32() { let arena = bumpalo::Bump::new(); @@ -1539,36 +1637,36 @@ mod tests { let arena = bumpalo::Bump::new(); let mut buf = bumpalo::vec![in &arena]; - // tests for 6 bytes in the output buffer + // tests for 7 bytes in the output buffer let (reg, expected) = ( X86_64GeneralReg::RAX, [ - 0x48, 0x31, 0xC0, // XOR rax, rax 0x0F, 0x94, 0xC0, // SETE al ; al are the 8 lower weight bits of rax + 0x48, 0x83, 0xE0, 0x01, // AND rax, 1 ], ); buf.clear(); sete_reg64(&mut buf, reg); assert_eq!(expected, &buf[..]); - // tests for 7 bytes in the output buffer + // tests for 8 bytes in the output buffer for (reg, expected) in &[ ( X86_64GeneralReg::RSP, [ - // XOR rsp, rsp - 0x48, 0x31, 0xE4, // SETE spl ; spl are the 8 lower weight bits of rsp - 0x40, 0x0F, 0x94, 0xC4, + 0x40, 0x0F, 0x94, 0xC4, // + // AND rsp, 1 + 0x48, 0x83, 0xE4, 0x01, ], ), ( X86_64GeneralReg::R15, [ - // XOR r15, r15 - 0x4D, 0x31, 0xFF, // SETE r15b ; r15b are the 8 lower weight bits of r15 - 0x41, 0x0F, 0x94, 0xC7, + 0x41, 0x0F, 0x94, 0xC7, // + // AND rsp, 1 + 0x49, 0x83, 0xE7, 0x01, ], ), ] { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index ae1270ecd9..e8df244eae 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -7,7 +7,7 @@ use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{ModuleName, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; -use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; +use roc_mono::ir::{BranchInfo, CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout, LayoutIds}; use target_lexicon::Triple; @@ -107,9 +107,36 @@ where let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); self.build_stmt(&stmt) } + Stmt::Switch { + cond_symbol, + cond_layout, + branches, + default_branch, + ret_layout, + } => { + self.load_literal_symbols(&[*cond_symbol])?; + self.build_switch( + cond_symbol, + cond_layout, + branches, + default_branch, + ret_layout, + )?; + self.free_symbols(stmt); + Ok(()) + } x => Err(format!("the statement, {:?}, is not yet implemented", x)), } } + // build_switch generates a instructions for a switch statement. + fn build_switch( + &mut self, + cond_symbol: &Symbol, + cond_layout: &Layout<'a>, + branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], + default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), + ret_layout: &Layout<'a>, + ) -> Result<(), String>; /// build_expr builds the expressions for the specified symbol. /// The builder must keep track of the symbol because it may be refered to later. diff --git a/compiler/gen_dev/tests/gen_num.rs b/compiler/gen_dev/tests/gen_num.rs index 291714d0ec..c10aef00bb 100644 --- a/compiler/gen_dev/tests/gen_num.rs +++ b/compiler/gen_dev/tests/gen_num.rs @@ -150,6 +150,16 @@ mod gen_num { true, bool ); + + assert_evals_to!( + indoc!( + r#" + 3 == 4 + "# + ), + false, + bool + ); } #[test] @@ -198,34 +208,6 @@ mod gen_num { ); } - /* - #[test] - fn f64_sqrt() { - // FIXME this works with normal types, but fails when checking uniqueness types - assert_evals_to!( - indoc!( - r#" - when Num.sqrt 100 is - Ok val -> val - Err _ -> -1 - "# - ), - 10.0, - f64 - ); - } - - #[test] - fn f64_round_old() { - assert_evals_to!("Num.round 3.6", 4, i64); - } - - #[test] - fn f64_abs() { - assert_evals_to!("Num.abs -4.7", 4.7, f64); - assert_evals_to!("Num.abs 5.8", 5.8, f64); - } - #[test] fn gen_if_fn() { assert_evals_to!( @@ -267,6 +249,55 @@ mod gen_num { ); } + #[test] + fn gen_fib_fn() { + assert_evals_to!( + indoc!( + r#" + fib = \n -> + if n == 0 then + 0 + else if n == 1 then + 1 + else + (fib (n - 1)) + (fib (n - 2)) + + fib 10 + "# + ), + 55, + i64 + ); + } + + /* + #[test] + fn f64_sqrt() { + // FIXME this works with normal types, but fails when checking uniqueness types + assert_evals_to!( + indoc!( + r#" + when Num.sqrt 100 is + Ok val -> val + Err _ -> -1 + "# + ), + 10.0, + f64 + ); + } + + #[test] + fn f64_round_old() { + assert_evals_to!("Num.round 3.6", 4, i64); + } + + #[test] + fn f64_abs() { + assert_evals_to!("Num.abs -4.7", 4.7, f64); + assert_evals_to!("Num.abs 5.8", 5.8, f64); + } + #[test] fn gen_float_eq() { assert_evals_to!( diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 0da8b3d777..3416f0a073 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -31,6 +31,11 @@ pub enum LowLevel { DictSize, DictEmpty, DictInsert, + DictRemove, + DictContains, + DictGetUnsafe, + DictKeys, + DictValues, NumAdd, NumAddWrap, NumAddChecked, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 0fa7c8a012..0a745dd6e3 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -741,8 +741,14 @@ define_builtins! { 11 DEC: "#dec" // internal function that increments the refcount 12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record 13 LIST_EQ: "#list_eq" // internal function that checks list equality - 14 GENERIC_EQ: "#generic_eq" // internal function that checks generic equality - 15 GENERIC_HASH: "#generic_hash" // internal function that checks list equality + + 14 GENERIC_HASH: "#generic_hash" // hash of arbitrary layouts + 15 GENERIC_HASH_REF: "#generic_hash_by_ref" // hash of arbitrary layouts, passed as an opaque pointer + + 16 GENERIC_EQ_REF: "#generic_eq_by_ref" // equality of arbitrary layouts, passed as an opaque pointer + 17 GENERIC_RC_REF: "#generic_rc_by_ref" // refcount of arbitrary layouts, passed as an opaque pointer + + 18 GENERIC_EQ: "#generic_eq" // internal function that checks generic equality } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias @@ -899,6 +905,11 @@ define_builtins! { // This should not be exposed to users, its for testing the // hash function ONLY 7 DICT_TEST_HASH: "hashTestOnly" + + 8 DICT_REMOVE: "remove" + 9 DICT_CONTAINS: "contains" + 10 DICT_KEYS: "keys" + 11 DICT_VALUES: "values" } 7 SET: "Set" => { 0 SET_SET: "Set" imported // the Set.Set type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 4399d8aa18..1215c50e3e 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -612,7 +612,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]), @@ -623,6 +622,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 @@ -638,5 +641,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { DictSize => arena.alloc_slice_copy(&[borrowed]), DictEmpty => &[], DictInsert => arena.alloc_slice_copy(&[owned, owned, owned]), + DictRemove => arena.alloc_slice_copy(&[owned, borrowed]), + DictContains => arena.alloc_slice_copy(&[borrowed, borrowed]), + DictGetUnsafe => arena.alloc_slice_copy(&[borrowed, borrowed]), + DictKeys | DictValues => arena.alloc_slice_copy(&[borrowed]), } } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 32b6a4d1c6..91a9768f1e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -683,6 +683,7 @@ mod test_mono { let Test.9 = 2i64; let Test.4 = Array [Test.8, Test.9]; let Test.3 = CallByName Test.1 Test.4; + dec Test.4; ret Test.3; "# ), @@ -708,6 +709,7 @@ mod test_mono { let Test.2 = Array [Test.5]; let Test.3 = 2i64; let Test.1 = CallByName List.5 Test.2 Test.3; + dec Test.2; ret Test.1; "# ), diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index c80e4088bd..2ed9c4c437 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -1620,8 +1620,8 @@ fn to_diff<'b>( pair => { // We hit none of the specific cases where we give more detailed information - let left = to_doc(alloc, Parens::Unnecessary, type1); - let right = to_doc(alloc, Parens::Unnecessary, type2); + let left = to_doc(alloc, parens, type1); + let right = to_doc(alloc, parens, type2); let is_int = |t: &ErrorType| match t { ErrorType::Type(Symbol::NUM_INT, _) => true, diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 80b455d3b2..f4f9c0f8b2 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -4703,4 +4703,38 @@ mod test_reporting { ), ) } + + #[test] + fn dict_type_formatting() { + // TODO could do better by pointing out we're parsing a function type + report_problem_as( + indoc!( + r#" + myDict : Dict I64 Str + myDict = Dict.insert Dict.empty "foo" 42 + + myDict + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + Something is off with the body of the `myDict` definition: + + 1│ myDict : Dict I64 Str + 2│ myDict = Dict.insert Dict.empty "foo" 42 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This `insert` call produces: + + Dict Str (Num a) + + But the type annotation on `myDict` says it should be: + + Dict I64 Str + "# + ), + ) + } } diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs index 510130a931..83791facb1 100644 --- a/editor/src/lang/expr.rs +++ b/editor/src/lang/expr.rs @@ -46,6 +46,7 @@ impl Output { } } +#[derive(Debug)] pub struct Env<'a> { pub home: ModuleId, pub var_store: &'a mut VarStore, @@ -106,7 +107,7 @@ impl<'a> Env<'a> { } pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { - todo!(); + dbg!("Don't Forget to set the region eventually"); } pub fn register_closure(&mut self, symbol: Symbol, references: References) { @@ -807,6 +808,7 @@ pub fn to_expr2<'a>( todo!() } Nested(sub_expr) => to_expr2(env, scope, sub_expr, region), + Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), // Below this point, we shouln't see any of these nodes anymore because // operator desugaring should have removed them! @@ -841,7 +843,7 @@ pub fn to_expr2<'a>( ); } - _ => todo!(), + rest => todo!("not yet implemented {:?}", rest), } } @@ -1180,3 +1182,55 @@ fn canonicalize_when_branch<'a>( references, ) } + +fn canonicalize_lookup( + env: &mut Env<'_>, + scope: &mut Scope, + module_name: &str, + ident: &str, + region: Region, +) -> (Expr2, Output) { + use Expr2::*; + + let mut output = Output::default(); + let can_expr = if module_name.is_empty() { + // Since module_name was empty, this is an unqualified var. + // Look it up in scope! + match scope.lookup(&(*ident).into(), region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(_problem) => { + // env.problem(Problem::RuntimeError(problem.clone())); + + // RuntimeError(problem) + todo!() + } + } + } else { + // Since module_name was nonempty, this is a qualified var. + // Look it up in the env! + match env.qualified_lookup(module_name, ident, region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(_problem) => { + // Either the module wasn't imported, or + // it was imported but it doesn't expose this ident. + // env.problem(Problem::RuntimeError(problem.clone())); + + // RuntimeError(problem) + + todo!() + } + } + }; + + // If it's valid, this ident should be in scope already. + + (can_expr, output) +} diff --git a/editor/src/lang/pool.rs b/editor/src/lang/pool.rs index 6821ae759a..e391751fce 100644 --- a/editor/src/lang/pool.rs +++ b/editor/src/lang/pool.rs @@ -65,6 +65,7 @@ impl PartialEq for NodeId { impl Copy for NodeId {} +#[derive(Debug)] pub struct Pool { nodes: *mut [u8; NODE_BYTES], num_nodes: u32, @@ -141,7 +142,7 @@ impl Pool { } } - pub fn get(&self, node_id: NodeId) -> &T { + pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { unsafe { let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; @@ -291,7 +292,7 @@ impl<'a, T: 'a + Sized> PoolVec { } pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { - debug_assert!(size_of::() <= NODE_BYTES); + debug_assert!(size_of::() <= NODE_BYTES, "{}", size_of::()); if len == 0 { Self::empty(pool) diff --git a/editor/src/lang/scope.rs b/editor/src/lang/scope.rs index 41090f6307..91f7c6af1e 100644 --- a/editor/src/lang/scope.rs +++ b/editor/src/lang/scope.rs @@ -2,23 +2,103 @@ #![allow(dead_code)] #![allow(unused_imports)] use crate::lang::pool::{Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Alias, TypeId}; +use crate::lang::types::{Alias, Type2, TypeId}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_types::{ - solved_types::{FreeVars, SolvedType}, - subs::{VarStore, Variable}, + builtin_aliases, + solved_types::{BuiltinAlias, FreeVars, SolvedType}, + subs::{VarId, VarStore, Variable}, }; fn solved_type_to_type_id( - _solved_type: &SolvedType, - _free_vars: &mut FreeVars, - _var_store: &mut VarStore, + pool: &mut Pool, + solved_type: &SolvedType, + free_vars: &mut FreeVars, + var_store: &mut VarStore, ) -> TypeId { - todo!() + let typ2 = to_type2(pool, solved_type, free_vars, var_store); + + pool.add(typ2) +} + +fn to_type2( + pool: &mut Pool, + solved_type: &SolvedType, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> Type2 { + match solved_type { + SolvedType::Alias(symbol, solved_type_variables, solved_actual) => { + let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); + + for (type_variable_node_id, (lowercase, solved_arg)) in type_variables + .iter_node_ids() + .zip(solved_type_variables.iter()) + { + let typ2 = to_type2(pool, solved_arg, free_vars, var_store); + + let node = pool.add(typ2); + + pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node); + } + + let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store); + + let actual = pool.add(actual_typ2); + + let typ2 = Type2::Alias(*symbol, type_variables, actual); + + typ2 + } + SolvedType::TagUnion(tags, ext) => { + let new_tags = PoolVec::with_capacity(tags.len() as u32, pool); + + for (tag_node_id, (_tag_name, args)) in new_tags.iter_node_ids().zip(tags.iter()) { + let new_args: PoolVec = PoolVec::with_capacity(args.len() as u32, pool); + + for (arg_node_id, arg) in new_args.iter_node_ids().zip(args.iter()) { + let node = to_type2(pool, arg, free_vars, var_store); + + pool[arg_node_id] = node; + } + + // tagname as PoolStr + pool[tag_node_id] = (PoolStr::new("", pool), new_args); + } + + let actual_typ2 = to_type2(pool, ext, free_vars, var_store); + + let actual = pool.add(actual_typ2); + + let typ2 = Type2::TagUnion(new_tags, actual); + + typ2 + } + SolvedType::Flex(var_id) => { + Type2::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)) + } + SolvedType::EmptyTagUnion => Type2::EmptyTagUnion, + rest => todo!("{:?}", rest), + } +} + +fn var_id_to_flex_var( + var_id: VarId, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> Variable { + if let Some(var) = free_vars.unnamed_vars.get(&var_id) { + *var + } else { + let var = var_store.fresh(); + free_vars.unnamed_vars.insert(var_id, var); + + var + } } #[derive(Debug)] @@ -40,45 +120,45 @@ pub struct Scope { } impl Scope { - pub fn new(home: ModuleId, _pool: &mut Pool, _var_store: &mut VarStore) -> Scope { - use roc_types::solved_types::{BuiltinAlias, FreeVars}; - let _solved_aliases = roc_types::builtin_aliases::aliases(); - let aliases = MutMap::default(); + pub fn new(home: ModuleId, pool: &mut Pool, var_store: &mut VarStore) -> Scope { + let solved_aliases = builtin_aliases::aliases(); + let mut aliases = MutMap::default(); - // for (symbol, builtin_alias) in solved_aliases { - // // let BuiltinAlias { region, vars, typ } = builtin_alias; - // let BuiltinAlias { vars, typ, .. } = builtin_alias; + for (symbol, builtin_alias) in solved_aliases { + // let BuiltinAlias { region, vars, typ } = builtin_alias; + let BuiltinAlias { vars, typ, .. } = builtin_alias; - // let mut free_vars = FreeVars::default(); - // let typ = solved_type_to_type_id(&typ, &mut free_vars, var_store); - // // roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + let mut free_vars = FreeVars::default(); - // // make sure to sort these variables to make them line up with the type arguments - // let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - // type_variables.sort(); + // roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + let actual = solved_type_to_type_id(pool, &typ, &mut free_vars, var_store); - // debug_assert_eq!(vars.len(), type_variables.len()); - // let variables = PoolVec::with_capacity(vars.len() as u32, pool); + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); - // let it = variables - // .iter_node_ids() - // .zip(vars.iter()) - // .zip(type_variables); - // for ((node_id, loc_name), (_, var)) in it { - // // TODO region is ignored, but "fake" anyway. How to resolve? - // let name = PoolStr::new(loc_name.value.as_str(), pool); - // pool[node_id] = (name, var); - // } + debug_assert_eq!(vars.len(), type_variables.len()); + let variables = PoolVec::with_capacity(vars.len() as u32, pool); - // let alias = Alias { - // actual: typ, - // /// We know that builtin aliases have no hiddden variables (e.g. in closures) - // hidden_variables: PoolVec::empty(pool), - // targs: variables, - // }; + let it = variables + .iter_node_ids() + .zip(vars.iter()) + .zip(type_variables); + for ((node_id, loc_name), (_, var)) in it { + // TODO region is ignored, but "fake" anyway. How to resolve? + let name = PoolStr::new(loc_name.value.as_str(), pool); + pool[node_id] = (name, var); + } - // aliases.insert(symbol, alias); - // } + let alias = Alias { + actual, + /// We know that builtin aliases have no hiddden variables (e.g. in closures) + hidden_variables: PoolVec::empty(pool), + targs: variables, + }; + + aliases.insert(symbol, alias); + } let idents = Symbol::default_in_scope(); let idents: MutMap<_, _> = idents.into_iter().collect(); diff --git a/editor/src/lang/types.rs b/editor/src/lang/types.rs index f0278f970e..41b353e5cc 100644 --- a/editor/src/lang/types.rs +++ b/editor/src/lang/types.rs @@ -4,13 +4,14 @@ use crate::lang::expr::Env; use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; use crate::lang::scope::Scope; +use inlinable_string::InlinableString; // use roc_can::expr::Output; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; -use roc_types::subs::Variable; use roc_types::types::{Problem, RecordField}; +use roc_types::{subs::Variable, types::ErrorType}; pub type TypeId = NodeId; @@ -29,16 +30,32 @@ pub enum Type2 { }, EmptyTagUnion, - TagUnion(PoolVec<(PoolStr, PoolVec)>, TypeId), - RecursiveTagUnion(Variable, PoolVec<(PoolStr, PoolVec)>, TypeId), + TagUnion(PoolVec<(PoolStr, PoolVec)>, TypeId), // 12B = 8B + 4B + RecursiveTagUnion(Variable, PoolVec<(PoolStr, PoolVec)>, TypeId), // 16B = 4B + 8B + 4B EmptyRec, - Record(PoolVec<(PoolStr, RecordField)>, TypeId), + Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 12B = 8B + 4B Function(PoolVec, TypeId, TypeId), // 16B = 8B + 4B + 4B Apply(Symbol, PoolVec), // 16B = 8B + 8B - Erroneous(roc_types::types::Problem), + Erroneous(Problem2), +} + +#[derive(Debug)] +pub enum Problem2 { + CanonicalizationProblem, + CircularType(Symbol, NodeId), // 12B = 8B + 4B + CyclicAlias(Symbol, PoolVec), // 16B = 8B + 8B + UnrecognizedIdent(PoolStr), // 8B + Shadowed(Located), + BadTypeArguments { + symbol: Symbol, // 8B + type_got: u8, // 1B + alias_needs: u8, // 1B + }, + InvalidModule, + SolvedTypeError, } impl ShallowClone for Type2 { @@ -309,7 +326,10 @@ pub fn to_type2<'a>( references.symbols.insert(symbol); Type2::Alias(symbol, args, actual) } - TypeApply::Erroneous(problem) => Type2::Erroneous(problem), + TypeApply::Erroneous(_problem) => { + // Type2::Erroneous(problem) + todo!() + } } } Function(argument_types, return_type) => { @@ -405,15 +425,16 @@ pub fn to_type2<'a>( ) { Ok(symbol) => symbol, - Err((original_region, shadow)) => { - let problem = Problem::Shadowed(original_region, shadow.clone()); + Err((_original_region, _shadow)) => { + // let problem = Problem2::Shadowed(original_region, shadow.clone()); - env.problem(roc_problem::can::Problem::ShadowingInAnnotation { - original_region, - shadow, - }); + // env.problem(roc_problem::can::Problem::ShadowingInAnnotation { + // original_region, + // shadow, + // }); - return Type2::Erroneous(problem); + // return Type2::Erroneous(problem); + todo!(); } }; @@ -455,7 +476,7 @@ pub fn to_type2<'a>( _ => { // If anything other than a lowercase identifier // appears here, the whole annotation is invalid. - return Type2::Erroneous(Problem::CanonicalizationProblem); + return Type2::Erroneous(Problem2::CanonicalizationProblem); } } } @@ -510,7 +531,7 @@ pub fn to_type2<'a>( } _ => { // This is a syntactically invalid type alias. - Type2::Erroneous(Problem::CanonicalizationProblem) + Type2::Erroneous(Problem2::CanonicalizationProblem) } } } diff --git a/editor/src/lib.rs b/editor/src/lib.rs index fd154fa768..dbe9c08d98 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -33,7 +33,6 @@ use cgmath::Vector2; use ed_model::Position; use lang::{pool::Pool, scope::Scope}; use pipelines::RectResources; -use roc_collections::all::MutMap; use roc_module::symbol::{IdentIds, ModuleIds}; use roc_region::all::Region; use roc_types::subs::VarStore; @@ -280,9 +279,9 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { } else { // queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush); - let mut pool = Pool::with_capacity(12); + let mut pool = Pool::with_capacity(1024); let mut var_store = VarStore::default(); - let dep_idents = MutMap::default(); + let dep_idents = IdentIds::exposed_builtins(8); let mut module_ids = ModuleIds::default(); let exposed_ident_ids = IdentIds::default(); @@ -303,14 +302,18 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box> { let region = Region::new(0, 0, 0, 0); let (expr2, _) = crate::lang::expr::str_to_expr2( - &arena, "True", &mut env, &mut scope, region, + &arena, + "Num.add 1 1", + &mut env, + &mut scope, + region, ) .unwrap(); render::render_expr2( &mut env, - &size, &expr2, + &size, CODE_TXT_XY.into(), &mut glyph_brush, ); diff --git a/editor/src/render.rs b/editor/src/render.rs index 8310b3fa40..bc4398890b 100644 --- a/editor/src/render.rs +++ b/editor/src/render.rs @@ -13,8 +13,8 @@ use crate::{ pub fn render_expr2<'a>( env: &mut Env<'a>, - size: &PhysicalSize, expr2: &Expr2, + size: &PhysicalSize, position: Vector2, glyph_brush: &mut GlyphBrush<()>, ) { @@ -93,6 +93,25 @@ pub fn render_expr2<'a>( queue_code_text_draw(&code_text, glyph_brush); } + Expr2::Call { expr: expr_id, .. } => { + let expr = env.pool.get(*expr_id); + + render_expr2(env, expr, size, position, glyph_brush); + } + Expr2::Var(symbol) => { + let text = format!("{:?}", symbol); + + let code_text = Text { + position, + area_bounds, + color: CODE_COLOR.into(), + text: text.as_str(), + size: CODE_FONT_SIZE, + ..Default::default() + }; + + queue_code_text_draw(&code_text, glyph_brush); + } rest => todo!("implement {:?} render", rest), }; }