[llvm] [SelectionDAG] Add expansion for llvm.convert.to.arbitrary.fp (PR #193595)

Dmitry Sidorov via llvm-commits llvm-commits at lists.llvm.org
Sun May 3 15:32:34 PDT 2026


================
@@ -0,0 +1,905 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 | FileCheck %s
+
+; Test llvm.convert.to.arbitrary.fp intrinsic expansion.
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f32(float, metadata, metadata, i1)
+declare i6 @llvm.convert.to.arbitrary.fp.i6.f32(float, metadata, metadata, i1)
+declare i4 @llvm.convert.to.arbitrary.fp.i4.f32(float, metadata, metadata, i1)
+declare <4 x i4> @llvm.convert.to.arbitrary.fp.v4i4.v4f32(<4 x float>, metadata, metadata, i1)
+
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f16(half, metadata, metadata, i1)
+declare i8 @llvm.convert.to.arbitrary.fp.i8.bf16(bfloat, metadata, metadata, i1)
+declare i8 @llvm.convert.to.arbitrary.fp.i8.f64(double, metadata, metadata, i1)
+
+; Float8E5M2
+; Layout: sign(1) exp(5) mant(2), bias=15
+; Supports: Inf, NaN, signed zero, denormals
+
+; Float8E5M2 normal: f32 1.0 -> 0_01111_00 = 0x3C
+define i8 @to_f8e5m2_normal() {
+; CHECK-LABEL: to_f8e5m2_normal:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 60
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 1.0, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 zero: f32 0.0 -> 0_00000_00 = 0x00
+define i8 @to_f8e5m2_zero() {
+; CHECK-LABEL: to_f8e5m2_zero:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0.0, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 negative zero: f32 -0.0 -> 1_00000_00 = 0x80
+define i8 @to_f8e5m2_neg_zero() {
+; CHECK-LABEL: to_f8e5m2_neg_zero:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x80
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float -0.0, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 +Inf: f32 Inf -> 0_11111_00 = 0x7C
+define i8 @to_f8e5m2_inf() {
+; CHECK-LABEL: to_f8e5m2_inf:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7c
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x7FF0000000000000, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 NaN: f32 NaN -> qNaN
+define i8 @to_f8e5m2_nan() {
+; CHECK-LABEL: to_f8e5m2_nan:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7e
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x7FF8000000000000, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 max: 57344.0 -> 0_11110_11 = 0x7B
+define i8 @to_f8e5m2_max() {
+; CHECK-LABEL: to_f8e5m2_max:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7b
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 57344.0, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 overflow (no saturation): 100000.0 -> Inf = 0x7C
+define i8 @to_f8e5m2_overflow() {
+; CHECK-LABEL: to_f8e5m2_overflow:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7c
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 100000.0, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 overflow with saturation: 100000.0 -> max = 0x7B
+define i8 @to_f8e5m2_overflow_sat() {
+; CHECK-LABEL: to_f8e5m2_overflow_sat:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7b
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 100000.0, metadata !"Float8E5M2", metadata !"round.tonearest", i1 true)
+  ret i8 %r
+}
+
+; Float8E5M2 denorm: very small value -> dst denorm
+define i8 @to_f8e5m2_denorm() {
+  ; 2^(-16) = 0x37800000 -> smallest E5M2 denorm 0_00000_01 = 0x01
+; CHECK-LABEL: to_f8e5m2_denorm:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 1
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x3EF0000000000000, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E5M2 runtime arg test
+define i8 @to_f8e5m2_dynamic(float %x) {
+; CHECK-LABEL: to_f8e5m2_dynamic:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_and_b32_e32 v1, 0x7fffff, v0
+; CHECK-NEXT:    v_ffbh_u32_e32 v3, v1
+; CHECK-NEXT:    v_bfe_u32 v2, v0, 23, 8
+; CHECK-NEXT:    v_add_u32_e32 v4, -8, v3
+; CHECK-NEXT:    v_cmp_ne_u32_e32 vcc, 0, v1
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s[4:5], 0, v2
+; CHECK-NEXT:    v_lshlrev_b32_e32 v4, v4, v1
+; CHECK-NEXT:    v_and_b32_e32 v4, 0x7fffff, v4
+; CHECK-NEXT:    s_and_b64 s[4:5], s[4:5], vcc
+; CHECK-NEXT:    v_cndmask_b32_e64 v4, v1, v4, s[4:5]
+; CHECK-NEXT:    v_and_b32_e32 v6, 0xfffff, v4
+; CHECK-NEXT:    v_cmp_ne_u32_e64 s[6:7], 0, v6
+; CHECK-NEXT:    v_lshrrev_b32_e32 v5, 21, v4
+; CHECK-NEXT:    v_cndmask_b32_e64 v6, 0, 1, s[6:7]
+; CHECK-NEXT:    v_and_or_b32 v6, v5, 1, v6
+; CHECK-NEXT:    v_lshrrev_b32_e32 v7, 20, v4
+; CHECK-NEXT:    v_and_b32_e32 v6, v7, v6
+; CHECK-NEXT:    v_add_u32_e32 v5, v5, v6
+; CHECK-NEXT:    v_sub_u32_e32 v3, 9, v3
+; CHECK-NEXT:    v_cmp_lt_i32_e64 s[6:7], 3, v5
+; CHECK-NEXT:    v_cndmask_b32_e64 v3, v2, v3, s[4:5]
+; CHECK-NEXT:    v_mov_b32_e32 v6, 0xffffff90
+; CHECK-NEXT:    v_addc_co_u32_e64 v6, s[4:5], v3, v6, s[6:7]
+; CHECK-NEXT:    v_sub_u32_e32 v3, 0x86, v3
+; CHECK-NEXT:    v_min_u32_e32 v3, 31, v3
+; CHECK-NEXT:    v_or_b32_e32 v4, 0x800000, v4
+; CHECK-NEXT:    v_sub_u32_e64 v9, v3, 1 clamp
+; CHECK-NEXT:    s_movk_i32 s4, 0x80
+; CHECK-NEXT:    v_bfe_u32 v10, v4, 0, v9
+; CHECK-NEXT:    v_and_b32_sdwa v0, v0, s4 dst_sel:DWORD dst_unused:UNUSED_PAD src0_sel:BYTE_3 src1_sel:DWORD
+; CHECK-NEXT:    v_cmp_ne_u32_e64 s[4:5], 0, v10
+; CHECK-NEXT:    v_lshrrev_b32_e32 v8, v3, v4
+; CHECK-NEXT:    v_cndmask_b32_e64 v10, 0, 1, s[4:5]
+; CHECK-NEXT:    v_and_or_b32 v10, v8, 1, v10
+; CHECK-NEXT:    v_lshrrev_b32_e32 v4, v9, v4
+; CHECK-NEXT:    v_and_b32_e32 v4, v4, v10
+; CHECK-NEXT:    v_cmp_ne_u32_e64 s[4:5], 0, v3
+; CHECK-NEXT:    v_cndmask_b32_e64 v3, 0, v4, s[4:5]
+; CHECK-NEXT:    v_add_u32_e32 v3, v8, v3
+; CHECK-NEXT:    v_cmp_lt_i32_e64 s[4:5], 3, v3
+; CHECK-NEXT:    v_cndmask_b32_e64 v5, v5, 0, s[6:7]
+; CHECK-NEXT:    v_lshlrev_b32_e32 v7, 2, v6
+; CHECK-NEXT:    v_cndmask_b32_e64 v3, v3, 0, s[4:5]
+; CHECK-NEXT:    v_cndmask_b32_e64 v4, 0, 4, s[4:5]
+; CHECK-NEXT:    v_or3_b32 v7, v0, v7, v5
+; CHECK-NEXT:    v_or3_b32 v3, v0, v4, v3
+; CHECK-NEXT:    v_cmp_gt_i32_e64 s[4:5], 1, v6
+; CHECK-NEXT:    v_cndmask_b32_e64 v3, v7, v3, s[4:5]
+; CHECK-NEXT:    v_cmp_lt_i32_e64 s[4:5], 3, v5
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s[6:7], 30, v6
+; CHECK-NEXT:    s_and_b64 s[6:7], s[6:7], s[4:5]
+; CHECK-NEXT:    v_cmp_lt_i32_e64 s[4:5], 30, v6
+; CHECK-NEXT:    v_or_b32_e32 v4, 0x7c, v0
+; CHECK-NEXT:    s_or_b64 s[4:5], s[4:5], s[6:7]
+; CHECK-NEXT:    v_or_b32_e32 v5, v2, v1
+; CHECK-NEXT:    v_cndmask_b32_e64 v3, v3, v4, s[4:5]
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s[4:5], 0, v5
+; CHECK-NEXT:    s_movk_i32 s6, 0xff
+; CHECK-NEXT:    v_cndmask_b32_e64 v0, v3, v0, s[4:5]
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s[4:5], 0, v1
+; CHECK-NEXT:    v_cmp_eq_u32_e64 s[6:7], s6, v2
+; CHECK-NEXT:    s_and_b64 s[4:5], s[6:7], s[4:5]
+; CHECK-NEXT:    v_cndmask_b32_e64 v0, v0, v4, s[4:5]
+; CHECK-NEXT:    v_mov_b32_e32 v1, 0x7e
+; CHECK-NEXT:    s_and_b64 vcc, s[6:7], vcc
+; CHECK-NEXT:    v_cndmask_b32_e32 v0, v0, v1, vcc
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float %x, metadata !"Float8E5M2", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E4M3FN (NanOnly, NanEncoding=AllOnes)
+; Layout: sign(1) exp(4) mant(3), maxExp=8, minExp=-6, bias=7
+; Only 0_1111_111 and 1_1111_111 are NaN; all other exp=15 values are finite.
+
+; Float8E4M3FN normal: f32 1.0 -> 0_0111_000 = 0x38
+define i8 @to_f8e4m3fn_normal() {
+; CHECK-LABEL: to_f8e4m3fn_normal:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 56
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 1.0, metadata !"Float8E4M3FN", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E4M3FN max: 448.0 -> 0_1111_110 = 0x7E
+define i8 @to_f8e4m3fn_max() {
+; CHECK-LABEL: to_f8e4m3fn_max:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7e
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 448.0, metadata !"Float8E4M3FN", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E4M3FN NaN: f32 NaN -> 0_1111_111 = 0x7F
+define i8 @to_f8e4m3fn_nan() {
+; CHECK-LABEL: to_f8e4m3fn_nan:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7f
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 0x7FF8000000000000, metadata !"Float8E4M3FN", metadata !"round.tonearest", i1 false)
+  ret i8 %r
+}
+
+; Float8E4M3FN overflow with saturation: 1000.0 -> max = 0x7E
+define i8 @to_f8e4m3fn_overflow_sat() {
+; CHECK-LABEL: to_f8e4m3fn_overflow_sat:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0x7e
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i8 @llvm.convert.to.arbitrary.fp.i8.f32(float 1000.0, metadata !"Float8E4M3FN", metadata !"round.tonearest", i1 true)
+  ret i8 %r
+}
+
+; Float6E3M2FN (FiniteOnly)
+; Layout: sign(1) exp(3) mant(2), bias=3, maxExp=4
+; No Inf, no NaN. All bit patterns are finite.
+
+; Float6E3M2FN normal: f32 1.0 -> 0_011_00 = 0x0C
+define i6 @to_f6e3m2fn_normal() {
+; CHECK-LABEL: to_f6e3m2fn_normal:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 12
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f32(float 1.0, metadata !"Float6E3M2FN", metadata !"round.tonearest", i1 false)
+  ret i6 %r
+}
+
+; Float6E3M2FN max: 28.0 -> 0_111_11 = 0x1F
+define i6 @to_f6e3m2fn_max() {
+; CHECK-LABEL: to_f6e3m2fn_max:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 31
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f32(float 28.0, metadata !"Float6E3M2FN", metadata !"round.tonearest", i1 false)
+  ret i6 %r
+}
+
+; Float6E3M2FN zero: f32 0.0 -> 0_000_00 = 0x00
+define i6 @to_f6e3m2fn_zero() {
+; CHECK-LABEL: to_f6e3m2fn_zero:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 0
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f32(float 0.0, metadata !"Float6E3M2FN", metadata !"round.tonearest", i1 false)
+  ret i6 %r
+}
+
+; Float6E3M2FN negative: f32 -1.0 -> 1_011_00
+define i6 @to_f6e3m2fn_negative() {
+; CHECK-LABEL: to_f6e3m2fn_negative:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 44
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f32(float -1.0, metadata !"Float6E3M2FN", metadata !"round.tonearest", i1 false)
+  ret i6 %r
+}
+
+; Float6E2M3FN (FiniteOnly)
+; Layout: sign(1) exp(2) mant(3), bias=1, maxExp=2
+; No Inf, no NaN. All bit patterns are finite.
+
+; Float6E2M3FN normal: f32 1.0 -> 0_01_000 = 0x08
+define i6 @to_f6e2m3fn_normal() {
+; CHECK-LABEL: to_f6e2m3fn_normal:
+; CHECK:       ; %bb.0:
+; CHECK-NEXT:    s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0)
+; CHECK-NEXT:    v_mov_b32_e32 v0, 8
+; CHECK-NEXT:    s_setpc_b64 s[30:31]
+  %r = call i6 @llvm.convert.to.arbitrary.fp.i6.f32(float 1.0, metadata !"Float6E2M3FN", metadata !"round.tonearest", i1 false)
+  ret i6 %r
+}
+
+; Float6E2M3FN max: 7.5 -> 0_11_111 = 0x1F
+define i6 @to_f6e2m3fn_max() {
+; CHECK-LABEL: to_f6e2m3fn_max:
----------------
MrSidims wrote:

After rewriting the lowering to use IS_FPCLASS and FFREXP constant folding became broken, probably because they fold at different stages. I left FIXME in the code, will investigate later and decide if a custom constant folding should be added for conversion intrinsic (easier, but less clean) or something has to be done for (likely) IS_FPCLASS lowering (may be harder - at least I don't see the path right away, but more clean).

https://github.com/llvm/llvm-project/pull/193595


More information about the llvm-commits mailing list