https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/215479
>From 7ab50f553aca50e277eb8fab6e390bacec87ac07 Mon Sep 17 00:00:00 2001 From: Xavier Roche <[email protected]> Date: Mon, 10 Aug 2026 18:03:56 +0200 Subject: [PATCH 1/2] [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 <[email protected]> Co-Authored-By: Codex GPT-6 <[email protected]> --- 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 bf190df9769dd..fe549129f94ec 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -617,6 +617,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 a stack overflow crash when evaluating deeply recursive `constexpr` function calls. (#GH201418, #GH200673) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 2193a89203f29..bb0563a099178 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@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@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@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@XZ"(ptr {{[^,]*}} %[[self]]) +// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE@XZ"(ptr {{[^,]*}} %[[i]]) +// X86-CXX23: call x86_thiscallcc void @"??1B@@QAE@XZ"(ptr {{[^,]*}} %[[j]]) +// X86-CXX23: ret void + +// X64-CXX23-LABEL: define dso_local void @"??AB@@SAX_VU0@00@Z" +// X64-CXX23: (ptr {{[^,]*}} %[[self:[^,]*]], ptr {{[^,]*}} %[[i:[^,]*]], ptr {{[^,]*}} %[[j:[^)]*]]) +// X64-CXX23: call void @"??1B@@QEAA@XZ"(ptr {{[^,]*}} %[[self]]) +// X64-CXX23: call void @"??1B@@QEAA@XZ"(ptr {{[^,]*}} %[[i]]) +// X64-CXX23: call void @"??1B@@QEAA@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@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@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@H@Z"(ptr {{[^,]*}} %[[idx1]], i32 noundef 2) +// X86-CXX23: to label %{{[^ ]+}} unwind label %[[IDX_CLEANUP:[^ ]+]] +// X86-CXX23: call void @"??AB@@SAX_VU0@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@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@XZ"(ptr {{[^,]*}} %[[obj]]) + +// X64-CXX23-LABEL: define dso_local void @"?call_subscript@@YAXXZ"() +// X64-CXX23: call noundef ptr @"??0B@@QEAA@H@Z"(ptr {{[^,]*}} %[[obj:[^,]*]], i32 noundef 1) +// X64-CXX23: invoke noundef ptr @"??0B@@QEAA@H@Z"(ptr {{[^,]*}} %[[idx2:[^,]*]], i32 noundef 3) +// X64-CXX23: to label %{{[^ ]+}} unwind label %[[OBJ_CLEANUP:[^ ]+]] +// X64-CXX23: invoke noundef ptr @"??0B@@QEAA@H@Z"(ptr {{[^,]*}} %[[idx1:[^,]*]], i32 noundef 2) +// X64-CXX23: to label %{{[^ ]+}} unwind label %[[IDX_CLEANUP:[^ ]+]] +// X64-CXX23: call void @"??AB@@SAX_VU0@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@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@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@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@W4__pass_object_size0@__clang@@H@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@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@W4__pass_object_size0@__clang@@H@Z"(ptr noundef %[[SELF]], i64 noundef %[[SIZE]], i32 noundef %[[INDEX]]) +#endif >From fae7b7f4639ea1cf6bbc27da30d06f4db418b227 Mon Sep 17 00:00:00 2001 From: Xavier Roche <[email protected]> Date: Thu, 1 Oct 2026 09:49:39 +0200 Subject: [PATCH 2/2] [Clang] Clarify explicit-object evaluation order comment Assisted-by: Claude (Anthropic) Co-Authored-By: Codex GPT-6 <[email protected]> --- 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 }; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
