Files
sx/issues/0146-int-to-float-cast-dropped-in-comparison.md
agra 7057175fb6 fix: promote mismatched comparison operands before emitting cmp (issue 0146)
A comparison with int-vs-float (or two float widths) operands emitted cmp on
the raw operands with no promotion, unlike the arithmetic arms -- producing a
mixed-type compare the LLVM verifier rejects / mis-evaluates. lowerBinaryOp now
coerces each operand to the promoted common type (from arithResultType) via
coerceToType (SIToFP / FPExt) for the ordering/equality arms when the promoted
type is a float, so LLVM gets a well-typed fcmp.

Regression: examples/0189-types-int-float-compare-promote.sx
2026-06-21 09:11:52 +03:00

2.7 KiB

0146 — xx i < t drops the int→float cast, emits a mixed-type compare

RESOLVED. lowerBinaryOp (src/ir/lower/expr.zig) now promotes the comparison operands to the common type before emitting the compare: for the ordering/equality arms, when the promoted type ty (from arithResultType) is a float, each operand whose IR type differs is coerced via coerceToType (SIToFP / FPExt). LLVM then receives same-typed operands and a well-formed fcmp, instead of a mixed-type compare the verifier rejects. Regression test: examples/0189-types-int-float-compare-promote.sx.

Summary

A comparison whose left operand is an inline int→float cast against a float right operand (if xx i < t { ... }, with i : i32 and t : f32) does NOT coerce the integer to float. The backend emits the compare with mismatched operand types and LLVM module verification fails the whole build:

LLVM verification failed: Both operands to ICmp instruction are not of the same type!
  %icmp = icmp slt i32 %load4, float %load5
error: default build pipeline failed: ComptimeVmBail: comptime emit_object: object emission failed

The same shape with == and > produces the matching icmp eq/icmp sgt variants — it is the cast in the comparison operand that is lost, not a single operator.

Minimal repro

#import "modules/std.sx";
ceil_half :: (v: f32) -> i32 {
    t := v - 0.5;
    i : i32 = xx t;
    if xx i < t {      // <-- icmp slt i32, float  — cast dropped
        i += 1;
    }
    i
}
main :: () -> i32 {
    print("{}\n", ceil_half(2.3));
    0
}

bash sx build on this fails at object emission with the verifier error above.

Expected

xx i is an i32→f32 cast; the comparison should be a float compare (fcmp), i.e. the cast must materialize before the compare. The literal value is computed correctly at comptime — only the emitted compare is malformed.

Workaround (documented, in use)

Bind the cast to a typed float local first, then compare the local:

i : i32 = xx t;
fi : f32 = xx i;       // materialize the cast into a typed local
if fi < t { i += 1; }  // float compare, both operands f32 — OK

This compiles and runs correctly. Applied in doc/selection.sx (ceil_half / floor_half_excl).

A sibling symptom in the same function family: sy := xx y + 0.5 (with y : i32, 0.5 a comptime float) infers sy as f64/double, so a later sy >= lo against an f32 lo emits fcmp oge double, float. Annotating the local explicitly (sy : f32 = xx y + 0.5) pins it to f32 and the compare matches. Likely the same missing-coercion root cause around mixed int/comptime-float expressions feeding a float local or a compare.