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