235 lines
11 KiB
C++
235 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2018-2022 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
*/
|
|
|
|
#include "shared/source/compiler_interface/intermediate_representations.h"
|
|
#include "shared/source/device/device.h"
|
|
#include "shared/source/device_binary_format/elf/elf.h"
|
|
#include "shared/source/device_binary_format/elf/elf_decoder.h"
|
|
#include "shared/source/device_binary_format/elf/ocl_elf.h"
|
|
#include "shared/source/helpers/file_io.h"
|
|
#include "shared/source/helpers/string.h"
|
|
#include "shared/test/common/device_binary_format/patchtokens_tests.h"
|
|
#include "shared/test/common/helpers/gtest_helpers.h"
|
|
#include "shared/test/common/helpers/test_files.h"
|
|
#include "shared/test/common/mocks/mock_device.h"
|
|
#include "shared/test/common/mocks/mock_elf.h"
|
|
#include "shared/test/unit_test/device_binary_format/elf/elf_tests_data.h"
|
|
|
|
#include "opencl/test/unit_test/mocks/mock_cl_device.h"
|
|
#include "opencl/test/unit_test/mocks/mock_program.h"
|
|
|
|
#include "compiler_options.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <cstring>
|
|
|
|
using namespace NEO;
|
|
|
|
class ProcessElfBinaryTests : public ::testing::Test {
|
|
public:
|
|
void SetUp() override {
|
|
device = std::make_unique<MockClDevice>(MockDevice::createWithNewExecutionEnvironment<MockDevice>(nullptr, rootDeviceIndex));
|
|
program = std::make_unique<MockProgram>(nullptr, false, toClDeviceVector(*device));
|
|
}
|
|
|
|
std::unique_ptr<MockProgram> program;
|
|
std::unique_ptr<ClDevice> device;
|
|
const uint32_t rootDeviceIndex = 1;
|
|
};
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenNullWhenCreatingProgramFromBinaryThenInvalidBinaryErrorIsReturned) {
|
|
cl_int retVal = program->createProgramFromBinary(nullptr, 0, *device);
|
|
EXPECT_EQ(CL_INVALID_BINARY, retVal);
|
|
}
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenInvalidBinaryWhenCreatingProgramFromBinaryThenInvalidBinaryErrorIsReturned) {
|
|
char pBinary[] = "thisistotallyinvalid\0";
|
|
size_t binarySize = strnlen_s(pBinary, 21);
|
|
cl_int retVal = program->createProgramFromBinary(pBinary, binarySize, *device);
|
|
|
|
EXPECT_EQ(CL_INVALID_BINARY, retVal);
|
|
}
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenValidBinaryWhenCreatingProgramFromBinaryThenSuccessIsReturned) {
|
|
auto mockElf = std::make_unique<MockElfBinaryPatchtokens<>>(device->getHardwareInfo());
|
|
auto pBinary = mockElf->storage;
|
|
auto binarySize = mockElf->storage.size();
|
|
|
|
cl_int retVal = program->createProgramFromBinary(pBinary.data(), binarySize, *device);
|
|
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
EXPECT_EQ(0, memcmp(pBinary.data(), program->buildInfos[rootDeviceIndex].packedDeviceBinary.get(), binarySize));
|
|
}
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenValidSpirBinaryWhenCreatingProgramFromBinaryThenSuccessIsReturned) {
|
|
//clCreateProgramWithIL => SPIR-V stored as source code
|
|
const uint32_t spirvBinary[2] = {0x03022307, 0x07230203};
|
|
size_t spirvBinarySize = sizeof(spirvBinary);
|
|
|
|
//clCompileProgram => SPIR-V stored as IR binary
|
|
program->isSpirV = true;
|
|
program->irBinary = makeCopy(spirvBinary, spirvBinarySize);
|
|
program->irBinarySize = spirvBinarySize;
|
|
program->deviceBuildInfos[device.get()].programBinaryType = CL_PROGRAM_BINARY_TYPE_LIBRARY;
|
|
EXPECT_NE(nullptr, program->irBinary);
|
|
EXPECT_NE(0u, program->irBinarySize);
|
|
EXPECT_TRUE(program->getIsSpirV());
|
|
|
|
//clGetProgramInfo => SPIR-V stored as ELF binary
|
|
cl_int retVal = program->packDeviceBinary(*device);
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
EXPECT_NE(nullptr, program->buildInfos[rootDeviceIndex].packedDeviceBinary);
|
|
EXPECT_NE(0u, program->buildInfos[rootDeviceIndex].packedDeviceBinarySize);
|
|
|
|
//use ELF reader to parse and validate ELF binary
|
|
std::string decodeErrors;
|
|
std::string decodeWarnings;
|
|
auto elf = NEO::Elf::decodeElf(ArrayRef<const uint8_t>(reinterpret_cast<const uint8_t *>(program->buildInfos[rootDeviceIndex].packedDeviceBinary.get()), program->buildInfos[rootDeviceIndex].packedDeviceBinarySize), decodeErrors, decodeWarnings);
|
|
auto header = elf.elfFileHeader;
|
|
ASSERT_NE(nullptr, header);
|
|
|
|
//check if ELF binary contains section SECTION_HEADER_TYPE_SPIRV
|
|
bool hasSpirvSection = false;
|
|
for (const auto &elfSectionHeader : elf.sectionHeaders) {
|
|
if (elfSectionHeader.header->type == NEO::Elf::SHT_OPENCL_SPIRV) {
|
|
hasSpirvSection = true;
|
|
break;
|
|
}
|
|
}
|
|
EXPECT_TRUE(hasSpirvSection);
|
|
|
|
//clCreateProgramWithBinary => new program should recognize SPIR-V binary
|
|
program->isSpirV = false;
|
|
auto elfBinary = makeCopy(program->buildInfos[rootDeviceIndex].packedDeviceBinary.get(), program->buildInfos[rootDeviceIndex].packedDeviceBinarySize);
|
|
retVal = program->createProgramFromBinary(elfBinary.get(), program->buildInfos[rootDeviceIndex].packedDeviceBinarySize, *device);
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
EXPECT_TRUE(program->getIsSpirV());
|
|
}
|
|
|
|
unsigned int BinaryTypeValues[] = {
|
|
CL_PROGRAM_BINARY_TYPE_EXECUTABLE,
|
|
CL_PROGRAM_BINARY_TYPE_LIBRARY,
|
|
CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT};
|
|
|
|
class ProcessElfBinaryTestsWithBinaryType : public ::testing::TestWithParam<unsigned int> {
|
|
public:
|
|
void SetUp() override {
|
|
device = std::make_unique<MockClDevice>(MockDevice::createWithNewExecutionEnvironment<MockDevice>(nullptr, rootDeviceIndex));
|
|
program = std::make_unique<MockProgram>(nullptr, false, toClDeviceVector(*device));
|
|
}
|
|
std::unique_ptr<MockProgram> program;
|
|
std::unique_ptr<ClDevice> device;
|
|
const uint32_t rootDeviceIndex = 1;
|
|
};
|
|
|
|
TEST_P(ProcessElfBinaryTestsWithBinaryType, GivenBinaryTypeWhenResolveProgramThenProgramIsProperlyResolved) {
|
|
auto mockElf = std::make_unique<MockElfBinaryPatchtokens<enabledIrFormat::ENABLE_SPIRV>>(device->getHardwareInfo());
|
|
auto pBinary = mockElf->storage;
|
|
auto binarySize = mockElf->storage.size();
|
|
|
|
cl_int retVal = program->createProgramFromBinary(pBinary.data(), binarySize, *device);
|
|
auto options = program->options;
|
|
auto genBinary = makeCopy(program->buildInfos[rootDeviceIndex].unpackedDeviceBinary.get(), program->buildInfos[rootDeviceIndex].unpackedDeviceBinarySize);
|
|
auto genBinarySize = program->buildInfos[rootDeviceIndex].unpackedDeviceBinarySize;
|
|
auto irBinary = makeCopy(program->irBinary.get(), program->irBinarySize);
|
|
auto irBinarySize = program->irBinarySize;
|
|
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
ASSERT_EQ(binarySize, program->buildInfos[rootDeviceIndex].packedDeviceBinarySize);
|
|
EXPECT_EQ(0, memcmp(pBinary.data(), program->buildInfos[rootDeviceIndex].packedDeviceBinary.get(), binarySize));
|
|
|
|
// delete program's elf reference to force a resolve
|
|
program->buildInfos[rootDeviceIndex].packedDeviceBinary.reset();
|
|
program->buildInfos[rootDeviceIndex].packedDeviceBinarySize = 0U;
|
|
program->deviceBuildInfos[device.get()].programBinaryType = GetParam();
|
|
retVal = program->packDeviceBinary(*device);
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
ASSERT_NE(nullptr, program->buildInfos[rootDeviceIndex].packedDeviceBinary);
|
|
|
|
std::string decodeErrors;
|
|
std::string decodeWarnings;
|
|
auto elf = NEO::Elf::decodeElf(ArrayRef<const uint8_t>(reinterpret_cast<const uint8_t *>(program->buildInfos[rootDeviceIndex].packedDeviceBinary.get()), program->buildInfos[rootDeviceIndex].packedDeviceBinarySize), decodeErrors, decodeWarnings);
|
|
ASSERT_NE(nullptr, elf.elfFileHeader);
|
|
ArrayRef<const uint8_t> decodedIr;
|
|
ArrayRef<const uint8_t> decodedDeviceBinary;
|
|
ArrayRef<const uint8_t> decodedOptions;
|
|
for (auto §ion : elf.sectionHeaders) {
|
|
switch (section.header->type) {
|
|
default:
|
|
break;
|
|
case NEO::Elf::SHT_OPENCL_LLVM_BINARY:
|
|
decodedIr = section.data;
|
|
break;
|
|
case NEO::Elf::SHT_OPENCL_SPIRV:
|
|
decodedIr = section.data;
|
|
break;
|
|
case NEO::Elf::SHT_OPENCL_DEV_BINARY:
|
|
decodedDeviceBinary = section.data;
|
|
break;
|
|
case NEO::Elf::SHT_OPENCL_OPTIONS:
|
|
decodedDeviceBinary = section.data;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_EQ(options.size(), decodedOptions.size());
|
|
ASSERT_EQ(genBinarySize, decodedDeviceBinary.size());
|
|
ASSERT_EQ(irBinarySize, decodedIr.size());
|
|
EXPECT_EQ(0, memcmp(genBinary.get(), decodedDeviceBinary.begin(), genBinarySize));
|
|
EXPECT_EQ(0, memcmp(irBinary.get(), decodedIr.begin(), irBinarySize));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(ResolveBinaryTests,
|
|
ProcessElfBinaryTestsWithBinaryType,
|
|
::testing::ValuesIn(BinaryTypeValues));
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenEmptyBuildOptionsWhenCreatingProgramFromBinaryThenSuccessIsReturned) {
|
|
auto mockElf = std::make_unique<MockElfBinaryPatchtokens<>>(device->getHardwareInfo());
|
|
auto pBinary = mockElf->storage;
|
|
auto binarySize = mockElf->storage.size();
|
|
|
|
cl_int retVal = program->createProgramFromBinary(pBinary.data(), binarySize, *device);
|
|
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
const auto &options = program->getOptions();
|
|
size_t optionsSize = strlen(options.c_str()) + 1;
|
|
EXPECT_EQ(0, memcmp("", options.c_str(), optionsSize));
|
|
}
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenNonEmptyBuildOptionsWhenCreatingProgramFromBinaryThenSuccessIsReturned) {
|
|
auto buildOptionsNotEmpty = CompilerOptions::concatenate(CompilerOptions::optDisable, "-DDEF_WAS_SPECIFIED=1");
|
|
auto mockElf = std::make_unique<MockElfBinaryPatchtokens<>>(buildOptionsNotEmpty, device->getHardwareInfo());
|
|
auto pBinary = mockElf->storage;
|
|
auto binarySize = mockElf->storage.size();
|
|
|
|
cl_int retVal = program->createProgramFromBinary(pBinary.data(), binarySize, *device);
|
|
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
const auto &options = program->getOptions();
|
|
EXPECT_TRUE(hasSubstr(options, buildOptionsNotEmpty));
|
|
}
|
|
|
|
TEST_F(ProcessElfBinaryTests, GivenBinaryWhenIncompatiblePatchtokenVerionThenProramCreationFails) {
|
|
PatchTokensTestData::ValidEmptyProgram programTokens;
|
|
{
|
|
NEO::Elf::ElfEncoder<> elfEncoder;
|
|
elfEncoder.getElfFileHeader().type = NEO::Elf::ET_OPENCL_EXECUTABLE;
|
|
elfEncoder.appendSection(NEO::Elf::SHT_OPENCL_DEV_BINARY, NEO::Elf::SectionNamesOpenCl::deviceBinary, programTokens.storage);
|
|
auto elfBinary = elfEncoder.encode();
|
|
cl_int retVal = program->createProgramFromBinary(elfBinary.data(), elfBinary.size(), *device);
|
|
EXPECT_EQ(CL_SUCCESS, retVal);
|
|
}
|
|
|
|
{
|
|
programTokens.headerMutable->Version -= 1;
|
|
NEO::Elf::ElfEncoder<> elfEncoder;
|
|
elfEncoder.getElfFileHeader().type = NEO::Elf::ET_OPENCL_EXECUTABLE;
|
|
elfEncoder.appendSection(NEO::Elf::SHT_OPENCL_DEV_BINARY, NEO::Elf::SectionNamesOpenCl::deviceBinary, programTokens.storage);
|
|
auto elfBinary = elfEncoder.encode();
|
|
cl_int retVal = program->createProgramFromBinary(elfBinary.data(), elfBinary.size(), *device);
|
|
EXPECT_EQ(CL_INVALID_BINARY, retVal);
|
|
}
|
|
} |