mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-24 16:51:06 +00:00
spirv-fuzz: Use component-wise selectors when flattening conditional branches (#3921)
Fixes #3920.
This commit is contained in:
parent
ebe0ea09f0
commit
8362eae552
@ -72,6 +72,107 @@ void FuzzerPassFlattenConditionalBranches::Apply() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t convergence_block_id =
|
||||||
|
TransformationFlattenConditionalBranch::FindConvergenceBlock(
|
||||||
|
GetIRContext(), *header);
|
||||||
|
|
||||||
|
// If the SPIR-V version is restricted so that OpSelect can only work on
|
||||||
|
// scalar, pointer and vector types then we cannot apply this
|
||||||
|
// transformation to a header whose convergence block features OpPhi
|
||||||
|
// instructions on different types, as we cannot convert such instructions
|
||||||
|
// to OpSelect instructions.
|
||||||
|
if (TransformationFlattenConditionalBranch::
|
||||||
|
OpSelectArgumentsAreRestricted(GetIRContext())) {
|
||||||
|
if (!GetIRContext()
|
||||||
|
->cfg()
|
||||||
|
->block(convergence_block_id)
|
||||||
|
->WhileEachPhiInst(
|
||||||
|
[this](opt::Instruction* phi_instruction) -> bool {
|
||||||
|
switch (GetIRContext()
|
||||||
|
->get_def_use_mgr()
|
||||||
|
->GetDef(phi_instruction->type_id())
|
||||||
|
->opcode()) {
|
||||||
|
case SpvOpTypeBool:
|
||||||
|
case SpvOpTypeInt:
|
||||||
|
case SpvOpTypeFloat:
|
||||||
|
case SpvOpTypePointer:
|
||||||
|
case SpvOpTypeVector:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
// An OpPhi is performed on a type not supported by OpSelect; we
|
||||||
|
// cannot flatten this selection.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the construct's convergence block features OpPhi instructions with
|
||||||
|
// vector result types then we may be *forced*, by the SPIR-V version, to
|
||||||
|
// turn these into component-wise OpSelect instructions, or we might wish
|
||||||
|
// to do so anyway. The following booleans capture whether we will opt
|
||||||
|
// to use a component-wise select even if we don't have to.
|
||||||
|
bool use_component_wise_2d_select_even_if_optional =
|
||||||
|
GetFuzzerContext()->ChooseEven();
|
||||||
|
bool use_component_wise_3d_select_even_if_optional =
|
||||||
|
GetFuzzerContext()->ChooseEven();
|
||||||
|
bool use_component_wise_4d_select_even_if_optional =
|
||||||
|
GetFuzzerContext()->ChooseEven();
|
||||||
|
|
||||||
|
// If we do need to perform any component-wise selections, we will need a
|
||||||
|
// fresh id for a boolean vector representing the selection's condition
|
||||||
|
// repeated N times, where N is the vector dimension.
|
||||||
|
uint32_t fresh_id_for_bvec2_selector = 0;
|
||||||
|
uint32_t fresh_id_for_bvec3_selector = 0;
|
||||||
|
uint32_t fresh_id_for_bvec4_selector = 0;
|
||||||
|
|
||||||
|
GetIRContext()
|
||||||
|
->cfg()
|
||||||
|
->block(convergence_block_id)
|
||||||
|
->ForEachPhiInst([this, &fresh_id_for_bvec2_selector,
|
||||||
|
&fresh_id_for_bvec3_selector,
|
||||||
|
&fresh_id_for_bvec4_selector,
|
||||||
|
use_component_wise_2d_select_even_if_optional,
|
||||||
|
use_component_wise_3d_select_even_if_optional,
|
||||||
|
use_component_wise_4d_select_even_if_optional](
|
||||||
|
opt::Instruction* phi_instruction) {
|
||||||
|
opt::Instruction* type_instruction =
|
||||||
|
GetIRContext()->get_def_use_mgr()->GetDef(
|
||||||
|
phi_instruction->type_id());
|
||||||
|
switch (type_instruction->opcode()) {
|
||||||
|
case SpvOpTypeVector: {
|
||||||
|
uint32_t dimension =
|
||||||
|
type_instruction->GetSingleWordInOperand(1);
|
||||||
|
switch (dimension) {
|
||||||
|
case 2:
|
||||||
|
PrepareForOpPhiOnVectors(
|
||||||
|
dimension,
|
||||||
|
use_component_wise_2d_select_even_if_optional,
|
||||||
|
&fresh_id_for_bvec2_selector);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
PrepareForOpPhiOnVectors(
|
||||||
|
dimension,
|
||||||
|
use_component_wise_3d_select_even_if_optional,
|
||||||
|
&fresh_id_for_bvec3_selector);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
PrepareForOpPhiOnVectors(
|
||||||
|
dimension,
|
||||||
|
use_component_wise_4d_select_even_if_optional,
|
||||||
|
&fresh_id_for_bvec4_selector);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "Invalid vector dimension.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Some instructions will require to be enclosed inside conditionals
|
// Some instructions will require to be enclosed inside conditionals
|
||||||
// because they have side effects (for example, loads and stores). Some of
|
// because they have side effects (for example, loads and stores). Some of
|
||||||
// this have no result id, so we require instruction descriptors to
|
// this have no result id, so we require instruction descriptors to
|
||||||
@ -116,10 +217,31 @@ void FuzzerPassFlattenConditionalBranches::Apply() {
|
|||||||
// Apply the transformation, evenly choosing whether to lay out the true
|
// Apply the transformation, evenly choosing whether to lay out the true
|
||||||
// branch or the false branch first.
|
// branch or the false branch first.
|
||||||
ApplyTransformation(TransformationFlattenConditionalBranch(
|
ApplyTransformation(TransformationFlattenConditionalBranch(
|
||||||
header->id(), GetFuzzerContext()->ChooseEven(), wrappers_info));
|
header->id(), GetFuzzerContext()->ChooseEven(),
|
||||||
|
fresh_id_for_bvec2_selector, fresh_id_for_bvec3_selector,
|
||||||
|
fresh_id_for_bvec4_selector, wrappers_info));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FuzzerPassFlattenConditionalBranches::PrepareForOpPhiOnVectors(
|
||||||
|
uint32_t vector_dimension, bool use_vector_select_if_optional,
|
||||||
|
uint32_t* fresh_id_for_bvec_selector) {
|
||||||
|
if (*fresh_id_for_bvec_selector != 0) {
|
||||||
|
// We already have a fresh id for a component-wise OpSelect of this
|
||||||
|
// dimension
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
|
||||||
|
GetIRContext()) ||
|
||||||
|
use_vector_select_if_optional) {
|
||||||
|
// We either have to, or have chosen to, perform a component-wise select, so
|
||||||
|
// we ensure that the right boolean vector type is available, and grab a
|
||||||
|
// fresh id.
|
||||||
|
FindOrCreateVectorType(FindOrCreateBoolType(), vector_dimension);
|
||||||
|
*fresh_id_for_bvec_selector = GetFuzzerContext()->GetFreshId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
@ -30,6 +30,16 @@ class FuzzerPassFlattenConditionalBranches : public FuzzerPass {
|
|||||||
~FuzzerPassFlattenConditionalBranches() override;
|
~FuzzerPassFlattenConditionalBranches() override;
|
||||||
|
|
||||||
void Apply() override;
|
void Apply() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// If the SPIR-V version requires vector OpSelects to be component-wise, or
|
||||||
|
// if |use_vector_select_if_optional| holds, |fresh_id_for_bvec_selector| is
|
||||||
|
// populated with a fresh id if it is currently zero, and a
|
||||||
|
// |vector_dimension|-dimensional boolean vector type is added to the module
|
||||||
|
// if not already present.
|
||||||
|
void PrepareForOpPhiOnVectors(uint32_t vector_dimension,
|
||||||
|
bool use_vector_select_if_optional,
|
||||||
|
uint32_t* fresh_id_for_bvec_selector);
|
||||||
};
|
};
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
@ -65,6 +65,16 @@ void FuzzerPassReplaceOpSelectsWithConditionalBranches::Apply() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the selector does not have scalar boolean type (i.e., it is a
|
||||||
|
// boolean vector) then ignore this OpSelect.
|
||||||
|
if (GetIRContext()
|
||||||
|
->get_def_use_mgr()
|
||||||
|
->GetDef(fuzzerutil::GetTypeId(
|
||||||
|
GetIRContext(), instruction.GetSingleWordInOperand(0)))
|
||||||
|
->opcode() != SpvOpTypeBool) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If the block is a loop header and we need to split it, the
|
// If the block is a loop header and we need to split it, the
|
||||||
// transformation cannot be applied because loop headers cannot be
|
// transformation cannot be applied because loop headers cannot be
|
||||||
// split. We can break out of this loop because the transformation can
|
// split. We can break out of this loop because the transformation can
|
||||||
|
@ -1468,10 +1468,23 @@ message TransformationFlattenConditionalBranch {
|
|||||||
// field is true.
|
// field is true.
|
||||||
bool true_branch_first = 2;
|
bool true_branch_first = 2;
|
||||||
|
|
||||||
|
// If the convergence block contains an OpPhi with bvec2 result type, it may
|
||||||
|
// be necessary to introduce a bvec2 with the selection construct's condition
|
||||||
|
// in both components in order to turn the OpPhi into an OpSelect. This
|
||||||
|
// this field provides a fresh id for an OpCompositeConstruct instruction for
|
||||||
|
// this purpose. It should be set to 0 if no such instruction is required.
|
||||||
|
uint32 fresh_id_for_bvec2_selector = 3;
|
||||||
|
|
||||||
|
// The same as |fresh_id_for_bvec2_selector| but for the bvec3 case.
|
||||||
|
uint32 fresh_id_for_bvec3_selector = 4;
|
||||||
|
|
||||||
|
// The same as |fresh_id_for_bvec2_selector| but for the bvec4 case.
|
||||||
|
uint32 fresh_id_for_bvec4_selector = 5;
|
||||||
|
|
||||||
// A list of instructions with side effects, which must be enclosed
|
// A list of instructions with side effects, which must be enclosed
|
||||||
// inside smaller conditionals before flattening the main one, and
|
// inside smaller conditionals before flattening the main one, and
|
||||||
// the corresponding fresh ids and module ids needed.
|
// the corresponding fresh ids and module ids needed.
|
||||||
repeated SideEffectWrapperInfo side_effect_wrapper_info = 3;
|
repeated SideEffectWrapperInfo side_effect_wrapper_info = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TransformationFunctionCall {
|
message TransformationFunctionCall {
|
||||||
|
@ -26,10 +26,15 @@ TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
|
|||||||
|
|
||||||
TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
|
TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
|
||||||
uint32_t header_block_id, bool true_branch_first,
|
uint32_t header_block_id, bool true_branch_first,
|
||||||
|
uint32_t fresh_id_for_bvec2_selector, uint32_t fresh_id_for_bvec3_selector,
|
||||||
|
uint32_t fresh_id_for_bvec4_selector,
|
||||||
const std::vector<protobufs::SideEffectWrapperInfo>&
|
const std::vector<protobufs::SideEffectWrapperInfo>&
|
||||||
side_effect_wrappers_info) {
|
side_effect_wrappers_info) {
|
||||||
message_.set_header_block_id(header_block_id);
|
message_.set_header_block_id(header_block_id);
|
||||||
message_.set_true_branch_first(true_branch_first);
|
message_.set_true_branch_first(true_branch_first);
|
||||||
|
message_.set_fresh_id_for_bvec2_selector(fresh_id_for_bvec2_selector);
|
||||||
|
message_.set_fresh_id_for_bvec3_selector(fresh_id_for_bvec3_selector);
|
||||||
|
message_.set_fresh_id_for_bvec4_selector(fresh_id_for_bvec4_selector);
|
||||||
for (auto const& side_effect_wrapper_info : side_effect_wrappers_info) {
|
for (auto const& side_effect_wrapper_info : side_effect_wrappers_info) {
|
||||||
*message_.add_side_effect_wrapper_info() = side_effect_wrapper_info;
|
*message_.add_side_effect_wrapper_info() = side_effect_wrapper_info;
|
||||||
}
|
}
|
||||||
@ -38,8 +43,8 @@ TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
|
|||||||
bool TransformationFlattenConditionalBranch::IsApplicable(
|
bool TransformationFlattenConditionalBranch::IsApplicable(
|
||||||
opt::IRContext* ir_context,
|
opt::IRContext* ir_context,
|
||||||
const TransformationContext& transformation_context) const {
|
const TransformationContext& transformation_context) const {
|
||||||
uint32_t header_block_id = message_.header_block_id();
|
auto header_block =
|
||||||
auto header_block = fuzzerutil::MaybeFindBlock(ir_context, header_block_id);
|
fuzzerutil::MaybeFindBlock(ir_context, message_.header_block_id());
|
||||||
|
|
||||||
// The block must have been found and it must be a selection header.
|
// The block must have been found and it must be a selection header.
|
||||||
if (!header_block || !header_block->GetMergeInst() ||
|
if (!header_block || !header_block->GetMergeInst() ||
|
||||||
@ -52,6 +57,81 @@ bool TransformationFlattenConditionalBranch::IsApplicable(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<uint32_t> used_fresh_ids;
|
||||||
|
|
||||||
|
// If ids have been provided to be used as vector guards for OpSelect
|
||||||
|
// instructions then they must be fresh.
|
||||||
|
for (uint32_t fresh_id_for_bvec_selector :
|
||||||
|
{message_.fresh_id_for_bvec2_selector(),
|
||||||
|
message_.fresh_id_for_bvec3_selector(),
|
||||||
|
message_.fresh_id_for_bvec4_selector()}) {
|
||||||
|
if (fresh_id_for_bvec_selector != 0) {
|
||||||
|
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
|
||||||
|
fresh_id_for_bvec_selector, ir_context, &used_fresh_ids)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OpSelectArgumentsAreRestricted(ir_context)) {
|
||||||
|
// OpPhi instructions at the convergence block for the selection are handled
|
||||||
|
// by turning them into OpSelect instructions. As the SPIR-V version in use
|
||||||
|
// has restrictions on the arguments that OpSelect can take, we must check
|
||||||
|
// that any OpPhi instructions are compatible with these restrictions.
|
||||||
|
uint32_t convergence_block_id =
|
||||||
|
FindConvergenceBlock(ir_context, *header_block);
|
||||||
|
// Consider every OpPhi instruction at the convergence block.
|
||||||
|
if (!ir_context->cfg()
|
||||||
|
->block(convergence_block_id)
|
||||||
|
->WhileEachPhiInst([this,
|
||||||
|
ir_context](opt::Instruction* inst) -> bool {
|
||||||
|
// Decide whether the OpPhi can be handled based on its result
|
||||||
|
// type.
|
||||||
|
opt::Instruction* phi_result_type =
|
||||||
|
ir_context->get_def_use_mgr()->GetDef(inst->type_id());
|
||||||
|
switch (phi_result_type->opcode()) {
|
||||||
|
case SpvOpTypeBool:
|
||||||
|
case SpvOpTypeInt:
|
||||||
|
case SpvOpTypeFloat:
|
||||||
|
case SpvOpTypePointer:
|
||||||
|
// Fine: OpSelect can work directly on scalar and pointer
|
||||||
|
// types.
|
||||||
|
return true;
|
||||||
|
case SpvOpTypeVector: {
|
||||||
|
// In its restricted form, OpSelect can only select between
|
||||||
|
// vectors if the condition of the select is a boolean
|
||||||
|
// boolean vector. We thus require the appropriate boolean
|
||||||
|
// vector type to be present.
|
||||||
|
uint32_t bool_type_id =
|
||||||
|
fuzzerutil::MaybeGetBoolType(ir_context);
|
||||||
|
uint32_t dimension =
|
||||||
|
phi_result_type->GetSingleWordInOperand(1);
|
||||||
|
if (fuzzerutil::MaybeGetVectorType(ir_context, bool_type_id,
|
||||||
|
dimension) == 0) {
|
||||||
|
// The required boolean vector type is not present.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The transformation needs to be equipped with a fresh id
|
||||||
|
// in which to store the vectorized version of the selection
|
||||||
|
// construct's condition.
|
||||||
|
switch (dimension) {
|
||||||
|
case 2:
|
||||||
|
return message_.fresh_id_for_bvec2_selector() != 0;
|
||||||
|
case 3:
|
||||||
|
return message_.fresh_id_for_bvec3_selector() != 0;
|
||||||
|
default:
|
||||||
|
assert(dimension == 4 && "Invalid vector dimension.");
|
||||||
|
return message_.fresh_id_for_bvec4_selector() != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use a set to keep track of the instructions that require fresh ids.
|
// Use a set to keep track of the instructions that require fresh ids.
|
||||||
std::set<opt::Instruction*> instructions_that_need_ids;
|
std::set<opt::Instruction*> instructions_that_need_ids;
|
||||||
|
|
||||||
@ -68,8 +148,6 @@ bool TransformationFlattenConditionalBranch::IsApplicable(
|
|||||||
auto insts_to_wrapper_info = GetInstructionsToWrapperInfo(ir_context);
|
auto insts_to_wrapper_info = GetInstructionsToWrapperInfo(ir_context);
|
||||||
|
|
||||||
{
|
{
|
||||||
std::set<uint32_t> used_fresh_ids;
|
|
||||||
|
|
||||||
// Check the ids in the map.
|
// Check the ids in the map.
|
||||||
for (const auto& inst_to_info : insts_to_wrapper_info) {
|
for (const auto& inst_to_info : insts_to_wrapper_info) {
|
||||||
// Check the fresh ids needed for all of the instructions that need to be
|
// Check the fresh ids needed for all of the instructions that need to be
|
||||||
@ -125,28 +203,6 @@ bool TransformationFlattenConditionalBranch::IsApplicable(
|
|||||||
void TransformationFlattenConditionalBranch::Apply(
|
void TransformationFlattenConditionalBranch::Apply(
|
||||||
opt::IRContext* ir_context,
|
opt::IRContext* ir_context,
|
||||||
TransformationContext* transformation_context) const {
|
TransformationContext* transformation_context) const {
|
||||||
uint32_t header_block_id = message_.header_block_id();
|
|
||||||
auto header_block = ir_context->cfg()->block(header_block_id);
|
|
||||||
|
|
||||||
// Find the first block where flow converges (it is not necessarily the merge
|
|
||||||
// block) by walking the true branch until reaching a block that
|
|
||||||
// post-dominates the header.
|
|
||||||
// This is necessary because a potential common set of blocks at the end of
|
|
||||||
// the construct should not be duplicated.
|
|
||||||
uint32_t convergence_block_id =
|
|
||||||
header_block->terminator()->GetSingleWordInOperand(1);
|
|
||||||
auto postdominator_analysis =
|
|
||||||
ir_context->GetPostDominatorAnalysis(header_block->GetParent());
|
|
||||||
while (!postdominator_analysis->Dominates(convergence_block_id,
|
|
||||||
header_block_id)) {
|
|
||||||
auto current_block = ir_context->get_instr_block(convergence_block_id);
|
|
||||||
// If the transformation is applicable, the terminator is OpBranch.
|
|
||||||
convergence_block_id =
|
|
||||||
current_block->terminator()->GetSingleWordInOperand(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto branch_instruction = header_block->terminator();
|
|
||||||
|
|
||||||
// branch = 1 corresponds to the true branch, branch = 2 corresponds to the
|
// branch = 1 corresponds to the true branch, branch = 2 corresponds to the
|
||||||
// false branch. If the true branch is to be laid out first, we need to visit
|
// false branch. If the true branch is to be laid out first, we need to visit
|
||||||
// the false branch first, because each branch is moved to right after the
|
// the false branch first, because each branch is moved to right after the
|
||||||
@ -158,58 +214,27 @@ void TransformationFlattenConditionalBranch::Apply(
|
|||||||
branches = {1, 2};
|
branches = {1, 2};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto header_block = ir_context->cfg()->block(message_.header_block_id());
|
||||||
|
|
||||||
// Get the ids of the starting blocks of the first and last branches to be
|
// Get the ids of the starting blocks of the first and last branches to be
|
||||||
// laid out. The first branch is the true branch iff
|
// laid out. The first branch is the true branch iff
|
||||||
// |message_.true_branch_first| is true.
|
// |message_.true_branch_first| is true.
|
||||||
|
auto branch_instruction = header_block->terminator();
|
||||||
uint32_t first_block_first_branch_id =
|
uint32_t first_block_first_branch_id =
|
||||||
branch_instruction->GetSingleWordInOperand(branches[1]);
|
branch_instruction->GetSingleWordInOperand(branches[1]);
|
||||||
uint32_t first_block_last_branch_id =
|
uint32_t first_block_last_branch_id =
|
||||||
branch_instruction->GetSingleWordInOperand(branches[0]);
|
branch_instruction->GetSingleWordInOperand(branches[0]);
|
||||||
|
|
||||||
|
uint32_t convergence_block_id =
|
||||||
|
FindConvergenceBlock(ir_context, *header_block);
|
||||||
|
|
||||||
// If the OpBranchConditional instruction in the header branches to the same
|
// If the OpBranchConditional instruction in the header branches to the same
|
||||||
// block for both values of the condition, this is the convergence block (the
|
// block for both values of the condition, this is the convergence block (the
|
||||||
// flow does not actually diverge) and the OpPhi instructions in it are still
|
// flow does not actually diverge) and the OpPhi instructions in it are still
|
||||||
// valid, so we do not need to make any changes.
|
// valid, so we do not need to make any changes.
|
||||||
if (first_block_first_branch_id != first_block_last_branch_id) {
|
if (first_block_first_branch_id != first_block_last_branch_id) {
|
||||||
// Replace all of the current OpPhi instructions in the convergence block
|
RewriteOpPhiInstructionsAtConvergenceBlock(
|
||||||
// with OpSelect.
|
*header_block, convergence_block_id, ir_context);
|
||||||
ir_context->get_instr_block(convergence_block_id)
|
|
||||||
->ForEachPhiInst([branch_instruction, header_block,
|
|
||||||
ir_context](opt::Instruction* phi_inst) {
|
|
||||||
assert(phi_inst->NumInOperands() == 4 &&
|
|
||||||
"We are going to replace an OpPhi with an OpSelect. This "
|
|
||||||
"only makes sense if the block has two distinct "
|
|
||||||
"predecessors.");
|
|
||||||
// The OpPhi takes values from two distinct predecessors. One
|
|
||||||
// predecessor is associated with the "true" path of the conditional
|
|
||||||
// we are flattening, the other with the "false" path, but these
|
|
||||||
// predecessors can appear in either order as operands to the OpPhi
|
|
||||||
// instruction.
|
|
||||||
|
|
||||||
std::vector<opt::Operand> operands;
|
|
||||||
operands.emplace_back(branch_instruction->GetInOperand(0));
|
|
||||||
|
|
||||||
uint32_t branch_instruction_true_block_id =
|
|
||||||
branch_instruction->GetSingleWordInOperand(1);
|
|
||||||
|
|
||||||
if (ir_context->GetDominatorAnalysis(header_block->GetParent())
|
|
||||||
->Dominates(branch_instruction_true_block_id,
|
|
||||||
phi_inst->GetSingleWordInOperand(1))) {
|
|
||||||
// The "true" branch is handled first in the OpPhi's operands; we
|
|
||||||
// thus provide operands to OpSelect in the same order that they
|
|
||||||
// appear in the OpPhi.
|
|
||||||
operands.emplace_back(phi_inst->GetInOperand(0));
|
|
||||||
operands.emplace_back(phi_inst->GetInOperand(2));
|
|
||||||
} else {
|
|
||||||
// The "false" branch is handled first in the OpPhi's operands; we
|
|
||||||
// thus provide operands to OpSelect in reverse of the order that
|
|
||||||
// they appear in the OpPhi.
|
|
||||||
operands.emplace_back(phi_inst->GetInOperand(2));
|
|
||||||
operands.emplace_back(phi_inst->GetInOperand(0));
|
|
||||||
}
|
|
||||||
phi_inst->SetOpcode(SpvOpSelect);
|
|
||||||
phi_inst->SetInOperands(std::move(operands));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the mapping from instructions to fresh ids.
|
// Get the mapping from instructions to fresh ids.
|
||||||
@ -732,7 +757,10 @@ bool TransformationFlattenConditionalBranch::InstructionCanBeHandled(
|
|||||||
|
|
||||||
std::unordered_set<uint32_t>
|
std::unordered_set<uint32_t>
|
||||||
TransformationFlattenConditionalBranch::GetFreshIds() const {
|
TransformationFlattenConditionalBranch::GetFreshIds() const {
|
||||||
std::unordered_set<uint32_t> result;
|
std::unordered_set<uint32_t> result = {
|
||||||
|
message_.fresh_id_for_bvec2_selector(),
|
||||||
|
message_.fresh_id_for_bvec3_selector(),
|
||||||
|
message_.fresh_id_for_bvec4_selector()};
|
||||||
for (auto& side_effect_wrapper_info : message_.side_effect_wrapper_info()) {
|
for (auto& side_effect_wrapper_info : message_.side_effect_wrapper_info()) {
|
||||||
result.insert(side_effect_wrapper_info.merge_block_id());
|
result.insert(side_effect_wrapper_info.merge_block_id());
|
||||||
result.insert(side_effect_wrapper_info.execute_block_id());
|
result.insert(side_effect_wrapper_info.execute_block_id());
|
||||||
@ -743,5 +771,166 @@ TransformationFlattenConditionalBranch::GetFreshIds() const {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t TransformationFlattenConditionalBranch::FindConvergenceBlock(
|
||||||
|
opt::IRContext* ir_context, const opt::BasicBlock& header_block) {
|
||||||
|
uint32_t result = header_block.terminator()->GetSingleWordInOperand(1);
|
||||||
|
auto postdominator_analysis =
|
||||||
|
ir_context->GetPostDominatorAnalysis(header_block.GetParent());
|
||||||
|
while (!postdominator_analysis->Dominates(result, header_block.id())) {
|
||||||
|
auto current_block = ir_context->get_instr_block(result);
|
||||||
|
// If the transformation is applicable, the terminator is OpBranch.
|
||||||
|
result = current_block->terminator()->GetSingleWordInOperand(0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
|
||||||
|
opt::IRContext* ir_context) {
|
||||||
|
switch (ir_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 true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformationFlattenConditionalBranch::AddBooleanVectorConstructorToBlock(
|
||||||
|
uint32_t fresh_id, uint32_t dimension,
|
||||||
|
const opt::Operand& branch_condition_operand, opt::IRContext* ir_context,
|
||||||
|
opt::BasicBlock* block) const {
|
||||||
|
opt::Instruction::OperandList in_operands;
|
||||||
|
for (uint32_t i = 0; i < dimension; i++) {
|
||||||
|
in_operands.emplace_back(branch_condition_operand);
|
||||||
|
}
|
||||||
|
block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
|
||||||
|
ir_context, SpvOpCompositeConstruct,
|
||||||
|
fuzzerutil::MaybeGetVectorType(
|
||||||
|
ir_context, fuzzerutil::MaybeGetBoolType(ir_context), dimension),
|
||||||
|
fresh_id, in_operands));
|
||||||
|
fuzzerutil::UpdateModuleIdBound(ir_context, fresh_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformationFlattenConditionalBranch::
|
||||||
|
RewriteOpPhiInstructionsAtConvergenceBlock(
|
||||||
|
const opt::BasicBlock& header_block, uint32_t convergence_block_id,
|
||||||
|
opt::IRContext* ir_context) const {
|
||||||
|
const opt::Instruction& branch_instruction = *header_block.terminator();
|
||||||
|
|
||||||
|
const opt::Operand& branch_condition_operand =
|
||||||
|
branch_instruction.GetInOperand(0);
|
||||||
|
|
||||||
|
// If we encounter OpPhi instructions on vector types then we may need to
|
||||||
|
// introduce vector versions of the selection construct's condition to use
|
||||||
|
// in corresponding OpSelect instructions. These booleans track whether we
|
||||||
|
// need to introduce such boolean vectors.
|
||||||
|
bool require_2d_boolean_vector = false;
|
||||||
|
bool require_3d_boolean_vector = false;
|
||||||
|
bool require_4d_boolean_vector = false;
|
||||||
|
|
||||||
|
// Consider every OpPhi instruction at the convergence block.
|
||||||
|
opt::BasicBlock* convergence_block =
|
||||||
|
ir_context->get_instr_block(convergence_block_id);
|
||||||
|
convergence_block->ForEachPhiInst(
|
||||||
|
[this, &branch_condition_operand, branch_instruction, &header_block,
|
||||||
|
ir_context, &require_2d_boolean_vector, &require_3d_boolean_vector,
|
||||||
|
&require_4d_boolean_vector](opt::Instruction* phi_inst) {
|
||||||
|
assert(phi_inst->NumInOperands() == 4 &&
|
||||||
|
"We are going to replace an OpPhi with an OpSelect. This "
|
||||||
|
"only makes sense if the block has two distinct "
|
||||||
|
"predecessors.");
|
||||||
|
// We are going to replace the OpPhi with an OpSelect. By default,
|
||||||
|
// the condition for the OpSelect will be the branch condition's
|
||||||
|
// operand. However, if the OpPhi has vector result type we may need
|
||||||
|
// to use a boolean vector as the condition instead.
|
||||||
|
opt::Operand selector_operand = branch_condition_operand;
|
||||||
|
opt::Instruction* type_inst =
|
||||||
|
ir_context->get_def_use_mgr()->GetDef(phi_inst->type_id());
|
||||||
|
if (type_inst->opcode() == SpvOpTypeVector) {
|
||||||
|
uint32_t dimension = type_inst->GetSingleWordInOperand(1);
|
||||||
|
switch (dimension) {
|
||||||
|
case 2:
|
||||||
|
// The OpPhi's result type is a 2D vector. If a fresh id for a
|
||||||
|
// bvec2 selector was provided then we should use it as the
|
||||||
|
// OpSelect's condition, and note the fact that we will need to
|
||||||
|
// add an instruction to bring this bvec2 into existence.
|
||||||
|
if (message_.fresh_id_for_bvec2_selector() != 0) {
|
||||||
|
selector_operand = {SPV_OPERAND_TYPE_TYPE_ID,
|
||||||
|
{message_.fresh_id_for_bvec2_selector()}};
|
||||||
|
require_2d_boolean_vector = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Similar to the 2D case.
|
||||||
|
if (message_.fresh_id_for_bvec3_selector() != 0) {
|
||||||
|
selector_operand = {SPV_OPERAND_TYPE_TYPE_ID,
|
||||||
|
{message_.fresh_id_for_bvec3_selector()}};
|
||||||
|
require_3d_boolean_vector = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(dimension == 4 && "Invalid vector dimension.");
|
||||||
|
// Similar to the 2D case.
|
||||||
|
if (message_.fresh_id_for_bvec4_selector() != 0) {
|
||||||
|
selector_operand = {SPV_OPERAND_TYPE_TYPE_ID,
|
||||||
|
{message_.fresh_id_for_bvec4_selector()}};
|
||||||
|
require_4d_boolean_vector = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<opt::Operand> operands;
|
||||||
|
operands.emplace_back(selector_operand);
|
||||||
|
|
||||||
|
uint32_t branch_instruction_true_block_id =
|
||||||
|
branch_instruction.GetSingleWordInOperand(1);
|
||||||
|
|
||||||
|
// The OpPhi takes values from two distinct predecessors. One
|
||||||
|
// predecessor is associated with the "true" path of the conditional
|
||||||
|
// we are flattening, the other with the "false" path, but these
|
||||||
|
// predecessors can appear in either order as operands to the OpPhi
|
||||||
|
// instruction. We determine in which order the OpPhi inputs should
|
||||||
|
// appear as OpSelect arguments by checking dominance of the true and
|
||||||
|
// false immediate successors of the header block.
|
||||||
|
if (ir_context->GetDominatorAnalysis(header_block.GetParent())
|
||||||
|
->Dominates(branch_instruction_true_block_id,
|
||||||
|
phi_inst->GetSingleWordInOperand(1))) {
|
||||||
|
// The "true" branch is handled first in the OpPhi's operands; we
|
||||||
|
// thus provide operands to OpSelect in the same order that they
|
||||||
|
// appear in the OpPhi.
|
||||||
|
operands.emplace_back(phi_inst->GetInOperand(0));
|
||||||
|
operands.emplace_back(phi_inst->GetInOperand(2));
|
||||||
|
} else {
|
||||||
|
// The "false" branch is handled first in the OpPhi's operands; we
|
||||||
|
// thus provide operands to OpSelect in reverse of the order that
|
||||||
|
// they appear in the OpPhi.
|
||||||
|
operands.emplace_back(phi_inst->GetInOperand(2));
|
||||||
|
operands.emplace_back(phi_inst->GetInOperand(0));
|
||||||
|
}
|
||||||
|
phi_inst->SetOpcode(SpvOpSelect);
|
||||||
|
phi_inst->SetInOperands(std::move(operands));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add boolean vector instructions to the start of the block as required.
|
||||||
|
if (require_2d_boolean_vector) {
|
||||||
|
AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec2_selector(),
|
||||||
|
2, branch_condition_operand, ir_context,
|
||||||
|
convergence_block);
|
||||||
|
}
|
||||||
|
if (require_3d_boolean_vector) {
|
||||||
|
AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec3_selector(),
|
||||||
|
3, branch_condition_operand, ir_context,
|
||||||
|
convergence_block);
|
||||||
|
}
|
||||||
|
if (require_4d_boolean_vector) {
|
||||||
|
AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec4_selector(),
|
||||||
|
4, branch_condition_operand, ir_context,
|
||||||
|
convergence_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
@ -27,6 +27,9 @@ class TransformationFlattenConditionalBranch : public Transformation {
|
|||||||
|
|
||||||
TransformationFlattenConditionalBranch(
|
TransformationFlattenConditionalBranch(
|
||||||
uint32_t header_block_id, bool true_branch_first,
|
uint32_t header_block_id, bool true_branch_first,
|
||||||
|
uint32_t fresh_id_for_bvec2_selector,
|
||||||
|
uint32_t fresh_id_for_bvec3_selector,
|
||||||
|
uint32_t fresh_id_for_bvec4_selector,
|
||||||
const std::vector<protobufs::SideEffectWrapperInfo>&
|
const std::vector<protobufs::SideEffectWrapperInfo>&
|
||||||
side_effect_wrappers_info);
|
side_effect_wrappers_info);
|
||||||
|
|
||||||
@ -81,6 +84,19 @@ class TransformationFlattenConditionalBranch : public Transformation {
|
|||||||
static bool InstructionNeedsPlaceholder(opt::IRContext* ir_context,
|
static bool InstructionNeedsPlaceholder(opt::IRContext* ir_context,
|
||||||
const opt::Instruction& instruction);
|
const opt::Instruction& instruction);
|
||||||
|
|
||||||
|
// Returns true if and only if the SPIR-V version is such that the arguments
|
||||||
|
// to OpSelect are restricted to only scalars, pointers (if the appropriate
|
||||||
|
// capability is enabled) and component-wise vectors.
|
||||||
|
static bool OpSelectArgumentsAreRestricted(opt::IRContext* ir_context);
|
||||||
|
|
||||||
|
// Find the first block where flow converges (it is not necessarily the merge
|
||||||
|
// block) by walking the true branch until reaching a block that post-
|
||||||
|
// dominates the header.
|
||||||
|
// This is necessary because a potential common set of blocks at the end of
|
||||||
|
// the construct should not be duplicated.
|
||||||
|
static uint32_t FindConvergenceBlock(opt::IRContext* ir_context,
|
||||||
|
const opt::BasicBlock& header_block);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Returns an unordered_map mapping instructions to the info required to
|
// Returns an unordered_map mapping instructions to the info required to
|
||||||
// enclose them inside a conditional. It maps the instructions to the
|
// enclose them inside a conditional. It maps the instructions to the
|
||||||
@ -108,6 +124,22 @@ class TransformationFlattenConditionalBranch : public Transformation {
|
|||||||
std::vector<uint32_t>* dead_blocks,
|
std::vector<uint32_t>* dead_blocks,
|
||||||
std::vector<uint32_t>* irrelevant_ids) const;
|
std::vector<uint32_t>* irrelevant_ids) const;
|
||||||
|
|
||||||
|
// Turns every OpPhi instruction of |convergence_block| -- the convergence
|
||||||
|
// block for |header_block| (both in |ir_context|) into an OpSelect
|
||||||
|
// instruction.
|
||||||
|
void RewriteOpPhiInstructionsAtConvergenceBlock(
|
||||||
|
const opt::BasicBlock& header_block, uint32_t convergence_block_id,
|
||||||
|
opt::IRContext* ir_context) const;
|
||||||
|
|
||||||
|
// Adds an OpCompositeExtract instruction to the start of |block| in
|
||||||
|
// |ir_context|, with result id given by |fresh_id|. The instruction will
|
||||||
|
// make a |dimension|-dimensional boolean vector with
|
||||||
|
// |branch_condition_operand| at every component.
|
||||||
|
void AddBooleanVectorConstructorToBlock(
|
||||||
|
uint32_t fresh_id, uint32_t dimension,
|
||||||
|
const opt::Operand& branch_condition_operand, opt::IRContext* ir_context,
|
||||||
|
opt::BasicBlock* block) const;
|
||||||
|
|
||||||
// Returns true if the given instruction either has no side effects or it can
|
// Returns true if the given instruction either has no side effects or it can
|
||||||
// be handled by being enclosed in a conditional.
|
// be handled by being enclosed in a conditional.
|
||||||
static bool InstructionCanBeHandled(opt::IRContext* ir_context,
|
static bool InstructionCanBeHandled(opt::IRContext* ir_context,
|
||||||
|
@ -145,42 +145,42 @@ TEST(TransformationFlattenConditionalBranchTest, Inapplicable) {
|
|||||||
TransformationContext transformation_context(
|
TransformationContext transformation_context(
|
||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
// Block %15 does not end with OpBranchConditional.
|
// Block %15 does not end with OpBranchConditional.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(15, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(15, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %17 is not a selection header.
|
// Block %17 is not a selection header.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(17, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(17, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %16 is a loop header, not a selection header.
|
// Block %16 is a loop header, not a selection header.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %19 and the corresponding merge block do not describe a single-entry,
|
// Block %19 and the corresponding merge block do not describe a single-entry,
|
||||||
// single-exit region, because there is a return instruction in %21.
|
// single-exit region, because there is a return instruction in %21.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(19, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(19, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %20 is the header of a construct containing an inner selection
|
// Block %20 is the header of a construct containing an inner selection
|
||||||
// construct.
|
// construct.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(20, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(20, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %22 is the header of a construct containing an inner loop.
|
// Block %22 is the header of a construct containing an inner loop.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(22, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(22, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %30 is the header of a construct containing a barrier instruction.
|
// Block %30 is the header of a construct containing a barrier instruction.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(30, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(30, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// %33 is not a block.
|
// %33 is not a block.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(33, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(33, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %36 and the corresponding merge block do not describe a single-entry,
|
// Block %36 and the corresponding merge block do not describe a single-entry,
|
||||||
// single-exit region, because block %37 breaks out of the outer loop.
|
// single-exit region, because block %37 breaks out of the outer loop.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(36, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(36, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,25 +250,29 @@ TEST(TransformationFlattenConditionalBranchTest, Simple) {
|
|||||||
kConsoleMessageConsumer));
|
kConsoleMessageConsumer));
|
||||||
TransformationContext transformation_context(
|
TransformationContext transformation_context(
|
||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
auto transformation1 = TransformationFlattenConditionalBranch(7, true, {});
|
auto transformation1 =
|
||||||
|
TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation1.IsApplicable(context.get(), transformation_context));
|
transformation1.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation1, context.get(),
|
ApplyAndCheckFreshIds(transformation1, context.get(),
|
||||||
&transformation_context);
|
&transformation_context);
|
||||||
|
|
||||||
auto transformation2 = TransformationFlattenConditionalBranch(13, false, {});
|
auto transformation2 =
|
||||||
|
TransformationFlattenConditionalBranch(13, false, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation2.IsApplicable(context.get(), transformation_context));
|
transformation2.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation2, context.get(),
|
ApplyAndCheckFreshIds(transformation2, context.get(),
|
||||||
&transformation_context);
|
&transformation_context);
|
||||||
|
|
||||||
auto transformation3 = TransformationFlattenConditionalBranch(15, true, {});
|
auto transformation3 =
|
||||||
|
TransformationFlattenConditionalBranch(15, true, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation3.IsApplicable(context.get(), transformation_context));
|
transformation3.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation3, context.get(),
|
ApplyAndCheckFreshIds(transformation3, context.get(),
|
||||||
&transformation_context);
|
&transformation_context);
|
||||||
|
|
||||||
auto transformation4 = TransformationFlattenConditionalBranch(22, false, {});
|
auto transformation4 =
|
||||||
|
TransformationFlattenConditionalBranch(22, false, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation4.IsApplicable(context.get(), transformation_context));
|
transformation4.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation4, context.get(),
|
ApplyAndCheckFreshIds(transformation4, context.get(),
|
||||||
@ -430,12 +434,12 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
// requiring fresh ids are not present in the map, and the transformation
|
// requiring fresh ids are not present in the map, and the transformation
|
||||||
// context does not have a source overflow ids.
|
// context does not have a source overflow ids.
|
||||||
|
|
||||||
ASSERT_DEATH(TransformationFlattenConditionalBranch(31, true, {})
|
ASSERT_DEATH(TransformationFlattenConditionalBranch(31, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context),
|
.IsApplicable(context.get(), transformation_context),
|
||||||
"Bad attempt to query whether overflow ids are available.");
|
"Bad attempt to query whether overflow ids are available.");
|
||||||
|
|
||||||
ASSERT_DEATH(TransformationFlattenConditionalBranch(
|
ASSERT_DEATH(TransformationFlattenConditionalBranch(
|
||||||
31, true,
|
31, true, 0, 0, 0,
|
||||||
{{MakeSideEffectWrapperInfo(
|
{{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101,
|
MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101,
|
||||||
102, 103, 104, 14)}})
|
102, 103, 104, 14)}})
|
||||||
@ -445,7 +449,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
|
|
||||||
// The map maps from an instruction to a list with not enough fresh ids.
|
// The map maps from an instruction to a list with not enough fresh ids.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
||||||
31, true,
|
31, true, 0, 0, 0,
|
||||||
{{MakeSideEffectWrapperInfo(
|
{{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101,
|
MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101,
|
||||||
102, 103, 0, 0)}})
|
102, 103, 0, 0)}})
|
||||||
@ -453,7 +457,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
|
|
||||||
// Not all fresh ids given are distinct.
|
// Not all fresh ids given are distinct.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
||||||
31, true,
|
31, true, 0, 0, 0,
|
||||||
{{MakeSideEffectWrapperInfo(
|
{{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 100,
|
MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 100,
|
||||||
102, 103, 104, 0)}})
|
102, 103, 104, 0)}})
|
||||||
@ -461,7 +465,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
|
|
||||||
// %48 heads a construct containing an OpSampledImage instruction.
|
// %48 heads a construct containing an OpSampledImage instruction.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
||||||
48, true,
|
48, true, 0, 0, 0,
|
||||||
{{MakeSideEffectWrapperInfo(
|
{{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(53, SpvOpLoad, 0), 100, 101,
|
MakeInstructionDescriptor(53, SpvOpLoad, 0), 100, 101,
|
||||||
102, 103, 104, 0)}})
|
102, 103, 104, 0)}})
|
||||||
@ -470,7 +474,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
// %0 is not a valid id.
|
// %0 is not a valid id.
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(
|
||||||
TransformationFlattenConditionalBranch(
|
TransformationFlattenConditionalBranch(
|
||||||
31, true,
|
31, true, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
|
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
|
||||||
104, 100, 101, 102, 103, 0),
|
104, 100, 101, 102, 103, 0),
|
||||||
MakeSideEffectWrapperInfo(
|
MakeSideEffectWrapperInfo(
|
||||||
@ -480,7 +484,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
// %17 is a float constant, while %6 has int type.
|
// %17 is a float constant, while %6 has int type.
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(
|
||||||
TransformationFlattenConditionalBranch(
|
TransformationFlattenConditionalBranch(
|
||||||
31, true,
|
31, true, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
|
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
|
||||||
104, 100, 101, 102, 103, 17),
|
104, 100, 101, 102, 103, 17),
|
||||||
MakeSideEffectWrapperInfo(
|
MakeSideEffectWrapperInfo(
|
||||||
@ -488,7 +492,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
auto transformation1 = TransformationFlattenConditionalBranch(
|
auto transformation1 = TransformationFlattenConditionalBranch(
|
||||||
31, true,
|
31, true, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
|
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
|
||||||
104, 100, 101, 102, 103, 70),
|
104, 100, 101, 102, 103, 70),
|
||||||
MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpStore, 0),
|
MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpStore, 0),
|
||||||
@ -509,7 +513,7 @@ TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
|
|||||||
std::move(overflow_ids_unique_ptr));
|
std::move(overflow_ids_unique_ptr));
|
||||||
|
|
||||||
auto transformation2 = TransformationFlattenConditionalBranch(
|
auto transformation2 = TransformationFlattenConditionalBranch(
|
||||||
36, false,
|
36, false, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(8, SpvOpStore, 0),
|
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(8, SpvOpStore, 0),
|
||||||
114, 113)});
|
114, 113)});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
@ -706,13 +710,13 @@ TEST(TransformationFlattenConditionalBranchTest, EdgeCases) {
|
|||||||
// The selection construct headed by %7 requires fresh ids because it contains
|
// The selection construct headed by %7 requires fresh ids because it contains
|
||||||
// a function call. This causes an assertion failure because transformation
|
// a function call. This causes an assertion failure because transformation
|
||||||
// context does not have a source of overflow ids.
|
// context does not have a source of overflow ids.
|
||||||
ASSERT_DEATH(TransformationFlattenConditionalBranch(7, true, {})
|
ASSERT_DEATH(TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context),
|
.IsApplicable(context.get(), transformation_context),
|
||||||
"Bad attempt to query whether overflow ids are available.");
|
"Bad attempt to query whether overflow ids are available.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto transformation1 = TransformationFlattenConditionalBranch(
|
auto transformation1 = TransformationFlattenConditionalBranch(
|
||||||
7, true,
|
7, true, 0, 0, 0,
|
||||||
{{MakeSideEffectWrapperInfo(
|
{{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(10, SpvOpFunctionCall, 0), 100, 101)}});
|
MakeInstructionDescriptor(10, SpvOpFunctionCall, 0), 100, 101)}});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
@ -724,16 +728,17 @@ TEST(TransformationFlattenConditionalBranchTest, EdgeCases) {
|
|||||||
// contains a function call returning void, whose result id is used.
|
// contains a function call returning void, whose result id is used.
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(
|
||||||
TransformationFlattenConditionalBranch(
|
TransformationFlattenConditionalBranch(
|
||||||
7, true,
|
7, true, 0, 0, 0,
|
||||||
{{MakeSideEffectWrapperInfo(
|
{{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(14, SpvOpFunctionCall, 0), 102, 103)}})
|
MakeInstructionDescriptor(14, SpvOpFunctionCall, 0), 102, 103)}})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
// Block %16 is unreachable.
|
// Block %16 is unreachable.
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, {})
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, 0, 0, 0, {})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
auto transformation2 = TransformationFlattenConditionalBranch(20, false, {});
|
auto transformation2 =
|
||||||
|
TransformationFlattenConditionalBranch(20, false, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation2.IsApplicable(context.get(), transformation_context));
|
transformation2.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation2, context.get(),
|
ApplyAndCheckFreshIds(transformation2, context.get(),
|
||||||
@ -837,7 +842,8 @@ TEST(TransformationFlattenConditionalBranchTest, PhiToSelect1) {
|
|||||||
TransformationContext transformation_context(
|
TransformationContext transformation_context(
|
||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
auto transformation = TransformationFlattenConditionalBranch(7, true, {});
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation.IsApplicable(context.get(), transformation_context));
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
@ -903,7 +909,8 @@ TEST(TransformationFlattenConditionalBranchTest, PhiToSelect2) {
|
|||||||
TransformationContext transformation_context(
|
TransformationContext transformation_context(
|
||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
auto transformation = TransformationFlattenConditionalBranch(7, true, {});
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation.IsApplicable(context.get(), transformation_context));
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
@ -971,7 +978,8 @@ TEST(TransformationFlattenConditionalBranchTest, PhiToSelect3) {
|
|||||||
TransformationContext transformation_context(
|
TransformationContext transformation_context(
|
||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
auto transformation = TransformationFlattenConditionalBranch(7, true, {});
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation.IsApplicable(context.get(), transformation_context));
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
@ -1041,7 +1049,8 @@ TEST(TransformationFlattenConditionalBranchTest, PhiToSelect4) {
|
|||||||
TransformationContext transformation_context(
|
TransformationContext transformation_context(
|
||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
auto transformation = TransformationFlattenConditionalBranch(7, true, {});
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
transformation.IsApplicable(context.get(), transformation_context));
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
@ -1118,7 +1127,7 @@ TEST(TransformationFlattenConditionalBranchTest, PhiToSelect5) {
|
|||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
auto transformation = TransformationFlattenConditionalBranch(
|
auto transformation = TransformationFlattenConditionalBranch(
|
||||||
7, true,
|
7, true, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(522, SpvOpLoad, 0),
|
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(522, SpvOpLoad, 0),
|
||||||
200, 201, 202, 203, 204, 5),
|
200, 201, 202, 203, 204, 5),
|
||||||
MakeSideEffectWrapperInfo(MakeInstructionDescriptor(466, SpvOpLoad, 0),
|
MakeSideEffectWrapperInfo(MakeInstructionDescriptor(466, SpvOpLoad, 0),
|
||||||
@ -1223,7 +1232,7 @@ TEST(TransformationFlattenConditionalBranchTest,
|
|||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
auto transformation = TransformationFlattenConditionalBranch(
|
auto transformation = TransformationFlattenConditionalBranch(
|
||||||
5, true,
|
5, true, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(20, SpvOpLoad, 0),
|
{MakeSideEffectWrapperInfo(MakeInstructionDescriptor(20, SpvOpLoad, 0),
|
||||||
100, 101, 102, 103, 104, 21)});
|
100, 101, 102, 103, 104, 21)});
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
@ -1290,13 +1299,553 @@ TEST(TransformationFlattenConditionalBranchTest, InapplicableSampledImageLoad) {
|
|||||||
MakeUnique<FactManager>(context.get()), validator_options);
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(
|
||||||
28, true,
|
28, true, 0, 0, 0,
|
||||||
{MakeSideEffectWrapperInfo(
|
{MakeSideEffectWrapperInfo(
|
||||||
MakeInstructionDescriptor(40, SpvOpLoad, 0), 100, 101,
|
MakeInstructionDescriptor(40, SpvOpLoad, 0), 100, 101,
|
||||||
102, 103, 104, 200)})
|
102, 103, 104, 200)})
|
||||||
.IsApplicable(context.get(), transformation_context));
|
.IsApplicable(context.get(), transformation_context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest,
|
||||||
|
InapplicablePhiToSelectVector) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
|
||||||
|
ASSERT_FALSE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest,
|
||||||
|
InapplicablePhiToSelectVector2) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%30 = OpTypeVector %6 3
|
||||||
|
%31 = OpTypeVector %6 2
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%40 = OpTypeFloat 32
|
||||||
|
%41 = OpTypeVector %40 4
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%60 = OpUndef %41
|
||||||
|
%61 = OpConstantComposite %31 %7 %7
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
%22 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
%23 = OpPhi %41 %60 %8 %60 %9
|
||||||
|
%24 = OpPhi %31 %61 %8 %61 %9
|
||||||
|
%25 = OpPhi %41 %60 %8 %60 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 101, 102, 103, {});
|
||||||
|
|
||||||
|
// bvec4 is not present in the module.
|
||||||
|
ASSERT_FALSE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest,
|
||||||
|
InapplicablePhiToSelectMatrix) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeFloat 32
|
||||||
|
%30 = OpTypeVector %10 3
|
||||||
|
%11 = OpTypeMatrix %30 3
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
|
||||||
|
ASSERT_FALSE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectVector) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
|
||||||
|
ASSERT_TRUE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
std::string expected_shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %8
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %9
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpSelect %11 %7 %12 %12
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectVector2) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%30 = OpTypeVector %6 3
|
||||||
|
%31 = OpTypeVector %6 2
|
||||||
|
%32 = OpTypeVector %6 4
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%40 = OpTypeFloat 32
|
||||||
|
%41 = OpTypeVector %40 4
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%60 = OpUndef %41
|
||||||
|
%61 = OpConstantComposite %31 %7 %7
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
%22 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
%23 = OpPhi %41 %60 %8 %60 %9
|
||||||
|
%24 = OpPhi %31 %61 %8 %61 %9
|
||||||
|
%25 = OpPhi %41 %60 %8 %60 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
// No id for the 2D vector case is provided.
|
||||||
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 0, 102, 103, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
// No id for the 3D vector case is provided.
|
||||||
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 0, 103, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
// No id for the 4D vector case is provided.
|
||||||
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 102, 0, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
// %10 is not fresh
|
||||||
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 10, 102, 103, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
// %10 is not fresh
|
||||||
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 10, 103, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
// %10 is not fresh
|
||||||
|
ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 102, 10, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
// Duplicate "fresh" ids used for boolean vector constructors
|
||||||
|
ASSERT_FALSE(
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 101, 102, 102, {})
|
||||||
|
.IsApplicable(context.get(), transformation_context));
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 101, 102, 103, {});
|
||||||
|
|
||||||
|
ASSERT_TRUE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
std::string expected_shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%30 = OpTypeVector %6 3
|
||||||
|
%31 = OpTypeVector %6 2
|
||||||
|
%32 = OpTypeVector %6 4
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%40 = OpTypeFloat 32
|
||||||
|
%41 = OpTypeVector %40 4
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%60 = OpUndef %41
|
||||||
|
%61 = OpConstantComposite %31 %7 %7
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %8
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %9
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%103 = OpCompositeConstruct %32 %7 %7 %7 %7
|
||||||
|
%102 = OpCompositeConstruct %30 %7 %7 %7
|
||||||
|
%101 = OpCompositeConstruct %31 %7 %7
|
||||||
|
%21 = OpSelect %11 %102 %12 %12
|
||||||
|
%22 = OpSelect %11 %102 %12 %12
|
||||||
|
%23 = OpSelect %41 %103 %60 %60
|
||||||
|
%24 = OpSelect %31 %101 %61 %61
|
||||||
|
%25 = OpSelect %41 %103 %60 %60
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectVector3) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%30 = OpTypeVector %6 3
|
||||||
|
%31 = OpTypeVector %6 2
|
||||||
|
%32 = OpTypeVector %6 4
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%40 = OpTypeFloat 32
|
||||||
|
%41 = OpTypeVector %40 4
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%60 = OpUndef %41
|
||||||
|
%61 = OpConstantComposite %31 %7 %7
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
%22 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
%23 = OpPhi %41 %60 %8 %60 %9
|
||||||
|
%24 = OpPhi %31 %61 %8 %61 %9
|
||||||
|
%25 = OpPhi %41 %60 %8 %60 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 101, 0, 103, {});
|
||||||
|
|
||||||
|
ASSERT_TRUE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
std::string expected_shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%30 = OpTypeVector %6 3
|
||||||
|
%31 = OpTypeVector %6 2
|
||||||
|
%32 = OpTypeVector %6 4
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeInt 32 1
|
||||||
|
%11 = OpTypeVector %10 3
|
||||||
|
%40 = OpTypeFloat 32
|
||||||
|
%41 = OpTypeVector %40 4
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%60 = OpUndef %41
|
||||||
|
%61 = OpConstantComposite %31 %7 %7
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %8
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %9
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%103 = OpCompositeConstruct %32 %7 %7 %7 %7
|
||||||
|
%101 = OpCompositeConstruct %31 %7 %7
|
||||||
|
%21 = OpSelect %11 %7 %12 %12
|
||||||
|
%22 = OpSelect %11 %7 %12 %12
|
||||||
|
%23 = OpSelect %41 %103 %60 %60
|
||||||
|
%24 = OpSelect %31 %101 %61 %61
|
||||||
|
%25 = OpSelect %41 %103 %60 %60
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectMatrix) {
|
||||||
|
std::string shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeFloat 32
|
||||||
|
%30 = OpTypeVector %10 3
|
||||||
|
%11 = OpTypeMatrix %30 3
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpSelectionMerge %20 None
|
||||||
|
OpBranchConditional %7 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpPhi %11 %12 %8 %12 %9
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||||
|
const auto consumer = nullptr;
|
||||||
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||||
|
spvtools::ValidatorOptions validator_options;
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
TransformationContext transformation_context(
|
||||||
|
MakeUnique<FactManager>(context.get()), validator_options);
|
||||||
|
|
||||||
|
auto transformation =
|
||||||
|
TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
|
||||||
|
ASSERT_TRUE(
|
||||||
|
transformation.IsApplicable(context.get(), transformation_context));
|
||||||
|
ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
|
||||||
|
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
|
||||||
|
kConsoleMessageConsumer));
|
||||||
|
|
||||||
|
std::string expected_shader = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %4 "main"
|
||||||
|
OpExecutionMode %4 OriginUpperLeft
|
||||||
|
OpSource ESSL 320
|
||||||
|
%2 = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %2
|
||||||
|
%6 = OpTypeBool
|
||||||
|
%7 = OpConstantTrue %6
|
||||||
|
%10 = OpTypeFloat 32
|
||||||
|
%30 = OpTypeVector %10 3
|
||||||
|
%11 = OpTypeMatrix %30 3
|
||||||
|
%12 = OpUndef %11
|
||||||
|
%4 = OpFunction %2 None %3
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %8
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %9
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %20
|
||||||
|
%20 = OpLabel
|
||||||
|
%21 = OpSelect %11 %7 %12 %12
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
Loading…
Reference in New Issue
Block a user