Merge branch 'trunk' of github.com:rtfeldman/roc into dict

This commit is contained in:
Chadtech 2021-01-16 14:55:09 -05:00
commit c7f6de2afe
93 changed files with 6706 additions and 5317 deletions

View file

@ -19,6 +19,7 @@ use crate::llvm::refcounting::{
};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use either::Either;
use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder;
use inkwell::context::Context;
@ -41,7 +42,7 @@ use roc_collections::all::{ImMap, MutSet};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{JoinPointId, Wrapped};
use roc_mono::ir::{CallType, JoinPointId, Wrapped};
use roc_mono::layout::{Builtin, ClosureLayout, Layout, LayoutIds, MemoryMode};
use target_lexicon::CallingConvention;
@ -444,6 +445,14 @@ pub fn construct_optimization_passes<'a>(
fpm.add_memcpy_optimize_pass(); // this one is very important
fpm.add_licm_pass();
// turn invoke into call
mpm.add_prune_eh_pass();
// remove unused global values (often the `_wrapper` can be removed)
mpm.add_global_dce_pass();
mpm.add_function_inlining_pass();
}
}
@ -490,15 +499,52 @@ fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace {
}
}
pub fn int_with_precision<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: i128,
precision: &Builtin,
) -> IntValue<'ctx> {
match precision {
Builtin::Usize => ptr_int(env.context, env.ptr_bytes).const_int(value as u64, false),
Builtin::Int128 => const_i128(env, value),
Builtin::Int64 => env.context.i64_type().const_int(value as u64, false),
Builtin::Int32 => env.context.i32_type().const_int(value as u64, false),
Builtin::Int16 => env.context.i16_type().const_int(value as u64, false),
Builtin::Int8 => env.context.i8_type().const_int(value as u64, false),
_ => panic!("Invalid layout for int literal = {:?}", precision),
}
}
pub fn float_with_precision<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: f64,
precision: &Builtin,
) -> FloatValue<'ctx> {
match precision {
Builtin::Float64 => env.context.f64_type().const_float(value),
Builtin::Float32 => env.context.f32_type().const_float(value),
_ => panic!("Invalid layout for float literal = {:?}", precision),
}
}
pub fn build_exp_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'_>,
literal: &roc_mono::ir::Literal<'a>,
) -> BasicValueEnum<'ctx> {
use roc_mono::ir::Literal::*;
match literal {
Int(num) => env.context.i64_type().const_int(*num as u64, true).into(),
Float(num) => env.context.f64_type().const_float(*num).into(),
Int(int) => match layout {
Layout::Builtin(builtin) => int_with_precision(env, *int as i128, builtin).into(),
_ => panic!("Invalid layout for int literal = {:?}", layout),
},
Float(float) => match layout {
Layout::Builtin(builtin) => float_with_precision(env, *float, builtin).into(),
_ => panic!("Invalid layout for float literal = {:?}", layout),
},
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => {
@ -625,101 +671,27 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
}
}
pub fn build_exp_expr<'a, 'ctx, 'env>(
pub fn build_exp_call<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
layout: &Layout<'a>,
expr: &roc_mono::ir::Expr<'a>,
call: &roc_mono::ir::Call<'a>,
) -> BasicValueEnum<'ctx> {
use roc_mono::ir::CallType::*;
use roc_mono::ir::Expr::*;
let roc_mono::ir::Call {
call_type,
arguments,
} = call;
match expr {
Literal(literal) => build_exp_literal(env, literal),
RunLowLevel(op, symbols) => {
run_low_level(env, layout_ids, scope, parent, layout, *op, symbols)
}
ForeignCall {
foreign_symbol,
arguments,
ret_layout,
match call_type {
CallType::ByName {
name, full_layout, ..
} => {
let mut arg_vals: Vec<BasicValueEnum> =
let mut arg_tuples: Vec<BasicValueEnum> =
Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
// crude approximation of the C calling convention
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes;
if pass_result_by_pointer {
// the return value is too big to pass through a register, so the caller must
// allocate space for it on its stack, and provide a pointer to write the result into
let ret_type =
basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let ret_ptr_type = get_ptr_type(&ret_type, AddressSpace::Generic);
let ret_ptr = env.builder.build_alloca(ret_type, "return_value");
arg_vals.push(ret_ptr.into());
arg_types.push(ret_ptr_type.into());
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type =
basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let function_type = env.context.void_type().fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign_symbol.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value();
env.builder.build_load(ret_ptr, "read_result")
} else {
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type =
basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let ret_type =
basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let function_type = get_fn_type(&ret_type, &arg_types);
let function = get_foreign_symbol(env, foreign_symbol.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
}
FunctionCall {
call_type: ByName(name),
full_layout,
args,
..
} => {
let mut arg_tuples: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for symbol in args.iter() {
for symbol in arguments.iter() {
arg_tuples.push(load_symbol(env, scope, symbol));
}
@ -733,16 +705,13 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
)
}
FunctionCall {
call_type: ByPointer(name),
args,
..
} => {
CallType::ByPointer { name, .. } => {
let sub_expr = load_symbol(env, scope, name);
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
let mut arg_vals: Vec<BasicValueEnum> =
Vec::with_capacity_in(arguments.len(), env.arena);
for arg in args.iter() {
for arg in arguments.iter() {
arg_vals.push(load_symbol(env, scope, arg));
}
@ -771,6 +740,33 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
CallType::LowLevel { op } => {
run_low_level(env, layout_ids, scope, parent, layout, *op, arguments)
}
CallType::Foreign {
foreign_symbol: foreign,
ret_layout,
} => build_foreign_symbol(env, scope, foreign, arguments, ret_layout),
}
}
pub fn build_exp_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
layout: &Layout<'a>,
expr: &roc_mono::ir::Expr<'a>,
) -> BasicValueEnum<'ctx> {
use inkwell::types::BasicType;
use roc_mono::ir::Expr::*;
match expr {
Literal(literal) => build_exp_literal(env, layout, literal),
Call(call) => build_exp_call(env, layout_ids, scope, parent, layout, call),
Struct(sorted_fields) => {
let ctx = env.context;
let builder = env.builder;
@ -916,15 +912,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
field_types.push(field_type);
if let Layout::RecursivePointer = tag_field_layout {
let ptr = allocate_with_refcount(env, &tag_layout, val);
let ptr = cast_basic_basic(
builder,
ptr.into(),
ctx.i64_type().ptr_type(AddressSpace::Generic).into(),
panic!(
r"non-recursive tag unions cannot directly contain a 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);
@ -983,7 +973,88 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
internal_type,
)
}
Tag { .. } => unreachable!("tags should have a union layout"),
Tag {
arguments,
tag_layout: Layout::RecursiveUnion(fields),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(fields);
debug_assert!(*union_size > 1);
let ptr_size = env.ptr_bytes;
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 = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
&fields[*tag_id as usize]
};
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
let (val, val_layout) = load_symbol_and_layout(env, 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.arena, env.context, tag_field_layout, ptr_size);
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 = cast_basic_basic(
builder,
val,
ctx.i64_type().ptr_type(AddressSpace::Generic).into(),
);
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 data_ptr = reserve_with_refcount(env, &tag_layout);
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_ptr = cast_basic_basic(
builder,
data_ptr.into(),
struct_type.ptr_type(AddressSpace::Generic).into(),
)
.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, "struct_gep")
.unwrap();
builder.build_store(field_ptr, field_val);
}
data_ptr.into()
}
Tag { .. } => unreachable!("tags should have a Union or RecursiveUnion layout"),
Reset(_) => todo!(),
Reuse { .. } => todo!(),
@ -1048,6 +1119,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
field_layouts,
..
} => {
use BasicValueEnum::*;
let builder = env.builder;
// Determine types, assumes the descriminant is in the field layouts
@ -1067,28 +1140,61 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
.struct_type(field_types.into_bump_slice(), false);
// cast the argument bytes into the desired shape for this tag
let argument = load_symbol(env, scope, structure).into_struct_value();
let argument = load_symbol(env, scope, structure);
let struct_value = cast_struct_struct(builder, argument, struct_type);
let struct_layout = Layout::Struct(field_layouts);
match argument {
StructValue(value) => {
let struct_value = cast_struct_struct(builder, value, struct_type);
let result = builder
.build_extract_value(struct_value, *index as u32, "")
.expect("desired field did not decode");
let result = builder
.build_extract_value(struct_value, *index as u32, "")
.expect("desired field did not decode");
if let Some(Layout::RecursivePointer) = field_layouts.get(*index as usize) {
let struct_layout = Layout::Struct(field_layouts);
let desired_type = block_of_memory(env.context, &struct_layout, env.ptr_bytes);
if let Some(Layout::RecursivePointer) = field_layouts.get(*index as usize) {
let desired_type =
block_of_memory(env.context, &struct_layout, env.ptr_bytes);
// the value is a pointer to the actual value; load that value!
use inkwell::types::BasicType;
let ptr = cast_basic_basic(
builder,
result,
desired_type.ptr_type(AddressSpace::Generic).into(),
);
builder.build_load(ptr.into_pointer_value(), "load_recursive_field")
} else {
result
// the value is a pointer to the actual value; load that value!
let ptr = cast_basic_basic(
builder,
result,
desired_type.ptr_type(AddressSpace::Generic).into(),
);
builder.build_load(ptr.into_pointer_value(), "load_recursive_field")
} else {
result
}
}
PointerValue(value) => {
let ptr = cast_basic_basic(
builder,
value.into(),
struct_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
let elem_ptr = builder
.build_struct_gep(ptr, *index as u32, "at_index_struct_gep")
.unwrap();
let result = builder.build_load(elem_ptr, "load_at_index_ptr");
if let Some(Layout::RecursivePointer) = field_layouts.get(*index as usize) {
// a recursive field is stored as a `i64*`, to use it we must cast it to
// a pointer to the block of memory representation
cast_basic_basic(
builder,
result,
block_of_memory(env.context, &struct_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic)
.into(),
)
} else {
result
}
}
_ => panic!("cannot look up index in {:?}", argument),
}
}
EmptyArray => empty_polymorphic_list(env),
@ -1134,12 +1240,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
}
pub fn allocate_with_refcount<'a, 'ctx, 'env>(
pub fn reserve_with_refcount<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
value: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> {
let builder = env.builder;
let ctx = env.context;
let len_type = env.ptr_int();
@ -1149,10 +1253,18 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>(
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes);
let data_ptr = allocate_with_refcount_help(env, layout, value_bytes_intvalue, rc1);
allocate_with_refcount_help(env, layout, value_bytes_intvalue, rc1)
}
pub fn allocate_with_refcount<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
value: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> {
let data_ptr = reserve_with_refcount(env, layout);
// store the value in the pointer
builder.build_store(data_ptr, value);
env.builder.build_store(data_ptr, value);
data_ptr
}
@ -1299,6 +1411,92 @@ fn list_literal<'a, 'ctx, 'env>(
)
}
#[allow(clippy::too_many_arguments)]
fn invoke_roc_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
symbol: Symbol,
layout: Layout<'a>,
function_value: Either<FunctionValue<'ctx>, PointerValue<'ctx>>,
arguments: &[Symbol],
pass: &'a roc_mono::ir::Stmt<'a>,
fail: &'a roc_mono::ir::Stmt<'a>,
) -> BasicValueEnum<'ctx> {
let context = env.context;
let call_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes);
let alloca = create_entry_block_alloca(env, parent, call_bt, symbol.ident_string(&env.interns));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
for arg in arguments.iter() {
arg_vals.push(load_symbol(env, scope, arg));
}
let pass_block = context.append_basic_block(parent, "invoke_pass");
let fail_block = context.append_basic_block(parent, "invoke_fail");
let call_result = {
let call = env.builder.build_invoke(
function_value,
arg_vals.as_slice(),
pass_block,
fail_block,
"tmp",
);
match function_value {
Either::Left(function) => {
call.set_call_convention(function.get_call_conventions());
}
Either::Right(_) => {
call.set_call_convention(FAST_CALL_CONV);
}
}
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
};
{
env.builder.position_at_end(pass_block);
env.builder.build_store(alloca, call_result);
scope.insert(symbol, (layout, alloca));
build_exp_stmt(env, layout_ids, scope, parent, pass);
scope.remove(&symbol);
}
{
env.builder.position_at_end(fail_block);
let landing_pad_type = {
let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into();
let selector_value = context.i32_type().into();
context.struct_type(&[exception_ptr, selector_value], false)
};
env.builder
.build_catch_all_landing_pad(
&landing_pad_type,
&BasicValueEnum::IntValue(context.i8_type().const_zero()),
context.i8_type().ptr_type(AddressSpace::Generic),
"invoke_landing_pad",
)
.into_struct_value();
build_exp_stmt(env, layout_ids, scope, parent, fail);
}
call_result
}
pub fn build_exp_stmt<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1324,6 +1522,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
let mut stack = Vec::with_capacity_in(queue.len(), env.arena);
for (symbol, expr, layout) in queue {
debug_assert!(layout != &Layout::RecursivePointer);
let context = &env.context;
let val = build_exp_expr(env, layout_ids, &scope, parent, layout, &expr);
@ -1381,6 +1580,92 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
value
}
Invoke {
symbol,
call,
layout,
pass,
fail: roc_mono::ir::Stmt::Rethrow,
} => {
// when the fail case is just Rethrow, there is no cleanup work to do
// so we can just treat this invoke as a normal call
let stmt =
roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
build_exp_stmt(env, layout_ids, scope, parent, &stmt)
}
Invoke {
symbol,
call,
layout,
pass,
fail,
} => match call.call_type {
CallType::ByName {
name,
ref full_layout,
..
} => {
let function_value = function_value_by_name(env, layout_ids, full_layout, name);
invoke_roc_function(
env,
layout_ids,
scope,
parent,
*symbol,
layout.clone(),
function_value.into(),
call.arguments,
pass,
fail,
)
}
CallType::ByPointer { name, .. } => {
let sub_expr = load_symbol(env, scope, &name);
let function_ptr = match sub_expr {
BasicValueEnum::PointerValue(ptr) => ptr,
non_ptr => {
panic!(
"Tried to call by pointer, but encountered a non-pointer: {:?}",
non_ptr
);
}
};
invoke_roc_function(
env,
layout_ids,
scope,
parent,
*symbol,
layout.clone(),
function_ptr.into(),
call.arguments,
pass,
fail,
)
}
CallType::Foreign {
ref foreign_symbol,
ref ret_layout,
} => build_foreign_symbol(env, scope, foreign_symbol, call.arguments, ret_layout),
CallType::LowLevel { .. } => {
unreachable!("lowlevel itself never throws exceptions")
}
},
Rethrow => {
cxa_rethrow_exception(env);
// used in exception handling
env.builder.build_unreachable();
env.context.i64_type().const_zero().into()
}
Switch {
branches,
default_branch,
@ -1594,6 +1879,22 @@ struct SwitchArgsIr<'a, 'ctx> {
pub ret_type: BasicTypeEnum<'ctx>,
}
fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValue<'ctx> {
// TODO verify the order [a, b] is correct for larger numbers when we can parse them
debug_assert!(value <= i64::MAX as i128);
// truncate the lower 64 bits
let value = value as u128;
let a = value as u64;
// get the upper 64 bits
let b = (value >> 64) as u64;
env.context
.i128_type()
.const_int_arbitrary_precision(&[a, b])
}
fn build_switch_ir<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1631,6 +1932,15 @@ fn build_switch_ir<'a, 'ctx, 'env>(
.build_bitcast(full_cond, env.context.i64_type(), "")
.into_int_value()
}
Layout::Builtin(Builtin::Float32) => {
// float matches are done on the bit pattern
cond_layout = Layout::Builtin(Builtin::Int32);
let full_cond = load_symbol(env, scope, cond_symbol);
builder
.build_bitcast(full_cond, env.context.i32_type(), "")
.into_int_value()
}
Layout::Union(_) => {
// we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64);
@ -1658,8 +1968,11 @@ fn build_switch_ir<'a, 'ctx, 'env>(
//
// they either need to all be i8, or i64
let int_val = match cond_layout {
Layout::Builtin(Builtin::Int128) => context.i128_type().const_int(*int as u64, false), /* TODO file an issue: you can't currently have an int literal bigger than 64 bits long, and also (as we see here), you can't currently have (at least in Inkwell) a when-branch with an i128 literal in its pattren */
Layout::Builtin(Builtin::Usize) => {
ptr_int(env.context, env.ptr_bytes).const_int(*int as u64, false)
}
Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Int128) => const_i128(env, *int as i128),
Layout::Builtin(Builtin::Int32) => context.i32_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Int16) => context.i16_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Int8) => context.i8_type().const_int(*int as u64, false),
@ -2027,7 +2340,11 @@ fn make_exception_catcher<'a, 'ctx, 'env>(
) -> FunctionValue<'ctx> {
let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap());
make_exception_catching_wrapper(env, roc_function, &wrapper_function_name)
let function_value = make_exception_catching_wrapper(env, roc_function, &wrapper_function_name);
function_value.set_linkage(Linkage::Internal);
function_value
}
fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
@ -2539,6 +2856,29 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
}
}
fn function_value_by_name<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
symbol: Symbol,
) -> FunctionValue<'ctx> {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str();
env.module.get_function(fn_name).unwrap_or_else(|| {
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
fn_name, symbol, layout
)
}
})
}
// #[allow(clippy::cognitive_complexity)]
#[inline(always)]
fn call_with_args<'a, 'ctx, 'env>(
@ -2549,21 +2889,7 @@ fn call_with_args<'a, 'ctx, 'env>(
_parent: FunctionValue<'ctx>,
args: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str();
let fn_val = env.module.get_function(fn_name).unwrap_or_else(|| {
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
fn_name, symbol, layout
)
}
});
let fn_val = function_value_by_name(env, layout_ids, layout, symbol);
let call = env.builder.build_call(fn_val, args, "call");
@ -2746,7 +3072,16 @@ fn run_low_level<'a, 'ctx, 'env>(
let inplace = get_inplace_from_layout(layout);
list_keep_if(env, inplace, parent, func, func_layout, list, list_layout)
list_keep_if(
env,
layout_ids,
inplace,
parent,
func,
func_layout,
list,
list_layout,
)
}
ListContains => {
// List.contains : List elem, elem -> Bool
@ -2756,7 +3091,15 @@ fn run_low_level<'a, 'ctx, 'env>(
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_contains(env, parent, elem, elem_layout, list, list_layout)
list_contains(
env,
layout_ids,
parent,
elem,
elem_layout,
list,
list_layout,
)
}
ListWalk => {
debug_assert_eq!(args.len(), 3);
@ -2849,7 +3192,7 @@ fn run_low_level<'a, 'ctx, 'env>(
use roc_mono::layout::Builtin::*;
match arg_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => {
Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
build_int_unary_op(env, arg.into_int_value(), arg_builtin, op)
}
Float128 | Float64 | Float32 | Float16 => {
@ -2887,7 +3230,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let tag_lt = env.context.i8_type().const_int(2_u64, false);
match lhs_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => {
Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
let are_equal = env.builder.build_int_compare(
IntPredicate::EQ,
lhs_arg.into_int_value(),
@ -2959,7 +3302,7 @@ fn run_low_level<'a, 'ctx, 'env>(
build_num_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
}
NumBitwiseAnd => {
NumBitwiseAnd | NumBitwiseXor => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2981,7 +3324,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
let (rhs_arg, rhs_layout) = load_symbol_and_layout(env, scope, &args[1]);
build_eq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout)
build_eq(env, layout_ids, lhs_arg, rhs_arg, lhs_layout, rhs_layout)
}
NotEq => {
debug_assert_eq!(args.len(), 2);
@ -2989,7 +3332,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
let (rhs_arg, rhs_layout) = load_symbol_and_layout(env, scope, &args[1]);
build_neq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout)
build_neq(env, layout_ids, lhs_arg, rhs_arg, lhs_layout, rhs_layout)
}
And => {
// The (&&) operator
@ -3095,6 +3438,73 @@ fn run_low_level<'a, 'ctx, 'env>(
}
}
fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
ret_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
// crude approximation of the C calling convention
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes;
if pass_result_by_pointer {
// the return value is too big to pass through a register, so the caller must
// allocate space for it on its stack, and provide a pointer to write the result into
let ret_type = basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let ret_ptr_type = get_ptr_type(&ret_type, AddressSpace::Generic);
let ret_ptr = env.builder.build_alloca(ret_type, "return_value");
arg_vals.push(ret_ptr.into());
arg_types.push(ret_ptr_type.into());
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let function_type = env.context.void_type().fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value();
env.builder.build_load(ret_ptr, "read_result")
} else {
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let ret_type = basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let function_type = get_fn_type(&ret_type, &arg_types);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
}
fn maybe_inplace_list<'a, 'ctx, 'env, InPlace, CloneFirst, Empty>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
@ -3251,6 +3661,7 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT),
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}
@ -3316,7 +3727,7 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
use roc_mono::layout::Builtin::*;
match lhs_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop(
Usize | Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop(
env,
parent,
lhs_arg.into_int_value(),
@ -3567,6 +3978,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
int_abs_raise_on_overflow(env, arg, arg_layout)
}
NumToFloat => {
// TODO: Handle differnt sized numbers
// This is an Int, so we need to convert it.
bd.build_cast(
InstructionOpcode::SIToFP,
@ -3693,6 +4105,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
let bd = env.builder;
// TODO: Handle differnt sized floats
match op {
NumNeg => bd.build_float_neg(arg, "negate_float").into(),
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
@ -3871,8 +4284,7 @@ fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicVal
call.set_call_convention(C_CALL_CONV);
}
#[allow(dead_code)]
fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
fn cxa_rethrow_exception(env: &Env<'_, '_, '_>) {
let name = "__cxa_rethrow";
let module = env.module;
@ -3891,10 +4303,10 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
cxa_rethrow
}
};
let call = env.builder.build_call(function, &[], "never_used");
let call = env.builder.build_call(function, &[], "rethrow");
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value().left().unwrap()
// call.try_as_basic_value().left().unwrap()
}
fn get_foreign_symbol<'a, 'ctx, 'env>(

View file

@ -1003,6 +1003,7 @@ pub fn list_walk_backwards<'a, 'ctx, 'env>(
/// List.contains : List elem, elem -> Bool
pub fn list_contains<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
elem: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>,
@ -1034,6 +1035,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
list_contains_help(
env,
layout_ids,
parent,
length,
list_ptr,
@ -1043,8 +1045,10 @@ pub fn list_contains<'a, 'ctx, 'env>(
)
}
#[allow(clippy::too_many_arguments)]
pub fn list_contains_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
length: IntValue<'ctx>,
source_ptr: PointerValue<'ctx>,
@ -1082,7 +1086,14 @@ pub fn list_contains_help<'a, 'ctx, 'env>(
let current_elem = builder.build_load(current_elem_ptr, "load_elem");
let has_found = build_eq(env, current_elem, elem, list_elem_layout, elem_layout);
let has_found = build_eq(
env,
layout_ids,
current_elem,
elem,
list_elem_layout,
elem_layout,
);
builder.build_store(bool_alloca, has_found.into_int_value());
@ -1111,8 +1122,10 @@ pub fn list_contains_help<'a, 'ctx, 'env>(
}
/// List.keepIf : List elem, (elem -> Bool) -> List elem
#[allow(clippy::too_many_arguments)]
pub fn list_keep_if<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
output_inplace: InPlace,
parent: FunctionValue<'ctx>,
func: BasicValueEnum<'ctx>,
@ -1212,6 +1225,9 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
builder.position_at_end(cont_block);
// consume the input list
decrement_refcount_layout(env, parent, layout_ids, list, list_layout);
builder.build_load(result, "load_result")
}
}

View file

@ -77,6 +77,28 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>(
.into_int_value()
}
fn str_to_i128<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> {
let cell = env.builder.build_alloca(value.get_type(), "cell");
env.builder.build_store(cell, value);
let i128_ptr = env
.builder
.build_bitcast(
cell,
env.context.i128_type().ptr_type(AddressSpace::Generic),
"cast",
)
.into_pointer_value();
env.builder
.build_load(i128_ptr, "load_as_i128")
.into_int_value()
}
fn zig_str_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
zig_str: StructValue<'ctx>,
@ -222,3 +244,19 @@ pub fn str_from_int<'a, 'ctx, 'env>(
zig_str_to_struct(env, zig_result).into()
}
/// Str.equal : Str, Str -> Bool
pub fn str_equal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value1: BasicValueEnum<'ctx>,
value2: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let str1_i128 = str_to_i128(env, value1);
let str2_i128 = str_to_i128(env, value2);
call_bitcode_fn(
env,
&[str1_i128.into(), str2_i128.into()],
&bitcode::STR_EQUAL,
)
}

View file

@ -1,10 +1,16 @@
use crate::llvm::build::Env;
use inkwell::values::BasicValueEnum;
use inkwell::{FloatPredicate, IntPredicate};
use roc_mono::layout::{Builtin, Layout};
use crate::llvm::build::{set_name, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::{basic_type_from_layout, get_ptr_type};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, StructValue};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
pub fn build_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: &Layout<'a>,
@ -43,6 +49,26 @@ pub fn build_eq<'a, 'ctx, 'env>(
(Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::EQ, "eq_i1"),
(Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::OEQ, "eq_f64"),
(Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::OEQ, "eq_f32"),
(Builtin::Str, Builtin::Str) => str_equal(env, lhs_val, rhs_val),
(Builtin::EmptyList, Builtin::EmptyList) => {
env.context.bool_type().const_int(1, false).into()
}
(Builtin::List(_, _), Builtin::EmptyList)
| (Builtin::EmptyList, Builtin::List(_, _)) => {
unreachable!("the `==` operator makes sure its two arguments have the same type and thus layout")
}
(Builtin::List(_, elem1), Builtin::List(_, elem2)) => {
debug_assert_eq!(elem1, elem2);
build_list_eq(
env,
layout_ids,
lhs_layout,
elem1,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
)
}
(b1, b2) => {
todo!("Handle equals for builtin layouts {:?} == {:?}", b1, b2);
}
@ -57,6 +83,7 @@ pub fn build_eq<'a, 'ctx, 'env>(
pub fn build_neq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: &Layout<'a>,
@ -95,6 +122,35 @@ pub fn build_neq<'a, 'ctx, 'env>(
(Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::NE, "neq_i1"),
(Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::ONE, "neq_f64"),
(Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::ONE, "neq_f32"),
(Builtin::Str, Builtin::Str) => {
let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value();
let result: IntValue = env.builder.build_not(is_equal, "negate");
result.into()
}
(Builtin::EmptyList, Builtin::EmptyList) => {
env.context.bool_type().const_int(0, false).into()
}
(Builtin::List(_, _), Builtin::EmptyList)
| (Builtin::EmptyList, Builtin::List(_, _)) => {
unreachable!("the `==` operator makes sure its two arguments have the same type and thus layout")
}
(Builtin::List(_, elem1), Builtin::List(_, elem2)) => {
debug_assert_eq!(elem1, elem2);
let equal = build_list_eq(
env,
layout_ids,
lhs_layout,
elem1,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
);
let not_equal: IntValue = env.builder.build_not(equal.into_int_value(), "not");
not_equal.into()
}
(b1, b2) => {
todo!("Handle not equals for builtin layouts {:?} == {:?}", b1, b2);
}
@ -110,3 +166,203 @@ pub fn build_neq<'a, 'ctx, 'env>(
}
}
}
fn build_list_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
list_layout: &Layout<'a>,
element_layout: &Layout<'a>,
list1: StructValue<'ctx>,
list2: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::LIST_EQ;
let fn_name = layout_ids
.get(symbol, &element_layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arena = env.arena;
let arg_type = basic_type_from_layout(arena, env.context, &list_layout, env.ptr_bytes);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.bool_type().into(),
&[arg_type, arg_type],
);
build_list_eq_help(env, layout_ids, function_value, element_layout);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[list1.into(), list2.into()], "list_eq");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap()
}
fn build_list_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
element_layout: &Layout<'a>,
) {
let ctx = env.context;
let builder = env.builder;
{
use inkwell::debug_info::AsDIScope;
let func_scope = parent.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
ctx,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&ctx, loc);
}
// Add args to scope
let mut it = parent.get_param_iter();
let list1 = it.next().unwrap().into_struct_value();
let list2 = it.next().unwrap().into_struct_value();
set_name(list1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(list2.into(), Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let return_true = ctx.append_basic_block(parent, "return_true");
let return_false = ctx.append_basic_block(parent, "return_false");
// first, check whether the length is equal
let len1 = list_len(env.builder, list1);
let len2 = list_len(env.builder, list2);
let length_equal: IntValue =
env.builder
.build_int_compare(IntPredicate::EQ, len1, len2, "bounds_check");
let then_block = ctx.append_basic_block(parent, "then");
env.builder
.build_conditional_branch(length_equal, then_block, return_false);
{
// the length is equal; check elements pointwise
env.builder.position_at_end(then_block);
let builder = env.builder;
let element_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes);
let ptr_type = get_ptr_type(&element_type, AddressSpace::Generic);
let ptr1 = load_list_ptr(env.builder, list1, ptr_type);
let ptr2 = load_list_ptr(env.builder, list2, ptr_type);
// we know that len1 == len2
let end = len1;
// allocate a stack slot for the current index
let index_alloca = builder.build_alloca(ctx.i64_type(), "index");
builder.build_store(index_alloca, ctx.i64_type().const_zero());
let loop_bb = ctx.append_basic_block(parent, "loop");
let body_bb = ctx.append_basic_block(parent, "body");
let increment_bb = ctx.append_basic_block(parent, "increment");
// the "top" of the loop
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
let curr_index = builder.build_load(index_alloca, "index").into_int_value();
// #index < end
let loop_end_cond =
builder.build_int_compare(IntPredicate::ULT, curr_index, end, "bounds_check");
// if we're at the end, and all elements were equal so far, return true
// otherwise check the current elements for equality
builder.build_conditional_branch(loop_end_cond, body_bb, return_true);
{
// loop body
builder.position_at_end(body_bb);
let elem1 = {
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr1, &[curr_index], "load_index") };
builder.build_load(elem_ptr, "get_elem")
};
let elem2 = {
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr2, &[curr_index], "load_index") };
builder.build_load(elem_ptr, "get_elem")
};
let are_equal = build_eq(
env,
layout_ids,
elem1,
elem2,
element_layout,
element_layout,
)
.into_int_value();
// if the elements are equal, increment the index and check the next element
// otherwise, return false
builder.build_conditional_branch(are_equal, increment_bb, return_false);
}
{
env.builder.position_at_end(increment_bb);
// constant 1i64
let one = ctx.i64_type().const_int(1, false);
let next_index = builder.build_int_add(curr_index, one, "nextindex");
builder.build_store(index_alloca, next_index);
// jump back to the top of the loop
builder.build_unconditional_branch(loop_bb);
}
}
{
env.builder.position_at_end(return_true);
env.builder
.build_return(Some(&env.context.bool_type().const_int(1, false)));
}
{
env.builder.position_at_end(return_false);
env.builder
.build_return(Some(&env.context.bool_type().const_int(0, false)));
}
}

View file

@ -137,7 +137,10 @@ pub fn basic_type_from_layout<'ctx>(
basic_type_from_record(arena, context, sorted_fields, ptr_bytes)
}
RecursiveUnion(_) | Union(_) => block_of_memory(context, layout, ptr_bytes),
RecursiveUnion(_) => block_of_memory(context, layout, ptr_bytes)
.ptr_type(AddressSpace::Generic)
.into(),
Union(_) => block_of_memory(context, layout, ptr_bytes),
RecursivePointer => {
// TODO make this dynamic
context
@ -165,6 +168,7 @@ pub fn basic_type_from_builtin<'ctx>(
Int16 => context.i16_type().as_basic_type_enum(),
Int8 => context.i8_type().as_basic_type_enum(),
Int1 => context.bool_type().as_basic_type_enum(),
Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(),
Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_type().as_basic_type_enum(),

View file

@ -8,6 +8,7 @@ use bumpalo::collections::Vec;
use inkwell::context::Context;
use inkwell::debug_info::AsDIScope;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Symbol;
@ -342,7 +343,8 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
}
RecursiveUnion(tags) => {
build_dec_union(env, layout_ids, tags, value);
debug_assert!(value.is_pointer_value());
build_dec_rec_union(env, layout_ids, tags, value.into_pointer_value());
}
FunctionPointer(_, _) | Pointer(_) => {}
@ -426,7 +428,8 @@ pub fn increment_refcount_layout<'a, 'ctx, 'env>(
}
RecursiveUnion(tags) => {
build_inc_union(env, layout_ids, tags, value);
debug_assert!(value.is_pointer_value());
build_inc_rec_union(env, layout_ids, tags, value.into_pointer_value());
}
Closure(_, closure_layout, _) => {
if closure_layout.contains_refcounted() {
@ -993,12 +996,28 @@ pub fn build_header<'a, 'ctx, 'env>(
fn_name: &str,
) -> FunctionValue<'ctx> {
let arena = env.arena;
let context = &env.context;
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
build_header_help(env, fn_name, env.context.void_type().into(), &[arg_type])
}
// inc and dec return void
let fn_type = context.void_type().fn_type(&[arg_type], false);
/// Build an increment or decrement function for a specific layout
pub fn build_header_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
fn_name: &str,
return_type: AnyTypeEnum<'ctx>,
arguments: &[BasicTypeEnum<'ctx>],
) -> FunctionValue<'ctx> {
use inkwell::types::AnyTypeEnum::*;
let fn_type = match return_type {
ArrayType(t) => t.fn_type(arguments, false),
FloatType(t) => t.fn_type(arguments, false),
FunctionType(_) => unreachable!("functions cannot return functions"),
IntType(t) => t.fn_type(arguments, false),
PointerType(t) => t.fn_type(arguments, false),
StructType(t) => t.fn_type(arguments, false),
VectorType(t) => t.fn_type(arguments, false),
VoidType(t) => t.fn_type(arguments, false),
};
let fn_val = env
.module
@ -1015,6 +1034,202 @@ pub fn build_header<'a, 'ctx, 'env>(
fn_val
}
pub fn build_dec_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>,
) {
let layout = Layout::RecursiveUnion(fields);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::DEC;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let function_value = build_header(env, &layout, &fn_name);
build_dec_rec_union_help(env, layout_ids, fields, function_value);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[value.into()], "decrement_union");
call.set_call_convention(FAST_CALL_CONV);
}
pub fn build_dec_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
debug_assert!(!tags.is_empty());
use inkwell::types::BasicType;
let context = &env.context;
let builder = env.builder;
// Add a basic block for the entry point
let entry = context.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&context, loc);
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);
let before_block = env.builder.get_insert_block().expect("to be in a function");
debug_assert!(arg_val.is_pointer_value());
let value_ptr = arg_val.into_pointer_value();
// next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
let merge_block = env.context.append_basic_block(parent, "decrement_merge");
builder.set_current_debug_location(&context, loc);
for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
continue;
}
let block = env.context.append_basic_block(parent, "tag_id_decrement");
env.builder.position_at_end(block);
let wrapper_type = basic_type_from_layout(
env.arena,
env.context,
&Layout::Struct(field_layouts),
env.ptr_bytes,
);
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = cast_basic_basic(
env.builder,
value_ptr.into(),
wrapper_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
// this field has type `*i64`, but is really a pointer to the data we want
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let ptr_as_i64_ptr = env
.builder
.build_load(elem_pointer, "load_recursive_pointer");
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type =
basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type);
// recursively decrement the field
let call = env.builder.build_call(
fn_val,
&[recursive_field_ptr],
"recursive_tag_decrement",
);
// Because it's an internal-only function, use the fast calling convention.
call.set_call_convention(FAST_CALL_CONV);
} else if field_layout.contains_refcounted() {
// TODO this loads the whole field onto the stack;
// that's wasteful if e.g. the field is a big record, where only
// some fields are actually refcounted.
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let field = env
.builder
.build_load(elem_pointer, "decrement_struct_field");
decrement_refcount_layout(env, parent, layout_ids, field, field_layout);
}
}
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
}
cases.reverse();
env.builder.position_at_end(before_block);
// read the tag_id
let current_tag_id = rec_union_read_tag(env, value_ptr);
// switch on it
env.builder
.build_switch(current_tag_id, merge_block, &cases);
env.builder.position_at_end(merge_block);
// decrement this cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
refcount_ptr.decrement(env, &layout);
// this function returns void
builder.build_return(None);
}
pub fn build_dec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1061,8 +1276,6 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
) {
debug_assert!(!tags.is_empty());
use inkwell::types::BasicType;
let context = &env.context;
let builder = env.builder;
@ -1088,32 +1301,18 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
);
builder.set_current_debug_location(&context, loc);
let mut scope = Scope::default();
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let layout = Layout::Union(tags);
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let alloca = create_entry_block_alloca(
env,
fn_val,
arg_val.get_type(),
arg_symbol.ident_string(&env.interns),
);
builder.build_store(alloca, arg_val);
scope.insert(arg_symbol, (layout.clone(), alloca));
let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);
let before_block = env.builder.get_insert_block().expect("to be in a function");
debug_assert!(arg_val.is_struct_value());
let wrapper_struct = arg_val.into_struct_value();
// next, make a jump table for all possible values of the tag_id
@ -1147,39 +1346,7 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
// this field has type `*i64`, but is really a pointer to the data we want
let ptr_as_i64_ptr = env
.builder
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap();
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let recursive_field_ptr = cast_basic_basic(
env.builder,
ptr_as_i64_ptr,
union_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
let recursive_field = env
.builder
.build_load(recursive_field_ptr, "load_recursive_field");
// recursively decrement the field
let call =
env.builder
.build_call(fn_val, &[recursive_field], "recursive_tag_decrement");
// Because it's an internal-only function, use the fast calling convention.
call.set_call_convention(FAST_CALL_CONV);
// TODO do this decrement before the recursive call?
// Then the recursive call is potentially TCE'd
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, recursive_field_ptr);
refcount_ptr.decrement(env, &layout);
panic!("a non-recursive tag union cannot contain RecursivePointer");
} else if field_layout.contains_refcounted() {
let field_ptr = env
.builder
@ -1227,6 +1394,213 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
builder.build_return(None);
}
pub fn build_inc_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>,
) {
let layout = Layout::RecursiveUnion(fields);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::INC;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let function_value = build_header(env, &layout, &fn_name);
build_inc_rec_union_help(env, layout_ids, fields, function_value);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[value.into()], "increment_union");
call.set_call_convention(FAST_CALL_CONV);
}
fn rec_union_read_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value_ptr: PointerValue<'ctx>,
) -> IntValue<'ctx> {
// Assumption: the tag is the first thing stored
// so cast the pointer to the data to a `i64*`
let tag_ptr = cast_basic_basic(
env.builder,
value_ptr.into(),
env.context
.i64_type()
.ptr_type(AddressSpace::Generic)
.into(),
)
.into_pointer_value();
env.builder
.build_load(tag_ptr, "load_tag_id")
.into_int_value()
}
pub fn build_inc_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
debug_assert!(!tags.is_empty());
use inkwell::types::BasicType;
let context = &env.context;
let builder = env.builder;
// Add a basic block for the entry point
let entry = context.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&context, loc);
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);
let before_block = env.builder.get_insert_block().expect("to be in a function");
debug_assert!(arg_val.is_pointer_value());
let value_ptr = arg_val.into_pointer_value();
// read the tag_id
let tag_id = rec_union_read_tag(env, value_ptr);
let tag_id_u8 = cast_basic_basic(env.builder, tag_id.into(), env.context.i8_type().into());
// next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
let merge_block = env.context.append_basic_block(parent, "increment_merge");
for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
continue;
}
let block = env.context.append_basic_block(parent, "tag_id_increment");
env.builder.position_at_end(block);
let wrapper_type = basic_type_from_layout(
env.arena,
env.context,
&Layout::Struct(field_layouts),
env.ptr_bytes,
);
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = cast_basic_basic(
env.builder,
value_ptr.into(),
wrapper_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
// this field has type `*i64`, but is really a pointer to the data we want
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let ptr_as_i64_ptr = env
.builder
.build_load(elem_pointer, "load_recursive_pointer");
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let recursive_field_ptr = cast_basic_basic(
env.builder,
ptr_as_i64_ptr,
union_type.ptr_type(AddressSpace::Generic).into(),
);
// recursively increment the field
let call = env.builder.build_call(
fn_val,
&[recursive_field_ptr],
"recursive_tag_increment",
);
// Because it's an internal-only function, use the fast calling convention.
call.set_call_convention(FAST_CALL_CONV);
} else if field_layout.contains_refcounted() {
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_field")
.unwrap();
let field = env.builder.build_load(elem_pointer, "load_field");
increment_refcount_layout(env, parent, layout_ids, field, field_layout);
}
}
env.builder.build_unconditional_branch(merge_block);
cases.push((env.context.i8_type().const_int(tag_id as u64, false), block));
}
env.builder.position_at_end(before_block);
env.builder
.build_switch(tag_id_u8.into_int_value(), merge_block, &cases);
env.builder.position_at_end(merge_block);
// increment this cons cell
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
refcount_ptr.increment(env);
// this function returns void
builder.build_return(None);
}
pub fn build_inc_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1299,26 +1673,12 @@ pub fn build_inc_union_help<'a, 'ctx, 'env>(
);
builder.set_current_debug_location(&context, loc);
let mut scope = Scope::default();
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let layout = Layout::Union(tags);
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let alloca = create_entry_block_alloca(
env,
fn_val,
arg_val.get_type(),
arg_symbol.ident_string(&env.interns),
);
builder.build_store(alloca, arg_val);
scope.insert(arg_symbol, (layout.clone(), alloca));
let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);