egglog/ast/
remove_globals.rs

1//! Remove global variables from the program by translating
2//! them into functions with no arguments.
3//! This requires type information, so it is done after type checking.
4//! Primitives are translated into functions with a primitive output.
5//! When a globally-bound primitive value is used in the actions of a rule,
6//! we add a new variable to the query bound to the primitive value.
7
8use crate::*;
9use crate::{core::ResolvedCall, typechecking::FuncType};
10use egglog_ast::generic_ast::{GenericAction, GenericExpr, GenericFact, GenericRule};
11
12struct GlobalRemover<'a> {
13    fresh: &'a mut SymbolGen,
14}
15
16/// Removes all globals from a program.
17/// No top level lets are allowed after this pass,
18/// nor any variable that references a global.
19/// Adds new functions for global variables
20/// and replaces references to globals with
21/// references to the new functions.
22/// e.g.
23/// ```ignore
24/// (let x 3)
25/// (Add x x)
26/// ```
27/// becomes
28/// ```ignore
29/// (function x () i64)
30/// (set (x) 3)
31/// (Add (x) (x))
32/// ```
33///
34/// If later, this global is referenced in a rule:
35/// ```ignore
36/// (rule ((Neg y))
37///       ((Add x x)))
38/// ```
39/// We instrument the query to make the value available:
40/// ```ignore
41/// (rule ((Neg y)
42///        (= fresh_var_for_x (x)))
43///       ((Add fresh_var_for_x fresh_var_for_x)))
44/// ```
45pub(crate) fn remove_globals(
46    prog: Vec<ResolvedNCommand>,
47    fresh: &mut SymbolGen,
48) -> Vec<ResolvedNCommand> {
49    let mut remover = GlobalRemover { fresh };
50    prog.into_iter()
51        .flat_map(|cmd| remover.remove_globals_cmd(cmd))
52        .collect()
53}
54
55fn resolved_var_to_call(var: &ResolvedVar) -> ResolvedCall {
56    assert!(
57        var.is_global_ref,
58        "resolved_var_to_call called on non-global var"
59    );
60    ResolvedCall::Func(FuncType {
61        name: var.name.clone(),
62        subtype: FunctionSubtype::Custom,
63        input: vec![],
64        output: var.sort.clone(),
65    })
66}
67
68/// TODO (yz) it would be better to implement replace_global_var
69/// as a function from ResolvedVar to ResolvedExpr
70/// and use it as an argument to `subst` instead of `visit_expr`,
71/// but we have not implemented `subst` for command.
72fn replace_global_vars(expr: ResolvedExpr) -> ResolvedExpr {
73    match expr.get_global_var() {
74        Some(resolved_var) => {
75            GenericExpr::Call(expr.span(), resolved_var_to_call(&resolved_var), vec![])
76        }
77        None => expr,
78    }
79}
80
81pub(crate) fn remove_globals_expr(expr: ResolvedExpr) -> ResolvedExpr {
82    expr.visit_exprs(&mut replace_global_vars)
83}
84
85fn remove_globals_action(action: ResolvedAction) -> ResolvedAction {
86    action.visit_exprs(&mut replace_global_vars)
87}
88
89impl GlobalRemover<'_> {
90    fn remove_globals_cmd(&mut self, cmd: ResolvedNCommand) -> Vec<ResolvedNCommand> {
91        match cmd {
92            GenericNCommand::CoreAction(action) => match action {
93                GenericAction::Let(span, name, expr) => {
94                    let ty = expr.output_type();
95
96                    let resolved_call = ResolvedCall::Func(FuncType {
97                        name: name.name.clone(),
98                        subtype: FunctionSubtype::Custom,
99                        input: vec![],
100                        output: ty.clone(),
101                    });
102                    let func_decl = ResolvedFunctionDecl {
103                        name: name.name,
104                        subtype: FunctionSubtype::Custom,
105                        schema: Schema {
106                            input: vec![],
107                            output: ty.name().to_owned(),
108                        },
109                        resolved_schema: resolved_call.clone(),
110                        merge: None,
111                        cost: None,
112                        unextractable: true,
113                        internal_hidden: false,
114                        internal_let: true,
115                        span: span.clone(),
116                        term_constructor: None,
117                    };
118                    vec![
119                        GenericNCommand::Function(func_decl),
120                        GenericNCommand::CoreAction(GenericAction::Set(
121                            span,
122                            resolved_call,
123                            vec![],
124                            remove_globals_expr(expr),
125                        )),
126                    ]
127                }
128                _ => vec![GenericNCommand::CoreAction(remove_globals_action(action))],
129            },
130            GenericNCommand::NormRule { rule } => {
131                // A map from the global variables in actions to their new names
132                // in the query.
133                let mut globals = HashMap::default();
134                rule.head.clone().visit_exprs(&mut |expr| {
135                    if let Some(resolved_var) = expr.get_global_var() {
136                        let new_name = self.fresh.fresh(&resolved_var.name);
137                        globals.insert(
138                            resolved_var.clone(),
139                            GenericExpr::Var(
140                                expr.span(),
141                                ResolvedVar {
142                                    name: new_name,
143                                    sort: resolved_var.sort.clone(),
144                                    is_global_ref: false,
145                                },
146                            ),
147                        );
148                    }
149                    expr
150                });
151                let new_facts: Vec<ResolvedFact> = globals
152                    .iter()
153                    .map(|(old, new)| {
154                        GenericFact::Eq(
155                            new.span(),
156                            GenericExpr::Call(new.span(), resolved_var_to_call(old), vec![]),
157                            new.clone(),
158                        )
159                    })
160                    .collect();
161
162                let new_rule = GenericRule {
163                    span: rule.span,
164                    // instrument the old facts and add the new facts to the end
165                    body: rule
166                        .body
167                        .iter()
168                        .map(|fact| fact.clone().visit_exprs(&mut replace_global_vars))
169                        .chain(new_facts)
170                        .collect(),
171                    // replace references to globals with the newly bound names
172                    head: rule.head.clone().visit_exprs(&mut |expr| {
173                        if let Some(resolved_var) = expr.get_global_var() {
174                            globals.get(&resolved_var).unwrap().clone()
175                        } else {
176                            expr
177                        }
178                    }),
179                    name: rule.name.clone(),
180                    ruleset: rule.ruleset.clone(),
181                    eval_mode: rule.eval_mode,
182                    no_decomp: rule.no_decomp,
183                    include_subsumed: rule.include_subsumed,
184                };
185                vec![GenericNCommand::NormRule { rule: new_rule }]
186            }
187            // Handle the corner case where a global command is wrap in (fail )
188            GenericNCommand::Fail(span, cmd) => {
189                let mut removed = self.remove_globals_cmd(*cmd);
190                let last = removed.pop().unwrap();
191                let boxed_last = Box::new(last);
192                let new_command = GenericNCommand::Fail(span, boxed_last);
193                removed.push(new_command);
194                removed
195            }
196            _ => vec![cmd.visit_exprs(&mut replace_global_vars)],
197        }
198    }
199}