| // Copyright (c) 2017 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 "source/opt/propagator.h" |
| |
| #include <map> |
| #include <memory> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "source/opt/build_module.h" |
| #include "source/opt/cfg.h" |
| #include "source/opt/ir_context.h" |
| |
| namespace spvtools { |
| namespace opt { |
| namespace { |
| |
| using ::testing::UnorderedElementsAre; |
| |
| class PropagatorTest : public testing::Test { |
| protected: |
| virtual void TearDown() { |
| ctx_.reset(nullptr); |
| values_.clear(); |
| values_vec_.clear(); |
| } |
| |
| void Assemble(const std::string& input) { |
| ctx_ = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input); |
| ASSERT_NE(nullptr, ctx_) << "Assembling failed for shader:\n" |
| << input << "\n"; |
| } |
| |
| bool Propagate(const SSAPropagator::VisitFunction& visit_fn) { |
| SSAPropagator propagator(ctx_.get(), visit_fn); |
| bool retval = false; |
| for (auto& fn : *ctx_->module()) { |
| retval |= propagator.Run(&fn); |
| } |
| return retval; |
| } |
| |
| const std::vector<uint32_t>& GetValues() { |
| values_vec_.clear(); |
| for (const auto& it : values_) { |
| values_vec_.push_back(it.second); |
| } |
| return values_vec_; |
| } |
| |
| std::unique_ptr<IRContext> ctx_; |
| std::map<uint32_t, uint32_t> values_; |
| std::vector<uint32_t> values_vec_; |
| }; |
| |
| TEST_F(PropagatorTest, LocalPropagate) { |
| const std::string spv_asm = R"( |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" %outparm |
| OpExecutionMode %main OriginUpperLeft |
| OpSource GLSL 450 |
| OpName %main "main" |
| OpName %x "x" |
| OpName %y "y" |
| OpName %z "z" |
| OpName %outparm "outparm" |
| OpDecorate %outparm Location 0 |
| %void = OpTypeVoid |
| %3 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %_ptr_Function_int = OpTypePointer Function %int |
| %int_4 = OpConstant %int 4 |
| %int_3 = OpConstant %int 3 |
| %int_1 = OpConstant %int 1 |
| %_ptr_Output_int = OpTypePointer Output %int |
| %outparm = OpVariable %_ptr_Output_int Output |
| %main = OpFunction %void None %3 |
| %5 = OpLabel |
| %x = OpVariable %_ptr_Function_int Function |
| %y = OpVariable %_ptr_Function_int Function |
| %z = OpVariable %_ptr_Function_int Function |
| OpStore %x %int_4 |
| OpStore %y %int_3 |
| OpStore %z %int_1 |
| %20 = OpLoad %int %z |
| OpStore %outparm %20 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| Assemble(spv_asm); |
| |
| const auto visit_fn = [this](Instruction* instr, BasicBlock** dest_bb) { |
| *dest_bb = nullptr; |
| if (instr->opcode() == spv::Op::OpStore) { |
| uint32_t lhs_id = instr->GetSingleWordOperand(0); |
| uint32_t rhs_id = instr->GetSingleWordOperand(1); |
| Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id); |
| if (rhs_def->opcode() == spv::Op::OpConstant) { |
| uint32_t val = rhs_def->GetSingleWordOperand(2); |
| values_[lhs_id] = val; |
| return SSAPropagator::kInteresting; |
| } |
| } |
| return SSAPropagator::kVarying; |
| }; |
| |
| EXPECT_TRUE(Propagate(visit_fn)); |
| EXPECT_THAT(GetValues(), UnorderedElementsAre(4, 3, 1)); |
| } |
| |
| TEST_F(PropagatorTest, PropagateThroughPhis) { |
| const std::string spv_asm = R"( |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" %x %outparm |
| OpExecutionMode %main OriginUpperLeft |
| OpSource GLSL 450 |
| OpName %main "main" |
| OpName %x "x" |
| OpName %outparm "outparm" |
| OpDecorate %x Flat |
| OpDecorate %x Location 0 |
| OpDecorate %outparm Location 0 |
| %void = OpTypeVoid |
| %3 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %bool = OpTypeBool |
| %_ptr_Function_int = OpTypePointer Function %int |
| %int_4 = OpConstant %int 4 |
| %int_3 = OpConstant %int 3 |
| %int_1 = OpConstant %int 1 |
| %_ptr_Input_int = OpTypePointer Input %int |
| %x = OpVariable %_ptr_Input_int Input |
| %_ptr_Output_int = OpTypePointer Output %int |
| %outparm = OpVariable %_ptr_Output_int Output |
| %main = OpFunction %void None %3 |
| %4 = OpLabel |
| %5 = OpLoad %int %x |
| %6 = OpSGreaterThan %bool %5 %int_3 |
| OpSelectionMerge %25 None |
| OpBranchConditional %6 %22 %23 |
| %22 = OpLabel |
| %7 = OpLoad %int %int_4 |
| OpBranch %25 |
| %23 = OpLabel |
| %8 = OpLoad %int %int_4 |
| OpBranch %25 |
| %25 = OpLabel |
| %35 = OpPhi %int %7 %22 %8 %23 |
| OpStore %outparm %35 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| Assemble(spv_asm); |
| |
| Instruction* phi_instr = nullptr; |
| const auto visit_fn = [this, &phi_instr](Instruction* instr, |
| BasicBlock** dest_bb) { |
| *dest_bb = nullptr; |
| if (instr->opcode() == spv::Op::OpLoad) { |
| uint32_t rhs_id = instr->GetSingleWordOperand(2); |
| Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id); |
| if (rhs_def->opcode() == spv::Op::OpConstant) { |
| uint32_t val = rhs_def->GetSingleWordOperand(2); |
| values_[instr->result_id()] = val; |
| return SSAPropagator::kInteresting; |
| } |
| } else if (instr->opcode() == spv::Op::OpPhi) { |
| phi_instr = instr; |
| SSAPropagator::PropStatus retval; |
| for (uint32_t i = 2; i < instr->NumOperands(); i += 2) { |
| uint32_t phi_arg_id = instr->GetSingleWordOperand(i); |
| auto it = values_.find(phi_arg_id); |
| if (it != values_.end()) { |
| EXPECT_EQ(it->second, 4u); |
| retval = SSAPropagator::kInteresting; |
| values_[instr->result_id()] = it->second; |
| } else { |
| retval = SSAPropagator::kNotInteresting; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| return SSAPropagator::kVarying; |
| }; |
| |
| EXPECT_TRUE(Propagate(visit_fn)); |
| |
| // The propagator should've concluded that the Phi instruction has a constant |
| // value of 4. |
| EXPECT_NE(phi_instr, nullptr); |
| EXPECT_EQ(values_[phi_instr->result_id()], 4u); |
| |
| EXPECT_THAT(GetValues(), UnorderedElementsAre(4u, 4u, 4u)); |
| } |
| |
| } // namespace |
| } // namespace opt |
| } // namespace spvtools |