mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-10 18:08:55 +03:00
Merge branch 'trunk' into merge-docs-into-load-file
This commit is contained in:
commit
a10f768c36
@ -52,9 +52,9 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio
|
||||
// // line length, that would have already happened back
|
||||
// // in the parsing step, and we never would have reached
|
||||
// // this code. Still, debug_assert that they won't!
|
||||
// debug_assert!(buf_col_offset <= std::u16::MAX as usize);
|
||||
// debug_assert!(ident.len() <= std::u16::MAX as usize);
|
||||
// debug_assert!((parsed_length - ident.len() - 1) <= std::u16::MAX as usize);
|
||||
// debug_assert!(buf_col_offset <= u16::MAX as usize);
|
||||
// debug_assert!(ident.len() <= u16::MAX as usize);
|
||||
// debug_assert!((parsed_length - ident.len() - 1) <= u16::MAX as usize);
|
||||
|
||||
// let start_line = state.line;
|
||||
|
||||
|
@ -322,8 +322,10 @@ pub fn constrain_expr(
|
||||
arguments,
|
||||
loc_body: boxed,
|
||||
captured_symbols,
|
||||
name,
|
||||
..
|
||||
} => {
|
||||
// NOTE defs are treated somewhere else!
|
||||
let loc_body_expr = &**boxed;
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
@ -372,34 +374,20 @@ pub fn constrain_expr(
|
||||
copy
|
||||
});
|
||||
|
||||
let closed_over_symbols = captured_symbols;
|
||||
|
||||
let mut tag_arguments = Vec::with_capacity(closed_over_symbols.len());
|
||||
let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len());
|
||||
|
||||
for (symbol, var) in closed_over_symbols {
|
||||
// make sure the variable is registered
|
||||
vars.push(*var);
|
||||
|
||||
// this symbol is captured, so it must be part of the closure type
|
||||
tag_arguments.push(Type::Variable(*var));
|
||||
|
||||
// make the variable equal to the looked-up type of symbol
|
||||
captured_symbols_constraints.push(Constraint::Lookup(
|
||||
*symbol,
|
||||
Expected::NoExpectation(Type::Variable(*var)),
|
||||
Region::zero(),
|
||||
));
|
||||
}
|
||||
|
||||
let tag_name_string = format!("Closure_{}", closure_var.index());
|
||||
let tag_name = roc_module::ident::TagName::Global(tag_name_string.into());
|
||||
let closure_type = Type::TagUnion(
|
||||
vec![(tag_name, tag_arguments)],
|
||||
Box::new(Type::Variable(closure_ext_var)),
|
||||
let closure_constraint = constrain_closure_size(
|
||||
*name,
|
||||
region,
|
||||
captured_symbols,
|
||||
closure_var,
|
||||
closure_ext_var,
|
||||
&mut vars,
|
||||
);
|
||||
|
||||
let fn_type = Type::Function(pattern_types, Box::new(closure_type), Box::new(ret_type));
|
||||
let fn_type = Type::Function(
|
||||
pattern_types,
|
||||
Box::new(Type::Variable(closure_var)),
|
||||
Box::new(ret_type),
|
||||
);
|
||||
|
||||
exists(
|
||||
vars,
|
||||
@ -421,7 +409,7 @@ pub fn constrain_expr(
|
||||
Category::Storage,
|
||||
region,
|
||||
),
|
||||
Constraint::And(captured_symbols_constraints),
|
||||
closure_constraint,
|
||||
]),
|
||||
)
|
||||
}
|
||||
@ -1104,9 +1092,12 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
Closure {
|
||||
function_type: fn_var,
|
||||
closure_type: closure_var,
|
||||
closure_ext_var,
|
||||
return_type: ret_var,
|
||||
captured_symbols,
|
||||
arguments,
|
||||
loc_body,
|
||||
name,
|
||||
..
|
||||
},
|
||||
Type::Function(arg_types, _, _),
|
||||
@ -1124,11 +1115,12 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
|
||||
let ret_var = *ret_var;
|
||||
let closure_var = *closure_var;
|
||||
let closure_ext_var = *closure_ext_var;
|
||||
let ret_type = Type::Variable(ret_var);
|
||||
let closure_type = Type::Variable(closure_var);
|
||||
|
||||
vars.push(ret_var);
|
||||
vars.push(closure_var);
|
||||
vars.push(closure_ext_var);
|
||||
|
||||
let it = arguments.iter().zip(arg_types.iter()).enumerate();
|
||||
for (index, ((pattern_var, loc_pattern), loc_ann)) in it {
|
||||
@ -1177,9 +1169,18 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
}
|
||||
}
|
||||
|
||||
let closure_constraint = constrain_closure_size(
|
||||
*name,
|
||||
region,
|
||||
captured_symbols,
|
||||
closure_var,
|
||||
closure_ext_var,
|
||||
&mut vars,
|
||||
);
|
||||
|
||||
let fn_type = Type::Function(
|
||||
pattern_types,
|
||||
Box::new(closure_type),
|
||||
Box::new(Type::Variable(closure_var)),
|
||||
Box::new(ret_type.clone()),
|
||||
);
|
||||
let body_type = NoExpectation(ret_type);
|
||||
@ -1209,6 +1210,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
Category::Storage,
|
||||
region,
|
||||
),
|
||||
closure_constraint,
|
||||
]),
|
||||
)
|
||||
}
|
||||
@ -1246,6 +1248,53 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
}))
|
||||
}
|
||||
|
||||
fn constrain_closure_size(
|
||||
name: Symbol,
|
||||
region: Region,
|
||||
captured_symbols: &[(Symbol, Variable)],
|
||||
closure_var: Variable,
|
||||
closure_ext_var: Variable,
|
||||
variables: &mut Vec<Variable>,
|
||||
) -> Constraint {
|
||||
debug_assert!(variables.iter().any(|s| *s == closure_var));
|
||||
debug_assert!(variables.iter().any(|s| *s == closure_ext_var));
|
||||
|
||||
let mut tag_arguments = Vec::with_capacity(captured_symbols.len());
|
||||
let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len());
|
||||
|
||||
for (symbol, var) in captured_symbols {
|
||||
// make sure the variable is registered
|
||||
variables.push(*var);
|
||||
|
||||
// this symbol is captured, so it must be part of the closure type
|
||||
tag_arguments.push(Type::Variable(*var));
|
||||
|
||||
// make the variable equal to the looked-up type of symbol
|
||||
captured_symbols_constraints.push(Constraint::Lookup(
|
||||
*symbol,
|
||||
Expected::NoExpectation(Type::Variable(*var)),
|
||||
Region::zero(),
|
||||
));
|
||||
}
|
||||
|
||||
let tag_name = roc_module::ident::TagName::Closure(name);
|
||||
let closure_type = Type::TagUnion(
|
||||
vec![(tag_name, tag_arguments)],
|
||||
Box::new(Type::Variable(closure_ext_var)),
|
||||
);
|
||||
|
||||
let finalizer = Eq(
|
||||
Type::Variable(closure_var),
|
||||
NoExpectation(closure_type),
|
||||
Category::ClosureSize,
|
||||
region,
|
||||
);
|
||||
|
||||
captured_symbols_constraints.push(finalizer);
|
||||
|
||||
Constraint::And(captured_symbols_constraints)
|
||||
}
|
||||
|
||||
fn instantiate_rigids(
|
||||
annotation: &Type,
|
||||
introduced_vars: &IntroducedVariables,
|
||||
|
@ -717,6 +717,7 @@ pub fn constrain_expr(
|
||||
arguments,
|
||||
loc_body: boxed,
|
||||
captured_symbols,
|
||||
name,
|
||||
..
|
||||
} => {
|
||||
use roc_can::expr::Recursive;
|
||||
@ -781,34 +782,22 @@ pub fn constrain_expr(
|
||||
|
||||
let defs_constraint = And(state.constraints);
|
||||
|
||||
let mut tag_arguments = Vec::with_capacity(captured_symbols.len());
|
||||
let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len());
|
||||
|
||||
for (symbol, var) in captured_symbols {
|
||||
// make sure the variable is registered
|
||||
vars.push(*var);
|
||||
|
||||
// this symbol is captured, so it must be part of the closure type
|
||||
tag_arguments.push(Type::Variable(*var));
|
||||
|
||||
// make the variable equal to the looked-up type of symbol
|
||||
captured_symbols_constraints.push(Constraint::Lookup(
|
||||
*symbol,
|
||||
Expected::NoExpectation(Type::Variable(*var)),
|
||||
Region::zero(),
|
||||
));
|
||||
}
|
||||
|
||||
let tag_name_string = format!("Closure_{}", closure_var.index());
|
||||
let tag_name = roc_module::ident::TagName::Global(tag_name_string.into());
|
||||
let closure_type = Type::TagUnion(
|
||||
vec![(tag_name, tag_arguments)],
|
||||
Box::new(Type::Variable(closure_ext_var)),
|
||||
let closure_constraint = constrain_closure_size(
|
||||
*name,
|
||||
region,
|
||||
captured_symbols,
|
||||
closure_var,
|
||||
closure_ext_var,
|
||||
&mut vars,
|
||||
);
|
||||
|
||||
let fn_type = attr_type(
|
||||
fn_uniq_type,
|
||||
Type::Function(pattern_types, Box::new(closure_type), Box::new(ret_type)),
|
||||
Type::Function(
|
||||
pattern_types,
|
||||
Box::new(Type::Variable(closure_var)),
|
||||
Box::new(ret_type),
|
||||
),
|
||||
);
|
||||
|
||||
exists(
|
||||
@ -831,7 +820,7 @@ pub fn constrain_expr(
|
||||
Category::Lambda,
|
||||
region,
|
||||
),
|
||||
Constraint::And(captured_symbols_constraints),
|
||||
closure_constraint,
|
||||
]),
|
||||
)
|
||||
}
|
||||
@ -2295,6 +2284,53 @@ fn constrain_def(
|
||||
}))
|
||||
}
|
||||
|
||||
fn constrain_closure_size(
|
||||
name: Symbol,
|
||||
region: Region,
|
||||
captured_symbols: &[(Symbol, Variable)],
|
||||
closure_var: Variable,
|
||||
closure_ext_var: Variable,
|
||||
variables: &mut Vec<Variable>,
|
||||
) -> Constraint {
|
||||
debug_assert!(variables.iter().any(|s| *s == closure_var));
|
||||
debug_assert!(variables.iter().any(|s| *s == closure_ext_var));
|
||||
|
||||
let mut tag_arguments = Vec::with_capacity(captured_symbols.len());
|
||||
let mut captured_symbols_constraints = Vec::with_capacity(captured_symbols.len());
|
||||
|
||||
for (symbol, var) in captured_symbols {
|
||||
// make sure the variable is registered
|
||||
variables.push(*var);
|
||||
|
||||
// this symbol is captured, so it must be part of the closure type
|
||||
tag_arguments.push(Type::Variable(*var));
|
||||
|
||||
// make the variable equal to the looked-up type of symbol
|
||||
captured_symbols_constraints.push(Constraint::Lookup(
|
||||
*symbol,
|
||||
Expected::NoExpectation(Type::Variable(*var)),
|
||||
Region::zero(),
|
||||
));
|
||||
}
|
||||
|
||||
let tag_name = roc_module::ident::TagName::Closure(name);
|
||||
let closure_type = Type::TagUnion(
|
||||
vec![(tag_name, tag_arguments)],
|
||||
Box::new(Type::Variable(closure_ext_var)),
|
||||
);
|
||||
|
||||
let finalizer = Eq(
|
||||
Type::Variable(closure_var),
|
||||
Expected::NoExpectation(closure_type),
|
||||
Category::ClosureSize,
|
||||
region,
|
||||
);
|
||||
|
||||
captured_symbols_constraints.push(finalizer);
|
||||
|
||||
Constraint::And(captured_symbols_constraints)
|
||||
}
|
||||
|
||||
fn instantiate_rigids(
|
||||
var_store: &mut VarStore,
|
||||
annotation: &Type,
|
||||
|
@ -1844,13 +1844,11 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
||||
|
||||
let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout, env.ptr_bytes);
|
||||
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
|
||||
let mut arg_symbols = Vec::new_in(arena);
|
||||
|
||||
for (layout, arg_symbol) in args.iter() {
|
||||
for (layout, _) in args.iter() {
|
||||
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
|
||||
|
||||
arg_basic_types.push(arg_type);
|
||||
arg_symbols.push(arg_symbol);
|
||||
}
|
||||
|
||||
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
|
||||
@ -1895,14 +1893,29 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
||||
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
|
||||
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
||||
|
||||
// the closure argument (if any) comes in as an opaque sequence of bytes.
|
||||
// we need to cast that to the specific closure data layout that the body expects
|
||||
let value = if let Symbol::ARG_CLOSURE = *arg_symbol {
|
||||
// blindly trust that there is a layout available for the closure data
|
||||
let layout = proc.closure_data_layout.clone().unwrap();
|
||||
|
||||
// cast the input into the type that the body expects
|
||||
let closure_data_type =
|
||||
basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
|
||||
|
||||
cast_basic_basic(env.builder, arg_val, closure_data_type)
|
||||
} else {
|
||||
arg_val
|
||||
};
|
||||
|
||||
let alloca = create_entry_block_alloca(
|
||||
env,
|
||||
fn_val,
|
||||
arg_val.get_type(),
|
||||
value.get_type(),
|
||||
arg_symbol.ident_string(&env.interns),
|
||||
);
|
||||
|
||||
builder.build_store(alloca, arg_val);
|
||||
builder.build_store(alloca, value);
|
||||
|
||||
scope.insert(*arg_symbol, (layout.clone(), alloca));
|
||||
}
|
||||
|
@ -111,8 +111,9 @@ pub fn basic_type_from_layout<'ctx>(
|
||||
basic_type_from_function_layout(arena, context, args, None, ret_layout, ptr_bytes)
|
||||
}
|
||||
Closure(args, closure_layout, ret_layout) => {
|
||||
let closure_data_layout = closure_layout.as_block_of_memory_layout();
|
||||
let closure_data =
|
||||
basic_type_from_layout(arena, context, &closure_layout.as_layout(), ptr_bytes);
|
||||
basic_type_from_layout(arena, context, &closure_data_layout, ptr_bytes);
|
||||
|
||||
let function_pointer = basic_type_from_function_layout(
|
||||
arena,
|
||||
|
@ -77,7 +77,7 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
|
||||
parent,
|
||||
layout_ids,
|
||||
field_ptr,
|
||||
&closure_layout.as_layout(),
|
||||
&closure_layout.as_block_of_memory_layout(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -936,8 +936,8 @@ mod gen_primitives {
|
||||
f
|
||||
|
||||
main =
|
||||
f = foo {}
|
||||
f {}
|
||||
g = foo {}
|
||||
g {}
|
||||
"#
|
||||
),
|
||||
42,
|
||||
@ -946,8 +946,34 @@ mod gen_primitives {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn closure_in_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
|
||||
foo = \{} ->
|
||||
x = 41
|
||||
|
||||
f = \{} -> x
|
||||
|
||||
[ f ]
|
||||
|
||||
main =
|
||||
items = foo {}
|
||||
|
||||
List.len items
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn specialize_closure() {
|
||||
use roc_std::RocList;
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -965,11 +991,12 @@ mod gen_primitives {
|
||||
main =
|
||||
items = foo {}
|
||||
|
||||
List.len items
|
||||
# List.len items
|
||||
List.map items (\f -> f {})
|
||||
"#
|
||||
),
|
||||
2,
|
||||
i64
|
||||
RocList::from_slice(&[41, 42]),
|
||||
RocList<i64>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -428,7 +428,6 @@ mod gen_records {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn optional_field_when_use_default_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -472,7 +471,6 @@ mod gen_records {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn optional_field_when_no_use_default_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -530,7 +528,6 @@ mod gen_records {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn optional_field_let_no_use_default_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -582,7 +579,6 @@ mod gen_records {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn optional_field_function_no_use_default_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -436,7 +436,6 @@ mod gen_tags {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn maybe_is_just_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -37,6 +37,9 @@ pub enum TagName {
|
||||
/// Private tags are associated with a specific module, and as such use a
|
||||
/// Symbol just like all other module-specific identifiers.
|
||||
Private(Symbol),
|
||||
|
||||
/// Used to connect the closure size to the function it corresponds to
|
||||
Closure(Symbol),
|
||||
}
|
||||
|
||||
impl TagName {
|
||||
@ -44,6 +47,7 @@ impl TagName {
|
||||
match self {
|
||||
TagName::Global(uppercase) => uppercase.as_inline_str().clone(),
|
||||
TagName::Private(symbol) => symbol.fully_qualified(interns, home),
|
||||
TagName::Closure(symbol) => symbol.fully_qualified(interns, home),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,15 @@ pub enum CapturedSymbols<'a> {
|
||||
Captured(&'a [(Symbol, Variable)]),
|
||||
}
|
||||
|
||||
impl<'a> CapturedSymbols<'a> {
|
||||
fn captures(&self) -> bool {
|
||||
match self {
|
||||
CapturedSymbols::None => false,
|
||||
CapturedSymbols::Captured(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PendingSpecialization {
|
||||
solved_type: SolvedType,
|
||||
@ -51,6 +60,7 @@ pub struct Proc<'a> {
|
||||
pub name: Symbol,
|
||||
pub args: &'a [(Layout<'a>, Symbol)],
|
||||
pub body: Stmt<'a>,
|
||||
pub closure_data_layout: Option<Layout<'a>>,
|
||||
pub ret_layout: Layout<'a>,
|
||||
pub is_self_recursive: SelfRecursive,
|
||||
}
|
||||
@ -533,7 +543,10 @@ impl<'a> Procs<'a> {
|
||||
.insert((symbol, layout.clone()), InProgress);
|
||||
|
||||
match specialize(env, self, symbol, layout_cache, pending, partial_proc) {
|
||||
Ok((proc, layout)) => {
|
||||
Ok((proc, _ignore_layout)) => {
|
||||
// the `layout` is a function pointer, while `_ignore_layout` can be a
|
||||
// closure. We only specialize functions, storing this value with a closure
|
||||
// layout will give trouble.
|
||||
self.specialized.insert((symbol, layout), Done(proc));
|
||||
}
|
||||
Err(error) => {
|
||||
@ -893,6 +906,7 @@ impl<'a> Expr<'a> {
|
||||
let doc_tag = match tag_name {
|
||||
TagName::Global(s) => alloc.text(s.as_str()),
|
||||
TagName::Private(s) => alloc.text(format!("{}", s)),
|
||||
TagName::Closure(s) => alloc.text(format!("Closure({})", s)),
|
||||
};
|
||||
|
||||
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
|
||||
@ -910,6 +924,7 @@ impl<'a> Expr<'a> {
|
||||
let doc_tag = match tag_name {
|
||||
TagName::Global(s) => alloc.text(s.as_str()),
|
||||
TagName::Private(s) => alloc.text(format!("{}", s)),
|
||||
TagName::Closure(s) => alloc.text(format!("Closure({})", s)),
|
||||
};
|
||||
|
||||
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
|
||||
@ -1401,6 +1416,9 @@ fn specialize_external<'a>(
|
||||
pattern_symbols
|
||||
};
|
||||
|
||||
let (proc_args, opt_closure_layout, ret_layout) =
|
||||
build_specialized_proc_from_var(env, layout_cache, proc_name, pattern_symbols, fn_var)?;
|
||||
|
||||
// unpack the closure symbols, if any
|
||||
if let CapturedSymbols::Captured(captured) = captured_symbols {
|
||||
let mut layouts = Vec::with_capacity_in(captured.len(), env.arena);
|
||||
@ -1412,10 +1430,9 @@ fn specialize_external<'a>(
|
||||
|
||||
let field_layouts = layouts.into_bump_slice();
|
||||
|
||||
let wrapped = if captured.len() > 1 {
|
||||
Wrapped::RecordOrSingleTagUnion
|
||||
} else {
|
||||
Wrapped::SingleElementRecord
|
||||
let wrapped = match &opt_closure_layout {
|
||||
Some(x) => x.get_wrapped(),
|
||||
None => unreachable!("symbols are captured, so this must be a closure"),
|
||||
};
|
||||
|
||||
for (index, (symbol, variable)) in captured.iter().enumerate() {
|
||||
@ -1433,9 +1450,6 @@ fn specialize_external<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
let (proc_args, ret_layout) =
|
||||
build_specialized_proc_from_var(env, layout_cache, pattern_symbols, fn_var)?;
|
||||
|
||||
// reset subs, so we don't get type errors when specializing for a different signature
|
||||
layout_cache.rollback_to(cache_snapshot);
|
||||
env.subs.rollback_to(snapshot);
|
||||
@ -1446,10 +1460,16 @@ fn specialize_external<'a>(
|
||||
SelfRecursive::NotSelfRecursive
|
||||
};
|
||||
|
||||
let closure_data_layout = match opt_closure_layout {
|
||||
Some(closure_layout) => Some(closure_layout.as_named_layout(proc_name)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let proc = Proc {
|
||||
name: proc_name,
|
||||
args: proc_args,
|
||||
body: specialized_body,
|
||||
closure_data_layout,
|
||||
ret_layout,
|
||||
is_self_recursive: recursivity,
|
||||
};
|
||||
@ -1457,13 +1477,20 @@ fn specialize_external<'a>(
|
||||
Ok(proc)
|
||||
}
|
||||
|
||||
type SpecializedLayout<'a> = (
|
||||
&'a [(Layout<'a>, Symbol)],
|
||||
Option<ClosureLayout<'a>>,
|
||||
Layout<'a>,
|
||||
);
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn build_specialized_proc_from_var<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
proc_name: Symbol,
|
||||
pattern_symbols: &[Symbol],
|
||||
fn_var: Variable,
|
||||
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>), LayoutProblem> {
|
||||
) -> Result<SpecializedLayout<'a>, LayoutProblem> {
|
||||
match layout_cache.from_var(env.arena, fn_var, env.subs) {
|
||||
Ok(Layout::FunctionPointer(pattern_layouts, ret_layout)) => {
|
||||
let mut pattern_layouts_vec = Vec::with_capacity_in(pattern_layouts.len(), env.arena);
|
||||
@ -1471,6 +1498,7 @@ fn build_specialized_proc_from_var<'a>(
|
||||
|
||||
build_specialized_proc(
|
||||
env.arena,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
pattern_layouts_vec,
|
||||
None,
|
||||
@ -1483,6 +1511,7 @@ fn build_specialized_proc_from_var<'a>(
|
||||
|
||||
build_specialized_proc(
|
||||
env.arena,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
pattern_layouts_vec,
|
||||
Some(closure_layout),
|
||||
@ -1496,6 +1525,7 @@ fn build_specialized_proc_from_var<'a>(
|
||||
build_specialized_proc_adapter(
|
||||
env,
|
||||
layout_cache,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
&pattern_vars,
|
||||
closure_layout,
|
||||
@ -1505,16 +1535,27 @@ fn build_specialized_proc_from_var<'a>(
|
||||
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args))
|
||||
if !pattern_symbols.is_empty() =>
|
||||
{
|
||||
build_specialized_proc_from_var(env, layout_cache, pattern_symbols, args[1])
|
||||
}
|
||||
Content::Alias(_, _, actual) => {
|
||||
build_specialized_proc_from_var(env, layout_cache, pattern_symbols, actual)
|
||||
build_specialized_proc_from_var(
|
||||
env,
|
||||
layout_cache,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
args[1],
|
||||
)
|
||||
}
|
||||
Content::Alias(_, _, actual) => build_specialized_proc_from_var(
|
||||
env,
|
||||
layout_cache,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
actual,
|
||||
),
|
||||
_ => {
|
||||
// a top-level constant 0-argument thunk
|
||||
build_specialized_proc_adapter(
|
||||
env,
|
||||
layout_cache,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
&[],
|
||||
None,
|
||||
@ -1529,11 +1570,12 @@ fn build_specialized_proc_from_var<'a>(
|
||||
fn build_specialized_proc_adapter<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
proc_name: Symbol,
|
||||
pattern_symbols: &[Symbol],
|
||||
pattern_vars: &[Variable],
|
||||
opt_closure_layout: Option<ClosureLayout<'a>>,
|
||||
ret_var: Variable,
|
||||
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>), LayoutProblem> {
|
||||
) -> Result<SpecializedLayout<'a>, LayoutProblem> {
|
||||
let mut arg_layouts = Vec::with_capacity_in(pattern_vars.len(), &env.arena);
|
||||
|
||||
for arg_var in pattern_vars {
|
||||
@ -1548,6 +1590,7 @@ fn build_specialized_proc_adapter<'a>(
|
||||
|
||||
build_specialized_proc(
|
||||
env.arena,
|
||||
proc_name,
|
||||
pattern_symbols,
|
||||
arg_layouts,
|
||||
opt_closure_layout,
|
||||
@ -1558,11 +1601,12 @@ fn build_specialized_proc_adapter<'a>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn build_specialized_proc<'a>(
|
||||
arena: &'a Bump,
|
||||
_proc_name: Symbol,
|
||||
pattern_symbols: &[Symbol],
|
||||
pattern_layouts: Vec<Layout<'a>>,
|
||||
opt_closure_layout: Option<ClosureLayout<'a>>,
|
||||
ret_layout: Layout<'a>,
|
||||
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>), LayoutProblem> {
|
||||
) -> Result<SpecializedLayout<'a>, LayoutProblem> {
|
||||
let mut proc_args = Vec::with_capacity_in(pattern_layouts.len(), arena);
|
||||
|
||||
let pattern_layouts_len = pattern_layouts.len();
|
||||
@ -1592,19 +1636,23 @@ fn build_specialized_proc<'a>(
|
||||
Some(layout) if pattern_symbols.last() == Some(&Symbol::ARG_CLOSURE) => {
|
||||
// here we define the lifted (now top-level) f function. Its final argument is `Symbol::ARG_CLOSURE`,
|
||||
// it stores the closure structure (just an integer in this case)
|
||||
proc_args.push((layout.as_layout(), Symbol::ARG_CLOSURE));
|
||||
proc_args.push((layout.as_block_of_memory_layout(), Symbol::ARG_CLOSURE));
|
||||
|
||||
debug_assert_eq!(
|
||||
pattern_layouts_len + 1,
|
||||
pattern_symbols.len(),
|
||||
"Tried to zip two vecs with different lengths!"
|
||||
);
|
||||
|
||||
let proc_args = proc_args.into_bump_slice();
|
||||
|
||||
Ok((proc_args, Some(layout), ret_layout))
|
||||
}
|
||||
Some(layout) => {
|
||||
// else if there is a closure layout, we're building the `f_closure` value
|
||||
// that means we're really creating a ( function_ptr, closure_data ) pair
|
||||
|
||||
let closure_data_layout = layout.as_layout();
|
||||
let closure_data_layout = layout.as_block_of_memory_layout();
|
||||
let function_ptr_layout = Layout::FunctionPointer(
|
||||
arena.alloc([Layout::Struct(&[]), closure_data_layout.clone()]),
|
||||
arena.alloc(ret_layout),
|
||||
@ -1613,7 +1661,7 @@ fn build_specialized_proc<'a>(
|
||||
let closure_layout =
|
||||
Layout::Struct(arena.alloc([function_ptr_layout, closure_data_layout]));
|
||||
|
||||
return Ok((&[], closure_layout));
|
||||
Ok((&[], None, closure_layout))
|
||||
}
|
||||
None => {
|
||||
// else we're making a normal function, no closure problems to worry about
|
||||
@ -1629,12 +1677,11 @@ fn build_specialized_proc<'a>(
|
||||
pattern_symbols.len(),
|
||||
"Tried to zip two vecs with different lengths!"
|
||||
);
|
||||
let proc_args = proc_args.into_bump_slice();
|
||||
|
||||
Ok((proc_args, None, ret_layout))
|
||||
}
|
||||
}
|
||||
|
||||
let proc_args = proc_args.into_bump_slice();
|
||||
|
||||
Ok((proc_args, ret_layout))
|
||||
}
|
||||
|
||||
fn specialize<'a>(
|
||||
@ -1721,6 +1768,7 @@ impl<'a> FunctionLayouts<'a> {
|
||||
result: (*result).clone(),
|
||||
full: layout,
|
||||
},
|
||||
Layout::Closure(_, _, _) => todo!(),
|
||||
_ => FunctionLayouts {
|
||||
full: layout.clone(),
|
||||
arguments: &[],
|
||||
@ -1953,8 +2001,25 @@ pub fn with_hole<'a>(
|
||||
match hole {
|
||||
Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([symbol])),
|
||||
_ => {
|
||||
// if you see this, there is variable aliasing going on
|
||||
Stmt::Ret(symbol)
|
||||
let result = Stmt::Ret(symbol);
|
||||
let original = symbol;
|
||||
|
||||
// we don't have a more accurate variable available, which means the variable
|
||||
// from the partial_proc will be used. So far that has given the correct
|
||||
// result, but I'm not sure this will continue to be the case in more complex
|
||||
// examples.
|
||||
let opt_fn_var = None;
|
||||
|
||||
// if this is a function symbol, ensure that it's properly specialized!
|
||||
reuse_function_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
opt_fn_var,
|
||||
symbol,
|
||||
result,
|
||||
original,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2617,7 +2682,7 @@ pub fn with_hole<'a>(
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let closure_data_layout = closure_layout.as_layout();
|
||||
let closure_data_layout = closure_layout.as_block_of_memory_layout();
|
||||
// define the function pointer
|
||||
let function_ptr_layout = {
|
||||
let mut temp =
|
||||
@ -2777,7 +2842,8 @@ pub fn with_hole<'a>(
|
||||
let closure_symbol = function_symbol;
|
||||
|
||||
// layout of the closure record
|
||||
let closure_record_layout = closure_fields.as_layout();
|
||||
let closure_record_layout =
|
||||
closure_fields.as_block_of_memory_layout();
|
||||
|
||||
let arg_symbols = {
|
||||
let mut temp =
|
||||
@ -3076,183 +3142,28 @@ pub fn from_can<'a>(
|
||||
let function_layout =
|
||||
layout_cache.from_var(env.arena, function_type, env.subs);
|
||||
|
||||
if let Ok(Layout::Closure(
|
||||
argument_layouts,
|
||||
closure_fields,
|
||||
ret_layout,
|
||||
)) = &function_layout
|
||||
{
|
||||
let mut captured_symbols =
|
||||
Vec::from_iter_in(captured_symbols, env.arena);
|
||||
captured_symbols.sort();
|
||||
let captured_symbols = captured_symbols.into_bump_slice();
|
||||
|
||||
procs.insert_named(
|
||||
env,
|
||||
layout_cache,
|
||||
*symbol,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
CapturedSymbols::Captured(captured_symbols),
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
let closure_data_layout = closure_fields.as_layout();
|
||||
// define the function pointer
|
||||
let function_ptr_layout = {
|
||||
let mut temp = Vec::from_iter_in(
|
||||
argument_layouts.iter().cloned(),
|
||||
env.arena,
|
||||
);
|
||||
temp.push(closure_data_layout.clone());
|
||||
Layout::FunctionPointer(temp.into_bump_slice(), ret_layout)
|
||||
let captured_symbols =
|
||||
if let Ok(Layout::Closure(_, _, _)) = &function_layout {
|
||||
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
|
||||
temp.sort();
|
||||
CapturedSymbols::Captured(temp.into_bump_slice())
|
||||
} else {
|
||||
CapturedSymbols::None
|
||||
};
|
||||
|
||||
let full_layout = function_ptr_layout.clone();
|
||||
procs.insert_named(
|
||||
env,
|
||||
layout_cache,
|
||||
*symbol,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
captured_symbols,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
let fn_var = function_type;
|
||||
let proc_name = *symbol;
|
||||
let pending = PendingSpecialization::from_var(env.subs, fn_var);
|
||||
|
||||
// When requested (that is, when procs.pending_specializations is `Some`),
|
||||
// store a pending specialization rather than specializing immediately.
|
||||
//
|
||||
// We do this so that we can do specialization in two passes: first,
|
||||
// build the mono_expr with all the specialized calls in place (but
|
||||
// no specializations performed yet), and then second, *after*
|
||||
// de-duplicating requested specializations (since multiple modules
|
||||
// which could be getting monomorphized in parallel might request
|
||||
// the same specialization independently), we work through the
|
||||
// queue of pending specializations to complete each specialization
|
||||
// exactly once.
|
||||
match &mut procs.pending_specializations {
|
||||
Some(pending_specializations) => {
|
||||
// register the pending specialization, so this gets code genned later
|
||||
add_pending(
|
||||
pending_specializations,
|
||||
proc_name,
|
||||
full_layout.clone(),
|
||||
pending,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let opt_partial_proc = procs.partial_procs.get(&proc_name);
|
||||
|
||||
match opt_partial_proc {
|
||||
None => panic!("invalid proc"),
|
||||
Some(partial_proc) => {
|
||||
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()?
|
||||
let partial_proc = partial_proc.clone();
|
||||
|
||||
// Mark this proc as in-progress, so if we're dealing with
|
||||
// mutually recursive functions, we don't loop forever.
|
||||
// (We had a bug around this before this system existed!)
|
||||
procs.specialized.insert(
|
||||
(proc_name, full_layout.clone()),
|
||||
InProgress,
|
||||
);
|
||||
|
||||
match specialize(
|
||||
env,
|
||||
procs,
|
||||
proc_name,
|
||||
layout_cache,
|
||||
pending,
|
||||
partial_proc,
|
||||
) {
|
||||
Ok((proc, _layout)) => {
|
||||
// TODO sometimes the full_layout is a
|
||||
// function pointer, but the layout is a
|
||||
// closure. Figure out how to handle this
|
||||
// debug_assert_eq!(full_layout, layout);
|
||||
// let function_layout = FunctionLayouts::from_layout(layout);
|
||||
|
||||
procs.specialized.remove(&(
|
||||
proc_name,
|
||||
full_layout.clone(),
|
||||
));
|
||||
|
||||
procs.specialized.insert(
|
||||
(
|
||||
proc_name,
|
||||
// function_layout.full.clone(),
|
||||
full_layout.clone(),
|
||||
),
|
||||
Done(proc),
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
panic!("procedure is invalid {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut stmt = from_can(env, cont.value, procs, layout_cache);
|
||||
|
||||
let function_pointer = env.unique_symbol();
|
||||
let closure_data = env.unique_symbol();
|
||||
|
||||
// define the closure
|
||||
let expr =
|
||||
Expr::Struct(env.arena.alloc([function_pointer, closure_data]));
|
||||
|
||||
stmt = Stmt::Let(
|
||||
*symbol,
|
||||
expr,
|
||||
Layout::Struct(env.arena.alloc([
|
||||
function_ptr_layout.clone(),
|
||||
closure_data_layout.clone(),
|
||||
])),
|
||||
env.arena.alloc(stmt),
|
||||
);
|
||||
|
||||
// define the closure data
|
||||
|
||||
let symbols = Vec::from_iter_in(
|
||||
captured_symbols.iter().map(|x| x.0),
|
||||
env.arena,
|
||||
)
|
||||
.into_bump_slice();
|
||||
let expr = Expr::Struct(symbols);
|
||||
|
||||
stmt = Stmt::Let(
|
||||
closure_data,
|
||||
expr,
|
||||
closure_data_layout.clone(),
|
||||
env.arena.alloc(stmt),
|
||||
);
|
||||
|
||||
let expr =
|
||||
Expr::FunctionPointer(*symbol, function_ptr_layout.clone());
|
||||
|
||||
stmt = Stmt::Let(
|
||||
function_pointer,
|
||||
expr,
|
||||
function_ptr_layout,
|
||||
env.arena.alloc(stmt),
|
||||
);
|
||||
|
||||
return stmt;
|
||||
} else {
|
||||
procs.insert_named(
|
||||
env,
|
||||
layout_cache,
|
||||
*symbol,
|
||||
function_type,
|
||||
arguments,
|
||||
loc_body,
|
||||
CapturedSymbols::None,
|
||||
is_self_recursive,
|
||||
return_type,
|
||||
);
|
||||
|
||||
return from_can(env, cont.value, procs, layout_cache);
|
||||
}
|
||||
return from_can(env, cont.value, procs, layout_cache);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@ -4195,6 +4106,133 @@ fn possible_reuse_symbol<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// If the symbol is a function, make sure it is properly specialized
|
||||
fn reuse_function_symbol<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
arg_var: Option<Variable>,
|
||||
symbol: Symbol,
|
||||
result: Stmt<'a>,
|
||||
original: Symbol,
|
||||
) -> Stmt<'a> {
|
||||
match procs.partial_procs.get(&original) {
|
||||
None => result,
|
||||
Some(partial_proc) => {
|
||||
let arg_var = arg_var.unwrap_or(partial_proc.annotation);
|
||||
// this symbol is a function, that is used by-name (e.g. as an argument to another
|
||||
// function). Register it with the current variable, then create a function pointer
|
||||
// to it in the IR.
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, arg_var, env.subs)
|
||||
.expect("creating layout does not fail");
|
||||
|
||||
// we have three kinds of functions really. Plain functions, closures by capture,
|
||||
// and closures by unification. Here we record whether this function captures
|
||||
// anything.
|
||||
let captures = partial_proc.captured_symbols.captures();
|
||||
let captured = partial_proc.captured_symbols.clone();
|
||||
|
||||
match layout {
|
||||
Layout::Closure(argument_layouts, closure_layout, ret_layout) if captures => {
|
||||
// this is a closure by capture, meaning it itself captures local variables.
|
||||
// we've defined the closure as a (function_ptr, closure_data) pair already
|
||||
|
||||
let mut stmt = result;
|
||||
|
||||
let function_pointer = env.unique_symbol();
|
||||
let closure_data = env.unique_symbol();
|
||||
|
||||
// let closure_data_layout = closure_layout.as_named_layout(original);
|
||||
let closure_data_layout = closure_layout.as_block_of_memory_layout();
|
||||
|
||||
// define the function pointer
|
||||
let function_ptr_layout = ClosureLayout::extend_function_layout(
|
||||
env.arena,
|
||||
argument_layouts,
|
||||
closure_layout.clone(),
|
||||
ret_layout,
|
||||
);
|
||||
|
||||
procs.insert_passed_by_name(
|
||||
env,
|
||||
arg_var,
|
||||
original,
|
||||
function_ptr_layout.clone(),
|
||||
layout_cache,
|
||||
);
|
||||
|
||||
// define the closure
|
||||
let expr = Expr::Struct(env.arena.alloc([function_pointer, closure_data]));
|
||||
|
||||
stmt = Stmt::Let(
|
||||
symbol,
|
||||
expr,
|
||||
Layout::Struct(
|
||||
env.arena
|
||||
.alloc([function_ptr_layout.clone(), closure_data_layout.clone()]),
|
||||
),
|
||||
env.arena.alloc(stmt),
|
||||
);
|
||||
|
||||
// define the closure data
|
||||
|
||||
let symbols = match captured {
|
||||
CapturedSymbols::Captured(captured_symbols) => {
|
||||
Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena)
|
||||
.into_bump_slice()
|
||||
}
|
||||
CapturedSymbols::None => unreachable!(),
|
||||
};
|
||||
|
||||
// define the closure data, unless it's a basic unwrapped type already
|
||||
match closure_layout.build_closure_data(original, symbols) {
|
||||
Ok(expr) => {
|
||||
stmt = Stmt::Let(
|
||||
closure_data,
|
||||
expr,
|
||||
closure_data_layout.clone(),
|
||||
env.arena.alloc(stmt),
|
||||
);
|
||||
}
|
||||
Err(current) => {
|
||||
// there is only one symbol captured, use that immediately
|
||||
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
|
||||
}
|
||||
}
|
||||
|
||||
let expr = Expr::FunctionPointer(original, function_ptr_layout.clone());
|
||||
|
||||
stmt = Stmt::Let(
|
||||
function_pointer,
|
||||
expr,
|
||||
function_ptr_layout,
|
||||
env.arena.alloc(stmt),
|
||||
);
|
||||
|
||||
stmt
|
||||
}
|
||||
_ => {
|
||||
procs.insert_passed_by_name(
|
||||
env,
|
||||
arg_var,
|
||||
original,
|
||||
layout.clone(),
|
||||
layout_cache,
|
||||
);
|
||||
|
||||
Stmt::Let(
|
||||
symbol,
|
||||
Expr::FunctionPointer(original, layout.clone()),
|
||||
layout,
|
||||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_to_symbol<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
@ -4206,32 +4244,25 @@ fn assign_to_symbol<'a>(
|
||||
) -> Stmt<'a> {
|
||||
// if this argument is already a symbol, we don't need to re-define it
|
||||
if let roc_can::expr::Expr::Var(original) = loc_arg.value {
|
||||
if procs.partial_procs.contains_key(&original) {
|
||||
// this symbol is a function, that is used by-name (e.g. as an argument to another
|
||||
// function). Register it with the current variable, then create a function pointer
|
||||
// to it in the IR.
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, arg_var, env.subs)
|
||||
.expect("creating layout does not fail");
|
||||
procs.insert_passed_by_name(env, arg_var, original, layout.clone(), layout_cache);
|
||||
|
||||
return Stmt::Let(
|
||||
symbol,
|
||||
Expr::FunctionPointer(original, layout.clone()),
|
||||
layout,
|
||||
env.arena.alloc(result),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
reuse_function_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
Some(arg_var),
|
||||
symbol,
|
||||
result,
|
||||
original,
|
||||
)
|
||||
} else {
|
||||
with_hole(
|
||||
env,
|
||||
loc_arg.value,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
with_hole(
|
||||
env,
|
||||
loc_arg.value,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
|
||||
fn assign_to_symbols<'a, I>(
|
||||
|
@ -33,34 +33,66 @@ pub enum Layout<'a> {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ClosureLayout<'a> {
|
||||
/// the layout that this specific closure captures
|
||||
captured: &'a [Layout<'a>],
|
||||
/// uses a Vec instead of a MutMap because it's Hash
|
||||
/// the vec is likely to be small, so linear search is fine
|
||||
captured: &'a [(TagName, &'a [Layout<'a>])],
|
||||
|
||||
/// the layout that represents the maximum size the closure layout can have
|
||||
max_size: &'a [Layout<'a>],
|
||||
layout: &'a Layout<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ClosureLayout<'a> {
|
||||
fn from_bool(arena: &'a Bump) -> Self {
|
||||
let layout = Layout::Builtin(Builtin::Int1);
|
||||
let layouts = arena.alloc([layout]);
|
||||
let layouts = arena.alloc(layout);
|
||||
ClosureLayout {
|
||||
captured: layouts,
|
||||
max_size: layouts,
|
||||
captured: &[],
|
||||
layout: layouts,
|
||||
}
|
||||
}
|
||||
fn from_byte(arena: &'a Bump) -> Self {
|
||||
let layout = Layout::Builtin(Builtin::Int8);
|
||||
let layouts = arena.alloc([layout]);
|
||||
let layouts = arena.alloc(layout);
|
||||
ClosureLayout {
|
||||
captured: layouts,
|
||||
max_size: layouts,
|
||||
captured: &[],
|
||||
layout: layouts,
|
||||
}
|
||||
}
|
||||
fn from_unwrapped(layouts: &'a [Layout<'a>]) -> Self {
|
||||
fn from_unwrapped(arena: &'a Bump, layouts: &'a [Layout<'a>]) -> Self {
|
||||
debug_assert!(!layouts.is_empty());
|
||||
let layout = if layouts.len() == 1 {
|
||||
&layouts[0]
|
||||
} else {
|
||||
arena.alloc(Layout::Struct(layouts))
|
||||
};
|
||||
|
||||
ClosureLayout {
|
||||
captured: layouts,
|
||||
max_size: layouts,
|
||||
captured: &[],
|
||||
layout,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_tag_union(arena: &'a Bump, tags: &'a [(TagName, &'a [Layout<'a>])]) -> Self {
|
||||
debug_assert!(tags.len() > 1);
|
||||
|
||||
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
|
||||
|
||||
for (_, tag_args) in tags.iter() {
|
||||
tag_arguments.push(&tag_args[0..]);
|
||||
}
|
||||
|
||||
ClosureLayout {
|
||||
captured: tags,
|
||||
layout: arena.alloc(Layout::Union(tag_arguments.into_bump_slice())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_wrapped(&self) -> crate::ir::Wrapped {
|
||||
use crate::ir::Wrapped;
|
||||
|
||||
match self.layout {
|
||||
Layout::Struct(_) => Wrapped::RecordOrSingleTagUnion,
|
||||
Layout::Union(_) => Wrapped::MultiTagUnion,
|
||||
_ => Wrapped::SingleElementRecord,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +110,7 @@ impl<'a> ClosureLayout<'a> {
|
||||
use UnionVariant::*;
|
||||
match variant {
|
||||
Never | Unit => {
|
||||
// a max closure size of 0 means this is a standart top-level function
|
||||
// a max closure size of 0 means this is a standard top-level function
|
||||
Ok(None)
|
||||
}
|
||||
BoolUnion { .. } => {
|
||||
@ -93,13 +125,15 @@ impl<'a> ClosureLayout<'a> {
|
||||
}
|
||||
Unwrapped(layouts) => {
|
||||
let closure_layout =
|
||||
ClosureLayout::from_unwrapped(layouts.into_bump_slice());
|
||||
ClosureLayout::from_unwrapped(arena, layouts.into_bump_slice());
|
||||
|
||||
Ok(Some(closure_layout))
|
||||
}
|
||||
Wrapped(_tags) => {
|
||||
Wrapped(tags) => {
|
||||
// Wrapped(Vec<'a, (TagName, &'a [Layout<'a>])>),
|
||||
todo!("can't specialize multi-size closures yet")
|
||||
let closure_layout =
|
||||
ClosureLayout::from_tag_union(arena, tags.into_bump_slice());
|
||||
Ok(Some(closure_layout))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,7 +152,8 @@ impl<'a> ClosureLayout<'a> {
|
||||
closure_layout: Self,
|
||||
ret_layout: &'a Layout<'a>,
|
||||
) -> Layout<'a> {
|
||||
let closure_data_layout = closure_layout.as_layout();
|
||||
let closure_data_layout = closure_layout.layout;
|
||||
|
||||
// define the function pointer
|
||||
let function_ptr_layout = {
|
||||
let mut temp = Vec::from_iter_in(argument_layouts.iter().cloned(), arena);
|
||||
@ -130,28 +165,73 @@ impl<'a> ClosureLayout<'a> {
|
||||
}
|
||||
|
||||
pub fn stack_size(&self, pointer_size: u32) -> u32 {
|
||||
self.max_size
|
||||
.iter()
|
||||
.map(|l| l.stack_size(pointer_size))
|
||||
.sum()
|
||||
self.layout.stack_size(pointer_size)
|
||||
}
|
||||
pub fn contains_refcounted(&self) -> bool {
|
||||
self.captured.iter().any(|l| l.contains_refcounted())
|
||||
self.layout.contains_refcounted()
|
||||
}
|
||||
pub fn safe_to_memcpy(&self) -> bool {
|
||||
self.captured.iter().all(|l| l.safe_to_memcpy())
|
||||
self.layout.safe_to_memcpy()
|
||||
}
|
||||
|
||||
pub fn as_layout(&self) -> Layout<'a> {
|
||||
if self.captured.len() == 1 {
|
||||
self.captured[0].clone()
|
||||
pub fn as_named_layout(&self, symbol: Symbol) -> Layout<'a> {
|
||||
let layouts = if self.captured.is_empty() {
|
||||
self.layout.clone()
|
||||
} else if let Some((_, tag_args)) = self
|
||||
.captured
|
||||
.iter()
|
||||
.find(|(tn, _)| *tn == TagName::Closure(symbol))
|
||||
{
|
||||
if tag_args.len() == 1 {
|
||||
tag_args[0].clone()
|
||||
} else {
|
||||
Layout::Struct(tag_args)
|
||||
}
|
||||
} else {
|
||||
Layout::Struct(self.captured)
|
||||
}
|
||||
unreachable!(
|
||||
"invariant broken, TagName::Closure({:?}) is not in {:?}",
|
||||
symbol, &self.captured
|
||||
);
|
||||
};
|
||||
|
||||
layouts
|
||||
}
|
||||
|
||||
pub fn as_block_of_memory_layout(&self) -> Layout<'a> {
|
||||
Layout::Struct(self.max_size)
|
||||
self.layout.clone()
|
||||
}
|
||||
|
||||
pub fn build_closure_data(
|
||||
&self,
|
||||
original: Symbol,
|
||||
symbols: &'a [Symbol],
|
||||
) -> Result<crate::ir::Expr<'a>, Symbol> {
|
||||
use crate::ir::Expr;
|
||||
|
||||
match self.layout {
|
||||
Layout::Struct(fields) => {
|
||||
debug_assert!(fields.len() > 1);
|
||||
debug_assert_eq!(fields.len(), symbols.len());
|
||||
|
||||
Ok(Expr::Struct(symbols))
|
||||
}
|
||||
Layout::Union(tags) => {
|
||||
let expr = Expr::Tag {
|
||||
tag_layout: Layout::Union(tags),
|
||||
tag_name: TagName::Closure(original),
|
||||
tag_id: 0,
|
||||
union_size: tags.len() as u8,
|
||||
arguments: symbols,
|
||||
};
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
_ => {
|
||||
debug_assert_eq!(symbols.len(), 1);
|
||||
|
||||
Err(symbols[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,13 +512,18 @@ impl<'a> LayoutCache<'a> {
|
||||
|
||||
let result = Layout::from_var(&mut env, var);
|
||||
|
||||
let cached_layout = match &result {
|
||||
Ok(layout) => Cached(layout.clone()),
|
||||
Err(problem) => Problem(problem.clone()),
|
||||
};
|
||||
// Don't actually cache. The layout cache is very hard to get right in the presence
|
||||
// of specialization, it's turned of for now so an invalid cache is never the cause
|
||||
// of a problem
|
||||
if false {
|
||||
let cached_layout = match &result {
|
||||
Ok(layout) => Cached(layout.clone()),
|
||||
Err(problem) => Problem(problem.clone()),
|
||||
};
|
||||
|
||||
self.layouts
|
||||
.update_value(cached_var, |existing| existing.value = cached_layout);
|
||||
self.layouts
|
||||
.update_value(cached_var, |existing| existing.value = cached_layout);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -2182,9 +2182,8 @@ mod test_mono {
|
||||
r#"
|
||||
procedure Test.1 (Test.5):
|
||||
let Test.2 = 42i64;
|
||||
let Test.14 = FunctionPointer Test.3;
|
||||
let Test.15 = Struct {Test.2};
|
||||
let Test.3 = Struct {Test.14, Test.15};
|
||||
let Test.13 = FunctionPointer Test.3;
|
||||
let Test.3 = Struct {Test.13, Test.2};
|
||||
ret Test.3;
|
||||
|
||||
procedure Test.3 (Test.11, #Attr.12):
|
||||
@ -2203,4 +2202,52 @@ mod test_mono {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_in_list() {
|
||||
compiles_to_ir(
|
||||
indoc!(
|
||||
r#"
|
||||
app Test provides [ main ] imports []
|
||||
|
||||
foo = \{} ->
|
||||
x = 41
|
||||
|
||||
f = \{} -> x
|
||||
|
||||
[ f ]
|
||||
|
||||
main =
|
||||
items = foo {}
|
||||
|
||||
List.len items
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
procedure List.7 (#Attr.2):
|
||||
let Test.7 = lowlevel ListLen #Attr.2;
|
||||
ret Test.7;
|
||||
|
||||
procedure Test.1 (Test.5):
|
||||
let Test.2 = 41i64;
|
||||
let Test.12 = FunctionPointer Test.3;
|
||||
let Test.11 = Struct {Test.12, Test.2};
|
||||
let Test.10 = Array [Test.11];
|
||||
ret Test.10;
|
||||
|
||||
procedure Test.3 (Test.9, #Attr.12):
|
||||
let Test.2 = Index 0 #Attr.12;
|
||||
ret Test.2;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.8 = Struct {};
|
||||
let Test.4 = CallByName Test.1 Test.8;
|
||||
let Test.6 = CallByName List.7 Test.4;
|
||||
dec Test.4;
|
||||
ret Test.6;
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ mod test_parse {
|
||||
|
||||
#[test]
|
||||
fn first_line_too_long() {
|
||||
let max_line_length = std::u16::MAX as usize;
|
||||
let max_line_length = u16::MAX as usize;
|
||||
|
||||
// the string literal "ZZZZZZZZZ" but with way more Zs
|
||||
let too_long_str_body: String = (1..max_line_length)
|
||||
|
@ -924,6 +924,10 @@ fn add_category<'b>(
|
||||
alloc.private_tag_name(*name),
|
||||
alloc.text(" private tag application has the type:"),
|
||||
]),
|
||||
TagApply {
|
||||
tag_name: TagName::Closure(_name),
|
||||
args_count: _,
|
||||
} => unreachable!("closure tags are for internal use only"),
|
||||
|
||||
Record => alloc.concat(vec![this_is, alloc.text(" a record of type:")]),
|
||||
|
||||
|
@ -248,6 +248,7 @@ impl<'a> RocDocAllocator<'a> {
|
||||
match tn {
|
||||
TagName::Global(uppercase) => self.global_tag_name(uppercase),
|
||||
TagName::Private(symbol) => self.private_tag_name(symbol),
|
||||
TagName::Closure(_symbol) => unreachable!("closure tags are internal only"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,12 @@ impl Pools {
|
||||
.split_last()
|
||||
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empy Pools"))
|
||||
}
|
||||
|
||||
pub fn extend_to(&mut self, n: usize) {
|
||||
for _ in self.len()..n {
|
||||
self.0.push(Vec::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -445,148 +451,149 @@ fn solve(
|
||||
subs.set_rank(var, next_rank);
|
||||
}
|
||||
|
||||
let work_in_next_pools = |next_pools: &mut Pools| {
|
||||
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank);
|
||||
// determine the next pool
|
||||
let next_pools;
|
||||
if next_rank.into_usize() < pools.len() {
|
||||
next_pools = pools
|
||||
} else {
|
||||
// we should be off by one at this point
|
||||
debug_assert_eq!(next_rank.into_usize(), 1 + pools.len());
|
||||
pools.extend_to(next_rank.into_usize());
|
||||
next_pools = pools;
|
||||
}
|
||||
|
||||
// Replace the contents of this pool with rigid_vars and flex_vars
|
||||
pool.clear();
|
||||
pool.reserve(rigid_vars.len() + flex_vars.len());
|
||||
pool.extend(rigid_vars.iter());
|
||||
pool.extend(flex_vars.iter());
|
||||
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank);
|
||||
|
||||
let mut new_env = env.clone();
|
||||
// Replace the contents of this pool with rigid_vars and flex_vars
|
||||
pool.clear();
|
||||
pool.reserve(rigid_vars.len() + flex_vars.len());
|
||||
pool.extend(rigid_vars.iter());
|
||||
pool.extend(flex_vars.iter());
|
||||
|
||||
// Add a variable for each def to local_def_vars.
|
||||
let mut local_def_vars = ImMap::default();
|
||||
// run solver in next pool
|
||||
|
||||
for (symbol, loc_type) in let_con.def_types.iter() {
|
||||
let def_type = loc_type.value.clone();
|
||||
// Add a variable for each def to local_def_vars.
|
||||
let mut local_def_vars = ImMap::default();
|
||||
|
||||
let var =
|
||||
type_to_var(subs, next_rank, next_pools, cached_aliases, &def_type);
|
||||
for (symbol, loc_type) in let_con.def_types.iter() {
|
||||
let def_type = &loc_type.value;
|
||||
|
||||
local_def_vars.insert(
|
||||
*symbol,
|
||||
Located {
|
||||
value: var,
|
||||
region: loc_type.region,
|
||||
},
|
||||
);
|
||||
}
|
||||
let var =
|
||||
type_to_var(subs, next_rank, next_pools, cached_aliases, def_type);
|
||||
|
||||
// run solver in next pool
|
||||
|
||||
// Solve the assignments' constraints first.
|
||||
let new_state = solve(
|
||||
&new_env,
|
||||
state,
|
||||
next_rank,
|
||||
next_pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
&let_con.defs_constraint,
|
||||
);
|
||||
let young_mark = new_state.mark;
|
||||
let visit_mark = young_mark.next();
|
||||
let final_mark = visit_mark.next();
|
||||
|
||||
debug_assert_eq!(
|
||||
{
|
||||
let offenders = next_pools
|
||||
.get(next_rank)
|
||||
.iter()
|
||||
.filter(|var| {
|
||||
let current = subs.get_without_compacting(
|
||||
roc_types::subs::Variable::clone(var),
|
||||
);
|
||||
|
||||
current.rank.into_usize() > next_rank.into_usize()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let result = offenders.len();
|
||||
|
||||
if result > 0 {
|
||||
dbg!(
|
||||
&subs,
|
||||
&offenders,
|
||||
&let_con.def_types,
|
||||
&let_con.def_aliases
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
local_def_vars.insert(
|
||||
*symbol,
|
||||
Located {
|
||||
value: var,
|
||||
region: loc_type.region,
|
||||
},
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// pop pool
|
||||
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
|
||||
// Solve the assignments' constraints first.
|
||||
let State {
|
||||
env: saved_env,
|
||||
mark,
|
||||
} = solve(
|
||||
&env,
|
||||
state,
|
||||
next_rank,
|
||||
next_pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
&let_con.defs_constraint,
|
||||
);
|
||||
|
||||
next_pools.get_mut(next_rank).clear();
|
||||
let young_mark = mark;
|
||||
let visit_mark = young_mark.next();
|
||||
let final_mark = visit_mark.next();
|
||||
|
||||
// check that things went well
|
||||
debug_assert!({
|
||||
// NOTE the `subs.redundant` check is added for the uniqueness
|
||||
// inference, and does not come from elm. It's unclear whether this is
|
||||
// a bug with uniqueness inference (something is redundant that
|
||||
// shouldn't be) or that it just never came up in elm.
|
||||
let failing: Vec<_> = rigid_vars
|
||||
debug_assert_eq!(
|
||||
{
|
||||
let offenders = next_pools
|
||||
.get(next_rank)
|
||||
.iter()
|
||||
.filter(|&var| {
|
||||
!subs.redundant(*var)
|
||||
&& subs.get_without_compacting(*var).rank != Rank::NONE
|
||||
.filter(|var| {
|
||||
let current = subs.get_without_compacting(
|
||||
roc_types::subs::Variable::clone(var),
|
||||
);
|
||||
|
||||
current.rank.into_usize() > next_rank.into_usize()
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !failing.is_empty() {
|
||||
println!("Rigids {:?}", &rigid_vars);
|
||||
println!("Failing {:?}", failing);
|
||||
let result = offenders.len();
|
||||
|
||||
if result > 0 {
|
||||
dbg!(&subs, &offenders, &let_con.def_types, &let_con.def_aliases);
|
||||
}
|
||||
|
||||
failing.is_empty()
|
||||
});
|
||||
result
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
if !new_env.vars_by_symbol.contains_key(&symbol) {
|
||||
new_env.vars_by_symbol.insert(*symbol, loc_var.value);
|
||||
}
|
||||
// pop pool
|
||||
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
|
||||
|
||||
next_pools.get_mut(next_rank).clear();
|
||||
|
||||
// check that things went well
|
||||
debug_assert!({
|
||||
// NOTE the `subs.redundant` check is added for the uniqueness
|
||||
// inference, and does not come from elm. It's unclear whether this is
|
||||
// a bug with uniqueness inference (something is redundant that
|
||||
// shouldn't be) or that it just never came up in elm.
|
||||
let failing: Vec<_> = rigid_vars
|
||||
.iter()
|
||||
.filter(|&var| {
|
||||
!subs.redundant(*var)
|
||||
&& subs.get_without_compacting(*var).rank != Rank::NONE
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !failing.is_empty() {
|
||||
println!("Rigids {:?}", &rigid_vars);
|
||||
println!("Failing {:?}", failing);
|
||||
}
|
||||
|
||||
// Note that this vars_by_symbol is the one returned by the
|
||||
// previous call to solve()
|
||||
let temp_state = State {
|
||||
env: new_state.env,
|
||||
mark: final_mark,
|
||||
};
|
||||
failing.is_empty()
|
||||
});
|
||||
|
||||
// Now solve the body, using the new vars_by_symbol which includes
|
||||
// the assignments' name-to-variable mappings.
|
||||
let new_state = solve(
|
||||
&new_env,
|
||||
temp_state,
|
||||
rank,
|
||||
next_pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
&ret_con,
|
||||
);
|
||||
|
||||
for (symbol, loc_var) in local_def_vars {
|
||||
check_for_infinite_type(subs, problems, symbol, loc_var);
|
||||
let mut new_env = env.clone();
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
// when there are duplicates, keep the one from `env`
|
||||
if !new_env.vars_by_symbol.contains_key(&symbol) {
|
||||
new_env.vars_by_symbol.insert(*symbol, loc_var.value);
|
||||
}
|
||||
}
|
||||
|
||||
new_state
|
||||
// Note that this vars_by_symbol is the one returned by the
|
||||
// previous call to solve()
|
||||
let temp_state = State {
|
||||
env: saved_env,
|
||||
mark: final_mark,
|
||||
};
|
||||
|
||||
if next_rank.into_usize() < pools.len() {
|
||||
work_in_next_pools(pools)
|
||||
} else {
|
||||
// TODO shouldn't this grow the pool, it does in the elm source
|
||||
work_in_next_pools(&mut pools.clone())
|
||||
// Now solve the body, using the new vars_by_symbol which includes
|
||||
// the assignments' name-to-variable mappings.
|
||||
let new_state = solve(
|
||||
&new_env,
|
||||
temp_state,
|
||||
rank,
|
||||
next_pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
&ret_con,
|
||||
);
|
||||
|
||||
for (symbol, loc_var) in local_def_vars {
|
||||
check_for_infinite_type(subs, problems, symbol, loc_var);
|
||||
}
|
||||
|
||||
new_state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ pub type Res<T> = Result<T, Problem>;
|
||||
pub struct NodeId(u32);
|
||||
|
||||
impl NodeId {
|
||||
pub const NONE: NodeId = NodeId(std::u32::MAX);
|
||||
pub const NONE: NodeId = NodeId(u32::MAX);
|
||||
|
||||
pub fn as_index(self) -> usize {
|
||||
self.0 as usize
|
||||
@ -154,7 +154,7 @@ impl Nodes {
|
||||
|
||||
self.nodes.push(node);
|
||||
|
||||
if index < std::u32::MAX as usize {
|
||||
if index < u32::MAX as usize {
|
||||
Ok(NodeId(index as u32))
|
||||
} else {
|
||||
// u32::MAX is reserved for NodeId::NONE, so if we hit that on a
|
||||
|
Loading…
Reference in New Issue
Block a user