Merge remote-tracking branch 'origin/trunk' into add-dec-types

This commit is contained in:
Folkert 2021-06-27 16:26:20 +02:00
commit bba1d1b4fe
43 changed files with 953 additions and 688 deletions

109
Cargo.lock generated
View file

@ -967,6 +967,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "drm-fourcc"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebbf3a5ed4671aabffefce172ff43d69c1f27dd2c6aea28e5212a70f32ada0cf"
dependencies = [
"serde",
]
[[package]]
name = "dtoa"
version = "0.4.8"
@ -1008,6 +1017,16 @@ dependencies = [
"termcolor",
]
[[package]]
name = "external-memory"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4dfe8d292b014422776a8c516862d2bff8a81b223a4461dfdc45f3862dc9d39"
dependencies = [
"bitflags",
"drm-fourcc",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -1221,9 +1240,9 @@ dependencies = [
[[package]]
name = "gfx-auxil"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccf8711c9994dfa34337466bee3ae1462e172874c432ce4eb120ab2e98d39cf"
checksum = "1694991b11d642680e82075a75c7c2bd75556b805efa7660b705689f05b1ab1c"
dependencies = [
"fxhash",
"gfx-hal",
@ -1232,14 +1251,15 @@ dependencies = [
[[package]]
name = "gfx-backend-dx11"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f839f27f8c8a6dc553ccca7f5b35a42009432bc25db9688bba7061cd394161f"
checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae"
dependencies = [
"arrayvec",
"bitflags",
"gfx-auxil",
"gfx-hal",
"gfx-renderdoc",
"libloading 0.7.0",
"log",
"parking_lot",
@ -1254,9 +1274,9 @@ dependencies = [
[[package]]
name = "gfx-backend-dx12"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3937738b0da5839bba4e33980d29f9a06dbce184d04a3a08c9a949e7953700e3"
checksum = "61f09e9d8c2aa69e9a21eb83c0f5d1a286c6d37da011f796e550d180b08090ce"
dependencies = [
"arrayvec",
"bit-set",
@ -1264,6 +1284,7 @@ dependencies = [
"d3d12",
"gfx-auxil",
"gfx-hal",
"gfx-renderdoc",
"log",
"parking_lot",
"range-alloc",
@ -1276,9 +1297,9 @@ dependencies = [
[[package]]
name = "gfx-backend-empty"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ac55ada4bfcd35479b3421eea324d36d7da5f724e2f66ecb36d4efdb7041a5e"
checksum = "29c8f813c47791918aa00dc9c9ddf961d23fa8c2a5d869e6cb8ea84f944820f4"
dependencies = [
"gfx-hal",
"log",
@ -1287,9 +1308,9 @@ dependencies = [
[[package]]
name = "gfx-backend-gl"
version = "0.8.1"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0caa03d6e0b7b4f202aea1f20c3f3288cfa06d92d24cea9d69c9a7627967244a"
checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152"
dependencies = [
"arrayvec",
"bitflags",
@ -1309,15 +1330,16 @@ dependencies = [
[[package]]
name = "gfx-backend-metal"
version = "0.8.2"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "340895ad544ba46433acb3bdabece0ef16f2dbedc030adbd7c9eaf2839fbed41"
checksum = "0de85808e2a98994c6af925253f8a9593bc57180ef1ea137deab6d35cc949517"
dependencies = [
"arrayvec",
"bitflags",
"block",
"cocoa-foundation",
"copyless",
"core-graphics-types",
"foreign-types",
"fxhash",
"gfx-hal",
@ -1334,15 +1356,16 @@ dependencies = [
[[package]]
name = "gfx-backend-vulkan"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a353fc6fdb42ec646de49bbb74e4870e37a7e680caf33f3ac0615c30b1146d94"
checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0"
dependencies = [
"arrayvec",
"ash",
"byteorder",
"core-graphics-types",
"gfx-hal",
"gfx-renderdoc",
"inplace_it",
"log",
"naga",
@ -1355,16 +1378,28 @@ dependencies = [
[[package]]
name = "gfx-hal"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d285bfd566f6b9134af908446ca350c0a1047495dfb9bbd826e701e8ee1d259"
checksum = "7fbb575ea793dd0507b3082f4f2cde62dc9f3cebd98f5cd49ba2a4da97a976fd"
dependencies = [
"bitflags",
"external-memory",
"naga",
"raw-window-handle",
"thiserror",
]
[[package]]
name = "gfx-renderdoc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8027995e247e2426d3a00d13f5191dd56c314bff02dc4b54cbf727f1ba9c40a"
dependencies = [
"libloading 0.7.0",
"log",
"renderdoc-sys",
]
[[package]]
name = "gimli"
version = "0.24.0"
@ -1880,13 +1915,13 @@ dependencies = [
[[package]]
name = "metal"
version = "0.22.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c12e48c737ee9a55e8bb2352bcde588f79ae308d3529ee888f7cc0f469b5777"
checksum = "79d7d769f1c104b8388294d6594d491d2e21240636f5f94d37f8a0f3d7904450"
dependencies = [
"bitflags",
"block",
"cocoa-foundation",
"core-graphics-types",
"foreign-types",
"log",
"objc",
@ -1956,9 +1991,9 @@ dependencies = [
[[package]]
name = "naga"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d74f2c7ace793a760165ac0679d6830809ad4e85f6886f72e4f8c4aa4291c5"
checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64"
dependencies = [
"bit-set",
"bitflags",
@ -1967,6 +2002,7 @@ dependencies = [
"log",
"num-traits",
"petgraph",
"rose_tree",
"spirv_headers",
"thiserror",
]
@ -2935,6 +2971,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "renderdoc-sys"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
[[package]]
name = "roc_build"
version = "0.1.0"
@ -3451,6 +3493,15 @@ dependencies = [
"smallvec",
]
[[package]]
name = "rose_tree"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2"
dependencies = [
"petgraph",
]
[[package]]
name = "rustc-demangle"
version = "0.1.19"
@ -4365,9 +4416,9 @@ dependencies = [
[[package]]
name = "wgpu"
version = "0.8.1"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "215fd50e66f794bd16683e7e0e0b9b53be265eb10fdf02276caf5de3e5743fcf"
checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275"
dependencies = [
"arrayvec",
"js-sys",
@ -4385,9 +4436,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.8.1"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d56c368fc0e6f3927c711d2b55a51ad4321218efc0239c4acf69e456ab70399"
checksum = "2af5c8acd3ae5781a277cdf65a17f3a7135de5ae782775620e74ea16c9d47770"
dependencies = [
"arrayvec",
"bitflags",
@ -4415,18 +4466,18 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa248d90c8e6832269b8955bf800e8241f942c25e18a235b7752226804d21556"
checksum = "4f5c9678cd533558e28b416d66947b099742df1939307478db54f867137f1b60"
dependencies = [
"bitflags",
]
[[package]]
name = "wgpu_glyph"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634570b440f4c24c2e6049ed01ec832c23d338dea3eca654d5760838017a1c8b"
checksum = "0fee8c96eda18195a7ad9989737183e0a357f14b15e98838c76abbcf56a5f970"
dependencies = [
"bytemuck",
"glyph_brush",

View file

@ -214,8 +214,48 @@ fn jit_to_ast_help<'a>(
match variant {
NonRecursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let offset = tags_and_layouts
.iter()
.map(|(_, fields)| {
fields
.iter()
.map(|l| l.stack_size(env.ptr_bytes))
.sum()
})
.max()
.unwrap_or(0);
let tag_id = *(ptr.add(offset as usize) as *const i64);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags[tag_name];
debug_assert_eq!(arg_layouts.len(), variables.len());
// NOTE assumes the data bytes are the first bytes
let it = variables.iter().copied().zip(arg_layouts.iter());
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
))
}
| Recursive {
Recursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(

View file

@ -121,7 +121,7 @@ mod cli_run {
&example_file("hello-world", "Hello.roc"),
"hello-world",
&[],
"Hello, World!!!!!!!!!!!!!\n",
"Hello, World!\n",
true,
);
}
@ -133,7 +133,7 @@ mod cli_run {
&example_file("hello-world", "Hello.roc"),
"hello-world",
&[],
"Hello, World!!!!!!!!!!!!!\n",
"Hello, World!\n",
true,
);
}

View file

@ -148,6 +148,7 @@ pub fn gen_from_mono_module(
opt_level,
loaded.procedures,
loaded.entry_point,
Some(&app_ll_file),
);
env.dibuilder.finalize();
@ -193,7 +194,7 @@ pub fn gen_from_mono_module(
// run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir")
.env_clear()
.args(&[app_ll_file.to_str().unwrap()])
.args(&["-instnamer", app_ll_file.to_str().unwrap()])
.output()
{
Ok(_) => {}

View file

@ -12,6 +12,7 @@ const Opaque = ?[*]u8;
const Inc = fn (?[*]u8) callconv(.C) void;
const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const HasTagId = fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, data: ?[*]u8 };
pub const RocList = extern struct {
bytes: ?[*]u8,
@ -405,11 +406,14 @@ pub fn listKeepOks(
before_width: usize,
result_width: usize,
after_width: usize,
has_tag_id: HasTagId,
dec_result: Dec,
) callconv(.C) RocList {
const good_constructor: u16 = 1;
return listKeepResult(
list,
RocResult.isOk,
good_constructor,
caller,
data,
inc_n_data,
@ -418,6 +422,7 @@ pub fn listKeepOks(
before_width,
result_width,
after_width,
has_tag_id,
dec_result,
);
}
@ -432,11 +437,14 @@ pub fn listKeepErrs(
before_width: usize,
result_width: usize,
after_width: usize,
has_tag_id: HasTagId,
dec_result: Dec,
) callconv(.C) RocList {
const good_constructor: u16 = 0;
return listKeepResult(
list,
RocResult.isErr,
good_constructor,
caller,
data,
inc_n_data,
@ -445,13 +453,14 @@ pub fn listKeepErrs(
before_width,
result_width,
after_width,
has_tag_id,
dec_result,
);
}
pub fn listKeepResult(
list: RocList,
is_good_constructor: fn (RocResult) bool,
good_constructor: u16,
caller: Caller1,
data: Opaque,
inc_n_data: IncN,
@ -460,6 +469,7 @@ pub fn listKeepResult(
before_width: usize,
result_width: usize,
after_width: usize,
has_tag_id: HasTagId,
dec_result: Dec,
) RocList {
if (list.bytes) |source_ptr| {
@ -479,11 +489,14 @@ pub fn listKeepResult(
const before_element = source_ptr + (i * before_width);
caller(data, before_element, temporary);
const result = utils.RocResult{ .bytes = temporary };
const after_element = temporary + @sizeOf(i64);
if (is_good_constructor(result)) {
@memcpy(target_ptr + (kept * after_width), after_element, after_width);
// a record { matched: bool, data: ?[*]u8 }
// for now, that data pointer is just the input `temporary` pointer
// this will change in the future to only return a pointer to the
// payload of the tag
const answer = has_tag_id(good_constructor, temporary);
if (answer.matched) {
const contents = (answer.data orelse unreachable);
@memcpy(target_ptr + (kept * after_width), contents, after_width);
kept += 1;
} else {
dec_result(temporary);
@ -606,7 +619,9 @@ pub fn listWalkUntil(
accum: Opaque,
alignment: u32,
element_width: usize,
continue_stop_width: usize,
accum_width: usize,
has_tag_id: HasTagId,
dec: Dec,
output: Opaque,
) callconv(.C) void {
@ -622,9 +637,10 @@ pub fn listWalkUntil(
return;
}
const bytes_ptr: [*]u8 = utils.alloc(TAG_WIDTH + accum_width, alignment);
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment);
@memcpy(bytes_ptr + TAG_WIDTH, accum orelse unreachable, accum_width);
// NOTE: assumes data bytes are the first bytes in a tag
@memcpy(bytes_ptr, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
var i: usize = 0;
@ -636,10 +652,12 @@ pub fn listWalkUntil(
inc_n_data(data, 1);
}
caller(data, element, bytes_ptr + TAG_WIDTH, bytes_ptr);
caller(data, element, bytes_ptr, bytes_ptr);
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes_ptr));
if (usizes[0] != 0) {
// [ Continue ..., Stop ]
const tag_id = has_tag_id(0, bytes_ptr);
if (!tag_id.matched) {
// decrement refcount of the remaining items
i += 1;
while (i < size) : (i += 1) {
@ -650,7 +668,7 @@ pub fn listWalkUntil(
}
}
@memcpy(output orelse unreachable, bytes_ptr + TAG_WIDTH, accum_width);
@memcpy(output orelse unreachable, bytes_ptr, accum_width);
utils.dealloc(bytes_ptr, alignment);
}
@ -1009,7 +1027,25 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
return output;
}
// input: RocList,
pub fn listSetInPlace(
bytes: ?[*]u8,
length: usize,
alignment: u32,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) callconv(.C) ?[*]u8 {
// INVARIANT: bounds checking happens on the roc side
//
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListGet input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listSetInPlaceHelp(bytes, length, alignment, index, element, element_width, dec);
}
pub fn listSet(
bytes: ?[*]u8,
length: usize,
@ -1028,23 +1064,34 @@ pub fn listSet(
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes));
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
// the element we will replace
var element_at_index = (bytes orelse undefined) + (index * element_width);
// decrement its refcount
dec(element_at_index);
// copy in the new element
@memcpy(element_at_index, element orelse undefined, element_width);
return bytes;
return listSetInPlaceHelp(bytes, length, alignment, index, element, element_width, dec);
} else {
return listSetClone(bytes, length, alignment, index, element, element_width, dec);
return listSetImmutable(bytes, length, alignment, index, element, element_width, dec);
}
}
inline fn listSetClone(
inline fn listSetInPlaceHelp(
bytes: ?[*]u8,
length: usize,
alignment: u32,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) ?[*]u8 {
// the element we will replace
var element_at_index = (bytes orelse undefined) + (index * element_width);
// decrement its refcount
dec(element_at_index);
// copy in the new element
@memcpy(element_at_index, element orelse undefined, element_width);
return bytes;
}
inline fn listSetImmutable(
old_bytes: ?[*]u8,
length: usize,
alignment: u32,
@ -1053,8 +1100,6 @@ inline fn listSetClone(
element_width: usize,
dec: Dec,
) ?[*]u8 {
@setCold(true);
const data_bytes = length * element_width;
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);

View file

@ -41,6 +41,7 @@ comptime {
exportListFn(list.listConcat, "concat");
exportListFn(list.listDrop, "drop");
exportListFn(list.listSet, "set");
exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listSwap, "swap");
}

View file

@ -17,7 +17,7 @@ const InPlace = packed enum(u8) {
const SMALL_STR_MAX_LENGTH = small_string_size - 1;
const small_string_size = 2 * @sizeOf(usize);
const blank_small_string: [16]u8 = init_blank_small_string(small_string_size);
const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size);
fn init_blank_small_string(comptime n: usize) [n]u8 {
var prime_list: [n]u8 = undefined;
@ -85,12 +85,6 @@ pub const RocStr = extern struct {
}
}
pub fn toSlice(self: RocStr) []u8 {
const str_bytes_ptr: [*]u8 = self.str_bytes orelse unreachable;
const str_bytes: []u8 = str_bytes_ptr[0..self.str_len];
return str_bytes;
}
// This takes ownership of the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them.
pub fn withCapacity(length: usize) RocStr {
@ -203,8 +197,8 @@ pub const RocStr = extern struct {
return result;
}
// NOTE: returns false for empty string!
pub fn isSmallStr(self: RocStr) bool {
// NOTE: returns False for empty string!
return @bitCast(isize, self.str_len) < 0;
}
@ -223,6 +217,82 @@ pub const RocStr = extern struct {
return self.len() == 0;
}
// If a string happens to be null-terminated already, then we can pass its
// bytes directly to functions (e.g. for opening files) that require
// null-terminated strings. Otherwise, we need to allocate and copy a new
// null-terminated string, which has a much higher performance cost!
fn isNullTerminated(self: RocStr) bool {
const len = self.len();
const longest_small_str = @sizeOf(RocStr) - 1;
// NOTE: We want to compare length here, *NOT* check for is_small_str!
// This is because we explicitly want the empty string to be handled in
// this branch, even though the empty string is not a small string.
//
// (The other branch dereferences the bytes pointer, which is not safe
// to do for the empty string.)
if (len <= longest_small_str) {
// If we're a small string, then usually the next byte after the
// end of the string will be zero. (Small strings set all their
// unused bytes to 0, so that comparison for equality can be fast.)
//
// However, empty strings are *not* null terminated, so if this is
// empty, it should return false.
//
// Also, if we are exactly a maximum-length small string,
// then the next byte is off the end of the struct;
// in that case, we are also not null-terminated!
return len != 0 and len != longest_small_str;
} else {
// This is a big string, and it's not empty, so we can safely
// dereference the pointer.
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const capacity_or_refcount: isize = (ptr - 1)[0];
// If capacity_or_refcount is positive, then it's a capacity value.
//
// If we have excess capacity, then we can safely read the next
// byte after the end of the string. Maybe it happens to be zero!
if (capacity_or_refcount > @intCast(isize, len)) {
return self.str_bytes[len] == 0;
} else {
// This string was refcounted or immortal; we can't safely read
// the next byte, so assume the string is not null-terminated.
return false;
}
}
}
// Returns (@sizeOf(RocStr) - 1) for small strings and the empty string.
// Returns 0 for refcounted stirngs and immortal strings.
// Returns the stored capacity value for all other strings.
pub fn capacity(self: RocStr) usize {
const len = self.len();
const longest_small_str = @sizeOf(RocStr) - 1;
if (len <= longest_small_str) {
// Note that although empty strings technically have the full
// capacity of a small string available, they aren't marked as small
// strings, so if you want to make use of that capacity, you need
// to first change its flag to mark it as a small string!
return longest_small_str;
} else {
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const capacity_or_refcount: isize = (ptr - 1)[0];
if (capacity_or_refcount > 0) {
// If capacity_or_refcount is positive, that means it's a
// capacity value.
return capacity_or_refcount;
} else {
// This is either a refcount or else this big string is stored
// in a readonly section; either way, it has no capacity,
// because we cannot mutate it in-place!
return 0;
}
}
}
pub fn isUnique(self: RocStr) bool {
// the empty list is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
@ -240,15 +310,13 @@ pub const RocStr = extern struct {
}
pub fn asSlice(self: RocStr) []u8 {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
return self.asU8ptr()[0..self.len()];
}
pub fn asU8ptr(self: RocStr) [*]u8 {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([16]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
}
// Given a pointer to some bytes, write the first (len) bytes of this

View file

@ -65,6 +65,7 @@ pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
pub const DEC_EQ: &str = "roc_builtins.dec.eq";

View file

@ -1,6 +1,6 @@
/// Helpers for interacting with the zig that generates bitcode
use crate::debug_info_init;
use crate::llvm::build::{Env, C_CALL_CONV, FAST_CALL_CONV};
use crate::llvm::build::{struct_from_fields, Env, C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{
decrement_refcount_layout, increment_n_refcount_layout, increment_refcount_layout,
@ -10,7 +10,7 @@ use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Layout, LayoutIds};
use roc_mono::layout::{Layout, LayoutIds, UnionLayout};
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -66,6 +66,127 @@ const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_8,
];
pub fn build_has_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function: FunctionValue<'ctx>,
union_layout: UnionLayout<'a>,
) -> FunctionValue<'ctx> {
let fn_name: &str = &format!("{}_has_tag_id", function.get_name().to_string_lossy());
// currently the code assumes we're dealing with a non-recursive layout
debug_assert!(matches!(union_layout, UnionLayout::NonRecursive(_)));
match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => build_has_tag_id_help(env, union_layout, &fn_name),
}
}
fn build_has_tag_id_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
union_layout: UnionLayout<'a>,
fn_name: &str,
) -> FunctionValue<'ctx> {
let i8_ptr_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let argument_types: &[BasicTypeEnum] = &[env.context.i16_type().into(), i8_ptr_type.into()];
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let output_type = crate::llvm::convert::zig_has_tag_id_type(env);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
output_type.into(),
&argument_types,
);
// called from zig, must use C calling convention
function_value.set_call_conventions(C_CALL_CONV);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let it = function_value.get_param_iter();
let arguments =
bumpalo::collections::Vec::from_iter_in(it.take(argument_types.len()), env.arena);
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS.iter()) {
argument.set_name(name.ident_string(&env.interns));
}
match arguments.as_slice() {
[tag_id, tag_value_ptr] => {
let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout));
let argument_cast = env
.builder
.build_bitcast(
*tag_value_ptr,
tag_type.ptr_type(AddressSpace::Generic),
"load_opaque",
)
.into_pointer_value();
let tag_value = env.builder.build_load(argument_cast, "get_value");
let actual_tag_id = {
let tag_id_i64 =
crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value)
.into_int_value();
env.builder
.build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16")
};
let answer = env.builder.build_int_compare(
inkwell::IntPredicate::EQ,
tag_id.into_int_value(),
actual_tag_id,
"compare",
);
let tag_data_ptr = {
let data_index = env
.context
.i64_type()
.const_int(TAG_DATA_INDEX as u64, false);
let ptr = unsafe {
env.builder.build_gep(
tag_value_ptr.into_pointer_value(),
&[data_index],
"get_data_ptr",
)
};
env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque")
};
let field_vals = [(0, answer.into()), (1, tag_data_ptr)];
let output = struct_from_fields(env, output_type, field_vals.iter().copied());
env.builder.build_return(Some(&output));
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
_ => unreachable!(),
}
}
pub fn build_transform_caller<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function: FunctionValue<'ctx>,

View file

@ -1,3 +1,5 @@
use std::path::Path;
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build_dict::{
dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
@ -42,7 +44,9 @@ use inkwell::values::{
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
use morphic_lib::{CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions};
use morphic_lib::{
CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar,
};
use roc_builtins::bitcode;
use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_module::ident::TagName;
@ -832,8 +836,21 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
)
}
CallType::LowLevel { op, update_mode: _ } => {
run_low_level(env, layout_ids, scope, parent, layout, *op, arguments)
CallType::LowLevel { op, update_mode } => {
let bytes = update_mode.to_bytes();
let update_var = UpdateModeVar(&bytes);
let update_mode = func_spec_solutions.update_mode(update_var).ok();
run_low_level(
env,
layout_ids,
scope,
parent,
layout,
*op,
arguments,
update_mode,
)
}
CallType::HigherOrderLowLevel {
@ -883,6 +900,32 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
}
}
pub const TAG_ID_INDEX: u32 = 1;
pub const TAG_DATA_INDEX: u32 = 0;
pub fn struct_from_fields<'a, 'ctx, 'env, I>(
env: &Env<'a, 'ctx, 'env>,
struct_type: StructType<'ctx>,
values: I,
) -> StructValue<'ctx>
where
I: Iterator<Item = (usize, BasicValueEnum<'ctx>)>,
{
let mut struct_value = struct_type.const_zero().into();
// Insert field exprs into struct_val
for (index, field_val) in values {
let index: u32 = index as u32;
struct_value = env
.builder
.build_insert_value(struct_value, field_val, index, "insert_record_field")
.unwrap();
}
struct_value.into_struct_value()
}
pub fn build_exp_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -909,7 +952,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
Struct(sorted_fields) => {
let ctx = env.context;
let builder = env.builder;
// Determine types
let num_fields = sorted_fields.len();
@ -929,67 +971,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let mut struct_val = struct_type.const_zero().into();
// Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder
.build_insert_value(struct_val, field_val, index as u32, "insert_record_field")
.unwrap();
}
BasicValueEnum::StructValue(struct_val.into_struct_value())
}
Tag {
union_size,
arguments,
tag_layout,
..
} if *union_size == 1 && matches!(tag_layout, UnionLayout::NonRecursive(_)) => {
let it = arguments.iter();
let ctx = env.context;
let builder = env.builder;
// Determine types
let num_fields = arguments.len() + 1;
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for field_symbol in it {
let (val, field_layout) = load_symbol_and_layout(scope, field_symbol);
if !field_layout.is_dropped_because_empty() {
let field_type = basic_type_from_layout(env, &field_layout);
field_types.push(field_type);
field_vals.push(val);
}
}
// If the struct has only one field that isn't zero-sized,
// unwrap it. This is what the layout expects us to do.
if field_vals.len() == 1 {
field_vals.pop().unwrap()
} else {
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let mut struct_val = struct_type.const_zero().into();
// Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder
.build_insert_value(
struct_val,
field_val,
index as u32,
"insert_single_tag_field",
)
.unwrap();
}
BasicValueEnum::StructValue(struct_val.into_struct_value())
}
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()).into()
}
Tag {
@ -999,8 +983,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_id,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
debug_assert!(*union_size > 1);
let ctx = env.context;
@ -1038,19 +1020,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let mut struct_val = struct_type.const_zero().into();
// Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() {
struct_val = builder
.build_insert_value(
struct_val,
field_val,
index as u32,
"insert_multi_tag_field",
)
.unwrap();
}
let struct_val =
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate());
// How we create tag values
//
@ -1076,9 +1049,21 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// This tricks comes from
// https://github.com/raviqqe/ssf/blob/bc32aae68940d5bddf5984128e85af75ca4f4686/ssf-llvm/src/expression_compiler.rs#L116
let internal_type = basic_type_from_layout(env, &tag_layout);
let internal_type = block_of_memory(env.context, layout, env.ptr_bytes);
cast_tag_to_block_of_memory(builder, struct_val.into_struct_value(), internal_type)
let data = cast_tag_to_block_of_memory(builder, struct_val, internal_type);
let wrapper_type = env
.context
.struct_type(&[data.get_type(), env.context.i64_type().into()], false);
let tag_id_intval = env.context.i64_type().const_int(*tag_id as u64, false);
let field_vals = [
(TAG_ID_INDEX as usize, tag_id_intval.into()),
(TAG_DATA_INDEX as usize, data),
];
struct_from_fields(env, wrapper_type, field_vals.iter().copied()).into()
}
Tag {
arguments,
@ -1087,8 +1072,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_id,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
debug_assert!(*union_size > 1);
let ctx = env.context;
@ -1132,7 +1115,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
// Create the struct_type
let data_ptr = reserve_with_refcount(env, &tag_layout);
let data_ptr = reserve_with_refcount_union_as_block_of_memory(env, fields);
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_ptr = env
.builder
@ -1166,9 +1150,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
debug_assert_eq!(*tag_id, 0);
debug_assert_eq!(arguments.len(), fields.len());
let struct_layout =
Layout::Union(UnionLayout::NonRecursive(env.arena.alloc([*fields])));
let ctx = env.context;
let builder = env.builder;
@ -1207,7 +1188,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
// Create the struct_type
let data_ptr = reserve_with_refcount(env, &struct_layout);
let data_ptr = reserve_with_refcount_union_as_block_of_memory(env, &[fields]);
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_ptr = env
.builder
@ -1241,8 +1223,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_id,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
let tag_struct_type = basic_type_from_layout(env, &tag_layout);
let tag_struct_type = block_of_memory_slices(env.context, fields, env.ptr_bytes);
if *tag_id == *nullable_id as u8 {
let output_type = tag_struct_type.ptr_type(AddressSpace::Generic);
@ -1299,7 +1280,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
// Create the struct_type
let data_ptr = reserve_with_refcount(env, &tag_layout);
let data_ptr = reserve_with_refcount_union_as_block_of_memory(env, fields);
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_ptr = env
.builder
@ -1398,10 +1380,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
// Create the struct_type
let data_ptr = reserve_with_refcount(
env,
&Layout::Union(UnionLayout::NonRecursive(&[other_fields])),
);
let data_ptr = reserve_with_refcount_union_as_block_of_memory(env, &[other_fields]);
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_ptr = env
@ -1506,6 +1485,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
match union_layout {
UnionLayout::NonRecursive(tag_layouts) => {
let index = *index - 1;
debug_assert!(argument.is_struct_value());
let field_layouts = tag_layouts[*tag_id as usize];
let struct_layout = Layout::Struct(field_layouts);
@ -1519,7 +1500,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
);
let result = builder
.build_extract_value(struct_value, *index as u32, "")
.build_extract_value(struct_value, index as u32, "")
.expect("desired field did not decode");
result
@ -1611,77 +1592,82 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
structure,
union_layout,
} => {
let builder = env.builder;
// cast the argument bytes into the desired shape for this tag
let (argument, _structure_layout) = load_symbol_and_layout(scope, structure);
match union_layout {
UnionLayout::NonRecursive(_) => {
let pointer = builder.build_alloca(argument.get_type(), "get_type");
builder.build_store(pointer, argument);
let tag_id_pointer = builder.build_bitcast(
pointer,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"tag_id_pointer",
);
builder.build_load(tag_id_pointer.into_pointer_value(), "load_tag_id")
}
UnionLayout::Recursive(_) => {
let pointer = argument.into_pointer_value();
let tag_id_pointer = builder.build_bitcast(
pointer,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"tag_id_pointer",
);
builder.build_load(tag_id_pointer.into_pointer_value(), "load_tag_id")
}
UnionLayout::NonNullableUnwrapped(_) => env.context.i64_type().const_zero().into(),
UnionLayout::NullableWrapped { nullable_id, .. } => {
let argument_ptr = argument.into_pointer_value();
let is_null = env.builder.build_is_null(argument_ptr, "is_null");
get_tag_id(env, parent, union_layout, argument)
}
}
}
let ctx = env.context;
let then_block = ctx.append_basic_block(parent, "then");
let else_block = ctx.append_basic_block(parent, "else");
let cont_block = ctx.append_basic_block(parent, "cont");
pub fn get_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
union_layout: &UnionLayout<'a>,
argument: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
match union_layout {
UnionLayout::NonRecursive(_) => {
let tag = argument.into_struct_value();
let result = builder.build_alloca(ctx.i64_type(), "result");
builder
.build_extract_value(tag, TAG_ID_INDEX, "get_tag_id")
.unwrap()
}
UnionLayout::Recursive(_) => {
let pointer = argument.into_pointer_value();
let tag_id_pointer = builder.build_bitcast(
pointer,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"tag_id_pointer",
);
builder.build_load(tag_id_pointer.into_pointer_value(), "load_tag_id")
}
UnionLayout::NonNullableUnwrapped(_) => env.context.i64_type().const_zero().into(),
UnionLayout::NullableWrapped { nullable_id, .. } => {
let argument_ptr = argument.into_pointer_value();
let is_null = env.builder.build_is_null(argument_ptr, "is_null");
env.builder
.build_conditional_branch(is_null, then_block, else_block);
let ctx = env.context;
let then_block = ctx.append_basic_block(parent, "then");
let else_block = ctx.append_basic_block(parent, "else");
let cont_block = ctx.append_basic_block(parent, "cont");
{
env.builder.position_at_end(then_block);
let tag_id = ctx.i64_type().const_int(*nullable_id as u64, false);
env.builder.build_store(result, tag_id);
env.builder.build_unconditional_branch(cont_block);
}
let result = builder.build_alloca(ctx.i64_type(), "result");
{
env.builder.position_at_end(else_block);
let tag_id = extract_tag_discriminant_ptr(env, argument_ptr);
env.builder.build_store(result, tag_id);
env.builder.build_unconditional_branch(cont_block);
}
env.builder
.build_conditional_branch(is_null, then_block, else_block);
env.builder.position_at_end(cont_block);
env.builder.build_load(result, "load_result")
}
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
let argument_ptr = argument.into_pointer_value();
let is_null = env.builder.build_is_null(argument_ptr, "is_null");
let ctx = env.context;
let then_value = ctx.i64_type().const_int(*nullable_id as u64, false);
let else_value = ctx.i64_type().const_int(!*nullable_id as u64, false);
env.builder
.build_select(is_null, then_value, else_value, "select_tag_id")
}
{
env.builder.position_at_end(then_block);
let tag_id = ctx.i64_type().const_int(*nullable_id as u64, false);
env.builder.build_store(result, tag_id);
env.builder.build_unconditional_branch(cont_block);
}
{
env.builder.position_at_end(else_block);
let tag_id = extract_tag_discriminant_ptr(env, argument_ptr);
env.builder.build_store(result, tag_id);
env.builder.build_unconditional_branch(cont_block);
}
env.builder.position_at_end(cont_block);
env.builder.build_load(result, "load_result")
}
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
let argument_ptr = argument.into_pointer_value();
let is_null = env.builder.build_is_null(argument_ptr, "is_null");
let ctx = env.context;
let then_value = ctx.i64_type().const_int(*nullable_id as u64, false);
let else_value = ctx.i64_type().const_int(!*nullable_id as u64, false);
env.builder
.build_select(is_null, then_value, else_value, "select_tag_id")
}
}
}
@ -1728,17 +1714,52 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>(
pub fn reserve_with_refcount<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
) -> PointerValue<'ctx> {
let stack_size = layout.stack_size(env.ptr_bytes);
let alignment_bytes = layout.alignment_bytes(env.ptr_bytes);
let basic_type = basic_type_from_layout(env, layout);
reserve_with_refcount_help(env, basic_type, stack_size, alignment_bytes)
}
fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
fields: &[&[Layout<'a>]],
) -> PointerValue<'ctx> {
let basic_type = block_of_memory_slices(env.context, fields, env.ptr_bytes);
let stack_size = fields
.iter()
.map(|tag| tag.iter().map(|l| l.stack_size(env.ptr_bytes)).sum())
.max()
.unwrap_or(0);
let alignment_bytes = fields
.iter()
.map(|tag| tag.iter().map(|l| l.alignment_bytes(env.ptr_bytes)))
.flatten()
.max()
.unwrap_or(0);
reserve_with_refcount_help(env, basic_type, stack_size, alignment_bytes)
}
fn reserve_with_refcount_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
basic_type: impl BasicType<'ctx>,
stack_size: u32,
alignment_bytes: u32,
) -> PointerValue<'ctx> {
let ctx = env.context;
let len_type = env.ptr_int();
let value_bytes = layout.stack_size(env.ptr_bytes);
let value_bytes_intvalue = len_type.const_int(value_bytes as u64, false);
let value_bytes_intvalue = len_type.const_int(stack_size as u64, false);
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes);
allocate_with_refcount_help(env, layout, value_bytes_intvalue, rc1)
allocate_with_refcount_help(env, basic_type, alignment_bytes, value_bytes_intvalue, rc1)
}
pub fn allocate_with_refcount<'a, 'ctx, 'env>(
@ -1756,17 +1777,17 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>(
pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
value_type: impl BasicType<'ctx>,
alignment_bytes: u32,
number_of_data_bytes: IntValue<'ctx>,
initial_refcount: IntValue<'ctx>,
) -> PointerValue<'ctx> {
let builder = env.builder;
let ctx = env.context;
let value_type = basic_type_from_layout(env, layout);
let len_type = env.ptr_int();
let extra_bytes = layout.alignment_bytes(env.ptr_bytes).max(env.ptr_bytes);
let extra_bytes = alignment_bytes.max(env.ptr_bytes);
let ptr = {
// number of bytes we will allocated
@ -1776,7 +1797,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
"add_extra_bytes",
);
env.call_alloc(number_of_bytes, layout.alignment_bytes(env.ptr_bytes))
env.call_alloc(number_of_bytes, alignment_bytes)
};
// We must return a pointer to the first element:
@ -2478,25 +2499,87 @@ pub fn complex_bitcast<'ctx>(
}
}
fn extract_tag_discriminant_struct<'a, 'ctx, 'env>(
pub fn extract_tag_discriminant<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
from_value: StructValue<'ctx>,
parent: FunctionValue<'ctx>,
union_layout: UnionLayout<'a>,
cond_value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> {
let struct_type = env
.context
.struct_type(&[env.context.i64_type().into()], false);
let builder = env.builder;
let struct_value = complex_bitcast_struct_struct(
env.builder,
from_value,
struct_type,
"extract_tag_discriminant_struct",
);
match union_layout {
UnionLayout::NonRecursive(_) => {
let pointer = builder.build_alloca(cond_value.get_type(), "get_type");
builder.build_store(pointer, cond_value);
let tag_id_pointer = builder.build_bitcast(
pointer,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"tag_id_pointer",
);
builder
.build_load(tag_id_pointer.into_pointer_value(), "load_tag_id")
.into_int_value()
}
UnionLayout::Recursive(_) => {
let pointer = cond_value.into_pointer_value();
let tag_id_pointer = builder.build_bitcast(
pointer,
env.context.i64_type().ptr_type(AddressSpace::Generic),
"tag_id_pointer",
);
builder
.build_load(tag_id_pointer.into_pointer_value(), "load_tag_id")
.into_int_value()
}
UnionLayout::NonNullableUnwrapped(_) => env.context.i64_type().const_zero(),
UnionLayout::NullableWrapped { nullable_id, .. } => {
let argument_ptr = cond_value.into_pointer_value();
let is_null = env.builder.build_is_null(argument_ptr, "is_null");
env.builder
.build_extract_value(struct_value, 0, "")
.expect("desired field did not decode")
.into_int_value()
let ctx = env.context;
let then_block = ctx.append_basic_block(parent, "then");
let else_block = ctx.append_basic_block(parent, "else");
let cont_block = ctx.append_basic_block(parent, "cont");
let result = builder.build_alloca(ctx.i64_type(), "result");
env.builder
.build_conditional_branch(is_null, then_block, else_block);
{
env.builder.position_at_end(then_block);
let tag_id = ctx.i64_type().const_int(nullable_id as u64, false);
env.builder.build_store(result, tag_id);
env.builder.build_unconditional_branch(cont_block);
}
{
env.builder.position_at_end(else_block);
let tag_id = extract_tag_discriminant_ptr(env, argument_ptr);
env.builder.build_store(result, tag_id);
env.builder.build_unconditional_branch(cont_block);
}
env.builder.position_at_end(cont_block);
env.builder
.build_load(result, "load_result")
.into_int_value()
}
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
let argument_ptr = cond_value.into_pointer_value();
let is_null = env.builder.build_is_null(argument_ptr, "is_null");
let ctx = env.context;
let then_value = ctx.i64_type().const_int(nullable_id as u64, false);
let else_value = ctx.i64_type().const_int(!nullable_id as u64, false);
env.builder
.build_select(is_null, then_value, else_value, "select_tag_id")
.into_int_value()
}
}
}
fn extract_tag_discriminant_ptr<'a, 'ctx, 'env>(
@ -2590,57 +2673,9 @@ fn build_switch_ir<'a, 'ctx, 'env>(
.into_int_value()
}
Layout::Union(variant) => {
use UnionLayout::*;
cond_layout = Layout::Builtin(Builtin::Int64);
match variant {
NonRecursive(_) => {
// we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64);
let full_cond = cond_value.into_struct_value();
extract_tag_discriminant_struct(env, full_cond)
}
Recursive(_) => {
// we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64);
debug_assert!(cond_value.is_pointer_value());
extract_tag_discriminant_ptr(env, cond_value.into_pointer_value())
}
NonNullableUnwrapped(_) => unreachable!("there is no tag to switch on"),
NullableWrapped { nullable_id, .. } => {
// we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64);
let full_cond_ptr = cond_value.into_pointer_value();
let comparison: IntValue =
env.builder.build_is_null(full_cond_ptr, "is_null_cond");
let when_null = || {
env.context
.i64_type()
.const_int(nullable_id as u64, false)
.into()
};
let when_not_null = || extract_tag_discriminant_ptr(env, full_cond_ptr).into();
crate::llvm::build_list::build_basic_phi2(
env,
parent,
comparison,
when_null,
when_not_null,
BasicTypeEnum::IntType(env.context.i64_type()),
)
.into_int_value()
}
NullableUnwrapped { .. } => {
// there are only two options, so we do a `tag_id == 0` check and branch on that
unreachable!(
"we never switch on the tag id directly for NullableUnwrapped unions"
)
}
}
extract_tag_discriminant(env, parent, variant, cond_value)
}
Layout::Builtin(_) => cond_value.into_int_value(),
other => todo!("Build switch value from layout: {:?}", other),
@ -3187,8 +3222,9 @@ pub fn build_procedures<'a, 'ctx, 'env>(
opt_level: OptLevel,
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
debug_output_file: Option<&Path>,
) {
build_procedures_help(env, opt_level, procedures, entry_point);
build_procedures_help(env, opt_level, procedures, entry_point, debug_output_file);
}
pub fn build_procedures_return_main<'a, 'ctx, 'env>(
@ -3197,7 +3233,7 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
) -> (&'static str, FunctionValue<'ctx>) {
let mod_solutions = build_procedures_help(env, opt_level, procedures, entry_point);
let mod_solutions = build_procedures_help(env, opt_level, procedures, entry_point, None);
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
}
@ -3207,6 +3243,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
opt_level: OptLevel,
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
debug_output_file: Option<&Path>,
) -> &'a ModSolutions {
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut scope = Scope::default();
@ -3265,13 +3302,22 @@ fn build_procedures_help<'a, 'ctx, 'env>(
);
fn_val.print_to_stderr();
// module.print_to_stderr();
panic!(
if let Some(app_ll_file) = debug_output_file {
env.module.print_to_file(&app_ll_file).unwrap();
panic!(
r"😱 LLVM errors when defining function {:?}; I wrote the full LLVM IR to {:?}",
fn_val.get_name().to_str().unwrap(),
app_ll_file,
);
} else {
panic!(
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
fn_val.get_name().to_str().unwrap(),
mode,
);
)
}
}
}
}
@ -3803,6 +3849,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
env,
layout_ids,
roc_function_call,
result_layout,
list,
element_layout,
default,
@ -4196,6 +4243,7 @@ fn run_low_level<'a, 'ctx, 'env>(
layout: &Layout<'a>,
op: LowLevel,
args: &[Symbol],
update_mode: Option<UpdateMode>,
) -> BasicValueEnum<'ctx> {
use LowLevel::*;
@ -4654,27 +4702,6 @@ fn run_low_level<'a, 'ctx, 'env>(
wrapper_struct,
)
}
ListSetInPlace => {
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (index, _) = load_symbol_and_layout(scope, &args[1]);
let (element, _) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => {
// no elements, so nothing to remove
empty_list(env)
}
Layout::Builtin(Builtin::List(element_layout)) => list_set(
env,
layout_ids,
list,
index.into_int_value(),
element,
element_layout,
),
_ => unreachable!("invalid dict layout"),
}
}
ListSet => {
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (index, _) = load_symbol_and_layout(scope, &args[1]);
@ -4692,6 +4719,7 @@ fn run_low_level<'a, 'ctx, 'env>(
index.into_int_value(),
element,
element_layout,
update_mode.unwrap(),
),
_ => unreachable!("invalid dict layout"),
}

View file

@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]
use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_n_wrapper, build_inc_wrapper, call_bitcode_fn,
call_void_bitcode_fn,
build_dec_wrapper, build_eq_wrapper, build_has_tag_id, build_inc_n_wrapper, build_inc_wrapper,
call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, RocFunctionCall,
@ -13,6 +13,7 @@ use inkwell::context::Context;
use inkwell::types::{BasicType, BasicTypeEnum, PointerType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use morphic_lib::UpdateMode;
use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
@ -350,6 +351,7 @@ pub fn list_set<'a, 'ctx, 'env>(
index: IntValue<'ctx>,
element: BasicValueEnum<'ctx>,
element_layout: &'a Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
@ -359,6 +361,11 @@ pub fn list_set<'a, 'ctx, 'env>(
env.context.i8_type().ptr_type(AddressSpace::Generic),
);
let symbol = match update_mode {
UpdateMode::InPlace => bitcode::LIST_SET_IN_PLACE,
UpdateMode::Immutable => bitcode::LIST_SET,
};
let new_bytes = call_bitcode_fn(
env,
&[
@ -370,7 +377,7 @@ pub fn list_set<'a, 'ctx, 'env>(
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
&bitcode::LIST_SET,
&symbol,
);
store_list(env, new_bytes.into_pointer_value(), length)
@ -410,6 +417,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
function_call_return_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
@ -450,6 +458,18 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
);
}
ListWalk::WalkUntil | ListWalk::WalkBackwardsUntil => {
let function = env
.builder
.get_insert_block()
.unwrap()
.get_parent()
.unwrap();
let has_tag_id = match function_call_return_layout {
Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout),
_ => unreachable!(),
};
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
call_void_bitcode_fn(
env,
@ -462,7 +482,9 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
pass_as_opaque(env, default_ptr),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
layout_width(env, function_call_return_layout),
layout_width(env, default_layout),
has_tag_id.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
pass_as_opaque(env, result_ptr),
],
@ -604,6 +626,18 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
let function = env
.builder
.get_insert_block()
.unwrap()
.get_parent()
.unwrap();
let has_tag_id = match result_layout {
Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout),
_ => unreachable!(),
};
call_bitcode_fn(
env,
&[
@ -616,6 +650,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
layout_width(env, before_layout),
layout_width(env, result_layout),
layout_width(env, after_layout),
has_tag_id.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_KEEP_OKS,
@ -635,6 +670,18 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
let function = env
.builder
.get_insert_block()
.unwrap()
.get_parent()
.unwrap();
let has_tag_id = match result_layout {
Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout),
_ => unreachable!(),
};
call_bitcode_fn(
env,
&[
@ -647,6 +694,7 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
layout_width(env, before_layout),
layout_width(env, result_layout),
layout_width(env, after_layout),
has_tag_id.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_KEEP_ERRS,
@ -1080,7 +1128,9 @@ pub fn allocate_list<'a, 'ctx, 'env>(
// we assume that the list is indeed used (dead variables are eliminated)
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes);
allocate_with_refcount_help(env, elem_layout, number_of_data_bytes, rc1)
let basic_type = basic_type_from_layout(env, elem_layout);
let alignment_bytes = elem_layout.alignment_bytes(env.ptr_bytes);
allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes, rc1)
}
pub fn store_list<'a, 'ctx, 'env>(

View file

@ -1,6 +1,6 @@
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::Env;
use crate::llvm::build::{cast_block_of_memory_to_tag, complex_bitcast, FAST_CALL_CONV};
use crate::llvm::build::{cast_block_of_memory_to_tag, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout;
@ -850,9 +850,10 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
match union_layout {
NonRecursive(tags) => {
// SAFETY we know that non-recursive tags cannot be NULL
let id1 = nonrec_tag_id(env, tag1.into_struct_value());
let id2 = nonrec_tag_id(env, tag2.into_struct_value());
let id1 =
crate::llvm::build::get_tag_id(env, parent, union_layout, tag1).into_int_value();
let id2 =
crate::llvm::build::get_tag_id(env, parent, union_layout, tag2).into_int_value();
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
@ -1216,19 +1217,6 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
.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>,

View file

@ -50,7 +50,13 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
NonRecursive(_) => block_of_memory(env.context, layout, env.ptr_bytes),
NonRecursive(_) => {
let data = block_of_memory(env.context, layout, env.ptr_bytes);
env.context
.struct_type(&[data, env.context.i64_type().into()], false)
.into()
}
}
}
RecursivePointer => {
@ -118,7 +124,11 @@ pub fn block_of_memory<'ctx>(
ptr_bytes: u32,
) -> BasicTypeEnum<'ctx> {
// TODO make this dynamic
let union_size = layout.stack_size(ptr_bytes as u32);
let mut union_size = layout.stack_size(ptr_bytes as u32);
if let Layout::Union(UnionLayout::NonRecursive { .. }) = layout {
union_size -= ptr_bytes;
}
block_of_memory_help(context, union_size)
}
@ -182,8 +192,8 @@ pub fn zig_str_type<'a, 'ctx, 'env>(
env.module.get_struct_type("str.RocStr").unwrap()
}
// pub fn zig_dec_type<'a, 'ctx, 'env>(
// env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
// ) -> StructType<'ctx> {
// env.module.get_struct_type("dec.RocDec").unwrap()
// }
pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
) -> StructType<'ctx> {
env.module.get_struct_type("list.HasTagId").unwrap()
}

View file

@ -1,12 +1,10 @@
use crate::debug_info_init;
use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, Env, FAST_CALL_CONV,
LLVM_SADD_WITH_OVERFLOW_I64,
LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX, TAG_ID_INDEX,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{
basic_type_from_layout, block_of_memory, block_of_memory_slices, ptr_int,
};
use crate::llvm::convert::{basic_type_from_layout, block_of_memory_slices, ptr_int};
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::context::Context;
@ -1584,7 +1582,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let basic_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let basic_type = basic_type_from_layout(env, &layout);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(
@ -1640,19 +1638,11 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let wrapper_struct = arg_val.into_struct_value();
// read the tag_id
let tag_id = {
// the first element of the wrapping struct is an array of i64
let first_array = env
.builder
.build_extract_value(wrapper_struct, 0, "read_tag_id")
.unwrap()
.into_array_value();
env.builder
.build_extract_value(first_array, 0, "read_tag_id_2")
.unwrap()
.into_int_value()
};
let tag_id = env
.builder
.build_extract_value(wrapper_struct, TAG_ID_INDEX, "read_tag_id")
.unwrap()
.into_int_value();
let tag_id_u8 = env
.builder
@ -1680,7 +1670,12 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
debug_assert!(wrapper_type.is_struct_type());
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, wrapper_struct, wrapper_type);
let data_bytes = env
.builder
.build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id")
.unwrap()
.into_struct_value();
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type);
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {

View file

@ -18,7 +18,6 @@ pub enum LowLevel {
ListLen,
ListGetUnsafe,
ListSet,
ListSetInPlace,
ListSingle,
ListRepeat,
ListReverse,
@ -125,7 +124,6 @@ impl LowLevel {
| ListLen
| ListGetUnsafe
| ListSet
| ListSetInPlace
| ListDrop
| ListSingle
| ListRepeat

View file

@ -861,11 +861,13 @@ fn expr_spec(
union_layout,
} => match union_layout {
UnionLayout::NonRecursive(_) => {
// let index = (*index - 1) as u32;
let index = (*index - 1) as u32;
let tag_value_id = env.symbols[structure];
let tuple_value_id =
builder.add_unwrap_union(block, tag_value_id, *tag_id as u32)?;
builder.add_get_tuple_field(block, tuple_value_id, *index as u32)
builder.add_get_tuple_field(block, tuple_value_id, index)
}
_ => {
// for the moment recursive tag unions don't quite work

View file

@ -760,7 +760,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
match op {
ListLen | StrIsEmpty | StrCountGraphemes => arena.alloc_slice_copy(&[borrowed]),
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),

View file

@ -2083,18 +2083,16 @@ fn specialize_external<'a>(
tag_id,
..
} => {
debug_assert_eq!(field_layouts.len() - 1, captured.len());
// TODO check for field_layouts.len() == 1 and do a rename in that case?
for (mut index, (symbol, _variable)) in captured.iter().enumerate() {
// the field layouts do store the tag, but the tag value is
// not captured. So we drop the layout of the tag ID here
index += 1;
debug_assert!(matches!(union_layout, UnionLayout::NonRecursive(_)));
debug_assert_eq!(field_layouts.len(), captured.len());
// TODO therefore should the wrapped here not be RecordOrSingleTagUnion?
for (index, (symbol, _variable)) in captured.iter().enumerate() {
let expr = Expr::UnionAtIndex {
tag_id,
structure: Symbol::ARG_CLOSURE,
index: index as _,
// union at index still expects the index to be +1; it thinks
// the tag id is stored
index: index as u64 + 1,
union_layout,
};
@ -4037,29 +4035,20 @@ fn construct_closure_data<'a>(
tag_name,
union_layout,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols = Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i128));
let expr2 = Expr::Tag {
let expr = Expr::Tag {
tag_id,
tag_layout: union_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
arguments: symbols,
};
let hole = Stmt::Let(
Stmt::Let(
assigned,
expr2,
expr,
lambda_set.runtime_representation(),
env.arena.alloc(hole),
);
let hole = env.arena.alloc(hole);
Stmt::Let(tag_id_symbol, expr1, Layout::Builtin(Builtin::Int64), hole)
)
}
ClosureRepresentation::Other(Layout::Struct(field_layouts)) => {
debug_assert_eq!(field_layouts.len(), symbols.len());
@ -4251,12 +4240,10 @@ fn convert_tag_union<'a>(
(tag, Layout::Union(union_layout))
}
NonRecursive { sorted_tag_layouts } => {
let tag_id_symbol = env.unique_symbol();
opt_tag_id_symbol = Some(tag_id_symbol);
opt_tag_id_symbol = None;
field_symbols = {
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
temp.push(tag_id_symbol);
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
temp.extend(field_symbols_temp.iter().map(|r| r.1));
@ -7086,8 +7073,7 @@ fn from_can_pattern_help<'a>(
ctors.push(Ctor {
tag_id: TagId(i as u8),
name: tag_name.clone(),
// don't include tag discriminant in arity
arity: args.len() - 1,
arity: args.len(),
})
}
@ -7100,13 +7086,13 @@ fn from_can_pattern_help<'a>(
debug_assert_eq!(
arguments.len(),
argument_layouts[1..].len(),
argument_layouts.len(),
"The {:?} tag got {} arguments, but its layout expects {}!",
tag_name,
arguments.len(),
argument_layouts[1..].len(),
argument_layouts.len(),
);
let it = argument_layouts[1..].iter();
let it = argument_layouts.iter();
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push((

View file

@ -109,6 +109,8 @@ impl<'a> UnionLayout<'a> {
pub fn layout_at(self, tag_id: u8, index: usize) -> Layout<'a> {
let result = match self {
UnionLayout::NonRecursive(tag_layouts) => {
let index = index - 1;
let field_layouts = tag_layouts[tag_id as usize];
// this cannot be recursive; return immediately
@ -565,16 +567,21 @@ impl<'a> Layout<'a> {
use UnionLayout::*;
match variant {
NonRecursive(fields) => fields
.iter()
.map(|tag_layout| {
tag_layout
.iter()
.map(|field| field.stack_size(pointer_size))
.sum()
})
.max()
.unwrap_or_default(),
NonRecursive(fields) => {
let data_size: u32 = fields
.iter()
.map(|tag_layout| {
tag_layout
.iter()
.map(|field| field.stack_size(pointer_size))
.sum()
})
.max()
.unwrap_or_default();
// TEMPORARY
pointer_size + data_size
}
Recursive(_)
| NullableWrapped { .. }
@ -1587,7 +1594,9 @@ pub fn union_sorted_tags_help<'a>(
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
// add the tag discriminant (size currently always hardcoded to i64)
arg_layouts.push(Layout::Builtin(TAG_SIZE));
if is_recursive {
arg_layouts.push(Layout::Builtin(TAG_SIZE));
}
for var in arguments {
match Layout::from_var(&mut env, var) {

View file

@ -37,7 +37,7 @@ fn hash_record() {
fn hash_result() {
assert_evals_to!(
"Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ",
2878521786781103245,
10806428154792634888,
u64
);
}

View file

@ -19,8 +19,8 @@ fn applied_tag_nothing_ir() {
"#
),
1,
(i64, i64),
|(tag, _)| tag
(i64, u8),
|(_, tag)| tag
);
}
@ -38,8 +38,8 @@ fn applied_tag_nothing() {
"#
),
1,
(i64, i64),
|(tag, _)| tag
(i64, u8),
|(_, tag)| tag
);
}
@ -56,8 +56,8 @@ fn applied_tag_just() {
y
"#
),
(0, 0x4),
(i64, i64)
(0x4, 0),
(i64, u8)
);
}
@ -74,8 +74,8 @@ fn applied_tag_just_ir() {
y
"#
),
(0, 0x4),
(i64, i64)
(0x4, 0),
(i64, u8)
);
}
@ -96,8 +96,8 @@ fn applied_tag_just_enum() {
y
"#
),
(0, 2),
(i64, u8)
(2, 0),
(u8, i64)
);
}
@ -633,8 +633,8 @@ fn nested_tag_union() {
x
"#
),
(0, (0, 41)),
(i64, (i64, i64))
((41, 0), 0),
((i64, i64), i64)
);
}
#[test]
@ -805,8 +805,8 @@ fn alignment_in_multi_tag_construction() {
#"
),
(1, 32i64, true),
(i64, i64, bool)
(32i64, true, 1),
(i64, bool, i64)
);
assert_evals_to!(
@ -818,8 +818,8 @@ fn alignment_in_multi_tag_construction() {
x
#"
),
(1, 32i64, true, 2u8),
(i64, i64, bool, u8)
(32i64, true, 2u8, 1),
(i64, bool, u8, i64)
);
}
@ -1003,14 +1003,14 @@ fn applied_tag_function_result() {
x : List (Result Str *)
x = List.map [ "a", "b" ] Ok
x
List.keepOks x (\y -> y)
"#
),
RocList::from_slice(&[
(1, RocStr::from_slice("a".as_bytes())),
(1, RocStr::from_slice("b".as_bytes()))
(RocStr::from_slice("a".as_bytes())),
(RocStr::from_slice("b".as_bytes()))
]),
RocList<(i64, RocStr)>
RocList<RocStr>
);
}

View file

@ -22,9 +22,8 @@ procedure Test.3 (Test.4):
procedure Test.0 ():
let Test.28 = 0i64;
let Test.31 = 0i64;
let Test.30 = 3i64;
let Test.26 = Just Test.31 Test.30;
let Test.26 = Just Test.30;
let Test.29 = 1i64;
let Test.27 = Nil Test.29;
let Test.12 = Cons Test.28 Test.26 Test.27;

View file

@ -1,15 +1,13 @@
procedure Num.42 (#Attr.2, #Attr.3):
let Test.17 = 0i64;
let Test.13 = lowlevel NotEq #Attr.3 Test.17;
if Test.13 then
let Test.16 = 1i64;
let Test.15 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
let Test.14 = Ok Test.16 Test.15;
ret Test.14;
let Test.15 = 0i64;
let Test.12 = lowlevel NotEq #Attr.3 Test.15;
if Test.12 then
let Test.14 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
let Test.13 = Ok Test.14;
ret Test.13;
else
let Test.12 = 0i64;
let Test.11 = Struct {};
let Test.10 = Err Test.12 Test.11;
let Test.10 = Err Test.11;
ret Test.10;
procedure Test.0 ():

View file

@ -3,9 +3,8 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Test.6;
procedure Test.0 ():
let Test.13 = 0i64;
let Test.12 = 41i64;
let Test.1 = Just Test.13 Test.12;
let Test.1 = Just Test.12;
let Test.9 = 0i64;
let Test.10 = GetTagId Test.1;
let Test.11 = lowlevel Eq Test.9 Test.10;

View file

@ -1,7 +1,6 @@
procedure Test.0 ():
let Test.10 = 0i64;
let Test.9 = 3i64;
let Test.3 = Just Test.10 Test.9;
let Test.3 = Just Test.9;
let Test.6 = 0i64;
let Test.7 = GetTagId Test.3;
let Test.8 = lowlevel Eq Test.6 Test.7;

View file

@ -1,8 +1,7 @@
procedure Test.0 ():
let Test.12 = 1i64;
let Test.10 = 1i64;
let Test.11 = 2i64;
let Test.5 = These Test.12 Test.10 Test.11;
let Test.5 = These Test.10 Test.11;
let Test.9 = GetTagId Test.5;
switch Test.9:
case 2:

View file

@ -1,22 +1,20 @@
procedure List.3 (#Attr.2, #Attr.3):
let Test.15 = lowlevel ListLen #Attr.2;
let Test.11 = lowlevel NumLt #Attr.3 Test.15;
if Test.11 then
let Test.14 = 1i64;
let Test.13 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.12 = Ok Test.14 Test.13;
ret Test.12;
let Test.13 = lowlevel ListLen #Attr.2;
let Test.10 = lowlevel NumLt #Attr.3 Test.13;
if Test.10 then
let Test.12 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.11 = Ok Test.12;
ret Test.11;
else
let Test.10 = 0i64;
let Test.9 = Struct {};
let Test.8 = Err Test.10 Test.9;
let Test.8 = Err Test.9;
ret Test.8;
procedure Test.1 (Test.2):
let Test.16 = 1i64;
let Test.17 = 2i64;
let Test.18 = 3i64;
let Test.6 = Array [Test.16, Test.17, Test.18];
let Test.14 = 1i64;
let Test.15 = 2i64;
let Test.16 = 3i64;
let Test.6 = Array [Test.14, Test.15, Test.16];
let Test.7 = 0i64;
let Test.5 = CallByName List.3 Test.6 Test.7;
dec Test.6;

View file

@ -3,11 +3,9 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Test.8;
procedure Test.0 ():
let Test.21 = 0i64;
let Test.23 = 0i64;
let Test.22 = 41i64;
let Test.20 = Just Test.23 Test.22;
let Test.2 = Just Test.21 Test.20;
let Test.21 = 41i64;
let Test.20 = Just Test.21;
let Test.2 = Just Test.20;
joinpoint Test.17:
let Test.11 = 1i64;
ret Test.11;

View file

@ -1,15 +1,13 @@
procedure List.3 (#Attr.2, #Attr.3):
let Test.39 = lowlevel ListLen #Attr.2;
let Test.35 = lowlevel NumLt #Attr.3 Test.39;
if Test.35 then
let Test.38 = 1i64;
let Test.37 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.36 = Ok Test.38 Test.37;
ret Test.36;
let Test.37 = lowlevel ListLen #Attr.2;
let Test.34 = lowlevel NumLt #Attr.3 Test.37;
if Test.34 then
let Test.36 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.35 = Ok Test.36;
ret Test.35;
else
let Test.34 = 0i64;
let Test.33 = Struct {};
let Test.32 = Err Test.34 Test.33;
let Test.32 = Err Test.33;
ret Test.32;
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
@ -22,8 +20,8 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
ret #Attr.2;
procedure Test.1 (Test.2):
let Test.40 = 0i64;
let Test.30 = CallByName List.3 Test.2 Test.40;
let Test.38 = 0i64;
let Test.30 = CallByName List.3 Test.2 Test.38;
let Test.31 = 0i64;
let Test.29 = CallByName List.3 Test.2 Test.31;
let Test.8 = Struct {Test.29, Test.30};
@ -58,8 +56,8 @@ procedure Test.1 (Test.2):
jump Test.26;
procedure Test.0 ():
let Test.41 = 1i64;
let Test.42 = 2i64;
let Test.7 = Array [Test.41, Test.42];
let Test.39 = 1i64;
let Test.40 = 2i64;
let Test.7 = Array [Test.39, Test.40];
let Test.6 = CallByName Test.1 Test.7;
ret Test.6;

View file

@ -1,15 +1,13 @@
procedure List.3 (#Attr.2, #Attr.3):
let Test.41 = lowlevel ListLen #Attr.2;
let Test.37 = lowlevel NumLt #Attr.3 Test.41;
if Test.37 then
let Test.40 = 1i64;
let Test.39 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.38 = Ok Test.40 Test.39;
ret Test.38;
let Test.39 = lowlevel ListLen #Attr.2;
let Test.36 = lowlevel NumLt #Attr.3 Test.39;
if Test.36 then
let Test.38 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.37 = Ok Test.38;
ret Test.37;
else
let Test.36 = 0i64;
let Test.35 = Struct {};
let Test.34 = Err Test.36 Test.35;
let Test.34 = Err Test.35;
ret Test.34;
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
@ -56,7 +54,7 @@ procedure Test.1 (Test.2, Test.3, Test.4):
procedure Test.0 ():
let Test.10 = 0i64;
let Test.11 = 0i64;
let Test.42 = 1i64;
let Test.12 = Array [Test.42];
let Test.40 = 1i64;
let Test.12 = Array [Test.40];
let Test.9 = CallByName Test.1 Test.10 Test.11 Test.12;
ret Test.9;

View file

@ -1,6 +1,6 @@
procedure Num.24 (#Attr.2, #Attr.3):
let Test.30 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.30;
let Test.29 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.29;
procedure Num.26 (#Attr.2, #Attr.3):
let Test.25 = lowlevel NumMul #Attr.2 #Attr.3;
@ -23,8 +23,8 @@ procedure Test.1 (Test.2, Test.3):
procedure Test.7 (Test.10, #Attr.12):
let Test.4 = UnionAtIndex (Id 0) (Index 1) #Attr.12;
let Test.29 = CallByName Num.24 Test.10 Test.4;
ret Test.29;
let Test.28 = CallByName Num.24 Test.10 Test.4;
ret Test.28;
procedure Test.8 (Test.11, #Attr.12):
let Test.6 = UnionAtIndex (Id 1) (Index 2) #Attr.12;
@ -44,12 +44,10 @@ procedure Test.0 ():
let Test.13 = CallByName Test.1 Test.14 Test.15;
ret Test.13;
in
let Test.28 = true;
if Test.28 then
let Test.32 = 0i64;
let Test.7 = ClosureTag(Test.7) Test.32 Test.4;
let Test.27 = true;
if Test.27 then
let Test.7 = ClosureTag(Test.7) Test.4;
jump Test.22 Test.7;
else
let Test.27 = 1i64;
let Test.8 = ClosureTag(Test.8) Test.27 Test.5 Test.6;
let Test.8 = ClosureTag(Test.8) Test.5 Test.6;
jump Test.22 Test.8;

View file

@ -1,6 +1,6 @@
procedure Num.24 (#Attr.2, #Attr.3):
let Test.26 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.26;
let Test.25 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.25;
procedure Num.26 (#Attr.2, #Attr.3):
let Test.21 = lowlevel NumMul #Attr.2 #Attr.3;
@ -8,8 +8,8 @@ procedure Num.26 (#Attr.2, #Attr.3):
procedure Test.6 (Test.8, #Attr.12):
let Test.4 = UnionAtIndex (Id 0) (Index 1) #Attr.12;
let Test.25 = CallByName Num.24 Test.8 Test.4;
ret Test.25;
let Test.24 = CallByName Num.24 Test.8 Test.4;
ret Test.24;
procedure Test.7 (Test.9, #Attr.12):
let Test.5 = UnionAtIndex (Id 1) (Index 1) #Attr.12;
@ -35,12 +35,10 @@ procedure Test.0 ():
jump Test.15 Test.17;
in
let Test.24 = true;
if Test.24 then
let Test.28 = 0i64;
let Test.6 = ClosureTag(Test.6) Test.28 Test.4;
let Test.23 = true;
if Test.23 then
let Test.6 = ClosureTag(Test.6) Test.4;
jump Test.19 Test.6;
else
let Test.23 = 1i64;
let Test.7 = ClosureTag(Test.7) Test.23 Test.5;
let Test.7 = ClosureTag(Test.7) Test.5;
jump Test.19 Test.7;

View file

@ -3,11 +3,9 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Test.8;
procedure Test.0 ():
let Test.21 = 0i64;
let Test.23 = 0i64;
let Test.22 = 41i64;
let Test.20 = Just Test.23 Test.22;
let Test.2 = Just Test.21 Test.20;
let Test.21 = 41i64;
let Test.20 = Just Test.21;
let Test.2 = Just Test.20;
joinpoint Test.17:
let Test.11 = 1i64;
ret Test.11;

View file

@ -1,7 +1,6 @@
procedure Test.1 (Test.5):
let Test.20 = 1i64;
let Test.19 = 2i64;
let Test.2 = Ok Test.20 Test.19;
let Test.2 = Ok Test.19;
joinpoint Test.9 Test.3:
ret Test.3;
in

View file

@ -28,13 +28,13 @@ arraystring = "0.3.0"
libc = "0.2"
page_size = "0.4"
winit = "0.24"
wgpu = "0.8"
wgpu = "0.9"
glyph_brush = "0.7"
log = "0.4"
zerocopy = "0.3"
env_logger = "0.8"
futures = "0.3"
wgpu_glyph = "0.12"
wgpu_glyph = "0.13"
cgmath = "0.18.0"
snafu = { version = "0.6", features = ["backtraces"] }
colored = "2"

View file

@ -31,7 +31,7 @@ Nice collection of research on innovative editors, [link](https://futureofcoding
* [Self](https://selflanguage.org/) programming language
* [Primitive](https://primitive.io/) code exploration in Virtual Reality
* [Luna](https://www.luna-lang.org/) language for interactive data processing and visualization
* [Hazel Livelits](https://hazel.org/papers/livelits-paper.pdf) interactive plugins, see GIF's [here](https://twitter.com/disconcision/status/1408155781120376833).
### Debugging
* [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer)
@ -70,6 +70,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* [Lamdu](http://www.lamdu.org/) live functional programming.
* [Sourcetrail](https://www.sourcetrail.com/) nice tree-like source explorer.
* [Unisonweb](https://www.unisonweb.org), definition based [editor](https://twitter.com/shojberg/status/1364666092598288385) as opposed to file based.
* [Utopia](https://utopia.app/) integrated design and development environment for React. Design and code update each other, in real time.
### Voice Interaction Related

View file

@ -1 +0,0 @@
hello-world

View file

@ -1,6 +0,0 @@
app "hello-world"
packages { base: "platform" }
imports []
provides [ main ] to base
main = "Hello, World!\n"

View file

@ -1,48 +0,0 @@
# Hello, World!
To run, `cd` into this directory and run:
```bash
$ cargo run run Hello.roc
```
To run in release mode instead, do:
```bash
$ cargo run --release run Hello.roc
```
## Troubleshooting
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure
function (in this case, a thunk that always returns `"Hello, World!"`) and
then the host calls that function. Fundamentally, that's the whole idea! The host
might not even have a `main` - it could be a library, a plugin, anything.
Everything else is built on this basic "hosts calling linked pure functions" design.
For example, things get more interesting when the compiled Roc function returns
a `Task` - that is, a tagged union data structure containing function pointers
to callback closures. This lets the Roc pure function describe arbitrary
chainable effects, which the host can interpret to perform I/O as requested by
the Roc program. (The tagged union `Task` would have a variant for each supported
I/O operation.)
In this trivial example, it's very easy to line up the API between the host and
the Roc program. In a more involved host, this would be much trickier - especially
if the API were changing frequently during development.
The idea there is to have a first-class concept of "glue code" which host authors
can write (it would be plain Roc code, but with some extra keywords that aren't
available in normal modules - kinda like `port module` in Elm), and which
describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary.
Roc application authors only care about the Roc-host/Roc-app portion, and the
host author only cares about the Roc-host/C bounary when implementing the host.
Using this glue code, the Roc compiler can generate C header files describing the
boundary. This not only gets us host compatibility with C compilers, but also
Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen)
generates correct Rust FFI bindings from C headers.

View file

@ -1,10 +0,0 @@
platform examples/hello-world
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -1,44 +0,0 @@
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
void* roc_alloc(size_t size, unsigned int alignment) {
return malloc(size);
}
void* roc_realloc(void* ptr, size_t old_size, size_t new_size, unsigned int alignment) {
return realloc(ptr, new_size);
}
void roc_dealloc(void* ptr, unsigned int alignment) {
free(ptr);
}
struct RocStr {
char* bytes;
size_t len;
};
struct RocCallResult {
size_t flag;
struct RocStr content;
};
extern void roc__mainForHost_1_exposed(struct RocCallResult *re);
int main() {
// Make space for the result
struct RocCallResult callresult;
// Call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult);
struct RocStr str = callresult.content;
// Write to stdout
write(1, &str.bytes, 14);
return 0;
}

View file

@ -3,10 +3,4 @@ app "hello-world"
imports []
provides [ main ] to base
greeting =
hi = "Hello"
name = "World"
"\(hi), \(name)!!!!!!!!!!!!!"
main = greeting
main = "Hello, World!\n"

View file

@ -1,7 +1,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
void* roc_alloc(size_t size, unsigned int alignment) {
return malloc(size);
@ -20,6 +22,29 @@ struct RocStr {
size_t len;
};
bool is_small_str(struct RocStr str) {
return ((ssize_t)str.len) < 0;
}
// Determine the length of the string, taking into
// account the small string optimization
size_t roc_str_len(struct RocStr str) {
char* bytes = (char*)&str;
char last_byte = bytes[sizeof(str) - 1];
char last_byte_xored = last_byte ^ 0b10000000;
size_t small_len = (size_t)(last_byte_xored);
size_t big_len = str.len;
// Avoid branch misprediction costs by always
// determining both small_len and big_len,
// so this compiles to a cmov instruction.
if (is_small_str(str)) {
return small_len;
} else {
return big_len;
}
}
struct RocCallResult {
size_t flag;
struct RocStr content;
@ -27,50 +52,32 @@ struct RocCallResult {
extern void roc__mainForHost_1_exposed(struct RocCallResult *re);
const size_t MAX_STACK_STR_BYTES = 1024;
int main() {
// make space for the result
struct RocCallResult callresult;
// Make space for the Roc call result
struct RocCallResult call_result;
// call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult);
struct RocStr str = callresult.content;
// Call Roc to populate call_result
roc__mainForHost_1_exposed(&call_result);
// Convert from RocStr to C string (null-terminated char*)
size_t len = str.len;
char* c_str;
// Determine str_len and the str_bytes pointer,
// taking into account the small string optimization.
struct RocStr str = call_result.content;
size_t str_len = roc_str_len(str);
char* str_bytes;
// Allocate on the stack unless the string is particularly big.
// (Don't want a stack overflow!)
if (len <= MAX_STACK_STR_BYTES) {
c_str = (char*)alloca(len + 1);
if (is_small_str(str)) {
str_bytes = (char*)&str;
} else {
c_str = (char*)malloc(len + 1);
str_bytes = str.bytes;
}
memcpy(c_str, str.bytes, len);
// Write to stdout
if (write(1, str_bytes, str_len) >= 0) {
// Writing succeeded!
return 0;
} else {
printf("Error writing to stdout: %s\n", strerror(errno));
// null-terminate
c_str[len] = 0;
// Print the string to stdout
printf("%s\n", c_str);
// Pointer to the beginning of the RocStr's actual allocation, which is
// the size_t immediately preceding the first stored byte.
size_t* str_base_ptr = (size_t*)str.bytes - 1;
// If *str_base_ptr is equal to 0, then the string is in the
// read-only data section of the binary, and can't be freed!
if (*str_base_ptr != 0) {
roc_dealloc(str_base_ptr, 8);
return 1;
}
// If we malloc'd c_str, free it.
if (len > MAX_STACK_STR_BYTES) {
free(c_str);
}
return 0;
}