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
75 lines
2.7 KiB
Markdown
75 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```sx
|
|
#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:
|
|
|
|
```sx
|
|
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`).
|
|
|
|
## Related
|
|
|
|
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.
|