[clang] 699792b - [CIR] Add cir.libc.memcpy Op (#176781)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 22 04:24:53 PST 2026
Author: Andrzej WarzyĆski
Date: 2026-01-22T12:24:48Z
New Revision: 699792b94bc7d8983b600482841fb7c3869be5b6
URL: https://github.com/llvm/llvm-project/commit/699792b94bc7d8983b600482841fb7c3869be5b6
DIFF: https://github.com/llvm/llvm-project/commit/699792b94bc7d8983b600482841fb7c3869be5b6.diff
LOG: [CIR] Add cir.libc.memcpy Op (#176781)
The operation is a 1:1 mapping to libc's memcpy.
NOTE: This patch upstreams code from
* https://github.com/llvm/clangir.
This Op was originally implemented by Vinicius Couto Espindola
in https://github.com/llvm/clangir/pull/237. Further
modifications were made by other ClangIR contributors.
Co-authored-by: Vinicius Couto Espindola <vini.couto.e at gmail.com>
Added:
clang/test/CIR/IR/invalid-memcpy.cir
clang/test/CIR/IR/libc-memcpy.cir
clang/test/CIR/Lowering/libc.cir
Modified:
clang/include/clang/CIR/Dialect/IR/CIROps.td
clang/lib/CIR/CodeGen/CIRGenBuilder.h
clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 27e6a650fde27..89a879cefe857 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3444,6 +3444,58 @@ def CIR_CopyOp : CIR_Op<"copy",[
}];
}
+//===----------------------------------------------------------------------===//
+// MemCpyOp && MemMoveOp
+//===----------------------------------------------------------------------===//
+
+class CIR_MemOp<string mnemonic> : CIR_Op<mnemonic, [
+ AllTypesMatch<["dst", "src"]>
+]> {
+ dag commonArgs = (ins
+ Arg<CIR_VoidPtrType, "", [MemWrite]>:$dst,
+ Arg<CIR_VoidPtrType, "", [MemRead]>:$src
+ );
+}
+
+def CIR_MemCpyOp : CIR_MemOp<"libc.memcpy"> {
+ let summary = "Equivalent to libc's `memcpy`";
+ let description = [{
+ Given two CIR pointers, `src` and `dst`, `cir.libc.memcpy` will copy `len`
+ bytes from the memory pointed by `src` to the memory pointed by `dst`.
+
+ While `cir.copy` is meant to be used for implicit copies in the code where
+ the length of the copy is known, `cir.memcpy` copies only from and to void
+ pointers, requiring the copy length to be passed as an argument.
+
+ As is the case for memcpy in the C standard library, this operation
+ exhibits undefined behavior (UB) if any of the following conditions hold:
+ * `src` and/or `dst` are null pointers; or
+ * the memory regions referenced by `src` and `dst` overlap.
+
+ Examples:
+
+ ```mlir
+ // Copying 2 bytes from one array to a record:
+ %2 = cir.const #cir.int<2> : !u32i
+ cir.libc.memcpy %2 bytes from %arr to %record : !cir.ptr<!arr> -> !cir.ptr<!record>
+ ```
+ }];
+
+ let arguments = !con(commonArgs, (ins CIR_AnyFundamentalUIntType:$len));
+
+ let assemblyFormat = [{
+ $len `bytes` `from` $src `to` $dst attr-dict
+ `:` type($len) `,` qualified(type($src)) `->` qualified(type($dst))
+ }];
+
+ let extraClassDeclaration = [{
+ /// Returns the byte length type.
+ cir::IntType getLenTy() { return getLen().getType(); }
+ }];
+}
+
+// TODO: MemMoveOp
+
//===----------------------------------------------------------------------===//
// ReturnAddrOp and FrameAddrOp
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index ff492edf0b04e..dc1ce9a901381 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -189,6 +189,16 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
return getType<cir::RecordType>(nameAttr, kind);
}
+ //
+ // Operation creation helpers
+ // --------------------------
+ //
+ cir::MemCpyOp createMemCpy(mlir::Location loc, mlir::Value dst,
+ mlir::Value src, mlir::Value len) {
+ return cir::MemCpyOp::create(*this, loc, dst, src, len);
+ }
+ // ---------------------------
+
cir::DataMemberAttr getDataMemberAttr(cir::DataMemberType ty,
unsigned memberIndex) {
return cir::DataMemberAttr::get(ty, memberIndex);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 7625bcccd520f..a774b0dcc6ba8 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -187,6 +187,15 @@ mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
return mlir::success();
}
+mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite(
+ cir::MemCpyOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>(
+ op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
+ /*isVolatile=*/false);
+ return mlir::success();
+}
+
mlir::LogicalResult CIRToLLVMSqrtOpLowering::matchAndRewrite(
cir::SqrtOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/IR/invalid-memcpy.cir b/clang/test/CIR/IR/invalid-memcpy.cir
new file mode 100644
index 0000000000000..805d03adcb95f
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-memcpy.cir
@@ -0,0 +1,37 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!s8i = !cir.int<s, 8>
+module {
+ // Should not memcpy with invalid length type.
+ cir.func @invalid_memcpy_len(%arg0 : !cir.ptr<!cir.void>, %arg1 : !s8i) {
+ // expected-error at +1 {{'cir.libc.memcpy' op operand #2 must be fundamental unsigned integer type, but got '!cir.int<s, 8>'}}
+ cir.libc.memcpy %arg1 bytes from %arg0 to %arg0 : !s8i, !cir.ptr<!cir.void> -> !cir.ptr<!cir.void>
+ cir.return
+ }
+}
+
+// -----
+
+!s8i = !cir.int<s, 8>
+!u32i = !cir.int<u, 32>
+module {
+ // Should not memcpy non-void pointers.
+ cir.func @invalid_memcpy_pointer_0(%arg0 : !cir.ptr<!s8i>, %arg1 : !u32i) {
+ // expected-error at +1 {{'cir.libc.memcpy' op operand #0 must be pointer to void type, but got '!cir.ptr<!cir.int<s, 8>>'}}
+ cir.libc.memcpy %arg1 bytes from %arg0 to %arg0 : !u32i, !cir.ptr<!s8i> -> !cir.ptr<!s8i>
+ cir.return
+ }
+}
+
+// -----
+
+!s8i = !cir.int<s, 8>
+!u32i = !cir.int<u, 32>
+module {
+ // Should not memcpy non-void pointers.
+ cir.func @invalid_memcpy_pointer_1(%arg0 : !cir.ptr<!cir.void>, %arg1 : !cir.ptr<!s8i>, %arg2 : !u32i) {
+ // expected-error at +1 {{'cir.libc.memcpy' op operand #1 must be pointer to void type, but got '!cir.ptr<!cir.int<s, 8>>'}}
+ cir.libc.memcpy %arg2 bytes from %arg1 to %arg0 : !u32i, !cir.ptr<!s8i> -> !cir.ptr<!cir.void>
+ cir.return
+ }
+}
diff --git a/clang/test/CIR/IR/libc-memcpy.cir b/clang/test/CIR/IR/libc-memcpy.cir
new file mode 100644
index 0000000000000..6769092f3beb1
--- /dev/null
+++ b/clang/test/CIR/IR/libc-memcpy.cir
@@ -0,0 +1,10 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+!u32i = !cir.int<u, 32>
+module {
+ cir.func @shouldParseLibcMemcpyOp(%arg0 : !cir.ptr<!cir.void>, %arg1 : !u32i) {
+ // CHECK: cir.libc.memcpy
+ cir.libc.memcpy %arg1 bytes from %arg0 to %arg0 : !u32i, !cir.ptr<!cir.void> -> !cir.ptr<!cir.void>
+ cir.return
+ }
+}
diff --git a/clang/test/CIR/Lowering/libc.cir b/clang/test/CIR/Lowering/libc.cir
new file mode 100644
index 0000000000000..74e384d08a74b
--- /dev/null
+++ b/clang/test/CIR/Lowering/libc.cir
@@ -0,0 +1,12 @@
+// RUN: cir-opt %s -cir-to-llvm -o %t.mlir
+// RUN: FileCheck --input-file=%t.mlir %s
+
+!void = !cir.void
+!u64i = !cir.int<u, 64>
+module {
+ cir.func @shouldLowerLibcMemcpyBuiltin(%arg0: !cir.ptr<!void>, %arg1: !cir.ptr<!void>, %arg2: !u64i) {
+ cir.libc.memcpy %arg2 bytes from %arg0 to %arg1 : !u64i, !cir.ptr<!void> -> !cir.ptr<!void>
+ // CHECK: "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %{{.+}}) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i64) -> ()
+ cir.return
+ }
+}
More information about the cfe-commits
mailing list