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. > 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 > 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 ## Calling bitcode functions

View file

@ -98,6 +98,14 @@ comptime {
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil."); 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| { inline for (FLOATS) |T| {
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin."); num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos."); 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 }); @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 { pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position }); return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
} }

View file

@ -100,6 +100,26 @@ interface Num
subWrap, subWrap,
sqrt, sqrt,
tan, tan,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toFloat, toFloat,
toStr toStr
] ]
@ -592,6 +612,35 @@ mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
## Convert ## 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]. ## Convert a number to a [Str].
## ##
## This is the same as calling `Num.format {}` - so for more details on ## 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?" "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 struct IntrinsicName {
pub options: [&'static str; 14], pub options: [&'static str; 14],
} }
@ -159,6 +159,21 @@ impl IntWidth {
_ => None, _ => 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 { impl Index<DecWidth> for IntrinsicName {
@ -214,11 +229,12 @@ macro_rules! float_intrinsic {
} }
#[macro_export] #[macro_export]
macro_rules! int_intrinsic { macro_rules! llvm_int_intrinsic {
($signed_name:literal, $unsigned_name:literal) => {{ ($signed_name:literal, $unsigned_name:literal) => {{
let mut output = IntrinsicName::default(); let mut output = IntrinsicName::default();
// The indeces align with the `Index` impl for `IntrinsicName`. // 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[4] = concat!($unsigned_name, ".i8");
output.options[5] = concat!($unsigned_name, ".i16"); output.options[5] = concat!($unsigned_name, ".i16");
output.options[6] = concat!($unsigned_name, ".i32"); 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_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos"); pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan"); 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_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures"; pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_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 // maxI128 : I128
add_type!(Symbol::NUM_MAX_I128, i128_type()); 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 // toStr : Num a -> Str
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_TO_STR, 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_MAX_U64=> num_max_u64,
NUM_MIN_I128=> num_min_i128, NUM_MIN_I128=> num_min_i128,
NUM_MAX_I128=> num_max_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, NUM_TO_STR => num_to_str,
RESULT_MAP => result_map, RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err, 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 // Num.toStr : Num a -> Str
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh(); let num_var = var_store.fresh();

View file

@ -1,7 +1,8 @@
/// Helpers for interacting with the zig that generates bitcode /// Helpers for interacting with the zig that generates bitcode
use crate::debug_info_init; use crate::debug_info_init;
use crate::llvm::build::{ 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::convert::basic_type_from_layout;
use crate::llvm::refcounting::{ use crate::llvm::refcounting::{
@ -11,9 +12,12 @@ use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum}; use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue}; use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace; use inkwell::AddressSpace;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{LambdaSet, Layout, LayoutIds, UnionLayout}; use roc_mono::layout::{LambdaSet, Layout, LayoutIds, UnionLayout};
use std::convert::TryInto;
pub fn call_bitcode_fn<'a, 'ctx, 'env>( pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>], args: &[BasicValueEnum<'ctx>],
@ -92,6 +96,63 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
call 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] = [ const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_1, Symbol::ARG_1,
Symbol::ARG_2, Symbol::ARG_2,

View file

@ -1,7 +1,9 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use std::path::Path; 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::{ use crate::llvm::build_dict::{
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection, 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, 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, CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar,
}; };
use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName}; 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_collections::all::{ImMap, MutMap, MutSet};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel; 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"; pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp";
const LLVM_ADD_WITH_OVERFLOW: IntrinsicName = 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 = 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 = 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_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
const LLVM_SUB_SATURATED: IntrinsicName = int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat"); const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
fn add_intrinsic<'ctx>( fn add_intrinsic<'ctx>(
module: &Module<'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 /// 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 /// 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>, env: &Env<'a, 'ctx, 'env>,
from_value: BasicValueEnum<'ctx>, from_value: BasicValueEnum<'ctx>,
to_type: BasicTypeEnum<'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>>, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>, entry_point: EntryPoint<'a>,
) -> (&'static str, FunctionValue<'ctx>) { ) -> (&'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) 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 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); debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]); 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 { match arg_builtin {
Int(int_width) => { Int(int_width) => {
let int_type = convert::int_type_from_int_width(env, *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( Float(float_width) => build_float_unary_op(
env, env,
@ -6186,7 +6202,7 @@ impl RocReturn {
} }
#[derive(Debug)] #[derive(Debug)]
enum CCReturn { pub enum CCReturn {
/// Return as normal /// Return as normal
Return, Return,
/// require an extra argument, a pointer /// 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? /// 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 return_size = layout.stack_size(env.target_info);
let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32; 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>( fn build_int_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
arg: IntValue<'ctx>, arg: IntValue<'ctx>,
int_type: IntType<'ctx>, arg_width: IntWidth,
arg_int_type: IntType<'ctx>,
op: LowLevel, op: LowLevel,
return_layout: &Layout<'a>, return_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
@ -6933,11 +6950,11 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
match op { match op {
NumNeg => { NumNeg => {
// integer abs overflows when applied to the minimum value of a signed type // 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 => { NumAbs => {
// integer abs overflows when applied to the minimum value of a signed type // 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 => { NumToFloat => {
// This is an Int, so we need to convert it. // 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", "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); unreachable!("Unrecognized int unary operation: {:?}", op);
} }

View file

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

View file

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

View file

@ -1010,6 +1010,26 @@ define_builtins! {
122 NUM_MAX_U64: "maxU64" 122 NUM_MAX_U64: "maxU64"
123 NUM_MIN_I128: "minI128" 123 NUM_MIN_I128: "minI128"
124 NUM_MAX_I128: "maxI128" 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" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 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 NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | 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]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),

View file

@ -5251,4 +5251,27 @@ mod solve_expr {
"{ j : a, lst : List a, s : Str }", "{ 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] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn is_multiple_of() { fn is_multiple_of() {