Skip to main content

yulang_native/
compare.rs

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}