mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Merge remote-tracking branch 'origin/trunk' into specialize-owned
This commit is contained in:
commit
2a22f6254f
31 changed files with 2840 additions and 480 deletions
|
@ -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!(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
pub fn contains(self: RocDict, key_size: usize, key_ptr: *const c_void, hash_code: u64) bool {
|
||||
return false;
|
||||
// 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 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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -770,6 +770,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
),
|
||||
);
|
||||
|
||||
// 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<Symbol, (SolvedType, Region)> {
|
|||
),
|
||||
);
|
||||
|
||||
// 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
|
||||
|
|
|
@ -83,6 +83,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
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, Def> {
|
|||
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();
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
complex_bitcast(
|
||||
env.builder,
|
||||
zig_dict.into(),
|
||||
crate::llvm::convert::dict(env.context, env.ptr_bytes).into(),
|
||||
"to_zig_dict",
|
||||
)
|
||||
.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()
|
||||
.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()
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
modify_refcount_dict(
|
||||
env,
|
||||
layout_ids,
|
||||
mode,
|
||||
layout,
|
||||
key_layout,
|
||||
value_layout,
|
||||
wrapper_struct,
|
||||
);
|
||||
}
|
||||
|
||||
todo!();
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -267,7 +267,6 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
|
@ -277,7 +276,6 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> 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<AArch64GeneralReg, AArch64FloatReg> 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<AArch64GeneralReg, AArch64FloatReg> 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<AArch64GeneralReg, AArch64FloatReg> 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<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
|
@ -434,6 +442,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
) {
|
||||
unimplemented!("registers equality not implemented yet for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
ret_reg64(buf, AArch64GeneralReg::LR)
|
||||
|
|
|
@ -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<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
/// dst should always come before sources.
|
||||
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
||||
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<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
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<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
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)?;
|
||||
|
|
|
@ -738,6 +738,7 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> 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<X86_64GeneralReg, X86_64FloatReg> 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<X86_64GeneralReg, X86_64FloatReg> 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<X86_64GeneralReg, X86_64FloatReg> 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,
|
||||
],
|
||||
),
|
||||
] {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -31,6 +31,11 @@ pub enum LowLevel {
|
|||
DictSize,
|
||||
DictEmpty,
|
||||
DictInsert,
|
||||
DictRemove,
|
||||
DictContains,
|
||||
DictGetUnsafe,
|
||||
DictKeys,
|
||||
DictValues,
|
||||
NumAdd,
|
||||
NumAddWrap,
|
||||
NumAddChecked,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
"#
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T>(&mut self, _node_id: NodeId<T>, _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)
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ impl<T> PartialEq for NodeId<T> {
|
|||
|
||||
impl<T> Copy for NodeId<T> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pool {
|
||||
nodes: *mut [u8; NODE_BYTES],
|
||||
num_nodes: u32,
|
||||
|
@ -141,7 +142,7 @@ impl Pool {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get<T>(&self, node_id: NodeId<T>) -> &T {
|
||||
pub fn get<'a, 'b, T>(&'a self, node_id: NodeId<T>) -> &'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<T> {
|
|||
}
|
||||
|
||||
pub fn with_capacity(len: u32, pool: &mut Pool) -> Self {
|
||||
debug_assert!(size_of::<T>() <= NODE_BYTES);
|
||||
debug_assert!(size_of::<T>() <= NODE_BYTES, "{}", size_of::<T>());
|
||||
|
||||
if len == 0 {
|
||||
Self::empty(pool)
|
||||
|
|
|
@ -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<Type2> = 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();
|
||||
|
|
|
@ -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<Type2>;
|
||||
|
||||
|
@ -29,16 +30,32 @@ pub enum Type2 {
|
|||
},
|
||||
|
||||
EmptyTagUnion,
|
||||
TagUnion(PoolVec<(PoolStr, PoolVec<Type2>)>, TypeId),
|
||||
RecursiveTagUnion(Variable, PoolVec<(PoolStr, PoolVec<Type2>)>, TypeId),
|
||||
TagUnion(PoolVec<(PoolStr, PoolVec<Type2>)>, TypeId), // 12B = 8B + 4B
|
||||
RecursiveTagUnion(Variable, PoolVec<(PoolStr, PoolVec<Type2>)>, TypeId), // 16B = 4B + 8B + 4B
|
||||
|
||||
EmptyRec,
|
||||
Record(PoolVec<(PoolStr, RecordField<Type2>)>, TypeId),
|
||||
Record(PoolVec<(PoolStr, RecordField<Type2>)>, TypeId), // 12B = 8B + 4B
|
||||
|
||||
Function(PoolVec<Type2>, TypeId, TypeId), // 16B = 8B + 4B + 4B
|
||||
Apply(Symbol, PoolVec<Type2>), // 16B = 8B + 8B
|
||||
|
||||
Erroneous(roc_types::types::Problem),
|
||||
Erroneous(Problem2),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Problem2 {
|
||||
CanonicalizationProblem,
|
||||
CircularType(Symbol, NodeId<ErrorType>), // 12B = 8B + 4B
|
||||
CyclicAlias(Symbol, PoolVec<Symbol>), // 16B = 8B + 8B
|
||||
UnrecognizedIdent(PoolStr), // 8B
|
||||
Shadowed(Located<PoolStr>),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
} 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<dyn Error>> {
|
|||
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,
|
||||
);
|
||||
|
|
|
@ -13,8 +13,8 @@ use crate::{
|
|||
|
||||
pub fn render_expr2<'a>(
|
||||
env: &mut Env<'a>,
|
||||
size: &PhysicalSize<u32>,
|
||||
expr2: &Expr2,
|
||||
size: &PhysicalSize<u32>,
|
||||
position: Vector2<f32>,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue