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 a4620807d0..d97d8087c6 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -352,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), } } 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 770cd1c17f..fc6af6b3e5 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -144,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(), }; @@ -158,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(); diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index e4843166ab..c0d050a865 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -79,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 @@ -102,6 +105,14 @@ comptime { 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 fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void { @export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong }); @@ -122,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/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 14f7c82198..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"; @@ -77,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 0dcc377c40..b14d1dd435 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -4,8 +4,8 @@ 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; @@ -501,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 diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 349cac6f80..d68f8dd85f 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -161,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, @@ -1088,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) @@ -3359,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/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 9afd056a91..b90c3a7790 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -204,6 +204,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) } @@ -312,7 +318,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)), } } @@ -516,20 +528,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, @@ -545,7 +543,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 fa254caf5b..675f981e72 100644 --- a/compiler/gen_dev/tests/helpers/eval.rs +++ b/compiler/gen_dev/tests/helpers/eval.rs @@ -73,17 +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 400f42f63a..2142b2e92e 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -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)), @@ -2117,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>, @@ -2236,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 { @@ -2297,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, @@ -3090,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()); @@ -3129,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) @@ -3172,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, @@ -3192,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>( @@ -3346,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>, @@ -3362,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. @@ -3392,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, @@ -3626,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 { @@ -3654,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 = @@ -3662,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( @@ -3680,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; @@ -3691,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, ); @@ -3714,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>( @@ -4738,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; @@ -5169,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>, @@ -5233,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"); @@ -5254,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() } } @@ -6142,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, @@ -6290,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/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/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 d360192d8c..583e6017c1 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -87,6 +87,8 @@ pub enum LowLevel { NumAtan, NumAcos, NumAsin, + NumBytesToU16, + NumBytesToU32, NumBitwiseAnd, NumBitwiseXor, NumBitwiseOr, @@ -122,10 +124,9 @@ impl LowLevel { | 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 e777d0c96e..aaa9ce321d 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -894,7 +894,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 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 b5b2584801..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,6 +998,8 @@ 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]), @@ -1097,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, @@ -1114,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 c3d957ec1e..ab967700f9 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -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) @@ -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, @@ -4673,14 +4746,16 @@ pub fn from_can<'a>( ); CapturedSymbols::None } - Err(e) => { - debug_assert!( - captured_symbols.is_empty(), - "{:?}, {:?}", - &captured_symbols, - e - ); - CapturedSymbols::None + Err(_) => { + // 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()) + } } }; @@ -5146,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, @@ -5289,8 +5335,6 @@ fn substitute_in_stmt_help<'a>( } } - Resume(_) => None, - RuntimeError(_) => None, } } @@ -5952,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, @@ -5968,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, @@ -6011,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); @@ -6045,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, @@ -6156,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)] @@ -6239,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) { @@ -6651,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)); @@ -7613,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>, @@ -7652,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; @@ -7876,7 +7927,15 @@ fn union_lambda_set_to_switch<'a>( assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - debug_assert!(!lambda_set.is_empty()); + if lambda_set.is_empty() { + // 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. We generate a runtime error here which allows + // code gen to proceed. We then assume that we hit another (more descriptive) error before + // hitting this one + + let msg = "a Lambda Set isempty. Most likely there is a type error in your program."; + return Stmt::RuntimeError(msg); + } let join_point_id = JoinPointId(env.unique_symbol()); diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 1737564ab9..e0a96e6a32 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -29,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! @@ -45,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)] @@ -259,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>), } @@ -292,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, } @@ -318,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), } } @@ -394,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"), @@ -587,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), } @@ -628,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 @@ -693,7 +858,6 @@ impl<'a> Layout<'a> { | NonNullableUnwrapped(_) => pointer_size, } } - Closure(_, lambda_set, _) => lambda_set.stack_size(pointer_size), RecursivePointer => pointer_size, } } @@ -725,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)) - } } } @@ -778,8 +939,6 @@ impl<'a> Layout<'a> { } } RecursivePointer => true, - - Closure(_, closure_layout, _) => closure_layout.contains_refcounted(), } } @@ -803,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)) - } } } } @@ -873,10 +1018,7 @@ impl<'a> LayoutCache<'a> { seen: Vec::new_in(arena), }; - 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) } pub fn snapshot(&mut self) -> SnapshotKeyPlaceholder { @@ -1130,22 +1272,10 @@ fn layout_from_flat_type<'a>( } } } - 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 @@ -2083,6 +2213,20 @@ fn layout_from_tag_union<'a>(arena: &'a Bump, tags: UnionTags, subs: &Subs) -> L } } +#[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 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/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index c055b54f6d..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]); 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 b8ee1d2422..fcd63e1f7e 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2393,7 +2393,6 @@ fn call_invalid_layout() { 3, i64, |x| x, - false, true ); } @@ -2657,3 +2656,125 @@ fn lambda_set_enum_byte_byte() { 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 + ); +} + +#[test] +#[should_panic(expected = "Roc failed with message: ")] +fn value_not_exposed_hits_panic() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main : I64 + main = + Str.toInt 32 + "# + ), + 32, + i64 + ); +} diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index 1531974636..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,8 +274,15 @@ 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; @@ -284,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); }; } @@ -296,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); } }; } @@ -322,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 1d512320fa..15ddbd26c6 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,23 +1,23 @@ 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.19 = lowlevel NumMul #Attr.2 #Attr.3; ret Test.19; procedure Test.1 (): - let Test.27 = 1i64; - ret Test.27; + let Test.25 = 1i64; + ret Test.25; procedure Test.2 (): - let Test.21 = 2i64; - ret Test.21; + let Test.20 = 2i64; + ret Test.20; procedure Test.3 (Test.6): - let Test.24 = CallByName Test.1; - let Test.23 = CallByName Num.24 Test.6 Test.24; - ret Test.23; + 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; @@ -34,8 +34,8 @@ procedure Test.0 (): let Test.11 = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.22 = true; - if Test.22 then + let Test.21 = true; + if Test.21 then let Test.3 = Struct {}; jump Test.16 Test.3; else 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 6e2caa4e72..dc49280bc7 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -521,6 +521,16 @@ pub fn u8_type() -> SolvedType { ) } +#[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( diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 805519e7d7..6625ab0630 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -327,6 +327,14 @@ fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt: 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), @@ -337,12 +345,22 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: } => write!(f, "Recursion({:?}, {:?})", structure, opt_name), Content::Structure(flat_type) => subs_fmt_flat_type(flat_type, subs, f), Content::Alias(name, arguments, actual) => { - write!(f, "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) => { @@ -354,7 +372,21 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f let slice = subs.get_subs_slice(*arguments.as_subs_slice()); write!(f, "Func({:?}, {:?}, {:?})", slice, lambda_set, result) } - FlatType::Record(_, _) => todo!(), + 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, "[ ")?; diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 169559476d..51771efe07 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1076,8 +1076,8 @@ fn unify_flat_type( // any other combination is a mismatch mismatch!( "Trying to unify two flat types that are incompatible: {:?} ~ {:?}", - other1, - other2 + roc_types::subs::SubsFmtFlatType(other1, subs), + roc_types::subs::SubsFmtFlatType(other2, subs) ) } } 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/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