diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 49b31e3425..c4ec83a944 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -1,12 +1,10 @@ # Building the Roc compiler from source -## Installing LLVM, Python, Zig, valgrind, libunwind, and libc++-dev +## Installing LLVM, Zig, valgrind, and Python 2.7 To build the compiler, you need these installed: -* `libunwind` (macOS should already have this one installed) -* `libc++-dev` and `libc++abi-dev` * Python 2.7 (Windows only), `python-is-python3` (Ubuntu) * [Zig](https://ziglang.org/), see below for version * LLVM, see below for version @@ -18,11 +16,6 @@ Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specifi For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. -### libunwind & libc++-dev - -MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`). -Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev libc++abi-dev`.) - ### libcxb libraries You may see an error like this during builds: diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index f75826c1a5..d97d8087c6 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -9,7 +9,7 @@ use roc_mono::ir::ProcLayout; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_region::all::{Located, Region}; -use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable}; +use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; struct Env<'a, 'env> { arena: &'a Bump, @@ -162,19 +162,27 @@ fn jit_to_ast_help<'a>( Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); Ok(single_tag_union_to_ast( env, ptr, field_layouts, - tag_name.clone(), + tag_name, payload_vars, )) } - Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => Ok( - single_tag_union_to_ast(env, ptr, field_layouts, *tag_name.clone(), &[]), - ), + Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { + let tag_name = &env.subs[*tag_name]; + + Ok(single_tag_union_to_ast( + env, + ptr, + field_layouts, + tag_name, + &[], + )) + } Content::Structure(FlatType::Func(_, _, _)) => { // a function with a struct as the closure environment Err(ToAstProblem::FunctionLayout) @@ -206,8 +214,13 @@ fn jit_to_ast_help<'a>( Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(union_layouts.len(), tags.len()); - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = - tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect(); + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); + + let tags_map: roc_collections::all::MutMap<_, _> = + tags_vec.iter().cloned().collect(); let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs); @@ -247,7 +260,7 @@ fn jit_to_ast_help<'a>( *(ptr.add(offset as usize) as *const i16) as i64 } Builtin::Int64 => { - // used by non-recursive tag unions at the + // used by non-recursive unions at the // moment, remove if that is no longer the case *(ptr.add(offset as usize) as *const i64) as i64 } @@ -262,7 +275,7 @@ fn jit_to_ast_help<'a>( let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr)); - let variables = &tags[tag_name]; + let variables = &tags_map[tag_name]; debug_assert_eq!(arg_layouts.len(), variables.len()); @@ -295,7 +308,7 @@ fn jit_to_ast_help<'a>( let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr)); - let variables = &tags[tag_name]; + let variables = &tags_map[tag_name]; // because the arg_layouts include the tag ID, it is one longer debug_assert_eq!( @@ -339,8 +352,6 @@ fn jit_to_ast_help<'a>( | Layout::RecursivePointer => { todo!("add support for rendering recursive tag unions in the REPL") } - - Layout::Closure(_, _, _) => Err(ToAstProblem::FunctionLayout), } } @@ -436,11 +447,12 @@ fn ptr_to_ast<'a>( Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); - let (tag_name, payload_vars) = tags.iter().next().unwrap(); - single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars) + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + single_tag_union_to_ast(env, ptr, field_layouts, tag_name, payload_vars) } Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { - single_tag_union_to_ast(env, ptr, field_layouts, *tag_name.clone(), &[]) + let tag_name = &env.subs[*tag_name]; + single_tag_union_to_ast(env, ptr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { struct_to_ast(env, ptr, &[], RecordFields::empty()) @@ -472,7 +484,8 @@ fn list_to_ast<'a>( Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => { debug_assert_eq!(vars.len(), 1); - let elem_var = *vars.first().unwrap(); + let elem_var_index = vars.into_iter().next().unwrap(); + let elem_var = env.subs[elem_var_index]; env.subs.get_content_without_compacting(elem_var) } @@ -511,14 +524,14 @@ fn single_tag_union_to_ast<'a>( env: &Env<'a, '_>, ptr: *const u8, field_layouts: &'a [Layout<'a>], - tag_name: TagName, + tag_name: &TagName, payload_vars: &[Variable], ) -> Expr<'a> { debug_assert_eq!(field_layouts.len(), payload_vars.len()); let arena = env.arena; - let tag_expr = tag_name_to_expr(env, &tag_name); + let tag_expr = tag_name_to_expr(env, tag_name); let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr)); @@ -639,6 +652,36 @@ fn struct_to_ast<'a>( } } +fn unpack_single_element_tag_union(subs: &Subs, tags: UnionTags) -> (&TagName, &[Variable]) { + let (tag_name_index, payload_vars_index) = tags.iter_all().next().unwrap(); + + let tag_name = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index].as_subs_slice(); + let payload_vars = subs.get_subs_slice(*subs_slice); + + (tag_name, payload_vars) +} + +fn unpack_two_element_tag_union( + subs: &Subs, + tags: UnionTags, +) -> (&TagName, &[Variable], &TagName, &[Variable]) { + let mut it = tags.iter_all(); + let (tag_name_index, payload_vars_index) = it.next().unwrap(); + + let tag_name1 = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index].as_subs_slice(); + let payload_vars1 = subs.get_subs_slice(*subs_slice); + + let (tag_name_index, payload_vars_index) = it.next().unwrap(); + + let tag_name2 = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index].as_subs_slice(); + let payload_vars2 = subs.get_subs_slice(*subs_slice); + + (tag_name1, payload_vars1, tag_name2, payload_vars2) +} + fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> { use Content::*; @@ -685,7 +728,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a } } FlatType::TagUnion(tags, _) if tags.len() == 1 => { - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); @@ -720,9 +763,8 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a Expr::Apply(loc_tag_expr, payload, CalledVia::Space) } FlatType::TagUnion(tags, _) if tags.len() == 2 => { - let mut tags_iter = tags.iter(); - let (tag_name_1, payload_vars_1) = tags_iter.next().unwrap(); - let (tag_name_2, payload_vars_2) = tags_iter.next().unwrap(); + let (tag_name_1, payload_vars_1, tag_name_2, payload_vars_2) = + unpack_two_element_tag_union(env.subs, *tags); debug_assert!(payload_vars_1.is_empty()); debug_assert!(payload_vars_2.is_empty()); @@ -801,7 +843,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> } } FlatType::TagUnion(tags, _) if tags.len() == 1 => { - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); @@ -839,8 +881,10 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> // anything with fewer tags is not a byte debug_assert!(tags.len() > 2); - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = - tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect(); + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs); @@ -923,7 +967,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E // This was a single-tag union that got unwrapped at runtime. debug_assert_eq!(tags.len(), 1); - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); // If this tag union represents a number, skip right to // returning tis as an Expr::Num diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index a1dd4d3372..76843c9d2d 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -135,10 +135,6 @@ pub fn gen_and_eval<'a>( &context, "", )); - // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no - // platform to provide them. - add_default_roc_externs(&context, module, &builder, ptr_bytes); - // mark our zig-defined builtins as internal for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); @@ -183,11 +179,15 @@ pub fn gen_and_eval<'a>( interns, module, ptr_bytes, - leak: false, + is_gen_test: false, // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), }; + // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no + // platform to provide them. + add_default_roc_externs(&env); + let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( &env, opt_level, diff --git a/cli/tests/fixtures/multi-dep-str/platform/host.zig b/cli/tests/fixtures/multi-dep-str/platform/host.zig index e60688a037..bf16bd9b65 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -40,6 +40,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const RocCallResult = extern struct { flag: usize, content: RocStr }; const Unit = extern struct {}; diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index e60688a037..bf16bd9b65 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -40,6 +40,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const RocCallResult = extern struct { flag: usize, content: RocStr }; const Unit = extern struct {}; diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 17b4b1c70a..54af7c5872 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -421,9 +421,6 @@ fn link_linux( "-lrt", "-lutil", "-lc_nonshared", - "-lc++", - "-lc++abi", - "-lunwind", libgcc_path.to_str().unwrap(), // Output "-o", @@ -488,9 +485,6 @@ fn link_macos( // "-lrt", // TODO shouldn't we need this? // "-lc_nonshared", // TODO shouldn't we need this? // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 - "-lc++", - // "-lc++abi", - // "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli // "Security", // Output diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index d3e68f112f..fc6af6b3e5 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -16,6 +16,10 @@ pub struct CodeGenTiming { pub emit_o_file: Duration, } +// TODO: If modules besides this one start needing to know which version of +// llvm we're using, consider moving me somewhere else. +const LLVM_VERSION: &str = "12"; + // TODO how should imported modules factor into this? What if those use builtins too? // TODO this should probably use more helper functions // TODO make this polymorphic in the llvm functions so it can be reused for another backend. @@ -140,7 +144,9 @@ pub fn gen_from_mono_module( interns: loaded.interns, module, ptr_bytes, - leak: false, + // in gen_tests, the compiler provides roc_panic + // and sets up the setjump/longjump exception handling + is_gen_test: false, exposed_to_host: loaded.exposed_to_host.keys().copied().collect(), }; @@ -154,6 +160,9 @@ pub fn gen_from_mono_module( env.dibuilder.finalize(); + // we don't use the debug info, and it causes weird errors. + module.strip_debug_info(); + // Uncomment this to see the module's optimized LLVM instruction output: // env.module.print_to_stderr(); @@ -195,7 +204,6 @@ pub fn gen_from_mono_module( // run the debugir https://github.com/vaivaswatha/debugir tool match Command::new("debugir") - .env_clear() .args(&["-instnamer", app_ll_file.to_str().unwrap()]) .output() { @@ -213,7 +221,6 @@ pub fn gen_from_mono_module( // assemble the .ll into a .bc let _ = Command::new("llvm-as") - .env_clear() .args(&[ app_ll_dbg_file.to_str().unwrap(), "-o", @@ -222,18 +229,26 @@ pub fn gen_from_mono_module( .output() .unwrap(); + let llc_args = &[ + "-filetype=obj", + app_bc_file.to_str().unwrap(), + "-o", + app_o_file.to_str().unwrap(), + ]; + // write the .o file. Note that this builds the .o for the local machine, // and ignores the `target_machine` entirely. - let _ = Command::new("llc-12") - .env_clear() - .args(&[ - "-filetype=obj", - app_bc_file.to_str().unwrap(), - "-o", - app_o_file.to_str().unwrap(), - ]) - .output() - .unwrap(); + // + // different systems name this executable differently, so we shotgun for + // the most common ones and then give up. + let _: Result = + Command::new(format!("llc-{}", LLVM_VERSION)) + .args(llc_args) + .output() + .or_else(|_| Command::new("llc").args(llc_args).output()) + .map_err(|_| { + panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION); + }); } else { // Emit the .o file diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 5259cf3e02..22e76054ea 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -733,6 +733,30 @@ pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: return output; } +pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { + const old_length = list.len(); + var output = list.reallocate(alignment, old_length + 1, element_width); + + // can't use one memcpy here because source and target overlap + if (output.bytes) |target| { + var i: usize = old_length; + + while (i > 0) { + i -= 1; + + // move the ith element to the (i + 1)th position + @memcpy(target + (i + 1) * element_width, target + i * element_width, element_width); + } + + // finally copy in the new first element + if (element) |source| { + @memcpy(target, source, element_width); + } + } + + return output; +} + pub fn listSwap( list: RocList, alignment: u32, diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index d76dcef2be..c0d050a865 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -33,6 +33,7 @@ comptime { exportListFn(list.listContains, "contains"); exportListFn(list.listRepeat, "repeat"); exportListFn(list.listAppend, "append"); + exportListFn(list.listPrepend, "prepend"); exportListFn(list.listSingle, "single"); exportListFn(list.listJoin, "join"); exportListFn(list.listRange, "range"); @@ -78,6 +79,9 @@ comptime { exportNumFn(num.powInt, "pow_int"); exportNumFn(num.acos, "acos"); exportNumFn(num.asin, "asin"); + exportNumFn(num.bytesToU16C, "bytes_to_u16"); + exportNumFn(num.bytesToU32C, "bytes_to_u32"); + exportNumFn(num.round, "round"); } // Str Module @@ -98,6 +102,15 @@ comptime { exportStrFn(str.strEqual, "equal"); exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.fromUtf8C, "from_utf8"); + exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); +} + +// Utils +const utils = @import("utils.zig"); +comptime { + exportUtilsFn(utils.test_panic, "test_panic"); + + @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } // Export helpers - Must be run inside a comptime @@ -120,6 +133,10 @@ fn exportDecFn(comptime func: anytype, comptime func_name: []const u8) void { exportBuiltinFn(func, "dec." ++ func_name); } +fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void { + exportBuiltinFn(func, "utils." ++ func_name); +} + // Custom panic function, as builtin Zig version errors during LLVM verification pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { std.debug.print("{s}: {?}", .{ message, stacktrace }); diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index b42aee20ca..94873f2fe6 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -1,6 +1,7 @@ const std = @import("std"); const always_inline = std.builtin.CallOptions.Modifier.always_inline; const math = std.math; +const RocList = @import("list.zig").RocList; pub fn atan(num: f64) callconv(.C) f64 { return @call(.{ .modifier = always_inline }, math.atan, .{num}); @@ -21,3 +22,25 @@ pub fn acos(num: f64) callconv(.C) f64 { pub fn asin(num: f64) callconv(.C) f64 { return @call(.{ .modifier = always_inline }, math.asin, .{num}); } + +pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 { + return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position }); +} + +fn bytesToU16(arg: RocList, position: usize) u16 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u16, [_]u8{ bytes[position], bytes[position + 1] }); +} + +pub fn bytesToU32C(arg: RocList, position: usize) callconv(.C) u32 { + return @call(.{ .modifier = always_inline }, bytesToU32, .{ arg, position }); +} + +fn bytesToU32(arg: RocList, position: usize) u32 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); +} + +pub fn round(num: f64) callconv(.C) i64 { + return @floatToInt(i32, (@round(num))); +} diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index bfabac41d2..5f21c91da5 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1156,6 +1156,11 @@ const FromUtf8Result = extern struct { problem_code: Utf8ByteProblem, }; +const CountAndStart = extern struct { + count: usize, + start: usize, +}; + pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); } @@ -1192,6 +1197,24 @@ fn fromUtf8(arg: RocList) FromUtf8Result { } } +pub fn fromUtf8RangeC(arg: RocList, countAndStart: CountAndStart, output: *FromUtf8Result) callconv(.C) void { + output.* = @call(.{ .modifier = always_inline }, fromUtf8Range, .{ arg, countAndStart }); +} + +fn fromUtf8Range(arg: RocList, countAndStart: CountAndStart) FromUtf8Result { + const bytes = @ptrCast([*]const u8, arg.bytes)[countAndStart.start..countAndStart.count]; + + if (unicode.utf8ValidateSlice(bytes)) { + // the output will be correct. Now we need to clone the input + const string = RocStr.init(@ptrCast([*]const u8, bytes), countAndStart.count); + + return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + } else { + const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); + return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; + } +} + fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: Utf8ByteProblem } { var index: usize = 0; diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 7551f89de2..f83fbfd227 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -15,12 +15,16 @@ extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen // This should never be passed a null pointer. extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void; +// Signals to the host that the program has paniced +extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void; + comptime { // During tetsts, use the testing allocators to satisfy these functions. if (std.builtin.is_test) { @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong }); + @export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong }); } } @@ -41,6 +45,10 @@ fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void { std.testing.allocator.destroy(ptr); } +fn testing_roc_panic(c_ptr: *c_void, _: u32) callconv(.C) void { + @panic("Roc paniced"); +} + pub fn alloc(size: usize, alignment: u32) [*]u8 { return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); } @@ -53,6 +61,22 @@ pub fn dealloc(c_ptr: [*]u8, alignment: u32) void { return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment }); } +// must export this explicitly because right now it is not used from zig code +pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { + return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment }); +} + +// indirection because otherwise zig creats an alias to the panic function which our LLVM code +// does not know how to deal with +pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { + const cstr = @ptrCast([*:0]u8, c_ptr); + + // const stderr = std.io.getStdErr().writer(); + // stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable; + + std.c.exit(1); +} + pub const Inc = fn (?[*]u8) callconv(.C) void; pub const IncN = fn (?[*]u8, u64) callconv(.C) void; pub const Dec = fn (?[*]u8) callconv(.C) void; diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 1b9276e4dc..c7ac45f69c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -8,6 +8,9 @@ pub const NUM_ACOS: &str = "roc_builtins.num.acos"; pub const NUM_ATAN: &str = "roc_builtins.num.atan"; pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite"; pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int"; +pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; +pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; +pub const NUM_ROUND: &str = "roc_builtins.num.round"; pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; @@ -24,6 +27,7 @@ pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float"; pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; +pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range"; pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; @@ -56,6 +60,7 @@ pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards"; pub const LIST_CONTAINS: &str = "roc_builtins.list.contains"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_APPEND: &str = "roc_builtins.list.append"; +pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_DROP: &str = "roc_builtins.list.drop"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SINGLE: &str = "roc_builtins.list.single"; @@ -75,3 +80,5 @@ pub const DEC_ADD_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow"; pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow"; pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow"; pub const DEC_DIV: &str = "roc_builtins.dec.div"; + +pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 198a6e648a..b14d1dd435 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -4,11 +4,12 @@ use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::builtin_aliases::{ bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type, - ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u32_type, u64_type, - u8_type, + ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u16_type, u32_type, + u64_type, u8_type, }; use roc_types::solved_types::SolvedType; use roc_types::subs::VarId; +use roc_types::types::RecordField; use std::collections::HashMap; /// Example: @@ -500,6 +501,32 @@ pub fn types() -> MutMap { Box::new(float_type(flex(TVAR1))), ); + // bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] + { + let position_out_of_bounds = SolvedType::TagUnion( + vec![(TagName::Global("OutOfBounds".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_top_level_function_type!( + Symbol::NUM_BYTES_TO_U16, + vec![list_type(u8_type()), nat_type()], + Box::new(result_type(u16_type(), position_out_of_bounds)), + ); + } + + // bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] + { + let position_out_of_bounds = SolvedType::TagUnion( + vec![(TagName::Global("OutOfBounds".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_top_level_function_type!( + Symbol::NUM_BYTES_TO_U32, + vec![list_type(u8_type()), nat_type()], + Box::new(result_type(u32_type(), position_out_of_bounds)), + ); + } + // Bool module // and : Bool, Bool -> Bool @@ -592,20 +619,50 @@ pub fn types() -> MutMap { ); // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* - let bad_utf8 = SolvedType::TagUnion( - vec![( - TagName::Global("BadUtf8".into()), - // vec![str_utf8_problem_type()], - vec![str_utf8_byte_problem_type(), nat_type()], - )], - Box::new(SolvedType::Wildcard), - ); + { + let bad_utf8 = SolvedType::TagUnion( + vec![( + TagName::Global("BadUtf8".into()), + vec![str_utf8_byte_problem_type(), nat_type()], + )], + Box::new(SolvedType::Wildcard), + ); - add_top_level_function_type!( - Symbol::STR_FROM_UTF8, - vec![list_type(u8_type())], - Box::new(result_type(str_type(), bad_utf8)), - ); + add_top_level_function_type!( + Symbol::STR_FROM_UTF8, + vec![list_type(u8_type())], + Box::new(result_type(str_type(), bad_utf8)), + ); + } + + // fromUtf8Range : List U8 -> Result Str [ BadUtf8 Utf8Problem, OutOfBounds ]* + { + let bad_utf8 = SolvedType::TagUnion( + vec![ + ( + TagName::Global("BadUtf8".into()), + vec![str_utf8_byte_problem_type(), nat_type()], + ), + (TagName::Global("OutOfBounds".into()), vec![]), + ], + Box::new(SolvedType::Wildcard), + ); + + add_top_level_function_type!( + Symbol::STR_FROM_UTF8_RANGE, + vec![ + list_type(u8_type()), + SolvedType::Record { + fields: vec![ + ("start".into(), RecordField::Required(nat_type())), + ("count".into(), RecordField::Required(nat_type())), + ], + ext: Box::new(SolvedType::EmptyRecord), + } + ], + Box::new(result_type(str_type(), bad_utf8)), + ); + } // toUtf8 : Str -> List U8 add_top_level_function_type!( diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 58a48b1a22..5bade7aeee 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -6,7 +6,7 @@ use roc_module::symbol::Symbol; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, Problem, RecordField, Type}; +use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type}; #[derive(Clone, Debug, PartialEq)] pub struct Annotation { @@ -227,14 +227,27 @@ fn can_annotation_help( } // make sure hidden variables are freshly instantiated - for var in alias.lambda_set_variables.iter() { - substitutions.insert(var.into_inner(), Type::Variable(var_store.fresh())); + let mut lambda_set_variables = + Vec::with_capacity(alias.lambda_set_variables.len()); + for typ in alias.lambda_set_variables.iter() { + if let Type::Variable(var) = typ.0 { + let fresh = var_store.fresh(); + substitutions.insert(var, Type::Variable(fresh)); + lambda_set_variables.push(LambdaSet(Type::Variable(fresh))); + } else { + unreachable!("at this point there should be only vars in there"); + } } // instantiate variables actual.substitute(&substitutions); - Type::Alias(symbol, vars, Box::new(actual)) + Type::Alias { + symbol, + type_arguments: vars, + lambda_set_variables, + actual: Box::new(actual), + } } None => { let mut args = Vec::new(); @@ -373,12 +386,18 @@ fn can_annotation_help( introduced_variables.insert_host_exposed_alias(symbol, actual_var); Type::HostExposedAlias { name: symbol, - arguments: vars, + type_arguments: vars, + lambda_set_variables: alias.lambda_set_variables.clone(), actual: Box::new(alias.typ.clone()), actual_var, } } else { - Type::Alias(symbol, vars, Box::new(alias.typ.clone())) + Type::Alias { + symbol, + type_arguments: vars, + lambda_set_variables: alias.lambda_set_variables.clone(), + actual: Box::new(alias.typ.clone()), + } } } _ => { diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index a3cd0d36a1..d68f8dd85f 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -63,6 +63,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_COUNT_GRAPHEMES => str_count_graphemes, STR_FROM_INT => str_from_int, STR_FROM_UTF8 => str_from_utf8, + STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_TO_UTF8 => str_to_utf8, STR_FROM_FLOAT=> str_from_float, LIST_LEN => list_len, @@ -160,6 +161,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ATAN => num_atan, NUM_ACOS => num_acos, NUM_ASIN => num_asin, + NUM_BYTES_TO_U16 => num_bytes_to_u16, + NUM_BYTES_TO_U32 => num_bytes_to_u32, NUM_MAX_INT => num_max_int, NUM_MIN_INT => num_min_int, NUM_BITWISE_AND => num_bitwise_and, @@ -1087,6 +1090,16 @@ fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] +fn num_bytes_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bytes_to(symbol, var_store, 1, LowLevel::NumBytesToU16) +} + +/// Num.bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] +fn num_bytes_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bytes_to(symbol, var_store, 3, LowLevel::NumBytesToU32) +} + /// Num.bitwiseAnd : Int a, Int a -> Int a fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumBitwiseAnd) @@ -1352,7 +1365,7 @@ fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* +/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]* fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { let bytes_var = var_store.fresh(); let bool_var = var_store.fresh(); @@ -1455,6 +1468,179 @@ fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var, ) } +/// Str.fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]* +fn str_from_utf8_range(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bytes_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let arg_record_var = var_store.fresh(); + let ll_record_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + // let arg_3 = RunLowLevel FromUtf8Range arg_1 arg_2 + // + // arg_3 : + // { a : Bool -- isOk + // , b : String -- result_str + // , c : Nat -- problem_byte_index + // , d : I8 -- problem_code + // } + // + // if arg_3.a then + // Ok arg_3.str + // else + // Err (BadUtf8 { byteIndex: arg_3.byteIndex, problem : arg_3.problem }) + + let def = crate::def::Def { + loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), + loc_expr: no_region(RunLowLevel { + op: LowLevel::StrFromUtf8Range, + args: vec![ + (bytes_var, Var(Symbol::ARG_1)), + (arg_record_var, Var(Symbol::ARG_2)), + ], + ret_var: ll_record_var, + }), + expr_var: ll_record_var, + pattern_vars: SendMap::default(), + annotation: None, + }; + + let cont = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // arg_2.c -> Bool + Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "c_isOk".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ), + // all is good + no_region(tag( + "Ok", + // arg_2.a -> Str + vec![Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "b_str".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }], + var_store, + )), + )], + final_else: Box::new( + // bad!! + no_region(tag( + "Err", + vec![tag( + "BadUtf8", + vec![ + Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "d_problem".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "a_byteIndex".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ], + var_store, + )], + var_store, + )), + ), + }; + + let roc_result = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + + // Only do the business with the let if we're in bounds! + + let bounds_var = var_store.fresh(); + let bounds_bool = var_store.fresh(); + let add_var = var_store.fresh(); + + let body = If { + cond_var: bounds_bool, + branch_var: ret_var, + branches: vec![( + no_region(RunLowLevel { + op: LowLevel::NumLte, + args: vec![ + ( + bounds_var, + RunLowLevel { + op: LowLevel::NumAdd, + args: vec![ + ( + add_var, + Access { + record_var: arg_record_var, + ext_var: var_store.fresh(), + field: "start".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), + }, + ), + ( + add_var, + Access { + record_var: arg_record_var, + ext_var: var_store.fresh(), + field: "count".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), + }, + ), + ], + ret_var: add_var, + }, + ), + ( + bounds_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(bytes_var, Var(Symbol::ARG_1))], + ret_var: bounds_var, + }, + ), + ], + ret_var: bounds_bool, + }), + no_region(roc_result), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(bytes_var, Symbol::ARG_1), (arg_record_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} /// Str.toUtf8 : Str -> List U8 fn str_to_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { @@ -3185,6 +3371,97 @@ fn defn( } } +#[inline(always)] +fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level: LowLevel) -> Def { + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + + let ret_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let add_var = var_store.fresh(); + let cast_var = var_store.fresh(); + + // Perform a bounds check. If it passes, run LowLevel::low_level + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // index + offset < List.len list + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + ( + len_var, + RunLowLevel { + op: LowLevel::NumAdd, + args: vec![ + (add_var, Var(Symbol::ARG_2)), + ( + add_var, + RunLowLevel { + ret_var: cast_var, + args: vec![(cast_var, Num(var_store.fresh(), offset))], + op: LowLevel::NumIntCast, + }, + ), + ], + ret_var: add_var, + }, + ), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // then-branch + no_region( + // Ok + tag( + "Ok", + vec![RunLowLevel { + op: low_level, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (len_var, Var(Symbol::ARG_2)), + ], + ret_var: elem_var, + }], + var_store, + ), + ), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + #[inline(always)] fn defn_help( fn_name: Symbol, diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index addc786a08..a300d48ea8 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -60,6 +60,22 @@ impl Constraint { true } + + pub fn contains_save_the_environment(&self) -> bool { + match self { + Constraint::Eq(_, _, _, _) => false, + Constraint::Store(_, _, _, _) => false, + Constraint::Lookup(_, _, _) => false, + Constraint::Pattern(_, _, _, _) => false, + Constraint::True => false, + Constraint::SaveTheEnvironment => true, + Constraint::Let(boxed) => { + boxed.ret_constraint.contains_save_the_environment() + || boxed.defs_constraint.contains_save_the_environment() + } + Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), + } + } } fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) { @@ -71,12 +87,12 @@ fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDe // lambda set variables are always flex for var in &detail.lambda_set_variables { - if declared.rigid_vars.contains(&var.into_inner()) { + if declared.rigid_vars.contains(var) { panic!("lambda set variable {:?} is declared as rigid", var); } - if !declared.flex_vars.contains(&var.into_inner()) { - accum.lambda_set_variables.insert(*var); + if !declared.flex_vars.contains(var) { + accum.lambda_set_variables.push(*var); } } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1c8f99f3ff..35f967e83a 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1632,7 +1632,7 @@ fn make_tag_union_recursive<'a>( typ.substitute_alias(symbol, &Type::Variable(rec_var)); } Type::RecursiveTagUnion(_, _, _) => {} - Type::Alias(_, _, actual) => make_tag_union_recursive( + Type::Alias { actual, .. } => make_tag_union_recursive( env, symbol, region, diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 07b87310c3..bde1032119 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -47,7 +47,7 @@ impl Scope { let alias = Alias { region, typ, - lambda_set_variables: MutSet::default(), + lambda_set_variables: Vec::new(), recursion_variables: MutSet::default(), type_variables: variables, }; @@ -198,6 +198,11 @@ impl Scope { true }); + let lambda_set_variables: Vec<_> = lambda_set_variables + .into_iter() + .map(|v| roc_types::types::LambdaSet(Type::Variable(v))) + .collect(); + let alias = Alias { region, type_variables: vars, diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 4704c67539..c47a1e5796 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -2,7 +2,7 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; use roc_collections::all::SendMap; -use roc_module::ident::TagName; +use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; @@ -89,9 +89,23 @@ pub fn str_type() -> Type { builtin_type(Symbol::STR_STR, Vec::new()) } +#[inline(always)] +fn builtin_alias( + symbol: Symbol, + type_arguments: Vec<(Lowercase, Type)>, + actual: Box, +) -> Type { + Type::Alias { + symbol, + type_arguments, + actual, + lambda_set_variables: vec![], + } +} + #[inline(always)] pub fn num_float(range: Type) -> Type { - Type::Alias( + builtin_alias( Symbol::NUM_FLOAT, vec![("range".into(), range.clone())], Box::new(num_num(num_floatingpoint(range))), @@ -108,7 +122,7 @@ pub fn num_floatingpoint(range: Type) -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias( + builtin_alias( Symbol::NUM_FLOATINGPOINT, vec![("range".into(), range)], Box::new(alias_content), @@ -122,12 +136,12 @@ pub fn num_binary64() -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) + builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) } #[inline(always)] pub fn num_int(range: Type) -> Type { - Type::Alias( + builtin_alias( Symbol::NUM_INT, vec![("range".into(), range.clone())], Box::new(num_num(num_integer(range))), @@ -141,7 +155,7 @@ pub fn num_signed64() -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) + builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) } #[inline(always)] @@ -154,7 +168,7 @@ pub fn num_integer(range: Type) -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias( + builtin_alias( Symbol::NUM_INTEGER, vec![("range".into(), range)], Box::new(alias_content), @@ -168,7 +182,7 @@ pub fn num_num(typ: Type) -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias( + builtin_alias( Symbol::NUM_NUM, vec![("range".into(), typ)], Box::new(alias_content), diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 09020430e5..24bf892102 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1121,7 +1121,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { } // this assert make the "root" of the constraint wasn't dropped - debug_assert!(format!("{:?}", &constraint).contains("SaveTheEnvironment")); + debug_assert!(constraint.contains_save_the_environment()); constraint } diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index a6462eb570..c8c1a24992 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -57,7 +57,7 @@ pub fn constrain_imported_values( // an imported symbol can be either an alias or a value match import.solved_type { - SolvedType::Alias(symbol, _, _) if symbol == loc_symbol.value => { + SolvedType::Alias(symbol, _, _, _) if symbol == loc_symbol.value => { // do nothing, in the future the alias definitions should not be in the list of imported values } _ => { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index f6ee048e7b..a0d3c38187 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -100,19 +100,6 @@ where self.free_symbols(stmt); Ok(()) } - Stmt::Invoke { - symbol, - layout, - call, - pass, - fail: _, - exception_id: _, - } => { - // for now, treat invoke as a normal call - self.build_expr(symbol, &Expr::Call(call.clone()), layout)?; - self.free_symbols(stmt); - self.build_stmt(pass) - } Stmt::Switch { cond_symbol, cond_layout, @@ -201,6 +188,12 @@ where Symbol::NUM_SUB => { self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout) } + Symbol::NUM_ROUND => self.build_run_low_level( + sym, + &LowLevel::NumRound, + arguments, + layout, + ), Symbol::BOOL_EQ => { self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout) } @@ -300,7 +293,13 @@ where // Should we panic? x => Err(format!("wrong layout, {:?}, for LowLevel::Eq", x)), }, - + LowLevel::NumRound => self.build_fn_call( + sym, + bitcode::NUM_ROUND.to_string(), + args, + &[Layout::Builtin(Builtin::Float64)], + layout, + ), x => Err(format!("low level, {:?}. is not yet implemented", x)), } } @@ -487,20 +486,6 @@ where self.scan_ast(following); } - Stmt::Invoke { - symbol, - layout: _, - call, - pass, - fail: _, - exception_id: _, - } => { - // for now, treat invoke as a normal call - self.set_last_seen(*symbol, stmt); - self.scan_ast_call(call, stmt); - self.scan_ast(pass); - } - Stmt::Switch { cond_symbol, branches, @@ -516,7 +501,6 @@ where Stmt::Ret(sym) => { self.set_last_seen(*sym, stmt); } - Stmt::Resume(_exception_id) => {} Stmt::Refcounting(modify, following) => { let sym = modify.get_symbol(); diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index e3b09d7b52..52ae6b4b08 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -179,6 +179,12 @@ fn build_object<'a, B: Backend<'a>>( "roc_dealloc".into(), "free".into(), )?; + generate_wrapper( + &mut backend, + &mut output, + "roc_panic".into(), + "roc_builtins.utils.test_panic".into(), + )?; } // Setup layout_ids for procedure calls. diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index 010bf078d0..d1c0ae0d75 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -287,578 +287,577 @@ mod dev_num { assert_evals_to!("Num.abs 5.8", 5.8, f64); } - /* #[test] - fn f64_sqrt() { - // FIXME this works with normal types, but fails when checking uniqueness types - assert_evals_to!( - indoc!( - r#" - when Num.sqrt 100 is - Ok val -> val - Err _ -> -1 - "# - ), - 10.0, - f64 - ); - } - - #[test] - fn f64_round_old() { + fn f64_round() { assert_evals_to!("Num.round 3.6", 4, i64); - } - - - - #[test] - fn gen_float_eq() { - assert_evals_to!( - indoc!( - r#" - 1.0 == 1.0 - "# - ), - true, - bool - ); - } - - #[test] - fn gen_div_f64() { - // FIXME this works with normal types, but fails when checking uniqueness types - assert_evals_to!( - indoc!( - r#" - when 48 / 2 is - Ok val -> val - Err _ -> -1 - "# - ), - 24.0, - f64 - ); - } - - #[test] - fn gen_int_neq() { - assert_evals_to!( - indoc!( - r#" - 4 != 5 - "# - ), - true, - bool - ); - } - - #[test] - fn gen_wrap_int_neq() { - assert_evals_to!( - indoc!( - r#" - wrappedNotEq : a, a -> Bool - wrappedNotEq = \num1, num2 -> - num1 != num2 - - wrappedNotEq 2 3 - "# - ), - true, - bool - ); - } - - #[test] - fn gen_sub_f64() { - assert_evals_to!( - indoc!( - r#" - 1.5 - 2.4 - 3 - "# - ), - -3.9, - f64 - ); - } - - #[test] - fn gen_div_i64() { - assert_evals_to!( - indoc!( - r#" - when 1000 // 10 is - Ok val -> val - Err _ -> -1 - "# - ), - 100, - i64 - ); - } - - #[test] - fn gen_div_by_zero_i64() { - assert_evals_to!( - indoc!( - r#" - when 1000 // 0 is - Err DivByZero -> 99 - _ -> -24 - "# - ), - 99, - i64 - ); - } - - #[test] - fn gen_rem_i64() { - assert_evals_to!( - indoc!( - r#" - when Num.rem 8 3 is - Ok val -> val - Err _ -> -1 - "# - ), - 2, - i64 - ); - } - - #[test] - fn gen_rem_div_by_zero_i64() { - assert_evals_to!( - indoc!( - r#" - when Num.rem 8 0 is - Err DivByZero -> 4 - Ok _ -> -23 - "# - ), - 4, - i64 - ); - } - - #[test] - fn gen_is_zero_i64() { - assert_evals_to!("Num.isZero 0", true, bool); - assert_evals_to!("Num.isZero 1", false, bool); - } - - #[test] - fn gen_is_positive_i64() { - assert_evals_to!("Num.isPositive 0", false, bool); - assert_evals_to!("Num.isPositive 1", true, bool); - assert_evals_to!("Num.isPositive -5", false, bool); - } - - #[test] - fn gen_is_negative_i64() { - assert_evals_to!("Num.isNegative 0", false, bool); - assert_evals_to!("Num.isNegative 3", false, bool); - assert_evals_to!("Num.isNegative -2", true, bool); - } - - #[test] - fn gen_is_positive_f64() { - assert_evals_to!("Num.isPositive 0.0", false, bool); - assert_evals_to!("Num.isPositive 4.7", true, bool); - assert_evals_to!("Num.isPositive -8.5", false, bool); - } - - #[test] - fn gen_is_negative_f64() { - assert_evals_to!("Num.isNegative 0.0", false, bool); - assert_evals_to!("Num.isNegative 9.9", false, bool); - assert_evals_to!("Num.isNegative -4.4", true, bool); - } - - #[test] - fn gen_is_zero_f64() { - assert_evals_to!("Num.isZero 0", true, bool); - assert_evals_to!("Num.isZero 0_0", true, bool); - assert_evals_to!("Num.isZero 0.0", true, bool); - assert_evals_to!("Num.isZero 1", false, bool); - } - - #[test] - fn gen_is_odd() { - assert_evals_to!("Num.isOdd 4", false, bool); - assert_evals_to!("Num.isOdd 5", true, bool); - } - - #[test] - fn gen_is_even() { - assert_evals_to!("Num.isEven 6", true, bool); - assert_evals_to!("Num.isEven 7", false, bool); - } - - #[test] - fn sin() { - assert_evals_to!("Num.sin 0", 0.0, f64); - assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); - } - - #[test] - fn cos() { - assert_evals_to!("Num.cos 0", 1.0, f64); - assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); - } - - #[test] - fn tan() { - assert_evals_to!("Num.tan 0", 0.0, f64); - assert_evals_to!("Num.tan 1", 1.557407724654902, f64); - } - - #[test] - fn lt_i64() { - assert_evals_to!("1 < 2", true, bool); - assert_evals_to!("1 < 1", false, bool); - assert_evals_to!("2 < 1", false, bool); - assert_evals_to!("0 < 0", false, bool); - } - - #[test] - fn lte_i64() { - assert_evals_to!("1 <= 1", true, bool); - assert_evals_to!("2 <= 1", false, bool); - assert_evals_to!("1 <= 2", true, bool); - assert_evals_to!("0 <= 0", true, bool); - } - - #[test] - fn gt_i64() { - assert_evals_to!("2 > 1", true, bool); - assert_evals_to!("2 > 2", false, bool); - assert_evals_to!("1 > 1", false, bool); - assert_evals_to!("0 > 0", false, bool); - } - - #[test] - fn gte_i64() { - assert_evals_to!("1 >= 1", true, bool); - assert_evals_to!("1 >= 2", false, bool); - assert_evals_to!("2 >= 1", true, bool); - assert_evals_to!("0 >= 0", true, bool); - } - - #[test] - fn lt_f64() { - assert_evals_to!("1.1 < 1.2", true, bool); - assert_evals_to!("1.1 < 1.1", false, bool); - assert_evals_to!("1.2 < 1.1", false, bool); - assert_evals_to!("0.0 < 0.0", false, bool); - } - - #[test] - fn lte_f64() { - assert_evals_to!("1.1 <= 1.1", true, bool); - assert_evals_to!("1.2 <= 1.1", false, bool); - assert_evals_to!("1.1 <= 1.2", true, bool); - assert_evals_to!("0.0 <= 0.0", true, bool); - } - - #[test] - fn gt_f64() { - assert_evals_to!("2.2 > 1.1", true, bool); - assert_evals_to!("2.2 > 2.2", false, bool); - assert_evals_to!("1.1 > 2.2", false, bool); - assert_evals_to!("0.0 > 0.0", false, bool); - } - - #[test] - fn gte_f64() { - assert_evals_to!("1.1 >= 1.1", true, bool); - assert_evals_to!("1.1 >= 1.2", false, bool); - assert_evals_to!("1.2 >= 1.1", true, bool); - assert_evals_to!("0.0 >= 0.0", true, bool); - } - - #[test] - fn gen_order_of_arithmetic_ops() { - assert_evals_to!( - indoc!( - r#" - 1 + 3 * 7 - 2 - "# - ), - 20, - i64 - ); - } - - #[test] - fn gen_order_of_arithmetic_ops_complex_float() { - assert_evals_to!( - indoc!( - r#" - 3 - 48 * 2.0 - "# - ), - -93.0, - f64 - ); - } - - #[test] - fn if_guard_bind_variable_false() { - assert_evals_to!( - indoc!( - r#" - wrapper = \{} -> - when 10 is - x if x == 5 -> 0 - _ -> 42 - - wrapper {} - "# - ), - 42, - i64 - ); - } - - #[test] - fn if_guard_bind_variable_true() { - assert_evals_to!( - indoc!( - r#" - wrapper = \{} -> - when 10 is - x if x == 10 -> 42 - _ -> 0 - - wrapper {} - "# - ), - 42, - i64 - ); - } - - #[test] - fn tail_call_elimination() { - assert_evals_to!( - indoc!( - r#" - sum = \n, accum -> - when n is - 0 -> accum - _ -> sum (n - 1) (n + accum) - - sum 1_000_000 0 - "# - ), - 500000500000, - i64 - ); - } - - #[test] - fn int_negate() { - assert_evals_to!("Num.neg 123", -123, i64); - } - - #[test] - fn gen_wrap_int_neg() { - assert_evals_to!( - indoc!( - r#" - wrappedNeg = \num -> -num - - wrappedNeg 3 - "# - ), - -3, - i64 - ); - } - - - #[test] - fn int_to_float() { - assert_evals_to!("Num.toFloat 0x9", 9.0, f64); - } - - #[test] - fn num_to_float() { - assert_evals_to!("Num.toFloat 9", 9.0, f64); - } - - #[test] - fn float_to_float() { - assert_evals_to!("Num.toFloat 0.5", 0.5, f64); - } - - #[test] - fn int_compare() { - assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder); - assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder); - assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder); - } - - #[test] - fn float_compare() { - assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder); - assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder); - assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder); - } - - #[test] - fn pow() { - assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64); - } - - #[test] - fn ceiling() { - assert_evals_to!("Num.ceiling 1.1", 2, i64); - } - - #[test] - fn floor() { - assert_evals_to!("Num.floor 1.9", 1, i64); + assert_evals_to!("Num.round 3.4", 3, i64); + assert_evals_to!("Num.round 2.5", 3, i64); + assert_evals_to!("Num.round -2.3", -2, i64); + assert_evals_to!("Num.round -2.5", -3, i64); } // #[test] - // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] - // fn int_overflow() { + // fn f64_sqrt() { + // // FIXME this works with normal types, but fails when checking uniqueness types // assert_evals_to!( // indoc!( // r#" - // 9_223_372_036_854_775_807 + 1 + // when Num.sqrt 100 is + // Ok val -> val + // Err _ -> -1 // "# // ), - // 0, + // 10.0, + // f64 + // ); + // } + + // #[test] + // fn gen_float_eq() { + // assert_evals_to!( + // indoc!( + // r#" + // 1.0 == 1.0 + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn gen_div_f64() { + // // FIXME this works with normal types, but fails when checking uniqueness types + // assert_evals_to!( + // indoc!( + // r#" + // when 48 / 2 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 24.0, + // f64 + // ); + // } + + // #[test] + // fn gen_int_neq() { + // assert_evals_to!( + // indoc!( + // r#" + // 4 != 5 + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn gen_wrap_int_neq() { + // assert_evals_to!( + // indoc!( + // r#" + // wrappedNotEq : a, a -> Bool + // wrappedNotEq = \num1, num2 -> + // num1 != num2 + + // wrappedNotEq 2 3 + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn gen_sub_f64() { + // assert_evals_to!( + // indoc!( + // r#" + // 1.5 - 2.4 - 3 + // "# + // ), + // -3.9, + // f64 + // ); + // } + + // #[test] + // fn gen_div_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when 1000 // 10 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 100, // i64 // ); // } - #[test] - fn int_add_checked() { - assert_evals_to!( - indoc!( - r#" - when Num.addChecked 1 2 is - Ok v -> v - _ -> -1 - "# - ), - 3, - i64 - ); + // #[test] + // fn gen_div_by_zero_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when 1000 // 0 is + // Err DivByZero -> 99 + // _ -> -24 + // "# + // ), + // 99, + // i64 + // ); + // } - assert_evals_to!( - indoc!( - r#" - when Num.addChecked 9_223_372_036_854_775_807 1 is - Err Overflow -> -1 - Ok v -> v - "# - ), - -1, - i64 - ); - } + // #[test] + // fn gen_rem_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.rem 8 3 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 2, + // i64 + // ); + // } - #[test] - fn int_add_wrap() { - assert_evals_to!( - indoc!( - r#" - Num.addWrap 9_223_372_036_854_775_807 1 - "# - ), - std::i64::MIN, - i64 - ); - } + // #[test] + // fn gen_rem_div_by_zero_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.rem 8 0 is + // Err DivByZero -> 4 + // Ok _ -> -23 + // "# + // ), + // 4, + // i64 + // ); + // } - #[test] - fn float_add_checked_pass() { - assert_evals_to!( - indoc!( - r#" - when Num.addChecked 1.0 0.0 is - Ok v -> v - Err Overflow -> -1.0 - "# - ), - 1.0, - f64 - ); - } + // #[test] + // fn gen_is_zero_i64() { + // assert_evals_to!("Num.isZero 0", true, bool); + // assert_evals_to!("Num.isZero 1", false, bool); + // } - #[test] - fn float_add_checked_fail() { - assert_evals_to!( - indoc!( - r#" - when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is - Err Overflow -> -1 - Ok v -> v - "# - ), - -1.0, - f64 - ); - } + // #[test] + // fn gen_is_positive_i64() { + // assert_evals_to!("Num.isPositive 0", false, bool); + // assert_evals_to!("Num.isPositive 1", true, bool); + // assert_evals_to!("Num.isPositive -5", false, bool); + // } - // #[test] - // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)] - // fn float_overflow() { - // assert_evals_to!( - // indoc!( - // r#" - // 1.7976931348623157e308 + 1.7976931348623157e308 - // "# - // ), - // 0.0, - // f64 - // ); - // } + // #[test] + // fn gen_is_negative_i64() { + // assert_evals_to!("Num.isNegative 0", false, bool); + // assert_evals_to!("Num.isNegative 3", false, bool); + // assert_evals_to!("Num.isNegative -2", true, bool); + // } - #[test] - fn max_i128() { - assert_evals_to!( - indoc!( - r#" - Num.maxI128 - "# - ), - i128::MAX, - i128 - ); - } + // #[test] + // fn gen_is_positive_f64() { + // assert_evals_to!("Num.isPositive 0.0", false, bool); + // assert_evals_to!("Num.isPositive 4.7", true, bool); + // assert_evals_to!("Num.isPositive -8.5", false, bool); + // } - #[test] - fn num_max_int() { - assert_evals_to!( - indoc!( - r#" - Num.maxInt - "# - ), - i64::MAX, - i64 - ); - } + // #[test] + // fn gen_is_negative_f64() { + // assert_evals_to!("Num.isNegative 0.0", false, bool); + // assert_evals_to!("Num.isNegative 9.9", false, bool); + // assert_evals_to!("Num.isNegative -4.4", true, bool); + // } - #[test] - fn num_min_int() { - assert_evals_to!( - indoc!( - r#" - Num.minInt - "# - ), - i64::MIN, - i64 - ); - } - */ + // #[test] + // fn gen_is_zero_f64() { + // assert_evals_to!("Num.isZero 0", true, bool); + // assert_evals_to!("Num.isZero 0_0", true, bool); + // assert_evals_to!("Num.isZero 0.0", true, bool); + // assert_evals_to!("Num.isZero 1", false, bool); + // } + + // #[test] + // fn gen_is_odd() { + // assert_evals_to!("Num.isOdd 4", false, bool); + // assert_evals_to!("Num.isOdd 5", true, bool); + // } + + // #[test] + // fn gen_is_even() { + // assert_evals_to!("Num.isEven 6", true, bool); + // assert_evals_to!("Num.isEven 7", false, bool); + // } + + // #[test] + // fn sin() { + // assert_evals_to!("Num.sin 0", 0.0, f64); + // assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); + // } + + // #[test] + // fn cos() { + // assert_evals_to!("Num.cos 0", 1.0, f64); + // assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); + // } + + // #[test] + // fn tan() { + // assert_evals_to!("Num.tan 0", 0.0, f64); + // assert_evals_to!("Num.tan 1", 1.557407724654902, f64); + // } + + // #[test] + // fn lt_i64() { + // assert_evals_to!("1 < 2", true, bool); + // assert_evals_to!("1 < 1", false, bool); + // assert_evals_to!("2 < 1", false, bool); + // assert_evals_to!("0 < 0", false, bool); + // } + + // #[test] + // fn lte_i64() { + // assert_evals_to!("1 <= 1", true, bool); + // assert_evals_to!("2 <= 1", false, bool); + // assert_evals_to!("1 <= 2", true, bool); + // assert_evals_to!("0 <= 0", true, bool); + // } + + // #[test] + // fn gt_i64() { + // assert_evals_to!("2 > 1", true, bool); + // assert_evals_to!("2 > 2", false, bool); + // assert_evals_to!("1 > 1", false, bool); + // assert_evals_to!("0 > 0", false, bool); + // } + + // #[test] + // fn gte_i64() { + // assert_evals_to!("1 >= 1", true, bool); + // assert_evals_to!("1 >= 2", false, bool); + // assert_evals_to!("2 >= 1", true, bool); + // assert_evals_to!("0 >= 0", true, bool); + // } + + // #[test] + // fn lt_f64() { + // assert_evals_to!("1.1 < 1.2", true, bool); + // assert_evals_to!("1.1 < 1.1", false, bool); + // assert_evals_to!("1.2 < 1.1", false, bool); + // assert_evals_to!("0.0 < 0.0", false, bool); + // } + + // #[test] + // fn lte_f64() { + // assert_evals_to!("1.1 <= 1.1", true, bool); + // assert_evals_to!("1.2 <= 1.1", false, bool); + // assert_evals_to!("1.1 <= 1.2", true, bool); + // assert_evals_to!("0.0 <= 0.0", true, bool); + // } + + // #[test] + // fn gt_f64() { + // assert_evals_to!("2.2 > 1.1", true, bool); + // assert_evals_to!("2.2 > 2.2", false, bool); + // assert_evals_to!("1.1 > 2.2", false, bool); + // assert_evals_to!("0.0 > 0.0", false, bool); + // } + + // #[test] + // fn gte_f64() { + // assert_evals_to!("1.1 >= 1.1", true, bool); + // assert_evals_to!("1.1 >= 1.2", false, bool); + // assert_evals_to!("1.2 >= 1.1", true, bool); + // assert_evals_to!("0.0 >= 0.0", true, bool); + // } + + // #[test] + // fn gen_order_of_arithmetic_ops() { + // assert_evals_to!( + // indoc!( + // r#" + // 1 + 3 * 7 - 2 + // "# + // ), + // 20, + // i64 + // ); + // } + + // #[test] + // fn gen_order_of_arithmetic_ops_complex_float() { + // assert_evals_to!( + // indoc!( + // r#" + // 3 - 48 * 2.0 + // "# + // ), + // -93.0, + // f64 + // ); + // } + + // #[test] + // fn if_guard_bind_variable_false() { + // assert_evals_to!( + // indoc!( + // r#" + // wrapper = \{} -> + // when 10 is + // x if x == 5 -> 0 + // _ -> 42 + + // wrapper {} + // "# + // ), + // 42, + // i64 + // ); + // } + + // #[test] + // fn if_guard_bind_variable_true() { + // assert_evals_to!( + // indoc!( + // r#" + // wrapper = \{} -> + // when 10 is + // x if x == 10 -> 42 + // _ -> 0 + + // wrapper {} + // "# + // ), + // 42, + // i64 + // ); + // } + + // #[test] + // fn tail_call_elimination() { + // assert_evals_to!( + // indoc!( + // r#" + // sum = \n, accum -> + // when n is + // 0 -> accum + // _ -> sum (n - 1) (n + accum) + + // sum 1_000_000 0 + // "# + // ), + // 500000500000, + // i64 + // ); + // } + + // #[test] + // fn int_negate() { + // assert_evals_to!("Num.neg 123", -123, i64); + // } + + // #[test] + // fn gen_wrap_int_neg() { + // assert_evals_to!( + // indoc!( + // r#" + // wrappedNeg = \num -> -num + + // wrappedNeg 3 + // "# + // ), + // -3, + // i64 + // ); + // } + + // #[test] + // fn int_to_float() { + // assert_evals_to!("Num.toFloat 0x9", 9.0, f64); + // } + + // #[test] + // fn num_to_float() { + // assert_evals_to!("Num.toFloat 9", 9.0, f64); + // } + + // #[test] + // fn float_to_float() { + // assert_evals_to!("Num.toFloat 0.5", 0.5, f64); + // } + + // #[test] + // fn int_compare() { + // assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder); + // assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder); + // assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder); + // } + + // #[test] + // fn float_compare() { + // assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder); + // assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder); + // assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder); + // } + + // #[test] + // fn pow() { + // assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64); + // } + + // #[test] + // fn ceiling() { + // assert_evals_to!("Num.ceiling 1.1", 2, i64); + // } + + // #[test] + // fn floor() { + // assert_evals_to!("Num.floor 1.9", 1, i64); + // } + + // // #[test] + // // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] + // // fn int_overflow() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // 9_223_372_036_854_775_807 + 1 + // // "# + // // ), + // // 0, + // // i64 + // // ); + // // } + + // #[test] + // fn int_add_checked() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 1 2 is + // Ok v -> v + // _ -> -1 + // "# + // ), + // 3, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 9_223_372_036_854_775_807 1 is + // Err Overflow -> -1 + // Ok v -> v + // "# + // ), + // -1, + // i64 + // ); + // } + + // #[test] + // fn int_add_wrap() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.addWrap 9_223_372_036_854_775_807 1 + // "# + // ), + // std::i64::MIN, + // i64 + // ); + // } + + // #[test] + // fn float_add_checked_pass() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 1.0 0.0 is + // Ok v -> v + // Err Overflow -> -1.0 + // "# + // ), + // 1.0, + // f64 + // ); + // } + + // #[test] + // fn float_add_checked_fail() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is + // Err Overflow -> -1 + // Ok v -> v + // "# + // ), + // -1.0, + // f64 + // ); + // } + + // // #[test] + // // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)] + // // fn float_overflow() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // 1.7976931348623157e308 + 1.7976931348623157e308 + // // "# + // // ), + // // 0.0, + // // f64 + // // ); + // // } + + // #[test] + // fn max_i128() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.maxI128 + // "# + // ), + // i128::MAX, + // i128 + // ); + // } + + // #[test] + // fn num_max_int() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.maxInt + // "# + // ), + // i64::MAX, + // i64 + // ); + // } + + // #[test] + // fn num_min_int() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.minInt + // "# + // ), + // i64::MIN, + // i64 + // ); + // } } diff --git a/compiler/gen_dev/tests/helpers/eval.rs b/compiler/gen_dev/tests/helpers/eval.rs index 48893f63c6..675f981e72 100644 --- a/compiler/gen_dev/tests/helpers/eval.rs +++ b/compiler/gen_dev/tests/helpers/eval.rs @@ -73,19 +73,22 @@ pub fn helper<'a>( procedures.insert(key, proc); } - /* - println!("=========== Procedures =========="); - println!("{:?}", procedures); - println!("=================================\n"); + // You can comment and uncomment this block out to get more useful information + // while you're working on the dev backend! + { + // println!("=========== Procedures =========="); + // println!("{:?}", procedures); + // println!("=================================\n"); - println!("=========== Interns =========="); - println!("{:?}", interns); - println!("=================================\n"); + // println!("=========== Interns =========="); + // println!("{:?}", interns); + // println!("=================================\n"); + + // println!("=========== Exposed =========="); + // println!("{:?}", exposed_to_host); + // println!("=================================\n"); + } - println!("=========== Exposed =========="); - println!("{:?}", exposed_to_host); - println!("=================================\n"); - */ debug_assert_eq!(exposed_to_host.len(), 1); let main_fn_symbol = loaded.entry_point.symbol; let main_fn_layout = loaded.entry_point.layout; diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index 2905b0da86..f873af0518 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -271,33 +271,11 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( } match closure_data_layout { - Layout::Closure(_, lambda_set, _) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() { - // do nothing - } else { - let closure_type = - basic_type_from_layout(env, &lambda_set.runtime_representation()) - .ptr_type(AddressSpace::Generic); - - let closure_cast = env - .builder - .build_bitcast(closure_ptr, closure_type, "load_opaque") - .into_pointer_value(); - - let closure_data = env.builder.build_load(closure_cast, "load_opaque"); - - arguments_cast.push(closure_data); - } + Layout::Struct(&[]) => { + // nothing to add } - Layout::Struct([Layout::Closure(_, lambda_set, _)]) => { - // a case required for Set.walk; may be able to remove when we can define builtins in - // terms of other builtins in the right way (using their function symbols instead of - // hacking with lowlevel ops). - let closure_type = basic_type_from_layout( - env, - &Layout::Struct(&[lambda_set.runtime_representation()]), - ) - .ptr_type(AddressSpace::Generic); + other => { + let closure_type = basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic); let closure_cast = env .builder @@ -308,13 +286,6 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( arguments_cast.push(closure_data); } - Layout::Struct([]) => { - // do nothing, should try to remove this case later - } - Layout::Struct(_) => { - // do nothing, should try to remove this case later - } - other => unreachable!("layout is not valid for a closure: {:?}", other), } let call = { @@ -625,26 +596,23 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( let default = [value1, value2]; let arguments_cast = match closure_data_layout { - Layout::Closure(_, lambda_set, _) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() { - &default - } else { - let closure_type = - basic_type_from_layout(env, &lambda_set.runtime_representation()) - .ptr_type(AddressSpace::Generic); - - let closure_cast = env - .builder - .build_bitcast(closure_ptr, closure_type, "load_opaque") - .into_pointer_value(); - - let closure_data = env.builder.build_load(closure_cast, "load_opaque"); - - env.arena.alloc([value1, value2, closure_data]) as &[_] - } + Layout::Struct(&[]) => { + // nothing to add + &default + } + other => { + let closure_type = + basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic); + + let closure_cast = env + .builder + .build_bitcast(closure_ptr, closure_type, "load_opaque") + .into_pointer_value(); + + let closure_data = env.builder.build_load(closure_cast, "load_opaque"); + + env.arena.alloc([value1, value2, closure_data]) as &[_] } - Layout::Struct([]) => &default, - other => unreachable!("layout is not valid for a closure: {:?}", other), }; let call = env.builder.build_call( diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 283e947b98..2142b2e92e 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -14,8 +14,8 @@ use crate::llvm::build_list::{ }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, - str_from_utf8, str_join_with, str_number_of_bytes, str_split, str_starts_with, - str_starts_with_code_point, str_to_utf8, + str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_split, + str_starts_with, str_starts_with_code_point, str_to_utf8, }; use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::convert::{ @@ -50,9 +50,7 @@ use roc_builtins::bitcode; use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; -use roc_mono::ir::{ - BranchInfo, CallType, EntryPoint, ExceptionId, JoinPointId, ModifyRc, OptLevel, ProcLayout, -}; +use roc_mono::ir::{BranchInfo, CallType, EntryPoint, JoinPointId, ModifyRc, OptLevel, ProcLayout}; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, UnionLayout}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification @@ -68,7 +66,7 @@ macro_rules! debug_info_init { ($env:expr, $function_value:expr) => {{ use inkwell::debug_info::AsDIScope; - let func_scope = $function_value.get_subprogram().unwrap(); + let func_scope = $function_value.get_subprogram().expect("subprogram"); let lexical_block = $env.dibuilder.create_lexical_block( /* scope */ func_scope.as_debug_info_scope(), /* file */ $env.compile_unit.get_file(), @@ -157,10 +155,26 @@ pub struct Env<'a, 'ctx, 'env> { pub module: &'ctx Module<'ctx>, pub interns: Interns, pub ptr_bytes: u32, - pub leak: bool, + pub is_gen_test: bool, pub exposed_to_host: MutSet, } +#[repr(u32)] +pub enum PanicTagId { + NullTerminatedString = 0, +} + +impl std::convert::TryFrom for PanicTagId { + type Error = (); + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(PanicTagId::NullTerminatedString), + _ => Err(()), + } + } +} + impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { pub fn ptr_int(&self) -> IntType<'ctx> { ptr_int(self.context, self.ptr_bytes) @@ -286,6 +300,20 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { ) } + pub fn call_panic(&self, message: PointerValue<'ctx>, tag_id: PanicTagId) { + let function = self.module.get_function("roc_panic").unwrap(); + let tag_id = self + .context + .i32_type() + .const_int(tag_id as u32 as u64, false); + + let call = self + .builder + .build_call(function, &[message.into(), tag_id.into()], "roc_panic"); + + call.set_call_convention(C_CALL_CONV); + } + pub fn new_debug_info(module: &Module<'ctx>) -> (DebugInfoBuilder<'ctx>, DICompileUnit<'ctx>) { module.create_debug_info_builder( true, @@ -365,14 +393,44 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { let f64_type = ctx.f64_type(); let i1_type = ctx.bool_type(); let i8_type = ctx.i8_type(); + let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); let i16_type = ctx.i16_type(); let i32_type = ctx.i32_type(); let i64_type = ctx.i64_type(); + let void_type = ctx.void_type(); if let Some(func) = module.get_function("__muloti4") { func.set_linkage(Linkage::WeakAny); } + add_intrinsic( + module, + LLVM_SETJMP, + i32_type.fn_type(&[i8_ptr_type.into()], false), + ); + + if true { + add_intrinsic( + module, + LLVM_LONGJMP, + void_type.fn_type(&[i8_ptr_type.into()], false), + ); + } else { + add_intrinsic( + module, + LLVM_LONGJMP, + void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false), + ); + } + + add_intrinsic( + module, + LLVM_FRAME_ADDRESS, + i8_ptr_type.fn_type(&[i32_type.into()], false), + ); + + add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false)); + add_intrinsic( module, LLVM_LOG_F64, @@ -482,6 +540,13 @@ static LLVM_POW_F64: &str = "llvm.pow.f64"; static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; +// static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress"; +static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8"; +static LLVM_STACK_SAVE: &str = "llvm.stacksave"; + +static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp"; +pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp"; + pub static LLVM_SADD_WITH_OVERFLOW_I8: &str = "llvm.sadd.with.overflow.i8"; pub static LLVM_SADD_WITH_OVERFLOW_I16: &str = "llvm.sadd.with.overflow.i16"; pub static LLVM_SADD_WITH_OVERFLOW_I32: &str = "llvm.sadd.with.overflow.i32"; @@ -865,17 +930,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>( } => { // we always initially invoke foreign symbols, but if there is nothing to clean up, // we emit a normal call - build_foreign_symbol( - env, - layout_ids, - func_spec_solutions, - scope, - parent, - foreign_symbol, - arguments, - ret_layout, - ForeignCallOrInvoke::Call, - ) + build_foreign_symbol(env, scope, foreign_symbol, arguments, ret_layout) } } } @@ -1082,14 +1137,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( ) .unwrap() } - (StructValue(argument), Layout::Closure(_, _, _)) => env - .builder - .build_extract_value( - argument, - *index as u32, - env.arena.alloc(format!("closure_field_access_{}_", index)), - ) - .unwrap(), ( PointerValue(argument), Layout::Union(UnionLayout::NonNullableUnwrapped(fields)), @@ -1363,7 +1410,6 @@ pub fn build_tag<'a, 'ctx, 'env>( debug_assert!(union_size > 1); let ctx = env.context; - let builder = env.builder; // Determine types let num_fields = arguments.len() + 1; @@ -1428,7 +1474,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); - let data = cast_tag_to_block_of_memory(builder, struct_val, internal_type); + let data = cast_tag_to_block_of_memory(env, struct_val, internal_type); let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); let wrapper_type = env .context @@ -2118,91 +2164,6 @@ fn list_literal<'a, 'ctx, 'env>( ) } -#[allow(clippy::too_many_arguments)] -fn invoke_roc_function<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, - func_spec_solutions: &FuncSpecSolutions, - scope: &mut Scope<'a, 'ctx>, - parent: FunctionValue<'ctx>, - symbol: Symbol, - layout: Layout<'a>, - function_value: FunctionValue<'ctx>, - arguments: &[Symbol], - closure_argument: Option>, - pass: &'a roc_mono::ir::Stmt<'a>, - fail: &'a roc_mono::ir::Stmt<'a>, - exception_id: ExceptionId, -) -> BasicValueEnum<'ctx> { - let context = env.context; - - let mut arg_vals: Vec = Vec::with_capacity_in(arguments.len(), env.arena); - - for arg in arguments.iter() { - arg_vals.push(load_symbol(scope, arg)); - } - arg_vals.extend(closure_argument); - - let pass_block = context.append_basic_block(parent, "invoke_pass"); - let fail_block = context.append_basic_block(parent, "invoke_fail"); - - let call_result = { - let call = env.builder.build_invoke( - function_value, - arg_vals.as_slice(), - pass_block, - fail_block, - "tmp", - ); - - call.set_call_convention(function_value.get_call_conventions()); - - call.try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) - }; - - { - env.builder.position_at_end(pass_block); - - scope.insert(symbol, (layout, call_result)); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, pass); - - scope.remove(&symbol); - } - - { - env.builder.position_at_end(fail_block); - - let landing_pad_type = { - let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); - let selector_value = context.i32_type().into(); - - context.struct_type(&[exception_ptr, selector_value], false) - }; - - let personality_function = get_gxx_personality_v0(env); - - let exception_object = env.builder.build_landing_pad( - landing_pad_type, - personality_function, - &[], - true, - "invoke_landing_pad", - ); - - scope.insert( - exception_id.into_inner(), - (Layout::Struct(&[]), exception_object), - ); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, fail); - } - - call_result -} - fn decrement_with_size_check<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -2237,7 +2198,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( parent: FunctionValue<'ctx>, stmt: &roc_mono::ir::Stmt<'a>, ) -> BasicValueEnum<'ctx> { - use roc_mono::ir::Expr; use roc_mono::ir::Stmt::*; match stmt { @@ -2298,95 +2258,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( value } - Invoke { - symbol, - call, - layout, - pass, - fail: roc_mono::ir::Stmt::Resume(_), - exception_id: _, - } => { - // when the fail case is just Rethrow, there is no cleanup work to do - // so we can just treat this invoke as a normal call - let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass); - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, &stmt) - } - - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => match call.call_type { - CallType::ByName { - name, - arg_layouts, - ref ret_layout, - specialization_id, - .. - } => { - let bytes = specialization_id.to_bytes(); - let callee_var = CalleeSpecVar(&bytes); - let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); - - let function_value = - function_value_by_func_spec(env, func_spec, name, arg_layouts, ret_layout); - - invoke_roc_function( - env, - layout_ids, - func_spec_solutions, - scope, - parent, - *symbol, - *layout, - function_value, - call.arguments, - None, - pass, - fail, - *exception_id, - ) - } - - CallType::Foreign { - ref foreign_symbol, - ref ret_layout, - } => build_foreign_symbol( - env, - layout_ids, - func_spec_solutions, - scope, - parent, - foreign_symbol, - call.arguments, - ret_layout, - ForeignCallOrInvoke::Invoke { - symbol: *symbol, - pass, - fail, - exception_id: *exception_id, - }, - ), - - CallType::LowLevel { .. } => { - unreachable!("lowlevel itself never throws exceptions") - } - - CallType::HigherOrderLowLevel { .. } => { - unreachable!("lowlevel itself never throws exceptions") - } - }, - - Resume(exception_id) => { - let exception_object = scope.get(&exception_id.into_inner()).unwrap().1; - env.builder.build_resume(exception_object); - - env.ptr_int().const_zero().into() - } - Switch { branches, default_branch, @@ -2656,17 +2527,12 @@ pub fn complex_bitcast_struct_struct<'ctx>( complex_bitcast(builder, from_value.into(), to_type.into(), name).into_struct_value() } -fn cast_tag_to_block_of_memory<'ctx>( - builder: &Builder<'ctx>, +fn cast_tag_to_block_of_memory<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, from_value: StructValue<'ctx>, to_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { - complex_bitcast( - builder, - from_value.into(), - to_type, - "tag_to_block_of_memory", - ) + complex_bitcast_check_size(env, from_value.into(), to_type, "tag_to_block_of_memory") } pub fn cast_block_of_memory_to_tag<'ctx>( @@ -2690,34 +2556,126 @@ pub fn complex_bitcast<'ctx>( to_type: BasicTypeEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { - // builder.build_bitcast(from_value, to_type, "cast_basic_basic") - // because this does not allow some (valid) bitcasts - use BasicTypeEnum::*; - match (from_value.get_type(), to_type) { - (PointerType(_), PointerType(_)) => { - // we can't use the more straightforward bitcast in all cases - // it seems like a bitcast only works on integers and pointers - // and crucially does not work not on arrays - builder.build_bitcast(from_value, to_type, name) - } - _ => { - // store the value in memory - let argument_pointer = builder.build_alloca(from_value.get_type(), "cast_alloca"); - builder.build_store(argument_pointer, from_value); - // then read it back as a different type - let to_type_pointer = builder - .build_bitcast( - argument_pointer, - to_type.ptr_type(inkwell::AddressSpace::Generic), - name, - ) - .into_pointer_value(); - - builder.build_load(to_type_pointer, "cast_value") - } + if let (PointerType(_), PointerType(_)) = (from_value.get_type(), to_type) { + // we can't use the more straightforward bitcast in all cases + // it seems like a bitcast only works on integers and pointers + // and crucially does not work not on arrays + return builder.build_bitcast(from_value, to_type, name); } + + complex_bitcast_from_bigger_than_to(builder, from_value, to_type, name) +} + +/// Check the size of the input and output types. Pretending we have more bytes at a pointer than +/// we actually do can lead to faulty optimizations and weird segfaults/crashes +fn complex_bitcast_check_size<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + use BasicTypeEnum::*; + + if let (PointerType(_), PointerType(_)) = (from_value.get_type(), to_type) { + // we can't use the more straightforward bitcast in all cases + // it seems like a bitcast only works on integers and pointers + // and crucially does not work not on arrays + return env.builder.build_bitcast(from_value, to_type, name); + } + + let block = env.builder.get_insert_block().expect("to be in a function"); + let parent = block.get_parent().expect("to be in a function"); + let then_block = env.context.append_basic_block(parent, "then"); + let else_block = env.context.append_basic_block(parent, "else"); + let cont_block = env.context.append_basic_block(parent, "cont"); + + let from_size = from_value.get_type().size_of().unwrap(); + let to_size = to_type.size_of().unwrap(); + + let condition = env.builder.build_int_compare( + IntPredicate::UGT, + from_size, + to_size, + "from_size >= to_size", + ); + + env.builder + .build_conditional_branch(condition, then_block, else_block); + + let then_answer = { + env.builder.position_at_end(then_block); + let result = complex_bitcast_from_bigger_than_to(env.builder, from_value, to_type, name); + env.builder.build_unconditional_branch(cont_block); + result + }; + + let else_answer = { + env.builder.position_at_end(else_block); + let result = complex_bitcast_to_bigger_than_from(env.builder, from_value, to_type, name); + env.builder.build_unconditional_branch(cont_block); + result + }; + + env.builder.position_at_end(cont_block); + + let result = env.builder.build_phi(then_answer.get_type(), "answer"); + + result.add_incoming(&[(&then_answer, then_block), (&else_answer, else_block)]); + + result.as_basic_value() +} + +fn complex_bitcast_from_bigger_than_to<'ctx>( + builder: &Builder<'ctx>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + // store the value in memory + let argument_pointer = builder.build_alloca(from_value.get_type(), "cast_alloca"); + builder.build_store(argument_pointer, from_value); + + // then read it back as a different type + let to_type_pointer = builder + .build_bitcast( + argument_pointer, + to_type.ptr_type(inkwell::AddressSpace::Generic), + name, + ) + .into_pointer_value(); + + builder.build_load(to_type_pointer, "cast_value") +} + +fn complex_bitcast_to_bigger_than_from<'ctx>( + builder: &Builder<'ctx>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + // reserve space in memory with the return type. This way, if the return type is bigger + // than the input type, we don't access invalid memory when later taking a pointer to + // the cast value + let storage = builder.build_alloca(to_type, "cast_alloca"); + + // then cast the pointer to our desired type + let from_type_pointer = builder + .build_bitcast( + storage, + from_value + .get_type() + .ptr_type(inkwell::AddressSpace::Generic), + name, + ) + .into_pointer_value(); + + // store the value in memory + builder.build_store(from_type_pointer, from_value); + + // then read it back as a different type + builder.build_load(storage, "cast_value") } /// get the tag id out of a pointer to a wrapped (i.e. stores the tag id at runtime) layout @@ -3004,13 +2962,19 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( roc_function: FunctionValue<'ctx>, c_function_name: &str, ) -> FunctionValue<'ctx> { - let roc_wrapper_function = make_exception_catcher(env, roc_function); + let context = env.context; - let roc_function_type = roc_wrapper_function.get_type(); + let wrapper_return_type = context.struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ); // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` - let mut argument_types = roc_function_type.get_param_types(); - let return_type = roc_function_type.get_return_type().unwrap(); + let mut argument_types = roc_function.get_type().get_param_types(); + let return_type = wrapper_return_type; let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); @@ -3043,12 +3007,28 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( let args = &args[..args.len() - 1]; debug_assert_eq!(args.len(), roc_function.get_params().len()); - debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len()); - let call_wrapped = builder.build_call(roc_wrapper_function, args, "call_wrapped_function"); - call_wrapped.set_call_convention(FAST_CALL_CONV); + let call_result = { + if env.is_gen_test { + let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len()); - let call_result = call_wrapped.try_as_basic_value().left().unwrap(); + builder.position_at_end(entry); + + let call_wrapped = + builder.build_call(roc_wrapper_function, args, "call_wrapped_function"); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + call_wrapped.try_as_basic_value().left().unwrap() + } else { + let call_unwrapped = builder.build_call(roc_function, args, "call_unwrapped_function"); + call_unwrapped.set_call_convention(FAST_CALL_CONV); + + let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); + + make_good_roc_result(env, call_unwrapped_result) + } + }; let output_arg = c_function .get_nth_param(output_arg_index as u32) @@ -3086,7 +3066,41 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( c_function } -fn invoke_and_catch<'a, 'ctx, 'env, F, T>( +pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let type_ = env.context.i8_type().array_type(5 * env.ptr_bytes); + + let global = match env.module.get_global("roc_sjlj_buffer") { + Some(global) => global, + None => env.module.add_global(type_, None, "roc_sjlj_buffer"), + }; + + global.set_initializer(&type_.const_zero()); + + env.builder + .build_bitcast( + global.as_pointer_value(), + env.context.i8_type().ptr_type(AddressSpace::Generic), + "cast_sjlj_buffer", + ) + .into_pointer_value() +} + +pub fn get_sjlj_message_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let type_ = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let global = match env.module.get_global("roc_sjlj_message_buffer") { + Some(global) => global, + None => env + .module + .add_global(type_, None, "roc_sjlj_message_buffer"), + }; + + global.set_initializer(&type_.const_zero()); + + global.as_pointer_value() +} + +fn set_jump_and_catch_long_jump<'a, 'ctx, 'env, F, T>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, function: F, @@ -3106,145 +3120,145 @@ where false, ); + let result_alloca = builder.build_alloca(call_result_type, "result"); + let then_block = context.append_basic_block(parent, "then_block"); let catch_block = context.append_basic_block(parent, "catch_block"); let cont_block = context.append_basic_block(parent, "cont_block"); - let result_alloca = builder.build_alloca(call_result_type, "result"); + let buffer = get_sjlj_buffer(env); - // invoke instead of call, so that we can catch any exeptions thrown in Roc code - let call_result = { - let call = builder.build_invoke( - function, - arguments, - then_block, - catch_block, - "call_roc_function", - ); - call.set_call_convention(calling_convention); - call.try_as_basic_value().left().unwrap() - }; - - // exception handling - { - builder.position_at_end(catch_block); - - build_catch_all_landing_pad(env, result_alloca); - - builder.build_unconditional_branch(cont_block); - } - - { - builder.position_at_end(then_block); - - let return_value = { - let v1 = call_result_type.const_zero(); - - let v2 = builder - .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") - .unwrap(); - let v3 = builder - .build_insert_value(v2, call_result, 1, "set_call_result") - .unwrap(); - - v3 - }; - - let ptr = builder.build_bitcast( - result_alloca, - call_result_type.ptr_type(AddressSpace::Generic), - "name", - ); - builder.build_store(ptr.into_pointer_value(), return_value); - - builder.build_unconditional_branch(cont_block); - } - - builder.position_at_end(cont_block); - - builder.build_load(result_alloca, "result") -} - -fn build_catch_all_landing_pad<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - result_alloca: PointerValue<'ctx>, -) { - let context = env.context; - let builder = env.builder; - - let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); - - let landing_pad_type = { - let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); - let selector_value = context.i32_type().into(); - - context.struct_type(&[exception_ptr, selector_value], false) - }; - - // null pointer functions as a catch-all catch clause - let null = u8_ptr.const_zero(); - - let personality_function = get_gxx_personality_v0(env); - - let info = builder - .build_landing_pad( - landing_pad_type, - personality_function, - &[null.into()], - false, - "main_landing_pad", - ) - .into_struct_value(); - - let exception_ptr = builder - .build_extract_value(info, 0, "exception_ptr") - .unwrap(); - - let thrown = cxa_begin_catch(env, exception_ptr); - - let error_msg = { - let exception_type = u8_ptr; - let ptr = builder.build_bitcast( - thrown, - exception_type.ptr_type(AddressSpace::Generic), - "cast", - ); - - builder.build_load(ptr.into_pointer_value(), "error_msg") - }; - - let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); - - let return_value = { - let v1 = return_type.const_zero(); - - // flag is non-zero, indicating failure - let flag = context.i64_type().const_int(1, false); - - let v2 = builder - .build_insert_value(v1, flag, 0, "set_error") - .unwrap(); - - let v3 = builder - .build_insert_value(v2, error_msg, 1, "set_exception") - .unwrap(); - - v3 - }; - - // bitcast result alloca so we can store our concrete type { flag, error_msg } in there - let result_alloca_bitcast = builder + let cast = env + .builder .build_bitcast( - result_alloca, - return_type.ptr_type(AddressSpace::Generic), - "result_alloca_bitcast", + buffer, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .array_type(5) + .ptr_type(AddressSpace::Generic), + "to [5 x i8*]", ) .into_pointer_value(); - // store our return value - builder.build_store(result_alloca_bitcast, return_value); + let zero = env.context.i32_type().const_zero(); - cxa_end_catch(env); + let index = env.context.i32_type().const_zero(); + let fa = unsafe { + env.builder + .build_in_bounds_gep(cast, &[zero, index], "name") + }; + + let index = env.context.i32_type().const_int(2, false); + let ss = unsafe { + env.builder + .build_in_bounds_gep(cast, &[zero, index], "name") + }; + + let index = env.context.i32_type().const_int(3, false); + let error_msg = unsafe { + env.builder + .build_in_bounds_gep(cast, &[zero, index], "name") + }; + + let frame_address = env.call_intrinsic( + LLVM_FRAME_ADDRESS, + &[env.context.i32_type().const_zero().into()], + ); + + env.builder.build_store(fa, frame_address); + + let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); + + env.builder.build_store(ss, stack_save); + + let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]); + let panicked_bool = env.builder.build_int_compare( + IntPredicate::NE, + panicked_u32.into_int_value(), + panicked_u32.get_type().into_int_type().const_zero(), + "to_bool", + ); + + env.builder + .build_conditional_branch(panicked_bool, catch_block, then_block); + + // all went well + { + builder.position_at_end(then_block); + + let call = env.builder.build_call(function, arguments, "call_function"); + + call.set_call_convention(calling_convention); + + let call_result = call.try_as_basic_value().left().unwrap(); + + let return_value = make_good_roc_result(env, call_result); + + builder.build_store(result_alloca, return_value); + + env.builder.build_unconditional_branch(cont_block); + } + + // something went wrong + { + builder.position_at_end(catch_block); + + let error_msg = { + // u8** + let ptr_int_ptr = builder.build_bitcast( + error_msg, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .ptr_type(AddressSpace::Generic), + "cast", + ); + + // u8* again + let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int"); + + ptr_int + }; + + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); + // let return_type = call_result_type; + + let return_value = { + let v1 = return_type.const_zero(); + + // flag is non-zero, indicating failure + let flag = context.i64_type().const_int(1, false); + + let v2 = builder + .build_insert_value(v1, flag, 0, "set_error") + .unwrap(); + + let v3 = builder + .build_insert_value(v2, error_msg, 1, "set_exception") + .unwrap(); + v3 + }; + + // bitcast result alloca so we can store our concrete type { flag, error_msg } in there + let result_alloca_bitcast = builder + .build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "result_alloca_bitcast", + ) + .into_pointer_value(); + + // store our return value + builder.build_store(result_alloca_bitcast, return_value); + + env.builder.build_unconditional_branch(cont_block); + } + + env.builder.position_at_end(cont_block); + + builder.build_load(result_alloca, "load_result") } fn make_exception_catcher<'a, 'ctx, 'env>( @@ -3260,6 +3274,30 @@ fn make_exception_catcher<'a, 'ctx, 'env>( function_value } +fn make_good_roc_result<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + return_value: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let context = env.context; + let builder = env.builder; + + let content_type = return_value.get_type(); + let wrapper_return_type = + context.struct_type(&[context.i64_type().into(), content_type], false); + + let v1 = wrapper_return_type.const_zero(); + + let v2 = builder + .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") + .unwrap(); + + let v3 = builder + .build_insert_value(v2, return_value, 1, "set_call_result") + .unwrap(); + + v3.into_struct_value().into() +} + fn make_exception_catching_wrapper<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, @@ -3276,11 +3314,14 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( let wrapper_return_type = context.struct_type( &[ context.i64_type().into(), - roc_function_type.get_return_type().unwrap(), + roc_function.get_type().get_return_type().unwrap(), ], false, ); + // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); + + // let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false); let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false); // Add main to the module. @@ -3306,7 +3347,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( debug_info_init!(env, wrapper_function); - let result = invoke_and_catch( + let result = set_jump_and_catch_long_jump( env, wrapper_function, roc_function, @@ -3540,18 +3581,6 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( lambda_set: LambdaSet<'a>, result: &Layout<'a>, ) { - let context = &env.context; - let builder = env.builder; - - // STEP 1: build function header - - // e.g. `roc__main_1_Fx_caller` - let function_name = format!( - "roc__{}_{}_caller", - def_name, - alias_symbol.as_str(&env.interns) - ); - let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); for layout in arguments { @@ -3568,6 +3597,9 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( }; argument_types.push(closure_argument_type.into()); + let context = &env.context; + let builder = env.builder; + let result_type = basic_type_from_layout(env, result); let roc_call_result_type = @@ -3576,6 +3608,15 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let output_type = { roc_call_result_type.ptr_type(AddressSpace::Generic) }; argument_types.push(output_type.into()); + // STEP 1: build function header + + // e.g. `roc__main_1_Fx_caller` + let function_name = format!( + "roc__{}_{}_caller", + def_name, + alias_symbol.as_str(&env.interns) + ); + let function_type = context.void_type().fn_type(&argument_types, false); let function_value = add_func( @@ -3594,9 +3635,15 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let mut parameters = function_value.get_params(); let output = parameters.pop().unwrap().into_pointer_value(); - let closure_data_ptr = parameters.pop().unwrap().into_pointer_value(); - let closure_data = builder.build_load(closure_data_ptr, "load_closure_data"); + let closure_data = if let Some(closure_data_ptr) = parameters.pop() { + let closure_data = + builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data"); + + env.arena.alloc([closure_data]) as &[_] + } else { + &[] + }; let mut parameters = parameters; @@ -3605,12 +3652,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( *param = builder.build_load(param.into_pointer_value(), "load_param"); } - let call_result = invoke_and_catch( + let call_result = set_jump_and_catch_long_jump( env, function_value, evaluator, evaluator.get_call_conventions(), - &[closure_data], + closure_data, result_type, ); @@ -3628,8 +3675,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( ); // STEP 4: build a {} -> u64 function that gives the size of the closure - let layout = lambda_set.runtime_representation(); - build_host_exposed_alias_size(env, def_name, alias_symbol, layout); + build_host_exposed_alias_size( + env, + def_name, + alias_symbol, + lambda_set.runtime_representation(), + ); } fn build_host_exposed_alias_size<'a, 'ctx, 'env>( @@ -4444,6 +4495,14 @@ fn run_low_level<'a, 'ctx, 'env>( str_from_utf8(env, parent, original_wrapper) } + StrFromUtf8Range => { + debug_assert_eq!(args.len(), 2); + + let list_wrapper = load_symbol(scope, &args[0]).into_struct_value(); + let count_and_start = load_symbol(scope, &args[1]).into_struct_value(); + + str_from_utf8_range(env, parent, list_wrapper, count_and_start) + } StrToUtf8 => { // Str.fromInt : Str -> List U8 debug_assert_eq!(args.len(), 1); @@ -4644,6 +4703,42 @@ fn run_low_level<'a, 'ctx, 'env>( } } } + NumBytesToU16 => { + debug_assert_eq!(args.len(), 2); + let list = load_symbol(scope, &args[0]).into_struct_value(); + let position = load_symbol(scope, &args[1]); + call_bitcode_fn( + env, + &[ + complex_bitcast( + env.builder, + list.into(), + env.context.i128_type().into(), + "to_i128", + ), + position, + ], + bitcode::NUM_BYTES_TO_U16, + ) + } + NumBytesToU32 => { + debug_assert_eq!(args.len(), 2); + let list = load_symbol(scope, &args[0]).into_struct_value(); + let position = load_symbol(scope, &args[1]); + call_bitcode_fn( + env, + &[ + complex_bitcast( + env.builder, + list.into(), + env.context.i128_type().into(), + "to_i128", + ), + position, + ], + bitcode::NUM_BYTES_TO_U32, + ) + } NumCompare => { use inkwell::FloatPredicate; @@ -5075,16 +5170,6 @@ fn run_low_level<'a, 'ctx, 'env>( } } -enum ForeignCallOrInvoke<'a> { - Call, - Invoke { - symbol: Symbol, - exception_id: ExceptionId, - pass: &'a roc_mono::ir::Stmt<'a>, - fail: &'a roc_mono::ir::Stmt<'a>, - }, -} - fn build_foreign_symbol_return_result<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &mut Scope<'a, 'ctx>, @@ -5139,14 +5224,10 @@ fn build_foreign_symbol_write_result_into_ptr<'a, 'ctx, 'env>( #[allow(clippy::too_many_arguments)] fn build_foreign_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, - func_spec_solutions: &FuncSpecSolutions, scope: &mut Scope<'a, 'ctx>, - parent: FunctionValue<'ctx>, foreign: &roc_module::ident::ForeignSymbol, arguments: &[Symbol], ret_layout: &Layout<'a>, - call_or_invoke: ForeignCallOrInvoke<'a>, ) -> BasicValueEnum<'ctx> { let ret_type = basic_type_from_layout(env, ret_layout); let return_pointer = env.builder.build_alloca(ret_type, "return_value"); @@ -5160,87 +5241,17 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( build_foreign_symbol_return_result(env, scope, foreign, arguments, ret_type) }; - match call_or_invoke { - ForeignCallOrInvoke::Call => { - let call = env.builder.build_call(function, arguments, "tmp"); + let call = env.builder.build_call(function, arguments, "tmp"); - // this is a foreign function, use c calling convention - call.set_call_convention(C_CALL_CONV); + // this is a foreign function, use c calling convention + call.set_call_convention(C_CALL_CONV); - call.try_as_basic_value(); + call.try_as_basic_value(); - if pass_result_by_pointer { - env.builder.build_load(return_pointer, "read_result") - } else { - call.try_as_basic_value().left().unwrap() - } - } - ForeignCallOrInvoke::Invoke { - symbol, - pass, - fail, - exception_id, - } => { - let pass_block = env.context.append_basic_block(parent, "invoke_pass"); - let fail_block = env.context.append_basic_block(parent, "invoke_fail"); - - let call = env - .builder - .build_invoke(function, arguments, pass_block, fail_block, "tmp"); - - // this is a foreign function, use c calling convention - call.set_call_convention(C_CALL_CONV); - - call.try_as_basic_value(); - - let call_result = if pass_result_by_pointer { - env.builder.build_load(return_pointer, "read_result") - } else { - call.try_as_basic_value().left().unwrap() - }; - - { - env.builder.position_at_end(pass_block); - - scope.insert(symbol, (*ret_layout, call_result)); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, pass); - - scope.remove(&symbol); - } - - { - env.builder.position_at_end(fail_block); - - let landing_pad_type = { - let exception_ptr = - env.context.i8_type().ptr_type(AddressSpace::Generic).into(); - let selector_value = env.context.i32_type().into(); - - env.context - .struct_type(&[exception_ptr, selector_value], false) - }; - - let personality_function = get_gxx_personality_v0(env); - - let exception_object = env.builder.build_landing_pad( - landing_pad_type, - personality_function, - &[], - true, - "invoke_landing_pad", - ); - - scope.insert( - exception_id.into_inner(), - (Layout::Struct(&[]), exception_object), - ); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, fail); - } - - call_result - } + if pass_result_by_pointer { + env.builder.build_load(return_pointer, "read_result") + } else { + call.try_as_basic_value().left().unwrap() } } @@ -6048,131 +6059,26 @@ fn define_global_error_str<'a, 'ctx, 'env>( } fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) { - let context = env.context; let builder = env.builder; - let info = { - // we represented both void and char pointers with `u8*` - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + // define the error message as a global + // (a hash is used such that the same value is not defined repeatedly) + let error_msg_global = define_global_error_str(env, message); - // allocate an exception (that can hold a pointer to a string) - let str_ptr_size = env - .context - .i64_type() - .const_int(env.ptr_bytes as u64, false); - let initial = cxa_allocate_exception(env, str_ptr_size); + let cast = env + .builder + .build_bitcast( + error_msg_global.as_pointer_value(), + env.context.i8_type().ptr_type(AddressSpace::Generic), + "cast_void", + ) + .into_pointer_value(); - // define the error message as a global - // (a hash is used such that the same value is not defined repeatedly) - let error_msg_global = define_global_error_str(env, message); - - // cast this to a void pointer - let error_msg_ptr = - builder.build_bitcast(error_msg_global.as_pointer_value(), u8_ptr, "unused"); - - // store this void pointer in the exception - let exception_type = u8_ptr; - let exception_value = error_msg_ptr; - - let temp = builder - .build_bitcast( - initial, - exception_type.ptr_type(AddressSpace::Generic), - "exception_object_str_ptr_ptr", - ) - .into_pointer_value(); - - builder.build_store(temp, exception_value); - - initial - }; - - cxa_throw_exception(env, info); + env.call_panic(cast, PanicTagId::NullTerminatedString); builder.build_unreachable(); } -fn cxa_allocate_exception<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - exception_size: IntValue<'ctx>, -) -> BasicValueEnum<'ctx> { - let name = "__cxa_allocate_exception"; - - let module = env.module; - let context = env.context; - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let function = match module.get_function(name) { - Some(gvalue) => gvalue, - None => { - // void *__cxa_allocate_exception(size_t thrown_size); - let cxa_allocate_exception = add_func( - module, - name, - u8_ptr.fn_type(&[context.i64_type().into()], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_allocate_exception - } - }; - let call = env.builder.build_call( - function, - &[exception_size.into()], - "exception_object_void_ptr", - ); - - call.set_call_convention(C_CALL_CONV); - call.try_as_basic_value().left().unwrap() -} - -fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicValueEnum<'ctx>) { - let name = "__cxa_throw"; - - let module = env.module; - let context = env.context; - let builder = env.builder; - - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let function = match module.get_function(name) { - Some(value) => value, - None => { - // void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) ); - let cxa_throw = add_func( - module, - name, - context - .void_type() - .fn_type(&[u8_ptr.into(), u8_ptr.into(), u8_ptr.into()], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_throw - } - }; - - // global storing the type info of a c++ int (equivalent to `i32` in llvm) - // we just need any valid such value, and arbitrarily use this one - let ztii = match module.get_global("_ZTIi") { - Some(gvalue) => gvalue.as_pointer_value(), - None => { - let ztii = module.add_global(u8_ptr, Some(AddressSpace::Generic), "_ZTIi"); - ztii.set_linkage(Linkage::External); - - ztii.as_pointer_value() - } - }; - - let type_info = builder.build_bitcast(ztii, u8_ptr, "cast"); - let null: BasicValueEnum = u8_ptr.const_zero().into(); - - let call = builder.build_call(function, &[info, type_info, null], "throw"); - call.set_call_convention(C_CALL_CONV); -} - fn get_foreign_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, foreign_symbol: roc_module::ident::ForeignSymbol, @@ -6196,86 +6102,6 @@ fn get_foreign_symbol<'a, 'ctx, 'env>( } } -fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { - let name = "__gxx_personality_v0"; - - let module = env.module; - let context = env.context; - - match module.get_function(name) { - Some(gvalue) => gvalue, - None => { - let personality_func = add_func( - module, - name, - context.i64_type().fn_type(&[], false), - Linkage::External, - C_CALL_CONV, - ); - - personality_func - } - } -} - -fn cxa_end_catch(env: &Env<'_, '_, '_>) { - let name = "__cxa_end_catch"; - - let module = env.module; - let context = env.context; - - let function = match module.get_function(name) { - Some(gvalue) => gvalue, - None => { - let cxa_end_catch = add_func( - module, - name, - context.void_type().fn_type(&[], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_end_catch - } - }; - let call = env.builder.build_call(function, &[], "never_used"); - - call.set_call_convention(C_CALL_CONV); -} - -fn cxa_begin_catch<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - exception_ptr: BasicValueEnum<'ctx>, -) -> BasicValueEnum<'ctx> { - let name = "__cxa_begin_catch"; - - let module = env.module; - let context = env.context; - - let function = match module.get_function(name) { - Some(gvalue) => gvalue, - None => { - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let cxa_begin_catch = add_func( - module, - name, - u8_ptr.fn_type(&[u8_ptr.into()], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_begin_catch - } - }; - let call = env - .builder - .build_call(function, &[exception_ptr], "exception_payload_ptr"); - - call.set_call_convention(C_CALL_CONV); - call.try_as_basic_value().left().unwrap() -} - /// Add a function to a module, after asserting that the function is unique. /// We never want to define the same function twice in the same module! /// The result can be bugs that are difficult to track down. diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 71b6131e2e..85e82e18e8 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -88,10 +88,6 @@ fn build_hash_layout<'a, 'ctx, 'env>( ) } }, - - Layout::Closure(_, _, _) => { - unreachable!("the type system will guarantee these are never hashed") - } } } diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 8c9d28663a..bd0d6344cd 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -121,69 +121,6 @@ pub fn list_repeat<'a, 'ctx, 'env>( ) } -/// List.prepend : List elem, elem -> List elem -pub fn list_prepend<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - original_wrapper: StructValue<'ctx>, - elem: BasicValueEnum<'ctx>, - elem_layout: &Layout<'a>, -) -> BasicValueEnum<'ctx> { - let builder = env.builder; - - // Load the usize length from the wrapper. - let len = list_len(builder, original_wrapper); - let elem_type = basic_type_from_layout(env, elem_layout); - let ptr_type = elem_type.ptr_type(AddressSpace::Generic); - let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type); - - // The output list length, which is the old list length + 1 - let new_list_len = env.builder.build_int_add( - env.ptr_int().const_int(1_u64, false), - len, - "new_list_length", - ); - - // Allocate space for the new array that we'll copy into. - let clone_ptr = allocate_list(env, elem_layout, new_list_len); - - builder.build_store(clone_ptr, elem); - - let index_1_ptr = unsafe { - builder.build_in_bounds_gep( - clone_ptr, - &[env.ptr_int().const_int(1_u64, false)], - "load_index", - ) - }; - - // Calculate the number of bytes we'll need to allocate. - let elem_bytes = env - .ptr_int() - .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); - - // This is the size of the list coming in, before we have added an element - // to the beginning. - let list_size = env - .builder - .build_int_mul(elem_bytes, len, "mul_old_len_by_elem_bytes"); - - let ptr_bytes = env.ptr_bytes; - - if elem_layout.safe_to_memcpy() { - // Copy the bytes from the original array into the new - // one we just allocated - // - // TODO how do we decide when to do the small memcpy vs the normal one? - builder - .build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size) - .unwrap(); - } else { - panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); - } - - store_list(env, clone_ptr, new_list_len) -} - /// List.join : List (List elem) -> List elem pub fn list_join<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -299,6 +236,25 @@ pub fn list_append<'a, 'ctx, 'env>( ) } +/// List.prepend : List elem, elem -> List elem +pub fn list_prepend<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + original_wrapper: StructValue<'ctx>, + element: BasicValueEnum<'ctx>, + element_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + call_bitcode_fn_returns_list( + env, + &[ + pass_list_as_i128(env, original_wrapper.into()), + env.alignment_intvalue(element_layout), + pass_element_as_opaque(env, element), + layout_width(env, element_layout), + ], + bitcode::LIST_PREPEND, + ) +} + /// List.swap : List elem, Nat, Nat -> List elem pub fn list_swap<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index b9175f5041..9d3c482f9d 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -250,6 +250,62 @@ pub fn str_to_utf8<'a, 'ctx, 'env>( call_bitcode_fn_returns_list(env, &[string], bitcode::STR_TO_UTF8) } +/// Str.fromUtf8 : List U8, { count : Nat, start : Nat } -> { a : Bool, b : Str, c : Nat, d : I8 } +pub fn str_from_utf8_range<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + _parent: FunctionValue<'ctx>, + list_wrapper: StructValue<'ctx>, + count_and_start: StructValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap(); + let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result"); + + call_void_bitcode_fn( + env, + &[ + complex_bitcast( + env.builder, + list_wrapper.into(), + env.context.i128_type().into(), + "to_i128", + ), + // TODO: This won't work for 32 bit targets! + complex_bitcast( + env.builder, + count_and_start.into(), + env.context.i128_type().into(), + "to_i128", + ), + result_ptr.into(), + ], + bitcode::STR_FROM_UTF8_RANGE, + ); + + let record_type = env.context.struct_type( + &[ + env.ptr_int().into(), + super::convert::zig_str_type(env).into(), + env.context.bool_type().into(), + ctx.i8_type().into(), + ], + false, + ); + + let result_ptr_cast = env + .builder + .build_bitcast( + result_ptr, + record_type.ptr_type(AddressSpace::Generic), + "to_unnamed", + ) + .into_pointer_value(); + + builder.build_load(result_ptr_cast, "load_utf8_validate_bytes_result") +} + /// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 } pub fn str_from_utf8<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 28f91d91b2..10463094df 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -198,10 +198,6 @@ fn build_eq<'a, 'ctx, 'env>( ) } }, - - Layout::Closure(_, _, _) => { - unreachable!("the type system will guarantee these are never compared") - } } } @@ -340,10 +336,6 @@ fn build_neq<'a, 'ctx, 'env>( Layout::RecursivePointer => { unreachable!("recursion pointers should never be compared directly") } - - Layout::Closure(_, _, _) => { - unreachable!("the type system will guarantee these are never compared") - } } } diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index df4a074e68..1522c21e9e 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -26,10 +26,6 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( use Layout::*; match layout { - Closure(_args, closure_layout, _ret_layout) => { - let closure_data_layout = closure_layout.runtime_representation(); - basic_type_from_layout(env, &closure_data_layout) - } Struct(sorted_fields) => basic_type_from_record(env, sorted_fields), Union(union_layout) => { use UnionLayout::*; diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 48bea9283b..a0040102ab 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -1,19 +1,18 @@ +use crate::llvm::build::Env; use crate::llvm::build::{add_func, C_CALL_CONV}; use crate::llvm::convert::ptr_int; -use inkwell::builder::Builder; -use inkwell::context::Context; -use inkwell::module::{Linkage, Module}; +use inkwell::module::Linkage; use inkwell::values::BasicValue; use inkwell::AddressSpace; /// Define functions for roc_alloc, roc_realloc, and roc_dealloc /// which use libc implementations (malloc, realloc, and free) -pub fn add_default_roc_externs<'ctx>( - ctx: &'ctx Context, - module: &Module<'ctx>, - builder: &Builder<'ctx>, - ptr_bytes: u32, -) { +pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { + let ctx = env.context; + let module = env.module; + let builder = env.builder; + let ptr_bytes = env.ptr_bytes; + let usize_type = ptr_int(ctx, ptr_bytes); let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic); @@ -139,4 +138,69 @@ pub fn add_default_roc_externs<'ctx>( crate::llvm::build::verify_fn(fn_val); } } + + add_sjlj_roc_panic(env) +} + +pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { + let ctx = env.context; + let module = env.module; + let builder = env.builder; + + // roc_panic + { + use crate::llvm::build::LLVM_LONGJMP; + + // The type of this function (but not the implementation) should have + // already been defined by the builtins, which rely on it. + let fn_val = module.get_function("roc_panic").unwrap(); + let mut params = fn_val.get_param_iter(); + let ptr_arg = params.next().unwrap(); + + // in debug mode, this is assumed to be NullTerminatedString + let _tag_id_arg = params.next().unwrap(); + + debug_assert!(params.next().is_none()); + + let subprogram = env.new_subprogram("roc_panic"); + fn_val.set_subprogram(subprogram); + + env.dibuilder.finalize(); + + // Add a basic block for the entry point + let entry = ctx.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + let buffer = crate::llvm::build::get_sjlj_buffer(env); + + // write our error message pointer + let index = env.ptr_int().const_int(3 * env.ptr_bytes as u64, false); + let message_buffer_raw = + unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; + let message_buffer = builder.build_bitcast( + message_buffer_raw, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .ptr_type(AddressSpace::Generic), + "to **u8", + ); + + env.builder + .build_store(message_buffer.into_pointer_value(), ptr_arg); + + let tag = env.context.i32_type().const_int(1, false); + if true { + let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into()]); + } else { + let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into(), tag.into()]); + } + + builder.build_unreachable(); + + if cfg!(debug_assertions) { + crate::llvm::build::verify_fn(fn_val); + } + } } diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 6aa30320ab..2b8a0a7e67 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -278,7 +278,7 @@ impl<'ctx> PointerToRefcount<'ctx> { // build then block { builder.position_at_end(then_block); - if !env.leak { + if !env.is_gen_test { let ptr = builder.build_pointer_cast( refcount_ptr.value, ctx.i8_type().ptr_type(AddressSpace::Generic), @@ -659,23 +659,6 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>( Some(function) } - Closure(_, lambda_set, _) => { - if lambda_set.contains_refcounted() { - let function = modify_refcount_layout_build_function( - env, - parent, - layout_ids, - mode, - when_recursive, - &lambda_set.runtime_representation(), - )?; - - Some(function) - } else { - None - } - } - Struct(layouts) => { let function = modify_refcount_struct(env, layout_ids, layouts, mode, when_recursive); diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index 1d6139ee80..6b8919f058 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -165,13 +165,14 @@ fn generate_entry_doc<'a>( (new_acc, Some(comments_or_new_lines)) } - Def::Annotation(loc_pattern, _loc_ann) => match loc_pattern.value { + Def::Annotation(loc_pattern, loc_ann) => match loc_pattern.value { Pattern::Identifier(identifier) => { // Check if the definition is exposed if ident_ids.get_id(&identifier.into()).is_some() { + let name = identifier.to_string(); let doc_def = DocDef { - name: identifier.to_string(), - type_annotation: NoTypeAnn, + name, + type_annotation: type_to_docs(false, loc_ann.value), type_vars: Vec::new(), docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), }; diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 5dab90a4f8..f6081e6e21 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -1,3 +1,4 @@ +use roc_can::annotation::IntroducedVariables; use roc_can::def::{Declaration, Def}; use roc_can::env::Env; use roc_can::expr::{Expr, Recursive}; @@ -160,25 +161,22 @@ fn build_effect_always( (function_var, closure) }; - use roc_can::annotation::IntroducedVariables; - let mut introduced_variables = IntroducedVariables::default(); let signature = { // Effect.always : a -> Effect a let var_a = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - let effect_a = { - let actual = build_effect_actual(effect_tag_name, Type::Variable(var_a), var_store); - - Type::Alias( - effect_symbol, - vec![("a".into(), Type::Variable(var_a))], - Box::new(actual), - ) - }; + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name, + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(closure_var); @@ -353,8 +351,6 @@ fn build_effect_map( loc_body: Box::new(Located::at_zero(body)), }; - use roc_can::annotation::IntroducedVariables; - let mut introduced_variables = IntroducedVariables::default(); let signature = { @@ -365,26 +361,25 @@ fn build_effect_map( introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("b".into(), var_b); - let effect_a = { - let actual = - build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store); + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); - Type::Alias( - effect_symbol, - vec![("a".into(), Type::Variable(var_a))], - Box::new(actual), - ) - }; - - let effect_b = { - let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store); - - Type::Alias( - effect_symbol, - vec![("b".into(), Type::Variable(var_b))], - Box::new(actual), - ) - }; + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(closure_var); @@ -526,8 +521,6 @@ fn build_effect_after( loc_body: Box::new(Located::at_zero(to_effect_call)), }; - use roc_can::annotation::IntroducedVariables; - let mut introduced_variables = IntroducedVariables::default(); let signature = { @@ -537,26 +530,25 @@ fn build_effect_after( introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("b".into(), var_b); - let effect_a = { - let actual = - build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store); + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); - Type::Alias( - effect_symbol, - vec![("a".into(), Type::Variable(var_a))], - Box::new(actual), - ) - }; - - let effect_b = { - let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store); - - Type::Alias( - effect_symbol, - vec![("b".into(), Type::Variable(var_b))], - Box::new(actual), - ) - }; + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(closure_var); @@ -763,6 +755,40 @@ pub fn build_host_exposed_def( } } +fn build_effect_alias( + effect_symbol: Symbol, + effect_tag_name: TagName, + a_name: &str, + a_var: Variable, + a_type: Type, + var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, +) -> Type { + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let actual = { + Type::TagUnion( + vec![( + effect_tag_name, + vec![Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + )], + )], + Box::new(Type::EmptyTagUnion), + ) + }; + + Type::Alias { + symbol: effect_symbol, + type_arguments: vec![(a_name.into(), Type::Variable(a_var))], + lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))], + actual: Box::new(actual), + } +} + pub fn build_effect_actual( effect_tag_name: TagName, a_type: Type, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 9c7726ce7f..639f3a3cd6 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -4060,7 +4060,7 @@ fn add_def_to_module<'a>( pattern_vars.push(*var); } - let layout = match layout_cache.from_var( + let layout = match layout_cache.raw_from_var( mono_env.arena, annotation, mono_env.subs, @@ -4085,7 +4085,7 @@ fn add_def_to_module<'a>( procs.insert_exposed( symbol, - ProcLayout::from_layout(mono_env.arena, layout), + ProcLayout::from_raw(mono_env.arena, layout), mono_env.arena, mono_env.subs, def.annotation, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 378ae723c6..583e6017c1 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -13,6 +13,7 @@ pub enum LowLevel { StrCountGraphemes, StrFromInt, StrFromUtf8, + StrFromUtf8Range, StrToUtf8, StrFromFloat, ListLen, @@ -86,6 +87,8 @@ pub enum LowLevel { NumAtan, NumAcos, NumAsin, + NumBytesToU16, + NumBytesToU32, NumBitwiseAnd, NumBitwiseXor, NumBitwiseOr, @@ -110,21 +113,20 @@ impl LowLevel { match self { StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt - | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrToUtf8 - | StrFromFloat | ListLen | ListGetUnsafe | ListSet | ListDrop | ListSingle - | ListRepeat | ListReverse | ListConcat | ListContains | ListAppend | ListPrepend - | ListJoin | ListRange | ListSwap | DictSize | DictEmpty | DictInsert | DictRemove - | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion - | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap + | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 + | StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet + | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains + | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty + | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues + | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd - | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy - | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => { - false - } + | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 + | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not + | Hash | ExpectTrue => false, ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index a0d95181e6..dfbc95f3a7 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -891,7 +891,9 @@ define_builtins! { 100 NUM_AT_DECIMAL: "@Decimal" 101 NUM_DECIMAL: "Decimal" imported 102 NUM_DEC: "Dec" imported // the Num.Dectype alias - + 103 NUM_BYTES_TO_U16: "bytesToU16" + 104 NUM_BYTES_TO_U32: "bytesToU32" + 105 NUM_CAST_TO_NAT: "#castToNat" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias @@ -921,6 +923,7 @@ define_builtins! { 15 STR_TO_UTF8: "toUtf8" 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime + 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 878cf7e3e2..8d62b2b4ab 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -64,24 +64,10 @@ where let mut hasher = DefaultHasher::new(); for layout in argument_layouts { - match layout { - Layout::Closure(_, lambda_set, _) => { - lambda_set.runtime_representation().hash(&mut hasher); - } - _ => { - layout.hash(&mut hasher); - } - } + layout.hash(&mut hasher); } - match return_layout { - Layout::Closure(_, lambda_set, _) => { - lambda_set.runtime_representation().hash(&mut hasher); - } - _ => { - return_layout.hash(&mut hasher); - } - } + return_layout.hash(&mut hasher); hasher.finish() }; @@ -303,30 +289,6 @@ fn stmt_spec<'a>( Ok(result) } - Invoke { - symbol, - call, - layout: call_layout, - pass, - fail, - exception_id: _, - } => { - // a call that might throw an exception - - let value_id = call_spec(builder, env, block, call_layout, call)?; - - let pass_block = builder.add_block(); - env.symbols.insert(*symbol, value_id); - let pass_value_id = stmt_spec(builder, env, pass_block, layout, pass)?; - env.symbols.remove(symbol); - let pass_block_expr = BlockExpr(pass_block, pass_value_id); - - let fail_block = builder.add_block(); - let fail_value_id = stmt_spec(builder, env, fail_block, layout, fail)?; - let fail_block_expr = BlockExpr(fail_block, fail_value_id); - - builder.add_choice(block, &[pass_block_expr, fail_block_expr]) - } Switch { cond_symbol: _, cond_layout: _, @@ -434,7 +396,7 @@ fn stmt_spec<'a>( let jpid = env.join_points[id]; builder.add_jump(block, jpid, argument, ret_type_id) } - Resume(_) | RuntimeError(_) => { + RuntimeError(_) => { let type_id = layout_spec(builder, layout)?; builder.add_terminate(block, type_id) @@ -1258,11 +1220,6 @@ fn layout_spec_help( } }, }, - Closure(_, lambda_set, _) => layout_spec_help( - builder, - &lambda_set.runtime_representation(), - when_recursive, - ), } } diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index f15758bcc2..419158288a 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -10,10 +10,7 @@ pub const OWNED: bool = false; pub const BORROWED: bool = true; fn should_borrow_layout(layout: &Layout) -> bool { - match layout { - Layout::Closure(_, _, _) => false, - _ => layout.is_refcounted(), - } + layout.is_refcounted() } pub fn infer_borrow<'a>( @@ -49,7 +46,14 @@ pub fn infer_borrow<'a>( // component (in top-sorted order, from primitives (std-lib) to main) let successor_map = &make_successor_mapping(arena, procs); - let successors = move |key: &Symbol| successor_map[key].iter().copied(); + let successors = move |key: &Symbol| { + let slice = match successor_map.get(key) { + None => &[] as &[_], + Some(s) => s.as_slice(), + }; + + slice.iter().copied() + }; let mut symbols = Vec::with_capacity_in(procs.len(), arena); symbols.extend(procs.keys().map(|x| x.0)); @@ -220,7 +224,10 @@ impl<'a> DeclarationToIndex<'a> { } } } - unreachable!("symbol/layout combo must be in DeclarationToIndex") + unreachable!( + "symbol/layout {:?} {:?} combo must be in DeclarationToIndex", + needle_symbol, needle_layout + ) } } @@ -362,10 +369,6 @@ impl<'a> ParamMap<'a> { Let(_, _, _, cont) => { stack.push(cont); } - Invoke { pass, fail, .. } => { - stack.push(pass); - stack.push(fail); - } Switch { branches, default_branch, @@ -376,7 +379,7 @@ impl<'a> ParamMap<'a> { } Refcounting(_, _) => unreachable!("these have not been introduced yet"), - Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => { + Ret(_) | Jump(_, _) | RuntimeError(_) => { // these are terminal, do nothing } } @@ -881,23 +884,6 @@ impl<'a> BorrowInfState<'a> { } } - Invoke { - symbol, - call, - layout: _, - pass, - fail, - exception_id: _, - } => { - self.collect_stmt(param_map, pass); - self.collect_stmt(param_map, fail); - - self.collect_call(param_map, *symbol, call); - - // TODO how to preserve the tail call of an invoke? - // self.preserve_tail_call(*x, v, b); - } - Jump(j, ys) => { let ps = param_map.get_join_point(*j); @@ -919,7 +905,7 @@ impl<'a> BorrowInfState<'a> { } Refcounting(_, _) => unreachable!("these have not been introduced yet"), - Ret(_) | RuntimeError(_) | Resume(_) => { + Ret(_) | RuntimeError(_) => { // these are terminal, do nothing } } @@ -1012,9 +998,12 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]), + NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), + NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), StrStartsWithCodePt => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]), + StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrToUtf8 => arena.alloc_slice_copy(&[owned]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), @@ -1096,13 +1085,6 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>) } stack.push(cont); } - Invoke { - call, pass, fail, .. - } => { - call_info_call(call, info); - stack.push(pass); - stack.push(fail); - } Switch { branches, default_branch, @@ -1113,7 +1095,7 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>) } Refcounting(_, _) => unreachable!("these have not been introduced yet"), - Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => { + Ret(_) | Jump(_, _) | RuntimeError(_) => { // these are terminal, do nothing } } diff --git a/compiler/mono/src/expand_rc.rs b/compiler/mono/src/expand_rc.rs index 820fcf5395..5b70006c84 100644 --- a/compiler/mono/src/expand_rc.rs +++ b/compiler/mono/src/expand_rc.rs @@ -160,17 +160,9 @@ impl<'a, 'i> Env<'a, 'i> { fn try_insert_struct_info(&mut self, symbol: Symbol, layout: &Layout<'a>) { use Layout::*; - match layout { - Struct(fields) => { - self.constructor_map.insert(symbol, 0); - self.layout_map.insert(symbol, Layout::Struct(fields)); - } - Closure(_, lambda_set, _) => { - self.constructor_map.insert(symbol, 0); - self.layout_map - .insert(symbol, lambda_set.runtime_representation()); - } - _ => {} + if let Struct(fields) = layout { + self.constructor_map.insert(symbol, 0); + self.layout_map.insert(symbol, Layout::Struct(fields)); } } @@ -244,10 +236,6 @@ fn layout_for_constructor<'a>( debug_assert_eq!(constructor, 0); HasFields(fields) } - Closure(_arguments, _lambda_set, _result) => { - // HasFields(fields) - ConstructorLayout::Unknown - } other => unreachable!("weird layout {:?}", other), } } @@ -368,20 +356,10 @@ pub fn expand_and_cancel_proc<'a>( let mut introduced = Vec::new_in(env.arena); for (layout, symbol) in arguments { - match layout { - Layout::Struct(fields) => { - env.insert_struct_info(*symbol, fields); + if let Layout::Struct(fields) = layout { + env.insert_struct_info(*symbol, fields); - introduced.push(*symbol); - } - Layout::Closure(_arguments, _lambda_set, _result) => { - // TODO can this be improved again? - // let fpointer = Layout::FunctionPointer(arguments, result); - // let fields = env.arena.alloc([fpointer, *closure_layout.layout]); - // env.insert_struct_info(*symbol, fields); - // introduced.push(*symbol); - } - _ => {} + introduced.push(*symbol); } } @@ -585,29 +563,6 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt< expand_and_cancel(env, cont) } - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => { - let pass = expand_and_cancel(env, pass); - let fail = expand_and_cancel(env, fail); - - let stmt = Invoke { - symbol: *symbol, - call: call.clone(), - layout: *layout, - pass, - fail, - exception_id: *exception_id, - }; - - env.arena.alloc(stmt) - } - Join { id, parameters, @@ -627,7 +582,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt< env.arena.alloc(stmt) } - Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, + Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, } }; diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 5da365c516..e8fc6476a2 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -32,26 +32,10 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet, MutSet) stack.push(cont); } - Invoke { - symbol, - call, - pass, - fail, - .. - } => { - occurring_variables_call(call, &mut result); - result.insert(*symbol); - bound_variables.insert(*symbol); - stack.push(pass); - stack.push(fail); - } - Ret(symbol) => { result.insert(*symbol); } - Resume(_) => {} - Refcounting(modify, cont) => { let symbol = modify.get_symbol(); result.insert(symbol); @@ -238,11 +222,6 @@ fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool { } } -fn consume_call(_: &VarMap, _: &crate::ir::Call<'_>) -> bool { - // variables bound by a call (or invoke) must always be consumed - true -} - impl<'a> Context<'a> { pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self { let mut vars = MutMap::default(); @@ -271,10 +250,20 @@ impl<'a> Context<'a> { fn get_var_info(&self, symbol: Symbol) -> VarInfo { match self.vars.get(&symbol) { Some(info) => *info, - None => panic!( - "Symbol {:?} {} has no info in {:?}", - symbol, symbol, self.vars - ), + None => { + eprintln!( + "Symbol {:?} {} has no info in self.vars", + symbol, + symbol, // self.vars + ); + + VarInfo { + persistent: true, + reference: false, + consume: false, + reset: false, + } + } } } @@ -804,22 +793,6 @@ impl<'a> Context<'a> { (new_b, live_vars) } - fn update_var_info_invoke( - &self, - symbol: Symbol, - layout: &Layout<'a>, - call: &crate::ir::Call<'a>, - ) -> Self { - // is this value a constant? - // TODO do function pointers also fall into this category? - let persistent = call.arguments.is_empty(); - - // must this value be consumed? - let consume = consume_call(&self.vars, call); - - self.update_var_info_help(symbol, layout, persistent, consume, false) - } - fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self { // is this value a constant? // TODO do function pointers also fall into this category? @@ -955,82 +928,6 @@ impl<'a> Context<'a> { ctx.visit_variable_declaration(*symbol, expr.clone(), *layout, b, &b_live_vars) } - Invoke { - symbol, - call, - pass, - fail, - layout, - exception_id, - } => { - // live vars of the whole expression - let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default()); - - let fail = { - // TODO should we use ctor info like Lean? - let ctx = self.clone(); - let (b, alt_live_vars) = ctx.visit_stmt(fail); - ctx.add_dec_for_alt(&invoke_live_vars, &alt_live_vars, b) - }; - - let pass = { - // TODO should we use ctor info like Lean? - let ctx = self.clone(); - let ctx = ctx.update_var_info_invoke(*symbol, layout, call); - let (b, alt_live_vars) = ctx.visit_stmt(pass); - ctx.add_dec_for_alt(&invoke_live_vars, &alt_live_vars, b) - }; - - let invoke = Invoke { - symbol: *symbol, - call: call.clone(), - pass, - fail, - layout: *layout, - exception_id: *exception_id, - }; - - let cont = self.arena.alloc(invoke); - - use crate::ir::CallType; - let stmt = match &call.call_type { - CallType::LowLevel { op, .. } => { - let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op); - self.add_dec_after_lowlevel(call.arguments, ps, cont, &invoke_live_vars) - } - - CallType::HigherOrderLowLevel { .. } => { - todo!("copy the code for normal calls over to here"); - } - - CallType::Foreign { .. } => { - let ps = crate::borrow::foreign_borrow_signature( - self.arena, - call.arguments.len(), - ); - - self.add_dec_after_lowlevel(call.arguments, ps, cont, &invoke_live_vars) - } - - CallType::ByName { - name, - ret_layout, - arg_layouts, - .. - } => { - let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); - - // get the borrow signature - let ps = self - .param_map - .get_symbol(*name, top_level) - .expect("function is defined"); - self.add_dec_after_application(call.arguments, ps, cont, &invoke_live_vars) - } - }; - - (stmt, invoke_live_vars) - } Join { id: j, parameters: _, @@ -1076,8 +973,6 @@ impl<'a> Context<'a> { } } - Resume(_) => (stmt, MutSet::default()), - Jump(j, xs) => { let empty = MutSet::default(); let j_live_vars = match self.jp_live_vars.get(j) { @@ -1166,25 +1061,7 @@ pub fn collect_stmt( vars } - Invoke { - symbol, - call, - pass, - fail, - .. - } => { - vars = collect_stmt(pass, jp_live_vars, vars); - vars = collect_stmt(fail, jp_live_vars, vars); - vars.remove(symbol); - - let mut result = MutSet::default(); - occurring_variables_call(call, &mut result); - - vars.extend(result); - - vars - } Ret(symbol) => { vars.insert(*symbol); vars @@ -1242,8 +1119,6 @@ pub fn collect_stmt( vars } - Resume(_) => vars, - RuntimeError(_) => vars, } } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 3536bf1cdb..7529792fc1 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -15,7 +15,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_types::solved_types::SolvedType; -use roc_types::subs::{Content, FlatType, Subs, SubsSlice, Variable}; +use roc_types::subs::{Content, FlatType, Subs, Variable, VariableSubsSlice}; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; @@ -546,11 +546,11 @@ impl<'a> Procs<'a> { // anonymous functions cannot reference themselves, therefore cannot be tail-recursive let is_self_recursive = false; - let layout = layout_cache - .from_var(env.arena, annotation, env.subs) + let raw_layout = layout_cache + .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); - let top_level = ProcLayout::from_layout(env.arena, layout); + let top_level = ProcLayout::from_raw(env.arena, raw_layout); match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { @@ -617,7 +617,11 @@ impl<'a> Procs<'a> { Ok((proc, layout)) => { let top_level = ProcLayout::from_raw(env.arena, layout); - debug_assert_eq!(outside_layout, top_level); + debug_assert_eq!( + outside_layout, top_level, + "different raw layouts for {:?}", + proc.name + ); if self.module_thunks.contains(&proc.name) { debug_assert!(top_level.arguments.is_empty()); @@ -690,10 +694,8 @@ impl<'a> Procs<'a> { layout: ProcLayout<'a>, layout_cache: &mut LayoutCache<'a>, ) { - let tuple = (name, layout); - // If we've already specialized this one, no further work is needed. - if self.specialized.contains_key(&tuple) { + if self.specialized.contains_key(&(name, layout)) { return; } @@ -703,15 +705,12 @@ impl<'a> Procs<'a> { return; } - // We're done with that tuple, so move layout back out to avoid cloning it. - let (name, layout) = tuple; - - let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); - // This should only be called when pending_specializations is Some. // Otherwise, it's being called in the wrong pass! match &mut self.pending_specializations { Some(pending_specializations) => { + let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); + // register the pending specialization, so this gets code genned later if self.module_thunks.contains(&name) { debug_assert!(layout.arguments.is_empty()); @@ -732,7 +731,26 @@ impl<'a> Procs<'a> { // (We had a bug around this before this system existed!) self.specialized.insert((symbol, layout), InProgress); - match specialize(env, self, symbol, layout_cache, pending, partial_proc) { + // See https://github.com/rtfeldman/roc/issues/1600 + // + // The annotation variable is the generic/lifted/top-level annotation. + // It is connected to the variables of the function's body + // + // fn_var is the variable representing the type that we actually need for the + // function right here. + // + // For some reason, it matters that we unify with the original variable. Extracting + // that variable into a SolvedType and then introducing it again severs some + // connection that turns out to be important + match specialize_variable( + env, + self, + symbol, + layout_cache, + fn_var, + Default::default(), + partial_proc, + ) { Ok((proc, _ignore_layout)) => { // the `layout` is a function pointer, while `_ignore_layout` can be a // closure. We only specialize functions, storing this value with a closure @@ -881,17 +899,6 @@ pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)]; #[derive(Clone, Debug, PartialEq)] pub enum Stmt<'a> { Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>), - Invoke { - symbol: Symbol, - call: Call<'a>, - layout: Layout<'a>, - pass: &'a Stmt<'a>, - fail: &'a Stmt<'a>, - exception_id: ExceptionId, - }, - /// after cleanup, rethrow the exception object (stored in the exception id) - /// so it bubbles up - Resume(ExceptionId), Switch { /// This *must* stand for an integer, because Switch potentially compiles to a jump table. cond_symbol: Symbol, @@ -1375,45 +1382,11 @@ impl<'a> Stmt<'a> { .append(alloc.hardline()) .append(cont.to_doc(alloc)), - Invoke { - symbol, - call, - pass, - fail: Stmt::Resume(_), - .. - } => alloc - .text("let ") - .append(symbol_to_doc(alloc, *symbol)) - .append(" = ") - .append(call.to_doc(alloc)) - .append(";") - .append(alloc.hardline()) - .append(pass.to_doc(alloc)), - - Invoke { - symbol, - call, - pass, - fail, - .. - } => alloc - .text("invoke ") - .append(symbol_to_doc(alloc, *symbol)) - .append(" = ") - .append(call.to_doc(alloc)) - .append(" catch") - .append(alloc.hardline()) - .append(fail.to_doc(alloc).indent(4)) - .append(alloc.hardline()) - .append(pass.to_doc(alloc)), - Ret(symbol) => alloc .text("ret ") .append(symbol_to_doc(alloc, *symbol)) .append(";"), - Resume(_) => alloc.text("unreachable;"), - Switch { cond_symbol, branches, @@ -2028,7 +2001,9 @@ fn specialize_external<'a>( aliases.insert(*symbol, (name, top_level, layout)); } - RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("so far"), + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("so far"); + } } } @@ -2096,7 +2071,7 @@ fn specialize_external<'a>( match closure_layout.layout_for_member(proc_name) { ClosureRepresentation::Union { - tag_layout: field_layouts, + alphabetic_order_fields: field_layouts, union_layout, tag_id, .. @@ -2104,7 +2079,23 @@ fn specialize_external<'a>( debug_assert!(matches!(union_layout, UnionLayout::NonRecursive(_))); debug_assert_eq!(field_layouts.len(), captured.len()); - for (index, (symbol, _variable)) in captured.iter().enumerate() { + // captured variables are in symbol-alphabetic order, but now we want + // them ordered by their alignment requirements + let mut combined = Vec::from_iter_in( + captured.iter().map(|(x, _)| x).zip(field_layouts.iter()), + env.arena, + ); + + let ptr_bytes = env.ptr_bytes; + + combined.sort_by(|(_, layout1), (_, layout2)| { + let size1 = layout1.alignment_bytes(ptr_bytes); + let size2 = layout2.alignment_bytes(ptr_bytes); + + size2.cmp(&size1) + }); + + for (index, (symbol, layout)) in combined.iter().enumerate() { let expr = Expr::UnionAtIndex { tag_id, structure: Symbol::ARG_CLOSURE, @@ -2112,52 +2103,65 @@ fn specialize_external<'a>( union_layout, }; - let layout = field_layouts[index]; - specialized_body = Stmt::Let( - *symbol, + **symbol, expr, - layout, + **layout, env.arena.alloc(specialized_body), ); } } - ClosureRepresentation::Other(layout) => match layout { - Layout::Struct(field_layouts) => { - debug_assert_eq!( - captured.len(), - field_layouts.len(), - "{:?} captures {:?} but has layout {:?}", - proc_name, - &captured, - &field_layouts + ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => { + // captured variables are in symbol-alphabetic order, but now we want + // them ordered by their alignment requirements + let mut combined = Vec::from_iter_in( + captured.iter().map(|(x, _)| x).zip(field_layouts.iter()), + env.arena, + ); + + let ptr_bytes = env.ptr_bytes; + + combined.sort_by(|(_, layout1), (_, layout2)| { + let size1 = layout1.alignment_bytes(ptr_bytes); + let size2 = layout2.alignment_bytes(ptr_bytes); + + size2.cmp(&size1) + }); + + debug_assert_eq!( + captured.len(), + field_layouts.len(), + "{:?} captures {:?} but has layout {:?}", + proc_name, + &captured, + &field_layouts + ); + + for (index, (symbol, layout)) in combined.iter().enumerate() { + let expr = Expr::StructAtIndex { + index: index as _, + field_layouts, + structure: Symbol::ARG_CLOSURE, + }; + + specialized_body = Stmt::Let( + **symbol, + expr, + **layout, + env.arena.alloc(specialized_body), ); - - for (index, (symbol, _variable)) in captured.iter().enumerate() { - let expr = Expr::StructAtIndex { - index: index as _, - field_layouts, - structure: Symbol::ARG_CLOSURE, - }; - - let layout = field_layouts[index]; - - specialized_body = Stmt::Let( - *symbol, - expr, - layout, - env.arena.alloc(specialized_body), - ); - } - // let symbol = captured[0].0; - // - // substitute_in_exprs( - // env.arena, - // &mut specialized_body, - // symbol, - // Symbol::ARG_CLOSURE, - // ); } + // let symbol = captured[0].0; + // + // substitute_in_exprs( + // env.arena, + // &mut specialized_body, + // symbol, + // Symbol::ARG_CLOSURE, + // ); + } + + ClosureRepresentation::Other(layout) => match layout { Layout::Builtin(Builtin::Int1) => { // just ignore this value // IDEA don't pass this value in the future @@ -2458,28 +2462,72 @@ fn specialize_solved_type<'a>( host_exposed_aliases: BumpMap, partial_proc: PartialProc<'a>, ) -> Result, SpecializeFailure<'a>> { + specialize_variable_help( + env, + procs, + proc_name, + layout_cache, + |env| introduce_solved_type_to_subs(env, &solved_type), + host_exposed_aliases, + partial_proc, + ) +} + +fn specialize_variable<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + proc_name: Symbol, + layout_cache: &mut LayoutCache<'a>, + fn_var: Variable, + host_exposed_aliases: BumpMap, + partial_proc: PartialProc<'a>, +) -> Result, SpecializeFailure<'a>> { + specialize_variable_help( + env, + procs, + proc_name, + layout_cache, + |_| fn_var, + host_exposed_aliases, + partial_proc, + ) +} + +fn specialize_variable_help<'a, F>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + proc_name: Symbol, + layout_cache: &mut LayoutCache<'a>, + fn_var_thunk: F, + host_exposed_aliases: BumpMap, + partial_proc: PartialProc<'a>, +) -> Result, SpecializeFailure<'a>> +where + F: FnOnce(&mut Env<'a, '_>) -> Variable, +{ // add the specializations that other modules require of us use roc_solve::solve::instantiate_rigids; let snapshot = env.subs.snapshot(); let cache_snapshot = layout_cache.snapshot(); - let fn_var = introduce_solved_type_to_subs(env, &solved_type); + // important: evaluate after the snapshot has been created! + let fn_var = fn_var_thunk(env); // for debugging only - let attempted_layout = layout_cache - .from_var(env.arena, fn_var, env.subs) + let raw = layout_cache + .raw_from_var(env.arena, fn_var, env.subs) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); - let raw = match attempted_layout { - Layout::Closure(a, lambda_set, c) => { - if procs.module_thunks.contains(&proc_name) { + let raw = if procs.module_thunks.contains(&proc_name) { + match raw { + RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::ZeroArgumentThunk(lambda_set.runtime_representation()) - } else { - RawFunctionLayout::Function(a, lambda_set, c) } + _ => raw, } - _ => RawFunctionLayout::ZeroArgumentThunk(attempted_layout), + } else { + raw }; // make sure rigid variables in the annotation are converted to flex variables @@ -2506,12 +2554,12 @@ fn specialize_solved_type<'a>( match specialized { Ok(proc) => { // when successful, the layout after unification should be the layout before unification - debug_assert_eq!( - attempted_layout, - layout_cache - .from_var(env.arena, fn_var, env.subs) - .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)) - ); + // debug_assert_eq!( + // attempted_layout, + // layout_cache + // .from_var(env.arena, fn_var, env.subs) + // .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)) + // ); env.subs.rollback_to(snapshot); layout_cache.rollback_to(cache_snapshot); @@ -2541,39 +2589,20 @@ impl<'a> ProcLayout<'a> { let mut arguments = Vec::with_capacity_in(old_arguments.len(), arena); for old in old_arguments { - match old { - Layout::Closure(_, lambda_set, _) => { - let repr = lambda_set.runtime_representation(); - arguments.push(repr) - } - other => arguments.push(*other), - } + let other = old; + arguments.push(*other); } - let new_result = match result { - Layout::Closure(_, lambda_set, _) => lambda_set.runtime_representation(), - other => other, - }; + let other = result; + let new_result = other; ProcLayout { arguments: arguments.into_bump_slice(), result: new_result, } } - pub fn from_layout(arena: &'a Bump, layout: Layout<'a>) -> Self { - match layout { - Layout::Closure(arguments, lambda_set, result) => { - let arguments = lambda_set.extend_argument_list(arena, arguments); - ProcLayout::new(arena, arguments, *result) - } - _ => ProcLayout { - arguments: &[], - result: layout, - }, - } - } - fn from_raw(arena: &'a Bump, raw: RawFunctionLayout<'a>) -> Self { + pub fn from_raw(arena: &'a Bump, raw: RawFunctionLayout<'a>) -> Self { match raw { RawFunctionLayout::Function(arguments, lambda_set, result) => { let arguments = lambda_set.extend_argument_list(arena, arguments); @@ -2666,11 +2695,13 @@ macro_rules! match_on_closure_argument { let arg_layouts = top_level.arguments; let ret_layout = top_level.result; + match closure_data_layout { RawFunctionLayout::Function(_, lambda_set, _) => { lowlevel_match_on_lambda_set( $env, lambda_set, + $op, $closure_data_symbol, |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { call_type: CallType::HigherOrderLowLevel { @@ -2750,32 +2781,36 @@ pub fn with_hole<'a>( hole, ), - Num(var, num) => match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) { - IntOrFloat::SignedIntType(precision) => Stmt::Let( - assigned, - Expr::Literal(Literal::Int(num.into())), - Layout::Builtin(int_precision_to_builtin(precision)), - hole, - ), - IntOrFloat::UnsignedIntType(precision) => Stmt::Let( - assigned, - Expr::Literal(Literal::Int(num.into())), - Layout::Builtin(int_precision_to_builtin(precision)), - hole, - ), - IntOrFloat::BinaryFloatType(precision) => Stmt::Let( - assigned, - Expr::Literal(Literal::Float(num as f64)), - Layout::Builtin(float_precision_to_builtin(precision)), - hole, - ), - IntOrFloat::DecimalFloatType => Stmt::Let( - assigned, - Expr::Literal(Literal::Float(num as f64)), - Layout::Builtin(Builtin::Decimal), - hole, - ), - }, + Num(var, num) => { + // first figure out what kind of number this is + + match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) { + IntOrFloat::SignedIntType(precision) => Stmt::Let( + assigned, + Expr::Literal(Literal::Int(num.into())), + Layout::Builtin(int_precision_to_builtin(precision)), + hole, + ), + IntOrFloat::UnsignedIntType(precision) => Stmt::Let( + assigned, + Expr::Literal(Literal::Int(num.into())), + Layout::Builtin(int_precision_to_builtin(precision)), + hole, + ), + IntOrFloat::BinaryFloatType(precision) => Stmt::Let( + assigned, + Expr::Literal(Literal::Float(num as f64)), + Layout::Builtin(float_precision_to_builtin(precision)), + hole, + ), + IntOrFloat::DecimalFloatType => Stmt::Let( + assigned, + Expr::Literal(Literal::Float(num as f64)), + Layout::Builtin(Builtin::Decimal), + hole, + ), + } + } LetNonRec(def, cont, _) => { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let Closure { @@ -4054,10 +4089,27 @@ fn construct_closure_data<'a>( match lambda_set.layout_for_member(name) { ClosureRepresentation::Union { tag_id, - tag_layout: _, + alphabetic_order_fields: field_layouts, tag_name, union_layout, } => { + // captured variables are in symbol-alphabetic order, but now we want + // them ordered by their alignment requirements + let mut combined = + Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena); + + let ptr_bytes = env.ptr_bytes; + + combined.sort_by(|(_, layout1), (_, layout2)| { + let size1 = layout1.alignment_bytes(ptr_bytes); + let size2 = layout2.alignment_bytes(ptr_bytes); + + size2.cmp(&size1) + }); + + let symbols = + Vec::from_iter_in(combined.iter().map(|(a, _)| **a), env.arena).into_bump_slice(); + let expr = Expr::Tag { tag_id, tag_layout: union_layout, @@ -4072,9 +4124,33 @@ fn construct_closure_data<'a>( env.arena.alloc(hole), ) } - ClosureRepresentation::Other(Layout::Struct(field_layouts)) => { + ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => { debug_assert_eq!(field_layouts.len(), symbols.len()); + // captured variables are in symbol-alphabetic order, but now we want + // them ordered by their alignment requirements + let mut combined = + Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena); + + let ptr_bytes = env.ptr_bytes; + + combined.sort_by(|(_, layout1), (_, layout2)| { + let size1 = layout1.alignment_bytes(ptr_bytes); + let size2 = layout2.alignment_bytes(ptr_bytes); + + size2.cmp(&size1) + }); + + let symbols = + Vec::from_iter_in(combined.iter().map(|(a, _)| **a), env.arena).into_bump_slice(); + let field_layouts = + Vec::from_iter_in(combined.iter().map(|(_, b)| **b), env.arena).into_bump_slice(); + + debug_assert_eq!( + Layout::Struct(field_layouts), + lambda_set.runtime_representation() + ); + let expr = Expr::Struct(symbols); Stmt::Let(assigned, expr, lambda_set.runtime_representation(), hole) @@ -4346,7 +4422,7 @@ fn convert_tag_union<'a>( #[allow(clippy::too_many_arguments)] fn tag_union_to_function<'a>( env: &mut Env<'a, '_>, - argument_variables: SubsSlice, + argument_variables: VariableSubsSlice, return_variable: Variable, tag_name: TagName, proc_symbol: Symbol, @@ -4554,15 +4630,12 @@ pub fn from_can<'a>( arguments, }; - let exception_id = ExceptionId(env.unique_symbol()); - let rest = Stmt::Invoke { - symbol: env.unique_symbol(), - call, - layout: bool_layout, - pass: env.arena.alloc(rest), - fail: env.arena.alloc(Stmt::Resume(exception_id)), - exception_id, - }; + let rest = Stmt::Let( + env.unique_symbol(), + Expr::Call(call), + bool_layout, + env.arena.alloc(rest), + ); with_hole( env, @@ -4674,8 +4747,15 @@ pub fn from_can<'a>( CapturedSymbols::None } Err(_) => { - debug_assert!(captured_symbols.is_empty()); - CapturedSymbols::None + // just allow this. see https://github.com/rtfeldman/roc/issues/1585 + if captured_symbols.is_empty() { + CapturedSymbols::None + } else { + let mut temp = + Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } } }; @@ -5141,35 +5221,6 @@ fn substitute_in_stmt_help<'a>( None } } - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => { - let opt_call = substitute_in_call(arena, call, subs); - let opt_pass = substitute_in_stmt_help(arena, pass, subs); - let opt_fail = substitute_in_stmt_help(arena, fail, subs); - - if opt_pass.is_some() || opt_fail.is_some() | opt_call.is_some() { - let pass = opt_pass.unwrap_or(pass); - let fail = opt_fail.unwrap_or_else(|| *fail); - let call = opt_call.unwrap_or_else(|| call.clone()); - - Some(arena.alloc(Invoke { - symbol: *symbol, - call, - layout: *layout, - pass, - fail, - exception_id: *exception_id, - })) - } else { - None - } - } Join { id, parameters, @@ -5284,8 +5335,6 @@ fn substitute_in_stmt_help<'a>( } } - Resume(_) => None, - RuntimeError(_) => None, } } @@ -5947,12 +5996,21 @@ fn reuse_function_symbol<'a>( None => { match arg_var { Some(arg_var) if env.is_imported_symbol(original) => { - let layout = layout_cache - .from_var(env.arena, arg_var, env.subs) + let raw = layout_cache + .raw_from_var(env.arena, arg_var, env.subs) .expect("creating layout does not fail"); if procs.imported_module_thunks.contains(&original) { - let top_level = ProcLayout::new(env.arena, &[], layout); + let layout = match raw { + RawFunctionLayout::ZeroArgumentThunk(layout) => layout, + RawFunctionLayout::Function(_, lambda_set, _) => { + lambda_set.runtime_representation() + } + }; + + let raw = RawFunctionLayout::ZeroArgumentThunk(layout); + let top_level = ProcLayout::from_raw(env.arena, raw); + procs.insert_passed_by_name( env, arg_var, @@ -5963,7 +6021,7 @@ fn reuse_function_symbol<'a>( force_thunk(env, original, layout, symbol, env.arena.alloc(result)) } else { - let top_level = ProcLayout::from_layout(env.arena, layout); + let top_level = ProcLayout::from_raw(env.arena, raw); procs.insert_passed_by_name( env, arg_var, @@ -6006,7 +6064,7 @@ fn reuse_function_symbol<'a>( let captured = partial_proc.captured_symbols.clone(); match res_layout { - RawFunctionLayout::Function(argument_layouts, lambda_set, ret_layout) => { + RawFunctionLayout::Function(_, lambda_set, _) => { // define the function pointer let function_ptr_layout = ProcLayout::from_raw(env.arena, res_layout); @@ -6040,7 +6098,11 @@ fn reuse_function_symbol<'a>( ) } else if procs.module_thunks.contains(&original) { // this is a 0-argument thunk - let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout); + + // TODO suspicious + // let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout); + // panic!("suspicious"); + let layout = lambda_set.runtime_representation(); let top_level = ProcLayout::new(env.arena, &[], layout); procs.insert_passed_by_name( env, @@ -6151,69 +6213,46 @@ fn add_needed_external<'a>( existing.insert(name, solved_type); } -fn can_throw_exception(call: &Call) -> bool { - match call.call_type { - CallType::ByName { name, .. } => matches!( - name, - Symbol::NUM_ADD - | Symbol::NUM_SUB - | Symbol::NUM_MUL - | Symbol::NUM_DIV_FLOAT - | Symbol::NUM_ABS - | Symbol::NUM_NEG - ), - - CallType::Foreign { .. } => { - // calling foreign functions is very unsafe - true - } - - CallType::LowLevel { .. } => { - // lowlevel operations themselves don't throw - // TODO except for on allocation? - false - } - CallType::HigherOrderLowLevel { .. } => { - // TODO throwing is based on whether the HOF can throw - // or if there is (potentially) allocation in the lowlevel - false - } - } -} - -/// Symbol that links an Invoke with a Rethrow -/// we'll assign the exception object to this symbol -/// so we can later rethrow the exception -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct ExceptionId(Symbol); - -impl ExceptionId { - pub fn into_inner(self) -> Symbol { - self.0 - } -} - fn build_call<'a>( - env: &mut Env<'a, '_>, + _env: &mut Env<'a, '_>, call: Call<'a>, assigned: Symbol, return_layout: Layout<'a>, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - if can_throw_exception(&call) { - let id = ExceptionId(env.unique_symbol()); - let fail = env.arena.alloc(Stmt::Resume(id)); - Stmt::Invoke { - symbol: assigned, - call, - layout: return_layout, - fail, - pass: hole, - exception_id: id, - } - } else { - Stmt::Let(assigned, Expr::Call(call), return_layout, hole) - } + Stmt::Let(assigned, Expr::Call(call), return_layout, hole) +} + +/// See https://github.com/rtfeldman/roc/issues/1549 +/// +/// What happened is that a function has a type error, but the arguments are not processed. +/// That means specializations were missing. Normally that is not a problem, but because +/// of our closure strategy, internal functions can "leak". That's what happened here. +/// +/// The solution is to evaluate the arguments as normal, and only when calling the function give an error +fn evaluate_arguments_then_runtime_error<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + msg: String, + loc_args: std::vec::Vec<(Variable, Located)>, +) -> Stmt<'a> { + let arena = env.arena; + + // eventually we will throw this runtime error + let result = Stmt::RuntimeError(env.arena.alloc(msg)); + + // but, we also still evaluate and specialize the arguments to give better error messages + let arg_symbols = Vec::from_iter_in( + loc_args + .iter() + .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), + arena, + ) + .into_bump_slice(); + + let iter = loc_args.into_iter().rev().zip(arg_symbols.iter().rev()); + assign_to_symbols(env, procs, layout_cache, iter, result) } #[allow(clippy::too_many_arguments)] @@ -6234,14 +6273,16 @@ fn call_by_name<'a>( "Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})", var, proc_name, fn_var ); - Stmt::RuntimeError(env.arena.alloc(msg)) + + evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args) } Err(LayoutProblem::Erroneous) => { let msg = format!( "Hit an erroneous type when creating a layout for {:?}", proc_name ); - Stmt::RuntimeError(env.arena.alloc(msg)) + + evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args) } Ok(RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout)) => { if procs.module_thunks.contains(&proc_name) { @@ -6646,8 +6687,12 @@ fn call_by_name_module_thunk<'a>( match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) { - Ok((proc, layout)) => { - debug_assert!(layout.is_zero_argument_thunk()); + Ok((proc, raw_layout)) => { + debug_assert!( + raw_layout.is_zero_argument_thunk(), + "but actually {:?}", + raw_layout + ); let was_present = procs.specialized.remove(&(proc_name, top_level_layout)); @@ -7503,17 +7548,18 @@ pub fn num_argument_to_int_or_float( debug_assert!(args.len() == 1); // Recurse on the second argument - num_argument_to_int_or_float(subs, ptr_bytes, args[0].1, false) + let var = subs[args.variables().into_iter().next().unwrap()]; + num_argument_to_int_or_float(subs, ptr_bytes, var, false) } - Content::Alias(Symbol::NUM_I128, _, _) - | Content::Alias(Symbol::NUM_SIGNED128, _, _) + Content::Alias(Symbol::NUM_I128, _, _) + | Content::Alias(Symbol::NUM_SIGNED128, _, _) | Content::Alias(Symbol::NUM_AT_SIGNED128, _, _) => { IntOrFloat::SignedIntType(IntPrecision::I128) } - Content::Alias(Symbol::NUM_INT, _, _)// We default Integer to I64 - | Content::Alias(Symbol::NUM_I64, _, _) - | Content::Alias(Symbol::NUM_SIGNED64, _, _) + Content::Alias(Symbol::NUM_INT, _, _)// We default Integer to I64 + | Content::Alias(Symbol::NUM_I64, _, _) + | Content::Alias(Symbol::NUM_SIGNED64, _, _) | Content::Alias(Symbol::NUM_AT_SIGNED64, _, _) => { IntOrFloat::SignedIntType(IntPrecision::I64) } @@ -7561,7 +7607,8 @@ pub fn num_argument_to_int_or_float( debug_assert!(args.len() == 1); // Recurse on the second argument - num_argument_to_int_or_float(subs, ptr_bytes, args[0].1, true) + let var = subs[args.variables().into_iter().next().unwrap()]; + num_argument_to_int_or_float(subs, ptr_bytes, var, true) } Content::Alias(Symbol::NUM_FLOAT, _, _) // We default FloatingPoint to F64 | Content::Alias(Symbol::NUM_F64, _, _) @@ -7606,6 +7653,7 @@ pub fn num_argument_to_int_or_float( fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>( env: &mut Env<'a, '_>, lambda_set: LambdaSet<'a>, + op: LowLevel, closure_data_symbol: Symbol, to_lowlevel_call: ToLowLevelCall, return_layout: Layout<'a>, @@ -7645,19 +7693,29 @@ where env.arena.alloc(result), ) } - Layout::Struct(_) => { - let function_symbol = lambda_set.set[0].0; + Layout::Struct(_) => match lambda_set.set.get(0) { + Some((function_symbol, _)) => { + let call_spec_id = env.next_call_specialization_id(); + let call = to_lowlevel_call( + *function_symbol, + closure_data_symbol, + lambda_set.is_represented(), + call_spec_id, + ); - let call_spec_id = env.next_call_specialization_id(); - let call = to_lowlevel_call( - function_symbol, - closure_data_symbol, - lambda_set.is_represented(), - call_spec_id, - ); + build_call(env, call, assigned, return_layout, env.arena.alloc(hole)) + } + None => { + eprintln!( + "a function passed to `{:?}` LowLevel call has an empty lambda set! + The most likely reason is that some symbol you use is not in scope. + ", + op + ); - build_call(env, call, assigned, return_layout, env.arena.alloc(hole)) - } + hole.clone() + } + }, Layout::Builtin(Builtin::Int1) => { let closure_tag_id_symbol = closure_data_symbol; @@ -7869,6 +7927,12 @@ fn union_lambda_set_to_switch<'a>( assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { + // NOTE this can happen if there is a type error somewhere. Since the lambda set is empty, + // there is really nothing we can do here, so we just proceed with the hole itself and + // hope that the type error is communicated in a clear way elsewhere. + if lambda_set.is_empty() { + return hole.clone(); + } debug_assert!(!lambda_set.is_empty()); let join_point_id = JoinPointId(env.unique_symbol()); diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 3b308b6c2c..e0a96e6a32 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1,11 +1,13 @@ use crate::ir::Parens; use bumpalo::collections::Vec; use bumpalo::Bump; -use roc_collections::all::{default_hasher, MutMap, MutSet}; +use roc_collections::all::{default_hasher, MutMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; -use roc_types::subs::{Content, FlatType, Subs, Variable}; -use roc_types::types::RecordField; +use roc_types::subs::{ + Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice, +}; +use roc_types::types::{gather_fields_unsorted_iter, RecordField}; use std::collections::HashMap; use ven_pretty::{DocAllocator, DocBuilder}; @@ -27,10 +29,154 @@ pub enum RawFunctionLayout<'a> { ZeroArgumentThunk(Layout<'a>), } -impl RawFunctionLayout<'_> { +impl<'a> RawFunctionLayout<'a> { pub fn is_zero_argument_thunk(&self) -> bool { matches!(self, RawFunctionLayout::ZeroArgumentThunk(_)) } + + fn new_help<'b>( + env: &mut Env<'a, 'b>, + var: Variable, + content: Content, + ) -> Result { + use roc_types::subs::Content::*; + match content { + FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), + RecursionVar { structure, .. } => { + let structure_content = env.subs.get_content_without_compacting(structure); + Self::new_help(env, structure, structure_content.clone()) + } + Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), + + // Ints + Alias(Symbol::NUM_I128, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int128))) + } + Alias(Symbol::NUM_I64, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int64))) + } + Alias(Symbol::NUM_I32, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int32))) + } + Alias(Symbol::NUM_I16, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int16))) + } + Alias(Symbol::NUM_I8, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int8))) + } + + // I think unsigned and signed use the same layout + Alias(Symbol::NUM_U128, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int128))) + } + Alias(Symbol::NUM_U64, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int64))) + } + Alias(Symbol::NUM_U32, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int32))) + } + Alias(Symbol::NUM_U16, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int16))) + } + Alias(Symbol::NUM_U8, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Int8))) + } + + Alias(Symbol::NUM_NAT, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Usize))) + } + + // Floats + Alias(Symbol::NUM_F64, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Float64))) + } + Alias(Symbol::NUM_F32, args, _) => { + debug_assert!(args.is_empty()); + Ok(Self::ZeroArgumentThunk(Layout::Builtin(Builtin::Float32))) + } + + Alias(symbol, _, _) if symbol.is_builtin() => Ok(Self::ZeroArgumentThunk( + Layout::new_help(env, var, content)?, + )), + + Alias(_, _, var) => Self::from_var(env, var), + Error => Err(LayoutProblem::Erroneous), + } + } + + fn layout_from_flat_type( + env: &mut Env<'a, '_>, + flat_type: FlatType, + ) -> Result { + use roc_types::subs::FlatType::*; + + let arena = env.arena; + + match flat_type { + Func(args, closure_var, ret_var) => { + let mut fn_args = Vec::with_capacity_in(args.len(), arena); + + for index in args.into_iter() { + let arg_var = env.subs[index]; + fn_args.push(Layout::from_var(env, arg_var)?); + } + + let ret = Layout::from_var(env, ret_var)?; + + let fn_args = fn_args.into_bump_slice(); + let ret = arena.alloc(ret); + + let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var)?; + + Ok(Self::Function(fn_args, lambda_set, ret)) + } + TagUnion(tags, ext) if tags.is_newtype_wrapper(env.subs) => { + debug_assert!(ext_var_is_empty_tag_union(env.subs, ext)); + let slice_index = tags.variables().into_iter().next().unwrap(); + let slice = env.subs[slice_index]; + let var_index = slice.into_iter().next().unwrap(); + let var = env.subs[var_index]; + + Self::from_var(env, var) + } + Record(fields, ext) if fields.len() == 1 => { + debug_assert!(ext_var_is_empty_record(env.subs, ext)); + + let var_index = fields.iter_variables().next().unwrap(); + let var = env.subs[var_index]; + + Self::from_var(env, var) + } + _ => { + let layout = layout_from_flat_type(env, flat_type)?; + Ok(Self::ZeroArgumentThunk(layout)) + } + } + } + + /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. + /// Panics if given a FlexVar or RigidVar, since those should have been + /// monomorphized away already! + fn from_var(env: &mut Env<'a, '_>, var: Variable) -> Result { + if env.is_seen(var) { + unreachable!("The initial variable of a signature cannot be seen already") + } else { + let content = env.subs.get_content_without_compacting(var); + Self::new_help(env, var, content.clone()) + } + } } /// Types for code gen must be monomorphic. No type variables allowed! @@ -43,9 +189,6 @@ pub enum Layout<'a> { Struct(&'a [Layout<'a>]), Union(UnionLayout<'a>), RecursivePointer, - - /// A function. The types of its arguments, then the type of its return value. - Closure(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -257,11 +400,16 @@ pub struct LambdaSet<'a> { pub enum ClosureRepresentation<'a> { /// the closure is represented as a union. Includes the tag ID! Union { - tag_layout: &'a [Layout<'a>], + alphabetic_order_fields: &'a [Layout<'a>], tag_name: TagName, tag_id: u8, union_layout: UnionLayout<'a>, }, + /// The closure is represented as a struct. The layouts are sorted + /// alphabetically by the identifier that is captured. + /// + /// We MUST sort these according to their stack size before code gen! + AlphabeticOrderStruct(&'a [Layout<'a>]), /// the representation is anything but a union Other(Layout<'a>), } @@ -290,16 +438,19 @@ impl<'a> LambdaSet<'a> { // here we rely on the fact that a union in a closure would be stored in a one-element record. // a closure representation that is itself union must be a of the shape `Closure1 ... | Closure2 ...` match union { - UnionLayout::NonRecursive(tags) => { - let index = self + UnionLayout::NonRecursive(_) => { + // get the fields from the set, where they are sorted in alphabetic order + // (and not yet sorted by their alignment) + let (index, (_, fields)) = self .set .iter() - .position(|(s, _)| *s == function_symbol) + .enumerate() + .find(|(_, (s, _))| *s == function_symbol) .unwrap(); ClosureRepresentation::Union { tag_id: index as u8, - tag_layout: tags[index], + alphabetic_order_fields: fields, tag_name: TagName::Closure(function_symbol), union_layout: *union, } @@ -316,6 +467,17 @@ impl<'a> LambdaSet<'a> { } => todo!("recursive closures"), } } + Layout::Struct(_) => { + // get the fields from the set, where they are sorted in alphabetic order + // (and not yet sorted by their alignment) + let (_, fields) = self + .set + .iter() + .find(|(s, _)| *s == function_symbol) + .unwrap(); + + ClosureRepresentation::AlphabeticOrderStruct(fields) + } _ => ClosureRepresentation::Other(*self.representation), } } @@ -366,7 +528,7 @@ impl<'a> LambdaSet<'a> { let mut env = Env { arena, subs, - seen: MutSet::default(), + seen: Vec::new_in(arena), }; for (tag_name, variables) in tags.iter() { @@ -392,10 +554,10 @@ impl<'a> LambdaSet<'a> { } Ok(()) | Err((_, Content::FlexVar(_))) => { - // TODO hack for builting functions. + // this can happen when there is a type error somewhere Ok(LambdaSet { set: &[], - representation: arena.alloc(Layout::Struct(&[])), + representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))), }) } _ => panic!("called LambdaSet.from_var on invalid input"), @@ -413,9 +575,10 @@ impl<'a> LambdaSet<'a> { use UnionVariant::*; match variant { Never => Layout::Union(UnionLayout::NonRecursive(&[])), - Unit | UnitWithArguments => Layout::Struct(&[]), - BoolUnion { .. } => Layout::Builtin(Builtin::Int1), - ByteUnion(_) => Layout::Builtin(Builtin::Int8), + Unit | UnitWithArguments | BoolUnion { .. } | ByteUnion(_) => { + // no useful information to store + Layout::Struct(&[]) + } Newtype { arguments: layouts, .. } => Layout::Struct(layouts.into_bump_slice()), @@ -486,7 +649,7 @@ pub enum Builtin<'a> { pub struct Env<'a, 'b> { arena: &'a Bump, - seen: MutSet, + seen: Vec<'a, Variable>, subs: &'b Subs, } @@ -494,19 +657,24 @@ impl<'a, 'b> Env<'a, 'b> { fn is_seen(&self, var: Variable) -> bool { let var = self.subs.get_root_key_without_compacting(var); - self.seen.contains(&var) + self.seen.iter().rev().any(|x| x == &var) } - fn insert_seen(&mut self, var: Variable) -> bool { + fn insert_seen(&mut self, var: Variable) { let var = self.subs.get_root_key_without_compacting(var); - self.seen.insert(var) + self.seen.push(var); } fn remove_seen(&mut self, var: Variable) -> bool { let var = self.subs.get_root_key_without_compacting(var); - self.seen.remove(&var) + if let Some(index) = self.seen.iter().rposition(|x| x == &var) { + self.seen.remove(index); + true + } else { + false + } } } @@ -579,6 +747,12 @@ impl<'a> Layout<'a> { Ok(Layout::Builtin(Builtin::Float32)) } + // Nat + Alias(Symbol::NUM_NAT, args, _) => { + debug_assert!(args.is_empty()); + Ok(Layout::Builtin(Builtin::Usize)) + } + Alias(_, _, var) => Self::from_var(env, var), Error => Err(LayoutProblem::Erroneous), } @@ -620,7 +794,6 @@ impl<'a> Layout<'a> { } } } - Closure(_, closure_layout, _) => closure_layout.safe_to_memcpy(), RecursivePointer => { // We cannot memcpy pointers, because then we would have the same pointer in multiple places! false @@ -636,6 +809,17 @@ impl<'a> Layout<'a> { } pub fn stack_size(&self, pointer_size: u32) -> u32 { + let width = self.stack_size_without_alignment(pointer_size); + let alignment = self.alignment_bytes(pointer_size); + + if alignment != 0 && width % alignment > 0 { + width + alignment - (width % alignment) + } else { + width + } + } + + fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 { use Layout::*; match self { @@ -674,7 +858,6 @@ impl<'a> Layout<'a> { | NonNullableUnwrapped(_) => pointer_size, } } - Closure(_, lambda_set, _) => lambda_set.stack_size(pointer_size), RecursivePointer => pointer_size, } } @@ -706,9 +889,6 @@ impl<'a> Layout<'a> { } Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size), Layout::RecursivePointer => pointer_size, - Layout::Closure(_, captured, _) => { - pointer_size.max(captured.alignment_bytes(pointer_size)) - } } } @@ -759,8 +939,6 @@ impl<'a> Layout<'a> { } } RecursivePointer => true, - - Closure(_, closure_layout, _) => closure_layout.contains_refcounted(), } } @@ -784,20 +962,6 @@ impl<'a> Layout<'a> { } Union(union_layout) => union_layout.to_doc(alloc, parens), RecursivePointer => alloc.text("*self"), - Closure(args, closure_layout, result) => { - let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction)); - - let bom = closure_layout - .representation - .to_doc(alloc, Parens::NotNeeded); - - alloc - .intersperse(args_doc, ", ") - .append(alloc.text(" {| ")) - .append(bom) - .append(" |} -> ") - .append(result.to_doc(alloc, Parens::InFunction)) - } } } } @@ -810,7 +974,7 @@ impl<'a> Layout<'a> { /// But if we're careful when to invalidate certain keys, we still get some benefit #[derive(Default, Debug)] pub struct LayoutCache<'a> { - layouts: ven_ena::unify::UnificationTable>>, + _marker: std::marker::PhantomData<&'a u8>, } #[derive(Debug, Clone)] @@ -820,38 +984,7 @@ pub enum CachedLayout<'a> { Problem(LayoutProblem), } -/// Must wrap so we can define a specific UnifyKey instance -/// PhantomData so we can store the 'a lifetime, which is needed to implement the UnifyKey trait, -/// specifically so we can use `type Value = CachedLayout<'a>` -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct CachedVariable<'a>(Variable, std::marker::PhantomData<&'a ()>); - -impl<'a> CachedVariable<'a> { - fn new(var: Variable) -> Self { - CachedVariable(var, std::marker::PhantomData) - } -} - -impl<'a> ven_ena::unify::UnifyKey for CachedVariable<'a> { - type Value = CachedLayout<'a>; - - fn index(&self) -> u32 { - self.0.index() - } - - fn from_index(index: u32) -> Self { - CachedVariable(Variable::from_index(index), std::marker::PhantomData) - } - - fn tag() -> &'static str { - "CachedVariable" - } -} - impl<'a> LayoutCache<'a> { - /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. - /// Panics if given a FlexVar or RigidVar, since those should have been - /// monomorphized away already! pub fn from_var( &mut self, arena: &'a Bump, @@ -861,39 +994,13 @@ impl<'a> LayoutCache<'a> { // Store things according to the root Variable, to avoid duplicate work. let var = subs.get_root_key_without_compacting(var); - let cached_var = CachedVariable::new(var); + let mut env = Env { + arena, + subs, + seen: Vec::new_in(arena), + }; - self.expand_to_fit(cached_var); - - use CachedLayout::*; - match self.layouts.probe_value(cached_var) { - Cached(result) => Ok(result), - Problem(problem) => Err(problem), - NotCached => { - let mut env = Env { - arena, - subs, - seen: MutSet::default(), - }; - - let result = Layout::from_var(&mut env, var); - - // Don't actually cache. The layout cache is very hard to get right in the presence - // of specialization, it's turned of for now so an invalid cache is never the cause - // of a problem - if false { - let cached_layout = match &result { - Ok(layout) => Cached(*layout), - Err(problem) => Problem(problem.clone()), - }; - - self.layouts - .update_value(cached_var, |existing| existing.value = cached_layout); - } - - result - } - } + Layout::from_var(&mut env, var) } pub fn raw_from_var( @@ -905,55 +1012,25 @@ impl<'a> LayoutCache<'a> { // Store things according to the root Variable, to avoid duplicate work. let var = subs.get_root_key_without_compacting(var); - let cached_var = CachedVariable::new(var); + let mut env = Env { + arena, + subs, + seen: Vec::new_in(arena), + }; - self.expand_to_fit(cached_var); - - use CachedLayout::*; - match self.layouts.probe_value(cached_var) { - Problem(problem) => Err(problem), - Cached(_) | NotCached => { - let mut env = Env { - arena, - subs, - seen: MutSet::default(), - }; - - Layout::from_var(&mut env, var).map(|l| match l { - Layout::Closure(a, b, c) => RawFunctionLayout::Function(a, b, c), - other => RawFunctionLayout::ZeroArgumentThunk(other), - }) - } - } + RawFunctionLayout::from_var(&mut env, var) } - fn expand_to_fit(&mut self, var: CachedVariable<'a>) { - use ven_ena::unify::UnifyKey; - - let required = (var.index() as isize) - (self.layouts.len() as isize) + 1; - if required > 0 { - self.layouts.reserve(required as usize); - - for _ in 0..required { - self.layouts.new_key(CachedLayout::NotCached); - } - } + pub fn snapshot(&mut self) -> SnapshotKeyPlaceholder { + SnapshotKeyPlaceholder } - pub fn snapshot( - &mut self, - ) -> ven_ena::unify::Snapshot>> { - self.layouts.snapshot() - } - - pub fn rollback_to( - &mut self, - snapshot: ven_ena::unify::Snapshot>>, - ) { - self.layouts.rollback_to(snapshot) - } + pub fn rollback_to(&mut self, _snapshot: SnapshotKeyPlaceholder) {} } +// placeholder for the type ven_ena::unify::Snapshot>> +pub struct SnapshotKeyPlaceholder; + impl<'a> Builtin<'a> { const I128_SIZE: u32 = std::mem::size_of::() as u32; const I64_SIZE: u32 = std::mem::size_of::() as u32; @@ -1108,6 +1185,8 @@ fn layout_from_flat_type<'a>( match flat_type { Apply(symbol, args) => { + let args = Vec::from_iter_in(args.into_iter().map(|index| subs[index]), arena); + match symbol { // Ints Symbol::NUM_NAT => { @@ -1175,8 +1254,8 @@ fn layout_from_flat_type<'a>( // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer debug_assert_eq!(args.len(), 1); - let var = args.first().unwrap(); - let content = subs.get_content_without_compacting(*var); + let var = args[0]; + let content = subs.get_content_without_compacting(var); layout_from_num_content(content) } @@ -1186,26 +1265,17 @@ fn layout_from_flat_type<'a>( Symbol::DICT_DICT => dict_layout_from_key_value(env, args[0], args[1]), Symbol::SET_SET => dict_layout_from_key_value(env, args[0], Variable::EMPTY_RECORD), _ => { - panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args)); + panic!( + "TODO layout_from_flat_type for Apply({:?}, {:?})", + symbol, args + ); } } } - Func(args, closure_var, ret_var) => { - let mut fn_args = Vec::with_capacity_in(args.len(), arena); - - for index in args.into_iter() { - let arg_var = env.subs[index]; - fn_args.push(Layout::from_var(env, arg_var)?); - } - - let ret = Layout::from_var(env, ret_var)?; - - let fn_args = fn_args.into_bump_slice(); - let ret = arena.alloc(ret); - + Func(_, closure_var, _) => { let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var)?; - Ok(Layout::Closure(fn_args, lambda_set, ret)) + Ok(lambda_set.runtime_representation()) } Record(fields, ext_var) => { // extract any values from the ext_var @@ -1255,8 +1325,7 @@ fn layout_from_flat_type<'a>( FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - let mut tags = MutMap::default(); - tags.insert(*tag_name, vec![]); + let tags = UnionTags::from_tag_name_index(tag_name); Ok(layout_from_tag_union(arena, tags, subs)) } @@ -1274,9 +1343,7 @@ fn layout_from_flat_type<'a>( let rec_var = subs.get_root_key_without_compacting(rec_var); let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - // VERY IMPORTANT: sort the tags - let mut tags_vec: std::vec::Vec<_> = tags.into_iter().collect(); - tags_vec.sort(); + let tags_vec = cheap_sort_tags(arena, tags, subs); let mut nullable = None; @@ -1298,7 +1365,8 @@ fn layout_from_flat_type<'a>( let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena); - for var in variables { + for var_index in variables { + let var = subs[var_index]; // TODO does this cause problems with mutually recursive unions? if rec_var == subs.get_root_key_without_compacting(var) { tag_layout.push(Layout::RecursivePointer); @@ -1358,26 +1426,27 @@ pub fn sort_record_fields<'a>( var: Variable, subs: &Subs, ) -> Vec<'a, (Lowercase, Variable, Result, Layout<'a>>)> { - let mut fields_map = MutMap::default(); - let mut env = Env { arena, subs, - seen: MutSet::default(), + seen: Vec::new_in(arena), }; - match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) { - Ok(()) | Err((_, Content::FlexVar(_))) => sort_record_fields_help(&mut env, fields_map), - Err(other) => panic!("invalid content in record variable: {:?}", other), - } + let (it, _) = gather_fields_unsorted_iter(subs, RecordFields::empty(), var); + + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + sort_record_fields_help(&mut env, it) } fn sort_record_fields_help<'a>( env: &mut Env<'a, '_>, - fields_map: MutMap>, + fields_map: impl Iterator)>, ) -> Vec<'a, (Lowercase, Variable, Result, Layout<'a>>)> { // Sort the fields by label - let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), env.arena); + let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena); for (label, field) in fields_map { let var = match field { @@ -1392,10 +1461,7 @@ fn sort_record_fields_help<'a>( let layout = Layout::from_var(env, var).expect("invalid layout from var"); - // Drop any zero-sized fields like {} - if !layout.is_dropped_because_empty() { - sorted_fields.push((label, var, Ok(layout))); - } + sorted_fields.push((label, var, Ok(layout))); } sorted_fields.sort_by( @@ -1580,6 +1646,222 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { ) } +fn union_sorted_tags_help_new<'a>( + arena: &'a Bump, + mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>, + opt_rec_var: Option, + subs: &Subs, +) -> UnionVariant<'a> { + // sort up front; make sure the ordering stays intact! + tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + + let mut env = Env { + arena, + subs, + seen: Vec::new_in(arena), + }; + + match tags_vec.len() { + 0 => { + // trying to instantiate a type with no values + UnionVariant::Never + } + 1 => { + let (tag_name, arguments) = tags_vec.remove(0); + let tag_name = tag_name.clone(); + + // just one tag in the union (but with arguments) can be a struct + let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); + let mut contains_zero_sized = false; + + // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int + match tag_name { + TagName::Private(Symbol::NUM_AT_NUM) => { + let var = subs[arguments.into_iter().next().unwrap()]; + layouts.push(unwrap_num_tag(subs, var).expect("invalid num layout")); + } + _ => { + for var_index in arguments { + let var = subs[var_index]; + match Layout::from_var(&mut env, var) { + Ok(layout) => { + // Drop any zero-sized arguments like {} + if !layout.is_dropped_because_empty() { + layouts.push(layout); + } else { + contains_zero_sized = true; + } + } + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty struct + layouts.push(Layout::Struct(&[])) + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + } + } + } + } + } + + layouts.sort_by(|layout1, layout2| { + let ptr_bytes = 8; + + let size1 = layout1.alignment_bytes(ptr_bytes); + let size2 = layout2.alignment_bytes(ptr_bytes); + + size2.cmp(&size1) + }); + + if layouts.is_empty() { + if contains_zero_sized { + UnionVariant::UnitWithArguments + } else { + UnionVariant::Unit + } + } else if opt_rec_var.is_some() { + UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped { + tag_name, + fields: layouts.into_bump_slice(), + }) + } else { + UnionVariant::Newtype { + tag_name, + arguments: layouts, + } + } + } + num_tags => { + // default path + let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); + let mut has_any_arguments = false; + + let mut nullable: Option<(i64, TagName)> = None; + + // only recursive tag unions can be nullable + let is_recursive = opt_rec_var.is_some(); + if is_recursive && GENERATE_NULLABLE { + for (index, (name, variables)) in tags_vec.iter().enumerate() { + if variables.is_empty() { + nullable = Some((index as i64, (*name).clone())); + break; + } + } + } + + for (index, (tag_name, arguments)) in tags_vec.into_iter().enumerate() { + // reserve space for the tag discriminant + if matches!(nullable, Some((i, _)) if i as usize == index) { + debug_assert!(arguments.is_empty()); + continue; + } + + let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); + + for var_index in arguments { + let var = subs[var_index]; + match Layout::from_var(&mut env, var) { + Ok(layout) => { + has_any_arguments = true; + + // make sure to not unroll recursive types! + let self_recursion = opt_rec_var.is_some() + && subs.get_root_key_without_compacting(var) + == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) + && is_recursive_tag_union(&layout); + + if self_recursion { + arg_layouts.push(Layout::RecursivePointer); + } else { + arg_layouts.push(layout); + } + } + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty struct + arg_layouts.push(Layout::Struct(&[])); + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + } + } + } + + arg_layouts.sort_by(|layout1, layout2| { + let ptr_bytes = 8; + + let size1 = layout1.alignment_bytes(ptr_bytes); + let size2 = layout2.alignment_bytes(ptr_bytes); + + size2.cmp(&size1) + }); + + answer.push((tag_name.clone(), arg_layouts.into_bump_slice())); + } + + match num_tags { + 2 if !has_any_arguments => { + // type can be stored in a boolean + + // tags_vec is sorted, and answer is sorted the same way + let ttrue = answer.remove(1).0; + let ffalse = answer.remove(0).0; + + UnionVariant::BoolUnion { ffalse, ttrue } + } + 3..=MAX_ENUM_SIZE if !has_any_arguments => { + // type can be stored in a byte + // needs the sorted tag names to determine the tag_id + let mut tag_names = Vec::with_capacity_in(answer.len(), arena); + + for (tag_name, _) in answer { + tag_names.push(tag_name); + } + + UnionVariant::ByteUnion(tag_names) + } + _ => { + let variant = if let Some((nullable_id, nullable_name)) = nullable { + if answer.len() == 1 { + let (other_name, other_arguments) = answer.drain(..).next().unwrap(); + let nullable_id = nullable_id != 0; + + WrappedVariant::NullableUnwrapped { + nullable_id, + nullable_name, + other_name, + other_fields: other_arguments, + } + } else { + WrappedVariant::NullableWrapped { + nullable_id, + nullable_name, + sorted_tag_layouts: answer, + } + } + } else if is_recursive { + debug_assert!(answer.len() > 1); + WrappedVariant::Recursive { + sorted_tag_layouts: answer, + } + } else { + WrappedVariant::NonRecursive { + sorted_tag_layouts: answer, + } + }; + + UnionVariant::Wrapped(variant) + } + } + } + } +} + pub fn union_sorted_tags_help<'a>( arena: &'a Bump, mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec)>, @@ -1587,12 +1869,12 @@ pub fn union_sorted_tags_help<'a>( subs: &Subs, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - tags_vec.sort(); + tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); let mut env = Env { arena, subs, - seen: MutSet::default(), + seen: Vec::new_in(arena), }; match tags_vec.len() { @@ -1731,7 +2013,7 @@ pub fn union_sorted_tags_help<'a>( size2.cmp(&size1) }); - answer.push((tag_name, arg_layouts.into_bump_slice())); + answer.push((tag_name.clone(), arg_layouts.into_bump_slice())); } match num_tags { @@ -1750,7 +2032,7 @@ pub fn union_sorted_tags_help<'a>( let mut tag_names = Vec::with_capacity_in(answer.len(), arena); for (tag_name, _) in answer { - tag_names.push(tag_name); + tag_names.push(tag_name.clone()); } UnionVariant::ByteUnion(tag_names) @@ -1792,26 +2074,81 @@ pub fn union_sorted_tags_help<'a>( } } -pub fn layout_from_tag_union<'a>( +fn cheap_sort_tags<'a, 'b>( arena: &'a Bump, - tags: MutMap>, - subs: &Subs, -) -> Layout<'a> { + tags: UnionTags, + subs: &'b Subs, +) -> Vec<'a, (&'b TagName, VariableSubsSlice)> { + let mut tags_vec = Vec::with_capacity_in(tags.len(), arena); + + for (tag_index, index) in tags.iter_all() { + let tag = &subs[tag_index]; + let slice = subs[index]; + + tags_vec.push((tag, slice)); + } + + tags_vec +} + +fn layout_from_newtype<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Layout<'a> { + debug_assert!(tags.is_newtype_wrapper(subs)); + + let slice_index = tags.variables().into_iter().next().unwrap(); + let slice = subs[slice_index]; + let var_index = slice.into_iter().next().unwrap(); + let var = subs[var_index]; + + let tag_name_index = tags.tag_names().into_iter().next().unwrap(); + let tag_name = &subs[tag_name_index]; + + if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { + unwrap_num_tag(subs, var).expect("invalid Num argument") + } else { + let mut env = Env { + arena, + subs, + seen: Vec::new_in(arena), + }; + + match Layout::from_var(&mut env, var) { + Ok(layout) => layout, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty struct + Layout::Struct(&[]) + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + todo!() + } + } + } +} + +fn layout_from_tag_union<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> Layout<'a> { use UnionVariant::*; - let tags_vec: std::vec::Vec<_> = tags.into_iter().collect(); + if tags.is_newtype_wrapper(subs) { + return layout_from_newtype(arena, tags, subs); + } + + let tags_vec = cheap_sort_tags(arena, tags, subs); match tags_vec.get(0) { - Some((tag_name, arguments)) if *tag_name == TagName::Private(Symbol::NUM_AT_NUM) => { + Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { debug_assert_eq!(arguments.len(), 1); - let var = arguments.iter().next().unwrap(); + let var_index = arguments.into_iter().next().unwrap(); + let var = subs[var_index]; - unwrap_num_tag(subs, *var).expect("invalid Num argument") + unwrap_num_tag(subs, var).expect("invalid Num argument") } _ => { let opt_rec_var = None; - let variant = union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs); + let variant = union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs); match variant { Never => Layout::Union(UnionLayout::NonRecursive(&[])), @@ -1819,14 +2156,16 @@ pub fn layout_from_tag_union<'a>( BoolUnion { .. } => Layout::Builtin(Builtin::Int1), ByteUnion(_) => Layout::Builtin(Builtin::Int8), Newtype { - arguments: mut field_layouts, + arguments: field_layouts, .. } => { - if field_layouts.len() == 1 { - field_layouts.pop().unwrap() + let answer1 = if field_layouts.len() == 1 { + field_layouts[0] } else { Layout::Struct(field_layouts.into_bump_slice()) - } + }; + + answer1 } Wrapped(variant) => { use WrappedVariant::*; @@ -1874,6 +2213,20 @@ pub fn layout_from_tag_union<'a>( } } +#[cfg(debug_assertions)] +fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { + // the ext_var is empty + let fields = roc_types::types::gather_fields(subs, RecordFields::empty(), ext_var); + + fields.fields.is_empty() +} + +#[cfg(not(debug_assertions))] +fn ext_var_is_empty_record(_subs: &Subs, _ext_var: Variable) -> bool { + // This should only ever be used in debug_assert! macros + unreachable!(); +} + #[cfg(debug_assertions)] fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty @@ -1948,7 +2301,7 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutPr Content::Alias(Symbol::NUM_INTEGER, args, _) => { debug_assert!(args.len() == 1); - let (_, precision_var) = args[0]; + let precision_var = subs[args.variables().into_iter().next().unwrap()]; let precision = subs.get_content_without_compacting(precision_var); @@ -1983,7 +2336,7 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutPr Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { debug_assert!(args.len() == 1); - let (_, precision_var) = args[0]; + let precision_var = subs[args.variables().into_iter().next().unwrap()]; let precision = subs.get_content_without_compacting(precision_var); diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 10bbd05939..41fdc63c63 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -135,28 +135,6 @@ fn function_s<'a, 'i>( arena.alloc(new_join) } - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => { - let new_pass = function_s(env, w, c, pass); - let new_fail = function_s(env, w, c, fail); - - let new_invoke = Invoke { - symbol: *symbol, - call: call.clone(), - layout: *layout, - pass: new_pass, - fail: new_fail, - exception_id: *exception_id, - }; - - arena.alloc(new_invoke) - } Switch { cond_symbol, cond_layout, @@ -195,7 +173,7 @@ fn function_s<'a, 'i>( arena.alloc(new_refcounting) } } - Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, + Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, } } @@ -318,37 +296,6 @@ fn function_d_main<'a, 'i>( } } } - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => { - if has_live_var(&env.jp_live_vars, stmt, x) { - let new_pass = { - let temp = function_d_main(env, x, c, pass); - function_d_finalize(env, x, c, temp) - }; - let new_fail = { - let temp = function_d_main(env, x, c, fail); - function_d_finalize(env, x, c, temp) - }; - let new_switch = Invoke { - symbol: *symbol, - call: call.clone(), - layout: *layout, - pass: new_pass, - fail: new_fail, - exception_id: *exception_id, - }; - - (arena.alloc(new_switch), true) - } else { - (stmt, false) - } - } Switch { cond_symbol, cond_layout, @@ -433,9 +380,7 @@ fn function_d_main<'a, 'i>( (arena.alloc(new_join), found) } - Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => { - (stmt, has_live_var(&env.jp_live_vars, stmt, x)) - } + Ret(_) | Jump(_, _) | RuntimeError(_) => (stmt, has_live_var(&env.jp_live_vars, stmt, x)), } } @@ -550,36 +495,13 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> arena.alloc(Let(*symbol, expr.clone(), *layout, b)) } - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => { - let branch_info = BranchInfo::None; - let new_pass = function_r_branch_body(env, &branch_info, pass); - let new_fail = function_r_branch_body(env, &branch_info, fail); - - let invoke = Invoke { - symbol: *symbol, - call: call.clone(), - layout: *layout, - pass: new_pass, - fail: new_fail, - exception_id: *exception_id, - }; - - arena.alloc(invoke) - } Refcounting(modify_rc, continuation) => { let b = function_r(env, continuation); arena.alloc(Refcounting(*modify_rc, b)) } - Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => { + Ret(_) | Jump(_, _) | RuntimeError(_) => { // terminals stmt } @@ -594,19 +516,6 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym debug_assert_ne!(*s, needle); has_live_var_expr(e, needle) || has_live_var(jp_live_vars, c, needle) } - Invoke { - symbol, - call, - pass, - fail, - .. - } => { - debug_assert_ne!(*symbol, needle); - - has_live_var_call(call, needle) - || has_live_var(jp_live_vars, pass, needle) - || has_live_var(jp_live_vars, fail, needle) - } Switch { cond_symbol, .. } if *cond_symbol == needle => true, Switch { branches, @@ -647,7 +556,7 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym Jump(id, arguments) => { arguments.iter().any(|s| *s == needle) || jp_live_vars[id].contains(&needle) } - Resume(_) | RuntimeError(_) => false, + RuntimeError(_) => false, } } diff --git a/compiler/mono/src/tail_recursion.rs b/compiler/mono/src/tail_recursion.rs index f0efd1736d..d0a75b0c2a 100644 --- a/compiler/mono/src/tail_recursion.rs +++ b/compiler/mono/src/tail_recursion.rs @@ -92,28 +92,6 @@ fn insert_jumps<'a>( Some(arena.alloc(jump)) } - Invoke { - symbol, - call: - crate::ir::Call { - call_type: CallType::ByName { name: fsym, .. }, - arguments, - .. - }, - fail, - pass: Stmt::Ret(rsym), - exception_id, - .. - } if needle == *fsym && symbol == rsym => { - debug_assert_eq!(fail, &&Stmt::Resume(*exception_id)); - - // replace the call and return with a jump - - let jump = Stmt::Jump(goal_id, arguments); - - Some(arena.alloc(jump)) - } - Let(symbol, expr, layout, cont) => { let opt_cont = insert_jumps(arena, cont, goal_id, needle); @@ -126,36 +104,6 @@ fn insert_jumps<'a>( } } - Invoke { - symbol, - call, - fail, - pass, - layout, - exception_id, - } => { - let opt_pass = insert_jumps(arena, pass, goal_id, needle); - let opt_fail = insert_jumps(arena, fail, goal_id, needle); - - if opt_pass.is_some() || opt_fail.is_some() { - let pass = opt_pass.unwrap_or(pass); - let fail = opt_fail.unwrap_or(fail); - - let stmt = Invoke { - symbol: *symbol, - call: call.clone(), - layout: *layout, - pass, - fail, - exception_id: *exception_id, - }; - - Some(arena.alloc(stmt)) - } else { - None - } - } - Join { id, parameters, @@ -241,7 +189,6 @@ fn insert_jumps<'a>( None => None, }, - Resume(_) => None, Ret(_) => None, Jump(_, _) => None, RuntimeError(_) => None, diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index 525818e554..ebf781e9d4 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -59,8 +59,16 @@ pub fn make_solved_types( args.push((name.clone(), SolvedType::new(solved_subs, *var))); } + let mut lambda_set_variables = Vec::with_capacity(alias.lambda_set_variables.len()); + for set in alias.lambda_set_variables.iter() { + lambda_set_variables.push(roc_types::solved_types::SolvedLambdaSet( + SolvedType::from_type(solved_subs, &set.0), + )); + } + let solved_type = SolvedType::from_type(solved_subs, &alias.typ); - let solved_alias = SolvedType::Alias(*symbol, args, Box::new(solved_type)); + let solved_alias = + SolvedType::Alias(*symbol, args, lambda_set_variables, Box::new(solved_type)); solved_types.insert(*symbol, solved_alias); } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 856771a2d3..efa09d4dde 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,14 +1,16 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{default_hasher, MutMap}; +use roc_collections::all::MutMap; +use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::solved_types::Solved; use roc_types::subs::{ - Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, SubsSlice, Variable, + AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, + SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, Category, ErrorType, PatternCategory}; +use roc_types::types::{gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory}; use roc_unify::unify::unify; use roc_unify::unify::Unified::*; @@ -643,12 +645,14 @@ fn type_to_variable( match typ { Variable(var) => *var, Apply(symbol, args) => { - let mut arg_vars = Vec::with_capacity(args.len()); + let mut new_arg_vars = Vec::with_capacity(args.len()); for arg in args { - arg_vars.push(type_to_variable(subs, rank, pools, cached, arg)) + let var = type_to_variable(subs, rank, pools, cached, arg); + new_arg_vars.push(var); } + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); let flat_type = FlatType::Apply(*symbol, arg_vars); let content = Content::Structure(flat_type); @@ -666,11 +670,7 @@ fn type_to_variable( new_arg_vars.push(var); } - let start = subs.variables.len() as u32; - let length = arg_vars.len() as u16; - let arg_vars = SubsSlice::new(start, length); - - subs.variables.extend(new_arg_vars); + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); let ret_var = type_to_variable(subs, rank, pools, cached, ret_type); let closure_var = type_to_variable(subs, rank, pools, cached, closure_type); @@ -679,60 +679,36 @@ fn type_to_variable( register(subs, rank, pools, content) } Record(fields, ext) => { - let mut field_vars = MutMap::with_capacity_and_hasher(fields.len(), default_hasher()); + let mut field_vars = Vec::with_capacity(fields.len()); for (field, field_type) in fields { let field_var = field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ)); - field_vars.insert(field.clone(), field_var); + field_vars.push((field.clone(), field_var)); } let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - let new_ext_var = match roc_types::pretty_print::chase_ext_record( - subs, - temp_ext_var, - &mut field_vars, - ) { - Ok(()) => Variable::EMPTY_RECORD, - Err((new, _)) => new, - }; - let mut all_fields: Vec<_> = field_vars.into_iter().collect(); - all_fields.sort_unstable_by(RecordFields::compare); + let (it, new_ext_var) = + gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); - let record_fields = RecordFields::insert_into_subs(subs, all_fields); + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + field_vars.sort_unstable_by(RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(subs, field_vars); let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); register(subs, rank, pools, content) } TagUnion(tags, ext) => { - let mut tag_vars = MutMap::with_capacity_and_hasher(tags.len(), default_hasher()); - - for (tag, tag_argument_types) in tags { - let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); - - for arg_type in tag_argument_types { - tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); - } - - tag_vars.insert(tag.clone(), tag_argument_vars); - } - - let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - let mut ext_tag_vec = Vec::new(); - let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( - subs, - temp_ext_var, - &mut ext_tag_vec, - ) { - Ok(()) => Variable::EMPTY_TAG_UNION, - Err((new, _)) => new, - }; - tag_vars.extend(ext_tag_vec.into_iter()); - - let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var)); + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); register(subs, rank, pools, content) } @@ -749,41 +725,19 @@ fn type_to_variable( }; debug_assert!(ext_tag_vec.is_empty()); - let content = Content::Structure(FlatType::FunctionOrTagUnion( - Box::new(tag_name.clone()), - *symbol, - new_ext_var, - )); + let start = subs.tag_names.len() as u32; + subs.tag_names.push(tag_name.clone()); + let slice = SubsIndex::new(start); + + let content = + Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, new_ext_var)); register(subs, rank, pools, content) } RecursiveTagUnion(rec_var, tags, ext) => { - let mut tag_vars = MutMap::with_capacity_and_hasher(tags.len(), default_hasher()); - - for (tag, tag_argument_types) in tags { - let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); - - for arg_type in tag_argument_types { - tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); - } - - tag_vars.insert(tag.clone(), tag_argument_vars); - } - - let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - let mut ext_tag_vec = Vec::new(); - let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( - subs, - temp_ext_var, - &mut ext_tag_vec, - ) { - Ok(()) => Variable::EMPTY_TAG_UNION, - Err((new, _)) => new, - }; - tag_vars.extend(ext_tag_vec.into_iter()); - + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); let content = - Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); + Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); let tag_union_var = register(subs, rank, pools, content); @@ -797,8 +751,35 @@ fn type_to_variable( tag_union_var } - Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL, - Alias(symbol, args, alias_type) => { + + Type::Alias { + symbol, + type_arguments: args, + actual: alias_type, + lambda_set_variables, + } => { + // the rank of these variables is NONE (encoded as 0 in practice) + // using them for other ranks causes issues + if rank.is_none() { + // TODO replace by arithmetic? + match *symbol { + Symbol::NUM_I128 => return Variable::I128, + Symbol::NUM_I64 => return Variable::I64, + Symbol::NUM_I32 => return Variable::I32, + Symbol::NUM_I16 => return Variable::I16, + Symbol::NUM_I8 => return Variable::I8, + + Symbol::NUM_U128 => return Variable::U128, + Symbol::NUM_U64 => return Variable::U64, + Symbol::NUM_U32 => return Variable::U32, + Symbol::NUM_U16 => return Variable::U16, + Symbol::NUM_U8 => return Variable::U8, + + // Symbol::NUM_NAT => return Variable::NAT, + _ => {} + } + } + let mut arg_vars = Vec::with_capacity(args.len()); for (arg, arg_type) in args { @@ -807,6 +788,13 @@ fn type_to_variable( arg_vars.push((arg.clone(), arg_var)); } + let lambda_set_variables: Vec<_> = lambda_set_variables + .iter() + .map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) + .collect(); + + let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); + let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); let content = Content::Alias(*symbol, arg_vars, alias_var); @@ -814,9 +802,10 @@ fn type_to_variable( } HostExposedAlias { name: symbol, - arguments: args, + type_arguments: args, actual: alias_type, actual_var, + lambda_set_variables, .. } => { let mut arg_vars = Vec::with_capacity(args.len()); @@ -827,6 +816,13 @@ fn type_to_variable( arg_vars.push((arg.clone(), arg_var)); } + let lambda_set_variables: Vec<_> = lambda_set_variables + .iter() + .map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) + .collect(); + + let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); + let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); // unify the actual_var with the result var @@ -857,6 +853,59 @@ fn type_to_variable( } } +fn type_to_union_tags( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + tags: &[(TagName, Vec)], + ext: &Type, +) -> (UnionTags, Variable) { + let mut tag_vars = Vec::with_capacity(tags.len()); + + let mut tag_argument_vars = Vec::new(); + for (tag, tag_argument_types) in tags { + for arg_type in tag_argument_types { + let new_var = type_to_variable(subs, rank, pools, cached, arg_type); + tag_argument_vars.push(new_var); + } + + let new_slice = VariableSubsSlice::insert_into_subs(subs, tag_argument_vars.drain(..)); + + tag_vars.push((tag.clone(), new_slice)); + } + + let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + + let ext = { + let (it, ext) = + roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + + // deduplicate, keeping the right-most occurrence of a tag name + let mut i = 0; + + while i < tag_vars.len() { + match (tag_vars.get(i), tag_vars.get(i + 1)) { + (Some((t1, _)), Some((t2, _))) => { + if t1 == t2 { + tag_vars.remove(i); + } else { + i += 1; + } + } + _ => break, + } + } + + ext + }; + + (UnionTags::insert_slices_into_subs(subs, tag_vars), ext) +} + fn check_for_infinite_type( subs: &mut Subs, problems: &mut Vec, @@ -865,7 +914,7 @@ fn check_for_infinite_type( ) { let var = loc_var.value; - while let Some((recursive, _chain)) = subs.occurs(var) { + while let Err((recursive, _chain)) = subs.occurs(var) { let description = subs.get(recursive); let content = description.content; @@ -882,19 +931,24 @@ fn check_for_infinite_type( }, ); - let mut new_tags = MutMap::default(); + let mut new_tags = Vec::with_capacity(tags.len()); - for (label, args) in &tags { - let new_args: Vec<_> = args - .iter() - .map(|var| subs.explicit_substitute(recursive, rec_var, *var)) - .collect(); + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; - new_tags.insert(label.clone(), new_args); + let mut new_vars = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + new_vars.push(subs.explicit_substitute(recursive, rec_var, var)); + } + + new_tags.push((subs[name_index].clone(), new_vars)); } let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); + let new_tags = UnionTags::insert_into_subs(subs, new_tags); + let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); subs.set_content(recursive, Content::Structure(flat_type)); @@ -1049,9 +1103,9 @@ fn adjust_rank_content( Apply(_, args) => { let mut rank = Rank::toplevel(); - for var in args { - rank = - rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for var_index in args.into_iter() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); } rank @@ -1104,9 +1158,13 @@ fn adjust_rank_content( TagUnion(tags, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in tags.values().flatten() { - rank = - rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } } rank @@ -1119,17 +1177,31 @@ fn adjust_rank_content( RecursiveTagUnion(rec_var, tags, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in tags.values().flatten() { - rank = - rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } } // THEORY: the recursion var has the same rank as the tag union itself // all types it uses are also in the tags already, so it cannot influence the // rank - debug_assert!( - rank >= adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var) - ); + + if cfg!(debug_assertions) { + let rec_var_rank = + adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var); + + debug_assert!( + rank >= rec_var_rank, + "rank was {:?} but recursion var {:?} has higher rank {:?}", + rank, + rec_var, + rec_var_rank + ); + } rank } @@ -1141,8 +1213,9 @@ fn adjust_rank_content( Alias(_, args, real_var) => { let mut rank = Rank::toplevel(); - for (_, var) in args { - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for var_index in args.variables() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); } // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() @@ -1225,7 +1298,8 @@ fn instantiate_rigids_help( Structure(flat_type) => { match flat_type { Apply(_, args) => { - for var in args.into_iter() { + for var_index in args.into_iter() { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } } @@ -1252,8 +1326,10 @@ fn instantiate_rigids_help( } TagUnion(tags, ext_var) => { - for (_, vars) in tags { - for var in vars.into_iter() { + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } } @@ -1268,8 +1344,10 @@ fn instantiate_rigids_help( RecursiveTagUnion(rec_var, tags, ext_var) => { instantiate_rigids_help(subs, max_rank, pools, rec_var); - for (_, vars) in tags { - for var in vars.into_iter() { + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } } @@ -1290,8 +1368,9 @@ fn instantiate_rigids_help( subs.set(copy, make_descriptor(FlexVar(Some(name)))); } - Alias(_, args, real_type_var) => { - for (_, var) in args.into_iter() { + Alias(_symbol, args, real_type_var) => { + for var_index in args.variables().into_iter() { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } @@ -1360,12 +1439,17 @@ fn deep_copy_var_help( Structure(flat_type) => { let new_flat_type = match flat_type { Apply(symbol, args) => { - let args = args - .into_iter() - .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) - .collect(); + let mut new_arg_vars = Vec::with_capacity(args.len()); - Apply(symbol, args) + for index in args.into_iter() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + Apply(symbol, arg_vars) } Func(arg_vars, closure_var, ret_var) => { @@ -1380,11 +1464,7 @@ fn deep_copy_var_help( new_arg_vars.push(copy_var); } - let start = subs.variables.len() as u32; - let length = arg_vars.len() as u16; - let arg_vars = SubsSlice::new(start, length); - - subs.variables.extend(new_arg_vars); + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); Func(arg_vars, new_closure_var, new_ret_var) } @@ -1433,17 +1513,35 @@ fn deep_copy_var_help( } TagUnion(tags, ext_var) => { - let mut new_tags = MutMap::default(); + let mut new_variable_slices = Vec::with_capacity(tags.len()); - for (tag, vars) in tags { - let new_vars: Vec = vars - .into_iter() - .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) - .collect(); - new_tags.insert(tag, new_vars); + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); } - TagUnion(new_tags, deep_copy_var_help(subs, max_rank, pools, ext_var)) + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + TagUnion(union_tags, new_ext) } FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( @@ -1453,23 +1551,37 @@ fn deep_copy_var_help( ), RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut new_tags = MutMap::default(); + let mut new_variable_slices = Vec::with_capacity(tags.len()); - let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } - for (tag, vars) in tags { - let new_vars: Vec = vars - .into_iter() - .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) - .collect(); - new_tags.insert(tag, new_vars); + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); } - RecursiveTagUnion( - new_rec_var, - new_tags, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + + RecursiveTagUnion(new_rec_var, union_tags, new_ext) } }; @@ -1503,13 +1615,20 @@ fn deep_copy_var_help( copy } - Alias(symbol, args, real_type_var) => { - let new_args = args - .into_iter() - .map(|(name, var)| (name, deep_copy_var_help(subs, max_rank, pools, var))) - .collect(); + Alias(symbol, mut args, real_type_var) => { + let mut new_vars = Vec::with_capacity(args.variables().len()); + + for var_index in args.variables() { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + + new_vars.push(new_var); + } + + args.replace_variables(subs, new_vars); + let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); - let new_content = Alias(symbol, new_args, new_real_type_var); + let new_content = Alias(symbol, args, new_real_type_var); subs.set(copy, make_descriptor(new_content)); diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 92907af3f7..4f93fe449e 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3903,6 +3903,7 @@ mod solve_expr { } #[test] + #[ignore] fn rbtree_full_remove_min() { infer_eq_without_problem( indoc!( diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index c81622475d..b3ee4c656f 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -27,6 +27,25 @@ pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { libc::free(c_ptr) } +#[no_mangle] +pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + use roc_gen_llvm::llvm::build::PanicTagId; + + use libc::c_char; + use std::convert::TryFrom; + use std::ffi::CStr; + + match PanicTagId::try_from(tag_id) { + Ok(PanicTagId::NullTerminatedString) => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + Err(_) => unreachable!(), + } +} + #[test] fn roc_list_construction() { let list = RocList::from_slice(&[1i64; 23]); @@ -276,6 +295,20 @@ fn list_prepend() { RocList::from_slice(&[6, 4]), RocList ); + + assert_evals_to!( + indoc!( + r#" + init : List Str + init = + ["foo"] + + List.prepend init "bar" + "# + ), + RocList::from_slice(&[RocStr::from_slice(b"bar"), RocStr::from_slice(b"foo"),]), + RocList + ); } #[test] diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index fb1621eb8c..2c4ff7d716 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -481,8 +481,12 @@ mod gen_num { } #[test] - fn f64_round_old() { + fn f64_round() { assert_evals_to!("Num.round 3.6", 4, i64); + assert_evals_to!("Num.round 3.4", 3, i64); + assert_evals_to!("Num.round 2.5", 3, i64); + assert_evals_to!("Num.round -2.3", -2, i64); + assert_evals_to!("Num.round -2.5", -3, i64); } #[test] @@ -1620,4 +1624,158 @@ mod gen_num { // overflow assert_evals_to!("Num.isMultipleOf -9223372036854775808 -1", true, bool); } + + #[test] + fn bytes_to_u16_clearly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU16 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u16 + ); + } + + #[test] + fn bytes_to_u16_subtly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU16 bytes 4 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u16 + ); + } + + #[test] + fn bytes_to_u32_clearly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU32 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u32 + ); + } + + #[test] + fn bytes_to_u32_subtly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU32 bytes 2 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u32 + ); + } + + #[test] + fn bytes_to_u16_max_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU16 [255, 255] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 65535, + u16 + ); + } + + #[test] + fn bytes_to_u16_min_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU16 [0, 0] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 0, + u16 + ); + } + + #[test] + fn bytes_to_u16_random_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU16 [164, 215] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 55_204, + u16 + ); + } + + #[test] + fn bytes_to_u32_min_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU32 [0, 0, 0, 0] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 0, + u32 + ); + } + + #[test] + fn bytes_to_u32_max_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU32 [255, 255, 255, 255] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 4_294_967_295, + u32 + ); + } + + #[test] + fn bytes_to_u32_random_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU32 [252, 124, 128, 121] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 2_038_463_740, + u32 + ); + } } diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 68c2b53142..a416a852a6 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1997,6 +1997,7 @@ fn case_or_pattern() { } #[test] +#[ignore] fn rosetree_basic() { assert_non_opt_evals_to!( indoc!( @@ -2392,7 +2393,6 @@ fn call_invalid_layout() { 3, i64, |x| x, - false, true ); } @@ -2529,3 +2529,234 @@ fn pattern_match_unit_tag() { i64 ); } + +#[test] +fn mirror_llvm_alignment_padding() { + // see https://github.com/rtfeldman/roc/issues/1569 + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main : Str + main = + p1 = {name : "test1", test: 1 == 1 } + + List.map [p1, p1 ] (\{ test } -> if test then "pass" else "fail") + |> Str.joinWith "\n" + + "# + ), + RocStr::from_slice(b"pass\npass"), + RocStr + ); +} + +#[test] +fn lambda_set_bool() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + p1 = (\u -> u == 97) + p2 = (\u -> u == 98) + + main : I64 + main = + oneOfResult = List.map [p1, p2] (\p -> p 42) + + when oneOfResult is + _ -> 32 + + "# + ), + 32, + i64 + ); +} + +#[test] +fn lambda_set_byte() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + p1 = (\u -> u == 97) + p2 = (\u -> u == 98) + p3 = (\u -> u == 99) + + main : I64 + main = + oneOfResult = List.map [p1, p2, p3] (\p -> p 42) + + when oneOfResult is + _ -> 32 + + "# + ), + 32, + i64 + ); +} + +#[test] +fn lambda_set_struct_byte() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + + main : I64 + main = + r : [ Red, Green, Blue ] + r = Red + + p1 = (\u -> r == u) + oneOfResult = List.map [p1, p1] (\p -> p Green) + + when oneOfResult is + _ -> 32 + + "# + ), + 32, + i64 + ); +} + +#[test] +fn lambda_set_enum_byte_byte() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + + main : I64 + main = + r : [ Red, Green, Blue ] + r = Red + + g : [ Red, Green, Blue ] + g = Green + + p1 = (\u -> r == u) + p2 = (\u -> g == u) + oneOfResult = List.map [p1, p2] (\p -> p Green) + + when oneOfResult is + _ -> 32 + + "# + ), + 32, + i64 + ); +} + +#[test] +fn list_walk_until() { + // see https://github.com/rtfeldman/roc/issues/1576 + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + + satisfyA : {} -> List {} + satisfyA = \_ -> [] + + oneOfResult = + List.walkUntil [ satisfyA ] (\_, _ -> Stop []) [] + + main = + when oneOfResult is + _ -> 32 + "# + ), + 32, + i64 + ); +} + +#[test] +fn int_literal_not_specialized_with_annotation() { + // see https://github.com/rtfeldman/roc/issues/1600 + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + satisfy : (U8 -> Str) -> Str + satisfy = \_ -> "foo" + + myEq : a, a -> Str + myEq = \_, _ -> "bar" + + p1 : Num * -> Str + p1 = (\u -> myEq u 64) + + when satisfy p1 is + _ -> 32 + "# + ), + 32, + i64 + ); +} + +#[test] +fn int_literal_not_specialized_no_annotation() { + // see https://github.com/rtfeldman/roc/issues/1600 + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + satisfy : (U8 -> Str) -> Str + satisfy = \_ -> "foo" + + myEq : a, a -> Str + myEq = \_, _ -> "bar" + + p1 = (\u -> myEq u 64) + + when satisfy p1 is + _ -> 32 + "# + ), + 32, + i64 + ); +} + +#[test] +fn unresolved_tvar_when_capture_is_unused() { + // see https://github.com/rtfeldman/roc/issues/1585 + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main : I64 + main = + r : Bool + r = False + + p1 = (\_ -> r == (1 == 1)) + oneOfResult = List.map [p1] (\p -> p Green) + + when oneOfResult is + _ -> 32 + + "# + ), + 32, + i64 + ); +} diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 504f59835f..e0978817f9 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -830,3 +830,118 @@ fn str_to_utf8() { &[u8] ); } + +#[test] +fn str_from_utf8_range() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { count: 5, start: 0 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("hello"), + RocStr + ); +} + +#[test] +fn str_from_utf8_range_slice() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { count: 4, start: 1 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("ello"), + RocStr + ); +} + +#[test] +fn str_from_utf8_range_slice_not_end() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { count: 3, start: 1 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("ell"), + RocStr + ); +} + +#[test] +fn str_from_utf8_range_order_does_not_matter() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 1, count: 3 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("ell"), + RocStr + ); +} + +#[test] +fn str_from_utf8_range_out_of_bounds_start_value() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 7, count: 3 } is + Ok _ -> "" + Err (BadUtf8 _ _) -> "" + Err OutOfBounds -> "out of bounds" + "# + ), + RocStr::from("out of bounds"), + RocStr + ); +} + +#[test] +fn str_from_utf8_range_count_too_high() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 0, count: 6 } is + Ok _ -> "" + Err (BadUtf8 _ _) -> "" + Err OutOfBounds -> "out of bounds" + "# + ), + RocStr::from("out of bounds"), + RocStr + ); +} + +#[test] +fn str_from_utf8_range_count_too_high_for_start() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 4, count: 3 } is + Ok _ -> "" + Err (BadUtf8 _ _) -> "" + Err OutOfBounds -> "out of bounds" + "# + ), + RocStr::from("out of bounds"), + RocStr + ); +} diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index 02436deccd..ce0547432d 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -33,7 +33,7 @@ pub fn helper<'a>( arena: &'a bumpalo::Bump, src: &str, stdlib: &'a roc_builtins::std::StdLib, - leak: bool, + is_gen_test: bool, ignore_problems: bool, context: &'a inkwell::context::Context, ) -> (&'static str, String, Library) { @@ -171,13 +171,6 @@ pub fn helper<'a>( let builder = context.create_builder(); let module = roc_gen_llvm::llvm::build::module_from_builtins(context, "app"); - // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no - // platform to provide them. - add_default_roc_externs(context, &module, &builder, ptr_bytes); - - // strip Zig debug stuff - module.strip_debug_info(); - let opt_level = if cfg!(debug_assertions) { OptLevel::Normal } else { @@ -219,11 +212,18 @@ pub fn helper<'a>( interns, module, ptr_bytes, - leak, + is_gen_test, // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), }; + // strip Zig debug stuff + module.strip_debug_info(); + + // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no + // platform to provide them. + add_default_roc_externs(&env); + let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( &env, opt_level, @@ -233,6 +233,9 @@ pub fn helper<'a>( env.dibuilder.finalize(); + // strip all debug info: we don't use it at the moment and causes weird validation issues + module.strip_debug_info(); + // Uncomment this to see the module's un-optimized LLVM instruction output: // env.module.print_to_stderr(); @@ -260,7 +263,7 @@ pub fn helper<'a>( #[macro_export] macro_rules! assert_llvm_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $ignore_problems:expr) => { + ($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => { use bumpalo::Bump; use inkwell::context::Context; use roc_gen_llvm::run_jit_function; @@ -271,11 +274,19 @@ macro_rules! assert_llvm_evals_to { // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let (main_fn_name, errors, lib) = - $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $ignore_problems, &context); + let is_gen_test = true; + let (main_fn_name, errors, lib) = $crate::helpers::eval::helper( + &arena, + $src, + stdlib, + is_gen_test, + $ignore_problems, + &context, + ); let transform = |success| { let expected = $expected; + #[allow(clippy::redundant_closure_call)] let given = $transform(success); assert_eq!(&given, &expected); }; @@ -283,7 +294,7 @@ macro_rules! assert_llvm_evals_to { }; ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { - assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false); + assert_llvm_evals_to!($src, $expected, $ty, $transform, false); }; } @@ -295,20 +306,7 @@ macro_rules! assert_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { // Same as above, except with an additional transformation argument. { - assert_evals_to!($src, $expected, $ty, $transform, true); - } - }; - ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { - // Run un-optimized tests, and then optimized tests, in separate scopes. - // These each rebuild everything from scratch, starting with - // parsing the source, so that there's no chance their passing - // or failing depends on leftover state from the previous one. - { - assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak, false); - } - { - // NOTE at the moment, the optimized tests do the same thing - // assert_opt_evals_to!($src, $expected, $ty, $transform, $leak); + assert_llvm_evals_to!($src, $expected, $ty, $transform, false); } }; } @@ -321,10 +319,10 @@ macro_rules! assert_non_opt_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { // Same as above, except with an additional transformation argument. { - assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false); + assert_llvm_evals_to!($src, $expected, $ty, $transform, false); } }; - ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{ - assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{ + assert_llvm_evals_to!($src, $expected, $ty, $transform); }}; } diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index 1c6b0e2d39..c57881e00b 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,6 +1,6 @@ procedure Num.25 (#Attr.2, #Attr.3): - let Test.15 = lowlevel NumSub #Attr.2 #Attr.3; - ret Test.15; + let Test.14 = lowlevel NumSub #Attr.2 #Attr.3; + ret Test.14; procedure Num.26 (#Attr.2, #Attr.3): let Test.12 = lowlevel NumMul #Attr.2 #Attr.3; @@ -8,13 +8,13 @@ procedure Num.26 (#Attr.2, #Attr.3): procedure Test.1 (Test.2, Test.3): joinpoint Test.7 Test.2 Test.3: - let Test.17 = 0i64; - let Test.18 = lowlevel Eq Test.17 Test.2; - if Test.18 then + let Test.15 = 0i64; + let Test.16 = lowlevel Eq Test.15 Test.2; + if Test.16 then ret Test.3; else - let Test.14 = 1i64; - let Test.10 = CallByName Num.25 Test.2 Test.14; + let Test.13 = 1i64; + let Test.10 = CallByName Num.25 Test.2 Test.13; let Test.11 = CallByName Num.26 Test.2 Test.3; jump Test.7 Test.10 Test.11; in diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index 0adc761dc1..a4168a5109 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,24 +1,20 @@ procedure List.7 (#Attr.2): - let Test.7 = lowlevel ListLen #Attr.2; - ret Test.7; + let Test.6 = lowlevel ListLen #Attr.2; + ret Test.6; procedure Num.24 (#Attr.2, #Attr.3): let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.14 = 1i64; - let Test.15 = 2i64; - let Test.1 = Array [Test.14, Test.15]; - let Test.11 = 5i64; - let Test.12 = 4i64; - invoke Test.8 = CallByName Num.24 Test.11 Test.12 catch - dec Test.1; - unreachable; - let Test.9 = 3i64; - invoke Test.3 = CallByName Num.24 Test.8 Test.9 catch - dec Test.1; - unreachable; + let Test.11 = 1i64; + let Test.12 = 2i64; + let Test.1 = Array [Test.11, Test.12]; + let Test.9 = 5i64; + let Test.10 = 4i64; + let Test.7 = CallByName Num.24 Test.9 Test.10; + let Test.8 = 3i64; + let Test.3 = CallByName Num.24 Test.7 Test.8; let Test.4 = CallByName List.7 Test.1; dec Test.1; let Test.2 = CallByName Num.24 Test.3 Test.4; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index 4ef134c7eb..65d6052dd9 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -3,16 +3,16 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.6; procedure Test.0 (): - let Test.12 = 41i64; - 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; - if Test.11 then + let Test.11 = 41i64; + let Test.1 = Just Test.11; + let Test.8 = 0i64; + let Test.9 = GetTagId Test.1; + let Test.10 = lowlevel Eq Test.8 Test.9; + if Test.10 then let Test.3 = UnionAtIndex (Id 0) (Index 0) Test.1; let Test.5 = 1i64; let Test.4 = CallByName Num.24 Test.3 Test.5; ret Test.4; else - let Test.8 = 1i64; - ret Test.8; + let Test.7 = 1i64; + ret Test.7; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index 38eea70396..a540fcc435 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -3,18 +3,18 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.10; procedure Test.3 (Test.5): - let Test.18 = 1i64; - let Test.19 = GetTagId Test.5; - let Test.20 = lowlevel Eq Test.18 Test.19; - if Test.20 then - let Test.13 = 0i64; - ret Test.13; + let Test.16 = 1i64; + let Test.17 = GetTagId Test.5; + let Test.18 = lowlevel Eq Test.16 Test.17; + if Test.18 then + let Test.12 = 0i64; + ret Test.12; else let Test.6 = UnionAtIndex (Id 0) (Index 1) Test.5; - let Test.15 = 1i64; - let Test.16 = CallByName Test.3 Test.6; - let Test.14 = CallByName Num.24 Test.15 Test.16; - ret Test.14; + let Test.14 = 1i64; + let Test.15 = CallByName Test.3 Test.6; + let Test.13 = CallByName Num.24 Test.14 Test.15; + ret Test.13; procedure Test.0 (): let Test.2 = Nil ; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 0c9ff5de3f..0a29c418b8 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,40 +1,40 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.23 = lowlevel ListLen #Attr.2; - let Test.21 = lowlevel NumLt #Attr.3 Test.23; - if Test.21 then - let Test.22 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; - ret Test.22; + let Test.22 = lowlevel ListLen #Attr.2; + let Test.20 = lowlevel NumLt #Attr.3 Test.22; + if Test.20 then + let Test.21 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + ret Test.21; else ret #Attr.2; procedure List.7 (#Attr.2): - let Test.10 = lowlevel ListLen #Attr.2; - ret Test.10; + let Test.9 = lowlevel ListLen #Attr.2; + ret Test.9; procedure Num.24 (#Attr.2, #Attr.3): let Test.7 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (): - let Test.12 = 1i64; - let Test.13 = 2i64; - let Test.14 = 3i64; - let Test.11 = Array [Test.12, Test.13, Test.14]; - ret Test.11; + let Test.11 = 1i64; + let Test.12 = 2i64; + let Test.13 = 3i64; + let Test.10 = Array [Test.11, Test.12, Test.13]; + ret Test.10; procedure Test.2 (Test.3): + let Test.17 = 0i64; let Test.18 = 0i64; - let Test.19 = 0i64; - let Test.17 = CallByName List.4 Test.3 Test.18 Test.19; - ret Test.17; + let Test.16 = CallByName List.4 Test.3 Test.17 Test.18; + ret Test.16; procedure Test.0 (): - let Test.16 = CallByName Test.1; - let Test.15 = CallByName Test.2 Test.16; - let Test.5 = CallByName List.7 Test.15; - dec Test.15; - let Test.9 = CallByName Test.1; - let Test.6 = CallByName List.7 Test.9; - dec Test.9; + let Test.15 = CallByName Test.1; + let Test.14 = CallByName Test.2 Test.15; + let Test.5 = CallByName List.7 Test.14; + dec Test.14; + let Test.8 = CallByName Test.1; + let Test.6 = CallByName List.7 Test.8; + dec Test.8; let Test.4 = CallByName Num.24 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 1f50df081b..d5cd03a2b9 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,22 +1,22 @@ +procedure List.7 (#Attr.2): + let Test.7 = lowlevel ListLen #Attr.2; + ret Test.7; + procedure List.7 (#Attr.2): let Test.8 = lowlevel ListLen #Attr.2; ret Test.8; -procedure List.7 (#Attr.2): - let Test.9 = lowlevel ListLen #Attr.2; - ret Test.9; - procedure Num.24 (#Attr.2, #Attr.3): let Test.6 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.11 = 1i64; - let Test.12 = 2i64; - let Test.13 = 3i64; - let Test.1 = Array [Test.11, Test.12, Test.13]; - let Test.10 = 1f64; - let Test.2 = Array [Test.10]; + let Test.10 = 1i64; + let Test.11 = 2i64; + let Test.12 = 3i64; + let Test.1 = Array [Test.10, Test.11, Test.12]; + let Test.9 = 1f64; + let Test.2 = Array [Test.9]; let Test.4 = CallByName List.7 Test.1; dec Test.1; let Test.5 = CallByName List.7 Test.2; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index 24279438b5..eba5cb4940 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -3,28 +3,28 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.8; procedure Test.0 (): - let Test.21 = 41i64; - let Test.20 = Just Test.21; - let Test.2 = Just Test.20; - joinpoint Test.17: - let Test.10 = 1i64; - ret Test.10; + let Test.20 = 41i64; + let Test.19 = Just Test.20; + let Test.2 = Just Test.19; + joinpoint Test.16: + let Test.9 = 1i64; + ret Test.9; in - let Test.15 = 0i64; - let Test.16 = GetTagId Test.2; - let Test.19 = lowlevel Eq Test.15 Test.16; - if Test.19 then - let Test.12 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.13 = 0i64; - let Test.14 = GetTagId Test.12; - let Test.18 = lowlevel Eq Test.13 Test.14; - if Test.18 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.11; + let Test.14 = 0i64; + let Test.15 = GetTagId Test.2; + let Test.18 = lowlevel Eq Test.14 Test.15; + if Test.18 then + let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 = 0i64; + let Test.13 = GetTagId Test.11; + let Test.17 = lowlevel Eq Test.12 Test.13; + if Test.17 then + let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10; let Test.7 = 1i64; let Test.6 = CallByName Num.24 Test.5 Test.7; ret Test.6; else - jump Test.17; + jump Test.16; else - jump Test.17; + jump Test.16; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index 9e261687aa..37f5c5108f 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -3,10 +3,10 @@ procedure Num.26 (#Attr.2, #Attr.3): ret Test.17; procedure Test.1 (Test.6): - let Test.25 = StructAtIndex 1 Test.6; - let Test.26 = false; - let Test.27 = lowlevel Eq Test.26 Test.25; - if Test.27 then + let Test.22 = StructAtIndex 1 Test.6; + let Test.23 = false; + let Test.24 = lowlevel Eq Test.23 Test.22; + if Test.24 then let Test.8 = StructAtIndex 0 Test.6; ret Test.8; else @@ -14,9 +14,9 @@ procedure Test.1 (Test.6): ret Test.10; procedure Test.1 (Test.6): - let Test.36 = false; - let Test.37 = lowlevel Eq Test.36 Test.6; - if Test.37 then + let Test.33 = false; + let Test.34 = lowlevel Eq Test.33 Test.6; + if Test.34 then let Test.8 = 3i64; ret Test.8; else @@ -24,19 +24,19 @@ procedure Test.1 (Test.6): ret Test.10; procedure Test.0 (): - let Test.40 = true; - let Test.5 = CallByName Test.1 Test.40; - let Test.38 = false; - let Test.3 = CallByName Test.1 Test.38; - let Test.31 = 11i64; - let Test.32 = true; - let Test.30 = Struct {Test.31, Test.32}; - let Test.4 = CallByName Test.1 Test.30; - let Test.28 = 7i64; - let Test.29 = false; - let Test.22 = Struct {Test.28, Test.29}; - let Test.2 = CallByName Test.1 Test.22; - let Test.19 = CallByName Num.26 Test.2 Test.3; - let Test.16 = CallByName Num.26 Test.19 Test.4; + let Test.37 = true; + let Test.5 = CallByName Test.1 Test.37; + let Test.35 = false; + let Test.3 = CallByName Test.1 Test.35; + let Test.28 = 11i64; + let Test.29 = true; + let Test.27 = Struct {Test.28, Test.29}; + let Test.4 = CallByName Test.1 Test.27; + let Test.25 = 7i64; + let Test.26 = false; + let Test.19 = Struct {Test.25, Test.26}; + let Test.2 = CallByName Test.1 Test.19; + let Test.18 = CallByName Num.26 Test.2 Test.3; + let Test.16 = CallByName Num.26 Test.18 Test.4; let Test.15 = CallByName Num.26 Test.16 Test.5; ret Test.15; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index 6d641e8807..079b4fb480 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -3,30 +3,28 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.19; procedure Num.25 (#Attr.2, #Attr.3): - let Test.23 = lowlevel NumSub #Attr.2 #Attr.3; - ret Test.23; + let Test.22 = lowlevel NumSub #Attr.2 #Attr.3; + ret Test.22; procedure Num.27 (#Attr.2, #Attr.3): - let Test.28 = lowlevel NumLt #Attr.2 #Attr.3; - ret Test.28; + let Test.26 = lowlevel NumLt #Attr.2 #Attr.3; + ret Test.26; procedure Test.1 (Test.2, Test.3, Test.4): joinpoint Test.12 Test.2 Test.3 Test.4: let Test.14 = CallByName Num.27 Test.3 Test.4; if Test.14 then dec Test.2; - let Test.27 = Array []; - let Test.26 = 0i64; - let Test.25 = Struct {Test.26, Test.27}; - let Test.5 = StructAtIndex 0 Test.25; - let Test.6 = StructAtIndex 1 Test.25; - let Test.22 = 1i64; - let Test.21 = CallByName Num.25 Test.5 Test.22; - let Test.16 = CallByName Test.1 Test.6 Test.3 Test.21; + let Test.25 = Array []; + let Test.24 = 0i64; + let Test.23 = Struct {Test.24, Test.25}; + let Test.5 = StructAtIndex 0 Test.23; + let Test.6 = StructAtIndex 1 Test.23; + let Test.21 = 1i64; + let Test.20 = CallByName Num.25 Test.5 Test.21; + let Test.16 = CallByName Test.1 Test.6 Test.3 Test.20; let Test.18 = 1i64; - invoke Test.17 = CallByName Num.24 Test.5 Test.18 catch - dec Test.16; - unreachable; + let Test.17 = CallByName Num.24 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index fabb2e373b..3992960017 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -10,8 +10,8 @@ procedure Test.1 (Test.4): ret Test.7; procedure Test.0 (): - let Test.10 = 4i64; - let Test.11 = 9i64; - let Test.6 = Struct {Test.10, Test.11}; + let Test.9 = 4i64; + let Test.10 = 9i64; + let Test.6 = Struct {Test.9, Test.10}; let Test.5 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 0def92d630..6b9b4191df 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -9,6 +9,6 @@ procedure Test.1 (Test.4): ret Test.7; procedure Test.0 (): - let Test.10 = 9i64; - let Test.5 = CallByName Test.1 Test.10; + let Test.9 = 9i64; + let Test.5 = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 964700af06..2c5a7ec53f 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -9,8 +9,8 @@ procedure Test.1 (Test.2): ret Test.7; procedure Test.0 (): - let Test.10 = 4i64; - let Test.11 = 9i64; - let Test.6 = Struct {Test.10, Test.11}; + let Test.9 = 4i64; + let Test.10 = 9i64; + let Test.6 = Struct {Test.9, Test.10}; let Test.5 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index aef4e20cbd..7ea40fe7ab 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -8,6 +8,6 @@ procedure Test.1 (Test.2): ret Test.7; procedure Test.0 (): - let Test.10 = 9i64; - let Test.5 = CallByName Test.1 Test.10; + let Test.9 = 9i64; + let Test.5 = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index da1f049ed5..15ddbd26c6 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,53 +1,43 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.28 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.28; - -procedure Num.26 (#Attr.2, #Attr.3): - let Test.22 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.22; - -procedure Test.1 (): - let Test.30 = 1i64; - ret Test.30; - -procedure Test.2 (): - let Test.24 = 2i64; + let Test.24 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.24; -procedure Test.3 (Test.6): - let Test.27 = CallByName Test.1; - let Test.26 = CallByName Num.24 Test.6 Test.27; - ret Test.26; +procedure Num.26 (#Attr.2, #Attr.3): + let Test.19 = lowlevel NumMul #Attr.2 #Attr.3; + ret Test.19; -procedure Test.4 (Test.7): - let Test.21 = CallByName Test.2; - let Test.20 = CallByName Num.26 Test.7 Test.21; +procedure Test.1 (): + let Test.25 = 1i64; + ret Test.25; + +procedure Test.2 (): + let Test.20 = 2i64; ret Test.20; +procedure Test.3 (Test.6): + let Test.23 = CallByName Test.1; + let Test.22 = CallByName Num.24 Test.6 Test.23; + ret Test.22; + +procedure Test.4 (Test.7): + let Test.18 = CallByName Test.2; + let Test.17 = CallByName Num.26 Test.7 Test.18; + ret Test.17; + procedure Test.5 (Test.8, Test.9): - joinpoint Test.15 Test.14: - ret Test.14; - in - switch Test.8: - case 0: - let Test.16 = CallByName Test.3 Test.9; - jump Test.15 Test.16; - - default: - let Test.17 = CallByName Test.4 Test.9; - jump Test.15 Test.17; - + let Test.14 = CallByName Test.3 Test.9; + ret Test.14; procedure Test.0 (): - joinpoint Test.19 Test.12: + joinpoint Test.16 Test.12: let Test.13 = 42i64; let Test.11 = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.25 = true; - if Test.25 then + let Test.21 = true; + if Test.21 then let Test.3 = Struct {}; - jump Test.19 Test.3; + jump Test.16 Test.3; else let Test.4 = Struct {}; - jump Test.19 Test.4; + jump Test.16 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 9425c98bb8..d5d4592f76 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,6 +1,6 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.29 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.29; + let Test.28 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.28; 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 0) #Attr.12; - let Test.28 = CallByName Num.24 Test.10 Test.4; - ret Test.28; + let Test.27 = CallByName Num.24 Test.10 Test.4; + ret Test.27; procedure Test.8 (Test.11, #Attr.12): let Test.6 = UnionAtIndex (Id 1) (Index 1) #Attr.12; @@ -44,8 +44,8 @@ procedure Test.0 (): let Test.13 = CallByName Test.1 Test.14 Test.15; ret Test.13; in - let Test.27 = true; - if Test.27 then + let Test.26 = true; + if Test.26 then let Test.7 = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index ace0bbda7a..40dd98733d 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,6 +1,6 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.25 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.25; + let Test.24 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.24; 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 0) #Attr.12; - let Test.24 = CallByName Num.24 Test.8 Test.4; - ret Test.24; + let Test.23 = CallByName Num.24 Test.8 Test.4; + ret Test.23; procedure Test.7 (Test.9, #Attr.12): let Test.5 = UnionAtIndex (Id 1) (Index 0) #Attr.12; @@ -35,8 +35,8 @@ procedure Test.0 (): jump Test.15 Test.17; in - let Test.23 = true; - if Test.23 then + let Test.22 = true; + if Test.22 then let Test.6 = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index 24279438b5..eba5cb4940 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -3,28 +3,28 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.8; procedure Test.0 (): - let Test.21 = 41i64; - let Test.20 = Just Test.21; - let Test.2 = Just Test.20; - joinpoint Test.17: - let Test.10 = 1i64; - ret Test.10; + let Test.20 = 41i64; + let Test.19 = Just Test.20; + let Test.2 = Just Test.19; + joinpoint Test.16: + let Test.9 = 1i64; + ret Test.9; in - let Test.15 = 0i64; - let Test.16 = GetTagId Test.2; - let Test.19 = lowlevel Eq Test.15 Test.16; - if Test.19 then - let Test.12 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.13 = 0i64; - let Test.14 = GetTagId Test.12; - let Test.18 = lowlevel Eq Test.13 Test.14; - if Test.18 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.11; + let Test.14 = 0i64; + let Test.15 = GetTagId Test.2; + let Test.18 = lowlevel Eq Test.14 Test.15; + if Test.18 then + let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 = 0i64; + let Test.13 = GetTagId Test.11; + let Test.17 = lowlevel Eq Test.12 Test.13; + if Test.17 then + let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10; let Test.7 = 1i64; let Test.6 = CallByName Num.24 Test.5 Test.7; ret Test.6; else - jump Test.17; + jump Test.16; else - jump Test.17; + jump Test.16; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index 9c37c1097b..339304f50f 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -3,7 +3,7 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.5; procedure Test.0 (): - let Test.7 = 2i64; + let Test.6 = 2i64; let Test.4 = 3i64; - let Test.3 = CallByName Num.24 Test.7 Test.4; + let Test.3 = CallByName Num.24 Test.6 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index f92d0897c3..c3de46f38e 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -3,26 +3,26 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.7; procedure Test.0 (): - let Test.17 = 3i64; - let Test.16 = 2i64; - let Test.4 = Struct {Test.16, Test.17}; - joinpoint Test.13: + let Test.16 = 3i64; + let Test.15 = 2i64; + let Test.4 = Struct {Test.15, Test.16}; + joinpoint Test.12: let Test.2 = StructAtIndex 0 Test.4; let Test.3 = StructAtIndex 1 Test.4; let Test.6 = CallByName Num.24 Test.2 Test.3; ret Test.6; in - let Test.11 = StructAtIndex 1 Test.4; - let Test.12 = 3i64; - let Test.15 = lowlevel Eq Test.12 Test.11; - if Test.15 then - let Test.9 = StructAtIndex 0 Test.4; - let Test.10 = 4i64; - let Test.14 = lowlevel Eq Test.10 Test.9; - if Test.14 then + let Test.10 = StructAtIndex 1 Test.4; + let Test.11 = 3i64; + let Test.14 = lowlevel Eq Test.11 Test.10; + if Test.14 then + let Test.8 = StructAtIndex 0 Test.4; + let Test.9 = 4i64; + let Test.13 = lowlevel Eq Test.9 Test.8; + if Test.13 then let Test.5 = 9i64; ret Test.5; else - jump Test.13; + jump Test.12; else - jump Test.13; + jump Test.12; diff --git a/compiler/types/src/builtin_aliases.rs b/compiler/types/src/builtin_aliases.rs index 4c93a949dc..dc49280bc7 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -365,6 +365,7 @@ pub fn num_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_NUM, vec![("range".into(), range.clone())], + vec![], Box::new(num_alias_content(range)), ) } @@ -381,6 +382,7 @@ pub fn floatingpoint_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_FLOATINGPOINT, vec![("range".into(), range.clone())], + vec![], Box::new(floatingpoint_alias_content(range)), ) } @@ -397,6 +399,7 @@ pub fn float_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_FLOAT, vec![("range".into(), range.clone())], + vec![], Box::new(float_alias_content(range)), ) } @@ -410,7 +413,12 @@ fn float_alias_content(range: SolvedType) -> SolvedType { #[inline(always)] pub fn nat_type() -> SolvedType { - SolvedType::Alias(Symbol::NUM_NAT, vec![], Box::new(nat_alias_content())) + SolvedType::Alias( + Symbol::NUM_NAT, + vec![], + vec![], + Box::new(nat_alias_content()), + ) } #[inline(always)] @@ -422,7 +430,12 @@ fn nat_alias_content() -> SolvedType { #[inline(always)] pub fn u64_type() -> SolvedType { - SolvedType::Alias(Symbol::NUM_U64, vec![], Box::new(u64_alias_content())) + SolvedType::Alias( + Symbol::NUM_U64, + vec![], + vec![], + Box::new(u64_alias_content()), + ) } #[inline(always)] @@ -434,7 +447,12 @@ fn u64_alias_content() -> SolvedType { #[inline(always)] pub fn u32_type() -> SolvedType { - SolvedType::Alias(Symbol::NUM_U32, vec![], Box::new(u32_alias_content())) + SolvedType::Alias( + Symbol::NUM_U32, + vec![], + vec![], + Box::new(u32_alias_content()), + ) } #[inline(always)] @@ -446,7 +464,12 @@ fn u32_alias_content() -> SolvedType { #[inline(always)] pub fn i128_type() -> SolvedType { - SolvedType::Alias(Symbol::NUM_I128, vec![], Box::new(i128_alias_content())) + SolvedType::Alias( + Symbol::NUM_I128, + vec![], + vec![], + Box::new(i128_alias_content()), + ) } #[inline(always)] @@ -461,6 +484,7 @@ pub fn int_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_INT, vec![("range".into(), range.clone())], + vec![], Box::new(int_alias_content(range)), ) } @@ -477,6 +501,7 @@ pub fn integer_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_INTEGER, vec![("range".into(), range.clone())], + vec![], Box::new(integer_alias_content(range)), ) } @@ -491,15 +516,27 @@ pub fn u8_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_U8, vec![], + vec![], Box::new(int_alias_content(unsigned8_type())), ) } +#[inline(always)] +pub fn u16_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_U16, + vec![], + vec![], + Box::new(int_alias_content(unsigned16_type())), + ) +} + #[inline(always)] pub fn binary64_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_BINARY64, vec![], + vec![], Box::new(binary64_alias_content()), ) } @@ -514,6 +551,7 @@ pub fn binary32_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_BINARY32, vec![], + vec![], Box::new(binary32_alias_content()), ) } @@ -528,6 +566,7 @@ pub fn natural_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_NATURAL, vec![], + vec![], Box::new(natural_alias_content()), ) } @@ -542,6 +581,7 @@ pub fn signed128_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_SIGNED128, vec![], + vec![], Box::new(signed128_alias_content()), ) } @@ -556,6 +596,7 @@ pub fn signed64_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_SIGNED64, vec![], + vec![], Box::new(signed64_alias_content()), ) } @@ -570,6 +611,7 @@ pub fn signed32_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_SIGNED32, vec![], + vec![], Box::new(signed32_alias_content()), ) } @@ -584,6 +626,7 @@ pub fn signed16_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_SIGNED16, vec![], + vec![], Box::new(signed16_alias_content()), ) } @@ -598,6 +641,7 @@ pub fn signed8_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_SIGNED8, vec![], + vec![], Box::new(signed8_alias_content()), ) } @@ -612,6 +656,7 @@ pub fn unsigned128_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_UNSIGNED128, vec![], + vec![], Box::new(unsigned128_alias_content()), ) } @@ -626,6 +671,7 @@ pub fn unsigned64_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_UNSIGNED64, vec![], + vec![], Box::new(unsigned64_alias_content()), ) } @@ -640,6 +686,7 @@ pub fn unsigned32_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_UNSIGNED32, vec![], + vec![], Box::new(unsigned32_alias_content()), ) } @@ -654,6 +701,7 @@ pub fn unsigned16_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_UNSIGNED16, vec![], + vec![], Box::new(unsigned16_alias_content()), ) } @@ -668,6 +716,7 @@ pub fn unsigned8_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_UNSIGNED8, vec![], + vec![], Box::new(unsigned8_alias_content()), ) } @@ -687,6 +736,7 @@ pub fn decimal_type() -> SolvedType { SolvedType::Alias( Symbol::NUM_DECIMAL, vec![], + vec![], Box::new(decimal_alias_content()), ) } @@ -695,7 +745,8 @@ pub fn decimal_type() -> SolvedType { pub fn bool_type() -> SolvedType { SolvedType::Alias( Symbol::BOOL_BOOL, - Vec::new(), + vec![], + vec![], Box::new(bool_alias_content()), ) } @@ -728,6 +779,7 @@ pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::RESULT_RESULT, vec![("ok".into(), a.clone()), ("err".into(), e.clone())], + vec![], Box::new(result_alias_content(a, e)), ) } @@ -757,7 +809,8 @@ pub fn str_type() -> SolvedType { pub fn str_utf8_problem_type() -> SolvedType { SolvedType::Alias( Symbol::STR_UT8_PROBLEM, - Vec::new(), + vec![], + vec![], Box::new(str_utf8_problem_alias_content()), ) } @@ -780,7 +833,8 @@ pub fn str_utf8_problem_alias_content() -> SolvedType { pub fn str_utf8_byte_problem_type() -> SolvedType { SolvedType::Alias( Symbol::STR_UT8_BYTE_PROBLEM, - Vec::new(), + vec![], + vec![], Box::new(str_utf8_byte_problem_alias_content()), ) } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 40eaac4913..23576b9af3 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -1,4 +1,4 @@ -use crate::subs::{Content, FlatType, GetSubsSlice, Subs, Variable}; +use crate::subs::{Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; use crate::types::{name_type_var, RecordField}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; @@ -76,25 +76,34 @@ fn find_names_needed( use crate::subs::Content::*; use crate::subs::FlatType::*; - while let Some((recursive, _chain)) = subs.occurs(variable) { + while let Err((recursive, _chain)) = subs.occurs(variable) { let rec_var = subs.fresh_unnamed_flex_var(); let content = subs.get_content_without_compacting(recursive); match content { Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + let ext_var = *ext_var; + let mut new_tags = MutMap::default(); - for (label, args) in tags { - let new_args = args - .clone() - .into_iter() - .map(|var| if var == recursive { rec_var } else { var }) - .collect(); + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; - new_tags.insert(label.clone(), new_args); + let mut new_vars = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + new_vars.push(if var == recursive { rec_var } else { var }); + } + + new_tags.insert(subs[name_index].clone(), new_vars); } - let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, *ext_var); + let mut x: Vec<_> = new_tags.into_iter().collect(); + x.sort(); + + let union_tags = UnionTags::insert_into_subs(subs, x); + + let flat_type = FlatType::RecursiveTagUnion(rec_var, union_tags, ext_var); subs.set_content(recursive, Content::Structure(flat_type)); } _ => panic!( @@ -141,8 +150,9 @@ fn find_names_needed( names_taken.insert(name.clone()); } Structure(Apply(_, args)) => { - for var in args { - find_names_needed(*var, subs, roots, root_appearances, names_taken); + for index in args.into_iter() { + let var = subs[index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); } } Structure(Func(arg_vars, _closure_var, ret_var)) => { @@ -162,11 +172,12 @@ fn find_names_needed( find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); } Structure(TagUnion(tags, ext_var)) => { - let mut sorted_tags: Vec<_> = tags.iter().collect(); - sorted_tags.sort(); - - for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() { - find_names_needed(*var, subs, roots, root_appearances, names_taken); + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); + } } find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); @@ -175,19 +186,22 @@ fn find_names_needed( find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); } Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => { - let mut sorted_tags: Vec<_> = tags.iter().collect(); - sorted_tags.sort(); - - for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() { - find_names_needed(*var, subs, roots, root_appearances, names_taken); + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); + } } find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); find_names_needed(*rec_var, subs, roots, root_appearances, names_taken); } Alias(_symbol, args, _actual) => { - for (_, var) in args { - find_names_needed(*var, subs, roots, root_appearances, names_taken); + // only find names for named parameters! + for var_index in args.variables().into_iter().take(args.len()) { + let var = subs[var_index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); } // TODO should we also look in the actual variable? // find_names_needed(_actual, subs, roots, root_appearances, names_taken); @@ -294,10 +308,13 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa Symbol::NUM_NUM => { debug_assert_eq!(args.len(), 1); - let (_, arg_var) = args - .get(0) + let arg_var_index = args + .variables() + .into_iter() + .next() .expect("Num was not applied to a type argument!"); - let content = subs.get_content_without_compacting(*arg_var); + let arg_var = subs[arg_var_index]; + let content = subs.get_content_without_compacting(arg_var); match &content { Alias(nested, _, _) => match *nested { @@ -320,11 +337,12 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa _ => write_parens!(write_parens, buf, { write_symbol(env, *symbol, buf); - for (_, var) in args { + for var_index in args.variables() { + let var = subs[var_index]; buf.push(' '); write_content( env, - subs.get_content_without_compacting(*var), + subs.get_content_without_compacting(var), subs, buf, Parens::InTypeParam, @@ -345,13 +363,94 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } } +enum ExtContent<'a> { + Empty, + Content(Variable, &'a Content), +} + +impl<'a> ExtContent<'a> { + fn from_var(subs: &'a Subs, ext: Variable) -> Self { + let content = subs.get_content_without_compacting(ext); + match content { + Content::Structure(FlatType::EmptyTagUnion) => ExtContent::Empty, + Content::Structure(FlatType::EmptyRecord) => ExtContent::Empty, + + Content::FlexVar(_) | Content::RigidVar(_) => ExtContent::Content(ext, content), + + other => unreachable!("something weird ended up in an ext var: {:?}", other), + } + } +} + +fn write_ext_content<'a>( + env: &Env, + subs: &'a Subs, + buf: &mut String, + ext_content: ExtContent<'a>, + parens: Parens, +) { + if let ExtContent::Content(_, content) = ext_content { + // This is an open record or tag union, so print the variable + // right after the '}' or ']' + // + // e.g. the "*" at the end of `{ x: I64 }*` + // or the "r" at the end of `{ x: I64 }r` + write_content(env, content, subs, buf, parens) + } +} + +fn write_sorted_tags2<'a>( + env: &Env, + subs: &'a Subs, + buf: &mut String, + tags: &UnionTags, + ext_var: Variable, +) -> ExtContent<'a> { + // Sort the fields so they always end up in the same order. + let (it, new_ext_var) = tags.unsorted_iterator_and_ext(subs, ext_var); + let mut sorted_fields: Vec<_> = it.collect(); + + let interns = &env.interns; + let home = env.home; + + sorted_fields.sort_by(|(a, _), (b, _)| { + a.as_ident_str(interns, home) + .cmp(&b.as_ident_str(interns, home)) + }); + + let mut any_written_yet = false; + + for (label, vars) in sorted_fields { + if any_written_yet { + buf.push_str(", "); + } else { + any_written_yet = true; + } + + buf.push_str(label.as_ident_str(interns, home).as_str()); + + for var in vars { + buf.push(' '); + write_content( + env, + subs.get_content_without_compacting(*var), + subs, + buf, + Parens::InTypeParam, + ); + } + } + + ExtContent::from_var(subs, new_ext_var) +} + fn write_sorted_tags<'a>( env: &Env, subs: &'a Subs, buf: &mut String, tags: &MutMap>, ext_var: Variable, -) -> Result<(), (Variable, &'a Content)> { +) -> ExtContent<'a> { // Sort the fields so they always end up in the same order. let mut sorted_fields = Vec::with_capacity(tags.len()); @@ -362,7 +461,7 @@ fn write_sorted_tags<'a>( // If the `ext` contains tags, merge them into the list of tags. // this can occur when inferring mutually recursive tags let mut from_ext = Default::default(); - let ext_content = chase_ext_tag_union(subs, ext_var, &mut from_ext); + let _ext_content = chase_ext_tag_union(subs, ext_var, &mut from_ext); for (tag_name, arguments) in from_ext.iter() { sorted_fields.push((tag_name, arguments)); @@ -399,19 +498,31 @@ fn write_sorted_tags<'a>( } } - ext_content + ExtContent::from_var(subs, ext_var) } fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut String, parens: Parens) { use crate::subs::FlatType::*; match flat_type { - Apply(symbol, args) => write_apply(env, *symbol, args, subs, buf, parens), + Apply(symbol, args) => write_apply( + env, + *symbol, + subs.get_subs_slice(*args.as_subs_slice()), + subs, + buf, + parens, + ), EmptyRecord => buf.push_str(EMPTY_RECORD), EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), - Func(args, _closure, ret) => { - write_fn(env, subs.get_subs_slice(*args), *ret, subs, buf, parens) - } + Func(args, _closure, ret) => write_fn( + env, + subs.get_subs_slice(*args.as_subs_slice()), + *ret, + subs, + buf, + parens, + ), Record(fields, ext_var) => { use crate::types::{gather_fields, RecordStructure}; @@ -476,54 +587,33 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin TagUnion(tags, ext_var) => { buf.push_str("[ "); - let ext_content = write_sorted_tags(env, subs, buf, tags, *ext_var); + let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var); buf.push_str(" ]"); - if let Err((_, content)) = ext_content { - // This is an open tag union, so print the variable - // right after the ']' - // - // e.g. the "*" at the end of `{ x: I64 }*` - // or the "r" at the end of `{ x: I64 }r` - write_content(env, content, subs, buf, parens) - } + write_ext_content(env, subs, buf, ext_content, parens) } FunctionOrTagUnion(tag_name, _, ext_var) => { buf.push_str("[ "); let mut tags: MutMap = MutMap::default(); - tags.insert(*tag_name.clone(), vec![]); + tags.insert(subs[*tag_name].clone(), vec![]); let ext_content = write_sorted_tags(env, subs, buf, &tags, *ext_var); buf.push_str(" ]"); - if let Err((_, content)) = ext_content { - // This is an open tag union, so print the variable - // right after the ']' - // - // e.g. the "*" at the end of `{ x: I64 }*` - // or the "r" at the end of `{ x: I64 }r` - write_content(env, content, subs, buf, parens) - } + write_ext_content(env, subs, buf, ext_content, parens) } RecursiveTagUnion(rec_var, tags, ext_var) => { buf.push_str("[ "); - let ext_content = write_sorted_tags(env, subs, buf, tags, *ext_var); + let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var); buf.push_str(" ]"); - if let Err((_, content)) = ext_content { - // This is an open tag union, so print the variable - // right after the ']' - // - // e.g. the "*" at the end of `{ x: I64 }*` - // or the "r" at the end of `{ x: I64 }r` - write_content(env, content, subs, buf, parens) - } + write_ext_content(env, subs, buf, ext_content, parens); buf.push_str(" as "); write_content( @@ -548,54 +638,41 @@ pub fn chase_ext_tag_union<'a>( use FlatType::*; match subs.get_content_without_compacting(var) { Content::Structure(EmptyTagUnion) => Ok(()), - Content::Structure(TagUnion(tags, ext_var)) - | Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => { - for (label, vars) in tags { - fields.push((label.clone(), vars.to_vec())); + Content::Structure(TagUnion(tags, ext_var)) => { + for (name_index, slice_index) in tags.iter_all() { + let subs_slice = subs[slice_index]; + let slice = subs.get_subs_slice(*subs_slice.as_subs_slice()); + let tag_name = subs[name_index].clone(); + + fields.push((tag_name, slice.to_vec())); + } + + chase_ext_tag_union(subs, *ext_var, fields) + } + + Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => { + for (name_index, slice_index) in tags.iter_all() { + let subs_slice = subs[slice_index]; + let slice = subs.get_subs_slice(*subs_slice.as_subs_slice()); + let tag_name = subs[name_index].clone(); + + fields.push((tag_name, slice.to_vec())); } chase_ext_tag_union(subs, *ext_var, fields) } Content::Structure(FunctionOrTagUnion(tag_name, _, ext_var)) => { - fields.push((*tag_name.clone(), vec![])); + fields.push((subs[*tag_name].clone(), vec![])); chase_ext_tag_union(subs, *ext_var, fields) } + Content::Alias(_, _, var) => chase_ext_tag_union(subs, *var, fields), content => Err((var, content)), } } -pub fn chase_ext_record( - subs: &Subs, - var: Variable, - fields: &mut MutMap>, -) -> Result<(), (Variable, Content)> { - use crate::subs::Content::*; - use crate::subs::FlatType::*; - - match subs.get_content_without_compacting(var) { - Structure(Record(sub_fields, sub_ext)) => { - for (i1, i2, i3) in sub_fields.iter_all() { - let label = &subs[i1]; - let var = subs[i2]; - let record_field = subs[i3].map(|_| var); - - fields.insert(label.clone(), record_field); - } - - chase_ext_record(subs, *sub_ext, fields) - } - - Structure(EmptyRecord) => Ok(()), - - Alias(_, _, var) => chase_ext_record(subs, *var, fields), - - content => Err((var, content.clone())), - } -} - fn write_apply( env: &Env, symbol: Symbol, diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index b3b1a10d86..f796e127fb 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -119,18 +119,24 @@ fn hash_solved_type_help( hash_solved_type_help(ext, flex_vars, state); } - Alias(name, arguments, actual) => { + Alias(name, arguments, solved_lambda_sets, actual) => { name.hash(state); for (name, x) in arguments { name.hash(state); hash_solved_type_help(x, flex_vars, state); } + + for set in solved_lambda_sets { + hash_solved_type_help(&set.0, flex_vars, state); + } + hash_solved_type_help(actual, flex_vars, state); } HostExposedAlias { name, arguments, + lambda_set_variables: solved_lambda_sets, actual, actual_var, } => { @@ -139,6 +145,11 @@ fn hash_solved_type_help( name.hash(state); hash_solved_type_help(x, flex_vars, state); } + + for set in solved_lambda_sets { + hash_solved_type_help(&set.0, flex_vars, state); + } + hash_solved_type_help(actual, flex_vars, state); var_id_hash_help(*actual_var, flex_vars, state); } @@ -156,6 +167,9 @@ fn var_id_hash_help(var_id: VarId, flex_vars: &mut Vec, state: } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SolvedLambdaSet(pub SolvedType); + /// This is a fully solved type, with no Variables remaining in it. #[derive(Debug, Clone, Eq)] pub enum SolvedType { @@ -183,11 +197,18 @@ pub enum SolvedType { Erroneous(Problem), /// A type alias - Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box), + /// TODO transmit lambda sets! + Alias( + Symbol, + Vec<(Lowercase, SolvedType)>, + Vec, + Box, + ), HostExposedAlias { name: Symbol, arguments: Vec<(Lowercase, SolvedType)>, + lambda_set_variables: Vec, actual_var: VarId, actual: Box, }, @@ -293,19 +314,37 @@ impl SolvedType { ) } Erroneous(problem) => SolvedType::Erroneous(problem.clone()), - Alias(symbol, args, box_type) => { + Alias { + symbol, + type_arguments, + lambda_set_variables, + actual: box_type, + .. + } => { let solved_type = Self::from_type(solved_subs, box_type); - let mut solved_args = Vec::with_capacity(args.len()); + let mut solved_args = Vec::with_capacity(type_arguments.len()); - for (name, var) in args { + for (name, var) in type_arguments { solved_args.push((name.clone(), Self::from_type(solved_subs, var))); } - SolvedType::Alias(*symbol, solved_args, Box::new(solved_type)) + let mut solved_lambda_sets = Vec::with_capacity(lambda_set_variables.len()); + + for var in lambda_set_variables { + solved_lambda_sets.push(SolvedLambdaSet(Self::from_type(solved_subs, &var.0))); + } + + SolvedType::Alias( + *symbol, + solved_args, + solved_lambda_sets, + Box::new(solved_type), + ) } HostExposedAlias { name, - arguments, + type_arguments: arguments, + lambda_set_variables, actual_var, actual, } => { @@ -316,9 +355,15 @@ impl SolvedType { solved_args.push((name.clone(), Self::from_type(solved_subs, var))); } + let mut solved_lambda_sets = Vec::with_capacity(lambda_set_variables.len()); + for var in lambda_set_variables { + solved_lambda_sets.push(SolvedLambdaSet(Self::from_type(solved_subs, &var.0))); + } + SolvedType::HostExposedAlias { name: *name, arguments: solved_args, + lambda_set_variables: solved_lambda_sets, actual_var: VarId::from_var(*actual_var, solved_subs.inner()), actual: Box::new(solved_type), } @@ -352,16 +397,29 @@ impl SolvedType { Alias(symbol, args, actual_var) => { let mut new_args = Vec::with_capacity(args.len()); - for (arg_name, arg_var) in args { + for (name_index, var_index) in args.named_type_arguments() { + let arg_var = subs[var_index]; + new_args.push(( - arg_name.clone(), - Self::from_var_help(subs, recursion_vars, *arg_var), + subs[name_index].clone(), + Self::from_var_help(subs, recursion_vars, arg_var), )); } + let mut solved_lambda_sets = Vec::with_capacity(0); + for var_index in args.unnamed_type_arguments() { + let var = subs[var_index]; + + solved_lambda_sets.push(SolvedLambdaSet(Self::from_var_help( + subs, + recursion_vars, + var, + ))); + } + let aliased_to = Self::from_var_help(subs, recursion_vars, *actual_var); - SolvedType::Alias(*symbol, new_args, Box::new(aliased_to)) + SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to)) } Error => SolvedType::Error, } @@ -378,8 +436,8 @@ impl SolvedType { Apply(symbol, args) => { let mut new_args = Vec::with_capacity(args.len()); - for var in args.iter().copied() { - new_args.push(Self::from_var_help(subs, recursion_vars, var)); + for var in subs.get_subs_slice(*args.as_subs_slice()) { + new_args.push(Self::from_var_help(subs, recursion_vars, *var)); } SolvedType::Apply(*symbol, new_args) @@ -387,7 +445,7 @@ impl SolvedType { Func(args, closure, ret) => { let mut new_args = Vec::with_capacity(args.len()); - for var in subs.get_subs_slice(*args) { + for var in subs.get_subs_slice(*args.as_subs_slice()) { new_args.push(Self::from_var_help(subs, recursion_vars, *var)); } @@ -420,13 +478,16 @@ impl SolvedType { TagUnion(tags, ext_var) => { let mut new_tags = Vec::with_capacity(tags.len()); - for (tag_name, args) in tags { - let mut new_args = Vec::with_capacity(args.len()); + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; - for var in args { - new_args.push(Self::from_var_help(subs, recursion_vars, *var)); + let mut new_args = Vec::with_capacity(slice.len()); + + for var_index in slice { + let var = subs[var_index]; + new_args.push(Self::from_var_help(subs, recursion_vars, var)); } - + let tag_name = subs[name_index].clone(); new_tags.push((tag_name.clone(), new_args)); } @@ -437,20 +498,23 @@ impl SolvedType { FunctionOrTagUnion(tag_name, symbol, ext_var) => { let ext = Self::from_var_help(subs, recursion_vars, *ext_var); - SolvedType::FunctionOrTagUnion(*tag_name.clone(), *symbol, Box::new(ext)) + SolvedType::FunctionOrTagUnion(subs[*tag_name].clone(), *symbol, Box::new(ext)) } RecursiveTagUnion(rec_var, tags, ext_var) => { recursion_vars.insert(subs, *rec_var); let mut new_tags = Vec::with_capacity(tags.len()); - for (tag_name, args) in tags { - let mut new_args = Vec::with_capacity(args.len()); + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; - for var in args { - new_args.push(Self::from_var_help(subs, recursion_vars, *var)); + let mut new_args = Vec::with_capacity(slice.len()); + + for var_index in slice { + let var = subs[var_index]; + new_args.push(Self::from_var_help(subs, recursion_vars, var)); } - + let tag_name = subs[name_index].clone(); new_tags.push((tag_name.clone(), new_args)); } @@ -607,20 +671,35 @@ pub fn to_type( Box::new(to_type(ext, free_vars, var_store)), ) } - Alias(symbol, solved_type_variables, solved_actual) => { + Alias(symbol, solved_type_variables, solved_lambda_sets, solved_actual) => { let mut type_variables = Vec::with_capacity(solved_type_variables.len()); for (lowercase, solved_arg) in solved_type_variables { type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store))); } + let mut lambda_set_variables = Vec::with_capacity(solved_lambda_sets.len()); + for solved_set in solved_lambda_sets { + lambda_set_variables.push(crate::types::LambdaSet(to_type( + &solved_set.0, + free_vars, + var_store, + ))) + } + let actual = to_type(solved_actual, free_vars, var_store); - Type::Alias(*symbol, type_variables, Box::new(actual)) + Type::Alias { + symbol: *symbol, + type_arguments: type_variables, + lambda_set_variables, + actual: Box::new(actual), + } } HostExposedAlias { name, arguments: solved_type_variables, + lambda_set_variables: solved_lambda_sets, actual_var, actual: solved_actual, } => { @@ -630,11 +709,21 @@ pub fn to_type( type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store))); } + let mut lambda_set_variables = Vec::with_capacity(solved_lambda_sets.len()); + for solved_set in solved_lambda_sets { + lambda_set_variables.push(crate::types::LambdaSet(to_type( + &solved_set.0, + free_vars, + var_store, + ))) + } + let actual = to_type(solved_actual, free_vars, var_store); Type::HostExposedAlias { name: *name, - arguments: type_variables, + type_arguments: type_variables, + lambda_set_variables, actual_var: var_id_to_flex_var(*actual_var, free_vars, var_store), actual: Box::new(actual), } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 861d5a8f3e..6625ab0630 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1,5 +1,5 @@ use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt}; -use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_collections::all::{ImMap, ImSet, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use std::fmt; @@ -9,9 +9,9 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -static_assertions::assert_eq_size!([u8; 72], Descriptor); -static_assertions::assert_eq_size!([u8; 56], Content); -static_assertions::assert_eq_size!([u8; 48], FlatType); +static_assertions::assert_eq_size!([u8; 48], Descriptor); +static_assertions::assert_eq_size!([u8; 32], Content); +static_assertions::assert_eq_size!([u8; 24], FlatType); static_assertions::assert_eq_size!([u8; 48], Problem); #[derive(Clone, Copy, Hash, PartialEq, Eq)] @@ -49,13 +49,29 @@ struct ErrorTypeState { problems: Vec, } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct Subs { utable: UnificationTable>, pub variables: Vec, pub tag_names: Vec, pub field_names: Vec, pub record_fields: Vec>, + pub variable_slices: Vec, +} + +impl Default for Subs { + fn default() -> Self { + Subs { + utable: Default::default(), + variables: Default::default(), + tag_names: Default::default(), + field_names: Default::default(), + record_fields: Default::default(), + // store an empty slice at the first position + // used for "TagOrFunction" + variable_slices: vec![VariableSubsSlice::default()], + } + } } /// A slice into the Vec of subs @@ -99,6 +115,20 @@ impl std::ops::Index> for Subs { } } +impl std::ops::Index> for Subs { + type Output = TagName; + + fn index(&self, index: SubsIndex) -> &Self::Output { + &self.tag_names[index.start as usize] + } +} + +impl std::ops::IndexMut> for Subs { + fn index_mut(&mut self, index: SubsIndex) -> &mut Self::Output { + &mut self.tag_names[index.start as usize] + } +} + impl std::ops::IndexMut> for Subs { fn index_mut(&mut self, index: SubsIndex) -> &mut Self::Output { &mut self.field_names[index.start as usize] @@ -119,6 +149,20 @@ impl std::ops::IndexMut>> for Subs { } } +impl std::ops::Index> for Subs { + type Output = VariableSubsSlice; + + fn index(&self, index: SubsIndex) -> &Self::Output { + &self.variable_slices[index.start as usize] + } +} + +impl std::ops::IndexMut> for Subs { + fn index_mut(&mut self, index: SubsIndex) -> &mut Self::Output { + &mut self.variable_slices[index.start as usize] + } +} + // custom debug impl std::fmt::Debug for SubsIndex { @@ -186,15 +230,15 @@ impl SubsSlice { &mut slice[self.start as usize..][..self.length as usize] } - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.length as usize } - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.len() == 0 } - pub fn new(start: u32, length: u16) -> Self { + pub const fn new(start: u32, length: u16) -> Self { Self { start, length, @@ -254,7 +298,119 @@ impl GetSubsSlice for Subs { impl fmt::Debug for Subs { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.utable.fmt(f) + writeln!(f)?; + for i in 0..self.len() { + let var = Variable(i as u32); + let desc = self.get_without_compacting(var); + + let root = self.get_root_key_without_compacting(var); + + if var == root { + write!(f, "{} => ", i)?; + + subs_fmt_desc(&desc, self, f)?; + } else { + write!(f, "{} => <{:?}>", i, root)?; + } + + writeln!(f)?; + } + + Ok(()) + } +} + +fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result { + subs_fmt_content(&this.content, subs, f)?; + + write!(f, " r: {:?}", &this.rank)?; + write!(f, " m: {:?}", &this.mark) +} + +pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs); + +impl<'a> fmt::Debug for SubsFmtContent<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + subs_fmt_content(self.0, self.1, f) + } +} + +fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result { + match this { + Content::FlexVar(name) => write!(f, "Flex({:?})", name), + Content::RigidVar(name) => write!(f, "Rigid({:?})", name), + Content::RecursionVar { + structure, + opt_name, + } => write!(f, "Recursion({:?}, {:?})", structure, opt_name), + Content::Structure(flat_type) => subs_fmt_flat_type(flat_type, subs, f), + Content::Alias(name, arguments, actual) => { + let slice = subs.get_subs_slice(*arguments.variables().as_subs_slice()); + + write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual) + } + Content::Error => write!(f, "Error"), + } +} + +pub struct SubsFmtFlatType<'a>(pub &'a FlatType, pub &'a Subs); + +impl<'a> fmt::Debug for SubsFmtFlatType<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + subs_fmt_flat_type(self.0, self.1, f) + } +} + +fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result { + match this { + FlatType::Apply(name, arguments) => { + let slice = subs.get_subs_slice(*arguments.as_subs_slice()); + + write!(f, "Apply({:?}, {:?})", name, slice) + } + FlatType::Func(arguments, lambda_set, result) => { + let slice = subs.get_subs_slice(*arguments.as_subs_slice()); + write!(f, "Func({:?}, {:?}, {:?})", slice, lambda_set, result) + } + FlatType::Record(fields, ext) => { + write!(f, "{{ ")?; + + let (it, new_ext) = fields.sorted_iterator_and_ext(subs, *ext); + for (name, content) in it { + let separator = match content { + RecordField::Optional(_) => '?', + RecordField::Required(_) => ':', + RecordField::Demanded(_) => ':', + }; + write!(f, "{:?} {} {:?}, ", name, separator, content)?; + } + + write!(f, "}}<{:?}>", new_ext) + } + FlatType::TagUnion(tags, ext) => { + write!(f, "[ ")?; + + let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext); + for (name, slice) in it { + write!(f, "{:?} {:?}, ", name, slice)?; + } + + write!(f, "]<{:?}>", new_ext) + } + FlatType::FunctionOrTagUnion(_, _, _) => todo!(), + FlatType::RecursiveTagUnion(rec, tags, ext) => { + write!(f, "[ ")?; + + let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext); + for (name, slice) in it { + write!(f, "{:?} {:?}, ", name, slice)?; + } + + write!(f, "]<{:?}> as <{:?}>", new_ext, rec) + } + FlatType::Erroneous(e) => write!(f, "Erroneous({:?})", e), + FlatType::EmptyRecord => write!(f, "EmptyRecord"), + FlatType::EmptyTagUnion => write!(f, "EmptyTagUnion"), } } @@ -351,7 +507,24 @@ impl From for Option { #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Variable(u32); -impl Variable { +macro_rules! define_const_var { + ($($(:pub)? $name:ident),* $(,)?) => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + enum ConstVariables { + $( $name, )* + FINAL_CONST_VAR + } + + impl Variable { + $( pub const $name: Variable = Variable(ConstVariables::$name as u32); )* + + pub const NUM_RESERVED_VARS: usize = ConstVariables::FINAL_CONST_VAR as usize; + } + + }; +} + +define_const_var! { // Reserved for indicating the absence of a variable. // This lets us avoid using Option for the Descriptor's // copy field, which is a relevant space savings because we make @@ -359,22 +532,146 @@ impl Variable { // // Also relevant: because this has the value 0, Descriptors can 0-initialize // to it in bulk - which is relevant, because Descriptors get initialized in bulk. - const NULL: Variable = Variable(0); + NULL, - pub const EMPTY_RECORD: Variable = Variable(1); - pub const EMPTY_TAG_UNION: Variable = Variable(2); - // Builtins - const BOOL_ENUM: Variable = Variable(3); - pub const BOOL: Variable = Variable(4); // Used in `if` conditions + :pub EMPTY_RECORD, + :pub EMPTY_TAG_UNION, - pub const NUM_RESERVED_VARS: usize = 5; + BOOL_ENUM, + :pub BOOL, + ORDER_ENUM, + :pub ORDER, + + // [ @Signed8 ] + AT_SIGNED8, + AT_SIGNED16, + AT_SIGNED32, + AT_SIGNED64, + AT_SIGNED128, + + AT_UNSIGNED8, + AT_UNSIGNED16, + AT_UNSIGNED32, + AT_UNSIGNED64, + AT_UNSIGNED128, + + AT_NATURAL, + + AT_BINARY32, + AT_BINARY64, + + AT_DECIMAL, + + // Signed8 : [ @Signed8 ] + :pub SIGNED8, + :pub SIGNED16, + :pub SIGNED32, + :pub SIGNED64, + :pub SIGNED128, + + :pub UNSIGNED8, + :pub UNSIGNED16, + :pub UNSIGNED32, + :pub UNSIGNED64, + :pub UNSIGNED128, + + :pub NATURAL, + + :pub BINARY32, + :pub BINARY64, + + :pub DECIMAL, + + // [ @Integer Signed8 ] + AT_INTEGER_SIGNED8, + AT_INTEGER_SIGNED16, + AT_INTEGER_SIGNED32, + AT_INTEGER_SIGNED64, + AT_INTEGER_SIGNED128, + + AT_INTEGER_UNSIGNED8, + AT_INTEGER_UNSIGNED16, + AT_INTEGER_UNSIGNED32, + AT_INTEGER_UNSIGNED64, + AT_INTEGER_UNSIGNED128, + + AT_INTEGER_NATURAL, + + // Integer Signed8 : [ @Integer Signed8 ] + INTEGER_SIGNED8, + INTEGER_SIGNED16, + INTEGER_SIGNED32, + INTEGER_SIGNED64, + INTEGER_SIGNED128, + + INTEGER_UNSIGNED8, + INTEGER_UNSIGNED16, + INTEGER_UNSIGNED32, + INTEGER_UNSIGNED64, + INTEGER_UNSIGNED128, + + INTEGER_NATURAL, + + // [ @Num (Integer Signed8) ] + AT_NUM_INTEGER_SIGNED8, + AT_NUM_INTEGER_SIGNED16, + AT_NUM_INTEGER_SIGNED32, + AT_NUM_INTEGER_SIGNED64, + AT_NUM_INTEGER_SIGNED128, + + AT_NUM_INTEGER_UNSIGNED8, + AT_NUM_INTEGER_UNSIGNED16, + AT_NUM_INTEGER_UNSIGNED32, + AT_NUM_INTEGER_UNSIGNED64, + AT_NUM_INTEGER_UNSIGNED128, + + AT_NUM_INTEGER_NATURAL, + + // Num (Integer Signed8) + NUM_INTEGER_SIGNED8, + NUM_INTEGER_SIGNED16, + NUM_INTEGER_SIGNED32, + NUM_INTEGER_SIGNED64, + NUM_INTEGER_SIGNED128, + + NUM_INTEGER_UNSIGNED8, + NUM_INTEGER_UNSIGNED16, + NUM_INTEGER_UNSIGNED32, + NUM_INTEGER_UNSIGNED64, + NUM_INTEGER_UNSIGNED128, + + NUM_INTEGER_NATURAL, + + // I8 : Num (Integer Signed8) + :pub I8, + :pub I16, + :pub I32, + :pub I64, + :pub I128, + + :pub U8, + :pub U16, + :pub U32, + :pub U64, + :pub U128, + + :pub NAT, + + :pub F32, + :pub F64, + + :pub DEC, +} + +impl Variable { const FIRST_USER_SPACE_VAR: Variable = Variable(Self::NUM_RESERVED_VARS as u32); /// # Safety /// /// This should only ever be called from tests! pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self { + debug_assert!(v <= Self::NUM_RESERVED_VARS as u32); Variable(v) } @@ -412,7 +709,7 @@ impl UnifyKey for Variable { } #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct LambdaSet(Variable); +pub struct LambdaSet(pub Variable); impl fmt::Debug for LambdaSet { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -424,6 +721,10 @@ impl LambdaSet { pub fn into_inner(self) -> Variable { self.0 } + + pub fn as_inner(&self) -> &Variable { + &self.0 + } } impl From for LambdaSet { @@ -455,6 +756,226 @@ impl fmt::Debug for VarId { } } +#[allow(clippy::too_many_arguments)] +fn integer_type( + subs: &mut Subs, + + num_at_signed64: Symbol, + num_signed64: Symbol, + num_i64: Symbol, + + at_signed64: Variable, + signed64: Variable, + + at_integer_signed64: Variable, + integer_signed64: Variable, + + at_num_integer_signed64: Variable, + num_integer_signed64: Variable, + + var_i64: Variable, +) { + let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_signed64), [])]); + + subs.set_content(at_signed64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + subs.set_content(signed64, { + Content::Alias(num_signed64, AliasVariables::default(), at_signed64) + }); + + // Num.Integer Num.Signed64 + + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_INTEGER), [signed64])], + ); + subs.set_content(at_integer_signed64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [("range".into(), signed64)], []); + subs.set_content(num_integer_signed64, { + Content::Alias(Symbol::NUM_INTEGER, vars, at_signed64) + }); + + // Num.Num (Num.Integer Num.Signed64) + + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_NUM), [integer_signed64])], + ); + subs.set_content(at_num_integer_signed64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [("range".into(), integer_signed64)], []); + subs.set_content(num_integer_signed64, { + Content::Alias(Symbol::NUM_NUM, vars, at_num_integer_signed64) + }); + + subs.set_content(var_i64, { + Content::Alias(num_i64, AliasVariables::default(), num_integer_signed64) + }); +} + +fn define_integer_types(subs: &mut Subs) { + integer_type( + subs, + Symbol::NUM_AT_SIGNED128, + Symbol::NUM_SIGNED128, + Symbol::NUM_I128, + Variable::AT_SIGNED128, + Variable::SIGNED128, + Variable::AT_INTEGER_SIGNED128, + Variable::INTEGER_SIGNED128, + Variable::AT_NUM_INTEGER_SIGNED128, + Variable::NUM_INTEGER_SIGNED128, + Variable::I128, + ); + + integer_type( + subs, + Symbol::NUM_AT_SIGNED64, + Symbol::NUM_SIGNED64, + Symbol::NUM_I64, + Variable::AT_SIGNED64, + Variable::SIGNED64, + Variable::AT_INTEGER_SIGNED64, + Variable::INTEGER_SIGNED64, + Variable::AT_NUM_INTEGER_SIGNED64, + Variable::NUM_INTEGER_SIGNED64, + Variable::I64, + ); + + integer_type( + subs, + Symbol::NUM_AT_SIGNED32, + Symbol::NUM_SIGNED32, + Symbol::NUM_I32, + Variable::AT_SIGNED32, + Variable::SIGNED32, + Variable::AT_INTEGER_SIGNED32, + Variable::INTEGER_SIGNED32, + Variable::AT_NUM_INTEGER_SIGNED32, + Variable::NUM_INTEGER_SIGNED32, + Variable::I32, + ); + + integer_type( + subs, + Symbol::NUM_AT_SIGNED16, + Symbol::NUM_SIGNED16, + Symbol::NUM_I16, + Variable::AT_SIGNED16, + Variable::SIGNED16, + Variable::AT_INTEGER_SIGNED16, + Variable::INTEGER_SIGNED16, + Variable::AT_NUM_INTEGER_SIGNED16, + Variable::NUM_INTEGER_SIGNED16, + Variable::I16, + ); + + integer_type( + subs, + Symbol::NUM_AT_SIGNED8, + Symbol::NUM_SIGNED8, + Symbol::NUM_I8, + Variable::AT_SIGNED8, + Variable::SIGNED8, + Variable::AT_INTEGER_SIGNED8, + Variable::INTEGER_SIGNED8, + Variable::AT_NUM_INTEGER_SIGNED8, + Variable::NUM_INTEGER_SIGNED8, + Variable::I8, + ); + + integer_type( + subs, + Symbol::NUM_AT_UNSIGNED128, + Symbol::NUM_UNSIGNED128, + Symbol::NUM_U128, + Variable::AT_UNSIGNED128, + Variable::UNSIGNED128, + Variable::AT_INTEGER_UNSIGNED128, + Variable::INTEGER_UNSIGNED128, + Variable::AT_NUM_INTEGER_UNSIGNED128, + Variable::NUM_INTEGER_UNSIGNED128, + Variable::U128, + ); + + integer_type( + subs, + Symbol::NUM_AT_UNSIGNED64, + Symbol::NUM_UNSIGNED64, + Symbol::NUM_U64, + Variable::AT_UNSIGNED64, + Variable::UNSIGNED64, + Variable::AT_INTEGER_UNSIGNED64, + Variable::INTEGER_UNSIGNED64, + Variable::AT_NUM_INTEGER_UNSIGNED64, + Variable::NUM_INTEGER_UNSIGNED64, + Variable::U64, + ); + + integer_type( + subs, + Symbol::NUM_AT_UNSIGNED32, + Symbol::NUM_UNSIGNED32, + Symbol::NUM_U32, + Variable::AT_UNSIGNED32, + Variable::UNSIGNED32, + Variable::AT_INTEGER_UNSIGNED32, + Variable::INTEGER_UNSIGNED32, + Variable::AT_NUM_INTEGER_UNSIGNED32, + Variable::NUM_INTEGER_UNSIGNED32, + Variable::U32, + ); + + integer_type( + subs, + Symbol::NUM_AT_UNSIGNED16, + Symbol::NUM_UNSIGNED16, + Symbol::NUM_U16, + Variable::AT_UNSIGNED16, + Variable::UNSIGNED16, + Variable::AT_INTEGER_UNSIGNED16, + Variable::INTEGER_UNSIGNED16, + Variable::AT_NUM_INTEGER_UNSIGNED16, + Variable::NUM_INTEGER_UNSIGNED16, + Variable::U16, + ); + + integer_type( + subs, + Symbol::NUM_AT_UNSIGNED8, + Symbol::NUM_UNSIGNED8, + Symbol::NUM_U8, + Variable::AT_UNSIGNED8, + Variable::UNSIGNED8, + Variable::AT_INTEGER_UNSIGNED8, + Variable::INTEGER_UNSIGNED8, + Variable::AT_NUM_INTEGER_UNSIGNED8, + Variable::NUM_INTEGER_UNSIGNED8, + Variable::U8, + ); + + // integer_type( + // subs, + // Symbol::NUM_AT_NATURAL, + // Symbol::NUM_NATURAL, + // Symbol::NUM_NAT, + // Variable::AT_NATURAL, + // Variable::NATURAL, + // Variable::AT_INTEGER_NATURAL, + // Variable::INTEGER_NATURAL, + // Variable::AT_NUM_INTEGER_NATURAL, + // Variable::NUM_INTEGER_NATURAL, + // Variable::NAT, + // ); +} + impl Subs { pub fn new(var_store: VarStore) -> Self { let entries = var_store.next; @@ -474,6 +995,8 @@ impl Subs { subs.utable.new_key(flex_var_descriptor()); } + define_integer_types(&mut subs); + subs.set_content( Variable::EMPTY_RECORD, Content::Structure(FlatType::EmptyRecord), @@ -483,16 +1006,27 @@ impl Subs { Content::Structure(FlatType::EmptyTagUnion), ); - subs.set_content(Variable::BOOL_ENUM, { - let mut tags = MutMap::default(); - tags.insert(TagName::Global("False".into()), vec![]); - tags.insert(TagName::Global("True".into()), vec![]); + let bool_union_tags = UnionTags::insert_into_subs( + &mut subs, + [ + (TagName::Global("False".into()), []), + (TagName::Global("True".into()), []), + ], + ); - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + subs.set_content(Variable::BOOL_ENUM, { + Content::Structure(FlatType::TagUnion( + bool_union_tags, + Variable::EMPTY_TAG_UNION, + )) }); subs.set_content(Variable::BOOL, { - Content::Alias(Symbol::BOOL_BOOL, vec![], Variable::BOOL_ENUM) + Content::Alias( + Symbol::BOOL_BOOL, + AliasVariables::default(), + Variable::BOOL_ENUM, + ) }); subs @@ -538,15 +1072,15 @@ impl Subs { &self.utable.probe_value_ref(key).value } - pub fn get_rank(&mut self, key: Variable) -> Rank { + pub fn get_rank(&self, key: Variable) -> Rank { self.utable.probe_value_ref(key).value.rank } - pub fn get_mark(&mut self, key: Variable) -> Mark { + pub fn get_mark(&self, key: Variable) -> Mark { self.utable.probe_value_ref(key).value.mark } - pub fn get_rank_mark(&mut self, key: Variable) -> (Rank, Mark) { + pub fn get_rank_mark(&self, key: Variable) -> (Rank, Mark) { let desc = &self.utable.probe_value_ref(key).value; (desc.rank, desc.mark) @@ -615,7 +1149,7 @@ impl Subs { self.utable.is_redirect(var) } - pub fn occurs(&self, var: Variable) -> Option<(Variable, Vec)> { + pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { occurs(self, &ImSet::default(), var) } @@ -705,6 +1239,10 @@ pub struct Rank(u32); impl Rank { pub const NONE: Rank = Rank(0); + pub fn is_none(&self) -> bool { + *self == Self::NONE + } + pub fn toplevel() -> Self { Rank(1) } @@ -795,10 +1333,104 @@ pub enum Content { opt_name: Option, }, Structure(FlatType), - Alias(Symbol, Vec<(Lowercase, Variable)>, Variable), + Alias(Symbol, AliasVariables, Variable), Error, } +#[derive(Clone, Copy, Debug, Default)] +pub struct AliasVariables { + lowercases_start: u32, + variables_start: u32, + lowercases_len: u16, + variables_len: u16, +} + +impl AliasVariables { + pub const fn names(&self) -> SubsSlice { + SubsSlice::new(self.lowercases_start, self.lowercases_len) + } + + pub const fn variables(&self) -> VariableSubsSlice { + VariableSubsSlice { + slice: SubsSlice::new(self.variables_start, self.variables_len), + } + } + + pub const fn len(&self) -> usize { + self.lowercases_len as usize + } + + pub const fn is_empty(&self) -> bool { + self.lowercases_len == 0 + } + + pub fn replace_variables( + &mut self, + subs: &mut Subs, + variables: impl IntoIterator, + ) { + let variables_start = subs.variables.len() as u32; + subs.variables.extend(variables); + let variables_len = (subs.variables.len() - variables_start as usize) as u16; + + debug_assert_eq!(variables_len, self.variables_len); + + self.variables_start = variables_start; + } + + pub fn named_type_arguments( + &self, + ) -> impl Iterator, SubsIndex)> { + let names = self.names(); + let vars = self.variables(); + + names.into_iter().zip(vars.into_iter()) + } + + pub fn unnamed_type_arguments(&self) -> impl Iterator> { + self.variables() + .into_iter() + .skip(self.lowercases_len as usize) + } + + pub fn insert_into_subs( + subs: &mut Subs, + type_arguments: I1, + unnamed_arguments: I2, + ) -> Self + where + I1: IntoIterator, + I2: IntoIterator, + { + let lowercases_start = subs.field_names.len() as u32; + let variables_start = subs.variables.len() as u32; + + let it1 = type_arguments.into_iter(); + let it2 = unnamed_arguments.into_iter(); + + subs.variables + .reserve(it1.size_hint().0 + it2.size_hint().0); + subs.field_names.reserve(it1.size_hint().0); + + for (field_name, var) in it1 { + subs.field_names.push(field_name); + subs.variables.push(var); + } + + subs.variables.extend(it2); + + let lowercases_len = (subs.field_names.len() as u32 - lowercases_start) as u16; + let variables_len = (subs.variables.len() as u32 - variables_start) as u16; + + Self { + lowercases_start, + variables_start, + lowercases_len, + variables_len, + } + } +} + impl Content { #[inline(always)] pub fn is_number(&self) -> bool { @@ -828,12 +1460,12 @@ impl Content { #[derive(Clone, Debug)] pub enum FlatType { - Apply(Symbol, Vec), - Func(SubsSlice, Variable, Variable), + Apply(Symbol, VariableSubsSlice), + Func(VariableSubsSlice, Variable, Variable), Record(RecordFields, Variable), - TagUnion(MutMap>, Variable), - FunctionOrTagUnion(Box, Symbol, Variable), - RecursiveTagUnion(Variable, MutMap>, Variable), + TagUnion(UnionTags, Variable), + FunctionOrTagUnion(SubsIndex, Symbol, Variable), + RecursiveTagUnion(Variable, UnionTags, Variable), Erroneous(Box), EmptyRecord, EmptyTagUnion, @@ -847,6 +1479,300 @@ pub enum Builtin { EmptyRecord, } +#[derive(Clone, Copy, Debug, Default)] +pub struct VariableSubsSlice { + slice: SubsSlice, +} + +impl VariableSubsSlice { + pub fn len(&self) -> usize { + self.slice.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn as_subs_slice(&self) -> &SubsSlice { + &self.slice + } + + pub fn new(start: u32, length: u16) -> Self { + Self { + slice: SubsSlice::new(start, length), + } + } + + pub fn insert_into_subs(subs: &mut Subs, input: I) -> Self + where + I: IntoIterator, + { + let start = subs.variables.len() as u32; + + subs.variables.extend(input.into_iter()); + + let length = (subs.variables.len() as u32 - start) as u16; + + Self::new(start, length) + } +} + +impl IntoIterator for VariableSubsSlice { + type Item = SubsIndex; + + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.slice.into_iter() + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct UnionTags { + length: u16, + tag_names_start: u32, + variables_start: u32, +} + +impl UnionTags { + pub fn is_newtype_wrapper(&self, subs: &Subs) -> bool { + if self.length != 1 { + return false; + } + + let slice = subs.variable_slices[self.variables_start as usize].slice; + slice.length == 1 + } + + pub fn from_tag_name_index(index: SubsIndex) -> Self { + Self::from_slices( + SubsSlice::new(index.start, 1), + SubsSlice::new(0, 1), // the first variablesubsslice is the empty slice + ) + } + + pub fn from_slices( + tag_names: SubsSlice, + variables: SubsSlice, + ) -> Self { + debug_assert_eq!(tag_names.len(), variables.len()); + + Self { + length: tag_names.len() as u16, + tag_names_start: tag_names.start, + variables_start: variables.start, + } + } + + pub const fn tag_names(&self) -> SubsSlice { + SubsSlice::new(self.tag_names_start, self.length) + } + + pub const fn variables(&self) -> SubsSlice { + SubsSlice::new(self.variables_start, self.length) + } + + pub const fn len(&self) -> usize { + self.length as usize + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn compare(x: &(TagName, T), y: &(TagName, T)) -> std::cmp::Ordering { + first(x, y) + } + pub fn insert_into_subs(subs: &mut Subs, input: I) -> Self + where + I: IntoIterator, + I2: IntoIterator, + { + let tag_names_start = subs.tag_names.len() as u32; + let variables_start = subs.variable_slices.len() as u32; + + let it = input.into_iter(); + let size_hint = it.size_hint().0; + + subs.tag_names.reserve(size_hint); + subs.variable_slices.reserve(size_hint); + + let mut length = 0; + for (k, v) in it { + let variables = VariableSubsSlice::insert_into_subs(subs, v.into_iter()); + + subs.tag_names.push(k); + subs.variable_slices.push(variables); + + length += 1; + } + + Self::from_slices( + SubsSlice::new(tag_names_start, length), + SubsSlice::new(variables_start, length), + ) + } + + pub fn tag_without_arguments(subs: &mut Subs, tag_name: TagName) -> Self { + subs.tag_names.push(tag_name); + + Self { + length: 1, + tag_names_start: (subs.tag_names.len() - 1) as u32, + variables_start: 0, + } + } + + pub fn insert_slices_into_subs(subs: &mut Subs, input: I) -> Self + where + I: IntoIterator, + { + let tag_names_start = subs.tag_names.len() as u32; + let variables_start = subs.variable_slices.len() as u32; + + let it = input.into_iter(); + let size_hint = it.size_hint().0; + + subs.tag_names.reserve(size_hint); + subs.variable_slices.reserve(size_hint); + + let mut length = 0; + for (k, variables) in it { + subs.tag_names.push(k); + subs.variable_slices.push(variables); + + length += 1; + } + + Self { + length, + tag_names_start, + variables_start, + } + } + + pub fn iter_all( + &self, + ) -> impl Iterator, SubsIndex)> { + self.tag_names() + .into_iter() + .zip(self.variables().into_iter()) + } + + #[inline(always)] + pub fn unsorted_iterator<'a>( + &'a self, + subs: &'a Subs, + ext: Variable, + ) -> impl Iterator + 'a { + let (it, _) = crate::types::gather_tags_unsorted_iter(subs, *self, ext); + + it.map(move |(label, slice)| (label, subs.get_subs_slice(*slice.as_subs_slice()))) + } + + pub fn unsorted_iterator_and_ext<'a>( + &'a self, + subs: &'a Subs, + ext: Variable, + ) -> (impl Iterator + 'a, Variable) { + let (it, ext) = crate::types::gather_tags_unsorted_iter(subs, *self, ext); + + ( + it.map(move |(label, slice)| (label, subs.get_subs_slice(*slice.as_subs_slice()))), + ext, + ) + } + + #[inline(always)] + pub fn sorted_iterator_and_ext<'a>( + &'_ self, + subs: &'a Subs, + ext: Variable, + ) -> (SortedTagsIterator<'a>, Variable) { + if is_empty_tag_union(subs, ext) { + ( + Box::new(self.iter_all().map(move |(i1, i2)| { + let tag_name: &TagName = &subs[i1]; + let subs_slice = subs[i2]; + + let slice = subs.get_subs_slice(*subs_slice.as_subs_slice()); + + (tag_name.clone(), slice) + })), + ext, + ) + } else { + let union_structure = crate::types::gather_tags(subs, *self, ext); + + ( + Box::new(union_structure.fields.into_iter()), + union_structure.ext, + ) + } + } + + #[inline(always)] + pub fn sorted_slices_iterator_and_ext<'a>( + &'_ self, + subs: &'a Subs, + ext: Variable, + ) -> (SortedTagsSlicesIterator<'a>, Variable) { + if is_empty_tag_union(subs, ext) { + ( + Box::new(self.iter_all().map(move |(i1, i2)| { + let tag_name: &TagName = &subs[i1]; + let subs_slice = subs[i2]; + + (tag_name.clone(), subs_slice) + })), + ext, + ) + } else { + let (fields, ext) = crate::types::gather_tags_slices(subs, *self, ext); + + (Box::new(fields.into_iter()), ext) + } + } +} + +pub type SortedTagsIterator<'a> = Box + 'a>; +pub type SortedTagsSlicesIterator<'a> = Box + 'a>; + +pub fn is_empty_tag_union(subs: &Subs, mut var: Variable) -> bool { + use crate::subs::Content::*; + use crate::subs::FlatType::*; + + loop { + match subs.get_content_without_compacting(var) { + FlexVar(_) => return true, + Structure(EmptyTagUnion) => return true, + Structure(TagUnion(sub_fields, sub_ext)) => { + if !sub_fields.is_empty() { + return false; + } + + var = *sub_ext; + } + Structure(RecursiveTagUnion(_, sub_fields, sub_ext)) => { + if !sub_fields.is_empty() { + return false; + } + + var = *sub_ext; + } + + Alias(_, _, actual_var) => { + // TODO according to elm/compiler: "TODO may be dropping useful alias info here" + var = *actual_var; + } + + _other => { + return false; + } + } + } +} + #[derive(Clone, Copy, Debug)] pub struct RecordFields { pub length: u16, @@ -862,7 +1788,7 @@ fn first(x: &(K, V), y: &(K, V)) -> std::cmp::Ordering { pub type SortedIterator<'a> = Box)> + 'a>; impl RecordFields { - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.length as usize } @@ -907,8 +1833,15 @@ impl RecordFields { let variables_start = subs.variables.len() as u32; let field_types_start = subs.record_fields.len() as u32; + let it = input.into_iter(); + let size_hint = it.size_hint().0; + + subs.variables.reserve(size_hint); + subs.field_names.reserve(size_hint); + subs.record_fields.reserve(size_hint); + let mut length = 0; - for (k, v) in input { + for (k, v) in it { let var = *v.as_inner(); let record_field = v.map(|_| ()); @@ -1027,17 +1960,17 @@ fn occurs( subs: &Subs, seen: &ImSet, input_var: Variable, -) -> Option<(Variable, Vec)> { +) -> Result<(), (Variable, Vec)> { use self::Content::*; use self::FlatType::*; let root_var = subs.get_root_key_without_compacting(input_var); if seen.contains(&root_var) { - Some((root_var, vec![])) + Err((root_var, vec![])) } else { match subs.get_content_without_compacting(root_var) { - FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => None, + FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()), Structure(flat_type) => { let mut new_seen = seen.clone(); @@ -1045,11 +1978,16 @@ fn occurs( new_seen.insert(root_var); match flat_type { - Apply(_, args) => short_circuit(subs, root_var, &new_seen, args.iter()), + Apply(_, args) => short_circuit( + subs, + root_var, + &new_seen, + subs.get_subs_slice(*args.as_subs_slice()).iter(), + ), Func(arg_vars, closure_var, ret_var) => { let it = once(ret_var) .chain(once(closure_var)) - .chain(subs.get_subs_slice(*arg_vars).iter()); + .chain(subs.get_subs_slice(*arg_vars.as_subs_slice()).iter()); short_circuit(subs, root_var, &new_seen, it) } Record(vars_by_field, ext_var) => { @@ -1059,8 +1997,15 @@ fn occurs( short_circuit(subs, root_var, &new_seen, it) } TagUnion(tags, ext_var) => { - let it = once(ext_var).chain(tags.values().flatten()); - short_circuit(subs, root_var, &new_seen, it) + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + short_circuit_help(subs, root_var, &new_seen, var)?; + } + } + + short_circuit_help(subs, root_var, &new_seen, *ext_var) } FunctionOrTagUnion(_, _, ext_var) => { let it = once(ext_var); @@ -1068,17 +2013,29 @@ fn occurs( } RecursiveTagUnion(_rec_var, tags, ext_var) => { // TODO rec_var is excluded here, verify that this is correct - let it = once(ext_var).chain(tags.values().flatten()); - short_circuit(subs, root_var, &new_seen, it) + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + short_circuit_help(subs, root_var, &new_seen, var)?; + } + } + + short_circuit_help(subs, root_var, &new_seen, *ext_var) } - EmptyRecord | EmptyTagUnion | Erroneous(_) => None, + EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()), } } Alias(_, args, _) => { let mut new_seen = seen.clone(); new_seen.insert(root_var); - let it = args.iter().map(|(_, var)| var); - short_circuit(subs, root_var, &new_seen, it) + + for var_index in args.variables().into_iter() { + let var = subs[var_index]; + short_circuit_help(subs, root_var, &new_seen, var)?; + } + + Ok(()) } } } @@ -1089,17 +2046,29 @@ fn short_circuit<'a, T>( root_key: Variable, seen: &ImSet, iter: T, -) -> Option<(Variable, Vec)> +) -> Result<(), (Variable, Vec)> where T: Iterator, { for var in iter { - if let Some((v, mut vec)) = occurs(subs, seen, *var) { - vec.push(root_key); - return Some((v, vec)); - } + short_circuit_help(subs, root_key, seen, *var)?; } - None + + Ok(()) +} + +fn short_circuit_help( + subs: &Subs, + root_key: Variable, + seen: &ImSet, + var: Variable, +) -> Result<(), (Variable, Vec)> { + if let Err((v, mut vec)) = occurs(subs, seen, var) { + vec.push(root_key); + return Err((v, vec)); + } + + Ok(()) } fn explicit_substitute( @@ -1126,12 +2095,13 @@ fn explicit_substitute( Structure(flat_type) => { match flat_type { Apply(symbol, args) => { - let new_args = args - .iter() - .map(|var| explicit_substitute(subs, from, to, *var, seen)) - .collect(); + for var_index in args.into_iter() { + let var = subs[var_index]; + let answer = explicit_substitute(subs, from, to, var, seen); + subs[var_index] = answer; + } - subs.set_content(in_var, Structure(Apply(symbol, new_args))); + subs.set_content(in_var, Structure(Apply(symbol, args))); } Func(arg_vars, closure_var, ret_var) => { for var_index in arg_vars.into_iter() { @@ -1149,14 +2119,38 @@ fn explicit_substitute( Structure(Func(arg_vars, new_closure_var, new_ret_var)), ); } - TagUnion(mut tags, ext_var) => { + TagUnion(tags, ext_var) => { let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); - for (_, variables) in tags.iter_mut() { - for var in variables.iter_mut() { - *var = explicit_substitute(subs, from, to, *var, seen); + + let mut new_slices = Vec::new(); + for slice_index in tags.variables() { + let slice = subs[slice_index]; + + let mut new_variables = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + let new_var = explicit_substitute(subs, from, to, var, seen); + new_variables.push(new_var); } + + let start = subs.variables.len() as u32; + let length = new_variables.len() as u16; + + subs.variables.extend(new_variables); + + new_slices.push(VariableSubsSlice::new(start, length)); } - subs.set_content(in_var, Structure(TagUnion(tags, new_ext_var))); + + let start = subs.variable_slices.len() as u32; + let length = new_slices.len(); + + subs.variable_slices.extend(new_slices); + + let mut union_tags = tags; + debug_assert_eq!(length, union_tags.len()); + union_tags.variables_start = start; + + subs.set_content(in_var, Structure(TagUnion(union_tags, new_ext_var))); } FunctionOrTagUnion(tag_name, symbol, ext_var) => { let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); @@ -1165,17 +2159,41 @@ fn explicit_substitute( Structure(FunctionOrTagUnion(tag_name, symbol, new_ext_var)), ); } - RecursiveTagUnion(rec_var, mut tags, ext_var) => { + RecursiveTagUnion(rec_var, tags, ext_var) => { // NOTE rec_var is not substituted, verify that this is correct! let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); - for (_, variables) in tags.iter_mut() { - for var in variables.iter_mut() { - *var = explicit_substitute(subs, from, to, *var, seen); + + let mut new_slices = Vec::new(); + for slice_index in tags.variables() { + let slice = subs[slice_index]; + + let mut new_variables = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + let new_var = explicit_substitute(subs, from, to, var, seen); + new_variables.push(new_var); } + + let start = subs.variables.len() as u32; + let length = new_variables.len() as u16; + + subs.variables.extend(new_variables); + + new_slices.push(VariableSubsSlice::new(start, length)); } + + let start = subs.variable_slices.len() as u32; + let length = new_slices.len(); + + subs.variable_slices.extend(new_slices); + + let mut union_tags = tags; + debug_assert_eq!(length, union_tags.len()); + union_tags.variables_start = start; + subs.set_content( in_var, - Structure(RecursiveTagUnion(rec_var, tags, new_ext_var)), + Structure(RecursiveTagUnion(rec_var, union_tags, new_ext_var)), ); } Record(vars_by_field, ext_var) => { @@ -1195,9 +2213,11 @@ fn explicit_substitute( in_var } - Alias(symbol, mut args, actual) => { - for (_, var) in args.iter_mut() { - *var = explicit_substitute(subs, from, to, *var, seen); + Alias(symbol, args, actual) => { + for index in args.variables().into_iter() { + let var = subs[index]; + let new_var = explicit_substitute(subs, from, to, var, seen); + subs[index] = new_var; } let new_actual = explicit_substitute(subs, from, to, actual, seen); @@ -1251,13 +2271,17 @@ fn get_var_names( RigidVar(name) => add_name(subs, 0, name, var, RigidVar, taken_names), - Alias(_, args, _) => args.into_iter().fold(taken_names, |answer, (_, arg_var)| { - get_var_names(subs, arg_var, answer) - }), + Alias(_, args, _) => args + .variables() + .into_iter() + .fold(taken_names, |answer, arg_var| { + get_var_names(subs, subs[arg_var], answer) + }), + Structure(flat_type) => match flat_type { FlatType::Apply(_, args) => { args.into_iter().fold(taken_names, |answer, arg_var| { - get_var_names(subs, arg_var, answer) + get_var_names(subs, subs[arg_var], answer) }) } @@ -1294,9 +2318,11 @@ fn get_var_names( FlatType::TagUnion(tags, ext_var) => { let mut taken_names = get_var_names(subs, ext_var, taken_names); - for vars in tags.values() { - for arg_var in vars { - taken_names = get_var_names(subs, *arg_var, taken_names) + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + taken_names = get_var_names(subs, var, taken_names) } } @@ -1311,9 +2337,11 @@ fn get_var_names( let taken_names = get_var_names(subs, ext_var, taken_names); let mut taken_names = get_var_names(subs, rec_var, taken_names); - for vars in tags.values() { - for arg_var in vars { - taken_names = get_var_names(subs, *arg_var, taken_names) + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let arg_var = subs[var_index]; + taken_names = get_var_names(subs, arg_var, taken_names) } } @@ -1447,12 +2475,19 @@ fn content_to_err_type( } Alias(symbol, args, aliased_to) => { - let err_args = args - .into_iter() - .map(|(name, var)| (name, var_to_err_type(subs, state, var))) - .collect(); let err_type = var_to_err_type(subs, state, aliased_to); + let mut err_args = Vec::with_capacity(args.names().len()); + + for (name_index, var_index) in args.named_type_arguments() { + let name = subs[name_index].clone(); + let var = subs[var_index]; + + let arg = var_to_err_type(subs, state, var); + + err_args.push((name, arg)); + } + ErrorType::Alias(symbol, err_args, Box::new(err_type)) } @@ -1471,7 +2506,10 @@ fn flat_type_to_err_type( Apply(symbol, args) => { let arg_types = args .into_iter() - .map(|var| var_to_err_type(subs, state, var)) + .map(|index| { + let arg_var = subs[index]; + var_to_err_type(subs, state, arg_var) + }) .collect(); ErrorType::Type(symbol, arg_types) @@ -1536,13 +2574,16 @@ fn flat_type_to_err_type( TagUnion(tags, ext_var) => { let mut err_tags = SendMap::default(); - for (tag, vars) in tags.into_iter() { - let mut err_vars = Vec::with_capacity(vars.len()); + for (name_index, slice_index) in tags.iter_all() { + let mut err_vars = Vec::with_capacity(tags.len()); - for var in vars { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; err_vars.push(var_to_err_type(subs, state, var)); } + let tag = subs[name_index].clone(); err_tags.insert(tag, err_vars); } @@ -1568,7 +2609,7 @@ fn flat_type_to_err_type( } FunctionOrTagUnion(tag_name, _, ext_var) => { - let tag_name = *tag_name; + let tag_name = subs[tag_name].clone(); let mut err_tags = SendMap::default(); @@ -1598,13 +2639,16 @@ fn flat_type_to_err_type( RecursiveTagUnion(rec_var, tags, ext_var) => { let mut err_tags = SendMap::default(); - for (tag, vars) in tags.into_iter() { - let mut err_vars = Vec::with_capacity(vars.len()); + for (name_index, slice_index) in tags.iter_all() { + let mut err_vars = Vec::with_capacity(tags.len()); - for var in vars { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; err_vars.push(var_to_err_type(subs, state, var)); } + let tag = subs[name_index].clone(); err_tags.insert(tag, err_vars); } @@ -1658,7 +2702,8 @@ fn restore_content(subs: &mut Subs, content: &Content) { Structure(flat_type) => match flat_type { Apply(_, args) => { - for &var in args { + for index in args.into_iter() { + let var = subs[index]; subs.restore(var); } } @@ -1685,8 +2730,12 @@ fn restore_content(subs: &mut Subs, content: &Content) { subs.restore(*ext_var); } TagUnion(tags, ext_var) => { - for var in tags.values().flatten() { - subs.restore(*var); + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + subs.restore(var); + } } subs.restore(*ext_var); @@ -1696,8 +2745,12 @@ fn restore_content(subs: &mut Subs, content: &Content) { } RecursiveTagUnion(rec_var, tags, ext_var) => { - for var in tags.values().flatten() { - subs.restore(*var); + for slice_index in tags.variables() { + let slice = subs[slice_index]; + for var_index in slice { + let var = subs[var_index]; + subs.restore(var); + } } subs.restore(*ext_var); @@ -1707,8 +2760,9 @@ fn restore_content(subs: &mut Subs, content: &Content) { Erroneous(_) => (), }, Alias(_, args, var) => { - for (_, arg_var) in args { - subs.restore(*arg_var); + for var_index in args.variables().into_iter() { + let var = subs[var_index]; + subs.restore(var); } subs.restore(*var); diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 72c23ea183..416ddbceea 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1,5 +1,7 @@ use crate::pretty_print::Parens; -use crate::subs::{LambdaSet, RecordFields, Subs, VarStore, Variable}; +use crate::subs::{ + GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, +}; use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -11,6 +13,11 @@ pub const TYPE_NUM: &str = "Num"; pub const TYPE_INTEGER: &str = "Integer"; pub const TYPE_FLOATINGPOINT: &str = "FloatingPoint"; +const GREEK_LETTERS: &[char] = &[ + 'α', 'ν', 'β', 'ξ', 'γ', 'ο', 'δ', 'π', 'ε', 'ρ', 'ζ', 'σ', 'η', 'τ', 'θ', 'υ', 'ι', 'φ', 'κ', + 'χ', 'λ', 'ψ', 'μ', 'ω', 'ς', +]; + /// /// Intuitively /// @@ -132,6 +139,26 @@ impl RecordField { } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LambdaSet(pub Type); + +impl LambdaSet { + fn substitute(&mut self, substitutions: &ImMap) { + self.0.substitute(substitutions); + } + + fn instantiate_aliases( + &mut self, + region: Region, + aliases: &ImMap, + var_store: &mut VarStore, + introduced: &mut ImSet, + ) { + self.0 + .instantiate_aliases(region, aliases, var_store, introduced) + } +} + #[derive(PartialEq, Eq, Clone)] pub enum Type { EmptyRec, @@ -141,10 +168,16 @@ pub enum Type { Record(SendMap>, Box), TagUnion(Vec<(TagName, Vec)>, Box), FunctionOrTagUnion(TagName, Symbol, Box), - Alias(Symbol, Vec<(Lowercase, Type)>, Box), + Alias { + symbol: Symbol, + type_arguments: Vec<(Lowercase, Type)>, + lambda_set_variables: Vec, + actual: Box, + }, HostExposedAlias { name: Symbol, - arguments: Vec<(Lowercase, Type)>, + type_arguments: Vec<(Lowercase, Type)>, + lambda_set_variables: Vec, actual_var: Variable, actual: Box, }, @@ -197,20 +230,36 @@ impl fmt::Debug for Type { write!(f, ")") } - Type::Alias(symbol, args, _actual) => { - write!(f, "Alias {:?}", symbol)?; + Type::Alias { + symbol, + type_arguments, + lambda_set_variables, + actual: _actual, + .. + } => { + write!(f, "(Alias {:?}", symbol)?; - for (_, arg) in args { + for (_, arg) in type_arguments { write!(f, " {:?}", arg)?; } + for (lambda_set, greek_letter) in + lambda_set_variables.iter().zip(GREEK_LETTERS.iter()) + { + write!(f, " {}@{:?}", greek_letter, lambda_set.0)?; + } + // Sometimes it's useful to see the expansion of the alias - // write!(f, "[ but actually {:?} ]", _actual)?; + write!(f, "[ but actually {:?} ]", _actual)?; + + write!(f, ")")?; Ok(()) } Type::HostExposedAlias { - name, arguments, .. + name, + type_arguments: arguments, + .. } => { write!(f, "HostExposedAlias {:?}", name)?; @@ -441,14 +490,24 @@ impl Type { } ext.substitute(substitutions); } - Alias(_, zipped, actual_type) => { - for (_, value) in zipped.iter_mut() { + Alias { + type_arguments, + lambda_set_variables, + actual, + .. + } => { + for (_, value) in type_arguments.iter_mut() { value.substitute(substitutions); } - actual_type.substitute(substitutions); + + for lambda_set in lambda_set_variables.iter_mut() { + lambda_set.substitute(substitutions); + } + + actual.substitute(substitutions); } HostExposedAlias { - arguments, + type_arguments: arguments, actual: actual_type, .. } => { @@ -496,8 +555,11 @@ impl Type { } ext.substitute_alias(rep_symbol, actual); } - Alias(_, _, actual_type) => { - actual_type.substitute_alias(rep_symbol, actual); + Alias { + actual: alias_actual, + .. + } => { + alias_actual.substitute_alias(rep_symbol, actual); } HostExposedAlias { actual: actual_type, @@ -546,9 +608,11 @@ impl Type { ext.contains_symbol(rep_symbol) || fields.values().any(|arg| arg.contains_symbol(rep_symbol)) } - Alias(alias_symbol, _, actual_type) => { - alias_symbol == &rep_symbol || actual_type.contains_symbol(rep_symbol) - } + Alias { + symbol: alias_symbol, + actual: actual_type, + .. + } => alias_symbol == &rep_symbol || actual_type.contains_symbol(rep_symbol), HostExposedAlias { name, actual, .. } => { name == &rep_symbol || actual.contains_symbol(rep_symbol) } @@ -584,7 +648,10 @@ impl Type { .values() .any(|arg| arg.contains_variable(rep_variable)) } - Alias(_, _, actual_type) => actual_type.contains_variable(rep_variable), + Alias { + actual: actual_type, + .. + } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)), EmptyRec | EmptyTagUnion | Erroneous(_) => false, @@ -601,7 +668,7 @@ impl Type { /// a shallow dealias, continue until the first constructor is not an alias. pub fn shallow_dealias(&self) -> &Self { match self { - Type::Alias(_, _, actual) => actual.shallow_dealias(), + Type::Alias { actual, .. } => actual.shallow_dealias(), _ => self, } } @@ -641,16 +708,26 @@ impl Type { ext.instantiate_aliases(region, aliases, var_store, introduced); } HostExposedAlias { - arguments: type_args, + type_arguments: type_args, + lambda_set_variables, actual: actual_type, .. } - | Alias(_, type_args, actual_type) => { + | Alias { + type_arguments: type_args, + lambda_set_variables, + actual: actual_type, + .. + } => { for arg in type_args { arg.1 .instantiate_aliases(region, aliases, var_store, introduced); } + for arg in lambda_set_variables { + arg.instantiate_aliases(region, aliases, var_store, introduced); + } + actual_type.instantiate_aliases(region, aliases, var_store, introduced); } Apply(symbol, args) => { @@ -685,40 +762,16 @@ impl Type { substitution.insert(*placeholder, filler); } - // Instantiate "hidden" uniqueness variables - // - // Aliases can hide uniqueness variables: e.g. in - // - // Model : { x : Int, y : Bool } - // - // Its lifted variant is - // - // Attr a Model - // - // where the `a` doesn't really mention the attributes on the fields. - for variable in actual.variables() { - if !substitution.contains_key(&variable) { - // Leave attributes on recursion variables untouched! - // - // In a recursive type like - // - // > [ Z, S Peano ] as Peano - // - // By default the lifted version is - // - // > Attr a ([ Z, S (Attr b Peano) ] as Peano) - // - // But, it must be true that a = b because Peano is self-recursive. - // Therefore we earlier have substituted - // - // > Attr a ([ Z, S (Attr a Peano) ] as Peano) - // - // And now we must make sure the `a`s stay the same variable, i.e. - // don't re-instantiate it here. - let var = var_store.fresh(); - substitution.insert(variable, Type::Variable(var)); + let mut lambda_set_variables = + Vec::with_capacity(alias.lambda_set_variables.len()); + for lambda_set in alias.lambda_set_variables.iter() { + let fresh = var_store.fresh(); + introduced.insert(fresh); - introduced.insert(var); + lambda_set_variables.push(LambdaSet(Type::Variable(fresh))); + + if let Type::Variable(lambda_set_var) = lambda_set.0 { + substitution.insert(lambda_set_var, Type::Variable(fresh)); } } @@ -736,13 +789,19 @@ impl Type { } ext.substitute(&substitution); - *self = Type::Alias( - *symbol, - named_args, - Box::new(Type::RecursiveTagUnion(new_rec_var, tags, ext)), - ); + *self = Type::Alias { + symbol: *symbol, + type_arguments: named_args, + lambda_set_variables: alias.lambda_set_variables.clone(), + actual: Box::new(Type::RecursiveTagUnion(new_rec_var, tags, ext)), + }; } else { - *self = Type::Alias(*symbol, named_args, Box::new(actual)); + *self = Type::Alias { + symbol: *symbol, + type_arguments: named_args, + lambda_set_variables: alias.lambda_set_variables.clone(), + actual: Box::new(actual), + }; } } else { // one of the special-cased Apply types. @@ -788,7 +847,11 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { } }); } - Alias(alias_symbol, _, actual_type) => { + Alias { + symbol: alias_symbol, + actual: actual_type, + .. + } => { accum.insert(*alias_symbol); symbols_help(actual_type, accum); } @@ -858,14 +921,20 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { // this rec var doesn't need to be in flex_vars or rigid_vars accum.remove(rec); } - Alias(_, args, actual) => { - for (_, arg) in args { + Alias { + type_arguments, + actual, + .. + } => { + for (_, arg) in type_arguments { variables_help(arg, accum); } variables_help(actual, accum); } HostExposedAlias { - arguments, actual, .. + type_arguments: arguments, + actual, + .. } => { for (_, arg) in arguments { variables_help(arg, accum); @@ -883,7 +952,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { #[derive(Default)] pub struct VariableDetail { pub type_variables: MutSet, - pub lambda_set_variables: MutSet, + pub lambda_set_variables: Vec, pub recursion_variables: MutSet, } @@ -910,7 +979,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { variables_help_detailed(arg, accum); } if let Type::Variable(v) = **closure { - accum.lambda_set_variables.insert(LambdaSet::from(v)); + accum.lambda_set_variables.push(v); } else { variables_help_detailed(closure, accum); } @@ -956,14 +1025,20 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { accum.recursion_variables.insert(*rec); } - Alias(_, args, actual) => { - for (_, arg) in args { + Alias { + type_arguments, + actual, + .. + } => { + for (_, arg) in type_arguments { variables_help_detailed(arg, accum); } variables_help_detailed(actual, accum); } HostExposedAlias { - arguments, actual, .. + type_arguments: arguments, + actual, + .. } => { for (_, arg) in arguments { variables_help_detailed(arg, accum); @@ -985,6 +1060,13 @@ pub struct RecordStructure { pub ext: Variable, } +#[derive(Debug)] +pub struct TagUnionStructure<'a> { + /// Invariant: these should be sorted! + pub fields: Vec<(TagName, &'a [Variable])>, + pub ext: Variable, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum PReason { TypedArg { @@ -1107,7 +1189,7 @@ pub struct Alias { /// lambda set variables, e.g. the one annotating the arrow in /// a |c|-> b - pub lambda_set_variables: MutSet, + pub lambda_set_variables: Vec, pub recursion_variables: MutSet, @@ -1547,7 +1629,13 @@ pub fn gather_fields_unsorted_iter( var = *actual_var; } - _ => break, + Structure(EmptyRecord) => break, + FlexVar(_) => break, + + // TODO investigate apparently this one pops up in the reporting tests! + RigidVar(_) => break, + + other => unreachable!("something weird ended up in a record type: {:?}", other), } } @@ -1580,3 +1668,104 @@ pub fn gather_fields(subs: &Subs, other_fields: RecordFields, var: Variable) -> ext, } } + +pub fn gather_tags_unsorted_iter( + subs: &Subs, + other_fields: UnionTags, + mut var: Variable, +) -> ( + impl Iterator + '_, + Variable, +) { + use crate::subs::Content::*; + use crate::subs::FlatType::*; + + let mut stack = vec![other_fields]; + + loop { + match subs.get_content_without_compacting(var) { + Structure(TagUnion(sub_fields, sub_ext)) => { + stack.push(*sub_fields); + + var = *sub_ext; + } + + Structure(FunctionOrTagUnion(_tag_name_index, _, _sub_ext)) => { + todo!("this variant does not use SOA yet, and therefore this case is unreachable right now") + // let sub_fields: UnionTags = (*tag_name_index).into(); + // stack.push(sub_fields); + // + // var = *sub_ext; + } + + Structure(RecursiveTagUnion(_, _sub_fields, _sub_ext)) => { + todo!("this variant does not use SOA yet, and therefore this case is unreachable right now") + // stack.push(*sub_fields); + // + // var = *sub_ext; + } + + Alias(_, _, actual_var) => { + // TODO according to elm/compiler: "TODO may be dropping useful alias info here" + var = *actual_var; + } + + Structure(EmptyTagUnion) => break, + FlexVar(_) => break, + + // TODO investigate this likely can happen when there is a type error + RigidVar(_) => break, + + other => unreachable!("something weird ended up in a tag union type: {:?}", other), + } + } + + let it = stack + .into_iter() + .map(|union_tags| union_tags.iter_all()) + .flatten() + .map(move |(i1, i2)| { + let tag_name: &TagName = &subs[i1]; + let subs_slice = subs[i2]; + + (tag_name, subs_slice) + }); + + (it, var) +} + +pub fn gather_tags_slices( + subs: &Subs, + other_fields: UnionTags, + var: Variable, +) -> (Vec<(TagName, VariableSubsSlice)>, Variable) { + let (it, ext) = gather_tags_unsorted_iter(subs, other_fields, var); + + let mut result: Vec<_> = it + .map(|(ref_label, field)| (ref_label.clone(), field)) + .collect(); + + result.sort_by(|(a, _), (b, _)| a.cmp(b)); + + (result, ext) +} + +pub fn gather_tags(subs: &Subs, other_fields: UnionTags, var: Variable) -> TagUnionStructure { + let (it, ext) = gather_tags_unsorted_iter(subs, other_fields, var); + + let mut result: Vec<_> = it + .map(|(ref_label, field)| { + ( + ref_label.clone(), + subs.get_subs_slice(*field.as_subs_slice()), + ) + }) + .collect(); + + result.sort_by(|(a, _), (b, _)| a.cmp(b)); + + TagUnionStructure { + fields: result, + ext, + } +} diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index f57e524fdf..51771efe07 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,9 +1,10 @@ -use roc_collections::all::{default_hasher, get_shared, relative_complement, union, MutMap}; +use roc_collections::all::MutMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ - Descriptor, FlatType, GetSubsSlice, Mark, OptVariable, RecordFields, Subs, SubsSlice, Variable, + AliasVariables, Descriptor, FlatType, GetSubsSlice, Mark, OptVariable, RecordFields, Subs, + SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{ErrorType, Mismatch, RecordField}; @@ -157,7 +158,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { Structure(flat_type) => { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } - Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, args, *real_var), + Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var), Error => { // Error propagates. Whatever we're comparing it to doesn't matter! merge(subs, &ctx, Error) @@ -171,7 +172,7 @@ fn unify_alias( pool: &mut Pool, ctx: &Context, symbol: Symbol, - args: &[(Lowercase, Variable)], + args: AliasVariables, real_var: Variable, ) -> Outcome { let other_content = &ctx.second_desc.content; @@ -179,7 +180,7 @@ fn unify_alias( match other_content { FlexVar(_) => { // Alias wins - merge(subs, ctx, Alias(symbol, args.to_owned(), real_var)) + merge(subs, ctx, Alias(symbol, args, real_var)) } RecursionVar { structure, .. } => unify_pool(subs, pool, real_var, *structure), RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second), @@ -187,17 +188,22 @@ fn unify_alias( if symbol == *other_symbol { if args.len() == other_args.len() { let mut problems = Vec::new(); - for ((_, l_var), (_, r_var)) in args.iter().zip(other_args.iter()) { - problems.extend(unify_pool(subs, pool, *l_var, *r_var)); + let it = args + .variables() + .into_iter() + .zip(other_args.variables().into_iter()); + + for (l, r) in it { + let l_var = subs[l]; + let r_var = subs[r]; + problems.extend(unify_pool(subs, pool, l_var, r_var)); } if problems.is_empty() { problems.extend(merge(subs, ctx, other_content.clone())); } - if problems.is_empty() { - problems.extend(unify_pool(subs, pool, real_var, *other_real_var)); - } + // if problems.is_empty() { problems.extend(unify_pool(subs, pool, real_var, *other_real_var)); } problems } else { @@ -413,15 +419,8 @@ fn unify_shared_fields( if num_shared_fields == matching_fields.len() { // pull fields in from the ext_var - let mut ext_fields = MutMap::default(); - let new_ext_var = - match roc_types::pretty_print::chase_ext_record(subs, ext, &mut ext_fields) { - Ok(()) => Variable::EMPTY_RECORD, - Err((new, _)) => new, - }; - - let mut ext_fields: Vec<_> = ext_fields.into_iter().collect(); - ext_fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); + let (ext_fields, new_ext_var) = RecordFields::empty().sorted_iterator_and_ext(subs, ext); + let ext_fields: Vec<_> = ext_fields.into_iter().collect(); let fields: RecordFields = match other_fields { OtherFields::None => { @@ -587,88 +586,58 @@ where result } -struct SeparateTags { - only_in_1: MutMap, - only_in_2: MutMap, - in_both: MutMap, +fn separate_union_tags( + subs: &Subs, + fields1: UnionTags, + ext1: Variable, + fields2: UnionTags, + ext2: Variable, +) -> (Separate, Variable, Variable) { + let (it1, new_ext1) = fields1.sorted_slices_iterator_and_ext(subs, ext1); + let (it2, new_ext2) = fields2.sorted_slices_iterator_and_ext(subs, ext2); + + (separate(it1, it2), new_ext1, new_ext2) } -fn separate_tags(tags1: MutMap, mut tags2: MutMap) -> SeparateTags -where - K: Ord + std::hash::Hash, -{ - let mut only_in_1 = MutMap::with_capacity_and_hasher(tags1.len(), default_hasher()); - - let max_common = tags1.len().min(tags2.len()); - let mut in_both = MutMap::with_capacity_and_hasher(max_common, default_hasher()); - - for (k, v1) in tags1.into_iter() { - match tags2.remove(&k) { - Some(v2) => { - in_both.insert(k, (v1, v2)); - } - None => { - only_in_1.insert(k, v1); - } - } - } - - SeparateTags { - only_in_1, - only_in_2: tags2, - in_both, - } +#[derive(Debug, Copy, Clone)] +enum Rec { + None, + Left(Variable), + Right(Variable), + Both(Variable, Variable), } -fn unify_tag_union( +#[allow(clippy::too_many_arguments)] +fn unify_tag_union_new( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - rec1: TagUnionStructure, - rec2: TagUnionStructure, - recursion: (Option, Option), + tags1: UnionTags, + initial_ext1: Variable, + tags2: UnionTags, + initial_ext2: Variable, + recursion_var: Rec, ) -> Outcome { - let tags1 = rec1.tags; - let tags2 = rec2.tags; + let (separate, ext1, ext2) = + separate_union_tags(subs, tags1, initial_ext1, tags2, initial_ext2); - let recursion_var = match recursion { - (None, None) => None, - (Some(v), None) | (None, Some(v)) => Some(v), - (Some(v1), Some(_v2)) => Some(v1), - }; + let shared_tags = separate.in_both; - // heuristic: our closure defunctionalization scheme generates a bunch of one-tag unions - // also our number types fall in this category too. - if tags1.len() == 1 - && tags2.len() == 1 - && tags1 == tags2 - && subs.get_root_key_without_compacting(rec1.ext) - == subs.get_root_key_without_compacting(rec2.ext) - { - return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var); - } - - let SeparateTags { - only_in_1: unique_tags1, - only_in_2: unique_tags2, - in_both: shared_tags, - } = separate_tags(tags1, tags2); - - if unique_tags1.is_empty() { - if unique_tags2.is_empty() { - let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext); + if separate.only_in_1.is_empty() { + if separate.only_in_2.is_empty() { + let ext_problems = unify_pool(subs, pool, ext1, ext2); if !ext_problems.is_empty() { return ext_problems; } - let mut tag_problems = unify_shared_tags( + let mut tag_problems = unify_shared_tags_new( subs, pool, ctx, shared_tags, - OtherTags::Empty, - rec1.ext, + OtherTags2::Empty, + ext1, recursion_var, ); @@ -676,20 +645,21 @@ fn unify_tag_union( tag_problems } else { - let flat_type = FlatType::TagUnion(unique_tags2, rec2.ext); + let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2); + let flat_type = FlatType::TagUnion(unique_tags2, ext2); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record); + let ext_problems = unify_pool(subs, pool, ext1, sub_record); if !ext_problems.is_empty() { return ext_problems; } - let mut tag_problems = unify_shared_tags( + let mut tag_problems = unify_shared_tags_new( subs, pool, ctx, shared_tags, - OtherTags::Empty, + OtherTags2::Empty, sub_record, recursion_var, ); @@ -698,21 +668,22 @@ fn unify_tag_union( tag_problems } - } else if unique_tags2.is_empty() { - let flat_type = FlatType::TagUnion(unique_tags1, rec1.ext); + } else if separate.only_in_2.is_empty() { + let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1); + let flat_type = FlatType::TagUnion(unique_tags1, ext1); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext); + let ext_problems = unify_pool(subs, pool, sub_record, ext2); if !ext_problems.is_empty() { return ext_problems; } - let mut tag_problems = unify_shared_tags( + let mut tag_problems = unify_shared_tags_new( subs, pool, ctx, shared_tags, - OtherTags::Empty, + OtherTags2::Empty, sub_record, recursion_var, ); @@ -721,10 +692,10 @@ fn unify_tag_union( tag_problems } else { - let other_tags = OtherTags::Union { - tags1: unique_tags1.clone(), - tags2: unique_tags2.clone(), - }; + let other_tags = OtherTags2::Union(separate.only_in_1.clone(), separate.only_in_2.clone()); + + let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1); + let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2); let ext = fresh(subs, pool, ctx, Content::FlexVar(None)); let flat_type1 = FlatType::TagUnion(unique_tags1, ext); @@ -749,13 +720,13 @@ fn unify_tag_union( let snapshot = subs.snapshot(); - let ext1_problems = unify_pool(subs, pool, rec1.ext, sub2); + let ext1_problems = unify_pool(subs, pool, ext1, sub2); if !ext1_problems.is_empty() { subs.rollback_to(snapshot); return ext1_problems; } - let ext2_problems = unify_pool(subs, pool, sub1, rec2.ext); + let ext2_problems = unify_pool(subs, pool, sub1, ext2); if !ext2_problems.is_empty() { subs.rollback_to(snapshot); return ext2_problems; @@ -764,7 +735,7 @@ fn unify_tag_union( subs.commit_snapshot(snapshot); let mut tag_problems = - unify_shared_tags(subs, pool, ctx, shared_tags, other_tags, ext, recursion_var); + unify_shared_tags_new(subs, pool, ctx, shared_tags, other_tags, ext, recursion_var); tag_problems.reserve(ext1_problems.len() + ext2_problems.len()); tag_problems.extend(ext1_problems); @@ -774,143 +745,145 @@ fn unify_tag_union( } } -fn unify_tag_union_not_recursive_recursive( +enum OtherTags2 { + Empty, + Union( + Vec<(TagName, VariableSubsSlice)>, + Vec<(TagName, VariableSubsSlice)>, + ), +} + +fn unify_shared_tags_new( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - rec1: TagUnionStructure, - rec2: TagUnionStructure, - recursion_var: Variable, + shared_tags: Vec<(TagName, (VariableSubsSlice, VariableSubsSlice))>, + other_tags: OtherTags2, + ext: Variable, + recursion_var: Rec, ) -> Outcome { - let tags1 = rec1.tags; - let tags2 = rec2.tags; - let shared_tags = get_shared(&tags1, &tags2); - // NOTE: don't use `difference` here. In contrast to Haskell, im's `difference` is symmetric - let unique_tags1 = relative_complement(&tags1, &tags2); - let unique_tags2 = relative_complement(&tags2, &tags1); + let mut matching_tags = Vec::default(); + let num_shared_tags = shared_tags.len(); - if unique_tags1.is_empty() { - if unique_tags2.is_empty() { - let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext); + for (name, (actual_vars, expected_vars)) in shared_tags { + let mut matching_vars = Vec::with_capacity(actual_vars.len()); - if !ext_problems.is_empty() { - return ext_problems; + let actual_len = actual_vars.len(); + let expected_len = expected_vars.len(); + + for (actual_index, expected_index) in actual_vars.into_iter().zip(expected_vars.into_iter()) + { + let actual = subs[actual_index]; + let expected = subs[expected_index]; + // NOTE the arguments of a tag can be recursive. For instance in the expression + // + // Cons 1 (Cons "foo" Nil) + // + // We need to not just check the outer layer (inferring ConsList Int) + // but also the inner layer (finding a type error, as desired) + // + // This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964 + // Polymorphic recursion is now a type error. + // + // The strategy is to expand the recursive tag union as deeply as the non-recursive one + // is. + // + // > RecursiveTagUnion(rvar, [ Cons a rvar, Nil ], ext) + // + // Conceptually becomes + // + // > RecursiveTagUnion(rvar, [ Cons a [ Cons a rvar, Nil ], Nil ], ext) + // + // and so on until the whole non-recursive tag union can be unified with it. + let mut problems = Vec::new(); + + problems.extend(unify_pool(subs, pool, actual, expected)); + + // clearly, this is very suspicious: these variables have just been unified. And yet, + // not doing this leads to stack overflows + if let Rec::Right(_) = recursion_var { + if problems.is_empty() { + matching_vars.push(expected); + } + } else if problems.is_empty() { + matching_vars.push(actual); } - - let mut tag_problems = unify_shared_tags_recursive_not_recursive( - subs, - pool, - ctx, - shared_tags, - MutMap::default(), - rec1.ext, - recursion_var, - ); - - tag_problems.extend(ext_problems); - - tag_problems - } else { - let flat_type = FlatType::TagUnion(unique_tags2, rec2.ext); - let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record); - - if !ext_problems.is_empty() { - return ext_problems; - } - - let mut tag_problems = unify_shared_tags_recursive_not_recursive( - subs, - pool, - ctx, - shared_tags, - MutMap::default(), - sub_record, - recursion_var, - ); - - tag_problems.extend(ext_problems); - - tag_problems - } - } else if unique_tags2.is_empty() { - let flat_type = FlatType::TagUnion(unique_tags1, rec1.ext); - let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext); - - if !ext_problems.is_empty() { - return ext_problems; } - let mut tag_problems = unify_shared_tags_recursive_not_recursive( - subs, - pool, - ctx, - shared_tags, - MutMap::default(), - sub_record, - recursion_var, - ); - - tag_problems.extend(ext_problems); - - tag_problems - } else { - let other_tags = union(unique_tags1.clone(), &unique_tags2); - - let ext = fresh(subs, pool, ctx, Content::FlexVar(None)); - let flat_type1 = FlatType::TagUnion(unique_tags1, ext); - let flat_type2 = FlatType::TagUnion(unique_tags2, ext); - - let sub1 = fresh(subs, pool, ctx, Structure(flat_type1)); - let sub2 = fresh(subs, pool, ctx, Structure(flat_type2)); - - // NOTE: for clearer error messages, we rollback unification of the ext vars when either fails - // - // This is inspired by - // - // - // f : [ Red, Green ] -> Bool - // f = \_ -> True - // - // f Blue - // - // In this case, we want the mismatch to be between `[ Blue ]a` and `[ Red, Green ]`, but - // without rolling back, the mismatch is between `[ Blue, Red, Green ]a` and `[ Red, Green ]`. - // TODO is this also required for the other cases? - - let snapshot = subs.snapshot(); - - let ext1_problems = unify_pool(subs, pool, rec1.ext, sub2); - if !ext1_problems.is_empty() { - subs.rollback_to(snapshot); - return ext1_problems; + // only do this check after unification so the error message has more info + if actual_len == expected_len && actual_len == matching_vars.len() { + matching_tags.push((name, matching_vars)); } - - let ext2_problems = unify_pool(subs, pool, sub1, rec2.ext); - if !ext2_problems.is_empty() { - subs.rollback_to(snapshot); - return ext2_problems; - } - - subs.commit_snapshot(snapshot); - - let mut tag_problems = unify_shared_tags_recursive_not_recursive( - subs, - pool, - ctx, - shared_tags, - other_tags, - ext, - recursion_var, - ); - - tag_problems.reserve(ext1_problems.len() + ext2_problems.len()); - tag_problems.extend(ext1_problems); - tag_problems.extend(ext2_problems); - - tag_problems } + + if num_shared_tags == matching_tags.len() { + // pull fields in from the ext_var + + let (ext_fields, new_ext_var) = UnionTags::default().sorted_iterator_and_ext(subs, ext); + let ext_fields: Vec<_> = ext_fields + .into_iter() + .map(|(label, variables)| (label, variables.to_vec())) + .collect(); + + let new_tags: UnionTags = match other_tags { + OtherTags2::Empty => { + if ext_fields.is_empty() { + UnionTags::insert_into_subs(subs, matching_tags) + } else { + let all_fields = merge_sorted(matching_tags, ext_fields); + UnionTags::insert_into_subs(subs, all_fields) + } + } + OtherTags2::Union(other1, other2) => { + let mut all_fields = merge_sorted(matching_tags, ext_fields); + all_fields = merge_sorted( + all_fields, + other1.into_iter().map(|(field_name, subs_slice)| { + let vec = subs.get_subs_slice(*subs_slice.as_subs_slice()).to_vec(); + + (field_name, vec) + }), + ); + + all_fields = merge_sorted( + all_fields, + other2.into_iter().map(|(field_name, subs_slice)| { + let vec = subs.get_subs_slice(*subs_slice.as_subs_slice()).to_vec(); + + (field_name, vec) + }), + ); + + UnionTags::insert_into_subs(subs, all_fields) + } + }; + + unify_shared_tags_merge_new(subs, ctx, new_tags, new_ext_var, recursion_var) + } else { + mismatch!( + "Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}", + num_shared_tags, + &matching_tags + ) + } +} + +fn unify_shared_tags_merge_new( + subs: &mut Subs, + ctx: &Context, + new_tags: UnionTags, + new_ext_var: Variable, + recursion_var: Rec, +) -> Outcome { + let flat_type = match recursion_var { + Rec::None => FlatType::TagUnion(new_tags, new_ext_var), + Rec::Left(rec) | Rec::Right(rec) | Rec::Both(rec, _) => { + debug_assert!(is_recursion_var(subs, rec)); + FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var) + } + }; + + merge(subs, ctx, Structure(flat_type)) } /// Is the given variable a structure. Does not consider Attr itself a structure, and instead looks @@ -924,197 +897,6 @@ fn is_structure(var: Variable, subs: &mut Subs) -> bool { } } -fn unify_shared_tags_recursive_not_recursive( - subs: &mut Subs, - pool: &mut Pool, - ctx: &Context, - shared_tags: MutMap, Vec)>, - other_tags: MutMap>, - ext: Variable, - recursion_var: Variable, -) -> Outcome { - let mut matching_tags = MutMap::default(); - let num_shared_tags = shared_tags.len(); - - for (name, (actual_vars, expected_vars)) in shared_tags { - let mut matching_vars = Vec::with_capacity(actual_vars.len()); - - let actual_len = actual_vars.len(); - let expected_len = expected_vars.len(); - - for (actual, expected) in actual_vars.into_iter().zip(expected_vars.into_iter()) { - // NOTE the arguments of a tag can be recursive. For instance in the expression - // - // Cons 1 (Cons "foo" Nil) - // - // We need to not just check the outer layer (inferring ConsList Int) - // but also the inner layer (finding a type error, as desired) - // - // This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964 - // Polymorphic recursion is now a type error. - // - // The strategy is to expand the recursive tag union as deeply as the non-recursive one - // is. - // - // > RecursiveTagUnion(rvar, [ Cons a rvar, Nil ], ext) - // - // Conceptually becomes - // - // > RecursiveTagUnion(rvar, [ Cons a [ Cons a rvar, Nil ], Nil ], ext) - // - // and so on until the whole non-recursive tag union can be unified with it. - let mut problems = Vec::new(); - - { - // we always unify NonRecursive with Recursive, so this should never happen - //debug_assert_ne!(Some(actual), recursion_var); - - problems.extend(unify_pool(subs, pool, actual, expected)); - } - - if problems.is_empty() { - matching_vars.push(expected); - } - } - - // only do this check after unification so the error message has more info - if actual_len == expected_len && actual_len == matching_vars.len() { - matching_tags.insert(name, matching_vars); - } - } - - if num_shared_tags == matching_tags.len() { - // merge fields from the ext_var into this tag union - let mut fields = Vec::new(); - let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(subs, ext, &mut fields) - { - Ok(()) => Variable::EMPTY_TAG_UNION, - Err((new, _)) => new, - }; - - let mut new_tags = union(matching_tags, &other_tags); - new_tags.extend(fields.into_iter()); - - let flat_type = FlatType::RecursiveTagUnion(recursion_var, new_tags, new_ext_var); - - merge(subs, ctx, Structure(flat_type)) - } else { - mismatch!("Problem with Tag Union") - } -} - -enum OtherTags { - Empty, - Union { - tags1: MutMap>, - tags2: MutMap>, - }, -} - -fn unify_shared_tags( - subs: &mut Subs, - pool: &mut Pool, - ctx: &Context, - shared_tags: MutMap, Vec)>, - other_tags: OtherTags, - ext: Variable, - recursion_var: Option, -) -> Outcome { - let mut matching_tags = MutMap::default(); - let num_shared_tags = shared_tags.len(); - - for (name, (actual_vars, expected_vars)) in shared_tags { - let mut matching_vars = Vec::with_capacity(actual_vars.len()); - - let actual_len = actual_vars.len(); - let expected_len = expected_vars.len(); - - for (actual, expected) in actual_vars.into_iter().zip(expected_vars.into_iter()) { - // NOTE the arguments of a tag can be recursive. For instance in the expression - // - // Cons 1 (Cons "foo" Nil) - // - // We need to not just check the outer layer (inferring ConsList Int) - // but also the inner layer (finding a type error, as desired) - // - // This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964 - // Polymorphic recursion is now a type error. - // - // The strategy is to expand the recursive tag union as deeply as the non-recursive one - // is. - // - // > RecursiveTagUnion(rvar, [ Cons a rvar, Nil ], ext) - // - // Conceptually becomes - // - // > RecursiveTagUnion(rvar, [ Cons a [ Cons a rvar, Nil ], Nil ], ext) - // - // and so on until the whole non-recursive tag union can be unified with it. - let mut problems = Vec::new(); - - { - problems.extend(unify_pool(subs, pool, actual, expected)); - } - - if problems.is_empty() { - matching_vars.push(actual); - } - } - - // only do this check after unification so the error message has more info - if actual_len == expected_len && actual_len == matching_vars.len() { - matching_tags.insert(name, matching_vars); - } - } - - if num_shared_tags == matching_tags.len() { - let mut new_tags = matching_tags; - - // merge fields from the ext_var into this tag union - let mut fields = Vec::new(); - let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(subs, ext, &mut fields) - { - Ok(()) => Variable::EMPTY_TAG_UNION, - Err((new, _)) => new, - }; - new_tags.extend(fields.into_iter()); - - match other_tags { - OtherTags::Empty => {} - OtherTags::Union { tags1, tags2 } => { - new_tags.reserve(tags1.len() + tags2.len()); - new_tags.extend(tags1); - new_tags.extend(tags2); - } - } - - unify_shared_tags_merge(subs, ctx, new_tags, new_ext_var, recursion_var) - } else { - mismatch!( - "Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}", - num_shared_tags, - &matching_tags - ) - } -} - -fn unify_shared_tags_merge( - subs: &mut Subs, - ctx: &Context, - new_tags: MutMap>, - new_ext_var: Variable, - recursion_var: Option, -) -> Outcome { - let flat_type = if let Some(rec) = recursion_var { - debug_assert!(is_recursion_var(subs, rec)); - FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var) - } else { - FlatType::TagUnion(new_tags, new_ext_var) - }; - - merge(subs, ctx, Structure(flat_type)) -} - #[inline(always)] fn unify_flat_type( subs: &mut Subs, @@ -1151,54 +933,44 @@ fn unify_flat_type( } (TagUnion(tags1, ext1), TagUnion(tags2, ext2)) => { - let union1 = gather_tags(subs, tags1.clone(), *ext1); - let union2 = gather_tags(subs, tags2.clone(), *ext2); - - unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) + unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, Rec::None) } (RecursiveTagUnion(recursion_var, tags1, ext1), TagUnion(tags2, ext2)) => { debug_assert!(is_recursion_var(subs, *recursion_var)); // this never happens in type-correct programs, but may happen if there is a type error - let union1 = gather_tags(subs, tags1.clone(), *ext1); - let union2 = gather_tags(subs, tags2.clone(), *ext2); - unify_tag_union( - subs, - pool, - ctx, - union1, - union2, - (Some(*recursion_var), None), - ) + let rec = Rec::Left(*recursion_var); + + unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec) } (TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => { debug_assert!(is_recursion_var(subs, *recursion_var)); - let union1 = gather_tags(subs, tags1.clone(), *ext1); - let union2 = gather_tags(subs, tags2.clone(), *ext2); - unify_tag_union_not_recursive_recursive(subs, pool, ctx, union1, union2, *recursion_var) + let rec = Rec::Right(*recursion_var); + + unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec) } (RecursiveTagUnion(rec1, tags1, ext1), RecursiveTagUnion(rec2, tags2, ext2)) => { debug_assert!(is_recursion_var(subs, *rec1)); debug_assert!(is_recursion_var(subs, *rec2)); - let union1 = gather_tags(subs, tags1.clone(), *ext1); - let union2 = gather_tags(subs, tags2.clone(), *ext2); + let rec = Rec::Both(*rec1, *rec2); let mut problems = - unify_tag_union(subs, pool, ctx, union1, union2, (Some(*rec1), Some(*rec2))); + unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec); problems.extend(unify_pool(subs, pool, *rec1, *rec2)); problems } (Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => { - let problems = unify_zip(subs, pool, l_args.iter(), r_args.iter()); + let problems = + unify_zip_slices(subs, pool, *l_args.as_subs_slice(), *r_args.as_subs_slice()); if problems.is_empty() { - merge(subs, ctx, Structure(Apply(*r_symbol, (*r_args).clone()))) + merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))) } else { problems } @@ -1206,7 +978,8 @@ fn unify_flat_type( (Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret)) if l_args.len() == r_args.len() => { - let arg_problems = unify_zip_slices(subs, pool, *l_args, *r_args); + let arg_problems = + unify_zip_slices(subs, pool, *l_args.as_subs_slice(), *r_args.as_subs_slice()); let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret); let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure); @@ -1249,9 +1022,12 @@ fn unify_flat_type( false, ) } - (FunctionOrTagUnion(tag_name_1, _, ext_1), FunctionOrTagUnion(tag_name_2, _, ext_2)) => { - if tag_name_1 == tag_name_2 { - let problems = unify_pool(subs, pool, *ext_1, *ext_2); + (FunctionOrTagUnion(tag_name_1, _, ext1), FunctionOrTagUnion(tag_name_2, _, ext2)) => { + let tag_name_1_ref = &subs[*tag_name_1]; + let tag_name_2_ref = &subs[*tag_name_2]; + + if tag_name_1_ref == tag_name_2_ref { + let problems = unify_pool(subs, pool, *ext1, *ext2); if problems.is_empty() { let content = subs.get_content_without_compacting(ctx.second).clone(); merge(subs, ctx, content) @@ -1259,73 +1035,51 @@ fn unify_flat_type( problems } } else { - let mut tags1 = MutMap::default(); - tags1.insert(*tag_name_1.clone(), vec![]); - let union1 = gather_tags(subs, tags1, *ext_1); + let tags1 = UnionTags::from_tag_name_index(*tag_name_1); + let tags2 = UnionTags::from_tag_name_index(*tag_name_2); - let mut tags2 = MutMap::default(); - tags2.insert(*tag_name_2.clone(), vec![]); - let union2 = gather_tags(subs, tags2, *ext_2); - - unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) + unify_tag_union_new(subs, pool, ctx, tags1, *ext1, tags2, *ext2, Rec::None) } } (TagUnion(tags1, ext1), FunctionOrTagUnion(tag_name, _, ext2)) => { - let union1 = gather_tags(subs, tags1.clone(), *ext1); + let tags2 = UnionTags::from_tag_name_index(*tag_name); - let mut tags2 = MutMap::default(); - tags2.insert(*tag_name.clone(), vec![]); - let union2 = gather_tags(subs, tags2, *ext2); - - unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) + unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, tags2, *ext2, Rec::None) } (FunctionOrTagUnion(tag_name, _, ext1), TagUnion(tags2, ext2)) => { - let mut tags1 = MutMap::default(); - tags1.insert(*tag_name.clone(), vec![]); - let union1 = gather_tags(subs, tags1, *ext1); + let tags1 = UnionTags::from_tag_name_index(*tag_name); - let union2 = gather_tags(subs, tags2.clone(), *ext2); - - unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) + unify_tag_union_new(subs, pool, ctx, tags1, *ext1, *tags2, *ext2, Rec::None) } (RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_name, _, ext2)) => { // this never happens in type-correct programs, but may happen if there is a type error debug_assert!(is_recursion_var(subs, *recursion_var)); - let mut tags2 = MutMap::default(); - tags2.insert(*tag_name.clone(), vec![]); + let tags2 = UnionTags::from_tag_name_index(*tag_name); + let rec = Rec::Left(*recursion_var); - let union1 = gather_tags(subs, tags1.clone(), *ext1); - let union2 = gather_tags(subs, tags2, *ext2); - - unify_tag_union( - subs, - pool, - ctx, - union1, - union2, - (Some(*recursion_var), None), - ) + unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, tags2, *ext2, rec) } (FunctionOrTagUnion(tag_name, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => { debug_assert!(is_recursion_var(subs, *recursion_var)); - let mut tags1 = MutMap::default(); - tags1.insert(*tag_name.clone(), vec![]); + let tags1 = UnionTags::from_tag_name_index(*tag_name); + let rec = Rec::Right(*recursion_var); - let union1 = gather_tags(subs, tags1, *ext1); - let union2 = gather_tags(subs, tags2.clone(), *ext2); - - unify_tag_union_not_recursive_recursive(subs, pool, ctx, union1, union2, *recursion_var) + // NOTE arguments are flipped by design + unify_tag_union_new(subs, pool, ctx, tags1, *ext1, *tags2, *ext2, rec) } - (other1, other2) => mismatch!( - "Trying to unify two flat types that are incompatible: {:?} ~ {:?}", - other1, - other2 - ), + (other1, other2) => { + // any other combination is a mismatch + mismatch!( + "Trying to unify two flat types that are incompatible: {:?} ~ {:?}", + roc_types::subs::SubsFmtFlatType(other1, subs), + roc_types::subs::SubsFmtFlatType(other2, subs) + ) + } } } @@ -1349,21 +1103,6 @@ fn unify_zip_slices( problems } -fn unify_zip<'a, I>(subs: &mut Subs, pool: &mut Pool, left_iter: I, right_iter: I) -> Outcome -where - I: Iterator, -{ - let mut problems = Vec::new(); - - let it = left_iter.zip(right_iter); - - for (&l_var, &r_var) in it { - problems.extend(unify_pool(subs, pool, l_var, r_var)); - } - - problems -} - #[inline(always)] fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content) -> Outcome { match other { @@ -1494,34 +1233,6 @@ fn fresh(subs: &mut Subs, pool: &mut Pool, ctx: &Context, content: Content) -> V ) } -fn gather_tags( - subs: &mut Subs, - mut tags: MutMap>, - var: Variable, -) -> TagUnionStructure { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - match subs.get_content_without_compacting(var) { - Structure(TagUnion(sub_tags, sub_ext)) => { - for (k, v) in sub_tags { - tags.insert(k.clone(), v.clone()); - } - - let sub_ext = *sub_ext; - gather_tags(subs, tags, sub_ext) - } - - Alias(_, _, var) => { - // TODO according to elm/compiler: "TODO may be dropping useful alias info here" - let var = *var; - gather_tags(subs, tags, var) - } - - _ => TagUnionStructure { tags, ext: var }, - } -} - fn is_recursion_var(subs: &Subs, var: Variable) -> bool { matches!( subs.get_content_without_compacting(var), @@ -1534,24 +1245,18 @@ fn unify_function_or_tag_union_and_func( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - tag_name: &TagName, + tag_name_index: &SubsIndex, tag_symbol: Symbol, tag_ext: Variable, - function_arguments: SubsSlice, + function_arguments: VariableSubsSlice, function_return: Variable, function_lambda_set: Variable, left: bool, ) -> Outcome { - use FlatType::*; + let tag_name = subs[*tag_name_index].clone(); - let mut new_tags = MutMap::with_capacity_and_hasher(1, default_hasher()); - - new_tags.insert( - tag_name.clone(), - subs.get_subs_slice(function_arguments).to_owned(), - ); - - let content = Structure(TagUnion(new_tags, tag_ext)); + let union_tags = UnionTags::insert_slices_into_subs(subs, [(tag_name, function_arguments)]); + let content = Content::Structure(FlatType::TagUnion(union_tags, tag_ext)); let new_tag_union_var = fresh(subs, pool, ctx, content); @@ -1562,12 +1267,11 @@ fn unify_function_or_tag_union_and_func( }; { + let tag_name = TagName::Closure(tag_symbol); + let union_tags = UnionTags::tag_without_arguments(subs, tag_name); + let lambda_set_ext = subs.fresh_unnamed_flex_var(); - - let mut closure_tags = MutMap::with_capacity_and_hasher(1, default_hasher()); - closure_tags.insert(TagName::Closure(tag_symbol), vec![]); - - let lambda_set_content = Structure(TagUnion(closure_tags, lambda_set_ext)); + let lambda_set_content = Structure(FlatType::TagUnion(union_tags, lambda_set_ext)); let tag_lambda_set = register( subs, diff --git a/docs/src/lib.rs b/docs/src/lib.rs index eb2c44379c..76875e9d64 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -653,7 +653,7 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool { is_multiline } TypeAnnotation::Function { args, output } => { - let mut is_multiline = should_be_multiline(output) || args.len() > 1; + let mut is_multiline = should_be_multiline(output) || args.len() > 2; for arg in args { if is_multiline { diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index 4d32960eb5..1eabb82d96 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -155,6 +155,10 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * ... * smart insert: press a shortcut and enter a plain english description of a code snippet you need. Examples: "convert string to list of chars", "sort list of records by field foo descending", "plot this list with date on x-axis"... * After the user has refactored code to be simpler, try finding other places in the code base where the same simplification can be made. +* Show most commonly changed settings on first run so new users can quickly customize their experience. Keeping record of changed settings should be opt-in. +* Detection of multiple people within same company/team working on same code at the same time (opt-in). +* Autocorrect likely typos for stuff like `-<` when not in string. +* If multiple functions are available for import, use function were types would match in insetion position. #### Autocomplete @@ -236,6 +240,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe ### Inspiration - [Boop](https://github.com/IvanMathy/Boop) scriptable scratchpad for developers. Contains collection of useful conversions: json formatting, url encoding, encode to base64... +- [processing](processing.org) Interactive editor, dragging left or right with mouse to change values. Instant results. ## High performance @@ -243,6 +248,23 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe - [10x editor](http://www.10xeditor.com/) IDE/Editor targeted at the professional developer with an emphasis on performance and scalability. + +## Positive feedback + +- It's nice to enhance the feeling of reward after completing a task, this increases motivation. +- Great for tutorials and the first run of the editor. +- Suggestions of occasions for positive feedback: + - Being able to compile successfully after starting out with more than X errors. + - Making a test succeed after repeated failures. +- Positive feedback could be delivered with messages and/or animations. Animations could be with fireworks, flying roc logo birds, sounds... +- The intensity of the message/animation could be increased based on the duration/difficulty of the task. +- Suggest to search for help or take a break after being stuck on a test/compile errors... for some time. A search could be done for group chats for relevant libraries. + +### Inspiration + +- [Duolingo](https://www.duolingo.com) app to learn languages +- [Khan academy](https://www.khanacademy.org/) free quality education for everyone + ## General Thoughts/Ideas Thoughts and ideas possibly taken from above inspirations or separate. diff --git a/editor/src/lang/scope.rs b/editor/src/lang/scope.rs index 6ae7a1d294..e17f3e09a9 100644 --- a/editor/src/lang/scope.rs +++ b/editor/src/lang/scope.rs @@ -32,7 +32,7 @@ fn to_type2( var_store: &mut VarStore, ) -> Type2 { match solved_type { - SolvedType::Alias(symbol, solved_type_variables, solved_actual) => { + SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => { let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); for (type_variable_node_id, (lowercase, solved_arg)) in type_variables diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs index 8a5a547eb7..68b29c7635 100644 --- a/editor/src/lang/solve.rs +++ b/editor/src/lang/solve.rs @@ -1,18 +1,22 @@ #![allow(clippy::all)] #![allow(dead_code)] use crate::lang::constrain::Constraint::{self, *}; -use crate::lang::pool::{Pool, ShallowClone}; +use crate::lang::pool::{Pool, PoolVec, ShallowClone}; use crate::lang::types::Type2; use bumpalo::Bump; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; +use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::solved_types::Solved; use roc_types::subs::{ - Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, SubsSlice, Variable, + AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, + SubsSlice, UnionTags, Variable, VariableSubsSlice, +}; +use roc_types::types::{ + gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField, }; -use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField}; use roc_unify::unify::unify; use roc_unify::unify::Unified::*; @@ -701,6 +705,7 @@ fn type_to_variable<'a>( )) } + let arg_vars = VariableSubsSlice::insert_into_subs(subs, arg_vars); let flat_type = FlatType::Apply(*symbol, arg_vars); let content = Content::Structure(flat_type); @@ -711,7 +716,7 @@ fn type_to_variable<'a>( EmptyTagUnion => roc_types::subs::Variable::EMPTY_TAG_UNION, Record(fields, ext_id) => { - let mut field_vars = MutMap::default(); + let mut field_vars = Vec::new(); for node_id in fields.iter_node_ids() { use RecordField::*; @@ -748,31 +753,29 @@ fn type_to_variable<'a>( )), }; - field_vars.insert(field.as_str(mempool).into(), field_var); + field_vars.push((field.as_str(mempool).into(), field_var)); } let ext = mempool.get(*ext_id); let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); - let new_ext_var = match roc_types::pretty_print::chase_ext_record( - subs, - temp_ext_var, - &mut field_vars, - ) { - Ok(()) => roc_types::subs::Variable::EMPTY_RECORD, - Err((new, _)) => new, - }; - let mut all_fields: Vec<_> = field_vars.into_iter().collect(); - all_fields.sort_unstable_by(RecordFields::compare); + let (it, new_ext_var) = + gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); - let record_fields = RecordFields::insert_into_subs(subs, all_fields); + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + field_vars.sort_unstable_by(RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(subs, field_vars); let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); register(subs, rank, pools, content) } - Alias(Symbol::BOOL_BOOL, _, _) => roc_types::subs::Variable::BOOL, Alias(symbol, args, alias_type_id) => { // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) @@ -815,6 +818,8 @@ fn type_to_variable<'a>( new_aliases.insert(arg_str, arg_var); } + let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []); + let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); let content = Content::Alias(*symbol, arg_vars, alias_var); @@ -827,34 +832,11 @@ fn type_to_variable<'a>( result } TagUnion(tags, ext_id) => { - let mut tag_vars = MutMap::default(); let ext = mempool.get(*ext_id); - for (tag_name, tag_argument_types) in tags.iter(mempool) { - let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); - - for arg_type in tag_argument_types.iter(mempool) { - tag_argument_vars.push(type_to_variable( - arena, mempool, subs, rank, pools, cached, arg_type, - )); - } - - tag_vars.insert(tag_name.clone(), tag_argument_vars); - } - - let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); - let mut ext_tag_vec = Vec::new(); - let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( - subs, - temp_ext_var, - &mut ext_tag_vec, - ) { - Ok(()) => roc_types::subs::Variable::EMPTY_TAG_UNION, - Err((new, _)) => new, - }; - tag_vars.extend(ext_tag_vec.into_iter()); - - let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var)); + let (union_tags, ext) = + type_to_union_tags(arena, mempool, subs, rank, pools, cached, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); register(subs, rank, pools, content) } @@ -871,11 +853,7 @@ fn type_to_variable<'a>( new_arg_vars.push(var) } - let start = subs.variables.len() as u32; - let length = arg_vars.len() as u16; - let arg_vars = SubsSlice::new(start, length); - - subs.variables.extend(new_arg_vars); + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); let ret_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ret_type); let closure_var = @@ -973,6 +951,63 @@ fn type_to_variable<'a>( } } +fn type_to_union_tags<'a>( + arena: &'a Bump, + mempool: &Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + tags: &PoolVec<(TagName, PoolVec)>, + ext: &Type2, +) -> (UnionTags, Variable) { + let mut tag_vars = Vec::with_capacity(tags.len()); + + let mut tag_argument_vars = Vec::new(); + for id in tags.iter_node_ids() { + let (tag, tag_argument_types) = mempool.get(id); + for arg_id in tag_argument_types.iter_node_ids() { + let arg_type = mempool.get(arg_id); + let new_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); + tag_argument_vars.push(new_var); + } + + let new_slice = VariableSubsSlice::insert_into_subs(subs, tag_argument_vars.drain(..)); + + tag_vars.push((tag.clone(), new_slice)); + } + + let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); + + let ext = { + let (it, ext) = + roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + + // deduplicate, keeping the right-most occurrence of a tag name + let mut i = 0; + + while i < tag_vars.len() { + match (tag_vars.get(i), tag_vars.get(i + 1)) { + (Some((t1, _)), Some((t2, _))) => { + if t1 == t2 { + tag_vars.remove(i); + } else { + i += 1; + } + } + _ => break, + } + } + + ext + }; + + (UnionTags::insert_slices_into_subs(subs, tag_vars), ext) +} + fn check_for_infinite_type( subs: &mut Subs, problems: &mut Vec, @@ -981,7 +1016,7 @@ fn check_for_infinite_type( ) { let var = loc_var.value; - while let Some((recursive, _chain)) = subs.occurs(var) { + while let Err((recursive, _chain)) = subs.occurs(var) { let description = subs.get(recursive); let content = description.content; @@ -998,19 +1033,24 @@ fn check_for_infinite_type( }, ); - let mut new_tags = MutMap::default(); + let mut new_tags = Vec::with_capacity(tags.len()); - for (label, args) in &tags { - let new_args: Vec<_> = args - .iter() - .map(|var| subs.explicit_substitute(recursive, rec_var, *var)) - .collect(); + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; - new_tags.insert(label.clone(), new_args); + let mut new_vars = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + new_vars.push(subs.explicit_substitute(recursive, rec_var, var)); + } + + new_tags.push((subs[name_index].clone(), new_vars)); } let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); + let new_tags = UnionTags::insert_into_subs(subs, new_tags); + let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); subs.set_content(recursive, Content::Structure(flat_type)); @@ -1176,9 +1216,9 @@ fn adjust_rank_content( Apply(_, args) => { let mut rank = Rank::toplevel(); - for var in args { - rank = - rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for var_index in args.into_iter() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); } rank @@ -1231,9 +1271,13 @@ fn adjust_rank_content( TagUnion(tags, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in tags.values().flatten() { - rank = - rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } } rank @@ -1246,9 +1290,13 @@ fn adjust_rank_content( RecursiveTagUnion(rec_var, tags, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in tags.values().flatten() { - rank = - rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } } // THEORY: the recursion var has the same rank as the tag union itself @@ -1268,8 +1316,9 @@ fn adjust_rank_content( Alias(_, args, real_var) => { let mut rank = Rank::toplevel(); - for (_, var) in args { - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); + for var_index in args.variables() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); } // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() @@ -1352,7 +1401,8 @@ fn instantiate_rigids_help( Structure(flat_type) => { match flat_type { Apply(_, args) => { - for var in args.into_iter() { + for var_index in args.into_iter() { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } } @@ -1379,8 +1429,10 @@ fn instantiate_rigids_help( } TagUnion(tags, ext_var) => { - for (_, vars) in tags { - for var in vars.into_iter() { + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } } @@ -1395,8 +1447,10 @@ fn instantiate_rigids_help( RecursiveTagUnion(rec_var, tags, ext_var) => { instantiate_rigids_help(subs, max_rank, pools, rec_var); - for (_, vars) in tags { - for var in vars.into_iter() { + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } } @@ -1418,7 +1472,8 @@ fn instantiate_rigids_help( } Alias(_, args, real_type_var) => { - for (_, var) in args.into_iter() { + for var_index in args.variables() { + let var = subs[var_index]; instantiate_rigids_help(subs, max_rank, pools, var); } @@ -1487,12 +1542,17 @@ fn deep_copy_var_help( Structure(flat_type) => { let new_flat_type = match flat_type { Apply(symbol, args) => { - let args = args - .into_iter() - .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) - .collect(); + let mut new_arg_vars = Vec::with_capacity(args.len()); - Apply(symbol, args) + for index in args.into_iter() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + Apply(symbol, arg_vars) } Func(arg_vars, closure_var, ret_var) => { @@ -1507,11 +1567,7 @@ fn deep_copy_var_help( new_arg_vars.push(copy_var); } - let start = subs.variables.len() as u32; - let length = arg_vars.len() as u16; - let arg_vars = SubsSlice::new(start, length); - - subs.variables.extend(new_arg_vars); + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); Func(arg_vars, new_closure_var, new_ret_var) } @@ -1560,17 +1616,35 @@ fn deep_copy_var_help( } TagUnion(tags, ext_var) => { - let mut new_tags = MutMap::default(); + let mut new_variable_slices = Vec::with_capacity(tags.len()); - for (tag, vars) in tags { - let new_vars: Vec = vars - .into_iter() - .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) - .collect(); - new_tags.insert(tag, new_vars); + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); } - TagUnion(new_tags, deep_copy_var_help(subs, max_rank, pools, ext_var)) + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + TagUnion(union_tags, new_ext) } FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( @@ -1580,23 +1654,36 @@ fn deep_copy_var_help( ), RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut new_tags = MutMap::default(); + let mut new_variable_slices = Vec::with_capacity(tags.len()); - let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } - for (tag, vars) in tags { - let new_vars: Vec = vars - .into_iter() - .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) - .collect(); - new_tags.insert(tag, new_vars); + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); } - RecursiveTagUnion( - new_rec_var, - new_tags, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + FlatType::RecursiveTagUnion(new_rec_var, union_tags, new_ext) } }; @@ -1630,13 +1717,19 @@ fn deep_copy_var_help( copy } - Alias(symbol, args, real_type_var) => { - let new_args = args - .into_iter() - .map(|(name, var)| (name, deep_copy_var_help(subs, max_rank, pools, var))) - .collect(); + Alias(symbol, mut args, real_type_var) => { + let mut new_args = Vec::with_capacity(args.variables().len()); + + for var_index in args.variables() { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_args.push(new_var); + } + + args.replace_variables(subs, new_args); + let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); - let new_content = Alias(symbol, new_args, new_real_type_var); + let new_content = Alias(symbol, args, new_real_type_var); subs.set(copy, make_descriptor(new_content)); diff --git a/examples/benchmarks/Bytes/Encode.roc b/examples/benchmarks/Bytes/Encode.roc index 92945e1971..9c8b845dc5 100644 --- a/examples/benchmarks/Bytes/Encode.roc +++ b/examples/benchmarks/Bytes/Encode.roc @@ -54,6 +54,7 @@ encode = \encoder -> encodeHelp encoder 0 output |> .output +encodeHelp : Encoder, Nat, List U8 -> { output: List U8, offset: Nat } encodeHelp = \encoder, offset, output -> when encoder is Unsigned8 value -> diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index fb3690646e..945c4d515e 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -45,6 +45,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const Unit = extern struct {}; pub export fn main() u8 { diff --git a/examples/effect/thing/platform-dir/host.zig b/examples/effect/thing/platform-dir/host.zig index 307bd69a73..063d28973f 100644 --- a/examples/effect/thing/platform-dir/host.zig +++ b/examples/effect/thing/platform-dir/host.zig @@ -45,6 +45,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const Unit = extern struct {}; pub export fn main() u8 { diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index 53156c212a..8726cab399 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -2,7 +2,9 @@ use core::ffi::c_void; use core::mem::MaybeUninit; +use libc::c_char; use roc_std::{RocCallResult, RocStr}; +use std::ffi::CStr; extern "C" { #[link_name = "roc__mainForHost_1_exposed"] @@ -29,6 +31,19 @@ pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { return libc::free(c_ptr); } +#[no_mangle] +pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + #[no_mangle] pub fn rust_main() -> isize { let mut call_result: MaybeUninit> = MaybeUninit::uninit(); diff --git a/examples/hello-world/platform/host.c b/examples/hello-world/platform/host.c index 65661e64aa..f968d0c763 100644 --- a/examples/hello-world/platform/host.c +++ b/examples/hello-world/platform/host.c @@ -17,6 +17,12 @@ void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); } +void roc_panic(void* ptr, unsigned int alignment) { + char* msg = (char *)ptr; + fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg); + exit(0); +} + struct RocStr { char* bytes; size_t len; diff --git a/examples/hello-zig/platform/host.zig b/examples/hello-zig/platform/host.zig index d1a563a68e..02b4d939a0 100644 --- a/examples/hello-zig/platform/host.zig +++ b/examples/hello-zig/platform/host.zig @@ -35,6 +35,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const mem = std.mem; const Allocator = mem.Allocator; diff --git a/examples/quicksort/platform/host.zig b/examples/quicksort/platform/host.zig index 6eaf3d1b91..1dd646d61d 100644 --- a/examples/quicksort/platform/host.zig +++ b/examples/quicksort/platform/host.zig @@ -38,6 +38,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + // warning! the array is currently stack-allocated so don't make this too big const NUM_NUMS = 100; diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 2ed29ae07c..16d1eb4660 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -91,12 +91,16 @@ impl RocList { } } - fn get_storage_ptr(&self) -> *const isize { - let ptr = self.elements as *const isize; + fn get_storage_ptr_help(elements: *mut T) -> *mut isize { + let ptr = elements as *mut isize; unsafe { ptr.offset(-1) } } + fn get_storage_ptr(&self) -> *const isize { + Self::get_storage_ptr_help(self.elements) + } + fn get_storage_ptr_mut(&mut self) -> *mut isize { self.get_storage_ptr() as *mut isize } @@ -278,6 +282,103 @@ impl RocList { fn align_of_storage_ptr() -> u32 { mem::align_of::().max(mem::align_of::()) as u32 } + + unsafe fn drop_pointer_to_first_argument(ptr: *mut T) { + let storage_ptr = Self::get_storage_ptr_help(ptr); + let storage_val = *storage_ptr; + + if storage_val == REFCOUNT_1 || storage_val > 0 { + // If we have no more references, or if this was unique, + // deallocate it. + roc_dealloc(storage_ptr as *mut c_void, Self::align_of_storage_ptr()); + } else if storage_val < 0 { + // If this still has more references, decrement one. + *storage_ptr = storage_val - 1; + } + + // The only remaining option is that this is in readonly memory, + // in which case we shouldn't attempt to do anything to it. + } +} + +impl<'a, T> IntoIterator for &'a RocList { + type Item = &'a T; + + type IntoIter = <&'a [T] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.as_slice().iter() + } +} + +impl IntoIterator for RocList { + type Item = T; + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let remaining = self.len(); + + let buf = unsafe { NonNull::new_unchecked(self.elements as _) }; + let ptr = self.elements; + + IntoIter { + buf, + ptr, + remaining, + } + } +} + +use core::ptr::NonNull; + +pub struct IntoIter { + buf: NonNull, + // pub cap: usize, + ptr: *const T, + remaining: usize, +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + next_help(self) + } +} + +fn next_help(this: &mut IntoIter) -> Option { + if this.remaining == 0 { + None + } else if mem::size_of::() == 0 { + // purposefully don't use 'ptr.offset' because for + // vectors with 0-size elements this would return the + // same pointer. + this.remaining -= 1; + + // Make up a value of this ZST. + Some(unsafe { mem::zeroed() }) + } else { + let old = this.ptr; + this.ptr = unsafe { this.ptr.offset(1) }; + this.remaining -= 1; + + Some(unsafe { ptr::read(old) }) + } +} + +impl Drop for IntoIter { + fn drop(&mut self) { + // drop the elements that we have not yet returned. + while let Some(item) = next_help(self) { + drop(item); + } + + // deallocate the whole buffer + unsafe { + RocList::drop_pointer_to_first_argument(self.buf.as_mut()); + } + } } impl Default for RocList { diff --git a/shell.nix b/shell.nix index 8ac9510a0d..e615b18015 100644 --- a/shell.nix +++ b/shell.nix @@ -50,10 +50,7 @@ let # lib deps glibc_multi - llvmPkgs.libcxx - llvmPkgs.libcxxabi libffi - libunwind libxml2 ncurses zlib @@ -78,9 +75,6 @@ in pkgs.mkShell { lib.makeLibraryPath ([ pkg-config stdenv.cc.cc.lib - llvmPkgs.libcxx - llvmPkgs.libcxxabi - libunwind libffi ncurses zlib