From 195925d99310196be7730ee7fb4b616e56652205 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 16:30:54 -0400 Subject: [PATCH 01/13] Removed 'main = \_ ->' from List.map tests, since they were to work around a bug, and also split list_map and list_append tests into separate tests --- compiler/gen/tests/gen_list.rs | 122 +++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index dac344d9e5..bacf1a17cd 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -34,7 +34,15 @@ mod gen_list { fn list_append() { assert_evals_to!("List.append [1] 2", &[1, 2], &'static [i64]); assert_evals_to!("List.append [1, 1] 2", &[1, 1, 2], &'static [i64]); + } + + #[test] + fn list_append_to_empty_list() { assert_evals_to!("List.append [] 3", &[3], &'static [i64]); + } + + #[test] + fn list_append_to_empty_list_of_int() { assert_evals_to!( indoc!( r#" @@ -48,11 +56,19 @@ mod gen_list { &[3, 3], &'static [i64] ); + } + + #[test] + fn list_append_bools() { assert_evals_to!( "List.append [ True, False ] True", &[true, false, true], &'static [bool] ); + } + + #[test] + fn list_append_longer_list() { assert_evals_to!( "List.append [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] 23", &[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], @@ -92,114 +108,118 @@ mod gen_list { } #[test] - fn list_map() { + fn list_map_on_empty_list_with_int_layout() { assert_evals_to!( indoc!( r#" - main = \{} -> - empty : List Int - empty = - [] + empty : List Int + empty = + [] - List.map empty (\x -> x) - main {} + List.map empty (\x -> x) "# ), &[], &'static [i64] ); + } + + #[test] + fn list_map_on_non_empty_list() { assert_evals_to!( indoc!( r#" - main = \{} -> - nonEmpty : List Int - nonEmpty = - [ 1 ] + nonEmpty : List Int + nonEmpty = + [ 1 ] - List.map nonEmpty (\x -> x) - main {} + List.map nonEmpty (\x -> x) "# ), &[1], &'static [i64] ); + } + + #[test] + fn list_map_changes_input() { assert_evals_to!( indoc!( r#" - main = \{} -> - nonEmpty : List Int - nonEmpty = - [ 1 ] + nonEmpty : List Int + nonEmpty = + [ 1 ] - List.map nonEmpty (\x -> x + 1) - main {} + List.map nonEmpty (\x -> x + 1) "# ), &[2], &'static [i64] ); + } + #[test] + fn list_map_on_big_list() { assert_evals_to!( indoc!( r#" - main = \{} -> - nonEmpty : List Int - nonEmpty = - [ 1, 2, 3, 4, 5 ] - List.map nonEmpty (\x -> x * 2) - main {} + nonEmpty : List Int + nonEmpty = + [ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 ] + + List.map nonEmpty (\x -> x * 2) "# ), - &[2, 4, 6, 8, 10], + &[2, 4, 6, 8, 10, 2, 4, 6, 8, 10, 2, 4, 6, 8, 10, 2, 4, 6, 8, 10, 2, 4, 6, 8, 10], &'static [i64] ); + } + #[test] + fn list_map_with_type_change() { assert_evals_to!( indoc!( r#" - main = \{} -> - nonEmpty : List Int - nonEmpty = - [ 1, 1, -4, 1, 2 ] - - - List.map nonEmpty (\x -> x > 0) - - main {} + nonEmpty : List Int + nonEmpty = + [ 1, 1, -4, 1, 2 ] + + + List.map nonEmpty (\x -> x > 0) "# ), &[true, true, false, true, true], &'static [bool] ); + } + #[test] + fn list_map_using_defined_function() { assert_evals_to!( indoc!( r#" - main = \{} -> - nonEmpty : List Int - nonEmpty = - [ 1, 1, -4, 1, 2 ] - - greaterThanOne : Int -> Bool - greaterThanOne = \i -> - i > 0 - - List.map nonEmpty greaterThanOne - - main {} + nonEmpty : List Int + nonEmpty = + [ 2, 2, -4, 2, 3 ] + + greaterThanOne : Int -> Bool + greaterThanOne = \i -> + i > 1 + + List.map nonEmpty greaterThanOne "# ), &[true, true, false, true, true], &'static [bool] ); + } + #[test] + fn list_map_all_inline() { assert_evals_to!( indoc!( r#" - main = \{} -> - List.map [] (\x -> x > 0) - - main {} + List.map [] (\x -> x > 0) "# ), &[], From b3ce6041a3d1ac2c15a993ab2f69964e81fe52cd Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 16:56:35 -0400 Subject: [PATCH 02/13] Split List.join and List.concat --- compiler/gen/tests/gen_list.rs | 48 ++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index bacf1a17cd..50f18b2695 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -228,20 +228,35 @@ mod gen_list { } #[test] - fn list_join() { + fn list_join_empty_list() { assert_evals_to!("List.join []", &[], &'static [i64]); + } + + #[test] + fn list_join_one_list() { assert_evals_to!("List.join [ [1, 2, 3 ] ]", &[1, 2, 3], &'static [i64]); + } + + #[test] + fn list_join_two_non_empty_lists() { assert_evals_to!( "List.join [ [1, 2, 3 ] , [4 ,5, 6] ]", &[1, 2, 3, 4, 5, 6], &'static [i64] ); + } + + #[test] + fn list_join_two_non_empty_lists_of_float() { assert_evals_to!( "List.join [ [ 1.2, 1.1 ], [ 2.1, 2.2 ] ]", &[1.2, 1.1, 2.1, 2.2], &'static [f64] ); + } + #[test] + fn list_join_to_big_list() { assert_evals_to!( indoc!( r#" @@ -263,7 +278,10 @@ mod gen_list { ], &'static [f64] ); + } + #[test] + fn list_join_defined_empty_list() { assert_evals_to!( indoc!( r#" @@ -277,9 +295,15 @@ mod gen_list { &[0.2, 11.11], &'static [f64] ); + } + #[test] + fn list_join_all_empty_lists() { assert_evals_to!("List.join [ [], [], [] ]", &[], &'static [f64]); + } + #[test] + fn list_join_one_empty_list() { assert_evals_to!( "List.join [ [ 1.2, 1.1 ], [] ]", &[1.2, 1.1], @@ -329,6 +353,10 @@ mod gen_list { ); assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]); assert_evals_to!("List.reverse [4]", &[4], &'static [i64]); + } + + #[test] + fn list_reverse_empty_list_of_int() { assert_evals_to!( indoc!( r#" @@ -342,6 +370,10 @@ mod gen_list { &[], &'static [i64] ); + } + + #[test] + fn list_reverse_empty_list() { assert_evals_to!("List.reverse []", &[], &'static [i64]); } @@ -367,9 +399,12 @@ mod gen_list { } #[test] - fn list_concat_vanilla() { + fn list_concat_two_empty_lists() { assert_evals_to!("List.concat [] []", &[], &'static [i64]); + } + #[test] + fn list_concat_two_empty_lists_of_int() { assert_evals_to!( indoc!( r#" @@ -387,16 +422,25 @@ mod gen_list { &[], &'static [i64] ); + } + #[test] + fn list_concat_second_list_is_empty() { assert_evals_to!("List.concat [ 12, 13 ] []", &[12, 13], &'static [i64]); assert_evals_to!( "List.concat [ 34, 43 ] [ 64, 55, 66 ]", &[34, 43, 64, 55, 66], &'static [i64] ); + } + #[test] + fn list_concat_first_list_is_empty() { assert_evals_to!("List.concat [] [ 23, 24 ]", &[23, 24], &'static [i64]); + } + #[test] + fn list_concat_two_non_empty_lists() { assert_evals_to!( "List.concat [1, 2 ] [ 3, 4 ]", &[1, 2, 3, 4], From 01b3d2cf616b93c7d797def6853a97e9fcc92f9b Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 17:41:03 -0400 Subject: [PATCH 03/13] broke up list_prepend_tests --- compiler/gen/tests/gen_list.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 50f18b2695..5b90ff3073 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -94,12 +94,19 @@ mod gen_list { &[6, 4], &'static [i64] ); + } + #[test] + fn list_prepend_bools() { assert_evals_to!( "List.prepend [ True, False ] True", &[true, true, false], &'static [bool] ); + } + + #[test] + fn list_prepend_big_list() { assert_evals_to!( "List.prepend [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100 ] 9", &[9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100], From 8547b7de893e5de1c7df4187e6ab1f7bb90a2dff Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 17:41:12 -0400 Subject: [PATCH 04/13] Commented in first_empty_list_test --- compiler/gen/tests/gen_list.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 5b90ff3073..5820ea43aa 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -607,24 +607,20 @@ mod gen_list { ); } - // TODO getting this to work requires generating a runtime error for the Ok - // branch here, which is not yet something we support as of when this - // test was originally written. - // - // #[test] - // fn first_empty_list() { - // assert_evals_to!( - // indoc!( - // r#" - // when List.first [] is - // Ok val -> val - // Err _ -> -1 - // "# - // ), - // -1, - // i64 - // ); - // } + #[test] + fn first_empty_list() { + assert_evals_to!( + indoc!( + r#" + when List.first [] is + Ok val -> val + Err _ -> -1 + "# + ), + -1, + i64 + ); + } #[test] fn get_empty_list() { From d0587fa92cbedfa13f836d3e34c444d421828a86 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 17:46:27 -0400 Subject: [PATCH 05/13] Commented in some quick sort tests --- compiler/gen/tests/gen_list.rs | 288 ++++++++++++++++----------------- 1 file changed, 144 insertions(+), 144 deletions(-) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 5820ea43aa..64384da15f 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -1028,151 +1028,151 @@ mod gen_list { }) } - // #[test] - // fn foobar2() { - // with_larger_debug_stack(|| { - // assert_evals_to!( - // indoc!( - // r#" - // quicksort : List (Num a) -> List (Num a) - // quicksort = \list -> - // quicksortHelp list 0 (List.len list - 1) - // - // - // quicksortHelp : List (Num a), Int, Int -> List (Num a) - // quicksortHelp = \list, low, high -> - // if low < high then - // when partition low high list is - // Pair partitionIndex partitioned -> - // partitioned - // |> quicksortHelp low (partitionIndex - 1) - // |> quicksortHelp (partitionIndex + 1) high - // else - // list - // - // - // swap : Int, Int, List a -> List a - // swap = \i, j, list -> - // when Pair (List.get list i) (List.get list j) is - // Pair (Ok atI) (Ok atJ) -> - // list - // |> List.set i atJ - // |> List.set j atI - // - // _ -> - // [] - // - // partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ] - // partition = \low, high, initialList -> - // when List.get initialList high is - // Ok pivot -> - // when partitionHelp (low - 1) low initialList high pivot is - // Pair newI newList -> - // Pair (newI + 1) (swap (newI + 1) high newList) - // - // Err _ -> - // Pair (low - 1) initialList - // - // - // partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ] - // partitionHelp = \i, j, list, high, pivot -> - // # if j < high then - // if False then - // when List.get list j is - // Ok value -> - // if value <= pivot then - // partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot - // else - // partitionHelp i (j + 1) list high pivot - // - // Err _ -> - // Pair i list - // else - // Pair i list - // - // - // - // quicksort [ 7, 4, 21, 19 ] - // "# - // ), - // &[19, 7, 4, 21], - // &'static [i64], - // ); - // }) - // } + #[test] + fn foobar2() { + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" + quicksort : List (Num a) -> List (Num a) + quicksort = \list -> + quicksortHelp list 0 (List.len list - 1) + + + quicksortHelp : List (Num a), Int, Int -> List (Num a) + quicksortHelp = \list, low, high -> + if low < high then + when partition low high list is + Pair partitionIndex partitioned -> + partitioned + |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp (partitionIndex + 1) high + else + list + + + swap : Int, Int, List a -> List a + swap = \i, j, list -> + when Pair (List.get list i) (List.get list j) is + Pair (Ok atI) (Ok atJ) -> + list + |> List.set i atJ + |> List.set j atI + + _ -> + [] + + partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ] + partition = \low, high, initialList -> + when List.get initialList high is + Ok pivot -> + when partitionHelp (low - 1) low initialList high pivot is + Pair newI newList -> + Pair (newI + 1) (swap (newI + 1) high newList) + + Err _ -> + Pair (low - 1) initialList + + + partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ] + partitionHelp = \i, j, list, high, pivot -> + # if j < high then + if False then + when List.get list j is + Ok value -> + if value <= pivot then + partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + else + partitionHelp i (j + 1) list high pivot + + Err _ -> + Pair i list + else + Pair i list + + + + quicksort [ 7, 4, 21, 19 ] + "# + ), + &[19, 7, 4, 21], + &'static [i64] + ); + }) + } - // #[test] - // fn foobar() { - // with_larger_debug_stack(|| { - // assert_evals_to!( - // indoc!( - // r#" - // quicksort : List (Num a) -> List (Num a) - // quicksort = \list -> - // quicksortHelp list 0 (List.len list - 1) - // - // - // quicksortHelp : List (Num a), Int, Int -> List (Num a) - // quicksortHelp = \list, low, high -> - // if low < high then - // when partition low high list is - // Pair partitionIndex partitioned -> - // partitioned - // |> quicksortHelp low (partitionIndex - 1) - // |> quicksortHelp (partitionIndex + 1) high - // else - // list - // - // - // swap : Int, Int, List a -> List a - // swap = \i, j, list -> - // when Pair (List.get list i) (List.get list j) is - // Pair (Ok atI) (Ok atJ) -> - // list - // |> List.set i atJ - // |> List.set j atI - // - // _ -> - // [] - // - // partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ] - // partition = \low, high, initialList -> - // when List.get initialList high is - // Ok pivot -> - // when partitionHelp (low - 1) low initialList high pivot is - // Pair newI newList -> - // Pair (newI + 1) (swap (newI + 1) high newList) - // - // Err _ -> - // Pair (low - 1) initialList - // - // - // partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ] - // partitionHelp = \i, j, list, high, pivot -> - // if j < high then - // when List.get list j is - // Ok value -> - // if value <= pivot then - // partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot - // else - // partitionHelp i (j + 1) list high pivot - // - // Err _ -> - // Pair i list - // else - // Pair i list - // - // - // - // when List.first (quicksort [0x1]) is - // _ -> 4 - // "# - // ), - // 4, - // i64, - // ); - // }) - // } + #[test] + fn foobar() { + with_larger_debug_stack(|| { + assert_evals_to!( + indoc!( + r#" + quicksort : List (Num a) -> List (Num a) + quicksort = \list -> + quicksortHelp list 0 (List.len list - 1) + + + quicksortHelp : List (Num a), Int, Int -> List (Num a) + quicksortHelp = \list, low, high -> + if low < high then + when partition low high list is + Pair partitionIndex partitioned -> + partitioned + |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp (partitionIndex + 1) high + else + list + + + swap : Int, Int, List a -> List a + swap = \i, j, list -> + when Pair (List.get list i) (List.get list j) is + Pair (Ok atI) (Ok atJ) -> + list + |> List.set i atJ + |> List.set j atI + + _ -> + [] + + partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ] + partition = \low, high, initialList -> + when List.get initialList high is + Ok pivot -> + when partitionHelp (low - 1) low initialList high pivot is + Pair newI newList -> + Pair (newI + 1) (swap (newI + 1) high newList) + + Err _ -> + Pair (low - 1) initialList + + + partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ] + partitionHelp = \i, j, list, high, pivot -> + if j < high then + when List.get list j is + Ok value -> + if value <= pivot then + partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + else + partitionHelp i (j + 1) list high pivot + + Err _ -> + Pair i list + else + Pair i list + + + + when List.first (quicksort [0x1]) is + _ -> 4 + "# + ), + 4, + i64 + ); + }) + } #[test] fn empty_list_increment_decrement() { From a0a00d3521956c884dbf7d66a404d66ea7af7709 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 19:56:42 -0400 Subject: [PATCH 06/13] Store list helper function --- compiler/gen/src/llvm/build_list.rs | 252 +++++----------------------- 1 file changed, 46 insertions(+), 206 deletions(-) diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 3db640e40d..e81b680736 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -35,35 +35,7 @@ pub fn list_single<'a, 'ctx, 'env>( builder.build_store(elem_ptr, elem); - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false)); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - // - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, ptr, env.ptr_int().const_int(1, false)) } /// List.repeat : Int, elem -> List elem @@ -138,33 +110,7 @@ pub fn list_repeat<'a, 'ctx, 'env>( builder.build_conditional_branch(end_cond, loop_bb, after_bb); builder.position_at_end(after_bb); - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, list_ptr, list_len) }; let build_else = || empty_polymorphic_list(env); @@ -204,12 +150,8 @@ pub fn list_prepend<'a, 'ctx, 'env>( "new_list_length", ); - let ptr_bytes = env.ptr_bytes; - // Allocate space for the new array that we'll copy into. let clone_ptr = allocate_list(env, elem_layout, new_list_len); - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr"); builder.build_store(clone_ptr, elem); @@ -232,6 +174,8 @@ pub fn list_prepend<'a, 'ctx, 'env>( .builder .build_int_mul(elem_bytes, len, "mul_old_len_by_elem_bytes"); + let ptr_bytes = env.ptr_bytes; + if elem_layout.safe_to_memcpy() { // Copy the bytes from the original array into the new // one we just malloc'd. @@ -242,30 +186,7 @@ pub fn list_prepend<'a, 'ctx, 'env>( panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); } - // Create a fresh wrapper struct for the newly populated array - let struct_type = collection(ctx, env.ptr_bytes); - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, new_list_len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, clone_ptr, new_list_len) } /// List.join : List (List elem) -> List elem @@ -443,39 +364,7 @@ pub fn list_join<'a, 'ctx, 'env>( inner_list_loop, ); - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = - builder.build_ptr_to_int(final_list_ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - final_list_sum, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, final_list_ptr, final_list_sum) }; let build_else = || empty_list(env); @@ -723,8 +612,6 @@ pub fn list_append<'a, 'ctx, 'env>( // Allocate space for the new array that we'll copy into. let clone_ptr = allocate_list(env, elem_layout, new_list_len); - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr"); // TODO check if malloc returned null; if so, runtime error for OOM! @@ -738,34 +625,11 @@ pub fn list_append<'a, 'ctx, 'env>( panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); } - // Create a fresh wrapper struct for the newly populated array - let struct_type = collection(ctx, env.ptr_bytes); - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, new_list_len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - let elem_ptr = unsafe { builder.build_in_bounds_gep(clone_ptr, &[list_len], "load_index") }; builder.build_store(elem_ptr, elem); - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, clone_ptr, new_list_len) } /// List.set : List elem, Int, elem -> List elem @@ -932,35 +796,7 @@ pub fn list_map<'a, 'ctx, 'env>( builder, parent, ctx, len, "#index", None, list_loop, ); - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = - builder.build_ptr_to_int(ret_list_ptr, int_type, "list_cast_ptr"); - - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, ret_list_ptr, len) }; build_basic_phi2( @@ -1171,40 +1007,7 @@ pub fn list_concat<'a, 'ctx, 'env>( second_loop, ); - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = - builder.build_ptr_to_int(combined_list_ptr, int_type, "list_cast_ptr"); - - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - combined_list_len, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, combined_list_ptr, combined_list_len) }; build_basic_phi2( @@ -1510,3 +1313,40 @@ pub fn allocate_list<'a, 'ctx, 'env>( list_element_ptr } + +fn store_list<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + list_ptr: PointerValue<'ctx>, + len: IntValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let ctx = env.context; + let builder = env.builder; + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr"); + let struct_type = collection(ctx, ptr_bytes); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) +} From a83891011d6e7849f547d90af01a44d067cb9c02 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 19:56:55 -0400 Subject: [PATCH 07/13] if_non_empty helper function --- compiler/gen/src/llvm/build_list.rs | 236 +++++++++++++--------------- 1 file changed, 112 insertions(+), 124 deletions(-) diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index e81b680736..0f32b0abb6 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -396,149 +396,86 @@ pub fn list_reverse<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { let (_, list_layout) = load_symbol_and_layout(env, scope, list); - match list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - - Layout::Builtin(Builtin::List(_, elem_layout)) => { - let wrapper_struct = load_symbol(env, scope, list).into_struct_value(); - + let non_empty_fn = + |elem_layout: &Layout<'a>, len: IntValue<'ctx>, wrapper_struct: StructValue<'ctx>| { let builder = env.builder; let ctx = env.context; - let len = list_len(builder, wrapper_struct); + // Allocate space for the new array that we'll copy into. + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - // list_len > 0 - // We do this check to avoid allocating memory. If the input - // list is empty, then we can just return an empty list. - let comparison = builder.build_int_compare( - IntPredicate::UGT, - len, - ctx.i64_type().const_int(0, false), - "greaterthanzero", - ); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - let build_then = || { - // Allocate space for the new array that we'll copy into. - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let reversed_list_ptr = allocate_list(env, elem_layout, len); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + // TODO check if malloc returned null; if so, runtime error for OOM! - let reversed_list_ptr = allocate_list(env, elem_layout, len); + let index_name = "#index"; + let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); - // TODO check if malloc returned null; if so, runtime error for OOM! + // Start at the last element in the list. + let last_elem_index = + builder.build_int_sub(len, ctx.i64_type().const_int(1, false), "lastelemindex"); + builder.build_store(start_alloca, last_elem_index); - let index_name = "#index"; - let start_alloca = builder.build_alloca(ctx.i64_type(), index_name); + let loop_bb = ctx.append_basic_block(parent, "loop"); + builder.build_unconditional_branch(loop_bb); + builder.position_at_end(loop_bb); - // Start at the last element in the list. - let last_elem_index = - builder.build_int_sub(len, ctx.i64_type().const_int(1, false), "lastelemindex"); - builder.build_store(start_alloca, last_elem_index); + // #index = #index - 1 + let curr_index = builder + .build_load(start_alloca, index_name) + .into_int_value(); + let next_index = + builder.build_int_sub(curr_index, ctx.i64_type().const_int(1, false), "nextindex"); - let loop_bb = ctx.append_basic_block(parent, "loop"); - builder.build_unconditional_branch(loop_bb); - builder.position_at_end(loop_bb); + builder.build_store(start_alloca, next_index); - // #index = #index - 1 - let curr_index = builder - .build_load(start_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_sub( - curr_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); + let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); - builder.build_store(start_alloca, next_index); + // The pointer to the element in the input list + let elem_ptr = + unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") }; - let list_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); - - // The pointer to the element in the input list - let elem_ptr = - unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") }; - - // The pointer to the element in the reversed list - let reverse_elem_ptr = unsafe { - builder.build_in_bounds_gep( - reversed_list_ptr, - &[builder.build_int_sub( - len, - builder.build_int_add( - curr_index, - ctx.i64_type().const_int(1, false), - "curr_index_plus_one", - ), - "next_index", - )], - "load_index_reversed_list", - ) - }; - - let elem = builder.build_load(elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(reverse_elem_ptr, elem); - - // #index != 0 - let end_cond = builder.build_int_compare( - IntPredicate::NE, - ctx.i64_type().const_int(0, false), - curr_index, - "loopcond", - ); - - let after_bb = ctx.append_basic_block(parent, "afterloop"); - - builder.build_conditional_branch(end_cond, loop_bb, after_bb); - builder.position_at_end(after_bb); - - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = - builder.build_ptr_to_int(reversed_list_ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", + // The pointer to the element in the reversed list + let reverse_elem_ptr = unsafe { + builder.build_in_bounds_gep( + reversed_list_ptr, + &[builder.build_int_sub( + len, + builder.build_int_add( + curr_index, + ctx.i64_type().const_int(1, false), + "curr_index_plus_one", + ), + "next_index", + )], + "load_index_reversed_list", ) }; - let build_else = || empty_list(env); + let elem = builder.build_load(elem_ptr, "get_elem"); - let struct_type = collection(ctx, env.ptr_bytes); + // Mutate the new array in-place to change the element. + builder.build_store(reverse_elem_ptr, elem); - build_basic_phi2( - env, - parent, - comparison, - build_then, - build_else, - BasicTypeEnum::StructType(struct_type), - ) - } - _ => { - unreachable!("Invalid List layout for List.reverse {:?}", list_layout); - } - } + // #index != 0 + let end_cond = builder.build_int_compare( + IntPredicate::NE, + ctx.i64_type().const_int(0, false), + curr_index, + "loopcond", + ); + + let after_bb = ctx.append_basic_block(parent, "afterloop"); + + builder.build_conditional_branch(end_cond, loop_bb, after_bb); + builder.position_at_end(after_bb); + + store_list(env, reversed_list_ptr, len) + }; + + if_non_empty(env, parent, scope, non_empty_fn, list, list_layout) } pub fn list_get_unsafe<'a, 'ctx, 'env>( @@ -1090,6 +1027,57 @@ where index_alloca } +fn if_non_empty<'a, 'ctx, 'env, 'b, NonEmptyFn>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + scope: &Scope<'a, 'ctx>, + mut build_non_empty: NonEmptyFn, + list: &Symbol, + list_layout: &'b Layout<'a>, +) -> BasicValueEnum<'ctx> +where + NonEmptyFn: FnMut(&Layout<'a>, IntValue<'ctx>, StructValue<'ctx>) -> BasicValueEnum<'ctx>, +{ + match list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + + Layout::Builtin(Builtin::List(_, elem_layout)) => { + let wrapper_struct = load_symbol(env, scope, list).into_struct_value(); + + let builder = env.builder; + let ctx = env.context; + + let len = list_len(builder, wrapper_struct); + + // list_len > 0 + // We do this check to avoid allocating memory. If the input + // list is empty, then we can just return an empty list. + let comparison = builder.build_int_compare( + IntPredicate::UGT, + len, + ctx.i64_type().const_int(0, false), + "greaterthanzero", + ); + + let build_empty = || empty_list(env); + + let struct_type = collection(ctx, env.ptr_bytes); + + build_basic_phi2( + env, + parent, + comparison, + || build_non_empty(elem_layout, len, wrapper_struct), + build_empty, + BasicTypeEnum::StructType(struct_type), + ) + } + _ => { + unreachable!("Invalid List layout for List.reverse {:?}", list_layout); + } + } +} + pub fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, From dedbd5b3bde7679456f102e37ec0f2725944ff3a Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 20:00:23 -0400 Subject: [PATCH 08/13] partial work on using if_non_empty in list_map --- compiler/gen/src/llvm/build_list.rs | 104 +++++++++------------------- 1 file changed, 33 insertions(+), 71 deletions(-) diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 0f32b0abb6..1488818885 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -667,88 +667,50 @@ pub fn list_map<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { match (func, func_layout) { (BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => { - match list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(_, elem_layout)) => { - let ctx = env.context; - let builder = env.builder; + let non_empty_fn = || { + let ctx = env.context; + let builder = env.builder; - let list_wrapper = list.into_struct_value(); + let ret_list_ptr = allocate_list(env, ret_elem_layout, len); - let len = list_len(builder, list_wrapper); + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - // len > 0 - // We do this check to avoid allocating memory. If the input - // list is empty, then we can just return an empty list. - let list_length_comparison = list_is_not_empty(builder, ctx, len); + let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type); - let if_list_is_empty = || empty_list(env); + let list_loop = |index| { + // The pointer to the element in the input list + let before_elem_ptr = + unsafe { builder.build_in_bounds_gep(list_ptr, &[index], "load_index") }; - let if_list_is_not_empty = || { - let ret_list_ptr = allocate_list(env, ret_elem_layout, len); + let before_elem = builder.build_load(before_elem_ptr, "get_before_elem"); - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + let call_site_value = + builder.build_call(func_ptr, env.arena.alloc([before_elem]), "map_func"); - let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type); + // set the calling convention explicitly for this call + call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV); - let list_loop = |index| { - // The pointer to the element in the input list - let before_elem_ptr = unsafe { - builder.build_in_bounds_gep(list_ptr, &[index], "load_index") - }; + let after_elem = call_site_value + .try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")); - let before_elem = - builder.build_load(before_elem_ptr, "get_before_elem"); - - let call_site_value = builder.build_call( - func_ptr, - env.arena.alloc([before_elem]), - "map_func", - ); - - // set the calling convention explicitly for this call - call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV); - - let after_elem = call_site_value - .try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")); - - // The pointer to the element in the mapped-over list - let after_elem_ptr = unsafe { - builder.build_in_bounds_gep( - ret_list_ptr, - &[index], - "load_index_after_list", - ) - }; - - // Mutate the new array in-place to change the element. - builder.build_store(after_elem_ptr, after_elem); - }; - - incrementing_index_loop( - builder, parent, ctx, len, "#index", None, list_loop, - ); - - store_list(env, ret_list_ptr, len) + // The pointer to the element in the mapped-over list + let after_elem_ptr = unsafe { + builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list") }; - build_basic_phi2( - env, - parent, - list_length_comparison, - if_list_is_not_empty, - if_list_is_empty, - BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), - ) - } - _ => { - unreachable!("Invalid List layout for List.map : {:?}", list_layout); - } - } + // Mutate the new array in-place to change the element. + builder.build_store(after_elem_ptr, after_elem); + }; + + incrementing_index_loop(builder, parent, ctx, len, "#index", None, list_loop); + + store_list(env, ret_list_ptr, len) + }; + + if_non_empty(env, parent, scope, non_empty_fn, list, list_layout) } _ => { unreachable!( From f2a8065adcb0398497f4e8ad778968dfe693dfb7 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 21:56:52 -0400 Subject: [PATCH 09/13] Moved over to incrementing_elem_loop from incrementing_index_loop --- compiler/gen/src/llvm/build.rs | 73 +++--------- compiler/gen/src/llvm/build_list.rs | 167 ++++++++++++++-------------- 2 files changed, 95 insertions(+), 145 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 3b71227f9c..07174597ae 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1,9 +1,9 @@ use crate::layout_id::LayoutIds; use crate::llvm::build_list::{ allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list, - incrementing_index_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty, + incrementing_elem_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty, list_join, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set, list_single, - load_list_ptr, + load_list_ptr, store_list, }; use crate::llvm::compare::{build_eq, build_neq}; use crate::llvm::convert::{ @@ -1674,9 +1674,9 @@ fn run_low_level<'a, 'ctx, 'env>( // List.reverse : List elem -> List elem debug_assert_eq!(args.len(), 1); - let list = &args[0]; + let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]); - list_reverse(env, parent, scope, list) + list_reverse(env, parent, list, list_layout) } ListConcat => { debug_assert_eq!(args.len(), 2); @@ -1719,9 +1719,8 @@ fn run_low_level<'a, 'ctx, 'env>( debug_assert_eq!(args.len(), 1); let (list, outer_list_layout) = load_symbol_and_layout(env, scope, &args[0]); - let outer_wrapper_struct = list.into_struct_value(); - list_join(env, parent, outer_wrapper_struct, outer_list_layout) + list_join(env, parent, list, outer_list_layout) } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => { debug_assert_eq!(args.len(), 1); @@ -2000,14 +1999,9 @@ fn str_concat<'a, 'ctx, 'env>( let combined_str_ptr = allocate_list(env, &CHAR_LAYOUT, combined_str_len); // FIRST LOOP - let first_loop = |first_index| { - let first_str_ptr = load_list_ptr(builder, first_str_wrapper, ptr_type); - - // The pointer to the element in the first list - let first_str_char_ptr = unsafe { - builder.build_in_bounds_gep(first_str_ptr, &[first_index], "load_index") - }; + let first_str_ptr = load_list_ptr(builder, first_str_wrapper, ptr_type); + let first_loop = |first_index, first_str_elem| { // The pointer to the element in the combined list let combined_str_elem_ptr = unsafe { builder.build_in_bounds_gep( @@ -2017,18 +2011,17 @@ fn str_concat<'a, 'ctx, 'env>( ) }; - let first_str_elem = builder.build_load(first_str_char_ptr, "get_elem"); - // Mutate the new array in-place to change the element. builder.build_store(combined_str_elem_ptr, first_str_elem); }; let index_name = "#index"; - let index_alloca = incrementing_index_loop( + let index_alloca = incrementing_elem_loop( builder, parent, ctx, + first_str_ptr, first_str_len, index_name, None, @@ -2039,14 +2032,9 @@ fn str_concat<'a, 'ctx, 'env>( builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); // SECOND LOOP - let second_loop = |second_index| { - let second_str_ptr = load_list_ptr(builder, second_str_wrapper, ptr_type); - - // The pointer to the element in the second list - let second_str_char_ptr = unsafe { - builder.build_in_bounds_gep(second_str_ptr, &[second_index], "load_index") - }; + let second_str_ptr = load_list_ptr(builder, second_str_wrapper, ptr_type); + let second_loop = |second_index, second_str_elem| { // The pointer to the element in the combined str. // Note that the pointer does not start at the index // 0, it starts at the index of first_str_len. In that @@ -2065,55 +2053,22 @@ fn str_concat<'a, 'ctx, 'env>( ) }; - let second_str_elem = builder.build_load(second_str_char_ptr, "get_elem"); - // Mutate the new array in-place to change the element. builder.build_store(combined_str_char_ptr, second_str_elem); }; - incrementing_index_loop( + incrementing_elem_loop( builder, parent, ctx, + second_str_ptr, second_str_len, index_name, Some(index_alloca), second_loop, ); - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(combined_str_ptr, int_type, "list_cast_ptr"); - - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - combined_str_len, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + store_list(env, combined_str_ptr, combined_str_len) }; build_basic_phi2( diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 1488818885..a17facd15a 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -1,11 +1,10 @@ -use crate::llvm::build::{load_symbol, load_symbol_and_layout, Env, InPlace, Scope}; +use crate::llvm::build::{Env, InPlace}; use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int}; use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::types::{BasicTypeEnum, PointerType}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::{AddressSpace, IntPredicate}; -use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, MemoryMode}; /// List.single : a -> List a @@ -193,7 +192,7 @@ pub fn list_prepend<'a, 'ctx, 'env>( pub fn list_join<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, - outer_list_wrapper: StructValue<'ctx>, + outer_list: BasicValueEnum<'ctx>, outer_list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { // List.join is implemented as follows: @@ -221,6 +220,7 @@ pub fn list_join<'a, 'ctx, 'env>( let inner_list_type = basic_type_from_layout(env.arena, ctx, &inner_list_layout, env.ptr_bytes); + let outer_list_wrapper = outer_list.into_struct_value(); let outer_list_len = list_len(builder, outer_list_wrapper); let outer_list_ptr = { let elem_ptr_type = get_ptr_type(&inner_list_type, AddressSpace::Generic); @@ -240,12 +240,7 @@ pub fn list_join<'a, 'ctx, 'env>( builder.build_store(list_len_sum_alloca, ctx.i64_type().const_int(0, false)); // List Sum Loop - let sum_loop = |sum_index| { - let inner_list_wrapper_ptr = unsafe { - builder.build_in_bounds_gep(outer_list_ptr, &[sum_index], "load_index") - }; - - let inner_list = builder.build_load(inner_list_wrapper_ptr, "inner_list"); + let sum_loop = |_, inner_list: BasicValueEnum<'ctx>| { let inner_list_len = list_len(builder, inner_list.into_struct_value()); let next_list_sum = builder.build_int_add( @@ -259,10 +254,11 @@ pub fn list_join<'a, 'ctx, 'env>( builder.build_store(list_len_sum_alloca, next_list_sum); }; - incrementing_index_loop( + incrementing_elem_loop( builder, parent, ctx, + outer_list_ptr, outer_list_len, "#sum_index", None, @@ -280,16 +276,8 @@ pub fn list_join<'a, 'ctx, 'env>( builder.build_store(dest_elem_ptr_alloca, final_list_ptr); // Inner List Loop - let inner_list_loop = |index| { - let inner_list_wrapper = { - let wrapper_ptr = unsafe { - builder.build_in_bounds_gep(outer_list_ptr, &[index], "load_index") - }; - - builder - .build_load(wrapper_ptr, "inner_list_wrapper") - .into_struct_value() - }; + let inner_list_loop = |_, inner_list: BasicValueEnum<'ctx>| { + let inner_list_wrapper = inner_list.into_struct_value(); let inner_list_len = list_len(builder, inner_list_wrapper); @@ -307,20 +295,10 @@ pub fn list_join<'a, 'ctx, 'env>( ); builder.position_at_end(inner_list_non_empty_block); + let inner_list_ptr = load_list_ptr(builder, inner_list_wrapper, elem_ptr_type); + // Element Inserting Loop - let inner_elem_loop = |inner_index| { - let src_elem_ptr = unsafe { - let inner_list_ptr = - load_list_ptr(builder, inner_list_wrapper, elem_ptr_type); - - builder.build_in_bounds_gep( - inner_list_ptr, - &[inner_index], - "load_index", - ) - }; - - let src_elem = builder.build_load(src_elem_ptr, "get_elem"); + let inner_elem_loop = |_, src_elem| { // TODO clone src_elem let curr_dest_elem_ptr = builder @@ -340,10 +318,11 @@ pub fn list_join<'a, 'ctx, 'env>( builder.build_store(dest_elem_ptr_alloca, inc_dest_elem_ptr); }; - incrementing_index_loop( + incrementing_elem_loop( builder, parent, ctx, + inner_list_ptr, inner_list_len, "#inner_index", None, @@ -354,10 +333,11 @@ pub fn list_join<'a, 'ctx, 'env>( builder.position_at_end(after_inner_list_non_empty_block); }; - incrementing_index_loop( + incrementing_elem_loop( builder, parent, ctx, + outer_list_ptr, outer_list_len, "#inner_list_index", None, @@ -391,11 +371,9 @@ pub fn list_join<'a, 'ctx, 'env>( pub fn list_reverse<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, - scope: &Scope<'a, 'ctx>, - list: &Symbol, + list: BasicValueEnum<'ctx>, + list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - let (_, list_layout) = load_symbol_and_layout(env, scope, list); - let non_empty_fn = |elem_layout: &Layout<'a>, len: IntValue<'ctx>, wrapper_struct: StructValue<'ctx>| { let builder = env.builder; @@ -475,7 +453,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( store_list(env, reversed_list_ptr, len) }; - if_non_empty(env, parent, scope, non_empty_fn, list, list_layout) + if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout) } pub fn list_get_unsafe<'a, 'ctx, 'env>( @@ -667,7 +645,9 @@ pub fn list_map<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { match (func, func_layout) { (BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => { - let non_empty_fn = || { + let non_empty_fn = |elem_layout: &Layout<'a>, + len: IntValue<'ctx>, + list_wrapper: StructValue<'ctx>| { let ctx = env.context; let builder = env.builder; @@ -678,12 +658,8 @@ pub fn list_map<'a, 'ctx, 'env>( let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type); - let list_loop = |index| { + let list_loop = |index, before_elem| { // The pointer to the element in the input list - let before_elem_ptr = - unsafe { builder.build_in_bounds_gep(list_ptr, &[index], "load_index") }; - - let before_elem = builder.build_load(before_elem_ptr, "get_before_elem"); let call_site_value = builder.build_call(func_ptr, env.arena.alloc([before_elem]), "map_func"); @@ -705,12 +681,14 @@ pub fn list_map<'a, 'ctx, 'env>( builder.build_store(after_elem_ptr, after_elem); }; - incrementing_index_loop(builder, parent, ctx, len, "#index", None, list_loop); + incrementing_elem_loop( + builder, parent, ctx, list_ptr, len, "#index", None, list_loop, + ); store_list(env, ret_list_ptr, len) }; - if_non_empty(env, parent, scope, non_empty_fn, list, list_layout) + if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout) } _ => { unreachable!( @@ -815,16 +793,7 @@ pub fn list_concat<'a, 'ctx, 'env>( let first_list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type); // FIRST LOOP - let first_loop = |first_index| { - // The pointer to the element in the first list - let first_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - first_list_ptr, - &[first_index], - "load_index", - ) - }; - + let first_loop = |first_index, first_list_elem| { // The pointer to the element in the combined list let combined_list_elem_ptr = unsafe { builder.build_in_bounds_gep( @@ -834,18 +803,17 @@ pub fn list_concat<'a, 'ctx, 'env>( ) }; - let first_list_elem = builder.build_load(first_list_elem_ptr, "get_elem"); - // Mutate the new array in-place to change the element. builder.build_store(combined_list_elem_ptr, first_list_elem); }; let index_name = "#index"; - let index_alloca = incrementing_index_loop( + let index_alloca = incrementing_elem_loop( builder, parent, ctx, + first_list_ptr, first_list_len, index_name, None, @@ -855,19 +823,10 @@ pub fn list_concat<'a, 'ctx, 'env>( // Reset the index variable to 0 builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); + let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type); + // SECOND LOOP - let second_loop = |second_index| { - let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type); - - // The pointer to the element in the second list - let second_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - second_list_ptr, - &[second_index], - "load_index", - ) - }; - + let second_loop = |second_index, second_list_elem| { // The pointer to the element in the combined list. // Note that the pointer does not start at the index // 0, it starts at the index of first_list_len. In that @@ -890,16 +849,15 @@ pub fn list_concat<'a, 'ctx, 'env>( ) }; - let second_list_elem = builder.build_load(second_list_elem_ptr, "get_elem"); - // Mutate the new array in-place to change the element. builder.build_store(combined_list_elem_ptr, second_list_elem); }; - incrementing_index_loop( + incrementing_elem_loop( builder, parent, ctx, + second_list_ptr, second_list_len, index_name, Some(index_alloca), @@ -939,7 +897,40 @@ pub fn list_concat<'a, 'ctx, 'env>( // This helper simulates a basic for loop, where // and index increments up from 0 to some end value -pub fn incrementing_index_loop<'ctx, LoopFn>( +pub fn incrementing_elem_loop<'ctx, LoopFn>( + builder: &Builder<'ctx>, + parent: FunctionValue<'ctx>, + ctx: &'ctx Context, + list_ptr: PointerValue<'ctx>, + end: IntValue<'ctx>, + index_name: &str, + maybe_alloca: Option>, + mut loop_fn: LoopFn, +) -> PointerValue<'ctx> +where + LoopFn: FnMut(IntValue<'ctx>, BasicValueEnum<'ctx>), +{ + incrementing_index_loop( + builder, + parent, + ctx, + end, + index_name, + maybe_alloca, + |index| { + // The pointer to the element in the list + let elem_ptr = unsafe { builder.build_in_bounds_gep(list_ptr, &[index], "load_index") }; + + let elem = builder.build_load(elem_ptr, "get_elem"); + + loop_fn(index, elem); + }, + ) +} + +// This helper simulates a basic for loop, where +// and index increments up from 0 to some end value +fn incrementing_index_loop<'ctx, LoopFn>( builder: &Builder<'ctx>, parent: FunctionValue<'ctx>, ctx: &'ctx Context, @@ -989,13 +980,19 @@ where index_alloca } -fn if_non_empty<'a, 'ctx, 'env, 'b, NonEmptyFn>( +// In many cases we dont want to do anything if the +// builtin was given an empty list. This is because +// allocating memory for a list is costly, so its +// better to skip if it we can. Furthermore, checking +// if a list is empty requires both seeing if the list +// is a NonEmpty layout and if its a List(elem_layout) +// but with a length of 0. +fn if_list_is_not_empty<'a, 'ctx, 'env, 'b, NonEmptyFn>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, - scope: &Scope<'a, 'ctx>, mut build_non_empty: NonEmptyFn, - list: &Symbol, - list_layout: &'b Layout<'a>, + list: BasicValueEnum<'ctx>, + list_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> where NonEmptyFn: FnMut(&Layout<'a>, IntValue<'ctx>, StructValue<'ctx>) -> BasicValueEnum<'ctx>, @@ -1004,16 +1001,14 @@ where Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::List(_, elem_layout)) => { - let wrapper_struct = load_symbol(env, scope, list).into_struct_value(); - let builder = env.builder; let ctx = env.context; + let wrapper_struct = list.into_struct_value(); + let len = list_len(builder, wrapper_struct); // list_len > 0 - // We do this check to avoid allocating memory. If the input - // list is empty, then we can just return an empty list. let comparison = builder.build_int_compare( IntPredicate::UGT, len, @@ -1264,7 +1259,7 @@ pub fn allocate_list<'a, 'ctx, 'env>( list_element_ptr } -fn store_list<'a, 'ctx, 'env>( +pub fn store_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, list_ptr: PointerValue<'ctx>, len: IntValue<'ctx>, From 257cb885111db0e26c225d2aab10e027e82849ce Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 22:31:14 -0400 Subject: [PATCH 10/13] Added name parameter to non_emptylist_function --- compiler/gen/src/llvm/build_list.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index a17facd15a..f2fdd22dad 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -453,7 +453,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( store_list(env, reversed_list_ptr, len) }; - if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout) + if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.reverse") } pub fn list_get_unsafe<'a, 'ctx, 'env>( @@ -688,7 +688,7 @@ pub fn list_map<'a, 'ctx, 'env>( store_list(env, ret_list_ptr, len) }; - if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout) + if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map") } _ => { unreachable!( @@ -993,6 +993,7 @@ fn if_list_is_not_empty<'a, 'ctx, 'env, 'b, NonEmptyFn>( mut build_non_empty: NonEmptyFn, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, + list_fn_name: &str, ) -> BasicValueEnum<'ctx> where NonEmptyFn: FnMut(&Layout<'a>, IntValue<'ctx>, StructValue<'ctx>) -> BasicValueEnum<'ctx>, @@ -1030,7 +1031,7 @@ where ) } _ => { - unreachable!("Invalid List layout for List.reverse {:?}", list_layout); + unreachable!("Invalid List layout for {} {:?}", list_fn_name, list_layout); } } } From 379235fd7efe146e49fbb5efe930e64ff5705350 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 22:44:56 -0400 Subject: [PATCH 11/13] Improved comment --- compiler/gen/src/llvm/build_list.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index f2fdd22dad..ba0984e9fb 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -980,13 +980,10 @@ where index_alloca } -// In many cases we dont want to do anything if the -// builtin was given an empty list. This is because -// allocating memory for a list is costly, so its -// better to skip if it we can. Furthermore, checking -// if a list is empty requires both seeing if the list -// is a NonEmpty layout and if its a List(elem_layout) -// but with a length of 0. +// This function checks if the list is empty, and +// if it is, it returns an empty list, and if not +// it runs whatever code is passed in under `build_non_empty` +// This is the avoid allocating memory if the list is empty. fn if_list_is_not_empty<'a, 'ctx, 'env, 'b, NonEmptyFn>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, From 08dba946e6867e8829c0eebc0b77e48ae1a468f1 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 23:03:56 -0400 Subject: [PATCH 12/13] Use struct parameter --- compiler/gen/src/llvm/build.rs | 14 +++++--- compiler/gen/src/llvm/build_list.rs | 52 +++++++++++++++++++---------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 07174597ae..34f512406a 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -3,7 +3,7 @@ use crate::llvm::build_list::{ allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list, incrementing_elem_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty, list_join, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set, list_single, - load_list_ptr, store_list, + load_list_ptr, store_list, LoopListArg, }; use crate::llvm::compare::{build_eq, build_neq}; use crate::llvm::convert::{ @@ -2021,8 +2021,10 @@ fn str_concat<'a, 'ctx, 'env>( builder, parent, ctx, - first_str_ptr, - first_str_len, + LoopListArg { + ptr: first_str_ptr, + len: first_str_len, + }, index_name, None, first_loop, @@ -2061,8 +2063,10 @@ fn str_concat<'a, 'ctx, 'env>( builder, parent, ctx, - second_str_ptr, - second_str_len, + LoopListArg { + ptr: second_str_ptr, + len: second_str_len, + }, index_name, Some(index_alloca), second_loop, diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index ba0984e9fb..66750627bf 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -258,8 +258,10 @@ pub fn list_join<'a, 'ctx, 'env>( builder, parent, ctx, - outer_list_ptr, - outer_list_len, + LoopListArg { + ptr: outer_list_ptr, + len: outer_list_len, + }, "#sum_index", None, sum_loop, @@ -322,8 +324,10 @@ pub fn list_join<'a, 'ctx, 'env>( builder, parent, ctx, - inner_list_ptr, - inner_list_len, + LoopListArg { + ptr: inner_list_ptr, + len: inner_list_len, + }, "#inner_index", None, inner_elem_loop, @@ -337,8 +341,10 @@ pub fn list_join<'a, 'ctx, 'env>( builder, parent, ctx, - outer_list_ptr, - outer_list_len, + LoopListArg { + ptr: outer_list_ptr, + len: outer_list_len, + }, "#inner_list_index", None, inner_list_loop, @@ -682,7 +688,13 @@ pub fn list_map<'a, 'ctx, 'env>( }; incrementing_elem_loop( - builder, parent, ctx, list_ptr, len, "#index", None, list_loop, + builder, + parent, + ctx, + LoopListArg { ptr: list_ptr, len }, + "#index", + None, + list_loop, ); store_list(env, ret_list_ptr, len) @@ -813,8 +825,10 @@ pub fn list_concat<'a, 'ctx, 'env>( builder, parent, ctx, - first_list_ptr, - first_list_len, + LoopListArg { + ptr: first_list_ptr, + len: first_list_len, + }, index_name, None, first_loop, @@ -857,8 +871,10 @@ pub fn list_concat<'a, 'ctx, 'env>( builder, parent, ctx, - second_list_ptr, - second_list_len, + LoopListArg { + ptr: second_list_ptr, + len: second_list_len, + }, index_name, Some(index_alloca), second_loop, @@ -895,14 +911,16 @@ pub fn list_concat<'a, 'ctx, 'env>( } } -// This helper simulates a basic for loop, where -// and index increments up from 0 to some end value +pub struct LoopListArg<'ctx> { + pub ptr: PointerValue<'ctx>, + pub len: IntValue<'ctx>, +} + pub fn incrementing_elem_loop<'ctx, LoopFn>( builder: &Builder<'ctx>, parent: FunctionValue<'ctx>, ctx: &'ctx Context, - list_ptr: PointerValue<'ctx>, - end: IntValue<'ctx>, + list: LoopListArg<'ctx>, index_name: &str, maybe_alloca: Option>, mut loop_fn: LoopFn, @@ -914,12 +932,12 @@ where builder, parent, ctx, - end, + list.len, index_name, maybe_alloca, |index| { // The pointer to the element in the list - let elem_ptr = unsafe { builder.build_in_bounds_gep(list_ptr, &[index], "load_index") }; + let elem_ptr = unsafe { builder.build_in_bounds_gep(list.ptr, &[index], "load_index") }; let elem = builder.build_load(elem_ptr, "get_elem"); From 6b351d037510c78ccb89bc485550290f13006c3a Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 29 Aug 2020 23:14:32 -0400 Subject: [PATCH 13/13] Comply with clippy --- compiler/mono/src/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 74d844be2d..8f9c4ac3a4 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -311,7 +311,7 @@ fn layout_from_flat_type<'a>( // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer debug_assert_eq!(args.len(), 1); - let var = args.iter().next().unwrap(); + let var = args.get(0).unwrap(); let content = subs.get_without_compacting(*var).content; layout_from_num_content(content)