Skip to content

[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

Merged
merged 1 commit into from
Nov 2, 2023

Conversation

yuxuanchen1997
Copy link
Member

This PR is proposing a fix for #65971.

Previously, given a coroutine like this

task foo(int a) {
  co_return;
}

Parameter a is 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's Referenced state.

@yuxuanchen1997 yuxuanchen1997 force-pushed the coroutine_unused_param_2 branch from 54255c5 to 453dd8c Compare November 1, 2023 19:18
@yuxuanchen1997 yuxuanchen1997 force-pushed the coroutine_unused_param_2 branch from 453dd8c to 3be73f1 Compare November 1, 2023 19:34
@yuxuanchen1997 yuxuanchen1997 marked this pull request as ready for review November 1, 2023 19:53
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" coroutines C++20 coroutines labels Nov 1, 2023
@llvmbot
Copy link
Member

llvmbot commented Nov 1, 2023

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-coroutines

Author: Yuxuan Chen (yuxuanchen1997)

Changes

This PR is proposing a fix for #65971.

Previously, given a coroutine like this

task foo(int a) {
  co_return;
}

Parameter a is 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's Referenced state.


Full diff: https://github.com/llvm/llvm-project/pull/70973.diff

2 Files Affected:

  • (modified) clang/lib/Sema/SemaCoroutine.cpp (+6)
  • (added) clang/test/SemaCXX/warn-unused-parameters-coroutine.cpp (+34)
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;
+  };
+}

@yuxuanchen1997
Copy link
Member Author

@ChuanqiXu9 it was my mistake. This patch does work.

@dcci dcci requested a review from ChuanqiXu9 November 1, 2023 22:42
Copy link
Member

@bcardosolopes bcardosolopes left a 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.

Copy link
Member

@bcardosolopes bcardosolopes left a 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.

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a 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.

@@ -1965,9 +1965,15 @@ bool Sema::buildCoroutineParameterMoves(SourceLocation Loc) {
if (PD->getType()->isDependentType())
continue;

// Preserve the referenced state for unused parameter diagnostics.
Copy link
Member

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.

@@ -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();
Copy link
Member

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.

Copy link
Member Author

@yuxuanchen1997 yuxuanchen1997 Nov 2, 2023

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool.

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool.

@ChuanqiXu9 ChuanqiXu9 merged commit 858b56e into llvm:main Nov 2, 2023
@yuxuanchen1997 yuxuanchen1997 deleted the coroutine_unused_param_2 branch November 3, 2023 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category coroutines C++20 coroutines
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

4 participants