Implement struct-by-reference for structs over 4 machine words

This commit is contained in:
Ayaz Hafiz 2023-06-12 16:39:20 -05:00
parent e1c874ac23
commit 47fb9fe6f2
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
5 changed files with 83 additions and 23 deletions

View file

@ -1042,9 +1042,14 @@ pub(crate) fn build_exp_expr<'a, 'ctx>(
call, call,
), ),
Struct(sorted_fields) => { Struct(sorted_fields) => RocStruct::build(
RocStruct::build(env, layout_interner, scope, sorted_fields).into() env,
} layout_interner,
layout_interner.get_repr(layout),
scope,
sorted_fields,
)
.into(),
Reuse { Reuse {
arguments, arguments,
@ -1605,7 +1610,8 @@ fn build_tag<'a, 'ctx>(
UnionLayout::NonRecursive(tags) => { UnionLayout::NonRecursive(tags) => {
debug_assert!(union_size > 1); debug_assert!(union_size > 1);
let data = RocStruct::build(env, layout_interner, scope, arguments); let data_layout_repr = LayoutRepr::Struct(tags[tag_id as usize]);
let data = RocStruct::build(env, layout_interner, data_layout_repr, scope, arguments);
let roc_union = let roc_union =
RocUnion::tagged_from_slices(layout_interner, env.context, tags, env.target_info); RocUnion::tagged_from_slices(layout_interner, env.context, tags, env.target_info);
@ -1738,7 +1744,8 @@ fn build_tag<'a, 'ctx>(
&[other_fields], &[other_fields],
); );
let data = RocStruct::build(env, layout_interner, scope, arguments); let data_layout_repr = LayoutRepr::Struct(other_fields);
let data = RocStruct::build(env, layout_interner, data_layout_repr, scope, arguments);
let value = roc_union.as_struct_value(env, data, None); let value = roc_union.as_struct_value(env, data, None);

View file

@ -353,8 +353,6 @@ impl<'ctx> RocUnion<'ctx> {
) -> StructValue<'ctx> { ) -> StructValue<'ctx> {
debug_assert_eq!(tag_id.is_some(), self.tag_type.is_some()); debug_assert_eq!(tag_id.is_some(), self.tag_type.is_some());
let RocStruct::ByValue(data) = data;
let tag_alloca = env.builder.build_alloca(self.struct_type(), "tag_alloca"); let tag_alloca = env.builder.build_alloca(self.struct_type(), "tag_alloca");
let data_buffer = env let data_buffer = env
@ -367,16 +365,36 @@ impl<'ctx> RocUnion<'ctx> {
) )
.unwrap(); .unwrap();
let cast_pointer = env.builder.build_pointer_cast( match data {
data_buffer, // NOTE: the data may be smaller than the buffer, so there might be uninitialized
data.get_type().ptr_type(AddressSpace::default()), // bytes in the buffer. We should never touch those, but e.g. valgrind might not
"to_data_ptr", // realize that. If that comes up, the solution is to just fill it with zeros
); RocStruct::ByValue(value) => {
let cast_pointer = env.builder.build_pointer_cast(
data_buffer,
value.get_type().ptr_type(AddressSpace::default()),
"to_data_ptr",
);
env.builder.build_store(cast_pointer, value);
}
RocStruct::ByReference(ptr) => {
let cast_pointer =
env.builder
.build_pointer_cast(data_buffer, ptr.get_type(), "to_data_ptr");
// NOTE: the data may be smaller than the buffer, so there might be uninitialized env.builder
// bytes in the buffer. We should never touch those, but e.g. valgrind might not .build_memcpy(
// realize that. If that comes up, the solution is to just fill it with zeros cast_pointer,
env.builder.build_store(cast_pointer, data); self.data_align,
ptr,
self.data_align,
env.context
.i32_type()
.const_int(self.data_width as _, false),
)
.expect("memcpy invariants must have been upheld");
}
}
// set the tag id // set the tag id
// //

View file

@ -3,7 +3,7 @@
use bumpalo::collections::Vec as AVec; use bumpalo::collections::Vec as AVec;
use inkwell::{ use inkwell::{
types::{BasicType, BasicTypeEnum, StructType}, types::{BasicType, BasicTypeEnum, StructType},
values::{BasicValue, BasicValueEnum, StructValue}, values::{BasicValue, BasicValueEnum, PointerValue, StructValue},
}; };
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{InLayout, LayoutInterner, LayoutRepr, STLayoutInterner}; use roc_mono::layout::{InLayout, LayoutInterner, LayoutRepr, STLayoutInterner};
@ -65,6 +65,9 @@ fn basic_type_from_record<'a, 'ctx>(
pub(crate) enum RocStruct<'ctx> { pub(crate) enum RocStruct<'ctx> {
/// The roc struct should be passed by rvalue. /// The roc struct should be passed by rvalue.
ByValue(StructValue<'ctx>), ByValue(StructValue<'ctx>),
/// The roc struct should be passed by reference.
ByReference(PointerValue<'ctx>),
} }
impl<'ctx> Into<BasicValueEnum<'ctx>> for RocStruct<'ctx> { impl<'ctx> Into<BasicValueEnum<'ctx>> for RocStruct<'ctx> {
@ -87,16 +90,30 @@ impl<'ctx> RocStruct<'ctx> {
pub fn build<'a>( pub fn build<'a>(
env: &Env<'a, 'ctx, '_>, env: &Env<'a, 'ctx, '_>,
layout_interner: &mut STLayoutInterner<'a>, layout_interner: &mut STLayoutInterner<'a>,
layout_repr: LayoutRepr<'a>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
sorted_fields: &[Symbol], sorted_fields: &[Symbol],
) -> Self { ) -> Self {
let struct_val = build_struct_value(env, layout_interner, scope, sorted_fields); let BuildStruct {
RocStruct::ByValue(struct_val) struct_type,
struct_val,
} = build_struct_helper(env, layout_interner, scope, sorted_fields);
let passed_by_ref = layout_repr.is_passed_by_reference(layout_interner, env.target_info);
if passed_by_ref {
let alloca = env.builder.build_alloca(struct_type, "struct_alloca");
env.builder.build_store(alloca, struct_val);
RocStruct::ByReference(alloca)
} else {
RocStruct::ByValue(struct_val)
}
} }
pub fn as_basic_value_enum(&self) -> BasicValueEnum<'ctx> { pub fn as_basic_value_enum(&self) -> BasicValueEnum<'ctx> {
match self { match self {
RocStruct::ByValue(struct_val) => struct_val.as_basic_value_enum(), RocStruct::ByValue(struct_val) => struct_val.as_basic_value_enum(),
RocStruct::ByReference(struct_ptr) => struct_ptr.as_basic_value_enum(),
} }
} }
@ -157,12 +174,17 @@ fn index_struct_value<'a, 'ctx>(
) )
} }
fn build_struct_value<'a, 'ctx>( struct BuildStruct<'ctx> {
struct_type: StructType<'ctx>,
struct_val: StructValue<'ctx>,
}
fn build_struct_helper<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>, env: &Env<'a, 'ctx, '_>,
layout_interner: &mut STLayoutInterner<'a>, layout_interner: &mut STLayoutInterner<'a>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
sorted_fields: &[Symbol], sorted_fields: &[Symbol],
) -> StructValue<'ctx> { ) -> BuildStruct<'ctx> {
let ctx = env.context; let ctx = env.context;
// Determine types // Determine types
@ -197,9 +219,12 @@ fn build_struct_value<'a, 'ctx>(
// Create the struct_type // Create the struct_type
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_val = struct_from_fields(env, struct_type, field_vals.into_iter().enumerate());
// Insert field exprs into struct_val BuildStruct {
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()) struct_type,
struct_val,
}
} }
pub fn struct_from_fields<'a, 'ctx, 'env, I>( pub fn struct_from_fields<'a, 'ctx, 'env, I>(

View file

@ -2617,6 +2617,11 @@ impl<'a> LayoutRepr<'a> {
} }
} }
LayoutRepr::Union(UnionLayout::NonRecursive(_)) => true, LayoutRepr::Union(UnionLayout::NonRecursive(_)) => true,
LayoutRepr::Struct(_) => {
// TODO: write tests for this!
self.stack_size(interner, target_info) as usize > target_info.max_by_value_size()
}
LayoutRepr::LambdaSet(lambda_set) => interner LayoutRepr::LambdaSet(lambda_set) => interner
.get_repr(lambda_set.runtime_representation()) .get_repr(lambda_set.runtime_representation())
.is_passed_by_reference(interner, target_info), .is_passed_by_reference(interner, target_info),

View file

@ -67,6 +67,11 @@ impl TargetInfo {
} }
} }
pub const fn max_by_value_size(&self) -> usize {
// Pass values larger than 4 machine words by reference.
self.ptr_size() * 4
}
pub const fn ptr_alignment_bytes(&self) -> usize { pub const fn ptr_alignment_bytes(&self) -> usize {
self.architecture.ptr_alignment_bytes() self.architecture.ptr_alignment_bytes()
} }