llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Xavier Roche (xroche)
<details>
<summary>Changes</summary>
On Microsoft ABI targets, an overloaded `operator[]` or `operator()` declared
with a C++23 explicit object parameter evaluates its index or argument
expressions before the object expression, and for a multi-index subscript the
object goes last:
```cpp
// clang++ -std=c++23 --target=x86_64-windows-msvc
struct A { int operator[](this A self, int i) { return i; } };
A make_a();
int make_i();
int f() { return make_a()[make_i()]; } // calls make_i() before make_a()
```
[expr.sub]/1 sequences the object argument before each index expression,
[expr.call]/8 does the same for a call, and [over.match.oper]/2 carries both
orders over to the overloaded forms. The prescribed-order switch in `EmitCall`
covers `<<`, `>>`, `&&`, `||`, `,` and `->*` but has no
`OO_Subscript` or `OO_Call` case, so those two fall back to the ABI's
right-to-left argument emission. Itanium targets already emit left to right. An
implicit-object operator resolves the object through the this-first member path
and a static operator emits and discards it up front, so neither is affected.
The index or argument expressions are unsequenced among themselves, so
`ForceLeftToRight` would constrain more than the standard asks, and on the MS
ABI argument evaluation order also drives parameter destruction order. This
adds a fourth `EvaluationOrder` value meaning "first argument before the rest"
and uses it for both operators at any arity, which makes a multi-index
subscript the general case and the single-index form fall out of it. Only the
object argument is hoisted out of the reversed run.
The switch now enumerates every operator with no `default:`, so `-Wswitch`
forces a decision on any operator added later.
Sema's `SequenceChecker` keeps an independent copy of the same list, which does
include both operators, so the two have disagreed since D81330. That copy still
bails out for a multi-index subscript, since it requires either exactly two
arguments or `OO_Call`, so `-Wunsequenced` does not model object-before-indices
there and now warns about an expression whose order CodeGen guarantees. That is
pre-existing and left for a separate patch.
Follow-up to #<!-- -->199351, where these missing cases had to be worked around
in the musttail argument-forwarding guard instead.
Assisted-by: Claude (Anthropic)
---
Full diff: https://github.com/llvm/llvm-project/pull/215479.diff
6 Files Affected:
- (modified) clang/docs/ReleaseNotes.md (+6)
- (modified) clang/lib/CodeGen/CGCall.cpp (+21-5)
- (modified) clang/lib/CodeGen/CGExpr.cpp (+77-20)
- (modified) clang/lib/CodeGen/CodeGenFunction.h (+4-1)
- (modified) clang/test/CodeGenCXX/cxx1z-eval-order.cpp (+78)
- (modified) clang/test/CodeGenCXX/microsoft-abi-arg-order.cpp (+79)
``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a00b725143d49..0472f20377d89 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -386,6 +386,12 @@ features cannot lower the translation-unit ABI level;
#### Bug Fixes to C++ Support
+- Fixed the evaluation order of an overloaded `operator[]` or `operator()`
+ declared with an explicit object parameter on Microsoft ABI targets. The
index
+ or argument expressions were evaluated before the object expression.
Parameter
+ destruction order for such a call is no longer reverse construction order, as
+ with the other operators that have a prescribed operand order.
+
- Fixed an issue where `__typeof__` incorrectly rejected cv-qualified function
types.
- Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped
from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and
``__ptr64``) and WebAssembly's ``__funcref``.
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index a29f7aab0e58b..9c6b4898bf580 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5106,8 +5106,13 @@ void CodeGenFunction::EmitCallArgs(
? Order == EvaluationOrder::ForceLeftToRight
: Order != EvaluationOrder::ForceRightToLeft;
+ // Only the first argument is hoisted out of the reversed run; the rest keep
+ // the ABI's order, which left-to-right emission already gives them.
+ 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>();
@@ -5123,7 +5128,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));
};
@@ -5136,8 +5141,17 @@ void CodeGenFunction::EmitCallArgs(
// Evaluate each argument in the appropriate order.
size_t CallArgsStart = Args.size();
+ // Start of the run of arguments emitted in reverse, past a hoisted first
one.
+ size_t ReversedStart = CallArgsStart;
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; // 0, then E-1 down to 1.
+ 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
@@ -5163,14 +5177,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 9201e40bc13a1..c313e55b6d4a3 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -7098,30 +7098,84 @@ 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.
+ // C++17 [over.match.oper]/2 keeps the operands of an overloaded operator in
+ // the order prescribed for the built-in operator. 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.
// FIXME: Revisit this based on C++ committee response to unimplementability.
EvaluationOrder Order = EvaluationOrder::Default;
bool StaticOperator = false;
if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(E)) {
- if (OCE->isAssignmentOp())
+ // Every operator is listed so that -Wswitch forces a decision on any
+ // operator added later.
+ switch (OCE->getOperator()) {
+ // The assignment operators sequence the right operand before the left.
This
+ // list is also spelled out in CXXOperatorCallExpr::isAssignmentOp.
+ case OO_Equal:
+ case OO_PlusEqual:
+ case OO_MinusEqual:
+ case OO_StarEqual:
+ case OO_SlashEqual:
+ case OO_PercentEqual:
+ case OO_CaretEqual:
+ case OO_AmpEqual:
+ case OO_PipeEqual:
+ case OO_LessLessEqual:
+ case OO_GreaterGreaterEqual:
Order = EvaluationOrder::ForceRightToLeft;
- else {
- switch (OCE->getOperator()) {
- case OO_LessLess:
- case OO_GreaterGreater:
- case OO_AmpAmp:
- case OO_PipePipe:
- case OO_Comma:
- case OO_ArrowStar:
- Order = EvaluationOrder::ForceLeftToRight;
- break;
- default:
- break;
- }
+ break;
+
+ // The shift, logical, comma and pointer-to-member operators sequence the
+ // left operand before the right.
+ case OO_LessLess:
+ case OO_GreaterGreater:
+ case OO_AmpAmp:
+ case OO_PipePipe:
+ case OO_Comma:
+ case OO_ArrowStar:
+ Order = EvaluationOrder::ForceLeftToRight;
+ break;
+
+ // [expr.sub]/1 and [expr.call]/8 sequence the object argument before the
+ // index or argument expressions, which are unsequenced among themselves.
+ case OO_Subscript:
+ case OO_Call:
+ Order = EvaluationOrder::ForceFirstBeforeRest;
+ break;
+
+ // No constraint on the operand order.
+ case OO_New:
+ case OO_Delete:
+ case OO_Array_New:
+ case OO_Array_Delete:
+ case OO_Plus:
+ case OO_Minus:
+ case OO_Star:
+ case OO_Slash:
+ case OO_Percent:
+ case OO_Caret:
+ case OO_Amp:
+ case OO_Pipe:
+ case OO_Tilde:
+ case OO_Exclaim:
+ case OO_Less:
+ case OO_Greater:
+ case OO_EqualEqual:
+ case OO_ExclaimEqual:
+ case OO_LessEqual:
+ case OO_GreaterEqual:
+ case OO_Spaceship:
+ case OO_PlusPlus:
+ case OO_MinusMinus:
+ case OO_Arrow:
+ case OO_Conditional:
+ case OO_Coawait:
+ break;
+
+ case OO_None:
+ case NUM_OVERLOADED_OPERATORS:
+ llvm_unreachable("Not an overloaded operator");
}
if (const auto *MD =
@@ -7133,7 +7187,10 @@ 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.
+ // and ignore it. Emitting it here already sequences the object first, so
no
+ // further constraint is needed.
+ 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 783f97dc354eb..4663fba4c3d42 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -5565,7 +5565,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 unsequenced.
+ 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..dca026d236d18 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,78 @@ void andor_lhs_before_rhs() {
// CHECK: call {{.*}}@{{.*}}make_b{{.*}}(
make_c() || make_b();
}
+
+// The C++23 cases live here rather than in a deducing-this test because the
+// rule under test is operand order, not the explicit object parameter itself.
+#if __cplusplus >= 202302L
+// An operator with an explicit object parameter takes the object as an
ordinary
+// argument, so the object-before-rest order is not implied by the this-first
+// emission path.
+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();
+
+// One case per function, so a label bounds each order assertion.
+// 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 are unsequenced against each other, so they 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 3f597705e0704..708242da4a199 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);
@@ -74,3 +76,80 @@ 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) {
+}
+
+// Order of destruction should be 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 noundef align 4 dead_on_return %[[self:[^,]*]], ptr
noundef align 4 dead_on_return %[[i:[^,]*]], ptr noundef align 4 dead_on_return
%[[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)];
+}
+
+// The object argument is evaluated first, the indices keep the right-to-left
+// order, and we should clean up the right things as we unwind.
+//
+// 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: %[[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: call void @"??AB@@SAX_VU0@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@XZ"(ptr {{[^,]*}} %[[idx2]])
+// X86-CXX23: cleanupret
+//
+// ehcleanup4:
+// 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: invoke noundef ptr @"??0B@@QEAA@H@Z"(ptr {{[^,]*}}
%[[idx1:[^,]*]], i32 noundef 2)
+// X64-CXX23: call void @"??AB@@SAX_VU0@00@Z"
+// X64-CXX23: (ptr noundef align 4 dead_on_return %[[obj]], ptr noundef
align 4 dead_on_return %[[idx1]], ptr noundef align 4 dead_on_return %[[idx2]])
+// X64-CXX23: ret void
+//
+// ehcleanup:
+// X64-CXX23: cleanuppad within none []
+// X64-CXX23: call void @"??1B@@QEAA@XZ"(ptr {{[^,]*}} %[[idx2]])
+// X64-CXX23: cleanupret
+//
+// ehcleanup6:
+// X64-CXX23: cleanuppad within none []
+// X64-CXX23: call void @"??1B@@QEAA@XZ"(ptr {{[^,]*}} %[[obj]])
+#endif
``````````
</details>
https://github.com/llvm/llvm-project/pull/215479
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits