1use crate::lcnf::{LcnfArg, LcnfExpr, LcnfFunDecl, LcnfLetValue, LcnfVarId};
6use std::collections::{HashMap, HashSet};
7
8use super::functions::*;
9use std::collections::VecDeque;
10
11#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct ParLivenessInfo {
14 pub live_in: Vec<std::collections::HashSet<u32>>,
15 pub live_out: Vec<std::collections::HashSet<u32>>,
16 pub defs: Vec<std::collections::HashSet<u32>>,
17 pub uses: Vec<std::collections::HashSet<u32>>,
18}
19impl ParLivenessInfo {
20 #[allow(dead_code)]
21 pub fn new(block_count: usize) -> Self {
22 ParLivenessInfo {
23 live_in: vec![std::collections::HashSet::new(); block_count],
24 live_out: vec![std::collections::HashSet::new(); block_count],
25 defs: vec![std::collections::HashSet::new(); block_count],
26 uses: vec![std::collections::HashSet::new(); block_count],
27 }
28 }
29 #[allow(dead_code)]
30 pub fn add_def(&mut self, block: usize, var: u32) {
31 if block < self.defs.len() {
32 self.defs[block].insert(var);
33 }
34 }
35 #[allow(dead_code)]
36 pub fn add_use(&mut self, block: usize, var: u32) {
37 if block < self.uses.len() {
38 self.uses[block].insert(var);
39 }
40 }
41 #[allow(dead_code)]
42 pub fn is_live_in(&self, block: usize, var: u32) -> bool {
43 self.live_in
44 .get(block)
45 .map(|s| s.contains(&var))
46 .unwrap_or(false)
47 }
48 #[allow(dead_code)]
49 pub fn is_live_out(&self, block: usize, var: u32) -> bool {
50 self.live_out
51 .get(block)
52 .map(|s| s.contains(&var))
53 .unwrap_or(false)
54 }
55}
56#[allow(dead_code)]
57#[derive(Debug, Clone, Default)]
58pub struct ParPassStats {
59 pub total_runs: u32,
60 pub successful_runs: u32,
61 pub total_changes: u64,
62 pub time_ms: u64,
63 pub iterations_used: u32,
64}
65impl ParPassStats {
66 #[allow(dead_code)]
67 pub fn new() -> Self {
68 Self::default()
69 }
70 #[allow(dead_code)]
71 pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
72 self.total_runs += 1;
73 self.successful_runs += 1;
74 self.total_changes += changes;
75 self.time_ms += time_ms;
76 self.iterations_used = iterations;
77 }
78 #[allow(dead_code)]
79 pub fn average_changes_per_run(&self) -> f64 {
80 if self.total_runs == 0 {
81 return 0.0;
82 }
83 self.total_changes as f64 / self.total_runs as f64
84 }
85 #[allow(dead_code)]
86 pub fn success_rate(&self) -> f64 {
87 if self.total_runs == 0 {
88 return 0.0;
89 }
90 self.successful_runs as f64 / self.total_runs as f64
91 }
92 #[allow(dead_code)]
93 pub fn format_summary(&self) -> String {
94 format!(
95 "Runs: {}/{}, Changes: {}, Time: {}ms",
96 self.successful_runs, self.total_runs, self.total_changes, self.time_ms
97 )
98 }
99}
100#[allow(dead_code)]
101#[derive(Debug, Clone)]
102pub struct ParCacheEntry {
103 pub key: String,
104 pub data: Vec<u8>,
105 pub timestamp: u64,
106 pub valid: bool,
107}
108#[derive(Debug, Clone)]
110pub struct ParallelRegion {
111 pub func_name: String,
113 pub start_block: usize,
115 pub end_block: usize,
117 pub kind: ParallelKind,
119 pub pattern: ParallelPattern,
121 pub estimated_speedup: f64,
123 pub shared_vars: Vec<String>,
125 pub private_vars: Vec<String>,
127 pub trip_count: Option<u64>,
129 pub dependence_info: DependenceInfo,
131}
132impl ParallelRegion {
133 pub fn new(func_name: impl Into<String>, kind: ParallelKind, pattern: ParallelPattern) -> Self {
135 ParallelRegion {
136 func_name: func_name.into(),
137 start_block: 0,
138 end_block: 0,
139 kind,
140 pattern,
141 estimated_speedup: 1.0,
142 shared_vars: Vec::new(),
143 private_vars: Vec::new(),
144 trip_count: None,
145 dependence_info: DependenceInfo::default(),
146 }
147 }
148 pub fn is_profitable(&self, threshold: f64) -> bool {
150 self.estimated_speedup > threshold && self.dependence_info.is_parallelizable()
151 }
152}
153#[derive(Debug, Clone)]
155pub struct OParDiagMsg {
156 pub severity: OParDiagSeverity,
157 pub pass: String,
158 pub message: String,
159}
160impl OParDiagMsg {
161 pub fn error(pass: impl Into<String>, msg: impl Into<String>) -> Self {
162 OParDiagMsg {
163 severity: OParDiagSeverity::Error,
164 pass: pass.into(),
165 message: msg.into(),
166 }
167 }
168 pub fn warning(pass: impl Into<String>, msg: impl Into<String>) -> Self {
169 OParDiagMsg {
170 severity: OParDiagSeverity::Warning,
171 pass: pass.into(),
172 message: msg.into(),
173 }
174 }
175 pub fn note(pass: impl Into<String>, msg: impl Into<String>) -> Self {
176 OParDiagMsg {
177 severity: OParDiagSeverity::Note,
178 pass: pass.into(),
179 message: msg.into(),
180 }
181 }
182}
183#[derive(Debug, Default)]
185pub struct OParDiagCollector {
186 pub(super) msgs: Vec<OParDiagMsg>,
187}
188impl OParDiagCollector {
189 pub fn new() -> Self {
190 OParDiagCollector::default()
191 }
192 pub fn emit(&mut self, d: OParDiagMsg) {
193 self.msgs.push(d);
194 }
195 pub fn has_errors(&self) -> bool {
196 self.msgs
197 .iter()
198 .any(|d| d.severity == OParDiagSeverity::Error)
199 }
200 pub fn errors(&self) -> Vec<&OParDiagMsg> {
201 self.msgs
202 .iter()
203 .filter(|d| d.severity == OParDiagSeverity::Error)
204 .collect()
205 }
206 pub fn warnings(&self) -> Vec<&OParDiagMsg> {
207 self.msgs
208 .iter()
209 .filter(|d| d.severity == OParDiagSeverity::Warning)
210 .collect()
211 }
212 pub fn len(&self) -> usize {
213 self.msgs.len()
214 }
215 pub fn is_empty(&self) -> bool {
216 self.msgs.is_empty()
217 }
218 pub fn clear(&mut self) {
219 self.msgs.clear();
220 }
221}
222#[allow(dead_code)]
223#[derive(Debug, Clone)]
224pub struct ParPassConfig {
225 pub phase: ParPassPhase,
226 pub enabled: bool,
227 pub max_iterations: u32,
228 pub debug_output: bool,
229 pub pass_name: String,
230}
231impl ParPassConfig {
232 #[allow(dead_code)]
233 pub fn new(name: impl Into<String>, phase: ParPassPhase) -> Self {
234 ParPassConfig {
235 phase,
236 enabled: true,
237 max_iterations: 10,
238 debug_output: false,
239 pass_name: name.into(),
240 }
241 }
242 #[allow(dead_code)]
243 pub fn disabled(mut self) -> Self {
244 self.enabled = false;
245 self
246 }
247 #[allow(dead_code)]
248 pub fn with_debug(mut self) -> Self {
249 self.debug_output = true;
250 self
251 }
252 #[allow(dead_code)]
253 pub fn max_iter(mut self, n: u32) -> Self {
254 self.max_iterations = n;
255 self
256 }
257}
258#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
260pub enum OParDiagSeverity {
261 Note,
262 Warning,
263 Error,
264}
265#[derive(Debug, Clone, PartialEq, Eq, Hash)]
267pub struct DepEdge {
268 pub from: String,
270 pub to: String,
272 pub distance: i64,
274}
275#[derive(Debug, Clone, Default)]
277pub struct ParallelReport {
278 pub regions_found: usize,
280 pub regions_transformed: usize,
282 pub estimated_total_speedup: f64,
284 pub race_conditions_detected: usize,
286}
287#[derive(Debug, Clone, Default)]
295pub struct DependenceInfo {
296 pub true_deps: Vec<DepEdge>,
298 pub anti_deps: Vec<DepEdge>,
300 pub output_deps: Vec<DepEdge>,
302 pub loop_carried_deps: Vec<DepEdge>,
304}
305impl DependenceInfo {
306 pub fn is_parallelizable(&self) -> bool {
308 self.loop_carried_deps.is_empty()
309 }
310 pub fn total_deps(&self) -> usize {
312 self.true_deps.len() + self.anti_deps.len() + self.output_deps.len()
313 }
314}
315#[derive(Debug, Clone)]
317pub struct ThreadSafetyInfo {
318 pub is_thread_safe: bool,
320 pub race_conditions: Vec<(String, String)>,
322 pub atomic_ops_needed: Vec<String>,
324}
325impl ThreadSafetyInfo {
326 pub fn safe() -> Self {
328 ThreadSafetyInfo {
329 is_thread_safe: true,
330 race_conditions: Vec::new(),
331 atomic_ops_needed: Vec::new(),
332 }
333 }
334 pub fn unsafe_race(write: impl Into<String>, read: impl Into<String>) -> Self {
336 ThreadSafetyInfo {
337 is_thread_safe: false,
338 race_conditions: vec![(write.into(), read.into())],
339 atomic_ops_needed: Vec::new(),
340 }
341 }
342}
343#[derive(Debug, Clone)]
345pub struct ParallelConfig {
346 pub min_speedup_threshold: f64,
348 pub max_functions: usize,
350 pub allow_speculative: bool,
352 pub hardware_threads: u32,
354}
355#[allow(dead_code)]
357#[derive(Debug, Clone, Default)]
358pub struct OParExtPassStats {
359 pub iterations: usize,
360 pub changed: bool,
361 pub nodes_visited: usize,
362 pub nodes_modified: usize,
363 pub time_ms: u64,
364 pub memory_bytes: usize,
365 pub errors: usize,
366}
367impl OParExtPassStats {
368 #[allow(dead_code)]
369 pub fn new() -> Self {
370 Self::default()
371 }
372 #[allow(dead_code)]
373 pub fn visit(&mut self) {
374 self.nodes_visited += 1;
375 }
376 #[allow(dead_code)]
377 pub fn modify(&mut self) {
378 self.nodes_modified += 1;
379 self.changed = true;
380 }
381 #[allow(dead_code)]
382 pub fn iterate(&mut self) {
383 self.iterations += 1;
384 }
385 #[allow(dead_code)]
386 pub fn error(&mut self) {
387 self.errors += 1;
388 }
389 #[allow(dead_code)]
390 pub fn efficiency(&self) -> f64 {
391 if self.nodes_visited == 0 {
392 0.0
393 } else {
394 self.nodes_modified as f64 / self.nodes_visited as f64
395 }
396 }
397 #[allow(dead_code)]
398 pub fn merge(&mut self, o: &OParExtPassStats) {
399 self.iterations += o.iterations;
400 self.changed |= o.changed;
401 self.nodes_visited += o.nodes_visited;
402 self.nodes_modified += o.nodes_modified;
403 self.time_ms += o.time_ms;
404 self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
405 self.errors += o.errors;
406 }
407}
408pub struct ParallelPass {
410 pub config: ParallelConfig,
412 pub regions: Vec<ParallelRegion>,
414 pub(super) safety_cache: HashMap<String, ThreadSafetyInfo>,
416}
417impl ParallelPass {
418 pub fn new(config: ParallelConfig) -> Self {
420 ParallelPass {
421 config,
422 regions: Vec::new(),
423 safety_cache: HashMap::new(),
424 }
425 }
426 pub fn default_pass() -> Self {
428 Self::new(ParallelConfig::default())
429 }
430 pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
432 self.regions.clear();
433 self.safety_cache.clear();
434 self.analyze_parallelism(decls);
435 self.transform_to_parallel(decls);
436 }
437 pub fn analyze_parallelism(&mut self, decls: &[LcnfFunDecl]) {
439 for decl in decls.iter().take(self.config.max_functions) {
440 if let Some(region) = self.analyse_decl(decl) {
441 self.regions.push(region);
442 }
443 }
444 }
445 pub fn transform_to_parallel(&mut self, decls: &mut [LcnfFunDecl]) {
452 let profitable: HashSet<String> = self
453 .regions
454 .iter()
455 .filter(|r| r.is_profitable(self.config.min_speedup_threshold))
456 .map(|r| r.func_name.clone())
457 .collect();
458 for decl in decls.iter_mut() {
459 if profitable.contains(&decl.name) {
460 let old_body = std::mem::replace(&mut decl.body, LcnfExpr::Unreachable);
461 decl.body = LcnfExpr::Let {
462 id: LcnfVarId(u64::MAX),
463 name: "__parallel_annotation__".to_string(),
464 ty: crate::lcnf::LcnfType::Unit,
465 value: LcnfLetValue::Ctor("parallel_region".to_string(), 0, vec![]),
466 body: Box::new(old_body),
467 };
468 }
469 }
470 }
471 pub fn report(&self) -> ParallelReport {
473 let races: usize = self
474 .safety_cache
475 .values()
476 .map(|s| s.race_conditions.len())
477 .sum();
478 let transformed = self
479 .regions
480 .iter()
481 .filter(|r| r.is_profitable(self.config.min_speedup_threshold))
482 .count();
483 let total_speedup = if self.regions.is_empty() {
484 1.0
485 } else {
486 self.regions
487 .iter()
488 .filter(|r| r.is_profitable(self.config.min_speedup_threshold))
489 .map(|r| r.estimated_speedup)
490 .fold(1.0_f64, f64::max)
491 };
492 ParallelReport {
493 regions_found: self.regions.len(),
494 regions_transformed: transformed,
495 estimated_total_speedup: total_speedup,
496 race_conditions_detected: races,
497 }
498 }
499 pub(super) fn analyse_decl(&mut self, decl: &LcnfFunDecl) -> Option<ParallelRegion> {
500 let pattern = PatternDetector::new(decl).detect()?;
501 let dep_info = DependenceAnalyser::new(decl).analyse();
502 if !dep_info.is_parallelizable() {
503 return None;
504 }
505 let kind = self.infer_kind(pattern, decl);
506 if kind == ParallelKind::SpeculativeParallel && !self.config.allow_speculative {
507 return None;
508 }
509 let trip_count = self.estimate_trip_count(decl);
510 let speedup = estimate_speedup_for_pattern(pattern, trip_count);
511 let detector = RaceDetector::new();
512 let safety = detector.analyse_decl(decl);
513 self.safety_cache.insert(decl.name.clone(), safety.clone());
514 if !safety.is_thread_safe && kind == ParallelKind::DataParallel {
515 return None;
516 }
517 let shared_vars = self.collect_shared_vars(decl);
518 let private_vars = self.collect_private_vars(decl);
519 Some(ParallelRegion {
520 func_name: decl.name.clone(),
521 start_block: 0,
522 end_block: decl.params.len().saturating_sub(1),
523 kind,
524 pattern,
525 estimated_speedup: speedup,
526 shared_vars,
527 private_vars,
528 trip_count,
529 dependence_info: dep_info,
530 })
531 }
532 pub(super) fn infer_kind(&self, pattern: ParallelPattern, _decl: &LcnfFunDecl) -> ParallelKind {
533 match pattern {
534 ParallelPattern::Map
535 | ParallelPattern::Filter
536 | ParallelPattern::Gather
537 | ParallelPattern::Scatter
538 | ParallelPattern::Stencil
539 | ParallelPattern::ParallelFor => ParallelKind::DataParallel,
540 ParallelPattern::Reduce => ParallelKind::DataParallel,
541 ParallelPattern::Scan => ParallelKind::PipelineParallel,
542 }
543 }
544 pub(super) fn estimate_trip_count(&self, decl: &LcnfFunDecl) -> Option<u64> {
545 for param in &decl.params {
546 if param.name == "n" || param.name == "len" || param.name == "size" {
547 return Some(256);
548 }
549 }
550 None
551 }
552 pub(super) fn collect_shared_vars(&self, decl: &LcnfFunDecl) -> Vec<String> {
553 decl.params
554 .iter()
555 .filter(|p| p.name.starts_with("arr") || p.name.starts_with("buf"))
556 .map(|p| p.name.clone())
557 .collect()
558 }
559 pub(super) fn collect_private_vars(&self, decl: &LcnfFunDecl) -> Vec<String> {
560 let mut privates = Vec::new();
561 Self::collect_let_names(&decl.body, &mut privates);
562 privates
563 }
564 pub(super) fn collect_let_names(expr: &LcnfExpr, out: &mut Vec<String>) {
565 match expr {
566 LcnfExpr::Let { name, body, .. } => {
567 out.push(name.clone());
568 Self::collect_let_names(body, out);
569 }
570 LcnfExpr::Case { alts, default, .. } => {
571 for alt in alts {
572 Self::collect_let_names(&alt.body, out);
573 }
574 if let Some(d) = default {
575 Self::collect_let_names(d, out);
576 }
577 }
578 _ => {}
579 }
580 }
581}
582#[allow(dead_code)]
584#[derive(Debug, Clone, PartialEq, Eq, Hash)]
585pub enum OParExtPassPhase {
586 Early,
587 Middle,
588 Late,
589 Finalize,
590}
591impl OParExtPassPhase {
592 #[allow(dead_code)]
593 pub fn is_early(&self) -> bool {
594 matches!(self, Self::Early)
595 }
596 #[allow(dead_code)]
597 pub fn is_middle(&self) -> bool {
598 matches!(self, Self::Middle)
599 }
600 #[allow(dead_code)]
601 pub fn is_late(&self) -> bool {
602 matches!(self, Self::Late)
603 }
604 #[allow(dead_code)]
605 pub fn is_finalize(&self) -> bool {
606 matches!(self, Self::Finalize)
607 }
608 #[allow(dead_code)]
609 pub fn order(&self) -> u32 {
610 match self {
611 Self::Early => 0,
612 Self::Middle => 1,
613 Self::Late => 2,
614 Self::Finalize => 3,
615 }
616 }
617 #[allow(dead_code)]
618 pub fn from_order(n: u32) -> Option<Self> {
619 match n {
620 0 => Some(Self::Early),
621 1 => Some(Self::Middle),
622 2 => Some(Self::Late),
623 3 => Some(Self::Finalize),
624 _ => None,
625 }
626 }
627}
628#[allow(dead_code)]
630#[derive(Debug, Default)]
631pub struct OParExtPassRegistry {
632 pub(super) configs: Vec<OParExtPassConfig>,
633 pub(super) stats: Vec<OParExtPassStats>,
634}
635impl OParExtPassRegistry {
636 #[allow(dead_code)]
637 pub fn new() -> Self {
638 Self::default()
639 }
640 #[allow(dead_code)]
641 pub fn register(&mut self, c: OParExtPassConfig) {
642 self.stats.push(OParExtPassStats::new());
643 self.configs.push(c);
644 }
645 #[allow(dead_code)]
646 pub fn len(&self) -> usize {
647 self.configs.len()
648 }
649 #[allow(dead_code)]
650 pub fn is_empty(&self) -> bool {
651 self.configs.is_empty()
652 }
653 #[allow(dead_code)]
654 pub fn get(&self, i: usize) -> Option<&OParExtPassConfig> {
655 self.configs.get(i)
656 }
657 #[allow(dead_code)]
658 pub fn get_stats(&self, i: usize) -> Option<&OParExtPassStats> {
659 self.stats.get(i)
660 }
661 #[allow(dead_code)]
662 pub fn enabled_passes(&self) -> Vec<&OParExtPassConfig> {
663 self.configs.iter().filter(|c| c.enabled).collect()
664 }
665 #[allow(dead_code)]
666 pub fn passes_in_phase(&self, ph: &OParExtPassPhase) -> Vec<&OParExtPassConfig> {
667 self.configs
668 .iter()
669 .filter(|c| c.enabled && &c.phase == ph)
670 .collect()
671 }
672 #[allow(dead_code)]
673 pub fn total_nodes_visited(&self) -> usize {
674 self.stats.iter().map(|s| s.nodes_visited).sum()
675 }
676 #[allow(dead_code)]
677 pub fn any_changed(&self) -> bool {
678 self.stats.iter().any(|s| s.changed)
679 }
680}
681#[allow(dead_code)]
683#[derive(Debug, Clone)]
684pub struct OParExtWorklist {
685 pub(super) items: std::collections::VecDeque<usize>,
686 pub(super) present: Vec<bool>,
687}
688impl OParExtWorklist {
689 #[allow(dead_code)]
690 pub fn new(capacity: usize) -> Self {
691 Self {
692 items: std::collections::VecDeque::new(),
693 present: vec![false; capacity],
694 }
695 }
696 #[allow(dead_code)]
697 pub fn push(&mut self, id: usize) {
698 if id < self.present.len() && !self.present[id] {
699 self.present[id] = true;
700 self.items.push_back(id);
701 }
702 }
703 #[allow(dead_code)]
704 pub fn push_front(&mut self, id: usize) {
705 if id < self.present.len() && !self.present[id] {
706 self.present[id] = true;
707 self.items.push_front(id);
708 }
709 }
710 #[allow(dead_code)]
711 pub fn pop(&mut self) -> Option<usize> {
712 let id = self.items.pop_front()?;
713 if id < self.present.len() {
714 self.present[id] = false;
715 }
716 Some(id)
717 }
718 #[allow(dead_code)]
719 pub fn is_empty(&self) -> bool {
720 self.items.is_empty()
721 }
722 #[allow(dead_code)]
723 pub fn len(&self) -> usize {
724 self.items.len()
725 }
726 #[allow(dead_code)]
727 pub fn contains(&self, id: usize) -> bool {
728 id < self.present.len() && self.present[id]
729 }
730 #[allow(dead_code)]
731 pub fn drain_all(&mut self) -> Vec<usize> {
732 let v: Vec<usize> = self.items.drain(..).collect();
733 for &id in &v {
734 if id < self.present.len() {
735 self.present[id] = false;
736 }
737 }
738 v
739 }
740}
741#[allow(dead_code)]
743#[derive(Debug, Clone)]
744pub struct OParExtDomTree {
745 pub(super) idom: Vec<Option<usize>>,
746 pub(super) children: Vec<Vec<usize>>,
747 pub(super) depth: Vec<usize>,
748}
749impl OParExtDomTree {
750 #[allow(dead_code)]
751 pub fn new(n: usize) -> Self {
752 Self {
753 idom: vec![None; n],
754 children: vec![Vec::new(); n],
755 depth: vec![0; n],
756 }
757 }
758 #[allow(dead_code)]
759 pub fn set_idom(&mut self, node: usize, dom: usize) {
760 if node < self.idom.len() {
761 self.idom[node] = Some(dom);
762 if dom < self.children.len() {
763 self.children[dom].push(node);
764 }
765 self.depth[node] = if dom < self.depth.len() {
766 self.depth[dom] + 1
767 } else {
768 1
769 };
770 }
771 }
772 #[allow(dead_code)]
773 pub fn dominates(&self, a: usize, mut b: usize) -> bool {
774 if a == b {
775 return true;
776 }
777 let n = self.idom.len();
778 for _ in 0..n {
779 match self.idom.get(b).copied().flatten() {
780 None => return false,
781 Some(p) if p == a => return true,
782 Some(p) if p == b => return false,
783 Some(p) => b = p,
784 }
785 }
786 false
787 }
788 #[allow(dead_code)]
789 pub fn children_of(&self, n: usize) -> &[usize] {
790 self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
791 }
792 #[allow(dead_code)]
793 pub fn depth_of(&self, n: usize) -> usize {
794 self.depth.get(n).copied().unwrap_or(0)
795 }
796 #[allow(dead_code)]
797 pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
798 let n = self.idom.len();
799 for _ in 0..(2 * n) {
800 if a == b {
801 return a;
802 }
803 if self.depth_of(a) > self.depth_of(b) {
804 a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
805 } else {
806 b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
807 }
808 }
809 0
810 }
811}
812#[allow(dead_code)]
813#[derive(Debug, Clone)]
814pub struct ParDepGraph {
815 pub(super) nodes: Vec<u32>,
816 pub(super) edges: Vec<(u32, u32)>,
817}
818impl ParDepGraph {
819 #[allow(dead_code)]
820 pub fn new() -> Self {
821 ParDepGraph {
822 nodes: Vec::new(),
823 edges: Vec::new(),
824 }
825 }
826 #[allow(dead_code)]
827 pub fn add_node(&mut self, id: u32) {
828 if !self.nodes.contains(&id) {
829 self.nodes.push(id);
830 }
831 }
832 #[allow(dead_code)]
833 pub fn add_dep(&mut self, dep: u32, dependent: u32) {
834 self.add_node(dep);
835 self.add_node(dependent);
836 self.edges.push((dep, dependent));
837 }
838 #[allow(dead_code)]
839 pub fn dependents_of(&self, node: u32) -> Vec<u32> {
840 self.edges
841 .iter()
842 .filter(|(d, _)| *d == node)
843 .map(|(_, dep)| *dep)
844 .collect()
845 }
846 #[allow(dead_code)]
847 pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
848 self.edges
849 .iter()
850 .filter(|(_, dep)| *dep == node)
851 .map(|(d, _)| *d)
852 .collect()
853 }
854 #[allow(dead_code)]
855 pub fn topological_sort(&self) -> Vec<u32> {
856 let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
857 for &n in &self.nodes {
858 in_degree.insert(n, 0);
859 }
860 for (_, dep) in &self.edges {
861 *in_degree.entry(*dep).or_insert(0) += 1;
862 }
863 let mut queue: std::collections::VecDeque<u32> = self
864 .nodes
865 .iter()
866 .filter(|&&n| in_degree[&n] == 0)
867 .copied()
868 .collect();
869 let mut result = Vec::new();
870 while let Some(node) = queue.pop_front() {
871 result.push(node);
872 for dep in self.dependents_of(node) {
873 let cnt = in_degree.entry(dep).or_insert(0);
874 *cnt = cnt.saturating_sub(1);
875 if *cnt == 0 {
876 queue.push_back(dep);
877 }
878 }
879 }
880 result
881 }
882 #[allow(dead_code)]
883 pub fn has_cycle(&self) -> bool {
884 self.topological_sort().len() < self.nodes.len()
885 }
886}
887#[derive(Debug, Default)]
889pub struct OParSourceBuffer {
890 pub(super) buf: String,
891 pub(super) indent_level: usize,
892 pub(super) indent_str: String,
893}
894impl OParSourceBuffer {
895 pub fn new() -> Self {
896 OParSourceBuffer {
897 buf: String::new(),
898 indent_level: 0,
899 indent_str: " ".to_string(),
900 }
901 }
902 pub fn with_indent(mut self, indent: impl Into<String>) -> Self {
903 self.indent_str = indent.into();
904 self
905 }
906 pub fn push_line(&mut self, line: &str) {
907 for _ in 0..self.indent_level {
908 self.buf.push_str(&self.indent_str);
909 }
910 self.buf.push_str(line);
911 self.buf.push('\n');
912 }
913 pub fn push_raw(&mut self, s: &str) {
914 self.buf.push_str(s);
915 }
916 pub fn indent(&mut self) {
917 self.indent_level += 1;
918 }
919 pub fn dedent(&mut self) {
920 self.indent_level = self.indent_level.saturating_sub(1);
921 }
922 pub fn as_str(&self) -> &str {
923 &self.buf
924 }
925 pub fn len(&self) -> usize {
926 self.buf.len()
927 }
928 pub fn is_empty(&self) -> bool {
929 self.buf.is_empty()
930 }
931 pub fn line_count(&self) -> usize {
932 self.buf.lines().count()
933 }
934 pub fn into_string(self) -> String {
935 self.buf
936 }
937 pub fn reset(&mut self) {
938 self.buf.clear();
939 self.indent_level = 0;
940 }
941}
942#[derive(Debug)]
944pub struct OParEventLog {
945 pub(super) entries: std::collections::VecDeque<String>,
946 pub(super) capacity: usize,
947}
948impl OParEventLog {
949 pub fn new(capacity: usize) -> Self {
950 OParEventLog {
951 entries: std::collections::VecDeque::with_capacity(capacity),
952 capacity,
953 }
954 }
955 pub fn push(&mut self, event: impl Into<String>) {
956 if self.entries.len() >= self.capacity {
957 self.entries.pop_front();
958 }
959 self.entries.push_back(event.into());
960 }
961 pub fn iter(&self) -> impl Iterator<Item = &String> {
962 self.entries.iter()
963 }
964 pub fn len(&self) -> usize {
965 self.entries.len()
966 }
967 pub fn is_empty(&self) -> bool {
968 self.entries.is_empty()
969 }
970 pub fn capacity(&self) -> usize {
971 self.capacity
972 }
973 pub fn clear(&mut self) {
974 self.entries.clear();
975 }
976}
977#[allow(dead_code)]
979#[derive(Debug)]
980pub struct OParExtCache {
981 pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
982 pub(super) cap: usize,
983 pub(super) total_hits: u64,
984 pub(super) total_misses: u64,
985}
986impl OParExtCache {
987 #[allow(dead_code)]
988 pub fn new(cap: usize) -> Self {
989 Self {
990 entries: Vec::new(),
991 cap,
992 total_hits: 0,
993 total_misses: 0,
994 }
995 }
996 #[allow(dead_code)]
997 pub fn get(&mut self, key: u64) -> Option<&[u8]> {
998 for e in self.entries.iter_mut() {
999 if e.0 == key && e.2 {
1000 e.3 += 1;
1001 self.total_hits += 1;
1002 return Some(&e.1);
1003 }
1004 }
1005 self.total_misses += 1;
1006 None
1007 }
1008 #[allow(dead_code)]
1009 pub fn put(&mut self, key: u64, data: Vec<u8>) {
1010 if self.entries.len() >= self.cap {
1011 self.entries.retain(|e| e.2);
1012 if self.entries.len() >= self.cap {
1013 self.entries.remove(0);
1014 }
1015 }
1016 self.entries.push((key, data, true, 0));
1017 }
1018 #[allow(dead_code)]
1019 pub fn invalidate(&mut self) {
1020 for e in self.entries.iter_mut() {
1021 e.2 = false;
1022 }
1023 }
1024 #[allow(dead_code)]
1025 pub fn hit_rate(&self) -> f64 {
1026 let t = self.total_hits + self.total_misses;
1027 if t == 0 {
1028 0.0
1029 } else {
1030 self.total_hits as f64 / t as f64
1031 }
1032 }
1033 #[allow(dead_code)]
1034 pub fn live_count(&self) -> usize {
1035 self.entries.iter().filter(|e| e.2).count()
1036 }
1037}
1038pub struct DependenceAnalyser<'a> {
1040 pub(super) decl: &'a LcnfFunDecl,
1041}
1042impl<'a> DependenceAnalyser<'a> {
1043 pub(super) fn new(decl: &'a LcnfFunDecl) -> Self {
1044 DependenceAnalyser { decl }
1045 }
1046 pub(super) fn analyse(&self) -> DependenceInfo {
1047 let accesses = self.collect_accesses(&self.decl.body);
1048 let mut info = DependenceInfo::default();
1049 for i in 0..accesses.len() {
1050 for j in (i + 1)..accesses.len() {
1051 let a = &accesses[i];
1052 let b = &accesses[j];
1053 if a.independent_from(b) {
1054 continue;
1055 }
1056 let edge = DepEdge {
1057 from: format!("{}{}", a.base, a.offset),
1058 to: format!("{}{}", b.base, b.offset),
1059 distance: (b.offset - a.offset).abs(),
1060 };
1061 let is_loop_carried = edge.distance > 0;
1062 if a.is_write && !b.is_write {
1063 info.true_deps.push(edge.clone());
1064 } else if !a.is_write && b.is_write {
1065 info.anti_deps.push(edge.clone());
1066 } else if a.is_write && b.is_write {
1067 info.output_deps.push(edge.clone());
1068 }
1069 if is_loop_carried {
1070 info.loop_carried_deps.push(edge);
1071 }
1072 }
1073 }
1074 info
1075 }
1076 pub(super) fn collect_accesses(&self, expr: &LcnfExpr) -> Vec<AffineAccess> {
1077 let mut out = Vec::new();
1078 self.collect_accesses_inner(expr, &mut out);
1079 out
1080 }
1081 pub(super) fn collect_accesses_inner(&self, expr: &LcnfExpr, out: &mut Vec<AffineAccess>) {
1082 match expr {
1083 LcnfExpr::Let {
1084 value, body, name, ..
1085 } => {
1086 match value {
1087 LcnfLetValue::App(LcnfArg::Var(fid), args) => {
1088 let coeff = if args.len() > 1 { 1 } else { 0 };
1089 out.push(AffineAccess {
1090 base: name.clone(),
1091 coeff,
1092 offset: fid.0 as i64,
1093 is_write: false,
1094 });
1095 }
1096 LcnfLetValue::Ctor(ctor_name, tag, _) => {
1097 out.push(AffineAccess {
1098 base: ctor_name.clone(),
1099 coeff: 1,
1100 offset: *tag as i64,
1101 is_write: true,
1102 });
1103 }
1104 _ => {}
1105 }
1106 self.collect_accesses_inner(body, out);
1107 }
1108 LcnfExpr::Case { alts, default, .. } => {
1109 for alt in alts {
1110 self.collect_accesses_inner(&alt.body, out);
1111 }
1112 if let Some(d) = default {
1113 self.collect_accesses_inner(d, out);
1114 }
1115 }
1116 _ => {}
1117 }
1118 }
1119}
1120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1122pub struct OParIncrKey {
1123 pub content_hash: u64,
1124 pub config_hash: u64,
1125}
1126impl OParIncrKey {
1127 pub fn new(content: u64, config: u64) -> Self {
1128 OParIncrKey {
1129 content_hash: content,
1130 config_hash: config,
1131 }
1132 }
1133 pub fn combined_hash(&self) -> u64 {
1134 self.content_hash.wrapping_mul(0x9e3779b97f4a7c15) ^ self.config_hash
1135 }
1136 pub fn matches(&self, other: &OParIncrKey) -> bool {
1137 self.content_hash == other.content_hash && self.config_hash == other.config_hash
1138 }
1139}
1140#[derive(Debug, Clone, Default)]
1142pub struct OParFeatures {
1143 pub(super) flags: std::collections::HashSet<String>,
1144}
1145impl OParFeatures {
1146 pub fn new() -> Self {
1147 OParFeatures::default()
1148 }
1149 pub fn enable(&mut self, flag: impl Into<String>) {
1150 self.flags.insert(flag.into());
1151 }
1152 pub fn disable(&mut self, flag: &str) {
1153 self.flags.remove(flag);
1154 }
1155 pub fn is_enabled(&self, flag: &str) -> bool {
1156 self.flags.contains(flag)
1157 }
1158 pub fn len(&self) -> usize {
1159 self.flags.len()
1160 }
1161 pub fn is_empty(&self) -> bool {
1162 self.flags.is_empty()
1163 }
1164 pub fn union(&self, other: &OParFeatures) -> OParFeatures {
1165 OParFeatures {
1166 flags: self.flags.union(&other.flags).cloned().collect(),
1167 }
1168 }
1169 pub fn intersection(&self, other: &OParFeatures) -> OParFeatures {
1170 OParFeatures {
1171 flags: self.flags.intersection(&other.flags).cloned().collect(),
1172 }
1173 }
1174}
1175#[derive(Debug, Default)]
1177pub struct OParIdGen {
1178 pub(super) next: u32,
1179}
1180impl OParIdGen {
1181 pub fn new() -> Self {
1182 OParIdGen::default()
1183 }
1184 pub fn next_id(&mut self) -> u32 {
1185 let id = self.next;
1186 self.next += 1;
1187 id
1188 }
1189 pub fn peek_next(&self) -> u32 {
1190 self.next
1191 }
1192 pub fn reset(&mut self) {
1193 self.next = 0;
1194 }
1195 pub fn skip(&mut self, n: u32) {
1196 self.next += n;
1197 }
1198}
1199#[allow(dead_code)]
1200pub struct ParPassRegistry {
1201 pub(super) configs: Vec<ParPassConfig>,
1202 pub(super) stats: std::collections::HashMap<String, ParPassStats>,
1203}
1204impl ParPassRegistry {
1205 #[allow(dead_code)]
1206 pub fn new() -> Self {
1207 ParPassRegistry {
1208 configs: Vec::new(),
1209 stats: std::collections::HashMap::new(),
1210 }
1211 }
1212 #[allow(dead_code)]
1213 pub fn register(&mut self, config: ParPassConfig) {
1214 self.stats
1215 .insert(config.pass_name.clone(), ParPassStats::new());
1216 self.configs.push(config);
1217 }
1218 #[allow(dead_code)]
1219 pub fn enabled_passes(&self) -> Vec<&ParPassConfig> {
1220 self.configs.iter().filter(|c| c.enabled).collect()
1221 }
1222 #[allow(dead_code)]
1223 pub fn get_stats(&self, name: &str) -> Option<&ParPassStats> {
1224 self.stats.get(name)
1225 }
1226 #[allow(dead_code)]
1227 pub fn total_passes(&self) -> usize {
1228 self.configs.len()
1229 }
1230 #[allow(dead_code)]
1231 pub fn enabled_count(&self) -> usize {
1232 self.enabled_passes().len()
1233 }
1234 #[allow(dead_code)]
1235 pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
1236 if let Some(stats) = self.stats.get_mut(name) {
1237 stats.record_run(changes, time_ms, iter);
1238 }
1239 }
1240}
1241#[allow(dead_code)]
1242#[derive(Debug, Clone)]
1243pub struct ParDominatorTree {
1244 pub idom: Vec<Option<u32>>,
1245 pub dom_children: Vec<Vec<u32>>,
1246 pub dom_depth: Vec<u32>,
1247}
1248impl ParDominatorTree {
1249 #[allow(dead_code)]
1250 pub fn new(size: usize) -> Self {
1251 ParDominatorTree {
1252 idom: vec![None; size],
1253 dom_children: vec![Vec::new(); size],
1254 dom_depth: vec![0; size],
1255 }
1256 }
1257 #[allow(dead_code)]
1258 pub fn set_idom(&mut self, node: usize, idom: u32) {
1259 self.idom[node] = Some(idom);
1260 }
1261 #[allow(dead_code)]
1262 pub fn dominates(&self, a: usize, b: usize) -> bool {
1263 if a == b {
1264 return true;
1265 }
1266 let mut cur = b;
1267 loop {
1268 match self.idom[cur] {
1269 Some(parent) if parent as usize == a => return true,
1270 Some(parent) if parent as usize == cur => return false,
1271 Some(parent) => cur = parent as usize,
1272 None => return false,
1273 }
1274 }
1275 }
1276 #[allow(dead_code)]
1277 pub fn depth(&self, node: usize) -> u32 {
1278 self.dom_depth.get(node).copied().unwrap_or(0)
1279 }
1280}
1281#[allow(dead_code)]
1282#[derive(Debug, Clone)]
1283pub struct ParWorklist {
1284 pub(super) items: std::collections::VecDeque<u32>,
1285 pub(super) in_worklist: std::collections::HashSet<u32>,
1286}
1287impl ParWorklist {
1288 #[allow(dead_code)]
1289 pub fn new() -> Self {
1290 ParWorklist {
1291 items: std::collections::VecDeque::new(),
1292 in_worklist: std::collections::HashSet::new(),
1293 }
1294 }
1295 #[allow(dead_code)]
1296 pub fn push(&mut self, item: u32) -> bool {
1297 if self.in_worklist.insert(item) {
1298 self.items.push_back(item);
1299 true
1300 } else {
1301 false
1302 }
1303 }
1304 #[allow(dead_code)]
1305 pub fn pop(&mut self) -> Option<u32> {
1306 let item = self.items.pop_front()?;
1307 self.in_worklist.remove(&item);
1308 Some(item)
1309 }
1310 #[allow(dead_code)]
1311 pub fn is_empty(&self) -> bool {
1312 self.items.is_empty()
1313 }
1314 #[allow(dead_code)]
1315 pub fn len(&self) -> usize {
1316 self.items.len()
1317 }
1318 #[allow(dead_code)]
1319 pub fn contains(&self, item: u32) -> bool {
1320 self.in_worklist.contains(&item)
1321 }
1322}
1323#[allow(dead_code)]
1325#[derive(Debug, Clone)]
1326pub struct OParExtPassConfig {
1327 pub name: String,
1328 pub phase: OParExtPassPhase,
1329 pub enabled: bool,
1330 pub max_iterations: usize,
1331 pub debug: u32,
1332 pub timeout_ms: Option<u64>,
1333}
1334impl OParExtPassConfig {
1335 #[allow(dead_code)]
1336 pub fn new(name: impl Into<String>) -> Self {
1337 Self {
1338 name: name.into(),
1339 phase: OParExtPassPhase::Middle,
1340 enabled: true,
1341 max_iterations: 100,
1342 debug: 0,
1343 timeout_ms: None,
1344 }
1345 }
1346 #[allow(dead_code)]
1347 pub fn with_phase(mut self, phase: OParExtPassPhase) -> Self {
1348 self.phase = phase;
1349 self
1350 }
1351 #[allow(dead_code)]
1352 pub fn with_max_iter(mut self, n: usize) -> Self {
1353 self.max_iterations = n;
1354 self
1355 }
1356 #[allow(dead_code)]
1357 pub fn with_debug(mut self, d: u32) -> Self {
1358 self.debug = d;
1359 self
1360 }
1361 #[allow(dead_code)]
1362 pub fn disabled(mut self) -> Self {
1363 self.enabled = false;
1364 self
1365 }
1366 #[allow(dead_code)]
1367 pub fn with_timeout(mut self, ms: u64) -> Self {
1368 self.timeout_ms = Some(ms);
1369 self
1370 }
1371 #[allow(dead_code)]
1372 pub fn is_debug_enabled(&self) -> bool {
1373 self.debug > 0
1374 }
1375}
1376#[allow(dead_code)]
1377#[derive(Debug, Clone, PartialEq)]
1378pub enum ParPassPhase {
1379 Analysis,
1380 Transformation,
1381 Verification,
1382 Cleanup,
1383}
1384impl ParPassPhase {
1385 #[allow(dead_code)]
1386 pub fn name(&self) -> &str {
1387 match self {
1388 ParPassPhase::Analysis => "analysis",
1389 ParPassPhase::Transformation => "transformation",
1390 ParPassPhase::Verification => "verification",
1391 ParPassPhase::Cleanup => "cleanup",
1392 }
1393 }
1394 #[allow(dead_code)]
1395 pub fn is_modifying(&self) -> bool {
1396 matches!(self, ParPassPhase::Transformation | ParPassPhase::Cleanup)
1397 }
1398}
1399#[derive(Debug, Clone, PartialEq, Eq)]
1401pub struct AffineAccess {
1402 pub base: String,
1404 pub coeff: i64,
1406 pub offset: i64,
1408 pub is_write: bool,
1410}
1411impl AffineAccess {
1412 pub fn independent_from(&self, other: &AffineAccess) -> bool {
1419 if self.base != other.base {
1420 return true;
1421 }
1422 if !self.is_write && !other.is_write {
1423 return true;
1424 }
1425 let g = gcd(self.coeff.unsigned_abs(), other.coeff.unsigned_abs()) as i64;
1426 if g == 0 {
1427 return self.offset != other.offset;
1428 }
1429 (self.offset - other.offset) % g != 0
1430 }
1431}
1432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1434pub enum ParallelPattern {
1435 Map,
1437 Filter,
1439 Reduce,
1441 Scan,
1443 Stencil,
1445 ParallelFor,
1447 Scatter,
1449 Gather,
1451}
1452#[allow(dead_code)]
1454#[derive(Debug, Clone, Default)]
1455pub struct OParExtConstFolder {
1456 pub(super) folds: usize,
1457 pub(super) failures: usize,
1458 pub(super) enabled: bool,
1459}
1460impl OParExtConstFolder {
1461 #[allow(dead_code)]
1462 pub fn new() -> Self {
1463 Self {
1464 folds: 0,
1465 failures: 0,
1466 enabled: true,
1467 }
1468 }
1469 #[allow(dead_code)]
1470 pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1471 self.folds += 1;
1472 a.checked_add(b)
1473 }
1474 #[allow(dead_code)]
1475 pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1476 self.folds += 1;
1477 a.checked_sub(b)
1478 }
1479 #[allow(dead_code)]
1480 pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1481 self.folds += 1;
1482 a.checked_mul(b)
1483 }
1484 #[allow(dead_code)]
1485 pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1486 if b == 0 {
1487 self.failures += 1;
1488 None
1489 } else {
1490 self.folds += 1;
1491 a.checked_div(b)
1492 }
1493 }
1494 #[allow(dead_code)]
1495 pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1496 if b == 0 {
1497 self.failures += 1;
1498 None
1499 } else {
1500 self.folds += 1;
1501 a.checked_rem(b)
1502 }
1503 }
1504 #[allow(dead_code)]
1505 pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
1506 self.folds += 1;
1507 a.checked_neg()
1508 }
1509 #[allow(dead_code)]
1510 pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1511 if s >= 64 {
1512 self.failures += 1;
1513 None
1514 } else {
1515 self.folds += 1;
1516 a.checked_shl(s)
1517 }
1518 }
1519 #[allow(dead_code)]
1520 pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1521 if s >= 64 {
1522 self.failures += 1;
1523 None
1524 } else {
1525 self.folds += 1;
1526 a.checked_shr(s)
1527 }
1528 }
1529 #[allow(dead_code)]
1530 pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
1531 self.folds += 1;
1532 a & b
1533 }
1534 #[allow(dead_code)]
1535 pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
1536 self.folds += 1;
1537 a | b
1538 }
1539 #[allow(dead_code)]
1540 pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
1541 self.folds += 1;
1542 a ^ b
1543 }
1544 #[allow(dead_code)]
1545 pub fn not_i64(&mut self, a: i64) -> i64 {
1546 self.folds += 1;
1547 !a
1548 }
1549 #[allow(dead_code)]
1550 pub fn fold_count(&self) -> usize {
1551 self.folds
1552 }
1553 #[allow(dead_code)]
1554 pub fn failure_count(&self) -> usize {
1555 self.failures
1556 }
1557 #[allow(dead_code)]
1558 pub fn enable(&mut self) {
1559 self.enabled = true;
1560 }
1561 #[allow(dead_code)]
1562 pub fn disable(&mut self) {
1563 self.enabled = false;
1564 }
1565 #[allow(dead_code)]
1566 pub fn is_enabled(&self) -> bool {
1567 self.enabled
1568 }
1569}
1570struct RaceDetector {
1575 pub(super) happens_before: HashSet<(LcnfVarId, LcnfVarId)>,
1577}
1578impl RaceDetector {
1579 pub(super) fn new() -> Self {
1580 RaceDetector {
1581 happens_before: HashSet::new(),
1582 }
1583 }
1584 pub(super) fn add_ordering(&mut self, before: LcnfVarId, after: LcnfVarId) {
1585 self.happens_before.insert((before, after));
1586 }
1587 pub(super) fn may_race(
1588 &self,
1589 a: LcnfVarId,
1590 b: LcnfVarId,
1591 a_is_write: bool,
1592 b_is_write: bool,
1593 ) -> bool {
1594 if !a_is_write && !b_is_write {
1595 return false;
1596 }
1597 !self.happens_before.contains(&(a, b)) && !self.happens_before.contains(&(b, a))
1598 }
1599 pub(super) fn analyse_decl(&self, decl: &LcnfFunDecl) -> ThreadSafetyInfo {
1600 let mut races = Vec::new();
1601 let mut atomics = Vec::new();
1602 let accesses = Self::collect_var_accesses(&decl.body);
1603 let writes: Vec<_> = accesses
1604 .iter()
1605 .filter(|(_, is_write, _)| *is_write)
1606 .collect();
1607 let reads: Vec<_> = accesses
1608 .iter()
1609 .filter(|(_, is_write, _)| !*is_write)
1610 .collect();
1611 for (wid, _, wname) in &writes {
1612 for (rid, _, rname) in &reads {
1613 if wid != rid && self.may_race(*wid, *rid, true, false) {
1614 races.push((wname.clone(), rname.clone()));
1615 }
1616 }
1617 for (wid2, _, wname2) in &writes {
1618 if wid != wid2 && self.may_race(*wid, *wid2, true, true) {
1619 atomics.push(wname.clone());
1620 atomics.push(wname2.clone());
1621 }
1622 }
1623 }
1624 atomics.sort();
1625 atomics.dedup();
1626 ThreadSafetyInfo {
1627 is_thread_safe: races.is_empty() && atomics.is_empty(),
1628 race_conditions: races,
1629 atomic_ops_needed: atomics,
1630 }
1631 }
1632 pub(super) fn collect_var_accesses(expr: &LcnfExpr) -> Vec<(LcnfVarId, bool, String)> {
1633 let mut out = Vec::new();
1634 Self::collect_inner(expr, &mut out);
1635 out
1636 }
1637 pub(super) fn collect_inner(expr: &LcnfExpr, out: &mut Vec<(LcnfVarId, bool, String)>) {
1638 match expr {
1639 LcnfExpr::Let {
1640 id,
1641 name,
1642 value,
1643 body,
1644 ..
1645 } => {
1646 let is_write = matches!(
1647 value, LcnfLetValue::Ctor(n, _, _) if n.contains("write") || n
1648 .contains("store")
1649 );
1650 out.push((*id, is_write, name.clone()));
1651 Self::collect_inner(body, out);
1652 }
1653 LcnfExpr::Case { alts, default, .. } => {
1654 for alt in alts {
1655 Self::collect_inner(&alt.body, out);
1656 }
1657 if let Some(d) = default {
1658 Self::collect_inner(d, out);
1659 }
1660 }
1661 _ => {}
1662 }
1663 }
1664}
1665#[derive(Debug, Clone, Default)]
1667pub struct OParConfig {
1668 pub(super) entries: std::collections::HashMap<String, String>,
1669}
1670impl OParConfig {
1671 pub fn new() -> Self {
1672 OParConfig::default()
1673 }
1674 pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
1675 self.entries.insert(key.into(), value.into());
1676 }
1677 pub fn get(&self, key: &str) -> Option<&str> {
1678 self.entries.get(key).map(|s| s.as_str())
1679 }
1680 pub fn get_bool(&self, key: &str) -> bool {
1681 matches!(self.get(key), Some("true") | Some("1") | Some("yes"))
1682 }
1683 pub fn get_int(&self, key: &str) -> Option<i64> {
1684 self.get(key)?.parse().ok()
1685 }
1686 pub fn len(&self) -> usize {
1687 self.entries.len()
1688 }
1689 pub fn is_empty(&self) -> bool {
1690 self.entries.is_empty()
1691 }
1692}
1693#[allow(dead_code)]
1694#[derive(Debug, Clone)]
1695pub struct ParAnalysisCache {
1696 pub(super) entries: std::collections::HashMap<String, ParCacheEntry>,
1697 pub(super) max_size: usize,
1698 pub(super) hits: u64,
1699 pub(super) misses: u64,
1700}
1701impl ParAnalysisCache {
1702 #[allow(dead_code)]
1703 pub fn new(max_size: usize) -> Self {
1704 ParAnalysisCache {
1705 entries: std::collections::HashMap::new(),
1706 max_size,
1707 hits: 0,
1708 misses: 0,
1709 }
1710 }
1711 #[allow(dead_code)]
1712 pub fn get(&mut self, key: &str) -> Option<&ParCacheEntry> {
1713 if self.entries.contains_key(key) {
1714 self.hits += 1;
1715 self.entries.get(key)
1716 } else {
1717 self.misses += 1;
1718 None
1719 }
1720 }
1721 #[allow(dead_code)]
1722 pub fn insert(&mut self, key: String, data: Vec<u8>) {
1723 if self.entries.len() >= self.max_size {
1724 if let Some(oldest) = self.entries.keys().next().cloned() {
1725 self.entries.remove(&oldest);
1726 }
1727 }
1728 self.entries.insert(
1729 key.clone(),
1730 ParCacheEntry {
1731 key,
1732 data,
1733 timestamp: 0,
1734 valid: true,
1735 },
1736 );
1737 }
1738 #[allow(dead_code)]
1739 pub fn invalidate(&mut self, key: &str) {
1740 if let Some(entry) = self.entries.get_mut(key) {
1741 entry.valid = false;
1742 }
1743 }
1744 #[allow(dead_code)]
1745 pub fn clear(&mut self) {
1746 self.entries.clear();
1747 }
1748 #[allow(dead_code)]
1749 pub fn hit_rate(&self) -> f64 {
1750 let total = self.hits + self.misses;
1751 if total == 0 {
1752 return 0.0;
1753 }
1754 self.hits as f64 / total as f64
1755 }
1756 #[allow(dead_code)]
1757 pub fn size(&self) -> usize {
1758 self.entries.len()
1759 }
1760}
1761#[allow(dead_code)]
1763#[derive(Debug, Clone)]
1764pub struct OParExtDepGraph {
1765 pub(super) n: usize,
1766 pub(super) adj: Vec<Vec<usize>>,
1767 pub(super) rev: Vec<Vec<usize>>,
1768 pub(super) edge_count: usize,
1769}
1770impl OParExtDepGraph {
1771 #[allow(dead_code)]
1772 pub fn new(n: usize) -> Self {
1773 Self {
1774 n,
1775 adj: vec![Vec::new(); n],
1776 rev: vec![Vec::new(); n],
1777 edge_count: 0,
1778 }
1779 }
1780 #[allow(dead_code)]
1781 pub fn add_edge(&mut self, from: usize, to: usize) {
1782 if from < self.n && to < self.n {
1783 if !self.adj[from].contains(&to) {
1784 self.adj[from].push(to);
1785 self.rev[to].push(from);
1786 self.edge_count += 1;
1787 }
1788 }
1789 }
1790 #[allow(dead_code)]
1791 pub fn succs(&self, n: usize) -> &[usize] {
1792 self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1793 }
1794 #[allow(dead_code)]
1795 pub fn preds(&self, n: usize) -> &[usize] {
1796 self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1797 }
1798 #[allow(dead_code)]
1799 pub fn topo_sort(&self) -> Option<Vec<usize>> {
1800 let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
1801 let mut q: std::collections::VecDeque<usize> =
1802 (0..self.n).filter(|&i| deg[i] == 0).collect();
1803 let mut out = Vec::with_capacity(self.n);
1804 while let Some(u) = q.pop_front() {
1805 out.push(u);
1806 for &v in &self.adj[u] {
1807 deg[v] -= 1;
1808 if deg[v] == 0 {
1809 q.push_back(v);
1810 }
1811 }
1812 }
1813 if out.len() == self.n {
1814 Some(out)
1815 } else {
1816 None
1817 }
1818 }
1819 #[allow(dead_code)]
1820 pub fn has_cycle(&self) -> bool {
1821 self.topo_sort().is_none()
1822 }
1823 #[allow(dead_code)]
1824 pub fn reachable(&self, start: usize) -> Vec<usize> {
1825 let mut vis = vec![false; self.n];
1826 let mut stk = vec![start];
1827 let mut out = Vec::new();
1828 while let Some(u) = stk.pop() {
1829 if u < self.n && !vis[u] {
1830 vis[u] = true;
1831 out.push(u);
1832 for &v in &self.adj[u] {
1833 if !vis[v] {
1834 stk.push(v);
1835 }
1836 }
1837 }
1838 }
1839 out
1840 }
1841 #[allow(dead_code)]
1842 pub fn scc(&self) -> Vec<Vec<usize>> {
1843 let mut visited = vec![false; self.n];
1844 let mut order = Vec::new();
1845 for i in 0..self.n {
1846 if !visited[i] {
1847 let mut stk = vec![(i, 0usize)];
1848 while let Some((u, idx)) = stk.last_mut() {
1849 if !visited[*u] {
1850 visited[*u] = true;
1851 }
1852 if *idx < self.adj[*u].len() {
1853 let v = self.adj[*u][*idx];
1854 *idx += 1;
1855 if !visited[v] {
1856 stk.push((v, 0));
1857 }
1858 } else {
1859 order.push(*u);
1860 stk.pop();
1861 }
1862 }
1863 }
1864 }
1865 let mut comp = vec![usize::MAX; self.n];
1866 let mut components: Vec<Vec<usize>> = Vec::new();
1867 for &start in order.iter().rev() {
1868 if comp[start] == usize::MAX {
1869 let cid = components.len();
1870 let mut component = Vec::new();
1871 let mut stk = vec![start];
1872 while let Some(u) = stk.pop() {
1873 if comp[u] == usize::MAX {
1874 comp[u] = cid;
1875 component.push(u);
1876 for &v in &self.rev[u] {
1877 if comp[v] == usize::MAX {
1878 stk.push(v);
1879 }
1880 }
1881 }
1882 }
1883 components.push(component);
1884 }
1885 }
1886 components
1887 }
1888 #[allow(dead_code)]
1889 pub fn node_count(&self) -> usize {
1890 self.n
1891 }
1892 #[allow(dead_code)]
1893 pub fn edge_count(&self) -> usize {
1894 self.edge_count
1895 }
1896}
1897#[derive(Debug, Clone, Default)]
1899pub struct OParEmitStats {
1900 pub bytes_emitted: usize,
1901 pub items_emitted: usize,
1902 pub errors: usize,
1903 pub warnings: usize,
1904 pub elapsed_ms: u64,
1905}
1906impl OParEmitStats {
1907 pub fn new() -> Self {
1908 OParEmitStats::default()
1909 }
1910 pub fn throughput_bps(&self) -> f64 {
1911 if self.elapsed_ms == 0 {
1912 0.0
1913 } else {
1914 self.bytes_emitted as f64 / (self.elapsed_ms as f64 / 1000.0)
1915 }
1916 }
1917 pub fn is_clean(&self) -> bool {
1918 self.errors == 0
1919 }
1920}
1921#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1923pub struct OParVersion {
1924 pub major: u32,
1925 pub minor: u32,
1926 pub patch: u32,
1927 pub pre: Option<String>,
1928}
1929impl OParVersion {
1930 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
1931 OParVersion {
1932 major,
1933 minor,
1934 patch,
1935 pre: None,
1936 }
1937 }
1938 pub fn with_pre(mut self, pre: impl Into<String>) -> Self {
1939 self.pre = Some(pre.into());
1940 self
1941 }
1942 pub fn is_stable(&self) -> bool {
1943 self.pre.is_none()
1944 }
1945 pub fn is_compatible_with(&self, other: &OParVersion) -> bool {
1946 self.major == other.major && self.minor >= other.minor
1947 }
1948}
1949#[derive(Debug, Default)]
1951pub struct OParNameScope {
1952 pub(super) declared: std::collections::HashSet<String>,
1953 pub(super) depth: usize,
1954 pub(super) parent: Option<Box<OParNameScope>>,
1955}
1956impl OParNameScope {
1957 pub fn new() -> Self {
1958 OParNameScope::default()
1959 }
1960 pub fn declare(&mut self, name: impl Into<String>) -> bool {
1961 self.declared.insert(name.into())
1962 }
1963 pub fn is_declared(&self, name: &str) -> bool {
1964 self.declared.contains(name)
1965 }
1966 pub fn push_scope(self) -> Self {
1967 OParNameScope {
1968 declared: std::collections::HashSet::new(),
1969 depth: self.depth + 1,
1970 parent: Some(Box::new(self)),
1971 }
1972 }
1973 pub fn pop_scope(self) -> Self {
1974 *self.parent.unwrap_or_default()
1975 }
1976 pub fn depth(&self) -> usize {
1977 self.depth
1978 }
1979 pub fn len(&self) -> usize {
1980 self.declared.len()
1981 }
1982}
1983#[derive(Debug, Clone)]
1985pub struct OParPassTiming {
1986 pub pass_name: String,
1987 pub elapsed_us: u64,
1988 pub items_processed: usize,
1989 pub bytes_before: usize,
1990 pub bytes_after: usize,
1991}
1992impl OParPassTiming {
1993 pub fn new(
1994 pass_name: impl Into<String>,
1995 elapsed_us: u64,
1996 items: usize,
1997 before: usize,
1998 after: usize,
1999 ) -> Self {
2000 OParPassTiming {
2001 pass_name: pass_name.into(),
2002 elapsed_us,
2003 items_processed: items,
2004 bytes_before: before,
2005 bytes_after: after,
2006 }
2007 }
2008 pub fn throughput_mps(&self) -> f64 {
2009 if self.elapsed_us == 0 {
2010 0.0
2011 } else {
2012 self.items_processed as f64 / (self.elapsed_us as f64 / 1_000_000.0)
2013 }
2014 }
2015 pub fn size_ratio(&self) -> f64 {
2016 if self.bytes_before == 0 {
2017 1.0
2018 } else {
2019 self.bytes_after as f64 / self.bytes_before as f64
2020 }
2021 }
2022 pub fn is_profitable(&self) -> bool {
2023 self.size_ratio() <= 1.05
2024 }
2025}
2026#[derive(Debug, Default)]
2028pub struct OParProfiler {
2029 pub(super) timings: Vec<OParPassTiming>,
2030}
2031impl OParProfiler {
2032 pub fn new() -> Self {
2033 OParProfiler::default()
2034 }
2035 pub fn record(&mut self, t: OParPassTiming) {
2036 self.timings.push(t);
2037 }
2038 pub fn total_elapsed_us(&self) -> u64 {
2039 self.timings.iter().map(|t| t.elapsed_us).sum()
2040 }
2041 pub fn slowest_pass(&self) -> Option<&OParPassTiming> {
2042 self.timings.iter().max_by_key(|t| t.elapsed_us)
2043 }
2044 pub fn num_passes(&self) -> usize {
2045 self.timings.len()
2046 }
2047 pub fn profitable_passes(&self) -> Vec<&OParPassTiming> {
2048 self.timings.iter().filter(|t| t.is_profitable()).collect()
2049 }
2050}
2051#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2053pub enum ParallelKind {
2054 DataParallel,
2056 TaskParallel,
2058 PipelineParallel,
2060 SpeculativeParallel,
2062}
2063#[allow(dead_code)]
2065#[derive(Debug, Clone, Default)]
2066pub struct OParExtLiveness {
2067 pub live_in: Vec<Vec<usize>>,
2068 pub live_out: Vec<Vec<usize>>,
2069 pub defs: Vec<Vec<usize>>,
2070 pub uses: Vec<Vec<usize>>,
2071}
2072impl OParExtLiveness {
2073 #[allow(dead_code)]
2074 pub fn new(n: usize) -> Self {
2075 Self {
2076 live_in: vec![Vec::new(); n],
2077 live_out: vec![Vec::new(); n],
2078 defs: vec![Vec::new(); n],
2079 uses: vec![Vec::new(); n],
2080 }
2081 }
2082 #[allow(dead_code)]
2083 pub fn live_in(&self, b: usize, v: usize) -> bool {
2084 self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2085 }
2086 #[allow(dead_code)]
2087 pub fn live_out(&self, b: usize, v: usize) -> bool {
2088 self.live_out
2089 .get(b)
2090 .map(|s| s.contains(&v))
2091 .unwrap_or(false)
2092 }
2093 #[allow(dead_code)]
2094 pub fn add_def(&mut self, b: usize, v: usize) {
2095 if let Some(s) = self.defs.get_mut(b) {
2096 if !s.contains(&v) {
2097 s.push(v);
2098 }
2099 }
2100 }
2101 #[allow(dead_code)]
2102 pub fn add_use(&mut self, b: usize, v: usize) {
2103 if let Some(s) = self.uses.get_mut(b) {
2104 if !s.contains(&v) {
2105 s.push(v);
2106 }
2107 }
2108 }
2109 #[allow(dead_code)]
2110 pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
2111 self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2112 }
2113 #[allow(dead_code)]
2114 pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
2115 self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2116 }
2117}
2118#[allow(dead_code)]
2119pub struct ParConstantFoldingHelper;
2120impl ParConstantFoldingHelper {
2121 #[allow(dead_code)]
2122 pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
2123 a.checked_add(b)
2124 }
2125 #[allow(dead_code)]
2126 pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
2127 a.checked_sub(b)
2128 }
2129 #[allow(dead_code)]
2130 pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
2131 a.checked_mul(b)
2132 }
2133 #[allow(dead_code)]
2134 pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
2135 if b == 0 {
2136 None
2137 } else {
2138 a.checked_div(b)
2139 }
2140 }
2141 #[allow(dead_code)]
2142 pub fn fold_add_f64(a: f64, b: f64) -> f64 {
2143 a + b
2144 }
2145 #[allow(dead_code)]
2146 pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
2147 a * b
2148 }
2149 #[allow(dead_code)]
2150 pub fn fold_neg_i64(a: i64) -> Option<i64> {
2151 a.checked_neg()
2152 }
2153 #[allow(dead_code)]
2154 pub fn fold_not_bool(a: bool) -> bool {
2155 !a
2156 }
2157 #[allow(dead_code)]
2158 pub fn fold_and_bool(a: bool, b: bool) -> bool {
2159 a && b
2160 }
2161 #[allow(dead_code)]
2162 pub fn fold_or_bool(a: bool, b: bool) -> bool {
2163 a || b
2164 }
2165 #[allow(dead_code)]
2166 pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
2167 a.checked_shl(b)
2168 }
2169 #[allow(dead_code)]
2170 pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
2171 a.checked_shr(b)
2172 }
2173 #[allow(dead_code)]
2174 pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
2175 if b == 0 {
2176 None
2177 } else {
2178 Some(a % b)
2179 }
2180 }
2181 #[allow(dead_code)]
2182 pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
2183 a & b
2184 }
2185 #[allow(dead_code)]
2186 pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
2187 a | b
2188 }
2189 #[allow(dead_code)]
2190 pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
2191 a ^ b
2192 }
2193 #[allow(dead_code)]
2194 pub fn fold_bitnot_i64(a: i64) -> i64 {
2195 !a
2196 }
2197}
2198pub struct PatternDetector<'a> {
2207 pub(super) decl: &'a LcnfFunDecl,
2208}
2209impl<'a> PatternDetector<'a> {
2210 pub(super) fn new(decl: &'a LcnfFunDecl) -> Self {
2211 PatternDetector { decl }
2212 }
2213 pub(super) fn detect(&self) -> Option<ParallelPattern> {
2214 if !self.decl.is_recursive {
2215 return None;
2216 }
2217 let reads = self.collect_reads(&self.decl.body);
2218 let writes = self.collect_writes(&self.decl.body);
2219 let has_accumulator = self.has_accumulator_update(&self.decl.body);
2220 let has_index_write = self.has_index_write(&self.decl.body);
2221 let has_index_read = !reads.is_empty();
2222 if has_accumulator && !has_index_write {
2223 return Some(ParallelPattern::Reduce);
2224 }
2225 if has_index_write && has_index_read {
2226 let write_bases: HashSet<&str> = writes.iter().map(|s| s.as_str()).collect();
2227 let read_bases: HashSet<&str> = reads.iter().map(|s| s.as_str()).collect();
2228 if write_bases == read_bases {
2229 return Some(ParallelPattern::Stencil);
2230 }
2231 return Some(ParallelPattern::Map);
2232 }
2233 if has_index_write && !has_index_read {
2234 return Some(ParallelPattern::Scatter);
2235 }
2236 if !has_index_write && has_index_read {
2237 return Some(ParallelPattern::Gather);
2238 }
2239 if self.has_filter_pattern(&self.decl.body) {
2240 return Some(ParallelPattern::Filter);
2241 }
2242 if self.has_scan_pattern(&self.decl.body) {
2243 return Some(ParallelPattern::Scan);
2244 }
2245 Some(ParallelPattern::ParallelFor)
2246 }
2247 pub(super) fn collect_reads(&self, expr: &LcnfExpr) -> Vec<String> {
2248 let mut out = Vec::new();
2249 self.collect_reads_inner(expr, &mut out);
2250 out
2251 }
2252 pub(super) fn collect_reads_inner(&self, expr: &LcnfExpr, out: &mut Vec<String>) {
2253 match expr {
2254 LcnfExpr::Let { value, body, .. } => {
2255 if let LcnfLetValue::App(LcnfArg::Var(id), _args) = value {
2256 out.push(format!("read_{}", id.0));
2257 }
2258 self.collect_reads_inner(body, out);
2259 }
2260 LcnfExpr::Case { alts, default, .. } => {
2261 for alt in alts {
2262 self.collect_reads_inner(&alt.body, out);
2263 }
2264 if let Some(d) = default {
2265 self.collect_reads_inner(d, out);
2266 }
2267 }
2268 LcnfExpr::Return(_) | LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => {}
2269 }
2270 }
2271 pub(super) fn collect_writes(&self, expr: &LcnfExpr) -> Vec<String> {
2272 let mut out = Vec::new();
2273 self.collect_writes_inner(expr, &mut out);
2274 out
2275 }
2276 pub(super) fn collect_writes_inner(&self, expr: &LcnfExpr, out: &mut Vec<String>) {
2277 match expr {
2278 LcnfExpr::Let {
2279 value, body, name, ..
2280 } => {
2281 if let LcnfLetValue::Ctor(ctor_name, _, _) = value {
2282 if ctor_name.contains("write") || ctor_name.contains("store") {
2283 out.push(name.clone());
2284 }
2285 }
2286 self.collect_writes_inner(body, out);
2287 }
2288 LcnfExpr::Case { alts, default, .. } => {
2289 for alt in alts {
2290 self.collect_writes_inner(&alt.body, out);
2291 }
2292 if let Some(d) = default {
2293 self.collect_writes_inner(d, out);
2294 }
2295 }
2296 LcnfExpr::Return(_) | LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => {}
2297 }
2298 }
2299 pub(super) fn has_accumulator_update(&self, expr: &LcnfExpr) -> bool {
2300 match expr {
2301 LcnfExpr::Let { value, body, .. } => {
2302 if let LcnfLetValue::App(LcnfArg::Var(fid), args) = value {
2303 let is_binop = args.len() == 2;
2304 let is_arith = fid.0 < 16;
2305 if is_binop && is_arith {
2306 return true;
2307 }
2308 }
2309 self.has_accumulator_update(body)
2310 }
2311 LcnfExpr::Case { alts, default, .. } => {
2312 alts.iter().any(|a| self.has_accumulator_update(&a.body))
2313 || default
2314 .as_ref()
2315 .map(|d| self.has_accumulator_update(d))
2316 .unwrap_or(false)
2317 }
2318 _ => false,
2319 }
2320 }
2321 pub(super) fn has_index_write(&self, expr: &LcnfExpr) -> bool {
2322 match expr {
2323 LcnfExpr::Let { value, body, .. } => {
2324 matches!(
2325 value, LcnfLetValue::Ctor(n, _, _) if n.contains("write") || n
2326 .contains("store")
2327 ) || self.has_index_write(body)
2328 }
2329 LcnfExpr::Case { alts, default, .. } => {
2330 alts.iter().any(|a| self.has_index_write(&a.body))
2331 || default
2332 .as_ref()
2333 .map(|d| self.has_index_write(d))
2334 .unwrap_or(false)
2335 }
2336 _ => false,
2337 }
2338 }
2339 pub(super) fn has_filter_pattern(&self, expr: &LcnfExpr) -> bool {
2340 matches!(expr, LcnfExpr::Case { alts, .. } if alts.len() == 2)
2341 || match expr {
2342 LcnfExpr::Let { body, .. } => self.has_filter_pattern(body),
2343 _ => false,
2344 }
2345 }
2346 pub(super) fn has_scan_pattern(&self, expr: &LcnfExpr) -> bool {
2347 self.has_accumulator_update(expr) && self.has_index_write(expr)
2348 }
2349}