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_struct::MonoFieldId;
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::{
mono_ir::{MonoExpr, MonoExprId, MonoExprs},
mono_ir::{MonoExpr, MonoExprId, MonoExprs, MonoStmt, MonoStmtId},
mono_module::Interns,
mono_num::Number,
mono_type::{MonoType, MonoTypes, Primitive},
specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds},
DebugInfo,
specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds},
DebugInfo, MonoTypeId,
};
use bumpalo::{collections::Vec, Bump};
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_types::subs::{Content, Subs};
use soa::{Index, NonEmptySlice, Slice};
use roc_types::subs::{Content, FlatType, Subs, Variable};
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> {
arena: &'a Bump,
subs: &'s mut Subs,
types_cache: &'c mut MonoCache,
types_cache: &'c mut MonoTypeCache,
mono_types: &'t mut MonoTypes,
mono_exprs: &'t mut MonoExprs,
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(
arena: &'a Bump,
subs: &'s mut Solved<Subs>,
types_cache: &'c mut MonoCache,
types_cache: &'c mut MonoTypeCache,
mono_types: &'t mut MonoTypes,
mono_exprs: &'t mut MonoExprs,
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 mut mono_from_var = |var| {
self.types_cache.monomorphize_var(
self.arena,
self.subs,
mono_types,
&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));
// Reserve a slice of IDs up front. This is so that we have a contiguous array
// of field IDs at the end of this, each corresponding to the appropriate record field.
let field_ids: Slice<MonoExprId> = self.mono_exprs.reserve_ids(fields.len() as u16);
let mut next_field_id = field_ids.start();
// We want to end up with a Slice<MonoExpr> of these, so accumulate a buffer of them
// and then add them to MonoExprs all at once, so they're added as one contiguous slice
// regardless of what else got added to MonoExprs as we were monomorphizing them.
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
// that Slice being populated with the exprs in the fields, with the correct ordering.
fields.retain(|(_name, field)| {
match self.to_mono_expr(&field.loc_expr.value) {
Some(mono_expr) => {
// 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)) };
buf.extend(
// flat_map these so we discard all the fields that monomorphized to None
fields.into_iter().flat_map(|(_name, field)| {
self.to_mono_expr(&field.loc_expr.value)
.map(|mono_expr| (mono_expr, field.loc_expr.region))
}),
);
next_field_id += 1;
self.mono_exprs
.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))
// If we ended up with exactly 1 field, return it unwrapped.
if buf.len() == 1 {
return buf.pop().map(|(expr, _region)| expr);
}
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!(),
// Expr::List {
// elem_var,
// loc_elems,
// } => todo!(),
// Expr::IngestedFile(path_buf, arc, variable) => todo!(),
// Expr::Var(symbol, variable) => todo!(),
// Expr::ParamsVar {
// symbol,
// var,
@ -226,8 +314,6 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
// branches,
// final_else,
// } => todo!(),
// Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(),
// Expr::LetNonRec(def, loc) => todo!(),
// Expr::Call(_, vec, called_via) => todo!(),
// Expr::RunLowLevel { op, args, ret_var } => todo!(),
// Expr::ForeignCall {

View file

@ -117,8 +117,8 @@ impl MonoExprs {
}
}
pub fn iter_slice(&self, expr_ids: Slice<MonoExprId>) -> impl Iterator<Item = &MonoExpr> {
expr_ids.indices().into_iter().map(|index| {
pub fn iter_slice(&self, exprs: Slice<MonoExpr>) -> impl Iterator<Item = &MonoExpr> {
exprs.indices().into_iter().map(|index| {
debug_assert!(
self.exprs.get(index).is_some(),
"A Slice index was not found in MonoExprs. This should never happen!"
@ -128,6 +128,20 @@ impl MonoExprs {
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)]
@ -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)]
pub enum MonoExpr {
Str(InternedStrId),
@ -159,35 +249,6 @@ pub enum MonoExpr {
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.
/// The Tag variant contains any applied values inside it.
Call {
@ -197,7 +258,7 @@ pub enum MonoExpr {
/// 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!
closure_type: MonoTypeId,
capture_type: MonoTypeId,
},
RunLowLevel {
op: LowLevel,
@ -220,14 +281,7 @@ pub enum MonoExpr {
/// A record literal or a tuple literal.
/// These have already been sorted alphabetically.
Struct(NonEmptySlice<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,
},
Struct(NonEmptySlice<MonoExpr>),
/// Look up exactly one field on a record, tuple, or tag payload.
/// At this point we've already unified those concepts and have
@ -264,28 +318,18 @@ pub enum MonoExpr {
args: Slice2<MonoTypeId, MonoExprId>,
},
Expect {
condition: MonoExprId,
continuation: 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,
Block {
stmts: Slice<MonoStmtId>,
final_expr: MonoExprId,
},
CompilerBug(Problem),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WhenBranch {
pub patterns: Slice<MonoPatternId>,
pub body: MonoExprId,
pub body: Slice<MonoStmtId>,
pub guard: Option<MonoExprId>,
}

View file

@ -8,6 +8,7 @@ use crate::{
mono_type::{MonoTypeId, MonoTypes},
MonoFieldId, MonoType,
};
use bumpalo::{collections::Vec, Bump};
use roc_collections::{Push, VecMap};
use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_solve::module::Solved;
@ -33,6 +34,7 @@ pub enum Problem {
),
BadNumTypeParam,
UninitializedReservedExpr,
FnDidNotHaveFnType,
}
/// 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>>;
/// Variables that have already been monomorphized.
pub struct MonoCache {
pub struct MonoTypeCache {
inner: VecMap<Variable, MonoTypeId>,
}
impl MonoCache {
impl MonoTypeCache {
pub fn from_solved_subs(subs: &Solved<Subs>) -> Self {
Self {
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.)
pub fn monomorphize_var(
&mut self,
arena: &Bump,
subs: &Subs,
mono_types: &mut MonoTypes,
field_indices: &mut RecordFieldIds,
@ -70,6 +73,7 @@ impl MonoCache {
var: Variable,
) -> Option<MonoTypeId> {
let mut env = Env {
arena,
cache: self,
mono_types,
field_ids: field_indices,
@ -78,12 +82,13 @@ impl MonoCache {
debug_info,
};
lower_var(&mut env, subs, var)
env.lower_var(subs, var)
}
}
struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> {
cache: &'c mut MonoCache,
struct Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
arena: &'a Bump,
cache: &'c mut MonoTypeCache,
#[allow(dead_code)]
mono_types: &'m mut MonoTypes,
#[allow(dead_code)]
@ -95,187 +100,226 @@ struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> {
debug_info: &'d mut Option<DebugInfo>,
}
fn lower_var<P: Push<Problem>>(
env: &mut Env<'_, '_, '_, '_, '_, '_, P>,
subs: &Subs,
var: Variable,
) -> Option<MonoTypeId> {
let root_var = subs.get_root_key_without_compacting(var);
impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
fn lower_builtin(
&mut self,
subs: &Subs,
symbol: Symbol,
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)
// 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) = env.cache.inner.get(&root_var) {
return Some(*mono_id);
// 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.");
}
}
// 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() {
lower_builtin(env, subs, symbol, args)
} else {
todo!("handle non-builtin Apply");
/// Exposed separately because sometimes we already looked up the Content and know it's a function,
/// and want to continue from there without redoing the lookup.
pub fn monomorphize_fn(
&mut self,
subs: &Subs,
arg_vars: SubsSlice<Variable>,
ret_var: Variable,
) -> 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) => {
// let mono_args = args
// .into_iter()
// .flat_map(|var_index| lower_var(env, subs[var_index]));
FlatType::Func(args, _capture, ret) => self.monomorphize_fn(subs, args, ret),
_ => {
todo!();
} /*
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)
// let func = lower_var(env, ret);
// Some(env.mono_types.add_function(func, mono_args))
// }
_ => {
todo!();
} /*
FlatType::Record(fields, ext) => {
let mut labeled_mono_ids = lower_record(env, fields, ext);
// Handle the special cases of 0 fields and 1 field.
match labeled_mono_ids.first() {
Some((label, first_field_id)) => {
if labeled_mono_ids.len() == 1 {
// 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);
// Handle the special cases of 0 fields and 1 field.
match labeled_mono_ids.first() {
Some((label, first_field_id)) => {
if labeled_mono_ids.len() == 1 {
// 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,
// after removing other empty things, return None.
return None;
}
}
None => {
// If we ended up with an empty record,
// after removing other empty things, return None.
return None;
// Now we know we have at least 2 fields, so sort them by field name.
// This can be unstable sort because all field names are known to be unique,
// 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.
// This can be unstable sort because all field names are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2));
// 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);
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))));
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()));
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::*;
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 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
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::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!(
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);
self.lower_builtin(subs, symbol, args_slice)
}
_ => {
let mono_id = self.lower_var(subs, real)?;
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!"
);
}
content => {
todo!("specialize this Content: {content:?}");
}
};
}
content => {
todo!("specialize this Content: {content:?}");
}
};
// 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.)
env.cache.inner.insert(root_var, mono_id);
// 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.)
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 {
@ -521,7 +565,7 @@ fn number_args_to_mono_id(
// labeled_mono_ids.extend(
// fields
// .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.
@ -602,29 +646,7 @@ fn number_args_to_mono_id(
// problems: &mut impl Push<Problem>,
// ) {
// 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 = ;
// }
// }
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_solve::FunctionKind;
use roc_specialize_types::{
DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, RecordFieldIds,
DebugInfo, Env, Interns, MonoExpr, MonoExprs, MonoTypeCache, MonoTypes, RecordFieldIds,
TupleElemIds,
};
use test_compile::{trim_and_deindent, SpecializedExprOut};
@ -59,7 +59,7 @@ fn specialize_expr<'a>(
let mut problems = Vec::new();
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_exprs = MonoExprs::new();
@ -152,10 +152,10 @@ fn dbg_mono_expr_help<'a>(
MonoExpr::Number(number) => {
write!(buf, "Number({:?})", number).unwrap();
}
MonoExpr::Struct(expr_ids) => {
MonoExpr::Struct(field_exprs) => {
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 {
write!(buf, ", ").unwrap();
}