use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum Region {
Arg(u16),
Stack,
Global,
Heapish,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum ValueClass {
Input(u16),
InputDeriv(u16),
Zero,
SmallConst(i64),
BigConst,
Unknown,
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum CallTarget {
Sym(String),
Anon,
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum Effect {
Write {
region: Region,
off: i32,
val: ValueClass,
},
Read {
region: Region,
off: i32,
},
NewRegion(Region),
Call(CallTarget),
Syscall(u32),
Branch,
Ret(ValueClass),
Capped,
}
impl Effect {
pub fn token(&self) -> u64 {
let mut h = Fnv::new();
match self {
Effect::Write { region, off, val } => {
h.tag(1);
region.hash(&mut h);
h.i32(*off);
val.hash(&mut h);
}
Effect::Read { region, off } => {
h.tag(8);
region.hash(&mut h);
h.i32(*off);
}
Effect::NewRegion(r) => {
h.tag(2);
r.hash(&mut h);
}
Effect::Call(t) => {
h.tag(3);
match t {
CallTarget::Sym(s) => {
h.tag(1);
for b in s.as_bytes() {
h.byte(*b);
}
}
CallTarget::Anon => h.tag(2),
}
}
Effect::Syscall(n) => {
h.tag(4);
h.u64(*n as u64);
}
Effect::Branch => h.tag(5),
Effect::Ret(v) => {
h.tag(6);
v.hash(&mut h);
}
Effect::Capped => h.tag(7),
}
h.0
}
}
impl Region {
fn hash(&self, h: &mut Fnv) {
match self {
Region::Arg(k) => {
h.tag(10);
h.u64(*k as u64);
}
Region::Stack => h.tag(11),
Region::Global => h.tag(13),
Region::Heapish => h.tag(12),
}
}
}
impl ValueClass {
fn hash(&self, h: &mut Fnv) {
match self {
ValueClass::Input(k) => {
h.tag(20);
h.u64(*k as u64);
}
ValueClass::InputDeriv(k) => {
h.tag(21);
h.u64(*k as u64);
}
ValueClass::Zero => h.tag(22),
ValueClass::SmallConst(v) => {
h.tag(23);
h.u64(*v as u64);
}
ValueClass::BigConst => h.tag(24),
ValueClass::Unknown => h.tag(25),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EffectTrace {
pub effects: Vec<Effect>,
pub instret: u64,
pub capped: bool,
}
impl EffectTrace {
pub fn tokens(&self) -> Vec<u64> {
self.effects.iter().map(|e| e.token()).collect()
}
pub fn complexity(&self) -> usize {
let mut c = 0usize;
for e in &self.effects {
c += match e {
Effect::Call(_) | Effect::Syscall(_) => 3,
Effect::Write { .. } | Effect::NewRegion(_) => 2,
Effect::Read { .. } => 1,
Effect::Ret(ValueClass::Input(_)) | Effect::Ret(ValueClass::InputDeriv(_)) => 1,
_ => 0,
};
}
c
}
}
pub struct Fnv(pub u64);
impl Fnv {
pub fn new() -> Self {
Fnv(0xcbf29ce484222325)
}
#[inline]
pub fn byte(&mut self, b: u8) {
self.0 ^= b as u64;
self.0 = self.0.wrapping_mul(0x100000001b3);
}
pub fn tag(&mut self, t: u8) {
self.byte(t);
}
pub fn u64(&mut self, v: u64) {
for i in 0..8 {
self.byte((v >> (i * 8)) as u8);
}
}
pub fn i32(&mut self, v: i32) {
self.u64(v as i64 as u64);
}
}
impl Default for Fnv {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_is_stable_and_distinct() {
let a = Effect::Write {
region: Region::Arg(0),
off: 8,
val: ValueClass::Input(1),
};
let b = Effect::Write {
region: Region::Arg(0),
off: 8,
val: ValueClass::Input(1),
};
let c = Effect::Write {
region: Region::Arg(0),
off: 16,
val: ValueClass::Input(1),
};
assert_eq!(a.token(), b.token());
assert_ne!(a.token(), c.token());
}
#[test]
fn complexity_withholds_tiny() {
let thunk = EffectTrace {
effects: vec![Effect::Ret(ValueClass::Input(0))],
instret: 2,
capped: false,
};
assert!(thunk.complexity() < 3);
let real = EffectTrace {
effects: vec![
Effect::NewRegion(Region::Arg(0)),
Effect::Write {
region: Region::Arg(0),
off: 0,
val: ValueClass::Input(1),
},
Effect::Call(CallTarget::Sym("memcpy".into())),
],
instret: 40,
capped: false,
};
assert!(real.complexity() >= 3);
}
}