| // Copyright (c) 2018 Google LLC | |
| // | |
| // 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/folding_rules.h" | |
| #include <limits> | |
| #include <memory> | |
| #include <utility> | |
| #include "ir_builder.h" | |
| #include "source/latest_version_glsl_std_450_header.h" | |
| #include "source/opt/ir_context.h" | |
| namespace spvtools { | |
| namespace opt { | |
| namespace { | |
| constexpr uint32_t kExtractCompositeIdInIdx = 0; | |
| constexpr uint32_t kInsertObjectIdInIdx = 0; | |
| constexpr uint32_t kInsertCompositeIdInIdx = 1; | |
| constexpr uint32_t kExtInstSetIdInIdx = 0; | |
| constexpr uint32_t kExtInstInstructionInIdx = 1; | |
| constexpr uint32_t kFMixXIdInIdx = 2; | |
| constexpr uint32_t kFMixYIdInIdx = 3; | |
| constexpr uint32_t kFMixAIdInIdx = 4; | |
| constexpr uint32_t kStoreObjectInIdx = 1; | |
| // Some image instructions may contain an "image operands" argument. | |
| // Returns the operand index for the "image operands". | |
| // Returns -1 if the instruction does not have image operands. | |
| int32_t ImageOperandsMaskInOperandIndex(Instruction* inst) { | |
| const auto opcode = inst->opcode(); | |
| switch (opcode) { | |
| case spv::Op::OpImageSampleImplicitLod: | |
| case spv::Op::OpImageSampleExplicitLod: | |
| case spv::Op::OpImageSampleProjImplicitLod: | |
| case spv::Op::OpImageSampleProjExplicitLod: | |
| case spv::Op::OpImageFetch: | |
| case spv::Op::OpImageRead: | |
| case spv::Op::OpImageSparseSampleImplicitLod: | |
| case spv::Op::OpImageSparseSampleExplicitLod: | |
| case spv::Op::OpImageSparseSampleProjImplicitLod: | |
| case spv::Op::OpImageSparseSampleProjExplicitLod: | |
| case spv::Op::OpImageSparseFetch: | |
| case spv::Op::OpImageSparseRead: | |
| return inst->NumOperands() > 4 ? 2 : -1; | |
| case spv::Op::OpImageSampleDrefImplicitLod: | |
| case spv::Op::OpImageSampleDrefExplicitLod: | |
| case spv::Op::OpImageSampleProjDrefImplicitLod: | |
| case spv::Op::OpImageSampleProjDrefExplicitLod: | |
| case spv::Op::OpImageGather: | |
| case spv::Op::OpImageDrefGather: | |
| case spv::Op::OpImageSparseSampleDrefImplicitLod: | |
| case spv::Op::OpImageSparseSampleDrefExplicitLod: | |
| case spv::Op::OpImageSparseSampleProjDrefImplicitLod: | |
| case spv::Op::OpImageSparseSampleProjDrefExplicitLod: | |
| case spv::Op::OpImageSparseGather: | |
| case spv::Op::OpImageSparseDrefGather: | |
| return inst->NumOperands() > 5 ? 3 : -1; | |
| case spv::Op::OpImageWrite: | |
| return inst->NumOperands() > 3 ? 3 : -1; | |
| default: | |
| return -1; | |
| } | |
| } | |
| // Returns the element width of |type|. | |
| uint32_t ElementWidth(const analysis::Type* type) { | |
| if (const analysis::Vector* vec_type = type->AsVector()) { | |
| return ElementWidth(vec_type->element_type()); | |
| } else if (const analysis::Float* float_type = type->AsFloat()) { | |
| return float_type->width(); | |
| } else { | |
| assert(type->AsInteger()); | |
| return type->AsInteger()->width(); | |
| } | |
| } | |
| // Returns true if |type| is Float or a vector of Float. | |
| bool HasFloatingPoint(const analysis::Type* type) { | |
| if (type->AsFloat()) { | |
| return true; | |
| } else if (const analysis::Vector* vec_type = type->AsVector()) { | |
| return vec_type->element_type()->AsFloat() != nullptr; | |
| } | |
| return false; | |
| } | |
| // Returns false if |val| is NaN, infinite or subnormal. | |
| template <typename T> | |
| bool IsValidResult(T val) { | |
| int classified = std::fpclassify(val); | |
| switch (classified) { | |
| case FP_NAN: | |
| case FP_INFINITE: | |
| case FP_SUBNORMAL: | |
| return false; | |
| default: | |
| return true; | |
| } | |
| } | |
| const analysis::Constant* ConstInput( | |
| const std::vector<const analysis::Constant*>& constants) { | |
| return constants[0] ? constants[0] : constants[1]; | |
| } | |
| Instruction* NonConstInput(IRContext* context, const analysis::Constant* c, | |
| Instruction* inst) { | |
| uint32_t in_op = c ? 1u : 0u; | |
| return context->get_def_use_mgr()->GetDef( | |
| inst->GetSingleWordInOperand(in_op)); | |
| } | |
| std::vector<uint32_t> ExtractInts(uint64_t val) { | |
| std::vector<uint32_t> words; | |
| words.push_back(static_cast<uint32_t>(val)); | |
| words.push_back(static_cast<uint32_t>(val >> 32)); | |
| return words; | |
| } | |
| std::vector<uint32_t> GetWordsFromScalarIntConstant( | |
| const analysis::IntConstant* c) { | |
| assert(c != nullptr); | |
| uint32_t width = c->type()->AsInteger()->width(); | |
| assert(width == 8 || width == 16 || width == 32 || width == 64); | |
| if (width == 64) { | |
| uint64_t uval = static_cast<uint64_t>(c->GetU64()); | |
| return ExtractInts(uval); | |
| } | |
| // Section 2.2.1 of the SPIR-V spec guarantees that all integer types | |
| // smaller than 32-bits are automatically zero or sign extended to 32-bits. | |
| return {c->GetU32BitValue()}; | |
| } | |
| std::vector<uint32_t> GetWordsFromScalarFloatConstant( | |
| const analysis::FloatConstant* c) { | |
| assert(c != nullptr); | |
| uint32_t width = c->type()->AsFloat()->width(); | |
| assert(width == 16 || width == 32 || width == 64); | |
| if (width == 64) { | |
| utils::FloatProxy<double> result(c->GetDouble()); | |
| return result.GetWords(); | |
| } | |
| // Section 2.2.1 of the SPIR-V spec guarantees that all floating-point types | |
| // smaller than 32-bits are automatically zero extended to 32-bits. | |
| return {c->GetU32BitValue()}; | |
| } | |
| std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant( | |
| analysis::ConstantManager* const_mgr, const analysis::Constant* c) { | |
| if (const auto* float_constant = c->AsFloatConstant()) { | |
| return GetWordsFromScalarFloatConstant(float_constant); | |
| } else if (const auto* int_constant = c->AsIntConstant()) { | |
| return GetWordsFromScalarIntConstant(int_constant); | |
| } else if (const auto* vec_constant = c->AsVectorConstant()) { | |
| std::vector<uint32_t> words; | |
| for (const auto* comp : vec_constant->GetComponents()) { | |
| auto comp_in_words = | |
| GetWordsFromNumericScalarOrVectorConstant(const_mgr, comp); | |
| words.insert(words.end(), comp_in_words.begin(), comp_in_words.end()); | |
| } | |
| return words; | |
| } | |
| return {}; | |
| } | |
| const analysis::Constant* ConvertWordsToNumericScalarOrVectorConstant( | |
| analysis::ConstantManager* const_mgr, const std::vector<uint32_t>& words, | |
| const analysis::Type* type) { | |
| if (type->AsInteger() || type->AsFloat()) | |
| return const_mgr->GetConstant(type, words); | |
| if (const auto* vec_type = type->AsVector()) | |
| return const_mgr->GetNumericVectorConstantWithWords(vec_type, words); | |
| return nullptr; | |
| } | |
| // Returns the negation of |c|. |c| must be a 32 or 64 bit floating point | |
| // constant. | |
| uint32_t NegateFloatingPointConstant(analysis::ConstantManager* const_mgr, | |
| const analysis::Constant* c) { | |
| assert(c); | |
| assert(c->type()->AsFloat()); | |
| uint32_t width = c->type()->AsFloat()->width(); | |
| assert(width == 32 || width == 64); | |
| std::vector<uint32_t> words; | |
| if (width == 64) { | |
| utils::FloatProxy<double> result(c->GetDouble() * -1.0); | |
| words = result.GetWords(); | |
| } else { | |
| utils::FloatProxy<float> result(c->GetFloat() * -1.0f); | |
| words = result.GetWords(); | |
| } | |
| const analysis::Constant* negated_const = | |
| const_mgr->GetConstant(c->type(), std::move(words)); | |
| return const_mgr->GetDefiningInstruction(negated_const)->result_id(); | |
| } | |
| // Negates the integer constant |c|. Returns the id of the defining instruction. | |
| uint32_t NegateIntegerConstant(analysis::ConstantManager* const_mgr, | |
| const analysis::Constant* c) { | |
| assert(c); | |
| assert(c->type()->AsInteger()); | |
| uint32_t width = c->type()->AsInteger()->width(); | |
| assert(width == 32 || width == 64); | |
| std::vector<uint32_t> words; | |
| if (width == 64) { | |
| uint64_t uval = static_cast<uint64_t>(0 - c->GetU64()); | |
| words = ExtractInts(uval); | |
| } else { | |
| words.push_back(static_cast<uint32_t>(0 - c->GetU32())); | |
| } | |
| const analysis::Constant* negated_const = | |
| const_mgr->GetConstant(c->type(), std::move(words)); | |
| return const_mgr->GetDefiningInstruction(negated_const)->result_id(); | |
| } | |
| // Negates the vector constant |c|. Returns the id of the defining instruction. | |
| uint32_t NegateVectorConstant(analysis::ConstantManager* const_mgr, | |
| const analysis::Constant* c) { | |
| assert(const_mgr && c); | |
| assert(c->type()->AsVector()); | |
| if (c->AsNullConstant()) { | |
| // 0.0 vs -0.0 shouldn't matter. | |
| return const_mgr->GetDefiningInstruction(c)->result_id(); | |
| } else { | |
| const analysis::Type* component_type = | |
| c->AsVectorConstant()->component_type(); | |
| std::vector<uint32_t> words; | |
| for (auto& comp : c->AsVectorConstant()->GetComponents()) { | |
| if (component_type->AsFloat()) { | |
| words.push_back(NegateFloatingPointConstant(const_mgr, comp)); | |
| } else { | |
| assert(component_type->AsInteger()); | |
| words.push_back(NegateIntegerConstant(const_mgr, comp)); | |
| } | |
| } | |
| const analysis::Constant* negated_const = | |
| const_mgr->GetConstant(c->type(), std::move(words)); | |
| return const_mgr->GetDefiningInstruction(negated_const)->result_id(); | |
| } | |
| } | |
| // Negates |c|. Returns the id of the defining instruction. | |
| uint32_t NegateConstant(analysis::ConstantManager* const_mgr, | |
| const analysis::Constant* c) { | |
| if (c->type()->AsVector()) { | |
| return NegateVectorConstant(const_mgr, c); | |
| } else if (c->type()->AsFloat()) { | |
| return NegateFloatingPointConstant(const_mgr, c); | |
| } else { | |
| assert(c->type()->AsInteger()); | |
| return NegateIntegerConstant(const_mgr, c); | |
| } | |
| } | |
| // Takes the reciprocal of |c|. |c|'s type must be Float or a vector of Float. | |
| // Returns 0 if the reciprocal is NaN, infinite or subnormal. | |
| uint32_t Reciprocal(analysis::ConstantManager* const_mgr, | |
| const analysis::Constant* c) { | |
| assert(const_mgr && c); | |
| assert(c->type()->AsFloat()); | |
| uint32_t width = c->type()->AsFloat()->width(); | |
| assert(width == 32 || width == 64); | |
| std::vector<uint32_t> words; | |
| if (c->IsZero()) { | |
| return 0; | |
| } | |
| if (width == 64) { | |
| spvtools::utils::FloatProxy<double> result(1.0 / c->GetDouble()); | |
| if (!IsValidResult(result.getAsFloat())) return 0; | |
| words = result.GetWords(); | |
| } else { | |
| spvtools::utils::FloatProxy<float> result(1.0f / c->GetFloat()); | |
| if (!IsValidResult(result.getAsFloat())) return 0; | |
| words = result.GetWords(); | |
| } | |
| const analysis::Constant* negated_const = | |
| const_mgr->GetConstant(c->type(), std::move(words)); | |
| return const_mgr->GetDefiningInstruction(negated_const)->result_id(); | |
| } | |
| // Replaces fdiv where second operand is constant with fmul. | |
| FoldingRule ReciprocalFDiv() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFDiv); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (!inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| if (constants[1] != nullptr) { | |
| uint32_t id = 0; | |
| if (const analysis::VectorConstant* vector_const = | |
| constants[1]->AsVectorConstant()) { | |
| std::vector<uint32_t> neg_ids; | |
| for (auto& comp : vector_const->GetComponents()) { | |
| id = Reciprocal(const_mgr, comp); | |
| if (id == 0) return false; | |
| neg_ids.push_back(id); | |
| } | |
| const analysis::Constant* negated_const = | |
| const_mgr->GetConstant(constants[1]->type(), std::move(neg_ids)); | |
| id = const_mgr->GetDefiningInstruction(negated_const)->result_id(); | |
| } else if (constants[1]->AsFloatConstant()) { | |
| id = Reciprocal(const_mgr, constants[1]); | |
| if (id == 0) return false; | |
| } else { | |
| // Don't fold a null constant. | |
| return false; | |
| } | |
| inst->SetOpcode(spv::Op::OpFMul); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0u)}}, | |
| {SPV_OPERAND_TYPE_ID, {id}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Elides consecutive negate instructions. | |
| FoldingRule MergeNegateArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFNegate || | |
| inst->opcode() == spv::Op::OpSNegate); | |
| (void)constants; | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| Instruction* op_inst = | |
| context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u)); | |
| if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (op_inst->opcode() == inst->opcode()) { | |
| // Elide negates. | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op_inst->GetSingleWordInOperand(0u)}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Merges negate into a mul or div operation if that operation contains a | |
| // constant operand. | |
| // Cases: | |
| // -(x * 2) = x * -2 | |
| // -(2 * x) = x * -2 | |
| // -(x / 2) = x / -2 | |
| // -(2 / x) = -2 / x | |
| FoldingRule MergeNegateMulDivArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFNegate || | |
| inst->opcode() == spv::Op::OpSNegate); | |
| (void)constants; | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| Instruction* op_inst = | |
| context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u)); | |
| if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| spv::Op opcode = op_inst->opcode(); | |
| if (opcode == spv::Op::OpFMul || opcode == spv::Op::OpFDiv || | |
| opcode == spv::Op::OpIMul || opcode == spv::Op::OpSDiv || | |
| opcode == spv::Op::OpUDiv) { | |
| std::vector<const analysis::Constant*> op_constants = | |
| const_mgr->GetOperandConstants(op_inst); | |
| // Merge negate into mul or div if one operand is constant. | |
| if (op_constants[0] || op_constants[1]) { | |
| bool zero_is_variable = op_constants[0] == nullptr; | |
| const analysis::Constant* c = ConstInput(op_constants); | |
| uint32_t neg_id = NegateConstant(const_mgr, c); | |
| uint32_t non_const_id = zero_is_variable | |
| ? op_inst->GetSingleWordInOperand(0u) | |
| : op_inst->GetSingleWordInOperand(1u); | |
| // Change this instruction to a mul/div. | |
| inst->SetOpcode(op_inst->opcode()); | |
| if (opcode == spv::Op::OpFDiv || opcode == spv::Op::OpUDiv || | |
| opcode == spv::Op::OpSDiv) { | |
| uint32_t op0 = zero_is_variable ? non_const_id : neg_id; | |
| uint32_t op1 = zero_is_variable ? neg_id : non_const_id; | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}}); | |
| } else { | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}}, | |
| {SPV_OPERAND_TYPE_ID, {neg_id}}}); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| // Merges negate into a add or sub operation if that operation contains a | |
| // constant operand. | |
| // Cases: | |
| // -(x + 2) = -2 - x | |
| // -(2 + x) = -2 - x | |
| // -(x - 2) = 2 - x | |
| // -(2 - x) = x - 2 | |
| FoldingRule MergeNegateAddSubArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFNegate || | |
| inst->opcode() == spv::Op::OpSNegate); | |
| (void)constants; | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| Instruction* op_inst = | |
| context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u)); | |
| if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| if (op_inst->opcode() == spv::Op::OpFAdd || | |
| op_inst->opcode() == spv::Op::OpFSub || | |
| op_inst->opcode() == spv::Op::OpIAdd || | |
| op_inst->opcode() == spv::Op::OpISub) { | |
| std::vector<const analysis::Constant*> op_constants = | |
| const_mgr->GetOperandConstants(op_inst); | |
| if (op_constants[0] || op_constants[1]) { | |
| bool zero_is_variable = op_constants[0] == nullptr; | |
| bool is_add = (op_inst->opcode() == spv::Op::OpFAdd) || | |
| (op_inst->opcode() == spv::Op::OpIAdd); | |
| bool swap_operands = !is_add || zero_is_variable; | |
| bool negate_const = is_add; | |
| const analysis::Constant* c = ConstInput(op_constants); | |
| uint32_t const_id = 0; | |
| if (negate_const) { | |
| const_id = NegateConstant(const_mgr, c); | |
| } else { | |
| const_id = zero_is_variable ? op_inst->GetSingleWordInOperand(1u) | |
| : op_inst->GetSingleWordInOperand(0u); | |
| } | |
| // Swap operands if necessary and make the instruction a subtraction. | |
| uint32_t op0 = | |
| zero_is_variable ? op_inst->GetSingleWordInOperand(0u) : const_id; | |
| uint32_t op1 = | |
| zero_is_variable ? const_id : op_inst->GetSingleWordInOperand(1u); | |
| if (swap_operands) std::swap(op0, op1); | |
| inst->SetOpcode(HasFloatingPoint(type) ? spv::Op::OpFSub | |
| : spv::Op::OpISub); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}}); | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| // Returns true if |c| has a zero element. | |
| bool HasZero(const analysis::Constant* c) { | |
| if (c->AsNullConstant()) { | |
| return true; | |
| } | |
| if (const analysis::VectorConstant* vec_const = c->AsVectorConstant()) { | |
| for (auto& comp : vec_const->GetComponents()) | |
| if (HasZero(comp)) return true; | |
| } else { | |
| assert(c->AsScalarConstant()); | |
| return c->AsScalarConstant()->IsZero(); | |
| } | |
| return false; | |
| } | |
| // Performs |input1| |opcode| |input2| and returns the merged constant result | |
| // id. Returns 0 if the result is not a valid value. The input types must be | |
| // Float. | |
| uint32_t PerformFloatingPointOperation(analysis::ConstantManager* const_mgr, | |
| spv::Op opcode, | |
| const analysis::Constant* input1, | |
| const analysis::Constant* input2) { | |
| const analysis::Type* type = input1->type(); | |
| assert(type->AsFloat()); | |
| uint32_t width = type->AsFloat()->width(); | |
| assert(width == 32 || width == 64); | |
| std::vector<uint32_t> words; | |
| #define FOLD_OP(op) \ | |
| if (width == 64) { \ | |
| utils::FloatProxy<double> val = \ | |
| input1->GetDouble() op input2->GetDouble(); \ | |
| double dval = val.getAsFloat(); \ | |
| if (!IsValidResult(dval)) return 0; \ | |
| words = val.GetWords(); \ | |
| } else { \ | |
| utils::FloatProxy<float> val = input1->GetFloat() op input2->GetFloat(); \ | |
| float fval = val.getAsFloat(); \ | |
| if (!IsValidResult(fval)) return 0; \ | |
| words = val.GetWords(); \ | |
| } \ | |
| static_assert(true, "require extra semicolon") | |
| switch (opcode) { | |
| case spv::Op::OpFMul: | |
| FOLD_OP(*); | |
| break; | |
| case spv::Op::OpFDiv: | |
| if (HasZero(input2)) return 0; | |
| FOLD_OP(/); | |
| break; | |
| case spv::Op::OpFAdd: | |
| FOLD_OP(+); | |
| break; | |
| case spv::Op::OpFSub: | |
| FOLD_OP(-); | |
| break; | |
| default: | |
| assert(false && "Unexpected operation"); | |
| break; | |
| } | |
| #undef FOLD_OP | |
| const analysis::Constant* merged_const = const_mgr->GetConstant(type, words); | |
| return const_mgr->GetDefiningInstruction(merged_const)->result_id(); | |
| } | |
| // Performs |input1| |opcode| |input2| and returns the merged constant result | |
| // id. Returns 0 if the result is not a valid value. The input types must be | |
| // Integers. | |
| uint32_t PerformIntegerOperation(analysis::ConstantManager* const_mgr, | |
| spv::Op opcode, | |
| const analysis::Constant* input1, | |
| const analysis::Constant* input2) { | |
| assert(input1->type()->AsInteger()); | |
| const analysis::Integer* type = input1->type()->AsInteger(); | |
| uint32_t width = type->AsInteger()->width(); | |
| assert(width == 32 || width == 64); | |
| std::vector<uint32_t> words; | |
| // Regardless of the sign of the constant, folding is performed on an unsigned | |
| // interpretation of the constant data. This avoids signed integer overflow | |
| // while folding, and works because sign is irrelevant for the IAdd, ISub and | |
| // IMul instructions. | |
| #define FOLD_OP(op) \ | |
| if (width == 64) { \ | |
| uint64_t val = input1->GetU64() op input2->GetU64(); \ | |
| words = ExtractInts(val); \ | |
| } else { \ | |
| uint32_t val = input1->GetU32() op input2->GetU32(); \ | |
| words.push_back(val); \ | |
| } \ | |
| static_assert(true, "require extra semicolon") | |
| switch (opcode) { | |
| case spv::Op::OpIMul: | |
| FOLD_OP(*); | |
| break; | |
| case spv::Op::OpSDiv: | |
| case spv::Op::OpUDiv: | |
| assert(false && "Should not merge integer division"); | |
| break; | |
| case spv::Op::OpIAdd: | |
| FOLD_OP(+); | |
| break; | |
| case spv::Op::OpISub: | |
| FOLD_OP(-); | |
| break; | |
| default: | |
| assert(false && "Unexpected operation"); | |
| break; | |
| } | |
| #undef FOLD_OP | |
| const analysis::Constant* merged_const = const_mgr->GetConstant(type, words); | |
| return const_mgr->GetDefiningInstruction(merged_const)->result_id(); | |
| } | |
| // Performs |input1| |opcode| |input2| and returns the merged constant result | |
| // id. Returns 0 if the result is not a valid value. The input types must be | |
| // Integers, Floats or Vectors of such. | |
| uint32_t PerformOperation(analysis::ConstantManager* const_mgr, spv::Op opcode, | |
| const analysis::Constant* input1, | |
| const analysis::Constant* input2) { | |
| assert(input1 && input2); | |
| const analysis::Type* type = input1->type(); | |
| std::vector<uint32_t> words; | |
| if (const analysis::Vector* vector_type = type->AsVector()) { | |
| const analysis::Type* ele_type = vector_type->element_type(); | |
| for (uint32_t i = 0; i != vector_type->element_count(); ++i) { | |
| uint32_t id = 0; | |
| const analysis::Constant* input1_comp = nullptr; | |
| if (const analysis::VectorConstant* input1_vector = | |
| input1->AsVectorConstant()) { | |
| input1_comp = input1_vector->GetComponents()[i]; | |
| } else { | |
| assert(input1->AsNullConstant()); | |
| input1_comp = const_mgr->GetConstant(ele_type, {}); | |
| } | |
| const analysis::Constant* input2_comp = nullptr; | |
| if (const analysis::VectorConstant* input2_vector = | |
| input2->AsVectorConstant()) { | |
| input2_comp = input2_vector->GetComponents()[i]; | |
| } else { | |
| assert(input2->AsNullConstant()); | |
| input2_comp = const_mgr->GetConstant(ele_type, {}); | |
| } | |
| if (ele_type->AsFloat()) { | |
| id = PerformFloatingPointOperation(const_mgr, opcode, input1_comp, | |
| input2_comp); | |
| } else { | |
| assert(ele_type->AsInteger()); | |
| id = PerformIntegerOperation(const_mgr, opcode, input1_comp, | |
| input2_comp); | |
| } | |
| if (id == 0) return 0; | |
| words.push_back(id); | |
| } | |
| const analysis::Constant* merged_const = | |
| const_mgr->GetConstant(type, words); | |
| return const_mgr->GetDefiningInstruction(merged_const)->result_id(); | |
| } else if (type->AsFloat()) { | |
| return PerformFloatingPointOperation(const_mgr, opcode, input1, input2); | |
| } else { | |
| assert(type->AsInteger()); | |
| return PerformIntegerOperation(const_mgr, opcode, input1, input2); | |
| } | |
| } | |
| // Merges consecutive multiplies where each contains one constant operand. | |
| // Cases: | |
| // 2 * (x * 2) = x * 4 | |
| // 2 * (2 * x) = x * 4 | |
| // (x * 2) * 2 = x * 4 | |
| // (2 * x) * 2 = x * 4 | |
| FoldingRule MergeMulMulArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFMul || | |
| inst->opcode() == spv::Op::OpIMul); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| // Determine the constant input and the variable input in |inst|. | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (HasFloatingPoint(type) && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == inst->opcode()) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2) return false; | |
| bool other_first_is_variable = other_constants[0] == nullptr; | |
| uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), | |
| const_input1, const_input2); | |
| if (merged_id == 0) return false; | |
| uint32_t non_const_id = other_first_is_variable | |
| ? other_inst->GetSingleWordInOperand(0u) | |
| : other_inst->GetSingleWordInOperand(1u); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}}, | |
| {SPV_OPERAND_TYPE_ID, {merged_id}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Merges divides into subsequent multiplies if each instruction contains one | |
| // constant operand. Does not support integer operations. | |
| // Cases: | |
| // 2 * (x / 2) = x * 1 | |
| // 2 * (2 / x) = 4 / x | |
| // (x / 2) * 2 = x * 1 | |
| // (2 / x) * 2 = 4 / x | |
| // (y / x) * x = y | |
| // x * (y / x) = y | |
| FoldingRule MergeMulDivArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFMul); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (!inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| for (uint32_t i = 0; i < 2; i++) { | |
| uint32_t op_id = inst->GetSingleWordInOperand(i); | |
| Instruction* op_inst = def_use_mgr->GetDef(op_id); | |
| if (op_inst->opcode() == spv::Op::OpFDiv) { | |
| if (op_inst->GetSingleWordInOperand(1) == | |
| inst->GetSingleWordInOperand(1 - i)) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op_inst->GetSingleWordInOperand(0)}}}); | |
| return true; | |
| } | |
| } | |
| } | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (!other_inst->IsFloatingPointFoldingAllowed()) return false; | |
| if (other_inst->opcode() == spv::Op::OpFDiv) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2 || HasZero(const_input2)) return false; | |
| bool other_first_is_variable = other_constants[0] == nullptr; | |
| // If the variable value is the second operand of the divide, multiply | |
| // the constants together. Otherwise divide the constants. | |
| uint32_t merged_id = PerformOperation( | |
| const_mgr, | |
| other_first_is_variable ? other_inst->opcode() : inst->opcode(), | |
| const_input1, const_input2); | |
| if (merged_id == 0) return false; | |
| uint32_t non_const_id = other_first_is_variable | |
| ? other_inst->GetSingleWordInOperand(0u) | |
| : other_inst->GetSingleWordInOperand(1u); | |
| // If the variable value is on the second operand of the div, then this | |
| // operation is a div. Otherwise it should be a multiply. | |
| inst->SetOpcode(other_first_is_variable ? inst->opcode() | |
| : other_inst->opcode()); | |
| if (other_first_is_variable) { | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}}, | |
| {SPV_OPERAND_TYPE_ID, {merged_id}}}); | |
| } else { | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {merged_id}}, | |
| {SPV_OPERAND_TYPE_ID, {non_const_id}}}); | |
| } | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Merges multiply of constant and negation. | |
| // Cases: | |
| // (-x) * 2 = x * -2 | |
| // 2 * (-x) = x * -2 | |
| FoldingRule MergeMulNegateArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFMul || | |
| inst->opcode() == spv::Op::OpIMul); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpFNegate || | |
| other_inst->opcode() == spv::Op::OpSNegate) { | |
| uint32_t neg_id = NegateConstant(const_mgr, const_input1); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}, | |
| {SPV_OPERAND_TYPE_ID, {neg_id}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Merges consecutive divides if each instruction contains one constant operand. | |
| // Does not support integer division. | |
| // Cases: | |
| // 2 / (x / 2) = 4 / x | |
| // 4 / (2 / x) = 2 * x | |
| // (4 / x) / 2 = 2 / x | |
| // (x / 2) / 2 = x / 4 | |
| FoldingRule MergeDivDivArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFDiv); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (!inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1 || HasZero(const_input1)) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (!other_inst->IsFloatingPointFoldingAllowed()) return false; | |
| bool first_is_variable = constants[0] == nullptr; | |
| if (other_inst->opcode() == inst->opcode()) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2 || HasZero(const_input2)) return false; | |
| bool other_first_is_variable = other_constants[0] == nullptr; | |
| spv::Op merge_op = inst->opcode(); | |
| if (other_first_is_variable) { | |
| // Constants magnify. | |
| merge_op = spv::Op::OpFMul; | |
| } | |
| // This is an x / (*) case. Swap the inputs. Doesn't harm multiply | |
| // because it is commutative. | |
| if (first_is_variable) std::swap(const_input1, const_input2); | |
| uint32_t merged_id = | |
| PerformOperation(const_mgr, merge_op, const_input1, const_input2); | |
| if (merged_id == 0) return false; | |
| uint32_t non_const_id = other_first_is_variable | |
| ? other_inst->GetSingleWordInOperand(0u) | |
| : other_inst->GetSingleWordInOperand(1u); | |
| spv::Op op = inst->opcode(); | |
| if (!first_is_variable && !other_first_is_variable) { | |
| // Effectively div of 1/x, so change to multiply. | |
| op = spv::Op::OpFMul; | |
| } | |
| uint32_t op1 = merged_id; | |
| uint32_t op2 = non_const_id; | |
| if (first_is_variable && other_first_is_variable) std::swap(op1, op2); | |
| inst->SetOpcode(op); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Fold multiplies succeeded by divides where each instruction contains a | |
| // constant operand. Does not support integer divide. | |
| // Cases: | |
| // 4 / (x * 2) = 2 / x | |
| // 4 / (2 * x) = 2 / x | |
| // (x * 4) / 2 = x * 2 | |
| // (4 * x) / 2 = x * 2 | |
| // (x * y) / x = y | |
| // (y * x) / x = y | |
| FoldingRule MergeDivMulArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFDiv); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (!inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| uint32_t op_id = inst->GetSingleWordInOperand(0); | |
| Instruction* op_inst = def_use_mgr->GetDef(op_id); | |
| if (op_inst->opcode() == spv::Op::OpFMul) { | |
| for (uint32_t i = 0; i < 2; i++) { | |
| if (op_inst->GetSingleWordInOperand(i) == | |
| inst->GetSingleWordInOperand(1)) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, | |
| {op_inst->GetSingleWordInOperand(1 - i)}}}); | |
| return true; | |
| } | |
| } | |
| } | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1 || HasZero(const_input1)) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (!other_inst->IsFloatingPointFoldingAllowed()) return false; | |
| bool first_is_variable = constants[0] == nullptr; | |
| if (other_inst->opcode() == spv::Op::OpFMul) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2) return false; | |
| bool other_first_is_variable = other_constants[0] == nullptr; | |
| // This is an x / (*) case. Swap the inputs. | |
| if (first_is_variable) std::swap(const_input1, const_input2); | |
| uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), | |
| const_input1, const_input2); | |
| if (merged_id == 0) return false; | |
| uint32_t non_const_id = other_first_is_variable | |
| ? other_inst->GetSingleWordInOperand(0u) | |
| : other_inst->GetSingleWordInOperand(1u); | |
| uint32_t op1 = merged_id; | |
| uint32_t op2 = non_const_id; | |
| if (first_is_variable) std::swap(op1, op2); | |
| // Convert to multiply | |
| if (first_is_variable) inst->SetOpcode(other_inst->opcode()); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Fold divides of a constant and a negation. | |
| // Cases: | |
| // (-x) / 2 = x / -2 | |
| // 2 / (-x) = -2 / x | |
| FoldingRule MergeDivNegateArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFDiv); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| if (!inst->IsFloatingPointFoldingAllowed()) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (!other_inst->IsFloatingPointFoldingAllowed()) return false; | |
| bool first_is_variable = constants[0] == nullptr; | |
| if (other_inst->opcode() == spv::Op::OpFNegate) { | |
| uint32_t neg_id = NegateConstant(const_mgr, const_input1); | |
| if (first_is_variable) { | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}, | |
| {SPV_OPERAND_TYPE_ID, {neg_id}}}); | |
| } else { | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {neg_id}}, | |
| {SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}}); | |
| } | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Folds addition of a constant and a negation. | |
| // Cases: | |
| // (-x) + 2 = 2 - x | |
| // 2 + (-x) = 2 - x | |
| FoldingRule MergeAddNegateArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFAdd || | |
| inst->opcode() == spv::Op::OpIAdd); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpSNegate || | |
| other_inst->opcode() == spv::Op::OpFNegate) { | |
| inst->SetOpcode(HasFloatingPoint(type) ? spv::Op::OpFSub | |
| : spv::Op::OpISub); | |
| uint32_t const_id = constants[0] ? inst->GetSingleWordInOperand(0u) | |
| : inst->GetSingleWordInOperand(1u); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {const_id}}, | |
| {SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Folds subtraction of a constant and a negation. | |
| // Cases: | |
| // (-x) - 2 = -2 - x | |
| // 2 - (-x) = x + 2 | |
| FoldingRule MergeSubNegateArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFSub || | |
| inst->opcode() == spv::Op::OpISub); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpSNegate || | |
| other_inst->opcode() == spv::Op::OpFNegate) { | |
| uint32_t op1 = 0; | |
| uint32_t op2 = 0; | |
| spv::Op opcode = inst->opcode(); | |
| if (constants[0] != nullptr) { | |
| op1 = other_inst->GetSingleWordInOperand(0u); | |
| op2 = inst->GetSingleWordInOperand(0u); | |
| opcode = HasFloatingPoint(type) ? spv::Op::OpFAdd : spv::Op::OpIAdd; | |
| } else { | |
| op1 = NegateConstant(const_mgr, const_input1); | |
| op2 = other_inst->GetSingleWordInOperand(0u); | |
| } | |
| inst->SetOpcode(opcode); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Folds addition of an addition where each operation has a constant operand. | |
| // Cases: | |
| // (x + 2) + 2 = x + 4 | |
| // (2 + x) + 2 = x + 4 | |
| // 2 + (x + 2) = x + 4 | |
| // 2 + (2 + x) = x + 4 | |
| FoldingRule MergeAddAddArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFAdd || | |
| inst->opcode() == spv::Op::OpIAdd); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpFAdd || | |
| other_inst->opcode() == spv::Op::OpIAdd) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2) return false; | |
| Instruction* non_const_input = | |
| NonConstInput(context, other_constants[0], other_inst); | |
| uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), | |
| const_input1, const_input2); | |
| if (merged_id == 0) return false; | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}}, | |
| {SPV_OPERAND_TYPE_ID, {merged_id}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Folds addition of a subtraction where each operation has a constant operand. | |
| // Cases: | |
| // (x - 2) + 2 = x + 0 | |
| // (2 - x) + 2 = 4 - x | |
| // 2 + (x - 2) = x + 0 | |
| // 2 + (2 - x) = 4 - x | |
| FoldingRule MergeAddSubArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFAdd || | |
| inst->opcode() == spv::Op::OpIAdd); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpFSub || | |
| other_inst->opcode() == spv::Op::OpISub) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2) return false; | |
| bool first_is_variable = other_constants[0] == nullptr; | |
| spv::Op op = inst->opcode(); | |
| uint32_t op1 = 0; | |
| uint32_t op2 = 0; | |
| if (first_is_variable) { | |
| // Subtract constants. Non-constant operand is first. | |
| op1 = other_inst->GetSingleWordInOperand(0u); | |
| op2 = PerformOperation(const_mgr, other_inst->opcode(), const_input1, | |
| const_input2); | |
| } else { | |
| // Add constants. Constant operand is first. Change the opcode. | |
| op1 = PerformOperation(const_mgr, inst->opcode(), const_input1, | |
| const_input2); | |
| op2 = other_inst->GetSingleWordInOperand(1u); | |
| op = other_inst->opcode(); | |
| } | |
| if (op1 == 0 || op2 == 0) return false; | |
| inst->SetOpcode(op); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Folds subtraction of an addition where each operand has a constant operand. | |
| // Cases: | |
| // (x + 2) - 2 = x + 0 | |
| // (2 + x) - 2 = x + 0 | |
| // 2 - (x + 2) = 0 - x | |
| // 2 - (2 + x) = 0 - x | |
| FoldingRule MergeSubAddArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFSub || | |
| inst->opcode() == spv::Op::OpISub); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpFAdd || | |
| other_inst->opcode() == spv::Op::OpIAdd) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2) return false; | |
| Instruction* non_const_input = | |
| NonConstInput(context, other_constants[0], other_inst); | |
| // If the first operand of the sub is not a constant, swap the constants | |
| // so the subtraction has the correct operands. | |
| if (constants[0] == nullptr) std::swap(const_input1, const_input2); | |
| // Subtract the constants. | |
| uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), | |
| const_input1, const_input2); | |
| spv::Op op = inst->opcode(); | |
| uint32_t op1 = 0; | |
| uint32_t op2 = 0; | |
| if (constants[0] == nullptr) { | |
| // Non-constant operand is first. Change the opcode. | |
| op1 = non_const_input->result_id(); | |
| op2 = merged_id; | |
| op = other_inst->opcode(); | |
| } else { | |
| // Constant operand is first. | |
| op1 = merged_id; | |
| op2 = non_const_input->result_id(); | |
| } | |
| if (op1 == 0 || op2 == 0) return false; | |
| inst->SetOpcode(op); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Folds subtraction of a subtraction where each operand has a constant operand. | |
| // Cases: | |
| // (x - 2) - 2 = x - 4 | |
| // (2 - x) - 2 = 0 - x | |
| // 2 - (x - 2) = 4 - x | |
| // 2 - (2 - x) = x + 0 | |
| FoldingRule MergeSubSubArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFSub || | |
| inst->opcode() == spv::Op::OpISub); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| const analysis::Constant* const_input1 = ConstInput(constants); | |
| if (!const_input1) return false; | |
| Instruction* other_inst = NonConstInput(context, constants[0], inst); | |
| if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (other_inst->opcode() == spv::Op::OpFSub || | |
| other_inst->opcode() == spv::Op::OpISub) { | |
| std::vector<const analysis::Constant*> other_constants = | |
| const_mgr->GetOperandConstants(other_inst); | |
| const analysis::Constant* const_input2 = ConstInput(other_constants); | |
| if (!const_input2) return false; | |
| Instruction* non_const_input = | |
| NonConstInput(context, other_constants[0], other_inst); | |
| // Merge the constants. | |
| uint32_t merged_id = 0; | |
| spv::Op merge_op = inst->opcode(); | |
| if (other_constants[0] == nullptr) { | |
| merge_op = uses_float ? spv::Op::OpFAdd : spv::Op::OpIAdd; | |
| } else if (constants[0] == nullptr) { | |
| std::swap(const_input1, const_input2); | |
| } | |
| merged_id = | |
| PerformOperation(const_mgr, merge_op, const_input1, const_input2); | |
| if (merged_id == 0) return false; | |
| spv::Op op = inst->opcode(); | |
| if (constants[0] != nullptr && other_constants[0] != nullptr) { | |
| // Change the operation. | |
| op = uses_float ? spv::Op::OpFAdd : spv::Op::OpIAdd; | |
| } | |
| uint32_t op1 = 0; | |
| uint32_t op2 = 0; | |
| if ((constants[0] == nullptr) ^ (other_constants[0] == nullptr)) { | |
| op1 = merged_id; | |
| op2 = non_const_input->result_id(); | |
| } else { | |
| op1 = non_const_input->result_id(); | |
| op2 = merged_id; | |
| } | |
| inst->SetOpcode(op); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // Helper function for MergeGenericAddSubArithmetic. If |addend| and | |
| // subtrahend of |sub| is the same, merge to copy of minuend of |sub|. | |
| bool MergeGenericAddendSub(uint32_t addend, uint32_t sub, Instruction* inst) { | |
| IRContext* context = inst->context(); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| Instruction* sub_inst = def_use_mgr->GetDef(sub); | |
| if (sub_inst->opcode() != spv::Op::OpFSub && | |
| sub_inst->opcode() != spv::Op::OpISub) | |
| return false; | |
| if (sub_inst->opcode() == spv::Op::OpFSub && | |
| !sub_inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| if (addend != sub_inst->GetSingleWordInOperand(1)) return false; | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {sub_inst->GetSingleWordInOperand(0)}}}); | |
| context->UpdateDefUse(inst); | |
| return true; | |
| } | |
| // Folds addition of a subtraction where the subtrahend is equal to the | |
| // other addend. Return a copy of the minuend. Accepts generic (const and | |
| // non-const) operands. | |
| // Cases: | |
| // (a - b) + b = a | |
| // b + (a - b) = a | |
| FoldingRule MergeGenericAddSubArithmetic() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpFAdd || | |
| inst->opcode() == spv::Op::OpIAdd); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| uint32_t width = ElementWidth(type); | |
| if (width != 32 && width != 64) return false; | |
| uint32_t add_op0 = inst->GetSingleWordInOperand(0); | |
| uint32_t add_op1 = inst->GetSingleWordInOperand(1); | |
| if (MergeGenericAddendSub(add_op0, add_op1, inst)) return true; | |
| return MergeGenericAddendSub(add_op1, add_op0, inst); | |
| }; | |
| } | |
| // Helper function for FactorAddMuls. If |factor0_0| is the same as |factor1_0|, | |
| // generate |factor0_0| * (|factor0_1| + |factor1_1|). | |
| bool FactorAddMulsOpnds(uint32_t factor0_0, uint32_t factor0_1, | |
| uint32_t factor1_0, uint32_t factor1_1, | |
| Instruction* inst) { | |
| IRContext* context = inst->context(); | |
| if (factor0_0 != factor1_0) return false; | |
| InstructionBuilder ir_builder( | |
| context, inst, | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | |
| Instruction* new_add_inst = ir_builder.AddBinaryOp( | |
| inst->type_id(), inst->opcode(), factor0_1, factor1_1); | |
| inst->SetOpcode(inst->opcode() == spv::Op::OpFAdd ? spv::Op::OpFMul | |
| : spv::Op::OpIMul); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {factor0_0}}, | |
| {SPV_OPERAND_TYPE_ID, {new_add_inst->result_id()}}}); | |
| context->UpdateDefUse(inst); | |
| return true; | |
| } | |
| // Perform the following factoring identity, handling all operand order | |
| // combinations: (a * b) + (a * c) = a * (b + c) | |
| FoldingRule FactorAddMuls() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpFAdd || | |
| inst->opcode() == spv::Op::OpIAdd); | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| bool uses_float = HasFloatingPoint(type); | |
| if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| uint32_t add_op0 = inst->GetSingleWordInOperand(0); | |
| Instruction* add_op0_inst = def_use_mgr->GetDef(add_op0); | |
| if (add_op0_inst->opcode() != spv::Op::OpFMul && | |
| add_op0_inst->opcode() != spv::Op::OpIMul) | |
| return false; | |
| uint32_t add_op1 = inst->GetSingleWordInOperand(1); | |
| Instruction* add_op1_inst = def_use_mgr->GetDef(add_op1); | |
| if (add_op1_inst->opcode() != spv::Op::OpFMul && | |
| add_op1_inst->opcode() != spv::Op::OpIMul) | |
| return false; | |
| // Only perform this optimization if both of the muls only have one use. | |
| // Otherwise this is a deoptimization in size and performance. | |
| if (def_use_mgr->NumUses(add_op0_inst) > 1) return false; | |
| if (def_use_mgr->NumUses(add_op1_inst) > 1) return false; | |
| if (add_op0_inst->opcode() == spv::Op::OpFMul && | |
| (!add_op0_inst->IsFloatingPointFoldingAllowed() || | |
| !add_op1_inst->IsFloatingPointFoldingAllowed())) | |
| return false; | |
| for (int i = 0; i < 2; i++) { | |
| for (int j = 0; j < 2; j++) { | |
| // Check if operand i in add_op0_inst matches operand j in add_op1_inst. | |
| if (FactorAddMulsOpnds(add_op0_inst->GetSingleWordInOperand(i), | |
| add_op0_inst->GetSingleWordInOperand(1 - i), | |
| add_op1_inst->GetSingleWordInOperand(j), | |
| add_op1_inst->GetSingleWordInOperand(1 - j), | |
| inst)) | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| // Replaces |inst| inplace with an FMA instruction |(x*y)+a|. | |
| void ReplaceWithFma(Instruction* inst, uint32_t x, uint32_t y, uint32_t a) { | |
| uint32_t ext = | |
| inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); | |
| if (ext == 0) { | |
| inst->context()->AddExtInstImport("GLSL.std.450"); | |
| ext = inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); | |
| assert(ext != 0 && | |
| "Could not add the GLSL.std.450 extended instruction set"); | |
| } | |
| std::vector<Operand> operands; | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {ext}}); | |
| operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {GLSLstd450Fma}}); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {x}}); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {y}}); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {a}}); | |
| inst->SetOpcode(spv::Op::OpExtInst); | |
| inst->SetInOperands(std::move(operands)); | |
| } | |
| // Folds a multiple and add into an Fma. | |
| // | |
| // Cases: | |
| // (x * y) + a = Fma x y a | |
| // a + (x * y) = Fma x y a | |
| bool MergeMulAddArithmetic(IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpFAdd); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| for (int i = 0; i < 2; i++) { | |
| uint32_t op_id = inst->GetSingleWordInOperand(i); | |
| Instruction* op_inst = def_use_mgr->GetDef(op_id); | |
| if (op_inst->opcode() != spv::Op::OpFMul) { | |
| continue; | |
| } | |
| if (!op_inst->IsFloatingPointFoldingAllowed()) { | |
| continue; | |
| } | |
| uint32_t x = op_inst->GetSingleWordInOperand(0); | |
| uint32_t y = op_inst->GetSingleWordInOperand(1); | |
| uint32_t a = inst->GetSingleWordInOperand((i + 1) % 2); | |
| ReplaceWithFma(inst, x, y, a); | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Replaces |sub| inplace with an FMA instruction |(x*y)+a| where |a| first gets | |
| // negated if |negate_addition| is true, otherwise |x| gets negated. | |
| void ReplaceWithFmaAndNegate(Instruction* sub, uint32_t x, uint32_t y, | |
| uint32_t a, bool negate_addition) { | |
| uint32_t ext = | |
| sub->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); | |
| if (ext == 0) { | |
| sub->context()->AddExtInstImport("GLSL.std.450"); | |
| ext = sub->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); | |
| assert(ext != 0 && | |
| "Could not add the GLSL.std.450 extended instruction set"); | |
| } | |
| InstructionBuilder ir_builder( | |
| sub->context(), sub, | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | |
| Instruction* neg = ir_builder.AddUnaryOp(sub->type_id(), spv::Op::OpFNegate, | |
| negate_addition ? a : x); | |
| uint32_t neg_op = neg->result_id(); // -a : -x | |
| std::vector<Operand> operands; | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {ext}}); | |
| operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {GLSLstd450Fma}}); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {negate_addition ? x : neg_op}}); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {y}}); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {negate_addition ? neg_op : a}}); | |
| sub->SetOpcode(spv::Op::OpExtInst); | |
| sub->SetInOperands(std::move(operands)); | |
| } | |
| // Folds a multiply and subtract into an Fma and negation. | |
| // | |
| // Cases: | |
| // (x * y) - a = Fma x y -a | |
| // a - (x * y) = Fma -x y a | |
| bool MergeMulSubArithmetic(IRContext* context, Instruction* sub, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(sub->opcode() == spv::Op::OpFSub); | |
| if (!sub->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| for (int i = 0; i < 2; i++) { | |
| uint32_t op_id = sub->GetSingleWordInOperand(i); | |
| Instruction* mul = def_use_mgr->GetDef(op_id); | |
| if (mul->opcode() != spv::Op::OpFMul) { | |
| continue; | |
| } | |
| if (!mul->IsFloatingPointFoldingAllowed()) { | |
| continue; | |
| } | |
| uint32_t x = mul->GetSingleWordInOperand(0); | |
| uint32_t y = mul->GetSingleWordInOperand(1); | |
| uint32_t a = sub->GetSingleWordInOperand((i + 1) % 2); | |
| ReplaceWithFmaAndNegate(sub, x, y, a, i == 0); | |
| return true; | |
| } | |
| return false; | |
| } | |
| FoldingRule IntMultipleBy1() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpIMul && | |
| "Wrong opcode. Should be OpIMul."); | |
| for (uint32_t i = 0; i < 2; i++) { | |
| if (constants[i] == nullptr) { | |
| continue; | |
| } | |
| const analysis::IntConstant* int_constant = constants[i]->AsIntConstant(); | |
| if (int_constant) { | |
| uint32_t width = ElementWidth(int_constant->type()); | |
| if (width != 32 && width != 64) return false; | |
| bool is_one = (width == 32) ? int_constant->GetU32BitValue() == 1u | |
| : int_constant->GetU64BitValue() == 1ull; | |
| if (is_one) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1 - i)}}}); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| // Returns the number of elements that the |index|th in operand in |inst| | |
| // contributes to the result of |inst|. |inst| must be an | |
| // OpCompositeConstructInstruction. | |
| uint32_t GetNumOfElementsContributedByOperand(IRContext* context, | |
| const Instruction* inst, | |
| uint32_t index) { | |
| assert(inst->opcode() == spv::Op::OpCompositeConstruct); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| analysis::TypeManager* type_mgr = context->get_type_mgr(); | |
| analysis::Vector* result_type = | |
| type_mgr->GetType(inst->type_id())->AsVector(); | |
| if (result_type == nullptr) { | |
| // If the result of the OpCompositeConstruct is not a vector then every | |
| // operands corresponds to a single element in the result. | |
| return 1; | |
| } | |
| // If the result type is a vector then the operands are either scalars or | |
| // vectors. If it is a scalar, then it corresponds to a single element. If it | |
| // is a vector, then each element in the vector will be an element in the | |
| // result. | |
| uint32_t id = inst->GetSingleWordInOperand(index); | |
| Instruction* def = def_use_mgr->GetDef(id); | |
| analysis::Vector* type = type_mgr->GetType(def->type_id())->AsVector(); | |
| if (type == nullptr) { | |
| return 1; | |
| } | |
| return type->element_count(); | |
| } | |
| // Returns the in-operands for an OpCompositeExtract instruction that are needed | |
| // to extract the |result_index|th element in the result of |inst| without using | |
| // the result of |inst|. Returns the empty vector if |result_index| is | |
| // out-of-bounds. |inst| must be an |OpCompositeConstruct| instruction. | |
| std::vector<Operand> GetExtractOperandsForElementOfCompositeConstruct( | |
| IRContext* context, const Instruction* inst, uint32_t result_index) { | |
| assert(inst->opcode() == spv::Op::OpCompositeConstruct); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| analysis::TypeManager* type_mgr = context->get_type_mgr(); | |
| analysis::Type* result_type = type_mgr->GetType(inst->type_id()); | |
| if (result_type->AsVector() == nullptr) { | |
| if (result_index < inst->NumInOperands()) { | |
| uint32_t id = inst->GetSingleWordInOperand(result_index); | |
| return {Operand(SPV_OPERAND_TYPE_ID, {id})}; | |
| } | |
| return {}; | |
| } | |
| // If the result type is a vector, then vector operands are concatenated. | |
| uint32_t total_element_count = 0; | |
| for (uint32_t idx = 0; idx < inst->NumInOperands(); ++idx) { | |
| uint32_t element_count = | |
| GetNumOfElementsContributedByOperand(context, inst, idx); | |
| total_element_count += element_count; | |
| if (result_index < total_element_count) { | |
| std::vector<Operand> operands; | |
| uint32_t id = inst->GetSingleWordInOperand(idx); | |
| Instruction* operand_def = def_use_mgr->GetDef(id); | |
| analysis::Type* operand_type = type_mgr->GetType(operand_def->type_id()); | |
| operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); | |
| if (operand_type->AsVector()) { | |
| uint32_t start_index_of_id = total_element_count - element_count; | |
| uint32_t index_into_id = result_index - start_index_of_id; | |
| operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index_into_id}}); | |
| } | |
| return operands; | |
| } | |
| } | |
| return {}; | |
| } | |
| bool CompositeConstructFeedingExtract( | |
| IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| // If the input to an OpCompositeExtract is an OpCompositeConstruct, | |
| // then we can simply use the appropriate element in the construction. | |
| assert(inst->opcode() == spv::Op::OpCompositeExtract && | |
| "Wrong opcode. Should be OpCompositeExtract."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| // If there are no index operands, then this rule cannot do anything. | |
| if (inst->NumInOperands() <= 1) { | |
| return false; | |
| } | |
| uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); | |
| Instruction* cinst = def_use_mgr->GetDef(cid); | |
| if (cinst->opcode() != spv::Op::OpCompositeConstruct) { | |
| return false; | |
| } | |
| uint32_t index_into_result = inst->GetSingleWordInOperand(1); | |
| std::vector<Operand> operands = | |
| GetExtractOperandsForElementOfCompositeConstruct(context, cinst, | |
| index_into_result); | |
| if (operands.empty()) { | |
| return false; | |
| } | |
| // Add the remaining indices for extraction. | |
| for (uint32_t i = 2; i < inst->NumInOperands(); ++i) { | |
| operands.push_back( | |
| {SPV_OPERAND_TYPE_LITERAL_INTEGER, {inst->GetSingleWordInOperand(i)}}); | |
| } | |
| if (operands.size() == 1) { | |
| // If there were no extra indices, then we have the final object. No need | |
| // to extract any more. | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| } | |
| inst->SetInOperands(std::move(operands)); | |
| return true; | |
| } | |
| // Walks the indexes chain from |start| to |end| of an OpCompositeInsert or | |
| // OpCompositeExtract instruction, and returns the type of the final element | |
| // being accessed. | |
| const analysis::Type* GetElementType(uint32_t type_id, | |
| Instruction::iterator start, | |
| Instruction::iterator end, | |
| const analysis::TypeManager* type_mgr) { | |
| const analysis::Type* type = type_mgr->GetType(type_id); | |
| for (auto index : make_range(std::move(start), std::move(end))) { | |
| assert(index.type == SPV_OPERAND_TYPE_LITERAL_INTEGER && | |
| index.words.size() == 1); | |
| if (auto* array_type = type->AsArray()) { | |
| type = array_type->element_type(); | |
| } else if (auto* matrix_type = type->AsMatrix()) { | |
| type = matrix_type->element_type(); | |
| } else if (auto* struct_type = type->AsStruct()) { | |
| type = struct_type->element_types()[index.words[0]]; | |
| } else { | |
| type = nullptr; | |
| } | |
| } | |
| return type; | |
| } | |
| // Returns true of |inst_1| and |inst_2| have the same indexes that will be used | |
| // to index into a composite object, excluding the last index. The two | |
| // instructions must have the same opcode, and be either OpCompositeExtract or | |
| // OpCompositeInsert instructions. | |
| bool HaveSameIndexesExceptForLast(Instruction* inst_1, Instruction* inst_2) { | |
| assert(inst_1->opcode() == inst_2->opcode() && | |
| "Expecting the opcodes to be the same."); | |
| assert((inst_1->opcode() == spv::Op::OpCompositeInsert || | |
| inst_1->opcode() == spv::Op::OpCompositeExtract) && | |
| "Instructions must be OpCompositeInsert or OpCompositeExtract."); | |
| if (inst_1->NumInOperands() != inst_2->NumInOperands()) { | |
| return false; | |
| } | |
| uint32_t first_index_position = | |
| (inst_1->opcode() == spv::Op::OpCompositeInsert ? 2 : 1); | |
| for (uint32_t i = first_index_position; i < inst_1->NumInOperands() - 1; | |
| i++) { | |
| if (inst_1->GetSingleWordInOperand(i) != | |
| inst_2->GetSingleWordInOperand(i)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // If the OpCompositeConstruct is simply putting back together elements that | |
| // where extracted from the same source, we can simply reuse the source. | |
| // | |
| // This is a common code pattern because of the way that scalar replacement | |
| // works. | |
| bool CompositeExtractFeedingConstruct( | |
| IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpCompositeConstruct && | |
| "Wrong opcode. Should be OpCompositeConstruct."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| uint32_t original_id = 0; | |
| if (inst->NumInOperands() == 0) { | |
| // The struct being constructed has no members. | |
| return false; | |
| } | |
| // Check each element to make sure they are: | |
| // - extractions | |
| // - extracting the same position they are inserting | |
| // - all extract from the same id. | |
| Instruction* first_element_inst = nullptr; | |
| for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { | |
| const uint32_t element_id = inst->GetSingleWordInOperand(i); | |
| Instruction* element_inst = def_use_mgr->GetDef(element_id); | |
| if (first_element_inst == nullptr) { | |
| first_element_inst = element_inst; | |
| } | |
| if (element_inst->opcode() != spv::Op::OpCompositeExtract) { | |
| return false; | |
| } | |
| if (!HaveSameIndexesExceptForLast(element_inst, first_element_inst)) { | |
| return false; | |
| } | |
| if (element_inst->GetSingleWordInOperand(element_inst->NumInOperands() - | |
| 1) != i) { | |
| return false; | |
| } | |
| if (i == 0) { | |
| original_id = | |
| element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); | |
| } else if (original_id != | |
| element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx)) { | |
| return false; | |
| } | |
| } | |
| // The last check it to see that the object being extracted from is the | |
| // correct type. | |
| Instruction* original_inst = def_use_mgr->GetDef(original_id); | |
| analysis::TypeManager* type_mgr = context->get_type_mgr(); | |
| const analysis::Type* original_type = | |
| GetElementType(original_inst->type_id(), first_element_inst->begin() + 3, | |
| first_element_inst->end() - 1, type_mgr); | |
| if (original_type == nullptr) { | |
| return false; | |
| } | |
| if (inst->type_id() != type_mgr->GetId(original_type)) { | |
| return false; | |
| } | |
| if (first_element_inst->NumInOperands() == 2) { | |
| // Simplify by using the original object. | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}}); | |
| return true; | |
| } | |
| // Copies the original id and all indexes except for the last to the new | |
| // extract instruction. | |
| inst->SetOpcode(spv::Op::OpCompositeExtract); | |
| inst->SetInOperands(std::vector<Operand>(first_element_inst->begin() + 2, | |
| first_element_inst->end() - 1)); | |
| return true; | |
| } | |
| FoldingRule InsertFeedingExtract() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpCompositeExtract && | |
| "Wrong opcode. Should be OpCompositeExtract."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); | |
| Instruction* cinst = def_use_mgr->GetDef(cid); | |
| if (cinst->opcode() != spv::Op::OpCompositeInsert) { | |
| return false; | |
| } | |
| // Find the first position where the list of insert and extract indicies | |
| // differ, if at all. | |
| uint32_t i; | |
| for (i = 1; i < inst->NumInOperands(); ++i) { | |
| if (i + 1 >= cinst->NumInOperands()) { | |
| break; | |
| } | |
| if (inst->GetSingleWordInOperand(i) != | |
| cinst->GetSingleWordInOperand(i + 1)) { | |
| break; | |
| } | |
| } | |
| // We are extracting the element that was inserted. | |
| if (i == inst->NumInOperands() && i + 1 == cinst->NumInOperands()) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, | |
| {cinst->GetSingleWordInOperand(kInsertObjectIdInIdx)}}}); | |
| return true; | |
| } | |
| // Extracting the value that was inserted along with values for the base | |
| // composite. Cannot do anything. | |
| if (i == inst->NumInOperands()) { | |
| return false; | |
| } | |
| // Extracting an element of the value that was inserted. Extract from | |
| // that value directly. | |
| if (i + 1 == cinst->NumInOperands()) { | |
| std::vector<Operand> operands; | |
| operands.push_back( | |
| {SPV_OPERAND_TYPE_ID, | |
| {cinst->GetSingleWordInOperand(kInsertObjectIdInIdx)}}); | |
| for (; i < inst->NumInOperands(); ++i) { | |
| operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, | |
| {inst->GetSingleWordInOperand(i)}}); | |
| } | |
| inst->SetInOperands(std::move(operands)); | |
| return true; | |
| } | |
| // Extracting a value that is disjoint from the element being inserted. | |
| // Rewrite the extract to use the composite input to the insert. | |
| std::vector<Operand> operands; | |
| operands.push_back( | |
| {SPV_OPERAND_TYPE_ID, | |
| {cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx)}}); | |
| for (i = 1; i < inst->NumInOperands(); ++i) { | |
| operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, | |
| {inst->GetSingleWordInOperand(i)}}); | |
| } | |
| inst->SetInOperands(std::move(operands)); | |
| return true; | |
| }; | |
| } | |
| // When a VectorShuffle is feeding an Extract, we can extract from one of the | |
| // operands of the VectorShuffle. We just need to adjust the index in the | |
| // extract instruction. | |
| FoldingRule VectorShuffleFeedingExtract() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpCompositeExtract && | |
| "Wrong opcode. Should be OpCompositeExtract."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| analysis::TypeManager* type_mgr = context->get_type_mgr(); | |
| uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); | |
| Instruction* cinst = def_use_mgr->GetDef(cid); | |
| if (cinst->opcode() != spv::Op::OpVectorShuffle) { | |
| return false; | |
| } | |
| // Find the size of the first vector operand of the VectorShuffle | |
| Instruction* first_input = | |
| def_use_mgr->GetDef(cinst->GetSingleWordInOperand(0)); | |
| analysis::Type* first_input_type = | |
| type_mgr->GetType(first_input->type_id()); | |
| assert(first_input_type->AsVector() && | |
| "Input to vector shuffle should be vectors."); | |
| uint32_t first_input_size = first_input_type->AsVector()->element_count(); | |
| // Get index of the element the vector shuffle is placing in the position | |
| // being extracted. | |
| uint32_t new_index = | |
| cinst->GetSingleWordInOperand(2 + inst->GetSingleWordInOperand(1)); | |
| // Extracting an undefined value so fold this extract into an undef. | |
| const uint32_t undef_literal_value = 0xffffffff; | |
| if (new_index == undef_literal_value) { | |
| inst->SetOpcode(spv::Op::OpUndef); | |
| inst->SetInOperands({}); | |
| return true; | |
| } | |
| // Get the id of the of the vector the elemtent comes from, and update the | |
| // index if needed. | |
| uint32_t new_vector = 0; | |
| if (new_index < first_input_size) { | |
| new_vector = cinst->GetSingleWordInOperand(0); | |
| } else { | |
| new_vector = cinst->GetSingleWordInOperand(1); | |
| new_index -= first_input_size; | |
| } | |
| // Update the extract instruction. | |
| inst->SetInOperand(kExtractCompositeIdInIdx, {new_vector}); | |
| inst->SetInOperand(1, {new_index}); | |
| return true; | |
| }; | |
| } | |
| // When an FMix with is feeding an Extract that extracts an element whose | |
| // corresponding |a| in the FMix is 0 or 1, we can extract from one of the | |
| // operands of the FMix. | |
| FoldingRule FMixFeedingExtract() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpCompositeExtract && | |
| "Wrong opcode. Should be OpCompositeExtract."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| uint32_t composite_id = | |
| inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); | |
| Instruction* composite_inst = def_use_mgr->GetDef(composite_id); | |
| if (composite_inst->opcode() != spv::Op::OpExtInst) { | |
| return false; | |
| } | |
| uint32_t inst_set_id = | |
| context->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); | |
| if (composite_inst->GetSingleWordInOperand(kExtInstSetIdInIdx) != | |
| inst_set_id || | |
| composite_inst->GetSingleWordInOperand(kExtInstInstructionInIdx) != | |
| GLSLstd450FMix) { | |
| return false; | |
| } | |
| // Get the |a| for the FMix instruction. | |
| uint32_t a_id = composite_inst->GetSingleWordInOperand(kFMixAIdInIdx); | |
| std::unique_ptr<Instruction> a(inst->Clone(context)); | |
| a->SetInOperand(kExtractCompositeIdInIdx, {a_id}); | |
| context->get_instruction_folder().FoldInstruction(a.get()); | |
| if (a->opcode() != spv::Op::OpCopyObject) { | |
| return false; | |
| } | |
| const analysis::Constant* a_const = | |
| const_mgr->FindDeclaredConstant(a->GetSingleWordInOperand(0)); | |
| if (!a_const) { | |
| return false; | |
| } | |
| bool use_x = false; | |
| assert(a_const->type()->AsFloat()); | |
| double element_value = a_const->GetValueAsDouble(); | |
| if (element_value == 0.0) { | |
| use_x = true; | |
| } else if (element_value == 1.0) { | |
| use_x = false; | |
| } else { | |
| return false; | |
| } | |
| // Get the id of the of the vector the element comes from. | |
| uint32_t new_vector = 0; | |
| if (use_x) { | |
| new_vector = composite_inst->GetSingleWordInOperand(kFMixXIdInIdx); | |
| } else { | |
| new_vector = composite_inst->GetSingleWordInOperand(kFMixYIdInIdx); | |
| } | |
| // Update the extract instruction. | |
| inst->SetInOperand(kExtractCompositeIdInIdx, {new_vector}); | |
| return true; | |
| }; | |
| } | |
| // Returns the number of elements in the composite type |type|. Returns 0 if | |
| // |type| is a scalar value. | |
| uint32_t GetNumberOfElements(const analysis::Type* type) { | |
| if (auto* vector_type = type->AsVector()) { | |
| return vector_type->element_count(); | |
| } | |
| if (auto* matrix_type = type->AsMatrix()) { | |
| return matrix_type->element_count(); | |
| } | |
| if (auto* struct_type = type->AsStruct()) { | |
| return static_cast<uint32_t>(struct_type->element_types().size()); | |
| } | |
| if (auto* array_type = type->AsArray()) { | |
| return array_type->length_info().words[0]; | |
| } | |
| return 0; | |
| } | |
| // Returns a map with the set of values that were inserted into an object by | |
| // the chain of OpCompositeInsertInstruction starting with |inst|. | |
| // The map will map the index to the value inserted at that index. | |
| std::map<uint32_t, uint32_t> GetInsertedValues(Instruction* inst) { | |
| analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr(); | |
| std::map<uint32_t, uint32_t> values_inserted; | |
| Instruction* current_inst = inst; | |
| while (current_inst->opcode() == spv::Op::OpCompositeInsert) { | |
| if (current_inst->NumInOperands() > inst->NumInOperands()) { | |
| // This is the catch the case | |
| // %2 = OpCompositeInsert %m2x2int %v2int_1_0 %m2x2int_undef 0 | |
| // %3 = OpCompositeInsert %m2x2int %int_4 %2 0 0 | |
| // %4 = OpCompositeInsert %m2x2int %v2int_2_3 %3 1 | |
| // In this case we cannot do a single construct to get the matrix. | |
| uint32_t partially_inserted_element_index = | |
| current_inst->GetSingleWordInOperand(inst->NumInOperands() - 1); | |
| if (values_inserted.count(partially_inserted_element_index) == 0) | |
| return {}; | |
| } | |
| if (HaveSameIndexesExceptForLast(inst, current_inst)) { | |
| values_inserted.insert( | |
| {current_inst->GetSingleWordInOperand(current_inst->NumInOperands() - | |
| 1), | |
| current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx)}); | |
| } | |
| current_inst = def_use_mgr->GetDef( | |
| current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx)); | |
| } | |
| return values_inserted; | |
| } | |
| // Returns true of there is an entry in |values_inserted| for every element of | |
| // |Type|. | |
| bool DoInsertedValuesCoverEntireObject( | |
| const analysis::Type* type, std::map<uint32_t, uint32_t>& values_inserted) { | |
| uint32_t container_size = GetNumberOfElements(type); | |
| if (container_size != values_inserted.size()) { | |
| return false; | |
| } | |
| if (values_inserted.rbegin()->first >= container_size) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Returns the type of the element that immediately contains the element being | |
| // inserted by the OpCompositeInsert instruction |inst|. | |
| const analysis::Type* GetContainerType(Instruction* inst) { | |
| assert(inst->opcode() == spv::Op::OpCompositeInsert); | |
| analysis::TypeManager* type_mgr = inst->context()->get_type_mgr(); | |
| return GetElementType(inst->type_id(), inst->begin() + 4, inst->end() - 1, | |
| type_mgr); | |
| } | |
| // Returns an OpCompositeConstruct instruction that build an object with | |
| // |type_id| out of the values in |values_inserted|. Each value will be | |
| // placed at the index corresponding to the value. The new instruction will | |
| // be placed before |insert_before|. | |
| Instruction* BuildCompositeConstruct( | |
| uint32_t type_id, const std::map<uint32_t, uint32_t>& values_inserted, | |
| Instruction* insert_before) { | |
| InstructionBuilder ir_builder( | |
| insert_before->context(), insert_before, | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | |
| std::vector<uint32_t> ids_in_order; | |
| for (auto it : values_inserted) { | |
| ids_in_order.push_back(it.second); | |
| } | |
| Instruction* construct = | |
| ir_builder.AddCompositeConstruct(type_id, ids_in_order); | |
| return construct; | |
| } | |
| // Replaces the OpCompositeInsert |inst| that inserts |construct| into the same | |
| // object as |inst| with final index removed. If the resulting | |
| // OpCompositeInsert instruction would have no remaining indexes, the | |
| // instruction is replaced with an OpCopyObject instead. | |
| void InsertConstructedObject(Instruction* inst, const Instruction* construct) { | |
| if (inst->NumInOperands() == 3) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {construct->result_id()}}}); | |
| } else { | |
| inst->SetInOperand(kInsertObjectIdInIdx, {construct->result_id()}); | |
| inst->RemoveOperand(inst->NumOperands() - 1); | |
| } | |
| } | |
| // Replaces a series of |OpCompositeInsert| instruction that cover the entire | |
| // object with an |OpCompositeConstruct|. | |
| bool CompositeInsertToCompositeConstruct( | |
| IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpCompositeInsert && | |
| "Wrong opcode. Should be OpCompositeInsert."); | |
| if (inst->NumInOperands() < 3) return false; | |
| std::map<uint32_t, uint32_t> values_inserted = GetInsertedValues(inst); | |
| const analysis::Type* container_type = GetContainerType(inst); | |
| if (container_type == nullptr) { | |
| return false; | |
| } | |
| if (!DoInsertedValuesCoverEntireObject(container_type, values_inserted)) { | |
| return false; | |
| } | |
| analysis::TypeManager* type_mgr = context->get_type_mgr(); | |
| Instruction* construct = BuildCompositeConstruct( | |
| type_mgr->GetId(container_type), values_inserted, inst); | |
| InsertConstructedObject(inst, construct); | |
| return true; | |
| } | |
| FoldingRule RedundantPhi() { | |
| // An OpPhi instruction where all values are the same or the result of the phi | |
| // itself, can be replaced by the value itself. | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpPhi && | |
| "Wrong opcode. Should be OpPhi."); | |
| uint32_t incoming_value = 0; | |
| for (uint32_t i = 0; i < inst->NumInOperands(); i += 2) { | |
| uint32_t op_id = inst->GetSingleWordInOperand(i); | |
| if (op_id == inst->result_id()) { | |
| continue; | |
| } | |
| if (incoming_value == 0) { | |
| incoming_value = op_id; | |
| } else if (op_id != incoming_value) { | |
| // Found two possible value. Can't simplify. | |
| return false; | |
| } | |
| } | |
| if (incoming_value == 0) { | |
| // Code looks invalid. Don't do anything. | |
| return false; | |
| } | |
| // We have a single incoming value. Simplify using that value. | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {incoming_value}}}); | |
| return true; | |
| }; | |
| } | |
| FoldingRule BitCastScalarOrVector() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpBitcast && constants.size() == 1); | |
| if (constants[0] == nullptr) return false; | |
| const analysis::Type* type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) | |
| return false; | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| std::vector<uint32_t> words = | |
| GetWordsFromNumericScalarOrVectorConstant(const_mgr, constants[0]); | |
| if (words.size() == 0) return false; | |
| const analysis::Constant* bitcasted_constant = | |
| ConvertWordsToNumericScalarOrVectorConstant(const_mgr, words, type); | |
| if (!bitcasted_constant) return false; | |
| auto new_feeder_id = | |
| const_mgr->GetDefiningInstruction(bitcasted_constant, inst->type_id()) | |
| ->result_id(); | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {new_feeder_id}}}); | |
| return true; | |
| }; | |
| } | |
| FoldingRule RedundantSelect() { | |
| // An OpSelect instruction where both values are the same or the condition is | |
| // constant can be replaced by one of the values | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpSelect && | |
| "Wrong opcode. Should be OpSelect."); | |
| assert(inst->NumInOperands() == 3); | |
| assert(constants.size() == 3); | |
| uint32_t true_id = inst->GetSingleWordInOperand(1); | |
| uint32_t false_id = inst->GetSingleWordInOperand(2); | |
| if (true_id == false_id) { | |
| // Both results are the same, condition doesn't matter | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {true_id}}}); | |
| return true; | |
| } else if (constants[0]) { | |
| const analysis::Type* type = constants[0]->type(); | |
| if (type->AsBool()) { | |
| // Scalar constant value, select the corresponding value. | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| if (constants[0]->AsNullConstant() || | |
| !constants[0]->AsBoolConstant()->value()) { | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {false_id}}}); | |
| } else { | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {true_id}}}); | |
| } | |
| return true; | |
| } else { | |
| assert(type->AsVector()); | |
| if (constants[0]->AsNullConstant()) { | |
| // All values come from false id. | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {false_id}}}); | |
| return true; | |
| } else { | |
| // Convert to a vector shuffle. | |
| std::vector<Operand> ops; | |
| ops.push_back({SPV_OPERAND_TYPE_ID, {true_id}}); | |
| ops.push_back({SPV_OPERAND_TYPE_ID, {false_id}}); | |
| const analysis::VectorConstant* vector_const = | |
| constants[0]->AsVectorConstant(); | |
| uint32_t size = | |
| static_cast<uint32_t>(vector_const->GetComponents().size()); | |
| for (uint32_t i = 0; i != size; ++i) { | |
| const analysis::Constant* component = | |
| vector_const->GetComponents()[i]; | |
| if (component->AsNullConstant() || | |
| !component->AsBoolConstant()->value()) { | |
| // Selecting from the false vector which is the second input | |
| // vector to the shuffle. Offset the index by |size|. | |
| ops.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i + size}}); | |
| } else { | |
| // Selecting from true vector which is the first input vector to | |
| // the shuffle. | |
| ops.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}); | |
| } | |
| } | |
| inst->SetOpcode(spv::Op::OpVectorShuffle); | |
| inst->SetInOperands(std::move(ops)); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| enum class FloatConstantKind { Unknown, Zero, One }; | |
| FloatConstantKind getFloatConstantKind(const analysis::Constant* constant) { | |
| if (constant == nullptr) { | |
| return FloatConstantKind::Unknown; | |
| } | |
| assert(HasFloatingPoint(constant->type()) && "Unexpected constant type"); | |
| if (constant->AsNullConstant()) { | |
| return FloatConstantKind::Zero; | |
| } else if (const analysis::VectorConstant* vc = | |
| constant->AsVectorConstant()) { | |
| const std::vector<const analysis::Constant*>& components = | |
| vc->GetComponents(); | |
| assert(!components.empty()); | |
| FloatConstantKind kind = getFloatConstantKind(components[0]); | |
| for (size_t i = 1; i < components.size(); ++i) { | |
| if (getFloatConstantKind(components[i]) != kind) { | |
| return FloatConstantKind::Unknown; | |
| } | |
| } | |
| return kind; | |
| } else if (const analysis::FloatConstant* fc = constant->AsFloatConstant()) { | |
| if (fc->IsZero()) return FloatConstantKind::Zero; | |
| uint32_t width = fc->type()->AsFloat()->width(); | |
| if (width != 32 && width != 64) return FloatConstantKind::Unknown; | |
| double value = (width == 64) ? fc->GetDoubleValue() : fc->GetFloatValue(); | |
| if (value == 0.0) { | |
| return FloatConstantKind::Zero; | |
| } else if (value == 1.0) { | |
| return FloatConstantKind::One; | |
| } else { | |
| return FloatConstantKind::Unknown; | |
| } | |
| } else { | |
| return FloatConstantKind::Unknown; | |
| } | |
| } | |
| FoldingRule RedundantFAdd() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFAdd && | |
| "Wrong opcode. Should be OpFAdd."); | |
| assert(constants.size() == 2); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| FloatConstantKind kind0 = getFloatConstantKind(constants[0]); | |
| FloatConstantKind kind1 = getFloatConstantKind(constants[1]); | |
| if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::Zero) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, | |
| {inst->GetSingleWordInOperand( | |
| kind0 == FloatConstantKind::Zero ? 1 : 0)}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| FoldingRule RedundantFSub() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFSub && | |
| "Wrong opcode. Should be OpFSub."); | |
| assert(constants.size() == 2); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| FloatConstantKind kind0 = getFloatConstantKind(constants[0]); | |
| FloatConstantKind kind1 = getFloatConstantKind(constants[1]); | |
| if (kind0 == FloatConstantKind::Zero) { | |
| inst->SetOpcode(spv::Op::OpFNegate); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1)}}}); | |
| return true; | |
| } | |
| if (kind1 == FloatConstantKind::Zero) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| FoldingRule RedundantFMul() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFMul && | |
| "Wrong opcode. Should be OpFMul."); | |
| assert(constants.size() == 2); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| FloatConstantKind kind0 = getFloatConstantKind(constants[0]); | |
| FloatConstantKind kind1 = getFloatConstantKind(constants[1]); | |
| if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::Zero) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, | |
| {inst->GetSingleWordInOperand( | |
| kind0 == FloatConstantKind::Zero ? 0 : 1)}}}); | |
| return true; | |
| } | |
| if (kind0 == FloatConstantKind::One || kind1 == FloatConstantKind::One) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, | |
| {inst->GetSingleWordInOperand( | |
| kind0 == FloatConstantKind::One ? 1 : 0)}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| FoldingRule RedundantFDiv() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpFDiv && | |
| "Wrong opcode. Should be OpFDiv."); | |
| assert(constants.size() == 2); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| FloatConstantKind kind0 = getFloatConstantKind(constants[0]); | |
| FloatConstantKind kind1 = getFloatConstantKind(constants[1]); | |
| if (kind0 == FloatConstantKind::Zero) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); | |
| return true; | |
| } | |
| if (kind1 == FloatConstantKind::One) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| FoldingRule RedundantFMix() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpExtInst && | |
| "Wrong opcode. Should be OpExtInst."); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| uint32_t instSetId = | |
| context->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); | |
| if (inst->GetSingleWordInOperand(kExtInstSetIdInIdx) == instSetId && | |
| inst->GetSingleWordInOperand(kExtInstInstructionInIdx) == | |
| GLSLstd450FMix) { | |
| assert(constants.size() == 5); | |
| FloatConstantKind kind4 = getFloatConstantKind(constants[4]); | |
| if (kind4 == FloatConstantKind::Zero || kind4 == FloatConstantKind::One) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| inst->SetInOperands( | |
| {{SPV_OPERAND_TYPE_ID, | |
| {inst->GetSingleWordInOperand(kind4 == FloatConstantKind::Zero | |
| ? kFMixXIdInIdx | |
| : kFMixYIdInIdx)}}}); | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| // This rule handles addition of zero for integers. | |
| FoldingRule RedundantIAdd() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpIAdd && | |
| "Wrong opcode. Should be OpIAdd."); | |
| uint32_t operand = std::numeric_limits<uint32_t>::max(); | |
| const analysis::Type* operand_type = nullptr; | |
| if (constants[0] && constants[0]->IsZero()) { | |
| operand = inst->GetSingleWordInOperand(1); | |
| operand_type = constants[0]->type(); | |
| } else if (constants[1] && constants[1]->IsZero()) { | |
| operand = inst->GetSingleWordInOperand(0); | |
| operand_type = constants[1]->type(); | |
| } | |
| if (operand != std::numeric_limits<uint32_t>::max()) { | |
| const analysis::Type* inst_type = | |
| context->get_type_mgr()->GetType(inst->type_id()); | |
| if (inst_type->IsSame(operand_type)) { | |
| inst->SetOpcode(spv::Op::OpCopyObject); | |
| } else { | |
| inst->SetOpcode(spv::Op::OpBitcast); | |
| } | |
| inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {operand}}}); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // This rule look for a dot with a constant vector containing a single 1 and | |
| // the rest 0s. This is the same as doing an extract. | |
| FoldingRule DotProductDoingExtract() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| assert(inst->opcode() == spv::Op::OpDot && | |
| "Wrong opcode. Should be OpDot."); | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| if (!inst->IsFloatingPointFoldingAllowed()) { | |
| return false; | |
| } | |
| for (int i = 0; i < 2; ++i) { | |
| if (!constants[i]) { | |
| continue; | |
| } | |
| const analysis::Vector* vector_type = constants[i]->type()->AsVector(); | |
| assert(vector_type && "Inputs to OpDot must be vectors."); | |
| const analysis::Float* element_type = | |
| vector_type->element_type()->AsFloat(); | |
| assert(element_type && "Inputs to OpDot must be vectors of floats."); | |
| uint32_t element_width = element_type->width(); | |
| if (element_width != 32 && element_width != 64) { | |
| return false; | |
| } | |
| std::vector<const analysis::Constant*> components; | |
| components = constants[i]->GetVectorComponents(const_mgr); | |
| constexpr uint32_t kNotFound = std::numeric_limits<uint32_t>::max(); | |
| uint32_t component_with_one = kNotFound; | |
| bool all_others_zero = true; | |
| for (uint32_t j = 0; j < components.size(); ++j) { | |
| const analysis::Constant* element = components[j]; | |
| double value = | |
| (element_width == 32 ? element->GetFloat() : element->GetDouble()); | |
| if (value == 0.0) { | |
| continue; | |
| } else if (value == 1.0) { | |
| if (component_with_one == kNotFound) { | |
| component_with_one = j; | |
| } else { | |
| component_with_one = kNotFound; | |
| break; | |
| } | |
| } else { | |
| all_others_zero = false; | |
| break; | |
| } | |
| } | |
| if (!all_others_zero || component_with_one == kNotFound) { | |
| continue; | |
| } | |
| std::vector<Operand> operands; | |
| operands.push_back( | |
| {SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1u - i)}}); | |
| operands.push_back( | |
| {SPV_OPERAND_TYPE_LITERAL_INTEGER, {component_with_one}}); | |
| inst->SetOpcode(spv::Op::OpCompositeExtract); | |
| inst->SetInOperands(std::move(operands)); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| // If we are storing an undef, then we can remove the store. | |
| // | |
| // TODO: We can do something similar for OpImageWrite, but checking for volatile | |
| // is complicated. Waiting to see if it is needed. | |
| FoldingRule StoringUndef() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpStore && | |
| "Wrong opcode. Should be OpStore."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| // If this is a volatile store, the store cannot be removed. | |
| if (inst->NumInOperands() == 3) { | |
| if (inst->GetSingleWordInOperand(2) & | |
| uint32_t(spv::MemoryAccessMask::Volatile)) { | |
| return false; | |
| } | |
| } | |
| uint32_t object_id = inst->GetSingleWordInOperand(kStoreObjectInIdx); | |
| Instruction* object_inst = def_use_mgr->GetDef(object_id); | |
| if (object_inst->opcode() == spv::Op::OpUndef) { | |
| inst->ToNop(); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| } | |
| FoldingRule VectorShuffleFeedingShuffle() { | |
| return [](IRContext* context, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpVectorShuffle && | |
| "Wrong opcode. Should be OpVectorShuffle."); | |
| analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); | |
| analysis::TypeManager* type_mgr = context->get_type_mgr(); | |
| Instruction* feeding_shuffle_inst = | |
| def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); | |
| analysis::Vector* op0_type = | |
| type_mgr->GetType(feeding_shuffle_inst->type_id())->AsVector(); | |
| uint32_t op0_length = op0_type->element_count(); | |
| bool feeder_is_op0 = true; | |
| if (feeding_shuffle_inst->opcode() != spv::Op::OpVectorShuffle) { | |
| feeding_shuffle_inst = | |
| def_use_mgr->GetDef(inst->GetSingleWordInOperand(1)); | |
| feeder_is_op0 = false; | |
| } | |
| if (feeding_shuffle_inst->opcode() != spv::Op::OpVectorShuffle) { | |
| return false; | |
| } | |
| Instruction* feeder2 = | |
| def_use_mgr->GetDef(feeding_shuffle_inst->GetSingleWordInOperand(0)); | |
| analysis::Vector* feeder_op0_type = | |
| type_mgr->GetType(feeder2->type_id())->AsVector(); | |
| uint32_t feeder_op0_length = feeder_op0_type->element_count(); | |
| uint32_t new_feeder_id = 0; | |
| std::vector<Operand> new_operands; | |
| new_operands.resize( | |
| 2, {SPV_OPERAND_TYPE_ID, {0}}); // Place holders for vector operands. | |
| const uint32_t undef_literal = 0xffffffff; | |
| for (uint32_t op = 2; op < inst->NumInOperands(); ++op) { | |
| uint32_t component_index = inst->GetSingleWordInOperand(op); | |
| // Do not interpret the undefined value literal as coming from operand 1. | |
| if (component_index != undef_literal && | |
| feeder_is_op0 == (component_index < op0_length)) { | |
| // This component comes from the feeding_shuffle_inst. Update | |
| // |component_index| to be the index into the operand of the feeder. | |
| // Adjust component_index to get the index into the operands of the | |
| // feeding_shuffle_inst. | |
| if (component_index >= op0_length) { | |
| component_index -= op0_length; | |
| } | |
| component_index = | |
| feeding_shuffle_inst->GetSingleWordInOperand(component_index + 2); | |
| // Check if we are using a component from the first or second operand of | |
| // the feeding instruction. | |
| if (component_index < feeder_op0_length) { | |
| if (new_feeder_id == 0) { | |
| // First time through, save the id of the operand the element comes | |
| // from. | |
| new_feeder_id = feeding_shuffle_inst->GetSingleWordInOperand(0); | |
| } else if (new_feeder_id != | |
| feeding_shuffle_inst->GetSingleWordInOperand(0)) { | |
| // We need both elements of the feeding_shuffle_inst, so we cannot | |
| // fold. | |
| return false; | |
| } | |
| } else if (component_index != undef_literal) { | |
| if (new_feeder_id == 0) { | |
| // First time through, save the id of the operand the element comes | |
| // from. | |
| new_feeder_id = feeding_shuffle_inst->GetSingleWordInOperand(1); | |
| } else if (new_feeder_id != | |
| feeding_shuffle_inst->GetSingleWordInOperand(1)) { | |
| // We need both elements of the feeding_shuffle_inst, so we cannot | |
| // fold. | |
| return false; | |
| } | |
| component_index -= feeder_op0_length; | |
| } | |
| if (!feeder_is_op0 && component_index != undef_literal) { | |
| component_index += op0_length; | |
| } | |
| } | |
| new_operands.push_back( | |
| {SPV_OPERAND_TYPE_LITERAL_INTEGER, {component_index}}); | |
| } | |
| if (new_feeder_id == 0) { | |
| analysis::ConstantManager* const_mgr = context->get_constant_mgr(); | |
| const analysis::Type* type = | |
| type_mgr->GetType(feeding_shuffle_inst->type_id()); | |
| const analysis::Constant* null_const = const_mgr->GetConstant(type, {}); | |
| new_feeder_id = | |
| const_mgr->GetDefiningInstruction(null_const, 0)->result_id(); | |
| } | |
| if (feeder_is_op0) { | |
| // If the size of the first vector operand changed then the indices | |
| // referring to the second operand need to be adjusted. | |
| Instruction* new_feeder_inst = def_use_mgr->GetDef(new_feeder_id); | |
| analysis::Type* new_feeder_type = | |
| type_mgr->GetType(new_feeder_inst->type_id()); | |
| uint32_t new_op0_size = new_feeder_type->AsVector()->element_count(); | |
| int32_t adjustment = op0_length - new_op0_size; | |
| if (adjustment != 0) { | |
| for (uint32_t i = 2; i < new_operands.size(); i++) { | |
| uint32_t operand = inst->GetSingleWordInOperand(i); | |
| if (operand >= op0_length && operand != undef_literal) { | |
| new_operands[i].words[0] -= adjustment; | |
| } | |
| } | |
| } | |
| new_operands[0].words[0] = new_feeder_id; | |
| new_operands[1] = inst->GetInOperand(1); | |
| } else { | |
| new_operands[1].words[0] = new_feeder_id; | |
| new_operands[0] = inst->GetInOperand(0); | |
| } | |
| inst->SetInOperands(std::move(new_operands)); | |
| return true; | |
| }; | |
| } | |
| // Removes duplicate ids from the interface list of an OpEntryPoint | |
| // instruction. | |
| FoldingRule RemoveRedundantOperands() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>&) { | |
| assert(inst->opcode() == spv::Op::OpEntryPoint && | |
| "Wrong opcode. Should be OpEntryPoint."); | |
| bool has_redundant_operand = false; | |
| std::unordered_set<uint32_t> seen_operands; | |
| std::vector<Operand> new_operands; | |
| new_operands.emplace_back(inst->GetOperand(0)); | |
| new_operands.emplace_back(inst->GetOperand(1)); | |
| new_operands.emplace_back(inst->GetOperand(2)); | |
| for (uint32_t i = 3; i < inst->NumOperands(); ++i) { | |
| if (seen_operands.insert(inst->GetSingleWordOperand(i)).second) { | |
| new_operands.emplace_back(inst->GetOperand(i)); | |
| } else { | |
| has_redundant_operand = true; | |
| } | |
| } | |
| if (!has_redundant_operand) { | |
| return false; | |
| } | |
| inst->SetInOperands(std::move(new_operands)); | |
| return true; | |
| }; | |
| } | |
| // If an image instruction's operand is a constant, updates the image operand | |
| // flag from Offset to ConstOffset. | |
| FoldingRule UpdateImageOperands() { | |
| return [](IRContext*, Instruction* inst, | |
| const std::vector<const analysis::Constant*>& constants) { | |
| const auto opcode = inst->opcode(); | |
| (void)opcode; | |
| assert((opcode == spv::Op::OpImageSampleImplicitLod || | |
| opcode == spv::Op::OpImageSampleExplicitLod || | |
| opcode == spv::Op::OpImageSampleDrefImplicitLod || | |
| opcode == spv::Op::OpImageSampleDrefExplicitLod || | |
| opcode == spv::Op::OpImageSampleProjImplicitLod || | |
| opcode == spv::Op::OpImageSampleProjExplicitLod || | |
| opcode == spv::Op::OpImageSampleProjDrefImplicitLod || | |
| opcode == spv::Op::OpImageSampleProjDrefExplicitLod || | |
| opcode == spv::Op::OpImageFetch || | |
| opcode == spv::Op::OpImageGather || | |
| opcode == spv::Op::OpImageDrefGather || | |
| opcode == spv::Op::OpImageRead || opcode == spv::Op::OpImageWrite || | |
| opcode == spv::Op::OpImageSparseSampleImplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleExplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleDrefImplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleDrefExplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleProjImplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleProjExplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleProjDrefImplicitLod || | |
| opcode == spv::Op::OpImageSparseSampleProjDrefExplicitLod || | |
| opcode == spv::Op::OpImageSparseFetch || | |
| opcode == spv::Op::OpImageSparseGather || | |
| opcode == spv::Op::OpImageSparseDrefGather || | |
| opcode == spv::Op::OpImageSparseRead) && | |
| "Wrong opcode. Should be an image instruction."); | |
| int32_t operand_index = ImageOperandsMaskInOperandIndex(inst); | |
| if (operand_index >= 0) { | |
| auto image_operands = inst->GetSingleWordInOperand(operand_index); | |
| if (image_operands & uint32_t(spv::ImageOperandsMask::Offset)) { | |
| uint32_t offset_operand_index = operand_index + 1; | |
| if (image_operands & uint32_t(spv::ImageOperandsMask::Bias)) | |
| offset_operand_index++; | |
| if (image_operands & uint32_t(spv::ImageOperandsMask::Lod)) | |
| offset_operand_index++; | |
| if (image_operands & uint32_t(spv::ImageOperandsMask::Grad)) | |
| offset_operand_index += 2; | |
| assert(((image_operands & | |
| uint32_t(spv::ImageOperandsMask::ConstOffset)) == 0) && | |
| "Offset and ConstOffset may not be used together"); | |
| if (offset_operand_index < inst->NumOperands()) { | |
| if (constants[offset_operand_index]) { | |
| image_operands = | |
| image_operands | uint32_t(spv::ImageOperandsMask::ConstOffset); | |
| image_operands = | |
| image_operands & ~uint32_t(spv::ImageOperandsMask::Offset); | |
| inst->SetInOperand(operand_index, {image_operands}); | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| }; | |
| } | |
| } // namespace | |
| void FoldingRules::AddFoldingRules() { | |
| // Add all folding rules to the list for the opcodes to which they apply. | |
| // Note that the order in which rules are added to the list matters. If a rule | |
| // applies to the instruction, the rest of the rules will not be attempted. | |
| // Take that into consideration. | |
| rules_[spv::Op::OpBitcast].push_back(BitCastScalarOrVector()); | |
| rules_[spv::Op::OpCompositeConstruct].push_back( | |
| CompositeExtractFeedingConstruct); | |
| rules_[spv::Op::OpCompositeExtract].push_back(InsertFeedingExtract()); | |
| rules_[spv::Op::OpCompositeExtract].push_back( | |
| CompositeConstructFeedingExtract); | |
| rules_[spv::Op::OpCompositeExtract].push_back(VectorShuffleFeedingExtract()); | |
| rules_[spv::Op::OpCompositeExtract].push_back(FMixFeedingExtract()); | |
| rules_[spv::Op::OpCompositeInsert].push_back( | |
| CompositeInsertToCompositeConstruct); | |
| rules_[spv::Op::OpDot].push_back(DotProductDoingExtract()); | |
| rules_[spv::Op::OpEntryPoint].push_back(RemoveRedundantOperands()); | |
| rules_[spv::Op::OpFAdd].push_back(RedundantFAdd()); | |
| rules_[spv::Op::OpFAdd].push_back(MergeAddNegateArithmetic()); | |
| rules_[spv::Op::OpFAdd].push_back(MergeAddAddArithmetic()); | |
| rules_[spv::Op::OpFAdd].push_back(MergeAddSubArithmetic()); | |
| rules_[spv::Op::OpFAdd].push_back(MergeGenericAddSubArithmetic()); | |
| rules_[spv::Op::OpFAdd].push_back(FactorAddMuls()); | |
| rules_[spv::Op::OpFAdd].push_back(MergeMulAddArithmetic); | |
| rules_[spv::Op::OpFDiv].push_back(RedundantFDiv()); | |
| rules_[spv::Op::OpFDiv].push_back(ReciprocalFDiv()); | |
| rules_[spv::Op::OpFDiv].push_back(MergeDivDivArithmetic()); | |
| rules_[spv::Op::OpFDiv].push_back(MergeDivMulArithmetic()); | |
| rules_[spv::Op::OpFDiv].push_back(MergeDivNegateArithmetic()); | |
| rules_[spv::Op::OpFMul].push_back(RedundantFMul()); | |
| rules_[spv::Op::OpFMul].push_back(MergeMulMulArithmetic()); | |
| rules_[spv::Op::OpFMul].push_back(MergeMulDivArithmetic()); | |
| rules_[spv::Op::OpFMul].push_back(MergeMulNegateArithmetic()); | |
| rules_[spv::Op::OpFNegate].push_back(MergeNegateArithmetic()); | |
| rules_[spv::Op::OpFNegate].push_back(MergeNegateAddSubArithmetic()); | |
| rules_[spv::Op::OpFNegate].push_back(MergeNegateMulDivArithmetic()); | |
| rules_[spv::Op::OpFSub].push_back(RedundantFSub()); | |
| rules_[spv::Op::OpFSub].push_back(MergeSubNegateArithmetic()); | |
| rules_[spv::Op::OpFSub].push_back(MergeSubAddArithmetic()); | |
| rules_[spv::Op::OpFSub].push_back(MergeSubSubArithmetic()); | |
| rules_[spv::Op::OpFSub].push_back(MergeMulSubArithmetic); | |
| rules_[spv::Op::OpIAdd].push_back(RedundantIAdd()); | |
| rules_[spv::Op::OpIAdd].push_back(MergeAddNegateArithmetic()); | |
| rules_[spv::Op::OpIAdd].push_back(MergeAddAddArithmetic()); | |
| rules_[spv::Op::OpIAdd].push_back(MergeAddSubArithmetic()); | |
| rules_[spv::Op::OpIAdd].push_back(MergeGenericAddSubArithmetic()); | |
| rules_[spv::Op::OpIAdd].push_back(FactorAddMuls()); | |
| rules_[spv::Op::OpIMul].push_back(IntMultipleBy1()); | |
| rules_[spv::Op::OpIMul].push_back(MergeMulMulArithmetic()); | |
| rules_[spv::Op::OpIMul].push_back(MergeMulNegateArithmetic()); | |
| rules_[spv::Op::OpISub].push_back(MergeSubNegateArithmetic()); | |
| rules_[spv::Op::OpISub].push_back(MergeSubAddArithmetic()); | |
| rules_[spv::Op::OpISub].push_back(MergeSubSubArithmetic()); | |
| rules_[spv::Op::OpPhi].push_back(RedundantPhi()); | |
| rules_[spv::Op::OpSNegate].push_back(MergeNegateArithmetic()); | |
| rules_[spv::Op::OpSNegate].push_back(MergeNegateMulDivArithmetic()); | |
| rules_[spv::Op::OpSNegate].push_back(MergeNegateAddSubArithmetic()); | |
| rules_[spv::Op::OpSelect].push_back(RedundantSelect()); | |
| rules_[spv::Op::OpStore].push_back(StoringUndef()); | |
| rules_[spv::Op::OpVectorShuffle].push_back(VectorShuffleFeedingShuffle()); | |
| rules_[spv::Op::OpImageSampleImplicitLod].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleExplicitLod].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleDrefImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleDrefExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleProjImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleProjExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleProjDrefImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSampleProjDrefExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageFetch].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageGather].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageDrefGather].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageRead].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageWrite].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleDrefImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleDrefExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleProjImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleProjExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleProjDrefImplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseSampleProjDrefExplicitLod].push_back( | |
| UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseFetch].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseGather].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseDrefGather].push_back(UpdateImageOperands()); | |
| rules_[spv::Op::OpImageSparseRead].push_back(UpdateImageOperands()); | |
| FeatureManager* feature_manager = context_->get_feature_mgr(); | |
| // Add rules for GLSLstd450 | |
| uint32_t ext_inst_glslstd450_id = | |
| feature_manager->GetExtInstImportId_GLSLstd450(); | |
| if (ext_inst_glslstd450_id != 0) { | |
| ext_rules_[{ext_inst_glslstd450_id, GLSLstd450FMix}].push_back( | |
| RedundantFMix()); | |
| } | |
| } | |
| } // namespace opt | |
| } // namespace spvtools |