add Num.count*Bits functions

This commit is contained in:
Brendan Hansknecht 2023-03-12 01:24:09 -08:00
parent fbaa257cef
commit 785da377c8
No known key found for this signature in database
GPG key ID: 0EA784685083E75B
11 changed files with 207 additions and 8 deletions

View file

@ -86,6 +86,10 @@ comptime {
num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow."); num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic."); num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic.");
num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated."); num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated.");
num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits.");
num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits.");
num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits.");
} }
inline for (INTEGERS) |FROM| { inline for (INTEGERS) |FROM| {

View file

@ -460,3 +460,30 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @clz(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @ctz(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(self: T) callconv(.C) usize {
return @as(usize, @popCount(T, self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}

View file

@ -68,6 +68,9 @@ interface Num
compare, compare,
pow, pow,
powInt, powInt,
countLeadingZeroBits,
countTrailingZeroBits,
countOneBits,
addWrap, addWrap,
addChecked, addChecked,
addSaturated, addSaturated,
@ -930,6 +933,46 @@ pow : Frac a, Frac a -> Frac a
## so large it causes an overflow. ## so large it causes an overflow.
powInt : Int a, Int a -> Int a powInt : Int a, Int a -> Int a
## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
##
## ```
## Num.countLeadingZeroBits 0b0001_1100u8
##
## 3
##
## Num.countLeadingZeroBits 0b0000_0000u8
##
## 8
## ```
countLeadingZeroBits : Int a -> Nat
## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
##
## ```
## Num.countTrailingZeroBits 0b0001_1100u8
##
## 2
##
## Num.countTrailingZeroBits 0b0000_0000u8
##
## 8
## ```
countTrailingZeroBits : Int a -> Nat
## Counts the number of set bits in an integer.
##
## ```
## Num.countOneBits 0b0001_1100u8
##
## 3
##
## Num.countOneBits 0b0000_0000u8
##
## 0
## ```
countOneBits : Int a -> Nat
addWrap : Int range, Int range -> Int range addWrap : Int range, Int range -> Int range
## Add two numbers, clamping on the maximum representable number rather than ## Add two numbers, clamping on the maximum representable number rather than

View file

@ -289,6 +289,12 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.
pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName = pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName =
float_intrinsic!("roc_builtins.num.mul_with_overflow"); float_intrinsic!("roc_builtins.num.mul_with_overflow");
pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_leading_zero_bits");
pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; 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_U32: &str = "roc_builtins.num.bytes_to_u32";

View file

@ -194,6 +194,9 @@ map_symbol_to_lowlevel_and_arity! {
NumShiftRightBy; NUM_SHIFT_RIGHT; 2, NumShiftRightBy; NUM_SHIFT_RIGHT; 2,
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2, NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
NumToStr; NUM_TO_STR; 1, NumToStr; NUM_TO_STR; 1,
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
Eq; BOOL_STRUCTURAL_EQ; 2, Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2, NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,

View file

@ -848,9 +848,24 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!(), _ => unreachable!(),
} }
} }
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos NumAbs
| NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin | NumNeg
| NumToIntChecked => { | NumRound
| NumSqrtUnchecked
| NumLogUnchecked
| NumSin
| NumCos
| NumCeiling
| NumFloor
| NumToFrac
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumToIntChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => {
arguments_with_layouts!((arg, arg_layout)); arguments_with_layouts!((arg, arg_layout));
match layout_interner.get(arg_layout) { match layout_interner.get(arg_layout) {
@ -2045,6 +2060,19 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked") complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked")
} }
} }
NumCountLeadingZeroBits => call_bitcode_fn(
env,
&[arg.into()],
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width],
),
NumCountTrailingZeroBits => call_bitcode_fn(
env,
&[arg.into()],
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width],
),
NumCountOneBits => {
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width])
}
_ => { _ => {
unreachable!("Unrecognized int unary operation: {:?}", op); unreachable!("Unrecognized int unary operation: {:?}", op);
} }

View file

@ -1489,6 +1489,40 @@ impl<'a> LowLevelCall<'a> {
} }
_ => panic_ret_type(), _ => panic_ret_type(),
}, },
NumCountLeadingZeroBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(
backend,
&bitcode::NUM_COUNT_LEADING_ZERO_BITS[width],
);
}
_ => panic_ret_type(),
},
NumCountTrailingZeroBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(
backend,
&bitcode::NUM_COUNT_TRAILING_ZERO_BITS[width],
);
}
_ => panic_ret_type(),
},
NumCountOneBits => match backend
.layout_interner
.get(backend.storage.symbol_layouts[&self.arguments[0]])
{
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(backend, &bitcode::NUM_COUNT_ONE_BITS[width]);
}
_ => panic_ret_type(),
},
NumRound => { NumRound => {
self.load_args(backend); self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);

View file

@ -101,6 +101,9 @@ pub enum LowLevel {
NumToIntChecked, NumToIntChecked,
NumToFloatChecked, NumToFloatChecked,
NumToStr, NumToStr,
NumCountLeadingZeroBits,
NumCountTrailingZeroBits,
NumCountOneBits,
Eq, Eq,
NotEq, NotEq,
And, And,
@ -312,6 +315,9 @@ map_symbol_to_lowlevel! {
NumShiftRightBy <= NUM_SHIFT_RIGHT, NumShiftRightBy <= NUM_SHIFT_RIGHT,
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
NumToStr <= NUM_TO_STR, NumToStr <= NUM_TO_STR,
NumCountLeadingZeroBits <= NUM_COUNT_LEADING_ZERO_BITS,
NumCountTrailingZeroBits <= NUM_COUNT_TRAILING_ZERO_BITS,
NumCountOneBits <= NUM_COUNT_ONE_BITS,
Eq <= BOOL_STRUCTURAL_EQ, Eq <= BOOL_STRUCTURAL_EQ,
NotEq <= BOOL_STRUCTURAL_NOT_EQ, NotEq <= BOOL_STRUCTURAL_NOT_EQ,
And <= BOOL_AND, And <= BOOL_AND,

View file

@ -1246,6 +1246,9 @@ define_builtins! {
145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel" 145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel" 146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel" 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"
} }
4 BOOL: "Bool" => { 4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias

View file

@ -1026,11 +1026,29 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked NumToStr
| NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos | NumAbs
| NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => { | NumNeg
arena.alloc_slice_copy(&[irrelevant]) | NumSin
} | NumCos
| NumSqrtUnchecked
| NumLogUnchecked
| NumRound
| NumCeiling
| NumFloor
| NumToFrac
| Not
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumIntCast
| NumToIntChecked
| NumToFloatCast
| NumToFloatChecked
| NumCountLeadingZeroBits
| NumCountTrailingZeroBits
| NumCountOneBits => 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(&[borrowed, borrowed]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]),

View file

@ -3807,3 +3807,30 @@ fn condition_polymorphic_num_becomes_float() {
f32 f32
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn num_count_leading_zero_bits() {
assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u8"#, 2, usize);
assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u16"#, 10, usize);
assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u32"#, 26, usize);
assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u64"#, 58, usize);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn num_count_trailing_zero_bits() {
assert_evals_to!(r#"Num.countTrailingZeroBits 0b0010_1000u8"#, 3, usize);
assert_evals_to!(r#"Num.countTrailingZeroBits 0b0010_0000u16"#, 5, usize);
assert_evals_to!(r#"Num.countTrailingZeroBits 0u32"#, 32, usize);
assert_evals_to!(r#"Num.countTrailingZeroBits 0b0010_1111u64"#, 0, usize);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn num_count_one_bits() {
assert_evals_to!(r#"Num.countOneBits 0b0010_1000u8"#, 2, usize);
assert_evals_to!(r#"Num.countOneBits 0b0010_0000u16"#, 1, usize);
assert_evals_to!(r#"Num.countOneBits 0u32"#, 0, usize);
assert_evals_to!(r#"Num.countOneBits 0b0010_1111u64"#, 5, usize);
}