use alloc::vec::Vec;
use miden_air::{
logup::RangeMsg,
trace::{
MainTrace, RANGE_CHECK_TRACE_OFFSET,
chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX},
},
};
use miden_core::{Felt, operations::Operation};
use miden_utils_testing::stack;
use super::{
build_trace_from_ops,
lookup_harness::{Expectations, InteractionLog},
};
use crate::RowIndex;
#[test]
fn u32_stack_op_emits_range_check_removes() {
let stack = [1, 255];
let operations = vec![Operation::U32add];
let trace = build_trace_from_ops(operations, &stack);
let log = InteractionLog::new(&trace);
let main = trace.main_trace();
let u32add_row = find_op_row(main, miden_core::operations::opcodes::U32ADD);
let mut exp = Expectations::new(&log);
for i in 0..4 {
let value = main.helper_register(i, u32add_row);
exp.remove(usize::from(u32add_row), &RangeMsg { value });
}
assert_eq!(exp.count_removes(), 4, "expected 4 helper-register range-check removes");
assert_eq!(exp.count_adds(), 0);
log.assert_contains(&exp);
}
#[test]
fn memory_chiplet_row_emits_range_check_removes() {
let addr: u64 = 262148;
let stack_input = stack![addr, 1, 2, 3, 4, addr];
let mut operations = vec![
Operation::MStoreW,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::MLoadW,
];
operations.resize(operations.len() + 60, Operation::Noop);
let trace = build_trace_from_ops(operations, &stack_input);
let log = InteractionLog::new(&trace);
let main = trace.main_trace();
let mut mem_rows: Vec<RowIndex> = Vec::new();
for row in 0..main.num_rows() {
let idx = RowIndex::from(row);
if main.is_memory_row(idx) {
mem_rows.push(idx);
}
}
assert_eq!(mem_rows.len(), 2, "expected exactly two memory chiplet rows");
let mut exp = Expectations::new(&log);
for mem_row in &mem_rows {
let row = usize::from(*mem_row);
let d0 = main.get(*mem_row, MEMORY_D0_COL_IDX);
let d1 = main.get(*mem_row, MEMORY_D1_COL_IDX);
let w0 = main.chiplet_memory_word_addr_lo(*mem_row);
let w1 = main.chiplet_memory_word_addr_hi(*mem_row);
let four_w1 = w1 * Felt::from_u8(4);
for value in [d0, d1, w0, w1, four_w1] {
exp.remove(row, &RangeMsg { value });
}
}
assert_eq!(exp.count_removes(), 5 * mem_rows.len(), "expected 5 RC removes per memory row");
assert_eq!(exp.count_adds(), 0);
log.assert_contains(&exp);
}
#[test]
fn range_checker_table_emits_per_row_adds() {
let stack = [1, 255];
let operations = vec![Operation::U32add];
let trace = build_trace_from_ops(operations, &stack);
let log = InteractionLog::new(&trace);
let main = trace.main_trace();
const M_COL_IDX: usize = RANGE_CHECK_TRACE_OFFSET;
const V_COL_IDX: usize = RANGE_CHECK_TRACE_OFFSET + 1;
let mut nonzero_mult_rows = 0usize;
let mut exp = Expectations::new(&log);
for row in 0..main.num_rows() {
let idx = RowIndex::from(row);
let m = main.get(idx, M_COL_IDX);
let v = main.get(idx, V_COL_IDX);
exp.push(row, m, &RangeMsg { value: v });
if m != Felt::from_u8(0) {
nonzero_mult_rows += 1;
}
}
assert!(nonzero_mult_rows > 0, "range checker table is empty — test is vacuous");
log.assert_contains(&exp);
}
fn find_op_row(main: &MainTrace, opcode: u8) -> RowIndex {
for row in 0..main.num_rows() {
let idx = RowIndex::from(row);
if main.get_op_code(idx) == Felt::from_u8(opcode) {
return idx;
}
}
panic!("no row with opcode 0x{opcode:02x} in trace");
}