[clang] [CIR][ARM] Add base 32-bit ARM (GenericARM) codegen and lowering (PR #204360)

Akshay K via cfe-commits cfe-commits at lists.llvm.org
Sun Aug 2 18:08:31 PDT 2026


https://github.com/kumarak updated https://github.com/llvm/llvm-project/pull/204360

>From 35ada36667fb6840a96642317c75a8f2cc4841a4 Mon Sep 17 00:00:00 2001
From: AkshayK <iit.akshay at gmail.com>
Date: Tue, 30 Jun 2026 12:39:21 -0400
Subject: [PATCH 1/2] [CIR][ARM] Add base 32-bit ARM (GenericARM) codegen and
 lowering

Add the GenericARM transform-pass CXXABI dispatch (ARM method-pointer ABI), drive the size_t width of exception allocation and cir.copy memcpy from the data layout, and implement the NEON vget_lane/vgetq_lane intrinsics in CIRGenBuiltinAArch64.cpp.
---
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp       |  4 +--
 .../lib/CIR/CodeGen/CIRGenBuiltinAArch64.cpp  | 36 +++++++++++++++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  4 +++
 .../TargetLowering/LowerItaniumCXXABI.cpp     |  8 +++++
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 26 +++++++++-----
 .../CodeGen/ARM/arm-aggregate-copy-size.cpp   | 21 +++++++++++
 .../CIR/CodeGen/ARM/arm-record-layout.cpp     | 36 +++++++++++++++++++
 .../CIR/CodeGen/ARM/arm-throw-alloc-size.cpp  | 22 ++++++++++++
 .../CodeGenBuiltins/ARM/arm-neon-vget-lane.c  | 25 +++++++++++++
 9 files changed, 171 insertions(+), 11 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/ARM/arm-aggregate-copy-size.cpp
 create mode 100644 clang/test/CIR/CodeGen/ARM/arm-record-layout.cpp
 create mode 100644 clang/test/CIR/CodeGen/ARM/arm-throw-alloc-size.cpp
 create mode 100644 clang/test/CIR/CodeGenBuiltins/ARM/arm-neon-vget-lane.c

diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 874f5188ae009..a578b8acfbc74 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2777,9 +2777,7 @@ emitTargetArchBuiltinExpr(CIRGenFunction *cgf, unsigned builtinID,
   case llvm::Triple::armeb:
   case llvm::Triple::thumb:
   case llvm::Triple::thumbeb:
-    // These are actually NYI, but that will be reported by emitBuiltinExpr.
-    // At this point, we don't even know that the builtin is target-specific.
-    return std::nullopt;
+    return cgf->emitARMBuiltinExpr(builtinID, e, returnValue, arch);
   case llvm::Triple::aarch64:
   case llvm::Triple::aarch64_32:
   case llvm::Triple::aarch64_be:
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinAArch64.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinAArch64.cpp
index dbc42404e11a4..77ae603221d15 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinAArch64.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinAArch64.cpp
@@ -1991,6 +1991,42 @@ static const std::pair<unsigned, unsigned> neonEquivalentIntrinsicMap[] = {
      NEON::BI__builtin_neon_vstl1q_lane_s64},
 };
 
+std::optional<mlir::Value>
+CIRGenFunction::emitARMBuiltinExpr(unsigned builtinID, const CallExpr *expr,
+                                   ReturnValueSlot returnValue,
+                                   llvm::Triple::ArchType arch) {
+  // Only the NEON lane-read intrinsics are implemented for 32-bit ARM; they
+  // lower to a vector element extraction. Other ARM builtins report errorNYI.
+  switch (builtinID) {
+  case NEON::BI__builtin_neon_vget_lane_i8:
+  case NEON::BI__builtin_neon_vget_lane_i16:
+  case NEON::BI__builtin_neon_vget_lane_i32:
+  case NEON::BI__builtin_neon_vget_lane_i64:
+  case NEON::BI__builtin_neon_vget_lane_bf16:
+  case NEON::BI__builtin_neon_vget_lane_f32:
+  case NEON::BI__builtin_neon_vgetq_lane_i8:
+  case NEON::BI__builtin_neon_vgetq_lane_i16:
+  case NEON::BI__builtin_neon_vgetq_lane_i32:
+  case NEON::BI__builtin_neon_vgetq_lane_i64:
+  case NEON::BI__builtin_neon_vgetq_lane_bf16:
+  case NEON::BI__builtin_neon_vgetq_lane_f32:
+  case NEON::BI__builtin_neon_vduph_lane_bf16:
+  case NEON::BI__builtin_neon_vduph_laneq_bf16: {
+    mlir::Location loc = getLoc(expr->getExprLoc());
+    mlir::Value vec = emitScalarExpr(expr->getArg(0));
+    mlir::Value index = emitScalarExpr(expr->getArg(1));
+    return cir::VecExtractOp::create(builder, loc, vec, index);
+  }
+  default:
+    break;
+  }
+
+  cgm.errorNYI(expr->getSourceRange(),
+               std::string("unimplemented ARM builtin call: ") +
+                   getContext().BuiltinInfo.getName(builtinID));
+  return mlir::Value{};
+}
+
 std::optional<mlir::Value>
 CIRGenFunction::emitAArch64BuiltinExpr(unsigned builtinID, const CallExpr *expr,
                                        ReturnValueSlot returnValue,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 4ba3ee59f49b0..830d3f9f14054 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1585,6 +1585,10 @@ class CIRGenFunction : public CIRGenTypeCache {
   emitAArch64BuiltinExpr(unsigned builtinID, const CallExpr *expr,
                          ReturnValueSlot returnValue,
                          llvm::Triple::ArchType arch);
+  std::optional<mlir::Value> emitARMBuiltinExpr(unsigned builtinID,
+                                                const CallExpr *expr,
+                                                ReturnValueSlot returnValue,
+                                                llvm::Triple::ArchType arch);
   std::optional<mlir::Value> emitAArch64SMEBuiltinExpr(unsigned builtinID,
                                                        const CallExpr *expr);
   std::optional<mlir::Value> emitAArch64SVEBuiltinExpr(unsigned builtinID,
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 6c276a83f18cf..3bcaa42f249ea 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -148,6 +148,14 @@ std::unique_ptr<CIRCXXABI> createItaniumCXXABI(LowerModule &lm) {
         /*useARMMethodPtrABI=*/true,
         /*use32BitVTableOffsetABI=*/true);
 
+  case clang::TargetCXXABI::GenericARM:
+    // 32-bit ARM uses the ARM method-pointer encoding but, unlike AppleARM64,
+    // does not use 32-bit vtable offsets.
+    return std::make_unique<LowerItaniumCXXABI>(
+        lm,
+        /*useARMMethodPtrABI=*/true,
+        /*use32BitVTableOffsetABI=*/false);
+
   case clang::TargetCXXABI::GenericItanium:
     return std::make_unique<LowerItaniumCXXABI>(lm);
 
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 3660c86378a9c..7a4ac6e2e32c9 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -287,13 +287,21 @@ static mlir::LLVM::CConv convertCallingConv(cir::CallingConv callingConv) {
   llvm_unreachable("Unknown calling convention");
 }
 
+/// Returns the integer type for the target's size_t, mirroring
+/// CodeGenModule::SizeTy: an integer as wide as the target pointer.
+static mlir::IntegerType getSizeTType(mlir::MLIRContext *ctx,
+                                      const mlir::DataLayout &dataLayout) {
+  return mlir::IntegerType::get(
+      ctx, dataLayout.getTypeSizeInBits(mlir::LLVM::LLVMPointerType::get(ctx)));
+}
+
 mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
     cir::CopyOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
-  mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
+  // The llvm.memcpy length is size_t-wide, so it cannot be hardcoded to i64.
+  mlir::Type lenTy = getSizeTType(rewriter.getContext(), dataLayout);
   const mlir::Value length = mlir::LLVM::ConstantOp::create(
-      rewriter, op.getLoc(), rewriter.getI64Type(),
-      op.getCopySizeInBytes(layout));
+      rewriter, op.getLoc(), lenTy, op.getCopySizeInBytes(dataLayout));
   assert(!cir::MissingFeatures::aggValueSlotVolatile());
 
   uint64_t dstTypeAlign = dataLayout.getTypeABIAlignment(convertTypeForMemory(
@@ -4391,15 +4399,17 @@ mlir::LogicalResult CIRToLLVMThrowOpLowering::matchAndRewrite(
 mlir::LogicalResult CIRToLLVMAllocExceptionOpLowering::matchAndRewrite(
     cir::AllocExceptionOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
-  // Get or create `declare ptr @__cxa_allocate_exception(i64)`
+  // Get or create `declare ptr @__cxa_allocate_exception(size_t)`. thrown_size
+  // is size_t, so hardcoding i64 mismatches the runtime on 32-bit targets.
   StringRef fnName = "__cxa_allocate_exception";
   auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
-  auto int64Ty = mlir::IntegerType::get(rewriter.getContext(), 64);
-  auto fnTy = mlir::LLVM::LLVMFunctionType::get(llvmPtrTy, {int64Ty});
+  mlir::IntegerType sizeTTy = getSizeTType(rewriter.getContext(), dataLayout);
+  auto fnTy = mlir::LLVM::LLVMFunctionType::get(llvmPtrTy, {sizeTTy});
 
   createLLVMFuncOpIfNotExist(rewriter, op, fnName, fnTy);
-  auto exceptionSize = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
-                                                      adaptor.getSizeAttr());
+  auto exceptionSize = mlir::LLVM::ConstantOp::create(
+      rewriter, op.getLoc(), sizeTTy,
+      rewriter.getIntegerAttr(sizeTTy, op.getSize()));
 
   auto allocaExceptionCall = mlir::LLVM::CallOp::create(
       rewriter, op.getLoc(), mlir::TypeRange{llvmPtrTy}, fnName,
diff --git a/clang/test/CIR/CodeGen/ARM/arm-aggregate-copy-size.cpp b/clang/test/CIR/CodeGen/ARM/arm-aggregate-copy-size.cpp
new file mode 100644
index 0000000000000..e2f6e38ced7b2
--- /dev/null
+++ b/clang/test/CIR/CodeGen/ARM/arm-aggregate-copy-size.cpp
@@ -0,0 +1,21 @@
+// The llvm.memcpy length for a cir.copy (aggregate pass-by-value) is size_t-wide:
+// i32 on 32-bit ARM, not the i64 used by 64-bit targets.
+//
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=ARM --input-file=%t.ll %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-x86.ll
+// RUN: FileCheck --check-prefix=X86 --input-file=%t-x86.ll %s
+
+struct P { int x; int y; };
+int sum(P p);
+int use() { P p; p.x = 1; p.y = 2; return sum(p); }
+
+// The cir.copy memcpy length width is resolved later, during lowering to LLVM.
+// CIR-LABEL: cir.func{{.*}} @_Z3usev()
+// CIR: cir.copy {{.*}} : !cir.ptr<!rec_P>
+
+// ARM: call void @llvm.memcpy.p0.p0.i32(ptr {{.*}}, ptr {{.*}}, i32 8, i1 false)
+
+// X86: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}}, ptr {{.*}}, i64 8, i1 false)
diff --git a/clang/test/CIR/CodeGen/ARM/arm-record-layout.cpp b/clang/test/CIR/CodeGen/ARM/arm-record-layout.cpp
new file mode 100644
index 0000000000000..82a96911c02e0
--- /dev/null
+++ b/clang/test/CIR/CodeGen/ARM/arm-record-layout.cpp
@@ -0,0 +1,36 @@
+// 32-bit ARM lowers end-to-end through CIR (GenericARM CXXABI, vtables); records
+// and vtables are 4-byte aligned with 4-byte pointers.
+//
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -emit-llvm %s -o %t-ogcg.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t-ogcg.ll %s
+
+struct S {
+  int *p;
+  int x;
+};
+
+S s;
+
+class A {
+public:
+  virtual void f();
+  int x;
+};
+
+void A::f() {}
+
+// CIR-DAG: !rec_S = !cir.struct<"S" {!cir.ptr<!s32i>, !s32i}>
+// CIR-DAG: !rec_A = !cir.struct<class "A" {!cir.vptr, !s32i}>
+// CIR-DAG: !cir.ptr<!cir.void> = #cir.ptr_spec<size = 32, abi = 32, preferred = 32, index = 32>
+// CIR: cir.global external @s = #cir.zero : !rec_S {alignment = 4 : i64}
+// CIR: cir.global {{.*}}@_ZTV1A = #cir.vtable<{{.*}}{alignment = 4 : i64}
+
+// LLVM: @s = global %struct.S zeroinitializer, align 4
+// LLVM: @_ZTV1A = global { [3 x ptr] } {{.*}}, align 4
+
+// OGCG: @s = global %struct.S zeroinitializer, align 4
+// OGCG: @_ZTV1A = {{.*}}constant { [3 x ptr] } {{.*}}, align 4
diff --git a/clang/test/CIR/CodeGen/ARM/arm-throw-alloc-size.cpp b/clang/test/CIR/CodeGen/ARM/arm-throw-alloc-size.cpp
new file mode 100644
index 0000000000000..5899a04da4878
--- /dev/null
+++ b/clang/test/CIR/CodeGen/ARM/arm-throw-alloc-size.cpp
@@ -0,0 +1,22 @@
+// __cxa_allocate_exception's thrown_size is size_t: i32 on 32-bit ARM, not the
+// i64 used by 64-bit targets.
+//
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple arm-linux-gnueabihf -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=ARM --input-file=%t.ll %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-x86.ll
+// RUN: FileCheck --check-prefix=X86 --input-file=%t-x86.ll %s
+
+void f() { throw 42; }
+
+// The size_t width for the __cxa_allocate_exception call is resolved later,
+// during lowering to LLVM.
+// CIR-LABEL: cir.func{{.*}} @_Z1fv()
+// CIR: cir.alloc.exception 4
+
+// ARM: declare ptr @__cxa_allocate_exception(i32)
+// ARM: call ptr @__cxa_allocate_exception(i32 4)
+
+// X86: declare ptr @__cxa_allocate_exception(i64)
+// X86: call ptr @__cxa_allocate_exception(i64 4)
diff --git a/clang/test/CIR/CodeGenBuiltins/ARM/arm-neon-vget-lane.c b/clang/test/CIR/CodeGenBuiltins/ARM/arm-neon-vget-lane.c
new file mode 100644
index 0000000000000..bc7b76c9dcc8a
--- /dev/null
+++ b/clang/test/CIR/CodeGenBuiltins/ARM/arm-neon-vget-lane.c
@@ -0,0 +1,25 @@
+// On 32-bit ARM the NEON vget_lane/vgetq_lane intrinsics lower to
+// __builtin_neon_* (unlike AArch64); check CIR lowers them to a vector extract.
+
+// RUN: %clang_cc1 -triple armv7-unknown-linux-gnueabihf -target-feature +neon -ffreestanding -fclangir -emit-cir %s -o - | FileCheck %s --check-prefix=CIR
+// RUN: %clang_cc1 -triple armv7-unknown-linux-gnueabihf -target-feature +neon -ffreestanding -fclangir -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
+
+#include <arm_neon.h>
+
+// CIR-LABEL: cir.func{{.*}} @get_s32(
+// CIR: cir.vec.extract {{.*}} : !cir.vector<4 x !s32i>
+// LLVM-LABEL: define dso_local i32 @get_s32(
+// LLVM: extractelement <4 x i32> %{{.*}}, i32 2
+int get_s32(int32x4_t v) { return vgetq_lane_s32(v, 2); }
+
+// CIR-LABEL: cir.func{{.*}} @get_f32(
+// CIR: cir.vec.extract {{.*}} : !cir.vector<4 x !cir.float>
+// LLVM-LABEL: define dso_local float @get_f32(
+// LLVM: extractelement <4 x float> %{{.*}}, i32 1
+float get_f32(float32x4_t v) { return vgetq_lane_f32(v, 1); }
+
+// CIR-LABEL: cir.func{{.*}} @get_s16(
+// CIR: cir.vec.extract {{.*}} : !cir.vector<4 x !s16i>
+// LLVM-LABEL: define dso_local i16 @get_s16(
+// LLVM: extractelement <4 x i16> %{{.*}}, i32 3
+short get_s16(int16x4_t v) { return vget_lane_s16(v, 3); }

>From 4a45571c3ce51153b3b360f65d27e55292d4643c Mon Sep 17 00:00:00 2001
From: AkshayK <iit.akshay at gmail.com>
Date: Sun, 2 Aug 2026 21:07:44 -0400
Subject: [PATCH 2/2] [CIR] Fix crash after a NYI diagnostic for target
 builtins

emitBuiltinExpr converts the `mlir::Value{}` "already diagnosed"
sentinel from emitTargetBuiltinExpr into a null RValue. When the
builtin's result is stored, that null reaches createStore and crashes
the compiler after the diagnostic was already emitted.

Return an undef of the expression type instead, matching the
fallthrough path below. getUndefRValue yields the empty RValue for
void, so builtins lowering to a CIR operation with no results are
unchanged.

Reproduces on AArch64 today via __builtin_neon_vcvt_f32_f16.
---
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index a578b8acfbc74..17631e86ad4a3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2733,10 +2733,12 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
   if (std::optional<mlir::Value> rst =
           emitTargetBuiltinExpr(builtinID, e, returnValue)) {
     mlir::Value v = rst.value();
-    // CIR dialect operations may have no results, no values will be returned
-    // even if it executes successfully.
+    // No value comes back either when the builtin was diagnosed as NYI (case 1
+    // above) or when it lowered to a CIR operation with no results. Fall back
+    // to an undef of the expression type; getUndefRValue yields the empty
+    // RValue for void, so the no-result case is unchanged.
     if (!v)
-      return RValue::get(nullptr);
+      return getUndefRValue(e->getType());
 
     switch (evalKind) {
     case cir::TEK_Scalar:



More information about the cfe-commits mailing list