slint/internal/compiler/passes/resolve_native_classes.rs
Milian Wolff 0f6c3a4fd7 Use SmolStr in more places of the compiler infrastructure
This removes a lot of allocations and speeds up the compiler step
a bit. Sadly, this patch is very invasive as it touches a lot of
files. That said, each individual hunk is pretty trivial.

For a non-trivial real-world example, the impact is significant,
we get rid of ~29% of all allocations and improve the runtime by
about 4.8% (measured until the viewer loop would start).

Before:
```
Benchmark 1: ./target/release/slint-viewer ../slint-perf/app.slint
  Time (mean ± σ):     664.2 ms ±   6.7 ms    [User: 589.2 ms, System: 74.0 ms]
  Range (min … max):   659.0 ms … 682.4 ms    10 runs

        allocations:            4886888
        temporary allocations:  857508
```

After:
```
Benchmark 1: ./target/release/slint-viewer ../slint-perf/app.slint
  Time (mean ± σ):     639.5 ms ±  17.8 ms    [User: 556.9 ms, System: 76.2 ms]
  Range (min … max):   621.4 ms … 666.5 ms    10 runs

        allocations:            3544318
        temporary allocations:  495685
```
2024-10-17 18:04:58 +02:00

166 lines
5.6 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! After inlining and moving declarations, all Element::base_type should be Type::BuiltinElement. This pass resolves them
//! to NativeClass and picking a variant that only contains the used properties.
use smol_str::SmolStr;
use std::collections::HashSet;
use std::rc::Rc;
use crate::langtype::{ElementType, NativeClass};
use crate::object_tree::{recurse_elem_including_sub_components, Component};
pub fn resolve_native_classes(component: &Component) {
recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
let new_native_class = {
let elem = elem.borrow();
let base_type = match &elem.base_type {
ElementType::Component(_) => {
// recurse_elem_including_sub_components will recurse into it
return;
}
ElementType::Builtin(b) => b,
ElementType::Native(_) => {
// already native
return;
}
ElementType::Global | ElementType::Error => panic!("This should not happen"),
};
let analysis = elem.property_analysis.borrow();
let native_properties_used: HashSet<_> = elem
.bindings
.keys()
.chain(analysis.iter().filter(|(_, v)| v.is_used()).map(|(k, _)| k))
.filter(|k| {
!elem.property_declarations.contains_key(*k)
&& base_type.as_ref().properties.contains_key(*k)
})
.collect();
select_minimal_class_based_on_property_usage(
&elem.base_type.as_builtin().native_class,
native_properties_used.into_iter(),
)
};
elem.borrow_mut().base_type = ElementType::Native(new_native_class);
})
}
fn lookup_property_distance(mut class: Rc<NativeClass>, name: &str) -> (usize, Rc<NativeClass>) {
let mut distance = 0;
loop {
if class.properties.contains_key(name)
|| (class.parent.is_none() && ["x", "y", "width", "height"].contains(&name))
{
return (distance, class);
}
distance += 1;
class = class.parent.as_ref().unwrap().clone();
}
}
fn select_minimal_class_based_on_property_usage<'a>(
class: &Rc<NativeClass>,
properties_used: impl Iterator<Item = &'a SmolStr>,
) -> Rc<NativeClass> {
let mut minimal_class = class.clone();
while let Some(class) = minimal_class.parent.clone() {
minimal_class = class;
}
let (_min_distance, minimal_class) = properties_used.fold(
(usize::MAX, minimal_class),
|(current_distance, current_class), prop_name| {
let (prop_distance, prop_class) = lookup_property_distance(class.clone(), prop_name);
if prop_distance < current_distance {
(prop_distance, prop_class)
} else {
(current_distance, current_class)
}
},
);
minimal_class
}
#[test]
fn test_select_minimal_class_based_on_property_usage() {
use crate::langtype::{BuiltinPropertyInfo, Type};
use smol_str::ToSmolStr;
let first = Rc::new(NativeClass::new_with_properties(
"first_class",
[("first_prop".to_smolstr(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(),
));
let mut second = NativeClass::new_with_properties(
"second_class",
[("second_prop".to_smolstr(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(),
);
second.parent = Some(first.clone());
let second = Rc::new(second);
let reduce_to_first =
select_minimal_class_based_on_property_usage(&second, ["first_prop".to_smolstr()].iter());
assert_eq!(reduce_to_first.class_name, first.class_name);
let reduce_to_second =
select_minimal_class_based_on_property_usage(&second, ["second_prop".to_smolstr()].iter());
assert_eq!(reduce_to_second.class_name, second.class_name);
let reduce_to_second = select_minimal_class_based_on_property_usage(
&second,
["first_prop".to_smolstr(), "second_prop".to_smolstr()].iter(),
);
assert_eq!(reduce_to_second.class_name, second.class_name);
}
#[test]
fn select_minimal_class() {
use smol_str::ToSmolStr;
let tr = crate::typeregister::TypeRegister::builtin();
let tr = tr.borrow();
let rect = tr.lookup_element("Rectangle").unwrap();
let rect = rect.as_builtin();
assert_eq!(
select_minimal_class_based_on_property_usage(
&rect.native_class,
["x".to_smolstr(), "width".to_smolstr()].iter()
)
.class_name,
"Empty",
);
assert_eq!(
select_minimal_class_based_on_property_usage(&rect.native_class, [].iter()).class_name,
"Empty",
);
assert_eq!(
select_minimal_class_based_on_property_usage(
&rect.native_class,
["border-width".to_smolstr()].iter()
)
.class_name,
"BasicBorderRectangle",
);
assert_eq!(
select_minimal_class_based_on_property_usage(
&rect.native_class,
["border-width".to_smolstr(), "x".to_smolstr()].iter()
)
.class_name,
"BasicBorderRectangle",
);
assert_eq!(
select_minimal_class_based_on_property_usage(
&rect.native_class,
["border-top-left-radius".to_smolstr(), "x".to_smolstr()].iter()
)
.class_name,
"BorderRectangle",
);
}