[clang] [llvm] [PowerPC] Enable __bf16 (PR #208598)
Ben Richard via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 9 18:01:39 PDT 2026
https://github.com/benrichard-amd created https://github.com/llvm/llvm-project/pull/208598
Hi,
This PR enables `__bf16` on PowerPC via software promotion to float32, as it does not have native `bf16` support.
Tests included.
This change was assisted by Claude.
>From 97997eb3ee2cd505d7dd2bc39b290a353b630d52 Mon Sep 17 00:00:00 2001
From: benrichard-amd <ben.richard at amd.com>
Date: Tue, 7 Jul 2026 20:41:59 +0000
Subject: [PATCH] [PowerPC] Enable __bf16 via software promotion to float32
Enable __bf16 on PowerPC to support machine learning and scientific
workloads that rely on bfloat16 arithmetic, bringing PowerPC in line
with x86 which already supports this type via software promotion.
__bf16 is now accepted on PowerPC Linux targets (powerpc, powerpc64,
powerpc64le) that have hardware floating-point, using the same
soft-promote approach as _Float16 and matching x86 behaviour without
native bf16 support.
Clang front-end (PPC.h):
- Initialize BFloat16Width, BFloat16Align, and BFloat16Format in the
PPCTargetInfo constructor so the bfloat APFloat format is correctly
reported to Clang.
- Set HasBFloat16=true in the constructor, with AIX/soft-float/SPE
targets clearing it in handleTargetFeatures().
LLVM backend (PPCISelLowering):
- Mark all extending loads and truncating stores for MVT::bf16 as
Expand. This directs TypeSoftPromoteHalf to use integer lhz/sth for
bf16 memory accesses.
- Mark BF16_TO_FP and FP_TO_BF16 as Expand for f32/f64/f128 so that
LegalizeDAG expands them to the shift+bitcast / __truncsfbf2 libcall
sequences before ISel. Without this they default to Legal and reach
ISel with no matching .td pattern, causing a "Cannot select" crash.
Tests:
- clang/test/CodeGen/PowerPC/bfloat16.c: verify the Clang frontend
accepts __bf16 and has correct sizeof/alignof.
- llvm/test/CodeGen/PowerPC/bfloat16-soft-promote.ll: verify backend
instruction selection for load/store, extend, truncate, arithmetic,
comparison, and sitofp for P8 and P10.
Assisted-by: Claude <noreply at anthropic.com>
---
clang/lib/Basic/Targets/PPC.cpp | 6 +
clang/lib/Basic/Targets/PPC.h | 3 +
clang/test/CodeGen/PowerPC/bfloat16.c | 45 +++++
llvm/lib/Target/PowerPC/PPCISelLowering.cpp | 16 ++
.../CodeGen/PowerPC/bfloat16-soft-promote.ll | 180 ++++++++++++++++++
5 files changed, 250 insertions(+)
create mode 100644 clang/test/CodeGen/PowerPC/bfloat16.c
create mode 100644 llvm/test/CodeGen/PowerPC/bfloat16-soft-promote.ll
diff --git a/clang/lib/Basic/Targets/PPC.cpp b/clang/lib/Basic/Targets/PPC.cpp
index c9a41df806aff..c1076f699d7e8 100644
--- a/clang/lib/Basic/Targets/PPC.cpp
+++ b/clang/lib/Basic/Targets/PPC.cpp
@@ -87,6 +87,12 @@ bool PPCTargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
// all.
}
+ // __bf16 is enabled by default in the constructor. Disable it on AIX
+ // (ABI not yet defined), soft-float, and SPE targets.
+ if (getTriple().isOSAIX() || FloatABI == SoftFloat || HasSPE) {
+ HasBFloat16 = false;
+ }
+
return true;
}
diff --git a/clang/lib/Basic/Targets/PPC.h b/clang/lib/Basic/Targets/PPC.h
index e3bf5072d932d..7ffe312d8ab16 100644
--- a/clang/lib/Basic/Targets/PPC.h
+++ b/clang/lib/Basic/Targets/PPC.h
@@ -83,9 +83,12 @@ class LLVM_LIBRARY_VISIBILITY PPCTargetInfo : public TargetInfo {
SuitableAlign = 128;
LongDoubleWidth = LongDoubleAlign = 128;
LongDoubleFormat = &llvm::APFloat::PPCDoubleDouble();
+ BFloat16Width = BFloat16Align = 16;
+ BFloat16Format = &llvm::APFloat::BFloat();
HasStrictFP = true;
HasIbm128 = true;
HasUnalignedAccess = true;
+ HasBFloat16 = true;
}
// Set the language option for altivec based on our value.
diff --git a/clang/test/CodeGen/PowerPC/bfloat16.c b/clang/test/CodeGen/PowerPC/bfloat16.c
new file mode 100644
index 0000000000000..47d3ed2e87dbc
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/bfloat16.c
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -triple powerpc64le-linux-gnu -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefix=PPC64LE
+// RUN: %clang_cc1 -triple powerpc-linux-gnu -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefix=PPC32
+// RUN: %clang_cc1 -triple powerpc64le-linux-gnu -target-cpu pwr10 -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefix=PPC64LE
+//
+// Test that __bf16 is accepted on PowerPC targets and that the Clang
+// frontend emits the expected bfloat IR type.
+
+// __bf16 must be accepted (no "not supported on this target" error).
+__bf16 global_bf;
+
+// PPC64LE: @global_bf = global bfloat
+// PPC32: @global_bf = global bfloat
+
+// Function signatures use bfloat type.
+__bf16 add(__bf16 a, __bf16 b) {
+ return a + b;
+// PPC64LE-LABEL: define {{.*}} bfloat @add(bfloat noundef %a, bfloat noundef %b)
+// PPC32-LABEL: define {{.*}} bfloat @add(bfloat noundef %a, bfloat noundef %b)
+}
+
+__bf16 mul(__bf16 a, __bf16 b) {
+ return a * b;
+// PPC64LE-LABEL: define {{.*}} bfloat @mul(bfloat noundef %a, bfloat noundef %b)
+// PPC32-LABEL: define {{.*}} bfloat @mul(bfloat noundef %a, bfloat noundef %b)
+}
+
+// Extend/truncate round-trips.
+float to_float(__bf16 a) {
+ return (float)a;
+// PPC64LE: fpext bfloat {{.*}} to float
+// PPC32: fpext bfloat {{.*}} to float
+}
+
+__bf16 from_float(float a) {
+ return (__bf16)a;
+// PPC64LE: fptrunc float {{.*}} to bfloat
+// PPC32: fptrunc float {{.*}} to bfloat
+}
+
+// sizeof and alignof must both be 2.
+_Static_assert(sizeof(__bf16) == 2, "sizeof(__bf16) != 2");
+_Static_assert(_Alignof(__bf16) == 2, "_Alignof(__bf16) != 2");
diff --git a/llvm/lib/Target/PowerPC/PPCISelLowering.cpp b/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
index f6c844b6428ce..9b49c0d150e37 100644
--- a/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
+++ b/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
@@ -241,6 +241,22 @@ PPCTargetLowering::PPCTargetLowering(const PPCTargetMachine &TM,
setTruncStoreAction(MVT::f128, MVT::f16, Expand);
setOperationAction(ISD::FP_TO_FP16, MVT::f128, Expand);
+ // bf16 is soft-promoted to f32 on all PowerPC targets.
+ // Loads/stores use the integer i16 path (lhz/sth) via TypeSoftPromoteHalf.
+ // BF16_TO_FP (extend) is done inline via left-shift-16.
+ // FP_TO_BF16 (truncate) is done via the __truncsfbf2 libcall.
+ //
+ // BF16_TO_FP and FP_TO_BF16 must be explicitly marked Expand so that
+ // LegalizeDAG expands them to the shift+bitcast / libcall sequences before
+ // ISel. Without this they default to Legal and reach ISel with no matching
+ // .td pattern, causing a "Cannot select" fatal error.
+ for (MVT VT : {MVT::f32, MVT::f64, MVT::f128}) {
+ setLoadExtAction(ISD::EXTLOAD, VT, MVT::bf16, Expand);
+ setTruncStoreAction(VT, MVT::bf16, Expand);
+ setOperationAction(ISD::BF16_TO_FP, VT, Expand);
+ setOperationAction(ISD::FP_TO_BF16, VT, Expand);
+ }
+
if (Subtarget.isISA3_0()) {
setLoadExtAction(ISD::EXTLOAD, MVT::f128, MVT::f16, Legal);
setLoadExtAction(ISD::EXTLOAD, MVT::f64, MVT::f16, Legal);
diff --git a/llvm/test/CodeGen/PowerPC/bfloat16-soft-promote.ll b/llvm/test/CodeGen/PowerPC/bfloat16-soft-promote.ll
new file mode 100644
index 0000000000000..0574ecf0d05a3
--- /dev/null
+++ b/llvm/test/CodeGen/PowerPC/bfloat16-soft-promote.ll
@@ -0,0 +1,180 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; Tests for __bf16 (bfloat) soft-promotion on PowerPC.
+;
+; All PowerPC targets soft-promote bf16 to f32:
+; - Loads/stores use integer lhz/sth (i16 path).
+; - BF16_TO_FP (extend) is done inline via a left-shift of 16 bits.
+; - FP_TO_BF16 (truncate) calls __truncsfbf2.
+; - Arithmetic is performed in f32 and the result truncated back to bf16.
+
+; RUN: llc -mcpu=pwr8 -mtriple=powerpc64le-unknown-unknown \
+; RUN: -verify-machineinstrs -ppc-asm-full-reg-names < %s | FileCheck %s \
+; RUN: --check-prefixes=CHECK,P8
+; RUN: llc -mcpu=pwr10 -mtriple=powerpc64le-unknown-unknown \
+; RUN: -verify-machineinstrs -ppc-asm-full-reg-names < %s | FileCheck %s \
+; RUN: --check-prefixes=CHECK,P10
+
+define void @store_bf16(bfloat %x, ptr %p) nounwind {
+; CHECK-LABEL: store_bf16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: xsmaxdp f0, f1, f1
+; CHECK-NEXT: xscvdpspn vs0, f0
+; CHECK-NEXT: mffprwz r3, f0
+; CHECK-NEXT: srwi r3, r3, 16
+; CHECK-NEXT: sth r3, 0(r4)
+; CHECK-NEXT: blr
+ store bfloat %x, ptr %p
+ ret void
+}
+
+define bfloat @load_bf16(ptr %p) nounwind {
+; CHECK-LABEL: load_bf16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lhz r3, 0(r3)
+; CHECK-NEXT: slwi r3, r3, 16
+; CHECK-NEXT: mtfprd f0, r3
+; CHECK-NEXT: xxsldwi vs0, vs0, vs0, 1
+; CHECK-NEXT: xscvspdpn f1, vs0
+; CHECK-NEXT: blr
+ %v = load bfloat, ptr %p
+ ret bfloat %v
+}
+
+define float @extend_bf16_to_f32(bfloat %a) nounwind {
+; CHECK-LABEL: extend_bf16_to_f32:
+; CHECK: # %bb.0:
+; CHECK-NEXT: blr
+ %r = fpext bfloat %a to float
+ ret float %r
+}
+
+define bfloat @trunc_f32_to_bf16(float %a) nounwind {
+; CHECK-LABEL: trunc_f32_to_bf16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: xsmaxdp f0, f1, f1
+; CHECK-NEXT: xscvdpspn vs0, f0
+; CHECK-NEXT: mffprwz r3, f0
+; CHECK-NEXT: rlwinm r3, r3, 0, 0, 15
+; CHECK-NEXT: mtfprd f0, r3
+; CHECK-NEXT: xxsldwi vs0, vs0, vs0, 1
+; CHECK-NEXT: xscvspdpn f1, vs0
+; CHECK-NEXT: blr
+ %r = fptrunc float %a to bfloat
+ ret bfloat %r
+}
+
+define bfloat @add_bf16(bfloat %a, bfloat %b) nounwind {
+; P8-LABEL: add_bf16:
+; P8: # %bb.0:
+; P8-NEXT: xsmaxdp f0, f1, f1
+; P8-NEXT: xscvdpspn vs0, f0
+; P8-NEXT: mffprwz r3, f0
+; P8-NEXT: xsmaxdp f0, f2, f2
+; P8-NEXT: xscvdpspn vs0, f0
+; P8-NEXT: rlwinm r3, r3, 0, 0, 15
+; P8-NEXT: mtfprd f1, r3
+; P8-NEXT: mffprwz r4, f0
+; P8-NEXT: rlwinm r4, r4, 0, 0, 15
+; P8-NEXT: mtfprd f0, r4
+; P8-NEXT: xxsldwi vs1, vs1, vs1, 1
+; P8-NEXT: xscvspdpn f1, vs1
+; P8-NEXT: xxsldwi vs0, vs0, vs0, 1
+; P8-NEXT: xscvspdpn f0, vs0
+; P8-NEXT: xsaddsp f0, f1, f0
+; P8-NEXT: xscvdpspn vs0, f0
+; P8-NEXT: mffprwz r3, f0
+; P8-NEXT: rlwinm r3, r3, 0, 0, 15
+; P8-NEXT: mtfprd f0, r3
+; P8-NEXT: xxsldwi vs0, vs0, vs0, 1
+; P8-NEXT: xscvspdpn f1, vs0
+; P8-NEXT: blr
+;
+; P10-LABEL: add_bf16:
+; P10: # %bb.0:
+; P10-NEXT: xsmaxdp f0, f1, f1
+; P10-NEXT: xscvdpspn vs0, f0
+; P10-NEXT: mffprwz r3, f0
+; P10-NEXT: xsmaxdp f0, f2, f2
+; P10-NEXT: rlwinm r3, r3, 0, 0, 15
+; P10-NEXT: xscvdpspn vs0, f0
+; P10-NEXT: mtfprd f1, r3
+; P10-NEXT: xxsldwi vs1, vs1, vs1, 1
+; P10-NEXT: xscvspdpn f1, vs1
+; P10-NEXT: mffprwz r4, f0
+; P10-NEXT: rlwinm r4, r4, 0, 0, 15
+; P10-NEXT: mtfprd f0, r4
+; P10-NEXT: xxsldwi vs0, vs0, vs0, 1
+; P10-NEXT: xscvspdpn f0, vs0
+; P10-NEXT: xsaddsp f0, f1, f0
+; P10-NEXT: xscvdpspn vs0, f0
+; P10-NEXT: mffprwz r3, f0
+; P10-NEXT: rlwinm r3, r3, 0, 0, 15
+; P10-NEXT: mtfprd f0, r3
+; P10-NEXT: xxsldwi vs0, vs0, vs0, 1
+; P10-NEXT: xscvspdpn f1, vs0
+; P10-NEXT: blr
+ %r = fadd bfloat %a, %b
+ ret bfloat %r
+}
+
+define i1 @cmp_bf16(bfloat %a, bfloat %b) nounwind {
+; P8-LABEL: cmp_bf16:
+; P8: # %bb.0:
+; P8-NEXT: xsmaxdp f0, f1, f1
+; P8-NEXT: xscvdpspn vs0, f0
+; P8-NEXT: mffprwz r3, f0
+; P8-NEXT: xsmaxdp f0, f2, f2
+; P8-NEXT: xscvdpspn vs0, f0
+; P8-NEXT: rlwinm r3, r3, 0, 0, 15
+; P8-NEXT: mtfprd f1, r3
+; P8-NEXT: li r3, 0
+; P8-NEXT: mffprwz r4, f0
+; P8-NEXT: rlwinm r4, r4, 0, 0, 15
+; P8-NEXT: mtfprd f0, r4
+; P8-NEXT: li r4, 1
+; P8-NEXT: xxsldwi vs1, vs1, vs1, 1
+; P8-NEXT: xscvspdpn f1, vs1
+; P8-NEXT: xxsldwi vs0, vs0, vs0, 1
+; P8-NEXT: xscvspdpn f0, vs0
+; P8-NEXT: fcmpu cr0, f1, f0
+; P8-NEXT: isellt r3, r4, r3
+; P8-NEXT: blr
+;
+; P10-LABEL: cmp_bf16:
+; P10: # %bb.0:
+; P10-NEXT: xsmaxdp f0, f1, f1
+; P10-NEXT: xscvdpspn vs0, f0
+; P10-NEXT: mffprwz r3, f0
+; P10-NEXT: xsmaxdp f0, f2, f2
+; P10-NEXT: rlwinm r3, r3, 0, 0, 15
+; P10-NEXT: xscvdpspn vs0, f0
+; P10-NEXT: mtfprd f1, r3
+; P10-NEXT: xxsldwi vs1, vs1, vs1, 1
+; P10-NEXT: xscvspdpn f1, vs1
+; P10-NEXT: mffprwz r4, f0
+; P10-NEXT: rlwinm r4, r4, 0, 0, 15
+; P10-NEXT: mtfprd f0, r4
+; P10-NEXT: xxsldwi vs0, vs0, vs0, 1
+; P10-NEXT: xscvspdpn f0, vs0
+; P10-NEXT: fcmpu cr0, f1, f0
+; P10-NEXT: setbc r3, lt
+; P10-NEXT: blr
+ %r = fcmp olt bfloat %a, %b
+ ret i1 %r
+}
+
+define bfloat @sitofp_i32_to_bf16(i32 %a) nounwind {
+; CHECK-LABEL: sitofp_i32_to_bf16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: mtfprwa f0, r3
+; CHECK-NEXT: xscvsxdsp f0, f0
+; CHECK-NEXT: xscvdpspn vs0, f0
+; CHECK-NEXT: mffprwz r3, f0
+; CHECK-NEXT: rlwinm r3, r3, 0, 0, 15
+; CHECK-NEXT: mtfprd f0, r3
+; CHECK-NEXT: xxsldwi vs0, vs0, vs0, 1
+; CHECK-NEXT: xscvspdpn f1, vs0
+; CHECK-NEXT: blr
+ %r = sitofp i32 %a to bfloat
+ ret bfloat %r
+}
More information about the cfe-commits
mailing list