use miden_assembly_syntax::{
ParsingError,
debuginfo::{SourceSpan, Span},
diagnostics::{RelatedLabel, Report},
};
use miden_core::{
Felt, ZERO,
events::SystemEvent,
operations::Operation::{self, *},
};
use super::{field_ops::append_pow2_op, push_u32_value};
use crate::{
MAX_U32_ROTATE_VALUE, MAX_U32_SHIFT_VALUE, ProcedureContext,
basic_block_builder::BasicBlockBuilder,
};
#[derive(PartialEq, Eq)]
pub enum U32OpMode {
Wrapping,
Overflowing,
}
pub fn u32testw(span_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops = [
Dup3, U32split, Drop, Eqz,
Dup3, U32split, Drop, Eqz, And,
Dup2, U32split, Drop, Eqz, And,
Dup1, U32split, Drop, Eqz, And,
];
span_builder.push_ops(ops);
}
pub fn u32assertw(span_builder: &mut BasicBlockBuilder, err_code: Felt) {
#[rustfmt::skip]
let ops = [
U32assert2(err_code),
MovUp3, MovUp3,
U32assert2(err_code),
MovUp3, MovUp3,
];
span_builder.push_ops(ops);
}
pub fn u32add(span_builder: &mut BasicBlockBuilder, op_mode: U32OpMode, imm: Option<u32>) {
u32widening_add(span_builder, imm);
if matches!(op_mode, U32OpMode::Overflowing) {
span_builder.push_op(Swap);
} else {
span_builder.push_ops([Swap, Drop]);
}
}
pub fn u32widening_add(span_builder: &mut BasicBlockBuilder, imm: Option<u32>) {
if let Some(imm) = imm {
push_u32_value(span_builder, imm);
}
span_builder.push_op(U32add);
}
pub fn u32overflowing_add(span_builder: &mut BasicBlockBuilder, imm: Option<u32>) {
u32widening_add(span_builder, imm);
span_builder.push_op(Swap);
}
pub fn u32widening_add3(span_builder: &mut BasicBlockBuilder) {
span_builder.push_op(U32add3);
}
pub fn u32overflowing_add3(span_builder: &mut BasicBlockBuilder) {
span_builder.push_op(U32add3);
span_builder.push_op(Swap);
}
pub fn u32wrapping_add3(span_builder: &mut BasicBlockBuilder) {
span_builder.push_op(U32add3);
span_builder.push_ops([Swap, Drop]);
}
pub fn u32sub(span_builder: &mut BasicBlockBuilder, op_mode: U32OpMode, imm: Option<u32>) {
handle_arithmetic_operation(span_builder, U32sub, op_mode, imm);
}
pub fn u32mul(span_builder: &mut BasicBlockBuilder, op_mode: U32OpMode, imm: Option<u32>) {
handle_arithmetic_operation(span_builder, U32mul, op_mode, imm);
}
pub fn u32div(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<Span<u32>>,
) -> Result<(), Report> {
handle_division(span_builder, proc_ctx, imm)?;
span_builder.push_op(Drop);
Ok(())
}
pub fn u32mod(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<Span<u32>>,
) -> Result<(), Report> {
handle_division(span_builder, proc_ctx, imm)?;
span_builder.push_ops([Swap, Drop]);
Ok(())
}
pub fn u32divmod(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<Span<u32>>,
) -> Result<(), Report> {
handle_division(span_builder, proc_ctx, imm)
}
pub fn u32not(span_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops = [
Push(Felt::from_u32(u32::MAX)),
U32assert2(ZERO),
Swap,
U32sub,
Drop,
];
span_builder.push_ops(ops);
}
pub fn u32shl(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<u8>,
span: SourceSpan,
) -> Result<(), Report> {
prepare_bitwise::<MAX_U32_SHIFT_VALUE>(span_builder, proc_ctx, imm, span)?;
if imm != Some(0) {
span_builder.push_ops([U32mul, Swap, Drop]);
}
Ok(())
}
pub fn u32shr(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<u8>,
span: SourceSpan,
) -> Result<(), Report> {
prepare_bitwise::<MAX_U32_SHIFT_VALUE>(span_builder, proc_ctx, imm, span)?;
if imm != Some(0) {
span_builder.push_ops([U32div, Drop]);
}
Ok(())
}
pub fn u32rotl(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<u8>,
span: SourceSpan,
) -> Result<(), Report> {
prepare_bitwise::<MAX_U32_ROTATE_VALUE>(span_builder, proc_ctx, imm, span)?;
if imm != Some(0) {
span_builder.push_ops([U32mul, Add]);
}
Ok(())
}
pub fn u32rotr(
span_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<u8>,
span: SourceSpan,
) -> Result<(), Report> {
match imm {
Some(0) => {
span_builder.push_op(Noop);
},
Some(imm) => {
if imm == 0 || imm > MAX_U32_ROTATE_VALUE {
return Err(RelatedLabel::error("invalid argument")
.with_labeled_span(span, "this instruction argument is out of range")
.with_help(format!("value must be in the range 0..={MAX_U32_ROTATE_VALUE}"))
.with_source_file(proc_ctx.source_manager().get(span.source_id()).ok())
.into());
}
span_builder.push_op(Push(Felt::new(1 << (32 - imm))));
span_builder.push_ops([U32mul, Add]);
},
None => {
span_builder.push_ops([Push(Felt::new(32)), Swap, U32sub, Drop]);
append_pow2_op(span_builder);
span_builder.push_ops([Mul, U32split, Add]);
},
}
Ok(())
}
pub fn u32popcnt(span_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops = [
Dup0,
Push(Felt::new(1 << 1)), U32div, Drop,
Push(Felt::new(0x55555555)),
U32and,
U32sub, Drop,
Dup0,
Push(Felt::new(1 << 2)), U32div, Drop,
Push(Felt::new(0x33333333)),
U32and,
Swap,
Push(Felt::new(0x33333333)),
U32and,
U32add, Swap, Drop,
Dup0,
Push(Felt::new(1 << 4)), U32div, Drop,
U32add, Swap, Drop,
Push(Felt::new(0x0F0F0F0F)),
U32and,
Push(Felt::new(0x01010101)),
U32mul, Swap, Drop,
Push(Felt::new(1 << 24)), U32div, Drop
];
span_builder.push_ops(ops);
}
pub fn u32clz(block_builder: &mut BasicBlockBuilder) {
block_builder.push_system_event(SystemEvent::U32Clz);
block_builder.push_op(AdvPop);
verify_clz(block_builder);
}
pub fn u32ctz(block_builder: &mut BasicBlockBuilder) {
block_builder.push_system_event(SystemEvent::U32Ctz);
block_builder.push_op(AdvPop);
verify_ctz(block_builder);
}
pub fn u32clo(block_builder: &mut BasicBlockBuilder) {
block_builder.push_system_event(SystemEvent::U32Clo);
block_builder.push_op(AdvPop);
verify_clo(block_builder);
}
pub fn u32cto(block_builder: &mut BasicBlockBuilder) {
block_builder.push_system_event(SystemEvent::U32Cto);
block_builder.push_op(AdvPop);
verify_cto(block_builder);
}
fn handle_arithmetic_operation(
block_builder: &mut BasicBlockBuilder,
op: Operation,
op_mode: U32OpMode,
imm: Option<u32>,
) {
if let Some(imm) = imm {
push_u32_value(block_builder, imm);
}
block_builder.push_op(op);
if matches!(op_mode, U32OpMode::Wrapping) {
if matches!(op, U32mul | U32add) {
block_builder.push_ops([Swap, Drop]);
} else {
block_builder.push_op(Drop);
}
}
}
fn handle_division(
block_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<Span<u32>>,
) -> Result<(), Report> {
if let Some(imm) = imm {
if imm == 0 {
let imm_span = imm.span();
let source_file = proc_ctx.source_manager().get(imm_span.source_id()).ok();
let error = Report::new(ParsingError::DivisionByZero { span: imm_span });
return if let Some(source_file) = source_file {
Err(error.with_source_code(source_file))
} else {
Err(error)
};
}
push_u32_value(block_builder, imm.into_inner());
}
block_builder.push_op(U32div);
Ok(())
}
fn prepare_bitwise<const MAX_VALUE: u8>(
block_builder: &mut BasicBlockBuilder,
proc_ctx: &ProcedureContext,
imm: Option<u8>,
span: SourceSpan,
) -> Result<(), Report> {
match imm {
Some(0) => {
block_builder.push_op(Noop);
},
Some(imm) => {
if imm == 0 || imm > MAX_VALUE {
return Err(RelatedLabel::error("invalid argument")
.with_labeled_span(span, "this instruction argument is out of range")
.with_help(format!("value must be in the range 0..={MAX_VALUE}"))
.with_source_file(proc_ctx.source_manager().get(span.source_id()).ok())
.into());
}
block_builder.push_op(Push(Felt::new(1 << imm)));
},
None => {
append_pow2_op(block_builder);
},
}
Ok(())
}
fn verify_clz(block_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops_group_1 = [
Push(Felt::from_u8(32)), Dup1, Neg, Add ];
block_builder.push_ops(ops_group_1);
append_pow2_op(block_builder);
#[rustfmt::skip]
let ops_group_2 = [
Push(Felt::from_u8(1)), Neg, Add,
Push(Felt::from_u8(2)), U32div, Drop,
Dup0, Incr,
Push(Felt::from_u32(u32::MAX)), MovUp2, Neg, Add,
Dup3, Eqz, MovDn3, MovUp4, U32and,
Dup2, Push(Felt::from_u8(32)), Eq, MovUp4, Dup1, Eq, Assert(ZERO),
MovDn2, Eq, Or, Assert(ZERO),
];
block_builder.push_ops(ops_group_2);
}
fn verify_clo(block_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops_group_1 = [
Push(Felt::from_u8(32)), Dup1, Neg, Add ];
block_builder.push_ops(ops_group_1);
append_pow2_op(block_builder);
#[rustfmt::skip]
let ops_group_2 = [
Push(Felt::from_u8(1)), Neg, Add,
Dup0, Push(Felt::from_u8(2)), U32div, Drop,
Push(Felt::from_u32(u32::MAX)), Swap, Neg, Add,
MovUp3, U32and,
Push(Felt::from_u32(u32::MAX)), MovUp2, Neg, Add,
Eq, Assert(ZERO),
];
block_builder.push_ops(ops_group_2);
}
fn verify_ctz(block_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops_group_1 = [
Swap, Dup1, ];
block_builder.push_ops(ops_group_1);
append_pow2_op(block_builder);
#[rustfmt::skip]
let ops_group_2 = [
Dup0,
Pad, Incr, Neg, Add,
Swap, U32split, Swap, Drop,
Dup0, MovUp2, Add,
MovUp2, U32and,
Eq, Assert(ZERO), ];
block_builder.push_ops(ops_group_2);
}
fn verify_cto(block_builder: &mut BasicBlockBuilder) {
#[rustfmt::skip]
let ops_group_1 = [
Swap, Dup1, ];
block_builder.push_ops(ops_group_1);
append_pow2_op(block_builder);
#[rustfmt::skip]
let ops_group_2 = [
Dup0,
Pad, Incr, Neg, Add,
Swap, U32split, Swap, Drop,
Dup1, Add,
MovUp2, U32and,
Eq, Assert(ZERO), ];
block_builder.push_ops(ops_group_2);
}
pub fn u32lt(block_builder: &mut BasicBlockBuilder) {
compute_lt(block_builder);
}
pub fn u32lte(block_builder: &mut BasicBlockBuilder) {
block_builder.push_op(Swap);
compute_lt(block_builder);
block_builder.push_op(Not);
}
pub fn u32gt(block_builder: &mut BasicBlockBuilder) {
block_builder.push_op(Swap);
compute_lt(block_builder);
}
pub fn u32gte(block_builder: &mut BasicBlockBuilder) {
compute_lt(block_builder);
block_builder.push_op(Not);
}
pub fn u32min(block_builder: &mut BasicBlockBuilder) {
compute_max_and_min(block_builder);
block_builder.push_op(Drop);
}
pub fn u32max(block_builder: &mut BasicBlockBuilder) {
compute_max_and_min(block_builder);
block_builder.push_ops([Swap, Drop]);
}
fn compute_lt(block_builder: &mut BasicBlockBuilder) {
block_builder.push_ops([U32sub, Swap, Drop])
}
fn compute_max_and_min(block_builder: &mut BasicBlockBuilder) {
block_builder.push_ops([Dup1, Dup1]);
#[rustfmt::skip]
block_builder.push_ops([
U32sub, Swap, Drop,
Eqz, CSwap,
]);
}