mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
nullable wrapped tags
This commit is contained in:
parent
98e1ee1bf7
commit
546b702740
8 changed files with 361 additions and 10 deletions
|
@ -1,6 +1,6 @@
|
|||
use crate::debug_info_init;
|
||||
use crate::llvm::bitcode::call_str_bitcode_fn;
|
||||
use crate::llvm::build::{get_tag_id, store_roc_value, Env};
|
||||
use crate::llvm::build::{get_tag_id, store_roc_value, tag_pointer_clear_tag_id, Env};
|
||||
use crate::llvm::build_list::{self, incrementing_elem_loop};
|
||||
use crate::llvm::convert::{basic_type_from_layout, RocUnion};
|
||||
use inkwell::builder::Builder;
|
||||
|
@ -503,6 +503,9 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
.unwrap();
|
||||
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
let layout = Layout::struct_no_name_order(
|
||||
env.arena.alloc([layout, union_layout.tag_id_layout()]),
|
||||
);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
|
@ -533,7 +536,182 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
Recursive(_) => todo!(),
|
||||
NonNullableUnwrapped(_) => todo!(),
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
other_tags,
|
||||
} => {
|
||||
let switch_block = env.context.append_basic_block(parent, "switch_block");
|
||||
let null_block = env.context.append_basic_block(parent, "null_block");
|
||||
|
||||
let id = get_tag_id(env, parent, &union_layout, tag_value);
|
||||
|
||||
let comparison = env
|
||||
.builder
|
||||
.build_is_null(tag_value.into_pointer_value(), "is_null");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(comparison, null_block, switch_block);
|
||||
|
||||
{
|
||||
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
|
||||
|
||||
for i in 0..other_tags.len() + 1 {
|
||||
if i == nullable_id as _ {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// write the "pointer" af the current offset
|
||||
{
|
||||
// first, store tag id as u32
|
||||
let tag_id_intval = env.context.i32_type().const_int(i as _, false);
|
||||
build_copy(env, ptr, offset, tag_id_intval.into());
|
||||
|
||||
// increment offset by 4
|
||||
let four = env.ptr_int().const_int(4, false);
|
||||
let offset = env.builder.build_int_add(offset, four, "");
|
||||
|
||||
// cast to u32
|
||||
let extra_offset =
|
||||
env.builder
|
||||
.build_int_cast(extra_offset, env.context.i32_type(), "");
|
||||
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
}
|
||||
|
||||
let fields = if i >= nullable_id as _ {
|
||||
other_tags[i - 1]
|
||||
} else {
|
||||
other_tags[i]
|
||||
};
|
||||
|
||||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
cases.push((id.get_type().const_int(i as u64, false), block));
|
||||
}
|
||||
|
||||
env.builder.position_at_end(switch_block);
|
||||
|
||||
match cases.pop() {
|
||||
Some((_, default)) => {
|
||||
env.builder.build_switch(id, default, &cases);
|
||||
}
|
||||
None => {
|
||||
// we're serializing an empty tag union; this code is effectively unreachable
|
||||
env.builder.build_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(null_block);
|
||||
|
||||
let value = env.ptr_int().const_zero();
|
||||
build_copy(env, ptr, offset, value.into());
|
||||
|
||||
env.builder.build_return(Some(&extra_offset));
|
||||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
let other_block = env.context.append_basic_block(parent, "other_block");
|
||||
let null_block = env.context.append_basic_block(parent, "null_block");
|
||||
|
||||
let comparison = env
|
||||
.builder
|
||||
.build_is_null(tag_value.into_pointer_value(), "is_null");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(comparison, null_block, other_block);
|
||||
|
||||
{
|
||||
env.builder.position_at_end(null_block);
|
||||
|
||||
let value = env.ptr_int().const_zero();
|
||||
build_copy(env, ptr, offset, value.into());
|
||||
|
||||
env.builder.build_return(Some(&extra_offset));
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(other_block);
|
||||
|
||||
// write the "pointer" af the current offset
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
|
||||
let layout = Layout::struct_no_name_order(other_fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(
|
||||
tag_value.into_pointer_value(),
|
||||
RocUnion::TAG_DATA_INDEX,
|
||||
"tag_data",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -472,10 +472,13 @@ impl<'a> UnionLayout<'a> {
|
|||
tags.len() < target_info.ptr_width() as usize
|
||||
}
|
||||
|
||||
pub const POINTER_MASK_32BIT: usize = 0b0000_0111;
|
||||
pub const POINTER_MASK_64BIT: usize = 0b0000_0011;
|
||||
|
||||
pub fn tag_id_pointer_bits_and_mask(target_info: TargetInfo) -> (usize, usize) {
|
||||
match target_info.ptr_width() {
|
||||
PtrWidth::Bytes8 => (3, 0b0000_0111),
|
||||
PtrWidth::Bytes4 => (2, 0b0000_0011),
|
||||
PtrWidth::Bytes8 => (3, Self::POINTER_MASK_64BIT),
|
||||
PtrWidth::Bytes4 => (2, Self::POINTER_MASK_32BIT),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,15 @@ impl ReplAppMemory for CliMemory {
|
|||
let reference: &RocStr = unsafe { std::mem::transmute(addr) };
|
||||
reference.as_str()
|
||||
}
|
||||
|
||||
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) {
|
||||
let addr_with_id = self.deref_usize(addr);
|
||||
let tag_id_mask = 0b111;
|
||||
|
||||
let tag_id = addr_with_id & tag_id_mask;
|
||||
let data_addr = addr_with_id & !tag_id_mask;
|
||||
(tag_id as _, data_addr as _)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mono_module_to_dylib<'a>(
|
||||
|
|
|
@ -257,14 +257,12 @@ fn tag_id_from_recursive_ptr<'a, M: ReplAppMemory>(
|
|||
rec_addr: usize,
|
||||
) -> (i64, usize) {
|
||||
let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info);
|
||||
let addr_with_id = mem.deref_usize(rec_addr);
|
||||
|
||||
if tag_in_ptr {
|
||||
let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info);
|
||||
let tag_id = addr_with_id & tag_id_mask;
|
||||
let data_addr = addr_with_id & !tag_id_mask;
|
||||
(tag_id as i64, data_addr)
|
||||
let (tag_id, data_addr) = mem.deref_pointer_with_tag_id(rec_addr);
|
||||
(tag_id as _, data_addr as _)
|
||||
} else {
|
||||
let addr_with_id = mem.deref_usize(rec_addr);
|
||||
let tag_id = tag_id_from_data(env, mem, union_layout, addr_with_id);
|
||||
(tag_id, addr_with_id)
|
||||
}
|
||||
|
|
|
@ -81,4 +81,6 @@ pub trait ReplAppMemory {
|
|||
}
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str;
|
||||
|
||||
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,14 @@ impl ReplAppMemory for ExpectMemory {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) {
|
||||
// because addr is an index/offset, we cannot use the low bits
|
||||
let tag_id = self.deref_u32(addr);
|
||||
let offset = self.deref_u32(addr + 4);
|
||||
|
||||
(tag_id as _, offset as _)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ExpectReplApp<'a> {
|
||||
|
|
|
@ -336,7 +336,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_result() {
|
||||
fn lookup_copy_result() {
|
||||
run_expect_test(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -375,6 +375,50 @@ mod test {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_clone_result() {
|
||||
run_expect_test(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main = 0
|
||||
|
||||
expect
|
||||
a : Result Str Str
|
||||
a = Ok "foo"
|
||||
|
||||
b : Result Str Str
|
||||
b = Err "bar"
|
||||
|
||||
a == b
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
This expectation failed:
|
||||
|
||||
5│> expect
|
||||
6│> a : Result Str Str
|
||||
7│> a = Ok "foo"
|
||||
8│>
|
||||
9│> b : Result Str Str
|
||||
10│> b = Err "bar"
|
||||
11│>
|
||||
12│> a == b
|
||||
|
||||
When it failed, these variables had these values:
|
||||
|
||||
a : Result Str Str
|
||||
a = Ok "foo"
|
||||
|
||||
b : Result Str Str
|
||||
b = Err "bar"
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_copy_record() {
|
||||
run_expect_test(
|
||||
|
@ -598,4 +642,104 @@ mod test {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linked_list() {
|
||||
run_expect_test(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main = 0
|
||||
|
||||
ConsList a : [ Nil, Cons a (ConsList a) ]
|
||||
|
||||
cons = \list, x -> Cons x list
|
||||
|
||||
expect
|
||||
a : ConsList Str
|
||||
a = Nil
|
||||
|
||||
b : ConsList Str
|
||||
b = Nil
|
||||
|> cons "Astra mortemque praestare gradatim"
|
||||
|> cons "Profundum et fundamentum"
|
||||
|
||||
a == b
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
This expectation failed:
|
||||
|
||||
9│> expect
|
||||
10│> a : ConsList Str
|
||||
11│> a = Nil
|
||||
12│>
|
||||
13│> b : ConsList Str
|
||||
14│> b = Nil
|
||||
15│> |> cons "Astra mortemque praestare gradatim"
|
||||
16│> |> cons "Profundum et fundamentum"
|
||||
17│>
|
||||
18│> a == b
|
||||
|
||||
When it failed, these variables had these values:
|
||||
|
||||
a : ConsList Str
|
||||
a = Nil
|
||||
|
||||
b : ConsList Str
|
||||
b = Cons "Profundum et fundamentum" (Cons "Astra mortemque praestare gradatim" Nil)
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree() {
|
||||
run_expect_test(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main = 0
|
||||
|
||||
Tree a : [ Empty, Leaf a, Node (Tree a) (Tree a) ]
|
||||
|
||||
cons = \list, x -> Cons x list
|
||||
|
||||
expect
|
||||
a : Tree Str
|
||||
a = Leaf "Astra mortemque praestare gradatim"
|
||||
|
||||
b : Tree Str
|
||||
b = Node Empty Empty
|
||||
|
||||
a == b
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
This expectation failed:
|
||||
|
||||
9│> expect
|
||||
10│> a : Tree Str
|
||||
11│> a = Leaf "Astra mortemque praestare gradatim"
|
||||
12│>
|
||||
13│> b : Tree Str
|
||||
14│> b = Node Empty Empty
|
||||
15│>
|
||||
16│> a == b
|
||||
|
||||
When it failed, these variables had these values:
|
||||
|
||||
a : Tree Str
|
||||
a = Leaf "Astra mortemque praestare gradatim"
|
||||
|
||||
b : Tree Str
|
||||
b = Node Empty Empty
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,15 @@ impl<'a> ReplAppMemory for WasmMemory<'a> {
|
|||
|
||||
unsafe { std::str::from_utf8_unchecked(str_bytes) }
|
||||
}
|
||||
|
||||
fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) {
|
||||
let addr_with_id = self.deref_usize(addr);
|
||||
let tag_id_mask = 0b11;
|
||||
|
||||
let tag_id = addr_with_id & tag_id_mask;
|
||||
let data_addr = addr_with_id & !tag_id_mask;
|
||||
(tag_id as _, data_addr as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> WasmReplApp<'a> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue