[clang] 61a6a2c - [CIR] Emit cir.libc.memmove and cir.libc.memset for memmove and memset (#227667)

via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 16 00:05:22 PDT 2026


Author: Steffen Larsen
Date: 2026-10-16T07:05:14Z
New Revision: 61a6a2c3dee06a584a8e173e0147ed5cfb64dc74

URL: https://github.com/llvm/llvm-project/commit/61a6a2c3dee06a584a8e173e0147ed5cfb64dc74
DIFF: https://github.com/llvm/llvm-project/commit/61a6a2c3dee06a584a8e173e0147ed5cfb64dc74.diff

LOG: [CIR] Emit cir.libc.memmove and cir.libc.memset for memmove and memset (#227667)

memmove, memset and their __builtin_ forms went through calls to memmove
and memset where classic codegen emits the llvm.memmove and llvm.memset
intrinsics. Emit cir.libc.memmove and cir.libc.memset instead, following
CGBuiltin.cpp and the existing memcpy handling. They lower to the same
intrinsics. __builtin_trivially_relocate shares the memmove path and, as
in classic codegen, scales its count by the element size.

Additionally, this adds alignment to the cir.libc.memmove Op (similar to
memcpy in #225845) and properly sets the alignment for both
cir.libc.memmove and cir.libc.memset.

On top of that, the emitDirectCallee now honors the
__attribute__((no_builtin)) and variants targetting specific builtins.

Fixes https://github.com/llvm/llvm-project/issues/229378.

Assisted-by: Claude Opus 5.5

---------

Signed-off-by: Steffen Holst Larsen <sholstla at amd.com>

Added: 
    clang/test/CIR/CodeGen/no-builtin-call.c
    clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
    clang/test/CIR/IR/libc-memmove.cir

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIROps.td
    clang/lib/CIR/CodeGen/CIRGenBuilder.h
    clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
    clang/lib/CIR/CodeGen/CIRGenExpr.cpp
    clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
    clang/test/CIR/CodeGen/libc.c
    clang/test/CIR/CodeGenBuiltins/builtin-bcopy.cpp
    clang/test/CIR/CodeGenBuiltins/builtin-inline.c

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 91401e64168b9..63d50761806a6 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -5544,10 +5544,15 @@ def CIR_MemMoveOp : CIR_MemOp<"libc.memmove"> {
     ```
   }];
 
-  let arguments = !con(commonArgs, (ins CIR_AnyFundamentalUIntType:$len));
+  let arguments = !con(commonArgs, (ins
+    CIR_AnyFundamentalUIntType:$len,
+    OptionalAttr<I64Attr>:$dst_alignment,
+    OptionalAttr<I64Attr>:$src_alignment
+  ));
 
   let assemblyFormat = [{
-    $len `bytes` `from` $src `to` $dst attr-dict
+    $len `bytes` `from` $src (`align` `(` $src_alignment^ `)`)?
+    `to` $dst (`align` `(` $dst_alignment^ `)`)? attr-dict
     `:` qualified(type($dst)) `,` type($len)
   }];
 

diff  --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 06e66044e7c22..da7baecc2d9fe 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -203,9 +203,12 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
         getI64IntegerAttr(src.getAlignment().getQuantity()));
   }
 
-  cir::MemMoveOp createMemMove(mlir::Location loc, mlir::Value dst,
-                               mlir::Value src, mlir::Value len) {
-    return cir::MemMoveOp::create(*this, loc, dst, src, len);
+  cir::MemMoveOp createMemMove(mlir::Location loc, Address dst, Address src,
+                               mlir::Value len) {
+    return cir::MemMoveOp::create(
+        *this, loc, dst.getPointer(), src.getPointer(), len,
+        getI64IntegerAttr(dst.getAlignment().getQuantity()),
+        getI64IntegerAttr(src.getAlignment().getQuantity()));
   }
 
   cir::MemSetOp createMemSet(mlir::Location loc, mlir::Value dst,

diff  --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 78ea48d79ecf3..d91c74e628da6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2482,8 +2482,7 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
                         e->getArg(0)->getExprLoc(), fd, 0);
     emitNonNullArgCheck(RValue::get(dest.getPointer()), e->getArg(1)->getType(),
                         e->getArg(1)->getExprLoc(), fd, 0);
-    builder.createMemMove(getLoc(e->getSourceRange()), dest.getPointer(),
-                          src.getPointer(), sizeVal);
+    builder.createMemMove(getLoc(e->getSourceRange()), dest, src, sizeVal);
     return RValue::get(nullptr);
   }
   case Builtin::BI__builtin_char_memchr:
@@ -2525,11 +2524,44 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
   case Builtin::BI__builtin___memcpy_chk:
   case Builtin::BI__builtin_objc_memmove_collectable:
   case Builtin::BI__builtin___memmove_chk:
+    break; // Handled as library calls below.
   case Builtin::BI__builtin_trivially_relocate:
   case Builtin::BImemmove:
-  case Builtin::BI__builtin_memmove:
+  case Builtin::BI__builtin_memmove: {
+    mlir::Location loc = getLoc(e->getSourceRange());
+    Address dest = emitPointerWithAlignment(e->getArg(0));
+    Address src = emitPointerWithAlignment(e->getArg(1));
+    mlir::Value sizeVal = emitScalarExpr(e->getArg(2));
+    if (builtinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
+      QualType eltTy = e->getArg(0)->getType()->getPointeeType();
+      CharUnits eltSize = getContext().getTypeSizeInChars(eltTy);
+      mlir::Value eltSizeVal =
+          builder.getConstInt(loc, sizeVal.getType(), eltSize.getQuantity());
+      sizeVal = builder.createMul(loc, sizeVal, eltSizeVal);
+    }
+    emitNonNullArgCheck(RValue::get(dest.getPointer()), e->getArg(0)->getType(),
+                        e->getArg(0)->getExprLoc(), fd, 0);
+    emitNonNullArgCheck(RValue::get(src.getPointer()), e->getArg(1)->getType(),
+                        e->getArg(1)->getExprLoc(), fd, 1);
+    assert(!cir::MissingFeatures::sanitizers());
+    Address destCast = dest.withElementType(builder, cgm.voidTy);
+    Address srcCast = src.withElementType(builder, cgm.voidTy);
+    builder.createMemMove(loc, destCast, srcCast, sizeVal);
+    assert(!cir::MissingFeatures::generateDebugInfo());
+    return RValue::get(dest.getPointer());
+  }
   case Builtin::BImemset:
-  case Builtin::BI__builtin_memset:
+  case Builtin::BI__builtin_memset: {
+    Address dest = emitPointerWithAlignment(e->getArg(0));
+    mlir::Value byteVal = builder.createIntCast(emitScalarExpr(e->getArg(1)),
+                                                builder.getUInt8Ty());
+    mlir::Value sizeVal = emitScalarExpr(e->getArg(2));
+    emitNonNullArgCheck(RValue::get(dest.getPointer()), e->getArg(0)->getType(),
+                        e->getArg(0)->getExprLoc(), fd, 0);
+    builder.createMemSet(getLoc(e->getSourceRange()), dest, byteVal, sizeVal);
+    assert(!cir::MissingFeatures::generateDebugInfo());
+    return RValue::get(dest.getPointer());
+  }
   case Builtin::BI__builtin_memset_inline:
   case Builtin::BI__builtin___memset_chk:
   case Builtin::BI__builtin_wmemchr:

diff  --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 7f305560289a7..321ebaaa34517 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2311,6 +2311,18 @@ RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot,
   llvm_unreachable("bad evaluation kind");
 }
 
+// Returns whether \p fn disables the builtin \p name with no_builtin. An empty
+// list disables all builtins.
+static bool hasNoBuiltinAttr(cir::FuncOp fn, StringRef name) {
+  auto noBuiltins = fn->getAttrOfType<mlir::ArrayAttr>(
+      cir::CIRDialect::getNoBuiltinsAttrName());
+  if (!noBuiltins)
+    return false;
+  return noBuiltins.empty() ||
+         llvm::is_contained(noBuiltins,
+                            mlir::StringAttr::get(fn.getContext(), name));
+}
+
 CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
   const auto *fd = cast<FunctionDecl>(gd.getDecl());
 
@@ -2320,18 +2332,12 @@ CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
 
     bool isPredefinedLibFunction =
         cgm.getASTContext().BuiltinInfo.isPredefinedLibFunction(builtinID);
-    // TODO: Read no-builtin function attribute and set this accordingly.
-    // Using false here matches OGCG's default behavior - builtins are called
-    // as builtins unless explicitly disabled. The previous value of true was
-    // overly conservative and caused functions to be marked as no_inline when
-    // they shouldn't be.
-    bool hasAttributeNoBuiltin = false;
-    assert(!cir::MissingFeatures::attributeNoBuiltin());
+    auto fn = dyn_cast<cir::FuncOp>(curFn);
+    bool hasAttributeNoBuiltin = fn && hasNoBuiltinAttr(fn, fd->getName());
 
     // When directly calling an inline builtin, call it through it's mangled
     // name to make it clear it's not the actual builtin.
-    if (auto fn = dyn_cast<cir::FuncOp>(curFn);
-        (!fn || fn.getName() != fdInlineName) &&
+    if ((!fn || fn.getName() != fdInlineName) &&
         CodeGenUtils::onlyHasInlineBuiltinDeclaration(fd)) {
       cir::FuncOp clone =
           mlir::cast_or_null<cir::FuncOp>(cgm.getGlobalValue(fdInlineName));

diff  --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index a6e48b2985c67..f0f037291debf 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -383,9 +383,10 @@ mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
-mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite(
-    cir::MemCpyOp op, OpAdaptor adaptor,
-    mlir::ConversionPatternRewriter &rewriter) const {
+template <typename CIROp, typename LLVMOp>
+static mlir::LogicalResult
+lowerMemTransferOp(CIROp op, typename CIROp::Adaptor adaptor,
+                   mlir::ConversionPatternRewriter &rewriter) {
   mlir::ArrayAttr argAttrs;
   if (op.getDstAlignment() || op.getSrcAlignment()) {
     mlir::NamedAttribute dstAlignAttr = rewriter.getNamedAttr(
@@ -399,7 +400,7 @@ mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite(
         /*src_attrs=*/rewriter.getDictionaryAttr({srcAlignAttr}),
     });
   }
-  rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>(
+  rewriter.replaceOpWithNewOp<LLVMOp>(
       op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
       /*isVolatile=*/false,
       /*access_groups=*/nullptr, /*alias_scopes=*/nullptr,
@@ -408,13 +409,18 @@ mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite(
+    cir::MemCpyOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  return lowerMemTransferOp<cir::MemCpyOp, mlir::LLVM::MemcpyOp>(op, adaptor,
+                                                                 rewriter);
+}
+
 mlir::LogicalResult CIRToLLVMMemMoveOpLowering::matchAndRewrite(
     cir::MemMoveOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
-  rewriter.replaceOpWithNewOp<mlir::LLVM::MemmoveOp>(
-      op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
-      /*isVolatile=*/false);
-  return mlir::success();
+  return lowerMemTransferOp<cir::MemMoveOp, mlir::LLVM::MemmoveOp>(op, adaptor,
+                                                                   rewriter);
 }
 
 mlir::LogicalResult CIRToLLVMMemSetOpLowering::matchAndRewrite(

diff  --git a/clang/test/CIR/CodeGen/libc.c b/clang/test/CIR/CodeGen/libc.c
index 1ea383b1559d2..afcb5ac013be4 100644
--- a/clang/test/CIR/CodeGen/libc.c
+++ b/clang/test/CIR/CodeGen/libc.c
@@ -3,19 +3,14 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
 // RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t-ogcg.ll
-// RUN: FileCheck --check-prefix=OGCG --input-file=%t-ogcg.ll %s
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-ogcg.ll %s
 
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -fwrapv
 // RUN: FileCheck --check-prefix=CIR_NO_POISON --input-file=%t.cir %s
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll -fwrapv
 // RUN: FileCheck --check-prefix=LLVM_NO_POISON --input-file=%t.ll %s
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t-ogcg-wrapv.ll -fwrapv
-// RUN: FileCheck --check-prefix=OGCG_NO_POISON --input-file=%t-ogcg-wrapv.ll %s
-
-// Note: In the final implementation, we will want these to generate
-// CIR-specific libc operations. This test is just a placeholder
-// to make sure we can compile these to normal function calls
-// until the special handling is implemented.
+// RUN: FileCheck --check-prefix=LLVM_NO_POISON --input-file=%t-ogcg-wrapv.ll %s
 
 void *memcpy(void *, const void *, unsigned long);
 void testMemcpy(void *dst, const void *src, unsigned long size) {
@@ -33,13 +28,52 @@ void *testMempcpy(void *dst, const void *src, unsigned long size) {
 void *memmove(void *, const void *, unsigned long);
 void testMemmove(void *src, const void *dst, unsigned long size) {
   memmove(dst, src, size);
-  // CHECK: cir.call @memmove
+  // CHECK: cir.libc.memmove %{{.+}} bytes from %{{.+}} to %{{.+}} : !cir.ptr<!void>, !u64i
+  // LLVM: call void @llvm.memmove.p0.p0.i64(ptr {{.*}}, ptr {{.*}}, i64 {{.*}}, i1 false)
 }
 
 void *memset(void *, int, unsigned long);
 void testMemset(void *dst, int val, unsigned long size) {
   memset(dst, val, size);
-  // CHECK: cir.call @memset
+  // CHECK: cir.libc.memset %{{.+}} bytes at %{{.+}} to %{{.+}} : !cir.ptr<!void>, !u8i, !u64i
+  // LLVM: call void @llvm.memset.p0.i64(ptr {{.*}}, i8 {{.*}}, i64 {{.*}}, i1 false)
+}
+
+void *testBuiltinMemmove(void *dst, const void *src, unsigned long size) {
+  return __builtin_memmove(dst, src, size);
+  // CHECK-LABEL: cir.func {{.*}} @testBuiltinMemmove(
+  // CHECK: %[[DST:.+]] = cir.load {{.*}} : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+  // CHECK: cir.libc.memmove %{{.+}} bytes from %{{.+}} align(1) to %[[DST]] align(1) : !cir.ptr<!void>, !u64i
+  // CHECK: cir.store %[[DST]], %{{.+}} : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
+  // LLVM-LABEL: define {{.*}} ptr @testBuiltinMemmove(
+  // LLVM: call void @llvm.memmove.p0.p0.i64(ptr {{.*}}, ptr {{.*}}, i64 {{.*}}, i1 false)
+}
+
+void *testBuiltinMemset(void *dst, int val, unsigned long size) {
+  return __builtin_memset(dst, val, size);
+  // CHECK-LABEL: cir.func {{.*}} @testBuiltinMemset(
+  // CHECK: %[[DST:.+]] = cir.load {{.*}} : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
+  // CHECK: %[[VAL:.+]] = cir.load {{.*}} : !cir.ptr<!s32i>, !s32i
+  // CHECK: %[[BYTE:.+]] = cir.cast integral %[[VAL]] : !s32i -> !u8i
+  // CHECK: cir.libc.memset %{{.+}} bytes at %[[DST]] align(1) to %[[BYTE]] : !cir.ptr<!void>, !u8i, !u64i
+  // CHECK: cir.store %[[DST]], %{{.+}} : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>
+  // LLVM-LABEL: define {{.*}} ptr @testBuiltinMemset(
+  // LLVM: [[VAL:%.*]] = load i32, ptr
+  // LLVM: [[BYTE:%.*]] = trunc i32 [[VAL]] to i8
+  // LLVM: call void @llvm.memset.p0.i64(ptr {{.*}}, i8 [[BYTE]], i64 {{.*}}, i1 false)
+}
+
+// The alignment comes from the pointee type of the argument before its
+// conversion to void *.
+void testBuiltinAligned(int *dst, const int *src, unsigned long size) {
+  __builtin_memmove(dst, src, size);
+  __builtin_memset(dst, 0, size);
+  // CHECK-LABEL: cir.func {{.*}} @testBuiltinAligned(
+  // CHECK: cir.libc.memmove %{{.+}} bytes from %{{.+}} align(4) to %{{.+}} align(4) : !cir.ptr<!void>, !u64i
+  // CHECK: cir.libc.memset %{{.+}} bytes at %{{.+}} align(4) to %{{.+}} : !cir.ptr<!void>, !u8i, !u64i
+  // LLVM-LABEL: define {{.*}} void @testBuiltinAligned(
+  // LLVM: call void @llvm.memmove.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 %{{.+}}, i1 false)
+  // LLVM: call void @llvm.memset.p0.i64(ptr align 4 %{{.+}}, i8 0, i64 %{{.+}}, i1 false)
 }
 
 double fabs(double);
@@ -59,10 +93,8 @@ int testAbs(int x) {
   return abs(x);
   // CHECK: cir.abs %{{.+}} min_is_poison : !s32i
   // LLVM: %{{.+}} = call i32 @llvm.abs.i32(i32 %{{.+}}, i1 true)
-  // OGCG: %{{.+}} = call i32 @llvm.abs.i32(i32 %{{.+}}, i1 true)
   // CIR_NO_POISON: cir.abs %{{[^ ]+}} : !s32i
   // LLVM_NO_POISON: %{{.+}} = call i32 @llvm.abs.i32(i32 %{{.+}}, i1 false)
-  // OGCG_NO_POISON: %{{.+}} = call i32 @llvm.abs.i32(i32 %{{.+}}, i1 false)
 }
 
 long labs(long);
@@ -70,10 +102,8 @@ long testLabs(long x) {
   return labs(x);
   // CHECK: cir.abs %{{.+}} min_is_poison : !s64i
   // LLVM: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 true)
-  // OGCG: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 true)
   // CIR_NO_POISON: cir.abs %{{[^ ]+}} : !s64i
   // LLVM_NO_POISON: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 false)
-  // OGCG_NO_POISON: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 false)
 }
 
 long long llabs(long long);
@@ -81,8 +111,6 @@ long long testLlabs(long long x) {
   return llabs(x);
   // CHECK: cir.abs %{{.+}} min_is_poison : !s64i
   // LLVM: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 true)
-  // OGCG: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 true)
   // CIR_NO_POISON: cir.abs %{{[^ ]+}} : !s64i
   // LLVM_NO_POISON: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 false)
-  // OGCG_NO_POISON: %{{.+}} = call i64 @llvm.abs.i64(i64 %{{.+}}, i1 false)
 }

diff  --git a/clang/test/CIR/CodeGen/no-builtin-call.c b/clang/test/CIR/CodeGen/no-builtin-call.c
new file mode 100644
index 0000000000000..b511bb7ffb89c
--- /dev/null
+++ b/clang/test/CIR/CodeGen/no-builtin-call.c
@@ -0,0 +1,98 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - \
+// RUN:   | FileCheck --check-prefix=CIR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - \
+// RUN:   | FileCheck --check-prefix=LLVM %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - \
+// RUN:   | FileCheck --check-prefix=LLVM %s
+
+// __attribute__((no_builtin)) on the calling function turns calls to the named
+// library functions into ordinary calls.
+
+typedef __SIZE_TYPE__ size_t;
+
+void *memset(void *s, int c, size_t n);
+void *memcpy(void *d, const void *s, size_t n);
+void *memmove(void *d, const void *s, size_t n);
+
+void none(char *s, char *d, size_t n) {
+  memset(s, 0, n);
+  memcpy(d, s, n);
+  memmove(d, s, n);
+}
+
+// CIR-LABEL: cir.func {{.*}} @none(
+// CIR:         cir.libc.memset
+// CIR:         cir.libc.memcpy
+// CIR:         cir.libc.memmove
+
+// LLVM-LABEL: define {{.*}} void @none(
+// LLVM:         call void @llvm.memset.p0.i64(
+// LLVM:         call void @llvm.memcpy.p0.p0.i64(
+// LLVM:         call void @llvm.memmove.p0.p0.i64(
+
+__attribute__((no_builtin("memset"))) void no_memset(char *s, char *d,
+                                                     size_t n) {
+  memset(s, 0, n);
+  memcpy(d, s, n);
+  memmove(d, s, n);
+}
+
+// CIR-LABEL: cir.func {{.*}} @no_memset(
+// CIR:         cir.call @memset(
+// CIR:         cir.libc.memcpy
+// CIR:         cir.libc.memmove
+
+// LLVM-LABEL: define {{.*}} void @no_memset(
+// LLVM:         call ptr @memset(
+// LLVM:         call void @llvm.memcpy.p0.p0.i64(
+// LLVM:         call void @llvm.memmove.p0.p0.i64(
+
+__attribute__((no_builtin("memcpy", "memmove"))) void
+no_memcpy_memmove(char *s, char *d, size_t n) {
+  memset(s, 0, n);
+  memcpy(d, s, n);
+  memmove(d, s, n);
+}
+
+// CIR-LABEL: cir.func {{.*}} @no_memcpy_memmove(
+// CIR:         cir.libc.memset
+// CIR:         cir.call @memcpy(
+// CIR:         cir.call @memmove(
+
+// LLVM-LABEL: define {{.*}} void @no_memcpy_memmove(
+// LLVM:         call void @llvm.memset.p0.i64(
+// LLVM:         call ptr @memcpy(
+// LLVM:         call ptr @memmove(
+
+__attribute__((no_builtin)) void no_builtins(char *s, char *d, size_t n) {
+  memset(s, 0, n);
+  memcpy(d, s, n);
+  memmove(d, s, n);
+}
+
+// CIR-LABEL: cir.func {{.*}} @no_builtins(
+// CIR:         cir.call @memset(
+// CIR:         cir.call @memcpy(
+// CIR:         cir.call @memmove(
+
+// LLVM-LABEL: define {{.*}} void @no_builtins(
+// LLVM:         call ptr @memset(
+// LLVM:         call ptr @memcpy(
+// LLVM:         call ptr @memmove(
+
+// The attribute only applies to library functions, not to __builtin_ names.
+__attribute__((no_builtin)) void builtin_names(char *s, char *d, size_t n) {
+  __builtin_memset(s, 0, n);
+  __builtin_memcpy(d, s, n);
+  __builtin_memmove(d, s, n);
+}
+
+// CIR-LABEL: cir.func {{.*}} @builtin_names(
+// CIR:         cir.libc.memset
+// CIR:         cir.libc.memcpy
+// CIR:         cir.libc.memmove
+
+// LLVM-LABEL: define {{.*}} void @builtin_names(
+// LLVM:         call void @llvm.memset.p0.i64(
+// LLVM:         call void @llvm.memcpy.p0.p0.i64(
+// LLVM:         call void @llvm.memmove.p0.p0.i64(

diff  --git a/clang/test/CIR/CodeGenBuiltins/builtin-bcopy.cpp b/clang/test/CIR/CodeGenBuiltins/builtin-bcopy.cpp
index 8a1243c13f7d7..2dbdb5b0d2789 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtin-bcopy.cpp
+++ b/clang/test/CIR/CodeGenBuiltins/builtin-bcopy.cpp
@@ -16,7 +16,7 @@ void foo(void) {
   // CIR: %[[V6:.*]] = cir.const #cir.int<4> : !u64i
   // CIR: %[[V7:.*]] = cir.const #cir.int<4> : !u64i
   // CIR: %[[V8:.*]] = cir.mul %[[V6]], %[[V7]] : !u64i
-  // CIR: cir.libc.memmove %[[V8]] bytes from %[[V3]] to %[[V5]] : !cir.ptr<!void>, !u64i
+  // CIR: cir.libc.memmove %[[V8]] bytes from %[[V3]] align(16) to %[[V5]] align(16) : !cir.ptr<!void>, !u64i
   // CIR: cir.return
 
   // LLVM-LABEL: define dso_local void @_Z3foov()
@@ -24,7 +24,7 @@ void foo(void) {
   // LLVM: %[[V2:.*]] = alloca [8 x float], align 16
   // LLVM: %[[V3:.*]] = getelementptr float, ptr %[[V1]], i32 0
   // LLVM: %[[V4:.*]] = getelementptr float, ptr %[[V2]], i32 0
-  // LLVM: call void @llvm.memmove.p0.p0.i64(ptr %[[V4]], ptr %[[V3]], i64 16, i1 false)
+  // LLVM: call void @llvm.memmove.p0.p0.i64(ptr align 16 %[[V4]], ptr align 16 %[[V3]], i64 16, i1 false)
   // LLVM: ret void
 
   // OGCG-LABEL: define dso_local void @_Z3foov()
@@ -120,11 +120,11 @@ void testbcopy(const void *src, void *dest, size_t n) {
 // CIR: %[[DEST:.*]] = cir.alloca "dest" {{.*}} init : !cir.ptr<!cir.ptr<!s8i>>
 // CIR: %[[SRC_TO_VOIDPTR:.*]] = cir.cast bitcast %[[SRC]] : !cir.ptr<!cir.ptr<!s8i>> -> !cir.ptr<!void>
 // CIR: %[[DEST_TO_VOIDPTR:.*]] = cir.cast bitcast %[[DEST]] : !cir.ptr<!cir.ptr<!s8i>> -> !cir.ptr<!void>
-// CIR: cir.libc.memmove {{.*}} bytes from %[[SRC_TO_VOIDPTR]] to %[[DEST_TO_VOIDPTR]]
+// CIR: cir.libc.memmove {{.*}} bytes from %[[SRC_TO_VOIDPTR]] align(8) to %[[DEST_TO_VOIDPTR]] align(8)
 // LLVM-LABEL: @testaddressof(
 // LLVM: %[[SRC:.*]] = alloca ptr
 // LLVM: %[[DEST:.*]] = alloca ptr
-// LLVM: call void @llvm.memmove.p0.p0.i64(ptr %[[DEST]], ptr %[[SRC]], i64 {{.*}}, i1 false)
+// LLVM: call void @llvm.memmove.p0.p0.i64(ptr align 8 %[[DEST]], ptr align 8 %[[SRC]], i64 {{.*}}, i1 false)
 // OGCG-LABEL: @testaddressof(
 // OGCG: %[[SRC:.*]] = alloca ptr
 // OGCG: %[[DEST:.*]] = alloca ptr

diff  --git a/clang/test/CIR/CodeGenBuiltins/builtin-inline.c b/clang/test/CIR/CodeGenBuiltins/builtin-inline.c
index 06437ecd6ccd6..f48d4bfc076bb 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtin-inline.c
+++ b/clang/test/CIR/CodeGenBuiltins/builtin-inline.c
@@ -3,7 +3,7 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -disable-llvm-passes %s -o %t-cir.ll
 // RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -disable-llvm-passes %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
 
 typedef unsigned long size_t;
 
@@ -26,15 +26,9 @@ void *test_inline_builtin_memcpy(void *a, const void *b, size_t c) {
 // CIR:         cir.call @memcpy.inline(
 // CIR:       }
 
-// LLVM: define internal ptr @memcpy.inline(ptr{{.*}}, ptr{{.*}}, i64{{.*}}) #{{[0-9]+}}
-
-// LLVM-LABEL: @test_inline_builtin_memcpy(
-// LLVM:         call ptr @memcpy.inline(
-
-// OGCG-LABEL: @test_inline_builtin_memcpy(
-// OGCG:         call ptr @memcpy.inline(
-
-// OGCG: define internal ptr @memcpy.inline(ptr{{.*}} %a, ptr{{.*}} %b, i64{{.*}} %c) #{{[0-9]+}}
+// CIR emits the .inline clone before its caller, OGCG after it.
+// LLVM-DAG: define internal ptr @memcpy.inline(ptr{{.*}}, ptr{{.*}}, i64{{.*}}) #{{[0-9]+}}
+// LLVM-DAG: call ptr @memcpy.inline(ptr noundef
 
 // Shadowing case
 // When a non-inline function definition shadows an inline builtin declaration,
@@ -68,7 +62,7 @@ void *test_shadowed_memmove(void *a, const void *b, size_t c) {
 // CIR-NOT: @memmove.inline
 
 // CIR-LABEL: @test_shadowed_memmove(
-// CIR: cir.call @memmove(
+// CIR: cir.libc.memmove
 // CIR-NOT: @memmove.inline
 // CIR: }
 
@@ -76,16 +70,6 @@ void *test_shadowed_memmove(void *a, const void *b, size_t c) {
 // LLVM-NOT: @memmove.inline
 
 // LLVM-LABEL: @test_shadowed_memmove(
-// TODO - this deviation from OGCG is expected until we implement the nobuiltin
-// attribute. See CIRGenFunction::emitDirectCallee
-// LLVM: call ptr @memmove(
+// LLVM: call void @llvm.memmove.p0.p0.i64(
 // LLVM-NOT: @memmove.inline
 // LLVM: }
-
-// OGCG: define dso_local ptr @memmove(ptr{{.*}} %a, ptr{{.*}} %b, i64{{.*}} %c) #{{[0-9]+}}
-// OGCG-NOT: @memmove.inline
-
-// OGCG-LABEL: @test_shadowed_memmove(
-// OGCG: call void @llvm.memmove.p0.p0.i64(
-// OGCG-NOT: @memmove.inline
-// OGCG: }

diff  --git a/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp b/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
new file mode 100644
index 0000000000000..520fac678d9e6
--- /dev/null
+++ b/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - \
+// RUN:   | FileCheck %s --check-prefix=CIR
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - \
+// RUN:   | FileCheck %s --check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - \
+// RUN:   | FileCheck %s --check-prefix=LLVM
+
+typedef __SIZE_TYPE__ size_t;
+
+struct S {
+  int a;
+  int b;
+};
+
+// The count is in elements, so it is scaled by sizeof(S) before the memmove.
+S *test(S *source, S *dest, size_t count) {
+  return __builtin_trivially_relocate(dest, source, count);
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z4testP1SS0_m(
+// CIR:         %[[SOURCE_ADDR:.*]] = cir.alloca "source"
+// CIR:         %[[DEST_ADDR:.*]] = cir.alloca "dest"
+// CIR:         %[[DEST:.*]] = cir.load {{.*}} %[[DEST_ADDR]]
+// CIR:         %[[SOURCE:.*]] = cir.load {{.*}} %[[SOURCE_ADDR]]
+// CIR:         %[[COUNT:.*]] = cir.load {{.*}} : !cir.ptr<!u64i>, !u64i
+// CIR:         %[[ELT_SIZE:.*]] = cir.const #cir.int<8> : !u64i
+// CIR:         %[[SIZE:.*]] = cir.mul %[[COUNT]], %[[ELT_SIZE]] : !u64i
+// CIR:         %[[DEST_VOID:.*]] = cir.cast bitcast %[[DEST]] : !cir.ptr<!rec_S> -> !cir.ptr<!void>
+// CIR:         %[[SOURCE_VOID:.*]] = cir.cast bitcast %[[SOURCE]] : !cir.ptr<!rec_S> -> !cir.ptr<!void>
+// CIR:         cir.libc.memmove %[[SIZE]] bytes from %[[SOURCE_VOID]] align(4) to %[[DEST_VOID]] align(4)
+// CIR:         cir.store %[[DEST]], %{{.*}} : !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>
+
+// LLVM-LABEL: define {{.*}} ptr @_Z4testP1SS0_m(
+// LLVM:         [[DEST:%.*]] = load ptr, ptr
+// LLVM:         [[SOURCE:%.*]] = load ptr, ptr
+// LLVM:         [[COUNT:%.*]] = load i64, ptr
+// LLVM:         [[SIZE:%.*]] = mul i64 [[COUNT]], 8
+// LLVM:         call void @llvm.memmove.p0.p0.i64(ptr align 4 [[DEST]], ptr align 4 [[SOURCE]], i64 [[SIZE]], i1 false)

diff  --git a/clang/test/CIR/IR/libc-memmove.cir b/clang/test/CIR/IR/libc-memmove.cir
new file mode 100644
index 0000000000000..a1ec6378c411a
--- /dev/null
+++ b/clang/test/CIR/IR/libc-memmove.cir
@@ -0,0 +1,12 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+!u64i = !cir.int<u, 64>
+module {
+  cir.func @shouldParseLibcMemmoveOp(%arg0 : !cir.ptr<!cir.void>, %arg1 : !u64i) {
+    // CHECK: cir.libc.memmove %{{.+}} bytes from %{{.+}} to %{{.+}} : !cir.ptr<!void>, !u64i
+    cir.libc.memmove %arg1 bytes from %arg0 to %arg0 : !cir.ptr<!cir.void>, !u64i
+    // CHECK: cir.libc.memmove %{{.+}} bytes from %{{.+}} align(4) to %{{.+}} align(8) : !cir.ptr<!void>, !u64i
+    cir.libc.memmove %arg1 bytes from %arg0 align(4) to %arg0 align(8) : !cir.ptr<!cir.void>, !u64i
+    cir.return
+  }
+}


        


More information about the cfe-commits mailing list