Author: Adam Smith Date: 2026-07-31T11:20:43-05:00 New Revision: 1fe55891a7fcaac526a2b224432145e80f79dc52
URL: https://github.com/llvm/llvm-project/commit/1fe55891a7fcaac526a2b224432145e80f79dc52 DIFF: https://github.com/llvm/llvm-project/commit/1fe55891a7fcaac526a2b224432145e80f79dc52.diff LOG: [CIR] Accept _BitInt up to 128 bits in x86_64 callconv lowering (#212668) The x86_64 bridge in `CallConvLoweringPass.cpp` rejected every `_BitInt`, so a function taking or returning one reported NYI even though the ABI library already classifies these types. The classifier has to be told the integer is bit-precise, and `mapCIRType` was dropping that flag, which its two-eightbyte handling keys on. With the flag forwarded, accepting widths up to 128 is mostly a matter of letting the classifier's answer through. Wider widths stay rejected, for the reason on the accept check. Relaxing that check alone was not enough. `convertABIArgInfo` discarded the classifier's coerce for every non-aggregate, which would have left a `_BitInt(33)` as i33 where classic CodeGen passes i64. It now takes the coercion path for a multi-register tuple coerce and for one wider than the natural type. A narrower or equal coerce still means unchanged, which is what preserves a `_BitInt(128)`'s 8-byte alignment. Two `noundef` gaps against classic CodeGen remain. Neither mechanism is new, but this change is what makes both reachable for `_BitInt`. CIRGen omits the attribute for a width that is not a multiple of 8, and the rewriter attaches none to the arguments it creates when flattening, so a `_BitInt(72)` that CIRGen did mark loses it once the pair is split. Added: clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir Modified: clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir Removed: ################################################################################ diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index f3e42ac5e73b7..06dae4c541e39 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -67,11 +67,11 @@ namespace { // Maps CIR types to llvm::abi::Type, runs the LLVM ABI Lowering Library's // SysV x86_64 classifier, and converts the result back into the // dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext -// consumes. Integer / pointer / bool / f32 / f64 scalars and struct / array -// aggregates are handled; unions, `_BitInt`, `_Complex`, vectors, wider -// floats, and packed or padded records are reported NYI by -// classifyX86_64Function so an unsupported signature fails the pass instead of -// being misclassified. +// consumes. Integer (including `_BitInt` up to 128 bits) / pointer / bool / +// f32 / f64 scalars and struct / array aggregates are handled. Unions, +// `_Complex`, vectors, wider floats, and packed or padded records are reported +// NYI by classifyX86_64Function so an unsupported signature fails the pass +// instead of being misclassified. //===----------------------------------------------------------------------===// /// Whether a struct's declared argument-passing kind (from the module's @@ -92,12 +92,13 @@ static bool recordCanPassInRegs(ModuleOp modOp, cir::RecordType recTy) { return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs; } -/// The CIR types the x86_64 bridge handles. Scalars: a regular integer up to -/// 64 bits, pointer, bool, void, f32, or f64. Aggregates: a complete struct -/// whose fields are all themselves supported, or an array of a supported -/// element type. `_BitInt`, `__int128`, unions, `_Complex`, vectors, wider -/// floats, and packed or padded records are not handled and are reported NYI -/// at the reject() choke point in classifyX86_64Function. +/// The CIR types the x86_64 bridge handles. Scalars: an integer up to 128 +/// bits (including `_BitInt` and `__int128`), pointer, bool, void, f32, or f64. +/// Aggregates: a complete struct whose fields are all themselves supported, or +/// an array of a supported element type. A `_BitInt` wider than 128 bits, +/// unions, `_Complex`, vectors, wider floats, and packed or padded records are +/// not handled and are reported NYI at the reject() choke point in +/// classifyX86_64Function. static bool isSupportedType(mlir::Type ty) { // A pointer is only handled in the default address space (null) or an // already-lowered target address space. A LangAddressSpaceAttr must be @@ -108,14 +109,18 @@ static bool isSupportedType(mlir::Type ty) { if (isa<cir::VoidType, cir::BoolType, cir::SingleType, cir::DoubleType>(ty)) return true; if (auto intTy = dyn_cast<cir::IntType>(ty)) { - // Integers up to 64 bits and __int128 are Direct; convertABIArgInfo's - // scalar branch passes them through (with sign/zero extension for - // sub-register widths), matching classic CodeGen. Intermediate widths - // (65..127) do not arise from C and would need a coercion the Direct - // branch does not apply, so they stay rejected, as does _BitInt pending - // its coercion and padding handling. + // Integers up to 64 bits, __int128, and _BitInt up to 128 bits are + // handled: the classifier extends a width below 32, widens 33 through 63 + // to i64, coerces 65 through 127 to a {i64, i64} pair, and passes 32, 64, + // and 128 in the natural type. A wider _BitInt classifies Indirect, + // where at a multiple of 8 the byval attributes the rewriter appends + // duplicate the llvm.noundef CIRGen already emitted and trip the + // uniqueness assertion on the merged dictionary. The bound is a blanket + // 128 because the widths that do not collide reach that same untested + // Indirect path. Non-_BitInt intermediate widths (65..127) do not arise + // from C. Both stay rejected. if (intTy.getIsBitInt()) - return false; + return intTy.getWidth() <= 128; return intTy.getWidth() <= 64 || intTy.getWidth() == 128; } if (auto arrTy = dyn_cast<cir::ArrayType>(ty)) @@ -182,7 +187,7 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type, .Case([&](cir::IntType intTy) { return tb.getIntegerType(intTy.getWidth(), llvm::Align(dl.getTypeABIAlignment(type)), - intTy.isSigned()); + intTy.isSigned(), intTy.getIsBitInt()); }) .Case([&](cir::PointerType ptrTy) { unsigned addrSpace = 0; @@ -249,43 +254,60 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type, /// Convert an llvm::abi::ArgInfo into the ArgClassification consumed by /// CIRABIRewriteContext. /// -/// Direct: a scalar passes as-is (nullptr coercion means "same as the -/// original CIR type"). A struct or array is coerced to a register-friendly -/// type; getDirect keeps canFlatten set so the rewriter can split a -/// multi-field coerced struct into individual wire arguments. If the -/// classifier picks a coercion this bridge cannot represent (e.g. an SSE -/// <2 x float> vector), std::nullopt is returned so the caller reports NYI -/// rather than silently passing the aggregate unchanged. +/// Direct: the value passes in register(s). A coercion is forwarded in the +/// three cases where the value has to be rebuilt on the wire: an aggregate +/// unpacked into the register(s) holding it, a scalar too wide for one register +/// 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 (an SSE <2 x float>, say) 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. -/// Every ArgInfo::getExtend() call site in the x86_64 classifier -/// (llvm/lib/ABI/Targets/X86.cpp) is gated on the operand being an integer, -/// so a non-integer, non-bool origTy here would mean the classifier -/// disagreed with its own source -- asserted rather than silently handled. +/// The x86_64 classifier (llvm/lib/ABI/Targets/X86.cpp) only returns Extend +/// for an integer or bool operand, so any other origTy is asserted rather +/// than silently handled. /// /// 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, and a zero-field -/// (empty) record is dropped from the signature. +/// Ignore: a void return, or a zero-field record dropped from the signature. static std::optional<ArgClassification> convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx, mlir::Type origTy) { if (info.isDirect()) { - // A scalar passes as-is; only an aggregate carries a coercion type. - if (!origTy || !isa<cir::RecordType, cir::ArrayType>(origTy)) + // 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. + // Leaving a scalar alone also preserves its ABI alignment: abiTypeToCIR + // drops the bit-precise flag, so a _BitInt(128) routed through it would + // come back as !cir.int<s, 128> with __int128's 16-byte alignment instead + // of 8. + const llvm::abi::Type *coerceAbi = info.getCoerceToType(); + bool isAggregate = isa_and_present<cir::RecordType, cir::ArrayType>(origTy); + bool coerceIsRegisterTuple = + isa_and_present<llvm::abi::RecordType>(coerceAbi); + // Compare widths rather than identity: a coerce no wider than the natural + // type carries the same value and needs no rewrite. + auto origInt = dyn_cast_if_present<cir::IntType>(origTy); + const auto *coerceInt = + dyn_cast_if_present<llvm::abi::IntegerType>(coerceAbi); + bool coerceWidensScalar = + origInt && coerceInt && + coerceInt->getSizeInBits().getFixedValue() > origInt.getWidth(); + if (!isAggregate && !coerceIsRegisterTuple && !coerceWidensScalar) return ArgClassification::getDirect(nullptr); - // An aggregate must coerce to a type this bridge can represent. A coerce - // this bridge cannot map (an SSE vector, or a nested type it does not - // handle) yields a null type; report that as NYI instead of leaving the - // aggregate as an unchanged by-value record. - mlir::Type coerced = abiTypeToCIR(info.getCoerceToType(), ctx); + // The coerce must be a type this bridge can represent. One it cannot map + // (an SSE vector, or a nested type it does not handle) yields a null type. + // Report that as NYI instead of leaving the value as an unchanged by-value + // record. + mlir::Type coerced = abiTypeToCIR(coerceAbi, ctx); if (!coerced) return std::nullopt; return ArgClassification::getDirect(coerced); } if (info.isExtend()) { - if (origTy && isa<cir::BoolType>(origTy)) + if (isa_and_present<cir::BoolType>(origTy)) return ArgClassification::getExtend(nullptr, info.isSignExt()); assert((!origTy || isa<cir::IntType>(origTy)) && "the x86_64 classifier only returns Extend for integers and bool"); diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir new file mode 100644 index 0000000000000..13c81882e9d0b --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir @@ -0,0 +1,815 @@ +// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s +// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 -cir-to-llvm -o - 2>/dev/null \ +// RUN: | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \ +// RUN: | FileCheck %s --check-prefix=LLVM + +!b17i = !cir.int<s, 17, bitint> +!ub17i = !cir.int<u, 17, bitint> +!b32i = !cir.int<s, 32, bitint> +!b33i = !cir.int<s, 33, bitint> +!ub33i = !cir.int<u, 33, bitint> +!b63i = !cir.int<s, 63, bitint> +!b64i = !cir.int<s, 64, bitint> +!b65i = !cir.int<s, 65, bitint> +!ub65i = !cir.int<u, 65, bitint> +!b127i = !cir.int<s, 127, bitint> +!b128i = !cir.int<s, 128, bitint> +!s8i = !cir.int<s, 8> +!rec_S = !cir.struct<"S" {!b17i}> +!rec_P = !cir.struct<"P" {!b65i}> +!rec_W = !cir.struct<"W" {!b128i}> +!rec_O = !cir.struct<"O" {!b128i, !s8i}> +!rec_Arr = !cir.struct<"Arr" {!cir.array<!b65i x 2>}> + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i8, dense<8>: vector<2xi64>>, + #dlti.dl_entry<i16, dense<16>: vector<2xi64>>, + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>, + #dlti.dl_entry<i128, dense<128>: vector<2xi64>>> +} { + + // A sub-register width is Extend: signext, and no coercion in the body. + cir.func @ret_b17(%arg0: !b17i) -> !b17i { + cir.return %arg0 : !b17i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b17( + // CHECK-SAME: %arg0: !cir.int<s, 17, bitint> {llvm.signext}) + // CHECK-SAME: -> (!cir.int<s, 17, bitint> {llvm.signext}) + // CHECK-NEXT: cir.return %arg0 : !cir.int<s, 17, bitint> + + // LLVM-LABEL: define signext i17 @ret_b17( + // LLVM-SAME: i17 signext %[[ARG:[0-9]+]]) + // LLVM-NEXT: ret i17 %[[ARG]] + + // Unsigned takes zeroext instead. + cir.func @ret_ub17(%arg0: !ub17i) -> !ub17i { + cir.return %arg0 : !ub17i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_ub17( + // CHECK-SAME: %arg0: !cir.int<u, 17, bitint> {llvm.zeroext}) + // CHECK-SAME: -> (!cir.int<u, 17, bitint> {llvm.zeroext}) + // CHECK-NEXT: cir.return %arg0 : !cir.int<u, 17, bitint> + + // LLVM-LABEL: define zeroext i17 @ret_ub17( + // LLVM-SAME: i17 zeroext %[[ARG:[0-9]+]]) + // LLVM-NEXT: ret i17 %[[ARG]] + + // Already the width the classifier names: Direct, nothing to rewrite. + cir.func @ret_b32(%arg0: !b32i) -> !b32i { + cir.return %arg0 : !b32i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b32(%arg0: !s32i_bitint) -> !s32i_bitint + // CHECK-NEXT: cir.return %arg0 : !s32i_bitint + + // LLVM-LABEL: define i32 @ret_b32( + // LLVM-SAME: i32 %[[ARG:[0-9]+]]) + // LLVM-NEXT: ret i32 %[[ARG]] + + // 33 bits travels in an i64. Its five bytes are fewer than the eightbyte, + // so both coerce slots are typed i64 and the narrow views sit on the + // accesses. That memory round trip is a truncate and a re-extend in LLVM. + cir.func @ret_b33(%arg0: !b33i) -> !b33i { + cir.return %arg0 : !b33i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b33(%arg0: !u64i) -> !u64i + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[ARGSLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGVIEW:[0-9]+]] = cir.cast bitcast %[[ARGSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[ARGVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 33, bitint>>, !cir.int<s, 33, bitint> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: cir.store %[[VAL]], %[[RETVIEW]] + // CHECK-SAME: : !cir.int<s, 33, bitint>, !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[RETSLOT]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: cir.return %[[WIDE]] : !u64i + + // LLVM-LABEL: define i64 @ret_b33( + // LLVM-SAME: i64 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[RETSLOT:[0-9]+]] = alloca i64 + // LLVM-NEXT: %[[ARGSLOT:[0-9]+]] = alloca i64 + // LLVM-NEXT: store i64 %[[ARG]], ptr %[[ARGSLOT]] + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i64, ptr %[[ARGSLOT]] + // LLVM-NEXT: %[[NARROW:[0-9]+]] = trunc i64 %[[WIDE]] to i33 + // LLVM-NEXT: %[[EXT:[0-9]+]] = sext i33 %[[NARROW]] to i64 + // LLVM-NEXT: store i64 %[[EXT]], ptr %[[RETSLOT]] + // LLVM-NEXT: %[[RES:[0-9]+]] = load i64, ptr %[[RETSLOT]] + // LLVM-NEXT: ret i64 %[[RES]] + + // Signedness changes only the extension back into the eightbyte. + cir.func @ret_ub33(%arg0: !ub33i) -> !ub33i { + cir.return %arg0 : !ub33i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_ub33(%arg0: !u64i) -> !u64i + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[ARGSLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGVIEW:[0-9]+]] = cir.cast bitcast %[[ARGSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<u, 33, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[ARGVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<u, 33, bitint>>, !cir.int<u, 33, bitint> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<u, 33, bitint>> + // CHECK-NEXT: cir.store %[[VAL]], %[[RETVIEW]] + // CHECK-SAME: : !cir.int<u, 33, bitint>, !cir.ptr<!cir.int<u, 33, bitint>> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[RETSLOT]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: cir.return %[[WIDE]] : !u64i + + // LLVM-LABEL: define i64 @ret_ub33( + // LLVM-SAME: i64 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[RETSLOT:[0-9]+]] = alloca i64 + // LLVM-NEXT: %[[ARGSLOT:[0-9]+]] = alloca i64 + // LLVM-NEXT: store i64 %[[ARG]], ptr %[[ARGSLOT]] + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i64, ptr %[[ARGSLOT]] + // LLVM-NEXT: %[[NARROW:[0-9]+]] = trunc i64 %[[WIDE]] to i33 + // LLVM-NEXT: %[[EXT:[0-9]+]] = zext i33 %[[NARROW]] to i64 + // LLVM-NEXT: store i64 %[[EXT]], ptr %[[RETSLOT]] + // LLVM-NEXT: %[[RES:[0-9]+]] = load i64, ptr %[[RETSLOT]] + // LLVM-NEXT: ret i64 %[[RES]] + + // 63 bits fills the eightbyte, and the slot takes whichever type is larger, + // so here it is the bit-precise one and the i64 view moves to the load. + cir.func @ret_b63(%arg0: !b63i) -> !b63i { + cir.return %arg0 : !b63i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b63(%arg0: !u64i) -> !u64i + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!cir.int<s, 63, bitint>> + // CHECK-NEXT: %[[ARGSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[ARGSLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGVIEW:[0-9]+]] = cir.cast bitcast %[[ARGSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 63, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[ARGVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 63, bitint>>, !cir.int<s, 63, bitint> + // CHECK-NEXT: cir.store %[[VAL]], %[[RETSLOT]] + // CHECK-SAME: : !cir.int<s, 63, bitint>, !cir.ptr<!cir.int<s, 63, bitint>> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 63, bitint>> -> !cir.ptr<!u64i> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[RETVIEW]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: cir.return %[[WIDE]] : !u64i + + // LLVM-LABEL: define i64 @ret_b63( + // LLVM-SAME: i64 %[[ARG:[0-9]+]]) + // LLVM: store i64 %[[ARG]], ptr %[[ARGSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i64, ptr %[[ARGSLOT]], align 8 + // LLVM-NEXT: %[[NARROW:[0-9]+]] = trunc i64 %[[WIDE]] to i63 + // LLVM-NEXT: %[[EXT:[0-9]+]] = sext i63 %[[NARROW]] to i64 + // LLVM-NEXT: store i64 %[[EXT]], ptr %[[RETSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[RES:[0-9]+]] = load i64, ptr %[[RETSLOT]], align 8 + // LLVM-NEXT: ret i64 %[[RES]] + + // A _BitInt exactly filling one register is Direct, signature unchanged. + cir.func @ret_b64(%arg0: !b64i) -> !b64i { + cir.return %arg0 : !b64i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b64(%arg0: !s64i_bitint) -> !s64i_bitint + // CHECK-NEXT: cir.return %arg0 : !s64i_bitint + + // LLVM-LABEL: define i64 @ret_b64( + // LLVM-SAME: i64 %[[ARG:[0-9]+]]) + // LLVM-NEXT: ret i64 %[[ARG]] + + // Two INTEGER eightbytes: coerced to a {i64, i64} pair the rewriter flattens + // into two arguments, which the prologue writes back into one slot. + cir.func @ret_b65(%arg0: !b65i) -> !b65i { + cir.return %arg0 : !b65i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b65(%arg0: !u64i, %arg1: !u64i) + // CHECK-SAME: -> !rec_anon_struct + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[VALSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[E0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[E0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[E1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[E1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[PAIR]], %[[VALSLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[VALVIEW:[0-9]+]] = cir.cast bitcast %[[VALSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[VALVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 65, bitint>>, !cir.int<s, 65, bitint> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: cir.store %[[VAL]], %[[RETVIEW]] + // CHECK-SAME: : !cir.int<s, 65, bitint>, !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.return %[[WIDE]] : !rec_anon_struct + + // LLVM-LABEL: define { i64, i64 } @ret_b65( + // LLVM-SAME: i64 %[[A0:[0-9]+]], i64 %[[A1:[0-9]+]]) + // LLVM-NEXT: %[[RETSLOT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[VALSLOT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[FLAT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[E0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 0 + // LLVM-NEXT: store i64 %[[A0]], ptr %[[E0]] + // LLVM-NEXT: %[[E1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 1 + // LLVM-NEXT: store i64 %[[A1]], ptr %[[E1]] + // LLVM-NEXT: %[[PAIR:[0-9]+]] = load { i64, i64 }, ptr %[[FLAT]] + // LLVM-NEXT: store { i64, i64 } %[[PAIR]], ptr %[[VALSLOT]] + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i128, ptr %[[VALSLOT]] + // LLVM-NEXT: %[[NARROW:[0-9]+]] = trunc i128 %[[WIDE]] to i65 + // LLVM-NEXT: %[[EXT:[0-9]+]] = sext i65 %[[NARROW]] to i128 + // LLVM-NEXT: store i128 %[[EXT]], ptr %[[RETSLOT]] + // LLVM-NEXT: %[[RES:[0-9]+]] = load { i64, i64 }, ptr %[[RETSLOT]] + // LLVM-NEXT: ret { i64, i64 } %[[RES]] + + // Unsigned pairs the same way and zero-extends on the way back. + cir.func @ret_ub65(%arg0: !ub65i) -> !ub65i { + cir.return %arg0 : !ub65i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_ub65(%arg0: !u64i, %arg1: !u64i) + // CHECK-SAME: -> !rec_anon_struct + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[VALSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[E0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[E0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[E1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[E1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[PAIR]], %[[VALSLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[VALVIEW:[0-9]+]] = cir.cast bitcast %[[VALSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<u, 65, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[VALVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<u, 65, bitint>>, !cir.int<u, 65, bitint> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<u, 65, bitint>> + // CHECK-NEXT: cir.store %[[VAL]], %[[RETVIEW]] + // CHECK-SAME: : !cir.int<u, 65, bitint>, !cir.ptr<!cir.int<u, 65, bitint>> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.return %[[WIDE]] : !rec_anon_struct + + // LLVM-LABEL: define { i64, i64 } @ret_ub65( + // LLVM-SAME: i64 %[[A0:[0-9]+]], i64 %[[A1:[0-9]+]]) + // LLVM-NEXT: %[[RETSLOT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[VALSLOT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[FLAT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[E0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 0 + // LLVM-NEXT: store i64 %[[A0]], ptr %[[E0]] + // LLVM-NEXT: %[[E1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 1 + // LLVM-NEXT: store i64 %[[A1]], ptr %[[E1]] + // LLVM-NEXT: %[[PAIR:[0-9]+]] = load { i64, i64 }, ptr %[[FLAT]] + // LLVM-NEXT: store { i64, i64 } %[[PAIR]], ptr %[[VALSLOT]] + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i128, ptr %[[VALSLOT]] + // LLVM-NEXT: %[[NARROW:[0-9]+]] = trunc i128 %[[WIDE]] to i65 + // LLVM-NEXT: %[[EXT:[0-9]+]] = zext i65 %[[NARROW]] to i128 + // LLVM-NEXT: store i128 %[[EXT]], ptr %[[RETSLOT]] + // LLVM-NEXT: %[[RES:[0-9]+]] = load { i64, i64 }, ptr %[[RETSLOT]] + // LLVM-NEXT: ret { i64, i64 } %[[RES]] + + // 127 bits fills both eightbytes, so the slot is the bit-precise type again. + cir.func @ret_b127(%arg0: !b127i) -> !b127i { + cir.return %arg0 : !b127i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b127(%arg0: !u64i, %arg1: !u64i) + // CHECK-SAME: -> !rec_anon_struct + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!cir.int<s, 127, bitint>> + // CHECK-NEXT: %[[VALSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[E0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[E0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[E1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[E1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[PAIR]], %[[VALSLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[VALVIEW:[0-9]+]] = cir.cast bitcast %[[VALSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 127, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[VALVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 127, bitint>>, !cir.int<s, 127, bitint> + // CHECK-NEXT: cir.store %[[VAL]], %[[RETSLOT]] + // CHECK-SAME: : !cir.int<s, 127, bitint>, !cir.ptr<!cir.int<s, 127, bitint>> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 127, bitint>> -> !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[RETVIEW]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.return %[[WIDE]] : !rec_anon_struct + + // LLVM-LABEL: define { i64, i64 } @ret_b127( + // LLVM-SAME: i64 %[[A0:[0-9]+]], i64 %[[A1:[0-9]+]]) + // LLVM-NEXT: %[[RETSLOT:[0-9]+]] = alloca i128 + // LLVM-NEXT: %[[VALSLOT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[FLAT:[0-9]+]] = alloca { i64, i64 } + // LLVM-NEXT: %[[E0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 0 + // LLVM-NEXT: store i64 %[[A0]], ptr %[[E0]] + // LLVM-NEXT: %[[E1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 1 + // LLVM-NEXT: store i64 %[[A1]], ptr %[[E1]] + // LLVM-NEXT: %[[PAIR:[0-9]+]] = load { i64, i64 }, ptr %[[FLAT]] + // LLVM-NEXT: store { i64, i64 } %[[PAIR]], ptr %[[VALSLOT]] + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i128, ptr %[[VALSLOT]] + // LLVM-NEXT: %[[NARROW:[0-9]+]] = trunc i128 %[[WIDE]] to i127 + // LLVM-NEXT: %[[EXT:[0-9]+]] = sext i127 %[[NARROW]] to i128 + // LLVM-NEXT: store i128 %[[EXT]], ptr %[[RETSLOT]] + // LLVM-NEXT: %[[RES:[0-9]+]] = load { i64, i64 }, ptr %[[RETSLOT]] + // LLVM-NEXT: ret { i64, i64 } %[[RES]] + + // Exactly two eightbytes: passed as one i128, nothing to rewrite. + cir.func @ret_b128(%arg0: !b128i) -> !b128i { + cir.return %arg0 : !b128i + } + + // CHECK-LABEL: cir.func{{.*}} @ret_b128(%arg0: !s128i_bitint) -> !s128i_bitint + // CHECK-NEXT: cir.return %arg0 : !s128i_bitint + + // LLVM-LABEL: define i128 @ret_b128( + // LLVM-SAME: i128 %[[ARG:[0-9]+]]) + // LLVM-NEXT: ret i128 %[[ARG]] + + // A call site widens the argument and narrows the result symmetrically, so + // the truncate and extend appear on both sides of the call. + cir.func @call_b33(%arg0: !b33i) -> !b33i { + %0 = cir.call @ret_b33(%arg0) : (!b33i) -> !b33i + cir.return %0 : !b33i + } + + // CHECK-LABEL: cir.func{{.*}} @call_b33(%arg0: !u64i) -> !u64i + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: %[[INSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[INSLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[INVIEW:[0-9]+]] = cir.cast bitcast %[[INSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[INVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 33, bitint>>, !cir.int<s, 33, bitint> + // CHECK-NEXT: %[[RESSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGSLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: %[[ARGVIEW:[0-9]+]] = cir.cast bitcast %[[ARGSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: cir.store %[[VAL]], %[[ARGVIEW]] + // CHECK-SAME: : !cir.int<s, 33, bitint>, !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[WIDE:[0-9]+]] = cir.load %[[ARGSLOT]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: %[[RES:[0-9]+]] = cir.call @ret_b33(%[[WIDE]]) : (!u64i) -> !u64i + // CHECK-NEXT: cir.store %[[RES]], %[[RESSLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[RESVIEW:[0-9]+]] = cir.cast bitcast %[[RESSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[NARROW:[0-9]+]] = cir.load %[[RESVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 33, bitint>>, !cir.int<s, 33, bitint> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: cir.store %[[NARROW]], %[[RETVIEW]] + // CHECK-SAME: : !cir.int<s, 33, bitint>, !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[OUT:[0-9]+]] = cir.load %[[RETSLOT]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: cir.return %[[OUT]] : !u64i + + // LLVM-LABEL: define i64 @call_b33( + // LLVM-SAME: i64 %[[ARG:[0-9]+]]) + // LLVM: store i64 %[[ARG]], ptr %[[INSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[INWIDE:[0-9]+]] = load i64, ptr %[[INSLOT]], align 8 + // LLVM-NEXT: %[[VAL:[0-9]+]] = trunc i64 %[[INWIDE]] to i33 + // LLVM: %[[ARGEXT:[0-9]+]] = sext i33 %[[VAL]] to i64 + // LLVM-NEXT: store i64 %[[ARGEXT]], ptr %[[ARGSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i64, ptr %[[ARGSLOT]], align 8 + // LLVM-NEXT: %[[RES:[0-9]+]] = call i64 @ret_b33(i64 %[[WIDE]]) + // LLVM-NEXT: store i64 %[[RES]], ptr %[[RESSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[RESWIDE:[0-9]+]] = load i64, ptr %[[RESSLOT]], align 8 + // LLVM-NEXT: %[[RESNARROW:[0-9]+]] = trunc i64 %[[RESWIDE]] to i33 + // LLVM-NEXT: %[[RETEXT:[0-9]+]] = sext i33 %[[RESNARROW]] to i64 + // LLVM-NEXT: store i64 %[[RETEXT]], ptr %[[RETSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[OUT:[0-9]+]] = load i64, ptr %[[RETSLOT]], align 8 + // LLVM-NEXT: ret i64 %[[OUT]] + + // A call site decomposes the argument into the two registers the callee + // expects and reassembles the returned pair. + cir.func @call_b65(%arg0: !b65i) -> !b65i { + %0 = cir.call @ret_b65(%arg0) : (!b65i) -> !b65i + cir.return %0 : !b65i + } + + // CHECK-LABEL: cir.func{{.*}} @call_b65(%arg0: !u64i, %arg1: !u64i) + // CHECK-SAME: -> !rec_anon_struct + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[INSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[I0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[I0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[I1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[I1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[INPAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[INPAIR]], %[[INSLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[INVIEW:[0-9]+]] = cir.cast bitcast %[[INSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[INVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 65, bitint>>, !cir.int<s, 65, bitint> + // CHECK-NEXT: %[[RESSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[ARGSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[ARGVIEW:[0-9]+]] = cir.cast bitcast %[[ARGSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: cir.store %[[VAL]], %[[ARGVIEW]] + // CHECK-SAME: : !cir.int<s, 65, bitint>, !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[A0P:[0-9]+]] = cir.get_member %[[ARGSLOT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: %[[A0:[0-9]+]] = cir.load %[[A0P]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: %[[A1P:[0-9]+]] = cir.get_member %[[ARGSLOT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: %[[A1:[0-9]+]] = cir.load %[[A1P]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: %[[RES:[0-9]+]] = cir.call @ret_b65(%[[A0]], %[[A1]]) + // CHECK-SAME: : (!u64i, !u64i) -> !rec_anon_struct + // CHECK-NEXT: cir.store %[[RES]], %[[RESSLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[RESVIEW:[0-9]+]] = cir.cast bitcast %[[RESSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[NARROW:[0-9]+]] = cir.load %[[RESVIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 65, bitint>>, !cir.int<s, 65, bitint> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: cir.store %[[NARROW]], %[[RETVIEW]] + // CHECK-SAME: : !cir.int<s, 65, bitint>, !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[OUT:[0-9]+]] = cir.load %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.return %[[OUT]] : !rec_anon_struct + + // LLVM-LABEL: define { i64, i64 } @call_b65( + // LLVM-SAME: i64 %{{[0-9]+}}, i64 %{{[0-9]+}}) + // LLVM: %[[VAL:[0-9]+]] = trunc i128 %{{[0-9]+}} to i65 + // LLVM: %[[EXT:[0-9]+]] = sext i65 %[[VAL]] to i128 + // LLVM-NEXT: store i128 %[[EXT]], ptr %[[ARGSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[A0P:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[ARGSLOT]], i32 0, i32 0 + // LLVM-NEXT: %[[A0:[0-9]+]] = load i64, ptr %[[A0P]] + // LLVM-NEXT: %[[A1P:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[ARGSLOT]], i32 0, i32 1 + // LLVM-NEXT: %[[A1:[0-9]+]] = load i64, ptr %[[A1P]] + // LLVM-NEXT: %[[RES:[0-9]+]] = call { i64, i64 } @ret_b65(i64 %[[A0]], i64 %[[A1]]) + // LLVM-NEXT: store { i64, i64 } %[[RES]], ptr %[[RESSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[RESWIDE:[0-9]+]] = load i128, ptr %[[RESSLOT]] + // LLVM-NEXT: %[[RESNARROW:[0-9]+]] = trunc i128 %[[RESWIDE]] to i65 + // LLVM-NEXT: %[[RETEXT:[0-9]+]] = sext i65 %[[RESNARROW]] to i128 + // LLVM-NEXT: store i128 %[[RETEXT]], ptr %[[RETSLOT:[0-9]+]], align 8 + // LLVM-NEXT: %[[OUT:[0-9]+]] = load { i64, i64 }, ptr %[[RETSLOT]], align 8 + // LLVM-NEXT: ret { i64, i64 } %[[OUT]] + + // Widths that classify diff erently in one signature: the 65-bit parameter + // expands to two registers, shifting the 33-bit one to the fourth position + // while the extended one stays where it was. + cir.func @take_widths(%arg0: !b17i, %arg1: !b65i, %arg2: !b33i) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_widths( + // CHECK-SAME: %arg0: !cir.int<s, 17, bitint> {llvm.signext} + // CHECK-SAME: , %arg1: !u64i, %arg2: !u64i, %arg3: !u64i) + // CHECK-NEXT: %[[W33SLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg3, %[[W33SLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[W33VIEW:[0-9]+]] = cir.cast bitcast %[[W33SLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %{{[0-9]+}} = cir.load %[[W33VIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 33, bitint>>, !cir.int<s, 33, bitint> + // CHECK-NEXT: %[[P65SLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[E0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[E0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[E1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg2, %[[E1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[PAIR]], %[[P65SLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[P65VIEW:[0-9]+]] = cir.cast bitcast %[[P65SLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %{{[0-9]+}} = cir.load %[[P65VIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 65, bitint>>, !cir.int<s, 65, bitint> + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_widths( + // LLVM-SAME: i17 signext %{{[0-9]+}}, i64 %[[A1:[0-9]+]] + // LLVM-SAME: , i64 %[[A2:[0-9]+]], i64 %[[A3:[0-9]+]]) + // The fourth register holds the 33-bit value, materialized first. + // LLVM-NEXT: %[[W33SLOT:[0-9]+]] = alloca i64, i64 1, align 8 + // LLVM-NEXT: store i64 %[[A3]], ptr %[[W33SLOT]], align 8 + // LLVM-NEXT: %[[W33:[0-9]+]] = load i64, ptr %[[W33SLOT]], align 8 + // LLVM-NEXT: %{{[0-9]+}} = trunc i64 %[[W33]] to i33 + // The second and third rebuild the 65-bit pair in order. + // LLVM-NEXT: %[[P65SLOT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[FLAT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[E0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 0 + // LLVM-NEXT: store i64 %[[A1]], ptr %[[E0]], align 8 + // LLVM-NEXT: %[[E1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 1 + // LLVM-NEXT: store i64 %[[A2]], ptr %[[E1]], align 8 + // LLVM-NEXT: %[[PAIR:[0-9]+]] = load { i64, i64 }, ptr %[[FLAT]], align 8 + // LLVM-NEXT: store { i64, i64 } %[[PAIR]], ptr %[[P65SLOT]], align 8 + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i128, ptr %[[P65SLOT]], align 8 + // LLVM-NEXT: %{{[0-9]+}} = trunc i128 %[[WIDE]] to i65 + // LLVM-NEXT: ret void + + // Calling it maps the caller's four registers back onto the callee's four: + // the extended argument passes straight through, the pair is decomposed, and + // the 33-bit value is re-widened. + cir.func @call_widths(%arg0: !b17i, %arg1: !b65i, %arg2: !b33i) { + cir.call @take_widths(%arg0, %arg1, %arg2) : (!b17i, !b65i, !b33i) -> () + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @call_widths( + // CHECK-SAME: %arg0: !cir.int<s, 17, bitint> {llvm.signext} + // CHECK-SAME: , %arg1: !u64i, %arg2: !u64i, %arg3: !u64i) + // CHECK-NEXT: %[[V33SLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg3, %[[V33SLOT]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[V33VIEW:[0-9]+]] = cir.cast bitcast %[[V33SLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[V33:[0-9]+]] = cir.load %[[V33VIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 33, bitint>>, !cir.int<s, 33, bitint> + // CHECK-NEXT: %[[V65SLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[E0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[E0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[E1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg2, %[[E1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[PAIR]], %[[V65SLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[V65VIEW:[0-9]+]] = cir.cast bitcast %[[V65SLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[V65:[0-9]+]] = cir.load %[[V65VIEW]] + // CHECK-SAME: : !cir.ptr<!cir.int<s, 65, bitint>>, !cir.int<s, 65, bitint> + // CHECK-NEXT: %[[W33SLOT:[0-9]+]] = cir.alloca "coerce" align(8) : !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIRSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[PAIRVIEW:[0-9]+]] = cir.cast bitcast %[[PAIRSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: cir.store %[[V65]], %[[PAIRVIEW]] + // CHECK-SAME: : !cir.int<s, 65, bitint>, !cir.ptr<!cir.int<s, 65, bitint>> + // CHECK-NEXT: %[[A1P:[0-9]+]] = cir.get_member %[[PAIRSLOT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: %[[A1:[0-9]+]] = cir.load %[[A1P]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: %[[A2P:[0-9]+]] = cir.get_member %[[PAIRSLOT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: %[[A2:[0-9]+]] = cir.load %[[A2P]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: %[[W33VIEW:[0-9]+]] = cir.cast bitcast %[[W33SLOT]] + // CHECK-SAME: : !cir.ptr<!u64i> -> !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: cir.store %[[V33]], %[[W33VIEW]] + // CHECK-SAME: : !cir.int<s, 33, bitint>, !cir.ptr<!cir.int<s, 33, bitint>> + // CHECK-NEXT: %[[A3:[0-9]+]] = cir.load %[[W33SLOT]] : !cir.ptr<!u64i>, !u64i + // CHECK-NEXT: cir.call @take_widths(%arg0, %[[A1]], %[[A2]], %[[A3]]) + // CHECK-SAME: : (!cir.int<s, 17, bitint> {llvm.signext} + // CHECK-SAME: , !u64i, !u64i, !u64i) -> () + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @call_widths( + // LLVM-SAME: i17 signext %[[A0:[0-9]+]], i64 %[[A1:[0-9]+]] + // LLVM-SAME: , i64 %[[A2:[0-9]+]], i64 %[[A3:[0-9]+]]) + // LLVM-NEXT: %[[V33SLOT:[0-9]+]] = alloca i64, i64 1, align 8 + // LLVM-NEXT: store i64 %[[A3]], ptr %[[V33SLOT]], align 8 + // LLVM-NEXT: %[[W33:[0-9]+]] = load i64, ptr %[[V33SLOT]], align 8 + // LLVM-NEXT: %[[V33:[0-9]+]] = trunc i64 %[[W33]] to i33 + // LLVM-NEXT: %[[V65SLOT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[FLAT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[E0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 0 + // LLVM-NEXT: store i64 %[[A1]], ptr %[[E0]], align 8 + // LLVM-NEXT: %[[E1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 1 + // LLVM-NEXT: store i64 %[[A2]], ptr %[[E1]], align 8 + // LLVM-NEXT: %[[PAIR:[0-9]+]] = load { i64, i64 }, ptr %[[FLAT]], align 8 + // LLVM-NEXT: store { i64, i64 } %[[PAIR]], ptr %[[V65SLOT]], align 8 + // LLVM-NEXT: %[[WIDE:[0-9]+]] = load i128, ptr %[[V65SLOT]], align 8 + // LLVM-NEXT: %[[V65:[0-9]+]] = trunc i128 %[[WIDE]] to i65 + // Each callee register traces back to the value it carries. + // LLVM-NEXT: %[[C3SLOT:[0-9]+]] = alloca i64, i64 1, align 8 + // LLVM-NEXT: %[[PAIRSLOT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[EXT65:[0-9]+]] = sext i65 %[[V65]] to i128 + // LLVM-NEXT: store i128 %[[EXT65]], ptr %[[PAIRSLOT]], align 8 + // LLVM-NEXT: %[[P0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[PAIRSLOT]], i32 0, i32 0 + // LLVM-NEXT: %[[C1:[0-9]+]] = load i64, ptr %[[P0]], align 8 + // LLVM-NEXT: %[[P1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[PAIRSLOT]], i32 0, i32 1 + // LLVM-NEXT: %[[C2:[0-9]+]] = load i64, ptr %[[P1]], align 8 + // LLVM-NEXT: %[[EXT33:[0-9]+]] = sext i33 %[[V33]] to i64 + // LLVM-NEXT: store i64 %[[EXT33]], ptr %[[C3SLOT]], align 8 + // LLVM-NEXT: %[[C3:[0-9]+]] = load i64, ptr %[[C3SLOT]], align 8 + // LLVM-NEXT: call void @take_widths(i17 signext %[[A0]], i64 %[[C1]] + // LLVM-SAME: , i64 %[[C2]], i64 %[[C3]]) + // LLVM-NEXT: ret void + + // Only a bit-precise type rounds up to a storage width, so a plain i40 + // coerces to its own width and the widen check leaves it alone. + cir.func @plain_i40(%arg0: !cir.int<s, 40>) -> !cir.int<s, 40> { + cir.return %arg0 : !cir.int<s, 40> + } + + // CHECK-LABEL: cir.func{{.*}} @plain_i40(%arg0: !cir.int<s, 40>) + // CHECK-SAME: -> !cir.int<s, 40> + // CHECK-NEXT: cir.return %arg0 : !cir.int<s, 40> + + // LLVM-LABEL: define i40 @plain_i40( + // LLVM-SAME: i40 %[[ARG:[0-9]+]]) + // LLVM-NEXT: ret i40 %[[ARG]] + + // A struct holding one sub-eightbyte _BitInt is a single INTEGER eightbyte: + // Direct, coerced to i32, and reconstituted before the body's own alloca. + cir.func @coerce_s(%arg0: !rec_S) -> !rec_S { + %0 = cir.alloca "a" align(4) : !cir.ptr<!rec_S> + cir.store %arg0, %0 : !rec_S, !cir.ptr<!rec_S> + %1 = cir.load %0 : !cir.ptr<!rec_S>, !rec_S + cir.return %1 : !rec_S + } + + // CHECK-LABEL: cir.func{{.*}} @coerce_s(%arg0: !s32i) -> !s32i + // CHECK-NEXT: %[[RETSLOT:[0-9]+]] = cir.alloca "coerce" align(4) : !cir.ptr<!rec_S> + // CHECK-NEXT: %[[ARGSLOT:[0-9]+]] = cir.alloca "coerce" align(4) : !cir.ptr<!s32i> + // CHECK-NEXT: cir.store %arg0, %[[ARGSLOT]] : !s32i, !cir.ptr<!s32i> + // CHECK-NEXT: %[[ARGVIEW:[0-9]+]] = cir.cast bitcast %[[ARGSLOT]] + // CHECK-SAME: : !cir.ptr<!s32i> -> !cir.ptr<!rec_S> + // CHECK-NEXT: %[[REC:[0-9]+]] = cir.load %[[ARGVIEW]] : !cir.ptr<!rec_S>, !rec_S + // CHECK-NEXT: %[[LOCAL:[0-9]+]] = cir.alloca "a" align(4) : !cir.ptr<!rec_S> + // CHECK-NEXT: cir.store %[[REC]], %[[LOCAL]] : !rec_S, !cir.ptr<!rec_S> + // CHECK-NEXT: %[[VAL:[0-9]+]] = cir.load %[[LOCAL]] : !cir.ptr<!rec_S>, !rec_S + // CHECK-NEXT: cir.store %[[VAL]], %[[RETSLOT]] : !rec_S, !cir.ptr<!rec_S> + // CHECK-NEXT: %[[RETVIEW:[0-9]+]] = cir.cast bitcast %[[RETSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_S> -> !cir.ptr<!s32i> + // CHECK-NEXT: %[[RES:[0-9]+]] = cir.load %[[RETVIEW]] : !cir.ptr<!s32i>, !s32i + // CHECK-NEXT: cir.return %[[RES]] : !s32i + + // LLVM-LABEL: define i32 @coerce_s( + // LLVM-SAME: i32 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[RETSLOT:[0-9]+]] = alloca %struct.S, i64 1, align 4 + // LLVM-NEXT: %[[ARGSLOT:[0-9]+]] = alloca i32, i64 1, align 4 + // LLVM-NEXT: store i32 %[[ARG]], ptr %[[ARGSLOT]], align 4 + // LLVM-NEXT: %[[REC:[0-9]+]] = load %struct.S, ptr %[[ARGSLOT]], align 4 + // LLVM-NEXT: %[[LOCAL:[0-9]+]] = alloca %struct.S, i64 1, align 4 + // LLVM-NEXT: store %struct.S %[[REC]], ptr %[[LOCAL]], align 4 + // LLVM-NEXT: %[[VAL:[0-9]+]] = load %struct.S, ptr %[[LOCAL]], align 4 + // LLVM-NEXT: store %struct.S %[[VAL]], ptr %[[RETSLOT]], align 4 + // LLVM-NEXT: %[[RES:[0-9]+]] = load i32, ptr %[[RETSLOT]], align 4 + // LLVM-NEXT: ret i32 %[[RES]] + + // A struct holding a two-eightbyte _BitInt flattens into an i64 pair. + cir.func @take_p(%arg0: !rec_P) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_p(%arg0: !u64i, %arg1: !u64i) + // CHECK-NEXT: %[[VALSLOT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[FLAT:[0-9]+]] = cir.alloca "coerce" align(8) + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[E0:[0-9]+]] = cir.get_member %[[FLAT]][0] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg0, %[[E0]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[E1:[0-9]+]] = cir.get_member %[[FLAT]][1] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i> + // CHECK-NEXT: cir.store %arg1, %[[E1]] : !u64i, !cir.ptr<!u64i> + // CHECK-NEXT: %[[PAIR:[0-9]+]] = cir.load %[[FLAT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct>, !rec_anon_struct + // CHECK-NEXT: cir.store %[[PAIR]], %[[VALSLOT]] + // CHECK-SAME: : !rec_anon_struct, !cir.ptr<!rec_anon_struct> + // CHECK-NEXT: %[[VIEW:[0-9]+]] = cir.cast bitcast %[[VALSLOT]] + // CHECK-SAME: : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!rec_P> + // CHECK-NEXT: %{{[0-9]+}} = cir.load %[[VIEW]] : !cir.ptr<!rec_P>, !rec_P + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_p( + // LLVM-SAME: i64 %[[A0:[0-9]+]], i64 %[[A1:[0-9]+]]) + // LLVM-NEXT: %[[VALSLOT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[FLAT:[0-9]+]] = alloca { i64, i64 }, i64 1, align 8 + // LLVM-NEXT: %[[E0:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 0 + // LLVM-NEXT: store i64 %[[A0]], ptr %[[E0]], align 8 + // LLVM-NEXT: %[[E1:[0-9]+]] = getelementptr inbounds nuw { i64, i64 } + // LLVM-SAME: , ptr %[[FLAT]], i32 0, i32 1 + // LLVM-NEXT: store i64 %[[A1]], ptr %[[E1]], align 8 + // LLVM-NEXT: %[[PAIR:[0-9]+]] = load { i64, i64 }, ptr %[[FLAT]], align 8 + // LLVM-NEXT: store { i64, i64 } %[[PAIR]], ptr %[[VALSLOT]], align 8 + // LLVM-NEXT: %{{[0-9]+}} = load %struct.P, ptr %[[VALSLOT]], align 16 + // LLVM-NEXT: ret void + + // A struct holding a _BitInt(128) coerces to a single i128. + cir.func @take_w(%arg0: !rec_W) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_w(%arg0: !s128i) + // CHECK-NEXT: %[[SLOT:[0-9]+]] = cir.alloca "coerce" align(16) : !cir.ptr<!s128i> + // CHECK-NEXT: cir.store %arg0, %[[SLOT]] : !s128i, !cir.ptr<!s128i> + // CHECK-NEXT: %[[VIEW:[0-9]+]] = cir.cast bitcast %[[SLOT]] + // CHECK-SAME: : !cir.ptr<!s128i> -> !cir.ptr<!rec_W> + // CHECK-NEXT: %{{[0-9]+}} = cir.load %[[VIEW]] : !cir.ptr<!rec_W>, !rec_W + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_w( + // LLVM-SAME: i128 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %[[SLOT:[0-9]+]] = alloca i128, i64 1, align 16 + // LLVM-NEXT: store i128 %[[ARG]], ptr %[[SLOT]], align 16 + // LLVM-NEXT: %{{[0-9]+}} = load %struct.W, ptr %[[SLOT]] + // LLVM-NEXT: ret void + + // A trailing byte pushes the struct past two eightbytes, so it passes byval, + // aligned 8 as _BitInt(128) requires rather than __int128's 16. + cir.func @take_o(%arg0: !rec_O) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_o( + // CHECK-SAME: %arg0: !cir.ptr<!rec_O> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !rec_O, llvm.noalias, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 : !cir.ptr<!rec_O>, !rec_O + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_o( + // LLVM-SAME: ptr noalias noundef byval(%struct.O) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %{{[0-9]+}} = load %struct.O, ptr %[[ARG]] + // LLVM-NEXT: ret void + + // Returning it takes the sret path, and the caller's buffer replaces the + // return slot the body allocated. + cir.func @ret_o() -> !rec_O { + %0 = cir.alloca "__retval" align(8) : !cir.ptr<!rec_O> + %1 = cir.const #cir.zero : !rec_O + cir.store %1, %0 : !rec_O, !cir.ptr<!rec_O> + %2 = cir.load %0 : !cir.ptr<!rec_O>, !rec_O + cir.return %2 : !rec_O + } + + // CHECK-LABEL: cir.func{{.*}} @ret_o( + // CHECK-SAME: %arg0: !cir.ptr<!rec_O> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_O + // CHECK-SAME: , llvm.writable}) + // CHECK-NEXT: %[[Z:[0-9]+]] = cir.const #cir.zero : !rec_O + // CHECK-NEXT: cir.store %[[Z]], %arg0 : !rec_O, !cir.ptr<!rec_O> + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @ret_o( + // LLVM-SAME: ptr dead_on_unwind noalias writable sret(%struct.O) align 8 + // LLVM-SAME: %[[ARG:[0-9]+]]) + // LLVM-NEXT: store %struct.O zeroinitializer, ptr %[[ARG]] + // LLVM-NEXT: ret void + + // Each _BitInt(65) element occupies two eightbytes, so an array of two is 32 + // bytes and the wrapping struct passes indirectly. + cir.func @take_arr(%arg0: !rec_Arr) { + cir.return + } + + // CHECK-LABEL: cir.func{{.*}} @take_arr( + // CHECK-SAME: %arg0: !cir.ptr<!rec_Arr> {llvm.align = 8 : i64 + // CHECK-SAME: , llvm.byval = !rec_Arr, llvm.noalias, llvm.noundef}) + // CHECK-NEXT: %{{[0-9]+}} = cir.load %arg0 : !cir.ptr<!rec_Arr>, !rec_Arr + // CHECK-NEXT: cir.return + + // LLVM-LABEL: define void @take_arr( + // LLVM-SAME: ptr noalias noundef byval(%struct.Arr) align 8 %[[ARG:[0-9]+]]) + // LLVM-NEXT: %{{[0-9]+}} = load %struct.Arr, ptr %[[ARG]] + // LLVM-NEXT: ret void +} diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir index eb023d8f8c586..ebda399c0d081 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir @@ -1,8 +1,9 @@ // RUN: not cir-opt %s -cir-call-conv-lowering=target=x86_64 2>&1 | FileCheck %s -!b17i = !cir.int<s, 17, bitint> -!b128i = !cir.int<s, 128, bitint> +!b129i = !cir.int<s, 129, bitint> +!b200i = !cir.int<s, 200, bitint> !i96 = !cir.int<s, 96> +!rec_D = !cir.struct<"D" {!b200i}> module attributes { dlti.dl_spec = #dlti.dl_spec< @@ -11,26 +12,29 @@ module attributes { #dlti.dl_entry<i128, dense<128>: vector<2xi64>>> } { - // A _BitInt is rejected pending its register-pair coercion handling, even at - // a sub-register width. - cir.func @take_b17(%arg0: !b17i) { + // 129 bits is the first rejected width. + cir.func @take_b129(%arg0: !b129i) { cir.return } - // CHECK: not yet implemented for type '!cir.int<s, 17, bitint> + // CHECK: not yet implemented for type '!cir.int<s, 129, bitint> - // A _BitInt(128) shares __int128's width but stays rejected: the accept check - // excludes bitint widths regardless of size. - cir.func @take_b128(%arg0: !b128i) { + cir.func @take_b200(%arg0: !b200i) { cir.return } - // CHECK: not yet implemented for type '!cir.int<s, 128, bitint> + // CHECK: not yet implemented for type '!cir.int<s, 200, bitint> - // A non-bitint integer with an intermediate width (65..127) is also rejected: - // it does not arise from C and would need a register-pair coercion the scalar - // Direct branch does not apply, so only widths up to 64 and exactly 128 are - // accepted. + // A struct carrying an over-wide _BitInt is rejected through the same + // recursive field check. + cir.func @take_d(%arg0: !rec_D) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.struct<"D" + + // A non-bitint integer of intermediate width (65..127) is rejected: only + // widths up to 64 and exactly 128 are accepted. cir.func @take_i96(%arg0: !i96) { cir.return } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
