mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-02 07:40:08 +00:00
08291a3a9e
A transformation that adds new OpPhi instructions to blocks with >=1 predecessors, so that its value depends on previously-defined ids of the right type, which are all synonymous. This instruction is also recorded as synonymous to the others. The related fuzzer pass still needs to be implemented. Fixes #3592 .
201 lines
7.0 KiB
C++
201 lines
7.0 KiB
C++
// Copyright (c) 2020 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_opphi_synonym.h"
|
|
|
|
#include "source/fuzz/fuzzer_util.h"
|
|
|
|
namespace spvtools {
|
|
namespace fuzz {
|
|
TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
|
|
const protobufs::TransformationAddOpPhiSynonym& message)
|
|
: message_(message) {}
|
|
|
|
TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
|
|
uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
|
|
uint32_t fresh_id) {
|
|
message_.set_block_id(block_id);
|
|
*message_.mutable_pred_to_id() =
|
|
fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids);
|
|
message_.set_fresh_id(fresh_id);
|
|
}
|
|
|
|
bool TransformationAddOpPhiSynonym::IsApplicable(
|
|
opt::IRContext* ir_context,
|
|
const TransformationContext& transformation_context) const {
|
|
// Check that |message_.block_id| is a block label id.
|
|
auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
|
|
if (!block) {
|
|
return false;
|
|
}
|
|
|
|
// Check that |message_.fresh_id| is actually fresh.
|
|
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
|
|
return false;
|
|
}
|
|
|
|
// Check that |message_.pred_to_id| contains a mapping for all of the block's
|
|
// predecessors.
|
|
std::vector<uint32_t> predecessors = ir_context->cfg()->preds(block->id());
|
|
|
|
// There must be at least one predecessor.
|
|
if (predecessors.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::map<uint32_t, uint32_t> preds_to_ids =
|
|
fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id());
|
|
|
|
// There must not be repeated key values in |message_.pred_to_id|.
|
|
if (preds_to_ids.size() != static_cast<size_t>(message_.pred_to_id_size())) {
|
|
return false;
|
|
}
|
|
|
|
// Check that each predecessor has a corresponding mapping and all of the
|
|
// corresponding ids exist.
|
|
for (uint32_t pred : predecessors) {
|
|
if (preds_to_ids.count(pred) == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Check that the id exists in the module.
|
|
if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get the first id and its type (which should be the same as all the other
|
|
// ones) and check that the transformation supports this type.
|
|
uint32_t first_id = preds_to_ids[predecessors[0]];
|
|
uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
|
|
if (!CheckTypeIsAllowed(ir_context, type_id)) {
|
|
return false;
|
|
}
|
|
|
|
// Check that the ids corresponding to predecessors are all synonymous, have
|
|
// the same type and are available to use at the end of the predecessor.
|
|
for (uint32_t pred : predecessors) {
|
|
auto id = preds_to_ids[pred];
|
|
|
|
// Check that the id has the same type as the other ones.
|
|
if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) {
|
|
return false;
|
|
}
|
|
|
|
// Check that the id is synonymous with the others by checking that it is
|
|
// synonymous with the first one (or it is the same id).
|
|
if (id != first_id &&
|
|
!transformation_context.GetFactManager()->IsSynonymous(
|
|
MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) {
|
|
return false;
|
|
}
|
|
|
|
// Check that the id is available at the end of the corresponding
|
|
// predecessor block.
|
|
|
|
auto pred_block = ir_context->get_instr_block(pred);
|
|
|
|
// We should always be able to find the predecessor block, since it is in
|
|
// the predecessors list of |block|.
|
|
assert(pred_block && "Could not find one of the predecessor blocks.");
|
|
|
|
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722): This
|
|
// function always returns false if the block is unreachable, so it may
|
|
// need to be refactored.
|
|
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
|
|
ir_context, pred_block->terminator(), id)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TransformationAddOpPhiSynonym::Apply(
|
|
opt::IRContext* ir_context,
|
|
TransformationContext* transformation_context) const {
|
|
// Get the type id from one of the ids.
|
|
uint32_t first_id = message_.pred_to_id(0).second();
|
|
uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
|
|
|
|
// Define the operand list.
|
|
opt::Instruction::OperandList operand_list;
|
|
|
|
// For each predecessor, add the corresponding operands.
|
|
for (auto& pair : message_.pred_to_id()) {
|
|
operand_list.emplace_back(
|
|
opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}});
|
|
operand_list.emplace_back(
|
|
opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}});
|
|
}
|
|
|
|
// Add a new OpPhi instructions at the beginning of the block.
|
|
ir_context->get_instr_block(message_.block_id())
|
|
->begin()
|
|
.InsertBefore(MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, type_id,
|
|
message_.fresh_id(),
|
|
std::move(operand_list)));
|
|
|
|
// Update the module id bound.
|
|
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
|
|
|
|
// Invalidate all analyses, since we added an instruction to the module.
|
|
ir_context->InvalidateAnalysesExceptFor(
|
|
opt::IRContext::Analysis::kAnalysisNone);
|
|
|
|
// Record the fact that the new id is synonym with the other ones by declaring
|
|
// that it is a synonym of the first one.
|
|
transformation_context->GetFactManager()->AddFactDataSynonym(
|
|
MakeDataDescriptor(message_.fresh_id(), {}),
|
|
MakeDataDescriptor(first_id, {}), ir_context);
|
|
}
|
|
|
|
protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const {
|
|
protobufs::Transformation result;
|
|
*result.mutable_add_opphi_synonym() = message_;
|
|
return result;
|
|
}
|
|
|
|
bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
|
|
opt::IRContext* ir_context, uint32_t type_id) {
|
|
auto type = ir_context->get_type_mgr()->GetType(type_id);
|
|
if (!type) {
|
|
return false;
|
|
}
|
|
|
|
// We allow the following types: Bool, Integer, Float, Vector, Matrix, Array,
|
|
// Struct.
|
|
if (type->AsBool() || type->AsInteger() || type->AsFloat() ||
|
|
type->AsVector() || type->AsMatrix() || type->AsArray() ||
|
|
type->AsStruct()) {
|
|
return true;
|
|
}
|
|
|
|
// We allow pointer types if the VariablePointers capability is enabled and
|
|
// the pointer has the correct storage class (Workgroup or StorageBuffer).
|
|
if (type->AsPointer()) {
|
|
auto storage_class = type->AsPointer()->storage_class();
|
|
return ir_context->get_feature_mgr()->HasCapability(
|
|
SpvCapabilityVariablePointers) &&
|
|
(storage_class == SpvStorageClassWorkgroup ||
|
|
storage_class == SpvStorageClassStorageBuffer);
|
|
}
|
|
|
|
// We do not allow other types.
|
|
return false;
|
|
}
|
|
|
|
} // namespace fuzz
|
|
} // namespace spvtools
|