#![allow(dead_code)]
use crate::arch::bpf::{DecodedInsn, InsnKind};
use crate::ir::Function;
use crate::ir::ssa::{Liveness, SsaInfo, Var};
#[must_use]
pub fn build_bpf_ssa(f: &Function<DecodedInsn>) -> SsaInfo {
crate::ir::ssa::build_ssa(f, insn_reads_writes)
}
#[must_use]
pub fn compute_bpf_liveness(f: &Function<DecodedInsn>) -> Liveness {
crate::ir::ssa::compute_liveness(f, insn_reads_writes)
}
#[must_use]
pub fn insn_reads_writes(insn: &DecodedInsn) -> (Vec<Var>, Vec<Var>) {
let dst = reg(insn.dst);
let src = reg(insn.src);
let is_reg_src = (insn.opcode & 0x08) != 0;
let op_nibble = (insn.opcode >> 4) & 0xf;
match insn.kind {
InsnKind::Alu32 | InsnKind::Alu64 => alu_reads_writes(dst, src, is_reg_src, op_nibble),
InsnKind::JmpCond | InsnKind::JmpCond32 => {
let mut reads = vec![Var::Reg(dst)];
if is_reg_src {
reads.push(Var::Reg(src));
}
(reads, Vec::new())
}
InsnKind::Exit => (vec![Var::Reg("r0".into())], Vec::new()),
InsnKind::Call => (caller_arg_regs(), caller_clobber_regs()),
InsnKind::CallReg => {
let mut reads = caller_arg_regs();
push_unique(&mut reads, Var::Reg(src));
(reads, caller_clobber_regs())
}
InsnKind::Load => {
let mem = memory_var(insn.src, insn.offset);
let reads = if insn.src == 10 {
vec![mem]
} else {
vec![Var::Reg(src), mem]
};
(reads, vec![Var::Reg(dst)])
}
InsnKind::Store => {
let mem = memory_var(insn.dst, insn.offset);
let mut reads = Vec::new();
if insn.dst != 10 {
reads.push(Var::Reg(dst));
}
if is_reg_src {
reads.push(Var::Reg(src));
}
(reads, vec![mem])
}
InsnKind::Lddw => (Vec::new(), vec![Var::Reg(dst)]),
InsnKind::Jmp | InsnKind::LddwSecondHalf | InsnKind::Unknown => (Vec::new(), Vec::new()),
InsnKind::Endian => (vec![Var::Reg(dst.clone())], vec![Var::Reg(dst)]),
}
}
fn alu_reads_writes(
dst: String,
src: String,
is_reg_src: bool,
op_nibble: u8,
) -> (Vec<Var>, Vec<Var>) {
const OP_NEG: u8 = 0x8;
const OP_MOV: u8 = 0xb;
const OP_END: u8 = 0xd;
match op_nibble {
OP_MOV => {
let mut reads = Vec::new();
if is_reg_src {
reads.push(Var::Reg(src));
}
(reads, vec![Var::Reg(dst)])
}
OP_NEG | OP_END => (vec![Var::Reg(dst.clone())], vec![Var::Reg(dst)]),
_ => {
let mut reads = vec![Var::Reg(dst.clone())];
if is_reg_src {
reads.push(Var::Reg(src));
}
(reads, vec![Var::Reg(dst)])
}
}
}
fn caller_arg_regs() -> Vec<Var> {
(1..=5).map(|n| Var::Reg(format!("r{n}"))).collect()
}
fn caller_clobber_regs() -> Vec<Var> {
(0..=5).map(|n| Var::Reg(format!("r{n}"))).collect()
}
fn reg(n: u8) -> String {
format!("r{n}")
}
fn memory_var(base: u8, offset: i16) -> Var {
if base == 10 {
Var::Stack(i64::from(offset))
} else {
Var::Memory
}
}
fn push_unique(v: &mut Vec<Var>, x: Var) {
if !v.contains(&x) {
v.push(x);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::bpf::{BpfVariant, decode};
use crate::ir::ssa::DefSite;
fn lift_linear(bytes: &[u8]) -> Function<DecodedInsn> {
use crate::common::VAddr;
use crate::ir::{BasicBlock, Terminator};
let insns = decode(bytes, 0x1000, BpfVariant::Sbfv1).expect("decode");
let term = match insns.last().map(|i| i.kind) {
Some(InsnKind::Exit) => Terminator::Return,
_ => Terminator::Fallthrough,
};
let block = BasicBlock {
addr: VAddr(0x1000),
insns,
terminator: term,
};
Function {
addr: VAddr(0x1000),
name: "test".into(),
blocks: vec![block],
}
}
#[test]
fn mov_writes_only_dst() {
let bytes = [
0xb7, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let mov = &f.blocks[0].insns[0];
let (reads, writes) = insn_reads_writes(mov);
assert!(reads.is_empty(), "mov-imm should not read; got {reads:?}");
assert_eq!(writes, vec![Var::Reg("r1".into())]);
}
#[test]
fn add64_rmw_reads_and_writes_dst() {
let bytes = [
0x0f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let f = lift_linear(&bytes);
let add = &f.blocks[0].insns[0];
let (reads, writes) = insn_reads_writes(add);
assert!(reads.contains(&Var::Reg("r1".into())));
assert!(reads.contains(&Var::Reg("r2".into())));
assert_eq!(writes, vec![Var::Reg("r1".into())]);
}
#[test]
fn ldxdw_with_r10_resolves_to_stack_slot() {
let bytes = [
0x79, 0xa0, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
let f = lift_linear(&bytes);
let ldx = &f.blocks[0].insns[0];
let (reads, writes) = insn_reads_writes(ldx);
assert!(reads.contains(&Var::Stack(-8)));
assert!(!reads.contains(&Var::Reg("r10".into())));
assert_eq!(writes, vec![Var::Reg("r0".into())]);
}
#[test]
fn ldxdw_non_r10_uses_memory_aggregate() {
let bytes = [
0x79, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
let f = lift_linear(&bytes);
let ldx = &f.blocks[0].insns[0];
let (reads, _writes) = insn_reads_writes(ldx);
assert!(reads.contains(&Var::Reg("r1".into())));
assert!(reads.contains(&Var::Memory));
}
#[test]
fn stxdw_with_r10_writes_stack_slot() {
let bytes = [
0x7b, 0x1a, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
let f = lift_linear(&bytes);
let stx = &f.blocks[0].insns[0];
let (reads, writes) = insn_reads_writes(stx);
assert!(reads.contains(&Var::Reg("r1".into())));
assert_eq!(writes, vec![Var::Stack(-8)]);
}
#[test]
fn call_reads_arg_regs_and_writes_caller_saved() {
let bytes = [
0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
let f = lift_linear(&bytes);
let call = &f.blocks[0].insns[0];
let (reads, writes) = insn_reads_writes(call);
for n in 1..=5 {
assert!(reads.contains(&Var::Reg(format!("r{n}"))));
}
for n in 0..=5 {
assert!(writes.contains(&Var::Reg(format!("r{n}"))));
}
}
#[test]
fn exit_reads_r0_only() {
let bytes = [0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let f = lift_linear(&bytes);
let exit = &f.blocks[0].insns[0];
let (reads, writes) = insn_reads_writes(exit);
assert_eq!(reads, vec![Var::Reg("r0".into())]);
assert!(writes.is_empty());
}
#[test]
fn lddw_writes_dst_no_reads() {
let bytes = [
0x18, 0x01, 0x00, 0x00, 0xbe, 0xba, 0xfe, 0xca, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let f = lift_linear(&bytes);
let lddw = &f.blocks[0].insns[0];
assert_eq!(lddw.kind, InsnKind::Lddw);
let (reads, writes) = insn_reads_writes(lddw);
assert!(reads.is_empty());
assert_eq!(writes, vec![Var::Reg("r1".into())]);
let cont = &f.blocks[0].insns[1];
assert_eq!(cont.kind, InsnKind::LddwSecondHalf);
let (cr, cw) = insn_reads_writes(cont);
assert!(cr.is_empty() && cw.is_empty());
}
#[test]
fn build_ssa_smoke() {
let bytes = [
0xb7, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0xbf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let ssa = build_bpf_ssa(&f);
let r1 = Var::Reg("r1".into());
let reach = ssa
.use_at
.get(&(0x1008, r1))
.copied()
.expect("use at ip=0x1008");
assert!(matches!(
ssa.defs[reach.0 as usize].site,
DefSite::Insn(ip) if ip == 0x1000
));
}
}