mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00

Tailors a target class for our needs. Replaces tons of uses across the entire compiler. This is a base for later adding new targets like thumb.
546 lines
17 KiB
Rust
546 lines
17 KiB
Rust
use crate::llvm::build::{BuilderExt, Env, FunctionSpec, RocReturn};
|
|
use crate::llvm::erased;
|
|
use crate::llvm::memcpy::build_memcpy;
|
|
use bumpalo::collections::{CollectIn, Vec as AVec};
|
|
use inkwell::context::Context;
|
|
use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
|
|
use inkwell::values::PointerValue;
|
|
use inkwell::AddressSpace;
|
|
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
|
use roc_mono::layout::{
|
|
round_up_to_alignment, Builtin, FunctionPointer, InLayout, Layout, LayoutInterner, LayoutRepr,
|
|
STLayoutInterner, UnionLayout,
|
|
};
|
|
use roc_target::Target;
|
|
|
|
use super::struct_::RocStruct;
|
|
|
|
pub fn basic_type_from_layout<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout: LayoutRepr<'_>,
|
|
) -> BasicTypeEnum<'ctx> {
|
|
use LayoutRepr::*;
|
|
|
|
match layout {
|
|
Struct(sorted_fields, ..) => {
|
|
basic_type_from_record(env, layout_interner, sorted_fields).into()
|
|
}
|
|
LambdaSet(lambda_set) => basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(lambda_set.runtime_representation()),
|
|
),
|
|
|
|
Ptr(inner_layout) => {
|
|
let inner_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(inner_layout),
|
|
);
|
|
|
|
inner_type.ptr_type(AddressSpace::default()).into()
|
|
}
|
|
Union(union_layout) => basic_type_from_union_layout(env, layout_interner, &union_layout),
|
|
|
|
RecursivePointer(_) => env
|
|
.context
|
|
.i64_type()
|
|
.ptr_type(AddressSpace::default())
|
|
.as_basic_type_enum(),
|
|
|
|
FunctionPointer(self::FunctionPointer { args, ret }) => {
|
|
let args = args.iter().map(|arg| {
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*arg))
|
|
});
|
|
|
|
let ret_repr = layout_interner.get_repr(ret);
|
|
let ret = basic_type_from_layout(env, layout_interner, ret_repr);
|
|
|
|
let roc_return = RocReturn::from_layout(layout_interner, ret_repr);
|
|
|
|
let fn_spec = FunctionSpec::fastcc(env, roc_return, ret, args.collect_in(env.arena));
|
|
|
|
fn_spec.typ.ptr_type(AddressSpace::default()).into()
|
|
}
|
|
Erased(_) => erased::basic_type(env).into(),
|
|
|
|
Builtin(builtin) => basic_type_from_builtin(env, &builtin),
|
|
}
|
|
}
|
|
|
|
fn basic_type_from_record<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
fields: &[InLayout<'_>],
|
|
) -> StructType<'ctx> {
|
|
let mut field_types = AVec::with_capacity_in(fields.len(), env.arena);
|
|
|
|
for field_layout in fields.iter() {
|
|
let typ = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(*field_layout),
|
|
);
|
|
|
|
field_types.push(typ);
|
|
}
|
|
|
|
env.context
|
|
.struct_type(field_types.into_bump_slice(), false)
|
|
}
|
|
|
|
pub fn struct_type_from_union_layout<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
union_layout: &UnionLayout<'_>,
|
|
) -> StructType<'ctx> {
|
|
use UnionLayout::*;
|
|
|
|
match union_layout {
|
|
NonRecursive([]) => env.context.struct_type(&[], false),
|
|
NonRecursive(tags) => {
|
|
RocUnion::tagged_from_slices(layout_interner, env.context, tags).struct_type()
|
|
}
|
|
Recursive(tags)
|
|
| NullableWrapped {
|
|
other_tags: tags, ..
|
|
} => {
|
|
if union_layout.stores_tag_id_as_data(env.target) {
|
|
RocUnion::tagged_from_slices(layout_interner, env.context, tags).struct_type()
|
|
} else {
|
|
RocUnion::untagged_from_slices(layout_interner, env.context, tags).struct_type()
|
|
}
|
|
}
|
|
NullableUnwrapped { other_fields, .. } => {
|
|
RocUnion::untagged_from_slices(layout_interner, env.context, &[other_fields])
|
|
.struct_type()
|
|
}
|
|
NonNullableUnwrapped(fields) => {
|
|
RocUnion::untagged_from_slices(layout_interner, env.context, &[fields]).struct_type()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn basic_type_from_union_layout<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
union_layout: &UnionLayout<'_>,
|
|
) -> BasicTypeEnum<'ctx> {
|
|
use UnionLayout::*;
|
|
|
|
let struct_type = struct_type_from_union_layout(env, layout_interner, union_layout);
|
|
|
|
match union_layout {
|
|
NonRecursive(_) => struct_type.into(),
|
|
Recursive(_)
|
|
| NonNullableUnwrapped(_)
|
|
| NullableWrapped { .. }
|
|
| NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::default()).into(),
|
|
}
|
|
}
|
|
|
|
pub fn basic_type_from_builtin<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
builtin: &Builtin<'_>,
|
|
) -> BasicTypeEnum<'ctx> {
|
|
use Builtin::*;
|
|
|
|
let context = env.context;
|
|
|
|
match builtin {
|
|
Int(int_width) => int_type_from_int_width(env, *int_width).as_basic_type_enum(),
|
|
Float(float_width) => float_type_from_float_width(env, *float_width).as_basic_type_enum(),
|
|
Bool => context.bool_type().as_basic_type_enum(),
|
|
Decimal => context.i128_type().as_basic_type_enum(),
|
|
List(_) => zig_list_type(env).into(),
|
|
Str => zig_str_type(env).into(),
|
|
}
|
|
}
|
|
|
|
/// Turn a layout into a BasicType that we use in LLVM function arguments.
|
|
///
|
|
/// This makes it possible to pass values as something different from how they are typically stored.
|
|
/// Current differences
|
|
///
|
|
/// - tag unions are passed by-reference. That means that
|
|
/// * `f : [Some I64, None] -> I64` is typed `{ { i64, i8 }, i64 }* -> i64`
|
|
/// * `f : { x : [Some I64, None] } -> I64 is typed `{ { { i64, i8 }, i64 } } -> i64`
|
|
///
|
|
/// Ideas exist to have (bigger than 2 register) records also be passed by-reference, but this
|
|
/// is not currently implemented
|
|
pub fn argument_type_from_layout<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout: LayoutRepr<'a>,
|
|
) -> BasicTypeEnum<'ctx> {
|
|
use LayoutRepr::*;
|
|
|
|
// TODO: can this just be "basic_type_from_layout => ptr if passed_by_ref"?
|
|
match layout {
|
|
LambdaSet(lambda_set) => argument_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(lambda_set.runtime_representation()),
|
|
),
|
|
Union(union_layout) => argument_type_from_union_layout(env, layout_interner, &union_layout),
|
|
Builtin(_) => {
|
|
let base = basic_type_from_layout(env, layout_interner, layout);
|
|
|
|
if layout.is_passed_by_reference(layout_interner) {
|
|
base.ptr_type(AddressSpace::default()).into()
|
|
} else {
|
|
base
|
|
}
|
|
}
|
|
Struct(_) => argument_type_from_struct_layout(env, layout_interner, layout),
|
|
_ => basic_type_from_layout(env, layout_interner, layout),
|
|
}
|
|
}
|
|
|
|
/// Some records are passed by-reference.
|
|
fn argument_type_from_struct_layout<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
struct_layout: LayoutRepr<'a>,
|
|
) -> BasicTypeEnum<'ctx> {
|
|
debug_assert!(matches!(struct_layout, LayoutRepr::Struct(_)));
|
|
let stack_type = basic_type_from_layout(env, layout_interner, struct_layout);
|
|
|
|
if struct_layout.is_passed_by_reference(layout_interner) {
|
|
stack_type.ptr_type(AddressSpace::default()).into()
|
|
} else {
|
|
stack_type
|
|
}
|
|
}
|
|
|
|
/// Non-recursive tag unions are stored on the stack, but passed by-reference
|
|
pub fn argument_type_from_union_layout<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
union_layout: &UnionLayout<'_>,
|
|
) -> BasicTypeEnum<'ctx> {
|
|
let heap_type = basic_type_from_union_layout(env, layout_interner, union_layout);
|
|
|
|
if let UnionLayout::NonRecursive(_) = union_layout {
|
|
heap_type.ptr_type(AddressSpace::default()).into()
|
|
} else {
|
|
heap_type
|
|
}
|
|
}
|
|
|
|
pub fn int_type_from_int_width<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
int_width: IntWidth,
|
|
) -> IntType<'ctx> {
|
|
use IntWidth::*;
|
|
|
|
match int_width {
|
|
U128 | I128 => env.context.i128_type(),
|
|
U64 | I64 => env.context.i64_type(),
|
|
U32 | I32 => env.context.i32_type(),
|
|
U16 | I16 => env.context.i16_type(),
|
|
U8 | I8 => env.context.i8_type(),
|
|
}
|
|
}
|
|
|
|
pub fn float_type_from_float_width<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
float_width: FloatWidth,
|
|
) -> FloatType<'ctx> {
|
|
use FloatWidth::*;
|
|
|
|
match float_width {
|
|
F64 => env.context.f64_type(),
|
|
F32 => env.context.f32_type(),
|
|
}
|
|
}
|
|
|
|
fn alignment_type(context: &Context, alignment: u32) -> BasicTypeEnum {
|
|
match alignment {
|
|
0 => context.struct_type(&[], false).into(),
|
|
1 => context.i8_type().into(),
|
|
2 => context.i16_type().into(),
|
|
4 => context.i32_type().into(),
|
|
8 => context.i64_type().into(),
|
|
16 => context.i128_type().into(),
|
|
_ => unimplemented!("weird alignment: {alignment}"),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
enum TagType {
|
|
I8,
|
|
I16,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub(crate) struct RocUnion<'ctx> {
|
|
struct_type: StructType<'ctx>,
|
|
data_align: u32,
|
|
data_width: u32,
|
|
tag_type: Option<TagType>,
|
|
}
|
|
|
|
fn is_multiple_of(big: u32, small: u32) -> bool {
|
|
match small {
|
|
0 => true, // 0 is a multiple of all n, because n * 0 = 0
|
|
n => big % n == 0,
|
|
}
|
|
}
|
|
|
|
impl<'ctx> RocUnion<'ctx> {
|
|
pub const TAG_ID_INDEX: u32 = 2;
|
|
pub const TAG_DATA_INDEX: u32 = 1;
|
|
|
|
fn new(
|
|
context: &'ctx Context,
|
|
data_align: u32,
|
|
data_width: u32,
|
|
tag_type: Option<TagType>,
|
|
) -> Self {
|
|
let bytes = round_up_to_alignment(data_width, data_align);
|
|
|
|
let byte_array_type = if is_multiple_of(bytes, 8) && is_multiple_of(data_align, 8) {
|
|
context
|
|
.i64_type()
|
|
.array_type(bytes / 8)
|
|
.as_basic_type_enum()
|
|
} else {
|
|
context.i8_type().array_type(bytes).as_basic_type_enum()
|
|
};
|
|
|
|
let alignment_array_type = alignment_type(context, data_align)
|
|
.array_type(0)
|
|
.as_basic_type_enum();
|
|
|
|
let struct_type = if let Some(tag_type) = tag_type {
|
|
let tag_width = match tag_type {
|
|
TagType::I8 => 1,
|
|
TagType::I16 => 2,
|
|
};
|
|
|
|
let tag_padding = round_up_to_alignment(tag_width, data_align) - tag_width;
|
|
let tag_padding_type = context
|
|
.i8_type()
|
|
.array_type(tag_padding)
|
|
.as_basic_type_enum();
|
|
|
|
context.struct_type(
|
|
&[
|
|
alignment_array_type,
|
|
byte_array_type,
|
|
match tag_type {
|
|
TagType::I8 => context.i8_type().into(),
|
|
TagType::I16 => context.i16_type().into(),
|
|
},
|
|
tag_padding_type,
|
|
],
|
|
false,
|
|
)
|
|
} else {
|
|
context.struct_type(&[alignment_array_type, byte_array_type], false)
|
|
};
|
|
|
|
Self {
|
|
struct_type,
|
|
data_align,
|
|
data_width,
|
|
tag_type,
|
|
}
|
|
}
|
|
|
|
pub fn struct_type(&self) -> StructType<'ctx> {
|
|
self.struct_type
|
|
}
|
|
|
|
pub fn tagged_from_slices(
|
|
interner: &STLayoutInterner,
|
|
context: &'ctx Context,
|
|
layouts: &[&[InLayout<'_>]],
|
|
) -> Self {
|
|
let tag_type = match layouts.len() {
|
|
0 => unreachable!("zero-element tag union is not represented as a RocUnion"),
|
|
1..=255 => TagType::I8,
|
|
_ => TagType::I16,
|
|
};
|
|
|
|
let (data_width, data_align) = Layout::stack_size_and_alignment_slices(interner, layouts);
|
|
|
|
Self::new(context, data_align, data_width, Some(tag_type))
|
|
}
|
|
|
|
pub fn untagged_from_slices(
|
|
interner: &STLayoutInterner,
|
|
context: &'ctx Context,
|
|
layouts: &[&[InLayout<'_>]],
|
|
) -> Self {
|
|
let (data_width, data_align) = Layout::stack_size_and_alignment_slices(interner, layouts);
|
|
|
|
Self::new(context, data_align, data_width, None)
|
|
}
|
|
|
|
pub fn data_width(&self) -> u32 {
|
|
self.data_width
|
|
}
|
|
|
|
pub fn tag_alignment(&self) -> u32 {
|
|
let tag_id_alignment = match self.tag_type {
|
|
None => 0,
|
|
Some(TagType::I8) => 1,
|
|
Some(TagType::I16) => 2,
|
|
};
|
|
|
|
self.data_align.max(tag_id_alignment)
|
|
}
|
|
|
|
pub fn tag_width(&self) -> u32 {
|
|
let tag_id_width = match self.tag_type {
|
|
None => 0,
|
|
Some(TagType::I8) => 1,
|
|
Some(TagType::I16) => 2,
|
|
};
|
|
|
|
let mut width = self.data_width;
|
|
|
|
// add padding between data and the tag id
|
|
width = round_up_to_alignment(width, tag_id_width);
|
|
|
|
// add tag id
|
|
width += tag_id_width;
|
|
|
|
// add padding after the tag id
|
|
width = round_up_to_alignment(width, self.tag_alignment());
|
|
|
|
width
|
|
}
|
|
|
|
pub fn write_struct_data<'a, 'env>(
|
|
&self,
|
|
env: &Env<'a, 'ctx, 'env>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
// The allocation of the tag to write into.
|
|
tag_alloca: PointerValue<'ctx>,
|
|
// The data to write into the union.
|
|
data: RocStruct<'ctx>,
|
|
data_layout: LayoutRepr<'a>,
|
|
tag_id: Option<usize>,
|
|
) {
|
|
debug_assert_eq!(tag_id.is_some(), self.tag_type.is_some());
|
|
|
|
let data_buffer = env.builder.new_build_struct_gep(
|
|
self.struct_type(),
|
|
tag_alloca,
|
|
Self::TAG_DATA_INDEX,
|
|
"data_buffer",
|
|
);
|
|
|
|
match data {
|
|
// NOTE: the data may be smaller than the buffer, so there might be uninitialized
|
|
// bytes in the buffer. We should never touch those, but e.g. valgrind might not
|
|
// realize that. If that comes up, the solution is to just fill it with zeros
|
|
RocStruct::ByValue(value) => {
|
|
let cast_pointer = env.builder.new_build_pointer_cast(
|
|
data_buffer,
|
|
value.get_type().ptr_type(AddressSpace::default()),
|
|
"to_data_ptr",
|
|
);
|
|
env.builder.new_build_store(cast_pointer, value);
|
|
}
|
|
RocStruct::ByReference(payload_data_ptr) => {
|
|
let cast_tag_pointer = env.builder.new_build_pointer_cast(
|
|
data_buffer,
|
|
payload_data_ptr.get_type(),
|
|
"to_data_ptr",
|
|
);
|
|
|
|
build_memcpy(
|
|
env,
|
|
layout_interner,
|
|
data_layout,
|
|
cast_tag_pointer,
|
|
payload_data_ptr,
|
|
);
|
|
}
|
|
}
|
|
|
|
// set the tag id
|
|
//
|
|
// NOTE: setting the tag id initially happened before writing the data into it.
|
|
// That turned out to expose UB. More info at https://github.com/roc-lang/roc/issues/3554
|
|
if let Some(tag_id) = tag_id {
|
|
let tag_id_type = match self.tag_type.unwrap() {
|
|
TagType::I8 => env.context.i8_type(),
|
|
TagType::I16 => env.context.i16_type(),
|
|
};
|
|
|
|
let tag_id_ptr = env.builder.new_build_struct_gep(
|
|
self.struct_type(),
|
|
tag_alloca,
|
|
Self::TAG_ID_INDEX,
|
|
"tag_id_ptr",
|
|
);
|
|
|
|
let tag_id = tag_id_type.const_int(tag_id as u64, false);
|
|
|
|
env.builder.new_build_store(tag_id_ptr, tag_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The int type that the C ABI turns our RocList/RocStr into
|
|
pub fn str_list_int(ctx: &Context, target: Target) -> IntType<'_> {
|
|
match target.ptr_width() {
|
|
roc_target::PtrWidth::Bytes4 => ctx.i64_type(),
|
|
roc_target::PtrWidth::Bytes8 => ctx.i128_type(),
|
|
}
|
|
}
|
|
|
|
pub fn zig_list_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
|
|
env.module.get_struct_type("list.RocList").unwrap()
|
|
}
|
|
|
|
pub fn zig_str_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
|
|
env.module.get_struct_type("str.RocStr").unwrap()
|
|
}
|
|
|
|
pub fn zig_dec_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
|
|
env.module.get_struct_type("dec.RocDec").unwrap()
|
|
}
|
|
|
|
pub fn zig_has_tag_id_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
|
|
let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::default());
|
|
|
|
env.context
|
|
.struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false)
|
|
}
|
|
|
|
pub fn zig_num_parse_result_type<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
type_name: &str,
|
|
) -> StructType<'ctx> {
|
|
let name = format!("num.NumParseResult({type_name})");
|
|
|
|
match env.module.get_struct_type(&name) {
|
|
Some(zig_type) => zig_type,
|
|
None => panic!("zig does not define the `{name}` type!"),
|
|
}
|
|
}
|
|
|
|
pub fn zig_to_int_checked_result_type<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
type_name: &str,
|
|
) -> StructType<'ctx> {
|
|
let name = format!("num.ToIntCheckedResult({type_name})");
|
|
|
|
match env.module.get_struct_type(&name) {
|
|
Some(zig_type) => zig_type,
|
|
None => panic!("zig does not define the `{name}` type!"),
|
|
}
|
|
}
|
|
|
|
pub fn zig_with_overflow_roc_dec<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
|
|
env.module
|
|
.get_struct_type("utils.WithOverflow(dec.RocDec)")
|
|
.unwrap()
|
|
}
|