1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927
|
// 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 "source/opt/inline_pass.h"
#include <unordered_set>
#include <utility>
#include "source/cfa.h"
#include "source/opt/reflect.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace opt {
namespace {
// Indices of operands in SPIR-V instructions
constexpr int kSpvFunctionCallFunctionId = 2;
constexpr int kSpvFunctionCallArgumentId = 3;
constexpr int kSpvReturnValueId = 0;
constexpr int kSpvDebugDeclareVarInIdx = 3;
constexpr int kSpvAccessChainBaseInIdx = 0;
} // namespace
uint32_t InlinePass::AddPointerToType(uint32_t type_id,
spv::StorageClass storage_class) {
uint32_t resultId = context()->TakeNextId();
if (resultId == 0) {
return resultId;
}
std::unique_ptr<Instruction> type_inst(
new Instruction(context(), spv::Op::OpTypePointer, 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}}}));
context()->AddType(std::move(type_inst));
analysis::Type* pointeeTy;
std::unique_ptr<analysis::Pointer> pointerTy;
std::tie(pointeeTy, pointerTy) =
context()->get_type_mgr()->GetTypeAndPointerType(
type_id, spv::StorageClass::Function);
context()->get_type_mgr()->RegisterType(resultId, *pointerTy);
return resultId;
}
void InlinePass::AddBranch(uint32_t label_id,
std::unique_ptr<BasicBlock>* block_ptr) {
std::unique_ptr<Instruction> newBranch(
new Instruction(context(), spv::Op::OpBranch, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
(*block_ptr)->AddInstruction(std::move(newBranch));
}
void InlinePass::AddBranchCond(uint32_t cond_id, uint32_t true_id,
uint32_t false_id,
std::unique_ptr<BasicBlock>* block_ptr) {
std::unique_ptr<Instruction> newBranch(
new Instruction(context(), spv::Op::OpBranchConditional, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}}));
(*block_ptr)->AddInstruction(std::move(newBranch));
}
void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
std::unique_ptr<BasicBlock>* block_ptr) {
std::unique_ptr<Instruction> newLoopMerge(new Instruction(
context(), spv::Op::OpLoopMerge, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}}));
(*block_ptr)->AddInstruction(std::move(newLoopMerge));
}
void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id,
std::unique_ptr<BasicBlock>* block_ptr,
const Instruction* line_inst,
const DebugScope& dbg_scope) {
std::unique_ptr<Instruction> newStore(
new Instruction(context(), spv::Op::OpStore, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}}));
if (line_inst != nullptr) {
newStore->AddDebugLine(line_inst);
}
newStore->SetDebugScope(dbg_scope);
(*block_ptr)->AddInstruction(std::move(newStore));
}
void InlinePass::AddLoad(uint32_t type_id, uint32_t resultId, uint32_t ptr_id,
std::unique_ptr<BasicBlock>* block_ptr,
const Instruction* line_inst,
const DebugScope& dbg_scope) {
std::unique_ptr<Instruction> newLoad(
new Instruction(context(), spv::Op::OpLoad, type_id, resultId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}}));
if (line_inst != nullptr) {
newLoad->AddDebugLine(line_inst);
}
newLoad->SetDebugScope(dbg_scope);
(*block_ptr)->AddInstruction(std::move(newLoad));
}
std::unique_ptr<Instruction> InlinePass::NewLabel(uint32_t label_id) {
std::unique_ptr<Instruction> newLabel(
new Instruction(context(), spv::Op::OpLabel, 0, label_id, {}));
return newLabel;
}
uint32_t InlinePass::GetFalseId() {
if (false_id_ != 0) return false_id_;
false_id_ = get_module()->GetGlobalValue(spv::Op::OpConstantFalse);
if (false_id_ != 0) return false_id_;
uint32_t boolId = get_module()->GetGlobalValue(spv::Op::OpTypeBool);
if (boolId == 0) {
boolId = context()->TakeNextId();
if (boolId == 0) {
return 0;
}
get_module()->AddGlobalValue(spv::Op::OpTypeBool, boolId, 0);
}
false_id_ = context()->TakeNextId();
if (false_id_ == 0) {
return 0;
}
get_module()->AddGlobalValue(spv::Op::OpConstantFalse, false_id_, boolId);
return false_id_;
}
void InlinePass::MapParams(
Function* calleeFn, BasicBlock::iterator call_inst_itr,
std::unordered_map<uint32_t, uint32_t>* callee2caller) {
int param_idx = 0;
calleeFn->ForEachParam(
[&call_inst_itr, ¶m_idx, &callee2caller](const Instruction* cpi) {
const uint32_t pid = cpi->result_id();
(*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
kSpvFunctionCallArgumentId + param_idx);
++param_idx;
});
}
bool InlinePass::CloneAndMapLocals(
Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars,
std::unordered_map<uint32_t, uint32_t>* callee2caller,
analysis::DebugInlinedAtContext* inlined_at_ctx) {
auto callee_block_itr = calleeFn->begin();
auto callee_var_itr = callee_block_itr->begin();
while (callee_var_itr->opcode() == spv::Op::OpVariable ||
callee_var_itr->GetCommonDebugOpcode() ==
CommonDebugInfoDebugDeclare) {
if (callee_var_itr->opcode() != spv::Op::OpVariable) {
++callee_var_itr;
continue;
}
std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
uint32_t newId = context()->TakeNextId();
if (newId == 0) {
return false;
}
get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
var_inst->SetResultId(newId);
var_inst->UpdateDebugInlinedAt(
context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
callee_var_itr->GetDebugInlinedAt(), inlined_at_ctx));
(*callee2caller)[callee_var_itr->result_id()] = newId;
new_vars->push_back(std::move(var_inst));
++callee_var_itr;
}
return true;
}
uint32_t InlinePass::CreateReturnVar(
Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
uint32_t returnVarId = 0;
const uint32_t calleeTypeId = calleeFn->type_id();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
"Cannot create a return variable of type void.");
// Find or create ptr to callee return type.
uint32_t returnVarTypeId =
type_mgr->FindPointerToType(calleeTypeId, spv::StorageClass::Function);
if (returnVarTypeId == 0) {
returnVarTypeId =
AddPointerToType(calleeTypeId, spv::StorageClass::Function);
if (returnVarTypeId == 0) {
return 0;
}
}
// Add return var to new function scope variables.
returnVarId = context()->TakeNextId();
if (returnVarId == 0) {
return 0;
}
std::unique_ptr<Instruction> var_inst(new Instruction(
context(), spv::Op::OpVariable, returnVarTypeId, returnVarId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
{(uint32_t)spv::StorageClass::Function}}}));
new_vars->push_back(std::move(var_inst));
get_decoration_mgr()->CloneDecorations(calleeFn->result_id(), returnVarId);
// Decorate the return var with AliasedPointer if the storage class of the
// pointee type is PhysicalStorageBuffer.
auto const pointee_type =
type_mgr->GetType(returnVarTypeId)->AsPointer()->pointee_type();
if (pointee_type->AsPointer() != nullptr) {
if (pointee_type->AsPointer()->storage_class() ==
spv::StorageClass::PhysicalStorageBuffer) {
get_decoration_mgr()->AddDecoration(
returnVarId, uint32_t(spv::Decoration::AliasedPointer));
}
}
return returnVarId;
}
bool InlinePass::IsSameBlockOp(const Instruction* inst) const {
return inst->opcode() == spv::Op::OpSampledImage ||
inst->opcode() == spv::Op::OpImage;
}
bool InlinePass::CloneSameBlockOps(
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* postCallSB,
std::unordered_map<uint32_t, Instruction*>* preCallSB,
std::unique_ptr<BasicBlock>* block_ptr) {
return (*inst)->WhileEachInId([&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 Instruction* inInst = mapItr2->second;
std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
if (!CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr)) {
return false;
}
const uint32_t rid = sb_inst->result_id();
const uint32_t nid = context()->TakeNextId();
if (nid == 0) {
return false;
}
get_decoration_mgr()->CloneDecorations(rid, nid);
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;
}
return true;
});
}
void InlinePass::MoveInstsBeforeEntryBlock(
std::unordered_map<uint32_t, Instruction*>* preCallSB,
BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr,
UptrVectorIterator<BasicBlock> call_block_itr) {
for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
cii = call_block_itr->begin()) {
Instruction* inst = &*cii;
inst->RemoveFromList();
std::unique_ptr<Instruction> cp_inst(inst);
// 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));
}
}
std::unique_ptr<BasicBlock> InlinePass::AddGuardBlock(
std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
std::unordered_map<uint32_t, uint32_t>* callee2caller,
std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id) {
const auto guard_block_id = context()->TakeNextId();
if (guard_block_id == 0) {
return nullptr;
}
AddBranch(guard_block_id, &new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
// Start the next block.
new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
// Reset the mapping of the callee's entry block to point to
// the guard block. Do this so we can fix up phis later on to
// satisfy dominance.
(*callee2caller)[entry_blk_label_id] = guard_block_id;
return new_blk_ptr;
}
InstructionList::iterator InlinePass::AddStoresForVariableInitializers(
const std::unordered_map<uint32_t, uint32_t>& callee2caller,
analysis::DebugInlinedAtContext* inlined_at_ctx,
std::unique_ptr<BasicBlock>* new_blk_ptr,
UptrVectorIterator<BasicBlock> callee_first_block_itr) {
auto callee_itr = callee_first_block_itr->begin();
while (callee_itr->opcode() == spv::Op::OpVariable ||
callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
if (callee_itr->opcode() == spv::Op::OpVariable &&
callee_itr->NumInOperands() == 2) {
assert(callee2caller.count(callee_itr->result_id()) &&
"Expected the variable to have already been mapped.");
uint32_t new_var_id = callee2caller.at(callee_itr->result_id());
// The initializer must be a constant or global value. No mapped
// should be used.
uint32_t val_id = callee_itr->GetSingleWordInOperand(1);
AddStore(new_var_id, val_id, new_blk_ptr, callee_itr->dbg_line_inst(),
context()->get_debug_info_mgr()->BuildDebugScope(
callee_itr->GetDebugScope(), inlined_at_ctx));
}
if (callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
InlineSingleInstruction(
callee2caller, new_blk_ptr->get(), &*callee_itr,
context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
callee_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx));
}
++callee_itr;
}
return callee_itr;
}
bool InlinePass::InlineSingleInstruction(
const std::unordered_map<uint32_t, uint32_t>& callee2caller,
BasicBlock* new_blk_ptr, const Instruction* inst, uint32_t dbg_inlined_at) {
// If we have return, it must be at the end of the callee. We will handle
// it at the end.
if (inst->opcode() == spv::Op::OpReturnValue ||
inst->opcode() == spv::Op::OpReturn)
return true;
// Copy callee instruction and remap all input Ids.
std::unique_ptr<Instruction> cp_inst(inst->Clone(context()));
cp_inst->ForEachInId([&callee2caller](uint32_t* iid) {
const auto mapItr = callee2caller.find(*iid);
if (mapItr != callee2caller.end()) {
*iid = mapItr->second;
}
});
// If result id is non-zero, remap it.
const uint32_t rid = cp_inst->result_id();
if (rid != 0) {
const auto mapItr = callee2caller.find(rid);
if (mapItr == callee2caller.end()) {
return false;
}
uint32_t nid = mapItr->second;
cp_inst->SetResultId(nid);
get_decoration_mgr()->CloneDecorations(rid, nid);
}
cp_inst->UpdateDebugInlinedAt(dbg_inlined_at);
new_blk_ptr->AddInstruction(std::move(cp_inst));
return true;
}
std::unique_ptr<BasicBlock> InlinePass::InlineReturn(
const std::unordered_map<uint32_t, uint32_t>& callee2caller,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
std::unique_ptr<BasicBlock> new_blk_ptr,
analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn,
const Instruction* inst, uint32_t returnVarId) {
// Store return value to return variable.
if (inst->opcode() == spv::Op::OpReturnValue) {
assert(returnVarId != 0);
uint32_t valId = inst->GetInOperand(kSpvReturnValueId).words[0];
const auto mapItr = callee2caller.find(valId);
if (mapItr != callee2caller.end()) {
valId = mapItr->second;
}
AddStore(returnVarId, valId, &new_blk_ptr, inst->dbg_line_inst(),
context()->get_debug_info_mgr()->BuildDebugScope(
inst->GetDebugScope(), inlined_at_ctx));
}
uint32_t returnLabelId = 0;
for (auto callee_block_itr = calleeFn->begin();
callee_block_itr != calleeFn->end(); ++callee_block_itr) {
if (spvOpcodeIsAbort(callee_block_itr->tail()->opcode())) {
returnLabelId = context()->TakeNextId();
break;
}
}
if (returnLabelId == 0) return new_blk_ptr;
if (inst->opcode() == spv::Op::OpReturn ||
inst->opcode() == spv::Op::OpReturnValue)
AddBranch(returnLabelId, &new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
return MakeUnique<BasicBlock>(NewLabel(returnLabelId));
}
bool InlinePass::InlineEntryBlock(
const std::unordered_map<uint32_t, uint32_t>& callee2caller,
std::unique_ptr<BasicBlock>* new_blk_ptr,
UptrVectorIterator<BasicBlock> callee_first_block,
analysis::DebugInlinedAtContext* inlined_at_ctx) {
auto callee_inst_itr = AddStoresForVariableInitializers(
callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block);
while (callee_inst_itr != callee_first_block->end()) {
// Don't inline function definition links, the calling function is not a
// definition.
if (callee_inst_itr->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
++callee_inst_itr;
continue;
}
if (!InlineSingleInstruction(
callee2caller, new_blk_ptr->get(), &*callee_inst_itr,
context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
callee_inst_itr->GetDebugScope().GetInlinedAt(),
inlined_at_ctx))) {
return false;
}
++callee_inst_itr;
}
return true;
}
std::unique_ptr<BasicBlock> InlinePass::InlineBasicBlocks(
std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
const std::unordered_map<uint32_t, uint32_t>& callee2caller,
std::unique_ptr<BasicBlock> new_blk_ptr,
analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn) {
auto callee_block_itr = calleeFn->begin();
++callee_block_itr;
while (callee_block_itr != calleeFn->end()) {
new_blocks->push_back(std::move(new_blk_ptr));
const auto mapItr =
callee2caller.find(callee_block_itr->GetLabelInst()->result_id());
if (mapItr == callee2caller.end()) return nullptr;
new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(mapItr->second));
auto tail_inst_itr = callee_block_itr->end();
for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr;
++inst_itr) {
// Don't inline function definition links, the calling function is not a
// definition
if (inst_itr->GetShader100DebugOpcode() ==
NonSemanticShaderDebugInfo100DebugFunctionDefinition)
continue;
if (!InlineSingleInstruction(
callee2caller, new_blk_ptr.get(), &*inst_itr,
context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
inst_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx))) {
return nullptr;
}
}
++callee_block_itr;
}
return new_blk_ptr;
}
bool InlinePass::MoveCallerInstsAfterFunctionCall(
std::unordered_map<uint32_t, Instruction*>* preCallSB,
std::unordered_map<uint32_t, uint32_t>* postCallSB,
std::unique_ptr<BasicBlock>* new_blk_ptr,
BasicBlock::iterator call_inst_itr, bool multiBlocks) {
// Copy remaining instructions from caller block.
for (Instruction* inst = call_inst_itr->NextNode(); inst;
inst = call_inst_itr->NextNode()) {
inst->RemoveFromList();
std::unique_ptr<Instruction> cp_inst(inst);
// If multiple blocks generated, regenerate any same-block
// instruction that has not been seen in this last block.
if (multiBlocks) {
if (!CloneSameBlockOps(&cp_inst, postCallSB, preCallSB, new_blk_ptr)) {
return false;
}
// 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->get()->AddInstruction(std::move(cp_inst));
}
return true;
}
void InlinePass::MoveLoopMergeInstToFirstBlock(
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
// Move the OpLoopMerge from the last block back to the first, where
// it belongs.
auto& first = new_blocks->front();
auto& last = new_blocks->back();
assert(first != last);
// Insert a modified copy of the loop merge into the first block.
auto loop_merge_itr = last->tail();
--loop_merge_itr;
assert(loop_merge_itr->opcode() == spv::Op::OpLoopMerge);
std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context()));
first->tail().InsertBefore(std::move(cp_inst));
// Remove the loop merge from the last block.
loop_merge_itr->RemoveFromList();
delete &*loop_merge_itr;
}
void InlinePass::UpdateSingleBlockLoopContinueTarget(
uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
auto& header = new_blocks->front();
auto* merge_inst = header->GetLoopMergeInst();
// The back-edge block is split at the branch to create a new back-edge
// block. The old block is modified to branch to the new block. The loop
// merge instruction is updated to declare the new block as the continue
// target. This has the effect of changing the loop from being a large
// continue construct and an empty loop construct to being a loop with a loop
// construct and a trivial continue construct. This change is made to satisfy
// structural dominance.
// Add the new basic block.
std::unique_ptr<BasicBlock> new_block =
MakeUnique<BasicBlock>(NewLabel(new_id));
auto& old_backedge = new_blocks->back();
auto old_branch = old_backedge->tail();
// Move the old back edge into the new block.
std::unique_ptr<Instruction> br(&*old_branch);
new_block->AddInstruction(std::move(br));
// Add a branch to the new block from the old back-edge block.
AddBranch(new_id, &old_backedge);
new_blocks->push_back(std::move(new_block));
// Update the loop's continue target to the new block.
merge_inst->SetInOperand(1u, {new_id});
}
bool InlinePass::GenInlineCode(
std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
std::vector<std::unique_ptr<Instruction>>* new_vars,
BasicBlock::iterator call_inst_itr,
UptrVectorIterator<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, Instruction*> preCallSB;
// Post-call same-block op ids
std::unordered_map<uint32_t, uint32_t> postCallSB;
analysis::DebugInlinedAtContext inlined_at_ctx(&*call_inst_itr);
// Invalidate the def-use chains. They are not kept up to date while
// inlining. However, certain calls try to keep them up-to-date if they are
// valid. These operations can fail.
context()->InvalidateAnalyses(IRContext::kAnalysisDefUse);
// If the caller is a loop header and the callee has multiple blocks, then the
// normal inlining logic will place the OpLoopMerge in the last of several
// blocks in the loop. Instead, it should be placed at the end of the first
// block. We'll wait to move the OpLoopMerge until the end of the regular
// inlining logic, and only if necessary.
bool caller_is_loop_header = call_block_itr->GetLoopMergeInst() != nullptr;
// Single-trip loop continue block
std::unique_ptr<BasicBlock> single_trip_loop_cont_blk;
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.
if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller, &inlined_at_ctx)) {
return false;
}
// First block needs to use label of original block
// but map callee label in case of phi reference.
uint32_t entry_blk_label_id = calleeFn->begin()->GetLabelInst()->result_id();
callee2caller[entry_blk_label_id] = call_block_itr->id();
std::unique_ptr<BasicBlock> new_blk_ptr =
MakeUnique<BasicBlock>(NewLabel(call_block_itr->id()));
// Move instructions of original caller block up to call instruction.
MoveInstsBeforeEntryBlock(&preCallSB, new_blk_ptr.get(), call_inst_itr,
call_block_itr);
if (caller_is_loop_header &&
(*(calleeFn->begin())).GetMergeInst() != nullptr) {
// We can't place both the caller's merge instruction and
// another merge instruction in the same block. So split the
// calling block. Insert an unconditional branch to a new guard
// block. Later, once we know the ID of the last block, we
// will move the caller's OpLoopMerge from the last generated
// block into the first block. We also wait to avoid
// invalidating various iterators.
new_blk_ptr = AddGuardBlock(new_blocks, &callee2caller,
std::move(new_blk_ptr), entry_blk_label_id);
if (new_blk_ptr == nullptr) return false;
}
// Create return var if needed.
const uint32_t calleeTypeId = calleeFn->type_id();
uint32_t returnVarId = 0;
analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
if (calleeType->AsVoid() == nullptr) {
returnVarId = CreateReturnVar(calleeFn, new_vars);
if (returnVarId == 0) {
return false;
}
}
calleeFn->WhileEachInst([&callee2caller, this](const Instruction* cpi) {
// Create set of callee result ids. Used to detect forward references
const uint32_t rid = cpi->result_id();
if (rid != 0 && callee2caller.find(rid) == callee2caller.end()) {
const uint32_t nid = context()->TakeNextId();
if (nid == 0) return false;
callee2caller[rid] = nid;
}
return true;
});
// Inline DebugClare instructions in the callee's header.
calleeFn->ForEachDebugInstructionsInHeader(
[&new_blk_ptr, &callee2caller, &inlined_at_ctx, this](Instruction* inst) {
InlineSingleInstruction(
callee2caller, new_blk_ptr.get(), inst,
context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
inst->GetDebugScope().GetInlinedAt(), &inlined_at_ctx));
});
// Inline the entry block of the callee function.
if (!InlineEntryBlock(callee2caller, &new_blk_ptr, calleeFn->begin(),
&inlined_at_ctx)) {
return false;
}
// Inline blocks of the callee function other than the entry block.
new_blk_ptr =
InlineBasicBlocks(new_blocks, callee2caller, std::move(new_blk_ptr),
&inlined_at_ctx, calleeFn);
if (new_blk_ptr == nullptr) return false;
new_blk_ptr = InlineReturn(callee2caller, new_blocks, std::move(new_blk_ptr),
&inlined_at_ctx, calleeFn,
&*(calleeFn->tail()->tail()), returnVarId);
// 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,
call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope());
}
// Move instructions of original caller block after call instruction.
if (!MoveCallerInstsAfterFunctionCall(&preCallSB, &postCallSB, &new_blk_ptr,
call_inst_itr,
calleeFn->begin() != calleeFn->end()))
return false;
// Finalize inline code.
new_blocks->push_back(std::move(new_blk_ptr));
if (caller_is_loop_header && (new_blocks->size() > 1)) {
MoveLoopMergeInstToFirstBlock(new_blocks);
// If the loop was a single basic block previously, update it's structure.
auto& header = new_blocks->front();
auto* merge_inst = header->GetLoopMergeInst();
if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
auto new_id = context()->TakeNextId();
if (new_id == 0) return false;
UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
}
}
// Update block map given replacement blocks.
for (auto& blk : *new_blocks) {
id2block_[blk->id()] = &*blk;
}
// We need to kill the name and decorations for the call, which will be
// deleted.
context()->KillNamesAndDecorates(&*call_inst_itr);
return true;
}
bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
if (inst->opcode() != spv::Op::OpFunctionCall) return false;
const uint32_t calleeFnId =
inst->GetSingleWordOperand(kSpvFunctionCallFunctionId);
const auto ci = inlinable_.find(calleeFnId);
if (ci == inlinable_.cend()) return false;
if (early_return_funcs_.find(calleeFnId) != early_return_funcs_.end()) {
// We rely on the merge-return pass to handle the early return case
// in advance.
std::string message =
"The function '" + id2function_[calleeFnId]->DefInst().PrettyPrint() +
"' could not be inlined because the return instruction "
"is not at the end of the function. This could be fixed by "
"running merge-return before inlining.";
consumer()(SPV_MSG_WARNING, "", {0, 0, 0}, message.c_str());
return false;
}
return true;
}
void InlinePass::UpdateSucceedingPhis(
std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
const auto firstBlk = new_blocks.begin();
const auto lastBlk = new_blocks.end() - 1;
const uint32_t firstId = (*firstBlk)->id();
const uint32_t lastId = (*lastBlk)->id();
const BasicBlock& const_last_block = *lastBlk->get();
const_last_block.ForEachSuccessorLabel(
[&firstId, &lastId, this](const uint32_t succ) {
BasicBlock* sbp = this->id2block_[succ];
sbp->ForEachPhiInst([&firstId, &lastId](Instruction* phi) {
phi->ForEachInId([&firstId, &lastId](uint32_t* id) {
if (*id == firstId) *id = lastId;
});
});
});
}
bool InlinePass::HasNoReturnInLoop(Function* func) {
// If control not structured, do not do loop/return analysis
// TODO: Analyze returns in non-structured control flow
if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader))
return false;
const auto structured_analysis = context()->GetStructuredCFGAnalysis();
// Search for returns in structured construct.
bool return_in_loop = false;
for (auto& blk : *func) {
auto terminal_ii = blk.cend();
--terminal_ii;
if (spvOpcodeIsReturn(terminal_ii->opcode()) &&
structured_analysis->ContainingLoop(blk.id()) != 0) {
return_in_loop = true;
break;
}
}
return !return_in_loop;
}
void InlinePass::AnalyzeReturns(Function* func) {
// Analyze functions without a return in loop.
if (HasNoReturnInLoop(func)) {
no_return_in_loop_.insert(func->result_id());
}
// Analyze functions with a return before its tail basic block.
for (auto& blk : *func) {
auto terminal_ii = blk.cend();
--terminal_ii;
if (spvOpcodeIsReturn(terminal_ii->opcode()) && &blk != func->tail()) {
early_return_funcs_.insert(func->result_id());
break;
}
}
}
bool InlinePass::IsInlinableFunction(Function* func) {
// We can only inline a function if it has blocks.
if (func->cbegin() == func->cend()) return false;
// Do not inline functions with DontInline flag.
if (func->control_mask() & uint32_t(spv::FunctionControlMask::DontInline)) {
return false;
}
// Do not inline functions with returns in loops. Currently early return
// functions are inlined by wrapping them in a one trip loop and implementing
// the returns as a branch to the loop's merge block. However, this can only
// done validly if the return was not in a loop in the original function.
// Also remember functions with multiple (early) returns.
AnalyzeReturns(func);
if (no_return_in_loop_.find(func->result_id()) == no_return_in_loop_.cend()) {
return false;
}
if (func->IsRecursive()) {
return false;
}
// Do not inline functions with an abort instruction if they are called from a
// continue construct. If it is inlined into a continue construct the backedge
// will no longer post-dominate the continue target, which is invalid. An
// `OpUnreachable` is acceptable because it will not change post-dominance if
// it is statically unreachable.
bool func_is_called_from_continue =
funcs_called_from_continue_.count(func->result_id()) != 0;
if (func_is_called_from_continue && ContainsAbortOtherThanUnreachable(func)) {
return false;
}
return true;
}
bool InlinePass::ContainsAbortOtherThanUnreachable(Function* func) const {
return !func->WhileEachInst([](Instruction* inst) {
return inst->opcode() == spv::Op::OpUnreachable ||
!spvOpcodeIsAbort(inst->opcode());
});
}
void InlinePass::InitializeInline() {
false_id_ = 0;
// clear collections
id2function_.clear();
id2block_.clear();
inlinable_.clear();
no_return_in_loop_.clear();
early_return_funcs_.clear();
funcs_called_from_continue_ =
context()->GetStructuredCFGAnalysis()->FindFuncsCalledFromContinue();
for (auto& fn : *get_module()) {
// Initialize function and block maps.
id2function_[fn.result_id()] = &fn;
for (auto& blk : fn) {
id2block_[blk.id()] = &blk;
}
// Compute inlinability
if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id());
}
}
InlinePass::InlinePass() {}
void InlinePass::FixDebugDeclares(Function* func) {
std::map<uint32_t, Instruction*> access_chains;
std::vector<Instruction*> debug_declare_insts;
func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) {
if (inst->opcode() == spv::Op::OpAccessChain) {
access_chains[inst->result_id()] = inst;
}
if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
debug_declare_insts.push_back(inst);
}
});
for (auto& inst : debug_declare_insts) {
FixDebugDeclare(inst, access_chains);
}
}
void InlinePass::FixDebugDeclare(
Instruction* dbg_declare_inst,
const std::map<uint32_t, Instruction*>& access_chains) {
do {
uint32_t var_id =
dbg_declare_inst->GetSingleWordInOperand(kSpvDebugDeclareVarInIdx);
// The def-use chains are not kept up to date while inlining, so we need to
// get the variable by traversing the functions.
auto it = access_chains.find(var_id);
if (it == access_chains.end()) {
return;
}
Instruction* access_chain = it->second;
// If the variable id in the debug declare is an access chain, it is
// invalid. it needs to be fixed up. The debug declare will be updated so
// that its Var operand becomes the base of the access chain. The indexes of
// the access chain are prepended before the indexes of the debug declare.
std::vector<Operand> operands;
for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) {
operands.push_back(dbg_declare_inst->GetInOperand(i));
}
uint32_t access_chain_base =
access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx);
operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base}));
operands.push_back(
dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1));
for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
i < access_chain->NumInOperands(); ++i) {
operands.push_back(access_chain->GetInOperand(i));
}
for (uint32_t i = kSpvDebugDeclareVarInIdx + 2;
i < dbg_declare_inst->NumInOperands(); ++i) {
operands.push_back(dbg_declare_inst->GetInOperand(i));
}
dbg_declare_inst->SetInOperands(std::move(operands));
} while (true);
}
} // namespace opt
} // namespace spvtools
|