Something went wrong. Try again.
The Monad language. Dependent types, functional programming compiled with LLVM. Hobby project. monad-lang.org
dependent-types language compiler programming-language functional-programming
Something went wrong. Try again.
5.0 kB · 103 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104/// Regression tests for the native-boolean-comparison-as-ORDINARY-VALUE/// boxing fix (`materialize_native_bool_arg`, `lang/codegen/emit.mo`).////// A native comparison (`I64.lt`/`.gt`/`.eq`/`.ne`) compiles, via/// `emit_arith_instr`, to a raw `icmp`-produced `i1` materialized into a/// `var_` temp -- deliberately left UNBOXED there so `ensure_i1_cond`'s/// "is this condition already a genuine i1" fast path can use it/// directly as a branch condition with no unboxing round-trip. That's/// correct for an `if`'s own condition, but wrong the moment the SAME/// comparison is used as an ORDINARY VALUE (a function-call argument, a/// constructor field, ...) -- this backend tracks no real per-register/// type (`llvm_value_type (var_ x)` is hardcoded `i64_`), so nothing/// downstream could tell "this `var_` is secretly `i1`" apart from a/// genuine `i64`. Found via `bootstrap compile cli/src/main.mo monad`'s own/// self-compile reaching (for the first time, after the string-phi fix)/// `lang/parser/diagnostic.mo`'s `line_end_after_go`, whose `not (a < b)`/// produced `call i64 @Bool_not(i64 %tN)` where `%tN` was actually/// declared `i1` -- `llc: '%tN' defined with type 'i1' but expected/// 'i64'`. Fixed by boxing into a genuine tagged Bool object (mirroring/// `NativeWrapKind.bool_result`'s existing `zext` + `2 - raw` pattern)/// whenever a native comparison is compiled as a call argument, in/// `compile_spine_args_go` (ordinary def calls) and `compile_ntv_args_go`/// (native/constructor calls).////// Uses `check` (a real, non-inlined top-level def taking two params) to/// force a genuine runtime `icmp`, not a compile-time-folded `bool_`/// constant -- `Bool.not (I64.lt 3 5)` on two LITERALS folds to/// `zext i1 true to i64` at compile time, which is valid IR regardless/// of whether the fix is present; only a REAL `icmp`-produced register/// exercises the bug this test guards.open IO {println}use lang::codegen::test::e2e_harness {compile_source_run_expect}/// `Bool.not (I64.lt a b)` where `a > b`: `I64.lt` is false, `Bool.not`/// flips it to true. Passing a real runtime comparison as `Bool.not`'s/// own argument is exactly the boxing site this fix covers.#[test]def test_native_comparison_as_ordinary_bool_arg_true : IO Bool := let source := r#"def check (a : I64) (b : I64) : I64 := if Bool.not (I64.lt a b) then 1 else 0def main (args : List String) : IO I64 := do { let r := check 5 3; return r}"# in compile_source_run_expect source "test_native_comparison_as_ordinary_bool_arg_true" 1/// Same shape, `a < b` this time: `I64.lt` is true, `Bool.not` flips it/// to false. Covers both `icmp`-result truth values through the fix's/// `zext`/`2 - raw` tag mapping.#[test]def test_native_comparison_as_ordinary_bool_arg_false : IO Bool := let source := r#"def check (a : I64) (b : I64) : I64 := if Bool.not (I64.lt a b) then 1 else 0def main (args : List String) : IO I64 := do { let r := check 3 5; return r}"# in compile_source_run_expect source "test_native_comparison_as_ordinary_bool_arg_false" 0/// A native comparison bound via `let` and REUSED (as two separate later/// `if`-conditions) -- `try_compile_let_beta_db` (`lang/codegen/emit.mo`)/// binds the let's compiled RHS value directly with no boxing step of/// its own, so every later reference is just `Term.var name`, losing the/// "this came from a native comparison" term-shape signal/// `ensure_i1_cond` needs -- found via `bootstrap compile cli/src/main.mo/// monad`'s own self-compile (`render_source_context`, `lang/parser//// diagnostic.mo`): a let-bound comparison reused as a LATER `if`'s own/// condition hit `ensure_i1_cond`'s "needs unboxing" branch (correctly,/// since `Term.var name` isn't itself a native-op application) and/// called `monad_get_tag` on a still-raw `i1` -- `llc: '%tN' defined/// with type 'i1' but expected 'i64'`. `3 < 5` is true: `r1` picks 1,/// `r2` picks 10 => 11.#[test]def test_let_bound_native_comparison_reused_true : IO Bool := let source := r#"def check (a : I64) (b : I64) : I64 := let too_small := I64.lt a b in let r1 := if too_small then 1 else 0 in let r2 := if too_small then 10 else 20 in I64.add r1 r2def main (args : List String) : IO I64 := do { let r := check 3 5; return r}"# in compile_source_run_expect source "test_let_bound_native_comparison_reused_true" 11/// Same shape, `5 < 3` (false) this time: `r1` picks 0, `r2` picks 20/// => 20.#[test]def test_let_bound_native_comparison_reused_false : IO Bool := let source := r#"def check (a : I64) (b : I64) : I64 := let too_small := I64.lt a b in let r1 := if too_small then 1 else 0 in let r2 := if too_small then 10 else 20 in I64.add r1 r2def main (args : List String) : IO I64 := do { let r := check 5 3; return r}"# in compile_source_run_expect source "test_let_bound_native_comparison_reused_false" 20