// 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 SOURCE_OPT_IR_BUILDER_H_ #define SOURCE_OPT_IR_BUILDER_H_ #include #include #include #include #include "source/opt/basic_block.h" #include "source/opt/constants.h" #include "source/opt/instruction.h" #include "source/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::max(); // Helper class to abstract instruction construction and insertion. // The instruction builder can preserve the following analyses (specified via // the constructors): // - Def-use analysis // - Instruction to block analysis class InstructionBuilder { public: using InsertionPointTy = BasicBlock::iterator; // Creates an InstructionBuilder, all new instructions will be inserted before // the instruction |insert_before|. InstructionBuilder( IRContext* context, Instruction* insert_before, IRContext::Analysis preserved_analyses = IRContext::kAnalysisNone) : InstructionBuilder(context, context->get_instr_block(insert_before), InsertionPointTy(insert_before), preserved_analyses) {} // Creates an InstructionBuilder, all new instructions will be inserted at the // end of the basic block |parent_block|. InstructionBuilder( IRContext* context, BasicBlock* parent_block, IRContext::Analysis preserved_analyses = IRContext::kAnalysisNone) : InstructionBuilder(context, parent_block, parent_block->end(), preserved_analyses) {} Instruction* AddNullaryOp(uint32_t type_id, spv::Op opcode) { uint32_t result_id = 0; if (type_id != 0) { result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } } std::unique_ptr new_inst( new Instruction(GetContext(), opcode, type_id, result_id, {})); return AddInstruction(std::move(new_inst)); } Instruction* AddUnaryOp(uint32_t type_id, spv::Op opcode, uint32_t operand1) { uint32_t result_id = 0; if (type_id != 0) { result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } } std::unique_ptr newUnOp(new Instruction( GetContext(), opcode, type_id, result_id, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}})); return AddInstruction(std::move(newUnOp)); } Instruction* AddBinaryOp(uint32_t type_id, spv::Op opcode, uint32_t operand1, uint32_t operand2) { uint32_t result_id = 0; if (type_id != 0) { result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } } std::unique_ptr newBinOp(new Instruction( GetContext(), opcode, type_id, opcode == spv::Op::OpStore ? 0 : result_id, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}}})); return AddInstruction(std::move(newBinOp)); } Instruction* AddTernaryOp(uint32_t type_id, spv::Op opcode, uint32_t operand1, uint32_t operand2, uint32_t operand3) { uint32_t result_id = 0; if (type_id != 0) { result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } } std::unique_ptr newTernOp(new Instruction( GetContext(), opcode, type_id, result_id, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}}})); return AddInstruction(std::move(newTernOp)); } Instruction* AddQuadOp(uint32_t type_id, spv::Op opcode, uint32_t operand1, uint32_t operand2, uint32_t operand3, uint32_t operand4) { uint32_t result_id = 0; if (type_id != 0) { result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } } std::unique_ptr newQuadOp(new Instruction( GetContext(), opcode, type_id, result_id, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand2}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand3}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand4}}})); return AddInstruction(std::move(newQuadOp)); } Instruction* AddIdLiteralOp(uint32_t type_id, spv::Op opcode, uint32_t id, uint32_t uliteral) { uint32_t result_id = 0; if (type_id != 0) { result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } } std::unique_ptr newBinOp(new Instruction( GetContext(), opcode, type_id, result_id, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {id}}, {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {uliteral}}})); return AddInstruction(std::move(newBinOp)); } // Creates an N-ary instruction of |opcode|. // |typid| must be the id of the instruction's type. // |operands| must be a sequence of operand ids. // Use |result| for the result id if non-zero. Instruction* AddNaryOp(uint32_t type_id, spv::Op opcode, const std::vector& operands, uint32_t result = 0) { std::vector ops; for (size_t i = 0; i < operands.size(); i++) { ops.push_back({SPV_OPERAND_TYPE_ID, {operands[i]}}); } // TODO(1841): Handle id overflow. std::unique_ptr new_inst(new Instruction( GetContext(), opcode, type_id, result != 0 ? result : GetContext()->TakeNextId(), ops)); return AddInstruction(std::move(new_inst)); } // Creates a new selection merge instruction. // The id |merge_id| is the merge basic block id. Instruction* AddSelectionMerge( uint32_t merge_id, uint32_t selection_control = static_cast( spv::SelectionControlMask::MaskNone)) { std::unique_ptr new_branch_merge(new Instruction( GetContext(), spv::Op::OpSelectionMerge, 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 loop merge instruction. // The id |merge_id| is the basic block id of the merge block. // |continue_id| is the id of the continue block. // |loop_control| are the loop control flags to be added to the instruction. Instruction* AddLoopMerge(uint32_t merge_id, uint32_t continue_id, uint32_t loop_control = static_cast( spv::LoopControlMask::MaskNone)) { std::unique_ptr new_branch_merge(new Instruction( GetContext(), spv::Op::OpLoopMerge, 0, 0, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}}, {spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {loop_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. Instruction* AddBranch(uint32_t label_id) { std::unique_ptr new_branch(new Instruction( GetContext(), spv::Op::OpBranch, 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. Instruction* AddConditionalBranch( uint32_t cond_id, uint32_t true_id, uint32_t false_id, uint32_t merge_id = kInvalidId, uint32_t selection_control = static_cast(spv::SelectionControlMask::MaskNone)) { if (merge_id != kInvalidId) { AddSelectionMerge(merge_id, selection_control); } std::unique_ptr new_branch(new Instruction( GetContext(), spv::Op::OpBranchConditional, 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 new switch instruction and the associated selection merge // instruction if requested. // The id |selector_id| is the id of the selector instruction, must be of // type int. // The id |default_id| is the id of the default basic block to branch to. // The vector |targets| is the pair of literal/branch id. // 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. Instruction* AddSwitch( uint32_t selector_id, uint32_t default_id, const std::vector>& targets, uint32_t merge_id = kInvalidId, uint32_t selection_control = static_cast(spv::SelectionControlMask::MaskNone)) { if (merge_id != kInvalidId) { AddSelectionMerge(merge_id, selection_control); } std::vector operands; operands.emplace_back( Operand{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {selector_id}}); operands.emplace_back( Operand{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {default_id}}); for (auto& target : targets) { operands.emplace_back( Operand{spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, target.first}); operands.emplace_back( Operand{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {target.second}}); } std::unique_ptr new_switch( new Instruction(GetContext(), spv::Op::OpSwitch, 0, 0, operands)); return AddInstruction(std::move(new_switch)); } // 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 . Instruction* AddPhi(uint32_t type, const std::vector& incomings, uint32_t result = 0) { assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected"); return AddNaryOp(type, spv::Op::OpPhi, incomings, result); } // Creates an addition instruction. // The id |type| must be the id of the instruction's type, must be the same as // |op1| and |op2| types. // The id |op1| is the left hand side of the operation. // The id |op2| is the right hand side of the operation. Instruction* AddIAdd(uint32_t type, uint32_t op1, uint32_t op2) { // TODO(1841): Handle id overflow. std::unique_ptr inst(new Instruction( GetContext(), spv::Op::OpIAdd, type, GetContext()->TakeNextId(), {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}})); return AddInstruction(std::move(inst)); } // Creates a less than instruction for unsigned integer. // The id |op1| is the left hand side of the operation. // The id |op2| is the right hand side of the operation. // It is assumed that |op1| and |op2| have the same underlying type. Instruction* AddULessThan(uint32_t op1, uint32_t op2) { analysis::Bool bool_type; uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type); // TODO(1841): Handle id overflow. std::unique_ptr inst(new Instruction( GetContext(), spv::Op::OpULessThan, type, GetContext()->TakeNextId(), {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}})); return AddInstruction(std::move(inst)); } // Creates a less than instruction for signed integer. // The id |op1| is the left hand side of the operation. // The id |op2| is the right hand side of the operation. // It is assumed that |op1| and |op2| have the same underlying type. Instruction* AddSLessThan(uint32_t op1, uint32_t op2) { analysis::Bool bool_type; uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type); // TODO(1841): Handle id overflow. std::unique_ptr inst(new Instruction( GetContext(), spv::Op::OpSLessThan, type, GetContext()->TakeNextId(), {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}})); return AddInstruction(std::move(inst)); } // Creates an OpILessThan or OpULessThen instruction depending on the sign of // |op1|. The id |op1| is the left hand side of the operation. The id |op2| is // the right hand side of the operation. It is assumed that |op1| and |op2| // have the same underlying type. Instruction* AddLessThan(uint32_t op1, uint32_t op2) { Instruction* op1_insn = context_->get_def_use_mgr()->GetDef(op1); analysis::Type* type = GetContext()->get_type_mgr()->GetType(op1_insn->type_id()); analysis::Integer* int_type = type->AsInteger(); assert(int_type && "Operand is not of int type"); if (int_type->IsSigned()) return AddSLessThan(op1, op2); else return AddULessThan(op1, op2); } // Creates a select instruction. // |type| must match the types of |true_value| and |false_value|. It is up to // the caller to ensure that |cond| is a correct type (bool or vector of // bool) for |type|. Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value, uint32_t false_value) { // TODO(1841): Handle id overflow. std::unique_ptr select(new Instruction( GetContext(), spv::Op::OpSelect, type, GetContext()->TakeNextId(), std::initializer_list{{SPV_OPERAND_TYPE_ID, {cond}}, {SPV_OPERAND_TYPE_ID, {true_value}}, {SPV_OPERAND_TYPE_ID, {false_value}}})); return AddInstruction(std::move(select)); } // Returns a pointer to the definition of a signed 32-bit integer constant // with the given value. Returns |nullptr| if the constant does not exist and // cannot be created. Instruction* GetSintConstant(int32_t value) { return GetIntConstant(value, true); } // Create a composite construct. // |type| should be a composite type and the number of elements it has should // match the size od |ids|. Instruction* AddCompositeConstruct(uint32_t type, const std::vector& ids) { std::vector ops; for (auto id : ids) { ops.emplace_back(SPV_OPERAND_TYPE_ID, std::initializer_list{id}); } // TODO(1841): Handle id overflow. std::unique_ptr construct( new Instruction(GetContext(), spv::Op::OpCompositeConstruct, type, GetContext()->TakeNextId(), ops)); return AddInstruction(std::move(construct)); } // Returns a pointer to the definition of an unsigned 32-bit integer constant // with the given value. Returns |nullptr| if the constant does not exist and // cannot be created. Instruction* GetUintConstant(uint32_t value) { return GetIntConstant(value, false); } uint32_t GetUintConstantId(uint32_t value) { Instruction* uint_inst = GetUintConstant(value); return (uint_inst != nullptr ? uint_inst->result_id() : 0); } // Adds either a signed or unsigned 32 bit integer constant to the binary // depending on the |sign|. If |sign| is true then the value is added as a // signed constant otherwise as an unsigned constant. If |sign| is false the // value must not be a negative number. Returns false if the constant does // not exists and could be be created. template Instruction* GetIntConstant(T value, bool sign) { // Assert that we are not trying to store a negative number in an unsigned // type. if (!sign) assert(value >= 0 && "Trying to add a signed integer with an unsigned type!"); analysis::Integer int_type{32, sign}; // Get or create the integer type. This rebuilds the type and manages the // memory for the rebuilt type. uint32_t type_id = GetContext()->get_type_mgr()->GetTypeInstruction(&int_type); if (type_id == 0) { return nullptr; } // Get the memory managed type so that it is safe to be stored by // GetConstant. analysis::Type* rebuilt_type = GetContext()->get_type_mgr()->GetType(type_id); // Even if the value is negative we need to pass the bit pattern as a // uint32_t to GetConstant. uint32_t word = value; // Create the constant value. const analysis::Constant* constant = GetContext()->get_constant_mgr()->GetConstant(rebuilt_type, {word}); // Create the OpConstant instruction using the type and the value. return GetContext()->get_constant_mgr()->GetDefiningInstruction(constant); } Instruction* GetBoolConstant(bool value) { analysis::Bool type; uint32_t type_id = GetContext()->get_type_mgr()->GetTypeInstruction(&type); analysis::Type* rebuilt_type = GetContext()->get_type_mgr()->GetType(type_id); uint32_t word = value; const analysis::Constant* constant = GetContext()->get_constant_mgr()->GetConstant(rebuilt_type, {word}); return GetContext()->get_constant_mgr()->GetDefiningInstruction(constant); } uint32_t GetBoolConstantId(bool value) { Instruction* inst = GetBoolConstant(value); return (inst != nullptr ? inst->result_id() : 0); } Instruction* AddCompositeExtract(uint32_t type, uint32_t id_of_composite, const std::vector& index_list) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {id_of_composite}}); for (uint32_t index : index_list) { operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}); } // TODO(1841): Handle id overflow. std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpCompositeExtract, type, GetContext()->TakeNextId(), operands)); return AddInstruction(std::move(new_inst)); } // Creates an unreachable instruction. Instruction* AddUnreachable() { std::unique_ptr select( new Instruction(GetContext(), spv::Op::OpUnreachable, 0, 0, std::initializer_list{})); return AddInstruction(std::move(select)); } Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id, std::vector ids) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}}); for (uint32_t index_id : ids) { operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}}); } // TODO(1841): Handle id overflow. std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpAccessChain, type_id, GetContext()->TakeNextId(), operands)); return AddInstruction(std::move(new_inst)); } Instruction* AddLoad(uint32_t type_id, uint32_t base_ptr_id, uint32_t alignment = 0) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}}); if (alignment != 0) { operands.push_back( {SPV_OPERAND_TYPE_MEMORY_ACCESS, {static_cast(spv::MemoryAccessMask::Aligned)}}); operands.push_back({SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, {alignment}}); } // TODO(1841): Handle id overflow. std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpLoad, type_id, GetContext()->TakeNextId(), operands)); return AddInstruction(std::move(new_inst)); } Instruction* AddVariable(uint32_t type_id, uint32_t storage_class) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {storage_class}}); std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpVariable, type_id, GetContext()->TakeNextId(), operands)); return AddInstruction(std::move(new_inst)); } Instruction* AddStore(uint32_t ptr_id, uint32_t obj_id) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {ptr_id}}); operands.push_back({SPV_OPERAND_TYPE_ID, {obj_id}}); std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpStore, 0, 0, operands)); return AddInstruction(std::move(new_inst)); } Instruction* AddFunctionCall(uint32_t result_type, uint32_t function, const std::vector& parameters) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {function}}); for (uint32_t id : parameters) { operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); } uint32_t result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpFunctionCall, result_type, result_id, operands)); return AddInstruction(std::move(new_inst)); } Instruction* AddVectorShuffle(uint32_t result_type, uint32_t vec1, uint32_t vec2, const std::vector& components) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {vec1}}); operands.push_back({SPV_OPERAND_TYPE_ID, {vec2}}); for (uint32_t id : components) { operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {id}}); } uint32_t result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpVectorShuffle, result_type, result_id, operands)); return AddInstruction(std::move(new_inst)); } Instruction* AddNaryExtendedInstruction( uint32_t result_type, uint32_t set, uint32_t instruction, const std::vector& ext_operands) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {set}}); operands.push_back( {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {instruction}}); for (uint32_t id : ext_operands) { operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); } uint32_t result_id = GetContext()->TakeNextId(); if (result_id == 0) { return nullptr; } std::unique_ptr new_inst(new Instruction( GetContext(), spv::Op::OpExtInst, result_type, result_id, operands)); return AddInstruction(std::move(new_inst)); } // Inserts the new instruction before the insertion point. Instruction* AddInstruction(std::unique_ptr&& insn) { 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_; } // Change the insertion point to insert before the instruction // |insert_before|. void SetInsertPoint(Instruction* insert_before) { parent_ = context_->get_instr_block(insert_before); insert_before_ = InsertionPointTy(insert_before); } // Change the insertion point to insert at the end of the basic block // |parent_block|. void SetInsertPoint(BasicBlock* parent_block) { parent_ = parent_block; insert_before_ = parent_block->end(); } // Returns the context which instructions are constructed for. IRContext* GetContext() const { return context_; } // Returns the set of preserved analyses. inline IRContext::Analysis GetPreservedAnalysis() const { return preserved_analyses_; } private: InstructionBuilder(IRContext* context, BasicBlock* parent, InsertionPointTy insert_before, IRContext::Analysis preserved_analyses) : context_(context), parent_(parent), insert_before_(insert_before), preserved_analyses_(preserved_analyses) { assert(!(preserved_analyses_ & ~(IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping))); } // Returns true if the users requested to update |analysis|. inline bool IsAnalysisUpdateRequested(IRContext::Analysis analysis) const { if (!GetContext()->AreAnalysesValid(analysis)) { // Do not try to update something that is not built. return false; } return preserved_analyses_ & analysis; } // Updates the def/use manager if the user requested it. If an update was not // requested, this function does nothing. inline void UpdateDefUseMgr(Instruction* insn) { if (IsAnalysisUpdateRequested(IRContext::kAnalysisDefUse)) GetContext()->get_def_use_mgr()->AnalyzeInstDefUse(insn); } // Updates the instruction to block analysis if the user requested it. If // an update was not requested, this function does nothing. inline void UpdateInstrToBlockMapping(Instruction* insn) { if (IsAnalysisUpdateRequested(IRContext::kAnalysisInstrToBlockMapping) && parent_) GetContext()->set_instr_block(insn, parent_); } IRContext* context_; BasicBlock* parent_; InsertionPointTy insert_before_; const IRContext::Analysis preserved_analyses_; }; } // namespace opt } // namespace spvtools #endif // SOURCE_OPT_IR_BUILDER_H_