https://github.com/xakep8 updated https://github.com/llvm/llvm-project/pull/225942
>From eafa7066fef1ab892a37cca6d59eb8daf02422e5 Mon Sep 17 00:00:00 2001 From: Kunal Dubey <[email protected]> Date: Thu, 24 Sep 2026 02:34:21 +0530 Subject: [PATCH] [CIR] Add target ABI lowering for wide _BitInt Teach CIR call-convention lowering about i386, AArch64, and PowerPC64 scalar _BitInt rules, preserve target storage alignment, and emit split constants using target endianness.\n\nDiagnose unsupported target-specific _BitInt va_arg lowering. --- clang/include/clang/CIR/CIRDataLayoutSpec.h | 7 +- clang/include/clang/CIR/Dialect/Passes.h | 5 +- clang/include/clang/CIR/Dialect/Passes.td | 16 +- clang/lib/CIR/CodeGen/CIRDataLayoutSpec.cpp | 15 +- clang/lib/CIR/CodeGen/CIRGenerator.cpp | 3 +- clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 25 +- .../Transforms/CallConvLoweringPass.cpp | 272 ++++++++++++++---- .../TargetLowering/CIRABIRewriteContext.cpp | 59 ++-- clang/lib/CIR/Lowering/CIRPasses.cpp | 42 ++- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 8 +- clang/lib/CIR/Lowering/LoweringHelpers.cpp | 12 +- .../test/CIR/CodeGen/bitint-wide-big-endian.c | 12 + .../call-conv-lowering-bitint-targets.c | 102 +++++++ ...l-conv-lowering-bitint-vaarg-unsupported.c | 16 ++ clang/tools/cir-translate/cir-translate.cpp | 3 +- 15 files changed, 486 insertions(+), 111 deletions(-) create mode 100644 clang/test/CIR/CodeGen/bitint-wide-big-endian.c create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-bitint-targets.c create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-bitint-vaarg-unsupported.c diff --git a/clang/include/clang/CIR/CIRDataLayoutSpec.h b/clang/include/clang/CIR/CIRDataLayoutSpec.h index c270f6283afe47..693130b6de6797 100644 --- a/clang/include/clang/CIR/CIRDataLayoutSpec.h +++ b/clang/include/clang/CIR/CIRDataLayoutSpec.h @@ -26,8 +26,11 @@ namespace cir { /// Translate \p dl into a DLTI data-layout spec and attach it to \p mod. /// On top of the plain mlir::translateDataLayout entries this adds a /// #cir.ptr_spec entry keyed on !cir.ptr, which CIR pointer types read for -/// their size and alignment; without it pointer widths default to 64 bits. -void setMLIRDataLayout(mlir::ModuleOp mod, const llvm::DataLayout &dl); +/// their size and alignment, and the target's maximum _BitInt alignment. +/// Without the pointer entry pointer widths default to 64 bits; without the +/// _BitInt entry its maximum alignment defaults to 64 bits. +void setMLIRDataLayout(mlir::ModuleOp mod, const llvm::DataLayout &dl, + unsigned bitIntMaxAlign = 64); } // namespace cir diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h index 42dd3383064568..0d11e4bec542a9 100644 --- a/clang/include/clang/CIR/Dialect/Passes.h +++ b/clang/include/clang/CIR/Dialect/Passes.h @@ -20,7 +20,7 @@ namespace cir { /// The ABI target whose calling-convention rules drive CallConvLowering. /// None is the unset state used when the pass runs in classification-attr /// mode instead of selecting a target. -enum class CallConvTarget { None, Test, X86_64 }; +enum class CallConvTarget { None, Test, X86_64, X86_32, AArch64, PPC64 }; } // namespace cir namespace clang { @@ -40,7 +40,8 @@ std::unique_ptr<Pass> createCallConvLoweringPass(cir::CallConvTarget target, llvm::abi::X86AVXABILevel x86AvxAbiLevel, bool allowsX86TargetAttrAvx, - const llvm::abi::X86ABICompatInfo &x86AbiCompat); + const llvm::abi::X86ABICompatInfo &x86AbiCompat, + const llvm::abi::AArch64ABIOptions &aarch64Options); std::unique_ptr<Pass> createHoistAllocasPass(); std::unique_ptr<Pass> createLoweringPreparePass(); std::unique_ptr<Pass> createLoweringPreparePass(clang::ASTContext *astCtx); diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td index 6994aa8a4b18ad..020366a02df0b1 100644 --- a/clang/include/clang/CIR/Dialect/Passes.td +++ b/clang/include/clang/CIR/Dialect/Passes.td @@ -225,10 +225,10 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> { Two driver modes select how each function's classification is computed: - - `target=<name>` selects an ABI target. Currently only `"test"` (the - MLIR test target in `mlir/lib/ABI/Targets/Test/`) is supported. Real - targets (x86_64, AArch64, ...) will be added once the LLVM ABI library - ships them. + - `target=<name>` selects an ABI target. The MLIR test target and x86_64 + System V have full classifiers. AArch64 uses the LLVM ABI classifier + for the scalar types it supports; i386 and PowerPC64 provide their + integer rules while their full classifiers are developed. - `classification-attr=<name>` reads a `DictionaryAttr` named `<name>` from each `cir.func` and parses it via the test-target injection helper. Used by tests to inject arbitrary classifications without @@ -246,7 +246,13 @@ def CallConvLowering : Pass<"cir-call-conv-lowering", "mlir::ModuleOp"> { clEnumValN(cir::CallConvTarget::Test, "test", "MLIR test ABI target"), clEnumValN(cir::CallConvTarget::X86_64, "x86_64", - "x86_64 System V") + "x86_64 System V"), + clEnumValN(cir::CallConvTarget::X86_32, "x86_32", + "32-bit x86"), + clEnumValN(cir::CallConvTarget::AArch64, "aarch64", + "AArch64"), + clEnumValN(cir::CallConvTarget::PPC64, "ppc64", + "64-bit PowerPC ELF") )}]>, Option<"classificationAttr", "classification-attr", "std::string", /*default=*/"\"\"", diff --git a/clang/lib/CIR/CodeGen/CIRDataLayoutSpec.cpp b/clang/lib/CIR/CodeGen/CIRDataLayoutSpec.cpp index a25fb46da5e8e7..fee867d11ee143 100644 --- a/clang/lib/CIR/CodeGen/CIRDataLayoutSpec.cpp +++ b/clang/lib/CIR/CodeGen/CIRDataLayoutSpec.cpp @@ -22,7 +22,8 @@ #include "clang/CIR/MissingFeatures.h" #include "llvm/IR/DataLayout.h" -void cir::setMLIRDataLayout(mlir::ModuleOp mod, const llvm::DataLayout &dl) { +void cir::setMLIRDataLayout(mlir::ModuleOp mod, const llvm::DataLayout &dl, + unsigned bitIntMaxAlign) { mlir::MLIRContext *mlirContext = mod.getContext(); mlir::DataLayoutSpecInterface dlSpec = mlir::translateDataLayout(dl, mlirContext); @@ -44,6 +45,18 @@ void cir::setMLIRDataLayout(mlir::ModuleOp mod, const llvm::DataLayout &dl) { dlSpec.getEntries().begin(), dlSpec.getEntries().end()); entries.push_back(mlir::DataLayoutEntryAttr::get(ptrKey, ptrSpec)); + // LLVM's data-layout string has no spelling for Clang's target-specific + // maximum _BitInt alignment (for example, 32 bits on i386, 64 on x86_64, + // and 128 on AArch64). Record it on a sentinel CIR _BitInt entry so + // arbitrary-width IntTypes can recover the AST layout decision. + auto bitIntKey = cir::IntType::get(mlirContext, /*width=*/1, + /*is_signed=*/false, + /*is_bit_int=*/true); + auto bitIntMaxAlignAttr = mlir::IntegerAttr::get( + mlir::IntegerType::get(mlirContext, 32), bitIntMaxAlign); + entries.push_back( + mlir::DataLayoutEntryAttr::get(bitIntKey, bitIntMaxAlignAttr)); + mod->setAttr(mlir::DLTIDialect::kDataLayoutAttrName, mlir::DataLayoutSpecAttr::get(mlirContext, entries)); } diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp index 8cbee4ea39a4b8..eb69f13f60db41 100644 --- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp @@ -58,7 +58,8 @@ void CIRGenerator::Initialize(ASTContext &astContext) { mlir::ModuleOp mod = cgm->getModule(); llvm::DataLayout layout = llvm::DataLayout(astContext.getTargetInfo().getDataLayoutString()); - cir::setMLIRDataLayout(mod, layout); + cir::setMLIRDataLayout(mod, layout, + astContext.getTargetInfo().getBitIntMaxAlign()); } bool CIRGenerator::verifyModule() const { return cgm->verifyModule(); } diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp index b9263609e4fe48..d5dd387fc3f253 100644 --- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -729,6 +729,23 @@ constexpr static uint64_t kBitsInByte = 8; constexpr static uint64_t kDefaultPointerSizeBits = 64; constexpr static uint64_t kDefaultPointerAlignment = 8; +/// Read Clang's target-specific maximum _BitInt alignment, in bits, from the +/// sentinel CIR integer data-layout entry. Hand-written CIR without the entry +/// keeps the historical/default 64-bit cap. +static uint64_t getBitIntMaxAlign(mlir::DataLayoutEntryListRef params) { + for (mlir::DataLayoutEntryInterface entry : params) { + if (!entry.isTypeEntry()) + continue; + auto key = + mlir::dyn_cast<cir::IntType>(mlir::cast<mlir::Type>(entry.getKey())); + if (!key || !key.isBitInt() || key.getWidth() != 1) + continue; + if (auto value = mlir::dyn_cast<mlir::IntegerAttr>(entry.getValue())) + return value.getValue().getZExtValue(); + } + return 64; +} + /// Returns the default-address-space #cir.ptr_spec entry, or a synthesized /// 64-bit default when there is none. Per-AS entries are not modeled yet. cir::PtrSpecAttr getPointerSpec(mlir::DataLayoutEntryListRef params, @@ -1046,7 +1063,7 @@ unsigned IntType::getStorageTypeWidth(const mlir::DataLayout &dataLayout) const { if (!isBitInt()) return getWidth(); - uint64_t alignBits = getABIAlignment(dataLayout, {}) * 8; + uint64_t alignBits = dataLayout.getTypeABIAlignment(*this) * 8; return static_cast<unsigned>(llvm::alignTo(getWidth(), alignBits)); } @@ -1063,10 +1080,10 @@ uint64_t IntType::getABIAlignment(const mlir::DataLayout &dataLayout, mlir::DataLayoutEntryListRef params) const { unsigned width = getWidth(); if (isBitInt()) { - // _BitInt alignment: min(PowerOf2Ceil(width), 64 bits) in bytes. - // Matches Clang's TargetInfo::getBitIntAlign with default max = 64. + // _BitInt alignment: min(PowerOf2Ceil(width), target maximum) in bytes. + // Matches Clang's TargetInfo::getBitIntAlign. uint64_t alignBits = - std::min(llvm::PowerOf2Ceil(width), static_cast<uint64_t>(64)); + std::min(llvm::PowerOf2Ceil(width), getBitIntMaxAlign(params)); return std::max(alignBits / 8, static_cast<uint64_t>(1)); } // Round up to a power-of-two byte alignment. DataLayout consumers such as diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index ae7960654e1e1b..1ac8cd333d9347 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -16,8 +16,8 @@ // target=test // Use the MLIR test ABI target (mlir/lib/ABI/Targets/Test/) to classify // each function. Predictable rules that approximate x86_64 SysV. Real -// targets (x86_64, AArch64) will be added once the LLVM ABI library -// ships them. +// x86_64 and AArch64 use LLVM ABI library classifiers; i386 and +// PowerPC64 currently provide their integer rules. // // classification-attr=<name> // Read a DictionaryAttr named <name> from each cir.func and parse it via @@ -69,10 +69,10 @@ namespace mlir { namespace { //===----------------------------------------------------------------------===// -// x86_64 System V classifier bridge +// LLVM ABI classifier bridge // -// 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 +// Maps CIR types to llvm::abi::Type, runs an LLVM ABI Lowering Library target +// classifier, and converts the result back into the dialect-agnostic // mlir::abi::FunctionClassification that CIRABIRewriteContext consumes. // isSupportedType says which CIR types the bridge handles, and a signature // naming any other fails the pass instead of being misclassified. @@ -645,20 +645,91 @@ static llvm::abi::RequiredArgs requiredArgs(cir::FuncType fnTy) { return llvm::abi::RequiredArgs(fnTy.getNumInputs()); } -/// Classify an x86_64 SysV signature (return type + argument types) using the -/// LLVM ABI library. Shared by the cir.func path, the variadic-call path and -/// the indirect-call path (the latter classifies from the callee function +/// The current AArch64 LLVM ABI classifier implements scalar arguments and +/// returns, but deliberately reports aggregate and vector classification NYI. +/// Keep those types out of the bridge: its NYI fallback is Ignore, which would +/// otherwise silently remove a value from the wire signature. +static bool isSupportedTypeForTarget(mlir::Type ty, const DataLayout &dl, + cir::CallConvTarget target) { + if (!isSupportedType(ty, dl)) + return false; + if (target != cir::CallConvTarget::AArch64) + return true; + return !isa<cir::ArrayType, cir::RecordType, cir::ComplexType, + cir::VectorType, cir::BitFieldType>(ty); +} + +/// Classify the integer rules needed on targets that do not yet have a full +/// LLVM ABI library target. All other types stay Direct, preserving the +/// pre-existing CIR behavior while fixing the function-boundary mismatch that +/// wide _BitInt exposed. +static FunctionClassification +classifyTargetIntRules(mlir::Type retTy, mlir::TypeRange inputs, + const DataLayout &dl, cir::CallConvTarget target) { + assert((target == cir::CallConvTarget::X86_32 || + target == cir::CallConvTarget::AArch64 || + target == cir::CallConvTarget::PPC64) && + "only integer fallback targets reach this helper"); + + auto classify = [&](mlir::Type ty, bool isReturn) { + if (isa<cir::BoolType>(ty) && target != cir::CallConvTarget::AArch64) + return ArgClassification::getExtend(/*coercedType=*/nullptr, + /*signExtend=*/false); + auto intTy = dyn_cast<cir::IntType>(ty); + if (!intTy) + return isa<cir::VoidType>(ty) ? ArgClassification::getIgnore() + : ArgClassification::getDirect(); + + unsigned indirectAbove = target == cir::CallConvTarget::X86_32 ? 64 : 128; + if (intTy.isBitInt() && intTy.getWidth() > indirectAbove) + return ArgClassification::getIndirect( + llvm::Align(dl.getTypeABIAlignment(intTy)), + /*byVal=*/!isReturn && target == cir::CallConvTarget::PPC64); + + unsigned extendBelow = target == cir::CallConvTarget::X86_32 ? 32 + : target == cir::CallConvTarget::PPC64 ? 64 + : 0; + if (intTy.getWidth() < extendBelow) + return ArgClassification::getExtend(/*coercedType=*/nullptr, + intTy.isSigned()); + return ArgClassification::getDirect(); + }; + + FunctionClassification fc; + fc.returnsVoid = isa<cir::VoidType>(retTy); + fc.returnInfo = classify(retTy, /*isReturn=*/true); + for (mlir::Type input : inputs) + fc.argInfos.push_back(classify(input, /*isReturn=*/false)); + return fc; +} + +static bool signatureSupportedByABITarget(mlir::Type retTy, + mlir::TypeRange inputs, + const DataLayout &dl, + cir::CallConvTarget target) { + if (!isa<cir::VoidType>(retTy) && + !isSupportedTypeForTarget(retTy, dl, target)) + return false; + return llvm::all_of(inputs, [&](mlir::Type input) { + return isSupportedTypeForTarget(input, dl, target); + }); +} + +/// Classify a signature (return type + argument types) using an LLVM ABI +/// library target. Shared by the cir.func path, the variadic-call path and the +/// indirect-call path (the latter classifies from the callee function /// pointer's pointee FuncType). \p required marks where the declared -/// parameters in \p inputs end. The classifier treats every argument past that -/// point as passed through an ellipsis. Returns std::nullopt and emits an NYI -/// error via \p emitError if the signature uses a type the bridge does not +/// parameters in \p inputs end. The classifier treats every argument past +/// that point as passed through an ellipsis. Returns std::nullopt and emits an +/// NYI error via \p emitError if the signature uses a type the bridge does not /// handle yet. -static std::optional<FunctionClassification> classifyX86_64Signature( - mlir::Type retCIR, mlir::TypeRange inputs, llvm::abi::RequiredArgs required, - MLIRContext *ctx, const DataLayout &dl, - mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, - llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { +static std::optional<FunctionClassification> +classifyABISignature(mlir::Type retCIR, mlir::TypeRange inputs, + llvm::abi::RequiredArgs required, MLIRContext *ctx, + const DataLayout &dl, mlir::abi::ABITypeMapper &typeMapper, + const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, + cir::CallConvTarget target, llvm::StringRef targetName, + llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { assert(retCIR && "signature return type must be non-null"); assert((!required.allowsOptionalArgs() || required.getNumRequiredArgs() <= inputs.size()) && @@ -666,11 +737,11 @@ static std::optional<FunctionClassification> classifyX86_64Signature( bool voidRet = isa<cir::VoidType>(retCIR); auto reject = [&](mlir::Type t) -> bool { - if (isSupportedType(t, dl)) + if (isSupportedTypeForTarget(t, dl, target)) return false; - emitError() - << "x86_64 calling-convention lowering not yet implemented for type " - << t; + emitError() << targetName + << " calling-convention lowering not yet implemented for type " + << t; return true; }; if (!voidRet && reject(retCIR)) @@ -693,7 +764,8 @@ static std::optional<FunctionClassification> classifyX86_64Signature( // convertABIArgInfo returns nullopt when the classifier picks a coercion this // bridge cannot represent. auto nyiCoercion = [&](mlir::Type t) { - emitError() << "x86_64 calling-convention lowering not yet " + emitError() << targetName + << " calling-convention lowering not yet " "implemented for the ABI coercion of type " << t; }; @@ -753,15 +825,15 @@ static llvm::abi::X86AVXABILevel funcAvxLevel(cir::FuncOp func, /// std::nullopt and emits an NYI error if the signature uses a type the bridge /// does not handle yet. static std::optional<FunctionClassification> -classifyX86_64Function(cir::FuncOp func, const DataLayout &dl, - mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, - ModuleOp modOp) { +classifyABIFunction(cir::FuncOp func, const DataLayout &dl, + mlir::abi::ABITypeMapper &typeMapper, + const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, + cir::CallConvTarget target, llvm::StringRef targetName) { cir::FuncType fnTy = func.getFunctionType(); - return classifyX86_64Signature(fnTy.getReturnType(), fnTy.getInputs(), - requiredArgs(fnTy), func->getContext(), dl, - typeMapper, targetInfo, modOp, - [&]() { return func.emitOpError(); }); + return classifyABISignature(fnTy.getReturnType(), fnTy.getInputs(), + requiredArgs(fnTy), func->getContext(), dl, + typeMapper, targetInfo, modOp, target, targetName, + [&]() { return func.emitOpError(); }); } /// Classify the single type fetched by a `cir.va_arg` as an unnamed argument @@ -772,9 +844,10 @@ static std::optional<ArgClassification> classifyX86_64VarArgType( mlir::abi::ABITypeMapper &typeMapper, const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { - std::optional<FunctionClassification> fc = classifyX86_64Signature( + std::optional<FunctionClassification> fc = classifyABISignature( cir::VoidType::get(ctx), mlir::TypeRange(ty), llvm::abi::RequiredArgs(0), - ctx, dl, typeMapper, targetInfo, modOp, emitError); + ctx, dl, typeMapper, targetInfo, modOp, cir::CallConvTarget::X86_64, + "x86_64", emitError); if (!fc) return std::nullopt; return fc->argInfos[0]; @@ -787,17 +860,18 @@ static std::optional<ArgClassification> classifyX86_64VarArgType( /// small struct is passed in registers early in the list and in memory once /// the integer registers are gone. Classifying from the call's operands /// rather than the callee's signature is what makes that accounting right. -static std::optional<FunctionClassification> classifyX86_64VariadicCall( +static std::optional<FunctionClassification> classifyABIVariadicCall( cir::CIRCallOpInterface call, cir::FuncType calleeTy, const DataLayout &dl, mlir::abi::ABITypeMapper &typeMapper, - const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp) { + const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, + cir::CallConvTarget target, llvm::StringRef targetName) { assert(calleeTy.isVarArg() && "only a variadic callee can take more operands than it declares"); Operation *op = call.getOperation(); - return classifyX86_64Signature( + return classifyABISignature( calleeTy.getReturnType(), call.getArgOperands().getTypes(), requiredArgs(calleeTy), op->getContext(), dl, typeMapper, targetInfo, - modOp, [&]() { return op->emitOpError(); }); + modOp, target, targetName, [&]() { return op->emitOpError(); }); } /// Whether \p fc gives the callee access to memory through a pointer the ABI @@ -845,8 +919,10 @@ struct CallConvLoweringPass using CallConvLoweringBase::CallConvLoweringBase; CallConvLoweringPass(const CallConvLoweringOptions &options, - const llvm::abi::X86ABICompatInfo &x86AbiCompat) - : CallConvLoweringBase(options), x86AbiCompat(x86AbiCompat) {} + const llvm::abi::X86ABICompatInfo &x86AbiCompat, + const llvm::abi::AArch64ABIOptions &aarch64Options) + : CallConvLoweringBase(options), x86AbiCompat(x86AbiCompat), + aarch64Options(aarch64Options) {} void runOnOperation() override; @@ -855,6 +931,7 @@ struct CallConvLoweringPass /// struct has no command-line parser, so a cir-opt run gets the library /// defaults rather than a target's values. llvm::abi::X86ABICompatInfo x86AbiCompat; + llvm::abi::AArch64ABIOptions aarch64Options; }; /// Record on \p fc whether \p returnType is CIR's void. The x86_64 classifier @@ -968,10 +1045,18 @@ void CallConvLoweringPass::runOnOperation() { static constexpr unsigned numAvxLevels = static_cast<unsigned>(llvm::abi::X86AVXABILevel::Last) + 1; bool isX86 = target == cir::CallConvTarget::X86_64; - std::optional<mlir::abi::ABITypeMapper> x86TypeMapper; + bool isAArch64 = target == cir::CallConvTarget::AArch64; + bool hasBitIntTarget = target == cir::CallConvTarget::X86_32 || + target == cir::CallConvTarget::PPC64; + bool hasABITarget = isX86 || isAArch64; + std::optional<mlir::abi::ABITypeMapper> abiTypeMapper; std::array<std::unique_ptr<llvm::abi::TargetInfo>, numAvxLevels> x86Targets; - if (isX86) - x86TypeMapper.emplace(dl); + std::unique_ptr<llvm::abi::TargetInfo> aarch64Target; + if (hasABITarget) + abiTypeMapper.emplace(dl); + if (isAArch64) + aarch64Target = llvm::abi::createAArch64TargetInfo( + abiTypeMapper->getTypeBuilder(), aarch64Options); auto x86TargetFor = [&](llvm::abi::X86AVXABILevel level) -> const llvm::abi::TargetInfo & { assert(static_cast<unsigned>(level) < numAvxLevels && @@ -980,7 +1065,7 @@ void CallConvLoweringPass::runOnOperation() { x86Targets[static_cast<unsigned>(level)]; if (!slot) slot = llvm::abi::createX86_64TargetInfo( - x86TypeMapper->getTypeBuilder(), level, + abiTypeMapper->getTypeBuilder(), level, /*Has64BitPointers=*/true, x86AbiCompat); return *slot; }; @@ -990,6 +1075,52 @@ void CallConvLoweringPass::runOnOperation() { return baseAvxLevel; return funcAvxLevel(func, baseAvxLevel); }; + auto targetFor = [&](cir::FuncOp func) -> const llvm::abi::TargetInfo & { + if (isX86) + return x86TargetFor(avxLevelFor(func)); + assert(isAArch64 && "only LLVM ABI library targets reach this helper"); + return *aarch64Target; + }; + llvm::StringRef targetName; + switch (target.getValue()) { + case cir::CallConvTarget::X86_64: + targetName = "x86_64"; + break; + case cir::CallConvTarget::X86_32: + targetName = "i386"; + break; + case cir::CallConvTarget::AArch64: + targetName = "AArch64"; + break; + case cir::CallConvTarget::PPC64: + targetName = "PowerPC64"; + break; + default: + break; + } + + // CIRABIRewriteContext::rewriteVAArg currently implements only the x86_64 + // SysV va_list layout. Reject _BitInt fetches on the other new targets + // before mutating any signatures. Even directly-passed widths need the + // target's register-save-area and stack cursor rules; leaving cir.va_arg to + // generic LLVM lowering can miscompile and, on AArch64 ELF, assert in the + // backend. + if (isAArch64 || hasBitIntTarget) { + bool hasVAArg = false; + moduleOp.walk([&](cir::VAArgOp v) { + auto intTy = dyn_cast<cir::IntType>(v.getType()); + if (!intTy || !intTy.isBitInt()) + return; + v->emitOpError() << targetName + << " va_arg lowering for _BitInt not yet " + "implemented in CallConvLowering"; + hasVAArg = true; + }); + if (hasVAArg) { + signalPassFailure(); + return; + } + } // Classify every cir.func up front. No IR mutation happens here, so // later walks can consult any function's classification regardless of @@ -1007,9 +1138,14 @@ void CallConvLoweringPass::runOnOperation() { llvm::any_of(fnTy.getInputs(), hasIncompleteRecordByValue))) return; std::optional<FunctionClassification> fc; - if (isX86) - fc = classifyX86_64Function(f, dl, *x86TypeMapper, - x86TargetFor(avxLevelFor(f)), moduleOp); + if (hasABITarget && + (isX86 || signatureSupportedByABITarget(fnTy.getReturnType(), + fnTy.getInputs(), dl, target))) + fc = classifyABIFunction(f, dl, *abiTypeMapper, targetFor(f), moduleOp, + target, targetName); + else if (hasBitIntTarget || isAArch64) + fc = classifyTargetIntRules(fnTy.getReturnType(), fnTy.getInputs(), dl, + target); else fc = classifyFunction(f, dl, target, classificationAttr); if (!fc) { @@ -1046,11 +1182,13 @@ void CallConvLoweringPass::runOnOperation() { return; callers[callee].push_back(op); - // Only the x86_64 driver classifies per call site. Under the other - // drivers the classification comes from a fixed per-function source, so - // such a call stays short a classification and rewriteCallSite reports it. + // LLVM ABI targets classify a variadic call from its full operand list. + // Under the other drivers the classification comes from a fixed + // per-function source, so such a call stays short a classification and + // rewriteCallSite reports it. cir::FuncType calleeTy = callee.getFunctionType(); - if (!isX86 || call.getNumArgOperands() <= calleeTy.getNumInputs()) + if ((!hasABITarget && !hasBitIntTarget) || + call.getNumArgOperands() <= calleeTy.getNumInputs()) return; // A callee declared without a prototype also takes more operands than it // declares, and the verifier allows it. Those extra arguments are named @@ -1067,9 +1205,17 @@ void CallConvLoweringPass::runOnOperation() { // Classic instead arranges every call site from the caller and reports a // caller whose level disagrees with its callee in checkFunctionCallABI, // which has no equivalent here yet. - std::optional<FunctionClassification> fc = - classifyX86_64VariadicCall(call, calleeTy, dl, *x86TypeMapper, - x86TargetFor(avxLevelFor(callee)), moduleOp); + std::optional<FunctionClassification> fc; + mlir::TypeRange callArgTypes = call.getArgOperands().getTypes(); + if (hasABITarget && + (isX86 || signatureSupportedByABITarget(calleeTy.getReturnType(), + callArgTypes, dl, target))) + fc = classifyABIVariadicCall(call, calleeTy, dl, *abiTypeMapper, + targetFor(callee), moduleOp, target, + targetName); + else + fc = classifyTargetIntRules(calleeTy.getReturnType(), callArgTypes, dl, + target); if (!fc) { anyFailed = true; return; @@ -1187,12 +1333,16 @@ void CallConvLoweringPass::runOnOperation() { // A callee resolved at run time carries no features of its own, so the // level comes from the function containing the call, which is the // declaration classic arranges every call site from. - if (isX86) - return classifyX86_64Signature( + if (hasABITarget && + (isX86 || signatureSupportedByABITarget(funcTy.getReturnType(), + argTypes, dl, target))) + return classifyABISignature( funcTy.getReturnType(), argTypes, requiredArgs(funcTy), ctx, dl, - *x86TypeMapper, - x86TargetFor(avxLevelFor(c->getParentOfType<cir::FuncOp>())), - moduleOp, [&]() { return c->emitOpError(); }); + *abiTypeMapper, targetFor(c->getParentOfType<cir::FuncOp>()), + moduleOp, target, targetName, [&]() { return c->emitOpError(); }); + if (hasBitIntTarget || isAArch64) + return classifyTargetIntRules(funcTy.getReturnType(), argTypes, dl, + target); return withReturnVoidness( mlir::abi::test::classify(argTypes, funcTy.getReturnType(), dl), funcTy.getReturnType()); @@ -1241,7 +1391,7 @@ void CallConvLoweringPass::runOnOperation() { for (cir::VAArgOp v : vaArgs) { cir::FuncOp enclosing = v->getParentOfType<cir::FuncOp>(); std::optional<ArgClassification> ac = classifyX86_64VarArgType( - v.getType(), ctx, dl, *x86TypeMapper, + v.getType(), ctx, dl, *abiTypeMapper, x86TargetFor(avxLevelFor(enclosing)), moduleOp, [&]() { return v->emitOpError(); }); if (!ac) { @@ -1265,10 +1415,12 @@ std::unique_ptr<Pass> mlir::createCallConvLoweringPass() { std::unique_ptr<Pass> mlir::createCallConvLoweringPass( cir::CallConvTarget target, llvm::abi::X86AVXABILevel x86AvxAbiLevel, bool allowsX86TargetAttrAvx, - const llvm::abi::X86ABICompatInfo &x86AbiCompat) { + const llvm::abi::X86ABICompatInfo &x86AbiCompat, + const llvm::abi::AArch64ABIOptions &aarch64Options) { CallConvLoweringOptions options; options.target = target; options.x86AvxAbiLevel = x86AvxAbiLevel; options.allowsX86TargetAttrAvx = allowsX86TargetAttrAvx; - return std::make_unique<CallConvLoweringPass>(options, x86AbiCompat); + return std::make_unique<CallConvLoweringPass>(options, x86AbiCompat, + aarch64Options); } diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index a9f73e667f1f3f..43f49971e749a3 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -31,16 +31,17 @@ using namespace mlir::abi; // // An Indirect argument is byval or not, following its classification's // byVal flag. byval is a by-value parameter the ABI passes in memory rather -// than registers, usually for its size. Non-byval is a by-value parameter -// whose type cannot be copied freely, because it has a non-trivial copy -// constructor, move constructor, or destructor, so the callee works on the -// caller's own object rather than a copy. +// than registers, usually for its size. Non-byval has two forms. A record +// whose type cannot be copied freely (for example because it has a non-trivial +// copy constructor) is forwarded in the caller's storage. Some ABIs also +// classify a trivially-copyable scalar such as a wide _BitInt as non-byval +// indirect; that still needs a source-language value copy, just without an +// LLVM byval attribute. // -// At the call site byval copies into a fresh alloca while a non-byval -// argument forwards the caller's storage. At the callee, byval loads the -// incoming pointer (a local copy), while non-byval rewires the CIRGen -// param-slot alloca to the incoming pointer so the body mutates the caller's -// storage in place. +// At the call site byval and non-record indirect arguments copy into a fresh +// alloca, while a non-byval record forwards the caller's storage. At the +// callee, copied arguments load the incoming pointer; a forwarded record +// rewires the CIRGen param-slot alloca to the incoming pointer. // // For Expand, the single struct argument is replaced by N scalar arguments // (one per field). At the callee, the N field block arguments are stored @@ -241,15 +242,21 @@ mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx, attrs.set(mlir::LLVM::LLVMDialect::getByValAttrName(), mlir::TypeAttr::get(pointeeTy)); } else { - // Classic adds llvm.dead_on_return when the object's lifetime ends in - // the callee, which needs the destructor's triviality from - // cir.record_layout's has_trivial_dtor. - assert(!cir::MissingFeatures::deadOnReturnAttr()); attrs.set(mlir::LLVM::LLVMDialect::getNoFreeObjAttrName(), builder.getUnitAttr()); attrs.set(mlir::LLVM::LLVMDialect::getDereferenceableAttrName(), builder.getI64IntegerAttr( dl.getTypeSize(pointeeTy).getFixedValue())); + // A copied scalar's temporary dies when the callee returns, exactly + // the lifetime represented by bare LLVM `dead_on_return` (all reachable + // memory, encoded as the all-ones integer form). Records still need + // the destructor-triviality information called out by the existing + // MissingFeatures hook before this can be set generally. + if (!isa<cir::RecordType>(pointeeTy)) + attrs.set(mlir::LLVM::LLVMDialect::getDeadOnReturnAttrName(), + builder.getI64IntegerAttr(-1)); + else + assert(!cir::MissingFeatures::deadOnReturnAttr()); } newArgAttrs.push_back(attrs.getDictionary(ctx)); } else { @@ -771,13 +778,14 @@ void insertArgCoercion( // to adapted (now of the original type != the alloca's pointee type). blockArg.replaceAllUsesExcept(adapted, coercionOps); } else if (ac.kind == ArgKind::Indirect) { - // byval and non-byval both lower to !cir.ptr<T>, and which it is shows - // up only in the attrs updateArgAttrs applies. Body lowering differs: - // byval copies into the callee (load at entry), while non-byval must - // operate on the caller's storage in place. + // Every indirect argument lowers to !cir.ptr<T>. An LLVM byval + // argument and a non-record argument both represent a source-language + // value copy, while a non-byval record must operate on the caller's + // storage in place. + bool copyIndirect = ac.byVal || !isa<cir::RecordType>(blockArg.getType()); auto ptrTy = cir::PointerType::get(blockArg.getType()); - if (!ac.byVal) { + if (!copyIndirect) { // Without byval, drop the spill store and let the slot's uses read the // incoming pointer, so the body operates on the caller's storage in // place. A byte-copy would be wrong for non-trivially-copyable @@ -800,8 +808,8 @@ void insertArgCoercion( if (destAlloca) pendingParamSlots.emplace_back(destAlloca, blockArg); } else { - // byval: load the incoming pointer so the body sees a T value (and - // any CIRGen param-slot store becomes a local copy of that value). + // Load the incoming pointer so the body sees a T value and any CIRGen + // parameter-slot store becomes a local copy of that value. blockArg.setType(ptrTy); builder.setInsertionPointToStart(&entry); @@ -1524,12 +1532,11 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp, dl, ac.directOffset); newArgs.push_back(arg); } else if (ac.kind == ArgKind::Indirect) { - // byval hands the callee its own copy. Without byval the argument must - // name the caller's storage instead, so that the object the callee - // operates on is the one the caller destroys. That means forwarding - // the address the operand was loaded from rather than the loaded value, - // so a store to that storage after the load is visible to the callee. - if (!ac.byVal) { + // byval and non-record indirect arguments hand the callee a value copy. + // A non-byval record must name the caller's storage instead, so that the + // object the callee operates on is the one the caller destroys. + bool copyIndirect = ac.byVal || !isa<cir::RecordType>(arg.getType()); + if (!copyIndirect) { // The rewritten parameter is a pointer to the argument type in the // default address space, so an operand read through an address-space // cast cannot be handed on as it stands. cir.load already pins the diff --git a/clang/lib/CIR/Lowering/CIRPasses.cpp b/clang/lib/CIR/Lowering/CIRPasses.cpp index b612b64b714760..28f18e4c128b1e 100644 --- a/clang/lib/CIR/Lowering/CIRPasses.cpp +++ b/clang/lib/CIR/Lowering/CIRPasses.cpp @@ -28,6 +28,16 @@ static CallConvTarget getCallConvTarget(const llvm::Triple &triple) { // Windows is not supported. UEFI shares its convention. if (triple.getArch() == llvm::Triple::x86_64 && !triple.isOSWindowsOrUEFI()) return CallConvTarget::X86_64; + if (triple.getArch() == llvm::Triple::x86 && triple.isOSBinFormatELF()) + return CallConvTarget::X86_32; + if (triple.getArch() == llvm::Triple::aarch64 || + triple.getArch() == llvm::Triple::aarch64_32 || + triple.getArch() == llvm::Triple::aarch64_be) + return CallConvTarget::AArch64; + if ((triple.getArch() == llvm::Triple::ppc64 || + triple.getArch() == llvm::Triple::ppc64le) && + triple.isOSBinFormatELF()) + return CallConvTarget::PPC64; return CallConvTarget::None; } @@ -74,6 +84,31 @@ getX86ABICompatInfo(const clang::ASTContext &astContext, return abiCompat; } +/// Resolve Clang's target and language options into the flags consumed by the +/// LLVM ABI library's AArch64 classifier. Keep this in sync with classic +/// CodeGen's CodeGenModule::getLLVMABITargetInfo. +static llvm::abi::AArch64ABIOptions +getAArch64ABIOptions(const clang::ASTContext &astContext, + clang::LangOptions::ClangABI compat) { + const clang::TargetInfo &targetInfo = astContext.getTargetInfo(); + const llvm::Triple &triple = targetInfo.getTriple(); + llvm::abi::AArch64ABIOptions options; + if (targetInfo.getABI() == "darwinpcs") + options.Kind = llvm::abi::AArch64ABIKind::DarwinPCS; + else if (triple.isOSWindows()) + options.Kind = llvm::abi::AArch64ABIKind::Win64; + else if (targetInfo.getABI() == "aapcs-soft") + options.Kind = llvm::abi::AArch64ABIKind::AAPCSSoft; + else + options.Kind = llvm::abi::AArch64ABIKind::AAPCS; + + options.IsILP32 = triple.getArch() == llvm::Triple::aarch64_32; + options.IsCXX = astContext.getLangOpts().CPlusPlus; + options.IsMicrosoftCXXABI = targetInfo.getCXXABI().isMicrosoft(); + options.CompatInfo.IsMatrixHA = compat > clang::LangOptions::ClangABI::Ver23; + return options; +} + mlir::LogicalResult runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext, clang::ASTContext &astContext, bool enableVerifier, @@ -116,8 +151,8 @@ runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext, if (enableCallConvLowering) { // CallConvLowering rewrites signatures and call sites using the classifier, // so it must run after CXXABILowering has lowered C++ ABI types to plain - // records the classifier can handle. Only the x86_64 System V classifier - // is implemented; other targets are left unchanged. + // records the classifier can handle. Targets without a full LLVM ABI + // library classifier use only the rules implemented by this pass. const clang::TargetInfo &targetInfo = astContext.getTargetInfo(); CallConvTarget target = getCallConvTarget(targetInfo.getTriple()); if (target != CallConvTarget::None) { @@ -135,7 +170,8 @@ runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext, pm.addPass(mlir::createCallConvLoweringPass( target, getX86AVXABILevel(targetInfo.getABI()), allowsX86TargetAttrAvx(astContext, compat), - getX86ABICompatInfo(astContext, compat))); + getX86ABICompatInfo(astContext, compat), + getAArch64ABIOptions(astContext, compat))); } } diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 53bffe02590025..13863d45323555 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -85,7 +85,7 @@ static uint64_t getMemoryFallbackAlignment(mlir::Type cirType, const mlir::DataLayout &dataLayout) { if (auto intTy = mlir::dyn_cast<cir::IntType>(cirType); intTy && intTy.isBitInt()) - return intTy.getABIAlignment(dataLayout, {}); + return dataLayout.getTypeABIAlignment(intTy); return dataLayout.getTypeABIAlignment(llvmMemType); } @@ -4501,14 +4501,14 @@ void ConvertCIRToLLVMPass::runOnOperation() { if (failed(applyPartialConversion(ops, target, std::move(patterns)))) signalPassFailure(); - // Drop the cir.ptr-keyed data-layout entries: they drove pointer-width + // Drop the CIR-type-keyed data-layout entries: they drove CIR layout // queries up to this point, but the LLVM IR exporter rejects CIR types. if (auto dlSpec = mlir::dyn_cast_or_null<mlir::DataLayoutSpecAttr>( module->getAttr(mlir::DLTIDialect::kDataLayoutAttrName))) { llvm::SmallVector<mlir::DataLayoutEntryInterface> kept; for (mlir::DataLayoutEntryInterface entry : dlSpec.getEntries()) { - if (entry.isTypeEntry() && - mlir::isa<cir::PointerType>(mlir::cast<mlir::Type>(entry.getKey()))) + if (entry.isTypeEntry() && mlir::isa<cir::PointerType, cir::IntType>( + mlir::cast<mlir::Type>(entry.getKey()))) continue; kept.push_back(entry); } diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp index 83d6ef6d935ab5..ed59ce28038b65 100644 --- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp +++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/CIR/LoweringHelpers.h" +#include "mlir/Dialect/DLTI/DLTI.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/LLVMIR/LLVMTypes.h" #include "mlir/IR/BuiltinTypes.h" @@ -61,10 +62,17 @@ mlir::Attribute getBitIntStorageAttr(mlir::ConversionPatternRewriter &rewriter, // If we have to do split storage, we are an array of bytes. Split this up // into the array that matches convertTypeForMemory. unsigned numBytes = storageBits / 8; + bool isBigEndian = false; + if (auto endianness = mlir::dyn_cast_if_present<mlir::StringAttr>( + dataLayout.getEndianness())) + isBigEndian = + endianness.getValue() == mlir::DLTIDialect::kDataLayoutEndiannessBig; llvm::SmallVector<mlir::APInt> bytes; bytes.reserve(numBytes); - for (unsigned i = 0; i != numBytes; ++i) - bytes.emplace_back(8, val.extractBitsAsZExtValue(8, i * 8)); + for (unsigned i = 0; i != numBytes; ++i) { + unsigned valueByte = isBigEndian ? numBytes - i - 1 : i; + bytes.emplace_back(8, val.extractBitsAsZExtValue(8, valueByte * 8)); + } auto i8Ty = mlir::IntegerType::get(intTy.getContext(), 8); return mlir::DenseElementsAttr::get( diff --git a/clang/test/CIR/CodeGen/bitint-wide-big-endian.c b/clang/test/CIR/CodeGen/bitint-wide-big-endian.c new file mode 100644 index 00000000000000..d54169b1498a34 --- /dev/null +++ b/clang/test/CIR/CodeGen/bitint-wide-big-endian.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -triple powerpc64-unknown-linux-gnu \ +// RUN: -fexperimental-max-bitint-width=8388608 -fclangir -emit-llvm %s -o - \ +// RUN: | FileCheck %s +// RUN: %clang_cc1 -triple powerpc64-unknown-linux-gnu \ +// RUN: -fexperimental-max-bitint-width=8388608 -emit-llvm %s -o - \ +// RUN: | FileCheck %s + +signed _BitInt(129) one = 1; +signed _BitInt(129) minus_two = -2; + +// CHECK-DAG: @one = global [24 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01", align 8 +// CHECK-DAG: @minus_two = global [24 x i8] c"\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FF\FE", align 8 diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-bitint-targets.c b/clang/test/CIR/CodeGen/call-conv-lowering-bitint-targets.c new file mode 100644 index 00000000000000..3053ad64b9eadf --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-bitint-targets.c @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 -triple i386-unknown-linux-gnu \ +// RUN: -fexperimental-max-bitint-width=8388608 -fclangir -emit-llvm %s -o - \ +// RUN: | FileCheck %s --check-prefix=I386 +// RUN: %clang_cc1 -triple powerpc64-unknown-linux-gnu \ +// RUN: -fexperimental-max-bitint-width=8388608 -fclangir -emit-llvm %s -o - \ +// RUN: | FileCheck %s --check-prefix=PPC64 +// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu \ +// RUN: -fexperimental-max-bitint-width=8388608 -fclangir -emit-llvm %s -o - \ +// RUN: | FileCheck %s --check-prefix=AARCH64 +// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu \ +// RUN: -fexperimental-max-bitint-width=8388608 -fclangir -emit-cir %s -o %t.cir +// RUN: cir-translate -cir-to-llvmir --disable-cc-lowering %t.cir -o - \ +// RUN: | FileCheck %s --check-prefix=AARCH64 + +typedef signed _BitInt(129) i129; +typedef i129 (*fn_t)(i129); + +extern i129 external(i129); + +i129 direct(i129 value) { return external(value); } + +// I386-LABEL: define dso_local void @direct( +// I386-SAME: ptr dead_on_unwind noalias writable sret([20 x i8]) align 4 %{{[^,]+}}, +// I386-SAME: ptr nofreeobj noundef align 4 dead_on_return dereferenceable(20) %{{[^)]+}}) +// I386: load i160, ptr %{{[^,]+}}, align 4 +// I386: call void @external( +// I386-SAME: ptr dead_on_unwind writable sret([20 x i8]) align 4 %{{[^,]+}}, +// I386-SAME: ptr nofreeobj noundef align 4 dead_on_return dereferenceable(20) %{{[^)]+}}) +// I386: declare void @external( +// I386-SAME: ptr dead_on_unwind writable sret([20 x i8]) align 4, +// I386-SAME: ptr nofreeobj noundef align 4 dead_on_return dereferenceable(20)) + +// PPC64-LABEL: define dso_local void @direct( +// PPC64-SAME: ptr dead_on_unwind noalias writable sret([24 x i8]) align 8 %{{[^,]+}}, +// PPC64-SAME: ptr noundef byval([24 x i8]) align 8 %{{[^)]+}}) +// PPC64: load i192, ptr %{{[^,]+}}, align 8 +// PPC64: call void @external( +// PPC64-SAME: ptr dead_on_unwind writable sret([24 x i8]) align 8 %{{[^,]+}}, +// PPC64-SAME: ptr noundef byval([24 x i8]) align 8 %{{[^)]+}}) +// PPC64: declare void @external( +// PPC64-SAME: ptr dead_on_unwind writable sret([24 x i8]) align 8, +// PPC64-SAME: ptr noundef byval([24 x i8]) align 8) + +// AARCH64-LABEL: define dso_local void @direct( +// AARCH64-SAME: ptr dead_on_unwind noalias writable sret(i256) align 16 %{{[^,]+}}, +// AARCH64-SAME: ptr nofreeobj noundef align 16 dead_on_return dereferenceable(32) %{{[^)]+}}) +// AARCH64: load i256, ptr %{{[^,]+}}, align 16 +// AARCH64: call void @external( +// AARCH64-SAME: ptr dead_on_unwind writable sret(i256) align 16 %{{[^,]+}}, +// AARCH64-SAME: ptr nofreeobj noundef align 16 dead_on_return dereferenceable(32) %{{[^)]+}}) +// AARCH64: declare void @external( +// AARCH64-SAME: ptr dead_on_unwind writable sret(i256) align 16, +// AARCH64-SAME: ptr nofreeobj noundef align 16 dead_on_return dereferenceable(32)) + +i129 indirect(fn_t fn, i129 value) { return fn(value); } + +// I386-LABEL: define dso_local void @indirect( +// I386: call void %{{[^ (]+}}( +// I386-SAME: ptr dead_on_unwind writable sret([20 x i8]) align 4 %{{[^,]+}}, +// I386-SAME: ptr nofreeobj noundef align 4 dead_on_return dereferenceable(20) %{{[^)]+}}) + +// PPC64-LABEL: define dso_local void @indirect( +// PPC64: call void %{{[^ (]+}}( +// PPC64-SAME: ptr dead_on_unwind writable sret([24 x i8]) align 8 %{{[^,]+}}, +// PPC64-SAME: ptr noundef byval([24 x i8]) align 8 %{{[^)]+}}) + +// AARCH64-LABEL: define dso_local void @indirect( +// AARCH64: call void %{{[^ (]+}}( +// AARCH64-SAME: ptr dead_on_unwind writable sret(i256) align 16 %{{[^,]+}}, +// AARCH64-SAME: ptr nofreeobj noundef align 16 dead_on_return dereferenceable(32) %{{[^)]+}}) + +signed _BitInt(31) extend31(signed _BitInt(31) value) { return value; } +signed _BitInt(33) extend33(signed _BitInt(33) value) { return value; } +_Bool extend_bool(_Bool value) { return value; } + +// I386-LABEL: define dso_local signext i31 @extend31(i31 noundef signext +// I386-LABEL: define dso_local i33 @extend33(i33 noundef +// I386-LABEL: define dso_local zeroext i1 @extend_bool(i1 noundef zeroext +// PPC64-LABEL: define dso_local signext i31 @extend31(i31 noundef signext +// PPC64-LABEL: define dso_local signext i33 @extend33(i33 noundef signext +// PPC64-LABEL: define dso_local zeroext i1 @extend_bool(i1 noundef zeroext +// AARCH64-LABEL: define dso_local i31 @extend31(i31 noundef +// AARCH64-LABEL: define dso_local i33 @extend33(i33 noundef +// AARCH64-LABEL: define dso_local i1 @extend_bool(i1 noundef + +signed _BitInt(64) boundary64(signed _BitInt(64) value) { return value; } +signed _BitInt(65) boundary65(signed _BitInt(65) value) { return value; } +signed _BitInt(128) boundary128(signed _BitInt(128) value) { return value; } + +// I386-LABEL: define dso_local i64 @boundary64(i64 noundef +// I386-LABEL: define dso_local void @boundary65( +// I386-SAME: ptr dead_on_unwind noalias writable sret([12 x i8]) align 4 %{{[^,]+}}, +// I386-SAME: ptr nofreeobj noundef align 4 dead_on_return dereferenceable(12) %{{[^)]+}}) +// I386-LABEL: define dso_local void @boundary128( +// I386-SAME: ptr dead_on_unwind noalias writable sret(i128) align 4 %{{[^,]+}}, +// I386-SAME: ptr nofreeobj noundef align 4 dead_on_return dereferenceable(16) %{{[^)]+}}) +// PPC64-LABEL: define dso_local i64 @boundary64(i64 noundef +// PPC64-LABEL: define dso_local i65 @boundary65(i65 noundef +// PPC64-LABEL: define dso_local i128 @boundary128(i128 noundef +// AARCH64-LABEL: define dso_local i64 @boundary64(i64 noundef +// AARCH64-LABEL: define dso_local i65 @boundary65(i65 noundef +// AARCH64-LABEL: define dso_local i128 @boundary128(i128 noundef diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-bitint-vaarg-unsupported.c b/clang/test/CIR/CodeGen/call-conv-lowering-bitint-vaarg-unsupported.c new file mode 100644 index 00000000000000..6df759c49df6d5 --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-bitint-vaarg-unsupported.c @@ -0,0 +1,16 @@ +// RUN: not %clang_cc1 -triple i386-unknown-linux-gnu -fclangir -emit-llvm \ +// RUN: %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=I386 +// RUN: not %clang_cc1 -triple powerpc64-unknown-linux-gnu -fclangir \ +// RUN: -emit-llvm %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=PPC64 +// RUN: not %clang_cc1 -triple aarch64-unknown-linux-gnu -fclangir -emit-llvm \ +// RUN: %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=AARCH64 + +typedef signed _BitInt(31) value_t; + +value_t fetch(__builtin_va_list ap) { + return __builtin_va_arg(ap, value_t); +} + +// I386: error: 'cir.va_arg' op i386 va_arg lowering for _BitInt not yet implemented in CallConvLowering +// PPC64: error: 'cir.va_arg' op PowerPC64 va_arg lowering for _BitInt not yet implemented in CallConvLowering +// AARCH64: error: 'cir.va_arg' op AArch64 va_arg lowering for _BitInt not yet implemented in CallConvLowering diff --git a/clang/tools/cir-translate/cir-translate.cpp b/clang/tools/cir-translate/cir-translate.cpp index cefa7d2996f300..3e882217df7ca1 100644 --- a/clang/tools/cir-translate/cir-translate.cpp +++ b/clang/tools/cir-translate/cir-translate.cpp @@ -114,7 +114,8 @@ llvm::LogicalResult prepareCIRModuleDataLayout(mlir::ModuleOp mod, context->loadDialect<mlir::DLTIDialect, mlir::LLVM::LLVMDialect, mlir::omp::OpenMPDialect>(); - cir::setMLIRDataLayout(mod, llvm::DataLayout(layoutString)); + cir::setMLIRDataLayout(mod, llvm::DataLayout(layoutString), + targetInfo->getBitIntMaxAlign()); return llvm::success(); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
