Skip to content
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

[libc++] ordering comparisons should be marked __attribute__((pure)) #62022

Open
danakj opened this issue Apr 8, 2023 · 9 comments
Open

[libc++] ordering comparisons should be marked __attribute__((pure)) #62022

danakj opened this issue Apr 8, 2023 · 9 comments
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema"

Comments

@danakj
Copy link
Contributor

danakj commented Apr 8, 2023

https://godbolt.org/z/e4rTvMeoW

The following code generates -Wassume warnings suggesting there are side effects when converting from a strong_ordering to a bool. The same is true of partial_ordering and weak_ordering.

#include <compare>

struct S {
    __attribute__((pure)) bool g() { return true; }

    __attribute__((pure)) auto operator<=>(const S& rhs) const {
        return std::strong_ordering::equal;
    }
};


constexpr __attribute__((pure)) __attribute__((noinline)) auto f(
    const S&, const S&) noexcept {
    return std::strong_ordering::equal;
}

int main() {
    S s1, s2;
    __builtin_assume(f(s1, s2) == 0);
    __builtin_assume(f(s1, s2) == std::strong_ordering::equal);
    __builtin_assume(s1 <= s2);  // What I want to do.
    int a, b;
    __builtin_assume(a <= b);  // Only this one is accepted without warning.

    __builtin_assume(std::strong_ordering::equivalent == 0);
    __builtin_assume(std::weak_ordering::equivalent == 0);
    __builtin_assume(std::partial_ordering::equivalent == 0);
}

I tried to find the right place to insert __attribute__((pure)). I think all of these:

  • _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator==(weak_ordering, weak_ordering) noexcept = default;
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator==(weak_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ == 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator< (weak_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ < 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator<=(weak_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ <= 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator> (weak_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ > 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator>=(weak_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ >= 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator< (_CmpUnspecifiedParam, weak_ordering __v) noexcept {
    return 0 < __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator<=(_CmpUnspecifiedParam, weak_ordering __v) noexcept {
    return 0 <= __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator> (_CmpUnspecifiedParam, weak_ordering __v) noexcept {
    return 0 > __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator>=(_CmpUnspecifiedParam, weak_ordering __v) noexcept {
    return 0 >= __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr weak_ordering operator<=>(weak_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr weak_ordering operator<=>(_CmpUnspecifiedParam, weak_ordering __v) noexcept {
    return __v < 0 ? weak_ordering::greater : (__v > 0 ? weak_ordering::less : __v);
    }
  • // comparisons
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator==(partial_ordering, partial_ordering) noexcept = default;
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator==(partial_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__is_ordered() && __v.__value_ == 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator< (partial_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__is_ordered() && __v.__value_ < 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator<=(partial_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__is_ordered() && __v.__value_ <= 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator> (partial_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__is_ordered() && __v.__value_ > 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator>=(partial_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__is_ordered() && __v.__value_ >= 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator< (_CmpUnspecifiedParam, partial_ordering __v) noexcept {
    return __v.__is_ordered() && 0 < __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator<=(_CmpUnspecifiedParam, partial_ordering __v) noexcept {
    return __v.__is_ordered() && 0 <= __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator> (_CmpUnspecifiedParam, partial_ordering __v) noexcept {
    return __v.__is_ordered() && 0 > __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator>=(_CmpUnspecifiedParam, partial_ordering __v) noexcept {
    return __v.__is_ordered() && 0 >= __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr partial_ordering operator<=>(partial_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr partial_ordering operator<=>(_CmpUnspecifiedParam, partial_ordering __v) noexcept {
    return __v < 0 ? partial_ordering::greater : (__v > 0 ? partial_ordering::less : __v);
    }
  • _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator==(strong_ordering, strong_ordering) noexcept = default;
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator==(strong_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ == 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator< (strong_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ < 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator<=(strong_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ <= 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator> (strong_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ > 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator>=(strong_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v.__value_ >= 0;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator< (_CmpUnspecifiedParam, strong_ordering __v) noexcept {
    return 0 < __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator<=(_CmpUnspecifiedParam, strong_ordering __v) noexcept {
    return 0 <= __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator> (_CmpUnspecifiedParam, strong_ordering __v) noexcept {
    return 0 > __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr bool operator>=(_CmpUnspecifiedParam, strong_ordering __v) noexcept {
    return 0 >= __v.__value_;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr strong_ordering operator<=>(strong_ordering __v, _CmpUnspecifiedParam) noexcept {
    return __v;
    }
    _LIBCPP_HIDE_FROM_ABI
    friend constexpr strong_ordering operator<=>(_CmpUnspecifiedParam, strong_ordering __v) noexcept {
    return __v < 0 ? strong_ordering::greater : (__v > 0 ? strong_ordering::less : __v);
    }
@danakj danakj changed the title [libc++] strong_ordering comparisons should be marked __attribute__((pure)) [libc++] ordering comparisons should be marked __attribute__((pure)) Apr 8, 2023
danakj added a commit to danakj/subspace that referenced this issue Apr 8, 2023
Clang generates warnings even if those comparisons are
pure: llvm/llvm-project#62022
@EugeneZelenko EugeneZelenko added libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. and removed new issue labels Apr 8, 2023
@philnik777
Copy link
Contributor

I don't think we want to do this. This isn't an ABI boundary, so we shouldn't have to annotate it. This would also set a precedent of annotating inline functions with [[gnu::pure]]. The next person would ask "Why isn't vector::size() annotated? I want to do __builtin_assume(vec.size() % 4 == 0)". Clang should instead either invest some more effort into checking the called functions, or move the warning to the backend, which can actually detect whether a given function has side-effects.

@danakj
Copy link
Contributor Author

danakj commented Apr 26, 2023

I agree clang should do this. Right now it's not possible to compare non-primitive types in __builtin_assume(). Is there some pragmatic middle ground that could allow comparisons in __builtin_assume() until Clang improves things? Is there some line we could draw between "all pure functions" and basic operations like comparison?

danakj added a commit to danakj/subspace that referenced this issue Apr 26, 2023
Clang generates warnings even if those comparisons are
pure: llvm/llvm-project#62022
@philnik777
Copy link
Contributor

philnik777 commented Apr 26, 2023

I agree clang should do this. Right now it's not possible to compare non-primitive types in __builtin_assume(). Is there some pragmatic middle ground that could allow comparisons in __builtin_assume() until Clang improves things? Is there some line we could draw between "all pure functions" and basic operations like comparison?

I don't think there is a clear defensible line anywhere other than "we don't do it anywhere" and "we mark all the pure functions as [[gnu::pure]]". e.g. arguably std::popcount() is just as basic, and one could also see it be used in a __builtin_assume(). If you have any clear line, I'm all ears, but I don't see it.

@philnik777 philnik777 added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Apr 26, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Apr 26, 2023

@llvm/issue-subscribers-clang-frontend

danakj added a commit to danakj/subspace that referenced this issue Apr 27, 2023
Clang generates warnings even if those comparisons are
pure: llvm/llvm-project#62022
danakj added a commit to chromium/subspace that referenced this issue Apr 30, 2023
Clang generates warnings even if those comparisons are
pure: llvm/llvm-project#62022
@philnik777 philnik777 removed the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label May 6, 2023
@pkasting
Copy link
Member

Besides "massive effort", what would be the arguments against "[[gnu::pure/const]] everything applicable"? Unnecessary ABI break? Risk of subtle bugs in the future if one becomes non-pure/const but attribute isn't removed?

Is it possible to guesstimate the potential benefit of such a change from the optimization side? Or would fewer false-positive warnings be the primary benefit?

@pkasting
Copy link
Member

Possible answers to my own question:

@philnik777
Copy link
Contributor

Besides "massive effort", what would be the arguments against "[[gnu::pure/const]] everything applicable"? Unnecessary ABI break? Risk of subtle bugs in the future if one becomes non-pure/const but attribute isn't removed?

Is it possible to guesstimate the potential benefit of such a change from the optimization side? Or would fewer false-positive warnings be the primary benefit?

This isn't an ABI break. My main concerns are that it will introduce very hard to find bugs without much of a benefit, as well as not having a clear line where to put these attributes. There doesn't seem to be any benefit to optimizations, since the compiler always has visibility into the implementation of the functions (we already use these attributes for functions that are in the dylib). AFAICT the only benefit to add these attributes is to avoid warnings with __builtin_assume, which makes this a very high risk undertaking with quite low rewards.

@pkasting
Copy link
Member

pkasting commented Jan 29, 2024 via email

@philnik777
Copy link
Contributor

philnik777 commented Jan 29, 2024

In theory, the compiler having visibility into the implementation seems like it should make these provide no optimization benefit. Is that known to be true in practice? Or is it plausible these could provide meaningful hints to the compiler in any cases?

It's quite hard to prove a negative, but there is the function-attrs pass, which infers these attributes. The only scenario I could think of is that we are writing something, but it's OK to be discarded. I'm not aware of any function like this in libc++, so that point seems moot.

Context for all these questions: was discussing trying to find code in chromium which has side effects inside the invocation of a particular macro, and someone noted that the lack of these situations on the library APIs might make it difficult to avoid false positives.

You can easily do that by implementing your check as a pass and run the pass mentioned above before. It's not likely super pretty, but it should get the job done.

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"
Projects
None yet
Development

No branches or pull requests

5 participants