Merge branch 'trunk' into gen-equality

This commit is contained in:
Richard Feldman 2021-02-12 19:13:01 -05:00 committed by GitHub
commit 76a9461cfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 2123 additions and 53 deletions

1
Cargo.lock generated
View file

@ -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",

View file

@ -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();

View file

@ -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);

View file

@ -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,

View 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));
}

View 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);
}
}

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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";

View file

@ -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

View file

@ -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 };

View file

@ -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();

View file

@ -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));
} }
} }

View file

@ -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!(

View file

@ -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)
}
} }
} }

View 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()
}

View 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()
}

View file

@ -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,

View file

@ -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>,

View file

@ -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)),

View file

@ -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;

View 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
);
}
}

View 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
);
}
}

View file

@ -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");

View file

@ -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");

View file

@ -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,

View file

@ -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");

View file

@ -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,
} }

View file

@ -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

View file

@ -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]),
} }
} }

View file

@ -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,

View file

@ -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(

View file

@ -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()?;

View file

@ -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)]

View file

@ -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"] }

View file

@ -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));

View file

@ -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");
} }
} }
_ => (), _ => (),

View file

@ -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(())
} }
} }

View file

@ -25,13 +25,26 @@ 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 {
Ok(None) Ok(None)
}
}
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(())
} }
} }

View file

@ -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}' // ^