mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
Incrementing index loop helper, deleted some comments, refactored List.concat to avoid some duplicate code work
This commit is contained in:
parent
9662ca6bd6
commit
e65c0cafbb
1 changed files with 392 additions and 598 deletions
|
@ -328,12 +328,7 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
// outer_list_len > 0
|
// outer_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the input
|
// We do this check to avoid allocating memory. If the input
|
||||||
// list is empty, then we can just return an empty list.
|
// list is empty, then we can just return an empty list.
|
||||||
let comparison = builder.build_int_compare(
|
let comparison = list_is_not_empty(builder, ctx, outer_list_len);
|
||||||
IntPredicate::UGT,
|
|
||||||
outer_list_len,
|
|
||||||
ctx.i64_type().const_int(0, false),
|
|
||||||
"greaterthanzero",
|
|
||||||
);
|
|
||||||
|
|
||||||
let build_then = || {
|
let build_then = || {
|
||||||
let list_len_sum_name = "#listslengthsum";
|
let list_len_sum_name = "#listslengthsum";
|
||||||
|
@ -342,33 +337,9 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
builder.build_store(list_len_sum_alloca, ctx.i64_type().const_int(0, false));
|
builder.build_store(list_len_sum_alloca, ctx.i64_type().const_int(0, false));
|
||||||
|
|
||||||
// List Sum Loop
|
// List Sum Loop
|
||||||
{
|
let sum_loop = |sum_index| {
|
||||||
let index_name = "#index";
|
|
||||||
let index_alloca = builder.build_alloca(ctx.i64_type(), index_name);
|
|
||||||
|
|
||||||
// index in the outer list, pointing to a inner list
|
|
||||||
let outer_list_index = ctx.i64_type().const_int(0, false);
|
|
||||||
|
|
||||||
builder.build_store(index_alloca, outer_list_index);
|
|
||||||
|
|
||||||
let outer_loop_bb = ctx.append_basic_block(parent, "loop");
|
|
||||||
builder.build_unconditional_branch(outer_loop_bb);
|
|
||||||
builder.position_at_end(outer_loop_bb);
|
|
||||||
|
|
||||||
// #index = #index + 1
|
|
||||||
let curr_index = builder
|
|
||||||
.build_load(index_alloca, index_name)
|
|
||||||
.into_int_value();
|
|
||||||
let next_index = builder.build_int_add(
|
|
||||||
curr_index,
|
|
||||||
ctx.i64_type().const_int(1, false),
|
|
||||||
"nextindex",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(index_alloca, next_index);
|
|
||||||
|
|
||||||
let inner_list_wrapper_ptr = unsafe {
|
let inner_list_wrapper_ptr = unsafe {
|
||||||
builder.build_in_bounds_gep(outer_list_ptr, &[curr_index], "load_index")
|
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 inner_list = builder.build_load(inner_list_wrapper_ptr, "inner_list");
|
||||||
|
@ -383,20 +354,18 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
|
|
||||||
builder.build_store(list_len_sum_alloca, next_list_sum);
|
builder.build_store(list_len_sum_alloca, next_list_sum);
|
||||||
|
};
|
||||||
|
|
||||||
// #index < outer_list_len
|
incrementing_index_loop(
|
||||||
let outer_loop_end_cond =
|
builder,
|
||||||
bounds_check_comparison(builder, next_index, outer_list_len);
|
parent,
|
||||||
|
ctx,
|
||||||
let after_outer_loop_bb = ctx.append_basic_block(parent, "after_outer_loop");
|
outer_list_len,
|
||||||
|
"#sum_index",
|
||||||
builder.build_conditional_branch(
|
None,
|
||||||
outer_loop_end_cond,
|
sum_loop,
|
||||||
outer_loop_bb,
|
|
||||||
after_outer_loop_bb,
|
|
||||||
);
|
);
|
||||||
builder.position_at_end(after_outer_loop_bb);
|
|
||||||
}
|
|
||||||
let final_list_sum = builder
|
let final_list_sum = builder
|
||||||
.build_load(list_len_sum_alloca, list_len_sum_name)
|
.build_load(list_len_sum_alloca, list_len_sum_name)
|
||||||
.into_int_value();
|
.into_int_value();
|
||||||
|
@ -409,32 +378,8 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
builder.build_store(dest_elem_ptr_alloca, final_list_ptr);
|
builder.build_store(dest_elem_ptr_alloca, final_list_ptr);
|
||||||
|
|
||||||
// Element inserting loop
|
// Inner List Loop
|
||||||
{
|
let inner_list_loop = |curr_index| {
|
||||||
let index_name = "#index";
|
|
||||||
let index_alloca = builder.build_alloca(ctx.i64_type(), index_name);
|
|
||||||
|
|
||||||
// index in the outer list, pointing to a inner list
|
|
||||||
let outer_list_index = ctx.i64_type().const_int(0, false);
|
|
||||||
|
|
||||||
builder.build_store(index_alloca, outer_list_index);
|
|
||||||
|
|
||||||
let outer_loop_bb = ctx.append_basic_block(parent, "loop");
|
|
||||||
builder.build_unconditional_branch(outer_loop_bb);
|
|
||||||
builder.position_at_end(outer_loop_bb);
|
|
||||||
|
|
||||||
// #index = #index + 1
|
|
||||||
let curr_index = builder
|
|
||||||
.build_load(index_alloca, index_name)
|
|
||||||
.into_int_value();
|
|
||||||
let next_index = builder.build_int_add(
|
|
||||||
curr_index,
|
|
||||||
ctx.i64_type().const_int(1, false),
|
|
||||||
"nextindex",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(index_alloca, next_index);
|
|
||||||
|
|
||||||
let inner_list_wrapper = {
|
let inner_list_wrapper = {
|
||||||
let wrapper_ptr = unsafe {
|
let wrapper_ptr = unsafe {
|
||||||
builder.build_in_bounds_gep(outer_list_ptr, &[curr_index], "load_index")
|
builder.build_in_bounds_gep(outer_list_ptr, &[curr_index], "load_index")
|
||||||
|
@ -445,16 +390,12 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
.into_struct_value()
|
.into_struct_value()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inner Loop
|
|
||||||
{
|
|
||||||
let inner_list_len = list_len(builder, inner_list_wrapper);
|
let inner_list_len = list_len(builder, inner_list_wrapper);
|
||||||
|
|
||||||
// inner_list_len > 0
|
// inner_list_len > 0
|
||||||
let inner_list_comparison = list_is_not_empty(builder, ctx, inner_list_len);
|
let inner_list_comparison = list_is_not_empty(builder, ctx, inner_list_len);
|
||||||
|
|
||||||
let inner_list_non_empty_block =
|
let inner_list_non_empty_block =
|
||||||
ctx.append_basic_block(parent, "inner_list_non_empty");
|
ctx.append_basic_block(parent, "inner_list_non_empty");
|
||||||
|
|
||||||
let after_inner_list_non_empty_block =
|
let after_inner_list_non_empty_block =
|
||||||
ctx.append_basic_block(parent, "branchcont");
|
ctx.append_basic_block(parent, "branchcont");
|
||||||
|
|
||||||
|
@ -465,36 +406,15 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
builder.position_at_end(inner_list_non_empty_block);
|
builder.position_at_end(inner_list_non_empty_block);
|
||||||
|
|
||||||
let inner_index_name = "#inner_index";
|
// Element Inserting Loop
|
||||||
let inner_index_alloca =
|
let inner_elem_loop = |inner_index| {
|
||||||
builder.build_alloca(ctx.i64_type(), inner_index_name);
|
|
||||||
|
|
||||||
let inner_list_index = ctx.i64_type().const_int(0, false);
|
|
||||||
builder.build_store(inner_index_alloca, inner_list_index);
|
|
||||||
|
|
||||||
let inner_loop_bb = ctx.append_basic_block(parent, "loop");
|
|
||||||
builder.build_unconditional_branch(inner_loop_bb);
|
|
||||||
builder.position_at_end(inner_loop_bb);
|
|
||||||
|
|
||||||
// #index = #index + 1
|
|
||||||
let curr_inner_index = builder
|
|
||||||
.build_load(inner_index_alloca, inner_index_name)
|
|
||||||
.into_int_value();
|
|
||||||
let next_inner_index = builder.build_int_add(
|
|
||||||
curr_inner_index,
|
|
||||||
ctx.i64_type().const_int(1, false),
|
|
||||||
"nextindex",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(inner_index_alloca, next_inner_index);
|
|
||||||
|
|
||||||
let src_elem_ptr = unsafe {
|
let src_elem_ptr = unsafe {
|
||||||
let inner_list_ptr =
|
let inner_list_ptr =
|
||||||
load_list_ptr(builder, inner_list_wrapper, elem_ptr_type);
|
load_list_ptr(builder, inner_list_wrapper, elem_ptr_type);
|
||||||
|
|
||||||
builder.build_in_bounds_gep(
|
builder.build_in_bounds_gep(
|
||||||
inner_list_ptr,
|
inner_list_ptr,
|
||||||
&[curr_inner_index],
|
&[inner_index],
|
||||||
"load_index",
|
"load_index",
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -517,36 +437,31 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.build_store(dest_elem_ptr_alloca, inc_dest_elem_ptr);
|
builder.build_store(dest_elem_ptr_alloca, inc_dest_elem_ptr);
|
||||||
|
};
|
||||||
|
|
||||||
let inner_loop_end_cond =
|
incrementing_index_loop(
|
||||||
bounds_check_comparison(builder, next_inner_index, inner_list_len);
|
builder,
|
||||||
|
parent,
|
||||||
let after_inner_loop_bb =
|
ctx,
|
||||||
ctx.append_basic_block(parent, "after_inner_loop");
|
inner_list_len,
|
||||||
|
"#inner_index",
|
||||||
builder.build_conditional_branch(
|
None,
|
||||||
inner_loop_end_cond,
|
inner_elem_loop,
|
||||||
inner_loop_bb,
|
|
||||||
after_inner_loop_bb,
|
|
||||||
);
|
);
|
||||||
builder.position_at_end(after_inner_loop_bb);
|
|
||||||
|
|
||||||
builder.build_unconditional_branch(after_inner_list_non_empty_block);
|
builder.build_unconditional_branch(after_inner_list_non_empty_block);
|
||||||
builder.position_at_end(after_inner_list_non_empty_block);
|
builder.position_at_end(after_inner_list_non_empty_block);
|
||||||
}
|
};
|
||||||
|
|
||||||
// #index < outer_list_len
|
incrementing_index_loop(
|
||||||
let outer_loop_end_cond =
|
builder,
|
||||||
bounds_check_comparison(builder, next_index, outer_list_len);
|
parent,
|
||||||
|
ctx,
|
||||||
let after_outer_loop_bb = ctx.append_basic_block(parent, "after_outer_loop");
|
outer_list_len,
|
||||||
|
"#inner_list_index",
|
||||||
builder.build_conditional_branch(
|
None,
|
||||||
outer_loop_end_cond,
|
inner_list_loop,
|
||||||
outer_loop_bb,
|
|
||||||
after_outer_loop_bb,
|
|
||||||
);
|
);
|
||||||
builder.position_at_end(after_outer_loop_bb);
|
|
||||||
|
|
||||||
let ptr_bytes = env.ptr_bytes;
|
let ptr_bytes = env.ptr_bytes;
|
||||||
let int_type = ptr_int(ctx, ptr_bytes);
|
let int_type = ptr_int(ctx, ptr_bytes);
|
||||||
|
@ -581,7 +496,6 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
collection(ctx, ptr_bytes),
|
collection(ctx, ptr_bytes),
|
||||||
"cast_collection",
|
"cast_collection",
|
||||||
)
|
)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let build_else = || empty_list(env);
|
let build_else = || empty_list(env);
|
||||||
|
@ -604,75 +518,6 @@ pub fn list_join<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_nonempty_list<'a, 'ctx, 'env>(
|
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
|
||||||
list_len: IntValue<'ctx>,
|
|
||||||
elems_ptr: PointerValue<'ctx>,
|
|
||||||
elem_layout: &Layout<'_>,
|
|
||||||
) -> (StructValue<'ctx>, PointerValue<'ctx>) {
|
|
||||||
let builder = env.builder;
|
|
||||||
let ctx = env.context;
|
|
||||||
let ptr_bytes = env.ptr_bytes;
|
|
||||||
|
|
||||||
// Calculate the number of bytes we'll need to allocate.
|
|
||||||
let elem_bytes = env
|
|
||||||
.ptr_int()
|
|
||||||
.const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false);
|
|
||||||
let size = env
|
|
||||||
.builder
|
|
||||||
.build_int_mul(elem_bytes, list_len, "mul_len_by_elem_bytes");
|
|
||||||
|
|
||||||
// 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 clone_ptr = builder
|
|
||||||
.build_array_malloc(elem_type, list_len, "list_ptr")
|
|
||||||
.unwrap();
|
|
||||||
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!
|
|
||||||
|
|
||||||
// Either memcpy or deep clone the array elements
|
|
||||||
if elem_layout.safe_to_memcpy() {
|
|
||||||
// Copy the bytes from the original array into the new
|
|
||||||
// one we just malloc'd.
|
|
||||||
//
|
|
||||||
// TODO how do we decide when to do the small memcpy vs the normal one?
|
|
||||||
builder.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size);
|
|
||||||
} else {
|
|
||||||
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, list_len, Builtin::WRAPPER_LEN, "insert_len")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let answer = builder
|
|
||||||
.build_bitcast(
|
|
||||||
struct_val.into_struct_value(),
|
|
||||||
collection(ctx, ptr_bytes),
|
|
||||||
"cast_collection",
|
|
||||||
)
|
|
||||||
.into_struct_value();
|
|
||||||
|
|
||||||
(answer, clone_ptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List.reverse : List elem -> List elem
|
/// List.reverse : List elem -> List elem
|
||||||
pub fn list_reverse<'a, 'ctx, 'env>(
|
pub fn list_reverse<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
@ -1046,58 +891,35 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
debug_assert_eq!(args.len(), 2);
|
debug_assert_eq!(args.len(), 2);
|
||||||
|
|
||||||
// This implementation is quite long, let me explain what is complicating it. Here are our
|
|
||||||
// contraints:
|
|
||||||
//
|
|
||||||
// constraint 1. lists might be empty because they have the layout `EmptyList`, or they might
|
|
||||||
// be empty because they have a `List` layout, but happen to be empty, such as in this code:
|
|
||||||
//
|
|
||||||
// list : List Int
|
|
||||||
// list =
|
|
||||||
// []
|
|
||||||
//
|
|
||||||
// So we have two sources of truth for emptiness.
|
|
||||||
//
|
|
||||||
// constraint 2. iterating over a non-empty list involves allocating memory for a index and
|
|
||||||
// a loop, and allocating memory is costly, so we dont want to even try to iterate over empty
|
|
||||||
// lists.
|
|
||||||
//
|
|
||||||
// Accounting for all the possibilities in the two constraints above gives us 9 code paths:
|
|
||||||
//
|
|
||||||
// first list EmptyList List(list) List(list)
|
|
||||||
// second list where list.length = 0 where list.length > 0
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// EmptyList | [] | [] | clone(1st_list) |
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// List(list) | [] | [] | clone(1st_list) |
|
|
||||||
// where list.length = 0 | | | |
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// List(list) | clone(2nd_list) | clone(2nd_list) | 2nd_list ++ 1st_list |
|
|
||||||
// where list.length > 0 | | | |
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
|
|
||||||
let (first_list, first_list_layout) = &args[0];
|
let (first_list, list_layout) = &args[0];
|
||||||
|
|
||||||
let (second_list, second_list_layout) = &args[1];
|
let (second_list, _) = &args[1];
|
||||||
|
|
||||||
let second_list_wrapper =
|
let second_list_wrapper =
|
||||||
build_expr(env, layout_ids, scope, parent, second_list).into_struct_value();
|
build_expr(env, layout_ids, scope, parent, second_list).into_struct_value();
|
||||||
|
|
||||||
let second_list_len = list_len(builder, second_list_wrapper);
|
let second_list_len = list_len(builder, second_list_wrapper);
|
||||||
|
|
||||||
match first_list_layout {
|
// We only match on the first lists layout
|
||||||
Layout::Builtin(Builtin::EmptyList) => {
|
// because the first and second input lists
|
||||||
match second_list_layout {
|
// necessarily have the same layout
|
||||||
|
match list_layout {
|
||||||
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
||||||
Layout::Builtin(Builtin::List(elem_layout)) => {
|
Layout::Builtin(Builtin::List(elem_layout)) => {
|
||||||
// THIS IS A COPY AND PASTE
|
let first_list_wrapper =
|
||||||
// All the code under the Layout::Builtin(Builtin::List()) match branch
|
build_expr(env, layout_ids, scope, parent, first_list).into_struct_value();
|
||||||
// is the same as what is under `if_first_list_is_empty`. Re-using
|
|
||||||
// `if_first_list_is_empty` here however, creates memory problems.
|
|
||||||
|
|
||||||
|
let first_list_len = list_len(builder, first_list_wrapper);
|
||||||
|
|
||||||
|
// first_list_len > 0
|
||||||
|
// We do this check to avoid allocating memory. If the first input
|
||||||
|
// list is empty, then we can just return the second list cloned
|
||||||
|
let first_list_length_comparison = list_is_not_empty(builder, ctx, first_list_len);
|
||||||
|
|
||||||
|
let if_first_list_is_empty = || {
|
||||||
// second_list_len > 0
|
// second_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the second input
|
// We do this check to avoid allocating memory. If the second input
|
||||||
// list is empty, then we can just return the first list cloned
|
// list is empty, then we can just return the first list cloned
|
||||||
|
@ -1129,25 +951,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
build_second_list_else,
|
build_second_list_else,
|
||||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
_ => {
|
|
||||||
unreachable!(
|
|
||||||
"Invalid List layout for second input list of List.concat: {:?}",
|
|
||||||
second_list_layout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Layout::Builtin(Builtin::List(elem_layout)) => {
|
|
||||||
let first_list_wrapper =
|
|
||||||
build_expr(env, layout_ids, scope, parent, first_list).into_struct_value();
|
|
||||||
|
|
||||||
let first_list_len = list_len(builder, first_list_wrapper);
|
|
||||||
|
|
||||||
// first_list_len > 0
|
|
||||||
// We do this check to avoid allocating memory. If the first input
|
|
||||||
// list is empty, then we can just return the second list cloned
|
|
||||||
let first_list_length_comparison = list_is_not_empty(builder, ctx, first_list_len);
|
|
||||||
|
|
||||||
let if_first_list_is_not_empty = || {
|
let if_first_list_is_not_empty = || {
|
||||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
||||||
|
@ -1166,18 +970,6 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
BasicValueEnum::StructValue(new_wrapper)
|
BasicValueEnum::StructValue(new_wrapper)
|
||||||
};
|
};
|
||||||
|
|
||||||
match second_list_layout {
|
|
||||||
Layout::Builtin(Builtin::EmptyList) => {
|
|
||||||
let (new_wrapper, _) = clone_nonempty_list(
|
|
||||||
env,
|
|
||||||
first_list_len,
|
|
||||||
load_list_ptr(builder, first_list_wrapper, ptr_type),
|
|
||||||
elem_layout,
|
|
||||||
);
|
|
||||||
|
|
||||||
BasicValueEnum::StructValue(new_wrapper)
|
|
||||||
}
|
|
||||||
Layout::Builtin(Builtin::List(_)) => {
|
|
||||||
// second_list_len > 0
|
// second_list_len > 0
|
||||||
// We do this check to avoid allocating memory. If the second input
|
// We do this check to avoid allocating memory. If the second input
|
||||||
// list is empty, then we can just return the first list cloned
|
// list is empty, then we can just return the first list cloned
|
||||||
|
@ -1185,11 +977,8 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
list_is_not_empty(builder, ctx, second_list_len);
|
list_is_not_empty(builder, ctx, second_list_len);
|
||||||
|
|
||||||
let if_second_list_is_not_empty = || {
|
let if_second_list_is_not_empty = || {
|
||||||
let combined_list_len = builder.build_int_add(
|
let combined_list_len =
|
||||||
first_list_len,
|
builder.build_int_add(first_list_len, second_list_len, "add_list_lengths");
|
||||||
second_list_len,
|
|
||||||
"add_list_lengths",
|
|
||||||
);
|
|
||||||
|
|
||||||
let combined_list_ptr = env
|
let combined_list_ptr = env
|
||||||
.builder
|
.builder
|
||||||
|
@ -1200,26 +989,9 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let index_name = "#index";
|
|
||||||
let index_alloca = builder.build_alloca(ctx.i64_type(), index_name);
|
|
||||||
// The index variable begins at 0 and increments
|
|
||||||
// on each iteration of the loop
|
|
||||||
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
|
||||||
|
|
||||||
// FIRST LOOP
|
// FIRST LOOP
|
||||||
{
|
let first_loop = |curr_first_loop_index| {
|
||||||
let first_loop_bb =
|
let first_list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type);
|
||||||
ctx.append_basic_block(parent, "first_list_concat_loop");
|
|
||||||
|
|
||||||
builder.build_unconditional_branch(first_loop_bb);
|
|
||||||
builder.position_at_end(first_loop_bb);
|
|
||||||
|
|
||||||
let curr_first_loop_index = builder
|
|
||||||
.build_load(index_alloca, index_name)
|
|
||||||
.into_int_value();
|
|
||||||
|
|
||||||
let first_list_ptr =
|
|
||||||
load_list_ptr(builder, first_list_wrapper, ptr_type);
|
|
||||||
|
|
||||||
// The pointer to the element in the first list
|
// The pointer to the element in the first list
|
||||||
let first_list_elem_ptr = unsafe {
|
let first_list_elem_ptr = unsafe {
|
||||||
|
@ -1239,57 +1011,30 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let first_list_elem =
|
let first_list_elem = builder.build_load(first_list_elem_ptr, "get_elem");
|
||||||
builder.build_load(first_list_elem_ptr, "get_elem");
|
|
||||||
|
|
||||||
// Mutate the new array in-place to change the element.
|
// Mutate the new array in-place to change the element.
|
||||||
builder.build_store(combined_list_elem_ptr, first_list_elem);
|
builder.build_store(combined_list_elem_ptr, first_list_elem);
|
||||||
|
};
|
||||||
|
|
||||||
// #index = #index + 1
|
let index_name = "#index";
|
||||||
let next_first_loop_index = builder.build_int_add(
|
|
||||||
curr_first_loop_index,
|
|
||||||
ctx.i64_type().const_int(1, false),
|
|
||||||
"nextindex",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(index_alloca, next_first_loop_index);
|
let index_alloca = incrementing_index_loop(
|
||||||
|
|
||||||
// #index < first_list_len
|
|
||||||
let first_loop_end_cond = bounds_check_comparison(
|
|
||||||
builder,
|
builder,
|
||||||
next_first_loop_index,
|
parent,
|
||||||
|
ctx,
|
||||||
first_list_len,
|
first_list_len,
|
||||||
|
index_name,
|
||||||
|
None,
|
||||||
|
first_loop,
|
||||||
);
|
);
|
||||||
|
|
||||||
let after_first_loop_bb =
|
|
||||||
ctx.append_basic_block(parent, "after_first_loop");
|
|
||||||
|
|
||||||
builder.build_conditional_branch(
|
|
||||||
first_loop_end_cond,
|
|
||||||
first_loop_bb,
|
|
||||||
after_first_loop_bb,
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.position_at_end(after_first_loop_bb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the index variable to 0
|
// Reset the index variable to 0
|
||||||
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||||
|
|
||||||
// SECOND LOOP
|
// SECOND LOOP
|
||||||
{
|
let second_loop = |curr_second_index| {
|
||||||
let second_loop_bb =
|
let second_list_ptr = load_list_ptr(builder, second_list_wrapper, ptr_type);
|
||||||
ctx.append_basic_block(parent, "second_list_concat_loop");
|
|
||||||
|
|
||||||
builder.build_unconditional_branch(second_loop_bb);
|
|
||||||
builder.position_at_end(second_loop_bb);
|
|
||||||
|
|
||||||
let curr_second_index = builder
|
|
||||||
.build_load(index_alloca, index_name)
|
|
||||||
.into_int_value();
|
|
||||||
|
|
||||||
let second_list_ptr =
|
|
||||||
load_list_ptr(builder, second_list_wrapper, ptr_type);
|
|
||||||
|
|
||||||
// The pointer to the element in the second list
|
// The pointer to the element in the second list
|
||||||
let second_list_elem_ptr = unsafe {
|
let second_list_elem_ptr = unsafe {
|
||||||
|
@ -1322,45 +1067,27 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let second_list_elem =
|
let second_list_elem = builder.build_load(second_list_elem_ptr, "get_elem");
|
||||||
builder.build_load(second_list_elem_ptr, "get_elem");
|
|
||||||
|
|
||||||
// Mutate the new array in-place to change the element.
|
// Mutate the new array in-place to change the element.
|
||||||
builder.build_store(combined_list_elem_ptr, second_list_elem);
|
builder.build_store(combined_list_elem_ptr, second_list_elem);
|
||||||
|
};
|
||||||
|
|
||||||
// #index = #index + 1
|
incrementing_index_loop(
|
||||||
let next_second_index = builder.build_int_add(
|
|
||||||
curr_second_index,
|
|
||||||
ctx.i64_type().const_int(1, false),
|
|
||||||
"increment_index",
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.build_store(index_alloca, next_second_index);
|
|
||||||
|
|
||||||
// #index < second_list_len
|
|
||||||
let second_loop_end_cond = bounds_check_comparison(
|
|
||||||
builder,
|
builder,
|
||||||
next_second_index,
|
parent,
|
||||||
|
ctx,
|
||||||
second_list_len,
|
second_list_len,
|
||||||
|
index_name,
|
||||||
|
Some(index_alloca),
|
||||||
|
second_loop,
|
||||||
);
|
);
|
||||||
|
|
||||||
let after_second_loop_bb =
|
|
||||||
ctx.append_basic_block(parent, "after_second_loop");
|
|
||||||
|
|
||||||
builder.build_conditional_branch(
|
|
||||||
second_loop_end_cond,
|
|
||||||
second_loop_bb,
|
|
||||||
after_second_loop_bb,
|
|
||||||
);
|
|
||||||
builder.position_at_end(after_second_loop_bb);
|
|
||||||
|
|
||||||
let ptr_bytes = env.ptr_bytes;
|
let ptr_bytes = env.ptr_bytes;
|
||||||
let int_type = ptr_int(ctx, ptr_bytes);
|
let int_type = ptr_int(ctx, ptr_bytes);
|
||||||
let ptr_as_int = builder.build_ptr_to_int(
|
let ptr_as_int =
|
||||||
combined_list_ptr,
|
builder.build_ptr_to_int(combined_list_ptr, int_type, "list_cast_ptr");
|
||||||
int_type,
|
|
||||||
"list_cast_ptr",
|
|
||||||
);
|
|
||||||
let struct_type = collection(ctx, ptr_bytes);
|
let struct_type = collection(ctx, ptr_bytes);
|
||||||
|
|
||||||
let mut struct_val;
|
let mut struct_val;
|
||||||
|
@ -1390,7 +1117,6 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
collection(ctx, ptr_bytes),
|
collection(ctx, ptr_bytes),
|
||||||
"cast_collection",
|
"cast_collection",
|
||||||
)
|
)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
build_basic_phi2(
|
build_basic_phi2(
|
||||||
|
@ -1401,59 +1127,6 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
if_second_list_is_empty,
|
if_second_list_is_empty,
|
||||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
unreachable!(
|
|
||||||
"Invalid List layout for second input list of List.concat: {:?}",
|
|
||||||
second_list_layout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let if_first_list_is_empty = || {
|
|
||||||
match second_list_layout {
|
|
||||||
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
|
|
||||||
Layout::Builtin(Builtin::List(elem_layout)) => {
|
|
||||||
// second_list_len > 0
|
|
||||||
// We do this check to avoid allocating memory. If the second input
|
|
||||||
// list is empty, then we can just return the first list cloned
|
|
||||||
let second_list_length_comparison =
|
|
||||||
list_is_not_empty(builder, ctx, second_list_len);
|
|
||||||
|
|
||||||
let build_second_list_then = || {
|
|
||||||
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 (new_wrapper, _) = clone_nonempty_list(
|
|
||||||
env,
|
|
||||||
second_list_len,
|
|
||||||
load_list_ptr(builder, second_list_wrapper, ptr_type),
|
|
||||||
elem_layout,
|
|
||||||
);
|
|
||||||
|
|
||||||
BasicValueEnum::StructValue(new_wrapper)
|
|
||||||
};
|
|
||||||
|
|
||||||
let build_second_list_else = || empty_list(env);
|
|
||||||
|
|
||||||
build_basic_phi2(
|
|
||||||
env,
|
|
||||||
parent,
|
|
||||||
second_list_length_comparison,
|
|
||||||
build_second_list_then,
|
|
||||||
build_second_list_else,
|
|
||||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
unreachable!(
|
|
||||||
"Invalid List layout for second input list of List.concat: {:?}",
|
|
||||||
second_list_layout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
build_basic_phi2(
|
build_basic_phi2(
|
||||||
|
@ -1468,12 +1141,64 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
"Invalid List layout for first list in List.concat : {:?}",
|
"Invalid List layout for first list in List.concat : {:?}",
|
||||||
first_list_layout
|
list_layout
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
end: IntValue<'ctx>,
|
||||||
|
index_name: &str,
|
||||||
|
// allocating memory for an index is costly, so sometimes
|
||||||
|
// we want to reuse an index if multiple loops happen in a
|
||||||
|
// series, such as the case in List.concat. A memory
|
||||||
|
// allocation cab be passed in to be used, and the memory
|
||||||
|
// allocation that _is_ used is the return value.
|
||||||
|
maybe_alloca: Option<PointerValue<'ctx>>,
|
||||||
|
mut loop_fn: LoopFn,
|
||||||
|
) -> PointerValue<'ctx>
|
||||||
|
where
|
||||||
|
LoopFn: FnMut(IntValue<'ctx>) -> (),
|
||||||
|
{
|
||||||
|
let index_alloca = match maybe_alloca {
|
||||||
|
None => builder.build_alloca(ctx.i64_type(), index_name),
|
||||||
|
Some(alloca) => alloca,
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||||
|
|
||||||
|
let loop_bb = ctx.append_basic_block(parent, "loop");
|
||||||
|
builder.build_unconditional_branch(loop_bb);
|
||||||
|
builder.position_at_end(loop_bb);
|
||||||
|
|
||||||
|
let curr_index = builder
|
||||||
|
.build_load(index_alloca, index_name)
|
||||||
|
.into_int_value();
|
||||||
|
let next_index =
|
||||||
|
builder.build_int_add(curr_index, ctx.i64_type().const_int(1, false), "nextindex");
|
||||||
|
|
||||||
|
builder.build_store(index_alloca, next_index);
|
||||||
|
|
||||||
|
// The body of the loop
|
||||||
|
loop_fn(curr_index);
|
||||||
|
|
||||||
|
// #index < end
|
||||||
|
let loop_end_cond = bounds_check_comparison(builder, next_index, end);
|
||||||
|
|
||||||
|
let after_loop_bb = ctx.append_basic_block(parent, "after_outer_loop");
|
||||||
|
|
||||||
|
builder.build_conditional_branch(loop_end_cond, loop_bb, after_loop_bb);
|
||||||
|
builder.position_at_end(after_loop_bb);
|
||||||
|
|
||||||
|
index_alloca
|
||||||
|
}
|
||||||
|
|
||||||
fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>(
|
fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
parent: FunctionValue<'ctx>,
|
parent: FunctionValue<'ctx>,
|
||||||
|
@ -1555,3 +1280,72 @@ fn load_list_ptr<'ctx>(
|
||||||
|
|
||||||
builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr")
|
builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clone_nonempty_list<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
list_len: IntValue<'ctx>,
|
||||||
|
elems_ptr: PointerValue<'ctx>,
|
||||||
|
elem_layout: &Layout<'_>,
|
||||||
|
) -> (StructValue<'ctx>, PointerValue<'ctx>) {
|
||||||
|
let builder = env.builder;
|
||||||
|
let ctx = env.context;
|
||||||
|
let ptr_bytes = env.ptr_bytes;
|
||||||
|
|
||||||
|
// Calculate the number of bytes we'll need to allocate.
|
||||||
|
let elem_bytes = env
|
||||||
|
.ptr_int()
|
||||||
|
.const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false);
|
||||||
|
let size = env
|
||||||
|
.builder
|
||||||
|
.build_int_mul(elem_bytes, list_len, "mul_len_by_elem_bytes");
|
||||||
|
|
||||||
|
// 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 clone_ptr = builder
|
||||||
|
.build_array_malloc(elem_type, list_len, "list_ptr")
|
||||||
|
.unwrap();
|
||||||
|
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!
|
||||||
|
|
||||||
|
// Either memcpy or deep clone the array elements
|
||||||
|
if elem_layout.safe_to_memcpy() {
|
||||||
|
// Copy the bytes from the original array into the new
|
||||||
|
// one we just malloc'd.
|
||||||
|
//
|
||||||
|
// TODO how do we decide when to do the small memcpy vs the normal one?
|
||||||
|
builder.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size);
|
||||||
|
} else {
|
||||||
|
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, list_len, Builtin::WRAPPER_LEN, "insert_len")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let answer = builder
|
||||||
|
.build_bitcast(
|
||||||
|
struct_val.into_struct_value(),
|
||||||
|
collection(ctx, ptr_bytes),
|
||||||
|
"cast_collection",
|
||||||
|
)
|
||||||
|
.into_struct_value();
|
||||||
|
|
||||||
|
(answer, clone_ptr)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue