1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-10 09:42:37 +00:00

ir/codegen: signed comparison lowering for i8/i16

Closes the §A follow-up gap: ordering compares (`<`, `<=`, `>`, `>=`)
on signed integer types now use the canonical 6502 `CMP / SBC / BVC /
EOR #$80` overflow-correction idiom so the N flag reflects the true
sign of the difference, instead of the previous BCC/BCS-based path
that always treated `$FFxx` as greater than `$00yy`.

The same change also fixes narrow-to-wide widening: assigning a
runtime `i8` expression to an `i16` variable now sign-extends the
high byte via a new `IrOp::SignExtend` op instead of zero-extending
it, so `var w: i16 = some_i8_neg` round-trips negative values.

The lowerer tracks signedness on each IR temp (analogous to the
existing `wide_hi` map) and threads it onto the new `Signedness`
field of `CmpLt`/`CmpGt`/`CmpLtEq`/`CmpGtEq` and their 16-bit
variants. The optimizer's constant-folder uses the same flag to
fold compares correctly under either signedness. Casts to `u8`/`u16`
strip the signed flag so an explicit `as` opt-out stays unsigned.

`examples/signed_compare.ne` exercises both bit widths through the
emulator harness — the four pip sprites at the top of the screen
show three lit (signed-correct) and one dark (would only light if
the compare regressed to unsigned semantics).
This commit is contained in:
Claude 2026-04-19 00:17:34 +00:00
parent 3719b6c0dd
commit 9719dc4111
No known key found for this signature in database
11 changed files with 878 additions and 81 deletions

View file

@ -3,7 +3,23 @@ mod tests;
use std::collections::{HashMap, HashSet};
use crate::ir::{IrBasicBlock, IrFunction, IrOp, IrProgram, IrTemp, IrTerminator, VarId};
use crate::ir::{
IrBasicBlock, IrFunction, IrOp, IrProgram, IrTemp, IrTerminator, Signedness, VarId,
};
/// Compare two byte-valued constants under the requested signedness.
/// Used by the constant-folding paths for the ordering comparisons —
/// when both operands have collapsed to compile-time constants, we
/// can pre-compute the boolean and replace the IR op with a
/// `LoadImm`. The signed branch reinterprets the byte through `i8`
/// so that e.g. `LoadImm 0xFF < LoadImm 0x01` folds to `1` under
/// signed semantics (-1 < 1) but `0` under unsigned (255 > 1).
fn cmp_const_signed(a: u8, b: u8, signed: Signedness) -> std::cmp::Ordering {
match signed {
Signedness::Signed => a.cast_signed().cmp(&b.cast_signed()),
Signedness::Unsigned => a.cmp(&b),
}
}
/// Run all optimization passes on the IR program.
pub fn optimize(program: &mut IrProgram) {
@ -291,30 +307,34 @@ fn const_fold_block(block: &mut IrBasicBlock, func_used: &HashSet<IrTemp>) {
constants.insert(dest, result);
}
}
IrOp::CmpLt(dest, a, b) => {
IrOp::CmpLt(dest, a, b, signed) => {
if let (Some(&va), Some(&vb)) = (constants.get(&a), constants.get(&b)) {
let result = u8::from(va < vb);
let result =
u8::from(cmp_const_signed(va, vb, signed) == std::cmp::Ordering::Less);
*op = IrOp::LoadImm(dest, result);
constants.insert(dest, result);
}
}
IrOp::CmpGt(dest, a, b) => {
IrOp::CmpGt(dest, a, b, signed) => {
if let (Some(&va), Some(&vb)) = (constants.get(&a), constants.get(&b)) {
let result = u8::from(va > vb);
let result =
u8::from(cmp_const_signed(va, vb, signed) == std::cmp::Ordering::Greater);
*op = IrOp::LoadImm(dest, result);
constants.insert(dest, result);
}
}
IrOp::CmpLtEq(dest, a, b) => {
IrOp::CmpLtEq(dest, a, b, signed) => {
if let (Some(&va), Some(&vb)) = (constants.get(&a), constants.get(&b)) {
let result = u8::from(va <= vb);
let result =
u8::from(cmp_const_signed(va, vb, signed) != std::cmp::Ordering::Greater);
*op = IrOp::LoadImm(dest, result);
constants.insert(dest, result);
}
}
IrOp::CmpGtEq(dest, a, b) => {
IrOp::CmpGtEq(dest, a, b, signed) => {
if let (Some(&va), Some(&vb)) = (constants.get(&a), constants.get(&b)) {
let result = u8::from(va >= vb);
let result =
u8::from(cmp_const_signed(va, vb, signed) != std::cmp::Ordering::Less);
*op = IrOp::LoadImm(dest, result);
constants.insert(dest, result);
}
@ -461,17 +481,17 @@ fn collect_source_temps(op: &IrOp, used: &mut HashSet<IrTemp>) {
| IrOp::ShiftRightVar(_, a, b)
| IrOp::CmpEq(_, a, b)
| IrOp::CmpNe(_, a, b)
| IrOp::CmpLt(_, a, b)
| IrOp::CmpGt(_, a, b)
| IrOp::CmpLtEq(_, a, b)
| IrOp::CmpGtEq(_, a, b) => {
| IrOp::CmpLt(_, a, b, _)
| IrOp::CmpGt(_, a, b, _)
| IrOp::CmpLtEq(_, a, b, _)
| IrOp::CmpGtEq(_, a, b, _) => {
used.insert(*a);
used.insert(*b);
}
IrOp::ShiftLeft(_, src, _) | IrOp::ShiftRight(_, src, _) => {
used.insert(*src);
}
IrOp::Negate(_, src) | IrOp::Complement(_, src) => {
IrOp::Negate(_, src) | IrOp::Complement(_, src) | IrOp::SignExtend(_, src) => {
used.insert(*src);
}
IrOp::ArrayLoad(_, _, idx) => {
@ -640,12 +660,13 @@ fn op_dest(op: &IrOp) -> Option<IrTemp> {
| IrOp::ShiftRightVar(d, _, _)
| IrOp::Negate(d, _)
| IrOp::Complement(d, _)
| IrOp::SignExtend(d, _)
| IrOp::CmpEq(d, _, _)
| IrOp::CmpNe(d, _, _)
| IrOp::CmpLt(d, _, _)
| IrOp::CmpGt(d, _, _)
| IrOp::CmpLtEq(d, _, _)
| IrOp::CmpGtEq(d, _, _)
| IrOp::CmpLt(d, _, _, _)
| IrOp::CmpGt(d, _, _, _)
| IrOp::CmpLtEq(d, _, _, _)
| IrOp::CmpGtEq(d, _, _, _)
| IrOp::ArrayLoad(d, _, _) => Some(*d),
IrOp::Call(dest, _, _) => *dest,
IrOp::ReadInput(d, _) => Some(*d),