Author: Adam Smith
Date: 2026-08-28T15:14:01-05:00
New Revision: 3f2274211ab60a4facdb4fe89a7fee5fa243b451

URL: 
https://github.com/llvm/llvm-project/commit/3f2274211ab60a4facdb4fe89a7fee5fa243b451
DIFF: 
https://github.com/llvm/llvm-project/commit/3f2274211ab60a4facdb4fe89a7fee5fa243b451.diff

LOG: [CIR] Skip ABI classification for an incomplete-record declaration 
(#218786)

A `cir.func` declaration whose signature carries an incomplete record by
value caused the pass to fail the whole module.

C++ requires a complete type at any call or definition, so only a
declaration can carry this shape, and no translation unit anywhere can
ever call or define it with real argument data. Classic CodeGen skips
full ABI lowering rather than attempting one. We now leave such a
declaration unclassified and match classic.

Assisted-by: Cursor / claude-opus-5

Added: 
    
clang/test/CIR/Transforms/abi-lowering/x86_64-incomplete-record-declaration.cir

Modified: 
    clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
    clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
    clang/test/CIR/Transforms/abi-lowering/declaration-rewrite.cir
    clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir

Removed: 
    


################################################################################
diff  --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 3b52e0635dd34..233b826853f76 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -136,6 +136,18 @@ static bool reachesNamedBitFieldUnit(mlir::Type ty) {
   return llvm::any_of(recTy.getMembers(), reachesNamedBitFieldUnit);
 }
 
+/// Whether \p ty, or an aggregate member/element reached by value (never
+/// through a pointer), is an incomplete record.  Such a record has no known
+/// layout, so no eightbyte classification can be built for it.
+static bool hasIncompleteRecordByValue(mlir::Type ty) {
+  if (auto recTy = dyn_cast<cir::RecordType>(ty))
+    return !recTy.isComplete() ||
+           llvm::any_of(recTy.getMembers(), hasIncompleteRecordByValue);
+  if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
+    return hasIncompleteRecordByValue(arrTy.getElementType());
+  return false;
+}
+
 /// The CIR types the x86_64 bridge handles.  Scalars: an integer up to 128
 /// bits (including `_BitInt` and `__int128`), pointer, vtable pointer, bool,
 /// void, or any floating-point type.  Aggregates: a complete struct or union
@@ -850,6 +862,15 @@ void CallConvLoweringPass::runOnOperation() {
   llvm::MapVector<cir::FuncOp, FunctionClassification> classifications;
   bool anyFailed = false;
   moduleOp.walk([&](cir::FuncOp f) {
+    // A complete type is required at any call or definition, so only a
+    // declaration can carry an incomplete-by-value parameter or return type,
+    // and no translation unit can ever call or define it with real argument
+    // data.  Leave it unclassified.
+    cir::FuncType fnTy = f.getFunctionType();
+    if (f.isDeclaration() &&
+        (hasIncompleteRecordByValue(fnTy.getReturnType()) ||
+         llvm::any_of(fnTy.getInputs(), hasIncompleteRecordByValue)))
+      return;
     std::optional<FunctionClassification> fc;
     if (isX86)
       fc = classifyX86_64Function(f, dl, *x86TypeMapper,

diff  --git a/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp 
b/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
index d1ca653f2de73..2675044179cb2 100644
--- a/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
+++ b/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
@@ -1,8 +1,6 @@
-// TODO(cir): drop -fno-clangir-call-conv-lowering once CallConvLowering
-// supports padded, packed, and over-aligned record shapes.
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir 
-fno-clangir-call-conv-lowering -emit-cir %s -o %t.cir
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t.cir
 // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir 
-fno-clangir-call-conv-lowering -emit-llvm %s -o %t-cir.ll
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o 
%t-cir.ll
 // RUN: FileCheck --check-prefix=LLVM,LLVMCIR --input-file=%t-cir.ll %s
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
 // RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s

diff  --git a/clang/test/CIR/Transforms/abi-lowering/declaration-rewrite.cir 
b/clang/test/CIR/Transforms/abi-lowering/declaration-rewrite.cir
index 2242ed65d74bc..1e6acf7e01dc1 100644
--- a/clang/test/CIR/Transforms/abi-lowering/declaration-rewrite.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/declaration-rewrite.cir
@@ -6,12 +6,18 @@
 // adaptation runs.
 
 !s32i = !cir.int<s, 32>
+!rec_Incomplete = !cir.struct<"Incomplete" incomplete>
 
 #ignore_first_arg = {
   return = { kind = "direct" },
   args   = [ { kind = "ignore" }, { kind = "direct" } ]
 }
 
+#ignore_only_arg = {
+  return = { kind = "direct" },
+  args   = [ { kind = "ignore" } ]
+}
+
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<i32, dense<32>: vector<2xi64>>>
 } {
@@ -31,4 +37,10 @@ module attributes {
   // CHECK:      cir.func{{.*}} @caller(%arg0: !s32i) -> !s32i
   // CHECK:        %[[R:.*]] = cir.call @ext_decl(%arg0) : (!s32i) -> !s32i
 
+  // "ignore" would drop the argument, but an incomplete one is left alone.
+  cir.func private @incomplete_decl(!rec_Incomplete)
+      attributes { test_classify = #ignore_only_arg }
+
+  // CHECK:      cir.func private @incomplete_decl(!rec_Incomplete)
+
 }

diff  --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
index 63697d8f6d0eb..7da3a76abc494 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
@@ -34,6 +34,7 @@
 !rec_EBits = !cir.struct<"EBits" {empty !u32i}>
 !rec_HoldsEmptyBits = !cir.struct<"HoldsEmptyBits" {empty !rec_EBits, data 
!s32i}>
 !rec_LeadPad = !cir.struct<"LeadPad" {pad !cir.array<!u8i x 8>, data !s32i}>
+!rec_Incomplete = !cir.struct<"Incomplete" incomplete>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
@@ -255,4 +256,13 @@ module attributes {
   }
 
   // CHECK: not yet implemented for type '!cir.struct<"NamedPlusZeroWidth"
+
+  // A definition (unlike a declaration) is refused: no incomplete-by-value
+  // definition can arise from valid C++, so this stays a hard reject rather
+  // than the declaration-only skip in 
x86_64-incomplete-record-declaration.cir.
+  cir.func @take_incomplete_defined(%arg0: !rec_Incomplete) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"Incomplete" incomplete>'
 }

diff  --git 
a/clang/test/CIR/Transforms/abi-lowering/x86_64-incomplete-record-declaration.cir
 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-incomplete-record-declaration.cir
new file mode 100644
index 0000000000000..1710ab9567059
--- /dev/null
+++ 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-incomplete-record-declaration.cir
@@ -0,0 +1,54 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!rec_Incomplete = !cir.struct<"Incomplete" incomplete>
+!rec_Wraps = !cir.struct<"Wraps" {data !rec_Incomplete}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
+
+module attributes {
+  dlti.dl_spec = #dlti.dl_spec<
+    #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
+} {
+
+  // Only a declaration can name an incomplete-by-value parameter: a call or
+  // a definition requires the type to be complete.  No layout means no
+  // eightbyte classification, but nothing here ever completes Incomplete
+  // either, so no call or definition of this exact signature can arise
+  // anywhere.  Left unclassified and unrewritten.
+  cir.func private @take_incomplete(!rec_Incomplete)
+
+  // CHECK: cir.func private @take_incomplete(!rec_Incomplete)
+
+  // Same reasoning for an incomplete-by-value return type.
+  cir.func private @make_incomplete() -> !rec_Incomplete
+
+  // CHECK: cir.func private @make_incomplete() -> !rec_Incomplete
+
+  // The reject propagates through an enclosing struct and through an array
+  // element, the same as any other unsupported member.
+  cir.func private @take_wrapped(!rec_Wraps)
+
+  // CHECK: cir.func private @take_wrapped(!rec_Wraps)
+
+  cir.func private @take_incomplete_array(!cir.array<!rec_Incomplete x 4>)
+
+  // CHECK: cir.func private @take_incomplete_array(!cir.array<!rec_Incomplete 
x 4>)
+
+  // A classifiable function sharing the module with an unclassifiable
+  // declaration is still rewritten: skipping one function must not abort
+  // the whole pass the way a hard NYI error would.
+  cir.func @take_pair(%arg0: !rec_Pair) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_pair(%arg0: !u64i)
+
+  cir.func @call_pair(%arg0: !rec_Pair) {
+    cir.call @take_pair(%arg0) : (!rec_Pair) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_pair(%arg0: !u64i)
+  // CHECK:   cir.call @take_pair(%{{.*}}) : (!u64i) -> ()
+}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to