| // 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 |