| // Copyright (c) 2018 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "effcee/effcee.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "source/opt/basic_block.h" |
| #include "source/opt/build_module.h" |
| #include "source/opt/instruction.h" |
| #include "source/opt/ir_builder.h" |
| #include "source/opt/type_manager.h" |
| #include "spirv-tools/libspirv.hpp" |
| |
| namespace spvtools { |
| namespace opt { |
| namespace { |
| |
| using Analysis = IRContext::Analysis; |
| using IRBuilderTest = ::testing::Test; |
| |
| bool Validate(const std::vector<uint32_t>& bin) { |
| spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; |
| spv_context spvContext = spvContextCreate(target_env); |
| spv_diagnostic diagnostic = nullptr; |
| spv_const_binary_t binary = {bin.data(), bin.size()}; |
| spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); |
| if (error != 0) spvDiagnosticPrint(diagnostic); |
| spvDiagnosticDestroy(diagnostic); |
| spvContextDestroy(spvContext); |
| return error == 0; |
| } |
| |
| void Match(const std::string& original, IRContext* context, |
| bool do_validation = true) { |
| std::vector<uint32_t> bin; |
| context->module()->ToBinary(&bin, true); |
| if (do_validation) { |
| EXPECT_TRUE(Validate(bin)); |
| } |
| std::string assembly; |
| SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); |
| EXPECT_TRUE( |
| tools.Disassemble(bin, &assembly, SpirvTools::kDefaultDisassembleOption)) |
| << "Disassembling failed for shader:\n" |
| << assembly << std::endl; |
| auto match_result = effcee::Match(assembly, original); |
| EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) |
| << match_result.message() << "\nChecking result:\n" |
| << assembly; |
| } |
| |
| TEST_F(IRBuilderTest, TestInsnAddition) { |
| const std::string text = R"( |
| ; CHECK: %18 = OpLabel |
| ; CHECK: OpPhi %int %int_0 %14 |
| ; CHECK: OpPhi %bool %16 %14 |
| ; CHECK: OpBranch %17 |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %2 "main" %3 |
| OpExecutionMode %2 OriginUpperLeft |
| OpSource GLSL 330 |
| OpName %2 "main" |
| OpName %4 "i" |
| OpName %3 "c" |
| OpDecorate %3 Location 0 |
| %5 = OpTypeVoid |
| %6 = OpTypeFunction %5 |
| %7 = OpTypeInt 32 1 |
| %8 = OpTypePointer Function %7 |
| %9 = OpConstant %7 0 |
| %10 = OpTypeBool |
| %11 = OpTypeFloat 32 |
| %12 = OpTypeVector %11 4 |
| %13 = OpTypePointer Output %12 |
| %3 = OpVariable %13 Output |
| %2 = OpFunction %5 None %6 |
| %14 = OpLabel |
| %4 = OpVariable %8 Function |
| OpStore %4 %9 |
| %15 = OpLoad %7 %4 |
| %16 = OpINotEqual %10 %15 %9 |
| OpSelectionMerge %17 None |
| OpBranchConditional %16 %18 %17 |
| %18 = OpLabel |
| OpBranch %17 |
| %17 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| { |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| |
| BasicBlock* bb = context->cfg()->block(18); |
| |
| // Build managers. |
| context->get_def_use_mgr(); |
| context->get_instr_block(nullptr); |
| |
| InstructionBuilder builder(context.get(), &*bb->begin()); |
| Instruction* phi1 = builder.AddPhi(7, {9, 14}); |
| Instruction* phi2 = builder.AddPhi(10, {16, 14}); |
| |
| // Make sure the InstructionBuilder did not update the def/use manager. |
| EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr); |
| EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr); |
| EXPECT_EQ(context->get_instr_block(phi1), nullptr); |
| EXPECT_EQ(context->get_instr_block(phi2), nullptr); |
| |
| Match(text, context.get()); |
| } |
| |
| { |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| |
| // Build managers. |
| context->get_def_use_mgr(); |
| context->get_instr_block(nullptr); |
| |
| BasicBlock* bb = context->cfg()->block(18); |
| InstructionBuilder builder( |
| context.get(), &*bb->begin(), |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); |
| Instruction* phi1 = builder.AddPhi(7, {9, 14}); |
| Instruction* phi2 = builder.AddPhi(10, {16, 14}); |
| |
| // Make sure InstructionBuilder updated the def/use manager |
| EXPECT_NE(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr); |
| EXPECT_NE(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr); |
| EXPECT_NE(context->get_instr_block(phi1), nullptr); |
| EXPECT_NE(context->get_instr_block(phi2), nullptr); |
| |
| Match(text, context.get()); |
| } |
| } |
| |
| TEST_F(IRBuilderTest, TestCondBranchAddition) { |
| const std::string text = R"( |
| ; CHECK: %main = OpFunction %void None %6 |
| ; CHECK-NEXT: %15 = OpLabel |
| ; CHECK-NEXT: OpSelectionMerge %13 None |
| ; CHECK-NEXT: OpBranchConditional %true %14 %13 |
| ; CHECK-NEXT: %14 = OpLabel |
| ; CHECK-NEXT: OpBranch %13 |
| ; CHECK-NEXT: %13 = OpLabel |
| ; CHECK-NEXT: OpReturn |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %2 "main" %3 |
| OpExecutionMode %2 OriginUpperLeft |
| OpSource GLSL 330 |
| OpName %2 "main" |
| OpName %4 "i" |
| OpName %3 "c" |
| OpDecorate %3 Location 0 |
| %5 = OpTypeVoid |
| %6 = OpTypeFunction %5 |
| %7 = OpTypeBool |
| %8 = OpTypePointer Private %7 |
| %9 = OpConstantTrue %7 |
| %10 = OpTypeFloat 32 |
| %11 = OpTypeVector %10 4 |
| %12 = OpTypePointer Output %11 |
| %3 = OpVariable %12 Output |
| %4 = OpVariable %8 Private |
| %2 = OpFunction %5 None %6 |
| %13 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| { |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| |
| Function& fn = *context->module()->begin(); |
| |
| BasicBlock& bb_merge = *fn.begin(); |
| |
| // TODO(1841): Handle id overflow. |
| fn.begin().InsertBefore(std::unique_ptr<BasicBlock>( |
| new BasicBlock(std::unique_ptr<Instruction>(new Instruction( |
| context.get(), spv::Op::OpLabel, 0, context->TakeNextId(), {}))))); |
| BasicBlock& bb_true = *fn.begin(); |
| { |
| InstructionBuilder builder(context.get(), &*bb_true.begin()); |
| builder.AddBranch(bb_merge.id()); |
| } |
| |
| // TODO(1841): Handle id overflow. |
| fn.begin().InsertBefore(std::unique_ptr<BasicBlock>( |
| new BasicBlock(std::unique_ptr<Instruction>(new Instruction( |
| context.get(), spv::Op::OpLabel, 0, context->TakeNextId(), {}))))); |
| BasicBlock& bb_cond = *fn.begin(); |
| |
| InstructionBuilder builder(context.get(), &bb_cond); |
| // This also test consecutive instruction insertion: merge selection + |
| // branch. |
| builder.AddConditionalBranch(9, bb_true.id(), bb_merge.id(), bb_merge.id()); |
| |
| Match(text, context.get()); |
| } |
| } |
| |
| TEST_F(IRBuilderTest, AddSelect) { |
| const std::string text = R"( |
| ; CHECK: [[bool:%\w+]] = OpTypeBool |
| ; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 |
| ; CHECK: [[true:%\w+]] = OpConstantTrue [[bool]] |
| ; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0 |
| ; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1 |
| ; CHECK: OpSelect [[uint]] [[true]] [[u0]] [[u1]] |
| OpCapability Kernel |
| OpCapability Linkage |
| OpMemoryModel Logical OpenCL |
| %1 = OpTypeVoid |
| %2 = OpTypeBool |
| %3 = OpTypeInt 32 0 |
| %4 = OpConstantTrue %2 |
| %5 = OpConstant %3 0 |
| %6 = OpConstant %3 1 |
| %7 = OpTypeFunction %1 |
| %8 = OpFunction %1 None %7 |
| %9 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| EXPECT_NE(nullptr, context); |
| |
| InstructionBuilder builder(context.get(), |
| &*context->module()->begin()->begin()->begin()); |
| EXPECT_NE(nullptr, builder.AddSelect(3u, 4u, 5u, 6u)); |
| |
| Match(text, context.get()); |
| } |
| |
| TEST_F(IRBuilderTest, AddCompositeConstruct) { |
| const std::string text = R"( |
| ; CHECK: [[uint:%\w+]] = OpTypeInt |
| ; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0 |
| ; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1 |
| ; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]] [[uint]] [[uint]] [[uint]] |
| ; CHECK: OpCompositeConstruct [[struct]] [[u0]] [[u1]] [[u1]] [[u0]] |
| OpCapability Kernel |
| OpCapability Linkage |
| OpMemoryModel Logical OpenCL |
| %1 = OpTypeVoid |
| %2 = OpTypeInt 32 0 |
| %3 = OpConstant %2 0 |
| %4 = OpConstant %2 1 |
| %5 = OpTypeStruct %2 %2 %2 %2 |
| %6 = OpTypeFunction %1 |
| %7 = OpFunction %1 None %6 |
| %8 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| EXPECT_NE(nullptr, context); |
| |
| InstructionBuilder builder(context.get(), |
| &*context->module()->begin()->begin()->begin()); |
| std::vector<uint32_t> ids = {3u, 4u, 4u, 3u}; |
| EXPECT_NE(nullptr, builder.AddCompositeConstruct(5u, ids)); |
| |
| Match(text, context.get()); |
| } |
| |
| TEST_F(IRBuilderTest, ConstantAdder) { |
| const std::string text = R"( |
| ; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 |
| ; CHECK: OpConstant [[uint]] 13 |
| ; CHECK: [[sint:%\w+]] = OpTypeInt 32 1 |
| ; CHECK: OpConstant [[sint]] -1 |
| ; CHECK: OpConstant [[uint]] 1 |
| ; CHECK: OpConstant [[sint]] 34 |
| ; CHECK: OpConstant [[uint]] 0 |
| ; CHECK: OpConstant [[sint]] 0 |
| OpCapability Shader |
| OpCapability Linkage |
| OpMemoryModel Logical GLSL450 |
| %1 = OpTypeVoid |
| %2 = OpTypeFunction %1 |
| %3 = OpFunction %1 None %2 |
| %4 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| EXPECT_NE(nullptr, context); |
| |
| InstructionBuilder builder(context.get(), |
| &*context->module()->begin()->begin()->begin()); |
| EXPECT_NE(nullptr, builder.GetUintConstant(13)); |
| EXPECT_NE(nullptr, builder.GetSintConstant(-1)); |
| |
| // Try adding the same constants again to make sure they aren't added. |
| EXPECT_NE(nullptr, builder.GetUintConstant(13)); |
| EXPECT_NE(nullptr, builder.GetSintConstant(-1)); |
| |
| // Try adding different constants to make sure the type is reused. |
| EXPECT_NE(nullptr, builder.GetUintConstant(1)); |
| EXPECT_NE(nullptr, builder.GetSintConstant(34)); |
| |
| // Try adding 0 as both signed and unsigned. |
| EXPECT_NE(nullptr, builder.GetUintConstant(0)); |
| EXPECT_NE(nullptr, builder.GetSintConstant(0)); |
| |
| Match(text, context.get()); |
| } |
| |
| TEST_F(IRBuilderTest, ConstantAdderTypeAlreadyExists) { |
| const std::string text = R"( |
| ; CHECK: OpConstant %uint 13 |
| ; CHECK: OpConstant %int -1 |
| ; CHECK: OpConstant %uint 1 |
| ; CHECK: OpConstant %int 34 |
| ; CHECK: OpConstant %uint 0 |
| ; CHECK: OpConstant %int 0 |
| OpCapability Shader |
| OpCapability Linkage |
| OpMemoryModel Logical GLSL450 |
| %1 = OpTypeVoid |
| %uint = OpTypeInt 32 0 |
| %int = OpTypeInt 32 1 |
| %4 = OpTypeFunction %1 |
| %5 = OpFunction %1 None %4 |
| %6 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| EXPECT_NE(nullptr, context); |
| |
| InstructionBuilder builder(context.get(), |
| &*context->module()->begin()->begin()->begin()); |
| Instruction* const_1 = builder.GetUintConstant(13); |
| Instruction* const_2 = builder.GetSintConstant(-1); |
| |
| EXPECT_NE(nullptr, const_1); |
| EXPECT_NE(nullptr, const_2); |
| |
| // Try adding the same constants again to make sure they aren't added. |
| EXPECT_EQ(const_1, builder.GetUintConstant(13)); |
| EXPECT_EQ(const_2, builder.GetSintConstant(-1)); |
| |
| Instruction* const_3 = builder.GetUintConstant(1); |
| Instruction* const_4 = builder.GetSintConstant(34); |
| |
| // Try adding different constants to make sure the type is reused. |
| EXPECT_NE(nullptr, const_3); |
| EXPECT_NE(nullptr, const_4); |
| |
| Instruction* const_5 = builder.GetUintConstant(0); |
| Instruction* const_6 = builder.GetSintConstant(0); |
| |
| // Try adding 0 as both signed and unsigned. |
| EXPECT_NE(nullptr, const_5); |
| EXPECT_NE(nullptr, const_6); |
| |
| // They have the same value but different types so should be unique. |
| EXPECT_NE(const_5, const_6); |
| |
| // Check the types are correct. |
| uint32_t type_id_unsigned = const_1->GetSingleWordOperand(0); |
| uint32_t type_id_signed = const_2->GetSingleWordOperand(0); |
| |
| EXPECT_NE(type_id_unsigned, type_id_signed); |
| |
| EXPECT_EQ(const_3->GetSingleWordOperand(0), type_id_unsigned); |
| EXPECT_EQ(const_5->GetSingleWordOperand(0), type_id_unsigned); |
| |
| EXPECT_EQ(const_4->GetSingleWordOperand(0), type_id_signed); |
| EXPECT_EQ(const_6->GetSingleWordOperand(0), type_id_signed); |
| |
| Match(text, context.get()); |
| } |
| |
| TEST_F(IRBuilderTest, AccelerationStructureNV) { |
| const std::string text = R"( |
| ; CHECK: OpTypeAccelerationStructureKHR |
| OpCapability Shader |
| OpCapability RayTracingNV |
| OpExtension "SPV_NV_ray_tracing" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %8 "main" |
| OpExecutionMode %8 OriginUpperLeft |
| %1 = OpTypeVoid |
| %2 = OpTypeBool |
| %3 = OpTypeAccelerationStructureNV |
| %7 = OpTypeFunction %1 |
| %8 = OpFunction %1 None %7 |
| %9 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| std::unique_ptr<IRContext> context = |
| BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); |
| EXPECT_NE(nullptr, context); |
| |
| InstructionBuilder builder(context.get(), |
| &*context->module()->begin()->begin()->begin()); |
| Match(text, context.get()); |
| } |
| |
| } // namespace |
| } // namespace opt |
| } // namespace spvtools |