// 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. #ifndef SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_ #define SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_ #include #include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/fuzzer_util.h" namespace spvtools { namespace fuzz { // A fuzzer pass that randomly adds code from other SPIR-V modules to the module // being transformed. class FuzzerPassDonateModules : public FuzzerPass { public: FuzzerPassDonateModules( opt::IRContext* ir_context, TransformationContext* transformation_context, FuzzerContext* fuzzer_context, protobufs::TransformationSequence* transformations, const std::vector& donor_suppliers); void Apply() override; // Donates the global declarations and functions of |donor_ir_context| into // the fuzzer pass's IR context. |make_livesafe| dictates whether the // functions of the donated module will be made livesafe (see // FactFunctionIsLivesafe). void DonateSingleModule(opt::IRContext* donor_ir_context, bool make_livesafe); private: // Adapts a storage class coming from a donor module so that it will work // in a recipient module, e.g. by changing Uniform to Private. static SpvStorageClass AdaptStorageClass(SpvStorageClass donor_storage_class); // Identifies all external instruction set imports in |donor_ir_context| and // populates |original_id_to_donated_id| with a mapping from the donor's id // for such an import to a corresponding import in the recipient. Aborts if // no such corresponding import is available. void HandleExternalInstructionImports( opt::IRContext* donor_ir_context, std::map* original_id_to_donated_id); // Considers all types, globals, constants and undefs in |donor_ir_context|. // For each instruction, uses |original_to_donated_id| to map its result id to // either (1) the id of an existing identical instruction in the recipient, or // (2) to a fresh id, in which case the instruction is also added to the // recipient (with any operand ids that it uses being remapped via // |original_id_to_donated_id|). void HandleTypesAndValues( opt::IRContext* donor_ir_context, std::map* original_id_to_donated_id); // Helper method for HandleTypesAndValues, to handle a single type/value. void HandleTypeOrValue( const opt::Instruction& type_or_value, std::map* original_id_to_donated_id); // Assumes that |donor_ir_context| does not exhibit recursion. Considers the // functions in |donor_ir_context|'s call graph in a reverse-topologically- // sorted order (leaves-to-root), adding each function to the recipient // module, rewritten to use fresh ids and using |original_id_to_donated_id| to // remap ids. The |make_livesafe| argument captures whether the functions in // the module are required to be made livesafe before being added to the // recipient. void HandleFunctions(opt::IRContext* donor_ir_context, std::map* original_id_to_donated_id, bool make_livesafe); // During donation we will have to ignore some instructions, e.g. because they // use opcodes that we cannot support or because they reference the ids of // instructions that have not been donated. This function encapsulates the // logic for deciding which whether instruction |instruction| from // |donor_ir_context| can be donated. bool CanDonateInstruction( opt::IRContext* donor_ir_context, const opt::Instruction& instruction, const std::map& original_id_to_donated_id, const std::set& skipped_instructions) const; // We treat the OpArrayLength instruction specially. In the donor shader this // instruction yields the length of a runtime array that is the final member // of a struct. During donation, we will have converted the runtime array // type, and the associated struct field, into a fixed-size array. // // Instead of donating this instruction, we turn it into an OpCopyObject // instruction that copies the size of the fixed-size array. void HandleOpArrayLength( const opt::Instruction& instruction, std::map* original_id_to_donated_id, std::vector* donated_instructions) const; // The instruction |instruction| is required to be an instruction that cannot // be easily donated, either because it uses an unsupported opcode, has an // unsupported result type, or uses id operands that could not be donated. // // If |instruction| generates a result id, the function attempts to add a // substitute for |instruction| to |donated_instructions| that has the correct // result type. If this cannot be done, the instruction's result id is added // to |skipped_instructions|. The mapping from donor ids to recipient ids is // managed by |original_id_to_donated_id|. void HandleDifficultInstruction( const opt::Instruction& instruction, std::map* original_id_to_donated_id, std::vector* donated_instructions, std::set* skipped_instructions); // Adds an instruction based in |instruction| to |donated_instructions| in a // form ready for donation. The original instruction comes from // |donor_ir_context|, and |original_id_to_donated_id| maps ids from // |donor_ir_context| to corresponding ids in the recipient module. void PrepareInstructionForDonation( const opt::Instruction& instruction, opt::IRContext* donor_ir_context, std::map* original_id_to_donated_id, std::vector* donated_instructions); // Tries to create a protobufs::LoopLimiterInfo given a loop header basic // block. Returns true if successful and outputs loop limiter into the |out| // variable. Otherwise, returns false. |out| contains an undefined value when // this function returns false. bool CreateLoopLimiterInfo( opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header, const std::map& original_id_to_donated_id, protobufs::LoopLimiterInfo* out); // Requires that |donated_instructions| represents a prepared version of the // instructions of |function_to_donate| (which comes from |donor_ir_context|) // ready for donation, and |original_id_to_donated_id| maps ids from // |donor_ir_context| to their corresponding ids in the recipient module. // // Attempts to add a livesafe version of the function, based on // |donated_instructions|, to the recipient module. Returns true if the // donation was successful, false otherwise. bool MaybeAddLivesafeFunction( const opt::Function& function_to_donate, opt::IRContext* donor_ir_context, const std::map& original_id_to_donated_id, const std::vector& donated_instructions); // Returns true if and only if |instruction| is a scalar, vector, matrix, // array or struct; i.e. it is not an opaque type. bool IsBasicType(const opt::Instruction& instruction) const; // Functions that supply SPIR-V modules std::vector donor_suppliers_; }; } // namespace fuzz } // namespace spvtools #endif // SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_