SPIRV-Tools/source/fuzz/transformation_outline_function.h
Alastair Donaldson 8d4261bc44
spirv-fuzz: Introduce TransformationContext (#3272)
Some transformations (e.g. TransformationAddFunction) rely on running
the validator to decide whether the transformation is applicable.  A
recent change allowed spirv-fuzz to take validator options, to cater
for the case where a module should be considered valid under
particular conditions.  However, validation during the checking of
transformations had no access to these validator options.

This change introduced TransformationContext, which currently consists
of a fact manager and a set of validator options, but could in the
future have other fields corresponding to other objects that it is
useful to have access to when applying transformations.  Now, instead
of checking and applying transformations in the context of a
FactManager, a TransformationContext is used.  This gives access to
the fact manager as before, and also access to the validator options
when they are needed.
2020-04-02 15:54:46 +01:00

226 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,
std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
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 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* ir_context,
TransformationContext* transformation_context) 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.
//
// 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_fresh_id_map,
opt::IRContext* ir_context, opt::Function* outlined_function,
TransformationContext* transformation_context) 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, 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_