Add exhaustive function call inlining to spirv-opt

Inlining is done for all functions designated as entry points.

Add optional validation to test fixture method SinglePassRunAndCheck.
This commit is contained in:
Greg Fischer 2016-11-10 10:11:50 -07:00 коммит произвёл David Neto
Родитель b85997a1df
Коммит 04fcc66743
21 изменённых файлов: 2249 добавлений и 45 удалений

Просмотреть файл

@ -95,6 +95,7 @@ Currently supported optimizations:
* Fold `OpSpecConstantOp` and `OpSpecConstantComposite`
* Unify constants
* Eliminate dead constant
* Inline all function calls in entry points
For the latest list with detailed documentation, please refer to
[`include/spirv-tools/optimizer.hpp`](include/spirv-tools/optimizer.hpp).

Просмотреть файл

@ -167,6 +167,15 @@ Optimizer::PassToken CreateUnifyConstantPass();
// OpSpecConstantOp.
Optimizer::PassToken CreateEliminateDeadConstantPass();
// Creates an inline pass.
// An inline pass exhaustively inlines all function calls in all functions
// designated as an entry point. The intent is to enable, albeit through
// brute force, analysis and optimization across function calls by subsequent
// passes. As the inlining is exhaustive, there is no attempt to optimize for
// size or runtime performance. Functions that are not designated as entry
// points are not changed.
Optimizer::PassToken CreateInlinePass();
} // namespace spvtools
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_

Просмотреть файл

@ -20,6 +20,7 @@ add_library(SPIRV-Tools-opt
function.h
fold_spec_constant_op_and_composite_pass.h
freeze_spec_constant_value_pass.h
inline_pass.h
instruction.h
ir_loader.h
log.h
@ -35,12 +36,14 @@ add_library(SPIRV-Tools-opt
type_manager.h
unify_const_pass.h
basic_block.cpp
build_module.cpp
def_use_manager.cpp
eliminate_dead_constant_pass.cpp
function.cpp
fold_spec_constant_op_and_composite_pass.cpp
freeze_spec_constant_value_pass.cpp
inline_pass.cpp
instruction.cpp
ir_loader.cpp
module.cpp

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) 2016 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 "basic_block.h"
namespace spvtools {
namespace ir {
void BasicBlock::ForEachSuccessorLabel(
const std::function<void(const uint32_t)>& f) {
const auto br = &*insts_.back();
switch (br->opcode()) {
case SpvOpBranch: {
f(br->GetOperand(0).words[0]);
} break;
case SpvOpBranchConditional:
case SpvOpSwitch: {
bool is_first = true;
br->ForEachInId([&is_first, &f](const uint32_t* idp) {
if (!is_first) f(*idp);
is_first = false;
});
} break;
default:
break;
}
}
} // namespace ir
} // namespace spvtools

Просмотреть файл

@ -47,6 +47,9 @@ class BasicBlock {
// The label starting this basic block.
Instruction& Label() { return *label_; }
// Returns the id of the label at the top of this block
inline uint32_t label_id() const { return label_->result_id(); }
iterator begin() { return iterator(&insts_, insts_.begin()); }
iterator end() { return iterator(&insts_, insts_.end()); }
const_iterator cbegin() { return const_iterator(&insts_, insts_.cbegin()); }
@ -59,6 +62,15 @@ class BasicBlock {
inline void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
// Runs the given function |f| on each Phi instruction in this basic block,
// and optionally on the debug line instructions that might precede them.
inline void ForEachPhiInst(const std::function<void(Instruction*)>& f,
bool run_on_debug_line_insts = false);
// Runs the given function |f| on each label id of each successor block
void ForEachSuccessorLabel(
const std::function<void(const uint32_t)>& f);
private:
// The enclosing function.
Function* function_;
@ -92,6 +104,14 @@ inline void BasicBlock::ForEachInst(
->ForEachInst(f, run_on_debug_line_insts);
}
inline void BasicBlock::ForEachPhiInst(
const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
for (auto& inst : insts_) {
if (inst->opcode() != SpvOpPhi) break;
inst->ForEachInst(f, run_on_debug_line_insts);
}
}
} // namespace ir
} // namespace spvtools

Просмотреть файл

@ -27,8 +27,8 @@ namespace {
spv_result_t SetSpvHeader(void* builder, spv_endianness_t, uint32_t magic,
uint32_t version, uint32_t generator,
uint32_t id_bound, uint32_t reserved) {
reinterpret_cast<ir::IrLoader*>(builder)->SetModuleHeader(
magic, version, generator, id_bound, reserved);
reinterpret_cast<ir::IrLoader*>(builder)
->SetModuleHeader(magic, version, generator, id_bound, reserved);
return SPV_SUCCESS;
};

Просмотреть файл

@ -36,13 +36,20 @@ void Function::ForEachInst(const std::function<void(const Instruction*)>& f,
->ForEachInst(f, run_on_debug_line_insts);
for (const auto& bb : blocks_)
static_cast<const BasicBlock*>(bb.get())->ForEachInst(
f, run_on_debug_line_insts);
static_cast<const BasicBlock*>(bb.get())
->ForEachInst(f, run_on_debug_line_insts);
if (end_inst_)
static_cast<const Instruction*>(end_inst_.get())
->ForEachInst(f, run_on_debug_line_insts);
}
void Function::ForEachParam(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts) const {
for (const auto& param : params_)
static_cast<const Instruction*>(param.get())
->ForEachInst(f, run_on_debug_line_insts);
}
} // namespace ir
} // namespace spvtools

Просмотреть файл

@ -51,6 +51,12 @@ class Function {
// Saves the given function end instruction.
inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst);
// Returns function's id
inline uint32_t result_id() const { return def_inst_->result_id(); }
// Returns function's type id
inline uint32_t type_id() const { return def_inst_->type_id(); }
iterator begin() { return iterator(&blocks_, blocks_.begin()); }
iterator end() { return iterator(&blocks_, blocks_.end()); }
const_iterator cbegin() { return const_iterator(&blocks_, blocks_.cbegin()); }
@ -63,6 +69,11 @@ class Function {
void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
// Runs the given function |f| on each parameter instruction in this function,
// and optionally on debug line instructions that might precede them.
void ForEachParam(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
private:
// The enclosing module.
Module* module_;

440
source/opt/inline_pass.cpp Normal file
Просмотреть файл

@ -0,0 +1,440 @@
// Copyright (c) 2017 The Khronos Group Inc.
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG 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 "inline_pass.h"
// Indices of operands in SPIR-V instructions
static const int kSpvEntryPointFunctionId = 1;
static const int kSpvFunctionCallFunctionId = 2;
static const int kSpvFunctionCallArgumentId = 3;
static const int kSpvReturnValueId = 0;
static const int kSpvTypePointerStorageClass = 1;
static const int kSpvTypePointerTypeId = 2;
namespace spvtools {
namespace opt {
uint32_t InlinePass::FindPointerToType(uint32_t type_id,
SpvStorageClass storage_class) {
ir::Module::inst_iterator type_itr = module_->types_values_begin();
for (; type_itr != module_->types_values_end(); ++type_itr) {
const ir::Instruction* type_inst = &*type_itr;
if (type_inst->opcode() == SpvOpTypePointer &&
type_inst->GetSingleWordOperand(kSpvTypePointerTypeId) == type_id &&
type_inst->GetSingleWordOperand(kSpvTypePointerStorageClass) ==
storage_class)
return type_inst->result_id();
}
return 0;
}
uint32_t InlinePass::AddPointerToType(uint32_t type_id,
SpvStorageClass storage_class) {
uint32_t resultId = TakeNextId();
std::unique_ptr<ir::Instruction> type_inst(new ir::Instruction(
SpvOpTypePointer, 0, resultId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
{uint32_t(storage_class)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_id}}}));
module_->AddType(std::move(type_inst));
return resultId;
}
void InlinePass::AddBranch(uint32_t label_id,
std::unique_ptr<ir::BasicBlock>* block_ptr) {
std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
SpvOpBranch, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
(*block_ptr)->AddInstruction(std::move(newBranch));
}
void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id,
std::unique_ptr<ir::BasicBlock>* block_ptr) {
std::unique_ptr<ir::Instruction> newStore(new ir::Instruction(
SpvOpStore, 0, 0, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}}));
(*block_ptr)->AddInstruction(std::move(newStore));
}
void InlinePass::AddLoad(uint32_t type_id, uint32_t resultId, uint32_t ptr_id,
std::unique_ptr<ir::BasicBlock>* block_ptr) {
std::unique_ptr<ir::Instruction> newLoad(new ir::Instruction(
SpvOpLoad, type_id, resultId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}}));
(*block_ptr)->AddInstruction(std::move(newLoad));
}
std::unique_ptr<ir::Instruction> InlinePass::NewLabel(uint32_t label_id) {
std::unique_ptr<ir::Instruction> newLabel(
new ir::Instruction(SpvOpLabel, 0, label_id, {}));
return newLabel;
}
void InlinePass::MapParams(
ir::Function* calleeFn,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
std::unordered_map<uint32_t, uint32_t>* callee2caller) {
int param_idx = 0;
calleeFn->ForEachParam(
[&call_inst_itr, &param_idx, &callee2caller](const ir::Instruction* cpi) {
const uint32_t pid = cpi->result_id();
(*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
kSpvFunctionCallArgumentId + param_idx);
param_idx++;
});
}
void InlinePass::CloneAndMapLocals(
ir::Function* calleeFn,
std::vector<std::unique_ptr<ir::Instruction>>* new_vars,
std::unordered_map<uint32_t, uint32_t>* callee2caller) {
auto callee_block_itr = calleeFn->begin();
auto callee_var_itr = callee_block_itr->begin();
while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) {
std::unique_ptr<ir::Instruction> var_inst(
new ir::Instruction(*callee_var_itr));
uint32_t newId = TakeNextId();
var_inst->SetResultId(newId);
(*callee2caller)[callee_var_itr->result_id()] = newId;
new_vars->push_back(std::move(var_inst));
callee_var_itr++;
}
}
uint32_t InlinePass::CreateReturnVar(
ir::Function* calleeFn,
std::vector<std::unique_ptr<ir::Instruction>>* new_vars) {
uint32_t returnVarId = 0;
const uint32_t calleeTypeId = calleeFn->type_id();
const ir::Instruction* calleeType =
def_use_mgr_->id_to_defs().find(calleeTypeId)->second;
if (calleeType->opcode() != SpvOpTypeVoid) {
// Find or create ptr to callee return type.
uint32_t returnVarTypeId =
FindPointerToType(calleeTypeId, SpvStorageClassFunction);
if (returnVarTypeId == 0)
returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
// Add return var to new function scope variables.
returnVarId = TakeNextId();
std::unique_ptr<ir::Instruction> var_inst(new ir::Instruction(
SpvOpVariable, returnVarTypeId, returnVarId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
{SpvStorageClassFunction}}}));
new_vars->push_back(std::move(var_inst));
}
return returnVarId;
}
bool InlinePass::IsSameBlockOp(const ir::Instruction* inst) const {
return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
}
void InlinePass::CloneSameBlockOps(
std::unique_ptr<ir::Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* postCallSB,
std::unordered_map<uint32_t, ir::Instruction*>* preCallSB,
std::unique_ptr<ir::BasicBlock>* block_ptr) {
(*inst)
->ForEachInId([&postCallSB, &preCallSB, &block_ptr, this](uint32_t* iid) {
const auto mapItr = (*postCallSB).find(*iid);
if (mapItr == (*postCallSB).end()) {
const auto mapItr2 = (*preCallSB).find(*iid);
if (mapItr2 != (*preCallSB).end()) {
// Clone pre-call same-block ops, map result id.
const ir::Instruction* inInst = mapItr2->second;
std::unique_ptr<ir::Instruction> sb_inst(
new ir::Instruction(*inInst));
CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr);
const uint32_t rid = sb_inst->result_id();
const uint32_t nid = this->TakeNextId();
sb_inst->SetResultId(nid);
(*postCallSB)[rid] = nid;
*iid = nid;
(*block_ptr)->AddInstruction(std::move(sb_inst));
}
} else {
// Reset same-block op operand.
*iid = mapItr->second;
}
});
}
void InlinePass::GenInlineCode(
std::vector<std::unique_ptr<ir::BasicBlock>>* new_blocks,
std::vector<std::unique_ptr<ir::Instruction>>* new_vars,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
ir::UptrVectorIterator<ir::BasicBlock> call_block_itr) {
// Map from all ids in the callee to their equivalent id in the caller
// as callee instructions are copied into caller.
std::unordered_map<uint32_t, uint32_t> callee2caller;
// Pre-call same-block insts
std::unordered_map<uint32_t, ir::Instruction*> preCallSB;
// Post-call same-block op ids
std::unordered_map<uint32_t, uint32_t> postCallSB;
ir::Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
kSpvFunctionCallFunctionId)];
// Map parameters to actual arguments.
MapParams(calleeFn, call_inst_itr, &callee2caller);
// Define caller local variables for all callee variables and create map to
// them.
CloneAndMapLocals(calleeFn, new_vars, &callee2caller);
// Create return var if needed.
uint32_t returnVarId = CreateReturnVar(calleeFn, new_vars);
// Clone and map callee code. Copy caller block code to beginning of
// first block and end of last block.
bool prevInstWasReturn = false;
uint32_t returnLabelId = 0;
bool multiBlocks = false;
const uint32_t calleeTypeId = calleeFn->type_id();
std::unique_ptr<ir::BasicBlock> new_blk_ptr;
calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
&call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
&returnLabelId, &returnVarId, &calleeTypeId,
&multiBlocks, &postCallSB, &preCallSB, this](
const ir::Instruction* cpi) {
switch (cpi->opcode()) {
case SpvOpFunction:
case SpvOpFunctionParameter:
case SpvOpVariable:
// Already processed
break;
case SpvOpLabel: {
// If previous instruction was early return, insert branch
// instruction to return block.
if (prevInstWasReturn) {
if (returnLabelId == 0) returnLabelId = this->TakeNextId();
AddBranch(returnLabelId, &new_blk_ptr);
prevInstWasReturn = false;
}
// Finish current block (if it exists) and get label for next block.
uint32_t labelId;
bool firstBlock = false;
if (new_blk_ptr != nullptr) {
new_blocks->push_back(std::move(new_blk_ptr));
// If result id is already mapped, use it, otherwise get a new
// one.
const uint32_t rid = cpi->result_id();
const auto mapItr = callee2caller.find(rid);
labelId = (mapItr != callee2caller.end()) ? mapItr->second
: this->TakeNextId();
} else {
// First block needs to use label of original block
// but map callee label in case of phi reference.
labelId = call_block_itr->label_id();
callee2caller[cpi->result_id()] = labelId;
firstBlock = true;
}
// Create first/next block.
new_blk_ptr.reset(new ir::BasicBlock(NewLabel(labelId)));
if (firstBlock) {
// Copy contents of original caller block up to call instruction.
for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
cii++) {
std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
// Remember same-block ops for possible regeneration.
if (IsSameBlockOp(&*cp_inst)) {
auto* sb_inst_ptr = cp_inst.get();
preCallSB[cp_inst->result_id()] = sb_inst_ptr;
}
new_blk_ptr->AddInstruction(std::move(cp_inst));
}
} else {
multiBlocks = true;
}
} break;
case SpvOpReturnValue: {
// Store return value to return variable.
assert(returnVarId != 0);
uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
const auto mapItr = callee2caller.find(valId);
if (mapItr != callee2caller.end()) {
valId = mapItr->second;
}
AddStore(returnVarId, valId, &new_blk_ptr);
// Remember we saw a return; if followed by a label, will need to
// insert branch.
prevInstWasReturn = true;
} break;
case SpvOpReturn: {
// Remember we saw a return; if followed by a label, will need to
// insert branch.
prevInstWasReturn = true;
} break;
case SpvOpFunctionEnd: {
// If there was an early return, create return label/block.
// If previous instruction was return, insert branch instruction
// to return block.
if (returnLabelId != 0) {
if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
new_blk_ptr.reset(new ir::BasicBlock(NewLabel(returnLabelId)));
multiBlocks = true;
}
// Load return value into result id of call, if it exists.
if (returnVarId != 0) {
const uint32_t resId = call_inst_itr->result_id();
assert(resId != 0);
AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
}
// Copy remaining instructions from caller block.
auto cii = call_inst_itr;
for (cii++; cii != call_block_itr->end(); cii++) {
std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
// If multiple blocks generated, regenerate any same-block
// instruction that has not been seen in this last block.
if (multiBlocks) {
CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB, &new_blk_ptr);
// Remember same-block ops in this block.
if (IsSameBlockOp(&*cp_inst)) {
const uint32_t rid = cp_inst->result_id();
postCallSB[rid] = rid;
}
}
new_blk_ptr->AddInstruction(std::move(cp_inst));
}
// Finalize inline code.
new_blocks->push_back(std::move(new_blk_ptr));
} break;
default: {
// Copy callee instruction and remap all input Ids.
std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cpi));
cp_inst->ForEachInId([&callee2caller, &cpi, this](uint32_t* iid) {
const auto mapItr = callee2caller.find(*iid);
if (mapItr != callee2caller.end()) {
*iid = mapItr->second;
} else if (cpi->has_labels()) {
const ir::Instruction* inst =
def_use_mgr_->id_to_defs().find(*iid)->second;
if (inst->opcode() == SpvOpLabel) {
// Forward label reference. Allocate a new label id, map it,
// use it and check for it at each label.
const uint32_t nid = this->TakeNextId();
callee2caller[*iid] = nid;
*iid = nid;
}
}
});
// Map and reset result id.
const uint32_t rid = cp_inst->result_id();
if (rid != 0) {
const uint32_t nid = this->TakeNextId();
callee2caller[rid] = nid;
cp_inst->SetResultId(nid);
}
new_blk_ptr->AddInstruction(std::move(cp_inst));
} break;
}
});
// Update block map given replacement blocks.
for (auto& blk : *new_blocks) {
id2block_[blk->label_id()] = &*blk;
}
}
bool InlinePass::Inline(ir::Function* func) {
bool modified = false;
// Using block iterators here because of block erasures and insertions.
for (auto bi = func->begin(); bi != func->end(); bi++) {
for (auto ii = bi->begin(); ii != bi->end();) {
if (ii->opcode() == SpvOp::SpvOpFunctionCall) {
// Inline call.
std::vector<std::unique_ptr<ir::BasicBlock>> newBlocks;
std::vector<std::unique_ptr<ir::Instruction>> newVars;
GenInlineCode(&newBlocks, &newVars, ii, bi);
// Update phi functions in successor blocks if call block
// is replaced with more than one block.
if (newBlocks.size() > 1) {
const auto firstBlk = newBlocks.begin();
const auto lastBlk = newBlocks.end() - 1;
const uint32_t firstId = (*firstBlk)->label_id();
const uint32_t lastId = (*lastBlk)->label_id();
(*lastBlk)
->ForEachSuccessorLabel([&firstId, &lastId, this](uint32_t succ) {
ir::BasicBlock* sbp = this->id2block_[succ];
sbp->ForEachPhiInst([&firstId, &lastId](ir::Instruction* phi) {
phi->ForEachInId([&firstId, &lastId](uint32_t* id) {
if (*id == firstId) *id = lastId;
});
});
});
}
// Replace old calling block with new block(s).
bi = bi.Erase();
bi = bi.InsertBefore(&newBlocks);
// Insert new function variables.
if (newVars.size() > 0) func->begin()->begin().InsertBefore(&newVars);
// Restart inlining at beginning of calling block.
ii = bi->begin();
modified = true;
} else {
ii++;
}
}
}
return modified;
}
void InlinePass::Initialize(ir::Module* module) {
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
// Initialize next unused Id.
next_id_ = module->id_bound();
// Save module.
module_ = module;
// Initialize function and block maps.
id2function_.clear();
id2block_.clear();
for (auto& fn : *module_) {
id2function_[fn.result_id()] = &fn;
for (auto& blk : fn) {
id2block_[blk.label_id()] = &blk;
}
}
};
Pass::Status InlinePass::ProcessImpl() {
// Do exhaustive inlining on each entry point function in module
bool modified = false;
for (auto& e : module_->entry_points()) {
ir::Function* fn =
id2function_[e.GetSingleWordOperand(kSpvEntryPointFunctionId)];
modified = modified || Inline(fn);
}
FinalizeNextId(module_);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
InlinePass::InlinePass()
: module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {}
Pass::Status InlinePass::Process(ir::Module* module) {
Initialize(module);
return ProcessImpl();
}
} // namespace opt
} // namespace spvtools

143
source/opt/inline_pass.h Normal file
Просмотреть файл

@ -0,0 +1,143 @@
// Copyright (c) 2017 The Khronos Group Inc.
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG 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_INLINE_PASS_H_
#define LIBSPIRV_OPT_INLINE_PASS_H_
#include <algorithm>
#include <memory>
#include <unordered_map>
#include <vector>
#include "def_use_manager.h"
#include "module.h"
#include "pass.h"
namespace spvtools {
namespace opt {
// See optimizer.hpp for documentation.
class InlinePass : public Pass {
public:
InlinePass();
Status Process(ir::Module*) override;
const char* name() const override { return "inline"; }
private:
// Return the next available Id and increment it.
inline uint32_t TakeNextId() { return next_id_++; }
// Write the next available Id back to the module.
inline void FinalizeNextId(ir::Module* module) {
module->SetIdBound(next_id_);
}
// Find pointer to type and storage in module, return its resultId,
// 0 if not found. TODO(greg-lunarg): Move this into type manager.
uint32_t FindPointerToType(uint32_t type_id, SpvStorageClass storage_class);
// Add pointer to type to module and return resultId.
uint32_t AddPointerToType(uint32_t type_id, SpvStorageClass storage_class);
// Add unconditional branch to labelId to end of block block_ptr.
void AddBranch(uint32_t labelId, std::unique_ptr<ir::BasicBlock>* block_ptr);
// Add store of valId to ptrId to end of block block_ptr.
void AddStore(uint32_t ptrId, uint32_t valId,
std::unique_ptr<ir::BasicBlock>* block_ptr);
// Add load of ptrId into resultId to end of block block_ptr.
void AddLoad(uint32_t typeId, uint32_t resultId, uint32_t ptrId,
std::unique_ptr<ir::BasicBlock>* block_ptr);
// Return new label.
std::unique_ptr<ir::Instruction> NewLabel(uint32_t label_id);
// Map callee params to caller args
void MapParams(ir::Function* calleeFn,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
std::unordered_map<uint32_t, uint32_t>* callee2caller);
// Clone and map callee locals
void CloneAndMapLocals(
ir::Function* calleeFn,
std::vector<std::unique_ptr<ir::Instruction>>* new_vars,
std::unordered_map<uint32_t, uint32_t>* callee2caller);
// Create return variable for callee clone code if needed. Return id
// if created, otherwise 0.
uint32_t CreateReturnVar(
ir::Function* calleeFn,
std::vector<std::unique_ptr<ir::Instruction>>* new_vars);
// Return true if instruction must be in the same block that its result
// is used.
bool IsSameBlockOp(const ir::Instruction* inst) const;
// Clone operands which must be in same block as consumer instructions.
// Look in preCallSB for instructions that need cloning. Look in
// postCallSB for instructions already cloned. Add cloned instruction
// to postCallSB.
void CloneSameBlockOps(
std::unique_ptr<ir::Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* postCallSB,
std::unordered_map<uint32_t, ir::Instruction*>* preCallSB,
std::unique_ptr<ir::BasicBlock>* block_ptr);
// Return in new_blocks the result of inlining the call at call_inst_itr
// within its block at call_block_itr. The block at call_block_itr can
// just be replaced with the blocks in new_blocks. Any additional branches
// are avoided. Debug instructions are cloned along with their callee
// instructions. Early returns are replaced by a store to a local return
// variable and a branch to a (created) exit block where the local variable
// is returned. Formal parameters are trivially mapped to their actual
// parameters. Note that the first block in new_blocks retains the label
// of the original calling block. Also note that if an exit block is
// created, it is the last block of new_blocks.
//
// Also return in new_vars additional OpVariable instructions required by
// and to be inserted into the caller function after the block at
// call_block_itr is replaced with new_blocks.
void GenInlineCode(std::vector<std::unique_ptr<ir::BasicBlock>>* new_blocks,
std::vector<std::unique_ptr<ir::Instruction>>* new_vars,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
ir::UptrVectorIterator<ir::BasicBlock> call_block_itr);
// Exhaustively inline all function calls in func as well as in
// all code that is inlined into func. Return true if func is modified.
bool Inline(ir::Function* func);
void Initialize(ir::Module* module);
Pass::Status ProcessImpl();
ir::Module* module_;
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Map from function's result id to function.
std::unordered_map<uint32_t, ir::Function*> id2function_;
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
// Next unused ID
uint32_t next_id_;
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_INLINE_PASS_H_

Просмотреть файл

@ -20,6 +20,8 @@
#include <utility>
#include <vector>
#include "operand.h"
#include "spirv-tools/libspirv.h"
#include "spirv/1.1/spirv.h"
@ -135,6 +137,8 @@ class Instruction {
inline void SetInOperand(uint32_t index, std::vector<uint32_t>&& data);
// Sets the result type id.
inline void SetResultType(uint32_t ty_id);
// Sets the result id
inline void SetResultId(uint32_t res_id);
// The following methods are similar to the above, but are for in operands.
uint32_t NumInOperands() const {
@ -162,6 +166,13 @@ class Instruction {
inline void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
// Runs the given function |f| on all "in" operand ids
inline void ForEachInId(const std::function<void(uint32_t*)>& f);
inline void ForEachInId(const std::function<void(const uint32_t*)>& f) const;
// Returns true if any operands can be labels
inline bool has_labels() const;
// Pushes the binary segments for this instruction into the back of *|binary|.
void ToBinaryWithoutAttachedDebugInsts(std::vector<uint32_t>* binary) const;
@ -194,6 +205,13 @@ inline void Instruction::SetInOperand(uint32_t index,
operands_[index + TypeResultIdCount()].words = std::move(data);
}
inline void Instruction::SetResultId(uint32_t res_id) {
result_id_ = res_id;
auto ridx = (type_id_ != 0) ? 1 : 0;
assert(operands_[ridx].type == SPV_OPERAND_TYPE_RESULT_ID);
operands_[ridx].words = {res_id};
}
inline void Instruction::SetResultType(uint32_t ty_id) {
if (type_id_ != 0) {
type_id_ = ty_id;
@ -228,6 +246,33 @@ inline void Instruction::ForEachInst(
f(this);
}
inline void Instruction::ForEachInId(const std::function<void(uint32_t*)>& f) {
for (auto& opnd : operands_)
if (opnd.type == SPV_OPERAND_TYPE_ID) f(&opnd.words[0]);
}
inline void Instruction::ForEachInId(
const std::function<void(const uint32_t*)>& f) const {
for (const auto& opnd : operands_)
if (opnd.type == SPV_OPERAND_TYPE_ID) f(&opnd.words[0]);
}
inline bool Instruction::has_labels() const {
switch (opcode_) {
case SpvOpSelectionMerge:
case SpvOpBranch:
case SpvOpLoopMerge:
case SpvOpBranchConditional:
case SpvOpSwitch:
case SpvOpPhi:
return true;
break;
default:
break;
}
return false;
}
} // namespace ir
} // namespace spvtools

Просмотреть файл

@ -81,6 +81,24 @@ class UptrVectorIterator
inline typename std::enable_if<!IsConstForMethod, UptrVectorIterator>::type
InsertBefore(Uptr value);
// Inserts the given |valueVector| to the position pointed to by this iterator
// and returns an iterator to the first newly inserted value.
// If the underlying vector changes capacity, all previous iterators will be
// invalidated. Otherwise, those previous iterators pointing to after the
// insertion point will be invalidated.
template <bool IsConstForMethod = IsConst>
inline typename std::enable_if<!IsConstForMethod, UptrVectorIterator>::type
InsertBefore(UptrVector* valueVector);
// Erases the value at the position pointed to by this iterator
// and returns an iterator to the following value.
// If the underlying vector changes capacity, all previous iterators will be
// invalidated. Otherwise, those previous iterators pointing to after the
// erasure point will be invalidated.
template <bool IsConstForMethod = IsConst>
inline typename std::enable_if<!IsConstForMethod, UptrVectorIterator>::type
Erase();
private:
UptrVector* container_; // The container we are manipulating.
UnderlyingIterator iterator_; // The raw iterator from the container.
@ -183,6 +201,30 @@ inline
return UptrVectorIterator(container_, container_->begin() + index);
}
template <typename VT, bool IC>
template <bool IsConstForMethod>
inline
typename std::enable_if<!IsConstForMethod, UptrVectorIterator<VT, IC>>::type
UptrVectorIterator<VT, IC>::InsertBefore(UptrVector* values) {
const auto pos = iterator_ - container_->begin();
const auto origsz = container_->size();
container_->resize(origsz + values->size());
std::move_backward(container_->begin() + pos, container_->begin() + origsz,
container_->end());
std::move(values->begin(), values->end(), container_->begin() + pos);
return UptrVectorIterator(container_, container_->begin() + pos);
}
template <typename VT, bool IC>
template <bool IsConstForMethod>
inline
typename std::enable_if<!IsConstForMethod, UptrVectorIterator<VT, IC>>::type
UptrVectorIterator<VT, IC>::Erase() {
auto index = iterator_ - container_->begin();
(void)container_->erase(iterator_);
return UptrVectorIterator(container_, container_->begin() + index);
}
} // namespace ir
} // namespace spvtools

Просмотреть файл

@ -76,9 +76,9 @@ void Module::ForEachInst(const std::function<void(Instruction*)>& f,
void Module::ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts) const {
#define DELEGATE(i) \
static_cast<const Instruction*>(i.get())->ForEachInst( \
f, run_on_debug_line_insts)
#define DELEGATE(i) \
static_cast<const Instruction*>(i.get()) \
->ForEachInst(f, run_on_debug_line_insts)
for (auto& i : capabilities_) DELEGATE(i);
for (auto& i : extensions_) DELEGATE(i);
for (auto& i : ext_inst_imports_) DELEGATE(i);
@ -89,8 +89,8 @@ void Module::ForEachInst(const std::function<void(const Instruction*)>& f,
for (auto& i : annotations_) DELEGATE(i);
for (auto& i : types_values_) DELEGATE(i);
for (auto& i : functions_) {
static_cast<const Function*>(i.get())->ForEachInst(f,
run_on_debug_line_insts);
static_cast<const Function*>(i.get())
->ForEachInst(f, run_on_debug_line_insts);
}
#undef DELEGATE
}
@ -112,15 +112,13 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const {
uint32_t Module::ComputeIdBound() const {
uint32_t highest = 0;
ForEachInst(
[&highest](const Instruction* inst) {
for (const auto& operand : *inst) {
if (spvIsIdType(operand.type)) {
highest = std::max(highest, operand.words[0]);
}
}
},
true /* scan debug line insts as well */);
ForEachInst([&highest](const Instruction* inst) {
for (const auto& operand : *inst) {
if (spvIsIdType(operand.type)) {
highest = std::max(highest, operand.words[0]);
}
}
}, true /* scan debug line insts as well */);
return highest + 1;
}

Просмотреть файл

@ -86,6 +86,8 @@ class Module {
std::vector<Instruction*> GetConstants();
std::vector<const Instruction*> GetConstants() const;
inline uint32_t id_bound() const { return header_.bound; }
// Iterators for debug instructions (excluding OpLine & OpNoLine) contained in
// this module.
inline inst_iterator debug_begin();
@ -93,6 +95,10 @@ class Module {
inline IteratorRange<inst_iterator> debugs();
inline IteratorRange<const_inst_iterator> debugs() const;
// Iterators for entry point instructions contained in this module
inline IteratorRange<inst_iterator> entry_points();
inline IteratorRange<const_inst_iterator> entry_points() const;
// Clears all debug instructions (excluding OpLine & OpNoLine).
void debug_clear() { debugs_.clear(); }
@ -204,6 +210,14 @@ inline IteratorRange<Module::const_inst_iterator> Module::debugs() const {
return make_const_range(debugs_);
}
inline IteratorRange<Module::inst_iterator> Module::entry_points() {
return make_range(entry_points_);
}
inline IteratorRange<Module::const_inst_iterator> Module::entry_points() const {
return make_const_range(entry_points_);
}
inline IteratorRange<Module::inst_iterator> Module::annotations() {
return make_range(annotations_);
}

Просмотреть файл

@ -118,4 +118,8 @@ Optimizer::PassToken CreateEliminateDeadConstantPass() {
MakeUnique<opt::EliminateDeadConstantPass>());
}
Optimizer::PassToken CreateInlinePass() {
return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::InlinePass>());
}
} // namespace spvtools

Просмотреть файл

@ -19,6 +19,7 @@
#include "eliminate_dead_constant_pass.h"
#include "fold_spec_constant_op_and_composite_pass.h"
#include "inline_pass.h"
#include "freeze_spec_constant_value_pass.h"
#include "null_pass.h"
#include "set_spec_constant_default_value_pass.h"

Просмотреть файл

@ -34,7 +34,7 @@ bool CompareTwoVectors(const U32VecVec a, const U32VecVec b) {
if (size == 0) return true;
if (size == 1) return a.front() == b.front();
std::vector<const std::vector<uint32_t> *> a_ptrs, b_ptrs;
std::vector<const std::vector<uint32_t>*> a_ptrs, b_ptrs;
a_ptrs.reserve(size);
a_ptrs.reserve(size);
for (uint32_t i = 0; i < size; ++i) {
@ -42,10 +42,10 @@ bool CompareTwoVectors(const U32VecVec a, const U32VecVec b) {
b_ptrs.push_back(&b[i]);
}
const auto cmp = [](const std::vector<uint32_t>* m,
const std::vector<uint32_t>* n) {
return m->front() < n->front();
};
const auto cmp =
[](const std::vector<uint32_t>* m, const std::vector<uint32_t>* n) {
return m->front() < n->front();
};
std::sort(a_ptrs.begin(), a_ptrs.end(), cmp);
std::sort(b_ptrs.begin(), b_ptrs.end(), cmp);

Просмотреть файл

@ -38,6 +38,11 @@ add_spvtools_unittest(TARGET pass_freeze_spec_const
LIBS SPIRV-Tools-opt
)
add_spvtools_unittest(TARGET pass_inline
SRCS inline_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)
add_spvtools_unittest(TARGET pass_eliminate_dead_const
SRCS eliminate_dead_const_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt

1371
test/opt/inline_test.cpp Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -45,28 +45,36 @@ class PassTest : public TestT {
tools_(SPV_ENV_UNIVERSAL_1_1),
manager_(new opt::PassManager()) {}
// Runs the given |pass| on the binary assembled from the |assembly|, and
// disassebles the optimized binary. Returns a tuple of disassembly string
// and the boolean value returned from pass Process() function.
std::tuple<std::string, opt::Pass::Status> OptimizeAndDisassemble(
// Runs the given |pass| on the binary assembled from the |original|.
// Returns a tuple of the optimized binary and the boolean value returned
// from pass Process() function.
std::tuple<std::vector<uint32_t>, opt::Pass::Status> OptimizeToBinary(
opt::Pass* pass, const std::string& original, bool skip_nop) {
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer_, original);
EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
<< original << std::endl;
if (!module) {
return std::make_tuple(std::string(), opt::Pass::Status::Failure);
return std::make_tuple(std::vector<uint32_t>(),
opt::Pass::Status::Failure);
}
const auto status = pass->Process(module.get());
std::vector<uint32_t> binary;
module->ToBinary(&binary, skip_nop);
std::string optimized;
EXPECT_TRUE(tools_.Disassemble(binary, &optimized))
<< "Disassembling failed for shader:\n"
<< original << std::endl;
return std::make_tuple(optimized, status);
return std::make_tuple(binary, status);
}
// Runs a single pass of class |PassT| on the binary assembled from the
// |assembly|. Returns a tuple of the optimized binary and the boolean value
// from the pass Process() function.
template <typename PassT, typename... Args>
std::tuple<std::vector<uint32_t>, opt::Pass::Status> SinglePassRunToBinary(
const std::string& assembly, bool skip_nop, Args&&... args) {
auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
pass->SetMessageConsumer(consumer_);
return OptimizeToBinary(pass.get(), assembly, skip_nop);
}
// Runs a single pass of class |PassT| on the binary assembled from the
@ -75,9 +83,52 @@ class PassTest : public TestT {
template <typename PassT, typename... Args>
std::tuple<std::string, opt::Pass::Status> SinglePassRunAndDisassemble(
const std::string& assembly, bool skip_nop, Args&&... args) {
auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
pass->SetMessageConsumer(consumer_);
return OptimizeAndDisassemble(pass.get(), assembly, skip_nop);
std::vector<uint32_t> optimized_bin;
auto status = opt::Pass::Status::SuccessWithoutChange;
std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
assembly, skip_nop, std::forward<Args>(args)...);
std::string optimized_asm;
EXPECT_TRUE(tools_.Disassemble(optimized_bin, &optimized_asm))
<< "Disassembling failed for shader:\n"
<< assembly << std::endl;
return std::make_tuple(optimized_asm, status);
}
// Runs a single pass of class |PassT| on the binary assembled from the
// |original| assembly, and checks whether the optimized binary can be
// disassembled to the |expected| assembly. Optionally will also validate
// the optimized binary. This does *not* involve pass manager. Callers
// are suggested to use SCOPED_TRACE() for better messages.
template <typename PassT, typename... Args>
void SinglePassRunAndCheck(const std::string& original,
const std::string& expected, bool skip_nop,
bool do_validation, Args&&... args) {
std::vector<uint32_t> optimized_bin;
auto status = opt::Pass::Status::SuccessWithoutChange;
std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
original, skip_nop, std::forward<Args>(args)...);
// Check whether the pass returns the correct modification indication.
EXPECT_NE(opt::Pass::Status::Failure, status);
EXPECT_EQ(original == expected,
status == opt::Pass::Status::SuccessWithoutChange);
if (do_validation) {
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_1;
spv_context context = spvContextCreate(target_env);
spv_diagnostic diagnostic = nullptr;
spv_const_binary_t binary = {optimized_bin.data(),
optimized_bin.size()};
spv_result_t error = spvValidate(context, &binary, &diagnostic);
EXPECT_EQ(error, 0);
if (error != 0)
spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
spvContextDestroy(context);
}
std::string optimized_asm;
EXPECT_TRUE(tools_.Disassemble(optimized_bin, &optimized_asm))
<< "Disassembling failed for shader:\n"
<< original << std::endl;
EXPECT_EQ(expected, optimized_asm);
}
// Runs a single pass of class |PassT| on the binary assembled from the
@ -88,15 +139,8 @@ class PassTest : public TestT {
void SinglePassRunAndCheck(const std::string& original,
const std::string& expected, bool skip_nop,
Args&&... args) {
std::string optimized;
auto status = opt::Pass::Status::SuccessWithoutChange;
std::tie(optimized, status) = SinglePassRunAndDisassemble<PassT>(
original, skip_nop, std::forward<Args>(args)...);
// Check whether the pass returns the correct modification indication.
EXPECT_NE(opt::Pass::Status::Failure, status);
EXPECT_EQ(original == expected,
status == opt::Pass::Status::SuccessWithoutChange);
EXPECT_EQ(expected, optimized);
SinglePassRunAndCheck<PassT>(original, expected, skip_nop, false,
std::forward<Args>(args)...);
}
// Adds a pass to be run.

Просмотреть файл

@ -61,6 +61,8 @@ Options:
e.g.: --set-spec-const-default-value "1:100 2:400"
--unify-const
Remove the duplicated constants.
--inline-entry-points-all
Exhaustively inline all function calls in entry points
-h, --help Print this help.
--version Display optimizer version information.
)",
@ -121,6 +123,8 @@ int main(int argc, char** argv) {
}
} else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
pass_manager.AddPass<opt::FreezeSpecConstantValuePass>();
} else if (0 == strcmp(cur_arg, "--inline-entry-points-all")) {
pass_manager.AddPass<opt::InlinePass>();
} else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) {
pass_manager.AddPass<opt::EliminateDeadConstantPass>();
} else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {