1use std::fmt;
2
3use yulang_runtime as runtime;
4
5use crate::cps_eval::{CpsEvalError, eval_cps_module};
6use crate::cps_lower::{CpsLowerError, lower_cps_module};
7use crate::cps_repr_cranelift::{CpsReprCraneliftError, compile_runtime_module_to_cps_repr_jit};
8use crate::cps_validate::{CpsValidateError, validate_cps_module};
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum CpsCompareError {
12 Lower(CpsLowerError),
13 Validate(CpsValidateError),
14 Eval(CpsEvalError),
15 Cranelift(CpsReprCraneliftError),
16 Vm(runtime::VmError),
17 ResidualRequest {
18 index: usize,
19 request: runtime::VmRequest,
20 },
21 RootCountMismatch {
22 vm: usize,
23 cps: usize,
24 },
25 ValueMismatch {
26 index: usize,
27 vm: runtime::VmValue,
28 cps: runtime::VmValue,
29 },
30 I64Mismatch {
31 index: usize,
32 vm: i64,
33 cps_cranelift: i64,
34 },
35 NonI64Value {
36 index: usize,
37 value: runtime::VmValue,
38 },
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct CpsReprI64CompareReport {
43 pub roots: Vec<CpsReprI64RootCompare>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct CpsReprI64RootCompare {
48 pub index: usize,
49 pub vm: i64,
50 pub cps_cranelift: i64,
51}
52
53impl fmt::Display for CpsCompareError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 CpsCompareError::Lower(error) => write!(f, "{error}"),
57 CpsCompareError::Validate(error) => write!(f, "{error}"),
58 CpsCompareError::Eval(error) => write!(f, "{error}"),
59 CpsCompareError::Cranelift(error) => write!(f, "{error}"),
60 CpsCompareError::Vm(error) => write!(f, "{error}"),
61 CpsCompareError::ResidualRequest { index, request } => write!(
62 f,
63 "VM root {index} produced a host/effect request instead of a value: {request:?}"
64 ),
65 CpsCompareError::RootCountMismatch { vm, cps } => {
66 write!(f, "VM produced {vm} roots but CPS produced {cps}")
67 }
68 CpsCompareError::ValueMismatch { index, vm, cps } => {
69 write!(f, "CPS root {index} mismatch: VM {vm:?}, CPS {cps:?}")
70 }
71 CpsCompareError::I64Mismatch {
72 index,
73 vm,
74 cps_cranelift,
75 } => write!(
76 f,
77 "CPS repr Cranelift root {index} mismatch: VM {vm}, Cranelift {cps_cranelift}"
78 ),
79 CpsCompareError::NonI64Value { index, value } => {
80 write!(f, "VM root {index} produced non-i64 value {value:?}")
81 }
82 }
83 }
84}
85
86impl std::error::Error for CpsCompareError {}
87
88impl From<CpsLowerError> for CpsCompareError {
89 fn from(value: CpsLowerError) -> Self {
90 Self::Lower(value)
91 }
92}
93
94impl From<CpsValidateError> for CpsCompareError {
95 fn from(value: CpsValidateError) -> Self {
96 Self::Validate(value)
97 }
98}
99
100impl From<CpsEvalError> for CpsCompareError {
101 fn from(value: CpsEvalError) -> Self {
102 Self::Eval(value)
103 }
104}
105
106impl From<CpsReprCraneliftError> for CpsCompareError {
107 fn from(value: CpsReprCraneliftError) -> Self {
108 Self::Cranelift(value)
109 }
110}
111
112impl From<runtime::VmError> for CpsCompareError {
113 fn from(value: runtime::VmError) -> Self {
114 Self::Vm(value)
115 }
116}
117
118pub fn compare_cps_module(module: &runtime::Module) -> Result<(), CpsCompareError> {
119 let cps_module = lower_cps_module(module)?;
120 validate_cps_module(&cps_module)?;
121 let cps_values = eval_cps_module(&cps_module)?;
122 let vm_results = runtime::compile_vm_module(module.clone())?.eval_roots()?;
123 if vm_results.len() != cps_values.len() {
124 return Err(CpsCompareError::RootCountMismatch {
125 vm: vm_results.len(),
126 cps: cps_values.len(),
127 });
128 }
129 for (index, (vm_result, cps)) in vm_results.into_iter().zip(cps_values).enumerate() {
130 let vm = match vm_result {
131 runtime::VmResult::Value(value) => value,
132 runtime::VmResult::Request(request) => {
133 return Err(CpsCompareError::ResidualRequest { index, request });
134 }
135 };
136 if vm != cps {
137 return Err(CpsCompareError::ValueMismatch { index, vm, cps });
138 }
139 }
140 Ok(())
141}
142
143pub fn compare_cps_repr_cranelift_i64(module: &runtime::Module) -> Result<(), CpsCompareError> {
144 compare_cps_repr_cranelift_i64_report(module).map(|_| ())
145}
146
147pub fn compare_cps_repr_cranelift_i64_report(
148 module: &runtime::Module,
149) -> Result<CpsReprI64CompareReport, CpsCompareError> {
150 let mut jit = compile_runtime_module_to_cps_repr_jit(module)?;
151 let cps_values = jit.run_roots_i64()?;
152 let vm_results = runtime::compile_vm_module(module.clone())?.eval_roots()?;
153 if vm_results.len() != cps_values.len() {
154 return Err(CpsCompareError::RootCountMismatch {
155 vm: vm_results.len(),
156 cps: cps_values.len(),
157 });
158 }
159 let mut roots = Vec::with_capacity(vm_results.len());
160 for (index, (vm_result, cps_cranelift)) in vm_results.into_iter().zip(cps_values).enumerate() {
161 let value = match vm_result {
162 runtime::VmResult::Value(value) => value,
163 runtime::VmResult::Request(request) => {
164 return Err(CpsCompareError::ResidualRequest { index, request });
165 }
166 };
167 let vm = match value {
168 runtime::VmValue::Int(value) => {
169 value
170 .parse::<i64>()
171 .map_err(|_| CpsCompareError::NonI64Value {
172 index,
173 value: runtime::VmValue::Int(value),
174 })?
175 }
176 runtime::VmValue::Bool(value) => i64::from(value),
177 value => return Err(CpsCompareError::NonI64Value { index, value }),
178 };
179 if vm != cps_cranelift {
180 return Err(CpsCompareError::I64Mismatch {
181 index,
182 vm,
183 cps_cranelift,
184 });
185 }
186 roots.push(CpsReprI64RootCompare {
187 index,
188 vm,
189 cps_cranelift,
190 });
191 }
192 Ok(CpsReprI64CompareReport { roots })
193}
194
195#[cfg(test)]
196mod tests {
197 use yulang_typed_ir as typed_ir;
198
199 use crate::compare::compare_module;
200
201 use super::*;
202
203 fn unknown_lit(lit: typed_ir::Lit) -> runtime::Expr {
204 runtime::Expr::typed(runtime::ExprKind::Lit(lit), runtime::Type::unknown())
205 }
206
207 fn primitive(op: typed_ir::PrimitiveOp) -> runtime::Expr {
208 runtime::Expr::typed(runtime::ExprKind::PrimitiveOp(op), runtime::Type::unknown())
209 }
210
211 fn apply(callee: runtime::Expr, arg: runtime::Expr) -> runtime::Expr {
212 runtime::Expr::typed(
213 runtime::ExprKind::Apply {
214 callee: Box::new(callee),
215 arg: Box::new(arg),
216 evidence: None,
217 instantiation: None,
218 },
219 runtime::Type::unknown(),
220 )
221 }
222
223 fn if_expr(
224 cond: runtime::Expr,
225 then_branch: runtime::Expr,
226 else_branch: runtime::Expr,
227 ) -> runtime::Expr {
228 runtime::Expr::typed(
229 runtime::ExprKind::If {
230 cond: Box::new(cond),
231 then_branch: Box::new(then_branch),
232 else_branch: Box::new(else_branch),
233 evidence: None,
234 },
235 runtime::Type::unknown(),
236 )
237 }
238
239 fn var(name: &str) -> runtime::Expr {
240 runtime::Expr::typed(
241 runtime::ExprKind::Var(typed_ir::Path::from_name(typed_ir::Name(name.to_string()))),
242 runtime::Type::unknown(),
243 )
244 }
245
246 fn effect_op(name: &str) -> runtime::Expr {
247 runtime::Expr::typed(
248 runtime::ExprKind::EffectOp(effect_operation_path(name)),
249 runtime::Type::unknown(),
250 )
251 }
252
253 fn effect_operation_path(name: &str) -> typed_ir::Path {
254 typed_ir::Path {
255 segments: vec![
256 typed_ir::Name(name.to_string()),
257 typed_ir::Name(name.to_string()),
258 ],
259 }
260 }
261
262 fn bind_pattern(name: &str) -> runtime::Pattern {
263 runtime::Pattern::Bind {
264 name: typed_ir::Name(name.to_string()),
265 ty: runtime::Type::unknown(),
266 }
267 }
268
269 fn handle_once(
270 effect: &str,
271 payload: &str,
272 resume: &str,
273 body: runtime::Expr,
274 arm_body: runtime::Expr,
275 ) -> runtime::Expr {
276 let effect_namespace = typed_ir::Path::from_name(typed_ir::Name(effect.to_string()));
277 let effect_operation = effect_operation_path(effect);
278 runtime::Expr::typed(
279 runtime::ExprKind::Handle {
280 body: Box::new(body),
281 arms: vec![runtime::HandleArm {
282 effect: effect_operation,
283 payload: bind_pattern(payload),
284 resume: Some(runtime::ResumeBinding {
285 name: typed_ir::Name(resume.to_string()),
286 ty: runtime::Type::unknown(),
287 }),
288 guard: None,
289 body: arm_body,
290 }],
291 evidence: runtime::JoinEvidence {
292 result: typed_ir::Type::Unknown,
293 },
294 handler: runtime::HandleEffect {
295 consumes: vec![effect_namespace],
296 residual_before: None,
297 residual_after: None,
298 },
299 },
300 runtime::Type::unknown(),
301 )
302 }
303
304 fn block(stmts: Vec<runtime::Stmt>, tail: runtime::Expr) -> runtime::Expr {
305 runtime::Expr::typed(
306 runtime::ExprKind::Block {
307 stmts,
308 tail: Some(Box::new(tail)),
309 },
310 runtime::Type::unknown(),
311 )
312 }
313
314 fn thunk(expr: runtime::Expr) -> runtime::Expr {
315 runtime::Expr::typed(
316 runtime::ExprKind::Thunk {
317 effect: typed_ir::Type::Unknown,
318 value: runtime::Type::unknown(),
319 expr: Box::new(expr),
320 },
321 runtime::Type::thunk(typed_ir::Type::Unknown, runtime::Type::unknown()),
322 )
323 }
324
325 fn handled_body(expr: runtime::Expr) -> runtime::Expr {
326 thunk(expr)
327 }
328
329 fn lambda(param: &str, body: runtime::Expr) -> runtime::Expr {
330 runtime::Expr::typed(
331 runtime::ExprKind::Lambda {
332 param: typed_ir::Name(param.to_string()),
333 param_effect_annotation: None,
334 param_function_allowed_effects: None,
335 body: Box::new(body),
336 },
337 runtime::Type::unknown(),
338 )
339 }
340
341 fn binding(name: &str, body: runtime::Expr) -> runtime::Binding {
342 runtime::Binding {
343 name: typed_ir::Path::from_name(typed_ir::Name(name.to_string())),
344 type_params: Vec::new(),
345 scheme: typed_ir::Scheme {
346 requirements: Vec::new(),
347 body: typed_ir::Type::Unknown,
348 },
349 body,
350 }
351 }
352
353 fn module_with_root(expr: runtime::Expr) -> runtime::Module {
354 module_with_bindings_and_root(Vec::new(), expr)
355 }
356
357 fn module_with_bindings_and_root(
358 bindings: Vec<runtime::Binding>,
359 expr: runtime::Expr,
360 ) -> runtime::Module {
361 runtime::Module {
362 path: typed_ir::Path::default(),
363 bindings,
364 root_exprs: vec![expr],
365 roots: vec![runtime::Root::Expr(0)],
366 role_impls: Vec::new(),
367 }
368 }
369
370 fn primitive_call(op: typed_ir::PrimitiveOp, args: Vec<runtime::Expr>) -> runtime::Expr {
371 args.into_iter()
372 .fold(primitive(op), |callee, arg| apply(callee, arg))
373 }
374
375 fn list_expr(items: Vec<i64>) -> runtime::Expr {
376 list_of_exprs(
377 items
378 .into_iter()
379 .map(|item| unknown_lit(typed_ir::Lit::Int(item.to_string())))
380 .collect(),
381 )
382 }
383
384 fn list_of_exprs(items: Vec<runtime::Expr>) -> runtime::Expr {
385 items
386 .into_iter()
387 .map(|item| primitive_call(typed_ir::PrimitiveOp::ListSingleton, vec![item]))
388 .fold(
389 primitive_call(
390 typed_ir::PrimitiveOp::ListEmpty,
391 vec![unknown_lit(typed_ir::Lit::Unit)],
392 ),
393 |acc, item| primitive_call(typed_ir::PrimitiveOp::ListMerge, vec![acc, item]),
394 )
395 }
396
397 fn record(fields: Vec<(&str, runtime::Expr)>) -> runtime::Expr {
398 runtime::Expr::typed(
399 runtime::ExprKind::Record {
400 fields: fields
401 .into_iter()
402 .map(|(name, value)| runtime::RecordExprField {
403 name: typed_ir::Name(name.to_string()),
404 value,
405 })
406 .collect(),
407 spread: None,
408 },
409 runtime::Type::unknown(),
410 )
411 }
412
413 fn variant(tag: &str, value: Option<runtime::Expr>) -> runtime::Expr {
414 runtime::Expr::typed(
415 runtime::ExprKind::Variant {
416 tag: typed_ir::Name(tag.to_string()),
417 value: value.map(Box::new),
418 },
419 runtime::Type::unknown(),
420 )
421 }
422
423 fn compare_all(module: &runtime::Module) {
424 compare_module(module).expect("native control matches VM");
425 compare_cps_module(module).expect("CPS matches VM");
426 }
427
428 fn compare_cps_cranelift_i64(module: &runtime::Module) {
429 compare_cps_repr_cranelift_i64(module).expect("CPS repr Cranelift matches VM");
430 }
431
432 fn cps_cranelift_display_roots(module: &runtime::Module) -> Vec<String> {
433 let mut jit =
434 compile_runtime_module_to_cps_repr_jit(module).expect("compiled CPS repr Cranelift");
435 jit.run_roots_display().expect("ran CPS repr Cranelift")
436 }
437
438 #[test]
439 fn compares_pure_int_add_with_vm_and_native_control() {
440 let expr = apply(
441 apply(
442 primitive(typed_ir::PrimitiveOp::IntAdd),
443 unknown_lit(typed_ir::Lit::Int("20".to_string())),
444 ),
445 unknown_lit(typed_ir::Lit::Int("22".to_string())),
446 );
447 compare_all(&module_with_root(expr));
448 }
449
450 #[test]
451 fn compares_pure_int_add_with_cps_repr_cranelift() {
452 let expr = apply(
453 apply(
454 primitive(typed_ir::PrimitiveOp::IntAdd),
455 unknown_lit(typed_ir::Lit::Int("20".to_string())),
456 ),
457 unknown_lit(typed_ir::Lit::Int("22".to_string())),
458 );
459 compare_cps_cranelift_i64(&module_with_root(expr));
460 }
461
462 #[test]
463 fn compares_effect_handler_with_cps_repr_cranelift() {
464 let body = apply(
465 effect_op("choose"),
466 unknown_lit(typed_ir::Lit::Int("1".to_string())),
467 );
468 let arm_body = apply(var("k"), var("x"));
469
470 compare_cps_cranelift_i64(&module_with_root(handle_once(
471 "choose",
472 "x",
473 "k",
474 handled_body(body),
475 arm_body,
476 )));
477 }
478
479 #[test]
480 fn compares_multishot_effect_handler_with_cps_repr_cranelift() {
481 let body = apply(
482 effect_op("choose"),
483 unknown_lit(typed_ir::Lit::Int("1".to_string())),
484 );
485 let resume_x = apply(var("k"), var("x"));
486 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
487 let arm_body = apply(
488 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
489 resume_two,
490 );
491
492 compare_cps_cranelift_i64(&module_with_root(handle_once(
493 "choose",
494 "x",
495 "k",
496 handled_body(body),
497 arm_body,
498 )));
499 }
500
501 #[test]
502 fn compares_effect_handler_rest_continuation_with_cps_repr_cranelift() {
503 let choose_one = apply(
504 effect_op("choose"),
505 unknown_lit(typed_ir::Lit::Int("1".to_string())),
506 );
507 let body = apply(
508 apply(primitive(typed_ir::PrimitiveOp::IntAdd), choose_one),
509 unknown_lit(typed_ir::Lit::Int("10".to_string())),
510 );
511 let resume_x = apply(var("k"), var("x"));
512 let resume_two = apply(var("k"), unknown_lit(typed_ir::Lit::Int("2".to_string())));
513 let arm_body = apply(
514 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
515 resume_two,
516 );
517
518 compare_cps_cranelift_i64(&module_with_root(handle_once(
519 "choose",
520 "x",
521 "k",
522 handled_body(body),
523 arm_body,
524 )));
525 }
526
527 #[test]
528 fn compares_effect_handler_branch_with_cps_repr_cranelift() {
529 let then_branch = apply(
530 apply(
531 primitive(typed_ir::PrimitiveOp::IntAdd),
532 apply(
533 effect_op("choose"),
534 unknown_lit(typed_ir::Lit::Int("1".to_string())),
535 ),
536 ),
537 unknown_lit(typed_ir::Lit::Int("10".to_string())),
538 );
539 let else_branch = apply(
540 apply(
541 primitive(typed_ir::PrimitiveOp::IntAdd),
542 apply(
543 effect_op("choose"),
544 unknown_lit(typed_ir::Lit::Int("2".to_string())),
545 ),
546 ),
547 unknown_lit(typed_ir::Lit::Int("20".to_string())),
548 );
549 let body = if_expr(
550 unknown_lit(typed_ir::Lit::Bool(true)),
551 then_branch,
552 else_branch,
553 );
554 let resume_x = apply(var("k"), var("x"));
555 let resume_three = apply(var("k"), unknown_lit(typed_ir::Lit::Int("3".to_string())));
556 let arm_body = apply(
557 apply(primitive(typed_ir::PrimitiveOp::IntAdd), resume_x),
558 resume_three,
559 );
560
561 compare_cps_cranelift_i64(&module_with_root(handle_once(
562 "choose",
563 "x",
564 "k",
565 handled_body(body),
566 arm_body,
567 )));
568 }
569
570 #[test]
571 fn compares_non_scalar_effect_payloads_with_cps_repr_cranelift() {
572 let cases = vec![
573 (
574 unknown_lit(typed_ir::Lit::String("ok".to_string())),
575 "ok".to_string(),
576 ),
577 (list_expr(vec![1, 2, 3]), "[1, 2, 3]".to_string()),
578 (
579 record(vec![(
580 "answer",
581 unknown_lit(typed_ir::Lit::Int("42".to_string())),
582 )]),
583 "{ answer: 42 }".to_string(),
584 ),
585 (
586 variant(
587 "just",
588 Some(unknown_lit(typed_ir::Lit::Int("7".to_string()))),
589 ),
590 ":just 7".to_string(),
591 ),
592 ];
593
594 for (payload, expected) in cases {
595 let body = apply(effect_op("choose"), payload);
596 let arm_body = apply(var("k"), var("x"));
597 let module = module_with_root(handle_once(
598 "choose",
599 "x",
600 "k",
601 handled_body(body),
602 arm_body,
603 ));
604
605 compare_cps_module(&module).expect("CPS matches VM");
606 assert_eq!(cps_cranelift_display_roots(&module), vec![expected]);
607 }
608 }
609
610 #[test]
611 fn carries_runtime_values_through_cps_repr_control_edges() {
612 let branch = module_with_root(if_expr(
613 unknown_lit(typed_ir::Lit::Bool(true)),
614 list_expr(vec![1, 2]),
615 list_expr(vec![3]),
616 ));
617 compare_cps_module(&branch).expect("branch CPS matches VM");
618 assert_eq!(
619 cps_cranelift_display_roots(&branch),
620 vec!["[1, 2]".to_string()]
621 );
622
623 let direct_call = module_with_bindings_and_root(
624 vec![binding("make", lambda("u", list_expr(vec![4, 5])))],
625 apply(var("make"), unknown_lit(typed_ir::Lit::Unit)),
626 );
627 compare_cps_module(&direct_call).expect("direct call CPS matches VM");
628 assert_eq!(
629 cps_cranelift_display_roots(&direct_call),
630 vec!["[4, 5]".to_string()]
631 );
632
633 let closure_apply = module_with_root(block(
634 vec![runtime::Stmt::Let {
635 pattern: bind_pattern("f"),
636 value: lambda("u", list_expr(vec![6, 7])),
637 }],
638 apply(var("f"), unknown_lit(typed_ir::Lit::Unit)),
639 ));
640 compare_cps_module(&closure_apply).expect("closure apply CPS matches VM");
641 assert_eq!(
642 cps_cranelift_display_roots(&closure_apply),
643 vec!["[6, 7]".to_string()]
644 );
645 }
646
647 #[test]
648 fn carries_large_closure_environment_through_cps_repr() {
649 let closure_apply = module_with_root(block(
650 vec![
651 runtime::Stmt::Let {
652 pattern: bind_pattern("a"),
653 value: unknown_lit(typed_ir::Lit::Int("1".to_string())),
654 },
655 runtime::Stmt::Let {
656 pattern: bind_pattern("b"),
657 value: unknown_lit(typed_ir::Lit::Int("2".to_string())),
658 },
659 runtime::Stmt::Let {
660 pattern: bind_pattern("c"),
661 value: unknown_lit(typed_ir::Lit::Int("3".to_string())),
662 },
663 runtime::Stmt::Let {
664 pattern: bind_pattern("d"),
665 value: unknown_lit(typed_ir::Lit::Int("4".to_string())),
666 },
667 runtime::Stmt::Let {
668 pattern: bind_pattern("e"),
669 value: unknown_lit(typed_ir::Lit::Int("5".to_string())),
670 },
671 runtime::Stmt::Let {
672 pattern: bind_pattern("f"),
673 value: lambda(
674 "u",
675 handle_once(
676 "choose",
677 "x",
678 "k",
679 handled_body(apply(
680 effect_op("choose"),
681 list_of_exprs(vec![
682 var("a"),
683 var("b"),
684 var("c"),
685 var("d"),
686 var("e"),
687 ]),
688 )),
689 apply(var("k"), var("x")),
690 ),
691 ),
692 },
693 ],
694 apply(var("f"), unknown_lit(typed_ir::Lit::Unit)),
695 ));
696
697 compare_cps_module(&closure_apply).expect("large closure env CPS matches VM");
698 assert_eq!(
699 cps_cranelift_display_roots(&closure_apply),
700 vec!["[1, 2, 3, 4, 5]".to_string()]
701 );
702 }
703
704 #[test]
705 fn applies_partial_top_level_function_through_cps_repr() {
706 let add = binding(
707 "add",
708 lambda(
709 "x",
710 lambda(
711 "y",
712 apply(
713 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
714 var("y"),
715 ),
716 ),
717 ),
718 );
719 let root = apply(
720 apply(var("add"), unknown_lit(typed_ir::Lit::Int("1".to_string()))),
721 unknown_lit(typed_ir::Lit::Int("41".to_string())),
722 );
723 let module = module_with_bindings_and_root(vec![add], root);
724
725 compare_cps_module(&module).expect("partial function CPS matches VM");
726 assert_eq!(cps_cranelift_display_roots(&module), vec!["42".to_string()]);
727 }
728
729 #[test]
730 fn compares_if_with_vm_and_native_control() {
731 let expr = if_expr(
732 apply(
733 apply(
734 primitive(typed_ir::PrimitiveOp::IntLt),
735 unknown_lit(typed_ir::Lit::Int("1".to_string())),
736 ),
737 unknown_lit(typed_ir::Lit::Int("2".to_string())),
738 ),
739 unknown_lit(typed_ir::Lit::String("then".to_string())),
740 unknown_lit(typed_ir::Lit::String("else".to_string())),
741 );
742 compare_all(&module_with_root(expr));
743 }
744
745 #[test]
746 fn compares_block_binding_with_vm_and_native_control() {
747 let expr = block(
748 vec![runtime::Stmt::Let {
749 pattern: bind_pattern("x"),
750 value: unknown_lit(typed_ir::Lit::Int("21".to_string())),
751 }],
752 apply(
753 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
754 var("x"),
755 ),
756 );
757 compare_all(&module_with_root(expr));
758 }
759
760 #[test]
761 fn compares_recursive_binding_with_vm_and_native_control() {
762 let countdown = binding(
763 "countdown",
764 lambda(
765 "n",
766 if_expr(
767 apply(
768 apply(primitive(typed_ir::PrimitiveOp::IntLe), var("n")),
769 unknown_lit(typed_ir::Lit::Int("0".to_string())),
770 ),
771 unknown_lit(typed_ir::Lit::Int("0".to_string())),
772 apply(
773 var("countdown"),
774 apply(
775 apply(primitive(typed_ir::PrimitiveOp::IntSub), var("n")),
776 unknown_lit(typed_ir::Lit::Int("1".to_string())),
777 ),
778 ),
779 ),
780 ),
781 );
782 let root = apply(
783 var("countdown"),
784 unknown_lit(typed_ir::Lit::Int("3".to_string())),
785 );
786 compare_all(&module_with_bindings_and_root(vec![countdown], root));
787 }
788}