Skip to main content

i_slint_compiler/passes/
deduplicate_property_read.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Do not read twice the same property, store in a local variable instead
5
6use crate::expression_tree::*;
7use crate::langtype::Type;
8use crate::object_tree::*;
9use smol_str::{SmolStr, format_smolstr};
10use std::cell::RefCell;
11use std::collections::{BTreeMap, HashMap};
12
13pub fn deduplicate_property_read(component: &Component) {
14    visit_all_expressions(component, |expr, ty| {
15        if matches!(ty(), Type::Callback { .. }) {
16            // Callback handlers don't register dependencies, so there is nothing to gain.
17            return;
18        }
19        // A hoisted read must not cross a write of the same property.
20        // Rather than tracking writes, only optimize expressions that can't write at all.
21        if !super::purity_check::is_pure(expr) {
22            return;
23        }
24        process_expression(expr, &DedupPropState::default());
25    });
26}
27
28struct ReadCount {
29    count: usize,
30    has_been_mapped: bool,
31}
32
33#[derive(Default)]
34struct PropertyReadCounts {
35    counts: HashMap<NamedReference, ReadCount>,
36    /// If at least one element of the map has duplicates
37    has_duplicate: bool,
38}
39
40#[derive(Default)]
41struct DedupPropState<'a> {
42    parent_state: Option<&'a DedupPropState<'a>>,
43    counts: RefCell<PropertyReadCounts>,
44}
45
46impl DedupPropState<'_> {
47    fn add(&self, nr: &NamedReference) {
48        if self.parent_state.is_some_and(|pc| pc.add_from_children(nr)) {
49            return;
50        }
51        let mut use_counts = self.counts.borrow_mut();
52        let use_counts = &mut *use_counts;
53        let has_duplicate = &mut use_counts.has_duplicate;
54        use_counts
55            .counts
56            .entry(nr.clone())
57            .and_modify(|c| {
58                if c.count == 1 {
59                    *has_duplicate = true;
60                }
61                c.count += 1;
62            })
63            .or_insert(ReadCount { count: 1, has_been_mapped: false });
64    }
65
66    fn add_from_children(&self, nr: &NamedReference) -> bool {
67        if self.parent_state.is_some_and(|pc| pc.add_from_children(nr)) {
68            return true;
69        }
70        let mut use_counts = self.counts.borrow_mut();
71        let use_counts = &mut *use_counts;
72        if let Some(c) = use_counts.counts.get_mut(nr) {
73            if c.count == 1 {
74                use_counts.has_duplicate = true;
75            }
76            c.count += 1;
77            true
78        } else {
79            false
80        }
81    }
82
83    fn get_mapping(&self, nr: &NamedReference) -> Option<SmolStr> {
84        self.parent_state.and_then(|pr| pr.get_mapping(nr)).or_else(|| {
85            self.counts.borrow_mut().counts.get_mut(nr).filter(|c| c.count > 1).map(|c| {
86                c.has_been_mapped = true;
87                map_nr(nr)
88            })
89        })
90    }
91}
92
93fn map_nr(nr: &NamedReference) -> SmolStr {
94    format_smolstr!("tmp_{}_{}", nr.element().borrow().id, nr.name())
95}
96
97fn process_expression(expr: &mut Expression, old_state: &DedupPropState) {
98    let new_state = DedupPropState { parent_state: Some(old_state), ..DedupPropState::default() };
99    collect_unconditional_read_count(expr, &new_state);
100    process_conditional_expressions(expr, &new_state);
101    do_replacements(expr, &new_state);
102
103    if new_state.counts.borrow().has_duplicate {
104        let mut stores = BTreeMap::<SmolStr, NamedReference>::new();
105        for (nr, c) in &new_state.counts.borrow().counts {
106            if c.has_been_mapped {
107                stores.insert(map_nr(nr), nr.clone());
108            }
109        }
110        let mut exprs = stores
111            .into_iter()
112            .map(|(name, nr)| Expression::StoreLocalVariable {
113                name,
114                value: Box::new(Expression::PropertyReference(nr)),
115            })
116            .collect::<Vec<_>>();
117        exprs.push(std::mem::take(expr));
118        *expr = Expression::CodeBlock(exprs);
119    }
120}
121
122// Collect all use of variable and their count, only in non conditional expression
123fn collect_unconditional_read_count(expr: &Expression, result: &DedupPropState) {
124    match expr {
125        Expression::PropertyReference(nr) => {
126            result.add(nr);
127        }
128        //Expression::RepeaterIndexReference { element } => {}
129        //Expression::RepeaterModelReference { element } => {}
130        Expression::BinaryExpression { lhs, rhs: _, op: '|' | '&', .. } => {
131            lhs.visit(|sub| collect_unconditional_read_count(sub, result))
132        }
133        Expression::Condition { condition, .. } => {
134            condition.visit(|sub| collect_unconditional_read_count(sub, result))
135        }
136        _ => expr.visit(|sub| collect_unconditional_read_count(sub, result)),
137    }
138}
139
140fn process_conditional_expressions(expr: &mut Expression, state: &DedupPropState) {
141    match expr {
142        Expression::BinaryExpression { lhs, rhs, op: '|' | '&', .. } => {
143            lhs.visit_mut(|sub| process_conditional_expressions(sub, state));
144            process_expression(rhs, state);
145        }
146        Expression::Condition { condition, true_expr, false_expr, .. } => {
147            condition.visit_mut(|sub| process_conditional_expressions(sub, state));
148            process_expression(true_expr, state);
149            process_expression(false_expr, state);
150        }
151        _ => expr.visit_mut(|sub| process_conditional_expressions(sub, state)),
152    }
153}
154
155fn do_replacements(expr: &mut Expression, state: &DedupPropState) {
156    match expr {
157        Expression::PropertyReference(nr) => {
158            if let Some(name) = state.get_mapping(nr) {
159                let ty = expr.ty();
160                *expr = Expression::ReadLocalVariable { name, ty };
161            }
162        }
163        Expression::BinaryExpression { lhs, rhs: _, op: '|' | '&', .. } => {
164            lhs.visit_mut(|sub| do_replacements(sub, state));
165        }
166        Expression::Condition { condition, .. } => {
167            condition.visit_mut(|sub| do_replacements(sub, state));
168        }
169        _ => expr.visit_mut(|sub| do_replacements(sub, state)),
170    }
171}