Skip to content

Commit

Permalink
feat: implement bitwise counters for u64 (#1179)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fumuran authored and bobbinth committed Feb 26, 2024
1 parent 3a1848c commit ff8ff8a
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Introduced `std::utils` module with `is_empty_word` procedure. Refactored `std::collections::smt`
and `std::collections::smt64` to use the procedure (#1107).
- Removed `checked` versions of the instructions in the `std::math::u64` module (#1142).
- Introduced `clz`, `ctz`, `clo` and `cto` instructions in the `std::math::u64` module (#1179).
- Removed `std::collections::smt64` (#1249)

#### VM Internals
Expand Down
4 changes: 4 additions & 0 deletions docs/src/user_docs/stdlib/math/u64.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ Many of the procedures listed below (e.g., `overflowing_add`, `wrapping_add`, `l
| shr | Performs right shift of one unsigned 64-bit integer using the pow2 operation.<br /> The input value to be shifted is assumed to be represented using 32-bit limbs.<br /> The shift value should be in the range [0, 64), otherwise it will result in an error.<br /> The stack transition looks as follows:<br /> [b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a >> b.<br /> This takes 44 cycles. |
| rotl | Performs left rotation of one unsigned 64-bit integer using the pow2 operation.<br /> The input value to be shifted is assumed to be represented using 32-bit limbs.<br /> The shift value should be in the range [0, 64), otherwise it will result in an error.<br /> The stack transition looks as follows:<br /> [b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.<br /> This takes 35 cycles. |
| rotr | Performs right rotation of one unsigned 64-bit integer using the pow2 operation.<br /> The input value to be shifted is assumed to be represented using 32-bit limbs.<br /> The shift value should be in the range [0, 64), otherwise it will result in an error.<br /> The stack transition looks as follows:<br /> [b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.<br /> This takes 40 cycles. |
| clz | Counts the number of leading zeros of one unsigned 64-bit integer.<br /> The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /> The stack transition looks as follows: `[n_hi, n_lo, ...] -> [clz, ...]`, where `clz` is a number of leading zeros of value `n`.<br /> This takes 43 cycles. |
| ctz | Counts the number of trailing zeros of one unsigned 64-bit integer.<br /> The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /> The stack transition looks as follows: `[n_hi, n_lo, ...] -> [ctz, ...]`, where `ctz` is a number of trailing zeros of value `n`.<br /> This takes 41 cycles. |
| clo | Counts the number of leading ones of one unsigned 64-bit integer.<br /> The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /> The stack transition looks as follows: `[n_hi, n_lo, ...] -> [clo, ...]`, where `clo` is a number of leading ones of value `n`.<br /> This takes 42 cycles. |
| cto | Counts the number of trailing ones of one unsigned 64-bit integer.<br /> The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /> The stack transition looks as follows: `[n_hi, n_lo, ...] -> [cto, ...]`, where `cto` is a number of trailing ones of value `n`.<br /> This takes 40 cycles. |
1 change: 0 additions & 1 deletion miden/tests/integration/operations/field_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,6 @@ proptest! {
let test = build_op_test!(asm_op, &[a]);
test.prop_expect_stack(&[expected as u64])?;
}

}

// FIELD OPS COMPARISON - RANDOMIZED TESTS
Expand Down
82 changes: 82 additions & 0 deletions stdlib/asm/math/u64.masm
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,85 @@ export.rotr
not
cswap
end

#! Counts the number of leading zeros of one unsigned 64-bit integer.
#! The input value is assumed to be represented using 32 bit limbs, but this is not checked.
#! Stack transition looks as follows:
#! [n_hi, n_lo, ...] -> [clz, ...], where clz is a number of leading zeros of value n.
#! This takes 43 cycles.
export.clz
dup.0
eq.0

if.true # if n_hi == 0
drop
u32clz
add.32 # clz(n_lo) + 32
else
swap
drop
u32clz # clz(n_hi)
end
end

#! Counts the number of trailing zeros of one unsigned 64-bit integer.
#! The input value is assumed to be represented using 32 bit limbs, but this is not checked.
#! Stack transition looks as follows:
#! [n_hi, n_lo, ...] -> [ctz, ...], where ctz is a number of trailing zeros of value n.
#! This takes 41 cycles.
export.ctz
swap
dup.0
eq.0

if.true # if n_lo == 0
drop
u32ctz
add.32 # ctz(n_hi) + 32
else
swap
drop
u32ctz # ctz(n_lo)
end
end

#! Counts the number of leading ones of one unsigned 64-bit integer.
#! The input value is assumed to be represented using 32 bit limbs, but this is not checked.
#! Stack transition looks as follows:
#! [n_hi, n_lo, ...] -> [clo, ...], where clo is a number of leading ones of value n.
#! This takes 42 cycles.
export.clo
dup.0
eq.4294967295

if.true # if n_hi == 11111111111111111111111111111111
drop
u32clo
add.32 # clo(n_lo) + 32
else
swap
drop
u32clo # clo(n_hi)
end
end

#! Counts the number of trailing ones of one unsigned 64-bit integer.
#! The input value is assumed to be represented using 32 bit limbs, but this is not checked.
#! Stack transition looks as follows:
#! [n_hi, n_lo, ...] -> [cto, ...], where cto is a number of trailing ones of value n.
#! This takes 40 cycles.
export.cto
swap
dup.0
eq.4294967295

if.true # if n_lo == 11111111111111111111111111111111
drop
u32cto
add.32 # cto(n_hi) + 32
else
swap
drop
u32cto # ctz(n_lo)
end
end
4 changes: 4 additions & 0 deletions stdlib/docs/math/u64.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@
| shr | Performs right shift of one unsigned 64-bit integer using the pow2 operation.<br /><br />The input value to be shifted is assumed to be represented using 32 bit limbs.<br /><br />The shift value should be in the range [0, 64), otherwise it will result in an<br /><br />error.<br /><br />Stack transition looks as follows:<br /><br />[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a >> b.<br /><br />This takes 44 cycles. |
| rotl | Performs left rotation of one unsigned 64-bit integer using the pow2 operation.<br /><br />The input value to be shifted is assumed to be represented using 32 bit limbs.<br /><br />The shift value should be in the range [0, 64), otherwise it will result in an<br /><br />error.<br /><br />Stack transition looks as follows:<br /><br />[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.<br /><br />This takes 35 cycles. |
| rotr | Performs right rotation of one unsigned 64-bit integer using the pow2 operation.<br /><br />The input value to be shifted is assumed to be represented using 32 bit limbs.<br /><br />The shift value should be in the range [0, 64), otherwise it will result in an<br /><br />error.<br /><br />Stack transition looks as follows:<br /><br />[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.<br /><br />This takes 40 cycles. |
| clz | Counts the number of leading zeros of one unsigned 64-bit integer.<br /><br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /><br />Stack transition looks as follows:<br /><br />[n_hi, n_lo, ...] -> [clz, ...], where clz is a number of leading zeros of value n.<br /><br />This takes 43 cycles. |
| ctz | Counts the number of trailing zeros of one unsigned 64-bit integer.<br /><br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /><br />Stack transition looks as follows:<br /><br />[n_hi, n_lo, ...] -> [ctz, ...], where ctz is a number of trailing zeros of value n.<br /><br />This takes 41 cycles. |
| clo | Counts the number of leading ones of one unsigned 64-bit integer.<br /><br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /><br />Stack transition looks as follows:<br /><br />[n_hi, n_lo, ...] -> [clo, ...], where clo is a number of leading ones of value n.<br /><br />This takes 42 cycles. |
| cto | Counts the number of trailing ones of one unsigned 64-bit integer.<br /><br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br /><br />Stack transition looks as follows:<br /><br />[n_hi, n_lo, ...] -> [cto, ...], where cto is a number of trailing ones of value n.<br /><br />This takes 40 cycles. |
120 changes: 120 additions & 0 deletions stdlib/tests/math/u64_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,66 @@ fn unchecked_rotr() {
build_test!(source, &[5, a0, a1, b as u64]).expect_stack(&[c1, c0, 5]);
}

#[test]
fn clz() {
let source = "
use.std::math::u64
begin
exec.u64::clz
end";

build_test!(source, &[0, 0]).expect_stack(&[64]);
build_test!(source, &[492665065, 0]).expect_stack(&[35]);
build_test!(source, &[3941320520, 0]).expect_stack(&[32]);
build_test!(source, &[3941320520, 492665065]).expect_stack(&[3]);
build_test!(source, &[492665065, 492665065]).expect_stack(&[3]);
}

#[test]
fn ctz() {
let source = "
use.std::math::u64
begin
exec.u64::ctz
end";

build_test!(source, &[0, 0]).expect_stack(&[64]);
build_test!(source, &[0, 3668265216]).expect_stack(&[40]);
build_test!(source, &[0, 3668265217]).expect_stack(&[32]);
build_test!(source, &[3668265216, 3668265217]).expect_stack(&[8]);
build_test!(source, &[3668265216, 3668265216]).expect_stack(&[8]);
}

#[test]
fn clo() {
let source = "
use.std::math::u64
begin
exec.u64::clo
end";

build_test!(source, &[4294967295, 4294967295]).expect_stack(&[64]);
build_test!(source, &[4278190080, 4294967295]).expect_stack(&[40]);
build_test!(source, &[0, 4294967295]).expect_stack(&[32]);
build_test!(source, &[0, 4278190080]).expect_stack(&[8]);
build_test!(source, &[4278190080, 4278190080]).expect_stack(&[8]);
}

#[test]
fn cto() {
let source = "
use.std::math::u64
begin
exec.u64::cto
end";

build_test!(source, &[4294967295, 4294967295]).expect_stack(&[64]);
build_test!(source, &[4294967295, 255]).expect_stack(&[40]);
build_test!(source, &[4294967295, 0]).expect_stack(&[32]);
build_test!(source, &[255, 0]).expect_stack(&[8]);
build_test!(source, &[255, 255]).expect_stack(&[8]);
}

// RANDOMIZED TESTS
// ================================================================================================

Expand Down Expand Up @@ -974,6 +1034,66 @@ proptest! {

build_test!(source, &[5, a0, a1, b as u64]).prop_expect_stack(&[c1, c0, 5])?;
}

#[test]
fn clz_proptest(a in any::<u64>()) {

let (a1, a0) = split_u64(a);
let c = a.leading_zeros() as u64;

let source = "
use.std::math::u64
begin
exec.u64::clz
end";

build_test!(source, &[a0, a1]).prop_expect_stack(&[c])?;
}

#[test]
fn ctz_proptest(a in any::<u64>()) {

let (a1, a0) = split_u64(a);
let c = a.trailing_zeros() as u64;

let source = "
use.std::math::u64
begin
exec.u64::ctz
end";

build_test!(source, &[a0, a1]).prop_expect_stack(&[c])?;
}

#[test]
fn clo_proptest(a in any::<u64>()) {

let (a1, a0) = split_u64(a);
let c = a.leading_ones() as u64;

let source = "
use.std::math::u64
begin
exec.u64::clo
end";

build_test!(source, &[a0, a1]).prop_expect_stack(&[c])?;
}

#[test]
fn cto_proptest(a in any::<u64>()) {

let (a1, a0) = split_u64(a);
let c = a.trailing_ones() as u64;

let source = "
use.std::math::u64
begin
exec.u64::cto
end";

build_test!(source, &[a0, a1]).prop_expect_stack(&[c])?;
}
}

// HELPER FUNCTIONS
Expand Down

0 comments on commit ff8ff8a

Please sign in to comment.