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
This commit is contained in:
agra
2026-06-21 09:11:52 +03:00
parent d4edf4b4b0
commit 7057175fb6
6 changed files with 138 additions and 0 deletions

View File

@@ -2761,6 +2761,34 @@ pub fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
}
}
// Comparison operand promotion. Arithmetic arms below carry the promoted
// common type `ty` on the result op, so the LLVM emitter re-matches the
// operands against it (`matchBinOpTypes`). Comparisons carry `.bool`
// instead, so `emitCmp`/`emitCmpOrdered` only see the raw operand LLVM
// types — and those only reconcile int↔int width (SExt/ZExt). A mixed
// int-vs-float compare (`xx i < t`, i:i32 t:f32) or a two-float-width
// compare (`f64 >= f32`) reaches the emitter with mismatched operands and
// fails LLVM verification (issue 0146). Coerce each operand up to the
// promoted common type HERE — `coerceToType` emits the SIToFP / FPExt /
// width-ext — so the operands are already type-equal when the cmp is built.
// Restricted to float `ty`: an int↔int compare is handled by the emitter,
// and a non-numeric `ty` (struct/string/enum) has its own cmp path.
switch (bop.op) {
.eq, .neq, .lt, .lte, .gt, .gte => {
if (Lowering.isFloat(ty)) {
const lhs_ir = self.builder.getRefType(lhs);
if (lhs_ir != ty and (Lowering.isFloat(lhs_ir) or self.isIntEx(lhs_ir))) {
lhs = self.coerceToType(lhs, lhs_ir, ty);
}
const rhs_ir = self.builder.getRefType(rhs);
if (rhs_ir != ty and (Lowering.isFloat(rhs_ir) or self.isIntEx(rhs_ir))) {
rhs = self.coerceToType(rhs, rhs_ir, ty);
}
}
},
else => {},
}
return switch (bop.op) {
.add => self.builder.add(lhs, rhs, ty),
.sub => self.builder.sub(lhs, rhs, ty),