mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-26 21:30:07 +00:00
b1350659b6
Fixes #3935.
230 lines
11 KiB
C++
230 lines
11 KiB
C++
// 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 <map>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
|
#include "source/fuzz/transformation.h"
|
|
#include "source/fuzz/transformation_context.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,
|
|
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id,
|
|
const std::map<uint32_t, uint32_t>& 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* ir_context,
|
|
const TransformationContext& transformation_context) 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 placeholder 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* ir_context,
|
|
TransformationContext* transformation_context) const override;
|
|
|
|
std::unordered_set<uint32_t> GetFreshIds() 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<opt::BasicBlock*> GetRegionBlocks(
|
|
opt::IRContext* ir_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<uint32_t> GetRegionInputIds(
|
|
opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& 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<uint32_t> GetRegionOutputIds(
|
|
opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& 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* ir_context,
|
|
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
|
const std::map<uint32_t, uint32_t>& 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* ir_context,
|
|
const opt::BasicBlock& original_region_exit_block,
|
|
const std::set<opt::BasicBlock*>& region_blocks,
|
|
const std::vector<uint32_t>& region_input_ids,
|
|
const std::vector<uint32_t>& region_output_ids,
|
|
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
|
const std::map<uint32_t, uint32_t>& 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 the vact manager in
|
|
// |transformation_context|.
|
|
std::unique_ptr<opt::Function> PrepareFunctionPrototype(
|
|
const std::vector<uint32_t>& region_input_ids,
|
|
const std::vector<uint32_t>& region_output_ids,
|
|
const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
|
|
opt::IRContext* ir_context,
|
|
TransformationContext* transformation_context) 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. Parameter
|
|
// |output_id_to_type_id| provides the type of each output id.
|
|
//
|
|
// The |transformation_context| 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<opt::BasicBlock*>& region_blocks,
|
|
const std::vector<uint32_t>& region_output_ids,
|
|
const std::map<uint32_t, uint32_t>& output_id_to_type_id,
|
|
const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
|
|
opt::IRContext* ir_context, opt::Function* outlined_function) 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* ir_context,
|
|
const std::set<opt::BasicBlock*>& region_blocks,
|
|
const std::vector<uint32_t>& region_input_ids,
|
|
const std::vector<uint32_t>& region_output_ids,
|
|
const std::map<uint32_t, uint32_t>& output_id_to_type_id,
|
|
uint32_t return_type_id,
|
|
std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
|
|
std::unique_ptr<opt::Instruction> 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_
|