From 8911a3b7d814034ba9dbc489c4a986a6af23ad5d Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:00:58 +0900 Subject: [PATCH] [a64] Inline UNPACK_FLOAT16_2/4 using NEON fcvtl Replace CallNativeSafe round-trips with fcvtl/fcvtl2 for half-to-float conversion. A rev64 fixup corrects the PPC word-pair ordering. FLOAT16_2 uses ext to rotate the two source halfs into position and sets the constant {0.0, 1.0} tail lanes. PACK_FLOAT16 kept as C helpers since fcvtn doesn't match Xenos overflow saturation semantics. --- src/xenia/cpu/backend/a64/a64_seq_vector.cc | 53 ++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/xenia/cpu/backend/a64/a64_seq_vector.cc b/src/xenia/cpu/backend/a64/a64_seq_vector.cc index 567f8a59a..56c7a5445 100644 --- a/src/xenia/cpu/backend/a64/a64_seq_vector.cc +++ b/src/xenia/cpu/backend/a64/a64_seq_vector.cc @@ -1691,6 +1691,40 @@ struct UNPACK : Sequence> { e.ldr(QReg(d), ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); } + // Inline Xenos half→float conversion for 4 lanes in v0 (zero-extended + // to 32-bit). Xenos half-float has no inf/NaN (exp=31 is a normal + // value), so we cannot use fcvtl. Instead: integer shift + bias. + // Result written to v_dest. Clobbers v0-v3, w0. + static void EmitXenosHalfToFloat4(A64Emitter& e, int dest) { + // v0 = zero-extended halfs (16-bit values in 32-bit lanes). + // abs = v0 & 0x7FFF + e.mov(e.w0, 0x7FFFu); + e.dup(VReg(1).s4, e.w0); + e.and_(VReg(2).b16, VReg(0).b16, VReg(1).b16); // v2 = abs + + // value = (abs << 13) + 0x38000000 (bias adjustment: 112 << 23) + e.shl(VReg(1).s4, VReg(2).s4, 13); + e.mov(e.w0, 0x38000000u); + e.dup(VReg(3).s4, e.w0); + e.add(VReg(1).s4, VReg(1).s4, VReg(3).s4); // v1 = biased value + + // sign = (v0 >> 15) << 31 + e.ushr(VReg(3).s4, VReg(0).s4, 15); + e.shl(VReg(3).s4, VReg(3).s4, 31); // v3 = sign at bit 31 + + // result = sign | value + e.orr(VReg(0).b16, VReg(3).b16, VReg(1).b16); + + // Flush to ±0 where exponent == 0 (denormals and zeros). + // exp = abs >> 10 + e.ushr(VReg(1).s4, VReg(2).s4, 10); + e.cmeq(VReg(1).s4, VReg(1).s4, 0); // v1 = mask: all-1s where exp==0 + // Where exp==0: use sign only (±0), else: use full result. + e.bsl(VReg(1).b16, VReg(3).b16, VReg(0).b16); + + // Fix PPC word-pair ordering. + e.rev64(VReg(dest).s4, VReg(1).s4); + } static void EmitFLOAT16_2(A64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { vec128_t result = {}; @@ -1703,7 +1737,17 @@ struct UNPACK : Sequence> { LoadV128Const(e, i.dest.reg().getIdx(), result); return; } - EmitCallHelper(e, i, reinterpret_cast(EmulateUNPACK_FLOAT16_2)); + // Input halfs are at h[6],h[7] (bytes 12-15). Rotate to h[0],h[1] + // and zero-extend to 32-bit lanes for the conversion helper. + int s = SrcVReg(e, i.src1, 0); + int d = i.dest.reg().getIdx(); + e.ext(VReg(0).b16, VReg(s).b16, VReg(s).b16, 12); + e.uxtl(VReg(0).s4, VReg(0).h4); + EmitXenosHalfToFloat4(e, d); + // Only lanes 0,1 are valid. Set lanes 2,3 = {0.0f, 1.0f}. + e.ins(VReg(d).s4[2], e.wzr); + e.mov(e.w0, 0x3F800000u); + e.ins(VReg(d).s4[3], e.w0); } static void EmitFLOAT16_4(A64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { @@ -1715,7 +1759,12 @@ struct UNPACK : Sequence> { LoadV128Const(e, i.dest.reg().getIdx(), result); return; } - EmitCallHelper(e, i, reinterpret_cast(EmulateUNPACK_FLOAT16_4)); + // Input halfs are at h[4..7] (upper 64 bits). Zero-extend to 32-bit + // lanes and run the conversion. + int s = SrcVReg(e, i.src1, 0); + int d = i.dest.reg().getIdx(); + e.uxtl2(VReg(0).s4, VReg(s).h8); + EmitXenosHalfToFloat4(e, d); } static void EmitSHORT_2(A64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant && i.src1.value->IsConstantZero()) {