use std::collections::HashSet;
use arrayvec::ArrayVec;
use liblisa::semantics::ARG_NAMES;
use liblisa::semantics::default::ops::Op;
use liblisa::semantics::default::{Expr, MAX_TEMPLATE_ARGS};
use liblisa::utils::bitmask_u128;
use log::{debug, trace};
use super::{AnyTemplate, Template};
use crate::search::TemplateIterator;
use crate::templates::symexec::{SymbolicExecution, SymbolicExecutionResult};
#[derive(Clone)]
pub struct PreprocessedTemplate<'a> {
expr: Expr<'a>,
arg_indices: Vec<u16>,
num_holes: usize,
const_values: &'a [i128],
ordering_indexes: Vec<Option<usize>>,
active_indices: u64,
const_output: Option<i128>,
}
impl std::fmt::Debug for PreprocessedTemplate<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.expr.display(
self.arg_indices
.iter()
.enumerate()
.map(
|(hole_index, const_index)| if let Some(const_index) = const_index.checked_sub(1) {
self.const_values[const_index as usize].to_string()
} else {
ARG_NAMES[hole_index].to_owned()
}
)
.collect::<Vec<_>>()
)
)
}
}
impl<'a> PreprocessedTemplate<'a> {
pub fn new(template: &Template<'a>, arg_indices: Vec<u16>, const_values: &'a [i128], const_output: Option<i128>) -> Self {
assert!(arg_indices.len() < 64);
let num_holes = arg_indices.iter().filter(|&&index| index == 0).count();
debug_assert_eq!(arg_indices.len(), template.num_holes);
let active_indices = arg_indices
.iter()
.enumerate()
.map(|(index, &const_index)| if const_index == 0 { 1 << index } else { 0 })
.fold(0, |a, b| a | b);
Self {
expr: *template.expr(),
num_holes,
ordering_indexes: template.ordering_indexes().to_vec(),
arg_indices,
const_values,
active_indices,
const_output,
}
}
pub fn expr(&self) -> &Expr<'a> {
&self.expr
}
pub fn const_output(&self) -> Option<i128> {
self.const_output
}
pub fn raw_arg_indices(&self) -> &[u16] {
&self.arg_indices
}
}
impl AnyTemplate for PreprocessedTemplate<'_> {
fn num_unfilled_holes(&self) -> usize {
self.num_holes
}
fn ordering_indexes(&self) -> &[Option<usize>] {
&self.ordering_indexes
}
fn num_total_holes(&self) -> usize {
self.arg_indices.len()
}
type IterActiveIndices<'me>
= ActiveIndicesIter
where
Self: 'me;
fn active_indices(&self) -> Self::IterActiveIndices<'_> {
ActiveIndicesIter {
active_indices: self.active_indices,
}
}
fn start_value(&self) -> usize {
self.const_values.len()
}
fn new_counter(&self) -> ArrayVec<u16, MAX_TEMPLATE_ARGS> {
self.arg_indices
.iter()
.map(move |&const_index| {
if let Some(n) = const_index.checked_sub(1) {
n
} else {
self.const_values.len() as u16
}
})
.collect()
}
}
pub struct ActiveIndicesIter {
active_indices: u64,
}
impl Iterator for ActiveIndicesIter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.active_indices == 0 {
None
} else {
let result = self.active_indices.trailing_zeros();
self.active_indices &= u64::MAX << (result + 1);
Some(result as usize)
}
}
}
pub struct TemplatePreprocessor<'r, 'a> {
template: &'r Template<'a>,
const_values: &'a [i128],
iter: TemplateIterator<'r, Template<'a>>,
seen: HashSet<Vec<(usize, i128)>>,
output_bits: usize,
}
impl<'r, 'a> TemplatePreprocessor<'r, 'a> {
pub fn new(template: &'r Template<'a>, const_values: &'a [i128], output_bits: usize) -> Self {
debug!("Preprocessing {:?}", template);
Self {
template,
const_values,
iter: TemplateIterator::new(template),
seen: HashSet::new(),
output_bits,
}
}
fn current_template(&self, const_output: Option<i128>) -> PreprocessedTemplate<'a> {
let arg_indices = self
.iter
.arg_indices()
.iter()
.map(|&index| {
if index >= self.const_values.len() as u16 {
0
} else {
index + 1
}
})
.collect();
PreprocessedTemplate::new(self.template, arg_indices, self.const_values, const_output)
}
fn compute_key(ops: &[Op], counter: &[u16], const_values: &[i128], output_mask: i128) -> (Vec<(usize, i128)>, Option<i128>) {
let mut key = Vec::new();
let mut value_stack = ArrayVec::<_, 32>::new();
value_stack.push(0);
let mut const_stack = Vec::new();
for (index, op) in ops.iter().enumerate() {
let input_constness = const_stack.drain(const_stack.len() - op.num_args()..).collect::<Vec<_>>();
let input_values = &value_stack[value_stack.len() - op.num_args()..];
let all_const = if let Op::Hole(hole_index) = op {
counter[*hole_index as usize] != const_values.len() as u16
} else {
input_constness.iter().all(|&(b, _)| b)
};
if !all_const {
for (&(is_const, index), &value) in input_constness.iter().zip(input_values.iter()) {
if is_const {
key.push((index, value));
}
}
}
let top_of_stack = value_stack.pop().unwrap();
let top_of_stack = op.eval_from_stack(
&|hole_index| {
let item = counter[hole_index];
const_values.get(item as usize).copied().unwrap_or(0)
},
top_of_stack,
&mut value_stack,
);
value_stack.push(top_of_stack);
const_stack.push((all_const, index));
}
if let Some((true, index)) = const_stack.pop() {
let const_output = value_stack.pop().unwrap();
key.push((index, const_output & output_mask));
(key, Some(const_output & output_mask))
} else {
(key, None)
}
}
pub fn preprocess_templates(&mut self) -> Vec<PreprocessedTemplate<'a>> {
let output_mask = bitmask_u128(self.output_bits as u32);
let mut result = Vec::new();
loop {
let s = SymbolicExecution::new(
self.template.expr().ops(),
self.iter.arg_indices(),
self.const_values,
output_mask,
);
let SymbolicExecutionResult {
leaf_consts,
const_output,
} = s.run();
trace!("Checking {:?} with key {leaf_consts:?}", self.current_template(const_output));
let (mut old_leaf_consts, old_const_output) = Self::compute_key(
self.template.expr().ops(),
self.iter.arg_indices(),
self.const_values,
output_mask as i128,
);
if old_const_output.is_some() && old_const_output != const_output {
panic!(
"Expected constant output changed: {:?}; Const output: {old_const_output:?} => {const_output:?}; Ops: {:?}; Arg indices: {:?}",
self.current_template(const_output),
self.template.expr().ops(),
self.iter.arg_indices()
)
}
old_leaf_consts.sort();
if old_leaf_consts != leaf_consts {
debug!(
"Leaf consts changed: {:?}; Leaf consts: {old_leaf_consts:X?} => {leaf_consts:X?}; Const output: {old_const_output:?} => {const_output:?}",
self.current_template(const_output)
);
}
if self.seen.insert(leaf_consts) {
trace!("Check result: push");
result.push(self.current_template(const_output));
} else {
trace!("Check result: ignore");
}
if !self.iter.next(self.const_values.len() + 1) {
break
}
}
result
}
}
#[cfg(test)]
mod tests {
use liblisa::semantics::default::builder::*;
use liblisa::semantics::default::expr;
use test_log::test;
use super::TemplatePreprocessor;
use crate::templates::Template;
#[test]
pub fn no_duplicates1() {
let t = Template::new(expr!(or(hole::<0>(), add(hole::<1>(), hole::<2>()))));
let mut p = TemplatePreprocessor::new(&t, &[0, 1, 2], 128);
let result = p.preprocess_templates();
println!("{result:#?}");
assert_eq!(result.len(), 28);
assert!(result.iter().any(|r| r.arg_indices == [1, 1, 1]));
assert!(result.iter().any(|r| r.arg_indices == [0, 0, 0]))
}
#[test]
pub fn no_duplicates2() {
let t = Template::new(expr!(or(
shl(hole::<0>(), or(hole::<2>(), shl(hole::<1>(), c::<4>()))),
shr(shl(hole::<1>(), or(hole::<2>(), shl(hole::<3>(), c::<4>()))), hole::<3>())
)));
let mut p = TemplatePreprocessor::new(&t, &[0, 1, 2], 128);
let result = p.preprocess_templates();
println!("{result:#?}");
assert_eq!(result.len(), 193);
}
#[test]
pub fn no_duplicates3() {
let t = Template::new(expr!(or(hole::<0>(), add(hole::<1>(), hole::<2>()))));
let mut p = TemplatePreprocessor::new(&t, &[0, 1, 2, 3, 4, 5, 6, 7, 128, 8, 9, 10, 11, 12, 13, 14, 15], 128);
let result = p.preprocess_templates();
assert_eq!(result.len(), 452);
}
}