Skip to main content

i_slint_compiler/llr/optim_passes/
count_property_use.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//! This pass fills the Property::use_count
5//!
6//! It assumes that use_count of all properties is zero initially
7
8use crate::llr::{
9    Animation, BindingExpression, CompilationUnit, EvaluationContext, Expression,
10    LocalMemberReference, MemberReference, ParentScope,
11};
12
13pub fn count_property_use(root: &CompilationUnit) {
14    // Visit the root properties that are used.
15    // 1. the public properties
16    for c in &root.public_components {
17        let root_ctx = EvaluationContext::new_sub_component(root, c.item_tree.root, (), None);
18        for p in c.public_properties.values() {
19            // A public function can be called from native code, so it is a root.
20            visit_property(&p.prop, &root_ctx);
21        }
22    }
23    for (idx, g) in root.globals.iter_enumerated().filter(|(_, g)| g.exported) {
24        let ctx = EvaluationContext::new_global(root, idx, ());
25        for p in g.public_properties.values() {
26            visit_property(&p.prop, &ctx);
27        }
28    }
29
30    root.for_each_sub_components(&mut |_, sc, ctx| {
31        // 2. the native items and bindings of properties
32        for (_, expr) in &sc.property_init {
33            let c = expr.use_count.get();
34            expr.use_count.set(c + 1);
35            if c == 0 {
36                visit_binding_expression(expr, ctx)
37            }
38        }
39        // 3. the init code
40        for expr in sc.pre_init_code.iter().chain(&sc.init_code) {
41            expr.borrow().visit_property_references(ctx, &mut visit_property);
42        }
43        // 4. the models
44        for (idx, r) in sc.repeated.iter_enumerated() {
45            r.model.borrow().visit_property_references(ctx, &mut visit_property);
46            if let Some(lv) = &r.listview {
47                visit_property(&lv.content_y, ctx);
48                if let Some(content_height) = &lv.content_height {
49                    visit_property(content_height, ctx);
50                }
51                if let Some(content_width) = &lv.content_width {
52                    visit_property(content_width, ctx);
53                }
54                visit_property(&lv.listview_width, ctx);
55                visit_property(&lv.listview_height, ctx);
56
57                let parent_ctx = ParentScope::new(ctx, Some(idx));
58                let rep_ctx = EvaluationContext::new_sub_component(
59                    root,
60                    r.sub_tree.root,
61                    (),
62                    Some(&parent_ctx),
63                );
64                visit_property(&lv.prop_y, &rep_ctx);
65                visit_property(&lv.prop_height, &rep_ctx);
66            }
67            for idx in r.data_prop.iter().chain(r.index_prop.iter()) {
68                // prevent optimizing model properties
69                let p = &root.sub_components[r.sub_tree.root].properties[*idx];
70                p.use_count.set(2);
71            }
72            if let Some(z_idx) = &r.dynamic_z {
73                let parent_ctx = ParentScope::new(ctx, Some(idx));
74                let rep_ctx = EvaluationContext::new_sub_component(
75                    root,
76                    r.sub_tree.root,
77                    (),
78                    Some(&parent_ctx),
79                );
80                visit_property(z_idx, &rep_ctx);
81            }
82        }
83
84        // 5. the layout info
85        sc.layout_info_h.borrow().visit_property_references(ctx, &mut visit_property);
86        sc.layout_info_v.borrow().visit_property_references(ctx, &mut visit_property);
87        if let Some(e) = &sc.grid_layout_input_for_repeated {
88            e.borrow().visit_property_references(ctx, &mut visit_property);
89        }
90        if let Some(e) = &sc.flexbox_layout_item_info_for_repeated {
91            e.borrow().visit_property_references(ctx, &mut visit_property);
92        }
93        if let Some((_, e)) = &sc.cross_axis_self_alignment_for_repeated {
94            e.borrow().visit_property_references(ctx, &mut visit_property);
95        }
96        if let Some((_, e)) = &sc.layout_order_for_repeated {
97            e.borrow().visit_property_references(ctx, &mut visit_property);
98        }
99        if let Some(e) = &sc.layout_info_v_constrained_for_repeated {
100            e.borrow().visit_property_references(ctx, &mut visit_property);
101        }
102        if let Some(e) = &sc.layout_info_v_at_cross_width_for_repeated {
103            e.borrow().visit_property_references(ctx, &mut visit_property);
104        }
105        if let Some(e) = &sc.grid_row_child_cross_width {
106            e.borrow().visit_property_references(ctx, &mut visit_property);
107        }
108        for child in &sc.grid_layout_children {
109            child.layout_info_h.borrow().visit_property_references(ctx, &mut visit_property);
110            child.layout_info_v.borrow().visit_property_references(ctx, &mut visit_property);
111        }
112
113        // 6. accessibility props and geometries
114        for b in sc.accessible_prop.values() {
115            b.borrow().visit_property_references(ctx, &mut visit_property)
116        }
117        for i in sc.geometries.iter().filter_map(Option::as_ref) {
118            i.borrow().visit_property_references(ctx, &mut visit_property)
119        }
120
121        // 7. aliases (if they were not optimized, they are probably used)
122        for twb in &sc.two_way_bindings {
123            visit_property(&twb.prop1.clone().into(), ctx);
124            visit_property(&twb.prop2, ctx);
125            if let &Some(p) = &twb.is_model {
126                let mut idx_prop = twb.prop2.clone();
127                let MemberReference::Relative { local_reference, .. } = &mut idx_prop else {
128                    unreachable!()
129                };
130                local_reference.reference = p.into();
131                visit_property(&idx_prop, ctx);
132            }
133        }
134
135        // 8. animations (`animate x { … }`): `remove_unused` keeps and remaps these,
136        // so the properties they read must be counted too.
137        for anim in sc.animations.values() {
138            anim.visit_property_references(ctx, &mut visit_property);
139        }
140
141        // Function bodies are visited on demand from visit_property when a call to
142        // them is found, so that an unreachable function keeps nothing alive.
143
144        // 9. change callbacks
145        for (p, e) in &sc.change_callbacks {
146            visit_property(p, ctx);
147            e.borrow().visit_property_references(ctx, &mut visit_property);
148        }
149
150        // 10. popup x/y coordinates
151        for popup in &sc.popup_windows {
152            let parent_ctx = ParentScope::new(ctx, None);
153            let popup_ctx = EvaluationContext::new_sub_component(
154                root,
155                popup.item_tree.root,
156                (),
157                Some(&parent_ctx),
158            );
159            popup.position.borrow().visit_property_references(&popup_ctx, &mut visit_property)
160        }
161        // 11. timer
162        for timer in &sc.timers {
163            timer.interval.borrow().visit_property_references(ctx, &mut visit_property);
164            timer.running.borrow().visit_property_references(ctx, &mut visit_property);
165            timer.triggered.borrow().visit_property_references(ctx, &mut visit_property);
166        }
167    });
168
169    for (idx, g) in root.globals.iter_enumerated() {
170        let ctx = EvaluationContext::new_global(root, idx, ());
171
172        for (p, e) in &g.change_callbacks {
173            visit_property(&LocalMemberReference::from(*p).into(), &ctx);
174            e.borrow().visit_property_references(&ctx, &mut visit_property);
175        }
176    }
177
178    if let Some(p) = &root.popup_menu {
179        let ctx = EvaluationContext::new_sub_component(root, p.item_tree.root, (), None);
180        visit_property(&p.entries, &ctx);
181        visit_property(&p.sub_menu, &ctx);
182        visit_property(&p.activated, &ctx);
183    }
184
185    // The z-order expressions are evaluated on every children visit
186    root.for_each_z_order_expression(&mut |e, ctx| {
187        e.borrow().visit_property_references(ctx, &mut visit_property)
188    });
189
190    clean_unused_bindings(root);
191}
192
193fn visit_property(pr: &MemberReference, ctx: &EvaluationContext) {
194    let p_info = ctx.property_info(pr);
195    if let Some(use_count) = &p_info.use_count {
196        use_count.set(use_count.get() + 1);
197    }
198    if let Some((binding, map)) = &p_info.binding {
199        let c = binding.use_count.get();
200        binding.use_count.set(c + 1);
201        if c == 0 {
202            let ctx2 = map.map_context(ctx);
203            visit_binding_expression(binding, &ctx2);
204        }
205    }
206    // A reference to a function is a call: count it and, the first time, visit
207    // its body so an unreachable function doesn't keep its property reads alive.
208    if let Some((function, map)) = ctx.function_info(pr) {
209        let c = function.use_count.get();
210        function.use_count.set(c + 1);
211        if c == 0 {
212            let ctx2 = map.map_context(ctx);
213            function.code.borrow().visit_property_references(&ctx2, &mut visit_property);
214        }
215    }
216}
217
218fn visit_binding_expression(binding: &BindingExpression, ctx: &EvaluationContext) {
219    binding.expression.borrow().visit_property_references(ctx, &mut visit_property);
220    match &binding.animation {
221        Some(Animation::Static(e) | Animation::Transition(e)) => {
222            e.visit_property_references(ctx, &mut visit_property);
223        }
224        None => (),
225    }
226}
227
228/// Bindings which have a use_count of zero can be cleared so that we won't ever visit them later.
229fn clean_unused_bindings(root: &CompilationUnit) {
230    root.for_each_sub_components(&mut |_, sc, _| {
231        for (_, e) in &sc.property_init {
232            if e.use_count.get() == 0 {
233                e.expression.replace(Expression::CodeBlock(Vec::new()));
234            }
235        }
236    });
237    for g in &root.globals {
238        for e in g.init_values.values() {
239            if e.use_count.get() == 0 {
240                e.expression.replace(Expression::CodeBlock(Vec::new()));
241            }
242        }
243    }
244}