Make type_to_variable manually tail-recursive

by using a work stack and reserving variables. fun stuff
This commit is contained in:
Folkert 2022-03-06 12:57:01 +01:00
parent 69be422d83
commit 04818292a8
No known key found for this signature in database
GPG Key ID: 1F17F6FFD112B97C

View File

@ -861,14 +861,65 @@ fn type_to_var(
_: &mut MutMap<Symbol, Variable>,
typ: &Type,
) -> Variable {
let mut arena = take_scratchpad();
if let Type::Variable(var) = typ {
*var
} else {
let mut arena = take_scratchpad();
let var = type_to_variable(subs, rank, pools, &arena, typ);
// let var = type_to_variable(subs, rank, pools, &arena, typ);
let var = type_to_variable(subs, rank, pools, &arena, typ);
arena.reset();
put_scratchpad(arena);
arena.reset();
put_scratchpad(arena);
var
var
}
}
enum RegisterVariable {
/// Based on the Type, we already know what variable this will be
Direct(Variable),
/// This Type needs more complicated Content. We reserve a Variable
/// for it, but put a placeholder Content in subs
Deferred,
}
impl RegisterVariable {
fn from_type(
subs: &mut Subs,
rank: Rank,
pools: &mut Pools,
arena: &'_ bumpalo::Bump,
typ: &Type,
) -> Self {
use RegisterVariable::*;
match typ {
Variable(var) => Direct(*var),
EmptyRec => Direct(Variable::EMPTY_RECORD),
EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION),
Type::Alias { symbol, .. } => {
if let Some(reserved) = Variable::get_reserved(*symbol) {
if rank.is_none() {
// reserved variables are stored with rank NONE
return Direct(reserved);
} else {
// for any other rank, we need to copy; it takes care of adjusting the rank
let copied = deep_copy_var_in(subs, rank, pools, reserved, arena);
return Direct(copied);
}
}
Deferred
}
_ => Deferred,
}
}
}
#[derive(Debug)]
enum TypeToVar<'a> {
Defer(&'a Type, Variable),
}
fn type_to_variable<'a>(
@ -880,255 +931,280 @@ fn type_to_variable<'a>(
) -> Variable {
use bumpalo::collections::Vec;
match typ {
Variable(var) => *var,
RangedNumber(typ, vars) => {
let ty_var = type_to_variable(subs, rank, pools, arena, typ);
let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied());
let content = Content::RangedNumber(ty_var, vars);
let mut stack = Vec::with_capacity_in(8, arena);
register(subs, rank, pools, content)
}
Apply(symbol, arguments, _) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
let var = type_to_variable(subs, rank, pools, arena, var_index);
subs.variables[target_index] = var;
}
let flat_type = FlatType::Apply(*symbol, new_arguments);
let content = Content::Structure(flat_type);
register(subs, rank, pools, content)
}
EmptyRec => Variable::EMPTY_RECORD,
EmptyTagUnion => Variable::EMPTY_TAG_UNION,
ClosureTag { name, ext } => {
let tag_name = TagName::Closure(*name);
let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1);
subs.tag_names.push(tag_name);
// the first VariableSubsSlice in the array is a zero-length slice
let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1));
let content = Content::Structure(FlatType::TagUnion(union_tags, *ext));
register(subs, rank, pools, content)
}
// This case is important for the rank of boolean variables
Function(arguments, closure_type, ret_type) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
let var = type_to_variable(subs, rank, pools, arena, var_index);
subs.variables[target_index] = var;
}
let ret_var = type_to_variable(subs, rank, pools, arena, ret_type);
let closure_var = type_to_variable(subs, rank, pools, arena, closure_type);
let content = Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var));
register(subs, rank, pools, content)
}
Record(fields, ext) => {
// An empty fields is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyRecord in canonicalization
debug_assert!(!fields.is_empty() || !ext.is_empty_record());
let mut field_vars = Vec::with_capacity_in(fields.len(), arena);
for (field, field_type) in fields {
let field_var =
field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ));
field_vars.push((field.clone(), field_var));
}
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
.expect("Something ended up weird in this record type");
let it = it
.into_iter()
.map(|(field, field_type)| (field.clone(), field_type));
field_vars.extend(it);
insertion_sort_by(&mut field_vars, RecordFields::compare);
let record_fields = RecordFields::insert_into_subs(subs, field_vars);
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
register(subs, rank, pools, content)
}
TagUnion(tags, ext) => {
// An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization
debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union());
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
register(subs, rank, pools, content)
}
FunctionOrTagUnion(tag_name, symbol, ext) => {
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
subs,
UnionTags::default(),
temp_ext_var,
);
for _ in it {
unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!");
}
let slice = SubsIndex::new(subs.tag_names.len() as u32);
subs.tag_names.push(tag_name.clone());
let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext));
register(subs, rank, pools, content)
}
RecursiveTagUnion(rec_var, tags, ext) => {
// An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization
debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union());
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
let content =
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext));
let tag_union_var = register(subs, rank, pools, content);
register_with_known_var(
subs,
*rec_var,
rank,
pools,
Content::RecursionVar {
opt_name: None,
structure: tag_union_var,
},
);
tag_union_var
}
Type::Alias {
symbol,
type_arguments,
actual,
lambda_set_variables,
kind,
} => {
if let Some(reserved) = Variable::get_reserved(*symbol) {
if rank.is_none() {
// reserved variables are stored with rank NONE
return reserved;
} else {
// for any other rank, we need to copy; it takes care of adjusting the rank
return deep_copy_var_in(subs, rank, pools, reserved, arena);
macro_rules! helper {
($typ:expr) => {{
match RegisterVariable::from_type(subs, rank, pools, arena, $typ) {
RegisterVariable::Direct(var) => var,
RegisterVariable::Deferred => {
let var = subs.fresh_unnamed_flex_var();
stack.push(TypeToVar::Defer($typ, var));
var
}
}
}};
}
let alias_variables = alias_to_var(
subs,
rank,
pools,
arena,
type_arguments,
lambda_set_variables,
);
let result = helper!(typ);
let alias_variable = if let Symbol::RESULT_RESULT = *symbol {
roc_result_to_var(subs, rank, pools, arena, actual)
} else {
type_to_variable(subs, rank, pools, arena, actual)
};
let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind);
while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() {
match typ {
Variable(_) | EmptyRec | EmptyTagUnion => {
unreachable!("This variant should never be deferred!")
}
RangedNumber(typ, vars) => {
let ty_var = helper!(typ);
let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied());
let content = Content::RangedNumber(ty_var, vars);
register(subs, rank, pools, content)
}
HostExposedAlias {
name: symbol,
type_arguments,
actual: alias_type,
actual_var,
lambda_set_variables,
..
} => {
let alias_variables = alias_to_var(
subs,
rank,
pools,
arena,
type_arguments,
lambda_set_variables,
);
register_with_known_var(subs, destination, rank, pools, content)
}
Apply(symbol, arguments, _) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
let var = helper!(var_index);
subs.variables[target_index] = var;
}
let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type);
// TODO(opaques): I think host-exposed aliases should always be structural
// (when does it make sense to give a host an opaque type?)
let content = Content::Alias(
*symbol,
alias_variables,
alias_variable,
AliasKind::Structural,
);
let result = register(subs, rank, pools, content);
let flat_type = FlatType::Apply(*symbol, new_arguments);
let content = Content::Structure(flat_type);
// We only want to unify the actual_var with the alias once
// if it's already redirected (and therefore, redundant)
// don't do it again
if !subs.redundant(*actual_var) {
let descriptor = subs.get(result);
subs.union(result, *actual_var, descriptor);
register_with_known_var(subs, destination, rank, pools, content)
}
result
}
Erroneous(problem) => {
let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone())));
ClosureTag { name, ext } => {
let tag_name = TagName::Closure(*name);
let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1);
register(subs, rank, pools, content)
}
}
}
subs.tag_names.push(tag_name);
#[inline(always)]
fn alias_to_var<'a>(
subs: &mut Subs,
rank: Rank,
pools: &mut Pools,
arena: &'a bumpalo::Bump,
type_arguments: &[(roc_module::ident::Lowercase, Type)],
lambda_set_variables: &[roc_types::types::LambdaSet],
) -> AliasVariables {
let length = type_arguments.len() + lambda_set_variables.len();
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
// the first VariableSubsSlice in the array is a zero-length slice
let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1));
for (target_index, (_, arg_type)) in (new_variables.indices()).zip(type_arguments) {
let copy_var = type_to_variable(subs, rank, pools, arena, arg_type);
subs.variables[target_index] = copy_var;
let content = Content::Structure(FlatType::TagUnion(union_tags, *ext));
register_with_known_var(subs, destination, rank, pools, content)
}
// This case is important for the rank of boolean variables
Function(arguments, closure_type, ret_type) => {
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
let var = helper!(var_index);
subs.variables[target_index] = var;
}
let ret_var = helper!(ret_type);
let closure_var = helper!(closure_type);
let content =
Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var));
register_with_known_var(subs, destination, rank, pools, content)
}
Record(fields, ext) => {
// An empty fields is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyRecord in canonicalization
debug_assert!(!fields.is_empty() || !ext.is_empty_record());
let mut field_vars = Vec::with_capacity_in(fields.len(), arena);
for (field, field_type) in fields {
let field_var = {
use roc_types::types::RecordField::*;
match &field_type {
Optional(t) => Optional(helper!(t)),
Required(t) => Required(helper!(t)),
Demanded(t) => Demanded(helper!(t)),
}
};
field_vars.push((field.clone(), field_var));
}
let temp_ext_var = helper!(ext);
let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
.expect("Something ended up weird in this record type");
let it = it
.into_iter()
.map(|(field, field_type)| (field.clone(), field_type));
field_vars.extend(it);
insertion_sort_by(&mut field_vars, RecordFields::compare);
let record_fields = RecordFields::insert_into_subs(subs, field_vars);
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
register_with_known_var(subs, destination, rank, pools, content)
}
TagUnion(tags, ext) => {
// An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization
debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union());
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
register_with_known_var(subs, destination, rank, pools, content)
}
FunctionOrTagUnion(tag_name, symbol, ext) => {
let temp_ext_var = helper!(ext);
let (it, ext) = roc_types::types::gather_tags_unsorted_iter(
subs,
UnionTags::default(),
temp_ext_var,
);
for _ in it {
unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!");
}
let slice = SubsIndex::new(subs.tag_names.len() as u32);
subs.tag_names.push(tag_name.clone());
let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext));
register_with_known_var(subs, destination, rank, pools, content)
}
RecursiveTagUnion(rec_var, tags, ext) => {
// An empty tags is inefficient (but would be correct)
// If hit, try to turn the value into an EmptyTagUnion in canonicalization
debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union());
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
let content =
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext));
let tag_union_var = destination;
register_with_known_var(subs, tag_union_var, rank, pools, content);
register_with_known_var(
subs,
*rec_var,
rank,
pools,
Content::RecursionVar {
opt_name: None,
structure: tag_union_var,
},
);
tag_union_var
}
Type::Alias {
symbol,
type_arguments,
actual,
lambda_set_variables,
kind,
} => {
debug_assert!(Variable::get_reserved(*symbol).is_none());
let alias_variables = {
let length = type_arguments.len() + lambda_set_variables.len();
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
for (target_index, (_, arg_type)) in
(new_variables.indices()).zip(type_arguments)
{
let copy_var = helper!(arg_type);
subs.variables[target_index] = copy_var;
}
let it = (new_variables.indices().skip(type_arguments.len()))
.zip(lambda_set_variables);
for (target_index, ls) in it {
let copy_var = helper!(&ls.0);
subs.variables[target_index] = copy_var;
}
AliasVariables {
variables_start: new_variables.start,
type_variables_len: type_arguments.len() as _,
all_variables_len: length as _,
}
};
let alias_variable = if let Symbol::RESULT_RESULT = *symbol {
roc_result_to_var(subs, rank, pools, arena, actual)
} else {
helper!(actual)
};
let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind);
register_with_known_var(subs, destination, rank, pools, content)
}
HostExposedAlias {
name: symbol,
type_arguments,
actual: alias_type,
actual_var,
lambda_set_variables,
..
} => {
let alias_variables = {
let length = type_arguments.len() + lambda_set_variables.len();
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
for (target_index, (_, arg_type)) in
(new_variables.indices()).zip(type_arguments)
{
let copy_var = helper!(arg_type);
subs.variables[target_index] = copy_var;
}
let it = (new_variables.indices().skip(type_arguments.len()))
.zip(lambda_set_variables);
for (target_index, ls) in it {
let copy_var = helper!(&ls.0);
subs.variables[target_index] = copy_var;
}
AliasVariables {
variables_start: new_variables.start,
type_variables_len: type_arguments.len() as _,
all_variables_len: length as _,
}
};
// cannot use helper! here because this variable may be involved in unification below
let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type);
// TODO(opaques): I think host-exposed aliases should always be structural
// (when does it make sense to give a host an opaque type?)
let content = Content::Alias(
*symbol,
alias_variables,
alias_variable,
AliasKind::Structural,
);
// let result = register(subs, rank, pools, content);
let result = register_with_known_var(subs, destination, rank, pools, content);
// We only want to unify the actual_var with the alias once
// if it's already redirected (and therefore, redundant)
// don't do it again
if !subs.redundant(*actual_var) {
let descriptor = subs.get(result);
subs.union(result, *actual_var, descriptor);
}
result
}
Erroneous(problem) => {
let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone())));
register_with_known_var(subs, destination, rank, pools, content)
}
};
}
let it = (new_variables.indices().skip(type_arguments.len())).zip(lambda_set_variables);
for (target_index, ls) in it {
let copy_var = type_to_variable(subs, rank, pools, arena, &ls.0);
subs.variables[target_index] = copy_var;
}
AliasVariables {
variables_start: new_variables.start,
type_variables_len: type_arguments.len() as _,
all_variables_len: length as _,
}
result
}
#[inline(always)]
@ -2174,7 +2250,7 @@ fn register_with_known_var(
rank: Rank,
pools: &mut Pools,
content: Content,
) {
) -> Variable {
let descriptor = Descriptor {
content,
rank,
@ -2185,4 +2261,6 @@ fn register_with_known_var(
subs.set(var, descriptor);
pools.get_mut(rank).push(var);
var
}