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::{format_smolstr, 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 handler can't be optimizes because they can have side effect.
17            // But that's fine as they also do not register dependencies
18            return;
19        }
20        process_expression(expr, &DedupPropState::default());
21    });
22}
23
24struct ReadCount {
25    count: usize,
26    has_been_mapped: bool,
27}
28
29#[derive(Default)]
30struct PropertyReadCounts {
31    counts: HashMap<NamedReference, ReadCount>,
32    /// If at least one element of the map has duplicates
33    has_duplicate: bool,
34    /// if there is an assignment of a property we currently disable this optimization
35    has_set: bool,
36}
37
38#[derive(Default)]
39struct DedupPropState<'a> {
40    parent_state: Option<&'a DedupPropState<'a>>,
41    counts: RefCell<PropertyReadCounts>,
42}
43
44impl DedupPropState<'_> {
45    fn add(&self, nr: &NamedReference) {
46        if self.parent_state.is_some_and(|pc| pc.add_from_children(nr)) {
47            return;
48        }
49        let mut use_counts = self.counts.borrow_mut();
50        let use_counts = &mut *use_counts;
51        let has_duplicate = &mut use_counts.has_duplicate;
52        use_counts
53            .counts
54            .entry(nr.clone())
55            .and_modify(|c| {
56                if c.count == 1 {
57                    *has_duplicate = true;
58                }
59                c.count += 1;
60            })
61            .or_insert(ReadCount { count: 1, has_been_mapped: false });
62    }
63
64    fn add_from_children(&self, nr: &NamedReference) -> bool {
65        if self.parent_state.is_some_and(|pc| pc.add_from_children(nr)) {
66            return true;
67        }
68        let mut use_counts = self.counts.borrow_mut();
69        let use_counts = &mut *use_counts;
70        if let Some(c) = use_counts.counts.get_mut(nr) {
71            if c.count == 1 {
72                use_counts.has_duplicate = true;
73            }
74            c.count += 1;
75            true
76        } else {
77            false
78        }
79    }
80
81    fn get_mapping(&self, nr: &NamedReference) -> Option<SmolStr> {
82        self.parent_state.and_then(|pr| pr.get_mapping(nr)).or_else(|| {
83            self.counts.borrow_mut().counts.get_mut(nr).filter(|c| c.count > 1).map(|c| {
84                c.has_been_mapped = true;
85                map_nr(nr)
86            })
87        })
88    }
89}
90
91fn map_nr(nr: &NamedReference) -> SmolStr {
92    format_smolstr!("tmp_{}_{}", nr.element().borrow().id, nr.name())
93}
94
95fn process_expression(expr: &mut Expression, old_state: &DedupPropState) {
96    if old_state.counts.borrow().has_set {
97        return;
98    }
99    let new_state = DedupPropState { parent_state: Some(old_state), ..DedupPropState::default() };
100    collect_unconditional_read_count(expr, &new_state);
101    process_conditional_expressions(expr, &new_state);
102    if new_state.counts.borrow().has_set {
103        old_state.counts.borrow_mut().has_set = true;
104    } else {
105        do_replacements(expr, &new_state);
106    }
107
108    if new_state.counts.borrow().has_duplicate {
109        let mut stores = BTreeMap::<SmolStr, NamedReference>::new();
110        for (nr, c) in &new_state.counts.borrow().counts {
111            if c.has_been_mapped {
112                stores.insert(map_nr(nr), nr.clone());
113            }
114        }
115        let mut exprs = stores
116            .into_iter()
117            .map(|(name, nr)| Expression::StoreLocalVariable {
118                name,
119                value: Box::new(Expression::PropertyReference(nr)),
120            })
121            .collect::<Vec<_>>();
122        exprs.push(std::mem::take(expr));
123        *expr = Expression::CodeBlock(exprs);
124    }
125}
126
127// Collect all use of variable and their count, only in non conditional expression
128fn collect_unconditional_read_count(expr: &Expression, result: &DedupPropState) {
129    if result.counts.borrow().has_set {
130        return;
131    }
132    match expr {
133        Expression::PropertyReference(nr) => {
134            result.add(nr);
135        }
136        //Expression::RepeaterIndexReference { element } => {}
137        //Expression::RepeaterModelReference { element } => {}
138        Expression::BinaryExpression { lhs, rhs: _, op: '|' | '&' } => {
139            lhs.visit(|sub| collect_unconditional_read_count(sub, result))
140        }
141        Expression::Condition { condition, .. } => {
142            condition.visit(|sub| collect_unconditional_read_count(sub, result))
143        }
144        Expression::SelfAssignment { .. } => {
145            result.counts.borrow_mut().has_set = true;
146        }
147        _ => expr.visit(|sub| collect_unconditional_read_count(sub, result)),
148    }
149}
150
151fn process_conditional_expressions(expr: &mut Expression, state: &DedupPropState) {
152    if state.counts.borrow().has_set {
153        return;
154    }
155    match expr {
156        Expression::BinaryExpression { lhs, rhs, op: '|' | '&' } => {
157            lhs.visit_mut(|sub| process_conditional_expressions(sub, state));
158            process_expression(rhs, state);
159        }
160        Expression::Condition { condition, true_expr, false_expr } => {
161            condition.visit_mut(|sub| process_conditional_expressions(sub, state));
162            process_expression(true_expr, state);
163            process_expression(false_expr, state);
164        }
165        Expression::SelfAssignment { .. } => {
166            state.counts.borrow_mut().has_set = true;
167        }
168        _ => expr.visit_mut(|sub| process_conditional_expressions(sub, state)),
169    }
170}
171
172fn do_replacements(expr: &mut Expression, state: &DedupPropState) {
173    match expr {
174        Expression::PropertyReference(nr) => {
175            if let Some(name) = state.get_mapping(nr) {
176                let ty = expr.ty();
177                *expr = Expression::ReadLocalVariable { name, ty };
178            }
179        }
180        Expression::BinaryExpression { lhs, rhs: _, op: '|' | '&' } => {
181            lhs.visit_mut(|sub| do_replacements(sub, state));
182        }
183        Expression::Condition { condition, .. } => {
184            condition.visit_mut(|sub| do_replacements(sub, state));
185        }
186        _ => expr.visit_mut(|sub| do_replacements(sub, state)),
187    }
188}