Download crates/invariant-extractor/src/extractor.rs from Snapkitty/rowm-polymorphic-notebook: direct link, hf CLI and curl.
- Browser
- Download file 7.82 kB
-
https://huggingface.co/Snapkitty/rowm-polymorphic-notebook/resolve/main/crates/invariant-extractor/src/extractor.rs
- Command line
-
hf download hf://Snapkitty/rowm-polymorphic-notebook/crates/invariant-extractor/src/extractor.rs
-
curl -L -o extractor.rs https://huggingface.co/Snapkitty/rowm-polymorphic-notebook/resolve/main/crates/invariant-extractor/src/extractor.rs
7.82 kB
| //! Invariant Extractor — main orchestrator | |
| use crate::symbolic::{SymbolicState, SymbolicValue}; | |
| use crate::abstract_domain::{AbstractDomain, Interval}; | |
| use crate::predicates::Predicate; | |
| use crate::subleq_analysis::{SubleqPattern, PatternMatcher}; | |
| use anyhow::{Result, anyhow}; | |
| use std::collections::{HashMap, HashSet}; | |
| use tracing::{debug, info}; | |
| pub struct InvariantExtractor { | |
| extracted_predicates: Vec<Predicate>, | |
| symbolic_state: SymbolicState, | |
| abstract_domain: Box<dyn AbstractDomain>, | |
| pattern_matcher: PatternMatcher, | |
| loop_headers: HashSet<usize>, | |
| invariant_cache: HashMap<usize, Vec<Predicate>>, | |
| } | |
| impl InvariantExtractor { | |
| pub fn new() -> Self { | |
| Self { | |
| extracted_predicates: Vec::new(), | |
| symbolic_state: SymbolicState::new(), | |
| abstract_domain: Box::new(Interval::new()), | |
| pattern_matcher: PatternMatcher::new(), | |
| loop_headers: HashSet::new(), | |
| invariant_cache: HashMap::new(), | |
| } | |
| } | |
| /// Analyze a SUBLEQ memory block and extract invariants | |
| pub fn analyze_block(&mut self, bytecode: &[i64], entry_ip: usize) -> Result<&Vec<Predicate>> { | |
| info!(entry_ip, len = bytecode.len(), "Starting invariant extraction"); | |
| // 1. Identify control flow structure (loop headers, basic blocks) | |
| self.discover_loops(bytecode, entry_ip)?; | |
| // 2. Symbolic execution from entry point | |
| self.symbolic_state.reset(); | |
| self.execute_symbolic(bytecode, entry_ip)?; | |
| // 3. Abstract interpretation over memory states | |
| self.abstract_interpret(bytecode, entry_ip)?; | |
| // 4. Pattern matching for known SUBLEQ idioms | |
| self.match_patterns(bytecode)?; | |
| // 5. Generate SMT-LIB predicates | |
| self.generate_predicates()?; | |
| Ok(&self.extracted_predicates) | |
| } | |
| /// Discover loop headers via backward branch analysis | |
| fn discover_loops(&mut self, bytecode: &[i64], entry: usize) -> Result<()> { | |
| let mut visited = HashSet::new(); | |
| let mut stack = vec![entry]; | |
| while let Some(ip) = stack.pop() { | |
| if !visited.insert(ip) { | |
| continue; | |
| } | |
| if ip + 2 >= bytecode.len() { | |
| continue; | |
| } | |
| let c = bytecode[ip + 2] as usize; | |
| // Forward edge | |
| if ip + 3 < bytecode.len() { | |
| stack.push(ip + 3); | |
| } | |
| // Backward edge → loop header | |
| if c < ip { | |
| self.loop_headers.insert(c); | |
| } | |
| if c < bytecode.len() { | |
| stack.push(c); | |
| } | |
| } | |
| debug!(count = self.loop_headers.len(), "Discovered loop headers"); | |
| Ok(()) | |
| } | |
| /// Symbolic execution over SUBLEQ instructions | |
| fn execute_symbolic(&mut self, bytecode: &[i64], mut ip: usize) -> Result<()> { | |
| const MAX_STEPS: u64 = 10_000; | |
| let mut steps = 0; | |
| while ip + 2 < bytecode.len() && steps < MAX_STEPS { | |
| let a = bytecode[ip] as usize; | |
| let b = bytecode[ip + 1] as usize; | |
| let c = bytecode[ip + 2] as usize; | |
| // Symbolic values | |
| let sym_a = self.symbolic_state.get_or_create(a); | |
| let sym_b = self.symbolic_state.get_or_create(b); | |
| // M[b] ← M[b] - M[a] | |
| let sym_res = SymbolicValue::sub(sym_b, sym_a); | |
| self.symbolic_state.assign(b, sym_res.clone()); | |
| // Record invariant at loop headers | |
| if self.loop_headers.contains(&ip) { | |
| let inv = self.abstract_domain.widen_at(ip, &self.symbolic_state); | |
| self.invariant_cache.entry(ip).or_default().push(inv); | |
| } | |
| // Choose path | |
| ip = if self.should_branch(&sym_res) { c } else { ip + 3 }; | |
| steps += 1; | |
| } | |
| Ok(()) | |
| } | |
| /// Abstract interpretation with widening | |
| fn abstract_interpret(&mut self, bytecode: &[i64], entry: usize) -> Result<()> { | |
| let mut worklist = vec![entry]; | |
| let mut abstract_states: HashMap<usize, Interval> = HashMap::new(); | |
| abstract_states.insert(entry, Interval::top()); | |
| while let Some(ip) = worklist.pop() { | |
| if ip + 2 >= bytecode.len() { | |
| continue; | |
| } | |
| let current = abstract_states[&ip].clone(); | |
| let c = bytecode[ip + 2] as usize; | |
| // Next IP candidates | |
| let next_ips = vec![ip + 3, c]; | |
| for next_ip in next_ips { | |
| if next_ip >= bytecode.len() { | |
| continue; | |
| } | |
| let widened = if self.loop_headers.contains(&next_ip) { | |
| current.widen(abstract_states.get(&next_ip).unwrap_or(&Interval::bottom())) | |
| } else { | |
| current.clone() | |
| }; | |
| if abstract_states.get(&next_ip) != Some(&widened) { | |
| abstract_states.insert(next_ip, widened); | |
| worklist.push(next_ip); | |
| } | |
| } | |
| } | |
| // Convert abstract states to predicates | |
| for (ip, state) in abstract_states { | |
| if let Some(range) = state.get(0) { | |
| self.extracted_predicates.push(Predicate::from_interval(ip, state)); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// Match known SUBLEQ patterns | |
| fn match_patterns(&mut self, bytecode: &[i64]) -> Result<()> { | |
| for pattern in self.pattern_matcher.scan(bytecode) { | |
| match pattern { | |
| SubleqPattern::Clear { target, .. } => { | |
| self.extracted_predicates.push(Predicate::Eq( | |
| SymbolicValue::Mem(target), | |
| SymbolicValue::Const(0), | |
| )); | |
| } | |
| SubleqPattern::Loop { header, counter, .. } => { | |
| self.extracted_predicates.push(Predicate::LoopInvariant { | |
| header, | |
| predicate: Box::new(Predicate::Le( | |
| SymbolicValue::Const(0), | |
| SymbolicValue::Mem(counter), | |
| )), | |
| }); | |
| } | |
| _ => {} | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn generate_predicates(&mut self) -> Result<()> { | |
| // Convert cached loop invariants | |
| for (header, preds) in self.invariant_cache.drain() { | |
| for p in preds { | |
| self.extracted_predicates.push(p); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn should_branch(&self, val: &SymbolicValue) -> bool { | |
| // If value is <= 0, branch | |
| matches!(val, SymbolicValue::Const(n) if *n <= 0) | |
| } | |
| } | |
| impl Default for InvariantExtractor { | |
| fn default() -> Self { | |
| Self::new() | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| fn test_loop_discovery() { | |
| let bytecode = vec![ | |
| 0, 1, 3, // Forward | |
| 1, 2, 0, // Backward to 0 | |
| ]; | |
| let mut extractor = InvariantExtractor::new(); | |
| extractor.discover_loops(&bytecode, 0).unwrap(); | |
| assert!(extractor.loop_headers.contains(&0)); | |
| } | |
| fn test_pattern_matching() { | |
| let bytecode = vec![ | |
| 5, 5, 3, // Clear M[5] | |
| 0, 0, 0, // Halt | |
| ]; | |
| let mut extractor = InvariantExtractor::new(); | |
| extractor.match_patterns(&bytecode).unwrap(); | |
| assert!(extractor.extracted_predicates.len() > 0); | |
| } | |
| } | |