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