Skip to main content

logicaffeine_compile/analysis/
pipes.rs

1//! Which function parameters carry a pipe.
2//!
3//! A pipe crosses into a function as a parameter declared by its element type (`## To consume (ch: Real)`:
4//! the interpreter types nothing here) or as `Pipe of Real`. A parameter is a pipe when its function sends into
5//! it, receives from it, closes it or selects on it, or hands it to a call or a launch where the callee's
6//! parameter is a pipe. A tier that types a parameter by its declaration asks this first, so a pipe of Real
7//! arrives as a pipe, never as a Real.
8
9use std::collections::{HashMap, HashSet};
10
11use logicaffeine_language::ast_depth::{for_each_node, Visited};
12
13use crate::ast_map::Arenas;
14
15use crate::ast::stmt::{Expr, SelectBranch, Stmt, TypeExpr};
16use crate::intern::{Interner, Symbol};
17
18/// The positions of each function's pipe parameters; a function with none is absent.
19pub fn pipe_params(stmts: &[Stmt], interner: &Interner) -> HashMap<Symbol, HashSet<usize>> {
20    let mut pipes: HashMap<Symbol, HashSet<usize>> = HashMap::new();
21    // Each function's hand-offs of a parameter: (callee, argument position, the parameter's position).
22    let mut handoffs: Vec<(Symbol, Symbol, usize, usize)> = Vec::new();
23    for stmt in stmts {
24        let Stmt::FunctionDef {
25            name, params, body, ..
26        } = stmt
27        else {
28            continue;
29        };
30        let position = |sym: Symbol| params.iter().position(|(p, _)| *p == sym);
31        let mut own: HashSet<usize> = params
32            .iter()
33            .enumerate()
34            .filter(|(_, (_, ty))| declares_pipe(ty, interner))
35            .map(|(i, _)| i)
36            .collect();
37        let mut pass = |callee: Symbol, args: &[&Expr]| {
38            for (j, arg) in args.iter().enumerate() {
39                if let Some(i) = identifier(arg).and_then(position) {
40                    handoffs.push((*name, callee, j, i));
41                }
42            }
43        };
44        for_each_node(body, |node| match node {
45            Visited::Stmt(s) => {
46                own.extend(
47                    pipe_operands(s)
48                        .into_iter()
49                        .filter_map(identifier)
50                        .filter_map(position),
51                );
52                if let Some((callee, args)) = handoff(s) {
53                    pass(callee, args);
54                }
55            }
56            Visited::Expr(Expr::Call { function, args }) => pass(*function, args),
57            Visited::Expr(_) => {}
58        });
59        pipes.insert(*name, own);
60    }
61    loop {
62        let mut grew = false;
63        for (f, callee, j, i) in &handoffs {
64            let callee_pipe = pipes.get(callee).is_some_and(|ps| ps.contains(j));
65            if callee_pipe && pipes.get_mut(f).is_some_and(|ps| ps.insert(*i)) {
66                grew = true;
67            }
68        }
69        if !grew {
70            break;
71        }
72    }
73    pipes.retain(|_, ps| !ps.is_empty());
74    pipes
75}
76
77/// The program with every pipe parameter declared as one: `ch: Real` a pipe parameter becomes
78/// `ch: Pipe of Real`, so each site that types a parameter by its declaration sees the pipe.
79pub fn declare_pipe_params<'a>(
80    stmts: &[Stmt<'a>],
81    arenas: Arenas<'a>,
82    interner: &mut Interner,
83) -> Vec<Stmt<'a>> {
84    let pipes = pipe_params(stmts, interner);
85    if pipes.is_empty() {
86        return stmts.to_vec();
87    }
88    let pipe = interner.intern("Pipe");
89    stmts
90        .iter()
91        .map(|stmt| match stmt {
92            Stmt::FunctionDef { name, params, .. } if pipes.contains_key(name) => {
93                let at = &pipes[name];
94                let params = params
95                    .iter()
96                    .enumerate()
97                    .map(|(i, (p, ty))| {
98                        if at.contains(&i) && !declares_pipe(ty, interner) {
99                            let element = arenas.types.alloc_slice(vec![(*ty).clone()]);
100                            (
101                                *p,
102                                &*arenas.types.alloc(TypeExpr::Generic {
103                                    base: pipe,
104                                    params: element,
105                                }),
106                            )
107                        } else {
108                            (*p, *ty)
109                        }
110                    })
111                    .collect();
112                let mut rewritten = stmt.clone();
113                if let Stmt::FunctionDef { params: slot, .. } = &mut rewritten {
114                    *slot = params;
115                }
116                rewritten
117            }
118            other => other.clone(),
119        })
120        .collect()
121}
122
123/// Whether a declared type is `Pipe of T`.
124fn declares_pipe(ty: &TypeExpr, interner: &Interner) -> bool {
125    matches!(ty, TypeExpr::Generic { base, .. } if interner.resolve(*base) == "Pipe")
126}
127
128fn identifier(e: &&Expr) -> Option<Symbol> {
129    match e {
130        Expr::Identifier(sym) => Some(*sym),
131        _ => None,
132    }
133}
134
135/// The pipes a statement itself operates on.
136fn pipe_operands<'s, 'a>(stmt: &'s Stmt<'a>) -> Vec<&'s &'a Expr<'a>> {
137    match stmt {
138        Stmt::SendPipe { pipe, .. }
139        | Stmt::TrySendPipe { pipe, .. }
140        | Stmt::ReceivePipe { pipe, .. }
141        | Stmt::TryReceivePipe { pipe, .. } => vec![pipe],
142        Stmt::CloseChannel { channel } => vec![channel],
143        Stmt::Select { branches } => branches
144            .iter()
145            .filter_map(|b| match b {
146                SelectBranch::Receive { pipe, .. } => Some(pipe),
147                SelectBranch::Timeout { .. } => None,
148            })
149            .collect(),
150        _ => Vec::new(),
151    }
152}
153
154/// A statement that hands arguments to a function: a call or a launch.
155fn handoff<'s, 'a>(stmt: &'s Stmt<'a>) -> Option<(Symbol, &'s [&'a Expr<'a>])> {
156    match stmt {
157        Stmt::Call { function, args }
158        | Stmt::LaunchTask { function, args }
159        | Stmt::LaunchTaskWithHandle { function, args, .. } => Some((*function, args.as_slice())),
160        _ => None,
161    }
162}