Add strength reduction; for now replace multiply by power of 2 Create a new optimization pass, strength reduction, which will replace integer multiplication by a constant power of 2 with an equivalent bit shift. More changes could be added later. - Does not duplicate constants - Adds vector |Concat| utility function to a common test header.
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 5cfcdb9..d438eeb 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp
@@ -185,6 +185,12 @@ // OpSpecConstantOp. Optimizer::PassToken CreateEliminateDeadConstantPass(); +// Creates a strength-reduction pass. +// A strength-reduction pass will look for opportunities to replace an +// instruction with an equivalent and less expensive one. For example, +// multiplying by a power of 2 can be replaced by a bit shift. +Optimizer::PassToken CreateStrengthReductionPass(); + // Creates a block merge pass. // This pass searches for blocks with a single Branch to a block with no // other predecessors and merges the blocks into a single block. Continue
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 09b0469..f91e5a2 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt
@@ -45,6 +45,7 @@ passes.h pass_manager.h set_spec_constant_default_value_pass.h + strength_reduction_pass.h strip_debug_info_pass.h types.h type_manager.h @@ -79,6 +80,7 @@ mem_pass.cpp pass.cpp pass_manager.cpp + strength_reduction_pass.cpp strip_debug_info_pass.cpp types.cpp type_manager.cpp
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 2e3133d..16600ba 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp
@@ -132,6 +132,11 @@ MakeUnique<opt::EliminateDeadConstantPass>()); } +Optimizer::PassToken CreateStrengthReductionPass() { + return MakeUnique<Optimizer::PassToken::Impl>( + MakeUnique<opt::StrengthReductionPass>()); +} + Optimizer::PassToken CreateBlockMergePass() { return MakeUnique<Optimizer::PassToken::Impl>( MakeUnique<opt::BlockMergePass>());
diff --git a/source/opt/passes.h b/source/opt/passes.h index c1fdd6a..572387f 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h
@@ -35,6 +35,7 @@ #include "aggressive_dead_code_elim_pass.h" #include "null_pass.h" #include "set_spec_constant_default_value_pass.h" +#include "strength_reduction_pass.h" #include "strip_debug_info_pass.h" #include "unify_const_pass.h"
diff --git a/source/opt/strength_reduction_pass.cpp b/source/opt/strength_reduction_pass.cpp new file mode 100644 index 0000000..58928c1 --- /dev/null +++ b/source/opt/strength_reduction_pass.cpp
@@ -0,0 +1,210 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "strength_reduction_pass.h" + +#include <algorithm> +#include <cstdio> +#include <cstring> +#include <unordered_map> +#include <unordered_set> + +#include "def_use_manager.h" +#include "log.h" +#include "reflect.h" + +namespace { +// Count the number of trailing zeros in the binary representation of +// |constVal|. +uint32_t CountTrailingZeros(uint32_t constVal) { + // Faster if we use the hardware count trailing zeros instruction. + // If not available, we could create a table. + uint32_t shiftAmount = 0; + while ((constVal & 1) == 0) { + ++shiftAmount; + constVal = (constVal >> 1); + } + return shiftAmount; +} + +// Return true if |val| is a power of 2. +bool IsPowerOf2(uint32_t val) { + // The idea is that the & will clear out the least + // significant 1 bit. If it is a power of 2, then + // there is exactly 1 bit set, and the value becomes 0. + if (val == 0) return false; + return ((val - 1) & val) == 0; +} + +} // namespace + +namespace spvtools { +namespace opt { + +Pass::Status StrengthReductionPass::Process(ir::Module* module) { + // Initialize the member variables on a per module basis. + bool modified = false; + def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module)); + int32_type_id_ = 0; + uint32_type_id_ = 0; + std::memset(constant_ids_, 0, sizeof(constant_ids_)); + next_id_ = module->IdBound(); + module_ = module; + + FindIntTypesAndConstants(); + modified = ScanFunctions(); + // Have to reset the id bound. + module->SetIdBound(next_id_); + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool StrengthReductionPass::ReplaceMultiplyByPowerOf2( + ir::BasicBlock::iterator* instPtr) { + ir::BasicBlock::iterator& inst = *instPtr; + assert(inst->opcode() == SpvOp::SpvOpIMul && + "Only works for multiplication of integers."); + bool modified = false; + + // Currently only works on 32-bit integers. + if (inst->type_id() != int32_type_id_ && inst->type_id() != uint32_type_id_) { + return modified; + } + + // Check the operands for a constant that is a power of 2. + for (int i = 0; i < 2; i++) { + uint32_t opId = inst->GetSingleWordInOperand(i); + ir::Instruction* opInst = def_use_mgr_->GetDef(opId); + if (opInst->opcode() == SpvOp::SpvOpConstant) { + // We found a constant operand. + uint32_t constVal = opInst->GetSingleWordOperand(2); + + if (IsPowerOf2(constVal)) { + modified = true; + uint32_t shiftAmount = CountTrailingZeros(constVal); + uint32_t shiftConstResultId = GetConstantId(shiftAmount); + + // Create the new instruction. + uint32_t newResultId = next_id_++; + std::vector<ir::Operand> newOperands; + newOperands.push_back(inst->GetInOperand(1 - i)); + ir::Operand shiftOperand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {shiftConstResultId}); + newOperands.push_back(shiftOperand); + std::unique_ptr<ir::Instruction> newInstruction( + new ir::Instruction(SpvOp::SpvOpShiftLeftLogical, inst->type_id(), + newResultId, newOperands)); + + // Insert the new instruction and update the data structures. + def_use_mgr_->AnalyzeInstDefUse(&*newInstruction); + inst = inst.InsertBefore(std::move(newInstruction)); + ++inst; + def_use_mgr_->ReplaceAllUsesWith(inst->result_id(), newResultId); + + // Remove the old instruction. + def_use_mgr_->KillInst(&*inst); + + // We do not want to replace the instruction twice if both operands + // are constants that are a power of 2. So we break here. + break; + } + } + } + + return modified; +} + +void StrengthReductionPass::FindIntTypesAndConstants() { + for (auto iter = module_->types_values_begin(); + iter != module_->types_values_end(); ++iter) { + switch (iter->opcode()) { + case SpvOp::SpvOpTypeInt: + if (iter->GetSingleWordOperand(1) == 32) { + if (iter->GetSingleWordOperand(2) == 1) { + int32_type_id_ = iter->result_id(); + } else { + uint32_type_id_ = iter->result_id(); + } + } + break; + case SpvOp::SpvOpConstant: + if (iter->type_id() == uint32_type_id_) { + uint32_t value = iter->GetSingleWordOperand(2); + if (value <= 32) constant_ids_[value] = iter->result_id(); + } + break; + default: + break; + } + } +} + +uint32_t StrengthReductionPass::GetConstantId(uint32_t val) { + assert(val <= 32 && + "This function does not handle constants larger than 32."); + + if (constant_ids_[val] == 0) { + if (uint32_type_id_ == 0) { + uint32_type_id_ = CreateUint32Type(); + } + + // Construct the constant. + uint32_t resultId = next_id_++; + ir::Operand constant(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {val}); + std::unique_ptr<ir::Instruction> newConstant(new ir::Instruction( + SpvOp::SpvOpConstant, uint32_type_id_, resultId, {constant})); + module_->AddGlobalValue(std::move(newConstant)); + + // Store the result id for next time. + constant_ids_[val] = resultId; + } + + return constant_ids_[val]; +} + +bool StrengthReductionPass::ScanFunctions() { + // I did not use |ForEachInst| in the module because the function that acts on + // the instruction gets a pointer to the instruction. We cannot use that to + // insert a new instruction. I want an iterator. + bool modified = false; + for (auto& func : *module_) { + for (auto& bb : func) { + for (auto inst = bb.begin(); inst != bb.end(); ++inst) { + switch (inst->opcode()) { + case SpvOp::SpvOpIMul: + if (ReplaceMultiplyByPowerOf2(&inst)) modified = true; + break; + default: + break; + } + } + } + } + return modified; +} + +uint32_t StrengthReductionPass::CreateUint32Type() { + uint32_t type_id = next_id_++; + ir::Operand widthOperand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {32}); + ir::Operand signOperand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {0}); + std::unique_ptr<ir::Instruction> newType(new ir::Instruction( + SpvOp::SpvOpTypeInt, type_id, 0, {widthOperand, signOperand})); + module_->AddType(std::move(newType)); + return type_id; +} + +} // namespace opt +} // namespace spvtools
diff --git a/source/opt/strength_reduction_pass.h b/source/opt/strength_reduction_pass.h new file mode 100644 index 0000000..3d39d45 --- /dev/null +++ b/source/opt/strength_reduction_pass.h
@@ -0,0 +1,75 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_ +#define LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_ + +#include "def_use_manager.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class StrengthReductionPass : public Pass { + public: + const char* name() const override { return "strength-reduction"; } + Status Process(ir::Module*) override; + + private: + // Replaces multiple by power of 2 with an equivalent bit shift. + // Returns true if something changed. + bool ReplaceMultiplyByPowerOf2(ir::BasicBlock::iterator*); + + // Scan the types and constants in the module looking for the the integer types that we are + // interested in. The shift operation needs a small unsigned integer. We need to find + // them or create them. We do not want duplicates. + void FindIntTypesAndConstants(); + + // Get the id for the given constant. If it does not exist, it will be + // created. The parameter must be between 0 and 32 inclusive. + uint32_t GetConstantId(uint32_t); + + // Replaces certain instructions in function bodies with presumably cheaper + // ones. Returns true if something changed. + bool ScanFunctions(); + + // Will create the type for an unsigned 32-bit integer and return the id. + // This functions assumes one does not already exist. + uint32_t CreateUint32Type(); + + // Def-Uses for the module we are processing + std::unique_ptr<analysis::DefUseManager> def_use_mgr_; + + // Type ids for the types of interest, or 0 if they do not exist. + uint32_t int32_type_id_; + uint32_t uint32_type_id_; + + // constant_ids[i] is the id for unsigned integer constant i. + // We set the limit at 32 because a bit shift of a 32-bit integer does not + // need a value larger than 32. + uint32_t constant_ids_[33]; + + // Next unused ID + uint32_t next_id_; + + // The module that the pass is being applied to. + ir::Module* module_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index 8bb13c3..64e46b8 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt
@@ -168,3 +168,8 @@ SRCS line_debug_info_test.cpp pass_utils.cpp LIBS SPIRV-Tools-opt ) + +add_spvtools_unittest(TARGET pass_strength_reduction + SRCS strength_reduction_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +)
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp index c568c2b..8f35044 100644 --- a/test/opt/inline_test.cpp +++ b/test/opt/inline_test.cpp
@@ -16,13 +16,6 @@ #include "pass_fixture.h" #include "pass_utils.h" -template <typename T> std::vector<T> concat(const std::vector<T> &a, const std::vector<T> &b) { - std::vector<T> ret = std::vector<T>(); - std::copy(a.begin(), a.end(), back_inserter(ret)); - std::copy(b.begin(), b.end(), back_inserter(ret)); - return ret; -} - namespace { using namespace spvtools; @@ -134,8 +127,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -284,8 +277,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -413,8 +406,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -549,8 +542,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -744,8 +737,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -941,8 +934,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -1147,8 +1140,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); } @@ -1353,8 +1346,8 @@ // clang-format on }; SinglePassRunAndCheck<opt::InlineExhaustivePass>( - JoinAllInsts(concat(concat(predefs, before), nonEntryFuncs)), - JoinAllInsts(concat(concat(predefs, after), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), /* skip_nop = */ false, /* do_validate = */ true); }
diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp index cc55d5d..7d7559a 100644 --- a/test/opt/local_single_block_elim.cpp +++ b/test/opt/local_single_block_elim.cpp
@@ -16,14 +16,6 @@ #include "pass_fixture.h" #include "pass_utils.h" -template <typename T> -std::vector<T> concat(const std::vector<T>& a, const std::vector<T>& b) { - std::vector<T> ret; - std::copy(a.begin(), a.end(), back_inserter(ret)); - std::copy(b.begin(), b.end(), back_inserter(ret)); - return ret; -} - namespace { using namespace spvtools;
diff --git a/test/opt/pass_utils.h b/test/opt/pass_utils.h index a2a3b3c..dbf87c9 100644 --- a/test/opt/pass_utils.h +++ b/test/opt/pass_utils.h
@@ -49,6 +49,16 @@ // will be ignored. std::string JoinNonDebugInsts(const std::vector<const char*>& insts); +// Returns a vector that contains the contents of |a| followed by the contents +// of |b|. +template <typename T> +std::vector<T> Concat(const std::vector<T>& a, const std::vector<T>& b) { + std::vector<T> ret; + std::copy(a.begin(), a.end(), back_inserter(ret)); + std::copy(b.begin(), b.end(), back_inserter(ret)); + return ret; +} + } // namespace spvtools #endif // LIBSPIRV_TEST_OPT_PASS_UTILS_H_
diff --git a/test/opt/strength_reduction_test.cpp b/test/opt/strength_reduction_test.cpp new file mode 100644 index 0000000..541be60 --- /dev/null +++ b/test/opt/strength_reduction_test.cpp
@@ -0,0 +1,427 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +#include <algorithm> +#include <cstdarg> +#include <iostream> +#include <sstream> +#include <unordered_set> + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using StrengthReductionBasicTest = PassTest<::testing::Test>; + +// Test to make sure we replace 5*8. +TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy8) { + const std::vector<const char*> text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_5 = OpConstant %uint 5", + "%uint_8 = OpConstant %uint 8", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_5 %uint_8", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + JoinAllInsts(text), /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_3")); +} + +// Test to make sure we replace 16*5. +TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy16) { + const std::vector<const char*> text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", + "%int_16 = OpConstant %int 16", + "%int_5 = OpConstant %int 5", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %int %int_16 %int_5", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + JoinAllInsts(text), /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_5 %uint_4")); +} + +// Test to make sure we replace a multiple of 32 and 4. +TEST_F(StrengthReductionBasicTest, BasicTwoPowersOf2) { + // In this case, we have two powers of 2. Need to make sure we replace only + // one of them for the bit shift. + // clang-format off + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%int_32 = OpConstant %int 32 + %int_4 = OpConstant %int 4 + %main = OpFunction %void None %4 + %8 = OpLabel + %9 = OpIMul %int %int_32 %int_4 + OpReturn + OpFunctionEnd +)"; + // clang-format on + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + text, /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_4 %uint_5")); +} + +// Test to make sure we don't replace 0*5. +TEST_F(StrengthReductionBasicTest, BasicDontReplace0) { + const std::vector<const char*> text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", + "%int_0 = OpConstant %int 0", + "%int_5 = OpConstant %int 5", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %int %int_0 %int_5", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + JoinAllInsts(text), /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// Test to make sure we do not replace a multiple of 5 and 7. +TEST_F(StrengthReductionBasicTest, BasicNoChange) { + const std::vector<const char*> text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "OpName %2 \"main\"", + "%3 = OpTypeVoid", + "%4 = OpTypeFunction %3", + "%5 = OpTypeInt 32 1", + "%6 = OpTypeInt 32 0", + "%7 = OpConstant %5 5", + "%8 = OpConstant %5 7", + "%2 = OpFunction %3 None %4", + "%9 = OpLabel", + "%10 = OpIMul %5 %7 %8", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + JoinAllInsts(text), /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// Test to make sure constants and types are reused and not duplicated. +TEST_F(StrengthReductionBasicTest, NoDuplicateConstantsAndTypes) { + const std::vector<const char*> text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_8 = OpConstant %uint 8", + "%uint_3 = OpConstant %uint 3", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_8 %uint_3", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + JoinAllInsts(text), /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, + Not(MatchesRegex(".*OpConstant %uint 3.*OpConstant %uint 3.*"))); + EXPECT_THAT(output, Not(MatchesRegex(".*OpTypeInt 32 0.*OpTypeInt 32 0.*"))); +} + +// Test to make sure we generate the constants only once +TEST_F(StrengthReductionBasicTest, BasicCreateOneConst) { + const std::vector<const char*> text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_5 = OpConstant %uint 5", + "%uint_9 = OpConstant %uint 9", + "%uint_128 = OpConstant %uint 128", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_5 %uint_128", + "%10 = OpIMul %uint %uint_9 %uint_128", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble<opt::StrengthReductionPass>( + JoinAllInsts(text), /* skip_nop = */ true); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_7")); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_9 %uint_7")); +} + +// Test to make sure we generate the instructions in the correct position and +// that the uses get replaced as well. Here we check that the use in the return +// is replaced, we also check that we can replace two OpIMuls when one feeds the +// other. +TEST_F(StrengthReductionBasicTest, BasicCheckPositionAndReplacement) { + // This is just the preamble to set up the test. + const std::vector<const char*> common_text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpName %main \"main\"", + "OpName %foo_i1_ \"foo(i1;\"", + "OpName %n \"n\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "OpName %param \"param\"", + "OpDecorate %gl_FragColor Location 0", + "%void = OpTypeVoid", + "%3 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", +"%_ptr_Function_int = OpTypePointer Function %int", + "%8 = OpTypeFunction %int %_ptr_Function_int", + "%int_256 = OpConstant %int 256", + "%int_2 = OpConstant %int 2", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_1 = OpConstant %float 1", + "%int_10 = OpConstant %int 10", + "%float_0_4 = OpConstant %float 0.4", + "%float_0_8 = OpConstant %float 0.8", + "%uint = OpTypeInt 32 0", + "%uint_8 = OpConstant %uint 8", + "%uint_1 = OpConstant %uint 1", + "%main = OpFunction %void None %3", + "%5 = OpLabel", + "%param = OpVariable %_ptr_Function_int Function", + "OpStore %param %int_10", + "%26 = OpFunctionCall %int %foo_i1_ %param", + "%27 = OpConvertSToF %float %26", + "%28 = OpFDiv %float %float_1 %27", + "%31 = OpCompositeConstruct %v4float %28 %float_0_4 %float_0_8 %float_1", + "OpStore %gl_FragColor %31", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + // This is the real test. The two OpIMul should be replaced. The expected + // output is in |foo_after|. + const std::vector<const char*> foo_before = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%14 = OpIMul %int %12 %int_256", + "%16 = OpIMul %int %14 %int_2", + "OpReturnValue %16", + "OpFunctionEnd", + + // clang-format on + }; + + const std::vector<const char*> foo_after = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%33 = OpShiftLeftLogical %int %12 %uint_8", + "%34 = OpShiftLeftLogical %int %33 %uint_1", + "OpReturnValue %34", + "OpFunctionEnd", + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck<opt::StrengthReductionPass>( + JoinAllInsts(Concat(common_text, foo_before)), + JoinAllInsts(Concat(common_text, foo_after)), + /* skip_nop = */ true, /* do_validate = */ true); +} + +// Test that, when the result of an OpIMul instruction has more than 1 use, and +// the instruction is replaced, all of the uses of the results are replace with +// the new result. +TEST_F(StrengthReductionBasicTest, BasicTestMultipleReplacements) { + // This is just the preamble to set up the test. + const std::vector<const char*> common_text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpName %main \"main\"", + "OpName %foo_i1_ \"foo(i1;\"", + "OpName %n \"n\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "OpName %param \"param\"", + "OpDecorate %gl_FragColor Location 0", + "%void = OpTypeVoid", + "%3 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", +"%_ptr_Function_int = OpTypePointer Function %int", + "%8 = OpTypeFunction %int %_ptr_Function_int", + "%int_256 = OpConstant %int 256", + "%int_2 = OpConstant %int 2", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_1 = OpConstant %float 1", + "%int_10 = OpConstant %int 10", + "%float_0_4 = OpConstant %float 0.4", + "%float_0_8 = OpConstant %float 0.8", + "%uint = OpTypeInt 32 0", + "%uint_8 = OpConstant %uint 8", + "%uint_1 = OpConstant %uint 1", + "%main = OpFunction %void None %3", + "%5 = OpLabel", + "%param = OpVariable %_ptr_Function_int Function", + "OpStore %param %int_10", + "%26 = OpFunctionCall %int %foo_i1_ %param", + "%27 = OpConvertSToF %float %26", + "%28 = OpFDiv %float %float_1 %27", + "%31 = OpCompositeConstruct %v4float %28 %float_0_4 %float_0_8 %float_1", + "OpStore %gl_FragColor %31", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + // This is the real test. The two OpIMul instructions should be replaced. In + // particular, we want to be sure that both uses of %16 are changed to use the + // new result. + const std::vector<const char*> foo_before = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%14 = OpIMul %int %12 %int_256", + "%16 = OpIMul %int %14 %int_2", + "%17 = OpIAdd %int %14 %16", + "OpReturnValue %17", + "OpFunctionEnd", + + // clang-format on + }; + + const std::vector<const char*> foo_after = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%34 = OpShiftLeftLogical %int %12 %uint_8", + "%35 = OpShiftLeftLogical %int %34 %uint_1", + "%17 = OpIAdd %int %34 %35", + "OpReturnValue %17", + "OpFunctionEnd", + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck<opt::StrengthReductionPass>( + JoinAllInsts(Concat(common_text, foo_before)), + JoinAllInsts(Concat(common_text, foo_after)), + /* skip_nop = */ true, /* do_validate = */ true); +} +} // anonymous namespace
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index f8bd520..1a53347 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp
@@ -112,6 +112,8 @@ Join two blocks into a single block if the second has the first as its only predecessor. Performed only on entry point call tree functions. + --strength-reduction + Replaces instructions with equivalent and less expensive ones. -h, --help Print this help. --version @@ -200,6 +202,8 @@ optimizer.RegisterPass(CreateEliminateDeadConstantPass()); } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) { optimizer.RegisterPass(CreateFoldSpecConstantOpAndCompositePass()); + } else if (0 == strcmp(cur_arg, "--strength-reduction")) { + optimizer.RegisterPass(CreateStrengthReductionPass()); } else if (0 == strcmp(cur_arg, "--unify-const")) { optimizer.RegisterPass(CreateUnifyConstantPass()); } else if (0 == strcmp(cur_arg, "--flatten-decorations")) {