introduce Ownership to list layout

This commit is contained in:
Folkert 2020-07-30 13:42:47 +02:00
parent d784f62cd3
commit 22471167d3
5 changed files with 213 additions and 16 deletions

View File

@ -720,22 +720,187 @@ pub fn build_expr<'a, 'ctx, 'env>(
}
RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args),
DecAfter(_, expr) => build_expr(env, layout_ids, scope, parent, expr),
DecAfter(symbol, expr) => {
match scope.get(symbol) {
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
Some((layout, ptr)) => {
match layout {
Layout::Builtin(Builtin::List(_, elem_layout)) if false => {
// first run the body
let body = build_expr(env, layout_ids, scope, parent, expr);
let wrapper_struct = env
.builder
.build_load(*ptr, symbol.ident_string(&env.interns))
.into_struct_value();
decrement_refcount_list(env, parent, wrapper_struct, body)
}
_ => {
// not refcounted, do nothing special
build_expr(env, layout_ids, scope, parent, expr)
}
}
}
}
}
Reuse(_, expr) => build_expr(env, layout_ids, scope, parent, expr),
Reset(_, expr) => build_expr(env, layout_ids, scope, parent, expr),
}
}
fn refcount_is_one_comparison<'ctx>(
builder: &Builder<'ctx>,
context: &'ctx Context,
refcount: IntValue<'ctx>,
) -> IntValue<'ctx> {
let refcount_one: IntValue<'ctx> = context
.i64_type()
.const_int((std::usize::MAX - 1) as _, false);
// Note: Check for refcount < refcount_1 as the "true" condition,
// to avoid misprediction. (In practice this should usually pass,
// and CPUs generally default to predicting that a forward jump
// shouldn't be taken; that is, they predict "else" won't be taken.)
builder.build_int_compare(
IntPredicate::ULT,
refcount,
refcount_one,
"refcount_one_check",
)
}
fn list_get_refcount_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
list_wrapper: StructValue<'ctx>,
) -> PointerValue<'ctx> {
let builder = env.builder;
let ctx = env.context;
// basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let elem_type = ctx.i64_type().into();
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
// get the refcount
let zero_index = ctx.i64_type().const_zero();
unsafe {
// SAFETY
// index 0 is always defined for lists.
builder.build_in_bounds_gep(array_data_ptr, &[zero_index], "refcount_index")
}
}
fn increment_refcount_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
original_wrapper: StructValue<'ctx>,
body: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let refcount_ptr = list_get_refcount_ptr(env, original_wrapper);
let refcount = env
.builder
.build_load(refcount_ptr, "get_refcount")
.into_int_value();
// our refcount 0 is actually usize::MAX, so incrementing the refcount means decrementing this value.
let decremented = env.builder.build_int_sub(
refcount,
ctx.i64_type().const_int(1 as u64, false),
"new_refcount",
);
// Mutate the new array in-place to change the element.
builder.build_store(refcount_ptr, decremented);
body
}
fn decrement_refcount_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
original_wrapper: StructValue<'ctx>,
body: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let refcount_ptr = list_get_refcount_ptr(env, original_wrapper);
let refcount = env
.builder
.build_load(refcount_ptr, "get_refcount")
.into_int_value();
let comparison = refcount_is_one_comparison(builder, env.context, refcount);
// the refcount is higher than 1, write the decremented value
let build_then = || {
// our refcount 0 is actually usize::MAX, so decrementing the refcount means incrementing this value.
let decremented = env.builder.build_int_add(
ctx.i64_type().const_int(1 as u64, false),
refcount,
"new_refcount",
);
// Mutate the new array in-place to change the element.
builder.build_store(refcount_ptr, decremented);
body
};
// refcount is one, and will be decremented. This list can be freed
let build_else = || {
let array_data_ptr = load_list_ptr(
builder,
original_wrapper,
ctx.i64_type().ptr_type(AddressSpace::Generic),
);
let free = builder.build_free(array_data_ptr);
builder.insert_instruction(&free, None);
body
};
let ret_type = body.get_type();
build_basic_phi2(
env,
parent,
comparison,
build_then,
build_else,
ret_type.into(),
)
}
fn load_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
symbol: &Symbol,
) -> BasicValueEnum<'ctx> {
match scope.get(symbol) {
Some((_, ptr)) => env
.builder
.build_load(*ptr, symbol.ident_string(&env.interns)),
Some((layout, ptr)) => match layout {
Layout::Builtin(Builtin::List(_, _)) if false => {
let load = env
.builder
.build_load(*ptr, symbol.ident_string(&env.interns));
let wrapper_struct = env
.builder
.build_load(*ptr, symbol.ident_string(&env.interns))
.into_struct_value();
increment_refcount_list(env, wrapper_struct, load)
}
_ => env
.builder
.build_load(*ptr, symbol.ident_string(&env.interns)),
},
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
}
}
@ -1670,7 +1835,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, list_layout) = &args[0];
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
Layout::Builtin(Builtin::List(_, elem_layout)) => {
let wrapper_struct =
build_expr(env, layout_ids, scope, parent, list).into_struct_value();
@ -1979,7 +2144,7 @@ fn run_low_level<'a, 'ctx, 'env>(
build_expr(env, layout_ids, scope, parent, &args[1].0).into_int_value();
match list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
Layout::Builtin(Builtin::List(_, elem_layout)) => {
let ctx = env.context;
let elem_type =
basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
@ -2129,7 +2294,7 @@ fn list_append<'a, 'ctx, 'env>(
Layout::Builtin(Builtin::EmptyList) => {
match second_list_layout {
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
// All the code under the Layout::Builtin(Builtin::List()) match branch
// is the same as what is under `if_first_list_is_empty`. Re-using
@ -2175,7 +2340,7 @@ fn list_append<'a, 'ctx, 'env>(
}
}
}
Layout::Builtin(Builtin::List(elem_layout)) => {
Layout::Builtin(Builtin::List(_, elem_layout)) => {
let first_list_wrapper =
build_expr(env, layout_ids, scope, parent, first_list).into_struct_value();
@ -2214,7 +2379,7 @@ fn list_append<'a, 'ctx, 'env>(
BasicValueEnum::StructValue(new_wrapper)
}
Layout::Builtin(Builtin::List(_)) => {
Layout::Builtin(Builtin::List(_, _)) => {
// 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
@ -2453,7 +2618,7 @@ fn list_append<'a, 'ctx, 'env>(
let if_first_list_is_empty = || {
match second_list_layout {
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
Layout::Builtin(Builtin::List(elem_layout)) => {
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

View File

@ -137,7 +137,7 @@ pub fn basic_type_from_layout<'ctx>(
.as_basic_type_enum(),
Map(_, _) | EmptyMap => panic!("TODO layout_to_basic_type for Builtin::Map"),
Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"),
List(_) => collection(context, ptr_bytes).into(),
List(_, _) => collection(context, ptr_bytes).into(),
EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)),
},
}

View File

@ -614,4 +614,19 @@ mod gen_list {
);
})
}
#[test]
fn gen_list_increment_decrement() {
assert_evals_to!(
indoc!(
r#"
x = [ 1,2,3 ]
List.len x
"#
),
3,
i64
);
}
}

View File

@ -1130,7 +1130,7 @@ fn from_can<'a>(
match list_layout_from_elem(arena, subs, elem_var, env.pointer_size) {
Ok(Layout::Builtin(Builtin::EmptyList)) => Expr::EmptyArray,
Ok(Layout::Builtin(Builtin::List(elem_layout))) => {
Ok(Layout::Builtin(Builtin::List(_, elem_layout))) => {
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
for loc_elem in loc_elems {

View File

@ -27,6 +27,20 @@ pub enum Layout<'a> {
Pointer(&'a Layout<'a>),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum Ownership {
/// The default. Object is reference counted
Owned,
/// Do not update reference counter, surrounding context
/// keeps this value alive
Borrowed,
/// Object is unique, can be mutated in-place and
/// is not reference counted
Unique,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Builtin<'a> {
Int128,
@ -42,7 +56,7 @@ pub enum Builtin<'a> {
Str,
Map(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>),
List(&'a Layout<'a>),
List(Ownership, &'a Layout<'a>),
EmptyStr,
EmptyList,
EmptyMap,
@ -219,7 +233,7 @@ impl<'a> Builtin<'a> {
Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size,
List(_, _) | EmptyList => Builtin::LIST_WORDS * pointer_size,
}
}
@ -229,7 +243,7 @@ impl<'a> Builtin<'a> {
match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyMap | EmptyList | EmptySet => true,
Str | Map(_, _) | Set(_) | List(_) => false,
Str | Map(_, _) | Set(_) | List(_, _) => false,
}
}
}
@ -696,7 +710,10 @@ pub fn list_layout_from_elem<'a>(
let elem_layout = Layout::new(arena, content, subs, pointer_size)?;
// This is a normal list.
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
Ok(Layout::Builtin(Builtin::List(
Ownership::Owned,
arena.alloc(elem_layout),
)))
}
}
}