// 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_TRANSFORMATION_OUTLINE_FUNCTION_H_ #define SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_ #include #include #include #include "source/fuzz/fact_manager.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" #include "source/opt/ir_context.h" namespace spvtools { namespace fuzz { class TransformationOutlineFunction : public Transformation { public: explicit TransformationOutlineFunction( const protobufs::TransformationOutlineFunction& message); TransformationOutlineFunction( uint32_t entry_block, uint32_t exit_block, uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id, uint32_t new_function_id, uint32_t new_function_region_entry_block, uint32_t new_caller_result_id, uint32_t new_callee_result_id, std::map&& input_id_to_fresh_id, std::map&& output_id_to_fresh_id); // - All the fresh ids occurring in the transformation must be distinct and // fresh // - |message_.entry_block| and |message_.exit_block| must form a single-entry // single-exit control flow graph region // - |message_.entry_block| must not start with OpVariable // - |message_.entry_block| must not be a loop header // - |message_.exit_block| must not be a merge block or the continue target // of a loop // - A structured control flow construct must lie either completely within the // region or completely outside it // - |message.entry_block| must not start with OpPhi; this is to keep the // transformation simple - another transformation should be used to split // a desired entry block that starts with OpPhi if needed // - |message_.input_id_to_fresh_id| must contain an entry for every id // defined outside the region but used in the region // - |message_.output_id_to_fresh_id| must contain an entry for every id // defined in the region but used outside the region bool IsApplicable(opt::IRContext* context, const FactManager& fact_manager) const override; // - A new function with id |message_.new_function_id| is added to the module. // - If the region generates output ids, the return type of this function is // a new struct type with one field per output id, and with type id // |message_.new_function_struct_return_type|, otherwise the function return // types is void and |message_.new_function_struct_return_type| is not used. // - If the region generates input ids, the new function has one parameter per // input id. Fresh ids for these parameters are provided by // |message_.input_id_to_fresh_id|. // - Unless the type required for the new function is already known, // |message_.new_function_type_id| is used as the type id for a new function // type, and the new function uses this type. // - The new function starts with a dummy block with id // |message_.new_function_first_block|, which jumps straight to a successor // block, to avoid violating rules on what the first block in a function may // look like. // - The outlined region is replaced with a single block, with the same id // as |message_.entry_block|, and which calls the new function, passing the // region's input ids as parameters. The result is stored in // |message_.new_caller_result_id|, which has type // |message_.new_function_struct_return_type| (unless there are // no output ids, in which case the return type is void). The components // of this returned struct are then copied out into the region's output ids. // The block ends with the merge instruction (if any) and terminator of // |message_.exit_block|. // - The body of the new function is identical to the outlined region, except // that (a) the region's entry block has id // |message_.new_function_region_entry_block|, (b) input id uses are // replaced with parameter accesses, (c) and definitions of output ids are // replaced with definitions of corresponding fresh ids provided by // |message_.output_id_to_fresh_id|, and (d) the block of the function // ends by returning a composite of type // |message_.new_function_struct_return_type| comprised of all the fresh // output ids (unless the return type is void, in which case no value is // returned. void Apply(opt::IRContext* context, FactManager* fact_manager) const override; protobufs::Transformation ToMessage() const override; // Returns the set of blocks dominated by |entry_block| and post-dominated // by |exit_block|. static std::set GetRegionBlocks( opt::IRContext* context, opt::BasicBlock* entry_block, opt::BasicBlock* exit_block); // Yields ids that are used in |region_set| and that are either parameters // to the function containing |region_set|, or are defined by blocks of this // function that are outside |region_set|. // // Special cases: OpPhi instructions in |region_entry_block| and the // terminator of |region_exit_block| do not get outlined, therefore // - id uses in OpPhi instructions in |region_entry_block| are ignored // - id uses in the terminator instruction of |region_exit_block| are ignored static std::vector GetRegionInputIds( opt::IRContext* context, const std::set& region_set, opt::BasicBlock* region_exit_block); // Yields all ids that are defined in |region_set| and used outside // |region_set|. // // Special cases: for similar reasons as for |GetRegionInputIds|, // - ids defined in the region and used in the terminator of // |region_exit_block| count as output ids static std::vector GetRegionOutputIds( opt::IRContext* context, const std::set& region_set, opt::BasicBlock* region_exit_block); private: // Ensures that the module's id bound is at least the maximum of any fresh id // associated with the transformation. void UpdateModuleIdBoundForFreshIds( opt::IRContext* context, const std::map& input_id_to_fresh_id_map, const std::map& output_id_to_fresh_id_map) const; // Uses |input_id_to_fresh_id_map| and |output_id_to_fresh_id_map| to convert, // in the region to be outlined, all the input ids in |region_input_ids| and // the output ids in |region_output_ids| to their fresh counterparts. // Parameters |region_blocks| provides access to the blocks that must be // modified, and |original_region_exit_block| allows for some special cases // where ids should not be remapped. void RemapInputAndOutputIdsInRegion( opt::IRContext* context, const opt::BasicBlock& original_region_exit_block, const std::set& region_blocks, const std::vector& region_input_ids, const std::vector& region_output_ids, const std::map& input_id_to_fresh_id_map, const std::map& output_id_to_fresh_id_map) const; // Produce a Function object that has the right function type and parameter // declarations. The function argument types and parameter ids are dictated // by |region_input_ids| and |input_id_to_fresh_id_map|. The function return // type is dictated by |region_output_ids|. // // A new struct type to represent the function return type, and a new function // type for the function, will be added to the module (unless suitable types // are already present). // // Facts about the function containing the outlined region that are relevant // to the new function are propagated via |fact_manager|. std::unique_ptr PrepareFunctionPrototype( const std::vector& region_input_ids, const std::vector& region_output_ids, const std::map& input_id_to_fresh_id_map, opt::IRContext* context, FactManager* fact_manager) const; // Creates the body of the outlined function by cloning blocks from the // original region, given by |region_blocks|, adapting the cloned version // of |original_region_exit_block| so that it returns something appropriate, // and patching up branches to |original_region_entry_block| to refer to its // clone. Parameters |region_output_ids| and |output_id_to_fresh_id_map| are // used to determine what the function should return. // // The |fact_manager| argument allow facts about blocks being outlined, e.g. // whether they are dead blocks, to be asserted about blocks that get created // during outlining. void PopulateOutlinedFunction( const opt::BasicBlock& original_region_entry_block, const opt::BasicBlock& original_region_exit_block, const std::set& region_blocks, const std::vector& region_output_ids, const std::map& output_id_to_fresh_id_map, opt::IRContext* context, opt::Function* outlined_function, FactManager* fact_manager) const; // Shrinks the outlined region, given by |region_blocks|, down to the single // block |original_region_entry_block|. This block is itself shrunk to just // contain: // - any OpPhi instructions that were originally present // - a call to the outlined function, with parameters provided by // |region_input_ids| // - instructions to route components of the call's return value into // |region_output_ids| // - The merge instruction (if any) and terminator of the original region's // exit block, given by |cloned_exit_block_merge| and // |cloned_exit_block_terminator| // Parameters |output_id_to_type_id| and |return_type_id| provide the // provide types for the region's output ids, and the return type of the // outlined function: as the module is in an inconsistent state when this // function is called, this information cannot be gotten from the def-use // manager. void ShrinkOriginalRegion( opt::IRContext* context, std::set& region_blocks, const std::vector& region_input_ids, const std::vector& region_output_ids, const std::map& output_id_to_type_id, uint32_t return_type_id, std::unique_ptr cloned_exit_block_merge, std::unique_ptr cloned_exit_block_terminator, opt::BasicBlock* original_region_entry_block) const; protobufs::TransformationOutlineFunction message_; }; } // namespace fuzz } // namespace spvtools #endif // SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_