mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Merge pull request #2472 from rtfeldman/add_Int.toInt_builtins
Add `{Int *}.to{Int *}` builtins
This commit is contained in:
commit
74daec84df
15 changed files with 929 additions and 21 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -111,6 +111,7 @@ pub enum LowLevel {
|
|||
NumShiftRightBy,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumToIntChecked,
|
||||
NumToStr,
|
||||
Eq,
|
||||
NotEq,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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 }"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue