[clang] [AMDGPU] Pack uniform float structs into vector types (PR #195417)

via cfe-commits cfe-commits at lists.llvm.org
Sat May 2 00:47:51 PDT 2026


https://github.com/addmisol updated https://github.com/llvm/llvm-project/pull/195417

>From c5ffb2e73bcf69513f94d8e7b89e8372d0d280b2 Mon Sep 17 00:00:00 2001
From: addmisol <218448340+addmisol at users.noreply.github.com>
Date: Fri, 6 Mar 2026 23:56:34 +0530
Subject: [PATCH 1/6] Create amdgpu-abi-struct-coerce.c

---
 .../test/CodeGen/amdgpu-abi-struct-coerce.c   | 71 +++++++++++++++++++
 1 file changed, 71 insertions(+)
 create mode 100644 clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c

diff --git a/clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c b/clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
new file mode 100644
index 0000000000000..2399630ff797b
--- /dev/null
+++ b/clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
@@ -0,0 +1,71 @@
+// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -emit-llvm -o - %s | FileCheck %s
+
+// Check that structs containing mixed float and int types are not coerced
+// to integer arrays. They should preserve the original struct type and
+// individual field types.
+
+typedef struct fp_int_pair {
+    float f;
+    int i;
+} fp_int_pair;
+
+// CHECK-LABEL: define{{.*}} %struct.fp_int_pair @return_fp_int_pair(float %x.coerce0, i32 %x.coerce1)
+// CHECK: ret %struct.fp_int_pair
+fp_int_pair return_fp_int_pair(fp_int_pair x) {
+    return x;
+}
+
+typedef struct int_fp_pair {
+    int i;
+    float f;
+} int_fp_pair;
+
+// CHECK-LABEL: define{{.*}} %struct.int_fp_pair @return_int_fp_pair(i32 %x.coerce0, float %x.coerce1)
+// CHECK: ret %struct.int_fp_pair
+int_fp_pair return_int_fp_pair(int_fp_pair x) {
+    return x;
+}
+
+typedef struct two_floats {
+    float a;
+    float b;
+} two_floats;
+
+// CHECK-LABEL: define{{.*}} %struct.two_floats @return_two_floats(float %x.coerce0, float %x.coerce1)
+// CHECK: ret %struct.two_floats
+two_floats return_two_floats(two_floats x) {
+    return x;
+}
+
+typedef struct two_ints {
+    int a;
+    int b;
+} two_ints;
+
+// CHECK-LABEL: define{{.*}} %struct.two_ints @return_two_ints(i32 %x.coerce0, i32 %x.coerce1)
+// CHECK: ret %struct.two_ints
+two_ints return_two_ints(two_ints x) {
+    return x;
+}
+
+// Structs <= 32 bits should still be coerced to i32 for return value
+typedef struct small_struct {
+    short a;
+    short b;
+} small_struct;
+
+// CHECK-LABEL: define{{.*}} i32 @return_small_struct(i16 %x.coerce0, i16 %x.coerce1)
+small_struct return_small_struct(small_struct x) {
+    return x;
+}
+
+// Structs <= 16 bits should still be coerced to i16 for return value
+typedef struct tiny_struct {
+    char a;
+    char b;
+} tiny_struct;
+
+// CHECK-LABEL: define{{.*}} i16 @return_tiny_struct(i8 %x.coerce0, i8 %x.coerce1)
+tiny_struct return_tiny_struct(tiny_struct x) {
+    return x;
+}

>From 68c200f848058ab22b3d25ce810f1639eac50556 Mon Sep 17 00:00:00 2001
From: addmisol <218448340+addmisol at users.noreply.github.com>
Date: Fri, 6 Mar 2026 23:57:11 +0530
Subject: [PATCH 2/6] Delete
 clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c

---
 .../test/CodeGen/amdgpu-abi-struct-coerce.c   | 71 -------------------
 1 file changed, 71 deletions(-)
 delete mode 100644 clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c

diff --git a/clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c b/clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
deleted file mode 100644
index 2399630ff797b..0000000000000
--- a/clang/test/CodeGen/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
+++ /dev/null
@@ -1,71 +0,0 @@
-// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -emit-llvm -o - %s | FileCheck %s
-
-// Check that structs containing mixed float and int types are not coerced
-// to integer arrays. They should preserve the original struct type and
-// individual field types.
-
-typedef struct fp_int_pair {
-    float f;
-    int i;
-} fp_int_pair;
-
-// CHECK-LABEL: define{{.*}} %struct.fp_int_pair @return_fp_int_pair(float %x.coerce0, i32 %x.coerce1)
-// CHECK: ret %struct.fp_int_pair
-fp_int_pair return_fp_int_pair(fp_int_pair x) {
-    return x;
-}
-
-typedef struct int_fp_pair {
-    int i;
-    float f;
-} int_fp_pair;
-
-// CHECK-LABEL: define{{.*}} %struct.int_fp_pair @return_int_fp_pair(i32 %x.coerce0, float %x.coerce1)
-// CHECK: ret %struct.int_fp_pair
-int_fp_pair return_int_fp_pair(int_fp_pair x) {
-    return x;
-}
-
-typedef struct two_floats {
-    float a;
-    float b;
-} two_floats;
-
-// CHECK-LABEL: define{{.*}} %struct.two_floats @return_two_floats(float %x.coerce0, float %x.coerce1)
-// CHECK: ret %struct.two_floats
-two_floats return_two_floats(two_floats x) {
-    return x;
-}
-
-typedef struct two_ints {
-    int a;
-    int b;
-} two_ints;
-
-// CHECK-LABEL: define{{.*}} %struct.two_ints @return_two_ints(i32 %x.coerce0, i32 %x.coerce1)
-// CHECK: ret %struct.two_ints
-two_ints return_two_ints(two_ints x) {
-    return x;
-}
-
-// Structs <= 32 bits should still be coerced to i32 for return value
-typedef struct small_struct {
-    short a;
-    short b;
-} small_struct;
-
-// CHECK-LABEL: define{{.*}} i32 @return_small_struct(i16 %x.coerce0, i16 %x.coerce1)
-small_struct return_small_struct(small_struct x) {
-    return x;
-}
-
-// Structs <= 16 bits should still be coerced to i16 for return value
-typedef struct tiny_struct {
-    char a;
-    char b;
-} tiny_struct;
-
-// CHECK-LABEL: define{{.*}} i16 @return_tiny_struct(i8 %x.coerce0, i8 %x.coerce1)
-tiny_struct return_tiny_struct(tiny_struct x) {
-    return x;
-}

>From a0240d7c483356ac87cae6718f0467e9c6781081 Mon Sep 17 00:00:00 2001
From: Addmisol <addmisol9 at gmail.com>
Date: Sat, 2 May 2026 11:16:00 +0530
Subject: [PATCH 3/6] Update AMDGPU.cpp

---
 clang/lib/CodeGen/Targets/AMDGPU.cpp | 98 ++++++++++++++++++++++++----
 1 file changed, 85 insertions(+), 13 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/AMDGPU.cpp b/clang/lib/CodeGen/Targets/AMDGPU.cpp
index a4b6a5fa5d35b..6fb50b47e532a 100644
--- a/clang/lib/CodeGen/Targets/AMDGPU.cpp
+++ b/clang/lib/CodeGen/Targets/AMDGPU.cpp
@@ -8,7 +8,6 @@
 
 #include "ABIInfoImpl.h"
 #include "TargetInfo.h"
-#include "clang/AST/DeclCXX.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/AMDGPUAddrSpace.h"
 
@@ -78,6 +77,67 @@ bool AMDGPUABIInfo::isHomogeneousAggregateSmallEnough(
   return Members * NumRegs <= MaxNumRegsForArgsRet;
 }
 
+/// Check if struct contains only identical float types that can be packed
+/// into a vector (e.g., {half, half} -> <2 x half>, {float, float} -> <2 x float>).
+/// Returns the vector type if packable, nullptr otherwise.
+static llvm::Type *getPackableHomogeneousFloatVectorType(const RecordDecl *RD,
+                                                          const ASTContext &Context,
+                                                          llvm::LLVMContext &VMContext) {
+  QualType FirstFloatTy;
+  unsigned Count = 0;
+
+  for (const FieldDecl *Field : RD->fields()) {
+    // No bitfields in float vector packing
+    if (Field->isBitField())
+      return nullptr;
+
+    QualType FieldTy = Field->getType();
+
+    // Must be a floating-point type
+    if (!FieldTy->isFloatingType())
+      return nullptr;
+
+    // All fields must be the same type
+    if (FirstFloatTy.isNull()) {
+      FirstFloatTy = FieldTy;
+    } else if (!Context.hasSameType(FirstFloatTy, FieldTy)) {
+      return nullptr; // Mixed float types like {half, float}
+    }
+
+    Count++;
+  }
+
+  // Only pack 2 or 4 elements (common vector sizes)
+  if (Count != 2 && Count != 4)
+    return nullptr;
+
+  // Convert QualType to LLVM Type
+  llvm::Type *EltTy = nullptr;
+  const BuiltinType *BT = FirstFloatTy->getAs<BuiltinType>();
+  if (!BT)
+    return nullptr;
+
+  switch (BT->getKind()) {
+  case BuiltinType::Half:
+  case BuiltinType::Float16:
+    EltTy = llvm::Type::getHalfTy(VMContext);
+    break;
+  case BuiltinType::BFloat16:
+    EltTy = llvm::Type::getBFloatTy(VMContext);
+    break;
+  case BuiltinType::Float:
+    EltTy = llvm::Type::getFloatTy(VMContext);
+    break;
+  case BuiltinType::Double:
+    EltTy = llvm::Type::getDoubleTy(VMContext);
+    break;
+  default:
+    return nullptr;
+  }
+
+  return llvm::FixedVectorType::get(EltTy, Count);
+}
+
 /// Check if all fields in an aggregate type contain only sub-32-bit integer
 /// types. Such aggregates should be packed into i32 registers rather than
 /// passed as individual elements. Aggregates containing floats or full-sized
@@ -218,6 +278,17 @@ ABIArgInfo AMDGPUABIInfo::classifyReturnType(QualType RetTy) const {
       uint64_t Size = getContext().getTypeSize(RetTy);
       if (Size <= 64) {
         const RecordDecl *RD = RetTy->getAsRecordDecl();
+
+        // First, try to pack uniform float structs into vectors
+        // e.g., {half, half} -> <2 x half>, {float, float} -> <2 x float>
+        if (RD) {
+          if (llvm::Type *VecTy = getPackableHomogeneousFloatVectorType(
+                  RD, getContext(), getVMContext())) {
+            return ABIArgInfo::getDirect(VecTy);
+          }
+        }
+
+        // Then, check for packable integer types
         bool ShouldPackToInt =
             RD && containsOnlyPackableIntegerTypes(RD, getContext());
 
@@ -319,6 +390,19 @@ ABIArgInfo AMDGPUABIInfo::classifyArgumentType(QualType Ty, bool Variadic,
     uint64_t Size = getContext().getTypeSize(Ty);
     if (Size <= 64) {
       const RecordDecl *RD = Ty->getAsRecordDecl();
+
+      // First, try to pack uniform float structs into vectors
+      // e.g., {half, half} -> <2 x half>, {float, float} -> <2 x float>
+      if (RD) {
+        if (llvm::Type *VecTy = getPackableHomogeneousFloatVectorType(
+                RD, getContext(), getVMContext())) {
+          unsigned NumRegs = (Size + 31) / 32;
+          NumRegsLeft -= std::min(NumRegsLeft, NumRegs);
+          return ABIArgInfo::getDirect(VecTy);
+        }
+      }
+
+      // Then, check for packable integer types
       bool ShouldPackToInt =
           RD && containsOnlyPackableIntegerTypes(RD, getContext());
 
@@ -382,9 +466,6 @@ class AMDGPUTargetCodeGenInfo : public TargetCodeGenInfo {
     return getLangASFromTargetAS(
         getABIInfo().getDataLayout().getAllocaAddrSpace());
   }
-
-  LangAS getSRetAddrSpace(const CXXRecordDecl *RD) const override;
-
   LangAS getGlobalVarAddressSpace(CodeGenModule &CGM,
                                   const VarDecl *D) const override;
   StringRef getLLVMSyncScopeStr(const LangOptions &LangOpts, SyncScope Scope,
@@ -544,15 +625,6 @@ llvm::Constant *AMDGPUTargetCodeGenInfo::getNullPointer(
       llvm::ConstantPointerNull::get(NPT), PT);
 }
 
-LangAS
-AMDGPUTargetCodeGenInfo::getSRetAddrSpace(const CXXRecordDecl *RD) const {
-  // Types with no viable copy/move must be constructed in-place , use the
-  // default AS so the sret pointer matches the "this" convention.
-  if (RD && !RD->canPassInRegisters())
-    return LangAS::Default;
-  return getASTAllocaAddressSpace();
-}
-
 LangAS
 AMDGPUTargetCodeGenInfo::getGlobalVarAddressSpace(CodeGenModule &CGM,
                                                   const VarDecl *D) const {

>From 7a525322360080a1c61ebd5f166f21ecc1e3f32d Mon Sep 17 00:00:00 2001
From: Addmisol <addmisol9 at gmail.com>
Date: Sat, 2 May 2026 11:23:09 +0530
Subject: [PATCH 4/6] Update AMDGPU.cpp

---
 clang/lib/CodeGen/Targets/AMDGPU.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/clang/lib/CodeGen/Targets/AMDGPU.cpp b/clang/lib/CodeGen/Targets/AMDGPU.cpp
index 6fb50b47e532a..f0233505c41b4 100644
--- a/clang/lib/CodeGen/Targets/AMDGPU.cpp
+++ b/clang/lib/CodeGen/Targets/AMDGPU.cpp
@@ -8,6 +8,7 @@
 
 #include "ABIInfoImpl.h"
 #include "TargetInfo.h"
+#include "clang/AST/DeclCXX.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/AMDGPUAddrSpace.h"
 
@@ -466,6 +467,9 @@ class AMDGPUTargetCodeGenInfo : public TargetCodeGenInfo {
     return getLangASFromTargetAS(
         getABIInfo().getDataLayout().getAllocaAddrSpace());
   }
+
+  LangAS getSRetAddrSpace(const CXXRecordDecl *RD) const override;
+
   LangAS getGlobalVarAddressSpace(CodeGenModule &CGM,
                                   const VarDecl *D) const override;
   StringRef getLLVMSyncScopeStr(const LangOptions &LangOpts, SyncScope Scope,
@@ -625,6 +629,15 @@ llvm::Constant *AMDGPUTargetCodeGenInfo::getNullPointer(
       llvm::ConstantPointerNull::get(NPT), PT);
 }
 
+LangAS
+AMDGPUTargetCodeGenInfo::getSRetAddrSpace(const CXXRecordDecl *RD) const {
+  // Types with no viable copy/move must be constructed in-place , use the
+  // default AS so the sret pointer matches the "this" convention.
+  if (RD && !RD->canPassInRegisters())
+    return LangAS::Default;
+  return getASTAllocaAddressSpace();
+}
+
 LangAS
 AMDGPUTargetCodeGenInfo::getGlobalVarAddressSpace(CodeGenModule &CGM,
                                                   const VarDecl *D) const {

>From 628a45dfb6eea77c3232628a86f026b3b6f46ed1 Mon Sep 17 00:00:00 2001
From: Addmisol <addmisol9 at gmail.com>
Date: Sat, 2 May 2026 11:24:29 +0530
Subject: [PATCH 5/6] Update amdgpu-abi-struct-coerce.c

---
 clang/test/CodeGen/amdgpu-abi-struct-coerce.c | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/clang/test/CodeGen/amdgpu-abi-struct-coerce.c b/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
index 2a1ebf0437f61..1092e06f97c1e 100644
--- a/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
+++ b/clang/test/CodeGen/amdgpu-abi-struct-coerce.c
@@ -38,8 +38,8 @@ typedef struct two_floats {
     float b;
 } two_floats;
 
-// CHECK-LABEL: define{{.*}} %struct.two_floats @return_two_floats(float %x.coerce0, float %x.coerce1)
-// CHECK: ret %struct.two_floats
+// Two floats can be packed into <2 x float> vector
+// CHECK-LABEL: define{{.*}} <2 x float> @return_two_floats(<2 x float> %x.coerce)
 two_floats return_two_floats(two_floats x) {
     return x;
 }
@@ -517,8 +517,8 @@ typedef struct half_struct {
 } half_struct;
 
 // Two halfs = 32 bits, but floats - should NOT be coerced
-// CHECK-LABEL: define{{.*}} %struct.half_struct @return_half_struct(half %x.coerce0, half %x.coerce1)
-// CHECK: ret %struct.half_struct
+// Two halfs = 32 bits - can be packed into <2 x half> vector
+// CHECK-LABEL: define{{.*}} <2 x half> @return_half_struct(<2 x half> %x.coerce)
 half_struct return_half_struct(half_struct x) {
     return x;
 }
@@ -540,8 +540,8 @@ typedef struct four_halfs {
 } four_halfs;
 
 // Four halfs = 64 bits - should NOT be coerced
-// CHECK-LABEL: define{{.*}} %struct.four_halfs @return_four_halfs(half %x.coerce0, half %x.coerce1, half %x.coerce2, half %x.coerce3)
-// CHECK: ret %struct.four_halfs
+// Four halfs = 64 bits - can be packed into <4 x half> vector
+// CHECK-LABEL: define{{.*}} <4 x half> @return_four_halfs(<4 x half> %x.coerce)
 four_halfs return_four_halfs(four_halfs x) {
     return x;
 }
@@ -554,8 +554,8 @@ typedef struct bfloat_struct {
 } bfloat_struct;
 
 // Two bfloats = 32 bits, but floats - should NOT be coerced
-// CHECK-LABEL: define{{.*}} %struct.bfloat_struct @return_bfloat_struct(bfloat %x.coerce0, bfloat %x.coerce1)
-// CHECK: ret %struct.bfloat_struct
+// Two bfloats = 32 bits - can be packed into <2 x bfloat> vector
+// CHECK-LABEL: define{{.*}} <2 x bfloat> @return_bfloat_struct(<2 x bfloat> %x.coerce)
 bfloat_struct return_bfloat_struct(bfloat_struct x) {
     return x;
 }
@@ -577,8 +577,8 @@ typedef struct four_bfloats {
 } four_bfloats;
 
 // Four bfloats = 64 bits - should NOT be coerced
-// CHECK-LABEL: define{{.*}} %struct.four_bfloats @return_four_bfloats(bfloat %x.coerce0, bfloat %x.coerce1, bfloat %x.coerce2, bfloat %x.coerce3)
-// CHECK: ret %struct.four_bfloats
+// Four bfloats = 64 bits - can be packed into <4 x bfloat> vector
+// CHECK-LABEL: define{{.*}} <4 x bfloat> @return_four_bfloats(<4 x bfloat> %x.coerce)
 four_bfloats return_four_bfloats(four_bfloats x) {
     return x;
 }

>From 87454a760197d9e6e4bc2fb0d2698203476444e3 Mon Sep 17 00:00:00 2001
From: Addmisol <addmisol9 at gmail.com>
Date: Sat, 2 May 2026 13:17:39 +0530
Subject: [PATCH 6/6] Update AMDGPU.cpp

---
 clang/lib/CodeGen/Targets/AMDGPU.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/AMDGPU.cpp b/clang/lib/CodeGen/Targets/AMDGPU.cpp
index f0233505c41b4..9ccd1d452ca72 100644
--- a/clang/lib/CodeGen/Targets/AMDGPU.cpp
+++ b/clang/lib/CodeGen/Targets/AMDGPU.cpp
@@ -79,11 +79,12 @@ bool AMDGPUABIInfo::isHomogeneousAggregateSmallEnough(
 }
 
 /// Check if struct contains only identical float types that can be packed
-/// into a vector (e.g., {half, half} -> <2 x half>, {float, float} -> <2 x float>).
-/// Returns the vector type if packable, nullptr otherwise.
-static llvm::Type *getPackableHomogeneousFloatVectorType(const RecordDecl *RD,
-                                                          const ASTContext &Context,
-                                                          llvm::LLVMContext &VMContext) {
+/// into a vector (e.g., {half, half} -> <2 x half>, {float, float} -> <2 x
+/// float>). Returns the vector type if packable, nullptr otherwise.
+static llvm::Type *
+getPackableHomogeneousFloatVectorType(const RecordDecl *RD,
+                                      const ASTContext &Context,
+                                      llvm::LLVMContext &VMContext) {
   QualType FirstFloatTy;
   unsigned Count = 0;
 



More information about the cfe-commits mailing list