mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Merge branch 'trunk' into run-all-examples
This commit is contained in:
commit
c4616b6da8
33 changed files with 2119 additions and 890 deletions
|
@ -460,4 +460,76 @@ mod cli_run {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// #[serial(effect)]
|
||||||
|
// fn run_effect_unoptimized() {
|
||||||
|
// check_output(
|
||||||
|
// &example_file("effect", "Main.roc"),
|
||||||
|
// &[],
|
||||||
|
// "I am Dep2.str2\n",
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial(multi_dep_str)]
|
||||||
|
fn run_multi_dep_str_unoptimized() {
|
||||||
|
check_output(
|
||||||
|
&fixture_file("multi-dep-str", "Main.roc"),
|
||||||
|
"multi-dep-str",
|
||||||
|
&[],
|
||||||
|
"I am Dep2.str2\n",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial(multi_dep_str)]
|
||||||
|
fn run_multi_dep_str_optimized() {
|
||||||
|
check_output(
|
||||||
|
&fixture_file("multi-dep-str", "Main.roc"),
|
||||||
|
"multi-dep-str",
|
||||||
|
&["--optimize"],
|
||||||
|
"I am Dep2.str2\n",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial(multi_dep_thunk)]
|
||||||
|
fn run_multi_dep_thunk_unoptimized() {
|
||||||
|
check_output(
|
||||||
|
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||||
|
"multi-dep-thunk",
|
||||||
|
&[],
|
||||||
|
"I am Dep2.value2\n",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial(multi_dep_thunk)]
|
||||||
|
fn run_multi_dep_thunk_optimized() {
|
||||||
|
check_output(
|
||||||
|
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||||
|
"multi-dep-thunk",
|
||||||
|
&["--optimize"],
|
||||||
|
"I am Dep2.value2\n",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial(effect)]
|
||||||
|
fn run_effect() {
|
||||||
|
check_output_with_stdin(
|
||||||
|
&example_file("effect", "Main.roc"),
|
||||||
|
"hello world how are you",
|
||||||
|
"effect-example",
|
||||||
|
&[],
|
||||||
|
"hello world how are you\n",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::llvm::convert::{
|
||||||
basic_type_from_builtin, basic_type_from_layout, block_of_memory_slices, ptr_int,
|
basic_type_from_builtin, basic_type_from_layout, block_of_memory_slices, ptr_int,
|
||||||
};
|
};
|
||||||
use crate::llvm::refcounting::{
|
use crate::llvm::refcounting::{
|
||||||
decrement_refcount_layout, increment_refcount_layout, PointerToRefcount,
|
build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount,
|
||||||
};
|
};
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
@ -893,7 +893,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TAG_ID_INDEX: u32 = 1;
|
const TAG_ID_INDEX: u32 = 1;
|
||||||
pub const TAG_DATA_INDEX: u32 = 0;
|
pub const TAG_DATA_INDEX: u32 = 0;
|
||||||
|
|
||||||
pub fn struct_from_fields<'a, 'ctx, 'env, I>(
|
pub fn struct_from_fields<'a, 'ctx, 'env, I>(
|
||||||
|
@ -919,6 +919,34 @@ where
|
||||||
struct_value.into_struct_value()
|
struct_value.into_struct_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn struct_pointer_from_fields<'a, 'ctx, 'env, I>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
struct_type: StructType<'ctx>,
|
||||||
|
input_pointer: PointerValue<'ctx>,
|
||||||
|
values: I,
|
||||||
|
) where
|
||||||
|
I: Iterator<Item = (usize, BasicValueEnum<'ctx>)>,
|
||||||
|
{
|
||||||
|
let struct_ptr = env
|
||||||
|
.builder
|
||||||
|
.build_bitcast(
|
||||||
|
input_pointer,
|
||||||
|
struct_type.ptr_type(AddressSpace::Generic),
|
||||||
|
"struct_ptr",
|
||||||
|
)
|
||||||
|
.into_pointer_value();
|
||||||
|
|
||||||
|
// Insert field exprs into struct_val
|
||||||
|
for (index, field_val) in values {
|
||||||
|
let field_ptr = env
|
||||||
|
.builder
|
||||||
|
.build_struct_gep(struct_ptr, index as u32, "field_struct_gep")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
env.builder.build_store(field_ptr, field_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_exp_expr<'a, 'ctx, 'env>(
|
pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
@ -969,16 +997,87 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()).into()
|
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reuse {
|
||||||
|
arguments,
|
||||||
|
tag_layout: union_layout,
|
||||||
|
tag_id,
|
||||||
|
symbol,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let reset = load_symbol(scope, symbol).into_pointer_value();
|
||||||
|
build_tag(
|
||||||
|
env,
|
||||||
|
scope,
|
||||||
|
union_layout,
|
||||||
|
*tag_id,
|
||||||
|
arguments,
|
||||||
|
Some(reset),
|
||||||
|
parent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Tag {
|
Tag {
|
||||||
arguments,
|
arguments,
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
union_size,
|
|
||||||
tag_id,
|
tag_id,
|
||||||
..
|
..
|
||||||
} => build_tag(env, scope, union_layout, *union_size, *tag_id, arguments),
|
} => build_tag(env, scope, union_layout, *tag_id, arguments, None, parent),
|
||||||
|
|
||||||
Reset(_) => todo!(),
|
Reset(symbol) => {
|
||||||
Reuse { .. } => todo!(),
|
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
|
||||||
|
let tag_ptr = tag_ptr.into_pointer_value();
|
||||||
|
|
||||||
|
// reset is only generated for union values
|
||||||
|
let union_layout = match layout {
|
||||||
|
Layout::Union(ul) => ul,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx = env.context;
|
||||||
|
let then_block = ctx.append_basic_block(parent, "then_reset");
|
||||||
|
let else_block = ctx.append_basic_block(parent, "else_decref");
|
||||||
|
let cont_block = ctx.append_basic_block(parent, "cont");
|
||||||
|
|
||||||
|
let refcount_ptr =
|
||||||
|
PointerToRefcount::from_ptr_to_data(env, tag_pointer_clear_tag_id(env, tag_ptr));
|
||||||
|
let is_unique = refcount_ptr.is_1(env);
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(is_unique, then_block, else_block);
|
||||||
|
|
||||||
|
{
|
||||||
|
// reset, when used on a unique reference, eagerly decrements the components of the
|
||||||
|
// referenced value, and returns the location of the now-invalid cell
|
||||||
|
env.builder.position_at_end(then_block);
|
||||||
|
|
||||||
|
let reset_function = build_reset(env, layout_ids, *union_layout);
|
||||||
|
let call = env
|
||||||
|
.builder
|
||||||
|
.build_call(reset_function, &[tag_ptr.into()], "call_reset");
|
||||||
|
|
||||||
|
call.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
|
let _ = call.try_as_basic_value();
|
||||||
|
|
||||||
|
env.builder.build_unconditional_branch(cont_block);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// If reset is used on a shared, non-reusable reference, it behaves
|
||||||
|
// like dec and returns NULL, which instructs reuse to behave like ctor
|
||||||
|
env.builder.position_at_end(else_block);
|
||||||
|
refcount_ptr.decrement(env, layout);
|
||||||
|
env.builder.build_unconditional_branch(cont_block);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(cont_block);
|
||||||
|
let phi = env.builder.build_phi(tag_ptr.get_type(), "branch");
|
||||||
|
|
||||||
|
let null_ptr = tag_ptr.get_type().const_null();
|
||||||
|
phi.add_incoming(&[(&tag_ptr, then_block), (&null_ptr, else_block)]);
|
||||||
|
|
||||||
|
phi.as_basic_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StructAtIndex {
|
StructAtIndex {
|
||||||
index, structure, ..
|
index, structure, ..
|
||||||
|
@ -1084,13 +1183,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
let tag_id_type =
|
let tag_id_type =
|
||||||
basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type();
|
basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type();
|
||||||
|
|
||||||
|
let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value());
|
||||||
|
|
||||||
lookup_at_index_ptr2(
|
lookup_at_index_ptr2(
|
||||||
env,
|
env,
|
||||||
union_layout,
|
union_layout,
|
||||||
tag_id_type,
|
tag_id_type,
|
||||||
field_layouts,
|
field_layouts,
|
||||||
*index as usize,
|
*index as usize,
|
||||||
argument.into_pointer_value(),
|
ptr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
UnionLayout::NonNullableUnwrapped(field_layouts) => {
|
UnionLayout::NonNullableUnwrapped(field_layouts) => {
|
||||||
|
@ -1125,13 +1226,14 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
let tag_id_type =
|
let tag_id_type =
|
||||||
basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type();
|
basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type();
|
||||||
|
|
||||||
|
let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value());
|
||||||
lookup_at_index_ptr2(
|
lookup_at_index_ptr2(
|
||||||
env,
|
env,
|
||||||
union_layout,
|
union_layout,
|
||||||
tag_id_type,
|
tag_id_type,
|
||||||
field_layouts,
|
field_layouts,
|
||||||
*index as usize,
|
*index as usize,
|
||||||
argument.into_pointer_value(),
|
ptr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
UnionLayout::NullableUnwrapped {
|
UnionLayout::NullableUnwrapped {
|
||||||
|
@ -1171,15 +1273,103 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn build_wrapped_tag<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
union_layout: &UnionLayout<'a>,
|
||||||
|
tag_id: u8,
|
||||||
|
arguments: &[Symbol],
|
||||||
|
tag_field_layouts: &[Layout<'a>],
|
||||||
|
tags: &[&[Layout<'a>]],
|
||||||
|
reuse_allocation: Option<PointerValue<'ctx>>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
let ctx = env.context;
|
||||||
|
let builder = env.builder;
|
||||||
|
|
||||||
|
let tag_id_layout = union_layout.tag_id_layout();
|
||||||
|
|
||||||
|
// Determine types
|
||||||
|
let num_fields = arguments.len() + 1;
|
||||||
|
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
|
||||||
|
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
||||||
|
|
||||||
|
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
|
||||||
|
let (val, _val_layout) = load_symbol_and_layout(scope, field_symbol);
|
||||||
|
|
||||||
|
let field_type = basic_type_from_layout(env, tag_field_layout);
|
||||||
|
|
||||||
|
field_types.push(field_type);
|
||||||
|
|
||||||
|
if let Layout::RecursivePointer = tag_field_layout {
|
||||||
|
debug_assert!(val.is_pointer_value());
|
||||||
|
|
||||||
|
// we store recursive pointers as `i64*`
|
||||||
|
let ptr = env.builder.build_bitcast(
|
||||||
|
val,
|
||||||
|
ctx.i64_type().ptr_type(AddressSpace::Generic),
|
||||||
|
"cast_recursive_pointer",
|
||||||
|
);
|
||||||
|
|
||||||
|
field_vals.push(ptr);
|
||||||
|
} else {
|
||||||
|
// this check fails for recursive tag unions, but can be helpful while debugging
|
||||||
|
// debug_assert_eq!(tag_field_layout, val_layout);
|
||||||
|
|
||||||
|
field_vals.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the struct_type
|
||||||
|
let raw_data_ptr = allocate_tag(env, parent, reuse_allocation, union_layout, tags);
|
||||||
|
let struct_type = env.context.struct_type(&field_types, false);
|
||||||
|
|
||||||
|
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
|
||||||
|
let tag_id_ptr = builder
|
||||||
|
.build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type();
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false));
|
||||||
|
|
||||||
|
let opaque_struct_ptr = builder
|
||||||
|
.build_struct_gep(raw_data_ptr, TAG_DATA_INDEX, "tag_data_index")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
struct_pointer_from_fields(
|
||||||
|
env,
|
||||||
|
struct_type,
|
||||||
|
opaque_struct_ptr,
|
||||||
|
field_vals.into_iter().enumerate(),
|
||||||
|
);
|
||||||
|
|
||||||
|
raw_data_ptr.into()
|
||||||
|
} else {
|
||||||
|
struct_pointer_from_fields(
|
||||||
|
env,
|
||||||
|
struct_type,
|
||||||
|
raw_data_ptr,
|
||||||
|
field_vals.into_iter().enumerate(),
|
||||||
|
);
|
||||||
|
|
||||||
|
tag_pointer_set_tag_id(env, tag_id, raw_data_ptr).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_tag<'a, 'ctx, 'env>(
|
pub fn build_tag<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
union_layout: &UnionLayout<'a>,
|
union_layout: &UnionLayout<'a>,
|
||||||
union_size: u8,
|
|
||||||
tag_id: u8,
|
tag_id: u8,
|
||||||
arguments: &[Symbol],
|
arguments: &[Symbol],
|
||||||
|
reuse_allocation: Option<PointerValue<'ctx>>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let tag_id_layout = union_layout.tag_id_layout();
|
let tag_id_layout = union_layout.tag_id_layout();
|
||||||
|
let union_size = union_layout.number_of_tags();
|
||||||
|
|
||||||
match union_layout {
|
match union_layout {
|
||||||
UnionLayout::NonRecursive(tags) => {
|
UnionLayout::NonRecursive(tags) => {
|
||||||
|
@ -1269,79 +1459,51 @@ pub fn build_tag<'a, 'ctx, 'env>(
|
||||||
UnionLayout::Recursive(tags) => {
|
UnionLayout::Recursive(tags) => {
|
||||||
debug_assert!(union_size > 1);
|
debug_assert!(union_size > 1);
|
||||||
|
|
||||||
let ctx = env.context;
|
|
||||||
let builder = env.builder;
|
|
||||||
|
|
||||||
// Determine types
|
|
||||||
let num_fields = arguments.len() + 1;
|
|
||||||
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
|
|
||||||
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
|
||||||
|
|
||||||
let tag_field_layouts = &tags[tag_id as usize];
|
let tag_field_layouts = &tags[tag_id as usize];
|
||||||
|
|
||||||
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
|
build_wrapped_tag(
|
||||||
let (val, _val_layout) = load_symbol_and_layout(scope, field_symbol);
|
env,
|
||||||
|
scope,
|
||||||
|
union_layout,
|
||||||
|
tag_id,
|
||||||
|
arguments,
|
||||||
|
&tag_field_layouts,
|
||||||
|
tags,
|
||||||
|
reuse_allocation,
|
||||||
|
parent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UnionLayout::NullableWrapped {
|
||||||
|
nullable_id,
|
||||||
|
other_tags: tags,
|
||||||
|
} => {
|
||||||
|
let tag_field_layouts = {
|
||||||
|
use std::cmp::Ordering::*;
|
||||||
|
match tag_id.cmp(&(*nullable_id as u8)) {
|
||||||
|
Equal => {
|
||||||
|
let layout = Layout::Union(*union_layout);
|
||||||
|
|
||||||
let field_type = basic_type_from_layout(env, tag_field_layout);
|
return basic_type_from_layout(env, &layout)
|
||||||
|
.into_pointer_type()
|
||||||
field_types.push(field_type);
|
.const_null()
|
||||||
|
.into();
|
||||||
if let Layout::RecursivePointer = tag_field_layout {
|
}
|
||||||
debug_assert!(val.is_pointer_value());
|
Less => &tags[tag_id as usize],
|
||||||
|
Greater => &tags[tag_id as usize - 1],
|
||||||
// we store recursive pointers as `i64*`
|
|
||||||
let ptr = env.builder.build_bitcast(
|
|
||||||
val,
|
|
||||||
ctx.i64_type().ptr_type(AddressSpace::Generic),
|
|
||||||
"cast_recursive_pointer",
|
|
||||||
);
|
|
||||||
|
|
||||||
field_vals.push(ptr);
|
|
||||||
} else {
|
|
||||||
// this check fails for recursive tag unions, but can be helpful while debugging
|
|
||||||
// debug_assert_eq!(tag_field_layout, val_layout);
|
|
||||||
|
|
||||||
field_vals.push(val);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Create the struct_type
|
build_wrapped_tag(
|
||||||
let raw_data_ptr =
|
env,
|
||||||
reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags);
|
scope,
|
||||||
|
union_layout,
|
||||||
let tag_id_ptr = builder
|
tag_id,
|
||||||
.build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index")
|
arguments,
|
||||||
.unwrap();
|
&tag_field_layouts,
|
||||||
|
tags,
|
||||||
let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type();
|
reuse_allocation,
|
||||||
|
parent,
|
||||||
env.builder
|
)
|
||||||
.build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false));
|
|
||||||
|
|
||||||
let opaque_struct_ptr = builder
|
|
||||||
.build_struct_gep(raw_data_ptr, TAG_DATA_INDEX, "tag_data_index")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let struct_type = env.context.struct_type(&field_types, false);
|
|
||||||
let struct_ptr = env
|
|
||||||
.builder
|
|
||||||
.build_bitcast(
|
|
||||||
opaque_struct_ptr,
|
|
||||||
struct_type.ptr_type(AddressSpace::Generic),
|
|
||||||
"struct_ptr",
|
|
||||||
)
|
|
||||||
.into_pointer_value();
|
|
||||||
|
|
||||||
// Insert field exprs into struct_val
|
|
||||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
|
||||||
let field_ptr = builder
|
|
||||||
.build_struct_gep(struct_ptr, index as u32, "field_struct_gep")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
builder.build_store(field_ptr, field_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_data_ptr.into()
|
|
||||||
}
|
}
|
||||||
UnionLayout::NonNullableUnwrapped(fields) => {
|
UnionLayout::NonNullableUnwrapped(fields) => {
|
||||||
debug_assert_eq!(union_size, 1);
|
debug_assert_eq!(union_size, 1);
|
||||||
|
@ -1349,7 +1511,6 @@ pub fn build_tag<'a, 'ctx, 'env>(
|
||||||
debug_assert_eq!(arguments.len(), fields.len());
|
debug_assert_eq!(arguments.len(), fields.len());
|
||||||
|
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
let builder = env.builder;
|
|
||||||
|
|
||||||
// Determine types
|
// Determine types
|
||||||
let num_fields = arguments.len() + 1;
|
let num_fields = arguments.len() + 1;
|
||||||
|
@ -1386,126 +1547,16 @@ pub fn build_tag<'a, 'ctx, 'env>(
|
||||||
reserve_with_refcount_union_as_block_of_memory(env, *union_layout, &[fields]);
|
reserve_with_refcount_union_as_block_of_memory(env, *union_layout, &[fields]);
|
||||||
|
|
||||||
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
|
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
|
||||||
let struct_ptr = env
|
|
||||||
.builder
|
|
||||||
.build_bitcast(
|
|
||||||
data_ptr,
|
|
||||||
struct_type.ptr_type(AddressSpace::Generic),
|
|
||||||
"block_of_memory_to_tag",
|
|
||||||
)
|
|
||||||
.into_pointer_value();
|
|
||||||
|
|
||||||
// Insert field exprs into struct_val
|
struct_pointer_from_fields(
|
||||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
env,
|
||||||
let field_ptr = builder
|
struct_type,
|
||||||
.build_struct_gep(struct_ptr, index as u32, "struct_gep")
|
data_ptr,
|
||||||
.unwrap();
|
field_vals.into_iter().enumerate(),
|
||||||
|
);
|
||||||
builder.build_store(field_ptr, field_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
data_ptr.into()
|
data_ptr.into()
|
||||||
}
|
}
|
||||||
UnionLayout::NullableWrapped {
|
|
||||||
nullable_id,
|
|
||||||
other_tags: tags,
|
|
||||||
} => {
|
|
||||||
if tag_id == *nullable_id as u8 {
|
|
||||||
let layout = Layout::Union(*union_layout);
|
|
||||||
|
|
||||||
return basic_type_from_layout(env, &layout)
|
|
||||||
.into_pointer_type()
|
|
||||||
.const_null()
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(union_size > 1);
|
|
||||||
|
|
||||||
let ctx = env.context;
|
|
||||||
let builder = env.builder;
|
|
||||||
|
|
||||||
// Determine types
|
|
||||||
let num_fields = arguments.len() + 1;
|
|
||||||
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
|
|
||||||
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
|
||||||
|
|
||||||
let tag_field_layouts = {
|
|
||||||
use std::cmp::Ordering::*;
|
|
||||||
match tag_id.cmp(&(*nullable_id as u8)) {
|
|
||||||
Equal => unreachable!("early return above"),
|
|
||||||
Less => &tags[tag_id as usize],
|
|
||||||
Greater => &tags[tag_id as usize - 1],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
|
|
||||||
let val = load_symbol(scope, field_symbol);
|
|
||||||
|
|
||||||
// Zero-sized fields have no runtime representation.
|
|
||||||
// The layout of the struct expects them to be dropped!
|
|
||||||
if !tag_field_layout.is_dropped_because_empty() {
|
|
||||||
let field_type = basic_type_from_layout(env, tag_field_layout);
|
|
||||||
|
|
||||||
field_types.push(field_type);
|
|
||||||
|
|
||||||
if let Layout::RecursivePointer = tag_field_layout {
|
|
||||||
debug_assert!(val.is_pointer_value());
|
|
||||||
|
|
||||||
// we store recursive pointers as `i64*`
|
|
||||||
let ptr = env.builder.build_bitcast(
|
|
||||||
val,
|
|
||||||
ctx.i64_type().ptr_type(AddressSpace::Generic),
|
|
||||||
"cast_recursive_pointer",
|
|
||||||
);
|
|
||||||
|
|
||||||
field_vals.push(ptr);
|
|
||||||
} else {
|
|
||||||
// this check fails for recursive tag unions, but can be helpful while debugging
|
|
||||||
// debug_assert_eq!(tag_field_layout, val_layout);
|
|
||||||
|
|
||||||
field_vals.push(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the struct_type
|
|
||||||
let raw_data_ptr =
|
|
||||||
reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags);
|
|
||||||
|
|
||||||
let tag_id_ptr = builder
|
|
||||||
.build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type();
|
|
||||||
|
|
||||||
env.builder
|
|
||||||
.build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false));
|
|
||||||
|
|
||||||
let opaque_struct_ptr = builder
|
|
||||||
.build_struct_gep(raw_data_ptr, TAG_DATA_INDEX, "tag_data_index")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let struct_type = env.context.struct_type(&field_types, false);
|
|
||||||
let struct_ptr = env
|
|
||||||
.builder
|
|
||||||
.build_bitcast(
|
|
||||||
opaque_struct_ptr,
|
|
||||||
struct_type.ptr_type(AddressSpace::Generic),
|
|
||||||
"struct_ptr",
|
|
||||||
)
|
|
||||||
.into_pointer_value();
|
|
||||||
|
|
||||||
// Insert field exprs into struct_val
|
|
||||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
|
||||||
let field_ptr = builder
|
|
||||||
.build_struct_gep(struct_ptr, index as u32, "field_struct_gep")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
builder.build_store(field_ptr, field_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_data_ptr.into()
|
|
||||||
}
|
|
||||||
UnionLayout::NullableUnwrapped {
|
UnionLayout::NullableUnwrapped {
|
||||||
nullable_id,
|
nullable_id,
|
||||||
other_fields,
|
other_fields,
|
||||||
|
@ -1526,7 +1577,6 @@ pub fn build_tag<'a, 'ctx, 'env>(
|
||||||
debug_assert!(union_size == 2);
|
debug_assert!(union_size == 2);
|
||||||
|
|
||||||
let ctx = env.context;
|
let ctx = env.context;
|
||||||
let builder = env.builder;
|
|
||||||
|
|
||||||
// Determine types
|
// Determine types
|
||||||
let num_fields = arguments.len() + 1;
|
let num_fields = arguments.len() + 1;
|
||||||
|
@ -1567,32 +1617,128 @@ pub fn build_tag<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
// Create the struct_type
|
// Create the struct_type
|
||||||
let data_ptr =
|
let data_ptr =
|
||||||
reserve_with_refcount_union_as_block_of_memory(env, *union_layout, &[other_fields]);
|
allocate_tag(env, parent, reuse_allocation, union_layout, &[other_fields]);
|
||||||
|
|
||||||
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
|
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
|
||||||
let struct_ptr = env
|
|
||||||
.builder
|
|
||||||
.build_bitcast(
|
|
||||||
data_ptr,
|
|
||||||
struct_type.ptr_type(AddressSpace::Generic),
|
|
||||||
"block_of_memory_to_tag",
|
|
||||||
)
|
|
||||||
.into_pointer_value();
|
|
||||||
|
|
||||||
// Insert field exprs into struct_val
|
struct_pointer_from_fields(
|
||||||
for (index, field_val) in field_vals.into_iter().enumerate() {
|
env,
|
||||||
let field_ptr = builder
|
struct_type,
|
||||||
.build_struct_gep(struct_ptr, index as u32, "struct_gep")
|
data_ptr,
|
||||||
.unwrap();
|
field_vals.into_iter().enumerate(),
|
||||||
|
);
|
||||||
builder.build_store(field_ptr, field_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
data_ptr.into()
|
data_ptr.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tag_pointer_set_tag_id<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
tag_id: u8,
|
||||||
|
pointer: PointerValue<'ctx>,
|
||||||
|
) -> PointerValue<'ctx> {
|
||||||
|
// we only have 3 bits, so can encode only 0..7
|
||||||
|
debug_assert!(tag_id < 8);
|
||||||
|
|
||||||
|
let ptr_int = env.ptr_int();
|
||||||
|
|
||||||
|
let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int");
|
||||||
|
|
||||||
|
let tag_id_intval = ptr_int.const_int(tag_id as u64, false);
|
||||||
|
let combined = env.builder.build_or(as_int, tag_id_intval, "store_tag_id");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_int_to_ptr(combined, pointer.get_type(), "to_ptr")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
pointer: PointerValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
let mask: u64 = 0b0000_0111;
|
||||||
|
|
||||||
|
let ptr_int = env.ptr_int();
|
||||||
|
|
||||||
|
let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int");
|
||||||
|
let mask_intval = env.ptr_int().const_int(mask, false);
|
||||||
|
|
||||||
|
let masked = env.builder.build_and(as_int, mask_intval, "mask");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_int_cast(masked, env.context.i8_type(), "to_u8")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
pointer: PointerValue<'ctx>,
|
||||||
|
) -> PointerValue<'ctx> {
|
||||||
|
let ptr_int = env.ptr_int();
|
||||||
|
|
||||||
|
let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int");
|
||||||
|
|
||||||
|
let mask = {
|
||||||
|
let a = env.ptr_int().const_all_ones();
|
||||||
|
let tag_id_bits = env.ptr_int().const_int(3, false);
|
||||||
|
env.builder.build_left_shift(a, tag_id_bits, "make_mask")
|
||||||
|
};
|
||||||
|
|
||||||
|
let masked = env.builder.build_and(as_int, mask, "masked");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_int_to_ptr(masked, pointer.get_type(), "to_ptr")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_tag<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
parent: FunctionValue<'ctx>,
|
||||||
|
reuse_allocation: Option<PointerValue<'ctx>>,
|
||||||
|
union_layout: &UnionLayout<'a>,
|
||||||
|
tags: &[&[Layout<'a>]],
|
||||||
|
) -> PointerValue<'ctx> {
|
||||||
|
match reuse_allocation {
|
||||||
|
Some(ptr) => {
|
||||||
|
// check if its a null pointer
|
||||||
|
let is_null_ptr = env.builder.build_is_null(ptr, "is_null_ptr");
|
||||||
|
let ctx = env.context;
|
||||||
|
let then_block = ctx.append_basic_block(parent, "then_allocate_fresh");
|
||||||
|
let else_block = ctx.append_basic_block(parent, "else_reuse");
|
||||||
|
let cont_block = ctx.append_basic_block(parent, "cont");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(is_null_ptr, then_block, else_block);
|
||||||
|
|
||||||
|
let raw_ptr = {
|
||||||
|
env.builder.position_at_end(then_block);
|
||||||
|
let raw_ptr =
|
||||||
|
reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags);
|
||||||
|
env.builder.build_unconditional_branch(cont_block);
|
||||||
|
raw_ptr
|
||||||
|
};
|
||||||
|
|
||||||
|
let reuse_ptr = {
|
||||||
|
env.builder.position_at_end(else_block);
|
||||||
|
|
||||||
|
let cleared = tag_pointer_clear_tag_id(env, ptr);
|
||||||
|
|
||||||
|
env.builder.build_unconditional_branch(cont_block);
|
||||||
|
|
||||||
|
cleared
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(cont_block);
|
||||||
|
let phi = env.builder.build_phi(raw_ptr.get_type(), "branch");
|
||||||
|
|
||||||
|
phi.add_incoming(&[(&raw_ptr, then_block), (&reuse_ptr, else_block)]);
|
||||||
|
|
||||||
|
phi.as_basic_value().into_pointer_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_tag_id<'a, 'ctx, 'env>(
|
pub fn get_tag_id<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
parent: FunctionValue<'ctx>,
|
parent: FunctionValue<'ctx>,
|
||||||
|
@ -1610,7 +1756,15 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
get_tag_id_non_recursive(env, tag)
|
get_tag_id_non_recursive(env, tag)
|
||||||
}
|
}
|
||||||
UnionLayout::Recursive(_) => get_tag_id_wrapped(env, argument.into_pointer_value()),
|
UnionLayout::Recursive(_) => {
|
||||||
|
let argument_ptr = argument.into_pointer_value();
|
||||||
|
|
||||||
|
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
|
||||||
|
get_tag_id_wrapped(env, argument_ptr)
|
||||||
|
} else {
|
||||||
|
tag_pointer_read_tag_id(env, argument_ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
UnionLayout::NonNullableUnwrapped(_) => tag_id_int_type.const_zero(),
|
UnionLayout::NonNullableUnwrapped(_) => tag_id_int_type.const_zero(),
|
||||||
UnionLayout::NullableWrapped { nullable_id, .. } => {
|
UnionLayout::NullableWrapped { nullable_id, .. } => {
|
||||||
let argument_ptr = argument.into_pointer_value();
|
let argument_ptr = argument.into_pointer_value();
|
||||||
|
@ -1635,7 +1789,12 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
{
|
{
|
||||||
env.builder.position_at_end(else_block);
|
env.builder.position_at_end(else_block);
|
||||||
let tag_id = get_tag_id_wrapped(env, argument_ptr);
|
|
||||||
|
let tag_id = if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
|
||||||
|
get_tag_id_wrapped(env, argument_ptr)
|
||||||
|
} else {
|
||||||
|
tag_pointer_read_tag_id(env, argument_ptr)
|
||||||
|
};
|
||||||
env.builder.build_store(result, tag_id);
|
env.builder.build_store(result, tag_id);
|
||||||
env.builder.build_unconditional_branch(cont_block);
|
env.builder.build_unconditional_branch(cont_block);
|
||||||
}
|
}
|
||||||
|
@ -1771,9 +1930,11 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>(
|
||||||
union_layout: UnionLayout<'a>,
|
union_layout: UnionLayout<'a>,
|
||||||
fields: &[&[Layout<'a>]],
|
fields: &[&[Layout<'a>]],
|
||||||
) -> PointerValue<'ctx> {
|
) -> PointerValue<'ctx> {
|
||||||
|
let ptr_bytes = env.ptr_bytes;
|
||||||
|
|
||||||
let block_type = block_of_memory_slices(env.context, fields, env.ptr_bytes);
|
let block_type = block_of_memory_slices(env.context, fields, env.ptr_bytes);
|
||||||
|
|
||||||
let basic_type = if union_layout.stores_tag_id() {
|
let basic_type = if union_layout.stores_tag_id_as_data(ptr_bytes) {
|
||||||
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
|
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
|
||||||
|
|
||||||
env.context
|
env.context
|
||||||
|
@ -1789,7 +1950,7 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>(
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if union_layout.stores_tag_id() {
|
if union_layout.stores_tag_id_as_data(ptr_bytes) {
|
||||||
stack_size += union_layout.tag_id_layout().stack_size(env.ptr_bytes);
|
stack_size += union_layout.tag_id_layout().stack_size(env.ptr_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2412,11 +2573,26 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
_ if layout.is_refcounted() => {
|
_ if layout.is_refcounted() => {
|
||||||
if value.is_pointer_value() {
|
if value.is_pointer_value() {
|
||||||
// BasicValueEnum::PointerValue(value_ptr) => {
|
|
||||||
let value_ptr = value.into_pointer_value();
|
let value_ptr = value.into_pointer_value();
|
||||||
let refcount_ptr =
|
|
||||||
PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
let then_block = env.context.append_basic_block(parent, "then");
|
||||||
refcount_ptr.decrement(env, layout);
|
let done_block = env.context.append_basic_block(parent, "done");
|
||||||
|
|
||||||
|
let condition =
|
||||||
|
env.builder.build_is_not_null(value_ptr, "box_is_not_null");
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(condition, then_block, done_block);
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(then_block);
|
||||||
|
let refcount_ptr =
|
||||||
|
PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
||||||
|
refcount_ptr.decrement(env, layout);
|
||||||
|
|
||||||
|
env.builder.build_unconditional_branch(done_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.builder.position_at_end(done_block);
|
||||||
} else {
|
} else {
|
||||||
eprint!("we're likely leaking memory; see issue #985 for details");
|
eprint!("we're likely leaking memory; see issue #985 for details");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::debug_info_init;
|
use crate::debug_info_init;
|
||||||
use crate::llvm::bitcode::call_bitcode_fn;
|
use crate::llvm::bitcode::call_bitcode_fn;
|
||||||
|
use crate::llvm::build::tag_pointer_clear_tag_id;
|
||||||
use crate::llvm::build::Env;
|
use crate::llvm::build::Env;
|
||||||
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
|
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
|
||||||
use crate::llvm::build_str;
|
use crate::llvm::build_str;
|
||||||
|
@ -493,14 +494,9 @@ fn hash_tag<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
|
|
||||||
// hash the tag data
|
// hash the tag data
|
||||||
let answer = hash_ptr_to_struct(
|
let tag = tag_pointer_clear_tag_id(env, tag.into_pointer_value());
|
||||||
env,
|
let answer =
|
||||||
layout_ids,
|
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
|
||||||
union_layout,
|
|
||||||
field_layouts,
|
|
||||||
seed,
|
|
||||||
tag.into_pointer_value(),
|
|
||||||
);
|
|
||||||
|
|
||||||
merge_phi.add_incoming(&[(&answer, block)]);
|
merge_phi.add_incoming(&[(&answer, block)]);
|
||||||
env.builder.build_unconditional_branch(merge_block);
|
env.builder.build_unconditional_branch(merge_block);
|
||||||
|
@ -598,6 +594,7 @@ fn hash_tag<'a, 'ctx, 'env>(
|
||||||
);
|
);
|
||||||
|
|
||||||
// hash tag data
|
// hash tag data
|
||||||
|
let tag = tag_pointer_clear_tag_id(env, tag);
|
||||||
let answer = hash_ptr_to_struct(
|
let answer = hash_ptr_to_struct(
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::llvm::build::Env;
|
|
||||||
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV};
|
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV};
|
||||||
|
use crate::llvm::build::{tag_pointer_clear_tag_id, Env};
|
||||||
use crate::llvm::build_list::{list_len, load_list_ptr};
|
use crate::llvm::build_list::{list_len, load_list_ptr};
|
||||||
use crate::llvm::build_str::str_equal;
|
use crate::llvm::build_str::str_equal;
|
||||||
use crate::llvm::convert::basic_type_from_layout;
|
use crate::llvm::convert::basic_type_from_layout;
|
||||||
|
@ -925,6 +925,10 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||||
let id1 = get_tag_id(env, parent, union_layout, tag1);
|
let id1 = get_tag_id(env, parent, union_layout, tag1);
|
||||||
let id2 = get_tag_id(env, parent, union_layout, tag2);
|
let id2 = get_tag_id(env, parent, union_layout, tag2);
|
||||||
|
|
||||||
|
// clear the tag_id so we get a pointer to the actual data
|
||||||
|
let tag1 = tag_pointer_clear_tag_id(env, tag1.into_pointer_value());
|
||||||
|
let tag2 = tag_pointer_clear_tag_id(env, tag2.into_pointer_value());
|
||||||
|
|
||||||
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
|
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
|
||||||
|
|
||||||
let same_tag =
|
let same_tag =
|
||||||
|
@ -944,14 +948,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||||
env.builder.position_at_end(block);
|
env.builder.position_at_end(block);
|
||||||
|
|
||||||
let answer = eq_ptr_to_struct(
|
let answer =
|
||||||
env,
|
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
|
||||||
layout_ids,
|
|
||||||
union_layout,
|
|
||||||
field_layouts,
|
|
||||||
tag1.into_pointer_value(),
|
|
||||||
tag2.into_pointer_value(),
|
|
||||||
);
|
|
||||||
|
|
||||||
env.builder.build_return(Some(&answer));
|
env.builder.build_return(Some(&answer));
|
||||||
|
|
||||||
|
@ -1073,6 +1071,10 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||||
let id1 = get_tag_id(env, parent, union_layout, tag1);
|
let id1 = get_tag_id(env, parent, union_layout, tag1);
|
||||||
let id2 = get_tag_id(env, parent, union_layout, tag2);
|
let id2 = get_tag_id(env, parent, union_layout, tag2);
|
||||||
|
|
||||||
|
// clear the tag_id so we get a pointer to the actual data
|
||||||
|
let tag1 = tag_pointer_clear_tag_id(env, tag1.into_pointer_value());
|
||||||
|
let tag2 = tag_pointer_clear_tag_id(env, tag2.into_pointer_value());
|
||||||
|
|
||||||
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
|
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
|
||||||
|
|
||||||
let same_tag =
|
let same_tag =
|
||||||
|
@ -1093,14 +1095,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||||
env.builder.position_at_end(block);
|
env.builder.position_at_end(block);
|
||||||
|
|
||||||
let answer = eq_ptr_to_struct(
|
let answer =
|
||||||
env,
|
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
|
||||||
layout_ids,
|
|
||||||
union_layout,
|
|
||||||
field_layouts,
|
|
||||||
tag1.into_pointer_value(),
|
|
||||||
tag2.into_pointer_value(),
|
|
||||||
);
|
|
||||||
|
|
||||||
env.builder.build_return(Some(&answer));
|
env.builder.build_return(Some(&answer));
|
||||||
|
|
||||||
|
|
|
@ -31,21 +31,31 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
||||||
basic_type_from_layout(env, &closure_data_layout)
|
basic_type_from_layout(env, &closure_data_layout)
|
||||||
}
|
}
|
||||||
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
|
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
|
||||||
Union(variant) => {
|
Union(union_layout) => {
|
||||||
use UnionLayout::*;
|
use UnionLayout::*;
|
||||||
|
|
||||||
let tag_id_type = basic_type_from_layout(env, &variant.tag_id_layout());
|
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
|
||||||
|
|
||||||
match variant {
|
match union_layout {
|
||||||
NullableWrapped {
|
NonRecursive(tags) => {
|
||||||
|
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
||||||
|
|
||||||
|
env.context.struct_type(&[data, tag_id_type], false).into()
|
||||||
|
}
|
||||||
|
Recursive(tags)
|
||||||
|
| NullableWrapped {
|
||||||
other_tags: tags, ..
|
other_tags: tags, ..
|
||||||
} => {
|
} => {
|
||||||
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
||||||
|
|
||||||
env.context
|
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
|
||||||
.struct_type(&[data, tag_id_type], false)
|
env.context
|
||||||
.ptr_type(AddressSpace::Generic)
|
.struct_type(&[data, tag_id_type], false)
|
||||||
.into()
|
.ptr_type(AddressSpace::Generic)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
data.ptr_type(AddressSpace::Generic).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
NullableUnwrapped { other_fields, .. } => {
|
NullableUnwrapped { other_fields, .. } => {
|
||||||
let block =
|
let block =
|
||||||
|
@ -56,19 +66,6 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
||||||
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
|
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
|
||||||
block.ptr_type(AddressSpace::Generic).into()
|
block.ptr_type(AddressSpace::Generic).into()
|
||||||
}
|
}
|
||||||
Recursive(tags) => {
|
|
||||||
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
|
||||||
|
|
||||||
env.context
|
|
||||||
.struct_type(&[data, tag_id_type], false)
|
|
||||||
.ptr_type(AddressSpace::Generic)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
NonRecursive(tags) => {
|
|
||||||
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
|
||||||
|
|
||||||
env.context.struct_type(&[data, tag_id_type], false).into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RecursivePointer => {
|
RecursivePointer => {
|
||||||
|
@ -145,16 +142,6 @@ pub fn union_data_is_struct_type<'ctx>(
|
||||||
context.struct_type(&[struct_type.into(), tag_id_type.into()], false)
|
context.struct_type(&[struct_type.into(), tag_id_type.into()], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn union_data_block_of_memory<'ctx>(
|
|
||||||
context: &'ctx Context,
|
|
||||||
tag_id_int_type: IntType<'ctx>,
|
|
||||||
layouts: &[&[Layout<'_>]],
|
|
||||||
ptr_bytes: u32,
|
|
||||||
) -> StructType<'ctx> {
|
|
||||||
let data_type = block_of_memory_slices(context, layouts, ptr_bytes);
|
|
||||||
context.struct_type(&[data_type, tag_id_int_type.into()], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_of_memory<'ctx>(
|
pub fn block_of_memory<'ctx>(
|
||||||
context: &'ctx Context,
|
context: &'ctx Context,
|
||||||
layout: &Layout<'_>,
|
layout: &Layout<'_>,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use crate::debug_info_init;
|
use crate::debug_info_init;
|
||||||
use crate::llvm::build::{
|
use crate::llvm::build::{
|
||||||
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
|
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
|
||||||
Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX,
|
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{basic_type_from_layout, ptr_int};
|
||||||
basic_type_from_layout, block_of_memory_slices, ptr_int, union_data_block_of_memory,
|
|
||||||
};
|
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use inkwell::basic_block::BasicBlock;
|
use inkwell::basic_block::BasicBlock;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
|
@ -644,70 +642,21 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
|
||||||
Union(variant) => {
|
Union(variant) => {
|
||||||
use UnionLayout::*;
|
use UnionLayout::*;
|
||||||
|
|
||||||
match variant {
|
if let NonRecursive(tags) = variant {
|
||||||
NullableWrapped {
|
let function = modify_refcount_union(env, layout_ids, mode, when_recursive, tags);
|
||||||
other_tags: tags, ..
|
|
||||||
} => {
|
|
||||||
let function = build_rec_union(
|
|
||||||
env,
|
|
||||||
layout_ids,
|
|
||||||
mode,
|
|
||||||
&WhenRecursive::Loop(*variant),
|
|
||||||
*variant,
|
|
||||||
tags,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(function)
|
return Some(function);
|
||||||
}
|
|
||||||
|
|
||||||
NullableUnwrapped { other_fields, .. } => {
|
|
||||||
let function = build_rec_union(
|
|
||||||
env,
|
|
||||||
layout_ids,
|
|
||||||
mode,
|
|
||||||
&WhenRecursive::Loop(*variant),
|
|
||||||
*variant,
|
|
||||||
env.arena.alloc([*other_fields]),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(function)
|
|
||||||
}
|
|
||||||
|
|
||||||
NonNullableUnwrapped(fields) => {
|
|
||||||
let function = build_rec_union(
|
|
||||||
env,
|
|
||||||
layout_ids,
|
|
||||||
mode,
|
|
||||||
&WhenRecursive::Loop(*variant),
|
|
||||||
*variant,
|
|
||||||
&*env.arena.alloc([*fields]),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
Some(function)
|
|
||||||
}
|
|
||||||
|
|
||||||
Recursive(tags) => {
|
|
||||||
let function = build_rec_union(
|
|
||||||
env,
|
|
||||||
layout_ids,
|
|
||||||
mode,
|
|
||||||
&WhenRecursive::Loop(*variant),
|
|
||||||
*variant,
|
|
||||||
tags,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
Some(function)
|
|
||||||
}
|
|
||||||
|
|
||||||
NonRecursive(tags) => {
|
|
||||||
let function =
|
|
||||||
modify_refcount_union(env, layout_ids, mode, when_recursive, tags);
|
|
||||||
|
|
||||||
Some(function)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let function = build_rec_union(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
mode,
|
||||||
|
&WhenRecursive::Loop(*variant),
|
||||||
|
*variant,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(function)
|
||||||
}
|
}
|
||||||
|
|
||||||
Closure(_, lambda_set, _) => {
|
Closure(_, lambda_set, _) => {
|
||||||
|
@ -1208,10 +1157,8 @@ fn build_rec_union<'a, 'ctx, 'env>(
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
when_recursive: &WhenRecursive<'a>,
|
when_recursive: &WhenRecursive<'a>,
|
||||||
union_layout: UnionLayout<'a>,
|
union_layout: UnionLayout<'a>,
|
||||||
tags: &'a [&'a [Layout<'a>]],
|
|
||||||
is_nullable: bool,
|
|
||||||
) -> FunctionValue<'ctx> {
|
) -> FunctionValue<'ctx> {
|
||||||
let layout = Layout::Union(UnionLayout::Recursive(tags));
|
let layout = Layout::Union(union_layout);
|
||||||
|
|
||||||
let (_, fn_name) = function_name_from_mode(
|
let (_, fn_name) = function_name_from_mode(
|
||||||
layout_ids,
|
layout_ids,
|
||||||
|
@ -1228,7 +1175,7 @@ fn build_rec_union<'a, 'ctx, 'env>(
|
||||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||||
|
|
||||||
let basic_type = basic_type_from_layout(env, &Layout::Union(union_layout));
|
let basic_type = basic_type_from_layout(env, &layout);
|
||||||
let function_value = build_header(env, basic_type, mode, &fn_name);
|
let function_value = build_header(env, basic_type, mode, &fn_name);
|
||||||
|
|
||||||
build_rec_union_help(
|
build_rec_union_help(
|
||||||
|
@ -1237,9 +1184,7 @@ fn build_rec_union<'a, 'ctx, 'env>(
|
||||||
mode,
|
mode,
|
||||||
when_recursive,
|
when_recursive,
|
||||||
union_layout,
|
union_layout,
|
||||||
tags,
|
|
||||||
function_value,
|
function_value,
|
||||||
is_nullable,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
env.builder.position_at_end(block);
|
env.builder.position_at_end(block);
|
||||||
|
@ -1260,10 +1205,10 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
when_recursive: &WhenRecursive<'a>,
|
when_recursive: &WhenRecursive<'a>,
|
||||||
union_layout: UnionLayout<'a>,
|
union_layout: UnionLayout<'a>,
|
||||||
tags: &'a [&'a [roc_mono::layout::Layout<'a>]],
|
|
||||||
fn_val: FunctionValue<'ctx>,
|
fn_val: FunctionValue<'ctx>,
|
||||||
is_nullable: bool,
|
|
||||||
) {
|
) {
|
||||||
|
let tags = union_layout_tags(env.arena, &union_layout);
|
||||||
|
let is_nullable = union_layout.is_nullable();
|
||||||
debug_assert!(!tags.is_empty());
|
debug_assert!(!tags.is_empty());
|
||||||
|
|
||||||
let context = &env.context;
|
let context = &env.context;
|
||||||
|
@ -1286,7 +1231,8 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
let parent = fn_val;
|
let parent = fn_val;
|
||||||
|
|
||||||
debug_assert!(arg_val.is_pointer_value());
|
debug_assert!(arg_val.is_pointer_value());
|
||||||
let value_ptr = arg_val.into_pointer_value();
|
let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val);
|
||||||
|
let value_ptr = tag_pointer_clear_tag_id(env, arg_val.into_pointer_value());
|
||||||
|
|
||||||
// to increment/decrement the cons-cell itself
|
// to increment/decrement the cons-cell itself
|
||||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
||||||
|
@ -1351,14 +1297,21 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
|
||||||
union_layout,
|
union_layout,
|
||||||
tags,
|
tags,
|
||||||
value_ptr,
|
value_ptr,
|
||||||
|
current_tag_id,
|
||||||
refcount_ptr,
|
refcount_ptr,
|
||||||
do_recurse_block,
|
do_recurse_block,
|
||||||
|
DecOrReuse::Dec,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DecOrReuse {
|
||||||
|
Dec,
|
||||||
|
Reuse,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
@ -1369,8 +1322,10 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
union_layout: UnionLayout<'a>,
|
union_layout: UnionLayout<'a>,
|
||||||
tags: &[&[Layout<'a>]],
|
tags: &[&[Layout<'a>]],
|
||||||
value_ptr: PointerValue<'ctx>,
|
value_ptr: PointerValue<'ctx>,
|
||||||
|
current_tag_id: IntValue<'ctx>,
|
||||||
refcount_ptr: PointerToRefcount<'ctx>,
|
refcount_ptr: PointerToRefcount<'ctx>,
|
||||||
match_block: BasicBlock<'ctx>,
|
match_block: BasicBlock<'ctx>,
|
||||||
|
decrement_or_reuse: DecOrReuse,
|
||||||
) {
|
) {
|
||||||
let mode = Mode::Dec;
|
let mode = Mode::Dec;
|
||||||
let call_mode = mode_to_call_mode(decrement_fn, mode);
|
let call_mode = mode_to_call_mode(decrement_fn, mode);
|
||||||
|
@ -1442,28 +1397,8 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
|
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
|
||||||
|
|
||||||
// therefore we must cast it to our desired type
|
// therefore we must cast it to our desired type
|
||||||
let union_type = match union_layout {
|
let union_type = basic_type_from_layout(env, &Layout::Union(union_layout));
|
||||||
UnionLayout::NonRecursive(_) => unreachable!(),
|
let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type);
|
||||||
UnionLayout::Recursive(_) | UnionLayout::NullableWrapped { .. } => {
|
|
||||||
union_data_block_of_memory(
|
|
||||||
env.context,
|
|
||||||
tag_id_int_type,
|
|
||||||
tags,
|
|
||||||
env.ptr_bytes,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
UnionLayout::NonNullableUnwrapped { .. }
|
|
||||||
| UnionLayout::NullableUnwrapped { .. } => {
|
|
||||||
block_of_memory_slices(env.context, tags, env.ptr_bytes)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let recursive_field_ptr = cast_basic_basic(
|
|
||||||
env.builder,
|
|
||||||
ptr_as_i64_ptr,
|
|
||||||
union_type.ptr_type(AddressSpace::Generic).into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
deferred_rec.push(recursive_field_ptr);
|
deferred_rec.push(recursive_field_ptr);
|
||||||
} else if field_layout.contains_refcounted() {
|
} else if field_layout.contains_refcounted() {
|
||||||
|
@ -1486,7 +1421,13 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
// lists. To achieve it, we must first load all fields that we want to inc/dec (done above)
|
// lists. To achieve it, we must first load all fields that we want to inc/dec (done above)
|
||||||
// and store them on the stack, then modify (and potentially free) the current cell, then
|
// and store them on the stack, then modify (and potentially free) the current cell, then
|
||||||
// actually inc/dec the fields.
|
// actually inc/dec the fields.
|
||||||
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
|
|
||||||
|
match decrement_or_reuse {
|
||||||
|
DecOrReuse::Reuse => {}
|
||||||
|
DecOrReuse::Dec => {
|
||||||
|
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (field, field_layout) in deferred_nonrec {
|
for (field, field_layout) in deferred_nonrec {
|
||||||
modify_refcount_layout_help(
|
modify_refcount_layout_help(
|
||||||
|
@ -1524,25 +1465,182 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||||
let (_, only_branch) = cases.pop().unwrap();
|
let (_, only_branch) = cases.pop().unwrap();
|
||||||
env.builder.build_unconditional_branch(only_branch);
|
env.builder.build_unconditional_branch(only_branch);
|
||||||
} else {
|
} else {
|
||||||
// read the tag_id
|
let default_block = env.context.append_basic_block(parent, "switch_default");
|
||||||
let current_tag_id = get_tag_id(env, parent, &union_layout, value_ptr.into());
|
|
||||||
|
|
||||||
let merge_block = env.context.append_basic_block(parent, "decrement_merge");
|
|
||||||
|
|
||||||
// switch on it
|
// switch on it
|
||||||
env.builder
|
env.builder
|
||||||
.build_switch(current_tag_id, merge_block, &cases);
|
.build_switch(current_tag_id, default_block, &cases);
|
||||||
|
|
||||||
env.builder.position_at_end(merge_block);
|
{
|
||||||
|
env.builder.position_at_end(default_block);
|
||||||
|
|
||||||
// increment/decrement the cons-cell itself
|
// increment/decrement the cons-cell itself
|
||||||
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
|
if let DecOrReuse::Dec = decrement_or_reuse {
|
||||||
|
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this function returns void
|
// this function returns void
|
||||||
builder.build_return(None);
|
builder.build_return(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn union_layout_tags<'a>(
|
||||||
|
arena: &'a bumpalo::Bump,
|
||||||
|
union_layout: &UnionLayout<'a>,
|
||||||
|
) -> &'a [&'a [Layout<'a>]] {
|
||||||
|
use UnionLayout::*;
|
||||||
|
|
||||||
|
match union_layout {
|
||||||
|
NullableWrapped {
|
||||||
|
other_tags: tags, ..
|
||||||
|
} => *tags,
|
||||||
|
NullableUnwrapped { other_fields, .. } => arena.alloc([*other_fields]),
|
||||||
|
NonNullableUnwrapped(fields) => arena.alloc([*fields]),
|
||||||
|
Recursive(tags) => tags,
|
||||||
|
NonRecursive(tags) => tags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_reset<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
union_layout: UnionLayout<'a>,
|
||||||
|
) -> FunctionValue<'ctx> {
|
||||||
|
let mode = Mode::Dec;
|
||||||
|
|
||||||
|
let layout_id = layout_ids.get(Symbol::DEC, &Layout::Union(union_layout));
|
||||||
|
let fn_name = layout_id.to_symbol_string(Symbol::DEC, &env.interns);
|
||||||
|
let fn_name = format!("{}_reset", fn_name);
|
||||||
|
|
||||||
|
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||||
|
let dec_function = build_rec_union(env, layout_ids, Mode::Dec, &when_recursive, union_layout);
|
||||||
|
|
||||||
|
let function = match env.module.get_function(fn_name.as_str()) {
|
||||||
|
Some(function_value) => function_value,
|
||||||
|
None => {
|
||||||
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||||
|
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||||
|
|
||||||
|
let basic_type = basic_type_from_layout(env, &Layout::Union(union_layout));
|
||||||
|
let function_value = build_header(env, basic_type, mode, &fn_name);
|
||||||
|
|
||||||
|
build_reuse_rec_union_help(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
&when_recursive,
|
||||||
|
union_layout,
|
||||||
|
function_value,
|
||||||
|
dec_function,
|
||||||
|
);
|
||||||
|
|
||||||
|
env.builder.position_at_end(block);
|
||||||
|
env.builder
|
||||||
|
.set_current_debug_location(env.context, di_location);
|
||||||
|
|
||||||
|
function_value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
when_recursive: &WhenRecursive<'a>,
|
||||||
|
union_layout: UnionLayout<'a>,
|
||||||
|
reset_function: FunctionValue<'ctx>,
|
||||||
|
dec_function: FunctionValue<'ctx>,
|
||||||
|
) {
|
||||||
|
let tags = union_layout_tags(env.arena, &union_layout);
|
||||||
|
let is_nullable = union_layout.is_nullable();
|
||||||
|
|
||||||
|
debug_assert!(!tags.is_empty());
|
||||||
|
|
||||||
|
let context = &env.context;
|
||||||
|
let builder = env.builder;
|
||||||
|
|
||||||
|
// Add a basic block for the entry point
|
||||||
|
let entry = context.append_basic_block(reset_function, "entry");
|
||||||
|
|
||||||
|
builder.position_at_end(entry);
|
||||||
|
|
||||||
|
debug_info_init!(env, reset_function);
|
||||||
|
|
||||||
|
// Add args to scope
|
||||||
|
let arg_symbol = Symbol::ARG_1;
|
||||||
|
|
||||||
|
let arg_val = reset_function.get_param_iter().next().unwrap();
|
||||||
|
|
||||||
|
arg_val.set_name(arg_symbol.ident_string(&env.interns));
|
||||||
|
|
||||||
|
let parent = reset_function;
|
||||||
|
|
||||||
|
debug_assert!(arg_val.is_pointer_value());
|
||||||
|
let current_tag_id = get_tag_id(env, reset_function, &union_layout, arg_val);
|
||||||
|
let value_ptr = tag_pointer_clear_tag_id(env, arg_val.into_pointer_value());
|
||||||
|
|
||||||
|
// to increment/decrement the cons-cell itself
|
||||||
|
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
||||||
|
let call_mode = CallMode::Dec;
|
||||||
|
|
||||||
|
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
|
||||||
|
|
||||||
|
let ctx = env.context;
|
||||||
|
if is_nullable {
|
||||||
|
let is_null = env.builder.build_is_null(value_ptr, "is_null");
|
||||||
|
|
||||||
|
let then_block = ctx.append_basic_block(parent, "then");
|
||||||
|
|
||||||
|
env.builder
|
||||||
|
.build_conditional_branch(is_null, then_block, should_recurse_block);
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(then_block);
|
||||||
|
env.builder.build_return(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env.builder.build_unconditional_branch(should_recurse_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.builder.position_at_end(should_recurse_block);
|
||||||
|
|
||||||
|
let layout = Layout::Union(union_layout);
|
||||||
|
|
||||||
|
let do_recurse_block = env.context.append_basic_block(parent, "do_recurse");
|
||||||
|
let no_recurse_block = env.context.append_basic_block(parent, "no_recurse");
|
||||||
|
|
||||||
|
builder.build_conditional_branch(refcount_ptr.is_1(env), do_recurse_block, no_recurse_block);
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(no_recurse_block);
|
||||||
|
|
||||||
|
refcount_ptr.modify(call_mode, &layout, env);
|
||||||
|
env.builder.build_return(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
env.builder.position_at_end(do_recurse_block);
|
||||||
|
|
||||||
|
build_rec_union_recursive_decrement(
|
||||||
|
env,
|
||||||
|
layout_ids,
|
||||||
|
when_recursive,
|
||||||
|
parent,
|
||||||
|
dec_function,
|
||||||
|
union_layout,
|
||||||
|
tags,
|
||||||
|
value_ptr,
|
||||||
|
current_tag_id,
|
||||||
|
refcount_ptr,
|
||||||
|
do_recurse_block,
|
||||||
|
DecOrReuse::Reuse,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn function_name_from_mode<'a>(
|
fn function_name_from_mode<'a>(
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
interns: &Interns,
|
interns: &Interns,
|
||||||
|
|
|
@ -2047,7 +2047,7 @@ fn update<'a>(
|
||||||
}
|
}
|
||||||
MadeSpecializations {
|
MadeSpecializations {
|
||||||
module_id,
|
module_id,
|
||||||
ident_ids,
|
mut ident_ids,
|
||||||
subs,
|
subs,
|
||||||
procedures,
|
procedures,
|
||||||
external_specializations_requested,
|
external_specializations_requested,
|
||||||
|
@ -2070,6 +2070,15 @@ fn update<'a>(
|
||||||
&& state.dependencies.solved_all()
|
&& state.dependencies.solved_all()
|
||||||
&& state.goal_phase == Phase::MakeSpecializations
|
&& state.goal_phase == Phase::MakeSpecializations
|
||||||
{
|
{
|
||||||
|
Proc::insert_reset_reuse_operations(
|
||||||
|
arena,
|
||||||
|
module_id,
|
||||||
|
&mut ident_ids,
|
||||||
|
&mut state.procedures,
|
||||||
|
);
|
||||||
|
|
||||||
|
Proc::insert_refcount_operations(arena, &mut state.procedures);
|
||||||
|
|
||||||
// display the mono IR of the module, for debug purposes
|
// display the mono IR of the module, for debug purposes
|
||||||
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
|
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
|
||||||
let procs_string = state
|
let procs_string = state
|
||||||
|
@ -2083,8 +2092,6 @@ fn update<'a>(
|
||||||
println!("{}", result);
|
println!("{}", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Proc::insert_refcount_operations(arena, &mut state.procedures);
|
|
||||||
|
|
||||||
// This is not safe with the new non-recursive RC updates that we do for tag unions
|
// This is not safe with the new non-recursive RC updates that we do for tag unions
|
||||||
//
|
//
|
||||||
// Proc::optimize_refcount_operations(
|
// Proc::optimize_refcount_operations(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use morphic_lib::TypeContext;
|
use morphic_lib::TypeContext;
|
||||||
use morphic_lib::{
|
use morphic_lib::{
|
||||||
BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext,
|
BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext,
|
||||||
FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId,
|
FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result,
|
||||||
UpdateModeVar, ValueId,
|
TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId,
|
||||||
};
|
};
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
@ -26,6 +26,26 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
|
||||||
const DEBUG: bool = false;
|
const DEBUG: bool = false;
|
||||||
const SIZE: usize = if DEBUG { 50 } else { 16 };
|
const SIZE: usize = if DEBUG { 50 } else { 16 };
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Hash)]
|
||||||
|
struct TagUnionId(u64);
|
||||||
|
|
||||||
|
fn recursive_tag_union_name_bytes(union_layout: &UnionLayout) -> TagUnionId {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
union_layout.hash(&mut hasher);
|
||||||
|
|
||||||
|
TagUnionId(hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TagUnionId {
|
||||||
|
const fn as_bytes(&self) -> [u8; 8] {
|
||||||
|
self.0.to_ne_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn func_name_bytes_help<'a, I>(
|
pub fn func_name_bytes_help<'a, I>(
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
argument_layouts: I,
|
argument_layouts: I,
|
||||||
|
@ -134,6 +154,8 @@ where
|
||||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||||
m.add_func(entry_point_name, entry_point_function)?;
|
m.add_func(entry_point_name, entry_point_function)?;
|
||||||
|
|
||||||
|
let mut type_definitions = MutSet::default();
|
||||||
|
|
||||||
// all other functions
|
// all other functions
|
||||||
for proc in procs {
|
for proc in procs {
|
||||||
let bytes = func_name_bytes(proc);
|
let bytes = func_name_bytes(proc);
|
||||||
|
@ -148,11 +170,32 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let spec = proc_spec(proc)?;
|
let (spec, type_names) = proc_spec(proc)?;
|
||||||
|
|
||||||
|
type_definitions.extend(type_names);
|
||||||
|
|
||||||
m.add_func(func_name, spec)?;
|
m.add_func(func_name, spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for union_layout in type_definitions {
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
let mut builder = TypeDefBuilder::new();
|
||||||
|
|
||||||
|
let variant_types = build_variant_types(&mut builder, &union_layout)?;
|
||||||
|
let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout {
|
||||||
|
debug_assert_eq!(variant_types.len(), 1);
|
||||||
|
variant_types[0]
|
||||||
|
} else {
|
||||||
|
builder.add_union_type(&variant_types)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let type_def = builder.build(root_type)?;
|
||||||
|
|
||||||
|
m.add_named_type(type_name, type_def)?;
|
||||||
|
}
|
||||||
|
|
||||||
m.build()?
|
m.build()?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -195,7 +238,7 @@ fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Resu
|
||||||
Ok(spec)
|
Ok(spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn proc_spec(proc: &Proc) -> Result<FuncDef> {
|
fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)> {
|
||||||
let mut builder = FuncDefBuilder::new();
|
let mut builder = FuncDefBuilder::new();
|
||||||
let mut env = Env::default();
|
let mut env = Env::default();
|
||||||
|
|
||||||
|
@ -218,21 +261,22 @@ fn proc_spec(proc: &Proc) -> Result<FuncDef> {
|
||||||
|
|
||||||
let spec = builder.build(arg_type_id, ret_type_id, root)?;
|
let spec = builder.build(arg_type_id, ret_type_id, root)?;
|
||||||
|
|
||||||
Ok(spec)
|
Ok((spec, env.type_names))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Env {
|
struct Env<'a> {
|
||||||
symbols: MutMap<Symbol, ValueId>,
|
symbols: MutMap<Symbol, ValueId>,
|
||||||
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>,
|
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>,
|
||||||
|
type_names: MutSet<UnionLayout<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stmt_spec(
|
fn stmt_spec<'a>(
|
||||||
builder: &mut FuncDefBuilder,
|
builder: &mut FuncDefBuilder,
|
||||||
env: &mut Env,
|
env: &mut Env<'a>,
|
||||||
block: BlockId,
|
block: BlockId,
|
||||||
layout: &Layout,
|
layout: &Layout,
|
||||||
stmt: &Stmt,
|
stmt: &Stmt<'a>,
|
||||||
) -> Result<ValueId> {
|
) -> Result<ValueId> {
|
||||||
use Stmt::*;
|
use Stmt::*;
|
||||||
|
|
||||||
|
@ -420,7 +464,27 @@ fn build_tuple_value(
|
||||||
builder.add_make_tuple(block, &value_ids)
|
builder.add_make_tuple(block, &value_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_tuple_type(builder: &mut FuncDefBuilder, layouts: &[Layout]) -> Result<TypeId> {
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum WhenRecursive<'a> {
|
||||||
|
Unreachable,
|
||||||
|
Loop(UnionLayout<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_recursive_tuple_type(
|
||||||
|
builder: &mut impl TypeContext,
|
||||||
|
layouts: &[Layout],
|
||||||
|
when_recursive: &WhenRecursive,
|
||||||
|
) -> Result<TypeId> {
|
||||||
|
let mut field_types = Vec::new();
|
||||||
|
|
||||||
|
for field in layouts.iter() {
|
||||||
|
field_types.push(layout_spec_help(builder, field, when_recursive)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.add_tuple_type(&field_types)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result<TypeId> {
|
||||||
let mut field_types = Vec::new();
|
let mut field_types = Vec::new();
|
||||||
|
|
||||||
for field in layouts.iter() {
|
for field in layouts.iter() {
|
||||||
|
@ -854,73 +918,161 @@ fn lowlevel_spec(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recursive_tag_variant(
|
||||||
|
builder: &mut impl TypeContext,
|
||||||
|
union_layout: &UnionLayout,
|
||||||
|
fields: &[Layout],
|
||||||
|
) -> Result<TypeId> {
|
||||||
|
let when_recursive = WhenRecursive::Loop(*union_layout);
|
||||||
|
|
||||||
|
let data_id = build_recursive_tuple_type(builder, fields, &when_recursive)?;
|
||||||
|
let cell_id = builder.add_heap_cell_type();
|
||||||
|
|
||||||
|
builder.add_tuple_type(&[cell_id, data_id])
|
||||||
|
}
|
||||||
|
|
||||||
fn build_variant_types(
|
fn build_variant_types(
|
||||||
builder: &mut FuncDefBuilder,
|
builder: &mut impl TypeContext,
|
||||||
union_layout: &UnionLayout,
|
union_layout: &UnionLayout,
|
||||||
) -> Result<Vec<TypeId>> {
|
) -> Result<Vec<TypeId>> {
|
||||||
use UnionLayout::*;
|
use UnionLayout::*;
|
||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result;
|
||||||
|
|
||||||
match union_layout {
|
match union_layout {
|
||||||
NonRecursive(tags) => {
|
NonRecursive(tags) => {
|
||||||
|
result = Vec::with_capacity(tags.len());
|
||||||
|
|
||||||
for tag in tags.iter() {
|
for tag in tags.iter() {
|
||||||
result.push(build_tuple_type(builder, tag)?);
|
result.push(build_tuple_type(builder, tag)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Recursive(_) => unreachable!(),
|
Recursive(tags) => {
|
||||||
NonNullableUnwrapped(_) => unreachable!(),
|
result = Vec::with_capacity(tags.len());
|
||||||
|
|
||||||
|
for tag in tags.iter() {
|
||||||
|
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NonNullableUnwrapped(fields) => {
|
||||||
|
result = vec![recursive_tag_variant(builder, union_layout, fields)?];
|
||||||
|
}
|
||||||
NullableWrapped {
|
NullableWrapped {
|
||||||
nullable_id: _,
|
nullable_id,
|
||||||
other_tags: _,
|
other_tags: tags,
|
||||||
} => unreachable!(),
|
} => {
|
||||||
|
result = Vec::with_capacity(tags.len() + 1);
|
||||||
|
|
||||||
|
let cutoff = *nullable_id as usize;
|
||||||
|
|
||||||
|
for tag in tags[..cutoff].iter() {
|
||||||
|
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let unit = builder.add_tuple_type(&[])?;
|
||||||
|
result.push(unit);
|
||||||
|
|
||||||
|
for tag in tags[cutoff..].iter() {
|
||||||
|
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
NullableUnwrapped {
|
NullableUnwrapped {
|
||||||
nullable_id: _,
|
nullable_id,
|
||||||
other_fields: _,
|
other_fields: fields,
|
||||||
} => unreachable!(),
|
} => {
|
||||||
|
let unit = builder.add_tuple_type(&[])?;
|
||||||
|
let other_type = recursive_tag_variant(builder, union_layout, fields)?;
|
||||||
|
|
||||||
|
if *nullable_id {
|
||||||
|
// nullable_id == 1
|
||||||
|
result = vec![other_type, unit];
|
||||||
|
} else {
|
||||||
|
result = vec![unit, other_type];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
|
fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
|
||||||
let cell = context.add_heap_cell_type();
|
let cell = context.add_heap_cell_type();
|
||||||
context.add_bag_type(cell)
|
context.add_bag_type(cell)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_spec(
|
fn expr_spec<'a>(
|
||||||
builder: &mut FuncDefBuilder,
|
builder: &mut FuncDefBuilder,
|
||||||
env: &mut Env,
|
env: &mut Env<'a>,
|
||||||
block: BlockId,
|
block: BlockId,
|
||||||
layout: &Layout,
|
layout: &Layout<'a>,
|
||||||
expr: &Expr,
|
expr: &Expr<'a>,
|
||||||
) -> Result<ValueId> {
|
) -> Result<ValueId> {
|
||||||
use Expr::*;
|
use Expr::*;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Literal(literal) => literal_spec(builder, block, literal),
|
Literal(literal) => literal_spec(builder, block, literal),
|
||||||
Call(call) => call_spec(builder, env, block, layout, call),
|
Call(call) => call_spec(builder, env, block, layout, call),
|
||||||
Tag {
|
Reuse {
|
||||||
tag_layout,
|
tag_layout,
|
||||||
tag_name: _,
|
tag_name: _,
|
||||||
tag_id,
|
tag_id,
|
||||||
union_size: _,
|
|
||||||
arguments,
|
arguments,
|
||||||
} => match tag_layout {
|
..
|
||||||
UnionLayout::NonRecursive(_) => {
|
}
|
||||||
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
| Tag {
|
||||||
let variant_types = build_variant_types(builder, tag_layout)?;
|
tag_layout,
|
||||||
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)
|
tag_name: _,
|
||||||
}
|
tag_id,
|
||||||
UnionLayout::Recursive(_)
|
arguments,
|
||||||
| UnionLayout::NonNullableUnwrapped(_)
|
} => {
|
||||||
| UnionLayout::NullableWrapped { .. }
|
let variant_types = build_variant_types(builder, tag_layout)?;
|
||||||
| UnionLayout::NullableUnwrapped { .. } => {
|
|
||||||
let result_type = worst_case_type(builder)?;
|
let data_id = build_tuple_value(builder, env, block, arguments)?;
|
||||||
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
let cell_id = builder.add_new_heap_cell(block)?;
|
||||||
builder.add_unknown_with(block, &[value_id], result_type)
|
|
||||||
}
|
let value_id = match tag_layout {
|
||||||
},
|
UnionLayout::NonRecursive(_) => {
|
||||||
|
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
||||||
|
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
|
||||||
|
}
|
||||||
|
UnionLayout::NonNullableUnwrapped(_) => {
|
||||||
|
let value_id = builder.add_make_tuple(block, &[cell_id, data_id])?;
|
||||||
|
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
env.type_names.insert(*tag_layout);
|
||||||
|
|
||||||
|
return builder.add_make_named(block, MOD_APP, type_name, value_id);
|
||||||
|
}
|
||||||
|
UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?,
|
||||||
|
UnionLayout::NullableWrapped { nullable_id, .. } => {
|
||||||
|
if *tag_id == *nullable_id as u8 {
|
||||||
|
data_id
|
||||||
|
} else {
|
||||||
|
builder.add_make_tuple(block, &[cell_id, data_id])?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
|
||||||
|
if *tag_id == *nullable_id as u8 {
|
||||||
|
data_id
|
||||||
|
} else {
|
||||||
|
builder.add_make_tuple(block, &[cell_id, data_id])?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let union_id =
|
||||||
|
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?;
|
||||||
|
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
env.type_names.insert(*tag_layout);
|
||||||
|
|
||||||
|
builder.add_make_named(block, MOD_APP, type_name, union_id)
|
||||||
|
}
|
||||||
Struct(fields) => build_tuple_value(builder, env, block, fields),
|
Struct(fields) => build_tuple_value(builder, env, block, fields),
|
||||||
UnionAtIndex {
|
UnionAtIndex {
|
||||||
index,
|
index,
|
||||||
|
@ -936,11 +1088,45 @@ fn expr_spec(
|
||||||
|
|
||||||
builder.add_get_tuple_field(block, tuple_value_id, index)
|
builder.add_get_tuple_field(block, tuple_value_id, index)
|
||||||
}
|
}
|
||||||
_ => {
|
UnionLayout::Recursive(_)
|
||||||
// for the moment recursive tag unions don't quite work
|
| UnionLayout::NullableUnwrapped { .. }
|
||||||
let value_id = env.symbols[structure];
|
| UnionLayout::NullableWrapped { .. } => {
|
||||||
let result_type = layout_spec(builder, layout)?;
|
let index = (*index) as u32;
|
||||||
builder.add_unknown_with(block, &[value_id], result_type)
|
let tag_value_id = env.symbols[structure];
|
||||||
|
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
||||||
|
let variant_id = builder.add_unwrap_union(block, union_id, *tag_id as u32)?;
|
||||||
|
|
||||||
|
// we're reading from this value, so touch the heap cell
|
||||||
|
let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?;
|
||||||
|
builder.add_touch(block, heap_cell)?;
|
||||||
|
|
||||||
|
let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?;
|
||||||
|
|
||||||
|
builder.add_get_tuple_field(block, tuple_value_id, index)
|
||||||
|
}
|
||||||
|
UnionLayout::NonNullableUnwrapped { .. } => {
|
||||||
|
let index = (*index) as u32;
|
||||||
|
debug_assert!(*tag_id == 0);
|
||||||
|
|
||||||
|
let tag_value_id = env.symbols[structure];
|
||||||
|
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
let variant_id =
|
||||||
|
builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
||||||
|
|
||||||
|
// we're reading from this value, so touch the heap cell
|
||||||
|
let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?;
|
||||||
|
builder.add_touch(block, heap_cell)?;
|
||||||
|
|
||||||
|
let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?;
|
||||||
|
|
||||||
|
builder.add_get_tuple_field(block, tuple_value_id, index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
StructAtIndex {
|
StructAtIndex {
|
||||||
|
@ -983,8 +1169,12 @@ fn expr_spec(
|
||||||
Err(()) => unreachable!("empty array does not have a list layout"),
|
Err(()) => unreachable!("empty array does not have a list layout"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reuse { .. } => todo!("currently unused"),
|
Reset(symbol) => {
|
||||||
Reset(_) => todo!("currently unused"),
|
let type_id = layout_spec(builder, layout)?;
|
||||||
|
let value_id = env.symbols[symbol];
|
||||||
|
|
||||||
|
builder.add_unknown_with(block, &[value_id], type_id)
|
||||||
|
}
|
||||||
RuntimeErrorFunction(_) => {
|
RuntimeErrorFunction(_) => {
|
||||||
let type_id = layout_spec(builder, layout)?;
|
let type_id = layout_spec(builder, layout)?;
|
||||||
|
|
||||||
|
@ -1007,34 +1197,66 @@ fn literal_spec(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_spec(builder: &mut FuncDefBuilder, layout: &Layout) -> Result<TypeId> {
|
fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId> {
|
||||||
|
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_spec_help(
|
||||||
|
builder: &mut impl TypeContext,
|
||||||
|
layout: &Layout,
|
||||||
|
when_recursive: &WhenRecursive,
|
||||||
|
) -> Result<TypeId> {
|
||||||
use Layout::*;
|
use Layout::*;
|
||||||
|
|
||||||
match layout {
|
match layout {
|
||||||
Builtin(builtin) => builtin_spec(builder, builtin),
|
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
|
||||||
Struct(fields) => build_tuple_type(builder, fields),
|
Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive),
|
||||||
Union(union_layout) => match union_layout {
|
Union(union_layout) => {
|
||||||
UnionLayout::NonRecursive(_) => {
|
let variant_types = build_variant_types(builder, union_layout)?;
|
||||||
let variant_types = build_variant_types(builder, union_layout)?;
|
|
||||||
builder.add_union_type(&variant_types)
|
match union_layout {
|
||||||
|
UnionLayout::NonRecursive(_) => builder.add_union_type(&variant_types),
|
||||||
|
UnionLayout::Recursive(_)
|
||||||
|
| UnionLayout::NullableUnwrapped { .. }
|
||||||
|
| UnionLayout::NullableWrapped { .. }
|
||||||
|
| UnionLayout::NonNullableUnwrapped(_) => {
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
Ok(builder.add_named_type(MOD_APP, type_name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
UnionLayout::Recursive(_) => worst_case_type(builder),
|
}
|
||||||
UnionLayout::NonNullableUnwrapped(_) => worst_case_type(builder),
|
RecursivePointer => match when_recursive {
|
||||||
UnionLayout::NullableWrapped {
|
WhenRecursive::Unreachable => {
|
||||||
nullable_id: _,
|
unreachable!()
|
||||||
other_tags: _,
|
}
|
||||||
} => worst_case_type(builder),
|
WhenRecursive::Loop(union_layout) => match union_layout {
|
||||||
UnionLayout::NullableUnwrapped {
|
UnionLayout::NonRecursive(_) => unreachable!(),
|
||||||
nullable_id: _,
|
UnionLayout::Recursive(_)
|
||||||
other_fields: _,
|
| UnionLayout::NullableUnwrapped { .. }
|
||||||
} => worst_case_type(builder),
|
| UnionLayout::NullableWrapped { .. }
|
||||||
|
| UnionLayout::NonNullableUnwrapped(_) => {
|
||||||
|
let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
|
||||||
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
|
Ok(builder.add_named_type(MOD_APP, type_name))
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
RecursivePointer => worst_case_type(builder),
|
Closure(_, lambda_set, _) => layout_spec_help(
|
||||||
Closure(_, lambda_set, _) => layout_spec(builder, &lambda_set.runtime_representation()),
|
builder,
|
||||||
|
&lambda_set.runtime_representation(),
|
||||||
|
when_recursive,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeId> {
|
fn builtin_spec(
|
||||||
|
builder: &mut impl TypeContext,
|
||||||
|
builtin: &Builtin,
|
||||||
|
when_recursive: &WhenRecursive,
|
||||||
|
) -> Result<TypeId> {
|
||||||
use Builtin::*;
|
use Builtin::*;
|
||||||
|
|
||||||
match builtin {
|
match builtin {
|
||||||
|
@ -1042,8 +1264,8 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
|
||||||
Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
|
Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
|
||||||
Str | EmptyStr => str_type(builder),
|
Str | EmptyStr => str_type(builder),
|
||||||
Dict(key_layout, value_layout) => {
|
Dict(key_layout, value_layout) => {
|
||||||
let value_type = layout_spec(builder, value_layout)?;
|
let value_type = layout_spec_help(builder, value_layout, when_recursive)?;
|
||||||
let key_type = layout_spec(builder, key_layout)?;
|
let key_type = layout_spec_help(builder, key_layout, when_recursive)?;
|
||||||
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
|
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
|
||||||
|
|
||||||
let cell = builder.add_heap_cell_type();
|
let cell = builder.add_heap_cell_type();
|
||||||
|
@ -1052,7 +1274,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
|
||||||
}
|
}
|
||||||
Set(key_layout) => {
|
Set(key_layout) => {
|
||||||
let value_type = builder.add_tuple_type(&[])?;
|
let value_type = builder.add_tuple_type(&[])?;
|
||||||
let key_type = layout_spec(builder, key_layout)?;
|
let key_type = layout_spec_help(builder, key_layout, when_recursive)?;
|
||||||
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
|
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
|
||||||
|
|
||||||
let cell = builder.add_heap_cell_type();
|
let cell = builder.add_heap_cell_type();
|
||||||
|
@ -1060,7 +1282,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
|
||||||
builder.add_tuple_type(&[cell, bag])
|
builder.add_tuple_type(&[cell, bag])
|
||||||
}
|
}
|
||||||
List(element_layout) => {
|
List(element_layout) => {
|
||||||
let element_type = layout_spec(builder, element_layout)?;
|
let element_type = layout_spec_help(builder, element_layout, when_recursive)?;
|
||||||
|
|
||||||
let cell = builder.add_heap_cell_type();
|
let cell = builder.add_heap_cell_type();
|
||||||
let bag = builder.add_bag_type(element_type)?;
|
let bag = builder.add_bag_type(element_type)?;
|
||||||
|
|
|
@ -154,11 +154,12 @@ struct VarInfo {
|
||||||
reference: bool, // true if the variable may be a reference (aka pointer) at runtime
|
reference: bool, // true if the variable may be a reference (aka pointer) at runtime
|
||||||
persistent: bool, // true if the variable is statically known to be marked a Persistent at runtime
|
persistent: bool, // true if the variable is statically known to be marked a Persistent at runtime
|
||||||
consume: bool, // true if the variable RC must be "consumed"
|
consume: bool, // true if the variable RC must be "consumed"
|
||||||
|
reset: bool, // true if the variable is the result of a Reset operation
|
||||||
}
|
}
|
||||||
|
|
||||||
type VarMap = MutMap<Symbol, VarInfo>;
|
type VarMap = MutMap<Symbol, VarInfo>;
|
||||||
type LiveVarSet = MutSet<Symbol>;
|
pub type LiveVarSet = MutSet<Symbol>;
|
||||||
type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>;
|
pub type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Context<'a> {
|
struct Context<'a> {
|
||||||
|
@ -254,6 +255,7 @@ impl<'a> Context<'a> {
|
||||||
reference: false, // assume function symbols are global constants
|
reference: false, // assume function symbols are global constants
|
||||||
persistent: true, // assume function symbols are global constants
|
persistent: true, // assume function symbols are global constants
|
||||||
consume: false, // no need to consume this variable
|
consume: false, // no need to consume this variable
|
||||||
|
reset: false, // reset symbols cannot be passed as function arguments
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -310,7 +312,12 @@ impl<'a> Context<'a> {
|
||||||
return stmt;
|
return stmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
let modify = ModifyRc::Dec(symbol);
|
let modify = if info.reset {
|
||||||
|
ModifyRc::DecRef(symbol)
|
||||||
|
} else {
|
||||||
|
ModifyRc::Dec(symbol)
|
||||||
|
};
|
||||||
|
|
||||||
self.arena.alloc(Stmt::Refcounting(modify, stmt))
|
self.arena.alloc(Stmt::Refcounting(modify, stmt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,12 +760,6 @@ impl<'a> Context<'a> {
|
||||||
arguments,
|
arguments,
|
||||||
}) => self.visit_call(z, call_type, arguments, l, b, b_live_vars),
|
}) => self.visit_call(z, call_type, arguments, l, b, b_live_vars),
|
||||||
|
|
||||||
EmptyArray | Literal(_) | Reset(_) | RuntimeErrorFunction(_) => {
|
|
||||||
// EmptyArray is always stack-allocated
|
|
||||||
// function pointers are persistent
|
|
||||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
|
||||||
}
|
|
||||||
|
|
||||||
StructAtIndex { structure: x, .. } => {
|
StructAtIndex { structure: x, .. } => {
|
||||||
let b = self.add_dec_if_needed(x, b, b_live_vars);
|
let b = self.add_dec_if_needed(x, b, b_live_vars);
|
||||||
let info_x = self.get_var_info(x);
|
let info_x = self.get_var_info(x);
|
||||||
|
@ -794,6 +795,12 @@ impl<'a> Context<'a> {
|
||||||
|
|
||||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmptyArray | Literal(_) | Reset(_) | RuntimeErrorFunction(_) => {
|
||||||
|
// EmptyArray is always stack-allocated
|
||||||
|
// function pointers are persistent
|
||||||
|
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(new_b, live_vars)
|
(new_b, live_vars)
|
||||||
|
@ -812,7 +819,7 @@ impl<'a> Context<'a> {
|
||||||
// must this value be consumed?
|
// must this value be consumed?
|
||||||
let consume = consume_call(&self.vars, call);
|
let consume = consume_call(&self.vars, call);
|
||||||
|
|
||||||
self.update_var_info_help(symbol, layout, persistent, consume)
|
self.update_var_info_help(symbol, layout, persistent, consume, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
|
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
|
||||||
|
@ -823,7 +830,9 @@ impl<'a> Context<'a> {
|
||||||
// must this value be consumed?
|
// must this value be consumed?
|
||||||
let consume = consume_expr(&self.vars, expr);
|
let consume = consume_expr(&self.vars, expr);
|
||||||
|
|
||||||
self.update_var_info_help(symbol, layout, persistent, consume)
|
let reset = matches!(expr, Expr::Reset(_));
|
||||||
|
|
||||||
|
self.update_var_info_help(symbol, layout, persistent, consume, reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_var_info_help(
|
fn update_var_info_help(
|
||||||
|
@ -832,6 +841,7 @@ impl<'a> Context<'a> {
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
persistent: bool,
|
persistent: bool,
|
||||||
consume: bool,
|
consume: bool,
|
||||||
|
reset: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// should we perform incs and decs on this value?
|
// should we perform incs and decs on this value?
|
||||||
let reference = layout.contains_refcounted();
|
let reference = layout.contains_refcounted();
|
||||||
|
@ -840,6 +850,7 @@ impl<'a> Context<'a> {
|
||||||
reference,
|
reference,
|
||||||
persistent,
|
persistent,
|
||||||
consume,
|
consume,
|
||||||
|
reset,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ctx = self.clone();
|
let mut ctx = self.clone();
|
||||||
|
@ -857,6 +868,7 @@ impl<'a> Context<'a> {
|
||||||
reference: p.layout.contains_refcounted(),
|
reference: p.layout.contains_refcounted(),
|
||||||
consume: !p.borrow,
|
consume: !p.borrow,
|
||||||
persistent: false,
|
persistent: false,
|
||||||
|
reset: false,
|
||||||
};
|
};
|
||||||
ctx.vars.insert(p.symbol, info);
|
ctx.vars.insert(p.symbol, info);
|
||||||
}
|
}
|
||||||
|
@ -956,15 +968,6 @@ impl<'a> Context<'a> {
|
||||||
// live vars of the whole expression
|
// live vars of the whole expression
|
||||||
let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
|
let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
|
||||||
|
|
||||||
// the result of an invoke should not be touched in the fail branch
|
|
||||||
// but it should be present in the pass branch (otherwise it would be dead)
|
|
||||||
// NOTE: we cheat a bit here to allow `invoke` when generating code for `expect`
|
|
||||||
let is_dead = !invoke_live_vars.contains(symbol);
|
|
||||||
|
|
||||||
if is_dead && layout.is_refcounted() {
|
|
||||||
panic!("A variable of a reference-counted layout is dead; that's a bug!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let fail = {
|
let fail = {
|
||||||
// TODO should we use ctor info like Lean?
|
// TODO should we use ctor info like Lean?
|
||||||
let ctx = self.clone();
|
let ctx = self.clone();
|
||||||
|
|
|
@ -227,6 +227,19 @@ impl<'a> Proc<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_reset_reuse_operations<'i>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
home: ModuleId,
|
||||||
|
ident_ids: &'i mut IdentIds,
|
||||||
|
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
|
) {
|
||||||
|
for (_, proc) in procs.iter_mut() {
|
||||||
|
let new_proc =
|
||||||
|
crate::reset_reuse::insert_reset_reuse(arena, home, ident_ids, proc.clone());
|
||||||
|
*proc = new_proc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn optimize_refcount_operations<'i, T>(
|
pub fn optimize_refcount_operations<'i, T>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
|
@ -1129,7 +1142,6 @@ pub enum Expr<'a> {
|
||||||
tag_layout: UnionLayout<'a>,
|
tag_layout: UnionLayout<'a>,
|
||||||
tag_name: TagName,
|
tag_name: TagName,
|
||||||
tag_id: u8,
|
tag_id: u8,
|
||||||
union_size: u8,
|
|
||||||
arguments: &'a [Symbol],
|
arguments: &'a [Symbol],
|
||||||
},
|
},
|
||||||
Struct(&'a [Symbol]),
|
Struct(&'a [Symbol]),
|
||||||
|
@ -1160,6 +1172,9 @@ pub enum Expr<'a> {
|
||||||
|
|
||||||
Reuse {
|
Reuse {
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
|
update_tag_id: bool,
|
||||||
|
// normal Tag fields
|
||||||
|
tag_layout: UnionLayout<'a>,
|
||||||
tag_name: TagName,
|
tag_name: TagName,
|
||||||
tag_id: u8,
|
tag_id: u8,
|
||||||
arguments: &'a [Symbol],
|
arguments: &'a [Symbol],
|
||||||
|
@ -1273,11 +1288,12 @@ impl<'a> Expr<'a> {
|
||||||
alloc
|
alloc
|
||||||
.text("Reuse ")
|
.text("Reuse ")
|
||||||
.append(symbol_to_doc(alloc, *symbol))
|
.append(symbol_to_doc(alloc, *symbol))
|
||||||
|
.append(alloc.space())
|
||||||
.append(doc_tag)
|
.append(doc_tag)
|
||||||
.append(alloc.space())
|
.append(alloc.space())
|
||||||
.append(alloc.intersperse(it, " "))
|
.append(alloc.intersperse(it, " "))
|
||||||
}
|
}
|
||||||
Reset(symbol) => alloc.text("Reuse ").append(symbol_to_doc(alloc, *symbol)),
|
Reset(symbol) => alloc.text("Reset ").append(symbol_to_doc(alloc, *symbol)),
|
||||||
|
|
||||||
Struct(args) => {
|
Struct(args) => {
|
||||||
let it = args.iter().map(|s| symbol_to_doc(alloc, *s));
|
let it = args.iter().map(|s| symbol_to_doc(alloc, *s));
|
||||||
|
@ -4036,14 +4052,12 @@ fn construct_closure_data<'a>(
|
||||||
ClosureRepresentation::Union {
|
ClosureRepresentation::Union {
|
||||||
tag_id,
|
tag_id,
|
||||||
tag_layout: _,
|
tag_layout: _,
|
||||||
union_size,
|
|
||||||
tag_name,
|
tag_name,
|
||||||
union_layout,
|
union_layout,
|
||||||
} => {
|
} => {
|
||||||
let expr = Expr::Tag {
|
let expr = Expr::Tag {
|
||||||
tag_id,
|
tag_id,
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
union_size,
|
|
||||||
tag_name,
|
tag_name,
|
||||||
arguments: symbols,
|
arguments: symbols,
|
||||||
};
|
};
|
||||||
|
@ -4172,19 +4186,28 @@ fn convert_tag_union<'a>(
|
||||||
assign_to_symbols(env, procs, layout_cache, iter, stmt)
|
assign_to_symbols(env, procs, layout_cache, iter, stmt)
|
||||||
}
|
}
|
||||||
Wrapped(variant) => {
|
Wrapped(variant) => {
|
||||||
let union_size = variant.number_of_tags() as u8;
|
|
||||||
let (tag_id, _) = variant.tag_name_to_id(&tag_name);
|
let (tag_id, _) = variant.tag_name_to_id(&tag_name);
|
||||||
|
|
||||||
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
|
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
|
||||||
|
|
||||||
let field_symbols;
|
let field_symbols;
|
||||||
let opt_tag_id_symbol;
|
|
||||||
|
// we must derive the union layout from the whole_var, building it up
|
||||||
|
// from `layouts` would unroll recursive tag unions, and that leads to
|
||||||
|
// problems down the line because we hash layouts and an unrolled
|
||||||
|
// version is not the same as the minimal version.
|
||||||
|
let union_layout = match return_on_layout_error!(
|
||||||
|
env,
|
||||||
|
layout_cache.from_var(env.arena, variant_var, env.subs)
|
||||||
|
) {
|
||||||
|
Layout::Union(ul) => ul,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
use WrappedVariant::*;
|
use WrappedVariant::*;
|
||||||
let (tag, union_layout) = match variant {
|
let (tag, union_layout) = match variant {
|
||||||
Recursive { sorted_tag_layouts } => {
|
Recursive { sorted_tag_layouts } => {
|
||||||
debug_assert!(sorted_tag_layouts.len() > 1);
|
debug_assert!(sorted_tag_layouts.len() > 1);
|
||||||
opt_tag_id_symbol = None;
|
|
||||||
|
|
||||||
field_symbols = {
|
field_symbols = {
|
||||||
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
|
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
|
||||||
|
@ -4201,27 +4224,21 @@ fn convert_tag_union<'a>(
|
||||||
layouts.push(arg_layouts);
|
layouts.push(arg_layouts);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(layouts.len() > 1);
|
|
||||||
let union_layout = UnionLayout::Recursive(layouts.into_bump_slice());
|
|
||||||
|
|
||||||
let tag = Expr::Tag {
|
let tag = Expr::Tag {
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
tag_name,
|
tag_name,
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
union_size,
|
|
||||||
arguments: field_symbols,
|
arguments: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
(tag, union_layout)
|
(tag, union_layout)
|
||||||
}
|
}
|
||||||
NonNullableUnwrapped {
|
NonNullableUnwrapped {
|
||||||
fields,
|
|
||||||
tag_name: wrapped_tag_name,
|
tag_name: wrapped_tag_name,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
debug_assert_eq!(tag_name, wrapped_tag_name);
|
debug_assert_eq!(tag_name, wrapped_tag_name);
|
||||||
|
|
||||||
opt_tag_id_symbol = None;
|
|
||||||
|
|
||||||
field_symbols = {
|
field_symbols = {
|
||||||
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
|
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
|
||||||
|
|
||||||
|
@ -4230,21 +4247,16 @@ fn convert_tag_union<'a>(
|
||||||
temp.into_bump_slice()
|
temp.into_bump_slice()
|
||||||
};
|
};
|
||||||
|
|
||||||
let union_layout = UnionLayout::NonNullableUnwrapped(fields);
|
|
||||||
|
|
||||||
let tag = Expr::Tag {
|
let tag = Expr::Tag {
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
tag_name,
|
tag_name,
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
union_size,
|
|
||||||
arguments: field_symbols,
|
arguments: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
(tag, union_layout)
|
(tag, union_layout)
|
||||||
}
|
}
|
||||||
NonRecursive { sorted_tag_layouts } => {
|
NonRecursive { sorted_tag_layouts } => {
|
||||||
opt_tag_id_symbol = None;
|
|
||||||
|
|
||||||
field_symbols = {
|
field_symbols = {
|
||||||
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
|
let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena);
|
||||||
|
|
||||||
|
@ -4260,25 +4272,18 @@ fn convert_tag_union<'a>(
|
||||||
layouts.push(arg_layouts);
|
layouts.push(arg_layouts);
|
||||||
}
|
}
|
||||||
|
|
||||||
let union_layout = UnionLayout::NonRecursive(layouts.into_bump_slice());
|
|
||||||
|
|
||||||
let tag = Expr::Tag {
|
let tag = Expr::Tag {
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
tag_name,
|
tag_name,
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
union_size,
|
|
||||||
arguments: field_symbols,
|
arguments: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
(tag, union_layout)
|
(tag, union_layout)
|
||||||
}
|
}
|
||||||
NullableWrapped {
|
NullableWrapped {
|
||||||
nullable_id,
|
sorted_tag_layouts, ..
|
||||||
nullable_name: _,
|
|
||||||
sorted_tag_layouts,
|
|
||||||
} => {
|
} => {
|
||||||
opt_tag_id_symbol = None;
|
|
||||||
|
|
||||||
field_symbols = {
|
field_symbols = {
|
||||||
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
|
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
|
||||||
|
|
||||||
|
@ -4294,31 +4299,16 @@ fn convert_tag_union<'a>(
|
||||||
layouts.push(arg_layouts);
|
layouts.push(arg_layouts);
|
||||||
}
|
}
|
||||||
|
|
||||||
let union_layout = UnionLayout::NullableWrapped {
|
|
||||||
nullable_id,
|
|
||||||
other_tags: layouts.into_bump_slice(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tag = Expr::Tag {
|
let tag = Expr::Tag {
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
tag_name,
|
tag_name,
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
union_size,
|
|
||||||
arguments: field_symbols,
|
arguments: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
(tag, union_layout)
|
(tag, union_layout)
|
||||||
}
|
}
|
||||||
NullableUnwrapped {
|
NullableUnwrapped { .. } => {
|
||||||
nullable_id,
|
|
||||||
nullable_name: _,
|
|
||||||
other_name: _,
|
|
||||||
other_fields,
|
|
||||||
} => {
|
|
||||||
// FIXME drop tag
|
|
||||||
let tag_id_symbol = env.unique_symbol();
|
|
||||||
opt_tag_id_symbol = Some(tag_id_symbol);
|
|
||||||
|
|
||||||
field_symbols = {
|
field_symbols = {
|
||||||
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
|
let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena);
|
||||||
|
|
||||||
|
@ -4327,16 +4317,10 @@ fn convert_tag_union<'a>(
|
||||||
temp.into_bump_slice()
|
temp.into_bump_slice()
|
||||||
};
|
};
|
||||||
|
|
||||||
let union_layout = UnionLayout::NullableUnwrapped {
|
|
||||||
nullable_id,
|
|
||||||
other_fields,
|
|
||||||
};
|
|
||||||
|
|
||||||
let tag = Expr::Tag {
|
let tag = Expr::Tag {
|
||||||
tag_layout: union_layout,
|
tag_layout: union_layout,
|
||||||
tag_name,
|
tag_name,
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
union_size,
|
|
||||||
arguments: field_symbols,
|
arguments: field_symbols,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4344,26 +4328,14 @@ fn convert_tag_union<'a>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut stmt = Stmt::Let(assigned, tag, Layout::Union(union_layout), hole);
|
let stmt = Stmt::Let(assigned, tag, Layout::Union(union_layout), hole);
|
||||||
let iter = field_symbols_temp
|
let iter = field_symbols_temp
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| x.2 .0)
|
.map(|x| x.2 .0)
|
||||||
.rev()
|
.rev()
|
||||||
.zip(field_symbols.iter().rev());
|
.zip(field_symbols.iter().rev());
|
||||||
|
|
||||||
stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt);
|
assign_to_symbols(env, procs, layout_cache, iter, stmt)
|
||||||
|
|
||||||
if let Some(tag_id_symbol) = opt_tag_id_symbol {
|
|
||||||
// define the tag id
|
|
||||||
stmt = Stmt::Let(
|
|
||||||
tag_id_symbol,
|
|
||||||
Expr::Literal(Literal::Int(tag_id as i128)),
|
|
||||||
union_layout.tag_id_layout(),
|
|
||||||
arena.alloc(stmt),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5382,7 +5354,6 @@ fn substitute_in_expr<'a>(
|
||||||
tag_layout,
|
tag_layout,
|
||||||
tag_name,
|
tag_name,
|
||||||
tag_id,
|
tag_id,
|
||||||
union_size,
|
|
||||||
arguments: args,
|
arguments: args,
|
||||||
} => {
|
} => {
|
||||||
let mut did_change = false;
|
let mut did_change = false;
|
||||||
|
@ -5404,7 +5375,6 @@ fn substitute_in_expr<'a>(
|
||||||
tag_layout: *tag_layout,
|
tag_layout: *tag_layout,
|
||||||
tag_name: tag_name.clone(),
|
tag_name: tag_name.clone(),
|
||||||
tag_id: *tag_id,
|
tag_id: *tag_id,
|
||||||
union_size: *union_size,
|
|
||||||
arguments,
|
arguments,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -7041,6 +7011,15 @@ fn from_can_pattern_help<'a>(
|
||||||
temp
|
temp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// we must derive the union layout from the whole_var, building it up
|
||||||
|
// from `layouts` would unroll recursive tag unions, and that leads to
|
||||||
|
// problems down the line because we hash layouts and an unrolled
|
||||||
|
// version is not the same as the minimal version.
|
||||||
|
let layout = match layout_cache.from_var(env.arena, *whole_var, env.subs) {
|
||||||
|
Ok(Layout::Union(ul)) => ul,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
use WrappedVariant::*;
|
use WrappedVariant::*;
|
||||||
match variant {
|
match variant {
|
||||||
NonRecursive {
|
NonRecursive {
|
||||||
|
@ -7085,18 +7064,6 @@ fn from_can_pattern_help<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let layouts: Vec<&'a [Layout<'a>]> = {
|
|
||||||
let mut temp = Vec::with_capacity_in(tags.len(), env.arena);
|
|
||||||
|
|
||||||
for (_, arg_layouts) in tags.into_iter() {
|
|
||||||
temp.push(*arg_layouts);
|
|
||||||
}
|
|
||||||
|
|
||||||
temp
|
|
||||||
};
|
|
||||||
|
|
||||||
let layout = UnionLayout::NonRecursive(layouts.into_bump_slice());
|
|
||||||
|
|
||||||
Pattern::AppliedTag {
|
Pattern::AppliedTag {
|
||||||
tag_name: tag_name.clone(),
|
tag_name: tag_name.clone(),
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
|
@ -7142,19 +7109,6 @@ fn from_can_pattern_help<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let layouts: Vec<&'a [Layout<'a>]> = {
|
|
||||||
let mut temp = Vec::with_capacity_in(tags.len(), env.arena);
|
|
||||||
|
|
||||||
for (_, arg_layouts) in tags.into_iter() {
|
|
||||||
temp.push(*arg_layouts);
|
|
||||||
}
|
|
||||||
|
|
||||||
temp
|
|
||||||
};
|
|
||||||
|
|
||||||
debug_assert!(layouts.len() > 1);
|
|
||||||
let layout = UnionLayout::Recursive(layouts.into_bump_slice());
|
|
||||||
|
|
||||||
Pattern::AppliedTag {
|
Pattern::AppliedTag {
|
||||||
tag_name: tag_name.clone(),
|
tag_name: tag_name.clone(),
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
|
@ -7198,8 +7152,6 @@ fn from_can_pattern_help<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = UnionLayout::NonNullableUnwrapped(fields);
|
|
||||||
|
|
||||||
Pattern::AppliedTag {
|
Pattern::AppliedTag {
|
||||||
tag_name: tag_name.clone(),
|
tag_name: tag_name.clone(),
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
|
@ -7273,21 +7225,6 @@ fn from_can_pattern_help<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let layouts: Vec<&'a [Layout<'a>]> = {
|
|
||||||
let mut temp = Vec::with_capacity_in(tags.len(), env.arena);
|
|
||||||
|
|
||||||
for (_, arg_layouts) in tags.into_iter() {
|
|
||||||
temp.push(*arg_layouts);
|
|
||||||
}
|
|
||||||
|
|
||||||
temp
|
|
||||||
};
|
|
||||||
|
|
||||||
let layout = UnionLayout::NullableWrapped {
|
|
||||||
nullable_id,
|
|
||||||
other_tags: layouts.into_bump_slice(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Pattern::AppliedTag {
|
Pattern::AppliedTag {
|
||||||
tag_name: tag_name.clone(),
|
tag_name: tag_name.clone(),
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
|
@ -7344,11 +7281,6 @@ fn from_can_pattern_help<'a>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = UnionLayout::NullableUnwrapped {
|
|
||||||
nullable_id,
|
|
||||||
other_fields,
|
|
||||||
};
|
|
||||||
|
|
||||||
Pattern::AppliedTag {
|
Pattern::AppliedTag {
|
||||||
tag_name: tag_name.clone(),
|
tag_name: tag_name.clone(),
|
||||||
tag_id: tag_id as u8,
|
tag_id: tag_id as u8,
|
||||||
|
|
|
@ -148,6 +148,16 @@ impl<'a> UnionLayout<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn number_of_tags(&'a self) -> usize {
|
||||||
|
match self {
|
||||||
|
UnionLayout::NonRecursive(tags) | UnionLayout::Recursive(tags) => tags.len(),
|
||||||
|
|
||||||
|
UnionLayout::NullableWrapped { other_tags, .. } => other_tags.len() + 1,
|
||||||
|
UnionLayout::NonNullableUnwrapped(_) => 1,
|
||||||
|
UnionLayout::NullableUnwrapped { .. } => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tag_id_builtin_help(union_size: usize) -> Builtin<'a> {
|
fn tag_id_builtin_help(union_size: usize) -> Builtin<'a> {
|
||||||
if union_size <= u8::MAX as usize {
|
if union_size <= u8::MAX as usize {
|
||||||
Builtin::Int8
|
Builtin::Int8
|
||||||
|
@ -178,12 +188,40 @@ impl<'a> UnionLayout<'a> {
|
||||||
Layout::Builtin(self.tag_id_builtin())
|
Layout::Builtin(self.tag_id_builtin())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stores_tag_id(&self) -> bool {
|
fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], ptr_bytes: u32) -> bool {
|
||||||
|
tags.len() <= ptr_bytes as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
// i.e. it is not implicit and not stored in the pointer bits
|
||||||
|
pub fn stores_tag_id_as_data(&self, ptr_bytes: u32) -> bool {
|
||||||
|
match self {
|
||||||
|
UnionLayout::NonRecursive(_) => true,
|
||||||
|
UnionLayout::Recursive(tags)
|
||||||
|
| UnionLayout::NullableWrapped {
|
||||||
|
other_tags: tags, ..
|
||||||
|
} => !Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes),
|
||||||
|
UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stores_tag_id_in_pointer(&self, ptr_bytes: u32) -> bool {
|
||||||
|
match self {
|
||||||
|
UnionLayout::NonRecursive(_) => false,
|
||||||
|
UnionLayout::Recursive(tags)
|
||||||
|
| UnionLayout::NullableWrapped {
|
||||||
|
other_tags: tags, ..
|
||||||
|
} => Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes),
|
||||||
|
UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tag_is_null(&self, tag_id: u8) -> bool {
|
||||||
match self {
|
match self {
|
||||||
UnionLayout::NonRecursive(_)
|
UnionLayout::NonRecursive(_)
|
||||||
| UnionLayout::Recursive(_)
|
| UnionLayout::NonNullableUnwrapped(_)
|
||||||
| UnionLayout::NullableWrapped { .. } => true,
|
| UnionLayout::Recursive(_) => false,
|
||||||
UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false,
|
UnionLayout::NullableWrapped { nullable_id, .. } => *nullable_id == tag_id as i64,
|
||||||
|
UnionLayout::NullableUnwrapped { nullable_id, .. } => *nullable_id == (tag_id != 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +251,6 @@ pub enum ClosureRepresentation<'a> {
|
||||||
tag_layout: &'a [Layout<'a>],
|
tag_layout: &'a [Layout<'a>],
|
||||||
tag_name: TagName,
|
tag_name: TagName,
|
||||||
tag_id: u8,
|
tag_id: u8,
|
||||||
union_size: u8,
|
|
||||||
union_layout: UnionLayout<'a>,
|
union_layout: UnionLayout<'a>,
|
||||||
},
|
},
|
||||||
/// the representation is anything but a union
|
/// the representation is anything but a union
|
||||||
|
@ -252,7 +289,6 @@ impl<'a> LambdaSet<'a> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
ClosureRepresentation::Union {
|
ClosureRepresentation::Union {
|
||||||
union_size: self.set.len() as u8,
|
|
||||||
tag_id: index as u8,
|
tag_id: index as u8,
|
||||||
tag_layout: tags[index],
|
tag_layout: tags[index],
|
||||||
tag_name: TagName::Closure(function_symbol),
|
tag_name: TagName::Closure(function_symbol),
|
||||||
|
@ -713,6 +749,7 @@ impl<'a> Layout<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RecursivePointer => true,
|
RecursivePointer => true,
|
||||||
|
|
||||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1509,6 +1546,18 @@ fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_recursive_tag_union(layout: &Layout) -> bool {
|
||||||
|
matches!(
|
||||||
|
layout,
|
||||||
|
Layout::Union(
|
||||||
|
UnionLayout::NullableUnwrapped { .. }
|
||||||
|
| UnionLayout::Recursive(_)
|
||||||
|
| UnionLayout::NullableWrapped { .. }
|
||||||
|
| UnionLayout::NonNullableUnwrapped { .. },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn union_sorted_tags_help<'a>(
|
pub fn union_sorted_tags_help<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
|
mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
|
||||||
|
@ -1624,10 +1673,17 @@ pub fn union_sorted_tags_help<'a>(
|
||||||
for var in arguments {
|
for var in arguments {
|
||||||
match Layout::from_var(&mut env, var) {
|
match Layout::from_var(&mut env, var) {
|
||||||
Ok(layout) => {
|
Ok(layout) => {
|
||||||
// Drop any zero-sized arguments like {}
|
has_any_arguments = true;
|
||||||
if !layout.is_dropped_because_empty() {
|
|
||||||
has_any_arguments = true;
|
|
||||||
|
|
||||||
|
// make sure to not unroll recursive types!
|
||||||
|
let self_recursion = opt_rec_var.is_some()
|
||||||
|
&& subs.get_root_key_without_compacting(var)
|
||||||
|
== subs.get_root_key_without_compacting(opt_rec_var.unwrap())
|
||||||
|
&& is_recursive_tag_union(&layout);
|
||||||
|
|
||||||
|
if self_recursion {
|
||||||
|
arg_layouts.push(Layout::RecursivePointer);
|
||||||
|
} else {
|
||||||
arg_layouts.push(layout);
|
arg_layouts.push(layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub mod expand_rc;
|
||||||
pub mod inc_dec;
|
pub mod inc_dec;
|
||||||
pub mod ir;
|
pub mod ir;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
pub mod reset_reuse;
|
||||||
pub mod tail_recursion;
|
pub mod tail_recursion;
|
||||||
|
|
||||||
// Temporary, while we can build up test cases and optimize the exhaustiveness checking.
|
// Temporary, while we can build up test cases and optimize the exhaustiveness checking.
|
||||||
|
|
679
compiler/mono/src/reset_reuse.rs
Normal file
679
compiler/mono/src/reset_reuse.rs
Normal file
|
@ -0,0 +1,679 @@
|
||||||
|
use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet};
|
||||||
|
use crate::ir::{BranchInfo, Call, Expr, Proc, Stmt};
|
||||||
|
use crate::layout::{Layout, UnionLayout};
|
||||||
|
use bumpalo::collections::Vec;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_collections::all::MutSet;
|
||||||
|
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||||
|
|
||||||
|
pub fn insert_reset_reuse<'a, 'i>(
|
||||||
|
arena: &'a Bump,
|
||||||
|
home: ModuleId,
|
||||||
|
ident_ids: &'i mut IdentIds,
|
||||||
|
mut proc: Proc<'a>,
|
||||||
|
) -> Proc<'a> {
|
||||||
|
let mut env = Env {
|
||||||
|
arena,
|
||||||
|
home,
|
||||||
|
ident_ids,
|
||||||
|
jp_live_vars: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_body = function_r(&mut env, arena.alloc(proc.body));
|
||||||
|
proc.body = new_body.clone();
|
||||||
|
|
||||||
|
proc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CtorInfo<'a> {
|
||||||
|
id: u8,
|
||||||
|
layout: UnionLayout<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn may_reuse(tag_layout: UnionLayout, tag_id: u8, other: &CtorInfo) -> bool {
|
||||||
|
if tag_layout != other.layout {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should not get here if the tag we matched on is represented as NULL
|
||||||
|
debug_assert!(!tag_layout.tag_is_null(other.id));
|
||||||
|
|
||||||
|
// furthermore, we can only use the memory if the tag we're creating is non-NULL
|
||||||
|
!tag_layout.tag_is_null(tag_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Env<'a, 'i> {
|
||||||
|
arena: &'a Bump,
|
||||||
|
|
||||||
|
/// required for creating new `Symbol`s
|
||||||
|
home: ModuleId,
|
||||||
|
ident_ids: &'i mut IdentIds,
|
||||||
|
|
||||||
|
jp_live_vars: JPLiveVarMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'i> Env<'a, 'i> {
|
||||||
|
fn unique_symbol(&mut self) -> Symbol {
|
||||||
|
let ident_id = self.ident_ids.gen_unique();
|
||||||
|
|
||||||
|
self.home.register_debug_idents(&self.ident_ids);
|
||||||
|
|
||||||
|
Symbol::new(self.home, ident_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_s<'a, 'i>(
|
||||||
|
env: &mut Env<'a, 'i>,
|
||||||
|
w: Symbol,
|
||||||
|
c: &CtorInfo<'a>,
|
||||||
|
stmt: &'a Stmt<'a>,
|
||||||
|
) -> &'a Stmt<'a> {
|
||||||
|
use Stmt::*;
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
|
||||||
|
match stmt {
|
||||||
|
Let(symbol, expr, layout, continuation) => match expr {
|
||||||
|
Expr::Tag {
|
||||||
|
tag_layout,
|
||||||
|
tag_id,
|
||||||
|
tag_name,
|
||||||
|
arguments,
|
||||||
|
} if may_reuse(*tag_layout, *tag_id, c) => {
|
||||||
|
// for now, always overwrite the tag ID just to be sure
|
||||||
|
let update_tag_id = true;
|
||||||
|
|
||||||
|
let new_expr = Expr::Reuse {
|
||||||
|
symbol: w,
|
||||||
|
update_tag_id,
|
||||||
|
tag_layout: *tag_layout,
|
||||||
|
tag_id: *tag_id,
|
||||||
|
tag_name: tag_name.clone(),
|
||||||
|
arguments,
|
||||||
|
};
|
||||||
|
let new_stmt = Let(*symbol, new_expr, *layout, continuation);
|
||||||
|
|
||||||
|
arena.alloc(new_stmt)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let rest = function_s(env, w, c, continuation);
|
||||||
|
let new_stmt = Let(*symbol, expr.clone(), *layout, rest);
|
||||||
|
|
||||||
|
arena.alloc(new_stmt)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
let id = *id;
|
||||||
|
let body: &Stmt = *body;
|
||||||
|
let new_body = function_s(env, w, c, body);
|
||||||
|
|
||||||
|
let new_join = if std::ptr::eq(body, new_body) || body == new_body {
|
||||||
|
// the join point body will consume w
|
||||||
|
Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body: new_body,
|
||||||
|
remainder,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let new_remainder = function_s(env, w, c, remainder);
|
||||||
|
|
||||||
|
Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
remainder: new_remainder,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(new_join)
|
||||||
|
}
|
||||||
|
Invoke {
|
||||||
|
symbol,
|
||||||
|
call,
|
||||||
|
layout,
|
||||||
|
pass,
|
||||||
|
fail,
|
||||||
|
exception_id,
|
||||||
|
} => {
|
||||||
|
let new_pass = function_s(env, w, c, pass);
|
||||||
|
let new_fail = function_s(env, w, c, fail);
|
||||||
|
|
||||||
|
let new_invoke = Invoke {
|
||||||
|
symbol: *symbol,
|
||||||
|
call: call.clone(),
|
||||||
|
layout: *layout,
|
||||||
|
pass: new_pass,
|
||||||
|
fail: new_fail,
|
||||||
|
exception_id: *exception_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(new_invoke)
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
cond_symbol,
|
||||||
|
cond_layout,
|
||||||
|
branches,
|
||||||
|
default_branch,
|
||||||
|
ret_layout,
|
||||||
|
} => {
|
||||||
|
let mut new_branches = Vec::with_capacity_in(branches.len(), arena);
|
||||||
|
new_branches.extend(branches.iter().map(|(tag, info, body)| {
|
||||||
|
let new_body = function_s(env, w, c, body);
|
||||||
|
|
||||||
|
(*tag, info.clone(), new_body.clone())
|
||||||
|
}));
|
||||||
|
|
||||||
|
let new_default = function_s(env, w, c, default_branch.1);
|
||||||
|
|
||||||
|
let new_switch = Switch {
|
||||||
|
cond_symbol: *cond_symbol,
|
||||||
|
cond_layout: *cond_layout,
|
||||||
|
branches: new_branches.into_bump_slice(),
|
||||||
|
default_branch: (default_branch.0.clone(), new_default),
|
||||||
|
ret_layout: *ret_layout,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(new_switch)
|
||||||
|
}
|
||||||
|
Refcounting(op, continuation) => {
|
||||||
|
let continuation: &Stmt = *continuation;
|
||||||
|
let new_continuation = function_s(env, w, c, continuation);
|
||||||
|
|
||||||
|
if std::ptr::eq(continuation, new_continuation) || continuation == new_continuation {
|
||||||
|
stmt
|
||||||
|
} else {
|
||||||
|
let new_refcounting = Refcounting(*op, new_continuation);
|
||||||
|
|
||||||
|
arena.alloc(new_refcounting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_function_s<'a, 'i>(
|
||||||
|
env: &mut Env<'a, 'i>,
|
||||||
|
x: Symbol,
|
||||||
|
c: &CtorInfo<'a>,
|
||||||
|
stmt: &'a Stmt<'a>,
|
||||||
|
) -> &'a Stmt<'a> {
|
||||||
|
let w = env.unique_symbol();
|
||||||
|
|
||||||
|
let new_stmt = function_s(env, w, c, stmt);
|
||||||
|
|
||||||
|
if std::ptr::eq(stmt, new_stmt) || stmt == new_stmt {
|
||||||
|
stmt
|
||||||
|
} else {
|
||||||
|
insert_reset(env, w, x, c.layout, new_stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_reset<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
w: Symbol,
|
||||||
|
x: Symbol,
|
||||||
|
union_layout: UnionLayout<'a>,
|
||||||
|
mut stmt: &'a Stmt<'a>,
|
||||||
|
) -> &'a Stmt<'a> {
|
||||||
|
use crate::ir::Expr::*;
|
||||||
|
|
||||||
|
let mut stack = vec![];
|
||||||
|
|
||||||
|
while let Stmt::Let(symbol, expr, expr_layout, rest) = stmt {
|
||||||
|
match &expr {
|
||||||
|
StructAtIndex { .. } | GetTagId { .. } | UnionAtIndex { .. } => {
|
||||||
|
stack.push((symbol, expr, expr_layout));
|
||||||
|
stmt = rest;
|
||||||
|
}
|
||||||
|
Literal(_)
|
||||||
|
| Call(_)
|
||||||
|
| Tag { .. }
|
||||||
|
| Struct(_)
|
||||||
|
| Array { .. }
|
||||||
|
| EmptyArray
|
||||||
|
| Reuse { .. }
|
||||||
|
| Reset(_)
|
||||||
|
| RuntimeErrorFunction(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let reset_expr = Expr::Reset(x);
|
||||||
|
|
||||||
|
// const I64: Layout<'static> = Layout::Builtin(crate::layout::Builtin::Int64);
|
||||||
|
|
||||||
|
let layout = Layout::Union(union_layout);
|
||||||
|
|
||||||
|
stmt = env.arena.alloc(Stmt::Let(w, reset_expr, layout, stmt));
|
||||||
|
|
||||||
|
for (symbol, expr, expr_layout) in stack.into_iter().rev() {
|
||||||
|
stmt = env
|
||||||
|
.arena
|
||||||
|
.alloc(Stmt::Let(*symbol, expr.clone(), *expr_layout, stmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_d_finalize<'a, 'i>(
|
||||||
|
env: &mut Env<'a, 'i>,
|
||||||
|
x: Symbol,
|
||||||
|
c: &CtorInfo<'a>,
|
||||||
|
output: (&'a Stmt<'a>, bool),
|
||||||
|
) -> &'a Stmt<'a> {
|
||||||
|
let (stmt, x_live_in_stmt) = output;
|
||||||
|
if x_live_in_stmt {
|
||||||
|
stmt
|
||||||
|
} else {
|
||||||
|
try_function_s(env, x, c, stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_d_main<'a, 'i>(
|
||||||
|
env: &mut Env<'a, 'i>,
|
||||||
|
x: Symbol,
|
||||||
|
c: &CtorInfo<'a>,
|
||||||
|
stmt: &'a Stmt<'a>,
|
||||||
|
) -> (&'a Stmt<'a>, bool) {
|
||||||
|
use Stmt::*;
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
|
||||||
|
match stmt {
|
||||||
|
Let(symbol, expr, layout, continuation) => {
|
||||||
|
match expr {
|
||||||
|
Expr::Tag { arguments, .. } if arguments.iter().any(|s| *s == x) => {
|
||||||
|
// If the scrutinee `x` (the one that is providing memory) is being
|
||||||
|
// stored in a constructor, then reuse will probably not be able to reuse memory at runtime.
|
||||||
|
// It may work only if the new cell is consumed, but we ignore this case.
|
||||||
|
(stmt, true)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let (b, found) = function_d_main(env, x, c, continuation);
|
||||||
|
|
||||||
|
// NOTE the &b != continuation is not found in the Lean source, but is required
|
||||||
|
// otherwise we observe the same symbol being reset twice
|
||||||
|
let mut result = MutSet::default();
|
||||||
|
if found
|
||||||
|
|| {
|
||||||
|
occurring_variables_expr(expr, &mut result);
|
||||||
|
!result.contains(&x)
|
||||||
|
}
|
||||||
|
|| &b != continuation
|
||||||
|
{
|
||||||
|
let let_stmt = Let(*symbol, expr.clone(), *layout, b);
|
||||||
|
|
||||||
|
(arena.alloc(let_stmt), found)
|
||||||
|
} else {
|
||||||
|
let b = try_function_s(env, x, c, b);
|
||||||
|
let let_stmt = Let(*symbol, expr.clone(), *layout, b);
|
||||||
|
|
||||||
|
(arena.alloc(let_stmt), found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Invoke {
|
||||||
|
symbol,
|
||||||
|
call,
|
||||||
|
layout,
|
||||||
|
pass,
|
||||||
|
fail,
|
||||||
|
exception_id,
|
||||||
|
} => {
|
||||||
|
if has_live_var(&env.jp_live_vars, stmt, x) {
|
||||||
|
let new_pass = {
|
||||||
|
let temp = function_d_main(env, x, c, pass);
|
||||||
|
function_d_finalize(env, x, c, temp)
|
||||||
|
};
|
||||||
|
let new_fail = {
|
||||||
|
let temp = function_d_main(env, x, c, fail);
|
||||||
|
function_d_finalize(env, x, c, temp)
|
||||||
|
};
|
||||||
|
let new_switch = Invoke {
|
||||||
|
symbol: *symbol,
|
||||||
|
call: call.clone(),
|
||||||
|
layout: *layout,
|
||||||
|
pass: new_pass,
|
||||||
|
fail: new_fail,
|
||||||
|
exception_id: *exception_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
(arena.alloc(new_switch), true)
|
||||||
|
} else {
|
||||||
|
(stmt, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
cond_symbol,
|
||||||
|
cond_layout,
|
||||||
|
branches,
|
||||||
|
default_branch,
|
||||||
|
ret_layout,
|
||||||
|
} => {
|
||||||
|
if has_live_var(&env.jp_live_vars, stmt, x) {
|
||||||
|
// if `x` is live in `stmt`, we recursively process each branch
|
||||||
|
let mut new_branches = Vec::with_capacity_in(branches.len(), arena);
|
||||||
|
|
||||||
|
for (tag, info, body) in branches.iter() {
|
||||||
|
let temp = function_d_main(env, x, c, body);
|
||||||
|
let new_body = function_d_finalize(env, x, c, temp);
|
||||||
|
|
||||||
|
new_branches.push((*tag, info.clone(), new_body.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_default = {
|
||||||
|
let (info, body) = default_branch;
|
||||||
|
let temp = function_d_main(env, x, c, body);
|
||||||
|
let new_body = function_d_finalize(env, x, c, temp);
|
||||||
|
|
||||||
|
(info.clone(), new_body)
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_switch = Switch {
|
||||||
|
cond_symbol: *cond_symbol,
|
||||||
|
cond_layout: *cond_layout,
|
||||||
|
branches: new_branches.into_bump_slice(),
|
||||||
|
default_branch: new_default,
|
||||||
|
ret_layout: *ret_layout,
|
||||||
|
};
|
||||||
|
|
||||||
|
(arena.alloc(new_switch), true)
|
||||||
|
} else {
|
||||||
|
(stmt, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Refcounting(modify_rc, continuation) => {
|
||||||
|
let (b, found) = function_d_main(env, x, c, continuation);
|
||||||
|
|
||||||
|
if found || modify_rc.get_symbol() != x {
|
||||||
|
let refcounting = Refcounting(*modify_rc, b);
|
||||||
|
|
||||||
|
(arena.alloc(refcounting), found)
|
||||||
|
} else {
|
||||||
|
let b = try_function_s(env, x, c, b);
|
||||||
|
let refcounting = Refcounting(*modify_rc, b);
|
||||||
|
|
||||||
|
(arena.alloc(refcounting), found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
env.jp_live_vars.insert(*id, LiveVarSet::default());
|
||||||
|
|
||||||
|
let body_live_vars = collect_stmt(body, &env.jp_live_vars, LiveVarSet::default());
|
||||||
|
|
||||||
|
env.jp_live_vars.insert(*id, body_live_vars);
|
||||||
|
|
||||||
|
let (b, found) = function_d_main(env, x, c, remainder);
|
||||||
|
|
||||||
|
let (v, _found) = function_d_main(env, x, c, body);
|
||||||
|
|
||||||
|
env.jp_live_vars.remove(id);
|
||||||
|
|
||||||
|
// If `found' == true`, then `Dmain b` must also have returned `(b, true)` since
|
||||||
|
// we assume the IR does not have dead join points. So, if `x` is live in `j` (i.e., `v`),
|
||||||
|
// then it must also live in `b` since `j` is reachable from `b` with a `jmp`.
|
||||||
|
// On the other hand, `x` may be live in `b` but dead in `j` (i.e., `v`). -/
|
||||||
|
let new_join = Join {
|
||||||
|
id: *id,
|
||||||
|
parameters,
|
||||||
|
body: v,
|
||||||
|
remainder: b,
|
||||||
|
};
|
||||||
|
|
||||||
|
(arena.alloc(new_join), found)
|
||||||
|
}
|
||||||
|
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => {
|
||||||
|
(stmt, has_live_var(&env.jp_live_vars, stmt, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_d<'a, 'i>(
|
||||||
|
env: &mut Env<'a, 'i>,
|
||||||
|
x: Symbol,
|
||||||
|
c: &CtorInfo<'a>,
|
||||||
|
stmt: &'a Stmt<'a>,
|
||||||
|
) -> &'a Stmt<'a> {
|
||||||
|
let temp = function_d_main(env, x, c, stmt);
|
||||||
|
|
||||||
|
function_d_finalize(env, x, c, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_r_branch_body<'a, 'i>(
|
||||||
|
env: &mut Env<'a, 'i>,
|
||||||
|
info: &BranchInfo<'a>,
|
||||||
|
body: &'a Stmt<'a>,
|
||||||
|
) -> &'a Stmt<'a> {
|
||||||
|
let temp = function_r(env, body);
|
||||||
|
|
||||||
|
match info {
|
||||||
|
BranchInfo::None => temp,
|
||||||
|
BranchInfo::Constructor {
|
||||||
|
scrutinee,
|
||||||
|
layout,
|
||||||
|
tag_id,
|
||||||
|
} => match layout {
|
||||||
|
Layout::Union(UnionLayout::NonRecursive(_)) => temp,
|
||||||
|
Layout::Union(union_layout) if !union_layout.tag_is_null(*tag_id) => {
|
||||||
|
let ctor_info = CtorInfo {
|
||||||
|
layout: *union_layout,
|
||||||
|
id: *tag_id,
|
||||||
|
};
|
||||||
|
function_d(env, *scrutinee, &ctor_info, temp)
|
||||||
|
}
|
||||||
|
_ => temp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
|
||||||
|
use Stmt::*;
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
|
||||||
|
match stmt {
|
||||||
|
Switch {
|
||||||
|
cond_symbol,
|
||||||
|
cond_layout,
|
||||||
|
branches,
|
||||||
|
default_branch,
|
||||||
|
ret_layout,
|
||||||
|
} => {
|
||||||
|
let mut new_branches = Vec::with_capacity_in(branches.len(), arena);
|
||||||
|
|
||||||
|
for (tag, info, body) in branches.iter() {
|
||||||
|
let new_body = function_r_branch_body(env, info, body);
|
||||||
|
|
||||||
|
new_branches.push((*tag, info.clone(), new_body.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_default = {
|
||||||
|
let (info, body) = default_branch;
|
||||||
|
|
||||||
|
let new_body = function_r_branch_body(env, info, body);
|
||||||
|
|
||||||
|
(info.clone(), new_body)
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_switch = Switch {
|
||||||
|
cond_symbol: *cond_symbol,
|
||||||
|
cond_layout: *cond_layout,
|
||||||
|
branches: new_branches.into_bump_slice(),
|
||||||
|
default_branch: new_default,
|
||||||
|
ret_layout: *ret_layout,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(new_switch)
|
||||||
|
}
|
||||||
|
|
||||||
|
Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
env.jp_live_vars.insert(*id, LiveVarSet::default());
|
||||||
|
|
||||||
|
let body_live_vars = collect_stmt(body, &env.jp_live_vars, LiveVarSet::default());
|
||||||
|
|
||||||
|
env.jp_live_vars.insert(*id, body_live_vars);
|
||||||
|
|
||||||
|
let b = function_r(env, remainder);
|
||||||
|
|
||||||
|
let v = function_r(env, body);
|
||||||
|
|
||||||
|
env.jp_live_vars.remove(id);
|
||||||
|
|
||||||
|
let join = Join {
|
||||||
|
id: *id,
|
||||||
|
parameters,
|
||||||
|
body: v,
|
||||||
|
remainder: b,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(join)
|
||||||
|
}
|
||||||
|
|
||||||
|
Let(symbol, expr, layout, continuation) => {
|
||||||
|
let b = function_r(env, continuation);
|
||||||
|
|
||||||
|
arena.alloc(Let(*symbol, expr.clone(), *layout, b))
|
||||||
|
}
|
||||||
|
Invoke {
|
||||||
|
symbol,
|
||||||
|
call,
|
||||||
|
layout,
|
||||||
|
pass,
|
||||||
|
fail,
|
||||||
|
exception_id,
|
||||||
|
} => {
|
||||||
|
let branch_info = BranchInfo::None;
|
||||||
|
let new_pass = function_r_branch_body(env, &branch_info, pass);
|
||||||
|
let new_fail = function_r_branch_body(env, &branch_info, fail);
|
||||||
|
|
||||||
|
let invoke = Invoke {
|
||||||
|
symbol: *symbol,
|
||||||
|
call: call.clone(),
|
||||||
|
layout: *layout,
|
||||||
|
pass: new_pass,
|
||||||
|
fail: new_fail,
|
||||||
|
exception_id: *exception_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(invoke)
|
||||||
|
}
|
||||||
|
Refcounting(modify_rc, continuation) => {
|
||||||
|
let b = function_r(env, continuation);
|
||||||
|
|
||||||
|
arena.alloc(Refcounting(*modify_rc, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => {
|
||||||
|
// terminals
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Symbol) -> bool {
|
||||||
|
use Stmt::*;
|
||||||
|
|
||||||
|
match stmt {
|
||||||
|
Let(s, e, _, c) => {
|
||||||
|
debug_assert_ne!(*s, needle);
|
||||||
|
has_live_var_expr(e, needle) || has_live_var(jp_live_vars, c, needle)
|
||||||
|
}
|
||||||
|
Invoke {
|
||||||
|
symbol,
|
||||||
|
call,
|
||||||
|
pass,
|
||||||
|
fail,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
debug_assert_ne!(*symbol, needle);
|
||||||
|
|
||||||
|
has_live_var_call(call, needle)
|
||||||
|
|| has_live_var(jp_live_vars, pass, needle)
|
||||||
|
|| has_live_var(jp_live_vars, fail, needle)
|
||||||
|
}
|
||||||
|
Switch { cond_symbol, .. } if *cond_symbol == needle => true,
|
||||||
|
Switch {
|
||||||
|
branches,
|
||||||
|
default_branch,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
has_live_var(jp_live_vars, default_branch.1, needle)
|
||||||
|
|| branches
|
||||||
|
.iter()
|
||||||
|
.any(|(_, _, body)| has_live_var(jp_live_vars, body, needle))
|
||||||
|
}
|
||||||
|
Ret(s) => *s == needle,
|
||||||
|
Refcounting(modify_rc, cont) => {
|
||||||
|
modify_rc.get_symbol() == needle || has_live_var(jp_live_vars, cont, needle)
|
||||||
|
}
|
||||||
|
Join {
|
||||||
|
id,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
remainder,
|
||||||
|
} => {
|
||||||
|
debug_assert!(parameters.iter().all(|p| p.symbol != needle));
|
||||||
|
|
||||||
|
let mut jp_live_vars = jp_live_vars.clone();
|
||||||
|
|
||||||
|
jp_live_vars.insert(*id, LiveVarSet::default());
|
||||||
|
|
||||||
|
let body_live_vars = collect_stmt(body, &jp_live_vars, LiveVarSet::default());
|
||||||
|
|
||||||
|
if body_live_vars.contains(&needle) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
jp_live_vars.insert(*id, body_live_vars);
|
||||||
|
|
||||||
|
has_live_var(&jp_live_vars, remainder, needle)
|
||||||
|
}
|
||||||
|
Jump(id, arguments) => {
|
||||||
|
arguments.iter().any(|s| *s == needle) || jp_live_vars[id].contains(&needle)
|
||||||
|
}
|
||||||
|
Resume(_) | RuntimeError(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool {
|
||||||
|
match expr {
|
||||||
|
Expr::Literal(_) => false,
|
||||||
|
Expr::Call(call) => has_live_var_call(call, needle),
|
||||||
|
Expr::Array { elems: fields, .. }
|
||||||
|
| Expr::Tag {
|
||||||
|
arguments: fields, ..
|
||||||
|
}
|
||||||
|
| Expr::Struct(fields) => fields.iter().any(|s| *s == needle),
|
||||||
|
Expr::StructAtIndex { structure, .. }
|
||||||
|
| Expr::GetTagId { structure, .. }
|
||||||
|
| Expr::UnionAtIndex { structure, .. } => *structure == needle,
|
||||||
|
Expr::EmptyArray => false,
|
||||||
|
Expr::Reuse {
|
||||||
|
symbol, arguments, ..
|
||||||
|
} => needle == *symbol || arguments.iter().any(|s| *s == needle),
|
||||||
|
Expr::Reset(symbol) => needle == *symbol,
|
||||||
|
Expr::RuntimeErrorFunction(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_live_var_call<'a>(call: &'a Call<'a>, needle: Symbol) -> bool {
|
||||||
|
call.arguments.iter().any(|s| *s == needle)
|
||||||
|
}
|
|
@ -3239,7 +3239,6 @@ fn to_requires_report<'a>(
|
||||||
ERequires::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
|
ERequires::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col),
|
||||||
|
|
||||||
ERequires::ListStart(row, col) => {
|
ERequires::ListStart(row, col) => {
|
||||||
dbg!(row, col);
|
|
||||||
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
let region = Region::from_row_col(row, col);
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
@ -3263,6 +3262,34 @@ fn to_requires_report<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ERequires::Rigid(row, col) => {
|
||||||
|
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
|
||||||
|
let region = Region::from_row_col(row, col);
|
||||||
|
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"),
|
||||||
|
alloc.region_with_subregion(surroundings, region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("I am expecting a list of rigids like "),
|
||||||
|
alloc.keyword("{}"),
|
||||||
|
alloc.reflow(" or "),
|
||||||
|
alloc.keyword("{model=>Model}"),
|
||||||
|
alloc.reflow(" next. A full "),
|
||||||
|
alloc.keyword("requires"),
|
||||||
|
alloc.reflow(" definition looks like"),
|
||||||
|
]),
|
||||||
|
alloc
|
||||||
|
.parser_suggestion("requires {model=>Model, msg=>Msg} {main : Effect {}}")
|
||||||
|
.indent(4),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "BAD REQUIRES RIGIDS".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => todo!("unhandled parse error {:?}", parse_problem),
|
_ => todo!("unhandled parse error {:?}", parse_problem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3368,6 +3395,7 @@ fn to_space_report<'a>(
|
||||||
title: "TAB CHARACTER".to_string(),
|
title: "TAB CHARACTER".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => todo!("unhandled type parse error: {:?}", &parse_problem),
|
_ => todo!("unhandled type parse error: {:?}", &parse_problem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5929,6 +5929,44 @@ mod test_reporting {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn platform_requires_rigids() {
|
||||||
|
report_header_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
platform folkertdev/foo
|
||||||
|
requires { main : Effect {} }
|
||||||
|
exposes []
|
||||||
|
packages {}
|
||||||
|
imports [Task]
|
||||||
|
provides [ mainForHost ]
|
||||||
|
effects fx.Effect
|
||||||
|
{
|
||||||
|
putChar : I64 -> Effect {},
|
||||||
|
putLine : Str -> Effect {},
|
||||||
|
getLine : Effect Str
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── BAD REQUIRES RIGIDS ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
I am partway through parsing a header, but I got stuck here:
|
||||||
|
|
||||||
|
1│ platform folkertdev/foo
|
||||||
|
2│ requires { main : Effect {} }
|
||||||
|
^
|
||||||
|
|
||||||
|
I am expecting a list of rigids like `{}` or `{model=>Model}` next. A full
|
||||||
|
`requires` definition looks like
|
||||||
|
|
||||||
|
requires {model=>Model, msg=>Msg} {main : Effect {}}
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exposes_identifier() {
|
fn exposes_identifier() {
|
||||||
report_header_problem_as(
|
report_header_problem_as(
|
||||||
|
|
|
@ -1555,9 +1555,9 @@ fn rbtree_balance_full() {
|
||||||
balance Red 0 0 Empty Empty
|
balance Red 0 0 Empty Empty
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
false,
|
true,
|
||||||
*const i64,
|
usize,
|
||||||
|x: *const i64| x.is_null()
|
|x| x != 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,8 @@ fn applied_tag_just_enum() {
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(2, 0),
|
(2, 0),
|
||||||
(u8, i64)
|
(u8, [u8; 7], u8),
|
||||||
|
|(a, _, c)| (a, c)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,8 +1079,8 @@ fn nested_recursive_literal() {
|
||||||
#"
|
#"
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
&(i64, i64, u8),
|
usize,
|
||||||
|x: &(i64, i64, u8)| x.2
|
|_| 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,8 @@ procedure Test.3 (Test.4):
|
||||||
jump Test.13 Test.4;
|
jump Test.13 Test.4;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.28 = 0i64;
|
let Test.28 = 3i64;
|
||||||
let Test.30 = 3i64;
|
let Test.26 = Just Test.28;
|
||||||
let Test.26 = Just Test.30;
|
|
||||||
let Test.29 = 1i64;
|
|
||||||
let Test.27 = Nil ;
|
let Test.27 = Nil ;
|
||||||
let Test.12 = Cons Test.26 Test.27;
|
let Test.12 = Cons Test.26 Test.27;
|
||||||
let Test.11 = CallByName Test.3 Test.12;
|
let Test.11 = CallByName Test.3 Test.12;
|
||||||
|
|
|
@ -10,9 +10,7 @@ procedure Test.2 (Test.3):
|
||||||
ret Test.11;
|
ret Test.11;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.17 = 0i64;
|
|
||||||
let Test.15 = 2i64;
|
let Test.15 = 2i64;
|
||||||
let Test.18 = 1i64;
|
|
||||||
let Test.16 = Nil ;
|
let Test.16 = Nil ;
|
||||||
let Test.9 = Cons Test.15 Test.16;
|
let Test.9 = Cons Test.15 Test.16;
|
||||||
let Test.8 = CallByName Test.2 Test.9;
|
let Test.8 = CallByName Test.2 Test.9;
|
||||||
|
|
|
@ -17,7 +17,6 @@ procedure Test.3 (Test.5):
|
||||||
ret Test.14;
|
ret Test.14;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.21 = 1i64;
|
|
||||||
let Test.2 = Nil ;
|
let Test.2 = Nil ;
|
||||||
let Test.8 = CallByName Test.3 Test.2;
|
let Test.8 = CallByName Test.3 Test.2;
|
||||||
let Test.9 = CallByName Test.3 Test.2;
|
let Test.9 = CallByName Test.3 Test.2;
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.9 = 0i64;
|
let Test.10 = Z ;
|
||||||
let Test.11 = 0i64;
|
let Test.9 = S Test.10;
|
||||||
let Test.13 = 0i64;
|
let Test.8 = S Test.9;
|
||||||
let Test.14 = 1i64;
|
|
||||||
let Test.12 = Z ;
|
|
||||||
let Test.10 = S Test.12;
|
|
||||||
let Test.8 = S Test.10;
|
|
||||||
let Test.2 = S Test.8;
|
let Test.2 = S Test.8;
|
||||||
ret Test.2;
|
ret Test.2;
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.13 = 0i64;
|
let Test.14 = Z ;
|
||||||
let Test.15 = 0i64;
|
let Test.13 = S Test.14;
|
||||||
let Test.17 = 0i64;
|
let Test.12 = S Test.13;
|
||||||
let Test.18 = 1i64;
|
|
||||||
let Test.16 = Z ;
|
|
||||||
let Test.14 = S Test.16;
|
|
||||||
let Test.12 = S Test.14;
|
|
||||||
let Test.2 = S Test.12;
|
let Test.2 = S Test.12;
|
||||||
let Test.9 = 1i64;
|
let Test.9 = 1i64;
|
||||||
let Test.10 = GetTagId Test.2;
|
let Test.10 = GetTagId Test.2;
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.19 = 0i64;
|
let Test.20 = Z ;
|
||||||
let Test.21 = 0i64;
|
let Test.19 = S Test.20;
|
||||||
let Test.23 = 0i64;
|
let Test.18 = S Test.19;
|
||||||
let Test.24 = 1i64;
|
|
||||||
let Test.22 = Z ;
|
|
||||||
let Test.20 = S Test.22;
|
|
||||||
let Test.18 = S Test.20;
|
|
||||||
let Test.2 = S Test.18;
|
let Test.2 = S Test.18;
|
||||||
let Test.15 = 0i64;
|
let Test.15 = 0i64;
|
||||||
let Test.16 = GetTagId Test.2;
|
let Test.16 = GetTagId Test.2;
|
||||||
|
|
|
@ -97,4 +97,3 @@ constFolding = \e ->
|
||||||
Pair y1 y2 -> Add y1 y2
|
Pair y1 y2 -> Add y1 y2
|
||||||
|
|
||||||
_ -> e
|
_ -> e
|
||||||
|
|
||||||
|
|
|
@ -38,12 +38,12 @@ makeMapHelp = \total, n, m ->
|
||||||
n1 = n - 1
|
n1 = n - 1
|
||||||
|
|
||||||
powerOf10 =
|
powerOf10 =
|
||||||
(n % 10 |> resultWithDefault 0) == 0
|
n |> Num.isMultipleOf 10
|
||||||
|
|
||||||
t1 = insert m n powerOf10
|
t1 = insert m n powerOf10
|
||||||
|
|
||||||
isFrequency =
|
isFrequency =
|
||||||
(n % 4 |> resultWithDefault 0) == 0
|
n |> Num.isMultipleOf 4
|
||||||
|
|
||||||
key = n1 + ((total - n1) // 5 |> resultWithDefault 0)
|
key = n1 + ((total - n1) // 5 |> resultWithDefault 0)
|
||||||
t2 = if isFrequency then delete t1 key else t1
|
t2 = if isFrequency then delete t1 key else t1
|
||||||
|
@ -85,8 +85,6 @@ isRed = \tree ->
|
||||||
Node Red _ _ _ _ -> True
|
Node Red _ _ _ _ -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
lt = \x, y -> x < y
|
|
||||||
|
|
||||||
ins : Tree I64 Bool, I64, Bool -> Tree I64 Bool
|
ins : Tree I64 Bool, I64, Bool -> Tree I64 Bool
|
||||||
ins = \tree, kx, vx ->
|
ins = \tree, kx, vx ->
|
||||||
when tree is
|
when tree is
|
||||||
|
@ -94,19 +92,24 @@ ins = \tree, kx, vx ->
|
||||||
Node Red Leaf kx vx Leaf
|
Node Red Leaf kx vx Leaf
|
||||||
|
|
||||||
Node Red a ky vy b ->
|
Node Red a ky vy b ->
|
||||||
if lt kx ky then
|
when Num.compare kx ky is
|
||||||
Node Red (ins a kx vx) ky vy b
|
LT -> Node Red (ins a kx vx) ky vy b
|
||||||
else if lt ky kx then
|
GT -> Node Red a ky vy (ins b kx vx)
|
||||||
Node Red a ky vy (ins b kx vx)
|
EQ -> Node Red a ky vy (ins b kx vx)
|
||||||
else
|
|
||||||
Node Red a ky vy (ins b kx vx)
|
|
||||||
|
|
||||||
Node Black a ky vy b ->
|
Node Black a ky vy b ->
|
||||||
if lt kx ky then
|
when Num.compare kx ky is
|
||||||
(if isRed a then balanceLeft (ins a kx vx) ky vy b else Node Black (ins a kx vx) ky vy b)
|
LT ->
|
||||||
else if lt ky kx then
|
when isRed a is
|
||||||
(if isRed b then balanceRight a ky vy (ins b kx vx) else Node Black a ky vy (ins b kx vx))
|
True -> balanceLeft (ins a kx vx) ky vy b
|
||||||
else Node Black a kx vx b
|
False -> Node Black (ins a kx vx) ky vy b
|
||||||
|
|
||||||
|
GT ->
|
||||||
|
when isRed b is
|
||||||
|
True -> balanceRight a ky vy (ins b kx vx)
|
||||||
|
False -> Node Black a ky vy (ins b kx vx)
|
||||||
|
EQ ->
|
||||||
|
Node Black a kx vx b
|
||||||
|
|
||||||
balanceLeft : Tree a b, a, b, Tree a b -> Tree a b
|
balanceLeft : Tree a b, a, b, Tree a b -> Tree a b
|
||||||
balanceLeft = \l, k, v, r ->
|
balanceLeft = \l, k, v, r ->
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
app "effect-example"
|
app "effect-example"
|
||||||
packages { base: "thing/platform-dir" }
|
packages { base: "thing/platform-dir" }
|
||||||
imports [base.Task]
|
imports [fx.Effect]
|
||||||
provides [ main ] to base
|
provides [ main ] to base
|
||||||
|
|
||||||
main : Task.Task {} []
|
main : Effect.Effect {}
|
||||||
main =
|
main =
|
||||||
Task.after Task.getLine \lineThisThing -> Task.putLine lineThisThing
|
Effect.after Effect.getLine \lineThisThing -> Effect.putLine lineThisThing
|
||||||
|
|
23
examples/effect/thing/platform-dir/Cargo.lock
generated
23
examples/effect/thing/platform-dir/Cargo.lock
generated
|
@ -1,23 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
[[package]]
|
|
||||||
name = "host"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"roc_std 0.1.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.79"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "roc_std"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
|
|
@ -1,14 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "host"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["The Roc Contributors"]
|
|
||||||
license = "UPL-1.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["staticlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
roc_std = { path = "../../../../roc_std" }
|
|
||||||
|
|
||||||
[workspace]
|
|
|
@ -1,16 +1,16 @@
|
||||||
platform folkertdev/foo
|
platform folkertdev/foo
|
||||||
requires { main : Effect {} }
|
requires {model=>Model, msg=>Msg} {main : Effect {}}
|
||||||
exposes []
|
exposes []
|
||||||
packages {}
|
packages {}
|
||||||
imports [Task]
|
imports [fx.Effect]
|
||||||
provides [ mainForHost ]
|
provides [ mainForHost ]
|
||||||
effects fx.Effect
|
effects fx.Effect
|
||||||
{
|
{
|
||||||
putChar : I64 -> Effect {},
|
|
||||||
putLine : Str -> Effect {},
|
putLine : Str -> Effect {},
|
||||||
getLine : Effect Str
|
getLine : Effect Str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
mainForHost : Task.Task {} [] as Fx
|
|
||||||
|
mainForHost : Effect.Effect {} as Fx
|
||||||
mainForHost = main
|
mainForHost = main
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
interface Task
|
|
||||||
exposes [ Task, after, always, fail, map, putLine, getLine ]
|
|
||||||
imports [ Effect ]
|
|
||||||
|
|
||||||
Task a err : Effect.Effect (Result a err)
|
|
||||||
|
|
||||||
always : a -> Task a *
|
|
||||||
always = \x -> Effect.always (Ok x)
|
|
||||||
|
|
||||||
fail : err -> Task * err
|
|
||||||
fail = \x -> Effect.always (Err x)
|
|
||||||
|
|
||||||
getLine : Task Str *
|
|
||||||
getLine = Effect.after Effect.getLine always
|
|
||||||
|
|
||||||
putLine : Str -> Task {} *
|
|
||||||
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})
|
|
||||||
|
|
||||||
map : Task a err, (a -> b) -> Task b err
|
|
||||||
map = \task, transform ->
|
|
||||||
Effect.map task \res ->
|
|
||||||
when res is
|
|
||||||
Ok x -> Ok (transform x)
|
|
||||||
Err e -> Err e
|
|
||||||
|
|
||||||
after : Task a err, (a -> Task b err) -> Task b err
|
|
||||||
after = \task, transform ->
|
|
||||||
Effect.after task \res ->
|
|
||||||
when res is
|
|
||||||
Ok x -> transform x
|
|
||||||
Err e -> Task.fail e
|
|
|
@ -1,7 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
extern int rust_main();
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
return rust_main();
|
|
||||||
}
|
|
184
examples/effect/thing/platform-dir/host.zig
Normal file
184
examples/effect/thing/platform-dir/host.zig
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const str = @import("str");
|
||||||
|
const RocStr = str.RocStr;
|
||||||
|
const testing = std.testing;
|
||||||
|
const expectEqual = testing.expectEqual;
|
||||||
|
const expect = testing.expect;
|
||||||
|
const maxInt = std.math.maxInt;
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
// This is a workaround for https://github.com/ziglang/zig/issues/8218
|
||||||
|
// which is only necessary on macOS.
|
||||||
|
//
|
||||||
|
// Once that issue is fixed, we can undo the changes in
|
||||||
|
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
|
||||||
|
// -fcompiler-rt in link.rs instead of doing this. Note that this
|
||||||
|
// workaround is present in many host.zig files, so make sure to undo
|
||||||
|
// it everywhere!
|
||||||
|
if (std.builtin.os.tag == .macos) {
|
||||||
|
_ = @import("compiler_rt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mem = std.mem;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
|
extern fn roc__mainForHost_1_exposed([*]u8) void;
|
||||||
|
extern fn roc__mainForHost_size() i64;
|
||||||
|
extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
|
||||||
|
extern fn roc__mainForHost_1_Fx_size() i64;
|
||||||
|
extern fn roc__mainForHost_1_Fx_result_size() i64;
|
||||||
|
|
||||||
|
extern fn malloc(size: usize) callconv(.C) ?*c_void;
|
||||||
|
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
|
||||||
|
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
|
||||||
|
|
||||||
|
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
|
||||||
|
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||||
|
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Unit = extern struct {};
|
||||||
|
|
||||||
|
pub export fn main() u8 {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
|
||||||
|
const size = @intCast(usize, roc__mainForHost_size());
|
||||||
|
const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable;
|
||||||
|
var output = @ptrCast([*]u8, raw_output);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
std.heap.c_allocator.free(raw_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ts1: std.os.timespec = undefined;
|
||||||
|
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
|
||||||
|
|
||||||
|
roc__mainForHost_1_exposed(output);
|
||||||
|
|
||||||
|
const elements = @ptrCast([*]u64, @alignCast(8, output));
|
||||||
|
|
||||||
|
var flag = elements[0];
|
||||||
|
|
||||||
|
if (flag == 0) {
|
||||||
|
// all is well
|
||||||
|
const closure_data_pointer = @ptrCast([*]u8, output[8..size]);
|
||||||
|
|
||||||
|
call_the_closure(closure_data_pointer);
|
||||||
|
} else {
|
||||||
|
const msg = @intToPtr([*:0]const u8, elements[1]);
|
||||||
|
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ts2: std.os.timespec = undefined;
|
||||||
|
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
|
||||||
|
|
||||||
|
const delta = to_seconds(ts2) - to_seconds(ts1);
|
||||||
|
|
||||||
|
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_seconds(tms: std.os.timespec) f64 {
|
||||||
|
return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
||||||
|
const size = roc__mainForHost_1_Fx_result_size();
|
||||||
|
const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable;
|
||||||
|
var output = @ptrCast([*]u8, raw_output);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
std.heap.c_allocator.free(raw_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flags: u8 = 0;
|
||||||
|
|
||||||
|
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output);
|
||||||
|
|
||||||
|
const elements = @ptrCast([*]u64, @alignCast(8, output));
|
||||||
|
|
||||||
|
var flag = elements[0];
|
||||||
|
|
||||||
|
if (flag == 0) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub export fn roc_fx_getLine() str.RocStr {
|
||||||
|
if (roc_fx_getLine_help()) |value| {
|
||||||
|
return value;
|
||||||
|
} else |err| {
|
||||||
|
return str.RocStr.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roc_fx_getLine_help() !RocStr {
|
||||||
|
const stdin = std.io.getStdIn().reader();
|
||||||
|
var buf: [400]u8 = undefined;
|
||||||
|
|
||||||
|
const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse "";
|
||||||
|
|
||||||
|
return str.RocStr.init(@ptrCast([*]const u8, line), line.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
|
||||||
|
for (rocPath.asSlice()) |char| {
|
||||||
|
stdout.print("{c}", .{char}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.print("\n", .{}) catch unreachable;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetInt = extern struct {
|
||||||
|
value: i64,
|
||||||
|
error_code: u8,
|
||||||
|
is_error: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub export fn roc_fx_getInt() GetInt {
|
||||||
|
if (roc_fx_getInt_help()) |value| {
|
||||||
|
const get_int = GetInt{ .is_error = false, .value = value, .error_code = 0 };
|
||||||
|
return get_int;
|
||||||
|
} else |err| switch (err) {
|
||||||
|
error.InvalidCharacter => {
|
||||||
|
return GetInt{ .is_error = true, .value = 0, .error_code = 0 };
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
return GetInt{ .is_error = true, .value = 0, .error_code = 1 };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roc_fx_getInt_help() !i64 {
|
||||||
|
const stdin = std.io.getStdIn().reader();
|
||||||
|
var buf: [40]u8 = undefined;
|
||||||
|
|
||||||
|
const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse "";
|
||||||
|
|
||||||
|
return std.fmt.parseInt(i64, line, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readLine() []u8 {
|
||||||
|
const stdin = std.io.getStdIn().reader();
|
||||||
|
return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse "";
|
||||||
|
}
|
|
@ -1,158 +0,0 @@
|
||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
use roc_std::alloca;
|
|
||||||
use roc_std::RocCallResult;
|
|
||||||
use roc_std::RocStr;
|
|
||||||
use std::alloc::Layout;
|
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#[link_name = "roc__mainForHost_1_exposed"]
|
|
||||||
fn roc_main(output: *mut u8) -> ();
|
|
||||||
|
|
||||||
#[link_name = "roc__mainForHost_1_size"]
|
|
||||||
fn roc_main_size() -> i64;
|
|
||||||
|
|
||||||
#[link_name = "roc__mainForHost_1_Fx_caller"]
|
|
||||||
fn call_Fx(
|
|
||||||
flags: &(),
|
|
||||||
function_pointer: *const u8,
|
|
||||||
closure_data: *const u8,
|
|
||||||
output: *mut u8,
|
|
||||||
) -> ();
|
|
||||||
|
|
||||||
#[link_name = "roc__mainForHost_1_Fx_size"]
|
|
||||||
fn size_Fx() -> i64;
|
|
||||||
|
|
||||||
#[link_name = "roc__mainForHost_1_Fx_result_size"]
|
|
||||||
fn size_Fx_result() -> i64;
|
|
||||||
|
|
||||||
fn malloc(size: usize) -> *mut c_void;
|
|
||||||
fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void;
|
|
||||||
fn free(c_ptr: *mut c_void);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
|
||||||
return malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe fn roc_realloc(
|
|
||||||
c_ptr: *mut c_void,
|
|
||||||
new_size: usize,
|
|
||||||
_old_size: usize,
|
|
||||||
_alignment: u32,
|
|
||||||
) -> *mut c_void {
|
|
||||||
return realloc(c_ptr, new_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
|
||||||
return free(c_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn roc_fx_putChar(foo: i64) -> () {
|
|
||||||
let character = foo as u8 as char;
|
|
||||||
print!("{}", character);
|
|
||||||
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn roc_fx_putLine(line: RocStr) -> () {
|
|
||||||
let bytes = line.as_slice();
|
|
||||||
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
|
||||||
println!("{}", string);
|
|
||||||
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn roc_fx_getLine() -> RocStr {
|
|
||||||
use std::io::{self, BufRead};
|
|
||||||
|
|
||||||
let stdin = io::stdin();
|
|
||||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
|
||||||
|
|
||||||
RocStr::from_slice(line1.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
|
|
||||||
let size = size_Fx_result() as usize;
|
|
||||||
|
|
||||||
alloca::with_stack_bytes(size, |buffer| {
|
|
||||||
let buffer: *mut std::ffi::c_void = buffer;
|
|
||||||
let buffer: *mut u8 = buffer as *mut u8;
|
|
||||||
|
|
||||||
call_Fx(
|
|
||||||
&(),
|
|
||||||
function_pointer,
|
|
||||||
closure_data_ptr as *const u8,
|
|
||||||
buffer as *mut u8,
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = &*(buffer as *mut RocCallResult<()>);
|
|
||||||
|
|
||||||
match output.into() {
|
|
||||||
Ok(_) => 0,
|
|
||||||
Err(e) => panic!("failed with {}", e),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn rust_main() -> isize {
|
|
||||||
eprintln!("Running Roc closure");
|
|
||||||
let start_time = SystemTime::now();
|
|
||||||
|
|
||||||
let size = unsafe { roc_main_size() } as usize;
|
|
||||||
let layout = Layout::array::<u8>(size).unwrap();
|
|
||||||
let answer = unsafe {
|
|
||||||
let buffer = std::alloc::alloc(layout);
|
|
||||||
|
|
||||||
roc_main(buffer);
|
|
||||||
|
|
||||||
let output = &*(buffer as *mut RocCallResult<()>);
|
|
||||||
|
|
||||||
match output.into() {
|
|
||||||
Ok(()) => {
|
|
||||||
let function_pointer = {
|
|
||||||
// this is a pointer to the location where the function pointer is stored
|
|
||||||
// we pass just the function pointer
|
|
||||||
let temp = buffer.offset(8) as *const i64;
|
|
||||||
|
|
||||||
(*temp) as *const u8
|
|
||||||
};
|
|
||||||
|
|
||||||
let closure_data_ptr = buffer.offset(16);
|
|
||||||
|
|
||||||
let result =
|
|
||||||
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8);
|
|
||||||
|
|
||||||
std::alloc::dealloc(buffer, layout);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
Err(msg) => {
|
|
||||||
std::alloc::dealloc(buffer, layout);
|
|
||||||
|
|
||||||
panic!("Roc failed with message: {}", msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let end_time = SystemTime::now();
|
|
||||||
let duration = end_time.duration_since(start_time).unwrap();
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"Roc closure took {:.4} ms to compute this answer: {:?}",
|
|
||||||
duration.as_secs_f64() * 1000.0,
|
|
||||||
// truncate the answer, so stdout is not swamped
|
|
||||||
answer
|
|
||||||
);
|
|
||||||
|
|
||||||
// Exit code
|
|
||||||
0
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue