The class factorize the instruction building process.
Def-use manager analysis can be updated on the fly to maintain coherency.
To be updated to take into account more analysis.
--- /dev/null
+// 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.
+
+#ifndef LIBSPIRV_OPT_IR_BUILDER_H_
+#define LIBSPIRV_OPT_IR_BUILDER_H_
+
+#include "opt/basic_block.h"
+#include "opt/instruction.h"
+#include "opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// In SPIR-V, ids are encoded as uint16_t, this id is guaranteed to be always
+// invalid.
+constexpr uint32_t kInvalidId = std::numeric_limits<uint32_t>::max();
+
+// Helper class to abstract instruction construction and insertion.
+// |AnalysesToPreserve| asks the InstructionBuilder to preserve requested
+// analyses.
+// Supported analyses:
+// - Def-use analysis
+// - Instruction to block analysis
+template <ir::IRContext::Analysis AnalysesToPreserve =
+ ir::IRContext::kAnalysisNone>
+class InstructionBuilder {
+ static_assert(!(AnalysesToPreserve &
+ ~(ir::IRContext::kAnalysisDefUse |
+ ir::IRContext::kAnalysisInstrToBlockMapping)),
+ "There some unsupported analyses");
+
+ public:
+ using InsertionPointTy = spvtools::ir::BasicBlock::iterator;
+
+ // Creates an InstructionBuilder, all new instructions will be inserted before
+ // the instruction |insert_before|.
+ InstructionBuilder(ir::IRContext* context, ir::Instruction* insert_before)
+ : InstructionBuilder(context, context->get_instr_block(insert_before),
+ InsertionPointTy(insert_before)) {}
+
+ // Creates an InstructionBuilder, all new instructions will be inserted at the
+ // end of the basic block |parent_block|.
+ InstructionBuilder(ir::IRContext* context, ir::BasicBlock* parent_block)
+ : InstructionBuilder(context, parent_block, parent_block->end()) {}
+
+ // Creates a new selection merge instruction.
+ // The id |merge_id| is the merge basic block id.
+ ir::Instruction* AddSelectionMerge(
+ uint32_t merge_id,
+ uint32_t selection_control = SpvSelectionControlMaskNone) {
+ std::unique_ptr<ir::Instruction> new_branch_merge(new ir::Instruction(
+ GetContext(), SpvOpSelectionMerge, 0, 0,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_SELECTION_CONTROL,
+ {selection_control}}}));
+ return AddInstruction(std::move(new_branch_merge));
+ }
+
+ // Creates a new branch instruction to |label_id|.
+ // Note that the user must make sure the final basic block is
+ // well formed.
+ ir::Instruction* AddBranch(uint32_t label_id) {
+ std::unique_ptr<ir::Instruction> new_branch(new ir::Instruction(
+ GetContext(), SpvOpBranch, 0, 0,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
+ return AddInstruction(std::move(new_branch));
+ }
+
+ // Creates a new conditional instruction and the associated selection merge
+ // instruction if requested.
+ // The id |cond_id| is the id of the condition instruction, must be of
+ // type bool.
+ // The id |true_id| is the id of the basic block to branch to if the condition
+ // is true.
+ // The id |false_id| is the id of the basic block to branch to if the
+ // condition is false.
+ // The id |merge_id| is the id of the merge basic block for the selection
+ // merge instruction. If |merge_id| equals kInvalidId then no selection merge
+ // instruction will be created.
+ // The value |selection_control| is the selection control flag for the
+ // selection merge instruction.
+ // Note that the user must make sure the final basic block is
+ // well formed.
+ ir::Instruction* AddConditionalBranch(
+ uint32_t cond_id, uint32_t true_id, uint32_t false_id,
+ uint32_t merge_id = kInvalidId,
+ uint32_t selection_control = SpvSelectionControlMaskNone) {
+ if (merge_id != kInvalidId) {
+ AddSelectionMerge(merge_id, selection_control);
+ }
+ std::unique_ptr<ir::Instruction> new_branch(new ir::Instruction(
+ GetContext(), SpvOpBranchConditional, 0, 0,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}}));
+ return AddInstruction(std::move(new_branch));
+ }
+
+ // Creates a phi instruction.
+ // The id |type| must be the id of the phi instruction's type.
+ // The vector |incomings| must be a sequence of pairs of <definition id,
+ // parent id>.
+ ir::Instruction* AddPhi(uint32_t type,
+ const std::vector<uint32_t>& incomings) {
+ assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected");
+ std::vector<ir::Operand> phi_ops;
+ for (size_t i = 0; i < incomings.size(); i++) {
+ phi_ops.push_back({SPV_OPERAND_TYPE_ID, {incomings[i]}});
+ }
+ std::unique_ptr<ir::Instruction> phi_inst(new ir::Instruction(
+ GetContext(), SpvOpPhi, type, GetContext()->TakeNextId(), phi_ops));
+ return AddInstruction(std::move(phi_inst));
+ }
+
+ // Inserts the new instruction before the insertion point.
+ ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
+ ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
+ UpdateInstrToBlockMapping(insn_ptr);
+ UpdateDefUseMgr(insn_ptr);
+ return insn_ptr;
+ }
+
+ // Returns the insertion point iterator.
+ InsertionPointTy GetInsertPoint() { return insert_before_; }
+
+ // Returns the context which instructions are constructed for.
+ ir::IRContext* GetContext() const { return context_; }
+
+ // Returns the set of preserved analyses.
+ inline static constexpr ir::IRContext::Analysis GetPreservedAnalysis() {
+ return AnalysesToPreserve;
+ }
+
+ private:
+ InstructionBuilder(ir::IRContext* context, ir::BasicBlock* parent,
+ InsertionPointTy insert_before)
+ : context_(context), parent_(parent), insert_before_(insert_before) {}
+
+ // Returns true if the users requested to update |analysis|.
+ inline static constexpr bool IsAnalysisUpdateRequested(
+ ir::IRContext::Analysis analysis) {
+ return AnalysesToPreserve & analysis;
+ }
+
+ // Updates the def/use manager if the user requested it. If he did not request
+ // an update, this function does nothing.
+ inline void UpdateDefUseMgr(ir::Instruction* insn) {
+ if (IsAnalysisUpdateRequested(ir::IRContext::kAnalysisDefUse))
+ GetContext()->get_def_use_mgr()->AnalyzeInstDefUse(insn);
+ }
+
+ // Updates the instruction to block analysis if the user requested it. If he
+ // did not request an update, this function does nothing.
+ inline void UpdateInstrToBlockMapping(ir::Instruction* insn) {
+ if (IsAnalysisUpdateRequested(
+ ir::IRContext::kAnalysisInstrToBlockMapping) &&
+ parent_)
+ GetContext()->set_instr_block(insn, parent_);
+ }
+
+ ir::IRContext* context_;
+ ir::BasicBlock* parent_;
+ InsertionPointTy insert_before_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_IR_BUILDER_H_
kAnalysisEnd = 1 << 6
};
- friend inline Analysis operator|(Analysis lhs, Analysis rhs);
+ friend inline constexpr Analysis operator|(Analysis lhs, Analysis rhs);
friend inline Analysis& operator|=(Analysis& lhs, Analysis rhs);
- friend inline Analysis operator<<(Analysis a, int shift);
+ friend inline constexpr Analysis operator<<(Analysis a, int shift);
friend inline Analysis& operator<<=(Analysis& a, int shift);
// Creates an |IRContext| that contains an owned |Module|
std::unique_ptr<opt::analysis::TypeManager> type_mgr_;
};
-inline ir::IRContext::Analysis operator|(ir::IRContext::Analysis lhs,
- ir::IRContext::Analysis rhs) {
+inline constexpr ir::IRContext::Analysis operator|(
+ ir::IRContext::Analysis lhs, ir::IRContext::Analysis rhs) {
return static_cast<ir::IRContext::Analysis>(static_cast<int>(lhs) |
static_cast<int>(rhs));
}
return lhs;
}
-inline ir::IRContext::Analysis operator<<(ir::IRContext::Analysis a,
- int shift) {
+inline constexpr ir::IRContext::Analysis operator<<(ir::IRContext::Analysis a,
+ int shift) {
return static_cast<ir::IRContext::Analysis>(static_cast<int>(a) << shift);
}
SRCS workaround1209_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)
+
+add_spvtools_unittest(TARGET ir_builder
+ SRCS ir_builder.cpp
+ LIBS SPIRV-Tools-opt
+)
--- /dev/null
+// 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+#ifdef SPIRV_EFFCEE
+#include "effcee/effcee.h"
+#endif
+
+#include "opt/basic_block.h"
+#include "opt/ir_builder.h"
+
+#include "opt/build_module.h"
+#include "opt/instruction.h"
+#include "opt/type_manager.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace {
+
+using namespace spvtools;
+using ir::IRContext;
+using Analysis = IRContext::Analysis;
+
+#ifdef SPIRV_EFFCEE
+
+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, ir::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<ir::IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ ir::BasicBlock* bb = context->cfg()->block(18);
+
+ // Build managers.
+ context->get_def_use_mgr();
+ context->get_instr_block(nullptr);
+
+ opt::InstructionBuilder<> builder(context.get(), &*bb->begin());
+ ir::Instruction* phi1 = builder.AddPhi(7, {9, 14});
+ ir::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<ir::IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build managers.
+ context->get_def_use_mgr();
+ context->get_instr_block(nullptr);
+
+ ir::BasicBlock* bb = context->cfg()->block(18);
+ opt::InstructionBuilder<ir::IRContext::kAnalysisDefUse |
+ ir::IRContext::kAnalysisInstrToBlockMapping>
+ builder(context.get(), &*bb->begin());
+ ir::Instruction* phi1 = builder.AddPhi(7, {9, 14});
+ ir::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 Function %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<ir::IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ ir::Function& fn = *context->module()->begin();
+
+ ir::BasicBlock& bb_merge = *fn.begin();
+
+ fn.begin().InsertBefore(std::unique_ptr<ir::BasicBlock>(
+ new ir::BasicBlock(std::unique_ptr<ir::Instruction>(new ir::Instruction(
+ context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
+ ir::BasicBlock& bb_true = *fn.begin();
+ {
+ opt::InstructionBuilder<> builder(context.get(), &*bb_true.begin());
+ builder.AddBranch(bb_merge.id());
+ }
+
+ fn.begin().InsertBefore(std::unique_ptr<ir::BasicBlock>(
+ new ir::BasicBlock(std::unique_ptr<ir::Instruction>(new ir::Instruction(
+ context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
+ ir::BasicBlock& bb_cond = *fn.begin();
+
+ opt::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());
+ }
+}
+
+#endif // SPIRV_EFFCEE
+
+} // anonymous namespace