diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0573ea661..82b3ee81b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/docs/src/user_docs/stdlib/math/u64.md b/docs/src/user_docs/stdlib/math/u64.md
index 5e3d06ec8a..5cf9b64337 100644
--- a/docs/src/user_docs/stdlib/math/u64.md
+++ b/docs/src/user_docs/stdlib/math/u64.md
@@ -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.
The input value to be shifted is assumed to be represented using 32-bit limbs.
The shift value should be in the range [0, 64), otherwise it will result in an error.
The stack transition looks as follows:
[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a >> b.
This takes 44 cycles. |
| rotl | Performs left rotation of one unsigned 64-bit integer using the pow2 operation.
The input value to be shifted is assumed to be represented using 32-bit limbs.
The shift value should be in the range [0, 64), otherwise it will result in an error.
The stack transition looks as follows:
[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.
This takes 35 cycles. |
| rotr | Performs right rotation of one unsigned 64-bit integer using the pow2 operation.
The input value to be shifted is assumed to be represented using 32-bit limbs.
The shift value should be in the range [0, 64), otherwise it will result in an error.
The stack transition looks as follows:
[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.
This takes 40 cycles. |
+| clz | 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.
The 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. |
+| ctz | 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.
The 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. |
+| clo | 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.
The 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. |
+| cto | 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.
The 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. |
diff --git a/miden/tests/integration/operations/field_ops.rs b/miden/tests/integration/operations/field_ops.rs
index fb0d7e9247..b7940dc6fa 100644
--- a/miden/tests/integration/operations/field_ops.rs
+++ b/miden/tests/integration/operations/field_ops.rs
@@ -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
diff --git a/stdlib/asm/math/u64.masm b/stdlib/asm/math/u64.masm
index fa2dd84b7f..63cc2ae957 100644
--- a/stdlib/asm/math/u64.masm
+++ b/stdlib/asm/math/u64.masm
@@ -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
diff --git a/stdlib/docs/math/u64.md b/stdlib/docs/math/u64.md
index 1db059cdd8..453ca6b438 100644
--- a/stdlib/docs/math/u64.md
+++ b/stdlib/docs/math/u64.md
@@ -27,3 +27,7 @@
| shr | Performs right shift of one unsigned 64-bit integer using the pow2 operation.
The input value to be shifted is assumed to be represented using 32 bit limbs.
The shift value should be in the range [0, 64), otherwise it will result in an
error.
Stack transition looks as follows:
[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a >> b.
This takes 44 cycles. |
| rotl | Performs left rotation of one unsigned 64-bit integer using the pow2 operation.
The input value to be shifted is assumed to be represented using 32 bit limbs.
The shift value should be in the range [0, 64), otherwise it will result in an
error.
Stack transition looks as follows:
[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.
This takes 35 cycles. |
| rotr | Performs right rotation of one unsigned 64-bit integer using the pow2 operation.
The input value to be shifted is assumed to be represented using 32 bit limbs.
The shift value should be in the range [0, 64), otherwise it will result in an
error.
Stack transition looks as follows:
[b, a_hi, a_lo, ...] -> [c_hi, c_lo, ...], where c = a << b mod 2^64.
This takes 40 cycles. |
+| clz | 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. |
+| ctz | 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. |
+| clo | 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. |
+| cto | 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. |
diff --git a/stdlib/tests/math/u64_mod.rs b/stdlib/tests/math/u64_mod.rs
index 7e3da8503a..28ac1aa7c9 100644
--- a/stdlib/tests/math/u64_mod.rs
+++ b/stdlib/tests/math/u64_mod.rs
@@ -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
// ================================================================================================
@@ -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::()) {
+
+ 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::()) {
+
+ 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::()) {
+
+ 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::()) {
+
+ 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