Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

294 lines
13 KiB
C++

// Copyright (c) 2026 Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT
/// @file kernarg_extension_test.cpp
/// @brief Unit tests for rocjitsu kernarg wrapper layout helpers.
#include "rocjitsu/code/patch/kernarg_extension.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include <span>
#include <vector>
namespace {
TEST(KernargExtensionTest, LayoutAndWriteSupportsMultiplePayloads) {
const std::array<rocjitsu::KernargExtensionPayloadLayout, 2> payloads = {{
{.size = 3, .alignment = 4},
{.size = 16, .alignment = 8},
}};
const auto layout =
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/10, std::span{payloads});
ASSERT_TRUE(layout.has_value());
EXPECT_EQ(layout->original_kernarg_size, 10u);
EXPECT_EQ(layout->original_kernarg_pointer_offset, 16u);
ASSERT_EQ(layout->payload_offsets.size(), 2u);
EXPECT_EQ(layout->payload_offsets[0], 24u);
EXPECT_EQ(layout->payload_offsets[1], 32u);
EXPECT_EQ(layout->wrapper_size, 48u);
const std::array<uint8_t, 10> original = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
const std::array<uint8_t, 3> payload0 = {0xAA, 0xBB, 0xCC};
const std::array<uint8_t, 16> payload1 = {
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
};
const uint64_t original_pointer = 0x123456789ABCDEF0ull;
const std::array<rocjitsu::KernargExtensionPayloadWrite, 2> writes = {{
{.data = payload0.data(), .size = static_cast<uint32_t>(payload0.size())},
{.data = payload1.data(), .size = static_cast<uint32_t>(payload1.size())},
}};
std::vector<uint8_t> wrapper(layout->wrapper_size, 0x5A);
ASSERT_TRUE(rocjitsu::write_kernarg_extension_wrapper(
std::span<uint8_t>(wrapper.data(), wrapper.size()), *layout, original.data(),
original_pointer, std::span{writes}));
EXPECT_TRUE(std::equal(original.begin(), original.end(), wrapper.begin()));
uint64_t copied_pointer = 0;
std::memcpy(&copied_pointer, wrapper.data() + layout->original_kernarg_pointer_offset,
sizeof(copied_pointer));
EXPECT_EQ(copied_pointer, original_pointer);
EXPECT_TRUE(
std::equal(payload0.begin(), payload0.end(), wrapper.begin() + layout->payload_offsets[0]));
EXPECT_TRUE(
std::equal(payload1.begin(), payload1.end(), wrapper.begin() + layout->payload_offsets[1]));
}
TEST(KernargExtensionTest, RejectsInvalidPayloadAlignment) {
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 4, .alignment = 3};
EXPECT_FALSE(
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/0, std::span{&payload, 1})
.has_value());
}
TEST(KernargExtensionTest, AllowsZeroSizeOriginalKernarg) {
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 24, .alignment = 8};
const auto layout =
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/0, std::span{&payload, 1});
ASSERT_TRUE(layout.has_value());
EXPECT_EQ(layout->original_kernarg_pointer_offset, 0u);
ASSERT_EQ(layout->payload_offsets.size(), 1u);
EXPECT_EQ(layout->payload_offsets[0], 8u);
EXPECT_EQ(layout->wrapper_size, 32u);
}
TEST(KernargExtensionTest, MetadataRoundTripsNamedPayloadContracts) {
const std::vector<rocjitsu::KernargExtensionMetadata> input = {{
.kernel_name = "kernel",
.variant_name = "variant",
.original_kernarg_size = 24,
.payloads =
{
{.size = 24, .alignment = 8, .name = "state"},
{.size = 12, .alignment = 4, .name = "workgroup-ids"},
},
}};
const auto parsed = rocjitsu::parse_kernarg_extension_metadata(
rocjitsu::serialize_kernarg_extension_metadata(input));
ASSERT_TRUE(parsed.has_value());
ASSERT_EQ(parsed->size(), 1u);
const auto &extension = parsed->front();
EXPECT_EQ(extension.kernel_name, input.front().kernel_name);
EXPECT_EQ(extension.variant_name, input.front().variant_name);
EXPECT_EQ(extension.original_kernarg_size, input.front().original_kernarg_size);
ASSERT_EQ(extension.payloads.size(), 2u);
for (size_t i = 0; i < extension.payloads.size(); ++i) {
EXPECT_EQ(extension.payloads[i].name, input.front().payloads[i].name);
EXPECT_EQ(extension.payloads[i].size, input.front().payloads[i].size);
EXPECT_EQ(extension.payloads[i].alignment, input.front().payloads[i].alignment);
}
}
// --- write_kernarg_extension_wrapper negative paths -------------------------
TEST(KernargExtensionTest, WrapperWriteRejectsWrongWrapperSize) {
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 8, .alignment = 8};
const auto layout =
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/0, std::span{&payload, 1});
ASSERT_TRUE(layout.has_value());
const std::array<uint8_t, 8> payload_bytes = {};
const std::array<rocjitsu::KernargExtensionPayloadWrite, 1> writes = {{
{.data = payload_bytes.data(), .size = static_cast<uint32_t>(payload_bytes.size())},
}};
// Buffer one byte short of layout.wrapper_size must be rejected.
std::vector<uint8_t> wrapper(layout->wrapper_size - 1, 0);
EXPECT_FALSE(rocjitsu::write_kernarg_extension_wrapper(
std::span<uint8_t>(wrapper.data(), wrapper.size()), *layout, /*original_kernarg=*/nullptr,
/*original_kernarg_pointer=*/0, std::span{writes}));
}
TEST(KernargExtensionTest, WrapperWriteRejectsPayloadCountMismatch) {
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 8, .alignment = 8};
const auto layout =
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/0, std::span{&payload, 1});
ASSERT_TRUE(layout.has_value());
std::vector<uint8_t> wrapper(layout->wrapper_size, 0);
// layout expects one payload; supply zero writes.
EXPECT_FALSE(rocjitsu::write_kernarg_extension_wrapper(
std::span<uint8_t>(wrapper.data(), wrapper.size()), *layout, /*original_kernarg=*/nullptr,
/*original_kernarg_pointer=*/0, std::span<const rocjitsu::KernargExtensionPayloadWrite>{}));
}
TEST(KernargExtensionTest, WrapperWriteRejectsNullOriginalWithNonZeroSize) {
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 8, .alignment = 8};
const auto layout =
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/16, std::span{&payload, 1});
ASSERT_TRUE(layout.has_value());
const std::array<uint8_t, 8> payload_bytes = {};
const std::array<rocjitsu::KernargExtensionPayloadWrite, 1> writes = {{
{.data = payload_bytes.data(), .size = static_cast<uint32_t>(payload_bytes.size())},
}};
std::vector<uint8_t> wrapper(layout->wrapper_size, 0);
// original_kernarg_size is 16 but the original pointer is null.
EXPECT_FALSE(rocjitsu::write_kernarg_extension_wrapper(
std::span<uint8_t>(wrapper.data(), wrapper.size()), *layout, /*original_kernarg=*/nullptr,
/*original_kernarg_pointer=*/0, std::span{writes}));
}
TEST(KernargExtensionTest, WrapperWriteRejectsNullPayloadDataWithNonZeroSize) {
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 8, .alignment = 8};
const auto layout =
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/0, std::span{&payload, 1});
ASSERT_TRUE(layout.has_value());
// Non-zero payload size with a null data pointer must be rejected.
const std::array<rocjitsu::KernargExtensionPayloadWrite, 1> writes = {{
{.data = nullptr, .size = 8},
}};
std::vector<uint8_t> wrapper(layout->wrapper_size, 0);
EXPECT_FALSE(rocjitsu::write_kernarg_extension_wrapper(
std::span<uint8_t>(wrapper.data(), wrapper.size()), *layout, /*original_kernarg=*/nullptr,
/*original_kernarg_pointer=*/0, std::span{writes}));
}
TEST(KernargExtensionTest, WrapperWriteRejectsForgedOversizedOriginalKernarg) {
// KernargExtensionLayout is public plain data; a forged layout can claim an
// original_kernarg_size far larger than the wrapper. The writer must reject it
// rather than memcpy nearly 4 GiB into a tiny buffer.
rocjitsu::KernargExtensionLayout layout;
layout.original_kernarg_size = std::numeric_limits<uint32_t>::max();
layout.original_kernarg_pointer_offset = 8;
layout.wrapper_size = 16;
std::vector<uint8_t> wrapper(layout.wrapper_size, 0);
EXPECT_FALSE(rocjitsu::write_kernarg_extension_wrapper(
std::span<uint8_t>(wrapper.data(), wrapper.size()), layout,
/*original_kernarg=*/wrapper.data(),
/*original_kernarg_pointer=*/0, std::span<const rocjitsu::KernargExtensionPayloadWrite>{}));
}
// --- make_kernarg_extension_layout overflow guards --------------------------
TEST(KernargExtensionTest, LayoutRejectsPayloadSizeOverflow) {
// The saved pointer already advances the cursor past 0, so a payload claiming
// the full uint32 range cannot fit and the layout must fail closed.
const rocjitsu::KernargExtensionPayloadLayout payload{
.size = std::numeric_limits<uint32_t>::max(), .alignment = 8};
EXPECT_FALSE(
rocjitsu::make_kernarg_extension_layout(/*original_kernarg_size=*/0, std::span{&payload, 1})
.has_value());
}
TEST(KernargExtensionTest, LayoutRejectsOriginalSizeAlignmentOverflow) {
// Aligning an original size within 7 bytes of the uint32 max up to 8 overflows.
const rocjitsu::KernargExtensionPayloadLayout payload{.size = 8, .alignment = 8};
EXPECT_FALSE(rocjitsu::make_kernarg_extension_layout(
/*original_kernarg_size=*/std::numeric_limits<uint32_t>::max() - 1,
std::span{&payload, 1})
.has_value());
}
// --- parse_kernarg_extension_metadata negative paths ------------------------
namespace {
// Header field byte offsets (see MetadataHeader in kernarg_extension.cpp):
// magic[8]@0, version@8, extension_count@12, payload_count@16, string_bytes@20.
constexpr size_t kVersionOffset = 8;
constexpr size_t kExtensionCountOffset = 12;
constexpr size_t kPayloadCountOffset = 16;
constexpr size_t kStringBytesOffset = 20;
// ExtensionRecord starts at 24; its reserved field is the 8th uint32.
constexpr size_t kFirstExtensionReservedOffset = 24 + 7 * sizeof(uint32_t);
std::vector<uint8_t> valid_metadata_bytes() {
const std::vector<rocjitsu::KernargExtensionMetadata> input = {{
.kernel_name = "kernel",
.variant_name = "variant",
.original_kernarg_size = 24,
.payloads = {{.size = 24, .alignment = 8, .name = "state"}},
}};
return rocjitsu::serialize_kernarg_extension_metadata(input);
}
void poke_u32(std::vector<uint8_t> &bytes, size_t offset, uint32_t value) {
ASSERT_GE(bytes.size(), offset + sizeof(value));
std::memcpy(bytes.data() + offset, &value, sizeof(value));
}
} // namespace
TEST(KernargExtensionTest, ParseAcceptsValidMetadata) {
// Positive control: the negative tests below all mutate valid_metadata_bytes(),
// so they would pass vacuously if the baseline builder stopped producing a
// parseable buffer. Anchor it here so each EXPECT_FALSE can only fail for its
// intended mutation.
EXPECT_TRUE(rocjitsu::parse_kernarg_extension_metadata(valid_metadata_bytes()).has_value());
}
TEST(KernargExtensionTest, ParseRejectsTruncatedHeader) {
auto bytes = valid_metadata_bytes();
ASSERT_GE(bytes.size(), 24u);
bytes.resize(23); // one byte short of the 24-byte header
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
TEST(KernargExtensionTest, ParseRejectsWrongMagic) {
auto bytes = valid_metadata_bytes();
bytes[0] ^= 0xFF; // corrupt the first magic byte
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
TEST(KernargExtensionTest, ParseRejectsUnknownVersion) {
auto bytes = valid_metadata_bytes();
poke_u32(bytes, kVersionOffset, 2);
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
TEST(KernargExtensionTest, ParseRejectsExtensionCountExceedingBuffer) {
auto bytes = valid_metadata_bytes();
poke_u32(bytes, kExtensionCountOffset, 0x10000u);
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
TEST(KernargExtensionTest, ParseRejectsPayloadCountExceedingBuffer) {
auto bytes = valid_metadata_bytes();
poke_u32(bytes, kPayloadCountOffset, 0x10000u);
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
TEST(KernargExtensionTest, ParseRejectsStringBytesExceedingBuffer) {
auto bytes = valid_metadata_bytes();
poke_u32(bytes, kStringBytesOffset, 0x10000u);
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
TEST(KernargExtensionTest, ParseRejectsNonZeroExtensionReserved) {
auto bytes = valid_metadata_bytes();
poke_u32(bytes, kFirstExtensionReservedOffset, 1);
EXPECT_FALSE(rocjitsu::parse_kernarg_extension_metadata(bytes).has_value());
}
} // namespace