diff --git a/test/api/test_saturating_add.cpp b/test/api/test_saturating_add.cpp index 54006a338564c4d3a6d441f123502d62c599457c..fdf9dfa5a09998d972a8d85e92f54d59113c276d 100644 --- a/test/api/test_saturating_add.cpp +++ b/test/api/test_saturating_add.cpp @@ -5,54 +5,93 @@ #include #include -#include "framework/array.h" -#include "framework/utils.h" - -static void test_intrinsiccv_saturating_add_u8(size_t src_a_padding, - size_t src_b_padding, - size_t dst_padding) { - // '3': The operation is unrolled twice. We need a full vector path and some - // scalar processing. - const size_t width = 3 * test::Options::vector_length() - 1; - const size_t height = 1; - - test::Array2D source_a{width, height, src_a_padding}; - ASSERT_TRUE(source_a.valid()); - test::Array2D source_b{width, height, src_b_padding}; - ASSERT_TRUE(source_b.valid()); - test::Array2D actual{width, height, dst_padding}; - ASSERT_TRUE(actual.valid()); - test::Array2D expected{width, height}; - ASSERT_TRUE(expected.valid()); - - // This will test the vector path. - // clang-format off - source_a.set(0, 0, {0, 1, 127, 127, 254, 254, 255}); - source_b.set(0, 0, {0, 1, 127, 128, 1, 2, 255}); - expected.set(0, 0, {0, 2, 254, 255, 255, 255, 255}); - // clang-format on - - // This will test the scalar path, if present. - size_t col = 2 * test::Options::vector_length(); - // clang-format off - source_a.set(0, col, {0, 1, 127, 127, 254, 254, 255}); - source_b.set(0, col, {0, 1, 127, 128, 1, 2, 255}); - expected.set(0, col, {0, 2, 254, 255, 255, 255, 255}); - // clang-format on - - intrinsiccv_saturating_add_u8(source_a.data(), source_a.stride(), - source_b.data(), source_b.stride(), - actual.data(), actual.stride(), width, height); - EXPECT_EQ_ARRAY2D(expected, actual); -} +#include + +#include "framework/operation.h" + +#define INTINSICCV_SATURATING_ADD(type, suffix) \ + INTINSICCV_API(saturating_add, intrinsiccv_saturating_add_##suffix, type) + +INTINSICCV_SATURATING_ADD(int8_t, s8); +INTINSICCV_SATURATING_ADD(uint8_t, u8); +INTINSICCV_SATURATING_ADD(int16_t, s16); +INTINSICCV_SATURATING_ADD(uint16_t, u16); +INTINSICCV_SATURATING_ADD(int32_t, s32); +INTINSICCV_SATURATING_ADD(uint32_t, u32); +INTINSICCV_SATURATING_ADD(int64_t, s64); +INTINSICCV_SATURATING_ADD(uint64_t, u64); + +template +class SaturatingAddTest final : public BinaryOperationTest { + /// Expose constructor of base class. + using BinaryOperationTest::BinaryOperationTest; + + protected: + using Elements = typename BinaryOperationTest::Elements; + using BinaryOperationTest::min; + using BinaryOperationTest::max; + + /// Calls the API-under-test in the appropriate way. + void call_api() override { + saturating_add()( + this->inputs_[0].data(), this->inputs_[0].stride(), + this->inputs_[1].data(), this->inputs_[1].stride(), + this->actual_[0].data(), this->actual_[0].stride(), this->width(), + this->height()); + } + + /// Returns different test data for signed and unsigned element types. + const std::vector& test_elements() override { + if constexpr (std::is_unsigned_v) { + static const std::vector kTestElements = { + // clang-format off + { 0, 0, 0}, + { 1, 1, 2}, + {max() - 1, 1, max()}, + {max() - 1, 2, max()}, + { max(), max(), max()}, + // clang-format on + }; + + return kTestElements; + } else { + static const std::vector kTestElements = { + // clang-format off + { min(), min(), min()}, + {min() + 1, -2, min()}, + {min() + 1, -1, min()}, + { -1, -1, -2}, + { 0, 0, 0}, + { 1, 1, 2}, + {max() - 1, 1, max()}, + {max() - 1, 2, max()}, + { max(), max(), max()}, + // clang-format on + }; + + return kTestElements; + } + } +}; // end of class SaturatingAddTest + +template +class SaturatingAdd : public testing::Test { + public: + /// Dummy value, only used to get the type. + ElementType value_; +}; // end of class SaturatingAdd -/// Tests that \ref intrinsiccv_saturating_add_u8 works when there are no -/// padding bytes at the end of rows. -TEST(Add, U8_NoPadding) { test_intrinsiccv_saturating_add_u8(0, 0, 0); } +using ElementTypes = ::testing::Types; +TYPED_TEST_SUITE(SaturatingAdd, ElementTypes); -/// Tests that \ref intrinsiccv_saturating_add_u8 works when there are padding -/// bytes at the end of rows. -TEST(Add, U8_WithPadding) { - size_t padding = test::Options::vector_length(); - test_intrinsiccv_saturating_add_u8(padding, 0, 0); +/// Tests \ref intrinsiccv_saturating_add_ API. +TYPED_TEST(SaturatingAdd, API) { + using ElementType = decltype(this->value_); + // Test without padding. + SaturatingAddTest{}.test(); + // Test with padding. + SaturatingAddTest{} + .with_padding(test::Options::vector_length()) + .test(); } diff --git a/test/framework/array.h b/test/framework/array.h index 07a4d34e337060f8bf85d9040330ff09615fa09f..ff5ce77d0d8a250d79bd9f778f54633717f9ce37 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -22,6 +22,8 @@ namespace test { template class Array2D final { public: + Array2D() = default; + explicit Array2D(size_t width, size_t height) : Array2D(width, height, 0) {} explicit Array2D(size_t width, size_t height, size_t padding_bytes) @@ -51,7 +53,14 @@ class Array2D final { Array2D &operator=(const Array2D &other) = delete; /// Move assignment operator. - Array2D &operator=(Array2D &&other) = delete; + Array2D &operator=(Array2D &&other) { + data_ = std::move(other.data_); + width_ = other.width_; + height_ = other.height_; + stride_ = other.stride_; + other.width_ = other.height_ = other.stride_ = 0; + return *this; + } /// Fills the underlying memory range with a given value skipping padding /// bytes. @@ -70,7 +79,7 @@ class Array2D final { /// false if there is not enough space in the row. void set(size_t row, size_t column, std::initializer_list values) { - ElementType *ptr = at(row, +column); + ElementType *ptr = at(row, column); if (!ptr) { return; } @@ -121,12 +130,8 @@ class Array2D final { /// Returns a pointer to a data element at a given row and column position, or /// nullptr if the requested position is invalid. ElementType *at(size_t row, size_t column) { - if (!check_access(row, column)) { - return nullptr; - } - - ElementType *ptr = add_stride(data(), row); - return &ptr[column]; + return const_cast( + const_cast *>(this)->at(row, column)); } /// Returns a constant pointer to a data element at a given row and column @@ -208,11 +213,11 @@ class Array2D final { /// Smart pointer to the managed memory. std::unique_ptr data_; /// Width a row in the array. - size_t width_; + size_t width_{0}; /// Number of rows in the array. - size_t height_; + size_t height_{0}; /// Stride in bytes between the first elements of two consecutive rows. - size_t stride_; + size_t stride_{0}; }; // end of class Array2D /// Compares two \ref Array2D objects for equality. diff --git a/test/framework/operation.h b/test/framework/operation.h new file mode 100644 index 0000000000000000000000000000000000000000..a69b47aa2116a3852efa13c607dd7cbd63007842 --- /dev/null +++ b/test/framework/operation.h @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_TEST_FRAMEWORK_OPERATION_H_ +#define INTRINSICCV_TEST_FRAMEWORK_OPERATION_H_ + +#include +#include +#include + +#include "framework/array.h" +#include "framework/utils.h" + +/// Abstract base class for operations with InputsSize number of inputs and +/// OutputsSize number of outputs. +template +class OperationTest { + public: + /// Shorthand for internal data layout representation. + using ArrayType = test::Array2D; + /// Shorthand for elements. + struct Elements { + ElementType values[InputsSize + OutputsSize]; + }; // end of struct Elements + + virtual ~OperationTest() {} + + /// Sets the number of padding bytes at the end of rows. + OperationTest& with_padding( + size_t padding) { + padding_ = padding; + return *this; + } + + void test() { + for (auto& input : inputs_) { + input = ArrayType{width(), height(), padding()}; + ASSERT_TRUE(input.valid()); + } + + for (auto& expected : expected_) { + expected = ArrayType{width(), height()}; + ASSERT_TRUE(expected.valid()); + } + + for (auto& actual : actual_) { + actual = ArrayType{width(), height(), padding()}; + ASSERT_TRUE(actual.valid()); + } + + setup(); + call_api(); + check(); + } + + protected: + /// Returns test data. + virtual const std::vector& test_elements() = 0; + + /// Prepares inputs and expected outputs for the operation. + void setup() { + auto elements_list = test_elements(); + // Check that the number of elements fit into the buffers. + ASSERT_LE(elements_list.size(), height()); + + size_t row_index = 0; + for (auto elements : elements_list) { + // Fill elements one vector length apart. + for (size_t column_index = 0; column_index < width(); + column_index += test::Options::vector_lanes()) { + for (size_t index = 0; index < inputs_.size(); ++index) { + inputs_[index].set(row_index, column_index, {elements.values[index]}); + } + + for (size_t index = 0; index < expected_.size(); ++index) { + expected_[index].set(row_index, column_index, + {elements.values[inputs_.size() + index]}); + } + } + + // Increment loop counter. + ++row_index; + } + } + + /// Calls the API-under-test in the appropriate way. + virtual void call_api() = 0; + + /// Checks that the result meets the expectations. + virtual void check() { + for (size_t index = 0; index < expected_.size(); ++index) { + EXPECT_EQ_ARRAY2D(expected_[index], actual_[index]); + } + } + + /// Tested number of rows. + virtual size_t height() { return test_elements().size(); } + + /// Tested number of elements in a row. + size_t width() const { + // Sufficient number of elements to exercise both vector and scalar paths. + return 3 * test::Options::vector_lanes() - 1; + } + + /// Returns the number of padding bytes at the end of rows. + size_t padding() const { return padding_; } + + /// Returns the minimum value for ElementType. + static constexpr ElementType min() { + return std::numeric_limits::min(); + } + + /// Returns the maximum value for ElementType. + static constexpr ElementType max() { + return std::numeric_limits::max(); + } + + /// Input operand(s) for the operation. + std::array inputs_; + /// Expected result of the operation. + std::array expected_; + /// Actual result of the operation. + std::array actual_; + /// Number of padding bytes at the end of rows. + size_t padding_{0}; +}; // end of class OperationTest + +template +class BinaryOperationTest : public OperationTest { +}; // end of class BinaryOperationTest + +#endif // INTRINSICCV_TEST_FRAMEWORK_OPERATION_H_ diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index e65eb033b0a98e0577cbbd4bdab773301ae2a204..cb606319feaffac08fb4f9bb8603dcb2c3875b60 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -5,20 +5,54 @@ #include #include +#include + #include "framework/array.h" #include "framework/utils.h" +/// Tests that the default constructor of test::Array2D default constructor +/// always creates an empty object. +TEST(Array2D, DefaultConstructor) { + test::Array2D array; + EXPECT_EQ(array.width(), 0); + EXPECT_EQ(array.height(), 0); + EXPECT_EQ(array.stride(), 0); + EXPECT_FALSE(array.valid()); +} + /// Tests that test::Array2D constructor always creates an object with the /// same contents. -TEST(Array2D, constructor) { +TEST(Array2D, Constructor) { size_t width = 5, height = 5; test::Array2D array_1{width, height}; + EXPECT_EQ(array_1.width(), width); + EXPECT_EQ(array_1.height(), height); + EXPECT_EQ(array_1.stride(), width * sizeof(uint32_t)); + EXPECT_TRUE(array_1.valid()); + test::Array2D array_2{width, height}; EXPECT_EQ_ARRAY2D(array_1, array_2); } -/// Tests that test::Array2D.get() works for set/get. -TEST(Array2D, get) { +/// Tests that the move assignment operator of test::Array2D works. +TEST(Array2D, MoveAssignment) { + size_t width = 5, height = 5; + test::Array2D array_1{width, height}; + test::Array2D array_2; + + array_2 = std::move(array_1); + EXPECT_EQ(array_2.width(), width); + EXPECT_EQ(array_2.height(), height); + EXPECT_EQ(array_2.stride(), width * sizeof(uint32_t)); + EXPECT_TRUE(array_2.valid()); + EXPECT_EQ(array_1.width(), 0); + EXPECT_EQ(array_1.height(), 0); + EXPECT_EQ(array_1.stride(), 0); + EXPECT_FALSE(array_1.valid()); +} + +/// Tests that test::Array2D.at() works for set/get. +TEST(Array2D, At) { size_t width = 1, height = 1; test::Array2D array_1{width, height}; test::Array2D array_2{width, height}; @@ -28,10 +62,21 @@ TEST(Array2D, get) { array_1.at(0, 0)[0] = 2; EXPECT_EQ(array_1.at(0, 0)[0], 2); + + EXPECT_EQ(array_1.at(0, 1), nullptr); + EXPECT_EQ(array_1.at(1, 0), nullptr); + EXPECT_EQ(array_1.at(1, 1), nullptr); + + // Constant array + const test::Array2D const_array{width, height}; + + EXPECT_EQ(const_array.at(0, 1), nullptr); + EXPECT_EQ(const_array.at(0, 1), nullptr); + EXPECT_EQ(const_array.at(0, 1), nullptr); } /// Tests that test::Array2D.set() works. -TEST(Array2D, set) { +TEST(Array2D, Set) { size_t width = 5, height = 2; test::Array2D array_1{width, height}; @@ -50,7 +95,7 @@ TEST(Array2D, set) { } /// Tests that test::Array2D.fill() works. -TEST(Array2D, fill) { +TEST(Array2D, Fill) { size_t width = 5, height = 2; test::Array2D array_1{width, height}; test::Array2D array_2{width, height}; @@ -195,7 +240,7 @@ static void PaddingClobbered(size_t row, size_t offset) { size_t stride = width * sizeof(ElementType) + padding; test::Array2D array(width, height, padding); - uint8_t *ptr = reinterpret_cast(array.data()); + uint8_t* ptr = reinterpret_cast(array.data()); ptr[row * stride + width * sizeof(ElementType) + offset] = 1; } @@ -230,6 +275,6 @@ TEST(Array2D, Coverage) { array.set(0, 0, {}); - uint64_t *ptr_1 = array.at(0, 0); + uint64_t* ptr_1 = array.at(0, 0); EXPECT_EQ(ptr_1, nullptr); } diff --git a/test/framework/utils.h b/test/framework/utils.h index 23640833705c4adaba1bda6d2159cd5070e37a4e..eb9ec7e1161664e87841e36ca66d27f52cf6f231 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -6,16 +6,42 @@ #define INTRINSICCV_TEST_FRAMEWORK_UTILS_H_ #include +#include +#include +#include + +#define INTINSICCV_API(name, impl, type) \ + template , bool> = true> \ + static decltype(auto) name() { \ + return &impl; \ + } namespace test { class Options { public: - /// Returns the vector length being tested. + /// Returns the vector length being tested. This is in bytes. static size_t vector_length() { return vector_length_; } - /// Sets the vector length. - static void set_vector_length(size_t value) { vector_length_ = value; } + /// Returns the number of lanes in a vector for a given integral type. + template , bool> = true> + static size_t vector_lanes() { + return vector_length_ / sizeof(ElementType); + } + + /// Sets the vector length in bytes. + static void set_vector_length(size_t value) { + // Check for power of two. + if ((value == 0) || ((value & (value - 1)) != 0)) { + std::cerr << "Vector length must be a power of two: " << value + << std::endl; + std::exit(1); + } + + vector_length_ = value; + } private: /// Vector length being tested.