diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 0280867251..4d482f3d3a 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -565,6 +565,7 @@ pub fn build( interns, layout_interner, }) => { + dbg!(expectations.len()); match config { BuildOnly => { // If possible, report the generated executable name relative to the current dir. @@ -792,12 +793,17 @@ fn make_argv_envp<'a, I: IntoIterator, S: AsRef>( // envp is an array of pointers to strings, conventionally of the // form key=value, which are passed as the environment of the new // program. The envp array must be terminated by a NULL pointer. + let mut buffer = Vec::with_capacity(100); let envp_cstrings: bumpalo::collections::Vec = std::env::vars_os() - .flat_map(|(k, v)| { - [ - CString::new(k.as_bytes()).unwrap(), - CString::new(v.as_bytes()).unwrap(), - ] + .map(|(k, v)| { + buffer.clear(); + + use std::io::Write; + buffer.write_all(k.as_bytes()).unwrap(); + buffer.write_all(b"=").unwrap(); + buffer.write_all(v.as_bytes()).unwrap(); + + CString::new(buffer.as_slice()).unwrap() }) .collect_in(arena); @@ -819,7 +825,7 @@ fn roc_run_native, S: AsRef>( unsafe { let executable = roc_run_executable_file_path(binary_bytes)?; - let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args); + let (argv_cstrings, envp_cstrings) = make_argv_envp(arena, &executable, args); let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings .iter() @@ -833,6 +839,8 @@ fn roc_run_native, S: AsRef>( .chain([std::ptr::null()]) .collect_in(arena); + let opt_level = OptLevel::Development; + match opt_level { OptLevel::Development => roc_run_native_debug( arena, @@ -933,7 +941,8 @@ fn roc_run_native_debug( let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap(); - let shm_name = format!("/roc_expect_buffer_{}", std::process::id()); + // let shm_name = format!("/roc_expect_buffer_{}", std::process::id()); + let shm_name = "/roc_expect_buffer"; let memory = ExpectMemory::create_or_reuse_mmap(&shm_name); let layout_interner = layout_interner.into_global(); @@ -980,6 +989,8 @@ fn roc_run_native_debug( } _ => unreachable!(), } + + println!("done?"); } #[cfg(target_os = "linux")] diff --git a/crates/compiler/build/src/program.rs b/crates/compiler/build/src/program.rs index b731677ba0..050b3038b1 100644 --- a/crates/compiler/build/src/program.rs +++ b/crates/compiler/build/src/program.rs @@ -277,7 +277,7 @@ fn gen_from_mono_module_llvm( interns: loaded.interns, module, target_info, - mode: LlvmBackendMode::Binary, + mode: LlvmBackendMode::BinaryDev, exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(), }; diff --git a/crates/compiler/builtins/bitcode/src/expect.zig b/crates/compiler/builtins/bitcode/src/expect.zig index 64bc0ef748..9aab2081a2 100644 --- a/crates/compiler/builtins/bitcode/src/expect.zig +++ b/crates/compiler/builtins/bitcode/src/expect.zig @@ -21,3 +21,33 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize { pub fn expectFailedStart() callconv(.C) [*]u8 { return SHARED_BUFFER.ptr; } + +extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int; +extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque; +extern fn kill(pid: c_int, sig: c_int) c_int; +extern fn getppid() c_int; + +pub fn readSharedBufferEnv() callconv(.C) void { + const name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes! + const shared_fd = shm_open(@ptrCast(*const i8, name), O_RDWR | O_CREAT, 0o666); + const length = 4096; + + const shared_ptr = mmap( + null, + length, + PROT_WRITE, + MAP_SHARED, + shared_fd, + 0, + ); + + const ptr = @ptrCast([*]u8, shared_ptr); + + SHARED_BUFFER = ptr[0..length]; +} + +pub fn expectFailedFinalize() callconv(.C) void { + const parent_pid = getppid(); + + _ = kill(parent_pid, SIGUSR1); +} diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 44ad0d99eb..eff2e48c97 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -168,9 +168,13 @@ comptime { if (builtin.target.cpu.arch != .wasm32) { exportUtilsFn(expect.expectFailedStart, "expect_failed_start"); + exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize"); // sets the buffer used for expect failures @export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak }); + // + + exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer"); } if (builtin.target.cpu.arch == .aarch64) { diff --git a/crates/compiler/builtins/build.rs b/crates/compiler/builtins/build.rs index 63b8ceb4cf..bf748c29de 100644 --- a/crates/compiler/builtins/build.rs +++ b/crates/compiler/builtins/build.rs @@ -12,7 +12,7 @@ use std::str; use tempfile::tempdir; /// To debug the zig code with debug prints, we need to disable the wasm code gen -const DEBUG: bool = false; +const DEBUG: bool = true; fn zig_executable() -> String { match std::env::var("ROC_ZIG") { @@ -45,11 +45,7 @@ fn main() { generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386"); generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64"); - generate_bc_file( - &bitcode_path, - "ir-windows-x86_64", - "builtins-windows-x86_64", - ); + // generate_bc_file( &bitcode_path, "ir-windows-x86_64", "builtins-windows-x86_64",); // OBJECT FILES #[cfg(windows)] diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 52878f2b3a..2cde884fe5 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -404,6 +404,7 @@ pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null" pub const UTILS_EXPECT_FAILED_START: &str = "roc_builtins.utils.expect_failed_start"; pub const UTILS_EXPECT_FAILED_FINALIZE: &str = "roc_builtins.utils.expect_failed_finalize"; +pub const UTILS_EXPECT_READ_ENV_SHARED_BUFFER: &str = "roc_builtins.utils.read_env_shared_buffer"; pub const UTILS_LONGJMP: &str = "longjmp"; pub const UTILS_SETJMP: &str = "setjmp"; diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 9747c640da..4a1e3c3c63 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -163,6 +163,7 @@ impl<'a, 'ctx> Scope<'a, 'ctx> { pub enum LlvmBackendMode { /// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host Binary, + BinaryDev, /// Creates a test wrapper around the main roc function to catch and report panics. /// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc) GenTest, @@ -174,6 +175,7 @@ impl LlvmBackendMode { pub(crate) fn has_host(self) -> bool { match self { LlvmBackendMode::Binary => true, + LlvmBackendMode::BinaryDev => true, LlvmBackendMode::GenTest => false, LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::CliTest => false, @@ -184,6 +186,7 @@ impl LlvmBackendMode { fn returns_roc_result(self) -> bool { match self { LlvmBackendMode::Binary => false, + LlvmBackendMode::BinaryDev => false, LlvmBackendMode::GenTest => true, LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::CliTest => true, @@ -193,6 +196,7 @@ impl LlvmBackendMode { fn runs_expects(self) -> bool { match self { LlvmBackendMode::Binary => false, + LlvmBackendMode::BinaryDev => true, LlvmBackendMode::GenTest => false, LlvmBackendMode::WasmGenTest => false, LlvmBackendMode::CliTest => true, @@ -2820,6 +2824,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( if env.mode.runs_expects() { bd.position_at_end(throw_block); + if let LlvmBackendMode::BinaryDev = env.mode { + crate::llvm::expect::read_env_shared_buffer(env); + } + match env.target_info.ptr_width() { roc_target::PtrWidth::Bytes8 => { clone_to_shared_memory( @@ -2831,6 +2839,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( lookups, ); + if let LlvmBackendMode::BinaryDev = env.mode { + crate::llvm::expect::finalize(env); + } + bd.build_unconditional_branch(then_block); } roc_target::PtrWidth::Bytes4 => { @@ -3935,7 +3947,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ) } - LlvmBackendMode::Binary => {} + LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {} } // a generic version that writes the result into a passed *u8 pointer @@ -3986,7 +3998,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() } - LlvmBackendMode::Binary => basic_type_from_layout(env, &return_layout), + LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => { + basic_type_from_layout(env, &return_layout) + } }; let size: BasicValueEnum = return_type.size_of().unwrap().into(); @@ -4958,7 +4972,7 @@ pub fn build_proc<'a, 'ctx, 'env>( GenTest | WasmGenTest | CliTest => { /* no host, or exposing types is not supported */ } - Binary => { + Binary | BinaryDev => { for (alias_name, (generated_function, top_level, layout)) in aliases.iter() { expose_alias_to_host( env, diff --git a/crates/compiler/gen_llvm/src/llvm/expect.rs b/crates/compiler/gen_llvm/src/llvm/expect.rs index 55262d559c..53cf2f55bd 100644 --- a/crates/compiler/gen_llvm/src/llvm/expect.rs +++ b/crates/compiler/gen_llvm/src/llvm/expect.rs @@ -93,6 +93,26 @@ fn write_state<'a, 'ctx, 'env>( env.builder.build_store(offset_ptr, offset); } +pub(crate) fn read_env_shared_buffer(env: &Env) { + let func = env + .module + .get_function(bitcode::UTILS_EXPECT_READ_ENV_SHARED_BUFFER) + .unwrap(); + + env.builder + .build_call(func, &[], "call_expect_read_env_shared_buffer"); +} + +pub(crate) fn finalize(env: &Env) { + let func = env + .module + .get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE) + .unwrap(); + + env.builder + .build_call(func, &[], "call_expect_failed_finalize"); +} + pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, diff --git a/crates/repl_expect/src/run.rs b/crates/repl_expect/src/run.rs index b948a40e79..568422c609 100644 --- a/crates/repl_expect/src/run.rs +++ b/crates/repl_expect/src/run.rs @@ -20,8 +20,8 @@ use roc_target::TargetInfo; use target_lexicon::Triple; pub struct ExpectMemory<'a> { - ptr: *mut u8, - length: usize, + pub ptr: *mut u8, + pub length: usize, shm_name: Option, _marker: std::marker::PhantomData<&'a ()>, } @@ -53,6 +53,7 @@ impl<'a> ExpectMemory<'a> { let ptr = unsafe { let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666); + libc::ftruncate(shared_fd, 0); libc::ftruncate(shared_fd, Self::SHM_SIZE as _); libc::mmap( @@ -65,6 +66,9 @@ impl<'a> ExpectMemory<'a> { ) }; + // puts in the initial header + let _ = ExpectSequence::new(ptr as *mut u8); + Self { ptr: ptr.cast(), length: Self::SHM_SIZE, @@ -80,6 +84,33 @@ impl<'a> ExpectMemory<'a> { } } +#[allow(clippy::too_many_arguments)] +pub fn run_inline_expects<'a, W: std::io::Write>( + writer: &mut W, + render_target: RenderTarget, + arena: &'a Bump, + interns: &'a Interns, + layout_interner: &Arc>>, + lib: &libloading::Library, + expectations: &mut VecMap, + expects: ExpectFunctions<'_>, +) -> std::io::Result<(usize, usize)> { + let shm_name = format!("/roc_expect_buffer_{}", std::process::id()); + let mut memory = ExpectMemory::create_or_reuse_mmap(&shm_name); + + run_expects_with_memory( + writer, + render_target, + arena, + interns, + layout_interner, + lib, + expectations, + expects, + &mut memory, + ) +} + #[allow(clippy::too_many_arguments)] pub fn run_toplevel_expects<'a, W: std::io::Write>( writer: &mut W, diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 1d5915be92..209ea9e06b 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -87,7 +87,9 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { const Unit = extern struct {}; -pub export fn main() callconv(.C) u8 { +pub fn main() !u8 { + const stderr = std.io.getStdErr().writer(); + // The size might be zero; if so, make it at least 8 so that we don't have a nullptr const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8); const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?; @@ -108,7 +110,6 @@ pub export fn main() callconv(.C) u8 { const nanos = timer.read(); const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0); - const stderr = std.io.getStdErr().writer(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; return 0;