mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 15:03:46 +00:00
2807 lines
97 KiB
Rust
2807 lines
97 KiB
Rust
use inkwell::{
|
|
types::{BasicType, IntType},
|
|
values::{
|
|
BasicValue, BasicValueEnum, FloatValue, FunctionValue, InstructionOpcode, IntValue,
|
|
PointerValue, StructValue,
|
|
},
|
|
AddressSpace, IntPredicate,
|
|
};
|
|
use morphic_lib::{FuncSpec, UpdateMode};
|
|
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
|
use roc_error_macros::internal_error;
|
|
use roc_module::{low_level::LowLevel, symbol::Symbol};
|
|
use roc_mono::{
|
|
ir::HigherOrderLowLevel,
|
|
layout::{
|
|
Builtin, InLayout, LambdaSet, Layout, LayoutIds, LayoutInterner, LayoutRepr,
|
|
STLayoutInterner,
|
|
},
|
|
list_element_layout,
|
|
};
|
|
use roc_target::PtrWidth;
|
|
|
|
use crate::llvm::{
|
|
bitcode::{
|
|
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_list_bitcode_fn,
|
|
call_str_bitcode_fn, call_void_bitcode_fn, pass_list_or_string_to_zig_32bit,
|
|
BitcodeReturns,
|
|
},
|
|
build::{
|
|
cast_basic_basic, complex_bitcast_check_size, create_entry_block_alloca,
|
|
function_value_by_func_spec, load_roc_value, roc_function_call, tag_pointer_clear_tag_id,
|
|
BuilderExt, RocReturn,
|
|
},
|
|
build_list::{
|
|
list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map,
|
|
list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity,
|
|
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
|
|
list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
|
|
},
|
|
compare::{generic_eq, generic_neq},
|
|
convert::{
|
|
self, argument_type_from_layout, basic_type_from_layout, zig_num_parse_result_type,
|
|
zig_to_int_checked_result_type,
|
|
},
|
|
intrinsics::{
|
|
// These instrinsics do not generate calls to libc and are safe to keep.
|
|
// If we find that any of them generate calls to libc on some platforms, we need to define them as zig bitcode.
|
|
LLVM_ADD_SATURATED,
|
|
LLVM_ADD_WITH_OVERFLOW,
|
|
LLVM_MUL_WITH_OVERFLOW,
|
|
LLVM_SUB_SATURATED,
|
|
LLVM_SUB_WITH_OVERFLOW,
|
|
},
|
|
refcounting::PointerToRefcount,
|
|
};
|
|
|
|
use super::{build::Env, convert::zig_dec_type};
|
|
use super::{
|
|
build::{throw_internal_exception, use_roc_value},
|
|
convert::zig_with_overflow_roc_dec,
|
|
scope::Scope,
|
|
};
|
|
|
|
pub(crate) fn run_low_level<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
scope: &Scope<'a, 'ctx>,
|
|
parent: FunctionValue<'ctx>,
|
|
layout: InLayout<'a>,
|
|
op: LowLevel,
|
|
args: &[Symbol],
|
|
update_mode: UpdateMode,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use LowLevel::*;
|
|
|
|
debug_assert!(!op.is_higher_order());
|
|
|
|
macro_rules! arguments {
|
|
() => {};
|
|
($($x:ident),+ $(,)?) => {
|
|
// look at that, a usage for if let ... else
|
|
let [$($x),+] = match &args {
|
|
[$($x),+] => {
|
|
[ $(scope.load_symbol($x)),+ ]
|
|
}
|
|
_ => {
|
|
// we could get fancier with reporting here, but this macro is used a bunch
|
|
// so I want to keep the expansion small (for now)
|
|
internal_error!("lowlevel operation has incorrect number of arguments!")
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
macro_rules! arguments_with_layouts {
|
|
() => {};
|
|
($(($x:ident, $y:ident)),+ $(,)?) => {
|
|
// look at that, a usage for if let ... else
|
|
let [$(($x, $y)),+] = match &args {
|
|
[$($x),+] => {
|
|
[ $(scope.load_symbol_and_layout($x)),+ ]
|
|
}
|
|
_ => {
|
|
// we could get fancier with reporting here, but this macro is used a bunch
|
|
// so I want to keep the expansion small (for now)
|
|
internal_error!("lowlevel operation has incorrect number of arguments!")
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
match op {
|
|
StrConcat => {
|
|
// Str.concat : Str, Str -> Str
|
|
arguments!(string1, string2);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string1, string2],
|
|
&[],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_CONCAT,
|
|
)
|
|
}
|
|
StrJoinWith => {
|
|
// Str.joinWith : List Str, Str -> Str
|
|
arguments!(list, string);
|
|
|
|
match env.target_info.ptr_width() {
|
|
PtrWidth::Bytes4 => {
|
|
// list and string are both stored as structs on the stack on 32-bit targets
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[list, string],
|
|
&[],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_JOIN_WITH,
|
|
)
|
|
}
|
|
PtrWidth::Bytes8 => {
|
|
// on 64-bit targets, strings are stored as pointers, but that is not what zig expects
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[string],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_JOIN_WITH,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
StrToScalars => {
|
|
// Str.toScalars : Str -> List U32
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::List,
|
|
bitcode::STR_TO_SCALARS,
|
|
)
|
|
}
|
|
StrStartsWith => {
|
|
// Str.startsWith : Str, Str -> Bool
|
|
arguments!(string, prefix);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string, prefix],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_STARTS_WITH,
|
|
)
|
|
}
|
|
StrStartsWithScalar => {
|
|
// Str.startsWithScalar : Str, U32 -> Bool
|
|
arguments!(string, prefix);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[prefix],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_STARTS_WITH_SCALAR,
|
|
)
|
|
}
|
|
StrEndsWith => {
|
|
// Str.startsWith : Str, Str -> Bool
|
|
arguments!(string, prefix);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string, prefix],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_ENDS_WITH,
|
|
)
|
|
}
|
|
StrToNum => {
|
|
// Str.toNum : Str -> Result (Num *) {}
|
|
arguments!(string);
|
|
|
|
let number_layout = match layout_interner.get_repr(layout) {
|
|
LayoutRepr::Struct(field_layouts) => field_layouts[0], // TODO: why is it sometimes a struct?
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
// match on the return layout to figure out which zig builtin we need
|
|
let intrinsic = match layout_interner.get_repr(number_layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width],
|
|
LayoutRepr::Builtin(Builtin::Float(float_width)) => {
|
|
&bitcode::STR_TO_FLOAT[float_width]
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let result = match env.target_info.ptr_width() {
|
|
PtrWidth::Bytes4 => {
|
|
let zig_function = env.module.get_function(intrinsic).unwrap();
|
|
let zig_function_type = zig_function.get_type();
|
|
|
|
match zig_function_type.get_return_type() {
|
|
Some(_) => call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
intrinsic,
|
|
),
|
|
None => {
|
|
let return_type_name = match layout_interner.get_repr(number_layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => {
|
|
int_width.type_name()
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Decimal) => {
|
|
// zig picks 128 for dec.RocDec
|
|
"i128"
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let return_type = zig_num_parse_result_type(env, return_type_name);
|
|
|
|
let zig_return_alloca = create_entry_block_alloca(
|
|
env,
|
|
parent,
|
|
return_type.into(),
|
|
"str_to_num",
|
|
);
|
|
|
|
let (a, b) =
|
|
pass_list_or_string_to_zig_32bit(env, string.into_struct_value());
|
|
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[zig_return_alloca.into(), a.into(), b.into()],
|
|
intrinsic,
|
|
);
|
|
|
|
let roc_return_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
)
|
|
.ptr_type(AddressSpace::default());
|
|
|
|
let roc_return_alloca = env.builder.build_pointer_cast(
|
|
zig_return_alloca,
|
|
roc_return_type,
|
|
"cast_to_roc",
|
|
);
|
|
|
|
load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
roc_return_alloca,
|
|
"str_to_num_result",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
PtrWidth::Bytes8 => {
|
|
let cc_return_by_pointer = match layout_interner.get_repr(number_layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => {
|
|
(int_width.stack_size() as usize > env.target_info.ptr_size())
|
|
.then_some(int_width.type_name())
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Decimal) => {
|
|
// zig picks 128 for dec.RocDec
|
|
Some("i128")
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(type_name) = cc_return_by_pointer {
|
|
let bitcode_return_type = zig_num_parse_result_type(env, type_name);
|
|
|
|
call_bitcode_fn_fixing_for_convention(
|
|
env,
|
|
layout_interner,
|
|
bitcode_return_type,
|
|
&[string],
|
|
layout,
|
|
intrinsic,
|
|
)
|
|
} else {
|
|
call_bitcode_fn(env, &[string], intrinsic)
|
|
}
|
|
}
|
|
};
|
|
|
|
// zig passes the result as a packed integer sometimes, instead of a struct. So we cast if needed.
|
|
// We check the type as expected in an argument position, since that is how we actually will use it.
|
|
let expected_type =
|
|
argument_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
|
|
let actual_type = result.get_type();
|
|
|
|
if expected_type != actual_type {
|
|
complex_bitcast_check_size(env, result, expected_type, "str_to_num_cast")
|
|
} else {
|
|
result
|
|
}
|
|
}
|
|
StrFromInt => {
|
|
// Str.fromInt : Int -> Str
|
|
debug_assert_eq!(args.len(), 1);
|
|
|
|
let (int, int_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
let int = int.into_int_value();
|
|
|
|
let int_width = match layout_interner.get_repr(int_layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[int.into()],
|
|
BitcodeReturns::Str,
|
|
&bitcode::STR_FROM_INT[int_width],
|
|
)
|
|
}
|
|
StrFromFloat => {
|
|
// Str.fromFloat : Float * -> Str
|
|
debug_assert_eq!(args.len(), 1);
|
|
|
|
let (float, float_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
|
|
let float_width = match layout_interner.get_repr(float_layout) {
|
|
LayoutRepr::Builtin(Builtin::Float(float_width)) => float_width,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[float],
|
|
BitcodeReturns::Str,
|
|
&bitcode::STR_FROM_FLOAT[float_width],
|
|
)
|
|
}
|
|
StrFromUtf8Range => {
|
|
let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap();
|
|
let result_ptr = env
|
|
.builder
|
|
.build_alloca(result_type, "alloca_utf8_validate_bytes_result");
|
|
|
|
match env.target_info.ptr_width() {
|
|
PtrWidth::Bytes4 => {
|
|
arguments!(list, start, count);
|
|
let (a, b) = pass_list_or_string_to_zig_32bit(env, list.into_struct_value());
|
|
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[
|
|
result_ptr.into(),
|
|
a.into(),
|
|
b.into(),
|
|
start,
|
|
count,
|
|
pass_update_mode(env, update_mode),
|
|
],
|
|
bitcode::STR_FROM_UTF8_RANGE,
|
|
);
|
|
}
|
|
PtrWidth::Bytes8 => {
|
|
arguments!(_list, start, count);
|
|
|
|
// we use the symbol here instead
|
|
let list = args[0];
|
|
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[
|
|
result_ptr.into(),
|
|
list_symbol_to_c_abi(env, scope, list).into(),
|
|
start,
|
|
count,
|
|
pass_update_mode(env, update_mode),
|
|
],
|
|
bitcode::STR_FROM_UTF8_RANGE,
|
|
);
|
|
}
|
|
}
|
|
|
|
crate::llvm::build_str::decode_from_utf8_result(env, layout_interner, result_ptr)
|
|
}
|
|
StrToUtf8 => {
|
|
// Str.fromInt : Str -> List U8
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::List,
|
|
bitcode::STR_TO_UTF8,
|
|
)
|
|
}
|
|
StrRepeat => {
|
|
// Str.repeat : Str, Nat -> Str
|
|
arguments!(string, count);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[count],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_REPEAT,
|
|
)
|
|
}
|
|
StrSplit => {
|
|
// Str.split : Str, Str -> List Str
|
|
arguments!(string, delimiter);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string, delimiter],
|
|
&[],
|
|
BitcodeReturns::List,
|
|
bitcode::STR_SPLIT,
|
|
)
|
|
}
|
|
StrIsEmpty => {
|
|
// Str.isEmpty : Str -> Str
|
|
arguments!(string);
|
|
|
|
// the builtin will always return an u64
|
|
let length = call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_NUMBER_OF_BYTES,
|
|
)
|
|
.into_int_value();
|
|
|
|
// cast to the appropriate usize of the current build
|
|
let byte_count =
|
|
env.builder
|
|
.build_int_cast_sign_flag(length, env.ptr_int(), false, "len_as_usize");
|
|
|
|
let is_zero = env.builder.build_int_compare(
|
|
IntPredicate::EQ,
|
|
byte_count,
|
|
env.ptr_int().const_zero(),
|
|
"str_len_is_zero",
|
|
);
|
|
BasicValueEnum::IntValue(is_zero)
|
|
}
|
|
StrCountGraphemes => {
|
|
// Str.countGraphemes : Str -> Nat
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
|
)
|
|
}
|
|
StrGetScalarUnsafe => {
|
|
// Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 }
|
|
arguments!(string, index);
|
|
|
|
use roc_target::OperatingSystem::*;
|
|
match env.target_info.operating_system {
|
|
Windows => {
|
|
let return_type = env.context.struct_type(
|
|
&[env.ptr_int().into(), env.context.i32_type().into()],
|
|
false,
|
|
);
|
|
|
|
let result = env.builder.build_alloca(return_type, "result");
|
|
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[result.into(), string, index],
|
|
bitcode::STR_GET_SCALAR_UNSAFE,
|
|
);
|
|
|
|
let return_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
);
|
|
let cast_result = env.builder.build_pointer_cast(
|
|
result,
|
|
return_type.ptr_type(AddressSpace::default()),
|
|
"cast",
|
|
);
|
|
|
|
env.builder
|
|
.new_build_load(return_type, cast_result, "load_result")
|
|
}
|
|
Unix => {
|
|
let result = call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[index],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_GET_SCALAR_UNSAFE,
|
|
);
|
|
|
|
// on 32-bit targets, zig bitpacks the struct
|
|
match env.target_info.ptr_width() {
|
|
PtrWidth::Bytes8 => result,
|
|
PtrWidth::Bytes4 => {
|
|
let to = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
);
|
|
complex_bitcast_check_size(env, result, to, "to_roc_record")
|
|
}
|
|
}
|
|
}
|
|
Wasi => unimplemented!(),
|
|
}
|
|
}
|
|
StrCountUtf8Bytes => {
|
|
// Str.countUtf8Bytes : Str -> Nat
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_COUNT_UTF8_BYTES,
|
|
)
|
|
}
|
|
StrGetCapacity => {
|
|
// Str.capacity : Str -> Nat
|
|
arguments!(string);
|
|
|
|
call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY)
|
|
}
|
|
StrSubstringUnsafe => {
|
|
// Str.substringUnsafe : Str, Nat, Nat -> Str
|
|
arguments!(string, start, length);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[start, length],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_SUBSTRING_UNSAFE,
|
|
)
|
|
}
|
|
StrReserve => {
|
|
// Str.reserve : Str, Nat -> Str
|
|
arguments!(string, capacity);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[capacity],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_RESERVE,
|
|
)
|
|
}
|
|
StrReleaseExcessCapacity => {
|
|
// Str.releaseExcessCapacity: Str -> Str
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_RELEASE_EXCESS_CAPACITY,
|
|
)
|
|
}
|
|
StrAppendScalar => {
|
|
// Str.appendScalar : Str, U32 -> Str
|
|
arguments!(string, capacity);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[capacity],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_APPEND_SCALAR,
|
|
)
|
|
}
|
|
StrTrim => {
|
|
// Str.trim : Str -> Str
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(env, &[string], &[], BitcodeReturns::Str, bitcode::STR_TRIM)
|
|
}
|
|
StrTrimLeft => {
|
|
// Str.trim : Str -> Str
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_TRIM_LEFT,
|
|
)
|
|
}
|
|
StrTrimRight => {
|
|
// Str.trim : Str -> Str
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_TRIM_RIGHT,
|
|
)
|
|
}
|
|
StrWithCapacity => {
|
|
// Str.withCapacity : Nat -> Str
|
|
arguments!(str_len);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[str_len],
|
|
BitcodeReturns::Str,
|
|
bitcode::STR_WITH_CAPACITY,
|
|
)
|
|
}
|
|
StrGraphemes => {
|
|
// Str.graphemes : Str -> List Str
|
|
arguments!(string);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[string],
|
|
&[],
|
|
BitcodeReturns::List,
|
|
bitcode::STR_GRAPHEMES,
|
|
)
|
|
}
|
|
ListLen => {
|
|
// List.len : List * -> Nat
|
|
arguments!(list);
|
|
|
|
list_len(env.builder, list.into_struct_value()).into()
|
|
}
|
|
ListGetCapacity => {
|
|
// List.capacity: List a -> Nat
|
|
arguments!(list);
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::LIST_CAPACITY,
|
|
)
|
|
}
|
|
ListWithCapacity => {
|
|
// List.withCapacity : Nat -> List a
|
|
arguments!(list_len);
|
|
|
|
let result_layout = layout;
|
|
list_with_capacity(
|
|
env,
|
|
layout_interner,
|
|
list_len.into_int_value(),
|
|
list_element_layout!(layout_interner, result_layout),
|
|
)
|
|
}
|
|
ListConcat => {
|
|
debug_assert_eq!(args.len(), 2);
|
|
|
|
let (first_list, list_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
|
|
let second_list = scope.load_symbol(&args[1]);
|
|
|
|
let element_layout = list_element_layout!(layout_interner, list_layout);
|
|
|
|
list_concat(
|
|
env,
|
|
layout_interner,
|
|
first_list,
|
|
second_list,
|
|
element_layout,
|
|
)
|
|
}
|
|
ListAppendUnsafe => {
|
|
// List.appendUnsafe : List elem, elem -> List elem
|
|
debug_assert_eq!(args.len(), 2);
|
|
|
|
let original_wrapper = scope.load_symbol(&args[0]).into_struct_value();
|
|
let (elem, elem_layout) = scope.load_symbol_and_layout(&args[1]);
|
|
|
|
list_append_unsafe(env, layout_interner, original_wrapper, elem, elem_layout)
|
|
}
|
|
ListPrepend => {
|
|
// List.prepend : List elem, elem -> List elem
|
|
debug_assert_eq!(args.len(), 2);
|
|
|
|
let original_wrapper = scope.load_symbol(&args[0]).into_struct_value();
|
|
let (elem, elem_layout) = scope.load_symbol_and_layout(&args[1]);
|
|
|
|
list_prepend(env, layout_interner, original_wrapper, elem, elem_layout)
|
|
}
|
|
ListReserve => {
|
|
// List.reserve : List elem, Nat -> List elem
|
|
debug_assert_eq!(args.len(), 2);
|
|
|
|
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
let element_layout = list_element_layout!(layout_interner, list_layout);
|
|
let spare = scope.load_symbol(&args[1]);
|
|
|
|
list_reserve(
|
|
env,
|
|
layout_interner,
|
|
list,
|
|
spare,
|
|
element_layout,
|
|
update_mode,
|
|
)
|
|
}
|
|
ListReleaseExcessCapacity => {
|
|
// List.releaseExcessCapacity: List elem -> List elem
|
|
debug_assert_eq!(args.len(), 1);
|
|
|
|
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
let element_layout = list_element_layout!(layout_interner, list_layout);
|
|
|
|
list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode)
|
|
}
|
|
ListSwap => {
|
|
// List.swap : List elem, Nat, Nat -> List elem
|
|
debug_assert_eq!(args.len(), 3);
|
|
|
|
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
let original_wrapper = list.into_struct_value();
|
|
|
|
let index_1 = scope.load_symbol(&args[1]);
|
|
let index_2 = scope.load_symbol(&args[2]);
|
|
|
|
let element_layout = list_element_layout!(layout_interner, list_layout);
|
|
list_swap(
|
|
env,
|
|
layout_interner,
|
|
original_wrapper,
|
|
index_1.into_int_value(),
|
|
index_2.into_int_value(),
|
|
element_layout,
|
|
update_mode,
|
|
)
|
|
}
|
|
ListSublist => {
|
|
debug_assert_eq!(args.len(), 3);
|
|
|
|
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
let original_wrapper = list.into_struct_value();
|
|
|
|
let start = scope.load_symbol(&args[1]);
|
|
let len = scope.load_symbol(&args[2]);
|
|
|
|
let element_layout = list_element_layout!(layout_interner, list_layout);
|
|
list_sublist(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
original_wrapper,
|
|
start.into_int_value(),
|
|
len.into_int_value(),
|
|
element_layout,
|
|
)
|
|
}
|
|
ListDropAt => {
|
|
// List.dropAt : List elem, Nat -> List elem
|
|
debug_assert_eq!(args.len(), 2);
|
|
|
|
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
let original_wrapper = list.into_struct_value();
|
|
|
|
let count = scope.load_symbol(&args[1]);
|
|
|
|
let element_layout = list_element_layout!(layout_interner, list_layout);
|
|
list_drop_at(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
original_wrapper,
|
|
count.into_int_value(),
|
|
element_layout,
|
|
)
|
|
}
|
|
StrGetUnsafe => {
|
|
// Str.getUnsafe : Str, Nat -> u8
|
|
arguments!(wrapper_struct, elem_index);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[wrapper_struct],
|
|
&[elem_index],
|
|
BitcodeReturns::Basic,
|
|
bitcode::STR_GET_UNSAFE,
|
|
)
|
|
}
|
|
ListGetUnsafe => {
|
|
// List.getUnsafe : List elem, Nat -> elem
|
|
arguments_with_layouts!((wrapper_struct, list_layout), (element_index, _l));
|
|
|
|
list_get_unsafe(
|
|
env,
|
|
layout_interner,
|
|
list_element_layout!(layout_interner, list_layout),
|
|
element_index.into_int_value(),
|
|
wrapper_struct.into_struct_value(),
|
|
)
|
|
}
|
|
ListReplaceUnsafe => {
|
|
arguments_with_layouts!((list, _l1), (index, _l2), (element, element_layout));
|
|
|
|
list_replace_unsafe(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
list,
|
|
index.into_int_value(),
|
|
element,
|
|
element_layout,
|
|
update_mode,
|
|
)
|
|
}
|
|
ListIsUnique => {
|
|
// List.isUnique : List a -> Bool
|
|
arguments!(list);
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[],
|
|
BitcodeReturns::Basic,
|
|
bitcode::LIST_IS_UNIQUE,
|
|
)
|
|
}
|
|
NumToStr => {
|
|
// Num.toStr : Num a -> Str
|
|
arguments_with_layouts!((num, num_layout));
|
|
|
|
match layout_interner.get_repr(num_layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => {
|
|
let int = num.into_int_value();
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[int.into()],
|
|
BitcodeReturns::Str,
|
|
&bitcode::STR_FROM_INT[int_width],
|
|
)
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Float(_float_width)) => {
|
|
let (float, float_layout) = scope.load_symbol_and_layout(&args[0]);
|
|
|
|
let float_width = match layout_interner.get_repr(float_layout) {
|
|
LayoutRepr::Builtin(Builtin::Float(float_width)) => float_width,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[float],
|
|
BitcodeReturns::Str,
|
|
&bitcode::STR_FROM_FLOAT[float_width],
|
|
)
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Decimal) => dec_to_str(env, num),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
NumAbs
|
|
| NumNeg
|
|
| NumRound
|
|
| NumSqrtUnchecked
|
|
| NumLogUnchecked
|
|
| NumSin
|
|
| NumCos
|
|
| NumCeiling
|
|
| NumFloor
|
|
| NumToFrac
|
|
| NumIsNan
|
|
| NumIsInfinite
|
|
| NumIsFinite
|
|
| NumAtan
|
|
| NumAcos
|
|
| NumAsin
|
|
| NumToIntChecked
|
|
| NumCountLeadingZeroBits
|
|
| NumCountTrailingZeroBits
|
|
| NumCountOneBits => {
|
|
arguments_with_layouts!((arg, arg_layout));
|
|
|
|
match layout_interner.get_repr(arg_layout) {
|
|
LayoutRepr::Builtin(arg_builtin) => {
|
|
use roc_mono::layout::Builtin::*;
|
|
|
|
match arg_builtin {
|
|
Int(int_width) => {
|
|
let int_type = convert::int_type_from_int_width(env, int_width);
|
|
build_int_unary_op(
|
|
env,
|
|
layout_interner,
|
|
parent,
|
|
arg.into_int_value(),
|
|
int_width,
|
|
int_type,
|
|
op,
|
|
layout,
|
|
)
|
|
}
|
|
Float(float_width) => build_float_unary_op(
|
|
env,
|
|
layout_interner,
|
|
layout,
|
|
arg.into_float_value(),
|
|
op,
|
|
float_width,
|
|
),
|
|
_ => {
|
|
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout);
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
unreachable!(
|
|
"Compiler bug: tried to run numeric operation {:?} on invalid layout: {:?}",
|
|
op, arg_layout
|
|
);
|
|
}
|
|
}
|
|
}
|
|
NumBytesToU16 => {
|
|
arguments!(list, position);
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[position],
|
|
BitcodeReturns::Basic,
|
|
bitcode::NUM_BYTES_TO_U16,
|
|
)
|
|
}
|
|
NumBytesToU32 => {
|
|
arguments!(list, position);
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[position],
|
|
BitcodeReturns::Basic,
|
|
bitcode::NUM_BYTES_TO_U32,
|
|
)
|
|
}
|
|
NumBytesToU64 => {
|
|
arguments!(list, position);
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[position],
|
|
BitcodeReturns::Basic,
|
|
bitcode::NUM_BYTES_TO_U64,
|
|
)
|
|
}
|
|
NumBytesToU128 => {
|
|
arguments!(list, position);
|
|
|
|
call_list_bitcode_fn(
|
|
env,
|
|
&[list.into_struct_value()],
|
|
&[position],
|
|
BitcodeReturns::Basic,
|
|
bitcode::NUM_BYTES_TO_U128,
|
|
)
|
|
}
|
|
NumCompare => {
|
|
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
|
|
|
use inkwell::FloatPredicate;
|
|
match (
|
|
layout_interner.get_repr(lhs_layout),
|
|
layout_interner.get_repr(rhs_layout),
|
|
) {
|
|
(LayoutRepr::Builtin(lhs_builtin), LayoutRepr::Builtin(rhs_builtin))
|
|
if lhs_builtin == rhs_builtin =>
|
|
{
|
|
use roc_mono::layout::Builtin::*;
|
|
|
|
let tag_eq = env.context.i8_type().const_int(0_u64, false);
|
|
let tag_gt = env.context.i8_type().const_int(1_u64, false);
|
|
let tag_lt = env.context.i8_type().const_int(2_u64, false);
|
|
|
|
match lhs_builtin {
|
|
Int(int_width) => {
|
|
let are_equal = env.builder.build_int_compare(
|
|
IntPredicate::EQ,
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
"int_eq",
|
|
);
|
|
|
|
let predicate = if int_width.is_signed() {
|
|
IntPredicate::SLT
|
|
} else {
|
|
IntPredicate::ULT
|
|
};
|
|
|
|
let is_less_than = env.builder.build_int_compare(
|
|
predicate,
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
"int_compare",
|
|
);
|
|
|
|
let step1 =
|
|
env.builder
|
|
.build_select(is_less_than, tag_lt, tag_gt, "lt_or_gt");
|
|
|
|
env.builder.build_select(
|
|
are_equal,
|
|
tag_eq,
|
|
step1.into_int_value(),
|
|
"lt_or_gt",
|
|
)
|
|
}
|
|
Float(_) => {
|
|
let are_equal = env.builder.build_float_compare(
|
|
FloatPredicate::OEQ,
|
|
lhs_arg.into_float_value(),
|
|
rhs_arg.into_float_value(),
|
|
"float_eq",
|
|
);
|
|
let is_less_than = env.builder.build_float_compare(
|
|
FloatPredicate::OLT,
|
|
lhs_arg.into_float_value(),
|
|
rhs_arg.into_float_value(),
|
|
"float_compare",
|
|
);
|
|
|
|
let step1 =
|
|
env.builder
|
|
.build_select(is_less_than, tag_lt, tag_gt, "lt_or_gt");
|
|
|
|
env.builder.build_select(
|
|
are_equal,
|
|
tag_eq,
|
|
step1.into_int_value(),
|
|
"lt_or_gt",
|
|
)
|
|
}
|
|
|
|
_ => {
|
|
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout);
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid layouts. The 2 layouts were: ({:?}) and ({:?})", op, lhs_layout, rhs_layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
|
|
| NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivFrac
|
|
| NumDivTruncUnchecked | NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap
|
|
| NumSubChecked | NumSubSaturated | NumMulWrap | NumMulSaturated | NumMulChecked => {
|
|
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
|
|
|
build_num_binop(
|
|
env,
|
|
layout_interner,
|
|
parent,
|
|
lhs_arg,
|
|
lhs_layout,
|
|
rhs_arg,
|
|
rhs_layout,
|
|
layout,
|
|
op,
|
|
)
|
|
}
|
|
NumBitwiseAnd | NumBitwiseOr | NumBitwiseXor => {
|
|
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
|
|
|
debug_assert_eq!(lhs_layout, rhs_layout);
|
|
let int_width = intwidth_from_layout(lhs_layout);
|
|
|
|
build_int_binop(
|
|
env,
|
|
parent,
|
|
int_width,
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
op,
|
|
)
|
|
}
|
|
NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => {
|
|
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
|
|
|
let int_width = intwidth_from_layout(lhs_layout);
|
|
|
|
debug_assert_eq!(rhs_layout, Layout::U8);
|
|
let rhs_arg = if rhs_layout != lhs_layout {
|
|
// LLVM shift intrinsics expect the left and right sides to have the same type, so
|
|
// here we cast up `rhs` to the lhs type. Since the rhs was checked to be a U8,
|
|
// this cast isn't lossy.
|
|
let rhs_arg = env.builder.build_int_cast(
|
|
rhs_arg.into_int_value(),
|
|
lhs_arg.get_type().into_int_type(),
|
|
"cast_for_shift",
|
|
);
|
|
rhs_arg.into()
|
|
} else {
|
|
rhs_arg
|
|
};
|
|
|
|
build_int_binop(
|
|
env,
|
|
parent,
|
|
int_width,
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
op,
|
|
)
|
|
}
|
|
NumIntCast => {
|
|
arguments!(arg);
|
|
|
|
let to = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
|
|
.into_int_type();
|
|
let to_signed = intwidth_from_layout(layout).is_signed();
|
|
|
|
env.builder
|
|
.build_int_cast_sign_flag(arg.into_int_value(), to, to_signed, "inc_cast")
|
|
.into()
|
|
}
|
|
NumToFloatCast => {
|
|
arguments_with_layouts!((arg, arg_layout));
|
|
|
|
match layout_interner.get_repr(arg_layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(width)) => {
|
|
// Converting from int to float
|
|
let int_val = arg.into_int_value();
|
|
let dest = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
)
|
|
.into_float_type();
|
|
|
|
if width.is_signed() {
|
|
env.builder
|
|
.build_signed_int_to_float(int_val, dest, "signed_int_to_float")
|
|
.into()
|
|
} else {
|
|
env.builder
|
|
.build_unsigned_int_to_float(int_val, dest, "unsigned_int_to_float")
|
|
.into()
|
|
}
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Float(_)) => {
|
|
// Converting from float to float - e.g. F64 to F32, or vice versa
|
|
let dest = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
)
|
|
.into_float_type();
|
|
|
|
env.builder
|
|
.build_float_cast(arg.into_float_value(), dest, "cast_float_to_float")
|
|
.into()
|
|
}
|
|
LayoutRepr::Builtin(Builtin::Decimal) => {
|
|
todo!("Support converting Dec values to floats.");
|
|
}
|
|
other => {
|
|
unreachable!("Tried to do a float cast to non-float layout {:?}", other);
|
|
}
|
|
}
|
|
}
|
|
NumToFloatChecked => {
|
|
// NOTE: There's a NumToIntChecked implementation above,
|
|
// which could be useful to look at when implementing this.
|
|
todo!("implement checked float conversion");
|
|
}
|
|
I128OfDec => {
|
|
arguments!(dec);
|
|
dec_to_i128(env, dec)
|
|
}
|
|
Eq => {
|
|
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
|
|
|
generic_eq(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
lhs_arg,
|
|
rhs_arg,
|
|
lhs_layout,
|
|
rhs_layout,
|
|
)
|
|
}
|
|
NotEq => {
|
|
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
|
|
|
|
generic_neq(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
lhs_arg,
|
|
rhs_arg,
|
|
lhs_layout,
|
|
rhs_layout,
|
|
)
|
|
}
|
|
And => {
|
|
// The (&&) operator
|
|
arguments!(lhs_arg, rhs_arg);
|
|
|
|
let bool_val = env.builder.build_and(
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
"bool_and",
|
|
);
|
|
|
|
BasicValueEnum::IntValue(bool_val)
|
|
}
|
|
Or => {
|
|
// The (||) operator
|
|
arguments!(lhs_arg, rhs_arg);
|
|
|
|
let bool_val = env.builder.build_or(
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
"bool_or",
|
|
);
|
|
|
|
BasicValueEnum::IntValue(bool_val)
|
|
}
|
|
Not => {
|
|
// The (!) operator
|
|
arguments!(arg);
|
|
|
|
let bool_val = env.builder.build_not(arg.into_int_value(), "bool_not");
|
|
BasicValueEnum::IntValue(bool_val)
|
|
}
|
|
Hash => {
|
|
unimplemented!()
|
|
}
|
|
|
|
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => {
|
|
unreachable!("these are higher order, and are handled elsewhere")
|
|
}
|
|
|
|
BoxExpr | UnboxExpr => {
|
|
unreachable!("The {:?} operation is turned into mono Expr", op)
|
|
}
|
|
|
|
PtrCast => {
|
|
arguments!(data_ptr);
|
|
|
|
let target_type =
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
|
|
.into_pointer_type();
|
|
|
|
debug_assert!(data_ptr.is_pointer_value());
|
|
|
|
env.builder
|
|
.build_pointer_cast(data_ptr.into_pointer_value(), target_type, "ptr_cast")
|
|
.into()
|
|
}
|
|
|
|
PtrWrite | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr
|
|
| RefCountDecDataPtr => {
|
|
unreachable!("Not used in LLVM backend: {:?}", op);
|
|
}
|
|
|
|
RefCountIsUnique => {
|
|
arguments_with_layouts!((data_ptr, data_layout));
|
|
|
|
let ptr = env.builder.build_pointer_cast(
|
|
data_ptr.into_pointer_value(),
|
|
env.context.i8_type().ptr_type(AddressSpace::default()),
|
|
"cast_to_i8_ptr",
|
|
);
|
|
|
|
let value_ptr = match layout_interner.get_repr(data_layout) {
|
|
LayoutRepr::Union(union_layout)
|
|
if union_layout.stores_tag_id_in_pointer(env.target_info) =>
|
|
{
|
|
tag_pointer_clear_tag_id(env, ptr)
|
|
}
|
|
_ => ptr,
|
|
};
|
|
|
|
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
|
|
|
|
BasicValueEnum::IntValue(refcount_ptr.is_1(env))
|
|
}
|
|
|
|
Unreachable => {
|
|
match RocReturn::from_layout(layout_interner, layout_interner.get_repr(layout)) {
|
|
RocReturn::Return => {
|
|
let basic_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
);
|
|
basic_type.const_zero()
|
|
}
|
|
RocReturn::ByPointer => {
|
|
let basic_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
);
|
|
let ptr = env.builder.build_alloca(basic_type, "unreachable_alloca");
|
|
env.builder.build_store(ptr, basic_type.const_zero());
|
|
|
|
ptr.into()
|
|
}
|
|
}
|
|
}
|
|
DictPseudoSeed => {
|
|
// Dict.pseudoSeed : {} -> u64
|
|
|
|
call_bitcode_fn(env, &[], bitcode::UTILS_DICT_PSEUDO_SEED)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn intwidth_from_layout(layout: InLayout) -> IntWidth {
|
|
layout.to_int_width()
|
|
}
|
|
|
|
fn build_int_binop<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
parent: FunctionValue<'ctx>,
|
|
int_width: IntWidth,
|
|
lhs: IntValue<'ctx>,
|
|
rhs: IntValue<'ctx>,
|
|
op: LowLevel,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use inkwell::IntPredicate::*;
|
|
use roc_module::low_level::LowLevel::*;
|
|
|
|
let bd = env.builder;
|
|
|
|
match op {
|
|
NumAdd => {
|
|
let result = env
|
|
.call_intrinsic(
|
|
&LLVM_ADD_WITH_OVERFLOW[int_width],
|
|
&[lhs.into(), rhs.into()],
|
|
)
|
|
.into_struct_value();
|
|
|
|
throw_on_overflow(env, parent, result, "integer addition overflowed!")
|
|
}
|
|
NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(),
|
|
NumAddChecked => env.call_intrinsic(
|
|
&LLVM_ADD_WITH_OVERFLOW[int_width],
|
|
&[lhs.into(), rhs.into()],
|
|
),
|
|
NumAddSaturated => {
|
|
env.call_intrinsic(&LLVM_ADD_SATURATED[int_width], &[lhs.into(), rhs.into()])
|
|
}
|
|
NumSub => {
|
|
let result = env
|
|
.call_intrinsic(
|
|
&LLVM_SUB_WITH_OVERFLOW[int_width],
|
|
&[lhs.into(), rhs.into()],
|
|
)
|
|
.into_struct_value();
|
|
|
|
throw_on_overflow(env, parent, result, "integer subtraction overflowed!")
|
|
}
|
|
NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(),
|
|
NumSubChecked => env.call_intrinsic(
|
|
&LLVM_SUB_WITH_OVERFLOW[int_width],
|
|
&[lhs.into(), rhs.into()],
|
|
),
|
|
NumSubSaturated => {
|
|
env.call_intrinsic(&LLVM_SUB_SATURATED[int_width], &[lhs.into(), rhs.into()])
|
|
}
|
|
NumMul => {
|
|
let result = env
|
|
.call_intrinsic(
|
|
&LLVM_MUL_WITH_OVERFLOW[int_width],
|
|
&[lhs.into(), rhs.into()],
|
|
)
|
|
.into_struct_value();
|
|
|
|
throw_on_overflow(env, parent, result, "integer multiplication overflowed!")
|
|
}
|
|
NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(),
|
|
NumMulSaturated => call_bitcode_fn(
|
|
env,
|
|
&[lhs.into(), rhs.into()],
|
|
&bitcode::NUM_MUL_SATURATED_INT[int_width],
|
|
),
|
|
NumMulChecked => env.call_intrinsic(
|
|
&LLVM_MUL_WITH_OVERFLOW[int_width],
|
|
&[lhs.into(), rhs.into()],
|
|
),
|
|
NumGt => {
|
|
if int_width.is_signed() {
|
|
bd.build_int_compare(SGT, lhs, rhs, "gt_int").into()
|
|
} else {
|
|
bd.build_int_compare(UGT, lhs, rhs, "gt_uint").into()
|
|
}
|
|
}
|
|
NumGte => {
|
|
if int_width.is_signed() {
|
|
bd.build_int_compare(SGE, lhs, rhs, "gte_int").into()
|
|
} else {
|
|
bd.build_int_compare(UGE, lhs, rhs, "gte_uint").into()
|
|
}
|
|
}
|
|
NumLt => {
|
|
if int_width.is_signed() {
|
|
bd.build_int_compare(SLT, lhs, rhs, "lt_int").into()
|
|
} else {
|
|
bd.build_int_compare(ULT, lhs, rhs, "lt_uint").into()
|
|
}
|
|
}
|
|
NumLte => {
|
|
if int_width.is_signed() {
|
|
bd.build_int_compare(SLE, lhs, rhs, "lte_int").into()
|
|
} else {
|
|
bd.build_int_compare(ULE, lhs, rhs, "lte_uint").into()
|
|
}
|
|
}
|
|
NumRemUnchecked => {
|
|
if int_width.is_signed() {
|
|
bd.build_int_signed_rem(lhs, rhs, "rem_int").into()
|
|
} else {
|
|
bd.build_int_unsigned_rem(lhs, rhs, "rem_uint").into()
|
|
}
|
|
}
|
|
NumIsMultipleOf => {
|
|
// this builds the following construct
|
|
//
|
|
// if (rhs == 0 || rhs == -1) {
|
|
// // lhs is a multiple of rhs iff
|
|
// //
|
|
// // - rhs == -1
|
|
// // - both rhs and lhs are 0
|
|
// //
|
|
// // the -1 case is important for overflow reasons `isize::MIN % -1` crashes in rust
|
|
// (rhs == -1) || (lhs == 0)
|
|
// } else {
|
|
// let rem = lhs % rhs;
|
|
// rem == 0
|
|
// }
|
|
//
|
|
// NOTE we'd like the branches to be swapped for better branch prediction,
|
|
// but llvm normalizes to the above ordering in -O3
|
|
let zero = rhs.get_type().const_zero();
|
|
let neg_1 = rhs.get_type().const_int(-1i64 as u64, false);
|
|
let is_signed = int_width.is_signed();
|
|
|
|
let special_block = env.context.append_basic_block(parent, "special_block");
|
|
let default_block = env.context.append_basic_block(parent, "default_block");
|
|
let cont_block = env.context.append_basic_block(parent, "branchcont");
|
|
|
|
if is_signed {
|
|
bd.build_switch(
|
|
rhs,
|
|
default_block,
|
|
&[(zero, special_block), (neg_1, special_block)],
|
|
)
|
|
} else {
|
|
bd.build_switch(rhs, default_block, &[(zero, special_block)])
|
|
};
|
|
|
|
let condition_rem = {
|
|
bd.position_at_end(default_block);
|
|
|
|
let rem = if is_signed {
|
|
bd.build_int_signed_rem(lhs, rhs, "int_rem")
|
|
} else {
|
|
bd.build_int_unsigned_rem(lhs, rhs, "uint_rem")
|
|
};
|
|
let result = bd.build_int_compare(IntPredicate::EQ, rem, zero, "is_zero_rem");
|
|
|
|
bd.build_unconditional_branch(cont_block);
|
|
result
|
|
};
|
|
|
|
let condition_special = {
|
|
bd.position_at_end(special_block);
|
|
|
|
let is_zero = bd.build_int_compare(IntPredicate::EQ, lhs, zero, "is_zero_lhs");
|
|
|
|
let result = if is_signed {
|
|
let is_neg_one =
|
|
bd.build_int_compare(IntPredicate::EQ, rhs, neg_1, "is_neg_one_rhs");
|
|
|
|
bd.build_or(is_neg_one, is_zero, "cond")
|
|
} else {
|
|
is_zero
|
|
};
|
|
|
|
bd.build_unconditional_branch(cont_block);
|
|
|
|
result
|
|
};
|
|
|
|
{
|
|
bd.position_at_end(cont_block);
|
|
|
|
let phi = bd.build_phi(env.context.bool_type(), "branch");
|
|
|
|
phi.add_incoming(&[
|
|
(&condition_rem, default_block),
|
|
(&condition_special, special_block),
|
|
]);
|
|
|
|
phi.as_basic_value()
|
|
}
|
|
}
|
|
NumPowInt => call_bitcode_fn(
|
|
env,
|
|
&[lhs.into(), rhs.into()],
|
|
&bitcode::NUM_POW_INT[int_width],
|
|
),
|
|
NumDivTruncUnchecked => {
|
|
if int_width.is_signed() {
|
|
bd.build_int_signed_div(lhs, rhs, "div_int").into()
|
|
} else {
|
|
bd.build_int_unsigned_div(lhs, rhs, "div_uint").into()
|
|
}
|
|
}
|
|
NumDivCeilUnchecked => call_bitcode_fn(
|
|
env,
|
|
&[lhs.into(), rhs.into()],
|
|
&bitcode::NUM_DIV_CEIL[int_width],
|
|
),
|
|
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
|
|
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
|
|
NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(),
|
|
NumShiftLeftBy => bd.build_left_shift(lhs, rhs, "int_shift_left").into(),
|
|
NumShiftRightBy => bd
|
|
.build_right_shift(lhs, rhs, true, "int_shift_right")
|
|
.into(),
|
|
NumShiftRightZfBy => bd
|
|
.build_right_shift(lhs, rhs, false, "int_shift_right_zf")
|
|
.into(),
|
|
|
|
_ => {
|
|
unreachable!("Unrecognized int binary operation: {:?}", op);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn build_num_binop<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
parent: FunctionValue<'ctx>,
|
|
lhs_arg: BasicValueEnum<'ctx>,
|
|
lhs_layout: InLayout<'a>,
|
|
rhs_arg: BasicValueEnum<'ctx>,
|
|
rhs_layout: InLayout<'a>,
|
|
return_layout: InLayout<'a>,
|
|
op: LowLevel,
|
|
) -> BasicValueEnum<'ctx> {
|
|
match (
|
|
layout_interner.get_repr(lhs_layout),
|
|
layout_interner.get_repr(rhs_layout),
|
|
) {
|
|
(LayoutRepr::Builtin(lhs_builtin), LayoutRepr::Builtin(rhs_builtin))
|
|
if lhs_builtin == rhs_builtin =>
|
|
{
|
|
use roc_mono::layout::Builtin::*;
|
|
|
|
match lhs_builtin {
|
|
Int(int_width) => build_int_binop(
|
|
env,
|
|
parent,
|
|
int_width,
|
|
lhs_arg.into_int_value(),
|
|
rhs_arg.into_int_value(),
|
|
op,
|
|
),
|
|
|
|
Float(float_width) => build_float_binop(
|
|
env,
|
|
float_width,
|
|
lhs_arg.into_float_value(),
|
|
rhs_arg.into_float_value(),
|
|
op,
|
|
),
|
|
|
|
Decimal => build_dec_binop(
|
|
env,
|
|
layout_interner,
|
|
parent,
|
|
lhs_arg,
|
|
rhs_arg,
|
|
return_layout,
|
|
op,
|
|
),
|
|
_ => {
|
|
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout);
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid layouts. The 2 layouts were: ({:?}) and ({:?})", op, lhs_layout, rhs_layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_float_binop<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
float_width: FloatWidth,
|
|
lhs: FloatValue<'ctx>,
|
|
rhs: FloatValue<'ctx>,
|
|
op: LowLevel,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use inkwell::FloatPredicate::*;
|
|
use roc_module::low_level::LowLevel::*;
|
|
|
|
let bd = env.builder;
|
|
|
|
match op {
|
|
NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(),
|
|
NumAddChecked => {
|
|
let context = env.context;
|
|
|
|
let result = bd.build_float_add(lhs, rhs, "add_float");
|
|
|
|
let is_finite =
|
|
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width])
|
|
.into_int_value();
|
|
let is_infinite = bd.build_not(is_finite, "negate");
|
|
|
|
let struct_type = context.struct_type(
|
|
&[context.f64_type().into(), context.bool_type().into()],
|
|
false,
|
|
);
|
|
|
|
let struct_value = {
|
|
let v1 = struct_type.const_zero();
|
|
let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap();
|
|
let v3 = bd
|
|
.build_insert_value(v2, is_infinite, 1, "set_is_infinite")
|
|
.unwrap();
|
|
|
|
v3.into_struct_value()
|
|
};
|
|
|
|
struct_value.into()
|
|
}
|
|
NumAddWrap => unreachable!("wrapping addition is not defined on floats"),
|
|
NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(),
|
|
NumSubChecked => {
|
|
let context = env.context;
|
|
|
|
let result = bd.build_float_sub(lhs, rhs, "sub_float");
|
|
|
|
let is_finite =
|
|
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width])
|
|
.into_int_value();
|
|
let is_infinite = bd.build_not(is_finite, "negate");
|
|
|
|
let struct_type = context.struct_type(
|
|
&[context.f64_type().into(), context.bool_type().into()],
|
|
false,
|
|
);
|
|
|
|
let struct_value = {
|
|
let v1 = struct_type.const_zero();
|
|
let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap();
|
|
let v3 = bd
|
|
.build_insert_value(v2, is_infinite, 1, "set_is_infinite")
|
|
.unwrap();
|
|
|
|
v3.into_struct_value()
|
|
};
|
|
|
|
struct_value.into()
|
|
}
|
|
NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"),
|
|
NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(),
|
|
NumMulSaturated => bd.build_float_mul(lhs, rhs, "mul_float").into(),
|
|
NumMulChecked => {
|
|
let context = env.context;
|
|
|
|
let result = bd.build_float_mul(lhs, rhs, "mul_float");
|
|
|
|
let is_finite =
|
|
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width])
|
|
.into_int_value();
|
|
let is_infinite = bd.build_not(is_finite, "negate");
|
|
|
|
let struct_type = context.struct_type(
|
|
&[context.f64_type().into(), context.bool_type().into()],
|
|
false,
|
|
);
|
|
|
|
let struct_value = {
|
|
let v1 = struct_type.const_zero();
|
|
let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap();
|
|
let v3 = bd
|
|
.build_insert_value(v2, is_infinite, 1, "set_is_infinite")
|
|
.unwrap();
|
|
|
|
v3.into_struct_value()
|
|
};
|
|
|
|
struct_value.into()
|
|
}
|
|
NumMulWrap => unreachable!("wrapping multiplication is not defined on floats"),
|
|
NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(),
|
|
NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(),
|
|
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),
|
|
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
|
|
NumDivFrac => bd.build_float_div(lhs, rhs, "div_float").into(),
|
|
NumPow => call_bitcode_fn(
|
|
env,
|
|
&[lhs.into(), rhs.into()],
|
|
&bitcode::NUM_POW[float_width],
|
|
),
|
|
_ => {
|
|
unreachable!("Unrecognized int binary operation: {:?}", op);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn throw_on_overflow<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
parent: FunctionValue<'ctx>,
|
|
result: StructValue<'ctx>, // of the form { value: T, has_overflowed: bool }
|
|
message: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let bd = env.builder;
|
|
let context = env.context;
|
|
|
|
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
|
|
|
|
let condition = bd.build_int_compare(
|
|
IntPredicate::EQ,
|
|
has_overflowed.into_int_value(),
|
|
context.bool_type().const_zero(),
|
|
"has_not_overflowed",
|
|
);
|
|
|
|
let then_block = context.append_basic_block(parent, "then_block");
|
|
let throw_block = context.append_basic_block(parent, "throw_block");
|
|
|
|
bd.build_conditional_branch(condition, then_block, throw_block);
|
|
|
|
bd.position_at_end(throw_block);
|
|
|
|
throw_internal_exception(env, parent, message);
|
|
|
|
bd.position_at_end(then_block);
|
|
|
|
bd.build_extract_value(result, 0, "operation_result")
|
|
.unwrap()
|
|
}
|
|
|
|
fn dec_split_into_words<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
value: IntValue<'ctx>,
|
|
) -> (IntValue<'ctx>, IntValue<'ctx>) {
|
|
let int_64 = env.context.i128_type().const_int(64, false);
|
|
let int_64_type = env.context.i64_type();
|
|
|
|
let left_bits_i128 = env
|
|
.builder
|
|
.build_right_shift(value, int_64, false, "left_bits_i128");
|
|
|
|
(
|
|
env.builder.build_int_cast(value, int_64_type, ""),
|
|
env.builder.build_int_cast(left_bits_i128, int_64_type, ""),
|
|
)
|
|
}
|
|
|
|
fn dec_alloca<'ctx>(env: &Env<'_, 'ctx, '_>, value: IntValue<'ctx>) -> PointerValue<'ctx> {
|
|
let dec_type = zig_dec_type(env);
|
|
|
|
let alloca = env.builder.build_alloca(dec_type, "dec_alloca");
|
|
|
|
let instruction = alloca.as_instruction_value().unwrap();
|
|
instruction.set_alignment(16).unwrap();
|
|
|
|
let ptr = env.builder.build_pointer_cast(
|
|
alloca,
|
|
value.get_type().ptr_type(AddressSpace::default()),
|
|
"cast_to_i128_ptr",
|
|
);
|
|
|
|
env.builder.build_store(ptr, value);
|
|
|
|
alloca
|
|
}
|
|
|
|
fn dec_to_str<'ctx>(env: &Env<'_, 'ctx, '_>, dec: BasicValueEnum<'ctx>) -> BasicValueEnum<'ctx> {
|
|
use roc_target::OperatingSystem::*;
|
|
|
|
let dec = dec.into_int_value();
|
|
|
|
match env.target_info.operating_system {
|
|
Windows => {
|
|
//
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[dec_alloca(env, dec).into()],
|
|
BitcodeReturns::Str,
|
|
bitcode::DEC_TO_STR,
|
|
)
|
|
}
|
|
Unix => {
|
|
let (low, high) = dec_split_into_words(env, dec);
|
|
|
|
call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[low.into(), high.into()],
|
|
BitcodeReturns::Str,
|
|
bitcode::DEC_TO_STR,
|
|
)
|
|
}
|
|
Wasi => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
fn dec_to_i128<'ctx>(env: &Env<'_, 'ctx, '_>, dec: BasicValueEnum<'ctx>) -> BasicValueEnum<'ctx> {
|
|
use roc_target::OperatingSystem::*;
|
|
|
|
let dec = dec.into_int_value();
|
|
|
|
match env.target_info.operating_system {
|
|
Windows => {
|
|
//
|
|
call_bitcode_fn(env, &[dec_alloca(env, dec).into()], bitcode::DEC_TO_I128)
|
|
}
|
|
Unix => {
|
|
let (low, high) = dec_split_into_words(env, dec);
|
|
|
|
call_bitcode_fn(env, &[low.into(), high.into()], bitcode::DEC_TO_I128)
|
|
}
|
|
Wasi => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
fn dec_binop_with_overflow<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
fn_name: &str,
|
|
lhs: BasicValueEnum<'ctx>,
|
|
rhs: BasicValueEnum<'ctx>,
|
|
) -> StructValue<'ctx> {
|
|
use roc_target::OperatingSystem::*;
|
|
|
|
let lhs = lhs.into_int_value();
|
|
let rhs = rhs.into_int_value();
|
|
|
|
let return_type = zig_with_overflow_roc_dec(env);
|
|
let return_alloca = env.builder.build_alloca(return_type, "return_alloca");
|
|
|
|
match env.target_info.operating_system {
|
|
Windows => {
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[
|
|
return_alloca.into(),
|
|
dec_alloca(env, lhs).into(),
|
|
dec_alloca(env, rhs).into(),
|
|
],
|
|
fn_name,
|
|
);
|
|
}
|
|
Unix => {
|
|
let (lhs_low, lhs_high) = dec_split_into_words(env, lhs);
|
|
let (rhs_low, rhs_high) = dec_split_into_words(env, rhs);
|
|
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[
|
|
return_alloca.into(),
|
|
lhs_low.into(),
|
|
lhs_high.into(),
|
|
rhs_low.into(),
|
|
rhs_high.into(),
|
|
],
|
|
fn_name,
|
|
);
|
|
}
|
|
Wasi => unimplemented!(),
|
|
}
|
|
|
|
env.builder
|
|
.new_build_load(return_type, return_alloca, "load_dec")
|
|
.into_struct_value()
|
|
}
|
|
|
|
pub(crate) fn dec_binop_with_unchecked<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
fn_name: &str,
|
|
lhs: BasicValueEnum<'ctx>,
|
|
rhs: BasicValueEnum<'ctx>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use roc_target::OperatingSystem::*;
|
|
|
|
let lhs = lhs.into_int_value();
|
|
let rhs = rhs.into_int_value();
|
|
|
|
match env.target_info.operating_system {
|
|
Windows => {
|
|
// windows is much nicer for us here
|
|
call_bitcode_fn(
|
|
env,
|
|
&[dec_alloca(env, lhs).into(), dec_alloca(env, rhs).into()],
|
|
fn_name,
|
|
)
|
|
}
|
|
Unix => {
|
|
let (lhs_low, lhs_high) = dec_split_into_words(env, lhs);
|
|
let (rhs_low, rhs_high) = dec_split_into_words(env, rhs);
|
|
|
|
call_bitcode_fn(
|
|
env,
|
|
&[
|
|
lhs_low.into(),
|
|
lhs_high.into(),
|
|
rhs_low.into(),
|
|
rhs_high.into(),
|
|
],
|
|
fn_name,
|
|
)
|
|
}
|
|
Wasi => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
/// Zig returns a nominal `WithOverflow(Dec)` struct (see [zig_with_overflow_roc_dec]),
|
|
/// but the Roc side may flatten the overflow struct. LLVM does not admit comparisons
|
|
/// between the two representations, so always cast to the Roc representation.
|
|
fn change_with_overflow_dec_to_roc_type<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
val: StructValue<'ctx>,
|
|
return_layout: InLayout<'a>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let return_type = convert::basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
);
|
|
let casted = cast_basic_basic(env.builder, val.into(), return_type);
|
|
use_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
casted,
|
|
"use_dec_with_overflow",
|
|
)
|
|
}
|
|
|
|
fn build_dec_binop<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
parent: FunctionValue<'ctx>,
|
|
lhs: BasicValueEnum<'ctx>,
|
|
rhs: BasicValueEnum<'ctx>,
|
|
return_layout: InLayout<'a>,
|
|
op: LowLevel,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use roc_module::low_level::LowLevel::*;
|
|
|
|
match op {
|
|
NumAddChecked => {
|
|
let val = dec_binop_with_overflow(env, bitcode::DEC_ADD_WITH_OVERFLOW, lhs, rhs);
|
|
change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout)
|
|
}
|
|
NumSubChecked => {
|
|
let val = dec_binop_with_overflow(env, bitcode::DEC_SUB_WITH_OVERFLOW, lhs, rhs);
|
|
change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout)
|
|
}
|
|
NumMulChecked => {
|
|
let val = dec_binop_with_overflow(env, bitcode::DEC_MUL_WITH_OVERFLOW, lhs, rhs);
|
|
change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout)
|
|
}
|
|
NumAdd => build_dec_binop_throw_on_overflow(
|
|
env,
|
|
parent,
|
|
bitcode::DEC_ADD_WITH_OVERFLOW,
|
|
lhs,
|
|
rhs,
|
|
"decimal addition overflowed",
|
|
),
|
|
NumSub => build_dec_binop_throw_on_overflow(
|
|
env,
|
|
parent,
|
|
bitcode::DEC_SUB_WITH_OVERFLOW,
|
|
lhs,
|
|
rhs,
|
|
"decimal subtraction overflowed",
|
|
),
|
|
NumMul => build_dec_binop_throw_on_overflow(
|
|
env,
|
|
parent,
|
|
bitcode::DEC_MUL_WITH_OVERFLOW,
|
|
lhs,
|
|
rhs,
|
|
"decimal multiplication overflowed",
|
|
),
|
|
NumDivFrac => dec_binop_with_unchecked(env, bitcode::DEC_DIV, lhs, rhs),
|
|
_ => {
|
|
unreachable!("Unrecognized int binary operation: {:?}", op);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_dec_binop_throw_on_overflow<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
parent: FunctionValue<'ctx>,
|
|
operation: &str,
|
|
lhs: BasicValueEnum<'ctx>,
|
|
rhs: BasicValueEnum<'ctx>,
|
|
message: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let result = dec_binop_with_overflow(env, operation, lhs, rhs);
|
|
|
|
let value = throw_on_overflow(env, parent, result, message).into_struct_value();
|
|
|
|
env.builder.build_extract_value(value, 0, "num").unwrap()
|
|
}
|
|
|
|
fn int_type_signed_min(int_type: IntType) -> IntValue {
|
|
let width = int_type.get_bit_width();
|
|
|
|
debug_assert!(width <= 128);
|
|
let shift = 128 - width as usize;
|
|
|
|
if shift < 64 {
|
|
let min = i128::MIN >> shift;
|
|
let a = min as u64;
|
|
let b = (min >> 64) as u64;
|
|
|
|
int_type.const_int_arbitrary_precision(&[b, a])
|
|
} else {
|
|
int_type.const_int((i128::MIN >> shift) as u64, false)
|
|
}
|
|
}
|
|
|
|
fn build_int_unary_op<'a, 'ctx, 'env>(
|
|
env: &Env<'a, 'ctx, 'env>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
parent: FunctionValue<'ctx>,
|
|
arg: IntValue<'ctx>,
|
|
arg_width: IntWidth,
|
|
arg_int_type: IntType<'ctx>,
|
|
op: LowLevel,
|
|
return_layout: InLayout<'a>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use roc_module::low_level::LowLevel::*;
|
|
|
|
let bd = env.builder;
|
|
|
|
match op {
|
|
NumNeg => {
|
|
// integer abs overflows when applied to the minimum value of a signed type
|
|
int_neg_raise_on_overflow(env, arg, arg_int_type)
|
|
}
|
|
NumAbs => {
|
|
// integer abs overflows when applied to the minimum value of a signed type
|
|
int_abs_raise_on_overflow(env, arg, arg_int_type)
|
|
}
|
|
NumToFrac => {
|
|
// This is an Int, so we need to convert it.
|
|
|
|
let target_float_type = match layout_interner.get_repr(return_layout) {
|
|
LayoutRepr::Builtin(Builtin::Float(float_width)) => {
|
|
convert::float_type_from_float_width(env, float_width)
|
|
}
|
|
_ => internal_error!("There can only be floats here!"),
|
|
};
|
|
|
|
bd.build_cast(
|
|
InstructionOpcode::SIToFP,
|
|
arg,
|
|
target_float_type,
|
|
"i64_to_f64",
|
|
)
|
|
}
|
|
NumToIntChecked => {
|
|
// return_layout : Result N [OutOfBounds]* ~ { result: N, out_of_bounds: bool }
|
|
|
|
let target_int_width = match layout_interner.get_repr(return_layout) {
|
|
LayoutRepr::Struct(field_layouts) if field_layouts.len() == 2 => {
|
|
debug_assert!(layout_interner.eq_repr(field_layouts[1], Layout::BOOL));
|
|
field_layouts[0].to_int_width()
|
|
}
|
|
layout => internal_error!(
|
|
"There can only be a result layout here, found {:?}!",
|
|
layout
|
|
),
|
|
};
|
|
|
|
let arg_always_fits_in_target = (arg_width.stack_size() < target_int_width.stack_size()
|
|
&& (
|
|
// If the arg is unsigned, it will always fit in either a signed or unsigned
|
|
// int of a larger width.
|
|
!arg_width.is_signed()
|
|
||
|
|
// Otherwise if the arg is signed, it will always fit in a signed int of a
|
|
// larger width.
|
|
(target_int_width.is_signed() )
|
|
) )
|
|
|| // Or if the two types are the same, they trivially fit.
|
|
arg_width == target_int_width;
|
|
|
|
// How the return type needs to be stored on the stack.
|
|
let return_type_stack_type = convert::basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
)
|
|
.into_struct_type();
|
|
// How the return type is actually used, in the Roc calling convention.
|
|
let return_type_use_type = convert::argument_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
);
|
|
|
|
if arg_always_fits_in_target {
|
|
// This is guaranteed to succeed so we can just make it an int cast and let LLVM
|
|
// optimize it away.
|
|
let target_int_type = convert::int_type_from_int_width(env, target_int_width);
|
|
let target_int_val: BasicValueEnum<'ctx> = env
|
|
.builder
|
|
.build_int_cast_sign_flag(
|
|
arg,
|
|
target_int_type,
|
|
target_int_width.is_signed(),
|
|
"int_cast",
|
|
)
|
|
.into();
|
|
|
|
let r = return_type_stack_type.const_zero();
|
|
let r = bd
|
|
.build_insert_value(r, target_int_val, 0, "converted_int")
|
|
.unwrap();
|
|
let r = bd
|
|
.build_insert_value(r, env.context.bool_type().const_zero(), 1, "out_of_bounds")
|
|
.unwrap();
|
|
|
|
r.into_struct_value().into()
|
|
} else {
|
|
let intrinsic = if !arg_width.is_signed() {
|
|
// We are trying to convert from unsigned to signed/unsigned of same or lesser width, e.g.
|
|
// u16 -> i16, u16 -> i8, or u16 -> u8. We only need to check that the argument
|
|
// value fits in the MAX target type value.
|
|
&bitcode::NUM_INT_TO_INT_CHECKING_MAX[target_int_width][arg_width]
|
|
} else {
|
|
// We are trying to convert from signed to signed/unsigned of same or lesser width, e.g.
|
|
// i16 -> u16, i16 -> i8, or i16 -> u8. We need to check that the argument value fits in
|
|
// the MAX and MIN target type.
|
|
&bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[target_int_width][arg_width]
|
|
};
|
|
|
|
let result = match env.target_info.ptr_width() {
|
|
PtrWidth::Bytes4 => {
|
|
let zig_function = env.module.get_function(intrinsic).unwrap();
|
|
let zig_function_type = zig_function.get_type();
|
|
|
|
match zig_function_type.get_return_type() {
|
|
Some(_) => call_str_bitcode_fn(
|
|
env,
|
|
&[],
|
|
&[arg.into()],
|
|
BitcodeReturns::Basic,
|
|
intrinsic,
|
|
),
|
|
None => {
|
|
let return_type = zig_to_int_checked_result_type(
|
|
env,
|
|
target_int_width.type_name(),
|
|
);
|
|
|
|
let zig_return_alloca = create_entry_block_alloca(
|
|
env,
|
|
parent,
|
|
return_type.into(),
|
|
"num_to_int",
|
|
);
|
|
|
|
call_void_bitcode_fn(
|
|
env,
|
|
&[zig_return_alloca.into(), arg.into()],
|
|
intrinsic,
|
|
);
|
|
|
|
let roc_return_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
)
|
|
.ptr_type(AddressSpace::default());
|
|
|
|
let roc_return_alloca = env.builder.build_pointer_cast(
|
|
zig_return_alloca,
|
|
roc_return_type,
|
|
"cast_to_roc",
|
|
);
|
|
|
|
load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
roc_return_alloca,
|
|
"num_to_int",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
PtrWidth::Bytes8 => {
|
|
if target_int_width.stack_size() as usize > env.target_info.ptr_size() {
|
|
let bitcode_return_type =
|
|
zig_to_int_checked_result_type(env, target_int_width.type_name());
|
|
|
|
call_bitcode_fn_fixing_for_convention(
|
|
env,
|
|
layout_interner,
|
|
bitcode_return_type,
|
|
&[arg.into()],
|
|
return_layout,
|
|
intrinsic,
|
|
)
|
|
} else {
|
|
call_bitcode_fn(env, &[arg.into()], intrinsic)
|
|
}
|
|
}
|
|
};
|
|
|
|
complex_bitcast_check_size(env, result, return_type_use_type, "cast_bitpacked")
|
|
}
|
|
}
|
|
NumCountLeadingZeroBits => call_bitcode_fn(
|
|
env,
|
|
&[arg.into()],
|
|
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width],
|
|
),
|
|
NumCountTrailingZeroBits => call_bitcode_fn(
|
|
env,
|
|
&[arg.into()],
|
|
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width],
|
|
),
|
|
NumCountOneBits => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width])
|
|
}
|
|
_ => {
|
|
unreachable!("Unrecognized int unary operation: {:?}", op);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn int_neg_raise_on_overflow<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
arg: IntValue<'ctx>,
|
|
int_type: IntType<'ctx>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let builder = env.builder;
|
|
|
|
let min_val = int_type_signed_min(int_type);
|
|
let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val");
|
|
|
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
|
let parent = block.get_parent().expect("to be in a function");
|
|
let then_block = env.context.append_basic_block(parent, "then");
|
|
let else_block = env.context.append_basic_block(parent, "else");
|
|
|
|
env.builder
|
|
.build_conditional_branch(condition, then_block, else_block);
|
|
|
|
builder.position_at_end(then_block);
|
|
|
|
throw_internal_exception(
|
|
env,
|
|
parent,
|
|
"integer negation overflowed because its argument is the minimum value",
|
|
);
|
|
|
|
builder.position_at_end(else_block);
|
|
|
|
builder.build_int_neg(arg, "negate_int").into()
|
|
}
|
|
|
|
fn int_abs_raise_on_overflow<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
arg: IntValue<'ctx>,
|
|
int_type: IntType<'ctx>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let builder = env.builder;
|
|
|
|
let min_val = int_type_signed_min(int_type);
|
|
let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val");
|
|
|
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
|
let parent = block.get_parent().expect("to be in a function");
|
|
let then_block = env.context.append_basic_block(parent, "then");
|
|
let else_block = env.context.append_basic_block(parent, "else");
|
|
|
|
env.builder
|
|
.build_conditional_branch(condition, then_block, else_block);
|
|
|
|
builder.position_at_end(then_block);
|
|
|
|
throw_internal_exception(
|
|
env,
|
|
parent,
|
|
"integer absolute overflowed because its argument is the minimum value",
|
|
);
|
|
|
|
builder.position_at_end(else_block);
|
|
|
|
int_abs_with_overflow(env, arg, int_type)
|
|
}
|
|
|
|
fn int_abs_with_overflow<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
arg: IntValue<'ctx>,
|
|
int_type: IntType<'ctx>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
// This is how libc's abs() is implemented - it uses no branching!
|
|
//
|
|
// abs = \arg ->
|
|
// shifted = arg >>> 63
|
|
//
|
|
// (xor arg shifted) - shifted
|
|
|
|
let bd = env.builder;
|
|
let shifted_name = "abs_shift_right";
|
|
let shifted_alloca = {
|
|
let bits_to_shift = int_type.get_bit_width() as u64 - 1;
|
|
let shift_val = int_type.const_int(bits_to_shift, false);
|
|
let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name);
|
|
let alloca = bd.build_alloca(int_type, "#int_abs_help");
|
|
|
|
// shifted = arg >>> 63
|
|
bd.build_store(alloca, shifted);
|
|
|
|
alloca
|
|
};
|
|
|
|
let xored_arg = bd.build_xor(
|
|
arg,
|
|
bd.new_build_load(int_type, shifted_alloca, shifted_name)
|
|
.into_int_value(),
|
|
"xor_arg_shifted",
|
|
);
|
|
|
|
BasicValueEnum::IntValue(
|
|
bd.build_int_sub(
|
|
xored_arg,
|
|
bd.new_build_load(int_type, shifted_alloca, shifted_name)
|
|
.into_int_value(),
|
|
"sub_xored_shifted",
|
|
),
|
|
)
|
|
}
|
|
|
|
fn build_float_unary_op<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout: InLayout<'a>,
|
|
arg: FloatValue<'ctx>,
|
|
op: LowLevel,
|
|
float_width: FloatWidth, // arg width
|
|
) -> BasicValueEnum<'ctx> {
|
|
use roc_module::low_level::LowLevel::*;
|
|
|
|
let bd = env.builder;
|
|
|
|
// TODO: Handle different sized floats
|
|
match op {
|
|
NumNeg => bd.build_float_neg(arg, "negate_float").into(),
|
|
NumAbs => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FABS[float_width]),
|
|
NumSqrtUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SQRT[float_width]),
|
|
NumLogUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_LOG[float_width]),
|
|
NumToFrac => {
|
|
let return_width = match layout_interner.get_repr(layout) {
|
|
LayoutRepr::Builtin(Builtin::Float(return_width)) => return_width,
|
|
_ => internal_error!("Layout for returning is not Float : {:?}", layout),
|
|
};
|
|
match (float_width, return_width) {
|
|
(FloatWidth::F32, FloatWidth::F32) => arg.into(),
|
|
(FloatWidth::F32, FloatWidth::F64) => bd.build_cast(
|
|
InstructionOpcode::FPExt,
|
|
arg,
|
|
env.context.f64_type(),
|
|
"f32_to_f64",
|
|
),
|
|
(FloatWidth::F64, FloatWidth::F32) => bd.build_cast(
|
|
InstructionOpcode::FPTrunc,
|
|
arg,
|
|
env.context.f32_type(),
|
|
"f64_to_f32",
|
|
),
|
|
(FloatWidth::F64, FloatWidth::F64) => arg.into(),
|
|
}
|
|
}
|
|
NumCeiling => {
|
|
let int_width = match layout_interner.get_repr(layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
|
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
|
};
|
|
match float_width {
|
|
FloatWidth::F32 => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F32[int_width])
|
|
}
|
|
FloatWidth::F64 => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F64[int_width])
|
|
}
|
|
}
|
|
}
|
|
NumFloor => {
|
|
let int_width = match layout_interner.get_repr(layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
|
_ => internal_error!("Floor return layout is not int: {:?}", layout),
|
|
};
|
|
match float_width {
|
|
FloatWidth::F32 => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F32[int_width])
|
|
}
|
|
FloatWidth::F64 => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F64[int_width])
|
|
}
|
|
}
|
|
}
|
|
NumRound => {
|
|
let int_width = match layout_interner.get_repr(layout) {
|
|
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
|
_ => internal_error!("Round return layout is not int: {:?}", layout),
|
|
};
|
|
match float_width {
|
|
FloatWidth::F32 => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F32[int_width])
|
|
}
|
|
FloatWidth::F64 => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F64[int_width])
|
|
}
|
|
}
|
|
}
|
|
NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]),
|
|
NumIsInfinite => {
|
|
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_INFINITE[float_width])
|
|
}
|
|
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
|
|
|
|
// trigonometry
|
|
NumSin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SIN[float_width]),
|
|
NumCos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COS[float_width]),
|
|
|
|
NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN[float_width]),
|
|
NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS[float_width]),
|
|
NumAsin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ASIN[float_width]),
|
|
|
|
_ => {
|
|
unreachable!("Unrecognized int unary operation: {:?}", op);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn run_higher_order_low_level<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
scope: &Scope<'a, 'ctx>,
|
|
return_layout: InLayout<'a>,
|
|
func_spec: FuncSpec,
|
|
higher_order: &HigherOrderLowLevel<'a>,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use roc_mono::ir::PassedFunction;
|
|
use roc_mono::low_level::HigherOrder::*;
|
|
|
|
let HigherOrderLowLevel {
|
|
op,
|
|
passed_function,
|
|
..
|
|
} = higher_order;
|
|
|
|
let PassedFunction {
|
|
argument_layouts,
|
|
return_layout: result_layout,
|
|
owns_captured_environment: function_owns_closure_data,
|
|
name: function_name,
|
|
captured_environment,
|
|
..
|
|
} = *passed_function;
|
|
|
|
// macros because functions cause lifetime issues related to the `env` or `layout_ids`
|
|
macro_rules! function_details {
|
|
() => {{
|
|
let function = function_value_by_func_spec(
|
|
env,
|
|
func_spec,
|
|
function_name.name(),
|
|
argument_layouts,
|
|
function_name.niche(),
|
|
return_layout,
|
|
);
|
|
|
|
let (closure, closure_layout) =
|
|
load_symbol_and_lambda_set(layout_interner, scope, &captured_environment);
|
|
|
|
(function, closure, closure_layout)
|
|
}};
|
|
}
|
|
|
|
match op {
|
|
ListMap { xs } => {
|
|
// List.map : List before, (before -> after) -> List after
|
|
let (list, list_layout) = scope.load_symbol_and_layout(xs);
|
|
|
|
let (function, closure, closure_layout) = function_details!();
|
|
|
|
match (
|
|
layout_interner.get_repr(list_layout),
|
|
layout_interner.get_repr(return_layout),
|
|
) {
|
|
(
|
|
LayoutRepr::Builtin(Builtin::List(element_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(result_layout)),
|
|
) => {
|
|
let argument_layouts = &[element_layout];
|
|
|
|
let roc_function_call = roc_function_call(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
function,
|
|
closure,
|
|
closure_layout,
|
|
function_owns_closure_data,
|
|
argument_layouts,
|
|
result_layout,
|
|
);
|
|
|
|
list_map(
|
|
env,
|
|
layout_interner,
|
|
roc_function_call,
|
|
list,
|
|
element_layout,
|
|
result_layout,
|
|
)
|
|
}
|
|
_ => unreachable!("invalid list layout"),
|
|
}
|
|
}
|
|
ListMap2 { xs, ys } => {
|
|
let (list1, list1_layout) = scope.load_symbol_and_layout(xs);
|
|
let (list2, list2_layout) = scope.load_symbol_and_layout(ys);
|
|
|
|
let (function, closure, closure_layout) = function_details!();
|
|
|
|
match (
|
|
layout_interner.get_repr(list1_layout),
|
|
layout_interner.get_repr(list2_layout),
|
|
layout_interner.get_repr(return_layout),
|
|
) {
|
|
(
|
|
LayoutRepr::Builtin(Builtin::List(element1_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(element2_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(result_layout)),
|
|
) => {
|
|
let argument_layouts = &[element1_layout, element2_layout];
|
|
|
|
let roc_function_call = roc_function_call(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
function,
|
|
closure,
|
|
closure_layout,
|
|
function_owns_closure_data,
|
|
argument_layouts,
|
|
result_layout,
|
|
);
|
|
|
|
list_map2(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
roc_function_call,
|
|
list1,
|
|
list2,
|
|
element1_layout,
|
|
element2_layout,
|
|
result_layout,
|
|
)
|
|
}
|
|
_ => unreachable!("invalid list layout"),
|
|
}
|
|
}
|
|
ListMap3 { xs, ys, zs } => {
|
|
let (list1, list1_layout) = scope.load_symbol_and_layout(xs);
|
|
let (list2, list2_layout) = scope.load_symbol_and_layout(ys);
|
|
let (list3, list3_layout) = scope.load_symbol_and_layout(zs);
|
|
|
|
let (function, closure, closure_layout) = function_details!();
|
|
|
|
match (
|
|
layout_interner.get_repr(list1_layout),
|
|
layout_interner.get_repr(list2_layout),
|
|
layout_interner.get_repr(list3_layout),
|
|
layout_interner.get_repr(return_layout),
|
|
) {
|
|
(
|
|
LayoutRepr::Builtin(Builtin::List(element1_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(element2_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(element3_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(result_layout)),
|
|
) => {
|
|
let argument_layouts = &[element1_layout, element2_layout, element3_layout];
|
|
|
|
let roc_function_call = roc_function_call(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
function,
|
|
closure,
|
|
closure_layout,
|
|
function_owns_closure_data,
|
|
argument_layouts,
|
|
result_layout,
|
|
);
|
|
|
|
list_map3(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
roc_function_call,
|
|
list1,
|
|
list2,
|
|
list3,
|
|
element1_layout,
|
|
element2_layout,
|
|
element3_layout,
|
|
result_layout,
|
|
)
|
|
}
|
|
_ => unreachable!("invalid list layout"),
|
|
}
|
|
}
|
|
ListMap4 { xs, ys, zs, ws } => {
|
|
let (list1, list1_layout) = scope.load_symbol_and_layout(xs);
|
|
let (list2, list2_layout) = scope.load_symbol_and_layout(ys);
|
|
let (list3, list3_layout) = scope.load_symbol_and_layout(zs);
|
|
let (list4, list4_layout) = scope.load_symbol_and_layout(ws);
|
|
|
|
let (function, closure, closure_layout) = function_details!();
|
|
|
|
match (
|
|
layout_interner.get_repr(list1_layout),
|
|
layout_interner.get_repr(list2_layout),
|
|
layout_interner.get_repr(list3_layout),
|
|
layout_interner.get_repr(list4_layout),
|
|
layout_interner.get_repr(return_layout),
|
|
) {
|
|
(
|
|
LayoutRepr::Builtin(Builtin::List(element1_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(element2_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(element3_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(element4_layout)),
|
|
LayoutRepr::Builtin(Builtin::List(result_layout)),
|
|
) => {
|
|
let argument_layouts = &[
|
|
element1_layout,
|
|
element2_layout,
|
|
element3_layout,
|
|
element4_layout,
|
|
];
|
|
|
|
let roc_function_call = roc_function_call(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
function,
|
|
closure,
|
|
closure_layout,
|
|
function_owns_closure_data,
|
|
argument_layouts,
|
|
result_layout,
|
|
);
|
|
|
|
list_map4(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
roc_function_call,
|
|
list1,
|
|
list2,
|
|
list3,
|
|
list4,
|
|
element1_layout,
|
|
element2_layout,
|
|
element3_layout,
|
|
element4_layout,
|
|
result_layout,
|
|
)
|
|
}
|
|
_ => unreachable!("invalid list layout"),
|
|
}
|
|
}
|
|
ListSortWith { xs } => {
|
|
// List.sortWith : List a, (a, a -> Ordering) -> List a
|
|
let (list, list_layout) = scope.load_symbol_and_layout(xs);
|
|
|
|
let (function, closure, closure_layout) = function_details!();
|
|
|
|
match layout_interner.get_repr(list_layout) {
|
|
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
|
|
use crate::llvm::bitcode::build_compare_wrapper;
|
|
|
|
let argument_layouts = &[element_layout, element_layout];
|
|
|
|
let compare_wrapper = build_compare_wrapper(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
function,
|
|
closure_layout,
|
|
element_layout,
|
|
)
|
|
.as_global_value()
|
|
.as_pointer_value();
|
|
|
|
let roc_function_call = roc_function_call(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
function,
|
|
closure,
|
|
closure_layout,
|
|
function_owns_closure_data,
|
|
argument_layouts,
|
|
result_layout,
|
|
);
|
|
|
|
list_sort_with(
|
|
env,
|
|
layout_interner,
|
|
roc_function_call,
|
|
compare_wrapper,
|
|
list,
|
|
element_layout,
|
|
)
|
|
}
|
|
_ => unreachable!("invalid list layout"),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn load_symbol_and_lambda_set<'a, 'ctx>(
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
scope: &Scope<'a, 'ctx>,
|
|
symbol: &Symbol,
|
|
) -> (BasicValueEnum<'ctx>, LambdaSet<'a>) {
|
|
let (ptr, layout) = scope.load_symbol_and_layout(symbol);
|
|
match layout_interner.get_repr(layout) {
|
|
LayoutRepr::LambdaSet(lambda_set) => (ptr, lambda_set),
|
|
other => panic!("Not a lambda set: {:?}, {:?}", other, ptr),
|
|
}
|
|
}
|