//===- llvm/unittest/CodeGen/SPIRVAPITest.cpp -----------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // /// \file /// Test that SPIR-V Backend provides an API call that translates LLVM IR Module /// into SPIR-V. // //===----------------------------------------------------------------------===// #include "llvm/AsmParser/Parser.h" #include "llvm/BinaryFormat/Magic.h" #include "llvm/IR/Module.h" #include "llvm/Support/SourceMgr.h" #include "llvm/TargetParser/Triple.h" #include "gtest/gtest.h" #include #include #include namespace llvm { extern "C" LLVM_EXTERNAL_VISIBILITY bool SPIRVTranslate(Module *M, std::string &SpirvObj, std::string &ErrMsg, const std::vector &AllowExtNames, llvm::CodeGenOptLevel OLevel, Triple TargetTriple); extern "C" bool SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg, const std::vector &AllowExtNames, const std::vector &Opts); class SPIRVAPITest : public testing::Test { protected: bool toSpirv(StringRef Assembly, std::string &Result, std::string &ErrMsg, const std::vector &AllowExtNames, llvm::CodeGenOptLevel OLevel, Triple TargetTriple) { SMDiagnostic ParseError; LLVMContext Context; std::unique_ptr M = parseAssemblyString(Assembly, ParseError, Context); if (!M) { ParseError.print("IR parsing failed: ", errs()); report_fatal_error("Can't parse input assembly."); } bool Status = SPIRVTranslate(M.get(), Result, ErrMsg, AllowExtNames, OLevel, TargetTriple); if (!Status) errs() << ErrMsg; return Status; } // TODO: Remove toSpirvLegacy() and related tests after existing clients // switch into a newer implementation of SPIRVTranslate(). bool toSpirvLegacy(StringRef Assembly, std::string &Result, std::string &ErrMsg, const std::vector &AllowExtNames, const std::vector &Opts) { SMDiagnostic ParseError; LLVMContext Context; std::unique_ptr M = parseAssemblyString(Assembly, ParseError, Context); if (!M) { ParseError.print("IR parsing failed: ", errs()); report_fatal_error("Can't parse input assembly."); } bool Status = SPIRVTranslateModule(M.get(), Result, ErrMsg, AllowExtNames, Opts); if (!Status) errs() << ErrMsg; return Status; } static constexpr StringRef ExtensionAssembly = R"( define dso_local spir_func void @test1() { entry: %res1 = tail call spir_func i32 @_Z26__spirv_GroupBitwiseAndKHR(i32 2, i32 0, i32 0) ret void } declare dso_local spir_func i32 @_Z26__spirv_GroupBitwiseAndKHR(i32, i32, i32) )"; static constexpr StringRef OkAssembly = R"( %struct = type { [1 x i64] } define spir_kernel void @foo(ptr noundef byval(%struct) %arg) { entry: call spir_func void @bar(<2 x i32> noundef ) ret void } define spir_func void @bar(<2 x i32> noundef) { entry: ret void } )"; }; TEST_F(SPIRVAPITest, checkTranslateOk) { StringRef Assemblies[] = {"", OkAssembly}; for (StringRef &Assembly : Assemblies) { std::string Result, Error; bool Status = toSpirv(Assembly, Result, Error, {}, CodeGenOptLevel::Default, Triple("spirv32-unknown-unknown")); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } } TEST_F(SPIRVAPITest, checkTranslateSupportExtensionByArg) { std::string Result, Error; std::vector ExtNames{"SPV_KHR_uniform_group_instructions"}; bool Status = toSpirv(ExtensionAssembly, Result, Error, ExtNames, CodeGenOptLevel::Aggressive, Triple("spirv64-unknown-unknown")); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } TEST_F(SPIRVAPITest, checkTranslateSupportExtensionByArgList) { std::string Result, Error; std::vector ExtNames{"SPV_KHR_subgroup_rotate", "SPV_KHR_uniform_group_instructions", "SPV_KHR_subgroup_rotate"}; bool Status = toSpirv(ExtensionAssembly, Result, Error, ExtNames, CodeGenOptLevel::Aggressive, Triple("spirv64-unknown-unknown")); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } TEST_F(SPIRVAPITest, checkTranslateAllExtensions) { std::string Result, Error; std::vector ExtNames{"all"}; bool Status = toSpirv(ExtensionAssembly, Result, Error, ExtNames, CodeGenOptLevel::Aggressive, Triple("spirv64-unknown-unknown")); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } TEST_F(SPIRVAPITest, checkTranslateUnknownExtensionByArg) { std::string Result, Error; std::vector ExtNames{"SPV_XYZ_my_unknown_extension"}; bool Status = toSpirv(ExtensionAssembly, Result, Error, ExtNames, CodeGenOptLevel::Aggressive, Triple("spirv64-unknown-unknown")); EXPECT_FALSE(Status); EXPECT_TRUE(Result.empty()); EXPECT_EQ(Error, "Unknown SPIR-V extension: SPV_XYZ_my_unknown_extension"); } #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST TEST_F(SPIRVAPITest, checkTranslateExtensionError) { std::string Result, Error; EXPECT_DEATH_IF_SUPPORTED( { toSpirv(ExtensionAssembly, Result, Error, {}, CodeGenOptLevel::Aggressive, Triple("spirv64-unknown-unknown")); }, "LLVM ERROR: __spirv_GroupBitwiseAndKHR: the builtin requires the " "following SPIR-V extension: SPV_KHR_uniform_group_instructions"); } TEST_F(SPIRVAPITest, checkTranslateWrongExtensionByArg) { std::string Result, Error; std::vector ExtNames{"SPV_KHR_subgroup_rotate"}; EXPECT_DEATH_IF_SUPPORTED( { toSpirv(ExtensionAssembly, Result, Error, ExtNames, CodeGenOptLevel::Aggressive, Triple("spirv64-unknown-unknown")); }, "LLVM ERROR: __spirv_GroupBitwiseAndKHR: the builtin requires the " "following SPIR-V extension: SPV_KHR_uniform_group_instructions"); } #endif // Legacy API calls. TODO: Remove after transition into a newer API. TEST_F(SPIRVAPITest, checkTranslateStringOptsOk) { StringRef Assemblies[] = {"", OkAssembly}; std::vector SetOfOpts[] = {{}, {"spirv32-unknown-unknown"}}; for (const auto &Opts : SetOfOpts) { for (StringRef &Assembly : Assemblies) { std::string Result, Error; bool Status = toSpirvLegacy(Assembly, Result, Error, {}, Opts); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } } } TEST_F(SPIRVAPITest, checkTranslateStringOptsError) { std::string Result, Error; bool Status = toSpirvLegacy(OkAssembly, Result, Error, {}, {"spirv64v1.6-unknown-unknown", "5"}); EXPECT_FALSE(Status); EXPECT_TRUE(Result.empty()); EXPECT_EQ(Error, "Invalid optimization level!"); } TEST_F(SPIRVAPITest, checkTranslateStringOptsErrorOk) { { std::string Result, Error; bool Status = toSpirvLegacy(OkAssembly, Result, Error, {}, {"spirv64v1.6-unknown-unknown", "5"}); EXPECT_FALSE(Status); EXPECT_TRUE(Result.empty()); EXPECT_EQ(Error, "Invalid optimization level!"); } { std::string Result, Error; bool Status = toSpirvLegacy(OkAssembly, Result, Error, {}, {"spirv64v1.6-unknown-unknown", "3"}); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } } TEST_F(SPIRVAPITest, checkTranslateStringOptsSupportExtensionByArg) { std::string Result, Error; std::vector ExtNames{"SPV_KHR_uniform_group_instructions"}; bool Status = toSpirvLegacy(ExtensionAssembly, Result, Error, ExtNames, {}); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } TEST_F(SPIRVAPITest, checkTranslateStringOptsSupportExtensionByArgList) { std::string Result, Error; std::vector ExtNames{"SPV_KHR_subgroup_rotate", "SPV_KHR_uniform_group_instructions", "SPV_KHR_subgroup_rotate"}; bool Status = toSpirvLegacy(ExtensionAssembly, Result, Error, ExtNames, {}); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } TEST_F(SPIRVAPITest, checkTranslateStringOptsAllExtensions) { std::string Result, Error; std::vector ExtNames{"all"}; bool Status = toSpirvLegacy(ExtensionAssembly, Result, Error, ExtNames, {}); EXPECT_TRUE(Status && Error.empty() && !Result.empty()); EXPECT_EQ(identify_magic(Result), file_magic::spirv_object); } TEST_F(SPIRVAPITest, checkTranslateStringOptsUnknownExtensionByArg) { std::string Result, Error; std::vector ExtNames{"SPV_XYZ_my_unknown_extension"}; bool Status = toSpirvLegacy(ExtensionAssembly, Result, Error, ExtNames, {}); EXPECT_FALSE(Status); EXPECT_TRUE(Result.empty()); EXPECT_EQ(Error, "Unknown SPIR-V extension: SPV_XYZ_my_unknown_extension"); } #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST TEST_F(SPIRVAPITest, checkTranslateStringOptsExtensionError) { std::string Result, Error; EXPECT_DEATH_IF_SUPPORTED( { toSpirvLegacy(ExtensionAssembly, Result, Error, {}, {}); }, "LLVM ERROR: __spirv_GroupBitwiseAndKHR: the builtin requires the " "following SPIR-V extension: SPV_KHR_uniform_group_instructions"); } TEST_F(SPIRVAPITest, checkTranslateStringOptsWrongExtensionByArg) { std::string Result, Error; std::vector ExtNames{"SPV_KHR_subgroup_rotate"}; EXPECT_DEATH_IF_SUPPORTED( { toSpirvLegacy(ExtensionAssembly, Result, Error, ExtNames, {}); }, "LLVM ERROR: __spirv_GroupBitwiseAndKHR: the builtin requires the " "following SPIR-V extension: SPV_KHR_uniform_group_instructions"); } #endif } // end namespace llvm