1use std::fmt;
2
3use yulang_runtime as runtime;
4
5use crate::abi::lower_closure_module_to_abi;
6use crate::abi_eval::{NativeAbiEvalError, eval_abi_module};
7use crate::closure::closure_convert_module;
8use crate::cranelift::{NativeCraneliftError, compile_abi_module};
9use crate::eval::{NativeEvalError, eval_module};
10use crate::lower::{NativeLowerError, lower_module};
11use crate::value_cranelift::{NativeValueCraneliftError, compile_value_abi_module};
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum NativeCompareError {
15 Lower(NativeLowerError),
16 Eval(NativeEvalError),
17 Abi(NativeAbiEvalError),
18 Vm(runtime::VmError),
19 ResidualRequest {
20 index: usize,
21 request: runtime::VmRequest,
22 },
23 RootCountMismatch {
24 vm: usize,
25 native: usize,
26 abi: usize,
27 },
28 ValueMismatch {
29 index: usize,
30 vm: runtime::VmValue,
31 native: runtime::VmValue,
32 },
33 AbiValueMismatch {
34 index: usize,
35 vm: runtime::VmValue,
36 abi: runtime::VmValue,
37 },
38}
39
40impl fmt::Display for NativeCompareError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 NativeCompareError::Lower(error) => write!(f, "{error}"),
44 NativeCompareError::Eval(error) => write!(f, "{error}"),
45 NativeCompareError::Abi(error) => write!(f, "{error}"),
46 NativeCompareError::Vm(error) => write!(f, "{error}"),
47 NativeCompareError::ResidualRequest { index, request } => write!(
48 f,
49 "VM root {index} produced a host/effect request instead of a value: {request:?}"
50 ),
51 NativeCompareError::RootCountMismatch { vm, native, abi } => {
52 write!(
53 f,
54 "root count mismatch: VM {vm}, native control {native}, native ABI {abi}"
55 )
56 }
57 NativeCompareError::ValueMismatch { index, vm, native } => write!(
58 f,
59 "native control root {index} mismatch: VM {vm:?}, native {native:?}"
60 ),
61 NativeCompareError::AbiValueMismatch { index, vm, abi } => write!(
62 f,
63 "native ABI root {index} mismatch: VM {vm:?}, native ABI {abi:?}"
64 ),
65 }
66 }
67}
68
69impl std::error::Error for NativeCompareError {}
70
71impl From<NativeLowerError> for NativeCompareError {
72 fn from(value: NativeLowerError) -> Self {
73 Self::Lower(value)
74 }
75}
76
77impl From<NativeEvalError> for NativeCompareError {
78 fn from(value: NativeEvalError) -> Self {
79 Self::Eval(value)
80 }
81}
82
83impl From<NativeAbiEvalError> for NativeCompareError {
84 fn from(value: NativeAbiEvalError) -> Self {
85 Self::Abi(value)
86 }
87}
88
89impl From<runtime::VmError> for NativeCompareError {
90 fn from(value: runtime::VmError) -> Self {
91 Self::Vm(value)
92 }
93}
94
95pub fn compare_module(module: &runtime::Module) -> Result<(), NativeCompareError> {
96 let native_module = lower_module(module)?;
97 let native_values = eval_module(&native_module)?;
98 let closure_module = closure_convert_module(&native_module);
99 let abi_module = lower_closure_module_to_abi(&closure_module);
100 let abi_values = eval_abi_module(&abi_module)?;
101 let vm_results = runtime::compile_vm_module(module.clone())?.eval_roots()?;
102 if vm_results.len() != native_values.len() || vm_results.len() != abi_values.len() {
103 return Err(NativeCompareError::RootCountMismatch {
104 vm: vm_results.len(),
105 native: native_values.len(),
106 abi: abi_values.len(),
107 });
108 }
109 for (index, ((vm_result, native), abi)) in vm_results
110 .into_iter()
111 .zip(native_values)
112 .zip(abi_values)
113 .enumerate()
114 {
115 let vm = match vm_result {
116 runtime::VmResult::Value(value) => value,
117 runtime::VmResult::Request(request) => {
118 return Err(NativeCompareError::ResidualRequest { index, request });
119 }
120 };
121 if vm != native {
122 return Err(NativeCompareError::ValueMismatch { index, vm, native });
123 }
124 if vm != abi {
125 return Err(NativeCompareError::AbiValueMismatch { index, vm, abi });
126 }
127 }
128 Ok(())
129}
130
131#[derive(Debug)]
132pub enum NativeValueCompareError {
133 Lower(NativeLowerError),
134 Eval(NativeEvalError),
135 Abi(NativeAbiEvalError),
136 Vm(runtime::VmError),
137 Cranelift(NativeValueCraneliftError),
138 ResidualRequest {
139 index: usize,
140 request: runtime::VmRequest,
141 },
142 RootCountMismatch {
143 vm: usize,
144 native: usize,
145 abi: usize,
146 cranelift: usize,
147 },
148 NativeMismatch {
149 index: usize,
150 vm: runtime::VmValue,
151 native: runtime::VmValue,
152 },
153 AbiMismatch {
154 index: usize,
155 vm: runtime::VmValue,
156 abi: runtime::VmValue,
157 },
158 CraneliftMismatch {
159 index: usize,
160 vm: runtime::VmValue,
161 cranelift: runtime::VmValue,
162 },
163}
164
165impl fmt::Display for NativeValueCompareError {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 NativeValueCompareError::Lower(error) => write!(f, "{error}"),
169 NativeValueCompareError::Eval(error) => write!(f, "{error}"),
170 NativeValueCompareError::Abi(error) => write!(f, "{error}"),
171 NativeValueCompareError::Vm(error) => write!(f, "{error}"),
172 NativeValueCompareError::Cranelift(error) => write!(f, "{error}"),
173 NativeValueCompareError::ResidualRequest { index, request } => write!(
174 f,
175 "VM root {index} produced a host/effect request instead of a value: {request:?}"
176 ),
177 NativeValueCompareError::RootCountMismatch {
178 vm,
179 native,
180 abi,
181 cranelift,
182 } => write!(
183 f,
184 "root count mismatch: VM {vm}, native control {native}, native ABI {abi}, value Cranelift {cranelift}"
185 ),
186 NativeValueCompareError::NativeMismatch { index, vm, native } => write!(
187 f,
188 "native control root {index} mismatch: VM {vm:?}, native {native:?}"
189 ),
190 NativeValueCompareError::AbiMismatch { index, vm, abi } => write!(
191 f,
192 "native ABI root {index} mismatch: VM {vm:?}, native ABI {abi:?}"
193 ),
194 NativeValueCompareError::CraneliftMismatch {
195 index,
196 vm,
197 cranelift,
198 } => write!(
199 f,
200 "value Cranelift root {index} mismatch: VM {vm:?}, Cranelift {cranelift:?}"
201 ),
202 }
203 }
204}
205
206impl std::error::Error for NativeValueCompareError {}
207
208impl From<NativeLowerError> for NativeValueCompareError {
209 fn from(error: NativeLowerError) -> Self {
210 NativeValueCompareError::Lower(error)
211 }
212}
213
214impl From<NativeEvalError> for NativeValueCompareError {
215 fn from(error: NativeEvalError) -> Self {
216 NativeValueCompareError::Eval(error)
217 }
218}
219
220impl From<NativeAbiEvalError> for NativeValueCompareError {
221 fn from(error: NativeAbiEvalError) -> Self {
222 NativeValueCompareError::Abi(error)
223 }
224}
225
226impl From<runtime::VmError> for NativeValueCompareError {
227 fn from(error: runtime::VmError) -> Self {
228 NativeValueCompareError::Vm(error)
229 }
230}
231
232impl From<NativeValueCraneliftError> for NativeValueCompareError {
233 fn from(error: NativeValueCraneliftError) -> Self {
234 NativeValueCompareError::Cranelift(error)
235 }
236}
237
238pub fn compare_module_value(module: &runtime::Module) -> Result<(), NativeValueCompareError> {
239 let native_module = lower_module(module)?;
240 let native_values = eval_module(&native_module)?;
241 let closure_module = closure_convert_module(&native_module);
242 let abi_module = lower_closure_module_to_abi(&closure_module);
243 let abi_values = eval_abi_module(&abi_module)?;
244 let mut jit = compile_value_abi_module(&abi_module)?;
245 let cranelift_values = jit.run_roots()?;
246
247 let vm_results = runtime::compile_vm_module(module.clone())?.eval_roots()?;
248 if vm_results.len() != native_values.len()
249 || vm_results.len() != abi_values.len()
250 || vm_results.len() != cranelift_values.len()
251 {
252 return Err(NativeValueCompareError::RootCountMismatch {
253 vm: vm_results.len(),
254 native: native_values.len(),
255 abi: abi_values.len(),
256 cranelift: cranelift_values.len(),
257 });
258 }
259
260 for (index, (((vm_result, native), abi), cranelift)) in vm_results
261 .into_iter()
262 .zip(native_values)
263 .zip(abi_values)
264 .zip(cranelift_values)
265 .enumerate()
266 {
267 let vm = match vm_result {
268 runtime::VmResult::Value(value) => value,
269 runtime::VmResult::Request(request) => {
270 return Err(NativeValueCompareError::ResidualRequest { index, request });
271 }
272 };
273 if vm != native {
274 return Err(NativeValueCompareError::NativeMismatch { index, vm, native });
275 }
276 if vm != abi {
277 return Err(NativeValueCompareError::AbiMismatch { index, vm, abi });
278 }
279 if vm != cranelift {
280 return Err(NativeValueCompareError::CraneliftMismatch {
281 index,
282 vm,
283 cranelift,
284 });
285 }
286 }
287 Ok(())
288}
289
290#[derive(Debug)]
291pub enum NativeSourceCompareError {
292 Lower(NativeLowerError),
293 Eval(NativeEvalError),
294 Abi(NativeAbiEvalError),
295 Vm(runtime::VmError),
296 Cranelift(NativeCraneliftError),
297 ResidualRequest {
298 index: usize,
299 request: runtime::VmRequest,
300 },
301 RootCountMismatch {
302 vm: usize,
303 native: usize,
304 abi: usize,
305 cranelift: usize,
306 },
307 UnsupportedVmScalar {
308 index: usize,
309 value: runtime::VmValue,
310 },
311 UnsupportedNativeScalar {
312 index: usize,
313 value: runtime::VmValue,
314 },
315 UnsupportedAbiScalar {
316 index: usize,
317 value: runtime::VmValue,
318 },
319 NativeMismatch {
320 index: usize,
321 vm: i64,
322 native: i64,
323 },
324 AbiMismatch {
325 index: usize,
326 vm: i64,
327 abi: i64,
328 },
329 CraneliftMismatch {
330 index: usize,
331 vm: i64,
332 cranelift: i64,
333 },
334}
335
336impl fmt::Display for NativeSourceCompareError {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 match self {
339 NativeSourceCompareError::Lower(error) => write!(f, "{error}"),
340 NativeSourceCompareError::Eval(error) => write!(f, "{error}"),
341 NativeSourceCompareError::Abi(error) => write!(f, "{error}"),
342 NativeSourceCompareError::Vm(error) => write!(f, "{error}"),
343 NativeSourceCompareError::Cranelift(error) => write!(f, "{error}"),
344 NativeSourceCompareError::ResidualRequest { index, request } => write!(
345 f,
346 "VM root {index} produced a host/effect request instead of a value: {request:?}"
347 ),
348 NativeSourceCompareError::RootCountMismatch {
349 vm,
350 native,
351 abi,
352 cranelift,
353 } => write!(
354 f,
355 "root count mismatch: VM {vm}, native control {native}, native ABI {abi}, Cranelift {cranelift}"
356 ),
357 NativeSourceCompareError::UnsupportedVmScalar { index, value } => write!(
358 f,
359 "VM root {index} produced non-scalar prototype value {value:?}"
360 ),
361 NativeSourceCompareError::UnsupportedNativeScalar { index, value } => write!(
362 f,
363 "native control root {index} produced non-scalar prototype value {value:?}"
364 ),
365 NativeSourceCompareError::UnsupportedAbiScalar { index, value } => write!(
366 f,
367 "native ABI root {index} produced non-scalar prototype value {value:?}"
368 ),
369 NativeSourceCompareError::NativeMismatch { index, vm, native } => write!(
370 f,
371 "native control root {index} mismatch: VM scalar {vm}, native scalar {native}"
372 ),
373 NativeSourceCompareError::AbiMismatch { index, vm, abi } => write!(
374 f,
375 "native ABI root {index} mismatch: VM scalar {vm}, native ABI scalar {abi}"
376 ),
377 NativeSourceCompareError::CraneliftMismatch {
378 index,
379 vm,
380 cranelift,
381 } => write!(
382 f,
383 "Cranelift root {index} mismatch: VM scalar {vm}, Cranelift scalar {cranelift}"
384 ),
385 }
386 }
387}
388
389impl std::error::Error for NativeSourceCompareError {}
390
391impl From<NativeLowerError> for NativeSourceCompareError {
392 fn from(error: NativeLowerError) -> Self {
393 NativeSourceCompareError::Lower(error)
394 }
395}
396
397impl From<NativeEvalError> for NativeSourceCompareError {
398 fn from(error: NativeEvalError) -> Self {
399 NativeSourceCompareError::Eval(error)
400 }
401}
402
403impl From<NativeAbiEvalError> for NativeSourceCompareError {
404 fn from(error: NativeAbiEvalError) -> Self {
405 NativeSourceCompareError::Abi(error)
406 }
407}
408
409impl From<runtime::VmError> for NativeSourceCompareError {
410 fn from(error: runtime::VmError) -> Self {
411 NativeSourceCompareError::Vm(error)
412 }
413}
414
415impl From<NativeCraneliftError> for NativeSourceCompareError {
416 fn from(error: NativeCraneliftError) -> Self {
417 NativeSourceCompareError::Cranelift(error)
418 }
419}
420
421pub fn compare_module_i64(module: &runtime::Module) -> Result<(), NativeSourceCompareError> {
422 compare_runtime_module_i64(module.clone())
423}
424
425fn compare_runtime_module_i64(
426 runtime_module: runtime::Module,
427) -> Result<(), NativeSourceCompareError> {
428 let native_module = lower_module(&runtime_module)?;
429 let native_values = eval_module(&native_module)?;
430 let closure_module = closure_convert_module(&native_module);
431 let abi_module = lower_closure_module_to_abi(&closure_module);
432 let abi_values = eval_abi_module(&abi_module)?;
433 let mut jit = compile_abi_module(&abi_module)?;
434 let cranelift_values = jit.run_roots_i64()?;
435
436 let vm_results = runtime::compile_vm_module(runtime_module)?.eval_roots()?;
437 if vm_results.len() != native_values.len()
438 || vm_results.len() != abi_values.len()
439 || vm_results.len() != cranelift_values.len()
440 {
441 return Err(NativeSourceCompareError::RootCountMismatch {
442 vm: vm_results.len(),
443 native: native_values.len(),
444 abi: abi_values.len(),
445 cranelift: cranelift_values.len(),
446 });
447 }
448
449 for (index, (((vm_result, native_value), abi_value), cranelift_value)) in vm_results
450 .into_iter()
451 .zip(native_values)
452 .zip(abi_values)
453 .zip(cranelift_values)
454 .enumerate()
455 {
456 let vm_value = match vm_result {
457 runtime::VmResult::Value(value) => value,
458 runtime::VmResult::Request(request) => {
459 return Err(NativeSourceCompareError::ResidualRequest { index, request });
460 }
461 };
462 let vm_scalar =
463 scalar_i64(vm_value.clone()).ok_or(NativeSourceCompareError::UnsupportedVmScalar {
464 index,
465 value: vm_value,
466 })?;
467 let native_scalar = scalar_i64(native_value.clone()).ok_or(
468 NativeSourceCompareError::UnsupportedNativeScalar {
469 index,
470 value: native_value,
471 },
472 )?;
473 let abi_scalar = scalar_i64(abi_value.clone()).ok_or(
474 NativeSourceCompareError::UnsupportedAbiScalar {
475 index,
476 value: abi_value,
477 },
478 )?;
479 if vm_scalar != native_scalar {
480 return Err(NativeSourceCompareError::NativeMismatch {
481 index,
482 vm: vm_scalar,
483 native: native_scalar,
484 });
485 }
486 if vm_scalar != abi_scalar {
487 return Err(NativeSourceCompareError::AbiMismatch {
488 index,
489 vm: vm_scalar,
490 abi: abi_scalar,
491 });
492 }
493 if vm_scalar != cranelift_value {
494 return Err(NativeSourceCompareError::CraneliftMismatch {
495 index,
496 vm: vm_scalar,
497 cranelift: cranelift_value,
498 });
499 }
500 }
501 Ok(())
502}
503
504fn scalar_i64(value: runtime::VmValue) -> Option<i64> {
505 match value {
506 runtime::VmValue::Int(value) => value.parse().ok(),
507 runtime::VmValue::Bool(value) => Some(i64::from(value)),
508 runtime::VmValue::Unit => Some(0),
509 _ => None,
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use yulang_typed_ir as typed_ir;
516
517 use super::*;
518
519 fn unknown_lit(lit: typed_ir::Lit) -> runtime::Expr {
520 runtime::Expr::typed(runtime::ExprKind::Lit(lit), runtime::Type::unknown())
521 }
522
523 fn primitive(op: typed_ir::PrimitiveOp) -> runtime::Expr {
524 runtime::Expr::typed(runtime::ExprKind::PrimitiveOp(op), runtime::Type::unknown())
525 }
526
527 fn apply(callee: runtime::Expr, arg: runtime::Expr) -> runtime::Expr {
528 runtime::Expr::typed(
529 runtime::ExprKind::Apply {
530 callee: Box::new(callee),
531 arg: Box::new(arg),
532 evidence: None,
533 instantiation: None,
534 },
535 runtime::Type::unknown(),
536 )
537 }
538
539 fn if_expr(
540 cond: runtime::Expr,
541 then_branch: runtime::Expr,
542 else_branch: runtime::Expr,
543 ) -> runtime::Expr {
544 runtime::Expr::typed(
545 runtime::ExprKind::If {
546 cond: Box::new(cond),
547 then_branch: Box::new(then_branch),
548 else_branch: Box::new(else_branch),
549 evidence: None,
550 },
551 runtime::Type::unknown(),
552 )
553 }
554
555 fn match_expr(scrutinee: runtime::Expr, arms: Vec<runtime::MatchArm>) -> runtime::Expr {
556 runtime::Expr::typed(
557 runtime::ExprKind::Match {
558 scrutinee: Box::new(scrutinee),
559 arms,
560 evidence: runtime::JoinEvidence {
561 result: typed_ir::Type::Unknown,
562 },
563 },
564 runtime::Type::unknown(),
565 )
566 }
567
568 fn var(name: &str) -> runtime::Expr {
569 runtime::Expr::typed(
570 runtime::ExprKind::Var(typed_ir::Path::from_name(typed_ir::Name(name.to_string()))),
571 runtime::Type::unknown(),
572 )
573 }
574
575 fn bind_pattern(name: &str) -> runtime::Pattern {
576 runtime::Pattern::Bind {
577 name: typed_ir::Name(name.to_string()),
578 ty: runtime::Type::unknown(),
579 }
580 }
581
582 fn block(stmts: Vec<runtime::Stmt>, tail: runtime::Expr) -> runtime::Expr {
583 runtime::Expr::typed(
584 runtime::ExprKind::Block {
585 stmts,
586 tail: Some(Box::new(tail)),
587 },
588 runtime::Type::unknown(),
589 )
590 }
591
592 fn lambda(param: &str, body: runtime::Expr) -> runtime::Expr {
593 runtime::Expr::typed(
594 runtime::ExprKind::Lambda {
595 param: typed_ir::Name(param.to_string()),
596 param_effect_annotation: None,
597 param_function_allowed_effects: None,
598 body: Box::new(body),
599 },
600 runtime::Type::unknown(),
601 )
602 }
603
604 fn binding(name: &str, body: runtime::Expr) -> runtime::Binding {
605 runtime::Binding {
606 name: typed_ir::Path::from_name(typed_ir::Name(name.to_string())),
607 type_params: Vec::new(),
608 scheme: typed_ir::Scheme {
609 requirements: Vec::new(),
610 body: typed_ir::Type::Unknown,
611 },
612 body,
613 }
614 }
615
616 fn int_lit(value: &str) -> runtime::Expr {
617 unknown_lit(typed_ir::Lit::Int(value.to_string()))
618 }
619
620 fn string_lit(value: &str) -> runtime::Expr {
621 unknown_lit(typed_ir::Lit::String(value.to_string()))
622 }
623
624 fn primitive_call(op: typed_ir::PrimitiveOp, args: Vec<runtime::Expr>) -> runtime::Expr {
625 args.into_iter()
626 .fold(primitive(op), |callee, arg| apply(callee, arg))
627 }
628
629 fn tuple(items: Vec<runtime::Expr>) -> runtime::Expr {
630 runtime::Expr::typed(runtime::ExprKind::Tuple(items), runtime::Type::unknown())
631 }
632
633 fn variant(tag: &str, value: Option<runtime::Expr>) -> runtime::Expr {
634 runtime::Expr::typed(
635 runtime::ExprKind::Variant {
636 tag: typed_ir::Name(tag.to_string()),
637 value: value.map(Box::new),
638 },
639 runtime::Type::unknown(),
640 )
641 }
642
643 fn range_included_excluded(start: &str, end: &str) -> runtime::Expr {
644 variant(
645 "within",
646 Some(tuple(vec![
647 variant("included", Some(int_lit(start))),
648 variant("excluded", Some(int_lit(end))),
649 ])),
650 )
651 }
652
653 fn record(fields: Vec<(&str, runtime::Expr)>) -> runtime::Expr {
654 record_with_spread(fields, None)
655 }
656
657 fn record_with_spread(
658 fields: Vec<(&str, runtime::Expr)>,
659 spread: Option<runtime::RecordSpreadExpr>,
660 ) -> runtime::Expr {
661 runtime::Expr::typed(
662 runtime::ExprKind::Record {
663 fields: fields
664 .into_iter()
665 .map(|(name, value)| runtime::RecordExprField {
666 name: typed_ir::Name(name.to_string()),
667 value,
668 })
669 .collect(),
670 spread,
671 },
672 runtime::Type::unknown(),
673 )
674 }
675
676 fn select(base: runtime::Expr, field: &str) -> runtime::Expr {
677 runtime::Expr::typed(
678 runtime::ExprKind::Select {
679 base: Box::new(base),
680 field: typed_ir::Name(field.to_string()),
681 },
682 runtime::Type::unknown(),
683 )
684 }
685
686 fn list(items: Vec<runtime::Expr>) -> runtime::Expr {
687 items.into_iter().fold(
688 apply(
689 primitive(typed_ir::PrimitiveOp::ListEmpty),
690 unknown_lit(typed_ir::Lit::Unit),
691 ),
692 |acc, item| {
693 apply(
694 apply(primitive(typed_ir::PrimitiveOp::ListMerge), acc),
695 apply(primitive(typed_ir::PrimitiveOp::ListSingleton), item),
696 )
697 },
698 )
699 }
700
701 fn list_pattern(
702 prefix: Vec<runtime::Pattern>,
703 spread: Option<runtime::Pattern>,
704 suffix: Vec<runtime::Pattern>,
705 ) -> runtime::Pattern {
706 runtime::Pattern::List {
707 prefix,
708 spread: spread.map(Box::new),
709 suffix,
710 ty: runtime::Type::unknown(),
711 }
712 }
713
714 fn record_pattern(
715 fields: Vec<(&str, runtime::Pattern)>,
716 spread: Option<runtime::RecordSpreadPattern>,
717 ) -> runtime::Pattern {
718 runtime::Pattern::Record {
719 fields: fields
720 .into_iter()
721 .map(|(name, pattern)| runtime::RecordPatternField {
722 name: typed_ir::Name(name.to_string()),
723 pattern,
724 default: None,
725 })
726 .collect(),
727 spread,
728 ty: runtime::Type::unknown(),
729 }
730 }
731
732 fn module_with_binding_and_root(
733 binding: runtime::Binding,
734 expr: runtime::Expr,
735 ) -> runtime::Module {
736 module_with_bindings_and_root(vec![binding], expr)
737 }
738
739 fn module_with_bindings_and_root(
740 bindings: Vec<runtime::Binding>,
741 expr: runtime::Expr,
742 ) -> runtime::Module {
743 runtime::Module {
744 path: typed_ir::Path::default(),
745 bindings,
746 root_exprs: vec![expr],
747 roots: vec![runtime::Root::Expr(0)],
748 role_impls: Vec::new(),
749 }
750 }
751
752 fn module_with_root(expr: runtime::Expr) -> runtime::Module {
753 runtime::Module {
754 path: typed_ir::Path::default(),
755 bindings: Vec::new(),
756 root_exprs: vec![expr],
757 roots: vec![runtime::Root::Expr(0)],
758 role_impls: Vec::new(),
759 }
760 }
761
762 #[test]
763 fn compares_pure_int_add_with_vm() {
764 let expr = apply(
765 apply(
766 primitive(typed_ir::PrimitiveOp::IntAdd),
767 unknown_lit(typed_ir::Lit::Int("20".to_string())),
768 ),
769 unknown_lit(typed_ir::Lit::Int("22".to_string())),
770 );
771 let module = module_with_root(expr);
772
773 compare_module(&module).expect("native control matches VM");
774 }
775
776 #[test]
777 fn compares_if_with_vm() {
778 let expr = if_expr(
779 apply(
780 apply(
781 primitive(typed_ir::PrimitiveOp::IntLt),
782 unknown_lit(typed_ir::Lit::Int("1".to_string())),
783 ),
784 unknown_lit(typed_ir::Lit::Int("2".to_string())),
785 ),
786 unknown_lit(typed_ir::Lit::String("then".to_string())),
787 unknown_lit(typed_ir::Lit::String("else".to_string())),
788 );
789 let module = module_with_root(expr);
790
791 compare_module(&module).expect("native control matches VM");
792 }
793
794 #[test]
795 fn compares_simple_block_binding_with_vm() {
796 let expr = block(
797 vec![runtime::Stmt::Let {
798 pattern: bind_pattern("x"),
799 value: unknown_lit(typed_ir::Lit::Int("21".to_string())),
800 }],
801 apply(
802 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
803 var("x"),
804 ),
805 );
806 let module = module_with_root(expr);
807
808 compare_module(&module).expect("native control matches VM");
809 }
810
811 #[test]
812 fn compares_direct_monomorphic_call_with_vm() {
813 let inc = binding(
814 "inc",
815 lambda(
816 "x",
817 apply(
818 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
819 unknown_lit(typed_ir::Lit::Int("1".to_string())),
820 ),
821 ),
822 );
823 let root = apply(
824 var("inc"),
825 unknown_lit(typed_ir::Lit::Int("41".to_string())),
826 );
827 let module = module_with_binding_and_root(inc, root);
828
829 compare_module(&module).expect("native control matches VM");
830 }
831
832 #[test]
833 fn compares_multiple_bindings_with_vm() {
834 let inc = binding(
835 "inc",
836 lambda(
837 "x",
838 apply(
839 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
840 unknown_lit(typed_ir::Lit::Int("1".to_string())),
841 ),
842 ),
843 );
844 let twice = binding(
845 "twice",
846 lambda("x", apply(var("inc"), apply(var("inc"), var("x")))),
847 );
848 let root = apply(
849 var("twice"),
850 unknown_lit(typed_ir::Lit::Int("40".to_string())),
851 );
852 let module = module_with_bindings_and_root(vec![inc, twice], root);
853
854 compare_module(&module).expect("native control matches VM");
855 }
856
857 #[test]
858 fn compares_recursive_binding_with_vm() {
859 let countdown = binding(
860 "countdown",
861 lambda(
862 "n",
863 if_expr(
864 apply(
865 apply(primitive(typed_ir::PrimitiveOp::IntLe), var("n")),
866 unknown_lit(typed_ir::Lit::Int("0".to_string())),
867 ),
868 unknown_lit(typed_ir::Lit::Int("0".to_string())),
869 apply(
870 var("countdown"),
871 apply(
872 apply(primitive(typed_ir::PrimitiveOp::IntSub), var("n")),
873 unknown_lit(typed_ir::Lit::Int("1".to_string())),
874 ),
875 ),
876 ),
877 ),
878 );
879 let root = apply(
880 var("countdown"),
881 unknown_lit(typed_ir::Lit::Int("3".to_string())),
882 );
883 let module = module_with_binding_and_root(countdown, root);
884
885 compare_module(&module).expect("native control matches VM");
886 }
887
888 #[test]
889 fn compares_immediate_lambda_call_with_vm() {
890 let expr = apply(
891 lambda(
892 "x",
893 apply(
894 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
895 unknown_lit(typed_ir::Lit::Int("1".to_string())),
896 ),
897 ),
898 unknown_lit(typed_ir::Lit::Int("41".to_string())),
899 );
900 let module = module_with_root(expr);
901
902 compare_module(&module).expect("native control matches VM");
903 }
904
905 #[test]
906 fn compares_lambda_capture_with_vm() {
907 let expr = block(
908 vec![runtime::Stmt::Let {
909 pattern: bind_pattern("y"),
910 value: unknown_lit(typed_ir::Lit::Int("10".to_string())),
911 }],
912 apply(
913 lambda(
914 "x",
915 apply(
916 apply(primitive(typed_ir::PrimitiveOp::IntAdd), var("x")),
917 var("y"),
918 ),
919 ),
920 unknown_lit(typed_ir::Lit::Int("32".to_string())),
921 ),
922 );
923 let module = module_with_root(expr);
924
925 compare_module(&module).expect("native control matches VM");
926 }
927
928 #[test]
929 fn compares_value_lambda_capture_with_value_cranelift() {
930 let expr = block(
931 vec![runtime::Stmt::Let {
932 pattern: bind_pattern("suffix"),
933 value: string_lit("!"),
934 }],
935 apply(
936 lambda(
937 "text",
938 primitive_call(
939 typed_ir::PrimitiveOp::StringConcat,
940 vec![var("text"), var("suffix")],
941 ),
942 ),
943 string_lit("yu"),
944 ),
945 );
946 let module = module_with_root(expr);
947
948 compare_module_value(&module).expect("value paths match");
949 }
950
951 #[test]
952 fn compares_if_with_vm_native_abi_and_cranelift() {
953 let module = module_with_root(if_expr(
954 apply(
955 apply(
956 primitive(typed_ir::PrimitiveOp::IntLt),
957 unknown_lit(typed_ir::Lit::Int("1".to_string())),
958 ),
959 unknown_lit(typed_ir::Lit::Int("2".to_string())),
960 ),
961 unknown_lit(typed_ir::Lit::Int("10".to_string())),
962 unknown_lit(typed_ir::Lit::Int("20".to_string())),
963 ));
964
965 compare_module_i64(&module).expect("scalar paths match");
966 }
967
968 #[test]
969 fn compares_record_select_with_value_cranelift() {
970 let module = module_with_root(select(
971 record(vec![("x", int_lit("1")), ("y", int_lit("2"))]),
972 "x",
973 ));
974
975 compare_module_value(&module).expect("value paths match");
976 }
977
978 #[test]
979 fn compares_record_spread_expr_with_value_cranelift() {
980 let base = record(vec![("x", int_lit("1"))]);
981 let module = module_with_root(select(
982 record_with_spread(
983 vec![("y", int_lit("2"))],
984 Some(runtime::RecordSpreadExpr::Head(Box::new(base))),
985 ),
986 "x",
987 ));
988
989 compare_module_value(&module).expect("value paths match");
990 }
991
992 #[test]
993 fn compares_list_spread_pattern_with_value_cranelift() {
994 let arm = runtime::MatchArm {
995 pattern: list_pattern(
996 vec![bind_pattern("head")],
997 Some(bind_pattern("middle")),
998 vec![bind_pattern("tail")],
999 ),
1000 guard: None,
1001 body: var("middle"),
1002 };
1003 let module = module_with_root(match_expr(
1004 list(vec![int_lit("1"), int_lit("2"), int_lit("3")]),
1005 vec![arm],
1006 ));
1007
1008 compare_module_value(&module).expect("value paths match");
1009 }
1010
1011 #[test]
1012 fn compares_record_spread_pattern_with_value_cranelift() {
1013 let arm = runtime::MatchArm {
1014 pattern: record_pattern(
1015 vec![("x", bind_pattern("x"))],
1016 Some(runtime::RecordSpreadPattern::Tail(Box::new(bind_pattern(
1017 "rest",
1018 )))),
1019 ),
1020 guard: None,
1021 body: select(var("rest"), "y"),
1022 };
1023 let module = module_with_root(match_expr(
1024 record(vec![("x", int_lit("1")), ("y", int_lit("2"))]),
1025 vec![arm],
1026 ));
1027
1028 compare_module_value(&module).expect("value paths match");
1029 }
1030
1031 #[test]
1032 fn compares_string_value_primitives_with_value_cranelift() {
1033 for root in [
1034 primitive_call(
1035 typed_ir::PrimitiveOp::StringEq,
1036 vec![string_lit("yu"), string_lit("yu")],
1037 ),
1038 primitive_call(
1039 typed_ir::PrimitiveOp::StringIndex,
1040 vec![string_lit("aćšz"), int_lit("2")],
1041 ),
1042 primitive_call(
1043 typed_ir::PrimitiveOp::StringIndexRangeRaw,
1044 vec![string_lit("aćšz"), int_lit("1"), int_lit("3")],
1045 ),
1046 primitive_call(
1047 typed_ir::PrimitiveOp::StringSpliceRaw,
1048 vec![
1049 string_lit("aćšz"),
1050 int_lit("1"),
1051 int_lit("3"),
1052 string_lit("bc"),
1053 ],
1054 ),
1055 primitive_call(
1056 typed_ir::PrimitiveOp::StringIndexRange,
1057 vec![string_lit("aćšz"), range_included_excluded("1", "3")],
1058 ),
1059 primitive_call(
1060 typed_ir::PrimitiveOp::StringSplice,
1061 vec![
1062 string_lit("aćšz"),
1063 range_included_excluded("1", "3"),
1064 string_lit("bc"),
1065 ],
1066 ),
1067 ] {
1068 compare_module_value(&module_with_root(root)).expect("value paths match");
1069 }
1070 }
1071
1072 #[test]
1073 fn compares_list_value_primitives_with_value_cranelift() {
1074 for root in [
1075 primitive_call(
1076 typed_ir::PrimitiveOp::ListIndexRange,
1077 vec![
1078 list(vec![int_lit("1"), int_lit("2"), int_lit("3"), int_lit("4")]),
1079 range_included_excluded("1", "3"),
1080 ],
1081 ),
1082 primitive_call(
1083 typed_ir::PrimitiveOp::ListSpliceRaw,
1084 vec![
1085 list(vec![int_lit("1"), int_lit("2"), int_lit("3"), int_lit("4")]),
1086 int_lit("1"),
1087 int_lit("3"),
1088 list(vec![int_lit("8"), int_lit("9")]),
1089 ],
1090 ),
1091 primitive_call(
1092 typed_ir::PrimitiveOp::ListSplice,
1093 vec![
1094 list(vec![int_lit("1"), int_lit("2"), int_lit("3"), int_lit("4")]),
1095 range_included_excluded("1", "3"),
1096 list(vec![int_lit("8"), int_lit("9")]),
1097 ],
1098 ),
1099 primitive_call(
1100 typed_ir::PrimitiveOp::ListViewRaw,
1101 vec![list(vec![int_lit("1"), int_lit("2")])],
1102 ),
1103 ] {
1104 compare_module_value(&module_with_root(root)).expect("value paths match");
1105 }
1106 }
1107}