Merge branch 'trunk' into rc-missed-opt

This commit is contained in:
Folkert de Vries 2021-03-30 14:13:24 +02:00 committed by GitHub
commit cebf5db894
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 256 additions and 199 deletions

View file

@ -3773,12 +3773,7 @@ fn make_specializations<'a>(
// TODO: for now this final specialization pass is sequential, // TODO: for now this final specialization pass is sequential,
// with no parallelization at all. We should try to parallelize // with no parallelization at all. We should try to parallelize
// this, but doing so will require a redesign of Procs. // this, but doing so will require a redesign of Procs.
procs = roc_mono::ir::specialize_all( procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
&mut mono_env,
procs,
&mut layout_cache,
// &finished_info.vars_by_symbol,
);
let external_specializations_requested = procs.externals_we_need.clone(); let external_specializations_requested = procs.externals_we_need.clone();
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena); let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);

View file

@ -558,13 +558,7 @@ impl<'a> Procs<'a> {
} }
} }
Err(error) => { Err(error) => {
let error_msg = format!( panic!("TODO generate a RuntimeError message for {:?}", error);
"TODO generate a RuntimeError message for {:?}",
error
);
self.runtime_errors
.insert(symbol, env.arena.alloc(error_msg));
panic!();
} }
} }
} }
@ -671,11 +665,7 @@ impl<'a> Procs<'a> {
self.specialized.insert((symbol, layout), Done(proc)); self.specialized.insert((symbol, layout), Done(proc));
} }
Err(error) => { Err(error) => {
let error_msg = panic!("TODO generate a RuntimeError message for {:?}", error);
format!("TODO generate a RuntimeError message for {:?}", error);
self.runtime_errors
.insert(symbol, env.arena.alloc(error_msg));
panic!();
} }
} }
} }
@ -699,7 +689,6 @@ fn add_pending<'a>(
#[derive(Default)] #[derive(Default)]
pub struct Specializations<'a> { pub struct Specializations<'a> {
by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
runtime_errors: MutSet<Symbol>,
} }
impl<'a> Specializations<'a> { impl<'a> Specializations<'a> {
@ -715,32 +704,15 @@ impl<'a> Specializations<'a> {
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc) !procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
); );
// We shouldn't already have a runtime error recorded for this symbol
debug_assert!(!self.runtime_errors.contains(&symbol));
procs_by_layout.insert(layout, proc); procs_by_layout.insert(layout, proc);
} }
pub fn runtime_error(&mut self, symbol: Symbol) {
// We shouldn't already have a normal proc recorded for this symbol
debug_assert!(!self.by_symbol.contains_key(&symbol));
self.runtime_errors.insert(symbol);
}
pub fn into_owned(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
(self.by_symbol, self.runtime_errors)
}
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
let runtime_errors: usize = self.runtime_errors.len(); self.by_symbol.len()
let specializations: usize = self.by_symbol.len();
runtime_errors + specializations
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.by_symbol.is_empty()
} }
} }
@ -1696,13 +1668,15 @@ pub fn specialize_all<'a>(
Ok((proc, layout)) => { Ok((proc, layout)) => {
procs.specialized.insert((name, layout), Done(proc)); procs.specialized.insert((name, layout), Done(proc));
} }
Err(error) => { Err(SpecializeFailure {
let error_msg = env.arena.alloc(format!( problem: _,
"TODO generate a RuntimeError message for {:?}", attempted_layout,
error }) => {
)); let proc = generate_runtime_error_function(env, name, attempted_layout);
procs.runtime_errors.insert(name, error_msg); procs
.specialized
.insert((name, attempted_layout), Done(proc));
} }
} }
} }
@ -1758,13 +1732,14 @@ pub fn specialize_all<'a>(
procs.specialized.insert((name, layout), Done(proc)); procs.specialized.insert((name, layout), Done(proc));
} }
} }
Err(error) => { Err(SpecializeFailure {
let error_msg = env.arena.alloc(format!( attempted_layout, ..
"TODO generate a RuntimeError message for {:?}", }) => {
error let proc = generate_runtime_error_function(env, name, attempted_layout);
));
procs.runtime_errors.insert(name, error_msg); procs
.specialized
.insert((name, attempted_layout), Done(proc));
} }
} }
} }
@ -1774,6 +1749,47 @@ pub fn specialize_all<'a>(
procs procs
} }
fn generate_runtime_error_function<'a>(
env: &mut Env<'a, '_>,
name: Symbol,
layout: Layout<'a>,
) -> Proc<'a> {
let (arg_layouts, ret_layout) = match layout {
Layout::FunctionPointer(a, r) => (a, *r),
_ => (&[] as &[_], layout),
};
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
for arg in arg_layouts {
args.push((*arg, env.unique_symbol()));
}
let mut msg = bumpalo::collections::string::String::with_capacity_in(80, env.arena);
use std::fmt::Write;
write!(
&mut msg,
"The {:?} function could not be generated, likely due to a type error.",
name
)
.unwrap();
eprintln!("emitted runtime error function {:?}", &msg);
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
Proc {
name,
args: args.into_bump_slice(),
body: runtime_error,
closure_data_layout: None,
ret_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
}
}
fn specialize_external<'a>( fn specialize_external<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
@ -2270,6 +2286,14 @@ fn build_specialized_proc<'a>(
} }
} }
#[derive(Debug)]
struct SpecializeFailure<'a> {
/// The layout we attempted to create
attempted_layout: Layout<'a>,
/// The problem we ran into while creating it
problem: LayoutProblem,
}
fn specialize<'a>( fn specialize<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
@ -2277,7 +2301,7 @@ fn specialize<'a>(
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
pending: PendingSpecialization, pending: PendingSpecialization,
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> { ) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> {
let PendingSpecialization { let PendingSpecialization {
solved_type, solved_type,
host_exposed_aliases, host_exposed_aliases,
@ -2321,7 +2345,7 @@ fn specialize_solved_type<'a>(
solved_type: SolvedType, solved_type: SolvedType,
host_exposed_aliases: MutMap<Symbol, SolvedType>, host_exposed_aliases: MutMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> { ) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> {
// add the specializations that other modules require of us // add the specializations that other modules require of us
use roc_solve::solve::instantiate_rigids; use roc_solve::solve::instantiate_rigids;
@ -2330,6 +2354,10 @@ fn specialize_solved_type<'a>(
let fn_var = introduce_solved_type_to_subs(env, &solved_type); let fn_var = introduce_solved_type_to_subs(env, &solved_type);
let attempted_layout = layout_cache
.from_var(&env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
// make sure rigid variables in the annotation are converted to flex variables // make sure rigid variables in the annotation are converted to flex variables
instantiate_rigids(env.subs, partial_proc.annotation); instantiate_rigids(env.subs, partial_proc.annotation);
@ -2353,17 +2381,25 @@ fn specialize_solved_type<'a>(
match specialized { match specialized {
Ok(proc) => { Ok(proc) => {
let layout = layout_cache // when successful, the layout after unification should be the layout before unification
.from_var(&env.arena, fn_var, env.subs) debug_assert_eq!(
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); attempted_layout,
layout_cache
.from_var(&env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err))
);
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
layout_cache.rollback_to(cache_snapshot); layout_cache.rollback_to(cache_snapshot);
Ok((proc, layout)) Ok((proc, attempted_layout))
} }
Err(error) => { Err(error) => {
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
layout_cache.rollback_to(cache_snapshot); layout_cache.rollback_to(cache_snapshot);
Err(error) Err(SpecializeFailure {
problem: error,
attempted_layout,
})
} }
} }
} }
@ -5893,6 +5929,20 @@ fn call_by_name<'a>(
// Register a pending_specialization for this function // Register a pending_specialization for this function
match layout_cache.from_var(env.arena, fn_var, env.subs) { match layout_cache.from_var(env.arena, fn_var, env.subs) {
Err(LayoutProblem::UnresolvedTypeVar(var)) => {
let msg = format!(
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
var, proc_name, fn_var
);
Stmt::RuntimeError(env.arena.alloc(msg))
}
Err(LayoutProblem::Erroneous) => {
let msg = format!(
"Hit an erroneous type when creating a layout for {:?}",
proc_name
);
Stmt::RuntimeError(env.arena.alloc(msg))
}
Ok(layout) => { Ok(layout) => {
// Build the CallByName node // Build the CallByName node
let arena = env.arena; let arena = env.arena;
@ -6028,123 +6078,42 @@ fn call_by_name<'a>(
"\n\n{:?}\n\n{:?}", "\n\n{:?}\n\n{:?}",
full_layout, layout full_layout, layout
); );
let function_layout =
FunctionLayouts::from_layout(env.arena, layout);
procs.specialized.remove(&(proc_name, full_layout)); call_specialized_proc(
env,
procs procs,
.specialized proc_name,
.insert((proc_name, function_layout.full), Done(proc)); proc,
layout,
if field_symbols.is_empty() { field_symbols,
debug_assert!(loc_args.is_empty()); loc_args,
layout_cache,
// This happens when we return a function, e.g. assigned,
// hole,
// foo = Num.add )
//
// Even though the layout (and type) are functions,
// there are no arguments. This confuses our IR,
// and we have to fix it here.
match full_layout {
Layout::Closure(_, closure_layout, _) => {
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
full_layout: function_layout.full,
arg_layouts: function_layout.arguments,
},
arguments: field_symbols,
};
// in the case of a closure specifically, we
// have to create a custom layout, to make sure
// the closure data is part of the layout
let closure_struct_layout = Layout::Struct(
env.arena.alloc([
function_layout.full,
closure_layout
.as_block_of_memory_layout(),
]),
);
build_call(
env,
call,
assigned,
closure_struct_layout,
hole,
)
}
_ => {
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
full_layout: function_layout.full,
arg_layouts: function_layout.arguments,
},
arguments: field_symbols,
};
build_call(
env,
call,
assigned,
function_layout.full,
hole,
)
}
}
} else {
debug_assert_eq!(
function_layout.arguments.len(),
field_symbols.len(),
"scroll up a bit for background"
);
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
full_layout: function_layout.full,
arg_layouts: function_layout.arguments,
},
arguments: field_symbols,
};
let iter = loc_args
.into_iter()
.rev()
.zip(field_symbols.iter().rev());
let result = build_call(
env,
call,
assigned,
function_layout.result,
hole,
);
assign_to_symbols(
env,
procs,
layout_cache,
iter,
result,
)
}
} }
Err(error) => { Err(SpecializeFailure {
let error_msg = env.arena.alloc(format!( attempted_layout,
"TODO generate a RuntimeError message for {:?}", problem: _,
error }) => {
)); let proc = generate_runtime_error_function(
env,
proc_name,
attempted_layout,
);
procs.runtime_errors.insert(proc_name, error_msg); call_specialized_proc(
env,
Stmt::RuntimeError(error_msg) procs,
proc_name,
proc,
layout,
field_symbols,
loc_args,
layout_cache,
assigned,
hole,
)
} }
} }
} }
@ -6191,33 +6160,104 @@ fn call_by_name<'a>(
} }
None => { None => {
// This must have been a runtime error. unreachable!("Proc name {:?} is invalid", proc_name)
match procs.runtime_errors.get(&proc_name) {
Some(error) => Stmt::RuntimeError(
env.arena.alloc(format!("runtime error {:?}", error)),
),
None => unreachable!("Proc name {:?} is invalid", proc_name),
}
} }
} }
} }
} }
} }
} }
Err(LayoutProblem::UnresolvedTypeVar(var)) => { }
let msg = format!( }
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
var, proc_name, fn_var #[allow(clippy::too_many_arguments)]
); fn call_specialized_proc<'a>(
Stmt::RuntimeError(env.arena.alloc(msg)) env: &mut Env<'a, '_>,
} procs: &mut Procs<'a>,
Err(LayoutProblem::Erroneous) => { proc_name: Symbol,
let msg = format!( proc: Proc<'a>,
"Hit an erroneous type when creating a layout for {:?}", layout: Layout<'a>,
proc_name field_symbols: &'a [Symbol],
); loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
Stmt::RuntimeError(env.arena.alloc(msg)) layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let function_layout = FunctionLayouts::from_layout(env.arena, layout);
procs.specialized.remove(&(proc_name, layout));
procs
.specialized
.insert((proc_name, function_layout.full), Done(proc));
if field_symbols.is_empty() {
debug_assert!(loc_args.is_empty());
// This happens when we return a function, e.g.
//
// foo = Num.add
//
// Even though the layout (and type) are functions,
// there are no arguments. This confuses our IR,
// and we have to fix it here.
match layout {
Layout::Closure(_, closure_layout, _) => {
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
full_layout: function_layout.full,
arg_layouts: function_layout.arguments,
},
arguments: field_symbols,
};
// in the case of a closure specifically, we
// have to create a custom layout, to make sure
// the closure data is part of the layout
let closure_struct_layout = Layout::Struct(env.arena.alloc([
function_layout.full,
closure_layout.as_block_of_memory_layout(),
]));
build_call(env, call, assigned, closure_struct_layout, hole)
}
_ => {
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
full_layout: function_layout.full,
arg_layouts: function_layout.arguments,
},
arguments: field_symbols,
};
build_call(env, call, assigned, function_layout.full, hole)
}
} }
} else {
debug_assert_eq!(
function_layout.arguments.len(),
field_symbols.len(),
"scroll up a bit for background"
);
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
full_layout: function_layout.full,
arg_layouts: function_layout.arguments,
},
arguments: field_symbols,
};
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
let result = build_call(env, call, assigned, function_layout.result, hole);
assign_to_symbols(env, procs, layout_cache, iter, result)
} }
} }

View file

@ -341,13 +341,13 @@ mod test_mono {
"#, "#,
indoc!( indoc!(
r#" r#"
procedure Num.46 (#Attr.2): procedure Num.47 (#Attr.2):
let Test.3 = lowlevel NumRound #Attr.2; let Test.3 = lowlevel NumRound #Attr.2;
ret Test.3; ret Test.3;
procedure Test.0 (): procedure Test.0 ():
let Test.2 = 3.6f64; let Test.2 = 3.6f64;
let Test.1 = CallByName Num.46 Test.2; let Test.1 = CallByName Num.47 Test.2;
ret Test.1; ret Test.1;
"# "#
), ),

View file

@ -404,6 +404,7 @@ mod gen_num {
); );
} }
#[test]
fn f64_sqrt_zero() { fn f64_sqrt_zero() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(

View file

@ -2276,3 +2276,23 @@ fn function_malformed_pattern() {
i64 i64
); );
} }
#[test]
#[should_panic(expected = "Hit an erroneous type when creating a layout for")]
fn call_invalid_layout() {
assert_llvm_evals_to!(
indoc!(
r#"
f : I64 -> I64
f = \x -> x
f {}
"#
),
3,
i64,
|x| x,
false,
true
);
}

View file

@ -37,6 +37,7 @@ pub fn helper<'a>(
src: &str, src: &str,
stdlib: &'a roc_builtins::std::StdLib, stdlib: &'a roc_builtins::std::StdLib,
leak: bool, leak: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) { ) -> (&'static str, String, Library) {
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope}; use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
@ -170,7 +171,7 @@ pub fn helper<'a>(
println!("{}", lines.join("\n")); println!("{}", lines.join("\n"));
// only crash at this point if there were no delayed_errors // only crash at this point if there were no delayed_errors
if delayed_errors.is_empty() { if delayed_errors.is_empty() && !ignore_problems {
assert_eq!(0, 1, "Mistakes were made"); assert_eq!(0, 1, "Mistakes were made");
} }
} }
@ -331,7 +332,7 @@ pub fn helper<'a>(
#[macro_export] #[macro_export]
macro_rules! assert_llvm_evals_to { macro_rules! assert_llvm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr, $ignore_problems:expr) => {
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use roc_gen::run_jit_function; use roc_gen::run_jit_function;
@ -343,7 +344,7 @@ macro_rules! assert_llvm_evals_to {
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let (main_fn_name, errors, lib) = let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, $ignore_problems, &context);
let transform = |success| { let transform = |success| {
let expected = $expected; let expected = $expected;
@ -354,7 +355,7 @@ macro_rules! assert_llvm_evals_to {
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_llvm_evals_to!($src, $expected, $ty, $transform, true); assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
}; };
} }
@ -375,7 +376,7 @@ macro_rules! assert_evals_to {
// parsing the source, so that there's no chance their passing // parsing the source, so that there's no chance their passing
// or failing depends on leftover state from the previous one. // or failing depends on leftover state from the previous one.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak, false);
} }
{ {
// NOTE at the moment, the optimized tests do the same thing // NOTE at the moment, the optimized tests do the same thing
@ -392,7 +393,7 @@ macro_rules! assert_non_opt_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, true); assert_llvm_evals_to!($src, $expected, $ty, $transform, true, false);
} }
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{ ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{