Improve representation of record fields in mono

This commit is contained in:
Richard Feldman 2024-11-18 21:36:24 -05:00
parent 073df686bc
commit 13efa083dd
No known key found for this signature in database
GPG key ID: DAC334802F365236
6 changed files with 459 additions and 307 deletions

View file

@ -17,4 +17,4 @@ pub use mono_module::{InternedStrId, Interns};
pub use mono_num::Number; pub use mono_num::Number;
pub use mono_struct::MonoFieldId; pub use mono_struct::MonoFieldId;
pub use mono_type::{MonoType, MonoTypeId, MonoTypes}; pub use mono_type::{MonoType, MonoTypeId, MonoTypes};
pub use specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}; pub use specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds};

View file

@ -1,22 +1,52 @@
use crate::{ use crate::{
mono_ir::{MonoExpr, MonoExprId, MonoExprs}, mono_ir::{MonoExpr, MonoExprId, MonoExprs, MonoStmt, MonoStmtId},
mono_module::Interns, mono_module::Interns,
mono_num::Number, mono_num::Number,
mono_type::{MonoType, MonoTypes, Primitive}, mono_type::{MonoType, MonoTypes, Primitive},
specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}, specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds},
DebugInfo, DebugInfo, MonoTypeId,
}; };
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_can::expr::{Expr, IntValue}; use roc_can::expr::{Expr, IntValue};
use roc_collections::Push; use roc_collections::{Push, VecMap};
use roc_module::{ident::Lowercase, symbol::ModuleId};
use roc_region::all::Region;
use roc_solve::module::Solved; use roc_solve::module::Solved;
use roc_types::subs::{Content, Subs}; use roc_types::subs::{Content, FlatType, Subs, Variable};
use soa::{Index, NonEmptySlice, Slice}; use soa::NonEmptySlice;
/// Function bodies that have already been specialized.
pub struct MonoFnCache {
inner: VecMap<(ModuleId, Variable, MonoTypeId), MonoExprId>,
}
impl MonoFnCache {
pub fn monomorphize_fn<'a, F: 'a + FnOnce(ModuleId, Variable) -> &'a Expr>(
&mut self,
// Sometimes we need to create specializations of functions that are defined in other modules.
module_id: ModuleId,
// The function Variable stored in the original function's canonical Expr. We use this as a way to
// uniquely identify the function expr within its module, since each fn Expr gets its own unique var.
// Doing it with Variable instead of IdentId lets us cache specializations of anonymous functions too.
fn_var: Variable,
// Given a ModuleId and Variable (to uniquely identify the canonical fn Expr within its module),
// get the canonical Expr of the function itself. We need this to create a specialization of it.
get_fn_expr: F,
// This tells us which specialization of the function we want.
mono_type_id: MonoTypeId,
) -> MonoExprId {
*self
.inner
.get_or_insert((module_id, fn_var, mono_type_id), || {
todo!("TODO lower the fn_expr using Env etc. (May need to add args to this method, not sure.)");
})
}
}
pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> { pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> {
arena: &'a Bump, arena: &'a Bump,
subs: &'s mut Subs, subs: &'s mut Subs,
types_cache: &'c mut MonoCache, types_cache: &'c mut MonoTypeCache,
mono_types: &'t mut MonoTypes, mono_types: &'t mut MonoTypes,
mono_exprs: &'t mut MonoExprs, mono_exprs: &'t mut MonoExprs,
record_field_ids: RecordFieldIds, record_field_ids: RecordFieldIds,
@ -30,7 +60,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
pub fn new( pub fn new(
arena: &'a Bump, arena: &'a Bump,
subs: &'s mut Solved<Subs>, subs: &'s mut Solved<Subs>,
types_cache: &'c mut MonoCache, types_cache: &'c mut MonoTypeCache,
mono_types: &'t mut MonoTypes, mono_types: &'t mut MonoTypes,
mono_exprs: &'t mut MonoExprs, mono_exprs: &'t mut MonoExprs,
record_field_ids: RecordFieldIds, record_field_ids: RecordFieldIds,
@ -58,6 +88,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
let mono_types = &mut self.mono_types; let mono_types = &mut self.mono_types;
let mut mono_from_var = |var| { let mut mono_from_var = |var| {
self.types_cache.monomorphize_var( self.types_cache.monomorphize_var(
self.arena,
self.subs, self.subs,
mono_types, mono_types,
&mut self.record_field_ids, &mut self.record_field_ids,
@ -151,59 +182,116 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
// Reserve a slice of IDs up front. This is so that we have a contiguous array // We want to end up with a Slice<MonoExpr> of these, so accumulate a buffer of them
// of field IDs at the end of this, each corresponding to the appropriate record field. // and then add them to MonoExprs all at once, so they're added as one contiguous slice
let field_ids: Slice<MonoExprId> = self.mono_exprs.reserve_ids(fields.len() as u16); // regardless of what else got added to MonoExprs as we were monomorphizing them.
let mut next_field_id = field_ids.start(); let mut buf: Vec<(MonoExpr, Region)> =
Vec::with_capacity_in(fields.len(), self.arena);
// Generate a MonoExpr for each field, using the reserved IDs so that we end up with buf.extend(
// that Slice being populated with the exprs in the fields, with the correct ordering. // flat_map these so we discard all the fields that monomorphized to None
fields.retain(|(_name, field)| { fields.into_iter().flat_map(|(_name, field)| {
match self.to_mono_expr(&field.loc_expr.value) { self.to_mono_expr(&field.loc_expr.value)
Some(mono_expr) => { .map(|mono_expr| (mono_expr, field.loc_expr.region))
// Safety: This will run *at most* field.len() times, possibly less, }),
// so this will never create an index that's out of bounds. );
let mono_expr_id =
unsafe { MonoExprId::new_unchecked(Index::new(next_field_id)) };
next_field_id += 1; // If we ended up with exactly 1 field, return it unwrapped.
if buf.len() == 1 {
self.mono_exprs return buf.pop().map(|(expr, _region)| expr);
.insert(mono_expr_id, mono_expr, field.loc_expr.region);
true
}
None => {
// Discard all the zero-sized fields as we go.
false
}
}
});
// Check for zero-sized and single-field records again now that we've discarded zero-sized fields,
// because we might have ended up with 0 or 1 remaining fields.
if fields.len() > 1 {
// Safety: We just verified that there's more than 1 field.
unsafe {
Some(MonoExpr::Struct(NonEmptySlice::new_unchecked(
field_ids.start,
fields.len() as u16,
)))
}
} else {
// If there are 0 fields remaining, return None. If there's 1, unwrap it.
fields
.first()
.and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value))
} }
NonEmptySlice::from_slice(self.mono_exprs.extend(buf.iter().copied()))
.map(MonoExpr::Struct)
} }
// Expr::Call((fn_var, fn_expr, capture_var, ret_var), args, called_via) => {
// let opt_ret_type = mono_from_var(*var);
// if opt_ret_type.is_none() {
// let fn_type = match self.subs.get_content_without_compacting(fn_var) {
// Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)) => {
// let todo = (); // TODO make is_effectful actually use the function's effectfulness!
// let is_effectful = false;
// // Calls to pure functions that return zero-sized types should be discarded.
// if !is_effectful {
// return None;
// }
// // Use the Content we already have to directly monomorphize the function, rather than
// // calling monomorphize_var and having it redo the Subs lookup and conditionals we just did.
// self.types_cache.monomorphize_fn(
// self.subs,
// self.mono_types,
// &mut self.record_field_ids,
// &mut self.tuple_elem_ids,
// &mut self.problems,
// self.debug_info,
// *arg_vars,
// *ret_var,
// )?
// }
// _ => {
// // This function didn't have a function type. Compiler bug!
// return Some(MonoExpr::CompilerBug(Problem::FnDidNotHaveFnType));
// }
// };
// let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type?
// let fn_expr = self.to_mono_expr(can_expr, stmts)?;
// let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices!
// let capture_type = mono_from_var(*capture_var);
// let todo = (); // How do we pre-reserve the statements? Is that possible? It does seem necessary...might not be possible though. Maybe we just need to make Vec rather than Slice on these.
// // We aren't returning anything, and this is an effectful function, so just push a statement to call it and move on.
// stmts.push(self.mono_stmts.add(MonoStmt::CallVoid {
// fn_type,
// fn_expr,
// args,
// capture_type,
// }));
// None
// } else {
// let fn_type = mono_from_var(*fn_var)?;
// let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type?
// let fn_expr = self.to_mono_expr(can_expr, stmts)?;
// let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices!
// let capture_type = mono_from_var(*capture_var);
// Some(MonoExpr::Call {
// fn_type,
// fn_expr,
// args,
// capture_type,
// })
// }
// }
// Expr::Var(symbol, var) => Some(MonoExpr::Lookup(*symbol, mono_from_var(*var)?)),
// Expr::LetNonRec(def, loc) => {
// let expr = self.to_mono_expr(def.loc_expr.value, stmts)?;
// let todo = (); // TODO if this is an underscore pattern and we're doing a fn call, convert it to Stmt::CallVoid
// let pattern = self.to_mono_pattern(def.loc_pattern.value);
// // TODO do we need to use any of these other fields? e.g. for the types?
// // pub struct Def {
// // pub loc_pattern: Loc<Pattern>,
// // pub loc_expr: Loc<Expr>,
// // pub expr_var: Variable,
// // pub pattern_vars: SendMap<Symbol, Variable>,
// // pub annotation: Option<Annotation>,
// // }
// todo!("split up the pattern into various Assign statements.");
// }
// Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(),
_ => todo!(), _ => todo!(),
// Expr::List { // Expr::List {
// elem_var, // elem_var,
// loc_elems, // loc_elems,
// } => todo!(), // } => todo!(),
// Expr::IngestedFile(path_buf, arc, variable) => todo!(), // Expr::IngestedFile(path_buf, arc, variable) => todo!(),
// Expr::Var(symbol, variable) => todo!(),
// Expr::ParamsVar { // Expr::ParamsVar {
// symbol, // symbol,
// var, // var,
@ -226,8 +314,6 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
// branches, // branches,
// final_else, // final_else,
// } => todo!(), // } => todo!(),
// Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(),
// Expr::LetNonRec(def, loc) => todo!(),
// Expr::Call(_, vec, called_via) => todo!(), // Expr::Call(_, vec, called_via) => todo!(),
// Expr::RunLowLevel { op, args, ret_var } => todo!(), // Expr::RunLowLevel { op, args, ret_var } => todo!(),
// Expr::ForeignCall { // Expr::ForeignCall {

View file

@ -117,8 +117,8 @@ impl MonoExprs {
} }
} }
pub fn iter_slice(&self, expr_ids: Slice<MonoExprId>) -> impl Iterator<Item = &MonoExpr> { pub fn iter_slice(&self, exprs: Slice<MonoExpr>) -> impl Iterator<Item = &MonoExpr> {
expr_ids.indices().into_iter().map(|index| { exprs.indices().into_iter().map(|index| {
debug_assert!( debug_assert!(
self.exprs.get(index).is_some(), self.exprs.get(index).is_some(),
"A Slice index was not found in MonoExprs. This should never happen!" "A Slice index was not found in MonoExprs. This should never happen!"
@ -128,6 +128,20 @@ impl MonoExprs {
unsafe { self.exprs.get_unchecked(index) } unsafe { self.exprs.get_unchecked(index) }
}) })
} }
pub fn extend(
&mut self,
exprs: impl Iterator<Item = (MonoExpr, Region)> + Clone,
) -> Slice<MonoExpr> {
let start = self.exprs.len();
self.exprs.extend(exprs.clone().map(|(expr, _region)| expr));
self.regions.extend(exprs.map(|(_expr, region)| region));
let len = self.exprs.len() - start;
Slice::new(start as u32, len as u16)
}
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -141,6 +155,82 @@ impl MonoExprId {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MonoStmtId {
inner: Id<MonoStmt>,
}
impl MonoStmtId {
pub(crate) unsafe fn new_unchecked(inner: Id<MonoStmt>) -> Self {
Self { inner }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MonoStmt {
/// Assign to a variable.
Assign(IdentId, MonoExprId),
AssignRec(IdentId, MonoExprId),
/// Introduce a variable, e.g. `var foo_` (we'll MonoStmt::Assign to it later.)
Declare(IdentId),
/// The `return` statement
Return(MonoExprId),
/// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this"
Crash {
msg: MonoExprId,
/// The type of the `crash` expression (which will have unified to whatever's around it)
expr_type: MonoTypeId,
},
Expect {
condition: MonoExprId,
/// If the expectation fails, we print the values of all the named variables
/// in the final expr. These are those values.
lookups_in_cond: Slice2<MonoTypeId, IdentId>,
},
Dbg {
source_location: InternedStrId,
source: InternedStrId,
expr: MonoExprId,
expr_type: MonoTypeId,
name: IdentId,
},
// Call a function that has no return value (or which we are discarding due to an underscore pattern).
CallVoid {
fn_type: MonoTypeId,
fn_expr: MonoExprId,
args: Slice2<MonoTypeId, MonoExprId>,
/// This is the type of the closure based only on canonical IR info,
/// not considering what other closures might later influence it.
/// Lambda set specialization may change this type later!
capture_type: MonoTypeId,
},
// Branching
When {
/// The actual condition of the when expression.
cond: MonoExprId,
cond_type: MonoTypeId,
/// Type of each branch (and therefore the type of the entire `when` expression)
branch_type: MonoTypeId,
/// Note: if the branches weren't exhaustive, we will have already generated a default
/// branch which crashes if it's reached. (The compiler will have reported an error already;
/// this is for if you want to run anyway.)
branches: NonEmptySlice<WhenBranch>,
},
If {
/// Type of each branch (and therefore the type of the entire `if` expression)
branch_type: MonoTypeId,
branches: Slice<(MonoStmtId, MonoStmtId)>,
final_else: Option<MonoTypeId>,
},
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum MonoExpr { pub enum MonoExpr {
Str(InternedStrId), Str(InternedStrId),
@ -159,35 +249,6 @@ pub enum MonoExpr {
params_type: MonoTypeId, params_type: MonoTypeId,
}, },
// Branching
When {
/// The actual condition of the when expression.
cond: MonoExprId,
cond_type: MonoTypeId,
/// Type of each branch (and therefore the type of the entire `when` expression)
branch_type: MonoTypeId,
/// Note: if the branches weren't exhaustive, we will have already generated a default
/// branch which crashes if it's reached. (The compiler will have reported an error already;
/// this is for if you want to run anyway.)
branches: NonEmptySlice<WhenBranch>,
},
If {
/// Type of each branch (and therefore the type of the entire `if` expression)
branch_type: MonoTypeId,
branches: Slice<(MonoExprId, MonoExprId)>,
final_else: Option<MonoTypeId>,
},
// Let
LetRec {
defs: Slice<Def>,
ending_expr: MonoExprId,
},
LetNonRec {
def: Def,
ending_expr: MonoExprId,
},
/// This is *only* for calling functions, not for tag application. /// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it. /// The Tag variant contains any applied values inside it.
Call { Call {
@ -197,7 +258,7 @@ pub enum MonoExpr {
/// This is the type of the closure based only on canonical IR info, /// This is the type of the closure based only on canonical IR info,
/// not considering what other closures might later influence it. /// not considering what other closures might later influence it.
/// Lambda set specialization may change this type later! /// Lambda set specialization may change this type later!
closure_type: MonoTypeId, capture_type: MonoTypeId,
}, },
RunLowLevel { RunLowLevel {
op: LowLevel, op: LowLevel,
@ -220,14 +281,7 @@ pub enum MonoExpr {
/// A record literal or a tuple literal. /// A record literal or a tuple literal.
/// These have already been sorted alphabetically. /// These have already been sorted alphabetically.
Struct(NonEmptySlice<MonoExprId>), Struct(NonEmptySlice<MonoExpr>),
/// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this"
Crash {
msg: MonoExprId,
/// The type of the `crash` expression (which will have unified to whatever's around it)
expr_type: MonoTypeId,
},
/// Look up exactly one field on a record, tuple, or tag payload. /// Look up exactly one field on a record, tuple, or tag payload.
/// At this point we've already unified those concepts and have /// At this point we've already unified those concepts and have
@ -264,28 +318,18 @@ pub enum MonoExpr {
args: Slice2<MonoTypeId, MonoExprId>, args: Slice2<MonoTypeId, MonoExprId>,
}, },
Expect { Block {
condition: MonoExprId, stmts: Slice<MonoStmtId>,
continuation: MonoExprId, final_expr: MonoExprId,
/// If the expectation fails, we print the values of all the named variables
/// in the final expr. These are those values.
lookups_in_cond: Slice2<MonoTypeId, IdentId>,
},
Dbg {
source_location: InternedStrId,
source: InternedStrId,
msg: MonoExprId,
continuation: MonoExprId,
expr_type: MonoTypeId,
name: IdentId,
}, },
CompilerBug(Problem), CompilerBug(Problem),
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct WhenBranch { pub struct WhenBranch {
pub patterns: Slice<MonoPatternId>, pub patterns: Slice<MonoPatternId>,
pub body: MonoExprId, pub body: Slice<MonoStmtId>,
pub guard: Option<MonoExprId>, pub guard: Option<MonoExprId>,
} }

View file

@ -8,6 +8,7 @@ use crate::{
mono_type::{MonoTypeId, MonoTypes}, mono_type::{MonoTypeId, MonoTypes},
MonoFieldId, MonoType, MonoFieldId, MonoType,
}; };
use bumpalo::{collections::Vec, Bump};
use roc_collections::{Push, VecMap}; use roc_collections::{Push, VecMap};
use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_solve::module::Solved; use roc_solve::module::Solved;
@ -33,6 +34,7 @@ pub enum Problem {
), ),
BadNumTypeParam, BadNumTypeParam,
UninitializedReservedExpr, UninitializedReservedExpr,
FnDidNotHaveFnType,
} }
/// For MonoTypes that are records, store their field indices. /// For MonoTypes that are records, store their field indices.
@ -46,11 +48,11 @@ pub type RecordFieldIds = VecMap<MonoTypeId, VecMap<Lowercase, MonoFieldId>>;
pub type TupleElemIds = VecMap<MonoTypeId, VecMap<u16, MonoFieldId>>; pub type TupleElemIds = VecMap<MonoTypeId, VecMap<u16, MonoFieldId>>;
/// Variables that have already been monomorphized. /// Variables that have already been monomorphized.
pub struct MonoCache { pub struct MonoTypeCache {
inner: VecMap<Variable, MonoTypeId>, inner: VecMap<Variable, MonoTypeId>,
} }
impl MonoCache { impl MonoTypeCache {
pub fn from_solved_subs(subs: &Solved<Subs>) -> Self { pub fn from_solved_subs(subs: &Solved<Subs>) -> Self {
Self { Self {
inner: VecMap::with_capacity(subs.inner().len()), inner: VecMap::with_capacity(subs.inner().len()),
@ -61,6 +63,7 @@ impl MonoCache {
/// (e.g. a zero-sized type like empty record, empty tuple, a record of just those, etc.) /// (e.g. a zero-sized type like empty record, empty tuple, a record of just those, etc.)
pub fn monomorphize_var( pub fn monomorphize_var(
&mut self, &mut self,
arena: &Bump,
subs: &Subs, subs: &Subs,
mono_types: &mut MonoTypes, mono_types: &mut MonoTypes,
field_indices: &mut RecordFieldIds, field_indices: &mut RecordFieldIds,
@ -70,6 +73,7 @@ impl MonoCache {
var: Variable, var: Variable,
) -> Option<MonoTypeId> { ) -> Option<MonoTypeId> {
let mut env = Env { let mut env = Env {
arena,
cache: self, cache: self,
mono_types, mono_types,
field_ids: field_indices, field_ids: field_indices,
@ -78,12 +82,13 @@ impl MonoCache {
debug_info, debug_info,
}; };
lower_var(&mut env, subs, var) env.lower_var(subs, var)
} }
} }
struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> { struct Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
cache: &'c mut MonoCache, arena: &'a Bump,
cache: &'c mut MonoTypeCache,
#[allow(dead_code)] #[allow(dead_code)]
mono_types: &'m mut MonoTypes, mono_types: &'m mut MonoTypes,
#[allow(dead_code)] #[allow(dead_code)]
@ -95,187 +100,226 @@ struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> {
debug_info: &'d mut Option<DebugInfo>, debug_info: &'d mut Option<DebugInfo>,
} }
fn lower_var<P: Push<Problem>>( impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
env: &mut Env<'_, '_, '_, '_, '_, '_, P>, fn lower_builtin(
subs: &Subs, &mut self,
var: Variable, subs: &Subs,
) -> Option<MonoTypeId> { symbol: Symbol,
let root_var = subs.get_root_key_without_compacting(var); args: SubsSlice<Variable>,
) -> MonoTypeId {
if symbol == Symbol::NUM_NUM {
number_args_to_mono_id(args, subs, self.problems)
} else if symbol == Symbol::NUM_FLOATINGPOINT {
num_num_args_to_mono_id(symbol, args, subs, self.problems)
} else if symbol == Symbol::LIST_LIST {
todo!();
// let mut new_args = args
// .into_iter()
// .flat_map(|var_index| self.lower_var( subs, subs[var_index]));
// TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId) // let arg = new_args.next();
// and then overwrite it rather than having a separate cache. That memory is already in cache } else {
// for sure, and the lookups should be faster because they're O(1) but don't require hashing. todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this.");
// Kinda creates a cyclic dep though. }
if let Some(mono_id) = env.cache.inner.get(&root_var) {
return Some(*mono_id);
} }
// Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. /// Exposed separately because sometimes we already looked up the Content and know it's a function,
let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) { /// and want to continue from there without redoing the lookup.
Content::Structure(flat_type) => match flat_type { pub fn monomorphize_fn(
FlatType::Apply(symbol, args) => { &mut self,
if symbol.is_builtin() { subs: &Subs,
lower_builtin(env, subs, symbol, args) arg_vars: SubsSlice<Variable>,
} else { ret_var: Variable,
todo!("handle non-builtin Apply"); ) -> MonoTypeId {
let func = self.lower_var(subs, ret_var);
let mut mono_args = Vec::with_capacity_in(arg_vars.len(), self.arena);
mono_args.extend(
arg_vars
.into_iter()
.flat_map(|var_index| self.lower_var(subs, subs[var_index])),
);
let todo = (); // TODO populate debuginfo as appropriate
self.mono_types.add_function(func, mono_args)
}
fn lower_var(&mut self, subs: &Subs, var: Variable) -> Option<MonoTypeId> {
let root_var = subs.get_root_key_without_compacting(var);
// TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId)
// and then overwrite it rather than having a separate cache. That memory is already in cache
// for sure, and the lookups should be faster because they're O(1) but don't require hashing.
// Kinda creates a cyclic dep though.
if let Some(mono_id) = self.cache.inner.get(&root_var) {
return Some(*mono_id);
}
// Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations.
let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) {
Content::Structure(flat_type) => match flat_type {
FlatType::Apply(symbol, args) => {
if symbol.is_builtin() {
self.lower_builtin(subs, symbol, args)
} else {
todo!("handle non-builtin Apply");
}
} }
} FlatType::Func(args, _capture, ret) => self.monomorphize_fn(subs, args, ret),
// FlatType::Func(args, _capture, ret) => { _ => {
// let mono_args = args todo!();
// .into_iter() } /*
// .flat_map(|var_index| lower_var(env, subs[var_index])); FlatType::Record(fields, ext) => {
let mut labeled_mono_ids = lower_record(env, fields, ext);
// let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) // Handle the special cases of 0 fields and 1 field.
// let func = lower_var(env, ret); match labeled_mono_ids.first() {
// Some(env.mono_types.add_function(func, mono_args)) Some((label, first_field_id)) => {
// } if labeled_mono_ids.len() == 1 {
_ => { // If we ended up with a single field, return it unwrapped.
todo!(); let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it)
} /* let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one.
FlatType::Record(fields, ext) => { return Some(*first_field_id);
let mut labeled_mono_ids = lower_record(env, fields, ext); }
}
// Handle the special cases of 0 fields and 1 field. None => {
match labeled_mono_ids.first() { // If we ended up with an empty record,
Some((label, first_field_id)) => { // after removing other empty things, return None.
if labeled_mono_ids.len() == 1 { return None;
// If we ended up with a single field, return it unwrapped.
let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it)
let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one.
return Some(*first_field_id);
} }
} }
None => {
// If we ended up with an empty record, // Now we know we have at least 2 fields, so sort them by field name.
// after removing other empty things, return None. // This can be unstable sort because all field names are known to be unique,
return None; // so sorting unstable won't be observable (and is faster than stable).
labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
// Safety: we already verified that this has at least 2 elements, and
// we would have early returned before this point if we had fewer than 2.
let mono_id = unsafe {
mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id))
};
let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16))));
self.field_ids.insert(mono_id, labeled_indices);
Some(mono_id)
}
FlatType::Tuple(elems, ext) => {
let indexed_mono_ids = lower_tuple(env, elems, ext);
// This can be unstable sort because all indices are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id))
}
FlatType::TagUnion(tags, ext) => {
let tagged_payload_ids = lower_tag_union(env, tags, ext);
// This can be unstable sort because all tag names are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id))
}
FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => {
// If this is still a FunctionOrTagUnion, turn it into a TagUnion.
// First, resolve the ext var.
let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext);
// Now lower all the tags we gathered from the ext var.
// (Do this in a separate pass to avoid borrow errors on Subs.)
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
// Then, add the tag names with no payloads. (There are no variables to lower here.)
for index in tag_names.into_iter() {
tags.push(((subs[index]).clone(), Vec::new()));
} }
Content::Structure(FlatType::TagUnion(
UnionTags::insert_into_subs(subs, tags),
TagExt::Any(Variable::EMPTY_TAG_UNION),
))
} }
FlatType::RecursiveTagUnion(rec, tags, ext) => {
let mut tags = resolve_tag_ext(subs, problems, *tags, *ext);
// Now we know we have at least 2 fields, so sort them by field name. // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs.
// This can be unstable sort because all field names are known to be unique, lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
// so sorting unstable won't be observable (and is faster than stable).
labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) Content::Structure(FlatType::RecursiveTagUnion(
lower_var(cache, subs, problems, *rec),
// Safety: we already verified that this has at least 2 elements, and UnionTags::insert_into_subs(subs, tags),
// we would have early returned before this point if we had fewer than 2. TagExt::Any(Variable::EMPTY_TAG_UNION),
let mono_id = unsafe { ))
mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id))
};
let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16))));
env.field_ids.insert(mono_id, labeled_indices);
Some(mono_id)
}
FlatType::Tuple(elems, ext) => {
let indexed_mono_ids = lower_tuple(env, elems, ext);
// This can be unstable sort because all indices are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id))
}
FlatType::TagUnion(tags, ext) => {
let tagged_payload_ids = lower_tag_union(env, tags, ext);
// This can be unstable sort because all tag names are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id))
}
FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => {
// If this is still a FunctionOrTagUnion, turn it into a TagUnion.
// First, resolve the ext var.
let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext);
// Now lower all the tags we gathered from the ext var.
// (Do this in a separate pass to avoid borrow errors on Subs.)
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
// Then, add the tag names with no payloads. (There are no variables to lower here.)
for index in tag_names.into_iter() {
tags.push(((subs[index]).clone(), Vec::new()));
} }
FlatType::EmptyRecord|
FlatType::EmptyTuple |
FlatType::EmptyTagUnion => None,
},
Content::Error => Content::Error,
*/
},
Content::RangedNumber(range) => {
use roc_types::num::NumericRange::*;
Content::Structure(FlatType::TagUnion( match range {
UnionTags::insert_into_subs(subs, tags), IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
TagExt::Any(Variable::EMPTY_TAG_UNION), IntAtLeastEitherSign(int_lit_width) => {
)) int_lit_width_to_mono_type_id(int_lit_width)
} }
FlatType::RecursiveTagUnion(rec, tags, ext) => { NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); NumAtLeastEitherSign(int_lit_width) => {
int_lit_width_to_mono_type_id(int_lit_width)
// Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. }
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
Content::Structure(FlatType::RecursiveTagUnion(
lower_var(cache, subs, problems, *rec),
UnionTags::insert_into_subs(subs, tags),
TagExt::Any(Variable::EMPTY_TAG_UNION),
))
}
FlatType::EmptyRecord|
FlatType::EmptyTuple |
FlatType::EmptyTagUnion => None,
},
Content::Error => Content::Error,
*/
},
Content::RangedNumber(range) => {
use roc_types::num::NumericRange::*;
match range {
IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
IntAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
NumAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
}
}
Content::Alias(symbol, args, real, kind) => {
match kind {
AliasKind::Opaque if symbol.is_builtin() => {
let args_slice = SubsSlice::new(args.variables_start, args.type_variables_len);
lower_builtin(env, subs, symbol, args_slice)
}
_ => {
let mono_id = lower_var(env, subs, real)?;
let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering.
mono_id
} }
} }
} Content::Alias(symbol, args, real, kind) => {
Content::FlexVar(_) match kind {
| Content::RigidVar(_) AliasKind::Opaque if symbol.is_builtin() => {
| Content::FlexAbleVar(_, _) let args_slice =
| Content::RigidAbleVar(_, _) => { SubsSlice::new(args.variables_start, args.type_variables_len);
// The only way we should reach this branch is in something like a `crash`. self.lower_builtin(subs, symbol, args_slice)
MonoTypeId::CRASH }
} _ => {
Content::ErasedLambda | Content::LambdaSet(_) => { let mono_id = self.lower_var(subs, real)?;
unreachable!( let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering.
mono_id
}
}
}
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => {
// The only way we should reach this branch is in something like a `crash`.
MonoTypeId::CRASH
}
Content::ErasedLambda | Content::LambdaSet(_) => {
unreachable!(
"This new monomorphization implementation must not do anything with lambda sets, because they'll be handled later!" "This new monomorphization implementation must not do anything with lambda sets, because they'll be handled later!"
); );
} }
content => { content => {
todo!("specialize this Content: {content:?}"); todo!("specialize this Content: {content:?}");
} }
}; };
// This var is now known to be monomorphic, so we don't repeat this work again later. // This var is now known to be monomorphic, so we don't repeat this work again later.
// (We don't insert entries for Unit values.) // (We don't insert entries for Unit values.)
env.cache.inner.insert(root_var, mono_id); self.cache.inner.insert(root_var, mono_id);
Some(mono_id) Some(mono_id)
}
} }
fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> MonoTypeId { fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> MonoTypeId {
@ -521,7 +565,7 @@ fn number_args_to_mono_id(
// labeled_mono_ids.extend( // labeled_mono_ids.extend(
// fields // fields
// .sorted_iterator(subs, ext) // .sorted_iterator(subs, ext)
// .map(|(label, field)| (label, lower_var(env, subs, *field.as_inner()))), // .map(|(label, field)| (label, self.lower_var( subs, *field.as_inner()))),
// ); // );
// // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back. // // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back.
@ -602,29 +646,7 @@ fn number_args_to_mono_id(
// problems: &mut impl Push<Problem>, // problems: &mut impl Push<Problem>,
// ) { // ) {
// for var in vars { // for var in vars {
// if let Some(var) = lower_var(env, *var) // hmm not sure if this is still a good idea as a helper function // if let Some(var) = self.lower_var( *var) // hmm not sure if this is still a good idea as a helper function
// *var = ; // *var = ;
// } // }
// } // }
fn lower_builtin<P: Push<Problem>>(
env: &mut Env<'_, '_, '_, '_, '_, '_, P>,
subs: &Subs,
symbol: Symbol,
args: SubsSlice<Variable>,
) -> MonoTypeId {
if symbol == Symbol::NUM_NUM {
number_args_to_mono_id(args, subs, env.problems)
} else if symbol == Symbol::NUM_FLOATINGPOINT {
num_num_args_to_mono_id(symbol, args, subs, env.problems)
} else if symbol == Symbol::LIST_LIST {
todo!();
// let mut new_args = args
// .into_iter()
// .flat_map(|var_index| lower_var(env, subs, subs[var_index]));
// let arg = new_args.next();
} else {
todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this.");
}
}

View file

@ -4,7 +4,7 @@ use roc_load::LoadedModule;
use roc_region::all::Region; use roc_region::all::Region;
use roc_solve::FunctionKind; use roc_solve::FunctionKind;
use roc_specialize_types::{ use roc_specialize_types::{
DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, RecordFieldIds, DebugInfo, Env, Interns, MonoExpr, MonoExprs, MonoTypeCache, MonoTypes, RecordFieldIds,
TupleElemIds, TupleElemIds,
}; };
use test_compile::{trim_and_deindent, SpecializedExprOut}; use test_compile::{trim_and_deindent, SpecializedExprOut};
@ -59,7 +59,7 @@ fn specialize_expr<'a>(
let mut problems = Vec::new(); let mut problems = Vec::new();
let mut debug_info: Option<DebugInfo> = None; let mut debug_info: Option<DebugInfo> = None;
let mut types_cache = MonoCache::from_solved_subs(&solved); let mut types_cache = MonoTypeCache::from_solved_subs(&solved);
let mut mono_types = MonoTypes::new(); let mut mono_types = MonoTypes::new();
let mut mono_exprs = MonoExprs::new(); let mut mono_exprs = MonoExprs::new();
@ -152,10 +152,10 @@ fn dbg_mono_expr_help<'a>(
MonoExpr::Number(number) => { MonoExpr::Number(number) => {
write!(buf, "Number({:?})", number).unwrap(); write!(buf, "Number({:?})", number).unwrap();
} }
MonoExpr::Struct(expr_ids) => { MonoExpr::Struct(field_exprs) => {
write!(buf, "Struct([").unwrap(); write!(buf, "Struct([").unwrap();
for (index, expr) in mono_exprs.iter_slice(expr_ids.as_slice()).enumerate() { for (index, expr) in mono_exprs.iter_slice(field_exprs.as_slice()).enumerate() {
if index > 0 { if index > 0 {
write!(buf, ", ").unwrap(); write!(buf, ", ").unwrap();
} }

View file

@ -2,8 +2,8 @@ use crate::SolvedExpr;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Region; use roc_region::all::Region;
use roc_specialize_types::{ use roc_specialize_types::{
DebugInfo, Env, Interns, MonoCache, MonoExprId, MonoExprs, MonoTypes, Problem, RecordFieldIds, DebugInfo, Env, Interns, MonoExprId, MonoExprs, MonoTypeCache, MonoTypes, Problem,
TupleElemIds, RecordFieldIds, TupleElemIds,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -29,7 +29,7 @@ impl SpecializedExpr {
let mut solved_out = self.solved_expr.solve_expr(input); let mut solved_out = self.solved_expr.solve_expr(input);
let mut problems = Vec::new(); let mut problems = Vec::new();
let mut debug_info: Option<DebugInfo> = None; let mut debug_info: Option<DebugInfo> = None;
let mut types_cache = MonoCache::from_solved_subs(&solved_out.subs); let mut types_cache = MonoTypeCache::from_solved_subs(&solved_out.subs);
let mut mono_types = MonoTypes::new(); let mut mono_types = MonoTypes::new();
let mut mono_exprs = MonoExprs::new(); let mut mono_exprs = MonoExprs::new();