Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make u32 shift and rotation instructions always checked #1135

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions assembly/src/assembler/instruction/u32_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ pub fn u32not(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, AssemblyError
/// the value to be shifted and splitting the result.
///
/// VM cycles per mode:
/// - u32shl: 18 cycles
/// - u32shl.b: 3 cycles
/// - u32shl: 19 cycles
/// - u32shl.b: 4 cycles
pub fn u32shl(span: &mut SpanBuilder, imm: Option<u8>) -> Result<Option<CodeBlock>, AssemblyError> {
prepare_bitwise::<MAX_U32_SHIFT_VALUE>(span, imm)?;
if imm != Some(0) {
Expand All @@ -214,8 +214,8 @@ pub fn u32shl(span: &mut SpanBuilder, imm: Option<u8>) -> Result<Option<CodeBloc
/// be shifted by it and returning the quotient.
///
/// VM cycles per mode:
/// - u32shr: 18 cycles
/// - u32shr.b: 3 cycles
/// - u32shr: 19 cycles
/// - u32shr.b: 4 cycles
pub fn u32shr(span: &mut SpanBuilder, imm: Option<u8>) -> Result<Option<CodeBlock>, AssemblyError> {
prepare_bitwise::<MAX_U32_SHIFT_VALUE>(span, imm)?;
if imm != Some(0) {
Expand All @@ -231,8 +231,8 @@ pub fn u32shr(span: &mut SpanBuilder, imm: Option<u8>) -> Result<Option<CodeBloc
/// value to be shifted by it and adding the overflow limb to the shifted limb.
///
/// VM cycles per mode:
/// - u32rotl: 18 cycles
/// - u32rotl.b: 3 cycles
/// - u32rotl: 19 cycles
/// - u32rotl.b: 4 cycles
pub fn u32rotl(
span: &mut SpanBuilder,
imm: Option<u8>,
Expand All @@ -251,25 +251,42 @@ pub fn u32rotl(
/// b is the shift amount, then adding the overflow limb to the shifted limb.
///
/// VM cycles per mode:
/// - u32rotr: 22 cycles
/// - u32rotr.b: 3 cycles
/// - u32rotr: 31 cycles
/// - u32rotr.b: 4 cycles
pub fn u32rotr(
span: &mut SpanBuilder,
imm: Option<u8>,
) -> Result<Option<CodeBlock>, AssemblyError> {
match imm {
Some(0) => {
// if rotation is performed by 0, do nothing (Noop)
span.push_op(Noop);
span.push_ops([Pad, U32assert2(ZERO), Drop]);
return Ok(None);
}
Some(imm) => {
validate_param(imm, 1..=MAX_U32_ROTATE_VALUE)?;
span.push_op(Push(Felt::new(1 << (32 - imm))));
span.push_ops([Push(Felt::new(1 << (32 - imm))), U32assert2(ZERO)]);
}
None => {
span.push_ops([Push(Felt::new(32)), Swap, U32sub, Drop]);
span.push_ops([
// Verify both b and a are u32.
U32assert2(ZERO),
// Calculate 32 - b and assert that the shift value b <= 31.
Push(Felt::from(MAX_U32_ROTATE_VALUE)),
Dup1,
U32sub,
Not,
Assert(ZERO),
Incr,
Dup1,
// If 32-b = 32, replace it with 0.
Eqz,
Not,
CSwap,
Drop,
]);
append_pow2_op(span);
span.push_op(Swap);
}
}
span.add_ops([U32mul, Add])
Expand Down Expand Up @@ -366,14 +383,15 @@ fn prepare_bitwise<const MAX_VALUE: u8>(
match imm {
Some(0) => {
// if shift/rotation is performed by 0, do nothing (Noop)
span.push_op(Noop);
span.push_ops([Pad, U32assert2(ZERO), Drop]);
}
Some(imm) => {
validate_param(imm, 1..=MAX_U32_ROTATE_VALUE)?;
span.push_op(Push(Felt::new(1 << imm)));
span.push_ops([Push(Felt::new(1 << imm)), U32assert2(ZERO)]);
}
None => {
append_pow2_op(span);
span.push_op(U32assert2(ZERO));
}
}
Ok(())
Expand Down
8 changes: 4 additions & 4 deletions docs/src/user_docs/assembly/u32_operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ If the error code is omitted, the default value of $0$ is assumed.
| u32or <br> - *(6 cycle)s* | [b, a, ...] | [c, ...] | Computes $c$ as a bitwise `OR` of binary representations of $a$ and $b$. <br> Fails if $max(a,b) \ge 2^{32}$ |
| u32xor <br> - *(1 cycle)* | [b, a, ...] | [c, ...] | Computes $c$ as a bitwise `XOR` of binary representations of $a$ and $b$. <br> Fails if $max(a,b) \ge 2^{32}$ |
| u32not <br> - *(5 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a bitwise `NOT` of binary representation of $a$. <br> Fails if $a \ge 2^{32}$ |
| u32shl <br> - *(40 cycles)* <br> u32shl.*b* <br> - *(3 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow (a \cdot 2^b) \mod 2^{32}$ <br> Undefined if $a \ge 2^{32}$ or $b > 31$ |
| u32shr <br> - *(40 cycles)* <br> u32shr.*b* <br> - *(3 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \lfloor a/2^b \rfloor$ <br> Undefined if $a \ge 2^{32}$ or $b > 31$ |
| u32rotl <br> - *(40 cycles)* <br> u32rotl.*b* <br> - *(3 cycles)* | [b, a, ...] | [c, ...] | Computes $c$ by rotating a 32-bit representation of $a$ to the left by $b$ bits. <br> Undefined if $a \ge 2^{32}$ or $b > 31$ |
| u32rotr <br> - *(44 cycles)* <br> u32rotr.*b* <br> - *(3 cycles)* | [b, a, ...] | [c, ...] | Computes $c$ by rotating a 32-bit representation of $a$ to the right by $b$ bits. <br> Undefined if $a \ge 2^{32}$ or $b > 31$ |
| u32shl <br> - *(19 cycles)* <br> u32shl.*b* <br> - *(4 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow (a \cdot 2^b) \mod 2^{32}$ <br> Fails if $a \ge 2^{32}$ or $b > 31$ |
| u32shr <br> - *(19 cycles)*<br> u32shr.*b* <br> - *(4 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \lfloor a/2^b \rfloor$ <br> Fails if $a \ge 2^{32}$ or $b > 31$ |
| u32rotl <br> - *(19 cycles)* <br> u32rotl.*b* <br> - *(4 cycles)* | [b, a, ...] | [c, ...] | Computes $c$ by rotating a 32-bit representation of $a$ to the left by $b$ bits. <br> Fails if $a \ge 2^{32}$ or $b > 31$ |
| u32rotr <br> - *(31 cycles)* <br> u32rotr.*b* <br> - *(4 cycles)* | [b, a, ...] | [c, ...] | Computes $c$ by rotating a 32-bit representation of $a$ to the right by $b$ bits. <br> Fails if $a \ge 2^{32}$ or $b > 31$ |
| u32popcnt <br> - *(33 cycles)* | [a, ...] | [b, ...] | Computes $b$ by counting the number of set bits in $a$ (hamming weight of $a$). <br> Undefined if $a \ge 2^{32}$ |

### Comparison operations
Expand Down
193 changes: 171 additions & 22 deletions miden/tests/integration/operations/u32_ops/bitwise_ops.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::test_input_out_of_bounds;
use super::{test_input_out_of_bounds, test_param_out_of_bounds};
use test_utils::{build_op_test, proptest::prelude::*, rand::rand_value, TestError, U32_BOUND};

// U32 OPERATIONS TESTS - MANUAL - BITWISE OPERATIONS
Expand Down Expand Up @@ -193,10 +193,20 @@ fn u32shl() {

let test = build_op_test!(asm_op, &[a as u64, b as u64]);
test.expect_stack(&[a.wrapping_shl(b) as u64]);
}

// --- test out of bounds input (should not fail) --------------------------------------------
#[test]
fn u32shl_fail() {
let asm_op = "u32shl";

// should fail if a >= 2^32
let test = build_op_test!(asm_op, &[U32_BOUND, 1]);
assert!(test.execute().is_ok());
test.expect_error(TestError::ExecutionError("NotU32Value"));

// should fail if b >= 32
let test = build_op_test!(asm_op, &[1, 32]);
// if b >= 32, 2^b >= 2^32 or not a u32
test.expect_error(TestError::ExecutionError("NotU32Value"));
}

#[test]
Expand Down Expand Up @@ -225,17 +235,20 @@ fn u32shl_b() {
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.wrapping_shl(b) as u64]);

// // --- test random values ---------------------------------------------------------------------
// let a = rand_value::<u32>();
// let b = rand_value::<u32>() % 32;
// --- test random values ---------------------------------------------------------------------
let a = rand_value::<u32>();
let b = rand_value::<u32>() % 32;

let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.wrapping_shl(b) as u64]);
}

// let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
// test.expect_stack(&[a.wrapping_shl(b) as u64]);
#[test]
fn u32shl_b_fail() {
let op_base = "u32shl";

// // --- test out of bounds input (should not fail) --------------------------------------------
// let b = 1;
// let test = build_op_test!(get_asm_op(b).as_str(), &[U32_BOUND]);
// assert!(test.execute().is_ok());
test_input_out_of_bounds(format!("{}.{}", op_base, 1).as_str());
test_param_out_of_bounds(op_base, 32);
}

#[test]
Expand Down Expand Up @@ -269,10 +282,19 @@ fn u32shr() {

let test = build_op_test!(asm_op, &[a as u64, b as u64]);
test.expect_stack(&[a.wrapping_shr(b) as u64]);
}

// --- test out of bounds inputs (should not fail) --------------------------------------------
#[test]
fn u32shr_fail() {
let asm_op = "u32shr";

// should fail if a >= 2^32
let test = build_op_test!(asm_op, &[U32_BOUND, 1]);
assert!(test.execute().is_ok());
test.expect_error(TestError::ExecutionError("NotU32Value"));

// should fail if b >= 32
let test = build_op_test!(asm_op, &[1, 32]);
test.expect_error(TestError::ExecutionError("NotU32Value"));
}

#[test]
Expand Down Expand Up @@ -307,11 +329,14 @@ fn u32shr_b() {

let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.wrapping_shr(b) as u64]);
}

// --- test out of bounds inputs (should not fail) --------------------------------------------
let b = 1;
let test = build_op_test!(get_asm_op(b).as_str(), &[U32_BOUND]);
assert!(test.execute().is_ok());
#[test]
fn u32shr_b_fail() {
let op_base = "u32shr";

test_input_out_of_bounds(format!("{}.{}", op_base, 1).as_str());
test_param_out_of_bounds(op_base, 32);
}

#[test]
Expand Down Expand Up @@ -356,10 +381,72 @@ fn u32rotl() {

let test = build_op_test!(asm_op, &[a as u64, b as u64]);
test.expect_stack(&[a.rotate_left(b) as u64]);
}

#[test]
fn u32rotl_fail() {
let asm_op = "u32rotl";

// --- test out of bounds inputs (should not fail) --------------------------------------------
// should fail if a >= 2^32
let test = build_op_test!(asm_op, &[U32_BOUND, 1]);
assert!(test.execute().is_ok());
test.expect_error(TestError::ExecutionError("NotU32Value"));

// should fail if b >= 32
let test = build_op_test!(asm_op, &[1, 32]);
test.expect_error(TestError::ExecutionError("NotU32Value"));
}

#[test]
fn u32rotl_b() {
// Computes c by rotating a 32-bit representation of a to the left by b bits.
let op_base = "u32rotl";
let get_asm_op = |b: u32| format!("{op_base}.{b}");

// --- test simple case -----------------------------------------------------------------------
let a = 1_u32;
let b = 1_u32;
let test = build_op_test!(get_asm_op(b).as_str(), &[5, a as u64]);
test.expect_stack(&[2, 5]);

// --- test simple wraparound case with large a -----------------------------------------------
let a = (1_u64 << 31) as u32;
let b: u32 = 1;
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[1]);

// --- test simple case wraparound case with max b --------------------------------------------
let a = 2_u32;
let b: u32 = 31;
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[1]);

// --- no change when a is max value (all 1s) -------------------------------------------------
let a = (U32_BOUND - 1) as u32;
let b = 2;
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a as u64]);

// --- test b = 0 ---------------------------------------------------------------------------
let a = rand_value::<u32>();
let b = 0;

let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.rotate_left(b) as u64]);

// --- test random values ---------------------------------------------------------------------
let a = rand_value::<u32>();
let b = rand_value::<u32>() % 32;

let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.rotate_left(b) as u64]);
}

#[test]
fn u32rotl_fail_b() {
let op_base = "u32rotl";

test_input_out_of_bounds(format!("{}.{}", op_base, 1).as_str());
test_param_out_of_bounds(op_base, 32);
}

#[test]
Expand Down Expand Up @@ -404,10 +491,72 @@ fn u32rotr() {

let test = build_op_test!(asm_op, &[a as u64, b as u64]);
test.expect_stack(&[a.rotate_right(b) as u64]);
}

#[test]
fn u32rotr_fail() {
let asm_op = "u32rotr";

// --- test out of bounds inputs (should not fail) --------------------------------------------
// should fail if a >= 2^32
let test = build_op_test!(asm_op, &[U32_BOUND, 1]);
assert!(test.execute().is_ok());
test.expect_error(TestError::ExecutionError("NotU32Value"));

// should fail if b >= 32
let test = build_op_test!(asm_op, &[1, 32]);
test.expect_error(TestError::ExecutionError("FailedAssertion"));
}

#[test]
fn u32rotr_b() {
// Computes c by rotating a 32-bit representation of a to the right by b bits.
let op_base = "u32rotr";
let get_asm_op = |b: u32| format!("{op_base}.{b}");

// --- test simple case -----------------------------------------------------------------------
let a = 2_u32;
let b = 1_u32;
let test = build_op_test!(get_asm_op(b).as_str(), &[5, a as u64]);
test.expect_stack(&[1, 5]);

// --- test simple wraparound case with small a -----------------------------------------------
let a = 1_u32;
let b = 1_u32;
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[U32_BOUND >> 1]);

// --- test simple case wraparound case with max b --------------------------------------------
let a = 1_u32;
let b: u32 = 31;
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[2]);

// --- no change when a is max value (all 1s) -------------------------------------------------
let a = (U32_BOUND - 1) as u32;
let b = 2;
let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a as u64]);

// --- test b = 0 ---------------------------------------------------------------------------
let a = rand_value::<u32>();
let b = 0;

let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.rotate_right(b) as u64]);

// --- test random values ---------------------------------------------------------------------
let a = rand_value::<u32>();
let b = rand_value::<u32>() % 32;

let test = build_op_test!(get_asm_op(b).as_str(), &[a as u64]);
test.expect_stack(&[a.rotate_right(b) as u64]);
}

#[test]
fn u32rotr_b_fail() {
let op_base = "u32rotr";

test_input_out_of_bounds(format!("{}.{}", op_base, 1).as_str());
test_param_out_of_bounds(op_base, 32);
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions miden/tests/integration/operations/u32_ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ pub fn test_inputs_out_of_bounds(asm_op: &str, input_count: usize) {
}
}

/// This helper function tests a provided assembly operation which takes a single parameter
/// to ensure that it fails when that parameter is over the maximum allowed value (out of bounds).
pub fn test_param_out_of_bounds(asm_op_base: &str, gt_max_value: u64) {
let asm_op = format!("{asm_op_base}.{gt_max_value}");
let test = build_op_test!(&asm_op);
test.expect_error(TestError::AssemblyError("parameter"));
}

/// This helper function tests that when the given u32 assembly instruction is executed on
/// out-of-bounds inputs it does not fail. Each input is tested independently.
pub fn test_unchecked_execution(asm_op: &str, input_count: usize) {
Expand Down
Loading