Add llvm_alignment_bytes and memcpy function

This commit is contained in:
Ayaz Hafiz 2023-06-15 16:49:20 -05:00
parent 75c290273d
commit f95cef8086
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
5 changed files with 222 additions and 104 deletions

View file

@ -0,0 +1,132 @@
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{
round_up_to_alignment, Builtin, Discriminant, InLayout, LayoutInterner, LayoutRepr,
STLayoutInterner, UnionLayout,
};
use roc_target::Architecture;
pub trait LlvmAlignment<'a> {
/// Alignment of a layout in bytes, as known and expected by LLVM.
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner<'a>) -> u32;
}
impl LlvmAlignment<'_> for IntWidth {
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner) -> u32 {
match self {
IntWidth::U8 | IntWidth::I8 => 1,
IntWidth::U16 | IntWidth::I16 => 2,
IntWidth::U32 | IntWidth::I32 => 4,
IntWidth::U64 | IntWidth::I64 => 8,
IntWidth::U128 | IntWidth::I128 => {
// 128-bit integers are not consistently represented by LLVM.
// - AArch64 uses 16-byte alignment (https://godbolt.org/z/dYrfG5o4b)
// - x86-64 uses 8-byte alignment (https://godbolt.org/z/qj5Mann6b)
let arch = interner.target_info().architecture;
match arch {
Architecture::X86_64 => 8,
_ => 16,
}
}
}
}
}
impl<'a> LlvmAlignment<'a> for FloatWidth {
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner<'a>) -> u32 {
self.alignment_bytes(interner.target_info())
}
}
impl<'a> LlvmAlignment<'a> for Discriminant {
fn llvm_alignment_bytes(&self, _interner: &STLayoutInterner<'a>) -> u32 {
self.alignment_bytes()
}
}
impl<'a> LlvmAlignment<'a> for Builtin<'a> {
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner<'a>) -> u32 {
use std::mem::align_of;
use Builtin::*;
let ptr_width = interner.target_info().ptr_width() as u32;
// for our data structures, what counts is the alignment of the `( ptr, len )` tuple, and
// since both of those are one pointer size, the alignment of that structure is a pointer
// size
match self {
Int(int_width) => (*int_width).llvm_alignment_bytes(interner),
Float(float_width) => float_width.llvm_alignment_bytes(interner),
Bool => align_of::<bool>() as u32,
Decimal => IntWidth::I128.llvm_alignment_bytes(interner),
// we often treat these as i128 (64-bit systems)
// or i64 (32-bit systems).
//
// In webassembly, For that to be safe
// they must be aligned to allow such access
List(_) => ptr_width,
Str => ptr_width,
}
}
}
impl<'a> LlvmAlignment<'a> for UnionLayout<'a> {
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner<'a>) -> u32 {
use UnionLayout::*;
match self {
NonRecursive(tags) => {
let max_alignment = tags
.iter()
.flat_map(|layouts| {
layouts
.iter()
.map(|layout| layout.llvm_alignment_bytes(interner))
})
.max();
let discriminant = self.discriminant();
match max_alignment {
Some(align) => round_up_to_alignment(
align.max(discriminant.llvm_alignment_bytes(interner)),
discriminant.llvm_alignment_bytes(interner),
),
None => {
// none of the tags had any payload, but the tag id still contains information
discriminant.llvm_alignment_bytes(interner)
}
}
}
Recursive(_)
| NullableWrapped { .. }
| NullableUnwrapped { .. }
| NonNullableUnwrapped(_) => interner.target_info().ptr_width() as u32,
}
}
}
impl<'a> LlvmAlignment<'a> for LayoutRepr<'a> {
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner<'a>) -> u32 {
use LayoutRepr::*;
match self {
Struct(field_layouts) => field_layouts
.iter()
.map(|x| x.llvm_alignment_bytes(interner))
.max()
.unwrap_or(0),
Union(variant) => variant.llvm_alignment_bytes(interner),
LambdaSet(lambda_set) => lambda_set
.runtime_representation()
.llvm_alignment_bytes(interner),
Builtin(builtin) => builtin.llvm_alignment_bytes(interner),
RecursivePointer(_) => interner.target_info().ptr_width() as u32,
Boxed(_) => interner.target_info().ptr_width() as u32,
}
}
}
impl<'a> LlvmAlignment<'a> for InLayout<'a> {
fn llvm_alignment_bytes(&self, interner: &STLayoutInterner<'a>) -> u32 {
interner.get_repr(*self).llvm_alignment_bytes(interner)
}
}

View file

@ -4,6 +4,7 @@ use crate::llvm::convert::{
argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout, zig_str_type,
};
use crate::llvm::expect::{clone_to_shared_memory, SharedMemoryPointer};
use crate::llvm::memcpy::build_memcpy;
use crate::llvm::refcounting::{
build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount,
};
@ -2370,24 +2371,13 @@ pub fn store_roc_value<'a, 'ctx>(
if layout_interner.is_passed_by_reference(layout) {
debug_assert!(value.is_pointer_value());
let align_bytes = layout_interner.alignment_bytes(layout);
if align_bytes > 0 {
let width = basic_type_from_layout(env, layout_interner, layout)
.size_of()
.unwrap();
let align_bytes = layout_interner.alignment_bytes_for_llvm(layout);
env.builder
.build_memcpy(
destination,
align_bytes,
value.into_pointer_value(),
align_bytes,
width,
)
.unwrap();
}
build_memcpy(
env,
layout_interner,
layout_interner.get_repr(layout),
destination,
value.into_pointer_value(),
);
} else {
let destination_type = destination
.get_type()
@ -2922,51 +2912,40 @@ fn build_return<'a, 'ctx>(
let destination = out_parameter.into_pointer_value();
if layout_interner.is_passed_by_reference(layout) {
let align_bytes = layout_interner.alignment_bytes(layout);
debug_assert!(
value.is_pointer_value(),
"{:?}: {:?}\n{:?}",
parent.get_name(),
value,
layout
);
if align_bytes > 0 {
debug_assert!(
value.is_pointer_value(),
"{:?}: {:?}\n{:?}",
parent.get_name(),
value,
layout
);
// What we want to do here is
//
// let value_ptr = value.into_pointer_value();
// if value_ptr.get_first_use().is_some() {
// value_ptr.replace_all_uses_with(destination);
//
// In other words, if the source pointer is used,
// then we just subsitute the source for the input pointer, done.
//
// Only that does not work if the source is not written to.
// A simple example is the identity function
//
// A slightly more complex case that will also make the above not
// work is when the source pointer is only incremented, but not
// written to. Then there is a first_use, but it's still invalid to
// subsitute source with destination
//
// Hence, we explicitly memcpy source to destination, and rely on
// LLVM optimizing away any inefficiencies.
let width = basic_type_from_layout(env, layout_interner, layout)
.size_of()
.unwrap();
let align_bytes = layout_interner.alignment_bytes_for_llvm(layout);
env.builder
.build_memcpy(
destination,
align_bytes,
value.into_pointer_value(),
align_bytes,
width,
)
.unwrap();
}
// What we want to do here is
//
// let value_ptr = value.into_pointer_value();
// if value_ptr.get_first_use().is_some() {
// value_ptr.replace_all_uses_with(destination);
//
// In other words, if the source pointer is used,
// then we just subsitute the source for the input pointer, done.
//
// Only that does not work if the source is not written to.
// A simple example is the identity function
//
// A slightly more complex case that will also make the above not
// work is when the source pointer is only incremented, but not
// written to. Then there is a first_use, but it's still invalid to
// subsitute source with destination
//
// Hence, we explicitly memcpy source to destination, and rely on
// LLVM optimizing away any inefficiencies.
build_memcpy(
env,
layout_interner,
layout_interner.get_repr(layout),
destination,
value.into_pointer_value(),
);
} else {
env.builder.build_store(destination, value);
}
@ -5153,24 +5132,13 @@ fn build_closure_caller<'a, 'ctx>(
);
if layout_interner.is_passed_by_reference(return_layout) {
let align_bytes = layout_interner.alignment_bytes(return_layout);
if align_bytes > 0 {
let width = basic_type_from_layout(env, layout_interner, return_layout)
.size_of()
.unwrap();
let align_bytes = layout_interner.alignment_bytes_for_llvm(return_layout);
env.builder
.build_memcpy(
output,
align_bytes,
call_result.into_pointer_value(),
align_bytes,
width,
)
.unwrap();
}
build_memcpy(
env,
layout_interner,
layout_interner.get_repr(return_layout),
output,
call_result.into_pointer_value(),
);
} else {
builder.build_store(output, call_result);
}

View file

@ -1,11 +1,11 @@
use crate::llvm::build::{BuilderExt, Env};
use crate::llvm::memcpy::build_memcpy;
use bumpalo::collections::Vec as AVec;
use inkwell::context::Context;
use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
use inkwell::values::StructValue;
use inkwell::AddressSpace;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_mono::layout::{
round_up_to_alignment, Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, STLayoutInterner,
UnionLayout,
@ -18,10 +18,18 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &'env STLayoutInterner<'a>,
layout: InLayout<'_>,
) -> BasicTypeEnum<'ctx> {
basic_type_from_layout_repr(env, layout_interner, layout_interner.get_repr(layout))
}
pub fn basic_type_from_layout_repr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &'env STLayoutInterner<'a>,
layout: LayoutRepr<'_>,
) -> BasicTypeEnum<'ctx> {
use LayoutRepr::*;
match layout_interner.get_repr(layout) {
match layout {
Struct(sorted_fields, ..) => {
basic_type_from_record(env, layout_interner, sorted_fields).into()
}
@ -440,28 +448,13 @@ impl<'ctx> RocUnion<'ctx> {
"to_data_ptr",
);
let (payload_stack_size, payload_align) =
data_layout.stack_size_and_alignment(layout_interner, env.target_info);
if payload_stack_size > 0 {
let bytes_to_memcpy = payload_data_ptr
.get_type()
.get_element_type()
.size_of()
.unwrap();
let align_bytes =
data_layout.alignment_bytes_for_llvm(layout_interner, env.target_info);
env.builder
.build_memcpy(
cast_tag_pointer,
self.data_align,
payload_data_ptr,
align_bytes,
bytes_to_memcpy
)
.unwrap_or_else(|e|internal_error!( "memcpy invariants must have been upheld: {e:?}. Union data align={}, source data align={}.", self.data_align, payload_align));
}
build_memcpy(
env,
layout_interner,
data_layout,
cast_tag_pointer,
payload_data_ptr,
);
}
}

View file

@ -0,0 +1,23 @@
use inkwell::{types::BasicType, values::PointerValue};
use roc_mono::layout::{LayoutRepr, STLayoutInterner};
use super::{align::LlvmAlignment, build::Env, convert::basic_type_from_layout_repr};
pub fn build_memcpy<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout: LayoutRepr<'a>,
destination: PointerValue<'ctx>,
source: PointerValue<'ctx>,
) {
let align_bytes = layout.llvm_alignment_bytes(layout_interner);
let width = basic_type_from_layout_repr(env, layout_interner, layout)
.size_of()
.unwrap();
if align_bytes > 0 {
// There is actually something to memcpy.
env.builder
.build_memcpy(destination, align_bytes, source, align_bytes, width)
.unwrap();
}
}

View file

@ -10,5 +10,7 @@ mod intrinsics;
mod lowlevel;
pub mod refcounting;
mod align;
mod memcpy;
mod scope;
mod struct_;