Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/synth-backend/src/arm_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,24 @@ fn compile_wasm_to_arm(
arm_instrs
};

// VCR-RA uxth/uxtb fold (#428, #242): `movw rM,#0xffff; and rD,rN,rM` →
// `uxth rD,rN` (and the 0xff/uxtb form), removing the dead `movw` — −1
// instruction, −1 live register per 16/8-bit mask. 0xffff/0xff are not Thumb-2
// modified immediates so the selector materializes them into a register; the
// dedicated zero-extend expresses the same masking inline. Removal-only +
// rewrite-in-place (offset-neutral). FLAG-OFF by default (opt-in
// `SYNTH_UXTH_FOLD=1`) ⇒ bit-identical (frozen gate green); the byte-changing
// default-on flip is the separate on-target-gated step, like the prior levers.
let arm_instrs = if std::env::var("SYNTH_UXTH_FOLD").is_ok() {
let (out, folds) = synth_synthesis::liveness::fold_uxth(&arm_instrs);
if std::env::var("SYNTH_FUSE_STATS").is_ok() {
eprintln!("[uxth-fold] {folds} mask-and folded to uxth/uxtb, movw dropped");
}
out
} else {
arm_instrs
};

// ISA feature gate: validate that all generated instructions are supported
// by the target. This catches FPU instructions on no-FPU targets, double-precision
// instructions on single-precision targets, etc.
Expand Down
50 changes: 50 additions & 0 deletions crates/synth-backend/src/arm_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,20 @@ impl ArmEncoder {
0xE6BF0070 | (rd_bits << 12) | rm_bits
}

ArmOp::Uxtb { rd, rm } => {
let rd_bits = reg_to_bits(rd);
let rm_bits = reg_to_bits(rm);
// UXTB encoding: cond | 01101110 1111 Rd rotate 00 0111 Rm (rotate=00)
0xE6EF0070 | (rd_bits << 12) | rm_bits
}

ArmOp::Uxth { rd, rm } => {
let rd_bits = reg_to_bits(rd);
let rm_bits = reg_to_bits(rm);
// UXTH encoding: cond | 01101111 1111 Rd rotate 00 0111 Rm (rotate=00)
0xE6FF0070 | (rd_bits << 12) | rm_bits
}

// Move instructions
ArmOp::Mov { rd, op2 } => {
let rd_bits = reg_to_bits(rd);
Expand Down Expand Up @@ -2178,6 +2192,42 @@ impl ArmEncoder {
}
}

// UXTB Rd,Rm — zero-extend byte (rd = rm & 0xff)
ArmOp::Uxtb { rd, rm } => {
let rd_bits = reg_to_bits(rd) as u16;
let rm_bits = reg_to_bits(rm) as u16;
if rd_bits < 8 && rm_bits < 8 {
// UXTB Rd, Rm (16-bit): 1011 0010 11 Rm Rd
let instr: u16 = 0xB2C0 | (rm_bits << 3) | rd_bits;
Ok(instr.to_le_bytes().to_vec())
} else {
// Thumb-2 UXTB.W: FA5F F(rd)80 (rm)
let hw1: u16 = 0xFA5F;
let hw2: u16 = (0xF080 | ((rd_bits as u32) << 8) | rm_bits as u32) as u16;
let mut bytes = hw1.to_le_bytes().to_vec();
bytes.extend_from_slice(&hw2.to_le_bytes());
Ok(bytes)
}
}

// UXTH Rd,Rm — zero-extend halfword (rd = rm & 0xffff)
ArmOp::Uxth { rd, rm } => {
let rd_bits = reg_to_bits(rd) as u16;
let rm_bits = reg_to_bits(rm) as u16;
if rd_bits < 8 && rm_bits < 8 {
// UXTH Rd, Rm (16-bit): 1011 0010 10 Rm Rd
let instr: u16 = 0xB280 | (rm_bits << 3) | rd_bits;
Ok(instr.to_le_bytes().to_vec())
} else {
// Thumb-2 UXTH.W: FA1F F(rd)80 (rm)
let hw1: u16 = 0xFA1F;
let hw2: u16 = (0xF080 | ((rd_bits as u32) << 8) | rm_bits as u32) as u16;
let mut bytes = hw1.to_le_bytes().to_vec();
bytes.extend_from_slice(&hw2.to_le_bytes());
Ok(bytes)
}
}

// CMP (can be 16-bit for low registers)
ArmOp::Cmp { rn, op2 } => {
let rn_bits = reg_to_bits(rn) as u16;
Expand Down
Loading