use crossbeam::queue::SegQueue;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::fmt;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use isla_lib::cache::{Cacheable, Cachekey};
use isla_lib::bitvector::BV;
use isla_lib::config::ISAConfig;
use isla_lib::executor;
use isla_lib::executor::{LocalFrame, TaskState};
use isla_lib::ir::*;
use isla_lib::log;
use isla_lib::simplify::{EventReferences, Taints};
use isla_lib::smt::{Accessor, EvPath, Event, Sym};
use isla_lib::zencode;
#[derive(Debug, Serialize, Deserialize)]
pub struct Footprint {
write_data_taints: (Taints, bool),
mem_addr_taints: (Taints, bool),
branch_addr_taints: (Taints, bool),
register_reads: HashSet<(Name, Vec<Accessor>)>,
register_writes: HashSet<(Name, Vec<Accessor>)>,
register_writes_tainted: HashSet<(Name, Vec<Accessor>)>,
register_writes_ignored: HashSet<(Option<Name>, Name)>,
is_store: bool,
is_load: bool,
is_branch: bool,
is_exclusive: bool,
is_cache_op: bool,
}
pub struct Footprintkey {
opcode: String,
}
impl Cachekey for Footprintkey {
fn key(&self) -> String {
format!("opcode_{}", self.opcode)
}
}
impl Cacheable for Footprint {
type Key = Footprintkey;
}
impl Footprint {
fn new() -> Self {
Footprint {
write_data_taints: (HashSet::new(), false),
mem_addr_taints: (HashSet::new(), false),
branch_addr_taints: (HashSet::new(), false),
register_reads: HashSet::new(),
register_writes: HashSet::new(),
register_writes_tainted: HashSet::new(),
register_writes_ignored: HashSet::new(),
is_store: false,
is_load: false,
is_branch: false,
is_exclusive: false,
is_cache_op: false,
}
}
pub fn pretty(&self, buf: &mut dyn Write, symtab: &Symtab) -> Result<(), Box<dyn Error>> {
write!(buf, "Footprint:\n Memory write:")?;
for (reg, accessor) in &self.write_data_taints.0 {
write!(buf, " {}", zencode::decode(symtab.to_str(*reg)))?;
for component in accessor {
component.pretty(buf, symtab)?
}
}
write!(buf, "\n Memory read:")?;
for (reg, accessor) in &self.register_writes_tainted {
write!(buf, " {}", zencode::decode(symtab.to_str(*reg)))?;
for component in accessor {
component.pretty(buf, symtab)?
}
}
write!(buf, "\n Memory address:")?;
for (reg, accessor) in &self.mem_addr_taints.0 {
write!(buf, " {}", zencode::decode(symtab.to_str(*reg)))?;
for component in accessor {
component.pretty(buf, symtab)?
}
}
write!(buf, "\n Branch address:")?;
for (reg, accessor) in &self.branch_addr_taints.0 {
write!(buf, " {}", zencode::decode(symtab.to_str(*reg)))?;
for component in accessor {
component.pretty(buf, symtab)?
}
}
write!(buf, "\n Register reads:")?;
for (reg, accessor) in &self.register_reads {
write!(buf, " {}", zencode::decode(symtab.to_str(*reg)))?;
for component in accessor {
component.pretty(buf, symtab)?
}
}
write!(buf, "\n Register writes:")?;
for (reg, accessor) in &self.register_writes {
write!(buf, " {}", zencode::decode(symtab.to_str(*reg)))?;
for component in accessor {
component.pretty(buf, symtab)?
}
}
write!(buf, "\n Register writes (ignore):")?;
for (from_reg, to_reg) in &self.register_writes_ignored {
if let Some(from_reg) = from_reg {
write!(
buf,
" {}->{}",
zencode::decode(symtab.to_str(*from_reg)),
zencode::decode(symtab.to_str(*to_reg))
)?
} else {
write!(buf, " {}", zencode::decode(symtab.to_str(*to_reg)))?
}
}
write!(buf, "\n Is store: {}", self.is_store)?;
write!(buf, "\n Is load: {}", self.is_load)?;
write!(buf, "\n Is exclusive: {}", self.is_exclusive)?;
write!(buf, "\n Is branch: {}", self.is_branch)?;
writeln!(buf)?;
Ok(())
}
}
#[allow(clippy::needless_range_loop)]
pub fn rmw_dep<B: BV>(from: usize, to: usize, instrs: &[B], footprints: &HashMap<B, Footprint>) -> bool {
if from > to {
return false;
}
let from_footprint = footprints.get(&instrs[from]).unwrap();
if !(from_footprint.is_exclusive && from_footprint.is_load) {
return false;
}
for i in (from + 1)..to {
if footprints.get(&instrs[i]).unwrap().is_exclusive {
return false;
}
}
let to_footprint = footprints.get(&instrs[to]).unwrap();
to_footprint.is_exclusive && to_footprint.is_store
}
#[allow(clippy::needless_range_loop)]
fn touched_by<B: BV>(
from: usize,
to: usize,
instrs: &[B],
footprints: &HashMap<B, Footprint>,
) -> HashSet<(Name, Vec<Accessor>)> {
let mut touched = footprints.get(&instrs[from]).unwrap().register_writes_tainted.clone();
let mut new_touched = HashSet::new();
for i in (from + 1)..to {
let footprint = footprints.get(&instrs[i]).unwrap();
for rreg in &touched {
if footprint.register_reads.contains(rreg) {
for wreg in &footprint.register_writes {
if footprint.register_writes_ignored.contains(&(None, wreg.0)) {
continue;
}
if footprint.register_writes_ignored.contains(&(Some(rreg.0), wreg.0)) {
continue;
}
new_touched.insert(wreg.clone());
}
}
}
if new_touched.is_empty() {
for wreg in &footprint.register_writes {
touched.remove(wreg);
}
} else {
new_touched.drain().for_each(|wreg| {
touched.insert(wreg);
})
}
}
touched
}
pub fn addr_dep<B: BV>(from: usize, to: usize, instrs: &[B], footprints: &HashMap<B, Footprint>) -> bool {
if from >= to {
return false;
}
let touched = touched_by(from, to, instrs, footprints);
for reg in &footprints.get(&instrs[to]).unwrap().mem_addr_taints.0 {
if touched.contains(reg) {
return true;
}
}
false
}
pub fn data_dep<B: BV>(from: usize, to: usize, instrs: &[B], footprints: &HashMap<B, Footprint>) -> bool {
if from >= to {
return false;
}
let touched = touched_by(from, to, instrs, footprints);
for reg in &footprints.get(&instrs[to]).unwrap().write_data_taints.0 {
if touched.contains(reg) {
return true;
}
}
false
}
#[allow(clippy::needless_range_loop)]
pub fn ctrl_dep<B: BV>(from: usize, to: usize, instrs: &[B], footprints: &HashMap<B, Footprint>) -> bool {
let to_footprint = footprints.get(&instrs[from]).unwrap();
if !(to_footprint.is_load || to_footprint.is_store) || (from >= to) {
return false;
}
let mut touched = footprints.get(&instrs[from]).unwrap().register_writes_tainted.clone();
let mut new_touched = Vec::new();
for i in (from + 1)..to {
let footprint = footprints.get(&instrs[i]).unwrap();
if footprint.is_branch {
for reg in &footprint.branch_addr_taints.0 {
if touched.contains(®) {
return true;
}
}
}
for rreg in &touched {
if footprint.register_reads.contains(rreg) {
for wreg in &footprint.register_writes {
if footprint.register_writes_ignored.contains(&(None, wreg.0)) {
continue;
}
if footprint.register_writes_ignored.contains(&(Some(rreg.0), wreg.0)) {
continue;
}
new_touched.push(wreg.clone());
}
}
}
new_touched.drain(..).for_each(|wreg| {
touched.insert(wreg);
})
}
false
}
#[derive(Debug)]
pub enum FootprintError {
NoIslaFootprintFn,
SymbolicInstruction,
ExecutionError(String),
}
impl fmt::Display for FootprintError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use FootprintError::*;
match self {
NoIslaFootprintFn => write!(
f,
"Footprint analysis failed. To calculate the syntactic\n\
register footprint, isla expects a sail function\n\
`isla_footprint' to be available in the model, which\n\
can be used to decode and execute an instruction"
),
SymbolicInstruction => write!(f, "Instruction opcode found during footprint analysis was symbolic"),
ExecutionError(msg) => write!(f, "{}", msg),
}
}
}
impl Error for FootprintError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
pub fn footprint_analysis<'ir, B>(
num_threads: usize,
thread_buckets: &[Vec<EvPath<B>>],
lets: &Bindings<'ir, B>,
regs: &Bindings<'ir, B>,
shared_state: &SharedState<B>,
isa_config: &ISAConfig<B>,
cache: Option<&Path>,
) -> Result<HashMap<B, Footprint>, FootprintError>
where
B: BV,
{
use FootprintError::*;
let mut concrete_opcodes: HashSet<B> = HashSet::new();
let mut footprints = HashMap::new();
for thread in thread_buckets {
for path in thread {
for event in path {
match event {
Event::Instr(Val::Bits(bv)) => {
if let Some(cache_dir) = &cache {
if let Some(footprint) =
Footprint::from_cache(Footprintkey { opcode: bv.to_string() }, cache_dir)
{
footprints.insert(*bv, footprint);
} else {
concrete_opcodes.insert(*bv);
}
} else {
concrete_opcodes.insert(*bv);
}
}
Event::Instr(_) => return Err(SymbolicInstruction),
_ => (),
}
}
}
}
log!(log::VERBOSE, &format!("Got {} uncached concrete opcodes for footprint analysis", concrete_opcodes.len()));
let function_id = match shared_state.symtab.get("zisla_footprint") {
Some(id) => id,
None => return Err(NoIslaFootprintFn),
};
let (args, _, instrs) =
shared_state.functions.get(&function_id).expect("isla_footprint function not in shared state!");
let task_state = TaskState::new();
let (task_opcodes, tasks): (Vec<B>, Vec<_>) = concrete_opcodes
.iter()
.enumerate()
.map(|(i, opcode)| {
(
opcode,
LocalFrame::new(function_id, args, Some(&[Val::Bits(*opcode)]), instrs)
.add_lets(lets)
.add_regs(regs)
.task(i, &task_state),
)
})
.unzip();
let mut footprint_buckets: Vec<Vec<EvPath<B>>> = vec![Vec::new(); tasks.len()];
let queue = Arc::new(SegQueue::new());
let now = Instant::now();
executor::start_multi(num_threads, None, tasks, &shared_state, queue.clone(), &executor::footprint_collector);
log!(log::VERBOSE, &format!("Footprint analysis symbolic execution took: {}ms", now.elapsed().as_millis()));
loop {
match queue.pop() {
Ok(Ok((task_id, mut events))) => {
let mut events: Vec<Event<B>> = events
.drain(..)
.rev()
.skip_while(|ev| !ev.is_cycle())
.filter(|ev| ev.is_reg() || ev.is_memory() || ev.is_branch() || ev.is_smt() || ev.is_fork())
.collect();
isla_lib::simplify::remove_unused(&mut events);
footprint_buckets[task_id].push(events)
}
Ok(Err(msg)) => return Err(ExecutionError(msg)),
Err(_) => break,
}
}
let num_footprints: usize = footprint_buckets.iter().map(|instr_paths| instr_paths.len()).sum();
log!(log::VERBOSE, &format!("There are {} footprints", num_footprints));
let read_exclusives: Vec<usize> =
isa_config.read_exclusives.iter().map(|k| shared_state.enum_member(*k).unwrap()).collect();
let write_exclusives: Vec<usize> =
isa_config.write_exclusives.iter().map(|k| shared_state.enum_member(*k).unwrap()).collect();
for (i, paths) in footprint_buckets.iter().enumerate() {
let opcode = task_opcodes[i];
log!(log::VERBOSE, &format!("{:?}", opcode));
let mut footprint = Footprint::new();
for events in paths {
let evrefs = EventReferences::from_events(events);
let mut forks: Vec<Sym> = Vec::new();
for event in events {
match event {
Event::Fork(_, v, _) => forks.push(*v),
Event::ReadReg(reg, accessor, _) if !isa_config.ignored_registers.contains(reg) => {
footprint.register_reads.insert((*reg, accessor.clone()));
}
Event::WriteReg(reg, accessor, data) if !isa_config.ignored_registers.contains(reg) => {
footprint.register_writes.insert((*reg, accessor.clone()));
if evrefs.value_taints(data, events).1 {
footprint.register_writes_tainted.insert((*reg, accessor.clone()));
}
}
Event::MarkReg { regs, mark } => {
if mark == "ignore_write" && regs.len() == 1 {
footprint.register_writes_ignored.insert((None, regs[0]));
}
if mark == "ignore_edge" && regs.len() == 2 {
footprint.register_writes_ignored.insert((Some(regs[0]), regs[1]));
}
}
Event::ReadMem { address, .. } => {
footprint.is_load = true;
if read_exclusives.iter().any(|rk| event.has_read_kind(*rk)) {
footprint.is_exclusive = true;
}
evrefs.collect_value_taints(
address,
events,
&mut footprint.mem_addr_taints.0,
&mut footprint.mem_addr_taints.1,
)
}
Event::WriteMem { address, data, .. } => {
footprint.is_store = true;
if write_exclusives.iter().any(|wk| event.has_write_kind(*wk)) {
footprint.is_exclusive = true;
}
evrefs.collect_value_taints(
address,
events,
&mut footprint.mem_addr_taints.0,
&mut footprint.mem_addr_taints.1,
);
evrefs.collect_value_taints(
data,
events,
&mut footprint.write_data_taints.0,
&mut footprint.write_data_taints.1,
);
}
Event::CacheOp { address, .. } => {
footprint.is_cache_op = true;
evrefs.collect_value_taints(
address,
events,
&mut footprint.mem_addr_taints.0,
&mut footprint.mem_addr_taints.1,
)
}
Event::Branch { address } => {
footprint.is_branch = true;
evrefs.collect_value_taints(
address,
events,
&mut footprint.branch_addr_taints.0,
&mut footprint.branch_addr_taints.1,
);
for v in &forks {
evrefs.collect_taints(
*v,
events,
&mut footprint.branch_addr_taints.0,
&mut footprint.branch_addr_taints.1,
)
}
}
_ => (),
}
}
}
if let Some(cache_dir) = &cache {
footprint.cache(Footprintkey { opcode: opcode.to_string() }, cache_dir);
}
footprints.insert(opcode, footprint);
}
Ok(footprints)
}