llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Adam Smith (adams381)
<details>
<summary>Changes</summary>
CIR lays a C++ empty class out as a record that is nothing but padding, and
isSupportedType rejected every padded record, so any signature naming one
failed the pass with an NYI. Ordinary C++ hits this constantly through tag
dispatch, allocators, and empty bases.
Implement empty C++ class support. Record emptiness as an is_empty flag on
cir.record_layout, filled by CIRGen from the AST, and have the pass read that
flag rather than inspect the type. The type cannot answer on its own, because
`struct { int : 3; }` and `struct { unsigned char c; }` lower to identical CIR,
yet classic CodeGen drops the first from the signature and passes the second in
a register. An empty record then maps with no fields, so the classifier drops
it on its own.
Assisted-by: Cursor / claude-opus-5
---
Patch is 49.07 KiB, truncated to 20.00 KiB below, full version:
https://github.com/llvm/llvm-project/pull/214742.diff
13 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIRAttrs.td (+12-4)
- (modified) clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp (+4-1)
- (modified) clang/lib/CIR/CodeGen/TargetInfo.cpp (+56)
- (modified) clang/lib/CIR/CodeGen/TargetInfo.h (+14)
- (modified) clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp (+57-13)
- (added) clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp (+149)
- (modified) clang/test/CIR/CodeGen/record-type-metadata.cpp (+58-4)
- (modified) clang/test/CIR/IR/invalid-record-layout.cir (+2-2)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
(+80-4)
- (added) clang/test/CIR/Transforms/abi-lowering/x86_64-empty-class.cir (+248)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir
(+1-1)
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir (+6-3)
- (modified) clang/unittests/CIR/RecordTypeMetadataTest.cpp (+19-5)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 71585cd83fb66..a10db4fa2419f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -143,6 +143,10 @@ def CIR_RecordLayoutAttr : CIR_Attr<"RecordLayout",
"record_layout", [
- `record_align_in_bytes`: from `ASTRecordLayout::getAlignment()`.
Needed because CIR's DataLayout cannot account for
`__attribute__((aligned(N)))`.
+ - `is_empty`: whether the record carries no data for argument passing
+ (mirrors `isEmptyRecordForABI`). The record type cannot answer this on
+ its own, because an empty class and a one-byte record of data can lower
+ to the same members.
Example:
```
@@ -151,11 +155,13 @@ def CIR_RecordLayoutAttr : CIR_Attr<"RecordLayout",
"record_layout", [
"Trivial" = #cir.record_layout<
arg_passing_kind = can_pass_in_regs,
has_trivial_dtor = true,
- record_align = 4>,
+ record_align = 4,
+ is_empty = false>,
"NonTrivialDtor" = #cir.record_layout<
arg_passing_kind = cannot_pass_in_regs,
has_trivial_dtor = false,
- record_align = 4>
+ record_align = 4,
+ is_empty = false>
}
}
```
@@ -164,14 +170,16 @@ def CIR_RecordLayoutAttr : CIR_Attr<"RecordLayout",
"record_layout", [
let parameters = (ins
EnumParameter<CIR_ArgPassingKind>:$arg_passing_kind,
"bool":$has_trivial_dtor,
- "uint64_t":$record_align
+ "uint64_t":$record_align,
+ "bool":$is_empty
);
let assemblyFormat = [{
`<`
`arg_passing_kind` `=` $arg_passing_kind `,`
`has_trivial_dtor` `=` $has_trivial_dtor `,`
- `record_align` `=` $record_align
+ `record_align` `=` $record_align `,`
+ `is_empty` `=` $is_empty
`>`
}];
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index e4476b88ec6f7..9d12b14db4d91 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -13,6 +13,7 @@
#include "CIRGenBuilder.h"
#include "CIRGenModule.h"
#include "CIRGenTypes.h"
+#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
@@ -759,10 +760,12 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd,
cir::RecordType *ty) {
hasTrivialDestructor = cxxRD->hasTrivialDestructor();
const auto &astLayout = astContext.getASTRecordLayout(rd);
uint64_t recordAlignInBytes = astLayout.getAlignment().getQuantity();
+ bool isEmpty =
+ isEmptyRecordForABI(astContext, astContext.getCanonicalTagType(rd));
cgm.addRecordLayout(ty->getName(), cir::RecordLayoutAttr::get(
mlirCtx, apk, hasTrivialDestructor,
- recordAlignInBytes));
+ recordAlignInBytes, isEmpty));
}
auto rl = std::make_unique<CIRGenRecordLayout>(
diff --git a/clang/lib/CIR/CodeGen/TargetInfo.cpp
b/clang/lib/CIR/CodeGen/TargetInfo.cpp
index ba7eeb29dd252..6847761ab5e5a 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.cpp
+++ b/clang/lib/CIR/CodeGen/TargetInfo.cpp
@@ -45,6 +45,62 @@ bool clang::CIRGen::isEmptyFieldForLayout(const ASTContext
&context,
return isEmptyRecordForLayout(context, fd->getType());
}
+bool clang::CIRGen::isEmptyFieldForABI(const ASTContext &context,
+ const FieldDecl *fd) {
+ if (fd->isUnnamedBitField())
+ return true;
+
+ QualType ft = fd->getType();
+
+ // An array of empty records is empty, and a zero-length array always is.
+ bool wasArray = false;
+ while (const ConstantArrayType *at = context.getAsConstantArrayType(ft)) {
+ if (at->isZeroSize())
+ return true;
+ ft = at->getElementType();
+ wasArray = true;
+ }
+
+ const auto *rt = ft->getAsCanonical<RecordType>();
+ if (!rt)
+ return false;
+
+ // A C++ record field is never empty under the Itanium ABI unless
+ // [[no_unique_address]] makes it so, and that exception covers a record
+ // rather than an array of them.
+ if (isa<CXXRecordDecl>(rt->getDecl()) &&
+ (wasArray || !fd->hasAttr<NoUniqueAddressAttr>()))
+ return false;
+
+ return isEmptyRecordForABI(context, ft);
+}
+
+bool clang::CIRGen::isEmptyRecordForABI(const ASTContext &context, QualType t)
{
+ const auto *rd = t->getAsRecordDecl();
+ if (!rd)
+ return false;
+ if (rd->hasFlexibleArrayMember())
+ return false;
+
+ if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd)) {
+ // A vtable pointer is neither a base nor a field, so clang's predicate
+ // calls a polymorphic class empty and leans on its callers rejecting one
as
+ // non-trivially-copyable beforehand. This answer is recorded as metadata
+ // and read without that precondition, so rule it out here instead.
+ if (cxxrd->isDynamicClass())
+ return false;
+
+ for (const auto &i : cxxrd->bases())
+ if (!isEmptyRecordForABI(context, i.getType()))
+ return false;
+ }
+
+ for (const auto *i : rd->fields())
+ if (!isEmptyFieldForABI(context, i))
+ return false;
+ return true;
+}
+
namespace {
class AMDGPUABIInfo : public ABIInfo {
diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h
b/clang/lib/CIR/CodeGen/TargetInfo.h
index 308d472234f99..fca7df45a627f 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.h
+++ b/clang/lib/CIR/CodeGen/TargetInfo.h
@@ -37,6 +37,20 @@ bool isEmptyFieldForLayout(const ASTContext &context, const
FieldDecl *fd);
/// if the [[no_unique_address]] attribute would have made them empty.
bool isEmptyRecordForLayout(const ASTContext &context, QualType t);
+/// isEmptyFieldForABI - Return true if the field is "empty" for argument
+/// passing. An unnamed bit-field of any width qualifies, as does an empty
+/// record, though a C++ one only under [[no_unique_address]] and never through
+/// an array of them. This differs from isEmptyFieldForLayout, which counts
+/// only a zero-width bit-field, because a narrower unnamed bit-field still
+/// occupies no ABI class even though it takes up layout space.
+bool isEmptyFieldForABI(const ASTContext &context, const FieldDecl *fd);
+
+/// isEmptyRecordForABI - Return true if a record contains only empty base
+/// classes and fields, and so contributes no data to argument passing. What
+/// that means for a signature is the caller's to decide: an empty record small
+/// enough is dropped, but one past two eightbytes is still passed indirectly.
+bool isEmptyRecordForABI(const ASTContext &context, QualType t);
+
class CIRGenFunction;
class TargetCIRGenInfo {
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 193c2b6f4a9dc..bd53d4e9bcaa8 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -69,8 +69,9 @@ namespace {
// dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
// consumes. Integer (including `_BitInt` up to 128 bits) / pointer / bool /
// f32 / f64 scalars and struct / union / array aggregates are handled.
-// `_Complex`, vectors, wider floats, packed or padded records, and a union no
-// member of which spans its declared size are reported NYI by
+// `_Complex`, vectors, wider floats, packed records, padded records the layout
+// metadata does not call empty, a union no member of which spans its declared
+// size, and a union with an empty member are reported NYI by
// classifyX86_64Function so an unsupported signature fails the pass instead of
// being misclassified.
//===----------------------------------------------------------------------===//
@@ -86,6 +87,27 @@ static bool recordCanPassInRegs(ModuleOp modOp,
cir::RecordType recTy) {
return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs;
}
+/// Whether a record carries no data for argument passing. The answer has to
+/// come from the module's record-layout metadata, because an empty C++ class
+/// and a record holding one byte of data lower to the same members. A record
+/// with no entry, such as one CXXABILowering synthesized, is treated as
+/// carrying data.
+static bool recordIsEmpty(ModuleOp modOp, cir::RecordType recTy) {
+ auto layout = cir::tryGetRecordLayout(modOp, recTy.getName());
+ return layout && layout.getIsEmpty();
+}
+
+/// Whether a union member holds no data, either because it is an empty record
+/// or an array of them. An array is stripped because the ABI library reduces
+/// a union to a single member and coerces from that member's bytes, and it
+/// cannot see that an array of empty records supplies none.
+static bool unionMemberIsEmpty(ModuleOp modOp, mlir::Type ty) {
+ while (auto arrTy = dyn_cast<cir::ArrayType>(ty))
+ ty = arrTy.getElementType();
+ auto recTy = dyn_cast<cir::RecordType>(ty);
+ return recTy && recordIsEmpty(modOp, recTy);
+}
+
/// A record's declared alignment, which the ABI uses for the byval and sret
/// alignment of an indirect argument. DataLayout derives alignment from the
/// members, so it cannot see `__attribute__((aligned(N)))`. The declared
value
@@ -105,7 +127,8 @@ static llvm::Align recordDeclaredAlign(ModuleOp modOp,
cir::RecordType recTy,
/// Aggregates: a complete struct or union whose members are all themselves
/// supported, or an array of a supported element type. Everything else is
/// reported NYI at the reject() choke point in classifyX86_64Function.
-static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
+static bool isSupportedType(mlir::Type ty, const DataLayout &dl,
+ ModuleOp modOp) {
// A pointer is only handled in the default address space (null) or an
// already-lowered target address space. A LangAddressSpaceAttr must be
// lowered before this pass, so reject it rather than silently dropping it.
@@ -130,7 +153,7 @@ static bool isSupportedType(mlir::Type ty, const DataLayout
&dl) {
return intTy.getWidth() <= 64 || intTy.getWidth() == 128;
}
if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
- return isSupportedType(arrTy.getElementType(), dl);
+ return isSupportedType(arrTy.getElementType(), dl, modOp);
if (auto recTy = dyn_cast<cir::RecordType>(ty)) {
// An incomplete record has no layout to classify, and a packed one needs
// pad-aware eightbyte classification this bridge does not implement.
@@ -156,14 +179,25 @@ static bool isSupportedType(mlir::Type ty, const
DataLayout &dl) {
};
if (!llvm::any_of(members, spansRecord))
return false;
+ // Classic sizes a union's coercion from the bytes that hold data, so
an
+ // empty member contributes none. The library instead reduces the
union
+ // to one member, picked by alignment and then by size, and coerces
from
+ // that member: an empty one can win either comparison and widen the
+ // coercion past what classic emits.
+ auto isEmptyMember = [&](mlir::Type m) {
+ return unionMemberIsEmpty(modOp, m);
+ };
+ if (llvm::any_of(members, isEmptyMember))
+ return false;
}
- } else if (recTy.getPadded()) {
- // A struct's padding is a member the classifier would have to recognize
- // as padding rather than data, which is not implemented.
+ } else if (recTy.getPadded() && !recordIsEmpty(modOp, recTy)) {
+ // Padding the classifier would have to tell apart from data is not
+ // implemented. An empty record has no data to confuse it with.
return false;
}
- return llvm::all_of(recTy.getMembers(),
- [&](mlir::Type m) { return isSupportedType(m, dl); });
+ return llvm::all_of(recTy.getMembers(), [&](mlir::Type m) {
+ return isSupportedType(m, dl, modOp);
+ });
}
return false;
}
@@ -251,6 +285,16 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
llvm::TypeSize sizeBits = llvm::TypeSize::getFixed(
dl.getTypeSizeInBits(type).getFixedValue());
llvm::Align align = recordDeclaredAlign(modOp, recTy, dl);
+
+ // An empty record holds no user data, so map it with no fields and let
+ // the classifier reach Ignore on its own. The size still decides
+ // between that and the memory class, which an empty class past two
+ // eightbytes lands in.
+ if (recordIsEmpty(modOp, recTy))
+ return tb.getRecordType(
+ /*Fields=*/{}, sizeBits, align,
llvm::abi::StructPacking::Default,
+ /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{}, flags);
+
SmallVector<llvm::abi::FieldInfo> fields;
fields.reserve(recTy.getMembers().size());
@@ -265,8 +309,8 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
llvm::abi::StructPacking::Default, flags);
}
- // isSupportedType rejects packed and padded structs, so every field
- // here sits at its naturally-aligned offset.
+ // An accepted non-empty struct is never padded, so every field here
+ // sits at its naturally-aligned offset.
uint64_t offsetBits = 0;
for (mlir::Type fieldTy : recTy.getMembers()) {
const llvm::abi::Type *mappedField =
@@ -307,7 +351,7 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
/// Indirect: an aggregate that does not fit in registers is passed via a
/// pointer (sret for returns, byval for arguments).
///
-/// Ignore: a void return, or a zero-field record dropped from the signature.
+/// Ignore: a void return, or an empty record dropped from the signature.
static std::optional<ArgClassification>
convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
mlir::Type origTy) {
@@ -388,7 +432,7 @@ static std::optional<FunctionClassification>
classifyX86_64Signature(
bool voidRet = isa<cir::VoidType>(retCIR);
auto reject = [&](mlir::Type t) -> bool {
- if (isSupportedType(t, dl))
+ if (isSupportedType(t, dl, modOp))
return false;
emitError()
<< "x86_64 calling-convention lowering not yet implemented for type "
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
new file mode 100644
index 0000000000000..3927a8ce3b8ae
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
@@ -0,0 +1,149 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir \
+// RUN: -clangir-enable-call-conv-lowering -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 -std=c++17 -fclangir \
+// RUN: -clangir-enable-call-conv-lowering -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefixes=LLVM,LLVM-CIR --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -emit-llvm %s
-o %t.ll
+// RUN: FileCheck --check-prefixes=LLVM,LLVM-OGCG --input-file=%t.ll %s
+
+struct Empty {};
+struct EmptyMem { Empty e; };
+struct HasEmptyBase : Empty {};
+struct Derived : EmptyMem { int i; };
+struct Aligned {} __attribute__((aligned(16)));
+struct NoUnique { [[no_unique_address]] Empty a, b, c; };
+struct NoUniqueOne { [[no_unique_address]] Empty e; };
+struct UnnamedBits { int : 3; };
+struct Reserved { unsigned : 32; };
+struct OneByte { unsigned char c; };
+struct ArrOfEmpty { Empty a[2]; };
+struct HasEmpty { int x; Empty e; };
+struct EmptyFirst { Empty e; int x; };
+struct alignas(32) Big32 {};
+union UBits { unsigned : 3; };
+union UNone {};
+
+// An empty class is passed in no register at all.
+int takeEmpty(Empty v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z9takeEmpty5Emptyi(%arg0: !s32i {{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 @_Z9takeEmpty5Emptyi(i32 noundef
%{{[^,]+}})
+
+// A plain empty member leaves the record non-empty under the Itanium rule, so
+// this is dropped because the member contributes no eightbyte.
+int takeEmptyMem(EmptyMem v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z12takeEmptyMem8EmptyMemi(%arg0: !s32i {{.*}}) ->
(!s32i
+// LLVM: define dso_local noundef i32 @_Z12takeEmptyMem8EmptyMemi(i32 noundef
%{{[^,]+}})
+
+// Emptiness does follow a base class.
+int takeHasEmptyBase(HasEmptyBase v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z16takeHasEmptyBase12HasEmptyBasei(%arg0: !s32i
{{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 @_Z16takeHasEmptyBase12HasEmptyBasei(i32
noundef %{{[^,]+}})
+
+// The empty base takes layout space, so the int sits at offset 4 and the
+// eightbyte covering both coerces to i64.
+int takeDerived(Derived v) { return v.i; }
+
+// CIR: cir.func {{.*}}@_Z11takeDerived7Derived(%arg0: !u64i {{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 @_Z11takeDerived7Derived(i64 %{{[^,]+}})
+
+// An alignment attribute widens the padding, and several [[no_unique_address]]
+// members do too, but neither makes the record carry data.
+int takeAligned(Aligned v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z11takeAligned7Alignedi(%arg0: !s32i {{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 @_Z11takeAligned7Alignedi(i32 noundef
%{{[^,]+}})
+
+int takeNoUnique(NoUnique v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z12takeNoUnique8NoUniquei(%arg0: !s32i {{.*}}) ->
(!s32i
+// LLVM: define dso_local noundef i32 @_Z12takeNoUnique8NoUniquei(i32 noundef
%{{[^,]+}})
+
+// [[no_unique_address]] is what lets a single empty member count as empty.
+int takeNoUniqueOne(NoUniqueOne v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z15takeNoUniqueOne11NoUniqueOnei(%arg0: !s32i {{.*}})
-> (!s32i
+// LLVM: define dso_local noundef i32 @_Z15takeNoUniqueOne11NoUniqueOnei(i32
noundef %{{[^,]+}})
+
+// A record holding only unnamed bit-fields carries no data either, though it
+// occupies layout space. It lowers to the same CIR type as OneByte below, so
+// the record-layout metadata is what separates them.
+int takeUnnamedBits(UnnamedBits v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z15takeUnnamedBits11UnnamedBitsi(%arg0: !s32i {{.*}})
-> (!s32i
+// LLVM: define dso_local noundef i32 @_Z15takeUnnamedBits11UnnamedBitsi(i32
noundef %{{[^,]+}})
+
+int takeReserved(Reserved v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z12takeReserved8Reservedi(%arg0: !s32i {{.*}}) ->
(!s32i
+// LLVM: define dso_local noundef i32 @_Z12takeReserved8Reservedi(i32 noundef
%{{[^,]+}})
+
+// A byte of real data keeps its register.
+int takeOneByte(OneByte v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z11takeOneByte7OneBytei(%arg0: !u8i {{.*}}, %arg1:
!s32i {{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 @_Z11takeOneByte7OneBytei(i8 %{{[^,]+}},
i32 noundef %{{[^,]+}})
+
+// An array of empty records leaves its record non-empty, and the array still
+// contributes no eightbyte.
+int takeArrOfEmpty(ArrOfEmpty v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z14takeArrOfEmpty10ArrOfEmptyi(%arg0: !s32i {{.*}})
-> (!s32i
+// LLVM: define dso_local noundef i32 @_Z14takeArrOfEmpty10ArrOfEmptyi(i32
noundef %{{[^,]+}})
+
+// An empty member contributes no eightbyte, so only the int is classified.
+int takeHasEmpty(HasEmpty v) { return v.x; }
+
+// CIR: cir.func {{.*}}@_Z12takeHasEmpty8HasEmpty(%arg0: !s32i {{.*}}) ->
(!s32i
+// LLVM: define dso_local noundef i32 @_Z12takeHasEmpty8HasEmpty(i32
%{{[^,]+}})
+
+int takeEmptyFirst(EmptyFirst v) { return v.x; }
+
+// CIR: cir.func {{.*}}@_Z14takeEmptyFirst10EmptyFirst(%arg0: !u64i {{.*}}) ->
(!s32i
+// LLVM: define dso_local noundef i32 @_Z14takeEmptyFirst10EmptyFirst(i64
%{{[^,]+}})
+
+// Past two eightbytes SysV says memory whatever the content, so an empty class
+// this size is passed indirectly at its declared alignment.
+int takeBig32(Big32 v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z9takeBig325Big32i(%arg0: !cir.ptr<!rec_Big32>
{llvm.align = 32 : i64, llvm.byval = !rec_Big32, llvm.noalias,
llvm.noundef}{{.*}}, %arg1: !s32i {{.*}}) -> (!s32i
+// LLVM-CIR: define dso_local noundef i32 @_Z9takeBig325Big32i(ptr noalias
noundef byval(%struct.Big32) align 32 %{{[^,]+}}, i32 noundef %{{[^,]+}})
+/...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/214742
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits