mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-27 22:00:07 +00:00
64eaa9832f
Fixes #3959.
418 lines
14 KiB
C++
418 lines
14 KiB
C++
// Copyright (c) 2020 Vasyl Teliman
|
|
//
|
|
// 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_propagate_instruction_up.h"
|
|
|
|
#include "source/fuzz/fuzzer_util.h"
|
|
#include "source/fuzz/instruction_descriptor.h"
|
|
|
|
namespace spvtools {
|
|
namespace fuzz {
|
|
namespace {
|
|
|
|
uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst,
|
|
uint32_t label_id) {
|
|
assert(phi_inst.opcode() == SpvOpPhi && "|phi_inst| is not an OpPhi");
|
|
|
|
for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) {
|
|
if (phi_inst.GetSingleWordInOperand(i) == label_id) {
|
|
return phi_inst.GetSingleWordInOperand(i - 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ContainsPointers(const opt::analysis::Type& type) {
|
|
switch (type.kind()) {
|
|
case opt::analysis::Type::kPointer:
|
|
return true;
|
|
case opt::analysis::Type::kStruct:
|
|
return std::any_of(type.AsStruct()->element_types().begin(),
|
|
type.AsStruct()->element_types().end(),
|
|
[](const opt::analysis::Type* element_type) {
|
|
return ContainsPointers(*element_type);
|
|
});
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) {
|
|
const auto* inst_block = ir_context->get_instr_block(inst);
|
|
assert(inst_block &&
|
|
"This function shouldn't be applied to global instructions or function"
|
|
"parameters");
|
|
|
|
for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
|
|
const auto& operand = inst->GetInOperand(i);
|
|
if (operand.type != SPV_OPERAND_TYPE_ID) {
|
|
// Consider only <id> operands.
|
|
continue;
|
|
}
|
|
|
|
auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
|
|
assert(dependency && "Operand has invalid id");
|
|
|
|
if (ir_context->get_instr_block(dependency) == inst_block &&
|
|
dependency->opcode() != SpvOpPhi) {
|
|
// |dependency| is "valid" if it's an OpPhi from the same basic block or
|
|
// an instruction from a different basic block.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
|
|
const protobufs::TransformationPropagateInstructionUp& message)
|
|
: message_(message) {}
|
|
|
|
TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
|
|
uint32_t block_id,
|
|
const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id) {
|
|
message_.set_block_id(block_id);
|
|
*message_.mutable_predecessor_id_to_fresh_id() =
|
|
fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id);
|
|
}
|
|
|
|
bool TransformationPropagateInstructionUp::IsApplicable(
|
|
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
|
|
// Check that we can apply this transformation to the |block_id|.
|
|
if (!IsApplicableToBlock(ir_context, message_.block_id())) {
|
|
return false;
|
|
}
|
|
|
|
const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
|
|
message_.predecessor_id_to_fresh_id());
|
|
for (auto id : ir_context->cfg()->preds(message_.block_id())) {
|
|
// Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id|
|
|
// map.
|
|
if (!predecessor_id_to_fresh_id.count(id)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::vector<uint32_t> maybe_fresh_ids;
|
|
maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size());
|
|
for (const auto& entry : predecessor_id_to_fresh_id) {
|
|
maybe_fresh_ids.push_back(entry.second);
|
|
}
|
|
|
|
// All ids must be unique and fresh.
|
|
return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
|
|
std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
|
|
[ir_context](uint32_t id) {
|
|
return fuzzerutil::IsFreshId(ir_context, id);
|
|
});
|
|
}
|
|
|
|
void TransformationPropagateInstructionUp::Apply(
|
|
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
|
|
auto* inst = GetInstructionToPropagate(ir_context, message_.block_id());
|
|
assert(inst &&
|
|
"The block must have at least one supported instruction to propagate");
|
|
assert(inst->result_id() && inst->type_id() &&
|
|
"|inst| must have a result id and a type id");
|
|
|
|
opt::Instruction::OperandList op_phi_operands;
|
|
const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
|
|
message_.predecessor_id_to_fresh_id());
|
|
std::unordered_set<uint32_t> visited_predecessors;
|
|
for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) {
|
|
// A block can have multiple identical predecessors.
|
|
if (visited_predecessors.count(predecessor_id)) {
|
|
continue;
|
|
}
|
|
|
|
visited_predecessors.insert(predecessor_id);
|
|
|
|
auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id);
|
|
|
|
// Compute InOperands for the OpPhi instruction to be inserted later.
|
|
op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}});
|
|
op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
|
|
|
|
// Create a clone of the |inst| to be inserted into the |predecessor_id|.
|
|
std::unique_ptr<opt::Instruction> clone(inst->Clone(ir_context));
|
|
clone->SetResultId(new_result_id);
|
|
|
|
fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
|
|
|
|
// Adjust |clone|'s operands to account for possible dependencies on OpPhi
|
|
// instructions from the same basic block.
|
|
for (uint32_t i = 0; i < clone->NumInOperands(); ++i) {
|
|
auto& operand = clone->GetInOperand(i);
|
|
if (operand.type != SPV_OPERAND_TYPE_ID) {
|
|
// Consider only ids.
|
|
continue;
|
|
}
|
|
|
|
const auto* dependency_inst =
|
|
ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
|
|
assert(dependency_inst && "|clone| depends on an invalid id");
|
|
|
|
if (ir_context->get_instr_block(dependency_inst->result_id()) !=
|
|
ir_context->cfg()->block(message_.block_id())) {
|
|
// We don't need to adjust anything if |dependency_inst| is from a
|
|
// different block, a global instruction or a function parameter.
|
|
continue;
|
|
}
|
|
|
|
assert(dependency_inst->opcode() == SpvOpPhi &&
|
|
"Propagated instruction can depend only on OpPhis from the same "
|
|
"basic block or instructions from different basic blocks");
|
|
|
|
auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id);
|
|
assert(new_id && "OpPhi instruction is missing a predecessor");
|
|
operand.words[0] = new_id;
|
|
}
|
|
|
|
auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction(
|
|
ir_context, predecessor_id, clone->opcode());
|
|
assert(insert_before_inst && "Can't insert |clone| into |predecessor_id");
|
|
|
|
insert_before_inst->InsertBefore(std::move(clone));
|
|
}
|
|
|
|
// Insert an OpPhi instruction into the basic block of |inst|.
|
|
ir_context->get_instr_block(inst)->begin()->InsertBefore(
|
|
MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, inst->type_id(),
|
|
inst->result_id(),
|
|
std::move(op_phi_operands)));
|
|
|
|
// Remove |inst| from the basic block.
|
|
ir_context->KillInst(inst);
|
|
|
|
// We have changed the module so most analyzes are now invalid.
|
|
ir_context->InvalidateAnalysesExceptFor(
|
|
opt::IRContext::Analysis::kAnalysisNone);
|
|
}
|
|
|
|
protobufs::Transformation TransformationPropagateInstructionUp::ToMessage()
|
|
const {
|
|
protobufs::Transformation result;
|
|
*result.mutable_propagate_instruction_up() = message_;
|
|
return result;
|
|
}
|
|
|
|
bool TransformationPropagateInstructionUp::IsOpcodeSupported(SpvOp opcode) {
|
|
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
|
|
// We only support "simple" instructions that don't work with memory.
|
|
// We should extend this so that we support the ones that modify the memory
|
|
// too.
|
|
switch (opcode) {
|
|
case SpvOpUndef:
|
|
case SpvOpAccessChain:
|
|
case SpvOpInBoundsAccessChain:
|
|
case SpvOpArrayLength:
|
|
case SpvOpVectorExtractDynamic:
|
|
case SpvOpVectorInsertDynamic:
|
|
case SpvOpVectorShuffle:
|
|
case SpvOpCompositeConstruct:
|
|
case SpvOpCompositeExtract:
|
|
case SpvOpCompositeInsert:
|
|
case SpvOpCopyObject:
|
|
case SpvOpTranspose:
|
|
case SpvOpConvertFToU:
|
|
case SpvOpConvertFToS:
|
|
case SpvOpConvertSToF:
|
|
case SpvOpConvertUToF:
|
|
case SpvOpUConvert:
|
|
case SpvOpSConvert:
|
|
case SpvOpFConvert:
|
|
case SpvOpQuantizeToF16:
|
|
case SpvOpSatConvertSToU:
|
|
case SpvOpSatConvertUToS:
|
|
case SpvOpBitcast:
|
|
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:
|
|
case SpvOpSelect:
|
|
case SpvOpIEqual:
|
|
case SpvOpINotEqual:
|
|
case SpvOpUGreaterThan:
|
|
case SpvOpSGreaterThan:
|
|
case SpvOpUGreaterThanEqual:
|
|
case SpvOpSGreaterThanEqual:
|
|
case SpvOpULessThan:
|
|
case SpvOpSLessThan:
|
|
case SpvOpULessThanEqual:
|
|
case SpvOpSLessThanEqual:
|
|
case SpvOpFOrdEqual:
|
|
case SpvOpFUnordEqual:
|
|
case SpvOpFOrdNotEqual:
|
|
case SpvOpFUnordNotEqual:
|
|
case SpvOpFOrdLessThan:
|
|
case SpvOpFUnordLessThan:
|
|
case SpvOpFOrdGreaterThan:
|
|
case SpvOpFUnordGreaterThan:
|
|
case SpvOpFOrdLessThanEqual:
|
|
case SpvOpFUnordLessThanEqual:
|
|
case SpvOpFOrdGreaterThanEqual:
|
|
case SpvOpFUnordGreaterThanEqual:
|
|
case SpvOpShiftRightLogical:
|
|
case SpvOpShiftRightArithmetic:
|
|
case SpvOpShiftLeftLogical:
|
|
case SpvOpBitwiseOr:
|
|
case SpvOpBitwiseXor:
|
|
case SpvOpBitwiseAnd:
|
|
case SpvOpNot:
|
|
case SpvOpBitFieldInsert:
|
|
case SpvOpBitFieldSExtract:
|
|
case SpvOpBitFieldUExtract:
|
|
case SpvOpBitReverse:
|
|
case SpvOpBitCount:
|
|
case SpvOpCopyLogical:
|
|
case SpvOpPtrEqual:
|
|
case SpvOpPtrNotEqual:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
opt::Instruction*
|
|
TransformationPropagateInstructionUp::GetInstructionToPropagate(
|
|
opt::IRContext* ir_context, uint32_t block_id) {
|
|
auto* block = ir_context->cfg()->block(block_id);
|
|
assert(block && "|block_id| is invalid");
|
|
|
|
for (auto& inst : *block) {
|
|
// We look for the first instruction in the block that satisfies the
|
|
// following rules:
|
|
// - it's not an OpPhi
|
|
// - it must be supported by this transformation
|
|
// - it may depend only on instructions from different basic blocks or on
|
|
// OpPhi instructions from the same basic block.
|
|
if (inst.opcode() == SpvOpPhi || !IsOpcodeSupported(inst.opcode()) ||
|
|
!inst.type_id() || !inst.result_id()) {
|
|
continue;
|
|
}
|
|
|
|
const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id());
|
|
assert(inst_type && "|inst| has invalid type");
|
|
|
|
if (inst_type->AsSampledImage()) {
|
|
// OpTypeSampledImage cannot be used as an argument to OpPhi instructions,
|
|
// thus we cannot support this type.
|
|
continue;
|
|
}
|
|
|
|
if (!ir_context->get_feature_mgr()->HasCapability(
|
|
SpvCapabilityVariablePointersStorageBuffer) &&
|
|
ContainsPointers(*inst_type)) {
|
|
// OpPhi supports pointer operands only with VariablePointers or
|
|
// VariablePointersStorageBuffer capabilities.
|
|
//
|
|
// Note that VariablePointers capability implicitly declares
|
|
// VariablePointersStorageBuffer capability.
|
|
continue;
|
|
}
|
|
|
|
if (!HasValidDependencies(ir_context, &inst)) {
|
|
continue;
|
|
}
|
|
|
|
return &inst;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool TransformationPropagateInstructionUp::IsApplicableToBlock(
|
|
opt::IRContext* ir_context, uint32_t block_id) {
|
|
// Check that |block_id| is valid.
|
|
const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
|
|
if (!label_inst || label_inst->opcode() != SpvOpLabel) {
|
|
return false;
|
|
}
|
|
|
|
// Check that |block| has predecessors.
|
|
const auto& predecessors = ir_context->cfg()->preds(block_id);
|
|
if (predecessors.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// The block must contain an instruction to propagate.
|
|
const auto* inst_to_propagate =
|
|
GetInstructionToPropagate(ir_context, block_id);
|
|
if (!inst_to_propagate) {
|
|
return false;
|
|
}
|
|
|
|
// We should be able to insert |inst_to_propagate| into every predecessor of
|
|
// |block|.
|
|
return std::all_of(predecessors.begin(), predecessors.end(),
|
|
[ir_context, inst_to_propagate](uint32_t predecessor_id) {
|
|
return fuzzerutil::GetLastInsertBeforeInstruction(
|
|
ir_context, predecessor_id,
|
|
inst_to_propagate->opcode()) != nullptr;
|
|
});
|
|
}
|
|
|
|
std::unordered_set<uint32_t> TransformationPropagateInstructionUp::GetFreshIds()
|
|
const {
|
|
std::unordered_set<uint32_t> result;
|
|
for (auto& pair : message_.predecessor_id_to_fresh_id()) {
|
|
result.insert(pair.second());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace fuzz
|
|
} // namespace spvtools
|