Specialize polymorphic non-function expressions

This commit fixes a long-standing bug wherein bindings to polymorphic,
non-function expressions would be lowered at binding site, rather than
being specialized at the call site.

Concretely, consider the program

```
main =
    n = 1

    idU8 : U8 -> U8
    idU8 = \m -> m

    idU8 n
```

Prior to this commit, we would lower `n = 1` as part of the IR, and the
`n` at the call site `idU8 n` would reference the lowered definition.
However, at the definition site, `1` has the polymorphic type `Num *` -
it is not until the the call site that we are able to refine the type
bound by `n`, but at that point it's too late. Since the default layout
for `Num *` is a signed 64-bit int, we would generate IR like

```
procedure main():
    let App.n : Builtin(Int(I64)) = 1i64;
    ...
    let App.5 : Builtin(Int(U8)) = CallByName Add.idU8 App.n;
    ret App.5;
```

But we know `idU8` expects a `u8`; giving it an `i64` is nonsense.
Indeed this would trigger LLVM miscompilations later on.

To remedy this, we now keep a sidecar table that maps symbols to the
polymorphic expression they reference, when they do so. We then
specialize references to symbols on the fly at usage sites, similar to
how we specialize function usages.

Looking at our example, the definition `n = 1` is now never lowered to
the IR directly. We only generate code for `1` at each place `n` is
referenced. As a larger example, you can imagine that

```
main =
    n = 1

    asU8 : U8 -> U8
    asU32 : U32 -> U8

    asU8 n + asU32 n
```

is lowered to the moral equivalent of

```
main =
    asU8 : U8 -> U8
    asU32 : U32 -> U8

    asU8 1 + asU32 1
```

Moreover, transient usages of polymorphic expressions are lowered
successfully with this approach. See for example the
`monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg` test in
this commit, which checks that

```
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
```

has proper code generated for it, in the presence of the polymorphic
`wrap` which references the polymorphic `poly`.

https://github.com/rtfeldman/roc/pull/2347 had a different approach to
this - polymorphic expressions would be converted to (possibly capturing) thunks.
This has the benefit of reducing code size if there are many polymorphic
usages, but may make the generated code slower and makes integration
with the existing IR implementation harder. In practice I think the
average number of polymorphic usages of an expression will be very
small.

Closes https://github.com/rtfeldman/roc/issues/2336
Closes https://github.com/rtfeldman/roc/issues/2254
Closes https://github.com/rtfeldman/roc/issues/2344
This commit is contained in:
ayazhafiz 2022-01-19 22:52:15 -05:00
parent 3342090e7b
commit a5de224626
23 changed files with 655 additions and 68 deletions

View file

@ -207,6 +207,67 @@ 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)]
pub struct PartialExprs(BumpMap<Symbol, PartialExprLink>);
impl PartialExprs {
pub fn new_in<'a>(arena: &'a Bump) -> Self {
Self(BumpMap::new_in(arena))
}
pub fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) {
self.0.insert(symbol, PartialExprLink::Expr(expr, expr_var));
}
pub fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) {
self.0.insert(symbol, PartialExprLink::Aliases(aliases));
}
pub fn contains(&self, symbol: Symbol) -> bool {
self.0.contains_key(&symbol)
}
pub 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));
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum CapturedSymbols<'a> { pub enum CapturedSymbols<'a> {
None, None,
@ -668,6 +729,7 @@ impl<'a> Specialized<'a> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Procs<'a> { pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>, pub partial_procs: PartialProcs<'a>,
pub partial_exprs: PartialExprs,
pub imported_module_thunks: &'a [Symbol], pub imported_module_thunks: &'a [Symbol],
pub module_thunks: &'a [Symbol], pub module_thunks: &'a [Symbol],
pending_specializations: PendingSpecializations<'a>, pending_specializations: PendingSpecializations<'a>,
@ -680,6 +742,7 @@ impl<'a> Procs<'a> {
pub fn new_in(arena: &'a Bump) -> Self { pub fn new_in(arena: &'a Bump) -> Self {
Self { Self {
partial_procs: PartialProcs::new_in(arena), partial_procs: PartialProcs::new_in(arena),
partial_exprs: PartialExprs::new_in(arena),
imported_module_thunks: &[], imported_module_thunks: &[],
module_thunks: &[], module_thunks: &[],
pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)), pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)),
@ -3103,16 +3166,20 @@ pub fn with_hole<'a>(
_ => {} _ => {}
} }
// continue with the default path let build_rest =
let mut stmt = with_hole( |env: &mut Env<'a, '_>,
env, procs: &mut Procs<'a>,
cont.value, layout_cache: &mut LayoutCache<'a>| {
variable, with_hole(
procs, env,
layout_cache, cont.value,
assigned, variable,
hole, procs,
); layout_cache,
assigned,
hole,
)
};
// a variable is aliased // a variable is aliased
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
@ -3124,18 +3191,17 @@ pub fn with_hole<'a>(
// //
// foo = RBTRee.empty // foo = RBTRee.empty
stmt = handle_variable_aliasing( handle_variable_aliasing(
env, env,
procs, procs,
layout_cache, layout_cache,
def.expr_var, def.expr_var,
symbol, symbol,
original, original,
stmt, build_rest,
); )
stmt
} else { } else {
let rest = build_rest(env, procs, layout_cache);
with_hole( with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
@ -3143,7 +3209,7 @@ pub fn with_hole<'a>(
procs, procs,
layout_cache, layout_cache,
symbol, symbol,
env.arena.alloc(stmt), env.arena.alloc(rest),
) )
} }
} else { } else {
@ -3328,6 +3394,7 @@ pub fn with_hole<'a>(
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
enum Field { enum Field {
// TODO: rename this since it can handle unspecialized expressions now too
Function(Symbol, Variable), Function(Symbol, Variable),
ValueSymbol, ValueSymbol,
Field(roc_can::expr::Field), Field(roc_can::expr::Field),
@ -3338,7 +3405,7 @@ pub fn with_hole<'a>(
use ReuseSymbol::*; use ReuseSymbol::*;
match fields.remove(&label) { match fields.remove(&label) {
Some(field) => match can_reuse_symbol(env, procs, &field.loc_expr.value) { 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); field_symbols.push(symbol);
can_fields.push(Field::Function(symbol, variable)); can_fields.push(Field::Function(symbol, variable));
} }
@ -4064,6 +4131,9 @@ pub fn with_hole<'a>(
LocalFunction(_) => { LocalFunction(_) => {
unreachable!("if this was known to be a function, we would not be here") 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) => { Imported(thunk_name) => {
debug_assert!(procs.is_imported_module_thunk(thunk_name)); debug_assert!(procs.is_imported_module_thunk(thunk_name));
@ -5023,6 +5093,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>( pub fn from_can<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
variable: Variable, variable: Variable,
@ -5177,19 +5272,26 @@ pub fn from_can<'a>(
// or // or
// //
// foo = RBTRee.empty // 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, env,
procs, procs,
layout_cache, layout_cache,
def.expr_var, def.expr_var,
*symbol, *symbol,
original, original,
rest, build_rest,
); );
return rest;
} }
roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => { roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => {
use roc_can::expr::Expr::*; use roc_can::expr::Expr::*;
@ -5273,6 +5375,21 @@ pub fn from_can<'a>(
return from_can(env, variable, new_outer, procs, layout_cache); 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);
return from_can(env, variable, cont.value, procs, layout_cache);
}
_ => { _ => {
let rest = from_can(env, variable, cont.value, procs, layout_cache); let rest = from_can(env, variable, cont.value, procs, layout_cache);
return with_hole( return with_hole(
@ -6290,6 +6407,7 @@ enum ReuseSymbol {
Imported(Symbol), Imported(Symbol),
LocalFunction(Symbol), LocalFunction(Symbol),
Value(Symbol), Value(Symbol),
UnspecializedExpr(Symbol),
NotASymbol, NotASymbol,
} }
@ -6307,6 +6425,8 @@ fn can_reuse_symbol<'a>(
Imported(symbol) Imported(symbol)
} else if procs.partial_procs.contains_key(symbol) { } else if procs.partial_procs.contains_key(symbol) {
LocalFunction(symbol) LocalFunction(symbol)
} else if procs.partial_exprs.contains(symbol) {
UnspecializedExpr(symbol)
} else { } else {
Value(symbol) Value(symbol)
} }
@ -6326,15 +6446,29 @@ fn possible_reuse_symbol<'a>(
} }
} }
fn handle_variable_aliasing<'a>( fn handle_variable_aliasing<'a, BuildRest>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
variable: Variable, variable: Variable,
left: Symbol, left: Symbol,
right: Symbol, right: Symbol,
mut result: Stmt<'a>, build_rest: BuildRest,
) -> Stmt<'a> { ) -> 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 procs.is_imported_module_thunk(right) {
// if this is an imported symbol, then we must make sure it is // if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer. // specialized, and wrap the original in a function pointer.
@ -6392,6 +6526,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 /// 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>( fn reuse_function_symbol<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
@ -6401,6 +6536,35 @@ fn reuse_function_symbol<'a>(
result: Stmt<'a>, result: Stmt<'a>,
original: Symbol, original: Symbol,
) -> Stmt<'a> { ) -> 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) { match procs.get_partial_proc(original) {
None => { None => {
match arg_var { match arg_var {
@ -6566,7 +6730,7 @@ fn assign_to_symbol<'a>(
) -> Stmt<'a> { ) -> Stmt<'a> {
use ReuseSymbol::*; use ReuseSymbol::*;
match can_reuse_symbol(env, procs, &loc_arg.value) { 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 // for functions we must make sure they are specialized correctly
reuse_function_symbol( reuse_function_symbol(
env, env,

View file

@ -2631,3 +2631,22 @@ fn list_find_empty_layout() {
i64 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

@ -2317,3 +2317,92 @@ fn sub_saturated() {
u8 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

@ -1262,3 +1262,108 @@ fn recursive_tag_union_into_flat_tag_union() {
|_| 0 |_| 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 (): procedure Test.0 ():
let Test.1 : Builtin(Int(I64)) = 5i64;
let Test.3 : Builtin(Int(I64)) = 3i64; let Test.3 : Builtin(Int(I64)) = 3i64;
ret Test.3; ret Test.3;

View file

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

View file

@ -3,13 +3,13 @@ procedure List.7 (#Attr.2):
ret Test.7; ret Test.7;
procedure Test.1 (Test.5): 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.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]; 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; ret Test.10;
procedure Test.3 (Test.9, #Attr.12): procedure Test.3 (Test.9, #Attr.12):
let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12;
let Test.2 : Builtin(Int(I64)) = 41i64;
ret Test.2; ret Test.2;
procedure Test.0 (): procedure Test.0 ():

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,6 @@
procedure Test.0 (): 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.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; let Test.6 : Builtin(Int(I64)) = StructAtIndex 0 Test.7;
ret Test.6; ret Test.6;

View file

@ -1,6 +1,6 @@
procedure List.7 (#Attr.2): procedure List.7 (#Attr.2):
let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; let Test.10 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2;
ret Test.7; ret Test.10;
procedure List.7 (#Attr.2): procedure List.7 (#Attr.2):
let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #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; ret Test.6;
procedure Test.0 (): procedure Test.0 ():
let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; let Test.9 : 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.9;
let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1; dec Test.9;
dec Test.1; let Test.7 : Builtin(List(Builtin(Float(F64)))) = Array [1f64];
let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.2; let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.7;
dec Test.2; dec Test.7;
let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.4 Test.5; let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.4 Test.5;
ret Test.3; 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): 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}; let Test.3 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2};
ret Test.3; ret Test.3;
procedure Test.3 (Test.9, #Attr.12): procedure Test.3 (Test.9, #Attr.12):
let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12;
let Test.2 : Builtin(Int(I64)) = 42i64;
ret Test.2; ret Test.2;
procedure Test.0 (): procedure Test.0 ():

View file

@ -1,13 +1,13 @@
procedure Num.24 (#Attr.2, #Attr.3): procedure Num.24 (#Attr.2, #Attr.3):
let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; let Test.9 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.8; ret Test.9;
procedure Test.1 (Test.4): procedure Test.1 (Test.4):
let Test.2 : Builtin(Int(I64)) = 10i64; let Test.8 : Builtin(Int(I64)) = 10i64;
let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.4; let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.4;
ret Test.7; ret Test.7;
procedure Test.0 (): procedure Test.0 ():
let Test.9 : Builtin(Int(I64)) = 9i64; let Test.10 : Builtin(Int(I64)) = 9i64;
let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9; let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.10;
ret Test.5; 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] // #[ignore]
// #[mono_test] // #[mono_test]
// fn static_str_closure() { // fn static_str_closure() {