Merge remote-tracking branch 'origin/trunk' into small-str

This commit is contained in:
Richard Feldman 2020-09-16 02:23:06 -04:00
commit 5d3645350d
33 changed files with 2597 additions and 902 deletions

View file

@ -24,7 +24,8 @@ use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{BasicTypeEnum, FunctionType, IntType, StructType};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{
BasicValue, CallSiteValue, FloatValue, FunctionValue, IntValue, PointerValue, StructValue,
BasicValue, CallSiteValue, FloatValue, FunctionValue, InstructionOpcode, IntValue,
PointerValue, StructValue,
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
@ -194,6 +195,34 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
let i8_type = ctx.i8_type();
let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic);
add_intrinsic(
module,
LLVM_MEMSET_I64,
void_type.fn_type(
&[
i8_ptr_type.into(),
i8_type.into(),
i64_type.into(),
i1_type.into(),
],
false,
),
);
add_intrinsic(
module,
LLVM_MEMSET_I32,
void_type.fn_type(
&[
i8_ptr_type.into(),
i8_type.into(),
i32_type.into(),
i1_type.into(),
],
false,
),
);
add_intrinsic(
module,
LLVM_SQRT_F64,
@ -226,30 +255,14 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
add_intrinsic(
module,
LLVM_MEMSET_I64,
void_type.fn_type(
&[
i8_ptr_type.into(),
i8_type.into(),
i64_type.into(),
i1_type.into(),
],
false,
),
LLVM_POW_F64,
f64_type.fn_type(&[f64_type.into(), f64_type.into()], false),
);
add_intrinsic(
module,
LLVM_MEMSET_I32,
void_type.fn_type(
&[
i8_ptr_type.into(),
i8_type.into(),
i32_type.into(),
i1_type.into(),
],
false,
),
LLVM_CEILING_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
}
@ -260,6 +273,8 @@ static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
static LLVM_FABS_F64: &str = "llvm.fabs.f64";
static LLVM_SIN_F64: &str = "llvm.sin.f64";
static LLVM_COS_F64: &str = "llvm.cos.f64";
static LLVM_POW_F64: &str = "llvm.pow.f64";
static LLVM_CEILING_F64: &str = "llvm.ceil.f64";
fn add_intrinsic<'ctx>(
module: &Module<'ctx>,
@ -320,6 +335,133 @@ pub fn construct_optimization_passes<'a>(
(mpm, fpm)
}
/// For communication with C (tests and platforms) we need to abide by the C calling convention
///
/// While small values are just returned like with the fast CC, larger structures need to
/// be written into a pointer (into the callers stack)
enum PassVia {
Register,
Memory,
}
impl PassVia {
fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self {
if layout.stack_size(ptr_bytes) > 16 {
PassVia::Memory
} else {
PassVia::Register
}
}
}
pub fn make_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
use inkwell::types::BasicType;
use PassVia::*;
let context = env.context;
let builder = env.builder;
let arena = env.arena;
let ptr_bytes = env.ptr_bytes;
let return_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes);
let roc_main_fn_name = "$Test.roc_main";
// make the roc main function
let roc_main_fn_type = return_type.fn_type(&[], false);
// Add main to the module.
let roc_main_fn = env
.module
.add_function(roc_main_fn_name, roc_main_fn_type, None);
// our exposed main function adheres to the C calling convention
roc_main_fn.set_call_conventions(FAST_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(roc_main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
build_exp_stmt(
env,
layout_ids,
&mut Scope::default(),
roc_main_fn,
main_body,
);
// build the C calling convention wrapper
let main_fn_name = "$Test.main";
let register_or_memory = PassVia::from_layout(env.ptr_bytes, layout);
let main_fn_type = match register_or_memory {
Memory => {
let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into();
context.void_type().fn_type(&[return_value_ptr], false)
}
Register => return_type.fn_type(&[], false),
};
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
// our exposed main function adheres to the C calling convention
main_fn.set_call_conventions(C_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
let call = builder.build_call(roc_main_fn, &[], "call_roc_main");
call.set_call_convention(FAST_CALL_CONV);
let call_result = call.try_as_basic_value().left().unwrap();
match register_or_memory {
Memory => {
// write the result into the supplied pointer
// this is a void function, therefore return None
let ptr_return_type = return_type.ptr_type(AddressSpace::Generic);
let ptr_as_int = main_fn.get_first_param().unwrap();
let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr");
builder.build_store(ptr.into_pointer_value(), call_result);
builder.build_return(None);
}
Register => {
// construct a normal return
// values are passed to the caller via registers
builder.build_return(Some(&call_result));
}
}
(main_fn_name, env.arena.alloc(main_fn))
}
fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace {
match layout {
Layout::Builtin(Builtin::EmptyList) => InPlace::InPlace,
Layout::Builtin(Builtin::List(memory_mode, _)) => match memory_mode {
MemoryMode::Unique => InPlace::InPlace,
MemoryMode::Refcounted => InPlace::Clone,
},
Layout::Builtin(Builtin::EmptyStr) => InPlace::InPlace,
Layout::Builtin(Builtin::Str) => InPlace::Clone,
_ => unreachable!("Layout {:?} does not have an inplace", layout),
}
}
pub fn build_exp_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
literal: &roc_mono::ir::Literal<'a>,
@ -418,8 +560,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
let ptr = allocate_list(env, &CHAR_LAYOUT, len);
let ptr = allocate_list(env, InPlace::Clone, &CHAR_LAYOUT, len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes);
@ -461,6 +602,7 @@ pub fn build_exp_expr<'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 roc_mono::ir::CallType::*;
@ -468,7 +610,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
match expr {
Literal(literal) => build_exp_literal(env, literal),
RunLowLevel(op, symbols) => run_low_level(env, scope, parent, *op, symbols),
RunLowLevel(op, symbols) => run_low_level(env, scope, parent, layout, *op, symbols),
FunctionCall {
call_type: ByName(name),
@ -833,7 +975,11 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}
}
EmptyArray => empty_polymorphic_list(env),
Array { elem_layout, elems } => list_literal(env, scope, elem_layout, elems),
Array { elem_layout, elems } => {
let inplace = get_inplace_from_layout(layout);
list_literal(env, inplace, scope, elem_layout, elems)
}
FunctionPointer(symbol, layout) => {
let fn_name = layout_ids
.get(*symbol, layout)
@ -922,6 +1068,7 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>(
fn list_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
scope: &Scope<'a, 'ctx>,
elem_layout: &Layout<'a>,
elems: &&[Symbol],
@ -937,7 +1084,7 @@ fn list_literal<'a, 'ctx, 'env>(
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
allocate_list(env, elem_layout, len)
allocate_list(env, inplace, elem_layout, len)
// TODO check if malloc returned null; if so, runtime error for OOM!
};
@ -995,7 +1142,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
Let(symbol, expr, layout, cont) => {
let context = &env.context;
let val = build_exp_expr(env, layout_ids, &scope, parent, &expr);
let val = build_exp_expr(env, layout_ids, &scope, parent, layout, &expr);
let expr_bt = if let Layout::RecursivePointer = layout {
match expr {
Expr::AccessAtIndex { field_layouts, .. } => {
@ -1611,6 +1758,7 @@ fn call_with_args<'a, 'ctx, 'env>(
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol))
}
#[derive(Copy, Clone)]
pub enum InPlace {
InPlace,
Clone,
@ -1639,6 +1787,7 @@ fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
layout: &Layout<'a>,
op: LowLevel,
args: &[Symbol],
) -> BasicValueEnum<'ctx> {
@ -1649,7 +1798,9 @@ fn run_low_level<'a, 'ctx, 'env>(
// Str.concat : Str, Str -> Str
debug_assert_eq!(args.len(), 2);
str_concat(env, scope, parent, args[0], args[1])
let inplace = get_inplace_from_layout(layout);
str_concat(env, inplace, scope, parent, args[0], args[1])
}
StrIsEmpty => {
// Str.isEmpty : Str -> Str
@ -1679,7 +1830,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
list_single(env, arg, arg_layout)
let inplace = get_inplace_from_layout(layout);
list_single(env, inplace, arg, arg_layout)
}
ListRepeat => {
// List.repeat : Int, elem -> List elem
@ -1688,7 +1841,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let list_len = load_symbol(env, scope, &args[0]).into_int_value();
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_repeat(env, parent, list_len, elem, elem_layout)
let inplace = get_inplace_from_layout(layout);
list_repeat(env, inplace, parent, list_len, elem, elem_layout)
}
ListReverse => {
// List.reverse : List elem -> List elem
@ -1696,7 +1851,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
list_reverse(env, parent, list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_reverse(env, parent, inplace, list, list_layout)
}
ListConcat => {
debug_assert_eq!(args.len(), 2);
@ -1705,7 +1862,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let second_list = load_symbol(env, scope, &args[1]);
list_concat(env, parent, first_list, second_list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_concat(env, inplace, parent, first_list, second_list, list_layout)
}
ListMap => {
// List.map : List before, (before -> after) -> List after
@ -1715,7 +1874,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_map(env, parent, func, func_layout, list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_map(env, inplace, parent, func, func_layout, list, list_layout)
}
ListKeepIf => {
// List.keepIf : List elem, (elem -> Bool) -> List elem
@ -1725,7 +1886,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (func, func_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_keep_if(env, parent, func, func_layout, list, list_layout)
let inplace = get_inplace_from_layout(layout);
list_keep_if(env, inplace, parent, func, func_layout, list, list_layout)
}
ListWalkRight => {
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
@ -1755,7 +1918,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(env, scope, &args[0]).into_struct_value();
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_append(env, original_wrapper, elem, elem_layout)
let inplace = get_inplace_from_layout(layout);
list_append(env, inplace, original_wrapper, elem, elem_layout)
}
ListPrepend => {
// List.prepend : List elem, elem -> List elem
@ -1764,7 +1929,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(env, scope, &args[0]).into_struct_value();
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_prepend(env, original_wrapper, elem, elem_layout)
let inplace = get_inplace_from_layout(layout);
list_prepend(env, inplace, original_wrapper, elem, elem_layout)
}
ListJoin => {
// List.join : List (List elem) -> List elem
@ -1772,9 +1939,12 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, outer_list_layout) = load_symbol_and_layout(env, scope, &args[0]);
list_join(env, parent, list, outer_list_layout)
let inplace = get_inplace_from_layout(layout);
list_join(env, inplace, parent, list, outer_list_layout)
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumToFloat => {
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling
| NumToFloat => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -1885,7 +2055,7 @@ fn run_low_level<'a, 'ctx, 'env>(
}
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumDivUnchecked => {
| NumDivUnchecked | NumPow => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -1990,6 +2160,8 @@ fn run_low_level<'a, 'ctx, 'env>(
ListSetInPlace => {
let (list_symbol, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
let output_inplace = get_inplace_from_layout(layout);
list_set(
parent,
&[
@ -1999,6 +2171,7 @@ fn run_low_level<'a, 'ctx, 'env>(
],
env,
InPlace::InPlace,
output_inplace,
)
}
ListSet => {
@ -2010,49 +2183,70 @@ fn run_low_level<'a, 'ctx, 'env>(
(load_symbol_and_layout(env, scope, &args[2])),
];
match list_layout {
Layout::Builtin(Builtin::List(MemoryMode::Unique, _)) => {
// the layout tells us this List.set can be done in-place
list_set(parent, arguments, env, InPlace::InPlace)
}
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
// no static guarantees, but all is not lost: we can check the refcount
// if it is one, we hold the final reference, and can mutate it in-place!
let builder = env.builder;
let ctx = env.context;
let output_inplace = get_inplace_from_layout(layout);
let ret_type =
basic_type_from_layout(env.arena, ctx, list_layout, env.ptr_bytes);
let in_place = || list_set(parent, arguments, env, InPlace::InPlace, output_inplace);
let clone = || list_set(parent, arguments, env, InPlace::Clone, output_inplace);
let empty = || list_symbol;
let refcount_ptr =
list_get_refcount_ptr(env, list_layout, list_symbol.into_struct_value());
let refcount = env
.builder
.build_load(refcount_ptr, "get_refcount")
.into_int_value();
let comparison = refcount_is_one_comparison(builder, env.context, refcount);
// build then block
// refcount is 1, so work in-place
let build_pass = || list_set(parent, arguments, env, InPlace::InPlace);
// build else block
// refcount != 1, so clone first
let build_fail = || list_set(parent, arguments, env, InPlace::Clone);
crate::llvm::build_list::build_basic_phi2(
env, parent, comparison, build_pass, build_fail, ret_type,
)
}
Layout::Builtin(Builtin::EmptyList) => list_symbol,
other => unreachable!("List.set: weird layout {:?}", other),
}
maybe_inplace_list(
env,
parent,
list_layout,
list_symbol.into_struct_value(),
in_place,
clone,
empty,
)
}
}
}
fn maybe_inplace_list<'a, 'ctx, 'env, InPlace, CloneFirst, Empty>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
list_layout: &Layout<'a>,
original_wrapper: StructValue<'ctx>,
mut in_place: InPlace,
clone: CloneFirst,
mut empty: Empty,
) -> BasicValueEnum<'ctx>
where
InPlace: FnMut() -> BasicValueEnum<'ctx>,
CloneFirst: FnMut() -> BasicValueEnum<'ctx>,
Empty: FnMut() -> BasicValueEnum<'ctx>,
{
match list_layout {
Layout::Builtin(Builtin::List(MemoryMode::Unique, _)) => {
// the layout tells us this List.set can be done in-place
in_place()
}
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
// no static guarantees, but all is not lost: we can check the refcount
// if it is one, we hold the final reference, and can mutate it in-place!
let builder = env.builder;
let ctx = env.context;
let ret_type = basic_type_from_layout(env.arena, ctx, list_layout, env.ptr_bytes);
let refcount_ptr = list_get_refcount_ptr(env, list_layout, original_wrapper);
let refcount = env
.builder
.build_load(refcount_ptr, "get_refcount")
.into_int_value();
let comparison = refcount_is_one_comparison(builder, env.context, refcount);
crate::llvm::build_list::build_basic_phi2(
env, parent, comparison, in_place, clone, ret_type,
)
}
Layout::Builtin(Builtin::EmptyList) => empty(),
other => unreachable!("Attempting list operation on invalid layout {:?}", other),
}
}
fn build_int_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
lhs: IntValue<'ctx>,
@ -2105,6 +2299,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
NumPow => env.call_intrinsic(LLVM_POW_F64, &[lhs.into(), rhs.into()]),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}
@ -2202,6 +2397,12 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]),
NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]),
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */
NumCeiling => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(LLVM_CEILING_F64, &[arg.into()]),
env.context.i64_type(),
"num_ceiling",
),
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}