Skip to main content

libc_cfg/
transform.rs

1use bool_logic::ast::{All, Any, Not, Var, any, expr};
2use bool_logic::cfg::ast::{Expr, Pred, flag, target_family};
3use bool_logic::visit_mut::{VisitMut, walk_mut_expr, walk_mut_expr_list};
4
5use bool_logic::transforms::dedup_list::DedupList;
6use bool_logic::transforms::eval_const::EvalConst;
7use bool_logic::transforms::flatten_nested_list::FlattenNestedList;
8use bool_logic::transforms::flatten_single::FlattenSingle;
9use bool_logic::transforms::merge_all_of_any::MergeAllOfAny;
10use bool_logic::transforms::merge_all_of_not_any::MergeAllOfNotAny;
11use bool_logic::transforms::simplify_all_not_any::SimplifyAllNotAny;
12use bool_logic::transforms::simplify_by_short_circuit::SimplifyByShortCircuit;
13use bool_logic::transforms::simplify_nested_list::SimplifyNestedList;
14
15use std::cmp::Ordering;
16use std::cmp::Ordering::{Equal, Greater, Less};
17use std::mem;
18
19use stdx::iter::filter_map_collect_vec;
20use stdx::iter::map_collect_vec;
21use stdx::vec::VecExt;
22
23use log::debug;
24use log::trace;
25
26pub fn simplified_expr(x: impl Into<Expr>) -> Expr {
27    let mut x = x.into();
28
29    debug!("input:                              {x}");
30
31    UnifyTargetFamily.visit_mut_expr(&mut x);
32    trace!("after  UnifyTargetFamily:           {x}");
33
34    for _ in 0..3 {
35        FlattenSingle.visit_mut_expr(&mut x);
36        trace!("after  FlattenSingle:               {x}");
37
38        FlattenNestedList.visit_mut_expr(&mut x);
39        trace!("after  FlattenNestedList:           {x}");
40
41        DedupList.visit_mut_expr(&mut x);
42        trace!("after  DedupList:                   {x}");
43
44        EvalConst.visit_mut_expr(&mut x);
45        trace!("after  EvalConst:                   {x}");
46
47        SimplifyNestedList.visit_mut_expr(&mut x);
48        trace!("after  SimplifyNestedList:          {x}");
49
50        MergeAllOfNotAny.visit_mut_expr(&mut x);
51        trace!("after  MergeAllOfNotAny:            {x}");
52
53        SimplifyAllNotAny.visit_mut_expr(&mut x);
54        trace!("after  SimplifyAllNotAny:           {x}");
55
56        MergeAllOfAny.visit_mut_expr(&mut x);
57        trace!("after  MergeAllOfAny:               {x}");
58
59        ImplyByKey.visit_mut_expr(&mut x);
60        trace!("after  ImplyByKey:                  {x}");
61
62        SuppressTargetFamily.visit_mut_expr(&mut x);
63        trace!("after  SuppressTargetFamily:        {x}");
64
65        EvalConst.visit_mut_expr(&mut x);
66        trace!("after  EvalConst:                   {x}");
67
68        MergePattern.visit_mut_expr(&mut x);
69        trace!("after  MergePattern:                {x}");
70
71        EvalConst.visit_mut_expr(&mut x);
72        trace!("after  EvalConst:                   {x}");
73
74        SimplifyByShortCircuit.visit_mut_expr(&mut x);
75        trace!("after  SimplifyByShortCircuit:      {x}");
76
77        EvalConst.visit_mut_expr(&mut x);
78        trace!("after  EvalConst:                   {x}");
79    }
80
81    SimplifyTargetFamily.visit_mut_expr(&mut x);
82    trace!("after  SimplifyTargetFamily:        {x}");
83
84    SortByPriority.visit_mut_expr(&mut x);
85    trace!("after  SortByPriority:              {x}");
86
87    SortByValue.visit_mut_expr(&mut x);
88    trace!("after  SortByValue:                 {x}");
89
90    debug!("output:                             {x}");
91
92    x
93}
94
95struct SortByPriority;
96
97impl SortByPriority {
98    fn get_priority(x: &Expr) -> u32 {
99        match x {
100            Expr::Not(_) => 103,
101            Expr::Any(_) => 101,
102            Expr::All(_) => 102,
103            Expr::Var(Var(pred)) => match pred.key.as_str() {
104                "target_family" => 1,
105                "target_arch" => 2,
106                "target_vendor" => 3,
107                "target_os" => 4,
108                "target_env" => 5,
109                "target_pointer_width" => 6,
110                _ => 0,
111            },
112            Expr::Const(_) => panic!(),
113        }
114    }
115}
116
117impl VisitMut<Pred> for SortByPriority {
118    fn visit_mut_expr(&mut self, expr: &mut Expr) {
119        if let Some(list) = expr.as_mut_expr_list() {
120            list.sort_by(|lhs, rhs| {
121                let lhs = Self::get_priority(lhs);
122                let rhs = Self::get_priority(rhs);
123                lhs.cmp(&rhs)
124            });
125        }
126
127        walk_mut_expr(self, expr);
128    }
129}
130
131struct SortByValue;
132
133impl SortByValue {
134    fn cmp_var(lhs: &Expr, rhs: &Expr) -> Ordering {
135        let Expr::Var(Var(lhs)) = lhs else {
136            return Equal;
137        };
138        let Expr::Var(Var(rhs)) = rhs else {
139            return Equal;
140        };
141
142        let ok = Ord::cmp(lhs.key.as_str(), rhs.key.as_str());
143
144        match (lhs.value.as_deref(), rhs.value.as_deref()) {
145            (None, None) => ok,
146            (Some(lv), Some(rv)) => ok.then_with(|| Ord::cmp(lv, rv)),
147            (None, Some(_)) => Less,
148            (Some(_), None) => Greater,
149        }
150    }
151
152    fn cmp_not(lhs: &Expr, rhs: &Expr) -> Ordering {
153        let Expr::Not(Not(lhs)) = lhs else {
154            return Equal;
155        };
156        let Expr::Not(Not(rhs)) = rhs else {
157            return Equal;
158        };
159
160        Self::cmp_var(lhs, rhs)
161    }
162}
163
164impl VisitMut<Pred> for SortByValue {
165    fn visit_mut_expr(&mut self, expr: &mut Expr) {
166        if let Some(list) = expr.as_mut_expr_list() {
167            list.sort_by(Self::cmp_var);
168            list.sort_by(Self::cmp_not);
169        }
170
171        walk_mut_expr(self, expr);
172    }
173}
174
175struct UnifyTargetFamily;
176
177impl VisitMut<Pred> for UnifyTargetFamily {
178    fn visit_mut_var(&mut self, Var(pred): &mut Var<Pred>) {
179        if pred.value.is_none() && matches!(pred.key.as_str(), "unix" | "windows" | "wasm") {
180            *pred = target_family(pred.key.clone());
181        }
182    }
183}
184
185struct SimplifyTargetFamily;
186
187impl VisitMut<Pred> for SimplifyTargetFamily {
188    fn visit_mut_var(&mut self, Var(pred): &mut Var<Pred>) {
189        if pred.key == "target_family" {
190            if let Some(value) = pred.value.as_deref() {
191                if matches!(value, "unix" | "windows" | "wasm") {
192                    *pred = flag(value);
193                }
194            }
195        }
196    }
197}
198
199struct ImplyByKey;
200
201impl ImplyByKey {
202    const UNIQUE_VALUED_KEYS: &'static [&'static str] = &[
203        "target_family",
204        "target_arch",
205        "target_vendor",
206        "target_os",
207        "target_env",
208        "target_pointer_width",
209    ];
210
211    fn is_expr_any_pred(any: &[Expr], key: &str) -> bool {
212        any.iter()
213            .all(|x| x.as_var().is_some_and(|Var(var)| var.key == key))
214    }
215
216    fn fix(pos_key: &str, pos_any_values: &[&str], expr: &mut Expr) {
217        match expr {
218            Expr::Any(Any(any)) => {
219                for x in any.iter_mut() {
220                    Self::fix(pos_key, pos_any_values, x);
221                }
222            }
223            Expr::All(All(all)) => {
224                for x in all.iter_mut() {
225                    Self::fix(pos_key, pos_any_values, x);
226                }
227            }
228            Expr::Not(Not(not)) => {
229                Self::fix(pos_key, pos_any_values, not);
230            }
231            Expr::Var(Var(var)) => {
232                if var.key == pos_key {
233                    let var_value = var.value.as_deref().unwrap();
234                    if pos_any_values.contains(&var_value) {
235                        if pos_any_values.len() == 1 {
236                            *expr = Expr::Const(true);
237                        }
238                    } else {
239                        *expr = Expr::Const(false);
240                    }
241                }
242            }
243            Expr::Const(_) => {}
244        }
245    }
246}
247
248impl VisitMut<Pred> for ImplyByKey {
249    fn visit_mut_all(&mut self, All(all): &mut All<Pred>) {
250        walk_mut_expr_list(self, all);
251
252        let mut i = 0;
253        while i < all.len() {
254            match &all[i] {
255                Expr::Var(Var(pos)) if Self::UNIQUE_VALUED_KEYS.contains(&pos.key.as_str()) => {
256                    assert!(pos.value.is_some());
257
258                    let pos = pos.clone();
259                    let pos_key = pos.key.as_str();
260                    let pos_any_values = &[pos.value.as_deref().unwrap()];
261
262                    for (_, x) in all.iter_mut().enumerate().filter(|&(j, _)| j != i) {
263                        Self::fix(pos_key, pos_any_values, x);
264                    }
265                }
266                Expr::Any(Any(any)) => {
267                    if let Some(pos_key) = Self::UNIQUE_VALUED_KEYS
268                        .iter()
269                        .find(|k| Self::is_expr_any_pred(any, k))
270                    {
271                        let any = any.clone();
272                        let pos_any_values = map_collect_vec(&any, |x| {
273                            x.as_var().unwrap().0.value.as_deref().unwrap()
274                        });
275
276                        for (_, x) in all.iter_mut().enumerate().filter(|&(j, _)| j != i) {
277                            Self::fix(pos_key, &pos_any_values, x);
278                        }
279                    }
280                }
281                _ => {}
282            }
283            i += 1;
284        }
285    }
286}
287
288struct SuppressTargetFamily;
289
290impl SuppressTargetFamily {
291    fn is_family_implier(x: &Expr) -> bool {
292        match x {
293            Expr::Var(Var(var)) => match (var.key.as_str(), var.value.as_deref()) {
294                // a specified `target_os` pins the target family;
295                // all Rust targets with vendor `apple` are unix
296                ("target_os", _) | ("target_vendor", Some("apple")) => true,
297                _ => false,
298            },
299            _ => false,
300        }
301    }
302
303    fn has_specified_family_implier(x: &Expr) -> bool {
304        if Self::is_family_implier(x) {
305            return true;
306        }
307
308        if let Expr::Any(Any(any)) = x {
309            return any.iter().all(Self::is_family_implier);
310        }
311
312        false
313    }
314
315    #[allow(clippy::match_like_matches_macro)]
316    fn is_suppressed_target_family(pred: &Pred) -> bool {
317        match (pred.key.as_str(), pred.value.as_deref()) {
318            ("target_family", Some("unix")) => true,
319            ("target_family", Some("windows")) => true,
320            _ => false,
321        }
322    }
323}
324
325impl VisitMut<Pred> for SuppressTargetFamily {
326    fn visit_mut_all(&mut self, All(all): &mut All<Pred>) {
327        if all.iter().any(Self::has_specified_family_implier) {
328            all.remove_if(|x| match x {
329                Expr::Var(Var(pred)) => Self::is_suppressed_target_family(pred),
330                Expr::Not(Not(not)) => match &**not {
331                    Expr::Var(Var(pred)) => Self::is_suppressed_target_family(pred),
332                    _ => false,
333                },
334                _ => false,
335            });
336        }
337
338        walk_mut_expr_list(self, all);
339    }
340}
341
342struct MergePattern;
343
344impl MergePattern {
345    fn merge(any_list: &mut [Expr]) {
346        let mut pattern_list = filter_map_collect_vec(any_list, |x| {
347            if let Expr::All(All(all)) = x {
348                if let [first, second] = all.as_mut_slice() {
349                    if first.is_any() || first.is_var() {
350                        return Some((first, second));
351                    }
352                }
353            }
354            None
355        });
356
357        if let [head, rest @ ..] = pattern_list.as_mut_slice() {
358            let agg = match head.0 {
359                Expr::Any(Any(any)) => any,
360                Expr::Var(var) => {
361                    *head.0 = expr(any((var.clone(),)));
362                    head.0.as_mut_any().map(|x| &mut x.0).unwrap()
363                }
364                _ => panic!(),
365            };
366
367            for x in rest {
368                let to_agg = if x.1 == head.1 {
369                    &mut *x.0
370                } else if x.0 == head.1 {
371                    &mut *x.1
372                } else {
373                    continue;
374                };
375
376                match mem::replace(to_agg, Expr::Const(false)) {
377                    Expr::Any(Any(any)) => agg.extend(any),
378                    Expr::Var(var) => agg.push(expr(var.clone())),
379                    other => *to_agg = other,
380                }
381            }
382
383            if agg.len() == 1 {
384                *head.0 = agg.pop().unwrap();
385            }
386        }
387    }
388}
389
390impl VisitMut<Pred> for MergePattern {
391    fn visit_mut_any(&mut self, Any(any_list): &mut Any<Pred>) {
392        Self::merge(any_list);
393        Self::merge(&mut any_list[1..]);
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use bool_logic::ast::all;
400    use bool_logic::ast::not;
401    use bool_logic::cfg::ast::target_os;
402    use bool_logic::cfg::ast::target_vendor;
403
404    use super::*;
405
406    #[test]
407    fn sort() {
408        let mut expr = expr(all((not(flag("unix")), flag("unix"))));
409        SortByPriority.visit_mut_expr(&mut expr);
410        assert_eq!(expr.to_string(), "all(unix, not(unix))");
411    }
412
413    #[test]
414    fn suppress_target_family() {
415        // a specified `target_os` pins the target family
416        let expr = simplified_expr(all((target_os("linux"), flag("unix"))));
417        assert_eq!(expr.to_string(), r#"target_os = "linux""#);
418
419        // vendor `apple` also pins the target family
420        let expr = simplified_expr(all((target_vendor("apple"), flag("unix"))));
421        assert_eq!(expr.to_string(), r#"target_vendor = "apple""#);
422
423        // other vendors do not
424        let expr = simplified_expr(all((target_vendor("unknown"), flag("unix"))));
425        assert_eq!(expr.to_string(), r#"all(unix, target_vendor = "unknown")"#);
426    }
427
428    #[test]
429    fn imply() {
430        {
431            let mut expr = expr(all((target_os("linux"), not(target_os("emscripten")))));
432            ImplyByKey.visit_mut_expr(&mut expr);
433            assert_eq!(expr.to_string(), r#"all(target_os = "linux", not(false))"#);
434        }
435        {
436            let mut expr = expr(all((
437                any((target_os("ios"), target_os("macos"))),     //
438                any((target_os("linux"), target_os("android"))), //
439            )));
440            ImplyByKey.visit_mut_expr(&mut expr);
441            assert_eq!(
442                expr.to_string(),
443                r#"all(any(target_os = "ios", target_os = "macos"), any(false, false))"#
444            );
445        }
446    }
447}