1use std::cell::Cell;
2use std::collections::{HashMap, HashSet};
3use std::fmt;
4use std::rc::Rc;
5
6use yulang_runtime as runtime;
7use yulang_typed_ir as typed_ir;
8
9use crate::cps_capture::infer_cps_captures;
10use crate::cps_effectful_calls::reify_effectful_direct_calls;
11use crate::cps_ir::{
12 CpsContinuation, CpsContinuationId, CpsFunction, CpsHandler, CpsHandlerArm, CpsHandlerEnv,
13 CpsHandlerId, CpsLiteral, CpsModule, CpsRecordField, CpsShotKind, CpsStmt, CpsTerminator,
14 CpsValueId,
15};
16
17pub type CpsLowerResult<T> = Result<T, CpsLowerError>;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum CpsLowerError {
21 UnsupportedRoot {
22 root: runtime::Root,
23 },
24 MissingRootExpr {
25 index: usize,
26 },
27 UnsupportedExpr {
28 kind: &'static str,
29 },
30 UnsupportedFreeVar {
31 path: typed_ir::Path,
32 },
33 UnsupportedBareEffectOp {
34 path: typed_ir::Path,
35 },
36 UnsupportedPattern {
37 kind: &'static str,
38 },
39 UnsupportedBinding {
40 path: typed_ir::Path,
41 reason: &'static str,
42 },
43 PrimitiveArityMismatch {
44 op: typed_ir::PrimitiveOp,
45 expected: usize,
46 actual: usize,
47 },
48 CallArityMismatch {
49 target: String,
50 expected: usize,
51 actual: usize,
52 },
53}
54
55impl fmt::Display for CpsLowerError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 CpsLowerError::UnsupportedRoot { root } => {
59 write!(f, "CPS lowering does not support root {root:?} yet")
60 }
61 CpsLowerError::MissingRootExpr { index } => {
62 write!(f, "runtime module has no root expression at index {index}")
63 }
64 CpsLowerError::UnsupportedExpr { kind } => {
65 write!(f, "CPS lowering does not support {kind} expressions yet")
66 }
67 CpsLowerError::UnsupportedFreeVar { path } => {
68 write!(
69 f,
70 "CPS lowering does not support free variable `{}` yet",
71 path_name(path)
72 )
73 }
74 CpsLowerError::UnsupportedBareEffectOp { path } => {
75 write!(
76 f,
77 "CPS lowering does not support bare effect operation `{}` yet",
78 path_name(path)
79 )
80 }
81 CpsLowerError::UnsupportedPattern { kind } => {
82 write!(f, "CPS lowering does not support {kind} patterns yet")
83 }
84 CpsLowerError::UnsupportedBinding { path, reason } => {
85 write!(
86 f,
87 "CPS lowering does not support binding {} yet: {reason}",
88 path_name(path)
89 )
90 }
91 CpsLowerError::PrimitiveArityMismatch {
92 op,
93 expected,
94 actual,
95 } => write!(
96 f,
97 "CPS lowering expected {expected} arguments for primitive {op:?}, got {actual}"
98 ),
99 CpsLowerError::CallArityMismatch {
100 target,
101 expected,
102 actual,
103 } => write!(
104 f,
105 "CPS lowering expected {expected} arguments for call to {target}, got {actual}"
106 ),
107 }
108 }
109}
110
111impl std::error::Error for CpsLowerError {}
112
113pub fn lower_cps_module(module: &runtime::Module) -> CpsLowerResult<CpsModule> {
114 let binding_table = module
115 .bindings
116 .iter()
117 .map(|binding| (binding.name.clone(), binding))
118 .collect::<HashMap<_, _>>();
119 let function_table = module
120 .bindings
121 .iter()
122 .filter_map(binding_function_info)
123 .collect::<HashMap<_, _>>();
124 let reachable = reachable_binding_paths(module, &function_table, &binding_table);
125 let functions = module
126 .bindings
127 .iter()
128 .filter(|binding| reachable.contains(&binding.name))
129 .map(|binding| lower_binding(binding, &function_table, &binding_table))
130 .collect::<CpsLowerResult<Vec<_>>>()?;
131
132 let mut roots = Vec::new();
133 for root in &module.roots {
134 match root {
135 runtime::Root::Expr(index) => {
136 let expr = module
137 .root_exprs
138 .get(*index)
139 .ok_or(CpsLowerError::MissingRootExpr { index: *index })?;
140 roots.push(
141 FunctionLowerer::new(
142 format!("root_{index}"),
143 &function_table,
144 &binding_table,
145 Vec::new(),
146 FunctionLoweringTraits::default(),
147 )
148 .lower_root(expr)?,
149 );
150 }
151 runtime::Root::Binding(_) => {
152 return Err(CpsLowerError::UnsupportedRoot { root: root.clone() });
153 }
154 }
155 }
156 let mut module = CpsModule { functions, roots };
157 reify_effectful_direct_calls(&mut module);
158 infer_cps_captures(&mut module);
159 make_handler_ids_global(&mut module);
160 if std::env::var_os("YULANG_DUMP_CPS").is_some() {
161 eprintln!("{module:#?}");
162 }
163 Ok(module)
164}
165
166fn make_handler_ids_global(module: &mut CpsModule) {
167 let mut next_handler = 0;
168 for function in module.functions.iter_mut().chain(&mut module.roots) {
169 let offset = next_handler;
170 next_handler += function.handlers.len();
171 if offset == 0 {
172 continue;
173 }
174 for handler in &mut function.handlers {
175 handler.id.0 += offset;
176 }
177 for continuation in &mut function.continuations {
178 for stmt in &mut continuation.stmts {
179 match stmt {
180 CpsStmt::ResumeWithHandler { handler, .. }
181 | CpsStmt::InstallHandler { handler, .. }
182 | CpsStmt::UninstallHandler { handler } => {
183 remap_handler_id(handler, offset);
184 }
185 _ => {}
186 }
187 }
188 if let CpsTerminator::Perform { handler, .. } = &mut continuation.terminator {
189 remap_handler_id(handler, offset);
190 }
191 }
192 }
193}
194
195fn remap_handler_id(handler: &mut CpsHandlerId, offset: usize) {
196 if *handler != dynamic_handler_id() {
197 handler.0 += offset;
198 }
199}
200
201fn reachable_binding_paths(
202 module: &runtime::Module,
203 functions: &HashMap<typed_ir::Path, FunctionInfo>,
204 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
205) -> HashSet<typed_ir::Path> {
206 let binding_bodies = module
207 .bindings
208 .iter()
209 .map(|binding| (binding.name.clone(), &binding.body))
210 .collect::<HashMap<_, _>>();
211 let mut reachable = HashSet::new();
212 let mut stack = Vec::new();
213 for root in &module.roots {
214 match root {
215 runtime::Root::Expr(index) => {
216 if let Some(expr) = module.root_exprs.get(*index) {
217 let mut visiting_values = HashSet::new();
218 collect_expr_direct_calls_inner(
219 expr,
220 functions,
221 bindings,
222 &mut stack,
223 &mut visiting_values,
224 );
225 }
226 }
227 runtime::Root::Binding(path) => stack.push(path.clone()),
228 }
229 }
230
231 while let Some(path) = stack.pop() {
232 if !reachable.insert(path.clone()) {
233 continue;
234 }
235 let Some(body) = binding_bodies.get(&path) else {
236 continue;
237 };
238 let mut visiting_values = HashSet::new();
239 collect_expr_direct_calls_inner(
240 body,
241 functions,
242 bindings,
243 &mut stack,
244 &mut visiting_values,
245 );
246 }
247 reachable
248}
249
250fn collect_expr_direct_calls_inner(
251 expr: &runtime::Expr,
252 functions: &HashMap<typed_ir::Path, FunctionInfo>,
253 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
254 out: &mut Vec<typed_ir::Path>,
255 visiting_values: &mut HashSet<typed_ir::Path>,
256) {
257 if runtime_type_is_bool_value(&expr.ty)
258 && let Ok(Some((target, _info, args))) = direct_apply_path(expr, functions)
259 && handler_wrapper_args_have_unconsumed_effects(target, &args, bindings)
260 {
261 out.push(target.clone());
262 }
263 if let Some((body, arms, _consumes)) = inline_thunk_handler_apply(expr, functions, bindings) {
264 collect_expr_direct_calls_inner(&body, functions, bindings, out, visiting_values);
265 for arm in &arms {
266 collect_pattern_direct_calls(&arm.payload, functions, bindings, out, visiting_values);
267 if let Some(guard) = &arm.guard {
268 collect_expr_direct_calls_inner(guard, functions, bindings, out, visiting_values);
269 }
270 collect_expr_direct_calls_inner(&arm.body, functions, bindings, out, visiting_values);
271 }
272 return;
273 }
274 if let Ok(Some((target, _info, args))) = direct_apply_path(expr, functions) {
275 if fail_prefix_path(target)
276 || bindings
277 .get(target)
278 .is_some_and(|binding| binding_is_throw_forwarder(binding))
279 {
280 for arg in args {
281 collect_expr_direct_calls_inner(arg, functions, bindings, out, visiting_values);
282 }
283 return;
284 }
285 if !matches!(expr.ty, runtime::Type::Thunk { .. })
286 && args.iter().any(|arg| is_inline_argument(arg))
287 {
288 if let Some(binding) = bindings.get(&target) {
289 let mut visiting = HashSet::new();
290 let mut memo = HashMap::new();
291 if binding_may_perform_when_called(
292 &target,
293 functions,
294 bindings,
295 &mut visiting,
296 &mut memo,
297 ) || (runtime_type_is_bool_value(&expr.ty)
298 && handler_wrapper_args_have_unconsumed_effects(&target, &args, bindings))
299 {
300 out.push(target.clone());
301 }
302 if binding_has_self_direct_call(&target, &binding.body, functions) {
303 out.push(target.clone());
304 } else {
305 collect_expr_direct_calls_inner(
306 &binding.body,
307 functions,
308 bindings,
309 out,
310 visiting_values,
311 );
312 }
313 }
314 } else {
315 out.push(target.clone());
316 }
317 }
318 match &expr.kind {
319 runtime::ExprKind::Lambda { body, .. } => {
320 collect_expr_direct_calls_inner(body, functions, bindings, out, visiting_values);
321 }
322 runtime::ExprKind::Apply { callee, arg, .. } => {
323 collect_expr_direct_calls_inner(callee, functions, bindings, out, visiting_values);
324 collect_expr_direct_calls_inner(arg, functions, bindings, out, visiting_values);
325 }
326 runtime::ExprKind::If {
327 cond,
328 then_branch,
329 else_branch,
330 ..
331 } => {
332 collect_expr_direct_calls_inner(cond, functions, bindings, out, visiting_values);
333 collect_expr_direct_calls_inner(then_branch, functions, bindings, out, visiting_values);
334 collect_expr_direct_calls_inner(else_branch, functions, bindings, out, visiting_values);
335 }
336 runtime::ExprKind::Tuple(items) => {
337 for item in items {
338 collect_expr_direct_calls_inner(item, functions, bindings, out, visiting_values);
339 }
340 }
341 runtime::ExprKind::Record { fields, spread } => {
342 for field in fields {
343 collect_expr_direct_calls_inner(
344 &field.value,
345 functions,
346 bindings,
347 out,
348 visiting_values,
349 );
350 }
351 if let Some(spread) = spread {
352 match spread {
353 runtime::RecordSpreadExpr::Head(expr)
354 | runtime::RecordSpreadExpr::Tail(expr) => {
355 collect_expr_direct_calls_inner(
356 expr,
357 functions,
358 bindings,
359 out,
360 visiting_values,
361 );
362 }
363 }
364 }
365 }
366 runtime::ExprKind::Variant {
367 value: Some(value), ..
368 } => collect_expr_direct_calls_inner(value, functions, bindings, out, visiting_values),
369 runtime::ExprKind::Select { base, .. } => {
370 collect_expr_direct_calls_inner(base, functions, bindings, out, visiting_values);
371 }
372 runtime::ExprKind::Match {
373 scrutinee, arms, ..
374 } => {
375 collect_expr_direct_calls_inner(scrutinee, functions, bindings, out, visiting_values);
376 for arm in arms {
377 if let Some(guard) = &arm.guard {
378 collect_expr_direct_calls_inner(
379 guard,
380 functions,
381 bindings,
382 out,
383 visiting_values,
384 );
385 }
386 collect_expr_direct_calls_inner(
387 &arm.body,
388 functions,
389 bindings,
390 out,
391 visiting_values,
392 );
393 collect_pattern_direct_calls(
394 &arm.pattern,
395 functions,
396 bindings,
397 out,
398 visiting_values,
399 );
400 }
401 }
402 runtime::ExprKind::Block { stmts, tail } => {
403 for (index, stmt) in stmts.iter().enumerate() {
404 match stmt {
405 runtime::Stmt::Let { pattern, value } => {
406 if unused_pure_let(
407 pattern,
408 value,
409 &stmts[index + 1..],
410 tail.as_deref(),
411 functions,
412 bindings,
413 ) {
414 continue;
415 }
416 collect_pattern_direct_calls(
417 pattern,
418 functions,
419 bindings,
420 out,
421 visiting_values,
422 );
423 collect_expr_direct_calls_inner(
424 value,
425 functions,
426 bindings,
427 out,
428 visiting_values,
429 );
430 }
431 runtime::Stmt::Expr(expr) => {
432 collect_expr_direct_calls_inner(
433 expr,
434 functions,
435 bindings,
436 out,
437 visiting_values,
438 );
439 }
440 runtime::Stmt::Module { body, .. } => {
441 collect_expr_direct_calls_inner(
442 body,
443 functions,
444 bindings,
445 out,
446 visiting_values,
447 );
448 }
449 }
450 }
451 if let Some(tail) = tail {
452 collect_expr_direct_calls_inner(tail, functions, bindings, out, visiting_values);
453 }
454 }
455 runtime::ExprKind::Handle { body, arms, .. } => {
456 collect_expr_direct_calls_inner(body, functions, bindings, out, visiting_values);
457 for arm in arms {
458 collect_pattern_direct_calls(
459 &arm.payload,
460 functions,
461 bindings,
462 out,
463 visiting_values,
464 );
465 if let Some(guard) = &arm.guard {
466 collect_expr_direct_calls_inner(
467 guard,
468 functions,
469 bindings,
470 out,
471 visiting_values,
472 );
473 }
474 collect_expr_direct_calls_inner(
475 &arm.body,
476 functions,
477 bindings,
478 out,
479 visiting_values,
480 );
481 }
482 }
483 runtime::ExprKind::BindHere { expr }
484 | runtime::ExprKind::Thunk { expr, .. }
485 | runtime::ExprKind::LocalPushId { body: expr, .. }
486 | runtime::ExprKind::AddId { thunk: expr, .. }
487 | runtime::ExprKind::Coerce { expr, .. }
488 | runtime::ExprKind::Pack { expr, .. } => {
489 collect_expr_direct_calls_inner(expr, functions, bindings, out, visiting_values);
490 }
491 runtime::ExprKind::Var(path) => {
492 if functions.contains_key(path) {
493 out.push(path.clone());
494 }
495 if let Some(binding) = bindings.get(path)
496 && let Some(body) = binding_value_body(binding)
497 && !matches!(&body.kind, runtime::ExprKind::Var(inner) if inner == path)
498 && visiting_values.insert(path.clone())
499 {
500 collect_expr_direct_calls_inner(body, functions, bindings, out, visiting_values);
501 visiting_values.remove(path);
502 }
503 }
504 runtime::ExprKind::EffectOp(_)
505 | runtime::ExprKind::PrimitiveOp(_)
506 | runtime::ExprKind::Lit(_)
507 | runtime::ExprKind::Variant { value: None, .. }
508 | runtime::ExprKind::PeekId
509 | runtime::ExprKind::FindId { .. } => {}
510 }
511}
512
513fn binding_has_self_direct_call(
514 target: &typed_ir::Path,
515 expr: &runtime::Expr,
516 functions: &HashMap<typed_ir::Path, FunctionInfo>,
517) -> bool {
518 if let Some((called, _)) = direct_apply_target(expr, functions)
519 && &called == target
520 {
521 return true;
522 }
523 match &expr.kind {
524 runtime::ExprKind::Lambda { body, .. }
525 | runtime::ExprKind::Thunk { expr: body, .. }
526 | runtime::ExprKind::BindHere { expr: body }
527 | runtime::ExprKind::LocalPushId { body, .. }
528 | runtime::ExprKind::AddId { thunk: body, .. }
529 | runtime::ExprKind::Coerce { expr: body, .. }
530 | runtime::ExprKind::Pack { expr: body, .. } => {
531 binding_has_self_direct_call(target, body, functions)
532 }
533 runtime::ExprKind::Apply { callee, arg, .. } => {
534 binding_has_self_direct_call(target, callee, functions)
535 || binding_has_self_direct_call(target, arg, functions)
536 }
537 runtime::ExprKind::If {
538 cond,
539 then_branch,
540 else_branch,
541 ..
542 } => {
543 binding_has_self_direct_call(target, cond, functions)
544 || binding_has_self_direct_call(target, then_branch, functions)
545 || binding_has_self_direct_call(target, else_branch, functions)
546 }
547 runtime::ExprKind::Tuple(items) => items
548 .iter()
549 .any(|item| binding_has_self_direct_call(target, item, functions)),
550 runtime::ExprKind::Record { fields, spread } => {
551 fields
552 .iter()
553 .any(|field| binding_has_self_direct_call(target, &field.value, functions))
554 || spread.as_ref().is_some_and(|spread| match spread {
555 runtime::RecordSpreadExpr::Head(expr)
556 | runtime::RecordSpreadExpr::Tail(expr) => {
557 binding_has_self_direct_call(target, expr, functions)
558 }
559 })
560 }
561 runtime::ExprKind::Variant {
562 value: Some(value), ..
563 }
564 | runtime::ExprKind::Select { base: value, .. } => {
565 binding_has_self_direct_call(target, value, functions)
566 }
567 runtime::ExprKind::Match {
568 scrutinee, arms, ..
569 } => {
570 binding_has_self_direct_call(target, scrutinee, functions)
571 || arms.iter().any(|arm| {
572 arm.guard
573 .as_ref()
574 .is_some_and(|guard| binding_has_self_direct_call(target, guard, functions))
575 || binding_has_self_direct_call(target, &arm.body, functions)
576 })
577 }
578 runtime::ExprKind::Handle { body, arms, .. } => {
579 binding_has_self_direct_call(target, body, functions)
580 || arms.iter().any(|arm| {
581 arm.guard
582 .as_ref()
583 .is_some_and(|guard| binding_has_self_direct_call(target, guard, functions))
584 || binding_has_self_direct_call(target, &arm.body, functions)
585 })
586 }
587 runtime::ExprKind::Block { stmts, tail } => {
588 stmts.iter().any(|stmt| match stmt {
589 runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
590 binding_has_self_direct_call(target, value, functions)
591 }
592 runtime::Stmt::Module { body, .. } => {
593 binding_has_self_direct_call(target, body, functions)
594 }
595 }) || tail
596 .as_ref()
597 .is_some_and(|tail| binding_has_self_direct_call(target, tail, functions))
598 }
599 runtime::ExprKind::Var(_)
600 | runtime::ExprKind::EffectOp(_)
601 | runtime::ExprKind::PrimitiveOp(_)
602 | runtime::ExprKind::Lit(_)
603 | runtime::ExprKind::Variant { value: None, .. }
604 | runtime::ExprKind::PeekId
605 | runtime::ExprKind::FindId { .. } => false,
606 }
607}
608
609fn collect_expr_performed_effects(expr: &runtime::Expr) -> Vec<typed_ir::Path> {
610 let mut effects = Vec::new();
611 collect_expr_performed_effects_into(expr, &mut effects);
612 effects
613}
614
615fn collect_expr_performed_effects_into(expr: &runtime::Expr, out: &mut Vec<typed_ir::Path>) {
616 if let Some(request) = effect_apply_nested(expr)
617 && !out.iter().any(|seen| seen == &request.effect)
618 {
619 out.push(request.effect);
620 }
621 match &expr.kind {
622 runtime::ExprKind::Lambda { body, .. }
623 | runtime::ExprKind::Thunk { expr: body, .. }
624 | runtime::ExprKind::LocalPushId { body, .. }
625 | runtime::ExprKind::BindHere { expr: body }
626 | runtime::ExprKind::AddId { thunk: body, .. }
627 | runtime::ExprKind::Coerce { expr: body, .. }
628 | runtime::ExprKind::Pack { expr: body, .. } => {
629 collect_expr_performed_effects_into(body, out);
630 }
631 runtime::ExprKind::Apply { callee, arg, .. } => {
632 collect_expr_performed_effects_into(callee, out);
633 collect_expr_performed_effects_into(arg, out);
634 }
635 runtime::ExprKind::If {
636 cond,
637 then_branch,
638 else_branch,
639 ..
640 } => {
641 collect_expr_performed_effects_into(cond, out);
642 collect_expr_performed_effects_into(then_branch, out);
643 collect_expr_performed_effects_into(else_branch, out);
644 }
645 runtime::ExprKind::Match {
646 scrutinee, arms, ..
647 } => {
648 collect_expr_performed_effects_into(scrutinee, out);
649 for arm in arms {
650 if let Some(guard) = &arm.guard {
651 collect_expr_performed_effects_into(guard, out);
652 }
653 collect_expr_performed_effects_into(&arm.body, out);
654 }
655 }
656 runtime::ExprKind::Handle { body, arms, .. } => {
657 collect_expr_performed_effects_into(body, out);
658 for arm in arms {
659 if let Some(guard) = &arm.guard {
660 collect_expr_performed_effects_into(guard, out);
661 }
662 collect_expr_performed_effects_into(&arm.body, out);
663 }
664 }
665 runtime::ExprKind::Block { stmts, tail } => {
666 for stmt in stmts {
667 match stmt {
668 runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
669 collect_expr_performed_effects_into(value, out);
670 }
671 runtime::Stmt::Module { body, .. } => {
672 collect_expr_performed_effects_into(body, out);
673 }
674 }
675 }
676 if let Some(tail) = tail {
677 collect_expr_performed_effects_into(tail, out);
678 }
679 }
680 runtime::ExprKind::Tuple(items) => {
681 for item in items {
682 collect_expr_performed_effects_into(item, out);
683 }
684 }
685 runtime::ExprKind::Record { fields, spread } => {
686 for field in fields {
687 collect_expr_performed_effects_into(&field.value, out);
688 }
689 if let Some(spread) = spread {
690 match spread {
691 runtime::RecordSpreadExpr::Head(expr)
692 | runtime::RecordSpreadExpr::Tail(expr) => {
693 collect_expr_performed_effects_into(expr, out);
694 }
695 }
696 }
697 }
698 runtime::ExprKind::Variant {
699 value: Some(value), ..
700 }
701 | runtime::ExprKind::Select { base: value, .. } => {
702 collect_expr_performed_effects_into(value, out);
703 }
704 runtime::ExprKind::Var(_)
705 | runtime::ExprKind::EffectOp(_)
706 | runtime::ExprKind::PrimitiveOp(_)
707 | runtime::ExprKind::Lit(_)
708 | runtime::ExprKind::Variant { value: None, .. }
709 | runtime::ExprKind::PeekId
710 | runtime::ExprKind::FindId { .. } => {}
711 }
712}
713
714fn collect_pattern_direct_calls(
715 pattern: &runtime::Pattern,
716 functions: &HashMap<typed_ir::Path, FunctionInfo>,
717 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
718 out: &mut Vec<typed_ir::Path>,
719 visiting_values: &mut HashSet<typed_ir::Path>,
720) {
721 match pattern {
722 runtime::Pattern::Tuple { items, .. } => {
723 for item in items {
724 collect_pattern_direct_calls(item, functions, bindings, out, visiting_values);
725 }
726 }
727 runtime::Pattern::List {
728 prefix,
729 spread,
730 suffix,
731 ..
732 } => {
733 for item in prefix {
734 collect_pattern_direct_calls(item, functions, bindings, out, visiting_values);
735 }
736 if let Some(spread) = spread {
737 collect_pattern_direct_calls(spread, functions, bindings, out, visiting_values);
738 }
739 for item in suffix {
740 collect_pattern_direct_calls(item, functions, bindings, out, visiting_values);
741 }
742 }
743 runtime::Pattern::Record { fields, spread, .. } => {
744 for field in fields {
745 collect_pattern_direct_calls(
746 &field.pattern,
747 functions,
748 bindings,
749 out,
750 visiting_values,
751 );
752 if let Some(default) = &field.default {
753 collect_expr_direct_calls_inner(
754 default,
755 functions,
756 bindings,
757 out,
758 visiting_values,
759 );
760 }
761 }
762 if let Some(spread) = spread {
763 match spread {
764 runtime::RecordSpreadPattern::Head(pattern)
765 | runtime::RecordSpreadPattern::Tail(pattern) => {
766 collect_pattern_direct_calls(
767 pattern,
768 functions,
769 bindings,
770 out,
771 visiting_values,
772 );
773 }
774 }
775 }
776 }
777 runtime::Pattern::Variant {
778 value: Some(value), ..
779 }
780 | runtime::Pattern::As { pattern: value, .. } => {
781 collect_pattern_direct_calls(value, functions, bindings, out, visiting_values);
782 }
783 runtime::Pattern::Or { left, right, .. } => {
784 collect_pattern_direct_calls(left, functions, bindings, out, visiting_values);
785 collect_pattern_direct_calls(right, functions, bindings, out, visiting_values);
786 }
787 runtime::Pattern::Wildcard { .. }
788 | runtime::Pattern::Bind { .. }
789 | runtime::Pattern::Lit { .. }
790 | runtime::Pattern::Variant { value: None, .. } => {}
791 }
792}
793
794fn unused_pure_let(
795 pattern: &runtime::Pattern,
796 value: &runtime::Expr,
797 rest: &[runtime::Stmt],
798 tail: Option<&runtime::Expr>,
799 functions: &HashMap<typed_ir::Path, FunctionInfo>,
800 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
801) -> bool {
802 let bound = pattern_bound_paths(pattern);
803 !bound.is_empty()
804 && pure_unused_expr(value, functions, bindings, &mut HashSet::new())
805 && !stmts_or_tail_use_any_path(rest, tail, &bound)
806}
807
808fn pattern_bound_paths(pattern: &runtime::Pattern) -> HashSet<typed_ir::Path> {
809 let mut paths = HashSet::new();
810 collect_pattern_bound_paths(pattern, &mut paths);
811 paths
812}
813
814fn collect_pattern_bound_paths(pattern: &runtime::Pattern, out: &mut HashSet<typed_ir::Path>) {
815 match pattern {
816 runtime::Pattern::Bind { name, .. } => {
817 out.insert(typed_ir::Path::from_name(name.clone()));
818 }
819 runtime::Pattern::Tuple { items, .. } => {
820 for item in items {
821 collect_pattern_bound_paths(item, out);
822 }
823 }
824 runtime::Pattern::List {
825 prefix,
826 spread,
827 suffix,
828 ..
829 } => {
830 for item in prefix {
831 collect_pattern_bound_paths(item, out);
832 }
833 if let Some(spread) = spread {
834 collect_pattern_bound_paths(spread, out);
835 }
836 for item in suffix {
837 collect_pattern_bound_paths(item, out);
838 }
839 }
840 runtime::Pattern::Record { fields, spread, .. } => {
841 for field in fields {
842 collect_pattern_bound_paths(&field.pattern, out);
843 }
844 if let Some(spread) = spread {
845 match spread {
846 runtime::RecordSpreadPattern::Head(pattern)
847 | runtime::RecordSpreadPattern::Tail(pattern) => {
848 collect_pattern_bound_paths(pattern, out);
849 }
850 }
851 }
852 }
853 runtime::Pattern::Variant {
854 value: Some(value), ..
855 }
856 | runtime::Pattern::As { pattern: value, .. } => {
857 collect_pattern_bound_paths(value, out);
858 }
859 runtime::Pattern::Or { left, right, .. } => {
860 collect_pattern_bound_paths(left, out);
861 collect_pattern_bound_paths(right, out);
862 }
863 runtime::Pattern::Wildcard { .. }
864 | runtime::Pattern::Lit { .. }
865 | runtime::Pattern::Variant { value: None, .. } => {}
866 }
867}
868
869fn pure_unused_expr(
870 expr: &runtime::Expr,
871 functions: &HashMap<typed_ir::Path, FunctionInfo>,
872 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
873 stack: &mut HashSet<typed_ir::Path>,
874) -> bool {
875 if let Some((op, args)) = primitive_apply(expr) {
876 return args.len() == primitive_arity(op)
877 && args
878 .into_iter()
879 .all(|arg| pure_unused_expr(arg, functions, bindings, stack));
880 }
881 if let Ok(Some((target, _, args))) = direct_apply_path(expr, functions) {
882 if !args
883 .iter()
884 .all(|arg| pure_unused_expr(arg, functions, bindings, stack))
885 {
886 return false;
887 }
888 let Some(binding) = bindings.get(target) else {
889 return false;
890 };
891 let (params, body) = collect_lambda_params(&binding.body);
892 if params.len() != args.len() || !stack.insert(target.clone()) {
893 return false;
894 }
895 let pure = pure_unused_expr(body, functions, bindings, stack);
896 stack.remove(target);
897 return pure;
898 }
899
900 match &expr.kind {
901 runtime::ExprKind::Var(_)
902 | runtime::ExprKind::EffectOp(_)
903 | runtime::ExprKind::PrimitiveOp(_)
904 | runtime::ExprKind::Lit(_)
905 | runtime::ExprKind::Lambda { .. }
906 | runtime::ExprKind::PeekId
907 | runtime::ExprKind::FindId { .. } => true,
908 runtime::ExprKind::Tuple(items) => items
909 .iter()
910 .all(|item| pure_unused_expr(item, functions, bindings, stack)),
911 runtime::ExprKind::Record { fields, spread } => {
912 spread.is_none()
913 && fields
914 .iter()
915 .all(|field| pure_unused_expr(&field.value, functions, bindings, stack))
916 }
917 runtime::ExprKind::Variant { value, .. } => value
918 .as_deref()
919 .is_none_or(|value| pure_unused_expr(value, functions, bindings, stack)),
920 runtime::ExprKind::BindHere { expr }
921 | runtime::ExprKind::Thunk { expr, .. }
922 | runtime::ExprKind::LocalPushId { body: expr, .. }
923 | runtime::ExprKind::AddId { thunk: expr, .. }
924 | runtime::ExprKind::Coerce { expr, .. }
925 | runtime::ExprKind::Pack { expr, .. } => {
926 pure_unused_expr(expr, functions, bindings, stack)
927 }
928 runtime::ExprKind::Apply { .. }
929 | runtime::ExprKind::If { .. }
930 | runtime::ExprKind::Select { .. }
931 | runtime::ExprKind::Match { .. }
932 | runtime::ExprKind::Block { .. }
933 | runtime::ExprKind::Handle { .. } => false,
934 }
935}
936
937fn stmts_or_tail_use_any_path(
938 stmts: &[runtime::Stmt],
939 tail: Option<&runtime::Expr>,
940 paths: &HashSet<typed_ir::Path>,
941) -> bool {
942 stmts.iter().any(|stmt| stmt_uses_any_path(stmt, paths))
943 || tail.is_some_and(|tail| expr_uses_any_path(tail, paths))
944}
945
946fn stmt_uses_any_path(stmt: &runtime::Stmt, paths: &HashSet<typed_ir::Path>) -> bool {
947 match stmt {
948 runtime::Stmt::Let { pattern, value } => {
949 pattern_default_uses_any_path(pattern, paths) || expr_uses_any_path(value, paths)
950 }
951 runtime::Stmt::Expr(expr) | runtime::Stmt::Module { body: expr, .. } => {
952 expr_uses_any_path(expr, paths)
953 }
954 }
955}
956
957fn pattern_default_uses_any_path(
958 pattern: &runtime::Pattern,
959 paths: &HashSet<typed_ir::Path>,
960) -> bool {
961 match pattern {
962 runtime::Pattern::Tuple { items, .. } => items
963 .iter()
964 .any(|item| pattern_default_uses_any_path(item, paths)),
965 runtime::Pattern::List {
966 prefix,
967 spread,
968 suffix,
969 ..
970 } => {
971 prefix
972 .iter()
973 .any(|item| pattern_default_uses_any_path(item, paths))
974 || spread
975 .as_deref()
976 .is_some_and(|spread| pattern_default_uses_any_path(spread, paths))
977 || suffix
978 .iter()
979 .any(|item| pattern_default_uses_any_path(item, paths))
980 }
981 runtime::Pattern::Record { fields, spread, .. } => {
982 fields.iter().any(|field| {
983 pattern_default_uses_any_path(&field.pattern, paths)
984 || field
985 .default
986 .as_ref()
987 .is_some_and(|default| expr_uses_any_path(default, paths))
988 }) || spread.as_ref().is_some_and(|spread| match spread {
989 runtime::RecordSpreadPattern::Head(pattern)
990 | runtime::RecordSpreadPattern::Tail(pattern) => {
991 pattern_default_uses_any_path(pattern, paths)
992 }
993 })
994 }
995 runtime::Pattern::Variant {
996 value: Some(value), ..
997 }
998 | runtime::Pattern::As { pattern: value, .. } => {
999 pattern_default_uses_any_path(value, paths)
1000 }
1001 runtime::Pattern::Or { left, right, .. } => {
1002 pattern_default_uses_any_path(left, paths)
1003 || pattern_default_uses_any_path(right, paths)
1004 }
1005 runtime::Pattern::Wildcard { .. }
1006 | runtime::Pattern::Bind { .. }
1007 | runtime::Pattern::Lit { .. }
1008 | runtime::Pattern::Variant { value: None, .. } => false,
1009 }
1010}
1011
1012fn expr_uses_any_path(expr: &runtime::Expr, paths: &HashSet<typed_ir::Path>) -> bool {
1013 match &expr.kind {
1014 runtime::ExprKind::Var(path) => paths.contains(path),
1015 runtime::ExprKind::Lambda { body, .. }
1016 | runtime::ExprKind::Thunk { expr: body, .. }
1017 | runtime::ExprKind::LocalPushId { body, .. }
1018 | runtime::ExprKind::BindHere { expr: body }
1019 | runtime::ExprKind::AddId { thunk: body, .. }
1020 | runtime::ExprKind::Coerce { expr: body, .. }
1021 | runtime::ExprKind::Pack { expr: body, .. } => expr_uses_any_path(body, paths),
1022 runtime::ExprKind::Apply { callee, arg, .. } => {
1023 expr_uses_any_path(callee, paths) || expr_uses_any_path(arg, paths)
1024 }
1025 runtime::ExprKind::If {
1026 cond,
1027 then_branch,
1028 else_branch,
1029 ..
1030 } => {
1031 expr_uses_any_path(cond, paths)
1032 || expr_uses_any_path(then_branch, paths)
1033 || expr_uses_any_path(else_branch, paths)
1034 }
1035 runtime::ExprKind::Tuple(items) => items.iter().any(|item| expr_uses_any_path(item, paths)),
1036 runtime::ExprKind::Record { fields, spread } => {
1037 fields
1038 .iter()
1039 .any(|field| expr_uses_any_path(&field.value, paths))
1040 || spread.as_ref().is_some_and(|spread| match spread {
1041 runtime::RecordSpreadExpr::Head(expr)
1042 | runtime::RecordSpreadExpr::Tail(expr) => expr_uses_any_path(expr, paths),
1043 })
1044 }
1045 runtime::ExprKind::Variant {
1046 value: Some(value), ..
1047 }
1048 | runtime::ExprKind::Select { base: value, .. } => expr_uses_any_path(value, paths),
1049 runtime::ExprKind::Match {
1050 scrutinee, arms, ..
1051 } => {
1052 expr_uses_any_path(scrutinee, paths)
1053 || arms.iter().any(|arm| {
1054 pattern_default_uses_any_path(&arm.pattern, paths)
1055 || arm
1056 .guard
1057 .as_ref()
1058 .is_some_and(|guard| expr_uses_any_path(guard, paths))
1059 || expr_uses_any_path(&arm.body, paths)
1060 })
1061 }
1062 runtime::ExprKind::Block { stmts, tail } => {
1063 stmts_or_tail_use_any_path(stmts, tail.as_deref(), paths)
1064 }
1065 runtime::ExprKind::Handle { body, arms, .. } => {
1066 expr_uses_any_path(body, paths)
1067 || arms.iter().any(|arm| {
1068 pattern_default_uses_any_path(&arm.payload, paths)
1069 || arm
1070 .guard
1071 .as_ref()
1072 .is_some_and(|guard| expr_uses_any_path(guard, paths))
1073 || expr_uses_any_path(&arm.body, paths)
1074 })
1075 }
1076 runtime::ExprKind::EffectOp(_)
1077 | runtime::ExprKind::PrimitiveOp(_)
1078 | runtime::ExprKind::Lit(_)
1079 | runtime::ExprKind::Variant { value: None, .. }
1080 | runtime::ExprKind::PeekId
1081 | runtime::ExprKind::FindId { .. } => false,
1082 }
1083}
1084
1085fn direct_apply_target<'expr>(
1086 expr: &'expr runtime::Expr,
1087 functions: &HashMap<typed_ir::Path, FunctionInfo>,
1088) -> Option<(typed_ir::Path, Vec<&'expr runtime::Expr>)> {
1089 direct_apply_path(expr, functions)
1090 .ok()
1091 .flatten()
1092 .map(|(path, _, args)| (path.clone(), args))
1093}
1094
1095fn inline_thunk_handler_apply(
1096 expr: &runtime::Expr,
1097 functions: &HashMap<typed_ir::Path, FunctionInfo>,
1098 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
1099) -> Option<(runtime::Expr, Vec<runtime::HandleArm>, Vec<typed_ir::Path>)> {
1100 let (target, _, args) = direct_apply_path(expr, functions).ok()??;
1101 if args.len() != 1 {
1102 return None;
1103 }
1104 let binding = bindings.get(target)?;
1105 if binding_has_self_direct_call(target, &binding.body, functions) {
1106 return None;
1107 }
1108 let (params, body) = collect_lambda_params(&binding.body);
1109 if params.len() != 1 {
1110 return None;
1111 }
1112 let (handled_body, arms, handler) = handler_wrapper_handle(body)?;
1113 let handled_body = handle_body_execution_inner(handled_body).unwrap_or(handled_body);
1114 let handled_body = transparent_expr(handled_body);
1115 let runtime::ExprKind::Var(body_var) = &handled_body.kind else {
1116 return None;
1117 };
1118 if body_var != &typed_ir::Path::from_name(params[0].clone()) {
1119 return None;
1120 }
1121 Some((args[0].clone(), arms.to_vec(), handler.consumes.clone()))
1122}
1123
1124fn transparent_expr(expr: &runtime::Expr) -> &runtime::Expr {
1125 let mut current = expr;
1126 loop {
1127 match ¤t.kind {
1128 runtime::ExprKind::Coerce { expr, .. }
1129 | runtime::ExprKind::Pack { expr, .. }
1130 | runtime::ExprKind::AddId { thunk: expr, .. } => current = expr,
1131 _ => return current,
1132 }
1133 }
1134}
1135
1136fn lower_binding(
1137 binding: &runtime::Binding,
1138 functions: &HashMap<typed_ir::Path, FunctionInfo>,
1139 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
1140) -> CpsLowerResult<CpsFunction> {
1141 if !binding.type_params.is_empty() {
1142 return Err(CpsLowerError::UnsupportedBinding {
1143 path: binding.name.clone(),
1144 reason: "residual type parameters",
1145 });
1146 }
1147 if let runtime::ExprKind::PrimitiveOp(op) = binding.body.kind {
1148 return Ok(lower_primitive_binding(&binding.name, op));
1149 }
1150 let (params, body) = collect_callable_params(&binding.body);
1151 if params.is_empty() {
1152 return Err(CpsLowerError::UnsupportedBinding {
1153 path: binding.name.clone(),
1154 reason: "non-function body",
1155 });
1156 }
1157 let traits = FunctionLoweringTraits::for_body(&binding.body, functions);
1158 FunctionLowerer::new(
1159 path_name(&binding.name),
1160 functions,
1161 bindings,
1162 params,
1163 traits,
1164 )
1165 .lower_root(&body)
1166 .map_err(|error| match error {
1167 CpsLowerError::UnsupportedExpr { kind } => CpsLowerError::UnsupportedBinding {
1168 path: binding.name.clone(),
1169 reason: kind,
1170 },
1171 error => error,
1172 })
1173}
1174
1175fn binding_function_info(binding: &runtime::Binding) -> Option<(typed_ir::Path, FunctionInfo)> {
1176 if let runtime::ExprKind::PrimitiveOp(op) = binding.body.kind {
1177 let arity = primitive_arity(op);
1178 return Some((
1179 binding.name.clone(),
1180 FunctionInfo {
1181 path: binding.name.clone(),
1182 name: path_name(&binding.name),
1183 arity,
1184 params: vec![runtime::Type::unknown(); arity],
1185 ret: runtime::Type::unknown(),
1186 },
1187 ));
1188 }
1189 let (params, body) = collect_callable_params(&binding.body);
1190 if params.is_empty() {
1191 return None;
1192 }
1193 let param_types = collect_fun_param_types(&binding.body, params.len());
1194 Some((
1195 binding.name.clone(),
1196 FunctionInfo {
1197 path: binding.name.clone(),
1198 name: path_name(&binding.name),
1199 arity: params.len(),
1200 params: param_types,
1201 ret: body.ty.clone(),
1202 },
1203 ))
1204}
1205
1206fn binding_value_body(binding: &runtime::Binding) -> Option<&runtime::Expr> {
1207 if matches!(binding.body.kind, runtime::ExprKind::PrimitiveOp(_)) {
1208 return None;
1209 }
1210 collect_callable_params(&binding.body)
1211 .0
1212 .is_empty()
1213 .then_some(&binding.body)
1214}
1215
1216fn binding_is_throw_forwarder(binding: &runtime::Binding) -> bool {
1217 let (params, body) = collect_callable_params(&binding.body);
1218 let [param] = params.as_slice() else {
1219 return false;
1220 };
1221 let body = transparent_effect_expr(&body);
1222 let runtime::ExprKind::Apply { callee, arg, .. } = &body.kind else {
1223 return false;
1224 };
1225 let callee = transparent_effect_expr(callee);
1226 let runtime::ExprKind::Var(callee) = &callee.kind else {
1227 return false;
1228 };
1229 if !throw_role_method_path(callee) {
1230 return false;
1231 }
1232 let arg = transparent_effect_expr(arg);
1233 matches!(&arg.kind, runtime::ExprKind::Var(path) if path == &typed_ir::Path::from_name(param.clone()))
1234}
1235
1236fn collect_fun_param_types(expr: &runtime::Expr, expected: usize) -> Vec<runtime::Type> {
1240 let mut params = Vec::with_capacity(expected);
1241 let mut current = expr;
1242 while params.len() < expected {
1243 match ¤t.kind {
1244 runtime::ExprKind::Lambda { body, .. } => {
1245 let arg_ty = match ¤t.ty {
1246 runtime::Type::Fun { param, .. } => (**param).clone(),
1247 _ => runtime::Type::unknown(),
1248 };
1249 params.push(arg_ty);
1250 current = body;
1251 }
1252 runtime::ExprKind::Block {
1253 tail: Some(tail), ..
1254 } => {
1255 current = tail;
1256 }
1257 runtime::ExprKind::Coerce { expr, .. }
1258 | runtime::ExprKind::Pack { expr, .. }
1259 | runtime::ExprKind::BindHere { expr } => {
1260 current = expr;
1261 }
1262 _ => break,
1263 }
1264 }
1265 while params.len() < expected {
1266 params.push(runtime::Type::unknown());
1267 }
1268 params
1269}
1270
1271fn lower_primitive_binding(path: &typed_ir::Path, op: typed_ir::PrimitiveOp) -> CpsFunction {
1272 let arity = primitive_arity(op);
1273 let params = (0..arity).map(CpsValueId).collect::<Vec<_>>();
1274 let dest = CpsValueId(arity);
1275 CpsFunction {
1276 name: path_name(path),
1277 params: params.clone(),
1278 entry: CpsContinuationId(0),
1279 continuations: vec![CpsContinuation {
1280 id: CpsContinuationId(0),
1281 params: params.clone(),
1282 captures: Vec::new(),
1283 shot_kind: CpsShotKind::MultiShot,
1284 stmts: vec![CpsStmt::Primitive {
1285 dest,
1286 op,
1287 args: params,
1288 }],
1289 terminator: CpsTerminator::Return(dest),
1290 }],
1291 handlers: Vec::new(),
1292 }
1293}
1294
1295#[derive(Debug, Clone, PartialEq, Eq)]
1296struct FunctionInfo {
1297 path: typed_ir::Path,
1298 name: String,
1299 arity: usize,
1300 params: Vec<runtime::Type>,
1306 ret: runtime::Type,
1307}
1308
1309#[derive(Debug, Clone, Copy, Default)]
1310struct FunctionLoweringTraits {
1311 higher_order_helper: bool,
1312}
1313
1314impl FunctionLoweringTraits {
1315 fn for_body(body: &runtime::Expr, functions: &HashMap<typed_ir::Path, FunctionInfo>) -> Self {
1316 Self {
1317 higher_order_helper: expr_contains_indirect_apply(body, functions),
1318 }
1319 }
1320}
1321
1322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1323enum DirectCallMode {
1324 SyncDirect,
1325 EffectfulWithResume,
1326}
1327
1328#[derive(Debug)]
1329struct DirectCallPlan<'expr, 'functions> {
1330 expr: &'expr runtime::Expr,
1331 target: String,
1332 info: &'functions FunctionInfo,
1333 args: Vec<&'expr runtime::Expr>,
1334 mode: DirectCallMode,
1335 target_may_perform: bool,
1336 info_returns_thunk: bool,
1337 force_handler_reentry_args: bool,
1338 should_inline: bool,
1339}
1340
1341#[derive(Debug, Clone, Default)]
1342struct DepthCounter {
1343 value: Rc<Cell<usize>>,
1344}
1345
1346impl DepthCounter {
1347 fn is_active(&self) -> bool {
1348 self.value.get() > 0
1349 }
1350
1351 fn is_inactive(&self) -> bool {
1352 self.value.get() == 0
1353 }
1354
1355 fn enter(&self) -> DepthGuard {
1356 self.value.set(self.value.get() + 1);
1357 DepthGuard {
1358 value: Rc::clone(&self.value),
1359 }
1360 }
1361}
1362
1363struct DepthGuard {
1364 value: Rc<Cell<usize>>,
1365}
1366
1367impl Drop for DepthGuard {
1368 fn drop(&mut self) {
1369 let current = self.value.get();
1370 debug_assert!(current > 0, "depth counter underflow");
1371 self.value.set(current.saturating_sub(1));
1372 }
1373}
1374
1375enum ExprLowerCase<'expr, 'functions> {
1376 InlineThunkHandler {
1377 body: runtime::Expr,
1378 arms: Vec<runtime::HandleArm>,
1379 consumes: Vec<typed_ir::Path>,
1380 },
1381 LocalExprApply {
1382 callee: runtime::Expr,
1383 arg: &'expr runtime::Expr,
1384 },
1385 EffectRequest(CpsEffectApply<'expr>),
1386 BindHere {
1387 expr: &'expr runtime::Expr,
1388 },
1389 Primitive {
1390 op: typed_ir::PrimitiveOp,
1391 args: Vec<&'expr runtime::Expr>,
1392 },
1393 PartialDirectApply {
1394 target_path: &'expr typed_ir::Path,
1395 info: &'functions FunctionInfo,
1396 args: Vec<&'expr runtime::Expr>,
1397 },
1398 DirectApply {
1399 target_path: &'expr typed_ir::Path,
1400 info: &'functions FunctionInfo,
1401 args: Vec<&'expr runtime::Expr>,
1402 },
1403 ResumeApply {
1404 resumption: CpsValueId,
1405 arg: &'expr runtime::Expr,
1406 },
1407 PlainExprKind,
1408}
1409
1410struct FunctionLowerer<'a> {
1411 name: String,
1412 functions: &'a HashMap<typed_ir::Path, FunctionInfo>,
1413 bindings: &'a HashMap<typed_ir::Path, &'a runtime::Binding>,
1414 next_value: usize,
1415 next_continuation: usize,
1416 next_handler: usize,
1417 continuations: Vec<CpsContinuation>,
1418 handlers: Vec<CpsHandler>,
1419 forced_handler_effects: Vec<(CpsHandlerId, typed_ir::Path)>,
1420 handlers_with_external_calls: HashSet<CpsHandlerId>,
1421 current: ContinuationBuilder,
1422 locals: HashMap<typed_ir::Path, CpsValueId>,
1423 function_param_values: HashMap<typed_ir::Path, CpsValueId>,
1424 local_exprs: HashMap<typed_ir::Path, runtime::Expr>,
1425 effect_guards: HashMap<runtime::EffectIdVar, CpsValueId>,
1426 resumptions: HashSet<typed_ir::Path>,
1427 inline_stack: HashSet<typed_ir::Path>,
1428 active_handler: Option<ActiveHandlerContext>,
1429 params: Vec<CpsValueId>,
1430 handler_value_conts: Vec<CpsContinuationId>,
1431 force_effectful_apply_depth: DepthCounter,
1432 sync_apply_for_immediate_force_depth: DepthCounter,
1433 sync_direct_call_for_ignored_force_depth: DepthCounter,
1434 higher_order_helper: bool,
1438}
1439
1440#[derive(Clone)]
1441struct ActiveHandlerContext {
1442 handler: CpsHandlerId,
1443 expected_effects: Vec<typed_ir::Path>,
1444 parent: Option<Box<ActiveHandlerContext>>,
1445}
1446
1447#[derive(Clone, Copy)]
1448struct EffectHandlerArmChain<'a> {
1449 effect: &'a typed_ir::Path,
1450 payload: CpsValueId,
1451 resume: CpsValueId,
1452 handler: CpsHandlerId,
1453 saved_locals: &'a HashMap<typed_ir::Path, CpsValueId>,
1454 saved_local_exprs: &'a HashMap<typed_ir::Path, runtime::Expr>,
1455 saved_resumptions: &'a HashSet<typed_ir::Path>,
1456}
1457
1458impl<'a> FunctionLowerer<'a> {
1459 fn mark_active_handlers_external_call(&mut self) {
1460 let mut current = self.active_handler.clone();
1461 while let Some(context) = current {
1462 self.handlers_with_external_calls.insert(context.handler);
1463 current = context.parent.as_deref().cloned();
1464 }
1465 }
1466}
1467
1468impl<'a> FunctionLowerer<'a> {
1469 fn new(
1470 name: String,
1471 functions: &'a HashMap<typed_ir::Path, FunctionInfo>,
1472 bindings: &'a HashMap<typed_ir::Path, &'a runtime::Binding>,
1473 params: Vec<typed_ir::Name>,
1474 traits: FunctionLoweringTraits,
1475 ) -> Self {
1476 let mut next_value = 0;
1477 let mut param_values = Vec::with_capacity(params.len());
1478 let mut locals = HashMap::new();
1479 let mut function_param_values = HashMap::new();
1480 for param in params {
1481 let value = CpsValueId(next_value);
1482 next_value += 1;
1483 let path = typed_ir::Path::from_name(param);
1484 locals.insert(path.clone(), value);
1485 function_param_values.insert(path, value);
1486 param_values.push(value);
1487 }
1488 Self {
1489 name,
1490 functions,
1491 bindings,
1492 next_value,
1493 next_continuation: 1,
1494 next_handler: 0,
1495 continuations: Vec::new(),
1496 handlers: Vec::new(),
1497 forced_handler_effects: Vec::new(),
1498 handlers_with_external_calls: HashSet::new(),
1499 current: ContinuationBuilder::new(CpsContinuationId(0), param_values.clone()),
1500 locals,
1501 function_param_values,
1502 local_exprs: HashMap::new(),
1503 effect_guards: HashMap::new(),
1504 resumptions: HashSet::new(),
1505 inline_stack: HashSet::new(),
1506 active_handler: None,
1507 params: param_values,
1508 handler_value_conts: Vec::new(),
1509 force_effectful_apply_depth: DepthCounter::default(),
1510 sync_apply_for_immediate_force_depth: DepthCounter::default(),
1511 sync_direct_call_for_ignored_force_depth: DepthCounter::default(),
1512 higher_order_helper: traits.higher_order_helper,
1513 }
1514 }
1515
1516 fn lower_root(mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsFunction> {
1517 let value = self.lower_expr(expr)?;
1518 let value = self.force_if_non_thunk_demand(value, &expr.ty);
1524 self.terminate(CpsTerminator::Return(value));
1525 self.finish_current();
1526 Ok(CpsFunction {
1527 name: self.name,
1528 params: self.params,
1529 entry: CpsContinuationId(0),
1530 continuations: self.continuations,
1531 handlers: self.handlers,
1532 })
1533 }
1534
1535 fn lower_expr(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1536 let lower_case = self.classify_expr(expr)?;
1537 self.lower_classified_expr(expr, lower_case)
1538 }
1539
1540 fn lower_classified_expr(
1541 &mut self,
1542 expr: &runtime::Expr,
1543 lower_case: ExprLowerCase<'_, '_>,
1544 ) -> CpsLowerResult<CpsValueId> {
1545 match lower_case {
1546 ExprLowerCase::InlineThunkHandler {
1547 body,
1548 arms,
1549 consumes,
1550 } => self.lower_handle(&body, &arms, &consumes),
1551 ExprLowerCase::LocalExprApply { callee, arg } => {
1552 self.lower_local_expr_apply_case(&callee, arg, &expr.ty)
1553 }
1554 ExprLowerCase::EffectRequest(request) => {
1555 let (expected_effects, handler) =
1556 self.effect_context_for_request(&request, &[], dynamic_handler_id());
1557 let (_, value) =
1558 self.begin_resume_after_perform(request, &expected_effects, handler)?;
1559 Ok(value)
1560 }
1561 ExprLowerCase::BindHere { expr } => self.lower_bind_here(expr),
1562 ExprLowerCase::Primitive { op, args } => self.lower_primitive_apply_case(op, args),
1563 ExprLowerCase::PartialDirectApply {
1564 target_path,
1565 info,
1566 args,
1567 } => self.lower_partial_direct_apply(target_path, info, args),
1568 ExprLowerCase::DirectApply {
1569 target_path,
1570 info,
1571 args,
1572 } => {
1573 let plan = self.plan_direct_call(expr, target_path, info, args);
1574 self.lower_direct_call_plan(plan)
1575 }
1576 ExprLowerCase::ResumeApply { resumption, arg } => {
1577 let arg = self.lower_expr(arg)?;
1578 let dest = self.fresh_value();
1579 self.current.stmts.push(CpsStmt::Resume {
1580 dest,
1581 resumption,
1582 arg,
1583 });
1584 Ok(dest)
1585 }
1586 ExprLowerCase::PlainExprKind => self.lower_expr_kind(expr),
1587 }
1588 }
1589
1590 fn lower_expr_kind(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1591 match &expr.kind {
1592 runtime::ExprKind::Lit(lit) => {
1593 let dest = self.fresh_value();
1594 self.current.stmts.push(CpsStmt::Literal {
1595 dest,
1596 literal: lower_literal(lit),
1597 });
1598 Ok(dest)
1599 }
1600 runtime::ExprKind::PrimitiveOp(_) => Err(CpsLowerError::UnsupportedExpr {
1601 kind: "bare primitive",
1602 }),
1603 runtime::ExprKind::Var(path) => {
1604 if let Some(expr) = self.local_exprs.get(path).cloned() {
1605 return self.lower_expr(&inline_callable_expr(&expr));
1606 }
1607 if let Some(value) = self.locals.get(path).copied() {
1608 return Ok(value);
1609 }
1610 if let Some(info) = self.functions.get(path) {
1611 return self.lower_function_value(path, info);
1612 }
1613 if let Some(expr) = self.bindings.get(path).and_then(|binding| {
1614 binding_value_body(binding).filter(
1615 |body| !matches!(&body.kind, runtime::ExprKind::Var(inner) if inner == path),
1616 )
1617 }) {
1618 let expr = expr.clone();
1619 if !self.inline_stack.insert(path.clone()) {
1620 return Err(CpsLowerError::UnsupportedFreeVar { path: path.clone() });
1621 }
1622 let value = self.lower_expr(&expr);
1623 self.inline_stack.remove(path);
1624 return value;
1625 }
1626 Err(CpsLowerError::UnsupportedFreeVar { path: path.clone() })
1627 }
1628 runtime::ExprKind::If {
1629 cond,
1630 then_branch,
1631 else_branch,
1632 ..
1633 } => self.lower_if(cond, then_branch, else_branch),
1634 runtime::ExprKind::Block { stmts, tail } => self.lower_block(stmts, tail.as_deref()),
1635 runtime::ExprKind::EffectOp(path) => {
1636 Err(CpsLowerError::UnsupportedBareEffectOp { path: path.clone() })
1637 }
1638 runtime::ExprKind::Lambda { param, body, .. } => self.lower_lambda(param, body),
1639 runtime::ExprKind::Apply { callee, arg, .. } => self.lower_apply(expr, callee, arg),
1640 runtime::ExprKind::Tuple(items) => self.lower_tuple(items),
1641 runtime::ExprKind::Record { fields, spread } => self.lower_record(fields, spread),
1642 runtime::ExprKind::Variant { tag, value } => self.lower_variant(tag, value.as_deref()),
1643 runtime::ExprKind::Select { base, field } => self.lower_select(base, field),
1644 runtime::ExprKind::Match { .. } => {
1645 if let Some((cond, then_branch, else_branch)) = bool_match(expr) {
1646 self.lower_if(cond, then_branch, else_branch)
1647 } else {
1648 self.lower_match(expr)
1649 }
1650 }
1651 runtime::ExprKind::Handle {
1652 body,
1653 arms,
1654 handler,
1655 ..
1656 } => self.lower_handle(body, arms, &handler.consumes),
1657 runtime::ExprKind::BindHere { expr } => self.lower_bind_here(expr),
1658 runtime::ExprKind::Thunk { expr, .. } => self.lower_thunk(expr),
1659 runtime::ExprKind::LocalPushId { id, body } => {
1660 let dest = self.fresh_value();
1661 self.current
1662 .stmts
1663 .push(CpsStmt::FreshGuard { dest, var: *id });
1664 let previous = self.effect_guards.insert(*id, dest);
1665 let result = self.lower_expr(body);
1666 restore_effect_guard(&mut self.effect_guards, *id, previous);
1667 result
1668 }
1669 runtime::ExprKind::AddId {
1670 id,
1671 allowed,
1672 active,
1673 thunk,
1674 } => {
1675 let thunk = self.lower_expr(thunk)?;
1676 let guard = self.lower_effect_id_ref(*id)?;
1677 let dest = self.fresh_value();
1678 self.current.stmts.push(CpsStmt::AddThunkBoundary {
1679 dest,
1680 thunk,
1681 guard,
1682 allowed: allowed.clone(),
1683 active: *active,
1684 });
1685 Ok(dest)
1686 }
1687 runtime::ExprKind::Coerce { expr, .. } | runtime::ExprKind::Pack { expr, .. } => {
1688 self.lower_expr(expr)
1689 }
1690 runtime::ExprKind::PeekId => {
1691 let dest = self.fresh_value();
1692 self.current.stmts.push(CpsStmt::PeekGuard { dest });
1693 Ok(dest)
1694 }
1695 runtime::ExprKind::FindId { id } => {
1696 let guard = self.lower_effect_id_ref(*id)?;
1697 let dest = self.fresh_value();
1698 self.current.stmts.push(CpsStmt::FindGuard { dest, guard });
1699 Ok(dest)
1700 }
1701 }
1702 }
1703
1704 fn classify_expr<'expr>(
1705 &self,
1706 expr: &'expr runtime::Expr,
1707 ) -> CpsLowerResult<ExprLowerCase<'expr, 'a>> {
1708 if let Some((body, arms, consumes)) =
1709 inline_thunk_handler_apply(expr, self.functions, self.bindings)
1710 {
1711 return Ok(ExprLowerCase::InlineThunkHandler {
1712 body,
1713 arms,
1714 consumes,
1715 });
1716 }
1717 if let Some((callee, arg)) = self.local_expr_apply_case(expr) {
1718 return Ok(ExprLowerCase::LocalExprApply { callee, arg });
1719 }
1720 if let Some(request) = effect_apply_body_request(expr) {
1721 return Ok(ExprLowerCase::EffectRequest(request));
1722 }
1723 if let runtime::ExprKind::BindHere { expr } = &expr.kind {
1724 return Ok(ExprLowerCase::BindHere { expr });
1725 }
1726 if let Some((op, args)) = primitive_apply(expr) {
1727 return Ok(ExprLowerCase::Primitive { op, args });
1728 }
1729 if let Some((target_path, info, args)) = partial_direct_apply_path(expr, self.functions)? {
1730 return Ok(ExprLowerCase::PartialDirectApply {
1731 target_path,
1732 info,
1733 args,
1734 });
1735 }
1736 if let Some((target_path, info, args)) = direct_apply_path(expr, self.functions)? {
1737 return Ok(ExprLowerCase::DirectApply {
1738 target_path,
1739 info,
1740 args,
1741 });
1742 }
1743 if let Some((resumption, arg)) = self.resume_apply(expr) {
1744 return Ok(ExprLowerCase::ResumeApply { resumption, arg });
1745 }
1746 Ok(ExprLowerCase::PlainExprKind)
1747 }
1748
1749 fn lower_primitive_apply_case(
1750 &mut self,
1751 op: typed_ir::PrimitiveOp,
1752 args: Vec<&runtime::Expr>,
1753 ) -> CpsLowerResult<CpsValueId> {
1754 let expected = primitive_arity(op);
1755 if args.len() != expected {
1756 return Err(CpsLowerError::PrimitiveArityMismatch {
1757 op,
1758 expected,
1759 actual: args.len(),
1760 });
1761 }
1762 let args = args
1763 .into_iter()
1764 .map(|arg| self.lower_expr(arg))
1765 .collect::<CpsLowerResult<Vec<_>>>()?;
1766 let dest = self.fresh_value();
1767 self.current
1768 .stmts
1769 .push(CpsStmt::Primitive { dest, op, args });
1770 Ok(dest)
1771 }
1772
1773 fn lower_effect_id_ref(&mut self, id: runtime::EffectIdRef) -> CpsLowerResult<CpsValueId> {
1774 match id {
1775 runtime::EffectIdRef::Peek => {
1776 let dest = self.fresh_value();
1777 self.current.stmts.push(CpsStmt::PeekGuard { dest });
1778 Ok(dest)
1779 }
1780 runtime::EffectIdRef::Var(var) => {
1781 self.effect_guards
1782 .get(&var)
1783 .copied()
1784 .ok_or(CpsLowerError::UnsupportedExpr {
1785 kind: "effect id variable outside local push scope",
1786 })
1787 }
1788 }
1789 }
1790
1791 fn lower_bind_here(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1792 let thunk = self.with_sync_apply_for_immediate_force_depth(|this| this.lower_expr(expr))?;
1793 let dest = self.fresh_value();
1794 self.current.stmts.push(CpsStmt::ForceThunk { dest, thunk });
1795 Ok(dest)
1796 }
1797
1798 fn lower_thunk(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
1799 let entry = self.fresh_continuation();
1800 let dest = self.fresh_value();
1801 let saved_current = std::mem::replace(
1802 &mut self.current,
1803 ContinuationBuilder::new(entry, Vec::new()),
1804 );
1805 let performed_effects = collect_expr_performed_effects(expr);
1806 let value = if !performed_effects.is_empty() {
1807 let (mut expected_effects, handler) = self.current_effect_context();
1808 if expected_effects.is_empty() {
1809 expected_effects = performed_effects;
1810 }
1811 let value_cont = self.fresh_continuation();
1812 let value = self.fresh_value();
1813 self.lower_handled_body(expr, &expected_effects, handler, Some(value_cont))?;
1814 self.current = ContinuationBuilder::new(value_cont, vec![value]);
1815 value
1816 } else {
1817 self.lower_expr(expr)?
1818 };
1819 self.terminate(CpsTerminator::Return(value));
1820 self.finish_current();
1821 self.current = saved_current;
1822 self.current.stmts.push(CpsStmt::MakeThunk { dest, entry });
1823 Ok(dest)
1824 }
1825
1826 fn lower_lambda(
1827 &mut self,
1828 param: &typed_ir::Name,
1829 body: &runtime::Expr,
1830 ) -> CpsLowerResult<CpsValueId> {
1831 let entry = self.fresh_continuation();
1832 let dest = self.fresh_value();
1833 let param_value = self.fresh_value();
1834 let saved_current = std::mem::replace(
1835 &mut self.current,
1836 ContinuationBuilder::new(entry, vec![param_value]),
1837 );
1838 let saved_locals = self.locals.clone();
1839 let saved_local_exprs = self.local_exprs.clone();
1840 let saved_resumptions = self.resumptions.clone();
1841 let param_path = typed_ir::Path::from_name(param.clone());
1842 self.local_exprs.remove(¶m_path);
1843 self.locals.insert(param_path, param_value);
1844 let value = if let Some(context) = self.active_handler.clone()
1845 && !collect_expr_performed_effects(body).is_empty()
1846 {
1847 let value_cont = self.fresh_continuation();
1848 let value = self.fresh_value();
1849 self.lower_handled_body(
1850 body,
1851 &context.expected_effects,
1852 context.handler,
1853 Some(value_cont),
1854 )?;
1855 self.current = ContinuationBuilder::new(value_cont, vec![value]);
1856 value
1857 } else {
1858 self.lower_expr(body)?
1859 };
1860 let value = self.force_if_non_thunk_demand(value, &body.ty);
1861 self.terminate(CpsTerminator::Return(value));
1862 self.finish_current();
1863 self.locals = saved_locals;
1864 self.local_exprs = saved_local_exprs;
1865 self.resumptions = saved_resumptions;
1866 self.current = saved_current;
1867 self.current
1868 .stmts
1869 .push(CpsStmt::MakeClosure { dest, entry });
1870 Ok(dest)
1871 }
1872
1873 fn lower_recursive_lambda(
1874 &mut self,
1875 name: &typed_ir::Name,
1876 param: &typed_ir::Name,
1877 body: &runtime::Expr,
1878 ) -> CpsLowerResult<CpsValueId> {
1879 let entry = self.fresh_continuation();
1880 let dest = self.fresh_value();
1881 let param_value = self.fresh_value();
1882 let saved_current = std::mem::replace(
1883 &mut self.current,
1884 ContinuationBuilder::new(entry, vec![param_value]),
1885 );
1886 let saved_locals = self.locals.clone();
1887 let saved_local_exprs = self.local_exprs.clone();
1888 let saved_resumptions = self.resumptions.clone();
1889 let self_path = typed_ir::Path::from_name(name.clone());
1890 self.local_exprs.remove(&self_path);
1891 self.locals.insert(self_path, dest);
1892 let param_path = typed_ir::Path::from_name(param.clone());
1893 self.local_exprs.remove(¶m_path);
1894 self.locals.insert(param_path, param_value);
1895 let value = if let Some(context) = self.active_handler.clone()
1896 && !collect_expr_performed_effects(body).is_empty()
1897 {
1898 let value_cont = self.fresh_continuation();
1899 let value = self.fresh_value();
1900 self.lower_handled_body(
1901 body,
1902 &context.expected_effects,
1903 context.handler,
1904 Some(value_cont),
1905 )?;
1906 self.current = ContinuationBuilder::new(value_cont, vec![value]);
1907 value
1908 } else {
1909 self.lower_expr(body)?
1910 };
1911 let value = self.force_if_non_thunk_demand(value, &body.ty);
1912 self.terminate(CpsTerminator::Return(value));
1913 self.finish_current();
1914 self.locals = saved_locals;
1915 self.local_exprs = saved_local_exprs;
1916 self.resumptions = saved_resumptions;
1917 self.current = saved_current;
1918 self.current
1919 .stmts
1920 .push(CpsStmt::MakeRecursiveClosure { dest, entry });
1921 Ok(dest)
1922 }
1923
1924 fn lower_function_value(
1925 &mut self,
1926 path: &typed_ir::Path,
1927 info: &FunctionInfo,
1928 ) -> CpsLowerResult<CpsValueId> {
1929 if info.arity != 1 {
1930 return Err(CpsLowerError::UnsupportedBinding {
1931 path: path.clone(),
1932 reason: "function value with arity other than 1",
1933 });
1934 }
1935 let entry = self.fresh_continuation();
1936 let dest = self.fresh_value();
1937 let param_value = self.fresh_value();
1938 let saved_current = std::mem::replace(
1939 &mut self.current,
1940 ContinuationBuilder::new(entry, vec![param_value]),
1941 );
1942 let result = self.fresh_value();
1943 self.current.stmts.push(CpsStmt::DirectCall {
1944 dest: result,
1945 target: info.name.clone(),
1946 args: vec![param_value],
1947 });
1948 if matches!(info.ret, runtime::Type::Thunk { .. })
1949 || self.target_may_perform_when_called(path)
1950 {
1951 self.mark_active_handlers_external_call();
1952 }
1953 self.terminate(CpsTerminator::Return(result));
1954 self.finish_current();
1955 self.current = saved_current;
1956 self.current
1957 .stmts
1958 .push(CpsStmt::MakeClosure { dest, entry });
1959 Ok(dest)
1960 }
1961
1962 fn lower_apply(
1963 &mut self,
1964 expr: &runtime::Expr,
1965 callee: &runtime::Expr,
1966 arg: &runtime::Expr,
1967 ) -> CpsLowerResult<CpsValueId> {
1968 let closure = self.lower_expr(callee)?;
1969 let callee_ty = callable_type_after_force(&callee.ty);
1970 let forced = self.fresh_value();
1971 self.current.stmts.push(CpsStmt::ForceThunk {
1972 dest: forced,
1973 thunk: closure,
1974 });
1975 let closure = forced;
1976 let arg = self.lower_expr_as_call_arg(callee_ty, arg)?;
1977 if self.force_effectful_apply_depth.is_active()
1984 || (self.sync_apply_for_immediate_force_depth.is_inactive()
1985 && self.higher_order_helper
1986 && matches!(expr.ty, runtime::Type::Thunk { .. }))
1987 {
1988 let post_cont = self.fresh_continuation();
1989 let result = self.fresh_value();
1990 self.terminate(CpsTerminator::EffectfulApply {
1991 closure,
1992 arg,
1993 resume: post_cont,
1994 });
1995 self.finish_current();
1996 self.mark_active_handlers_external_call();
1997 self.current = ContinuationBuilder::new(post_cont, vec![result]);
1998 return Ok(self.force_if_non_thunk_demand(result, &expr.ty));
1999 }
2000 let dest = self.fresh_value();
2001 self.current
2002 .stmts
2003 .push(CpsStmt::ApplyClosure { dest, closure, arg });
2004 Ok(self.force_if_non_thunk_demand(dest, &expr.ty))
2005 }
2006
2007 fn lower_partial_direct_apply(
2008 &mut self,
2009 target_path: &typed_ir::Path,
2010 info: &FunctionInfo,
2011 args: Vec<&runtime::Expr>,
2012 ) -> CpsLowerResult<CpsValueId> {
2013 let info_params = info.params.clone();
2014 let lowered_args = args
2015 .into_iter()
2016 .enumerate()
2017 .map(|(idx, arg)| {
2018 let expected = info_params
2019 .get(idx)
2020 .cloned()
2021 .unwrap_or_else(runtime::Type::unknown);
2022 let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
2023 self.lower_expr_as_thunk_value(arg)?
2024 } else {
2025 self.lower_expr(arg)?
2026 };
2027 Ok(self.force_if_non_thunk_demand(lowered, &expected))
2028 })
2029 .collect::<CpsLowerResult<Vec<_>>>()?;
2030 self.emit_partial_direct_closure(target_path, info, lowered_args)
2031 }
2032
2033 fn emit_partial_direct_closure(
2034 &mut self,
2035 target_path: &typed_ir::Path,
2036 info: &FunctionInfo,
2037 captured_args: Vec<CpsValueId>,
2038 ) -> CpsLowerResult<CpsValueId> {
2039 let entry = self.fresh_continuation();
2040 let dest = self.fresh_value();
2041 let param_value = self.fresh_value();
2042 let saved_current = std::mem::replace(
2043 &mut self.current,
2044 ContinuationBuilder::new(entry, vec![param_value]),
2045 );
2046 let mut call_args = captured_args;
2047 call_args.push(param_value);
2048 let result = if call_args.len() == info.arity {
2049 let result = self.fresh_value();
2050 self.current.stmts.push(CpsStmt::DirectCall {
2051 dest: result,
2052 target: info.name.clone(),
2053 args: call_args,
2054 });
2055 if matches!(info.ret, runtime::Type::Thunk { .. })
2056 || self.target_may_perform_when_called(target_path)
2057 {
2058 self.mark_active_handlers_external_call();
2059 }
2060 result
2061 } else {
2062 self.emit_partial_direct_closure(target_path, info, call_args)?
2063 };
2064 self.terminate(CpsTerminator::Return(result));
2065 self.finish_current();
2066 self.current = saved_current;
2067 self.current
2068 .stmts
2069 .push(CpsStmt::MakeClosure { dest, entry });
2070 Ok(dest)
2071 }
2072
2073 fn lower_expr_as_call_arg(
2078 &mut self,
2079 callee_ty: &runtime::Type,
2080 arg: &runtime::Expr,
2081 ) -> CpsLowerResult<CpsValueId> {
2082 let param_ty = match callee_ty {
2083 runtime::Type::Fun { param, .. } => Some(param.as_ref()),
2084 _ => None,
2085 };
2086 let param_is_thunk = matches!(param_ty, Some(runtime::Type::Thunk { .. }));
2087 if param_is_thunk {
2088 self.lower_expr_as_thunk_value(arg)
2089 } else {
2090 self.lower_expr(arg)
2091 }
2092 }
2093
2094 fn lower_expr_as_thunk_value(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
2103 match &expr.kind {
2109 runtime::ExprKind::Thunk { .. } | runtime::ExprKind::Var(_) => self.lower_expr(expr),
2110 _ if self.expr_contains_resume_apply(expr) => {
2111 self.lower_thunk_for_handler_reentry(expr)
2112 }
2113 _ => self.lower_thunk(expr),
2114 }
2115 }
2116
2117 fn lower_thunk_for_handler_reentry(
2118 &mut self,
2119 expr: &runtime::Expr,
2120 ) -> CpsLowerResult<CpsValueId> {
2121 self.lower_expr_with_forced_effectful_applies_inner(|this| this.lower_thunk(expr))
2122 }
2123
2124 fn lower_expr_with_forced_effectful_applies(
2125 &mut self,
2126 expr: &runtime::Expr,
2127 ) -> CpsLowerResult<CpsValueId> {
2128 self.lower_expr_with_forced_effectful_applies_inner(|this| this.lower_expr(expr))
2129 }
2130
2131 fn lower_expr_with_forced_effectful_applies_inner<T>(
2132 &mut self,
2133 lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2134 ) -> CpsLowerResult<T> {
2135 self.with_force_effectful_apply_depth(lower)
2136 }
2137
2138 fn with_force_effectful_apply_depth<T>(
2139 &mut self,
2140 lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2141 ) -> CpsLowerResult<T> {
2142 let _depth = self.force_effectful_apply_depth.enter();
2143 let result = lower(self);
2144 result
2145 }
2146
2147 fn with_sync_apply_for_immediate_force_depth<T>(
2148 &mut self,
2149 lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2150 ) -> CpsLowerResult<T> {
2151 let _depth = self.sync_apply_for_immediate_force_depth.enter();
2152 let result = lower(self);
2153 result
2154 }
2155
2156 fn with_sync_direct_call_for_ignored_force_depth<T>(
2157 &mut self,
2158 lower: impl FnOnce(&mut Self) -> CpsLowerResult<T>,
2159 ) -> CpsLowerResult<T> {
2160 let _depth = self.sync_direct_call_for_ignored_force_depth.enter();
2161 let result = lower(self);
2162 result
2163 }
2164
2165 fn force_if_non_thunk_demand(
2174 &mut self,
2175 value: CpsValueId,
2176 expected_ty: &runtime::Type,
2177 ) -> CpsValueId {
2178 if matches!(expected_ty, runtime::Type::Thunk { .. }) {
2179 return value;
2180 }
2181 let forced = self.fresh_value();
2182 self.current.stmts.push(CpsStmt::ForceThunk {
2183 dest: forced,
2184 thunk: value,
2185 });
2186 forced
2187 }
2188
2189 fn lower_tuple(&mut self, items: &[runtime::Expr]) -> CpsLowerResult<CpsValueId> {
2190 let items = items
2191 .iter()
2192 .map(|item| self.lower_expr(item))
2193 .collect::<CpsLowerResult<Vec<_>>>()?;
2194 let dest = self.fresh_value();
2195 self.current.stmts.push(CpsStmt::Tuple { dest, items });
2196 Ok(dest)
2197 }
2198
2199 fn lower_record(
2200 &mut self,
2201 fields: &[runtime::RecordExprField],
2202 spread: &Option<runtime::RecordSpreadExpr>,
2203 ) -> CpsLowerResult<CpsValueId> {
2204 let base = spread
2205 .as_ref()
2206 .map(|spread| match spread {
2207 runtime::RecordSpreadExpr::Head(expr) | runtime::RecordSpreadExpr::Tail(expr) => {
2208 self.lower_expr(expr)
2209 }
2210 })
2211 .transpose()?;
2212 let fields = fields
2213 .iter()
2214 .map(|field| {
2215 Ok(CpsRecordField {
2216 name: field.name.clone(),
2217 value: self.lower_expr(&field.value)?,
2218 })
2219 })
2220 .collect::<CpsLowerResult<Vec<_>>>()?;
2221 let dest = self.fresh_value();
2222 self.current
2223 .stmts
2224 .push(CpsStmt::Record { dest, base, fields });
2225 Ok(dest)
2226 }
2227
2228 fn lower_variant(
2229 &mut self,
2230 tag: &typed_ir::Name,
2231 value: Option<&runtime::Expr>,
2232 ) -> CpsLowerResult<CpsValueId> {
2233 let value = value.map(|value| self.lower_expr(value)).transpose()?;
2234 let dest = self.fresh_value();
2235 self.current.stmts.push(CpsStmt::Variant {
2236 dest,
2237 tag: tag.clone(),
2238 value,
2239 });
2240 Ok(dest)
2241 }
2242
2243 fn lower_select(
2244 &mut self,
2245 base: &runtime::Expr,
2246 field: &typed_ir::Name,
2247 ) -> CpsLowerResult<CpsValueId> {
2248 let base = self.lower_expr(base)?;
2249 let dest = self.fresh_value();
2250 self.current.stmts.push(CpsStmt::Select {
2251 dest,
2252 base,
2253 field: field.clone(),
2254 });
2255 Ok(dest)
2256 }
2257
2258 fn lower_if(
2259 &mut self,
2260 cond: &runtime::Expr,
2261 then_branch: &runtime::Expr,
2262 else_branch: &runtime::Expr,
2263 ) -> CpsLowerResult<CpsValueId> {
2264 if !collect_expr_performed_effects(cond).is_empty()
2265 || self.expr_may_perform_when_evaluated(cond)
2266 {
2267 let (expected_effects, handler) = self.current_effect_context();
2268 let value_cont = self.fresh_continuation();
2269 let result = self.fresh_value();
2270 self.lower_handled_effect_condition_if(
2271 cond,
2272 then_branch,
2273 else_branch,
2274 &expected_effects,
2275 handler,
2276 Some(value_cont),
2277 )?;
2278 self.current = ContinuationBuilder::new(value_cont, vec![result]);
2279 return Ok(result);
2280 }
2281
2282 let cond = self.lower_expr(cond)?;
2283 let saved_locals = self.locals.clone();
2284 let saved_local_exprs = self.local_exprs.clone();
2285 let then_cont = self.fresh_continuation();
2286 let else_cont = self.fresh_continuation();
2287 let merge_cont = self.fresh_continuation();
2288 let result = self.fresh_value();
2289
2290 self.terminate(CpsTerminator::Branch {
2291 cond,
2292 then_cont,
2293 else_cont,
2294 });
2295 self.finish_current();
2296
2297 self.current = ContinuationBuilder::new(then_cont, Vec::new());
2298 self.locals = saved_locals.clone();
2299 self.local_exprs = saved_local_exprs.clone();
2300 let then_value = self.lower_expr(then_branch)?;
2301 self.terminate(CpsTerminator::Continue {
2302 target: merge_cont,
2303 args: vec![then_value],
2304 });
2305 self.finish_current();
2306
2307 self.current = ContinuationBuilder::new(else_cont, Vec::new());
2308 self.locals = saved_locals.clone();
2309 self.local_exprs = saved_local_exprs.clone();
2310 let else_value = self.lower_expr(else_branch)?;
2311 self.terminate(CpsTerminator::Continue {
2312 target: merge_cont,
2313 args: vec![else_value],
2314 });
2315 self.finish_current();
2316
2317 self.current = ContinuationBuilder::new(merge_cont, vec![result]);
2318 self.locals = saved_locals;
2319 self.local_exprs = saved_local_exprs;
2320 Ok(result)
2321 }
2322
2323 fn lower_block(
2324 &mut self,
2325 stmts: &[runtime::Stmt],
2326 tail: Option<&runtime::Expr>,
2327 ) -> CpsLowerResult<CpsValueId> {
2328 let saved_locals = self.locals.clone();
2329 let saved_local_exprs = self.local_exprs.clone();
2330 let mut last_expr_value = None;
2331 for (index, stmt) in stmts.iter().enumerate() {
2332 match stmt {
2333 runtime::Stmt::Let { pattern, value } => {
2334 last_expr_value = None;
2335 if unused_pure_let(
2336 pattern,
2337 value,
2338 &stmts[index + 1..],
2339 tail,
2340 self.functions,
2341 self.bindings,
2342 ) {
2343 continue;
2344 }
2345 if let Some((name, param, body)) = recursive_lambda_let(pattern, value) {
2346 let value = self.lower_recursive_lambda(name, param, body)?;
2347 self.locals
2348 .insert(typed_ir::Path::from_name(name.clone()), value);
2349 continue;
2350 }
2351 let value = self.lower_expr(value)?;
2352 self.bind_pattern(pattern, value)?;
2353 }
2354 runtime::Stmt::Expr(expr) => {
2355 if !stmts[index + 1..].is_empty() || tail.is_some() {
2356 if let runtime::ExprKind::BindHere { expr: inner } = &expr.kind {
2357 let thunk =
2358 self.with_sync_direct_call_for_ignored_force_depth(|this| {
2359 this.with_sync_apply_for_immediate_force_depth(|this| {
2360 this.lower_expr(inner)
2361 })
2362 })?;
2363 let post_cont = self.fresh_continuation();
2364 let ignored = self.fresh_value();
2365 self.terminate(CpsTerminator::EffectfulForce {
2366 thunk,
2367 resume: post_cont,
2368 });
2369 self.finish_current();
2370 self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
2371 let value = self.lower_block(&stmts[index + 1..], tail)?;
2372 self.locals = saved_locals;
2373 self.local_exprs = saved_local_exprs;
2374 return Ok(value);
2375 }
2376 }
2377 last_expr_value = Some(self.lower_expr(expr)?);
2378 }
2379 runtime::Stmt::Module { .. } => {
2380 self.locals = saved_locals;
2381 return Err(CpsLowerError::UnsupportedExpr {
2382 kind: "module statement",
2383 });
2384 }
2385 }
2386 }
2387 let value = match tail {
2388 Some(tail) => self.lower_expr(tail)?,
2389 None => match last_expr_value {
2390 Some(value) => value,
2391 None => {
2392 let value = self.fresh_value();
2393 self.current.stmts.push(CpsStmt::Literal {
2394 dest: value,
2395 literal: CpsLiteral::Unit,
2396 });
2397 value
2398 }
2399 },
2400 };
2401 self.locals = saved_locals;
2402 self.local_exprs = saved_local_exprs;
2403 Ok(value)
2404 }
2405
2406 fn lower_match(&mut self, expr: &runtime::Expr) -> CpsLowerResult<CpsValueId> {
2407 let runtime::ExprKind::Match {
2408 scrutinee, arms, ..
2409 } = &expr.kind
2410 else {
2411 return Err(CpsLowerError::UnsupportedExpr { kind: "match" });
2412 };
2413 let scrutinee = self.lower_expr(scrutinee)?;
2414 let saved_locals = self.locals.clone();
2415 let saved_local_exprs = self.local_exprs.clone();
2416 let saved_resumptions = self.resumptions.clone();
2417 let merge_cont = self.fresh_continuation();
2418 let result = self.fresh_value();
2419 let fallback_cont = self.fresh_continuation();
2420 let mut arm_conts = Vec::with_capacity(arms.len());
2421 let mut guard_conts = Vec::with_capacity(arms.len());
2422 let mut next_conts = Vec::with_capacity(arms.len());
2423 for _ in arms {
2424 arm_conts.push(self.fresh_continuation());
2425 guard_conts.push(None);
2426 }
2427
2428 let mut current_test_cont = None;
2429 for (index, arm) in arms.iter().enumerate() {
2430 if let Some(test_cont) = current_test_cont {
2431 self.current = ContinuationBuilder::new(test_cont, Vec::new());
2432 self.locals = saved_locals.clone();
2433 self.local_exprs = saved_local_exprs.clone();
2434 self.resumptions = saved_resumptions.clone();
2435 }
2436 let next_cont = if index + 1 == arms.len() {
2437 fallback_cont
2438 } else {
2439 let next = self.fresh_continuation();
2440 current_test_cont = Some(next);
2441 next
2442 };
2443 next_conts.push(next_cont);
2444 let success_cont = if arm.guard.is_some() {
2445 let guard_cont = self.fresh_continuation();
2446 guard_conts[index] = Some(guard_cont);
2447 guard_cont
2448 } else {
2449 arm_conts[index]
2450 };
2451 if self.lower_pattern_test(scrutinee, &arm.pattern, success_cont, next_cont)? {
2452 self.finish_current();
2453 }
2454 }
2455
2456 self.current = ContinuationBuilder::new(fallback_cont, Vec::new());
2457 let unit = self.fresh_value();
2458 self.current.stmts.push(CpsStmt::Literal {
2459 dest: unit,
2460 literal: CpsLiteral::Unit,
2461 });
2462 self.terminate(CpsTerminator::Continue {
2463 target: merge_cont,
2464 args: vec![unit],
2465 });
2466 self.finish_current();
2467
2468 for (index, arm) in arms.iter().enumerate() {
2469 let Some(guard_cont) = guard_conts[index] else {
2470 continue;
2471 };
2472 let Some(guard) = &arm.guard else {
2473 continue;
2474 };
2475 self.current = ContinuationBuilder::new(guard_cont, Vec::new());
2476 self.locals = saved_locals.clone();
2477 self.local_exprs = saved_local_exprs.clone();
2478 self.resumptions = saved_resumptions.clone();
2479 self.bind_pattern(&arm.pattern, scrutinee)?;
2480 if !collect_expr_performed_effects(guard).is_empty() {
2481 let (expected_effects, handler) = self.current_effect_context();
2482 let guard_value_cont = self.fresh_continuation();
2483 let guard_value = self.fresh_value();
2484 self.lower_handled_body(guard, &expected_effects, handler, Some(guard_value_cont))?;
2485 self.current = ContinuationBuilder::new(guard_value_cont, vec![guard_value]);
2486 self.locals = saved_locals.clone();
2487 self.local_exprs = saved_local_exprs.clone();
2488 self.resumptions = saved_resumptions.clone();
2489 self.terminate(CpsTerminator::Branch {
2490 cond: guard_value,
2491 then_cont: arm_conts[index],
2492 else_cont: next_conts[index],
2493 });
2494 self.finish_current();
2495 } else {
2496 let guard_value = self.lower_expr(guard)?;
2497 self.terminate(CpsTerminator::Branch {
2498 cond: guard_value,
2499 then_cont: arm_conts[index],
2500 else_cont: next_conts[index],
2501 });
2502 self.finish_current();
2503 }
2504 }
2505
2506 for (arm, arm_cont) in arms.iter().zip(arm_conts) {
2507 self.current = ContinuationBuilder::new(arm_cont, Vec::new());
2508 self.locals = saved_locals.clone();
2509 self.local_exprs = saved_local_exprs.clone();
2510 self.resumptions = saved_resumptions.clone();
2511 self.bind_pattern(&arm.pattern, scrutinee)?;
2512 let value = self.lower_expr(&arm.body)?;
2513 self.terminate(CpsTerminator::Continue {
2514 target: merge_cont,
2515 args: vec![value],
2516 });
2517 self.finish_current();
2518 }
2519
2520 self.current = ContinuationBuilder::new(merge_cont, vec![result]);
2521 self.locals = saved_locals;
2522 self.local_exprs = saved_local_exprs;
2523 self.resumptions = saved_resumptions;
2524 Ok(result)
2525 }
2526
2527 fn lower_pattern_test(
2528 &mut self,
2529 value: CpsValueId,
2530 pattern: &runtime::Pattern,
2531 then_cont: CpsContinuationId,
2532 else_cont: CpsContinuationId,
2533 ) -> CpsLowerResult<bool> {
2534 match pattern {
2535 runtime::Pattern::Wildcard { .. } | runtime::Pattern::Bind { .. } => {
2536 self.terminate(CpsTerminator::Continue {
2537 target: then_cont,
2538 args: Vec::new(),
2539 });
2540 Ok(true)
2541 }
2542 runtime::Pattern::Lit {
2543 lit: typed_ir::Lit::Bool(true),
2544 ..
2545 } => {
2546 self.terminate(CpsTerminator::Branch {
2547 cond: value,
2548 then_cont,
2549 else_cont,
2550 });
2551 Ok(true)
2552 }
2553 runtime::Pattern::Lit {
2554 lit: typed_ir::Lit::Bool(false),
2555 ..
2556 } => {
2557 let cond = self.fresh_value();
2558 self.current.stmts.push(CpsStmt::Primitive {
2559 dest: cond,
2560 op: typed_ir::PrimitiveOp::BoolNot,
2561 args: vec![value],
2562 });
2563 self.terminate(CpsTerminator::Branch {
2564 cond,
2565 then_cont,
2566 else_cont,
2567 });
2568 Ok(true)
2569 }
2570 runtime::Pattern::Lit {
2571 lit: typed_ir::Lit::Unit,
2572 ..
2573 } => {
2574 self.terminate(CpsTerminator::Continue {
2575 target: then_cont,
2576 args: Vec::new(),
2577 });
2578 Ok(true)
2579 }
2580 runtime::Pattern::Lit {
2581 lit: typed_ir::Lit::Int(expected),
2582 ..
2583 } => {
2584 let literal = self.fresh_value();
2585 self.current.stmts.push(CpsStmt::Literal {
2586 dest: literal,
2587 literal: CpsLiteral::Int(expected.clone()),
2588 });
2589 let cond = self.fresh_value();
2590 self.current.stmts.push(CpsStmt::Primitive {
2591 dest: cond,
2592 op: typed_ir::PrimitiveOp::IntEq,
2593 args: vec![value, literal],
2594 });
2595 self.terminate(CpsTerminator::Branch {
2596 cond,
2597 then_cont,
2598 else_cont,
2599 });
2600 Ok(true)
2601 }
2602 runtime::Pattern::Tuple { items, .. } => {
2603 self.lower_tuple_pattern_test(value, items, 0, then_cont, else_cont)
2604 }
2605 runtime::Pattern::List {
2606 prefix,
2607 spread,
2608 suffix,
2609 ..
2610 } => self.lower_list_pattern_test(
2611 value,
2612 prefix,
2613 spread.as_deref(),
2614 suffix,
2615 then_cont,
2616 else_cont,
2617 ),
2618 runtime::Pattern::Record { fields, spread, .. } => {
2619 self.lower_record_pattern_test(value, fields, spread.as_ref(), then_cont, else_cont)
2620 }
2621 runtime::Pattern::Variant {
2622 tag,
2623 value: payload,
2624 ..
2625 } => {
2626 let cond = self.fresh_value();
2627 self.current.stmts.push(CpsStmt::VariantTagEq {
2628 dest: cond,
2629 variant: value,
2630 tag: tag.clone(),
2631 });
2632 let matched_cont = if payload.is_some() {
2633 self.fresh_continuation()
2634 } else {
2635 then_cont
2636 };
2637 self.terminate(CpsTerminator::Branch {
2638 cond,
2639 then_cont: matched_cont,
2640 else_cont,
2641 });
2642 if let Some(payload) = payload {
2643 self.finish_current();
2644 self.current = ContinuationBuilder::new(matched_cont, Vec::new());
2645 let payload_value = self.fresh_value();
2646 self.current.stmts.push(CpsStmt::VariantPayload {
2647 dest: payload_value,
2648 variant: value,
2649 });
2650 self.lower_pattern_test(payload_value, payload, then_cont, else_cont)
2651 } else {
2652 Ok(true)
2653 }
2654 }
2655 runtime::Pattern::Or { left, right, .. } => {
2656 let right_cont = self.fresh_continuation();
2657 self.lower_pattern_test(value, left, then_cont, right_cont)?;
2658 self.finish_current();
2659 self.current = ContinuationBuilder::new(right_cont, Vec::new());
2660 self.lower_pattern_test(value, right, then_cont, else_cont)
2661 }
2662 runtime::Pattern::As { pattern, .. } => {
2663 self.lower_pattern_test(value, pattern, then_cont, else_cont)
2664 }
2665 _ => Err(CpsLowerError::UnsupportedPattern {
2666 kind: "match pattern",
2667 }),
2668 }
2669 }
2670
2671 fn lower_tuple_pattern_test(
2672 &mut self,
2673 value: CpsValueId,
2674 items: &[runtime::Pattern],
2675 index: usize,
2676 then_cont: CpsContinuationId,
2677 else_cont: CpsContinuationId,
2678 ) -> CpsLowerResult<bool> {
2679 let Some(item) = items.get(index) else {
2680 self.terminate(CpsTerminator::Continue {
2681 target: then_cont,
2682 args: Vec::new(),
2683 });
2684 return Ok(true);
2685 };
2686 let next_cont = self.fresh_continuation();
2687 let item_value = self.fresh_value();
2688 self.current.stmts.push(CpsStmt::TupleGet {
2689 dest: item_value,
2690 tuple: value,
2691 index,
2692 });
2693 self.lower_pattern_test(item_value, item, next_cont, else_cont)?;
2694 self.finish_current();
2695 self.current = ContinuationBuilder::new(next_cont, Vec::new());
2696 self.lower_tuple_pattern_test(value, items, index + 1, then_cont, else_cont)
2697 }
2698
2699 fn lower_list_pattern_test(
2700 &mut self,
2701 value: CpsValueId,
2702 prefix: &[runtime::Pattern],
2703 spread: Option<&runtime::Pattern>,
2704 suffix: &[runtime::Pattern],
2705 then_cont: CpsContinuationId,
2706 else_cont: CpsContinuationId,
2707 ) -> CpsLowerResult<bool> {
2708 let len = self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]);
2709 let required = self.emit_int_literal((prefix.len() + suffix.len()) as i64);
2710 let op = if spread.is_some() {
2711 typed_ir::PrimitiveOp::IntGe
2712 } else {
2713 typed_ir::PrimitiveOp::IntEq
2714 };
2715 let len_cond = self.emit_primitive(op, vec![len, required]);
2716 let items_cont = self.fresh_continuation();
2717 self.terminate(CpsTerminator::Branch {
2718 cond: len_cond,
2719 then_cont: items_cont,
2720 else_cont,
2721 });
2722 self.finish_current();
2723 self.current = ContinuationBuilder::new(items_cont, Vec::new());
2724
2725 let mut tests = Vec::new();
2726 for (index, item) in prefix.iter().enumerate() {
2727 let index = self.emit_int_literal(index as i64);
2728 let item_value =
2729 self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
2730 tests.push((item_value, item));
2731 }
2732 if let Some(spread) = spread {
2733 let len = self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]);
2734 let start = self.emit_int_literal(prefix.len() as i64);
2735 let suffix_len = self.emit_int_literal(suffix.len() as i64);
2736 let end = self.emit_primitive(typed_ir::PrimitiveOp::IntSub, vec![len, suffix_len]);
2737 let slice = self.emit_primitive(
2738 typed_ir::PrimitiveOp::ListIndexRangeRaw,
2739 vec![value, start, end],
2740 );
2741 tests.push((slice, spread));
2742 }
2743 for (offset, item) in suffix.iter().enumerate() {
2744 let len = self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]);
2745 let suffix_len = self.emit_int_literal(suffix.len() as i64);
2746 let suffix_start =
2747 self.emit_primitive(typed_ir::PrimitiveOp::IntSub, vec![len, suffix_len]);
2748 let offset = self.emit_int_literal(offset as i64);
2749 let index =
2750 self.emit_primitive(typed_ir::PrimitiveOp::IntAdd, vec![suffix_start, offset]);
2751 let item_value =
2752 self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
2753 tests.push((item_value, item));
2754 }
2755 self.lower_extracted_pattern_tests(tests, 0, then_cont, else_cont)
2756 }
2757
2758 fn lower_record_pattern_test(
2759 &mut self,
2760 value: CpsValueId,
2761 fields: &[runtime::RecordPatternField],
2762 spread: Option<&runtime::RecordSpreadPattern>,
2763 then_cont: CpsContinuationId,
2764 else_cont: CpsContinuationId,
2765 ) -> CpsLowerResult<bool> {
2766 let fields_done = self.fresh_continuation();
2767 self.lower_record_field_pattern_tests(value, fields, 0, fields_done, else_cont)?;
2768 self.finish_current();
2769 self.current = ContinuationBuilder::new(fields_done, Vec::new());
2770 if let Some(spread) = record_spread_pattern(spread) {
2771 let rest = self.emit_record_without_fields(value, fields);
2772 self.lower_pattern_test(rest, spread, then_cont, else_cont)
2773 } else {
2774 self.terminate(CpsTerminator::Continue {
2775 target: then_cont,
2776 args: Vec::new(),
2777 });
2778 Ok(true)
2779 }
2780 }
2781
2782 fn lower_record_field_pattern_tests(
2783 &mut self,
2784 value: CpsValueId,
2785 fields: &[runtime::RecordPatternField],
2786 index: usize,
2787 then_cont: CpsContinuationId,
2788 else_cont: CpsContinuationId,
2789 ) -> CpsLowerResult<bool> {
2790 let Some(field) = fields.get(index) else {
2791 self.terminate(CpsTerminator::Continue {
2792 target: then_cont,
2793 args: Vec::new(),
2794 });
2795 return Ok(true);
2796 };
2797 let field_value = self.fresh_value();
2798 if let Some(default) = &field.default {
2799 let default = self.lower_expr(default)?;
2800 self.current.stmts.push(CpsStmt::SelectWithDefault {
2801 dest: field_value,
2802 base: value,
2803 field: field.name.clone(),
2804 default,
2805 });
2806 } else {
2807 let present = self.fresh_value();
2808 let present_cont = self.fresh_continuation();
2809 self.current.stmts.push(CpsStmt::RecordHasField {
2810 dest: present,
2811 base: value,
2812 field: field.name.clone(),
2813 });
2814 self.terminate(CpsTerminator::Branch {
2815 cond: present,
2816 then_cont: present_cont,
2817 else_cont,
2818 });
2819 self.finish_current();
2820 self.current = ContinuationBuilder::new(present_cont, Vec::new());
2821 self.current.stmts.push(CpsStmt::Select {
2822 dest: field_value,
2823 base: value,
2824 field: field.name.clone(),
2825 });
2826 }
2827 let next_cont = self.fresh_continuation();
2828 self.lower_pattern_test(field_value, &field.pattern, next_cont, else_cont)?;
2829 self.finish_current();
2830 self.current = ContinuationBuilder::new(next_cont, Vec::new());
2831 self.lower_record_field_pattern_tests(value, fields, index + 1, then_cont, else_cont)
2832 }
2833
2834 fn lower_extracted_pattern_tests(
2835 &mut self,
2836 tests: Vec<(CpsValueId, &runtime::Pattern)>,
2837 index: usize,
2838 then_cont: CpsContinuationId,
2839 else_cont: CpsContinuationId,
2840 ) -> CpsLowerResult<bool> {
2841 let Some((value, pattern)) = tests.get(index).copied() else {
2842 self.terminate(CpsTerminator::Continue {
2843 target: then_cont,
2844 args: Vec::new(),
2845 });
2846 return Ok(true);
2847 };
2848 let next_cont = self.fresh_continuation();
2849 self.lower_pattern_test(value, pattern, next_cont, else_cont)?;
2850 self.finish_current();
2851 self.current = ContinuationBuilder::new(next_cont, Vec::new());
2852 self.lower_extracted_pattern_tests(tests, index + 1, then_cont, else_cont)
2853 }
2854
2855 fn lower_handle(
2856 &mut self,
2857 body: &runtime::Expr,
2858 arms: &[runtime::HandleArm],
2859 consumes: &[typed_ir::Path],
2860 ) -> CpsLowerResult<CpsValueId> {
2861 let mut value_arms = arms
2862 .iter()
2863 .filter(|arm| arm.resume.is_none() && arm.effect.segments.is_empty());
2864 let value_arm = value_arms.next();
2865 if value_arms.next().is_some() {
2866 return Err(CpsLowerError::UnsupportedExpr {
2867 kind: "multi-value handler",
2868 });
2869 }
2870 if value_arm.is_some_and(|arm| arm.guard.is_some()) {
2871 return Err(CpsLowerError::UnsupportedExpr {
2872 kind: "handler guard",
2873 });
2874 }
2875
2876 let effect_arms = arms
2877 .iter()
2878 .filter(|arm| arm.resume.is_some())
2879 .collect::<Vec<_>>();
2880 if effect_arms.is_empty() {
2881 let value = self.lower_expr(body)?;
2882 return self.lower_handle_value(value, value_arm);
2883 };
2884 if arms
2885 .iter()
2886 .any(|candidate| candidate.resume.is_none() && !candidate.effect.segments.is_empty())
2887 {
2888 return Err(CpsLowerError::UnsupportedExpr {
2889 kind: "handler without resume",
2890 });
2891 }
2892 let saved_locals = self.locals.clone();
2893 let saved_local_exprs = self.local_exprs.clone();
2894 let saved_resumptions = self.resumptions.clone();
2895 let saved_forced_handler_effects_len = self.forced_handler_effects.len();
2896 let saved_handler_value_conts_len = self.handler_value_conts.len();
2897
2898 let value_cont = self.fresh_continuation();
2899 let merge_cont = self.fresh_continuation();
2900 let handler = self.fresh_handler();
2901 let result = self.fresh_value();
2902 let effects = effect_arms
2903 .iter()
2904 .flat_map(|arm| scoped_handler_effects(consumes, &arm.effect))
2905 .collect::<Vec<_>>();
2906
2907 let saved_active_handler = self.active_handler.clone();
2908 self.active_handler = Some(ActiveHandlerContext {
2909 handler,
2910 expected_effects: effects.clone(),
2911 parent: saved_active_handler.clone().map(Box::new),
2912 });
2913 self.handler_value_conts.push(value_cont);
2914 self.current.stmts.push(CpsStmt::InstallHandler {
2915 handler,
2916 envs: Vec::new(),
2917 value: value_cont,
2918 escape: merge_cont,
2923 });
2924 self.lower_handled_body(body, &effects, handler, None)?;
2925 self.handler_value_conts
2926 .truncate(saved_handler_value_conts_len);
2927 self.active_handler = saved_active_handler.clone();
2928 let used_effects = self.performed_effects_for_handler(handler);
2929 let body_made_external_call = self.handlers_with_external_calls.contains(&handler);
2930 let mut handler_entries: Vec<(typed_ir::Path, CpsContinuationId, Vec<usize>)> =
2931 Vec::with_capacity(effect_arms.len());
2932 for (arm_index, arm) in effect_arms.iter().enumerate() {
2933 for effect in scoped_handler_effects(consumes, &arm.effect) {
2934 let directly_used = used_effects
2935 .iter()
2936 .any(|used| effect_matches(&effect, used) || effect_matches(used, &effect));
2937 if directly_used || body_made_external_call {
2942 if let Some((_, _, arm_indices)) = handler_entries
2943 .iter_mut()
2944 .find(|(existing, _, _)| existing == &effect)
2945 {
2946 arm_indices.push(arm_index);
2947 } else {
2948 handler_entries.push((effect, self.fresh_continuation(), vec![arm_index]));
2949 }
2950 }
2951 }
2952 }
2953 if !handler_entries.is_empty() {
2954 self.handlers.push(CpsHandler {
2955 id: handler,
2956 arms: handler_entries
2957 .iter()
2958 .map(|(effect, entry, _)| CpsHandlerArm {
2959 effect: effect.clone(),
2960 entry: *entry,
2961 })
2962 .collect(),
2963 });
2964 }
2965
2966 let handled_value = self.fresh_value();
2967 self.current = ContinuationBuilder::new(value_cont, vec![handled_value]);
2968 self.current
2969 .stmts
2970 .push(CpsStmt::UninstallHandler { handler });
2971 self.locals = saved_locals.clone();
2972 self.local_exprs = saved_local_exprs.clone();
2973 self.resumptions = saved_resumptions.clone();
2974 let handled = self.lower_handle_value(handled_value, value_arm)?;
2975 self.terminate(CpsTerminator::Continue {
2976 target: merge_cont,
2977 args: vec![handled],
2978 });
2979 self.finish_current();
2980
2981 for (effect, handler_cont, arm_indices) in handler_entries {
2982 let handler_payload = self.fresh_value();
2983 let handler_resume = self.fresh_value();
2984 self.current =
2985 ContinuationBuilder::new(handler_cont, vec![handler_payload, handler_resume]);
2986 self.current
2987 .stmts
2988 .push(CpsStmt::UninstallHandler { handler });
2989 self.locals = saved_locals.clone();
2990 self.local_exprs = saved_local_exprs.clone();
2991 self.resumptions = saved_resumptions.clone();
2992 self.lower_effect_handler_arm_chain(
2993 effect_arms.as_slice(),
2994 &arm_indices,
2995 0,
2996 EffectHandlerArmChain {
2997 effect: &effect,
2998 payload: handler_payload,
2999 resume: handler_resume,
3000 handler,
3001 saved_locals: &saved_locals,
3002 saved_local_exprs: &saved_local_exprs,
3003 saved_resumptions: &saved_resumptions,
3004 },
3005 )?;
3006 }
3007
3008 self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3009 self.locals = saved_locals;
3010 self.local_exprs = saved_local_exprs;
3011 self.resumptions = saved_resumptions;
3012 self.active_handler = saved_active_handler;
3013 self.forced_handler_effects
3014 .truncate(saved_forced_handler_effects_len);
3015 self.handler_value_conts
3016 .truncate(saved_handler_value_conts_len);
3017 Ok(result)
3018 }
3019
3020 fn lower_effect_handler_arm_chain(
3021 &mut self,
3022 effect_arms: &[&runtime::HandleArm],
3023 arm_indices: &[usize],
3024 index: usize,
3025 ctx: EffectHandlerArmChain<'_>,
3026 ) -> CpsLowerResult<()> {
3027 let Some(arm_index) = arm_indices.get(index).copied() else {
3028 let unit = self.fresh_value();
3029 self.current.stmts.push(CpsStmt::Literal {
3030 dest: unit,
3031 literal: CpsLiteral::Unit,
3032 });
3033 self.terminate(CpsTerminator::Return(unit));
3034 self.finish_current();
3035 return Ok(());
3036 };
3037 let arm = effect_arms[arm_index];
3038 let next_cont = self.fresh_continuation();
3039 let body_cont = self.fresh_continuation();
3040 let success_cont = if arm.guard.is_some() {
3041 self.fresh_continuation()
3042 } else {
3043 body_cont
3044 };
3045
3046 if self.lower_pattern_test(ctx.payload, &arm.payload, success_cont, next_cont)? {
3047 self.finish_current();
3048 }
3049
3050 if let Some(guard) = &arm.guard {
3051 self.current = ContinuationBuilder::new(success_cont, Vec::new());
3052 self.locals = ctx.saved_locals.clone();
3053 self.local_exprs = ctx.saved_local_exprs.clone();
3054 self.resumptions = ctx.saved_resumptions.clone();
3055 self.bind_effect_handler_arm_locals(arm, ctx.payload, ctx.resume)?;
3056 if !collect_expr_performed_effects(guard).is_empty()
3057 || self.expr_may_perform_when_evaluated(guard)
3058 {
3059 let guard_value_cont = self.fresh_continuation();
3060 let guard_value = self.fresh_value();
3061 self.lower_handled_body(
3062 guard,
3063 std::slice::from_ref(ctx.effect),
3064 ctx.handler,
3065 Some(guard_value_cont),
3066 )?;
3067 self.current = ContinuationBuilder::new(guard_value_cont, vec![guard_value]);
3068 self.locals = ctx.saved_locals.clone();
3069 self.local_exprs = ctx.saved_local_exprs.clone();
3070 self.resumptions = ctx.saved_resumptions.clone();
3071 self.bind_effect_handler_arm_locals(arm, ctx.payload, ctx.resume)?;
3072 self.terminate(CpsTerminator::Branch {
3073 cond: guard_value,
3074 then_cont: body_cont,
3075 else_cont: next_cont,
3076 });
3077 self.finish_current();
3078 } else {
3079 let guard_value = self.lower_expr(guard)?;
3080 self.terminate(CpsTerminator::Branch {
3081 cond: guard_value,
3082 then_cont: body_cont,
3083 else_cont: next_cont,
3084 });
3085 self.finish_current();
3086 }
3087 }
3088
3089 self.current = ContinuationBuilder::new(body_cont, Vec::new());
3090 self.locals = ctx.saved_locals.clone();
3091 self.local_exprs = ctx.saved_local_exprs.clone();
3092 self.resumptions = ctx.saved_resumptions.clone();
3093 self.bind_effect_handler_arm_locals(arm, ctx.payload, ctx.resume)?;
3094 let handled = self.lower_handler_body_expr(&arm.body, Some(ctx.handler))?;
3095 self.terminate(CpsTerminator::Return(handled));
3096 self.finish_current();
3097
3098 self.current = ContinuationBuilder::new(next_cont, Vec::new());
3099 self.locals = ctx.saved_locals.clone();
3100 self.local_exprs = ctx.saved_local_exprs.clone();
3101 self.resumptions = ctx.saved_resumptions.clone();
3102 self.lower_effect_handler_arm_chain(effect_arms, arm_indices, index + 1, ctx)
3103 }
3104
3105 fn bind_effect_handler_arm_locals(
3106 &mut self,
3107 arm: &runtime::HandleArm,
3108 payload: CpsValueId,
3109 resume: CpsValueId,
3110 ) -> CpsLowerResult<()> {
3111 let Some(resume_binding) = &arm.resume else {
3112 return Err(CpsLowerError::UnsupportedExpr {
3113 kind: "handler without resume",
3114 });
3115 };
3116 self.bind_pattern(&arm.payload, payload)?;
3117 let resume_path = typed_ir::Path::from_name(resume_binding.name.clone());
3118 self.locals.insert(resume_path.clone(), resume);
3119 self.resumptions.insert(resume_path);
3120 Ok(())
3121 }
3122
3123 fn lower_handle_value(
3124 &mut self,
3125 value: CpsValueId,
3126 value_arm: Option<&runtime::HandleArm>,
3127 ) -> CpsLowerResult<CpsValueId> {
3128 let Some(arm) = value_arm else {
3129 return Ok(value);
3130 };
3131 self.bind_pattern(&arm.payload, value)?;
3132 self.lower_handler_body_expr(&arm.body, None)
3133 }
3134
3135 fn lower_handler_body_expr(
3136 &mut self,
3137 expr: &runtime::Expr,
3138 handler: Option<CpsHandlerId>,
3139 ) -> CpsLowerResult<CpsValueId> {
3140 if let Some(inner) = handler_body_plain_value_inner(expr) {
3141 return self.lower_expr(inner);
3142 }
3143 if let Some(handler) = handler
3144 && let Some(reentry) = self.handler_reentry_apply(expr, handler)?
3145 {
3146 self.current.stmts.push(CpsStmt::ResumeWithHandler {
3147 dest: reentry.dest,
3148 resumption: reentry.resumption,
3149 arg: reentry.arg,
3150 handler,
3151 envs: reentry.envs,
3152 });
3153 return Ok(reentry.dest);
3154 }
3155 if handler.is_some() && self.apply_chain_contains_resume_argument(expr) {
3156 return self.lower_expr_with_forced_effectful_applies(expr);
3157 }
3158 if let Some(handler) = handler
3159 && let runtime::ExprKind::Block { stmts, tail } = &expr.kind
3160 {
3161 if stmts.is_empty()
3162 && let Some(tail) = tail
3163 {
3164 return self.lower_handler_body_expr(tail, Some(handler));
3165 }
3166 return self.lower_handler_body_block(stmts, tail.as_deref(), handler);
3167 }
3168 if let Some(handler) = handler
3169 && let runtime::ExprKind::If {
3170 cond,
3171 then_branch,
3172 else_branch,
3173 ..
3174 } = &expr.kind
3175 {
3176 return self.lower_handler_body_if(cond, then_branch, else_branch, handler);
3177 }
3178 if let Some(handler) = handler
3179 && let runtime::ExprKind::Match { .. } = &expr.kind
3180 {
3181 return self.lower_handler_body_match(expr, handler);
3182 }
3183 self.lower_expr(expr)
3184 }
3185
3186 fn lower_handler_body_block(
3187 &mut self,
3188 stmts: &[runtime::Stmt],
3189 tail: Option<&runtime::Expr>,
3190 handler: CpsHandlerId,
3191 ) -> CpsLowerResult<CpsValueId> {
3192 let value_cont = self.fresh_continuation();
3193 let value = self.fresh_value();
3194 let (expected_effects, condition_handler) = self.handler_body_effect_context(
3195 tail.unwrap_or_else(|| {
3196 stmts
3197 .last()
3198 .and_then(|stmt| match stmt {
3199 runtime::Stmt::Let { value, .. }
3200 | runtime::Stmt::Expr(value)
3201 | runtime::Stmt::Module { body: value, .. } => Some(value),
3202 })
3203 .expect("non-empty handler block")
3204 }),
3205 handler,
3206 );
3207 self.lower_handled_block(
3208 stmts,
3209 tail,
3210 &expected_effects,
3211 condition_handler,
3212 Some(value_cont),
3213 )?;
3214 self.current = ContinuationBuilder::new(value_cont, vec![value]);
3215 Ok(value)
3216 }
3217
3218 fn lower_handler_body_if(
3219 &mut self,
3220 cond: &runtime::Expr,
3221 then_branch: &runtime::Expr,
3222 else_branch: &runtime::Expr,
3223 handler: CpsHandlerId,
3224 ) -> CpsLowerResult<CpsValueId> {
3225 if !collect_expr_performed_effects(cond).is_empty()
3226 || self.expr_may_perform_when_evaluated(cond)
3227 {
3228 return self.lower_handler_body_effect_condition_if(
3229 cond,
3230 then_branch,
3231 else_branch,
3232 handler,
3233 );
3234 }
3235
3236 let cond_value = if let Some(reentry) = self.handler_reentry_apply(cond, handler)? {
3237 self.current.stmts.push(CpsStmt::ResumeWithHandler {
3238 dest: reentry.dest,
3239 resumption: reentry.resumption,
3240 arg: reentry.arg,
3241 handler,
3242 envs: reentry.envs,
3243 });
3244 reentry.dest
3245 } else {
3246 self.lower_expr(cond)?
3247 };
3248
3249 let saved_locals = self.locals.clone();
3250 let saved_local_exprs = self.local_exprs.clone();
3251 let saved_resumptions = self.resumptions.clone();
3252 let then_cont = self.fresh_continuation();
3253 let else_cont = self.fresh_continuation();
3254 let merge_cont = self.fresh_continuation();
3255 let result = self.fresh_value();
3256
3257 self.terminate(CpsTerminator::Branch {
3258 cond: cond_value,
3259 then_cont,
3260 else_cont,
3261 });
3262 self.finish_current();
3263
3264 self.current = ContinuationBuilder::new(then_cont, Vec::new());
3265 self.locals = saved_locals.clone();
3266 self.local_exprs = saved_local_exprs.clone();
3267 self.resumptions = saved_resumptions.clone();
3268 let then_value = self.lower_handler_body_expr(then_branch, Some(handler))?;
3269 self.terminate(CpsTerminator::Continue {
3270 target: merge_cont,
3271 args: vec![then_value],
3272 });
3273 self.finish_current();
3274
3275 self.current = ContinuationBuilder::new(else_cont, Vec::new());
3276 self.locals = saved_locals.clone();
3277 self.local_exprs = saved_local_exprs.clone();
3278 self.resumptions = saved_resumptions.clone();
3279 let else_value = self.lower_handler_body_expr(else_branch, Some(handler))?;
3280 self.terminate(CpsTerminator::Continue {
3281 target: merge_cont,
3282 args: vec![else_value],
3283 });
3284 self.finish_current();
3285
3286 self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3287 self.locals = saved_locals;
3288 self.local_exprs = saved_local_exprs;
3289 self.resumptions = saved_resumptions;
3290 Ok(result)
3291 }
3292
3293 fn lower_handler_body_effect_condition_if(
3294 &mut self,
3295 cond: &runtime::Expr,
3296 then_branch: &runtime::Expr,
3297 else_branch: &runtime::Expr,
3298 handler: CpsHandlerId,
3299 ) -> CpsLowerResult<CpsValueId> {
3300 let saved_locals = self.locals.clone();
3301 let saved_local_exprs = self.local_exprs.clone();
3302 let saved_resumptions = self.resumptions.clone();
3303 let cond_cont = self.fresh_continuation();
3304 let then_cont = self.fresh_continuation();
3305 let else_cont = self.fresh_continuation();
3306 let merge_cont = self.fresh_continuation();
3307 let result = self.fresh_value();
3308 let cond_value = self.fresh_value();
3309 let (expected_effects, condition_handler) = self.handler_body_effect_context(cond, handler);
3310
3311 self.lower_effectful_condition_to_continuation(
3312 cond,
3313 &expected_effects,
3314 condition_handler,
3315 cond_cont,
3316 )?;
3317
3318 self.current = ContinuationBuilder::new(cond_cont, vec![cond_value]);
3319 self.locals = saved_locals.clone();
3320 self.local_exprs = saved_local_exprs.clone();
3321 self.resumptions = saved_resumptions.clone();
3322 self.terminate(CpsTerminator::Branch {
3323 cond: cond_value,
3324 then_cont,
3325 else_cont,
3326 });
3327 self.finish_current();
3328
3329 self.current = ContinuationBuilder::new(then_cont, Vec::new());
3330 self.locals = saved_locals.clone();
3331 self.local_exprs = saved_local_exprs.clone();
3332 self.resumptions = saved_resumptions.clone();
3333 let then_value = self.lower_handler_body_expr(then_branch, Some(handler))?;
3334 self.terminate(CpsTerminator::Continue {
3335 target: merge_cont,
3336 args: vec![then_value],
3337 });
3338 self.finish_current();
3339
3340 self.current = ContinuationBuilder::new(else_cont, Vec::new());
3341 self.locals = saved_locals.clone();
3342 self.local_exprs = saved_local_exprs.clone();
3343 self.resumptions = saved_resumptions.clone();
3344 let else_value = self.lower_handler_body_expr(else_branch, Some(handler))?;
3345 self.terminate(CpsTerminator::Continue {
3346 target: merge_cont,
3347 args: vec![else_value],
3348 });
3349 self.finish_current();
3350
3351 self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3352 self.locals = saved_locals;
3353 self.local_exprs = saved_local_exprs;
3354 self.resumptions = saved_resumptions;
3355 Ok(result)
3356 }
3357
3358 fn lower_effectful_condition_to_continuation(
3359 &mut self,
3360 cond: &runtime::Expr,
3361 expected_effects: &[typed_ir::Path],
3362 handler: CpsHandlerId,
3363 cond_cont: CpsContinuationId,
3364 ) -> CpsLowerResult<typed_ir::Path> {
3365 self.lower_handled_body(cond, expected_effects, handler, Some(cond_cont))
3366 }
3367
3368 fn lower_handler_body_match(
3369 &mut self,
3370 expr: &runtime::Expr,
3371 handler: CpsHandlerId,
3372 ) -> CpsLowerResult<CpsValueId> {
3373 let runtime::ExprKind::Match {
3374 scrutinee, arms, ..
3375 } = &expr.kind
3376 else {
3377 return Err(CpsLowerError::UnsupportedExpr { kind: "match" });
3378 };
3379 let scrutinee = if !collect_expr_performed_effects(scrutinee).is_empty()
3380 || self.expr_may_perform_when_evaluated(scrutinee)
3381 {
3382 let scrutinee_cont = self.fresh_continuation();
3383 let scrutinee_value = self.fresh_value();
3384 let (expected_effects, condition_handler) =
3385 self.handler_body_effect_context(scrutinee, handler);
3386 self.lower_handled_body(
3387 scrutinee,
3388 &expected_effects,
3389 condition_handler,
3390 Some(scrutinee_cont),
3391 )?;
3392 self.current = ContinuationBuilder::new(scrutinee_cont, vec![scrutinee_value]);
3393 scrutinee_value
3394 } else if let Some(reentry) = self.handler_reentry_apply(scrutinee, handler)? {
3395 self.current.stmts.push(CpsStmt::ResumeWithHandler {
3396 dest: reentry.dest,
3397 resumption: reentry.resumption,
3398 arg: reentry.arg,
3399 handler,
3400 envs: reentry.envs,
3401 });
3402 reentry.dest
3403 } else {
3404 self.lower_expr(scrutinee)?
3405 };
3406
3407 let saved_locals = self.locals.clone();
3408 let saved_local_exprs = self.local_exprs.clone();
3409 let saved_resumptions = self.resumptions.clone();
3410 let merge_cont = self.fresh_continuation();
3411 let result = self.fresh_value();
3412 let fallback_cont = self.fresh_continuation();
3413 let mut arm_conts = Vec::with_capacity(arms.len());
3414 let mut guard_conts = Vec::with_capacity(arms.len());
3415 let mut next_conts = Vec::with_capacity(arms.len());
3416 for _ in arms {
3417 arm_conts.push(self.fresh_continuation());
3418 guard_conts.push(None);
3419 }
3420
3421 let mut current_test_cont = None;
3422 for (index, arm) in arms.iter().enumerate() {
3423 if let Some(test_cont) = current_test_cont {
3424 self.current = ContinuationBuilder::new(test_cont, Vec::new());
3425 self.locals = saved_locals.clone();
3426 self.local_exprs = saved_local_exprs.clone();
3427 self.resumptions = saved_resumptions.clone();
3428 }
3429 let next_cont = if index + 1 == arms.len() {
3430 fallback_cont
3431 } else {
3432 let next = self.fresh_continuation();
3433 current_test_cont = Some(next);
3434 next
3435 };
3436 next_conts.push(next_cont);
3437 let success_cont = if arm.guard.is_some() {
3438 let guard_cont = self.fresh_continuation();
3439 guard_conts[index] = Some(guard_cont);
3440 guard_cont
3441 } else {
3442 arm_conts[index]
3443 };
3444 if self.lower_pattern_test(scrutinee, &arm.pattern, success_cont, next_cont)? {
3445 self.finish_current();
3446 }
3447 }
3448
3449 self.current = ContinuationBuilder::new(fallback_cont, Vec::new());
3450 let unit = self.fresh_value();
3451 self.current.stmts.push(CpsStmt::Literal {
3452 dest: unit,
3453 literal: CpsLiteral::Unit,
3454 });
3455 self.terminate(CpsTerminator::Continue {
3456 target: merge_cont,
3457 args: vec![unit],
3458 });
3459 self.finish_current();
3460
3461 for (index, arm) in arms.iter().enumerate() {
3462 let Some(guard_cont) = guard_conts[index] else {
3463 continue;
3464 };
3465 let Some(guard) = &arm.guard else {
3466 continue;
3467 };
3468 self.current = ContinuationBuilder::new(guard_cont, Vec::new());
3469 self.locals = saved_locals.clone();
3470 self.local_exprs = saved_local_exprs.clone();
3471 self.resumptions = saved_resumptions.clone();
3472 self.bind_pattern(&arm.pattern, scrutinee)?;
3473 let guard_value = self.lower_expr(guard)?;
3474 self.terminate(CpsTerminator::Branch {
3475 cond: guard_value,
3476 then_cont: arm_conts[index],
3477 else_cont: next_conts[index],
3478 });
3479 self.finish_current();
3480 }
3481
3482 for (arm, arm_cont) in arms.iter().zip(arm_conts) {
3483 self.current = ContinuationBuilder::new(arm_cont, Vec::new());
3484 self.locals = saved_locals.clone();
3485 self.local_exprs = saved_local_exprs.clone();
3486 self.resumptions = saved_resumptions.clone();
3487 self.bind_pattern(&arm.pattern, scrutinee)?;
3488 let value = self.lower_handler_body_expr(&arm.body, Some(handler))?;
3489 self.terminate(CpsTerminator::Continue {
3490 target: merge_cont,
3491 args: vec![value],
3492 });
3493 self.finish_current();
3494 }
3495
3496 self.current = ContinuationBuilder::new(merge_cont, vec![result]);
3497 self.locals = saved_locals;
3498 self.local_exprs = saved_local_exprs;
3499 self.resumptions = saved_resumptions;
3500 Ok(result)
3501 }
3502
3503 fn handler_body_effect_context(
3504 &self,
3505 expr: &runtime::Expr,
3506 handler: CpsHandlerId,
3507 ) -> (Vec<typed_ir::Path>, CpsHandlerId) {
3508 let performed_effects = collect_expr_performed_effects(expr);
3509 let (mut expected_effects, mut condition_handler) = self.current_effect_context();
3510 if expected_effects.is_empty() && !performed_effects.is_empty() {
3511 expected_effects = performed_effects;
3512 condition_handler = handler;
3513 }
3514 (expected_effects, condition_handler)
3515 }
3516
3517 fn lower_handled_body(
3518 &mut self,
3519 body: &runtime::Expr,
3520 expected_effects: &[typed_ir::Path],
3521 handler: CpsHandlerId,
3522 value_cont: Option<CpsContinuationId>,
3523 ) -> CpsLowerResult<typed_ir::Path> {
3524 if let runtime::ExprKind::Var(path) = &body.kind
3525 && let Some(expr) = self.local_exprs.get(path).cloned()
3526 {
3527 let expr = inline_callable_expr(&expr);
3528 return self.lower_handled_body(&expr, expected_effects, handler, value_cont);
3529 }
3530
3531 if let runtime::ExprKind::LocalPushId { id, body } = &body.kind {
3532 let dest = self.fresh_value();
3533 self.current
3534 .stmts
3535 .push(CpsStmt::FreshGuard { dest, var: *id });
3536 let previous = self.effect_guards.insert(*id, dest);
3537 let result = self.lower_handled_body(body, expected_effects, handler, value_cont);
3538 restore_effect_guard(&mut self.effect_guards, *id, previous);
3539 return result;
3540 }
3541
3542 if let Some(expr) = handle_body_execution_inner(body) {
3543 return self.lower_handled_body(expr, expected_effects, handler, value_cont);
3544 }
3545
3546 if let runtime::ExprKind::BindHere { expr } = &body.kind
3547 && matches!(expr.kind, runtime::ExprKind::Block { .. })
3548 {
3549 return self.lower_handled_body(expr, expected_effects, handler, value_cont);
3550 }
3551
3552 if let Some(request) = effect_apply_body_request(body) {
3553 let (expected_effects, handler) =
3554 self.effect_context_for_request(&request, expected_effects, handler);
3555 let (effect, resumed_value) =
3556 self.begin_resume_after_perform(request, &expected_effects, handler)?;
3557 self.finish_resumed_handled_value(resumed_value, value_cont);
3558 return Ok(effect);
3559 }
3560
3561 if let runtime::ExprKind::BindHere { expr } = &body.kind {
3562 let value = self.lower_bind_here(expr)?;
3563 self.finish_handled_value(value, value_cont);
3564 return Ok(default_expected_effect(expected_effects));
3565 }
3566
3567 if let Some((resumption, arg)) = self.resume_apply(body) {
3568 let arg = self.lower_expr(arg)?;
3569 let dest = self.fresh_value();
3570 self.current.stmts.push(CpsStmt::ResumeWithHandler {
3571 dest,
3572 resumption,
3573 arg,
3574 handler,
3575 envs: Vec::new(),
3576 });
3577 for effect in expected_effects {
3578 if !self
3579 .forced_handler_effects
3580 .iter()
3581 .any(|(seen_handler, seen_effect)| {
3582 *seen_handler == handler && seen_effect == effect
3583 })
3584 {
3585 self.forced_handler_effects.push((handler, effect.clone()));
3586 }
3587 }
3588 self.finish_handled_value(dest, value_cont);
3589 return Ok(default_expected_effect(expected_effects));
3590 }
3591
3592 if let Some(reentry) = self.handler_reentry_apply(body, handler)? {
3593 self.current.stmts.push(CpsStmt::ResumeWithHandler {
3594 dest: reentry.dest,
3595 resumption: reentry.resumption,
3596 arg: reentry.arg,
3597 handler,
3598 envs: reentry.envs,
3599 });
3600 for effect in expected_effects {
3601 if !self
3602 .forced_handler_effects
3603 .iter()
3604 .any(|(seen_handler, seen_effect)| {
3605 *seen_handler == handler && seen_effect == effect
3606 })
3607 {
3608 self.forced_handler_effects.push((handler, effect.clone()));
3609 }
3610 }
3611 self.finish_handled_value(reentry.dest, value_cont);
3612 return Ok(default_expected_effect(expected_effects));
3613 }
3614
3615 if let Some((op, args)) = primitive_apply(body) {
3616 let expected = primitive_arity(op);
3617 if args.len() != expected {
3618 return Err(CpsLowerError::PrimitiveArityMismatch {
3619 op,
3620 expected,
3621 actual: args.len(),
3622 });
3623 }
3624 let effect_arg = args
3625 .iter()
3626 .enumerate()
3627 .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)));
3628 let Some((effect_index, request)) = effect_arg else {
3629 let value = self.lower_expr(body)?;
3630 self.finish_handled_value(value, value_cont);
3631 return Ok(default_expected_effect(expected_effects));
3632 };
3633 let (expected_effects, handler) =
3634 self.effect_context_for_request(&request, expected_effects, handler);
3635 let (effect, resumed_value) =
3636 self.begin_resume_after_perform(request, &expected_effects, handler)?;
3637 let mut lowered_args = Vec::with_capacity(args.len());
3638 for (index, arg) in args.into_iter().enumerate() {
3639 if index == effect_index {
3640 lowered_args.push(resumed_value);
3641 } else {
3642 lowered_args.push(self.lower_expr(arg)?);
3643 }
3644 }
3645 let dest = self.fresh_value();
3646 self.current.stmts.push(CpsStmt::Primitive {
3647 dest,
3648 op,
3649 args: lowered_args,
3650 });
3651 self.finish_resumed_handled_value(dest, value_cont);
3652 return Ok(effect);
3653 }
3654
3655 if let Some((target_path, info, args)) = direct_apply_path(body, self.functions)? {
3656 let effect_arg = args
3657 .iter()
3658 .enumerate()
3659 .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)));
3660 let Some((effect_index, request)) = effect_arg else {
3661 let call_may_perform = self.target_may_perform_when_called(target_path);
3662 let needs_handler_wrapper_boundary =
3663 self.direct_call_needs_handler_wrapper_boundary(target_path, &args, &body.ty);
3664 if call_may_perform || needs_handler_wrapper_boundary {
3665 self.lower_handled_effectful_call_value(
3666 info,
3667 args,
3668 &body.ty,
3669 call_may_perform || needs_handler_wrapper_boundary,
3670 value_cont,
3671 )?;
3672 } else {
3673 let value = self.lower_expr(body)?;
3674 self.finish_handled_value(value, value_cont);
3675 }
3676 return Ok(default_expected_effect(expected_effects));
3677 };
3678 let (expected_effects, handler) =
3679 self.effect_context_for_request(&request, expected_effects, handler);
3680 let (effect, resumed_value) =
3681 self.begin_resume_after_perform(request, &expected_effects, handler)?;
3682 let mut lowered_args = Vec::with_capacity(args.len());
3683 for (index, arg) in args.into_iter().enumerate() {
3684 if index == effect_index {
3685 lowered_args.push(resumed_value);
3686 } else {
3687 lowered_args.push(self.lower_expr(arg)?);
3688 }
3689 }
3690 if fail_prefix_path(&info.path)
3691 && let [value] = lowered_args.as_slice()
3692 {
3693 self.finish_resumed_handled_value(*value, value_cont);
3694 return Ok(effect);
3695 }
3696 let dest = self.fresh_value();
3697 let should_force_direct = direct_call_result_needs_force(body, info);
3698 self.current.stmts.push(CpsStmt::DirectCall {
3699 dest,
3700 target: info.name.clone(),
3701 args: lowered_args,
3702 });
3703 let dest = if should_force_direct {
3704 let forced = self.fresh_value();
3705 self.current.stmts.push(CpsStmt::ForceThunk {
3706 dest: forced,
3707 thunk: dest,
3708 });
3709 forced
3710 } else {
3711 dest
3712 };
3713 self.finish_resumed_handled_value(dest, value_cont);
3714 return Ok(effect);
3715 }
3716
3717 if let runtime::ExprKind::If {
3718 cond,
3719 then_branch,
3720 else_branch,
3721 ..
3722 } = &body.kind
3723 {
3724 return self.lower_handled_if(
3725 cond,
3726 then_branch,
3727 else_branch,
3728 expected_effects,
3729 handler,
3730 value_cont,
3731 );
3732 }
3733
3734 if let Some((cond, then_branch, else_branch)) = bool_match(body) {
3735 return self.lower_handled_if(
3736 cond,
3737 then_branch,
3738 else_branch,
3739 expected_effects,
3740 handler,
3741 value_cont,
3742 );
3743 }
3744
3745 if let runtime::ExprKind::Match { .. } = &body.kind {
3746 return self.lower_handled_match(body, expected_effects, handler, value_cont);
3747 }
3748
3749 if let runtime::ExprKind::Block { stmts, tail } = &body.kind {
3750 return self.lower_handled_block(
3751 stmts,
3752 tail.as_deref(),
3753 expected_effects,
3754 handler,
3755 value_cont,
3756 );
3757 }
3758
3759 if body_is_thunk_value(body) {
3760 let thunk = self.lower_expr(body)?;
3761 let dest = self.fresh_value();
3762 self.current.stmts.push(CpsStmt::ForceThunk { dest, thunk });
3763 for effect in expected_effects {
3764 if !self
3765 .forced_handler_effects
3766 .iter()
3767 .any(|(seen_handler, seen_effect)| {
3768 *seen_handler == handler && seen_effect == effect
3769 })
3770 {
3771 self.forced_handler_effects.push((handler, effect.clone()));
3772 }
3773 }
3774 self.finish_handled_value(dest, value_cont);
3775 return Ok(default_expected_effect(expected_effects));
3776 }
3777
3778 let value = match self.lower_expr(body) {
3779 Ok(value) => value,
3780 Err(CpsLowerError::UnsupportedExpr { .. }) => {
3781 return Err(CpsLowerError::UnsupportedExpr {
3782 kind: "handler body continuation",
3783 });
3784 }
3785 Err(error) => return Err(error),
3786 };
3787 self.finish_handled_value(value, value_cont);
3788 Ok(default_expected_effect(expected_effects))
3789 }
3790
3791 fn finish_handled_value(&mut self, value: CpsValueId, value_cont: Option<CpsContinuationId>) {
3792 match value_cont {
3793 Some(value_cont) => self.terminate(CpsTerminator::Continue {
3794 target: value_cont,
3795 args: vec![value],
3796 }),
3797 None => self.terminate(CpsTerminator::Return(value)),
3798 }
3799 self.finish_current();
3800 }
3801
3802 fn finish_resumed_handled_value(
3803 &mut self,
3804 value: CpsValueId,
3805 value_cont: Option<CpsContinuationId>,
3806 ) {
3807 if value_cont.is_some_and(|value_cont| self.handler_value_conts.contains(&value_cont)) {
3808 self.terminate(CpsTerminator::Return(value));
3809 self.finish_current();
3810 return;
3811 }
3812 self.finish_handled_value(value, value_cont);
3813 }
3814
3815 fn lower_handled_if(
3816 &mut self,
3817 cond: &runtime::Expr,
3818 then_branch: &runtime::Expr,
3819 else_branch: &runtime::Expr,
3820 expected_effects: &[typed_ir::Path],
3821 handler: CpsHandlerId,
3822 value_cont: Option<CpsContinuationId>,
3823 ) -> CpsLowerResult<typed_ir::Path> {
3824 if !collect_expr_performed_effects(cond).is_empty()
3825 || self.expr_may_perform_when_evaluated(cond)
3826 {
3827 return self.lower_handled_effect_condition_if(
3828 cond,
3829 then_branch,
3830 else_branch,
3831 expected_effects,
3832 handler,
3833 value_cont,
3834 );
3835 }
3836
3837 let cond = self.lower_expr(cond)?;
3838 let saved_locals = self.locals.clone();
3839 let saved_local_exprs = self.local_exprs.clone();
3840 let saved_resumptions = self.resumptions.clone();
3841 let then_cont = self.fresh_continuation();
3842 let else_cont = self.fresh_continuation();
3843
3844 self.terminate(CpsTerminator::Branch {
3845 cond,
3846 then_cont,
3847 else_cont,
3848 });
3849 self.finish_current();
3850
3851 self.current = ContinuationBuilder::new(then_cont, Vec::new());
3852 self.locals = saved_locals.clone();
3853 self.local_exprs = saved_local_exprs.clone();
3854 self.resumptions = saved_resumptions.clone();
3855 let then_effect =
3856 self.lower_handled_body(then_branch, expected_effects, handler, value_cont)?;
3857
3858 self.current = ContinuationBuilder::new(else_cont, Vec::new());
3859 self.locals = saved_locals.clone();
3860 self.local_exprs = saved_local_exprs.clone();
3861 self.resumptions = saved_resumptions.clone();
3862 let else_effect =
3863 self.lower_handled_body(else_branch, expected_effects, handler, value_cont)?;
3864 if !handled_effects_compatible(expected_effects, &then_effect, &else_effect) {
3865 return Err(CpsLowerError::UnsupportedExpr {
3866 kind: "handler effect mismatch",
3867 });
3868 }
3869
3870 self.locals = saved_locals;
3871 self.local_exprs = saved_local_exprs;
3872 self.resumptions = saved_resumptions;
3873 Ok(then_effect)
3874 }
3875
3876 fn lower_handled_effect_condition_if(
3877 &mut self,
3878 cond: &runtime::Expr,
3879 then_branch: &runtime::Expr,
3880 else_branch: &runtime::Expr,
3881 expected_effects: &[typed_ir::Path],
3882 handler: CpsHandlerId,
3883 value_cont: Option<CpsContinuationId>,
3884 ) -> CpsLowerResult<typed_ir::Path> {
3885 let saved_locals = self.locals.clone();
3886 let saved_local_exprs = self.local_exprs.clone();
3887 let saved_resumptions = self.resumptions.clone();
3888 let cond_cont = self.fresh_continuation();
3889 let then_cont = self.fresh_continuation();
3890 let else_cont = self.fresh_continuation();
3891 let cond_value = self.fresh_value();
3892
3893 let cond_effect = self.lower_effectful_condition_to_continuation(
3894 cond,
3895 expected_effects,
3896 handler,
3897 cond_cont,
3898 )?;
3899
3900 self.current = ContinuationBuilder::new(cond_cont, vec![cond_value]);
3901 self.locals = saved_locals.clone();
3902 self.local_exprs = saved_local_exprs.clone();
3903 self.resumptions = saved_resumptions.clone();
3904 self.terminate(CpsTerminator::Branch {
3905 cond: cond_value,
3906 then_cont,
3907 else_cont,
3908 });
3909 self.finish_current();
3910
3911 self.current = ContinuationBuilder::new(then_cont, Vec::new());
3912 self.locals = saved_locals.clone();
3913 self.local_exprs = saved_local_exprs.clone();
3914 self.resumptions = saved_resumptions.clone();
3915 let then_effect =
3916 self.lower_handled_body(then_branch, expected_effects, handler, value_cont)?;
3917
3918 self.current = ContinuationBuilder::new(else_cont, Vec::new());
3919 self.locals = saved_locals.clone();
3920 self.local_exprs = saved_local_exprs.clone();
3921 self.resumptions = saved_resumptions.clone();
3922 let else_effect =
3923 self.lower_handled_body(else_branch, expected_effects, handler, value_cont)?;
3924 if !handled_effects_compatible(expected_effects, &cond_effect, &then_effect)
3925 || !handled_effects_compatible(expected_effects, &cond_effect, &else_effect)
3926 || !handled_effects_compatible(expected_effects, &then_effect, &else_effect)
3927 {
3928 return Err(CpsLowerError::UnsupportedExpr {
3929 kind: "handler effect mismatch",
3930 });
3931 }
3932
3933 self.locals = saved_locals;
3934 self.local_exprs = saved_local_exprs;
3935 self.resumptions = saved_resumptions;
3936 Ok(cond_effect)
3937 }
3938
3939 fn lower_handled_match(
3940 &mut self,
3941 expr: &runtime::Expr,
3942 expected_effects: &[typed_ir::Path],
3943 handler: CpsHandlerId,
3944 value_cont: Option<CpsContinuationId>,
3945 ) -> CpsLowerResult<typed_ir::Path> {
3946 let runtime::ExprKind::Match {
3947 scrutinee, arms, ..
3948 } = &expr.kind
3949 else {
3950 return Err(CpsLowerError::UnsupportedExpr { kind: "match" });
3951 };
3952 let scrutinee = self.lower_expr(scrutinee)?;
3953 let saved_locals = self.locals.clone();
3954 let saved_local_exprs = self.local_exprs.clone();
3955 let saved_resumptions = self.resumptions.clone();
3956 let fallback_cont = self.fresh_continuation();
3957 let mut arm_conts = Vec::with_capacity(arms.len());
3958 let mut guard_conts = Vec::with_capacity(arms.len());
3959 let mut next_conts = Vec::with_capacity(arms.len());
3960 for _ in arms {
3961 arm_conts.push(self.fresh_continuation());
3962 guard_conts.push(None);
3963 }
3964
3965 let mut current_test_cont = None;
3966 for (index, arm) in arms.iter().enumerate() {
3967 if let Some(test_cont) = current_test_cont {
3968 self.current = ContinuationBuilder::new(test_cont, Vec::new());
3969 self.locals = saved_locals.clone();
3970 self.local_exprs = saved_local_exprs.clone();
3971 self.resumptions = saved_resumptions.clone();
3972 }
3973 let next_cont = if index + 1 == arms.len() {
3974 fallback_cont
3975 } else {
3976 let next = self.fresh_continuation();
3977 current_test_cont = Some(next);
3978 next
3979 };
3980 next_conts.push(next_cont);
3981 let success_cont = if arm.guard.is_some() {
3982 let guard_cont = self.fresh_continuation();
3983 guard_conts[index] = Some(guard_cont);
3984 guard_cont
3985 } else {
3986 arm_conts[index]
3987 };
3988 if self.lower_pattern_test(scrutinee, &arm.pattern, success_cont, next_cont)? {
3989 self.finish_current();
3990 }
3991 }
3992
3993 self.current = ContinuationBuilder::new(fallback_cont, Vec::new());
3994 let unit = self.fresh_value();
3995 self.current.stmts.push(CpsStmt::Literal {
3996 dest: unit,
3997 literal: CpsLiteral::Unit,
3998 });
3999 self.finish_handled_value(unit, value_cont);
4000
4001 let mut joined_effect: Option<typed_ir::Path> = None;
4002 for (index, arm) in arms.iter().enumerate() {
4003 let Some(guard_cont) = guard_conts[index] else {
4004 continue;
4005 };
4006 let Some(guard) = &arm.guard else {
4007 continue;
4008 };
4009 self.current = ContinuationBuilder::new(guard_cont, Vec::new());
4010 self.locals = saved_locals.clone();
4011 self.local_exprs = saved_local_exprs.clone();
4012 self.resumptions = saved_resumptions.clone();
4013 self.bind_pattern(&arm.pattern, scrutinee)?;
4014 if !collect_expr_performed_effects(guard).is_empty() {
4015 let guard_value_cont = self.fresh_continuation();
4016 let guard_value = self.fresh_value();
4017 let guard_effect = self.lower_handled_body(
4018 guard,
4019 expected_effects,
4020 handler,
4021 Some(guard_value_cont),
4022 )?;
4023 join_handled_effect(&mut joined_effect, expected_effects, guard_effect)?;
4024 self.current = ContinuationBuilder::new(guard_value_cont, vec![guard_value]);
4025 self.locals = saved_locals.clone();
4026 self.local_exprs = saved_local_exprs.clone();
4027 self.resumptions = saved_resumptions.clone();
4028 self.terminate(CpsTerminator::Branch {
4029 cond: guard_value,
4030 then_cont: arm_conts[index],
4031 else_cont: next_conts[index],
4032 });
4033 self.finish_current();
4034 } else {
4035 let guard_value = self.lower_expr(guard)?;
4036 self.terminate(CpsTerminator::Branch {
4037 cond: guard_value,
4038 then_cont: arm_conts[index],
4039 else_cont: next_conts[index],
4040 });
4041 self.finish_current();
4042 }
4043 }
4044
4045 for (arm, arm_cont) in arms.iter().zip(arm_conts) {
4046 self.current = ContinuationBuilder::new(arm_cont, Vec::new());
4047 self.locals = saved_locals.clone();
4048 self.local_exprs = saved_local_exprs.clone();
4049 self.resumptions = saved_resumptions.clone();
4050 self.bind_pattern(&arm.pattern, scrutinee)?;
4051 let effect =
4052 self.lower_handled_body(&arm.body, expected_effects, handler, value_cont)?;
4053 join_handled_effect(&mut joined_effect, expected_effects, effect)?;
4054 }
4055
4056 self.locals = saved_locals;
4057 self.local_exprs = saved_local_exprs;
4058 self.resumptions = saved_resumptions;
4059 Ok(joined_effect.unwrap_or_else(|| default_expected_effect(expected_effects)))
4060 }
4061
4062 fn lower_handled_block(
4063 &mut self,
4064 stmts: &[runtime::Stmt],
4065 tail: Option<&runtime::Expr>,
4066 expected_effects: &[typed_ir::Path],
4067 handler: CpsHandlerId,
4068 value_cont: Option<CpsContinuationId>,
4069 ) -> CpsLowerResult<typed_ir::Path> {
4070 for (index, stmt) in stmts.iter().enumerate() {
4071 match stmt {
4072 runtime::Stmt::Let { pattern, value } => {
4073 if unused_pure_let(
4074 pattern,
4075 value,
4076 &stmts[index + 1..],
4077 tail,
4078 self.functions,
4079 self.bindings,
4080 ) {
4081 continue;
4082 }
4083 if let Some((name, param, body)) = recursive_lambda_let(pattern, value) {
4084 let value = self.lower_recursive_lambda(name, param, body)?;
4085 self.locals
4086 .insert(typed_ir::Path::from_name(name.clone()), value);
4087 continue;
4088 }
4089 if let Some(request) = effect_apply_nested(value) {
4090 let (routed_effects, routed_handler) =
4091 self.effect_context_for_request(&request, expected_effects, handler);
4092 let (effect, resumed_value) = self.begin_resume_after_perform(
4093 request,
4094 &routed_effects,
4095 routed_handler,
4096 )?;
4097 self.bind_pattern(pattern, resumed_value)?;
4098 let rest_effect = self.lower_handled_block(
4099 &stmts[index + 1..],
4100 tail,
4101 expected_effects,
4102 handler,
4103 None,
4104 )?;
4105 if !handled_effects_compatible(expected_effects, &rest_effect, &effect) {
4106 return Err(CpsLowerError::UnsupportedExpr {
4107 kind: "handler effect mismatch",
4108 });
4109 }
4110 return Ok(effect);
4111 }
4112
4113 if let Some((target_path, info, args)) =
4119 direct_apply_path(value, self.functions)?
4120 {
4121 let target = info.name.clone();
4122 let call_may_perform = self.target_may_perform_when_called(target_path);
4123 let needs_handler_wrapper_boundary = self
4124 .direct_call_needs_handler_wrapper_boundary(
4125 target_path,
4126 &args,
4127 &value.ty,
4128 );
4129 if call_may_perform
4130 || needs_handler_wrapper_boundary
4131 || (self.active_handler.is_some()
4132 && matches!(info.ret, runtime::Type::Thunk { .. }))
4133 {
4134 return self.lower_handled_effectful_call_let(
4135 pattern,
4136 target,
4137 info,
4138 args,
4139 &value.ty,
4140 call_may_perform || needs_handler_wrapper_boundary,
4141 &stmts[index + 1..],
4142 tail,
4143 expected_effects,
4144 handler,
4145 value_cont,
4146 );
4147 }
4148 }
4149 if let runtime::ExprKind::Apply { callee, arg, .. } = &value.kind
4155 && callee_type_may_perform(&callee.ty)
4156 {
4157 return self.lower_handled_effectful_apply_let(
4158 pattern,
4159 callee,
4160 arg,
4161 &value.ty,
4162 &stmts[index + 1..],
4163 tail,
4164 expected_effects,
4165 handler,
4166 value_cont,
4167 );
4168 }
4169
4170 let value = self.lower_expr(value)?;
4171 self.bind_pattern(pattern, value)?;
4172 }
4173 runtime::Stmt::Expr(expr) => {
4174 if !stmts[index + 1..].is_empty() || tail.is_some() {
4175 if let runtime::ExprKind::BindHere { expr: inner } = &expr.kind {
4176 let thunk =
4177 self.with_sync_direct_call_for_ignored_force_depth(|this| {
4178 this.with_sync_apply_for_immediate_force_depth(|this| {
4179 this.lower_expr(inner)
4180 })
4181 })?;
4182 let post_cont = self.fresh_continuation();
4183 let ignored = self.fresh_value();
4184 self.terminate(CpsTerminator::EffectfulForce {
4185 thunk,
4186 resume: post_cont,
4187 });
4188 self.finish_current();
4189 self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
4190 return self.lower_handled_block(
4191 &stmts[index + 1..],
4192 tail,
4193 expected_effects,
4194 handler,
4195 value_cont,
4196 );
4197 }
4198 }
4199
4200 if let Some(request) = effect_apply_nested(expr) {
4201 let (routed_effects, routed_handler) =
4202 self.effect_context_for_request(&request, expected_effects, handler);
4203 let (effect, resumed_value) = self.begin_resume_after_perform(
4204 request,
4205 &routed_effects,
4206 routed_handler,
4207 )?;
4208 if stmts[index + 1..].is_empty() && tail.is_none() {
4209 self.finish_handled_value(resumed_value, value_cont);
4210 return Ok(effect);
4211 }
4212 let rest_effect = self.lower_handled_block(
4213 &stmts[index + 1..],
4214 tail,
4215 expected_effects,
4216 handler,
4217 None,
4218 )?;
4219 if !handled_effects_compatible(expected_effects, &rest_effect, &effect) {
4220 return Err(CpsLowerError::UnsupportedExpr {
4221 kind: "handler effect mismatch",
4222 });
4223 }
4224 return Ok(effect);
4225 }
4226
4227 let is_direct_call = direct_apply(expr, self.functions)?.is_some();
4228 let mut value = self.lower_expr(expr)?;
4229 if is_direct_call || matches!(expr.ty, runtime::Type::Thunk { .. }) {
4236 let dest = self.fresh_value();
4237 self.current
4238 .stmts
4239 .push(CpsStmt::ForceThunk { dest, thunk: value });
4240 value = dest;
4241 }
4242 if stmts[index + 1..].is_empty() && tail.is_none() {
4243 self.finish_handled_value(value, value_cont);
4244 return Ok(default_expected_effect(expected_effects));
4245 }
4246 }
4247 runtime::Stmt::Module { .. } => {
4248 return Err(CpsLowerError::UnsupportedExpr {
4249 kind: "module statement",
4250 });
4251 }
4252 }
4253 }
4254
4255 match tail {
4256 Some(tail) => self.lower_handled_body(tail, expected_effects, handler, value_cont),
4257 None => Err(CpsLowerError::UnsupportedExpr {
4258 kind: "handler body continuation",
4259 }),
4260 }
4261 }
4262
4263 fn lower_handled_effectful_call_value(
4268 &mut self,
4269 info: &FunctionInfo,
4270 args: Vec<&runtime::Expr>,
4271 call_ty: &runtime::Type,
4272 call_may_perform: bool,
4273 value_cont: Option<CpsContinuationId>,
4274 ) -> CpsLowerResult<()> {
4275 let info_params = info.params.clone();
4276 let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
4277 let lowered_args = args
4278 .into_iter()
4279 .enumerate()
4280 .map(|(idx, arg)| -> CpsLowerResult<CpsValueId> {
4281 let expected = info_params
4282 .get(idx)
4283 .cloned()
4284 .unwrap_or_else(runtime::Type::unknown);
4285 let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
4286 self.lower_expr_as_thunk_value(arg)?
4287 } else {
4288 self.lower_expr(arg)?
4289 };
4290 Ok(self.force_if_non_thunk_demand(lowered, &expected))
4291 })
4292 .collect::<CpsLowerResult<Vec<_>>>()?;
4293 if fail_prefix_path(&info.path)
4294 && let [value] = lowered_args.as_slice()
4295 {
4296 self.finish_handled_value(*value, value_cont);
4297 return Ok(());
4298 }
4299 let post_cont = self.fresh_continuation();
4300 let result_id = self.fresh_value();
4301 self.terminate(CpsTerminator::EffectfulCall {
4302 target: info.name.clone(),
4303 args: lowered_args,
4304 resume: post_cont,
4305 });
4306 self.finish_current();
4307 if info_returns_thunk || call_may_perform {
4308 self.mark_active_handlers_external_call();
4309 }
4310
4311 self.current = ContinuationBuilder::new(post_cont, vec![result_id]);
4312 let value = if matches!(call_ty, runtime::Type::Thunk { .. }) && !call_may_perform {
4313 result_id
4314 } else {
4315 let force_cont = self.fresh_continuation();
4320 let forced = self.fresh_value();
4321 self.terminate(CpsTerminator::EffectfulForce {
4322 thunk: result_id,
4323 resume: force_cont,
4324 });
4325 self.finish_current();
4326 self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4327 forced
4328 };
4329 self.finish_handled_value(value, value_cont);
4330 Ok(())
4331 }
4332
4333 #[allow(clippy::too_many_arguments)]
4334 fn lower_handled_effectful_call_let(
4335 &mut self,
4336 pattern: &runtime::Pattern,
4337 target: String,
4338 info: &FunctionInfo,
4339 args: Vec<&runtime::Expr>,
4340 call_ty: &runtime::Type,
4341 call_may_perform: bool,
4342 rest: &[runtime::Stmt],
4343 tail: Option<&runtime::Expr>,
4344 expected_effects: &[typed_ir::Path],
4345 handler: CpsHandlerId,
4346 value_cont: Option<CpsContinuationId>,
4347 ) -> CpsLowerResult<typed_ir::Path> {
4348 let info_params = info.params.clone();
4349 let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
4350 let lowered_args = args
4351 .into_iter()
4352 .enumerate()
4353 .map(|(idx, arg)| -> CpsLowerResult<CpsValueId> {
4354 let expected = info_params
4355 .get(idx)
4356 .cloned()
4357 .unwrap_or_else(runtime::Type::unknown);
4358 let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
4359 self.lower_expr_as_thunk_value(arg)?
4360 } else {
4361 self.lower_expr(arg)?
4362 };
4363 Ok(self.force_if_non_thunk_demand(lowered, &expected))
4369 })
4370 .collect::<CpsLowerResult<Vec<_>>>()?;
4371 if fail_prefix_path(&info.path)
4372 && let [value] = lowered_args.as_slice()
4373 {
4374 self.bind_pattern(pattern, *value)?;
4375 return self.lower_handled_block(rest, tail, expected_effects, handler, value_cont);
4376 }
4377
4378 let post_cont = self.fresh_continuation();
4379 let result_id = self.fresh_value();
4380
4381 self.terminate(CpsTerminator::EffectfulCall {
4382 target,
4383 args: lowered_args,
4384 resume: post_cont,
4385 });
4386 self.finish_current();
4387 if info_returns_thunk || call_may_perform {
4388 self.mark_active_handlers_external_call();
4389 }
4390
4391 self.current = ContinuationBuilder::new(post_cont, vec![result_id]);
4392 let bound = if matches!(call_ty, runtime::Type::Thunk { .. }) {
4398 result_id
4399 } else {
4400 let force_cont = self.fresh_continuation();
4401 let forced = self.fresh_value();
4402 self.terminate(CpsTerminator::EffectfulForce {
4403 thunk: result_id,
4404 resume: force_cont,
4405 });
4406 self.finish_current();
4407 self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4408 forced
4409 };
4410 self.bind_pattern(pattern, bound)?;
4411
4412 let rest_effect =
4413 self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)?;
4414 Ok(rest_effect)
4415 }
4416
4417 #[allow(clippy::too_many_arguments)]
4421 fn lower_handled_effectful_apply_let(
4422 &mut self,
4423 pattern: &runtime::Pattern,
4424 callee: &runtime::Expr,
4425 arg: &runtime::Expr,
4426 apply_ty: &runtime::Type,
4427 rest: &[runtime::Stmt],
4428 tail: Option<&runtime::Expr>,
4429 expected_effects: &[typed_ir::Path],
4430 handler: CpsHandlerId,
4431 value_cont: Option<CpsContinuationId>,
4432 ) -> CpsLowerResult<typed_ir::Path> {
4433 let closure = self.lower_expr(callee)?;
4434 let arg_value = self.lower_expr_as_call_arg(&callee.ty, arg)?;
4435
4436 let post_cont = self.fresh_continuation();
4437 let result_id = self.fresh_value();
4438
4439 self.terminate(CpsTerminator::EffectfulApply {
4440 closure,
4441 arg: arg_value,
4442 resume: post_cont,
4443 });
4444 self.finish_current();
4445
4446 self.current = ContinuationBuilder::new(post_cont, vec![result_id]);
4447 let bound = if matches!(apply_ty, runtime::Type::Thunk { .. }) {
4448 result_id
4449 } else {
4450 let force_cont = self.fresh_continuation();
4451 let forced = self.fresh_value();
4452 self.terminate(CpsTerminator::EffectfulForce {
4453 thunk: result_id,
4454 resume: force_cont,
4455 });
4456 self.finish_current();
4457 self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4458 forced
4459 };
4460 self.bind_pattern(pattern, bound)?;
4461
4462 self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)
4463 }
4464
4465 #[allow(dead_code, clippy::too_many_arguments)]
4473 fn lower_handled_effectful_call_expr(
4474 &mut self,
4475 target: String,
4476 info: &FunctionInfo,
4477 args: Vec<&runtime::Expr>,
4478 call_ty: &runtime::Type,
4479 rest: &[runtime::Stmt],
4480 tail: Option<&runtime::Expr>,
4481 expected_effects: &[typed_ir::Path],
4482 handler: CpsHandlerId,
4483 value_cont: Option<CpsContinuationId>,
4484 ) -> CpsLowerResult<typed_ir::Path> {
4485 let info_params = info.params.clone();
4486 let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
4487 let lowered_args = args
4488 .into_iter()
4489 .enumerate()
4490 .map(|(idx, arg)| {
4491 if matches!(info_params.get(idx), Some(runtime::Type::Thunk { .. })) {
4492 self.lower_expr_as_thunk_value(arg)
4493 } else {
4494 self.lower_expr(arg)
4495 }
4496 })
4497 .collect::<CpsLowerResult<Vec<_>>>()?;
4498
4499 let post_cont = self.fresh_continuation();
4500 let ignored = self.fresh_value();
4501
4502 self.terminate(CpsTerminator::EffectfulCall {
4503 target,
4504 args: lowered_args,
4505 resume: post_cont,
4506 });
4507 self.finish_current();
4508 if info_returns_thunk {
4509 self.mark_active_handlers_external_call();
4510 }
4511
4512 self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
4513 if !matches!(call_ty, runtime::Type::Thunk { .. }) {
4517 let force_cont = self.fresh_continuation();
4518 let forced = self.fresh_value();
4519 self.terminate(CpsTerminator::EffectfulForce {
4520 thunk: ignored,
4521 resume: force_cont,
4522 });
4523 self.finish_current();
4524 self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4525 }
4526
4527 self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)
4528 }
4529
4530 #[allow(dead_code, clippy::too_many_arguments)]
4531 fn lower_handled_effectful_apply_expr(
4532 &mut self,
4533 callee: &runtime::Expr,
4534 arg: &runtime::Expr,
4535 apply_ty: &runtime::Type,
4536 rest: &[runtime::Stmt],
4537 tail: Option<&runtime::Expr>,
4538 expected_effects: &[typed_ir::Path],
4539 handler: CpsHandlerId,
4540 value_cont: Option<CpsContinuationId>,
4541 ) -> CpsLowerResult<typed_ir::Path> {
4542 let closure = self.lower_expr(callee)?;
4543 let arg_value = self.lower_expr_as_call_arg(&callee.ty, arg)?;
4544
4545 let post_cont = self.fresh_continuation();
4546 let ignored = self.fresh_value();
4547
4548 self.terminate(CpsTerminator::EffectfulApply {
4549 closure,
4550 arg: arg_value,
4551 resume: post_cont,
4552 });
4553 self.finish_current();
4554
4555 self.current = ContinuationBuilder::new(post_cont, vec![ignored]);
4556 if !matches!(apply_ty, runtime::Type::Thunk { .. }) {
4557 let force_cont = self.fresh_continuation();
4558 let forced = self.fresh_value();
4559 self.terminate(CpsTerminator::EffectfulForce {
4560 thunk: ignored,
4561 resume: force_cont,
4562 });
4563 self.finish_current();
4564 self.current = ContinuationBuilder::new(force_cont, vec![forced]);
4565 }
4566
4567 self.lower_handled_block(rest, tail, expected_effects, handler, value_cont)
4568 }
4569
4570 fn begin_resume_after_perform(
4571 &mut self,
4572 request: CpsEffectApply<'_>,
4573 expected_effects: &[typed_ir::Path],
4574 handler: CpsHandlerId,
4575 ) -> CpsLowerResult<(typed_ir::Path, CpsValueId)> {
4576 let effect = request.effect.clone();
4577 let payload = request.payload;
4578 let blocked = request.blocked;
4579 if let Some(payload) = handle_body_execution_inner(payload) {
4580 return self.begin_resume_after_perform(
4581 CpsEffectApply {
4582 effect,
4583 payload,
4584 blocked,
4585 },
4586 expected_effects,
4587 handler,
4588 );
4589 }
4590
4591 if let Some((op, args)) = primitive_apply(payload) {
4592 let expected = primitive_arity(op);
4593 if args.len() != expected {
4594 return Err(CpsLowerError::PrimitiveArityMismatch {
4595 op,
4596 expected,
4597 actual: args.len(),
4598 });
4599 }
4600 if let Some((effect_index, inner_request)) = args
4601 .iter()
4602 .enumerate()
4603 .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)))
4604 {
4605 let (_, resumed_value) =
4606 self.begin_resume_after_perform(inner_request, expected_effects, handler)?;
4607 let mut lowered_args = Vec::with_capacity(args.len());
4608 for (index, arg) in args.into_iter().enumerate() {
4609 if index == effect_index {
4610 lowered_args.push(resumed_value);
4611 } else {
4612 lowered_args.push(self.lower_expr(arg)?);
4613 }
4614 }
4615 let payload = self.fresh_value();
4616 self.current.stmts.push(CpsStmt::Primitive {
4617 dest: payload,
4618 op,
4619 args: lowered_args,
4620 });
4621 return self.begin_resume_after_perform_value(
4622 effect,
4623 payload,
4624 blocked,
4625 expected_effects,
4626 handler,
4627 );
4628 }
4629 }
4630
4631 if let Some((target, info, args)) = direct_apply(payload, self.functions)? {
4632 if let Some((effect_index, inner_request)) = args
4633 .iter()
4634 .enumerate()
4635 .filter(|(index, _)| {
4636 !matches!(info.params.get(*index), Some(runtime::Type::Thunk { .. }))
4637 })
4638 .find_map(|(index, arg)| effect_apply_nested(arg).map(|effect| (index, effect)))
4639 {
4640 let (_, resumed_value) =
4641 self.begin_resume_after_perform(inner_request, expected_effects, handler)?;
4642 let mut lowered_args = Vec::with_capacity(args.len());
4643 for (index, arg) in args.into_iter().enumerate() {
4644 if index == effect_index {
4645 lowered_args.push(resumed_value);
4646 } else {
4647 lowered_args.push(self.lower_expr(arg)?);
4648 }
4649 }
4650 let payload_expr = payload;
4651 let payload = self.fresh_value();
4652 self.current.stmts.push(CpsStmt::DirectCall {
4653 dest: payload,
4654 target,
4655 args: lowered_args,
4656 });
4657 let payload = if direct_call_result_needs_force(payload_expr, info) {
4658 let forced = self.fresh_value();
4659 self.current.stmts.push(CpsStmt::ForceThunk {
4660 dest: forced,
4661 thunk: payload,
4662 });
4663 forced
4664 } else {
4665 payload
4666 };
4667 return self.begin_resume_after_perform_value(
4668 effect,
4669 payload,
4670 blocked,
4671 expected_effects,
4672 handler,
4673 );
4674 }
4675 }
4676
4677 let payload = self.lower_expr(payload)?;
4678 self.begin_resume_after_perform_value(effect, payload, blocked, expected_effects, handler)
4679 }
4680
4681 fn begin_resume_after_perform_value(
4682 &mut self,
4683 effect: typed_ir::Path,
4684 payload: CpsValueId,
4685 blocked: Option<runtime::EffectIdRef>,
4686 _expected_effects: &[typed_ir::Path],
4687 handler: CpsHandlerId,
4688 ) -> CpsLowerResult<(typed_ir::Path, CpsValueId)> {
4689 let blocked = blocked
4690 .map(|blocked| self.lower_effect_id_ref(blocked))
4691 .transpose()?;
4692 let resume_cont = self.fresh_continuation();
4693 self.terminate(CpsTerminator::Perform {
4694 effect: effect.clone(),
4695 payload,
4696 resume: resume_cont,
4697 handler,
4698 blocked,
4699 });
4700 self.finish_current();
4701
4702 let resumed_value = self.fresh_value();
4703 self.current = ContinuationBuilder::new(resume_cont, vec![resumed_value]);
4704 Ok((effect, resumed_value))
4705 }
4706
4707 fn bind_pattern(
4708 &mut self,
4709 pattern: &runtime::Pattern,
4710 value: CpsValueId,
4711 ) -> CpsLowerResult<()> {
4712 match pattern {
4713 runtime::Pattern::Wildcard { .. } => Ok(()),
4714 runtime::Pattern::Bind { name, .. } => {
4715 let path = typed_ir::Path::from_name(name.clone());
4716 self.local_exprs.remove(&path);
4717 self.locals.insert(path, value);
4718 Ok(())
4719 }
4720 runtime::Pattern::Lit { .. } => Ok(()),
4721 runtime::Pattern::Tuple { items, .. } => {
4722 for (index, item) in items.iter().enumerate() {
4723 let item_value = self.fresh_value();
4724 self.current.stmts.push(CpsStmt::TupleGet {
4725 dest: item_value,
4726 tuple: value,
4727 index,
4728 });
4729 self.bind_pattern(item, item_value)?;
4730 }
4731 Ok(())
4732 }
4733 runtime::Pattern::List {
4734 prefix,
4735 spread,
4736 suffix,
4737 ..
4738 } => self.bind_list_pattern(prefix, spread.as_deref(), suffix, value),
4739 runtime::Pattern::Record { fields, spread, .. } => {
4740 self.bind_record_pattern(fields, spread.as_ref(), value)
4741 }
4742 runtime::Pattern::Variant {
4743 value: Some(payload),
4744 ..
4745 } => {
4746 let payload_value = self.fresh_value();
4747 self.current.stmts.push(CpsStmt::VariantPayload {
4748 dest: payload_value,
4749 variant: value,
4750 });
4751 self.bind_pattern(payload, payload_value)
4752 }
4753 runtime::Pattern::Variant { value: None, .. } => Ok(()),
4754 runtime::Pattern::Or { .. } => Err(CpsLowerError::UnsupportedPattern { kind: "or" }),
4755 runtime::Pattern::As { pattern, name, .. } => {
4756 self.bind_pattern(pattern, value)?;
4757 self.locals
4758 .insert(typed_ir::Path::from_name(name.clone()), value);
4759 Ok(())
4760 }
4761 }
4762 }
4763
4764 fn bind_list_pattern(
4765 &mut self,
4766 prefix: &[runtime::Pattern],
4767 spread: Option<&runtime::Pattern>,
4768 suffix: &[runtime::Pattern],
4769 value: CpsValueId,
4770 ) -> CpsLowerResult<()> {
4771 let len = if spread.is_some() || !suffix.is_empty() {
4772 Some(self.emit_primitive(typed_ir::PrimitiveOp::ListLen, vec![value]))
4773 } else {
4774 None
4775 };
4776 for (index, item) in prefix.iter().enumerate() {
4777 let index = self.emit_int_literal(index as i64);
4778 let item_value =
4779 self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
4780 self.bind_pattern(item, item_value)?;
4781 }
4782 if let Some(spread) = spread {
4783 let start = self.emit_int_literal(prefix.len() as i64);
4784 let suffix_len = self.emit_int_literal(suffix.len() as i64);
4785 let end = self.emit_primitive(
4786 typed_ir::PrimitiveOp::IntSub,
4787 vec![len.expect("list spread requires len"), suffix_len],
4788 );
4789 let slice = self.emit_primitive(
4790 typed_ir::PrimitiveOp::ListIndexRangeRaw,
4791 vec![value, start, end],
4792 );
4793 self.bind_pattern(spread, slice)?;
4794 }
4795 for (offset, item) in suffix.iter().enumerate() {
4796 let suffix_len = self.emit_int_literal(suffix.len() as i64);
4797 let suffix_start = self.emit_primitive(
4798 typed_ir::PrimitiveOp::IntSub,
4799 vec![len.expect("list suffix requires len"), suffix_len],
4800 );
4801 let offset = self.emit_int_literal(offset as i64);
4802 let index =
4803 self.emit_primitive(typed_ir::PrimitiveOp::IntAdd, vec![suffix_start, offset]);
4804 let item_value =
4805 self.emit_primitive(typed_ir::PrimitiveOp::ListIndex, vec![value, index]);
4806 self.bind_pattern(item, item_value)?;
4807 }
4808 Ok(())
4809 }
4810
4811 fn bind_record_pattern(
4812 &mut self,
4813 fields: &[runtime::RecordPatternField],
4814 spread: Option<&runtime::RecordSpreadPattern>,
4815 value: CpsValueId,
4816 ) -> CpsLowerResult<()> {
4817 for field in fields {
4818 let field_value = self.fresh_value();
4819 if let Some(default) = &field.default {
4820 let default = self.lower_expr(default)?;
4821 self.current.stmts.push(CpsStmt::SelectWithDefault {
4822 dest: field_value,
4823 base: value,
4824 field: field.name.clone(),
4825 default,
4826 });
4827 } else {
4828 self.current.stmts.push(CpsStmt::Select {
4829 dest: field_value,
4830 base: value,
4831 field: field.name.clone(),
4832 });
4833 }
4834 self.bind_pattern(&field.pattern, field_value)?;
4835 }
4836 if let Some(spread) = record_spread_pattern(spread) {
4837 let rest = self.emit_record_without_fields(value, fields);
4838 self.bind_pattern(spread, rest)?;
4839 }
4840 Ok(())
4841 }
4842
4843 fn emit_record_without_fields(
4844 &mut self,
4845 value: CpsValueId,
4846 fields: &[runtime::RecordPatternField],
4847 ) -> CpsValueId {
4848 let dest = self.fresh_value();
4849 self.current.stmts.push(CpsStmt::RecordWithoutFields {
4850 dest,
4851 base: value,
4852 fields: fields.iter().map(|field| field.name.clone()).collect(),
4853 });
4854 dest
4855 }
4856
4857 fn emit_int_literal(&mut self, value: i64) -> CpsValueId {
4858 let dest = self.fresh_value();
4859 self.current.stmts.push(CpsStmt::Literal {
4860 dest,
4861 literal: CpsLiteral::Int(value.to_string()),
4862 });
4863 dest
4864 }
4865
4866 fn emit_primitive(&mut self, op: typed_ir::PrimitiveOp, args: Vec<CpsValueId>) -> CpsValueId {
4867 let dest = self.fresh_value();
4868 self.current
4869 .stmts
4870 .push(CpsStmt::Primitive { dest, op, args });
4871 dest
4872 }
4873
4874 fn fresh_value(&mut self) -> CpsValueId {
4875 let value = CpsValueId(self.next_value);
4876 self.next_value += 1;
4877 value
4878 }
4879
4880 fn fresh_continuation(&mut self) -> CpsContinuationId {
4881 let continuation = CpsContinuationId(self.next_continuation);
4882 self.next_continuation += 1;
4883 continuation
4884 }
4885
4886 fn fresh_handler(&mut self) -> CpsHandlerId {
4887 let handler = CpsHandlerId(self.next_handler);
4888 self.next_handler += 1;
4889 handler
4890 }
4891
4892 fn current_effect_context(&self) -> (Vec<typed_ir::Path>, CpsHandlerId) {
4893 self.active_handler
4894 .as_ref()
4895 .map(|context| (context.expected_effects.clone(), context.handler))
4896 .unwrap_or_else(|| (Vec::new(), dynamic_handler_id()))
4897 }
4898
4899 fn effect_context_for_request(
4900 &self,
4901 request: &CpsEffectApply<'_>,
4902 expected_effects: &[typed_ir::Path],
4903 handler: CpsHandlerId,
4904 ) -> (Vec<typed_ir::Path>, CpsHandlerId) {
4905 if let Some(context) = self.active_context_for_effect(&request.effect) {
4906 return (context.expected_effects.clone(), context.handler);
4907 }
4908 if matches_any_effect(expected_effects, &request.effect) {
4909 return (expected_effects.to_vec(), handler);
4910 }
4911 (Vec::new(), dynamic_handler_id())
4912 }
4913
4914 fn active_context_for_effect(&self, effect: &typed_ir::Path) -> Option<&ActiveHandlerContext> {
4915 let mut current = self.active_handler.as_ref();
4916 while let Some(context) = current {
4917 if matches_any_effect(&context.expected_effects, effect) {
4918 return Some(context);
4919 }
4920 current = context.parent.as_deref();
4921 }
4922 None
4923 }
4924
4925 fn performed_effects_for_handler(&self, handler: CpsHandlerId) -> Vec<typed_ir::Path> {
4926 let mut effects = Vec::new();
4927 for continuation in &self.continuations {
4928 if let CpsTerminator::Perform {
4929 effect,
4930 handler: used,
4931 ..
4932 } = &continuation.terminator
4933 && *used == handler
4934 && !effects.iter().any(|seen| seen == effect)
4935 {
4936 effects.push(effect.clone());
4937 }
4938 }
4939 for (used, effect) in &self.forced_handler_effects {
4940 if *used == handler && !effects.iter().any(|seen| seen == effect) {
4941 effects.push(effect.clone());
4942 }
4943 }
4944 effects
4945 }
4946
4947 fn target_may_perform_when_called(&self, target: &typed_ir::Path) -> bool {
4948 let mut visiting = HashSet::new();
4949 let mut memo = HashMap::new();
4950 binding_may_perform_when_called(
4951 target,
4952 self.functions,
4953 self.bindings,
4954 &mut visiting,
4955 &mut memo,
4956 )
4957 }
4958
4959 fn direct_call_has_handler_reentry_arg(
4960 &self,
4961 target: &typed_ir::Path,
4962 args: &[&runtime::Expr],
4963 ) -> bool {
4964 if self.active_handler.is_none() {
4965 return false;
4966 }
4967 let Some(binding) = self.bindings.get(target) else {
4968 return false;
4969 };
4970 if handler_wrapper_info(binding).is_none() {
4971 return false;
4972 }
4973 args.iter().any(|arg| self.expr_contains_resume_apply(arg))
4974 }
4975
4976 fn direct_call_needs_handler_wrapper_boundary(
4977 &self,
4978 target: &typed_ir::Path,
4979 args: &[&runtime::Expr],
4980 result_ty: &runtime::Type,
4981 ) -> bool {
4982 if !runtime_type_is_bool_value(result_ty) {
4983 return false;
4984 }
4985 if path_name(target) == self.name {
4986 return false;
4987 }
4988 let Some(binding) = self.bindings.get(target) else {
4989 return false;
4990 };
4991 let Some(wrapper) = handler_wrapper_info(binding) else {
4992 return false;
4993 };
4994 handler_wrapper_args_have_unconsumed_effects_for_wrapper(args, &wrapper)
4995 }
4996
4997 fn expr_may_perform_when_evaluated(&self, expr: &runtime::Expr) -> bool {
4998 let mut visiting = HashSet::new();
4999 let mut memo = HashMap::new();
5000 expr_may_perform_when_evaluated(
5001 expr,
5002 self.functions,
5003 self.bindings,
5004 &mut visiting,
5005 &mut memo,
5006 )
5007 }
5008
5009 fn plan_direct_call<'expr, 'functions>(
5010 &self,
5011 expr: &'expr runtime::Expr,
5012 target_path: &'expr typed_ir::Path,
5013 info: &'functions FunctionInfo,
5014 args: Vec<&'expr runtime::Expr>,
5015 ) -> DirectCallPlan<'expr, 'functions> {
5016 let info_returns_thunk = matches!(info.ret, runtime::Type::Thunk { .. });
5017 let target_may_perform = self.target_may_perform_when_called(target_path);
5018 let needs_handler_wrapper_boundary =
5019 self.direct_call_needs_handler_wrapper_boundary(target_path, &args, &expr.ty);
5020 let force_handler_reentry_args =
5021 self.direct_call_has_handler_reentry_arg(target_path, &args);
5022 let should_inline = (!matches!(expr.ty, runtime::Type::Thunk { .. })
5023 && args.iter().any(|arg| is_inline_argument(arg)))
5024 || (self.active_handler.is_some() && info_returns_thunk && target_may_perform);
5025 let ignored_immediate_force =
5026 self.sync_direct_call_for_ignored_force_depth.is_active() && info_returns_thunk;
5027 let ignored_unit_immediate_force =
5028 ignored_immediate_force && runtime_type_is_unit_value(&expr.ty);
5029 let mode = if (self.higher_order_helper
5030 && info_returns_thunk
5031 && !ignored_unit_immediate_force)
5032 || (!ignored_immediate_force && (target_may_perform || needs_handler_wrapper_boundary))
5033 {
5034 DirectCallMode::EffectfulWithResume
5035 } else {
5036 DirectCallMode::SyncDirect
5037 };
5038 DirectCallPlan {
5039 expr,
5040 target: info.name.clone(),
5041 info,
5042 args,
5043 mode,
5044 target_may_perform,
5045 info_returns_thunk,
5046 force_handler_reentry_args,
5047 should_inline,
5048 }
5049 }
5050
5051 fn lower_direct_call_plan(
5052 &mut self,
5053 plan: DirectCallPlan<'_, '_>,
5054 ) -> CpsLowerResult<CpsValueId> {
5055 if (fail_prefix_path(&plan.info.path)
5056 || self
5057 .bindings
5058 .get(&plan.info.path)
5059 .is_some_and(|binding| binding_is_throw_forwarder(binding)))
5060 && let [arg] = plan.args.as_slice()
5061 {
5062 let expected = plan
5063 .info
5064 .params
5065 .first()
5066 .cloned()
5067 .unwrap_or_else(runtime::Type::unknown);
5068 let value = self.lower_direct_call_arg(arg, &expected)?;
5069 return Ok(self.force_if_non_thunk_demand(value, &plan.expr.ty));
5070 }
5071 if plan.should_inline
5072 && let Some(value) = self.lower_inline_direct_apply(plan.expr)?
5073 {
5074 return Ok(self.force_if_non_thunk_demand(value, &plan.expr.ty));
5075 }
5076
5077 let args = self.lower_direct_call_args(&plan)?;
5078 match plan.mode {
5079 DirectCallMode::EffectfulWithResume => {
5080 self.mark_active_handlers_external_call();
5085 let post_cont = self.fresh_continuation();
5086 let result = self.fresh_value();
5087 self.terminate(CpsTerminator::EffectfulCall {
5088 target: plan.target,
5089 args,
5090 resume: post_cont,
5091 });
5092 self.finish_current();
5093 self.current = ContinuationBuilder::new(post_cont, vec![result]);
5094 Ok(self.force_if_non_thunk_demand(result, &plan.expr.ty))
5104 }
5105 DirectCallMode::SyncDirect => {
5106 let dest = self.fresh_value();
5107 self.current.stmts.push(CpsStmt::DirectCall {
5108 dest,
5109 target: plan.target,
5110 args,
5111 });
5112 if plan.info_returns_thunk || plan.target_may_perform {
5113 self.mark_active_handlers_external_call();
5114 }
5115 Ok(self.force_if_non_thunk_demand(dest, &plan.expr.ty))
5124 }
5125 }
5126 }
5127
5128 fn lower_direct_call_args(
5129 &mut self,
5130 plan: &DirectCallPlan<'_, '_>,
5131 ) -> CpsLowerResult<Vec<CpsValueId>> {
5132 let info_params = plan.info.params.clone();
5133 plan.args
5134 .iter()
5135 .enumerate()
5136 .map(|(idx, arg)| {
5137 let expected = info_params
5138 .get(idx)
5139 .cloned()
5140 .unwrap_or_else(runtime::Type::unknown);
5141 let force_effectful_arg = plan.target_may_perform
5142 && expr_contains_indirect_apply(arg, self.functions)
5143 && !type_is_callable_after_force(&arg.ty)
5144 && !matches!(expected, runtime::Type::Thunk { .. })
5145 && !matches!(arg.ty, runtime::Type::Thunk { .. });
5146 let needs_forced_effectful_depth = (plan.force_handler_reentry_args
5147 && self.expr_contains_resume_apply(arg))
5148 || force_effectful_arg;
5149 if needs_forced_effectful_depth {
5150 self.with_force_effectful_apply_depth(|this| {
5151 this.lower_direct_call_arg(arg, &expected)
5152 })
5153 } else {
5154 self.lower_direct_call_arg(arg, &expected)
5155 }
5156 })
5157 .collect()
5158 }
5159
5160 fn lower_direct_call_arg(
5161 &mut self,
5162 arg: &runtime::Expr,
5163 expected: &runtime::Type,
5164 ) -> CpsLowerResult<CpsValueId> {
5165 let lowered = if matches!(expected, runtime::Type::Thunk { .. }) {
5166 self.lower_expr_as_thunk_value(arg)?
5167 } else {
5168 self.lower_expr(arg)?
5169 };
5170 Ok(self.force_if_non_thunk_demand(lowered, expected))
5171 }
5172
5173 fn lower_inline_direct_apply(
5174 &mut self,
5175 expr: &runtime::Expr,
5176 ) -> CpsLowerResult<Option<CpsValueId>> {
5177 let Some((target, info, args)) = direct_apply_path(expr, self.functions)? else {
5178 return Ok(None);
5179 };
5180 if path_name(target) == self.name || !self.inline_stack.insert(target.clone()) {
5181 return Ok(None);
5182 }
5183 let Some(binding) = self.bindings.get(target) else {
5184 self.inline_stack.remove(target);
5185 return Ok(None);
5186 };
5187 if binding_has_self_direct_call(target, &binding.body, self.functions) {
5188 self.inline_stack.remove(target);
5189 return Ok(None);
5190 }
5191 let (params, body) = collect_callable_params(&binding.body);
5192 if params.len() != args.len() {
5193 self.inline_stack.remove(target);
5194 return Ok(None);
5195 }
5196 let info_params = info.params.clone();
5197
5198 let saved_locals = self.locals.clone();
5199 let saved_local_exprs = self.local_exprs.clone();
5200 let saved_resumptions = self.resumptions.clone();
5201 for (idx, (param, arg)) in params.into_iter().zip(args).enumerate() {
5202 let path = typed_ir::Path::from_name(param);
5203 let expected = info_params
5204 .get(idx)
5205 .cloned()
5206 .unwrap_or_else(runtime::Type::unknown);
5207 if (is_inline_argument(arg) || matches!(arg.ty, runtime::Type::Thunk { .. }))
5208 && !expr_uses_path(arg, &path)
5209 {
5210 self.local_exprs.insert(path, arg.clone());
5211 } else {
5212 let value = self.lower_direct_call_arg(arg, &expected)?;
5213 self.locals.insert(path, value);
5214 }
5215 }
5216 let value = self.lower_expr(&body);
5217 self.inline_stack.remove(target);
5218 self.locals = saved_locals;
5219 self.local_exprs = saved_local_exprs;
5220 self.resumptions = saved_resumptions;
5221 value.map(Some)
5222 }
5223
5224 fn local_expr_apply_case<'expr>(
5225 &self,
5226 expr: &'expr runtime::Expr,
5227 ) -> Option<(runtime::Expr, &'expr runtime::Expr)> {
5228 let runtime::ExprKind::Apply { callee, arg, .. } = &expr.kind else {
5229 return None;
5230 };
5231 let callee = transparent_effect_expr(callee);
5232 let runtime::ExprKind::Var(path) = &callee.kind else {
5233 return None;
5234 };
5235 let callee = self.local_exprs.get(path).cloned()?;
5236 if callable_expr_is_thunk_wrapped(&callee) {
5237 return Some((callee, arg));
5238 }
5239 let inline_callee = inline_callable_expr(&callee);
5240 let (params, _) = collect_lambda_params(&inline_callee);
5241 (params.len() == 1).then_some((callee, arg))
5242 }
5243
5244 fn lower_local_expr_apply_case(
5245 &mut self,
5246 callee: &runtime::Expr,
5247 arg: &runtime::Expr,
5248 result_ty: &runtime::Type,
5249 ) -> CpsLowerResult<CpsValueId> {
5250 if callable_expr_is_thunk_wrapped(&callee) {
5251 let closure = self.lower_expr(&callee)?;
5252 let forced = self.fresh_value();
5253 self.current.stmts.push(CpsStmt::ForceThunk {
5254 dest: forced,
5255 thunk: closure,
5256 });
5257 let callee_ty = callable_type_after_force(&callee.ty);
5258 let arg = self.lower_expr_as_call_arg(callee_ty, arg)?;
5259 if self.force_effectful_apply_depth.is_active()
5260 || (self.sync_apply_for_immediate_force_depth.is_inactive()
5261 && self.higher_order_helper
5262 && matches!(result_ty, runtime::Type::Thunk { .. }))
5263 {
5264 let post_cont = self.fresh_continuation();
5265 let result = self.fresh_value();
5266 self.terminate(CpsTerminator::EffectfulApply {
5267 closure: forced,
5268 arg,
5269 resume: post_cont,
5270 });
5271 self.finish_current();
5272 self.mark_active_handlers_external_call();
5273 self.current = ContinuationBuilder::new(post_cont, vec![result]);
5274 return Ok(self.force_if_non_thunk_demand(result, result_ty));
5275 }
5276 let dest = self.fresh_value();
5277 self.current.stmts.push(CpsStmt::ApplyClosure {
5278 dest,
5279 closure: forced,
5280 arg,
5281 });
5282 return Ok(self.force_if_non_thunk_demand(dest, result_ty));
5283 }
5284 let callee = inline_callable_expr(&callee);
5285 let (params, body) = collect_lambda_params(&callee);
5286 if params.len() != 1 {
5287 unreachable!("local expr apply classifier only selects unary local callables")
5288 }
5289 let saved_locals = self.locals.clone();
5290 let saved_local_exprs = self.local_exprs.clone();
5291 let saved_resumptions = self.resumptions.clone();
5292 let path = typed_ir::Path::from_name(params[0].clone());
5293 if (is_inline_argument(arg) || matches!(arg.ty, runtime::Type::Thunk { .. }))
5294 && !expr_uses_path(arg, &path)
5295 {
5296 self.local_exprs.insert(path, arg.clone());
5297 } else {
5298 let value = self.lower_expr(arg)?;
5299 self.locals.insert(path, value);
5300 }
5301 let value = self.lower_expr(body);
5302 self.locals = saved_locals;
5303 self.local_exprs = saved_local_exprs;
5304 self.resumptions = saved_resumptions;
5305 value
5306 }
5307
5308 fn handler_reentry_apply(
5309 &mut self,
5310 expr: &runtime::Expr,
5311 handler: CpsHandlerId,
5312 ) -> CpsLowerResult<Option<HandlerReentry>> {
5313 let Some((target, _, args)) = direct_apply_path(expr, self.functions)? else {
5314 return Ok(None);
5315 };
5316 let Some(binding) = self.bindings.get(target) else {
5317 return Ok(None);
5318 };
5319 let Some(wrapper) = handler_wrapper_info(binding) else {
5320 return Ok(None);
5321 };
5322 if wrapper.params.len() != args.len() || wrapper.params.is_empty() {
5323 return Ok(None);
5324 }
5325
5326 let mut resume_candidate = None;
5327 for (index, arg) in args.iter().enumerate() {
5328 let Some((resumption, resume_arg)) = self.resume_thunk_argument(arg) else {
5329 continue;
5330 };
5331 if resume_candidate.is_some() {
5332 return Ok(None);
5333 }
5334 resume_candidate = Some((index, resumption, resume_arg));
5335 }
5336 let Some((resume_index, resumption, resume_arg)) = resume_candidate else {
5337 return Ok(None);
5338 };
5339
5340 let state_params = wrapper
5341 .params
5342 .iter()
5343 .enumerate()
5344 .filter_map(|(index, param)| (index != resume_index).then_some(param))
5345 .collect::<Vec<_>>();
5346 let state_args = args
5347 .iter()
5348 .enumerate()
5349 .filter_map(|(index, arg)| (index != resume_index).then_some(*arg))
5350 .map(|arg| self.lower_expr(arg))
5351 .collect::<CpsLowerResult<Vec<_>>>()?;
5352 let arg = self.lower_expr(resume_arg)?;
5353 let envs = self.handler_reentry_envs(
5354 handler,
5355 &wrapper.arms,
5356 &wrapper.consumes,
5357 &state_params,
5358 &state_args,
5359 );
5360 let dest = self.fresh_value();
5361 Ok(Some(HandlerReentry {
5362 dest,
5363 resumption,
5364 arg,
5365 envs,
5366 }))
5367 }
5368
5369 fn resume_thunk_argument<'expr>(
5370 &self,
5371 expr: &'expr runtime::Expr,
5372 ) -> Option<(CpsValueId, &'expr runtime::Expr)> {
5373 let expr = transparent_effect_expr(expr);
5374 let runtime::ExprKind::Thunk { expr, .. } = &expr.kind else {
5375 return None;
5376 };
5377 let expr = handle_body_execution_inner(expr).unwrap_or(expr);
5378 self.resume_apply(expr)
5379 }
5380
5381 fn handler_reentry_envs(
5382 &self,
5383 handler: CpsHandlerId,
5384 arms: &[runtime::HandleArm],
5385 consumes: &[typed_ir::Path],
5386 state_params: &[&typed_ir::Name],
5387 state_args: &[CpsValueId],
5388 ) -> Vec<CpsHandlerEnv> {
5389 let Some(handler) = self
5390 .handlers
5391 .iter()
5392 .find(|candidate| candidate.id == handler)
5393 else {
5394 return Vec::new();
5395 };
5396 let mut envs = Vec::new();
5397 for arm in arms {
5398 let values = state_params
5399 .iter()
5400 .zip(state_args.iter().copied())
5401 .filter_map(|(param, value)| {
5402 expr_uses_path(&arm.body, &typed_ir::Path::from_name((*param).clone()))
5403 .then_some(value)
5404 })
5405 .collect::<Vec<_>>();
5406 if values.is_empty() {
5407 continue;
5408 }
5409 let targets = state_params
5410 .iter()
5411 .zip(state_args.iter().copied())
5412 .filter_map(|(param, value)| {
5413 let path = typed_ir::Path::from_name((*param).clone());
5414 expr_uses_path(&arm.body, &path).then(|| {
5415 self.function_param_values
5416 .get(&path)
5417 .copied()
5418 .unwrap_or(value)
5419 })
5420 })
5421 .collect::<Vec<_>>();
5422 for effect in scoped_handler_effects(consumes, &arm.effect) {
5423 let Some(entry) = handler
5424 .arms
5425 .iter()
5426 .find(|candidate| effect_matches(&candidate.effect, &effect))
5427 .map(|arm| arm.entry)
5428 else {
5429 continue;
5430 };
5431 envs.push(CpsHandlerEnv {
5432 entry,
5433 values: values.clone(),
5434 targets: targets.clone(),
5435 });
5436 }
5437 }
5438 envs
5439 }
5440
5441 fn resume_apply<'expr>(
5442 &self,
5443 expr: &'expr runtime::Expr,
5444 ) -> Option<(CpsValueId, &'expr runtime::Expr)> {
5445 let runtime::ExprKind::Apply { callee, arg, .. } = &expr.kind else {
5446 return None;
5447 };
5448 let runtime::ExprKind::Var(path) = &callee.kind else {
5449 return None;
5450 };
5451 if !self.resumptions.contains(path) {
5452 return None;
5453 }
5454 let resumption = *self.locals.get(path)?;
5455 Some((resumption, arg.as_ref()))
5456 }
5457
5458 fn apply_chain_contains_resume_argument(&self, expr: &runtime::Expr) -> bool {
5459 let mut current = transparent_effect_expr(expr);
5460 while let runtime::ExprKind::Apply { callee, arg, .. } = ¤t.kind {
5461 if self.expr_contains_resume_apply(arg) {
5462 return true;
5463 }
5464 current = transparent_effect_expr(callee);
5465 }
5466 false
5467 }
5468
5469 fn expr_contains_resume_apply(&self, expr: &runtime::Expr) -> bool {
5470 if self.resume_apply(expr).is_some() {
5471 return true;
5472 }
5473 match &expr.kind {
5474 runtime::ExprKind::Lambda { body, .. }
5475 | runtime::ExprKind::Thunk { expr: body, .. }
5476 | runtime::ExprKind::LocalPushId { body, .. }
5477 | runtime::ExprKind::BindHere { expr: body }
5478 | runtime::ExprKind::AddId { thunk: body, .. }
5479 | runtime::ExprKind::Coerce { expr: body, .. }
5480 | runtime::ExprKind::Pack { expr: body, .. } => self.expr_contains_resume_apply(body),
5481 runtime::ExprKind::Apply { callee, arg, .. } => {
5482 self.expr_contains_resume_apply(callee) || self.expr_contains_resume_apply(arg)
5483 }
5484 runtime::ExprKind::If {
5485 cond,
5486 then_branch,
5487 else_branch,
5488 ..
5489 } => {
5490 self.expr_contains_resume_apply(cond)
5491 || self.expr_contains_resume_apply(then_branch)
5492 || self.expr_contains_resume_apply(else_branch)
5493 }
5494 runtime::ExprKind::Tuple(items) => items
5495 .iter()
5496 .any(|item| self.expr_contains_resume_apply(item)),
5497 runtime::ExprKind::Record { fields, spread } => {
5498 fields
5499 .iter()
5500 .any(|field| self.expr_contains_resume_apply(&field.value))
5501 || spread.as_ref().is_some_and(|spread| match spread {
5502 runtime::RecordSpreadExpr::Head(expr)
5503 | runtime::RecordSpreadExpr::Tail(expr) => {
5504 self.expr_contains_resume_apply(expr)
5505 }
5506 })
5507 }
5508 runtime::ExprKind::Variant {
5509 value: Some(value), ..
5510 }
5511 | runtime::ExprKind::Select { base: value, .. } => {
5512 self.expr_contains_resume_apply(value)
5513 }
5514 runtime::ExprKind::Match {
5515 scrutinee, arms, ..
5516 } => {
5517 self.expr_contains_resume_apply(scrutinee)
5518 || arms.iter().any(|arm| {
5519 arm.guard
5520 .as_ref()
5521 .is_some_and(|guard| self.expr_contains_resume_apply(guard))
5522 || self.expr_contains_resume_apply(&arm.body)
5523 })
5524 }
5525 runtime::ExprKind::Block { stmts, tail } => {
5526 stmts.iter().any(|stmt| match stmt {
5527 runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
5528 self.expr_contains_resume_apply(value)
5529 }
5530 runtime::Stmt::Module { body, .. } => self.expr_contains_resume_apply(body),
5531 }) || tail
5532 .as_ref()
5533 .is_some_and(|tail| self.expr_contains_resume_apply(tail))
5534 }
5535 runtime::ExprKind::Handle { body, arms, .. } => {
5536 self.expr_contains_resume_apply(body)
5537 || arms.iter().any(|arm| {
5538 arm.guard
5539 .as_ref()
5540 .is_some_and(|guard| self.expr_contains_resume_apply(guard))
5541 || self.expr_contains_resume_apply(&arm.body)
5542 })
5543 }
5544 runtime::ExprKind::Var(_)
5545 | runtime::ExprKind::EffectOp(_)
5546 | runtime::ExprKind::PrimitiveOp(_)
5547 | runtime::ExprKind::Lit(_)
5548 | runtime::ExprKind::Variant { value: None, .. }
5549 | runtime::ExprKind::PeekId
5550 | runtime::ExprKind::FindId { .. } => false,
5551 }
5552 }
5553
5554 fn terminate(&mut self, terminator: CpsTerminator) {
5555 self.current.terminator = Some(terminator);
5556 }
5557
5558 fn finish_current(&mut self) {
5559 let terminator = self
5560 .current
5561 .terminator
5562 .take()
5563 .expect("CPS lowerer finished an unterminated continuation");
5564 let id = self.current.id;
5565 let params = std::mem::take(&mut self.current.params);
5566 let captures = std::mem::take(&mut self.current.captures);
5567 let stmts = std::mem::take(&mut self.current.stmts);
5568 self.finish_continuation(id, params, captures, stmts, terminator);
5569 }
5570
5571 fn finish_continuation(
5572 &mut self,
5573 id: CpsContinuationId,
5574 params: Vec<CpsValueId>,
5575 captures: Vec<CpsValueId>,
5576 mut stmts: Vec<CpsStmt>,
5577 terminator: CpsTerminator,
5578 ) {
5579 if let Some(force_index) = first_known_thunk_force_with_rest(&stmts) {
5580 let rest = stmts.split_off(force_index + 1);
5581 let force = stmts
5582 .pop()
5583 .expect("force_index points at a ForceThunk statement");
5584 let CpsStmt::ForceThunk { dest, thunk } = force else {
5585 unreachable!("force_index points at a ForceThunk statement")
5586 };
5587 let resume = self.fresh_continuation();
5588 let raw_forced = self.fresh_value();
5589 self.continuations.push(CpsContinuation {
5590 id,
5591 params,
5592 captures,
5593 shot_kind: CpsShotKind::MultiShot,
5594 stmts,
5595 terminator: CpsTerminator::EffectfulForce { thunk, resume },
5596 });
5597 let mut resumed_stmts = Vec::with_capacity(rest.len() + 1);
5601 resumed_stmts.push(CpsStmt::ForceThunk {
5602 dest,
5603 thunk: raw_forced,
5604 });
5605 resumed_stmts.extend(rest);
5606 self.finish_continuation(
5607 resume,
5608 vec![raw_forced],
5609 Vec::new(),
5610 resumed_stmts,
5611 terminator,
5612 );
5613 return;
5614 }
5615 self.continuations.push(CpsContinuation {
5616 id,
5617 params,
5618 captures,
5619 shot_kind: CpsShotKind::MultiShot,
5620 stmts,
5621 terminator,
5622 });
5623 }
5624}
5625
5626fn restore_effect_guard(
5627 guards: &mut HashMap<runtime::EffectIdVar, CpsValueId>,
5628 id: runtime::EffectIdVar,
5629 previous: Option<CpsValueId>,
5630) {
5631 match previous {
5632 Some(previous) => {
5633 guards.insert(id, previous);
5634 }
5635 None => {
5636 guards.remove(&id);
5637 }
5638 }
5639}
5640
5641fn effect_matches(expected: &typed_ir::Path, actual: &typed_ir::Path) -> bool {
5642 actual == expected
5643 || (!expected.segments.is_empty()
5644 && actual.segments.len() == expected.segments.len() + 1
5645 && actual.segments.starts_with(&expected.segments))
5646 || (expected.segments.len() == 1 && actual.segments.last() == expected.segments.first())
5647}
5648
5649fn scoped_handler_effects(
5650 consumes: &[typed_ir::Path],
5651 effect: &typed_ir::Path,
5652) -> Vec<typed_ir::Path> {
5653 if effect.segments.is_empty()
5654 || consumes.is_empty()
5655 || !consumes.iter().any(is_local_ref_effect_scope)
5656 {
5657 return vec![effect.clone()];
5658 }
5659
5660 let mut scoped = Vec::new();
5661 for consume in consumes {
5662 let effect_already_scoped = effect_matches(consume, effect)
5663 || (!consume.segments.is_empty() && effect.segments.starts_with(&consume.segments));
5664 let effect_names_consumed_effect =
5665 effect.segments.len() == 1 && consume.segments.last() == effect.segments.first();
5666 let path = if effect_already_scoped {
5667 effect.clone()
5668 } else if effect_names_consumed_effect {
5669 consume.clone()
5670 } else {
5671 typed_ir::Path {
5672 segments: consume
5673 .segments
5674 .iter()
5675 .chain(effect.segments.iter())
5676 .cloned()
5677 .collect(),
5678 }
5679 };
5680 if !scoped.iter().any(|existing| existing == &path) {
5681 scoped.push(path);
5682 }
5683 }
5684 scoped
5685}
5686
5687fn is_local_ref_effect_scope(path: &typed_ir::Path) -> bool {
5688 path.segments
5689 .first()
5690 .is_some_and(|segment| segment.0.starts_with('&'))
5691}
5692
5693fn is_inline_argument(expr: &runtime::Expr) -> bool {
5694 match &expr.kind {
5695 runtime::ExprKind::Lambda { .. }
5696 | runtime::ExprKind::Thunk { .. }
5697 | runtime::ExprKind::LocalPushId { .. } => true,
5698 runtime::ExprKind::BindHere { expr }
5699 | runtime::ExprKind::AddId { thunk: expr, .. }
5700 | runtime::ExprKind::Coerce { expr, .. }
5701 | runtime::ExprKind::Pack { expr, .. } => is_inline_argument(expr),
5702 _ => false,
5703 }
5704}
5705
5706fn inline_callable_expr(expr: &runtime::Expr) -> runtime::Expr {
5707 match &expr.kind {
5708 runtime::ExprKind::Thunk { expr, .. }
5709 | runtime::ExprKind::LocalPushId { body: expr, .. }
5710 | runtime::ExprKind::BindHere { expr }
5711 | runtime::ExprKind::AddId { thunk: expr, .. }
5712 | runtime::ExprKind::Coerce { expr, .. }
5713 | runtime::ExprKind::Pack { expr, .. } => inline_callable_expr(expr),
5714 _ => expr.clone(),
5715 }
5716}
5717
5718fn callable_expr_is_thunk_wrapped(expr: &runtime::Expr) -> bool {
5719 match &expr.kind {
5720 runtime::ExprKind::Thunk { .. } => true,
5721 runtime::ExprKind::LocalPushId { body, .. }
5722 | runtime::ExprKind::BindHere { expr: body }
5723 | runtime::ExprKind::AddId { thunk: body, .. }
5724 | runtime::ExprKind::Coerce { expr: body, .. }
5725 | runtime::ExprKind::Pack { expr: body, .. } => callable_expr_is_thunk_wrapped(body),
5726 _ => false,
5727 }
5728}
5729
5730struct HandlerWrapperInfo {
5731 params: Vec<typed_ir::Name>,
5732 arms: Vec<runtime::HandleArm>,
5733 consumes: Vec<typed_ir::Path>,
5734}
5735
5736struct HandlerReentry {
5737 dest: CpsValueId,
5738 resumption: CpsValueId,
5739 arg: CpsValueId,
5740 envs: Vec<CpsHandlerEnv>,
5741}
5742
5743fn handler_wrapper_info(binding: &runtime::Binding) -> Option<HandlerWrapperInfo> {
5744 let (params, body) = collect_lambda_params(&binding.body);
5745 let handled_param = params.last()?;
5746 let (handled_body, arms, handler) = handler_wrapper_handle(body)?;
5747 let handled_body = handle_body_execution_inner(handled_body).unwrap_or(handled_body);
5748 let handled_body = transparent_expr(handled_body);
5749 let runtime::ExprKind::Var(body_var) = &handled_body.kind else {
5750 return None;
5751 };
5752 if body_var != &typed_ir::Path::from_name(handled_param.clone()) {
5753 return None;
5754 }
5755 Some(HandlerWrapperInfo {
5756 params,
5757 arms: arms.to_vec(),
5758 consumes: handler.consumes.clone(),
5759 })
5760}
5761
5762fn handler_wrapper_handle(
5763 expr: &runtime::Expr,
5764) -> Option<(
5765 &runtime::Expr,
5766 &[runtime::HandleArm],
5767 &runtime::HandleEffect,
5768)> {
5769 let mut current = expr;
5770 loop {
5771 match ¤t.kind {
5772 runtime::ExprKind::LocalPushId { body, .. } => current = body,
5773 runtime::ExprKind::BindHere { expr }
5774 | runtime::ExprKind::Coerce { expr, .. }
5775 | runtime::ExprKind::Pack { expr, .. } => current = expr,
5776 runtime::ExprKind::AddId { thunk, .. }
5777 | runtime::ExprKind::Thunk { expr: thunk, .. } => {
5778 current = thunk;
5779 }
5780 runtime::ExprKind::Handle {
5781 body,
5782 arms,
5783 handler,
5784 ..
5785 } => return Some((body, arms, handler)),
5786 _ => return None,
5787 }
5788 }
5789}
5790
5791fn handler_wrapper_args_have_unconsumed_effects(
5792 target: &typed_ir::Path,
5793 args: &[&runtime::Expr],
5794 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
5795) -> bool {
5796 let Some(binding) = bindings.get(target) else {
5797 return false;
5798 };
5799 let Some(wrapper) = handler_wrapper_info(binding) else {
5800 return false;
5801 };
5802 handler_wrapper_args_have_unconsumed_effects_for_wrapper(args, &wrapper)
5803}
5804
5805fn handler_wrapper_args_have_unconsumed_effects_for_wrapper(
5806 args: &[&runtime::Expr],
5807 wrapper: &HandlerWrapperInfo,
5808) -> bool {
5809 args.iter().any(|arg| {
5813 collect_expr_performed_effects(arg).iter().any(|effect| {
5814 !wrapper
5815 .consumes
5816 .iter()
5817 .any(|consume| effect_matches(consume, effect))
5818 })
5819 })
5820}
5821
5822fn binding_may_perform_when_called(
5823 target: &typed_ir::Path,
5824 functions: &HashMap<typed_ir::Path, FunctionInfo>,
5825 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
5826 visiting: &mut HashSet<typed_ir::Path>,
5827 memo: &mut HashMap<typed_ir::Path, bool>,
5828) -> bool {
5829 if let Some(result) = memo.get(target) {
5830 return *result;
5831 }
5832 if !visiting.insert(target.clone()) {
5833 return false;
5834 }
5835 let result = bindings.get(target).is_some_and(|binding| {
5836 if matches!(binding.body.kind, runtime::ExprKind::PrimitiveOp(_)) {
5837 return false;
5838 }
5839 let (_, body) = collect_callable_params(&binding.body);
5840 expr_may_perform_when_evaluated(&body, functions, bindings, visiting, memo)
5841 });
5842 visiting.remove(target);
5843 memo.insert(target.clone(), result);
5844 result
5845}
5846
5847fn expr_may_perform_when_evaluated(
5848 expr: &runtime::Expr,
5849 functions: &HashMap<typed_ir::Path, FunctionInfo>,
5850 bindings: &HashMap<typed_ir::Path, &runtime::Binding>,
5851 visiting: &mut HashSet<typed_ir::Path>,
5852 memo: &mut HashMap<typed_ir::Path, bool>,
5853) -> bool {
5854 if let Some(inner) = handle_body_execution_inner(expr) {
5855 return expr_may_perform_when_evaluated(inner, functions, bindings, visiting, memo);
5856 }
5857 if effect_apply_body_request(expr).is_some() {
5858 return true;
5859 }
5860 if let Ok(Some((target, _, args))) = direct_apply_path(expr, functions) {
5861 return args
5862 .iter()
5863 .any(|arg| expr_may_perform_when_evaluated(arg, functions, bindings, visiting, memo))
5864 || binding_may_perform_when_called(target, functions, bindings, visiting, memo);
5865 }
5866 if let Some((_, args)) = primitive_apply(expr) {
5867 return args
5868 .iter()
5869 .any(|arg| expr_may_perform_when_evaluated(arg, functions, bindings, visiting, memo));
5870 }
5871
5872 match &expr.kind {
5873 runtime::ExprKind::Apply { callee, arg, .. } => {
5874 expr_may_perform_when_evaluated(callee, functions, bindings, visiting, memo)
5875 || expr_may_perform_when_evaluated(arg, functions, bindings, visiting, memo)
5876 || callee_type_may_perform(&callee.ty)
5877 }
5878 runtime::ExprKind::If {
5879 cond,
5880 then_branch,
5881 else_branch,
5882 ..
5883 } => {
5884 expr_may_perform_when_evaluated(cond, functions, bindings, visiting, memo)
5885 || expr_may_perform_when_evaluated(then_branch, functions, bindings, visiting, memo)
5886 || expr_may_perform_when_evaluated(else_branch, functions, bindings, visiting, memo)
5887 }
5888 runtime::ExprKind::Tuple(items) => items
5889 .iter()
5890 .any(|item| expr_may_perform_when_evaluated(item, functions, bindings, visiting, memo)),
5891 runtime::ExprKind::Record { fields, spread } => {
5892 fields.iter().any(|field| {
5893 expr_may_perform_when_evaluated(&field.value, functions, bindings, visiting, memo)
5894 }) || spread.as_ref().is_some_and(|spread| match spread {
5895 runtime::RecordSpreadExpr::Head(expr) | runtime::RecordSpreadExpr::Tail(expr) => {
5896 expr_may_perform_when_evaluated(expr, functions, bindings, visiting, memo)
5897 }
5898 })
5899 }
5900 runtime::ExprKind::Variant {
5901 value: Some(value), ..
5902 }
5903 | runtime::ExprKind::Select { base: value, .. } => {
5904 expr_may_perform_when_evaluated(value, functions, bindings, visiting, memo)
5905 }
5906 runtime::ExprKind::Match {
5907 scrutinee, arms, ..
5908 } => {
5909 expr_may_perform_when_evaluated(scrutinee, functions, bindings, visiting, memo)
5910 || arms.iter().any(|arm| {
5911 arm.guard.as_ref().is_some_and(|guard| {
5912 expr_may_perform_when_evaluated(guard, functions, bindings, visiting, memo)
5913 }) || expr_may_perform_when_evaluated(
5914 &arm.body, functions, bindings, visiting, memo,
5915 )
5916 })
5917 }
5918 runtime::ExprKind::Block { stmts, tail } => {
5919 stmts.iter().any(|stmt| match stmt {
5920 runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
5921 expr_may_perform_when_evaluated(value, functions, bindings, visiting, memo)
5922 }
5923 runtime::Stmt::Module { body, .. } => {
5924 expr_may_perform_when_evaluated(body, functions, bindings, visiting, memo)
5925 }
5926 }) || tail.as_ref().is_some_and(|tail| {
5927 expr_may_perform_when_evaluated(tail, functions, bindings, visiting, memo)
5928 })
5929 }
5930 runtime::ExprKind::Handle { body, arms, .. } => {
5931 expr_may_perform_when_evaluated(body, functions, bindings, visiting, memo)
5932 || arms.iter().any(|arm| {
5933 arm.guard.as_ref().is_some_and(|guard| {
5934 expr_may_perform_when_evaluated(guard, functions, bindings, visiting, memo)
5935 }) || expr_may_perform_when_evaluated(
5936 &arm.body, functions, bindings, visiting, memo,
5937 )
5938 })
5939 }
5940 runtime::ExprKind::Lambda { .. } | runtime::ExprKind::Thunk { .. } => false,
5941 runtime::ExprKind::LocalPushId { body, .. }
5942 | runtime::ExprKind::BindHere { expr: body }
5943 | runtime::ExprKind::AddId { thunk: body, .. }
5944 | runtime::ExprKind::Coerce { expr: body, .. }
5945 | runtime::ExprKind::Pack { expr: body, .. } => {
5946 expr_may_perform_when_evaluated(body, functions, bindings, visiting, memo)
5947 }
5948 runtime::ExprKind::Var(_)
5949 | runtime::ExprKind::EffectOp(_)
5950 | runtime::ExprKind::PrimitiveOp(_)
5951 | runtime::ExprKind::Lit(_)
5952 | runtime::ExprKind::Variant { value: None, .. }
5953 | runtime::ExprKind::PeekId
5954 | runtime::ExprKind::FindId { .. } => false,
5955 }
5956}
5957
5958fn expr_contains_indirect_apply(
5959 expr: &runtime::Expr,
5960 functions: &HashMap<typed_ir::Path, FunctionInfo>,
5961) -> bool {
5962 let this_is_indirect_apply = matches!(expr.kind, runtime::ExprKind::Apply { .. })
5963 && primitive_apply(expr).is_none()
5964 && effect_apply_body_request(expr).is_none()
5965 && direct_apply_path(expr, functions).ok().flatten().is_none();
5966 if this_is_indirect_apply {
5967 return true;
5968 }
5969
5970 match &expr.kind {
5971 runtime::ExprKind::Lambda { body, .. }
5972 | runtime::ExprKind::Thunk { expr: body, .. }
5973 | runtime::ExprKind::LocalPushId { body, .. }
5974 | runtime::ExprKind::BindHere { expr: body }
5975 | runtime::ExprKind::AddId { thunk: body, .. }
5976 | runtime::ExprKind::Coerce { expr: body, .. }
5977 | runtime::ExprKind::Pack { expr: body, .. } => {
5978 expr_contains_indirect_apply(body, functions)
5979 }
5980 runtime::ExprKind::Apply { callee, arg, .. } => {
5981 expr_contains_indirect_apply(callee, functions)
5982 || expr_contains_indirect_apply(arg, functions)
5983 }
5984 runtime::ExprKind::If {
5985 cond,
5986 then_branch,
5987 else_branch,
5988 ..
5989 } => {
5990 expr_contains_indirect_apply(cond, functions)
5991 || expr_contains_indirect_apply(then_branch, functions)
5992 || expr_contains_indirect_apply(else_branch, functions)
5993 }
5994 runtime::ExprKind::Tuple(items) => items
5995 .iter()
5996 .any(|item| expr_contains_indirect_apply(item, functions)),
5997 runtime::ExprKind::Record { fields, spread } => {
5998 fields
5999 .iter()
6000 .any(|field| expr_contains_indirect_apply(&field.value, functions))
6001 || spread.as_ref().is_some_and(|spread| match spread {
6002 runtime::RecordSpreadExpr::Head(expr)
6003 | runtime::RecordSpreadExpr::Tail(expr) => {
6004 expr_contains_indirect_apply(expr, functions)
6005 }
6006 })
6007 }
6008 runtime::ExprKind::Variant {
6009 value: Some(value), ..
6010 }
6011 | runtime::ExprKind::Select { base: value, .. } => {
6012 expr_contains_indirect_apply(value, functions)
6013 }
6014 runtime::ExprKind::Match {
6015 scrutinee, arms, ..
6016 } => {
6017 expr_contains_indirect_apply(scrutinee, functions)
6018 || arms.iter().any(|arm| {
6019 arm.guard
6020 .as_ref()
6021 .is_some_and(|guard| expr_contains_indirect_apply(guard, functions))
6022 || expr_contains_indirect_apply(&arm.body, functions)
6023 })
6024 }
6025 runtime::ExprKind::Block { stmts, tail } => {
6026 stmts.iter().any(|stmt| match stmt {
6027 runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
6028 expr_contains_indirect_apply(value, functions)
6029 }
6030 runtime::Stmt::Module { body, .. } => expr_contains_indirect_apply(body, functions),
6031 }) || tail
6032 .as_ref()
6033 .is_some_and(|tail| expr_contains_indirect_apply(tail, functions))
6034 }
6035 runtime::ExprKind::Handle { body, arms, .. } => {
6036 expr_contains_indirect_apply(body, functions)
6037 || arms.iter().any(|arm| {
6038 arm.guard
6039 .as_ref()
6040 .is_some_and(|guard| expr_contains_indirect_apply(guard, functions))
6041 || expr_contains_indirect_apply(&arm.body, functions)
6042 })
6043 }
6044 runtime::ExprKind::Var(_)
6045 | runtime::ExprKind::EffectOp(_)
6046 | runtime::ExprKind::PrimitiveOp(_)
6047 | runtime::ExprKind::Lit(_)
6048 | runtime::ExprKind::Variant { value: None, .. }
6049 | runtime::ExprKind::PeekId
6050 | runtime::ExprKind::FindId { .. } => false,
6051 }
6052}
6053
6054fn expr_uses_path(expr: &runtime::Expr, path: &typed_ir::Path) -> bool {
6055 match &expr.kind {
6056 runtime::ExprKind::Var(candidate) => candidate == path,
6057 runtime::ExprKind::Lambda { body, .. }
6058 | runtime::ExprKind::Thunk { expr: body, .. }
6059 | runtime::ExprKind::LocalPushId { body, .. }
6060 | runtime::ExprKind::BindHere { expr: body }
6061 | runtime::ExprKind::AddId { thunk: body, .. }
6062 | runtime::ExprKind::Coerce { expr: body, .. }
6063 | runtime::ExprKind::Pack { expr: body, .. } => expr_uses_path(body, path),
6064 runtime::ExprKind::Apply { callee, arg, .. } => {
6065 expr_uses_path(callee, path) || expr_uses_path(arg, path)
6066 }
6067 runtime::ExprKind::If {
6068 cond,
6069 then_branch,
6070 else_branch,
6071 ..
6072 } => {
6073 expr_uses_path(cond, path)
6074 || expr_uses_path(then_branch, path)
6075 || expr_uses_path(else_branch, path)
6076 }
6077 runtime::ExprKind::Tuple(items) => items.iter().any(|item| expr_uses_path(item, path)),
6078 runtime::ExprKind::Record { fields, spread } => {
6079 fields
6080 .iter()
6081 .any(|field| expr_uses_path(&field.value, path))
6082 || spread.as_ref().is_some_and(|spread| match spread {
6083 runtime::RecordSpreadExpr::Head(expr)
6084 | runtime::RecordSpreadExpr::Tail(expr) => expr_uses_path(expr, path),
6085 })
6086 }
6087 runtime::ExprKind::Variant {
6088 value: Some(value), ..
6089 }
6090 | runtime::ExprKind::Select { base: value, .. } => expr_uses_path(value, path),
6091 runtime::ExprKind::Match {
6092 scrutinee, arms, ..
6093 } => {
6094 expr_uses_path(scrutinee, path)
6095 || arms.iter().any(|arm| {
6096 arm.guard
6097 .as_ref()
6098 .is_some_and(|guard| expr_uses_path(guard, path))
6099 || expr_uses_path(&arm.body, path)
6100 })
6101 }
6102 runtime::ExprKind::Block { stmts, tail } => {
6103 stmts.iter().any(|stmt| match stmt {
6104 runtime::Stmt::Let { value, .. } | runtime::Stmt::Expr(value) => {
6105 expr_uses_path(value, path)
6106 }
6107 runtime::Stmt::Module { body, .. } => expr_uses_path(body, path),
6108 }) || tail.as_ref().is_some_and(|tail| expr_uses_path(tail, path))
6109 }
6110 runtime::ExprKind::Handle { body, arms, .. } => {
6111 expr_uses_path(body, path)
6112 || arms.iter().any(|arm| {
6113 arm.guard
6114 .as_ref()
6115 .is_some_and(|guard| expr_uses_path(guard, path))
6116 || expr_uses_path(&arm.body, path)
6117 })
6118 }
6119 runtime::ExprKind::EffectOp(_)
6120 | runtime::ExprKind::PrimitiveOp(_)
6121 | runtime::ExprKind::Lit(_)
6122 | runtime::ExprKind::Variant { value: None, .. }
6123 | runtime::ExprKind::PeekId
6124 | runtime::ExprKind::FindId { .. } => false,
6125 }
6126}
6127
6128fn matches_any_effect(expected: &[typed_ir::Path], actual: &typed_ir::Path) -> bool {
6129 expected
6130 .iter()
6131 .any(|expected| effect_matches(expected, actual))
6132}
6133
6134fn record_spread_pattern(
6135 spread: Option<&runtime::RecordSpreadPattern>,
6136) -> Option<&runtime::Pattern> {
6137 match spread {
6138 Some(runtime::RecordSpreadPattern::Head(pattern))
6139 | Some(runtime::RecordSpreadPattern::Tail(pattern)) => Some(pattern),
6140 None => None,
6141 }
6142}
6143
6144fn handled_effects_compatible(
6145 expected: &[typed_ir::Path],
6146 left: &typed_ir::Path,
6147 right: &typed_ir::Path,
6148) -> bool {
6149 left == right || matches_any_effect(expected, left) || matches_any_effect(expected, right)
6150}
6151
6152fn join_handled_effect(
6153 joined: &mut Option<typed_ir::Path>,
6154 expected: &[typed_ir::Path],
6155 effect: typed_ir::Path,
6156) -> CpsLowerResult<()> {
6157 if let Some(previous) = joined {
6158 if !handled_effects_compatible(expected, previous, &effect) {
6159 return Err(CpsLowerError::UnsupportedExpr {
6160 kind: "handler effect mismatch",
6161 });
6162 }
6163 } else {
6164 *joined = Some(effect);
6165 }
6166 Ok(())
6167}
6168
6169fn default_expected_effect(expected: &[typed_ir::Path]) -> typed_ir::Path {
6170 expected.first().cloned().unwrap_or_default()
6171}
6172
6173fn dynamic_handler_id() -> CpsHandlerId {
6174 CpsHandlerId(usize::MAX)
6175}
6176
6177fn body_is_thunk_value(expr: &runtime::Expr) -> bool {
6178 matches!(expr.ty, runtime::Type::Thunk { .. })
6179 && !matches!(expr.kind, runtime::ExprKind::Thunk { .. })
6180}
6181
6182struct ContinuationBuilder {
6183 id: CpsContinuationId,
6184 params: Vec<CpsValueId>,
6185 captures: Vec<CpsValueId>,
6186 stmts: Vec<CpsStmt>,
6187 terminator: Option<CpsTerminator>,
6188}
6189
6190impl ContinuationBuilder {
6191 fn new(id: CpsContinuationId, params: Vec<CpsValueId>) -> Self {
6192 Self {
6193 id,
6194 params,
6195 captures: Vec::new(),
6196 stmts: Vec::new(),
6197 terminator: None,
6198 }
6199 }
6200}
6201
6202fn first_known_thunk_force_with_rest(stmts: &[CpsStmt]) -> Option<usize> {
6203 let mut known_thunks = HashSet::new();
6204 for (index, stmt) in stmts.iter().enumerate() {
6205 match stmt {
6206 CpsStmt::MakeThunk { dest, .. }
6207 | CpsStmt::MakeRecursiveClosure { dest, .. }
6208 | CpsStmt::AddThunkBoundary { dest, .. } => {
6209 if matches!(
6210 stmt,
6211 CpsStmt::MakeThunk { .. } | CpsStmt::AddThunkBoundary { .. }
6212 ) {
6213 known_thunks.insert(*dest);
6214 }
6215 }
6216 CpsStmt::ForceThunk { thunk, .. } if known_thunks.contains(thunk) => {
6217 return Some(index);
6218 }
6219 _ => {}
6220 }
6221 }
6222 None
6223}
6224
6225fn collect_lambda_params(expr: &runtime::Expr) -> (Vec<typed_ir::Name>, &runtime::Expr) {
6226 let mut params = Vec::new();
6227 let mut current = expr;
6228 while let runtime::ExprKind::Lambda { param, body, .. } = ¤t.kind {
6229 params.push(param.clone());
6230 current = body;
6231 }
6232 (params, current)
6233}
6234
6235fn collect_callable_params(expr: &runtime::Expr) -> (Vec<typed_ir::Name>, runtime::Expr) {
6236 let (mut params, body) = collect_lambda_params(expr);
6237 let mut body = body.clone();
6238 while let runtime::ExprKind::Block {
6239 stmts,
6240 tail: Some(tail),
6241 } = &body.kind
6242 {
6243 let (tail_params, tail_body) = collect_lambda_params(tail);
6244 if tail_params.is_empty() {
6245 break;
6246 }
6247 params.extend(tail_params);
6248 body = runtime::Expr::typed(
6249 runtime::ExprKind::Block {
6250 stmts: stmts.clone(),
6251 tail: Some(Box::new(tail_body.clone())),
6252 },
6253 body.ty.clone(),
6254 );
6255 }
6256 (params, body)
6257}
6258
6259fn recursive_lambda_let<'a>(
6260 pattern: &'a runtime::Pattern,
6261 value: &'a runtime::Expr,
6262) -> Option<(&'a typed_ir::Name, &'a typed_ir::Name, &'a runtime::Expr)> {
6263 let runtime::Pattern::Bind { name, .. } = pattern else {
6264 return None;
6265 };
6266 let runtime::ExprKind::Lambda { param, body, .. } = &value.kind else {
6267 return None;
6268 };
6269 let self_path = typed_ir::Path::from_name(name.clone());
6270 expr_uses_path(body, &self_path).then_some((name, param, body.as_ref()))
6271}
6272
6273fn lower_literal(lit: &typed_ir::Lit) -> CpsLiteral {
6274 match lit {
6275 typed_ir::Lit::Int(value) => CpsLiteral::Int(value.clone()),
6276 typed_ir::Lit::Float(value) => CpsLiteral::Float(value.clone()),
6277 typed_ir::Lit::String(value) => CpsLiteral::String(value.clone()),
6278 typed_ir::Lit::Bool(value) => CpsLiteral::Bool(*value),
6279 typed_ir::Lit::Unit => CpsLiteral::Unit,
6280 }
6281}
6282
6283fn primitive_apply(expr: &runtime::Expr) -> Option<(typed_ir::PrimitiveOp, Vec<&runtime::Expr>)> {
6284 let mut args = Vec::new();
6285 let mut current = expr;
6286 loop {
6287 current = transparent_effect_expr(current);
6288 match ¤t.kind {
6289 runtime::ExprKind::Apply { callee, arg, .. } => {
6290 args.push(arg.as_ref());
6291 current = callee;
6292 }
6293 _ => break,
6294 }
6295 }
6296 let runtime::ExprKind::PrimitiveOp(op) = ¤t.kind else {
6297 return None;
6298 };
6299 args.reverse();
6300 Some((*op, args))
6301}
6302
6303#[derive(Debug, Clone)]
6304struct CpsEffectApply<'a> {
6305 effect: typed_ir::Path,
6306 payload: &'a runtime::Expr,
6307 blocked: Option<runtime::EffectIdRef>,
6308}
6309
6310fn effect_apply(expr: &runtime::Expr) -> Option<CpsEffectApply<'_>> {
6311 let runtime::ExprKind::Apply { callee, arg, .. } = &expr.kind else {
6312 return None;
6313 };
6314 let callee = transparent_effect_expr(callee);
6315 let effect = match &callee.kind {
6316 runtime::ExprKind::EffectOp(effect) => effect,
6317 runtime::ExprKind::Var(path) if debug_role_method_path(path) => path,
6318 _ => return None,
6319 };
6320 Some(CpsEffectApply {
6321 effect: effect.clone(),
6322 payload: arg.as_ref(),
6323 blocked: None,
6324 })
6325}
6326
6327fn effect_apply_nested(expr: &runtime::Expr) -> Option<CpsEffectApply<'_>> {
6328 if let Some(inner) = handle_body_execution_inner(expr) {
6329 return effect_apply_nested(inner);
6330 }
6331 let mut current = expr;
6332 let mut blocked = None;
6333 loop {
6334 match ¤t.kind {
6335 runtime::ExprKind::BindHere { expr }
6336 | runtime::ExprKind::Coerce { expr, .. }
6337 | runtime::ExprKind::Pack { expr, .. } => current = expr,
6338 runtime::ExprKind::AddId {
6339 id, allowed, thunk, ..
6340 } => {
6341 let request = effect_apply_nested(thunk)?;
6342 if blocked.is_none() && !effect_allowed_by_type(allowed, &request.effect) {
6343 blocked = Some(*id);
6344 }
6345 return Some(CpsEffectApply {
6346 blocked: blocked.or(request.blocked),
6347 ..request
6348 });
6349 }
6350 _ => {
6351 let mut request = effect_apply(current)?;
6352 request.blocked = blocked.or(request.blocked);
6353 return Some(request);
6354 }
6355 }
6356 }
6357}
6358
6359fn effect_apply_body_request(expr: &runtime::Expr) -> Option<CpsEffectApply<'_>> {
6360 match &expr.kind {
6361 runtime::ExprKind::BindHere { .. }
6362 | runtime::ExprKind::AddId { .. }
6363 | runtime::ExprKind::Coerce { .. }
6364 | runtime::ExprKind::Pack { .. } => effect_apply_nested(expr),
6365 _ => effect_apply(expr),
6366 }
6367}
6368
6369fn transparent_effect_expr(expr: &runtime::Expr) -> &runtime::Expr {
6370 let mut current = expr;
6371 loop {
6372 match ¤t.kind {
6373 runtime::ExprKind::BindHere { expr }
6374 | runtime::ExprKind::Coerce { expr, .. }
6375 | runtime::ExprKind::Pack { expr, .. }
6376 | runtime::ExprKind::AddId { thunk: expr, .. } => current = expr,
6377 _ => return current,
6378 }
6379 }
6380}
6381
6382fn effect_allowed_by_type(allowed: &typed_ir::Type, effect: &typed_ir::Path) -> bool {
6383 match allowed {
6384 typed_ir::Type::Any => true,
6385 typed_ir::Type::Never => false,
6386 typed_ir::Type::Named { path, .. } => effect_path_matches_allowed(path, effect),
6387 typed_ir::Type::Row { items, tail } => {
6388 items
6389 .iter()
6390 .any(|item| effect_allowed_by_type(item, effect))
6391 || matches!(tail.as_ref(), typed_ir::Type::Any)
6392 }
6393 _ => false,
6394 }
6395}
6396
6397fn effect_path_matches_allowed(allowed: &typed_ir::Path, effect: &typed_ir::Path) -> bool {
6398 if effect.segments.starts_with(&allowed.segments) {
6399 return true;
6400 }
6401 if allowed.segments.len() > 1
6402 && effect.segments.len() == allowed.segments.len()
6403 && effect.segments[..effect.segments.len() - 1]
6404 == allowed.segments[..allowed.segments.len() - 1]
6405 && effect_segment_matches_allowed(
6406 &allowed.segments[allowed.segments.len() - 1],
6407 &effect.segments[effect.segments.len() - 1],
6408 )
6409 {
6410 return true;
6411 }
6412 effect
6413 .segments
6414 .iter()
6415 .enumerate()
6416 .skip(1)
6417 .any(|(index, _)| effect.segments[index..].starts_with(&allowed.segments))
6418}
6419
6420fn effect_segment_matches_allowed(allowed: &typed_ir::Name, effect: &typed_ir::Name) -> bool {
6421 allowed == effect
6422 || effect
6423 .0
6424 .strip_suffix("#effect")
6425 .is_some_and(|base| base == allowed.0)
6426}
6427
6428fn handle_body_execution_inner(expr: &runtime::Expr) -> Option<&runtime::Expr> {
6429 let mut current = expr;
6435 loop {
6436 match ¤t.kind {
6437 runtime::ExprKind::BindHere { expr }
6438 | runtime::ExprKind::Coerce { expr, .. }
6439 | runtime::ExprKind::Pack { expr, .. } => current = expr,
6440 _ => break,
6441 }
6442 }
6443 let runtime::ExprKind::Thunk { expr, .. } = ¤t.kind else {
6444 return None;
6445 };
6446 let mut inner = expr.as_ref();
6447 loop {
6448 match &inner.kind {
6449 runtime::ExprKind::BindHere { expr }
6450 | runtime::ExprKind::Coerce { expr, .. }
6451 | runtime::ExprKind::Pack { expr, .. } => inner = expr,
6452 _ => break,
6453 }
6454 }
6455 Some(inner)
6456}
6457
6458fn handler_body_plain_value_inner(expr: &runtime::Expr) -> Option<&runtime::Expr> {
6459 let inner = handle_body_execution_inner(expr)?;
6460 match inner.kind {
6461 runtime::ExprKind::Var(_) | runtime::ExprKind::Lit(_) => Some(inner),
6462 _ => None,
6463 }
6464}
6465
6466fn direct_apply<'expr, 'functions>(
6467 expr: &'expr runtime::Expr,
6468 functions: &'functions HashMap<typed_ir::Path, FunctionInfo>,
6469) -> CpsLowerResult<Option<(String, &'functions FunctionInfo, Vec<&'expr runtime::Expr>)>> {
6470 let Some((_, target, args)) = direct_apply_path(expr, functions)? else {
6471 return Ok(None);
6472 };
6473 Ok(Some((target.name.clone(), target, args)))
6474}
6475
6476fn direct_call_result_needs_force(expr: &runtime::Expr, target: &FunctionInfo) -> bool {
6477 matches!(target.ret, runtime::Type::Thunk { .. })
6478 && !matches!(expr.ty, runtime::Type::Thunk { .. })
6479}
6480
6481fn bool_match(expr: &runtime::Expr) -> Option<(&runtime::Expr, &runtime::Expr, &runtime::Expr)> {
6482 let runtime::ExprKind::Match {
6483 scrutinee, arms, ..
6484 } = &expr.kind
6485 else {
6486 return None;
6487 };
6488 if arms.len() != 2 || arms.iter().any(|arm| arm.guard.is_some()) {
6489 return None;
6490 }
6491 let mut then_branch = None;
6492 let mut else_branch = None;
6493 for arm in arms {
6494 match &arm.pattern {
6495 runtime::Pattern::Lit {
6496 lit: typed_ir::Lit::Bool(true),
6497 ..
6498 } => then_branch = Some(&arm.body),
6499 runtime::Pattern::Lit {
6500 lit: typed_ir::Lit::Bool(false),
6501 ..
6502 } => else_branch = Some(&arm.body),
6503 _ => return None,
6504 }
6505 }
6506 Some((scrutinee, then_branch?, else_branch?))
6507}
6508
6509fn callee_type_may_perform(ty: &runtime::Type) -> bool {
6516 match ty {
6517 runtime::Type::Fun { ret, .. } => {
6518 matches!(ret.as_ref(), runtime::Type::Thunk { .. }) || callee_type_may_perform(ret)
6519 }
6520 runtime::Type::Thunk { .. } => true,
6521 runtime::Type::Unknown => true,
6522 runtime::Type::Core(_) => false,
6523 }
6524}
6525
6526fn type_is_callable_after_force(ty: &runtime::Type) -> bool {
6527 matches!(callable_type_after_force(ty), runtime::Type::Fun { .. })
6528}
6529
6530fn runtime_type_is_unit_value(ty: &runtime::Type) -> bool {
6531 matches!(
6532 ty,
6533 runtime::Type::Core(typed_ir::Type::Named { path, args })
6534 if args.is_empty()
6535 && path.segments.len() == 1
6536 && path.segments[0].0 == "unit"
6537 )
6538}
6539
6540fn runtime_type_is_bool_value(ty: &runtime::Type) -> bool {
6541 match ty {
6542 runtime::Type::Thunk { value, .. } => runtime_type_is_bool_value(value),
6543 runtime::Type::Core(typed_ir::Type::Named { path, args }) => {
6544 args.is_empty() && path.segments.len() == 1 && path.segments[0].0 == "bool"
6545 }
6546 runtime::Type::Unknown | runtime::Type::Core(_) | runtime::Type::Fun { .. } => false,
6547 }
6548}
6549
6550fn callable_type_after_force(ty: &runtime::Type) -> &runtime::Type {
6551 match ty {
6552 runtime::Type::Thunk { value, .. } => value,
6553 _ => ty,
6554 }
6555}
6556
6557fn direct_apply_path<'expr, 'functions>(
6558 expr: &'expr runtime::Expr,
6559 functions: &'functions HashMap<typed_ir::Path, FunctionInfo>,
6560) -> CpsLowerResult<
6561 Option<(
6562 &'expr typed_ir::Path,
6563 &'functions FunctionInfo,
6564 Vec<&'expr runtime::Expr>,
6565 )>,
6566> {
6567 let mut args = Vec::new();
6568 let mut current = expr;
6569 loop {
6570 current = transparent_effect_expr(current);
6571 match ¤t.kind {
6572 runtime::ExprKind::Apply { callee, arg, .. } => {
6573 args.push(arg.as_ref());
6574 current = callee;
6575 }
6576 _ => break,
6577 }
6578 }
6579 let runtime::ExprKind::Var(path) = ¤t.kind else {
6580 return Ok(None);
6581 };
6582 let Some(target) = functions.get(path) else {
6583 return Ok(None);
6584 };
6585 if args.is_empty() {
6586 return Ok(None);
6587 }
6588 if args.len() < target.arity {
6589 return Ok(None);
6590 }
6591 if args.len() > target.arity {
6592 return Ok(None);
6593 }
6594 args.reverse();
6595 Ok(Some((path, target, args)))
6596}
6597
6598fn partial_direct_apply_path<'expr, 'functions>(
6599 expr: &'expr runtime::Expr,
6600 functions: &'functions HashMap<typed_ir::Path, FunctionInfo>,
6601) -> CpsLowerResult<
6602 Option<(
6603 &'expr typed_ir::Path,
6604 &'functions FunctionInfo,
6605 Vec<&'expr runtime::Expr>,
6606 )>,
6607> {
6608 let mut args = Vec::new();
6609 let mut current = expr;
6610 loop {
6611 current = transparent_effect_expr(current);
6612 match ¤t.kind {
6613 runtime::ExprKind::Apply { callee, arg, .. } => {
6614 args.push(arg.as_ref());
6615 current = callee;
6616 }
6617 _ => break,
6618 }
6619 }
6620 let runtime::ExprKind::Var(path) = ¤t.kind else {
6621 return Ok(None);
6622 };
6623 let Some(target) = functions.get(path) else {
6624 return Ok(None);
6625 };
6626 if args.is_empty() || args.len() >= target.arity {
6627 return Ok(None);
6628 }
6629 args.reverse();
6630 Ok(Some((path, target, args)))
6631}
6632
6633fn primitive_arity(op: typed_ir::PrimitiveOp) -> usize {
6634 use typed_ir::PrimitiveOp;
6635 match op {
6636 PrimitiveOp::BoolNot
6637 | PrimitiveOp::ListEmpty
6638 | PrimitiveOp::ListSingleton
6639 | PrimitiveOp::ListLen
6640 | PrimitiveOp::ListViewRaw
6641 | PrimitiveOp::StringLen
6642 | PrimitiveOp::StringToBytes
6643 | PrimitiveOp::BytesLen
6644 | PrimitiveOp::BytesToUtf8Raw
6645 | PrimitiveOp::BytesToPath
6646 | PrimitiveOp::PathToBytes
6647 | PrimitiveOp::IntToString
6648 | PrimitiveOp::IntToHex
6649 | PrimitiveOp::IntToUpperHex
6650 | PrimitiveOp::FloatToString
6651 | PrimitiveOp::BoolToString => 1,
6652 PrimitiveOp::BoolEq
6653 | PrimitiveOp::ListMerge
6654 | PrimitiveOp::ListIndex
6655 | PrimitiveOp::ListIndexRange
6656 | PrimitiveOp::StringIndex
6657 | PrimitiveOp::StringIndexRange
6658 | PrimitiveOp::IntAdd
6659 | PrimitiveOp::IntSub
6660 | PrimitiveOp::IntMul
6661 | PrimitiveOp::IntDiv
6662 | PrimitiveOp::IntEq
6663 | PrimitiveOp::IntLt
6664 | PrimitiveOp::IntLe
6665 | PrimitiveOp::IntGt
6666 | PrimitiveOp::IntGe
6667 | PrimitiveOp::FloatAdd
6668 | PrimitiveOp::FloatSub
6669 | PrimitiveOp::FloatMul
6670 | PrimitiveOp::FloatDiv
6671 | PrimitiveOp::FloatEq
6672 | PrimitiveOp::FloatLt
6673 | PrimitiveOp::FloatLe
6674 | PrimitiveOp::FloatGt
6675 | PrimitiveOp::FloatGe
6676 | PrimitiveOp::StringEq
6677 | PrimitiveOp::StringConcat
6678 | PrimitiveOp::BytesEq
6679 | PrimitiveOp::BytesConcat
6680 | PrimitiveOp::BytesIndex
6681 | PrimitiveOp::BytesIndexRange => 2,
6682 PrimitiveOp::ListSplice
6683 | PrimitiveOp::ListIndexRangeRaw
6684 | PrimitiveOp::StringSplice
6685 | PrimitiveOp::StringIndexRangeRaw => 3,
6686 PrimitiveOp::ListSpliceRaw | PrimitiveOp::StringSpliceRaw => 4,
6687 }
6688}
6689
6690fn path_name(path: &typed_ir::Path) -> String {
6691 path.segments
6692 .iter()
6693 .map(|segment| segment.0.as_str())
6694 .collect::<Vec<_>>()
6695 .join("::")
6696}
6697
6698fn debug_role_method_path(path: &typed_ir::Path) -> bool {
6699 let [std, prelude, role, method] = path.segments.as_slice() else {
6700 return false;
6701 };
6702 std.0 == "std" && prelude.0 == "prelude" && role.0 == "Debug" && method.0 == "debug"
6703}
6704
6705fn throw_role_method_path(path: &typed_ir::Path) -> bool {
6706 let [std, module, role, method] = path.segments.as_slice() else {
6707 return false;
6708 };
6709 std.0 == "std" && module.0 == "error" && role.0 == "Throw" && method.0 == "throw"
6710}
6711
6712fn fail_prefix_path(path: &typed_ir::Path) -> bool {
6713 let [std, prelude, op] = path.segments.as_slice() else {
6714 return false;
6715 };
6716 std.0 == "std" && prelude.0 == "prelude" && op.0.starts_with("#op:prefix:fail")
6717}
6718
6719#[cfg(test)]
6720mod tests {
6721 use crate::cps_eval::eval_cps_module;
6722 use crate::cps_repr::{eval_cps_repr_module, lower_cps_repr_module};
6723 use crate::cps_validate::validate_cps_module;
6724
6725 use super::*;
6726
6727 fn unknown_lit(lit: typed_ir::Lit) -> runtime::Expr {
6728 runtime::Expr::typed(runtime::ExprKind::Lit(lit), runtime::Type::unknown())
6729 }
6730
6731 fn primitive(op: typed_ir::PrimitiveOp) -> runtime::Expr {
6732 runtime::Expr::typed(runtime::ExprKind::PrimitiveOp(op), runtime::Type::unknown())
6733 }
6734
6735 fn apply(callee: runtime::Expr, arg: runtime::Expr) -> runtime::Expr {
6736 runtime::Expr::typed(
6737 runtime::ExprKind::Apply {
6738 callee: Box::new(callee),
6739 arg: Box::new(arg),
6740 evidence: None,
6741 instantiation: None,
6742 },
6743 runtime::Type::unknown(),
6744 )
6745 }
6746
6747 fn if_expr(
6748 cond: runtime::Expr,
6749 then_branch: runtime::Expr,
6750 else_branch: runtime::Expr,
6751 ) -> runtime::Expr {
6752 runtime::Expr::typed(
6753 runtime::ExprKind::If {
6754 cond: Box::new(cond),
6755 then_branch: Box::new(then_branch),
6756 else_branch: Box::new(else_branch),
6757 evidence: None,
6758 },
6759 runtime::Type::unknown(),
6760 )
6761 }
6762
6763 fn var(name: &str) -> runtime::Expr {
6764 runtime::Expr::typed(
6765 runtime::ExprKind::Var(typed_ir::Path::from_name(typed_ir::Name(name.to_string()))),
6766 runtime::Type::unknown(),
6767 )
6768 }
6769
6770 fn effect_op(name: &str) -> runtime::Expr {
6771 runtime::Expr::typed(
6772 runtime::ExprKind::EffectOp(typed_ir::Path::from_name(typed_ir::Name(
6773 name.to_string(),
6774 ))),
6775 runtime::Type::unknown(),
6776 )
6777 }
6778
6779 fn effect_op_path(path: typed_ir::Path) -> runtime::Expr {
6780 runtime::Expr::typed(runtime::ExprKind::EffectOp(path), runtime::Type::unknown())
6781 }
6782
6783 fn bind_pattern(name: &str) -> runtime::Pattern {
6784 runtime::Pattern::Bind {
6785 name: typed_ir::Name(name.to_string()),
6786 ty: runtime::Type::unknown(),
6787 }
6788 }
6789
6790 fn handle_once(
6791 effect: &str,
6792 payload: &str,
6793 resume: &str,
6794 body: runtime::Expr,
6795 arm_body: runtime::Expr,
6796 ) -> runtime::Expr {
6797 let effect = typed_ir::Path::from_name(typed_ir::Name(effect.to_string()));
6798 runtime::Expr::typed(
6799 runtime::ExprKind::Handle {
6800 body: Box::new(body),
6801 arms: vec![runtime::HandleArm {
6802 effect: effect.clone(),
6803 payload: bind_pattern(payload),
6804 resume: Some(runtime::ResumeBinding {
6805 name: typed_ir::Name(resume.to_string()),
6806 ty: runtime::Type::unknown(),
6807 }),
6808 guard: None,
6809 body: arm_body,
6810 }],
6811 evidence: runtime::JoinEvidence {
6812 result: typed_ir::Type::Unknown,
6813 },
6814 handler: runtime::HandleEffect {
6815 consumes: vec![effect],
6816 residual_before: None,
6817 residual_after: None,
6818 },
6819 },
6820 runtime::Type::unknown(),
6821 )
6822 }
6823
6824 fn handle_once_with_value(
6825 effect: &str,
6826 payload: &str,
6827 resume: &str,
6828 body: runtime::Expr,
6829 arm_body: runtime::Expr,
6830 value_payload: &str,
6831 value_body: runtime::Expr,
6832 ) -> runtime::Expr {
6833 let effect = typed_ir::Path::from_name(typed_ir::Name(effect.to_string()));
6834 runtime::Expr::typed(
6835 runtime::ExprKind::Handle {
6836 body: Box::new(body),
6837 arms: vec![
6838 runtime::HandleArm {
6839 effect: effect.clone(),
6840 payload: bind_pattern(payload),
6841 resume: Some(runtime::ResumeBinding {
6842 name: typed_ir::Name(resume.to_string()),
6843 ty: runtime::Type::unknown(),
6844 }),
6845 guard: None,
6846 body: arm_body,
6847 },
6848 runtime::HandleArm {
6849 effect: typed_ir::Path::default(),
6850 payload: bind_pattern(value_payload),
6851 resume: None,
6852 guard: None,
6853 body: value_body,
6854 },
6855 ],
6856 evidence: runtime::JoinEvidence {
6857 result: typed_ir::Type::Unknown,
6858 },
6859 handler: runtime::HandleEffect {
6860 consumes: vec![effect],
6861 residual_before: None,
6862 residual_after: None,
6863 },
6864 },
6865 runtime::Type::unknown(),
6866 )
6867 }
6868
6869 fn handle_value(
6870 body: runtime::Expr,
6871 value_payload: &str,
6872 value_body: runtime::Expr,
6873 ) -> runtime::Expr {
6874 runtime::Expr::typed(
6875 runtime::ExprKind::Handle {
6876 body: Box::new(body),
6877 arms: vec![runtime::HandleArm {
6878 effect: typed_ir::Path::default(),
6879 payload: bind_pattern(value_payload),
6880 resume: None,
6881 guard: None,
6882 body: value_body,
6883 }],
6884 evidence: runtime::JoinEvidence {
6885 result: typed_ir::Type::Unknown,
6886 },
6887 handler: runtime::HandleEffect {
6888 consumes: Vec::new(),
6889 residual_before: None,
6890 residual_after: None,
6891 },
6892 },
6893 runtime::Type::unknown(),
6894 )
6895 }
6896
6897 fn handle_once_expr(
6898 effect: typed_ir::Path,
6899 payload: &str,
6900 resume: &str,
6901 body: runtime::Expr,
6902 arm_body: runtime::Expr,
6903 ) -> runtime::Expr {
6904 runtime::Expr::typed(
6905 runtime::ExprKind::Handle {
6906 body: Box::new(body),
6907 arms: vec![runtime::HandleArm {
6908 effect: effect.clone(),
6909 payload: bind_pattern(payload),
6910 resume: Some(runtime::ResumeBinding {
6911 name: typed_ir::Name(resume.to_string()),
6912 ty: runtime::Type::unknown(),
6913 }),
6914 guard: None,
6915 body: arm_body,
6916 }],
6917 evidence: runtime::JoinEvidence {
6918 result: typed_ir::Type::Unknown,
6919 },
6920 handler: runtime::HandleEffect {
6921 consumes: vec![effect],
6922 residual_before: None,
6923 residual_after: None,
6924 },
6925 },
6926 runtime::Type::unknown(),
6927 )
6928 }
6929
6930 fn block(stmts: Vec<runtime::Stmt>, tail: runtime::Expr) -> runtime::Expr {
6931 runtime::Expr::typed(
6932 runtime::ExprKind::Block {
6933 stmts,
6934 tail: Some(Box::new(tail)),
6935 },
6936 runtime::Type::unknown(),
6937 )
6938 }
6939
6940 fn thunk(expr: runtime::Expr) -> runtime::Expr {
6941 runtime::Expr::typed(
6942 runtime::ExprKind::Thunk {
6943 effect: typed_ir::Type::Unknown,
6944 value: runtime::Type::unknown(),
6945 expr: Box::new(expr),
6946 },
6947 runtime::Type::unknown(),
6948 )
6949 }
6950
6951 fn bind_here(expr: runtime::Expr) -> runtime::Expr {
6952 runtime::Expr::typed(
6953 runtime::ExprKind::BindHere {
6954 expr: Box::new(expr),
6955 },
6956 runtime::Type::unknown(),
6957 )
6958 }
6959
6960 fn local_push_id(id: usize, body: runtime::Expr) -> runtime::Expr {
6961 runtime::Expr::typed(
6962 runtime::ExprKind::LocalPushId {
6963 id: runtime::EffectIdVar(id),
6964 body: Box::new(body),
6965 },
6966 runtime::Type::unknown(),
6967 )
6968 }
6969
6970 fn peek_id() -> runtime::Expr {
6971 runtime::Expr::typed(runtime::ExprKind::PeekId, runtime::Type::unknown())
6972 }
6973
6974 fn add_id(
6975 id: runtime::EffectIdRef,
6976 allowed: typed_ir::Type,
6977 thunk: runtime::Expr,
6978 ) -> runtime::Expr {
6979 add_id_with_active(id, allowed, false, thunk)
6980 }
6981
6982 fn add_id_with_active(
6983 id: runtime::EffectIdRef,
6984 allowed: typed_ir::Type,
6985 active: bool,
6986 thunk: runtime::Expr,
6987 ) -> runtime::Expr {
6988 runtime::Expr::typed(
6989 runtime::ExprKind::AddId {
6990 id,
6991 allowed,
6992 active,
6993 thunk: Box::new(thunk),
6994 },
6995 runtime::Type::unknown(),
6996 )
6997 }
6998
6999 fn lambda(param: &str, body: runtime::Expr) -> runtime::Expr {
7000 runtime::Expr::typed(
7001 runtime::ExprKind::Lambda {
7002 param: typed_ir::Name(param.to_string()),
7003 param_effect_annotation: None,
7004 param_function_allowed_effects: None,
7005 body: Box::new(body),
7006 },
7007 runtime::Type::unknown(),
7008 )
7009 }
7010
7011 fn binding(name: &str, body: runtime::Expr) -> runtime::Binding {
7012 runtime::Binding {
7013 name: typed_ir::Path::from_name(typed_ir::Name(name.to_string())),
7014 type_params: Vec::new(),
7015 scheme: typed_ir::Scheme {
7016 requirements: Vec::new(),
7017 body: typed_ir::Type::Unknown,
7018 },
7019 body,
7020 }
7021 }
7022
7023 fn effect_path(effect: &str, op: &str) -> typed_ir::Path {
7024 typed_ir::Path {
7025 segments: vec![
7026 typed_ir::Name(effect.to_string()),
7027 typed_ir::Name(op.to_string()),
7028 ],
7029 }
7030 }
7031
7032 fn module_with_root(expr: runtime::Expr) -> runtime::Module {
7033 module_with_bindings_and_root(Vec::new(), expr)
7034 }
7035
7036 fn module_with_bindings_and_root(
7037 bindings: Vec<runtime::Binding>,
7038 expr: runtime::Expr,
7039 ) -> runtime::Module {
7040 runtime::Module {
7041 path: typed_ir::Path::default(),
7042 bindings,
7043 root_exprs: vec![expr],
7044 roots: vec![runtime::Root::Expr(0)],
7045 role_impls: Vec::new(),
7046 }
7047 }
7048
7049 #[test]
7050 fn lowers_pure_int_add_to_multishot_cps() {
7051 let expr = apply(
7052 apply(
7053 primitive(typed_ir::PrimitiveOp::IntAdd),
7054 unknown_lit(typed_ir::Lit::Int("20".to_string())),
7055 ),
7056 unknown_lit(typed_ir::Lit::Int("22".to_string())),
7057 );
7058 let module = module_with_root(expr);
7059 let lowered = lower_cps_module(&module).expect("lowered");
7060
7061 validate_cps_module(&lowered).expect("valid CPS");
7062 assert_eq!(lowered.roots.len(), 1);
7063 assert_eq!(lowered.roots[0].continuations.len(), 1);
7064 assert_eq!(
7065 lowered.roots[0].continuations[0].shot_kind,
7066 CpsShotKind::MultiShot
7067 );
7068 assert_eq!(
7069 lowered.roots[0].continuations[0].stmts,
7070 vec![
7071 CpsStmt::Literal {
7072 dest: CpsValueId(0),
7073 literal: CpsLiteral::Int("20".to_string()),
7074 },
7075 CpsStmt::Literal {
7076 dest: CpsValueId(1),
7077 literal: CpsLiteral::Int("22".to_string()),
7078 },
7079 CpsStmt::Primitive {
7080 dest: CpsValueId(2),
7081 op: typed_ir::PrimitiveOp::IntAdd,
7082 args: vec![CpsValueId(0), CpsValueId(1)],
7083 },
7084 CpsStmt::ForceThunk {
7088 dest: CpsValueId(3),
7089 thunk: CpsValueId(2),
7090 },
7091 ]
7092 );
7093 assert_eq!(
7094 lowered.roots[0].continuations[0].terminator,
7095 CpsTerminator::Return(CpsValueId(3))
7096 );
7097 }
7098
7099 #[test]
7100 fn lowers_local_push_and_peek_id_to_guard_statements() {
7101 let module = module_with_root(local_push_id(0, peek_id()));
7102 let lowered = lower_cps_module(&module).expect("lowered");
7103
7104 validate_cps_module(&lowered).expect("valid CPS");
7105 let stmts = &lowered.roots[0].continuations[0].stmts;
7106 assert!(matches!(stmts[0], CpsStmt::FreshGuard { .. }));
7107 assert!(matches!(stmts[1], CpsStmt::PeekGuard { .. }));
7108 assert_eq!(
7109 eval_cps_module(&lowered).expect("evaluated"),
7110 vec![runtime::VmValue::EffectId(0)]
7111 );
7112 }
7113
7114 #[test]
7115 fn lowers_add_id_var_to_enclosing_local_push_guard() {
7116 let module = module_with_root(local_push_id(
7117 0,
7118 local_push_id(
7119 1,
7120 add_id(
7121 runtime::EffectIdRef::Var(runtime::EffectIdVar(0)),
7122 typed_ir::Type::Never,
7123 thunk(unknown_lit(typed_ir::Lit::Int("7".to_string()))),
7124 ),
7125 ),
7126 ));
7127 let lowered = lower_cps_module(&module).expect("lowered");
7128
7129 validate_cps_module(&lowered).expect("valid CPS");
7130 let fresh_guards: HashMap<_, _> = lowered.roots[0]
7131 .continuations
7132 .iter()
7133 .flat_map(|continuation| continuation.stmts.iter())
7134 .filter_map(|stmt| match stmt {
7135 CpsStmt::FreshGuard { dest, var } => Some((*var, *dest)),
7136 _ => None,
7137 })
7138 .collect();
7139 let outer_guard = *fresh_guards
7140 .get(&runtime::EffectIdVar(0))
7141 .expect("outer fresh guard");
7142 let inner_guard = *fresh_guards
7143 .get(&runtime::EffectIdVar(1))
7144 .expect("inner fresh guard");
7145 assert_ne!(outer_guard, inner_guard);
7146 assert!(
7147 lowered.roots[0]
7148 .continuations
7149 .iter()
7150 .flat_map(|continuation| continuation.stmts.iter())
7151 .any(|stmt| matches!(stmt, CpsStmt::AddThunkBoundary { guard: found, .. } if *found == outer_guard))
7152 );
7153 assert_eq!(
7154 eval_cps_module(&lowered).expect("evaluated"),
7155 vec![runtime::VmValue::Int("7".to_string())]
7156 );
7157 }
7158
7159 #[test]
7160 fn lowers_add_id_blocked_effect_to_perform_blocked_guard() {
7161 let body = add_id(
7162 runtime::EffectIdRef::Peek,
7163 typed_ir::Type::Never,
7164 apply(
7165 effect_op("choose"),
7166 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7167 ),
7168 );
7169 let module = module_with_root(handle_once("choose", "x", "k", body, var("x")));
7170 let lowered = lower_cps_module(&module).expect("lowered");
7171
7172 validate_cps_module(&lowered).expect("valid CPS");
7173 let perform = lowered.roots[0]
7174 .continuations
7175 .iter()
7176 .find_map(|continuation| match &continuation.terminator {
7177 CpsTerminator::Perform { blocked, .. } => *blocked,
7178 _ => None,
7179 })
7180 .expect("blocked perform guard");
7181 assert!(
7182 lowered.roots[0].continuations[0]
7183 .stmts
7184 .iter()
7185 .any(|stmt| matches!(stmt, CpsStmt::PeekGuard { dest } if *dest == perform))
7186 );
7187 }
7188
7189 #[test]
7190 fn active_add_id_blocks_inner_handler_during_thunk_force() {
7191 let perform = apply(
7192 effect_op("choose"),
7193 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7194 );
7195 let guarded_thunk = add_id_with_active(
7196 runtime::EffectIdRef::Peek,
7197 typed_ir::Type::Never,
7198 true,
7199 thunk(perform),
7200 );
7201 let inner = handle_once(
7202 "choose",
7203 "_x",
7204 "_k",
7205 bind_here(guarded_thunk),
7206 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7207 );
7208 let body = local_push_id(0, inner);
7209 let root = handle_once(
7210 "choose",
7211 "_x",
7212 "_k",
7213 body,
7214 unknown_lit(typed_ir::Lit::Int("20".to_string())),
7215 );
7216 let lowered = lower_cps_module(&module_with_root(root)).expect("lowered");
7217
7218 validate_cps_module(&lowered).expect("valid CPS");
7219 assert!(matches!(
7220 eval_cps_module(&lowered),
7221 Err(crate::cps_eval::CpsEvalError::MissingHandler { .. })
7222 ));
7223 assert!(eval_cps_repr_module(&lower_cps_repr_module(&lowered)).is_err());
7224 }
7225
7226 #[test]
7227 fn active_add_id_var_blocks_inner_handler_during_callback_force() {
7228 let perform = apply(
7229 effect_op("choose"),
7230 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7231 );
7232 let guarded_thunk = add_id_with_active(
7233 runtime::EffectIdRef::Var(runtime::EffectIdVar(0)),
7234 typed_ir::Type::Never,
7235 true,
7236 thunk(perform),
7237 );
7238 let inner = handle_once(
7239 "choose",
7240 "_x",
7241 "_k",
7242 bind_here(guarded_thunk),
7243 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7244 );
7245 let body = local_push_id(0, inner);
7246 let root = handle_once(
7247 "choose",
7248 "_x",
7249 "_k",
7250 body,
7251 unknown_lit(typed_ir::Lit::Int("20".to_string())),
7252 );
7253 let lowered = lower_cps_module(&module_with_root(root)).expect("lowered");
7254
7255 validate_cps_module(&lowered).expect("valid CPS");
7256 assert!(matches!(
7257 eval_cps_module(&lowered),
7258 Err(crate::cps_eval::CpsEvalError::MissingHandler { .. })
7259 ));
7260 assert!(eval_cps_repr_module(&lower_cps_repr_module(&lowered)).is_err());
7261 }
7262
7263 #[test]
7264 fn active_add_id_allows_matching_effect_for_inner_handler() {
7265 let perform = apply(
7266 effect_op("choose"),
7267 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7268 );
7269 let guarded_thunk = add_id_with_active(
7270 runtime::EffectIdRef::Peek,
7271 typed_ir::Type::Named {
7272 path: typed_ir::Path::from_name(typed_ir::Name("choose".to_string())),
7273 args: Vec::new(),
7274 },
7275 true,
7276 thunk(perform),
7277 );
7278 let inner = handle_once(
7279 "choose",
7280 "_x",
7281 "_k",
7282 bind_here(guarded_thunk),
7283 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7284 );
7285 let body = local_push_id(0, inner);
7286 let root = handle_once(
7287 "choose",
7288 "_x",
7289 "_k",
7290 body,
7291 unknown_lit(typed_ir::Lit::Int("20".to_string())),
7292 );
7293 let lowered = lower_cps_module(&module_with_root(root)).expect("lowered");
7294
7295 validate_cps_module(&lowered).expect("valid CPS");
7296 assert_eq!(
7297 eval_cps_module(&lowered).expect("evaluated"),
7298 vec![runtime::VmValue::Int("10".to_string())]
7299 );
7300 assert_eq!(
7301 eval_cps_repr_module(&lower_cps_repr_module(&lowered)).expect("repr evaluated"),
7302 vec![runtime::VmValue::Int("10".to_string())]
7303 );
7304 }
7305
7306 #[test]
7307 fn skips_unreachable_non_function_binding() {
7308 let module = runtime::Module {
7309 path: typed_ir::Path::default(),
7310 bindings: vec![runtime::Binding {
7311 name: typed_ir::Path::from_name(typed_ir::Name("unused".to_string())),
7312 type_params: Vec::new(),
7313 scheme: typed_ir::Scheme {
7314 requirements: Vec::new(),
7315 body: typed_ir::Type::Unknown,
7316 },
7317 body: unknown_lit(typed_ir::Lit::Int("0".to_string())),
7318 }],
7319 root_exprs: vec![unknown_lit(typed_ir::Lit::Int("41".to_string()))],
7320 roots: vec![runtime::Root::Expr(0)],
7321 role_impls: Vec::new(),
7322 };
7323 let lowered = lower_cps_module(&module).expect("lowered");
7324
7325 assert!(lowered.functions.is_empty());
7326 assert_eq!(
7327 eval_cps_module(&lowered).expect("evaluated"),
7328 vec![runtime::VmValue::Int("41".to_string())]
7329 );
7330 }
7331
7332 #[test]
7333 fn inlines_thunk_handler_wrapper_call() {
7334 let effect = effect_path("sub", "return");
7335 let handler = binding(
7336 "run",
7337 lambda(
7338 "x",
7339 handle_once_expr(effect.clone(), "a", "_k", var("x"), thunk(var("a"))),
7340 ),
7341 );
7342 let root = apply(
7343 var("run"),
7344 thunk(apply(
7345 effect_op_path(effect),
7346 unknown_lit(typed_ir::Lit::Int("41".to_string())),
7347 )),
7348 );
7349 let module = module_with_bindings_and_root(vec![handler], root);
7350 let lowered = lower_cps_module(&module).expect("lowered");
7351
7352 validate_cps_module(&lowered).expect("valid CPS");
7353 assert!(lowered.functions.is_empty());
7354 assert_eq!(
7355 eval_cps_module(&lowered).expect("evaluated"),
7356 vec![runtime::VmValue::Int("41".to_string())]
7357 );
7358 }
7359
7360 #[test]
7361 fn lowers_if_to_multishot_continuation_graph() {
7362 let module = module_with_root(if_expr(
7363 unknown_lit(typed_ir::Lit::Bool(true)),
7364 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7365 unknown_lit(typed_ir::Lit::Int("2".to_string())),
7366 ));
7367 let lowered = lower_cps_module(&module).expect("lowered");
7368 let root = &lowered.roots[0];
7369
7370 validate_cps_module(&lowered).expect("valid CPS");
7371 assert_eq!(root.continuations.len(), 4);
7372 assert!(
7373 root.continuations
7374 .iter()
7375 .all(|continuation| continuation.shot_kind == CpsShotKind::MultiShot)
7376 );
7377 assert_eq!(
7378 root.continuations[0].terminator,
7379 CpsTerminator::Branch {
7380 cond: CpsValueId(0),
7381 then_cont: CpsContinuationId(1),
7382 else_cont: CpsContinuationId(2),
7383 }
7384 );
7385 assert_eq!(root.continuations[3].params, vec![CpsValueId(1)]);
7386 assert_eq!(root.continuations[3].stmts.len(), 1);
7390 assert!(matches!(
7391 root.continuations[3].stmts[0],
7392 CpsStmt::ForceThunk {
7393 dest: CpsValueId(4),
7394 thunk: CpsValueId(1),
7395 }
7396 ));
7397 assert_eq!(
7398 root.continuations[3].terminator,
7399 CpsTerminator::Return(CpsValueId(4))
7400 );
7401 }
7402
7403 #[test]
7404 fn lowers_direct_call_to_cps() {
7405 let inc = binding(
7406 "inc",
7407 lambda(
7408 "x",
7409 apply(
7410 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
7411 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7412 ),
7413 ),
7414 );
7415 let root = apply(
7416 var("inc"),
7417 unknown_lit(typed_ir::Lit::Int("41".to_string())),
7418 );
7419 let module = module_with_bindings_and_root(vec![inc], root);
7420 let lowered = lower_cps_module(&module).expect("lowered");
7421
7422 validate_cps_module(&lowered).expect("valid CPS");
7423 assert_eq!(lowered.functions.len(), 1);
7424 assert_eq!(lowered.functions[0].name, "inc");
7425 assert_eq!(lowered.functions[0].params, vec![CpsValueId(0)]);
7426 assert_eq!(
7427 lowered.roots[0].continuations[0].stmts,
7428 vec![
7429 CpsStmt::Literal {
7430 dest: CpsValueId(0),
7431 literal: CpsLiteral::Int("41".to_string()),
7432 },
7433 CpsStmt::ForceThunk {
7437 dest: CpsValueId(1),
7438 thunk: CpsValueId(0),
7439 },
7440 CpsStmt::DirectCall {
7441 dest: CpsValueId(2),
7442 target: "inc".to_string(),
7443 args: vec![CpsValueId(1)],
7444 },
7445 CpsStmt::ForceThunk {
7450 dest: CpsValueId(3),
7451 thunk: CpsValueId(2),
7452 },
7453 CpsStmt::ForceThunk {
7457 dest: CpsValueId(4),
7458 thunk: CpsValueId(3),
7459 },
7460 ]
7461 );
7462 }
7463
7464 #[test]
7465 fn lowers_single_effect_handler_with_resumption() {
7466 let body = apply(
7467 effect_op("choose"),
7468 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7469 );
7470 let resume_x = apply(var("k"), var("x"));
7471 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7472 let arm_body = apply(
7473 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7474 resume_two,
7475 );
7476 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7477 let lowered = lower_cps_module(&module).expect("lowered");
7478 let root = &lowered.roots[0];
7479
7480 validate_cps_module(&lowered).expect("valid CPS");
7481 assert_eq!(root.handlers.len(), 1);
7482 assert!(
7483 root.continuations.iter().any(|continuation| matches!(
7484 continuation.terminator,
7485 CpsTerminator::Perform { .. }
7486 ))
7487 );
7488 assert!(
7489 root.continuations
7490 .iter()
7491 .flat_map(|continuation| &continuation.stmts)
7492 .any(|stmt| matches!(stmt, CpsStmt::Resume { .. }))
7493 );
7494 assert_eq!(
7495 eval_cps_module(&lowered).expect("evaluated"),
7496 vec![runtime::VmValue::Int("3".to_string())]
7497 );
7498 }
7499
7500 #[test]
7501 fn lowers_value_handler_arm() {
7502 let body = unknown_lit(typed_ir::Lit::Int("1".to_string()));
7503 let value_body = apply(
7504 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("value")),
7505 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7506 );
7507 let module = module_with_root(handle_value(body, "value", value_body));
7508 let lowered = lower_cps_module(&module).expect("lowered");
7509
7510 validate_cps_module(&lowered).expect("valid CPS");
7511 assert_eq!(
7512 eval_cps_module(&lowered).expect("evaluated"),
7513 vec![runtime::VmValue::Int("11".to_string())]
7514 );
7515 }
7516
7517 #[test]
7518 fn leaves_resume_result_outside_value_arm() {
7519 let body = apply(
7520 effect_op("choose"),
7521 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7522 );
7523 let arm_body = apply(var("k"), var("x"));
7524 let value_body = apply(
7525 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("value")),
7526 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7527 );
7528 let module = module_with_root(handle_once_with_value(
7529 "choose", "x", "k", body, arm_body, "value", value_body,
7530 ));
7531 let lowered = lower_cps_module(&module).expect("lowered");
7532
7533 validate_cps_module(&lowered).expect("valid CPS");
7534 assert_eq!(
7535 eval_cps_module(&lowered).expect("evaluated"),
7536 vec![runtime::VmValue::Int("1".to_string())]
7537 );
7538 }
7539
7540 #[test]
7541 fn leaves_multishot_resume_results_outside_value_arm() {
7542 let body = apply(
7543 effect_op("choose"),
7544 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7545 );
7546 let resume_x = apply(var("k"), var("x"));
7547 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7548 let arm_body = apply(
7549 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7550 resume_two,
7551 );
7552 let value_body = apply(
7553 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("value")),
7554 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7555 );
7556 let module = module_with_root(handle_once_with_value(
7557 "choose", "x", "k", body, arm_body, "value", value_body,
7558 ));
7559 let lowered = lower_cps_module(&module).expect("lowered");
7560
7561 validate_cps_module(&lowered).expect("valid CPS");
7562 assert_eq!(
7563 eval_cps_module(&lowered).expect("evaluated"),
7564 vec![runtime::VmValue::Int("3".to_string())]
7565 );
7566 }
7567
7568 #[test]
7569 fn lowers_effect_handler_body_rest_into_resumption_continuation() {
7570 let choose_one = apply(
7571 effect_op("choose"),
7572 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7573 );
7574 let body = apply(
7575 apply(primitive(typed_ir::PrimitiveOp::IntAdd), choose_one),
7576 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7577 );
7578 let resume_x = apply(var("k"), var("x"));
7579 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7580 let arm_body = apply(
7581 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7582 resume_two,
7583 );
7584 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7585 let lowered = lower_cps_module(&module).expect("lowered");
7586
7587 validate_cps_module(&lowered).expect("valid CPS");
7588 assert_eq!(
7589 eval_cps_module(&lowered).expect("evaluated"),
7590 vec![runtime::VmValue::Int("23".to_string())]
7591 );
7592 }
7593
7594 #[test]
7595 fn lowers_direct_call_rest_into_resumption_continuation() {
7596 let inc = binding(
7597 "inc",
7598 lambda(
7599 "x",
7600 apply(
7601 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
7602 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7603 ),
7604 ),
7605 );
7606 let body = apply(
7607 var("inc"),
7608 apply(
7609 effect_op("choose"),
7610 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7611 ),
7612 );
7613 let resume_x = apply(var("k"), var("x"));
7614 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7615 let arm_body = apply(
7616 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7617 resume_two,
7618 );
7619 let module = module_with_bindings_and_root(
7620 vec![inc],
7621 handle_once("choose", "x", "k", body, arm_body),
7622 );
7623 let lowered = lower_cps_module(&module).expect("lowered");
7624
7625 validate_cps_module(&lowered).expect("valid CPS");
7626 assert_eq!(
7627 eval_cps_module(&lowered).expect("evaluated"),
7628 vec![runtime::VmValue::Int("23".to_string())]
7629 );
7630 }
7631
7632 #[test]
7633 fn lowers_block_rest_into_resumption_continuation() {
7634 let body = block(
7635 vec![runtime::Stmt::Let {
7636 pattern: bind_pattern("y"),
7637 value: apply(
7638 effect_op("choose"),
7639 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7640 ),
7641 }],
7642 apply(
7643 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("y")),
7644 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7645 ),
7646 );
7647 let resume_x = apply(var("k"), var("x"));
7648 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7649 let arm_body = apply(
7650 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7651 resume_two,
7652 );
7653 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7654 let lowered = lower_cps_module(&module).expect("lowered");
7655
7656 validate_cps_module(&lowered).expect("valid CPS");
7657 assert_eq!(
7658 eval_cps_module(&lowered).expect("evaluated"),
7659 vec![runtime::VmValue::Int("23".to_string())]
7660 );
7661 }
7662
7663 #[test]
7664 fn lowers_multiple_block_effects_into_nested_resumption_continuations() {
7665 let body = block(
7666 vec![
7667 runtime::Stmt::Let {
7668 pattern: bind_pattern("a"),
7669 value: apply(
7670 effect_op("choose"),
7671 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7672 ),
7673 },
7674 runtime::Stmt::Let {
7675 pattern: bind_pattern("b"),
7676 value: apply(
7677 effect_op("choose"),
7678 unknown_lit(typed_ir::Lit::Int("2".to_string())),
7679 ),
7680 },
7681 ],
7682 apply(
7683 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("a")),
7684 var("b"),
7685 ),
7686 );
7687 let arm_body = apply(var("k"), var("x"));
7688 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7689 let lowered = lower_cps_module(&module).expect("lowered");
7690
7691 validate_cps_module(&lowered).expect("valid CPS");
7692 assert!(
7693 lowered.roots[0]
7694 .continuations
7695 .iter()
7696 .filter(|continuation| matches!(
7697 continuation.terminator,
7698 CpsTerminator::Perform { .. }
7699 ))
7700 .count()
7701 >= 2
7702 );
7703 assert_eq!(
7704 eval_cps_module(&lowered).expect("evaluated"),
7705 vec![runtime::VmValue::Int("3".to_string())]
7706 );
7707 }
7708
7709 #[test]
7710 fn lowers_block_expr_statement_rest_into_resumption_continuation() {
7711 let body = block(
7712 vec![runtime::Stmt::Expr(apply(
7713 effect_op("choose"),
7714 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7715 ))],
7716 apply(
7717 apply(
7718 primitive(typed_ir::PrimitiveOp::IntAdd),
7719 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7720 ),
7721 unknown_lit(typed_ir::Lit::Int("20".to_string())),
7722 ),
7723 );
7724 let resume_x = apply(var("k"), var("x"));
7725 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7726 let arm_body = apply(
7727 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7728 resume_two,
7729 );
7730 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7731 let lowered = lower_cps_module(&module).expect("lowered");
7732
7733 validate_cps_module(&lowered).expect("valid CPS");
7734 assert_eq!(
7735 eval_cps_module(&lowered).expect("evaluated"),
7736 vec![runtime::VmValue::Int("60".to_string())]
7737 );
7738 }
7739
7740 #[test]
7741 fn unwraps_thunked_handle_body_before_cps_effect_lowering() {
7742 let body = bind_here(thunk(block(
7743 vec![runtime::Stmt::Let {
7744 pattern: bind_pattern("y"),
7745 value: apply(
7746 effect_op("choose"),
7747 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7748 ),
7749 }],
7750 apply(
7751 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("y")),
7752 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7753 ),
7754 )));
7755 let resume_x = apply(var("k"), var("x"));
7756 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7757 let arm_body = apply(
7758 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7759 resume_two,
7760 );
7761 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7762 let lowered = lower_cps_module(&module).expect("lowered");
7763
7764 validate_cps_module(&lowered).expect("valid CPS");
7765 assert_eq!(
7766 eval_cps_module(&lowered).expect("evaluated"),
7767 vec![runtime::VmValue::Int("23".to_string())]
7768 );
7769 }
7770
7771 #[test]
7772 fn infers_capture_for_block_value_used_after_effect() {
7773 let body = block(
7774 vec![
7775 runtime::Stmt::Let {
7776 pattern: bind_pattern("z"),
7777 value: unknown_lit(typed_ir::Lit::Int("10".to_string())),
7778 },
7779 runtime::Stmt::Let {
7780 pattern: bind_pattern("y"),
7781 value: apply(
7782 effect_op("choose"),
7783 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7784 ),
7785 },
7786 ],
7787 apply(
7788 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("y")),
7789 var("z"),
7790 ),
7791 );
7792 let resume_x = apply(var("k"), var("x"));
7793 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
7794 let arm_body = apply(
7795 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7796 resume_two,
7797 );
7798 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7799 let lowered = lower_cps_module(&module).expect("lowered");
7800
7801 validate_cps_module(&lowered).expect("valid CPS");
7802 assert!(
7803 lowered.roots[0]
7804 .continuations
7805 .iter()
7806 .any(|continuation| !continuation.captures.is_empty())
7807 );
7808 assert_eq!(
7809 eval_cps_module(&lowered).expect("evaluated"),
7810 vec![runtime::VmValue::Int("23".to_string())]
7811 );
7812 }
7813
7814 #[test]
7815 fn lowers_bind_here_tail_effect_statement() {
7816 let body = bind_here(apply(
7817 effect_op("choose"),
7818 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7819 ));
7820 let arm_body = apply(var("k"), var("x"));
7821 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7822
7823 let lowered = lower_cps_module(&module).expect("lowered");
7824 validate_cps_module(&lowered).expect("valid CPS");
7825 assert_eq!(
7826 eval_cps_module(&lowered).expect("evaluated"),
7827 vec![runtime::VmValue::Int("1".to_string())]
7828 );
7829 }
7830
7831 #[test]
7832 fn lowers_if_branches_with_distinct_resume_continuations() {
7833 let then_branch = apply(
7834 apply(
7835 primitive(typed_ir::PrimitiveOp::IntAdd),
7836 apply(
7837 effect_op("choose"),
7838 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7839 ),
7840 ),
7841 unknown_lit(typed_ir::Lit::Int("10".to_string())),
7842 );
7843 let else_branch = apply(
7844 apply(
7845 primitive(typed_ir::PrimitiveOp::IntAdd),
7846 apply(
7847 effect_op("choose"),
7848 unknown_lit(typed_ir::Lit::Int("2".to_string())),
7849 ),
7850 ),
7851 unknown_lit(typed_ir::Lit::Int("20".to_string())),
7852 );
7853 let body = if_expr(
7854 unknown_lit(typed_ir::Lit::Bool(true)),
7855 then_branch,
7856 else_branch,
7857 );
7858 let resume_x = apply(var("k"), var("x"));
7859 let resume_three = apply(var("k"), unknown_lit(typed_ir::Lit::Int("3".to_string())));
7860 let arm_body = apply(
7861 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
7862 resume_three,
7863 );
7864 let module = module_with_root(handle_once("choose", "x", "k", body, arm_body));
7865 let lowered = lower_cps_module(&module).expect("lowered");
7866
7867 validate_cps_module(&lowered).expect("valid CPS");
7868 assert_eq!(
7869 eval_cps_module(&lowered).expect("evaluated"),
7870 vec![runtime::VmValue::Int("24".to_string())]
7871 );
7872 }
7873
7874 #[test]
7875 fn lowers_overapplied_direct_call_as_apply_to_result() {
7876 let make_id = binding("make_id", lambda("x", lambda("y", var("y"))));
7877 let root = apply(
7878 apply(
7879 var("make_id"),
7880 unknown_lit(typed_ir::Lit::Int("1".to_string())),
7881 ),
7882 unknown_lit(typed_ir::Lit::Int("2".to_string())),
7883 );
7884 let module = module_with_bindings_and_root(vec![make_id], root);
7885
7886 assert_eq!(
7887 eval_cps_module(&lower_cps_module(&module).expect("lowered")).expect("evaluated"),
7888 vec![runtime::VmValue::Int("2".to_string())]
7889 );
7890 }
7891}