Merge remote-tracking branch 'origin/trunk' into exception-handling

This commit is contained in:
Folkert 2020-09-19 21:50:57 +02:00
commit 16fc5dd497
23 changed files with 1280 additions and 351 deletions

View file

@ -1,10 +1,10 @@
use crate::layout_id::LayoutIds;
use crate::llvm::build_list::{
allocate_list, build_basic_phi2, clone_nonempty_list, empty_list, empty_polymorphic_list,
incrementing_elem_loop, list_append, list_concat, list_get_unsafe, list_is_not_empty,
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_get_unsafe,
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set,
list_single, list_walk_right, load_list_ptr, store_list,
list_single, list_walk_right,
};
use crate::llvm::build_str::{str_concat, str_len, CHAR_LAYOUT};
use crate::llvm::compare::{build_eq, build_neq};
use crate::llvm::convert::{
basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int,
@ -23,10 +23,12 @@ use inkwell::module::{Linkage, Module};
use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{BasicTypeEnum, FunctionType, IntType, StructType};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::InstructionOpcode;
use inkwell::values::{BasicValue, FloatValue, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace;
use inkwell::values::{
BasicValue, CallSiteValue, FloatValue, FunctionValue, InstructionOpcode, IntValue,
PointerValue, StructValue,
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
use roc_collections::all::{ImMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol};
@ -95,6 +97,77 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
pub fn ptr_int(&self) -> IntType<'ctx> {
ptr_int(self.context, self.ptr_bytes)
}
pub fn small_str_bytes(&self) -> u32 {
self.ptr_bytes * 2
}
pub fn build_intrinsic_call(
&self,
intrinsic_name: &'static str,
args: &[BasicValueEnum<'ctx>],
) -> CallSiteValue<'ctx> {
let fn_val = self
.module
.get_function(intrinsic_name)
.unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), self.arena);
for arg in args.iter() {
arg_vals.push(*arg);
}
let call = self
.builder
.build_call(fn_val, arg_vals.into_bump_slice(), "call");
call.set_call_convention(fn_val.get_call_conventions());
call
}
pub fn call_intrinsic(
&self,
intrinsic_name: &'static str,
args: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> {
let call = self.build_intrinsic_call(intrinsic_name, args);
call.try_as_basic_value().left().unwrap_or_else(|| {
panic!(
"LLVM error: Invalid call by name for intrinsic {}",
intrinsic_name
)
})
}
pub fn call_memset(
&self,
bytes_ptr: PointerValue<'ctx>,
filler: IntValue<'ctx>,
length: IntValue<'ctx>,
) -> CallSiteValue<'ctx> {
let false_val = self.context.bool_type().const_int(0, false);
let intrinsic_name = match self.ptr_bytes {
8 => LLVM_MEMSET_I64,
4 => LLVM_MEMSET_I32,
other => {
unreachable!("Unsupported number of ptr_bytes {:?}", other);
}
};
self.build_intrinsic_call(
intrinsic_name,
&[
bytes_ptr.into(),
filler.into(),
length.into(),
false_val.into(),
],
)
}
}
pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> {
@ -114,8 +187,41 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
// List of all supported LLVM intrinsics:
//
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let i64_type = ctx.i64_type();
let void_type = ctx.void_type();
let i1_type = ctx.bool_type();
let f64_type = ctx.f64_type();
let i64_type = ctx.i64_type();
let i32_type = ctx.i32_type();
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,
@ -158,8 +264,16 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
LLVM_CEILING_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
add_intrinsic(
module,
LLVM_FLOOR_F64,
f64_type.fn_type(&[f64_type.into()], false),
);
}
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
static LLVM_SQRT_F64: &str = "llvm.sqrt.f64";
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
static LLVM_FABS_F64: &str = "llvm.fabs.f64";
@ -167,6 +281,7 @@ 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";
static LLVM_FLOOR_F64: &str = "llvm.floor.f64";
fn add_intrinsic<'ctx>(
module: &Module<'ctx>,
@ -521,70 +636,124 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
} else {
let ctx = env.context;
let builder = env.builder;
let len_u64 = str_literal.len() as u64;
let elem_bytes = CHAR_LAYOUT.stack_size(env.ptr_bytes) as u64;
let ptr_bytes = env.ptr_bytes;
let ptr = {
let populate_str = |ptr| {
// Copy the elements from the list literal into the array
for (index, char) in str_literal.as_bytes().iter().enumerate() {
let val = env
.context
.i8_type()
.const_int(*char as u64, false)
.as_basic_value_enum();
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
builder.build_store(elem_ptr, val);
}
};
if str_literal.len() < env.small_str_bytes() as usize {
// TODO support big endian systems
let array_alloca = builder.build_array_alloca(
ctx.i8_type(),
ctx.i8_type().const_int(env.small_str_bytes() as u64, false),
"alloca_small_str",
);
// Zero out all the bytes. If we don't do this, then
// small strings would have uninitialized bytes, which could
// cause string equality checks to fail randomly.
//
// We're running memset over *all* the bytes, even though
// the final one is about to be manually overridden, on
// the theory that LLVM will optimize the memset call
// into two instructions to move appropriately-sized
// zero integers into the appropriate locations instead
// of doing any iteration.
//
// TODO: look at the compiled output to verify this theory!
env.call_memset(
array_alloca,
ctx.i8_type().const_zero(),
env.ptr_int().const_int(env.small_str_bytes() as u64, false),
);
let final_byte = (str_literal.len() as u8) | 0b1000_0000;
let final_byte_ptr = unsafe {
builder.build_in_bounds_gep(
array_alloca,
&[ctx
.i8_type()
.const_int(env.small_str_bytes() as u64 - 1, false)],
"str_literal_final_byte",
)
};
builder.build_store(
final_byte_ptr,
ctx.i8_type().const_int(final_byte as u64, false),
);
populate_str(array_alloca);
builder.build_load(
builder
.build_bitcast(
array_alloca,
collection(ctx, ptr_bytes).ptr_type(AddressSpace::Generic),
"cast_collection",
)
.into_pointer_value(),
"small_str_array",
)
} else {
let bytes_len = elem_bytes * len_u64;
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
allocate_list(env, InPlace::Clone, &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);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
// TODO check if malloc returned null; if so, runtime error for OOM!
};
let mut struct_val;
// Copy the elements from the list literal into the array
for (index, char) in str_literal.as_bytes().iter().enumerate() {
let val = env
.context
.i8_type()
.const_int(*char as u64, false)
.as_basic_value_enum();
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
Builtin::WRAPPER_PTR,
"insert_ptr",
)
.unwrap();
builder.build_store(elem_ptr, val);
}
// Store the length
struct_val = builder
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
let ptr_bytes = env.ptr_bytes;
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);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false));
let mut struct_val;
populate_str(ptr);
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
Builtin::WRAPPER_PTR,
"insert_ptr",
builder.build_bitcast(
struct_val.into_struct_value(),
collection(ctx, ptr_bytes),
"cast_collection",
)
.unwrap();
// Store the length
struct_val = builder
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
// Bitcast to an array of raw bytes
builder.build_bitcast(
struct_val.into_struct_value(),
collection(ctx, ptr_bytes),
"cast_collection",
)
// TODO check if malloc returned null; if so, runtime error for OOM!
}
}
}
}
}
static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
pub fn build_exp_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1390,6 +1559,16 @@ pub fn load_symbol<'a, 'ctx, 'env>(
}
}
pub fn ptr_from_symbol<'a, 'ctx, 'scope>(
scope: &'scope Scope<'a, 'ctx>,
symbol: Symbol,
) -> &'scope PointerValue<'ctx> {
match scope.get(&symbol) {
Some((_, ptr)) => ptr,
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
}
}
pub fn load_symbol_and_layout<'a, 'ctx, 'env, 'b>(
env: &Env<'a, 'ctx, 'env>,
scope: &'b Scope<'a, 'ctx>,
@ -1743,36 +1922,6 @@ fn call_with_args<'a, 'ctx, 'env>(
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name for name {:?}", symbol))
}
fn call_intrinsic<'a, 'ctx, 'env>(
intrinsic_name: &'static str,
env: &Env<'a, 'ctx, 'env>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
) -> BasicValueEnum<'ctx> {
let fn_val = env
.module
.get_function(intrinsic_name)
.unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for (arg, _layout) in args.iter() {
arg_vals.push(*arg);
}
let call = env
.builder
.build_call(fn_val, arg_vals.into_bump_slice(), "call");
call.set_call_convention(fn_val.get_call_conventions());
call.try_as_basic_value().left().unwrap_or_else(|| {
panic!(
"LLVM error: Invalid call by name for intrinsic {}",
intrinsic_name
)
})
}
#[derive(Copy, Clone)]
pub enum InPlace {
InPlace,
@ -1813,13 +1962,23 @@ fn run_low_level<'a, 'ctx, 'env>(
// Str.concat : Str, Str -> Str
debug_assert_eq!(args.len(), 2);
let first_str = load_symbol(env, scope, &args[0]);
let second_str = load_symbol(env, scope, &args[1]);
let inplace = get_inplace_from_layout(layout);
str_concat(env, inplace, parent, first_str, second_str)
str_concat(env, inplace, scope, parent, args[0], args[1])
}
StrIsEmpty => {
// Str.isEmpty : Str -> Str
debug_assert_eq!(args.len(), 1);
let wrapper_ptr = ptr_from_symbol(scope, args[0]);
let len = str_len(env, parent, *wrapper_ptr);
let is_zero = env.builder.build_int_compare(
IntPredicate::EQ,
len,
env.ptr_int().const_zero(),
"str_len_is_zero",
);
BasicValueEnum::IntValue(is_zero)
}
ListLen => {
// List.len : List * -> Int
@ -1948,7 +2107,7 @@ fn run_low_level<'a, 'ctx, 'env>(
list_join(env, inplace, parent, list, outer_list_layout)
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor
| NumToFloat => {
debug_assert_eq!(args.len(), 1);
@ -1963,7 +2122,7 @@ fn run_low_level<'a, 'ctx, 'env>(
build_int_unary_op(env, arg.into_int_value(), arg_layout, op)
}
Float128 | Float64 | Float32 | Float16 => {
build_float_unary_op(env, arg.into_float_value(), arg_layout, op)
build_float_unary_op(env, arg.into_float_value(), op)
}
_ => {
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout);
@ -1980,7 +2139,6 @@ fn run_low_level<'a, 'ctx, 'env>(
}
NumCompare => {
use inkwell::FloatPredicate;
use inkwell::IntPredicate;
debug_assert_eq!(args.len(), 2);
@ -2061,7 +2219,7 @@ fn run_low_level<'a, 'ctx, 'env>(
}
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumDivUnchecked | NumPow => {
| NumDivUnchecked | NumPow | NumPowInt => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2253,179 +2411,6 @@ where
}
}
/// Str.concat : Str, Str -> Str
fn str_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
parent: FunctionValue<'ctx>,
first_str: BasicValueEnum<'ctx>,
second_str: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let second_str_wrapper = second_str.into_struct_value();
let second_str_len = list_len(builder, second_str_wrapper);
let first_str_wrapper = first_str.into_struct_value();
let first_str_len = list_len(builder, first_str_wrapper);
// first_str_len > 0
// We do this check to avoid allocating memory. If the first input
// str is empty, then we can just return the second str cloned
let first_str_length_comparison = list_is_not_empty(builder, ctx, first_str_len);
let if_first_str_is_empty = || {
// second_str_len > 0
// We do this check to avoid allocating memory. If the second input
// str is empty, then we can just return an empty str
let second_str_length_comparison = list_is_not_empty(builder, ctx, second_str_len);
let build_second_str_then = || {
let char_type = basic_type_from_layout(env.arena, ctx, &CHAR_LAYOUT, env.ptr_bytes);
let ptr_type = get_ptr_type(&char_type, AddressSpace::Generic);
let (new_wrapper, _) = clone_nonempty_list(
env,
inplace,
second_str_len,
load_list_ptr(builder, second_str_wrapper, ptr_type),
&CHAR_LAYOUT,
);
BasicValueEnum::StructValue(new_wrapper)
};
let build_second_str_else = || empty_list(env);
build_basic_phi2(
env,
parent,
second_str_length_comparison,
build_second_str_then,
build_second_str_else,
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
)
};
let if_first_str_is_not_empty = || {
let char_type = ctx.i8_type().into();
let ptr_type = get_ptr_type(&char_type, AddressSpace::Generic);
let if_second_str_is_empty = || {
let (new_wrapper, _) = clone_nonempty_list(
env,
inplace,
first_str_len,
load_list_ptr(builder, first_str_wrapper, ptr_type),
&CHAR_LAYOUT,
);
BasicValueEnum::StructValue(new_wrapper)
};
// second_str_len > 0
// We do this check to avoid allocating memory. If the second input
// str is empty, then we can just return the first str cloned
let second_str_length_comparison = list_is_not_empty(builder, ctx, second_str_len);
let if_second_str_is_not_empty = || {
let combined_str_len =
builder.build_int_add(first_str_len, second_str_len, "add_list_lengths");
let combined_str_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, combined_str_len);
// FIRST LOOP
let first_str_ptr = load_list_ptr(builder, first_str_wrapper, ptr_type);
let first_loop = |first_index, first_str_elem| {
// The pointer to the element in the combined list
let combined_str_elem_ptr = unsafe {
builder.build_in_bounds_gep(
combined_str_ptr,
&[first_index],
"load_index_combined_list",
)
};
// Mutate the new array in-place to change the element.
builder.build_store(combined_str_elem_ptr, first_str_elem);
};
let index_name = "#index";
let index_alloca = incrementing_elem_loop(
builder,
ctx,
parent,
first_str_ptr,
first_str_len,
index_name,
first_loop,
);
// Reset the index variable to 0
builder.build_store(index_alloca, ctx.i64_type().const_int(0, false));
// SECOND LOOP
let second_str_ptr = load_list_ptr(builder, second_str_wrapper, ptr_type);
let second_loop = |second_index, second_str_elem| {
// The pointer to the element in the combined str.
// Note that the pointer does not start at the index
// 0, it starts at the index of first_str_len. In that
// sense it is "offset".
let offset_combined_str_char_ptr = unsafe {
builder.build_in_bounds_gep(combined_str_ptr, &[first_str_len], "elem")
};
// The pointer to the char from the second str
// in the combined list
let combined_str_char_ptr = unsafe {
builder.build_in_bounds_gep(
offset_combined_str_char_ptr,
&[second_index],
"load_index_combined_list",
)
};
// Mutate the new array in-place to change the element.
builder.build_store(combined_str_char_ptr, second_str_elem);
};
incrementing_elem_loop(
builder,
ctx,
parent,
second_str_ptr,
second_str_len,
index_name,
second_loop,
);
store_list(env, combined_str_ptr, combined_str_len)
};
build_basic_phi2(
env,
parent,
second_str_length_comparison,
if_second_str_is_not_empty,
if_second_str_is_empty,
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
)
};
build_basic_phi2(
env,
parent,
first_str_length_comparison,
if_first_str_is_not_empty,
if_first_str_is_empty,
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
)
}
fn build_int_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
lhs: IntValue<'ctx>,
@ -2449,12 +2434,32 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(),
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
NumPowInt => call_bitcode_fn(NumPowInt, env, &[lhs.into(), rhs.into()], "pow_int_"),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}
}
}
fn call_bitcode_fn<'a, 'ctx, 'env>(
op: LowLevel,
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> BasicValueEnum<'ctx> {
let fn_val = env
.module
.get_function(fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
let call = env.builder.build_call(fn_val, args, "call_builtin");
call.set_call_convention(fn_val.get_call_conventions());
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op))
}
fn build_float_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
lhs: FloatValue<'ctx>,
@ -2478,11 +2483,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 => call_intrinsic(
LLVM_POW_F64,
env,
&[(lhs.into(), _lhs_layout), (rhs.into(), _rhs_layout)],
),
NumPow => env.call_intrinsic(LLVM_POW_F64, &[lhs.into(), rhs.into()]),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}
@ -2540,22 +2541,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
}
NumToFloat => {
// TODO specialize this to be not just for i64!
let builtin_fn_name = "i64_to_f64_";
let fn_val = env
.module
.get_function(builtin_fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name));
let call = env
.builder
.build_call(fn_val, &[arg.into()], "call_builtin");
call.set_call_convention(fn_val.get_call_conventions());
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op))
call_bitcode_fn(NumToFloat, env, &[arg.into()], "i64_to_f64_")
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
@ -2566,7 +2552,6 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
fn build_float_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
arg: FloatValue<'ctx>,
arg_layout: &Layout<'a>,
op: LowLevel,
) -> BasicValueEnum<'ctx> {
use roc_module::low_level::LowLevel::*;
@ -2575,18 +2560,24 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
match op {
NumNeg => bd.build_float_neg(arg, "negate_float").into(),
NumAbs => call_intrinsic(LLVM_FABS_F64, env, &[(arg.into(), arg_layout)]),
NumSqrtUnchecked => call_intrinsic(LLVM_SQRT_F64, env, &[(arg.into(), arg_layout)]),
NumRound => call_intrinsic(LLVM_LROUND_I64_F64, env, &[(arg.into(), arg_layout)]),
NumSin => call_intrinsic(LLVM_SIN_F64, env, &[(arg.into(), arg_layout)]),
NumCos => call_intrinsic(LLVM_COS_F64, env, &[(arg.into(), arg_layout)]),
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]),
NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]),
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,
call_intrinsic(LLVM_CEILING_F64, env, &[(arg.into(), arg_layout)]),
env.call_intrinsic(LLVM_CEILING_F64, &[arg.into()]),
env.context.i64_type(),
"num_ceiling",
),
NumFloor => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(LLVM_FLOOR_F64, &[arg.into()]),
env.context.i64_type(),
"num_floor",
),
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}