From 8301573225c34e2c6d265f2575160eb25c99edb8 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Fri, 11 Apr 2025 15:51:41 +0100 Subject: [PATCH 01/26] Smarter memory protection with Buffer class Signed-off-by: Jakub Sujak --- CMakeLists.txt | 2 + test/common/buffer.cpp | 91 ++++++++++++++++++++++++++++++++++++++ test/common/buffer.hpp | 80 +++++++++++++++++++++++++++++++++ test/tests/buffer_test.cpp | 79 +++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 test/common/buffer.cpp create mode 100644 test/common/buffer.hpp create mode 100644 test/tests/buffer_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d2f4b5fe..ea7b9926 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -330,6 +330,7 @@ if(KLEIDIAI_BUILD_TESTS) add_library(kleidiai_test_framework test/common/bfloat16.cpp + test/common/buffer.cpp test/common/compare.cpp test/common/cpu_info.cpp test/common/data_format.cpp @@ -388,6 +389,7 @@ if(KLEIDIAI_BUILD_TESTS) else() add_executable(kleidiai_test test/tests/bfloat16_test.cpp + test/tests/buffer_test.cpp test/tests/float16_test.cpp test/tests/imatmul_test.cpp test/tests/matmul_clamp_f16_bf16p_bf16p_test.cpp diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp new file mode 100644 index 00000000..0e9a4726 --- /dev/null +++ b/test/common/buffer.cpp @@ -0,0 +1,91 @@ +// +// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "buffer.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "kai/kai_common.h" + +namespace kai::test { + +Buffer::Buffer(const size_t size, const BufferProtectionPolicy protection_policy) : + m_user_buffer_size(size), m_protection_policy(protection_policy) { + KAI_ASSUME_MSG(size > 0, "Buffers must be of non-zero size"); + + switch (protection_policy) { + case BufferProtectionPolicy::ProtectUnderflow: + case BufferProtectionPolicy::ProtectOverflow: + allocate_with_guard_pages(); + break; + default: + allocate(); + } +} + +void* Buffer::data() const { + return static_cast(m_buffer.get()) + m_user_buffer_offset; +} + +size_t Buffer::size() const { + return m_user_buffer_size; +} + +void Buffer::allocate() { + m_buffer = handle(std::malloc(m_user_buffer_size), &std::free); + KAI_ASSUME_MSG(m_buffer.get() != nullptr, "Failure allocating memory"); +} + +void Buffer::allocate_with_guard_pages() { +#if defined(__linux__) || defined(__APPLE__) + const size_t page_size = sysconf(_SC_PAGESIZE); + + // Offset the user buffer by the size of the first guard page + m_user_buffer_offset = page_size; + + // The user buffer is rounded to the size of the nearest whole page. + // This forms the valid region between the two guard pages + const size_t valid_region_size = kai_roundup(m_user_buffer_size, page_size); + const size_t protected_region_size = 2 * page_size; + const size_t total_memory_size = valid_region_size + protected_region_size; + + if (m_protection_policy == BufferProtectionPolicy::ProtectOverflow) { + // To detect overflows we offset the user buffer so that edge of the buffer is aligned to the start of the + // higher guard page thus detecting whenever a buffer overflow occurs. + m_user_buffer_offset += valid_region_size - m_user_buffer_size; + } + + auto mmap_deleter = [total_memory_size](void* ptr) { + if (munmap(ptr, total_memory_size) != 0) { + KAI_ERROR("Failure deleting memory mappings"); + } + }; + + m_buffer = + handle(mmap(nullptr, total_memory_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0), mmap_deleter); + if (m_buffer.get() == MAP_FAILED) { + KAI_ERROR("Failure mapping memory"); + } + + void* head_guard_page = m_buffer.get(); + void* tail_guard_page = static_cast(m_buffer.get()) + (total_memory_size - page_size); + + if (mprotect(head_guard_page, page_size, PROT_NONE) != 0) { + KAI_ERROR("Failure protecting page immediately preceding buffer"); + } + if (mprotect(tail_guard_page, page_size, PROT_NONE) != 0) { + KAI_ERROR("Failure protecting page immediately following buffer"); + } +#endif // defined(__linux__) || defined(__APPLE__) +} + +} // namespace kai::test diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp new file mode 100644 index 00000000..86f69fd8 --- /dev/null +++ b/test/common/buffer.hpp @@ -0,0 +1,80 @@ +// +// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include + +namespace kai::test { + +enum class BufferProtectionPolicy : uint8_t { + None = 0, + ProtectUnderflow = 1, + ProtectOverflow = 2, +}; + +/// Buffer is a high-level abstraction for a block of memory. +/// +/// The class performs dynamic memory allocation and management in an opaque manner. The underlying memory resource can +/// be requested using the familiar @ref Buffer::data() function and interacted with using @ref +/// kai::test::read_array() and @ref kai::test::write_array() utilities. +/// +/// Buffer can be instantiated with one of the following protection policies: +/// - @ref BufferProtectionPolicy::None No protection mechanisms are enabled. +/// - @ref BufferProtectionPolicy::ProtectUnderflow Memory equal to the size of the nearest whole page is allocated, +/// guard pages set up on either side, and the user buffer aligned +/// to the end of the lower guard page thus detecting whenever a +/// buffer underflow occurs. +/// - @ref BufferProtectionPolicy::ProtectOverflow Same as above, but now the edge of the user buffer is aligned to +/// the start of the higher guard page thus detecting whenever a +/// buffer overflow occurs. +/// +class Buffer { + // Handle to the underlying memory resource + using handle = std::unique_ptr>; + +public: + explicit Buffer(size_t size, BufferProtectionPolicy protection_policy = BufferProtectionPolicy::None); + + Buffer(const Buffer& other) = delete; + Buffer(Buffer&& other) noexcept = default; + Buffer& operator=(const Buffer& other) = delete; + Buffer& operator=(Buffer&& other) noexcept = default; + + ~Buffer() = default; + + /// Gets the base memory address to the user buffer. + /// + /// @return Memory address of the user buffer. + [[nodiscard]] void* data() const; + + /// Gets the size of the user buffer. + /// + /// Depending on the @ref BufferProtectionPolicy policy enabled, the actual size of memory allocated may be larger. + /// However, this function guarantees to always provide the size of the user buffer only. + /// + /// @return Size of the user buffer region in bytes. + [[nodiscard]] size_t size() const; + +private: + /// Naively allocate memory. + void allocate(); + + /// Allocate memory with surrounding guard pages. + void allocate_with_guard_pages(); + + handle m_buffer = nullptr; + + size_t m_user_buffer_size; + size_t m_user_buffer_offset = 0; + + BufferProtectionPolicy m_protection_policy; +}; + +} // namespace kai::test diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp new file mode 100644 index 00000000..364ba63b --- /dev/null +++ b/test/tests/buffer_test.cpp @@ -0,0 +1,79 @@ +// +// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kai::test { + +namespace { +constexpr size_t g_num_runs = 100; +} // namespace + +TEST(Buffer, NoPolicy) { + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(0, std::numeric_limits::max()); + + for (size_t i = 0; i < g_num_runs; ++i) { + const size_t buffer_size = dist(rng); + + const auto buffer = Buffer(buffer_size, BufferProtectionPolicy::None); + + const auto* data = static_cast(buffer.data()); + ASSERT_NE(data, nullptr); + } +} + +#if defined(__linux__) || defined(__APPLE__) +TEST(Buffer, DetectUnderflow) { + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(0, std::numeric_limits::max()); + + for (size_t i = 0; i < g_num_runs; ++i) { + const size_t buffer_size = dist(rng); + + const auto buffer = Buffer(buffer_size, BufferProtectionPolicy::ProtectUnderflow); + + const auto* data = static_cast(buffer.data()); + ASSERT_NE(data, nullptr); + ASSERT_NE(data, MAP_FAILED); + + EXPECT_EXIT({ [[maybe_unused]] const volatile auto val = *--data; }, testing::KilledBySignal(SIGBUS), ""); + } +} + +TEST(Buffer, DetectOverflow) { + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(0, std::numeric_limits::max()); + + for (size_t i = 0; i < g_num_runs; ++i) { + const size_t buffer_size = dist(rng); + + const auto buffer = Buffer(buffer_size, BufferProtectionPolicy::ProtectOverflow); + + const auto* data = static_cast(buffer.data()); + ASSERT_NE(data, nullptr); + ASSERT_NE(data, MAP_FAILED); + + EXPECT_EXIT( + { [[maybe_unused]] const volatile auto val = *(data + buffer_size); }, testing::KilledBySignal(SIGBUS), ""); + } +} +#endif // defined(__linux__) || defined(__APPLE__) + +} // namespace kai::test -- GitLab From 696a557e4e07cbc1e7976f835d485c65eac52871 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Fri, 11 Apr 2025 17:04:22 +0100 Subject: [PATCH 02/26] Trap SIGBUS and SIGSEV signals Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index 364ba63b..778e90c0 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -20,12 +20,18 @@ namespace kai::test { namespace { constexpr size_t g_num_runs = 100; + +#if defined(__APPLE__) +constexpr size_t g_signal = SIGBUS; +#else // if defined(__APPLE__) +constexpr size_t g_signal = SIGSEGV; +#endif // #if defined(__APPLE__) #else } // namespace TEST(Buffer, NoPolicy) { std::random_device rd; std::mt19937 rng(rd()); - std::uniform_int_distribution dist(0, std::numeric_limits::max()); + std::uniform_int_distribution dist(1, std::numeric_limits::max()); for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); @@ -41,7 +47,7 @@ TEST(Buffer, NoPolicy) { TEST(Buffer, DetectUnderflow) { std::random_device rd; std::mt19937 rng(rd()); - std::uniform_int_distribution dist(0, std::numeric_limits::max()); + std::uniform_int_distribution dist(1, std::numeric_limits::max()); for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); @@ -52,14 +58,14 @@ TEST(Buffer, DetectUnderflow) { ASSERT_NE(data, nullptr); ASSERT_NE(data, MAP_FAILED); - EXPECT_EXIT({ [[maybe_unused]] const volatile auto val = *--data; }, testing::KilledBySignal(SIGBUS), ""); + EXPECT_EXIT({ [[maybe_unused]] const volatile auto val = *--data; }, testing::KilledBySignal(g_signal), ""); } } TEST(Buffer, DetectOverflow) { std::random_device rd; std::mt19937 rng(rd()); - std::uniform_int_distribution dist(0, std::numeric_limits::max()); + std::uniform_int_distribution dist(1, std::numeric_limits::max()); for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); @@ -71,9 +77,10 @@ TEST(Buffer, DetectOverflow) { ASSERT_NE(data, MAP_FAILED); EXPECT_EXIT( - { [[maybe_unused]] const volatile auto val = *(data + buffer_size); }, testing::KilledBySignal(SIGBUS), ""); + { [[maybe_unused]] const volatile auto val = *(data + buffer_size); }, testing::KilledBySignal(g_signal), + ""); } } -#endif // defined(__linux__) || defined(__APPLE__) +#endif // if defined(__linux__) || defined(__APPLE__) } // namespace kai::test -- GitLab From e3a687da54f0b04b036d21cbdfd88baf879a4439 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 09:41:39 +0100 Subject: [PATCH 03/26] Ignore missing default case in switch statement in test dependency Signed-off-by: Jakub Sujak --- test/common/buffer.hpp | 2 +- test/tests/buffer_test.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp index 86f69fd8..f38e394c 100644 --- a/test/common/buffer.hpp +++ b/test/common/buffer.hpp @@ -36,7 +36,7 @@ enum class BufferProtectionPolicy : uint8_t { /// buffer overflow occurs. /// class Buffer { - // Handle to the underlying memory resource + // Handle to the underlying memory resource and its deleter using handle = std::unique_ptr>; public: diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index 778e90c0..fed85dff 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -58,7 +58,11 @@ TEST(Buffer, DetectUnderflow) { ASSERT_NE(data, nullptr); ASSERT_NE(data, MAP_FAILED); +// Ignore missing default case in switch statement in test dependency +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-default" EXPECT_EXIT({ [[maybe_unused]] const volatile auto val = *--data; }, testing::KilledBySignal(g_signal), ""); +#pragma GCC diagnostic pop } } @@ -76,9 +80,13 @@ TEST(Buffer, DetectOverflow) { ASSERT_NE(data, nullptr); ASSERT_NE(data, MAP_FAILED); +// Ignore missing default case in switch statement in test dependency +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-default" EXPECT_EXIT( { [[maybe_unused]] const volatile auto val = *(data + buffer_size); }, testing::KilledBySignal(g_signal), ""); +#pragma GCC diagnostic pop } } #endif // if defined(__linux__) || defined(__APPLE__) -- GitLab From d8240ee6b174c0e0805d2a2a42c084c7f6fb4828 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 09:49:28 +0100 Subject: [PATCH 04/26] Use double quotes for header include Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index fed85dff..0bc5ab92 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -4,6 +4,8 @@ // SPDX-License-Identifier: Apache-2.0 // +#include "test/common/buffer.hpp" + #include #include #include @@ -14,7 +16,6 @@ #include #include #include -#include namespace kai::test { -- GitLab From 9505d5ceabdcc0b1bf4861534fc7158f8aac56dd Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 14:55:45 +0100 Subject: [PATCH 05/26] Set BufferProtectionPolicy using an environment variable Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 22 ++++++++++++++---- test/common/buffer.hpp | 46 ++++++++++++++++++++----------------- test/tests/buffer_test.cpp | 47 ++++++++++++++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 0e9a4726..c640a300 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -9,20 +9,31 @@ #include #include -#include #include #include #include +#include #include "kai/kai_common.h" namespace kai::test { -Buffer::Buffer(const size_t size, const BufferProtectionPolicy protection_policy) : - m_user_buffer_size(size), m_protection_policy(protection_policy) { +Buffer::Buffer(const size_t size) : m_user_buffer_size(size) { KAI_ASSUME_MSG(size > 0, "Buffers must be of non-zero size"); - switch (protection_policy) { + std::string buffer_policy; + if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { + buffer_policy = std::string(buffer_policy_env); + } + + if (buffer_policy == "PROTECT_UNDERFLOW") { + m_protection_policy = BufferProtectionPolicy::ProtectUnderflow; + } + if (buffer_policy == "PROTECT_OVERFLOW") { + m_protection_policy = BufferProtectionPolicy::ProtectOverflow; + } + + switch (m_protection_policy) { case BufferProtectionPolicy::ProtectUnderflow: case BufferProtectionPolicy::ProtectOverflow: allocate_with_guard_pages(); @@ -43,6 +54,7 @@ size_t Buffer::size() const { void Buffer::allocate() { m_buffer = handle(std::malloc(m_user_buffer_size), &std::free); KAI_ASSUME_MSG(m_buffer.get() != nullptr, "Failure allocating memory"); + KAI_ASSUME_MSG(m_user_buffer_offset == 0, "Buffer offset must be zero for naive allocation"); } void Buffer::allocate_with_guard_pages() { @@ -85,6 +97,8 @@ void Buffer::allocate_with_guard_pages() { if (mprotect(tail_guard_page, page_size, PROT_NONE) != 0) { KAI_ERROR("Failure protecting page immediately following buffer"); } +#else // #if defined(__linux__) || defined(__APPLE__) + KAI_ERROR("Method not implemented for target platform"); #endif // defined(__linux__) || defined(__APPLE__) } diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp index f38e394c..42ffa11e 100644 --- a/test/common/buffer.hpp +++ b/test/common/buffer.hpp @@ -13,34 +13,23 @@ namespace kai::test { -enum class BufferProtectionPolicy : uint8_t { - None = 0, - ProtectUnderflow = 1, - ProtectOverflow = 2, -}; - /// Buffer is a high-level abstraction for a block of memory. /// /// The class performs dynamic memory allocation and management in an opaque manner. The underlying memory resource can /// be requested using the familiar @ref Buffer::data() function and interacted with using @ref /// kai::test::read_array() and @ref kai::test::write_array() utilities. /// -/// Buffer can be instantiated with one of the following protection policies: -/// - @ref BufferProtectionPolicy::None No protection mechanisms are enabled. -/// - @ref BufferProtectionPolicy::ProtectUnderflow Memory equal to the size of the nearest whole page is allocated, -/// guard pages set up on either side, and the user buffer aligned -/// to the end of the lower guard page thus detecting whenever a -/// buffer underflow occurs. -/// - @ref BufferProtectionPolicy::ProtectOverflow Same as above, but now the edge of the user buffer is aligned to -/// the start of the higher guard page thus detecting whenever a -/// buffer overflow occurs. +/// Buffer comes with protection mechanisms defined by @ref BufferProtectionPolicy. These are enabled by setting the +/// KAI_TEST_BUFFER_POLICY environment variable, for example: +/// KAI_TEST_BUFFER_POLICY=PROTECT_UNDERFLOW to enable @ref BufferProtectionPolicy::ProtectUnderflow. +/// KAI_TEST_BUFFER_POLICY=PROTECT_OVERFLOW to enable @ref BufferProtectionPolicy::ProtectOverflow. /// class Buffer { // Handle to the underlying memory resource and its deleter using handle = std::unique_ptr>; public: - explicit Buffer(size_t size, BufferProtectionPolicy protection_policy = BufferProtectionPolicy::None); + explicit Buffer(size_t size); Buffer(const Buffer& other) = delete; Buffer(Buffer&& other) noexcept = default; @@ -49,9 +38,9 @@ public: ~Buffer() = default; - /// Gets the base memory address to the user buffer. + /// Gets the base memory address of the user buffer. /// - /// @return Memory address of the user buffer. + /// @return Base memory address of the user buffer. [[nodiscard]] void* data() const; /// Gets the size of the user buffer. @@ -59,14 +48,29 @@ public: /// Depending on the @ref BufferProtectionPolicy policy enabled, the actual size of memory allocated may be larger. /// However, this function guarantees to always provide the size of the user buffer only. /// - /// @return Size of the user buffer region in bytes. + /// @return Size of the user buffer in bytes. [[nodiscard]] size_t size() const; private: + /// Buffer can be protected with one of the following protection policies: + /// - @ref BufferProtectionPolicy::None No protection mechanisms are enabled. + /// - @ref BufferProtectionPolicy::ProtectUnderflow Memory equal to the size of the nearest whole page is + /// allocated, guard pages set up on either side, and the user + /// buffer aligned to the end of the head guard page thus + /// detecting whenever a buffer underflow occurs. + /// - @ref BufferProtectionPolicy::ProtectOverflow Same as above, but now the edge of the user buffer is aligned + /// to the start of the tail guard page thus detecting whenever a + /// buffer overflow occurs. + enum class BufferProtectionPolicy : uint8_t { + None = 0, + ProtectUnderflow = 1, + ProtectOverflow = 2, + }; + /// Naively allocate memory. void allocate(); - /// Allocate memory with surrounding guard pages. + /// Allocate memory with adjacent guard pages. void allocate_with_guard_pages(); handle m_buffer = nullptr; @@ -74,7 +78,7 @@ private: size_t m_user_buffer_size; size_t m_user_buffer_offset = 0; - BufferProtectionPolicy m_protection_policy; + BufferProtectionPolicy m_protection_policy = BufferProtectionPolicy::None; }; } // namespace kai::test diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index 0bc5ab92..2790eeff 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace kai::test { @@ -34,14 +35,26 @@ TEST(Buffer, NoPolicy) { std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); + // Store the current buffer policy + std::string buffer_policy; + if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { + buffer_policy = std::string(buffer_policy_env); + } + + // Overwrite the buffer policy for purpose of the test + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", "NONE", 1 /* overwrite */), 0); + for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); - const auto buffer = Buffer(buffer_size, BufferProtectionPolicy::None); + const auto buffer = Buffer(buffer_size); const auto* data = static_cast(buffer.data()); ASSERT_NE(data, nullptr); } + + // Restore the buffer policy to its original value + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } #if defined(__linux__) || defined(__APPLE__) @@ -50,21 +63,33 @@ TEST(Buffer, DetectUnderflow) { std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); + // Store the current buffer policy + std::string buffer_policy; + if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { + buffer_policy = std::string(buffer_policy_env); + } + + // Overwrite the buffer policy for purpose of the test + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", "PROTECT_UNDERFLOW", 1 /* overwrite */), 0); + for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); - const auto buffer = Buffer(buffer_size, BufferProtectionPolicy::ProtectUnderflow); + const auto buffer = Buffer(buffer_size); const auto* data = static_cast(buffer.data()); ASSERT_NE(data, nullptr); ASSERT_NE(data, MAP_FAILED); -// Ignore missing default case in switch statement in test dependency +// Ignore missing default case in switch statement in test dependency macro #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" EXPECT_EXIT({ [[maybe_unused]] const volatile auto val = *--data; }, testing::KilledBySignal(g_signal), ""); #pragma GCC diagnostic pop } + + // Restore the buffer policy to its original value + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } TEST(Buffer, DetectOverflow) { @@ -72,16 +97,25 @@ TEST(Buffer, DetectOverflow) { std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); + // Store the current buffer policy + std::string buffer_policy; + if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { + buffer_policy = std::string(buffer_policy_env); + } + + // Overwrite the buffer policy for purpose of the test + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", "PROTECT_OVERFLOW", 1 /* overwrite */), 0); + for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); - const auto buffer = Buffer(buffer_size, BufferProtectionPolicy::ProtectOverflow); + const auto buffer = Buffer(buffer_size); const auto* data = static_cast(buffer.data()); ASSERT_NE(data, nullptr); ASSERT_NE(data, MAP_FAILED); -// Ignore missing default case in switch statement in test dependency +// Ignore missing default case in switch statement in test dependency macro #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" EXPECT_EXIT( @@ -89,6 +123,9 @@ TEST(Buffer, DetectOverflow) { ""); #pragma GCC diagnostic pop } + + // Restore the buffer policy to its original value + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } #endif // if defined(__linux__) || defined(__APPLE__) -- GitLab From 25b29a30f08a0a2da455ba84dfe33dcef0a8fc31 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 16:13:46 +0100 Subject: [PATCH 06/26] Reimplement matmul_clamp_f32_f32_f32p_test using Buffer Signed-off-by: Jakub Sujak --- test/tests/matmul_clamp_f32_f32_f32p_test.cpp | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/test/tests/matmul_clamp_f32_f32_f32p_test.cpp b/test/tests/matmul_clamp_f32_f32_f32p_test.cpp index be124f22..253338bb 100644 --- a/test/tests/matmul_clamp_f32_f32_f32p_test.cpp +++ b/test/tests/matmul_clamp_f32_f32_f32p_test.cpp @@ -10,9 +10,12 @@ #include #include #include -#include +#include #include +#include #include +#include +#include #include #include "kai/kai_common.h" @@ -26,7 +29,6 @@ #include "test/common/memory.hpp" #include "test/common/test_suite.hpp" #include "test/reference/clamp.hpp" -#include "test/reference/fill.hpp" #include "test/reference/matmul.hpp" namespace kai::test { @@ -59,6 +61,41 @@ const std::array, 2> ukern kai_run_matmul_clamp_f32_f32_f32p2vlx1b_1x16vl_sme2_mla}, "matmul_clamp_f32_f32_f32p2vlx1b_1x16vl_sme2_mla", cpu_has_sme2}}}; + +// TODO: Reimplement these helpers in fill.cpp. These methods are currently duplicated here so they can be specialized +// on the Buffer return type. +template +Buffer fill_matrix_raw(size_t height, size_t width, std::function gen) { + const auto size = height * width * size_in_bits / 8; + KAI_ASSUME(width * size_in_bits % 8 == 0); + + Buffer data(size); + auto ptr = static_cast(data.data()); + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + write_array(ptr, y * width + x, gen(y, x)); + } + } + + return data; +} + +template +Buffer fill_matrix_random_raw(size_t height, size_t width, uint32_t seed) { + using TDist = std::conditional_t< + std::is_floating_point_v, std::uniform_real_distribution, std::uniform_int_distribution>; + + std::mt19937 rnd(seed); + TDist dist; + + return fill_matrix_raw(height, width, [&](size_t, size_t) { return dist(rnd); }); +} + +template +Buffer fill_random(size_t length, uint32_t seed) { + return fill_matrix_random_raw(1, length, seed); +} } // namespace class MatMulTest_f32_f32_f32p : public ::testing::TestWithParam {}; @@ -85,9 +122,9 @@ TEST_P(MatMulTest_f32_f32_f32p, EndToEnd) // NOLINT(google-readability-avoid-un const auto sr = ukernel_variant.interface.get_sr(); // Generates input data. - const auto ref_lhs = fill_random(m * k, seed + 0); - const auto ref_rhs = fill_random(n * k, seed + 1); - const auto ref_bias = fill_random(n, seed + 2); + const Buffer ref_lhs(fill_random(m * k, seed + 0)); + const Buffer ref_rhs(fill_random(n * k, seed + 1)); + const Buffer ref_bias(fill_random(n, seed + 2)); // Runs the reference implementation const auto ref_dst_no_clamp = matmul( @@ -103,19 +140,19 @@ TEST_P(MatMulTest_f32_f32_f32p, EndToEnd) // NOLINT(google-readability-avoid-un const auto rhs_stride = n * sizeof(float); size_t imp_packed_rhs_size = 0; - std::unique_ptr> imp_packed_rhs; + std::unique_ptr imp_packed_rhs; switch (variant_idx) { case 0: // matmul_clamp_f32_f32_f32p16vlx1b_1x16vl_sme2_mla imp_packed_rhs_size = kai_get_rhs_packed_size_rhs_pack_kxn_f32p16vlx1b_f32_f32_sme(n, k); - imp_packed_rhs = std::make_unique>(imp_packed_rhs_size); + imp_packed_rhs = std::make_unique(imp_packed_rhs_size); kai_run_rhs_pack_kxn_f32p16vlx1b_f32_f32_sme( 1, n, k, nr, kr, sr, rhs_stride, ref_rhs.data(), ref_bias.data(), nullptr, imp_packed_rhs->data(), 0, nullptr); break; case 1: // matmul_clamp_f32_f32_f32p2vlx1b_1x16vl_sme2_mla imp_packed_rhs_size = kai_get_rhs_packed_size_rhs_pack_kxn_f32p2vlx1biasf32_f32_f32_sme(n, k); - imp_packed_rhs = std::make_unique>(imp_packed_rhs_size); + imp_packed_rhs = std::make_unique(imp_packed_rhs_size); kai_run_rhs_pack_kxn_f32p2vlx1biasf32_f32_f32_sme( 1, n, k, nr, kr, sr, rhs_stride, ref_rhs.data(), ref_bias.data(), nullptr, imp_packed_rhs->data(), 0, nullptr); @@ -128,9 +165,9 @@ TEST_P(MatMulTest_f32_f32_f32p, EndToEnd) // NOLINT(google-readability-avoid-un const auto imp_dst_size = ukernel_variant.interface.get_dst_size(m, n); ASSERT_EQ(imp_dst_size, ref_dst.size()); - std::vector imp_dst(imp_dst_size); + Buffer imp_dst(imp_dst_size); ukernel_variant.interface.run_matmul( - m, n, k, ref_lhs.data(), 1, imp_packed_rhs->data(), reinterpret_cast(imp_dst.data()), 1, 1, clamp_min, + m, n, k, ref_lhs.data(), 1, imp_packed_rhs->data(), static_cast(imp_dst.data()), 1, 1, clamp_min, clamp_max); // Compare the output of the micro-kernels against the output of the reference implementation. -- GitLab From aa8e6e5980809ec2356b7a55d62456cce11d1a0d Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 16:21:22 +0100 Subject: [PATCH 07/26] Organize includes Signed-off-by: Jakub Sujak --- test/tests/matmul_clamp_f32_f32_f32p_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/matmul_clamp_f32_f32_f32p_test.cpp b/test/tests/matmul_clamp_f32_f32_f32p_test.cpp index 253338bb..9842e9fb 100644 --- a/test/tests/matmul_clamp_f32_f32_f32p_test.cpp +++ b/test/tests/matmul_clamp_f32_f32_f32p_test.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -24,6 +23,7 @@ #include "kai/ukernels/matmul/matmul_clamp_f32_f32_f32p/kai_matmul_clamp_f32_f32_f32p_interface.h" #include "kai/ukernels/matmul/pack/kai_rhs_pack_kxn_f32p16vlx1b_f32_f32_sme.h" #include "kai/ukernels/matmul/pack/kai_rhs_pack_kxn_f32p2vlx1biasf32_f32_f32_sme.h" +#include "test/common/buffer.hpp" #include "test/common/cpu_info.hpp" #include "test/common/data_type.hpp" #include "test/common/memory.hpp" -- GitLab From 59bc35c49a5abafcdb0eb7cabab226ac2fb8472a Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 16:35:42 +0100 Subject: [PATCH 08/26] nit naming Signed-off-by: Jakub Sujak --- test/common/buffer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp index 42ffa11e..d7001972 100644 --- a/test/common/buffer.hpp +++ b/test/common/buffer.hpp @@ -16,7 +16,7 @@ namespace kai::test { /// Buffer is a high-level abstraction for a block of memory. /// /// The class performs dynamic memory allocation and management in an opaque manner. The underlying memory resource can -/// be requested using the familiar @ref Buffer::data() function and interacted with using @ref +/// be requested using the familiar @ref Buffer::data() method and interacted with using @ref /// kai::test::read_array() and @ref kai::test::write_array() utilities. /// /// Buffer comes with protection mechanisms defined by @ref BufferProtectionPolicy. These are enabled by setting the -- GitLab From 9ef585974c203ee078b9f58bf66a5a193bed079d Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 16:45:12 +0100 Subject: [PATCH 09/26] Correct description of BufferProtectionPolicy::ProtectUnderflow Signed-off-by: Jakub Sujak --- test/common/buffer.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp index d7001972..d4c275e7 100644 --- a/test/common/buffer.hpp +++ b/test/common/buffer.hpp @@ -54,10 +54,10 @@ public: private: /// Buffer can be protected with one of the following protection policies: /// - @ref BufferProtectionPolicy::None No protection mechanisms are enabled. - /// - @ref BufferProtectionPolicy::ProtectUnderflow Memory equal to the size of the nearest whole page is - /// allocated, guard pages set up on either side, and the user - /// buffer aligned to the end of the head guard page thus - /// detecting whenever a buffer underflow occurs. + /// - @ref BufferProtectionPolicy::ProtectUnderflow Memory equal to the size of the user buffer rounded to the + /// nearest whole page plus adjacent guard pages is allocated, + /// and the user buffer is aligned to the end of the head guard + /// page thus detecting whenever a buffer underflow occurs. /// - @ref BufferProtectionPolicy::ProtectOverflow Same as above, but now the edge of the user buffer is aligned /// to the start of the tail guard page thus detecting whenever a /// buffer overflow occurs. -- GitLab From 8ee6202fe15ffc2ffc627a7ace94e0dbdbe84dc9 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 17:15:18 +0100 Subject: [PATCH 10/26] Align test name with environment variable Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index 2790eeff..b914768a 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -58,7 +58,7 @@ TEST(Buffer, NoPolicy) { } #if defined(__linux__) || defined(__APPLE__) -TEST(Buffer, DetectUnderflow) { +TEST(Buffer, ProtectUnderflow) { std::random_device rd; std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); @@ -92,7 +92,7 @@ TEST(Buffer, DetectUnderflow) { ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } -TEST(Buffer, DetectOverflow) { +TEST(Buffer, ProtectOverflow) { std::random_device rd; std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); -- GitLab From dad1128797817d8af14666e4b1f81b56a9815619 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Mon, 14 Apr 2025 17:22:31 +0100 Subject: [PATCH 11/26] Guard inclusion of Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index c640a300..c16f5016 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -6,7 +6,10 @@ #include "buffer.hpp" +#if defined(__linux__) || defined(__APPLE__) #include +#endif // defined(__linux__) || defined(__APPLE__) + #include #include -- GitLab From 14e23898b8a625eade8dde58ad28bb7d9c5e5498 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Tue, 15 Apr 2025 11:48:23 +0100 Subject: [PATCH 12/26] Catch multiple signals Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index b914768a..a0b7868c 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -22,12 +22,6 @@ namespace kai::test { namespace { constexpr size_t g_num_runs = 100; - -#if defined(__APPLE__) -constexpr size_t g_signal = SIGBUS; -#else // if defined(__APPLE__) -constexpr size_t g_signal = SIGSEGV; -#endif // #if defined(__APPLE__) #else } // namespace TEST(Buffer, NoPolicy) { @@ -84,7 +78,15 @@ TEST(Buffer, ProtectUnderflow) { // Ignore missing default case in switch statement in test dependency macro #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" - EXPECT_EXIT({ [[maybe_unused]] const volatile auto val = *--data; }, testing::KilledBySignal(g_signal), ""); + EXPECT_EXIT( + // Underflow by one byte + { [[maybe_unused]] const volatile auto val = *--data; }, + [](const size_t exit_status) { + return testing::KilledBySignal(SIGBUS)(exit_status) || // + testing::KilledBySignal(SIGSEGV)(exit_status) || // + testing::KilledBySignal(SIGABRT)(exit_status); // + }, + ""); #pragma GCC diagnostic pop } @@ -119,7 +121,13 @@ TEST(Buffer, ProtectOverflow) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" EXPECT_EXIT( - { [[maybe_unused]] const volatile auto val = *(data + buffer_size); }, testing::KilledBySignal(g_signal), + // Overflow by one byte + { [[maybe_unused]] const volatile auto val = *(data + buffer_size); }, + [](const size_t exit_status) { + return testing::KilledBySignal(SIGBUS)(exit_status) || // + testing::KilledBySignal(SIGSEGV)(exit_status) || // + testing::KilledBySignal(SIGABRT)(exit_status); // + }, ""); #pragma GCC diagnostic pop } -- GitLab From 21e8d22d9b3be3222fd6da03ba208d58311d5d40 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Wed, 16 Apr 2025 09:59:58 +0100 Subject: [PATCH 13/26] Improve inline opportunities Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 8 -------- test/common/buffer.hpp | 8 ++++++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index c16f5016..6d0ce46d 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -46,14 +46,6 @@ Buffer::Buffer(const size_t size) : m_user_buffer_size(size) { } } -void* Buffer::data() const { - return static_cast(m_buffer.get()) + m_user_buffer_offset; -} - -size_t Buffer::size() const { - return m_user_buffer_size; -} - void Buffer::allocate() { m_buffer = handle(std::malloc(m_user_buffer_size), &std::free); KAI_ASSUME_MSG(m_buffer.get() != nullptr, "Failure allocating memory"); diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp index d4c275e7..b749018c 100644 --- a/test/common/buffer.hpp +++ b/test/common/buffer.hpp @@ -41,7 +41,9 @@ public: /// Gets the base memory address of the user buffer. /// /// @return Base memory address of the user buffer. - [[nodiscard]] void* data() const; + [[nodiscard]] void* data() const { + return static_cast(m_buffer.get()) + m_user_buffer_offset; + } /// Gets the size of the user buffer. /// @@ -49,7 +51,9 @@ public: /// However, this function guarantees to always provide the size of the user buffer only. /// /// @return Size of the user buffer in bytes. - [[nodiscard]] size_t size() const; + [[nodiscard]] size_t size() const { + return m_user_buffer_size; + } private: /// Buffer can be protected with one of the following protection policies: -- GitLab From 3196690452e7aefa13afa14f15962ead9b1275ed Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Wed, 16 Apr 2025 13:37:20 +0100 Subject: [PATCH 14/26] Guard inclusion of unistd.h header Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 6d0ce46d..86e7d6d1 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -8,9 +8,8 @@ #if defined(__linux__) || defined(__APPLE__) #include -#endif // defined(__linux__) || defined(__APPLE__) - #include +#endif // defined(__linux__) || defined(__APPLE__) #include #include -- GitLab From d95c0cb901f851707a28535295b5c6b54477c2cf Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Wed, 16 Apr 2025 13:46:57 +0100 Subject: [PATCH 15/26] Try uint64_t for page size Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 86e7d6d1..993a4b2f 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -12,6 +12,7 @@ #endif // defined(__linux__) || defined(__APPLE__) #include +#include #include #include #include @@ -53,7 +54,7 @@ void Buffer::allocate() { void Buffer::allocate_with_guard_pages() { #if defined(__linux__) || defined(__APPLE__) - const size_t page_size = sysconf(_SC_PAGESIZE); + const uint64_t page_size = sysconf(_SC_PAGESIZE); // Offset the user buffer by the size of the first guard page m_user_buffer_offset = page_size; -- GitLab From 42f93750c7ee27cd35171ee7937103a46bd26cba Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Wed, 16 Apr 2025 13:56:08 +0100 Subject: [PATCH 16/26] Guard declaration of and calls to Buffer::allocate_with_guard_pages() Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 11 +++++------ test/common/buffer.hpp | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 993a4b2f..0165607d 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -31,16 +31,17 @@ Buffer::Buffer(const size_t size) : m_user_buffer_size(size) { if (buffer_policy == "PROTECT_UNDERFLOW") { m_protection_policy = BufferProtectionPolicy::ProtectUnderflow; - } - if (buffer_policy == "PROTECT_OVERFLOW") { + } else if (buffer_policy == "PROTECT_OVERFLOW") { m_protection_policy = BufferProtectionPolicy::ProtectOverflow; } switch (m_protection_policy) { +#if defined(__linux__) || defined(__APPLE__) case BufferProtectionPolicy::ProtectUnderflow: case BufferProtectionPolicy::ProtectOverflow: allocate_with_guard_pages(); break; +#endif // defined(__linux__) || defined(__APPLE__) default: allocate(); } @@ -52,8 +53,8 @@ void Buffer::allocate() { KAI_ASSUME_MSG(m_user_buffer_offset == 0, "Buffer offset must be zero for naive allocation"); } -void Buffer::allocate_with_guard_pages() { #if defined(__linux__) || defined(__APPLE__) +void Buffer::allocate_with_guard_pages() { const uint64_t page_size = sysconf(_SC_PAGESIZE); // Offset the user buffer by the size of the first guard page @@ -92,9 +93,7 @@ void Buffer::allocate_with_guard_pages() { if (mprotect(tail_guard_page, page_size, PROT_NONE) != 0) { KAI_ERROR("Failure protecting page immediately following buffer"); } -#else // #if defined(__linux__) || defined(__APPLE__) - KAI_ERROR("Method not implemented for target platform"); -#endif // defined(__linux__) || defined(__APPLE__) } +#endif // defined(__linux__) || defined(__APPLE__) } // namespace kai::test diff --git a/test/common/buffer.hpp b/test/common/buffer.hpp index b749018c..a2226f53 100644 --- a/test/common/buffer.hpp +++ b/test/common/buffer.hpp @@ -74,8 +74,10 @@ private: /// Naively allocate memory. void allocate(); +#if defined(__linux__) || defined(__APPLE__) /// Allocate memory with adjacent guard pages. void allocate_with_guard_pages(); +#endif // defined(__linux__) || defined(__APPLE__) handle m_buffer = nullptr; -- GitLab From cbd2cea7b613e0eda7dfe2025d45e8ee4525fc6f Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Wed, 16 Apr 2025 16:17:17 +0100 Subject: [PATCH 17/26] Check result of sysconf(_SC_PAGESIZE) Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 0165607d..5bb2e82c 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -55,7 +55,10 @@ void Buffer::allocate() { #if defined(__linux__) || defined(__APPLE__) void Buffer::allocate_with_guard_pages() { - const uint64_t page_size = sysconf(_SC_PAGESIZE); + const auto sc_pagesize_res = sysconf(_SC_PAGESIZE); + KAI_ASSUME_MSG(sc_pagesize_res != -1, "Error finding page size"); + + const auto page_size = static_cast(sc_pagesize_res); // Offset the user buffer by the size of the first guard page m_user_buffer_offset = page_size; -- GitLab From e9fa40b1b0f69e5239c11014e33ae2e09e53dc89 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Wed, 16 Apr 2025 16:38:33 +0100 Subject: [PATCH 18/26] Wrap in KAI_MAX to avoid passing negative value to mprotect Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 5bb2e82c..a6f34a4f 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -90,10 +90,10 @@ void Buffer::allocate_with_guard_pages() { void* head_guard_page = m_buffer.get(); void* tail_guard_page = static_cast(m_buffer.get()) + (total_memory_size - page_size); - if (mprotect(head_guard_page, page_size, PROT_NONE) != 0) { + if (mprotect(head_guard_page, KAI_MAX(0, page_size), PROT_NONE) != 0) { KAI_ERROR("Failure protecting page immediately preceding buffer"); } - if (mprotect(tail_guard_page, page_size, PROT_NONE) != 0) { + if (mprotect(tail_guard_page, KAI_MAX(0, page_size), PROT_NONE) != 0) { KAI_ERROR("Failure protecting page immediately following buffer"); } } -- GitLab From 7cc5ad4abfdca95ad10eb1f3aae36a5068f7d242 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Thu, 17 Apr 2025 09:53:26 +0100 Subject: [PATCH 19/26] Use std::max Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index a6f34a4f..fb5eeb41 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -11,9 +11,8 @@ #include #endif // defined(__linux__) || defined(__APPLE__) +#include #include -#include -#include #include #include @@ -90,10 +89,10 @@ void Buffer::allocate_with_guard_pages() { void* head_guard_page = m_buffer.get(); void* tail_guard_page = static_cast(m_buffer.get()) + (total_memory_size - page_size); - if (mprotect(head_guard_page, KAI_MAX(0, page_size), PROT_NONE) != 0) { + if (mprotect(head_guard_page, std::max(static_cast(0), page_size), PROT_NONE) != 0) { KAI_ERROR("Failure protecting page immediately preceding buffer"); } - if (mprotect(tail_guard_page, KAI_MAX(0, page_size), PROT_NONE) != 0) { + if (mprotect(tail_guard_page, std::max(static_cast(0), page_size), PROT_NONE) != 0) { KAI_ERROR("Failure protecting page immediately following buffer"); } } -- GitLab From acae662c05e527f3e3e075d899ed4d3311e4f067 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Tue, 29 Apr 2025 12:59:10 +0100 Subject: [PATCH 20/26] Check for invalid buffer policies Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 27 ++++++++++++++++++++------- test/tests/buffer_test.cpp | 32 +++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index fb5eeb41..11c2f833 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "kai/kai_common.h" @@ -23,15 +24,27 @@ namespace kai::test { Buffer::Buffer(const size_t size) : m_user_buffer_size(size) { KAI_ASSUME_MSG(size > 0, "Buffers must be of non-zero size"); - std::string buffer_policy; - if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { - buffer_policy = std::string(buffer_policy_env); + const char* val = getenv("KAI_TEST_BUFFER_POLICY"); + const std::string buffer_policy = (val != nullptr) ? std::string(val) : std::string("NONE"); + + std::ostringstream oss; + + if (buffer_policy == "PROTECT_UNDERFLOW" || buffer_policy == "PROTECT_OVERFLOW") { +#if defined(__linux__) || defined(__APPLE__) + m_protection_policy = (buffer_policy == "PROTECT_UNDERFLOW") ? BufferProtectionPolicy::ProtectUnderflow + : BufferProtectionPolicy::ProtectOverflow; +#else // defined(__linux__) || defined(__APPLE__) + oss << buffer_policy << " buffer protection policy is not supported on target platform"; +#endif // defined(__linux__) || defined(__APPLE__) + } else if (buffer_policy == "NONE") { + m_protection_policy = BufferProtectionPolicy::None; + } else { + oss << "Unrecognized buffer protection policy provided by KAI_TEST_BUFFER_POLICY: "; + oss << buffer_policy; } - if (buffer_policy == "PROTECT_UNDERFLOW") { - m_protection_policy = BufferProtectionPolicy::ProtectUnderflow; - } else if (buffer_policy == "PROTECT_OVERFLOW") { - m_protection_policy = BufferProtectionPolicy::ProtectOverflow; + if (!oss.str().empty()) { + KAI_ERROR(oss.str().c_str()); } switch (m_protection_policy) { diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index a0b7868c..ad66bf1d 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -24,7 +24,7 @@ namespace { constexpr size_t g_num_runs = 100; } // namespace -TEST(Buffer, NoPolicy) { +TEST(Buffer, NonePolicy) { std::random_device rd; std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); @@ -51,8 +51,34 @@ TEST(Buffer, NoPolicy) { ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } +TEST(Buffer, InvalidPolicy) { + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(1, std::numeric_limits::max()); + + // Store the current buffer policy + std::string buffer_policy; + if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { + buffer_policy = std::string(buffer_policy_env); + } + + // Overwrite the buffer policy for purpose of the test + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", "INVALID_POLICY_TEST", 1 /* overwrite */), 0); + + for (size_t i = 0; i < g_num_runs; ++i) { + const size_t buffer_size = dist(rng); + + EXPECT_EXIT( + { [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, + [](const size_t exit_status) { return testing::KilledBySignal(SIGABRT)(exit_status); }, ""); + } + + // Restore the buffer policy to its original value + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); +} + #if defined(__linux__) || defined(__APPLE__) -TEST(Buffer, ProtectUnderflow) { +TEST(Buffer, ProtectUnderflowPolicy) { std::random_device rd; std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); @@ -94,7 +120,7 @@ TEST(Buffer, ProtectUnderflow) { ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } -TEST(Buffer, ProtectOverflow) { +TEST(Buffer, ProtectOverflowPolicy) { std::random_device rd; std::mt19937 rng(rd()); std::uniform_int_distribution dist(1, std::numeric_limits::max()); -- GitLab From 8a0d3d78c3dd1c5a927fb1d364cf5d671f49edb1 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Tue, 29 Apr 2025 14:44:00 +0100 Subject: [PATCH 21/26] Add compiler diagnostic ignore Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index ad66bf1d..ea983779 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -68,9 +68,13 @@ TEST(Buffer, InvalidPolicy) { for (size_t i = 0; i < g_num_runs; ++i) { const size_t buffer_size = dist(rng); +// Ignore missing default case in switch statement in test dependency macro +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-default" EXPECT_EXIT( { [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, [](const size_t exit_status) { return testing::KilledBySignal(SIGABRT)(exit_status); }, ""); +#pragma GCC diagnostic pop } // Restore the buffer policy to its original value -- GitLab From c97c831e717a1dffb6046d2557f791e2f2be32ca Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Thu, 1 May 2025 09:41:24 +0100 Subject: [PATCH 22/26] Assert test failure Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index ea983779..1b1d7d84 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -71,9 +71,7 @@ TEST(Buffer, InvalidPolicy) { // Ignore missing default case in switch statement in test dependency macro #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" - EXPECT_EXIT( - { [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, - [](const size_t exit_status) { return testing::KilledBySignal(SIGABRT)(exit_status); }, ""); + ASSERT_DEATH({ [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, ""); #pragma GCC diagnostic pop } -- GitLab From 11f44ece40f5c31badd1168f38e5fab0c6e7d8a5 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Thu, 1 May 2025 14:34:49 +0100 Subject: [PATCH 23/26] Use EXPECT instead of ASSERT Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index 1b1d7d84..fbd5e259 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -71,7 +71,7 @@ TEST(Buffer, InvalidPolicy) { // Ignore missing default case in switch statement in test dependency macro #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" - ASSERT_DEATH({ [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, ""); + EXPECT_DEATH({ [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, ""); #pragma GCC diagnostic pop } -- GitLab From 8ef76838a73a86179abd8947967403b3dda17da2 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Thu, 1 May 2025 14:54:26 +0100 Subject: [PATCH 24/26] Remove InvalidPolicy test Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index fbd5e259..d2037343 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -51,34 +51,6 @@ TEST(Buffer, NonePolicy) { ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } -TEST(Buffer, InvalidPolicy) { - std::random_device rd; - std::mt19937 rng(rd()); - std::uniform_int_distribution dist(1, std::numeric_limits::max()); - - // Store the current buffer policy - std::string buffer_policy; - if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { - buffer_policy = std::string(buffer_policy_env); - } - - // Overwrite the buffer policy for purpose of the test - ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", "INVALID_POLICY_TEST", 1 /* overwrite */), 0); - - for (size_t i = 0; i < g_num_runs; ++i) { - const size_t buffer_size = dist(rng); - -// Ignore missing default case in switch statement in test dependency macro -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wswitch-default" - EXPECT_DEATH({ [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, ""); -#pragma GCC diagnostic pop - } - - // Restore the buffer policy to its original value - ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); -} - #if defined(__linux__) || defined(__APPLE__) TEST(Buffer, ProtectUnderflowPolicy) { std::random_device rd; -- GitLab From 7dc0f165b2298ea5973376996a664c3551270a88 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Fri, 2 May 2025 10:33:01 +0100 Subject: [PATCH 25/26] Revert "Remove InvalidPolicy test" This reverts commit 8ef76838a73a86179abd8947967403b3dda17da2. Signed-off-by: Jakub Sujak --- test/tests/buffer_test.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/tests/buffer_test.cpp b/test/tests/buffer_test.cpp index d2037343..fbd5e259 100644 --- a/test/tests/buffer_test.cpp +++ b/test/tests/buffer_test.cpp @@ -51,6 +51,34 @@ TEST(Buffer, NonePolicy) { ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); } +TEST(Buffer, InvalidPolicy) { + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(1, std::numeric_limits::max()); + + // Store the current buffer policy + std::string buffer_policy; + if (const char* buffer_policy_env = getenv("KAI_TEST_BUFFER_POLICY")) { + buffer_policy = std::string(buffer_policy_env); + } + + // Overwrite the buffer policy for purpose of the test + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", "INVALID_POLICY_TEST", 1 /* overwrite */), 0); + + for (size_t i = 0; i < g_num_runs; ++i) { + const size_t buffer_size = dist(rng); + +// Ignore missing default case in switch statement in test dependency macro +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-default" + EXPECT_DEATH({ [[maybe_unused]] const auto buffer = Buffer(buffer_size); }, ""); +#pragma GCC diagnostic pop + } + + // Restore the buffer policy to its original value + ASSERT_EQ(setenv("KAI_TEST_BUFFER_POLICY", buffer_policy.c_str(), 1 /* overwrite */), 0); +} + #if defined(__linux__) || defined(__APPLE__) TEST(Buffer, ProtectUnderflowPolicy) { std::random_device rd; -- GitLab From cb89685dcb73c2edf454a31d7d3b33e16fbdba49 Mon Sep 17 00:00:00 2001 From: Jakub Sujak Date: Fri, 2 May 2025 10:37:38 +0100 Subject: [PATCH 26/26] Check for empty buffer policy Signed-off-by: Jakub Sujak --- test/common/buffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/buffer.cpp b/test/common/buffer.cpp index 11c2f833..65c9e261 100644 --- a/test/common/buffer.cpp +++ b/test/common/buffer.cpp @@ -36,7 +36,7 @@ Buffer::Buffer(const size_t size) : m_user_buffer_size(size) { #else // defined(__linux__) || defined(__APPLE__) oss << buffer_policy << " buffer protection policy is not supported on target platform"; #endif // defined(__linux__) || defined(__APPLE__) - } else if (buffer_policy == "NONE") { + } else if (buffer_policy == "NONE" || buffer_policy == "") { m_protection_policy = BufferProtectionPolicy::None; } else { oss << "Unrecognized buffer protection policy provided by KAI_TEST_BUFFER_POLICY: "; -- GitLab