// 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 <map>
#include <memory>
#include <string>
#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"
#include "source/opt/pass.h"
#include "source/opt/propagator.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() == SpvOpStore) {
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() == SpvOpConstant) {
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() == SpvOpLoad) {
uint32_t rhs_id = instr->GetSingleWordOperand(2);
Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id);
if (rhs_def->opcode() == SpvOpConstant) {
uint32_t val = rhs_def->GetSingleWordOperand(2);
values_[instr->result_id()] = val;
return SSAPropagator::kInteresting;
}
} else if (instr->opcode() == SpvOpPhi) {
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