working expects in roc dev

This commit is contained in:
Folkert 2022-10-05 20:57:37 +02:00
parent 67494e9df2
commit 8c4a2f58fc
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
10 changed files with 129 additions and 21 deletions

View file

@ -565,6 +565,7 @@ pub fn build(
interns, interns,
layout_interner, layout_interner,
}) => { }) => {
dbg!(expectations.len());
match config { match config {
BuildOnly => { BuildOnly => {
// If possible, report the generated executable name relative to the current dir. // If possible, report the generated executable name relative to the current dir.
@ -792,12 +793,17 @@ fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
// envp is an array of pointers to strings, conventionally of the // envp is an array of pointers to strings, conventionally of the
// form key=value, which are passed as the environment of the new // form key=value, which are passed as the environment of the new
// program. The envp array must be terminated by a NULL pointer. // program. The envp array must be terminated by a NULL pointer.
let mut buffer = Vec::with_capacity(100);
let envp_cstrings: bumpalo::collections::Vec<CString> = std::env::vars_os() let envp_cstrings: bumpalo::collections::Vec<CString> = std::env::vars_os()
.flat_map(|(k, v)| { .map(|(k, v)| {
[ buffer.clear();
CString::new(k.as_bytes()).unwrap(),
CString::new(v.as_bytes()).unwrap(), 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); .collect_in(arena);
@ -819,7 +825,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
unsafe { unsafe {
let executable = roc_run_executable_file_path(binary_bytes)?; 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 let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
.iter() .iter()
@ -833,6 +839,8 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
.chain([std::ptr::null()]) .chain([std::ptr::null()])
.collect_in(arena); .collect_in(arena);
let opt_level = OptLevel::Development;
match opt_level { match opt_level {
OptLevel::Development => roc_run_native_debug( OptLevel::Development => roc_run_native_debug(
arena, arena,
@ -933,7 +941,8 @@ fn roc_run_native_debug(
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap(); 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 memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
let layout_interner = layout_interner.into_global(); let layout_interner = layout_interner.into_global();
@ -980,6 +989,8 @@ fn roc_run_native_debug(
} }
_ => unreachable!(), _ => unreachable!(),
} }
println!("done?");
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View file

@ -277,7 +277,7 @@ fn gen_from_mono_module_llvm(
interns: loaded.interns, interns: loaded.interns,
module, module,
target_info, target_info,
mode: LlvmBackendMode::Binary, mode: LlvmBackendMode::BinaryDev,
exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(), exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
}; };

View file

@ -21,3 +21,33 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize {
pub fn expectFailedStart() callconv(.C) [*]u8 { pub fn expectFailedStart() callconv(.C) [*]u8 {
return SHARED_BUFFER.ptr; 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);
}

View file

@ -168,9 +168,13 @@ comptime {
if (builtin.target.cpu.arch != .wasm32) { if (builtin.target.cpu.arch != .wasm32) {
exportUtilsFn(expect.expectFailedStart, "expect_failed_start"); exportUtilsFn(expect.expectFailedStart, "expect_failed_start");
exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize");
// sets the buffer used for expect failures // sets the buffer used for expect failures
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak }); @export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
//
exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer");
} }
if (builtin.target.cpu.arch == .aarch64) { if (builtin.target.cpu.arch == .aarch64) {

View file

@ -12,7 +12,7 @@ use std::str;
use tempfile::tempdir; use tempfile::tempdir;
/// To debug the zig code with debug prints, we need to disable the wasm code gen /// 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 { fn zig_executable() -> String {
match std::env::var("ROC_ZIG") { 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-i386", "builtins-i386");
generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64"); generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
generate_bc_file( // generate_bc_file( &bitcode_path, "ir-windows-x86_64", "builtins-windows-x86_64",);
&bitcode_path,
"ir-windows-x86_64",
"builtins-windows-x86_64",
);
// OBJECT FILES // OBJECT FILES
#[cfg(windows)] #[cfg(windows)]

View file

@ -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_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_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_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp"; pub const UTILS_SETJMP: &str = "setjmp";

View file

@ -163,6 +163,7 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
pub enum LlvmBackendMode { pub enum LlvmBackendMode {
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host /// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
Binary, Binary,
BinaryDev,
/// Creates a test wrapper around the main roc function to catch and report panics. /// 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) /// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
GenTest, GenTest,
@ -174,6 +175,7 @@ impl LlvmBackendMode {
pub(crate) fn has_host(self) -> bool { pub(crate) fn has_host(self) -> bool {
match self { match self {
LlvmBackendMode::Binary => true, LlvmBackendMode::Binary => true,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::GenTest => false, LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => false, LlvmBackendMode::CliTest => false,
@ -184,6 +186,7 @@ impl LlvmBackendMode {
fn returns_roc_result(self) -> bool { fn returns_roc_result(self) -> bool {
match self { match self {
LlvmBackendMode::Binary => false, LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => false,
LlvmBackendMode::GenTest => true, LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => true, LlvmBackendMode::CliTest => true,
@ -193,6 +196,7 @@ impl LlvmBackendMode {
fn runs_expects(self) -> bool { fn runs_expects(self) -> bool {
match self { match self {
LlvmBackendMode::Binary => false, LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::GenTest => false, LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false, LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => true, LlvmBackendMode::CliTest => true,
@ -2820,6 +2824,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
if env.mode.runs_expects() { if env.mode.runs_expects() {
bd.position_at_end(throw_block); 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() { match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes8 => { roc_target::PtrWidth::Bytes8 => {
clone_to_shared_memory( clone_to_shared_memory(
@ -2831,6 +2839,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
lookups, lookups,
); );
if let LlvmBackendMode::BinaryDev = env.mode {
crate::llvm::expect::finalize(env);
}
bd.build_unconditional_branch(then_block); bd.build_unconditional_branch(then_block);
} }
roc_target::PtrWidth::Bytes4 => { 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 // 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() 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(); let size: BasicValueEnum = return_type.size_of().unwrap().into();
@ -4958,7 +4972,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
GenTest | WasmGenTest | CliTest => { GenTest | WasmGenTest | CliTest => {
/* no host, or exposing types is not supported */ /* no host, or exposing types is not supported */
} }
Binary => { Binary | BinaryDev => {
for (alias_name, (generated_function, top_level, layout)) in aliases.iter() { for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
expose_alias_to_host( expose_alias_to_host(
env, env,

View file

@ -93,6 +93,26 @@ fn write_state<'a, 'ctx, 'env>(
env.builder.build_store(offset_ptr, offset); 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>( pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,

View file

@ -20,8 +20,8 @@ use roc_target::TargetInfo;
use target_lexicon::Triple; use target_lexicon::Triple;
pub struct ExpectMemory<'a> { pub struct ExpectMemory<'a> {
ptr: *mut u8, pub ptr: *mut u8,
length: usize, pub length: usize,
shm_name: Option<std::ffi::CString>, shm_name: Option<std::ffi::CString>,
_marker: std::marker::PhantomData<&'a ()>, _marker: std::marker::PhantomData<&'a ()>,
} }
@ -53,6 +53,7 @@ impl<'a> ExpectMemory<'a> {
let ptr = unsafe { let ptr = unsafe {
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666); 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::ftruncate(shared_fd, Self::SHM_SIZE as _);
libc::mmap( libc::mmap(
@ -65,6 +66,9 @@ impl<'a> ExpectMemory<'a> {
) )
}; };
// puts in the initial header
let _ = ExpectSequence::new(ptr as *mut u8);
Self { Self {
ptr: ptr.cast(), ptr: ptr.cast(),
length: Self::SHM_SIZE, 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<GlobalInterner<'a, Layout<'a>>>,
lib: &libloading::Library,
expectations: &mut VecMap<ModuleId, Expectations>,
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)] #[allow(clippy::too_many_arguments)]
pub fn run_toplevel_expects<'a, W: std::io::Write>( pub fn run_toplevel_expects<'a, W: std::io::Write>(
writer: &mut W, writer: &mut W,

View file

@ -87,7 +87,9 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
const Unit = extern struct {}; 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 // 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 size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?; 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 nanos = timer.read();
const seconds = (@intToFloat(f64, nanos) / 1_000_000_000.0); 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; stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
return 0; return 0;