Merge pull request #5010 from roc-lang/tuple-record-unify

Unify IR generation for tuples and records
This commit is contained in:
Ayaz 2023-02-20 18:40:52 -06:00 committed by GitHub
commit 2f251310c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 199 additions and 257 deletions

View file

@ -4309,93 +4309,20 @@ pub fn with_hole<'a>(
Err(_) => return runtime_error(env, "Can't create tuple with improper layout"),
};
let mut elem_symbols = Vec::with_capacity_in(elems.len(), env.arena);
let mut can_elems = Vec::with_capacity_in(elems.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(Variable, Loc<roc_can::expr::Expr>),
}
// Hacky way to let us remove the owned elements from the vector, possibly out-of-order.
let mut elems = Vec::from_iter_in(elems.into_iter().map(Some), env.arena);
let take_elem_expr = move |index: usize| elems[index].take();
for (index, variable, _) in sorted_elems.into_iter() {
// TODO how should function pointers be handled here?
use ReuseSymbol::*;
let (var, loc_expr) = elems[index].take().unwrap();
match can_reuse_symbol(env, procs, &loc_expr.value, var) {
Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => {
elem_symbols.push(symbol);
can_elems.push(Field::FunctionOrUnspecialized(symbol, variable));
}
Value(symbol) => {
let reusable = procs.get_or_insert_symbol_specialization(
env,
layout_cache,
symbol,
var,
);
elem_symbols.push(reusable);
can_elems.push(Field::ValueSymbol);
}
NotASymbol => {
elem_symbols.push(env.unique_symbol());
can_elems.push(Field::Field(var, *loc_expr));
}
}
}
// 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, tuple_var, env.subs) {
Ok(layout) => layout,
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let elem_symbols = elem_symbols.into_bump_slice();
let mut stmt = if let [only_field] = elem_symbols {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
hole
} else {
Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole)
};
for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_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,
env.arena.alloc(stmt),
symbol,
);
}
Field::Field(var, loc_expr) => {
stmt = with_hole(
env,
loc_expr.value,
var,
procs,
layout_cache,
*symbol,
env.arena.alloc(stmt),
);
}
}
}
stmt
compile_struct_like(
env,
procs,
layout_cache,
sorted_elems,
take_elem_expr,
tuple_var,
hole,
assigned,
)
}
Record {
@ -4417,100 +4344,19 @@ pub fn with_hole<'a>(
Err(_) => return runtime_error(env, "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);
let take_field_expr =
move |field: Lowercase| fields.remove(&field).map(|f| (f.var, f.loc_expr));
#[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.get_or_insert_symbol_specialization(
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 runtime_error(env, "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,
env.arena.alloc(stmt),
symbol,
);
}
Field::Field(field) => {
stmt = with_hole(
env,
field.loc_expr.value,
field.var,
procs,
layout_cache,
*symbol,
env.arena.alloc(stmt),
);
}
}
}
stmt
compile_struct_like(
env,
procs,
layout_cache,
sorted_fields,
take_field_expr,
record_var,
hole,
assigned,
)
}
EmptyRecord => let_empty_struct(assigned, hole),
@ -4830,49 +4676,18 @@ pub fn with_hole<'a>(
}
}
let record_symbol = possible_reuse_symbol_or_specialize(
compile_struct_like_access(
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,
field_layouts,
index.expect("field not in its own type") as _,
*loc_expr,
record_symbol,
stmt,
);
stmt
record_var,
hole,
assigned,
field_var,
)
}
RecordAccessor(accessor_data) => {
@ -4950,61 +4765,30 @@ pub fn with_hole<'a>(
Ok(fields) => fields,
Err(_) => return runtime_error(env, "Can't access tuple with improper layout"),
};
let mut field_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena);
let mut final_index = None;
let mut elem_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena);
for (current, (index, _, elem_layout)) in sorted_elems.into_iter().enumerate() {
elem_layouts.push(elem_layout);
field_layouts.push(elem_layout);
if index == accessed_index {
final_index = Some(current);
}
}
let tuple_symbol = possible_reuse_symbol_or_specialize(
compile_struct_like_access(
env,
procs,
layout_cache,
&loc_expr.value,
tuple_var,
);
let mut stmt = match elem_layouts.as_slice() {
[_] => {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, tuple_symbol);
hole
}
_ => {
let expr = Expr::StructAtIndex {
index: final_index.expect("field not in its own type") as u64,
field_layouts: elem_layouts.into_bump_slice(),
structure: tuple_symbol,
};
let layout = layout_cache
.from_var(env.arena, elem_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,
tuple_var,
field_layouts,
final_index.expect("elem not in its own type") as u64,
*loc_expr,
tuple_symbol,
stmt,
);
stmt
tuple_var,
hole,
assigned,
elem_var,
)
}
OpaqueWrapFunction(wrap_fn_data) => {
@ -5762,6 +5546,162 @@ pub fn with_hole<'a>(
}
}
/// Compiles an access into a tuple or record.
fn compile_struct_like_access<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
field_layouts: Vec<'a, InLayout<'a>>,
index: u64,
loc_expr: Loc<roc_can::expr::Expr>,
struct_like_var: Variable,
hole: &'a Stmt<'a>,
assigned: Symbol,
elem_var: Variable,
) -> Stmt<'a> {
let struct_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&loc_expr.value,
struct_like_var,
);
let mut stmt = match field_layouts.as_slice() {
[_] => {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, struct_symbol);
hole
}
_ => {
let expr = Expr::StructAtIndex {
index,
field_layouts: field_layouts.into_bump_slice(),
structure: struct_symbol,
};
let layout = layout_cache
.from_var(env.arena, elem_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,
struct_like_var,
loc_expr,
struct_symbol,
stmt,
);
stmt
}
/// Compiles a record or a tuple.
// TODO: UnusedLayout is because `sort_record_fields` currently returns a three-tuple, but is, in
// fact, unneeded for the compilation.
fn compile_struct_like<'a, L, UnusedLayout>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
sorted_elems: Vec<(L, Variable, UnusedLayout)>,
mut take_elem_expr: impl FnMut(L) -> Option<(Variable, Box<Loc<roc_can::expr::Expr>>)>,
struct_like_var: Variable,
hole: &'a Stmt<'a>,
assigned: Symbol,
) -> Stmt<'a> {
let mut elem_symbols = Vec::with_capacity_in(sorted_elems.len(), env.arena);
let mut can_elems = Vec::with_capacity_in(sorted_elems.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(Variable, Loc<roc_can::expr::Expr>),
}
for (index, variable, _) in sorted_elems.into_iter() {
// TODO how should function pointers be handled here?
use ReuseSymbol::*;
match take_elem_expr(index) {
Some((var, loc_expr)) => match can_reuse_symbol(env, procs, &loc_expr.value, var) {
Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => {
elem_symbols.push(symbol);
can_elems.push(Field::FunctionOrUnspecialized(symbol, variable));
}
Value(symbol) => {
let reusable =
procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var);
elem_symbols.push(reusable);
can_elems.push(Field::ValueSymbol);
}
NotASymbol => {
elem_symbols.push(env.unique_symbol());
can_elems.push(Field::Field(var, *loc_expr));
}
},
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, struct_like_var, env.subs) {
Ok(layout) => layout,
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let elem_symbols = elem_symbols.into_bump_slice();
let mut stmt = if let [only_field] = elem_symbols {
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
hole
} else {
Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole)
};
for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_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,
env.arena.alloc(stmt),
symbol,
);
}
Field::Field(var, loc_expr) => {
stmt = with_hole(
env,
loc_expr.value,
var,
procs,
layout_cache,
*symbol,
env.arena.alloc(stmt),
);
}
}
}
stmt
}
#[inline(always)]
fn late_resolve_ability_specialization<'a>(
env: &mut Env<'a, '_>,

View file

@ -6,6 +6,8 @@
#![warn(clippy::dbg_macro)]
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
// Not a useful lint for us
#![allow(clippy::too_many_arguments)]
pub mod borrow;
pub mod code_gen_help;