//=== ParseHLSLRootSignatureTest.cpp - Parse Root Signature tests ---------===// // // 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 // //===----------------------------------------------------------------------===// #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/HeaderSearchOptions.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/ModuleLoader.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Lex/LexHLSLRootSignature.h" #include "clang/Parse/ParseHLSLRootSignature.h" #include "gtest/gtest.h" using namespace clang; using namespace llvm::hlsl::rootsig; namespace { // Diagnostic helper for helper tests class ExpectedDiagConsumer : public DiagnosticConsumer { virtual void anchor() {} void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override { if (!FirstDiag || !ExpectedDiagID.has_value()) { Satisfied = false; return; } FirstDiag = false; Satisfied = ExpectedDiagID.value() == Info.getID(); } bool FirstDiag = true; bool Satisfied = false; std::optional ExpectedDiagID; public: void setNoDiag() { Satisfied = true; ExpectedDiagID = std::nullopt; } void setExpected(unsigned DiagID) { Satisfied = false; ExpectedDiagID = DiagID; } bool isSatisfied() { return Satisfied; } }; // The test fixture. class ParseHLSLRootSignatureTest : public ::testing::Test { protected: ParseHLSLRootSignatureTest() : FileMgr(FileMgrOpts), DiagID(new DiagnosticIDs()), Consumer(new ExpectedDiagConsumer()), Diags(DiagID, new DiagnosticOptions, Consumer), SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions) { // This is an arbitrarily chosen target triple to create the target info. TargetOpts->Triple = "dxil"; Target = TargetInfo::CreateTargetInfo(Diags, *TargetOpts); } std::unique_ptr createPP(StringRef Source, TrivialModuleLoader &ModLoader) { std::unique_ptr Buf = llvm::MemoryBuffer::getMemBuffer(Source); SourceMgr.setMainFileID(SourceMgr.createFileID(std::move(Buf))); HeaderSearchOptions SearchOpts; HeaderSearch HeaderInfo(SearchOpts, SourceMgr, Diags, LangOpts, Target.get()); auto PP = std::make_unique( PPOpts, Diags, LangOpts, SourceMgr, HeaderInfo, ModLoader, /*IILookup =*/nullptr, /*OwnsHeaderSearch =*/false); PP->Initialize(*Target); PP->EnterMainSourceFile(); return PP; } FileSystemOptions FileMgrOpts; FileManager FileMgr; IntrusiveRefCntPtr DiagID; ExpectedDiagConsumer *Consumer; DiagnosticsEngine Diags; SourceManager SourceMgr; LangOptions LangOpts; PreprocessorOptions PPOpts; std::shared_ptr TargetOpts; IntrusiveRefCntPtr Target; }; // Valid Parser Tests TEST_F(ParseHLSLRootSignatureTest, ValidParseEmptyTest) { const llvm::StringLiteral Source = R"cc()cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test no diagnostics produced Consumer->setNoDiag(); ASSERT_FALSE(Parser.parse()); ASSERT_EQ((int)Elements.size(), 0); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, ValidParseDTClausesTest) { const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV(b0), SRV(space = 3, offset = 32, t42, flags = 0, numDescriptors = 4), visibility = SHADER_VISIBILITY_PIXEL, Sampler(s987, space = +2, offset = DESCRIPTOR_RANGE_OFFSET_APPEND), UAV(u4294967294, numDescriptors = unbounded, flags = Descriptors_Volatile | Data_Volatile | Data_Static_While_Set_At_Execute | Data_Static | Descriptors_Static_Keeping_Buffer_Bounds_Checks ) ), DescriptorTable() )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test no diagnostics produced Consumer->setNoDiag(); ASSERT_FALSE(Parser.parse()); // First Descriptor Table with 4 elements RootElement Elem = Elements[0]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Type, ClauseType::CBuffer); ASSERT_EQ(std::get(Elem).Reg.ViewType, RegisterType::BReg); ASSERT_EQ(std::get(Elem).Reg.Number, 0u); ASSERT_EQ(std::get(Elem).NumDescriptors, 1u); ASSERT_EQ(std::get(Elem).Space, 0u); ASSERT_EQ(std::get(Elem).Offset, DescriptorTableOffsetAppend); ASSERT_EQ(std::get(Elem).Flags, DescriptorRangeFlags::DataStaticWhileSetAtExecute); Elem = Elements[1]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Type, ClauseType::SRV); ASSERT_EQ(std::get(Elem).Reg.ViewType, RegisterType::TReg); ASSERT_EQ(std::get(Elem).Reg.Number, 42u); ASSERT_EQ(std::get(Elem).NumDescriptors, 4u); ASSERT_EQ(std::get(Elem).Space, 3u); ASSERT_EQ(std::get(Elem).Offset, 32u); ASSERT_EQ(std::get(Elem).Flags, DescriptorRangeFlags::None); Elem = Elements[2]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Type, ClauseType::Sampler); ASSERT_EQ(std::get(Elem).Reg.ViewType, RegisterType::SReg); ASSERT_EQ(std::get(Elem).Reg.Number, 987u); ASSERT_EQ(std::get(Elem).NumDescriptors, 1u); ASSERT_EQ(std::get(Elem).Space, 2u); ASSERT_EQ(std::get(Elem).Offset, DescriptorTableOffsetAppend); ASSERT_EQ(std::get(Elem).Flags, DescriptorRangeFlags::None); Elem = Elements[3]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Type, ClauseType::UAV); ASSERT_EQ(std::get(Elem).Reg.ViewType, RegisterType::UReg); ASSERT_EQ(std::get(Elem).Reg.Number, 4294967294u); ASSERT_EQ(std::get(Elem).NumDescriptors, NumDescriptorsUnbounded); ASSERT_EQ(std::get(Elem).Space, 0u); ASSERT_EQ(std::get(Elem).Offset, DescriptorTableOffsetAppend); ASSERT_EQ(std::get(Elem).Flags, DescriptorRangeFlags::ValidFlags); Elem = Elements[4]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).NumClauses, (uint32_t)4); ASSERT_EQ(std::get(Elem).Visibility, ShaderVisibility::Pixel); // Empty Descriptor Table Elem = Elements[5]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).NumClauses, 0u); ASSERT_EQ(std::get(Elem).Visibility, ShaderVisibility::All); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, ValidSamplerFlagsTest) { // This test will checks we can set the valid enum for Sampler descriptor // range flags const llvm::StringLiteral Source = R"cc( DescriptorTable(Sampler(s0, flags = DESCRIPTORS_VOLATILE)) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test no diagnostics produced Consumer->setNoDiag(); ASSERT_FALSE(Parser.parse()); RootElement Elem = Elements[0]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Type, ClauseType::Sampler); ASSERT_EQ(std::get(Elem).Flags, DescriptorRangeFlags::ValidSamplerFlags); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, ValidParseRootConsantsTest) { const llvm::StringLiteral Source = R"cc( RootConstants(num32BitConstants = 1, b0), RootConstants(b42, space = 3, num32BitConstants = 4294967295, visibility = SHADER_VISIBILITY_HULL ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test no diagnostics produced Consumer->setNoDiag(); ASSERT_FALSE(Parser.parse()); ASSERT_EQ(Elements.size(), 2u); RootElement Elem = Elements[0]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Num32BitConstants, 1u); ASSERT_EQ(std::get(Elem).Reg.ViewType, RegisterType::BReg); ASSERT_EQ(std::get(Elem).Reg.Number, 0u); ASSERT_EQ(std::get(Elem).Space, 0u); ASSERT_EQ(std::get(Elem).Visibility, ShaderVisibility::All); Elem = Elements[1]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem).Num32BitConstants, 4294967295u); ASSERT_EQ(std::get(Elem).Reg.ViewType, RegisterType::BReg); ASSERT_EQ(std::get(Elem).Reg.Number, 42u); ASSERT_EQ(std::get(Elem).Space, 3u); ASSERT_EQ(std::get(Elem).Visibility, ShaderVisibility::Hull); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, ValidParseRootFlagsTest) { const llvm::StringLiteral Source = R"cc( RootFlags(), RootFlags(0), RootFlags( deny_domain_shader_root_access | deny_pixel_shader_root_access | local_root_signature | cbv_srv_uav_heap_directly_indexed | deny_amplification_shader_root_access | deny_geometry_shader_root_access | deny_hull_shader_root_access | deny_mesh_shader_root_access | allow_stream_output | sampler_heap_directly_indexed | allow_input_assembler_input_layout | deny_vertex_shader_root_access ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test no diagnostics produced Consumer->setNoDiag(); ASSERT_FALSE(Parser.parse()); ASSERT_EQ(Elements.size(), 3u); RootElement Elem = Elements[0]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem), RootFlags::None); Elem = Elements[1]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem), RootFlags::None); Elem = Elements[2]; ASSERT_TRUE(std::holds_alternative(Elem)); ASSERT_EQ(std::get(Elem), RootFlags::ValidFlags); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, ValidTrailingCommaTest) { // This test will checks we can handling trailing commas ',' const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV(b0, ), SRV(t42), ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test no diagnostics produced Consumer->setNoDiag(); ASSERT_FALSE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } // Invalid Parser Tests TEST_F(ParseHLSLRootSignatureTest, InvalidParseUnexpectedTokenTest) { const llvm::StringLiteral Source = R"cc( DescriptorTable() space )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_unexpected_end_of_params); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidParseInvalidTokenTest) { const llvm::StringLiteral Source = R"cc( notAnIdentifier )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced - invalid token Consumer->setExpected(diag::err_hlsl_unexpected_end_of_params); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidParseUnexpectedEndOfStreamTest) { const llvm::StringLiteral Source = R"cc( DescriptorTable )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced - end of stream Consumer->setExpected(diag::err_expected_after); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidMissingDTParameterTest) { // This test will check that the parsing fails due a mandatory // parameter (register) not being specified const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV() ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_missing_param); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidMissingRCParameterTest) { // This test will check that the parsing fails due a mandatory // parameter (num32BitConstants) not being specified const llvm::StringLiteral Source = R"cc( RootConstants(b0) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_missing_param); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidRepeatedMandatoryDTParameterTest) { // This test will check that the parsing fails due the same mandatory // parameter being specified multiple times const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV(b32, b84) ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_repeat_param); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidRepeatedMandatoryRCParameterTest) { // This test will check that the parsing fails due the same mandatory // parameter being specified multiple times const llvm::StringLiteral Source = R"cc( RootConstants(num32BitConstants = 32, num32BitConstants = 24) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_repeat_param); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidRepeatedOptionalDTParameterTest) { // This test will check that the parsing fails due the same optional // parameter being specified multiple times const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV(space = 2, space = 0) ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_repeat_param); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidRepeatedOptionalRCParameterTest) { // This test will check that the parsing fails due the same optional // parameter being specified multiple times const llvm::StringLiteral Source = R"cc( RootConstants( visibility = Shader_Visibility_All, b0, num32BitConstants = 1, visibility = Shader_Visibility_Pixel ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_repeat_param); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidLexOverflowedNumberTest) { // This test will check that the lexing fails due to an integer overflow const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV(b4294967296) ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_number_literal_overflow); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } TEST_F(ParseHLSLRootSignatureTest, InvalidNonZeroFlagsTest) { // This test will check that parsing fails when a non-zero integer literal // is given to flags const llvm::StringLiteral Source = R"cc( DescriptorTable( CBV(b0, flags = 3) ) )cc"; TrivialModuleLoader ModLoader; auto PP = createPP(Source, ModLoader); auto TokLoc = SourceLocation(); hlsl::RootSignatureLexer Lexer(Source, TokLoc); SmallVector Elements; hlsl::RootSignatureParser Parser(Elements, Lexer, *PP); // Test correct diagnostic produced Consumer->setExpected(diag::err_hlsl_rootsig_non_zero_flag); ASSERT_TRUE(Parser.parse()); ASSERT_TRUE(Consumer->isSatisfied()); } } // anonymous namespace