From 4f97ab29656d2d0f184a66d893971224b93dbf76 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 24 Jun 2023 10:22:23 -0700 Subject: [PATCH 01/11] remove old compiler-rt workarounds for macos --- .../fixtures/multi-dep-str/platform/host.zig | 14 --- .../multi-dep-thunk/platform/host.zig | 14 --- .../tests/fixtures/packages/platform/host.zig | 14 --- .../algorithms/fibonacci-platform/host.zig | 14 --- .../algorithms/quicksort-platform/host.zig | 14 --- .../benchmarks/platform/host.zig | 14 --- .../expects/zig-platform/host.zig | 14 --- crates/compiler/build/src/link.rs | 95 +------------------ crates/compiler/build/src/target.rs | 4 +- .../builtins/bitcode/src/compiler_rt.zig | 45 +++++++++ crates/linker/src/pe.rs | 1 + crates/valgrind/zig-platform/host.zig | 14 --- examples/cli/effects-platform/host.zig | 14 --- examples/cli/tui-platform/host.zig | 14 --- .../platform-switching/zig-platform/host.zig | 14 --- 15 files changed, 49 insertions(+), 250 deletions(-) diff --git a/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig b/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig index 4248e171a8c..2a4fb112849 100644 --- a/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index 18bb5d00749..a7cff59a0b1 100644 --- a/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/crates/cli/tests/fixtures/packages/platform/host.zig b/crates/cli/tests/fixtures/packages/platform/host.zig index 4248e171a8c..2a4fb112849 100644 --- a/crates/cli/tests/fixtures/packages/platform/host.zig +++ b/crates/cli/tests/fixtures/packages/platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/crates/cli_testing_examples/algorithms/fibonacci-platform/host.zig b/crates/cli_testing_examples/algorithms/fibonacci-platform/host.zig index c27916c26f2..6e2d2e6b393 100644 --- a/crates/cli_testing_examples/algorithms/fibonacci-platform/host.zig +++ b/crates/cli_testing_examples/algorithms/fibonacci-platform/host.zig @@ -5,20 +5,6 @@ const expectEqual = testing.expectEqual; const expect = testing.expect; const maxInt = std.math.maxInt; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/crates/cli_testing_examples/algorithms/quicksort-platform/host.zig b/crates/cli_testing_examples/algorithms/quicksort-platform/host.zig index c402674a231..1ae4b1cc0e6 100644 --- a/crates/cli_testing_examples/algorithms/quicksort-platform/host.zig +++ b/crates/cli_testing_examples/algorithms/quicksort-platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/crates/cli_testing_examples/benchmarks/platform/host.zig b/crates/cli_testing_examples/benchmarks/platform/host.zig index 85f343c061b..7902302f451 100644 --- a/crates/cli_testing_examples/benchmarks/platform/host.zig +++ b/crates/cli_testing_examples/benchmarks/platform/host.zig @@ -7,20 +7,6 @@ const expectEqual = testing.expectEqual; const expect = testing.expect; const maxInt = std.math.maxInt; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/crates/cli_testing_examples/expects/zig-platform/host.zig b/crates/cli_testing_examples/expects/zig-platform/host.zig index 20c09320d90..33c1995d8df 100644 --- a/crates/cli_testing_examples/expects/zig-platform/host.zig +++ b/crates/cli_testing_examples/expects/zig-platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const Align = 2 * @alignOf(usize); extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; diff --git a/crates/compiler/build/src/link.rs b/crates/compiler/build/src/link.rs index 2c69ced034f..a48e57c1732 100644 --- a/crates/compiler/build/src/link.rs +++ b/crates/compiler/build/src/link.rs @@ -126,7 +126,7 @@ fn find_wasi_libc_path() -> PathBuf { internal_error!("cannot find `wasi-libc.a`") } -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(unix)] #[allow(clippy::too_many_arguments)] pub fn build_zig_host_native( env_path: &str, @@ -257,99 +257,6 @@ pub fn build_zig_host_native( zig_cmd } -#[cfg(target_os = "macos")] -#[allow(clippy::too_many_arguments)] -pub fn build_zig_host_native( - env_path: &str, - env_home: &str, - emit_bin: &str, - zig_host_src: &str, - _target: &str, - opt_level: OptLevel, - shared_lib_path: Option<&Path>, - builtins_host_path: &Path, - // For compatibility with the non-macOS def above. Keep these in sync. -) -> Command { - use serde_json::Value; - - // Run `zig env` to find the location of zig's std/ directory - let zig_env_output = zig().args(["env"]).output().unwrap(); - - let zig_env_json = if zig_env_output.status.success() { - std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| { - internal_error!( - "`zig env` failed; its stderr output was invalid utf8 ({:?})", - utf8_err - ); - }) - } else { - match std::str::from_utf8(&zig_env_output.stderr) { - Ok(stderr) => internal_error!("`zig env` failed - stderr output was: {:?}", stderr), - Err(utf8_err) => internal_error!( - "`zig env` failed; its stderr output was invalid utf8 ({:?})", - utf8_err - ), - } - }; - - let mut zig_compiler_rt_path = match serde_json::from_str(zig_env_json) { - Ok(Value::Object(map)) => match map.get("std_dir") { - Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)), - _ => { - internal_error!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json); - } - }, - _ => { - internal_error!( - "Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}", - zig_env_json - ); - } - }; - - zig_compiler_rt_path.push("special"); - zig_compiler_rt_path.push("compiler_rt.zig"); - - let mut zig_cmd = zig(); - zig_cmd - .env_clear() - .env("PATH", env_path) - .env("HOME", env_home); - if let Some(shared_lib_path) = shared_lib_path { - zig_cmd.args([ - "build-exe", - "-fPIE", - shared_lib_path.to_str().unwrap(), - builtins_host_path.to_str().unwrap(), - ]); - } else { - zig_cmd.args(["build-obj"]); - } - zig_cmd.args([ - zig_host_src, - &format!("-femit-bin={}", emit_bin), - "--pkg-begin", - "glue", - find_zig_glue_path().to_str().unwrap(), - "--pkg-end", - // include the zig runtime - "--pkg-begin", - "compiler_rt", - zig_compiler_rt_path.to_str().unwrap(), - "--pkg-end", - // include libc - "--library", - "c", - ]); - if matches!(opt_level, OptLevel::Optimize) { - zig_cmd.args(["-O", "ReleaseSafe"]); - } else if matches!(opt_level, OptLevel::Size) { - zig_cmd.args(["-O", "ReleaseSmall"]); - } - - zig_cmd -} - pub fn build_zig_host_wasm32( env_path: &str, env_home: &str, diff --git a/crates/compiler/build/src/target.rs b/crates/compiler/build/src/target.rs index 6abe50cb349..4acd2ce8c4a 100644 --- a/crates/compiler/build/src/target.rs +++ b/crates/compiler/build/src/target.rs @@ -87,12 +87,12 @@ pub fn target_zig_str(target: &Triple) -> &'static str { architecture: Architecture::X86_64, operating_system: OperatingSystem::Darwin, .. - } => "x86_64-apple-darwin", + } => "x86_64-macos-gnu", Triple { architecture: Architecture::Aarch64(_), operating_system: OperatingSystem::Darwin, .. - } => "aarch64-apple-darwin", + } => "aarch64-macos-gnu", _ => internal_error!("TODO gracefully handle unsupported target: {:?}", target), } } diff --git a/crates/compiler/builtins/bitcode/src/compiler_rt.zig b/crates/compiler/builtins/bitcode/src/compiler_rt.zig index b00961fa218..e4bc02da9bf 100644 --- a/crates/compiler/builtins/bitcode/src/compiler_rt.zig +++ b/crates/compiler/builtins/bitcode/src/compiler_rt.zig @@ -18,6 +18,7 @@ const v2u64 = @Vector(2, u64); // Export it as weak incase it is already linked in by something else. comptime { @export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak }); + @export(__lshrti3, .{ .name = "__lshrti3", .linkage = .Weak }); if (want_windows_v2u64_abi) { @export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = .Weak }); @export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = .Weak }); @@ -440,3 +441,47 @@ pub inline fn floatFractionalBits(comptime T: type) comptime_int { else => @compileError("unknown floating point type " ++ @typeName(T)), }; } + +pub fn __lshrti3(a: i128, b: i32) callconv(.C) i128 { + return lshrXi3(i128, a, b); +} + +// Logical shift right: shift in 0 from left to right +// Precondition: 0 <= b < T.bit_count +inline fn lshrXi3(comptime T: type, a: T, b: i32) T { + const word_t = HalveInt(T, false); + const S = std.math.Log2Int(word_t.HalfT); + + const input = word_t{ .all = a }; + var output: word_t = undefined; + + if (b >= word_t.bits) { + output.s.high = 0; + output.s.low = input.s.high >> @intCast(S, b - word_t.bits); + } else if (b == 0) { + return a; + } else { + output.s.high = input.s.high >> @intCast(S, b); + output.s.low = input.s.high << @intCast(S, word_t.bits - b); + output.s.low |= input.s.low >> @intCast(S, b); + } + + return output.all; +} + +/// Allows to access underlying bits as two equally sized lower and higher +/// signed or unsigned integers. +fn HalveInt(comptime T: type, comptime signed_half: bool) type { + return extern union { + pub const bits = @divExact(@typeInfo(T).Int.bits, 2); + pub const HalfTU = std.meta.Int(.unsigned, bits); + pub const HalfTS = std.meta.Int(.signed, bits); + pub const HalfT = if (signed_half) HalfTS else HalfTU; + + all: T, + s: if (native_endian == .Little) + extern struct { low: HalfT, high: HalfT } + else + extern struct { high: HalfT, low: HalfT }, + }; +} diff --git a/crates/linker/src/pe.rs b/crates/linker/src/pe.rs index 0332aaf7144..8848172e1d9 100644 --- a/crates/linker/src/pe.rs +++ b/crates/linker/src/pe.rs @@ -444,6 +444,7 @@ pub(crate) fn surgery_pe(executable_path: &Path, metadata_path: &Path, roc_app_b "__fixsfti", "__fixunsdfti", "__fixunssfti", + "__lshrti3", "memcpy_decision", ] .contains(&name.as_str()); diff --git a/crates/valgrind/zig-platform/host.zig b/crates/valgrind/zig-platform/host.zig index 6025d07d157..8ba1179e4ff 100644 --- a/crates/valgrind/zig-platform/host.zig +++ b/crates/valgrind/zig-platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const Align = 2 * @alignOf(usize); extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; diff --git a/examples/cli/effects-platform/host.zig b/examples/cli/effects-platform/host.zig index a0780797b31..e0e100278ff 100644 --- a/examples/cli/effects-platform/host.zig +++ b/examples/cli/effects-platform/host.zig @@ -7,20 +7,6 @@ const expectEqual = testing.expectEqual; const expect = testing.expect; const maxInt = std.math.maxInt; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/examples/cli/tui-platform/host.zig b/examples/cli/tui-platform/host.zig index 0eb4717d8c7..6e1c1869e93 100644 --- a/examples/cli/tui-platform/host.zig +++ b/examples/cli/tui-platform/host.zig @@ -7,20 +7,6 @@ const expectEqual = testing.expectEqual; const expect = testing.expect; const maxInt = std.math.maxInt; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const mem = std.mem; const Allocator = mem.Allocator; diff --git a/examples/platform-switching/zig-platform/host.zig b/examples/platform-switching/zig-platform/host.zig index 6025d07d157..8ba1179e4ff 100644 --- a/examples/platform-switching/zig-platform/host.zig +++ b/examples/platform-switching/zig-platform/host.zig @@ -6,20 +6,6 @@ const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - const Align = 2 * @alignOf(usize); extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; From 8738c95d6f80115f17cd9551a4c6534141c19039 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 26 Jun 2023 23:51:26 +0200 Subject: [PATCH 02/11] give `0` as a value to our enum attributes --- crates/compiler/build/src/program.rs | 2 +- crates/compiler/gen_llvm/src/llvm/bitcode.rs | 8 ++++---- crates/compiler/gen_llvm/src/llvm/build.rs | 4 ++-- crates/compiler/gen_llvm/src/llvm/lowlevel.rs | 6 +++--- crates/compiler/test_gen/src/helpers/llvm.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/compiler/build/src/program.rs b/crates/compiler/build/src/program.rs index 09f8123f261..6b183d093f6 100644 --- a/crates/compiler/build/src/program.rs +++ b/crates/compiler/build/src/program.rs @@ -166,7 +166,7 @@ fn gen_from_mono_module_llvm<'a>( let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let enum_attr = context.create_enum_attribute(kind_id, 1); + let enum_attr = context.create_enum_attribute(kind_id, 0); for function in module.get_functions() { let name = function.get_name().to_str().unwrap(); diff --git a/crates/compiler/gen_llvm/src/llvm/bitcode.rs b/crates/compiler/gen_llvm/src/llvm/bitcode.rs index 4762076da89..64dac3814e0 100644 --- a/crates/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/crates/compiler/gen_llvm/src/llvm/bitcode.rs @@ -225,7 +225,7 @@ fn build_transform_caller_help<'a, 'ctx>( let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let attr = env.context.create_enum_attribute(kind_id, 1); + let attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, attr); let entry = env.context.append_basic_block(function_value, "entry"); @@ -408,7 +408,7 @@ fn build_rc_wrapper<'a, 'ctx>( let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let attr = env.context.create_enum_attribute(kind_id, 1); + let attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, attr); let entry = env.context.append_basic_block(function_value, "entry"); @@ -497,7 +497,7 @@ pub fn build_eq_wrapper<'a, 'ctx>( let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let attr = env.context.create_enum_attribute(kind_id, 1); + let attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, attr); let entry = env.context.append_basic_block(function_value, "entry"); @@ -598,7 +598,7 @@ pub fn build_compare_wrapper<'a, 'ctx>( let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let attr = env.context.create_enum_attribute(kind_id, 1); + let attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, attr); let entry = env.context.append_basic_block(function_value, "entry"); diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 38a06b4dac7..4d9cf61b795 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -5207,14 +5207,14 @@ fn build_proc_header<'a, 'ctx>( if false { let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let enum_attr = env.context.create_enum_attribute(kind_id, 1); + let enum_attr = env.context.create_enum_attribute(kind_id, 0); fn_val.add_attribute(AttributeLoc::Function, enum_attr); } if false { let kind_id = Attribute::get_named_enum_kind_id("noinline"); debug_assert!(kind_id > 0); - let enum_attr = env.context.create_enum_attribute(kind_id, 1); + let enum_attr = env.context.create_enum_attribute(kind_id, 0); fn_val.add_attribute(AttributeLoc::Function, enum_attr); } diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index 63a9a15e740..285f4b929a8 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -1844,19 +1844,19 @@ fn throw_because_overflow<'ctx>(env: &Env<'_, 'ctx, '_>, message: &str) { // prevent inlining of this function let kind_id = Attribute::get_named_enum_kind_id("noinline"); debug_assert!(kind_id > 0); - let enum_attr = env.context.create_enum_attribute(kind_id, 1); + let enum_attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, enum_attr); // calling this function is unlikely let kind_id = Attribute::get_named_enum_kind_id("cold"); debug_assert!(kind_id > 0); - let enum_attr = env.context.create_enum_attribute(kind_id, 1); + let enum_attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, enum_attr); // this function never returns let kind_id = Attribute::get_named_enum_kind_id("noreturn"); debug_assert!(kind_id > 0); - let enum_attr = env.context.create_enum_attribute(kind_id, 1); + let enum_attr = env.context.create_enum_attribute(kind_id, 0); function_value.add_attribute(AttributeLoc::Function, enum_attr); // Add a basic block for the entry point diff --git a/crates/compiler/test_gen/src/helpers/llvm.rs b/crates/compiler/test_gen/src/helpers/llvm.rs index 2ec40d96245..6ad7cc29a37 100644 --- a/crates/compiler/test_gen/src/helpers/llvm.rs +++ b/crates/compiler/test_gen/src/helpers/llvm.rs @@ -192,7 +192,7 @@ fn create_llvm_module<'a>( let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); - let attr = context.create_enum_attribute(kind_id, 1); + let attr = context.create_enum_attribute(kind_id, 0); for function in module.get_functions() { let name = function.get_name().to_str().unwrap(); From 7311c565f17c98c92cd03b4a4fa40ac17391e7cb Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 26 Jun 2023 23:54:03 +0200 Subject: [PATCH 03/11] use updated llvm type signatures --- crates/compiler/gen_llvm/src/llvm/build.rs | 25 ++++++++----- crates/compiler/gen_llvm/src/llvm/struct_.rs | 37 ++++++++++---------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 4d9cf61b795..3870d5ba388 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -1941,8 +1941,12 @@ fn tag_pointer_set_tag_id<'ctx>( // NOTE: assumes the lower bits of `cast_pointer` are all 0 let indexed_pointer = unsafe { - env.builder - .build_in_bounds_gep(cast_pointer, &[tag_id_intval], "indexed_pointer") + env.builder.new_build_in_bounds_gep( + env.context.i8_type(), + cast_pointer, + &[tag_id_intval], + "indexed_pointer", + ) }; env.builder @@ -1994,7 +1998,14 @@ pub fn tag_pointer_clear_tag_id<'ctx>( "cast_to_i8_ptr", ); - let indexed_pointer = unsafe { env.builder.build_gep(cast_pointer, &[index], "new_ptr") }; + let indexed_pointer = unsafe { + env.builder.new_build_in_bounds_gep( + env.context.i8_type(), + cast_pointer, + &[index], + "new_ptr", + ) + }; env.builder .build_pointer_cast(indexed_pointer, pointer.get_type(), "cast_from_i8_ptr") @@ -3954,11 +3965,9 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>( arguments_for_call.push(*arg); } else { match layout_interner.get_repr(*layout) { - LayoutRepr::Builtin(Builtin::List(_)) => { - let list_type = arg_type - .into_pointer_type() - .get_element_type() - .into_struct_type(); + repr @ LayoutRepr::Builtin(Builtin::List(_)) => { + let list_type = basic_type_from_layout(env, layout_interner, repr); + let loaded = env.builder.new_build_load( list_type, arg.into_pointer_value(), diff --git a/crates/compiler/gen_llvm/src/llvm/struct_.rs b/crates/compiler/gen_llvm/src/llvm/struct_.rs index 30da7eb55e0..64226445868 100644 --- a/crates/compiler/gen_llvm/src/llvm/struct_.rs +++ b/crates/compiler/gen_llvm/src/llvm/struct_.rs @@ -92,7 +92,15 @@ impl<'ctx> RocStruct<'ctx> { index_struct_value(env, layout_interner, field_layouts, *argument, index) } (Self::ByReference(ptr), LayoutRepr::Struct(field_layouts)) => { - index_struct_ptr(env, layout_interner, field_layouts, *ptr, index) + let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); + index_struct_ptr( + env, + layout_interner, + struct_type.into_struct_type(), + field_layouts, + *ptr, + index, + ) } (other, layout) => { unreachable!( @@ -135,26 +143,26 @@ fn index_struct_value<'a, 'ctx>( fn index_struct_ptr<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, + struct_type: StructType<'ctx>, field_layouts: &[InLayout<'a>], ptr: PointerValue<'ctx>, index: u64, ) -> BasicValueEnum<'ctx> { debug_assert!(!field_layouts.is_empty()); - let field_value = get_field_from_ptr( - env, - ptr, - index as _, - env.arena - .alloc(format!("struct_field_access_record_{}", index)), - ); - let field_layout = field_layouts[index as usize]; + let field_repr = layout_interner.get_repr(field_layout); + + let name = format!("struct_field_access_record_{}", index); + let field_value = env + .builder + .new_build_struct_gep(struct_type, ptr, index as u32, &name) + .unwrap(); load_roc_value( env, layout_interner, - layout_interner.get_repr(field_layout), + field_repr, field_value, "struct_field", ) @@ -171,15 +179,6 @@ fn get_field_from_value<'ctx>( .unwrap() } -fn get_field_from_ptr<'ctx>( - env: &Env<'_, 'ctx, '_>, - ptr: PointerValue<'ctx>, - index: u32, - name: &str, -) -> PointerValue<'ctx> { - env.builder.build_struct_gep(ptr, index, name).unwrap() -} - struct BuildStruct<'ctx> { struct_type: StructType<'ctx>, struct_val: StructValue<'ctx>, From 7c9c3d829cf10ad02ee90527809f7bb435dfc330 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 27 Jun 2023 00:37:09 +0200 Subject: [PATCH 04/11] use 32-bit GEP indices where easily possible --- crates/compiler/gen_llvm/src/llvm/build.rs | 17 +++++++++++------ .../compiler/gen_llvm/src/llvm/refcounting.rs | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 3870d5ba388..6bcae600c44 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -1931,7 +1931,7 @@ fn tag_pointer_set_tag_id<'ctx>( // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3) debug_assert!((tag_id as u32) < env.target_info.ptr_width() as u32); - let tag_id_intval = env.ptr_int().const_int(tag_id as u64, false); + let tag_id_intval = env.context.i32_type().const_int(tag_id as u64, false); let cast_pointer = env.builder.build_pointer_cast( pointer, @@ -1991,6 +1991,9 @@ pub fn tag_pointer_clear_tag_id<'ctx>( let current_tag_id = env.builder.build_and(as_int, mask, "masked"); let index = env.builder.build_int_neg(current_tag_id, "index"); + let index = env + .builder + .build_int_cast(index, env.context.i32_type(), "to_i32"); let cast_pointer = env.builder.build_pointer_cast( pointer, @@ -2444,8 +2447,9 @@ fn list_literal<'a, 'ctx>( // all elements are constants, so we can use the memory in the constants section directly // here we make a pointer to the first actual element (skipping the 0 bytes that // represent the refcount) - let zero = env.ptr_int().const_zero(); - let offset = env.ptr_int().const_int(zero_elements as _, false); + let i32_type = env.context.i32_type(); + let zero = i32_type.const_zero(); + let offset = i32_type.const_int(zero_elements as _, false); let ptr = unsafe { env.builder.new_build_in_bounds_gep( @@ -2474,7 +2478,7 @@ fn list_literal<'a, 'ctx>( // then replace the `undef`s with the values that we evaluate at runtime for (index, val) in runtime_evaluated_elements { - let index_val = ctx.i64_type().const_int(index as u64, false); + let index_val = ctx.i32_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index") }; @@ -2495,7 +2499,7 @@ fn list_literal<'a, 'ctx>( } ListLiteralElement::Symbol(symbol) => scope.load_symbol(symbol), }; - let index_val = ctx.i64_type().const_int(index as u64, false); + let index_val = ctx.i32_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index") }; @@ -6259,7 +6263,8 @@ fn define_global_str_literal_ptr<'ctx>( env.context.i8_type(), ptr, &[env - .ptr_int() + .context + .i32_type() .const_int(env.target_info.ptr_width() as u64, false)], "get_rc_ptr", ) diff --git a/crates/compiler/gen_llvm/src/llvm/refcounting.rs b/crates/compiler/gen_llvm/src/llvm/refcounting.rs index 06f9e73bf1e..6d03fa54c29 100644 --- a/crates/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/crates/compiler/gen_llvm/src/llvm/refcounting.rs @@ -61,7 +61,7 @@ impl<'ctx> PointerToRefcount<'ctx> { builder.build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr"); // get a pointer to index -1 - let index_intvalue = refcount_type.const_int(-1_i64 as u64, false); + let index_intvalue = env.context.i32_type().const_int(-1_i64 as u64, false); let refcount_ptr = unsafe { builder.new_build_in_bounds_gep( env.ptr_int(), From b26ef289b62d2c4ca4896997d8cecf51ce57237b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 26 Jun 2023 19:38:58 -0400 Subject: [PATCH 05/11] Add basic webserver platform --- examples/webserver/.gitignore | 2 + examples/webserver/Http.roc | 4 + examples/webserver/HttpInternal.roc | 4 + examples/webserver/build.sh | 2 + examples/webserver/main.roc | 6 ++ examples/webserver/platform/Cargo.toml | 38 +++++++ examples/webserver/platform/build.rs | 9 ++ examples/webserver/platform/host.c | 14 +++ examples/webserver/platform/main.roc | 9 ++ examples/webserver/platform/src/lib.rs | 128 ++++++++++++++++++++++++ examples/webserver/platform/src/main.rs | 3 + 11 files changed, 219 insertions(+) create mode 100644 examples/webserver/.gitignore create mode 100644 examples/webserver/Http.roc create mode 100644 examples/webserver/HttpInternal.roc create mode 100755 examples/webserver/build.sh create mode 100644 examples/webserver/main.roc create mode 100644 examples/webserver/platform/Cargo.toml create mode 100644 examples/webserver/platform/build.rs create mode 100644 examples/webserver/platform/host.c create mode 100644 examples/webserver/platform/main.roc create mode 100644 examples/webserver/platform/src/lib.rs create mode 100644 examples/webserver/platform/src/main.rs diff --git a/examples/webserver/.gitignore b/examples/webserver/.gitignore new file mode 100644 index 00000000000..d0e2c82835f --- /dev/null +++ b/examples/webserver/.gitignore @@ -0,0 +1,2 @@ +platform/glue +app diff --git a/examples/webserver/Http.roc b/examples/webserver/Http.roc new file mode 100644 index 00000000000..bdb8a14eba3 --- /dev/null +++ b/examples/webserver/Http.roc @@ -0,0 +1,4 @@ +interface Http exposes [request] imports [HttpInternal] + +request : Str -> Str +request = \req -> HttpInternal.request req diff --git a/examples/webserver/HttpInternal.roc b/examples/webserver/HttpInternal.roc new file mode 100644 index 00000000000..0201a2d2723 --- /dev/null +++ b/examples/webserver/HttpInternal.roc @@ -0,0 +1,4 @@ +interface HttpInternal exposes [request] imports [] + +request : Str -> Str +request = \req -> req diff --git a/examples/webserver/build.sh b/examples/webserver/build.sh new file mode 100755 index 00000000000..46790466f83 --- /dev/null +++ b/examples/webserver/build.sh @@ -0,0 +1,2 @@ +roc glue ../../crates/glue/src/RustGlue.roc platform/glue platform/main.roc +roc build diff --git a/examples/webserver/main.roc b/examples/webserver/main.roc new file mode 100644 index 00000000000..d5660386553 --- /dev/null +++ b/examples/webserver/main.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "platform/main.roc" } + imports [] + provides [main] to pf + +main = \str -> "hi, \(str)!!" diff --git a/examples/webserver/platform/Cargo.toml b/examples/webserver/platform/Cargo.toml new file mode 100644 index 00000000000..29062b4c73f --- /dev/null +++ b/examples/webserver/platform/Cargo.toml @@ -0,0 +1,38 @@ +# ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ +# +# This file is a fixture template. If the file you're looking at is +# in the fixture-templates/ directory, then you're all set - go ahead +# and modify it, and it will modify all the fixture tests. +# +# If this file is in the fixtures/ directory, on the other hand, then +# it is gitignored and will be overwritten the next time tests run. +# So you probably don't want to modify it by hand! Instead, modify the +# file with the same name in the fixture-templates/ directory. + +[package] +name = "host" +version = "0.0.1" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +links = "app" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_std = { path = "glue/roc_std" } +roc_app = { path = "glue/roc_app" } +libc = "0.2" +hyper = { version = "0.14", features= ["http1", "http2", "client", "server", "runtime", "backports", "deprecated"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3" +bytes = "1.0" + +[workspace] diff --git a/examples/webserver/platform/build.rs b/examples/webserver/platform/build.rs new file mode 100644 index 00000000000..47763b34c39 --- /dev/null +++ b/examples/webserver/platform/build.rs @@ -0,0 +1,9 @@ +fn main() { + #[cfg(not(windows))] + println!("cargo:rustc-link-lib=dylib=app"); + + #[cfg(windows)] + println!("cargo:rustc-link-lib=dylib=libapp"); + + println!("cargo:rustc-link-search=."); +} diff --git a/examples/webserver/platform/host.c b/examples/webserver/platform/host.c new file mode 100644 index 00000000000..3914d3f6eed --- /dev/null +++ b/examples/webserver/platform/host.c @@ -0,0 +1,14 @@ +// ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ +// +// This file is a fixture template. If the file you're looking at is +// in the fixture-templates/ directory, then you're all set - go ahead +// and modify it, and it will modify all the fixture tests. +// +// If this file is in the fixtures/ directory, on the other hand, then +// it is gitignored and will be overwritten the next time tests run. +// So you probably don't want to modify it by hand! Instead, modify the +// file with the same name in the fixture-templates/ directory. + +extern int rust_main(); + +int main() { return rust_main(); } diff --git a/examples/webserver/platform/main.roc b/examples/webserver/platform/main.roc new file mode 100644 index 00000000000..c01047b7575 --- /dev/null +++ b/examples/webserver/platform/main.roc @@ -0,0 +1,9 @@ +platform "webserver-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [mainForHost] + +mainForHost : Str -> Str +mainForHost = \str -> main str diff --git a/examples/webserver/platform/src/lib.rs b/examples/webserver/platform/src/lib.rs new file mode 100644 index 00000000000..6cbdb4f27fe --- /dev/null +++ b/examples/webserver/platform/src/lib.rs @@ -0,0 +1,128 @@ +use futures::{Future, FutureExt}; +use hyper::{Body, Request, Response, Server, StatusCode}; +use std::convert::Infallible; +use std::net::SocketAddr; +use std::panic::AssertUnwindSafe; +use tokio::task::spawn_blocking; +use roc_app; +use std::time::Instant; + +const LISTEN_ON_PORT: u16 = 8000; + +fn call_roc(req_bytes: &[u8]) -> (StatusCode, Vec) { + // TODO setjmp (both for signal handlers and for roc_panic, bc calling Rust panics from FFI code is UB) + // TODO install signal handlers + // TODO convert roc_bytes to RocList, call roc_mainForHost, and convert from its RocList response + let req_str: &str = std::str::from_utf8(req_bytes).unwrap(); // TODO don't unwrap + + (StatusCode::OK, roc_app::mainForHost(req_str.into()).as_str().into()) +} + +async fn handle(req: Request) -> Response { + match hyper::body::to_bytes(req.into_body()).await { + Ok(req_body) => { + spawn_blocking(move || { + let (status_code, resp_bytes) = call_roc(&req_body); + + Response::builder() + .status(status_code) // TODO get status code from Roc too + .body(Body::from(resp_bytes)) + .unwrap() // TODO don't unwrap() here + }) + .then(|resp| async { + resp.unwrap() // TODO don't unwrap here + }) + .await + } + Err(_) => todo!(), // TODO + } +} + +/// Translate Rust panics in the given Future into 500 errors +async fn handle_panics( + fut: impl Future>, +) -> Result, Infallible> { + match AssertUnwindSafe(fut).catch_unwind().await { + Ok(response) => Ok(response), + Err(_panic) => { + let error = Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body("Panic detected!".into()) + .unwrap(); // TODO don't unwrap here + + Ok(error) + } + } +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + let start_time = Instant::now(); + let addr = SocketAddr::from(([127, 0, 0, 1], LISTEN_ON_PORT)); + + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() // TODO print and error and return nonzero + .block_on(async { + let server = Server::bind(&addr).serve(hyper::service::make_service_fn(|_conn| async { + Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle(req)))) + })); + + let elapsed_time = start_time.elapsed(); + + println!("Server started up and listening on {:?} in {} ms", addr, elapsed_time.as_millis()); + + match server.await { + Ok(_) => 0, + Err(err) => { + eprintln!("Error initializing Rust `hyper` server: {}", err); // TODO improve this + 1 + } + } + }) +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" 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 unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/examples/webserver/platform/src/main.rs b/examples/webserver/platform/src/main.rs new file mode 100644 index 00000000000..0765384f29f --- /dev/null +++ b/examples/webserver/platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main() as _); +} From db8d222c52f6f15093e01965dd2d4d09adb85b09 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 26 Jun 2023 21:12:49 -0400 Subject: [PATCH 06/11] Refactor some webserver example init stuff --- examples/webserver/platform/src/lib.rs | 55 ++++++++++++++------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/examples/webserver/platform/src/lib.rs b/examples/webserver/platform/src/lib.rs index 6cbdb4f27fe..09d5b6de134 100644 --- a/examples/webserver/platform/src/lib.rs +++ b/examples/webserver/platform/src/lib.rs @@ -5,9 +5,8 @@ use std::net::SocketAddr; use std::panic::AssertUnwindSafe; use tokio::task::spawn_blocking; use roc_app; -use std::time::Instant; -const LISTEN_ON_PORT: u16 = 8000; +const DEFAULT_PORT: u16 = 8000; fn call_roc(req_bytes: &[u8]) -> (StatusCode, Vec) { // TODO setjmp (both for signal handlers and for roc_panic, bc calling Rust panics from FFI code is UB) @@ -55,32 +54,36 @@ async fn handle_panics( } } +const LOCALHOST: [u8; 4] = [127, 0, 0, 1]; + +async fn run_server(port: u16) -> i32 { + let addr = SocketAddr::from((LOCALHOST, port)); + let server = Server::bind(&addr).serve(hyper::service::make_service_fn(|_conn| async { + Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle(req)))) + })); + + println!("Listening on "); + + match server.await { + Ok(_) => 0, + Err(err) => { + eprintln!("Error initializing Rust `hyper` server: {}", err); // TODO improve this + + 1 + } + } +} + #[no_mangle] pub extern "C" fn rust_main() -> i32 { - let start_time = Instant::now(); - let addr = SocketAddr::from(([127, 0, 0, 1], LISTEN_ON_PORT)); - - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() // TODO print and error and return nonzero - .block_on(async { - let server = Server::bind(&addr).serve(hyper::service::make_service_fn(|_conn| async { - Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle(req)))) - })); - - let elapsed_time = start_time.elapsed(); - - println!("Server started up and listening on {:?} in {} ms", addr, elapsed_time.as_millis()); - - match server.await { - Ok(_) => 0, - Err(err) => { - eprintln!("Error initializing Rust `hyper` server: {}", err); // TODO improve this - 1 - } - } - }) + match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + Ok(runtime) => runtime.block_on(async { run_server(DEFAULT_PORT).await }), + Err(err) => { + eprintln!("Error initializing tokio multithreaded runtime: {}", err); // TODO improve this + + 1 + } + } } // Externs required by roc_std and by the Roc app From 7df7d5100627b85973093fd15a80d08e67186b1f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 26 Jun 2023 22:13:59 -0400 Subject: [PATCH 07/11] webserver example setjmp/longjmp error handling --- examples/webserver/platform/Cargo.toml | 4 +- examples/webserver/platform/src/lib.rs | 151 +++++++++++++++++++------ 2 files changed, 117 insertions(+), 38 deletions(-) diff --git a/examples/webserver/platform/Cargo.toml b/examples/webserver/platform/Cargo.toml index 29062b4c73f..867603fafc2 100644 --- a/examples/webserver/platform/Cargo.toml +++ b/examples/webserver/platform/Cargo.toml @@ -27,10 +27,10 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "glue/roc_std" } +roc_std = { path = "glue/roc_std", features = ["std"] } roc_app = { path = "glue/roc_app" } libc = "0.2" -hyper = { version = "0.14", features= ["http1", "http2", "client", "server", "runtime", "backports", "deprecated"] } +hyper = { version = "0.14", features = ["http1", "http2", "client", "server", "runtime", "backports", "deprecated"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } futures = "0.3" bytes = "1.0" diff --git a/examples/webserver/platform/src/lib.rs b/examples/webserver/platform/src/lib.rs index 09d5b6de134..ad54ace3592 100644 --- a/examples/webserver/platform/src/lib.rs +++ b/examples/webserver/platform/src/lib.rs @@ -1,37 +1,114 @@ use futures::{Future, FutureExt}; use hyper::{Body, Request, Response, Server, StatusCode}; +use roc_app; +use roc_std::RocStr; +use std::cell::RefCell; use std::convert::Infallible; use std::net::SocketAddr; +use std::os::raw::{c_int, c_long, c_void}; use std::panic::AssertUnwindSafe; use tokio::task::spawn_blocking; -use roc_app; +use libc::{sigaction, siginfo_t, sigemptyset, SIGBUS, SIGFPE, SIGILL, SIGSEGV, sighandler_t, sigset_t, SA_SIGINFO, SIG_DFL}; const DEFAULT_PORT: u16 = 8000; -fn call_roc(req_bytes: &[u8]) -> (StatusCode, Vec) { - // TODO setjmp (both for signal handlers and for roc_panic, bc calling Rust panics from FFI code is UB) - // TODO install signal handlers - // TODO convert roc_bytes to RocList, call roc_mainForHost, and convert from its RocList response - let req_str: &str = std::str::from_utf8(req_bytes).unwrap(); // TODO don't unwrap +// If we have a roc_panic or a segfault, these will be used to record where to jump back to +// (a point at which we can return a different response). +thread_local! { + // 64 is the biggest jmp_buf in setjmp.h + static SETJMP_ENV: RefCell<[c_long; 64]> = RefCell::new([0 as c_long; 64]); + static ROC_CRASH_MSG: RefCell = RefCell::new(RocStr::empty()); + static SIGNAL_CAUGHT: RefCell = RefCell::new(0); +} + +extern "C" { + #[link_name = "setjmp"] + pub fn setjmp(env: *mut c_void) -> c_int; + + #[link_name = "longjmp"] + pub fn longjmp(env: *mut c_void, val: c_int); +} + +unsafe extern "C" fn signal_handler(sig: c_int, _: *mut siginfo_t, _: *mut libc::c_void) { + SIGNAL_CAUGHT.with(|val| { + *val.borrow_mut() = sig; + }); + + SETJMP_ENV.with(|env| { + longjmp(env.borrow_mut().as_mut_ptr().cast(), 1); + }); +} + +fn setup_signal(sig: c_int) { + let sa = libc::sigaction { + sa_sigaction: signal_handler as sighandler_t, + sa_mask: sigset_t::default(), + sa_flags: SA_SIGINFO, + }; + + let mut old_sa = libc::sigaction { + sa_sigaction: SIG_DFL, + sa_mask: sigset_t::default(), + sa_flags: 0, + }; + + unsafe { + sigemptyset(&mut old_sa.sa_mask as *mut sigset_t); + sigaction(sig, &sa, &mut old_sa); + } +} + +fn call_roc(req_bytes: &[u8]) -> Response { + let mut setjmp_result = 0; + + SETJMP_ENV.with(|env| { + setjmp_result = unsafe { setjmp(env.borrow_mut().as_mut_ptr().cast()) }; + }); + + if setjmp_result == 0 { + setup_signal(SIGSEGV); + setup_signal(SIGILL); + setup_signal(SIGFPE); + setup_signal(SIGBUS); + + let req_str: &str = std::str::from_utf8(req_bytes).unwrap(); // TODO don't unwrap + let resp: String = roc_app::mainForHost(req_str.into()).as_str().into(); + + Response::builder() + .status(StatusCode::OK) // TODO get status code from Roc too + .body(Body::from(resp)) + .unwrap() // TODO don't unwrap() here + } else { + let mut crash_msg: String = String::new(); + let mut sig: c_int = 0; + + SIGNAL_CAUGHT.with(|val| { + sig = *val.borrow(); + }); + + if sig == 0 { + ROC_CRASH_MSG.with(|env| { + crash_msg = env.borrow().as_str().into(); + }); + } else { + crash_msg = "Roc crashed with signal {sig}".into(); // TODO print the name of the signal + } - (StatusCode::OK, roc_app::mainForHost(req_str.into()).as_str().into()) + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from(crash_msg)) + .unwrap() // TODO don't unwrap() here + } } -async fn handle(req: Request) -> Response { +async fn handle_req(req: Request) -> Response { match hyper::body::to_bytes(req.into_body()).await { Ok(req_body) => { - spawn_blocking(move || { - let (status_code, resp_bytes) = call_roc(&req_body); - - Response::builder() - .status(status_code) // TODO get status code from Roc too - .body(Body::from(resp_bytes)) - .unwrap() // TODO don't unwrap() here - }) - .then(|resp| async { - resp.unwrap() // TODO don't unwrap here - }) - .await + spawn_blocking(move || call_roc(&req_body)) + .then(|resp| async { + resp.unwrap() // TODO don't unwrap here + }) + .await } Err(_) => todo!(), // TODO } @@ -59,7 +136,7 @@ const LOCALHOST: [u8; 4] = [127, 0, 0, 1]; async fn run_server(port: u16) -> i32 { let addr = SocketAddr::from((LOCALHOST, port)); let server = Server::bind(&addr).serve(hyper::service::make_service_fn(|_conn| async { - Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle(req)))) + Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle_req(req)))) })); println!("Listening on "); @@ -76,7 +153,10 @@ async fn run_server(port: u16) -> i32 { #[no_mangle] pub extern "C" fn rust_main() -> i32 { - match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + match tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + { Ok(runtime) => runtime.block_on(async { run_server(DEFAULT_PORT).await }), Err(err) => { eprintln!("Error initializing tokio multithreaded runtime: {}", err); // TODO improve this @@ -88,10 +168,6 @@ pub extern "C" fn rust_main() -> i32 { // Externs required by roc_std and by the Roc app -use core::ffi::c_void; -use std::ffi::CStr; -use std::os::raw::c_char; - #[no_mangle] pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { return libc::malloc(size); @@ -113,16 +189,19 @@ pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { } #[no_mangle] -pub unsafe extern "C" 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!(), - } +pub unsafe extern "C" fn roc_panic(msg: RocStr) { + // Set the last caught signal to 0, so we don't mistake this for a signal. + SIGNAL_CAUGHT.with(|val| { + *val.borrow_mut() = 0; + }); + + ROC_CRASH_MSG.with(|val| { + *val.borrow_mut() = msg; + }); + + SETJMP_ENV.with(|env| { + longjmp(env.borrow_mut().as_mut_ptr().cast(), 1); + }); } #[no_mangle] From 1c52c23c5faae9582d2cede9c0e6bf57b180283b Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 27 Jun 2023 11:02:24 +0200 Subject: [PATCH 08/11] Revert "use 32-bit GEP indices where easily possible" This reverts commit 7c9c3d829cf10ad02ee90527809f7bb435dfc330. --- crates/compiler/gen_llvm/src/llvm/build.rs | 17 ++++++----------- .../compiler/gen_llvm/src/llvm/refcounting.rs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 6bcae600c44..3870d5ba388 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -1931,7 +1931,7 @@ fn tag_pointer_set_tag_id<'ctx>( // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3) debug_assert!((tag_id as u32) < env.target_info.ptr_width() as u32); - let tag_id_intval = env.context.i32_type().const_int(tag_id as u64, false); + let tag_id_intval = env.ptr_int().const_int(tag_id as u64, false); let cast_pointer = env.builder.build_pointer_cast( pointer, @@ -1991,9 +1991,6 @@ pub fn tag_pointer_clear_tag_id<'ctx>( let current_tag_id = env.builder.build_and(as_int, mask, "masked"); let index = env.builder.build_int_neg(current_tag_id, "index"); - let index = env - .builder - .build_int_cast(index, env.context.i32_type(), "to_i32"); let cast_pointer = env.builder.build_pointer_cast( pointer, @@ -2447,9 +2444,8 @@ fn list_literal<'a, 'ctx>( // all elements are constants, so we can use the memory in the constants section directly // here we make a pointer to the first actual element (skipping the 0 bytes that // represent the refcount) - let i32_type = env.context.i32_type(); - let zero = i32_type.const_zero(); - let offset = i32_type.const_int(zero_elements as _, false); + let zero = env.ptr_int().const_zero(); + let offset = env.ptr_int().const_int(zero_elements as _, false); let ptr = unsafe { env.builder.new_build_in_bounds_gep( @@ -2478,7 +2474,7 @@ fn list_literal<'a, 'ctx>( // then replace the `undef`s with the values that we evaluate at runtime for (index, val) in runtime_evaluated_elements { - let index_val = ctx.i32_type().const_int(index as u64, false); + let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index") }; @@ -2499,7 +2495,7 @@ fn list_literal<'a, 'ctx>( } ListLiteralElement::Symbol(symbol) => scope.load_symbol(symbol), }; - let index_val = ctx.i32_type().const_int(index as u64, false); + let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index") }; @@ -6263,8 +6259,7 @@ fn define_global_str_literal_ptr<'ctx>( env.context.i8_type(), ptr, &[env - .context - .i32_type() + .ptr_int() .const_int(env.target_info.ptr_width() as u64, false)], "get_rc_ptr", ) diff --git a/crates/compiler/gen_llvm/src/llvm/refcounting.rs b/crates/compiler/gen_llvm/src/llvm/refcounting.rs index 6d03fa54c29..06f9e73bf1e 100644 --- a/crates/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/crates/compiler/gen_llvm/src/llvm/refcounting.rs @@ -61,7 +61,7 @@ impl<'ctx> PointerToRefcount<'ctx> { builder.build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr"); // get a pointer to index -1 - let index_intvalue = env.context.i32_type().const_int(-1_i64 as u64, false); + let index_intvalue = refcount_type.const_int(-1_i64 as u64, false); let refcount_ptr = unsafe { builder.new_build_in_bounds_gep( env.ptr_int(), From 8bdbc22f295e4a32311f78e267a4794818bf9aab Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 27 Jun 2023 16:11:54 +0200 Subject: [PATCH 09/11] basic-cli old docs site Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com> --- www/build.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/www/build.sh b/www/build.sh index e2df683f4b8..cdd14f3cfd2 100755 --- a/www/build.sh +++ b/www/build.sh @@ -115,4 +115,13 @@ mkdir -p $BASIC_CLI_PACKAGE_DIR rm generated-docs/*.* # we already copied over the *.js and *.css files earlier, so just drop these. mv generated-docs/* $BASIC_CLI_PACKAGE_DIR # move all the folders to build/packages/basic-cli +# set up docs for basic-cli 0.3.2 +BASIC_CLI_DIR_0_3_2=$BASIC_CLI_PACKAGE_DIR/0-3-2 +mkdir -p $BASIC_CLI_DIR_0_3_2 +curl -fL --output $BASIC_CLI_DIR_0_3_2/docs.tar.gz https://github.com/roc-lang/basic-cli/releases/download/0.3.2/docs.tar.gz +tar -xf $BASIC_CLI_DIR_0_3_2/docs.tar.gz -C $BASIC_CLI_DIR_0_3_2/ +rm $BASIC_CLI_DIR_0_3_2/docs.tar.gz +mv $BASIC_CLI_DIR_0_3_2/generated-docs/* $BASIC_CLI_DIR_0_3_2 +rm -rf $BASIC_CLI_DIR_0_3_2/generated-docs + popd From bf2c01554fba1e8eb607f2dc53d57405c52f0049 Mon Sep 17 00:00:00 2001 From: Kilian Vounckx Date: Tue, 27 Jun 2023 20:12:02 +0200 Subject: [PATCH 10/11] Give better error messages when provides is missing in header --- crates/reporting/src/error/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index abf66192240..3d1cba1403a 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -3469,7 +3469,7 @@ fn to_provides_report<'a>( } } - EProvides::Provides(pos) => { + EProvides::Provides(pos) | EProvides::IndentProvides(pos) => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); From 3bc8a33668bfe62d757816cc1da71b9562884172 Mon Sep 17 00:00:00 2001 From: Kilian Vounckx Date: Tue, 27 Jun 2023 20:50:48 +0200 Subject: [PATCH 11/11] Add missing provides error message test --- crates/reporting/tests/test_reporting.rs | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index aeed31a9fcc..d186cd20bed 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -5938,6 +5938,40 @@ In roc, functions are always written as a lambda, like{} ) } + #[test] + fn missing_provides_in_app_header() { + report_header_problem_as( + indoc!( + r#" + app "broken" + packages { + pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.2/tE4xS_zLdmmxmHwHih9kHWQ7fsXtJr7W7h3425-eZFk.tar.br", + } + imports [ + pf.Stdout, + ] + + main = + Stdout.line "answer" + "# + ), + indoc!( + r#" + ── WEIRD PROVIDES ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am partway through parsing a header, but I got stuck here: + + 7│ ] + ^ + + I am expecting the `provides` keyword next, like + + provides [Animal, default, tame] + "# + ), + ) + } + #[test] fn platform_requires_rigids() { report_header_problem_as(