mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-18 19:10:18 +00:00
Merge remote-tracking branch 'origin/main' into expect-fx-codegen
This commit is contained in:
commit
a22e04361c
222 changed files with 10039 additions and 1945 deletions
|
@ -23,5 +23,5 @@ roc_error_macros = {path="../../error_macros"}
|
|||
roc_debug_flags = {path="../debug_flags"}
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.12.1", features = [ "bumpalo" ] }
|
||||
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
|
||||
static_assertions = "1.1.0"
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#![allow(clippy::manual_map)]
|
||||
|
||||
use crate::layout::{
|
||||
Builtin, CapturesNiche, ClosureRepresentation, LambdaName, LambdaSet, Layout, LayoutCache,
|
||||
LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant,
|
||||
Builtin, CapturesNiche, ClosureCallOptions, ClosureRepresentation, EnumDispatch, LambdaName,
|
||||
LambdaSet, Layout, LayoutCache, LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout,
|
||||
WrappedVariant,
|
||||
};
|
||||
use bumpalo::collections::{CollectIn, Vec};
|
||||
use bumpalo::Bump;
|
||||
|
@ -343,10 +344,16 @@ impl<'a> Proc<'a> {
|
|||
D::Doc: Clone,
|
||||
A: Clone,
|
||||
{
|
||||
let args_doc = self
|
||||
.args
|
||||
.iter()
|
||||
.map(|(_, symbol)| symbol_to_doc(alloc, *symbol));
|
||||
let args_doc = self.args.iter().map(|(layout, symbol)| {
|
||||
let arg_doc = symbol_to_doc(alloc, *symbol);
|
||||
if pretty_print_ir_symbols() {
|
||||
arg_doc
|
||||
.append(alloc.reflow(": "))
|
||||
.append(layout.to_doc(alloc, Parens::NotNeeded))
|
||||
} else {
|
||||
arg_doc
|
||||
}
|
||||
});
|
||||
|
||||
if pretty_print_ir_symbols() {
|
||||
alloc
|
||||
|
@ -621,6 +628,10 @@ impl<'a> Suspended<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.symbol_or_lambdas.is_empty()
|
||||
}
|
||||
|
||||
fn specialization(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
|
@ -654,8 +665,22 @@ enum PendingSpecializations<'a> {
|
|||
/// that we can give specializations we need to modules higher up in the dependency chain, so
|
||||
/// that they can start making specializations too
|
||||
Finding(Suspended<'a>),
|
||||
/// We are making specializations. If any new one comes up, we can just make it immediately
|
||||
Making,
|
||||
/// We are making specializations.
|
||||
/// If any new one comes up while specializing a body, we can do one of two things:
|
||||
/// - if the new specialization is for a symbol that is not in the current stack of symbols
|
||||
/// being specialized, make it immediately
|
||||
/// - if it is, we must suspend the specialization, and we'll do it once the stack is clear
|
||||
/// again.
|
||||
Making(Suspended<'a>),
|
||||
}
|
||||
|
||||
impl<'a> PendingSpecializations<'a> {
|
||||
fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
PendingSpecializations::Finding(suspended)
|
||||
| PendingSpecializations::Making(suspended) => suspended.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -940,6 +965,8 @@ pub struct Procs<'a> {
|
|||
pub runtime_errors: BumpMap<Symbol, &'a str>,
|
||||
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations<'a>>,
|
||||
symbol_specializations: SymbolSpecializations<'a>,
|
||||
/// The current set of functions under specialization.
|
||||
pub specialization_stack: Vec<'a, Symbol>,
|
||||
}
|
||||
|
||||
impl<'a> Procs<'a> {
|
||||
|
@ -954,8 +981,43 @@ impl<'a> Procs<'a> {
|
|||
runtime_errors: BumpMap::new_in(arena),
|
||||
externals_we_need: BumpMap::new_in(arena),
|
||||
symbol_specializations: Default::default(),
|
||||
specialization_stack: Vec::with_capacity_in(16, arena),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_active_specialization(&mut self, specialization: Symbol) {
|
||||
self.specialization_stack.push(specialization);
|
||||
}
|
||||
|
||||
fn pop_active_specialization(&mut self, specialization: Symbol) {
|
||||
let popped = self
|
||||
.specialization_stack
|
||||
.pop()
|
||||
.expect("specialization stack is empty");
|
||||
debug_assert_eq!(
|
||||
popped, specialization,
|
||||
"incorrect popped specialization: passed {:?}, but was {:?}",
|
||||
specialization, popped
|
||||
);
|
||||
}
|
||||
|
||||
/// If we need to specialize a function that is already in the stack, we must wait to do so
|
||||
/// until that function is popped off. That's because the type environment will be configured
|
||||
/// for the existing specialization on the stack.
|
||||
///
|
||||
/// For example, in
|
||||
///
|
||||
/// foo = \val, b -> if b then "done" else bar val
|
||||
/// bar = \_ -> foo {} True
|
||||
/// foo "" False
|
||||
///
|
||||
/// During the specialization of `foo : Str False -> Str`, we specialize `bar : Str -> Str`,
|
||||
/// which in turn needs a specialization of `foo : {} False -> Str`. However, we can't
|
||||
/// specialize both `foo : Str False -> Str` and `foo : {} False -> Str` at the same time, so
|
||||
/// the latter specialization must be deferred.
|
||||
fn symbol_needs_suspended_specialization(&self, specialization: Symbol) -> bool {
|
||||
self.specialization_stack.contains(&specialization)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -1045,8 +1107,14 @@ impl<'a> Procs<'a> {
|
|||
debug_assert!(layout.arguments.is_empty());
|
||||
}
|
||||
|
||||
match &mut self.pending_specializations {
|
||||
PendingSpecializations::Finding(suspended) => {
|
||||
let needs_suspended_specialization =
|
||||
self.symbol_needs_suspended_specialization(name.name());
|
||||
match (
|
||||
&mut self.pending_specializations,
|
||||
needs_suspended_specialization,
|
||||
) {
|
||||
(PendingSpecializations::Finding(suspended), _)
|
||||
| (PendingSpecializations::Making(suspended), true) => {
|
||||
// register the pending specialization, so this gets code genned later
|
||||
suspended.specialization(env.subs, name, layout, annotation);
|
||||
|
||||
|
@ -1080,7 +1148,7 @@ impl<'a> Procs<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
PendingSpecializations::Making => {
|
||||
(PendingSpecializations::Making(_), false) => {
|
||||
// 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!)
|
||||
|
@ -1178,7 +1246,8 @@ impl<'a> Procs<'a> {
|
|||
}
|
||||
|
||||
// If this is an imported symbol, let its home module make this specialization
|
||||
if env.is_imported_symbol(name.name()) {
|
||||
if env.is_imported_symbol(name.name()) || env.is_unloaded_derived_symbol(name.name(), self)
|
||||
{
|
||||
add_needed_external(self, env, fn_var, name);
|
||||
return;
|
||||
}
|
||||
|
@ -1190,11 +1259,17 @@ impl<'a> Procs<'a> {
|
|||
|
||||
// This should only be called when pending_specializations is Some.
|
||||
// Otherwise, it's being called in the wrong pass!
|
||||
match &mut self.pending_specializations {
|
||||
PendingSpecializations::Finding(suspended) => {
|
||||
let needs_suspended_specialization =
|
||||
self.symbol_needs_suspended_specialization(name.name());
|
||||
match (
|
||||
&mut self.pending_specializations,
|
||||
needs_suspended_specialization,
|
||||
) {
|
||||
(PendingSpecializations::Finding(suspended), _)
|
||||
| (PendingSpecializations::Making(suspended), true) => {
|
||||
suspended.specialization(env.subs, name, layout, fn_var);
|
||||
}
|
||||
PendingSpecializations::Making => {
|
||||
(PendingSpecializations::Making(_), false) => {
|
||||
let symbol = name;
|
||||
|
||||
let partial_proc_id = match self.partial_procs.symbol_to_id(symbol.name()) {
|
||||
|
@ -1207,7 +1282,7 @@ impl<'a> Procs<'a> {
|
|||
// (We had a bug around this before this system existed!)
|
||||
self.specialized.mark_in_progress(symbol.name(), layout);
|
||||
|
||||
// See https://github.com/rtfeldman/roc/issues/1600
|
||||
// See https://github.com/roc-lang/roc/issues/1600
|
||||
//
|
||||
// The annotation variable is the generic/lifted/top-level annotation.
|
||||
// It is connected to the variables of the function's body
|
||||
|
@ -1342,6 +1417,13 @@ impl<'a, 'i> Env<'a, 'i> {
|
|||
self.home == ModuleId::DERIVED_GEN
|
||||
&& symbol.module_id() == ModuleId::DERIVED_SYNTH
|
||||
&& !procs.partial_procs.contains_key(symbol)
|
||||
// TODO: locking to find the answer in the `Derived_gen` module is not great, since
|
||||
// Derived_gen also blocks other modules specializing. Improve this later.
|
||||
&& self
|
||||
.derived_module
|
||||
.lock()
|
||||
.expect("derived module is poisoned")
|
||||
.is_derived_def(symbol)
|
||||
}
|
||||
|
||||
/// Unifies two variables and performs lambda set compaction.
|
||||
|
@ -2501,7 +2583,7 @@ fn patterns_to_when<'a>(
|
|||
// are only stores anyway, no branches.
|
||||
//
|
||||
// NOTE this fails if the pattern contains rigid variables,
|
||||
// see https://github.com/rtfeldman/roc/issues/786
|
||||
// see https://github.com/roc-lang/roc/issues/786
|
||||
// this must be fixed when moving exhaustiveness checking to the new canonical AST
|
||||
for (pattern_var, annotated_mark, pattern) in patterns.into_iter() {
|
||||
if annotated_mark.exhaustive.is_non_exhaustive(env.subs) {
|
||||
|
@ -2739,26 +2821,51 @@ pub fn specialize_all<'a>(
|
|||
specializations_for_host: HostSpecializations<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
) -> Procs<'a> {
|
||||
for externals in externals_others_need {
|
||||
specialize_external_specializations(env, &mut procs, layout_cache, externals);
|
||||
}
|
||||
|
||||
// When calling from_can, pending_specializations should be unavailable.
|
||||
// This must be a single pass, and we must not add any more entries to it!
|
||||
let pending_specializations = std::mem::replace(
|
||||
&mut procs.pending_specializations,
|
||||
PendingSpecializations::Making,
|
||||
PendingSpecializations::Making(Suspended::new_in(env.arena)),
|
||||
);
|
||||
|
||||
// Add all of our existing pending specializations.
|
||||
match pending_specializations {
|
||||
PendingSpecializations::Making => {}
|
||||
PendingSpecializations::Finding(suspended) => {
|
||||
specialize_suspended(env, &mut procs, layout_cache, suspended)
|
||||
}
|
||||
PendingSpecializations::Making(suspended) => {
|
||||
debug_assert!(
|
||||
suspended.is_empty(),
|
||||
"suspended specializations cannot ever start off non-empty when making"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Specialize all the symbols everyone else needs.
|
||||
for externals in externals_others_need {
|
||||
specialize_external_specializations(env, &mut procs, layout_cache, externals);
|
||||
}
|
||||
|
||||
// Specialize any symbols the host needs.
|
||||
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
|
||||
|
||||
// Now, we must go through and continuously complete any new suspended specializations that were
|
||||
// discovered in specializing the other demanded symbols.
|
||||
while !procs.pending_specializations.is_empty() {
|
||||
let pending_specializations = std::mem::replace(
|
||||
&mut procs.pending_specializations,
|
||||
PendingSpecializations::Making(Suspended::new_in(env.arena)),
|
||||
);
|
||||
match pending_specializations {
|
||||
PendingSpecializations::Making(suspended) => {
|
||||
specialize_suspended(env, &mut procs, layout_cache, suspended);
|
||||
}
|
||||
PendingSpecializations::Finding(_) => {
|
||||
internal_error!("should not have this variant after making specializations")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
procs.symbol_specializations.is_empty(),
|
||||
"{:?}",
|
||||
|
@ -3127,6 +3234,8 @@ fn specialize_external<'a>(
|
|||
closure: opt_closure_layout,
|
||||
ret_layout,
|
||||
} => {
|
||||
let mut proc_args = Vec::from_iter_in(proc_args.iter().copied(), env.arena);
|
||||
|
||||
// unpack the closure symbols, if any
|
||||
match (opt_closure_layout, captured_symbols) {
|
||||
(Some(closure_layout), CapturedSymbols::Captured(captured)) => {
|
||||
|
@ -3196,6 +3305,9 @@ fn specialize_external<'a>(
|
|||
ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => {
|
||||
// captured variables are in symbol-alphabetic order, but now we want
|
||||
// them ordered by their alignment requirements
|
||||
//
|
||||
// TODO: sort only the fields and apply the found permutation to the symbols
|
||||
// TODO: can we move this ordering to `layout_for_member`?
|
||||
let mut combined = Vec::from_iter_in(
|
||||
captured.iter().map(|(x, _)| x).zip(field_layouts.iter()),
|
||||
env.arena,
|
||||
|
@ -3210,19 +3322,25 @@ fn specialize_external<'a>(
|
|||
size2.cmp(&size1)
|
||||
});
|
||||
|
||||
let ordered_field_layouts = Vec::from_iter_in(
|
||||
combined.iter().map(|(_, layout)| **layout),
|
||||
env.arena,
|
||||
);
|
||||
let ordered_field_layouts = ordered_field_layouts.into_bump_slice();
|
||||
|
||||
debug_assert_eq!(
|
||||
captured.len(),
|
||||
field_layouts.len(),
|
||||
ordered_field_layouts.len(),
|
||||
"{:?} captures {:?} but has layout {:?}",
|
||||
lambda_name,
|
||||
&captured,
|
||||
&field_layouts
|
||||
&ordered_field_layouts
|
||||
);
|
||||
|
||||
for (index, (symbol, layout)) in combined.iter().enumerate() {
|
||||
let expr = Expr::StructAtIndex {
|
||||
index: index as _,
|
||||
field_layouts,
|
||||
field_layouts: ordered_field_layouts,
|
||||
structure: Symbol::ARG_CLOSURE,
|
||||
};
|
||||
|
||||
|
@ -3235,51 +3353,37 @@ fn specialize_external<'a>(
|
|||
env.arena.alloc(specialized_body),
|
||||
);
|
||||
}
|
||||
// let symbol = captured[0].0;
|
||||
//
|
||||
// substitute_in_exprs(
|
||||
// env.arena,
|
||||
// &mut specialized_body,
|
||||
// symbol,
|
||||
// Symbol::ARG_CLOSURE,
|
||||
// );
|
||||
}
|
||||
|
||||
ClosureRepresentation::Other(layout) => match layout {
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
// just ignore this value
|
||||
// IDEA don't pass this value in the future
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
|
||||
// just ignore this value
|
||||
// IDEA don't pass this value in the future
|
||||
}
|
||||
other => {
|
||||
// NOTE other values always should be wrapped in a 1-element record
|
||||
unreachable!(
|
||||
"{:?} is not a valid closure data representation",
|
||||
other
|
||||
)
|
||||
}
|
||||
},
|
||||
ClosureRepresentation::UnwrappedCapture(_layout) => {
|
||||
debug_assert_eq!(captured.len(), 1);
|
||||
let (captured_symbol, _captured_layout) = captured[0];
|
||||
|
||||
// The capture set is unwrapped, so simply replace the closure argument
|
||||
// to the function with the unwrapped capture name.
|
||||
let captured_symbol = get_specialized_name(captured_symbol);
|
||||
let closure_arg = proc_args.last_mut().unwrap();
|
||||
debug_assert_eq!(closure_arg.1, Symbol::ARG_CLOSURE);
|
||||
closure_arg.1 = captured_symbol;
|
||||
}
|
||||
|
||||
ClosureRepresentation::EnumDispatch(_) => {
|
||||
// just ignore this value, since it's not a capture
|
||||
// IDEA don't pass this value in the future
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, CapturedSymbols::None) | (None, CapturedSymbols::Captured([])) => {}
|
||||
_ => unreachable!("to closure or not to closure?"),
|
||||
}
|
||||
|
||||
let proc_args: Vec<_> = proc_args
|
||||
.iter()
|
||||
.map(|&(layout, symbol)| {
|
||||
// Grab the specialization symbol, if it exists.
|
||||
let symbol = procs
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
.unwrap_or(symbol);
|
||||
|
||||
(layout, symbol)
|
||||
})
|
||||
.collect_in(env.arena);
|
||||
proc_args.iter_mut().for_each(|(_layout, symbol)| {
|
||||
// Grab the specialization symbol, if it exists.
|
||||
*symbol = procs
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
.unwrap_or(*symbol);
|
||||
});
|
||||
|
||||
// reset subs, so we don't get type errors when specializing for a different signature
|
||||
layout_cache.rollback_to(cache_snapshot);
|
||||
|
@ -3566,6 +3670,8 @@ where
|
|||
let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation;
|
||||
instantiate_rigids(env.subs, annotation_var);
|
||||
|
||||
procs.push_active_specialization(proc_name.name());
|
||||
|
||||
let specialized = specialize_external(
|
||||
env,
|
||||
procs,
|
||||
|
@ -3576,6 +3682,8 @@ where
|
|||
partial_proc_id,
|
||||
);
|
||||
|
||||
procs.pop_active_specialization(proc_name.name());
|
||||
|
||||
match specialized {
|
||||
Ok(proc) => {
|
||||
// when successful, the layout after unification should be the layout before unification
|
||||
|
@ -3869,7 +3977,7 @@ pub fn with_hole<'a>(
|
|||
)
|
||||
}
|
||||
Tag {
|
||||
variant_var,
|
||||
tag_union_var: variant_var,
|
||||
name: tag_name,
|
||||
arguments: args,
|
||||
..
|
||||
|
@ -5396,29 +5504,42 @@ where
|
|||
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
ClosureRepresentation::Other(Layout::Builtin(Builtin::Bool)) => {
|
||||
debug_assert_eq!(symbols.len(), 0);
|
||||
ClosureRepresentation::UnwrappedCapture(_layout) => {
|
||||
debug_assert_eq!(symbols.len(), 1);
|
||||
|
||||
debug_assert_eq!(lambda_set.set.len(), 2);
|
||||
let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name();
|
||||
let expr = Expr::Literal(Literal::Bool(tag_id));
|
||||
let mut symbols = symbols;
|
||||
let (captured_symbol, _) = symbols.next().unwrap();
|
||||
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
// The capture set is unwrapped, so just replaced the assigned capture symbol with the
|
||||
// only capture.
|
||||
let mut hole = hole.clone();
|
||||
substitute_in_exprs(env.arena, &mut hole, assigned, *captured_symbol);
|
||||
hole
|
||||
}
|
||||
ClosureRepresentation::Other(Layout::Builtin(Builtin::Int(IntWidth::U8))) => {
|
||||
debug_assert_eq!(symbols.len(), 0);
|
||||
ClosureRepresentation::EnumDispatch(repr) => match repr {
|
||||
EnumDispatch::Bool => {
|
||||
debug_assert_eq!(symbols.len(), 0);
|
||||
|
||||
debug_assert!(lambda_set.set.len() > 2);
|
||||
let tag_id = lambda_set
|
||||
.iter_set()
|
||||
.position(|s| s.name() == name.name())
|
||||
.unwrap() as u8;
|
||||
debug_assert_eq!(lambda_set.len(), 2);
|
||||
let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name();
|
||||
let expr = Expr::Literal(Literal::Bool(tag_id));
|
||||
|
||||
let expr = Expr::Literal(Literal::Byte(tag_id));
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
EnumDispatch::U8 => {
|
||||
debug_assert_eq!(symbols.len(), 0);
|
||||
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
debug_assert!(lambda_set.len() > 2);
|
||||
let tag_id = lambda_set
|
||||
.iter_set()
|
||||
.position(|s| s.name() == name.name())
|
||||
.unwrap() as u8;
|
||||
|
||||
let expr = Expr::Literal(Literal::Byte(tag_id));
|
||||
|
||||
Stmt::Let(assigned, expr, lambda_set_layout, hole)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result
|
||||
|
@ -5692,7 +5813,7 @@ fn tag_union_to_function<'a>(
|
|||
}
|
||||
|
||||
let loc_body = Loc::at_zero(roc_can::expr::Expr::Tag {
|
||||
variant_var: return_variable,
|
||||
tag_union_var: return_variable,
|
||||
name: tag_name,
|
||||
arguments: loc_expr_args,
|
||||
ext_var,
|
||||
|
@ -5857,10 +5978,7 @@ fn register_capturing_closure<'a>(
|
|||
Content::Structure(FlatType::Func(_, closure_var, _)) => {
|
||||
match LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info) {
|
||||
Ok(lambda_set) => {
|
||||
if let Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} = lambda_set.runtime_representation()
|
||||
{
|
||||
if lambda_set.is_represented().is_none() {
|
||||
CapturedSymbols::None
|
||||
} else {
|
||||
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
|
||||
|
@ -5869,7 +5987,7 @@ fn register_capturing_closure<'a>(
|
|||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
|
||||
// just allow this. see https://github.com/roc-lang/roc/issues/1585
|
||||
if captured_symbols.is_empty() {
|
||||
CapturedSymbols::None
|
||||
} else {
|
||||
|
@ -7162,7 +7280,7 @@ fn can_reuse_symbol<'a>(
|
|||
|
||||
if arguments.contains(&symbol) {
|
||||
Value(symbol)
|
||||
} else if env.is_imported_symbol(symbol) {
|
||||
} else if env.is_imported_symbol(symbol) || env.is_unloaded_derived_symbol(symbol, procs) {
|
||||
Imported(symbol)
|
||||
} else if procs.partial_procs.contains_key(symbol) {
|
||||
LocalFunction(symbol)
|
||||
|
@ -7334,7 +7452,10 @@ fn specialize_symbol<'a>(
|
|||
match procs.get_partial_proc(original) {
|
||||
None => {
|
||||
match arg_var {
|
||||
Some(arg_var) if env.is_imported_symbol(original) => {
|
||||
Some(arg_var)
|
||||
if env.is_imported_symbol(original)
|
||||
|| env.is_unloaded_derived_symbol(original, procs) =>
|
||||
{
|
||||
let raw = match layout_cache.raw_from_var(env.arena, arg_var, env.subs) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return_on_layout_error_help!(env, e, "specialize_symbol"),
|
||||
|
@ -7603,7 +7724,7 @@ fn build_call<'a>(
|
|||
Stmt::Let(assigned, Expr::Call(call), return_layout, hole)
|
||||
}
|
||||
|
||||
/// See https://github.com/rtfeldman/roc/issues/1549
|
||||
/// See https://github.com/roc-lang/roc/issues/1549
|
||||
///
|
||||
/// What happened is that a function has a type error, but the arguments are not processed.
|
||||
/// That means specializations were missing. Normally that is not a problem, but because
|
||||
|
@ -7936,8 +8057,14 @@ fn call_by_name_help<'a>(
|
|||
debug_assert!(top_level_layout.arguments.is_empty());
|
||||
}
|
||||
|
||||
match &mut procs.pending_specializations {
|
||||
PendingSpecializations::Finding(suspended) => {
|
||||
let needs_suspended_specialization =
|
||||
procs.symbol_needs_suspended_specialization(proc_name.name());
|
||||
match (
|
||||
&mut procs.pending_specializations,
|
||||
needs_suspended_specialization,
|
||||
) {
|
||||
(PendingSpecializations::Finding(suspended), _)
|
||||
| (PendingSpecializations::Making(suspended), true) => {
|
||||
debug_assert!(!env.is_imported_symbol(proc_name.name()));
|
||||
|
||||
// register the pending specialization, so this gets code genned later
|
||||
|
@ -7966,7 +8093,7 @@ fn call_by_name_help<'a>(
|
|||
hole,
|
||||
)
|
||||
}
|
||||
PendingSpecializations::Making => {
|
||||
(PendingSpecializations::Making(_), false) => {
|
||||
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name.name());
|
||||
|
||||
let field_symbols = field_symbols.into_bump_slice();
|
||||
|
@ -8100,8 +8227,13 @@ fn call_by_name_module_thunk<'a>(
|
|||
debug_assert!(top_level_layout.arguments.is_empty());
|
||||
}
|
||||
|
||||
match &mut procs.pending_specializations {
|
||||
PendingSpecializations::Finding(suspended) => {
|
||||
let needs_suspended_specialization = procs.symbol_needs_suspended_specialization(proc_name);
|
||||
match (
|
||||
&mut procs.pending_specializations,
|
||||
needs_suspended_specialization,
|
||||
) {
|
||||
(PendingSpecializations::Finding(suspended), _)
|
||||
| (PendingSpecializations::Making(suspended), true) => {
|
||||
debug_assert!(!env.is_imported_symbol(proc_name));
|
||||
|
||||
// register the pending specialization, so this gets code genned later
|
||||
|
@ -8114,7 +8246,7 @@ fn call_by_name_module_thunk<'a>(
|
|||
|
||||
force_thunk(env, proc_name, inner_layout, assigned, hole)
|
||||
}
|
||||
PendingSpecializations::Making => {
|
||||
(PendingSpecializations::Making(_), false) => {
|
||||
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
|
||||
|
||||
match opt_partial_proc {
|
||||
|
@ -9166,9 +9298,9 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
|
|||
where
|
||||
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
|
||||
{
|
||||
match lambda_set.runtime_representation() {
|
||||
Layout::VOID => empty_lambda_set_error(),
|
||||
Layout::Union(union_layout) => {
|
||||
match lambda_set.call_by_name_options() {
|
||||
ClosureCallOptions::Void => empty_lambda_set_error(),
|
||||
ClosureCallOptions::Union(union_layout) => {
|
||||
let closure_tag_id_symbol = env.unique_symbol();
|
||||
|
||||
let result = lowlevel_union_lambda_set_to_switch(
|
||||
|
@ -9197,7 +9329,7 @@ where
|
|||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
Layout::Struct { .. } => match lambda_set.iter_set().next() {
|
||||
ClosureCallOptions::Struct { .. } => match lambda_set.iter_set().next() {
|
||||
Some(lambda_name) => {
|
||||
let call_spec_id = env.next_call_specialization_id();
|
||||
let update_mode = env.next_update_mode_id();
|
||||
|
@ -9222,39 +9354,58 @@ where
|
|||
hole.clone()
|
||||
}
|
||||
},
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
ClosureCallOptions::UnwrappedCapture(_) => {
|
||||
let lambda_name = lambda_set
|
||||
.iter_set()
|
||||
.next()
|
||||
.expect("no function in lambda set");
|
||||
|
||||
lowlevel_enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Bool),
|
||||
let call_spec_id = env.next_call_specialization_id();
|
||||
let update_mode = env.next_update_mode_id();
|
||||
let call = to_lowlevel_call((
|
||||
lambda_name,
|
||||
closure_data_symbol,
|
||||
lambda_set.is_represented(),
|
||||
to_lowlevel_call,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
call_spec_id,
|
||||
update_mode,
|
||||
));
|
||||
|
||||
lowlevel_enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)),
|
||||
closure_data_symbol,
|
||||
lambda_set.is_represented(),
|
||||
to_lowlevel_call,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
|
||||
}
|
||||
other => todo!("{:?}", other),
|
||||
ClosureCallOptions::EnumDispatch(repr) => match repr {
|
||||
EnumDispatch::Bool => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
|
||||
lowlevel_enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Bool),
|
||||
closure_data_symbol,
|
||||
lambda_set.is_represented(),
|
||||
to_lowlevel_call,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
EnumDispatch::U8 => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
|
||||
lowlevel_enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)),
|
||||
closure_data_symbol,
|
||||
lambda_set.is_represented(),
|
||||
to_lowlevel_call,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9345,15 +9496,14 @@ fn match_on_lambda_set<'a>(
|
|||
assigned: Symbol,
|
||||
hole: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
match lambda_set.runtime_representation() {
|
||||
Layout::VOID => empty_lambda_set_error(),
|
||||
Layout::Union(union_layout) => {
|
||||
match lambda_set.call_by_name_options() {
|
||||
ClosureCallOptions::Void => empty_lambda_set_error(),
|
||||
ClosureCallOptions::Union(union_layout) => {
|
||||
let closure_tag_id_symbol = env.unique_symbol();
|
||||
|
||||
let result = union_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set,
|
||||
Layout::Union(union_layout),
|
||||
closure_tag_id_symbol,
|
||||
union_layout.tag_id_layout(),
|
||||
closure_data_symbol,
|
||||
|
@ -9377,9 +9527,9 @@ fn match_on_lambda_set<'a>(
|
|||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
Layout::Struct {
|
||||
ClosureCallOptions::Struct {
|
||||
field_layouts,
|
||||
field_order_hash,
|
||||
field_order_hash: _,
|
||||
} => {
|
||||
let function_symbol = match lambda_set.iter_set().next() {
|
||||
Some(function_symbol) => function_symbol,
|
||||
|
@ -9403,10 +9553,6 @@ fn match_on_lambda_set<'a>(
|
|||
_ => ClosureInfo::Captures {
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
closure_data_layout: Layout::Struct {
|
||||
field_layouts,
|
||||
field_order_hash,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -9421,15 +9567,21 @@ fn match_on_lambda_set<'a>(
|
|||
hole,
|
||||
)
|
||||
}
|
||||
Layout::Builtin(Builtin::Bool) => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
ClosureCallOptions::UnwrappedCapture(_) => {
|
||||
let function_symbol = lambda_set
|
||||
.iter_set()
|
||||
.next()
|
||||
.expect("no function in lambda set");
|
||||
|
||||
enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Bool),
|
||||
let closure_info = ClosureInfo::Captures {
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
};
|
||||
|
||||
union_lambda_set_branch_help(
|
||||
env,
|
||||
function_symbol,
|
||||
closure_info,
|
||||
argument_symbols,
|
||||
argument_layouts,
|
||||
return_layout,
|
||||
|
@ -9437,23 +9589,38 @@ fn match_on_lambda_set<'a>(
|
|||
hole,
|
||||
)
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
ClosureCallOptions::EnumDispatch(repr) => match repr {
|
||||
EnumDispatch::Bool => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
|
||||
enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)),
|
||||
closure_data_symbol,
|
||||
argument_symbols,
|
||||
argument_layouts,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
other => todo!("{:?}", other),
|
||||
enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Bool),
|
||||
argument_symbols,
|
||||
argument_layouts,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
EnumDispatch::U8 => {
|
||||
let closure_tag_id_symbol = closure_data_symbol;
|
||||
|
||||
enum_lambda_set_to_switch(
|
||||
env,
|
||||
lambda_set.iter_set(),
|
||||
closure_tag_id_symbol,
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)),
|
||||
argument_symbols,
|
||||
argument_layouts,
|
||||
return_layout,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9461,7 +9628,6 @@ fn match_on_lambda_set<'a>(
|
|||
fn union_lambda_set_to_switch<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
lambda_set: LambdaSet<'a>,
|
||||
closure_layout: Layout<'a>,
|
||||
closure_tag_id_symbol: Symbol,
|
||||
closure_tag_id_layout: Layout<'a>,
|
||||
closure_data_symbol: Symbol,
|
||||
|
@ -9471,7 +9637,7 @@ fn union_lambda_set_to_switch<'a>(
|
|||
assigned: Symbol,
|
||||
hole: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
if lambda_set.set.is_empty() {
|
||||
if lambda_set.is_empty() {
|
||||
// NOTE this can happen if there is a type error somewhere. Since the lambda set is empty,
|
||||
// there is really nothing we can do here. We generate a runtime error here which allows
|
||||
// code gen to proceed. We then assume that we hit another (more descriptive) error before
|
||||
|
@ -9481,7 +9647,7 @@ fn union_lambda_set_to_switch<'a>(
|
|||
|
||||
let join_point_id = JoinPointId(env.unique_symbol());
|
||||
|
||||
let mut branches = Vec::with_capacity_in(lambda_set.set.len(), env.arena);
|
||||
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
|
||||
|
||||
for (i, lambda_name) in lambda_set.iter_set().enumerate() {
|
||||
let closure_info = if lambda_name.no_captures() {
|
||||
|
@ -9490,7 +9656,6 @@ fn union_lambda_set_to_switch<'a>(
|
|||
ClosureInfo::Captures {
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
closure_data_layout: closure_layout,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9560,11 +9725,10 @@ fn union_lambda_set_branch<'a>(
|
|||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ClosureInfo<'a> {
|
||||
Captures {
|
||||
closure_data_symbol: Symbol,
|
||||
/// The layout of this closure variant
|
||||
closure_data_layout: Layout<'a>,
|
||||
/// The whole lambda set representation this closure is a variant of
|
||||
lambda_set: LambdaSet<'a>,
|
||||
},
|
||||
|
@ -9586,34 +9750,21 @@ fn union_lambda_set_branch_help<'a>(
|
|||
ClosureInfo::Captures {
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
closure_data_layout,
|
||||
} => match closure_data_layout {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
}
|
||||
| Layout::Builtin(Builtin::Bool)
|
||||
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
|
||||
(argument_layouts_slice, argument_symbols_slice)
|
||||
}
|
||||
_ => {
|
||||
// extend layouts with the layout of the closure environment
|
||||
let mut argument_layouts =
|
||||
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
|
||||
argument_layouts.extend(argument_layouts_slice);
|
||||
argument_layouts.push(Layout::LambdaSet(lambda_set));
|
||||
|
||||
} => {
|
||||
let argument_layouts =
|
||||
lambda_set.extend_argument_list(env.arena, argument_layouts_slice);
|
||||
let argument_symbols = if argument_layouts.len() > argument_layouts_slice.len() {
|
||||
// extend symbols with the symbol of the closure environment
|
||||
let mut argument_symbols =
|
||||
Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena);
|
||||
argument_symbols.extend(argument_symbols_slice);
|
||||
argument_symbols.push(closure_data_symbol);
|
||||
|
||||
(
|
||||
argument_layouts.into_bump_slice(),
|
||||
argument_symbols.into_bump_slice(),
|
||||
)
|
||||
}
|
||||
},
|
||||
argument_symbols.into_bump_slice()
|
||||
} else {
|
||||
argument_symbols_slice
|
||||
};
|
||||
(argument_layouts, argument_symbols)
|
||||
}
|
||||
ClosureInfo::DoesNotCapture => {
|
||||
// sometimes unification causes a function that does not itself capture anything
|
||||
// to still get a lambda set that does store information. We must not pass a closure
|
||||
|
@ -9637,13 +9788,14 @@ fn union_lambda_set_branch_help<'a>(
|
|||
build_call(env, call, assigned, *return_layout, hole)
|
||||
}
|
||||
|
||||
/// Switches over a enum lambda set, which may dispatch to different functions, none of which
|
||||
/// capture.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn enum_lambda_set_to_switch<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
lambda_set: impl ExactSizeIterator<Item = LambdaName<'a>>,
|
||||
closure_tag_id_symbol: Symbol,
|
||||
closure_tag_id_layout: Layout<'a>,
|
||||
closure_data_symbol: Symbol,
|
||||
argument_symbols: &'a [Symbol],
|
||||
argument_layouts: &'a [Layout<'a>],
|
||||
return_layout: &'a Layout<'a>,
|
||||
|
@ -9656,15 +9808,11 @@ fn enum_lambda_set_to_switch<'a>(
|
|||
|
||||
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
|
||||
|
||||
let closure_layout = closure_tag_id_layout;
|
||||
|
||||
for (i, lambda_name) in lambda_set.into_iter().enumerate() {
|
||||
let stmt = enum_lambda_set_branch(
|
||||
env,
|
||||
join_point_id,
|
||||
lambda_name,
|
||||
closure_data_symbol,
|
||||
closure_layout,
|
||||
argument_symbols,
|
||||
argument_layouts,
|
||||
return_layout,
|
||||
|
@ -9700,15 +9848,14 @@ fn enum_lambda_set_to_switch<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// A branch for an enum lambda set branch dispatch, which never capture!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn enum_lambda_set_branch<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
join_point_id: JoinPointId,
|
||||
lambda_name: LambdaName<'a>,
|
||||
closure_data_symbol: Symbol,
|
||||
closure_data_layout: Layout<'a>,
|
||||
argument_symbols_slice: &'a [Symbol],
|
||||
argument_layouts_slice: &'a [Layout<'a>],
|
||||
argument_symbols: &'a [Symbol],
|
||||
argument_layouts: &'a [Layout<'a>],
|
||||
return_layout: &'a Layout<'a>,
|
||||
) -> Stmt<'a> {
|
||||
let result_symbol = env.unique_symbol();
|
||||
|
@ -9717,34 +9864,6 @@ fn enum_lambda_set_branch<'a>(
|
|||
|
||||
let assigned = result_symbol;
|
||||
|
||||
let (argument_layouts, argument_symbols) = match closure_data_layout {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
}
|
||||
| Layout::Builtin(Builtin::Bool)
|
||||
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
|
||||
(argument_layouts_slice, argument_symbols_slice)
|
||||
}
|
||||
_ => {
|
||||
// extend layouts with the layout of the closure environment
|
||||
let mut argument_layouts =
|
||||
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
|
||||
argument_layouts.extend(argument_layouts_slice);
|
||||
argument_layouts.push(closure_data_layout);
|
||||
|
||||
// extend symbols with the symbol of the closure environment
|
||||
let mut argument_symbols =
|
||||
Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena);
|
||||
argument_symbols.extend(argument_symbols_slice);
|
||||
argument_symbols.push(closure_data_symbol);
|
||||
|
||||
(
|
||||
argument_layouts.into_bump_slice(),
|
||||
argument_symbols.into_bump_slice(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let call = self::Call {
|
||||
call_type: CallType::ByName {
|
||||
name: lambda_name,
|
||||
|
|
|
@ -263,7 +263,7 @@ pub enum Layout<'a> {
|
|||
/// so keep a hash of the record order for disambiguation. This still of course may result
|
||||
/// in collisions, but it's unlikely.
|
||||
///
|
||||
/// See also https://github.com/rtfeldman/roc/issues/2535.
|
||||
/// See also https://github.com/roc-lang/roc/issues/2535.
|
||||
field_order_hash: FieldOrderHash,
|
||||
field_layouts: &'a [Layout<'a>],
|
||||
},
|
||||
|
@ -731,7 +731,7 @@ impl std::fmt::Debug for LambdaSet<'_> {
|
|||
/// By recording the captures layouts this lambda expects in its identifier, we can distinguish
|
||||
/// between such differences when constructing closure capture data.
|
||||
///
|
||||
/// See also https://github.com/rtfeldman/roc/issues/3336.
|
||||
/// See also https://github.com/roc-lang/roc/issues/3336.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct CapturesNiche<'a>(&'a [Layout<'a>]);
|
||||
|
||||
|
@ -783,28 +783,56 @@ impl<'a> LambdaName<'a> {
|
|||
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct LambdaSet<'a> {
|
||||
/// collection of function names and their closure arguments
|
||||
pub set: &'a [(Symbol, &'a [Layout<'a>])],
|
||||
set: &'a [(Symbol, &'a [Layout<'a>])],
|
||||
/// how the closure will be represented at runtime
|
||||
representation: &'a Layout<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EnumDispatch {
|
||||
Bool,
|
||||
U8,
|
||||
}
|
||||
|
||||
/// representation of the closure *for a particular function*
|
||||
#[derive(Debug)]
|
||||
pub enum ClosureRepresentation<'a> {
|
||||
/// the closure is represented as a union. Includes the tag ID!
|
||||
/// The closure is represented as a union. Includes the tag ID!
|
||||
/// Each variant is a different function, and its payloads are the captures.
|
||||
Union {
|
||||
alphabetic_order_fields: &'a [Layout<'a>],
|
||||
closure_name: Symbol,
|
||||
tag_id: TagIdIntType,
|
||||
union_layout: UnionLayout<'a>,
|
||||
},
|
||||
/// The closure is represented as a struct. The layouts are sorted
|
||||
/// alphabetically by the identifier that is captured.
|
||||
/// The closure is one function, whose captures are represented as a struct.
|
||||
/// The layouts are sorted alphabetically by the identifier that is captured.
|
||||
///
|
||||
/// We MUST sort these according to their stack size before code gen!
|
||||
AlphabeticOrderStruct(&'a [Layout<'a>]),
|
||||
/// the representation is anything but a union
|
||||
Other(Layout<'a>),
|
||||
/// The closure is one function that captures a single identifier, whose value is unwrapped.
|
||||
UnwrappedCapture(Layout<'a>),
|
||||
/// The closure dispatches to multiple functions, but none of them capture anything, so this is
|
||||
/// a boolean or integer flag.
|
||||
EnumDispatch(EnumDispatch),
|
||||
}
|
||||
|
||||
/// How the closure should be seen when determining a call-by-name.
|
||||
#[derive(Debug)]
|
||||
pub enum ClosureCallOptions<'a> {
|
||||
/// This is an empty lambda set, dispatching is an error
|
||||
Void,
|
||||
/// One of a few capturing functions can be called to
|
||||
Union(UnionLayout<'a>),
|
||||
/// The closure is one function, whose captures are represented as a struct.
|
||||
Struct {
|
||||
field_layouts: &'a [Layout<'a>],
|
||||
field_order_hash: FieldOrderHash,
|
||||
},
|
||||
/// The closure is one function that captures a single identifier, whose value is unwrapped.
|
||||
UnwrappedCapture(Layout<'a>),
|
||||
/// The closure dispatches to multiple possible functions, none of which capture.
|
||||
EnumDispatch(EnumDispatch),
|
||||
}
|
||||
|
||||
impl<'a> LambdaSet<'a> {
|
||||
|
@ -818,13 +846,17 @@ impl<'a> LambdaSet<'a> {
|
|||
}
|
||||
|
||||
pub fn is_represented(&self) -> Option<Layout<'a>> {
|
||||
match self.representation {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
if self.has_unwrapped_capture_repr() {
|
||||
Some(*self.representation)
|
||||
} else if self.has_enum_dispatch_repr() {
|
||||
None
|
||||
} else {
|
||||
match self.representation {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => None,
|
||||
repr => Some(*repr),
|
||||
}
|
||||
| Layout::Builtin(Builtin::Bool)
|
||||
| Layout::Builtin(Builtin::Int(..)) => None,
|
||||
repr => Some(*repr),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,6 +867,16 @@ impl<'a> LambdaSet<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.set.is_empty()
|
||||
}
|
||||
|
||||
pub fn layout_for_member_with_lambda_name(
|
||||
&self,
|
||||
lambda_name: LambdaName,
|
||||
|
@ -923,6 +965,11 @@ impl<'a> LambdaSet<'a> {
|
|||
where
|
||||
F: Fn(Symbol, &[Layout]) -> bool,
|
||||
{
|
||||
if self.has_unwrapped_capture_repr() {
|
||||
// Only one function, that captures one identifier.
|
||||
return ClosureRepresentation::UnwrappedCapture(*self.representation);
|
||||
}
|
||||
|
||||
match self.representation {
|
||||
Layout::Union(union) => {
|
||||
// here we rely on the fact that a union in a closure would be stored in a one-element record.
|
||||
|
@ -1004,7 +1051,58 @@ impl<'a> LambdaSet<'a> {
|
|||
|
||||
ClosureRepresentation::AlphabeticOrderStruct(fields)
|
||||
}
|
||||
_ => ClosureRepresentation::Other(*self.representation),
|
||||
layout => {
|
||||
debug_assert!(self.has_enum_dispatch_repr(),);
|
||||
let enum_repr = match layout {
|
||||
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
|
||||
other => internal_error!("Invalid layout for enum dispatch: {:?}", other),
|
||||
};
|
||||
ClosureRepresentation::EnumDispatch(enum_repr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_unwrapped_capture_repr(&self) -> bool {
|
||||
self.set.len() == 1 && self.set[0].1.len() == 1
|
||||
}
|
||||
|
||||
fn has_enum_dispatch_repr(&self) -> bool {
|
||||
self.set.len() > 1 && self.set.iter().all(|(_, captures)| captures.is_empty())
|
||||
}
|
||||
|
||||
pub fn call_by_name_options(&self) -> ClosureCallOptions<'a> {
|
||||
if self.has_unwrapped_capture_repr() {
|
||||
return ClosureCallOptions::UnwrappedCapture(*self.representation);
|
||||
}
|
||||
|
||||
match self.representation {
|
||||
Layout::Union(union_layout) => {
|
||||
if self.representation == &Layout::VOID {
|
||||
debug_assert!(self.set.is_empty());
|
||||
return ClosureCallOptions::Void;
|
||||
}
|
||||
ClosureCallOptions::Union(*union_layout)
|
||||
}
|
||||
Layout::Struct {
|
||||
field_layouts,
|
||||
field_order_hash,
|
||||
} => {
|
||||
debug_assert_eq!(self.set.len(), 1);
|
||||
ClosureCallOptions::Struct {
|
||||
field_layouts,
|
||||
field_order_hash: *field_order_hash,
|
||||
}
|
||||
}
|
||||
layout => {
|
||||
debug_assert!(self.has_enum_dispatch_repr());
|
||||
let enum_repr = match layout {
|
||||
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
|
||||
other => internal_error!("Invalid layout for enum dispatch: {:?}", other),
|
||||
};
|
||||
ClosureCallOptions::EnumDispatch(enum_repr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1013,30 +1111,27 @@ impl<'a> LambdaSet<'a> {
|
|||
arena: &'a Bump,
|
||||
argument_layouts: &'a [Layout<'a>],
|
||||
) -> &'a [Layout<'a>] {
|
||||
if let [] = self.set {
|
||||
// TERRIBLE HACK for builting functions
|
||||
argument_layouts
|
||||
} else {
|
||||
match self.representation {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => {
|
||||
// this function does not have anything in its closure, and the lambda set is a
|
||||
// singleton, so we pass no extra argument
|
||||
argument_layouts
|
||||
}
|
||||
Layout::Builtin(Builtin::Bool)
|
||||
| Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8)) => {
|
||||
// we don't pass this along either
|
||||
argument_layouts
|
||||
}
|
||||
_ => {
|
||||
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
|
||||
arguments.extend(argument_layouts);
|
||||
arguments.push(Layout::LambdaSet(*self));
|
||||
match self.call_by_name_options() {
|
||||
ClosureCallOptions::Void => argument_layouts,
|
||||
ClosureCallOptions::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => {
|
||||
// this function does not have anything in its closure, and the lambda set is a
|
||||
// singleton, so we pass no extra argument
|
||||
argument_layouts
|
||||
}
|
||||
ClosureCallOptions::Struct { .. }
|
||||
| ClosureCallOptions::Union(_)
|
||||
| ClosureCallOptions::UnwrappedCapture(_) => {
|
||||
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
|
||||
arguments.extend(argument_layouts);
|
||||
arguments.push(Layout::LambdaSet(*self));
|
||||
|
||||
arguments.into_bump_slice()
|
||||
}
|
||||
arguments.into_bump_slice()
|
||||
}
|
||||
ClosureCallOptions::EnumDispatch(_) => {
|
||||
// No captures, don't pass this along
|
||||
argument_layouts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1053,7 +1148,7 @@ impl<'a> LambdaSet<'a> {
|
|||
lambdas.sort_by_key(|(sym, _)| *sym);
|
||||
|
||||
let mut set: Vec<(Symbol, &[Layout])> = Vec::with_capacity_in(lambdas.len(), arena);
|
||||
let mut set_with_variables: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)> =
|
||||
let mut set_with_variables: std::vec::Vec<(&Symbol, &[Variable])> =
|
||||
std::vec::Vec::with_capacity(lambdas.len());
|
||||
|
||||
let mut last_function_symbol = None;
|
||||
|
@ -1090,7 +1185,7 @@ impl<'a> LambdaSet<'a> {
|
|||
has_duplicate_lambda_names = has_duplicate_lambda_names || is_multimorphic;
|
||||
|
||||
set.push((*function_symbol, arguments));
|
||||
set_with_variables.push((*function_symbol, variables.to_vec()));
|
||||
set_with_variables.push((function_symbol, variables.as_slice()));
|
||||
|
||||
last_function_symbol = Some(function_symbol);
|
||||
}
|
||||
|
@ -1139,7 +1234,7 @@ impl<'a> LambdaSet<'a> {
|
|||
}
|
||||
ResolvedLambdaSet::Unbound => {
|
||||
// The lambda set is unbound which means it must be unused. Just give it the empty lambda set.
|
||||
// See also https://github.com/rtfeldman/roc/issues/3163.
|
||||
// See also https://github.com/roc-lang/roc/issues/3163.
|
||||
Ok(LambdaSet {
|
||||
set: &[],
|
||||
representation: arena.alloc(Layout::UNIT),
|
||||
|
@ -1151,70 +1246,23 @@ impl<'a> LambdaSet<'a> {
|
|||
fn make_representation(
|
||||
arena: &'a Bump,
|
||||
subs: &Subs,
|
||||
tags: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>,
|
||||
tags: std::vec::Vec<(&Symbol, &[Variable])>,
|
||||
opt_rec_var: Option<Variable>,
|
||||
target_info: TargetInfo,
|
||||
) -> Layout<'a> {
|
||||
if let Some(rec_var) = opt_rec_var {
|
||||
let tags: std::vec::Vec<_> = tags
|
||||
.iter()
|
||||
.map(|(sym, vars)| (sym, vars.as_slice()))
|
||||
.collect();
|
||||
let tags = UnsortedUnionLabels { tags };
|
||||
let mut env = Env {
|
||||
seen: Vec::new_in(arena),
|
||||
target_info,
|
||||
arena,
|
||||
subs,
|
||||
};
|
||||
let union_labels = UnsortedUnionLabels { tags };
|
||||
let mut env = Env {
|
||||
seen: Vec::new_in(arena),
|
||||
target_info,
|
||||
arena,
|
||||
subs,
|
||||
};
|
||||
|
||||
return layout_from_recursive_union(&mut env, rec_var, &tags)
|
||||
.expect("unable to create lambda set representation");
|
||||
}
|
||||
match opt_rec_var {
|
||||
Some(rec_var) => layout_from_recursive_union(&mut env, rec_var, &union_labels)
|
||||
.expect("unable to create lambda set representation"),
|
||||
|
||||
// otherwise, this is a closure with a payload
|
||||
let variant = union_sorted_tags_help(arena, tags, opt_rec_var, subs, target_info);
|
||||
|
||||
use UnionVariant::*;
|
||||
match variant {
|
||||
Never => Layout::VOID,
|
||||
BoolUnion { .. } => Layout::bool(),
|
||||
ByteUnion { .. } => Layout::u8(),
|
||||
Unit | UnitWithArguments => {
|
||||
// no useful information to store
|
||||
Layout::UNIT
|
||||
}
|
||||
Newtype {
|
||||
arguments: layouts, ..
|
||||
} => Layout::struct_no_name_order(layouts.into_bump_slice()),
|
||||
Wrapped(variant) => {
|
||||
use WrappedVariant::*;
|
||||
|
||||
match variant {
|
||||
NonRecursive {
|
||||
sorted_tag_layouts: tags,
|
||||
} => {
|
||||
debug_assert!(tags.len() > 1);
|
||||
|
||||
// if the closed-over value is actually a layout, it should be wrapped in a 1-element record
|
||||
debug_assert!(matches!(tags[0].0, TagOrClosure::Closure(_)));
|
||||
|
||||
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
|
||||
|
||||
for (_, tag_args) in tags.iter() {
|
||||
tag_arguments.push(&tag_args[0..]);
|
||||
}
|
||||
Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice()))
|
||||
}
|
||||
|
||||
Recursive { .. }
|
||||
| NullableUnwrapped { .. }
|
||||
| NullableWrapped { .. }
|
||||
| NonNullableUnwrapped { .. } => {
|
||||
internal_error!("Recursive layouts should be produced in an earlier branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
None => layout_from_union(&mut env, &union_labels),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1239,7 +1287,7 @@ enum ResolvedLambdaSet {
|
|||
OptVariable,
|
||||
),
|
||||
/// TODO: figure out if this can happen in a correct program, or is the result of a bug in our
|
||||
/// compiler. See https://github.com/rtfeldman/roc/issues/3163.
|
||||
/// compiler. See https://github.com/roc-lang/roc/issues/3163.
|
||||
Unbound,
|
||||
}
|
||||
|
||||
|
@ -1777,6 +1825,13 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runtime_representation(&self) -> Self {
|
||||
match self {
|
||||
Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation(),
|
||||
other => *other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Avoid recomputing Layout from Variable multiple times.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(clippy::dbg_macro)]
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
|
||||
|
||||
pub mod borrow;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue