[clang] [CIR] Add support for __builtin_alloca (PR #157116)
Morris Hafner via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 8 08:38:43 PDT 2025
https://github.com/mmha updated https://github.com/llvm/llvm-project/pull/157116
>From 4121aecbda6be80161eb8fe22c351fadd5bbe62a Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Fri, 5 Sep 2025 16:27:28 +0200
Subject: [PATCH 1/2] [CIR] Add support for __builtin_alloca
This patch adds support for the alloca builtin and extends AllocaOp with a dynamic size argument.
---
.../CIR/Dialect/Builder/CIRBaseBuilder.h | 16 ++++++
clang/include/clang/CIR/Dialect/IR/CIROps.td | 21 +++++++-
clang/include/clang/CIR/MissingFeatures.h | 1 -
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 51 +++++++++++++++++++
.../CIR/Dialect/Transforms/HoistAllocas.cpp | 3 +-
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 9 ++--
clang/test/CIR/CodeGen/builtin_call.cpp | 10 ++++
clang/test/CIR/IR/alloca.cir | 32 ++++++++++++
8 files changed, 137 insertions(+), 6 deletions(-)
create mode 100644 clang/test/CIR/IR/alloca.cir
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 8dc4ca2f9c4e1..a3f167e3cde2c 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -221,6 +221,22 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return cir::ConstPtrAttr::get(type, getI64IntegerAttr(value));
}
+ mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
+ mlir::Type type, llvm::StringRef name,
+ mlir::IntegerAttr alignment,
+ mlir::Value dynAllocSize) {
+ return cir::AllocaOp::create(*this, loc, addrType, type, name, alignment,
+ dynAllocSize);
+ }
+
+ mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
+ mlir::Type type, llvm::StringRef name,
+ clang::CharUnits alignment,
+ mlir::Value dynAllocSize) {
+ mlir::IntegerAttr alignmentAttr = getAlignmentAttr(alignment);
+ return createAlloca(loc, addrType, type, name, alignmentAttr, dynAllocSize);
+ }
+
mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
mlir::Type type, llvm::StringRef name,
mlir::IntegerAttr alignment) {
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 4592078af966b..63aea2c404b54 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -341,6 +341,11 @@ def CIR_AllocaOp : CIR_Op<"alloca", [
The presence of the `const` attribute indicates that the local variable is
declared with C/C++ `const` keyword.
+ The `dynAllocSize` specifies the size to dynamically allocate on the stack
+ and ignores the allocation size based on the original type. This is useful
+ when handling VLAs or the `alloca` builtin and is omitted when declaring
+ regular local variables.
+
The result type is a pointer to the input's type.
Example:
@@ -356,6 +361,7 @@ def CIR_AllocaOp : CIR_Op<"alloca", [
}];
let arguments = (ins
+ Optional<CIR_AnyFundamentalIntType>:$dynAllocSize,
TypeAttr:$allocaType,
StrAttr:$name,
UnitAttr:$init,
@@ -372,16 +378,29 @@ def CIR_AllocaOp : CIR_Op<"alloca", [
OpBuilder<(ins "mlir::Type":$addr,
"mlir::Type":$allocaType,
"llvm::StringRef":$name,
- "mlir::IntegerAttr":$alignment)>
+ "mlir::IntegerAttr":$alignment)>,
+
+ OpBuilder<(ins "mlir::Type":$addr,
+ "mlir::Type":$allocaType,
+ "llvm::StringRef":$name,
+ "mlir::IntegerAttr":$alignment,
+ "mlir::Value":$dynAllocSize),
+ [{
+ if (dynAllocSize)
+ $_state.addOperands(dynAllocSize);
+ build($_builder, $_state, addr, allocaType, name, alignment);
+ }]>
];
let extraClassDeclaration = [{
// Whether the alloca input type is a pointer.
bool isPointerType() { return ::mlir::isa<::cir::PointerType>(getAllocaType()); }
+ bool isDynamic() { return (bool)getDynAllocSize(); }
}];
let assemblyFormat = [{
$allocaType `,` qualified(type($addr)) `,`
+ ($dynAllocSize^ `:` type($dynAllocSize) `,`)?
`[` $name
(`,` `init` $init^)?
(`,` `const` $constant^)?
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 948e3feca2bb5..1e64278d118b5 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -62,7 +62,6 @@ struct MissingFeatures {
static bool opAllocaEscapeByReference() { return false; }
static bool opAllocaReference() { return false; }
static bool opAllocaAnnotations() { return false; }
- static bool opAllocaDynAllocSize() { return false; }
static bool opAllocaCaptureByInit() { return false; }
// FuncOp handling
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index b68e91f64dc84..ddd95466dc1d0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -149,6 +149,57 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
emitVAEnd(emitVAListRef(e->getArg(0)).getPointer());
return {};
+ case Builtin::BIalloca:
+ case Builtin::BI_alloca:
+ case Builtin::BI__builtin_alloca_uninitialized:
+ case Builtin::BI__builtin_alloca: {
+ // Get alloca size input
+ mlir::Value size = emitScalarExpr(e->getArg(0));
+
+ // The alignment of the alloca should correspond to __BIGGEST_ALIGNMENT__.
+ const TargetInfo &TI = getContext().getTargetInfo();
+ const CharUnits SuitableAlignmentInBytes =
+ getContext().toCharUnitsFromBits(TI.getSuitableAlign());
+
+ // Emit the alloca op with type `u8 *` to match the semantics of
+ // `llvm.alloca`. We later bitcast the type to `void *` to match the
+ // semantics of C/C++
+ // FIXME(cir): It may make sense to allow AllocaOp of type `u8` to return a
+ // pointer of type `void *`. This will require a change to the allocaOp
+ // verifier.
+ auto allocaAddr = builder.createAlloca(
+ getLoc(e->getSourceRange()), builder.getUInt8PtrTy(),
+ builder.getUInt8Ty(), "bi_alloca", SuitableAlignmentInBytes, size);
+
+ // Initialize the allocated buffer if required.
+ if (builtinID != Builtin::BI__builtin_alloca_uninitialized) {
+ // Initialize the alloca with the given size and alignment according to
+ // the lang opts. Only the trivial non-initialization is supported for
+ // now.
+
+ switch (getLangOpts().getTrivialAutoVarInit()) {
+ case LangOptions::TrivialAutoVarInitKind::Uninitialized:
+ // Nothing to initialize.
+ break;
+ case LangOptions::TrivialAutoVarInitKind::Zero:
+ case LangOptions::TrivialAutoVarInitKind::Pattern:
+ cgm.errorNYI("trivial auto var init");
+ break;
+ }
+ }
+
+ // An alloca will always return a pointer to the alloca (stack) address
+ // space. This address space need not be the same as the AST / Language
+ // default (e.g. in C / C++ auto vars are in the generic address space). At
+ // the AST level this is handled within CreateTempAlloca et al., but for the
+ // builtin / dynamic alloca we have to handle it here.
+ assert(!cir::MissingFeatures::addressSpace());
+
+ // Bitcast the alloca to the expected type.
+ return RValue::get(
+ builder.createBitcast(allocaAddr, builder.getVoidPtrTy()));
+ }
+
case Builtin::BIfabs:
case Builtin::BIfabsf:
case Builtin::BIfabsl:
diff --git a/clang/lib/CIR/Dialect/Transforms/HoistAllocas.cpp b/clang/lib/CIR/Dialect/Transforms/HoistAllocas.cpp
index 4e0a041d26ce1..72bbf08c79b16 100644
--- a/clang/lib/CIR/Dialect/Transforms/HoistAllocas.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/HoistAllocas.cpp
@@ -42,7 +42,8 @@ static void process(mlir::ModuleOp mod, cir::FuncOp func) {
if (alloca->getBlock() == &entryBlock)
return;
// Don't hoist allocas with dynamic alloca size.
- assert(!cir::MissingFeatures::opAllocaDynAllocSize());
+ if (alloca.getDynAllocSize())
+ return;
// Hoist allocas into the entry block.
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index ee9f58c829ca9..8e8cd2a63dcd5 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1053,9 +1053,12 @@ mlir::LogicalResult CIRToLLVMBaseClassAddrOpLowering::matchAndRewrite(
mlir::LogicalResult CIRToLLVMAllocaOpLowering::matchAndRewrite(
cir::AllocaOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
- assert(!cir::MissingFeatures::opAllocaDynAllocSize());
- mlir::Value size = rewriter.create<mlir::LLVM::ConstantOp>(
- op.getLoc(), typeConverter->convertType(rewriter.getIndexType()), 1);
+ mlir::Value size =
+ op.isDynamic() ? adaptor.getDynAllocSize()
+ : rewriter.create<mlir::LLVM::ConstantOp>(
+ op.getLoc(),
+ typeConverter->convertType(rewriter.getIndexType()),
+ rewriter.getIntegerAttr(rewriter.getIndexType(), 1));
mlir::Type elementTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getAllocaType());
mlir::Type resultTy =
diff --git a/clang/test/CIR/CodeGen/builtin_call.cpp b/clang/test/CIR/CodeGen/builtin_call.cpp
index 09be7937a4330..d416841c5d6fe 100644
--- a/clang/test/CIR/CodeGen/builtin_call.cpp
+++ b/clang/test/CIR/CodeGen/builtin_call.cpp
@@ -258,3 +258,13 @@ void trap2() {
// LLVM: {{.+}}:
// LLVM-NEXT: call void @_Z2f1v()
// LLVM: }
+
+void *test_alloca(unsigned long n) {
+ return __builtin_alloca(n);
+}
+
+// CIR-LABEL: @_Z11test_allocam(
+// CIR: %{{.+}} = cir.alloca !u8i, !cir.ptr<!u8i>, %{{.+}} : !u64i, ["bi_alloca"]
+
+// LLVM-LABEL: @_Z11test_allocam(
+// LLVM: alloca i8, i64 %{{.+}}
diff --git a/clang/test/CIR/IR/alloca.cir b/clang/test/CIR/IR/alloca.cir
new file mode 100644
index 0000000000000..12f7e6ac6a914
--- /dev/null
+++ b/clang/test/CIR/IR/alloca.cir
@@ -0,0 +1,32 @@
+
+// RUN: cir-opt %s | FileCheck %s
+
+!u64i = !cir.int<u, 64>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+module {
+ cir.func dso_local @_Z11test_allocam(%arg0: !u64i) -> !cir.ptr<!void> {
+ %0 = cir.alloca !u64i, !cir.ptr<!u64i>, ["n", init] {alignment = 8 : i64}
+ %1 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["__retval"] {alignment = 8 : i64}
+ cir.store %arg0, %0 : !u64i, !cir.ptr<!u64i>
+ %2 = cir.load align(8) %0 : !cir.ptr<!u64i>, !u64i
+ // Dynamically sized alloca
+ %3 = cir.alloca !u8i, !cir.ptr<!u8i>, %2 : !u64i, ["bi_alloca"] {alignment = 16 : i64}
+ %4 = cir.cast(bitcast, %3 : !cir.ptr<!u8i>), !cir.ptr<!void>
+ cir.store %4, %1 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
+ %5 = cir.load %1 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+ cir.return %5 : !cir.ptr<!void>
+ }
+
+ // CHECK: cir.func dso_local @_Z11test_allocam(%arg0: !u64i) -> !cir.ptr<!void> {
+ // CHECK: %0 = cir.alloca !u64i, !cir.ptr<!u64i>, ["n", init] {alignment = 8 : i64}
+ // CHECK: %1 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["__retval"] {alignment = 8 : i64}
+ // CHECK: cir.store %arg0, %0 : !u64i, !cir.ptr<!u64i>
+ // CHECK: %2 = cir.load align(8) %0 : !cir.ptr<!u64i>, !u64i
+ // CHECK: %3 = cir.alloca !u8i, !cir.ptr<!u8i>, %2 : !u64i, ["bi_alloca"] {alignment = 16 : i64}
+ // CHECK: %4 = cir.cast(bitcast, %3 : !cir.ptr<!u8i>), !cir.ptr<!void>
+ // CHECK: cir.store %4, %1 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
+ // CHECK: %5 = cir.load %1 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+ // CHECK: cir.return %5 : !cir.ptr<!void>
+ // CHECK: }
+}
>From 073ace995467000014769e2ba1da77c6ce6dffe8 Mon Sep 17 00:00:00 2001
From: Morris Hafner <mhafner at nvidia.com>
Date: Mon, 8 Sep 2025 17:37:05 +0200
Subject: [PATCH 2/2] Address review feedback
---
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 10 ++---
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 10 ++---
clang/test/CIR/CodeGen/builtin_call.cpp | 38 +++++++++++++++++++
3 files changed, 48 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index ddd95466dc1d0..7d410dbf07a01 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -157,9 +157,9 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
mlir::Value size = emitScalarExpr(e->getArg(0));
// The alignment of the alloca should correspond to __BIGGEST_ALIGNMENT__.
- const TargetInfo &TI = getContext().getTargetInfo();
- const CharUnits SuitableAlignmentInBytes =
- getContext().toCharUnitsFromBits(TI.getSuitableAlign());
+ const TargetInfo &ti = getContext().getTargetInfo();
+ const CharUnits suitableAlignmentInBytes =
+ getContext().toCharUnitsFromBits(ti.getSuitableAlign());
// Emit the alloca op with type `u8 *` to match the semantics of
// `llvm.alloca`. We later bitcast the type to `void *` to match the
@@ -167,9 +167,9 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
// FIXME(cir): It may make sense to allow AllocaOp of type `u8` to return a
// pointer of type `void *`. This will require a change to the allocaOp
// verifier.
- auto allocaAddr = builder.createAlloca(
+ mlir::Value allocaAddr = builder.createAlloca(
getLoc(e->getSourceRange()), builder.getUInt8PtrTy(),
- builder.getUInt8Ty(), "bi_alloca", SuitableAlignmentInBytes, size);
+ builder.getUInt8Ty(), "bi_alloca", suitableAlignmentInBytes, size);
// Initialize the allocated buffer if required.
if (builtinID != Builtin::BI__builtin_alloca_uninitialized) {
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 8e8cd2a63dcd5..ba2a7f4075351 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1054,11 +1054,11 @@ mlir::LogicalResult CIRToLLVMAllocaOpLowering::matchAndRewrite(
cir::AllocaOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value size =
- op.isDynamic() ? adaptor.getDynAllocSize()
- : rewriter.create<mlir::LLVM::ConstantOp>(
- op.getLoc(),
- typeConverter->convertType(rewriter.getIndexType()),
- rewriter.getIntegerAttr(rewriter.getIndexType(), 1));
+ op.isDynamic()
+ ? adaptor.getDynAllocSize()
+ : rewriter.create<mlir::LLVM::ConstantOp>(
+ op.getLoc(),
+ typeConverter->convertType(rewriter.getIndexType()), 1);
mlir::Type elementTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getAllocaType());
mlir::Type resultTy =
diff --git a/clang/test/CIR/CodeGen/builtin_call.cpp b/clang/test/CIR/CodeGen/builtin_call.cpp
index d416841c5d6fe..853d894a3311b 100644
--- a/clang/test/CIR/CodeGen/builtin_call.cpp
+++ b/clang/test/CIR/CodeGen/builtin_call.cpp
@@ -211,6 +211,10 @@ void unreachable() {
// LLVM: unreachable
// LLVM: }
+// OGCG-LABEL: @_Z11unreachablev
+// OGCG: unreachable
+// OGCG: }
+
void f1();
void unreachable2() {
__builtin_unreachable();
@@ -229,6 +233,9 @@ void unreachable2() {
// LLVM-NEXT: call void @_Z2f1v()
// LLVM: }
+// OGCG-LABEL: @_Z12unreachable2v
+// OGCG: unreachable
+
void trap() {
__builtin_trap();
}
@@ -241,6 +248,10 @@ void trap() {
// LLVM: call void @llvm.trap()
// LLVM: }
+// OGCG-LABEL: @_Z4trapv
+// OGCG: call void @llvm.trap()
+// OGCG: }
+
void trap2() {
__builtin_trap();
f1();
@@ -259,6 +270,12 @@ void trap2() {
// LLVM-NEXT: call void @_Z2f1v()
// LLVM: }
+// OGCG-LABEL: define{{.*}} void @_Z5trap2v
+// OGCG: call void @llvm.trap()
+// OGCG-NEXT: call void @_Z2f1v()
+// OGCG: ret void
+// OGCG: }
+
void *test_alloca(unsigned long n) {
return __builtin_alloca(n);
}
@@ -268,3 +285,24 @@ void *test_alloca(unsigned long n) {
// LLVM-LABEL: @_Z11test_allocam(
// LLVM: alloca i8, i64 %{{.+}}
+
+// OGCG-LABEL: @_Z11test_allocam(
+// OGCG: alloca i8, i64 %{{.+}}
+
+bool test_multiple_allocas(unsigned long n) {
+ void *a = __builtin_alloca(n);
+ void *b = __builtin_alloca(n);
+ return a != b;
+}
+
+// CIR-LABEL: @_Z21test_multiple_allocasm(
+// CIR: %{{.+}} = cir.alloca !u8i, !cir.ptr<!u8i>, %{{.+}} : !u64i, ["bi_alloca"]
+// CIR: %{{.+}} = cir.alloca !u8i, !cir.ptr<!u8i>, %{{.+}} : !u64i, ["bi_alloca"]
+
+// LLVM-LABEL: @_Z21test_multiple_allocasm(
+// LLVM: alloca i8, i64 %{{.+}}
+// LLVM: alloca i8, i64 %{{.+}}
+
+// OGCG-LABEL: @_Z21test_multiple_allocasm(
+// OGCG: alloca i8, i64 %{{.+}}
+// OGCG: alloca i8, i64 %{{.+}}
More information about the cfe-commits
mailing list