mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-01 21:40:58 +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_target",
|
||||
"roc_types",
|
||||
"signal-hook",
|
||||
"strip-ansi-escapes",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
|
|
|
|||
|
|
@ -8,12 +8,10 @@ use roc_build::link::{LinkType, LinkingStrategy};
|
|||
use roc_collections::VecMap;
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
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_module::symbol::{Interns, ModuleId};
|
||||
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 std::env;
|
||||
use std::ffi::{CString, OsStr};
|
||||
|
|
@ -320,8 +318,6 @@ pub enum FormatMode {
|
|||
CheckOnly,
|
||||
}
|
||||
|
||||
const SHM_SIZE: i64 = 1024;
|
||||
|
||||
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
||||
let start_time = Instant::now();
|
||||
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 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(
|
||||
&mut writer,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
|
|
@ -421,7 +410,6 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
interns,
|
||||
&lib,
|
||||
&mut expectations,
|
||||
shared_buffer.as_mut_ptr(),
|
||||
expects,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -953,94 +941,13 @@ impl ExecutableFile {
|
|||
// with Expect
|
||||
#[cfg(target_family = "unix")]
|
||||
unsafe fn roc_run_native_debug(
|
||||
executable: ExecutableFile,
|
||||
argv: &[*const c_char],
|
||||
envp: &[*const c_char],
|
||||
mut expectations: VecMap<ModuleId, Expectations>,
|
||||
interns: Interns,
|
||||
_executable: ExecutableFile,
|
||||
_argv: &[*const c_char],
|
||||
_envp: &[*const c_char],
|
||||
_expectations: VecMap<ModuleId, Expectations>,
|
||||
_interns: Interns,
|
||||
) {
|
||||
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
|
||||
|
||||
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!(),
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
|
|||
|
|
@ -463,6 +463,7 @@ fn stmt_spec<'a>(
|
|||
builder.add_choice(block, &cases)
|
||||
}
|
||||
Expect { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
||||
ExpectFx { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
||||
Ret(symbol) => Ok(env.symbols[symbol]),
|
||||
Refcounting(modify_rc, continuation) => match modify_rc {
|
||||
ModifyRc::Inc(symbol, _) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
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 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 {
|
||||
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) {
|
||||
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 });
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
},
|
||||
|
||||
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)),
|
||||
|
||||
RuntimeError(err) => RuntimeError(err.clone()),
|
||||
|
|
|
|||
|
|
@ -231,6 +231,13 @@ pub enum Expr {
|
|||
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
|
||||
TypedHole(Variable),
|
||||
|
||||
|
|
@ -277,6 +284,7 @@ impl Expr {
|
|||
Category::OpaqueWrap(opaque_name)
|
||||
}
|
||||
Self::Expect { .. } => Category::Expect,
|
||||
Self::ExpectFx { .. } => Category::Expect,
|
||||
|
||||
// these nodes place no constraints on the expression's type
|
||||
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) => {
|
||||
let mut new_defs = Vec::with_capacity(defs.len());
|
||||
|
||||
|
|
@ -2327,7 +2357,7 @@ impl Declarations {
|
|||
) -> usize {
|
||||
let index = self.declarations.len();
|
||||
|
||||
self.declarations.push(DeclarationTag::Expectation);
|
||||
self.declarations.push(DeclarationTag::ExpectationFx);
|
||||
self.variables.push(Variable::BOOL);
|
||||
self.symbols.push(Loc::at(preceding_comment, name));
|
||||
self.annotations.push(None);
|
||||
|
|
@ -2513,13 +2543,13 @@ impl Declarations {
|
|||
}
|
||||
Expectation => {
|
||||
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);
|
||||
}
|
||||
ExpectationFx => {
|
||||
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);
|
||||
}
|
||||
|
|
@ -2658,6 +2688,9 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
|
|||
}
|
||||
Expr::Expect {
|
||||
loc_continuation, ..
|
||||
}
|
||||
| Expr::ExpectFx {
|
||||
loc_continuation, ..
|
||||
} => {
|
||||
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:
|
||||
/// 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
|
||||
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 {
|
||||
NonRecursive(Region, Box<Def>),
|
||||
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 = Expr::Expect {
|
||||
loc_condition: Box::new(loc_expr),
|
||||
loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)),
|
||||
lookups_in_cond,
|
||||
let expect = if has_effects {
|
||||
Expr::ExpectFx {
|
||||
loc_condition: Box::new(loc_expr),
|
||||
loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)),
|
||||
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);
|
||||
|
|
@ -2764,14 +2813,21 @@ struct ExpectCollector {
|
|||
|
||||
impl crate::traverse::Visitor for ExpectCollector {
|
||||
fn visit_expr(&mut self, expr: &Expr, _region: Region, var: Variable) {
|
||||
if let Expr::Expect {
|
||||
lookups_in_cond,
|
||||
loc_condition,
|
||||
..
|
||||
} = expr
|
||||
{
|
||||
self.expects
|
||||
.insert(loc_condition.region, lookups_in_cond.to_vec());
|
||||
match expr {
|
||||
Expr::Expect {
|
||||
lookups_in_cond,
|
||||
loc_condition,
|
||||
..
|
||||
}
|
||||
| Expr::ExpectFx {
|
||||
lookups_in_cond,
|
||||
loc_condition,
|
||||
..
|
||||
} => {
|
||||
self.expects
|
||||
.insert(loc_condition.region, lookups_in_cond.to_vec());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
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 {
|
||||
captured_symbols,
|
||||
name,
|
||||
|
|
|
|||
|
|
@ -276,6 +276,19 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
|
|||
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::RuntimeError(..) => { /* terminal */ }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -520,6 +520,53 @@ pub fn constrain_expr(
|
|||
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 {
|
||||
cond_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(_) => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,10 +199,6 @@ impl LlvmBackendMode {
|
|||
LlvmBackendMode::CliTest => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn runs_expects_in_separate_process(self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Env<'a, 'ctx, 'env> {
|
||||
|
|
@ -2802,15 +2798,67 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
lookups,
|
||||
);
|
||||
|
||||
// NOTE: signals to the parent process that an expect failed
|
||||
if env.mode.runs_expects_in_separate_process() {
|
||||
let func = env
|
||||
.module
|
||||
.get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE)
|
||||
.unwrap();
|
||||
bd.build_unconditional_branch(then_block);
|
||||
}
|
||||
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.build_call(func, &[], "call_expect_finalize_failed");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -708,6 +708,7 @@ impl<'a> WasmBackend<'a> {
|
|||
Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following),
|
||||
|
||||
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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -707,6 +707,12 @@ struct LateSpecializationsModule<'a> {
|
|||
procs_base: ProcsBase<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ToplevelExpects {
|
||||
pub pure: VecMap<Symbol, Region>,
|
||||
pub fx: VecMap<Symbol, Region>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MonomorphizedModule<'a> {
|
||||
pub module_id: ModuleId,
|
||||
|
|
@ -716,7 +722,7 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
pub toplevel_expects: VecMap<Symbol, Region>,
|
||||
pub toplevel_expects: ToplevelExpects,
|
||||
pub entry_point: EntryPoint<'a>,
|
||||
pub exposed_to_host: ExposedToHost,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
|
|
@ -821,7 +827,7 @@ enum Msg<'a> {
|
|||
solved_subs: Solved<Subs>,
|
||||
module_timing: ModuleTiming,
|
||||
abilities_store: AbilitiesStore,
|
||||
toplevel_expects: VecMap<Symbol, Region>,
|
||||
toplevel_expects: ToplevelExpects,
|
||||
},
|
||||
MadeSpecializations {
|
||||
module_id: ModuleId,
|
||||
|
|
@ -908,7 +914,7 @@ struct State<'a> {
|
|||
pub module_cache: ModuleCache<'a>,
|
||||
pub dependencies: Dependencies<'a>,
|
||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
pub toplevel_expects: VecMap<Symbol, Region>,
|
||||
pub toplevel_expects: ToplevelExpects,
|
||||
pub exposed_to_host: ExposedToHost,
|
||||
|
||||
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
|
||||
|
|
@ -981,7 +987,7 @@ impl<'a> State<'a> {
|
|||
module_cache: ModuleCache::default(),
|
||||
dependencies,
|
||||
procedures: MutMap::default(),
|
||||
toplevel_expects: VecMap::default(),
|
||||
toplevel_expects: ToplevelExpects::default(),
|
||||
exposed_to_host: ExposedToHost::default(),
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
|
|
@ -2527,7 +2533,8 @@ fn update<'a>(
|
|||
|
||||
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
|
||||
.module_cache
|
||||
|
|
@ -4818,7 +4825,7 @@ fn build_pending_specializations<'a>(
|
|||
let find_specializations_start = Instant::now();
|
||||
|
||||
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 {
|
||||
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 {
|
||||
annotation: expr_var,
|
||||
|
|
@ -5131,7 +5138,7 @@ fn build_pending_specializations<'a>(
|
|||
let expr_region = declarations.expressions[index].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);
|
||||
}
|
||||
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 {
|
||||
annotation: expr_var,
|
||||
|
|
@ -5207,7 +5214,7 @@ fn build_pending_specializations<'a>(
|
|||
let expr_region = declarations.expressions[index].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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ impl<'a> ParamMap<'a> {
|
|||
}
|
||||
|
||||
Expect { remainder, .. } => stack.push(remainder),
|
||||
ExpectFx { remainder, .. } => stack.push(remainder),
|
||||
|
||||
Switch {
|
||||
branches,
|
||||
|
|
@ -820,6 +821,10 @@ impl<'a> BorrowInfState<'a> {
|
|||
self.collect_stmt(param_map, remainder);
|
||||
}
|
||||
|
||||
ExpectFx { remainder, .. } => {
|
||||
self.collect_stmt(param_map, remainder);
|
||||
}
|
||||
|
||||
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
|
||||
|
||||
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),
|
||||
ExpectFx { remainder, .. } => stack.push(remainder),
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ExpectFx {
|
||||
condition,
|
||||
remainder,
|
||||
lookups,
|
||||
..
|
||||
} => {
|
||||
result.insert(*condition);
|
||||
result.extend(lookups.iter().copied());
|
||||
stack.push(remainder);
|
||||
}
|
||||
|
||||
Jump(_, arguments) => {
|
||||
result.extend(arguments.iter().copied());
|
||||
}
|
||||
|
|
@ -671,8 +682,8 @@ impl<'a> Context<'a> {
|
|||
|
||||
match ownership {
|
||||
DataBorrowedFunctionOwns => {
|
||||
// the data is borrowed;
|
||||
// increment it to own the values so the function can use them
|
||||
// the data is borrowed; increment it to own the values so the function
|
||||
// can use them
|
||||
let rc = Stmt::Refcounting(ModifyRc::Inc(argument, 1), stmt);
|
||||
|
||||
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.
|
||||
// We don't want to touch the RC on the roc side, so treat these as borrowed.
|
||||
// incrementing/consuming the closure (if needed) is done by the zig implementation. We
|
||||
// don't want to touch the RC on the roc side, so treat these as borrowed.
|
||||
const FUNCTION: 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)
|
||||
}
|
||||
ListSortWith { xs } => {
|
||||
// NOTE: we may apply the function to the same argument multiple times.
|
||||
// for that to be valid, the function must borrow its argument. This is not
|
||||
// enforced at the moment
|
||||
// NOTE: we may apply the function to the same argument multiple times. for that to
|
||||
// be valid, the function must borrow its argument. This is not enforced at the
|
||||
// moment
|
||||
//
|
||||
// we also don't check that both arguments have the same ownership characteristics
|
||||
let ownerships = [(xs, function_ps[0])];
|
||||
|
|
@ -768,7 +779,8 @@ impl<'a> Context<'a> {
|
|||
|
||||
match ownership {
|
||||
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 condition_stmt = branch_on_list_uniqueness(
|
||||
|
|
@ -914,8 +926,7 @@ impl<'a> Context<'a> {
|
|||
}
|
||||
|
||||
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
|
||||
// EmptyArray is always stack-allocated
|
||||
// function pointers are persistent
|
||||
// EmptyArray is always stack-allocated function pointers are persistent
|
||||
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 {
|
||||
// is this value a constant?
|
||||
// TODO do function pointers also fall into this category?
|
||||
// is this value a constant? TODO do function pointers also fall into this category?
|
||||
let persistent = false;
|
||||
|
||||
// must this value be consumed?
|
||||
|
|
@ -979,9 +989,7 @@ impl<'a> Context<'a> {
|
|||
|
||||
// Add `dec` instructions for parameters that are
|
||||
//
|
||||
// - references
|
||||
// - not alive in `b`
|
||||
// - not borrow.
|
||||
// - references - not alive in `b` - not borrow.
|
||||
//
|
||||
// That is, we must make sure these parameters are consumed.
|
||||
fn add_dec_for_dead_params(
|
||||
|
|
@ -1021,9 +1029,9 @@ impl<'a> Context<'a> {
|
|||
) -> (&'a Stmt<'a>, LiveVarSet) {
|
||||
use Stmt::*;
|
||||
|
||||
// let-chains can be very long, especially for large (list) literals
|
||||
// in (rust) debug mode, this function can overflow the stack for such values
|
||||
// so we have to write an explicit loop.
|
||||
// let-chains can be very long, especially for large (list) literals in (rust) debug mode,
|
||||
// this function can overflow the stack for such values so we have to write an explicit
|
||||
// loop.
|
||||
{
|
||||
let mut cont = stmt;
|
||||
let mut triples = Vec::new_in(self.arena);
|
||||
|
|
@ -1199,6 +1207,30 @@ impl<'a> Context<'a> {
|
|||
(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()),
|
||||
}
|
||||
}
|
||||
|
|
@ -1313,6 +1345,17 @@ pub fn collect_stmt(
|
|||
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 {
|
||||
id: j,
|
||||
parameters,
|
||||
|
|
|
|||
|
|
@ -1513,6 +1513,14 @@ pub enum Stmt<'a> {
|
|||
/// what happens after the expect
|
||||
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`
|
||||
Join {
|
||||
id: JoinPointId,
|
||||
|
|
@ -2100,6 +2108,17 @@ impl<'a> Stmt<'a> {
|
|||
.append(alloc.hardline())
|
||||
.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
|
||||
.text("ret ")
|
||||
.append(symbol_to_doc(alloc, *symbol))
|
||||
|
|
@ -4163,6 +4182,7 @@ pub fn with_hole<'a>(
|
|||
EmptyRecord => let_empty_struct(assigned, hole),
|
||||
|
||||
Expect { .. } => unreachable!("I think this is unreachable"),
|
||||
ExpectFx { .. } => unreachable!("I think this is unreachable"),
|
||||
|
||||
If {
|
||||
cond_var,
|
||||
|
|
@ -6133,6 +6153,45 @@ pub fn from_can<'a>(
|
|||
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) => {
|
||||
// because Roc is strict, only functions can be recursive!
|
||||
for def in defs.into_iter() {
|
||||
|
|
@ -6497,6 +6556,32 @@ fn substitute_in_stmt_help<'a>(
|
|||
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) => {
|
||||
let mut did_change = false;
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
@ -446,6 +471,39 @@ fn function_d_main<'a, 'i>(
|
|||
(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 {
|
||||
id,
|
||||
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)
|
||||
}
|
||||
|
||||
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(_) => {
|
||||
// terminals
|
||||
stmt
|
||||
|
|
@ -653,6 +731,11 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
|
|||
remainder,
|
||||
..
|
||||
} => *condition == needle || has_live_var(jp_live_vars, remainder, needle),
|
||||
ExpectFx {
|
||||
condition,
|
||||
remainder,
|
||||
..
|
||||
} => *condition == needle || has_live_var(jp_live_vars, remainder, needle),
|
||||
Join {
|
||||
id,
|
||||
parameters,
|
||||
|
|
|
|||
|
|
@ -273,6 +273,30 @@ fn insert_jumps<'a>(
|
|||
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,
|
||||
Jump(_, _) => None,
|
||||
RuntimeError(_) => None,
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ fn parse_defs_end<'a>(
|
|||
let parse_expect_vanilla =
|
||||
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 = either!(parse_expect_vanilla, parse_expect_fx);
|
||||
let parse_expect = either!(parse_expect_fx, parse_expect_vanilla);
|
||||
|
||||
match space0_after_e(
|
||||
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 value_def = match expect_flavor {
|
||||
Either::First(_) => ValueDef::Expect {
|
||||
Either::Second(_) => ValueDef::Expect {
|
||||
condition: arena.alloc(loc_def_expr),
|
||||
preceding_comment,
|
||||
},
|
||||
Either::Second(_) => ValueDef::ExpectFx {
|
||||
Either::First(_) => ValueDef::ExpectFx {
|
||||
condition: arena.alloc(loc_def_expr),
|
||||
preceding_comment,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
|||
ZeroArgumentTag { .. } => todo!(),
|
||||
OpaqueRef { .. } => todo!(),
|
||||
Expect { .. } => todo!(),
|
||||
ExpectFx { .. } => todo!(),
|
||||
TypedHole(_) => todo!(),
|
||||
RuntimeError(_) => todo!(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ roc_build = { path = "../compiler/build" }
|
|||
|
||||
libloading = "0.7.1"
|
||||
inkwell = { path = "../vendor/inkwell" }
|
||||
signal-hook = "0.3.14"
|
||||
libc = "0.2.106"
|
||||
|
||||
[dev-dependencies]
|
||||
test_gen = { path = "../compiler/test_gen" }
|
||||
libc = "0.2.106"
|
||||
roc_build = { path = "../compiler/build", features = ["target-aarch64", "target-x86_64"] }
|
||||
tempfile = "3.2.0"
|
||||
indoc = "1.0.7"
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ mod test {
|
|||
const BUFFER_SIZE: usize = 1024;
|
||||
|
||||
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
|
||||
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) };
|
||||
|
||||
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,
|
||||
RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
interns,
|
||||
&lib,
|
||||
&mut expectations,
|
||||
shared_buffer.as_mut_ptr(),
|
||||
expects,
|
||||
&mut memory,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
use std::os::unix::process::parent_id;
|
||||
|
||||
use bumpalo::collections::Vec as BumpVec;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use roc_build::link::llvm_module_to_dylib;
|
||||
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_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::ir::OptLevel;
|
||||
|
|
@ -12,6 +18,67 @@ use roc_reporting::{error::expect::Renderer, report::RenderTarget};
|
|||
use roc_target::TargetInfo;
|
||||
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)]
|
||||
pub fn run_expects<W: std::io::Write>(
|
||||
writer: &mut W,
|
||||
|
|
@ -20,21 +87,66 @@ pub fn run_expects<W: std::io::Write>(
|
|||
interns: &Interns,
|
||||
lib: &libloading::Library,
|
||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||
shared_ptr: *mut u8,
|
||||
expects: bumpalo::collections::Vec<'_, ToplevelExpect<'_>>,
|
||||
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,
|
||||
lib,
|
||||
expectations,
|
||||
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 {
|
||||
let result = run_expect(
|
||||
for expect in expects.fx {
|
||||
let result = run_expect_fx(
|
||||
writer,
|
||||
render_target,
|
||||
arena,
|
||||
interns,
|
||||
lib,
|
||||
expectations,
|
||||
shared_ptr,
|
||||
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,
|
||||
)?;
|
||||
|
||||
|
|
@ -48,23 +160,23 @@ pub fn run_expects<W: std::io::Write>(
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run_expect<W: std::io::Write>(
|
||||
fn run_expect_pure<W: std::io::Write>(
|
||||
writer: &mut W,
|
||||
render_target: RenderTarget,
|
||||
arena: &Bump,
|
||||
interns: &Interns,
|
||||
lib: &libloading::Library,
|
||||
expectations: &mut VecMap<ModuleId, Expectations>,
|
||||
shared_ptr: *mut u8,
|
||||
shared_memory: &mut ExpectMemory,
|
||||
expect: ToplevelExpect<'_>,
|
||||
) -> std::io::Result<bool> {
|
||||
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 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 {
|
||||
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(
|
||||
writer: &mut impl std::io::Write,
|
||||
arena: &Bump,
|
||||
|
|
@ -205,8 +415,8 @@ impl ExpectSequence {
|
|||
fn new(ptr: *mut u8) -> Self {
|
||||
unsafe {
|
||||
let ptr = ptr as *mut usize;
|
||||
*ptr.add(Self::COUNT_INDEX) = 0;
|
||||
*ptr.add(Self::OFFSET_INDEX) = Self::START_OFFSET;
|
||||
std::ptr::write_unaligned(ptr.add(Self::COUNT_INDEX), 0);
|
||||
std::ptr::write_unaligned(ptr.add(Self::OFFSET_INDEX), Self::START_OFFSET);
|
||||
}
|
||||
|
||||
Self {
|
||||
|
|
@ -251,13 +461,19 @@ pub struct ToplevelExpect<'a> {
|
|||
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>(
|
||||
arena: &'a Bump,
|
||||
target: Triple,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
opt_level: OptLevel,
|
||||
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 MonomorphizedModule {
|
||||
|
|
@ -306,18 +522,25 @@ pub fn expect_mono_module_to_dylib<'a>(
|
|||
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(
|
||||
&env,
|
||||
opt_level,
|
||||
toplevel_expects.unzip_slices().0,
|
||||
&expect_symbols,
|
||||
procedures,
|
||||
opt_entry_point,
|
||||
);
|
||||
|
||||
let expects = bumpalo::collections::Vec::from_iter_in(
|
||||
let expects_fx = bumpalo::collections::Vec::from_iter_in(
|
||||
toplevel_expects
|
||||
.fx
|
||||
.into_iter()
|
||||
.zip(expect_names.into_iter())
|
||||
.zip(expect_names.iter().skip(toplevel_expects.pure.len()))
|
||||
.map(|((symbol, region), name)| ToplevelExpect {
|
||||
symbol,
|
||||
region,
|
||||
|
|
@ -326,6 +549,24 @@ pub fn expect_mono_module_to_dylib<'a>(
|
|||
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();
|
||||
|
||||
// we don't use the debug info, and it causes weird errors.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue