kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit cb2b07f44e58d147ac6486faa6cb68d62c5e6642
parent 0142bdbd455d90d7c280c7e25131e45857a5a639
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 10:54:18 -0700

wasm: fix NaN-correct f.ne re-lowering and 64-bit logical-not

Two distinct wasm bugs surfaced by the toy FP-compare cases (lane W,
kit cc -target wasm32-none -c ... | kit run on the .wasm):

1. lang/wasm: f32.ne/f64.ne were re-lowered to KIT_CG_FP_ONE (ordered
   not-equal, false on NaN). wasm's f.ne is the UNORDERED not-equal
   (true when a != b OR either operand is NaN). Map to KIT_CG_FP_UNE.
   This is the basis of isnan(x) = (x != x), so @isunordered and any
   float `!=` round-tripped through wasm gave the wrong NaN answer.

2. src/arch/wasm emit: UO_NOT (logical not) of a 64-bit value emits
   i64.eqz, which pops i64 but pushes i32, while storing into the i64
   destination local -> the validator rejected the module
   ("local type mismatch expected=0x7e got=0x7f"), a hard fatal on any
   negated FP compare like !(a < b). Widen the i32 boolean back with
   i64.extend_i32_u when the destination is i64.

Fix (1) also resolves two latent parse-corpus cases on lane W
(rv64_fp_nan_compare, builtin_islessgreater_unordered). Drops the three
now-obsolete .wasm.skip sidecars; 152/153 pass on all lanes (R/I/L/C/W,
O0+O1). Full toy suite 1390 pass / 0 fail; parse corpus lane W 0 fail.

Diffstat:
Mlang/wasm/cg.c | 4+++-
Msrc/arch/wasm/emit.c | 7++++++-
Dtest/parse/cases/builtin_islessgreater_unordered.wasm.skip | 1-
Dtest/toy/cases/152_fp_cmp_builtins_a.wasm.skip | 1-
Dtest/toy/cases/153_fp_cmp_negation_b.wasm.skip | 1-
5 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -997,7 +997,9 @@ static int wasm_fp_cmp_op(uint8_t kind, KitCgFpCmpOp* out) { return 1; case WASM_INSN_F32_NE: case WASM_INSN_F64_NE: - *out = KIT_CG_FP_ONE; + /* wasm f.ne is the UNORDERED not-equal: true when a != b OR either + * operand is NaN. Map to UNE, not ONE (ordered, false on NaN). */ + *out = KIT_CG_FP_UNE; return 1; case WASM_INSN_F32_LT: case WASM_INSN_F64_LT: diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -3440,11 +3440,16 @@ static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { break; } case UO_NOT: { - /* a == 0 — produces i32 0/1 */ + /* a == 0 — i{32,64}.eqz always produces an i32 0/1. When the CG + * destination is i64 (e.g. !x where x was zext'd to i64 before the + * negation), widen the i32 boolean back to i64 so the following + * local.set is well-typed. */ emit_push_operand(t, w->imm_kind, w->imm_a, w->a, w->type); emit_insn( t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQZ : WASM_INSN_I32_EQZ, 0); + if (vt == WASM_VAL_I64) + emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); break; } } diff --git a/test/parse/cases/builtin_islessgreater_unordered.wasm.skip b/test/parse/cases/builtin_islessgreater_unordered.wasm.skip @@ -1 +0,0 @@ -wasm re-lowering of the discriminating FP predicates (ONE / unordered duals) is not yet NaN-correct on the foundation; same gap fails the untouched rv64_fp_nan_compare on lane W (got 4 at ne_d). Tracked separately from the C-frontend Route A/B work. diff --git a/test/toy/cases/152_fp_cmp_builtins_a.wasm.skip b/test/toy/cases/152_fp_cmp_builtins_a.wasm.skip @@ -1 +0,0 @@ -wasm re-lowering of the discriminating FP predicates (ONE / the unordered duals synthesized by @isunordered) is not yet NaN-correct on the foundation; the same gap fails the untouched rv64_fp_nan_compare on lane W. Tracked separately from the toy Route A work. diff --git a/test/toy/cases/153_fp_cmp_negation_b.wasm.skip b/test/toy/cases/153_fp_cmp_negation_b.wasm.skip @@ -1 +0,0 @@ -wasm re-lowering of a negated FP compare (the unordered dual) is not yet correct on the foundation: `kit run` on the .wasm hits a relower `local type mismatch (expected i64 got i32)` fatal. Reproduces with a minimal `!(1.0 < 2.0)` and no builtins, so it is a foundation-level gap, not toy Route B. Tracked separately.