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
