-
Notifications
You must be signed in to change notification settings - Fork 11
Implement lane-wise modulo. #86
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
base: master
Are you sure you want to change the base?
Conversation
add to_array oops make 23 for now not sure we want to commit to this? remove named function
Reverting mistake, this is not a `struct` definition but a variable!
#include <cstddef> | ||
#include <cstdint> | ||
|
||
namespace zoo::math { |
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.
You know, should you have used maths
, I would have let it slide. But since you didn't, if you try to use the British maths
I will reject the renaming on the basis that there is no need to make a change like that
;-)
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.
I wouldn't have let 'maths' slide.
inc/zoo/swar/math.h
Outdated
template <typename IntegerType = size_t> | ||
constexpr static | ||
std::enable_if_t<std::is_integral_v<IntegerType>, bool> | ||
is_power_of_two(IntegerType x) noexcept { |
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.
Why snake_case
instead of the normal camelCase
and DromedaryCamelCase
customary in the rest of zoo
for these things?
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.
habit, let's move to camelCase to be consistent. will start email thread about case to ask if you have strong preference to avoid polluting PR with unrelated discussion.
inc/zoo/swar/math.h
Outdated
#pragma once | ||
#include "SWAR.h" | ||
#include <cstddef> | ||
#include <cstdint> |
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.
SWAR.h
includes this, or its equivalent, stdint.h
, I see no point in things like std::uint64_t
instead of just uint64_t
inc/zoo/swar/math.h
Outdated
constexpr static | ||
std::enable_if_t<std::is_integral_v<IntegerType>, bool> | ||
is_power_of_two(IntegerType x) noexcept { | ||
return x > 0 && (x & (x - 1)) == 0; |
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.
Indentation.
Why the X > 0
instead of the customary X
or bool(X)
?
test/swar/BasicOperations.cpp
Outdated
static_assert(equals(S{S::Literal, {1, 2, 3, 4}}, | ||
S{S::Literal, {1, 2, 3, 4}}).value() | ||
== BS{BS::Literal, {T, T, T, T}}.value()); | ||
|
||
static_assert(equals(S{S::Literal, {1, 2, 3, 4}}, | ||
S{S::Literal, {5, 6, 7, 8}}).value() | ||
== BS{BS::Literal, {F, F, F, F}}.value()); | ||
|
||
static_assert(equals(S{S::Literal, {1, 2, 3, 4}}, | ||
S{S::Literal, {5, 2, 7, 4}}).value() | ||
== BS{BS::Literal, {F, T, F, T}}.value()); |
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.
NO ACCIDENTAL INDENTATION LEVELS
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.
this is not accidental, but just not intentionally formatted yet. didn't feel super obvious to me what this should look like.
I have repeatedly expressed that accidental indentation levels are contrary to the principles of how the sources ought to express more detail. |
inc/zoo/swar/SWAR.h
Outdated
@@ -63,6 +66,7 @@ constexpr std::make_unsigned_t<T> lsbIndex(T v) noexcept { | |||
template<int NBits_, typename T = uint64_t> | |||
struct SWAR { | |||
using type = std::make_unsigned_t<T>; | |||
using Boolean = BooleanSWAR<NBits_, T>; |
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.
I don't like this forward reference.
It is not that intuition tells me that MSVC won't like this and give us subtle compilation problems because of MSVCs bugs, but something deeper. I don't think that the abstraction of BooleanSWAR
is in any way something that SWAR
ought to advertise. There's something fishy here that I can not express well.
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.
Yeah I wasn't sure about this either... wanted to see what you thought about this.
inc/zoo/swar/math.h
Outdated
static_assert(moduloPowerOfTwo<4>(0) == 0); | ||
static_assert(moduloPowerOfTwo<8>(9) == 1); | ||
static_assert(moduloPowerOfTwo<4096>(4097) == 1); |
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.
I know we have some pollution like this in our headers. It is pollution we haven't bothered to clean. Please move these legitimate compile time tests to their "translation unit" out of the header.
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.
Good callout, thanks.
inc/zoo/swar/math.h
Outdated
constexpr static auto isPowerOfTwo(S x) noexcept { | ||
constexpr auto NBits = S::NBits; | ||
using T = typename S::type; | ||
auto greater_than_0 = greaterEqual_MSB_off(x, S{0}); |
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.
Does this work if the MSB is not off?
I think that, for example, a SWAR of 4 bits per lane with one lane containing the MSB, like for example 8, will fail.
I'm sorry that the _MSB_off
is confusing
std::enable_if_t<zoo::math::isPowerOfTwo<size_t, N>(), S> | ||
moduloPowerOfTwo(const S x) noexcept { | ||
constexpr auto N_minus_1 = N - 1; | ||
constexpr auto N_in_lanes = zoo::meta::BitmaskMaker<typename S::type, N_minus_1, S::NBits>::value; | ||
auto y = x.value() & N_in_lanes; | ||
return S{y}; | ||
} | ||
|
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.
The way this is formulated is perhaps not useful:
For integral types, when the compiler can prove the modulus is a power of two, the compiler will, for sure, use bitwise conjunction rather than the assembler for the modulo instruction.
There remains the question of when the compiler doesn't know whether the modulus is a power of two, this would be only at runtime, and then, programmers have explicitly accounted for this improvement since ages, I don't think this way of going about it will give them an incentive to use this primitive.
This is useful for "weird" types that represent integers, like multi-precision integers, I don't know, that do not satisfy the "Integral" property required by isPowerOfTwo
.
There is one aspect that I really like aobut this code: the structural guarantee that you won't pass a non-power-of-two as template argument, I see myself making some code with lane sizes not powers of two, and in some remote part of the code there is an assumption broken of the lane size being a powr of two, invoking bitwise conjuction, for "hilarity".
test/swar/BasicOperations.cpp
Outdated
@@ -1,11 +1,14 @@ | |||
#include "zoo/swar/SWAR.h" |
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.
Why?
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.
probably an auto-import
const bool (&expected)[S::Lanes]) { | ||
return equals(S{S::Literal, left}, S{S::Literal, right}).value() |
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.
Accidental levels of indentation, scopes are not closed at the indentation level.
Do you think moving the end of the argument list to the right helps the code in any way?
Why is the declaration of the three arguments more nested than the implementation?
S{S::Literal, {5, 2, 7, 4}}).value() | ||
== BS{BS::Literal, {F, T, F, T}}.value()); | ||
template <typename S = S, typename BS = BS> | ||
constexpr auto laneWiseEqualsTest( |
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.
This is not right, if you want "all lanes equal" you use the underlying value comparison as in all other tests.
Unit tests ought to demonstrate how to use a codebase. Using per-lane comparisons for a whole-base-type comparison is inadequate.
There is a probem with equals
which is that is terseness invites incorrect use.
Even if you use equals
, the comparison against a BooleanSWAR
of all true
is inelegant:
not(boolean(~equals(
, since we have the conversion to boolean
} | ||
static_assert(laneWiseEqualsTest({1, 2, 3, 4}, {1, 2, 3, 4}, {1, 1, 1, 1})); | ||
static_assert(laneWiseEqualsTest({1, 2, 3, 4}, {5, 6, 7, 8}, {0, 0, 0, 0})); | ||
static_assert(laneWiseEqualsTest({1, 2, 3, 4}, {5, 2, 7, 4}, {0, 1, 0, 1})); |
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.
This is legitimate as written
static_assert(laneWiseEqualsTest({1, 2, 3, 4}, {1, 2, 3, 4}, {1, 1, 1, 1})); | ||
static_assert(laneWiseEqualsTest({1, 2, 3, 4}, {5, 6, 7, 8}, {0, 0, 0, 0})); |
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.
These are whole-BooleanSWAR
comparisons that can be achieved via the boolean conversion
S{S::Literal, {5, 2, 7, 4}}).value() | ||
== BS{BS::Literal, {F, T, F, T}}.value()); | ||
template <typename S = S, typename BS = BS> | ||
constexpr auto laneWiseEqualsTest( |
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.
laneWiseEquals
is problematic:
What does it mean? there are results that are expected to be equal or different according to a collection of expected boolean values, that is not even suggested by the name.
Then, laneWiseEquals
suggests LANE WISE EQUALS. I guess that you call your "expected" comparison "WISE EQUAL".
Notwithstanding other problems, perhaps lanewiseEqualityComparisonExpected
?
} | ||
static_assert(powerOfTwoTest<S, BS>({1, 2, 3, 4}, {1, 1, 0, 1})); | ||
static_assert(powerOfTwoTest<S, BS>({2, 3, 64, 77}, {1, 0, 1, 0})); | ||
static_assert(powerOfTwoTest<S, BS>({3, 65, 128, 0}, {0, 0, 1, 1})); |
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.
This much copy&pasting is not valuable.
Did you consider packing all of the redundancy into an X
macro?
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.
We need to document much better what are the primitives available and especially common idioms around them.
In this PR there is clear misuse of existing operations and the introduction of idioms that are not well justified, because the common idioms are not well known.
I've not worked in cleaning up the SWAR tests because I believe that they ought to be generated by a code generator where you express the intent and the test cases are generated and their source code.
We just have to stop making this more confusing by accidental misuse.
The implementation look good. |
No safety about the subtract one, but shouldn't be an issue if your values are in range.
Depends on the "ergonomics" PR, which should merge first, and then the "base" of this PR should be moved to
master
.