[clang] [Clang] Sequence the object argument of an overloaded operator[] or operator() first (PR #215479)

Xavier Roche via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 00:08:23 PDT 2026


https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/215479

>From 9a8bea8fd6cd6cf125ae410b8a3ab07633461e7e Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Mon, 10 Aug 2026 18:03:56 +0200
Subject: [PATCH 1/3] [Clang] Sequence explicit-object operator operands on
 Microsoft ABI targets

Clang evaluated the object after the other operands of explicit-object
operator[] and operator() calls on Microsoft ABI targets. C++ requires the
object first. Emit it before the remaining operands while keeping their
relative ABI order and the hidden size argument attached to the object.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 5 <noreply at anthropic.com>
Co-Authored-By: Codex GPT-6 <noreply at openai.com>
---
 clang/docs/ReleaseNotes.md                    |   5 +
 clang/lib/CodeGen/CGCall.cpp                  |  26 ++++-
 clang/lib/CodeGen/CGExpr.cpp                  |  16 +--
 clang/lib/CodeGen/CodeGenFunction.h           |   5 +-
 clang/test/CodeGenCXX/cxx1z-eval-order.cpp    |  72 ++++++++++++
 .../CodeGenCXX/microsoft-abi-arg-order.cpp    | 109 +++++++++++++++++-
 6 files changed, 218 insertions(+), 15 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index c5eed2b035e66..4f70963b7864d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -621,6 +621,11 @@ features cannot lower the translation-unit ABI level;
 
 #### Bug Fixes to C++ Support
 
+- Clang now evaluates the object before the arguments of overloaded
+  `operator[]` and `operator()` with explicit object parameters on Microsoft
+  ABI targets. Parameter destruction still follows Microsoft ABI order and
+  can differ from reverse construction order.
+
 - Fixed lambdas with specifiers or attributes after the capture list being
   misparsed as function declarations in direct-initialization contexts under
   `-fms-extensions` or in HLSL mode.
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index c294621b28d1d..fb191f4c428e6 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5131,8 +5131,12 @@ void CodeGenFunction::EmitCallArgs(
           ? Order == EvaluationOrder::ForceLeftToRight
           : Order != EvaluationOrder::ForceRightToLeft;
 
+  // Keep the ABI order of the remaining arguments when hoisting the first.
+  bool HoistFirstArg = !LeftToRight && ArgTypes.size() > 1 &&
+                       Order == EvaluationOrder::ForceFirstBeforeRest;
+
   auto MaybeEmitImplicitObjectSize = [&](unsigned I, const Expr *Arg,
-                                         RValue EmittedArg) {
+                                         RValue EmittedArg, bool Reversed) {
     if (!AC.hasFunctionDecl() || I >= AC.getNumParams())
       return;
     auto *PS = AC.getParamDecl(I)->getAttr<PassObjectSizeAttr>();
@@ -5148,7 +5152,7 @@ void CodeGenFunction::EmitCallArgs(
     Args.add(RValue::get(V), SizeTy);
     // If we're emitting args in reverse, be sure to do so with
     // pass_object_size, as well.
-    if (!LeftToRight)
+    if (Reversed)
       std::swap(Args.back(), *(&Args.back() - 1));
   };
 
@@ -5160,9 +5164,17 @@ void CodeGenFunction::EmitCallArgs(
   }
 
   // Evaluate each argument in the appropriate order.
-  size_t CallArgsStart = Args.size();
+  // Keep the hoisted argument and its hidden parameters outside the reversal.
+  size_t ReversedStart = Args.size();
   for (unsigned I = 0, E = ArgTypes.size(); I != E; ++I) {
-    unsigned Idx = LeftToRight ? I : E - I - 1;
+    unsigned Idx;
+    if (LeftToRight)
+      Idx = I;
+    else if (HoistFirstArg)
+      Idx = I == 0 ? 0 : E - I;
+    else
+      Idx = E - I - 1;
+    bool Reversed = !LeftToRight && !(HoistFirstArg && I == 0);
     CallExpr::const_arg_iterator Arg = ArgRange.begin() + Idx;
     unsigned InitialArgSize = Args.size();
     // If *Arg is an ObjCIndirectCopyRestoreExpr, check that either the types of
@@ -5188,14 +5200,16 @@ void CodeGenFunction::EmitCallArgs(
       // @llvm.objectsize should never have side-effects and shouldn't need
       // destruction/cleanups, so we can safely "emit" it after its arg,
       // regardless of right-to-leftness
-      MaybeEmitImplicitObjectSize(Idx, *Arg, RVArg);
+      MaybeEmitImplicitObjectSize(Idx, *Arg, RVArg, Reversed);
     }
+    if (HoistFirstArg && I == 0)
+      ReversedStart = Args.size();
   }
 
   if (!LeftToRight) {
     // Un-reverse the arguments we just evaluated so they match up with the LLVM
     // IR function.
-    std::reverse(Args.begin() + CallArgsStart, Args.end());
+    std::reverse(Args.begin() + ReversedStart, Args.end());
 
     // Reverse the writebacks to match the MSVC ABI.
     Args.reverseWritebacks();
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 8acf4b95f3e77..bc3e77d4a008c 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -7270,11 +7270,8 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
   if (Chain)
     Args.add(RValue::get(Chain), CGM.getContext().VoidPtrTy);
 
-  // C++17 requires that we evaluate arguments to a call using assignment syntax
-  // right-to-left, and that we evaluate arguments to certain other operators
-  // left-to-right. Note that we allow this to override the order dictated by
-  // the calling convention on the MS ABI, which means that parameter
-  // destruction order is not necessarily reverse construction order.
+  // Operator notation follows the language's operand order. On the MS ABI,
+  // this can make parameter destruction differ from reverse construction order.
   // FIXME: Revisit this based on C++ committee response to unimplementability.
   EvaluationOrder Order = EvaluationOrder::Default;
   bool StaticOperator = false;
@@ -7291,6 +7288,10 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
       case OO_ArrowStar:
         Order = EvaluationOrder::ForceLeftToRight;
         break;
+      case OO_Subscript:
+      case OO_Call:
+        Order = EvaluationOrder::ForceFirstBeforeRest;
+        break;
       default:
         break;
       }
@@ -7304,8 +7305,9 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
 
   auto Arguments = E->arguments();
   if (StaticOperator) {
-    // If we're calling a static operator, we need to emit the object argument
-    // and ignore it.
+    // A static operator evaluates and discards the object before the call.
+    if (Order == EvaluationOrder::ForceFirstBeforeRest)
+      Order = EvaluationOrder::Default;
     EmitIgnoredExpr(E->getArg(0));
     Arguments = drop_begin(Arguments, 1);
   }
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 9976a411fd673..f2db5d59bc88f 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -5653,7 +5653,10 @@ class CodeGenFunction : public CodeGenTypeCache {
     ///! Language semantics require left-to-right evaluation.
     ForceLeftToRight,
     ///! Language semantics require right-to-left evaluation.
-    ForceRightToLeft
+    ForceRightToLeft,
+    ///! Language semantics require only the first argument to be evaluated
+    ///! first; the others are indeterminately sequenced.
+    ForceFirstBeforeRest
   };
 
   // Wrapper for function prototype sources. Wraps either a FunctionProtoType or
diff --git a/clang/test/CodeGenCXX/cxx1z-eval-order.cpp b/clang/test/CodeGenCXX/cxx1z-eval-order.cpp
index 04c1b50f497e8..023d95cd88536 100644
--- a/clang/test/CodeGenCXX/cxx1z-eval-order.cpp
+++ b/clang/test/CodeGenCXX/cxx1z-eval-order.cpp
@@ -1,6 +1,9 @@
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ITANIUM
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple i686-windows | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-WINDOWS
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple x86_64-windows | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-WINDOWS
+// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=CXX23 --check-prefix=CXX23-ITANIUM
+// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple i686-windows | FileCheck %s --check-prefix=CXX23 --check-prefix=CXX23-WINDOWS
+// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple x86_64-windows | FileCheck %s --check-prefix=CXX23 --check-prefix=CXX23-WINDOWS
 
 struct B;
 struct A {
@@ -269,3 +272,72 @@ void andor_lhs_before_rhs() {
   // CHECK: call {{.*}}@{{.*}}make_b{{.*}}(
   make_c() || make_b();
 }
+
+#if __cplusplus >= 202302L
+struct D {
+  void operator[](this D self, B b);
+  void operator[](this D self, B b, C c);
+  void operator[](this D self, B b, C c, A a);
+  void operator()(this D self, B b, C c);
+};
+struct E {
+  static void operator()(B b, C c);
+};
+D make_d();
+E make_e();
+
+// CXX23-LABEL: define {{.*}}@{{.*}}subscript_object_before_index{{.*}}(
+void subscript_object_before_index() {
+  // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_b{{.*}}(
+  make_d()[make_b()];
+// CXX23: }
+}
+
+// CXX23-LABEL: define {{.*}}@{{.*}}subscript_object_before_indices{{.*}}(
+void subscript_object_before_indices() {
+  // The indices can run in either order, so keep the ABI order.
+  // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_b{{.*}}(
+  make_d()[make_b(), make_c()];
+// CXX23: }
+}
+
+// CXX23-LABEL: define {{.*}}@{{.*}}subscript_object_before_three_indices{{.*}}(
+void subscript_object_before_three_indices() {
+  // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_a{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_a{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_b{{.*}}(
+  make_d()[make_b(), make_c(), make_a()];
+// CXX23: }
+}
+
+// CXX23-LABEL: define {{.*}}@{{.*}}call_object_before_args{{.*}}(
+void call_object_before_args() {
+  // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_b{{.*}}(
+  make_d()(make_b(), make_c());
+// CXX23: }
+}
+
+// CXX23-LABEL: define {{.*}}@{{.*}}static_operator_object_first{{.*}}(
+void static_operator_object_first() {
+  // CXX23: call {{.*}}@{{.*}}make_e{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_b{{.*}}(
+  make_e()(make_b(), make_c());
+// CXX23: }
+}
+#endif
diff --git a/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp b/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp
index c879c483686e5..0e0a3de19ba7c 100644
--- a/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp
+++ b/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp
@@ -1,5 +1,7 @@
 // RUN: %clang_cc1 -mconstructor-aliases -std=c++11 -fexceptions -emit-llvm %s -o - -triple=i386-pc-win32 | FileCheck %s -check-prefix=X86
 // RUN: %clang_cc1 -mconstructor-aliases -std=c++11 -fexceptions -emit-llvm %s -o - -triple=x86_64-pc-win32 | FileCheck %s -check-prefix=X64
+// RUN: %clang_cc1 -mconstructor-aliases -std=c++23 -fexceptions -emit-llvm %s -o - -triple=i386-pc-win32 | FileCheck %s -check-prefix=X86-CXX23
+// RUN: %clang_cc1 -mconstructor-aliases -std=c++23 -fexceptions -emit-llvm %s -o - -triple=x86_64-pc-win32 | FileCheck %s -check-prefix=X64-CXX23
 
 struct A {
   A(int a);
@@ -11,7 +13,7 @@ struct A {
 void foo(A a, A b, A c) {
 }
 
-// Order of destruction should be left to right.
+// Parameter destruction is left to right.
 //
 // X86-LABEL: define dso_local void @"?foo@@YAXUA@@00 at Z"
 // X86:          (ptr inalloca([[argmem_ty:<{ %struct.A, %struct.A, %struct.A }>]]) %0)
@@ -74,3 +76,108 @@ void call_foo() {
 //
 //   ehcleanup:
 // X64: call void @"??1A@@QEAA at XZ"(ptr {{[^,]*}} %[[arg3]])
+
+#if __cplusplus >= 202302L
+struct B {
+  B(int b);
+  B(const B &o);
+  ~B();
+  int b;
+  void operator[](this B self, B i, B j);
+};
+
+void B::operator[](this B self, B i, B j) {
+}
+
+// Parameter destruction is left to right.
+//
+// X86-CXX23-LABEL: define dso_local void @"??AB@@SAX_VU0 at 00@Z"
+// X86-CXX23:          (ptr inalloca([[argmem_b:<{ %struct.B, %struct.B, %struct.B }>]]) %0)
+// X86-CXX23: %[[self:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %0, i32 0, i32 0
+// X86-CXX23: %[[i:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %0, i32 0, i32 1
+// X86-CXX23: %[[j:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %0, i32 0, i32 2
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[self]])
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[i]])
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[j]])
+// X86-CXX23: ret void
+
+// X64-CXX23-LABEL: define dso_local void @"??AB@@SAX_VU0 at 00@Z"
+// X64-CXX23:         (ptr {{[^,]*}} %[[self:[^,]*]], ptr {{[^,]*}} %[[i:[^,]*]], ptr {{[^,]*}} %[[j:[^)]*]])
+// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[self]])
+// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[i]])
+// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[j]])
+// X64-CXX23: ret void
+
+
+void call_subscript() {
+  B(1)[B(2), B(3)];
+}
+
+// X86-CXX23-LABEL: define dso_local void @"?call_subscript@@YAXXZ"()
+// X86-CXX23: %[[argmem:[^ ]*]] = alloca inalloca [[argmem_b]]
+// X86-CXX23: %[[obj:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 0
+// X86-CXX23: call x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[obj]], i32 noundef 1)
+// X86-CXX23: %[[idx2:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 2
+// X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[idx2]], i32 noundef 3)
+// X86-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]]
+// X86-CXX23: %[[idx1:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 1
+// X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[idx1]], i32 noundef 2)
+// X86-CXX23: to label %{{[^ ]+}} unwind label %[[IDX_CLEANUP:[^ ]+]]
+// X86-CXX23: call void @"??AB@@SAX_VU0 at 00@Z"(ptr inalloca([[argmem_b]]) %[[argmem]])
+// X86-CXX23: ret void
+//
+// X86-CXX23: [[IDX_CLEANUP]]:
+// X86-CXX23: cleanuppad within none []
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[idx2]])
+// X86-CXX23: cleanupret {{.*}} unwind label %[[OBJ_CLEANUP]]
+//
+// X86-CXX23: [[OBJ_CLEANUP]]:
+// X86-CXX23: cleanuppad within none []
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[obj]])
+
+// X64-CXX23-LABEL: define dso_local void @"?call_subscript@@YAXXZ"()
+// X64-CXX23: call noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[obj:[^,]*]], i32 noundef 1)
+// X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[idx2:[^,]*]], i32 noundef 3)
+// X64-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]]
+// X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[idx1:[^,]*]], i32 noundef 2)
+// X64-CXX23: to label %{{[^ ]+}} unwind label %[[IDX_CLEANUP:[^ ]+]]
+// X64-CXX23: call void @"??AB@@SAX_VU0 at 00@Z"
+// X64-CXX23:       (ptr {{[^,]*}} %[[obj]], ptr {{[^,]*}} %[[idx1]], ptr {{[^,]*}} %[[idx2]])
+// X64-CXX23: ret void
+//
+// X64-CXX23: [[IDX_CLEANUP]]:
+// X64-CXX23: cleanuppad within none []
+// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[idx2]])
+// X64-CXX23: cleanupret {{.*}} unwind label %[[OBJ_CLEANUP]]
+//
+// X64-CXX23: [[OBJ_CLEANUP]]:
+// X64-CXX23: cleanuppad within none []
+// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[obj]])
+
+struct SizeObject {
+  operator SizeObject *();
+  int operator[](this SizeObject *const Self
+                     __attribute__((pass_object_size(0))),
+                 int Index);
+};
+SizeObject make_size_object();
+int make_size_index();
+
+int pass_object_size_for_object() {
+  return make_size_object()[make_size_index()];
+}
+
+// X86-CXX23-LABEL: define dso_local noundef i32 @"?pass_object_size_for_object@@YAHXZ"()
+// X86-CXX23: call void @"?make_size_object@@YA?AUSizeObject@@XZ"()
+// X86-CXX23: %[[SELF:[^ ]+]] = call x86_thiscallcc noundef ptr @"??BSizeObject@@QAEPAU0 at XZ"
+// X86-CXX23: %[[SIZE:[^ ]+]] = call i32 @llvm.objectsize.i32.p0(ptr %[[SELF]],
+// X86-CXX23: %[[INDEX:[^ ]+]] = call noundef i32 @"?make_size_index@@YAHXZ"()
+// X86-CXX23: call noundef i32 @"??ASizeObject@@SAH_VQAU0 at W4__pass_object_size0@__clang@@H at Z"(ptr noundef %[[SELF]], i32 noundef %[[SIZE]], i32 noundef %[[INDEX]])
+
+// X64-CXX23-LABEL: define dso_local noundef i32 @"?pass_object_size_for_object@@YAHXZ"()
+// X64-CXX23: call i8 @"?make_size_object@@YA?AUSizeObject@@XZ"()
+// X64-CXX23: %[[SELF:[^ ]+]] = call noundef ptr @"??BSizeObject@@QEAAPEAU0 at XZ"
+// X64-CXX23: %[[SIZE:[^ ]+]] = call i64 @llvm.objectsize.i64.p0(ptr %[[SELF]],
+// X64-CXX23: %[[INDEX:[^ ]+]] = call noundef i32 @"?make_size_index@@YAHXZ"()
+// X64-CXX23: call noundef i32 @"??ASizeObject@@SAH_VQEAU0 at W4__pass_object_size0@__clang@@H at Z"(ptr noundef %[[SELF]], i64 noundef %[[SIZE]], i32 noundef %[[INDEX]])
+#endif

>From 1c19f327aa029be6bac776d1faeedc30ed32ebf2 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Thu, 1 Oct 2026 09:49:39 +0200
Subject: [PATCH 2/3] [Clang] Clarify explicit-object evaluation order comment

Assisted-by: Claude (Anthropic)
Co-Authored-By: Codex GPT-6 <noreply at openai.com>
---
 clang/lib/CodeGen/CodeGenFunction.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index f2db5d59bc88f..759b03772f56b 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -5654,8 +5654,7 @@ class CodeGenFunction : public CodeGenTypeCache {
     ForceLeftToRight,
     ///! Language semantics require right-to-left evaluation.
     ForceRightToLeft,
-    ///! Language semantics require only the first argument to be evaluated
-    ///! first; the others are indeterminately sequenced.
+    ///! Language semantics require the first argument before the others.
     ForceFirstBeforeRest
   };
 

>From 315d5c4c96c27fea42199c8ac3a5e5081ff249d5 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 2 Oct 2026 09:07:33 +0200
Subject: [PATCH 3/3] [Clang] Evaluate overloaded subscript indices left to
 right

MSVC evaluates scalar subscript operands from left to right, while class-valued operands can follow ABI argument order. Match the left-to-right subscript behavior in operator notation without changing argument placement or destruction order.

Extend the x86 and x64 CodeGen checks for explicit and implicit object parameters. Keep a call-operator check for ABI slot placement.

Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
Co-Authored-By: Codex GPT-6 <noreply at openai.com>
---
 clang/docs/ReleaseNotes.md                    |  5 +-
 clang/lib/CodeGen/CGExpr.cpp                  |  2 +-
 clang/lib/CodeGen/CGExprCXX.cpp               |  6 ++-
 clang/test/CodeGenCXX/cxx1z-eval-order.cpp    | 45 +++++++++++------
 .../CodeGenCXX/microsoft-abi-arg-order.cpp    | 48 ++++++++++++++++---
 5 files changed, 81 insertions(+), 25 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 4f70963b7864d..e04ae39b3287d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -623,8 +623,9 @@ features cannot lower the translation-unit ABI level;
 
 - Clang now evaluates the object before the arguments of overloaded
   `operator[]` and `operator()` with explicit object parameters on Microsoft
-  ABI targets. Parameter destruction still follows Microsoft ABI order and
-  can differ from reverse construction order.
+  ABI targets. It evaluates multidimensional subscript indices left to right.
+  Parameter destruction still follows Microsoft ABI order and can differ from
+  reverse construction order.
 
 - Fixed lambdas with specifiers or attributes after the capture list being
   misparsed as function declarations in direct-initialization contexts under
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index bc3e77d4a008c..f81051aa60e1d 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -7286,9 +7286,9 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
       case OO_PipePipe:
       case OO_Comma:
       case OO_ArrowStar:
+      case OO_Subscript:
         Order = EvaluationOrder::ForceLeftToRight;
         break;
-      case OO_Subscript:
       case OO_Call:
         Order = EvaluationOrder::ForceFirstBeforeRest;
         break;
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 8279611accba3..b6cfdc3cbcbed 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -67,13 +67,17 @@ commonEmitCXXMemberOrOperatorCall(CodeGenFunction &CGF, GlobalDecl GD,
   } else if (CE) {
     // Special case: skip first argument of CXXOperatorCall (it is "this").
     unsigned ArgsToSkip = 0;
+    CodeGenFunction::EvaluationOrder Order =
+        CodeGenFunction::EvaluationOrder::Default;
     if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(CE)) {
       if (const auto *M = dyn_cast<CXXMethodDecl>(Op->getCalleeDecl()))
         ArgsToSkip =
             static_cast<unsigned>(!M->isExplicitObjectMemberFunction());
+      if (Op->getOperator() == OO_Subscript)
+        Order = CodeGenFunction::EvaluationOrder::ForceLeftToRight;
     }
     CGF.EmitCallArgs(Args, FPT, drop_begin(CE->arguments(), ArgsToSkip),
-                     CE->getDirectCallee());
+                     CE->getDirectCallee(), /*ParamsToSkip=*/0, Order);
   } else {
     assert(
         FPT->getNumParams() == 0 &&
diff --git a/clang/test/CodeGenCXX/cxx1z-eval-order.cpp b/clang/test/CodeGenCXX/cxx1z-eval-order.cpp
index 023d95cd88536..1f8ae551e43fe 100644
--- a/clang/test/CodeGenCXX/cxx1z-eval-order.cpp
+++ b/clang/test/CodeGenCXX/cxx1z-eval-order.cpp
@@ -1,9 +1,9 @@
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ITANIUM
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple i686-windows | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-WINDOWS
 // RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple x86_64-windows | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-WINDOWS
-// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=CXX23 --check-prefix=CXX23-ITANIUM
-// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple i686-windows | FileCheck %s --check-prefix=CXX23 --check-prefix=CXX23-WINDOWS
-// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple x86_64-windows | FileCheck %s --check-prefix=CXX23 --check-prefix=CXX23-WINDOWS
+// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ITANIUM --check-prefix=CXX23 --check-prefix=CXX23-ITANIUM
+// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple i686-windows | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-WINDOWS --check-prefix=CXX23 --check-prefix=CXX23-WINDOWS
+// RUN: %clang_cc1 -std=c++23 %s -emit-llvm -o - -triple x86_64-windows | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-WINDOWS --check-prefix=CXX23 --check-prefix=CXX23-WINDOWS
 
 struct B;
 struct A {
@@ -282,9 +282,14 @@ struct D {
 };
 struct E {
   static void operator()(B b, C c);
+  static void operator[](B b, C c);
+};
+struct F {
+  void operator[](B b, C c);
 };
 D make_d();
 E make_e();
+F make_f();
 
 // CXX23-LABEL: define {{.*}}@{{.*}}subscript_object_before_index{{.*}}(
 void subscript_object_before_index() {
@@ -296,12 +301,9 @@ void subscript_object_before_index() {
 
 // CXX23-LABEL: define {{.*}}@{{.*}}subscript_object_before_indices{{.*}}(
 void subscript_object_before_indices() {
-  // The indices can run in either order, so keep the ABI order.
   // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
-  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_b{{.*}}(
-  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_c{{.*}}(
-  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_c{{.*}}(
-  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_c{{.*}}(
   make_d()[make_b(), make_c()];
 // CXX23: }
 }
@@ -309,16 +311,22 @@ void subscript_object_before_indices() {
 // CXX23-LABEL: define {{.*}}@{{.*}}subscript_object_before_three_indices{{.*}}(
 void subscript_object_before_three_indices() {
   // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
-  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_b{{.*}}(
-  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_c{{.*}}(
-  // CXX23-ITANIUM: call {{.*}}@{{.*}}make_a{{.*}}(
-  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_a{{.*}}(
-  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_c{{.*}}(
-  // CXX23-WINDOWS: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_c{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_a{{.*}}(
   make_d()[make_b(), make_c(), make_a()];
 // CXX23: }
 }
 
+// CXX23-LABEL: define {{.*}}@{{.*}}implicit_subscript_object_before_indices{{.*}}(
+void implicit_subscript_object_before_indices() {
+  // CXX23: call {{.*}}@{{.*}}make_f{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_c{{.*}}(
+  make_f()[make_b(), make_c()];
+// CXX23: }
+}
+
 // CXX23-LABEL: define {{.*}}@{{.*}}call_object_before_args{{.*}}(
 void call_object_before_args() {
   // CXX23: call {{.*}}@{{.*}}make_d{{.*}}(
@@ -340,4 +348,13 @@ void static_operator_object_first() {
   make_e()(make_b(), make_c());
 // CXX23: }
 }
+
+// CXX23-LABEL: define {{.*}}@{{.*}}static_subscript_object_first{{.*}}(
+void static_subscript_object_first() {
+  // CXX23: call {{.*}}@{{.*}}make_e{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_b{{.*}}(
+  // CXX23: call {{.*}}@{{.*}}make_c{{.*}}(
+  make_e()[make_b(), make_c()];
+// CXX23: }
+}
 #endif
diff --git a/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp b/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp
index 0e0a3de19ba7c..7d3a325e5072b 100644
--- a/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp
+++ b/clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp
@@ -84,6 +84,7 @@ struct B {
   ~B();
   int b;
   void operator[](this B self, B i, B j);
+  void operator()(this B self, B i, B j);
 };
 
 void B::operator[](this B self, B i, B j) {
@@ -117,18 +118,18 @@ void call_subscript() {
 // X86-CXX23: %[[argmem:[^ ]*]] = alloca inalloca [[argmem_b]]
 // X86-CXX23: %[[obj:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 0
 // X86-CXX23: call x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[obj]], i32 noundef 1)
-// X86-CXX23: %[[idx2:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 2
-// X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[idx2]], i32 noundef 3)
-// X86-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]]
 // X86-CXX23: %[[idx1:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 1
 // X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[idx1]], i32 noundef 2)
+// X86-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]]
+// X86-CXX23: %[[idx2:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 2
+// X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[idx2]], i32 noundef 3)
 // X86-CXX23: to label %{{[^ ]+}} unwind label %[[IDX_CLEANUP:[^ ]+]]
 // X86-CXX23: call void @"??AB@@SAX_VU0 at 00@Z"(ptr inalloca([[argmem_b]]) %[[argmem]])
 // X86-CXX23: ret void
 //
 // X86-CXX23: [[IDX_CLEANUP]]:
 // X86-CXX23: cleanuppad within none []
-// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[idx2]])
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[idx1]])
 // X86-CXX23: cleanupret {{.*}} unwind label %[[OBJ_CLEANUP]]
 //
 // X86-CXX23: [[OBJ_CLEANUP]]:
@@ -137,9 +138,9 @@ void call_subscript() {
 
 // X64-CXX23-LABEL: define dso_local void @"?call_subscript@@YAXXZ"()
 // X64-CXX23: call noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[obj:[^,]*]], i32 noundef 1)
-// X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[idx2:[^,]*]], i32 noundef 3)
-// X64-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]]
 // X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[idx1:[^,]*]], i32 noundef 2)
+// X64-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]]
+// X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[idx2:[^,]*]], i32 noundef 3)
 // X64-CXX23: to label %{{[^ ]+}} unwind label %[[IDX_CLEANUP:[^ ]+]]
 // X64-CXX23: call void @"??AB@@SAX_VU0 at 00@Z"
 // X64-CXX23:       (ptr {{[^,]*}} %[[obj]], ptr {{[^,]*}} %[[idx1]], ptr {{[^,]*}} %[[idx2]])
@@ -147,13 +148,46 @@ void call_subscript() {
 //
 // X64-CXX23: [[IDX_CLEANUP]]:
 // X64-CXX23: cleanuppad within none []
-// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[idx2]])
+// X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[idx1]])
 // X64-CXX23: cleanupret {{.*}} unwind label %[[OBJ_CLEANUP]]
 //
 // X64-CXX23: [[OBJ_CLEANUP]]:
 // X64-CXX23: cleanuppad within none []
 // X64-CXX23: call void @"??1B@@QEAA at XZ"(ptr {{[^,]*}} %[[obj]])
 
+void call_call() {
+  B(1)(B(2), B(3));
+}
+
+// The call arguments must occupy their declared parameter slots.
+//
+// X86-CXX23-LABEL: define dso_local void @"?call_call@@YAXXZ"()
+// X86-CXX23: %[[argmem:[^ ]*]] = alloca inalloca [[argmem_b]]
+// X86-CXX23: %[[obj:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 0
+// X86-CXX23: call x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[obj]], i32 noundef 1)
+// X86-CXX23: %[[arg2:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 2
+// X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[arg2]], i32 noundef 3)
+// X86-CXX23: %[[arg1:[^ ]*]] = getelementptr inbounds nuw [[argmem_b]], ptr %[[argmem]], i32 0, i32 1
+// X86-CXX23: invoke x86_thiscallcc noundef ptr @"??0B@@QAE at H@Z"(ptr {{[^,]*}} %[[arg1]], i32 noundef 2)
+// X86-CXX23: call void @"??RB@@SAX_VU0 at 00@Z"(ptr inalloca([[argmem_b]]) %[[argmem]])
+// X86-CXX23: ret void
+//
+//   ehcleanup:
+// X86-CXX23: cleanuppad within none []
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[arg2]])
+// X86-CXX23: cleanupret
+//
+//   ehcleanup4:
+// X86-CXX23: cleanuppad within none []
+// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE at XZ"(ptr {{[^,]*}} %[[obj]])
+
+// X64-CXX23-LABEL: define dso_local void @"?call_call@@YAXXZ"()
+// X64-CXX23: call noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[obj:[^,]*]], i32 noundef 1)
+// X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[arg2:[^,]*]], i32 noundef 3)
+// X64-CXX23: invoke noundef ptr @"??0B@@QEAA at H@Z"(ptr {{[^,]*}} %[[arg1:[^,]*]], i32 noundef 2)
+// X64-CXX23: call void @"??RB@@SAX_VU0 at 00@Z"
+// X64-CXX23:       (ptr {{[^,]*}} %[[obj]], ptr {{[^,]*}} %[[arg1]], ptr {{[^,]*}} %[[arg2]])
+// X64-CXX23: ret void
 struct SizeObject {
   operator SizeObject *();
   int operator[](this SizeObject *const Self



More information about the cfe-commits mailing list