i_slint_compiler/llr/optim_passes/
inline_expressions.rs1use crate::expression_tree::{BuiltinFunction, ImageReference};
10use crate::langtype::Type;
11use crate::llr::{CompilationUnit, ContextMap, EvaluationContext, Expression};
12
13const PROPERTY_ACCESS_COST: isize = 1000;
14const ALLOC_COST: isize = 700;
15const ARRAY_INDEX_COST: isize = 500;
16const INLINE_THRESHOLD: isize = ALLOC_COST * 2 - 10;
19const INLINE_SINGLE_THRESHOLD: isize = ALLOC_COST * 10;
21
22fn expression_cost(exp: &Expression, ctx: &EvaluationContext) -> isize {
24 let mut cost = match exp {
25 Expression::StringLiteral(_) => ALLOC_COST,
26 Expression::NumberLiteral(_) => 0,
27 Expression::BoolLiteral(_) => 0,
28 Expression::KeysLiteral(_) => 0,
29 Expression::PropertyReference(_) => PROPERTY_ACCESS_COST,
30 Expression::FunctionParameterReference { .. } => return isize::MAX,
31 Expression::StoreLocalVariable { .. } => 0,
32 Expression::ReadLocalVariable { .. } => 1,
33 Expression::StructFieldAccess { .. } => 1,
34 Expression::ArrayIndex { .. } => ARRAY_INDEX_COST,
35 Expression::Cast { .. } => 0,
36 Expression::CodeBlock(_) => 0,
37 Expression::BuiltinFunctionCall { function, .. } => builtin_function_cost(function),
38 Expression::CallBackCall { callback, .. } => callback_cost(callback, ctx),
39 Expression::FunctionCall { function, .. } => callback_cost(function, ctx),
40 Expression::ItemMemberFunctionCall { function } => callback_cost(function, ctx),
41 Expression::ExtraBuiltinFunctionCall { .. } => return isize::MAX,
42 Expression::PropertyAssignment { .. } => return isize::MAX,
43 Expression::ModelDataAssignment { .. } => return isize::MAX,
44 Expression::ArrayIndexAssignment { .. } => return isize::MAX,
45 Expression::SliceIndexAssignment { .. } => return isize::MAX,
46 Expression::BinaryExpression { .. } => 1,
47 Expression::UnaryOp { .. } => 1,
48 Expression::ImageReference {
52 resource_ref: ImageReference::EmbeddedTexture { .. }, ..
53 } => 1,
54 Expression::ImageReference { .. } => return isize::MAX,
55 Expression::Condition { condition, true_expr, false_expr } => {
56 return expression_cost(condition, ctx)
57 .saturating_add(
58 expression_cost(true_expr, ctx).max(expression_cost(false_expr, ctx)),
59 )
60 .saturating_add(10);
61 }
62 Expression::Array { .. } => return isize::MAX,
65 Expression::Struct { .. } => 1,
66 Expression::EasingCurve(_) => 1,
67 Expression::MouseCursor(_) => 1,
68 Expression::LinearGradient { .. } => ALLOC_COST,
69 Expression::RadialGradient { .. } => ALLOC_COST,
70 Expression::ConicGradient { .. } => ALLOC_COST,
71 Expression::EnumerationValue(_) => 0,
72 Expression::LayoutCacheAccess { .. } => PROPERTY_ACCESS_COST,
73 Expression::GridRepeaterCacheAccess { .. } => PROPERTY_ACCESS_COST,
74 Expression::WithLayoutItemInfo { .. } => return isize::MAX,
75 Expression::WithFlexboxLayoutItemInfo { .. } => return isize::MAX,
76 Expression::SolveFlexboxLayoutWithMeasure { .. } => return isize::MAX,
77 Expression::BoxLayoutInfoOrthoWithMeasure { .. } => return isize::MAX,
78 Expression::FlexboxLayoutInfoCrossAxisWithMeasure { .. } => return isize::MAX,
79 Expression::WithGridInputData { .. } => return isize::MAX,
80 Expression::MinMax { .. } => 10,
81 Expression::EmptyComponentFactory => 10,
82 Expression::EmptyDataTransfer => 10,
83 Expression::TranslationReference { .. } => PROPERTY_ACCESS_COST + 2 * ALLOC_COST,
84 Expression::Closure { .. } => 0,
87 Expression::DebugHook { .. } => return isize::MAX,
89 };
90
91 exp.visit(|e| cost = cost.saturating_add(expression_cost(e, ctx)));
92
93 cost
94}
95
96fn callback_cost(_callback: &crate::llr::MemberReference, _ctx: &EvaluationContext) -> isize {
97 isize::MAX
99}
100
101fn builtin_function_cost(function: &BuiltinFunction) -> isize {
102 match function {
103 BuiltinFunction::GetWindowScaleFactor => PROPERTY_ACCESS_COST,
104 BuiltinFunction::GetWindowDefaultFontSize => PROPERTY_ACCESS_COST,
105 BuiltinFunction::AnimationTick => PROPERTY_ACCESS_COST,
106 BuiltinFunction::DecimalSeparator => PROPERTY_ACCESS_COST,
107 BuiltinFunction::DefaultWindowTitle => PROPERTY_ACCESS_COST,
108 BuiltinFunction::Debug => isize::MAX,
109 BuiltinFunction::Mod => 10,
110 BuiltinFunction::Round => 10,
111 BuiltinFunction::Ceil => 10,
112 BuiltinFunction::Floor => 10,
113 BuiltinFunction::Abs => 10,
114 BuiltinFunction::Sqrt => 10,
115 BuiltinFunction::Cos => 10,
116 BuiltinFunction::Sin => 10,
117 BuiltinFunction::Tan => 10,
118 BuiltinFunction::ACos => 10,
119 BuiltinFunction::ASin => 10,
120 BuiltinFunction::ATan => 10,
121 BuiltinFunction::ATan2 => 10,
122 BuiltinFunction::Log => 10,
123 BuiltinFunction::Ln => 10,
124 BuiltinFunction::Pow => 10,
125 BuiltinFunction::Exp => 10,
126 BuiltinFunction::ToFixed => ALLOC_COST,
127 BuiltinFunction::ToPrecision => ALLOC_COST,
128 BuiltinFunction::ToStringUnlocalized => ALLOC_COST,
129 BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => isize::MAX,
130 BuiltinFunction::ShowPopupWindow
131 | BuiltinFunction::ClosePopupWindow
132 | BuiltinFunction::ShowPopupMenu
133 | BuiltinFunction::ShowPopupMenuInternal => isize::MAX,
134 BuiltinFunction::SetSelectionOffsets => isize::MAX,
135 BuiltinFunction::ItemFontMetrics => PROPERTY_ACCESS_COST,
136 BuiltinFunction::StringToFloat => 50,
137 BuiltinFunction::StringIsFloat => 50,
138 BuiltinFunction::StringIsEmpty => 50,
139 BuiltinFunction::StringCharacterCount => 50,
140 BuiltinFunction::StringStartsWith | BuiltinFunction::StringEndsWith => 50,
141 BuiltinFunction::StringToLowercase | BuiltinFunction::StringToUppercase => ALLOC_COST,
142 BuiltinFunction::StringReplaceAll => ALLOC_COST,
143 BuiltinFunction::KeysToString => ALLOC_COST,
144 BuiltinFunction::ColorRgbaStruct => 50,
145 BuiltinFunction::ColorHsvaStruct => 50,
146 BuiltinFunction::ColorOklchStruct => 50,
147 BuiltinFunction::ColorBrighter => 50,
148 BuiltinFunction::ColorDarker => 50,
149 BuiltinFunction::ColorTransparentize => 50,
150 BuiltinFunction::ColorMix => 50,
151 BuiltinFunction::ColorWithAlpha => 50,
152 BuiltinFunction::ImageSize => 50,
153 BuiltinFunction::ArrayLength => 50,
154 BuiltinFunction::ArrayPush
155 | BuiltinFunction::ArrayRemove
156 | BuiltinFunction::ArrayInsert => ALLOC_COST,
157 BuiltinFunction::Rgb => 50,
158 BuiltinFunction::Hsv => 50,
159 BuiltinFunction::Oklch => 50,
160 BuiltinFunction::ImplicitLayoutInfo(_) => isize::MAX,
161 BuiltinFunction::ItemAbsolutePosition => isize::MAX,
162 BuiltinFunction::RegisterCustomFontByPath => isize::MAX,
163 BuiltinFunction::RegisterCustomFontByMemory => isize::MAX,
164 BuiltinFunction::RegisterBitmapFont => isize::MAX,
165 BuiltinFunction::ColorScheme => PROPERTY_ACCESS_COST,
166 BuiltinFunction::AccentColor => PROPERTY_ACCESS_COST,
167 BuiltinFunction::SupportsNativeMenuBar => 10,
168 BuiltinFunction::SetupMenuBar => isize::MAX,
169 BuiltinFunction::SetupSystemTrayIcon => isize::MAX,
170 BuiltinFunction::MonthDayCount => isize::MAX,
171 BuiltinFunction::MonthOffset => isize::MAX,
172 BuiltinFunction::FormatDate => isize::MAX,
173 BuiltinFunction::DateNow => isize::MAX,
174 BuiltinFunction::ValidDate => isize::MAX,
175 BuiltinFunction::ParseDate => isize::MAX,
176 BuiltinFunction::SetTextInputFocused => PROPERTY_ACCESS_COST,
177 BuiltinFunction::TextInputFocused => PROPERTY_ACCESS_COST,
178 BuiltinFunction::Translate => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
179 BuiltinFunction::Use24HourFormat => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
180 BuiltinFunction::UpdateTimers => 10,
181 BuiltinFunction::DetectOperatingSystem => 10,
182 BuiltinFunction::StartTimer => 10,
183 BuiltinFunction::StopTimer => 10,
184 BuiltinFunction::RestartTimer => 10,
185 BuiltinFunction::ParseMarkdown => isize::MAX,
186 BuiltinFunction::StringToStyledText => ALLOC_COST,
187 BuiltinFunction::ColorToStyledText => ALLOC_COST,
188 BuiltinFunction::OpenUrl => isize::MAX,
189 BuiltinFunction::MacosBringAllWindowsToFront => isize::MAX,
190 BuiltinFunction::PathPointAt => isize::MAX,
191 BuiltinFunction::PathAngleAt => isize::MAX,
192 BuiltinFunction::ArrayAny | BuiltinFunction::ArrayAll | BuiltinFunction::ArrayFindIndex => {
194 isize::MAX
195 }
196 }
197}
198
199pub fn inline_simple_expressions(root: &CompilationUnit) {
200 let mut counter = 0usize;
202 root.for_each_expression(&mut |e, ctx| {
203 inline_simple_expressions_in_expression(&mut e.borrow_mut(), ctx, &mut counter)
204 })
205}
206
207fn inline_simple_expressions_in_expression(
208 expr: &mut Expression,
209 ctx: &EvaluationContext,
210 counter: &mut usize,
211) {
212 if let Expression::FunctionCall { function, .. } = expr {
214 let inline_target = ctx.function_info(function).and_then(|(f, map)| {
215 if f.use_count.get() != 1 || !body_is_inline_safe(&f.code.borrow(), &map) {
216 return None;
217 }
218 f.use_count.set(0);
219 adjust_use_count(&f.code.borrow(), &map.map_context(ctx), -1);
223 let body = f.code.replace(Expression::CodeBlock(Vec::new()));
226 Some((body, f.args.clone(), map))
227 });
228 if let Some((mut body, arg_types, map)) = inline_target {
229 let Expression::FunctionCall { arguments, .. } =
230 std::mem::replace(expr, Expression::CodeBlock(Vec::new()))
231 else {
232 unreachable!()
233 };
234 let uid = *counter;
235 *counter += 1;
236 map.map_expression(&mut body);
237 adjust_use_count(&body, ctx, 1);
239 substitute_function_parameters(&mut body, uid, &arg_types);
240 *expr = if arguments.is_empty() {
241 body
242 } else {
243 let mut stmts = arguments
244 .into_iter()
245 .enumerate()
246 .map(|(i, a)| Expression::StoreLocalVariable {
247 name: function_arg_local_name(uid, i),
248 value: Box::new(a),
249 })
250 .collect::<Vec<_>>();
251 stmts.push(body);
252 Expression::CodeBlock(stmts)
253 };
254 inline_simple_expressions_in_expression(expr, ctx, counter);
257 return;
258 }
259 }
260
261 if let Expression::PropertyReference(prop) = expr {
262 let prop_info = ctx.property_info(prop);
263 if prop_info.analysis.as_ref().is_some_and(|a| !a.is_set && !a.is_set_externally) {
264 if let Some((binding, map)) = prop_info.binding {
265 if binding.animation.is_none() && binding.kind != super::super::BindingKind::State {
266 let mapped_ctx = map.map_context(ctx);
267 let cost = expression_cost(&binding.expression.borrow(), &mapped_ctx);
268 let use_count = binding.use_count.get();
269 debug_assert!(
270 use_count > 0,
271 "We use a property and its count is zero: {}",
272 crate::llr::pretty_print::DisplayPropertyRef(prop, ctx)
273 );
274 if cost <= INLINE_THRESHOLD
275 || (use_count == 1 && cost <= INLINE_SINGLE_THRESHOLD)
276 {
277 *expr = binding.expression.borrow().clone();
279 map.map_expression(expr);
280 binding.use_count.set(use_count - 1);
282 if let Some(use_count) = prop_info.use_count {
283 use_count.set(use_count.get() - 1);
284 }
285 adjust_use_count(expr, ctx, 1);
286 if use_count == 1 {
287 adjust_use_count(&binding.expression.borrow(), &mapped_ctx, -1);
288 binding.expression.replace(Expression::CodeBlock(Vec::new()));
289 }
290 }
291 }
292 } else if let Some(use_count) = prop_info.use_count
293 && let Some(e) = Expression::default_value_for_type(&prop_info.ty)
294 {
295 use_count.set(use_count.get() - 1);
296 *expr = e;
297 }
298 }
299 };
300
301 expr.visit_mut(|e| inline_simple_expressions_in_expression(e, ctx, counter));
302}
303
304fn body_is_inline_safe(exp: &Expression, map: &ContextMap) -> bool {
315 let mut params = std::collections::HashSet::new();
316 let mut safe = true;
317 exp.visit_recursive(&mut |e| match e {
318 Expression::FunctionParameterReference { index } => safe &= params.insert(*index),
319 Expression::BuiltinFunctionCall { function, .. } => {
320 safe &= match function {
321 BuiltinFunction::ShowPopupMenu
322 | BuiltinFunction::ShowPopupMenuInternal
323 | BuiltinFunction::UpdateTimers => matches!(map, ContextMap::Identity),
324 _ => true,
325 }
326 }
327 _ => {}
328 });
329 safe
330}
331
332fn function_arg_local_name(uid: usize, index: usize) -> smol_str::SmolStr {
334 smol_str::format_smolstr!("inlined_fn_arg_{uid}_{index}")
335}
336
337fn substitute_function_parameters(expr: &mut Expression, uid: usize, arg_types: &[Type]) {
339 expr.visit_recursive_mut(&mut |e| {
340 if let Expression::FunctionParameterReference { index } = e {
341 let index = *index;
342 *e = Expression::ReadLocalVariable {
343 name: function_arg_local_name(uid, index),
344 ty: arg_types[index].clone(),
345 };
346 }
347 });
348}
349
350fn adjust_use_count(expr: &Expression, ctx: &EvaluationContext, adjust: isize) {
351 expr.visit_property_references(ctx, &mut |p, ctx| {
352 let prop_info = ctx.property_info(p);
353 if let Some(use_count) = prop_info.use_count {
354 use_count.set(use_count.get().checked_add_signed(adjust).unwrap());
355 }
356 if let Some((binding, _)) = prop_info.binding {
357 let use_count = binding.use_count.get().checked_add_signed(adjust).unwrap();
358 binding.use_count.set(use_count);
359 }
360 });
361}