[CIR] Classify empty records as Ignore in x86_64 callconv (#211078)
The x86_64 aggregate calling-convention bridge rejects a zero-field record as NYI, even though the SysV classifier already treats an empty record as NoClass/NoClass (so it returns Ignore) and the rewriter already drops arguments and returns classified Ignore. Dropping the zero-field reject in `isSupportedType` lets a C empty struct classify as Ignore: the argument slot is removed, the remaining arguments shift down, and an empty-record return lowers to void. The C++ empty class stays NYI. CIRGen lays it out as a single padded byte, which the padded reject still catches. Unions, packed, and all-float aggregates remain NYI as before. --------- Co-authored-by: Andy Kaylor <akaylor@nvidia.com>
This commit is contained in:
parent
fcbf28ffc8
commit
4fff13bfb9
3 changed files with 70 additions and 15 deletions
|
|
@ -115,13 +115,13 @@ static bool isSupportedType(mlir::Type ty) {
|
|||
// Unions and packed / padded records each need classification this bridge
|
||||
// does not implement (a union widen fixup and pad-aware eightbyte
|
||||
// classification), so reject them here and report NYI rather than
|
||||
// misclassify. Empty-for-ABI records classify as Ignore, which is also
|
||||
// deferred: a C empty struct is a zero-field record, and CIRGen lays out
|
||||
// an empty C++ class as a single padded byte (caught by the padded check).
|
||||
// misclassify. A zero-field record (a C empty struct) classifies as
|
||||
// Ignore and is dropped from the lowered signature. CIRGen lays out an
|
||||
// empty C++ class as a single padded byte, which the padded check rejects.
|
||||
// A real one-byte struct such as `{char[1]}` has a field and is not
|
||||
// padded, so it is classified normally.
|
||||
if (recTy.isUnion() || !recTy.isComplete() || recTy.getPacked() ||
|
||||
recTy.getPadded() || recTy.getMembers().empty())
|
||||
recTy.getPadded())
|
||||
return false;
|
||||
return llvm::all_of(recTy.getMembers(),
|
||||
[](mlir::Type m) { return isSupportedType(m); });
|
||||
|
|
@ -257,8 +257,8 @@ 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 has no register or stack slot. (Empty-for-ABI
|
||||
/// records are rejected by isSupportedType, so they never reach here.)
|
||||
/// Ignore: a void return has no register or stack slot, and a zero-field
|
||||
/// (empty) record is dropped from the signature.
|
||||
static std::optional<ArgClassification>
|
||||
convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
|
||||
mlir::Type origTy) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
!rec_P = !cir.struct<"P" packed {!s8i, !s32i}>
|
||||
!rec_Ov = !cir.struct<"Ov" padded {!s32i, !cir.array<!u8i x 12>}>
|
||||
!rec_E = !cir.struct<"E" padded {!u8i}>
|
||||
!rec_E0 = !cir.struct<"E0" {}>
|
||||
!rec_FF = !cir.struct<"FF" {!cir.float, !cir.float}>
|
||||
!rec_RetFF = !cir.struct<"RetFF" {!cir.float, !cir.float}>
|
||||
|
||||
|
|
@ -50,14 +49,6 @@ module attributes {
|
|||
|
||||
// CHECK: not yet implemented for type '!cir.struct<"E" padded
|
||||
|
||||
// A zero-field record (a C empty struct) also classifies as Ignore, which is
|
||||
// deferred, so it is rejected rather than passed with the argument dropped.
|
||||
cir.func @take_e0(%arg0: !rec_E0) {
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK: not yet implemented for type '!cir.struct<"E0"
|
||||
|
||||
// An all-float struct classifies to an SSE vector coerce this bridge does
|
||||
// not represent, so it is reported NYI rather than passed unchanged.
|
||||
cir.func @take_ff(%arg0: !rec_FF) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
|
||||
|
||||
!s32i = !cir.int<s, 32>
|
||||
!rec_E0 = !cir.struct<"E0" {}>
|
||||
|
||||
module attributes {
|
||||
dlti.dl_spec = #dlti.dl_spec<
|
||||
#dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
|
||||
#dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
|
||||
} {
|
||||
|
||||
// A zero-field record (a C empty struct) classifies as Ignore: the argument
|
||||
// is dropped from the lowered signature.
|
||||
cir.func @take_empty(%arg0: !rec_E0) {
|
||||
cir.return
|
||||
}
|
||||
|
||||
// CHECK: cir.func{{.*}} @take_empty()
|
||||
// CHECK-NEXT: cir.return
|
||||
|
||||
// The empty argument is dropped even when a real argument remains; the real
|
||||
// argument shifts down to the first slot.
|
||||
cir.func @take_mixed(%arg0: !rec_E0, %arg1: !s32i) -> !s32i {
|
||||
cir.return %arg1 : !s32i
|
||||
}
|
||||
|
||||
// CHECK: cir.func{{.*}} @take_mixed(%arg0: !s32i) -> !s32i
|
||||
// CHECK-NEXT: cir.return %arg0 : !s32i
|
||||
|
||||
// An empty-record return is dropped: the function returns void. The
|
||||
// local storage slot survives the rewrite; only the return is dropped.
|
||||
cir.func @ret_empty() -> !rec_E0 {
|
||||
%0 = cir.alloca "r" align(1) : !cir.ptr<!rec_E0>
|
||||
%1 = cir.load %0 : !cir.ptr<!rec_E0>, !rec_E0
|
||||
cir.return %1 : !rec_E0
|
||||
}
|
||||
|
||||
// CHECK: cir.func{{.*}} @ret_empty()
|
||||
// CHECK: cir.alloca {{.*}} !cir.ptr<!rec_E0>
|
||||
// CHECK: cir.return{{$}}
|
||||
|
||||
// The empty argument is also dropped at call sites: the caller's own empty
|
||||
// argument is dropped, and the call to take_mixed drops it too.
|
||||
cir.func @caller(%arg0: !rec_E0, %arg1: !s32i) -> !s32i {
|
||||
%0 = cir.call @take_mixed(%arg0, %arg1) : (!rec_E0, !s32i) -> !s32i
|
||||
cir.return %0 : !s32i
|
||||
}
|
||||
|
||||
// CHECK: cir.func{{.*}} @caller(%arg0: !s32i) -> !s32i
|
||||
// CHECK: cir.call @take_mixed(%arg0) : (!s32i) -> !s32i
|
||||
|
||||
// A local empty struct lives in an alloca; the alloca survives the rewrite
|
||||
// while the empty operand is dropped from the call.
|
||||
cir.func @caller_local(%arg0: !s32i) -> !s32i {
|
||||
%0 = cir.alloca "e" align(1) : !cir.ptr<!rec_E0>
|
||||
%1 = cir.load %0 : !cir.ptr<!rec_E0>, !rec_E0
|
||||
%2 = cir.call @take_mixed(%1, %arg0) : (!rec_E0, !s32i) -> !s32i
|
||||
cir.return %2 : !s32i
|
||||
}
|
||||
|
||||
// CHECK: cir.func{{.*}} @caller_local(%arg0: !s32i) -> !s32i
|
||||
// CHECK: cir.alloca "e" align(1) : !cir.ptr<!rec_E0>
|
||||
// CHECK: cir.call @take_mixed(%arg0) : (!s32i) -> !s32i
|
||||
}
|
||||
Loading…
Reference in a new issue