Merge pull request #2375 from rtfeldman/expression-monomorphization-inlined

Monomorphize polymorphic non-function values
This commit is contained in:
Folkert de Vries 2022-01-21 20:10:42 +01:00 committed by GitHub
commit c0ea664141
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 879 additions and 90 deletions

View File

@ -207,6 +207,72 @@ impl<'a> PartialProc<'a> {
}
}
#[derive(Clone, Debug)]
enum PartialExprLink {
Aliases(Symbol),
Expr(roc_can::expr::Expr, Variable),
}
/// A table of symbols to polymorphic expressions. For example, in the program
///
/// n = 1
///
/// asU8 : U8 -> U8
/// asU8 = \_ -> 1
///
/// asU32 : U32 -> U8
/// asU32 = \_ -> 1
///
/// asU8 n + asU32 n
///
/// The expression bound by `n` doesn't have a definite layout until it is used
/// at the call sites `asU8 n`, `asU32 n`.
///
/// Polymorphic *functions* are stored in `PartialProc`s, since functions are
/// non longer first-class once we finish lowering to the IR.
#[derive(Clone, Debug)]
struct PartialExprs(BumpMap<Symbol, PartialExprLink>);
impl PartialExprs {
fn new_in(arena: &Bump) -> Self {
Self(BumpMap::new_in(arena))
}
fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) {
self.0.insert(symbol, PartialExprLink::Expr(expr, expr_var));
}
fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) {
self.0.insert(symbol, PartialExprLink::Aliases(aliases));
}
fn contains(&self, symbol: Symbol) -> bool {
self.0.contains_key(&symbol)
}
fn get(&mut self, mut symbol: Symbol) -> Option<(&roc_can::expr::Expr, Variable)> {
// In practice the alias chain is very short
loop {
match self.0.get(&symbol) {
None => {
return None;
}
Some(&PartialExprLink::Aliases(real_symbol)) => {
symbol = real_symbol;
}
Some(PartialExprLink::Expr(expr, var)) => {
return Some((expr, *var));
}
}
}
}
fn remove(&mut self, symbol: Symbol) {
debug_assert!(self.contains(symbol));
self.0.remove(&symbol);
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CapturedSymbols<'a> {
None,
@ -668,6 +734,7 @@ impl<'a> Specialized<'a> {
#[derive(Clone, Debug)]
pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>,
partial_exprs: PartialExprs,
pub imported_module_thunks: &'a [Symbol],
pub module_thunks: &'a [Symbol],
pending_specializations: PendingSpecializations<'a>,
@ -680,6 +747,7 @@ impl<'a> Procs<'a> {
pub fn new_in(arena: &'a Bump) -> Self {
Self {
partial_procs: PartialProcs::new_in(arena),
partial_exprs: PartialExprs::new_in(arena),
imported_module_thunks: &[],
module_thunks: &[],
pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)),
@ -3107,16 +3175,20 @@ pub fn with_hole<'a>(
_ => {}
}
// continue with the default path
let mut stmt = with_hole(
env,
cont.value,
variable,
procs,
layout_cache,
assigned,
hole,
);
let build_rest =
|env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>| {
with_hole(
env,
cont.value,
variable,
procs,
layout_cache,
assigned,
hole,
)
};
// a variable is aliased
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
@ -3128,18 +3200,17 @@ pub fn with_hole<'a>(
//
// foo = RBTRee.empty
stmt = handle_variable_aliasing(
handle_variable_aliasing(
env,
procs,
layout_cache,
def.expr_var,
symbol,
original,
stmt,
);
stmt
build_rest,
)
} else {
let rest = build_rest(env, procs, layout_cache);
with_hole(
env,
def.loc_expr.value,
@ -3147,7 +3218,7 @@ pub fn with_hole<'a>(
procs,
layout_cache,
symbol,
env.arena.alloc(stmt),
env.arena.alloc(rest),
)
}
} else {
@ -3332,6 +3403,7 @@ pub fn with_hole<'a>(
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
enum Field {
// TODO: rename this since it can handle unspecialized expressions now too
Function(Symbol, Variable),
ValueSymbol,
Field(roc_can::expr::Field),
@ -3342,7 +3414,7 @@ pub fn with_hole<'a>(
use ReuseSymbol::*;
match fields.remove(&label) {
Some(field) => match can_reuse_symbol(env, procs, &field.loc_expr.value) {
Imported(symbol) | LocalFunction(symbol) => {
Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => {
field_symbols.push(symbol);
can_fields.push(Field::Function(symbol, variable));
}
@ -3798,9 +3870,16 @@ pub fn with_hole<'a>(
);
match raw_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
construct_closure_data(env, lambda_set, name, &[], assigned, hole)
}
RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
name,
&[],
assigned,
hole,
),
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(),
}
}
@ -3933,13 +4012,36 @@ pub fn with_hole<'a>(
);
}
CopyExisting(index) => {
let record_needs_specialization =
procs.partial_exprs.contains(structure);
let specialized_structure_sym = if record_needs_specialization {
// We need to specialize the record now; create a new one for it.
// TODO: reuse this symbol for all updates
env.unique_symbol()
} else {
// The record is already good.
structure
};
let access_expr = Expr::StructAtIndex {
structure,
structure: specialized_structure_sym,
index,
field_layouts,
};
stmt =
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
if record_needs_specialization {
stmt = reuse_function_symbol(
env,
procs,
layout_cache,
Some(record_var),
specialized_structure_sym,
stmt,
structure,
);
}
}
}
}
@ -3994,10 +4096,18 @@ pub fn with_hole<'a>(
// define the closure data
let symbols =
Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena)
.into_bump_slice();
Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice();
construct_closure_data(env, lambda_set, name, symbols, assigned, hole)
construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
name,
symbols,
assigned,
hole,
)
}
}
}
@ -4068,6 +4178,9 @@ pub fn with_hole<'a>(
LocalFunction(_) => {
unreachable!("if this was known to be a function, we would not be here")
}
UnspecializedExpr(_) => {
unreachable!("if this was known to be an unspecialized expression, we would not be here")
}
Imported(thunk_name) => {
debug_assert!(procs.is_imported_module_thunk(thunk_name));
@ -4446,17 +4559,20 @@ pub fn with_hole<'a>(
}
}
#[allow(clippy::too_many_arguments)]
fn construct_closure_data<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
lambda_set: LambdaSet<'a>,
name: Symbol,
symbols: &'a [Symbol],
symbols: &'a [&(Symbol, Variable)],
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let lambda_set_layout = Layout::LambdaSet(lambda_set);
match lambda_set.layout_for_member(name) {
let mut result = match lambda_set.layout_for_member(name) {
ClosureRepresentation::Union {
tag_id,
alphabetic_order_fields: field_layouts,
@ -4465,8 +4581,10 @@ fn construct_closure_data<'a>(
} => {
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
let mut combined =
Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena);
let mut combined = Vec::from_iter_in(
symbols.iter().map(|&&(s, _)| s).zip(field_layouts.iter()),
env.arena,
);
let ptr_bytes = env.ptr_bytes;
@ -4478,7 +4596,7 @@ fn construct_closure_data<'a>(
});
let symbols =
Vec::from_iter_in(combined.iter().map(|(a, _)| **a), env.arena).into_bump_slice();
Vec::from_iter_in(combined.iter().map(|(a, _)| *a), env.arena).into_bump_slice();
let expr = Expr::Tag {
tag_id,
@ -4494,8 +4612,10 @@ fn construct_closure_data<'a>(
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
let mut combined =
Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena);
let mut combined = Vec::from_iter_in(
symbols.iter().map(|&(s, _)| s).zip(field_layouts.iter()),
env.arena,
);
let ptr_bytes = env.ptr_bytes;
@ -4539,7 +4659,23 @@ fn construct_closure_data<'a>(
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
_ => unreachable!(),
};
// Some of the captured symbols may be references to polymorphic expressions,
// which have not been specialized yet. We need to perform those
// specializations now so that there are real symbols to capture.
//
// TODO: this is not quite right. What we should actually be doing is removing references to
// polymorphic expressions from the captured symbols, and allowing the specializations of those
// symbols to be inlined when specializing the closure body elsewhere.
for &&(symbol, var) in symbols {
if procs.partial_exprs.contains(symbol) {
result =
reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
}
}
result
}
#[allow(clippy::too_many_arguments)]
@ -4842,9 +4978,16 @@ fn tag_union_to_function<'a>(
);
match raw_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
construct_closure_data(env, lambda_set, proc_symbol, &[], assigned, hole)
}
RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
proc_symbol,
&[],
assigned,
hole,
),
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(),
}
}
@ -5027,6 +5170,31 @@ fn register_capturing_closure<'a>(
}
}
fn is_literal_like(expr: &roc_can::expr::Expr) -> bool {
use roc_can::expr::Expr::*;
matches!(
expr,
Num(..)
| Int(..)
| Float(..)
| List { .. }
| Str(_)
| ZeroArgumentTag { .. }
| Tag { .. }
| Record { .. }
)
}
fn expr_is_polymorphic<'a>(
env: &mut Env<'a, '_>,
expr: &roc_can::expr::Expr,
expr_var: Variable,
) -> bool {
// TODO: I don't think we need the `is_literal_like` check, but taking it slow for now...
let is_flex_or_rigid = |c: &Content| matches!(c, Content::FlexVar(_) | Content::RigidVar(_));
is_literal_like(expr) && env.subs.var_contains_content(expr_var, is_flex_or_rigid)
}
pub fn from_can<'a>(
env: &mut Env<'a, '_>,
variable: Variable,
@ -5181,19 +5349,26 @@ pub fn from_can<'a>(
// or
//
// foo = RBTRee.empty
let mut rest = from_can(env, def.expr_var, cont.value, procs, layout_cache);
rest = handle_variable_aliasing(
// TODO: right now we need help out rustc with the closure types;
// it isn't able to infer the right lifetime bounds. See if we
// can remove the annotations in the future.
let build_rest =
|env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>| {
from_can(env, def.expr_var, cont.value, procs, layout_cache)
};
return handle_variable_aliasing(
env,
procs,
layout_cache,
def.expr_var,
*symbol,
original,
rest,
build_rest,
);
return rest;
}
roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => {
use roc_can::expr::Expr::*;
@ -5277,6 +5452,26 @@ pub fn from_can<'a>(
return from_can(env, variable, new_outer, procs, layout_cache);
}
ref body if expr_is_polymorphic(env, body, def.expr_var) => {
// This is a pattern like
//
// n = 1
// asU8 n
//
// At the definition site `n = 1` we only know `1` to have the type `[Int *]`,
// which won't be refined until the call `asU8 n`. Add it as a partial expression
// that will be specialized at each concrete usage site.
procs
.partial_exprs
.insert(*symbol, def.loc_expr.value, def.expr_var);
let result = from_can(env, variable, cont.value, procs, layout_cache);
// We won't see this symbol again.
procs.partial_exprs.remove(*symbol);
return result;
}
_ => {
let rest = from_can(env, variable, cont.value, procs, layout_cache);
return with_hole(
@ -6294,6 +6489,7 @@ enum ReuseSymbol {
Imported(Symbol),
LocalFunction(Symbol),
Value(Symbol),
UnspecializedExpr(Symbol),
NotASymbol,
}
@ -6311,6 +6507,8 @@ fn can_reuse_symbol<'a>(
Imported(symbol)
} else if procs.partial_procs.contains_key(symbol) {
LocalFunction(symbol)
} else if procs.partial_exprs.contains(symbol) {
UnspecializedExpr(symbol)
} else {
Value(symbol)
}
@ -6330,15 +6528,29 @@ fn possible_reuse_symbol<'a>(
}
}
fn handle_variable_aliasing<'a>(
fn handle_variable_aliasing<'a, BuildRest>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
variable: Variable,
left: Symbol,
right: Symbol,
mut result: Stmt<'a>,
) -> Stmt<'a> {
build_rest: BuildRest,
) -> Stmt<'a>
where
BuildRest: FnOnce(&mut Env<'a, '_>, &mut Procs<'a>, &mut LayoutCache<'a>) -> Stmt<'a>,
{
if procs.partial_exprs.contains(right) {
// If `right` links to a partial expression, make sure we link `left` to it as well, so
// that usages of it will be specialized when building the rest of the program.
procs.partial_exprs.insert_alias(left, right);
return build_rest(env, procs, layout_cache);
}
// Otherwise we're dealing with an alias to something that doesn't need to be specialized, or
// whose usages will already be specialized in the rest of the program. Let's just build the
// rest of the program now to get our hole.
let mut result = build_rest(env, procs, layout_cache);
if procs.is_imported_module_thunk(right) {
// if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer.
@ -6396,6 +6608,7 @@ fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
}
/// If the symbol is a function, make sure it is properly specialized
// TODO: rename this now that we handle polymorphic non-function expressions too
fn reuse_function_symbol<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
@ -6405,6 +6618,35 @@ fn reuse_function_symbol<'a>(
result: Stmt<'a>,
original: Symbol,
) -> Stmt<'a> {
if let Some((expr, expr_var)) = procs.partial_exprs.get(original) {
// Specialize the expression type now, based off the `arg_var` we've been given.
// TODO: cache the specialized result
let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot();
let _unified = roc_unify::unify::unify(
env.subs,
arg_var.unwrap(),
expr_var,
roc_unify::unify::Mode::Eq,
);
let result = with_hole(
env,
expr.clone(),
expr_var,
procs,
layout_cache,
symbol,
env.arena.alloc(result),
);
// Restore the prior state so as not to interfere with future specializations.
env.subs.rollback_to(snapshot);
layout_cache.rollback_to(cache_snapshot);
return result;
}
match procs.get_partial_proc(original) {
None => {
match arg_var {
@ -6495,7 +6737,7 @@ fn reuse_function_symbol<'a>(
let symbols = match captured {
CapturedSymbols::Captured(captured_symbols) => {
Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena)
Vec::from_iter_in(captured_symbols.iter(), env.arena)
.into_bump_slice()
}
CapturedSymbols::None => unreachable!(),
@ -6503,6 +6745,8 @@ fn reuse_function_symbol<'a>(
construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
original,
symbols,
@ -6539,6 +6783,8 @@ fn reuse_function_symbol<'a>(
// unification may still cause it to have an extra argument
construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
original,
&[],
@ -6570,7 +6816,7 @@ fn assign_to_symbol<'a>(
) -> Stmt<'a> {
use ReuseSymbol::*;
match can_reuse_symbol(env, procs, &loc_arg.value) {
Imported(original) | LocalFunction(original) => {
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
// for functions we must make sure they are specialized correctly
reuse_function_symbol(
env,
@ -7222,8 +7468,8 @@ fn call_specialized_proc<'a>(
.map(|pp| &pp.captured_symbols)
{
Some(&CapturedSymbols::Captured(captured_symbols)) => {
let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena)
.into_bump_slice();
let symbols =
Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice();
let closure_data_symbol = env.unique_symbol();
@ -7248,6 +7494,8 @@ fn call_specialized_proc<'a>(
let result = construct_closure_data(
env,
procs,
layout_cache,
lambda_set,
proc_name,
symbols,

View File

@ -2631,3 +2631,22 @@ fn list_find_empty_layout() {
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_lists() {
assert_evals_to!(
indoc!(
r#"
l = [1, 2, 3]
f : List U8, List U16 -> Nat
f = \_, _ -> 18
f l l
"#
),
18,
u64
)
}

View File

@ -2289,3 +2289,92 @@ fn sub_saturated() {
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_ints() {
assert_evals_to!(
indoc!(
r#"
x = 100
f : U8, U32 -> Nat
f = \_, _ -> 18
f x x
"#
),
18,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_floats() {
assert_evals_to!(
indoc!(
r#"
x = 100.0
f : F32, F64 -> Nat
f = \_, _ -> 18
f x x
"#
),
18,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_ints_names_dont_conflict() {
assert_evals_to!(
indoc!(
r#"
f : U8 -> Nat
f = \_ -> 9
x =
n = 100
f n
y =
n = 100
f n
x + y
"#
),
18,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_ints_aliased() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
y = 100
w1 = y
w2 = y
f1 : U8, U32 -> U8
f1 = \_, _ -> 1
f2 : U32, U8 -> U8
f2 = \_, _ -> 2
f1 w1 w2 + f2 w1 w2
"#
),
3,
u8
)
}

View File

@ -3182,3 +3182,28 @@ fn recursively_build_effect() {
RocStr
);
}
#[test]
#[ignore = "TODO; currently generates bad code because `a` isn't specialized inside the closure."]
#[cfg(any(feature = "gen-llvm"))]
fn polymophic_expression_captured_inside_closure() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
asU8 : U8 -> U8
asU8 = \_ -> 30
main =
a = 15
f = \{} ->
asU8 a
f {}
"#
),
30,
u8
);
}

View File

@ -59,7 +59,10 @@ fn list_int_inc() {
),
RocList<RocList<i64>>,
&[
3, // list
// TODO be smarter about coalescing polymorphic list values
1, // list0
1, // list1
1, // list2
1 // result
]
);
@ -77,7 +80,10 @@ fn list_int_dealloc() {
),
usize,
&[
0, // list
// TODO be smarter about coalescing polymorphic list values
0, // list0
0, // list1
0, // list2
0 // result
]
);
@ -130,6 +136,7 @@ fn struct_inc() {
indoc!(
r#"
s = Str.concat "A long enough string " "to be heap-allocated"
r1 : { a: I64, b: Str, c: Str }
r1 = { a: 123, b: s, c: s }
{ y: r1, z: r1 }
"#
@ -146,6 +153,7 @@ fn struct_dealloc() {
indoc!(
r#"
s = Str.concat "A long enough string " "to be heap-allocated"
r1 : { a: I64, b: Str, c: Str }
r1 = { a: 123, b: s, c: s }
r2 = { x: 456, y: r1, z: r1 }
r2.x

View File

@ -1262,3 +1262,108 @@ fn recursive_tag_union_into_flat_tag_union() {
|_| 0
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_tag() {
assert_evals_to!(
indoc!(
r#"
b = False
f : Bool, [True, False, Idk] -> U8
f = \_, _ -> 18
f b b
"#
),
18,
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_applied_tag() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main =
a = A "abc"
f = \x ->
when x is
A y -> y
B y -> y
f a
"#
),
RocStr::from_slice(b"abc"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_tag_with_polymorphic_arg() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
a = A
wrap = Wrapped a
useWrap1 : [Wrapped [A], Other] -> U8
useWrap1 =
\w -> when w is
Wrapped A -> 2
Other -> 3
useWrap2 : [Wrapped [A, B]] -> U8
useWrap2 =
\w -> when w is
Wrapped A -> 5
Wrapped B -> 7
useWrap1 wrap * useWrap2 wrap
"#
),
10,
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
mono : U8
mono = 15
poly = A
wrap = Wrapped poly mono
useWrap1 : [Wrapped [A] U8, Other] -> U8
useWrap1 =
\w -> when w is
Wrapped A n -> n
Other -> 0
useWrap2 : [Wrapped [A, B] U8] -> U8
useWrap2 =
\w -> when w is
Wrapped A n -> n
Wrapped B _ -> 0
useWrap1 wrap * useWrap2 wrap
"#
),
225,
u8
)
}

View File

@ -1,4 +1,3 @@
procedure Test.0 ():
let Test.1 : Builtin(Int(I64)) = 5i64;
let Test.3 : Builtin(Int(I64)) = 3i64;
ret Test.3;

View File

@ -1,3 +1,3 @@
procedure Test.0 ():
let Test.1 : Builtin(Int(I64)) = 5i64;
ret Test.1;
let Test.2 : Builtin(Int(I64)) = 5i64;
ret Test.2;

View File

@ -3,13 +3,13 @@ procedure List.7 (#Attr.2):
ret Test.7;
procedure Test.1 (Test.5):
let Test.2 : Builtin(Int(I64)) = 41i64;
let Test.11 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2};
let Test.10 : Builtin(List(LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11];
ret Test.10;
procedure Test.3 (Test.9, #Attr.12):
let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12;
let Test.2 : Builtin(Int(I64)) = 41i64;
ret Test.2;
procedure Test.0 ():

View File

@ -15,7 +15,6 @@ procedure Test.2 (Test.6):
ret Test.24;
procedure Test.0 ():
let Test.1 : Builtin(List(LambdaSet([]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [];
joinpoint Test.22 Test.3:
let Test.14 : Builtin(Int(U64)) = 0i64;
let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14;
@ -35,9 +34,9 @@ procedure Test.0 ():
in
let Test.25 : Builtin(Bool) = false;
if Test.25 then
let Test.1 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [];
jump Test.22 Test.1;
else
dec Test.1;
let Test.23 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {};
let Test.21 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [Test.23];
jump Test.22 Test.21;

View File

@ -1,19 +1,19 @@
procedure List.7 (#Attr.2):
let Test.6 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2;
ret Test.6;
let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2;
ret Test.7;
procedure Num.24 (#Attr.2, #Attr.3):
let Test.5 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.5;
procedure Test.0 ():
let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64];
let Test.9 : Builtin(Int(U64)) = 5i64;
let Test.10 : Builtin(Int(U64)) = 4i64;
let Test.7 : Builtin(Int(U64)) = CallByName Num.24 Test.9 Test.10;
let Test.8 : Builtin(Int(U64)) = 3i64;
let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.7 Test.8;
let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1;
dec Test.1;
let Test.10 : Builtin(Int(U64)) = 5i64;
let Test.11 : Builtin(Int(U64)) = 4i64;
let Test.8 : Builtin(Int(U64)) = CallByName Num.24 Test.10 Test.11;
let Test.9 : Builtin(Int(U64)) = 3i64;
let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.8 Test.9;
let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64];
let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.6;
dec Test.6;
let Test.2 : Builtin(Int(U64)) = CallByName Num.24 Test.3 Test.4;
ret Test.2;

View File

@ -1,9 +1,9 @@
procedure Num.24 (#Attr.2, #Attr.3):
let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.4;
let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.6;
procedure Test.0 ():
let Test.1 : Builtin(Int(I64)) = 3i64;
let Test.2 : Builtin(Int(I64)) = 4i64;
let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.1 Test.2;
let Test.4 : Builtin(Int(I64)) = 3i64;
let Test.5 : Builtin(Int(I64)) = 4i64;
let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.4 Test.5;
ret Test.3;

View File

@ -1,5 +1,3 @@
procedure Test.0 ():
let Test.1 : Builtin(Int(I64)) = 5i64;
let Test.4 : Builtin(Int(I64)) = 17i64;
let Test.2 : Builtin(Int(I64)) = 1337i64;
ret Test.2;

View File

@ -1,8 +1,6 @@
procedure Test.0 ():
let Test.1 : Builtin(Int(I64)) = 5i64;
let Test.4 : Builtin(Int(I64)) = 17i64;
let Test.5 : Builtin(Int(I64)) = 1i64;
let Test.2 : Builtin(Int(I64)) = 1337i64;
let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.4};
let Test.3 : Builtin(Int(I64)) = 17i64;
let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.3};
let Test.6 : Builtin(Int(I64)) = StructAtIndex 0 Test.7;
ret Test.6;

View File

@ -1,6 +1,6 @@
procedure List.7 (#Attr.2):
let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2;
ret Test.7;
let Test.10 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2;
ret Test.10;
procedure List.7 (#Attr.2):
let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2;
@ -11,11 +11,11 @@ procedure Num.24 (#Attr.2, #Attr.3):
ret Test.6;
procedure Test.0 ():
let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64];
let Test.2 : Builtin(List(Builtin(Float(F64)))) = Array [1f64];
let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1;
dec Test.1;
let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.2;
dec Test.2;
let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64];
let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.9;
dec Test.9;
let Test.7 : Builtin(List(Builtin(Float(F64)))) = Array [1f64];
let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.7;
dec Test.7;
let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.4 Test.5;
ret Test.3;

View File

@ -0,0 +1,20 @@
procedure Test.2 (Test.4):
let Test.11 : Builtin(Int(U8)) = 0i64;
let Test.12 : Builtin(Int(U8)) = GetTagId Test.4;
let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12;
if Test.13 then
let Test.5 : Builtin(Str) = UnionAtIndex (Id 0) (Index 0) Test.4;
inc Test.5;
dec Test.4;
ret Test.5;
else
let Test.6 : Builtin(Str) = UnionAtIndex (Id 1) (Index 0) Test.4;
inc Test.6;
dec Test.4;
ret Test.6;
procedure Test.0 ():
let Test.14 : Builtin(Str) = "A";
let Test.8 : Union(NonRecursive([[Builtin(Str)], [Builtin(Str)]])) = A Test.14;
let Test.7 : Builtin(Str) = CallByName Test.2 Test.8;
ret Test.7;

View File

@ -0,0 +1,9 @@
procedure Test.2 (Test.3, Test.4):
let Test.8 : Builtin(Int(U64)) = 18i64;
ret Test.8;
procedure Test.0 ():
let Test.6 : Builtin(Float(F32)) = 100f64;
let Test.7 : Builtin(Float(F64)) = 100f64;
let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7;
ret Test.5;

View File

@ -0,0 +1,9 @@
procedure Test.2 (Test.3, Test.4):
let Test.8 : Builtin(Int(U64)) = 18i64;
ret Test.8;
procedure Test.0 ():
let Test.6 : Builtin(Int(U8)) = 100i64;
let Test.7 : Builtin(Int(U32)) = 100i64;
let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7;
ret Test.5;

View File

@ -0,0 +1,23 @@
procedure Num.24 (#Attr.2, #Attr.3):
let Test.12 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.12;
procedure Test.4 (Test.7, Test.8):
let Test.17 : Builtin(Int(U64)) = 1i64;
ret Test.17;
procedure Test.4 (Test.7, Test.8):
let Test.18 : Builtin(Int(U64)) = 1i64;
ret Test.18;
procedure Test.0 ():
let Test.4 : LambdaSet([( Test.4, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {};
let Test.4 : LambdaSet([( Test.4, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {};
let Test.15 : Builtin(Int(U8)) = 100i64;
let Test.16 : Builtin(Int(U32)) = 100i64;
let Test.10 : Builtin(Int(U64)) = CallByName Test.4 Test.15 Test.16;
let Test.13 : Builtin(Int(U32)) = 100i64;
let Test.14 : Builtin(Int(U8)) = 100i64;
let Test.11 : Builtin(Int(U64)) = CallByName Test.4 Test.13 Test.14;
let Test.9 : Builtin(Int(U64)) = CallByName Num.24 Test.10 Test.11;
ret Test.9;

View File

@ -0,0 +1,11 @@
procedure Test.2 (Test.3, Test.4):
let Test.8 : Builtin(Int(U64)) = 18i64;
ret Test.8;
procedure Test.0 ():
let Test.6 : Builtin(List(Builtin(Int(U8)))) = Array [1i64, 2i64, 3i64];
let Test.7 : Builtin(List(Builtin(Int(U16)))) = Array [1i64, 2i64, 3i64];
let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7;
dec Test.7;
dec Test.6;
ret Test.5;

View File

@ -0,0 +1,9 @@
procedure Test.2 (Test.4, Test.5):
let Test.9 : Builtin(Int(U8)) = 18i64;
ret Test.9;
procedure Test.0 ():
let Test.7 : Builtin(Bool) = false;
let Test.8 : Builtin(Int(U8)) = 0u8;
let Test.6 : Builtin(Int(U8)) = CallByName Test.2 Test.7 Test.8;
ret Test.6;

View File

@ -0,0 +1,10 @@
procedure Test.4 (Test.8):
let Test.11 : Builtin(Int(U64)) = 1i64;
ret Test.11;
procedure Test.0 ():
let Test.13 : Builtin(Bool) = false;
let Test.12 : Builtin(Bool) = false;
let Test.10 : Struct([Builtin(Bool), Builtin(Bool)]) = Struct {Test.12, Test.13};
let Test.9 : Builtin(Int(U64)) = CallByName Test.4 Test.10;
ret Test.9;

View File

@ -1,10 +1,10 @@
procedure Test.1 (Test.5):
let Test.2 : Builtin(Int(I64)) = 42i64;
let Test.3 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2};
ret Test.3;
procedure Test.3 (Test.9, #Attr.12):
let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12;
let Test.2 : Builtin(Int(I64)) = 42i64;
ret Test.2;
procedure Test.0 ():

View File

@ -1,13 +1,13 @@
procedure Num.24 (#Attr.2, #Attr.3):
let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.8;
let Test.9 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.9;
procedure Test.1 (Test.4):
let Test.2 : Builtin(Int(I64)) = 10i64;
let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.4;
let Test.8 : Builtin(Int(I64)) = 10i64;
let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.4;
ret Test.7;
procedure Test.0 ():
let Test.9 : Builtin(Int(I64)) = 9i64;
let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9;
let Test.10 : Builtin(Int(I64)) = 9i64;
let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.10;
ret Test.5;

View File

@ -1111,6 +1111,131 @@ fn empty_list_of_function_type() {
)
}
#[mono_test]
fn monomorphized_ints() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
x = 100
f : U8, U32 -> Nat
f = \_, _ -> 18
f x x
"#
)
}
#[mono_test]
fn monomorphized_floats() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
x = 100.0
f : F32, F64 -> Nat
f = \_, _ -> 18
f x x
"#
)
}
#[mono_test]
#[ignore = "TODO"]
fn monomorphized_ints_aliased() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
y = 100
w1 = y
w2 = y
f = \_, _ -> 1
f1 : U8, U32 -> Nat
f1 = f
f2 : U32, U8 -> Nat
f2 = f
f1 w1 w2 + f2 w1 w2
"#
)
}
#[mono_test]
fn monomorphized_tag() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
b = False
f : Bool, [True, False, Idk] -> U8
f = \_, _ -> 18
f b b
"#
)
}
#[mono_test]
fn monomorphized_tag_with_aliased_args() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
b = False
c = False
a = A b c
f : [A Bool Bool] -> Nat
f = \_ -> 1
f a
"#
)
}
#[mono_test]
fn monomorphized_list() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
l = [1, 2, 3]
f : List U8, List U16 -> Nat
f = \_, _ -> 18
f l l
"#
)
}
#[mono_test]
fn monomorphized_applied_tag() {
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main =
a = A "A"
f = \x ->
when x is
A y -> y
B y -> y
f a
"#
)
}
// #[ignore]
// #[mono_test]
// fn static_str_closure() {

View File

@ -1364,6 +1364,15 @@ impl Subs {
pub fn commit_snapshot(&mut self, snapshot: Snapshot<InPlace<Variable>>) {
self.utable.commit(snapshot)
}
/// Checks whether the content of `var`, or any nested content, satisfies the `predicate`.
pub fn var_contains_content<P>(&self, var: Variable, predicate: P) -> bool
where
P: Fn(&Content) -> bool + Copy,
{
let mut seen_recursion_vars = MutSet::default();
var_contains_content_help(self, var, predicate, &mut seen_recursion_vars)
}
}
#[inline(always)]
@ -3464,3 +3473,79 @@ fn deep_copy_var_to_help<'a>(
}
}
}
fn var_contains_content_help<P>(
subs: &Subs,
var: Variable,
predicate: P,
seen_recursion_vars: &mut MutSet<Variable>,
) -> bool
where
P: Fn(&Content) -> bool + Copy,
{
let mut stack = vec![var];
macro_rules! push_var_slice {
($slice:expr) => {
stack.extend(subs.get_subs_slice($slice))
};
}
while let Some(var) = stack.pop() {
if seen_recursion_vars.contains(&var) {
continue;
}
let content = subs.get_content_without_compacting(var);
if predicate(content) {
return true;
}
use Content::*;
use FlatType::*;
match content {
FlexVar(_) | RigidVar(_) => {}
RecursionVar {
structure,
opt_name: _,
} => {
seen_recursion_vars.insert(var);
stack.push(*structure);
}
Structure(flat_type) => match flat_type {
Apply(_, vars) => push_var_slice!(*vars),
Func(args, clos, ret) => {
push_var_slice!(*args);
stack.push(*clos);
stack.push(*ret);
}
Record(fields, var) => {
push_var_slice!(fields.variables());
stack.push(*var);
}
TagUnion(tags, ext_var) => {
for i in tags.variables() {
push_var_slice!(subs[i]);
}
stack.push(*ext_var);
}
FunctionOrTagUnion(_, _, var) => stack.push(*var),
RecursiveTagUnion(rec_var, tags, ext_var) => {
seen_recursion_vars.insert(*rec_var);
for i in tags.variables() {
push_var_slice!(subs[i]);
}
stack.push(*ext_var);
}
Erroneous(_) | EmptyRecord | EmptyTagUnion => {}
},
Alias(_, arguments, real_type_var) => {
push_var_slice!(arguments.variables());
stack.push(*real_type_var);
}
Error => {}
}
}
false
}