[llvm-branch-commits] [clang] [CIR] Support direct-in-registers aggregates and array coercions in call-conv lowering (PR #220207)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Sep 1 02:50:47 PDT 2026


https://github.com/skc7 created https://github.com/llvm/llvm-project/pull/220207

**Summary:**
- The AMDGPU classifier can pass or return an aggregate directly in registers and can pack a 33-64 bit aggregate
into a [2 x i32] register pair. The CallConvLowering bridge handled neither, so such signatures hit the NYI path. Handle both in convertABIArgInfo/abiTypeToCIR.

**Changes:**
- A Direct classification with a null coerce type is now always a pass-through, for aggregates as well as scalars. Previously only scalars were passed through. An aggregate fell into the coercion path and reported NYI.
- `abiTypeToCIR` gains an `ArrayType` case, mapping an `llvm::abi::ArrayType` coerce to a `cir::ArrayType` so the AMDGPU [2 x i32] pack is representable.
- Added cir-opt coverage for 64-bit struct coerced to [2 x i32] and larger struct passed directly in registers.
- Dropped the `-fno-clangir-call-conv-lowering` opt-out from `amdgcn-buffer-rsrc-type.hip`.

Assisted by: Claude Opus 4.8

>From 6a65e58b022eaccf03f3a33c1194d00342927d2f Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Tue, 1 Sep 2026 15:01:27 +0530
Subject: [PATCH] [CIR] Support direct-in-registers aggregates and array
 coercions in call-conv lowering

---
 .../Transforms/CallConvLoweringPass.cpp       | 17 +++++++++++---
 .../CodeGenHIP/amdgcn-buffer-rsrc-type.hip    |  7 ++----
 .../abi-lowering/amdgpu-scalars.cir           | 22 +++++++++++++++++++
 3 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index c1c69947d55c5..c808810391a8d 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -297,6 +297,12 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
         return cir::VectorType::get(elemCIR,
                                     vecTy->getNumElements().getFixedValue());
       })
+      .Case([&](const llvm::abi::ArrayType *arrTy) -> mlir::Type {
+        mlir::Type elemCIR = abiTypeToCIR(arrTy->getElementType(), ctx);
+        if (!elemCIR)
+          return nullptr;
+        return cir::ArrayType::get(elemCIR, arrTy->getNumElements());
+      })
       .Case([&](const llvm::abi::RecordType *recTy) -> mlir::Type {
         SmallVector<mlir::Type> fieldTypes;
         fieldTypes.reserve(recTy->getFields().size());
@@ -455,9 +461,11 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
 /// split into a tuple of them, and a scalar the classifier widens to fill its
 /// eightbyte.  getDirect keeps canFlatten set so the rewriter can split a
 /// multi-field coerced struct into individual wire arguments.  Any other scalar
-/// passes in its natural CIR type, which a null coercion denotes.  A coercion
-/// this bridge cannot represent yields std::nullopt so the caller reports NYI
-/// rather than silently passing the value unchanged.
+/// passes in its natural CIR type, which a null coercion denotes. An aggregate
+/// with a null coercion is likewise passed directly in registers, which the
+/// target backend splits.  A coercion this bridge cannot represent yields
+/// std::nullopt so the caller reports NYI rather than silently passing the
+/// value unchanged.
 ///
 /// Extend: bool or a sub-register integer needs a signext/zeroext attribute.
 /// The x86_64 classifier (llvm/lib/ABI/Targets/X86.cpp) only returns Extend
@@ -480,6 +488,9 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
     // The classifier names a coerce type even where it matches the natural
     // type, so a non-null coerce does not by itself mean a rewrite is needed.
     const llvm::abi::Type *coerceAbi = info.getCoerceToType();
+    // A null coerce is always a pass-through.
+    if (!coerceAbi)
+      return ArgClassification::getDirect(nullptr);
     bool isAggregate = isa_and_present<cir::RecordType, cir::ArrayType>(origTy);
     // For a _Complex or a vector the classifier's coerce is only sometimes the
     // natural type, so it has to be read rather than assumed.
diff --git a/clang/test/CIR/CodeGenHIP/amdgcn-buffer-rsrc-type.hip b/clang/test/CIR/CodeGenHIP/amdgcn-buffer-rsrc-type.hip
index 0045a731c579f..dbc0b36095d10 100644
--- a/clang/test/CIR/CodeGenHIP/amdgcn-buffer-rsrc-type.hip
+++ b/clang/test/CIR/CodeGenHIP/amdgcn-buffer-rsrc-type.hip
@@ -2,15 +2,12 @@
 
 // REQUIRES: amdgpu-registered-target
 
-// TODO(cir): drop -fno-clangir-call-conv-lowering once CallConvLowering
-// supports the AMDGPU direct-in-registers aggregate return of a buffer
-// resource struct.
 // RUN: %clang_cc1 -triple amdgpu11.00-amd-amdhsa -x hip -std=c++11 -fclangir \
-// RUN: -fcuda-is-device -fno-clangir-call-conv-lowering -emit-cir %s -o %t.cir
+// RUN: -fcuda-is-device -emit-cir %s -o %t.cir
 // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
 
 // RUN: %clang_cc1 -triple amdgpu11.00-amd-amdhsa -x hip -std=c++11 -fclangir \
-// RUN: -fcuda-is-device -fno-clangir-call-conv-lowering -emit-llvm %s -o %t-cir.ll
+// RUN: -fcuda-is-device -emit-llvm %s -o %t-cir.ll
 // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
 
 // RUN: %clang_cc1 -triple amdgpu11.00-amd-amdhsa -x hip -std=c++11 \
diff --git a/clang/test/CIR/Transforms/abi-lowering/amdgpu-scalars.cir b/clang/test/CIR/Transforms/abi-lowering/amdgpu-scalars.cir
index 5b9049e119757..60e407ca407dd 100644
--- a/clang/test/CIR/Transforms/abi-lowering/amdgpu-scalars.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/amdgpu-scalars.cir
@@ -4,10 +4,13 @@
 !s16i = !cir.int<s, 16>
 !u16i = !cir.int<u, 16>
 !s32i = !cir.int<s, 32>
+!u32i = !cir.int<u, 32>
 !s64i = !cir.int<s, 64>
 
 !rec_E0 = !cir.struct<"E0" {}>
 !rec_Pair16 = !cir.struct<"Pair16" {data !s16i, data !s16i}>
+!rec_Pair32 = !cir.struct<"Pair32" {data !s32i, data !s32i}>
+!rec_Triple = !cir.struct<"Triple" {data !s32i, data !s32i, data !s32i}>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
@@ -95,6 +98,25 @@ module attributes {
 
   // CHECK: cir.func{{.*}} @takes_small(%{{.*}}: !u32i)
 
+  // A 33-64 bit aggregate is packed into a pair of i32 registers, coerced on
+  // the wire to a [2 x i32] array.
+  cir.func @takes_pair32(%arg0: !rec_Pair32) {
+    %0 = cir.alloca "p" align(4) : !cir.ptr<!rec_Pair32>
+    cir.store %arg0, %0 : !rec_Pair32, !cir.ptr<!rec_Pair32>
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @takes_pair32(%{{.*}}: !cir.array<!u32i x 2>)
+
+  // A larger aggregate that still fits the register budget is passed directly
+  // in registers. The signature is left unchanged and the target backend will
+  // split it into registers.
+  cir.func @takes_triple(%arg0: !rec_Triple) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @takes_triple(%arg0: !rec_Triple)
+
   // A kernel scalar argument stays Direct: the AMDGPU_KERNEL convention is
   // routed to the kernel classifier and the signature is unchanged.
   cir.func @kernel_scalar(%arg0: !s32i) cc(amdgpu_kernel) {



More information about the llvm-branch-commits mailing list