1use std::collections::HashMap;
2
3use crate::arena::Arena;
4use crate::ast::stmt::{Expr, Literal, Stmt, TypeExpr, Block, StringPart};
5use crate::intern::{Interner, Symbol};
6use std::collections::HashSet;
7
8use super::bta::{self, BindingTime, Division};
9use super::effects::EffectEnv;
10
11type SpecKey = (Symbol, Vec<Option<Literal>>);
12
13struct FuncInfo<'a> {
14 name: Symbol,
15 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
16 body: Block<'a>,
17 generics: Vec<Symbol>,
18 return_type: Option<&'a TypeExpr<'a>>,
19 opt_flags: crate::optimization::OptimizationConfig,
20}
21
22struct SpecRegistry<'a> {
23 cache: HashMap<SpecKey, Symbol>,
24 new_funcs: Vec<Stmt<'a>>,
25 variant_count: HashMap<Symbol, usize>,
26 history: Vec<SpecKey>,
27 bta_cache: Option<super::bta::BtaCache>,
28}
29
30impl<'a> SpecRegistry<'a> {
31 fn new() -> Self {
32 SpecRegistry {
33 cache: HashMap::new(),
34 new_funcs: Vec::new(),
35 variant_count: HashMap::new(),
36 history: Vec::new(),
37 bta_cache: None,
38 }
39 }
40}
41
42fn spec_key_embeds(earlier: &SpecKey, later: &SpecKey) -> bool {
43 if earlier.0 != later.0 {
44 return false;
45 }
46 let ea = &earlier.1;
47 let la = &later.1;
48 if ea.len() != la.len() {
49 return false;
50 }
51 if ea == la {
52 return false; }
54 let mut strict = false;
55 for (e, l) in ea.iter().zip(la.iter()) {
56 match (e, l) {
57 (None, _) => {} (Some(_), None) => return false, (Some(e_lit), Some(l_lit)) => {
60 if !literal_embeds(e_lit, l_lit) {
61 return false;
62 }
63 if e_lit != l_lit {
64 strict = true;
65 }
66 }
67 }
68 }
69 strict
70}
71
72fn literal_embeds(a: &Literal, b: &Literal) -> bool {
73 match (a, b) {
74 (Literal::Number(x), Literal::Number(y)) => x.abs() <= y.abs(),
75 (Literal::Float(x), Literal::Float(y)) => x.abs() <= y.abs(),
76 (Literal::Boolean(_), Literal::Boolean(_)) => true,
77 (Literal::Text(x), Literal::Text(y)) => x.index() <= y.index(),
78 (Literal::Nothing, Literal::Nothing) => true,
79 _ => a == b,
80 }
81}
82
83fn collect_func_defs<'a>(stmts: &[Stmt<'a>]) -> HashMap<Symbol, FuncInfo<'a>> {
84 let mut defs = HashMap::new();
85 for stmt in stmts {
86 if let Stmt::FunctionDef { name, params, body, generics, return_type, is_native, opt_flags, .. } = stmt {
87 if !is_native {
88 defs.insert(*name, FuncInfo {
89 name: *name,
90 params: params.clone(),
91 body,
92 generics: generics.clone(),
93 return_type: *return_type,
94 opt_flags: opt_flags.clone(),
95 });
96 }
97 }
98 }
99 defs
100}
101
102fn body_has_io(stmts: &[Stmt]) -> bool {
103 for stmt in stmts {
104 if stmt_has_io(stmt) {
105 return true;
106 }
107 }
108 false
109}
110
111fn stmt_has_io(stmt: &Stmt) -> bool {
117 match stmt {
118 Stmt::Show { .. }
119 | Stmt::WriteFile { .. }
120 | Stmt::SendMessage { .. }
121 | Stmt::StreamMessage { .. }
122 | Stmt::Sleep { .. }
123 | Stmt::SendPipe { .. }
124 | Stmt::TrySendPipe { .. }
125 | Stmt::ReceivePipe { .. }
126 | Stmt::TryReceivePipe { .. }
127 | Stmt::ReadFrom { .. }
128 | Stmt::Check { .. } => true,
129 Stmt::LaunchTask { .. }
131 | Stmt::LaunchTaskWithHandle { .. }
132 | Stmt::CreatePipe { .. }
133 | Stmt::StopTask { .. }
134 | Stmt::Spawn { .. }
135 | Stmt::Listen { .. }
136 | Stmt::ConnectTo { .. }
137 | Stmt::LetPeerAgent { .. }
138 | Stmt::AwaitMessage { .. }
139 | Stmt::Sync { .. }
140 | Stmt::Mount { .. } => true,
141 Stmt::IncreaseCrdt { .. }
142 | Stmt::DecreaseCrdt { .. }
143 | Stmt::MergeCrdt { .. }
144 | Stmt::AppendToSequence { .. }
145 | Stmt::ResolveConflict { .. } => true,
146 Stmt::Select { .. } | Stmt::Concurrent { .. } | Stmt::Parallel { .. } => true,
149 Stmt::If { then_block, else_block, .. } => {
150 body_has_io(then_block)
151 || else_block.map_or(false, |eb| body_has_io(eb))
152 }
153 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => body_has_io(body),
154 Stmt::Zone { body, .. } => body_has_io(body),
155 Stmt::Inspect { arms, .. } => {
156 arms.iter().any(|arm| body_has_io(arm.body))
157 }
158 _ => false,
159 }
160}
161
162fn body_has_escape(stmts: &[Stmt]) -> bool {
163 for stmt in stmts {
164 match stmt {
165 Stmt::Escape { .. } => return true,
166 Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
167 if expr_has_escape(value) {
168 return true;
169 }
170 }
171 Stmt::If { then_block, else_block, .. } => {
172 if body_has_escape(then_block) {
173 return true;
174 }
175 if let Some(eb) = else_block {
176 if body_has_escape(eb) {
177 return true;
178 }
179 }
180 }
181 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
182 if body_has_escape(body) {
183 return true;
184 }
185 }
186 Stmt::Return { value } => {
187 if let Some(v) = value {
188 if expr_has_escape(v) {
189 return true;
190 }
191 }
192 }
193 _ => {}
194 }
195 }
196 false
197}
198
199fn expr_has_escape(expr: &Expr) -> bool {
200 match expr {
201 Expr::Escape { .. } => true,
202 _ => false,
203 }
204}
205
206fn classify_arg<'a>(arg: &Expr<'a>, division: Option<&Division>) -> Option<Literal> {
207 match arg {
208 Expr::Literal(lit) => Some(lit.clone()),
209 _ => {
210 if let Some(div) = division {
211 match bta::analyze_expr(arg, div) {
212 BindingTime::Static(lit) => Some(lit),
213 BindingTime::Dynamic => None,
214 }
215 } else {
216 None
217 }
218 }
219 }
220}
221
222fn compute_spec_key<'a>(function: Symbol, args: &[&'a Expr<'a>], division: Option<&Division>) -> (SpecKey, Vec<Option<Literal>>) {
223 let arg_classifications: Vec<Option<Literal>> = args.iter()
224 .map(|a| classify_arg(a, division))
225 .collect();
226 let key = (function, arg_classifications.clone());
227 (key, arg_classifications)
228}
229
230fn is_mixed(classifications: &[Option<Literal>]) -> bool {
231 let has_static = classifications.iter().any(|c| c.is_some());
232 let has_dynamic = classifications.iter().any(|c| c.is_none());
233 has_static && has_dynamic
234}
235
236fn make_spec_name(interner: &mut Interner, func_name: Symbol, classifications: &[Option<Literal>]) -> Symbol {
237 let base = interner.resolve(func_name);
238 let mut name = base.to_string();
239 for (i, c) in classifications.iter().enumerate() {
240 if let Some(lit) = c {
241 name.push_str(&format!("_s{}_{}", i, literal_to_name_part(lit)));
242 }
243 }
244 interner.intern(&name)
245}
246
247fn literal_to_name_part(lit: &Literal) -> String {
248 match lit {
249 Literal::Number(n) => format!("{}", n),
250 Literal::Float(f) => format!("{}", f).replace('.', "d").replace('-', "n"),
251 Literal::Boolean(b) => format!("{}", b),
252 Literal::Text(s) => format!("t{:x}", s.index()),
253 Literal::Nothing => "nothing".to_string(),
254 _ => "x".to_string(),
255 }
256}
257
258fn substitute_expr<'a>(
259 expr: &'a Expr<'a>,
260 substitutions: &HashMap<Symbol, &'a Expr<'a>>,
261 expr_arena: &'a Arena<Expr<'a>>,
262) -> &'a Expr<'a> {
263 match expr {
264 Expr::Identifier(sym) => {
265 if let Some(replacement) = substitutions.get(sym) {
266 replacement
267 } else {
268 expr
269 }
270 }
271 Expr::BinaryOp { op, left, right } => {
272 let new_left = substitute_expr(left, substitutions, expr_arena);
273 let new_right = substitute_expr(right, substitutions, expr_arena);
274 if std::ptr::eq(new_left as *const _, *left as *const _)
275 && std::ptr::eq(new_right as *const _, *right as *const _) {
276 expr
277 } else {
278 expr_arena.alloc(Expr::BinaryOp {
279 op: *op,
280 left: new_left,
281 right: new_right,
282 })
283 }
284 }
285 Expr::Not { operand } => {
286 let new_operand = substitute_expr(operand, substitutions, expr_arena);
287 if std::ptr::eq(new_operand as *const _, *operand as *const _) {
288 expr
289 } else {
290 expr_arena.alloc(Expr::Not { operand: new_operand })
291 }
292 }
293 Expr::Call { function, args } => {
294 let new_args: Vec<&'a Expr<'a>> = args.iter()
295 .map(|a| substitute_expr(a, substitutions, expr_arena))
296 .collect();
297 let changed = new_args.iter().zip(args.iter())
298 .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
299 if changed {
300 expr_arena.alloc(Expr::Call { function: *function, args: new_args })
301 } else {
302 expr
303 }
304 }
305 Expr::Index { collection, index } => {
306 let new_coll = substitute_expr(collection, substitutions, expr_arena);
307 let new_idx = substitute_expr(index, substitutions, expr_arena);
308 if std::ptr::eq(new_coll as *const _, *collection as *const _)
309 && std::ptr::eq(new_idx as *const _, *index as *const _) {
310 expr
311 } else {
312 expr_arena.alloc(Expr::Index { collection: new_coll, index: new_idx })
313 }
314 }
315 Expr::Length { collection } => {
316 let new_coll = substitute_expr(collection, substitutions, expr_arena);
317 if std::ptr::eq(new_coll as *const _, *collection as *const _) {
318 expr
319 } else {
320 expr_arena.alloc(Expr::Length { collection: new_coll })
321 }
322 }
323 Expr::Slice { collection, start, end } => {
324 let new_coll = substitute_expr(collection, substitutions, expr_arena);
325 let new_start = substitute_expr(start, substitutions, expr_arena);
326 let new_end = substitute_expr(end, substitutions, expr_arena);
327 if std::ptr::eq(new_coll as *const _, *collection as *const _)
328 && std::ptr::eq(new_start as *const _, *start as *const _)
329 && std::ptr::eq(new_end as *const _, *end as *const _) {
330 expr
331 } else {
332 expr_arena.alloc(Expr::Slice { collection: new_coll, start: new_start, end: new_end })
333 }
334 }
335 Expr::FieldAccess { object, field } => {
336 let new_obj = substitute_expr(object, substitutions, expr_arena);
337 if std::ptr::eq(new_obj as *const _, *object as *const _) {
338 expr
339 } else {
340 expr_arena.alloc(Expr::FieldAccess { object: new_obj, field: *field })
341 }
342 }
343 Expr::Contains { collection, value } => {
344 let new_coll = substitute_expr(collection, substitutions, expr_arena);
345 let new_val = substitute_expr(value, substitutions, expr_arena);
346 if std::ptr::eq(new_coll as *const _, *collection as *const _)
347 && std::ptr::eq(new_val as *const _, *value as *const _) {
348 expr
349 } else {
350 expr_arena.alloc(Expr::Contains { collection: new_coll, value: new_val })
351 }
352 }
353 Expr::NewVariant { enum_name, variant, fields } => {
354 let new_fields: Vec<(Symbol, &'a Expr<'a>)> = fields.iter()
355 .map(|(name, val)| (*name, substitute_expr(val, substitutions, expr_arena)))
356 .collect();
357 let changed = new_fields.iter().zip(fields.iter())
358 .any(|((_, new_v), (_, old_v))| !std::ptr::eq(*new_v as *const _, *old_v as *const _));
359 if changed {
360 expr_arena.alloc(Expr::NewVariant { enum_name: *enum_name, variant: *variant, fields: new_fields })
361 } else {
362 expr
363 }
364 }
365 Expr::New { type_name, type_args, init_fields } => {
366 let new_fields: Vec<(Symbol, &'a Expr<'a>)> = init_fields.iter()
367 .map(|(name, val)| (*name, substitute_expr(val, substitutions, expr_arena)))
368 .collect();
369 let changed = new_fields.iter().zip(init_fields.iter())
370 .any(|((_, new_v), (_, old_v))| !std::ptr::eq(*new_v as *const _, *old_v as *const _));
371 if changed {
372 expr_arena.alloc(Expr::New { type_name: *type_name, type_args: type_args.clone(), init_fields: new_fields })
373 } else {
374 expr
375 }
376 }
377 Expr::OptionSome { value } => {
378 let new_val = substitute_expr(value, substitutions, expr_arena);
379 if std::ptr::eq(new_val as *const _, *value as *const _) { expr }
380 else { expr_arena.alloc(Expr::OptionSome { value: new_val }) }
381 }
382 Expr::Copy { expr: inner } => {
383 let new_inner = substitute_expr(inner, substitutions, expr_arena);
384 if std::ptr::eq(new_inner as *const _, *inner as *const _) { expr }
385 else { expr_arena.alloc(Expr::Copy { expr: new_inner }) }
386 }
387 Expr::Give { value } => {
388 let new_val = substitute_expr(value, substitutions, expr_arena);
389 if std::ptr::eq(new_val as *const _, *value as *const _) { expr }
390 else { expr_arena.alloc(Expr::Give { value: new_val }) }
391 }
392 Expr::List(items) => {
393 let new_items: Vec<&'a Expr<'a>> = items.iter()
394 .map(|item| substitute_expr(item, substitutions, expr_arena))
395 .collect();
396 let changed = new_items.iter().zip(items.iter())
397 .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
398 if changed { expr_arena.alloc(Expr::List(new_items)) }
399 else { expr }
400 }
401 Expr::Tuple(items) => {
402 let new_items: Vec<&'a Expr<'a>> = items.iter()
403 .map(|item| substitute_expr(item, substitutions, expr_arena))
404 .collect();
405 let changed = new_items.iter().zip(items.iter())
406 .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
407 if changed { expr_arena.alloc(Expr::Tuple(new_items)) }
408 else { expr }
409 }
410 Expr::Range { start, end } => {
411 let new_start = substitute_expr(start, substitutions, expr_arena);
412 let new_end = substitute_expr(end, substitutions, expr_arena);
413 if std::ptr::eq(new_start as *const _, *start as *const _)
414 && std::ptr::eq(new_end as *const _, *end as *const _) { expr }
415 else { expr_arena.alloc(Expr::Range { start: new_start, end: new_end }) }
416 }
417 Expr::Union { left, right } => {
418 let new_left = substitute_expr(left, substitutions, expr_arena);
419 let new_right = substitute_expr(right, substitutions, expr_arena);
420 if std::ptr::eq(new_left as *const _, *left as *const _)
421 && std::ptr::eq(new_right as *const _, *right as *const _) { expr }
422 else { expr_arena.alloc(Expr::Union { left: new_left, right: new_right }) }
423 }
424 Expr::Intersection { left, right } => {
425 let new_left = substitute_expr(left, substitutions, expr_arena);
426 let new_right = substitute_expr(right, substitutions, expr_arena);
427 if std::ptr::eq(new_left as *const _, *left as *const _)
428 && std::ptr::eq(new_right as *const _, *right as *const _) { expr }
429 else { expr_arena.alloc(Expr::Intersection { left: new_left, right: new_right }) }
430 }
431 Expr::CallExpr { callee, args } => {
432 let new_callee = substitute_expr(callee, substitutions, expr_arena);
433 let new_args: Vec<&'a Expr<'a>> = args.iter()
434 .map(|a| substitute_expr(a, substitutions, expr_arena))
435 .collect();
436 let changed = !std::ptr::eq(new_callee as *const _, *callee as *const _)
437 || new_args.iter().zip(args.iter())
438 .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
439 if changed {
440 expr_arena.alloc(Expr::CallExpr { callee: new_callee, args: new_args })
441 } else {
442 expr
443 }
444 }
445 Expr::WithCapacity { value, capacity } => {
446 let new_val = substitute_expr(value, substitutions, expr_arena);
447 let new_cap = substitute_expr(capacity, substitutions, expr_arena);
448 if std::ptr::eq(new_val as *const _, *value as *const _)
449 && std::ptr::eq(new_cap as *const _, *capacity as *const _) { expr }
450 else { expr_arena.alloc(Expr::WithCapacity { value: new_val, capacity: new_cap }) }
451 }
452 Expr::InterpolatedString(parts) => {
453 let new_parts: Vec<StringPart<'a>> = parts.iter()
454 .map(|part| match part {
455 StringPart::Literal(_) => part.clone(),
456 StringPart::Expr { value, format_spec, debug } => {
457 let new_val = substitute_expr(value, substitutions, expr_arena);
458 if std::ptr::eq(new_val as *const _, *value as *const _) {
459 part.clone()
460 } else {
461 StringPart::Expr { value: new_val, format_spec: *format_spec, debug: *debug }
462 }
463 }
464 })
465 .collect();
466 expr_arena.alloc(Expr::InterpolatedString(new_parts))
467 }
468 _ => expr,
469 }
470}
471
472fn substitute_stmt<'a>(
473 stmt: &Stmt<'a>,
474 substitutions: &HashMap<Symbol, &'a Expr<'a>>,
475 expr_arena: &'a Arena<Expr<'a>>,
476 stmt_arena: &'a Arena<Stmt<'a>>,
477) -> Stmt<'a> {
478 match stmt {
479 Stmt::Let { var, value, mutable, ty } => Stmt::Let {
480 var: *var,
481 value: substitute_expr(value, substitutions, expr_arena),
482 mutable: *mutable,
483 ty: *ty,
484 },
485 Stmt::Set { target, value } => Stmt::Set {
486 target: *target,
487 value: substitute_expr(value, substitutions, expr_arena),
488 },
489 Stmt::Return { value } => Stmt::Return {
490 value: value.map(|v| substitute_expr(v, substitutions, expr_arena)),
491 },
492 Stmt::Show { object, recipient } => Stmt::Show {
493 object: substitute_expr(object, substitutions, expr_arena),
494 recipient: *recipient,
495 },
496 Stmt::Call { function, args } => Stmt::Call {
497 function: *function,
498 args: args.iter().map(|a| substitute_expr(a, substitutions, expr_arena)).collect(),
499 },
500 Stmt::If { cond, then_block, else_block } => {
501 let new_then: Vec<Stmt<'a>> = then_block.iter()
502 .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
503 .collect();
504 let new_else = else_block.map(|eb| {
505 let stmts: Vec<Stmt<'a>> = eb.iter()
506 .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
507 .collect();
508 stmt_arena.alloc_slice(stmts) as &[Stmt<'a>]
509 });
510 Stmt::If {
511 cond: substitute_expr(cond, substitutions, expr_arena),
512 then_block: stmt_arena.alloc_slice(new_then),
513 else_block: new_else,
514 }
515 }
516 Stmt::While { cond, body, decreasing } => {
517 let new_body: Vec<Stmt<'a>> = body.iter()
518 .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
519 .collect();
520 Stmt::While {
521 cond: substitute_expr(cond, substitutions, expr_arena),
522 body: stmt_arena.alloc_slice(new_body),
523 decreasing: *decreasing,
524 }
525 }
526 Stmt::Repeat { pattern, iterable, body } => {
527 let new_body: Vec<Stmt<'a>> = body.iter()
528 .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
529 .collect();
530 Stmt::Repeat {
531 pattern: pattern.clone(),
532 iterable: substitute_expr(iterable, substitutions, expr_arena),
533 body: stmt_arena.alloc_slice(new_body),
534 }
535 }
536 Stmt::SetIndex { collection, index, value } => Stmt::SetIndex {
537 collection: substitute_expr(collection, substitutions, expr_arena),
538 index: substitute_expr(index, substitutions, expr_arena),
539 value: substitute_expr(value, substitutions, expr_arena),
540 },
541 Stmt::Push { value, collection } => Stmt::Push {
542 value: substitute_expr(value, substitutions, expr_arena),
543 collection: substitute_expr(collection, substitutions, expr_arena),
544 },
545 Stmt::SetField { object, field, value } => Stmt::SetField {
546 object: substitute_expr(object, substitutions, expr_arena),
547 field: *field,
548 value: substitute_expr(value, substitutions, expr_arena),
549 },
550 Stmt::Give { object, recipient } => Stmt::Give {
551 object: substitute_expr(object, substitutions, expr_arena),
552 recipient: substitute_expr(recipient, substitutions, expr_arena),
553 },
554 Stmt::Add { value, collection } => Stmt::Add {
555 value: substitute_expr(value, substitutions, expr_arena),
556 collection: substitute_expr(collection, substitutions, expr_arena),
557 },
558 Stmt::Remove { value, collection } => Stmt::Remove {
559 value: substitute_expr(value, substitutions, expr_arena),
560 collection: substitute_expr(collection, substitutions, expr_arena),
561 },
562 Stmt::Inspect { target, arms, has_otherwise } => {
563 let new_arms: Vec<_> = arms.iter().map(|arm| {
564 let new_body: Vec<Stmt<'a>> = arm.body.iter()
565 .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
566 .collect();
567 crate::ast::stmt::MatchArm {
568 enum_name: arm.enum_name,
569 variant: arm.variant,
570 bindings: arm.bindings.clone(),
571 body: stmt_arena.alloc_slice(new_body),
572 }
573 }).collect();
574 Stmt::Inspect {
575 target: substitute_expr(target, substitutions, expr_arena),
576 arms: new_arms,
577 has_otherwise: *has_otherwise,
578 }
579 }
580 Stmt::RuntimeAssert { condition, hard } => Stmt::RuntimeAssert {
581 condition: substitute_expr(condition, substitutions, expr_arena),
582 hard: *hard,
583 },
584 other => other.clone(),
585 }
586}
587
588pub(crate) fn substitute_block<'a>(
589 block: Block<'a>,
590 substitutions: &HashMap<Symbol, &'a Expr<'a>>,
591 expr_arena: &'a Arena<Expr<'a>>,
592 stmt_arena: &'a Arena<Stmt<'a>>,
593) -> Vec<Stmt<'a>> {
594 block.iter()
595 .map(|s| substitute_stmt(s, substitutions, expr_arena, stmt_arena))
596 .collect()
597}
598
599fn count_stmts(stmts: &[Stmt]) -> usize {
600 let mut count = 0;
601 for stmt in stmts {
602 count += 1;
603 match stmt {
604 Stmt::If { then_block, else_block, .. } => {
605 count += count_stmts(then_block);
606 if let Some(eb) = else_block {
607 count += count_stmts(eb);
608 }
609 }
610 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
611 count += count_stmts(body);
612 }
613 Stmt::FunctionDef { body, .. } => {
614 count += count_stmts(body);
615 }
616 _ => {}
617 }
618 }
619 count
620}
621
622fn try_specialize_call<'a>(
623 function: Symbol,
624 args: &[&'a Expr<'a>],
625 func_defs: &HashMap<Symbol, FuncInfo<'a>>,
626 registry: &mut SpecRegistry<'a>,
627 expr_arena: &'a Arena<Expr<'a>>,
628 stmt_arena: &'a Arena<Stmt<'a>>,
629 interner: &mut Interner,
630 effect_env: Option<&EffectEnv>,
631) -> Option<(Symbol, Vec<&'a Expr<'a>>)> {
632 let mut division = Division::new();
634 if let Some(func_info) = func_defs.get(&function) {
635 for (i, (param_sym, _)) in func_info.params.iter().enumerate() {
636 if let Some(arg) = args.get(i) {
637 if let Expr::Literal(lit) = arg {
638 division.insert(*param_sym, BindingTime::Static(lit.clone()));
639 }
640 }
641 }
642 }
643 let (key, classifications) = compute_spec_key(function, args, Some(&division));
644
645 if !is_mixed(&classifications) {
646 return None;
647 }
648
649 if let Some(&cached_name) = registry.cache.get(&key) {
650 let dynamic_args: Vec<&'a Expr<'a>> = args.iter().zip(classifications.iter())
651 .filter(|(_, c)| c.is_none())
652 .map(|(a, _)| *a)
653 .collect();
654 return Some((cached_name, dynamic_args));
655 }
656
657 if registry.history.iter().any(|prev| spec_key_embeds(prev, &key)) {
659 return None;
660 }
661
662 let count = registry.variant_count.get(&function).copied().unwrap_or(0);
663 if count >= 8 {
664 return None;
665 }
666
667 let func_info = func_defs.get(&function)?;
668
669 let safe_to_specialize = if let Some(env) = effect_env {
675 let fn_name = interner.resolve(function);
676 env.function_is_specialization_safe(fn_name)
677 } else {
678 !body_has_io(func_info.body)
679 };
680 if !safe_to_specialize {
681 return None;
682 }
683
684 if body_has_escape(func_info.body) {
685 return None;
686 }
687
688 let spec_name = make_spec_name(interner, func_info.name, &classifications);
689
690 let mut substitutions: HashMap<Symbol, &'a Expr<'a>> = HashMap::new();
691 let mut new_params: Vec<(Symbol, &'a TypeExpr<'a>)> = Vec::new();
692
693 for (i, (param_sym, param_type)) in func_info.params.iter().enumerate() {
694 if let Some(Some(lit)) = classifications.get(i) {
695 let lit_expr = expr_arena.alloc(Expr::Literal(lit.clone()));
696 substitutions.insert(*param_sym, lit_expr);
697 } else {
698 new_params.push((*param_sym, *param_type));
699 }
700 }
701
702 let specialized_body = substitute_block(func_info.body, &substitutions, expr_arena, stmt_arena);
703
704 let folded = super::fold::fold_stmts(specialized_body, expr_arena, stmt_arena, interner);
705 let optimized = super::dce::eliminate_dead_code(folded, stmt_arena, expr_arena);
706
707 let original_cost = count_stmts(func_info.body) + func_info.params.len();
708 let specialized_cost = count_stmts(&optimized) + new_params.len();
709
710 if specialized_cost as f64 > original_cost as f64 * 0.8 {
711 return None;
712 }
713
714 registry.history.push(key.clone());
716 registry.cache.insert(key, spec_name);
717 *registry.variant_count.entry(function).or_insert(0) += 1;
718
719 let cascaded: Vec<Stmt<'a>> = optimized.into_iter()
721 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
722 .collect();
723
724 let new_body = stmt_arena.alloc_slice(cascaded);
725
726 let new_func = Stmt::FunctionDef {
727 name: spec_name,
728 generics: func_info.generics.clone(),
729 params: new_params,
730 body: new_body,
731 return_type: func_info.return_type,
732 is_native: false,
733 native_path: None,
734 is_exported: false,
735 export_target: None,
736 opt_flags: func_info.opt_flags.clone(),
737 };
738
739 registry.new_funcs.push(new_func);
740
741 let dynamic_args: Vec<&'a Expr<'a>> = args.iter().zip(classifications.iter())
742 .filter(|(_, c)| c.is_none())
743 .map(|(a, _)| *a)
744 .collect();
745
746 Some((spec_name, dynamic_args))
747}
748
749fn specialize_in_expr<'a>(
750 expr: &'a Expr<'a>,
751 func_defs: &HashMap<Symbol, FuncInfo<'a>>,
752 registry: &mut SpecRegistry<'a>,
753 expr_arena: &'a Arena<Expr<'a>>,
754 stmt_arena: &'a Arena<Stmt<'a>>,
755 interner: &mut Interner,
756 effect_env: Option<&EffectEnv>,
757) -> &'a Expr<'a> {
758 match expr {
759 Expr::Call { function, args } => {
760 let new_args: Vec<&'a Expr<'a>> = args.iter()
761 .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
762 .collect();
763
764 if let Some((spec_name, dynamic_args)) = try_specialize_call(
765 *function, &new_args, func_defs, registry, expr_arena, stmt_arena, interner, effect_env,
766 ) {
767 expr_arena.alloc(Expr::Call {
768 function: spec_name,
769 args: dynamic_args,
770 })
771 } else {
772 let changed = new_args.iter().zip(args.iter())
773 .any(|(new, old)| !std::ptr::eq(*new as *const _, *old as *const _));
774 if changed {
775 expr_arena.alloc(Expr::Call { function: *function, args: new_args })
776 } else {
777 expr
778 }
779 }
780 }
781 Expr::BinaryOp { op, left, right } => {
782 let new_left = specialize_in_expr(left, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
783 let new_right = specialize_in_expr(right, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
784 if std::ptr::eq(new_left as *const _, *left as *const _)
785 && std::ptr::eq(new_right as *const _, *right as *const _) {
786 expr
787 } else {
788 expr_arena.alloc(Expr::BinaryOp { op: *op, left: new_left, right: new_right })
789 }
790 }
791 Expr::Not { operand } => {
792 let new_op = specialize_in_expr(operand, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
793 if std::ptr::eq(new_op as *const _, *operand as *const _) {
794 expr
795 } else {
796 expr_arena.alloc(Expr::Not { operand: new_op })
797 }
798 }
799 _ => expr,
800 }
801}
802
803fn specialize_in_stmt<'a>(
804 stmt: Stmt<'a>,
805 func_defs: &HashMap<Symbol, FuncInfo<'a>>,
806 registry: &mut SpecRegistry<'a>,
807 expr_arena: &'a Arena<Expr<'a>>,
808 stmt_arena: &'a Arena<Stmt<'a>>,
809 interner: &mut Interner,
810 effect_env: Option<&EffectEnv>,
811) -> Stmt<'a> {
812 match stmt {
813 Stmt::Let { var, value, mutable, ty } => Stmt::Let {
814 var,
815 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
816 mutable,
817 ty,
818 },
819 Stmt::Set { target, value } => Stmt::Set {
820 target,
821 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
822 },
823 Stmt::Return { value } => Stmt::Return {
824 value: value.map(|v| specialize_in_expr(v, func_defs, registry, expr_arena, stmt_arena, interner, effect_env)),
825 },
826 Stmt::Show { object, recipient } => Stmt::Show {
827 object: specialize_in_expr(object, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
828 recipient,
829 },
830 Stmt::Call { function, args } => {
831 let new_args: Vec<&'a Expr<'a>> = args.iter()
832 .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
833 .collect();
834
835 if let Some((spec_name, dynamic_args)) = try_specialize_call(
836 function, &new_args, func_defs, registry, expr_arena, stmt_arena, interner, effect_env,
837 ) {
838 Stmt::Call { function: spec_name, args: dynamic_args }
839 } else {
840 Stmt::Call { function, args: new_args }
841 }
842 }
843 Stmt::If { cond, then_block, else_block } => {
844 let new_cond = specialize_in_expr(cond, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
845 let new_then: Vec<Stmt<'a>> = then_block.iter().cloned()
846 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
847 .collect();
848 let new_else = else_block.map(|eb| {
849 let stmts: Vec<Stmt<'a>> = eb.iter().cloned()
850 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
851 .collect();
852 stmt_arena.alloc_slice(stmts) as &[Stmt<'a>]
853 });
854 Stmt::If {
855 cond: new_cond,
856 then_block: stmt_arena.alloc_slice(new_then),
857 else_block: new_else,
858 }
859 }
860 Stmt::While { cond, body, decreasing } => {
861 let new_cond = specialize_in_expr(cond, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
862 let new_body: Vec<Stmt<'a>> = body.iter().cloned()
863 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
864 .collect();
865 Stmt::While {
866 cond: new_cond,
867 body: stmt_arena.alloc_slice(new_body),
868 decreasing,
869 }
870 }
871 Stmt::Repeat { pattern, iterable, body } => {
872 let new_iter = specialize_in_expr(iterable, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
873 let new_body: Vec<Stmt<'a>> = body.iter().cloned()
874 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
875 .collect();
876 Stmt::Repeat {
877 pattern,
878 iterable: new_iter,
879 body: stmt_arena.alloc_slice(new_body),
880 }
881 }
882 Stmt::SetIndex { collection, index, value } => Stmt::SetIndex {
883 collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
884 index: specialize_in_expr(index, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
885 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
886 },
887 Stmt::Push { value, collection } => Stmt::Push {
888 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
889 collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
890 },
891 Stmt::SetField { object, field, value } => Stmt::SetField {
892 object: specialize_in_expr(object, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
893 field,
894 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
895 },
896 Stmt::Give { object, recipient } => Stmt::Give {
897 object: specialize_in_expr(object, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
898 recipient: specialize_in_expr(recipient, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
899 },
900 Stmt::Add { value, collection } => Stmt::Add {
901 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
902 collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
903 },
904 Stmt::Remove { value, collection } => Stmt::Remove {
905 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
906 collection: specialize_in_expr(collection, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
907 },
908 Stmt::Inspect { target, arms, has_otherwise } => {
909 let new_target = specialize_in_expr(target, func_defs, registry, expr_arena, stmt_arena, interner, effect_env);
910 let new_arms: Vec<_> = arms.into_iter().map(|arm| {
911 let new_body: Vec<Stmt<'a>> = arm.body.iter().cloned()
912 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
913 .collect();
914 crate::ast::stmt::MatchArm {
915 enum_name: arm.enum_name,
916 variant: arm.variant,
917 bindings: arm.bindings,
918 body: stmt_arena.alloc_slice(new_body),
919 }
920 }).collect();
921 Stmt::Inspect {
922 target: new_target,
923 arms: new_arms,
924 has_otherwise,
925 }
926 }
927 Stmt::RuntimeAssert { condition, hard } => Stmt::RuntimeAssert {
928 condition: specialize_in_expr(condition, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
929 hard,
930 },
931 Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } => {
932 if is_native {
933 return Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags };
934 }
935 let new_body: Vec<Stmt<'a>> = body.iter().cloned()
936 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
937 .collect();
938 Stmt::FunctionDef {
939 name,
940 params,
941 generics,
942 body: stmt_arena.alloc_slice(new_body),
943 return_type,
944 is_native,
945 native_path,
946 is_exported,
947 export_target,
948 opt_flags,
949 }
950 }
951 Stmt::Concurrent { tasks } => {
957 let new_tasks: Vec<Stmt<'a>> = tasks.iter().cloned()
958 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
959 .collect();
960 Stmt::Concurrent { tasks: stmt_arena.alloc_slice(new_tasks) }
961 }
962 Stmt::Parallel { tasks } => {
963 let new_tasks: Vec<Stmt<'a>> = tasks.iter().cloned()
964 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
965 .collect();
966 Stmt::Parallel { tasks: stmt_arena.alloc_slice(new_tasks) }
967 }
968 Stmt::Zone { name, capacity, source_file, body } => {
969 let new_body: Vec<Stmt<'a>> = body.iter().cloned()
970 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
971 .collect();
972 Stmt::Zone { name, capacity, source_file, body: stmt_arena.alloc_slice(new_body) }
973 }
974 Stmt::Select { branches } => {
975 let new_branches: Vec<crate::ast::stmt::SelectBranch<'a>> = branches.into_iter().map(|b| match b {
976 crate::ast::stmt::SelectBranch::Receive { var, pipe, body } => {
977 let new_body: Vec<Stmt<'a>> = body.iter().cloned()
978 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
979 .collect();
980 crate::ast::stmt::SelectBranch::Receive {
981 var,
982 pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
983 body: stmt_arena.alloc_slice(new_body),
984 }
985 }
986 crate::ast::stmt::SelectBranch::Timeout { milliseconds, body } => {
987 let new_body: Vec<Stmt<'a>> = body.iter().cloned()
988 .map(|s| specialize_in_stmt(s, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
989 .collect();
990 crate::ast::stmt::SelectBranch::Timeout {
991 milliseconds: specialize_in_expr(milliseconds, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
992 body: stmt_arena.alloc_slice(new_body),
993 }
994 }
995 }).collect();
996 Stmt::Select { branches: new_branches }
997 }
998 Stmt::SendPipe { value, pipe } => Stmt::SendPipe {
999 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1000 pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1001 },
1002 Stmt::TrySendPipe { value, pipe, result } => Stmt::TrySendPipe {
1003 value: specialize_in_expr(value, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1004 pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1005 result,
1006 },
1007 Stmt::ReceivePipe { var, pipe } => Stmt::ReceivePipe {
1008 var,
1009 pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1010 },
1011 Stmt::TryReceivePipe { var, pipe } => Stmt::TryReceivePipe {
1012 var,
1013 pipe: specialize_in_expr(pipe, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1014 },
1015 Stmt::LaunchTask { function, args } => Stmt::LaunchTask {
1016 function,
1017 args: args.iter()
1018 .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
1019 .collect(),
1020 },
1021 Stmt::LaunchTaskWithHandle { handle, function, args } => Stmt::LaunchTaskWithHandle {
1022 handle,
1023 function,
1024 args: args.iter()
1025 .map(|a| specialize_in_expr(a, func_defs, registry, expr_arena, stmt_arena, interner, effect_env))
1026 .collect(),
1027 },
1028 Stmt::StopTask { handle } => Stmt::StopTask {
1029 handle: specialize_in_expr(handle, func_defs, registry, expr_arena, stmt_arena, interner, effect_env),
1030 },
1031 other => other,
1032 }
1033}
1034
1035pub fn specialize_stmts<'a>(
1036 stmts: Vec<Stmt<'a>>,
1037 expr_arena: &'a Arena<Expr<'a>>,
1038 stmt_arena: &'a Arena<Stmt<'a>>,
1039 interner: &mut Interner,
1040) -> (Vec<Stmt<'a>>, usize) {
1041 let mut variant_count = HashMap::new();
1042 specialize_stmts_with_state(stmts, expr_arena, stmt_arena, interner, &mut variant_count, None)
1043}
1044
1045pub fn specialize_stmts_with_state<'a>(
1046 stmts: Vec<Stmt<'a>>,
1047 expr_arena: &'a Arena<Expr<'a>>,
1048 stmt_arena: &'a Arena<Stmt<'a>>,
1049 interner: &mut Interner,
1050 persistent_variant_count: &mut HashMap<Symbol, usize>,
1051 bta_cache: Option<&super::bta::BtaCache>,
1052) -> (Vec<Stmt<'a>>, usize) {
1053 let effect_env = EffectEnv::from_stmts(&stmts, interner);
1054 let func_defs = collect_func_defs(&stmts);
1055 let mut registry = SpecRegistry::new();
1056 registry.variant_count = persistent_variant_count.clone();
1057 if let Some(cache) = bta_cache {
1058 registry.bta_cache = Some(cache.clone());
1059 }
1060
1061 let specialized: Vec<Stmt<'a>> = stmts.into_iter()
1062 .map(|stmt| specialize_in_stmt(stmt, &func_defs, &mut registry, expr_arena, stmt_arena, interner, Some(&effect_env)))
1063 .collect();
1064
1065 let changes = registry.new_funcs.len();
1066 *persistent_variant_count = registry.variant_count;
1067 let mut result = registry.new_funcs;
1068 result.extend(specialized);
1069 (result, changes)
1070}
1071
1072pub fn cleanup_identities<'a>(
1079 stmts: Vec<Stmt<'a>>,
1080 expr_arena: &'a Arena<Expr<'a>>,
1081 stmt_arena: &'a Arena<Stmt<'a>>,
1082) -> Vec<Stmt<'a>> {
1083 let mut current = stmts;
1084 for _ in 0..4 {
1085 let next = cleanup_pass(¤t, expr_arena, stmt_arena);
1086 if next.len() == current.len() {
1087 let mut same = true;
1089 for (a, b) in next.iter().zip(current.iter()) {
1090 if !stmt_structurally_equal(a, b) {
1091 same = false;
1092 break;
1093 }
1094 }
1095 if same {
1096 return next;
1097 }
1098 }
1099 current = next;
1100 }
1101 current
1102}
1103
1104fn cleanup_pass<'a>(
1105 stmts: &[Stmt<'a>],
1106 expr_arena: &'a Arena<Expr<'a>>,
1107 stmt_arena: &'a Arena<Stmt<'a>>,
1108) -> Vec<Stmt<'a>> {
1109 let mut result = Vec::with_capacity(stmts.len());
1110
1111 for (i, stmt) in stmts.iter().enumerate() {
1112 match stmt {
1113 Stmt::Let { var, value: Expr::Identifier(src), .. } if var == src => {
1115 continue;
1116 }
1117 Stmt::Let { var, value, mutable: false, .. } => {
1119 if let Some(Stmt::Return { value: Some(Expr::Identifier(ret_var)) }) = stmts.get(i + 1) {
1120 if var == ret_var {
1121 if let Expr::Literal(_) = value {
1122 result.push(Stmt::Return { value: Some(value) });
1123 continue;
1125 }
1126 }
1127 }
1128 result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1130 }
1131 Stmt::Return { value: Some(Expr::Identifier(ret_var)) } => {
1133 if i > 0 {
1134 if let Stmt::Let { var, value: Expr::Literal(_), mutable: false, .. } = &stmts[i - 1] {
1135 if var == ret_var {
1136 continue;
1137 }
1138 }
1139 }
1140 result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1141 }
1142 Stmt::Inspect { arms, has_otherwise: true, .. } if arms.len() == 1 => {
1144 let arm = &arms[0];
1145 if arm.variant.is_none() {
1146 for s in arm.body {
1148 result.push(cleanup_stmt(s, expr_arena, stmt_arena));
1149 }
1150 continue;
1151 }
1152 result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1153 }
1154 _ => {
1155 result.push(cleanup_stmt(stmt, expr_arena, stmt_arena));
1156 }
1157 }
1158 }
1159
1160 result
1161}
1162
1163fn cleanup_stmt<'a>(
1164 stmt: &Stmt<'a>,
1165 expr_arena: &'a Arena<Expr<'a>>,
1166 stmt_arena: &'a Arena<Stmt<'a>>,
1167) -> Stmt<'a> {
1168 match stmt {
1169 Stmt::If { cond, then_block, else_block } => {
1170 let new_then = cleanup_pass(then_block, expr_arena, stmt_arena);
1171 let new_else = else_block.map(|eb| {
1172 let cleaned = cleanup_pass(eb, expr_arena, stmt_arena);
1173 stmt_arena.alloc_slice(cleaned) as &[Stmt<'a>]
1174 });
1175 Stmt::If {
1176 cond,
1177 then_block: stmt_arena.alloc_slice(new_then),
1178 else_block: new_else,
1179 }
1180 }
1181 Stmt::While { cond, body, decreasing } => {
1182 let new_body = cleanup_pass(body, expr_arena, stmt_arena);
1183 Stmt::While {
1184 cond,
1185 body: stmt_arena.alloc_slice(new_body),
1186 decreasing: *decreasing,
1187 }
1188 }
1189 Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } => {
1190 if *is_native {
1191 return stmt.clone();
1192 }
1193 let new_body = cleanup_pass(body, expr_arena, stmt_arena);
1194 Stmt::FunctionDef {
1195 name: *name,
1196 params: params.clone(),
1197 generics: generics.clone(),
1198 body: stmt_arena.alloc_slice(new_body),
1199 return_type: *return_type,
1200 is_native: *is_native,
1201 native_path: *native_path,
1202 is_exported: *is_exported,
1203 export_target: *export_target,
1204 opt_flags: opt_flags.clone(),
1205 }
1206 }
1207 Stmt::Repeat { pattern, iterable, body } => {
1208 let new_body = cleanup_pass(body, expr_arena, stmt_arena);
1209 Stmt::Repeat {
1210 pattern: pattern.clone(),
1211 iterable,
1212 body: stmt_arena.alloc_slice(new_body),
1213 }
1214 }
1215 other => other.clone(),
1216 }
1217}
1218
1219fn stmt_structurally_equal(a: &Stmt, b: &Stmt) -> bool {
1220 std::mem::discriminant(a) == std::mem::discriminant(b)
1221}