1use anyhow::{Context, Result};
7use std::collections::HashMap;
8use std::path::Path;
9
10use crate::spatial::{BoundingBox, Octree};
11use crate::storage::{EdgeRec, MetadataRec, NodePoint, NodeRec, StorageManager};
12use glam::Vec3;
13
14#[derive(Debug, Clone)]
16pub struct CfgBlockMetadata {
17 pub block_kind: String,
18 pub terminator: String,
19 pub byte_start: u64,
20 pub byte_end: u64,
21 pub start_line: u64,
22 pub start_col: u64,
23 pub end_line: u64,
24 pub end_col: u64,
25}
26
27#[derive(Debug, Clone)]
29pub struct CfgBlock {
30 pub id: u64,
31 pub function_id: i64,
32 pub block_kind: String,
33 pub terminator: String,
34 pub byte_start: u64,
35 pub byte_end: u64,
36 pub start_line: u64,
37 pub start_col: u64,
38 pub end_line: u64,
39 pub end_col: u64,
40 pub dominator_depth: u32,
41 pub loop_nesting: u32,
42 pub branch_count: u32,
43}
44
45impl CfgBlock {
46 pub fn to_node_point(&self) -> NodePoint {
47 NodePoint {
48 id: self.id,
49 x: self.dominator_depth as f32,
50 y: self.loop_nesting as f32,
51 z: self.function_id as f32, }
53 }
54
55 pub fn to_node_rec(&self, _assigned_id: u64) -> NodeRec {
56 use crate::spatial::MortonEncode;
57 if let Ok(mut f) = std::fs::OpenOptions::new()
59 .create(true)
60 .append(true)
61 .open("/tmp/cfg_debug.log")
62 {
63 use std::io::Write;
64 let _ = writeln!(
65 f,
66 "to_node_rec: id={}, function_id={}, z={}",
67 self.id, self.function_id, self.function_id as f32
68 );
69 }
70 NodeRec {
71 id: self.id, morton_code: <()>::encode_morton(
73 self.dominator_depth,
74 self.loop_nesting,
75 self.function_id as u32, ),
77 x: self.dominator_depth as f32,
78 y: self.loop_nesting as f32,
79 z: self.function_id as f32, edge_off: 0,
81 edge_len: 0,
82 flags: self.branch_count,
84 begin_ts: 0,
85 end_ts: 0,
86 tx_id: 0,
87 visibility: 1,
88 _padding: [0; 7],
89 }
90 }
91
92 pub fn to_metadata(&self) -> CfgBlockMetadata {
93 CfgBlockMetadata {
94 block_kind: self.block_kind.clone(),
95 terminator: self.terminator.clone(),
96 byte_start: self.byte_start,
97 byte_end: self.byte_end,
98 start_line: self.start_line,
99 start_col: self.start_col,
100 end_line: self.end_line,
101 end_col: self.end_col,
102 }
103 }
104
105 pub fn from_metadata(
106 id: u64,
107 function_id: i64,
108 meta: &CfgBlockMetadata,
109 dominator_depth: u32,
110 loop_nesting: u32,
111 branch_count: u32,
112 ) -> Self {
113 CfgBlock {
114 id,
115 function_id,
116 block_kind: meta.block_kind.clone(),
117 terminator: meta.terminator.clone(),
118 byte_start: meta.byte_start,
119 byte_end: meta.byte_end,
120 start_line: meta.start_line,
121 start_col: meta.start_col,
122 end_line: meta.end_line,
123 end_col: meta.end_col,
124 dominator_depth,
125 loop_nesting,
126 branch_count,
127 }
128 }
129}
130
131pub struct CfgStore {
133 pub octree: Octree,
135 storage: StorageManager,
136 function_bounds: HashMap<i64, BoundingBox>,
137 metadata: HashMap<u64, CfgBlockMetadata>,
139 function_blocks: HashMap<i64, Vec<u64>>,
141}
142
143impl CfgStore {
144 pub fn create(path: &Path) -> Result<Self> {
145 let storage = StorageManager::create(path).context("Failed to create storage")?;
146 let bounds = BoundingBox::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(100.0, 100.0, 100.0));
147 let octree = Octree::new(bounds);
148 Ok(Self {
149 octree,
150 storage,
151 function_bounds: HashMap::new(),
152 metadata: HashMap::new(),
153 function_blocks: HashMap::new(),
154 })
155 }
156
157 pub fn open(path: &Path) -> Result<Self> {
158 let storage = StorageManager::open(path).context("Failed to open storage")?;
159 let bounds = BoundingBox::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(100.0, 100.0, 100.0));
160 let mut octree = Octree::new(bounds);
161
162 let mut metadata = HashMap::new();
164 let mut function_blocks: HashMap<i64, Vec<u64>> = HashMap::new();
165 let mut function_bounds: HashMap<i64, BoundingBox> = HashMap::new();
166
167 for node_id in 0..storage.node_count() as u64 {
169 if let Some(node) = storage.get_node(node_id) {
170 let function_id = node.z as i64;
172
173 let block_metadata = if let Some(meta_rec) = storage.get_metadata(node_id) {
175 CfgBlockMetadata {
176 block_kind: meta_rec.get_block_kind(),
177 terminator: meta_rec.get_terminator(),
178 byte_start: meta_rec.byte_start,
179 byte_end: meta_rec.byte_end,
180 start_line: meta_rec.start_line,
181 start_col: meta_rec.start_col,
182 end_line: meta_rec.end_line,
183 end_col: meta_rec.end_col,
184 }
185 } else {
186 CfgBlockMetadata {
188 block_kind: "unknown".to_string(),
189 terminator: "unknown".to_string(),
190 byte_start: 0,
191 byte_end: 0,
192 start_line: 0,
193 start_col: 0,
194 end_line: 0,
195 end_col: 0,
196 }
197 };
198 metadata.insert(node_id, block_metadata);
199
200 function_blocks
202 .entry(function_id)
203 .or_default()
204 .push(node_id);
205
206 let min = Vec3::new(node.x, node.y, node.z);
208 let max = Vec3::new(node.x, node.y, node.z);
209 let entry = function_bounds
210 .entry(function_id)
211 .or_insert(BoundingBox::new(min, max));
212 entry.min = entry.min.min(min);
213 entry.max = entry.max.max(max);
214
215 let point = NodePoint {
217 id: node_id,
218 x: node.x,
219 y: node.y,
220 z: node.z,
221 };
222 octree.insert(point);
223 }
224 }
225
226 Ok(Self {
227 octree,
228 storage,
229 function_bounds,
230 metadata,
231 function_blocks,
232 })
233 }
234
235 pub fn insert_block(&mut self, block: CfgBlock) -> Result<u64> {
236 let function_id = block.function_id;
237
238 let assigned_id = self
240 .storage
241 .insert_node(block.to_node_rec(self.storage.node_count() as u64))
242 .context("Failed to insert into storage")?;
243
244 let meta_rec = MetadataRec::from_strings(
246 &block.block_kind,
247 &block.terminator,
248 block.byte_start,
249 block.byte_end,
250 block.start_line,
251 block.start_col,
252 block.end_line,
253 block.end_col,
254 );
255
256 self.storage
258 .insert_metadata_at(assigned_id, meta_rec)
259 .context("Failed to insert metadata")?;
260
261 let metadata = block.to_metadata();
263 self.metadata.insert(assigned_id, metadata);
264
265 self.function_blocks
267 .entry(function_id)
268 .or_default()
269 .push(assigned_id);
270
271 self.update_function_bounds(function_id, &block);
272 self.octree.insert(block.to_node_point());
273
274 Ok(assigned_id)
275 }
276
277 pub fn insert_blocks(&mut self, blocks: Vec<CfgBlock>) -> Result<()> {
278 for block in blocks {
279 self.insert_block(block)?;
280 }
281 Ok(())
282 }
283
284 pub fn query_nearby(&self, point: Vec3, radius: f32) -> Vec<NodePoint> {
285 self.octree.query_sphere(point, radius)
286 }
287
288 pub fn query_knn(&self, point: Vec3, k: usize) -> Vec<(NodePoint, f32)> {
301 self.octree.query_knn(point, k)
302 }
303
304 pub fn block_count(&self) -> usize {
305 self.storage.node_count()
306 }
307
308 pub fn metadata_count(&self) -> usize {
309 self.storage.metadata_count()
310 }
311
312 pub fn get_stored_metadata(&self, id: u64) -> Option<&crate::storage::MetadataRec> {
313 self.storage.get_metadata(id)
314 }
315
316 pub fn flush(&mut self) -> Result<()> {
318 self.storage.flush()
319 }
320
321 pub fn get_block_at_timestamp(
326 &self,
327 logical_block_id: u64,
328 timestamp: u64,
329 ) -> Option<CfgBlock> {
330 let _node = self
332 .storage
333 .get_node_at_timestamp(logical_block_id, timestamp)?;
334
335 let versions = self.storage.get_version_history(logical_block_id)?;
339
340 for &version_id in versions {
341 if let Some(node_version) = self.storage.get_node(version_id) {
342 if node_version.begin_ts <= timestamp
343 && (node_version.end_ts == 0 || node_version.end_ts > timestamp)
344 {
345 if let Some(meta) = self.metadata.get(&version_id) {
347 let function_id = self
350 .function_blocks
351 .iter()
352 .find(|(_, blocks)| blocks.contains(&version_id))
353 .map(|(fid, _)| *fid)
354 .unwrap_or(0);
355
356 return Some(CfgBlock::from_metadata(
357 logical_block_id,
358 function_id,
359 meta,
360 node_version.x as u32,
361 node_version.y as u32,
362 node_version.z as u32,
363 ));
364 }
365 }
366 }
367 }
368
369 None
370 }
371
372 pub fn get_blocks_for_function(&self, function_id: i64) -> Vec<CfgBlock> {
374 let mut blocks = Vec::new();
375
376 if let Some(block_ids) = self.function_blocks.get(&function_id) {
378 for &block_id in block_ids {
379 if let Some(node) = self.storage.get_node(block_id) {
381 if let Some(meta) = self.metadata.get(&block_id) {
383 blocks.push(CfgBlock::from_metadata(
386 node.id,
387 function_id,
388 meta,
389 node.x as u32, node.y as u32, node.flags, ));
393 }
394 }
395 }
396 }
397
398 blocks
399 }
400
401 pub fn get_all_edges(&self) -> Vec<EdgeRec> {
402 let mut edges = Vec::new();
403 for i in 0..self.storage.edge_count() {
404 if let Some(edge) = self.storage.get_edge(i as u64) {
405 edges.push(*edge);
406 }
407 }
408 edges
409 }
410 pub fn insert_edge(&mut self, edge: EdgeRec) -> Result<u64> {
412 self.storage
413 .insert_edge(edge)
414 .context("Failed to insert edge")
415 }
416
417 pub fn get_edges_for_node(&self, source_idx: u64) -> Vec<EdgeRec> {
418 let mut edges = Vec::new();
419 for i in 0..self.storage.edge_count() {
420 if let Some(edge) = self.storage.get_edge(i as u64) {
421 if edge.src == source_idx {
422 edges.push(*edge);
423 }
424 }
425 }
426 edges
427 }
428
429 pub fn get_function_ids(&self) -> std::collections::HashSet<i64> {
434 self.function_blocks.keys().copied().collect()
435 }
436
437 pub fn get_block_ids_for_function(&self, function_id: i64) -> Option<&[u64]> {
442 self.function_blocks.get(&function_id).map(|v| v.as_slice())
443 }
444
445 pub fn count_blocks_for_function(&self, function_id: i64) -> usize {
447 self.function_blocks
448 .get(&function_id)
449 .map(|v| v.len())
450 .unwrap_or(0)
451 }
452
453 pub fn edge_count(&self) -> usize {
455 self.storage.edge_count()
456 }
457
458 fn update_function_bounds(&mut self, function_id: i64, block: &CfgBlock) {
459 let pos = Vec3::new(
460 block.dominator_depth as f32,
461 block.loop_nesting as f32,
462 block.branch_count as f32,
463 );
464 self.function_bounds
465 .entry(function_id)
466 .and_modify(|bounds| {
467 bounds.min = bounds.min.min(pos);
468 bounds.max = bounds.max.max(pos);
469 })
470 .or_insert_with(|| {
471 BoundingBox::new(
472 pos - Vec3::new(5.0, 5.0, 5.0),
473 pos + Vec3::new(5.0, 5.0, 5.0),
474 )
475 });
476 }
477}
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::algorithms::astar::CfgGraphNode;
482 use crate::algorithms::dominance::compute_dominance;
483 use tempfile::tempdir;
484
485 #[test]
486 fn test_cfg_store_create() {
487 let temp_dir = tempdir().unwrap();
488 let db_path = temp_dir.path().join("test_cfg.db");
489 let result = CfgStore::create(&db_path);
490 assert!(result.is_ok(), "Should create CFG store");
491 }
492
493 #[test]
497 fn test_dominator_depth_computation_from_cfg() {
498 let nodes = vec![
508 CfgGraphNode {
509 id: 0,
510 x: 0.0,
511 y: 0.0,
512 z: 0.0,
513 successors: vec![1, 4],
514 }, CfgGraphNode {
516 id: 1,
517 x: 1.0,
518 y: 0.0,
519 z: 0.0,
520 successors: vec![2],
521 }, CfgGraphNode {
523 id: 2,
524 x: 2.0,
525 y: 0.0,
526 z: 0.0,
527 successors: vec![3],
528 }, CfgGraphNode {
530 id: 3,
531 x: 3.0,
532 y: 0.0,
533 z: 0.0,
534 successors: vec![],
535 }, CfgGraphNode {
537 id: 4,
538 x: 1.0,
539 y: 1.0,
540 z: 0.0,
541 successors: vec![3],
542 }, ];
544
545 let dom_result = compute_dominance(&nodes, 0);
546
547 assert!(
549 dom_result.dominators.contains_key(&0),
550 "Layer 1: Entry should have dominators"
551 );
552 assert!(
553 dom_result.dominators.contains_key(&3),
554 "Layer 1: Exit block C should have dominators"
555 );
556
557 let entry_depth = dom_result.dominators.get(&0).unwrap().len() as u32;
559 let a_depth = dom_result.dominators.get(&1).unwrap().len() as u32;
560 let b_depth = dom_result.dominators.get(&2).unwrap().len() as u32;
561 let c_depth = dom_result.dominators.get(&3).unwrap().len() as u32;
562 let d_depth = dom_result.dominators.get(&4).unwrap().len() as u32;
563
564 assert_eq!(
565 entry_depth, 1,
566 "Layer 2: Entry should have depth 1 (only itself)"
567 );
568 assert!(
569 a_depth >= 2,
570 "Layer 2: A should have depth >= 2 (Entry + A)"
571 );
572 assert!(b_depth >= a_depth, "Layer 2: B depth should be >= A depth");
573 assert!(
574 c_depth >= 2,
575 "Layer 2: C should have at least entry + self dominators"
576 );
577 assert!(
578 d_depth >= 2,
579 "Layer 2: D should have depth >= 2 (Entry + D)"
580 );
581
582 let temp_dir = tempdir().unwrap();
585 let db_path = temp_dir.path().join("test_dominator.db");
586 let mut store = CfgStore::create(&db_path).unwrap();
587
588 let blocks = vec![
590 CfgBlock {
591 id: 0,
592 function_id: 1,
593 block_kind: "entry".to_string(),
594 terminator: "fallthrough".to_string(),
595 byte_start: 0,
596 byte_end: 10,
597 start_line: 1,
598 start_col: 0,
599 end_line: 1,
600 end_col: 10,
601 dominator_depth: entry_depth,
602 loop_nesting: 0,
603 branch_count: 0,
604 },
605 CfgBlock {
606 id: 1,
607 function_id: 1,
608 block_kind: "block".to_string(),
609 terminator: "fallthrough".to_string(),
610 byte_start: 10,
611 byte_end: 20,
612 start_line: 2,
613 start_col: 0,
614 end_line: 2,
615 end_col: 10,
616 dominator_depth: a_depth,
617 loop_nesting: 0,
618 branch_count: 0,
619 },
620 CfgBlock {
621 id: 2,
622 function_id: 1,
623 block_kind: "block".to_string(),
624 terminator: "fallthrough".to_string(),
625 byte_start: 20,
626 byte_end: 30,
627 start_line: 3,
628 start_col: 0,
629 end_line: 3,
630 end_col: 10,
631 dominator_depth: b_depth,
632 loop_nesting: 0,
633 branch_count: 0,
634 },
635 ];
636
637 for block in &blocks {
638 store.insert_block(block.clone()).unwrap();
639 }
640
641 let retrieved = store.get_blocks_for_function(1);
643 assert_eq!(retrieved.len(), 3, "Layer 3: Should retrieve all 3 blocks");
644
645 let entry_block = retrieved.iter().find(|b| b.block_kind == "entry").unwrap();
647 assert_eq!(
648 entry_block.dominator_depth, 1,
649 "Layer 3: Entry dominator depth should be preserved"
650 );
651
652 let nearby = store.query_nearby(glam::Vec3::new(entry_depth as f32, 0.0, 0.0), 5.0);
655 assert!(
656 !nearby.is_empty(),
657 "Layer 4: Should find entry block by dominator depth coordinate"
658 );
659 }
660
661 #[test]
666 fn test_cfg_store_octree_persistence() {
667 let temp_dir = tempdir().unwrap();
668 let db_path = temp_dir.path().join("test_octree_persist.db");
669
670 let mut store = CfgStore::create(&db_path).unwrap();
672
673 let blocks = vec![
674 CfgBlock {
675 id: 1,
676 function_id: 42,
677 block_kind: "entry".to_string(),
678 terminator: "fallthrough".to_string(),
679 byte_start: 0,
680 byte_end: 50,
681 start_line: 1,
682 start_col: 0,
683 end_line: 5,
684 end_col: 10,
685 dominator_depth: 0,
686 loop_nesting: 0,
687 branch_count: 0,
688 },
689 CfgBlock {
690 id: 2,
691 function_id: 42,
692 block_kind: "if".to_string(),
693 terminator: "conditional".to_string(),
694 byte_start: 50,
695 byte_end: 100,
696 start_line: 6,
697 start_col: 4,
698 end_line: 10,
699 end_col: 15,
700 dominator_depth: 1,
701 loop_nesting: 0,
702 branch_count: 1,
703 },
704 CfgBlock {
705 id: 3,
706 function_id: 42,
707 block_kind: "loop".to_string(),
708 terminator: "jump".to_string(),
709 byte_start: 100,
710 byte_end: 150,
711 start_line: 11,
712 start_col: 4,
713 end_line: 15,
714 end_col: 20,
715 dominator_depth: 2,
716 loop_nesting: 1,
717 branch_count: 2,
718 },
719 ];
720
721 for block in &blocks {
722 store.insert_block(block.clone()).unwrap();
723 }
724
725 let nearby_before = store.query_nearby(glam::Vec3::new(1.0, 0.0, 42.0), 5.0);
727 assert!(
728 !nearby_before.is_empty(),
729 "Should find blocks before persistence"
730 );
731
732 let octree_bytes = store.octree.to_bytes().expect("Should serialize octree");
734 assert!(
735 !octree_bytes.is_empty(),
736 "Serialized octree should not be empty"
737 );
738
739 let mut store2 = CfgStore::create(&db_path).unwrap();
741 store2.octree = Octree::from_bytes(&octree_bytes).expect("Should deserialize octree");
742
743 let nearby_after = store2.query_nearby(glam::Vec3::new(1.0, 0.0, 42.0), 5.0);
745 assert!(
746 !nearby_after.is_empty(),
747 "Should find blocks after octree restore"
748 );
749 assert_eq!(
750 nearby_before.len(),
751 nearby_after.len(),
752 "Should find same number of blocks"
753 );
754 }
755
756 #[test]
760 fn test_cfg_store_function_index_roundtrip() {
761 let temp_dir = tempdir().unwrap();
762 let db_path = temp_dir.path().join("test_function_roundtrip.db");
763
764 let mut store = CfgStore::create(&db_path).unwrap();
766 let block = CfgBlock {
767 id: 1,
768 function_id: 42,
769 block_kind: "entry".to_string(),
770 terminator: "fallthrough".to_string(),
771 byte_start: 0,
772 byte_end: 50,
773 start_line: 1,
774 start_col: 0,
775 end_line: 5,
776 end_col: 10,
777 dominator_depth: 0,
778 loop_nesting: 0,
779 branch_count: 0,
780 };
781 store.insert_block(block.clone()).unwrap();
782 assert_eq!(store.count_blocks_for_function(42), 1);
783
784 let store2 = CfgStore::open(&db_path).unwrap();
786 assert_eq!(
787 store2.count_blocks_for_function(42),
788 1,
789 "function_blocks should be rebuilt from storage on open"
790 );
791 }
792 #[test]
793 #[cfg(feature = "telemetry")]
794 fn test_octree_telemetry_loop_guard() {
795 use crate::telemetry::LoopGuard;
796
797 let guard = LoopGuard::new("octree_traversal", 100);
799
800 for i in 0..150 {
802 if i < 100 {
803 assert!(guard.check().is_ok(), "Should allow iteration {}", i);
804 } else {
805 assert!(guard.check().is_err(), "Should fail at iteration {}", i);
806 break;
807 }
808 }
809 }
810}