use alloc::{vec, vec::Vec};
use miden_core::{
Felt,
field::{Algebra, PrimeCharacteristicRing, QuadFelt},
utils::RowMajorMatrix,
};
use miden_lifted_air::{BaseAir, LiftedAir, LiftedAirBuilder};
use crate::{
logup::{
Challenges, CyclicConstraintLookupBuilder, Deg, LookupAir, LookupBatch, LookupBuilder,
LookupColumn, LookupGroup, LookupMessage, NUM_PUBLIC_VALUES, NUM_RANDOMNESS,
NUM_SIGMA_VALUES, build_logup_aux_trace, frac_col,
},
relations::{BusId, MAX_MESSAGE_WIDTH, NUM_BUS_IDS, ProvideMult},
utils::current_main,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum BytePairOp {
AndNot,
Xor,
}
impl BytePairOp {
pub fn apply(self, a: u8, b: u8) -> u8 {
match self {
BytePairOp::AndNot => (!a) & b,
BytePairOp::Xor => a ^ b,
}
}
pub fn tag(self) -> u8 {
match self {
BytePairOp::AndNot => 0,
BytePairOp::Xor => 1,
}
}
}
pub const COL_MULT_ANDNOT: usize = 0;
pub const COL_MULT_XOR: usize = 1;
pub const COL_MULT_RANGE16: usize = 2;
pub const NUM_MAIN_COLS: usize = 3;
pub const NUM_AUX_COLS: usize = 2;
pub const NUM_PREPROCESSED_COLS: usize = 4;
pub const PRE_A: usize = 0;
pub const PRE_B: usize = 1;
pub const PRE_C_ANDNOT: usize = 2;
pub const PRE_C_XOR: usize = 3;
pub const NUM_LOOKUP_COLS: usize = NUM_PREPROCESSED_COLS + NUM_MAIN_COLS;
pub const TRACE_HEIGHT: usize = 1 << 16;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Multiplicities {
pub andnot: ProvideMult,
pub xor: ProvideMult,
pub range16: ProvideMult,
}
impl Multiplicities {
pub fn op(&self, op: BytePairOp) -> u32 {
match op {
BytePairOp::AndNot => self.andnot,
BytePairOp::Xor => self.xor,
}
}
pub fn is_nonzero(&self) -> bool {
self.andnot != 0 || self.xor != 0 || self.range16 != 0
}
}
const NUM_BYTE_PAIRS: usize = 1 << 16;
const fn pair_idx(a: u8, b: u8) -> usize {
((a as usize) << 8) | (b as usize)
}
#[derive(Debug, Clone)]
pub struct BytePairLutRequires {
counts: Vec<Multiplicities>,
}
impl Default for BytePairLutRequires {
fn default() -> Self {
Self {
counts: vec![Multiplicities::default(); NUM_BYTE_PAIRS],
}
}
}
impl BytePairLutRequires {
pub fn new() -> Self {
Self::default()
}
pub fn require(&mut self, op: BytePairOp, a: u8, b: u8) -> u8 {
let mults = &mut self.counts[pair_idx(a, b)];
match op {
BytePairOp::AndNot => mults.andnot += 1,
BytePairOp::Xor => mults.xor += 1,
}
op.apply(a, b)
}
pub fn require_range16(&mut self, w: u16) {
let a = (w & 0xff) as u8;
let b = (w >> 8) as u8;
self.counts[pair_idx(a, b)].range16 += 1;
}
pub fn multiplicity(&self, op: BytePairOp, a: u8, b: u8) -> ProvideMult {
self.counts[pair_idx(a, b)].op(op)
}
pub fn multiplicity_range16(&self, w: u16) -> ProvideMult {
let a = (w & 0xff) as u8;
let b = (w >> 8) as u8;
self.counts[pair_idx(a, b)].range16
}
}
pub fn require_logic64(bpl_req: &mut BytePairLutRequires, op: BytePairOp, a: u64, b: u64) -> u64 {
let a_bytes = a.to_le_bytes();
let b_bytes = b.to_le_bytes();
for i in 0..8 {
bpl_req.require(op, a_bytes[i], b_bytes[i]);
}
op.apply_u64(a, b)
}
impl BytePairOp {
fn apply_u64(self, a: u64, b: u64) -> u64 {
match self {
BytePairOp::AndNot => (!a) & b,
BytePairOp::Xor => a ^ b,
}
}
}
pub(crate) fn preprocessed_table() -> RowMajorMatrix<Felt> {
let mut values = Vec::with_capacity(TRACE_HEIGHT * NUM_PREPROCESSED_COLS);
for idx in 0..NUM_BYTE_PAIRS {
let a = (idx >> 8) as u8;
let b = (idx & 0xff) as u8;
values.extend([
Felt::from(a),
Felt::from(b),
Felt::from(BytePairOp::AndNot.apply(a, b)),
Felt::from(BytePairOp::Xor.apply(a, b)),
]);
}
RowMajorMatrix::new(values, NUM_PREPROCESSED_COLS)
}
pub fn generate_trace(requires: BytePairLutRequires) -> RowMajorMatrix<Felt> {
let mut values = Vec::with_capacity(TRACE_HEIGHT * NUM_MAIN_COLS);
for mults in &requires.counts {
values.extend([Felt::from(mults.andnot), Felt::from(mults.xor), Felt::from(mults.range16)]);
}
RowMajorMatrix::new(values, NUM_MAIN_COLS)
}
#[derive(Debug, Clone)]
pub struct BytePairLutMsg<E> {
pub op: E,
pub a: E,
pub b: E,
pub c: E,
}
impl<E, EF> LookupMessage<E, EF> for BytePairLutMsg<E>
where
E: Algebra<E>,
EF: Algebra<E>,
{
fn encode(&self, challenges: &Challenges<EF>) -> EF {
challenges.encode(
BusId::BytePairLut as usize,
[self.op.clone(), self.a.clone(), self.b.clone(), self.c.clone()],
)
}
}
#[derive(Debug, Clone)]
pub struct Range16Msg<E> {
pub w: E,
}
impl<E, EF> LookupMessage<E, EF> for Range16Msg<E>
where
E: Algebra<E>,
EF: Algebra<E>,
{
fn encode(&self, challenges: &Challenges<EF>) -> EF {
challenges.encode(BusId::Range16 as usize, [self.w.clone()])
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct BytePairLutAir;
impl BaseAir<Felt> for BytePairLutAir {
fn width(&self) -> usize {
NUM_MAIN_COLS
}
fn preprocessed_trace(&self) -> Option<RowMajorMatrix<Felt>> {
Some(preprocessed_table())
}
fn preprocessed_width(&self) -> usize {
NUM_PREPROCESSED_COLS
}
fn num_public_values(&self) -> usize {
NUM_PUBLIC_VALUES
}
}
impl LiftedAir<Felt, QuadFelt> for BytePairLutAir {
fn num_randomness(&self) -> usize {
NUM_RANDOMNESS
}
fn aux_width(&self) -> usize {
NUM_AUX_COLS
}
fn num_aux_values(&self) -> usize {
NUM_SIGMA_VALUES
}
fn build_aux_trace(
&self,
main: &RowMajorMatrix<Felt>,
_air_inputs: &[Felt],
_aux_inputs: &[Felt],
challenges: &[QuadFelt],
) -> (RowMajorMatrix<QuadFelt>, Vec<QuadFelt>) {
build_aux(main, challenges)
}
fn eval<AB: LiftedAirBuilder<F = Felt>>(&self, builder: &mut AB) {
let mut lb =
CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0);
<Self as LookupAir<_>>::eval(self, &mut lb);
}
}
const COLUMN_SHAPE: [usize; 2] = [1, 2];
impl<LB> LookupAir<LB> for BytePairLutAir
where
LB: LookupBuilder<F = Felt>,
{
fn num_columns(&self) -> usize {
NUM_AUX_COLS
}
fn column_shape(&self) -> &[usize] {
&COLUMN_SHAPE
}
fn max_message_width(&self) -> usize {
MAX_MESSAGE_WIDTH
}
fn num_bus_ids(&self) -> usize {
NUM_BUS_IDS
}
fn eval(&self, builder: &mut LB) {
let local: [LB::Var; NUM_LOOKUP_COLS] = current_main(builder.main(), 0);
let a_value: LB::Expr = local[PRE_A].into();
let b_value: LB::Expr = local[PRE_B].into();
let c_andnot: LB::Expr = local[PRE_C_ANDNOT].into();
let c_xor: LB::Expr = local[PRE_C_XOR].into();
let andnot_op: LB::Expr = LB::Expr::from(Felt::from(BytePairOp::AndNot.tag()));
let xor_op: LB::Expr = LB::Expr::from(Felt::from(BytePairOp::Xor.tag()));
let two_56: LB::Expr = LB::Expr::from(Felt::from(256u16));
let w: LB::Expr = a_value.clone() + two_56 * b_value.clone();
let mult_andnot: LB::Expr = local[NUM_PREPROCESSED_COLS + COL_MULT_ANDNOT].into();
let mult_xor: LB::Expr = local[NUM_PREPROCESSED_COLS + COL_MULT_XOR].into();
let mult_range16: LB::Expr = local[NUM_PREPROCESSED_COLS + COL_MULT_RANGE16].into();
let neg_andnot: LB::Expr = LB::Expr::ZERO - mult_andnot;
let neg_xor: LB::Expr = LB::Expr::ZERO - mult_xor;
let neg_range16: LB::Expr = LB::Expr::ZERO - mult_range16;
let interaction_deg = Deg { v: 1, u: 1 };
let provides_deg = Deg { v: 1, u: 2 };
let pair_deg = Deg { v: 3, u: 2 };
frac_col!(
builder,
"bpl-self-provides",
provides_deg,
(
"andnot",
neg_andnot,
BytePairLutMsg {
op: andnot_op,
a: a_value.clone(),
b: b_value.clone(),
c: c_andnot,
},
interaction_deg
),
);
frac_col!(
builder,
"bpl-self-provides",
pair_deg,
(
"xor",
neg_xor,
BytePairLutMsg {
op: xor_op,
a: a_value.clone(),
b: b_value.clone(),
c: c_xor,
},
interaction_deg
),
("range16", neg_range16, Range16Msg { w }, interaction_deg),
);
}
}
pub(crate) fn build_aux(
main: &RowMajorMatrix<Felt>,
challenges: &[QuadFelt],
) -> (RowMajorMatrix<QuadFelt>, Vec<QuadFelt>) {
let combined = combine_with_preprocessed(main);
build_logup_aux_trace(&BytePairLutAir, &combined, challenges)
}
fn combine_with_preprocessed(main: &RowMajorMatrix<Felt>) -> RowMajorMatrix<Felt> {
let pre = preprocessed_table();
let height = main.values.len() / NUM_MAIN_COLS;
debug_assert_eq!(
pre.values.len() / NUM_PREPROCESSED_COLS,
height,
"preprocessed and main trace heights must match",
);
let mut values = Vec::with_capacity(height * NUM_LOOKUP_COLS);
for r in 0..height {
let pre_row = &pre.values[r * NUM_PREPROCESSED_COLS..(r + 1) * NUM_PREPROCESSED_COLS];
let main_row = &main.values[r * NUM_MAIN_COLS..(r + 1) * NUM_MAIN_COLS];
values.extend_from_slice(pre_row);
values.extend_from_slice(main_row);
}
RowMajorMatrix::new(values, NUM_LOOKUP_COLS)
}