Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2019-10-11 20:38:46 -07:00
parent 6ceb4bdb35
commit 70cbb974c4
91 changed files with 8337 additions and 1264 deletions

View File

@@ -734,10 +734,6 @@ static_library("spvtools_reduce") {
"source/reduce/remove_function_reduction_opportunity_finder.h",
"source/reduce/remove_instruction_reduction_opportunity.cpp",
"source/reduce/remove_instruction_reduction_opportunity.h",
"source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp",
"source/reduce/remove_opname_instruction_reduction_opportunity_finder.h",
"source/reduce/remove_relaxed_precision_decoration_opportunity_finder.cpp",
"source/reduce/remove_relaxed_precision_decoration_opportunity_finder.h",
"source/reduce/remove_selection_reduction_opportunity.cpp",
"source/reduce/remove_selection_reduction_opportunity.h",
"source/reduce/remove_selection_reduction_opportunity_finder.cpp",

View File

@@ -1,7 +1,11 @@
Revision history for SPIRV-Tools
v2019.5-dev 2019-08-08
v2019.5-dev 2019-10-09
- General:
- Export SPIRV-Tools targets on installation
- SPIRV-Tools support for SPIR-V 1.5 (#2865)
- Add WebGPU SPIR-V Assembler in JavaScript. (#2876)
- Add Bazel build configuration. (#2891)
- Optimizer
- Add descriptor array scalar replacement (#2742)
- Add pass to wrap OpKill in a function call (#2790)
@@ -9,17 +13,44 @@ v2019.5-dev 2019-08-08
- Add pass to replace AMD shader ballot extension (#2811)
- Add pass to make Float32 operation relax precision (#2808)
- Add pass to make relax precision operation Float16 (#2808)
- Add pass to replace uses of 3 AMD extensions (#2814)
- Fold Min, Max, and Clamp instructions. (#2836)
- Better handling of OpKill in continues (#2842,#2922,#2933)
- Enable OpTypeCooperativeMatrix specialization (#2927)
Fixes:
Instrument: Fix version 2 output record write for tess eval shaders. (#2782)
Instrument: Add support for Buffer Device Address extension (#2792)
Fix check for changed binary in API call. (#2798)
- Instrument: Fix version 2 output record write for tess eval shaders. (#2782)
- Instrument: Add support for Buffer Device Address extension (#2792)
- Fix check for changed binary in API call. (#2798)
- For WebGPU<->Vulkan optimization, set correct execution environment (#2834)
- Handle OpConstantNull in copy-prop-arrays. (#2870)
- Use OpReturn* in wrap-opkill (#2886)
- Validator
- Add generic builtin validation of target (#2843)
- Extra resource interface validation (#2864)
- Adding valilidation checks for OpEntryPoint duplicate names and execution mode (#2862)
- Relaxed bitcast with pointers (#2878)
- Validate physical storage buffer restrictions (#2930)
- Add SPV_KHR_shader_clock validation (#2879)
Fixes:
Fix validation of constant matrices (#2794)
Update "remquor" validation
- Fix validation of constant matrices (#2794)
- Update "remquor" validation
- Only allow previously declared forward refs in structs (#2920)
- Reduce
- Remove relaxed precision decorations (#2797)
- Reduce/fuzz: improve command line args (#2932)
- Improve remove unref instr pass (#2945)
Fixes:
- Fuzz
- Fix add-dead-break and add-dead-continue passes to respect dominance (#2838)
- Add fuzzer pass to copy objects (#2853)
- Add fuzzer pass to replace ids with synonyms (#2857)
- Allow validation during spirv-fuzz replay (#2873)
- Employ the "swarm testing" idea in spirv-fuzz (#2890)
- reduce/fuzz: improve command line args (#2932)
- option to convert shader into a form that renders red (#2934)
- Add fuzzer pass to change selection controls (#2944)
- add transformation and pass to construct composites (#2941)
v2019.4 2019-08-08
- General:

View File

@@ -1 +1 @@
"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-102-gc18c9ff6"
"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-120-g2276e597"

View File

@@ -37,14 +37,20 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass.h
fuzzer_pass_add_dead_breaks.h
fuzzer_pass_add_dead_continues.h
fuzzer_pass_add_no_contraction_decorations.h
fuzzer_pass_add_useful_constructs.h
fuzzer_pass_adjust_function_controls.h
fuzzer_pass_adjust_loop_controls.h
fuzzer_pass_adjust_selection_controls.h
fuzzer_pass_apply_id_synonyms.h
fuzzer_pass_construct_composites.h
fuzzer_pass_copy_objects.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_permute_blocks.h
fuzzer_pass_split_blocks.h
fuzzer_util.h
id_use_descriptor.h
instruction_descriptor.h
protobufs/spirvfuzz_protobufs.h
pseudo_random_generator.h
random_generator.h
@@ -55,15 +61,20 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_constant_scalar.h
transformation_add_dead_break.h
transformation_add_dead_continue.h
transformation_add_no_contraction_decoration.h
transformation_add_type_boolean.h
transformation_add_type_float.h
transformation_add_type_int.h
transformation_add_type_pointer.h
transformation_construct_composite.h
transformation_copy_object.h
transformation_move_block_down.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
transformation_replace_id_with_synonym.h
transformation_set_function_control.h
transformation_set_loop_control.h
transformation_set_selection_control.h
transformation_split_block.h
uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
@@ -76,14 +87,20 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass.cpp
fuzzer_pass_add_dead_breaks.cpp
fuzzer_pass_add_dead_continues.cpp
fuzzer_pass_add_no_contraction_decorations.cpp
fuzzer_pass_add_useful_constructs.cpp
fuzzer_pass_adjust_function_controls.cpp
fuzzer_pass_adjust_loop_controls.cpp
fuzzer_pass_adjust_selection_controls.cpp
fuzzer_pass_apply_id_synonyms.cpp
fuzzer_pass_construct_composites.cpp
fuzzer_pass_copy_objects.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_permute_blocks.cpp
fuzzer_pass_split_blocks.cpp
fuzzer_util.cpp
id_use_descriptor.cpp
instruction_descriptor.cpp
pseudo_random_generator.cpp
random_generator.cpp
replayer.cpp
@@ -93,15 +110,20 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_constant_scalar.cpp
transformation_add_dead_break.cpp
transformation_add_dead_continue.cpp
transformation_add_no_contraction_decoration.cpp
transformation_add_type_boolean.cpp
transformation_add_type_float.cpp
transformation_add_type_int.cpp
transformation_add_type_pointer.cpp
transformation_construct_composite.cpp
transformation_copy_object.cpp
transformation_move_block_down.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_id_with_synonym.cpp
transformation_set_function_control.cpp
transformation_set_loop_control.cpp
transformation_set_selection_control.cpp
transformation_split_block.cpp
uniform_buffer_element_descriptor.cpp
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc

View File

@@ -15,6 +15,7 @@
#include "source/fuzz/force_render_red.h"
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/uniform_buffer_element_descriptor.h"
@@ -147,9 +148,10 @@ MakeConstantUniformReplacement(opt::IRContext* ir_context,
uint32_t greater_than_instruction,
uint32_t in_operand_index) {
return MakeUnique<TransformationReplaceConstantWithUniform>(
transformation::MakeIdUseDescriptor(constant_id, SpvOpFOrdGreaterThan,
in_operand_index,
greater_than_instruction, 0),
MakeIdUseDescriptor(constant_id,
MakeInstructionDescriptor(greater_than_instruction,
SpvOpFOrdGreaterThan, 0),
in_operand_index),
fact_manager.GetUniformDescriptorsForConstant(ir_context, constant_id)[0],
ir_context->TakeNextId(), ir_context->TakeNextId());
}

View File

@@ -22,8 +22,13 @@
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
#include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/fuzzer_pass_construct_composites.h"
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
@@ -138,6 +143,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassApplyIdSynonyms>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassConstructComposites>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassCopyObjects>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
@@ -162,6 +170,29 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
passes[fuzzer_context.RandomIndex(passes)]->Apply();
}
// Now apply some passes that it does not make sense to apply repeatedly,
// as they do not unlock other passes.
std::vector<std::unique_ptr<FuzzerPass>> final_passes;
MaybeAddPass<FuzzerPassAdjustFunctionControls>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustLoopControls>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustSelectionControls>(
&passes, ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out);
for (auto& pass : final_passes) {
pass->Apply();
}
if (fuzzer_context.ChooseEven()) {
FuzzerPassAddNoContractionDecorations(ir_context.get(), &fact_manager,
&fuzzer_context,
transformation_sequence_out)
.Apply();
}
// Encode the module as a binary.
ir_context->module()->ToBinary(binary_out, false);

View File

@@ -25,12 +25,25 @@ namespace {
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingFunctionControl = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingLoopControl = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
// Default limits for various quantities that are chosen during fuzzing.
// Keep them in alphabetical order.
const uint32_t kDefaultMaxLoopControlPartialCount = 100;
const uint32_t kDefaultMaxLoopControlPeelCount = 100;
// Default functions for controlling how deep to go during recursive
// generation/transformation. Keep them in alphabetical order.
@@ -53,6 +66,16 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
chance_of_adding_dead_continue_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
chance_of_adding_no_contraction_decoration_ =
ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
chance_of_adjusting_function_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingFunctionControl);
chance_of_adjusting_loop_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingLoopControl);
chance_of_adjusting_selection_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
chance_of_constructing_composite_ =
ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
chance_of_moving_block_down_ =
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
@@ -61,6 +84,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount;
max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount;
}
FuzzerContext::~FuzzerContext() = default;

View File

@@ -62,6 +62,21 @@ class FuzzerContext {
uint32_t GetChanceOfAddingDeadContinue() {
return chance_of_adding_dead_continue_;
}
uint32_t GetChanceOfAddingNoContractionDecoration() {
return chance_of_adding_no_contraction_decoration_;
}
uint32_t GetChanceOfAdjustingFunctionControl() {
return chance_of_adjusting_function_control_;
}
uint32_t GetChanceOfAdjustingLoopControl() {
return chance_of_adjusting_loop_control_;
}
uint32_t GetChanceOfAdjustingSelectionControl() {
return chance_of_adjusting_selection_control_;
}
uint32_t GetChanceOfConstructingComposite() {
return chance_of_constructing_composite_;
}
uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
uint32_t GetChanceOfObfuscatingConstant() {
@@ -71,6 +86,12 @@ class FuzzerContext {
return chance_of_replacing_id_with_synonym_;
}
uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
uint32_t GetRandomLoopControlPeelCount() {
return random_generator_->RandomUint32(max_loop_control_peel_count_);
}
uint32_t GetRandomLoopControlPartialCount() {
return random_generator_->RandomUint32(max_loop_control_partial_count_);
}
// Functions to control how deeply to recurse.
// Keep them in alphabetical order.
@@ -88,12 +109,23 @@ class FuzzerContext {
// Keep them in alphabetical order.
uint32_t chance_of_adding_dead_break_;
uint32_t chance_of_adding_dead_continue_;
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adjusting_function_control_;
uint32_t chance_of_adjusting_loop_control_;
uint32_t chance_of_adjusting_selection_control_;
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_splitting_block_;
// Limits associated with various quantities for which random values are
// chosen during fuzzing.
// Keep them in alphabetical order.
uint32_t max_loop_control_partial_count_;
uint32_t max_loop_control_peel_count_;
// Functions to determine with what probability to go deeper when generating
// or mutating constructs recursively.
const std::function<bool(uint32_t, RandomGenerator*)>&

View File

@@ -27,5 +27,104 @@ FuzzerPass::FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerPass::~FuzzerPass() = default;
std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
const opt::Function& function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant) {
// TODO(afd) The following is (relatively) simple, but may end up being
// prohibitively inefficient, as it walks the whole dominator tree for
// every instruction that is considered.
std::vector<opt::Instruction*> result;
// Consider all global declarations
for (auto& global : GetIRContext()->module()->types_values()) {
if (instruction_is_relevant(GetIRContext(), &global)) {
result.push_back(&global);
}
}
// Consider all previous instructions in this block
for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
++prev_inst_it) {
if (instruction_is_relevant(GetIRContext(), &*prev_inst_it)) {
result.push_back(&*prev_inst_it);
}
}
// Walk the dominator tree to consider all instructions from dominating
// blocks
auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(&function);
for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
next_dominator != nullptr;
next_dominator =
dominator_analysis->ImmediateDominator(next_dominator)) {
for (auto& dominating_inst : *next_dominator) {
if (instruction_is_relevant(GetIRContext(), &dominating_inst)) {
result.push_back(&dominating_inst);
}
}
}
return result;
}
void FuzzerPass::MaybeAddTransformationBeforeEachInstruction(
std::function<uint32_t(
const opt::Function& function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it, uint32_t base, uint32_t offset)>
maybe_apply_transformation) {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// We now consider every instruction in the block, randomly deciding
// whether to apply a transformation before it.
// In order for transformations to insert new instructions, they need to
// be able to identify the instruction to insert before. We enable this
// by tracking a base instruction, which must generate a result id, and
// an offset (to allow us to identify instructions that do not generate
// result ids).
// The initial base instruction is the block label.
uint32_t base = block.id();
uint32_t offset = 0;
// Consider every instruction in the block.
for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
if (inst_it->HasResultId()) {
// In the case that the instruction has a result id, we use the
// instruction as its own base, with zero offset.
base = inst_it->result_id();
offset = 0;
} else {
// The instruction does not have a result id, so we need to identify
// it via the latest instruction that did have a result id (base), and
// an incremented offset.
offset++;
}
// Invoke the provided function, which might apply a transformation.
// Its return value informs us of how many instructions it inserted.
// (This will be 0 if no transformation was applied.)
uint32_t num_instructions_inserted =
maybe_apply_transformation(function, &block, inst_it, base, offset);
if (!inst_it->HasResultId()) {
// We are tracking the current id-less instruction via an offset,
// |offset|, from a previous instruction, |base|, that has an id. We
// increment |offset| to reflect any newly-inserted instructions.
//
// An alternative would be to reset |base| to be an id generated by
// a newly-inserted instruction, but that would be more complex, and
// sticking to a |base| that already existed before this
// transformation was applied makes the applicability of future
// transformations less tightly coupled with the presence of the just-
// applied transformation.
offset += num_instructions_inserted;
}
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -15,9 +15,13 @@
#ifndef SOURCE_FUZZ_FUZZER_PASS_H_
#define SOURCE_FUZZ_FUZZER_PASS_H_
#include <functional>
#include <vector>
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
@@ -48,6 +52,44 @@ class FuzzerPass {
return transformations_;
}
// Returns all instructions that are *available* at |inst_it|, which is
// required to be inside block |block| of function |function| - that is, all
// instructions at global scope and all instructions that strictly dominate
// |inst_it|.
//
// Filters said instructions to return only those that satisfy the
// |instruction_is_relevant| predicate. This, for instance, could ignore all
// instructions that have a particular decoration.
std::vector<opt::Instruction*> FindAvailableInstructions(
const opt::Function& function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant);
// A helper method that iterates through each instruction in each block, at
// all times tracking a base instruction and offset that allows that latest
// instruction to be located even if it has no result id.
//
// The code to manipulate the base and offset is a bit fiddly, and the point
// of this method is to avoiding having to duplicate it in multiple
// transformation passes.
//
// The function |maybe_apply_transformation| is invoked for each instruction
// |inst_it| in block |block| of function |function| that is encountered. The
// |base| and |offset| parameters to the function object allow |inst_it| to be
// identified.
//
// The job of |maybe_apply_transformation| is to randomly decide whether to
// try to apply some transformation, and then - if selected - to attempt to
// apply it. The function returns the number of instructions that were
// inserted before |inst_it|, so that |offset| can be updated.
//
void MaybeAddTransformationBeforeEachInstruction(
std::function<uint32_t(
const opt::Function& function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it, uint32_t base, uint32_t offset)>
maybe_apply_transformation);
private:
opt::IRContext* ir_context_;
FactManager* fact_manager_;

View File

@@ -0,0 +1,61 @@
// 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/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddNoContractionDecorations::FuzzerPassAddNoContractionDecorations(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
FuzzerPassAddNoContractionDecorations::
~FuzzerPassAddNoContractionDecorations() = default;
void FuzzerPassAddNoContractionDecorations::Apply() {
// Consider every instruction in every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& inst : block) {
// Restrict attention to arithmetic instructions (as defined in the
// SPIR-V specification).
if (TransformationAddNoContractionDecoration::IsArithmetic(
inst.opcode())) {
// Randomly choose whether to apply the NoContraction decoration to
// this arithmetic instruction.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfAddingNoContractionDecoration())) {
TransformationAddNoContractionDecoration transformation(
inst.result_id());
assert(transformation.IsApplicable(GetIRContext(),
*GetFactManager()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
}
}
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,39 @@
// 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_ADD_NO_CONTRACTION_DECORATIONS_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that applies the NoContraction decoration to arithmetic instructions.
class FuzzerPassAddNoContractionDecorations : public FuzzerPass {
public:
FuzzerPassAddNoContractionDecorations(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddNoContractionDecorations() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_

View File

@@ -24,8 +24,6 @@
namespace spvtools {
namespace fuzz {
using opt::IRContext;
FuzzerPassAddUsefulConstructs::FuzzerPassAddUsefulConstructs(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,

View File

@@ -0,0 +1,73 @@
// 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/fuzzer_pass_adjust_function_controls.h"
#include "source/fuzz/transformation_set_function_control.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustFunctionControls::FuzzerPassAdjustFunctionControls(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
FuzzerPassAdjustFunctionControls::~FuzzerPassAdjustFunctionControls() = default;
void FuzzerPassAdjustFunctionControls::Apply() {
// Consider every function in the module.
for (auto& function : *GetIRContext()->module()) {
// Randomly decide whether to adjust this function's controls.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingFunctionControl())) {
// Grab the function control mask for the function in its present form.
uint32_t existing_function_control_mask =
function.DefInst().GetSingleWordInOperand(0);
// For the new mask, we first randomly select one of three basic masks:
// None, Inline or DontInline. These are always valid (and are mutually
// exclusive).
std::vector<uint32_t> basic_function_control_masks = {
SpvFunctionControlMaskNone, SpvFunctionControlInlineMask,
SpvFunctionControlDontInlineMask};
uint32_t new_function_control_mask =
basic_function_control_masks[GetFuzzerContext()->RandomIndex(
basic_function_control_masks)];
// We now consider the Pure and Const mask bits. If these are already
// set on the function then it's OK to keep them, but also interesting
// to consider dropping them, so we decide randomly in each case.
for (auto mask_bit :
{SpvFunctionControlPureMask, SpvFunctionControlConstMask}) {
if ((existing_function_control_mask & mask_bit) &&
GetFuzzerContext()->ChooseEven()) {
new_function_control_mask |= mask_bit;
}
}
// Create and add a transformation.
TransformationSetFunctionControl transformation(
function.DefInst().result_id(), new_function_control_mask);
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,39 @@
// 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_ADJUST_FUNCTION_CONTROLS_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that adjusts the function controls on OpFunction instructions.
class FuzzerPassAdjustFunctionControls : public FuzzerPass {
public:
FuzzerPassAdjustFunctionControls(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustFunctionControls() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_

View File

@@ -0,0 +1,121 @@
// 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/fuzzer_pass_adjust_loop_controls.h"
#include "source/fuzz/transformation_set_loop_control.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
void FuzzerPassAdjustLoopControls::Apply() {
// Consider every merge instruction in the module (via looking through all
// functions and blocks).
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
if (auto merge_inst = block.GetMergeInst()) {
// Ignore the instruction if it is not a loop merge.
if (merge_inst->opcode() != SpvOpLoopMerge) {
continue;
}
// Decide randomly whether to adjust this loop merge.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingLoopControl())) {
continue;
}
uint32_t existing_mask = merge_inst->GetSingleWordOperand(
TransformationSetLoopControl::kLoopControlMaskInOperandIndex);
// First, set the new mask to one of None, Unroll or DontUnroll.
std::vector<uint32_t> basic_masks = {SpvLoopControlMaskNone,
SpvLoopControlUnrollMask,
SpvLoopControlDontUnrollMask};
uint32_t new_mask =
basic_masks[GetFuzzerContext()->RandomIndex(basic_masks)];
// For the loop controls that depend on guarantees about what the loop
// does, check which of these were present in the existing mask and
// randomly decide whether to keep them. They are just hints, so
// removing them should not change the semantics of the module.
for (auto mask_bit :
{SpvLoopControlDependencyInfiniteMask,
SpvLoopControlDependencyLengthMask,
SpvLoopControlMinIterationsMask, SpvLoopControlMaxIterationsMask,
SpvLoopControlIterationMultipleMask}) {
if ((existing_mask & mask_bit) && GetFuzzerContext()->ChooseEven()) {
// The mask bits we are considering are not available in all SPIR-V
// versions. However, we only include a mask bit if it was present
// in the original loop control mask, and we work under the
// assumption that we are transforming a valid module, thus we don't
// need to actually check whether the SPIR-V version being used
// supports these loop control mask bits.
new_mask |= mask_bit;
}
}
// We use 0 for peel count and partial count in the case that we choose
// not to set these controls.
uint32_t peel_count = 0;
uint32_t partial_count = 0;
// PeelCount and PartialCount are not compatible with DontUnroll, so
// we check whether DontUnroll is set.
if (!(new_mask & SpvLoopControlDontUnrollMask)) {
// If PeelCount is supported by this SPIR-V version, randomly choose
// whether to set it. If it was set in the original mask and is not
// selected for setting here, that amounts to dropping it.
if (TransformationSetLoopControl::PeelCountIsSupported(
GetIRContext()) &&
GetFuzzerContext()->ChooseEven()) {
new_mask |= SpvLoopControlPeelCountMask;
// The peel count is chosen randomly - if PeelCount was already set
// this will overwrite whatever peel count was previously used.
peel_count = GetFuzzerContext()->GetRandomLoopControlPeelCount();
}
// Similar, but for PartialCount.
if (TransformationSetLoopControl::PartialCountIsSupported(
GetIRContext()) &&
GetFuzzerContext()->ChooseEven()) {
new_mask |= SpvLoopControlPartialCountMask;
partial_count =
GetFuzzerContext()->GetRandomLoopControlPartialCount();
}
}
// Apply the transformation and add it to the output transformation
// sequence.
TransformationSetLoopControl transformation(block.id(), new_mask,
peel_count, partial_count);
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -12,25 +12,28 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_REDUCE_REMOVE_RELAXED_PRECISION_OPPORTUNITY_FINDER_H_
#define SOURCE_REDUCE_REMOVE_RELAXED_PRECISION_OPPORTUNITY_FINDER_H_
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
#include "source/reduce/reduction_opportunity_finder.h"
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace reduce {
namespace fuzz {
// A finder for opportunities to remove relaxed precision decorations.
class RemoveRelaxedPrecisionDecorationOpportunityFinder
: public ReductionOpportunityFinder {
// A pass that adjusts the loop controls on OpLoopMerge instructions.
class FuzzerPassAdjustLoopControls : public FuzzerPass {
public:
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const override;
FuzzerPassAdjustLoopControls(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
std::string GetName() const override;
~FuzzerPassAdjustLoopControls() override;
void Apply() override;
};
} // namespace reduce
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_RELAXED_PRECISION_OPPORTUNITY_FINDER_H_
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_

View File

@@ -0,0 +1,76 @@
// 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/fuzzer_pass_adjust_selection_controls.h"
#include "source/fuzz/transformation_set_selection_control.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustSelectionControls::FuzzerPassAdjustSelectionControls(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
FuzzerPassAdjustSelectionControls::~FuzzerPassAdjustSelectionControls() =
default;
void FuzzerPassAdjustSelectionControls::Apply() {
// Consider every merge instruction in the module (via looking through all
// functions and blocks).
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
if (auto merge_inst = block.GetMergeInst()) {
// Ignore the instruction if it is not a selection merge.
if (merge_inst->opcode() != SpvOpSelectionMerge) {
continue;
}
// Choose randomly whether to change the selection control for this
// instruction.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingSelectionControl())) {
continue;
}
// The choices to change the selection control to are the set of valid
// controls, minus the current control.
std::vector<uint32_t> choices;
for (auto control :
{SpvSelectionControlMaskNone, SpvSelectionControlFlattenMask,
SpvSelectionControlDontFlattenMask}) {
if (control == merge_inst->GetSingleWordOperand(1)) {
continue;
}
choices.push_back(control);
}
// Apply the transformation and add it to the output transformation
// sequence.
TransformationSetSelectionControl transformation(
block.id(), choices[GetFuzzerContext()->RandomIndex(choices)]);
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,39 @@
// 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_ADJUST_SELECTION_CONTROLS_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that adjusts the selection controls on OpSelectionMerge instructions.
class FuzzerPassAdjustSelectionControls : public FuzzerPass {
public:
FuzzerPassAdjustSelectionControls(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustSelectionControls() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_

View File

@@ -63,9 +63,6 @@ void FuzzerPassApplyIdSynonyms::Apply() {
GetFuzzerContext()->RandomIndex(synonyms_to_try);
auto synonym_to_try = synonyms_to_try[synonym_index];
synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index);
assert(synonym_to_try->index().empty() &&
"Right now we only support id == id synonyms; supporting "
"e.g. id == index-into-vector will come later");
if (!TransformationReplaceIdWithSynonym::
ReplacingUseWithSynonymIsOk(GetIRContext(), use_inst,
@@ -74,10 +71,24 @@ void FuzzerPassApplyIdSynonyms::Apply() {
continue;
}
// At present, we generate direct id synonyms (through
// OpCopyObject), which require no indices, and id synonyms that
// require a single index (through OpCompositeConstruct).
assert(synonym_to_try->index_size() <= 1);
// If an index is required, then we need to extract an element
// from a composite (e.g. through OpCompositeExtract), and this
// requires a fresh result id.
auto fresh_id_for_temporary =
synonym_to_try->index().empty()
? 0
: GetFuzzerContext()->GetFreshId();
TransformationReplaceIdWithSynonym replace_id_transformation(
transformation::MakeIdUseDescriptorFromUse(
GetIRContext(), use_inst, use_in_operand_index),
*synonym_to_try, 0);
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
use_in_operand_index),
*synonym_to_try, fresh_id_for_temporary);
// The transformation should be applicable by construction.
assert(replace_id_transformation.IsApplicable(GetIRContext(),
*GetFactManager()));

View File

@@ -0,0 +1,328 @@
// 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/fuzzer_pass_construct_composites.h"
#include <cmath>
#include <memory>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_construct_composite.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
FuzzerPassConstructComposites::FuzzerPassConstructComposites(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
void FuzzerPassConstructComposites::Apply() {
// Gather up the ids of all composite types.
std::vector<uint32_t> composite_type_ids;
for (auto& inst : GetIRContext()->types_values()) {
if (fuzzerutil::IsCompositeType(
GetIRContext()->get_type_mgr()->GetType(inst.result_id()))) {
composite_type_ids.push_back(inst.result_id());
}
}
MaybeAddTransformationBeforeEachInstruction(
[this, &composite_type_ids](const opt::Function& function,
opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
uint32_t base, uint32_t offset) -> uint32_t {
// Check whether it is legitimate to insert a composite construction
// before the instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
SpvOpCompositeConstruct, inst_it)) {
return 0;
}
// Randomly decide whether to try inserting an object copy here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfConstructingComposite())) {
return 0;
}
// For each instruction that is available at this program point (i.e. an
// instruction that is global or whose definition strictly dominates the
// program point) and suitable for making a synonym of, associate it
// with the id of its result type.
TypeIdToInstructions type_id_to_available_instructions;
for (auto instruction : FindAvailableInstructions(
function, block, inst_it, fuzzerutil::CanMakeSynonymOf)) {
RecordAvailableInstruction(instruction,
&type_id_to_available_instructions);
}
// At this point, |composite_type_ids| captures all the composite types
// we could try to create, while |type_id_to_available_instructions|
// captures all the available result ids we might use, organized by
// type.
// Now we try to find a composite that we can construct. We might not
// manage, if there is a paucity of available ingredients in the module
// (e.g. if our only available composite was a boolean vector and we had
// no instructions generating boolean result types available).
//
// If we succeed, |chosen_composite_type| will end up being non-zero,
// and |constructor_arguments| will end up giving us result ids suitable
// for constructing a composite of that type. Otherwise these variables
// will remain 0 and null respectively.
uint32_t chosen_composite_type = 0;
std::unique_ptr<std::vector<uint32_t>> constructor_arguments = nullptr;
// Initially, all composite type ids are available for us to try. Keep
// trying until we run out of options.
auto composites_to_try_constructing = composite_type_ids;
while (!composites_to_try_constructing.empty()) {
// Remove a composite type from the composite types left for us to
// try.
auto index =
GetFuzzerContext()->RandomIndex(composites_to_try_constructing);
auto next_composite_to_try_constructing =
composites_to_try_constructing[index];
composites_to_try_constructing.erase(
composites_to_try_constructing.begin() + index);
// Now try to construct a composite of this type, using an appropriate
// helper method depending on the kind of composite type.
auto composite_type = GetIRContext()->get_type_mgr()->GetType(
next_composite_to_try_constructing);
if (auto array_type = composite_type->AsArray()) {
constructor_arguments = TryConstructingArrayComposite(
*array_type, type_id_to_available_instructions);
} else if (auto matrix_type = composite_type->AsMatrix()) {
constructor_arguments = TryConstructingMatrixComposite(
*matrix_type, type_id_to_available_instructions);
} else if (auto struct_type = composite_type->AsStruct()) {
constructor_arguments = TryConstructingStructComposite(
*struct_type, type_id_to_available_instructions);
} else {
auto vector_type = composite_type->AsVector();
assert(vector_type &&
"The space of possible composite types should be covered by "
"the above cases.");
constructor_arguments = TryConstructingVectorComposite(
*vector_type, type_id_to_available_instructions);
}
if (constructor_arguments != nullptr) {
// We succeeded! Note the composite type we finally settled on, and
// exit from the loop.
chosen_composite_type = next_composite_to_try_constructing;
break;
}
}
if (!chosen_composite_type) {
// We did not manage to make a composite; return 0 to indicate that no
// instructions were added.
assert(constructor_arguments == nullptr);
return 0;
}
assert(constructor_arguments != nullptr);
// Make and apply a transformation.
TransformationConstructComposite transformation(
chosen_composite_type, *constructor_arguments, base, offset,
GetFuzzerContext()->GetFreshId());
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"This transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
// Indicate that one instruction was added.
return 1;
});
}
void FuzzerPassConstructComposites::RecordAvailableInstruction(
opt::Instruction* inst,
TypeIdToInstructions* type_id_to_available_instructions) {
if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
(*type_id_to_available_instructions)[inst->type_id()] = {};
}
type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
}
std::unique_ptr<std::vector<uint32_t>>
FuzzerPassConstructComposites::TryConstructingArrayComposite(
const opt::analysis::Array& array_type,
const TypeIdToInstructions& type_id_to_available_instructions) {
// TODO make these be true by construction
assert(array_type.length_info().words.size() == 2);
assert(array_type.length_info().words[0] ==
opt::analysis::Array::LengthInfo::kConstant);
auto result = MakeUnique<std::vector<uint32_t>>();
auto element_type_id =
GetIRContext()->get_type_mgr()->GetId(array_type.element_type());
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
if (available_instructions == type_id_to_available_instructions.cend()) {
// TODO comment infeasible
return nullptr;
}
for (uint32_t index = 0; index < array_type.length_info().words[1]; index++) {
result->push_back(available_instructions
->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]
->result_id());
}
return result;
}
std::unique_ptr<std::vector<uint32_t>>
FuzzerPassConstructComposites::TryConstructingMatrixComposite(
const opt::analysis::Matrix& matrix_type,
const TypeIdToInstructions& type_id_to_available_instructions) {
(void)(matrix_type);
(void)(type_id_to_available_instructions);
assert(false);
return nullptr;
}
std::unique_ptr<std::vector<uint32_t>>
FuzzerPassConstructComposites::TryConstructingStructComposite(
const opt::analysis::Struct& struct_type,
const TypeIdToInstructions& type_id_to_available_instructions) {
auto result = MakeUnique<std::vector<uint32_t>>();
for (auto element_type : struct_type.element_types()) {
auto element_type_id = GetIRContext()->get_type_mgr()->GetId(element_type);
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
if (available_instructions == type_id_to_available_instructions.cend()) {
// TODO comment infeasible
return nullptr;
}
result->push_back(available_instructions
->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]
->result_id());
}
return result;
}
std::unique_ptr<std::vector<uint32_t>>
FuzzerPassConstructComposites::TryConstructingVectorComposite(
const opt::analysis::Vector& vector_type,
const TypeIdToInstructions& type_id_to_available_instructions) {
// Get details of the type underlying the vector, and the width of the vector,
// for convenience.
auto element_type = vector_type.element_type();
auto element_count = vector_type.element_count();
// Collect a mapping, from type id to width, for scalar/vector types that are
// smaller in width than |vector_type|, but that have the same underlying
// type. For example, if |vector_type| is vec4, the mapping will be { float
// -> 1, vec2 -> 2, vec3 -> 3 }. The mapping will have missing entries if
// some of these types do not exist.
// TODO comment why we have the list as well.
std::vector<uint32_t> smaller_vector_type_ids;
std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
// Add the underlying type. This id must exist, in order for |vector_type| to
// exist.
auto scalar_type_id = GetIRContext()->get_type_mgr()->GetId(element_type);
smaller_vector_type_ids.push_back(scalar_type_id);
smaller_vector_type_id_to_width[scalar_type_id] = 1;
// Now add every vector type with width at least 2, and less than the width of
// |vector_type|.
for (uint32_t width = 2; width < element_count; width++) {
opt::analysis::Vector smaller_vector_type(vector_type.element_type(),
width);
auto smaller_vector_type_id =
GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
// TODO recap why it might be 0
if (smaller_vector_type_id) {
smaller_vector_type_ids.push_back(smaller_vector_type_id);
smaller_vector_type_id_to_width[smaller_vector_type_id] = width;
}
}
// Now we know the types that are available to us, we set about populating a
// vector of the right length. We do this by deciding, with no order in mind,
// which instructions we will use to populate the vector, and subsequently
// randomly choosing an order. This is to avoid biasing construction of
// vectors with smaller vectors to the left and scalars to the right. That is
// a concern because, e.g. in the case of populating a vec4, if we populate
// the constructor instructions left-to-right, we can always choose a vec3 to
// construct the first three elements, but can only choose a vec3 to construct
// the last three elements if we chose a float to construct the first element
// (otherwise there will not be space left for a vec3).
uint32_t vector_slots_used = 0;
// The instructions we will use to construct the vector, in no particular
// order at this stage.
std::vector<opt::Instruction*> instructions_to_use;
while (vector_slots_used < vector_type.element_count()) {
std::vector<opt::Instruction*> instructions_to_choose_from;
for (auto& entry : smaller_vector_type_id_to_width) {
if (entry.second >
std::min(vector_type.element_count() - 1,
vector_type.element_count() - vector_slots_used)) {
continue;
}
auto available_instructions =
type_id_to_available_instructions.find(entry.first);
if (available_instructions == type_id_to_available_instructions.cend()) {
continue;
}
instructions_to_choose_from.insert(instructions_to_choose_from.end(),
available_instructions->second.begin(),
available_instructions->second.end());
}
if (instructions_to_choose_from.empty()) {
// TODO comment - like fuzzed into a corner
return nullptr;
}
auto instruction_to_use =
instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
instructions_to_choose_from)];
instructions_to_use.push_back(instruction_to_use);
auto chosen_type =
GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
if (chosen_type->AsVector()) {
assert(chosen_type->AsVector()->element_type() == element_type);
assert(chosen_type->AsVector()->element_count() < element_count);
assert(chosen_type->AsVector()->element_count() <=
element_count - vector_slots_used);
vector_slots_used += chosen_type->AsVector()->element_count();
} else {
assert(chosen_type == element_type);
vector_slots_used += 1;
}
}
assert(vector_slots_used == vector_type.element_count());
auto result = MakeUnique<std::vector<uint32_t>>();
std::vector<uint32_t> operands;
while (!instructions_to_use.empty()) {
auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
result->push_back(instructions_to_use[index]->result_id());
instructions_to_use.erase(instructions_to_use.begin() + index);
}
assert(result->size() > 1);
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,79 @@
// 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_CONSTRUCT_COMPOSITES_H_
#define SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
#include "source/fuzz/fuzzer_pass.h"
#include <map>
#include <vector>
namespace spvtools {
namespace fuzz {
// A fuzzer pass for constructing composite objects from smaller objects.
class FuzzerPassConstructComposites : public FuzzerPass {
public:
FuzzerPassConstructComposites(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassConstructComposites();
void Apply() override;
private:
// Used to map a type id to relevant instructions whose result type matches
// the type id.
typedef std::map<uint32_t, std::vector<opt::Instruction*>>
TypeIdToInstructions;
// Considers all instructions that are available at |inst| - instructions
// whose results could be packed into a composite - and updates
// |type_id_to_available_instructions| so that each such instruction is
// associated with its the id of its result type.
void RecordAvailableInstruction(
opt::Instruction* inst,
TypeIdToInstructions* type_id_to_available_instructions);
// Attempts to find suitable instruction result ids from the values of
// |type_id_to_available_instructions| that would allow a composite of type
// |array_type| to be constructed. Returns said ids if they can be found.
// Returns |nullptr| otherwise.
std::unique_ptr<std::vector<uint32_t>> TryConstructingArrayComposite(
const opt::analysis::Array& array_type,
const TypeIdToInstructions& type_id_to_available_instructions);
// Similar to TryConstructingArrayComposite, but for matrices.
std::unique_ptr<std::vector<uint32_t>> TryConstructingMatrixComposite(
const opt::analysis::Matrix& matrix_type,
const TypeIdToInstructions& type_id_to_available_instructions);
// Similar to TryConstructingArrayComposite, but for structs.
std::unique_ptr<std::vector<uint32_t>> TryConstructingStructComposite(
const opt::analysis::Struct& struct_type,
const TypeIdToInstructions& type_id_to_available_instructions);
// Similar to TryConstructingArrayComposite, but for vectors.
std::unique_ptr<std::vector<uint32_t>> TryConstructingVectorComposite(
const opt::analysis::Vector& vector_type,
const TypeIdToInstructions& type_id_to_available_instructions);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_

View File

@@ -14,6 +14,7 @@
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_copy_object.h"
namespace spvtools {
@@ -28,120 +29,46 @@ FuzzerPassCopyObjects::FuzzerPassCopyObjects(
FuzzerPassCopyObjects::~FuzzerPassCopyObjects() = default;
void FuzzerPassCopyObjects::Apply() {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// We now consider every instruction in the block, randomly deciding
// whether to add an object copy before the instruction.
// In order to insert an object copy instruction, we need to be able to
// identify the instruction a copy should be inserted before. We do this
// by tracking a base instruction, which must generate a result id, and an
// offset (to allow us to identify instructions that do not generate
// result ids).
// The initial base instruction is the block label.
uint32_t base = block.id();
uint32_t offset = 0;
// Consider every instruction in the block.
for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
if (inst_it->HasResultId()) {
// In the case that the instruction has a result id, we use the
// instruction as its own base, with zero offset.
base = inst_it->result_id();
offset = 0;
} else {
// The instruction does not have a result id, so we need to identify
// it via the latest instruction that did have a result id (base), and
// an incremented offset.
offset++;
}
MaybeAddTransformationBeforeEachInstruction(
[this](const opt::Function& function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it, uint32_t base,
uint32_t offset) -> uint32_t {
// Check whether it is legitimate to insert a copy before this
// instruction.
if (!TransformationCopyObject::CanInsertCopyBefore(inst_it)) {
continue;
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyObject,
inst_it)) {
return 0;
}
// Randomly decide whether to try inserting an object copy here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfCopyingObject())) {
continue;
return 0;
}
// Populate list of potential instructions that can be copied.
// TODO(afd) The following is (relatively) simple, but may end up being
// prohibitively inefficient, as it walks the whole dominator tree for
// every copy that is added.
std::vector<opt::Instruction*> copyable_instructions;
std::vector<opt::Instruction*> relevant_instructions =
FindAvailableInstructions(function, block, inst_it,
fuzzerutil::CanMakeSynonymOf);
// Consider all global declarations
for (auto& global : GetIRContext()->module()->types_values()) {
if (TransformationCopyObject::IsCopyable(GetIRContext(), &global)) {
copyable_instructions.push_back(&global);
}
}
// Consider all previous instructions in this block
for (auto prev_inst_it = block.begin(); prev_inst_it != inst_it;
++prev_inst_it) {
if (TransformationCopyObject::IsCopyable(GetIRContext(),
&*prev_inst_it)) {
copyable_instructions.push_back(&*prev_inst_it);
}
}
// Walk the dominator tree to consider all instructions from dominating
// blocks
auto dominator_analysis =
GetIRContext()->GetDominatorAnalysis(&function);
for (auto next_dominator =
dominator_analysis->ImmediateDominator(&block);
next_dominator != nullptr;
next_dominator =
dominator_analysis->ImmediateDominator(next_dominator)) {
for (auto& dominating_inst : *next_dominator) {
if (TransformationCopyObject::IsCopyable(GetIRContext(),
&dominating_inst)) {
copyable_instructions.push_back(&dominating_inst);
}
}
}
// At this point, |copyable_instructions| contains all the instructions
// At this point, |relevant_instructions| contains all the instructions
// we might think of copying.
if (!copyable_instructions.empty()) {
// Choose a copyable instruction at random, and create and apply an
// object copying transformation based on it.
uint32_t index =
GetFuzzerContext()->RandomIndex(copyable_instructions);
TransformationCopyObject transformation(
copyable_instructions[index]->result_id(), base, offset,
GetFuzzerContext()->GetFreshId());
assert(
transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"This transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
if (!inst_it->HasResultId()) {
// We have inserted a new instruction before the current
// instruction, and we are tracking the current id-less instruction
// via an offset (offset) from a previous instruction (base) that
// has an id. We increment |offset| to reflect the newly-inserted
// instruction.
//
// This is slightly preferable to the alternative of setting |base|
// to be the result id of the new instruction, since on replay we
// might end up eliminating this copy but keeping a subsequent copy.
offset++;
}
if (relevant_instructions.empty()) {
return 0;
}
}
}
}
// Choose a copyable instruction at random, and create and apply an
// object copying transformation based on it.
uint32_t index = GetFuzzerContext()->RandomIndex(relevant_instructions);
TransformationCopyObject transformation(
relevant_instructions[index]->result_id(), base, offset,
GetFuzzerContext()->GetFreshId());
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"This transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
return 1;
});
}
} // namespace fuzz

View File

@@ -16,6 +16,7 @@
#include <cmath>
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/opt/ir_context.h"
@@ -102,10 +103,11 @@ void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
// We randomly decide, based on the current depth of obfuscation, whether
// to further obfuscate this operand.
if (GetFuzzerContext()->GoDeeperInConstantObfuscation(depth)) {
auto in_operand_use = transformation::MakeIdUseDescriptor(
auto in_operand_use = MakeIdUseDescriptor(
binary_operator_instruction->GetSingleWordInOperand(index),
binary_operator_instruction->opcode(), index,
binary_operator_instruction->result_id(), 0);
MakeInstructionDescriptor(binary_operator_instruction->result_id(),
binary_operator_instruction->opcode(), 0),
index);
ObfuscateConstant(depth + 1, in_operand_use);
}
}
@@ -366,14 +368,17 @@ void FuzzerPassObfuscateConstants::MaybeAddConstantIdUse(
// it.
protobufs::IdUseDescriptor id_use_descriptor;
id_use_descriptor.set_id_of_interest(operand_id);
id_use_descriptor.set_target_instruction_opcode(inst.opcode());
id_use_descriptor.mutable_enclosing_instruction()
->set_target_instruction_opcode(inst.opcode());
id_use_descriptor.mutable_enclosing_instruction()
->set_base_instruction_result_id(base_instruction_result_id);
id_use_descriptor.mutable_enclosing_instruction()
->set_num_opcodes_to_ignore(
skipped_opcode_count.find(inst.opcode()) ==
skipped_opcode_count.end()
? 0
: skipped_opcode_count.at(inst.opcode()));
id_use_descriptor.set_in_operand_index(in_operand_index);
id_use_descriptor.set_base_instruction_result_id(
base_instruction_result_id);
id_use_descriptor.set_num_opcodes_to_ignore(
skipped_opcode_count.find(inst.opcode()) == skipped_opcode_count.end()
? 0
: skipped_opcode_count.at(inst.opcode()));
constant_uses->push_back(id_use_descriptor);
} break;
default:

View File

@@ -216,6 +216,13 @@ bool NewEdgeRespectsUseDefDominance(opt::IRContext* context,
return true;
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2919): the
// solution below to determining whether a new edge respects dominance
// rules is incomplete. Test
// TransformationAddDeadContinueTest::DISABLED_Miscellaneous6 exposes the
// problem. In practice, this limitation does not bite too often, and the
// worst it does is leads to SPIR-V that spirv-val rejects.
// Let us assume that the module being manipulated is valid according to the
// rules of the SPIR-V language.
//
@@ -307,6 +314,47 @@ bool BlockIsReachableInItsFunction(opt::IRContext* context,
->Dominates(enclosing_function->entry().get(), bb);
}
bool CanInsertOpcodeBeforeInstruction(
SpvOp opcode, const opt::BasicBlock::iterator& instruction_in_block) {
if (instruction_in_block->PreviousNode() &&
(instruction_in_block->PreviousNode()->opcode() == SpvOpLoopMerge ||
instruction_in_block->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
// We cannot insert directly after a merge instruction.
return false;
}
if (opcode != SpvOpVariable &&
instruction_in_block->opcode() == SpvOpVariable) {
// We cannot insert a non-OpVariable instruction directly before a
// variable; variables in a function must be contiguous in the entry block.
return false;
}
// We cannot insert a non-OpPhi instruction directly before an OpPhi, because
// OpPhi instructions need to be contiguous at the start of a block.
return opcode == SpvOpPhi || instruction_in_block->opcode() != SpvOpPhi;
}
bool CanMakeSynonymOf(opt::IRContext* ir_context, opt::Instruction* inst) {
if (!inst->HasResultId()) {
// We can only make a synonym of an instruction that generates an id.
return false;
}
if (!inst->type_id()) {
// We can only make a synonym of an instruction that has a type.
return false;
}
// We do not make synonyms of objects that have decorations: if the synonym is
// not decorated analogously, using the original object vs. its synonymous
// form may not be equivalent.
return ir_context->get_decoration_mgr()
->GetDecorationsFor(inst->result_id(), true)
.empty();
}
bool IsCompositeType(const opt::analysis::Type* type) {
return type && (type->AsArray() || type->AsMatrix() || type->AsStruct() ||
type->AsVector());
}
} // namespace fuzzerutil
} // namespace fuzz

View File

@@ -87,6 +87,18 @@ bool NewEdgeRespectsUseDefDominance(opt::IRContext* context,
bool BlockIsReachableInItsFunction(opt::IRContext* context,
opt::BasicBlock* bb);
// Determines whether it is OK to insert an instruction with opcode |opcode|
// before |instruction_in_block|.
bool CanInsertOpcodeBeforeInstruction(
SpvOp opcode, const opt::BasicBlock::iterator& instruction_in_block);
// Determines whether it is OK to make a synonym of |inst|.
bool CanMakeSynonymOf(opt::IRContext* ir_context, opt::Instruction* inst);
// Determines whether the given type is a composite; that is: an array, matrix,
// struct or vector.
bool IsCompositeType(const opt::analysis::Type* type);
} // namespace fuzzerutil
} // namespace fuzz

View File

@@ -14,71 +14,41 @@
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
opt::Instruction* transformation::FindInstruction(
const protobufs::IdUseDescriptor& descriptor,
spvtools::opt::IRContext* context) {
for (auto& function : *context->module()) {
for (auto& block : function) {
bool found_base = block.id() == descriptor.base_instruction_result_id();
uint32_t num_ignored = 0;
for (auto& instruction : block) {
if (instruction.HasResultId() &&
instruction.result_id() ==
descriptor.base_instruction_result_id()) {
assert(!found_base &&
"It should not be possible to find the base instruction "
"multiple times.");
found_base = true;
assert(num_ignored == 0 &&
"The skipped instruction count should only be incremented "
"after the instruction base has been found.");
}
if (found_base &&
instruction.opcode() == descriptor.target_instruction_opcode()) {
if (num_ignored == descriptor.num_opcodes_to_ignore()) {
if (descriptor.in_operand_index() >= instruction.NumInOperands()) {
return nullptr;
}
auto in_operand =
instruction.GetInOperand(descriptor.in_operand_index());
if (in_operand.type != SPV_OPERAND_TYPE_ID) {
return nullptr;
}
if (in_operand.words[0] != descriptor.id_of_interest()) {
return nullptr;
}
return &instruction;
}
num_ignored++;
}
}
if (found_base) {
// We found the base instruction, but did not find the target
// instruction in the same block.
return nullptr;
}
}
opt::Instruction* FindInstructionContainingUse(
const protobufs::IdUseDescriptor& id_use_descriptor,
opt::IRContext* context) {
auto result =
FindInstruction(id_use_descriptor.enclosing_instruction(), context);
if (!result) {
return nullptr;
}
if (id_use_descriptor.in_operand_index() >= result->NumInOperands()) {
return nullptr;
}
if (result->GetSingleWordInOperand(id_use_descriptor.in_operand_index()) !=
id_use_descriptor.id_of_interest()) {
return nullptr;
}
return nullptr;
}
protobufs::IdUseDescriptor transformation::MakeIdUseDescriptor(
uint32_t id_of_interest, SpvOp target_instruction_opcode,
uint32_t in_operand_index, uint32_t base_instruction_result_id,
uint32_t num_opcodes_to_ignore) {
protobufs::IdUseDescriptor result;
result.set_id_of_interest(id_of_interest);
result.set_target_instruction_opcode(target_instruction_opcode);
result.set_in_operand_index(in_operand_index);
result.set_base_instruction_result_id(base_instruction_result_id);
result.set_num_opcodes_to_ignore(num_opcodes_to_ignore);
return result;
}
protobufs::IdUseDescriptor transformation::MakeIdUseDescriptorFromUse(
protobufs::IdUseDescriptor MakeIdUseDescriptor(
uint32_t id_of_interest,
const protobufs::InstructionDescriptor& enclosing_instruction,
uint32_t in_operand_index) {
protobufs::IdUseDescriptor result;
result.set_id_of_interest(id_of_interest);
*result.mutable_enclosing_instruction() = enclosing_instruction;
result.set_in_operand_index(in_operand_index);
return result;
}
protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse(
opt::IRContext* context, opt::Instruction* inst,
uint32_t in_operand_index) {
auto in_operand = inst->GetInOperand(in_operand_index);
@@ -94,9 +64,11 @@ protobufs::IdUseDescriptor transformation::MakeIdUseDescriptorFromUse(
num_opcodes_to_ignore = 0;
}
if (&inst_in_block == inst) {
return MakeIdUseDescriptor(id_of_interest, inst->opcode(),
in_operand_index, base_instruction_result_id,
num_opcodes_to_ignore);
return MakeIdUseDescriptor(
id_of_interest,
MakeInstructionDescriptor(base_instruction_result_id, inst->opcode(),
num_opcodes_to_ignore),
in_operand_index);
}
if (inst_in_block.opcode() == inst->opcode()) {
num_opcodes_to_ignore++;

View File

@@ -12,28 +12,28 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_ID_USE_LOCATOR_H_
#define SOURCE_FUZZ_ID_USE_LOCATOR_H_
#ifndef SOURCE_FUZZ_ID_USE_DESCRIPTOR_H_
#define SOURCE_FUZZ_ID_USE_DESCRIPTOR_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
namespace transformation {
// Looks for an instruction in |context| such that the id use represented by
// |descriptor| is one of the operands to said instruction. Returns |nullptr|
// if no such instruction can be found.
opt::Instruction* FindInstruction(const protobufs::IdUseDescriptor& descriptor,
opt::IRContext* context);
// Looks for an instruction in |context| that contains a use
// identified by |id_use_descriptor|.
// Returns |nullptr| if no such instruction can be found.
opt::Instruction* FindInstructionContainingUse(
const protobufs::IdUseDescriptor& id_use_descriptor,
opt::IRContext* context);
// Creates an IdUseDescriptor protobuf message from the given components.
// See the protobuf definition for details of what these components mean.
protobufs::IdUseDescriptor MakeIdUseDescriptor(
uint32_t id_of_interest, SpvOp target_instruction_opcode,
uint32_t in_operand_index, uint32_t base_instruction_result_id,
uint32_t num_opcodes_to_ignore);
uint32_t id_of_interest,
const protobufs::InstructionDescriptor& enclosing_instruction,
uint32_t in_operand_index);
// Given an id use, represented by the instruction |inst| that uses the id, and
// the input operand index |in_operand_index| associated with the usage, returns
@@ -41,8 +41,7 @@ protobufs::IdUseDescriptor MakeIdUseDescriptor(
protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse(
opt::IRContext* context, opt::Instruction* inst, uint32_t in_operand_index);
} // namespace transformation
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_ID_USE_LOCATOR_H_
#endif // SOURCE_FUZZ_ID_USE_DESCRIPTOR_H_

View File

@@ -0,0 +1,70 @@
// 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/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
opt::Instruction* FindInstruction(
const protobufs::InstructionDescriptor& instruction_descriptor,
spvtools::opt::IRContext* context) {
for (auto& function : *context->module()) {
for (auto& block : function) {
bool found_base =
block.id() == instruction_descriptor.base_instruction_result_id();
uint32_t num_ignored = 0;
for (auto& instruction : block) {
if (instruction.HasResultId() &&
instruction.result_id() ==
instruction_descriptor.base_instruction_result_id()) {
assert(!found_base &&
"It should not be possible to find the base instruction "
"multiple times.");
found_base = true;
assert(num_ignored == 0 &&
"The skipped instruction count should only be incremented "
"after the instruction base has been found.");
}
if (found_base &&
instruction.opcode() ==
instruction_descriptor.target_instruction_opcode()) {
if (num_ignored == instruction_descriptor.num_opcodes_to_ignore()) {
return &instruction;
}
num_ignored++;
}
}
if (found_base) {
// We found the base instruction, but did not find the target
// instruction in the same block.
return nullptr;
}
}
}
return nullptr;
}
protobufs::InstructionDescriptor MakeInstructionDescriptor(
uint32_t base_instruction_result_id, SpvOp target_instruction_opcode,
uint32_t num_opcodes_to_ignore) {
protobufs::InstructionDescriptor result;
result.set_base_instruction_result_id(base_instruction_result_id);
result.set_target_instruction_opcode(target_instruction_opcode);
result.set_num_opcodes_to_ignore(num_opcodes_to_ignore);
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,40 @@
// 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_INSTRUCTION_DESCRIPTOR_H_
#define SOURCE_FUZZ_INSTRUCTION_DESCRIPTOR_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
// Looks for an instruction in |context| corresponding to |descriptor|.
// Returns |nullptr| if no such instruction can be found.
opt::Instruction* FindInstruction(
const protobufs::InstructionDescriptor& instruction_descriptor,
opt::IRContext* context);
// Creates an InstructionDescriptor protobuf message from the given
// components. See the protobuf definition for details of what these
// components mean.
protobufs::InstructionDescriptor MakeInstructionDescriptor(
uint32_t base_instruction_result_id, SpvOp target_instruction_opcode,
uint32_t num_opcodes_to_ignore);
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_INSTRUCTION_DESCRIPTOR_H_

View File

@@ -21,17 +21,37 @@ syntax = "proto3";
package spvtools.fuzz.protobufs;
message InstructionDescriptor {
// Describes an instruction in some block of a function with respect to a
// base instruction.
// The id of an instruction after which the instruction being described is
// believed to be located. It might be the using instruction itself.
uint32 base_instruction_result_id = 1;
// The opcode for the instruction being described.
uint32 target_instruction_opcode = 2;
// The number of matching opcodes to skip over when searching from the base
// instruction to the instruction being described.
uint32 num_opcodes_to_ignore = 3;
}
message IdUseDescriptor {
// Describes a use of an id as an input operand to an instruction in some block
// of a function.
// Describes a use of an id as an input operand to an instruction in some
// block of a function.
// Example:
// - id_of_interest = 42
// - target_instruction_opcode = OpStore
// - enclosing_instruction = (
// base_instruction_result_id = 50,
// target_instruction_opcode = OpStore
// num_opcodes_to_ignore = 7
// )
// - in_operand_index = 1
// - base_instruction_result_id = 50
// - num_opcodes_to_ignore = 7
// represents a use of id 42 as input operand 1 to an OpStore instruction,
// such that the OpStore instruction can be found in the same basic block as
// the instruction with result id 50, and in particular is the 8th OpStore
@@ -41,20 +61,11 @@ message IdUseDescriptor {
// An id that we would like to be able to find a use of.
uint32 id_of_interest = 1;
// The opcode for the instruction that uses the id.
uint32 target_instruction_opcode = 2;
// The input operand index at which the use is expected.
InstructionDescriptor enclosing_instruction = 2;
uint32 in_operand_index = 3;
// The id of an instruction after which the instruction that contains the use
// is believed to occur; it might be the using instruction itself.
uint32 base_instruction_result_id = 4;
// The number of matching opcodes to skip over when searching for the using
// instruction from the base instruction.
uint32 num_opcodes_to_ignore = 5;
}
message DataDescriptor {
@@ -167,12 +178,18 @@ message Transformation {
TransformationAddTypeFloat add_type_float = 6;
TransformationAddTypeInt add_type_int = 7;
TransformationAddDeadBreak add_dead_break = 8;
TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9;
TransformationReplaceBooleanConstantWithConstantBinary
replace_boolean_constant_with_constant_binary = 9;
TransformationAddTypePointer add_type_pointer = 10;
TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
TransformationAddDeadContinue add_dead_continue = 12;
TransformationCopyObject copy_object = 13;
TransformationReplaceIdWithSynonym replace_id_with_synonym = 14;
TransformationSetSelectionControl set_selection_control = 15;
TransformationConstructComposite construct_composite = 16;
TransformationSetLoopControl set_loop_control = 17;
TransformationSetFunctionControl set_function_control = 18;
TransformationAddNoContractionDecoration add_no_contraction_decoration = 19;
// Add additional option using the next available number.
}
}
@@ -245,6 +262,15 @@ message TransformationAddDeadContinue {
}
message TransformationAddNoContractionDecoration {
// Applies OpDecorate NoContraction to the given result id
// Result id to be decorated
uint32 result_id = 1;
}
message TransformationAddTypeBoolean {
// Adds OpTypeBool to the module
@@ -317,6 +343,30 @@ message TransformationCopyObject {
}
message TransformationConstructComposite {
// A transformation that introduces an OpCompositeConstruct instruction to
// make a composite object.
// Id of the type of the composite that is to be constructed
uint32 composite_type_id = 1;
// Ids of the objects that will form the components of the composite
repeated uint32 component = 2;
// The id of an instruction in a block
uint32 base_instruction_id = 3;
// An offset, such that OpCompositeConstruct instruction should be inserted
// right before the instruction |offset| instructions after
// |base_instruction_id|
uint32 offset = 4;
// A fresh id for the composite object
uint32 fresh_id = 5;
}
message TransformationMoveBlockDown {
// A transformation that moves a basic block to be one position lower in
@@ -384,6 +434,59 @@ message TransformationReplaceIdWithSynonym {
uint32 fresh_id_for_temporary = 3;
}
message TransformationSetFunctionControl {
// A transformation that sets the function control operand of an OpFunction
// instruction.
// The result id of an OpFunction instruction
uint32 function_id = 1;
// The value to which the 'function control' operand should be set.
uint32 function_control = 2;
}
message TransformationSetLoopControl {
// A transformation that sets the loop control operand of an OpLoopMerge
// instruction.
// The id of a basic block that should contain OpLoopMerge
uint32 block_id = 1;
// The value to which the 'loop control' operand should be set.
// This must be a legal loop control mask.
uint32 loop_control = 2;
// Provides a peel count value for the loop. Used if and only if the
// PeelCount bit is set. Must be zero if the PeelCount bit is not set (can
// still be zero if this bit is set).
uint32 peel_count = 3;
// Provides a partial count value for the loop. Used if and only if the
// PartialCount bit is set. Must be zero if the PartialCount bit is not set
// (can still be zero if this bit is set).
uint32 partial_count = 4;
}
message TransformationSetSelectionControl {
// A transformation that sets the selection control operand of an
// OpSelectionMerge instruction.
// The id of a basic block that should contain OpSelectionMerge
uint32 block_id = 1;
// The value to which the 'selection control' operand should be set.
// Although technically 'selection control' is a literal mask that can be
// some combination of 'None', 'Flatten' and 'DontFlatten', the combination
// 'Flatten | DontFlatten' does not make sense and is not allowed here.
uint32 selection_control = 2;
}
message TransformationSplitBlock {
// A transformation that splits a basic block into two basic blocks

View File

@@ -16,21 +16,26 @@
#include <cassert>
#include "source/fuzz/transformation_add_constant_boolean.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_dead_break.h"
#include "source/fuzz/transformation_add_dead_continue.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
#include "source/fuzz/transformation_add_type_boolean.h"
#include "source/fuzz/transformation_add_type_float.h"
#include "source/fuzz/transformation_add_type_int.h"
#include "source/fuzz/transformation_add_type_pointer.h"
#include "source/fuzz/transformation_construct_composite.h"
#include "source/fuzz/transformation_copy_object.h"
#include "source/fuzz/transformation_move_block_down.h"
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
#include "source/fuzz/transformation_set_function_control.h"
#include "source/fuzz/transformation_set_loop_control.h"
#include "source/fuzz/transformation_set_selection_control.h"
#include "source/fuzz/transformation_split_block.h"
#include "source/util/make_unique.h"
#include "transformation_add_constant_boolean.h"
#include "transformation_add_constant_scalar.h"
#include "transformation_add_dead_break.h"
#include "transformation_add_dead_continue.h"
#include "transformation_add_type_boolean.h"
#include "transformation_add_type_float.h"
#include "transformation_add_type_int.h"
#include "transformation_add_type_pointer.h"
#include "transformation_copy_object.h"
#include "transformation_move_block_down.h"
#include "transformation_replace_boolean_constant_with_constant_binary.h"
#include "transformation_replace_constant_with_uniform.h"
#include "transformation_replace_id_with_synonym.h"
#include "transformation_split_block.h"
namespace spvtools {
namespace fuzz {
@@ -51,6 +56,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kAddDeadContinue:
return MakeUnique<TransformationAddDeadContinue>(
message.add_dead_continue());
case protobufs::Transformation::TransformationCase::
kAddNoContractionDecoration:
return MakeUnique<TransformationAddNoContractionDecoration>(
message.add_no_contraction_decoration());
case protobufs::Transformation::TransformationCase::kAddTypeBoolean:
return MakeUnique<TransformationAddTypeBoolean>(
message.add_type_boolean());
@@ -61,6 +70,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kAddTypePointer:
return MakeUnique<TransformationAddTypePointer>(
message.add_type_pointer());
case protobufs::Transformation::TransformationCase::kConstructComposite:
return MakeUnique<TransformationConstructComposite>(
message.construct_composite());
case protobufs::Transformation::TransformationCase::kCopyObject:
return MakeUnique<TransformationCopyObject>(message.copy_object());
case protobufs::Transformation::TransformationCase::kMoveBlockDown:
@@ -76,6 +88,15 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
return MakeUnique<TransformationReplaceIdWithSynonym>(
message.replace_id_with_synonym());
case protobufs::Transformation::TransformationCase::kSetFunctionControl:
return MakeUnique<TransformationSetFunctionControl>(
message.set_function_control());
case protobufs::Transformation::TransformationCase::kSetLoopControl:
return MakeUnique<TransformationSetLoopControl>(
message.set_loop_control());
case protobufs::Transformation::TransformationCase::kSetSelectionControl:
return MakeUnique<TransformationSetSelectionControl>(
message.set_selection_control());
case protobufs::Transformation::TransformationCase::kSplitBlock:
return MakeUnique<TransformationSplitBlock>(message.split_block());
case protobufs::Transformation::TRANSFORMATION_NOT_SET:

View File

@@ -0,0 +1,110 @@
// 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_no_contraction_decoration.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationAddNoContractionDecoration::
TransformationAddNoContractionDecoration(
const spvtools::fuzz::protobufs::
TransformationAddNoContractionDecoration& message)
: message_(message) {}
TransformationAddNoContractionDecoration::
TransformationAddNoContractionDecoration(uint32_t result_id) {
message_.set_result_id(result_id);
}
bool TransformationAddNoContractionDecoration::IsApplicable(
opt::IRContext* context,
const spvtools::fuzz::FactManager& /*unused*/) const {
// |message_.result_id| must be the id of an instruction.
auto instr = context->get_def_use_mgr()->GetDef(message_.result_id());
if (!instr) {
return false;
}
// The instruction must be arithmetic.
return IsArithmetic(instr->opcode());
}
void TransformationAddNoContractionDecoration::Apply(
opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
// Add a NoContraction decoration targeting |message_.result_id|.
context->get_decoration_mgr()->AddDecoration(message_.result_id(),
SpvDecorationNoContraction);
}
protobufs::Transformation TransformationAddNoContractionDecoration::ToMessage()
const {
protobufs::Transformation result;
*result.mutable_add_no_contraction_decoration() = message_;
return result;
}
bool TransformationAddNoContractionDecoration::IsArithmetic(uint32_t opcode) {
switch (opcode) {
case SpvOpSNegate:
case SpvOpFNegate:
case SpvOpIAdd:
case SpvOpFAdd:
case SpvOpISub:
case SpvOpFSub:
case SpvOpIMul:
case SpvOpFMul:
case SpvOpUDiv:
case SpvOpSDiv:
case SpvOpFDiv:
case SpvOpUMod:
case SpvOpSRem:
case SpvOpSMod:
case SpvOpFRem:
case SpvOpFMod:
case SpvOpVectorTimesScalar:
case SpvOpMatrixTimesScalar:
case SpvOpVectorTimesMatrix:
case SpvOpMatrixTimesVector:
case SpvOpMatrixTimesMatrix:
case SpvOpOuterProduct:
case SpvOpDot:
case SpvOpIAddCarry:
case SpvOpISubBorrow:
case SpvOpUMulExtended:
case SpvOpSMulExtended:
case SpvOpAny:
case SpvOpAll:
case SpvOpIsNan:
case SpvOpIsInf:
case SpvOpIsFinite:
case SpvOpIsNormal:
case SpvOpSignBitSet:
case SpvOpLessOrGreater:
case SpvOpOrdered:
case SpvOpUnordered:
case SpvOpLogicalEqual:
case SpvOpLogicalNotEqual:
case SpvOpLogicalOr:
case SpvOpLogicalAnd:
case SpvOpLogicalNot:
return true;
default:
return false;
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,58 @@
// 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_ADD_NO_CONTRACTION_DECORATION_H_
#define SOURCE_FUZZ_TRANSFORMATION_ADD_NO_CONTRACTION_DECORATION_H_
#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 TransformationAddNoContractionDecoration : public Transformation {
public:
explicit TransformationAddNoContractionDecoration(
const protobufs::TransformationAddNoContractionDecoration& message);
explicit TransformationAddNoContractionDecoration(uint32_t fresh_id);
// - |message_.result_id| must be the result id of an arithmetic instruction,
// as defined by the SPIR-V specification.
// - It does not matter whether this instruction is already annotated with the
// NoContraction decoration.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// Adds a decoration of the form:
// 'OpDecoration |message_.result_id| NoContraction'
// to the module.
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
// Returns true if and only if |opcode| is the opcode of an arithmetic
// instruction, as defined by the SPIR-V specification.
static bool IsArithmetic(uint32_t opcode);
private:
protobufs::TransformationAddNoContractionDecoration message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_NO_CONTRACTION_DECORATION_H_

View File

@@ -0,0 +1,310 @@
// 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_construct_composite.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationConstructComposite::TransformationConstructComposite(
const protobufs::TransformationConstructComposite& message)
: message_(message) {}
TransformationConstructComposite::TransformationConstructComposite(
uint32_t composite_type_id, std::vector<uint32_t> component,
uint32_t base_instruction_id, uint32_t offset, uint32_t fresh_id) {
message_.set_composite_type_id(composite_type_id);
for (auto a_component : component) {
message_.add_component(a_component);
}
message_.set_base_instruction_id(base_instruction_id);
message_.set_offset(offset);
message_.set_fresh_id(fresh_id);
}
bool TransformationConstructComposite::IsApplicable(
opt::IRContext* context, const FactManager& /*fact_manager*/) const {
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
// We require the id for the composite constructor to be unused.
return false;
}
auto base_instruction =
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
if (!base_instruction) {
// The given id to insert after is not defined.
return false;
}
auto destination_block = context->get_instr_block(base_instruction);
if (!destination_block) {
// The given id to insert after is not in a block.
return false;
}
auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
destination_block, base_instruction, message_.offset());
if (insert_before == destination_block->end()) {
// The offset was inappropriate.
return false;
}
auto composite_type =
context->get_type_mgr()->GetType(message_.composite_type_id());
if (!fuzzerutil::IsCompositeType(composite_type)) {
// The type must actually be a composite.
return false;
}
// If the type is an array, matrix, struct or vector, the components need to
// be suitable for constructing something of that type.
if (composite_type->AsArray() && !ComponentsForArrayConstructionAreOK(
context, *composite_type->AsArray())) {
return false;
}
if (composite_type->AsMatrix() && !ComponentsForMatrixConstructionAreOK(
context, *composite_type->AsMatrix())) {
return false;
}
if (composite_type->AsStruct() && !ComponentsForStructConstructionAreOK(
context, *composite_type->AsStruct())) {
return false;
}
if (composite_type->AsVector() && !ComponentsForVectorConstructionAreOK(
context, *composite_type->AsVector())) {
return false;
}
// Now check whether every component being used to initialize the composite is
// available at the desired program point.
for (auto& component : message_.component()) {
auto component_inst = context->get_def_use_mgr()->GetDef(component);
if (!context->get_instr_block(component)) {
// The component does not have a block; that means it is in global scope,
// which is OK. (Whether the component actually corresponds to an
// instruction is checked above when determining whether types are
// suitable.)
continue;
}
// Check whether the component is available.
if (insert_before->HasResultId() &&
insert_before->result_id() == component) {
// This constitutes trying to use an id right before it is defined. The
// special case is needed due to an instruction always dominating itself.
return false;
}
if (!context
->GetDominatorAnalysis(
context->get_instr_block(&*insert_before)->GetParent())
->Dominates(component_inst, &*insert_before)) {
// The instruction defining the component must dominate the instruction we
// wish to insert the composite before.
return false;
}
}
return true;
}
void TransformationConstructComposite::Apply(opt::IRContext* context,
FactManager* fact_manager) const {
// Use the base and offset information from the transformation to determine
// where in the module a new instruction should be inserted.
auto base_instruction =
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
auto destination_block = context->get_instr_block(base_instruction);
auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
destination_block, base_instruction, message_.offset());
// Prepare the input operands for an OpCompositeConstruct instruction.
opt::Instruction::OperandList in_operands;
for (auto& component_id : message_.component()) {
in_operands.push_back({SPV_OPERAND_TYPE_ID, {component_id}});
}
// Insert an OpCompositeConstruct instruction.
insert_before.InsertBefore(MakeUnique<opt::Instruction>(
context, SpvOpCompositeConstruct, message_.composite_type_id(),
message_.fresh_id(), in_operands));
// Inform the fact manager that we now have new synonyms: every component of
// the composite is synonymous with the id used to construct that component.
auto composite_type =
context->get_type_mgr()->GetType(message_.composite_type_id());
uint32_t index = 0;
for (auto component : message_.component()) {
protobufs::Fact fact;
fact.mutable_id_synonym_fact()->set_id(component);
fact.mutable_id_synonym_fact()->mutable_data_descriptor()->set_object(
message_.fresh_id());
fact.mutable_id_synonym_fact()->mutable_data_descriptor()->add_index(index);
fact_manager->AddFact(fact, context);
if (composite_type->AsVector()) {
// The vector case is a bit fiddly, because one argument to a vector
// constructor can cover more than one element.
auto component_type = context->get_type_mgr()->GetType(
context->get_def_use_mgr()->GetDef(component)->type_id());
if (component_type->AsVector()) {
assert(component_type->AsVector()->element_type() ==
composite_type->AsVector()->element_type());
index += component_type->AsVector()->element_count();
} else {
assert(component_type == composite_type->AsVector()->element_type());
index++;
}
} else {
// The non-vector cases are all easy: the constructor has exactly the same
// number of arguments as the number of sub-components, so we can just
// increment the index.
index++;
}
}
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
bool TransformationConstructComposite::ComponentsForArrayConstructionAreOK(
opt::IRContext* context, const opt::analysis::Array& array_type) const {
if (array_type.length_info().words[0] !=
opt::analysis::Array::LengthInfo::kConstant) {
// We only handle constant-sized arrays.
return false;
}
if (array_type.length_info().words.size() != 2) {
// We only handle the case where the array size can be captured in a single
// word.
return false;
}
// Get the array size.
auto array_size = array_type.length_info().words[1];
if (static_cast<uint32_t>(message_.component().size()) != array_size) {
// The number of components must match the array size.
return false;
}
// Check that each component is the result id of an instruction whose type is
// the array's element type.
for (auto component_id : message_.component()) {
auto inst = context->get_def_use_mgr()->GetDef(component_id);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type != array_type.element_type()) {
// The component's type does not match the array's element type.
return false;
}
}
return true;
}
bool TransformationConstructComposite::ComponentsForMatrixConstructionAreOK(
opt::IRContext* context, const opt::analysis::Matrix& matrix_type) const {
if (static_cast<uint32_t>(message_.component().size()) !=
matrix_type.element_count()) {
// The number of components must match the number of columns of the matrix.
return false;
}
// Check that each component is the result id of an instruction whose type is
// the matrix's column type.
for (auto component_id : message_.component()) {
auto inst = context->get_def_use_mgr()->GetDef(component_id);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type != matrix_type.element_type()) {
// The component's type does not match the matrix's column type.
return false;
}
}
return true;
}
bool TransformationConstructComposite::ComponentsForStructConstructionAreOK(
opt::IRContext* context, const opt::analysis::Struct& struct_type) const {
if (static_cast<uint32_t>(message_.component().size()) !=
struct_type.element_types().size()) {
// The number of components must match the number of fields of the struct.
return false;
}
// Check that each component is the result id of an instruction those type
// matches the associated field type.
for (uint32_t field_index = 0;
field_index < struct_type.element_types().size(); field_index++) {
auto inst =
context->get_def_use_mgr()->GetDef(message_.component()[field_index]);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type != struct_type.element_types()[field_index]) {
// The component's type does not match the corresponding field type.
return false;
}
}
return true;
}
bool TransformationConstructComposite::ComponentsForVectorConstructionAreOK(
opt::IRContext* context, const opt::analysis::Vector& vector_type) const {
uint32_t base_element_count = 0;
auto element_type = vector_type.element_type();
for (auto& component_id : message_.component()) {
auto inst = context->get_def_use_mgr()->GetDef(component_id);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type == element_type) {
base_element_count++;
} else if (component_type->AsVector() &&
component_type->AsVector()->element_type() == element_type) {
base_element_count += component_type->AsVector()->element_count();
} else {
// The component was not appropriate; e.g. no type corresponding to the
// given id was found, or the type that was found was not compatible
// with the vector being constructed.
return false;
}
}
// The number of components provided (when vector components are flattened
// out) needs to match the length of the vector being constructed.
return base_element_count == vector_type.element_count();
}
protobufs::Transformation TransformationConstructComposite::ToMessage() const {
protobufs::Transformation result;
*result.mutable_construct_composite() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,88 @@
// 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_CONSTRUCT_COMPOSITE_H_
#define SOURCE_FUZZ_TRANSFORMATION_CONSTRUCT_COMPOSITE_H_
#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 TransformationConstructComposite : public Transformation {
public:
explicit TransformationConstructComposite(
const protobufs::TransformationConstructComposite& message);
TransformationConstructComposite(uint32_t composite_type_id,
std::vector<uint32_t> component,
uint32_t base_instruction_id,
uint32_t offset, uint32_t fresh_id);
// - |message_.fresh_id| must not be used by the module.
// - |message_.composite_type_id| must be the id of a composite type
// - The elements of |message_.component| must be result ids that are
// suitable for constructing an element of the given composite type, in
// order
// - The elements of |message_.component| must not be the target of any
// decorations.
// - |message_.base_instruction_id| must be the result id of an instruction
// 'base' in some block 'blk'.
// - 'blk' must contain an instruction 'inst' located |message_.offset|
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
// 'base').
// - It must be legal to insert an OpCompositeConstruct instruction directly
// before 'inst'.
// - Each element of |message_.component| must be available directly before
// 'inst'.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// Inserts a new OpCompositeConstruct instruction, with id
// |message_.fresh_id|, directly before the instruction identified by
// |message_.base_instruction_id| and |message_.offset|. The instruction
// creates a composite of type |message_.composite_type_id| using the ids of
// |message_.component|.
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
private:
// Helper to decide whether the components of the transformation are suitable
// for constructing an array of the given type.
bool ComponentsForArrayConstructionAreOK(
opt::IRContext* context, const opt::analysis::Array& array_type) const;
// Similar, but for matrices.
bool ComponentsForMatrixConstructionAreOK(
opt::IRContext* context, const opt::analysis::Matrix& matrix_type) const;
// Similar, but for structs.
bool ComponentsForStructConstructionAreOK(
opt::IRContext* context, const opt::analysis::Struct& struct_type) const;
// Similar, but for vectors.
bool ComponentsForVectorConstructionAreOK(
opt::IRContext* context, const opt::analysis::Vector& vector_type) const;
protobufs::TransformationConstructComposite message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_CONSTRUCT_COMPOSITE_H_

View File

@@ -46,7 +46,7 @@ bool TransformationCopyObject::IsApplicable(
if (!object_inst) {
return false;
}
if (!IsCopyable(context, object_inst)) {
if (!fuzzerutil::CanMakeSynonymOf(context, object_inst)) {
return false;
}
@@ -71,7 +71,8 @@ bool TransformationCopyObject::IsApplicable(
return false;
}
if (!CanInsertCopyBefore(insert_before)) {
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyObject,
insert_before)) {
return false;
}
@@ -91,13 +92,6 @@ bool TransformationCopyObject::IsApplicable(
void TransformationCopyObject::Apply(opt::IRContext* context,
FactManager* fact_manager) const {
// - A new instruction,
// %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
// is added directly before the instruction at |message_.insert_after_id| +
// |message_|.offset, where %ty is the type of |message_.object|.
// - The fact that |message_.fresh_id| and |message_.object| are synonyms
// is added to the fact manager.
// The id of the object to be copied must exist
auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
assert(object_inst && "The object to be copied must exist.");
auto base_instruction =
@@ -132,43 +126,5 @@ protobufs::Transformation TransformationCopyObject::ToMessage() const {
return result;
}
bool TransformationCopyObject::IsCopyable(opt::IRContext* ir_context,
opt::Instruction* inst) {
if (!inst->HasResultId()) {
// We can only apply OpCopyObject to instructions that generate ids.
return false;
}
if (!inst->type_id()) {
// We can only apply OpCopyObject to instructions that have types.
return false;
}
// We do not copy objects that have decorations: if the copy is not
// decorated analogously, using the original object vs. its copy may not be
// equivalent.
// TODO(afd): it would be possible to make the copy but not add an id
// synonym.
return ir_context->get_decoration_mgr()
->GetDecorationsFor(inst->result_id(), true)
.empty();
}
bool TransformationCopyObject::CanInsertCopyBefore(
const opt::BasicBlock::iterator& instruction_in_block) {
if (instruction_in_block->PreviousNode() &&
(instruction_in_block->PreviousNode()->opcode() == SpvOpLoopMerge ||
instruction_in_block->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
// We cannot insert a copy directly after a merge instruction.
return false;
}
if (instruction_in_block->opcode() == SpvOpVariable) {
// We cannot insert a copy directly before a variable; variables in a
// function must be contiguous in the entry block.
return false;
}
// We cannot insert a copy directly before OpPhi, because OpPhi instructions
// need to be contiguous at the start of a block.
return instruction_in_block->opcode() != SpvOpPhi;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -37,14 +37,14 @@ class TransformationCopyObject : public Transformation {
// has a result type
// - |message_.object| must not be the target of any decoration.
// TODO(afd): consider copying decorations along with objects.
// - |message_.insert_after_id| must be the result id of an instruction
// - |message_.base_instruction_id| must be the result id of an instruction
// 'base' in some block 'blk'.
// - 'blk' must contain an instruction 'inst' located |message_.offset|
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
// 'base').
// - It must be legal to insert an OpCopyObject instruction directly
// before 'inst'.
// - |message_object| must be available directly before 'inst'.
// - |message_.object| must be available directly before 'inst'.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
@@ -58,14 +58,6 @@ class TransformationCopyObject : public Transformation {
protobufs::Transformation ToMessage() const override;
// Determines whether it is OK to make a copy of |inst|.
static bool IsCopyable(opt::IRContext* ir_context, opt::Instruction* inst);
// Determines whether it is OK to insert a copy instruction before the given
// instruction.
static bool CanInsertCopyBefore(
const opt::BasicBlock::iterator& instruction_in_block);
private:
protobufs::TransformationCopyObject message_;
};

View File

@@ -238,7 +238,7 @@ bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
// The id use descriptor must identify some instruction
auto instruction =
transformation::FindInstruction(message_.id_use_descriptor(), context);
FindInstructionContainingUse(message_.id_use_descriptor(), context);
if (instruction == nullptr) {
return false;
}
@@ -268,7 +268,7 @@ TransformationReplaceBooleanConstantWithConstantBinary::ApplyWithResult(
message_.fresh_id_for_binary_operation(), operands);
opt::Instruction* result = binary_instruction.get();
auto instruction_containing_constant_use =
transformation::FindInstruction(message_.id_use_descriptor(), context);
FindInstructionContainingUse(message_.id_use_descriptor(), context);
// We want to insert the new instruction before the instruction that contains
// the use of the boolean, but we need to go backwards one more instruction if

View File

@@ -149,7 +149,7 @@ bool TransformationReplaceConstantWithUniform::IsApplicable(
// The id use descriptor must identify some instruction with respect to the
// module.
auto instruction_using_constant =
transformation::FindInstruction(message_.id_use_descriptor(), context);
FindInstructionContainingUse(message_.id_use_descriptor(), context);
if (!instruction_using_constant) {
return false;
}
@@ -188,7 +188,7 @@ void TransformationReplaceConstantWithUniform::Apply(
spvtools::fuzz::FactManager* /*unused*/) const {
// Get the instruction that contains the id use we wish to replace.
auto instruction_containing_constant_use =
transformation::FindInstruction(message_.id_use_descriptor(), context);
FindInstructionContainingUse(message_.id_use_descriptor(), context);
assert(instruction_containing_constant_use &&
"Precondition requires that the id use can be found.");
assert(instruction_containing_constant_use->GetSingleWordInOperand(

View File

@@ -20,6 +20,7 @@
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/opt/types.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
@@ -33,9 +34,7 @@ TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
protobufs::IdUseDescriptor id_use_descriptor,
protobufs::DataDescriptor data_descriptor,
uint32_t fresh_id_for_temporary) {
assert(fresh_id_for_temporary == 0 && data_descriptor.index().size() == 0 &&
"At present we do not support making an id that is synonymous with an "
"index into a composite.");
assert((fresh_id_for_temporary == 0) == (data_descriptor.index().empty()));
*message_.mutable_id_use_descriptor() = std::move(id_use_descriptor);
*message_.mutable_data_descriptor() = std::move(data_descriptor);
message_.set_fresh_id_for_temporary(fresh_id_for_temporary);
@@ -61,12 +60,15 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
return false;
}
// Does the id use descriptor in the transformation identify an instruction?
auto use_instruction =
transformation::FindInstruction(message_.id_use_descriptor(), context);
FindInstructionContainingUse(message_.id_use_descriptor(), context);
if (!use_instruction) {
return false;
}
// Is it legitimate to replace the use identified by the id use descriptor
// with a synonym?
if (!ReplacingUseWithSynonymIsOk(
context, use_instruction,
message_.id_use_descriptor().in_operand_index(),
@@ -74,8 +76,23 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
return false;
}
assert(message_.fresh_id_for_temporary() == 0);
assert(message_.data_descriptor().index().empty());
if (message_.fresh_id_for_temporary() == 0) {
if (!message_.data_descriptor().index().empty()) {
// If we have no id to use as a temporary variable, we should not have any
// indices to extract from.
return false;
}
} else {
if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_temporary())) {
// The id to be used as a temporary needs to be fresh.
return false;
}
if (message_.data_descriptor().index_size() != 1) {
// At present we support just a single index to allow extracting directly
// from a composite.
return false;
}
}
return true;
}
@@ -83,12 +100,79 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
void TransformationReplaceIdWithSynonym::Apply(
spvtools::opt::IRContext* context,
spvtools::fuzz::FactManager* /*unused*/) const {
assert(message_.data_descriptor().index().empty());
auto instruction_to_change =
transformation::FindInstruction(message_.id_use_descriptor(), context);
FindInstructionContainingUse(message_.id_use_descriptor(), context);
// Ultimately we are going to replace the id use identified in the
// transformation with |replacement_id|, which will either be the synonym's
// id, or the id of a temporary used to extract the synonym from a composite.
uint32_t replacement_id;
if (message_.fresh_id_for_temporary()) {
// The transformation having a temporary variable means that we need to
// extract the synonym from a composite.
uint32_t type_id_of_id_to_be_replaced =
context->get_def_use_mgr()
->GetDef(message_.id_use_descriptor().id_of_interest())
->type_id();
opt::analysis::Type* type_of_id_to_be_replaced =
context->get_type_mgr()->GetType(type_id_of_id_to_be_replaced);
opt::analysis::Type* type_of_composite = context->get_type_mgr()->GetType(
context->get_def_use_mgr()
->GetDef(message_.data_descriptor().object())
->type_id());
// Intuitively we want to make an OpCompositeExtract instruction, to get the
// synonym out of the composite. But in the case of a vector, the synonym
// might involve multiple vector indices; e.g. the y and z components of a
// vec4 might be synonymous with a vec2, and in that case OpCompositeExtract
// doesn't give us what we want; we need to use OpVectorShuffle instead.
std::unique_ptr<opt::Instruction> new_instruction;
if (type_of_composite->AsVector() &&
type_of_composite->AsVector()->element_type() !=
type_of_id_to_be_replaced) {
// We need to extract a vector from inside a vector, so we will need to
// use OpVectorShuffle.
assert(type_of_id_to_be_replaced->AsVector());
assert(type_of_id_to_be_replaced->AsVector()->element_type() ==
type_of_composite->AsVector()->element_type());
opt::Instruction::OperandList shuffle_operands = {
{SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}},
{SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}}};
for (uint32_t i = 0;
i < type_of_id_to_be_replaced->AsVector()->element_count(); i++) {
shuffle_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER,
{message_.data_descriptor().index(0) + i}});
}
new_instruction = MakeUnique<opt::Instruction>(
context, SpvOpVectorShuffle, type_id_of_id_to_be_replaced,
message_.fresh_id_for_temporary(), shuffle_operands);
} else {
// We are either extracting from a non-vector, or extracting a scalar from
// a vector, so we can use OpCompositeExtract.
opt::Instruction::OperandList extract_operands = {
{SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER,
{message_.data_descriptor().index(0)}}};
new_instruction = MakeUnique<opt::Instruction>(
context, SpvOpCompositeExtract, type_id_of_id_to_be_replaced,
message_.fresh_id_for_temporary(), extract_operands);
}
instruction_to_change->InsertBefore(std::move(new_instruction));
// The replacement id is the temporary variable we used to extract the
// synonym from a composite.
replacement_id = message_.fresh_id_for_temporary();
fuzzerutil::UpdateModuleIdBound(context, replacement_id);
} else {
// The replacement id is the synonym's id.
replacement_id = message_.data_descriptor().object();
}
instruction_to_change->SetInOperand(
message_.id_use_descriptor().in_operand_index(),
{message_.data_descriptor().object()});
message_.id_use_descriptor().in_operand_index(), {replacement_id});
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
}

View File

@@ -0,0 +1,100 @@
// 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_set_function_control.h"
namespace spvtools {
namespace fuzz {
TransformationSetFunctionControl::TransformationSetFunctionControl(
const spvtools::fuzz::protobufs::TransformationSetFunctionControl& message)
: message_(message) {}
TransformationSetFunctionControl::TransformationSetFunctionControl(
uint32_t function_id, uint32_t function_control) {
message_.set_function_id(function_id);
message_.set_function_control(function_control);
}
bool TransformationSetFunctionControl::IsApplicable(
opt::IRContext* context, const FactManager& /*unused*/) const {
opt::Instruction* function_def_instruction =
FindFunctionDefInstruction(context);
if (!function_def_instruction) {
// The given function id does not correspond to any function.
return false;
}
uint32_t existing_function_control_mask =
function_def_instruction->GetSingleWordInOperand(0);
// Check (via an assertion) that function control mask doesn't have any bad
// bits set.
uint32_t acceptable_function_control_bits =
SpvFunctionControlInlineMask | SpvFunctionControlDontInlineMask |
SpvFunctionControlPureMask | SpvFunctionControlConstMask;
// The following is to keep release-mode compilers happy as this variable is
// only used in an assertion.
(void)(acceptable_function_control_bits);
assert(!(message_.function_control() & ~acceptable_function_control_bits) &&
"Nonsensical loop control bits were found.");
// Check (via an assertion) that function control mask does not have both
// Inline and DontInline bits set.
assert(!((message_.function_control() & SpvFunctionControlInlineMask) &&
(message_.function_control() & SpvFunctionControlDontInlineMask)) &&
"It is not OK to set both the 'Inline' and 'DontInline' bits of a "
"function control mask");
// Check that Const and Pure are only present if they were present on the
// original function
for (auto mask_bit :
{SpvFunctionControlPureMask, SpvFunctionControlConstMask}) {
if ((message_.function_control() & mask_bit) &&
!(existing_function_control_mask & mask_bit)) {
return false;
}
}
return true;
}
void TransformationSetFunctionControl::Apply(opt::IRContext* context,
FactManager* /*unused*/) const {
opt::Instruction* function_def_instruction =
FindFunctionDefInstruction(context);
function_def_instruction->SetInOperand(0, {message_.function_control()});
}
protobufs::Transformation TransformationSetFunctionControl::ToMessage() const {
protobufs::Transformation result;
*result.mutable_set_function_control() = message_;
return result;
}
opt::Instruction* TransformationSetFunctionControl ::FindFunctionDefInstruction(
opt::IRContext* context) const {
// Look through all functions for a function whose defining instruction's
// result id matches |message_.function_id|, returning the defining
// instruction if found.
for (auto& function : *context->module()) {
if (function.DefInst().result_id() == message_.function_id()) {
return &function.DefInst();
}
}
// A nullptr result indicates that no match was found.
return nullptr;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,58 @@
// 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_SET_FUNCTION_CONTROL_H_
#define SOURCE_FUZZ_TRANSFORMATION_SET_FUNCTION_CONTROL_H_
#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 TransformationSetFunctionControl : public Transformation {
public:
explicit TransformationSetFunctionControl(
const protobufs::TransformationSetFunctionControl& message);
TransformationSetFunctionControl(uint32_t function_id,
uint32_t function_control);
// - |message_.function_id| must be the result id of an OpFunction
// instruction.
// - |message_.function_control| must be a function control mask that sets
// at most one of 'Inline' or 'DontInline', and that may not contain 'Pure'
// (respectively 'Const') unless the existing function control mask contains
// 'Pure' (respectively 'Const').
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// The function control operand of instruction |message_.function_id| is
// over-written with |message_.function_control|.
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
private:
opt::Instruction* FindFunctionDefInstruction(opt::IRContext* context) const;
protobufs::TransformationSetFunctionControl message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_SET_FUNCTION_CONTROL_H_

View File

@@ -0,0 +1,216 @@
// 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_set_loop_control.h"
namespace spvtools {
namespace fuzz {
TransformationSetLoopControl::TransformationSetLoopControl(
const spvtools::fuzz::protobufs::TransformationSetLoopControl& message)
: message_(message) {}
TransformationSetLoopControl::TransformationSetLoopControl(
uint32_t block_id, uint32_t loop_control, uint32_t peel_count,
uint32_t partial_count) {
message_.set_block_id(block_id);
message_.set_loop_control(loop_control);
message_.set_peel_count(peel_count);
message_.set_partial_count(partial_count);
}
bool TransformationSetLoopControl::IsApplicable(
opt::IRContext* context, const FactManager& /*unused*/) const {
// |message_.block_id| must identify a block that ends with OpLoopMerge.
auto block = context->get_instr_block(message_.block_id());
if (!block) {
return false;
}
auto merge_inst = block->GetMergeInst();
if (!merge_inst || merge_inst->opcode() != SpvOpLoopMerge) {
return false;
}
// We sanity-check that the transformation does not try to set any meaningless
// bits of the loop control mask.
uint32_t all_loop_control_mask_bits_set =
SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask |
SpvLoopControlDependencyInfiniteMask |
SpvLoopControlDependencyLengthMask | SpvLoopControlMinIterationsMask |
SpvLoopControlMaxIterationsMask | SpvLoopControlIterationMultipleMask |
SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask;
// The variable is only used in an assertion; the following keeps release-mode
// compilers happy.
(void)(all_loop_control_mask_bits_set);
// No additional bits should be set.
assert(!(message_.loop_control() & ~all_loop_control_mask_bits_set));
// Grab the loop control mask currently associated with the OpLoopMerge
// instruction.
auto existing_loop_control_mask =
merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
// Check that there is no attempt to set one of the loop controls that
// requires guarantees to hold.
for (SpvLoopControlMask mask :
{SpvLoopControlDependencyInfiniteMask,
SpvLoopControlDependencyLengthMask, SpvLoopControlMinIterationsMask,
SpvLoopControlMaxIterationsMask, SpvLoopControlIterationMultipleMask}) {
// We have a problem if this loop control bit was not set in the original
// loop control mask but is set by the transformation.
if (LoopControlBitIsAddedByTransformation(mask,
existing_loop_control_mask)) {
return false;
}
}
if ((message_.loop_control() &
(SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)) &&
!(PeelCountIsSupported(context) && PartialCountIsSupported(context))) {
// At least one of PeelCount or PartialCount is used, but the SPIR-V version
// in question does not support these loop controls.
return false;
}
if (message_.peel_count() > 0 &&
!(message_.loop_control() & SpvLoopControlPeelCountMask)) {
// Peel count provided, but peel count mask bit not set.
return false;
}
if (message_.partial_count() > 0 &&
!(message_.loop_control() & SpvLoopControlPartialCountMask)) {
// Partial count provided, but partial count mask bit not set.
return false;
}
// We must not set both 'don't unroll' and one of 'peel count' or 'partial
// count'.
return !((message_.loop_control() & SpvLoopControlDontUnrollMask) &&
(message_.loop_control() &
(SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)));
}
void TransformationSetLoopControl::Apply(opt::IRContext* context,
FactManager* /*unused*/) const {
// Grab the loop merge instruction and its associated loop control mask.
auto merge_inst =
context->get_instr_block(message_.block_id())->GetMergeInst();
auto existing_loop_control_mask =
merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
// We are going to replace the OpLoopMerge's operands with this list.
opt::Instruction::OperandList new_operands;
// We add the existing merge block and continue target ids.
new_operands.push_back(merge_inst->GetInOperand(0));
new_operands.push_back(merge_inst->GetInOperand(1));
// We use the loop control mask from the transformation.
new_operands.push_back(
{SPV_OPERAND_TYPE_LOOP_CONTROL, {message_.loop_control()}});
// It remains to determine what literals to provide, in association with
// the new loop control mask.
//
// For the loop controls that require guarantees to hold about the number
// of loop iterations, we need to keep, from the original OpLoopMerge, any
// literals associated with loop control bits that are still set.
uint32_t literal_index = 0; // Indexes into the literals from the original
// instruction.
for (SpvLoopControlMask mask :
{SpvLoopControlDependencyLengthMask, SpvLoopControlMinIterationsMask,
SpvLoopControlMaxIterationsMask, SpvLoopControlIterationMultipleMask}) {
// Check whether the bit was set in the original loop control mask.
if (existing_loop_control_mask & mask) {
// Check whether the bit is set in the new loop control mask.
if (message_.loop_control() & mask) {
// Add the associated literal to our sequence of replacement operands.
new_operands.push_back(
{SPV_OPERAND_TYPE_LITERAL_INTEGER,
{merge_inst->GetSingleWordInOperand(
kLoopControlFirstLiteralInOperandIndex + literal_index)}});
}
// Increment our index into the original loop control mask's literals,
// whether or not the bit was set in the new mask.
literal_index++;
}
}
// If PeelCount is set in the new mask, |message_.peel_count| provides the
// associated peel count.
if (message_.loop_control() & SpvLoopControlPeelCountMask) {
new_operands.push_back(
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.peel_count()}});
}
// Similar, but for PartialCount.
if (message_.loop_control() & SpvLoopControlPartialCountMask) {
new_operands.push_back(
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.partial_count()}});
}
// Replace the input operands of the OpLoopMerge with the new operands we have
// accumulated.
merge_inst->SetInOperands(std::move(new_operands));
}
protobufs::Transformation TransformationSetLoopControl::ToMessage() const {
protobufs::Transformation result;
*result.mutable_set_loop_control() = message_;
return result;
}
bool TransformationSetLoopControl::LoopControlBitIsAddedByTransformation(
SpvLoopControlMask loop_control_single_bit_mask,
uint32_t existing_loop_control_mask) const {
return !(loop_control_single_bit_mask & existing_loop_control_mask) &&
(loop_control_single_bit_mask & message_.loop_control());
}
bool TransformationSetLoopControl::PartialCountIsSupported(
opt::IRContext* context) {
// TODO(afd): We capture the universal environments for which this loop
// control is definitely not supported. The check should be refined on
// demand for other target environments.
switch (context->grammar().target_env()) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_UNIVERSAL_1_1:
case SPV_ENV_UNIVERSAL_1_2:
case SPV_ENV_UNIVERSAL_1_3:
return false;
default:
return true;
}
}
bool TransformationSetLoopControl::PeelCountIsSupported(
opt::IRContext* context) {
// TODO(afd): We capture the universal environments for which this loop
// control is definitely not supported. The check should be refined on
// demand for other target environments.
switch (context->grammar().target_env()) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_UNIVERSAL_1_1:
case SPV_ENV_UNIVERSAL_1_2:
case SPV_ENV_UNIVERSAL_1_3:
return false;
default:
return true;
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,79 @@
// 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_SET_LOOP_CONTROL_H_
#define SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_
#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 TransformationSetLoopControl : public Transformation {
public:
const static uint32_t kLoopControlMaskInOperandIndex = 2;
const static uint32_t kLoopControlFirstLiteralInOperandIndex = 3;
explicit TransformationSetLoopControl(
const protobufs::TransformationSetLoopControl& message);
TransformationSetLoopControl(uint32_t block_id, uint32_t loop_control,
uint32_t peel_count, uint32_t partial_count);
// - |message_.block_id| must be a block containing an OpLoopMerge
// instruction.
// - |message_.loop_control| must be a legal loop control mask that
// only uses controls available in the SPIR-V version associated with
// |context|, and must not add loop controls that are only valid in the
// presence of guarantees about what the loop does (e.g. MinIterations).
// - |message_.peel_count| (respectively |message_.partial_count|) must be
// zero PeelCount (respectively PartialCount) is set in
// |message_.loop_control|.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// - The loop control operand of the OpLoopMergeInstruction in
// |message_.block_id| is overwritten with |message_.loop_control|.
// - The literals associated with the loop control are updated to reflect any
// controls with associated literals that have been removed (e.g.
// MinIterations), and any that have been added (PeelCount and/or
// PartialCount).
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
// Does the version of SPIR-V being used support the PartialCount loop
// control?
static bool PartialCountIsSupported(opt::IRContext* context);
// Does the version of SPIR-V being used support the PeelCount loop control?
static bool PeelCountIsSupported(opt::IRContext* context);
private:
// Returns true if and only if |loop_single_bit_mask| is *not* set in
// |existing_loop_control| but *is* set in |message_.loop_control|.
bool LoopControlBitIsAddedByTransformation(
SpvLoopControlMask loop_control_single_bit_mask,
uint32_t existing_loop_control_mask) const;
protobufs::TransformationSetLoopControl message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_

View File

@@ -0,0 +1,60 @@
// 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_set_selection_control.h"
namespace spvtools {
namespace fuzz {
TransformationSetSelectionControl::TransformationSetSelectionControl(
const spvtools::fuzz::protobufs::TransformationSetSelectionControl& message)
: message_(message) {}
TransformationSetSelectionControl::TransformationSetSelectionControl(
uint32_t block_id, uint32_t selection_control) {
message_.set_block_id(block_id);
message_.set_selection_control(selection_control);
}
bool TransformationSetSelectionControl::IsApplicable(
opt::IRContext* context, const FactManager& /*unused*/) const {
assert((message_.selection_control() == SpvSelectionControlMaskNone ||
message_.selection_control() == SpvSelectionControlFlattenMask ||
message_.selection_control() == SpvSelectionControlDontFlattenMask) &&
"Selection control should never be set to something other than "
"'None', 'Flatten' or 'DontFlatten'");
if (auto block = context->get_instr_block(message_.block_id())) {
if (auto merge_inst = block->GetMergeInst()) {
return merge_inst->opcode() == SpvOpSelectionMerge;
}
}
// Either the block did not exit, or did not end with OpSelectionMerge.
return false;
}
void TransformationSetSelectionControl::Apply(opt::IRContext* context,
FactManager* /*unused*/) const {
context->get_instr_block(message_.block_id())
->GetMergeInst()
->SetInOperand(1, {message_.selection_control()});
}
protobufs::Transformation TransformationSetSelectionControl::ToMessage() const {
protobufs::Transformation result;
*result.mutable_set_selection_control() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,54 @@
// 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_SET_SELECTION_CONTROL_H_
#define SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_
#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 TransformationSetSelectionControl : public Transformation {
public:
explicit TransformationSetSelectionControl(
const protobufs::TransformationSetSelectionControl& message);
TransformationSetSelectionControl(uint32_t block_id,
uint32_t selection_control);
// - |message_.block_id| must be a block containing an OpSelectionMerge
// instruction.
// - |message_.selection_control| must be one of None, Flatten or
// DontFlatten.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// - The selection control operand of the OpSelectionMergeInstruction in
// |message_.block_id| is overwritten with |message_.selection_control|.
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationSetSelectionControl message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_

View File

@@ -94,6 +94,14 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
if (analyses_to_invalidate & kAnalysisTypes) {
analyses_to_invalidate |= kAnalysisConstants;
}
// The dominator analysis hold the psuedo entry and exit nodes from the CFG.
// Also if the CFG change the dominators many changed as well, so the
// dominator analysis should be invalidated as well.
if (analyses_to_invalidate & kAnalysisCFG) {
analyses_to_invalidate |= kAnalysisDominatorAnalysis;
}
if (analyses_to_invalidate & kAnalysisDefUse) {
def_use_mgr_.reset(nullptr);
}

View File

@@ -409,6 +409,22 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) {
{static_cast<uint32_t>(
type->AsForwardPointer()->storage_class())}}});
break;
case Type::kCooperativeMatrixNV: {
auto coop_mat = type->AsCooperativeMatrixNV();
uint32_t const component_type =
GetTypeInstruction(coop_mat->component_type());
if (component_type == 0) {
return 0;
}
typeInst = MakeUnique<Instruction>(
context(), SpvOpTypeCooperativeMatrixNV, 0, id,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_ID, {component_type}},
{SPV_OPERAND_TYPE_SCOPE_ID, {coop_mat->scope_id()}},
{SPV_OPERAND_TYPE_ID, {coop_mat->rows_id()}},
{SPV_OPERAND_TYPE_ID, {coop_mat->columns_id()}}});
break;
}
default:
assert(false && "Unexpected type");
break;
@@ -604,6 +620,14 @@ Type* TypeManager::RebuildType(const Type& type) {
}
break;
}
case Type::kCooperativeMatrixNV: {
const CooperativeMatrixNV* cm_type = type.AsCooperativeMatrixNV();
const Type* component_type = cm_type->component_type();
rebuilt_ty = MakeUnique<CooperativeMatrixNV>(
RebuildType(*component_type), cm_type->scope_id(), cm_type->rows_id(),
cm_type->columns_id());
break;
}
default:
assert(false && "Unhandled type");
return nullptr;
@@ -832,6 +856,12 @@ Type* TypeManager::RecordIfTypeDefinition(const Instruction& inst) {
case SpvOpTypeAccelerationStructureNV:
type = new AccelerationStructureNV();
break;
case SpvOpTypeCooperativeMatrixNV:
type = new CooperativeMatrixNV(GetType(inst.GetSingleWordInOperand(0)),
inst.GetSingleWordInOperand(1),
inst.GetSingleWordInOperand(2),
inst.GetSingleWordInOperand(3));
break;
default:
SPIRV_UNIMPLEMENTED(consumer_, "unhandled type");
break;

View File

@@ -127,6 +127,7 @@ std::unique_ptr<Type> Type::Clone() const {
DeclareKindCase(PipeStorage);
DeclareKindCase(NamedBarrier);
DeclareKindCase(AccelerationStructureNV);
DeclareKindCase(CooperativeMatrixNV);
#undef DeclareKindCase
default:
assert(false && "Unhandled type");
@@ -171,6 +172,7 @@ bool Type::operator==(const Type& other) const {
DeclareKindCase(PipeStorage);
DeclareKindCase(NamedBarrier);
DeclareKindCase(AccelerationStructureNV);
DeclareKindCase(CooperativeMatrixNV);
#undef DeclareKindCase
default:
assert(false && "Unhandled type");
@@ -220,6 +222,7 @@ void Type::GetHashWords(std::vector<uint32_t>* words,
DeclareKindCase(PipeStorage);
DeclareKindCase(NamedBarrier);
DeclareKindCase(AccelerationStructureNV);
DeclareKindCase(CooperativeMatrixNV);
#undef DeclareKindCase
default:
assert(false && "Unhandled type");
@@ -654,6 +657,44 @@ void ForwardPointer::GetExtraHashWords(
if (pointer_) pointer_->GetHashWords(words, seen);
}
CooperativeMatrixNV::CooperativeMatrixNV(const Type* type, const uint32_t scope,
const uint32_t rows,
const uint32_t columns)
: Type(kCooperativeMatrixNV),
component_type_(type),
scope_id_(scope),
rows_id_(rows),
columns_id_(columns) {
assert(type != nullptr);
assert(scope != 0);
assert(rows != 0);
assert(columns != 0);
}
std::string CooperativeMatrixNV::str() const {
std::ostringstream oss;
oss << "<" << component_type_->str() << ", " << scope_id_ << ", " << rows_id_
<< ", " << columns_id_ << ">";
return oss.str();
}
void CooperativeMatrixNV::GetExtraHashWords(
std::vector<uint32_t>* words, std::unordered_set<const Type*>* pSet) const {
component_type_->GetHashWords(words, pSet);
words->push_back(scope_id_);
words->push_back(rows_id_);
words->push_back(columns_id_);
}
bool CooperativeMatrixNV::IsSameImpl(const Type* that,
IsSameCache* seen) const {
const CooperativeMatrixNV* mt = that->AsCooperativeMatrixNV();
if (!mt) return false;
return component_type_->IsSameImpl(mt->component_type_, seen) &&
scope_id_ == mt->scope_id_ && rows_id_ == mt->rows_id_ &&
columns_id_ == mt->columns_id_ && HasSameDecorations(that);
}
} // namespace analysis
} // namespace opt
} // namespace spvtools

View File

@@ -58,6 +58,7 @@ class ForwardPointer;
class PipeStorage;
class NamedBarrier;
class AccelerationStructureNV;
class CooperativeMatrixNV;
// Abstract class for a SPIR-V type. It has a bunch of As<sublcass>() methods,
// which is used as a way to probe the actual <subclass>.
@@ -93,6 +94,7 @@ class Type {
kPipeStorage,
kNamedBarrier,
kAccelerationStructureNV,
kCooperativeMatrixNV
};
Type(Kind k) : kind_(k) {}
@@ -196,6 +198,7 @@ class Type {
DeclareCastMethod(PipeStorage)
DeclareCastMethod(NamedBarrier)
DeclareCastMethod(AccelerationStructureNV)
DeclareCastMethod(CooperativeMatrixNV)
#undef DeclareCastMethod
protected:
@@ -597,6 +600,36 @@ class ForwardPointer : public Type {
const Pointer* pointer_;
};
class CooperativeMatrixNV : public Type {
public:
CooperativeMatrixNV(const Type* type, const uint32_t scope,
const uint32_t rows, const uint32_t columns);
CooperativeMatrixNV(const CooperativeMatrixNV&) = default;
std::string str() const override;
CooperativeMatrixNV* AsCooperativeMatrixNV() override { return this; }
const CooperativeMatrixNV* AsCooperativeMatrixNV() const override {
return this;
}
void GetExtraHashWords(std::vector<uint32_t>*,
std::unordered_set<const Type*>*) const override;
const Type* component_type() const { return component_type_; }
uint32_t scope_id() const { return scope_id_; }
uint32_t rows_id() const { return rows_id_; }
uint32_t columns_id() const { return columns_id_; }
private:
bool IsSameImpl(const Type* that, IsSameCache*) const override;
const Type* component_type_;
const uint32_t scope_id_;
const uint32_t rows_id_;
const uint32_t columns_id_;
};
#define DefineParameterlessType(type, name) \
class type : public Type { \
public: \

View File

@@ -15,7 +15,7 @@
#include "source/print.h"
#if defined(SPIRV_ANDROID) || defined(SPIRV_LINUX) || defined(SPIRV_MAC) || \
defined(SPIRV_FREEBSD)
defined(SPIRV_FREEBSD) || defined(SPIRV_EMSCRIPTEN)
namespace spvtools {
clr::reset::operator const char*() { return "\x1b[0m"; }

View File

@@ -29,8 +29,6 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
remove_instruction_reduction_opportunity.h
remove_function_reduction_opportunity.h
remove_function_reduction_opportunity_finder.h
remove_opname_instruction_reduction_opportunity_finder.h
remove_relaxed_precision_decoration_opportunity_finder.h
remove_selection_reduction_opportunity.h
remove_selection_reduction_opportunity_finder.h
remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -57,11 +55,9 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
remove_function_reduction_opportunity.cpp
remove_function_reduction_opportunity_finder.cpp
remove_instruction_reduction_opportunity.cpp
remove_relaxed_precision_decoration_opportunity_finder.cpp
remove_selection_reduction_opportunity.cpp
remove_selection_reduction_opportunity_finder.cpp
remove_unreferenced_instruction_reduction_opportunity_finder.cpp
remove_opname_instruction_reduction_opportunity_finder.cpp
structured_loop_to_selection_reduction_opportunity.cpp
structured_loop_to_selection_reduction_opportunity_finder.cpp
conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp

View File

@@ -73,7 +73,7 @@ ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
result.push_back(
MakeUnique<
ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
block.terminator(), redirect_to_true));
context, block.terminator(), redirect_to_true));
}
}
}

View File

@@ -19,12 +19,15 @@
namespace spvtools {
namespace reduce {
using opt::IRContext;
using opt::Instruction;
ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
Instruction* conditional_branch_instruction, bool redirect_to_true)
: conditional_branch_instruction_(conditional_branch_instruction),
IRContext* context, Instruction* conditional_branch_instruction,
bool redirect_to_true)
: context_(context),
conditional_branch_instruction_(conditional_branch_instruction),
redirect_to_true_(redirect_to_true) {}
bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
@@ -43,11 +46,24 @@ void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
uint32_t operand_to_copy =
redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
auto old_successor_block_id =
conditional_branch_instruction_->GetSingleWordInOperand(
operand_to_modify);
// Do the branch redirection.
conditional_branch_instruction_->SetInOperand(
operand_to_modify,
{conditional_branch_instruction_->GetSingleWordInOperand(
operand_to_copy)});
// The old successor block may have phi instructions; these will need to
// respect the change in edges.
AdaptPhiInstructionsForRemovedEdge(
context_->get_instr_block(conditional_branch_instruction_)->id(),
context_->cfg()->block(old_successor_block_id));
// We have changed the CFG.
context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
}
} // namespace reduce

View File

@@ -31,7 +31,8 @@ class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
// to the true target; otherwise, the true target will be changed to also
// point to the false target.
explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
opt::IRContext* context, opt::Instruction* conditional_branch_instruction,
bool redirect_to_true);
bool PreconditionHolds() override;
@@ -39,6 +40,7 @@ class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
void Apply() override;
private:
opt::IRContext* context_;
opt::Instruction* conditional_branch_instruction_;
// If true, the false target will be changed to point to the true target;

View File

@@ -24,8 +24,6 @@
#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
#include "source/reduce/remove_block_reduction_opportunity_finder.h"
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_relaxed_precision_decoration_opportunity_finder.h"
#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
@@ -35,41 +33,32 @@
namespace spvtools {
namespace reduce {
struct Reducer::Impl {
explicit Impl(spv_target_env env) : target_env(env) {}
bool ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options);
const spv_target_env target_env; // Target environment.
MessageConsumer consumer; // Message consumer.
InterestingnessFunction interestingness_function;
std::vector<std::unique_ptr<ReductionPass>> passes;
};
Reducer::Reducer(spv_target_env env) : impl_(MakeUnique<Impl>(env)) {}
Reducer::Reducer(spv_target_env target_env) : target_env_(target_env) {}
Reducer::~Reducer() = default;
void Reducer::SetMessageConsumer(MessageConsumer c) {
for (auto& pass : impl_->passes) {
for (auto& pass : passes_) {
pass->SetMessageConsumer(c);
}
impl_->consumer = std::move(c);
for (auto& pass : cleanup_passes_) {
pass->SetMessageConsumer(c);
}
consumer_ = std::move(c);
}
void Reducer::SetInterestingnessFunction(
Reducer::InterestingnessFunction interestingness_function) {
impl_->interestingness_function = std::move(interestingness_function);
interestingness_function_ = std::move(interestingness_function);
}
Reducer::ReductionResultStatus Reducer::Run(
std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
spv_const_reducer_options options,
spv_validator_options validator_options) const {
spv_validator_options validator_options) {
std::vector<uint32_t> current_binary(std::move(binary_in));
spvtools::SpirvTools tools(impl_->target_env);
spvtools::SpirvTools tools(target_env_);
assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
// Keeps track of how many reduction attempts have been tried. Reduction
@@ -79,113 +68,49 @@ Reducer::ReductionResultStatus Reducer::Run(
// Initial state should be valid.
if (!tools.Validate(&current_binary[0], current_binary.size(),
validator_options)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial binary is invalid; stopping.");
consumer_(SPV_MSG_INFO, nullptr, {},
"Initial binary is invalid; stopping.");
return Reducer::ReductionResultStatus::kInitialStateInvalid;
}
// Initial state should be interesting.
if (!impl_->interestingness_function(current_binary, reductions_applied)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Initial state was not interesting; stopping.");
if (!interestingness_function_(current_binary, reductions_applied)) {
consumer_(SPV_MSG_INFO, nullptr, {},
"Initial state was not interesting; stopping.");
return Reducer::ReductionResultStatus::kInitialStateNotInteresting;
}
// Determines whether, on completing one round of reduction passes, it is
// worthwhile trying a further round.
bool another_round_worthwhile = true;
Reducer::ReductionResultStatus result =
RunPasses(&passes_, options, validator_options, tools, &current_binary,
&reductions_applied);
// Apply round after round of reduction passes until we hit the reduction
// step limit, or deem that another round is not going to be worthwhile.
while (!impl_->ReachedStepLimit(reductions_applied, options) &&
another_round_worthwhile) {
// At the start of a round of reduction passes, assume another round will
// not be worthwhile unless we find evidence to the contrary.
another_round_worthwhile = false;
// Iterate through the available passes
for (auto& pass : impl_->passes) {
// If this pass hasn't reached its minimum granularity then it's
// worth eventually doing another round of reductions, in order to
// try this pass at a finer granularity.
another_round_worthwhile |= !pass->ReachedMinimumGranularity();
// Keep applying this pass at its current granularity until it stops
// working or we hit the reduction step limit.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
("Trying pass " + pass->GetName() + ".").c_str());
do {
auto maybe_result = pass->TryApplyReduction(current_binary);
if (maybe_result.empty()) {
// For this round, the pass has no more opportunities (chunks) to
// apply, so move on to the next pass.
impl_->consumer(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
break;
}
bool interesting = false;
std::stringstream stringstream;
reductions_applied++;
stringstream << "Pass " << pass->GetName() << " made reduction step "
<< reductions_applied << ".";
impl_->consumer(SPV_MSG_INFO, nullptr, {},
(stringstream.str().c_str()));
if (!tools.Validate(&maybe_result[0], maybe_result.size(),
validator_options)) {
// The reduction step went wrong and an invalid binary was produced.
// By design, this shouldn't happen; this is a safeguard to stop an
// invalid binary from being regarded as interesting.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step produced an invalid binary.");
if (options->fail_on_validation_error) {
return Reducer::ReductionResultStatus::kStateInvalid;
}
} else if (impl_->interestingness_function(maybe_result,
reductions_applied)) {
// Success! The binary produced by this reduction step is
// interesting, so make it the binary of interest henceforth, and
// note that it's worth doing another round of reduction passes.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step succeeded.");
current_binary = std::move(maybe_result);
interesting = true;
another_round_worthwhile = true;
}
// We must call this before the next call to TryApplyReduction.
pass->NotifyInteresting(interesting);
// Bail out if the reduction step limit has been reached.
} while (!impl_->ReachedStepLimit(reductions_applied, options));
}
if (result == Reducer::ReductionResultStatus::kComplete) {
// Cleanup passes.
result = RunPasses(&cleanup_passes_, options, validator_options, tools,
&current_binary, &reductions_applied);
}
if (result == Reducer::ReductionResultStatus::kComplete) {
consumer_(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
}
// Even if the reduction has failed by this point (e.g. due to producing an
// invalid binary), we still update the output binary for better debugging.
*binary_out = std::move(current_binary);
// Report whether reduction completed, or bailed out early due to reaching
// the step limit.
if (impl_->ReachedStepLimit(reductions_applied, options)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reached reduction step limit; stopping.");
return Reducer::ReductionResultStatus::kReachedStepLimit;
}
impl_->consumer(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
return Reducer::ReductionResultStatus::kComplete;
return result;
}
void Reducer::AddDefaultReductionPasses() {
AddReductionPass(spvtools::MakeUnique<
RemoveOpNameInstructionReductionOpportunityFinder>());
AddReductionPass(spvtools::MakeUnique<
RemoveRelaxedPrecisionDecorationOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<
RemoveUnreferencedInstructionReductionOpportunityFinder>(false));
AddReductionPass(
spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
AddReductionPass(spvtools::MakeUnique<
RemoveUnreferencedInstructionReductionOpportunityFinder>());
AddReductionPass(spvtools::MakeUnique<
StructuredLoopToSelectionReductionOpportunityFinder>());
AddReductionPass(
@@ -201,18 +126,117 @@ void Reducer::AddDefaultReductionPasses() {
ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
// Cleanup passes.
AddCleanupReductionPass(
spvtools::MakeUnique<
RemoveUnreferencedInstructionReductionOpportunityFinder>(true));
}
void Reducer::AddReductionPass(
std::unique_ptr<ReductionOpportunityFinder>&& finder) {
impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(
impl_->target_env, std::move(finder)));
passes_.push_back(
spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
}
bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options) {
void Reducer::AddCleanupReductionPass(
std::unique_ptr<ReductionOpportunityFinder>&& finder) {
cleanup_passes_.push_back(
spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
}
bool Reducer::ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options) {
return current_step >= options->step_limit;
}
Reducer::ReductionResultStatus Reducer::RunPasses(
std::vector<std::unique_ptr<ReductionPass>>* passes,
spv_const_reducer_options options, spv_validator_options validator_options,
const SpirvTools& tools, std::vector<uint32_t>* current_binary,
uint32_t* const reductions_applied) {
// Determines whether, on completing one round of reduction passes, it is
// worthwhile trying a further round.
bool another_round_worthwhile = true;
// Apply round after round of reduction passes until we hit the reduction
// step limit, or deem that another round is not going to be worthwhile.
while (!ReachedStepLimit(*reductions_applied, options) &&
another_round_worthwhile) {
// At the start of a round of reduction passes, assume another round will
// not be worthwhile unless we find evidence to the contrary.
another_round_worthwhile = false;
// Iterate through the available passes.
for (auto& pass : *passes) {
// If this pass hasn't reached its minimum granularity then it's
// worth eventually doing another round of reductions, in order to
// try this pass at a finer granularity.
another_round_worthwhile |= !pass->ReachedMinimumGranularity();
// Keep applying this pass at its current granularity until it stops
// working or we hit the reduction step limit.
consumer_(SPV_MSG_INFO, nullptr, {},
("Trying pass " + pass->GetName() + ".").c_str());
do {
auto maybe_result = pass->TryApplyReduction(*current_binary);
if (maybe_result.empty()) {
// For this round, the pass has no more opportunities (chunks) to
// apply, so move on to the next pass.
consumer_(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
break;
}
bool interesting = false;
std::stringstream stringstream;
(*reductions_applied)++;
stringstream << "Pass " << pass->GetName() << " made reduction step "
<< *reductions_applied << ".";
consumer_(SPV_MSG_INFO, nullptr, {}, (stringstream.str().c_str()));
if (!tools.Validate(&maybe_result[0], maybe_result.size(),
validator_options)) {
// The reduction step went wrong and an invalid binary was produced.
// By design, this shouldn't happen; this is a safeguard to stop an
// invalid binary from being regarded as interesting.
consumer_(SPV_MSG_INFO, nullptr, {},
"Reduction step produced an invalid binary.");
if (options->fail_on_validation_error) {
// In this mode, we fail, so we update the current binary so it is
// output for debugging.
*current_binary = std::move(maybe_result);
return Reducer::ReductionResultStatus::kStateInvalid;
}
} else if (interestingness_function_(maybe_result,
*reductions_applied)) {
// Success! The binary produced by this reduction step is
// interesting, so make it the binary of interest henceforth, and
// note that it's worth doing another round of reduction passes.
consumer_(SPV_MSG_INFO, nullptr, {}, "Reduction step succeeded.");
*current_binary = std::move(maybe_result);
interesting = true;
another_round_worthwhile = true;
}
// We must call this before the next call to TryApplyReduction.
pass->NotifyInteresting(interesting);
// Bail out if the reduction step limit has been reached.
} while (!ReachedStepLimit(*reductions_applied, options));
}
}
// Report whether reduction completed, or bailed out early due to reaching
// the step limit.
if (ReachedStepLimit(*reductions_applied, options)) {
consumer_(SPV_MSG_INFO, nullptr, {},
"Reached reduction step limit; stopping.");
return Reducer::ReductionResultStatus::kReachedStepLimit;
}
// The passes completed successfully, although we may still run more passes.
return Reducer::ReductionResultStatus::kComplete;
}
} // namespace reduce
} // namespace spvtools

View File

@@ -50,7 +50,7 @@ class Reducer {
using InterestingnessFunction =
std::function<bool(const std::vector<uint32_t>&, uint32_t)>;
// Constructs an instance with the given target |env|, which is used to
// Constructs an instance with the given target |target_env|, which is used to
// decode the binary to be reduced later.
//
// The constructed instance will have an empty message consumer, which just
@@ -59,7 +59,7 @@ class Reducer {
//
// The constructed instance also needs to have an interestingness function
// set and some reduction passes added to it in order to be useful.
explicit Reducer(spv_target_env env);
explicit Reducer(spv_target_env target_env);
// Disables copy/move constructor/assignment operations.
Reducer(const Reducer&) = delete;
@@ -86,17 +86,34 @@ class Reducer {
// that will be iterated over.
void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);
// Adds a cleanup reduction pass based on the given finder to the sequence of
// passes that will run after other passes.
void AddCleanupReductionPass(
std::unique_ptr<ReductionOpportunityFinder>&& finder);
// Reduces the given SPIR-V module |binary_out|.
// The reduced binary ends up in |binary_out|.
// A status is returned.
ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
std::vector<uint32_t>* binary_out,
spv_const_reducer_options options,
spv_validator_options validator_options) const;
spv_validator_options validator_options);
private:
struct Impl; // Opaque struct for holding internal data.
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.
static bool ReachedStepLimit(uint32_t current_step,
spv_const_reducer_options options);
ReductionResultStatus RunPasses(
std::vector<std::unique_ptr<ReductionPass>>* passes,
spv_const_reducer_options options,
spv_validator_options validator_options, const SpirvTools& tools,
std::vector<uint32_t>* current_binary, uint32_t* reductions_applied);
const spv_target_env target_env_;
MessageConsumer consumer_;
InterestingnessFunction interestingness_function_;
std::vector<std::unique_ptr<ReductionPass>> passes_;
std::vector<std::unique_ptr<ReductionPass>> cleanup_passes_;
};
} // namespace reduce

View File

@@ -44,5 +44,22 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
return undef_id;
}
void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
opt::BasicBlock* to_block) {
to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
Instruction::OperandList new_in_operands;
// Go through the OpPhi's input operands in (variable, parent) pairs.
for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
// Keep all pairs where the parent is not the block from which the edge
// is being removed.
if (phi_inst->GetInOperand(index + 1).words[0] != from_id) {
new_in_operands.push_back(phi_inst->GetInOperand(index));
new_in_operands.push_back(phi_inst->GetInOperand(index + 1));
}
}
phi_inst->SetInOperands(std::move(new_in_operands));
});
}
} // namespace reduce
} // namespace spvtools

View File

@@ -30,6 +30,11 @@ extern const uint32_t kFalseBranchOperandIndex;
// adding one if it does not exist.
uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
// Removes any components of |to_block|'s phi instructions relating to
// |from_id|.
void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
opt::BasicBlock* to_block);
} // namespace reduce
} // namespace spvtools

View File

@@ -1,45 +0,0 @@
// Copyright (c) 2018 Google Inc.
//
// 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/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/opcode.h"
#include "source/opt/instruction.h"
#include "source/reduce/remove_instruction_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
using opt::IRContext;
std::vector<std::unique_ptr<ReductionOpportunity>>
RemoveOpNameInstructionReductionOpportunityFinder::GetAvailableOpportunities(
IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
for (auto& inst : context->module()->debugs2()) {
if (inst.opcode() == SpvOpName || inst.opcode() == SpvOpMemberName) {
result.push_back(
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
}
return result;
}
std::string RemoveOpNameInstructionReductionOpportunityFinder::GetName() const {
return "RemoveOpNameInstructionReductionOpportunityFinder";
}
} // namespace reduce
} // namespace spvtools

View File

@@ -1,45 +0,0 @@
// Copyright (c) 2018 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_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
#include "source/reduce/reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
// A finder for opportunities to remove OpName instructions. As well as making
// the module smaller, removing an OpName instruction may create opportunities
// for subsequently removing the instructions that create the ids to which the
// OpName applies.
class RemoveOpNameInstructionReductionOpportunityFinder
: public ReductionOpportunityFinder {
public:
RemoveOpNameInstructionReductionOpportunityFinder() = default;
~RemoveOpNameInstructionReductionOpportunityFinder() override = default;
std::string GetName() const final;
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const final;
private:
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_

View File

@@ -1,49 +0,0 @@
// Copyright (c) 2018 Google Inc.
//
// 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/reduce/remove_relaxed_precision_decoration_opportunity_finder.h"
#include "source/reduce/remove_instruction_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
std::vector<std::unique_ptr<ReductionOpportunity>>
RemoveRelaxedPrecisionDecorationOpportunityFinder::GetAvailableOpportunities(
opt::IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
// Consider all annotation instructions
for (auto& inst : context->module()->annotations()) {
// We are interested in removing instructions of the form:
// SpvOpDecorate %id RelaxedPrecision
// and
// SpvOpMemberDecorate %id member RelaxedPrecision
if ((inst.opcode() == SpvOpDecorate &&
inst.GetSingleWordInOperand(1) == SpvDecorationRelaxedPrecision) ||
(inst.opcode() == SpvOpMemberDecorate &&
inst.GetSingleWordInOperand(2) == SpvDecorationRelaxedPrecision)) {
result.push_back(
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
}
return result;
}
std::string RemoveRelaxedPrecisionDecorationOpportunityFinder::GetName() const {
return "RemoveRelaxedPrecisionDecorationOpportunityFinder";
}
} // namespace reduce
} // namespace spvtools

View File

@@ -21,28 +21,109 @@
namespace spvtools {
namespace reduce {
RemoveUnreferencedInstructionReductionOpportunityFinder::
RemoveUnreferencedInstructionReductionOpportunityFinder(
bool remove_constants_and_undefs)
: remove_constants_and_undefs_(remove_constants_and_undefs) {}
std::vector<std::unique_ptr<ReductionOpportunity>>
RemoveUnreferencedInstructionReductionOpportunityFinder::
GetAvailableOpportunities(opt::IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
for (auto& inst : context->module()->debugs1()) {
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
continue;
}
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
for (auto& inst : context->module()->debugs2()) {
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
continue;
}
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
for (auto& inst : context->module()->debugs3()) {
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
continue;
}
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
for (auto& inst : context->module()->types_values()) {
if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
continue;
}
if (!remove_constants_and_undefs_ &&
spvOpcodeIsConstantOrUndef(inst.opcode())) {
continue;
}
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
for (auto& inst : context->module()->annotations()) {
if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
continue;
}
uint32_t decoration = SpvDecorationMax;
switch (inst.opcode()) {
case SpvOpDecorate:
case SpvOpDecorateId:
case SpvOpDecorateString:
decoration = inst.GetSingleWordInOperand(1u);
break;
case SpvOpMemberDecorate:
case SpvOpMemberDecorateString:
decoration = inst.GetSingleWordInOperand(2u);
break;
default:
break;
}
// We conservatively only remove specific decorations that we believe will
// not change the shader interface, will not make the shader invalid, will
// actually be found in practice, etc.
switch (decoration) {
case SpvDecorationRelaxedPrecision:
case SpvDecorationNoSignedWrap:
case SpvDecorationNoContraction:
case SpvDecorationNoUnsignedWrap:
case SpvDecorationUserSemantic:
break;
default:
// Give up.
continue;
}
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}
for (auto& function : *context->module()) {
for (auto& block : function) {
for (auto& inst : block) {
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
continue;
}
if (!remove_constants_and_undefs_ &&
spvOpcodeIsConstantOrUndef(inst.opcode())) {
continue;
}
if (spvOpcodeIsBlockTerminator(inst.opcode()) ||
inst.opcode() == SpvOpSelectionMerge ||
inst.opcode() == SpvOpLoopMerge) {
// In this reduction pass we do not want to affect static control
// flow.
// In this reduction pass we do not want to affect static
// control flow.
continue;
}
// Given that we're in a block, we should only get here if the
// instruction is not directly related to control flow; i.e., it's
// some straightforward instruction with an unused result, like an
// arithmetic operation or function call.
// Given that we're in a block, we should only get here if
// the instruction is not directly related to control flow;
// i.e., it's some straightforward instruction with an
// unused result, like an arithmetic operation or function
// call.
result.push_back(
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
}

View File

@@ -28,7 +28,8 @@ namespace reduce {
class RemoveUnreferencedInstructionReductionOpportunityFinder
: public ReductionOpportunityFinder {
public:
RemoveUnreferencedInstructionReductionOpportunityFinder() = default;
explicit RemoveUnreferencedInstructionReductionOpportunityFinder(
bool remove_constants_and_undefs);
~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
@@ -38,6 +39,7 @@ class RemoveUnreferencedInstructionReductionOpportunityFinder
opt::IRContext* context) const final;
private:
bool remove_constants_and_undefs_;
};
} // namespace reduce

View File

@@ -167,23 +167,6 @@ void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
context_->cfg()->block(new_target_id));
}
void StructuredLoopToSelectionReductionOpportunity::
AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, BasicBlock* to_block) {
to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
Instruction::OperandList new_in_operands;
// Go through the OpPhi's input operands in (variable, parent) pairs.
for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
// Keep all pairs where the parent is not the block from which the edge
// is being removed.
if (phi_inst->GetInOperand(index + 1).words[0] != from_id) {
new_in_operands.push_back(phi_inst->GetInOperand(index));
new_in_operands.push_back(phi_inst->GetInOperand(index + 1));
}
}
phi_inst->SetInOperands(std::move(new_in_operands));
});
}
void StructuredLoopToSelectionReductionOpportunity::
AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {

View File

@@ -62,11 +62,6 @@ class StructuredLoopToSelectionReductionOpportunity
void RedirectEdge(uint32_t source_id, uint32_t original_target_id,
uint32_t new_target_id);
// Removes any components of |to_block|'s phi instructions relating to
// |from_id|.
void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
opt::BasicBlock* to_block);
// Adds components to |to_block|'s phi instructions to account for a new
// incoming edge from |from_id|.
void AdaptPhiInstructionsForAddedEdge(uint32_t from_id,

View File

@@ -590,9 +590,68 @@ spv_result_t StructuredSwitchChecks(ValidationState_t& _, Function* function,
return SPV_SUCCESS;
}
// Validates that all CFG divergences (i.e. conditional branch or switch) are
// structured correctly. Either divergence is preceded by a merge instruction
// or the divergence introduces at most one unseen label.
spv_result_t ValidateStructuredSelections(
ValidationState_t& _, const std::vector<const BasicBlock*>& postorder) {
std::unordered_set<uint32_t> seen;
for (auto iter = postorder.rbegin(); iter != postorder.rend(); ++iter) {
const auto* block = *iter;
const auto* terminator = block->terminator();
if (!terminator) continue;
const auto index = terminator - &_.ordered_instructions()[0];
auto* merge = &_.ordered_instructions()[index - 1];
// Marks merges and continues as seen.
if (merge->opcode() == SpvOpSelectionMerge) {
seen.insert(merge->GetOperandAs<uint32_t>(0));
} else if (merge->opcode() == SpvOpLoopMerge) {
seen.insert(merge->GetOperandAs<uint32_t>(0));
seen.insert(merge->GetOperandAs<uint32_t>(1));
} else {
// Only track the pointer if it is a merge instruction.
merge = nullptr;
}
// Skip unreachable blocks.
if (!block->reachable()) continue;
if (terminator->opcode() == SpvOpBranchConditional) {
const auto true_label = terminator->GetOperandAs<uint32_t>(1);
const auto false_label = terminator->GetOperandAs<uint32_t>(2);
// Mark the upcoming blocks as seen now, but only error out if this block
// was missing a merge instruction and both labels hadn't been seen
// previously.
const bool both_unseen =
seen.insert(true_label).second && seen.insert(false_label).second;
if (!merge && both_unseen) {
return _.diag(SPV_ERROR_INVALID_CFG, terminator)
<< "Selection must be structured";
}
} else if (terminator->opcode() == SpvOpSwitch) {
uint32_t count = 0;
// Mark the targets as seen now, but only error out if this block was
// missing a merge instruction and there were multiple unseen labels.
for (uint32_t i = 1; i < terminator->operands().size(); i += 2) {
const auto target = terminator->GetOperandAs<uint32_t>(i);
if (seen.insert(target).second) {
count++;
}
}
if (!merge && count > 1) {
return _.diag(SPV_ERROR_INVALID_CFG, terminator)
<< "Selection must be structured";
}
}
}
return SPV_SUCCESS;
}
spv_result_t StructuredControlFlowChecks(
ValidationState_t& _, Function* function,
const std::vector<std::pair<uint32_t, uint32_t>>& back_edges) {
const std::vector<std::pair<uint32_t, uint32_t>>& back_edges,
const std::vector<const BasicBlock*>& postorder) {
/// Check all backedges target only loop headers and have exactly one
/// back-edge branching to it
@@ -709,6 +768,10 @@ spv_result_t StructuredControlFlowChecks(
}
}
if (auto error = ValidateStructuredSelections(_, postorder)) {
return error;
}
return SPV_SUCCESS;
}
@@ -931,7 +994,8 @@ spv_result_t PerformCfgChecks(ValidationState_t& _) {
/// Structured control flow checks are only required for shader capabilities
if (_.HasCapability(SpvCapabilityShader)) {
if (auto error = StructuredControlFlowChecks(_, &function, back_edges))
if (auto error =
StructuredControlFlowChecks(_, &function, back_edges, postorder))
return error;
}
}

View File

@@ -42,10 +42,13 @@ spv_result_t ValidateUndef(ValidationState_t& _, const Instruction* inst) {
spv_result_t ValidateShaderClock(ValidationState_t& _,
const Instruction* inst) {
// #2952: disabled until scope discussion is resolved.
#if 0
const uint32_t execution_scope = inst->word(3);
if (auto error = ValidateExecutionScope(_, inst, execution_scope)) {
return error;
}
#endif
// Result Type must be a 64 - bit unsigned integer type or
// a vector of two - components of 32 -

View File

@@ -24,15 +24,20 @@ if (${SPIRV_BUILD_FUZZER})
transformation_add_constant_scalar_test.cpp
transformation_add_dead_break_test.cpp
transformation_add_dead_continue_test.cpp
transformation_add_no_contraction_decoration_test.cpp
transformation_add_type_boolean_test.cpp
transformation_add_type_float_test.cpp
transformation_add_type_int_test.cpp
transformation_add_type_pointer_test.cpp
transformation_construct_composite_test.cpp
transformation_copy_object_test.cpp
transformation_move_block_down_test.cpp
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_id_with_synonym_test.cpp
transformation_set_function_control_test.cpp
transformation_set_loop_control_test.cpp
transformation_set_selection_control_test.cpp
transformation_split_block_test.cpp
uniform_buffer_element_descriptor_test.cpp)

View File

@@ -21,6 +21,8 @@ namespace spvtools {
namespace fuzz {
namespace {
const uint32_t kNumFuzzerRuns = 20;
// Assembles the given |shader| text, and then runs the fuzzer |num_runs|
// times, using successive seeds starting from |initial_seed|. Checks that
// the binary produced after each fuzzer run is valid, and that replaying
@@ -29,7 +31,7 @@ namespace {
void RunFuzzerAndReplayer(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t initial_seed, uint32_t num_runs) {
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
@@ -240,9 +242,9 @@ TEST(FuzzerReplayerTest, Miscellaneous1) {
OpFunctionEnd
)";
// Do 5 fuzzer runs, starting from an initial seed of 0 (seed value chosen
// Do some fuzzer runs, starting from an initial seed of 0 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 5);
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, kNumFuzzerRuns);
}
TEST(FuzzerReplayerTest, Miscellaneous2) {
@@ -303,7 +305,7 @@ TEST(FuzzerReplayerTest, Miscellaneous2) {
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %16 %139
OpEntryPoint Fragment %4 "main" %16 %139 %25 %68
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
@@ -485,9 +487,9 @@ TEST(FuzzerReplayerTest, Miscellaneous2) {
OpFunctionEnd
)";
// Do 5 fuzzer runs, starting from an initial seed of 10 (seed value chosen
// Do some fuzzer runs, starting from an initial seed of 10 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 5);
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, kNumFuzzerRuns);
}
TEST(FuzzerReplayerTest, Miscellaneous3) {
@@ -603,7 +605,7 @@ TEST(FuzzerReplayerTest, Miscellaneous3) {
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %68 %100
OpEntryPoint Fragment %4 "main" %68 %100 %24
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
@@ -970,9 +972,9 @@ TEST(FuzzerReplayerTest, Miscellaneous3) {
*facts.mutable_fact()->Add() = temp;
}
// Do 5 fuzzer runs, starting from an initial seed of 94 (seed value chosen
// Do some fuzzer runs, starting from an initial seed of 94 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, facts, 94, 5);
RunFuzzerAndReplayer(shader, facts, 94, kNumFuzzerRuns);
}
} // namespace

View File

@@ -154,7 +154,7 @@ void RunAndCheckShrinker(
void RunFuzzerAndShrinker(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t seed) {
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
@@ -434,7 +434,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous2) {
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %16 %139
OpEntryPoint Fragment %4 "main" %16 %139 %25 %68
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
@@ -732,7 +732,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) {
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %68 %100
OpEntryPoint Fragment %4 "main" %68 %100 %24
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"

View File

@@ -1626,6 +1626,52 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous5) {
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationAddDeadContinueTest, DISABLED_Miscellaneous6) {
// A miscellaneous test that exposing a known bug in spirv-fuzz.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2919): re-enable
// this test as an when the known issue is fixed.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%9 = OpConstantTrue %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %13 %12 None
OpBranchConditional %9 %13 %11
%11 = OpLabel
%20 = OpCopyObject %6 %9
OpBranch %12
%12 = OpLabel
OpBranchConditional %9 %10 %13
%13 = OpLabel
%21 = OpCopyObject %6 %20
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
auto bad_transformation = TransformationAddDeadContinue(10, true, {});
ASSERT_FALSE(bad_transformation.IsApplicable(context.get(), fact_manager));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,194 @@
// 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_no_contraction_decoration.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationAddNoContractionDecorationTest, BasicScenarios) {
// This is a simple transformation and this test handles the main cases.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "x"
OpName %10 "y"
OpName %14 "i"
OpDecorate %32 NoContraction
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%11 = OpConstant %6 2
%12 = OpTypeInt 32 1
%13 = OpTypePointer Function %12
%15 = OpConstant %12 0
%22 = OpConstant %12 10
%23 = OpTypeBool
%31 = OpConstant %6 3.5999999
%38 = OpConstant %12 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
%14 = OpVariable %13 Function
OpStore %8 %9
OpStore %10 %11
OpStore %14 %15
OpBranch %16
%16 = OpLabel
OpLoopMerge %18 %19 None
OpBranch %20
%20 = OpLabel
%21 = OpLoad %12 %14
%24 = OpSLessThan %23 %21 %22
OpBranchConditional %24 %17 %18
%17 = OpLabel
%25 = OpLoad %6 %10
%26 = OpLoad %6 %10
%27 = OpFMul %6 %25 %26
%28 = OpLoad %6 %8
%29 = OpFAdd %6 %28 %27
OpStore %8 %29
%30 = OpLoad %6 %10
%32 = OpFDiv %6 %30 %31
OpStore %10 %32
%33 = OpLoad %12 %14
%34 = OpConvertSToF %6 %33
%35 = OpLoad %6 %8
%36 = OpFAdd %6 %35 %34
OpStore %8 %36
OpBranch %19
%19 = OpLabel
%37 = OpLoad %12 %14
%39 = OpIAdd %12 %37 %38
OpStore %14 %39
OpBranch %16
%18 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
// Invalid: 200 is not an id
ASSERT_FALSE(TransformationAddNoContractionDecoration(200).IsApplicable(
context.get(), fact_manager));
// Invalid: 17 is a block id
ASSERT_FALSE(TransformationAddNoContractionDecoration(17).IsApplicable(
context.get(), fact_manager));
// Invalid: 24 is not arithmetic
ASSERT_FALSE(TransformationAddNoContractionDecoration(24).IsApplicable(
context.get(), fact_manager));
// It is valid to add NoContraction to each of these ids (and it's fine to
// have duplicates of the decoration, in the case of 32).
for (uint32_t result_id : {32u, 32u, 27u, 29u, 39u}) {
TransformationAddNoContractionDecoration transformation(result_id);
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
transformation.Apply(context.get(), &fact_manager);
ASSERT_TRUE(IsValid(env, context.get()));
}
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "x"
OpName %10 "y"
OpName %14 "i"
OpDecorate %32 NoContraction
OpDecorate %32 NoContraction
OpDecorate %32 NoContraction
OpDecorate %27 NoContraction
OpDecorate %29 NoContraction
OpDecorate %39 NoContraction
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%11 = OpConstant %6 2
%12 = OpTypeInt 32 1
%13 = OpTypePointer Function %12
%15 = OpConstant %12 0
%22 = OpConstant %12 10
%23 = OpTypeBool
%31 = OpConstant %6 3.5999999
%38 = OpConstant %12 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
%14 = OpVariable %13 Function
OpStore %8 %9
OpStore %10 %11
OpStore %14 %15
OpBranch %16
%16 = OpLabel
OpLoopMerge %18 %19 None
OpBranch %20
%20 = OpLabel
%21 = OpLoad %12 %14
%24 = OpSLessThan %23 %21 %22
OpBranchConditional %24 %17 %18
%17 = OpLabel
%25 = OpLoad %6 %10
%26 = OpLoad %6 %10
%27 = OpFMul %6 %25 %26
%28 = OpLoad %6 %8
%29 = OpFAdd %6 %28 %27
OpStore %8 %29
%30 = OpLoad %6 %10
%32 = OpFDiv %6 %30 %31
OpStore %10 %32
%33 = OpLoad %12 %14
%34 = OpConvertSToF %6 %33
%35 = OpLoad %6 %8
%36 = OpFAdd %6 %35 %34
OpStore %8 %36
OpBranch %19
%19 = OpLabel
%37 = OpLoad %12 %14
%39 = OpIAdd %12 %37 %38
OpStore %14 %39
OpBranch %16
%18 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
@@ -164,12 +165,14 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest,
FactManager fact_manager;
std::vector<protobufs::IdUseDescriptor> uses_of_true = {
transformation::MakeIdUseDescriptor(41, SpvOpStore, 1, 44, 12),
transformation::MakeIdUseDescriptor(41, SpvOpLogicalOr, 0, 46, 0)};
MakeIdUseDescriptor(41, MakeInstructionDescriptor(44, SpvOpStore, 12), 1),
MakeIdUseDescriptor(41, MakeInstructionDescriptor(46, SpvOpLogicalOr, 0),
0)};
std::vector<protobufs::IdUseDescriptor> uses_of_false = {
transformation::MakeIdUseDescriptor(43, SpvOpStore, 1, 44, 13),
transformation::MakeIdUseDescriptor(43, SpvOpLogicalAnd, 1, 48, 0)};
MakeIdUseDescriptor(43, MakeInstructionDescriptor(44, SpvOpStore, 13), 1),
MakeIdUseDescriptor(43, MakeInstructionDescriptor(48, SpvOpLogicalAnd, 0),
1)};
const uint32_t fresh_id = 100;
@@ -529,10 +532,10 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest,
FactManager fact_manager;
auto use_of_true_in_if =
transformation::MakeIdUseDescriptor(13, SpvOpBranchConditional, 0, 10, 0);
auto use_of_false_in_while =
transformation::MakeIdUseDescriptor(21, SpvOpBranchConditional, 0, 16, 0);
auto use_of_true_in_if = MakeIdUseDescriptor(
13, MakeInstructionDescriptor(10, SpvOpBranchConditional, 0), 0);
auto use_of_false_in_while = MakeIdUseDescriptor(
21, MakeInstructionDescriptor(16, SpvOpBranchConditional, 0), 0);
auto replacement_1 = TransformationReplaceBooleanConstantWithConstantBinary(
use_of_true_in_if, 9, 11, SpvOpSLessThan, 100);
@@ -641,8 +644,8 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest, OpPhi) {
FactManager fact_manager;
auto replacement = TransformationReplaceBooleanConstantWithConstantBinary(
transformation::MakeIdUseDescriptor(9, SpvOpPhi, 0, 23, 0), 13, 15,
SpvOpSLessThan, 100);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(23, SpvOpPhi, 0), 0), 13,
15, SpvOpSLessThan, 100);
ASSERT_FALSE(replacement.IsApplicable(context.get(), fact_manager));
}

View File

@@ -13,6 +13,7 @@
// limitations under the License.
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
@@ -116,11 +117,11 @@ TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) {
// The constant ids are 9, 11 and 14, for 1, 2 and 3 respectively.
protobufs::IdUseDescriptor use_of_9_in_store =
transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(8, SpvOpStore, 0), 1);
protobufs::IdUseDescriptor use_of_11_in_add =
transformation::MakeIdUseDescriptor(11, SpvOpIAdd, 1, 12, 0);
MakeIdUseDescriptor(11, MakeInstructionDescriptor(12, SpvOpIAdd, 0), 1);
protobufs::IdUseDescriptor use_of_14_in_add =
transformation::MakeIdUseDescriptor(14, SpvOpIAdd, 0, 15, 0);
MakeIdUseDescriptor(14, MakeInstructionDescriptor(15, SpvOpIAdd, 0), 0);
// These transformations work: they match the facts.
auto transformation_use_of_9_in_store =
@@ -167,7 +168,7 @@ TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) {
// The following transformation does not apply because the id descriptor is
// not sensible.
protobufs::IdUseDescriptor nonsense_id_use_descriptor =
transformation::MakeIdUseDescriptor(9, SpvOpIAdd, 0, 15, 0);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(15, SpvOpIAdd, 0), 0);
ASSERT_FALSE(TransformationReplaceConstantWithUniform(
nonsense_id_use_descriptor, blockname_a, 101, 102)
.IsApplicable(context.get(), fact_manager));
@@ -477,13 +478,13 @@ TEST(TransformationReplaceConstantWithUniformTest, NestedStruct) {
// The constant ids are 13, 15, 17 and 20, for 1, 2, 3 and 4 respectively.
protobufs::IdUseDescriptor use_of_13_in_store =
transformation::MakeIdUseDescriptor(13, SpvOpStore, 1, 21, 0);
MakeIdUseDescriptor(13, MakeInstructionDescriptor(21, SpvOpStore, 0), 1);
protobufs::IdUseDescriptor use_of_15_in_add =
transformation::MakeIdUseDescriptor(15, SpvOpIAdd, 1, 16, 0);
MakeIdUseDescriptor(15, MakeInstructionDescriptor(16, SpvOpIAdd, 0), 1);
protobufs::IdUseDescriptor use_of_17_in_add =
transformation::MakeIdUseDescriptor(17, SpvOpIAdd, 0, 19, 0);
MakeIdUseDescriptor(17, MakeInstructionDescriptor(19, SpvOpIAdd, 0), 0);
protobufs::IdUseDescriptor use_of_20_in_store =
transformation::MakeIdUseDescriptor(20, SpvOpStore, 1, 19, 1);
MakeIdUseDescriptor(20, MakeInstructionDescriptor(19, SpvOpStore, 1), 1);
// These transformations work: they match the facts.
auto transformation_use_of_13_in_store =
@@ -703,7 +704,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NoUniformIntPointerPresent) {
// The constant id is 9 for 0.
protobufs::IdUseDescriptor use_of_9_in_store =
transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(8, SpvOpStore, 0), 1);
// This transformation is not available because no uniform pointer to integer
// type is present:
@@ -778,7 +779,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NoConstantPresentForIndex) {
// The constant id is 9 for 9.
protobufs::IdUseDescriptor use_of_9_in_store =
transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(8, SpvOpStore, 0), 1);
// This transformation is not available because no constant is present for the
// index 1 required to index into the uniform buffer:
@@ -852,7 +853,7 @@ TEST(TransformationReplaceConstantWithUniformTest,
// The constant id is 9 for 3.0.
protobufs::IdUseDescriptor use_of_9_in_store =
transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(8, SpvOpStore, 0), 1);
// This transformation is not available because no integer type is present to
// allow a constant index to be expressed:
@@ -937,9 +938,9 @@ TEST(TransformationReplaceConstantWithUniformTest,
// The constant ids for 9 and 10 are 9 and 11 respectively
protobufs::IdUseDescriptor use_of_9_in_store =
transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 10, 0);
MakeIdUseDescriptor(9, MakeInstructionDescriptor(10, SpvOpStore, 0), 1);
protobufs::IdUseDescriptor use_of_11_in_store =
transformation::MakeIdUseDescriptor(11, SpvOpStore, 1, 10, 1);
MakeIdUseDescriptor(11, MakeInstructionDescriptor(10, SpvOpStore, 1), 1);
// These are right:
ASSERT_TRUE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
@@ -1220,58 +1221,58 @@ TEST(TransformationReplaceConstantWithUniformTest, ComplexReplacements) {
std::vector<TransformationReplaceConstantWithUniform> transformations;
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(18, SpvOpStore, 1, 20, 0),
MakeIdUseDescriptor(18, MakeInstructionDescriptor(20, SpvOpStore, 0), 1),
uniform_f_a_4, 200, 201));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(22, SpvOpStore, 1, 23, 0),
MakeIdUseDescriptor(22, MakeInstructionDescriptor(23, SpvOpStore, 0), 1),
uniform_f_a_3, 202, 203));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(25, SpvOpStore, 1, 26, 0),
MakeIdUseDescriptor(25, MakeInstructionDescriptor(26, SpvOpStore, 0), 1),
uniform_f_a_2, 204, 205));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(28, SpvOpStore, 1, 29, 0),
MakeIdUseDescriptor(28, MakeInstructionDescriptor(29, SpvOpStore, 0), 1),
uniform_f_a_1, 206, 207));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(31, SpvOpStore, 1, 32, 0),
MakeIdUseDescriptor(31, MakeInstructionDescriptor(32, SpvOpStore, 0), 1),
uniform_f_a_0, 208, 209));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(30, SpvOpStore, 1, 35, 0),
MakeIdUseDescriptor(30, MakeInstructionDescriptor(35, SpvOpStore, 0), 1),
uniform_f_b_w, 210, 211));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(27, SpvOpStore, 1, 37, 0),
MakeIdUseDescriptor(27, MakeInstructionDescriptor(37, SpvOpStore, 0), 1),
uniform_f_b_z, 212, 213));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(24, SpvOpStore, 1, 39, 0),
MakeIdUseDescriptor(24, MakeInstructionDescriptor(39, SpvOpStore, 0), 1),
uniform_f_b_y, 214, 215));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(21, SpvOpStore, 1, 41, 0),
MakeIdUseDescriptor(21, MakeInstructionDescriptor(41, SpvOpStore, 0), 1),
uniform_f_b_x, 216, 217));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(44, SpvOpStore, 1, 45, 0),
MakeIdUseDescriptor(44, MakeInstructionDescriptor(45, SpvOpStore, 0), 1),
uniform_f_c_z, 220, 221));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(46, SpvOpStore, 1, 47, 0),
MakeIdUseDescriptor(46, MakeInstructionDescriptor(47, SpvOpStore, 0), 1),
uniform_f_c_y, 222, 223));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(48, SpvOpStore, 1, 49, 0),
MakeIdUseDescriptor(48, MakeInstructionDescriptor(49, SpvOpStore, 0), 1),
uniform_f_c_x, 224, 225));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(50, SpvOpStore, 1, 52, 0),
MakeIdUseDescriptor(50, MakeInstructionDescriptor(52, SpvOpStore, 0), 1),
uniform_f_d, 226, 227));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(53, SpvOpStore, 1, 54, 0),
MakeIdUseDescriptor(53, MakeInstructionDescriptor(54, SpvOpStore, 0), 1),
uniform_h_x, 228, 229));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(55, SpvOpStore, 1, 56, 0),
MakeIdUseDescriptor(55, MakeInstructionDescriptor(56, SpvOpStore, 0), 1),
uniform_h_y, 230, 231));
transformations.emplace_back(TransformationReplaceConstantWithUniform(
transformation::MakeIdUseDescriptor(42, SpvOpStore, 1, 43, 0), uniform_g,
218, 219));
MakeIdUseDescriptor(42, MakeInstructionDescriptor(43, SpvOpStore, 0), 1),
uniform_g, 218, 219));
for (auto& transformation : transformations) {
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));

View File

@@ -0,0 +1,251 @@
// 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_set_function_control.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationSetFunctionControlTest, VariousScenarios) {
// This is a simple transformation; this test captures the important things
// to check for.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %54
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %11 "foo(i1;i1;"
OpName %9 "a"
OpName %10 "b"
OpName %13 "bar("
OpName %17 "baz(i1;"
OpName %16 "x"
OpName %21 "boo(i1;i1;"
OpName %19 "a"
OpName %20 "b"
OpName %29 "g"
OpName %42 "param"
OpName %44 "param"
OpName %45 "param"
OpName %48 "param"
OpName %49 "param"
OpName %54 "color"
OpDecorate %54 Location 0
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%8 = OpTypeFunction %6 %7 %7
%15 = OpTypeFunction %6 %7
%28 = OpTypePointer Private %6
%29 = OpVariable %28 Private
%30 = OpConstant %6 2
%31 = OpConstant %6 5
%51 = OpTypeFloat 32
%52 = OpTypeVector %51 4
%53 = OpTypePointer Output %52
%54 = OpVariable %53 Output
%4 = OpFunction %2 None %3
%5 = OpLabel
%42 = OpVariable %7 Function
%44 = OpVariable %7 Function
%45 = OpVariable %7 Function
%48 = OpVariable %7 Function
%49 = OpVariable %7 Function
%41 = OpFunctionCall %2 %13
OpStore %42 %30
%43 = OpFunctionCall %6 %17 %42
OpStore %44 %31
%46 = OpLoad %6 %29
OpStore %45 %46
%47 = OpFunctionCall %6 %21 %44 %45
OpStore %48 %43
OpStore %49 %47
%50 = OpFunctionCall %6 %11 %48 %49
OpReturn
OpFunctionEnd
%11 = OpFunction %6 Const %8
%9 = OpFunctionParameter %7
%10 = OpFunctionParameter %7
%12 = OpLabel
%23 = OpLoad %6 %9
%24 = OpLoad %6 %10
%25 = OpIAdd %6 %23 %24
OpReturnValue %25
OpFunctionEnd
%13 = OpFunction %2 Inline %3
%14 = OpLabel
OpStore %29 %30
OpReturn
OpFunctionEnd
%17 = OpFunction %6 Pure|DontInline %15
%16 = OpFunctionParameter %7
%18 = OpLabel
%32 = OpLoad %6 %16
%33 = OpIAdd %6 %31 %32
OpReturnValue %33
OpFunctionEnd
%21 = OpFunction %6 DontInline %8
%19 = OpFunctionParameter %7
%20 = OpFunctionParameter %7
%22 = OpLabel
%36 = OpLoad %6 %19
%37 = OpLoad %6 %20
%38 = OpIMul %6 %36 %37
OpReturnValue %38
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
FactManager fact_manager;
// %36 is not a function
ASSERT_FALSE(TransformationSetFunctionControl(36, SpvFunctionControlMaskNone)
.IsApplicable(context.get(), fact_manager));
// Cannot add the Pure function control to %4 as it did not already have it
ASSERT_FALSE(TransformationSetFunctionControl(4, SpvFunctionControlPureMask)
.IsApplicable(context.get(), fact_manager));
// Cannot add the Const function control to %21 as it did not already
// have it
ASSERT_FALSE(TransformationSetFunctionControl(21, SpvFunctionControlConstMask)
.IsApplicable(context.get(), fact_manager));
// Set to None, removing Const
TransformationSetFunctionControl transformation1(11,
SpvFunctionControlMaskNone);
ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
transformation1.Apply(context.get(), &fact_manager);
// Set to Inline; silly to do it on an entry point, but it is allowed
TransformationSetFunctionControl transformation2(
4, SpvFunctionControlInlineMask);
ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
transformation2.Apply(context.get(), &fact_manager);
// Set to Pure, removing DontInline
TransformationSetFunctionControl transformation3(17,
SpvFunctionControlPureMask);
ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
transformation3.Apply(context.get(), &fact_manager);
// Change from Inline to DontInline
TransformationSetFunctionControl transformation4(
13, SpvFunctionControlDontInlineMask);
ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
transformation4.Apply(context.get(), &fact_manager);
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %54
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %11 "foo(i1;i1;"
OpName %9 "a"
OpName %10 "b"
OpName %13 "bar("
OpName %17 "baz(i1;"
OpName %16 "x"
OpName %21 "boo(i1;i1;"
OpName %19 "a"
OpName %20 "b"
OpName %29 "g"
OpName %42 "param"
OpName %44 "param"
OpName %45 "param"
OpName %48 "param"
OpName %49 "param"
OpName %54 "color"
OpDecorate %54 Location 0
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%8 = OpTypeFunction %6 %7 %7
%15 = OpTypeFunction %6 %7
%28 = OpTypePointer Private %6
%29 = OpVariable %28 Private
%30 = OpConstant %6 2
%31 = OpConstant %6 5
%51 = OpTypeFloat 32
%52 = OpTypeVector %51 4
%53 = OpTypePointer Output %52
%54 = OpVariable %53 Output
%4 = OpFunction %2 Inline %3
%5 = OpLabel
%42 = OpVariable %7 Function
%44 = OpVariable %7 Function
%45 = OpVariable %7 Function
%48 = OpVariable %7 Function
%49 = OpVariable %7 Function
%41 = OpFunctionCall %2 %13
OpStore %42 %30
%43 = OpFunctionCall %6 %17 %42
OpStore %44 %31
%46 = OpLoad %6 %29
OpStore %45 %46
%47 = OpFunctionCall %6 %21 %44 %45
OpStore %48 %43
OpStore %49 %47
%50 = OpFunctionCall %6 %11 %48 %49
OpReturn
OpFunctionEnd
%11 = OpFunction %6 None %8
%9 = OpFunctionParameter %7
%10 = OpFunctionParameter %7
%12 = OpLabel
%23 = OpLoad %6 %9
%24 = OpLoad %6 %10
%25 = OpIAdd %6 %23 %24
OpReturnValue %25
OpFunctionEnd
%13 = OpFunction %2 DontInline %3
%14 = OpLabel
OpStore %29 %30
OpReturn
OpFunctionEnd
%17 = OpFunction %6 Pure %15
%16 = OpFunctionParameter %7
%18 = OpLabel
%32 = OpLoad %6 %16
%33 = OpIAdd %6 %31 %32
OpReturnValue %33
OpFunctionEnd
%21 = OpFunction %6 DontInline %8
%19 = OpFunctionParameter %7
%20 = OpFunctionParameter %7
%22 = OpLabel
%36 = OpLoad %6 %19
%37 = OpLoad %6 %20
%38 = OpIMul %6 %36 %37
OpReturnValue %38
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,968 @@
// 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_set_loop_control.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationSetLoopControlTest, VariousScenarios) {
// This test features loops with various different controls, and goes through
// a number of acceptable and unacceptable transformations to those controls.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%16 = OpConstant %6 100
%17 = OpTypeBool
%20 = OpConstant %6 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%22 = OpVariable %7 Function
%32 = OpVariable %7 Function
%42 = OpVariable %7 Function
%52 = OpVariable %7 Function
%62 = OpVariable %7 Function
%72 = OpVariable %7 Function
%82 = OpVariable %7 Function
%92 = OpVariable %7 Function
%102 = OpVariable %7 Function
%112 = OpVariable %7 Function
%122 = OpVariable %7 Function
OpStore %8 %9
OpBranch %10
%10 = OpLabel
%132 = OpPhi %6 %9 %5 %21 %13
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%18 = OpSLessThan %17 %132 %16
OpBranchConditional %18 %11 %12
%11 = OpLabel
OpBranch %13
%13 = OpLabel
%21 = OpIAdd %6 %132 %20
OpStore %8 %21
OpBranch %10
%12 = OpLabel
OpStore %22 %9
OpBranch %23
%23 = OpLabel
%133 = OpPhi %6 %9 %12 %31 %26
OpLoopMerge %25 %26 Unroll
OpBranch %27
%27 = OpLabel
%29 = OpSLessThan %17 %133 %16
OpBranchConditional %29 %24 %25
%24 = OpLabel
OpBranch %26
%26 = OpLabel
%31 = OpIAdd %6 %133 %20
OpStore %22 %31
OpBranch %23
%25 = OpLabel
OpStore %32 %9
OpBranch %33
%33 = OpLabel
%134 = OpPhi %6 %9 %25 %41 %36
OpLoopMerge %35 %36 DontUnroll
OpBranch %37
%37 = OpLabel
%39 = OpSLessThan %17 %134 %16
OpBranchConditional %39 %34 %35
%34 = OpLabel
OpBranch %36
%36 = OpLabel
%41 = OpIAdd %6 %134 %20
OpStore %32 %41
OpBranch %33
%35 = OpLabel
OpStore %42 %9
OpBranch %43
%43 = OpLabel
%135 = OpPhi %6 %9 %35 %51 %46
OpLoopMerge %45 %46 DependencyInfinite
OpBranch %47
%47 = OpLabel
%49 = OpSLessThan %17 %135 %16
OpBranchConditional %49 %44 %45
%44 = OpLabel
OpBranch %46
%46 = OpLabel
%51 = OpIAdd %6 %135 %20
OpStore %42 %51
OpBranch %43
%45 = OpLabel
OpStore %52 %9
OpBranch %53
%53 = OpLabel
%136 = OpPhi %6 %9 %45 %61 %56
OpLoopMerge %55 %56 DependencyLength 3
OpBranch %57
%57 = OpLabel
%59 = OpSLessThan %17 %136 %16
OpBranchConditional %59 %54 %55
%54 = OpLabel
OpBranch %56
%56 = OpLabel
%61 = OpIAdd %6 %136 %20
OpStore %52 %61
OpBranch %53
%55 = OpLabel
OpStore %62 %9
OpBranch %63
%63 = OpLabel
%137 = OpPhi %6 %9 %55 %71 %66
OpLoopMerge %65 %66 MinIterations 10
OpBranch %67
%67 = OpLabel
%69 = OpSLessThan %17 %137 %16
OpBranchConditional %69 %64 %65
%64 = OpLabel
OpBranch %66
%66 = OpLabel
%71 = OpIAdd %6 %137 %20
OpStore %62 %71
OpBranch %63
%65 = OpLabel
OpStore %72 %9
OpBranch %73
%73 = OpLabel
%138 = OpPhi %6 %9 %65 %81 %76
OpLoopMerge %75 %76 MaxIterations 50
OpBranch %77
%77 = OpLabel
%79 = OpSLessThan %17 %138 %16
OpBranchConditional %79 %74 %75
%74 = OpLabel
OpBranch %76
%76 = OpLabel
%81 = OpIAdd %6 %138 %20
OpStore %72 %81
OpBranch %73
%75 = OpLabel
OpStore %82 %9
OpBranch %83
%83 = OpLabel
%139 = OpPhi %6 %9 %75 %91 %86
OpLoopMerge %85 %86 IterationMultiple 4
OpBranch %87
%87 = OpLabel
%89 = OpSLessThan %17 %139 %16
OpBranchConditional %89 %84 %85
%84 = OpLabel
OpBranch %86
%86 = OpLabel
%91 = OpIAdd %6 %139 %20
OpStore %82 %91
OpBranch %83
%85 = OpLabel
OpStore %92 %9
OpBranch %93
%93 = OpLabel
%140 = OpPhi %6 %9 %85 %101 %96
OpLoopMerge %95 %96 PeelCount 2
OpBranch %97
%97 = OpLabel
%99 = OpSLessThan %17 %140 %16
OpBranchConditional %99 %94 %95
%94 = OpLabel
OpBranch %96
%96 = OpLabel
%101 = OpIAdd %6 %140 %20
OpStore %92 %101
OpBranch %93
%95 = OpLabel
OpStore %102 %9
OpBranch %103
%103 = OpLabel
%141 = OpPhi %6 %9 %95 %111 %106
OpLoopMerge %105 %106 PartialCount 3
OpBranch %107
%107 = OpLabel
%109 = OpSLessThan %17 %141 %16
OpBranchConditional %109 %104 %105
%104 = OpLabel
OpBranch %106
%106 = OpLabel
%111 = OpIAdd %6 %141 %20
OpStore %102 %111
OpBranch %103
%105 = OpLabel
OpStore %112 %9
OpBranch %113
%113 = OpLabel
%142 = OpPhi %6 %9 %105 %121 %116
OpLoopMerge %115 %116 Unroll|PeelCount|PartialCount 3 4
OpBranch %117
%117 = OpLabel
%119 = OpSLessThan %17 %142 %16
OpBranchConditional %119 %114 %115
%114 = OpLabel
OpBranch %116
%116 = OpLabel
%121 = OpIAdd %6 %142 %20
OpStore %112 %121
OpBranch %113
%115 = OpLabel
OpStore %122 %9
OpBranch %123
%123 = OpLabel
%143 = OpPhi %6 %9 %115 %131 %126
OpLoopMerge %125 %126 DependencyLength|MinIterations|MaxIterations|IterationMultiple|PeelCount|PartialCount 2 5 90 4 7 14
OpBranch %127
%127 = OpLabel
%129 = OpSLessThan %17 %143 %16
OpBranchConditional %129 %124 %125
%124 = OpLabel
OpBranch %126
%126 = OpLabel
%131 = OpIAdd %6 %143 %20
OpStore %122 %131
OpBranch %123
%125 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
// These are the loop headers together with the selection controls of their
// merge instructions:
// %10 None
// %23 Unroll
// %33 DontUnroll
// %43 DependencyInfinite
// %53 DependencyLength 3
// %63 MinIterations 10
// %73 MaxIterations 50
// %83 IterationMultiple 4
// %93 PeelCount 2
// %103 PartialCount 3
// %113 Unroll|PeelCount|PartialCount 3 4
// %123
// DependencyLength|MinIterations|MaxIterations|IterationMultiple|PeelCount|PartialCount
// 2 5 90 4 7 14
ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(10, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
10, SpvLoopControlDependencyInfiniteMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(10, SpvLoopControlDependencyLengthMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(10, SpvLoopControlMinIterationsMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(10, SpvLoopControlMaxIterationsMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
10, SpvLoopControlIterationMultipleMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 0, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 3, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(
10,
SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
3, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(10,
SpvLoopControlUnrollMask |
SpvLoopControlPeelCountMask |
SpvLoopControlPartialCountMask,
3, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(10,
SpvLoopControlDontUnrollMask |
SpvLoopControlPeelCountMask |
SpvLoopControlPartialCountMask,
3, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(
23,
SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
3, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(23, SpvLoopControlMaxIterationsMask, 2, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(33, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(33, SpvLoopControlMinIterationsMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
33, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(33,
SpvLoopControlDontUnrollMask |
SpvLoopControlPartialCountMask,
0, 10)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(43, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(
43,
SpvLoopControlMaskNone | SpvLoopControlDependencyInfiniteMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
43, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
43,
SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(43,
SpvLoopControlDependencyInfiniteMask |
SpvLoopControlDependencyLengthMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
43, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(53, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(53, SpvLoopControlMaxIterationsMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
53, SpvLoopControlMaskNone | SpvLoopControlDependencyLengthMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(
53, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
53, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyLengthMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(53,
SpvLoopControlDependencyInfiniteMask |
SpvLoopControlDependencyLengthMask,
0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
53,
SpvLoopControlUnrollMask | SpvLoopControlDependencyLengthMask |
SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
5, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(63, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(63,
SpvLoopControlUnrollMask |
SpvLoopControlMinIterationsMask |
SpvLoopControlPeelCountMask |
SpvLoopControlPartialCountMask,
5, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(63,
SpvLoopControlUnrollMask |
SpvLoopControlMinIterationsMask |
SpvLoopControlPeelCountMask,
23, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
63,
SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
SpvLoopControlPeelCountMask,
2, 23)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(73, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
73,
SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
SpvLoopControlPeelCountMask |
SpvLoopControlPartialCountMask,
5, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(73,
SpvLoopControlUnrollMask |
SpvLoopControlMaxIterationsMask |
SpvLoopControlPeelCountMask,
23, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
73,
SpvLoopControlUnrollMask | SpvLoopControlMaxIterationsMask |
SpvLoopControlPeelCountMask,
2, 23)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
83,
SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
SpvLoopControlPeelCountMask |
SpvLoopControlPartialCountMask,
5, 3)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(83,
SpvLoopControlUnrollMask |
SpvLoopControlIterationMultipleMask |
SpvLoopControlPeelCountMask,
23, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(83,
SpvLoopControlUnrollMask |
SpvLoopControlIterationMultipleMask |
SpvLoopControlPeelCountMask,
2, 23)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(93, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 8)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(93, SpvLoopControlPartialCountMask, 0, 8)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(
93,
SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
16, 8)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(103, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(103,
SpvLoopControlDontUnrollMask |
SpvLoopControlPartialCountMask,
0, 60)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(113, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(
113,
SpvLoopControlIterationMultipleMask | SpvLoopControlPeelCountMask, 12,
0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlMaskNone, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(123, SpvLoopControlDontUnrollMask, 0, 0)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(
TransformationSetLoopControl(
123,
SpvLoopControlMinIterationsMask | SpvLoopControlMaxIterationsMask |
SpvLoopControlIterationMultipleMask |
SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
7, 8)
.IsApplicable(context.get(), fact_manager));
ASSERT_TRUE(TransformationSetLoopControl(123,
SpvLoopControlUnrollMask |
SpvLoopControlMinIterationsMask |
SpvLoopControlMaxIterationsMask |
SpvLoopControlPartialCountMask,
0, 9)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationSetLoopControl(
123,
SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
SpvLoopControlMaxIterationsMask |
SpvLoopControlPartialCountMask,
7, 9)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(
TransformationSetLoopControl(
123,
SpvLoopControlDontUnrollMask | SpvLoopControlMinIterationsMask |
SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
7, 9)
.IsApplicable(context.get(), fact_manager));
TransformationSetLoopControl(10,
SpvLoopControlUnrollMask |
SpvLoopControlPeelCountMask |
SpvLoopControlPartialCountMask,
3, 3)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(
43, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
0, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(63,
SpvLoopControlUnrollMask |
SpvLoopControlMinIterationsMask |
SpvLoopControlPeelCountMask,
23, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(73,
SpvLoopControlUnrollMask |
SpvLoopControlMaxIterationsMask |
SpvLoopControlPeelCountMask,
23, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(
93, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 16, 8)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
.Apply(context.get(), &fact_manager);
TransformationSetLoopControl(
123,
SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
0, 9)
.Apply(context.get(), &fact_manager);
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%16 = OpConstant %6 100
%17 = OpTypeBool
%20 = OpConstant %6 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%22 = OpVariable %7 Function
%32 = OpVariable %7 Function
%42 = OpVariable %7 Function
%52 = OpVariable %7 Function
%62 = OpVariable %7 Function
%72 = OpVariable %7 Function
%82 = OpVariable %7 Function
%92 = OpVariable %7 Function
%102 = OpVariable %7 Function
%112 = OpVariable %7 Function
%122 = OpVariable %7 Function
OpStore %8 %9
OpBranch %10
%10 = OpLabel
%132 = OpPhi %6 %9 %5 %21 %13
OpLoopMerge %12 %13 Unroll|PeelCount|PartialCount 3 3
OpBranch %14
%14 = OpLabel
%18 = OpSLessThan %17 %132 %16
OpBranchConditional %18 %11 %12
%11 = OpLabel
OpBranch %13
%13 = OpLabel
%21 = OpIAdd %6 %132 %20
OpStore %8 %21
OpBranch %10
%12 = OpLabel
OpStore %22 %9
OpBranch %23
%23 = OpLabel
%133 = OpPhi %6 %9 %12 %31 %26
OpLoopMerge %25 %26 DontUnroll
OpBranch %27
%27 = OpLabel
%29 = OpSLessThan %17 %133 %16
OpBranchConditional %29 %24 %25
%24 = OpLabel
OpBranch %26
%26 = OpLabel
%31 = OpIAdd %6 %133 %20
OpStore %22 %31
OpBranch %23
%25 = OpLabel
OpStore %32 %9
OpBranch %33
%33 = OpLabel
%134 = OpPhi %6 %9 %25 %41 %36
OpLoopMerge %35 %36 Unroll
OpBranch %37
%37 = OpLabel
%39 = OpSLessThan %17 %134 %16
OpBranchConditional %39 %34 %35
%34 = OpLabel
OpBranch %36
%36 = OpLabel
%41 = OpIAdd %6 %134 %20
OpStore %32 %41
OpBranch %33
%35 = OpLabel
OpStore %42 %9
OpBranch %43
%43 = OpLabel
%135 = OpPhi %6 %9 %35 %51 %46
OpLoopMerge %45 %46 DontUnroll|DependencyInfinite
OpBranch %47
%47 = OpLabel
%49 = OpSLessThan %17 %135 %16
OpBranchConditional %49 %44 %45
%44 = OpLabel
OpBranch %46
%46 = OpLabel
%51 = OpIAdd %6 %135 %20
OpStore %42 %51
OpBranch %43
%45 = OpLabel
OpStore %52 %9
OpBranch %53
%53 = OpLabel
%136 = OpPhi %6 %9 %45 %61 %56
OpLoopMerge %55 %56 None
OpBranch %57
%57 = OpLabel
%59 = OpSLessThan %17 %136 %16
OpBranchConditional %59 %54 %55
%54 = OpLabel
OpBranch %56
%56 = OpLabel
%61 = OpIAdd %6 %136 %20
OpStore %52 %61
OpBranch %53
%55 = OpLabel
OpStore %62 %9
OpBranch %63
%63 = OpLabel
%137 = OpPhi %6 %9 %55 %71 %66
OpLoopMerge %65 %66 Unroll|MinIterations|PeelCount 10 23
OpBranch %67
%67 = OpLabel
%69 = OpSLessThan %17 %137 %16
OpBranchConditional %69 %64 %65
%64 = OpLabel
OpBranch %66
%66 = OpLabel
%71 = OpIAdd %6 %137 %20
OpStore %62 %71
OpBranch %63
%65 = OpLabel
OpStore %72 %9
OpBranch %73
%73 = OpLabel
%138 = OpPhi %6 %9 %65 %81 %76
OpLoopMerge %75 %76 Unroll|MaxIterations|PeelCount 50 23
OpBranch %77
%77 = OpLabel
%79 = OpSLessThan %17 %138 %16
OpBranchConditional %79 %74 %75
%74 = OpLabel
OpBranch %76
%76 = OpLabel
%81 = OpIAdd %6 %138 %20
OpStore %72 %81
OpBranch %73
%75 = OpLabel
OpStore %82 %9
OpBranch %83
%83 = OpLabel
%139 = OpPhi %6 %9 %75 %91 %86
OpLoopMerge %85 %86 DontUnroll
OpBranch %87
%87 = OpLabel
%89 = OpSLessThan %17 %139 %16
OpBranchConditional %89 %84 %85
%84 = OpLabel
OpBranch %86
%86 = OpLabel
%91 = OpIAdd %6 %139 %20
OpStore %82 %91
OpBranch %83
%85 = OpLabel
OpStore %92 %9
OpBranch %93
%93 = OpLabel
%140 = OpPhi %6 %9 %85 %101 %96
OpLoopMerge %95 %96 PeelCount|PartialCount 16 8
OpBranch %97
%97 = OpLabel
%99 = OpSLessThan %17 %140 %16
OpBranchConditional %99 %94 %95
%94 = OpLabel
OpBranch %96
%96 = OpLabel
%101 = OpIAdd %6 %140 %20
OpStore %92 %101
OpBranch %93
%95 = OpLabel
OpStore %102 %9
OpBranch %103
%103 = OpLabel
%141 = OpPhi %6 %9 %95 %111 %106
OpLoopMerge %105 %106 PartialCount 60
OpBranch %107
%107 = OpLabel
%109 = OpSLessThan %17 %141 %16
OpBranchConditional %109 %104 %105
%104 = OpLabel
OpBranch %106
%106 = OpLabel
%111 = OpIAdd %6 %141 %20
OpStore %102 %111
OpBranch %103
%105 = OpLabel
OpStore %112 %9
OpBranch %113
%113 = OpLabel
%142 = OpPhi %6 %9 %105 %121 %116
OpLoopMerge %115 %116 PeelCount 12
OpBranch %117
%117 = OpLabel
%119 = OpSLessThan %17 %142 %16
OpBranchConditional %119 %114 %115
%114 = OpLabel
OpBranch %116
%116 = OpLabel
%121 = OpIAdd %6 %142 %20
OpStore %112 %121
OpBranch %113
%115 = OpLabel
OpStore %122 %9
OpBranch %123
%123 = OpLabel
%143 = OpPhi %6 %9 %115 %131 %126
OpLoopMerge %125 %126 Unroll|MinIterations|MaxIterations|PartialCount 5 90 9
OpBranch %127
%127 = OpLabel
%129 = OpSLessThan %17 %143 %16
OpBranchConditional %129 %124 %125
%124 = OpLabel
OpBranch %126
%126 = OpLabel
%131 = OpIAdd %6 %143 %20
OpStore %122 %131
OpBranch %123
%125 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationSetLoopControlTest, CheckSPIRVVersionsRespected) {
// This test checks that we do not allow introducing PeelCount and
// PartialCount loop controls if the SPIR-V version being used does not
// support them.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "i"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%16 = OpConstant %6 10
%17 = OpTypeBool
%20 = OpConstant %6 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpStore %8 %9
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %6 %8
%18 = OpSLessThan %17 %15 %16
OpBranchConditional %18 %11 %12
%11 = OpLabel
OpBranch %13
%13 = OpLabel
%19 = OpLoad %6 %8
%21 = OpIAdd %6 %19 %20
OpStore %8 %21
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto consumer = nullptr;
const auto context_1_0 =
BuildModule(SPV_ENV_UNIVERSAL_1_0, consumer, shader, kFuzzAssembleOption);
const auto context_1_1 =
BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, shader, kFuzzAssembleOption);
const auto context_1_2 =
BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer, shader, kFuzzAssembleOption);
const auto context_1_3 =
BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer, shader, kFuzzAssembleOption);
const auto context_1_4 =
BuildModule(SPV_ENV_UNIVERSAL_1_4, consumer, shader, kFuzzAssembleOption);
const auto context_1_5 =
BuildModule(SPV_ENV_UNIVERSAL_1_5, consumer, shader, kFuzzAssembleOption);
FactManager fact_manager;
TransformationSetLoopControl set_peel_and_partial(
10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4);
// PeelCount and PartialCount were introduced in SPIRV 1.4, so are not valid
// in the context of older versions.
ASSERT_FALSE(
set_peel_and_partial.IsApplicable(context_1_0.get(), fact_manager));
ASSERT_FALSE(
set_peel_and_partial.IsApplicable(context_1_1.get(), fact_manager));
ASSERT_FALSE(
set_peel_and_partial.IsApplicable(context_1_2.get(), fact_manager));
ASSERT_FALSE(
set_peel_and_partial.IsApplicable(context_1_3.get(), fact_manager));
ASSERT_TRUE(
set_peel_and_partial.IsApplicable(context_1_4.get(), fact_manager));
ASSERT_TRUE(
set_peel_and_partial.IsApplicable(context_1_5.get(), fact_manager));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,219 @@
// 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_set_selection_control.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationSetSelectionControlTest, VariousScenarios) {
// This is a simple transformation; this test captures the important things
// to check for.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "i"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%16 = OpConstant %6 10
%17 = OpTypeBool
%20 = OpConstant %6 3
%25 = OpConstant %6 1
%28 = OpConstant %6 2
%38 = OpConstant %6 4
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpStore %8 %9
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %6 %8
%18 = OpSLessThan %17 %15 %16
OpBranchConditional %18 %11 %12
%11 = OpLabel
%19 = OpLoad %6 %8
%21 = OpSGreaterThan %17 %19 %20
OpSelectionMerge %23 Flatten
OpBranchConditional %21 %22 %23
%22 = OpLabel
%24 = OpLoad %6 %8
%26 = OpIAdd %6 %24 %25
OpStore %8 %26
OpBranch %23
%23 = OpLabel
%27 = OpLoad %6 %8
%29 = OpSLessThan %17 %27 %28
OpSelectionMerge %31 DontFlatten
OpBranchConditional %29 %30 %31
%30 = OpLabel
%32 = OpLoad %6 %8
%33 = OpISub %6 %32 %25
OpStore %8 %33
OpBranch %31
%31 = OpLabel
%34 = OpLoad %6 %8
OpSelectionMerge %37 None
OpSwitch %34 %36 0 %35
%36 = OpLabel
OpBranch %37
%35 = OpLabel
%39 = OpLoad %6 %8
%40 = OpIAdd %6 %39 %38
OpStore %8 %40
OpBranch %36
%37 = OpLabel
OpBranch %13
%13 = OpLabel
%43 = OpLoad %6 %8
%44 = OpIAdd %6 %43 %25
OpStore %8 %44
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
FactManager fact_manager;
// %44 is not a block
ASSERT_FALSE(
TransformationSetSelectionControl(44, SpvSelectionControlFlattenMask)
.IsApplicable(context.get(), fact_manager));
// %13 does not end with OpSelectionMerge
ASSERT_FALSE(
TransformationSetSelectionControl(13, SpvSelectionControlMaskNone)
.IsApplicable(context.get(), fact_manager));
// %10 ends in OpLoopMerge, not OpSelectionMerge
ASSERT_FALSE(
TransformationSetSelectionControl(10, SpvSelectionControlMaskNone)
.IsApplicable(context.get(), fact_manager));
TransformationSetSelectionControl transformation1(
11, SpvSelectionControlDontFlattenMask);
ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
transformation1.Apply(context.get(), &fact_manager);
TransformationSetSelectionControl transformation2(
23, SpvSelectionControlFlattenMask);
ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
transformation2.Apply(context.get(), &fact_manager);
TransformationSetSelectionControl transformation3(
31, SpvSelectionControlMaskNone);
ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
transformation3.Apply(context.get(), &fact_manager);
TransformationSetSelectionControl transformation4(
31, SpvSelectionControlFlattenMask);
ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
transformation4.Apply(context.get(), &fact_manager);
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "i"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 0
%16 = OpConstant %6 10
%17 = OpTypeBool
%20 = OpConstant %6 3
%25 = OpConstant %6 1
%28 = OpConstant %6 2
%38 = OpConstant %6 4
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
OpStore %8 %9
OpBranch %10
%10 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %6 %8
%18 = OpSLessThan %17 %15 %16
OpBranchConditional %18 %11 %12
%11 = OpLabel
%19 = OpLoad %6 %8
%21 = OpSGreaterThan %17 %19 %20
OpSelectionMerge %23 DontFlatten
OpBranchConditional %21 %22 %23
%22 = OpLabel
%24 = OpLoad %6 %8
%26 = OpIAdd %6 %24 %25
OpStore %8 %26
OpBranch %23
%23 = OpLabel
%27 = OpLoad %6 %8
%29 = OpSLessThan %17 %27 %28
OpSelectionMerge %31 Flatten
OpBranchConditional %29 %30 %31
%30 = OpLabel
%32 = OpLoad %6 %8
%33 = OpISub %6 %32 %25
OpStore %8 %33
OpBranch %31
%31 = OpLabel
%34 = OpLoad %6 %8
OpSelectionMerge %37 Flatten
OpSwitch %34 %36 0 %35
%36 = OpLabel
OpBranch %37
%35 = OpLabel
%39 = OpLoad %6 %8
%40 = OpIAdd %6 %39 %38
OpStore %8 %40
OpBranch %36
%37 = OpLabel
OpBranch %13
%13 = OpLabel
%43 = OpLoad %6 %8
%44 = OpIAdd %6 %43 %25
OpStore %8 %44
OpBranch %10
%12 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@@ -375,14 +375,12 @@ OpEntryPoint Vertex %1 "func" %2
OpSelectionMerge %12 None
OpBranchConditional %true %13 %12
%13 = OpLabel
OpBranchConditional %true %14 %15
OpBranchConditional %true %14 %12
%14 = OpLabel
OpBranch %12
%15 = OpLabel
OpBranch %12
%12 = OpLabel
%16 = OpPhi %uint %uint_0 %11 %uint_0 %14 %uint_1 %15
OpStore %2 %16
%15 = OpPhi %uint %uint_0 %11 %uint_0 %13 %uint_1 %14
OpStore %2 %15
OpReturn
OpFunctionEnd
)";

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/opt/ir_context.h"
#include <algorithm>
#include <memory>
#include <string>
@@ -19,7 +21,6 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "source/opt/ir_context.h"
#include "source/opt/pass.h"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
@@ -664,6 +665,90 @@ OpFunctionEnd)";
EXPECT_EQ(next_id_bound, 0);
EXPECT_EQ(current_bound, context->module()->id_bound());
}
TEST_F(IRContextTest, CfgAndDomAnalysis) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpFunction %1 None %2
%4 = OpLabel
OpReturn
OpFunctionEnd)";
std::unique_ptr<IRContext> ctx =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
// Building the dominator analysis should build the CFG.
ASSERT_TRUE(ctx->module()->begin() != ctx->module()->end());
ctx->GetDominatorAnalysis(&*ctx->module()->begin());
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisCFG));
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDominatorAnalysis));
// Invalidating the CFG analysis should invalidate the dominator analysis.
ctx->InvalidateAnalyses(IRContext::kAnalysisCFG);
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisCFG));
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisDominatorAnalysis));
}
TEST_F(IRContextTest, AsanErrorTest) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "x"
OpName %10 "y"
OpDecorate %8 RelaxedPrecision
OpDecorate %10 RelaxedPrecision
OpDecorate %11 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
OpStore %8 %9
%11 = OpLoad %6 %8
OpBranch %20
%20 = OpLabel
%21 = OpPhi %6 %11 %5
OpStore %10 %21
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(
env, consumer, shader, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
opt::Function* fun =
context->cfg()->block(5)->GetParent(); // Computes the CFG analysis
opt::DominatorAnalysis* dom = nullptr;
dom = context->GetDominatorAnalysis(fun); // Computes the dominator analysis,
// which depends on the CFG
// analysis
context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisDominatorAnalysis); // Invalidates the
// CFG analysis
dom = context->GetDominatorAnalysis(
fun); // Recompute the CFG analysis because the Dominator tree uses it.
auto bb = dom->ImmediateDominator(5);
std::cout
<< bb->id(); // Make sure asan does not complain about use after free.
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -156,7 +156,8 @@ std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
types.emplace_back(new ReserveId());
types.emplace_back(new Queue());
// Pipe, Forward Pointer, PipeStorage, NamedBarrier, AccelerationStructureNV
// Pipe, Forward Pointer, PipeStorage, NamedBarrier, AccelerationStructureNV,
// CooperativeMatrixNV
types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
@@ -165,6 +166,7 @@ std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
types.emplace_back(new PipeStorage());
types.emplace_back(new NamedBarrier());
types.emplace_back(new AccelerationStructureNV());
types.emplace_back(new CooperativeMatrixNV(f32, 24, 24, 24));
return types;
}
@@ -214,6 +216,7 @@ TEST(TypeManager, TypeStrings) {
%arr_spec_const_with_id = OpTypeArray %s32 %spec_const_with_id
%arr_long_constant = OpTypeArray %s32 %long_constant
%arr_spec_const_op = OpTypeArray %s32 %spec_const_op
%cm = OpTypeCooperativeMatrixNV %f64 %id4 %id4 %id4
)";
std::vector<std::pair<uint32_t, std::string>> type_id_strs = {
@@ -251,6 +254,7 @@ TEST(TypeManager, TypeStrings) {
{36, "[sint32, id(1), words(1,99,42)]"},
{37, "[sint32, id(33), words(0,705032704,1)]"},
{38, "[sint32, id(34), words(2,34)]"},
{39, "<float64, 6, 6, 6>"},
};
std::unique_ptr<IRContext> context =
@@ -1060,6 +1064,7 @@ TEST(TypeManager, GetTypeInstructionAllTypes) {
; CHECK: OpTypePipeStorage
; CHECK: OpTypeNamedBarrier
; CHECK: OpTypeAccelerationStructureNV
; CHECK: OpTypeCooperativeMatrixNV [[f32]] [[uint24]] [[uint24]] [[uint24]]
OpCapability Shader
OpCapability Int64
OpCapability Linkage

View File

@@ -23,8 +23,6 @@ add_spvtools_unittest(TARGET reduce
reducer_test.cpp
remove_block_test.cpp
remove_function_test.cpp
remove_opname_instruction_test.cpp
remove_relaxed_precision_decoration_test.cpp
remove_selection_test.cpp
remove_unreferenced_instruction_test.cpp
structured_loop_to_selection_test.cpp

View File

@@ -14,8 +14,8 @@
#include "source/reduce/reducer.h"
#include "source/opt/build_module.h"
#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
#include "test/reduce/reduce_test_util.h"
@@ -23,6 +23,12 @@ namespace spvtools {
namespace reduce {
namespace {
using opt::BasicBlock;
using opt::IRContext;
const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
const MessageConsumer kMessageConsumer = CLIMessageConsumer;
// This changes its mind each time IsInteresting is invoked as to whether the
// binary is interesting, until some limit is reached after which the binary is
// always deemed interesting. This is useful to test that reduction passes
@@ -55,6 +61,8 @@ class PingPongInteresting {
};
TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
// Check that ExprToConstant and RemoveUnreferenced work together; once some
// ID uses have been changed to constants, those IDs can be removed.
std::string original = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
@@ -149,15 +157,6 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %60
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %16 "buf2"
OpMemberName %16 0 "i"
OpName %18 ""
OpName %25 "buf1"
OpMemberName %25 0 "f"
OpName %27 ""
OpName %60 "_GLF_color"
OpMemberDecorate %16 0 Offset 0
OpDecorate %16 Block
OpDecorate %18 DescriptorSet 0
@@ -174,14 +173,12 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
%16 = OpTypeStruct %6
%17 = OpTypePointer Uniform %16
%18 = OpVariable %17 Uniform
%19 = OpTypePointer Uniform %6
%22 = OpTypeBool
%100 = OpConstantTrue %22
%24 = OpTypeFloat 32
%25 = OpTypeStruct %24
%26 = OpTypePointer Uniform %25
%27 = OpVariable %26 Uniform
%28 = OpTypePointer Uniform %24
%31 = OpConstant %24 2
%56 = OpConstant %6 1
%58 = OpTypeVector %24 4
@@ -209,8 +206,7 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
OpFunctionEnd
)";
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
Reducer reducer(env);
Reducer reducer(kEnv);
PingPongInteresting ping_pong_interesting(10);
reducer.SetMessageConsumer(NopDiagnostic);
reducer.SetInterestingnessFunction(
@@ -218,12 +214,13 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
return ping_pong_interesting.IsInteresting(binary);
});
reducer.AddReductionPass(
MakeUnique<OperandToConstReductionOpportunityFinder>());
MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>(
false));
reducer.AddReductionPass(
MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
MakeUnique<OperandToConstReductionOpportunityFinder>());
std::vector<uint32_t> binary_in;
SpirvTools t(env);
SpirvTools t(kEnv);
ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
std::vector<uint32_t> binary_out;
@@ -237,71 +234,169 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
CheckEqual(env, expected, binary_out);
CheckEqual(kEnv, expected, binary_out);
}
TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
const std::string original = R"(
bool InterestingWhileOpcodeExists(const std::vector<uint32_t>& binary,
uint32_t opcode, uint32_t count, bool dump) {
if (dump) {
std::stringstream ss;
ss << "temp_" << count << ".spv";
DumpShader(binary, ss.str().c_str());
}
std::unique_ptr<IRContext> context =
BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size());
assert(context);
bool interesting = false;
for (auto& function : *context->module()) {
context->cfg()->ForEachBlockInPostOrder(
&*function.begin(), [opcode, &interesting](BasicBlock* block) -> void {
for (auto& inst : *block) {
if (inst.opcode() == opcode) {
interesting = true;
break;
}
}
});
if (interesting) {
break;
}
}
return interesting;
}
bool InterestingWhileIMulReachable(const std::vector<uint32_t>& binary,
uint32_t count) {
return InterestingWhileOpcodeExists(binary, SpvOpIMul, count, false);
}
bool InterestingWhileSDivReachable(const std::vector<uint32_t>& binary,
uint32_t count) {
return InterestingWhileOpcodeExists(binary, SpvOpSDiv, count, false);
}
// The shader below was derived from the following GLSL, and optimized.
// #version 310 es
// precision highp float;
// layout(location = 0) out vec4 _GLF_color;
// int foo() {
// int x = 1;
// int y;
// x = y / x; // SDiv
// return x;
// }
// void main() {
// int c;
// while (bool(c)) {
// do {
// if (bool(c)) {
// if (bool(c)) {
// ++c;
// } else {
// _GLF_color.x = float(c*c); // IMul
// }
// return;
// }
// } while(bool(foo()));
// return;
// }
// }
const std::string kShaderWithLoopsDivAndMul = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpEntryPoint Fragment %4 "main" %49
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
OpName %3 "a"
OpName %4 "this-name-counts-as-usage-for-load-instruction"
%5 = OpTypeVoid
%6 = OpTypeFunction %5
%7 = OpTypeFloat 32
%8 = OpTypePointer Function %7
%9 = OpConstant %7 1
%2 = OpFunction %5 None %6
%10 = OpLabel
%3 = OpVariable %8 Function
%4 = OpLoad %7 %3
OpStore %3 %9
OpName %4 "main"
OpName %49 "_GLF_color"
OpDecorate %49 Location 0
OpDecorate %52 RelaxedPrecision
OpDecorate %77 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%12 = OpConstant %6 1
%27 = OpTypeBool
%28 = OpTypeInt 32 0
%29 = OpConstant %28 0
%46 = OpTypeFloat 32
%47 = OpTypeVector %46 4
%48 = OpTypePointer Output %47
%49 = OpVariable %48 Output
%54 = OpTypePointer Output %46
%64 = OpConstantFalse %27
%67 = OpConstantTrue %27
%81 = OpUndef %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %61
%61 = OpLabel
OpLoopMerge %60 %63 None
OpBranch %20
%20 = OpLabel
%30 = OpINotEqual %27 %81 %29
OpLoopMerge %22 %23 None
OpBranchConditional %30 %21 %22
%21 = OpLabel
OpBranch %31
%31 = OpLabel
OpLoopMerge %33 %38 None
OpBranch %32
%32 = OpLabel
OpSelectionMerge %38 None
OpBranchConditional %30 %37 %38
%37 = OpLabel
OpSelectionMerge %42 None
OpBranchConditional %30 %41 %45
%41 = OpLabel
OpBranch %42
%45 = OpLabel
%52 = OpIMul %6 %81 %81
%53 = OpConvertSToF %46 %52
%55 = OpAccessChain %54 %49 %29
OpStore %55 %53
OpBranch %42
%42 = OpLabel
OpBranch %33
%38 = OpLabel
%77 = OpSDiv %6 %81 %12
%58 = OpINotEqual %27 %77 %29
OpBranchConditional %58 %31 %33
%33 = OpLabel
%86 = OpPhi %27 %67 %42 %64 %38
OpSelectionMerge %68 None
OpBranchConditional %86 %22 %68
%68 = OpLabel
OpBranch %22
%23 = OpLabel
OpBranch %20
%22 = OpLabel
%90 = OpPhi %27 %64 %20 %86 %33 %67 %68
OpSelectionMerge %70 None
OpBranchConditional %90 %60 %70
%70 = OpLabel
OpBranch %60
%63 = OpLabel
OpBranch %61
%60 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string expected = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
%5 = OpTypeVoid
%6 = OpTypeFunction %5
%7 = OpTypeFloat 32
%8 = OpTypePointer Function %7
%9 = OpConstant %7 1
%2 = OpFunction %5 None %6
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
TEST(ReducerTest, ShaderReduceWhileMulReachable) {
Reducer reducer(kEnv);
spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
Reducer reducer(env);
// Make ping-pong interesting very quickly, as there are not many
// opportunities.
PingPongInteresting ping_pong_interesting(1);
reducer.SetMessageConsumer(NopDiagnostic);
reducer.SetInterestingnessFunction(
[&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
return ping_pong_interesting.IsInteresting(binary);
});
reducer.AddReductionPass(
MakeUnique<RemoveOpNameInstructionReductionOpportunityFinder>());
reducer.AddReductionPass(
MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
reducer.SetInterestingnessFunction(InterestingWhileIMulReachable);
reducer.AddDefaultReductionPasses();
reducer.SetMessageConsumer(kMessageConsumer);
std::vector<uint32_t> binary_in;
SpirvTools t(env);
SpirvTools t(kEnv);
ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
ASSERT_TRUE(
t.Assemble(kShaderWithLoopsDivAndMul, &binary_in, kReduceAssembleOption));
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
@@ -312,8 +407,30 @@ TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
std::move(binary_in), &binary_out, reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
}
CheckEqual(env, expected, binary_out);
TEST(ReducerTest, ShaderReduceWhileDivReachable) {
Reducer reducer(kEnv);
reducer.SetInterestingnessFunction(InterestingWhileSDivReachable);
reducer.AddDefaultReductionPasses();
reducer.SetMessageConsumer(kMessageConsumer);
std::vector<uint32_t> binary_in;
SpirvTools t(kEnv);
ASSERT_TRUE(
t.Assemble(kShaderWithLoopsDivAndMul, &binary_in, kReduceAssembleOption));
std::vector<uint32_t> binary_out;
spvtools::ReducerOptions reducer_options;
reducer_options.set_step_limit(500);
reducer_options.set_fail_on_validation_error(true);
spvtools::ValidatorOptions validator_options;
Reducer::ReductionResultStatus status = reducer.Run(
std::move(binary_in), &binary_out, reducer_options, validator_options);
ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
}
} // namespace

View File

@@ -1,225 +0,0 @@
// Copyright (c) 2018 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/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/reduction_pass.h"
#include "test/reduce/reduce_test_util.h"
namespace spvtools {
namespace reduce {
namespace {
TEST(RemoveOpnameInstructionReductionPassTest, NothingToRemove) {
const std::string source = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, source, kReduceAssembleOption);
const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveOpnameInstructionReductionPassTest, RemoveSingleOpName) {
const std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
)";
const std::string epilogue = R"(
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpName %4 "main"
)" + epilogue;
const std::string expected = prologue + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, original, kReduceAssembleOption);
const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckEqual(env, expected, context.get());
}
TEST(RemoveOpnameInstructionReductionPassTest, TryApplyRemovesAllOpName) {
const std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
)";
const std::string epilogue = R"(
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpConstant %6 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function
%11 = OpVariable %7 Function
%12 = OpVariable %7 Function
OpStore %8 %9
OpStore %10 %9
OpStore %11 %9
OpStore %12 %9
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpName %4 "main"
OpName %8 "a"
OpName %10 "b"
OpName %11 "c"
OpName %12 "d"
)" + epilogue;
const std::string expected = prologue + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
{
// Check the right number of opportunities is detected
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, original, kReduceAssembleOption);
const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(5, ops.size());
}
{
// The reduction should remove all OpName
std::vector<uint32_t> binary;
SpirvTools t(env);
ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
auto reduced_binary =
ReductionPass(env,
spvtools::MakeUnique<
RemoveOpNameInstructionReductionOpportunityFinder>())
.TryApplyReduction(binary);
CheckEqual(env, expected, reduced_binary);
}
}
TEST(RemoveOpnameInstructionReductionPassTest,
TryApplyRemovesAllOpNameAndOpMemberName) {
const std::string prologue = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
)";
const std::string epilogue = R"(
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeInt 32 1
%8 = OpTypeVector %6 3
%9 = OpTypeStruct %6 %7 %8
%10 = OpTypePointer Function %9
%12 = OpConstant %7 0
%13 = OpConstant %6 1
%14 = OpTypePointer Function %6
%4 = OpFunction %2 None %3
%5 = OpLabel
%11 = OpVariable %10 Function
%15 = OpAccessChain %14 %11 %12
OpStore %15 %13
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpName %4 "main"
OpName %9 "S"
OpMemberName %9 0 "f"
OpMemberName %9 1 "i"
OpMemberName %9 2 "v"
OpName %11 "s"
)" + epilogue;
const std::string expected = prologue + epilogue;
const auto env = SPV_ENV_UNIVERSAL_1_3;
{
// Check the right number of opportunities is detected
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, original, kReduceAssembleOption);
const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(6, ops.size());
}
{
// The reduction should remove all OpName
std::vector<uint32_t> binary;
SpirvTools t(env);
ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
auto reduced_binary =
ReductionPass(env,
spvtools::MakeUnique<
RemoveOpNameInstructionReductionOpportunityFinder>())
.TryApplyReduction(binary);
CheckEqual(env, expected, reduced_binary);
}
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@@ -1,177 +0,0 @@
// 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/reduce/remove_relaxed_precision_decoration_opportunity_finder.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/reduction_pass.h"
#include "test/reduce/reduce_test_util.h"
namespace spvtools {
namespace reduce {
namespace {
TEST(RemoveRelaxedPrecisionDecorationTest, NothingToRemove) {
const std::string source = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, source, kReduceAssembleOption);
const auto ops = RemoveRelaxedPrecisionDecorationOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveRelaxedPrecisionDecorationTest, RemoveDecorations) {
const std::string source = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "f"
OpName %12 "i"
OpName %16 "v"
OpName %19 "S"
OpMemberName %19 0 "a"
OpMemberName %19 1 "b"
OpMemberName %19 2 "c"
OpName %21 "s"
OpDecorate %8 RelaxedPrecision
OpDecorate %12 RelaxedPrecision
OpDecorate %16 RelaxedPrecision
OpDecorate %17 RelaxedPrecision
OpDecorate %18 RelaxedPrecision
OpMemberDecorate %19 0 RelaxedPrecision
OpMemberDecorate %19 1 RelaxedPrecision
OpMemberDecorate %19 2 RelaxedPrecision
OpDecorate %22 RelaxedPrecision
OpDecorate %23 RelaxedPrecision
OpDecorate %24 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpConstant %6 2
%10 = OpTypeInt 32 1
%11 = OpTypePointer Function %10
%13 = OpConstant %10 22
%14 = OpTypeVector %6 2
%15 = OpTypePointer Function %14
%19 = OpTypeStruct %10 %6 %14
%20 = OpTypePointer Function %19
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%12 = OpVariable %11 Function
%16 = OpVariable %15 Function
%21 = OpVariable %20 Function
OpStore %8 %9
OpStore %12 %13
%17 = OpLoad %6 %8
%18 = OpCompositeConstruct %14 %17 %17
OpStore %16 %18
%22 = OpLoad %10 %12
%23 = OpLoad %6 %8
%24 = OpLoad %14 %16
%25 = OpCompositeConstruct %19 %22 %23 %24
OpStore %21 %25
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, source, kReduceAssembleOption);
const auto ops = RemoveRelaxedPrecisionDecorationOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(11, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
}
const std::string expected = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "f"
OpName %12 "i"
OpName %16 "v"
OpName %19 "S"
OpMemberName %19 0 "a"
OpMemberName %19 1 "b"
OpMemberName %19 2 "c"
OpName %21 "s"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpConstant %6 2
%10 = OpTypeInt 32 1
%11 = OpTypePointer Function %10
%13 = OpConstant %10 22
%14 = OpTypeVector %6 2
%15 = OpTypePointer Function %14
%19 = OpTypeStruct %10 %6 %14
%20 = OpTypePointer Function %19
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%12 = OpVariable %11 Function
%16 = OpVariable %15 Function
%21 = OpVariable %20 Function
OpStore %8 %9
OpStore %12 %13
%17 = OpLoad %6 %8
%18 = OpCompositeConstruct %14 %17 %17
OpStore %16 %18
%22 = OpLoad %10 %12
%23 = OpLoad %6 %8
%24 = OpLoad %14 %16
%25 = OpCompositeConstruct %19 %22 %23 %24
OpStore %21 %25
OpReturn
OpFunctionEnd
)";
CheckEqual(env, expected, context.get());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@@ -23,19 +23,26 @@ namespace spvtools {
namespace reduce {
namespace {
const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
const std::string prologue = R"(
// A module with some unused instructions, including some unused OpStore
// instructions.
RemoveUnreferencedInstructionReductionOpportunityFinder finder(true);
const std::string original = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "a"
OpName %10 "b"
OpName %12 "c"
OpName %14 "d"
OpSource ESSL 310 ; 0
OpName %4 "main" ; 1
OpName %8 "a" ; 2
OpName %10 "b" ; 3
OpName %12 "c" ; 4
OpName %14 "d" ; 5
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
@@ -49,51 +56,323 @@ TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
%10 = OpVariable %7 Function
%12 = OpVariable %7 Function
%14 = OpVariable %7 Function
OpStore %8 %9 ; 6
OpStore %10 %11 ; 7
OpStore %12 %13 ; 8
%15 = OpLoad %6 %8
OpStore %14 %15 ; 9
OpReturn
OpFunctionEnd
)";
const std::string epilogue = R"(
const MessageConsumer consumer = nullptr;
const auto context =
BuildModule(kEnv, consumer, original, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(10, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
const std::string step_2 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpConstant %6 10 ; 0
%11 = OpConstant %6 20 ; 1
%13 = OpConstant %6 30 ; 2
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%10 = OpVariable %7 Function ; 3
%12 = OpVariable %7 Function ; 4
%14 = OpVariable %7 Function ; 5
%15 = OpLoad %6 %8 ; 6
OpReturn
OpFunctionEnd
)";
const std::string original = prologue + R"(
OpStore %8 %9
OpStore %10 %11
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
CheckEqual(kEnv, step_2, context.get());
const std::string expected_after_2 = prologue + R"(
OpStore %12 %13
%15 = OpLoad %6 %8
OpStore %14 %15
)" + epilogue;
ops = finder.GetAvailableOpportunities(context.get());
const std::string expected_after_4 = prologue + R"(
%15 = OpLoad %6 %8
)" + epilogue;
ASSERT_EQ(7, ops.size());
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, original, kReduceAssembleOption);
const auto ops = RemoveUnreferencedInstructionReductionOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(4, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
CheckEqual(env, expected_after_2, context.get());
const std::string step_3 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function ; 0
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(ops[2]->PreconditionHolds());
ops[2]->TryToApply();
ASSERT_TRUE(ops[3]->PreconditionHolds());
ops[3]->TryToApply();
CheckEqual(kEnv, step_3, context.get());
CheckEqual(env, expected_after_4, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
const std::string step_4 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6 ; 0
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, step_4, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
const std::string step_5 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1 ; 0
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, step_5, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
const std::string step_6 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, step_6, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveUnreferencedInstructionReductionPassTest, Referenced) {
// A module with some unused global variables, constants, and types. Some will
// not be removed initially because of the OpDecorate instructions.
RemoveUnreferencedInstructionReductionOpportunityFinder finder(true);
const std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310 ; 1
OpName %4 "main" ; 2
OpName %12 "a" ; 3
OpDecorate %12 RelaxedPrecision ; 4
OpDecorate %13 RelaxedPrecision ; 5
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantTrue %6 ; 6
%10 = OpTypeInt 32 1
%11 = OpTypePointer Private %10
%12 = OpVariable %11 Private
%13 = OpConstant %10 1
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(6, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool ; 1
%10 = OpTypeInt 32 1
%11 = OpTypePointer Private %10
%12 = OpVariable %11 Private ; 2
%13 = OpConstant %10 1 ; 3
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(3, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
std::string after_2 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%10 = OpTypeInt 32 1
%11 = OpTypePointer Private %10 ; 1
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after_2, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
std::string after_3 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%10 = OpTypeInt 32 1 ; 1
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after_3, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
for (auto& op : ops) {
ASSERT_TRUE(op->PreconditionHolds());
op->TryToApply();
CheckValid(kEnv, context.get());
}
std::string after_4 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after_4, context.get());
ops = finder.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
} // namespace

View File

@@ -3026,6 +3026,7 @@ OpMemoryModel Logical GLSL450
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %block None
OpBranchConditional %undef %block %unreachable
%block = OpLabel
OpReturn
@@ -3049,6 +3050,7 @@ OpMemoryModel Logical GLSL450
%undef = OpUndef %int
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %block1 None
OpSwitch %undef %block1 0 %unreachable 1 %block2
%block1 = OpLabel
OpReturn
@@ -3748,7 +3750,355 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
/// TODO(umar): Nested CFG constructs
TEST_F(ValidateCFG, MissingMergeConditionalBranchBad) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranchConditional %undef %then %else
%then = OpLabel
OpReturn
%else = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
}
TEST_F(ValidateCFG, MissingMergeSwitchBad) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%undef = OpUndef %int
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSwitch %undef %then 0 %else
%then = OpLabel
OpReturn
%else = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
}
TEST_F(ValidateCFG, MissingMergeSwitchBad2) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%undef = OpUndef %int
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSwitch %undef %then 0 %then 1 %then 2 %else
%then = OpLabel
OpReturn
%else = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
}
TEST_F(ValidateCFG, MissingMergeOneBranchToMergeGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %b3 None
OpBranchConditional %undef %b1 %b2
%b1 = OpLabel
OpBranchConditional %undef %b2 %b3
%b2 = OpLabel
OpBranch %b3
%b3 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeSameTargetConditionalBranchGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranchConditional %undef %then %then
%then = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeOneTargetSwitchGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%undef = OpUndef %int
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSwitch %undef %then 0 %then 1 %then
%then = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeOneUnseenTargetSwitchGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%undef_int = OpUndef %int
%bool = OpTypeBool
%undef_bool = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %merge None
OpBranchConditional %undef_bool %merge %b1
%b1 = OpLabel
OpSwitch %undef_int %b2 0 %b2 1 %merge 2 %b2
%b2 = OpLabel
OpBranch %merge
%merge = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeLoopBreakGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranch %loop
%loop = OpLabel
OpLoopMerge %exit %continue None
OpBranch %body
%body = OpLabel
OpBranchConditional %undef %body2 %exit
%body2 = OpLabel
OpBranch %continue
%continue = OpLabel
OpBranch %loop
%exit = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeLoopContinueGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranch %loop
%loop = OpLabel
OpLoopMerge %exit %continue None
OpBranch %body
%body = OpLabel
OpBranchConditional %undef %body2 %continue
%body2 = OpLabel
OpBranch %continue
%continue = OpLabel
OpBranch %loop
%exit = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeSwitchBreakGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %merge None
OpSwitch %int_0 %merge 1 %b1
%b1 = OpLabel
OpBranchConditional %undef %merge %b2
%b2 = OpLabel
OpBranch %merge
%merge = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeSwitchFallThroughGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %merge None
OpSwitch %int_0 %b1 1 %b2
%b1 = OpLabel
OpBranchConditional %undef %b3 %b2
%b2 = OpLabel
OpBranch %merge
%b3 = OpLabel
OpBranch %merge
%merge = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, MissingMergeInALoopBad) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpBranch %loop
%loop = OpLabel
OpLoopMerge %exit %continue None
OpBranch %body
%body = OpLabel
OpBranchConditional %undef %b1 %b2
%b1 = OpLabel
OpBranch %exit
%b2 = OpLabel
OpBranch %continue
%continue = OpLabel
OpBranch %loop
%exit = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
}
TEST_F(ValidateCFG, MissingMergeCrissCrossBad) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%undef = OpUndef %bool
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpSelectionMerge %merge None
OpBranchConditional %undef %b1 %b2
%b1 = OpLabel
OpBranchConditional %undef %b3 %b4
%b2 = OpLabel
OpBranchConditional %undef %b3 %b4
%b3 = OpLabel
OpBranch %merge
%b4 = OpLabel
OpBranch %merge
%merge = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
}
} // namespace
} // namespace val

View File

@@ -144,7 +144,8 @@ OpFunctionEnd)";
EXPECT_THAT(getDiagnosticString(), HasSubstr("vector of two components"));
}
TEST_F(ValidateMisc, ShaderClockExecutionScope) {
// #2952: disabled until scope discussion is resolved.
TEST_F(ValidateMisc, DISABLED_ShaderClockExecutionScope) {
const std::string spirv = ShaderClockSpriv + R"(
%3 = OpTypeFunction %void
%ulong = OpTypeInt 64 0