From ea812de63f4e3ae213a59f8db7fcbe136ea7e19d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:05:22 -0500 Subject: [PATCH 1/9] s/entry_var/elem_var/g for clarity --- src/can/expr.rs | 6 +++--- src/constrain/expr.rs | 10 +++++----- src/uniqueness/mod.rs | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/can/expr.rs b/src/can/expr.rs index 1c96d70858..7ff696ee54 100644 --- a/src/can/expr.rs +++ b/src/can/expr.rs @@ -37,7 +37,7 @@ pub enum Expr { Str(Box), BlockStr(Box), List { - entry_var: Variable, + elem_var: Variable, loc_elems: Vec>, }, @@ -201,7 +201,7 @@ pub fn canonicalize_expr<'a>( if loc_elems.is_empty() { ( List { - entry_var: var_store.fresh(), + elem_var: var_store.fresh(), loc_elems: Vec::new(), }, Output::default(), @@ -228,7 +228,7 @@ pub fn canonicalize_expr<'a>( ( List { - entry_var: var_store.fresh(), + elem_var: var_store.fresh(), loc_elems: can_elems, }, output, diff --git a/src/constrain/expr.rs b/src/constrain/expr.rs index 392f6d0d51..a1936f5306 100644 --- a/src/constrain/expr.rs +++ b/src/constrain/expr.rs @@ -171,17 +171,17 @@ pub fn constrain_expr( } Str(_) | BlockStr(_) => Eq(str_type(), expected, region), List { - entry_var, + elem_var, loc_elems, .. } => { if loc_elems.is_empty() { exists( - vec![*entry_var], - Eq(empty_list_type(*entry_var), expected, region), + vec![*elem_var], + Eq(empty_list_type(*elem_var), expected, region), ) } else { - let list_elem_type = Type::Variable(*entry_var); + let list_elem_type = Type::Variable(*elem_var); let mut constraints = Vec::with_capacity(1 + loc_elems.len()); for loc_elem in loc_elems { @@ -195,7 +195,7 @@ pub fn constrain_expr( constraints.push(Eq(list_type(list_elem_type), expected, region)); - exists(vec![*entry_var], And(constraints)) + exists(vec![*elem_var], And(constraints)) } } Call(boxed, loc_args, _application_style) => { diff --git a/src/uniqueness/mod.rs b/src/uniqueness/mod.rs index ee27b9ff3e..931a28e26a 100644 --- a/src/uniqueness/mod.rs +++ b/src/uniqueness/mod.rs @@ -493,16 +493,16 @@ pub fn constrain_expr( exists(vars, And(arg_cons)) } List { - entry_var, + elem_var, loc_elems, } => { let uniq_var = var_store.fresh(); if loc_elems.is_empty() { - let inferred = builtins::empty_list_type(Bool::variable(uniq_var), *entry_var); - exists(vec![*entry_var, uniq_var], Eq(inferred, expected, region)) + let inferred = builtins::empty_list_type(Bool::variable(uniq_var), *elem_var); + exists(vec![*elem_var, uniq_var], Eq(inferred, expected, region)) } else { // constrain `expected ~ List a` and that all elements `~ a`. - let entry_type = Type::Variable(*entry_var); + let entry_type = Type::Variable(*elem_var); let mut constraints = Vec::with_capacity(1 + loc_elems.len()); for loc_elem in loc_elems.iter() { @@ -524,7 +524,7 @@ pub fn constrain_expr( let inferred = list_type(Bool::variable(uniq_var), entry_type); constraints.push(Eq(inferred, expected, region)); - exists(vec![*entry_var, uniq_var], And(constraints)) + exists(vec![*elem_var, uniq_var], And(constraints)) } } Var(symbol) => { From f957d694c83198dab14708e4d516fec53c72afe7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:05:41 -0500 Subject: [PATCH 2/9] Added List.getUnsafe for now --- src/builtins.rs | 8 ++++++++ src/module/symbol.rs | 1 + 2 files changed, 9 insertions(+) diff --git a/src/builtins.rs b/src/builtins.rs index 87ad4f5bfa..1188421ab3 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -372,6 +372,14 @@ pub fn types() -> MutMap { ), ); + add_type( + Symbol::LIST_GET_UNSAFE, // TODO remove this once we can code gen Result + SolvedType::Func( + vec![list_type(flex(TVAR1)), int_type()], + Box::new(flex(TVAR1)), + ), + ); + // set : List a, Int, a -> List a add_type( Symbol::LIST_SET, diff --git a/src/module/symbol.rs b/src/module/symbol.rs index 68813f0bec..a3a101222d 100644 --- a/src/module/symbol.rs +++ b/src/module/symbol.rs @@ -606,6 +606,7 @@ define_builtins! { 3 LIST_GET: "get" 4 LIST_SET: "set" 5 LIST_MAP: "map" + 6 LIST_GET_UNSAFE: "getUnsafe" // TODO remove once we can code gen Result } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias From 17fd5e5b15eefe790bb40d119df1cb055de8fa0a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:05:57 -0500 Subject: [PATCH 3/9] Add a comment to stack_size function --- src/mono/layout.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mono/layout.rs b/src/mono/layout.rs index ba772440af..1627e5cf56 100644 --- a/src/mono/layout.rs +++ b/src/mono/layout.rs @@ -58,6 +58,10 @@ impl<'a> Layout<'a> { } } + /// TODO this will probably need to move to crane:: because + /// LLVM gets the answer using different APIs! Also, might be + /// nice to rename it to bytes_size, both to include the units + /// and also because this is the size on the stack *and* the heap! pub fn stack_size(&self, cfg: TargetFrontendConfig) -> u32 { use Layout::*; From b5fff4c0cde3123f0056d887622c6753f6364255 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:06:07 -0500 Subject: [PATCH 4/9] Make a debug panic more helpful --- src/mono/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/layout.rs b/src/mono/layout.rs index 1627e5cf56..dab5f492b3 100644 --- a/src/mono/layout.rs +++ b/src/mono/layout.rs @@ -256,7 +256,7 @@ fn layout_from_flat_type<'a>( } } _ => { - panic!("TODO handle a tag union with mutliple tags."); + panic!("TODO handle a tag union with mutliple tags: {:?}", tags); } } } From 46f8f2313f1920174b6e6f4d05c32c17405ef791 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:06:37 -0500 Subject: [PATCH 5/9] Cranelift test for getting an elem from a List Int --- tests/test_gen.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_gen.rs b/tests/test_gen.rs index 284350f339..17ca4c4696 100644 --- a/tests/test_gen.rs +++ b/tests/test_gen.rs @@ -343,15 +343,20 @@ mod test_gen { assert_evals_to!("1234.0", 1234.0, f64); } + #[test] + fn basic_int_list() { + assert_crane_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 1", 9, i64, |a| a); + } + #[test] fn branch_first_float() { assert_evals_to!( indoc!( r#" - when 1.23 is - 1.23 -> 12 - _ -> 34 - "# + when 1.23 is + 1.23 -> 12 + _ -> 34 + "# ), 12, i64 From 5d07057e0998c7d37ffb65b4ac9be5889d9dc52e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:07:19 -0500 Subject: [PATCH 6/9] Add a test_infer for List.get --- tests/test_infer.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/test_infer.rs b/tests/test_infer.rs index bdb414e66b..d7a7f115b8 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -2228,10 +2228,10 @@ mod test_infer { list |> List.set i atJ |> List.set j atI - + _ -> list - + partition : Int, Int, List Int -> [ Pair Int (List Int) ] partition = \low, high, initialList -> when List.get initialList high is @@ -2244,19 +2244,19 @@ mod test_infer { go (i + 1) (j + 1) (swap (i + 1) j list) else go i (j + 1) list - + Err _ -> Pair i list else Pair i list - + when go (low - 1) low initialList is Pair newI newList -> Pair (newI + 1) (swap (newI + 1) high newList) - + Err _ -> Pair (low - 1) initialList - + partition "# ), @@ -2282,4 +2282,16 @@ mod test_infer { "List Int -> List Int", ); } + + #[test] + fn list_get() { + infer_eq_without_problem( + indoc!( + r#" + List.get [ 10, 9, 8, 7 ] 1 + "# + ), + "Result Int [ IndexOutOfBounds ]*", + ); + } } From 374c739e03805443baae5d0e81fe20996f8e3577 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:07:56 -0500 Subject: [PATCH 7/9] Build an Array in Cranelift --- src/crane/build.rs | 33 +++++++++++++++++++++++++++++++-- src/mono/expr.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/crane/build.rs b/src/crane/build.rs index bbf114a471..6e860b7c49 100644 --- a/src/crane/build.rs +++ b/src/crane/build.rs @@ -247,8 +247,37 @@ pub fn build_expr<'a, B: Backend>( // TODO: Instead of NUL-terminating, return a struct // with the pointer and also the length and capacity. let nul_terminator = builder.ins().iconst(types::I8, 0); - let index = bytes_len as u64 - 1; - let offset = Offset32::new(index as i32); + let index = bytes_len as i32 - 1; + let offset = Offset32::new(index); + + builder.ins().store(mem_flags, nul_terminator, ptr, offset); + + ptr + } + } + Array { elem_layout, elems } => { + if elems.is_empty() { + panic!("TODO build an empty Array in Crane"); + } else { + let elem_bytes = elem_layout.stack_size(env.cfg) as usize; + let bytes_len = (elem_bytes * elems.len()) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */; + let ptr = call_malloc(env, module, builder, bytes_len); + let mem_flags = MemFlags::new(); + + // Copy the elements from the literal into the array + for (index, elem) in elems.iter().enumerate() { + let offset = Offset32::new(elem_bytes as i32 * index as i32); + let val = build_expr(env, scope, module, builder, elem, procs); + + builder.ins().store(mem_flags, val, ptr, offset); + } + + // Add a NUL terminator at the end. + // TODO: Instead of NUL-terminating, return a struct + // with the pointer and also the length and capacity. + let nul_terminator = builder.ins().iconst(types::I8, 0); + let index = bytes_len as i32 - 1; + let offset = Offset32::new(index); builder.ins().store(mem_flags, nul_terminator, ptr, offset); diff --git a/src/mono/expr.rs b/src/mono/expr.rs index 930c906dfd..03cdc2c17c 100644 --- a/src/mono/expr.rs +++ b/src/mono/expr.rs @@ -105,6 +105,11 @@ pub enum Expr<'a> { struct_layout: Layout<'a>, }, + Array { + elem_layout: Layout<'a>, + elems: &'a [Expr<'a>], + }, + RuntimeError(&'a str), } @@ -287,6 +292,31 @@ fn from_can<'a>( } } + List { + elem_var, + loc_elems, + } => { + let subs = env.subs; + let arena = env.arena; + let content = subs.get_without_compacting(elem_var).content; + let elem_layout = match Layout::from_content(arena, content, subs) { + Ok(layout) => layout, + Err(()) => { + panic!("TODO gracefully handle List with invalid element layout"); + } + }; + + let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); + + for loc_elem in loc_elems { + elems.push(from_can(env, loc_elem.value, procs, None)); + } + + Expr::Array { + elem_layout, + elems: elems.into_bump_slice(), + } + } other => panic!("TODO convert canonicalized {:?} to ll::Expr", other), } } From 25e55185b54dbec30b529240ab7e2e0c261ee68f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:08:05 -0500 Subject: [PATCH 8/9] Implement List.getUnsafe in Cranelift --- src/crane/build.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/crane/build.rs b/src/crane/build.rs index 6e860b7c49..a4d7a1da5b 100644 --- a/src/crane/build.rs +++ b/src/crane/build.rs @@ -584,6 +584,26 @@ fn call_with_args<'a, B: Backend>( debug_assert!(args.len() == 2); builder.ins().imul(args[0], args[1]) } + Symbol::LIST_GET_UNSAFE => { + debug_assert!(args.len() == 2); + + let list_ptr = args[0]; + let elem_index = args[1]; + + let elem_type = Type::int(64).unwrap(); // TODO Look this up instead of hardcoding it! + let elem_bytes = 8; // TODO Look this up instead of hardcoding it! + let elem_size = builder.ins().iconst(types::I64, elem_bytes); + + // Multiply the requested index by the size of each element. + let offset = builder.ins().imul(elem_index, elem_size); + + builder.ins().load_complex( + elem_type, + MemFlags::new(), + &[list_ptr, offset], + Offset32::new(0), + ) + } _ => { let fn_id = match scope.get(&symbol) { Some(ScopeEntry::Func{ func_id, .. }) => *func_id, From 81f3605fde781c9d0e0204c36e686491f488e6d9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Feb 2020 20:55:27 -0500 Subject: [PATCH 9/9] Fix uniqueness tests --- tests/test_uniqueness_infer.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_uniqueness_infer.rs b/tests/test_uniqueness_infer.rs index a515171aa6..a57dc8730b 100644 --- a/tests/test_uniqueness_infer.rs +++ b/tests/test_uniqueness_infer.rs @@ -1024,7 +1024,7 @@ mod test_infer_uniq { \{ left, right } -> { left, right } "# ), - "Attr * (Attr (* | a | b) { left : (Attr a c), right : (Attr b d) }* -> Attr * { left : (Attr a c), right : (Attr b d) })", + "Attr * (Attr (* | a | b) { left : (Attr b c), right : (Attr a d) }* -> Attr * { left : (Attr b c), right : (Attr a d) })", ); } @@ -1066,7 +1066,7 @@ mod test_infer_uniq { // TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers? // i.e. the `b` could be ignored in this example, is that true in general? // seems like it because we don't really extract anything. - "Attr * (Attr (* | a | b) [ Foo (Attr b c) (Attr a *) ]* -> Attr * [ Foo (Attr b c) (Attr * Str) ]*)", + "Attr * (Attr (* | a | b) [ Foo (Attr a c) (Attr b *) ]* -> Attr * [ Foo (Attr a c) (Attr * Str) ]*)", ); } @@ -1289,7 +1289,7 @@ mod test_infer_uniq { r "# ), - "Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)" + "Attr * (Attr (a | b) { foo : (Attr a { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr a { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)" ); } @@ -1307,7 +1307,7 @@ mod test_infer_uniq { r "# ), - "Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e)" + "Attr * (Attr (a | b) { foo : (Attr a { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr a { bar : (Attr Shared c) }d) }e)" ); } @@ -1342,7 +1342,7 @@ mod test_infer_uniq { r.tic.tac.toe "# ), - "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | c | d) { bar : (Attr (c | d) { baz : (Attr d f) }*) }*), tic : (Attr (a | d | e) { tac : (Attr (d | e) { toe : (Attr d f) }*) }*) }* -> Attr d f)" + "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | c | e) { bar : (Attr (b | e) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | d) { tac : (Attr (a | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)" // "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (c | d | e) { bar : (Attr (c | d) { baz : (Attr c f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (a | c) { toe : (Attr c f) }*) }*) }* -> Attr c f)" // "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | d | e) { bar : (Attr (b | d) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (b | c) { toe : (Attr b f) }*) }*) }* -> Attr b f)" // "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (b | c | e) { bar : (Attr (b | e) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | d) { tac : (Attr (b | d) { toe : (Attr b f) }*) }*) }* -> Attr b f)" @@ -1980,7 +1980,7 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \list -> + \list -> p = List.get list 1 q = List.get list 1 @@ -1996,12 +1996,12 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \list -> + \list -> when List.get list 0 is - Ok v -> + Ok v -> List.set list 0 (v + 1) - Err _ -> + Err _ -> list "# ), @@ -2014,14 +2014,14 @@ mod test_infer_uniq { infer_eq( indoc!( r#" - \list -> + \list -> if List.isEmpty list then list else List.set list 0 42 "# ), - "Attr * (Attr (a | b) (List (Attr a Int)) -> Attr (a | b) (List (Attr a Int)))", + "Attr * (Attr (a | b) (List (Attr b Int)) -> Attr (a | b) (List (Attr b Int)))", ); } }