roc/crates/compiler/mono/src/ir.rs
Ayaz Hafiz 5164994fb5
Do not attempt to lookup functions in expects
Functions are not useful to print in expect results, because they are
only printed opaquely as `<function>`. Moreover, their transformation to
closure sets during mono can be extremely lossy, up to and including the
elision of symbols for function closure symbols. As such, simply do not
attempt to lookup or print functions referenced in expects.

Closes #4389
2022-10-24 10:28:56 -05:00

10422 lines
368 KiB
Rust

#![allow(clippy::manual_map)]
use crate::layout::{
self, Builtin, CapturesNiche, ClosureCallOptions, ClosureRepresentation, EnumDispatch,
LambdaName, LambdaSet, Layout, LayoutCache, LayoutInterner, LayoutProblem, RawFunctionLayout,
STLayoutInterner, TagIdIntType, UnionLayout, WrappedVariant,
};
use bumpalo::collections::{CollectIn, Vec};
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::abilities::SpecializationId;
use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup, IntValue};
use roc_can::module::ExposedByModule;
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_collections::VecMap;
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::{
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
ROC_PRINT_RUNTIME_ERROR_GEN,
};
use roc_derive::SharedDerivedModule;
use roc_error_macros::{internal_error, todo_abilities};
use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId};
use roc_intern::Interner;
use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapshot};
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::{RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
use roc_types::subs::{
instantiate_rigids, Content, ExhaustiveMark, FlatType, RedundantMark, StorageSubs, Subs,
Variable, VariableSubsSlice,
};
use std::collections::HashMap;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
#[inline(always)]
pub fn pretty_print_ir_symbols() -> bool {
dbg_do!(ROC_PRINT_IR_AFTER_SPECIALIZATION, {
return true;
});
dbg_do!(ROC_PRINT_IR_AFTER_RESET_REUSE, {
return true;
});
dbg_do!(ROC_PRINT_IR_AFTER_REFCOUNT, {
return true;
});
false
}
// if your changes cause this number to go down, great!
// please change it to the lower number.
// if it went up, maybe check that the change is really required
roc_error_macros::assert_sizeof_wasm!(Literal, 24);
roc_error_macros::assert_sizeof_wasm!(Expr, 48);
roc_error_macros::assert_sizeof_wasm!(Stmt, 120);
roc_error_macros::assert_sizeof_wasm!(ProcLayout, 40);
roc_error_macros::assert_sizeof_wasm!(Call, 44);
roc_error_macros::assert_sizeof_wasm!(CallType, 36);
roc_error_macros::assert_sizeof_non_wasm!(Literal, 3 * 8);
roc_error_macros::assert_sizeof_non_wasm!(Expr, 10 * 8);
roc_error_macros::assert_sizeof_non_wasm!(Stmt, 19 * 8);
roc_error_macros::assert_sizeof_non_wasm!(ProcLayout, 8 * 8);
roc_error_macros::assert_sizeof_non_wasm!(Call, 9 * 8);
roc_error_macros::assert_sizeof_non_wasm!(CallType, 7 * 8);
macro_rules! return_on_layout_error {
($env:expr, $layout_result:expr, $context_msg:expr) => {
match $layout_result {
Ok(cached) => cached,
Err(error) => return_on_layout_error_help!($env, error, $context_msg),
}
};
}
macro_rules! return_on_layout_error_help {
($env:expr, $error:expr, $context_msg:expr) => {{
match $error {
LayoutProblem::UnresolvedTypeVar(_) => {
return Stmt::RuntimeError(
$env.arena
.alloc(format!("UnresolvedTypeVar: {}", $context_msg,)),
);
}
LayoutProblem::Erroneous => {
return Stmt::RuntimeError(
$env.arena.alloc(format!("Erroneous: {}", $context_msg,)),
);
}
}
}};
}
#[derive(Debug, Clone, Copy)]
pub enum OptLevel {
Development,
Normal,
Size,
Optimize,
}
#[derive(Debug, Clone, Copy)]
pub struct EntryPoint<'a> {
pub symbol: Symbol,
pub layout: ProcLayout<'a>,
}
#[derive(Clone, Copy, Debug)]
pub struct PartialProcId(usize);
#[derive(Clone, Debug)]
pub struct PartialProcs<'a> {
/// maps a function name (symbol) to an index
symbols: Vec<'a, Symbol>,
/// An entry (a, b) means `a` directly references the lambda value of `b`,
/// i.e. this came from a `let a = b in ...` where `b` was defined as a
/// lambda earlier.
references: Vec<'a, (Symbol, Symbol)>,
partial_procs: Vec<'a, PartialProc<'a>>,
}
impl<'a> PartialProcs<'a> {
fn new_in(arena: &'a Bump) -> Self {
Self {
symbols: Vec::new_in(arena),
references: Vec::new_in(arena),
partial_procs: Vec::new_in(arena),
}
}
fn contains_key(&self, symbol: Symbol) -> bool {
self.symbol_to_id(symbol).is_some()
}
fn symbol_to_id(&self, mut symbol: Symbol) -> Option<PartialProcId> {
while let Some(real_symbol) = self
.references
.iter()
.find(|(alias, _)| *alias == symbol)
.map(|(_, real)| real)
{
symbol = *real_symbol;
}
self.symbols
.iter()
.position(|s| *s == symbol)
.map(PartialProcId)
}
fn get_symbol(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
let id = self.symbol_to_id(symbol)?;
Some(self.get_id(id))
}
fn get_id(&self, id: PartialProcId) -> &PartialProc<'a> {
&self.partial_procs[id.0]
}
pub fn insert(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) -> PartialProcId {
debug_assert!(
!self.contains_key(symbol),
"The {:?} is inserted as a partial proc twice: that's a bug!",
symbol,
);
let id = PartialProcId(self.symbols.len());
self.symbols.push(symbol);
self.partial_procs.push(partial_proc);
id
}
pub fn insert_alias(&mut self, alias: Symbol, real_symbol: Symbol) {
debug_assert!(
!self.contains_key(alias),
"{:?} is inserted as a partial proc twice: that's a bug!",
alias,
);
debug_assert!(
self.contains_key(real_symbol),
"{:?} is not a partial proc or another alias: that's a bug!",
real_symbol,
);
self.references.push((alias, real_symbol));
}
pub fn drain(self) -> impl Iterator<Item = (Symbol, PartialProc<'a>)> {
debug_assert_eq!(self.symbols.len(), self.partial_procs.len());
self.symbols.into_iter().zip(self.partial_procs.into_iter())
}
}
#[derive(Clone, Debug)]
pub struct PartialProc<'a> {
pub annotation: Variable,
pub pattern_symbols: &'a [Symbol],
pub captured_symbols: CapturedSymbols<'a>,
pub body: roc_can::expr::Expr,
pub body_var: Variable,
pub is_self_recursive: bool,
}
impl<'a> PartialProc<'a> {
#[allow(clippy::too_many_arguments)]
pub fn from_named_function(
env: &mut Env<'a, '_>,
annotation: Variable,
loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc<roc_can::pattern::Pattern>)>,
loc_body: Loc<roc_can::expr::Expr>,
captured_symbols: CapturedSymbols<'a>,
is_self_recursive: bool,
ret_var: Variable,
) -> PartialProc<'a> {
let number_of_arguments = loc_args.len();
match patterns_to_when(env, loc_args, ret_var, loc_body) {
Ok((_, pattern_symbols, body)) => {
// a named closure. Since these aren't specialized by the surrounding
// context, we can't add pending specializations for them yet.
// (If we did, all named polymorphic functions would immediately error
// on trying to convert a flex var to a Layout.)
let pattern_symbols = pattern_symbols.into_bump_slice();
PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body: body.value,
body_var: ret_var,
is_self_recursive,
}
}
Err(error) => {
let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena);
for _ in 0..number_of_arguments {
pattern_symbols.push(env.unique_symbol());
}
PartialProc {
annotation,
pattern_symbols: pattern_symbols.into_bump_slice(),
captured_symbols: CapturedSymbols::None,
body: roc_can::expr::Expr::RuntimeError(error.value),
body_var: ret_var,
is_self_recursive: false,
}
}
}
}
}
#[derive(Clone, Copy, Debug)]
struct AbilityMember(Symbol);
/// A table of aliases of ability member symbols.
#[derive(Clone, Debug)]
struct AbilityAliases(BumpMap<Symbol, AbilityMember>);
impl AbilityAliases {
fn new_in(arena: &Bump) -> Self {
Self(BumpMap::new_in(arena))
}
fn insert(&mut self, symbol: Symbol, member: AbilityMember) {
self.0.insert(symbol, member);
}
fn get(&self, symbol: Symbol) -> Option<&AbilityMember> {
self.0.get(&symbol)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CapturedSymbols<'a> {
None,
Captured(&'a [(Symbol, Variable)]),
}
impl<'a> CapturedSymbols<'a> {
fn captures(&self) -> bool {
match self {
CapturedSymbols::None => false,
CapturedSymbols::Captured(_) => true,
}
}
}
impl<'a> Default for CapturedSymbols<'a> {
fn default() -> Self {
CapturedSymbols::None
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Proc<'a> {
pub name: LambdaName<'a>,
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,
pub must_own_arguments: bool,
pub host_exposed_layouts: HostExposedLayouts<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum HostExposedLayouts<'a> {
NotHostExposed,
HostExposed {
rigids: BumpMap<Lowercase, Layout<'a>>,
aliases: BumpMap<Symbol, (Symbol, ProcLayout<'a>, RawFunctionLayout<'a>)>,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum SelfRecursive {
NotSelfRecursive,
SelfRecursive(JoinPointId),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Parens {
NotNeeded,
InTypeParam,
InFunction,
}
impl<'a> Proc<'a> {
pub fn to_doc<'b, D, A, I>(
&'b self,
alloc: &'b D,
interner: &'b I,
_parens: Parens,
) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
I: Interner<'a, Layout<'a>>,
{
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,
interner,
Parens::NotNeeded,
))
} else {
arg_doc
}
});
if pretty_print_ir_symbols() {
alloc
.text("procedure : ")
.append(symbol_to_doc(alloc, self.name.name()))
.append(" ")
.append(self.ret_layout.to_doc(alloc, interner, Parens::NotNeeded))
.append(alloc.hardline())
.append(alloc.text("procedure = "))
.append(symbol_to_doc(alloc, self.name.name()))
.append(" (")
.append(alloc.intersperse(args_doc, ", "))
.append("):")
.append(alloc.hardline())
.append(self.body.to_doc(alloc, interner).indent(4))
} else {
alloc
.text("procedure ")
.append(symbol_to_doc(alloc, self.name.name()))
.append(" (")
.append(alloc.intersperse(args_doc, ", "))
.append("):")
.append(alloc.hardline())
.append(self.body.to_doc(alloc, interner).indent(4))
}
}
pub fn to_pretty<I>(&self, interner: &I, width: usize) -> String
where
I: Interner<'a, Layout<'a>>,
{
let allocator = BoxAllocator;
let mut w = std::vec::Vec::new();
self.to_doc::<_, (), _>(&allocator, interner, Parens::NotNeeded)
.1
.render(width, &mut w)
.unwrap();
w.push(b'\n');
String::from_utf8(w).unwrap()
}
pub fn insert_refcount_operations<'i>(
arena: &'a Bump,
layout_interner: &'i STLayoutInterner<'a>,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs));
crate::inc_dec::visit_procs(
arena,
layout_interner,
home,
ident_ids,
update_mode_ids,
borrow_params,
procs,
);
}
pub fn insert_reset_reuse_operations<'i>(
arena: &'a Bump,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
for (_, proc) in procs.iter_mut() {
let new_proc = crate::reset_reuse::insert_reset_reuse(
arena,
home,
ident_ids,
update_mode_ids,
proc.clone(),
);
*proc = new_proc;
}
}
fn make_tail_recursive(&mut self, env: &mut Env<'a, '_>) {
let mut args = Vec::with_capacity_in(self.args.len(), env.arena);
let mut proc_args = Vec::with_capacity_in(self.args.len(), env.arena);
for (layout, symbol) in self.args {
let new = env.unique_symbol();
args.push((*layout, *symbol, new));
proc_args.push((*layout, new));
}
use self::SelfRecursive::*;
if let SelfRecursive(id) = self.is_self_recursive {
let transformed = crate::tail_recursion::make_tail_recursive(
env.arena,
id,
self.name,
self.body.clone(),
args.into_bump_slice(),
self.ret_layout,
);
if let Some(with_tco) = transformed {
self.body = with_tco;
self.args = proc_args.into_bump_slice();
}
}
}
}
/// A host-exposed function must be specialized; it's a seed for subsequent specializations
#[derive(Clone, Debug)]
pub struct HostSpecializations<'a> {
/// Not a bumpalo vec because bumpalo is not thread safe
/// Separate array so we can search for membership quickly
/// If it's a value and not a lambda, the value is recorded as LambdaName::no_niche.
symbol_or_lambdas: std::vec::Vec<LambdaName<'a>>,
storage_subs: StorageSubs,
/// For each symbol, what types to specialize it for, points into the storage_subs
types_to_specialize: std::vec::Vec<Variable>,
/// Variables for an exposed alias
exposed_aliases: std::vec::Vec<std::vec::Vec<(Symbol, Variable)>>,
}
impl Default for HostSpecializations<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> HostSpecializations<'a> {
pub fn new() -> Self {
Self {
symbol_or_lambdas: std::vec::Vec::new(),
storage_subs: StorageSubs::new(Subs::default()),
types_to_specialize: std::vec::Vec::new(),
exposed_aliases: std::vec::Vec::new(),
}
}
pub fn insert_host_exposed(
&mut self,
env_subs: &mut Subs,
symbol_or_lambda: LambdaName<'a>,
opt_annotation: Option<roc_can::def::Annotation>,
variable: Variable,
) {
let variable = self.storage_subs.extend_with_variable(env_subs, variable);
let mut host_exposed_aliases = std::vec::Vec::new();
if let Some(annotation) = opt_annotation {
host_exposed_aliases.extend(annotation.introduced_variables.host_exposed_aliases);
}
match self
.symbol_or_lambdas
.iter()
.position(|s| *s == symbol_or_lambda)
{
None => {
self.symbol_or_lambdas.push(symbol_or_lambda);
self.types_to_specialize.push(variable);
self.exposed_aliases.push(host_exposed_aliases);
}
Some(_) => {
// we assume that only one specialization of a function is directly exposed to the
// host. Other host-exposed symbols may (transitively) specialize this symbol,
// but then the existing specialization mechanism will find those specializations
panic!("A host-exposed symbol can only be exposed once");
}
}
debug_assert_eq!(self.types_to_specialize.len(), self.exposed_aliases.len());
}
fn decompose(
self,
) -> (
StorageSubs,
impl Iterator<Item = (LambdaName<'a>, Variable, std::vec::Vec<(Symbol, Variable)>)>,
) {
let it1 = self.symbol_or_lambdas.into_iter();
let it2 = self.types_to_specialize.into_iter();
let it3 = self.exposed_aliases.into_iter();
(
self.storage_subs,
it1.zip(it2).zip(it3).map(|((a, b), c)| (a, b, c)),
)
}
}
/// Specializations of this module's symbols that other modules need.
/// One struct represents one pair of modules, e.g. what module A wants of module B.
#[derive(Clone, Debug)]
pub struct ExternalSpecializations<'a> {
/// Not a bumpalo vec because bumpalo is not thread safe
/// Separate array so we can search for membership quickly
/// If it's a value and not a lambda, the value is recorded as LambdaName::no_niche.
pub symbol_or_lambda: std::vec::Vec<LambdaName<'a>>,
storage: ExternalModuleStorage,
/// For each symbol, what types to specialize it for, points into the storage_subs
types_to_specialize: std::vec::Vec<std::vec::Vec<Variable>>,
}
impl Default for ExternalSpecializations<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> ExternalSpecializations<'a> {
pub fn new() -> Self {
Self {
symbol_or_lambda: std::vec::Vec::new(),
storage: ExternalModuleStorage::new(Subs::default()),
types_to_specialize: std::vec::Vec::new(),
}
}
fn insert_external(
&mut self,
symbol_or_lambda: LambdaName<'a>,
env_subs: &mut Subs,
variable: Variable,
) {
let stored_variable = self.storage.extend_with_variable(env_subs, variable);
roc_tracing::debug!(original = ?variable, stored = ?stored_variable, "stored needed external");
match self
.symbol_or_lambda
.iter()
.position(|s| *s == symbol_or_lambda)
{
None => {
self.symbol_or_lambda.push(symbol_or_lambda);
self.types_to_specialize.push(vec![stored_variable]);
}
Some(index) => {
let types_to_specialize = &mut self.types_to_specialize[index];
types_to_specialize.push(stored_variable);
}
}
}
fn decompose(
self,
) -> (
StorageSubs,
impl Iterator<Item = (LambdaName<'a>, std::vec::Vec<Variable>)>,
) {
(
self.storage.into_storage_subs(),
self.symbol_or_lambda
.into_iter()
.zip(self.types_to_specialize.into_iter()),
)
}
fn snapshot_cache(&mut self) -> ExternalModuleStorageSnapshot {
self.storage.snapshot_cache()
}
fn rollback_cache(&mut self, snapshot: ExternalModuleStorageSnapshot) {
self.storage.rollback_cache(snapshot)
}
fn invalidate_cache(&mut self, changed_variables: &[Variable]) {
self.storage.invalidate_cache(changed_variables)
}
fn invalidate_whole_cache(&mut self) {
self.storage.invalidate_whole_cache()
}
}
#[derive(Clone, Debug)]
pub struct Suspended<'a> {
pub store: StorageSubs,
/// LambdaName::no_niche if it's a value
pub symbol_or_lambdas: Vec<'a, LambdaName<'a>>,
pub layouts: Vec<'a, ProcLayout<'a>>,
pub variables: Vec<'a, Variable>,
}
impl<'a> Suspended<'a> {
fn new_in(arena: &'a Bump) -> Self {
Self {
store: StorageSubs::new(Subs::new_from_varstore(Default::default())),
symbol_or_lambdas: Vec::new_in(arena),
layouts: Vec::new_in(arena),
variables: Vec::new_in(arena),
}
}
fn is_empty(&self) -> bool {
self.symbol_or_lambdas.is_empty()
}
fn specialization(
&mut self,
subs: &mut Subs,
symbol_or_lambda: LambdaName<'a>,
proc_layout: ProcLayout<'a>,
variable: Variable,
) {
// de-duplicate
for (i, s) in self.symbol_or_lambdas.iter().enumerate() {
if *s == symbol_or_lambda {
let existing = &self.layouts[i];
if &proc_layout == existing {
// symbol + layout combo exists
return;
}
}
}
self.symbol_or_lambdas.push(symbol_or_lambda);
self.layouts.push(proc_layout);
let variable = self.store.import_variable_from(subs, variable).variable;
self.variables.push(variable);
}
}
#[derive(Clone, Debug)]
enum PendingSpecializations<'a> {
/// We are finding specializations we need. This is a separate step so
/// 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 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)]
struct Specialized<'a> {
symbols: std::vec::Vec<Symbol>,
proc_layouts: std::vec::Vec<ProcLayout<'a>>,
procedures: std::vec::Vec<InProgressProc<'a>>,
}
impl<'a> Specialized<'a> {
fn len(&self) -> usize {
self.symbols.len()
}
#[allow(dead_code)]
fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
fn into_iter_assert_done(self) -> impl Iterator<Item = (Symbol, ProcLayout<'a>, Proc<'a>)> {
self.symbols
.into_iter()
.zip(self.proc_layouts.into_iter())
.zip(self.procedures.into_iter())
.filter_map(|((s, l), in_progress)| {
if let Symbol::REMOVED_SPECIALIZATION = s {
None
} else {
match in_progress {
InProgressProc::InProgress => {
panic!("Function {:?} ({:?}) is not done specializing", s, l)
}
InProgressProc::Done(proc) => Some((s, l, proc)),
}
}
})
}
fn is_specialized(&self, symbol: Symbol, layout: &ProcLayout<'a>) -> bool {
for (i, s) in self.symbols.iter().enumerate() {
if *s == symbol && &self.proc_layouts[i] == layout {
return true;
}
}
false
}
fn mark_in_progress(&mut self, symbol: Symbol, layout: ProcLayout<'a>) {
for (i, s) in self.symbols.iter().enumerate() {
if *s == symbol && self.proc_layouts[i] == layout {
match &self.procedures[i] {
InProgressProc::InProgress => {
return;
}
InProgressProc::Done(_) => {
panic!("marking in progress, but this proc is already done!")
}
}
}
}
// the key/layout combo was not found; insert it
self.symbols.push(symbol);
self.proc_layouts.push(layout);
self.procedures.push(InProgressProc::InProgress);
}
fn remove_specialized(&mut self, symbol: Symbol, layout: &ProcLayout<'a>) -> bool {
let mut index = None;
for (i, s) in self.symbols.iter().enumerate() {
if *s == symbol && &self.proc_layouts[i] == layout {
index = Some(i);
}
}
if let Some(index) = index {
self.symbols[index] = Symbol::REMOVED_SPECIALIZATION;
true
} else {
false
}
}
fn insert_specialized(&mut self, symbol: Symbol, layout: ProcLayout<'a>, proc: Proc<'a>) {
for (i, s) in self.symbols.iter().enumerate() {
if *s == symbol && self.proc_layouts[i] == layout {
match &self.procedures[i] {
InProgressProc::InProgress => {
self.procedures[i] = InProgressProc::Done(proc);
return;
}
InProgressProc::Done(_) => {
// overwrite existing! this is important in practice
// TODO investigate why we generate the wrong proc in some cases and then
// correct later
self.procedures[i] = InProgressProc::Done(proc);
return;
}
}
}
}
// the key/layout combo was not found; insert it
self.symbols.push(symbol);
self.proc_layouts.push(layout);
self.procedures.push(InProgressProc::Done(proc));
}
}
/// Uniquely determines the specialization of a polymorphic (non-proc) value symbol.
/// Two specializations are equivalent if their [`SpecializationMark`]s are equal.
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
struct SpecializationMark<'a> {
/// The layout of the symbol itself.
layout: Layout<'a>,
/// If this symbol is a closure def, we must also keep track of what function it specializes,
/// because the [`layout`] field will only keep track of its closure and lambda set - which can
/// be the same for two different function specializations. For example,
///
/// id = if True then \x -> x else \y -> y
/// { a: id "", b: id 1u8 }
///
/// The lambda set and captures of `id` is the same in both usages inside the record, but the
/// reified specializations of `\x -> x` and `\y -> y` must be for Str and U8.
///
/// Note that this field is not relevant for anything that is not a function.
function_mark: Option<RawFunctionLayout<'a>>,
}
/// When walking a function body, we may encounter specialized usages of polymorphic symbols. For
/// example
///
/// myTag = A
/// use1 : [A, B]
/// use1 = myTag
/// use2 : [A, B, C]
/// use2 = myTag
///
/// We keep track of the specializations of `myTag` and create fresh symbols when there is more
/// than one, so that a unique def can be created for each.
#[derive(Default, Debug, Clone)]
struct SymbolSpecializations<'a>(
// THEORY:
// 1. the number of symbols in a def is very small
// 2. the number of specializations of a symbol in a def is even smaller (almost always only one)
// So, a linear VecMap is preferrable. Use a two-layered one to make (1) extraction of defs easy
// and (2) reads of a certain symbol be determined by its first occurrence, not its last.
VecMap<Symbol, VecMap<SpecializationMark<'a>, (Variable, Symbol)>>,
);
impl<'a> SymbolSpecializations<'a> {
/// Gets a specialization for a symbol, or creates a new one.
#[inline(always)]
fn get_or_insert(
&mut self,
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
symbol: Symbol,
specialization_var: Variable,
) -> Symbol {
let arena = env.arena;
let subs: &Subs = env.subs;
let layout = match layout_cache.from_var(arena, specialization_var, subs) {
Ok(layout) => layout,
// This can happen when the def symbol has a type error. In such cases just use the
// def symbol, which is erroring.
Err(_) => return symbol,
};
let is_closure = matches!(
subs.get_content_without_compacting(specialization_var),
Content::Structure(FlatType::Func(..))
);
let function_mark = if is_closure {
let fn_layout = match layout_cache.raw_from_var(arena, specialization_var, subs) {
Ok(layout) => layout,
// This can happen when the def symbol has a type error. In such cases just use the
// def symbol, which is erroring.
Err(_) => return symbol,
};
Some(fn_layout)
} else {
None
};
let specialization_mark = SpecializationMark {
layout,
function_mark,
};
let symbol_specializations = self.0.get_or_insert(symbol, Default::default);
// For the first specialization, always reuse the current symbol. The vast majority of defs
// only have one instance type, so this preserves readability of the IR.
// TODO: turn me off and see what breaks.
let needs_fresh_symbol = !symbol_specializations.is_empty();
let mut make_specialized_symbol = || {
if needs_fresh_symbol {
env.unique_symbol()
} else {
symbol
}
};
let (_var, specialized_symbol) = symbol_specializations
.get_or_insert(specialization_mark, || {
(specialization_var, make_specialized_symbol())
});
*specialized_symbol
}
/// Inserts a known specialization for a symbol. Returns the overwritten specialization, if any.
pub fn get_or_insert_known(
&mut self,
symbol: Symbol,
mark: SpecializationMark<'a>,
specialization_var: Variable,
specialization_symbol: Symbol,
) -> Option<(Variable, Symbol)> {
self.0
.get_or_insert(symbol, Default::default)
.insert(mark, (specialization_var, specialization_symbol))
}
/// Removes all specializations for a symbol, returning the type and symbol of each specialization.
pub fn remove(
&mut self,
symbol: Symbol,
) -> impl ExactSizeIterator<Item = (SpecializationMark<'a>, (Variable, Symbol))> {
self.0
.remove(&symbol)
.map(|(_, specializations)| specializations)
.unwrap_or_default()
.into_iter()
}
/// Expects and removes at most a single specialization symbol for the given requested symbol.
/// A symbol may have no specializations if it is never referenced in a body, so it is possible
/// for this to return None.
pub fn remove_single(&mut self, symbol: Symbol) -> Option<Symbol> {
let mut specializations = self.remove(symbol);
debug_assert!(
specializations.len() < 2,
"Symbol {:?} has multiple specializations",
symbol
);
specializations.next().map(|(_, (_, symbol))| symbol)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Debug, Default)]
pub struct ProcsBase<'a> {
pub partial_procs: BumpMap<Symbol, PartialProc<'a>>,
pub module_thunks: &'a [Symbol],
/// A host-exposed function must be specialized; it's a seed for subsequent specializations
pub host_specializations: HostSpecializations<'a>,
pub runtime_errors: BumpMap<Symbol, &'a str>,
pub imported_module_thunks: &'a [Symbol],
}
#[derive(Clone, Debug)]
pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>,
ability_member_aliases: AbilityAliases,
pub imported_module_thunks: &'a [Symbol],
pub module_thunks: &'a [Symbol],
pending_specializations: PendingSpecializations<'a>,
specialized: Specialized<'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> {
pub fn new_in(arena: &'a Bump) -> Self {
Self {
partial_procs: PartialProcs::new_in(arena),
ability_member_aliases: AbilityAliases::new_in(arena),
imported_module_thunks: &[],
module_thunks: &[],
pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)),
specialized: Specialized::default(),
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)]
pub enum InProgressProc<'a> {
InProgress,
Done(Proc<'a>),
}
impl<'a> Procs<'a> {
fn is_imported_module_thunk(&self, symbol: Symbol) -> bool {
self.imported_module_thunks.iter().any(|x| *x == symbol)
}
fn is_module_thunk(&self, symbol: Symbol) -> bool {
self.module_thunks.iter().any(|x| *x == symbol)
}
fn get_partial_proc<'b>(&'b self, symbol: Symbol) -> Option<&'b PartialProc<'a>> {
self.partial_procs.get_symbol(symbol)
}
pub fn get_specialized_procs_without_rc(
self,
env: &mut Env<'a, '_>,
) -> (MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ProcsBase<'a>) {
let mut specialized_procs =
MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
for (symbol, layout, mut proc) in self.specialized.into_iter_assert_done() {
proc.make_tail_recursive(env);
let key = (symbol, layout);
specialized_procs.insert(key, proc);
}
let restored_procs_base = ProcsBase {
partial_procs: self.partial_procs.drain().collect(),
module_thunks: self.module_thunks,
// This must now be empty
host_specializations: HostSpecializations::default(),
runtime_errors: self.runtime_errors,
imported_module_thunks: self.imported_module_thunks,
};
(specialized_procs, restored_procs_base)
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
fn insert_anonymous(
&mut self,
env: &mut Env<'a, '_>,
name: LambdaName<'a>,
annotation: Variable,
loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc<roc_can::pattern::Pattern>)>,
loc_body: Loc<roc_can::expr::Expr>,
captured_symbols: CapturedSymbols<'a>,
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) -> Result<ProcLayout<'a>, RuntimeError> {
let raw_layout = layout_cache
.raw_from_var(env.arena, annotation, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw_layout,
name.captures_niche(),
);
// anonymous functions cannot reference themselves, therefore cannot be tail-recursive
// EXCEPT when the closure conversion makes it tail-recursive.
let is_self_recursive = match top_level.arguments.last() {
Some(Layout::LambdaSet(lambda_set)) => lambda_set.contains(name.name()),
_ => false,
};
match patterns_to_when(env, loc_args, ret_var, loc_body) {
Ok((_, pattern_symbols, body)) => {
// an anonymous closure. These will always be specialized already
// by the surrounding context, so we can add pending specializations
// for them immediately.
let already_specialized = self.specialized.is_specialized(name.name(), &top_level);
let layout = top_level;
// if we've already specialized this one, no further work is needed.
if !already_specialized {
if self.is_module_thunk(name.name()) {
debug_assert!(layout.arguments.is_empty());
}
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);
match self.partial_procs.symbol_to_id(name.name()) {
Some(occupied) => {
let existing = self.partial_procs.get_id(occupied);
// if we're adding the same partial proc twice, they must be the actual same!
//
// NOTE we can't skip extra work! we still need to make the specialization for this
// invocation. The content of the `annotation` can be different, even if the variable
// number is the same
debug_assert_eq!(annotation, existing.annotation);
debug_assert_eq!(captured_symbols, existing.captured_symbols);
debug_assert_eq!(is_self_recursive, existing.is_self_recursive);
// the partial proc is already in there, do nothing
}
None => {
let pattern_symbols = pattern_symbols.into_bump_slice();
let partial_proc = PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body: body.value,
body_var: ret_var,
is_self_recursive,
};
self.partial_procs.insert(name.name(), partial_proc);
}
}
}
(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!)
self.specialized.mark_in_progress(name.name(), layout);
let partial_proc_id = if let Some(partial_proc_id) =
self.partial_procs.symbol_to_id(name.name())
{
let existing = self.partial_procs.get_id(partial_proc_id);
// if we're adding the same partial proc twice, they must be the actual same!
//
// NOTE we can't skip extra work! we still need to make the specialization for this
// invocation. The content of the `annotation` can be different, even if the variable
// number is the same
debug_assert_eq!(annotation, existing.annotation);
debug_assert_eq!(captured_symbols, existing.captured_symbols);
debug_assert_eq!(is_self_recursive, existing.is_self_recursive);
partial_proc_id
} else {
let pattern_symbols = pattern_symbols.into_bump_slice();
let partial_proc = PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body: body.value,
body_var: ret_var,
is_self_recursive,
};
self.partial_procs.insert(name.name(), partial_proc)
};
match specialize_variable(
env,
self,
name,
layout_cache,
annotation,
&[],
partial_proc_id,
) {
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.
let arguments = Vec::from_iter_in(
proc.args.iter().map(|(l, _)| *l),
env.arena,
)
.into_bump_slice();
let proper_layout = ProcLayout {
arguments,
result: proc.ret_layout,
captures_niche: proc.name.captures_niche(),
};
// NOTE: some functions are specialized to have a closure, but don't actually
// need any closure argument. Here is where we correct this sort of thing,
// by trusting the layout of the Proc, not of what we specialize for
self.specialized.remove_specialized(name.name(), &layout);
self.specialized.insert_specialized(
name.name(),
proper_layout,
proc,
);
}
Err(error) => {
panic!("TODO generate a RuntimeError message for {:?}", error);
}
}
}
}
}
Ok(layout)
}
Err(loc_error) => Err(loc_error.value),
}
}
fn insert_passed_by_name(
&mut self,
env: &mut Env<'a, '_>,
fn_var: Variable,
name: LambdaName<'a>,
layout: ProcLayout<'a>,
layout_cache: &mut LayoutCache<'a>,
) {
// If we've already specialized this one, no further work is needed.
if self.specialized.is_specialized(name.name(), &layout) {
return;
}
// If this is an imported symbol, let its home module make this specialization
if env.is_imported_symbol(name.name()) || env.is_unloaded_derived_symbol(name.name(), self)
{
add_needed_external(self, env, fn_var, name);
return;
}
// register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&name.name()) {
debug_assert!(layout.arguments.is_empty());
}
// This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass!
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(_), false) => {
let symbol = name;
let partial_proc_id = match self.partial_procs.symbol_to_id(symbol.name()) {
Some(p) => p,
None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home),
};
// 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!)
self.specialized.mark_in_progress(symbol.name(), layout);
// 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
//
// fn_var is the variable representing the type that we actually need for the
// function right here.
//
// For some reason, it matters that we unify with the original variable. Extracting
// that variable into a SolvedType and then introducing it again severs some
// connection that turns out to be important
match specialize_variable(
env,
self,
symbol,
layout_cache,
fn_var,
Default::default(),
partial_proc_id,
) {
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.
let arguments =
Vec::from_iter_in(proc.args.iter().map(|(l, _)| *l), env.arena)
.into_bump_slice();
let proper_layout = ProcLayout {
arguments,
result: proc.ret_layout,
captures_niche: proc.name.captures_niche(),
};
// NOTE: some functions are specialized to have a closure, but don't actually
// need any closure argument. Here is where we correct this sort of thing,
// by trusting the layout of the Proc, not of what we specialize for
self.specialized.remove_specialized(symbol.name(), &layout);
self.specialized
.insert_specialized(symbol.name(), proper_layout, proc);
}
Err(error) => {
panic!("TODO generate a RuntimeError message for {:?}", error);
}
}
}
}
}
}
#[derive(Default)]
pub struct Specializations<'a> {
by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
}
impl<'a> Specializations<'a> {
pub fn insert(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) {
let procs_by_layout = self
.by_symbol
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
// If we already have an entry for this, it should be no different
// from what we're about to insert.
debug_assert!(
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
);
procs_by_layout.insert(layout, proc);
}
pub fn len(&self) -> usize {
self.by_symbol.len()
}
pub fn is_empty(&self) -> bool {
self.by_symbol.is_empty()
}
}
pub struct Env<'a, 'i> {
pub arena: &'a Bump,
pub subs: &'i mut Subs,
pub home: ModuleId,
pub ident_ids: &'i mut IdentIds,
pub target_info: TargetInfo,
pub update_mode_ids: &'i mut UpdateModeIds,
pub call_specialization_counter: u32,
// TODO: WorldAbilities and exposed_by_module share things, think about how to combine them
pub abilities: AbilitiesView<'i>,
pub exposed_by_module: &'i ExposedByModule,
pub derived_module: &'i SharedDerivedModule,
}
impl<'a, 'i> Env<'a, 'i> {
pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
Symbol::new(self.home, ident_id)
}
pub fn next_update_mode_id(&mut self) -> UpdateModeId {
self.update_mode_ids.next_id()
}
pub fn next_call_specialization_id(&mut self) -> CallSpecId {
let id = CallSpecId {
id: self.call_specialization_counter,
};
self.call_specialization_counter += 1;
id
}
pub fn is_imported_symbol(&self, symbol: Symbol) -> bool {
let sym_module = symbol.module_id();
sym_module != self.home
// The Derived_gen module takes responsibility for code-generating symbols in the
// Derived_synth module.
&& !(self.home == ModuleId::DERIVED_GEN && sym_module == ModuleId::DERIVED_SYNTH)
}
/// While specializing the Derived_gen module, derived implementation symbols from the
/// Derived_synth module may be discovered. These implementations may not have yet been loaded
/// into the Derived_gen module, because we only load them before making specializations, and
/// not during mono itself (yet).
///
/// When this procedure returns `true`, the symbol should be marked as an external specialization,
/// so that a subsequent specializations pass loads the derived implementation into Derived_gen
/// and then code-generates appropriately.
pub fn is_unloaded_derived_symbol(&self, symbol: Symbol, procs: &Procs<'a>) -> bool {
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.
/// Use this rather than [roc_unify::unify] directly!
fn unify<'b, 'c: 'b>(
&mut self,
external_specializations: impl IntoIterator<Item = &'b mut ExternalSpecializations<'c>>,
layout_cache: &mut LayoutCache,
left: Variable,
right: Variable,
) -> Result<(), UnificationFailed> {
debug_assert_ne!(
self.home,
ModuleId::DERIVED_SYNTH,
"should never be monomorphizing the derived synth module!"
);
let changed_variables = roc_late_solve::unify(
self.home,
self.arena,
self.subs,
&self.abilities,
self.derived_module,
self.exposed_by_module,
left,
right,
)?;
layout_cache.invalidate(changed_variables.iter().copied());
external_specializations
.into_iter()
.for_each(|e| e.invalidate_cache(&changed_variables));
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)]
pub struct JoinPointId(pub Symbol);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Param<'a> {
pub symbol: Symbol,
pub borrow: bool,
pub layout: Layout<'a>,
}
impl<'a> Param<'a> {
pub const EMPTY: Self = Param {
symbol: Symbol::EMPTY_PARAM,
borrow: false,
layout: Layout::UNIT,
};
}
pub fn cond<'a>(
env: &mut Env<'a, '_>,
cond_symbol: Symbol,
cond_layout: Layout<'a>,
pass: Stmt<'a>,
fail: Stmt<'a>,
ret_layout: Layout<'a>,
) -> Stmt<'a> {
let branches = env.arena.alloc([(1u64, BranchInfo::None, pass)]);
let default_branch = (BranchInfo::None, &*env.arena.alloc(fail));
Stmt::Switch {
cond_symbol,
cond_layout,
ret_layout,
branches,
default_branch,
}
}
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
Switch {
/// This *must* stand for an integer, because Switch potentially compiles to a jump table.
cond_symbol: Symbol,
cond_layout: Layout<'a>,
/// The u64 in the tuple will be compared directly to the condition Expr.
/// If they are equal, this branch will be taken.
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
/// If no other branches pass, this default branch will be taken.
default_branch: (BranchInfo<'a>, &'a Stmt<'a>),
/// Each branch must return a value of this type.
ret_layout: Layout<'a>,
},
Ret(Symbol),
Refcounting(ModifyRc, &'a Stmt<'a>),
Expect {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
layouts: &'a [Layout<'a>],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
ExpectFx {
condition: Symbol,
region: Region,
lookups: &'a [Symbol],
layouts: &'a [Layout<'a>],
/// what happens after the expect
remainder: &'a Stmt<'a>,
},
/// a join point `join f <params> = <continuation> in remainder`
Join {
id: JoinPointId,
parameters: &'a [Param<'a>],
/// body of the join point
/// what happens after _jumping to_ the join point
body: &'a Stmt<'a>,
/// what happens after _defining_ the join point
remainder: &'a Stmt<'a>,
},
Jump(JoinPointId, &'a [Symbol]),
RuntimeError(&'a str),
}
/// in the block below, symbol `scrutinee` is assumed be be of shape `tag_id`
#[derive(Clone, Debug, PartialEq)]
pub enum BranchInfo<'a> {
None,
Constructor {
scrutinee: Symbol,
layout: Layout<'a>,
tag_id: TagIdIntType,
},
}
impl<'a> BranchInfo<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use BranchInfo::*;
match self {
Constructor {
tag_id,
scrutinee,
layout: _,
} if pretty_print_ir_symbols() => alloc
.hardline()
.append(" BranchInfo: { scrutinee: ")
.append(symbol_to_doc(alloc, *scrutinee))
.append(", tag_id: ")
.append(format!("{}", tag_id))
.append("} "),
_ => {
if pretty_print_ir_symbols() {
alloc.text(" <no branch info>")
} else {
alloc.text("")
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ModifyRc {
/// Increment a reference count
Inc(Symbol, u64),
/// Decrement a reference count
Dec(Symbol),
/// A DecRef is a non-recursive reference count decrement
/// e.g. If we Dec a list of lists, then if the reference count of the outer list is one,
/// a Dec will recursively decrement all elements, then free the memory of the outer list.
/// A DecRef would just free the outer list.
/// That is dangerous because you may not free the elements, but in our Zig builtins,
/// sometimes we know we already dealt with the elements (e.g. by copying them all over
/// to a new list) and so we can just do a DecRef, which is much cheaper in such a case.
DecRef(Symbol),
}
impl ModifyRc {
pub fn to_doc<'a, D, A>(self, alloc: &'a D) -> DocBuilder<'a, D, A>
where
D: DocAllocator<'a, A>,
D::Doc: Clone,
A: Clone,
{
use ModifyRc::*;
match self {
Inc(symbol, 1) => alloc
.text("inc ")
.append(symbol_to_doc(alloc, symbol))
.append(";"),
Inc(symbol, n) => alloc
.text("inc ")
.append(alloc.text(format!("{} ", n)))
.append(symbol_to_doc(alloc, symbol))
.append(";"),
Dec(symbol) => alloc
.text("dec ")
.append(symbol_to_doc(alloc, symbol))
.append(";"),
DecRef(symbol) => alloc
.text("decref ")
.append(symbol_to_doc(alloc, symbol))
.append(";"),
}
}
pub fn get_symbol(&self) -> Symbol {
use ModifyRc::*;
match self {
Inc(symbol, _) => *symbol,
Dec(symbol) => *symbol,
DecRef(symbol) => *symbol,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Literal<'a> {
// Literals
/// stored as raw bytes rather than a number to avoid an alignment bump
Int([u8; 16]),
/// stored as raw bytes rather than a number to avoid an alignment bump
U128([u8; 16]),
Float(f64),
/// stored as raw bytes rather than a number to avoid an alignment bump
Decimal([u8; 16]),
Str(&'a str),
/// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
/// so they can (at least potentially) be emitted as 1-bit machine bools.
///
/// So [True, False] compiles to this, and so do [A, B] and [Foo, Bar].
/// However, a union like [True, False, Other Int] would not.
Bool(bool),
/// Closed tag unions containing between 3 and 256 tags (all of 0 arity)
/// compile to bytes, e.g. [Blue, Black, Red, Green, White]
Byte(u8),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ListLiteralElement<'a> {
Literal(Literal<'a>),
Symbol(Symbol),
}
impl<'a> ListLiteralElement<'a> {
pub fn to_symbol(&self) -> Option<Symbol> {
match self {
Self::Symbol(s) => Some(*s),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Call<'a> {
pub call_type: CallType<'a>,
pub arguments: &'a [Symbol],
}
impl<'a> Call<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use CallType::*;
let arguments = self.arguments;
match self.call_type {
CallType::ByName { name, .. } => {
let it = std::iter::once(name.name())
.chain(arguments.iter().copied())
.map(|s| symbol_to_doc(alloc, s));
alloc.text("CallByName ").append(alloc.intersperse(it, " "))
}
LowLevel { op: lowlevel, .. } => {
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text(format!("lowlevel {:?} ", lowlevel))
.append(alloc.intersperse(it, " "))
}
HigherOrder(higher_order) => {
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text(format!("lowlevel {:?} ", higher_order.op))
.append(alloc.intersperse(it, " "))
}
Foreign {
ref foreign_symbol, ..
} => {
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text(format!("foreign {:?} ", foreign_symbol.as_str()))
.append(alloc.intersperse(it, " "))
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CallSpecId {
id: u32,
}
impl CallSpecId {
pub fn to_bytes(self) -> [u8; 4] {
self.id.to_ne_bytes()
}
/// Dummy value for generating refcount helper procs in the backends
/// This happens *after* specialization so it's safe
pub const BACKEND_DUMMY: Self = Self { id: 0 };
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UpdateModeId {
id: u32,
}
impl UpdateModeId {
pub fn to_bytes(self) -> [u8; 4] {
self.id.to_ne_bytes()
}
/// Dummy value for generating refcount helper procs in the backends
/// This happens *after* alias analysis so it's safe
pub const BACKEND_DUMMY: Self = Self { id: 0 };
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UpdateModeIds {
next: u32,
}
impl UpdateModeIds {
pub const fn new() -> Self {
Self { next: 0 }
}
pub fn next_id(&mut self) -> UpdateModeId {
let id = UpdateModeId { id: self.next };
self.next += 1;
id
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CallType<'a> {
ByName {
name: LambdaName<'a>,
ret_layout: &'a Layout<'a>,
arg_layouts: &'a [Layout<'a>],
specialization_id: CallSpecId,
},
Foreign {
foreign_symbol: ForeignSymbol,
ret_layout: &'a Layout<'a>,
},
LowLevel {
op: LowLevel,
update_mode: UpdateModeId,
},
HigherOrder(&'a HigherOrderLowLevel<'a>),
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PassedFunction<'a> {
/// name of the top-level function that is passed as an argument
/// e.g. in `List.map xs Num.abs` this would be `Num.abs`
pub name: LambdaName<'a>,
pub argument_layouts: &'a [Layout<'a>],
pub return_layout: Layout<'a>,
pub specialization_id: CallSpecId,
/// Symbol of the environment captured by the function argument
pub captured_environment: Symbol,
pub owns_captured_environment: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct HigherOrderLowLevel<'a> {
pub op: crate::low_level::HigherOrder,
/// TODO I _think_ we can get rid of this, perhaps only keeping track of
/// the layout of the closure argument, if any
pub closure_env_layout: Option<Layout<'a>>,
/// update mode of the higher order lowlevel itself
pub update_mode: UpdateModeId,
pub passed_function: PassedFunction<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Expr<'a> {
Literal(Literal<'a>),
// Functions
Call(Call<'a>),
Tag {
tag_layout: UnionLayout<'a>,
tag_id: TagIdIntType,
arguments: &'a [Symbol],
},
Struct(&'a [Symbol]),
StructAtIndex {
index: u64,
field_layouts: &'a [Layout<'a>],
structure: Symbol,
},
GetTagId {
structure: Symbol,
union_layout: UnionLayout<'a>,
},
UnionAtIndex {
structure: Symbol,
tag_id: TagIdIntType,
union_layout: UnionLayout<'a>,
index: u64,
},
Array {
elem_layout: Layout<'a>,
elems: &'a [ListLiteralElement<'a>],
},
EmptyArray,
ExprBox {
symbol: Symbol,
},
ExprUnbox {
symbol: Symbol,
},
Reuse {
symbol: Symbol,
update_tag_id: bool,
update_mode: UpdateModeId,
// normal Tag fields
tag_layout: UnionLayout<'a>,
tag_id: TagIdIntType,
arguments: &'a [Symbol],
},
Reset {
symbol: Symbol,
update_mode: UpdateModeId,
},
RuntimeErrorFunction(&'a str),
}
impl<'a> Literal<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use Literal::*;
match self {
Int(bytes) => alloc.text(format!("{}i64", i128::from_ne_bytes(*bytes))),
U128(bytes) => alloc.text(format!("{}u128", u128::from_ne_bytes(*bytes))),
Float(lit) => alloc.text(format!("{}f64", lit)),
Decimal(bytes) => alloc.text(format!("{}dec", RocDec::from_ne_bytes(*bytes))),
Bool(lit) => alloc.text(format!("{}", lit)),
Byte(lit) => alloc.text(format!("{}u8", lit)),
Str(lit) => alloc.text(format!("{:?}", lit)),
}
}
}
pub(crate) fn symbol_to_doc_string(symbol: Symbol) -> String {
use roc_module::ident::ModuleName;
if pretty_print_ir_symbols() {
format!("{:?}", symbol)
} else {
let text = format!("{}", symbol);
if text.starts_with(ModuleName::APP) {
let name: String = text.trim_start_matches(ModuleName::APP).into();
format!("Test{}", name)
} else {
text
}
}
}
fn symbol_to_doc<'b, D, A>(alloc: &'b D, symbol: Symbol) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
alloc.text(symbol_to_doc_string(symbol))
}
fn join_point_to_doc<'b, D, A>(alloc: &'b D, symbol: JoinPointId) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
symbol_to_doc(alloc, symbol.0)
}
impl<'a> Expr<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use Expr::*;
match self {
Literal(lit) => lit.to_doc(alloc),
Call(call) => call.to_doc(alloc),
Tag {
tag_id, arguments, ..
} => {
let doc_tag = alloc
.text("TagId(")
.append(alloc.text(tag_id.to_string()))
.append(")");
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
doc_tag
.append(alloc.space())
.append(alloc.intersperse(it, " "))
}
Reuse {
symbol,
tag_id,
arguments,
update_mode,
..
} => {
let doc_tag = alloc
.text("TagId(")
.append(alloc.text(tag_id.to_string()))
.append(")");
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text("Reuse ")
.append(symbol_to_doc(alloc, *symbol))
.append(alloc.space())
.append(format!("{:?}", update_mode))
.append(alloc.space())
.append(doc_tag)
.append(alloc.space())
.append(alloc.intersperse(it, " "))
}
Reset {
symbol,
update_mode,
} => alloc.text(format!(
"Reset {{ symbol: {:?}, id: {} }}",
symbol, update_mode.id
)),
Struct(args) => {
let it = args.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text("Struct {")
.append(alloc.intersperse(it, ", "))
.append(alloc.text("}"))
}
Array { elems, .. } => {
let it = elems.iter().map(|e| match e {
ListLiteralElement::Literal(l) => l.to_doc(alloc),
ListLiteralElement::Symbol(s) => symbol_to_doc(alloc, *s),
});
alloc
.text("Array [")
.append(alloc.intersperse(it, ", "))
.append(alloc.text("]"))
}
EmptyArray => alloc.text("Array []"),
StructAtIndex {
index, structure, ..
} => alloc
.text(format!("StructAtIndex {} ", index))
.append(symbol_to_doc(alloc, *structure)),
RuntimeErrorFunction(s) => alloc.text(format!("ErrorFunction {}", s)),
GetTagId { structure, .. } => alloc
.text("GetTagId ")
.append(symbol_to_doc(alloc, *structure)),
ExprBox { symbol, .. } => alloc.text("Box ").append(symbol_to_doc(alloc, *symbol)),
ExprUnbox { symbol, .. } => alloc.text("Unbox ").append(symbol_to_doc(alloc, *symbol)),
UnionAtIndex {
tag_id,
structure,
index,
..
} => alloc
.text(format!("UnionAtIndex (Id {}) (Index {}) ", tag_id, index))
.append(symbol_to_doc(alloc, *structure)),
}
}
pub fn to_pretty(&self, width: usize) -> String {
let allocator = BoxAllocator;
let mut w = std::vec::Vec::new();
self.to_doc::<_, ()>(&allocator)
.1
.render(width, &mut w)
.unwrap();
w.push(b'\n');
String::from_utf8(w).unwrap()
}
}
impl<'a> Stmt<'a> {
pub fn new(
env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr,
var: Variable,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
) -> Self {
from_can(env, var, can_expr, procs, layout_cache)
}
pub fn to_doc<'b, D, A, I>(&'b self, alloc: &'b D, interner: &I) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
I: Interner<'a, Layout<'a>>,
{
use Stmt::*;
match self {
Let(symbol, expr, layout, cont) => alloc
.text("let ")
.append(symbol_to_doc(alloc, *symbol))
.append(" : ")
.append(layout.to_doc(alloc, interner, Parens::NotNeeded))
.append(" = ")
.append(expr.to_doc(alloc))
.append(";")
.append(alloc.hardline())
.append(cont.to_doc(alloc, interner)),
Refcounting(modify, cont) => modify
.to_doc(alloc)
.append(alloc.hardline())
.append(cont.to_doc(alloc, interner)),
Expect {
condition,
remainder,
..
} => alloc
.text("expect ")
.append(symbol_to_doc(alloc, *condition))
.append(";")
.append(alloc.hardline())
.append(remainder.to_doc(alloc, interner)),
ExpectFx {
condition,
remainder,
..
} => alloc
.text("expect-fx ")
.append(symbol_to_doc(alloc, *condition))
.append(";")
.append(alloc.hardline())
.append(remainder.to_doc(alloc, interner)),
Ret(symbol) => alloc
.text("ret ")
.append(symbol_to_doc(alloc, *symbol))
.append(";"),
Switch {
cond_symbol,
branches,
default_branch,
..
} => {
match branches {
[(1, info, pass)] => {
let fail = default_branch.1;
alloc
.text("if ")
.append(symbol_to_doc(alloc, *cond_symbol))
.append(" then")
.append(info.to_doc(alloc))
.append(alloc.hardline())
.append(pass.to_doc(alloc, interner).indent(4))
.append(alloc.hardline())
.append(alloc.text("else"))
.append(default_branch.0.to_doc(alloc))
.append(alloc.hardline())
.append(fail.to_doc(alloc, interner).indent(4))
}
_ => {
let default_doc = alloc
.text("default:")
.append(alloc.hardline())
.append(default_branch.1.to_doc(alloc, interner).indent(4))
.indent(4);
let branches_docs = branches
.iter()
.map(|(tag, _info, expr)| {
alloc
.text(format!("case {}:", tag))
.append(alloc.hardline())
.append(expr.to_doc(alloc, interner).indent(4))
.indent(4)
})
.chain(std::iter::once(default_doc));
//
alloc
.text("switch ")
.append(symbol_to_doc(alloc, *cond_symbol))
.append(":")
.append(alloc.hardline())
.append(alloc.intersperse(
branches_docs,
alloc.hardline().append(alloc.hardline()),
))
.append(alloc.hardline())
}
}
}
RuntimeError(s) => alloc.text(format!("Error {}", s)),
Join {
id,
parameters,
body: continuation,
remainder,
} => {
let it = parameters.iter().map(|p| symbol_to_doc(alloc, p.symbol));
alloc.intersperse(
vec![
alloc
.text("joinpoint ")
.append(join_point_to_doc(alloc, *id))
.append(" ".repeat(parameters.len().min(1)))
.append(alloc.intersperse(it, alloc.space()))
.append(":"),
continuation.to_doc(alloc, interner).indent(4),
alloc.text("in"),
remainder.to_doc(alloc, interner),
],
alloc.hardline(),
)
}
Jump(id, arguments) => {
let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s));
alloc
.text("jump ")
.append(join_point_to_doc(alloc, *id))
.append(" ".repeat(arguments.len().min(1)))
.append(alloc.intersperse(it, alloc.space()))
.append(";")
}
}
}
pub fn to_pretty<I>(&self, interner: &I, width: usize) -> String
where
I: Interner<'a, Layout<'a>>,
{
let allocator = BoxAllocator;
let mut w = std::vec::Vec::new();
self.to_doc::<_, (), _>(&allocator, interner)
.1
.render(width, &mut w)
.unwrap();
w.push(b'\n');
String::from_utf8(w).unwrap()
}
pub fn is_terminal(&self) -> bool {
use Stmt::*;
match self {
Switch { .. } => {
// TODO is this the reason Lean only looks at the outermost `when`?
true
}
Ret(_) => true,
Jump(_, _) => true,
_ => false,
}
}
pub fn if_then_else(
arena: &'a Bump,
condition_symbol: Symbol,
return_layout: Layout<'a>,
then_branch_stmt: Stmt<'a>,
else_branch_stmt: &'a Stmt<'a>,
) -> Self {
let then_branch_info = BranchInfo::Constructor {
scrutinee: condition_symbol,
layout: Layout::bool(),
tag_id: 1,
};
let then_branch = (1u64, then_branch_info, then_branch_stmt);
let else_branch_info = BranchInfo::Constructor {
scrutinee: condition_symbol,
layout: Layout::bool(),
tag_id: 0,
};
let else_branch = (else_branch_info, else_branch_stmt);
Stmt::Switch {
cond_symbol: condition_symbol,
cond_layout: Layout::bool(),
branches: &*arena.alloc([then_branch]),
default_branch: else_branch,
ret_layout: return_layout,
}
}
}
fn from_can_let<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
def: Box<roc_can::def::Def>,
cont: Box<Loc<roc_can::expr::Expr>>,
variable: Variable,
opt_assigned_and_hole: Option<(Symbol, &'a Stmt<'a>)>,
) -> Stmt<'a> {
use roc_can::expr::Expr::*;
macro_rules! lower_rest {
($variable:expr, $expr:expr) => {
lower_rest!(env, procs, layout_cache, $variable, $expr)
};
($env:expr, $procs:expr, $layout_cache:expr, $variable:expr, $expr:expr) => {
match opt_assigned_and_hole {
None => from_can($env, $variable, $expr, $procs, $layout_cache),
Some((assigned, hole)) => with_hole(
$env,
$expr,
$variable,
$procs,
$layout_cache,
assigned,
hole,
),
}
};
}
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
return match def.loc_expr.value {
Closure(closure_data) => {
register_capturing_closure(env, procs, layout_cache, *symbol, closure_data);
lower_rest!(variable, cont.value)
}
Accessor(accessor_data) => {
let fresh_record_symbol = env.unique_symbol();
register_noncapturing_closure(
env,
procs,
*symbol,
accessor_data.to_closure_data(fresh_record_symbol),
);
lower_rest!(variable, cont.value)
}
Var(original, _) | AbilityMember(original, _, _) => {
// a variable is aliased, e.g.
//
// foo = bar
//
// or
//
// foo = RBTRee.empty
// TODO: right now we need help out rustc with the closure types;
// it isn't able to infer the right lifetime bounds. See if we
// can remove the annotations in the future.
let build_rest =
|env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>| {
lower_rest!(env, procs, layout_cache, variable, cont.value)
};
return handle_variable_aliasing(
env,
procs,
layout_cache,
def.expr_var,
*symbol,
original,
build_rest,
);
}
LetNonRec(nested_def, nested_cont) => {
use roc_can::expr::Expr::*;
// We must transform
//
// let answer = 1337
// in
// let unused =
// let nested = 17
// in
// nested
// in
// answer
//
// into
//
// let answer = 1337
// in
// let nested = 17
// in
// let unused = nested
// in
// answer
let new_def = roc_can::def::Def {
loc_pattern: def.loc_pattern,
loc_expr: *nested_cont,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
};
let new_inner = LetNonRec(Box::new(new_def), cont);
let new_outer = LetNonRec(nested_def, Box::new(Loc::at_zero(new_inner)));
lower_rest!(variable, new_outer)
}
LetRec(nested_defs, nested_cont, cycle_mark) => {
use roc_can::expr::Expr::*;
// We must transform
//
// let answer = 1337
// in
// let unused =
// let nested = \{} -> nested {}
// in
// nested
// in
// answer
//
// into
//
// let answer = 1337
// in
// let nested = \{} -> nested {}
// in
// let unused = nested
// in
// answer
let new_def = roc_can::def::Def {
loc_pattern: def.loc_pattern,
loc_expr: *nested_cont,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
};
let new_inner = LetNonRec(Box::new(new_def), cont);
let new_outer = LetRec(nested_defs, Box::new(Loc::at_zero(new_inner)), cycle_mark);
lower_rest!(variable, new_outer)
}
_ => {
let rest = lower_rest!(variable, cont.value);
// Remove all the requested symbol specializations now, since this is the
// def site and hence we won't need them any higher up.
let mut needed_specializations = procs.symbol_specializations.remove(*symbol);
match needed_specializations.len() {
0 => {
// We don't need any specializations, that means this symbol is never
// referenced.
with_hole(
env,
def.loc_expr.value,
def.expr_var,
procs,
layout_cache,
*symbol,
env.arena.alloc(rest),
)
}
// We do need specializations
1 => {
let (_specialization_mark, (var, specialized_symbol)) =
needed_specializations.next().unwrap();
// Make sure rigid variables in the annotation are converted to flex variables.
instantiate_rigids(env.subs, def.expr_var);
// Unify the expr_var with the requested specialization once.
let _res = env.unify(
procs.externals_we_need.values_mut(),
layout_cache,
var,
def.expr_var,
);
with_hole(
env,
def.loc_expr.value,
def.expr_var,
procs,
layout_cache,
specialized_symbol,
env.arena.alloc(rest),
)
}
_n => {
let mut stmt = rest;
// Make sure rigid variables in the annotation are converted to flex variables.
instantiate_rigids(env.subs, def.expr_var);
// Need to eat the cost and create a specialized version of the body for
// each specialization.
for (_specialization_mark, (var, specialized_symbol)) in
needed_specializations
{
use roc_can::copy::deep_copy_type_vars_into_expr;
let (new_def_expr_var, specialized_expr) = deep_copy_type_vars_into_expr(
env.subs,
def.expr_var,
&def.loc_expr.value,
)
.expect(
"expr marked as having specializations, but it has no type variables!",
);
let _res = env.unify(
procs.externals_we_need.values_mut(),
layout_cache,
var,
new_def_expr_var,
);
stmt = with_hole(
env,
specialized_expr,
new_def_expr_var,
procs,
layout_cache,
specialized_symbol,
env.arena.alloc(stmt),
);
}
stmt
}
}
}
};
}
// this may be a destructure pattern
let (mono_pattern, assignments) =
match from_can_pattern(env, procs, layout_cache, &def.loc_pattern.value) {
Ok(v) => v,
Err(_) => todo!(),
};
// convert the continuation
let mut stmt = lower_rest!(variable, cont.value);
// layer on any default record fields
for (symbol, variable, expr) in assignments {
let specialization_symbol = procs
.symbol_specializations
.remove_single(symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(symbol);
let hole = env.arena.alloc(stmt);
stmt = with_hole(
env,
expr,
variable,
procs,
layout_cache,
specialization_symbol,
hole,
);
}
match def.loc_expr.value {
roc_can::expr::Expr::Var(outer_symbol, _) if !procs.is_module_thunk(outer_symbol) => {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
}
_ => {
let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
env,
def.loc_expr.value,
def.expr_var,
procs,
layout_cache,
outer_symbol,
env.arena.alloc(stmt),
)
}
}
}
/// turn record/tag patterns into a when expression, e.g.
///
/// foo = \{ x } -> body
///
/// becomes
///
/// foo = \r -> when r is { x } -> body
///
/// conversion of one-pattern when expressions will do the most optimal thing
#[allow(clippy::type_complexity)]
fn patterns_to_when<'a>(
env: &mut Env<'a, '_>,
patterns: std::vec::Vec<(Variable, AnnotatedMark, Loc<roc_can::pattern::Pattern>)>,
body_var: Variable,
body: Loc<roc_can::expr::Expr>,
) -> Result<(Vec<'a, Variable>, Vec<'a, Symbol>, Loc<roc_can::expr::Expr>), Loc<RuntimeError>> {
let mut arg_vars = Vec::with_capacity_in(patterns.len(), env.arena);
let mut symbols = Vec::with_capacity_in(patterns.len(), env.arena);
let mut body = Ok(body);
// patterns that are not yet in a when (e.g. in let or function arguments) must be irrefutable
// to pass type checking. So the order in which we add them to the body does not matter: there
// are only stores anyway, no branches.
//
// NOTE this fails if the pattern contains rigid variables,
// 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) {
// Even if the body was Ok, replace it with this Err.
// If it was already an Err, leave it at that Err, so the first
// RuntimeError we encountered remains the first.
let value = RuntimeError::UnsupportedPattern(pattern.region);
body = body.and({
Err(Loc {
region: pattern.region,
value,
})
});
} else if let Ok(unwrapped_body) = body {
let (new_symbol, new_body) =
pattern_to_when(env, pattern_var, pattern, body_var, unwrapped_body);
symbols.push(new_symbol);
arg_vars.push(pattern_var);
body = Ok(new_body)
}
}
match body {
Ok(body) => Ok((arg_vars, symbols, body)),
Err(loc_error) => Err(loc_error),
}
}
/// turn irrefutable patterns into when. For example
///
/// foo = \{ x } -> body
///
/// Assuming the above program typechecks, the pattern match cannot fail
/// (it is irrefutable). It becomes
///
/// foo = \r ->
/// when r is
/// { x } -> body
///
/// conversion of one-pattern when expressions will do the most optimal thing
fn pattern_to_when<'a>(
env: &mut Env<'a, '_>,
pattern_var: Variable,
pattern: Loc<roc_can::pattern::Pattern>,
body_var: Variable,
body: Loc<roc_can::expr::Expr>,
) -> (Symbol, Loc<roc_can::expr::Expr>) {
use roc_can::expr::Expr::*;
use roc_can::expr::{WhenBranch, WhenBranchPattern};
use roc_can::pattern::Pattern::*;
match &pattern.value {
Identifier(symbol) => (*symbol, body),
Underscore => {
// for underscore we generate a dummy Symbol
(env.unique_symbol(), body)
}
Shadowed(region, loc_ident, new_symbol) => {
let error = roc_problem::can::RuntimeError::Shadowing {
original_region: *region,
shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
};
(*new_symbol, Loc::at_zero(RuntimeError(error)))
}
UnsupportedPattern(region) => {
// create the runtime error here, instead of delegating to When.
// UnsupportedPattern should then never occur in When
let error = roc_problem::can::RuntimeError::UnsupportedPattern(*region);
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
MalformedPattern(problem, region) => {
// create the runtime error here, instead of delegating to When.
let error = roc_problem::can::RuntimeError::MalformedPattern(*problem, *region);
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
OpaqueNotInScope(loc_ident) => {
// create the runtime error here, instead of delegating to When.
// TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
let error = roc_problem::can::RuntimeError::UnsupportedPattern(loc_ident.region);
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
AppliedTag { .. } | RecordDestructure { .. } | UnwrappedOpaque { .. } => {
let symbol = env.unique_symbol();
let wrapped_body = When {
cond_var: pattern_var,
expr_var: body_var,
region: Region::zero(),
loc_cond: Box::new(Loc::at_zero(Var(symbol, pattern_var))),
branches: vec![WhenBranch {
patterns: vec![WhenBranchPattern {
pattern,
degenerate: false,
}],
value: body,
guard: None,
// If this type-checked, it's non-redundant
redundant: RedundantMark::known_non_redundant(),
}],
branches_cond_var: pattern_var,
// If this type-checked, it's exhaustive
exhaustive: ExhaustiveMark::known_exhaustive(),
};
(symbol, Loc::at_zero(wrapped_body))
}
IntLiteral(..)
| NumLiteral(..)
| FloatLiteral(..)
| StrLiteral(..)
| roc_can::pattern::Pattern::SingleQuote(..) => {
// These patters are refutable, and thus should never occur outside a `when` expression
// They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
}
AbilityMemberSpecialization { .. } => {
unreachable!(
"Ability member specialization {:?} should never appear in a when!",
pattern.value
)
}
}
}
fn specialize_suspended<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
suspended: Suspended<'a>,
) {
let offset_variable = StorageSubs::merge_into(suspended.store, env.subs);
for (i, (symbol_or_lambda, var)) in suspended
.symbol_or_lambdas
.iter()
.zip(suspended.variables.iter())
.enumerate()
{
let name = *symbol_or_lambda;
let outside_layout = suspended.layouts[i];
let var = offset_variable(*var);
// TODO define our own Entry for Specialized?
let partial_proc = if procs
.specialized
.is_specialized(name.name(), &outside_layout)
{
// already specialized, just continue
continue;
} else {
match procs.partial_procs.symbol_to_id(name.name()) {
Some(v) => {
// 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
.mark_in_progress(name.name(), outside_layout);
v
}
None => {
// TODO this assumes the specialization is done by another module
// make sure this does not become a problem down the road!
debug_assert!(name.name().module_id() != name.name().module_id());
continue;
}
}
};
match specialize_variable(env, procs, name, layout_cache, var, &[], partial_proc) {
Ok((proc, _layout)) => {
// TODO this code is duplicated elsewhere
// 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.
let arguments = Vec::from_iter_in(proc.args.iter().map(|(l, _)| *l), env.arena)
.into_bump_slice();
let proper_layout = ProcLayout {
arguments,
result: proc.ret_layout,
captures_niche: proc.name.captures_niche(),
};
if procs.is_module_thunk(proc.name.name()) {
debug_assert!(
proper_layout.arguments.is_empty(),
"{:?} from {:?}",
name,
proper_layout
);
}
// NOTE: some functions are specialized to have a closure, but don't actually
// need any closure argument. Here is where we correct this sort of thing,
// by trusting the layout of the Proc, not of what we specialize for
procs
.specialized
.remove_specialized(name.name(), &outside_layout);
procs
.specialized
.insert_specialized(name.name(), proper_layout, proc);
}
Err(SpecializeFailure {
attempted_layout, ..
}) => {
let proc = generate_runtime_error_function(
env,
layout_cache,
name.name(),
attempted_layout,
);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
attempted_layout,
CapturesNiche::no_niche(),
);
procs
.specialized
.insert_specialized(name.name(), top_level, proc);
}
}
}
}
pub fn specialize_all<'a>(
env: &mut Env<'a, '_>,
mut procs: Procs<'a>,
externals_others_need: std::vec::Vec<ExternalSpecializations<'a>>,
specializations_for_host: HostSpecializations<'a>,
layout_cache: &mut LayoutCache<'a>,
) -> Procs<'a> {
// 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(Suspended::new_in(env.arena)),
);
// Add all of our existing pending specializations.
match pending_specializations {
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(),
"{:?}",
&procs.symbol_specializations
);
procs
}
fn specialize_host_specializations<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
host_specializations: HostSpecializations<'a>,
) {
let (store, it) = host_specializations.decompose();
let offset_variable = StorageSubs::merge_into(store, env.subs);
for (symbol, variable, host_exposed_aliases) in it {
specialize_external_help(
env,
procs,
layout_cache,
symbol,
offset_variable(variable),
&host_exposed_aliases,
)
}
}
fn specialize_external_specializations<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
externals_others_need: ExternalSpecializations<'a>,
) {
let (store, it) = externals_others_need.decompose();
let offset_variable = StorageSubs::merge_into(store, env.subs);
for (symbol, solved_types) in it {
for store_variable in solved_types {
let imported_variable = offset_variable(store_variable);
roc_tracing::debug!(proc_name = ?symbol, ?store_variable, ?imported_variable, "specializing needed external");
// historical note: we used to deduplicate with a hash here,
// but the cost of that hash is very high. So for now we make
// duplicate specializations, and the insertion into a hash map
// below will deduplicate them.
specialize_external_help(env, procs, layout_cache, symbol, imported_variable, &[])
}
}
}
fn specialize_external_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
name: LambdaName<'a>,
variable: Variable,
host_exposed_aliases: &[(Symbol, Variable)],
) {
let partial_proc_id = match procs.partial_procs.symbol_to_id(name.name()) {
Some(v) => v,
None => {
panic!("Cannot find a partial proc for {:?}", name);
}
};
let specialization_result = specialize_variable(
env,
procs,
name,
layout_cache,
variable,
host_exposed_aliases,
partial_proc_id,
);
match specialization_result {
Ok((proc, layout)) => {
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
layout,
proc.name.captures_niche(),
);
if procs.is_module_thunk(name.name()) {
debug_assert!(top_level.arguments.is_empty());
}
procs
.specialized
.insert_specialized(name.name(), top_level, proc);
}
Err(SpecializeFailure { attempted_layout }) => {
let proc =
generate_runtime_error_function(env, layout_cache, name.name(), attempted_layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
attempted_layout,
proc.name.captures_niche(),
);
procs
.specialized
.insert_specialized(name.name(), top_level, proc);
}
}
}
fn generate_runtime_error_function<'a>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
name: Symbol,
layout: RawFunctionLayout<'a>,
) -> Proc<'a> {
let mut msg = bumpalo::collections::string::String::with_capacity_in(80, env.arena);
use std::fmt::Write;
write!(
&mut msg,
"The {:?} function could not be generated, likely due to a type error.",
name
)
.unwrap();
dbg_do!(ROC_PRINT_RUNTIME_ERROR_GEN, {
eprintln!(
"emitted runtime error function {:?} for layout {:?}",
&msg, layout
);
});
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
let (args, ret_layout) = match layout {
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
let real_arg_layouts =
lambda_set.extend_argument_list(env.arena, &layout_cache.interner, arg_layouts);
let mut args = Vec::with_capacity_in(real_arg_layouts.len(), env.arena);
for arg in arg_layouts {
args.push((*arg, env.unique_symbol()));
}
if real_arg_layouts.len() != arg_layouts.len() {
args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
}
(args.into_bump_slice(), *ret_layout)
}
RawFunctionLayout::ZeroArgumentThunk(ret_layout) => (&[] as &[_], ret_layout),
};
Proc {
name: LambdaName::no_niche(name),
args,
body: runtime_error,
closure_data_layout: None,
ret_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
}
}
/// A snapshot of the state of types at a moment in time.
/// Includes the exact types, but also auxiliary information like layouts.
struct TypeStateSnapshot {
subs_snapshot: roc_types::subs::SubsSnapshot,
layout_snapshot: crate::layout::CacheSnapshot,
external_storage_snapshot: VecMap<ModuleId, ExternalModuleStorageSnapshot>,
}
/// Takes a snapshot of the type state. Snapshots should be taken before new specializations, and
/// accordingly [rolled back][rollback_typestate] a specialization is complete, so as to not
/// interfere with other specializations.
fn snapshot_typestate(
subs: &mut Subs,
procs: &mut Procs,
layout_cache: &mut LayoutCache<'_>,
) -> TypeStateSnapshot {
TypeStateSnapshot {
subs_snapshot: subs.snapshot(),
layout_snapshot: layout_cache.snapshot(),
external_storage_snapshot: procs
.externals_we_need
.iter_mut()
.map(|(module, es)| (*module, es.snapshot_cache()))
.collect(),
}
}
/// Rolls back the type state to the given [snapshot].
/// Should be called after a specialization is complete to avoid interfering with other
/// specializations.
fn rollback_typestate(
subs: &mut Subs,
procs: &mut Procs,
layout_cache: &mut LayoutCache<'_>,
snapshot: TypeStateSnapshot,
) {
let TypeStateSnapshot {
subs_snapshot,
layout_snapshot,
mut external_storage_snapshot,
} = snapshot;
subs.rollback_to(subs_snapshot);
layout_cache.rollback_to(layout_snapshot);
for (module, es) in procs.externals_we_need.iter_mut() {
if let Some((_, snapshot)) = external_storage_snapshot.remove(module) {
es.rollback_cache(snapshot);
} else {
es.invalidate_whole_cache();
}
}
}
/// Specialize a single proc.
///
/// The caller should snapshot and rollback the type state before and after calling this function,
/// respectively. This function will not take snapshots itself, but will modify the type state.
fn specialize_proc_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
lambda_name: LambdaName<'a>,
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
host_exposed_variables: &[(Symbol, Variable)],
partial_proc_id: PartialProcId,
) -> Result<Proc<'a>, LayoutProblem> {
let partial_proc = procs.partial_procs.get_id(partial_proc_id);
let captured_symbols = partial_proc.captured_symbols;
let _unified = env.unify(
procs.externals_we_need.values_mut(),
layout_cache,
partial_proc.annotation,
fn_var,
);
// This will not hold for programs with type errors
// let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
// debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
// if this is a closure, add the closure record argument
let pattern_symbols = match partial_proc.captured_symbols {
CapturedSymbols::None => partial_proc.pattern_symbols,
CapturedSymbols::Captured([]) => partial_proc.pattern_symbols,
CapturedSymbols::Captured(_) => {
let mut temp =
Vec::from_iter_in(partial_proc.pattern_symbols.iter().copied(), env.arena);
temp.push(Symbol::ARG_CLOSURE);
temp.into_bump_slice()
}
};
let specialized =
build_specialized_proc_from_var(env, layout_cache, lambda_name, pattern_symbols, fn_var)?;
let recursivity = if partial_proc.is_self_recursive {
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
} else {
SelfRecursive::NotSelfRecursive
};
let body = partial_proc.body.clone();
let body_var = partial_proc.body_var;
// determine the layout of aliases/rigids exposed to the host
let host_exposed_layouts = if host_exposed_variables.is_empty() {
HostExposedLayouts::NotHostExposed
} else {
let mut aliases = BumpMap::new_in(env.arena);
for (symbol, variable) in host_exposed_variables {
let layout = layout_cache
.raw_from_var(env.arena, *variable, env.subs)
.unwrap();
let name = env.unique_symbol();
match layout {
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => {
let assigned = env.unique_symbol();
let mut argument_symbols =
Vec::with_capacity_in(argument_layouts.len(), env.arena);
let mut proc_arguments =
Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
let mut top_level_arguments =
Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
for layout in argument_layouts {
let symbol = env.unique_symbol();
proc_arguments.push((*layout, symbol));
argument_symbols.push(symbol);
top_level_arguments.push(*layout);
}
// the proc needs to take an extra closure argument
let lambda_set_layout = Layout::LambdaSet(lambda_set);
proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE));
// this should also be reflected in the TopLevel signature
top_level_arguments.push(lambda_set_layout);
let hole = env.arena.alloc(Stmt::Ret(assigned));
let body = match_on_lambda_set(
env,
layout_cache,
procs,
lambda_set,
Symbol::ARG_CLOSURE,
argument_symbols.into_bump_slice(),
argument_layouts,
return_layout,
assigned,
hole,
);
let proc = Proc {
name: LambdaName::no_niche(name),
args: proc_arguments.into_bump_slice(),
body,
closure_data_layout: None,
ret_layout: *return_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
let top_level = ProcLayout::new(
env.arena,
top_level_arguments.into_bump_slice(),
CapturesNiche::no_niche(),
*return_layout,
);
procs.specialized.insert_specialized(name, top_level, proc);
aliases.insert(*symbol, (name, top_level, layout));
}
RawFunctionLayout::ZeroArgumentThunk(result) => {
let assigned = env.unique_symbol();
let hole = env.arena.alloc(Stmt::Ret(assigned));
let forced = force_thunk(env, lambda_name.name(), result, assigned, hole);
let proc = Proc {
name: LambdaName::no_niche(name),
args: &[],
body: forced,
closure_data_layout: None,
ret_layout: result,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
layout,
CapturesNiche::no_niche(),
);
procs.specialized.insert_specialized(name, top_level, proc);
aliases.insert(
*symbol,
(
name,
ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), result),
layout,
),
);
}
}
}
HostExposedLayouts::HostExposed {
rigids: BumpMap::new_in(env.arena),
aliases,
}
};
let mut specialized_body = from_can(env, body_var, body, procs, layout_cache);
let specialized_proc = match specialized {
SpecializedLayout::FunctionPointerBody {
ret_layout,
closure: opt_closure_layout,
} => {
// this is a function body like
//
// foo = Num.add
//
// we need to expand this to
//
// foo = \x,y -> Num.add x y
let closure_data_layout = match opt_closure_layout {
Some(lambda_set) => Layout::LambdaSet(lambda_set),
None => Layout::UNIT,
};
// I'm not sure how to handle the closure case, does it ever occur?
debug_assert!(matches!(captured_symbols, CapturedSymbols::None));
Proc {
name: lambda_name,
args: &[],
body: specialized_body,
closure_data_layout: Some(closure_data_layout),
ret_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
}
}
SpecializedLayout::FunctionBody {
arguments: proc_args,
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)) => {
// debug_assert!(!captured.is_empty());
// An argument from the closure list may have taken on a specialized symbol
// name during the evaluation of the def body. If this is the case, load the
// specialized name rather than the original captured name!
let mut get_specialized_name = |symbol| {
procs
.symbol_specializations
.remove_single(symbol)
.unwrap_or(symbol)
};
match closure_layout
.layout_for_member_with_lambda_name(&layout_cache.interner, lambda_name)
{
ClosureRepresentation::Union {
alphabetic_order_fields: field_layouts,
union_layout,
tag_id,
..
} => {
debug_assert!(matches!(
union_layout,
UnionLayout::NonRecursive(_)
| UnionLayout::Recursive(_)
| UnionLayout::NullableUnwrapped { .. }
));
debug_assert_eq!(field_layouts.len(), captured.len());
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
let mut combined = Vec::from_iter_in(
captured.iter().map(|(x, _)| x).zip(field_layouts.iter()),
env.arena,
);
let ptr_bytes = env.target_info;
combined.sort_by(|(_, layout1), (_, layout2)| {
let size1 =
layout1.alignment_bytes(&layout_cache.interner, ptr_bytes);
let size2 =
layout2.alignment_bytes(&layout_cache.interner, ptr_bytes);
size2.cmp(&size1)
});
for (index, (symbol, _)) in combined.iter().enumerate() {
let layout = union_layout.layout_at(tag_id, index);
let expr = Expr::UnionAtIndex {
tag_id,
structure: Symbol::ARG_CLOSURE,
index: index as u64,
union_layout,
};
let symbol = get_specialized_name(**symbol);
specialized_body = Stmt::Let(
symbol,
expr,
layout,
env.arena.alloc(specialized_body),
);
}
}
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,
);
let ptr_bytes = env.target_info;
combined.sort_by(|(_, layout1), (_, layout2)| {
let size1 =
layout1.alignment_bytes(&layout_cache.interner, ptr_bytes);
let size2 =
layout2.alignment_bytes(&layout_cache.interner, ptr_bytes);
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(),
ordered_field_layouts.len(),
"{:?} captures {:?} but has layout {:?}",
lambda_name,
&captured,
&ordered_field_layouts
);
for (index, (symbol, layout)) in combined.iter().enumerate() {
let expr = Expr::StructAtIndex {
index: index as _,
field_layouts: ordered_field_layouts,
structure: Symbol::ARG_CLOSURE,
};
let symbol = get_specialized_name(**symbol);
specialized_body = Stmt::Let(
symbol,
expr,
**layout,
env.arena.alloc(specialized_body),
);
}
}
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?"),
}
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);
});
let closure_data_layout = match opt_closure_layout {
Some(lambda_set) => Some(Layout::LambdaSet(lambda_set)),
None => None,
};
Proc {
name: lambda_name,
args: proc_args.into_bump_slice(),
body: specialized_body,
closure_data_layout,
ret_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
}
}
};
Ok(specialized_proc)
}
#[derive(Debug)]
enum SpecializedLayout<'a> {
/// A body like `foo = \a,b,c -> ...`
FunctionBody {
arguments: &'a [(Layout<'a>, Symbol)],
closure: Option<LambdaSet<'a>>,
ret_layout: Layout<'a>,
},
/// A body like `foo = Num.add`
FunctionPointerBody {
closure: Option<LambdaSet<'a>>,
ret_layout: Layout<'a>,
},
}
#[allow(clippy::type_complexity)]
fn build_specialized_proc_from_var<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
lambda_name: LambdaName<'a>,
pattern_symbols: &[Symbol],
fn_var: Variable,
) -> Result<SpecializedLayout<'a>, LayoutProblem> {
match layout_cache.raw_from_var(env.arena, fn_var, env.subs)? {
RawFunctionLayout::Function(pattern_layouts, closure_layout, ret_layout) => {
let mut pattern_layouts_vec = Vec::with_capacity_in(pattern_layouts.len(), env.arena);
pattern_layouts_vec.extend_from_slice(pattern_layouts);
build_specialized_proc(
env.arena,
lambda_name,
pattern_symbols,
pattern_layouts_vec,
Some(closure_layout),
*ret_layout,
)
}
RawFunctionLayout::ZeroArgumentThunk(ret_layout) => {
// a top-level constant 0-argument thunk
build_specialized_proc(
env.arena,
lambda_name,
pattern_symbols,
Vec::new_in(env.arena),
None,
ret_layout,
)
}
}
}
#[allow(clippy::type_complexity)]
fn build_specialized_proc<'a>(
arena: &'a Bump,
lambda_name: LambdaName<'a>,
pattern_symbols: &[Symbol],
pattern_layouts: Vec<'a, Layout<'a>>,
lambda_set: Option<LambdaSet<'a>>,
ret_layout: Layout<'a>,
) -> Result<SpecializedLayout<'a>, LayoutProblem> {
use SpecializedLayout::*;
let mut proc_args = Vec::with_capacity_in(pattern_layouts.len(), arena);
let pattern_layouts_len = pattern_layouts.len();
for (arg_layout, arg_name) in pattern_layouts.into_iter().zip(pattern_symbols.iter()) {
proc_args.push((arg_layout, *arg_name));
}
// Given
//
// foo =
// x = 42
//
// f = \{} -> x
//
// We desugar that into
//
// f = \{}, x -> x
//
// foo =
// x = 42
//
// f_closure = { ptr: f, closure: x }
//
// then
let proc_name = lambda_name.name();
match lambda_set {
Some(lambda_set) 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::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
debug_assert_eq!(
pattern_layouts_len + 1,
pattern_symbols.len(),
"Tried to zip two vecs with different lengths in {:?}!",
proc_name,
);
let proc_args = proc_args.into_bump_slice();
Ok(FunctionBody {
arguments: proc_args,
closure: Some(lambda_set),
ret_layout,
})
}
Some(lambda_set) => {
// a function that returns a function, but is not itself a closure
// e.g. f = Num.add
// make sure there is not arg_closure argument without a closure layout
debug_assert!(pattern_symbols.last() != Some(&Symbol::ARG_CLOSURE));
use std::cmp::Ordering;
match pattern_layouts_len.cmp(&pattern_symbols.len()) {
Ordering::Equal => {
let proc_args = proc_args.into_bump_slice();
Ok(FunctionBody {
arguments: proc_args,
closure: None,
ret_layout,
})
}
Ordering::Greater => {
if pattern_symbols.is_empty() {
let ret_layout = Layout::LambdaSet(lambda_set);
Ok(FunctionPointerBody {
closure: None,
ret_layout,
})
} else {
// so far, the problem when hitting this branch was always somewhere else
// I think this branch should not be reachable in a bugfree compiler
panic!(
"more arguments (according to the layout) than argument symbols for {:?}",
proc_name
)
}
}
Ordering::Less => panic!(
"more argument symbols than arguments (according to the layout) for {:?}",
proc_name
),
}
}
None => {
// else we're making a normal function, no closure problems to worry about
// we'll just assert some things
// make sure there is not arg_closure argument without a closure layout
debug_assert!(pattern_symbols.last() != Some(&Symbol::ARG_CLOSURE));
use std::cmp::Ordering;
match pattern_layouts_len.cmp(&pattern_symbols.len()) {
Ordering::Equal => {
let proc_args = proc_args.into_bump_slice();
Ok(FunctionBody {
arguments: proc_args,
closure: None,
ret_layout,
})
}
Ordering::Greater => {
if pattern_symbols.is_empty() {
Ok(FunctionPointerBody {
closure: None,
ret_layout,
})
} else {
// so far, the problem when hitting this branch was always somewhere else
// I think this branch should not be reachable in a bugfree compiler
panic!(
"more arguments (according to the layout) than argument symbols for {:?}",
proc_name
)
}
}
Ordering::Less => panic!(
"more argument symbols than arguments (according to the layout) for {:?}",
proc_name
),
}
}
}
}
#[derive(Debug)]
struct SpecializeFailure<'a> {
/// The layout we attempted to create
attempted_layout: RawFunctionLayout<'a>,
}
type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>);
fn specialize_variable<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
proc_name: LambdaName<'a>,
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
host_exposed_variables: &[(Symbol, Variable)],
partial_proc_id: PartialProcId,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
let snapshot = snapshot_typestate(env.subs, procs, layout_cache);
// for debugging only
let raw = layout_cache
.raw_from_var(env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let raw = if procs.is_module_thunk(proc_name.name()) {
match raw {
RawFunctionLayout::Function(_, lambda_set, _) => {
RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set))
}
_ => raw,
}
} else {
raw
};
// make sure rigid variables in the annotation are converted to flex variables
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());
roc_tracing::debug!(?proc_name, ?fn_var, fn_content = ?roc_types::subs::SubsFmtContent(env.subs.get_content_without_compacting(fn_var), env.subs), "specialization start");
let specialized = specialize_proc_help(
env,
procs,
proc_name,
layout_cache,
fn_var,
host_exposed_variables,
partial_proc_id,
);
roc_tracing::debug!(
?proc_name,
succeeded = specialized.is_ok(),
"specialization end"
);
procs.pop_active_specialization(proc_name.name());
let result = match specialized {
Ok(proc) => {
// when successful, the layout after unification should be the layout before unification
// debug_assert_eq!(
// attempted_layout,
// layout_cache
// .from_var(env.arena, fn_var, env.subs)
// .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err))
// );
Ok((proc, raw))
}
Err(error) => {
// earlier we made this information available where we handle the failure
// but we didn't do anything useful with it. So it's here if we ever need it again
let _ = error;
Err(SpecializeFailure {
attempted_layout: raw,
})
}
};
rollback_typestate(env.subs, procs, layout_cache, snapshot);
result
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ProcLayout<'a> {
pub arguments: &'a [Layout<'a>],
pub result: Layout<'a>,
pub captures_niche: CapturesNiche<'a>,
}
impl<'a> ProcLayout<'a> {
pub fn new(
arena: &'a Bump,
old_arguments: &'a [Layout<'a>],
old_captures_niche: CapturesNiche<'a>,
result: Layout<'a>,
) -> Self {
let mut arguments = Vec::with_capacity_in(old_arguments.len(), arena);
for old in old_arguments {
let other = old;
arguments.push(*other);
}
let other = result;
let new_result = other;
ProcLayout {
arguments: arguments.into_bump_slice(),
captures_niche: old_captures_niche,
result: new_result,
}
}
pub fn from_raw(
arena: &'a Bump,
interner: &LayoutInterner<'a>,
raw: RawFunctionLayout<'a>,
captures_niche: CapturesNiche<'a>,
) -> Self {
match raw {
RawFunctionLayout::Function(arguments, lambda_set, result) => {
let arguments = lambda_set.extend_argument_list(arena, interner, arguments);
ProcLayout::new(arena, arguments, captures_niche, *result)
}
RawFunctionLayout::ZeroArgumentThunk(result) => {
ProcLayout::new(arena, &[], CapturesNiche::no_niche(), result)
}
}
}
}
fn specialize_naked_symbol<'a>(
env: &mut Env<'a, '_>,
variable: Variable,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
symbol: Symbol,
) -> Stmt<'a> {
if procs.is_module_thunk(symbol) {
let fn_var = variable;
// This is a top-level declaration, which will code gen to a 0-arity thunk.
let result = call_by_name(
env,
procs,
fn_var,
symbol,
std::vec::Vec::new(),
layout_cache,
assigned,
hole,
);
return result;
} else if env.is_imported_symbol(symbol) {
match layout_cache.from_var(env.arena, variable, env.subs) {
Err(e) => panic!("invalid layout {:?}", e),
Ok(_) => {
// this is a 0-arity thunk
let result = call_by_name(
env,
procs,
variable,
symbol,
std::vec::Vec::new(),
layout_cache,
assigned,
match hole {
Stmt::Jump(id, _) => env
.arena
.alloc(Stmt::Jump(*id, env.arena.alloc([assigned]))),
Stmt::Ret(_) => env.arena.alloc(Stmt::Ret(assigned)),
hole => hole,
},
);
return result;
}
}
}
let result = match hole {
Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([symbol])),
_ => Stmt::Ret(symbol),
};
// if the symbol is a function symbol, ensure it is properly specialized!
let original = symbol;
let opt_fn_var = Some(variable);
// if this is a function symbol, ensure that it's properly specialized!
specialize_symbol(
env,
procs,
layout_cache,
opt_fn_var,
symbol,
result,
original,
)
}
fn try_make_literal<'a>(can_expr: &roc_can::expr::Expr, layout: Layout<'a>) -> Option<Literal<'a>> {
use roc_can::expr::Expr::*;
match can_expr {
Int(_, _, int_str, int, _bound) => {
Some(make_num_literal(layout, int_str, IntOrFloatValue::Int(*int)).to_expr_literal())
}
Float(_, _, float_str, float, _bound) => Some(
make_num_literal(layout, float_str, IntOrFloatValue::Float(*float)).to_expr_literal(),
),
// TODO investigate lifetime trouble
// Str(string) => Some(Literal::Str(env.arena.alloc(string))),
Num(_, num_str, num, _bound) => {
Some(make_num_literal(layout, num_str, IntOrFloatValue::Int(*num)).to_expr_literal())
}
_ => None,
}
}
pub fn with_hole<'a>(
env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr,
variable: Variable,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
use roc_can::expr::Expr::*;
let arena = env.arena;
match can_expr {
Int(_, _, int_str, int, _bound) => assign_num_literal_expr(
env,
layout_cache,
assigned,
variable,
&int_str,
IntOrFloatValue::Int(int),
hole,
),
Float(_, _, float_str, float, _bound) => assign_num_literal_expr(
env,
layout_cache,
assigned,
variable,
&float_str,
IntOrFloatValue::Float(float),
hole,
),
Num(_, num_str, num, _bound) => assign_num_literal_expr(
env,
layout_cache,
assigned,
variable,
&num_str,
IntOrFloatValue::Int(num),
hole,
),
Str(string) => Stmt::Let(
assigned,
Expr::Literal(Literal::Str(arena.alloc(string))),
Layout::Builtin(Builtin::Str),
hole,
),
SingleQuote(_, _, character, _) => {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
Stmt::Let(
assigned,
Expr::Literal(Literal::Int((character as i128).to_ne_bytes())),
layout,
hole,
)
}
LetNonRec(def, cont) => from_can_let(
env,
procs,
layout_cache,
def,
cont,
variable,
Some((assigned, hole)),
),
LetRec(defs, cont, _cycle_mark) => {
// because Roc is strict, only functions can be recursive!
for def in defs.into_iter() {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure(closure_data) = def.loc_expr.value {
register_noncapturing_closure(env, procs, *symbol, closure_data);
continue;
}
}
unreachable!("recursive value does not have Identifier pattern")
}
with_hole(
env,
cont.value,
variable,
procs,
layout_cache,
assigned,
hole,
)
}
Var(mut symbol, _) => {
// If this symbol is a raw value, find the real name we gave to its specialized usage.
if let ReuseSymbol::Value(_symbol) = can_reuse_symbol(
env,
procs,
&roc_can::expr::Expr::Var(symbol, variable),
variable,
) {
let real_symbol =
procs
.symbol_specializations
.get_or_insert(env, layout_cache, symbol, variable);
symbol = real_symbol;
}
specialize_naked_symbol(env, variable, procs, layout_cache, assigned, hole, symbol)
}
AbilityMember(member, specialization_id, specialization_var) => {
let specialization_symbol = late_resolve_ability_specialization(
env,
member,
specialization_id,
specialization_var,
);
specialize_naked_symbol(
env,
variable,
procs,
layout_cache,
assigned,
hole,
specialization_symbol,
)
}
Tag {
tag_union_var: variant_var,
name: tag_name,
arguments: args,
..
} => {
let arena = env.arena;
debug_assert!(!matches!(
env.subs.get_content_without_compacting(variant_var),
Content::Structure(FlatType::Func(_, _, _))
));
convert_tag_union(
env,
variant_var,
assigned,
hole,
tag_name,
procs,
layout_cache,
args,
arena,
)
}
ZeroArgumentTag {
variant_var: _,
name: tag_name,
ext_var,
closure_name,
} => {
let arena = env.arena;
let content = env.subs.get_content_without_compacting(variable);
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = content {
let ret_var = *ret_var;
let arg_vars = *arg_vars;
tag_union_to_function(
env,
arg_vars,
ret_var,
tag_name,
closure_name,
ext_var,
procs,
variable,
layout_cache,
assigned,
hole,
)
} else {
convert_tag_union(
env,
variable,
assigned,
hole,
tag_name,
procs,
layout_cache,
std::vec::Vec::new(),
arena,
)
}
}
OpaqueRef { argument, .. } => {
let (arg_var, loc_arg_expr) = *argument;
match can_reuse_symbol(env, procs, &loc_arg_expr.value, arg_var) {
// Opaques decay to their argument.
ReuseSymbol::Value(symbol) => {
let real_name = procs.symbol_specializations.get_or_insert(
env,
layout_cache,
symbol,
arg_var,
);
let mut result = hole.clone();
substitute_in_exprs(arena, &mut result, assigned, real_name);
result
}
_ => with_hole(
env,
loc_arg_expr.value,
arg_var,
procs,
layout_cache,
assigned,
hole,
),
}
}
Record {
record_var,
mut fields,
..
} => {
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
layout::sort_record_fields(&mut layout_env, record_var)
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
};
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
#[allow(clippy::enum_variant_names)]
enum Field {
// TODO: rename this since it can handle unspecialized expressions now too
FunctionOrUnspecialized(Symbol, Variable),
ValueSymbol,
Field(roc_can::expr::Field),
}
for (label, variable, _) in sorted_fields.into_iter() {
// TODO how should function pointers be handled here?
use ReuseSymbol::*;
match fields.remove(&label) {
Some(field) => {
match can_reuse_symbol(env, procs, &field.loc_expr.value, field.var) {
Imported(symbol)
| LocalFunction(symbol)
| UnspecializedExpr(symbol) => {
field_symbols.push(symbol);
can_fields.push(Field::FunctionOrUnspecialized(symbol, variable));
}
Value(symbol) => {
let reusable = procs.symbol_specializations.get_or_insert(
env,
layout_cache,
symbol,
field.var,
);
field_symbols.push(reusable);
can_fields.push(Field::ValueSymbol);
}
NotASymbol => {
field_symbols.push(env.unique_symbol());
can_fields.push(Field::Field(field));
}
}
}
None => {
// this field was optional, but not given
continue;
}
}
}
// creating a record from the var will unpack it if it's just a single field.
let layout = match layout_cache.from_var(env.arena, record_var, env.subs) {
Ok(layout) => layout,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
};
let field_symbols = field_symbols.into_bump_slice();
let mut stmt = if let [only_field] = field_symbols {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
hole
} else {
Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole)
};
for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev())
{
match opt_field {
Field::ValueSymbol => {
// this symbol is already defined; nothing to do
}
Field::FunctionOrUnspecialized(symbol, variable) => {
stmt = specialize_symbol(
env,
procs,
layout_cache,
Some(variable),
symbol,
stmt,
symbol,
);
}
Field::Field(field) => {
stmt = with_hole(
env,
field.loc_expr.value,
field.var,
procs,
layout_cache,
*symbol,
env.arena.alloc(stmt),
);
}
}
}
stmt
}
EmptyRecord => let_empty_struct(assigned, hole),
Expect { .. } => unreachable!("I think this is unreachable"),
ExpectFx { .. } => unreachable!("I think this is unreachable"),
If {
cond_var,
branch_var,
branches,
final_else,
} => {
match (
layout_cache.from_var(env.arena, branch_var, env.subs),
layout_cache.from_var(env.arena, cond_var, env.subs),
) {
(Ok(ret_layout), Ok(cond_layout)) => {
// if the hole is a return, then we don't need to merge the two
// branches together again, we can just immediately return
let is_terminated = matches!(hole, Stmt::Ret(_));
if is_terminated {
let terminator = hole;
let mut stmt = with_hole(
env,
final_else.value,
branch_var,
procs,
layout_cache,
assigned,
terminator,
);
for (loc_cond, loc_then) in branches.into_iter().rev() {
let branching_symbol = env.unique_symbol();
let then = with_hole(
env,
loc_then.value,
branch_var,
procs,
layout_cache,
assigned,
terminator,
);
stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout);
// add condition
stmt = with_hole(
env,
loc_cond.value,
cond_var,
procs,
layout_cache,
branching_symbol,
env.arena.alloc(stmt),
);
}
stmt
} else {
let assigned_in_jump = env.unique_symbol();
let id = JoinPointId(env.unique_symbol());
let terminator = env
.arena
.alloc(Stmt::Jump(id, env.arena.alloc([assigned_in_jump])));
let mut stmt = with_hole(
env,
final_else.value,
branch_var,
procs,
layout_cache,
assigned_in_jump,
terminator,
);
for (loc_cond, loc_then) in branches.into_iter().rev() {
let branching_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_cond.value,
cond_var,
);
let then = with_hole(
env,
loc_then.value,
branch_var,
procs,
layout_cache,
assigned_in_jump,
terminator,
);
stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout);
// add condition
stmt = assign_to_symbol(
env,
procs,
layout_cache,
cond_var,
loc_cond,
branching_symbol,
stmt,
);
}
let layout = layout_cache
.from_var(env.arena, branch_var, env.subs)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
let param = Param {
symbol: assigned,
layout,
borrow: false,
};
Stmt::Join {
id,
parameters: env.arena.alloc([param]),
remainder: env.arena.alloc(stmt),
body: hole,
}
}
}
(Err(_), _) => Stmt::RuntimeError("invalid ret_layout"),
(_, Err(_)) => Stmt::RuntimeError("invalid cond_layout"),
}
}
When {
cond_var,
expr_var,
region: _,
loc_cond,
branches,
branches_cond_var: _,
exhaustive,
} => {
let cond_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_cond.value,
cond_var,
);
let id = JoinPointId(env.unique_symbol());
let mut stmt = from_can_when(
env,
cond_var,
expr_var,
cond_symbol,
branches,
exhaustive,
layout_cache,
procs,
Some(id),
);
// define the `when` condition
stmt = assign_to_symbol(
env,
procs,
layout_cache,
cond_var,
*loc_cond,
cond_symbol,
stmt,
);
let layout = layout_cache
.from_var(env.arena, expr_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
let param = Param {
symbol: assigned,
layout,
borrow: false,
};
Stmt::Join {
id,
parameters: env.arena.alloc([param]),
remainder: env.arena.alloc(stmt),
body: env.arena.alloc(hole),
}
}
List {
loc_elems,
elem_var,
..
} if loc_elems.is_empty() => {
// because an empty list has an unknown element type, it is handled differently
let opt_elem_layout = layout_cache.from_var(env.arena, elem_var, env.subs);
match opt_elem_layout {
Ok(elem_layout) => {
let expr = Expr::EmptyArray;
Stmt::Let(
assigned,
expr,
Layout::Builtin(Builtin::List(env.arena.alloc(elem_layout))),
hole,
)
}
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
let expr = Expr::EmptyArray;
Stmt::Let(
assigned,
expr,
Layout::Builtin(Builtin::List(&Layout::VOID)),
hole,
)
}
Err(LayoutProblem::Erroneous) => panic!("list element is error type"),
}
}
List {
elem_var,
loc_elems,
} => {
let mut arg_symbols = Vec::with_capacity_in(loc_elems.len(), env.arena);
let mut elements = Vec::with_capacity_in(loc_elems.len(), env.arena);
let mut symbol_exprs = Vec::with_capacity_in(loc_elems.len(), env.arena);
let elem_layout = layout_cache
.from_var(env.arena, elem_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
for arg_expr in loc_elems.into_iter() {
if let Some(literal) = try_make_literal(&arg_expr.value, elem_layout) {
elements.push(ListLiteralElement::Literal(literal));
} else {
let symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&arg_expr.value,
elem_var,
);
elements.push(ListLiteralElement::Symbol(symbol));
arg_symbols.push(symbol);
symbol_exprs.push(arg_expr);
}
}
let arg_symbols = arg_symbols.into_bump_slice();
let expr = Expr::Array {
elem_layout,
elems: elements.into_bump_slice(),
};
let stmt = Stmt::Let(
assigned,
expr,
Layout::Builtin(Builtin::List(env.arena.alloc(elem_layout))),
hole,
);
let iter = symbol_exprs
.into_iter()
.rev()
.map(|e| (elem_var, e))
.zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
Access {
record_var,
field_var,
field,
loc_expr,
..
} => {
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
layout::sort_record_fields(&mut layout_env, record_var)
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't access record with improper layout"),
};
let mut index = None;
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut current = 0;
for (label, _, opt_field_layout) in sorted_fields.into_iter() {
match opt_field_layout {
Err(_) => {
// this was an optional field, and now does not exist!
// do not increment `current`!
}
Ok(field_layout) => {
field_layouts.push(field_layout);
if label == field {
index = Some(current);
}
current += 1;
}
}
}
let record_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_expr.value,
record_var,
);
let mut stmt = match field_layouts.as_slice() {
[_] => {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, record_symbol);
hole
}
_ => {
let expr = Expr::StructAtIndex {
index: index.expect("field not in its own type") as u64,
field_layouts: field_layouts.into_bump_slice(),
structure: record_symbol,
};
let layout = layout_cache
.from_var(env.arena, field_var, env.subs)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
Stmt::Let(assigned, expr, layout, hole)
}
};
stmt = assign_to_symbol(
env,
procs,
layout_cache,
record_var,
*loc_expr,
record_symbol,
stmt,
);
stmt
}
Accessor(accessor_data) => {
let field_var = accessor_data.field_var;
let fresh_record_symbol = env.unique_symbol();
let ClosureData {
name,
function_type,
arguments,
loc_body,
..
} = accessor_data.to_closure_data(fresh_record_symbol);
match procs.insert_anonymous(
env,
LambdaName::no_niche(name),
function_type,
arguments,
*loc_body,
CapturedSymbols::None,
field_var,
layout_cache,
) {
Ok(_) => {
let raw_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, function_type, env.subs),
"Expr::Accessor"
);
match raw_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, name, &[]);
construct_closure_data(
env,
layout_cache,
lambda_set,
lambda_name,
&[],
assigned,
hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(),
}
}
Err(_error) => Stmt::RuntimeError(
"TODO convert anonymous function error to a RuntimeError string",
),
}
}
OpaqueWrapFunction(wrap_fn_data) => {
let opaque_var = wrap_fn_data.opaque_var;
let arg_symbol = env.unique_symbol();
let ClosureData {
name,
function_type,
arguments,
loc_body,
..
} = wrap_fn_data.to_closure_data(arg_symbol);
match procs.insert_anonymous(
env,
LambdaName::no_niche(name),
function_type,
arguments,
*loc_body,
CapturedSymbols::None,
opaque_var,
layout_cache,
) {
Ok(_) => {
let raw_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, function_type, env.subs),
"Expr::OpaqueWrapFunction"
);
match raw_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, name, &[]);
construct_closure_data(
env,
layout_cache,
lambda_set,
lambda_name,
&[],
assigned,
hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
internal_error!("should not be a thunk!")
}
}
}
Err(_error) => Stmt::RuntimeError(
"TODO convert anonymous function error to a RuntimeError string",
),
}
}
Update {
record_var,
symbol: structure,
updates,
..
} => {
use FieldType::*;
enum FieldType<'a> {
CopyExisting(u64),
UpdateExisting(&'a roc_can::expr::Field),
}
// Strategy: turn a record update into the creation of a new record.
// This has the benefit that we don't need to do anything special for reference
// counting
let sorted_fields_result = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
layout::sort_record_fields(&mut layout_env, record_var)
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't update record with improper layout"),
};
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut symbols = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut fields = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut current = 0;
for (label, _, opt_field_layout) in sorted_fields.into_iter() {
match opt_field_layout {
Err(_) => {
debug_assert!(!updates.contains_key(&label));
// this was an optional field, and now does not exist!
// do not increment `current`!
}
Ok(field_layout) => {
field_layouts.push(field_layout);
if let Some(field) = updates.get(&label) {
let field_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&field.loc_expr.value,
field.var,
);
fields.push(UpdateExisting(field));
symbols.push(field_symbol);
} else {
fields.push(CopyExisting(current));
symbols.push(env.unique_symbol());
}
current += 1;
}
}
}
let symbols = symbols.into_bump_slice();
let record_layout = layout_cache
.from_var(env.arena, record_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
let field_layouts = match &record_layout {
Layout::Struct { field_layouts, .. } => *field_layouts,
other => arena.alloc([*other]),
};
debug_assert_eq!(field_layouts.len(), symbols.len());
debug_assert_eq!(fields.len(), symbols.len());
if symbols.len() == 1 {
// TODO we can probably special-case this more, skippiing the generation of
// UpdateExisting
let mut stmt = hole.clone();
let what_to_do = &fields[0];
match what_to_do {
UpdateExisting(field) => {
substitute_in_exprs(env.arena, &mut stmt, assigned, symbols[0]);
stmt = assign_to_symbol(
env,
procs,
layout_cache,
field.var,
*field.loc_expr.clone(),
symbols[0],
stmt,
);
}
CopyExisting(_) => {
unreachable!(
r"when a record has just one field and is updated, it must update that one field"
);
}
}
stmt
} else {
let expr = Expr::Struct(symbols);
let mut stmt = Stmt::Let(assigned, expr, record_layout, hole);
let it = field_layouts.iter().zip(symbols.iter()).zip(fields);
for ((field_layout, symbol), what_to_do) in it {
match what_to_do {
UpdateExisting(field) => {
stmt = assign_to_symbol(
env,
procs,
layout_cache,
field.var,
*field.loc_expr.clone(),
*symbol,
stmt,
);
}
CopyExisting(index) => {
let record_needs_specialization =
procs.ability_member_aliases.get(structure).is_some();
let specialized_structure_sym = if record_needs_specialization {
// We need to specialize the record now; create a new one for it.
// TODO: reuse this symbol for all updates
env.unique_symbol()
} else {
// The record is already good.
structure
};
let access_expr = Expr::StructAtIndex {
structure: specialized_structure_sym,
index,
field_layouts,
};
stmt =
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
// If the records needs specialization or it's a thunk, we need to
// create the specialized definition or force the thunk, respectively.
// Both cases are handled below.
if record_needs_specialization || procs.is_module_thunk(structure) {
stmt = specialize_symbol(
env,
procs,
layout_cache,
Some(record_var),
specialized_structure_sym,
stmt,
structure,
);
}
}
}
}
stmt
}
}
Closure(ClosureData {
function_type,
return_type,
name,
arguments,
captured_symbols,
loc_body: boxed_body,
..
}) => {
let loc_body = *boxed_body;
let raw = layout_cache.raw_from_var(env.arena, function_type, env.subs);
match return_on_layout_error!(env, raw, "Expr::Closure") {
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!("a closure syntactically always must have at least one argument")
}
RawFunctionLayout::Function(_argument_layouts, lambda_set, _ret_layout) => {
let mut captured_symbols = Vec::from_iter_in(captured_symbols, env.arena);
captured_symbols.sort();
let captured_symbols = captured_symbols.into_bump_slice();
let symbols =
Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice();
let lambda_name = find_lambda_name(
env,
layout_cache,
lambda_set,
name,
symbols.iter().copied(),
);
let inserted = procs.insert_anonymous(
env,
lambda_name,
function_type,
arguments,
loc_body,
CapturedSymbols::Captured(captured_symbols),
return_type,
layout_cache,
);
if let Err(runtime_error) = inserted {
return Stmt::RuntimeError(
env.arena
.alloc(format!("RuntimeError: {:?}", runtime_error,)),
);
} else {
drop(inserted);
}
// define the closure data
construct_closure_data(
env,
layout_cache,
lambda_set,
lambda_name,
symbols.iter().copied(),
assigned,
hole,
)
}
}
}
Call(boxed, loc_args, _) => {
let (fn_var, loc_expr, _lambda_set_var, _ret_var) = *boxed;
// even if a call looks like it's by name, it may in fact be by-pointer.
// E.g. in `(\f, x -> f x)` the call is in fact by pointer.
// So we check the function name against the list of partial procedures,
// the procedures that we have lifted to the top-level and can call by name
// if it's in there, it's a call by name, otherwise it's a call by pointer
let is_known = |key| {
// a proc in this module, or an imported symbol
procs.partial_procs.contains_key(key)
|| (env.is_imported_symbol(key) && !procs.is_imported_module_thunk(key))
};
match loc_expr.value {
roc_can::expr::Expr::Var(proc_name, _) if is_known(proc_name) => {
// a call by a known name
call_by_name(
env,
procs,
fn_var,
proc_name,
loc_args,
layout_cache,
assigned,
hole,
)
}
roc_can::expr::Expr::AbilityMember(member, specialization_id, _) => {
let specialization_proc_name =
late_resolve_ability_specialization(env, member, specialization_id, fn_var);
call_by_name(
env,
procs,
fn_var,
specialization_proc_name,
loc_args,
layout_cache,
assigned,
hole,
)
}
_ => {
// Call by pointer - the closure was anonymous, e.g.
//
// ((\a -> a) 5)
//
// It might even be the anonymous result of a conditional:
//
// ((if x > 0 then \a -> a else \_ -> 0) 5)
//
// It could be named too:
//
// ((if x > 0 then foo else bar) 5)
//
// also this occurs for functions passed in as arguments, e.g.
//
// (\f, x -> f x)
let arg_symbols = Vec::from_iter_in(
loc_args.iter().map(|(var, arg_expr)| {
possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&arg_expr.value,
*var,
)
}),
arena,
)
.into_bump_slice();
let full_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, fn_var, env.subs),
"Expr::Call"
);
// if the function expression (loc_expr) is already a symbol,
// re-use that symbol, and don't define its value again
let mut result;
use ReuseSymbol::*;
match can_reuse_symbol(env, procs, &loc_expr.value, fn_var) {
LocalFunction(_) => {
unreachable!("if this was known to be a function, we would not be here")
}
Imported(thunk_name) => {
debug_assert!(procs.is_imported_module_thunk(thunk_name));
add_needed_external(
procs,
env,
fn_var,
LambdaName::no_niche(thunk_name),
);
let function_symbol = env.unique_symbol();
match full_layout {
RawFunctionLayout::Function(
arg_layouts,
lambda_set,
ret_layout,
) => {
let closure_data_symbol = function_symbol;
result = match_on_lambda_set(
env,
layout_cache,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
arg_layouts,
ret_layout,
assigned,
hole,
);
result = force_thunk(
env,
thunk_name,
Layout::LambdaSet(lambda_set),
function_symbol,
env.arena.alloc(result),
);
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!("calling a non-closure layout")
}
}
}
Value(function_symbol) => {
let function_symbol = procs.symbol_specializations.get_or_insert(
env,
layout_cache,
function_symbol,
fn_var,
);
match full_layout {
RawFunctionLayout::Function(
arg_layouts,
lambda_set,
ret_layout,
) => {
let closure_data_symbol = function_symbol;
result = match_on_lambda_set(
env,
layout_cache,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
arg_layouts,
ret_layout,
assigned,
hole,
);
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!("calling a non-closure layout")
}
}
}
UnspecializedExpr(symbol) => {
match procs.ability_member_aliases.get(symbol).unwrap() {
&self::AbilityMember(member) => {
let resolved_proc = resolve_ability_specialization(env.home, env.subs, &env.abilities, member, fn_var)
.expect("Recorded as an ability member, but it doesn't have a specialization");
let resolved_proc = match resolved_proc {
Resolved::Specialization(symbol) => symbol,
Resolved::NeedsGenerated(_) => {
todo_abilities!("Generate impls for structural types")
}
};
// a call by a known name
return call_by_name(
env,
procs,
fn_var,
resolved_proc,
loc_args,
layout_cache,
assigned,
hole,
);
}
}
}
NotASymbol => {
// the expression is not a symbol. That means it's an expression
// evaluating to a function value.
match full_layout {
RawFunctionLayout::Function(
arg_layouts,
lambda_set,
ret_layout,
) => {
let closure_data_symbol = env.unique_symbol();
result = match_on_lambda_set(
env,
layout_cache,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
arg_layouts,
ret_layout,
assigned,
hole,
);
result = with_hole(
env,
loc_expr.value,
fn_var,
procs,
layout_cache,
closure_data_symbol,
env.arena.alloc(result),
);
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!(
"{:?} cannot be called in the source language",
full_layout
)
}
}
}
}
let iter = loc_args.into_iter().rev().zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
}
}
ForeignCall {
foreign_symbol,
args,
ret_var,
} => {
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
for (var, arg_expr) in args.iter() {
arg_symbols.push(possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
arg_expr,
*var,
));
}
let arg_symbols = arg_symbols.into_bump_slice();
// layout of the return type
let layout = return_on_layout_error!(
env,
layout_cache.from_var(env.arena, ret_var, env.subs),
"ForeignCall"
);
let call = self::Call {
call_type: CallType::Foreign {
foreign_symbol,
ret_layout: env.arena.alloc(layout),
},
arguments: arg_symbols,
};
let result = build_call(env, call, assigned, layout, hole);
let iter = args
.into_iter()
.rev()
.map(|(a, b)| (a, Loc::at_zero(b)))
.zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
RunLowLevel { op, args, ret_var } => {
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
for (var, arg_expr) in args.iter() {
arg_symbols.push(possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
arg_expr,
*var,
));
}
let arg_symbols = arg_symbols.into_bump_slice();
// layout of the return type
let layout = return_on_layout_error!(
env,
layout_cache.from_var(env.arena, ret_var, env.subs),
"RunLowLevel"
);
macro_rules! match_on_closure_argument {
( $ho:ident, [$($x:ident),* $(,)?]) => {{
let closure_index = op.function_argument_position();
let closure_data_symbol = arg_symbols[closure_index];
let closure_data_var = args[closure_index].0;
let closure_data_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, closure_data_var, env.subs),
"match_on_closure_argument"
);
// NB: I don't think the top_level here can have a captures niche?
let top_level_capture_niche = CapturesNiche::no_niche();
let top_level = ProcLayout::from_raw(env.arena, &layout_cache.interner, closure_data_layout, top_level_capture_niche);
let arena = env.arena;
let arg_layouts = top_level.arguments;
let ret_layout = top_level.result;
match closure_data_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
lowlevel_match_on_lambda_set(
env,
layout_cache,
lambda_set,
op,
closure_data_symbol,
|(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| {
let passed_function = PassedFunction {
name: top_level_function,
captured_environment: closure_data_symbol,
owns_captured_environment: false,
specialization_id,
argument_layouts: arg_layouts,
return_layout: ret_layout,
};
let higher_order = HigherOrderLowLevel {
op: crate::low_level::HigherOrder::$ho { $($x,)* },
closure_env_layout,
update_mode,
passed_function,
};
self::Call {
call_type: CallType::HigherOrder(arena.alloc(higher_order)),
arguments: arena.alloc([$($x,)* top_level_function.name(), closure_data]),
}
},
layout,
assigned,
hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"),
}
}};
}
use LowLevel::*;
match op {
ListMap => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListMap, [xs])
}
ListSortWith => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListSortWith, [xs])
}
ListMap2 => {
debug_assert_eq!(arg_symbols.len(), 3);
let xs = arg_symbols[0];
let ys = arg_symbols[1];
match_on_closure_argument!(ListMap2, [xs, ys])
}
ListMap3 => {
debug_assert_eq!(arg_symbols.len(), 4);
let xs = arg_symbols[0];
let ys = arg_symbols[1];
let zs = arg_symbols[2];
match_on_closure_argument!(ListMap3, [xs, ys, zs])
}
ListMap4 => {
debug_assert_eq!(arg_symbols.len(), 5);
let xs = arg_symbols[0];
let ys = arg_symbols[1];
let zs = arg_symbols[2];
let ws = arg_symbols[3];
match_on_closure_argument!(ListMap4, [xs, ys, zs, ws])
}
BoxExpr => {
debug_assert_eq!(arg_symbols.len(), 1);
let x = arg_symbols[0];
Stmt::Let(assigned, Expr::ExprBox { symbol: x }, layout, hole)
}
UnboxExpr => {
debug_assert_eq!(arg_symbols.len(), 1);
let x = arg_symbols[0];
Stmt::Let(assigned, Expr::ExprUnbox { symbol: x }, layout, hole)
}
_ => {
let call = self::Call {
call_type: CallType::LowLevel {
op,
update_mode: env.next_update_mode_id(),
},
arguments: arg_symbols,
};
let result = build_call(env, call, assigned, layout, hole);
let iter = args
.into_iter()
.rev()
.map(|(a, b)| (a, Loc::at_zero(b)))
.zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
}
}
TypedHole(_) => Stmt::RuntimeError("Hit a blank"),
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(e.runtime_message())),
}
}
#[inline(always)]
fn late_resolve_ability_specialization<'a>(
env: &mut Env<'a, '_>,
member: Symbol,
specialization_id: Option<SpecializationId>,
specialization_var: Variable,
) -> Symbol {
let opt_resolved = specialization_id.and_then(|id| {
env.abilities
.with_module_abilities_store(env.home, |store| store.get_resolved(id))
});
if let Some(spec_symbol) = opt_resolved {
// Fast path: specialization is monomorphic, was found during solving.
spec_symbol
} else if let Content::Structure(FlatType::Func(_, lambda_set, _)) =
env.subs.get_content_without_compacting(specialization_var)
{
// Fast path: the member is a function, so the lambda set will tell us the
// specialization.
use roc_types::subs::LambdaSet;
let LambdaSet {
solved,
unspecialized,
recursion_var: _,
ambient_function: _,
} = env.subs.get_lambda_set(*lambda_set);
debug_assert!(unspecialized.is_empty());
let mut iter_lambda_set = solved.iter_all();
debug_assert_eq!(iter_lambda_set.len(), 1);
let spec_symbol_index = iter_lambda_set.next().unwrap().0;
env.subs[spec_symbol_index]
} else {
// Otherwise, resolve by checking the able var.
let specialization = resolve_ability_specialization(
env.home,
env.subs,
&env.abilities,
member,
specialization_var,
)
.expect("Ability specialization is unknown - code generation cannot proceed!");
match specialization {
Resolved::Specialization(symbol) => symbol,
Resolved::NeedsGenerated(var) => {
let derive_key = roc_derive_key::Derived::builtin(
member.try_into().expect("derived symbols must be builtins"),
env.subs,
var,
)
.expect("specialization var not derivable!");
match derive_key {
roc_derive_key::Derived::Immediate(imm)
| roc_derive_key::Derived::SingleLambdaSetImmediate(imm) => {
// The immediate may be an ability member itself, so it must be resolved!
late_resolve_ability_specialization(env, imm, None, specialization_var)
}
roc_derive_key::Derived::Key(derive_key) => {
let mut derived_module = env
.derived_module
.lock()
.expect("derived module unavailable");
derived_module
.get_or_insert(env.exposed_by_module, derive_key)
.0
}
}
}
}
}
}
fn find_lambda_name<'a, I>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
lambda_set: LambdaSet<'a>,
function_name: Symbol,
captures: I,
) -> LambdaName<'a>
where
I: IntoIterator<Item = &'a (Symbol, Variable)>,
{
let this_function_captures_layouts = captures
.into_iter()
.map(|(_, var)| {
layout_cache
.from_var(env.arena, *var, env.subs)
.expect("layout problem for capture")
})
.collect_in::<Vec<_>>(env.arena);
lambda_set.find_lambda_name(
&layout_cache.interner,
function_name,
&this_function_captures_layouts,
)
}
#[allow(clippy::too_many_arguments)]
fn construct_closure_data<'a, I>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
lambda_set: LambdaSet<'a>,
name: LambdaName<'a>,
symbols: I,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
I: IntoIterator<Item = &'a (Symbol, Variable)>,
I::IntoIter: ExactSizeIterator,
{
let lambda_set_layout = Layout::LambdaSet(lambda_set);
let symbols = symbols.into_iter();
let result = match lambda_set.layout_for_member_with_lambda_name(&layout_cache.interner, name) {
ClosureRepresentation::Union {
tag_id,
alphabetic_order_fields: field_layouts,
union_layout,
closure_name: _,
} => {
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
let mut combined = Vec::with_capacity_in(symbols.len(), env.arena);
for ((symbol, _variable), layout) in symbols.zip(field_layouts.iter()) {
combined.push((*symbol, layout))
}
let ptr_bytes = env.target_info;
combined.sort_by(|(_, layout1), (_, layout2)| {
let size1 = layout1.alignment_bytes(&layout_cache.interner, ptr_bytes);
let size2 = layout2.alignment_bytes(&layout_cache.interner, ptr_bytes);
size2.cmp(&size1)
});
let symbols =
Vec::from_iter_in(combined.iter().map(|(a, _)| *a), env.arena).into_bump_slice();
let expr = Expr::Tag {
tag_id,
tag_layout: union_layout,
arguments: symbols,
};
Stmt::Let(assigned, expr, lambda_set_layout, env.arena.alloc(hole))
}
ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => {
debug_assert_eq!(field_layouts.len(), symbols.len());
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
let mut combined = Vec::with_capacity_in(symbols.len(), env.arena);
for ((symbol, _variable), layout) in symbols.zip(field_layouts.iter()) {
combined.push((*symbol, layout))
}
let ptr_bytes = env.target_info;
combined.sort_by(|(_, layout1), (_, layout2)| {
let size1 = layout1.alignment_bytes(&layout_cache.interner, ptr_bytes);
let size2 = layout2.alignment_bytes(&layout_cache.interner, ptr_bytes);
size2.cmp(&size1)
});
let symbols =
Vec::from_iter_in(combined.iter().map(|(a, _)| *a), env.arena).into_bump_slice();
let field_layouts =
Vec::from_iter_in(combined.iter().map(|(_, b)| **b), env.arena).into_bump_slice();
debug_assert_eq!(
Layout::struct_no_name_order(field_layouts),
lambda_set.runtime_representation(&layout_cache.interner)
);
let expr = Expr::Struct(symbols);
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
ClosureRepresentation::UnwrappedCapture(_layout) => {
debug_assert_eq!(symbols.len(), 1);
let mut symbols = symbols;
let (captured_symbol, _) = symbols.next().unwrap();
// 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::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
debug_assert_eq!(symbols.len(), 0);
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));
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
EnumDispatch::U8 => {
debug_assert_eq!(symbols.len(), 0);
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
}
#[allow(clippy::too_many_arguments)]
fn convert_tag_union<'a>(
env: &mut Env<'a, '_>,
variant_var: Variable,
assigned: Symbol,
hole: &'a Stmt<'a>,
tag_name: TagName,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
args: std::vec::Vec<(Variable, Loc<roc_can::expr::Expr>)>,
arena: &'a Bump,
) -> Stmt<'a> {
use crate::layout::UnionVariant::*;
let res_variant = {
let mut layout_env =
layout::Env::from_components(layout_cache, env.subs, env.arena, env.target_info);
crate::layout::union_sorted_tags(&mut layout_env, variant_var)
};
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Unresolved type variable for tag {}",
tag_name.0.as_str()
)))
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Tag {} was part of a type error!",
tag_name.0.as_str()
)));
}
};
match variant {
Never => unreachable!(
"The `[]` type has no constructors, source var {:?}",
variant_var
),
Unit => Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole),
BoolUnion { ttrue, .. } => Stmt::Let(
assigned,
Expr::Literal(Literal::Bool(&tag_name == ttrue.expect_tag_ref())),
Layout::Builtin(Builtin::Bool),
hole,
),
ByteUnion(tag_names) => {
let opt_tag_id = tag_names
.iter()
.position(|key| key.expect_tag_ref() == &tag_name);
match opt_tag_id {
Some(tag_id) => Stmt::Let(
assigned,
Expr::Literal(Literal::Byte(tag_id as u8)),
Layout::Builtin(Builtin::Int(IntWidth::U8)),
hole,
),
None => Stmt::RuntimeError("tag must be in its own type"),
}
}
Newtype {
arguments: field_layouts,
..
} => {
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
field_symbols.extend(field_symbols_temp.iter().map(|r| r.1));
let field_symbols = field_symbols.into_bump_slice();
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
let layout = layout_cache
.from_var(env.arena, variant_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
// even though this was originally a Tag, we treat it as a Struct from now on
let stmt = if let [only_field] = field_symbols {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
hole
} else {
Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole)
};
let iter = field_symbols_temp.into_iter().map(|(_, _, data)| data);
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
NewtypeByVoid {
data_tag_arguments: field_layouts,
data_tag_name,
..
} => {
let dataful_tag = data_tag_name.expect_tag();
if dataful_tag != tag_name {
// this tag is not represented, and hence will never be reached, at runtime.
Stmt::RuntimeError("voided tag constructor is unreachable")
} else {
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
field_symbols.extend(field_symbols_temp.iter().map(|r| r.1));
let field_symbols = field_symbols.into_bump_slice();
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
let layout = layout_cache
.from_var(env.arena, variant_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
// even though this was originally a Tag, we treat it as a Struct from now on
let stmt = if let [only_field] = field_symbols {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
hole
} else {
Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole)
};
let iter = field_symbols_temp.into_iter().map(|(_, _, data)| data);
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
}
Wrapped(variant) => {
let (tag_id, _) = variant.tag_name_to_id(&tag_name);
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
let field_symbols;
// we must derive the union layout from the whole_var, building it up
// from `layouts` would unroll recursive tag unions, and that leads to
// problems down the line because we hash layouts and an unrolled
// version is not the same as the minimal version.
let union_layout = match return_on_layout_error!(
env,
layout_cache.from_var(env.arena, variant_var, env.subs),
"Wrapped"
) {
Layout::Union(ul) => ul,
other => internal_error!(
"unexpected layout {:?} for {:?}",
other,
roc_types::subs::SubsFmtContent(
env.subs.get_content_without_compacting(variant_var),
env.subs
)
),
};
use WrappedVariant::*;
let (tag, union_layout) = match variant {
Recursive { sorted_tag_layouts } => {
debug_assert!(sorted_tag_layouts.len() > 1);
field_symbols = {
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
temp.extend(field_symbols_temp.iter().map(|r| r.1));
temp.into_bump_slice()
};
let mut layouts: Vec<&'a [Layout<'a>]> =
Vec::with_capacity_in(sorted_tag_layouts.len(), env.arena);
for (_, arg_layouts) in sorted_tag_layouts.into_iter() {
layouts.push(arg_layouts);
}
let tag = Expr::Tag {
tag_layout: union_layout,
tag_id: tag_id as _,
arguments: field_symbols,
};
(tag, union_layout)
}
NonNullableUnwrapped {
tag_name: wrapped_tag_name,
..
} => {
debug_assert_eq!(wrapped_tag_name.expect_tag(), tag_name);
field_symbols = {
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
temp.extend(field_symbols_temp.iter().map(|r| r.1));
temp.into_bump_slice()
};
let tag = Expr::Tag {
tag_layout: union_layout,
tag_id: tag_id as _,
arguments: field_symbols,
};
(tag, union_layout)
}
NonRecursive { sorted_tag_layouts } => {
field_symbols = {
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
temp.extend(field_symbols_temp.iter().map(|r| r.1));
temp.into_bump_slice()
};
let mut layouts: Vec<&'a [Layout<'a>]> =
Vec::with_capacity_in(sorted_tag_layouts.len(), env.arena);
for (_, arg_layouts) in sorted_tag_layouts.into_iter() {
layouts.push(arg_layouts);
}
let tag = Expr::Tag {
tag_layout: union_layout,
tag_id: tag_id as _,
arguments: field_symbols,
};
(tag, union_layout)
}
NullableWrapped {
sorted_tag_layouts, ..
} => {
field_symbols = {
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
temp.extend(field_symbols_temp.iter().map(|r| r.1));
temp.into_bump_slice()
};
let mut layouts: Vec<&'a [Layout<'a>]> =
Vec::with_capacity_in(sorted_tag_layouts.len(), env.arena);
for (_, arg_layouts) in sorted_tag_layouts.into_iter() {
layouts.push(arg_layouts);
}
let tag = Expr::Tag {
tag_layout: union_layout,
tag_id: tag_id as _,
arguments: field_symbols,
};
(tag, union_layout)
}
NullableUnwrapped { .. } => {
field_symbols = {
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
temp.extend(field_symbols_temp.iter().map(|r| r.1));
temp.into_bump_slice()
};
let tag = Expr::Tag {
tag_layout: union_layout,
tag_id: tag_id as _,
arguments: field_symbols,
};
(tag, union_layout)
}
};
let stmt = Stmt::Let(assigned, tag, Layout::Union(union_layout), hole);
let iter = field_symbols_temp
.into_iter()
.map(|x| x.2 .0)
.rev()
.zip(field_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
}
}
#[allow(clippy::too_many_arguments)]
fn tag_union_to_function<'a>(
env: &mut Env<'a, '_>,
argument_variables: VariableSubsSlice,
return_variable: Variable,
tag_name: TagName,
proc_symbol: Symbol,
ext_var: Variable,
procs: &mut Procs<'a>,
whole_var: Variable,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let mut loc_pattern_args = vec![];
let mut loc_expr_args = vec![];
for index in argument_variables {
let arg_var = env.subs[index];
let arg_symbol = env.unique_symbol();
let loc_pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(arg_symbol));
let loc_expr = Loc::at_zero(roc_can::expr::Expr::Var(arg_symbol, arg_var));
loc_pattern_args.push((arg_var, AnnotatedMark::known_exhaustive(), loc_pattern));
loc_expr_args.push((arg_var, loc_expr));
}
let loc_body = Loc::at_zero(roc_can::expr::Expr::Tag {
tag_union_var: return_variable,
name: tag_name,
arguments: loc_expr_args,
ext_var,
});
// Lambda does not capture anything, can't have a captures niche
let lambda_name = LambdaName::no_niche(proc_symbol);
let inserted = procs.insert_anonymous(
env,
lambda_name,
whole_var,
loc_pattern_args,
loc_body,
CapturedSymbols::None,
return_variable,
layout_cache,
);
match inserted {
Ok(_layout) => {
// only need to construct closure data
let raw_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, whole_var, env.subs),
"tag_union_to_function"
);
match raw_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, proc_symbol, &[]);
debug_assert!(lambda_name.no_captures());
construct_closure_data(
env,
layout_cache,
lambda_set,
lambda_name,
&[],
assigned,
hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(),
}
}
Err(runtime_error) => Stmt::RuntimeError(env.arena.alloc(format!(
"Could not produce tag function due to a runtime error: {:?}",
runtime_error,
))),
}
}
#[allow(clippy::type_complexity)]
fn sorted_field_symbols<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
mut args: std::vec::Vec<(Variable, Loc<roc_can::expr::Expr>)>,
) -> Vec<
'a,
(
u32,
Symbol,
((Variable, Loc<roc_can::expr::Expr>), &'a Symbol),
),
> {
let mut field_symbols_temp = Vec::with_capacity_in(args.len(), env.arena);
for (var, mut arg) in args.drain(..) {
// Layout will unpack this unwrapped tag if it only has one (non-zero-sized) field
let layout = match layout_cache.from_var(env.arena, var, env.subs) {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
// this argument has type `forall a. a`, which is isomorphic to
// the empty type (Void, Never, the empty tag union `[]`)
// Note it does not catch the use of `[]` currently.
use roc_can::expr::Expr;
arg.value = Expr::RuntimeError(RuntimeError::VoidValue);
Layout::UNIT
}
Err(LayoutProblem::Erroneous) => {
// something went very wrong
panic!("TODO turn fn_var into a RuntimeError")
}
};
let alignment = layout.alignment_bytes(&layout_cache.interner, env.target_info);
let symbol = possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg.value, var);
field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol))));
}
field_symbols_temp.sort_by(|a, b| b.0.cmp(&a.0));
field_symbols_temp
}
/// Insert a closure that does capture symbols (because it is top-level) to the list of partial procs
fn register_noncapturing_closure<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
closure_name: Symbol,
closure_data: ClosureData,
) {
let ClosureData {
function_type,
return_type,
recursive,
arguments,
loc_body: boxed_body,
captured_symbols,
..
} = closure_data;
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
let loc_body = *boxed_body;
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// this should be a top-level declaration, and hence have no captured symbols
// if we ever do hit this (and it's not a bug), we should make sure to put the
// captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function
debug_assert!(captured_symbols.is_empty());
let partial_proc = PartialProc::from_named_function(
env,
function_type,
arguments,
loc_body,
CapturedSymbols::None,
is_self_recursive,
return_type,
);
procs.partial_procs.insert(closure_name, partial_proc);
}
/// Insert a closure that may capture symbols to the list of partial procs
fn register_capturing_closure<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
closure_name: Symbol,
closure_data: ClosureData,
) {
// the function surrounding the closure definition may be specialized multiple times,
// hence in theory this partial proc may be added multiple times. That would be wasteful
// so we check whether this partial proc is already there.
//
// (the `gen_primitives::task_always_twice` test has this behavior)
if !procs.partial_procs.contains_key(closure_name) {
let ClosureData {
function_type,
return_type,
closure_type,
recursive,
arguments,
loc_body: boxed_body,
captured_symbols,
..
} = closure_data;
let loc_body = *boxed_body;
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
let captured_symbols = match *env.subs.get_content_without_compacting(function_type) {
Content::Structure(FlatType::Func(_, closure_var, _)) => {
let lambda_set_layout = {
LambdaSet::from_var_pub(
layout_cache,
env.arena,
env.subs,
closure_var,
env.target_info,
)
};
match lambda_set_layout {
Ok(lambda_set) => {
if lambda_set.is_represented(&layout_cache.interner).is_none() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
Err(_) => {
// just allow this. see https://github.com/roc-lang/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
}
}
_ => {
// This is a value (zero-argument thunk); it cannot capture any variables.
debug_assert!(
captured_symbols.is_empty(),
"{:?} with layout {:?} {:?} {:?}",
&captured_symbols,
layout_cache.raw_from_var(env.arena, function_type, env.subs,),
env.subs,
(function_type, closure_type),
);
CapturedSymbols::None
}
};
let partial_proc = PartialProc::from_named_function(
env,
function_type,
arguments,
loc_body,
captured_symbols,
is_self_recursive,
return_type,
);
procs.partial_procs.insert(closure_name, partial_proc);
}
}
pub fn from_can<'a>(
env: &mut Env<'a, '_>,
variable: Variable,
can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
) -> Stmt<'a> {
use roc_can::expr::Expr::*;
match can_expr {
When {
cond_var,
expr_var,
region: _,
loc_cond,
branches,
branches_cond_var: _,
exhaustive,
} => {
let cond_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_cond.value,
cond_var,
);
let stmt = from_can_when(
env,
cond_var,
expr_var,
cond_symbol,
branches,
exhaustive,
layout_cache,
procs,
None,
);
// define the `when` condition
assign_to_symbol(
env,
procs,
layout_cache,
cond_var,
*loc_cond,
cond_symbol,
stmt,
)
}
If {
cond_var,
branch_var,
branches,
final_else,
} => {
let ret_layout = layout_cache
.from_var(env.arena, branch_var, env.subs)
.expect("invalid ret_layout");
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs)
.expect("invalid cond_layout");
let mut stmt = from_can(env, branch_var, final_else.value, procs, layout_cache);
for (loc_cond, loc_then) in branches.into_iter().rev() {
let branching_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_cond.value,
cond_var,
);
let then = from_can(env, branch_var, loc_then.value, procs, layout_cache);
stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout);
stmt = assign_to_symbol(
env,
procs,
layout_cache,
cond_var,
loc_cond,
branching_symbol,
stmt,
);
}
stmt
}
Expect {
loc_condition,
loc_continuation,
lookups_in_cond,
} => {
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut layouts = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
env,
symbol,
Some(specialization_id),
var,
),
None => symbol,
};
let res_layout = layout_cache.from_var(env.arena, var, env.subs);
let layout = return_on_layout_error!(env, res_layout, "Expect");
if !matches!(layout, Layout::LambdaSet(..)) {
// Exclude functions from lookups
lookups.push(symbol);
layouts.push(layout);
}
}
let mut stmt = Stmt::Expect {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
layouts: layouts.into_bump_slice(),
remainder: env.arena.alloc(rest),
};
stmt = with_hole(
env,
loc_condition.value,
variable,
procs,
layout_cache,
cond_symbol,
env.arena.alloc(stmt),
);
stmt
}
ExpectFx {
loc_condition,
loc_continuation,
lookups_in_cond,
} => {
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
let cond_symbol = env.unique_symbol();
let mut lookups = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
let mut layouts = Vec::with_capacity_in(lookups_in_cond.len(), env.arena);
for ExpectLookup {
symbol,
var,
ability_info,
} in lookups_in_cond
{
let symbol = match ability_info {
Some(specialization_id) => late_resolve_ability_specialization(
env,
symbol,
Some(specialization_id),
var,
),
None => symbol,
};
let res_layout = layout_cache.from_var(env.arena, var, env.subs);
let layout = return_on_layout_error!(env, res_layout, "Expect");
if !matches!(layout, Layout::LambdaSet(..)) {
// Exclude functions from lookups
lookups.push(symbol);
layouts.push(layout);
}
}
let mut stmt = Stmt::ExpectFx {
condition: cond_symbol,
region: loc_condition.region,
lookups: lookups.into_bump_slice(),
layouts: layouts.into_bump_slice(),
remainder: env.arena.alloc(rest),
};
stmt = with_hole(
env,
loc_condition.value,
variable,
procs,
layout_cache,
cond_symbol,
env.arena.alloc(stmt),
);
stmt
}
LetRec(defs, cont, _cycle_mark) => {
// because Roc is strict, only functions can be recursive!
for def in defs.into_iter() {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
// Now that we know for sure it's a closure, get an owned
// version of these variant args so we can use them properly.
match def.loc_expr.value {
Closure(closure_data) => {
register_capturing_closure(
env,
procs,
layout_cache,
*symbol,
closure_data,
);
continue;
}
_ => unreachable!("recursive value is not a function"),
}
}
unreachable!("recursive value does not have Identifier pattern")
}
from_can(env, variable, cont.value, procs, layout_cache)
}
LetNonRec(def, cont) => from_can_let(env, procs, layout_cache, def, cont, variable, None),
_ => {
let symbol = env.unique_symbol();
let hole = env.arena.alloc(Stmt::Ret(symbol));
with_hole(env, can_expr, variable, procs, layout_cache, symbol, hole)
}
}
}
fn to_opt_branches<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
branches: std::vec::Vec<roc_can::expr::WhenBranch>,
exhaustive_mark: ExhaustiveMark,
layout_cache: &mut LayoutCache<'a>,
) -> std::vec::Vec<(
Pattern<'a>,
Option<Loc<roc_can::expr::Expr>>,
roc_can::expr::Expr,
)> {
debug_assert!(!branches.is_empty());
let mut opt_branches = std::vec::Vec::new();
for when_branch in branches {
if when_branch.redundant.is_redundant(env.subs) {
// Don't codegen this branch since it's redundant.
continue;
}
for loc_pattern in when_branch.patterns {
match from_can_pattern(env, procs, layout_cache, &loc_pattern.pattern.value) {
Ok((mono_pattern, assignments)) => {
let loc_expr = if !loc_pattern.degenerate {
let mut loc_expr = when_branch.value.clone();
let region = loc_pattern.pattern.region;
for (symbol, variable, expr) in assignments.into_iter().rev() {
let def = roc_can::def::Def {
annotation: None,
expr_var: variable,
loc_expr: Loc::at(region, expr),
loc_pattern: Loc::at(
region,
roc_can::pattern::Pattern::Identifier(symbol),
),
pattern_vars: std::iter::once((symbol, variable)).collect(),
};
let new_expr =
roc_can::expr::Expr::LetNonRec(Box::new(def), Box::new(loc_expr));
loc_expr = Loc::at(region, new_expr);
}
loc_expr
} else {
// This pattern is degenerate; when it's reached we must emit a runtime
// error.
Loc::at_zero(roc_can::expr::Expr::RuntimeError(
RuntimeError::DegenerateBranch(loc_pattern.pattern.region),
))
};
// TODO remove clone?
opt_branches.push((mono_pattern, when_branch.guard.clone(), loc_expr.value));
}
Err(runtime_error) => {
// TODO remove clone?
opt_branches.push((
Pattern::Underscore,
when_branch.guard.clone(),
roc_can::expr::Expr::RuntimeError(runtime_error),
));
}
}
}
}
if exhaustive_mark.is_non_exhaustive(env.subs) {
// In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive.
// So we not only report exhaustiveness errors, but also correct them
opt_branches.push((
Pattern::Underscore,
None,
roc_can::expr::Expr::RuntimeError(roc_problem::can::RuntimeError::NonExhaustivePattern),
));
}
opt_branches
}
#[allow(clippy::too_many_arguments)]
fn from_can_when<'a>(
env: &mut Env<'a, '_>,
cond_var: Variable,
expr_var: Variable,
cond_symbol: Symbol,
branches: std::vec::Vec<roc_can::expr::WhenBranch>,
exhaustive_mark: ExhaustiveMark,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
join_point: Option<JoinPointId>,
) -> Stmt<'a> {
if branches.is_empty() {
// A when-expression with no branches is a runtime error.
// We can't know what to return!
return Stmt::RuntimeError("Hit a 0-branch when expression");
}
let opt_branches = to_opt_branches(env, procs, branches, exhaustive_mark, layout_cache);
let cond_layout = return_on_layout_error!(
env,
layout_cache.from_var(env.arena, cond_var, env.subs),
"from_can_when cond_layout"
);
let ret_layout = return_on_layout_error!(
env,
layout_cache.from_var(env.arena, expr_var, env.subs),
"from_can_when ret_layout"
);
let arena = env.arena;
let it = opt_branches
.into_iter()
.filter_map(|(pattern, opt_guard, can_expr)| {
// If the pattern has a void layout we can drop it; however, we must still perform the
// work of building the body, because that may contain specializations we must
// discover for use elsewhere. See
// `unreachable_branch_is_eliminated_but_produces_lambda_specializations` in test_mono
// for an example.
let should_eliminate_branch = pattern.is_voided();
// If we're going to eliminate the branch, we need to take a snapshot of the symbol
// specializations before we enter the branch, because any new specializations that
// will be added in the branch body will never need to be resolved!
let specialization_symbol_snapshot = if should_eliminate_branch {
Some(std::mem::take(&mut procs.symbol_specializations))
} else {
None
};
let branch_stmt = match join_point {
None => from_can(env, expr_var, can_expr, procs, layout_cache),
Some(id) => {
let symbol = env.unique_symbol();
let arguments = bumpalo::vec![in env.arena; symbol].into_bump_slice();
let jump = env.arena.alloc(Stmt::Jump(id, arguments));
with_hole(env, can_expr, expr_var, procs, layout_cache, symbol, jump)
}
};
use crate::decision_tree::Guard;
let result = if let Some(loc_expr) = opt_guard {
let id = JoinPointId(env.unique_symbol());
let symbol = env.unique_symbol();
let jump = env.arena.alloc(Stmt::Jump(id, env.arena.alloc([symbol])));
let guard_stmt = with_hole(
env,
loc_expr.value,
Variable::BOOL,
procs,
layout_cache,
symbol,
jump,
);
(
pattern.clone(),
Guard::Guard {
id,
pattern,
stmt: guard_stmt,
},
branch_stmt,
)
} else {
(pattern, Guard::NoGuard, branch_stmt)
};
if should_eliminate_branch {
procs.symbol_specializations = specialization_symbol_snapshot.unwrap();
None
} else {
Some(result)
}
});
let mono_branches = Vec::from_iter_in(it, arena);
crate::decision_tree::optimize_when(
env,
procs,
layout_cache,
cond_symbol,
cond_layout,
ret_layout,
mono_branches,
)
}
fn substitute(substitutions: &BumpMap<Symbol, Symbol>, s: Symbol) -> Option<Symbol> {
match substitutions.get(&s) {
Some(new) => {
debug_assert!(!substitutions.contains_key(new));
Some(*new)
}
None => None,
}
}
fn substitute_in_exprs<'a>(arena: &'a Bump, stmt: &mut Stmt<'a>, from: Symbol, to: Symbol) {
let mut subs = BumpMap::with_capacity_in(1, arena);
subs.insert(from, to);
// TODO clean this up
let ref_stmt = arena.alloc(stmt.clone());
if let Some(new) = substitute_in_stmt_help(arena, ref_stmt, &subs) {
*stmt = new.clone();
}
}
fn substitute_in_stmt_help<'a>(
arena: &'a Bump,
stmt: &'a Stmt<'a>,
subs: &BumpMap<Symbol, Symbol>,
) -> Option<&'a Stmt<'a>> {
use Stmt::*;
match stmt {
Let(symbol, expr, layout, cont) => {
let opt_cont = substitute_in_stmt_help(arena, cont, subs);
let opt_expr = substitute_in_expr(arena, expr, subs);
if opt_expr.is_some() || opt_cont.is_some() {
let cont = opt_cont.unwrap_or(cont);
let expr = opt_expr.unwrap_or_else(|| expr.clone());
Some(arena.alloc(Let(*symbol, expr, *layout, cont)))
} else {
None
}
}
Join {
id,
parameters,
remainder,
body: continuation,
} => {
let opt_remainder = substitute_in_stmt_help(arena, remainder, subs);
let opt_continuation = substitute_in_stmt_help(arena, continuation, subs);
if opt_remainder.is_some() || opt_continuation.is_some() {
let remainder = opt_remainder.unwrap_or(remainder);
let continuation = opt_continuation.unwrap_or(*continuation);
Some(arena.alloc(Join {
id: *id,
parameters,
remainder,
body: continuation,
}))
} else {
None
}
}
Switch {
cond_symbol,
cond_layout,
branches,
default_branch,
ret_layout,
} => {
let opt_default = substitute_in_stmt_help(arena, default_branch.1, subs);
let mut did_change = false;
let opt_branches = Vec::from_iter_in(
branches.iter().map(|(label, info, branch)| {
match substitute_in_stmt_help(arena, branch, subs) {
None => None,
Some(branch) => {
did_change = true;
Some((*label, info.clone(), branch.clone()))
}
}
}),
arena,
);
if opt_default.is_some() || did_change {
let default_branch = (
default_branch.0.clone(),
opt_default.unwrap_or(default_branch.1),
);
let branches = if did_change {
let new = Vec::from_iter_in(
opt_branches.into_iter().zip(branches.iter()).map(
|(opt_branch, branch)| match opt_branch {
None => branch.clone(),
Some(new_branch) => new_branch,
},
),
arena,
);
new.into_bump_slice()
} else {
branches
};
Some(arena.alloc(Switch {
cond_symbol: *cond_symbol,
cond_layout: *cond_layout,
default_branch,
branches,
ret_layout: *ret_layout,
}))
} else {
None
}
}
Ret(s) => match substitute(subs, *s) {
Some(s) => Some(arena.alloc(Ret(s))),
None => None,
},
Refcounting(modify, cont) => {
// TODO should we substitute in the ModifyRc?
match substitute_in_stmt_help(arena, cont, subs) {
Some(cont) => Some(arena.alloc(Refcounting(*modify, cont))),
None => None,
}
}
Expect {
condition,
region,
lookups,
layouts,
remainder,
} => {
let new_remainder =
substitute_in_stmt_help(arena, remainder, subs).unwrap_or(remainder);
let new_lookups = Vec::from_iter_in(
lookups.iter().map(|s| substitute(subs, *s).unwrap_or(*s)),
arena,
);
let expect = Expect {
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
layouts,
remainder: new_remainder,
};
Some(arena.alloc(expect))
}
ExpectFx {
condition,
region,
lookups,
layouts,
remainder,
} => {
let new_remainder =
substitute_in_stmt_help(arena, remainder, subs).unwrap_or(remainder);
let new_lookups = Vec::from_iter_in(
lookups.iter().map(|s| substitute(subs, *s).unwrap_or(*s)),
arena,
);
let expect = ExpectFx {
condition: substitute(subs, *condition).unwrap_or(*condition),
region: *region,
lookups: new_lookups.into_bump_slice(),
layouts,
remainder: new_remainder,
};
Some(arena.alloc(expect))
}
Jump(id, args) => {
let mut did_change = false;
let new_args = Vec::from_iter_in(
args.iter().map(|s| match substitute(subs, *s) {
None => *s,
Some(s) => {
did_change = true;
s
}
}),
arena,
);
if did_change {
let args = new_args.into_bump_slice();
Some(arena.alloc(Jump(*id, args)))
} else {
None
}
}
RuntimeError(_) => None,
}
}
fn substitute_in_call<'a>(
arena: &'a Bump,
call: &'a Call<'a>,
subs: &BumpMap<Symbol, Symbol>,
) -> Option<Call<'a>> {
let Call {
call_type,
arguments,
} = call;
let opt_call_type = match call_type {
CallType::ByName {
name,
arg_layouts,
ret_layout,
specialization_id,
} => substitute(subs, name.name()).map(|new| CallType::ByName {
name: name.replace_name(new),
arg_layouts,
ret_layout: *ret_layout,
specialization_id: *specialization_id,
}),
CallType::Foreign { .. } => None,
CallType::LowLevel { .. } => None,
CallType::HigherOrder { .. } => None,
};
let mut did_change = false;
let new_args = Vec::from_iter_in(
arguments.iter().map(|s| match substitute(subs, *s) {
None => *s,
Some(s) => {
did_change = true;
s
}
}),
arena,
);
if did_change || opt_call_type.is_some() {
let call_type = opt_call_type.unwrap_or_else(|| call_type.clone());
let arguments = new_args.into_bump_slice();
Some(self::Call {
call_type,
arguments,
})
} else {
None
}
}
fn substitute_in_expr<'a>(
arena: &'a Bump,
expr: &'a Expr<'a>,
subs: &BumpMap<Symbol, Symbol>,
) -> Option<Expr<'a>> {
use Expr::*;
match expr {
Literal(_) | EmptyArray | RuntimeErrorFunction(_) => None,
Call(call) => substitute_in_call(arena, call, subs).map(Expr::Call),
Tag {
tag_layout,
tag_id,
arguments: args,
} => {
let mut did_change = false;
let new_args = Vec::from_iter_in(
args.iter().map(|s| match substitute(subs, *s) {
None => *s,
Some(s) => {
did_change = true;
s
}
}),
arena,
);
if did_change {
let arguments = new_args.into_bump_slice();
Some(Tag {
tag_layout: *tag_layout,
tag_id: *tag_id,
arguments,
})
} else {
None
}
}
Reuse { .. } | Reset { .. } => unreachable!("reset/reuse have not been introduced yet"),
Struct(args) => {
let mut did_change = false;
let new_args = Vec::from_iter_in(
args.iter().map(|s| match substitute(subs, *s) {
None => *s,
Some(s) => {
did_change = true;
s
}
}),
arena,
);
if did_change {
let args = new_args.into_bump_slice();
Some(Struct(args))
} else {
None
}
}
Array {
elems: args,
elem_layout,
} => {
let mut did_change = false;
let new_args = Vec::from_iter_in(
args.iter().map(|e| {
if let ListLiteralElement::Symbol(s) = e {
match substitute(subs, *s) {
None => ListLiteralElement::Symbol(*s),
Some(s) => {
did_change = true;
ListLiteralElement::Symbol(s)
}
}
} else {
*e
}
}),
arena,
);
if did_change {
let args = new_args.into_bump_slice();
Some(Array {
elem_layout: *elem_layout,
elems: args,
})
} else {
None
}
}
ExprBox { symbol } => {
substitute(subs, *symbol).map(|new_symbol| ExprBox { symbol: new_symbol })
}
ExprUnbox { symbol } => {
substitute(subs, *symbol).map(|new_symbol| ExprUnbox { symbol: new_symbol })
}
StructAtIndex {
index,
structure,
field_layouts,
} => match substitute(subs, *structure) {
Some(structure) => Some(StructAtIndex {
index: *index,
field_layouts: *field_layouts,
structure,
}),
None => None,
},
GetTagId {
structure,
union_layout,
} => match substitute(subs, *structure) {
Some(structure) => Some(GetTagId {
structure,
union_layout: *union_layout,
}),
None => None,
},
UnionAtIndex {
structure,
tag_id,
index,
union_layout,
} => match substitute(subs, *structure) {
Some(structure) => Some(UnionAtIndex {
structure,
tag_id: *tag_id,
index: *index,
union_layout: *union_layout,
}),
None => None,
},
}
}
#[allow(clippy::too_many_arguments)]
pub fn store_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
stmt: Stmt<'a>,
) -> Stmt<'a> {
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
StorePattern::Productive(new) => new,
StorePattern::NotProductive(new) => new,
}
}
enum StorePattern<'a> {
/// we bound new symbols
Productive(Stmt<'a>),
/// no new symbols were bound in this pattern
NotProductive(Stmt<'a>),
}
/// It is crucial for correct RC insertion that we don't create dead variables!
#[allow(clippy::too_many_arguments)]
fn store_pattern_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
use Pattern::*;
match can_pat {
Identifier(symbol) => {
// An identifier in a pattern can define at most one specialization!
// Remove any requested specializations for this name now, since this is the definition site.
let specialization_symbol = procs
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
substitute_in_exprs(env.arena, &mut stmt, specialization_symbol, outer_symbol);
}
Underscore => {
// do nothing
return StorePattern::NotProductive(stmt);
}
IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
[(pattern, _layout)] => {
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
}
_ => {
let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
fields.extend(arguments.iter().map(|x| x.1));
let layout = Layout::struct_no_name_order(fields.into_bump_slice());
return store_newtype_pattern(
env,
procs,
layout_cache,
outer_symbol,
&layout,
arguments,
stmt,
);
}
},
AppliedTag {
arguments,
layout,
tag_id,
..
} => {
return store_tag_pattern(
env,
procs,
layout_cache,
outer_symbol,
*layout,
arguments,
*tag_id,
stmt,
);
}
Voided { .. } => {
return StorePattern::NotProductive(stmt);
}
OpaqueUnwrap { argument, .. } => {
let (pattern, _layout) = &**argument;
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
}
RecordDestructure(destructs, [_single_field]) => {
for destruct in destructs {
match &destruct.typ {
DestructType::Required(symbol) => {
let specialization_symbol = procs
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
substitute_in_exprs(
env.arena,
&mut stmt,
specialization_symbol,
outer_symbol,
);
}
DestructType::Guard(guard_pattern) => {
return store_pattern_help(
env,
procs,
layout_cache,
guard_pattern,
outer_symbol,
stmt,
);
}
}
}
}
RecordDestructure(destructs, sorted_fields) => {
let mut is_productive = false;
for (index, destruct) in destructs.iter().enumerate().rev() {
match store_record_destruct(
env,
procs,
layout_cache,
destruct,
index as u64,
outer_symbol,
sorted_fields,
stmt,
) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
}
StorePattern::NotProductive(new) => {
stmt = new;
}
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
}
}
}
StorePattern::Productive(stmt)
}
#[allow(clippy::too_many_arguments)]
fn store_tag_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
structure: Symbol,
union_layout: UnionLayout<'a>,
arguments: &[(Pattern<'a>, Layout<'a>)],
tag_id: TagIdIntType,
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
use Pattern::*;
let mut is_productive = false;
for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() {
let mut arg_layout = *arg_layout;
if let Layout::RecursivePointer = arg_layout {
arg_layout = Layout::Union(union_layout);
}
let load = Expr::UnionAtIndex {
index: index as u64,
structure,
tag_id,
union_layout,
};
match argument {
Identifier(symbol) => {
// Pattern can define only one specialization
let symbol = procs
.symbol_specializations
.remove_single(*symbol)
.unwrap_or(*symbol);
// store immediately in the given symbol
stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
is_productive = true;
}
Underscore => {
// ignore
}
IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
_ => {
// store the field in a symbol, and continue matching on it
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
// only if we bind one of its (sub)fields to a used name should we
// extract the field
stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
}
StorePattern::NotProductive(new) => {
// do nothing
stmt = new;
}
}
}
}
}
if is_productive {
StorePattern::Productive(stmt)
} else {
StorePattern::NotProductive(stmt)
}
}
#[allow(clippy::too_many_arguments)]
fn store_newtype_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
structure: Symbol,
layout: &Layout<'a>,
arguments: &[(Pattern<'a>, Layout<'a>)],
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
use Pattern::*;
let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
let mut is_productive = false;
for (_, layout) in arguments {
arg_layouts.push(*layout);
}
for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() {
let mut arg_layout = *arg_layout;
if let Layout::RecursivePointer = arg_layout {
arg_layout = *layout;
}
let load = Expr::StructAtIndex {
index: index as u64,
field_layouts: arg_layouts.clone().into_bump_slice(),
structure,
};
match argument {
Identifier(symbol) => {
// store immediately in the given symbol, removing it specialization if it had any
let specialization_symbol = procs
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
stmt = Stmt::Let(
specialization_symbol,
load,
arg_layout,
env.arena.alloc(stmt),
);
is_productive = true;
}
Underscore => {
// ignore
}
IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
_ => {
// store the field in a symbol, and continue matching on it
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
// only if we bind one of its (sub)fields to a used name should we
// extract the field
stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
}
StorePattern::NotProductive(new) => {
// do nothing
stmt = new;
}
}
}
}
}
if is_productive {
StorePattern::Productive(stmt)
} else {
StorePattern::NotProductive(stmt)
}
}
#[allow(clippy::too_many_arguments)]
fn store_record_destruct<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
destruct: &RecordDestruct<'a>,
index: u64,
outer_symbol: Symbol,
sorted_fields: &'a [Layout<'a>],
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
use Pattern::*;
let load = Expr::StructAtIndex {
index,
field_layouts: sorted_fields,
structure: outer_symbol,
};
match &destruct.typ {
DestructType::Required(symbol) => {
// A destructure can define at most one specialization!
// Remove any requested specializations for this name now, since this is the definition site.
let specialization_symbol = procs
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
stmt = Stmt::Let(
specialization_symbol,
load,
destruct.layout,
env.arena.alloc(stmt),
);
}
DestructType::Guard(guard_pattern) => match &guard_pattern {
Identifier(symbol) => {
let specialization_symbol = procs
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
stmt = Stmt::Let(
specialization_symbol,
load,
destruct.layout,
env.arena.alloc(stmt),
);
}
Underscore => {
// important that this is special-cased to do nothing: mono record patterns will extract all the
// fields, but those not bound in the source code are guarded with the underscore
// pattern. So given some record `{ x : a, y : b }`, a match
//
// { x } -> ...
//
// is actually
//
// { x, y: _ } -> ...
//
// internally. But `y` is never used, so we must make sure it't not stored/loaded.
return StorePattern::NotProductive(stmt);
}
IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
_ => {
let symbol = env.unique_symbol();
match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = new;
stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
}
StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
}
}
},
}
StorePattern::Productive(stmt)
}
/// We want to re-use symbols that are not function symbols
/// for any other expression, we create a new symbol, and will
/// later make sure it gets assigned the correct value.
#[derive(Debug)]
enum ReuseSymbol {
Imported(Symbol),
LocalFunction(Symbol),
Value(Symbol),
UnspecializedExpr(Symbol),
NotASymbol,
}
fn can_reuse_symbol<'a>(
env: &mut Env<'a, '_>,
procs: &Procs<'a>,
expr: &roc_can::expr::Expr,
expr_var: Variable,
) -> ReuseSymbol {
use roc_can::expr::Expr::*;
use ReuseSymbol::*;
let symbol = match expr {
AbilityMember(member, specialization_id, _) => {
late_resolve_ability_specialization(env, *member, *specialization_id, expr_var)
}
Var(symbol, _) => *symbol,
_ => return NotASymbol,
};
let arguments = [
Symbol::ARG_1,
Symbol::ARG_2,
Symbol::ARG_3,
Symbol::ARG_4,
Symbol::ARG_5,
Symbol::ARG_6,
Symbol::ARG_7,
];
if arguments.contains(&symbol) {
Value(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)
} else if procs.ability_member_aliases.get(symbol).is_some() {
UnspecializedExpr(symbol)
} else {
Value(symbol)
}
}
fn possible_reuse_symbol_or_specialize<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
expr: &roc_can::expr::Expr,
var: Variable,
) -> Symbol {
match can_reuse_symbol(env, procs, expr, var) {
ReuseSymbol::Value(symbol) => {
procs
.symbol_specializations
.get_or_insert(env, layout_cache, symbol, var)
}
_ => env.unique_symbol(),
}
}
fn handle_variable_aliasing<'a, BuildRest>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
variable: Variable,
left: Symbol,
right: Symbol,
build_rest: BuildRest,
) -> Stmt<'a>
where
BuildRest: FnOnce(&mut Env<'a, '_>, &mut Procs<'a>, &mut LayoutCache<'a>) -> Stmt<'a>,
{
// 1. Handle references to ability members - we could be aliasing an ability member, or another
// alias to an ability member.
{
let is_ability_member = env
.abilities
.with_module_abilities_store(env.home, |store| store.is_ability_member_name(right));
if is_ability_member {
procs
.ability_member_aliases
.insert(left, AbilityMember(right));
return build_rest(env, procs, layout_cache);
}
if let Some(&ability_member) = procs.ability_member_aliases.get(right) {
procs.ability_member_aliases.insert(left, ability_member);
return build_rest(env, procs, layout_cache);
}
}
// 2. Handle references to a known proc - again, we may be either aliasing the proc, or another
// alias to a proc.
if procs.partial_procs.contains_key(right) {
// This is an alias to a function defined in this module.
// Attach the alias, then build the rest of the module, so that we reference and specialize
// the correct proc.
procs.partial_procs.insert_alias(left, right);
return build_rest(env, procs, layout_cache);
}
// Otherwise we're dealing with an alias whose usages will tell us what specializations we
// need. So let's figure those out first.
let result = build_rest(env, procs, layout_cache);
// The specializations we wanted of the symbol on the LHS of this alias.
let needed_specializations_of_left = procs.symbol_specializations.remove(left);
if procs.is_imported_module_thunk(right) {
// if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer.
let mut result = result;
let no_specializations_needed = needed_specializations_of_left.len() == 0;
let needed_specializations_of_left = needed_specializations_of_left
.map(|(_, spec)| Some(spec))
// HACK: sometimes specializations can be lost, for example for `x` in
// x = Bool.true
// p = \_ -> x == 1
// that's because when specializing `p`, we collect specializations for `x`, but then
// drop all of them when leaving the body of `p`, because `x` is an argument of `p` in
// such a case.
// So, if we have no recorded specializations, suppose we are in a case like this, and
// generate the default implementation.
//
// TODO: we should fix this properly. I think the way to do it is to only have proc
// specialization only drop specializations of non-captured symbols. That's because
// captured symbols can only ever be specialized outside the closure.
// After that is done, remove this hack.
.chain(if no_specializations_needed {
[Some((variable, left))]
} else {
[None]
})
.flatten();
for (variable, left) in needed_specializations_of_left {
add_needed_external(procs, env, variable, LambdaName::no_niche(right));
let res_layout = layout_cache.from_var(env.arena, variable, env.subs);
let layout = return_on_layout_error!(env, res_layout, "handle_variable_aliasing");
result = force_thunk(env, right, layout, left, env.arena.alloc(result));
}
result
} else if env.is_imported_symbol(right) {
// if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer.
add_needed_external(procs, env, variable, LambdaName::no_niche(right));
// then we must construct its closure; since imported symbols have no closure, we use the empty struct
let_empty_struct(left, env.arena.alloc(result))
} else {
// Otherwise, we are referencing a non-proc value.
// We need to lift all specializations of "left" to be specializations of "right".
let mut scratchpad_update_specializations = std::vec::Vec::new();
let left_had_specialization_symbols = needed_specializations_of_left.len() > 0;
for (specialization_mark, (specialized_var, specialized_sym)) in
needed_specializations_of_left
{
let old_specialized_sym = procs.symbol_specializations.get_or_insert_known(
right,
specialization_mark,
specialized_var,
specialized_sym,
);
if let Some((_, old_specialized_sym)) = old_specialized_sym {
scratchpad_update_specializations.push((old_specialized_sym, specialized_sym));
}
}
let mut result = result;
if left_had_specialization_symbols {
// If the symbol is specialized, only the specializations need to be updated.
for (old_specialized_sym, specialized_sym) in
scratchpad_update_specializations.into_iter()
{
substitute_in_exprs(env.arena, &mut result, old_specialized_sym, specialized_sym);
}
} else {
substitute_in_exprs(env.arena, &mut result, left, right);
}
result
}
}
fn force_thunk<'a>(
env: &mut Env<'a, '_>,
thunk_name: Symbol,
layout: Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let call = self::Call {
call_type: CallType::ByName {
name: LambdaName::no_niche(thunk_name),
ret_layout: env.arena.alloc(layout),
arg_layouts: &[],
specialization_id: env.next_call_specialization_id(),
},
arguments: &[],
};
build_call(env, call, assigned, layout, env.arena.alloc(hole))
}
fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole)
}
/// If the symbol is a function or polymorphic value, make sure it is properly specialized
fn specialize_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.get_partial_proc(original) {
None => {
match arg_var {
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"),
};
if procs.is_imported_module_thunk(original) {
let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => {
Layout::LambdaSet(lambda_set)
}
};
let raw = RawFunctionLayout::ZeroArgumentThunk(layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
force_thunk(env, original, layout, symbol, env.arena.alloc(result))
} else {
// Imported symbol, so it must have no captures niche (since
// top-levels can't capture)
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
raw,
CapturesNiche::no_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
let_empty_struct(symbol, env.arena.alloc(result))
}
}
_ => {
// danger: a foreign symbol may not be specialized!
debug_assert!(
!env.is_imported_symbol(original),
"symbol {:?} while processing module {:?}",
original,
(env.home, &arg_var),
);
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 res_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, arg_var, env.subs),
"specialize_symbol res_layout"
);
// 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;
match res_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
if captures {
let symbols = match captured {
CapturedSymbols::Captured(captured_symbols) => {
Vec::from_iter_in(captured_symbols.iter(), env.arena)
.into_bump_slice()
}
CapturedSymbols::None => unreachable!(),
};
let lambda_name = find_lambda_name(
env,
layout_cache,
lambda_set,
original,
symbols.iter().copied(),
);
// define the function pointer
let function_ptr_layout = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
res_layout,
lambda_name.captures_niche(),
);
// this is a closure by capture, meaning it itself captures local variables.
procs.insert_passed_by_name(
env,
arg_var,
lambda_name,
function_ptr_layout,
layout_cache,
);
let closure_data = symbol;
construct_closure_data(
env,
layout_cache,
lambda_set,
lambda_name,
symbols.iter().copied(),
closure_data,
env.arena.alloc(result),
)
} else if procs.is_module_thunk(original) {
// this is a 0-argument thunk
// TODO suspicious
// let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout);
// panic!("suspicious");
let layout = Layout::LambdaSet(lambda_set);
let top_level =
ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), layout);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
force_thunk(env, original, layout, symbol, env.arena.alloc(result))
} else {
// even though this function may not itself capture,
// unification may still cause it to have an extra argument
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, original, &[]);
debug_assert!(lambda_name.no_captures());
// define the function pointer
let function_ptr_layout = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
res_layout,
lambda_name.captures_niche(),
);
procs.insert_passed_by_name(
env,
arg_var,
lambda_name,
function_ptr_layout,
layout_cache,
);
construct_closure_data(
env,
layout_cache,
lambda_set,
lambda_name,
&[],
symbol,
env.arena.alloc(result),
)
}
}
RawFunctionLayout::ZeroArgumentThunk(ret_layout) => {
// this is a 0-argument thunk
let top_level =
ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), ret_layout);
procs.insert_passed_by_name(
env,
arg_var,
LambdaName::no_niche(original),
top_level,
layout_cache,
);
force_thunk(env, original, ret_layout, symbol, env.arena.alloc(result))
}
}
}
}
}
fn assign_to_symbol<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
arg_var: Variable,
loc_arg: Loc<roc_can::expr::Expr>,
symbol: Symbol,
result: Stmt<'a>,
) -> Stmt<'a> {
use ReuseSymbol::*;
match can_reuse_symbol(env, procs, &loc_arg.value, arg_var) {
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
// for functions we must make sure they are specialized correctly
specialize_symbol(
env,
procs,
layout_cache,
Some(arg_var),
symbol,
result,
original,
)
}
Value(_symbol) => result,
NotASymbol => with_hole(
env,
loc_arg.value,
arg_var,
procs,
layout_cache,
symbol,
env.arena.alloc(result),
),
}
}
fn assign_to_symbols<'a, I>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
iter: I,
mut result: Stmt<'a>,
) -> Stmt<'a>
where
I: Iterator<Item = ((Variable, Loc<roc_can::expr::Expr>), &'a Symbol)>,
{
for ((arg_var, loc_arg), symbol) in iter {
result = assign_to_symbol(env, procs, layout_cache, arg_var, loc_arg, *symbol, result);
}
result
}
fn add_needed_external<'a>(
procs: &mut Procs<'a>,
env: &mut Env<'a, '_>,
fn_var: Variable,
name: LambdaName<'a>,
) {
// call of a function that is not in this module
use hashbrown::hash_map::Entry::{Occupied, Vacant};
let existing = match procs.externals_we_need.entry(name.name().module_id()) {
Vacant(entry) => entry.insert(ExternalSpecializations::new()),
Occupied(entry) => entry.into_mut(),
};
roc_tracing::debug!(proc_name = ?name, ?fn_var, fn_content = ?roc_types::subs::SubsFmtContent(env.subs.get_content_without_compacting(fn_var), env.subs), "needed external");
existing.insert_external(name, env.subs, fn_var);
}
fn build_call<'a>(
_env: &mut Env<'a, '_>,
call: Call<'a>,
assigned: Symbol,
return_layout: Layout<'a>,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
Stmt::Let(assigned, Expr::Call(call), return_layout, hole)
}
/// 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
/// of our closure strategy, internal functions can "leak". That's what happened here.
///
/// The solution is to evaluate the arguments as normal, and only when calling the function give an error
fn evaluate_arguments_then_runtime_error<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
msg: String,
loc_args: std::vec::Vec<(Variable, Loc<roc_can::expr::Expr>)>,
) -> Stmt<'a> {
let arena = env.arena;
// eventually we will throw this runtime error
let result = Stmt::RuntimeError(env.arena.alloc(msg));
// but, we also still evaluate and specialize the arguments to give better error messages
let arg_symbols = Vec::from_iter_in(
loc_args.iter().map(|(var, arg_expr)| {
possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg_expr.value, *var)
}),
arena,
)
.into_bump_slice();
let iter = loc_args.into_iter().rev().zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
#[allow(clippy::too_many_arguments)]
fn call_by_name<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
fn_var: Variable,
proc_name: Symbol,
loc_args: std::vec::Vec<(Variable, Loc<roc_can::expr::Expr>)>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
// Register a pending_specialization for this function
match layout_cache.raw_from_var(env.arena, fn_var, env.subs) {
Err(LayoutProblem::UnresolvedTypeVar(var)) => {
let msg = format!(
"Hit an unresolved type variable {:?} when creating a layout for {:?} (var {:?})",
var, proc_name, fn_var
);
evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args)
}
Err(LayoutProblem::Erroneous) => {
let msg = format!(
"Hit an erroneous type when creating a layout for {:?}",
proc_name
);
evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args)
}
Ok(RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout)) => {
if procs.is_module_thunk(proc_name) {
if loc_args.is_empty() {
call_by_name_module_thunk(
env,
procs,
fn_var,
proc_name,
env.arena.alloc(Layout::LambdaSet(lambda_set)),
layout_cache,
assigned,
hole,
)
} else {
// here we turn a call to a module thunk into forcing of that thunk
// the thunk represents the closure environment for the body, so we then match
// on the closure environment to perform the call that the body represents.
//
// Example:
//
// > main = parseA "foo" "bar"
// > parseA = Str.concat
let closure_data_symbol = env.unique_symbol();
let arena = env.arena;
let arg_symbols = Vec::from_iter_in(
loc_args.iter().map(|(arg_var, arg_expr)| {
possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&arg_expr.value,
*arg_var,
)
}),
arena,
)
.into_bump_slice();
debug_assert_eq!(arg_symbols.len(), arg_layouts.len());
let result = match_on_lambda_set(
env,
layout_cache,
procs,
lambda_set,
closure_data_symbol,
arg_symbols,
arg_layouts,
ret_layout,
assigned,
hole,
);
let result = call_by_name_module_thunk(
env,
procs,
fn_var,
proc_name,
env.arena.alloc(Layout::LambdaSet(lambda_set)),
layout_cache,
closure_data_symbol,
env.arena.alloc(result),
);
let iter = loc_args.into_iter().rev().zip(arg_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
} else {
call_by_name_help(
env,
procs,
fn_var,
proc_name,
loc_args,
lambda_set,
arg_layouts,
ret_layout,
layout_cache,
assigned,
hole,
)
}
}
Ok(RawFunctionLayout::ZeroArgumentThunk(ret_layout)) => {
if procs.is_module_thunk(proc_name) {
// here we turn a call to a module thunk into forcing of that thunk
call_by_name_module_thunk(
env,
procs,
fn_var,
proc_name,
env.arena.alloc(ret_layout),
layout_cache,
assigned,
hole,
)
} else if env.is_imported_symbol(proc_name) {
add_needed_external(procs, env, fn_var, LambdaName::no_niche(proc_name));
force_thunk(env, proc_name, ret_layout, assigned, hole)
} else {
panic!("most likely we're trying to call something that is not a function");
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn call_by_name_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
fn_var: Variable,
proc_name: Symbol,
loc_args: std::vec::Vec<(Variable, Loc<roc_can::expr::Expr>)>,
lambda_set: LambdaSet<'a>,
argument_layouts: &'a [Layout<'a>],
ret_layout: &'a Layout<'a>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let original_fn_var = fn_var;
let arena = env.arena;
// the arguments given to the function, stored in symbols
let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena);
field_symbols.extend(loc_args.iter().map(|(arg_var, arg_expr)| {
possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg_expr.value, *arg_var)
}));
// THEORY: with a call by name, there are three options:
// - this is actually a thunk, and the lambda set is empty
// - the name references a function directly, like `main = \x -> ...`. In this case the
// lambda set includes only the function itself, and hence there is exactly one captures
// niche for the function.
// - the name references a value that yields a function, like
// `main = if b then \x -> .. else \y -> ..`. In that case the name being called never
// actually appears in the lambda set, and in fact has no capture set, and hence no
// captures niche.
// So, if this function has any captures niche, it will be the first one.
let mut iter_lambda_names = lambda_set
.iter_set()
.filter(|lam_name| lam_name.name() == proc_name);
let proc_name = match iter_lambda_names.next() {
Some(name) => {
debug_assert!(
iter_lambda_names.next().is_none(),
"Somehow, call by name for {:?} has multiple capture niches: {:?}",
proc_name,
lambda_set
);
name
}
None => LambdaName::no_niche(proc_name),
};
// If required, add an extra argument to the layout that is the captured environment
// afterwards, we MUST make sure the number of arguments in the layout matches the
// number of arguments actually passed.
let top_level_layout = {
let argument_layouts =
lambda_set.extend_argument_list(env.arena, &layout_cache.interner, argument_layouts);
ProcLayout::new(
env.arena,
argument_layouts,
proc_name.captures_niche(),
*ret_layout,
)
};
// the variables of the given arguments
let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
for (var, _) in &loc_args {
match layout_cache.from_var(env.arena, *var, env.subs) {
Ok(_) => {
pattern_vars.push(*var);
}
Err(_) => {
// One of this function's arguments code gens to a runtime error,
// so attempting to call it will immediately crash.
return Stmt::RuntimeError("TODO runtime error for invalid layout");
}
}
}
// If we've already specialized this one, no further work is needed.
if procs
.specialized
.is_specialized(proc_name.name(), &top_level_layout)
{
debug_assert_eq!(
argument_layouts.len(),
field_symbols.len(),
"see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
);
call_specialized_proc(
env,
procs,
proc_name,
lambda_set,
RawFunctionLayout::Function(argument_layouts, lambda_set, ret_layout),
top_level_layout,
field_symbols.into_bump_slice(),
loc_args,
layout_cache,
assigned,
hole,
)
} else if env.is_imported_symbol(proc_name.name())
|| env.is_unloaded_derived_symbol(proc_name.name(), procs)
{
add_needed_external(procs, env, original_fn_var, proc_name);
debug_assert_ne!(proc_name.name().module_id(), ModuleId::ATTR);
if procs.is_imported_module_thunk(proc_name.name()) {
force_thunk(
env,
proc_name.name(),
Layout::LambdaSet(lambda_set),
assigned,
hole,
)
} else if field_symbols.is_empty() {
// this is a case like `Str.concat`, an imported standard function, applied to zero arguments
// imported symbols cannot capture anything
let captured = &[];
debug_assert!(proc_name.no_captures());
construct_closure_data(
env,
layout_cache,
lambda_set,
proc_name,
captured,
assigned,
hole,
)
} else {
debug_assert_eq!(
argument_layouts.len(),
field_symbols.len(),
"see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
);
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout,
arg_layouts: argument_layouts,
specialization_id: env.next_call_specialization_id(),
},
arguments: field_symbols,
};
let result = build_call(env, call, assigned, *ret_layout, hole);
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
assign_to_symbols(env, procs, layout_cache, iter, result)
}
} else {
// 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.
if procs.is_module_thunk(proc_name.name()) {
debug_assert!(top_level_layout.arguments.is_empty());
}
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
suspended.specialization(env.subs, proc_name, top_level_layout, fn_var);
debug_assert_eq!(
argument_layouts.len(),
field_symbols.len(),
"see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
);
let field_symbols = field_symbols.into_bump_slice();
call_specialized_proc(
env,
procs,
proc_name,
lambda_set,
RawFunctionLayout::Function(argument_layouts, lambda_set, ret_layout),
top_level_layout,
field_symbols,
loc_args,
layout_cache,
assigned,
hole,
)
}
(PendingSpecializations::Making(_), false) => {
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name.name());
let field_symbols = field_symbols.into_bump_slice();
match opt_partial_proc {
Some(partial_proc) => {
// 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
.mark_in_progress(proc_name.name(), top_level_layout);
match specialize_variable(
env,
procs,
proc_name,
layout_cache,
fn_var,
&[],
partial_proc,
) {
Ok((proc, layout)) => {
let proc_name = proc.name;
let function_layout = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
layout,
proc_name.captures_niche(),
);
procs.specialized.insert_specialized(
proc_name.name(),
function_layout,
proc,
);
// now we just call our freshly-specialized function
call_specialized_proc(
env,
procs,
proc_name,
lambda_set,
layout,
function_layout,
field_symbols,
loc_args,
layout_cache,
assigned,
hole,
)
}
Err(SpecializeFailure { attempted_layout }) => {
let proc = generate_runtime_error_function(
env,
layout_cache,
proc_name.name(),
attempted_layout,
);
let proc_name = proc.name;
let function_layout = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
attempted_layout,
proc_name.captures_niche(),
);
procs.specialized.insert_specialized(
proc_name.name(),
function_layout,
proc,
);
call_specialized_proc(
env,
procs,
proc_name,
lambda_set,
attempted_layout,
function_layout,
field_symbols,
loc_args,
layout_cache,
assigned,
hole,
)
}
}
}
None => {
unreachable!("Proc name {:?} is invalid", proc_name)
}
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn call_by_name_module_thunk<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
fn_var: Variable,
proc_name: Symbol,
ret_layout: &'a Layout<'a>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let top_level_layout = ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), *ret_layout);
let inner_layout = *ret_layout;
// If we've already specialized this one, no further work is needed.
let already_specialized = procs
.specialized
.is_specialized(proc_name, &top_level_layout);
if already_specialized {
force_thunk(env, proc_name, inner_layout, assigned, hole)
} else {
// 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.
if procs.is_module_thunk(proc_name) {
debug_assert!(top_level_layout.arguments.is_empty());
}
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
suspended.specialization(
env.subs,
LambdaName::no_niche(proc_name),
top_level_layout,
fn_var,
);
force_thunk(env, proc_name, inner_layout, assigned, hole)
}
(PendingSpecializations::Making(_), false) => {
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
match opt_partial_proc {
Some(partial_proc) => {
// 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
.mark_in_progress(proc_name, top_level_layout);
match specialize_variable(
env,
procs,
LambdaName::no_niche(proc_name),
layout_cache,
fn_var,
&[],
partial_proc,
) {
Ok((proc, raw_layout)) => {
debug_assert!(
raw_layout.is_zero_argument_thunk(),
"but actually {:?}",
raw_layout
);
let was_present = procs
.specialized
.remove_specialized(proc_name, &top_level_layout);
debug_assert!(was_present);
procs.specialized.insert_specialized(
proc_name,
top_level_layout,
proc,
);
force_thunk(env, proc_name, inner_layout, assigned, hole)
}
Err(SpecializeFailure { attempted_layout }) => {
let proc = generate_runtime_error_function(
env,
layout_cache,
proc_name,
attempted_layout,
);
let was_present = procs
.specialized
.remove_specialized(proc_name, &top_level_layout);
debug_assert!(was_present);
procs.specialized.insert_specialized(
proc_name,
top_level_layout,
proc,
);
force_thunk(env, proc_name, inner_layout, assigned, hole)
}
}
}
None => {
unreachable!("Proc name {:?} is invalid", proc_name)
}
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn call_specialized_proc<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
proc_name: LambdaName<'a>,
lambda_set: LambdaSet<'a>,
layout: RawFunctionLayout<'a>,
function_layout: ProcLayout<'a>,
field_symbols: &'a [Symbol],
loc_args: std::vec::Vec<(Variable, Loc<roc_can::expr::Expr>)>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
if field_symbols.is_empty() {
debug_assert!(loc_args.is_empty());
// This happens when we return a function, e.g.
//
// foo = Num.add
//
// Even though the layout (and type) are functions,
// there are no arguments. This confuses our IR,
// and we have to fix it here.
match layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
// when the body is a closure, the function will return the closure environment
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: env.arena.alloc(function_layout.result),
arg_layouts: function_layout.arguments,
specialization_id: env.next_call_specialization_id(),
},
arguments: field_symbols,
};
// the closure argument is already added here (to get the right specialization)
// but now we need to remove it because the `match_on_lambda_set` will add it again
build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole)
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!()
}
}
} else {
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
match procs
.partial_procs
.get_symbol(proc_name.name())
.map(|pp| &pp.captured_symbols)
{
Some(&CapturedSymbols::Captured(captured_symbols)) => {
let symbols =
Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice();
let closure_data_symbol = env.unique_symbol();
// the closure argument is already added here (to get the right specialization)
// but now we need to remove it because the `match_on_lambda_set` will add it again
let mut argument_layouts =
Vec::from_iter_in(function_layout.arguments.iter().copied(), env.arena);
argument_layouts.pop().unwrap();
debug_assert_eq!(argument_layouts.len(), field_symbols.len(),);
let new_hole = match_on_lambda_set(
env,
layout_cache,
procs,
lambda_set,
closure_data_symbol,
field_symbols,
argument_layouts.into_bump_slice(),
env.arena.alloc(function_layout.result),
assigned,
hole,
);
let result = construct_closure_data(
env,
layout_cache,
lambda_set,
proc_name,
symbols.iter().copied(),
closure_data_symbol,
env.arena.alloc(new_hole),
);
assign_to_symbols(env, procs, layout_cache, iter, result)
}
_ => {
debug_assert_eq!(
function_layout.arguments.len(),
field_symbols.len(),
"function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}",
proc_name,
function_layout,
function_layout.arguments.len(),
field_symbols.len(),
);
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: env.arena.alloc(function_layout.result),
arg_layouts: function_layout.arguments,
specialization_id: env.next_call_specialization_id(),
},
arguments: field_symbols,
};
let result = build_call(env, call, assigned, function_layout.result, hole);
assign_to_symbols(env, procs, layout_cache, iter, result)
}
}
}
}
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern<'a> {
Identifier(Symbol),
Underscore,
IntLiteral([u8; 16], IntWidth),
FloatLiteral(u64, FloatWidth),
DecimalLiteral([u8; 16]),
BitLiteral {
value: bool,
tag_name: TagName,
union: roc_exhaustive::Union,
},
EnumLiteral {
tag_id: u8,
tag_name: TagName,
union: roc_exhaustive::Union,
},
StrLiteral(Box<str>),
RecordDestructure(Vec<'a, RecordDestruct<'a>>, &'a [Layout<'a>]),
NewtypeDestructure {
tag_name: TagName,
arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>,
},
AppliedTag {
tag_name: TagName,
tag_id: TagIdIntType,
arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>,
layout: UnionLayout<'a>,
union: roc_exhaustive::Union,
},
Voided {
tag_name: TagName,
},
OpaqueUnwrap {
opaque: Symbol,
argument: Box<(Pattern<'a>, Layout<'a>)>,
},
}
impl<'a> Pattern<'a> {
/// This pattern contains a pattern match on Void (i.e. [], the empty tag union)
/// such branches are not reachable at runtime
pub fn is_voided(&self) -> bool {
let mut stack: std::vec::Vec<&Pattern> = vec![self];
while let Some(pattern) = stack.pop() {
match pattern {
Pattern::Identifier(_)
| Pattern::Underscore
| Pattern::IntLiteral(_, _)
| Pattern::FloatLiteral(_, _)
| Pattern::DecimalLiteral(_)
| Pattern::BitLiteral { .. }
| Pattern::EnumLiteral { .. }
| Pattern::StrLiteral(_) => { /* terminal */ }
Pattern::RecordDestructure(destructs, _) => {
for destruct in destructs {
match &destruct.typ {
DestructType::Required(_) => { /* do nothing */ }
DestructType::Guard(pattern) => {
stack.push(pattern);
}
}
}
}
Pattern::NewtypeDestructure { arguments, .. } => {
stack.extend(arguments.iter().map(|(t, _)| t))
}
Pattern::Voided { .. } => return true,
Pattern::AppliedTag { arguments, .. } => {
stack.extend(arguments.iter().map(|(t, _)| t))
}
Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0),
}
}
false
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RecordDestruct<'a> {
pub label: Lowercase,
pub variable: Variable,
pub layout: Layout<'a>,
pub typ: DestructType<'a>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum DestructType<'a> {
Required(Symbol),
Guard(Pattern<'a>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct WhenBranch<'a> {
pub patterns: Vec<'a, Pattern<'a>>,
pub value: Expr<'a>,
pub guard: Option<Stmt<'a>>,
}
#[allow(clippy::type_complexity)]
fn from_can_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pattern: &roc_can::pattern::Pattern,
) -> Result<
(
Pattern<'a>,
Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
),
RuntimeError,
> {
let mut assignments = Vec::new_in(env.arena);
let pattern = from_can_pattern_help(env, procs, layout_cache, can_pattern, &mut assignments)?;
Ok((pattern, assignments))
}
fn from_can_pattern_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pattern: &roc_can::pattern::Pattern,
assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
) -> Result<Pattern<'a>, RuntimeError> {
use roc_can::pattern::Pattern::*;
match can_pattern {
Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern(
env,
layout_cache,
*var,
int_str,
IntOrFloatValue::Int(*int),
)),
FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern(
env,
layout_cache,
*var,
float_str,
IntOrFloatValue::Float(*float),
)),
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
SingleQuote(var, _, c, _) => match layout_cache.from_var(env.arena, *var, env.subs) {
Ok(Layout::Builtin(Builtin::Int(width))) => {
Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width))
}
o => internal_error!("an integer width was expected, but we found {:?}", o),
},
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
original_region: *region,
shadow: ident.clone(),
kind: ShadowKind::Variable,
}),
UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
MalformedPattern(_problem, region) => {
// TODO preserve malformed problem information here?
Err(RuntimeError::UnsupportedPattern(*region))
}
OpaqueNotInScope(loc_ident) => {
// TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
Err(RuntimeError::UnsupportedPattern(loc_ident.region))
}
NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern(
env,
layout_cache,
*var,
num_str,
IntOrFloatValue::Int(*num),
)),
AppliedTag {
whole_var,
tag_name,
arguments,
..
} => {
use crate::layout::UnionVariant::*;
use roc_exhaustive::Union;
let res_variant = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
crate::layout::union_sorted_tags(&mut layout_env, *whole_var).map_err(Into::into)
};
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Err(RuntimeError::UnresolvedTypeVar)
}
Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
};
let result = match variant {
Never => unreachable!(
"there is no pattern of type `[]`, union var {:?}",
*whole_var
),
Unit => Pattern::EnumLiteral {
tag_id: 0,
tag_name: tag_name.clone(),
union: Union {
render_as: RenderAs::Tag,
alternatives: vec![Ctor {
tag_id: TagId(0),
name: CtorName::Tag(tag_name.clone()),
arity: 0,
}],
},
},
BoolUnion { ttrue, ffalse } => {
let (ttrue, ffalse) = (ttrue.expect_tag(), ffalse.expect_tag());
Pattern::BitLiteral {
value: tag_name == &ttrue,
tag_name: tag_name.clone(),
union: Union {
render_as: RenderAs::Tag,
alternatives: vec![
Ctor {
tag_id: TagId(0),
name: CtorName::Tag(ffalse),
arity: 0,
},
Ctor {
tag_id: TagId(1),
name: CtorName::Tag(ttrue),
arity: 0,
},
],
},
}
}
ByteUnion(tag_names) => {
let tag_id = tag_names
.iter()
.position(|key| tag_name == key.expect_tag_ref())
.expect("tag must be in its own type");
let mut ctors = std::vec::Vec::with_capacity(tag_names.len());
for (i, tag_name) in tag_names.into_iter().enumerate() {
ctors.push(Ctor {
tag_id: TagId(i as _),
name: CtorName::Tag(tag_name.expect_tag()),
arity: 0,
})
}
let union = roc_exhaustive::Union {
render_as: RenderAs::Tag,
alternatives: ctors,
};
Pattern::EnumLiteral {
tag_id: tag_id as u8,
tag_name: tag_name.clone(),
union,
}
}
Newtype {
arguments: field_layouts,
..
} => {
let mut arguments = arguments.clone();
arguments.sort_by(|arg1, arg2| {
let size1 = layout_cache
.from_var(env.arena, arg1.0, env.subs)
.map(|x| x.alignment_bytes(&layout_cache.interner, env.target_info))
.unwrap_or(0);
let size2 = layout_cache
.from_var(env.arena, arg2.0, env.subs)
.map(|x| x.alignment_bytes(&layout_cache.interner, env.target_info))
.unwrap_or(0);
size2.cmp(&size1)
});
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::NewtypeDestructure {
tag_name: tag_name.clone(),
arguments: mono_args,
}
}
NewtypeByVoid {
data_tag_arguments,
data_tag_name,
..
} => {
let data_tag_name = data_tag_name.expect_tag();
if tag_name != &data_tag_name {
// this tag is not represented at runtime
Pattern::Voided {
tag_name: tag_name.clone(),
}
} else {
let mut arguments = arguments.clone();
arguments.sort_by(|arg1, arg2| {
let size1 = layout_cache
.from_var(env.arena, arg1.0, env.subs)
.map(|x| x.alignment_bytes(&layout_cache.interner, env.target_info))
.unwrap_or(0);
let size2 = layout_cache
.from_var(env.arena, arg2.0, env.subs)
.map(|x| x.alignment_bytes(&layout_cache.interner, env.target_info))
.unwrap_or(0);
size2.cmp(&size1)
});
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
let it = arguments.iter().zip(data_tag_arguments.iter());
for ((_, loc_pat), layout) in it {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::NewtypeDestructure {
tag_name: tag_name.clone(),
arguments: mono_args,
}
}
}
Wrapped(variant) => {
let (tag_id, argument_layouts) = variant.tag_name_to_id(tag_name);
let number_of_tags = variant.number_of_tags();
let mut ctors = std::vec::Vec::with_capacity(number_of_tags);
let arguments = {
let mut temp = arguments.clone();
temp.sort_by(|arg1, arg2| {
let layout1 =
layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
let layout2 =
layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
let size1 =
layout1.alignment_bytes(&layout_cache.interner, env.target_info);
let size2 =
layout2.alignment_bytes(&layout_cache.interner, env.target_info);
size2.cmp(&size1)
});
temp
};
// we must derive the union layout from the whole_var, building it up
// from `layouts` would unroll recursive tag unions, and that leads to
// problems down the line because we hash layouts and an unrolled
// version is not the same as the minimal version.
let layout = match layout_cache.from_var(env.arena, *whole_var, env.subs) {
Ok(Layout::Union(ul)) => ul,
_ => unreachable!(),
};
use WrappedVariant::*;
match variant {
NonRecursive {
sorted_tag_layouts: ref tags,
} => {
debug_assert!(tags.len() > 1);
for (i, (tag_name, args)) in tags.iter().enumerate() {
ctors.push(Ctor {
tag_id: TagId(i as _),
name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
arity: args.len(),
})
}
let union = roc_exhaustive::Union {
render_as: RenderAs::Tag,
alternatives: ctors,
};
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
debug_assert_eq!(
arguments.len(),
argument_layouts.len(),
"The {:?} tag got {} arguments, but its layout expects {}!",
tag_name,
arguments.len(),
argument_layouts.len(),
);
let it = argument_layouts.iter();
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::AppliedTag {
tag_name: tag_name.clone(),
tag_id: tag_id as _,
arguments: mono_args,
union,
layout,
}
}
Recursive {
sorted_tag_layouts: ref tags,
} => {
debug_assert!(tags.len() > 1);
for (i, (tag_name, args)) in tags.iter().enumerate() {
ctors.push(Ctor {
tag_id: TagId(i as _),
name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
// don't include tag discriminant in arity
arity: args.len() - 1,
})
}
let union = roc_exhaustive::Union {
render_as: RenderAs::Tag,
alternatives: ctors,
};
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
debug_assert_eq!(arguments.len(), argument_layouts.len());
let it = argument_layouts.iter();
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::AppliedTag {
tag_name: tag_name.clone(),
tag_id: tag_id as _,
arguments: mono_args,
union,
layout,
}
}
NonNullableUnwrapped {
tag_name: w_tag_name,
fields,
} => {
debug_assert_eq!(w_tag_name.expect_tag_ref(), tag_name);
ctors.push(Ctor {
tag_id: TagId(0),
name: CtorName::Tag(tag_name.clone()),
arity: fields.len(),
});
let union = roc_exhaustive::Union {
render_as: RenderAs::Tag,
alternatives: ctors,
};
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
debug_assert_eq!(arguments.len(), argument_layouts.len());
let it = argument_layouts.iter();
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::AppliedTag {
tag_name: tag_name.clone(),
tag_id: tag_id as _,
arguments: mono_args,
union,
layout,
}
}
NullableWrapped {
sorted_tag_layouts: ref tags,
nullable_id,
nullable_name,
} => {
debug_assert!(!tags.is_empty());
let mut i = 0;
for (tag_name, args) in tags.iter() {
if i == nullable_id as usize {
ctors.push(Ctor {
tag_id: TagId(i as _),
name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
// don't include tag discriminant in arity
arity: 0,
});
i += 1;
}
ctors.push(Ctor {
tag_id: TagId(i as _),
name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
// don't include tag discriminant in arity
arity: args.len() - 1,
});
i += 1;
}
if i == nullable_id as usize {
ctors.push(Ctor {
tag_id: TagId(i as _),
name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
// don't include tag discriminant in arity
arity: 0,
});
}
let union = roc_exhaustive::Union {
render_as: RenderAs::Tag,
alternatives: ctors,
};
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
let it = if tag_name == nullable_name.expect_tag_ref() {
[].iter()
} else {
argument_layouts.iter()
};
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::AppliedTag {
tag_name: tag_name.clone(),
tag_id: tag_id as _,
arguments: mono_args,
union,
layout,
}
}
NullableUnwrapped {
other_fields,
nullable_id,
nullable_name,
other_name: _,
} => {
debug_assert!(!other_fields.is_empty());
ctors.push(Ctor {
tag_id: TagId(nullable_id as _),
name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
arity: 0,
});
ctors.push(Ctor {
tag_id: TagId(!nullable_id as _),
name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
// FIXME drop tag
arity: other_fields.len() - 1,
});
let union = roc_exhaustive::Union {
render_as: RenderAs::Tag,
alternatives: ctors,
};
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
let it = if tag_name == nullable_name.expect_tag_ref() {
[].iter()
} else {
// FIXME drop tag
argument_layouts.iter()
};
for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::AppliedTag {
tag_name: tag_name.clone(),
tag_id: tag_id as _,
arguments: mono_args,
union,
layout,
}
}
}
}
};
Ok(result)
}
UnwrappedOpaque {
opaque, argument, ..
} => {
let (arg_var, loc_arg_pattern) = &(**argument);
let arg_layout = layout_cache
.from_var(env.arena, *arg_var, env.subs)
.unwrap();
let mono_arg_pattern = from_can_pattern_help(
env,
procs,
layout_cache,
&loc_arg_pattern.value,
assignments,
)?;
Ok(Pattern::OpaqueUnwrap {
opaque: *opaque,
argument: Box::new((mono_arg_pattern, arg_layout)),
})
}
RecordDestructure {
whole_var,
destructs,
..
} => {
// sorted fields based on the type
let sorted_fields = {
let mut layout_env = layout::Env::from_components(
layout_cache,
env.subs,
env.arena,
env.target_info,
);
crate::layout::sort_record_fields(&mut layout_env, *whole_var)
.map_err(RuntimeError::from)?
};
// sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
let mut destructs_by_label = BumpMap::with_capacity_in(destructs.len(), env.arena);
destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x)));
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
// next we step through both sequences of fields. The outer loop is the sequence based
// on the type, since not all fields need to actually be destructured in the source
// language.
//
// However in mono patterns, we do destruct all patterns (but use Underscore) when
// in the source the field is not matche in the source language.
//
// Optional fields somewhat complicate the matter here
for (label, variable, res_layout) in sorted_fields.into_iter() {
match res_layout {
Ok(field_layout) => {
// the field is non-optional according to the type
match destructs_by_label.remove(&label) {
Some(destruct) => {
// this field is destructured by the pattern
mono_destructs.push(from_can_record_destruct(
env,
procs,
layout_cache,
&destruct.value,
field_layout,
assignments,
)?);
}
None => {
// this field is not destructured by the pattern
// put in an underscore
mono_destructs.push(RecordDestruct {
label: label.clone(),
variable,
layout: field_layout,
typ: DestructType::Guard(Pattern::Underscore),
});
}
}
// the layout of this field is part of the layout of the record
field_layouts.push(field_layout);
}
Err(field_layout) => {
// the field is optional according to the type
match destructs_by_label.remove(&label) {
Some(destruct) => {
// this field is destructured by the pattern
match &destruct.value.typ {
roc_can::pattern::DestructType::Optional(_, loc_expr) => {
// if we reach this stage, the optional field is not present
// so we push the default assignment into the branch
assignments.push((
destruct.value.symbol,
variable,
loc_expr.value.clone(),
));
}
_ => unreachable!(
"only optional destructs can be optional fields"
),
};
}
None => {
// this field is not destructured by the pattern
// put in an underscore
mono_destructs.push(RecordDestruct {
label: label.clone(),
variable,
layout: field_layout,
typ: DestructType::Guard(Pattern::Underscore),
});
}
}
}
}
}
for (_, destruct) in destructs_by_label.drain() {
// this destruct is not in the type, but is in the pattern
// it must be an optional field, and we will use the default
match &destruct.value.typ {
roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
// TODO these don't match up in the uniqueness inference; when we remove
// that, reinstate this assert!
//
// dbg!(&env.subs.get_content_without_compacting(*field_var));
// dbg!(&env.subs.get_content_without_compacting(destruct.var).content);
// debug_assert_eq!(
// env.subs.get_root_key_without_compacting(*field_var),
// env.subs.get_root_key_without_compacting(destruct.value.var)
// );
assignments.push((
destruct.value.symbol,
// destruct.value.var,
*field_var,
loc_expr.value.clone(),
));
}
_ => unreachable!("only optional destructs can be optional fields"),
}
}
Ok(Pattern::RecordDestructure(
mono_destructs,
field_layouts.into_bump_slice(),
))
}
}
}
fn from_can_record_destruct<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_rd: &roc_can::pattern::RecordDestruct,
field_layout: Layout<'a>,
assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
) -> Result<RecordDestruct<'a>, RuntimeError> {
Ok(RecordDestruct {
label: can_rd.label.clone(),
variable: can_rd.var,
layout: field_layout,
typ: match &can_rd.typ {
roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol),
roc_can::pattern::DestructType::Optional(_, _) => {
// if we reach this stage, the optional field is present
DestructType::Required(can_rd.symbol)
}
roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard(
from_can_pattern_help(env, procs, layout_cache, &loc_pattern.value, assignments)?,
),
},
})
}
enum IntOrFloatValue {
Int(IntValue),
Float(f64),
}
enum NumLiteral {
Int([u8; 16], IntWidth),
U128([u8; 16]),
Float(f64, FloatWidth),
Decimal([u8; 16]),
}
impl NumLiteral {
fn to_expr_literal(&self) -> Literal<'static> {
match *self {
NumLiteral::Int(n, _) => Literal::Int(n),
NumLiteral::U128(n) => Literal::U128(n),
NumLiteral::Float(n, _) => Literal::Float(n),
NumLiteral::Decimal(n) => Literal::Decimal(n),
}
}
fn to_pattern(&self) -> Pattern<'static> {
match *self {
NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w),
NumLiteral::U128(n) => Pattern::IntLiteral(n, IntWidth::U128),
NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w),
NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n),
}
}
}
fn make_num_literal(layout: Layout<'_>, num_str: &str, num_value: IntOrFloatValue) -> NumLiteral {
match layout {
Layout::Builtin(Builtin::Int(width)) => match num_value {
IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width),
IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n),
IntOrFloatValue::Float(..) => {
internal_error!("Float value where int was expected, should have been a type error")
}
},
Layout::Builtin(Builtin::Float(width)) => match num_value {
IntOrFloatValue::Float(n) => NumLiteral::Float(n, width),
IntOrFloatValue::Int(int_value) => match int_value {
IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width),
IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width),
},
},
Layout::Builtin(Builtin::Decimal) => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
None => internal_error!(
"Invalid decimal for float literal = {}. This should be a type error!",
num_str
),
};
NumLiteral::Decimal(dec.to_ne_bytes())
}
layout => internal_error!(
"Found a non-num layout where a number was expected: {:?}",
layout
),
}
}
fn assign_num_literal_expr<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
variable: Variable,
num_str: &str,
num_value: IntOrFloatValue,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
let literal = make_num_literal(layout, num_str, num_value).to_expr_literal();
Stmt::Let(assigned, Expr::Literal(literal), layout, hole)
}
fn make_num_literal_pattern<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
variable: Variable,
num_str: &str,
num_value: IntOrFloatValue,
) -> Pattern<'a> {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
let literal = make_num_literal(layout, num_str, num_value);
literal.to_pattern()
}
type ToLowLevelCallArguments<'a> = (
LambdaName<'a>,
Symbol,
Option<Layout<'a>>,
CallSpecId,
UpdateModeId,
);
/// Use the lambda set to figure out how to make a lowlevel call
#[allow(clippy::too_many_arguments)]
fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
lambda_set: LambdaSet<'a>,
op: LowLevel,
closure_data_symbol: Symbol,
to_lowlevel_call: ToLowLevelCall,
return_layout: Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.call_by_name_options(&layout_cache.interner) {
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(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
union_layout.tag_id_layout(),
closure_data_symbol,
lambda_set.is_represented(&layout_cache.interner),
to_lowlevel_call,
return_layout,
assigned,
hole,
);
// extract & assign the closure_tag_id_symbol
let expr = Expr::GetTagId {
structure: closure_data_symbol,
union_layout,
};
Stmt::Let(
closure_tag_id_symbol,
expr,
union_layout.tag_id_layout(),
env.arena.alloc(result),
)
}
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();
let call = to_lowlevel_call((
lambda_name,
closure_data_symbol,
lambda_set.is_represented(&layout_cache.interner),
call_spec_id,
update_mode,
));
build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
}
None => {
eprintln!(
"a function passed to `{:?}` LowLevel call has an empty lambda set!
The most likely reason is that some symbol you use is not in scope.
",
op
);
hole.clone()
}
},
ClosureCallOptions::UnwrappedCapture(_) => {
let lambda_name = lambda_set
.iter_set()
.next()
.expect("no function in lambda set");
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(&layout_cache.interner),
call_spec_id,
update_mode,
));
build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
}
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(&layout_cache.interner),
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(&layout_cache.interner),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
},
}
}
#[allow(clippy::too_many_arguments)]
fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>(
env: &mut Env<'a, '_>,
lambda_set: impl ExactSizeIterator<Item = LambdaName<'a>> + 'a,
closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol,
closure_env_layout: Option<Layout<'a>>,
to_lowlevel_call: ToLowLevelCall,
return_layout: Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
debug_assert_ne!(lambda_set.len(), 0);
let join_point_id = JoinPointId(env.unique_symbol());
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
for (i, lambda_name) in lambda_set.into_iter().enumerate() {
let assigned = env.unique_symbol();
let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned]));
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,
closure_env_layout,
call_spec_id,
update_mode,
));
let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole));
branches.push((i as u64, BranchInfo::None, stmt));
}
let default_branch = {
let (_, info, stmt) = branches.pop().unwrap();
(info, &*env.arena.alloc(stmt))
};
let switch = Stmt::Switch {
cond_symbol: closure_tag_id_symbol,
cond_layout: closure_tag_id_layout,
branches: branches.into_bump_slice(),
default_branch,
ret_layout: return_layout,
};
let param = Param {
symbol: assigned,
layout: return_layout,
borrow: false,
};
Stmt::Join {
id: join_point_id,
parameters: &*env.arena.alloc([param]),
body: hole,
remainder: env.arena.alloc(switch),
}
}
fn empty_lambda_set_error() -> Stmt<'static> {
let msg = "a Lambda Set is empty. Most likely there is a type error in your program.";
Stmt::RuntimeError(msg)
}
/// Use the lambda set to figure out how to make a call-by-name
#[allow(clippy::too_many_arguments)]
fn match_on_lambda_set<'a>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
procs: &mut Procs<'a>,
lambda_set: LambdaSet<'a>,
closure_data_symbol: Symbol,
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
match lambda_set.call_by_name_options(&layout_cache.interner) {
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,
layout_cache,
lambda_set,
closure_tag_id_symbol,
union_layout.tag_id_layout(),
closure_data_symbol,
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
);
// extract & assign the closure_tag_id_symbol
let expr = Expr::GetTagId {
structure: closure_data_symbol,
union_layout,
};
Stmt::Let(
closure_tag_id_symbol,
expr,
union_layout.tag_id_layout(),
env.arena.alloc(result),
)
}
ClosureCallOptions::Struct {
field_layouts,
field_order_hash: _,
} => {
let function_symbol = match lambda_set.iter_set().next() {
Some(function_symbol) => function_symbol,
None => {
// Lambda set is empty, so this function is never called; synthesize a function
// that always yields a runtime error.
let name = env.unique_symbol();
let function_layout =
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout);
let proc =
generate_runtime_error_function(env, layout_cache, name, function_layout);
let top_level = ProcLayout::from_raw(
env.arena,
&layout_cache.interner,
function_layout,
CapturesNiche::no_niche(),
);
procs.specialized.insert_specialized(name, top_level, proc);
LambdaName::no_niche(name)
}
};
let closure_info = match field_layouts {
[] => ClosureInfo::DoesNotCapture,
_ => ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
},
};
union_lambda_set_branch_help(
env,
layout_cache,
function_symbol,
closure_info,
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
ClosureCallOptions::UnwrappedCapture(_) => {
let function_symbol = lambda_set
.iter_set()
.next()
.expect("no function in lambda set");
let closure_info = ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
};
union_lambda_set_branch_help(
env,
layout_cache,
function_symbol,
closure_info,
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
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::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,
)
}
},
}
}
#[allow(clippy::too_many_arguments)]
fn union_lambda_set_to_switch<'a>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
lambda_set: LambdaSet<'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>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
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
// hitting this one
return empty_lambda_set_error();
}
let join_point_id = JoinPointId(env.unique_symbol());
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() {
ClosureInfo::DoesNotCapture
} else {
ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
}
};
let stmt = union_lambda_set_branch(
env,
layout_cache,
join_point_id,
lambda_name,
closure_info,
argument_symbols,
argument_layouts,
return_layout,
);
branches.push((i as u64, BranchInfo::None, stmt));
}
let default_branch = {
let (_, info, stmt) = branches.pop().unwrap();
(info, &*env.arena.alloc(stmt))
};
let switch = Stmt::Switch {
cond_symbol: closure_tag_id_symbol,
cond_layout: closure_tag_id_layout,
branches: branches.into_bump_slice(),
default_branch,
ret_layout: *return_layout,
};
let param = Param {
symbol: assigned,
layout: *return_layout,
borrow: false,
};
Stmt::Join {
id: join_point_id,
parameters: &*env.arena.alloc([param]),
body: hole,
remainder: env.arena.alloc(switch),
}
}
#[allow(clippy::too_many_arguments)]
fn union_lambda_set_branch<'a>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
join_point_id: JoinPointId,
lambda_name: LambdaName<'a>,
closure_info: ClosureInfo<'a>,
argument_symbols_slice: &'a [Symbol],
argument_layouts_slice: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
) -> Stmt<'a> {
let result_symbol = env.unique_symbol();
let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
union_lambda_set_branch_help(
env,
layout_cache,
lambda_name,
closure_info,
argument_symbols_slice,
argument_layouts_slice,
return_layout,
result_symbol,
env.arena.alloc(hole),
)
}
#[derive(Clone, Copy)]
enum ClosureInfo<'a> {
Captures {
closure_data_symbol: Symbol,
/// The whole lambda set representation this closure is a variant of
lambda_set: LambdaSet<'a>,
},
DoesNotCapture,
}
#[allow(clippy::too_many_arguments)]
fn union_lambda_set_branch_help<'a>(
env: &mut Env<'a, '_>,
layout_cache: &LayoutCache<'a>,
lambda_name: LambdaName<'a>,
closure_info: ClosureInfo<'a>,
argument_symbols_slice: &'a [Symbol],
argument_layouts_slice: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let (argument_layouts, argument_symbols) = match closure_info {
ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
} => {
let argument_layouts = lambda_set.extend_argument_list(
env.arena,
&layout_cache.interner,
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_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
// argument in this case
(argument_layouts_slice, argument_symbols_slice)
}
};
// build the call
let call = self::Call {
call_type: CallType::ByName {
name: lambda_name,
ret_layout: return_layout,
arg_layouts: argument_layouts,
specialization_id: env.next_call_specialization_id(),
},
arguments: argument_symbols,
};
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>,
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
debug_assert_ne!(lambda_set.len(), 0);
let join_point_id = JoinPointId(env.unique_symbol());
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
for (i, lambda_name) in lambda_set.into_iter().enumerate() {
let stmt = enum_lambda_set_branch(
env,
join_point_id,
lambda_name,
argument_symbols,
argument_layouts,
return_layout,
);
branches.push((i as u64, BranchInfo::None, stmt));
}
let default_branch = {
let (_, info, stmt) = branches.pop().unwrap();
(info, &*env.arena.alloc(stmt))
};
let switch = Stmt::Switch {
cond_symbol: closure_tag_id_symbol,
cond_layout: closure_tag_id_layout,
branches: branches.into_bump_slice(),
default_branch,
ret_layout: *return_layout,
};
let param = Param {
symbol: assigned,
layout: *return_layout,
borrow: false,
};
Stmt::Join {
id: join_point_id,
parameters: &*env.arena.alloc([param]),
body: hole,
remainder: env.arena.alloc(switch),
}
}
/// 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>,
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
) -> Stmt<'a> {
let result_symbol = env.unique_symbol();
let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
let assigned = result_symbol;
let call = self::Call {
call_type: CallType::ByName {
name: lambda_name,
ret_layout: return_layout,
arg_layouts: argument_layouts,
specialization_id: env.next_call_specialization_id(),
},
arguments: argument_symbols,
};
build_call(env, call, assigned, *return_layout, env.arena.alloc(hole))
}
#[allow(clippy::too_many_arguments)]
fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>(
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,
closure_env_layout: Option<Layout<'a>>,
to_lowlevel_call: ToLowLevelCall,
return_layout: Layout<'a>,
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
debug_assert_ne!(lambda_set.len(), 0);
let join_point_id = JoinPointId(env.unique_symbol());
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
for (i, function_symbol) in lambda_set.into_iter().enumerate() {
let result_symbol = env.unique_symbol();
let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
let call_spec_id = env.next_call_specialization_id();
let update_mode = env.next_update_mode_id();
let call = to_lowlevel_call((
function_symbol,
closure_data_symbol,
closure_env_layout,
call_spec_id,
update_mode,
));
let stmt = build_call(
env,
call,
result_symbol,
return_layout,
env.arena.alloc(hole),
);
branches.push((i as u64, BranchInfo::None, stmt));
}
let default_branch = {
let (_, info, stmt) = branches.pop().unwrap();
(info, &*env.arena.alloc(stmt))
};
let switch = Stmt::Switch {
cond_symbol: closure_tag_id_symbol,
cond_layout: closure_tag_id_layout,
branches: branches.into_bump_slice(),
default_branch,
ret_layout: return_layout,
};
let param = Param {
symbol: assigned,
layout: return_layout,
borrow: false,
};
Stmt::Join {
id: join_point_id,
parameters: &*env.arena.alloc([param]),
body: hole,
remainder: env.arena.alloc(switch),
}
}