use core::array;
use miden_core::{
Felt,
field::{Algebra, PrimeCharacteristicRing},
operations::opcodes,
};
use crate::constraints::{
decoder::columns::DecoderCols, op_flags::get_op_index, stack::columns::StackCols,
};
pub struct LookupOpFlags<E> {
end: E,
repeat: E,
respan: E,
call: E,
syscall: E,
mrupdate: E,
cryptostream: E,
join: E,
split: E,
span: E,
loop_op: E,
dyn_op: E,
dyncall: E,
push: E,
hperm: E,
mpverify: E,
mstream: E,
pipe: E,
evalcircuit: E,
log_precompile: E,
hornerbase: E,
hornerext: E,
mload: E,
mstore: E,
mloadw: E,
mstorew: E,
u32and: E,
u32xor: E,
end_next: E,
repeat_next: E,
respan_next: E,
halt_next: E,
left_shift: E,
right_shift: E,
overflow: E,
u32_rc_op: E,
}
impl<E> LookupOpFlags<E>
where
E: PrimeCharacteristicRing + Clone,
{
pub fn from_main_cols<V>(
decoder: &DecoderCols<V>,
stack: &StackCols<V>,
decoder_next: &DecoderCols<V>,
) -> Self
where
V: Copy,
E: Algebra<V>,
{
let bits: [[E; 2]; 7] = 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_0 = bits[6][0].clone() * bits[5][0].clone() * bits[4][0].clone();
let b654_2 = bits[6][0].clone() * bits[5][1].clone() * bits[4][0].clone();
let deg7 = |b654: &E, op: u8| -> E {
let op = op as usize;
b654.clone() * b321[(op >> 1) & 7].clone() * bits[0][op & 1].clone()
};
let mload = deg7(&b654_0, opcodes::MLOAD);
let u32and = deg7(&b654_2, opcodes::U32AND);
let u32xor = deg7(&b654_2, opcodes::U32XOR);
let mloadw = deg7(&b654_2, opcodes::MLOADW);
let mstore = deg7(&b654_2, opcodes::MSTORE);
let mstorew = deg7(&b654_2, opcodes::MSTOREW);
let deg5_extra: E = decoder.extra[0].into();
let deg5 = |op: u8| -> E { deg5_extra.clone() * b3210[get_op_index(op)].clone() };
let hperm = deg5(opcodes::HPERM);
let mpverify = deg5(opcodes::MPVERIFY);
let pipe = deg5(opcodes::PIPE);
let mstream = deg5(opcodes::MSTREAM);
let split = deg5(opcodes::SPLIT);
let loop_op = deg5(opcodes::LOOP);
let span = deg5(opcodes::SPAN);
let join = deg5(opcodes::JOIN);
let dyn_op = deg5(opcodes::DYN);
let push = deg5(opcodes::PUSH);
let dyncall = deg5(opcodes::DYNCALL);
let evalcircuit = deg5(opcodes::EVALCIRCUIT);
let log_precompile = deg5(opcodes::LOGPRECOMPILE);
let hornerbase = deg5(opcodes::HORNERBASE);
let hornerext = deg5(opcodes::HORNEREXT);
let deg4_extra: E = decoder.extra[1].into();
let deg4 = |op: u8| -> E { b432[get_op_index(op)].clone() * deg4_extra.clone() };
let end = deg4(opcodes::END);
let repeat = deg4(opcodes::REPEAT);
let respan = deg4(opcodes::RESPAN);
let call = deg4(opcodes::CALL);
let syscall = deg4(opcodes::SYSCALL);
let mrupdate = deg4(opcodes::MRUPDATE);
let cryptostream = deg4(opcodes::CRYPTOSTREAM);
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 b3n: E = decoder_next.op_bits[3].into();
let b2n: E = decoder_next.op_bits[2].into();
let nb3n = E::ONE - b3n.clone();
let nb2n = E::ONE - b2n.clone();
(
prefix.clone() * nb3n.clone() * nb2n.clone(), prefix.clone() * nb3n * b2n.clone(), prefix.clone() * b3n.clone() * nb2n, prefix * b3n * b2n, )
};
let u32_rc_op = bits[6][1].clone() * bits[5][0].clone() * bits[4][0].clone();
let u32split = u32_rc_op.clone() * b321[get_op_index(opcodes::U32SPLIT)].clone();
let prefix_01 = bits[6][0].clone() * bits[5][1].clone();
let prefix_011 = prefix_01.clone() * bits[4][1].clone();
let right_shift = prefix_011 + push.clone() + u32split;
let prefix_010 = prefix_01 * bits[4][0].clone();
let u32_add3_madd_group = u32_rc_op.clone() * bits[3][1].clone() * bits[2][1].clone();
let is_loop = decoder.end_block_flags().is_loop;
let end_loop = end.clone() * is_loop;
let left_shift = prefix_010
+ u32_add3_madd_group
+ split.clone()
+ loop_op.clone()
+ repeat.clone()
+ end_loop
+ dyn_op.clone();
let b0: E = stack.b0.into();
let overflow = (b0 - E::from_u64(16)) * stack.h0;
Self {
end,
repeat,
respan,
call,
syscall,
mrupdate,
cryptostream,
join,
split,
span,
loop_op,
dyn_op,
dyncall,
push,
hperm,
mpverify,
mstream,
pipe,
evalcircuit,
log_precompile,
hornerbase,
hornerext,
mload,
mstore,
mloadw,
mstorew,
u32and,
u32xor,
end_next,
repeat_next,
respan_next,
halt_next,
left_shift,
right_shift,
overflow,
u32_rc_op,
}
}
}
impl LookupOpFlags<Felt> {
pub fn from_boolean_row(
decoder: &DecoderCols<Felt>,
stack: &StackCols<Felt>,
decoder_next: &DecoderCols<Felt>,
) -> Self {
let opcode = decode_opcode_u8(&decoder.op_bits);
let opcode_next = decode_opcode_u8(&decoder_next.op_bits);
let mut f = Self::all_zero();
match opcode {
opcodes::JOIN => f.join = Felt::ONE,
opcodes::SPLIT => f.split = Felt::ONE,
opcodes::SPAN => f.span = Felt::ONE,
opcodes::LOOP => f.loop_op = Felt::ONE,
opcodes::DYN => f.dyn_op = Felt::ONE,
opcodes::DYNCALL => f.dyncall = Felt::ONE,
opcodes::PUSH => f.push = Felt::ONE,
opcodes::HPERM => f.hperm = Felt::ONE,
opcodes::MPVERIFY => f.mpverify = Felt::ONE,
opcodes::MSTREAM => f.mstream = Felt::ONE,
opcodes::PIPE => f.pipe = Felt::ONE,
opcodes::EVALCIRCUIT => f.evalcircuit = Felt::ONE,
opcodes::LOGPRECOMPILE => f.log_precompile = Felt::ONE,
opcodes::HORNERBASE => f.hornerbase = Felt::ONE,
opcodes::HORNEREXT => f.hornerext = Felt::ONE,
opcodes::END => f.end = Felt::ONE,
opcodes::REPEAT => f.repeat = Felt::ONE,
opcodes::RESPAN => f.respan = Felt::ONE,
opcodes::CALL => f.call = Felt::ONE,
opcodes::SYSCALL => f.syscall = Felt::ONE,
opcodes::MRUPDATE => f.mrupdate = Felt::ONE,
opcodes::CRYPTOSTREAM => f.cryptostream = Felt::ONE,
opcodes::MLOAD => f.mload = Felt::ONE,
opcodes::MSTORE => f.mstore = Felt::ONE,
opcodes::MLOADW => f.mloadw = Felt::ONE,
opcodes::MSTOREW => f.mstorew = Felt::ONE,
opcodes::U32AND => f.u32and = Felt::ONE,
opcodes::U32XOR => f.u32xor = Felt::ONE,
_ => {},
}
match opcode_next {
opcodes::END => f.end_next = Felt::ONE,
opcodes::REPEAT => f.repeat_next = Felt::ONE,
opcodes::RESPAN => f.respan_next = Felt::ONE,
opcodes::HALT => f.halt_next = Felt::ONE,
_ => {},
}
f.u32_rc_op = bool_to_felt((64..80).contains(&opcode));
f.right_shift = bool_to_felt(
(48..64).contains(&opcode) || opcode == opcodes::PUSH || opcode == opcodes::U32SPLIT,
);
let is_end_loop = opcode == opcodes::END && decoder.end_block_flags().is_loop == Felt::ONE;
f.left_shift = bool_to_felt(
(32..48).contains(&opcode)
|| matches!(
opcode,
opcodes::U32ADD3
| opcodes::U32MADD
| opcodes::SPLIT
| opcodes::LOOP
| opcodes::REPEAT
| opcodes::DYN
)
|| is_end_loop,
);
f.overflow = (stack.b0 - Felt::from_u64(16)) * stack.h0;
#[cfg(debug_assertions)]
f.assert_matches_polynomial(decoder, stack, decoder_next);
f
}
fn all_zero() -> Self {
Self {
end: Felt::ZERO,
repeat: Felt::ZERO,
respan: Felt::ZERO,
call: Felt::ZERO,
syscall: Felt::ZERO,
mrupdate: Felt::ZERO,
cryptostream: Felt::ZERO,
join: Felt::ZERO,
split: Felt::ZERO,
span: Felt::ZERO,
loop_op: Felt::ZERO,
dyn_op: Felt::ZERO,
dyncall: Felt::ZERO,
push: Felt::ZERO,
hperm: Felt::ZERO,
mpverify: Felt::ZERO,
mstream: Felt::ZERO,
pipe: Felt::ZERO,
evalcircuit: Felt::ZERO,
log_precompile: Felt::ZERO,
hornerbase: Felt::ZERO,
hornerext: Felt::ZERO,
mload: Felt::ZERO,
mstore: Felt::ZERO,
mloadw: Felt::ZERO,
mstorew: Felt::ZERO,
u32and: Felt::ZERO,
u32xor: Felt::ZERO,
end_next: Felt::ZERO,
repeat_next: Felt::ZERO,
respan_next: Felt::ZERO,
halt_next: Felt::ZERO,
left_shift: Felt::ZERO,
right_shift: Felt::ZERO,
overflow: Felt::ZERO,
u32_rc_op: Felt::ZERO,
}
}
#[cfg(debug_assertions)]
fn assert_matches_polynomial(
&self,
decoder: &DecoderCols<Felt>,
stack: &StackCols<Felt>,
decoder_next: &DecoderCols<Felt>,
) {
let p = Self::from_main_cols::<Felt>(decoder, stack, decoder_next);
if !decoder.op_bits.iter().all(|b| *b == Felt::ZERO || *b == Felt::ONE) {
return;
}
macro_rules! check {
($($name:ident),* $(,)?) => {
$(
debug_assert_eq!(
self.$name, p.$name,
concat!("LookupOpFlags parity: ", stringify!($name)),
);
)*
};
}
check!(
end,
repeat,
respan,
call,
syscall,
mrupdate,
cryptostream,
join,
split,
span,
loop_op,
dyn_op,
dyncall,
push,
hperm,
mpverify,
mstream,
pipe,
evalcircuit,
log_precompile,
hornerbase,
hornerext,
mload,
mstore,
mloadw,
mstorew,
u32and,
u32xor,
end_next,
repeat_next,
respan_next,
halt_next,
left_shift,
right_shift,
overflow,
u32_rc_op,
);
}
}
#[inline]
fn decode_opcode_u8(op_bits: &[Felt; 7]) -> u8 {
let mut out = 0u8;
for (k, bit) in op_bits.iter().enumerate() {
out |= ((bit.as_canonical_u64() & 1) as u8) << k;
}
out
}
#[inline]
fn bool_to_felt(b: bool) -> Felt {
if b { Felt::ONE } else { Felt::ZERO }
}
macro_rules! accessors {
($( $(#[$meta:meta])* $name:ident ),* $(,)?) => {
impl<E: Clone> LookupOpFlags<E> {
$(
$(#[$meta])*
#[inline(always)]
pub fn $name(&self) -> E {
self.$name.clone()
}
)*
}
};
}
accessors!(
end,
repeat,
respan,
call,
syscall,
mrupdate,
cryptostream,
join,
split,
span,
loop_op,
dyn_op,
dyncall,
push,
hperm,
mpverify,
mstream,
pipe,
evalcircuit,
log_precompile,
hornerbase,
hornerext,
mload,
mstore,
mloadw,
mstorew,
u32and,
u32xor,
end_next,
repeat_next,
respan_next,
halt_next,
left_shift,
right_shift,
overflow,
u32_rc_op,
);
#[cfg(test)]
mod tests {
use miden_core::{ONE, ZERO, operations::Operation};
use super::LookupOpFlags;
use crate::constraints::op_flags::generate_test_row;
#[test]
fn u32_rc_op_flag() {
let u32_ops = [
Operation::U32add,
Operation::U32sub,
Operation::U32mul,
Operation::U32div,
Operation::U32split,
Operation::U32assert2(ZERO),
Operation::U32add3,
Operation::U32madd,
];
for op in u32_ops {
let flags = flags_for_opcode(op.op_code().into());
assert_eq!(flags.u32_rc_op(), ONE, "u32_rc_op should be ONE for {op:?}");
}
let non_u32_ops = [
Operation::Add,
Operation::Mul,
Operation::And, ];
for op in non_u32_ops {
let flags = flags_for_opcode(op.op_code().into());
assert_eq!(flags.u32_rc_op(), ZERO, "u32_rc_op should be ZERO for {op:?}");
}
}
fn flags_for_opcode(opcode: usize) -> LookupOpFlags<miden_core::Felt> {
let row = generate_test_row(opcode);
let row_next = generate_test_row(0);
LookupOpFlags::from_main_cols(&row.decoder, &row.stack, &row_next.decoder)
}
}