mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge branch 'trunk' into gen-equality
This commit is contained in:
commit
76a9461cfe
41 changed files with 2123 additions and 53 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -816,6 +816,7 @@ dependencies = [
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -3,6 +3,7 @@ use roc_build::{
|
||||||
link::{link, rebuild_host, LinkType},
|
link::{link, rebuild_host, LinkType},
|
||||||
program,
|
program,
|
||||||
};
|
};
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_gen::llvm::build::OptLevel;
|
use roc_gen::llvm::build::OptLevel;
|
||||||
use roc_load::file::LoadingProblem;
|
use roc_load::file::LoadingProblem;
|
||||||
|
@ -47,6 +48,7 @@ pub fn build_file<'a>(
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
builtin_defs_map,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let path_to_platform = loaded.platform_path.clone();
|
let path_to_platform = loaded.platform_path.clone();
|
||||||
|
|
|
@ -3,6 +3,7 @@ use bumpalo::Bump;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use roc_build::link::module_to_dylib;
|
use roc_build::link::module_to_dylib;
|
||||||
use roc_build::program::FunctionIterator;
|
use roc_build::program::FunctionIterator;
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
use roc_fmt::annotation::Formattable;
|
use roc_fmt::annotation::Formattable;
|
||||||
use roc_fmt::annotation::{Newlines, Parens};
|
use roc_fmt::annotation::{Newlines, Parens};
|
||||||
|
@ -51,6 +52,7 @@ pub fn gen_and_eval<'a>(
|
||||||
src_dir,
|
src_dir,
|
||||||
exposed_types,
|
exposed_types,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
builtin_defs_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut loaded = match loaded {
|
let mut loaded = match loaded {
|
||||||
|
@ -125,6 +127,9 @@ pub fn gen_and_eval<'a>(
|
||||||
Ok(ReplOutput::Problems(lines))
|
Ok(ReplOutput::Problems(lines))
|
||||||
} else {
|
} else {
|
||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
|
|
||||||
|
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||||
|
|
||||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
|
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
|
||||||
let builder = context.create_builder();
|
let builder = context.create_builder();
|
||||||
|
|
||||||
|
@ -157,8 +162,6 @@ pub fn gen_and_eval<'a>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
|
||||||
|
|
||||||
let module = arena.alloc(module);
|
let module = arena.alloc(module);
|
||||||
let (module_pass, function_pass) =
|
let (module_pass, function_pass) =
|
||||||
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||||
|
|
|
@ -74,7 +74,6 @@ pub fn gen_from_mono_module(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the binary
|
// Generate the binary
|
||||||
|
|
||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
let module = arena.alloc(module_from_builtins(&context, "app"));
|
let module = arena.alloc(module_from_builtins(&context, "app"));
|
||||||
|
|
||||||
|
@ -94,9 +93,8 @@ pub fn gen_from_mono_module(
|
||||||
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
|
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
|
||||||
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||||
|
|
||||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
|
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||||
let env = roc_gen::llvm::build::Env {
|
let env = roc_gen::llvm::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
|
|
139
compiler/builtins/bitcode/src/dict.zig
Normal file
139
compiler/builtins/bitcode/src/dict.zig
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
const expectEqual = testing.expectEqual;
|
||||||
|
const mem = std.mem;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
|
const level_size = 32;
|
||||||
|
|
||||||
|
const InPlace = packed enum(u8) {
|
||||||
|
InPlace,
|
||||||
|
Clone,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Slot = packed enum(u8) {
|
||||||
|
Empty,
|
||||||
|
Filled,
|
||||||
|
PreviouslyFilled,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RocDict = extern struct {
|
||||||
|
dict_bytes: ?[*]u8,
|
||||||
|
dict_slot_len: usize,
|
||||||
|
dict_entries_len: usize,
|
||||||
|
|
||||||
|
pub fn empty() RocDict {
|
||||||
|
return RocDict{
|
||||||
|
.dict_entries_len = 0,
|
||||||
|
.dict_slot_len = 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_entries: usize,
|
||||||
|
key_size: usize,
|
||||||
|
value_size: usize,
|
||||||
|
) RocDict {
|
||||||
|
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);
|
||||||
|
|
||||||
|
return RocDict{
|
||||||
|
.dict_bytes = first_slot,
|
||||||
|
.dict_slot_len = number_of_slots,
|
||||||
|
.dict_entries_len = number_of_entries,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asU8ptr(self: RocDict) [*]u8 {
|
||||||
|
return @ptrCast([*]u8, self.dict_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(self: RocDict, key_size: usize, key_ptr: *const c_void, hash_code: u64) bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(self: RocDict) usize {
|
||||||
|
return self.dict_entries_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEmpty(self: RocDict) bool {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return new_dict;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dict.empty
|
||||||
|
pub fn dictEmpty() callconv(.C) RocDict {
|
||||||
|
return RocDict.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slotSize(key_size: usize, value_size: usize) usize {
|
||||||
|
return @sizeOf(Slot) + key_size + value_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dict.len
|
||||||
|
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);
|
||||||
|
|
||||||
|
const dict = dictEmpty();
|
||||||
|
|
||||||
|
expectEqual(false, dict.contains(4, @ptrCast(*const c_void, &""), 9));
|
||||||
|
}
|
255
compiler/builtins/bitcode/src/hash.zig
Normal file
255
compiler/builtins/bitcode/src/hash.zig
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2015-2021 Zig Contributors
|
||||||
|
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||||
|
// The MIT license requires this copyright notice to be included in all copies
|
||||||
|
// and substantial portions of the software.
|
||||||
|
const std = @import("std");
|
||||||
|
const str = @import("str.zig");
|
||||||
|
const mem = std.mem;
|
||||||
|
|
||||||
|
pub fn wyhash(seed: u64, bytes: ?[*]const u8, length: usize) callconv(.C) u64 {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
if (bytes) |nonnull| {
|
||||||
|
return wyhash_hash(seed, nonnull[0..length]);
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wyhash_rocstr(seed: u64, input: str.RocStr) callconv(.C) u64 {
|
||||||
|
return wyhash_hash(seed, input.asSlice());
|
||||||
|
}
|
||||||
|
|
||||||
|
const primes = [_]u64{
|
||||||
|
0xa0761d6478bd642f,
|
||||||
|
0xe7037ed1a0b428db,
|
||||||
|
0x8ebc6af09c88c6e3,
|
||||||
|
0x589965cc75374cc3,
|
||||||
|
0x1d8e4e27c47d124f,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn read_bytes(comptime bytes: u8, data: []const u8) u64 {
|
||||||
|
const T = std.meta.Int(.unsigned, 8 * bytes);
|
||||||
|
return mem.readIntLittle(T, data[0..bytes]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_8bytes_swapped(data: []const u8) u64 {
|
||||||
|
return (read_bytes(4, data) << 32 | read_bytes(4, data[4..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mum(a: u64, b: u64) u64 {
|
||||||
|
var r = std.math.mulWide(u64, a, b);
|
||||||
|
r = (r >> 64) ^ r;
|
||||||
|
return @truncate(u64, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mix0(a: u64, b: u64, seed: u64) u64 {
|
||||||
|
return mum(a ^ seed ^ primes[0], b ^ seed ^ primes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mix1(a: u64, b: u64, seed: u64) u64 {
|
||||||
|
return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wyhash version which does not store internal state for handling partial buffers.
|
||||||
|
// This is needed so that we can maximize the speed for the short key case, which will
|
||||||
|
// use the non-iterative api which the public Wyhash exposes.
|
||||||
|
const WyhashStateless = struct {
|
||||||
|
seed: u64,
|
||||||
|
msg_len: usize,
|
||||||
|
|
||||||
|
pub fn init(seed: u64) WyhashStateless {
|
||||||
|
return WyhashStateless{
|
||||||
|
.seed = seed,
|
||||||
|
.msg_len = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round(self: *WyhashStateless, b: []const u8) void {
|
||||||
|
std.debug.assert(b.len == 32);
|
||||||
|
|
||||||
|
self.seed = mix0(
|
||||||
|
read_bytes(8, b[0..]),
|
||||||
|
read_bytes(8, b[8..]),
|
||||||
|
self.seed,
|
||||||
|
) ^ mix1(
|
||||||
|
read_bytes(8, b[16..]),
|
||||||
|
read_bytes(8, b[24..]),
|
||||||
|
self.seed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(self: *WyhashStateless, b: []const u8) void {
|
||||||
|
std.debug.assert(b.len % 32 == 0);
|
||||||
|
|
||||||
|
var off: usize = 0;
|
||||||
|
while (off < b.len) : (off += 32) {
|
||||||
|
@call(.{ .modifier = .always_inline }, self.round, .{b[off .. off + 32]});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.msg_len += b.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn final(self: *WyhashStateless, b: []const u8) u64 {
|
||||||
|
std.debug.assert(b.len < 32);
|
||||||
|
|
||||||
|
const seed = self.seed;
|
||||||
|
const rem_len = @intCast(u5, b.len);
|
||||||
|
const rem_key = b[0..rem_len];
|
||||||
|
|
||||||
|
self.seed = switch (rem_len) {
|
||||||
|
0 => seed,
|
||||||
|
1 => mix0(read_bytes(1, rem_key), primes[4], seed),
|
||||||
|
2 => mix0(read_bytes(2, rem_key), primes[4], seed),
|
||||||
|
3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed),
|
||||||
|
4 => mix0(read_bytes(4, rem_key), primes[4], seed),
|
||||||
|
5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed),
|
||||||
|
6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed),
|
||||||
|
7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed),
|
||||||
|
8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed),
|
||||||
|
9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed),
|
||||||
|
10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed),
|
||||||
|
11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed),
|
||||||
|
12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed),
|
||||||
|
13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed),
|
||||||
|
14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed),
|
||||||
|
15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed),
|
||||||
|
16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed),
|
||||||
|
17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed),
|
||||||
|
18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed),
|
||||||
|
19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed),
|
||||||
|
20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed),
|
||||||
|
21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed),
|
||||||
|
22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed),
|
||||||
|
23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed),
|
||||||
|
24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed),
|
||||||
|
25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed),
|
||||||
|
26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed),
|
||||||
|
27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed),
|
||||||
|
28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed),
|
||||||
|
29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed),
|
||||||
|
30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed),
|
||||||
|
31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.msg_len += b.len;
|
||||||
|
return mum(self.seed ^ self.msg_len, primes[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(seed: u64, input: []const u8) u64 {
|
||||||
|
const aligned_len = input.len - (input.len % 32);
|
||||||
|
|
||||||
|
var c = WyhashStateless.init(seed);
|
||||||
|
@call(.{ .modifier = .always_inline }, c.update, .{input[0..aligned_len]});
|
||||||
|
return @call(.{ .modifier = .always_inline }, c.final, .{input[aligned_len..]});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Fast non-cryptographic 64bit hash function.
|
||||||
|
/// See https://github.com/wangyi-fudan/wyhash
|
||||||
|
pub const Wyhash = struct {
|
||||||
|
state: WyhashStateless,
|
||||||
|
|
||||||
|
buf: [32]u8,
|
||||||
|
buf_len: usize,
|
||||||
|
|
||||||
|
pub fn init(seed: u64) Wyhash {
|
||||||
|
return Wyhash{
|
||||||
|
.state = WyhashStateless.init(seed),
|
||||||
|
.buf = undefined,
|
||||||
|
.buf_len = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(self: *Wyhash, b: []const u8) void {
|
||||||
|
var off: usize = 0;
|
||||||
|
|
||||||
|
if (self.buf_len != 0 and self.buf_len + b.len >= 32) {
|
||||||
|
off += 32 - self.buf_len;
|
||||||
|
mem.copy(u8, self.buf[self.buf_len..], b[0..off]);
|
||||||
|
self.state.update(self.buf[0..]);
|
||||||
|
self.buf_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remain_len = b.len - off;
|
||||||
|
const aligned_len = remain_len - (remain_len % 32);
|
||||||
|
self.state.update(b[off .. off + aligned_len]);
|
||||||
|
|
||||||
|
mem.copy(u8, self.buf[self.buf_len..], b[off + aligned_len ..]);
|
||||||
|
self.buf_len += @intCast(u8, b[off + aligned_len ..].len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn final(self: *Wyhash) u64 {
|
||||||
|
const seed = self.state.seed;
|
||||||
|
const rem_len = @intCast(u5, self.buf_len);
|
||||||
|
const rem_key = self.buf[0..self.buf_len];
|
||||||
|
|
||||||
|
return self.state.final(rem_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(seed: u64, input: []const u8) u64 {
|
||||||
|
return WyhashStateless.hash(seed, input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn wyhash_hash(seed: u64, input: []const u8) u64 {
|
||||||
|
return Wyhash.hash(seed, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectEqual = std.testing.expectEqual;
|
||||||
|
|
||||||
|
test "test vectors" {
|
||||||
|
const hash = Wyhash.hash;
|
||||||
|
|
||||||
|
expectEqual(hash(0, ""), 0x0);
|
||||||
|
expectEqual(hash(1, "a"), 0xbed235177f41d328);
|
||||||
|
expectEqual(hash(2, "abc"), 0xbe348debe59b27c3);
|
||||||
|
expectEqual(hash(3, "message digest"), 0x37320f657213a290);
|
||||||
|
expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c);
|
||||||
|
expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f);
|
||||||
|
expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "test vectors streaming" {
|
||||||
|
var wh = Wyhash.init(5);
|
||||||
|
for ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") |e| {
|
||||||
|
wh.update(mem.asBytes(&e));
|
||||||
|
}
|
||||||
|
expectEqual(wh.final(), 0x602a1894d3bbfe7f);
|
||||||
|
|
||||||
|
const pattern = "1234567890";
|
||||||
|
const count = 8;
|
||||||
|
const result = 0x829e9c148b75970e;
|
||||||
|
expectEqual(Wyhash.hash(6, pattern ** 8), result);
|
||||||
|
|
||||||
|
wh = Wyhash.init(6);
|
||||||
|
var i: u32 = 0;
|
||||||
|
while (i < count) : (i += 1) {
|
||||||
|
wh.update(pattern);
|
||||||
|
}
|
||||||
|
expectEqual(wh.final(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "iterative non-divisible update" {
|
||||||
|
var buf: [8192]u8 = undefined;
|
||||||
|
for (buf) |*e, i| {
|
||||||
|
e.* = @truncate(u8, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const seed = 0x128dad08f;
|
||||||
|
|
||||||
|
var end: usize = 32;
|
||||||
|
while (end < buf.len) : (end += 32) {
|
||||||
|
const non_iterative_hash = Wyhash.hash(seed, buf[0..end]);
|
||||||
|
|
||||||
|
var wy = Wyhash.init(seed);
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < end) : (i += 33) {
|
||||||
|
wy.update(buf[i..std.math.min(i + 33, end)]);
|
||||||
|
}
|
||||||
|
const iterative_hash = wy.final();
|
||||||
|
|
||||||
|
std.testing.expectEqual(iterative_hash, non_iterative_hash);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,17 @@ const builtin = @import("builtin");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
// Dict Module
|
||||||
|
const dict = @import("dict.zig");
|
||||||
|
const hash = @import("hash.zig");
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
exportDictFn(dict.dictLen, "len");
|
||||||
|
exportDictFn(dict.dictEmpty, "empty");
|
||||||
|
exportDictFn(hash.wyhash, "hash");
|
||||||
|
exportDictFn(hash.wyhash_rocstr, "hash_str");
|
||||||
|
}
|
||||||
|
|
||||||
// Num Module
|
// Num Module
|
||||||
const num = @import("num.zig");
|
const num = @import("num.zig");
|
||||||
comptime {
|
comptime {
|
||||||
|
@ -37,6 +48,9 @@ fn exportNumFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
|
fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
exportBuiltinFn(func, "str." ++ func_name);
|
exportBuiltinFn(func, "str." ++ func_name);
|
||||||
}
|
}
|
||||||
|
fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||||
|
exportBuiltinFn(func, "dict." ++ func_name);
|
||||||
|
}
|
||||||
|
|
||||||
// Run all tests in imported modules
|
// Run all tests in imported modules
|
||||||
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
|
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
|
||||||
|
|
|
@ -40,8 +40,6 @@ pub const RocStr = extern struct {
|
||||||
// This clones the pointed-to bytes if they won't fit in a
|
// This clones the pointed-to bytes if they won't fit in a
|
||||||
// small string, and returns a (pointer, len) tuple which points to them.
|
// small string, and returns a (pointer, len) tuple which points to them.
|
||||||
pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr {
|
pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr {
|
||||||
const roc_str_size = @sizeOf(RocStr);
|
|
||||||
|
|
||||||
var result = RocStr.allocate(allocator, InPlace.Clone, length);
|
var result = RocStr.allocate(allocator, InPlace.Clone, length);
|
||||||
@memcpy(result.asU8ptr(), bytes_ptr, length);
|
@memcpy(result.asU8ptr(), bytes_ptr, length);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
interface Dict2
|
interface Dict
|
||||||
exposes [ isEmpty, map ]
|
exposes [ isEmpty, map ]
|
||||||
imports []
|
imports []
|
||||||
|
|
||||||
|
size : Dict * * -> Nat
|
||||||
|
|
||||||
isEmpty : Dict * * -> Bool
|
isEmpty : Dict * * -> Bool
|
||||||
|
|
||||||
## Convert each key and value in the #Dict to something new, by calling a conversion
|
## Convert each key and value in the #Dict to something new, by calling a conversion
|
||||||
|
@ -13,4 +15,8 @@ isEmpty : Dict * * -> Bool
|
||||||
##
|
##
|
||||||
## `map` functions like this are common in Roc, and they all work similarly.
|
## `map` functions like this are common in Roc, and they all work similarly.
|
||||||
## See for example #Result.map, #List.map, and #Set.map.
|
## See for example #Result.map, #List.map, and #Set.map.
|
||||||
map : List before, (before -> after) -> List after
|
map :
|
||||||
|
Dict beforeKey beforeValue,
|
||||||
|
(\{ key: beforeKey, value: beforeValue } ->
|
||||||
|
{ key: afterKey, value: afterValue }
|
||||||
|
) -> Dict afterKey afterValue
|
||||||
|
|
|
@ -34,3 +34,8 @@ pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||||
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
|
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
|
||||||
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
|
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
|
||||||
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
|
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
|
@ -4,7 +4,7 @@ use roc_module::symbol::Symbol;
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use roc_types::builtin_aliases::{
|
use roc_types::builtin_aliases::{
|
||||||
bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
|
bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
|
||||||
result_type, set_type, str_type,
|
result_type, set_type, str_type, u64_type,
|
||||||
};
|
};
|
||||||
use roc_types::solved_types::SolvedType;
|
use roc_types::solved_types::SolvedType;
|
||||||
use roc_types::subs::VarId;
|
use roc_types::subs::VarId;
|
||||||
|
@ -729,7 +729,22 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
|
|
||||||
// Dict module
|
// Dict module
|
||||||
|
|
||||||
// empty : Dict k v
|
// Dict.hashTestOnly : Nat, v -> Nat
|
||||||
|
add_type(
|
||||||
|
Symbol::DICT_TEST_HASH,
|
||||||
|
top_level_function(vec![u64_type(), flex(TVAR2)], Box::new(nat_type())),
|
||||||
|
);
|
||||||
|
|
||||||
|
// len : Dict * * -> Nat
|
||||||
|
add_type(
|
||||||
|
Symbol::DICT_LEN,
|
||||||
|
top_level_function(
|
||||||
|
vec![dict_type(flex(TVAR1), flex(TVAR2))],
|
||||||
|
Box::new(nat_type()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// empty : Dict * *
|
||||||
add_type(Symbol::DICT_EMPTY, dict_type(flex(TVAR1), flex(TVAR2)));
|
add_type(Symbol::DICT_EMPTY, dict_type(flex(TVAR1), flex(TVAR2)));
|
||||||
|
|
||||||
// singleton : k, v -> Dict k v
|
// singleton : k, v -> Dict k v
|
||||||
|
|
|
@ -906,6 +906,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
|
|
||||||
// Dict module
|
// Dict module
|
||||||
|
|
||||||
|
// : Attr * (Dict k v) -> Attr * Nat
|
||||||
|
add_type(Symbol::DICT_LEN, {
|
||||||
|
let_tvars! { star1, k , v, star2, int };
|
||||||
|
|
||||||
|
unique_function(vec![dict_type(star1, k, v)], int_type(star2, int))
|
||||||
|
});
|
||||||
|
|
||||||
// empty : Attr * (Dict k v)
|
// empty : Attr * (Dict k v)
|
||||||
add_type(Symbol::DICT_EMPTY, {
|
add_type(Symbol::DICT_EMPTY, {
|
||||||
let_tvars! { star, k , v };
|
let_tvars! { star, k , v };
|
||||||
|
|
|
@ -79,6 +79,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||||
LIST_KEEP_IF => list_keep_if,
|
LIST_KEEP_IF => list_keep_if,
|
||||||
LIST_WALK => list_walk,
|
LIST_WALK => list_walk,
|
||||||
LIST_WALK_BACKWARDS => list_walk_backwards,
|
LIST_WALK_BACKWARDS => list_walk_backwards,
|
||||||
|
DICT_TEST_HASH => dict_hash_test_only,
|
||||||
|
DICT_LEN => dict_len,
|
||||||
|
DICT_EMPTY => dict_empty,
|
||||||
|
DICT_INSERT => dict_insert,
|
||||||
NUM_ADD => num_add,
|
NUM_ADD => num_add,
|
||||||
NUM_ADD_CHECKED => num_add_checked,
|
NUM_ADD_CHECKED => num_add_checked,
|
||||||
NUM_ADD_WRAP => num_add_wrap,
|
NUM_ADD_WRAP => num_add_wrap,
|
||||||
|
@ -174,6 +178,10 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||||
Symbol::LIST_KEEP_IF => list_keep_if,
|
Symbol::LIST_KEEP_IF => list_keep_if,
|
||||||
Symbol::LIST_WALK => list_walk,
|
Symbol::LIST_WALK => list_walk,
|
||||||
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
|
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
|
||||||
|
Symbol::DICT_TEST_HASH => dict_hash_test_only,
|
||||||
|
Symbol::DICT_LEN => dict_len,
|
||||||
|
Symbol::DICT_EMPTY => dict_empty,
|
||||||
|
Symbol::DICT_INSERT => dict_insert,
|
||||||
Symbol::NUM_ADD => num_add,
|
Symbol::NUM_ADD => num_add,
|
||||||
Symbol::NUM_ADD_CHECKED => num_add_checked,
|
Symbol::NUM_ADD_CHECKED => num_add_checked,
|
||||||
Symbol::NUM_ADD_WRAP => num_add_wrap,
|
Symbol::NUM_ADD_WRAP => num_add_wrap,
|
||||||
|
@ -1780,7 +1788,7 @@ fn list_keep_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List.contains : List elem, elem, -> Bool
|
/// List.contains : List elem, elem -> Bool
|
||||||
fn list_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let list_var = var_store.fresh();
|
let list_var = var_store.fresh();
|
||||||
let elem_var = var_store.fresh();
|
let elem_var = var_store.fresh();
|
||||||
|
@ -1828,6 +1836,97 @@ fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dict.hashTestOnly : k, v -> Nat
|
||||||
|
pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let key_var = var_store.fresh();
|
||||||
|
let value_var = var_store.fresh();
|
||||||
|
let nat_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::Hash,
|
||||||
|
args: vec![
|
||||||
|
(key_var, Var(Symbol::ARG_1)),
|
||||||
|
(value_var, Var(Symbol::ARG_2)),
|
||||||
|
],
|
||||||
|
ret_var: nat_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(key_var, Symbol::ARG_1), (value_var, Symbol::ARG_2)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
nat_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dict.len : Dict * * -> Nat
|
||||||
|
fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let size_var = var_store.fresh();
|
||||||
|
let dict_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::DictSize,
|
||||||
|
args: vec![(dict_var, Var(Symbol::ARG_1))],
|
||||||
|
ret_var: size_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(dict_var, Symbol::ARG_1)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
size_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dict.empty : Dict * *
|
||||||
|
fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let dict_var = var_store.fresh();
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::DictEmpty,
|
||||||
|
args: vec![],
|
||||||
|
ret_var: dict_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
Def {
|
||||||
|
annotation: None,
|
||||||
|
expr_var: dict_var,
|
||||||
|
loc_expr: Located::at_zero(body),
|
||||||
|
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
|
||||||
|
pattern_vars: SendMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dict.insert : Dict k v, k, v -> Dict k v
|
||||||
|
fn dict_insert(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let dict_var = var_store.fresh();
|
||||||
|
let key_var = var_store.fresh();
|
||||||
|
let val_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::DictInsert,
|
||||||
|
args: vec![
|
||||||
|
(dict_var, Var(Symbol::ARG_1)),
|
||||||
|
(key_var, Var(Symbol::ARG_2)),
|
||||||
|
(val_var, Var(Symbol::ARG_3)),
|
||||||
|
],
|
||||||
|
ret_var: dict_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![
|
||||||
|
(dict_var, Symbol::ARG_1),
|
||||||
|
(key_var, Symbol::ARG_2),
|
||||||
|
(val_var, Symbol::ARG_3),
|
||||||
|
],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
dict_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Num.rem : Int, Int -> Result Int [ DivByZero ]*
|
/// Num.rem : Int, Int -> Result Int [ DivByZero ]*
|
||||||
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let num_var = var_store.fresh();
|
let num_var = var_store.fresh();
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::builtins;
|
|
||||||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::expr::{Expr, Output};
|
use crate::expr::{Expr, Output};
|
||||||
|
@ -41,7 +40,7 @@ pub struct ModuleOutput {
|
||||||
|
|
||||||
// TODO trim these down
|
// TODO trim these down
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn canonicalize_module_defs<'a>(
|
pub fn canonicalize_module_defs<'a, F>(
|
||||||
arena: &Bump,
|
arena: &Bump,
|
||||||
loc_defs: &'a [Located<ast::Def<'a>>],
|
loc_defs: &'a [Located<ast::Def<'a>>],
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
|
@ -52,7 +51,11 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||||
exposed_symbols: &MutSet<Symbol>,
|
exposed_symbols: &MutSet<Symbol>,
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
) -> Result<ModuleOutput, RuntimeError> {
|
look_up_builtin: F,
|
||||||
|
) -> Result<ModuleOutput, RuntimeError>
|
||||||
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
|
{
|
||||||
let mut can_exposed_imports = MutMap::default();
|
let mut can_exposed_imports = MutMap::default();
|
||||||
let mut scope = Scope::new(home, var_store);
|
let mut scope = Scope::new(home, var_store);
|
||||||
let num_deps = dep_idents.len();
|
let num_deps = dep_idents.len();
|
||||||
|
@ -284,7 +287,7 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
for symbol in references.iter() {
|
for symbol in references.iter() {
|
||||||
if symbol.is_builtin() {
|
if symbol.is_builtin() {
|
||||||
// this can fail when the symbol is for builtin types, or has no implementation yet
|
// this can fail when the symbol is for builtin types, or has no implementation yet
|
||||||
if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) {
|
if let Some(def) = look_up_builtin(*symbol, var_store) {
|
||||||
declarations.push(Declaration::Builtin(def));
|
declarations.push(Declaration::Builtin(def));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -967,6 +967,21 @@ mod test_can {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dict() {
|
||||||
|
let src = indoc!(
|
||||||
|
r#"
|
||||||
|
x = Dict.empty
|
||||||
|
|
||||||
|
Dict.len x
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
let arena = Bump::new();
|
||||||
|
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||||
|
|
||||||
|
assert_eq!(problems, Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unused_def_regression() {
|
fn unused_def_regression() {
|
||||||
let src = indoc!(
|
let src = indoc!(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::llvm::build_dict::{dict_empty, dict_insert, dict_len};
|
||||||
|
use crate::llvm::build_hash::generic_hash;
|
||||||
use crate::llvm::build_list::{
|
use crate::llvm::build_list::{
|
||||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
||||||
list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat,
|
list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat,
|
||||||
|
@ -7,7 +9,7 @@ use crate::llvm::build_str::{
|
||||||
str_concat, str_count_graphemes, str_ends_with, str_from_int, str_join_with,
|
str_concat, str_count_graphemes, str_ends_with, str_from_int, str_join_with,
|
||||||
str_number_of_bytes, str_split, str_starts_with, CHAR_LAYOUT,
|
str_number_of_bytes, str_split, str_starts_with, CHAR_LAYOUT,
|
||||||
};
|
};
|
||||||
use crate::llvm::compare::{generic_eq, generic_neq};
|
use crate::llvm::compare::{build_eq, build_neq, generic_eq, generic_neq};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{
|
||||||
basic_type_from_builtin, basic_type_from_layout, block_of_memory, block_of_memory_slices,
|
basic_type_from_builtin, basic_type_from_layout, block_of_memory, block_of_memory_slices,
|
||||||
collection, get_fn_type, get_ptr_type, ptr_int,
|
collection, get_fn_type, get_ptr_type, ptr_int,
|
||||||
|
@ -3978,6 +3980,31 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
empty,
|
empty,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Hash => {
|
||||||
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
let seed = load_symbol(scope, &args[0]);
|
||||||
|
let (value, layout) = load_symbol_and_layout(scope, &args[1]);
|
||||||
|
|
||||||
|
debug_assert!(seed.is_int_value());
|
||||||
|
|
||||||
|
generic_hash(env, layout_ids, seed.into_int_value(), value, layout).into()
|
||||||
|
}
|
||||||
|
DictSize => {
|
||||||
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
dict_len(env, scope, args[0])
|
||||||
|
}
|
||||||
|
DictEmpty => {
|
||||||
|
debug_assert_eq!(args.len(), 0);
|
||||||
|
dict_empty(env, scope)
|
||||||
|
}
|
||||||
|
DictInsert => {
|
||||||
|
debug_assert_eq!(args.len(), 3);
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
110
compiler/gen/src/llvm/build_dict.rs
Normal file
110
compiler/gen/src/llvm/build_dict.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
use crate::llvm::build::{
|
||||||
|
call_bitcode_fn, call_void_bitcode_fn, complex_bitcast, load_symbol, load_symbol_and_layout,
|
||||||
|
Env, Scope,
|
||||||
|
};
|
||||||
|
use crate::llvm::convert::collection;
|
||||||
|
use inkwell::types::BasicTypeEnum;
|
||||||
|
use inkwell::values::{BasicValueEnum, IntValue, StructValue};
|
||||||
|
use inkwell::AddressSpace;
|
||||||
|
use roc_builtins::bitcode;
|
||||||
|
use roc_module::symbol::Symbol;
|
||||||
|
use roc_mono::layout::{Builtin, Layout};
|
||||||
|
|
||||||
|
pub fn dict_len<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
dict_symbol: Symbol,
|
||||||
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
let ctx = env.context;
|
||||||
|
|
||||||
|
let (_, dict_layout) = load_symbol_and_layout(scope, &dict_symbol);
|
||||||
|
|
||||||
|
match dict_layout {
|
||||||
|
Layout::Builtin(Builtin::Dict(_, _)) => {
|
||||||
|
let dict_as_int = dict_symbol_to_i128(env, scope, dict_symbol);
|
||||||
|
|
||||||
|
call_bitcode_fn(env, &[dict_as_int.into()], &bitcode::DICT_LEN)
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(),
|
||||||
|
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dict_empty<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
_scope: &Scope<'a, 'ctx>,
|
||||||
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
// get the RocDict type defined by zig
|
||||||
|
let roc_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
|
||||||
|
|
||||||
|
// we must give a pointer for the bitcode function to write the result into
|
||||||
|
let result_alloc = env.builder.build_alloca(roc_dict_type, "dict_empty");
|
||||||
|
|
||||||
|
call_void_bitcode_fn(env, &[result_alloc.into()], &bitcode::DICT_EMPTY);
|
||||||
|
|
||||||
|
let result = env
|
||||||
|
.builder
|
||||||
|
.build_load(result_alloc, "load_result")
|
||||||
|
.into_struct_value();
|
||||||
|
|
||||||
|
zig_dict_to_struct(env, result).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dict_symbol_to_i128<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
symbol: Symbol,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
let dict = load_symbol(scope, &symbol);
|
||||||
|
|
||||||
|
let i128_type = env.context.i128_type().into();
|
||||||
|
|
||||||
|
complex_bitcast(&env.builder, dict, i128_type, "dict_to_i128").into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zig_dict_to_struct<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
zig_dict: StructValue<'ctx>,
|
||||||
|
) -> StructValue<'ctx> {
|
||||||
|
let builder = env.builder;
|
||||||
|
|
||||||
|
// get the RocStr type defined by zig
|
||||||
|
let zig_str_type = env.module.get_struct_type("dict.RocDict").unwrap();
|
||||||
|
|
||||||
|
let ret_type = BasicTypeEnum::StructType(collection(env.context, env.ptr_bytes));
|
||||||
|
|
||||||
|
// a roundabout way of casting (LLVM does not accept a standard bitcast)
|
||||||
|
let allocation = builder.build_alloca(zig_str_type, "zig_result");
|
||||||
|
|
||||||
|
builder.build_store(allocation, zig_dict);
|
||||||
|
|
||||||
|
let ptr3 = builder
|
||||||
|
.build_bitcast(
|
||||||
|
allocation,
|
||||||
|
env.context.i128_type().ptr_type(AddressSpace::Generic),
|
||||||
|
"cast",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
let ptr4 = builder
|
||||||
|
.build_bitcast(
|
||||||
|
ptr3,
|
||||||
|
ret_type.into_struct_type().ptr_type(AddressSpace::Generic),
|
||||||
|
"cast",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
builder.build_load(ptr4, "load").into_struct_value()
|
||||||
|
}
|
945
compiler/gen/src/llvm/build_hash.rs
Normal file
945
compiler/gen/src/llvm/build_hash.rs
Normal file
|
@ -0,0 +1,945 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
use crate::llvm::build_str;
|
||||||
|
use crate::llvm::convert::basic_type_from_layout;
|
||||||
|
use bumpalo::collections::Vec;
|
||||||
|
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||||
|
use roc_builtins::bitcode;
|
||||||
|
use roc_module::symbol::Symbol;
|
||||||
|
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum WhenRecursive<'a> {
|
||||||
|
Unreachable,
|
||||||
|
Loop(UnionLayout<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generic_hash<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
val: BasicValueEnum<'ctx>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
// NOTE: C and Zig use this value for their initial HashMap seed: 0xc70f6907
|
||||||
|
build_hash_layout(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
seed,
|
||||||
|
val,
|
||||||
|
layout,
|
||||||
|
WhenRecursive::Unreachable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_layout<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
val: BasicValueEnum<'ctx>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
match layout {
|
||||||
|
Layout::Builtin(builtin) => {
|
||||||
|
hash_builtin(env, layout_ids, seed, val, layout, builtin, when_recursive)
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::Struct(fields) => build_hash_struct(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
fields,
|
||||||
|
when_recursive,
|
||||||
|
seed,
|
||||||
|
val.into_struct_value(),
|
||||||
|
),
|
||||||
|
|
||||||
|
Layout::PhantomEmptyStruct => {
|
||||||
|
// just does nothing and returns the seed
|
||||||
|
seed
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::Union(union_layout) => {
|
||||||
|
build_hash_tag(env, layout_ids, layout, union_layout, seed, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::RecursivePointer => match when_recursive {
|
||||||
|
WhenRecursive::Unreachable => {
|
||||||
|
unreachable!("recursion pointers should never be hashed directly")
|
||||||
|
}
|
||||||
|
WhenRecursive::Loop(union_layout) => {
|
||||||
|
let layout = Layout::Union(union_layout.clone());
|
||||||
|
|
||||||
|
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
|
||||||
|
|
||||||
|
// cast the i64 pointer to a pointer to block of memory
|
||||||
|
let field_cast = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(val, bt, "i64_to_opaque")
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
build_hash_tag(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
&layout,
|
||||||
|
&union_layout,
|
||||||
|
seed,
|
||||||
|
field_cast.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Layout::Pointer(_) => {
|
||||||
|
unreachable!("unused")
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
|
||||||
|
unreachable!("the type system will guarantee these are never hashed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_hash_layout<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
val: BasicValueEnum<'ctx>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
build_hash_layout(env, layout_ids, seed, val, layout, when_recursive)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_builtin<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
val: BasicValueEnum<'ctx>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
builtin: &Builtin<'a>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
let ptr_bytes = env.ptr_bytes;
|
||||||
|
|
||||||
|
match builtin {
|
||||||
|
Builtin::Int128
|
||||||
|
| Builtin::Int64
|
||||||
|
| Builtin::Int32
|
||||||
|
| Builtin::Int16
|
||||||
|
| Builtin::Int8
|
||||||
|
| Builtin::Int1
|
||||||
|
| Builtin::Float64
|
||||||
|
| Builtin::Float32
|
||||||
|
| Builtin::Float128
|
||||||
|
| Builtin::Float16
|
||||||
|
| Builtin::Usize => {
|
||||||
|
let hash_bytes = store_and_use_as_u8_ptr(env, val, &layout);
|
||||||
|
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
|
||||||
|
}
|
||||||
|
Builtin::Str => {
|
||||||
|
// let zig deal with big vs small string
|
||||||
|
call_bitcode_fn(
|
||||||
|
env,
|
||||||
|
&[seed.into(), build_str::str_to_i128(env, val).into()],
|
||||||
|
&bitcode::DICT_HASH_STR,
|
||||||
|
)
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
Builtin::EmptyStr | Builtin::EmptyDict | Builtin::EmptyList | Builtin::EmptySet => {
|
||||||
|
hash_empty_collection(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builtin::Dict(_, _) => {
|
||||||
|
todo!("Implement hash for Dict")
|
||||||
|
}
|
||||||
|
Builtin::Set(_) => {
|
||||||
|
todo!("Implement Hash for Set")
|
||||||
|
}
|
||||||
|
Builtin::List(_, element_layout) => build_hash_list(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
layout,
|
||||||
|
element_layout,
|
||||||
|
when_recursive,
|
||||||
|
seed,
|
||||||
|
val.into_struct_value(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_struct<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
field_layouts: &'a [Layout<'a>],
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
value: StructValue<'ctx>,
|
||||||
|
) -> IntValue<'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 struct_layout = Layout::Struct(field_layouts);
|
||||||
|
|
||||||
|
let symbol = Symbol::GENERIC_HASH;
|
||||||
|
let fn_name = layout_ids
|
||||||
|
.get(symbol, &struct_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 arena = env.arena;
|
||||||
|
|
||||||
|
let seed_type = env.context.i64_type();
|
||||||
|
|
||||||
|
let arg_type =
|
||||||
|
basic_type_from_layout(arena, env.context, &struct_layout, env.ptr_bytes);
|
||||||
|
|
||||||
|
let function_value = crate::llvm::refcounting::build_header_help(
|
||||||
|
env,
|
||||||
|
&fn_name,
|
||||||
|
seed_type.into(),
|
||||||
|
&[seed_type.into(), arg_type],
|
||||||
|
);
|
||||||
|
|
||||||
|
build_hash_struct_help(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
function_value,
|
||||||
|
when_recursive,
|
||||||
|
field_layouts,
|
||||||
|
);
|
||||||
|
|
||||||
|
function_value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
env.builder
|
||||||
|
.set_current_debug_location(env.context, di_location);
|
||||||
|
let call = env
|
||||||
|
.builder
|
||||||
|
.build_call(function, &[seed.into(), value.into()], "struct_hash");
|
||||||
|
|
||||||
|
call.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
|
call.try_as_basic_value().left().unwrap().into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_struct_help<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add args to scope
|
||||||
|
let mut it = parent.get_param_iter();
|
||||||
|
let seed = it.next().unwrap().into_int_value();
|
||||||
|
let value = it.next().unwrap().into_struct_value();
|
||||||
|
|
||||||
|
set_name(seed.into(), Symbol::ARG_1.ident_string(&env.interns));
|
||||||
|
set_name(value.into(), Symbol::ARG_2.ident_string(&env.interns));
|
||||||
|
|
||||||
|
let entry = ctx.append_basic_block(parent, "entry");
|
||||||
|
env.builder.position_at_end(entry);
|
||||||
|
|
||||||
|
let result = hash_struct(env, layout_ids, seed, value, when_recursive, field_layouts);
|
||||||
|
|
||||||
|
env.builder.build_return(Some(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_struct<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
mut seed: IntValue<'ctx>,
|
||||||
|
value: StructValue<'ctx>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
field_layouts: &[Layout<'a>],
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
let ptr_bytes = env.ptr_bytes;
|
||||||
|
|
||||||
|
let layout = Layout::Struct(field_layouts);
|
||||||
|
|
||||||
|
// Optimization: if the bit representation of equal values is the same
|
||||||
|
// just hash the bits. Caveat here is tags: e.g. `Nothing` in `Just a`
|
||||||
|
// contains garbage bits after the tag (currently)
|
||||||
|
if false {
|
||||||
|
// this is a struct of only basic types, so we can just hash its bits
|
||||||
|
let hash_bytes = store_and_use_as_u8_ptr(env, value.into(), &layout);
|
||||||
|
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
|
||||||
|
} else {
|
||||||
|
for (index, field_layout) in field_layouts.iter().enumerate() {
|
||||||
|
let field = env
|
||||||
|
.builder
|
||||||
|
.build_extract_value(value, index as u32, "eq_field")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Layout::RecursivePointer = field_layout {
|
||||||
|
match &when_recursive {
|
||||||
|
WhenRecursive::Unreachable => {
|
||||||
|
unreachable!("The current layout should not be recursive, but is")
|
||||||
|
}
|
||||||
|
WhenRecursive::Loop(union_layout) => {
|
||||||
|
let field_layout = Layout::Union(union_layout.clone());
|
||||||
|
|
||||||
|
let bt = basic_type_from_layout(
|
||||||
|
env.arena,
|
||||||
|
env.context,
|
||||||
|
&field_layout,
|
||||||
|
env.ptr_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
// cast the i64 pointer to a pointer to block of memory
|
||||||
|
let field_cast = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(field, bt, "i64_to_opaque")
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
seed = append_hash_layout(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
seed,
|
||||||
|
field_cast.into(),
|
||||||
|
&field_layout,
|
||||||
|
when_recursive.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
seed = append_hash_layout(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
seed,
|
||||||
|
field,
|
||||||
|
field_layout,
|
||||||
|
when_recursive.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_tag<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
union_layout: &UnionLayout<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
value: BasicValueEnum<'ctx>,
|
||||||
|
) -> IntValue<'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;
|
||||||
|
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 arena = env.arena;
|
||||||
|
|
||||||
|
let seed_type = env.context.i64_type();
|
||||||
|
|
||||||
|
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
|
||||||
|
|
||||||
|
let function_value = crate::llvm::refcounting::build_header_help(
|
||||||
|
env,
|
||||||
|
&fn_name,
|
||||||
|
seed_type.into(),
|
||||||
|
&[seed_type.into(), arg_type],
|
||||||
|
);
|
||||||
|
|
||||||
|
build_hash_tag_help(env, layout_ids, function_value, union_layout);
|
||||||
|
|
||||||
|
function_value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
env.builder
|
||||||
|
.set_current_debug_location(env.context, di_location);
|
||||||
|
let call = env
|
||||||
|
.builder
|
||||||
|
.build_call(function, &[seed.into(), value], "struct_hash");
|
||||||
|
|
||||||
|
call.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
|
call.try_as_basic_value().left().unwrap().into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_tag_help<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add args to scope
|
||||||
|
let mut it = parent.get_param_iter();
|
||||||
|
let seed = it.next().unwrap().into_int_value();
|
||||||
|
let value = it.next().unwrap();
|
||||||
|
|
||||||
|
set_name(seed.into(), Symbol::ARG_1.ident_string(&env.interns));
|
||||||
|
set_name(value, Symbol::ARG_2.ident_string(&env.interns));
|
||||||
|
|
||||||
|
let entry = ctx.append_basic_block(parent, "entry");
|
||||||
|
env.builder.position_at_end(entry);
|
||||||
|
|
||||||
|
let result = hash_tag(env, layout_ids, parent, seed, value, union_layout);
|
||||||
|
|
||||||
|
env.builder.build_return(Some(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_tag<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
tag: BasicValueEnum<'ctx>,
|
||||||
|
union_layout: &UnionLayout<'a>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
use UnionLayout::*;
|
||||||
|
|
||||||
|
let entry_block = env.builder.get_insert_block().unwrap();
|
||||||
|
|
||||||
|
let merge_block = env.context.append_basic_block(parent, "merge_block");
|
||||||
|
env.builder.position_at_end(merge_block);
|
||||||
|
|
||||||
|
let merge_phi = env.builder.build_phi(env.context.i64_type(), "merge_hash");
|
||||||
|
|
||||||
|
env.builder.position_at_end(entry_block);
|
||||||
|
match union_layout {
|
||||||
|
NonRecursive(tags) => {
|
||||||
|
// SAFETY we know that non-recursive tags cannot be NULL
|
||||||
|
let tag_id = nonrec_tag_id(env, tag.into_struct_value());
|
||||||
|
|
||||||
|
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||||
|
|
||||||
|
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||||
|
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
|
||||||
|
// TODO drop tag id?
|
||||||
|
let struct_layout = Layout::Struct(field_layouts);
|
||||||
|
|
||||||
|
let wrapper_type =
|
||||||
|
basic_type_from_layout(env.arena, env.context, &struct_layout, env.ptr_bytes);
|
||||||
|
debug_assert!(wrapper_type.is_struct_type());
|
||||||
|
|
||||||
|
let as_struct =
|
||||||
|
cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type);
|
||||||
|
|
||||||
|
let answer = build_hash_struct(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
field_layouts,
|
||||||
|
WhenRecursive::Unreachable,
|
||||||
|
seed,
|
||||||
|
as_struct,
|
||||||
|
);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
|
||||||
|
cases.push((
|
||||||
|
env.context.i64_type().const_int(tag_id as u64, false),
|
||||||
|
block,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
env.builder.position_at_end(entry_block);
|
||||||
|
|
||||||
|
let default = cases.pop().unwrap().1;
|
||||||
|
|
||||||
|
env.builder.build_switch(tag_id, default, &cases);
|
||||||
|
}
|
||||||
|
Recursive(tags) => {
|
||||||
|
// SAFETY recursive tag unions are not NULL
|
||||||
|
let tag_id = unsafe { rec_tag_id_unsafe(env, tag.into_pointer_value()) };
|
||||||
|
|
||||||
|
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||||
|
|
||||||
|
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||||
|
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
|
||||||
|
let answer = hash_ptr_to_struct(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
union_layout,
|
||||||
|
field_layouts,
|
||||||
|
seed,
|
||||||
|
tag.into_pointer_value(),
|
||||||
|
);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
|
||||||
|
cases.push((
|
||||||
|
env.context.i64_type().const_int(tag_id as u64, false),
|
||||||
|
block,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
env.builder.position_at_end(entry_block);
|
||||||
|
|
||||||
|
let default = cases.pop().unwrap().1;
|
||||||
|
|
||||||
|
env.builder.build_switch(tag_id, default, &cases);
|
||||||
|
}
|
||||||
|
NullableUnwrapped { other_fields, .. } => {
|
||||||
|
let tag = tag.into_pointer_value();
|
||||||
|
let other_fields = &other_fields[1..];
|
||||||
|
|
||||||
|
let is_null = env.builder.build_is_null(tag, "is_null");
|
||||||
|
|
||||||
|
let hash_null_block = env.context.append_basic_block(parent, "hash_null_block");
|
||||||
|
let hash_other_block = env.context.append_basic_block(parent, "hash_other_block");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(is_null, hash_null_block, hash_other_block);
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(hash_null_block);
|
||||||
|
|
||||||
|
let answer = hash_null(seed);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, hash_null_block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(hash_other_block);
|
||||||
|
|
||||||
|
let answer =
|
||||||
|
hash_ptr_to_struct(env, layout_ids, union_layout, other_fields, seed, tag);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, hash_other_block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NullableWrapped { other_tags, .. } => {
|
||||||
|
let tag = tag.into_pointer_value();
|
||||||
|
|
||||||
|
let is_null = env.builder.build_is_null(tag, "is_null");
|
||||||
|
|
||||||
|
let hash_null_block = env.context.append_basic_block(parent, "hash_null_block");
|
||||||
|
let hash_other_block = env.context.append_basic_block(parent, "hash_other_block");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(is_null, hash_null_block, hash_other_block);
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(hash_null_block);
|
||||||
|
|
||||||
|
let answer = hash_null(seed);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, hash_null_block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(hash_other_block);
|
||||||
|
|
||||||
|
// SAFETY recursive tag unions are not NULL
|
||||||
|
let tag_id = unsafe { rec_tag_id_unsafe(env, tag) };
|
||||||
|
|
||||||
|
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
|
||||||
|
|
||||||
|
for (tag_id, field_layouts) in other_tags.iter().enumerate() {
|
||||||
|
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
|
||||||
|
let answer =
|
||||||
|
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
|
||||||
|
cases.push((
|
||||||
|
env.context.i64_type().const_int(tag_id as u64, false),
|
||||||
|
block,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
env.builder.position_at_end(hash_other_block);
|
||||||
|
|
||||||
|
let default = cases.pop().unwrap().1;
|
||||||
|
|
||||||
|
env.builder.build_switch(tag_id, default, &cases);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NonNullableUnwrapped(field_layouts) => {
|
||||||
|
let answer = hash_ptr_to_struct(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
union_layout,
|
||||||
|
field_layouts,
|
||||||
|
seed,
|
||||||
|
tag.into_pointer_value(),
|
||||||
|
);
|
||||||
|
|
||||||
|
merge_phi.add_incoming(&[(&answer, entry_block)]);
|
||||||
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.builder.position_at_end(merge_block);
|
||||||
|
|
||||||
|
merge_phi.as_basic_value().into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_list<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
element_layout: &Layout<'a>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
value: StructValue<'ctx>,
|
||||||
|
) -> IntValue<'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;
|
||||||
|
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 arena = env.arena;
|
||||||
|
|
||||||
|
let seed_type = env.context.i64_type();
|
||||||
|
|
||||||
|
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
|
||||||
|
|
||||||
|
let function_value = crate::llvm::refcounting::build_header_help(
|
||||||
|
env,
|
||||||
|
&fn_name,
|
||||||
|
seed_type.into(),
|
||||||
|
&[seed_type.into(), arg_type],
|
||||||
|
);
|
||||||
|
|
||||||
|
build_hash_list_help(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
function_value,
|
||||||
|
when_recursive,
|
||||||
|
element_layout,
|
||||||
|
);
|
||||||
|
|
||||||
|
function_value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
env.builder
|
||||||
|
.set_current_debug_location(env.context, di_location);
|
||||||
|
let call = env
|
||||||
|
.builder
|
||||||
|
.build_call(function, &[seed.into(), value.into()], "struct_hash");
|
||||||
|
|
||||||
|
call.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
|
call.try_as_basic_value().left().unwrap().into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hash_list_help<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add args to scope
|
||||||
|
let mut it = parent.get_param_iter();
|
||||||
|
let seed = it.next().unwrap().into_int_value();
|
||||||
|
let value = it.next().unwrap().into_struct_value();
|
||||||
|
|
||||||
|
set_name(seed.into(), Symbol::ARG_1.ident_string(&env.interns));
|
||||||
|
set_name(value.into(), Symbol::ARG_2.ident_string(&env.interns));
|
||||||
|
|
||||||
|
let entry = ctx.append_basic_block(parent, "entry");
|
||||||
|
env.builder.position_at_end(entry);
|
||||||
|
|
||||||
|
let result = hash_list(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
parent,
|
||||||
|
seed,
|
||||||
|
value,
|
||||||
|
when_recursive,
|
||||||
|
element_layout,
|
||||||
|
);
|
||||||
|
|
||||||
|
env.builder.build_return(Some(&result));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_list<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
value: StructValue<'ctx>,
|
||||||
|
when_recursive: WhenRecursive<'a>,
|
||||||
|
element_layout: &Layout<'a>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||||
|
use inkwell::types::BasicType;
|
||||||
|
|
||||||
|
// hash of a list is the hash of its elements
|
||||||
|
let done_block = env.context.append_basic_block(parent, "done");
|
||||||
|
let loop_block = env.context.append_basic_block(parent, "loop");
|
||||||
|
|
||||||
|
let element_type =
|
||||||
|
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes);
|
||||||
|
let ptr_type = element_type.ptr_type(inkwell::AddressSpace::Generic);
|
||||||
|
|
||||||
|
let (length, ptr) = load_list(env.builder, value, ptr_type);
|
||||||
|
|
||||||
|
let result = env.builder.build_alloca(env.context.i64_type(), "result");
|
||||||
|
env.builder.build_store(result, seed);
|
||||||
|
|
||||||
|
let is_empty = env.builder.build_int_compare(
|
||||||
|
inkwell::IntPredicate::EQ,
|
||||||
|
length,
|
||||||
|
env.ptr_int().const_zero(),
|
||||||
|
"is_empty",
|
||||||
|
);
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(is_empty, done_block, loop_block);
|
||||||
|
|
||||||
|
env.builder.position_at_end(loop_block);
|
||||||
|
|
||||||
|
let loop_fn = |_index, element| {
|
||||||
|
let seed = env
|
||||||
|
.builder
|
||||||
|
.build_load(result, "load_current")
|
||||||
|
.into_int_value();
|
||||||
|
|
||||||
|
let answer = append_hash_layout(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
seed,
|
||||||
|
element,
|
||||||
|
element_layout,
|
||||||
|
when_recursive.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
env.builder.build_store(result, answer);
|
||||||
|
};
|
||||||
|
|
||||||
|
incrementing_elem_loop(
|
||||||
|
env.builder,
|
||||||
|
env.context,
|
||||||
|
parent,
|
||||||
|
ptr,
|
||||||
|
length,
|
||||||
|
"current_index",
|
||||||
|
loop_fn,
|
||||||
|
);
|
||||||
|
|
||||||
|
env.builder.build_unconditional_branch(done_block);
|
||||||
|
|
||||||
|
env.builder.position_at_end(done_block);
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_load(result, "load_current")
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_null(seed: IntValue<'_>) -> IntValue<'_> {
|
||||||
|
seed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_empty_collection(seed: IntValue<'_>) -> IntValue<'_> {
|
||||||
|
seed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_ptr_to_struct<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
union_layout: &UnionLayout<'a>,
|
||||||
|
field_layouts: &'a [Layout<'a>],
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
tag: PointerValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
use inkwell::types::BasicType;
|
||||||
|
|
||||||
|
let struct_layout = Layout::Struct(field_layouts);
|
||||||
|
|
||||||
|
let wrapper_type =
|
||||||
|
basic_type_from_layout(env.arena, env.context, &struct_layout, env.ptr_bytes);
|
||||||
|
debug_assert!(wrapper_type.is_struct_type());
|
||||||
|
|
||||||
|
// cast the opaque pointer to a pointer of the correct shape
|
||||||
|
let struct_ptr = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(
|
||||||
|
tag,
|
||||||
|
wrapper_type.ptr_type(inkwell::AddressSpace::Generic),
|
||||||
|
"opaque_to_correct",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
let struct_value = env
|
||||||
|
.builder
|
||||||
|
.build_load(struct_ptr, "load_struct1")
|
||||||
|
.into_struct_value();
|
||||||
|
|
||||||
|
build_hash_struct(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
field_layouts,
|
||||||
|
WhenRecursive::Loop(union_layout.clone()),
|
||||||
|
seed,
|
||||||
|
struct_value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
value: BasicValueEnum<'ctx>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
) -> PointerValue<'ctx> {
|
||||||
|
let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
|
||||||
|
let alloc = env.builder.build_alloca(basic_type, "store");
|
||||||
|
env.builder.build_store(alloc, value);
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_bitcast(
|
||||||
|
alloc,
|
||||||
|
env.context
|
||||||
|
.i8_type()
|
||||||
|
.ptr_type(inkwell::AddressSpace::Generic),
|
||||||
|
"as_u8_ptr",
|
||||||
|
)
|
||||||
|
.into_pointer_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_bitcode_fn<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
seed: IntValue<'ctx>,
|
||||||
|
buffer: PointerValue<'ctx>,
|
||||||
|
width: u32,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
let num_bytes = env.context.i64_type().const_int(width as u64, false);
|
||||||
|
|
||||||
|
call_bitcode_fn(
|
||||||
|
env,
|
||||||
|
&[seed.into(), buffer.into(), num_bytes.into()],
|
||||||
|
&bitcode::DICT_HASH,
|
||||||
|
)
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonrec_tag_id<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
tag: StructValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
complex_bitcast(
|
||||||
|
env.builder,
|
||||||
|
tag.into(),
|
||||||
|
env.context.i64_type().into(),
|
||||||
|
"load_tag_id",
|
||||||
|
)
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn rec_tag_id_unsafe<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
tag: PointerValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
let ptr = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(
|
||||||
|
tag,
|
||||||
|
env.context
|
||||||
|
.i64_type()
|
||||||
|
.ptr_type(inkwell::AddressSpace::Generic),
|
||||||
|
"cast_for_tag_id",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
env.builder.build_load(ptr, "load_tag_id").into_int_value()
|
||||||
|
}
|
|
@ -133,7 +133,7 @@ pub fn list_repeat<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.prepend List elem, elem -> List elem
|
/// List.prepend : List elem, elem -> List elem
|
||||||
pub fn list_prepend<'a, 'ctx, 'env>(
|
pub fn list_prepend<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
inplace: InPlace,
|
inplace: InPlace,
|
||||||
|
|
|
@ -3,8 +3,9 @@ use crate::llvm::build::{
|
||||||
};
|
};
|
||||||
use crate::llvm::build_list::{allocate_list, store_list};
|
use crate::llvm::build_list::{allocate_list, store_list};
|
||||||
use crate::llvm::convert::collection;
|
use crate::llvm::convert::collection;
|
||||||
|
use inkwell::builder::Builder;
|
||||||
use inkwell::types::BasicTypeEnum;
|
use inkwell::types::BasicTypeEnum;
|
||||||
use inkwell::values::{BasicValueEnum, IntValue, StructValue};
|
use inkwell::values::{BasicValueEnum, IntValue, PointerValue, StructValue};
|
||||||
use inkwell::AddressSpace;
|
use inkwell::AddressSpace;
|
||||||
use roc_builtins::bitcode;
|
use roc_builtins::bitcode;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
|
@ -68,7 +69,7 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>(
|
||||||
complex_bitcast(&env.builder, string, i128_type, "str_to_i128").into_int_value()
|
complex_bitcast(&env.builder, string, i128_type, "str_to_i128").into_int_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn str_to_i128<'a, 'ctx, 'env>(
|
pub fn str_to_i128<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
value: BasicValueEnum<'ctx>,
|
value: BasicValueEnum<'ctx>,
|
||||||
) -> IntValue<'ctx> {
|
) -> IntValue<'ctx> {
|
||||||
|
@ -125,6 +126,24 @@ fn zig_str_to_struct<'a, 'ctx, 'env>(
|
||||||
builder.build_load(ptr4, "load").into_struct_value()
|
builder.build_load(ptr4, "load").into_struct_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn destructure<'ctx>(
|
||||||
|
builder: &Builder<'ctx>,
|
||||||
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
) -> (PointerValue<'ctx>, IntValue<'ctx>) {
|
||||||
|
let length = builder
|
||||||
|
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len")
|
||||||
|
.unwrap()
|
||||||
|
.into_int_value();
|
||||||
|
|
||||||
|
// a `*mut u8` pointer
|
||||||
|
let generic_ptr = builder
|
||||||
|
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
|
||||||
|
.unwrap()
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
(generic_ptr, length)
|
||||||
|
}
|
||||||
|
|
||||||
/// Str.concat : Str, Str -> Str
|
/// Str.concat : Str, Str -> Str
|
||||||
pub fn str_concat<'a, 'ctx, 'env>(
|
pub fn str_concat<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
|
|
@ -185,7 +185,7 @@ pub fn basic_type_from_builtin<'ctx>(
|
||||||
Float64 => context.f64_type().as_basic_type_enum(),
|
Float64 => context.f64_type().as_basic_type_enum(),
|
||||||
Float32 => context.f32_type().as_basic_type_enum(),
|
Float32 => context.f32_type().as_basic_type_enum(),
|
||||||
Float16 => context.f16_type().as_basic_type_enum(),
|
Float16 => context.f16_type().as_basic_type_enum(),
|
||||||
Dict(_, _) | EmptyDict => panic!("TODO layout_to_basic_type for Builtin::Dict"),
|
Dict(_, _) | EmptyDict => collection(context, ptr_bytes).into(),
|
||||||
Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"),
|
Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"),
|
||||||
List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(),
|
List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(),
|
||||||
EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)),
|
EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)),
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod build;
|
pub mod build;
|
||||||
|
pub mod build_dict;
|
||||||
|
pub mod build_hash;
|
||||||
pub mod build_list;
|
pub mod build_list;
|
||||||
pub mod build_str;
|
pub mod build_str;
|
||||||
pub mod compare;
|
pub mod compare;
|
||||||
|
|
28
compiler/gen/tests/gen_dict.rs
Normal file
28
compiler/gen/tests/gen_dict.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pretty_assertions;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate indoc;
|
||||||
|
|
||||||
|
extern crate bumpalo;
|
||||||
|
extern crate inkwell;
|
||||||
|
extern crate libc;
|
||||||
|
extern crate roc_gen;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod gen_dict {
|
||||||
|
#[test]
|
||||||
|
fn dict_empty_len() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Dict.len Dict.empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
usize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
157
compiler/gen/tests/gen_hash.rs
Normal file
157
compiler/gen/tests/gen_hash.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pretty_assertions;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate indoc;
|
||||||
|
|
||||||
|
extern crate bumpalo;
|
||||||
|
extern crate inkwell;
|
||||||
|
extern crate libc;
|
||||||
|
extern crate roc_gen;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod gen_hash {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_hash() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Dict.hashTestOnly 0 0
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
9718519427346233646,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_str_with_seed() {
|
||||||
|
assert_evals_to!("Dict.hashTestOnly 1 \"a\"", 0xbed235177f41d328, u64);
|
||||||
|
assert_evals_to!("Dict.hashTestOnly 2 \"abc\"", 0xbe348debe59b27c3, u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_record() {
|
||||||
|
assert_evals_to!("Dict.hashTestOnly 1 { x: \"a\" } ", 0xbed235177f41d328, u64);
|
||||||
|
assert_evals_to!(
|
||||||
|
"Dict.hashTestOnly 1 { x: 42, y: 3.14 } ",
|
||||||
|
5348189196103430707,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_result() {
|
||||||
|
assert_evals_to!(
|
||||||
|
"Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ",
|
||||||
|
2878521786781103245,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_linked_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
|
input : LinkedList I64
|
||||||
|
input = Nil
|
||||||
|
|
||||||
|
Dict.hashTestOnly 0 input
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
|
input : LinkedList I64
|
||||||
|
input = Cons 4 (Cons 3 Nil)
|
||||||
|
|
||||||
|
Dict.hashTestOnly 0 input
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
8287696503006938486,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_expr() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Var I64 ]
|
||||||
|
|
||||||
|
x : Expr
|
||||||
|
x = Val 1
|
||||||
|
|
||||||
|
Dict.hashTestOnly 0 (Add x x)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
18264046914072177411,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_nullable_expr() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Empty ]
|
||||||
|
|
||||||
|
x : Expr
|
||||||
|
x = Val 1
|
||||||
|
|
||||||
|
Dict.hashTestOnly 0 (Add x x)
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
11103255846683455235,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_rosetree() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Rose a : [ Rose (List (Rose a)) ]
|
||||||
|
|
||||||
|
x : Rose I64
|
||||||
|
x = Rose []
|
||||||
|
|
||||||
|
Dict.hashTestOnly 0 x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_list() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x : List Str
|
||||||
|
x = [ "foo", "bar", "baz" ]
|
||||||
|
|
||||||
|
Dict.hashTestOnly 0 x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
10731521034618280801,
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,11 @@
|
||||||
use libloading::Library;
|
use libloading::Library;
|
||||||
use roc_build::link::module_to_dylib;
|
use roc_build::link::module_to_dylib;
|
||||||
use roc_build::program::FunctionIterator;
|
use roc_build::program::FunctionIterator;
|
||||||
|
use roc_can::builtins::{builtin_defs_map, dict_hash_test_only};
|
||||||
|
use roc_can::def::Def;
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
|
use roc_module::symbol::Symbol;
|
||||||
|
use roc_types::subs::VarStore;
|
||||||
|
|
||||||
fn promote_expr_to_module(src: &str) -> String {
|
fn promote_expr_to_module(src: &str) -> String {
|
||||||
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||||
|
@ -15,6 +19,15 @@ fn promote_expr_to_module(src: &str) -> String {
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
}
|
}
|
||||||
|
pub fn test_builtin_defs(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
|
||||||
|
match builtin_defs_map(symbol, var_store) {
|
||||||
|
Some(def) => Some(def),
|
||||||
|
None => match symbol {
|
||||||
|
Symbol::DICT_TEST_HASH => Some(dict_hash_test_only(symbol, var_store)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn helper<'a>(
|
pub fn helper<'a>(
|
||||||
arena: &'a bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
|
@ -53,6 +66,7 @@ pub fn helper<'a>(
|
||||||
src_dir,
|
src_dir,
|
||||||
exposed_types,
|
exposed_types,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
test_builtin_defs,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut loaded = loaded.expect("failed to load module");
|
let mut loaded = loaded.expect("failed to load module");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use libloading::Library;
|
use libloading::Library;
|
||||||
use roc_build::link::{link, LinkType};
|
use roc_build::link::{link, LinkType};
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ pub fn helper<'a>(
|
||||||
src_dir,
|
src_dir,
|
||||||
exposed_types,
|
exposed_types,
|
||||||
8,
|
8,
|
||||||
|
builtin_defs_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut loaded = loaded.expect("failed to load module");
|
let mut loaded = loaded.expect("failed to load module");
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crossbeam::thread;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use roc_builtins::std::{Mode, StdLib};
|
use roc_builtins::std::{Mode, StdLib};
|
||||||
use roc_can::constraint::Constraint;
|
use roc_can::constraint::Constraint;
|
||||||
use roc_can::def::Declaration;
|
use roc_can::def::{Declaration, Def};
|
||||||
use roc_can::module::{canonicalize_module_defs, Module};
|
use roc_can::module::{canonicalize_module_defs, Module};
|
||||||
use roc_collections::all::{default_hasher, MutMap, MutSet};
|
use roc_collections::all::{default_hasher, MutMap, MutSet};
|
||||||
use roc_constrain::module::{
|
use roc_constrain::module::{
|
||||||
|
@ -1012,14 +1012,18 @@ fn enqueue_task<'a>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_and_typecheck<'a>(
|
pub fn load_and_typecheck<'a, F>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
stdlib: &'a StdLib,
|
stdlib: &'a StdLib,
|
||||||
src_dir: &Path,
|
src_dir: &Path,
|
||||||
exposed_types: SubsByModule,
|
exposed_types: SubsByModule,
|
||||||
ptr_bytes: u32,
|
ptr_bytes: u32,
|
||||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
look_up_builtin: F,
|
||||||
|
) -> Result<LoadedModule, LoadingProblem<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
|
{
|
||||||
use LoadResult::*;
|
use LoadResult::*;
|
||||||
|
|
||||||
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
|
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
|
||||||
|
@ -1032,20 +1036,25 @@ pub fn load_and_typecheck<'a>(
|
||||||
exposed_types,
|
exposed_types,
|
||||||
Phase::SolveTypes,
|
Phase::SolveTypes,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
look_up_builtin,
|
||||||
)? {
|
)? {
|
||||||
Monomorphized(_) => unreachable!(""),
|
Monomorphized(_) => unreachable!(""),
|
||||||
TypeChecked(module) => Ok(module),
|
TypeChecked(module) => Ok(module),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_and_monomorphize<'a>(
|
pub fn load_and_monomorphize<'a, F>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
stdlib: &'a StdLib,
|
stdlib: &'a StdLib,
|
||||||
src_dir: &Path,
|
src_dir: &Path,
|
||||||
exposed_types: SubsByModule,
|
exposed_types: SubsByModule,
|
||||||
ptr_bytes: u32,
|
ptr_bytes: u32,
|
||||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
look_up_builtin: F,
|
||||||
|
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
|
{
|
||||||
use LoadResult::*;
|
use LoadResult::*;
|
||||||
|
|
||||||
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
|
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
|
||||||
|
@ -1058,13 +1067,15 @@ pub fn load_and_monomorphize<'a>(
|
||||||
exposed_types,
|
exposed_types,
|
||||||
Phase::MakeSpecializations,
|
Phase::MakeSpecializations,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
look_up_builtin,
|
||||||
)? {
|
)? {
|
||||||
Monomorphized(module) => Ok(module),
|
Monomorphized(module) => Ok(module),
|
||||||
TypeChecked(_) => unreachable!(""),
|
TypeChecked(_) => unreachable!(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_and_monomorphize_from_str<'a>(
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn load_and_monomorphize_from_str<'a, F>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
src: &'a str,
|
src: &'a str,
|
||||||
|
@ -1072,7 +1083,11 @@ pub fn load_and_monomorphize_from_str<'a>(
|
||||||
src_dir: &Path,
|
src_dir: &Path,
|
||||||
exposed_types: SubsByModule,
|
exposed_types: SubsByModule,
|
||||||
ptr_bytes: u32,
|
ptr_bytes: u32,
|
||||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
look_up_builtin: F,
|
||||||
|
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
|
{
|
||||||
use LoadResult::*;
|
use LoadResult::*;
|
||||||
|
|
||||||
let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?;
|
let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?;
|
||||||
|
@ -1085,6 +1100,7 @@ pub fn load_and_monomorphize_from_str<'a>(
|
||||||
exposed_types,
|
exposed_types,
|
||||||
Phase::MakeSpecializations,
|
Phase::MakeSpecializations,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
look_up_builtin,
|
||||||
)? {
|
)? {
|
||||||
Monomorphized(module) => Ok(module),
|
Monomorphized(module) => Ok(module),
|
||||||
TypeChecked(_) => unreachable!(""),
|
TypeChecked(_) => unreachable!(""),
|
||||||
|
@ -1213,7 +1229,8 @@ enum LoadResult<'a> {
|
||||||
/// and then linking them together, and possibly caching them by the hash of their
|
/// and then linking them together, and possibly caching them by the hash of their
|
||||||
/// specializations, so if none of their specializations changed, we don't even need
|
/// specializations, so if none of their specializations changed, we don't even need
|
||||||
/// to rebuild the module and can link in the cached one directly.)
|
/// to rebuild the module and can link in the cached one directly.)
|
||||||
fn load<'a>(
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn load<'a, F>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
//filename: PathBuf,
|
//filename: PathBuf,
|
||||||
load_start: LoadStart<'a>,
|
load_start: LoadStart<'a>,
|
||||||
|
@ -1222,8 +1239,10 @@ fn load<'a>(
|
||||||
exposed_types: SubsByModule,
|
exposed_types: SubsByModule,
|
||||||
goal_phase: Phase,
|
goal_phase: Phase,
|
||||||
ptr_bytes: u32,
|
ptr_bytes: u32,
|
||||||
|
look_up_builtins: F,
|
||||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>>
|
) -> Result<LoadResult<'a>, LoadingProblem<'a>>
|
||||||
where
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
{
|
{
|
||||||
let LoadStart {
|
let LoadStart {
|
||||||
arc_modules,
|
arc_modules,
|
||||||
|
@ -1317,6 +1336,7 @@ where
|
||||||
.stack_size(EXPANDED_STACK_SIZE)
|
.stack_size(EXPANDED_STACK_SIZE)
|
||||||
.spawn(move |_| {
|
.spawn(move |_| {
|
||||||
// Keep listening until we receive a Shutdown msg
|
// Keep listening until we receive a Shutdown msg
|
||||||
|
|
||||||
for msg in worker_msg_rx.iter() {
|
for msg in worker_msg_rx.iter() {
|
||||||
match msg {
|
match msg {
|
||||||
WorkerMsg::Shutdown => {
|
WorkerMsg::Shutdown => {
|
||||||
|
@ -1343,6 +1363,7 @@ where
|
||||||
src_dir,
|
src_dir,
|
||||||
msg_tx.clone(),
|
msg_tx.clone(),
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
|
look_up_builtins,
|
||||||
);
|
);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
|
@ -3362,7 +3383,8 @@ fn unpack_exposes_entries<'a>(
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize_and_constrain<'a>(
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn canonicalize_and_constrain<'a, F>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
module_ids: &ModuleIds,
|
module_ids: &ModuleIds,
|
||||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
|
@ -3370,7 +3392,11 @@ fn canonicalize_and_constrain<'a>(
|
||||||
aliases: MutMap<Symbol, Alias>,
|
aliases: MutMap<Symbol, Alias>,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
parsed: ParsedModule<'a>,
|
parsed: ParsedModule<'a>,
|
||||||
) -> Result<Msg<'a>, LoadingProblem<'a>> {
|
look_up_builtins: F,
|
||||||
|
) -> Result<Msg<'a>, LoadingProblem<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
|
{
|
||||||
let canonicalize_start = SystemTime::now();
|
let canonicalize_start = SystemTime::now();
|
||||||
|
|
||||||
let ParsedModule {
|
let ParsedModule {
|
||||||
|
@ -3408,6 +3434,7 @@ fn canonicalize_and_constrain<'a>(
|
||||||
exposed_imports,
|
exposed_imports,
|
||||||
&exposed_symbols,
|
&exposed_symbols,
|
||||||
&mut var_store,
|
&mut var_store,
|
||||||
|
look_up_builtins,
|
||||||
);
|
);
|
||||||
let canonicalize_end = SystemTime::now();
|
let canonicalize_end = SystemTime::now();
|
||||||
|
|
||||||
|
@ -3836,13 +3863,17 @@ fn add_def_to_module<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_task<'a>(
|
fn run_task<'a, F>(
|
||||||
task: BuildTask<'a>,
|
task: BuildTask<'a>,
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
src_dir: &Path,
|
src_dir: &Path,
|
||||||
msg_tx: MsgSender<'a>,
|
msg_tx: MsgSender<'a>,
|
||||||
ptr_bytes: u32,
|
ptr_bytes: u32,
|
||||||
) -> Result<(), LoadingProblem<'a>> {
|
look_up_builtins: F,
|
||||||
|
) -> Result<(), LoadingProblem<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||||
|
{
|
||||||
use BuildTask::*;
|
use BuildTask::*;
|
||||||
|
|
||||||
let msg = match task {
|
let msg = match task {
|
||||||
|
@ -3878,6 +3909,7 @@ fn run_task<'a>(
|
||||||
aliases,
|
aliases,
|
||||||
mode,
|
mode,
|
||||||
parsed,
|
parsed,
|
||||||
|
look_up_builtins,
|
||||||
),
|
),
|
||||||
Solve {
|
Solve {
|
||||||
module,
|
module,
|
||||||
|
|
|
@ -18,6 +18,7 @@ mod test_load {
|
||||||
use crate::helpers::fixtures_dir;
|
use crate::helpers::fixtures_dir;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_can::def::Declaration::*;
|
use roc_can::def::Declaration::*;
|
||||||
use roc_can::def::Def;
|
use roc_can::def::Def;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
|
@ -86,6 +87,7 @@ mod test_load {
|
||||||
dir.path(),
|
dir.path(),
|
||||||
exposed_types,
|
exposed_types,
|
||||||
8,
|
8,
|
||||||
|
builtin_defs_map,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,6 +130,7 @@ mod test_load {
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
8,
|
8,
|
||||||
|
builtin_defs_map,
|
||||||
);
|
);
|
||||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||||
|
|
||||||
|
@ -291,6 +294,7 @@ mod test_load {
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
8,
|
8,
|
||||||
|
builtin_defs_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||||
|
|
|
@ -28,6 +28,9 @@ pub enum LowLevel {
|
||||||
ListWalk,
|
ListWalk,
|
||||||
ListWalkBackwards,
|
ListWalkBackwards,
|
||||||
ListSum,
|
ListSum,
|
||||||
|
DictSize,
|
||||||
|
DictEmpty,
|
||||||
|
DictInsert,
|
||||||
NumAdd,
|
NumAdd,
|
||||||
NumAddWrap,
|
NumAddWrap,
|
||||||
NumAddChecked,
|
NumAddChecked,
|
||||||
|
@ -66,4 +69,5 @@ pub enum LowLevel {
|
||||||
And,
|
And,
|
||||||
Or,
|
Or,
|
||||||
Not,
|
Not,
|
||||||
|
Hash,
|
||||||
}
|
}
|
||||||
|
|
|
@ -742,6 +742,7 @@ define_builtins! {
|
||||||
12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record
|
12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record
|
||||||
13 LIST_EQ: "#list_eq" // internal function that checks list equality
|
13 LIST_EQ: "#list_eq" // internal function that checks list equality
|
||||||
14 GENERIC_EQ: "#generic_eq" // internal function that checks generic equality
|
14 GENERIC_EQ: "#generic_eq" // internal function that checks generic equality
|
||||||
|
15 GENERIC_HASH: "#generic_hash" // internal function that checks list equality
|
||||||
}
|
}
|
||||||
1 NUM: "Num" => {
|
1 NUM: "Num" => {
|
||||||
0 NUM_NUM: "Num" imported // the Num.Num type alias
|
0 NUM_NUM: "Num" imported // the Num.Num type alias
|
||||||
|
@ -893,6 +894,11 @@ define_builtins! {
|
||||||
3 DICT_SINGLETON: "singleton"
|
3 DICT_SINGLETON: "singleton"
|
||||||
4 DICT_GET: "get"
|
4 DICT_GET: "get"
|
||||||
5 DICT_INSERT: "insert"
|
5 DICT_INSERT: "insert"
|
||||||
|
6 DICT_LEN: "len"
|
||||||
|
|
||||||
|
// This should not be exposed to users, its for testing the
|
||||||
|
// hash function ONLY
|
||||||
|
7 DICT_TEST_HASH: "hashTestOnly"
|
||||||
}
|
}
|
||||||
7 SET: "Set" => {
|
7 SET: "Set" => {
|
||||||
0 SET_SET: "Set" imported // the Set.Set type alias
|
0 SET_SET: "Set" imported // the Set.Set type alias
|
||||||
|
|
|
@ -588,5 +588,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
}
|
}
|
||||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
|
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
|
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
|
DictSize => arena.alloc_slice_copy(&[borrowed]),
|
||||||
|
DictEmpty => &[],
|
||||||
|
DictInsert => arena.alloc_slice_copy(&[owned, owned, owned]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -392,7 +392,6 @@ impl<'a> Layout<'a> {
|
||||||
content: Content,
|
content: Content,
|
||||||
) -> Result<Self, LayoutProblem> {
|
) -> Result<Self, LayoutProblem> {
|
||||||
use roc_types::subs::Content::*;
|
use roc_types::subs::Content::*;
|
||||||
|
|
||||||
match content {
|
match content {
|
||||||
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
|
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
|
||||||
RecursionVar { structure, .. } => {
|
RecursionVar { structure, .. } => {
|
||||||
|
@ -961,6 +960,7 @@ fn layout_from_flat_type<'a>(
|
||||||
|
|
||||||
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
|
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
|
||||||
Symbol::LIST_LIST => list_layout_from_elem(env, args[0]),
|
Symbol::LIST_LIST => list_layout_from_elem(env, args[0]),
|
||||||
|
Symbol::DICT_DICT => dict_layout_from_key_value(env, args[0], args[1]),
|
||||||
Symbol::ATTR_ATTR => {
|
Symbol::ATTR_ATTR => {
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
|
@ -1796,6 +1796,47 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dict_layout_from_key_value<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
key_var: Variable,
|
||||||
|
value_var: Variable,
|
||||||
|
) -> Result<Layout<'a>, LayoutProblem> {
|
||||||
|
match env.subs.get_without_compacting(key_var).content {
|
||||||
|
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, key_args)) => {
|
||||||
|
debug_assert_eq!(key_args.len(), 2);
|
||||||
|
|
||||||
|
let var = *key_args.get(1).unwrap();
|
||||||
|
|
||||||
|
dict_layout_from_key_value(env, var, value_var)
|
||||||
|
}
|
||||||
|
Content::FlexVar(_) | Content::RigidVar(_) => {
|
||||||
|
// If this was still a (Dict * *) then it must have been an empty dict
|
||||||
|
Ok(Layout::Builtin(Builtin::EmptyDict))
|
||||||
|
}
|
||||||
|
key_content => {
|
||||||
|
match env.subs.get_without_compacting(value_var).content {
|
||||||
|
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, value_args)) => {
|
||||||
|
debug_assert_eq!(value_args.len(), 2);
|
||||||
|
|
||||||
|
let var = *value_args.get(1).unwrap();
|
||||||
|
|
||||||
|
dict_layout_from_key_value(env, key_var, var)
|
||||||
|
}
|
||||||
|
value_content => {
|
||||||
|
let key_layout = Layout::new_help(env, key_var, key_content)?;
|
||||||
|
let value_layout = Layout::new_help(env, value_var, value_content)?;
|
||||||
|
|
||||||
|
// This is a normal list.
|
||||||
|
Ok(Layout::Builtin(Builtin::Dict(
|
||||||
|
env.arena.alloc(key_layout),
|
||||||
|
env.arena.alloc(value_layout),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_layout_from_elem<'a>(
|
pub fn list_layout_from_elem<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
elem_var: Variable,
|
elem_var: Variable,
|
||||||
|
|
|
@ -12,6 +12,7 @@ mod helpers;
|
||||||
// Test monomorphization
|
// Test monomorphization
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_mono {
|
mod test_mono {
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::ir::Proc;
|
use roc_mono::ir::Proc;
|
||||||
|
@ -54,6 +55,7 @@ mod test_mono {
|
||||||
}
|
}
|
||||||
|
|
||||||
let exposed_types = MutMap::default();
|
let exposed_types = MutMap::default();
|
||||||
|
|
||||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||||
arena,
|
arena,
|
||||||
filename,
|
filename,
|
||||||
|
@ -62,6 +64,7 @@ mod test_mono {
|
||||||
src_dir,
|
src_dir,
|
||||||
exposed_types,
|
exposed_types,
|
||||||
8,
|
8,
|
||||||
|
builtin_defs_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut loaded = loaded.expect("failed to load module");
|
let mut loaded = loaded.expect("failed to load module");
|
||||||
|
@ -631,6 +634,31 @@ mod test_mono {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dict() {
|
||||||
|
compiles_to_ir(
|
||||||
|
r#"
|
||||||
|
Dict.len Dict.empty
|
||||||
|
"#,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
procedure Dict.2 ():
|
||||||
|
let Test.4 = lowlevel DictEmpty ;
|
||||||
|
ret Test.4;
|
||||||
|
|
||||||
|
procedure Dict.6 (#Attr.2):
|
||||||
|
let Test.3 = lowlevel DictSize #Attr.2;
|
||||||
|
ret Test.3;
|
||||||
|
|
||||||
|
procedure Test.0 ():
|
||||||
|
let Test.2 = FunctionPointer Dict.2;
|
||||||
|
let Test.1 = CallByName Dict.6 Test.2;
|
||||||
|
ret Test.1;
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_append_closure() {
|
fn list_append_closure() {
|
||||||
compiles_to_ir(
|
compiles_to_ir(
|
||||||
|
|
|
@ -10,6 +10,7 @@ mod helpers;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod solve_expr {
|
mod solve_expr {
|
||||||
use crate::helpers::with_larger_debug_stack;
|
use crate::helpers::with_larger_debug_stack;
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||||
|
|
||||||
|
@ -63,6 +64,7 @@ mod solve_expr {
|
||||||
dir.path(),
|
dir.path(),
|
||||||
exposed_types,
|
exposed_types,
|
||||||
8,
|
8,
|
||||||
|
builtin_defs_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
dir.close()?;
|
dir.close()?;
|
||||||
|
|
|
@ -377,6 +377,18 @@ fn nat_alias_content() -> SolvedType {
|
||||||
int_alias_content(natural_type())
|
int_alias_content(natural_type())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// U64
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn u64_type() -> SolvedType {
|
||||||
|
SolvedType::Alias(Symbol::NUM_U64, vec![], Box::new(u64_alias_content()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn u64_alias_content() -> SolvedType {
|
||||||
|
int_alias_content(unsigned64_type())
|
||||||
|
}
|
||||||
|
|
||||||
// INT
|
// INT
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
@ -15,6 +15,7 @@ fs_extra = "1.2.0"
|
||||||
pulldown-cmark = { version = "0.8", default-features = false }
|
pulldown-cmark = { version = "0.8", default-features = false }
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_builtins = { path = "../compiler/builtins" }
|
roc_builtins = { path = "../compiler/builtins" }
|
||||||
|
roc_can = { path = "../compiler/can" }
|
||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
bumpalo = { version = "3.2", features = ["collections"] }
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ extern crate serde_derive;
|
||||||
extern crate pulldown_cmark;
|
extern crate pulldown_cmark;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
use roc_builtins::std::StdLib;
|
use roc_builtins::std::StdLib;
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_load::docs::Documentation;
|
use roc_load::docs::Documentation;
|
||||||
use roc_load::docs::ModuleDocumentation;
|
use roc_load::docs::ModuleDocumentation;
|
||||||
|
|
||||||
|
@ -133,6 +134,7 @@ fn files_to_documentations(
|
||||||
src_dir,
|
src_dir,
|
||||||
MutMap::default(),
|
MutMap::default(),
|
||||||
8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system)
|
8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system)
|
||||||
|
builtin_defs_map,
|
||||||
)
|
)
|
||||||
.expect("TODO gracefully handle load failing");
|
.expect("TODO gracefully handle load failing");
|
||||||
files_docs.extend(loaded.documentation.drain().map(|x| x.1));
|
files_docs.extend(loaded.documentation.drain().map(|x| x.1));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::error::EdResult;
|
use crate::error::EdResult;
|
||||||
use crate::mvc::app_model::AppModel;
|
use crate::mvc::app_model::AppModel;
|
||||||
use crate::mvc::app_update::{handle_copy, handle_paste, pass_keydown_to_focused};
|
use crate::mvc::app_update::{handle_copy, handle_cut, handle_paste, pass_keydown_to_focused};
|
||||||
use winit::event::VirtualKeyCode::*;
|
use winit::event::VirtualKeyCode::*;
|
||||||
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
|
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
|
||||||
|
|
||||||
|
@ -22,11 +22,7 @@ pub fn handle_keydown(
|
||||||
|
|
||||||
Copy => handle_copy(app_model)?,
|
Copy => handle_copy(app_model)?,
|
||||||
Paste => handle_paste(app_model)?,
|
Paste => handle_paste(app_model)?,
|
||||||
Cut => {
|
Cut => handle_cut(app_model)?,
|
||||||
//handle_cut(app_model)?
|
|
||||||
todo!("cut");
|
|
||||||
}
|
|
||||||
|
|
||||||
C => {
|
C => {
|
||||||
if modifiers.ctrl() {
|
if modifiers.ctrl() {
|
||||||
handle_copy(app_model)?
|
handle_copy(app_model)?
|
||||||
|
@ -39,8 +35,7 @@ pub fn handle_keydown(
|
||||||
}
|
}
|
||||||
X => {
|
X => {
|
||||||
if modifiers.ctrl() {
|
if modifiers.ctrl() {
|
||||||
//handle_cut(app_model)?
|
handle_cut(app_model)?
|
||||||
todo!("cut");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -8,7 +8,7 @@ use winit::event::{ModifiersState, VirtualKeyCode};
|
||||||
pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> {
|
pub fn handle_copy(app_model: &mut AppModel) -> EdResult<()> {
|
||||||
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||||
if ed_model.has_focus {
|
if ed_model.has_focus {
|
||||||
let selected_str_opt = super::ed_model::get_selected_str(ed_model)?;
|
let selected_str_opt = ed_model.get_selected_str()?;
|
||||||
|
|
||||||
if let Some(selected_str) = selected_str_opt {
|
if let Some(selected_str) = selected_str_opt {
|
||||||
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, selected_str)?;
|
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, selected_str)?;
|
||||||
|
@ -76,6 +76,22 @@ pub fn handle_paste(app_model: &mut AppModel) -> EdResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_cut(app_model: &mut AppModel) -> EdResult<()> {
|
||||||
|
if let Some(ref mut ed_model) = app_model.ed_model_opt {
|
||||||
|
if ed_model.has_focus {
|
||||||
|
let selected_str_opt = ed_model.get_selected_str()?;
|
||||||
|
|
||||||
|
if let Some(selected_str) = selected_str_opt {
|
||||||
|
app_model::set_clipboard_txt(&mut app_model.clipboard_opt, selected_str)?;
|
||||||
|
|
||||||
|
ed_model.del_selection()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pass_keydown_to_focused(
|
pub fn pass_keydown_to_focused(
|
||||||
modifiers: &ModifiersState,
|
modifiers: &ModifiersState,
|
||||||
virtual_keycode: VirtualKeyCode,
|
virtual_keycode: VirtualKeyCode,
|
||||||
|
@ -102,7 +118,7 @@ pub fn handle_new_char(received_char: &char, app_model: &mut AppModel) -> EdResu
|
||||||
pub mod test_app_update {
|
pub mod test_app_update {
|
||||||
use crate::mvc::app_model;
|
use crate::mvc::app_model;
|
||||||
use crate::mvc::app_model::{AppModel, Clipboard};
|
use crate::mvc::app_model::{AppModel, Clipboard};
|
||||||
use crate::mvc::app_update::{handle_copy, handle_paste};
|
use crate::mvc::app_update::{handle_copy, handle_cut, handle_paste};
|
||||||
use crate::mvc::ed_model::{EdModel, Position, RawSelection};
|
use crate::mvc::ed_model::{EdModel, Position, RawSelection};
|
||||||
use crate::mvc::ed_update::test_ed_update::gen_caret_text_buf;
|
use crate::mvc::ed_update::test_ed_update::gen_caret_text_buf;
|
||||||
use crate::selection::test_selection::{all_lines_vec, convert_selection_to_dsl};
|
use crate::selection::test_selection::{all_lines_vec, convert_selection_to_dsl};
|
||||||
|
@ -171,12 +187,42 @@ pub mod test_app_update {
|
||||||
Ok(app_model.clipboard_opt)
|
Ok(app_model.clipboard_opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_cut(
|
||||||
|
pre_lines_str: &[&str],
|
||||||
|
expected_clipboard_content: &str,
|
||||||
|
expected_post_lines_str: &[&str],
|
||||||
|
clipboard_opt: Option<Clipboard>,
|
||||||
|
) -> Result<Option<Clipboard>, String> {
|
||||||
|
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
|
||||||
|
|
||||||
|
let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt, clipboard_opt);
|
||||||
|
|
||||||
|
handle_cut(&mut app_model)?;
|
||||||
|
|
||||||
|
let clipboard_content = app_model::get_clipboard_txt(&mut app_model.clipboard_opt)?;
|
||||||
|
|
||||||
|
assert_eq!(clipboard_content, expected_clipboard_content);
|
||||||
|
|
||||||
|
let ed_model = app_model.ed_model_opt.unwrap();
|
||||||
|
let mut text_buf_lines = all_lines_vec(&ed_model.text_buf);
|
||||||
|
let post_lines_str = convert_selection_to_dsl(
|
||||||
|
ed_model.selection_opt,
|
||||||
|
ed_model.caret_pos,
|
||||||
|
&mut text_buf_lines,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(post_lines_str, expected_post_lines_str);
|
||||||
|
|
||||||
|
Ok(app_model.clipboard_opt)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // ignored because of clipboard problems on ci
|
#[ignore] // ignored because of clipboard problems on ci
|
||||||
fn copy_paste() -> Result<(), String> {
|
fn copy_paste_cut() -> Result<(), String> {
|
||||||
// can only init clipboard once
|
// can only init clipboard once
|
||||||
let mut clipboard_opt = AppModel::init_clipboard_opt();
|
let mut clipboard_opt = AppModel::init_clipboard_opt();
|
||||||
|
|
||||||
|
// copy
|
||||||
clipboard_opt = assert_copy(&["[a]|"], "a", clipboard_opt)?;
|
clipboard_opt = assert_copy(&["[a]|"], "a", clipboard_opt)?;
|
||||||
clipboard_opt = assert_copy(&["|[b]"], "b", clipboard_opt)?;
|
clipboard_opt = assert_copy(&["|[b]"], "b", clipboard_opt)?;
|
||||||
clipboard_opt = assert_copy(&["a[ ]|"], " ", clipboard_opt)?;
|
clipboard_opt = assert_copy(&["a[ ]|"], " ", clipboard_opt)?;
|
||||||
|
@ -188,6 +234,8 @@ pub mod test_app_update {
|
||||||
"ef\nghi",
|
"ef\nghi",
|
||||||
clipboard_opt,
|
clipboard_opt,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// paste
|
||||||
clipboard_opt = assert_paste(&["|"], "", &["|"], clipboard_opt)?;
|
clipboard_opt = assert_paste(&["|"], "", &["|"], clipboard_opt)?;
|
||||||
clipboard_opt = assert_paste(&["|"], "a", &["a|"], clipboard_opt)?;
|
clipboard_opt = assert_paste(&["|"], "a", &["a|"], clipboard_opt)?;
|
||||||
clipboard_opt = assert_paste(&["a|"], "b", &["ab|"], clipboard_opt)?;
|
clipboard_opt = assert_paste(&["a|"], "b", &["ab|"], clipboard_opt)?;
|
||||||
|
@ -196,13 +244,27 @@ pub mod test_app_update {
|
||||||
clipboard_opt = assert_paste(&["[ab]|"], "d", &["d|"], clipboard_opt)?;
|
clipboard_opt = assert_paste(&["[ab]|"], "d", &["d|"], clipboard_opt)?;
|
||||||
clipboard_opt = assert_paste(&["a[b]|c"], "e", &["ae|c"], clipboard_opt)?;
|
clipboard_opt = assert_paste(&["a[b]|c"], "e", &["ae|c"], clipboard_opt)?;
|
||||||
clipboard_opt = assert_paste(&["a\n", "[b\n", "]|"], "f", &["a\n", "f|"], clipboard_opt)?;
|
clipboard_opt = assert_paste(&["a\n", "[b\n", "]|"], "f", &["a\n", "f|"], clipboard_opt)?;
|
||||||
assert_paste(
|
clipboard_opt = assert_paste(
|
||||||
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
|
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
|
||||||
"ef\nghi",
|
"ef\nghi",
|
||||||
&["abc\n", "def\n", "ghi|\n", "jkl"],
|
&["abc\n", "def\n", "ghi|\n", "jkl"],
|
||||||
clipboard_opt,
|
clipboard_opt,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// cut
|
||||||
|
clipboard_opt = assert_cut(&["[a]|"], "a", &["|"], clipboard_opt)?;
|
||||||
|
clipboard_opt = assert_cut(&["|[b]"], "b", &["|"], clipboard_opt)?;
|
||||||
|
clipboard_opt = assert_cut(&["a[ ]|"], " ", &["a|"], clipboard_opt)?;
|
||||||
|
clipboard_opt = assert_cut(&["[ ]|b"], " ", &["|b"], clipboard_opt)?;
|
||||||
|
clipboard_opt = assert_cut(&["a\n", "[b\n", "]|"], "b\n", &["a\n", "|"], clipboard_opt)?;
|
||||||
|
clipboard_opt = assert_cut(&["[a\n", " b\n", "]|"], "a\n b\n", &["|"], clipboard_opt)?;
|
||||||
|
assert_cut(
|
||||||
|
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"],
|
||||||
|
"ef\nghi",
|
||||||
|
&["abc\n", "d|\n", "jkl"],
|
||||||
|
clipboard_opt,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,10 @@ pub fn init_model(file_path: &Path) -> EdResult<EdModel> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_selected_str(ed_model: &EdModel) -> EdResult<Option<&str>> {
|
impl EdModel {
|
||||||
if let Some(curr_selection) = ed_model.selection_opt {
|
pub fn get_selected_str(&self) -> EdResult<Option<&str>> {
|
||||||
let selected_str = ed_model.text_buf.get_selection(curr_selection)?;
|
if let Some(curr_selection) = self.selection_opt {
|
||||||
|
let selected_str = self.text_buf.get_selection(curr_selection)?;
|
||||||
|
|
||||||
Ok(Some(selected_str))
|
Ok(Some(selected_str))
|
||||||
} else {
|
} else {
|
||||||
|
@ -35,6 +36,18 @@ pub fn get_selected_str(ed_model: &EdModel) -> EdResult<Option<&str>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn del_selection(&mut self) -> EdResult<()> {
|
||||||
|
if let Some(selection) = self.selection_opt {
|
||||||
|
self.text_buf.del_selection(selection)?;
|
||||||
|
|
||||||
|
self.caret_pos = selection.start_pos;
|
||||||
|
}
|
||||||
|
self.selection_opt = None;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub line: usize,
|
pub line: usize,
|
||||||
|
|
|
@ -353,7 +353,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
|
||||||
}
|
}
|
||||||
'\u{3}' // Ctrl + C
|
'\u{3}' // Ctrl + C
|
||||||
| '\u{16}' // Ctrl + V
|
| '\u{16}' // Ctrl + V
|
||||||
| '\u{30}' // Ctrl + X
|
| '\u{18}' // Ctrl + X
|
||||||
| '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
|
| '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
|
||||||
| '\u{f0000}'..='\u{ffffd}' // ^
|
| '\u{f0000}'..='\u{ffffd}' // ^
|
||||||
| '\u{100000}'..='\u{10fffd}' // ^
|
| '\u{100000}'..='\u{10fffd}' // ^
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue