| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966 |
- // Copyright (c) 2019 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #include "source/fuzz/transformation_add_function.h"
- #include "source/fuzz/fuzzer_util.h"
- #include "source/fuzz/instruction_message.h"
- namespace spvtools {
- namespace fuzz {
- TransformationAddFunction::TransformationAddFunction(
- protobufs::TransformationAddFunction message)
- : message_(std::move(message)) {}
- TransformationAddFunction::TransformationAddFunction(
- const std::vector<protobufs::Instruction>& instructions) {
- for (auto& instruction : instructions) {
- *message_.add_instruction() = instruction;
- }
- message_.set_is_livesafe(false);
- }
- TransformationAddFunction::TransformationAddFunction(
- const std::vector<protobufs::Instruction>& instructions,
- uint32_t loop_limiter_variable_id, uint32_t loop_limit_constant_id,
- const std::vector<protobufs::LoopLimiterInfo>& loop_limiters,
- uint32_t kill_unreachable_return_value_id,
- const std::vector<protobufs::AccessChainClampingInfo>&
- access_chain_clampers) {
- for (auto& instruction : instructions) {
- *message_.add_instruction() = instruction;
- }
- message_.set_is_livesafe(true);
- message_.set_loop_limiter_variable_id(loop_limiter_variable_id);
- message_.set_loop_limit_constant_id(loop_limit_constant_id);
- for (auto& loop_limiter : loop_limiters) {
- *message_.add_loop_limiter_info() = loop_limiter;
- }
- message_.set_kill_unreachable_return_value_id(
- kill_unreachable_return_value_id);
- for (auto& access_clamper : access_chain_clampers) {
- *message_.add_access_chain_clamping_info() = access_clamper;
- }
- }
- bool TransformationAddFunction::IsApplicable(
- opt::IRContext* ir_context,
- const TransformationContext& transformation_context) const {
- // This transformation may use a lot of ids, all of which need to be fresh
- // and distinct. This set tracks them.
- std::set<uint32_t> ids_used_by_this_transformation;
- // Ensure that all result ids in the new function are fresh and distinct.
- for (auto& instruction : message_.instruction()) {
- if (instruction.result_id()) {
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- instruction.result_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- }
- }
- if (message_.is_livesafe()) {
- // Ensure that all ids provided for making the function livesafe are fresh
- // and distinct.
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- message_.loop_limiter_variable_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- for (auto& loop_limiter_info : message_.loop_limiter_info()) {
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- loop_limiter_info.load_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- loop_limiter_info.increment_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- loop_limiter_info.compare_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- loop_limiter_info.logical_op_id(), ir_context,
- &ids_used_by_this_transformation)) {
- return false;
- }
- }
- for (auto& access_chain_clamping_info :
- message_.access_chain_clamping_info()) {
- for (auto& pair : access_chain_clamping_info.compare_and_select_ids()) {
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- pair.first(), ir_context, &ids_used_by_this_transformation)) {
- return false;
- }
- if (!CheckIdIsFreshAndNotUsedByThisTransformation(
- pair.second(), ir_context, &ids_used_by_this_transformation)) {
- return false;
- }
- }
- }
- }
- // Because checking all the conditions for a function to be valid is a big
- // job that the SPIR-V validator can already do, a "try it and see" approach
- // is taken here.
- // We first clone the current module, so that we can try adding the new
- // function without risking wrecking |ir_context|.
- auto cloned_module = fuzzerutil::CloneIRContext(ir_context);
- // We try to add a function to the cloned module, which may fail if
- // |message_.instruction| is not sufficiently well-formed.
- if (!TryToAddFunction(cloned_module.get())) {
- return false;
- }
- // Check whether the cloned module is still valid after adding the function.
- // If it is not, the transformation is not applicable.
- if (!fuzzerutil::IsValid(cloned_module.get(),
- transformation_context.GetValidatorOptions(),
- fuzzerutil::kSilentMessageConsumer)) {
- return false;
- }
- if (message_.is_livesafe()) {
- if (!TryToMakeFunctionLivesafe(cloned_module.get(),
- transformation_context)) {
- return false;
- }
- // After making the function livesafe, we check validity of the module
- // again. This is because the turning of OpKill, OpUnreachable and OpReturn
- // instructions into branches changes control flow graph reachability, which
- // has the potential to make the module invalid when it was otherwise valid.
- // It is simpler to rely on the validator to guard against this than to
- // consider all scenarios when making a function livesafe.
- if (!fuzzerutil::IsValid(cloned_module.get(),
- transformation_context.GetValidatorOptions(),
- fuzzerutil::kSilentMessageConsumer)) {
- return false;
- }
- }
- return true;
- }
- void TransformationAddFunction::Apply(
- opt::IRContext* ir_context,
- TransformationContext* transformation_context) const {
- // Add the function to the module. As the transformation is applicable, this
- // should succeed.
- bool success = TryToAddFunction(ir_context);
- assert(success && "The function should be successfully added.");
- (void)(success); // Keep release builds happy (otherwise they may complain
- // that |success| is not used).
- if (message_.is_livesafe()) {
- // Make the function livesafe, which also should succeed.
- success = TryToMakeFunctionLivesafe(ir_context, *transformation_context);
- assert(success && "It should be possible to make the function livesafe.");
- (void)(success); // Keep release builds happy.
- }
- ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
- assert(spv::Op(message_.instruction(0).opcode()) == spv::Op::OpFunction &&
- "The first instruction of an 'add function' transformation must be "
- "OpFunction.");
- if (message_.is_livesafe()) {
- // Inform the fact manager that the function is livesafe.
- transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
- message_.instruction(0).result_id());
- } else {
- // Inform the fact manager that all blocks in the function are dead.
- for (auto& inst : message_.instruction()) {
- if (spv::Op(inst.opcode()) == spv::Op::OpLabel) {
- transformation_context->GetFactManager()->AddFactBlockIsDead(
- inst.result_id());
- }
- }
- }
- // Record the fact that all pointer parameters and variables declared in the
- // function should be regarded as having irrelevant values. This allows other
- // passes to store arbitrarily to such variables, and to pass them freely as
- // parameters to other functions knowing that it is OK if they get
- // over-written.
- for (auto& instruction : message_.instruction()) {
- switch (spv::Op(instruction.opcode())) {
- case spv::Op::OpFunctionParameter:
- if (ir_context->get_def_use_mgr()
- ->GetDef(instruction.result_type_id())
- ->opcode() == spv::Op::OpTypePointer) {
- transformation_context->GetFactManager()
- ->AddFactValueOfPointeeIsIrrelevant(instruction.result_id());
- }
- break;
- case spv::Op::OpVariable:
- transformation_context->GetFactManager()
- ->AddFactValueOfPointeeIsIrrelevant(instruction.result_id());
- break;
- default:
- break;
- }
- }
- }
- protobufs::Transformation TransformationAddFunction::ToMessage() const {
- protobufs::Transformation result;
- *result.mutable_add_function() = message_;
- return result;
- }
- bool TransformationAddFunction::TryToAddFunction(
- opt::IRContext* ir_context) const {
- // This function returns false if |message_.instruction| was not well-formed
- // enough to actually create a function and add it to |ir_context|.
- // A function must have at least some instructions.
- if (message_.instruction().empty()) {
- return false;
- }
- // A function must start with OpFunction.
- auto function_begin = message_.instruction(0);
- if (spv::Op(function_begin.opcode()) != spv::Op::OpFunction) {
- return false;
- }
- // Make a function, headed by the OpFunction instruction.
- std::unique_ptr<opt::Function> new_function = MakeUnique<opt::Function>(
- InstructionFromMessage(ir_context, function_begin));
- // Keeps track of which instruction protobuf message we are currently
- // considering.
- uint32_t instruction_index = 1;
- const auto num_instructions =
- static_cast<uint32_t>(message_.instruction().size());
- // Iterate through all function parameter instructions, adding parameters to
- // the new function.
- while (instruction_index < num_instructions &&
- spv::Op(message_.instruction(instruction_index).opcode()) ==
- spv::Op::OpFunctionParameter) {
- new_function->AddParameter(InstructionFromMessage(
- ir_context, message_.instruction(instruction_index)));
- instruction_index++;
- }
- // After the parameters, there needs to be a label.
- if (instruction_index == num_instructions ||
- spv::Op(message_.instruction(instruction_index).opcode()) !=
- spv::Op::OpLabel) {
- return false;
- }
- // Iterate through the instructions block by block until the end of the
- // function is reached.
- while (instruction_index < num_instructions &&
- spv::Op(message_.instruction(instruction_index).opcode()) !=
- spv::Op::OpFunctionEnd) {
- // Invariant: we should always be at a label instruction at this point.
- assert(spv::Op(message_.instruction(instruction_index).opcode()) ==
- spv::Op::OpLabel);
- // Make a basic block using the label instruction.
- std::unique_ptr<opt::BasicBlock> block =
- MakeUnique<opt::BasicBlock>(InstructionFromMessage(
- ir_context, message_.instruction(instruction_index)));
- // Consider successive instructions until we hit another label or the end
- // of the function, adding each such instruction to the block.
- instruction_index++;
- while (instruction_index < num_instructions &&
- spv::Op(message_.instruction(instruction_index).opcode()) !=
- spv::Op::OpFunctionEnd &&
- spv::Op(message_.instruction(instruction_index).opcode()) !=
- spv::Op::OpLabel) {
- block->AddInstruction(InstructionFromMessage(
- ir_context, message_.instruction(instruction_index)));
- instruction_index++;
- }
- // Add the block to the new function.
- new_function->AddBasicBlock(std::move(block));
- }
- // Having considered all the blocks, we should be at the last instruction and
- // it needs to be OpFunctionEnd.
- if (instruction_index != num_instructions - 1 ||
- spv::Op(message_.instruction(instruction_index).opcode()) !=
- spv::Op::OpFunctionEnd) {
- return false;
- }
- // Set the function's final instruction, add the function to the module and
- // report success.
- new_function->SetFunctionEnd(InstructionFromMessage(
- ir_context, message_.instruction(instruction_index)));
- ir_context->AddFunction(std::move(new_function));
- ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
- return true;
- }
- bool TransformationAddFunction::TryToMakeFunctionLivesafe(
- opt::IRContext* ir_context,
- const TransformationContext& transformation_context) const {
- assert(message_.is_livesafe() && "Precondition: is_livesafe must hold.");
- // Get a pointer to the added function.
- opt::Function* added_function = nullptr;
- for (auto& function : *ir_context->module()) {
- if (function.result_id() == message_.instruction(0).result_id()) {
- added_function = &function;
- break;
- }
- }
- assert(added_function && "The added function should have been found.");
- if (!TryToAddLoopLimiters(ir_context, added_function)) {
- // Adding loop limiters did not work; bail out.
- return false;
- }
- // Consider all the instructions in the function, and:
- // - attempt to replace OpKill and OpUnreachable with return instructions
- // - attempt to clamp access chains to be within bounds
- // - check that OpFunctionCall instructions are only to livesafe functions
- for (auto& block : *added_function) {
- for (auto& inst : block) {
- switch (inst.opcode()) {
- case spv::Op::OpKill:
- case spv::Op::OpUnreachable:
- if (!TryToTurnKillOrUnreachableIntoReturn(ir_context, added_function,
- &inst)) {
- return false;
- }
- break;
- case spv::Op::OpAccessChain:
- case spv::Op::OpInBoundsAccessChain:
- if (!TryToClampAccessChainIndices(ir_context, &inst)) {
- return false;
- }
- break;
- case spv::Op::OpFunctionCall:
- // A livesafe function my only call other livesafe functions.
- if (!transformation_context.GetFactManager()->FunctionIsLivesafe(
- inst.GetSingleWordInOperand(0))) {
- return false;
- }
- default:
- break;
- }
- }
- }
- return true;
- }
- uint32_t TransformationAddFunction::GetBackEdgeBlockId(
- opt::IRContext* ir_context, uint32_t loop_header_block_id) {
- const auto* loop_header_block =
- ir_context->cfg()->block(loop_header_block_id);
- assert(loop_header_block && "|loop_header_block_id| is invalid");
- for (auto pred : ir_context->cfg()->preds(loop_header_block_id)) {
- if (ir_context->GetDominatorAnalysis(loop_header_block->GetParent())
- ->Dominates(loop_header_block_id, pred)) {
- return pred;
- }
- }
- return 0;
- }
- bool TransformationAddFunction::TryToAddLoopLimiters(
- opt::IRContext* ir_context, opt::Function* added_function) const {
- // Collect up all the loop headers so that we can subsequently add loop
- // limiting logic.
- std::vector<opt::BasicBlock*> loop_headers;
- for (auto& block : *added_function) {
- if (block.IsLoopHeader()) {
- loop_headers.push_back(&block);
- }
- }
- if (loop_headers.empty()) {
- // There are no loops, so no need to add any loop limiters.
- return true;
- }
- // Check that the module contains appropriate ingredients for declaring and
- // manipulating a loop limiter.
- auto loop_limit_constant_id_instr =
- ir_context->get_def_use_mgr()->GetDef(message_.loop_limit_constant_id());
- if (!loop_limit_constant_id_instr ||
- loop_limit_constant_id_instr->opcode() != spv::Op::OpConstant) {
- // The loop limit constant id instruction must exist and have an
- // appropriate opcode.
- return false;
- }
- auto loop_limit_type = ir_context->get_def_use_mgr()->GetDef(
- loop_limit_constant_id_instr->type_id());
- if (loop_limit_type->opcode() != spv::Op::OpTypeInt ||
- loop_limit_type->GetSingleWordInOperand(0) != 32) {
- // The type of the loop limit constant must be 32-bit integer. It
- // doesn't actually matter whether the integer is signed or not.
- return false;
- }
- // Find the id of the "unsigned int" type.
- opt::analysis::Integer unsigned_int_type(32, false);
- uint32_t unsigned_int_type_id =
- ir_context->get_type_mgr()->GetId(&unsigned_int_type);
- if (!unsigned_int_type_id) {
- // Unsigned int is not available; we need this type in order to add loop
- // limiters.
- return false;
- }
- auto registered_unsigned_int_type =
- ir_context->get_type_mgr()->GetRegisteredType(&unsigned_int_type);
- // Look for 0 of type unsigned int.
- opt::analysis::IntConstant zero(registered_unsigned_int_type->AsInteger(),
- {0});
- auto registered_zero = ir_context->get_constant_mgr()->FindConstant(&zero);
- if (!registered_zero) {
- // We need 0 in order to be able to initialize loop limiters.
- return false;
- }
- uint32_t zero_id = ir_context->get_constant_mgr()
- ->GetDefiningInstruction(registered_zero)
- ->result_id();
- // Look for 1 of type unsigned int.
- opt::analysis::IntConstant one(registered_unsigned_int_type->AsInteger(),
- {1});
- auto registered_one = ir_context->get_constant_mgr()->FindConstant(&one);
- if (!registered_one) {
- // We need 1 in order to be able to increment loop limiters.
- return false;
- }
- uint32_t one_id = ir_context->get_constant_mgr()
- ->GetDefiningInstruction(registered_one)
- ->result_id();
- // Look for pointer-to-unsigned int type.
- opt::analysis::Pointer pointer_to_unsigned_int_type(
- registered_unsigned_int_type, spv::StorageClass::Function);
- uint32_t pointer_to_unsigned_int_type_id =
- ir_context->get_type_mgr()->GetId(&pointer_to_unsigned_int_type);
- if (!pointer_to_unsigned_int_type_id) {
- // We need pointer-to-unsigned int in order to declare the loop limiter
- // variable.
- return false;
- }
- // Look for bool type.
- opt::analysis::Bool bool_type;
- uint32_t bool_type_id = ir_context->get_type_mgr()->GetId(&bool_type);
- if (!bool_type_id) {
- // We need bool in order to compare the loop limiter's value with the loop
- // limit constant.
- return false;
- }
- // Declare the loop limiter variable at the start of the function's entry
- // block, via an instruction of the form:
- // %loop_limiter_var = spv::Op::OpVariable %ptr_to_uint Function %zero
- added_function->begin()->begin()->InsertBefore(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpVariable, pointer_to_unsigned_int_type_id,
- message_.loop_limiter_variable_id(),
- opt::Instruction::OperandList({{SPV_OPERAND_TYPE_STORAGE_CLASS,
- {uint32_t(spv::StorageClass::Function)}},
- {SPV_OPERAND_TYPE_ID, {zero_id}}})));
- // Update the module's id bound since we have added the loop limiter
- // variable id.
- fuzzerutil::UpdateModuleIdBound(ir_context,
- message_.loop_limiter_variable_id());
- // Consider each loop in turn.
- for (auto loop_header : loop_headers) {
- // Look for the loop's back-edge block. This is a predecessor of the loop
- // header that is dominated by the loop header.
- const auto back_edge_block_id =
- GetBackEdgeBlockId(ir_context, loop_header->id());
- if (!back_edge_block_id) {
- // The loop's back-edge block must be unreachable. This means that the
- // loop cannot iterate, so there is no need to make it lifesafe; we can
- // move on from this loop.
- continue;
- }
- // If the loop's merge block is unreachable, then there are no constraints
- // on where the merge block appears in relation to the blocks of the loop.
- // This means we need to be careful when adding a branch from the back-edge
- // block to the merge block: the branch might make the loop merge reachable,
- // and it might then be dominated by the loop header and possibly by other
- // blocks in the loop. Since a block needs to appear before those blocks it
- // strictly dominates, this could make the module invalid. To avoid this
- // problem we bail out in the case where the loop header does not dominate
- // the loop merge.
- if (!ir_context->GetDominatorAnalysis(added_function)
- ->Dominates(loop_header->id(), loop_header->MergeBlockId())) {
- return false;
- }
- // Go through the sequence of loop limiter infos and find the one
- // corresponding to this loop.
- bool found = false;
- protobufs::LoopLimiterInfo loop_limiter_info;
- for (auto& info : message_.loop_limiter_info()) {
- if (info.loop_header_id() == loop_header->id()) {
- loop_limiter_info = info;
- found = true;
- break;
- }
- }
- if (!found) {
- // We don't have loop limiter info for this loop header.
- return false;
- }
- // The back-edge block either has the form:
- //
- // (1)
- //
- // %l = OpLabel
- // ... instructions ...
- // OpBranch %loop_header
- //
- // (2)
- //
- // %l = OpLabel
- // ... instructions ...
- // OpBranchConditional %c %loop_header %loop_merge
- //
- // (3)
- //
- // %l = OpLabel
- // ... instructions ...
- // OpBranchConditional %c %loop_merge %loop_header
- //
- // We turn these into the following:
- //
- // (1)
- //
- // %l = OpLabel
- // ... instructions ...
- // %t1 = OpLoad %uint32 %loop_limiter
- // %t2 = OpIAdd %uint32 %t1 %one
- // OpStore %loop_limiter %t2
- // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit
- // OpBranchConditional %t3 %loop_merge %loop_header
- //
- // (2)
- //
- // %l = OpLabel
- // ... instructions ...
- // %t1 = OpLoad %uint32 %loop_limiter
- // %t2 = OpIAdd %uint32 %t1 %one
- // OpStore %loop_limiter %t2
- // %t3 = OpULessThan %bool %t1 %loop_limit
- // %t4 = OpLogicalAnd %bool %c %t3
- // OpBranchConditional %t4 %loop_header %loop_merge
- //
- // (3)
- //
- // %l = OpLabel
- // ... instructions ...
- // %t1 = OpLoad %uint32 %loop_limiter
- // %t2 = OpIAdd %uint32 %t1 %one
- // OpStore %loop_limiter %t2
- // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit
- // %t4 = OpLogicalOr %bool %c %t3
- // OpBranchConditional %t4 %loop_merge %loop_header
- auto back_edge_block = ir_context->cfg()->block(back_edge_block_id);
- auto back_edge_block_terminator = back_edge_block->terminator();
- bool compare_using_greater_than_equal;
- if (back_edge_block_terminator->opcode() == spv::Op::OpBranch) {
- compare_using_greater_than_equal = true;
- } else {
- assert(back_edge_block_terminator->opcode() ==
- spv::Op::OpBranchConditional);
- assert(((back_edge_block_terminator->GetSingleWordInOperand(1) ==
- loop_header->id() &&
- back_edge_block_terminator->GetSingleWordInOperand(2) ==
- loop_header->MergeBlockId()) ||
- (back_edge_block_terminator->GetSingleWordInOperand(2) ==
- loop_header->id() &&
- back_edge_block_terminator->GetSingleWordInOperand(1) ==
- loop_header->MergeBlockId())) &&
- "A back edge edge block must branch to"
- " either the loop header or merge");
- compare_using_greater_than_equal =
- back_edge_block_terminator->GetSingleWordInOperand(1) ==
- loop_header->MergeBlockId();
- }
- std::vector<std::unique_ptr<opt::Instruction>> new_instructions;
- // Add a load from the loop limiter variable, of the form:
- // %t1 = OpLoad %uint32 %loop_limiter
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpLoad, unsigned_int_type_id,
- loop_limiter_info.load_id(),
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {message_.loop_limiter_variable_id()}}})));
- // Increment the loaded value:
- // %t2 = OpIAdd %uint32 %t1 %one
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpIAdd, unsigned_int_type_id,
- loop_limiter_info.increment_id(),
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.load_id()}},
- {SPV_OPERAND_TYPE_ID, {one_id}}})));
- // Store the incremented value back to the loop limiter variable:
- // OpStore %loop_limiter %t2
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpStore, 0, 0,
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {message_.loop_limiter_variable_id()}},
- {SPV_OPERAND_TYPE_ID, {loop_limiter_info.increment_id()}}})));
- // Compare the loaded value with the loop limit; either:
- // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit
- // or
- // %t3 = OpULessThan %bool %t1 %loop_limit
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context,
- compare_using_greater_than_equal ? spv::Op::OpUGreaterThanEqual
- : spv::Op::OpULessThan,
- bool_type_id, loop_limiter_info.compare_id(),
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.load_id()}},
- {SPV_OPERAND_TYPE_ID, {message_.loop_limit_constant_id()}}})));
- if (back_edge_block_terminator->opcode() == spv::Op::OpBranchConditional) {
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context,
- compare_using_greater_than_equal ? spv::Op::OpLogicalOr
- : spv::Op::OpLogicalAnd,
- bool_type_id, loop_limiter_info.logical_op_id(),
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID,
- {back_edge_block_terminator->GetSingleWordInOperand(0)}},
- {SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}}})));
- }
- // Add the new instructions at the end of the back edge block, before the
- // terminator and any loop merge instruction (as the back edge block can
- // be the loop header).
- if (back_edge_block->GetLoopMergeInst()) {
- back_edge_block->GetLoopMergeInst()->InsertBefore(
- std::move(new_instructions));
- } else {
- back_edge_block_terminator->InsertBefore(std::move(new_instructions));
- }
- if (back_edge_block_terminator->opcode() == spv::Op::OpBranchConditional) {
- back_edge_block_terminator->SetInOperand(
- 0, {loop_limiter_info.logical_op_id()});
- } else {
- assert(back_edge_block_terminator->opcode() == spv::Op::OpBranch &&
- "Back-edge terminator must be OpBranch or OpBranchConditional");
- // Check that, if the merge block starts with OpPhi instructions, suitable
- // ids have been provided to give these instructions a value corresponding
- // to the new incoming edge from the back edge block.
- auto merge_block = ir_context->cfg()->block(loop_header->MergeBlockId());
- if (!fuzzerutil::PhiIdsOkForNewEdge(ir_context, back_edge_block,
- merge_block,
- loop_limiter_info.phi_id())) {
- return false;
- }
- // Augment OpPhi instructions at the loop merge with the given ids.
- uint32_t phi_index = 0;
- for (auto& inst : *merge_block) {
- if (inst.opcode() != spv::Op::OpPhi) {
- break;
- }
- assert(phi_index <
- static_cast<uint32_t>(loop_limiter_info.phi_id().size()) &&
- "There should be at least one phi id per OpPhi instruction.");
- inst.AddOperand(
- {SPV_OPERAND_TYPE_ID, {loop_limiter_info.phi_id(phi_index)}});
- inst.AddOperand({SPV_OPERAND_TYPE_ID, {back_edge_block_id}});
- phi_index++;
- }
- // Add the new edge, by changing OpBranch to OpBranchConditional.
- back_edge_block_terminator->SetOpcode(spv::Op::OpBranchConditional);
- back_edge_block_terminator->SetInOperands(opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}},
- {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}},
- {SPV_OPERAND_TYPE_ID, {loop_header->id()}}}));
- }
- // Update the module's id bound with respect to the various ids that
- // have been used for loop limiter manipulation.
- fuzzerutil::UpdateModuleIdBound(ir_context, loop_limiter_info.load_id());
- fuzzerutil::UpdateModuleIdBound(ir_context,
- loop_limiter_info.increment_id());
- fuzzerutil::UpdateModuleIdBound(ir_context, loop_limiter_info.compare_id());
- fuzzerutil::UpdateModuleIdBound(ir_context,
- loop_limiter_info.logical_op_id());
- }
- return true;
- }
- bool TransformationAddFunction::TryToTurnKillOrUnreachableIntoReturn(
- opt::IRContext* ir_context, opt::Function* added_function,
- opt::Instruction* kill_or_unreachable_inst) const {
- assert((kill_or_unreachable_inst->opcode() == spv::Op::OpKill ||
- kill_or_unreachable_inst->opcode() == spv::Op::OpUnreachable) &&
- "Precondition: instruction must be OpKill or OpUnreachable.");
- // Get the function's return type.
- auto function_return_type_inst =
- ir_context->get_def_use_mgr()->GetDef(added_function->type_id());
- if (function_return_type_inst->opcode() == spv::Op::OpTypeVoid) {
- // The function has void return type, so change this instruction to
- // OpReturn.
- kill_or_unreachable_inst->SetOpcode(spv::Op::OpReturn);
- } else {
- // The function has non-void return type, so change this instruction
- // to OpReturnValue, using the value id provided with the
- // transformation.
- // We first check that the id, %id, provided with the transformation
- // specifically to turn OpKill and OpUnreachable instructions into
- // OpReturnValue %id has the same type as the function's return type.
- if (ir_context->get_def_use_mgr()
- ->GetDef(message_.kill_unreachable_return_value_id())
- ->type_id() != function_return_type_inst->result_id()) {
- return false;
- }
- kill_or_unreachable_inst->SetOpcode(spv::Op::OpReturnValue);
- kill_or_unreachable_inst->SetInOperands(
- {{SPV_OPERAND_TYPE_ID, {message_.kill_unreachable_return_value_id()}}});
- }
- return true;
- }
- bool TransformationAddFunction::TryToClampAccessChainIndices(
- opt::IRContext* ir_context, opt::Instruction* access_chain_inst) const {
- assert((access_chain_inst->opcode() == spv::Op::OpAccessChain ||
- access_chain_inst->opcode() == spv::Op::OpInBoundsAccessChain) &&
- "Precondition: instruction must be OpAccessChain or "
- "OpInBoundsAccessChain.");
- // Find the AccessChainClampingInfo associated with this access chain.
- const protobufs::AccessChainClampingInfo* access_chain_clamping_info =
- nullptr;
- for (auto& clamping_info : message_.access_chain_clamping_info()) {
- if (clamping_info.access_chain_id() == access_chain_inst->result_id()) {
- access_chain_clamping_info = &clamping_info;
- break;
- }
- }
- if (!access_chain_clamping_info) {
- // No access chain clamping information was found; the function cannot be
- // made livesafe.
- return false;
- }
- // Check that there is a (compare_id, select_id) pair for every
- // index associated with the instruction.
- if (static_cast<uint32_t>(
- access_chain_clamping_info->compare_and_select_ids().size()) !=
- access_chain_inst->NumInOperands() - 1) {
- return false;
- }
- // Walk the access chain, clamping each index to be within bounds if it is
- // not a constant.
- auto base_object = ir_context->get_def_use_mgr()->GetDef(
- access_chain_inst->GetSingleWordInOperand(0));
- assert(base_object && "The base object must exist.");
- auto pointer_type =
- ir_context->get_def_use_mgr()->GetDef(base_object->type_id());
- assert(pointer_type && pointer_type->opcode() == spv::Op::OpTypePointer &&
- "The base object must have pointer type.");
- auto should_be_composite_type = ir_context->get_def_use_mgr()->GetDef(
- pointer_type->GetSingleWordInOperand(1));
- // Consider each index input operand in turn (operand 0 is the base object).
- for (uint32_t index = 1; index < access_chain_inst->NumInOperands();
- index++) {
- // We are going to turn:
- //
- // %result = OpAccessChain %type %object ... %index ...
- //
- // into:
- //
- // %t1 = OpULessThanEqual %bool %index %bound_minus_one
- // %t2 = OpSelect %int_type %t1 %index %bound_minus_one
- // %result = OpAccessChain %type %object ... %t2 ...
- //
- // ... unless %index is already a constant.
- // Get the bound for the composite being indexed into; e.g. the number of
- // columns of matrix or the size of an array.
- uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
- *should_be_composite_type, ir_context);
- // Get the instruction associated with the index and figure out its integer
- // type.
- const uint32_t index_id = access_chain_inst->GetSingleWordInOperand(index);
- auto index_inst = ir_context->get_def_use_mgr()->GetDef(index_id);
- auto index_type_inst =
- ir_context->get_def_use_mgr()->GetDef(index_inst->type_id());
- assert(index_type_inst->opcode() == spv::Op::OpTypeInt);
- assert(index_type_inst->GetSingleWordInOperand(0) == 32);
- opt::analysis::Integer* index_int_type =
- ir_context->get_type_mgr()
- ->GetType(index_type_inst->result_id())
- ->AsInteger();
- if (index_inst->opcode() != spv::Op::OpConstant ||
- index_inst->GetSingleWordInOperand(0) >= bound) {
- // The index is either non-constant or an out-of-bounds constant, so we
- // need to clamp it.
- assert(should_be_composite_type->opcode() != spv::Op::OpTypeStruct &&
- "Access chain indices into structures are required to be "
- "constants.");
- opt::analysis::IntConstant bound_minus_one(index_int_type, {bound - 1});
- if (!ir_context->get_constant_mgr()->FindConstant(&bound_minus_one)) {
- // We do not have an integer constant whose value is |bound| -1.
- return false;
- }
- opt::analysis::Bool bool_type;
- uint32_t bool_type_id = ir_context->get_type_mgr()->GetId(&bool_type);
- if (!bool_type_id) {
- // Bool type is not declared; we cannot do a comparison.
- return false;
- }
- uint32_t bound_minus_one_id =
- ir_context->get_constant_mgr()
- ->GetDefiningInstruction(&bound_minus_one)
- ->result_id();
- uint32_t compare_id =
- access_chain_clamping_info->compare_and_select_ids(index - 1).first();
- uint32_t select_id =
- access_chain_clamping_info->compare_and_select_ids(index - 1)
- .second();
- std::vector<std::unique_ptr<opt::Instruction>> new_instructions;
- // Compare the index with the bound via an instruction of the form:
- // %t1 = OpULessThanEqual %bool %index %bound_minus_one
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpULessThanEqual, bool_type_id, compare_id,
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {index_inst->result_id()}},
- {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
- // Select the index if in-bounds, otherwise one less than the bound:
- // %t2 = OpSelect %int_type %t1 %index %bound_minus_one
- new_instructions.push_back(MakeUnique<opt::Instruction>(
- ir_context, spv::Op::OpSelect, index_type_inst->result_id(),
- select_id,
- opt::Instruction::OperandList(
- {{SPV_OPERAND_TYPE_ID, {compare_id}},
- {SPV_OPERAND_TYPE_ID, {index_inst->result_id()}},
- {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
- // Add the new instructions before the access chain
- access_chain_inst->InsertBefore(std::move(new_instructions));
- // Replace %index with %t2.
- access_chain_inst->SetInOperand(index, {select_id});
- fuzzerutil::UpdateModuleIdBound(ir_context, compare_id);
- fuzzerutil::UpdateModuleIdBound(ir_context, select_id);
- }
- should_be_composite_type =
- FollowCompositeIndex(ir_context, *should_be_composite_type, index_id);
- }
- return true;
- }
- opt::Instruction* TransformationAddFunction::FollowCompositeIndex(
- opt::IRContext* ir_context, const opt::Instruction& composite_type_inst,
- uint32_t index_id) {
- uint32_t sub_object_type_id;
- switch (composite_type_inst.opcode()) {
- case spv::Op::OpTypeArray:
- case spv::Op::OpTypeRuntimeArray:
- sub_object_type_id = composite_type_inst.GetSingleWordInOperand(0);
- break;
- case spv::Op::OpTypeMatrix:
- case spv::Op::OpTypeVector:
- sub_object_type_id = composite_type_inst.GetSingleWordInOperand(0);
- break;
- case spv::Op::OpTypeStruct: {
- auto index_inst = ir_context->get_def_use_mgr()->GetDef(index_id);
- assert(index_inst->opcode() == spv::Op::OpConstant);
- assert(ir_context->get_def_use_mgr()
- ->GetDef(index_inst->type_id())
- ->opcode() == spv::Op::OpTypeInt);
- assert(ir_context->get_def_use_mgr()
- ->GetDef(index_inst->type_id())
- ->GetSingleWordInOperand(0) == 32);
- uint32_t index_value = index_inst->GetSingleWordInOperand(0);
- sub_object_type_id =
- composite_type_inst.GetSingleWordInOperand(index_value);
- break;
- }
- default:
- assert(false && "Unknown composite type.");
- sub_object_type_id = 0;
- break;
- }
- assert(sub_object_type_id && "No sub-object found.");
- return ir_context->get_def_use_mgr()->GetDef(sub_object_type_id);
- }
- std::unordered_set<uint32_t> TransformationAddFunction::GetFreshIds() const {
- std::unordered_set<uint32_t> result;
- for (auto& instruction : message_.instruction()) {
- result.insert(instruction.result_id());
- }
- if (message_.is_livesafe()) {
- result.insert(message_.loop_limiter_variable_id());
- for (auto& loop_limiter_info : message_.loop_limiter_info()) {
- result.insert(loop_limiter_info.load_id());
- result.insert(loop_limiter_info.increment_id());
- result.insert(loop_limiter_info.compare_id());
- result.insert(loop_limiter_info.logical_op_id());
- }
- for (auto& access_chain_clamping_info :
- message_.access_chain_clamping_info()) {
- for (auto& pair : access_chain_clamping_info.compare_and_select_ids()) {
- result.insert(pair.first());
- result.insert(pair.second());
- }
- }
- }
- return result;
- }
- } // namespace fuzz
- } // namespace spvtools
|