use miden_core::field::PrimeCharacteristicRing;
use miden_crypto::stark::air::AirBuilder;
use p3_field::Dup;
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 end_flags = local.decoder.end_block_flags();
let caller_frame_end = op_flags.end() * end_flags.restores_caller_frame;
let normal_mask = AB::Expr::ONE - call_or_dyncall_or_syscall.dup() - caller_frame_end;
let depth_delta_part = (depth_next - depth) * 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 - 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 = local.stack.b1;
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);
let context_start = op_flags.call() + op_flags.dyncall() + op_flags.syscall();
builder
.when_transition()
.when(context_start.dup())
.assert_zero(overflow_addr_next);
let end_flags = local.decoder.end_block_flags();
let caller_frame_end = op_flags.end() * end_flags.restores_caller_frame;
let pointer_changes = context_start
+ caller_frame_end
+ op_flags.right_shift()
+ op_flags.left_shift() * op_flags.overflow();
builder
.when_transition()
.when(pointer_changes.not())
.assert_eq(overflow_addr_next, overflow_addr);
let fills_bottom_slot = op_flags.left_shift() + op_flags.dyncall();
builder
.when(op_flags.overflow().not())
.when(fills_bottom_slot)
.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"
);
}
#[test]
fn dyncall_zeros_s15_when_overflow_is_empty() {
let mut local = generate_test_row(opcodes::DYNCALL.into());
local.stack.b0 = Felt::new_unchecked(16);
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.top[15] = ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"DYNCALL must zero s15 when no overflow item can be restored"
);
}
#[test]
fn noop_preserves_overflow_address() {
let mut local = generate_test_row(opcodes::NOOP.into());
local.stack.b0 = Felt::new_unchecked(17);
local.stack.b1 = Felt::new_unchecked(11);
local.stack.h0 = ONE;
let mut next = generate_test_row(0);
next.stack.b0 = Felt::new_unchecked(17);
next.stack.b1 = local.stack.b1;
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
next.stack.b1 += ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"NOOP must preserve the overflow address"
);
}
#[test]
fn call_family_resets_overflow_address() {
for opcode in [opcodes::CALL, opcodes::DYNCALL, opcodes::SYSCALL] {
let mut local = generate_test_row(opcode.into());
local.stack.b0 = Felt::new_unchecked(17);
local.stack.b1 = Felt::new_unchecked(11);
local.stack.h0 = ONE;
let mut next = generate_test_row(0);
next.stack.b0 = Felt::new_unchecked(16);
next.stack.b1 = ZERO;
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
next.stack.b1 = ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"opcode {opcode} must reset the overflow address"
);
}
}
#[test]
fn call_end_allows_overflow_address_restoration() {
let mut local = generate_test_row(opcodes::END.into());
local.decoder.hasher_state[6] = ONE;
local.stack.b0 = Felt::new_unchecked(17);
local.stack.b1 = Felt::new_unchecked(11);
local.stack.h0 = ONE;
let mut next = generate_test_row(0);
next.stack.b0 = Felt::new_unchecked(23);
next.stack.b1 = Felt::new_unchecked(7);
next.stack.h0 = ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
}
#[test]
fn continuation_end_preserves_overflow_address() {
let mut local = generate_test_row(opcodes::END.into());
local.stack.b0 = Felt::new_unchecked(17);
local.stack.b1 = Felt::new_unchecked(11);
local.stack.h0 = ONE;
let mut next = generate_test_row(0);
next.stack.b0 = local.stack.b0;
next.stack.b1 = local.stack.b1;
next.stack.h0 = ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
next.stack.b1 += ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"a continuation END must preserve the overflow address"
);
}
#[test]
fn continuation_end_preserves_stack_depth() {
let mut local = generate_test_row(opcodes::END.into());
local.stack.b0 = Felt::new_unchecked(17);
local.stack.b1 = Felt::new_unchecked(11);
local.stack.h0 = ONE;
let mut next = generate_test_row(0);
next.stack.b0 = local.stack.b0;
next.stack.b1 = local.stack.b1;
next.stack.h0 = ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(evaluations.iter().all(|value| *value == QuadFelt::ZERO));
next.stack.b0 += ONE;
let evaluations = eval_stack_overflow(&local, &next);
assert!(
evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"a continuation END must preserve stack depth"
);
}
}