weir/lib.rs
1//! Weir dataflow analysis crate.
2//!
3//! This crate owns dataflow analyses and reusable fixed-point execution
4//! scaffolding. CPU functions in module internals are parity oracles only; the
5//! production surface builds Vyre programs for GPU dispatch.
6//!
7//! # Example: dominators on a diamond CFG
8//!
9//! ```
10//! use std::collections::{HashMap, HashSet};
11//! use weir::ssa::{try_compute_dominators, Block, Cfg};
12//!
13//! // CFG: 0 → {1, 2} → 3
14//! let mut blocks = HashMap::new();
15//! for (id, preds, succs) in [
16//! (0u32, vec![], vec![1, 2]),
17//! (1, vec![0], vec![3]),
18//! (2, vec![0], vec![3]),
19//! (3, vec![1, 2], vec![]),
20//! ] {
21//! blocks.insert(id, Block { id, preds, succs, defs: HashSet::new(), uses: HashSet::new() });
22//! }
23//!
24//! let idoms = try_compute_dominators(&Cfg { entry: 0, blocks }).unwrap();
25//! assert_eq!(idoms[&1], 0);
26//! assert_eq!(idoms[&2], 0);
27//! assert_eq!(idoms[&3], 0); // merge point is dominated by the diamond head
28//! ```
29
30/// Unifying trait and generic drivers for Weir's bitset fixed-point families.
31///
32/// This module is primarily used via the [`define_analysis_family!`] macro.
33/// See [`reaching`](crate::reaching), [`live`](crate::live),
34/// [`points_to`](crate::points_to), and [`slice`](crate::slice) for
35/// concrete families.
36pub mod analysis_family;
37
38/// Call-graph construction with indirect resolution (DF-5).
39///
40/// Build a callgraph propagation Program:
41///
42/// ```no_run
43/// use weir::callgraph::callgraph_build;
44///
45/// let program = callgraph_build("direct", "indirect", "pts", "cg_out");
46/// ```
47pub mod callgraph;
48
49#[cfg(any(test, feature = "cpu-parity"))]
50/// Bitset closure oracle for CPU parity verification.
51///
52/// ```ignore
53/// use weir::bitset_closure_oracle;
54/// ```
55pub mod bitset_closure_oracle;
56
57/// Control-dependence query (does `b` execute on every path through `a`?).
58///
59/// Build a control-dependence Program:
60///
61/// ```no_run
62/// use weir::control_dependence::control_dependence;
63///
64/// let program = control_dependence(4, "successor_pdom", "pdom_n", "out");
65/// ```
66pub mod control_dependence;
67
68/// Cross-language FFI reachability analysis.
69///
70/// Build a cross-language reach Program:
71///
72/// ```no_run
73/// use weir::cross_language::cross_language;
74///
75/// let program = cross_language(
76/// 4, "source", "sink", "post_cross",
77/// "current", "next", "changed", "seed", "out",
78/// ).unwrap();
79/// ```
80pub mod cross_language;
81
82/// Def-use chain queries over SSA form (DF-11).
83///
84/// Look up uses of an SSA definition:
85///
86/// ```
87/// use std::collections::HashMap;
88/// use weir::def_use::try_def_use_chain;
89/// use weir::ssa::SsaForm;
90///
91/// let form = SsaForm {
92/// phi_nodes: HashMap::new(),
93/// renamed_usages: HashMap::new(),
94/// def_use_chains: {
95/// let mut m = HashMap::new();
96/// m.insert(7, vec![3, 5, 9]);
97/// m
98/// },
99/// };
100///
101/// assert_eq!(try_def_use_chain(&form, 7).unwrap(), vec![3, 5, 9]);
102/// ```
103pub mod def_use;
104
105mod dense_domain;
106pub mod dispatch_decode;
107mod dispatch_input_cache;
108pub mod resident_cache_identity;
109
110/// Dominator-tree construction and query (DF-1).
111///
112/// Build a dominator predicate Program:
113///
114/// ```no_run
115/// use weir::dominators::dominates;
116///
117/// let program = dominates(4, "dom_set", "target", "out");
118/// ```
119pub mod dominators;
120
121mod error_format;
122
123/// Escape analysis: does a pointer leave its defining frame? (DF-8).
124///
125/// Build an escape-propagation Program:
126///
127/// ```no_run
128/// use weir::escape::escape_analyze_with_count;
129///
130/// let program = escape_analyze_with_count("pts", "cg", "out", 64);
131/// ```
132pub mod escape;
133
134/// Escape-set query over points-to sets.
135///
136/// Build an escape-query Program:
137///
138/// ```no_run
139/// use weir::escapes::escapes;
140///
141/// let program = escapes(4, "pts", "escape_set", "out");
142/// ```
143pub mod escapes;
144
145/// Batch fixed-point execution over multiple seed sets.
146///
147/// ```ignore
148/// use weir::fixed_point_batch::FixedPointBatch;
149/// ```
150pub mod fixed_point_batch;
151
152mod fixed_point_closure;
153
154/// Scale-aware execution planning for fixed-point analyses.
155///
156/// Build an execution plan from graph shape:
157///
158/// ```
159/// use weir::fixed_point_execution_plan::{plan_from_parts, FixedPointExecutionPlan};
160/// use weir::fixed_point_scratch::FrontierDensityTelemetry;
161///
162/// let telemetry = FrontierDensityTelemetry::default();
163/// let plan = plan_from_parts(128, 16, 0, 2048, telemetry).unwrap();
164/// assert!(!plan.should_use_resident_graph());
165/// ```
166pub mod fixed_point_execution_plan;
167
168/// LRU cache for reusable fixed-point execution plans.
169///
170/// ```ignore
171/// use weir::fixed_point_execution_plan_cache::FixedPointExecutionPlanCache;
172/// ```
173pub mod fixed_point_execution_plan_cache;
174
175/// Packed forward CSR graph buffers for fixed-point GPU wrappers.
176///
177/// Build a prepared graph for generic forward analysis:
178///
179/// ```
180/// use weir::fixed_point_graph::FixedPointForwardGraph;
181///
182/// let graph = FixedPointForwardGraph::new(
183/// "example", 2, &[0, 1, 1], &[1], &[0xFFFF_FFFF],
184/// ).unwrap();
185/// assert_eq!(graph.node_count(), 2);
186/// assert_eq!(graph.edge_count(), 1);
187/// ```
188pub mod fixed_point_graph;
189
190/// Resident-GPU fixed-point closure drivers.
191///
192/// ```ignore
193/// use weir::fixed_point_resident::FixedPointResidentGraph;
194/// ```
195pub mod fixed_point_resident;
196
197/// Batch resident fixed-point execution.
198///
199/// ```ignore
200/// use weir::fixed_point_resident_batch::FixedPointResidentBatch;
201/// ```
202pub mod fixed_point_resident_batch;
203
204/// Cache for resident fixed-point graph and plan reuse.
205///
206/// ```ignore
207/// use weir::fixed_point_resident_cache::FixedPointResidentGraphCache;
208/// ```
209pub mod fixed_point_resident_cache;
210
211/// Reusable resident frontier scratch buffers.
212///
213/// ```ignore
214/// use weir::fixed_point_resident_frontier::FixedPointResidentFrontierScratch;
215/// ```
216pub mod fixed_point_resident_frontier;
217
218/// Resident execution plan construction.
219///
220/// ```ignore
221/// use weir::fixed_point_resident_plan::FixedPointResidentPlan;
222/// ```
223pub mod fixed_point_resident_plan;
224
225mod fixed_point_resident_sequence;
226
227/// Reusable host-side scratch for fixed-point graph preparation.
228///
229/// ```
230/// use weir::fixed_point_scratch::FixedPointScratch;
231///
232/// let scratch = FixedPointScratch::default();
233/// ```
234pub mod fixed_point_scratch;
235
236/// Shared graph-layout contracts for CSR normalization.
237///
238/// Normalize a raw CSR into a canonical layout:
239///
240/// ```
241/// use weir::graph_layout::{CsrGraph, NormalizedCsrGraph, CsrGraphNormalizationScratch};
242///
243/// let graph = CsrGraph::new(2, &[0, 1, 1], &[1], &[0xFFFF_FFFF]);
244/// let normalized = graph.normalize("example").unwrap();
245/// assert_eq!(normalized.node_count(), 2);
246/// assert_eq!(normalized.edge_count(), 1);
247/// ```
248pub mod graph_layout;
249
250/// IFDS/IDE interprocedural dataflow framework (DF-4).
251///
252/// Build one reachability step over the exploded supergraph:
253///
254/// ```no_run
255/// use weir::ifds::ifds_reach_step;
256/// use vyre_primitives::graph::program_graph::ProgramGraphShape;
257///
258/// let shape = ProgramGraphShape::new(4, 4);
259/// let step = ifds_reach_step(shape, "fin", "fout");
260/// ```
261pub mod ifds;
262
263mod ifds_borrowed_solve;
264#[cfg(any(test, feature = "cpu-parity"))]
265mod ifds_cpu_oracle;
266#[cfg(test)]
267mod ifds_cpu_oracle_tests;
268mod ifds_csr_prepare;
269mod ifds_csr_split;
270mod ifds_frontier_decode;
271
272/// GPU-native IFDS/IDE driver (G3).
273///
274/// Build a BFS step over the exploded supergraph:
275///
276/// ```no_run
277/// use weir::ifds_gpu::{ifds_gpu_step, IfdsShape};
278///
279/// let shape = IfdsShape {
280/// num_procs: 1, blocks_per_proc: 4, facts_per_proc: 4, edge_count: 4,
281/// };
282/// let step = ifds_gpu_step(shape, "fin", "fout").unwrap();
283/// ```
284pub mod ifds_gpu;
285
286mod ifds_gpu_bytes;
287mod ifds_resident_alloc;
288
289/// Batch resident IFDS execution.
290///
291/// ```ignore
292/// use weir::ifds_resident_batch::ResidentIfdsBatch;
293/// ```
294pub mod ifds_resident_batch;
295
296/// Cache for resident IFDS graph reuse.
297///
298/// ```ignore
299/// use weir::ifds_resident_cache::ResidentIfdsCache;
300/// ```
301pub mod ifds_resident_cache;
302
303/// Direct-resident IFDS batch facade.
304///
305/// ```ignore
306/// use weir::ifds_resident_direct_batch::DirectResidentIfdsBatch;
307/// ```
308pub mod ifds_resident_direct_batch;
309
310/// Direct resident IFDS CSR preparation.
311///
312/// ```ignore
313/// use weir::ifds_resident_direct_prepare::prepare_ifds_csr_resident_direct_via;
314/// ```
315pub mod ifds_resident_direct_prepare;
316
317/// Reusable host scratch for direct-resident IFDS CSR preparation.
318///
319/// ```ignore
320/// use weir::ifds_resident_direct_prepare_scratch::DirectResidentIfdsPrepareScratch;
321/// ```
322pub mod ifds_resident_direct_prepare_scratch;
323
324mod ifds_resident_solve;
325mod ifds_resident_types;
326mod ifds_seed_frontiers;
327mod ifds_seed_pack;
328mod ifds_shape;
329
330/// Adapter from the common Vyre resident backend contract to Weir IFDS.
331///
332/// ```ignore
333/// use weir::ifds_vyre_resident::VyreResidentIfds;
334/// ```
335pub mod ifds_vyre_resident;
336
337/// Live-variable analysis (DF-2 backward variant).
338///
339/// Prepare a graph for backward live-variable closure:
340///
341/// ```
342/// use weir::live::prepare_live_graph;
343///
344/// let graph = prepare_live_graph(
345/// 2,
346/// &[0, 1, 1],
347/// &[1],
348/// &[0xFFFF_FFFF],
349/// ).unwrap();
350/// assert_eq!(graph.node_count(), 2);
351/// ```
352pub mod live;
353
354/// Liveness query: is variable `v` live at node `n`?
355///
356/// Build a live-at query Program:
357///
358/// ```no_run
359/// use weir::live_at::live_at;
360///
361/// let program = live_at(4, "live_in", "query", "out");
362/// ```
363pub mod live_at;
364
365/// Loop summarization with widening+narrowing (DF-10).
366///
367/// Build a loop-summary Program:
368///
369/// ```no_run
370/// use weir::loop_sum::loop_summarize;
371///
372/// let program = loop_summarize("prev", "next", "out");
373/// ```
374pub mod loop_sum;
375
376/// May-alias query packed as a bitset.
377///
378/// Build a may-alias Program:
379///
380/// ```no_run
381/// use weir::may_alias::may_alias;
382///
383/// let program = may_alias(4, "pts_p", "pts_q", "scratch", "out");
384/// ```
385pub mod may_alias;
386
387/// Must-initialization analysis.
388///
389/// Build a must-init query Program:
390///
391/// ```no_run
392/// use weir::must_init::must_init;
393///
394/// let program = must_init(4, "def_dominates", "use_set", "out");
395/// ```
396pub mod must_init;
397
398#[cfg(any(test, feature = "cpu-parity"))]
399/// CPU parity oracle dispatch surface.
400///
401/// ```ignore
402/// use weir::oracle;
403/// ```
404pub mod oracle;
405
406/// Relation import/export fixtures for IFDS and differential evidence.
407///
408/// ```ignore
409/// use weir::relation_interchange::{
410/// export_relation_interchange_bytes, import_relation_interchange_bytes,
411/// };
412/// ```
413pub mod relation_interchange;
414
415mod output_scratch;
416
417/// Andersen points-to analysis (DF-3).
418///
419/// Prepare a graph for points-to subset closure:
420///
421/// ```
422/// use weir::points_to::prepare_points_to_subset_graph;
423///
424/// let graph = prepare_points_to_subset_graph(
425/// 2,
426/// &[0, 1, 1],
427/// &[1],
428/// &[0xFFFF_FFFF],
429/// ).unwrap();
430/// assert_eq!(graph.node_count(), 2);
431/// ```
432pub mod points_to;
433
434/// Post-dominator tree query.
435///
436/// Build a post-dominates query Program:
437///
438/// ```no_run
439/// use weir::post_dominates::post_dominates;
440///
441/// let program = post_dominates(4, "pdom_set", "target_set", "out");
442/// ```
443pub mod post_dominates;
444
445/// Interval range propagation (DF-7).
446///
447/// Build a range-propagation Program:
448///
449/// ```no_run
450/// use weir::range::range_propagate;
451///
452/// let program = range_propagate("defs_in", "edges_in", "ranges_out");
453/// ```
454pub mod range;
455
456/// Interval bound check for VSA results.
457///
458/// Build a range-check Program:
459///
460/// ```no_run
461/// use weir::range_check::range_check;
462///
463/// let program = range_check(4, "interval_hi", "bound", "out");
464/// ```
465pub mod range_check;
466
467/// Witness extraction from IFDS reachability results.
468///
469/// Prepare a witness graph and extract a source-to-sink path:
470///
471/// ```
472/// use weir::reachability_witness::{
473/// prepare_witness_graph, extract_path_prepared, NodeAttr, PathSeed,
474/// };
475///
476/// let attrs = vec![
477/// NodeAttr { byte_start: 0, byte_end: 4, file_idx: 0 },
478/// NodeAttr { byte_start: 5, byte_end: 9, file_idx: 0 },
479/// ];
480/// let files = vec!["example.c".to_string()];
481/// let descriptions = vec!["source".to_string(), "sink".to_string()];
482/// let edge_offsets = vec![0, 1, 1];
483/// let edge_targets = vec![1];
484/// let edge_kind_mask = vec![1u32];
485///
486/// let graph = prepare_witness_graph(
487/// &edge_offsets, &edge_targets, &edge_kind_mask,
488/// &attrs, &files, &descriptions, "c-c11",
489/// );
490///
491/// let seed = PathSeed {
492/// source_file: "example.c".to_string(),
493/// source_node: 0,
494/// sink_file: "example.c".to_string(),
495/// sink_node: 1,
496/// };
497/// let source_reach = vec![u32::MAX; 1];
498/// let path = extract_path_prepared(&seed, &source_reach, &[], &graph).unwrap();
499/// assert_eq!(path.statements.len(), 2);
500/// ```
501pub mod reachability_witness;
502
503/// Reaching definitions analysis (DF-2).
504///
505/// Prepare a graph for forward reaching-definitions closure:
506///
507/// ```
508/// use weir::reaching::prepare_reaching_graph;
509///
510/// let graph = prepare_reaching_graph(
511/// 2,
512/// &[0, 1, 1],
513/// &[1],
514/// &[0xFFFF_FFFF],
515/// ).unwrap();
516/// assert_eq!(graph.node_count(), 2);
517/// assert_eq!(graph.edge_count(), 1);
518/// ```
519pub mod reaching;
520
521/// Reaching-definitions query packed as a bitset.
522///
523/// Build a reaching-def query Program:
524///
525/// ```no_run
526/// use weir::reaching_def::reaching_def;
527///
528/// let program = reaching_def(4, "gen_kill_in", "use_set", "out");
529/// ```
530pub mod reaching_def;
531
532/// Compact reaching-definition summary queries.
533///
534/// Fold partial summary triples into a single summary:
535///
536/// ```
537/// use weir::reaching_def_summary::fold_summary_partials;
538///
539/// let partials = [3, 1, 0xAB, 2, 0, 0xCD, 5, 1, 0xEF];
540/// let [count, any, checksum] = fold_summary_partials(&partials);
541/// assert_eq!(count, 10);
542/// assert_eq!(any, 1);
543/// assert_eq!(checksum, 0xAB ^ 0xEF);
544/// ```
545pub mod reaching_def_summary;
546
547mod resident_cache_lru;
548
549/// Strongly-connected-component membership query.
550///
551/// Build an SCC-membership query Program:
552///
553/// ```no_run
554/// use weir::scc_query::scc_query;
555///
556/// let program = scc_query(4, "same_scc", "query", "out");
557/// ```
558pub mod scc_query;
559
560/// Backward program slicer (DF-6).
561///
562/// Prepare a graph for backward slicing:
563///
564/// ```
565/// use weir::slice::prepare_slice_graph;
566///
567/// let graph = prepare_slice_graph(
568/// 2,
569/// &[0, 1, 1],
570/// &[1],
571/// &[0xFFFF_FFFF],
572/// ).unwrap();
573/// assert_eq!(graph.node_count(), 2);
574/// ```
575pub mod slice;
576
577/// Soundness regime markers and evidence types.
578///
579/// Tag a dataflow result with its soundness regime:
580///
581/// ```
582/// use weir::soundness::{DataflowEvidence, Soundness, PrimitiveSoundness, PrecisionContract};
583///
584/// let evidence = DataflowEvidence::new(vec![1u32, 2, 3], Soundness::Exact);
585/// assert_eq!(evidence.soundness, Soundness::Exact);
586///
587/// let primitive = PrimitiveSoundness::new("weir::ssa", Soundness::Exact);
588/// assert_eq!(primitive.op_id, "weir::ssa");
589/// let contract = PrecisionContract::ZeroFalsePositive;
590/// ```
591pub mod soundness;
592
593/// SSA construction via Cytron's algorithm (DF-1).
594///
595/// Compute immediate dominators for a diamond CFG:
596///
597/// ```
598/// use std::collections::{HashMap, HashSet};
599/// use weir::ssa::{try_compute_dominators, Block, Cfg};
600///
601/// let mut blocks = HashMap::new();
602/// for (id, preds, succs) in [
603/// (0u32, vec![], vec![1, 2]),
604/// (1, vec![0], vec![3]),
605/// (2, vec![0], vec![3]),
606/// (3, vec![1, 2], vec![]),
607/// ] {
608/// blocks.insert(id, Block { id, preds, succs, defs: HashSet::new(), uses: HashSet::new() });
609/// }
610///
611/// let idoms = try_compute_dominators(&Cfg { entry: 0, blocks }).unwrap();
612/// assert_eq!(idoms[&1], 0);
613/// assert_eq!(idoms[&2], 0);
614/// assert_eq!(idoms[&3], 0);
615/// ```
616pub mod ssa;
617
618mod staging_reserve;
619
620/// Persistent procedure summaries (DF-9).
621///
622/// Build a summary-computation Program:
623///
624/// ```no_run
625/// use weir::summary::summarize_function;
626///
627/// let program = summarize_function(
628/// "fn_ast_in", "callgraph_in", "cached_summary_in", "summary_out",
629/// );
630/// ```
631pub mod summary;
632
633#[cfg(any(test, feature = "test-harness"))]
634/// Test harness for parity and conformance testing.
635///
636/// ```ignore
637/// use weir::test_harness;
638/// ```
639#[cfg(feature = "test-harness")]
640pub mod test_harness;
641
642/// Forward and backward CFG traversal step builders.
643///
644/// Build a forward CFG propagation step:
645///
646/// ```no_run
647/// use weir::traversal_step::forward_cfg_step;
648/// use vyre_primitives::graph::program_graph::ProgramGraphShape;
649///
650/// let shape = ProgramGraphShape::new(4, 4);
651/// let step = forward_cfg_step(shape, "fin", "fout");
652/// ```
653pub mod traversal_step;
654
655/// Constant-value-set reachability query.
656///
657/// Build a value-set query Program:
658///
659/// ```no_run
660/// use weir::value_set::value_set;
661///
662/// let program = value_set(4, "const_in", "query", "out");
663/// ```
664pub mod value_set;
665
666#[cfg(test)]
667mod tests {
668 mod test_ssa;
669
670 #[test]
671 fn crate_private_cpu_helpers_are_explicit_reference_oracles() {
672 let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
673 let src = manifest_dir.join("src");
674 let mut stack = vec![src];
675 let mut findings = Vec::new();
676 while let Some(path) = stack.pop() {
677 let entries = std::fs::read_dir(&path)
678 .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
679 for entry in entries {
680 let entry = entry.unwrap_or_else(|error| {
681 panic!(
682 "failed to read directory entry under {}: {error}",
683 path.display()
684 )
685 });
686 let path = entry.path();
687 if path.is_dir() {
688 stack.push(path);
689 continue;
690 }
691 if path.extension().and_then(std::ffi::OsStr::to_str) != Some("rs") {
692 continue;
693 }
694 let source = std::fs::read_to_string(&path)
695 .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
696 let lines = source.lines().collect::<Vec<_>>();
697 for (line_index, line) in lines.iter().enumerate() {
698 let trimmed = line.trim_start();
699 if !trimmed.starts_with("pub(crate) fn ") || !trimmed.contains("cpu") {
700 continue;
701 }
702 let context_start = line_index.saturating_sub(4);
703 let context = lines[context_start..=line_index].join("\n");
704 if !context.contains("#[deprecated")
705 || !context.contains("reference oracle only")
706 {
707 findings.push(format!(
708 "{}:{}: {line}",
709 path.strip_prefix(manifest_dir).unwrap_or(&path).display(),
710 line_index + 1
711 ));
712 }
713 }
714 }
715 }
716 assert!(
717 findings.is_empty(),
718 "crate-private CPU helpers must be explicit deprecated reference oracles, never production fallback hooks:\n{}",
719 findings.join("\n")
720 );
721 }
722
723 #[test]
724 fn production_modules_do_not_call_cpu_oracles_directly() {
725 const CPU_ORACLE_NAMES: &[&str] = &[
726 "callgraph_build_cpu",
727 "compute_cpu",
728 "cpu_ref",
729 "cpu_ref_sharded",
730 "cpu_subset_closure",
731 "ifds_reach_closure_cpu",
732 "live_closure_cpu",
733 "loop_summarize_cpu",
734 "range_propagate_cpu",
735 "reaching_closure_cpu",
736 "slice_closure_cpu",
737 "solve_cpu",
738 "summarize_function_cpu",
739 ];
740
741 let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
742 let src = manifest_dir.join("src");
743 let mut stack = vec![src];
744 let mut findings = Vec::new();
745 while let Some(path) = stack.pop() {
746 let entries = std::fs::read_dir(&path)
747 .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
748 for entry in entries {
749 let entry = entry.unwrap_or_else(|error| {
750 panic!(
751 "failed to read directory entry under {}: {error}",
752 path.display()
753 )
754 });
755 let path = entry.path();
756 if path.is_dir() {
757 stack.push(path);
758 continue;
759 }
760 if path.extension().and_then(std::ffi::OsStr::to_str) != Some("rs") {
761 continue;
762 }
763 let relative = path.strip_prefix(manifest_dir).unwrap_or(&path);
764 let relative_text = relative.to_string_lossy();
765 if relative_text.contains("/oracle/")
766 || relative_text.contains("_tests/")
767 || relative_text.contains("/test_harness/")
768 || relative_text.ends_with("_tests.rs")
769 || relative_text.ends_with("/tests.rs")
770 || relative_text.ends_with("_cpu_oracle.rs")
771 || relative_text.ends_with("/cpu_oracle.rs")
772 || relative_text.ends_with("/oracle.rs")
773 {
774 continue;
775 }
776 let source = std::fs::read_to_string(&path)
777 .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
778 let mut deprecated_attr_body = false;
779 let mut pending_reference_oracle_deprecation = false;
780 let mut reference_oracle_body_depth = 0i32;
781 let mut pending_test_cfg = false;
782 let mut test_module_depth = 0i32;
783 for (line_index, line) in source.lines().enumerate() {
784 let trimmed = line.trim_start();
785 if test_module_depth > 0 {
786 test_module_depth += super::brace_delta(line);
787 continue;
788 }
789 if reference_oracle_body_depth > 0 {
790 reference_oracle_body_depth += super::brace_delta(line);
791 continue;
792 }
793 if trimmed.starts_with("#[cfg(test)]") {
794 pending_test_cfg = true;
795 continue;
796 }
797 if pending_test_cfg {
798 if trimmed.starts_with("mod ") {
799 test_module_depth = super::brace_delta(line).max(1);
800 pending_test_cfg = false;
801 continue;
802 }
803 if !trimmed.starts_with("#[") {
804 pending_test_cfg = false;
805 }
806 }
807 if deprecated_attr_body {
808 pending_reference_oracle_deprecation |=
809 trimmed.contains("reference oracle only");
810 deprecated_attr_body = !trimmed.contains(")]");
811 continue;
812 }
813 if trimmed.starts_with("#[deprecated") {
814 pending_reference_oracle_deprecation =
815 trimmed.contains("reference oracle only");
816 deprecated_attr_body = !trimmed.contains(")]");
817 continue;
818 }
819 if pending_reference_oracle_deprecation && trimmed.starts_with("pub(crate) fn ")
820 {
821 reference_oracle_body_depth = super::brace_delta(line).max(1);
822 pending_reference_oracle_deprecation = false;
823 continue;
824 }
825 if !trimmed.starts_with("#[") {
826 pending_reference_oracle_deprecation = false;
827 }
828 if trimmed.starts_with("pub(crate) fn ")
829 || trimmed.starts_with("pub(crate) use ")
830 || trimmed.starts_with("use ")
831 || trimmed.starts_with("pub use ")
832 || trimmed.starts_with("///")
833 || trimmed.starts_with("//!")
834 {
835 continue;
836 }
837 if CPU_ORACLE_NAMES.iter().any(|name| trimmed.contains(name)) {
838 findings.push(format!("{}:{}: {line}", relative.display(), line_index + 1));
839 }
840 }
841 }
842 }
843 assert!(
844 findings.is_empty(),
845 "production Weir modules must dispatch GPU Programs and may only reach CPU oracles through weir::oracle parity APIs:\n{}",
846 findings.join("\n")
847 );
848 }
849}
850
851#[cfg(test)]
852fn brace_delta(line: &str) -> i32 {
853 line.bytes().fold(0i32, |depth, byte| match byte {
854 b'{' => depth + 1,
855 b'}' => depth - 1,
856 _ => depth,
857 })
858}
859
860pub use soundness::{
861 validate_dynamic_pipeline, validate_dynamic_primitive, validate_pipeline, validate_primitive,
862 DataflowEvidence, DynamicPrimitiveSoundness, DynamicSoundnessViolation, PrecisionContract,
863 PrimitiveSoundness, Soundness, SoundnessTagged, SoundnessViolation,
864};