mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Merge pull request #3769 from roc-lang/expect-fx-codegen
expect fx codegen
This commit is contained in:
commit
c06e5cfa32
24 changed files with 773 additions and 191 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4009,6 +4009,7 @@ dependencies = [
|
||||||
"roc_std",
|
"roc_std",
|
||||||
"roc_target",
|
"roc_target",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
|
"signal-hook",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
|
@ -8,12 +8,10 @@ use roc_build::link::{LinkType, LinkingStrategy};
|
||||||
use roc_collections::VecMap;
|
use roc_collections::VecMap;
|
||||||
use roc_error_macros::{internal_error, user_error};
|
use roc_error_macros::{internal_error, user_error};
|
||||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||||
use roc_gen_llvm::run_roc::RocCallResult;
|
|
||||||
use roc_gen_llvm::run_roc_dylib;
|
|
||||||
use roc_load::{ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading};
|
use roc_load::{ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading};
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
use roc_repl_expect::run::{expect_mono_module_to_dylib, roc_dev_expect};
|
use roc_repl_expect::run::expect_mono_module_to_dylib;
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::{CString, OsStr};
|
use std::ffi::{CString, OsStr};
|
||||||
|
@ -320,8 +318,6 @@ pub enum FormatMode {
|
||||||
CheckOnly,
|
CheckOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHM_SIZE: i64 = 1024;
|
|
||||||
|
|
||||||
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
@ -407,13 +403,6 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||||||
|
|
||||||
let mut writer = std::io::stdout();
|
let mut writer = std::io::stdout();
|
||||||
|
|
||||||
let mut shared_buffer = vec![0u8; SHM_SIZE as usize];
|
|
||||||
|
|
||||||
let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ());
|
|
||||||
let mut result = RocCallResult::default();
|
|
||||||
let slice = (shared_buffer.as_mut_ptr(), shared_buffer.len());
|
|
||||||
unsafe { set_shared_buffer(slice, &mut result) };
|
|
||||||
|
|
||||||
let (failed, passed) = roc_repl_expect::run::run_expects(
|
let (failed, passed) = roc_repl_expect::run::run_expects(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||||
|
@ -421,7 +410,6 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||||||
interns,
|
interns,
|
||||||
&lib,
|
&lib,
|
||||||
&mut expectations,
|
&mut expectations,
|
||||||
shared_buffer.as_mut_ptr(),
|
|
||||||
expects,
|
expects,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -953,94 +941,13 @@ impl ExecutableFile {
|
||||||
// with Expect
|
// with Expect
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
unsafe fn roc_run_native_debug(
|
unsafe fn roc_run_native_debug(
|
||||||
executable: ExecutableFile,
|
_executable: ExecutableFile,
|
||||||
argv: &[*const c_char],
|
_argv: &[*const c_char],
|
||||||
envp: &[*const c_char],
|
_envp: &[*const c_char],
|
||||||
mut expectations: VecMap<ModuleId, Expectations>,
|
_expectations: VecMap<ModuleId, Expectations>,
|
||||||
interns: Interns,
|
_interns: Interns,
|
||||||
) {
|
) {
|
||||||
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
|
todo!()
|
||||||
|
|
||||||
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
|
|
||||||
|
|
||||||
match libc::fork() {
|
|
||||||
0 => {
|
|
||||||
// we are the child
|
|
||||||
|
|
||||||
if executable.execve(argv, envp) < 0 {
|
|
||||||
// Get the current value of errno
|
|
||||||
let e = errno::errno();
|
|
||||||
|
|
||||||
// Extract the error code as an i32
|
|
||||||
let code = e.0;
|
|
||||||
|
|
||||||
// Display a human-friendly error message
|
|
||||||
println!("💥 Error {}: {}", code, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1 => {
|
|
||||||
// something failed
|
|
||||||
|
|
||||||
// Get the current value of errno
|
|
||||||
let e = errno::errno();
|
|
||||||
|
|
||||||
// Extract the error code as an i32
|
|
||||||
let code = e.0;
|
|
||||||
|
|
||||||
// Display a human-friendly error message
|
|
||||||
println!("Error {}: {}", code, e);
|
|
||||||
|
|
||||||
process::exit(1)
|
|
||||||
}
|
|
||||||
1.. => {
|
|
||||||
let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes!
|
|
||||||
let cstring = CString::new(name).unwrap();
|
|
||||||
|
|
||||||
let arena = &bumpalo::Bump::new();
|
|
||||||
let interns = arena.alloc(interns);
|
|
||||||
|
|
||||||
for sig in &mut signals {
|
|
||||||
match sig {
|
|
||||||
SIGCHLD => {
|
|
||||||
// clean up
|
|
||||||
libc::shm_unlink(cstring.as_ptr().cast());
|
|
||||||
|
|
||||||
// done!
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
SIGUSR1 => {
|
|
||||||
// this is the signal we use for an expect failure. Let's see what the child told us
|
|
||||||
let shared_fd =
|
|
||||||
libc::shm_open(cstring.as_ptr().cast(), libc::O_RDONLY, 0o666);
|
|
||||||
|
|
||||||
libc::ftruncate(shared_fd, SHM_SIZE);
|
|
||||||
|
|
||||||
let shared_ptr = libc::mmap(
|
|
||||||
std::ptr::null_mut(),
|
|
||||||
SHM_SIZE as usize,
|
|
||||||
libc::PROT_READ,
|
|
||||||
libc::MAP_SHARED,
|
|
||||||
shared_fd,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
let shared_memory_ptr: *mut u8 = shared_ptr.cast();
|
|
||||||
|
|
||||||
roc_dev_expect(
|
|
||||||
&mut std::io::stdout(),
|
|
||||||
arena,
|
|
||||||
&mut expectations,
|
|
||||||
interns,
|
|
||||||
shared_memory_ptr,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
_ => println!("received signal {}", sig),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|
|
@ -463,6 +463,7 @@ fn stmt_spec<'a>(
|
||||||
builder.add_choice(block, &cases)
|
builder.add_choice(block, &cases)
|
||||||
}
|
}
|
||||||
Expect { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
Expect { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
||||||
|
ExpectFx { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
||||||
Ret(symbol) => Ok(env.symbols[symbol]),
|
Ret(symbol) => Ok(env.symbols[symbol]),
|
||||||
Refcounting(modify_rc, continuation) => match modify_rc {
|
Refcounting(modify_rc, continuation) => match modify_rc {
|
||||||
ModifyRc::Inc(symbol, _) => {
|
ModifyRc::Inc(symbol, _) => {
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
const SIGUSR1: c_int = 10;
|
const SIGUSR1: c_int = 10;
|
||||||
|
|
||||||
const O_RDWR: c_int = 2;
|
const O_RDWR: c_int = 2;
|
||||||
|
@ -26,9 +21,3 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expectFailedFinalize() callconv(.C) void {
|
|
||||||
const parent_pid = getppid();
|
|
||||||
|
|
||||||
_ = kill(parent_pid, SIGUSR1);
|
|
||||||
}
|
|
||||||
|
|
|
@ -168,7 +168,6 @@ 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 });
|
||||||
|
|
|
@ -613,6 +613,16 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
|
||||||
lookups_in_cond: lookups_in_cond.to_vec(),
|
lookups_in_cond: lookups_in_cond.to_vec(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition,
|
||||||
|
loc_continuation,
|
||||||
|
lookups_in_cond,
|
||||||
|
} => ExpectFx {
|
||||||
|
loc_condition: Box::new(loc_condition.map(|e| go_help!(e))),
|
||||||
|
loc_continuation: Box::new(loc_continuation.map(|e| go_help!(e))),
|
||||||
|
lookups_in_cond: lookups_in_cond.to_vec(),
|
||||||
|
},
|
||||||
|
|
||||||
TypedHole(v) => TypedHole(sub!(*v)),
|
TypedHole(v) => TypedHole(sub!(*v)),
|
||||||
|
|
||||||
RuntimeError(err) => RuntimeError(err.clone()),
|
RuntimeError(err) => RuntimeError(err.clone()),
|
||||||
|
|
|
@ -231,6 +231,13 @@ pub enum Expr {
|
||||||
lookups_in_cond: Vec<(Symbol, Variable)>,
|
lookups_in_cond: Vec<(Symbol, Variable)>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// not parsed, but is generated when lowering toplevel effectful expects
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition: Box<Loc<Expr>>,
|
||||||
|
loc_continuation: Box<Loc<Expr>>,
|
||||||
|
lookups_in_cond: Vec<(Symbol, Variable)>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Rendered as empty box in editor
|
/// Rendered as empty box in editor
|
||||||
TypedHole(Variable),
|
TypedHole(Variable),
|
||||||
|
|
||||||
|
@ -277,6 +284,7 @@ impl Expr {
|
||||||
Category::OpaqueWrap(opaque_name)
|
Category::OpaqueWrap(opaque_name)
|
||||||
}
|
}
|
||||||
Self::Expect { .. } => Category::Expect,
|
Self::Expect { .. } => Category::Expect,
|
||||||
|
Self::ExpectFx { .. } => Category::Expect,
|
||||||
|
|
||||||
// these nodes place no constraints on the expression's type
|
// these nodes place no constraints on the expression's type
|
||||||
Self::TypedHole(_) | Self::RuntimeError(..) => Category::Unknown,
|
Self::TypedHole(_) | Self::RuntimeError(..) => Category::Unknown,
|
||||||
|
@ -1772,6 +1780,28 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition,
|
||||||
|
loc_continuation,
|
||||||
|
lookups_in_cond,
|
||||||
|
} => {
|
||||||
|
let loc_condition = Loc {
|
||||||
|
region: loc_condition.region,
|
||||||
|
value: inline_calls(var_store, scope, loc_condition.value),
|
||||||
|
};
|
||||||
|
|
||||||
|
let loc_continuation = Loc {
|
||||||
|
region: loc_continuation.region,
|
||||||
|
value: inline_calls(var_store, scope, loc_continuation.value),
|
||||||
|
};
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition: Box::new(loc_condition),
|
||||||
|
loc_continuation: Box::new(loc_continuation),
|
||||||
|
lookups_in_cond,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LetRec(defs, loc_expr, mark) => {
|
LetRec(defs, loc_expr, mark) => {
|
||||||
let mut new_defs = Vec::with_capacity(defs.len());
|
let mut new_defs = Vec::with_capacity(defs.len());
|
||||||
|
|
||||||
|
@ -2327,7 +2357,7 @@ impl Declarations {
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let index = self.declarations.len();
|
let index = self.declarations.len();
|
||||||
|
|
||||||
self.declarations.push(DeclarationTag::Expectation);
|
self.declarations.push(DeclarationTag::ExpectationFx);
|
||||||
self.variables.push(Variable::BOOL);
|
self.variables.push(Variable::BOOL);
|
||||||
self.symbols.push(Loc::at(preceding_comment, name));
|
self.symbols.push(Loc::at(preceding_comment, name));
|
||||||
self.annotations.push(None);
|
self.annotations.push(None);
|
||||||
|
@ -2513,13 +2543,13 @@ impl Declarations {
|
||||||
}
|
}
|
||||||
Expectation => {
|
Expectation => {
|
||||||
let loc_expr =
|
let loc_expr =
|
||||||
toplevel_expect_to_inline_expect(self.expressions[index].clone());
|
toplevel_expect_to_inline_expect_pure(self.expressions[index].clone());
|
||||||
|
|
||||||
collector.visit_expr(&loc_expr.value, loc_expr.region, var);
|
collector.visit_expr(&loc_expr.value, loc_expr.region, var);
|
||||||
}
|
}
|
||||||
ExpectationFx => {
|
ExpectationFx => {
|
||||||
let loc_expr =
|
let loc_expr =
|
||||||
toplevel_expect_to_inline_expect(self.expressions[index].clone());
|
toplevel_expect_to_inline_expect_fx(self.expressions[index].clone());
|
||||||
|
|
||||||
collector.visit_expr(&loc_expr.value, loc_expr.region, var);
|
collector.visit_expr(&loc_expr.value, loc_expr.region, var);
|
||||||
}
|
}
|
||||||
|
@ -2658,6 +2688,9 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
|
||||||
}
|
}
|
||||||
Expr::Expect {
|
Expr::Expect {
|
||||||
loc_continuation, ..
|
loc_continuation, ..
|
||||||
|
}
|
||||||
|
| Expr::ExpectFx {
|
||||||
|
loc_continuation, ..
|
||||||
} => {
|
} => {
|
||||||
stack.push(&(*loc_continuation).value);
|
stack.push(&(*loc_continuation).value);
|
||||||
|
|
||||||
|
@ -2705,7 +2738,15 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
|
||||||
/// This is supposed to happen just before monomorphization:
|
/// This is supposed to happen just before monomorphization:
|
||||||
/// all type errors and such are generated from the user source,
|
/// all type errors and such are generated from the user source,
|
||||||
/// but this transformation means that we don't need special codegen for toplevel expects
|
/// but this transformation means that we don't need special codegen for toplevel expects
|
||||||
pub fn toplevel_expect_to_inline_expect(mut loc_expr: Loc<Expr>) -> Loc<Expr> {
|
pub fn toplevel_expect_to_inline_expect_pure(loc_expr: Loc<Expr>) -> Loc<Expr> {
|
||||||
|
toplevel_expect_to_inline_expect_help(loc_expr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toplevel_expect_to_inline_expect_fx(loc_expr: Loc<Expr>) -> Loc<Expr> {
|
||||||
|
toplevel_expect_to_inline_expect_help(loc_expr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toplevel_expect_to_inline_expect_help(mut loc_expr: Loc<Expr>, has_effects: bool) -> Loc<Expr> {
|
||||||
enum StoredDef {
|
enum StoredDef {
|
||||||
NonRecursive(Region, Box<Def>),
|
NonRecursive(Region, Box<Def>),
|
||||||
Recursive(Region, Vec<Def>, IllegalCycleMark),
|
Recursive(Region, Vec<Def>, IllegalCycleMark),
|
||||||
|
@ -2735,10 +2776,18 @@ pub fn toplevel_expect_to_inline_expect(mut loc_expr: Loc<Expr>) -> Loc<Expr> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let expect_region = loc_expr.region;
|
let expect_region = loc_expr.region;
|
||||||
let expect = Expr::Expect {
|
let expect = if has_effects {
|
||||||
|
Expr::ExpectFx {
|
||||||
loc_condition: Box::new(loc_expr),
|
loc_condition: Box::new(loc_expr),
|
||||||
loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)),
|
loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)),
|
||||||
lookups_in_cond,
|
lookups_in_cond,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Expr::Expect {
|
||||||
|
loc_condition: Box::new(loc_expr),
|
||||||
|
loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)),
|
||||||
|
lookups_in_cond,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut loc_expr = Loc::at(expect_region, expect);
|
let mut loc_expr = Loc::at(expect_region, expect);
|
||||||
|
@ -2764,15 +2813,22 @@ struct ExpectCollector {
|
||||||
|
|
||||||
impl crate::traverse::Visitor for ExpectCollector {
|
impl crate::traverse::Visitor for ExpectCollector {
|
||||||
fn visit_expr(&mut self, expr: &Expr, _region: Region, var: Variable) {
|
fn visit_expr(&mut self, expr: &Expr, _region: Region, var: Variable) {
|
||||||
if let Expr::Expect {
|
match expr {
|
||||||
|
Expr::Expect {
|
||||||
lookups_in_cond,
|
lookups_in_cond,
|
||||||
loc_condition,
|
loc_condition,
|
||||||
..
|
..
|
||||||
} = expr
|
}
|
||||||
{
|
| Expr::ExpectFx {
|
||||||
|
lookups_in_cond,
|
||||||
|
loc_condition,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
self.expects
|
self.expects
|
||||||
.insert(loc_condition.region, lookups_in_cond.to_vec());
|
.insert(loc_condition.region, lookups_in_cond.to_vec());
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
walk_expr(self, expr, var)
|
walk_expr(self, expr, var)
|
||||||
}
|
}
|
||||||
|
|
|
@ -964,6 +964,23 @@ fn fix_values_captured_in_closure_expr(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition,
|
||||||
|
loc_continuation,
|
||||||
|
lookups_in_cond: _,
|
||||||
|
} => {
|
||||||
|
fix_values_captured_in_closure_expr(
|
||||||
|
&mut loc_condition.value,
|
||||||
|
no_capture_symbols,
|
||||||
|
closure_captures,
|
||||||
|
);
|
||||||
|
fix_values_captured_in_closure_expr(
|
||||||
|
&mut loc_continuation.value,
|
||||||
|
no_capture_symbols,
|
||||||
|
closure_captures,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Closure(ClosureData {
|
Closure(ClosureData {
|
||||||
captured_symbols,
|
captured_symbols,
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -276,6 +276,19 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
|
||||||
Variable::NULL,
|
Variable::NULL,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Expr::ExpectFx {
|
||||||
|
loc_condition,
|
||||||
|
loc_continuation,
|
||||||
|
lookups_in_cond: _,
|
||||||
|
} => {
|
||||||
|
// TODO: what type does an expect have? bool
|
||||||
|
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::NULL);
|
||||||
|
visitor.visit_expr(
|
||||||
|
&loc_continuation.value,
|
||||||
|
loc_continuation.region,
|
||||||
|
Variable::NULL,
|
||||||
|
);
|
||||||
|
}
|
||||||
Expr::TypedHole(_) => { /* terminal */ }
|
Expr::TypedHole(_) => { /* terminal */ }
|
||||||
Expr::RuntimeError(..) => { /* terminal */ }
|
Expr::RuntimeError(..) => { /* terminal */ }
|
||||||
}
|
}
|
||||||
|
|
|
@ -520,6 +520,53 @@ pub fn constrain_expr(
|
||||||
constraints.exists_many(vars, all_constraints)
|
constraints.exists_many(vars, all_constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition,
|
||||||
|
loc_continuation,
|
||||||
|
lookups_in_cond,
|
||||||
|
} => {
|
||||||
|
let expect_bool = |region| {
|
||||||
|
let bool_type = Type::Variable(Variable::BOOL);
|
||||||
|
Expected::ForReason(Reason::ExpectCondition, bool_type, region)
|
||||||
|
};
|
||||||
|
|
||||||
|
let cond_con = constrain_expr(
|
||||||
|
constraints,
|
||||||
|
env,
|
||||||
|
loc_condition.region,
|
||||||
|
&loc_condition.value,
|
||||||
|
expect_bool(loc_condition.region),
|
||||||
|
);
|
||||||
|
|
||||||
|
let continuation_con = constrain_expr(
|
||||||
|
constraints,
|
||||||
|
env,
|
||||||
|
loc_continuation.region,
|
||||||
|
&loc_continuation.value,
|
||||||
|
expected,
|
||||||
|
);
|
||||||
|
|
||||||
|
// + 2 for cond_con and continuation_con
|
||||||
|
let mut all_constraints = Vec::with_capacity(lookups_in_cond.len() + 2);
|
||||||
|
|
||||||
|
all_constraints.push(cond_con);
|
||||||
|
all_constraints.push(continuation_con);
|
||||||
|
|
||||||
|
let mut vars = Vec::with_capacity(lookups_in_cond.len());
|
||||||
|
|
||||||
|
for (symbol, var) in lookups_in_cond.iter() {
|
||||||
|
vars.push(*var);
|
||||||
|
|
||||||
|
all_constraints.push(constraints.lookup(
|
||||||
|
*symbol,
|
||||||
|
NoExpectation(Type::Variable(*var)),
|
||||||
|
Region::zero(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
constraints.exists_many(vars, all_constraints)
|
||||||
|
}
|
||||||
|
|
||||||
If {
|
If {
|
||||||
cond_var,
|
cond_var,
|
||||||
branch_var,
|
branch_var,
|
||||||
|
|
|
@ -1004,7 +1004,8 @@ trait Backend<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
|
Stmt::Expect { .. } => todo!("expect is not implemented in the dev backend"),
|
||||||
|
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the dev backend"),
|
||||||
|
|
||||||
Stmt::RuntimeError(_) => {}
|
Stmt::RuntimeError(_) => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,10 +199,6 @@ impl LlvmBackendMode {
|
||||||
LlvmBackendMode::CliTest => true,
|
LlvmBackendMode::CliTest => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runs_expects_in_separate_process(self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Env<'a, 'ctx, 'env> {
|
pub struct Env<'a, 'ctx, 'env> {
|
||||||
|
@ -2802,15 +2798,67 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
lookups,
|
lookups,
|
||||||
);
|
);
|
||||||
|
|
||||||
// NOTE: signals to the parent process that an expect failed
|
bd.build_unconditional_branch(then_block);
|
||||||
if env.mode.runs_expects_in_separate_process() {
|
|
||||||
let func = env
|
|
||||||
.module
|
|
||||||
.get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
bd.build_call(func, &[], "call_expect_finalize_failed");
|
|
||||||
}
|
}
|
||||||
|
roc_target::PtrWidth::Bytes4 => {
|
||||||
|
// temporary WASM implementation
|
||||||
|
throw_exception(env, "An expectation failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bd.position_at_end(throw_block);
|
||||||
|
bd.build_unconditional_branch(then_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
bd.position_at_end(then_block);
|
||||||
|
|
||||||
|
build_exp_stmt(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
func_spec_solutions,
|
||||||
|
scope,
|
||||||
|
parent,
|
||||||
|
remainder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition: cond_symbol,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts: _,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
let bd = env.builder;
|
||||||
|
let context = env.context;
|
||||||
|
|
||||||
|
let (cond, _cond_layout) = load_symbol_and_layout(scope, cond_symbol);
|
||||||
|
|
||||||
|
let condition = bd.build_int_compare(
|
||||||
|
IntPredicate::EQ,
|
||||||
|
cond.into_int_value(),
|
||||||
|
context.bool_type().const_int(1, false),
|
||||||
|
"is_true",
|
||||||
|
);
|
||||||
|
|
||||||
|
let then_block = context.append_basic_block(parent, "then_block");
|
||||||
|
let throw_block = context.append_basic_block(parent, "throw_block");
|
||||||
|
|
||||||
|
bd.build_conditional_branch(condition, then_block, throw_block);
|
||||||
|
|
||||||
|
if env.mode.runs_expects() {
|
||||||
|
bd.position_at_end(throw_block);
|
||||||
|
|
||||||
|
match env.target_info.ptr_width() {
|
||||||
|
roc_target::PtrWidth::Bytes8 => {
|
||||||
|
clone_to_shared_memory(
|
||||||
|
env,
|
||||||
|
scope,
|
||||||
|
layout_ids,
|
||||||
|
*cond_symbol,
|
||||||
|
*region,
|
||||||
|
lookups,
|
||||||
|
);
|
||||||
|
|
||||||
bd.build_unconditional_branch(then_block);
|
bd.build_unconditional_branch(then_block);
|
||||||
}
|
}
|
||||||
|
|
|
@ -708,6 +708,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following),
|
Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following),
|
||||||
|
|
||||||
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
|
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
|
||||||
|
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the wasm backend"),
|
||||||
|
|
||||||
Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg),
|
Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg),
|
||||||
}
|
}
|
||||||
|
|
|
@ -707,6 +707,12 @@ struct LateSpecializationsModule<'a> {
|
||||||
procs_base: ProcsBase<'a>,
|
procs_base: ProcsBase<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ToplevelExpects {
|
||||||
|
pub pure: VecMap<Symbol, Region>,
|
||||||
|
pub fx: VecMap<Symbol, Region>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MonomorphizedModule<'a> {
|
pub struct MonomorphizedModule<'a> {
|
||||||
pub module_id: ModuleId,
|
pub module_id: ModuleId,
|
||||||
|
@ -716,7 +722,7 @@ pub struct MonomorphizedModule<'a> {
|
||||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||||
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
||||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
pub toplevel_expects: VecMap<Symbol, Region>,
|
pub toplevel_expects: ToplevelExpects,
|
||||||
pub entry_point: EntryPoint<'a>,
|
pub entry_point: EntryPoint<'a>,
|
||||||
pub exposed_to_host: ExposedToHost,
|
pub exposed_to_host: ExposedToHost,
|
||||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||||
|
@ -821,7 +827,7 @@ enum Msg<'a> {
|
||||||
solved_subs: Solved<Subs>,
|
solved_subs: Solved<Subs>,
|
||||||
module_timing: ModuleTiming,
|
module_timing: ModuleTiming,
|
||||||
abilities_store: AbilitiesStore,
|
abilities_store: AbilitiesStore,
|
||||||
toplevel_expects: VecMap<Symbol, Region>,
|
toplevel_expects: ToplevelExpects,
|
||||||
},
|
},
|
||||||
MadeSpecializations {
|
MadeSpecializations {
|
||||||
module_id: ModuleId,
|
module_id: ModuleId,
|
||||||
|
@ -908,7 +914,7 @@ struct State<'a> {
|
||||||
pub module_cache: ModuleCache<'a>,
|
pub module_cache: ModuleCache<'a>,
|
||||||
pub dependencies: Dependencies<'a>,
|
pub dependencies: Dependencies<'a>,
|
||||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
pub toplevel_expects: VecMap<Symbol, Region>,
|
pub toplevel_expects: ToplevelExpects,
|
||||||
pub exposed_to_host: ExposedToHost,
|
pub exposed_to_host: ExposedToHost,
|
||||||
|
|
||||||
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
|
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
|
||||||
|
@ -981,7 +987,7 @@ impl<'a> State<'a> {
|
||||||
module_cache: ModuleCache::default(),
|
module_cache: ModuleCache::default(),
|
||||||
dependencies,
|
dependencies,
|
||||||
procedures: MutMap::default(),
|
procedures: MutMap::default(),
|
||||||
toplevel_expects: VecMap::default(),
|
toplevel_expects: ToplevelExpects::default(),
|
||||||
exposed_to_host: ExposedToHost::default(),
|
exposed_to_host: ExposedToHost::default(),
|
||||||
exposed_types,
|
exposed_types,
|
||||||
arc_modules,
|
arc_modules,
|
||||||
|
@ -2527,7 +2533,8 @@ fn update<'a>(
|
||||||
|
|
||||||
let subs = solved_subs.into_inner();
|
let subs = solved_subs.into_inner();
|
||||||
|
|
||||||
state.toplevel_expects.extend(toplevel_expects);
|
state.toplevel_expects.pure.extend(toplevel_expects.pure);
|
||||||
|
state.toplevel_expects.fx.extend(toplevel_expects.fx);
|
||||||
|
|
||||||
state
|
state
|
||||||
.module_cache
|
.module_cache
|
||||||
|
@ -4818,7 +4825,7 @@ fn build_pending_specializations<'a>(
|
||||||
let find_specializations_start = Instant::now();
|
let find_specializations_start = Instant::now();
|
||||||
|
|
||||||
let mut module_thunks = bumpalo::collections::Vec::new_in(arena);
|
let mut module_thunks = bumpalo::collections::Vec::new_in(arena);
|
||||||
let mut toplevel_expects = VecMap::default();
|
let mut toplevel_expects = ToplevelExpects::default();
|
||||||
|
|
||||||
let mut procs_base = ProcsBase {
|
let mut procs_base = ProcsBase {
|
||||||
partial_procs: BumpMap::default(),
|
partial_procs: BumpMap::default(),
|
||||||
|
@ -5111,7 +5118,7 @@ fn build_pending_specializations<'a>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = roc_can::expr::toplevel_expect_to_inline_expect(body);
|
let body = roc_can::expr::toplevel_expect_to_inline_expect_pure(body);
|
||||||
|
|
||||||
let proc = PartialProc {
|
let proc = PartialProc {
|
||||||
annotation: expr_var,
|
annotation: expr_var,
|
||||||
|
@ -5131,7 +5138,7 @@ fn build_pending_specializations<'a>(
|
||||||
let expr_region = declarations.expressions[index].region;
|
let expr_region = declarations.expressions[index].region;
|
||||||
let region = Region::span_across(&name_region, &expr_region);
|
let region = Region::span_across(&name_region, &expr_region);
|
||||||
|
|
||||||
toplevel_expects.insert(symbol, region);
|
toplevel_expects.pure.insert(symbol, region);
|
||||||
procs_base.partial_procs.insert(symbol, proc);
|
procs_base.partial_procs.insert(symbol, proc);
|
||||||
}
|
}
|
||||||
ExpectationFx => {
|
ExpectationFx => {
|
||||||
|
@ -5187,7 +5194,7 @@ fn build_pending_specializations<'a>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = roc_can::expr::toplevel_expect_to_inline_expect(body);
|
let body = roc_can::expr::toplevel_expect_to_inline_expect_fx(body);
|
||||||
|
|
||||||
let proc = PartialProc {
|
let proc = PartialProc {
|
||||||
annotation: expr_var,
|
annotation: expr_var,
|
||||||
|
@ -5207,7 +5214,7 @@ fn build_pending_specializations<'a>(
|
||||||
let expr_region = declarations.expressions[index].region;
|
let expr_region = declarations.expressions[index].region;
|
||||||
let region = Region::span_across(&name_region, &expr_region);
|
let region = Region::span_across(&name_region, &expr_region);
|
||||||
|
|
||||||
toplevel_expects.insert(symbol, region);
|
toplevel_expects.fx.insert(symbol, region);
|
||||||
procs_base.partial_procs.insert(symbol, proc);
|
procs_base.partial_procs.insert(symbol, proc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,6 +309,7 @@ impl<'a> ParamMap<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect { remainder, .. } => stack.push(remainder),
|
Expect { remainder, .. } => stack.push(remainder),
|
||||||
|
ExpectFx { remainder, .. } => stack.push(remainder),
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
branches,
|
branches,
|
||||||
|
@ -820,6 +821,10 @@ impl<'a> BorrowInfState<'a> {
|
||||||
self.collect_stmt(param_map, remainder);
|
self.collect_stmt(param_map, remainder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx { remainder, .. } => {
|
||||||
|
self.collect_stmt(param_map, remainder);
|
||||||
|
}
|
||||||
|
|
||||||
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
|
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
|
||||||
|
|
||||||
Ret(_) | RuntimeError(_) => {
|
Ret(_) | RuntimeError(_) => {
|
||||||
|
@ -988,6 +993,7 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>)
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect { remainder, .. } => stack.push(remainder),
|
Expect { remainder, .. } => stack.push(remainder),
|
||||||
|
ExpectFx { remainder, .. } => stack.push(remainder),
|
||||||
|
|
||||||
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
|
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,17 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>)
|
||||||
stack.push(remainder);
|
stack.push(remainder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
remainder,
|
||||||
|
lookups,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
result.insert(*condition);
|
||||||
|
result.extend(lookups.iter().copied());
|
||||||
|
stack.push(remainder);
|
||||||
|
}
|
||||||
|
|
||||||
Jump(_, arguments) => {
|
Jump(_, arguments) => {
|
||||||
result.extend(arguments.iter().copied());
|
result.extend(arguments.iter().copied());
|
||||||
}
|
}
|
||||||
|
@ -671,8 +682,8 @@ impl<'a> Context<'a> {
|
||||||
|
|
||||||
match ownership {
|
match ownership {
|
||||||
DataBorrowedFunctionOwns => {
|
DataBorrowedFunctionOwns => {
|
||||||
// the data is borrowed;
|
// the data is borrowed; increment it to own the values so the function
|
||||||
// increment it to own the values so the function can use them
|
// can use them
|
||||||
let rc = Stmt::Refcounting(ModifyRc::Inc(argument, 1), stmt);
|
let rc = Stmt::Refcounting(ModifyRc::Inc(argument, 1), stmt);
|
||||||
|
|
||||||
stmt = self.arena.alloc(rc);
|
stmt = self.arena.alloc(rc);
|
||||||
|
@ -690,8 +701,8 @@ impl<'a> Context<'a> {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// incrementing/consuming the closure (if needed) is done by the zig implementation.
|
// incrementing/consuming the closure (if needed) is done by the zig implementation. We
|
||||||
// We don't want to touch the RC on the roc side, so treat these as borrowed.
|
// don't want to touch the RC on the roc side, so treat these as borrowed.
|
||||||
const FUNCTION: bool = BORROWED;
|
const FUNCTION: bool = BORROWED;
|
||||||
const CLOSURE_DATA: bool = BORROWED;
|
const CLOSURE_DATA: bool = BORROWED;
|
||||||
|
|
||||||
|
@ -753,9 +764,9 @@ impl<'a> Context<'a> {
|
||||||
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
|
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
|
||||||
}
|
}
|
||||||
ListSortWith { xs } => {
|
ListSortWith { xs } => {
|
||||||
// NOTE: we may apply the function to the same argument multiple times.
|
// NOTE: we may apply the function to the same argument multiple times. for that to
|
||||||
// for that to be valid, the function must borrow its argument. This is not
|
// be valid, the function must borrow its argument. This is not enforced at the
|
||||||
// enforced at the moment
|
// moment
|
||||||
//
|
//
|
||||||
// we also don't check that both arguments have the same ownership characteristics
|
// we also don't check that both arguments have the same ownership characteristics
|
||||||
let ownerships = [(xs, function_ps[0])];
|
let ownerships = [(xs, function_ps[0])];
|
||||||
|
@ -768,7 +779,8 @@ impl<'a> Context<'a> {
|
||||||
|
|
||||||
match ownership {
|
match ownership {
|
||||||
DataOwnedFunctionOwns => {
|
DataOwnedFunctionOwns => {
|
||||||
// if non-unique, elements have been consumed, must still consume the list itself
|
// if non-unique, elements have been consumed, must still consume the
|
||||||
|
// list itself
|
||||||
let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), b);
|
let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), b);
|
||||||
|
|
||||||
let condition_stmt = branch_on_list_uniqueness(
|
let condition_stmt = branch_on_list_uniqueness(
|
||||||
|
@ -914,8 +926,7 @@ impl<'a> Context<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
|
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
|
||||||
// EmptyArray is always stack-allocated
|
// EmptyArray is always stack-allocated function pointers are persistent
|
||||||
// function pointers are persistent
|
|
||||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -924,8 +935,7 @@ impl<'a> Context<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
|
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
|
||||||
// is this value a constant?
|
// is this value a constant? TODO do function pointers also fall into this category?
|
||||||
// TODO do function pointers also fall into this category?
|
|
||||||
let persistent = false;
|
let persistent = false;
|
||||||
|
|
||||||
// must this value be consumed?
|
// must this value be consumed?
|
||||||
|
@ -979,9 +989,7 @@ impl<'a> Context<'a> {
|
||||||
|
|
||||||
// Add `dec` instructions for parameters that are
|
// Add `dec` instructions for parameters that are
|
||||||
//
|
//
|
||||||
// - references
|
// - references - not alive in `b` - not borrow.
|
||||||
// - not alive in `b`
|
|
||||||
// - not borrow.
|
|
||||||
//
|
//
|
||||||
// That is, we must make sure these parameters are consumed.
|
// That is, we must make sure these parameters are consumed.
|
||||||
fn add_dec_for_dead_params(
|
fn add_dec_for_dead_params(
|
||||||
|
@ -1021,9 +1029,9 @@ impl<'a> Context<'a> {
|
||||||
) -> (&'a Stmt<'a>, LiveVarSet) {
|
) -> (&'a Stmt<'a>, LiveVarSet) {
|
||||||
use Stmt::*;
|
use Stmt::*;
|
||||||
|
|
||||||
// let-chains can be very long, especially for large (list) literals
|
// let-chains can be very long, especially for large (list) literals in (rust) debug mode,
|
||||||
// in (rust) debug mode, this function can overflow the stack for such values
|
// this function can overflow the stack for such values so we have to write an explicit
|
||||||
// so we have to write an explicit loop.
|
// loop.
|
||||||
{
|
{
|
||||||
let mut cont = stmt;
|
let mut cont = stmt;
|
||||||
let mut triples = Vec::new_in(self.arena);
|
let mut triples = Vec::new_in(self.arena);
|
||||||
|
@ -1199,6 +1207,30 @@ impl<'a> Context<'a> {
|
||||||
(expect, b_live_vars)
|
(expect, b_live_vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
remainder,
|
||||||
|
condition,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
} => {
|
||||||
|
let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder);
|
||||||
|
|
||||||
|
let expect = self.arena.alloc(Stmt::ExpectFx {
|
||||||
|
condition: *condition,
|
||||||
|
region: *region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder: b,
|
||||||
|
});
|
||||||
|
|
||||||
|
let expect = self.add_inc_before_consume_all(lookups, expect, &b_live_vars);
|
||||||
|
|
||||||
|
b_live_vars.extend(lookups.iter().copied());
|
||||||
|
|
||||||
|
(expect, b_live_vars)
|
||||||
|
}
|
||||||
|
|
||||||
RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()),
|
RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1313,6 +1345,17 @@ pub fn collect_stmt(
|
||||||
collect_stmt(remainder, jp_live_vars, vars)
|
collect_stmt(remainder, jp_live_vars, vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
remainder,
|
||||||
|
lookups,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
vars.insert(*condition);
|
||||||
|
vars.extend(lookups.iter().copied());
|
||||||
|
collect_stmt(remainder, jp_live_vars, vars)
|
||||||
|
}
|
||||||
|
|
||||||
Join {
|
Join {
|
||||||
id: j,
|
id: j,
|
||||||
parameters,
|
parameters,
|
||||||
|
|
|
@ -1513,6 +1513,14 @@ pub enum Stmt<'a> {
|
||||||
/// what happens after the expect
|
/// what happens after the expect
|
||||||
remainder: &'a Stmt<'a>,
|
remainder: &'a Stmt<'a>,
|
||||||
},
|
},
|
||||||
|
ExpectFx {
|
||||||
|
condition: Symbol,
|
||||||
|
region: Region,
|
||||||
|
lookups: &'a [Symbol],
|
||||||
|
layouts: &'a [Layout<'a>],
|
||||||
|
/// what happens after the expect
|
||||||
|
remainder: &'a Stmt<'a>,
|
||||||
|
},
|
||||||
/// a join point `join f <params> = <continuation> in remainder`
|
/// a join point `join f <params> = <continuation> in remainder`
|
||||||
Join {
|
Join {
|
||||||
id: JoinPointId,
|
id: JoinPointId,
|
||||||
|
@ -2100,6 +2108,17 @@ impl<'a> Stmt<'a> {
|
||||||
.append(alloc.hardline())
|
.append(alloc.hardline())
|
||||||
.append(remainder.to_doc(alloc)),
|
.append(remainder.to_doc(alloc)),
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
remainder,
|
||||||
|
..
|
||||||
|
} => alloc
|
||||||
|
.text("expect-fx ")
|
||||||
|
.append(symbol_to_doc(alloc, *condition))
|
||||||
|
.append(";")
|
||||||
|
.append(alloc.hardline())
|
||||||
|
.append(remainder.to_doc(alloc)),
|
||||||
|
|
||||||
Ret(symbol) => alloc
|
Ret(symbol) => alloc
|
||||||
.text("ret ")
|
.text("ret ")
|
||||||
.append(symbol_to_doc(alloc, *symbol))
|
.append(symbol_to_doc(alloc, *symbol))
|
||||||
|
@ -4163,6 +4182,7 @@ pub fn with_hole<'a>(
|
||||||
EmptyRecord => let_empty_struct(assigned, hole),
|
EmptyRecord => let_empty_struct(assigned, hole),
|
||||||
|
|
||||||
Expect { .. } => unreachable!("I think this is unreachable"),
|
Expect { .. } => unreachable!("I think this is unreachable"),
|
||||||
|
ExpectFx { .. } => unreachable!("I think this is unreachable"),
|
||||||
|
|
||||||
If {
|
If {
|
||||||
cond_var,
|
cond_var,
|
||||||
|
@ -6133,6 +6153,45 @@ pub fn from_can<'a>(
|
||||||
stmt
|
stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
loc_condition,
|
||||||
|
loc_continuation,
|
||||||
|
lookups_in_cond,
|
||||||
|
} => {
|
||||||
|
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
|
||||||
|
let cond_symbol = env.unique_symbol();
|
||||||
|
|
||||||
|
let lookups = Vec::from_iter_in(lookups_in_cond.iter().map(|t| t.0), env.arena);
|
||||||
|
|
||||||
|
let mut layouts = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
|
||||||
|
|
||||||
|
for (_, var) in lookups_in_cond {
|
||||||
|
let res_layout = layout_cache.from_var(env.arena, var, env.subs);
|
||||||
|
let layout = return_on_layout_error!(env, res_layout, "Expect");
|
||||||
|
layouts.push(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stmt = Stmt::ExpectFx {
|
||||||
|
condition: cond_symbol,
|
||||||
|
region: loc_condition.region,
|
||||||
|
lookups: lookups.into_bump_slice(),
|
||||||
|
layouts: layouts.into_bump_slice(),
|
||||||
|
remainder: env.arena.alloc(rest),
|
||||||
|
};
|
||||||
|
|
||||||
|
stmt = with_hole(
|
||||||
|
env,
|
||||||
|
loc_condition.value,
|
||||||
|
variable,
|
||||||
|
procs,
|
||||||
|
layout_cache,
|
||||||
|
cond_symbol,
|
||||||
|
env.arena.alloc(stmt),
|
||||||
|
);
|
||||||
|
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
|
||||||
LetRec(defs, cont, _cycle_mark) => {
|
LetRec(defs, cont, _cycle_mark) => {
|
||||||
// because Roc is strict, only functions can be recursive!
|
// because Roc is strict, only functions can be recursive!
|
||||||
for def in defs.into_iter() {
|
for def in defs.into_iter() {
|
||||||
|
@ -6497,6 +6556,32 @@ fn substitute_in_stmt_help<'a>(
|
||||||
Some(arena.alloc(expect))
|
Some(arena.alloc(expect))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
let new_remainder =
|
||||||
|
substitute_in_stmt_help(arena, remainder, subs).unwrap_or(remainder);
|
||||||
|
|
||||||
|
let new_lookups = Vec::from_iter_in(
|
||||||
|
lookups.iter().map(|s| substitute(subs, *s).unwrap_or(*s)),
|
||||||
|
arena,
|
||||||
|
);
|
||||||
|
|
||||||
|
let expect = ExpectFx {
|
||||||
|
condition: substitute(subs, *condition).unwrap_or(*condition),
|
||||||
|
region: *region,
|
||||||
|
lookups: new_lookups.into_bump_slice(),
|
||||||
|
layouts,
|
||||||
|
remainder: new_remainder,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(arena.alloc(expect))
|
||||||
|
}
|
||||||
|
|
||||||
Jump(id, args) => {
|
Jump(id, args) => {
|
||||||
let mut did_change = false;
|
let mut did_change = false;
|
||||||
let new_args = Vec::from_iter_in(
|
let new_args = Vec::from_iter_in(
|
||||||
|
|
|
@ -216,6 +216,31 @@ fn function_s<'a, 'i>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
let continuation: &Stmt = *remainder;
|
||||||
|
let new_continuation = function_s(env, w, c, continuation);
|
||||||
|
|
||||||
|
if std::ptr::eq(continuation, new_continuation) || continuation == new_continuation {
|
||||||
|
stmt
|
||||||
|
} else {
|
||||||
|
let new_refcounting = ExpectFx {
|
||||||
|
condition: *condition,
|
||||||
|
region: *region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder: new_continuation,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(new_refcounting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
|
Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,6 +471,39 @@ fn function_d_main<'a, 'i>(
|
||||||
(arena.alloc(refcounting), found)
|
(arena.alloc(refcounting), found)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
let (b, found) = function_d_main(env, x, c, remainder);
|
||||||
|
|
||||||
|
if found || *condition != x {
|
||||||
|
let refcounting = ExpectFx {
|
||||||
|
condition: *condition,
|
||||||
|
region: *region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder: b,
|
||||||
|
};
|
||||||
|
|
||||||
|
(arena.alloc(refcounting), found)
|
||||||
|
} else {
|
||||||
|
let b = try_function_s(env, x, c, b);
|
||||||
|
|
||||||
|
let refcounting = ExpectFx {
|
||||||
|
condition: *condition,
|
||||||
|
region: *region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder: b,
|
||||||
|
};
|
||||||
|
|
||||||
|
(arena.alloc(refcounting), found)
|
||||||
|
}
|
||||||
|
}
|
||||||
Join {
|
Join {
|
||||||
id,
|
id,
|
||||||
parameters,
|
parameters,
|
||||||
|
@ -618,6 +676,26 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
|
||||||
arena.alloc(expect)
|
arena.alloc(expect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
let b = function_r(env, remainder);
|
||||||
|
|
||||||
|
let expect = ExpectFx {
|
||||||
|
condition: *condition,
|
||||||
|
region: *region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder: b,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(expect)
|
||||||
|
}
|
||||||
|
|
||||||
Ret(_) | Jump(_, _) | RuntimeError(_) => {
|
Ret(_) | Jump(_, _) | RuntimeError(_) => {
|
||||||
// terminals
|
// terminals
|
||||||
stmt
|
stmt
|
||||||
|
@ -653,6 +731,11 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
|
||||||
remainder,
|
remainder,
|
||||||
..
|
..
|
||||||
} => *condition == needle || has_live_var(jp_live_vars, remainder, needle),
|
} => *condition == needle || has_live_var(jp_live_vars, remainder, needle),
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
remainder,
|
||||||
|
..
|
||||||
|
} => *condition == needle || has_live_var(jp_live_vars, remainder, needle),
|
||||||
Join {
|
Join {
|
||||||
id,
|
id,
|
||||||
parameters,
|
parameters,
|
||||||
|
|
|
@ -273,6 +273,30 @@ fn insert_jumps<'a>(
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ExpectFx {
|
||||||
|
condition,
|
||||||
|
region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder,
|
||||||
|
} => match insert_jumps(
|
||||||
|
arena,
|
||||||
|
remainder,
|
||||||
|
goal_id,
|
||||||
|
needle,
|
||||||
|
needle_arguments,
|
||||||
|
needle_result,
|
||||||
|
) {
|
||||||
|
Some(cont) => Some(arena.alloc(ExpectFx {
|
||||||
|
condition: *condition,
|
||||||
|
region: *region,
|
||||||
|
lookups,
|
||||||
|
layouts,
|
||||||
|
remainder: cont,
|
||||||
|
})),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
|
||||||
Ret(_) => None,
|
Ret(_) => None,
|
||||||
Jump(_, _) => None,
|
Jump(_, _) => None,
|
||||||
RuntimeError(_) => None,
|
RuntimeError(_) => None,
|
||||||
|
|
|
@ -636,7 +636,7 @@ fn parse_defs_end<'a>(
|
||||||
let parse_expect_vanilla =
|
let parse_expect_vanilla =
|
||||||
crate::parser::keyword_e(crate::keyword::EXPECT, EExpect::Expect);
|
crate::parser::keyword_e(crate::keyword::EXPECT, EExpect::Expect);
|
||||||
let parse_expect_fx = crate::parser::keyword_e(crate::keyword::EXPECT_FX, EExpect::Expect);
|
let parse_expect_fx = crate::parser::keyword_e(crate::keyword::EXPECT_FX, EExpect::Expect);
|
||||||
let parse_expect = either!(parse_expect_vanilla, parse_expect_fx);
|
let parse_expect = either!(parse_expect_fx, parse_expect_vanilla);
|
||||||
|
|
||||||
match space0_after_e(
|
match space0_after_e(
|
||||||
crate::pattern::loc_pattern_help(min_indent),
|
crate::pattern::loc_pattern_help(min_indent),
|
||||||
|
@ -682,11 +682,11 @@ fn parse_defs_end<'a>(
|
||||||
let preceding_comment = Region::new(spaces_before_current_start, start);
|
let preceding_comment = Region::new(spaces_before_current_start, start);
|
||||||
|
|
||||||
let value_def = match expect_flavor {
|
let value_def = match expect_flavor {
|
||||||
Either::First(_) => ValueDef::Expect {
|
Either::Second(_) => ValueDef::Expect {
|
||||||
condition: arena.alloc(loc_def_expr),
|
condition: arena.alloc(loc_def_expr),
|
||||||
preceding_comment,
|
preceding_comment,
|
||||||
},
|
},
|
||||||
Either::Second(_) => ValueDef::ExpectFx {
|
Either::First(_) => ValueDef::ExpectFx {
|
||||||
condition: arena.alloc(loc_def_expr),
|
condition: arena.alloc(loc_def_expr),
|
||||||
preceding_comment,
|
preceding_comment,
|
||||||
},
|
},
|
||||||
|
|
|
@ -257,6 +257,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||||
ZeroArgumentTag { .. } => todo!(),
|
ZeroArgumentTag { .. } => todo!(),
|
||||||
OpaqueRef { .. } => todo!(),
|
OpaqueRef { .. } => todo!(),
|
||||||
Expect { .. } => todo!(),
|
Expect { .. } => todo!(),
|
||||||
|
ExpectFx { .. } => todo!(),
|
||||||
TypedHole(_) => todo!(),
|
TypedHole(_) => todo!(),
|
||||||
RuntimeError(_) => todo!(),
|
RuntimeError(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,11 @@ roc_build = { path = "../compiler/build" }
|
||||||
|
|
||||||
libloading = "0.7.1"
|
libloading = "0.7.1"
|
||||||
inkwell = { path = "../vendor/inkwell" }
|
inkwell = { path = "../vendor/inkwell" }
|
||||||
|
signal-hook = "0.3.14"
|
||||||
|
libc = "0.2.106"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test_gen = { path = "../compiler/test_gen" }
|
test_gen = { path = "../compiler/test_gen" }
|
||||||
libc = "0.2.106"
|
|
||||||
roc_build = { path = "../compiler/build", features = ["target-aarch64", "target-x86_64"] }
|
roc_build = { path = "../compiler/build", features = ["target-aarch64", "target-x86_64"] }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
indoc = "1.0.7"
|
indoc = "1.0.7"
|
||||||
|
|
|
@ -141,6 +141,7 @@ mod test {
|
||||||
const BUFFER_SIZE: usize = 1024;
|
const BUFFER_SIZE: usize = 1024;
|
||||||
|
|
||||||
let mut shared_buffer = [0u8; BUFFER_SIZE];
|
let mut shared_buffer = [0u8; BUFFER_SIZE];
|
||||||
|
let mut memory = crate::run::ExpectMemory::from_slice(&mut shared_buffer);
|
||||||
|
|
||||||
// communicate the mmapped name to zig/roc
|
// communicate the mmapped name to zig/roc
|
||||||
let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ());
|
let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ());
|
||||||
|
@ -148,15 +149,15 @@ mod test {
|
||||||
unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) };
|
unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) };
|
||||||
|
|
||||||
let mut writer = Vec::with_capacity(1024);
|
let mut writer = Vec::with_capacity(1024);
|
||||||
let (_failed, _passed) = crate::run::run_expects(
|
let (_failed, _passed) = crate::run::run_expects_with_memory(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
RenderTarget::ColorTerminal,
|
RenderTarget::ColorTerminal,
|
||||||
arena,
|
arena,
|
||||||
interns,
|
interns,
|
||||||
&lib,
|
&lib,
|
||||||
&mut expectations,
|
&mut expectations,
|
||||||
shared_buffer.as_mut_ptr(),
|
|
||||||
expects,
|
expects,
|
||||||
|
&mut memory,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
use std::os::unix::process::parent_id;
|
||||||
|
|
||||||
use bumpalo::collections::Vec as BumpVec;
|
use bumpalo::collections::Vec as BumpVec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use roc_build::link::llvm_module_to_dylib;
|
use roc_build::link::llvm_module_to_dylib;
|
||||||
use roc_collections::{MutSet, VecMap};
|
use roc_collections::{MutSet, VecMap};
|
||||||
use roc_gen_llvm::llvm::{build::LlvmBackendMode, externs::add_default_roc_externs};
|
use roc_gen_llvm::{
|
||||||
|
llvm::{build::LlvmBackendMode, externs::add_default_roc_externs},
|
||||||
|
run_roc::RocCallResult,
|
||||||
|
run_roc_dylib,
|
||||||
|
};
|
||||||
use roc_load::{EntryPoint, Expectations, MonomorphizedModule};
|
use roc_load::{EntryPoint, Expectations, MonomorphizedModule};
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_mono::ir::OptLevel;
|
use roc_mono::ir::OptLevel;
|
||||||
|
@ -12,6 +18,67 @@ use roc_reporting::{error::expect::Renderer, report::RenderTarget};
|
||||||
use roc_target::TargetInfo;
|
use roc_target::TargetInfo;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
pub(crate) struct ExpectMemory<'a> {
|
||||||
|
ptr: *mut u8,
|
||||||
|
length: usize,
|
||||||
|
shm_name: Option<std::ffi::CString>,
|
||||||
|
_marker: std::marker::PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ExpectMemory<'a> {
|
||||||
|
const SHM_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn from_slice(slice: &mut [u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
ptr: slice.as_mut_ptr(),
|
||||||
|
length: slice.len(),
|
||||||
|
shm_name: None,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_or_reuse_mmap(shm_name: &str) -> Self {
|
||||||
|
let cstring = std::ffi::CString::new(shm_name).unwrap();
|
||||||
|
Self::mmap_help(cstring, libc::O_RDWR | libc::O_CREAT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reuse_mmap(&mut self) -> Option<Self> {
|
||||||
|
let shm_name = self.shm_name.as_ref()?.clone();
|
||||||
|
Some(Self::mmap_help(shm_name, libc::O_RDWR))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mmap_help(cstring: std::ffi::CString, shm_flags: i32) -> Self {
|
||||||
|
let ptr = unsafe {
|
||||||
|
let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666);
|
||||||
|
|
||||||
|
libc::ftruncate(shared_fd, Self::SHM_SIZE as _);
|
||||||
|
|
||||||
|
libc::mmap(
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
Self::SHM_SIZE,
|
||||||
|
libc::PROT_WRITE | libc::PROT_READ,
|
||||||
|
libc::MAP_SHARED,
|
||||||
|
shared_fd,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
ptr: ptr.cast(),
|
||||||
|
length: Self::SHM_SIZE,
|
||||||
|
shm_name: Some(cstring),
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_shared_buffer(&mut self, lib: &libloading::Library) {
|
||||||
|
let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ());
|
||||||
|
let mut result = RocCallResult::default();
|
||||||
|
unsafe { set_shared_buffer((self.ptr, self.length), &mut result) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn run_expects<W: std::io::Write>(
|
pub fn run_expects<W: std::io::Write>(
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
|
@ -20,21 +87,66 @@ pub fn run_expects<W: std::io::Write>(
|
||||||
interns: &Interns,
|
interns: &Interns,
|
||||||
lib: &libloading::Library,
|
lib: &libloading::Library,
|
||||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||||
shared_ptr: *mut u8,
|
expects: ExpectFunctions<'_>,
|
||||||
expects: bumpalo::collections::Vec<'_, ToplevelExpect<'_>>,
|
|
||||||
) -> std::io::Result<(usize, usize)> {
|
) -> std::io::Result<(usize, usize)> {
|
||||||
let mut failed = 0;
|
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
|
||||||
let mut passed = 0;
|
let mut memory = ExpectMemory::create_or_reuse_mmap(&shm_name);
|
||||||
|
|
||||||
for expect in expects {
|
run_expects_with_memory(
|
||||||
let result = run_expect(
|
|
||||||
writer,
|
writer,
|
||||||
render_target,
|
render_target,
|
||||||
arena,
|
arena,
|
||||||
interns,
|
interns,
|
||||||
lib,
|
lib,
|
||||||
expectations,
|
expectations,
|
||||||
shared_ptr,
|
expects,
|
||||||
|
&mut memory,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn run_expects_with_memory<W: std::io::Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
render_target: RenderTarget,
|
||||||
|
arena: &Bump,
|
||||||
|
interns: &Interns,
|
||||||
|
lib: &libloading::Library,
|
||||||
|
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||||
|
expects: ExpectFunctions<'_>,
|
||||||
|
memory: &mut ExpectMemory,
|
||||||
|
) -> std::io::Result<(usize, usize)> {
|
||||||
|
let mut failed = 0;
|
||||||
|
let mut passed = 0;
|
||||||
|
|
||||||
|
for expect in expects.fx {
|
||||||
|
let result = run_expect_fx(
|
||||||
|
writer,
|
||||||
|
render_target,
|
||||||
|
arena,
|
||||||
|
interns,
|
||||||
|
lib,
|
||||||
|
expectations,
|
||||||
|
memory,
|
||||||
|
expect,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
true => passed += 1,
|
||||||
|
false => failed += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.set_shared_buffer(lib);
|
||||||
|
|
||||||
|
for expect in expects.pure {
|
||||||
|
let result = run_expect_pure(
|
||||||
|
writer,
|
||||||
|
render_target,
|
||||||
|
arena,
|
||||||
|
interns,
|
||||||
|
lib,
|
||||||
|
expectations,
|
||||||
|
memory,
|
||||||
expect,
|
expect,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -48,23 +160,23 @@ pub fn run_expects<W: std::io::Write>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn run_expect<W: std::io::Write>(
|
fn run_expect_pure<W: std::io::Write>(
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
render_target: RenderTarget,
|
render_target: RenderTarget,
|
||||||
arena: &Bump,
|
arena: &Bump,
|
||||||
interns: &Interns,
|
interns: &Interns,
|
||||||
lib: &libloading::Library,
|
lib: &libloading::Library,
|
||||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||||
shared_ptr: *mut u8,
|
shared_memory: &mut ExpectMemory,
|
||||||
expect: ToplevelExpect<'_>,
|
expect: ToplevelExpect<'_>,
|
||||||
) -> std::io::Result<bool> {
|
) -> std::io::Result<bool> {
|
||||||
use roc_gen_llvm::try_run_jit_function;
|
use roc_gen_llvm::try_run_jit_function;
|
||||||
|
|
||||||
let sequence = ExpectSequence::new(shared_ptr.cast());
|
let sequence = ExpectSequence::new(shared_memory.ptr.cast());
|
||||||
|
|
||||||
let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
|
let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
|
||||||
|
|
||||||
let shared_memory_ptr: *const u8 = shared_ptr.cast();
|
let shared_memory_ptr: *const u8 = shared_memory.ptr.cast();
|
||||||
|
|
||||||
if result.is_err() || sequence.count_failures() > 0 {
|
if result.is_err() || sequence.count_failures() > 0 {
|
||||||
let module_id = expect.symbol.module_id();
|
let module_id = expect.symbol.module_id();
|
||||||
|
@ -103,6 +215,104 @@ fn run_expect<W: std::io::Write>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn run_expect_fx<W: std::io::Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
render_target: RenderTarget,
|
||||||
|
arena: &Bump,
|
||||||
|
interns: &Interns,
|
||||||
|
lib: &libloading::Library,
|
||||||
|
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||||
|
parent_memory: &mut ExpectMemory,
|
||||||
|
expect: ToplevelExpect<'_>,
|
||||||
|
) -> std::io::Result<bool> {
|
||||||
|
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
|
||||||
|
|
||||||
|
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
|
||||||
|
|
||||||
|
match unsafe { libc::fork() } {
|
||||||
|
0 => unsafe {
|
||||||
|
// we are the child
|
||||||
|
|
||||||
|
use roc_gen_llvm::try_run_jit_function;
|
||||||
|
|
||||||
|
let mut child_memory = parent_memory.reuse_mmap().unwrap();
|
||||||
|
|
||||||
|
let sequence = ExpectSequence::new(child_memory.ptr);
|
||||||
|
|
||||||
|
child_memory.set_shared_buffer(lib);
|
||||||
|
|
||||||
|
let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
|
||||||
|
|
||||||
|
if let Err(msg) = result {
|
||||||
|
panic!("roc panic {}", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if sequence.count_failures() > 0 {
|
||||||
|
libc::kill(parent_id() as _, SIGUSR1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(0)
|
||||||
|
},
|
||||||
|
-1 => {
|
||||||
|
// something failed
|
||||||
|
|
||||||
|
// Display a human-friendly error message
|
||||||
|
println!("Error {:?}", std::io::Error::last_os_error());
|
||||||
|
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
1.. => {
|
||||||
|
let mut has_succeeded = true;
|
||||||
|
|
||||||
|
for sig in &mut signals {
|
||||||
|
match sig {
|
||||||
|
SIGCHLD => {
|
||||||
|
// done!
|
||||||
|
return Ok(has_succeeded);
|
||||||
|
}
|
||||||
|
SIGUSR1 => {
|
||||||
|
// this is the signal we use for an expect failure. Let's see what the child told us
|
||||||
|
has_succeeded = false;
|
||||||
|
|
||||||
|
let frame =
|
||||||
|
ExpectFrame::at_offset(parent_memory.ptr, ExpectSequence::START_OFFSET);
|
||||||
|
let module_id = frame.module_id;
|
||||||
|
|
||||||
|
let data = expectations.get_mut(&module_id).unwrap();
|
||||||
|
let filename = data.path.to_owned();
|
||||||
|
let source = std::fs::read_to_string(&data.path).unwrap();
|
||||||
|
|
||||||
|
let renderer = Renderer::new(
|
||||||
|
arena,
|
||||||
|
interns,
|
||||||
|
render_target,
|
||||||
|
module_id,
|
||||||
|
filename,
|
||||||
|
&source,
|
||||||
|
);
|
||||||
|
|
||||||
|
render_expect_failure(
|
||||||
|
writer,
|
||||||
|
&renderer,
|
||||||
|
arena,
|
||||||
|
None,
|
||||||
|
expectations,
|
||||||
|
interns,
|
||||||
|
parent_memory.ptr,
|
||||||
|
ExpectSequence::START_OFFSET,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
_ => println!("received signal {}", sig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn roc_dev_expect(
|
pub fn roc_dev_expect(
|
||||||
writer: &mut impl std::io::Write,
|
writer: &mut impl std::io::Write,
|
||||||
arena: &Bump,
|
arena: &Bump,
|
||||||
|
@ -205,8 +415,8 @@ impl ExpectSequence {
|
||||||
fn new(ptr: *mut u8) -> Self {
|
fn new(ptr: *mut u8) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ptr as *mut usize;
|
let ptr = ptr as *mut usize;
|
||||||
*ptr.add(Self::COUNT_INDEX) = 0;
|
std::ptr::write_unaligned(ptr.add(Self::COUNT_INDEX), 0);
|
||||||
*ptr.add(Self::OFFSET_INDEX) = Self::START_OFFSET;
|
std::ptr::write_unaligned(ptr.add(Self::OFFSET_INDEX), Self::START_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -251,13 +461,19 @@ pub struct ToplevelExpect<'a> {
|
||||||
pub region: Region,
|
pub region: Region,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ExpectFunctions<'a> {
|
||||||
|
pub pure: BumpVec<'a, ToplevelExpect<'a>>,
|
||||||
|
pub fx: BumpVec<'a, ToplevelExpect<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn expect_mono_module_to_dylib<'a>(
|
pub fn expect_mono_module_to_dylib<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
target: Triple,
|
target: Triple,
|
||||||
loaded: MonomorphizedModule<'a>,
|
loaded: MonomorphizedModule<'a>,
|
||||||
opt_level: OptLevel,
|
opt_level: OptLevel,
|
||||||
mode: LlvmBackendMode,
|
mode: LlvmBackendMode,
|
||||||
) -> Result<(libloading::Library, BumpVec<'a, ToplevelExpect<'a>>), libloading::Error> {
|
) -> Result<(libloading::Library, ExpectFunctions<'a>), libloading::Error> {
|
||||||
let target_info = TargetInfo::from(&target);
|
let target_info = TargetInfo::from(&target);
|
||||||
|
|
||||||
let MonomorphizedModule {
|
let MonomorphizedModule {
|
||||||
|
@ -306,18 +522,25 @@ pub fn expect_mono_module_to_dylib<'a>(
|
||||||
EntryPoint::Test => None,
|
EntryPoint::Test => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let capacity = toplevel_expects.pure.len() + toplevel_expects.fx.len();
|
||||||
|
let mut expect_symbols = BumpVec::with_capacity_in(capacity, env.arena);
|
||||||
|
|
||||||
|
expect_symbols.extend(toplevel_expects.pure.keys().copied());
|
||||||
|
expect_symbols.extend(toplevel_expects.fx.keys().copied());
|
||||||
|
|
||||||
let expect_names = roc_gen_llvm::llvm::build::build_procedures_expose_expects(
|
let expect_names = roc_gen_llvm::llvm::build::build_procedures_expose_expects(
|
||||||
&env,
|
&env,
|
||||||
opt_level,
|
opt_level,
|
||||||
toplevel_expects.unzip_slices().0,
|
&expect_symbols,
|
||||||
procedures,
|
procedures,
|
||||||
opt_entry_point,
|
opt_entry_point,
|
||||||
);
|
);
|
||||||
|
|
||||||
let expects = bumpalo::collections::Vec::from_iter_in(
|
let expects_fx = bumpalo::collections::Vec::from_iter_in(
|
||||||
toplevel_expects
|
toplevel_expects
|
||||||
|
.fx
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(expect_names.into_iter())
|
.zip(expect_names.iter().skip(toplevel_expects.pure.len()))
|
||||||
.map(|((symbol, region), name)| ToplevelExpect {
|
.map(|((symbol, region), name)| ToplevelExpect {
|
||||||
symbol,
|
symbol,
|
||||||
region,
|
region,
|
||||||
|
@ -326,6 +549,24 @@ pub fn expect_mono_module_to_dylib<'a>(
|
||||||
env.arena,
|
env.arena,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let expects_pure = bumpalo::collections::Vec::from_iter_in(
|
||||||
|
toplevel_expects
|
||||||
|
.pure
|
||||||
|
.into_iter()
|
||||||
|
.zip(expect_names.iter())
|
||||||
|
.map(|((symbol, region), name)| ToplevelExpect {
|
||||||
|
symbol,
|
||||||
|
region,
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
env.arena,
|
||||||
|
);
|
||||||
|
|
||||||
|
let expects = ExpectFunctions {
|
||||||
|
pure: expects_pure,
|
||||||
|
fx: expects_fx,
|
||||||
|
};
|
||||||
|
|
||||||
env.dibuilder.finalize();
|
env.dibuilder.finalize();
|
||||||
|
|
||||||
// we don't use the debug info, and it causes weird errors.
|
// we don't use the debug info, and it causes weird errors.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue