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:
parent
3719b6c0dd
commit
9719dc4111
11 changed files with 878 additions and 81 deletions
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue