// 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_dead_continue.h" #include "source/fuzz/fuzzer_util.h" namespace spvtools { namespace fuzz { TransformationAddDeadContinue::TransformationAddDeadContinue( const spvtools::fuzz::protobufs::TransformationAddDeadContinue& message) : message_(message) {} TransformationAddDeadContinue::TransformationAddDeadContinue( uint32_t from_block, bool continue_condition_value, std::vector phi_id) { message_.set_from_block(from_block); message_.set_continue_condition_value(continue_condition_value); for (auto id : phi_id) { message_.add_phi_id(id); } } bool TransformationAddDeadContinue::IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const { // First, we check that a constant with the same value as // |message_.continue_condition_value| is present. if (!fuzzerutil::MaybeGetBoolConstantId( ir_context, message_.continue_condition_value())) { // The required constant is not present, so the transformation cannot be // applied. return false; } // Check that |message_.from_block| really is a block id. opt::BasicBlock* bb_from = fuzzerutil::MaybeFindBlock(ir_context, message_.from_block()); if (bb_from == nullptr) { return false; } // Check that |message_.from_block| ends with an unconditional branch. if (bb_from->terminator()->opcode() != SpvOpBranch) { // The block associated with the id does not end with an unconditional // branch. return false; } assert(bb_from != nullptr && "We should have found a block if this line of code is reached."); assert( bb_from->id() == message_.from_block() && "The id of the block we found should match the source id for the break."); // Get the header for the innermost loop containing |message_.from_block|. // Because the structured CFG analysis does not regard a loop header as part // of the loop it heads, we check first whether bb_from is a loop header // before using the structured CFG analysis. auto loop_header = bb_from->IsLoopHeader() ? message_.from_block() : ir_context->GetStructuredCFGAnalysis()->ContainingLoop( message_.from_block()); if (!loop_header) { return false; } auto continue_block = ir_context->cfg()->block(loop_header)->ContinueBlockId(); if (!fuzzerutil::BlockIsReachableInItsFunction( ir_context, ir_context->cfg()->block(continue_block))) { // If the loop's continue block is unreachable, we conservatively do not // allow adding a dead continue, to avoid the compilations that arise due to // the lack of sensible dominance information for unreachable blocks. return false; } if (fuzzerutil::BlockIsInLoopContinueConstruct( ir_context, message_.from_block(), loop_header)) { // We cannot jump to the continue target from the continue construct. return false; } if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) { // A branch straight to the continue target that is also a merge block might // break the property that a construct header must dominate its merge block // (if the merge block is reachable). return false; } // Check whether the data passed to extend OpPhi instructions is appropriate. if (!fuzzerutil::PhiIdsOkForNewEdge(ir_context, bb_from, ir_context->cfg()->block(continue_block), message_.phi_id())) { return false; } // Adding the dead break is only valid if SPIR-V rules related to dominance // hold. Rather than checking these rules explicitly, we defer to the // validator. We make a clone of the module, apply the transformation to the // clone, and check whether the transformed clone is valid. // // In principle some of the above checks could be removed, with more reliance // being placed on the validator. This should be revisited if we are sure // the validator is complete with respect to checking structured control flow // rules. auto cloned_context = fuzzerutil::CloneIRContext(ir_context); ApplyImpl(cloned_context.get()); return fuzzerutil::IsValid(cloned_context.get(), transformation_context.GetValidatorOptions()); } void TransformationAddDeadContinue::Apply( opt::IRContext* ir_context, TransformationContext* /*unused*/) const { ApplyImpl(ir_context); // Invalidate all analyses ir_context->InvalidateAnalysesExceptFor( opt::IRContext::Analysis::kAnalysisNone); } protobufs::Transformation TransformationAddDeadContinue::ToMessage() const { protobufs::Transformation result; *result.mutable_add_dead_continue() = message_; return result; } void TransformationAddDeadContinue::ApplyImpl( spvtools::opt::IRContext* ir_context) const { auto bb_from = ir_context->cfg()->block(message_.from_block()); auto continue_block = bb_from->IsLoopHeader() ? bb_from->ContinueBlockId() : ir_context->GetStructuredCFGAnalysis()->LoopContinueBlock( message_.from_block()); assert(continue_block && "message_.from_block must be in a loop."); fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis( ir_context, bb_from, ir_context->cfg()->block(continue_block), message_.continue_condition_value(), message_.phi_id()); } } // namespace fuzz } // namespace spvtools