https://github.com/yuxuanchen1997 updated 
https://github.com/llvm/llvm-project/pull/178495

>From d7904ab087f5801f9da7a89079b9a6c4122e9b8b Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <[email protected]>
Date: Wed, 28 Jan 2026 11:14:32 -0800
Subject: [PATCH] [Clang] Fix coro_await_elidable breaking with parenthesized
 expressions

The applySafeElideContext function used IgnoreImplicit() to find the
underlying CallExpr, but this didn't strip ParenExpr nodes. When code
like `co_await (fn(leaf()))` was parsed, the operand was wrapped in a
ParenExpr, causing HALO (Heap Allocation eLision Optimization) to fail.

This fix uses IgnoreExprNodes with IgnoreImplicitSingleStep and
IgnoreParensSingleStep to iteratively strip both implicit nodes and
parentheses until reaching a fixed point, handling any ordering of
these nodes in the AST.

Fixes the issue where adding parentheses around co_await's argument
would prevent heap elision for coro_await_elidable coroutines, which
is particularly problematic since parentheses are often required in
real-world code due to co_await's tight binding with operators.
---
 clang/lib/Sema/SemaCoroutine.cpp              |  7 ++-
 .../CodeGenCoroutines/coro-await-elidable.cpp | 50 +++++++++++++++++++
 2 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index c0aba832dba94..90af7340c4614 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
+#include "clang/AST/IgnoreExpr.h"
 #include "clang/AST/StmtCXX.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Lex/Preprocessor.h"
@@ -841,7 +842,11 @@ static bool isAttributedCoroAwaitElidable(const QualType 
&QT) {
 }
 
 static void applySafeElideContext(Expr *Operand) {
-  auto *Call = dyn_cast<CallExpr>(Operand->IgnoreImplicit());
+  // Strip both implicit nodes and parentheses to find the underlying CallExpr.
+  // The AST may have these in either order, so we apply both transformations
+  // iteratively until reaching a fixed point.
+  auto *Call = dyn_cast<CallExpr>(IgnoreExprNodes(
+      Operand, IgnoreImplicitSingleStep, IgnoreParensSingleStep));
   if (!Call || !Call->isPRValue())
     return;
 
diff --git a/clang/test/CodeGenCoroutines/coro-await-elidable.cpp 
b/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
index deb19b4a50043..71c56f310b344 100644
--- a/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
+++ b/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
@@ -124,4 +124,54 @@ Task<int> elidableWithPackRecursive() {
   co_return co_await sumAll(addTasks(returnSame(1), returnSame(2)), 
returnSame(3));
 }
 
+// Test that parenthesized expressions don't break HALO
+// CHECK-LABEL: define{{.*}} @_Z14withParenthesev{{.*}} {
+Task<int> withParenthese() {
+  // CHECK: call void @_Z6calleev(ptr {{.*}}) #[[ELIDE_SAFE]]
+  co_return co_await (callee());
+}
+
+// Test nested parentheses
+// CHECK-LABEL: define{{.*}} @_Z20withNestedParenthesev{{.*}} {
+Task<int> withNestedParenthese() {
+  // CHECK: call void @_Z6calleev(ptr {{.*}}) #[[ELIDE_SAFE]]
+  co_return co_await ((callee()));
+}
+
+// Test parentheses with elidable argument
+// CHECK-LABEL: define{{.*}} @_Z21withParenArgsElidablev{{.*}} {
+Task<int> withParenArgsElidable() {
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) 
#[[ELIDE_SAFE]]
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3){{$}}
+  co_return co_await (addTasks(returnSame(2), returnSame(3)));
+}
+
+// Test parentheses around elidable argument expressions
+// CHECK-LABEL: define{{.*}} @_Z24withParenInsideArgsFirstv{{.*}} {
+Task<int> withParenInsideArgsFirst() {
+  // Argument wrapped in parens should still be elidable
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 4) 
#[[ELIDE_SAFE]]
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 5){{$}}
+  co_return co_await addTasks((returnSame(4)), returnSame(5));
+}
+
+// CHECK-LABEL: define{{.*}} @_Z25withParenInsideArgsSecondv{{.*}} {
+Task<int> withParenInsideArgsSecond() {
+  // Both arguments wrapped in parens, first should still be elidable
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 6) 
#[[ELIDE_SAFE]]
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 7){{$}}
+  co_return co_await addTasks((returnSame(6)), (returnSame(7)));
+}
+
+// Test operator overloading scenario (like the `operator|` case)
+Task<int> operator|(int, [[clang::coro_await_elidable_argument]] Task<int> 
&&t) {
+  co_return co_await t;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z15withOperatorOldv{{.*}} {
+Task<int> withOperatorOld() {
+  // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 8) 
#[[ELIDE_SAFE]]
+  co_return co_await (0 | returnSame(8));
+}
+
 // CHECK: attributes #[[ELIDE_SAFE]] = { coro_elide_safe }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to