- Notifications
You must be signed in to change notification settings - Fork 15.3k
[Clang] Preserve coroutine parameter referenced state #70973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
54255c5 to 453dd8c Compare 453dd8c to 3be73f1 Compare | @llvm/pr-subscribers-clang @llvm/pr-subscribers-coroutines Author: Yuxuan Chen (yuxuanchen1997) ChangesThis PR is proposing a fix for #65971. Previously, given a coroutine like this Parameter Compiler Explorer shows that GCC warns against this case correctly, but clang does not: https://godbolt.org/z/Wo7dfqeaf This patch addresses this issue by preserving the original Full diff: https://github.com/llvm/llvm-project/pull/70973.diff 2 Files Affected:
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 38ac406b14adadf..bee80db8d166a68 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -1965,9 +1965,15 @@ bool Sema::buildCoroutineParameterMoves(SourceLocation Loc) { if (PD->getType()->isDependentType()) continue; + // Preserve the referenced state for unused parameter diagnostics. + bool DeclReferenced = PD->isReferenced(); + ExprResult PDRefExpr = BuildDeclRefExpr(PD, PD->getType().getNonReferenceType(), ExprValueKind::VK_LValue, Loc); // FIXME: scope? + + PD->setReferenced(DeclReferenced); + if (PDRefExpr.isInvalid()) return false; diff --git a/clang/test/SemaCXX/warn-unused-parameters-coroutine.cpp b/clang/test/SemaCXX/warn-unused-parameters-coroutine.cpp new file mode 100644 index 000000000000000..b4c01550f9f7883 --- /dev/null +++ b/clang/test/SemaCXX/warn-unused-parameters-coroutine.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -fsyntax-only -Wunused-parameter -verify -std=c++20 %s + +#include "Inputs/std-coroutine.h" + +struct awaitable { + bool await_ready() noexcept; + void await_resume() noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; +}; + +struct task : awaitable { + struct promise_type { + task get_return_object() noexcept; + awaitable initial_suspend() noexcept; + awaitable final_suspend() noexcept; + void unhandled_exception() noexcept; + void return_void() noexcept; + }; +}; + +task foo(int a) { // expected-warning{{unused parameter 'a'}} + co_return; +} + +task bar(int a, int b) { // expected-warning{{unused parameter 'b'}} + a = a + 1; + co_return; +} + +void create_closure() { + auto closure = [](int c) -> task { // expected-warning{{unused parameter 'c'}} + co_return; + }; +} |
| @ChuanqiXu9 it was my mistake. This patch does work. |
bcardosolopes left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for improving this! I haven't looked at your previous review of this PR, but I like the simplicity of this one.
bcardosolopes left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. @ChuanqiXu9 should give the final blessing though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks much better now. Let's try to optimize it a bit more.
| if (PD->getType()->isDependentType()) | ||
| continue; | ||
| | ||
| // Preserve the referenced state for unused parameter diagnostics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Readers can understand what is going on here. But they may be confused about the motivation. Let's add a comment to explain the reasons. I mean, something like, if we don't do so, we can't diagnose the unused parameter things.
| continue; | ||
| | ||
| // Preserve the referenced state for unused parameter diagnostics. | ||
| bool DeclReferenced = PD->isReferenced(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we will construct the parameter moves at the very beginning of the coroutine function, I am wondering if it is really possible that PD->isReferenced() may be true now. And if it is not possible,
we should convert it to assert(!PD->isReferenced());. Please changing this after verifying this with some actual coroutine related workloads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PD->isReferenced() may be true. The order Sema processes things is based on how Parser is sending AST nodes to it. I can try to explain based on my limited understanding and corroborate with a test case. So please feel free to correct me if I am wrong.
Let's take a look at this very simple coroutine. Suppose task is defined somewhere as a return object.
task foo(int a, int b) { a = a + 1; co_return; } Sema is first asked to compile foo like a normal function. Decl a and b are processed normally. When we use a in the a = a + 1 statement, we mark a as referenced.
When the parser encounters co_return, it calls Sema::ActOnCoreturn.
What Sema::ActOnCoreturn (and its siblings handling other co_ family of statements) does is to call Sema::ActOnCoroutineBodyStart. It checks if clang is already treating foo as a coroutine. It will find out that, no. We are not compiling foo like a coroutine yet, let's perform necessary checks and establish this context. buildCoroutineParameterMoves is called as a result. At that time a already has references we need to carry along.
Suppose that our function has subsequent co_* statements, Sema knows that we already established the context and doesn't build them again.
The aforementioned foo coroutine will cause clang ICE if we assert(!PD->isReferenced()) here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool.
ChuanqiXu9 left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
| continue; | ||
| | ||
| // Preserve the referenced state for unused parameter diagnostics. | ||
| bool DeclReferenced = PD->isReferenced(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool.
This PR is proposing a fix for #65971.
Previously, given a coroutine like this
Parameter
ais never used. However, because C++ coroutines move constructs the variable to a heap allocated coroutine activation frame, we considered all parameters referenced. When diagnosing unused parameters, we cannot distinguish if the variable reference was due to coroutine parameter moves.Compiler Explorer shows that GCC warns against this case correctly, but clang does not: https://godbolt.org/z/Wo7dfqeaf
This patch addresses this issue by preserving the original
ParmVarDecl'sReferencedstate.