use core::array;
use miden_core::{
field::{Algebra, PrimeCharacteristicRing},
operations::opcodes,
};
use crate::constraints::{decoder::columns::DecoderCols, stack::columns::StackCols};
#[cfg(test)]
use crate::trace::decoder::{NUM_OP_BITS, OP_BITS_RANGE};
pub const NUM_DEGREE_7_OPS: usize = 64;
pub const NUM_DEGREE_6_OPS: usize = 8;
pub const NUM_DEGREE_5_OPS: usize = 16;
pub const NUM_DEGREE_4_OPS: usize = 8;
pub const NUM_STACK_IMPACT_FLAGS: usize = 16;
const DEGREE_7_OPCODE_STARTS: usize = 0;
const DEGREE_7_OPCODE_ENDS: usize = DEGREE_7_OPCODE_STARTS + 63;
const DEGREE_6_OPCODE_STARTS: usize = DEGREE_7_OPCODE_ENDS + 1;
const DEGREE_6_OPCODE_ENDS: usize = DEGREE_6_OPCODE_STARTS + 15;
const DEGREE_5_OPCODE_STARTS: usize = DEGREE_6_OPCODE_ENDS + 1;
const DEGREE_5_OPCODE_ENDS: usize = DEGREE_5_OPCODE_STARTS + 15;
const DEGREE_4_OPCODE_STARTS: usize = DEGREE_5_OPCODE_ENDS + 1;
#[cfg(test)]
const DEGREE_4_OPCODE_ENDS: usize = DEGREE_4_OPCODE_STARTS + 31;
type OpBits<E> = [[E; 2]; 7];
pub struct OpFlags<E> {
degree7_op_flags: [E; NUM_DEGREE_7_OPS],
degree6_op_flags: [E; NUM_DEGREE_6_OPS],
degree5_op_flags: [E; NUM_DEGREE_5_OPS],
degree4_op_flags: [E; NUM_DEGREE_4_OPS],
no_shift_flags: [E; NUM_STACK_IMPACT_FLAGS],
left_shift_flags: [E; NUM_STACK_IMPACT_FLAGS],
right_shift_flags: [E; NUM_STACK_IMPACT_FLAGS],
left_shift: E,
right_shift: E,
control_flow: E,
overflow: E,
end_next: E,
repeat_next: E,
respan_next: E,
halt_next: E,
}
impl<E> OpFlags<E>
where
E: PrimeCharacteristicRing,
{
pub fn new<V>(
decoder: &DecoderCols<V>,
stack: &StackCols<V>,
decoder_next: &DecoderCols<V>,
) -> Self
where
V: Copy,
E: Algebra<V>,
{
let bits: OpBits<E> = array::from_fn(|k| {
let val = decoder.op_bits[k];
[E::ONE - val, val.into()]
});
let b32: [E; 4] = array::from_fn(|i| bits[3][i >> 1].clone() * bits[2][i & 1].clone());
let b321: [E; 8] = array::from_fn(|i| b32[i >> 1].clone() * bits[1][i & 1].clone());
let b3210: [E; 16] = array::from_fn(|i| b321[i >> 1].clone() * bits[0][i & 1].clone());
let b432: [E; 8] = array::from_fn(|i| bits[4][i >> 2].clone() * b32[i & 3].clone());
let b654: [E; 4] = array::from_fn(|i| {
bits[5][i >> 1].clone() * bits[4][i & 1].clone() * bits[6][0].clone()
});
let b654321: [E; 32] = array::from_fn(|i| b654[i >> 3].clone() * b321[i & 7].clone());
let pre_b0 = PreB0Flags {
movup_or_movdn: [
b654321[opcodes::MOVUP2 as usize >> 1].clone(), b654321[opcodes::MOVUP3 as usize >> 1].clone(), b654321[opcodes::MOVUP4 as usize >> 1].clone(), b654321[opcodes::MOVUP5 as usize >> 1].clone(), b654321[opcodes::MOVUP6 as usize >> 1].clone(), b654321[opcodes::MOVUP7 as usize >> 1].clone(), b654321[opcodes::MOVUP8 as usize >> 1].clone(), ],
swapw2_or_swapw3: b654321[opcodes::SWAPW2 as usize >> 1].clone(),
advpopw_or_expacc: b654321[opcodes::ADVPOPW as usize >> 1].clone(),
};
let degree7_op_flags: [E; NUM_DEGREE_7_OPS] =
array::from_fn(|i| b654321[i >> 1].clone() * bits[0][i & 1].clone());
let degree6_prefix = bits[6][1].clone() * bits[5][0].clone() * bits[4][0].clone();
let degree6_op_flags: [E; NUM_DEGREE_6_OPS] =
array::from_fn(|i| degree6_prefix.clone() * b321[i].clone());
let degree5_extra: E = decoder.extra[0].into();
let degree5_op_flags: [E; NUM_DEGREE_5_OPS] =
array::from_fn(|i| degree5_extra.clone() * b3210[i].clone());
let degree4_extra = decoder.extra[1];
let degree4_op_flags: [E; NUM_DEGREE_4_OPS] =
array::from_fn(|i| b432[i].clone() * degree4_extra);
let composite = Self::compute_composite_flags::<V>(
&bits,
°ree7_op_flags,
pre_b0,
°ree6_op_flags,
°ree5_op_flags,
°ree4_op_flags,
decoder,
stack,
);
let (end_next, repeat_next, respan_next, halt_next) = {
let prefix: E = decoder_next.extra[1].into();
let prefix = prefix * decoder_next.op_bits[4];
let b32_next: [E; 4] = {
let b3 = decoder_next.op_bits[3];
let b2 = decoder_next.op_bits[2];
let bits_next: [[E; 2]; 2] = [[E::ONE - b2, b2.into()], [E::ONE - b3, b3.into()]];
array::from_fn(|i| bits_next[1][i >> 1].clone() * bits_next[0][i & 1].clone())
};
(
prefix.clone() * b32_next[0].clone(), prefix.clone() * b32_next[1].clone(), prefix.clone() * b32_next[2].clone(), prefix * b32_next[3].clone(), )
};
Self {
degree7_op_flags,
degree6_op_flags,
degree5_op_flags,
degree4_op_flags,
no_shift_flags: composite.no_shift,
left_shift_flags: composite.left_shift,
right_shift_flags: composite.right_shift,
left_shift: composite.left_shift_scalar,
right_shift: composite.right_shift_scalar,
control_flow: composite.control_flow,
overflow: composite.overflow,
end_next,
repeat_next,
respan_next,
halt_next,
}
}
#[allow(clippy::too_many_arguments)]
fn compute_composite_flags<V>(
bits: &OpBits<E>,
deg7: &[E; NUM_DEGREE_7_OPS],
pre_b0: PreB0Flags<E>,
deg6: &[E; NUM_DEGREE_6_OPS],
deg5: &[E; NUM_DEGREE_5_OPS],
deg4: &[E; NUM_DEGREE_4_OPS],
decoder: &DecoderCols<V>,
stack: &StackCols<V>,
) -> CompositeFlags<E>
where
V: Copy,
E: Algebra<V>,
{
let PreB0Flags {
movup_or_movdn,
swapw2_or_swapw3,
advpopw_or_expacc,
} = pre_b0;
let op7 = |op: u8| deg7[op as usize].clone();
let op6 = |op: u8| deg6[get_op_index(op)].clone();
let op5 = |op: u8| deg5[get_op_index(op)].clone();
let op4 = |op: u8| deg4[get_op_index(op)].clone();
let prefix_01 = bits[6][0].clone() * bits[5][1].clone();
let prefix_100 = bits[6][1].clone() * bits[5][0].clone() * bits[4][0].clone();
let is_loop_end = decoder.end_block_flags().is_loop;
let end_loop_flag = op4(opcodes::END) * is_loop_end;
let no_shift_depth0 = E::sum_array::<10>(&[
op7(opcodes::NOOP),
op6(opcodes::U32ASSERT2),
op5(opcodes::MPVERIFY),
op5(opcodes::SPAN),
op5(opcodes::JOIN),
op7(opcodes::EMIT),
op4(opcodes::RESPAN),
op4(opcodes::HALT),
op4(opcodes::CALL),
op4(opcodes::END) * (E::ONE - is_loop_end),
]);
let no_shift_depth1 = deg7[0..8].iter().cloned().sum::<E>() - op7(opcodes::NOOP);
let u32_arith_group = prefix_100.clone() * bits[3][0].clone();
let no_shift_depth4 = E::sum_array::<5>(&[
movup_or_movdn[1].clone(),
advpopw_or_expacc,
swapw2_or_swapw3.clone(),
op7(opcodes::EXT2MUL),
op4(opcodes::MRUPDATE),
]);
let no_shift_depth8
= movup_or_movdn[5].clone()
+ op7(opcodes::SWAPW)
- swapw2_or_swapw3.clone();
let no_shift_depth12
= swapw2_or_swapw3
+ op5(opcodes::HPERM)
- op7(opcodes::SWAPW3);
let no_shift = accumulate_depth_deltas([
no_shift_depth0,
no_shift_depth1,
op7(opcodes::SWAP) + u32_arith_group,
movup_or_movdn[0].clone(),
no_shift_depth4,
movup_or_movdn[2].clone(),
movup_or_movdn[3].clone(),
movup_or_movdn[4].clone(),
no_shift_depth8,
movup_or_movdn[6].clone(),
E::ZERO,
E::ZERO,
no_shift_depth12,
E::ZERO,
E::ZERO,
E::ZERO,
]);
let all_mov_pairs = E::sum_array::<7>(&movup_or_movdn);
let all_movdn = all_mov_pairs.clone() * bits[0][1].clone();
let left_shift_depth1 = E::sum_array::<11>(&[
op7(opcodes::ASSERT),
all_movdn,
op7(opcodes::DROP),
op7(opcodes::MSTORE),
op7(opcodes::MSTOREW),
deg7[47].clone(),
op5(opcodes::SPLIT),
op5(opcodes::LOOP),
end_loop_flag.clone(),
op5(opcodes::DYN),
op5(opcodes::DYNCALL),
]);
let left_shift_depth2 = deg7[32..40].iter().cloned().sum::<E>() - op7(opcodes::ASSERT);
let left_shift_depth3
= op7(opcodes::CSWAP)
+ op6(opcodes::U32ADD3)
+ op6(opcodes::U32MADD)
- op7(opcodes::MOVDN2);
let left_shift = accumulate_depth_deltas([
E::ZERO,
left_shift_depth1,
left_shift_depth2,
left_shift_depth3,
-op7(opcodes::MOVDN3),
op7(opcodes::MLOADW) - op7(opcodes::MOVDN4),
-op7(opcodes::MOVDN5),
-op7(opcodes::MOVDN6),
-op7(opcodes::MOVDN7),
op7(opcodes::CSWAPW) - op7(opcodes::MOVDN8),
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
]);
let all_movup = all_mov_pairs * bits[0][0].clone();
let right_shift_depth0
= deg7[48..64].iter().cloned().sum::<E>()
+ op5(opcodes::PUSH)
+ all_movup;
let right_shift = accumulate_depth_deltas([
right_shift_depth0,
op6(opcodes::U32SPLIT),
-op7(opcodes::MOVUP2),
-op7(opcodes::MOVUP3),
-op7(opcodes::MOVUP4),
-op7(opcodes::MOVUP5),
-op7(opcodes::MOVUP6),
-op7(opcodes::MOVUP7),
-op7(opcodes::MOVUP8),
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
E::ZERO,
]);
let prefix_011 = prefix_01.clone() * bits[4][1].clone();
let right_shift_scalar
= prefix_011
+ op5(opcodes::PUSH)
+ op6(opcodes::U32SPLIT);
let prefix_010 = prefix_01 * bits[4][0].clone();
let u32_add3_madd_group = prefix_100 * bits[3][1].clone() * bits[2][1].clone();
let left_shift_scalar = E::sum_array::<7>(&[
prefix_010,
u32_add3_madd_group,
op5(opcodes::SPLIT),
op5(opcodes::LOOP),
op4(opcodes::REPEAT),
end_loop_flag,
op5(opcodes::DYN),
]);
let control_flow = E::sum_array::<6>(&[
bits[3][0].clone() * bits[2][1].clone() * decoder.extra[0],
bits[4][1].clone() * decoder.extra[1],
op5(opcodes::DYNCALL),
op5(opcodes::DYN),
op4(opcodes::SYSCALL),
op4(opcodes::CALL),
]);
let overflow: E = stack.b0.into();
let overflow = (overflow - E::from_u64(16)) * stack.h0;
CompositeFlags {
no_shift,
left_shift,
right_shift,
left_shift_scalar,
right_shift_scalar,
control_flow,
overflow,
}
}
#[inline(always)]
pub fn no_shift_at(&self, index: usize) -> E {
self.no_shift_flags[index].clone()
}
#[inline(always)]
pub fn left_shift_at(&self, index: usize) -> E {
self.left_shift_flags[index].clone()
}
#[inline(always)]
pub fn right_shift_at(&self, index: usize) -> E {
self.right_shift_flags[index].clone()
}
#[inline(always)]
pub fn right_shift(&self) -> E {
self.right_shift.clone()
}
#[inline(always)]
pub fn left_shift(&self) -> E {
self.left_shift.clone()
}
#[inline(always)]
pub fn control_flow(&self) -> E {
self.control_flow.clone()
}
#[inline(always)]
pub fn overflow(&self) -> E {
self.overflow.clone()
}
#[inline(always)]
pub fn end_next(&self) -> E {
self.end_next.clone()
}
#[inline(always)]
pub fn repeat_next(&self) -> E {
self.repeat_next.clone()
}
#[inline(always)]
pub fn respan_next(&self) -> E {
self.respan_next.clone()
}
#[inline(always)]
pub fn halt_next(&self) -> E {
self.halt_next.clone()
}
}
macro_rules! op_flag_getters {
($array:ident, $( $(#[$meta:meta])* $name:ident => $op:expr ),* $(,)?) => {
$(
$(#[$meta])*
#[inline(always)]
pub fn $name(&self) -> E {
self.$array[get_op_index($op)].clone()
}
)*
};
}
impl<E: PrimeCharacteristicRing> OpFlags<E> {
op_flag_getters!(degree7_op_flags,
#[expect(dead_code)]
noop => opcodes::NOOP,
eqz => opcodes::EQZ,
neg => opcodes::NEG,
inv => opcodes::INV,
incr => opcodes::INCR,
not => opcodes::NOT,
#[expect(dead_code)]
mload => opcodes::MLOAD,
swap => opcodes::SWAP,
caller => opcodes::CALLER,
movup2 => opcodes::MOVUP2,
movdn2 => opcodes::MOVDN2,
movup3 => opcodes::MOVUP3,
movdn3 => opcodes::MOVDN3,
#[expect(dead_code)]
advpopw => opcodes::ADVPOPW,
expacc => opcodes::EXPACC,
movup4 => opcodes::MOVUP4,
movdn4 => opcodes::MOVDN4,
movup5 => opcodes::MOVUP5,
movdn5 => opcodes::MOVDN5,
movup6 => opcodes::MOVUP6,
movdn6 => opcodes::MOVDN6,
movup7 => opcodes::MOVUP7,
movdn7 => opcodes::MOVDN7,
swapw => opcodes::SWAPW,
movup8 => opcodes::MOVUP8,
movdn8 => opcodes::MOVDN8,
swapw2 => opcodes::SWAPW2,
swapw3 => opcodes::SWAPW3,
swapdw => opcodes::SWAPDW,
ext2mul => opcodes::EXT2MUL,
assert_op => opcodes::ASSERT,
eq => opcodes::EQ,
add => opcodes::ADD,
mul => opcodes::MUL,
and => opcodes::AND,
or => opcodes::OR,
#[expect(dead_code)]
u32and => opcodes::U32AND,
#[expect(dead_code)]
u32xor => opcodes::U32XOR,
frie2f4 => opcodes::FRIE2F4,
#[expect(dead_code)]
drop => opcodes::DROP,
cswap => opcodes::CSWAP,
cswapw => opcodes::CSWAPW,
#[expect(dead_code)]
mloadw => opcodes::MLOADW,
#[expect(dead_code)]
mstore => opcodes::MSTORE,
#[expect(dead_code)]
mstorew => opcodes::MSTOREW,
pad => opcodes::PAD,
dup => opcodes::DUP0,
dup1 => opcodes::DUP1,
dup2 => opcodes::DUP2,
dup3 => opcodes::DUP3,
dup4 => opcodes::DUP4,
dup5 => opcodes::DUP5,
dup6 => opcodes::DUP6,
dup7 => opcodes::DUP7,
dup9 => opcodes::DUP9,
dup11 => opcodes::DUP11,
dup13 => opcodes::DUP13,
dup15 => opcodes::DUP15,
#[expect(dead_code)]
advpop => opcodes::ADVPOP,
sdepth => opcodes::SDEPTH,
clk => opcodes::CLK,
);
op_flag_getters!(degree6_op_flags,
u32add => opcodes::U32ADD,
u32sub => opcodes::U32SUB,
u32mul => opcodes::U32MUL,
u32div => opcodes::U32DIV,
u32split => opcodes::U32SPLIT,
u32assert2 => opcodes::U32ASSERT2,
u32add3 => opcodes::U32ADD3,
u32madd => opcodes::U32MADD,
);
op_flag_getters!(degree5_op_flags,
#[expect(dead_code)]
hperm => opcodes::HPERM,
#[expect(dead_code)]
mpverify => opcodes::MPVERIFY,
split => opcodes::SPLIT,
loop_op => opcodes::LOOP,
span => opcodes::SPAN,
#[expect(dead_code)]
join => opcodes::JOIN,
push => opcodes::PUSH,
dyn_op => opcodes::DYN,
dyncall => opcodes::DYNCALL,
#[expect(dead_code)]
evalcircuit => opcodes::EVALCIRCUIT,
#[expect(dead_code)]
log_precompile => opcodes::LOGPRECOMPILE,
hornerbase => opcodes::HORNERBASE,
hornerext => opcodes::HORNEREXT,
#[expect(dead_code)]
mstream => opcodes::MSTREAM,
#[expect(dead_code)]
pipe => opcodes::PIPE,
);
op_flag_getters!(degree4_op_flags,
#[expect(dead_code)]
mrupdate => opcodes::MRUPDATE,
call => opcodes::CALL,
syscall => opcodes::SYSCALL,
end => opcodes::END,
repeat => opcodes::REPEAT,
respan => opcodes::RESPAN,
halt => opcodes::HALT,
cryptostream => opcodes::CRYPTOSTREAM,
);
}
struct PreB0Flags<E> {
movup_or_movdn: [E; 7],
swapw2_or_swapw3: E,
advpopw_or_expacc: E,
}
struct CompositeFlags<E> {
no_shift: [E; NUM_STACK_IMPACT_FLAGS],
left_shift: [E; NUM_STACK_IMPACT_FLAGS],
right_shift: [E; NUM_STACK_IMPACT_FLAGS],
left_shift_scalar: E,
right_shift_scalar: E,
control_flow: E,
overflow: E,
}
pub const fn get_op_index(opcode: u8) -> usize {
let opcode = opcode as usize;
if opcode <= DEGREE_7_OPCODE_ENDS {
opcode
} else if opcode <= DEGREE_6_OPCODE_ENDS {
(opcode - DEGREE_6_OPCODE_STARTS) / 2
} else if opcode <= DEGREE_5_OPCODE_ENDS {
opcode - DEGREE_5_OPCODE_STARTS
} else {
(opcode - DEGREE_4_OPCODE_STARTS) / 4
}
}
fn accumulate_depth_deltas<const N: usize, E: PrimeCharacteristicRing>(
mut deltas: [E; N],
) -> [E; N] {
for i in 1..N {
deltas[i] = deltas[i - 1].clone() + deltas[i].clone();
}
deltas
}
#[cfg(test)]
pub fn generate_test_row(opcode: usize) -> crate::MainCols<miden_core::Felt> {
use miden_core::{Felt, ZERO};
use crate::trace::{TRACE_WIDTH, decoder::OP_BITS_EXTRA_COLS_RANGE};
let op_bits = get_op_bits(opcode);
let mut row = [ZERO; TRACE_WIDTH];
for (i, &bit) in op_bits.iter().enumerate() {
row[OP_BITS_RANGE.start + crate::trace::DECODER_TRACE_OFFSET + i] = bit;
}
let bit_6 = op_bits[6];
let bit_5 = op_bits[5];
let bit_4 = op_bits[4];
row[OP_BITS_EXTRA_COLS_RANGE.start + crate::trace::DECODER_TRACE_OFFSET] =
bit_6 * (Felt::ONE - bit_5) * bit_4;
row[OP_BITS_EXTRA_COLS_RANGE.start + 1 + crate::trace::DECODER_TRACE_OFFSET] = bit_6 * bit_5;
unsafe { core::mem::transmute::<[Felt; TRACE_WIDTH], crate::MainCols<Felt>>(row) }
}
#[cfg(test)]
pub fn get_op_bits(opcode: usize) -> [miden_core::Felt; NUM_OP_BITS] {
use miden_core::{Felt, ZERO};
let mut opcode_copy = opcode;
let mut bit_array = [ZERO; NUM_OP_BITS];
for bit in bit_array.iter_mut() {
*bit = Felt::new_unchecked((opcode_copy & 1) as u64);
opcode_copy >>= 1;
}
assert_eq!(opcode_copy, 0, "Opcode must be 7 bits");
bit_array
}
#[cfg(test)]
mod tests;