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:
Родитель
b85997a1df
Коммит
04fcc66743
|
@ -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_;
|
||||
|
|
|
@ -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, ¶m_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
|
|
@ -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
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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")) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче