https://github.com/steffenlarsen updated 
https://github.com/llvm/llvm-project/pull/227667

>From 7943ab852ebde7a223a8303a25c105c31019b177 Mon Sep 17 00:00:00 2001
From: Steffen Holst Larsen <[email protected]>
Date: Wed, 23 Sep 2026 00:16:15 -0500
Subject: [PATCH 1/4] [CIR] Emit cir.libc.memmove and cir.libc.memset for
 memmove and memset

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.

Note that emitDirectCallee still ignores __attribute__((no_builtin)), so
calls to these functions from a no_builtin function now become
intrinsics where classic codegen keeps the library call. This should be
addressed separately as part of the no_builtin support.

Assisted-by: Claude Opus 5.5

Signed-off-by: Steffen Holst Larsen <[email protected]>
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  9 ++-
 clang/lib/CIR/CodeGen/CIRGenBuilder.h         |  9 ++-
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp       | 44 +++++++++++---
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 17 +++++-
 clang/test/CIR/CodeGen/libc.c                 | 58 ++++++++++++++-----
 .../CIR/CodeGenBuiltins/builtin-bcopy.cpp     |  8 +--
 .../test/CIR/CodeGenBuiltins/builtin-inline.c | 28 ++-------
 .../builtin-trivially-relocate.cpp            | 38 ++++++++++++
 clang/test/CIR/IR/libc-memmove.cir            | 12 ++++
 9 files changed, 169 insertions(+), 54 deletions(-)
 create mode 100644 
clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
 create mode 100644 clang/test/CIR/IR/libc-memmove.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index de805d4c642a2..43e1f3433a41d 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -5307,10 +5307,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 b581212b0db56..cb350f220d611 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 245708691b7d9..14e47ca3854d8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2412,8 +2412,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:
@@ -2448,15 +2447,46 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl 
&gd, unsigned builtinID,
     }
     return RValue::get(dest.getPointer());
   }
+  case Builtin::BI__builtin_trivially_relocate:
+  case Builtin::BImemmove:
+  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());
+    builder.createMemMove(loc, dest.withElementType(builder, cgm.voidTy),
+                          src.withElementType(builder, cgm.voidTy), sizeVal);
+    assert(!cir::MissingFeatures::generateDebugInfo());
+    return RValue::get(dest.getPointer());
+  }
+  case Builtin::BImemset:
+  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_memcpy_inline:
   case Builtin::BI__builtin___memcpy_chk:
   case Builtin::BI__builtin_objc_memmove_collectable:
   case Builtin::BI__builtin___memmove_chk:
-  case Builtin::BI__builtin_trivially_relocate:
-  case Builtin::BImemmove:
-  case Builtin::BI__builtin_memmove:
-  case Builtin::BImemset:
-  case Builtin::BI__builtin_memset:
   case Builtin::BI__builtin_memset_inline:
   case Builtin::BI__builtin___memset_chk:
   case Builtin::BI__builtin_wmemchr:
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 4d2a99d5015fe..3b498a4007c17 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -349,9 +349,24 @@ mlir::LogicalResult 
CIRToLLVMMemCpyOpLowering::matchAndRewrite(
 mlir::LogicalResult CIRToLLVMMemMoveOpLowering::matchAndRewrite(
     cir::MemMoveOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
+  mlir::ArrayAttr argAttrs;
+  if (op.getDstAlignment() || op.getSrcAlignment()) {
+    mlir::NamedAttribute dstAlignAttr = rewriter.getNamedAttr(
+        mlir::LLVM::LLVMDialect::getAlignAttrName(),
+        rewriter.getI64IntegerAttr(op.getDstAlignment().value_or(1)));
+    mlir::NamedAttribute srcAlignAttr = rewriter.getNamedAttr(
+        mlir::LLVM::LLVMDialect::getAlignAttrName(),
+        rewriter.getI64IntegerAttr(op.getSrcAlignment().value_or(1)));
+    argAttrs = rewriter.getArrayAttr({
+        /*dst_attrs=*/rewriter.getDictionaryAttr({dstAlignAttr}),
+        /*src_attrs=*/rewriter.getDictionaryAttr({srcAlignAttr}),
+    });
+  }
   rewriter.replaceOpWithNewOp<mlir::LLVM::MemmoveOp>(
       op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
-      /*isVolatile=*/false);
+      /*isVolatile=*/false, /*access_groups=*/nullptr, 
/*alias_scopes=*/nullptr,
+      /*noalias_scopes=*/nullptr, /*tbaa=*/nullptr, /*arg_attrs=*/argAttrs,
+      /*res_attrs=*/nullptr);
   return mlir::success();
 }
 
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/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..c49e1ff6caf37
--- /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:         %[[SOURCE_VOID:.*]] = cir.cast bitcast %[[SOURCE]] : 
!cir.ptr<!rec_S> -> !cir.ptr<!void>
+// CIR:         %[[DEST_VOID:.*]] = cir.cast bitcast %[[DEST]] : 
!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
+  }
+}

>From 2c8b3deab392ddf535df08698d93ad0189651663 Mon Sep 17 00:00:00 2001
From: Steffen Holst Larsen <[email protected]>
Date: Thu, 1 Oct 2026 02:10:02 -0500
Subject: [PATCH 2/4] Deterministic ordering

Signed-off-by: Steffen Holst Larsen <[email protected]>
---
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp                      | 5 +++--
 .../test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp  | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp 
b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index aac268728a775..b674a3c1b4d39 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2471,8 +2471,9 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl 
&gd, unsigned builtinID,
     emitNonNullArgCheck(RValue::get(src.getPointer()), e->getArg(1)->getType(),
                         e->getArg(1)->getExprLoc(), fd, 1);
     assert(!cir::MissingFeatures::sanitizers());
-    builder.createMemMove(loc, dest.withElementType(builder, cgm.voidTy),
-                          src.withElementType(builder, cgm.voidTy), sizeVal);
+    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());
   }
diff --git a/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp 
b/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
index c49e1ff6caf37..520fac678d9e6 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
+++ b/clang/test/CIR/CodeGenBuiltins/builtin-trivially-relocate.cpp
@@ -25,8 +25,8 @@ S *test(S *source, S *dest, size_t count) {
 // 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:         %[[SOURCE_VOID:.*]] = cir.cast bitcast %[[SOURCE]] : 
!cir.ptr<!rec_S> -> !cir.ptr<!void>
 // 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>>
 

>From 560ea013b52b0fc428f76b10928e9ae48c7eaf72 Mon Sep 17 00:00:00 2001
From: Steffen Holst Larsen <[email protected]>
Date: Mon, 5 Oct 2026 07:23:39 -0500
Subject: [PATCH 3/4] Unify memmove and memcpy rewrite functions

Signed-off-by: Steffen Holst Larsen <[email protected]>
---
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 37 +++++++------------
 1 file changed, 14 insertions(+), 23 deletions(-)

diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 0493cd0f16708..b3935ad92141a 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -321,9 +321,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(
@@ -337,7 +338,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,
@@ -346,28 +347,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 {
-  mlir::ArrayAttr argAttrs;
-  if (op.getDstAlignment() || op.getSrcAlignment()) {
-    mlir::NamedAttribute dstAlignAttr = rewriter.getNamedAttr(
-        mlir::LLVM::LLVMDialect::getAlignAttrName(),
-        rewriter.getI64IntegerAttr(op.getDstAlignment().value_or(1)));
-    mlir::NamedAttribute srcAlignAttr = rewriter.getNamedAttr(
-        mlir::LLVM::LLVMDialect::getAlignAttrName(),
-        rewriter.getI64IntegerAttr(op.getSrcAlignment().value_or(1)));
-    argAttrs = rewriter.getArrayAttr({
-        /*dst_attrs=*/rewriter.getDictionaryAttr({dstAlignAttr}),
-        /*src_attrs=*/rewriter.getDictionaryAttr({srcAlignAttr}),
-    });
-  }
-  rewriter.replaceOpWithNewOp<mlir::LLVM::MemmoveOp>(
-      op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
-      /*isVolatile=*/false, /*access_groups=*/nullptr, 
/*alias_scopes=*/nullptr,
-      /*noalias_scopes=*/nullptr, /*tbaa=*/nullptr, /*arg_attrs=*/argAttrs,
-      /*res_attrs=*/nullptr);
-  return mlir::success();
+  return lowerMemTransferOp<cir::MemMoveOp, mlir::LLVM::MemmoveOp>(op, adaptor,
+                                                                   rewriter);
 }
 
 mlir::LogicalResult CIRToLLVMMemSetOpLowering::matchAndRewrite(

>From 8d79b65abce6c818d49183ea99e5fea0881d635d Mon Sep 17 00:00:00 2001
From: Steffen Holst Larsen <[email protected]>
Date: Mon, 5 Oct 2026 07:59:40 -0500
Subject: [PATCH 4/4] Honor nobuiltin attributes

Signed-off-by: Steffen Holst Larsen <[email protected]>
---
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp     | 24 +++---
 clang/test/CIR/CodeGen/no-builtin-call.c | 98 ++++++++++++++++++++++++
 2 files changed, 113 insertions(+), 9 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/no-builtin-call.c

diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index d285800111c45..b93e0217b15a1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2244,6 +2244,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());
 
@@ -2253,18 +2265,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/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(

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to