diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 78c4957a6f..95566a3ea2 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -67,6 +67,8 @@ const NUMBERS = INTEGERS ++ FLOATS; comptime { exportNumFn(num.bytesToU16C, "bytes_to_u16"); exportNumFn(num.bytesToU32C, "bytes_to_u32"); + exportNumFn(num.bytesToU64C, "bytes_to_u64"); + exportNumFn(num.bytesToU128C, "bytes_to_u128"); inline for (INTEGERS) |T, i| { num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig index 3cc9f2ad75..dbbf87c1cf 100644 --- a/crates/compiler/builtins/bitcode/src/num.zig +++ b/crates/compiler/builtins/bitcode/src/num.zig @@ -236,6 +236,24 @@ fn bytesToU32(arg: RocList, position: usize) u32 { return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); } +pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 { + return @call(.{ .modifier = always_inline }, bytesToU64, .{ arg, position }); +} + +fn bytesToU64(arg: RocList, position: usize) u64 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u64, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] }); +} + +pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 { + return @call(.{ .modifier = always_inline }, bytesToU128, .{ arg, position }); +} + +fn bytesToU128(arg: RocList, position: usize) u128 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u128, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] }); +} + fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) { switch (@typeInfo(T)) { .Int => { diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 75c7ca880c..d7413b55cb 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -89,6 +89,8 @@ interface Num intCast, bytesToU16, bytesToU32, + bytesToU64, + bytesToU128, divCeil, divCeilChecked, divTrunc, @@ -508,6 +510,8 @@ intCast : Int a -> Int b bytesToU16Lowlevel : List U8, Nat -> U16 bytesToU32Lowlevel : List U8, Nat -> U32 +bytesToU64Lowlevel : List U8, Nat -> U64 +bytesToU128Lowlevel : List U8, Nat -> U128 bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds] bytesToU16 = \bytes, index -> @@ -529,6 +533,26 @@ bytesToU32 = \bytes, index -> else Err OutOfBounds +bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds] +bytesToU64 = \bytes, index -> + # we need at least 7 more bytes + offset = 7 + + if index + offset < List.len bytes then + Ok (bytesToU64Lowlevel bytes index) + else + Err OutOfBounds + +bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds] +bytesToU128 = \bytes, index -> + # we need at least 15 more bytes + offset = 15 + + if index + offset < List.len bytes then + Ok (bytesToU128Lowlevel bytes index) + else + Err OutOfBounds + compare : Num a, Num a -> [LT, EQ, GT] ## Returns `Bool.true` if the first number is less than the second. diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 2ac1217410..69782f2afa 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -297,6 +297,8 @@ pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.c pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; +pub const NUM_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64"; +pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128"; pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 43a4d8c93f..8cf8fa8ae5 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -187,6 +187,8 @@ map_symbol_to_lowlevel_and_arity! { NumAsin; NUM_ASIN; 1, NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2, NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2, + NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2, + NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2, NumBitwiseAnd; NUM_BITWISE_AND; 2, NumBitwiseXor; NUM_BITWISE_XOR; 2, NumBitwiseOr; NUM_BITWISE_OR; 2, diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index 6ef102e62e..4a1d09cc27 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -929,6 +929,28 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( bitcode::NUM_BYTES_TO_U32, ) } + NumBytesToU64 => { + arguments!(list, position); + + call_list_bitcode_fn( + env, + &[list.into_struct_value()], + &[position], + BitcodeReturns::Basic, + bitcode::NUM_BYTES_TO_U64, + ) + } + NumBytesToU128 => { + arguments!(list, position); + + call_list_bitcode_fn( + env, + &[list.into_struct_value()], + &[position], + BitcodeReturns::Basic, + bitcode::NUM_BYTES_TO_U128, + ) + } NumCompare => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index f2b60d0a8d..130d842d5b 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -1611,6 +1611,8 @@ impl<'a> LowLevelCall<'a> { }, NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16), NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32), + NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64), + NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128), NumBitwiseAnd => { self.load_args(backend); match CodeGenNumType::from(self.ret_layout) { diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index 6c5dd78a70..1a7d870f60 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -90,6 +90,8 @@ pub enum LowLevel { NumAsin, NumBytesToU16, NumBytesToU32, + NumBytesToU64, + NumBytesToU128, NumBitwiseAnd, NumBitwiseXor, NumBitwiseOr, @@ -308,6 +310,8 @@ map_symbol_to_lowlevel! { NumAsin <= NUM_ASIN, NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL, NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL, + NumBytesToU64 <= NUM_BYTES_TO_U64_LOWLEVEL, + NumBytesToU128 <= NUM_BYTES_TO_U128_LOWLEVEL, NumBitwiseAnd <= NUM_BITWISE_AND, NumBitwiseXor <= NUM_BITWISE_XOR, NumBitwiseOr <= NUM_BITWISE_OR, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 9a2eca581c..34096ee30b 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1189,66 +1189,70 @@ define_builtins! { 88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias 89 NUM_BYTES_TO_U16: "bytesToU16" 90 NUM_BYTES_TO_U32: "bytesToU32" - 91 NUM_CAST_TO_NAT: "#castToNat" - 92 NUM_DIV_CEIL: "divCeil" - 93 NUM_DIV_CEIL_CHECKED: "divCeilChecked" - 94 NUM_TO_STR: "toStr" - 95 NUM_MIN_I8: "minI8" - 96 NUM_MAX_I8: "maxI8" - 97 NUM_MIN_U8: "minU8" - 98 NUM_MAX_U8: "maxU8" - 99 NUM_MIN_I16: "minI16" - 100 NUM_MAX_I16: "maxI16" - 101 NUM_MIN_U16: "minU16" - 102 NUM_MAX_U16: "maxU16" - 103 NUM_MIN_I32: "minI32" - 104 NUM_MAX_I32: "maxI32" - 105 NUM_MIN_U32: "minU32" - 106 NUM_MAX_U32: "maxU32" - 107 NUM_MIN_I64: "minI64" - 108 NUM_MAX_I64: "maxI64" - 109 NUM_MIN_U64: "minU64" - 110 NUM_MAX_U64: "maxU64" - 111 NUM_MIN_I128: "minI128" - 112 NUM_MAX_I128: "maxI128" - 113 NUM_MIN_U128: "minU128" - 114 NUM_MAX_U128: "maxU128" - 115 NUM_TO_I8: "toI8" - 116 NUM_TO_I8_CHECKED: "toI8Checked" - 117 NUM_TO_I16: "toI16" - 118 NUM_TO_I16_CHECKED: "toI16Checked" - 119 NUM_TO_I32: "toI32" - 120 NUM_TO_I32_CHECKED: "toI32Checked" - 121 NUM_TO_I64: "toI64" - 122 NUM_TO_I64_CHECKED: "toI64Checked" - 123 NUM_TO_I128: "toI128" - 124 NUM_TO_I128_CHECKED: "toI128Checked" - 125 NUM_TO_U8: "toU8" - 126 NUM_TO_U8_CHECKED: "toU8Checked" - 127 NUM_TO_U16: "toU16" - 128 NUM_TO_U16_CHECKED: "toU16Checked" - 129 NUM_TO_U32: "toU32" - 130 NUM_TO_U32_CHECKED: "toU32Checked" - 131 NUM_TO_U64: "toU64" - 132 NUM_TO_U64_CHECKED: "toU64Checked" - 133 NUM_TO_U128: "toU128" - 134 NUM_TO_U128_CHECKED: "toU128Checked" - 135 NUM_TO_NAT: "toNat" - 136 NUM_TO_NAT_CHECKED: "toNatChecked" - 137 NUM_TO_F32: "toF32" - 138 NUM_TO_F32_CHECKED: "toF32Checked" - 139 NUM_TO_F64: "toF64" - 140 NUM_TO_F64_CHECKED: "toF64Checked" - 141 NUM_MAX_F64: "maxF64" - 142 NUM_MIN_F64: "minF64" - 143 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel" - 144 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel" - 145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel" - 146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel" - 147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel" - 148 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits" - 149 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits" - 150 NUM_COUNT_ONE_BITS: "countOneBits" + 91 NUM_BYTES_TO_U64: "bytesToU64" + 92 NUM_BYTES_TO_U128: "bytesToU128" + 93 NUM_CAST_TO_NAT: "#castToNat" + 94 NUM_DIV_CEIL: "divCeil" + 95 NUM_DIV_CEIL_CHECKED: "divCeilChecked" + 96 NUM_TO_STR: "toStr" + 97 NUM_MIN_I8: "minI8" + 98 NUM_MAX_I8: "maxI8" + 99 NUM_MIN_U8: "minU8" + 100 NUM_MAX_U8: "maxU8" + 101 NUM_MIN_I16: "minI16" + 102 NUM_MAX_I16: "maxI16" + 103 NUM_MIN_U16: "minU16" + 104 NUM_MAX_U16: "maxU16" + 105 NUM_MIN_I32: "minI32" + 106 NUM_MAX_I32: "maxI32" + 107 NUM_MIN_U32: "minU32" + 108 NUM_MAX_U32: "maxU32" + 109 NUM_MIN_I64: "minI64" + 110 NUM_MAX_I64: "maxI64" + 111 NUM_MIN_U64: "minU64" + 112 NUM_MAX_U64: "maxU64" + 113 NUM_MIN_I128: "minI128" + 114 NUM_MAX_I128: "maxI128" + 115 NUM_MIN_U128: "minU128" + 116 NUM_MAX_U128: "maxU128" + 117 NUM_TO_I8: "toI8" + 118 NUM_TO_I8_CHECKED: "toI8Checked" + 119 NUM_TO_I16: "toI16" + 120 NUM_TO_I16_CHECKED: "toI16Checked" + 121 NUM_TO_I32: "toI32" + 122 NUM_TO_I32_CHECKED: "toI32Checked" + 123 NUM_TO_I64: "toI64" + 124 NUM_TO_I64_CHECKED: "toI64Checked" + 125 NUM_TO_I128: "toI128" + 126 NUM_TO_I128_CHECKED: "toI128Checked" + 127 NUM_TO_U8: "toU8" + 128 NUM_TO_U8_CHECKED: "toU8Checked" + 129 NUM_TO_U16: "toU16" + 130 NUM_TO_U16_CHECKED: "toU16Checked" + 131 NUM_TO_U32: "toU32" + 132 NUM_TO_U32_CHECKED: "toU32Checked" + 133 NUM_TO_U64: "toU64" + 134 NUM_TO_U64_CHECKED: "toU64Checked" + 135 NUM_TO_U128: "toU128" + 136 NUM_TO_U128_CHECKED: "toU128Checked" + 137 NUM_TO_NAT: "toNat" + 138 NUM_TO_NAT_CHECKED: "toNatChecked" + 139 NUM_TO_F32: "toF32" + 140 NUM_TO_F32_CHECKED: "toF32Checked" + 141 NUM_TO_F64: "toF64" + 142 NUM_TO_F64_CHECKED: "toF64Checked" + 143 NUM_MAX_F64: "maxF64" + 144 NUM_MIN_F64: "minF64" + 145 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel" + 146 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel" + 147 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel" + 148 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel" + 149 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel" + 150 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel" + 151 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel" + 152 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits" + 153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits" + 154 NUM_COUNT_ONE_BITS: "countOneBits" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index e584db8e66..44f67491a4 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -1051,6 +1051,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { | NumCountOneBits => arena.alloc_slice_copy(&[irrelevant]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), + NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]), + NumBytesToU128 => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]), StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), diff --git a/crates/compiler/mono/src/low_level.rs b/crates/compiler/mono/src/low_level.rs index ddad554c81..d346838e88 100644 --- a/crates/compiler/mono/src/low_level.rs +++ b/crates/compiler/mono/src/low_level.rs @@ -120,6 +120,8 @@ enum FirstOrder { NumShiftRightBy, NumBytesToU16, NumBytesToU32, + NumBytesToU64, + NumBytesToU128, NumShiftRightZfBy, NumIntCast, NumFloatCast, diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 1e0dd0bd34..2e9276c86f 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2806,6 +2806,74 @@ fn bytes_to_u32_subtly_out_of_bounds() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_clearly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU64 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_subtly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello world" + when Num.bytesToU64 bytes 4 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_clearly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU128 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_subtly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello world!!!!!!" + when Num.bytesToU128 bytes 2 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_max_u8s() { @@ -2902,6 +2970,102 @@ fn bytes_to_u32_random_u8s() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_min_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU64 [0, 0, 0, 0, 0, 0, 0, 0] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 0, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_max_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU64 [255, 255, 255, 255, 255, 255, 255, 255] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 18_446_744_073_709_551_615, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_random_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU64 [252, 124, 128, 121, 1, 32, 177, 211] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 15_254_008_603_586_100_476, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_min_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU128 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 0, + u128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_max_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU128 [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 340_282_366_920_938_463_463_374_607_431_768_211_455, + u128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_random_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU128 [252, 124, 128, 121, 1, 32, 177, 211, 3, 57, 203, 122, 95, 164, 23, 145] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 192_860_816_096_412_392_720_639_456_393_488_792_828, + u128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn when_on_i32() {