[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