use miden_core::field::PrimeCharacteristicRing;
use miden_crypto::stark::air::AirBuilder;
use crate::{
CoreCols, MidenAirBuilder,
constraints::{constants::*, op_flags::OpFlags, utils::BoolNot},
};
pub fn enforce_main<AB>(
builder: &mut AB,
local: &CoreCols<AB::Var>,
next: &CoreCols<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: MidenAirBuilder,
{
builder.when_first_row().assert_eq(local.stack.b0, F_16);
builder.when_last_row().assert_eq(local.stack.b0, F_16);
builder.when_first_row().assert_zero(local.stack.b1);
builder.when_last_row().assert_zero(local.stack.b1);
enforce_stack_depth_constraints(builder, local, next, op_flags);
{
let depth = local.stack.b0;
builder.when(op_flags.overflow().not()).assert_eq(depth, F_16);
}
enforce_overflow_index_constraints(builder, local, next, op_flags);
}
fn enforce_stack_depth_constraints<AB>(
builder: &mut AB,
local: &CoreCols<AB::Var>,
next: &CoreCols<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: MidenAirBuilder,
{
let depth = local.stack.b0;
let depth_next = next.stack.b0;
let call_or_dyncall_or_syscall = op_flags.call() + op_flags.dyncall() + op_flags.syscall();
let is_call_or_dyncall_end = local.decoder.hasher_state[6];
let is_syscall_end = local.decoder.hasher_state[7];
let call_or_dyncall_or_syscall_end = op_flags.end() * (is_call_or_dyncall_end + is_syscall_end);
let normal_mask =
AB::Expr::ONE - call_or_dyncall_or_syscall.clone() - call_or_dyncall_or_syscall_end;
let depth_delta_part = (depth_next.into() - depth.into()) * normal_mask;
let left_shift_part = op_flags.left_shift() * op_flags.overflow();
let right_shift_part = op_flags.right_shift();
let call_part = call_or_dyncall_or_syscall * (depth_next.into() - F_16);
builder
.when_transition()
.assert_zero(depth_delta_part + left_shift_part - right_shift_part + call_part);
}
fn enforce_overflow_index_constraints<AB>(
builder: &mut AB,
local: &CoreCols<AB::Var>,
next: &CoreCols<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: MidenAirBuilder,
{
let overflow_addr_next = next.stack.b1;
let clk = local.system.clk;
let last_stack_item_next = next.stack.get(15);
builder.when(op_flags.right_shift()).assert_eq(overflow_addr_next, clk);
builder
.when(op_flags.overflow().not())
.when(op_flags.left_shift())
.assert_zero(last_stack_item_next);
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use miden_core::{
Felt, ONE, ZERO,
field::{PrimeCharacteristicRing, QuadFelt},
operations::opcodes,
};
use super::enforce_main;
use crate::constraints::{
columns::CoreCols,
op_flags::{OpFlags, generate_test_row},
stack::test_utils::ConstraintEvalBuilder,
};
fn eval_stack_overflow(local: &CoreCols<Felt>, next: &CoreCols<Felt>) -> Vec<QuadFelt> {
let mut builder = ConstraintEvalBuilder::new();
let op_flags = OpFlags::new(&local.decoder, &local.stack, &next.decoder);
enforce_main(&mut builder, local, next, &op_flags);
builder.evaluations
}
#[test]
fn frie2f4_decrements_non_empty_overflow_depth() {
let mut local = generate_test_row(opcodes::FRIE2F4.into());
local.stack.b0 = Felt::new_unchecked(17);
local.stack.h0 = ONE;
let mut next = generate_test_row(0);
next.stack.b0 = Felt::new_unchecked(16);
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
next.stack.b0 = Felt::new_unchecked(17);
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"FRIE2F4 must decrement stack depth when overflow is non-empty"
);
}
#[test]
fn frie2f4_zeros_s15_when_overflow_is_empty() {
let mut local = generate_test_row(opcodes::FRIE2F4.into());
local.stack.b0 = Felt::new_unchecked(16);
local.stack.h0 = ZERO;
let mut next = generate_test_row(0);
next.stack.b0 = Felt::new_unchecked(16);
next.stack.top[15] = ZERO;
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
next.stack.top[15] = ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"FRIE2F4 must zero s15 when no overflow item can be restored"
);
}
}