slint/internal/compiler/passes/deduplicate_property_read.rs
2022-05-31 14:55:09 +02:00

174 lines
6.1 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! Do not read twice the same property, store in a local variable instead
use crate::expression_tree::*;
use crate::langtype::Type;
use crate::object_tree::*;
use std::cell::RefCell;
use std::collections::HashMap;
pub fn deduplicate_property_read(component: &Component) {
visit_all_expressions(component, |expr, ty| {
if matches!(ty(), Type::Callback { .. }) {
// Callback handler can't be optimizes because they can have side effect.
// But that's fine as they also do not register dependencies
return;
}
process_expression(expr, &DedupPropState::default());
});
}
#[derive(Default)]
struct PropertyReadCounts {
counts: HashMap<NamedReference, usize>,
/// If at least one element of the map has duplicates
has_duplicate: bool,
/// if there is an assignment of a property we currently disable this optimization
has_set: bool,
}
#[derive(Default)]
struct DedupPropState<'a> {
parent_state: Option<&'a DedupPropState<'a>>,
counts: RefCell<PropertyReadCounts>,
}
impl<'a> DedupPropState<'a> {
fn add(&self, nr: &NamedReference) {
if self.parent_state.map_or(false, |pc| pc.add_from_children(nr)) {
return;
}
let mut use_counts = self.counts.borrow_mut();
let use_counts = &mut *use_counts;
let has_duplicate = &mut use_counts.has_duplicate;
use_counts
.counts
.entry(nr.clone())
.and_modify(|c| {
if *c == 1 {
*has_duplicate = true;
}
*c += 1
})
.or_insert(1);
}
fn add_from_children(&self, nr: &NamedReference) -> bool {
if self.parent_state.map_or(false, |pc| pc.add_from_children(nr)) {
return true;
}
let mut use_counts = self.counts.borrow_mut();
let use_counts = &mut *use_counts;
if let Some(c) = use_counts.counts.get_mut(nr) {
if *c == 1 {
use_counts.has_duplicate = true;
}
*c += 1;
true
} else {
false
}
}
fn get_mapping(&self, nr: &NamedReference) -> Option<String> {
self.parent_state.and_then(|pr| pr.get_mapping(nr)).or_else(|| {
if self.counts.borrow().counts.get(nr).map_or(false, |c| *c > 1) {
Some(format!("tmp_{}_{}", nr.element().borrow().id, nr.name()))
} else {
None
}
})
}
}
fn process_expression(expr: &mut Expression, old_state: &DedupPropState) {
if old_state.counts.borrow().has_set {
return;
}
let new_state = DedupPropState { parent_state: Some(old_state), ..DedupPropState::default() };
collect_unconditional_read_count(expr, &new_state);
process_conditional_expressions(expr, &new_state);
if new_state.counts.borrow().has_set {
old_state.counts.borrow_mut().has_set = true;
return;
}
do_replacements(expr, &new_state);
if new_state.counts.borrow().has_duplicate {
let mut stores = vec![];
for (nr, count) in &new_state.counts.borrow().counts {
if *count > 1 {
let new_name = new_state.get_mapping(nr).unwrap();
stores.push(Expression::StoreLocalVariable {
name: new_name,
value: Box::new(Expression::PropertyReference(nr.clone())),
});
}
}
stores.push(std::mem::take(expr));
*expr = Expression::CodeBlock(stores);
}
}
// Collect all use of variable and their count, only in non conditional expression
fn collect_unconditional_read_count(expr: &Expression, result: &DedupPropState) {
match expr {
Expression::PropertyReference(nr) => {
result.add(nr);
}
//Expression::RepeaterIndexReference { element } => {}
//Expression::RepeaterModelReference { element } => {}
Expression::BinaryExpression { lhs, rhs: _, op } if matches!(op, '|' | '&') => {
lhs.visit(|sub| collect_unconditional_read_count(sub, result))
}
Expression::Condition { condition, .. } => {
condition.visit(|sub| collect_unconditional_read_count(sub, result))
}
Expression::SelfAssignment { .. } => {
result.counts.borrow_mut().has_set = true;
return;
}
_ => expr.visit(|sub| collect_unconditional_read_count(sub, result)),
}
}
fn process_conditional_expressions(expr: &mut Expression, state: &DedupPropState) {
if state.counts.borrow().has_set {
return;
}
match expr {
Expression::BinaryExpression { lhs, rhs, op } if matches!(op, '|' | '&') => {
lhs.visit_mut(|sub| process_conditional_expressions(sub, state));
process_expression(rhs, state);
}
Expression::Condition { condition, true_expr, false_expr } => {
condition.visit_mut(|sub| process_conditional_expressions(sub, state));
process_expression(true_expr, state);
process_expression(false_expr, state);
}
Expression::SelfAssignment { .. } => {
state.counts.borrow_mut().has_set = true;
return;
}
_ => expr.visit_mut(|sub| process_conditional_expressions(sub, state)),
}
}
fn do_replacements(expr: &mut Expression, state: &DedupPropState) {
match expr {
Expression::PropertyReference(nr) => {
if let Some(name) = state.get_mapping(nr) {
let ty = expr.ty();
*expr = Expression::ReadLocalVariable { name, ty };
}
}
Expression::BinaryExpression { lhs, rhs: _, op } if matches!(op, '|' | '&') => {
lhs.visit_mut(|sub| do_replacements(sub, state));
}
Expression::Condition { condition, .. } => {
condition.visit_mut(|sub| do_replacements(sub, state));
}
_ => expr.visit_mut(|sub| do_replacements(sub, state)),
}
}