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

Move self_logand to the logical_and callable #1959

Open
wants to merge 40 commits into
base: main
Choose a base branch
from

Conversation

SadiinsoSnowfall
Copy link
Collaborator

No description provided.

@SadiinsoSnowfall SadiinsoSnowfall marked this pull request as draft September 11, 2024 17:27
@SadiinsoSnowfall SadiinsoSnowfall force-pushed the callable-logand branch 4 times, most recently from af4d016 to b0012c1 Compare September 19, 2024 08:28
@SadiinsoSnowfall SadiinsoSnowfall marked this pull request as ready for review September 26, 2024 14:56
@SadiinsoSnowfall
Copy link
Collaborator Author

While attempting to fix this callable for the "no simd" configurations, I encountered the following problem:
Say we want to compile the following function:

auto test(logical<short> a, logical<wide<int>> b) {
    return logical_and(a, b);
}

Because of the rules defined by common_logical_t, this should return a logical<wide<int>>, as logical<wide> always takes priority. However, in "no simd" mode, this isn't the case and the logical_and callable attempts to return a logical<wide<short>>, failing to compile. This is because in this mode, the logical_and function is mapped over each element of the b wide value. In doing so, it is passed two logical_scalar_values of type logical<short> and logical<int> at every step. The problem being that in this case, the logical<short> type takes precedence.

The current fix involves using a convert operation before returning the result from the callable. This is a no-op in "simd" mode as the computed type and the returned type do match. A better fix to this problem would be to directly pass the computed type, in this case common_logical_t<T, U> to the map function called in the callable dispatch implementations.

@jfalcou
Copy link
Owner

jfalcou commented Sep 30, 2024

A better fix to this problem would be to directly pass the computed type, in this case common_logical_t<T, U> to the map function called in the callable dispatch implementations.

This would also improve compile time by not having a convoluted map result computation.
@DenisYaroshevskiy maybe I should fix map outside of this PR then we can update this one and so we don't have the convert laying around.

@SadiinsoSnowfall SadiinsoSnowfall marked this pull request as draft October 16, 2024 10:01
@SadiinsoSnowfall
Copy link
Collaborator Author

waiting on the map2 branch

@SadiinsoSnowfall
Copy link
Collaborator Author

After some talking we decided to change the behaviour of common_logical such that the returned type is always a logical of the type of the first of its parameter. This should be mirroring what is done for bitwise callables and fix the problem we were encountering with the callables using common_logical.

This is a breaking change.

@jfalcou
Copy link
Owner

jfalcou commented Dec 5, 2024

@DenisYaroshevskiy looks like it works. Your opinion is welcome so we can move forward.

Copy link
Collaborator

@DenisYaroshevskiy DenisYaroshevskiy left a comment

Choose a reason for hiding this comment

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

I don't like that there is a mess with conversions.
We do two or more conversions per operation.

I would like the logical callable to handle that.

I am ok if that happens with the "smaller logical result type"

include/eve/arch/cpu/logical_wide.hpp Outdated Show resolved Hide resolved
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Usually - this type of code would be in regular file (since you have the platform implementations.

Also - seems like conversions should be in behaviour, not here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Unaddressed.

Copy link
Owner

Choose a reason for hiding this comment

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

We do that a lot for small impl in a lot of places. Save on including a file for 5 lines.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We only do that if we don't have the special file includes. Either one file or everyone gets a file.

I mean - that's how it is now. Let's not make this harder.

Copy link
Owner

Choose a reason for hiding this comment

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

OK this is true.
@SadiinsoSnowfall put it back in a common/ file

@SadiinsoSnowfall
Copy link
Collaborator Author

Note: this PR now also changes the following callables: logical_notand, logical_andnot, logical_or, logical_notor, logical_ornot, logical_xor as they have a dependency on the return type of logical_and and had hardcoded return type in their generic impl.

Also:

  • changed test.hpp to add cartesian and cartesian_square, previously used in both logical_and and common_value unit tests.
  • hotfixed operator< in kumi, this fix from Joel will get into kumi mainline.
  • fixed friends functions to work with the new common_logical behaviour (they will be cleaned up in futures PRs).
  • changed the logical_xxx unit tests to perform some more cross-type tests and ensure that the new common_logical behaviour is used.
  • fixed hz_device to make it work with the new common_logical behaviour.

The new behaviour is the following: given two types A and B, both either bool or logical_value, the type returned from common_logical will be:

  • bool if both A and B are bools
  • A if B is a bool (or vice versa)
  • A if it is a simd_value and B isn't.
  • B if it is a simd_value and A isn't.
  • If both A and B are simd_values or scalar_values, the return type will be:
    • The smallest of the two
    • The "most unsigned" one
    • The "most integral" one
    • The first one (in the case both have the same size, signedness and integral-ness)

Copy link
Collaborator

@DenisYaroshevskiy DenisYaroshevskiy left a comment

Choose a reason for hiding this comment

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

quick review

include/eve/arch/cpu/logical.hpp Show resolved Hide resolved
include/eve/arch/cpu/logical_wide.hpp Outdated Show resolved Hide resolved
include/eve/concept/value.hpp Outdated Show resolved Hide resolved
include/eve/deps/kumi/tuple.hpp Outdated Show resolved Hide resolved
test/test.hpp Show resolved Hide resolved
}

template<typename T, typename U>
using priority_t = eve::as_logical_t<decltype(find_priority_type<T, U>())>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm iffy about this:

It seems like you rewrote a chunk of common_logical a second time and compare them.

Is this a fare description?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought that comparing common_logical_t to itself would not be that useful. But without knowing exactly what types we are currently processing, it is difficult to know what should be the output of common_logical_t. If you want I can try to discriminate between floats, signed integrals and unsigned integrals where applicable to make the desired output more clear.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems like there is only two usages of priority_t.

Just be explicit about the things you test, don't try to write a function.

Writing a function to validate input is a good idea only if you can somehow write that function in a much easier way than the original.

Copy link
Owner

Choose a reason for hiding this comment

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

those two usage are actually used in the test doign a cartesian product of tests over all combo of scalar.
This is not something we can verify easily without missing stuff.

test/unit/module/core/logical_ops.hpp Outdated Show resolved Hide resolved
{
constexpr bool supports_map_no_conversion = requires{ map(this->derived(), x, xs...); };

if constexpr (supports_map_no_conversion) return map(this->derived(), x, xs...);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we have a use case for this but - why "has_implementation" check is after supports_map_no_conversion?

Probably a question for @jfalcou, you don't need to fix this now.

Copy link
Owner

Choose a reason for hiding this comment

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

this is a remnant from before. This is not requried anymore, @SadiinsoSnowfall reverted it.

if constexpr (std::same_as<T, c_t>) return e;
else if constexpr (std::same_as<T, bool> || scalar_value<T>) return c_t{e};
else return detail::call_convert(e, as_element<c_t>{});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do you need this? I'd expect call_convert to handle them. The bool? Adding a bool in convert seems like the right fix here then.

Copy link
Owner

Choose a reason for hiding this comment

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

Adding bool to convert and required tests is a bit big for this. Let's say this stay and later we PR convert for bool.

@@ -106,15 +106,15 @@ namespace eve
namespace detail
{
template<value T, value U, callable_options O>
EVE_FORCEINLINE constexpr common_logical_t<T,U>
EVE_FORCEINLINE constexpr auto
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is the return type changing here? (not that I care, unless it changed).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was a remnant from resolving the tuple problem. This should be fixed now.

{ return EVE_DISPATCH_CALL(a, b); }
template<typename T, typename U>
constexpr EVE_FORCEINLINE common_logical_t<T, U> operator()(T a, U b) const noexcept
requires (same_lanes_or_scalar<T, U> && !arithmetic_simd_value<T> && !arithmetic_simd_value<U>)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a very weird requirement. Can't you just require relaxed_logical_value on T and U?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See the comment in the file.

Copy link
Collaborator

Choose a reason for hiding this comment

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

what comment.
Like - ok - this is an unacceptable requirement for an interface. We need something reasonable.

if constexpr( scalar_value<T> && scalar_value<U> ) return r_t(a && b);
else return a && b;
if constexpr (relaxed_logical_scalar_value<T>) return a && b;
else return bit_cast(a.bits() & b.bits(), as<T>{});
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand this else. You are in cpu right?

Is this like what - logical_value && is_wide_logical? Can you be more explicit about that, this is very confusing.

At least a comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a comment to clarify.

{ return EVE_DISPATCH_CALL(a, b); }
template<typename T, typename U>
constexpr EVE_FORCEINLINE common_logical_t<T, U> operator()(T a, U b) const noexcept
requires (same_lanes_or_scalar<T, U> && !arithmetic_simd_value<T> && !arithmetic_simd_value<U>)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment here about the requirements.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated the answer to the review.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is still not ok for an interface.

}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Unaddressed.

//! @{
//! @concept relaxed_logical_scalar_value
//! @brief The concept `relaxed_logical_scalar_value<T>` is satisfied if and only if T is a
//! boolean value or satisfies the `eve::scalar_value` concept.
Copy link
Collaborator

Choose a reason for hiding this comment

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

the scalar value here is ofc weird but w/e, I don't want to change it now.

}
(std::make_index_sequence<sizeof...(Ts)-1>());
return res;
(std::make_index_sequence<sizeof...(Us)-1>());
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's with this kumi change? This should probably go in upstream first, no? Or do we need to upgrade in lockstep?

Copy link
Owner

Choose a reason for hiding this comment

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

upstreal is fixed already as externals CI pass.
This is probably some noise and should be turned back to Ts.

include/eve/detail/hz_device.hpp Show resolved Hide resolved
{
template<value T, value U>
template<typename T, typename U>
requires(eve::same_lanes_or_scalar<T, U>)
constexpr EVE_FORCEINLINE common_logical_t<T,U> operator()(T a, U b) const
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

the simd tolerance looks very weird, whatever that is.

{
template<value T, value U>
template<typename T, typename U>
Copy link
Collaborator

Choose a reason for hiding this comment

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

is_not_equal is not a logical element wise callable.
It's just not.
We need to figure out what's up.

auto tmp = is_not_equal(a, b);
if constexpr( floating_value<r_t> ) return tmp && (is_not_nan(a) || is_not_nan(b));
else return tmp;
if constexpr( floating_value<T> ) return tmp && (is_not_nan(a) || is_not_nan(b));
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is weird but it's not on you, i'll just ignore it.

{ return EVE_DISPATCH_CALL(a, b); }
template<typename T, typename U>
constexpr EVE_FORCEINLINE common_logical_t<T, U> operator()(T a, U b) const noexcept
requires (same_lanes_or_scalar<T, U> && !arithmetic_simd_value<T> && !arithmetic_simd_value<U>)
Copy link
Collaborator

Choose a reason for hiding this comment

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

what comment.
Like - ok - this is an unacceptable requirement for an interface. We need something reasonable.

}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We only do that if we don't have the special file includes. Either one file or everyone gets a file.

I mean - that's how it is now. Let's not make this harder.

{ return EVE_DISPATCH_CALL(a, b); }
template<typename T, typename U>
constexpr EVE_FORCEINLINE common_logical_t<T, U> operator()(T a, U b) const noexcept
requires (same_lanes_or_scalar<T, U> && !arithmetic_simd_value<T> && !arithmetic_simd_value<U>)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is still not ok for an interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants