-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Too aggresive C++23 static call operator optimization #67976
Comments
Godbolt: https://godbolt.org/z/d8sG1so1h |
@llvm/issue-subscribers-bug
Hello,
It looks like clang is too aggressive for C++23 static call optimization. Here is the simplified example of real code: #include <functional>
#include <iostream>
template <typename Tag>
struct Wrapper {
template <typename F, typename... Args>
static void operator()(F&& callable, Args&&... args) {
Invoke(std::forward<F>(callable), std::forward<Args>(args)...);
}
template <typename F, typename... Args>
static void Invoke(F&& callable, Args&&... args) {
std::invoke(std::forward<F>(callable), std::forward<Args>(args)...);
}
};
struct Manager {
template <typename SingletonClass>
static SingletonClass& Get() {
std::cout << "get call\n";
static SingletonClass instance;
return instance;
}
};
struct Tag {};
int main()
{
using TaggedWrapper = Wrapper<Tag>;
Manager::Get<TaggedWrapper>()([] { std::cout << "Function\n"; });
} We assume that Manager::Get() will be called and |
Reduced code: https://godbolt.org/z/q84zajYGs
The expression |
@llvm/issue-subscribers-clang-frontend
Hello,
It looks like clang is too aggressive for C++23 static call optimization. Here is the simplified example of real code: #include <functional>
#include <iostream>
template <typename Tag>
struct Wrapper {
template <typename F, typename... Args>
static void operator()(F&& callable, Args&&... args) {
Invoke(std::forward<F>(callable), std::forward<Args>(args)...);
}
template <typename F, typename... Args>
static void Invoke(F&& callable, Args&&... args) {
std::invoke(std::forward<F>(callable), std::forward<Args>(args)...);
}
};
struct Manager {
template <typename SingletonClass>
static SingletonClass& Get() {
std::cout << "get call\n";
static SingletonClass instance;
return instance;
}
};
struct Tag {};
int main()
{
using TaggedWrapper = Wrapper<Tag>;
Manager::Get<TaggedWrapper>()([] { std::cout << "Function\n"; });
} We assume that Manager::Get() will be called and |
@llvm/issue-subscribers-clang-codegen
Hello,
It looks like clang is too aggressive for C++23 static call optimization. Here is the simplified example of real code: #include <functional>
#include <iostream>
template <typename Tag>
struct Wrapper {
template <typename F, typename... Args>
static void operator()(F&& callable, Args&&... args) {
Invoke(std::forward<F>(callable), std::forward<Args>(args)...);
}
template <typename F, typename... Args>
static void Invoke(F&& callable, Args&&... args) {
std::invoke(std::forward<F>(callable), std::forward<Args>(args)...);
}
};
struct Manager {
template <typename SingletonClass>
static SingletonClass& Get() {
std::cout << "get call\n";
static SingletonClass instance;
return instance;
}
};
struct Tag {};
int main()
{
using TaggedWrapper = Wrapper<Tag>;
Manager::Get<TaggedWrapper>()([] { std::cout << "Function\n"; });
} We assume that Manager::Get() will be called and |
If we look at the AST: https://godbolt.org/z/7xExo9oob `-FunctionDecl <line:17:1, line:20:1> line:17:5 main 'int ()'
`-CompoundStmt <col:12, line:20:1>
|-CallExpr <line:18:19, col:20> 'void'
| `-ImplicitCastExpr <col:19, col:20> 'void (*)()' <FunctionToPointerDecay>
| `-DeclRefExpr <col:19, col:20> 'void ()' lvalue CXXMethod 0xf6ea300 'operator()' 'void ()' It is already elided away. CC @zygoloid |
Yeah, that AST is clearly wrong. The callee of the outer call expression should be a member access, like it is for a call to `-FunctionDecl <line:17:1, line:20:1> line:17:5 main 'int ()'
`-CompoundStmt <col:12, line:20:1>
|-CallExpr <line:18:5, col:31> 'void'
| `-ImplicitCastExpr <col:5, col:29> 'void (*)()' <FunctionToPointerDecay>
| `-MemberExpr <col:5, col:29> 'void ()' lvalue .operator() 0xf744560
| `-CallExpr <col:5, col:18> 'Wrapper':'Wrapper' lvalue
| `-ImplicitCastExpr <col:5, col:14> 'Wrapper &(*)()' <FunctionToPointerDecay>
| `-DeclRefExpr <col:5, col:14> 'Wrapper &()' lvalue CXXMethod 0xf746478 'Get' 'Wrapper &()'
| `-NestedNameSpecifier TypeSpec 'Manager' The outer call expression should be a |
Compare the AST of (static) member/operator calls (https://godbolt.org/z/rn3s49GEz): struct Foo {
void f() {}
void operator()() {}
};
struct Bar {
static void f() {}
static void operator()() {}
};
template <typename T>
T& g() {
static T t;
return t;
}
g<Foo>().f(); // Non-static member call
/*
CXXMemberCallExpr <line:17:5, col:16> 'void'
`-MemberExpr <col:5, col:14> '<bound member function type>' .f 0xbae3a90
`-CallExpr <col:5, col:12> 'Foo':'Foo' lvalue
`-ImplicitCastExpr <col:5, col:10> 'Foo &(*)()' <FunctionToPointerDecay>
`-DeclRefExpr <col:5, col:10> 'Foo &()' lvalue Function 0xbb03710 'g' 'Foo &()' (FunctionTemplate 0xbae42f0 'g')
*/
g<Foo>()(); // Non-static operator call
/*
CXXOperatorCallExpr <line:18:5, col:14> 'void' '()'
|-ImplicitCastExpr <col:13, col:14> 'void (*)()' <FunctionToPointerDecay>
| `-DeclRefExpr <col:13, col:14> 'void ()' lvalue CXXMethod 0xbae3b78 'operator()' 'void ()'
`-CallExpr <col:5, col:12> 'Foo':'Foo' lvalue
`-ImplicitCastExpr <col:5, col:10> 'Foo &(*)()' <FunctionToPointerDecay>
`-DeclRefExpr <col:5, col:10> 'Foo &()' lvalue Function 0xbb03710 'g' 'Foo &()' (FunctionTemplate 0xbae42f0 'g')
*/
g<Bar>().f(); // Static member call (with object)
/*
CallExpr <line:19:5, col:16> 'void'
`-ImplicitCastExpr <col:5, col:14> 'void (*)()' <FunctionToPointerDecay>
`-MemberExpr <col:5, col:14> 'void ()' lvalue .f 0xbae3eb0
`-CallExpr <col:5, col:12> 'Bar':'Bar' lvalue
`-ImplicitCastExpr <col:5, col:10> 'Bar &(*)()' <FunctionToPointerDecay>
`-DeclRefExpr <col:5, col:10> 'Bar &()' lvalue Function 0xbb03f80 'g' 'Bar &()' (FunctionTemplate 0xbae42f0 'g')
*/
g<Bar>()(); // Static operator call
/*
CallExpr <line:20:13, col:14> 'void'
`-ImplicitCastExpr <col:13, col:14> 'void (*)()' <FunctionToPointerDecay>
`-DeclRefExpr <col:13, col:14> 'void ()' lvalue CXXMethod 0xbae3f98 'operator()' 'void ()'
*/ If we use |
Hm, good question, but I don't think so. The only real purpose of
In contrast, we use |
@dtcxzyw asked me if I'd like to work on this issue, so I'm trying to write a patch for it now. Will create a PR in a few days. |
PR made: #68485 |
### Description clang don't evaluate the object argument of `static operator()` and `static operator[]` currently, for example: ```cpp #include <iostream> struct Foo { static int operator()(int x, int y) { std::cout << "Foo::operator()" << std::endl; return x + y; } static int operator[](int x, int y) { std::cout << "Foo::operator[]" << std::endl; return x + y; } }; Foo getFoo() { std::cout << "getFoo()" << std::endl; return {}; } int main() { std::cout << getFoo()(1, 2) << std::endl; std::cout << getFoo()[1, 2] << std::endl; } ``` `getFoo()` is expected to be called, but clang don't call it currently (17.0.2). This PR fixes this issue. Fixes #67976. ### Walkthrough - **clang/lib/Sema/SemaOverload.cpp** - **`Sema::CreateOverloadedArraySubscriptExpr` & `Sema::BuildCallToObjectOfClassType`** Previously clang generate `CallExpr` for static operators, ignoring the object argument. In this PR `CXXOperatorCallExpr` is generated for static operators instead, with the object argument as the first argument. - **`TryObjectArgumentInitialization`** `const` / `volatile` objects are allowed for static methods, so that we can call static operators on them. - **clang/lib/CodeGen/CGExpr.cpp** - **`CodeGenFunction::EmitCall`** CodeGen changes for `CXXOperatorCallExpr` with static operators: emit and ignore the object argument first, then emit the operator call. - **clang/lib/AST/ExprConstant.cpp** - **`ExprEvaluatorBase::handleCallExpr`** Evaluation of static operators in constexpr also need some small changes to work, so that the arguments won't be out of position. - **clang/lib/Sema/SemaChecking.cpp** - **`Sema::CheckFunctionCall`** Code for argument checking also need to be modify, or it will fail the test `clang/test/SemaCXX/overloaded-operator-decl.cpp`. ### Tests - **Added:** - **clang/test/AST/ast-dump-static-operators.cpp** Verify the AST generated for static operators. - **clang/test/SemaCXX/cxx2b-static-operator.cpp** Static operators should be able to be called on const / volatile objects. - **Modified:** - **clang/test/CodeGenCXX/cxx2b-static-call-operator.cpp** - **clang/test/CodeGenCXX/cxx2b-static-subscript-operator.cpp** Matching the new CodeGen. ### Documentation - **clang/docs/ReleaseNotes.rst** Update release notes. --------- Co-authored-by: Shafik Yaghmour <[email protected]> Co-authored-by: cor3ntin <[email protected]> Co-authored-by: Aaron Ballman <[email protected]>
…0108) This re-applies 30155fc with a fix for clangd. ### Description clang don't evaluate the object argument of `static operator()` and `static operator[]` currently, for example: ```cpp #include <iostream> struct Foo { static int operator()(int x, int y) { std::cout << "Foo::operator()" << std::endl; return x + y; } static int operator[](int x, int y) { std::cout << "Foo::operator[]" << std::endl; return x + y; } }; Foo getFoo() { std::cout << "getFoo()" << std::endl; return {}; } int main() { std::cout << getFoo()(1, 2) << std::endl; std::cout << getFoo()[1, 2] << std::endl; } ``` `getFoo()` is expected to be called, but clang don't call it currently (17.0.6). This PR fixes this issue. Fixes #67976, reland #68485. ### Walkthrough - **clang/lib/Sema/SemaOverload.cpp** - **`Sema::CreateOverloadedArraySubscriptExpr` & `Sema::BuildCallToObjectOfClassType`** Previously clang generate `CallExpr` for static operators, ignoring the object argument. In this PR `CXXOperatorCallExpr` is generated for static operators instead, with the object argument as the first argument. - **`TryObjectArgumentInitialization`** `const` / `volatile` objects are allowed for static methods, so that we can call static operators on them. - **clang/lib/CodeGen/CGExpr.cpp** - **`CodeGenFunction::EmitCall`** CodeGen changes for `CXXOperatorCallExpr` with static operators: emit and ignore the object argument first, then emit the operator call. - **clang/lib/AST/ExprConstant.cpp** - **`ExprEvaluatorBase::handleCallExpr`** Evaluation of static operators in constexpr also need some small changes to work, so that the arguments won't be out of position. - **clang/lib/Sema/SemaChecking.cpp** - **`Sema::CheckFunctionCall`** Code for argument checking also need to be modify, or it will fail the test `clang/test/SemaCXX/overloaded-operator-decl.cpp`. - **clang-tools-extra/clangd/InlayHints.cpp** - **`InlayHintVisitor::VisitCallExpr`** Now that the `CXXOperatorCallExpr` for static operators also have object argument, we should also take care of this situation in clangd. ### Tests - **Added:** - **clang/test/AST/ast-dump-static-operators.cpp** Verify the AST generated for static operators. - **clang/test/SemaCXX/cxx2b-static-operator.cpp** Static operators should be able to be called on const / volatile objects. - **Modified:** - **clang/test/CodeGenCXX/cxx2b-static-call-operator.cpp** - **clang/test/CodeGenCXX/cxx2b-static-subscript-operator.cpp** Matching the new CodeGen. ### Documentation - **clang/docs/ReleaseNotes.rst** Update release notes. --------- Co-authored-by: Shafik Yaghmour <[email protected]> Co-authored-by: cor3ntin <[email protected]> Co-authored-by: Aaron Ballman <[email protected]>
…eland)' to release/18.x (cherry picked from commit ee01a2c) This re-applies 30155fc with a fix for clangd. clang don't evaluate the object argument of `static operator()` and `static operator[]` currently, for example: ```cpp struct Foo { static int operator()(int x, int y) { std::cout << "Foo::operator()" << std::endl; return x + y; } static int operator[](int x, int y) { std::cout << "Foo::operator[]" << std::endl; return x + y; } }; Foo getFoo() { std::cout << "getFoo()" << std::endl; return {}; } int main() { std::cout << getFoo()(1, 2) << std::endl; std::cout << getFoo()[1, 2] << std::endl; } ``` `getFoo()` is expected to be called, but clang don't call it currently (17.0.6). This PR fixes this issue. Fixes llvm#67976, reland llvm#68485. - **clang/lib/Sema/SemaOverload.cpp** - **`Sema::CreateOverloadedArraySubscriptExpr` & `Sema::BuildCallToObjectOfClassType`** Previously clang generate `CallExpr` for static operators, ignoring the object argument. In this PR `CXXOperatorCallExpr` is generated for static operators instead, with the object argument as the first argument. - **`TryObjectArgumentInitialization`** `const` / `volatile` objects are allowed for static methods, so that we can call static operators on them. - **clang/lib/CodeGen/CGExpr.cpp** - **`CodeGenFunction::EmitCall`** CodeGen changes for `CXXOperatorCallExpr` with static operators: emit and ignore the object argument first, then emit the operator call. - **clang/lib/AST/ExprConstant.cpp** - **`ExprEvaluatorBase::handleCallExpr`** Evaluation of static operators in constexpr also need some small changes to work, so that the arguments won't be out of position. - **clang/lib/Sema/SemaChecking.cpp** - **`Sema::CheckFunctionCall`** Code for argument checking also need to be modify, or it will fail the test `clang/test/SemaCXX/overloaded-operator-decl.cpp`. - **clang-tools-extra/clangd/InlayHints.cpp** - **`InlayHintVisitor::VisitCallExpr`** Now that the `CXXOperatorCallExpr` for static operators also have object argument, we should also take care of this situation in clangd. - **Added:** - **clang/test/AST/ast-dump-static-operators.cpp** Verify the AST generated for static operators. - **clang/test/SemaCXX/cxx2b-static-operator.cpp** Static operators should be able to be called on const / volatile objects. - **Modified:** - **clang/test/CodeGenCXX/cxx2b-static-call-operator.cpp** - **clang/test/CodeGenCXX/cxx2b-static-subscript-operator.cpp** Matching the new CodeGen. - **clang/docs/ReleaseNotes.rst** Update release notes. --------- Co-authored-by: Shafik Yaghmour <[email protected]> Co-authored-by: cor3ntin <[email protected]> Co-authored-by: Aaron Ballman <[email protected]>
https://godbolt.org/z/qeoEeaWxh Trying to catch up with the 18.x release now -> #80109 |
Hello,
It looks like clang is too aggressive for C++23 static call optimization. Here is the simplified example of real code:
We assume that Manager::Get() will be called and
get call
will be printed, but it doesn't happen. If change call in main toManager::Get<TaggedWrapper>().Invoke([] { std::cout << "Function\n"; });
it works as expected.get cal
also will be printed if removestatic
fromoperator()
. It reproducible even with-O0
. clang-17.0.1The text was updated successfully, but these errors were encountered: