[clang] [CIR] Add target ABI lowering for wide _BitInt (PR #225942)
Kunal Dubey via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 25 02:53:18 PDT 2026
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 <xakep8 at protonmail.com>
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();
}
More information about the cfe-commits
mailing list