Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
test/.vscode
build
.cache
.idea
**cmake-build**
Comment on lines +6 to +7
Copy link
Owner

Choose a reason for hiding this comment

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

Does your IDE really require building inline with the sources, or within the source tree?
I am loathe to pollute the repository with support for the anti-pattern of building within the sources; however, Visual Studio Code forced me to yield. VSCode is a poor tool that glitches in very frustrating ways when asked to build out of the source tree, that's the only reason for the test/.vscode, build and .cache.
So, unless your tool glitches, adopt the practice of not building in-tree and remove this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why is this such a bad thing? I think most tools / LSPs expect to build in the source tree? I think CLion does this by default and I think clangd by default looks for ./build in the root of the project?

Copy link
Owner

Choose a reason for hiding this comment

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

Just two things I dislike:
Most tools like the command line grep, find, etc. are extremely hard to configure for understanding .gitignore, so, you just slow down your work flow by having to treat the non-sources directories as explicit special cases with every non-git tool you use.

Let's say you want to build with GCC and also with Clang.
Who gets to build in-tree? both?
So, for every flavor of build you may want you need to provide an entry point within the tree, and add it to the .gitignore.
How's that helpful instead of segregating builds from sources?

These are two instances of the more general problem of disorganization. A freshly cloned repository ought to contain only files generated by people or by tools that are so sophisticated they may as well. Further processing of "sources" ought not to dump the results into a place where it is hard to tell them apart from files made by people.

It baffles me that colleagues expect that whatever configuration is good for holding source files is going to be just as good for built files. For example, what if I have a configuration of very small blocks at the file system level expecting that source code is among the very small files in the distribution of file size, but keep the build filesystem with large blocks, because of the same reason on the opposite?

See? there are multiplicity of day to day advantages of keeping things organized. Very fundamental stuff for Software Engineers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry took a while to get back to this.

I understand both of these points, I think the overriding point from me regardless of my opinion is that this is how it is going to be used by people, so we might as well make it convenient for people to contribute. Many IDEs and tools like the build folder to be in the source tree. I'm using the clangd LSP which is hooking into nvim which I believe requires the compile_commands.json to be in ./build. JetBrains also will build in the source tree by default into cmake-build-[BUILD_TYPE].

Secondly, you can use tools like ripgrep in place of grep which are .gitignore sensitive, which is awesome.

Though I hear your points well and agree, this addition doesn't prevent more optimal workflows and enables anyone to contribute more easily.

Copy link
Owner

Choose a reason for hiding this comment

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

Let's continue this conversation online, at this point, it is an item that should not go in this PR, but in another PR if we decide to support building in-tree.

Let's have PRs with a "single responsibility".

Empty file added CMakeLists.txt
Empty file.
1 change: 1 addition & 0 deletions inc/zoo/meta/BitmaskMaker.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct BitmaskMaker {

static_assert(0xF0F0 == BitmaskMaker<uint16_t, 0xF0, 8>::value);
static_assert(0xEDFEDFED == BitmaskMaker<uint32_t, 0xFED, 12>::value);
static_assert(0b0001'0001 == BitmaskMaker<unsigned char, 1, 4>::value);

}} // zoo::meta

Expand Down
14 changes: 14 additions & 0 deletions inc/zoo/swar/SWAR.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "zoo/meta/log.h"

#include <type_traits>
#include <initializer_list>

#ifdef _MSC_VER
#include <iso646.h>
Expand Down Expand Up @@ -70,6 +71,19 @@ struct SWAR {

constexpr T value() const noexcept { return m_v; }

constexpr static T baseFromLaneLiterals(std::initializer_list<T> args) noexcept {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@thecppzoo i wonder what you think about this utility?

Copy link
Owner

Choose a reason for hiding this comment

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

Ah, we could do real literals for very popular cases such as

s4_64, s8_64, s16_64, s32_64;
s4_32, s8_32 , s16_32;
s4_16, s8_16;
s4_8;

A constructor from initializer list would be ambiguous since you could have an initializer list of only one element and then it would not be possible to distinguish between a literal with one element or a whole-swar initialization with a base type...

I am not able to decide this minute, but I thank you for posing the question.

I would like to improve the ergonomics, do you have ideas further down the road in this direction or is this just as far as your vision goes at the moment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah i was trying a few variants with variadic arguments but that was a little tricky when trying to iterate over them, i feel like there is yet a better solution.

this is one i was inspired for right now, but i think i will defo have more ideas with respect to ergonomics!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@thecppzoo how does this update look? makes it unambiguous that the number of lanes to use.
how would you go about using the different types for the arguments here dynamically?

T result = 0;
for (auto arg: args) {
result = (result << NBits) | arg;
}
return result;
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 big endian, have you thought about which one ought to be the "canonical" endianness for literals?

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 think it should read most significant on the left, least significant on the right, like it is now.

}

constexpr static SWAR fromLaneLiterals(std::initializer_list<T> args) noexcept {
return SWAR(baseFromLaneLiterals(args));
}


#define SWAR_UNARY_OPERATORS_X_LIST \
X(SWAR, ~)
//constexpr SWAR operator~() const noexcept { return SWAR{~m_v}; }
Expand Down
99 changes: 91 additions & 8 deletions inc/zoo/swar/associative_iteration.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ template<int NB, typename B>
constexpr auto makeLaneMaskFromMSB(SWAR<NB, B> input) {
using S = SWAR<NB, B>;
auto msb = input & S{S::MostSignificantBit};
auto msbCopiedToLSB = S{msb.value() >> (NB - 1)};
auto msbCopiedToLSB = S{static_cast<B>(msb.value() >> (NB - 1))};
return impl::makeLaneMaskFromMSB_and_LSB(msb, msbCopiedToLSB);
}

Expand Down Expand Up @@ -191,8 +191,13 @@ template<
typename CountHalver
>
constexpr auto associativeOperatorIterated_regressive(
Base base, Base neutral, IterationCount count, IterationCount forSquaring,
Operator op, unsigned log2Count, CountHalver ch
const Base base,
const Base neutral,
IterationCount count,
const IterationCount forSquaring,
const Operator op,
unsigned log2Count,
const CountHalver ch
) {
auto result = neutral;
if(!log2Count) { return result; }
Expand All @@ -205,6 +210,36 @@ constexpr auto associativeOperatorIterated_regressive(
return result;
}


// What I don't understand is why this doesn't work?
template <typename T>
constexpr auto multiply(T a , T b) {
auto operation = [](auto left, auto right, auto count) {
if (count) {
return left + right;
} else {
return left;
}
};

auto updateCount = [](auto count) {
return count << 1;
};

constexpr auto numBits = sizeof(T) * 8;
return associativeOperatorIterated_regressive(
a, // base
0, // neutral
b, // count
1, // forSquaring, pretty sure this is where i am not understanding
operation, // operation
numBits, // log2Count
updateCount // halver
);
}

// static_assert(multiply(3, 4) == 12, "multiply failed");

template<int ActualBits, int NB, typename T>
constexpr auto multiplication_OverflowUnsafe_SpecificBitCount(
SWAR<NB, T> multiplicand, SWAR<NB, T> multiplier
Expand All @@ -218,17 +253,54 @@ constexpr auto multiplication_OverflowUnsafe_SpecificBitCount(

auto halver = [](auto counts) {
auto msbCleared = counts & ~S{S::MostSignificantBit};
return S{msbCleared.value() << 1};
return S{static_cast<T>(msbCleared.value() << 1)};
};

multiplier = S{multiplier.value() << (NB - ActualBits)};
multiplier = S{static_cast<T>(multiplier.value() << (NB - ActualBits))};
return associativeOperatorIterated_regressive(
multiplicand, S{0}, multiplier, S{S::MostSignificantBit}, operation,
ActualBits, halver
multiplicand,
S{0},
multiplier,
S{S::MostSignificantBit},
operation,
ActualBits,
halver
);
}

/// \note Not removed yet because it is an example of "progressive" associative exponentiation
template<int ActualBits, int NB, typename T>
constexpr auto exponentiation_OverflowUnsafe_SpecificBitCount(
SWAR<NB, T> x,
SWAR<NB, T> exponent
) {
using S = SWAR<NB, T>;

auto operation = [](auto left, auto right, auto counts) {
const auto mask = makeLaneMaskFromMSB(counts);
const auto product =
multiplication_OverflowUnsafe_SpecificBitCount<ActualBits>(left, right);
return (product & mask) | (left & ~mask);
};

// halver should work same as multiplication... i think...
auto halver = [](auto counts) {
auto msbCleared = counts & ~S{S::MostSignificantBit};
return S{static_cast<T>(msbCleared.value() << 1)};
};

exponent = S{static_cast<T>(exponent.value() << (NB - ActualBits))};
return associativeOperatorIterated_regressive(
x,
S{meta::BitmaskMaker<T, 1, NB>().value}, // neutral is lane wise..
exponent,
S{S::MostSignificantBit},
operation,
ActualBits,
halver
);
}

// \note Not removed yet because it is an example of "progressive" associative exponentiation
template<int ActualBits, int NB, typename T>
constexpr auto multiplication_OverflowUnsafe_SpecificBitCount_deprecated(
SWAR<NB, T> multiplicand,
Expand Down Expand Up @@ -261,6 +333,17 @@ constexpr auto multiplication_OverflowUnsafe(
);
}

template<int NB, typename T>
constexpr auto exponentiation_OverflowUnsafe(
SWAR<NB, T> base,
SWAR<NB, T> exponent
) {
return
exponentiation_OverflowUnsafe_SpecificBitCount<NB>(
base, exponent
);
}

template<int NB, typename T>
struct SWAR_Pair{
SWAR<NB, T> even, odd;
Expand Down
11 changes: 11 additions & 0 deletions test/swar/BasicOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ static_assert(
multiplication_OverflowUnsafe_SpecificBitCount<3>(Micand, Mplier).value()
);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

GOOD NEWS. Looks like my initial, first-pass implementation was correct after all!
Seems like part of the issue was how I'm trying to compare the binary literals.

All value I tried when evaluating using the hex idiom you guys have already got provides consistently correct answers.

static_assert(0b00000010000000110000010100000110 == 0x02'03'05'06);

TEST_CASE("Jamie's totally working exponentiation :D") {
constexpr auto base = SWAR<8, u32>::fromLaneLiterals({2, 3, 5, 6}); // {(2 << 24) + (3 << 16) + (5 << 8) + (6)};
constexpr auto exponent = SWAR<8, u32>::fromLaneLiterals({7, 0, 2, 3}); // 7 | 0 | 2 | 3
constexpr auto expected = SWAR<8, u32>::fromLaneLiterals({128, 1, 25, 216}); // 128 | 1 | 25 | 216
constexpr auto actual = exponentiation_OverflowUnsafe(base, exponent);
static_assert(expected.value() == actual.value());
CHECK(expected.value() == actual.value());
}

}

#define HE(nbits, t, v0, v1) \
Expand Down