[clang] Fix memcpy-operator= generation with restrict parameters. (PR #194906)

Erich Keane via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 30 06:50:23 PDT 2026


https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/194906

>From 277be76708620fb2c2fc6a351ae8ee95c304ab8a Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Wed, 29 Apr 2026 09:18:52 -0700
Subject: [PATCH 1/3] Fix memcpy-operator= generation with restrict parameters.

The below issue (and #63884) both report that we reject (and also
assert, because the memcpy failed) the memcpy we're generating for a
restrict field of a type with an implicit copy constructor.

First, we shouldn't be rejecting it this late, IF we wanted to reject it
(I contend we do not), we should do it at the same time we reject
const-members/make this a deleted operator.  Second, of course we
shouldn't fail.

This patch fixes the above by inserting a bit-cast in this situation.
We know this is a valid cast/copy, since we're just doing this by
memcpy.

We COULD ALWAYS introduce this cast, however I fear that doing so might
interfere with users of tooling, so this patch limits itself to only the
'restrict-member' case. I was unable to find any other qualifiers that
BOTH opted us in for these builtin-members-copy-with-memcpy, and didn't
delete the copy operator.

Fixes: #37979
---
 clang/lib/Sema/SemaDeclCXX.cpp | 17 +++++++++++++++++
 clang/test/SemaCXX/GH37979.cpp | 25 +++++++++++++++++++++++++
 2 files changed, 42 insertions(+)
 create mode 100644 clang/test/SemaCXX/GH37979.cpp

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 159afdacdd110..ee3308189d46d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -14928,6 +14928,23 @@ buildMemcpyForAssignmentOp(Sema &S, SourceLocation Loc, QualType T,
       S.Context, To, UO_AddrOf, S.Context.getPointerType(To->getType()),
       VK_PRValue, OK_Ordinary, Loc, false, S.CurFPFeatureOverrides());
 
+  // If this is restrict qualified, the call to memcpy will fail as this ends up
+  // being an illegal pointer cast (as it has non-local qualifiers in
+  // difference). Sema rules prevent us from calling this with anything other
+  // than the restrict qualifiers as far as I can tell(fields cant have an AS,
+  // copy is deleted if it is const, OBJC can't get here, pointer-auth can't be
+  // applied to any problematic things). In order to avoid that problem, we can
+  // just insert a cast directly to const void * / void *.
+  if (T.isRestrictQualified()) {
+    From = ImplicitCastExpr::Create(
+        S.Context, S.Context.getPointerType(S.Context.VoidTy.withConst()),
+        CK_BitCast, From, /*BasePath=*/nullptr, VK_PRValue,
+        FPOptionsOverride());
+    To = ImplicitCastExpr::Create(
+        S.Context, S.Context.VoidPtrTy, CK_BitCast, To,
+        /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
+  }
+
   bool NeedsCollectableMemCpy = false;
   if (auto *RD = T->getBaseElementTypeUnsafe()->getAsRecordDecl())
     NeedsCollectableMemCpy = RD->hasObjectMember();
diff --git a/clang/test/SemaCXX/GH37979.cpp b/clang/test/SemaCXX/GH37979.cpp
new file mode 100644
index 0000000000000..f6ad964c88257
--- /dev/null
+++ b/clang/test/SemaCXX/GH37979.cpp
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -pedantic %s
+// RUN: %clang_cc1 -fsyntax-only -ast-dump  %s | FileCheck %s
+// expected-no-diagnostics
+
+struct Obj { int * __restrict myPtr[2]; };
+
+void do_copy() {
+    Obj a, b;
+    a = b;
+    // CHECK-LABEL: CXXMethodDecl{{.*}} implicit used constexpr operator= 'Obj &(const Obj &) noexcept'
+    // CHECK-NEXT: ParmVarDecl
+    // CHECK-NEXT: CompoundStmt
+    // CHECK-NEXT: CallExpr
+    // CHECK-NEXT: ImplicitCastExpr{{.*}}<BuiltinFnToFnPtr>
+    // CHECK-NEXT: DeclRefExpr{{.*}}__builtin_memcpy
+    // CHECK-NEXT: ImplicitCastExpr{{.*}}'void *' <BitCast>
+    // CHECK-NEXT: UnaryOperator{{.*}} 'int *__restrict (*)[2]' prefix '&'
+    // CHECK-NEXT: MemberExpr{{.*}} 'int *__restrict[2]' lvalue ->myPtr
+    // CHECK-NEXT: CXXThisExpr{{.*}} 'Obj *' this
+    //
+    // CHECK-NEXT: ImplicitCastExpr{{.*}}'const void *' <BitCast>
+    // CHECK-NEXT: UnaryOperator{{.*}} 'int *__restrict const __restrict (*)[2]' prefix '&'
+    // CHECK-NEXT: MemberExpr{{.*}} 'int *__restrict const __restrict[2]' lvalue .myPtr
+    // CHECK-NEXT: DeclRefExpr{{.*}} 'const Obj' lvalue ParmVar
+}

>From ab4cec0c0bfc5e9d58d2e07cd8611cbc44628ff4 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Wed, 29 Apr 2026 12:11:20 -0700
Subject: [PATCH 2/3] Change strategy

---
 clang/lib/Sema/SemaDeclCXX.cpp | 19 +------------------
 clang/test/SemaCXX/GH37979.cpp | 15 +++------------
 2 files changed, 4 insertions(+), 30 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index ee3308189d46d..fc5392de5ef24 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -14928,23 +14928,6 @@ buildMemcpyForAssignmentOp(Sema &S, SourceLocation Loc, QualType T,
       S.Context, To, UO_AddrOf, S.Context.getPointerType(To->getType()),
       VK_PRValue, OK_Ordinary, Loc, false, S.CurFPFeatureOverrides());
 
-  // If this is restrict qualified, the call to memcpy will fail as this ends up
-  // being an illegal pointer cast (as it has non-local qualifiers in
-  // difference). Sema rules prevent us from calling this with anything other
-  // than the restrict qualifiers as far as I can tell(fields cant have an AS,
-  // copy is deleted if it is const, OBJC can't get here, pointer-auth can't be
-  // applied to any problematic things). In order to avoid that problem, we can
-  // just insert a cast directly to const void * / void *.
-  if (T.isRestrictQualified()) {
-    From = ImplicitCastExpr::Create(
-        S.Context, S.Context.getPointerType(S.Context.VoidTy.withConst()),
-        CK_BitCast, From, /*BasePath=*/nullptr, VK_PRValue,
-        FPOptionsOverride());
-    To = ImplicitCastExpr::Create(
-        S.Context, S.Context.VoidPtrTy, CK_BitCast, To,
-        /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
-  }
-
   bool NeedsCollectableMemCpy = false;
   if (auto *RD = T->getBaseElementTypeUnsafe()->getAsRecordDecl())
     NeedsCollectableMemCpy = RD->hasObjectMember();
@@ -15198,7 +15181,7 @@ buildSingleCopyAssign(Sema &S, SourceLocation Loc, QualType T,
                       bool CopyingBaseSubobject, bool Copying) {
   // Maybe we should use a memcpy?
   if (T->isArrayType() && !T.isConstQualified() && !T.isVolatileQualified() &&
-      T.isTriviallyCopyableType(S.Context))
+      !T.isRestrictQualified() && T.isTriviallyCopyableType(S.Context))
     return buildMemcpyForAssignmentOp(S, Loc, T, To, From);
 
   StmtResult Result(buildSingleCopyAssignRecursively(S, Loc, T, To, From,
diff --git a/clang/test/SemaCXX/GH37979.cpp b/clang/test/SemaCXX/GH37979.cpp
index f6ad964c88257..526db3739cf82 100644
--- a/clang/test/SemaCXX/GH37979.cpp
+++ b/clang/test/SemaCXX/GH37979.cpp
@@ -10,16 +10,7 @@ void do_copy() {
     // CHECK-LABEL: CXXMethodDecl{{.*}} implicit used constexpr operator= 'Obj &(const Obj &) noexcept'
     // CHECK-NEXT: ParmVarDecl
     // CHECK-NEXT: CompoundStmt
-    // CHECK-NEXT: CallExpr
-    // CHECK-NEXT: ImplicitCastExpr{{.*}}<BuiltinFnToFnPtr>
-    // CHECK-NEXT: DeclRefExpr{{.*}}__builtin_memcpy
-    // CHECK-NEXT: ImplicitCastExpr{{.*}}'void *' <BitCast>
-    // CHECK-NEXT: UnaryOperator{{.*}} 'int *__restrict (*)[2]' prefix '&'
-    // CHECK-NEXT: MemberExpr{{.*}} 'int *__restrict[2]' lvalue ->myPtr
-    // CHECK-NEXT: CXXThisExpr{{.*}} 'Obj *' this
-    //
-    // CHECK-NEXT: ImplicitCastExpr{{.*}}'const void *' <BitCast>
-    // CHECK-NEXT: UnaryOperator{{.*}} 'int *__restrict const __restrict (*)[2]' prefix '&'
-    // CHECK-NEXT: MemberExpr{{.*}} 'int *__restrict const __restrict[2]' lvalue .myPtr
-    // CHECK-NEXT: DeclRefExpr{{.*}} 'const Obj' lvalue ParmVar
+    // Make sure that this uses the for-loop in the AST rather than trying to do
+    // the early builtin_memcpy opt.
+    // CHECK-NEXT: ForStmt
 }

>From 815f363982e891ca8f689ad7eb21688b8c53c7f1 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Thu, 30 Apr 2026 06:49:59 -0700
Subject: [PATCH 3/3] add release note, change to ALL qualifiers to switch to
 for loop.

---
 clang/docs/ReleaseNotes.rst    | 1 +
 clang/lib/Sema/SemaDeclCXX.cpp | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 00987436a8e81..d9d22ee6fa7c1 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -567,6 +567,7 @@ Bug Fixes to C++ Support
   conforming and could lead to recursive constraint satisfaction checking. (#GH149443)
 - Fixed a crash in Itanium C++ name mangling for a lambda in a local class field initializer inside a constructor/destructor. (#GH176395)
 - Fixed crashes in Itanium C++ name mangling for lambdas with trailing requires-clauses involving requires-expressions. (#GH100774) (#GH123854)
+- Fixed an invalid rejection and assertion failure while generating ``operator=`` for fields with the ``__restrict`` qualifier. (#GH37979)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index fc5392de5ef24..2ff426622593c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -15180,8 +15180,8 @@ buildSingleCopyAssign(Sema &S, SourceLocation Loc, QualType T,
                       const ExprBuilder &To, const ExprBuilder &From,
                       bool CopyingBaseSubobject, bool Copying) {
   // Maybe we should use a memcpy?
-  if (T->isArrayType() && !T.isConstQualified() && !T.isVolatileQualified() &&
-      !T.isRestrictQualified() && T.isTriviallyCopyableType(S.Context))
+  if (T->isArrayType() && !T.hasQualifiers() &&
+      T.isTriviallyCopyableType(S.Context))
     return buildMemcpyForAssignmentOp(S, Loc, T, To, From);
 
   StmtResult Result(buildSingleCopyAssignRecursively(S, Loc, T, To, From,



More information about the cfe-commits mailing list