Merge pull request #2472 from rtfeldman/add_Int.toInt_builtins

Add `{Int *}.to{Int *}` builtins
This commit is contained in:
Brendan Hansknecht 2022-02-21 18:03:14 +00:00 committed by GitHub
commit 74daec84df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 929 additions and 21 deletions

View file

@ -28,7 +28,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look
> The bitcode is a bunch of bytes that aren't particularly human-readable.
> If you want to take a look at the human-readable LLVM IR, look at
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
> `compiler/builtins/bitcode/builtins.ll`
## Calling bitcode functions

View file

@ -98,6 +98,14 @@ comptime {
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
}
inline for (INTEGERS) |FROM| {
inline for (INTEGERS) |TO| {
// We're exporting more than we need here, but that's okay.
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
}
inline for (FLOATS) |T| {
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");

View file

@ -108,6 +108,39 @@ pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn ToIntCheckedResult(comptime T: type) type {
// On the Roc side we sort by alignment; putting the errorcode last
// always works out (no number with smaller alignment than 1).
return extern struct {
value: T,
out_of_bounds: bool,
};
}
pub fn exportToIntCheckingMax(comptime From: type, comptime To: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
if (input > std.math.maxInt(To)) {
return .{ .out_of_bounds = true, .value = 0 };
}
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
}
}.func;
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
}
pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
if (input > std.math.maxInt(To) or input < std.math.minInt(To)) {
return .{ .out_of_bounds = true, .value = 0 };
}
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
}
}.func;
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
}
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
}

View file

@ -100,6 +100,26 @@ interface Num
subWrap,
sqrt,
tan,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toFloat,
toStr
]
@ -592,6 +612,35 @@ mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
## Convert
## Convert any [Int] to a specifically-sized [Int], without checking validity.
## These are unchecked bitwise operations,
## so if the source number is outside the target range, then these will
## effectively modulo-wrap around the target range to reach a valid value.
toI8 : Int * -> I8
toI16 : Int * -> I16
toI32 : Int * -> I32
toI64 : Int * -> I64
toI128 : Int * -> I128
toU8 : Int * -> U8
toU16 : Int * -> U16
toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Convert any [Int] to a specifically-sized [Int], after checking validity.
## These are checked bitwise operations,
## so if the source number is outside the target range, then these will
## return `Err OutOfBounds`.
toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
toI64Checked : Int * -> Result I64 [ OutOfBounds ]*
toI128Checked : Int * -> Result I128 [ OutOfBounds ]*
toU8Checked : Int * -> Result U8 [ OutOfBounds ]*
toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
## Convert a number to a [Str].
##
## This is the same as calling `Num.format {}` - so for more details on

View file

@ -12,7 +12,7 @@ pub const BUILTINS_WASM32_OBJ_PATH: &str = env!(
"Env var BUILTINS_WASM32_O not found. Is there a problem with the build script?"
);
#[derive(Debug, Default)]
#[derive(Debug, Default, Copy, Clone)]
pub struct IntrinsicName {
pub options: [&'static str; 14],
}
@ -159,6 +159,21 @@ impl IntWidth {
_ => None,
}
}
pub const fn type_name(&self) -> &'static str {
match self {
Self::I8 => "i8",
Self::I16 => "i16",
Self::I32 => "i32",
Self::I64 => "i64",
Self::I128 => "i128",
Self::U8 => "u8",
Self::U16 => "u16",
Self::U32 => "u32",
Self::U64 => "u64",
Self::U128 => "u128",
}
}
}
impl Index<DecWidth> for IntrinsicName {
@ -214,11 +229,12 @@ macro_rules! float_intrinsic {
}
#[macro_export]
macro_rules! int_intrinsic {
macro_rules! llvm_int_intrinsic {
($signed_name:literal, $unsigned_name:literal) => {{
let mut output = IntrinsicName::default();
// The indeces align with the `Index` impl for `IntrinsicName`.
// LLVM uses the same types for both signed and unsigned integers.
output.options[4] = concat!($unsigned_name, ".i8");
output.options[5] = concat!($unsigned_name, ".i16");
output.options[6] = concat!($unsigned_name, ".i32");
@ -239,6 +255,28 @@ macro_rules! int_intrinsic {
};
}
#[macro_export]
macro_rules! int_intrinsic {
($name:expr) => {{
let mut output = IntrinsicName::default();
// The indices align with the `Index` impl for `IntrinsicName`.
output.options[4] = concat!($name, ".u8");
output.options[5] = concat!($name, ".u16");
output.options[6] = concat!($name, ".u32");
output.options[7] = concat!($name, ".u64");
output.options[8] = concat!($name, ".u128");
output.options[9] = concat!($name, ".i8");
output.options[10] = concat!($name, ".i16");
output.options[11] = concat!($name, ".i32");
output.options[12] = concat!($name, ".i64");
output.options[13] = concat!($name, ".i128");
output
}};
}
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
@ -339,3 +377,50 @@ pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null"
pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";
#[derive(Debug, Default)]
pub struct IntToIntrinsicName {
pub options: [IntrinsicName; 10],
}
impl IntToIntrinsicName {
pub const fn default() -> Self {
Self {
options: [IntrinsicName::default(); 10],
}
}
}
impl Index<IntWidth> for IntToIntrinsicName {
type Output = IntrinsicName;
fn index(&self, index: IntWidth) -> &Self::Output {
&self.options[index as usize]
}
}
#[macro_export]
macro_rules! int_to_int_intrinsic {
($name_prefix:literal, $name_suffix:literal) => {{
let mut output = IntToIntrinsicName::default();
output.options[0] = int_intrinsic!(concat!($name_prefix, "u8", $name_suffix));
output.options[1] = int_intrinsic!(concat!($name_prefix, "u16", $name_suffix));
output.options[2] = int_intrinsic!(concat!($name_prefix, "u32", $name_suffix));
output.options[3] = int_intrinsic!(concat!($name_prefix, "u64", $name_suffix));
output.options[4] = int_intrinsic!(concat!($name_prefix, "u128", $name_suffix));
output.options[5] = int_intrinsic!(concat!($name_prefix, "i8", $name_suffix));
output.options[6] = int_intrinsic!(concat!($name_prefix, "i16", $name_suffix));
output.options[7] = int_intrinsic!(concat!($name_prefix, "i32", $name_suffix));
output.options[8] = int_intrinsic!(concat!($name_prefix, "i64", $name_suffix));
output.options[9] = int_intrinsic!(concat!($name_prefix, "i128", $name_suffix));
output
}};
}
pub const NUM_INT_TO_INT_CHECKING_MAX: IntToIntrinsicName =
int_to_int_intrinsic!("roc_builtins.num.int_to_", "_checking_max");
pub const NUM_INT_TO_INT_CHECKING_MAX_AND_MIN: IntToIntrinsicName =
int_to_int_intrinsic!("roc_builtins.num.int_to_", "_checking_max_and_min");

View file

@ -445,6 +445,156 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// maxI128 : I128
add_type!(Symbol::NUM_MAX_I128, i128_type());
// toI8 : Int * -> I8
add_top_level_function_type!(
Symbol::NUM_TO_I8,
vec![int_type(flex(TVAR1))],
Box::new(i8_type()),
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
// toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I8_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i8_type(), out_of_bounds.clone())),
);
// toI16 : Int * -> I16
add_top_level_function_type!(
Symbol::NUM_TO_I16,
vec![int_type(flex(TVAR1))],
Box::new(i16_type()),
);
// toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I16_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i16_type(), out_of_bounds.clone())),
);
// toI32 : Int * -> I32
add_top_level_function_type!(
Symbol::NUM_TO_I32,
vec![int_type(flex(TVAR1))],
Box::new(i32_type()),
);
// toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I32_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i32_type(), out_of_bounds.clone())),
);
// toI64 : Int * -> I64
add_top_level_function_type!(
Symbol::NUM_TO_I64,
vec![int_type(flex(TVAR1))],
Box::new(i64_type()),
);
// toI64Checked : Int * -> Result I64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I64_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i64_type(), out_of_bounds.clone())),
);
// toI128 : Int * -> I128
add_top_level_function_type!(
Symbol::NUM_TO_I128,
vec![int_type(flex(TVAR1))],
Box::new(i128_type()),
);
// toI128Checked : Int * -> Result I128 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I128_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i128_type(), out_of_bounds)),
);
// toU8 : Int * -> U8
add_top_level_function_type!(
Symbol::NUM_TO_U8,
vec![int_type(flex(TVAR1))],
Box::new(u8_type()),
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
// toU8Checked : Int * -> Result U8 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U8_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u8_type(), out_of_bounds.clone())),
);
// toU16 : Int * -> U16
add_top_level_function_type!(
Symbol::NUM_TO_U16,
vec![int_type(flex(TVAR1))],
Box::new(u16_type()),
);
// toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U16_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u16_type(), out_of_bounds.clone())),
);
// toU32 : Int * -> U32
add_top_level_function_type!(
Symbol::NUM_TO_U32,
vec![int_type(flex(TVAR1))],
Box::new(u32_type()),
);
// toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U32_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u32_type(), out_of_bounds.clone())),
);
// toU64 : Int * -> U64
add_top_level_function_type!(
Symbol::NUM_TO_U64,
vec![int_type(flex(TVAR1))],
Box::new(u64_type()),
);
// toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U64_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u64_type(), out_of_bounds.clone())),
);
// toU128 : Int * -> U128
add_top_level_function_type!(
Symbol::NUM_TO_U128,
vec![int_type(flex(TVAR1))],
Box::new(u128_type()),
);
// toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U128_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u128_type(), out_of_bounds)),
);
// toStr : Num a -> Str
add_top_level_function_type!(
Symbol::NUM_TO_STR,

View file

@ -242,6 +242,26 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_MAX_U64=> num_max_u64,
NUM_MIN_I128=> num_min_i128,
NUM_MAX_I128=> num_max_i128,
NUM_TO_I8 => num_to_i8,
NUM_TO_I8_CHECKED => num_to_i8_checked,
NUM_TO_I16 => num_to_i16,
NUM_TO_I16_CHECKED => num_to_i16_checked,
NUM_TO_I32 => num_to_i32,
NUM_TO_I32_CHECKED => num_to_i32_checked,
NUM_TO_I64 => num_to_i64,
NUM_TO_I64_CHECKED => num_to_i64_checked,
NUM_TO_I128 => num_to_i128,
NUM_TO_I128_CHECKED => num_to_i128_checked,
NUM_TO_U8 => num_to_u8,
NUM_TO_U8_CHECKED => num_to_u8_checked,
NUM_TO_U16 => num_to_u16,
NUM_TO_U16_CHECKED => num_to_u16_checked,
NUM_TO_U32 => num_to_u32,
NUM_TO_U32_CHECKED => num_to_u32_checked,
NUM_TO_U64 => num_to_u64,
NUM_TO_U64_CHECKED => num_to_u64_checked,
NUM_TO_U128 => num_to_u128,
NUM_TO_U128_CHECKED => num_to_u128_checked,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
@ -390,6 +410,174 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
// Num.toI8 : Int * -> I8
fn num_to_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI16 : Int * -> I16
fn num_to_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI32 : Int * -> I32
fn num_to_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI64 : Int * -> I64
fn num_to_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI128 : Int * -> I128
fn num_to_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU8 : Int * -> U8
fn num_to_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU16 : Int * -> U16
fn num_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU32 : Int * -> U32
fn num_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU64 : Int * -> U64
fn num_to_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU128 : Int * -> U128
fn num_to_u128(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
// let arg_2 = RunLowLevel NumToXXXChecked arg_1
// if arg_2.b then
// Err OutOfBounds
// else
// Ok arg_2.a
//
// "a" and "b" because the lowlevel return value looks like { converted_val: XXX, out_of_bounds: bool },
// and codegen will sort by alignment, so "a" will be the first key, etc.
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_2.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
),
// out of bounds!
no_region(tag(
"Err",
vec![tag("OutOfBounds", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_2.a
tag(
"Ok",
vec![
// arg_2.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),
field_var: num_var_2,
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
],
var_store,
),
),
),
};
// arg_2 = RunLowLevel NumToXXXChecked arg_1
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)),
loc_expr: no_region(RunLowLevel {
op: lowlevel,
args: vec![(num_var_1, Var(Symbol::ARG_1))],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
macro_rules! num_to_checked {
($($fn:ident)*) => {$(
// Num.toXXXChecked : Int * -> Result XXX [ OutOfBounds ]*
fn $fn(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Use the generic `NumToIntChecked`; we'll figure out exactly what layout(s) we need
// during code generation after types are resolved.
to_num_checked(symbol, var_store, LowLevel::NumToIntChecked)
}
)*}
}
num_to_checked! {
num_to_i8_checked
num_to_i16_checked
num_to_i32_checked
num_to_i64_checked
num_to_i128_checked
num_to_u8_checked
num_to_u16_checked
num_to_u32_checked
num_to_u64_checked
num_to_u128_checked
}
// Num.toStr : Num a -> Str
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();

View file

@ -1,7 +1,8 @@
/// Helpers for interacting with the zig that generates bitcode
use crate::debug_info_init;
use crate::llvm::build::{
load_roc_value, struct_from_fields, Env, C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX,
complex_bitcast_check_size, load_roc_value, struct_from_fields, to_cc_return, CCReturn, Env,
C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX,
};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{
@ -11,9 +12,12 @@ use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{LambdaSet, Layout, LayoutIds, UnionLayout};
use std::convert::TryInto;
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
@ -92,6 +96,63 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
call
}
pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
return_layout: &Layout<'_>,
fn_name: &str,
) -> BasicValueEnum<'ctx> {
// Calling zig bitcode, so we must follow C calling conventions.
let cc_return = to_cc_return(env, return_layout);
match cc_return {
CCReturn::Return => {
// We'll get a return value
call_bitcode_fn(env, args, fn_name)
}
CCReturn::ByPointer => {
// We need to pass the return value by pointer.
let roc_return_type = basic_type_from_layout(env, return_layout);
let cc_ptr_return_type = env
.module
.get_function(fn_name)
.unwrap()
.get_type()
.get_param_types()[0]
.into_pointer_type();
let cc_return_type: BasicTypeEnum<'ctx> = cc_ptr_return_type
.get_element_type()
.try_into()
.expect("Zig bitcode return type is not a basic type!");
let cc_return_value_ptr = env.builder.build_alloca(cc_return_type, "return_value");
let fixed_args: Vec<BasicValueEnum<'ctx>> = [cc_return_value_ptr.into()]
.iter()
.chain(args)
.copied()
.collect();
call_void_bitcode_fn(env, &fixed_args, fn_name);
let cc_return_value = env.builder.build_load(cc_return_value_ptr, "read_result");
if roc_return_type.size_of() == cc_return_type.size_of() {
cc_return_value
} else {
// We need to convert the C-callconv return type, which may be larger than the Roc
// return type, into the Roc return type.
complex_bitcast_check_size(
env,
cc_return_value,
roc_return_type,
"c_value_to_roc_value",
)
}
}
CCReturn::Void => {
internal_error!("Tried to call valued bitcode function, but it has no return type")
}
}
}
const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_1,
Symbol::ARG_2,

View file

@ -1,7 +1,9 @@
use std::convert::TryFrom;
use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::bitcode::{
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_void_bitcode_fn,
};
use crate::llvm::build_dict::{
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
@ -53,7 +55,7 @@ use morphic_lib::{
CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar,
};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName};
use roc_builtins::{float_intrinsic, int_intrinsic};
use roc_builtins::{float_intrinsic, llvm_int_intrinsic};
use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
@ -609,14 +611,14 @@ static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp";
pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp";
const LLVM_ADD_WITH_OVERFLOW: IntrinsicName =
int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow");
llvm_int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow");
const LLVM_SUB_WITH_OVERFLOW: IntrinsicName =
int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow");
llvm_int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow");
const LLVM_MUL_WITH_OVERFLOW: IntrinsicName =
int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow");
llvm_int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow");
const LLVM_ADD_SATURATED: IntrinsicName = int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
const LLVM_SUB_SATURATED: IntrinsicName = int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
fn add_intrinsic<'ctx>(
module: &Module<'ctx>,
@ -2921,7 +2923,7 @@ pub fn complex_bitcast<'ctx>(
/// Check the size of the input and output types. Pretending we have more bytes at a pointer than
/// we actually do can lead to faulty optimizations and weird segfaults/crashes
fn complex_bitcast_check_size<'a, 'ctx, 'env>(
pub fn complex_bitcast_check_size<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
from_value: BasicValueEnum<'ctx>,
to_type: BasicTypeEnum<'ctx>,
@ -4073,7 +4075,13 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
) -> (&'static str, FunctionValue<'ctx>) {
let mod_solutions = build_procedures_help(env, opt_level, procedures, entry_point, None);
let mod_solutions = build_procedures_help(
env,
opt_level,
procedures,
entry_point,
Some(Path::new("/tmp/test.ll")),
);
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
}
@ -5680,7 +5688,8 @@ fn run_low_level<'a, 'ctx, 'env>(
}
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin
| NumToIntChecked => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
@ -5692,7 +5701,14 @@ fn run_low_level<'a, 'ctx, 'env>(
match arg_builtin {
Int(int_width) => {
let int_type = convert::int_type_from_int_width(env, *int_width);
build_int_unary_op(env, arg.into_int_value(), int_type, op, layout)
build_int_unary_op(
env,
arg.into_int_value(),
*int_width,
int_type,
op,
layout,
)
}
Float(float_width) => build_float_unary_op(
env,
@ -6186,7 +6202,7 @@ impl RocReturn {
}
#[derive(Debug)]
enum CCReturn {
pub enum CCReturn {
/// Return as normal
Return,
/// require an extra argument, a pointer
@ -6228,7 +6244,7 @@ impl CCReturn {
}
/// According to the C ABI, how should we return a value with the given layout?
fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
pub fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
let return_size = layout.stack_size(env.target_info);
let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32;
@ -6922,7 +6938,8 @@ fn int_type_signed_min(int_type: IntType) -> IntValue {
fn build_int_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
arg: IntValue<'ctx>,
int_type: IntType<'ctx>,
arg_width: IntWidth,
arg_int_type: IntType<'ctx>,
op: LowLevel,
return_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
@ -6933,11 +6950,11 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
match op {
NumNeg => {
// integer abs overflows when applied to the minimum value of a signed type
int_neg_raise_on_overflow(env, arg, int_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, int_type)
int_abs_raise_on_overflow(env, arg, arg_int_type)
}
NumToFloat => {
// This is an Int, so we need to convert it.
@ -6956,6 +6973,75 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
"i64_to_f64",
)
}
NumToIntChecked => {
// return_layout : Result N [ OutOfBounds ]* ~ { result: N, out_of_bounds: bool }
let target_int_width = match return_layout {
Layout::Struct(layouts) if layouts.len() == 2 => {
debug_assert!(matches!(layouts[1], Layout::Builtin(Builtin::Bool)));
match layouts[0] {
Layout::Builtin(Builtin::Int(iw)) => iw,
layout => internal_error!(
"There can only be an int layout here, found {:?}!",
layout
),
}
}
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;
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(arg, target_int_type, "int_cast")
.into();
let return_type =
convert::basic_type_from_layout(env, return_layout).into_struct_type();
let r = return_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 bitcode_fn = 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]
};
call_bitcode_fn_fixing_for_convention(env, &[arg.into()], return_layout, bitcode_fn)
}
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}

View file

@ -655,6 +655,9 @@ impl<'a> LowLevelCall<'a> {
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
}
}
NumToIntChecked => {
todo!()
}
And => {
self.load_args(backend);
backend.code_builder.i32_and();

View file

@ -111,6 +111,7 @@ pub enum LowLevel {
NumShiftRightBy,
NumShiftRightZfBy,
NumIntCast,
NumToIntChecked,
NumToStr,
Eq,
NotEq,

View file

@ -1010,6 +1010,26 @@ define_builtins! {
122 NUM_MAX_U64: "maxU64"
123 NUM_MIN_I128: "minI128"
124 NUM_MAX_I128: "maxI128"
125 NUM_TO_I8: "toI8"
126 NUM_TO_I8_CHECKED: "toI8Checked"
127 NUM_TO_I16: "toI16"
128 NUM_TO_I16_CHECKED: "toI16Checked"
129 NUM_TO_I32: "toI32"
130 NUM_TO_I32_CHECKED: "toI32Checked"
131 NUM_TO_I64: "toI64"
132 NUM_TO_I64_CHECKED: "toI64Checked"
133 NUM_TO_I128: "toI128"
134 NUM_TO_I128_CHECKED: "toI128Checked"
135 NUM_TO_U8: "toU8"
136 NUM_TO_U8_CHECKED: "toU8Checked"
137 NUM_TO_U16: "toU16"
138 NUM_TO_U16_CHECKED: "toU16Checked"
139 NUM_TO_U32: "toU32"
140 NUM_TO_U32_CHECKED: "toU32Checked"
141 NUM_TO_U64: "toU64"
142 NUM_TO_U64_CHECKED: "toU64Checked"
143 NUM_TO_U128: "toU128"
144 NUM_TO_U128_CHECKED: "toU128Checked"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -984,7 +984,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
| NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
| NumAsin | NumIntCast | NumToIntChecked => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),

View file

@ -5251,4 +5251,27 @@ mod solve_expr {
"{ j : a, lst : List a, s : Str }",
)
}
#[test]
fn to_int() {
infer_eq_without_problem(
indoc!(
r#"
{
toI8: Num.toI8,
toI16: Num.toI16,
toI32: Num.toI32,
toI64: Num.toI64,
toI128: Num.toI128,
toU8: Num.toU8,
toU16: Num.toU16,
toU32: Num.toU32,
toU64: Num.toU64,
toU128: Num.toU128,
}
"#
),
r#"{ toI128 : Int * -> I128, toI16 : Int * -> I16, toI32 : Int * -> I32, toI64 : Int * -> I64, toI8 : Int * -> I8, toU128 : Int * -> U128, toU16 : Int * -> U16, toU32 : Int * -> U32, toU64 : Int * -> U64, toU8 : Int * -> U8 }"#,
)
}
}

View file

@ -2053,6 +2053,207 @@ fn max_u8() {
);
}
macro_rules! to_int_tests {
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($(
#[test]
#[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))]
fn $test_name() {
let input = format!("{} {}", $fn, $input);
assert_evals_to!(&input, $output, $typ)
}
)*)*}
}
to_int_tests! {
"Num.toI8", i8, (
to_i8_same_width, "15u8", 15, ["gen-wasm"]
to_i8_truncate, "115i32", 115, ["gen-wasm"]
to_i8_truncate_wraps, "500i32", -12, ["gen-wasm"]
)
"Num.toI16", i16, (
to_i16_same_width, "15u16", 15, ["gen-wasm"]
to_i16_extend, "15i8", 15, ["gen-wasm"]
to_i16_truncate, "115i32", 115, ["gen-wasm"]
to_i16_truncate_wraps, "60000i32", -5536, ["gen-wasm"]
)
"Num.toI32", i32, (
to_i32_same_width, "15u32", 15, ["gen-wasm"]
to_i32_extend, "15i8", 15, ["gen-wasm"]
to_i32_truncate, "115i64", 115, ["gen-wasm"]
to_i32_truncate_wraps, "5000000000i64", 705032704, ["gen-wasm"]
)
"Num.toI64", i64, (
to_i64_same_width, "15u64", 15, ["gen-wasm"]
to_i64_extend, "15i8", 15, ["gen-wasm"]
to_i64_truncate, "115i128", 115
to_i64_truncate_wraps, "10_000_000_000_000_000_000i128", -8446744073709551616
)
"Num.toI128", i128, (
to_i128_same_width, "15u128", 15
to_i128_extend, "15i8", 15
)
"Num.toU8", u8, (
to_u8_same_width, "15i8", 15, ["gen-wasm"]
to_u8_truncate, "115i32", 115, ["gen-wasm"]
to_u8_truncate_wraps, "500i32", 244, ["gen-wasm"]
)
"Num.toU16", u16, (
to_u16_same_width, "15i16", 15, ["gen-wasm"]
to_u16_extend, "15i8", 15, ["gen-wasm"]
to_u16_truncate, "115i32", 115, ["gen-wasm"]
to_u16_truncate_wraps, "600000000i32", 17920, ["gen-wasm"]
)
"Num.toU32", u32, (
to_u32_same_width, "15i32", 15, ["gen-wasm"]
to_u32_extend, "15i8", 15, ["gen-wasm"]
to_u32_truncate, "115i64", 115, ["gen-wasm"]
to_u32_truncate_wraps, "5000000000000000000i64", 1156841472, ["gen-wasm"]
)
"Num.toU64", u64, (
to_u64_same_width, "15i64", 15, ["gen-wasm"]
to_u64_extend, "15i8", 15, ["gen-wasm"]
to_u64_truncate, "115i128", 115
to_u64_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128
)
"Num.toU128", u128, (
to_u128_same_width, "15i128", 15
to_u128_extend, "15i8", 15
)
}
macro_rules! to_int_checked_tests {
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr)*))*) => {$($(
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn $test_name() {
let sentinel = 23;
// Some n = Ok n, None = OutOfBounds
let expected = match $output.into() {
None => sentinel,
Some(n) => {
assert_ne!(n, sentinel);
n
}
};
let input = format!("Result.withDefault ({} {}) {}", $fn, $input, sentinel);
assert_evals_to!(&input, expected, $typ)
}
)*)*}
}
to_int_checked_tests! {
"Num.toI8Checked", i8, (
to_i8_checked_same, "15i8", 15
to_i8_checked_same_width_unsigned_fits, "15u8", 15
to_i8_checked_same_width_unsigned_oob, "128u8", None
to_i8_checked_larger_width_signed_fits_pos, "15i16", 15
to_i8_checked_larger_width_signed_oob_pos, "128i16", None
to_i8_checked_larger_width_signed_fits_neg, "-15i16", -15
to_i8_checked_larger_width_signed_oob_neg, "-129i16", None
to_i8_checked_larger_width_unsigned_fits_pos, "15u16", 15
to_i8_checked_larger_width_unsigned_oob_pos, "128u16", None
)
"Num.toI16Checked", i16, (
to_i16_checked_smaller_width_pos, "15i8", 15
to_i16_checked_smaller_width_neg, "-15i8", -15
to_i16_checked_same, "15i16", 15
to_i16_checked_same_width_unsigned_fits, "15u16", 15
to_i16_checked_same_width_unsigned_oob, "32768u16", None
to_i16_checked_larger_width_signed_fits_pos, "15i32", 15
to_i16_checked_larger_width_signed_oob_pos, "32768i32", None
to_i16_checked_larger_width_signed_fits_neg, "-15i32", -15
to_i16_checked_larger_width_signed_oob_neg, "-32769i32", None
to_i16_checked_larger_width_unsigned_fits_pos, "15u32", 15
to_i16_checked_larger_width_unsigned_oob_pos, "32768u32", None
)
"Num.toI32Checked", i32, (
to_i32_checked_smaller_width_pos, "15i8", 15
to_i32_checked_smaller_width_neg, "-15i8", -15
to_i32_checked_same, "15i32", 15
to_i32_checked_same_width_unsigned_fits, "15u32", 15
to_i32_checked_same_width_unsigned_oob, "2147483648u32", None
to_i32_checked_larger_width_signed_fits_pos, "15i64", 15
to_i32_checked_larger_width_signed_oob_pos, "2147483648i64", None
to_i32_checked_larger_width_signed_fits_neg, "-15i64", -15
to_i32_checked_larger_width_signed_oob_neg, "-2147483649i64", None
to_i32_checked_larger_width_unsigned_fits_pos, "15u64", 15
to_i32_checked_larger_width_unsigned_oob_pos, "2147483648u64", None
)
"Num.toI64Checked", i64, (
to_i64_checked_smaller_width_pos, "15i8", 15
to_i64_checked_smaller_width_neg, "-15i8", -15
to_i64_checked_same, "15i64", 15
to_i64_checked_same_width_unsigned_fits, "15u64", 15
to_i64_checked_same_width_unsigned_oob, "9223372036854775808u64", None
to_i64_checked_larger_width_signed_fits_pos, "15i128", 15
to_i64_checked_larger_width_signed_oob_pos, "9223372036854775808i128", None
to_i64_checked_larger_width_signed_fits_neg, "-15i128", -15
to_i64_checked_larger_width_signed_oob_neg, "-9223372036854775809i128", None
to_i64_checked_larger_width_unsigned_fits_pos, "15u128", 15
to_i64_checked_larger_width_unsigned_oob_pos, "9223372036854775808u128", None
)
"Num.toI128Checked", i128, (
to_i128_checked_smaller_width_pos, "15i8", 15
to_i128_checked_smaller_width_neg, "-15i8", -15
to_i128_checked_same, "15i128", 15
to_i128_checked_same_width_unsigned_fits, "15u128", 15
to_i128_checked_same_width_unsigned_oob, "170141183460469231731687303715884105728u128", None
)
"Num.toU8Checked", u8, (
to_u8_checked_same, "15u8", 15
to_u8_checked_same_width_signed_fits, "15i8", 15
to_u8_checked_same_width_signed_oob, "-1i8", None
to_u8_checked_larger_width_signed_fits_pos, "15i16", 15
to_u8_checked_larger_width_signed_oob_pos, "256i16", None
to_u8_checked_larger_width_signed_oob_neg, "-1i16", None
to_u8_checked_larger_width_unsigned_fits_pos, "15u16", 15
to_u8_checked_larger_width_unsigned_oob_pos, "256u16", None
)
"Num.toU16Checked", u16, (
to_u16_checked_smaller_width_pos, "15i8", 15
to_u16_checked_smaller_width_neg_oob, "-15i8", None
to_u16_checked_same, "15u16", 15
to_u16_checked_same_width_signed_fits, "15i16", 15
to_u16_checked_same_width_signed_oob, "-1i16", None
to_u16_checked_larger_width_signed_fits_pos, "15i32", 15
to_u16_checked_larger_width_signed_oob_pos, "65536i32", None
to_u16_checked_larger_width_signed_oob_neg, "-1i32", None
to_u16_checked_larger_width_unsigned_fits_pos, "15u32", 15
to_u16_checked_larger_width_unsigned_oob_pos, "65536u32", None
)
"Num.toU32Checked", u32, (
to_u32_checked_smaller_width_pos, "15i8", 15
to_u32_checked_smaller_width_neg_oob, "-15i8", None
to_u32_checked_same, "15u32", 15
to_u32_checked_same_width_signed_fits, "15i32", 15
to_u32_checked_same_width_signed_oob, "-1i32", None
to_u32_checked_larger_width_signed_fits_pos, "15i64", 15
to_u32_checked_larger_width_signed_oob_pos, "4294967296i64", None
to_u32_checked_larger_width_signed_oob_neg, "-1i64", None
to_u32_checked_larger_width_unsigned_fits_pos, "15u64", 15
to_u32_checked_larger_width_unsigned_oob_pos, "4294967296u64", None
)
"Num.toU64Checked", u64, (
to_u64_checked_smaller_width_pos, "15i8", 15
to_u64_checked_smaller_width_neg_oob, "-15i8", None
to_u64_checked_same, "15u64", 15
to_u64_checked_same_width_signed_fits, "15i64", 15
to_u64_checked_same_width_signed_oob, "-1i64", None
to_u64_checked_larger_width_signed_fits_pos, "15i128", 15
to_u64_checked_larger_width_signed_oob_pos, "18446744073709551616i128", None
to_u64_checked_larger_width_signed_oob_neg, "-1i128", None
to_u64_checked_larger_width_unsigned_fits_pos, "15u128", 15
to_u64_checked_larger_width_unsigned_oob_pos, "18446744073709551616u128", None
)
"Num.toU128Checked", u128, (
to_u128_checked_smaller_width_pos, "15i8", 15
to_u128_checked_smaller_width_neg_oob, "-15i8", None
to_u128_checked_same, "15u128", 15
to_u128_checked_same_width_signed_fits, "15i128", 15
to_u128_checked_same_width_signed_oob, "-1i128", None
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn is_multiple_of() {