SPIRV-Tools/source/fuzz/transformation_construct_composite.cpp
Alastair Donaldson 00170cc5e6
spirv-fuzz: Refactor 'copy object' and 'construct composite' transformations (#2966)
Rework these transformations to identify instructions via (base,
opcode, skip-count) triples, rather than (base, offset) pairs.
2019-10-15 20:00:17 +01:00

301 lines
12 KiB
C++

// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/transformation_construct_composite.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/opt/instruction.h"
namespace spvtools {
namespace fuzz {
TransformationConstructComposite::TransformationConstructComposite(
const protobufs::TransformationConstructComposite& message)
: message_(message) {}
TransformationConstructComposite::TransformationConstructComposite(
uint32_t composite_type_id, std::vector<uint32_t> component,
const protobufs::InstructionDescriptor& instruction_to_insert_before,
uint32_t fresh_id) {
message_.set_composite_type_id(composite_type_id);
for (auto a_component : component) {
message_.add_component(a_component);
}
*message_.mutable_instruction_to_insert_before() =
instruction_to_insert_before;
message_.set_fresh_id(fresh_id);
}
bool TransformationConstructComposite::IsApplicable(
opt::IRContext* context, const FactManager& /*fact_manager*/) const {
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
// We require the id for the composite constructor to be unused.
return false;
}
auto insert_before =
FindInstruction(message_.instruction_to_insert_before(), context);
if (!insert_before) {
// The instruction before which the composite should be inserted was not
// found.
return false;
}
auto composite_type =
context->get_type_mgr()->GetType(message_.composite_type_id());
if (!fuzzerutil::IsCompositeType(composite_type)) {
// The type must actually be a composite.
return false;
}
// If the type is an array, matrix, struct or vector, the components need to
// be suitable for constructing something of that type.
if (composite_type->AsArray() && !ComponentsForArrayConstructionAreOK(
context, *composite_type->AsArray())) {
return false;
}
if (composite_type->AsMatrix() && !ComponentsForMatrixConstructionAreOK(
context, *composite_type->AsMatrix())) {
return false;
}
if (composite_type->AsStruct() && !ComponentsForStructConstructionAreOK(
context, *composite_type->AsStruct())) {
return false;
}
if (composite_type->AsVector() && !ComponentsForVectorConstructionAreOK(
context, *composite_type->AsVector())) {
return false;
}
// Now check whether every component being used to initialize the composite is
// available at the desired program point.
for (auto& component : message_.component()) {
auto component_inst = context->get_def_use_mgr()->GetDef(component);
if (!context->get_instr_block(component)) {
// The component does not have a block; that means it is in global scope,
// which is OK. (Whether the component actually corresponds to an
// instruction is checked above when determining whether types are
// suitable.)
continue;
}
// Check whether the component is available.
if (insert_before->HasResultId() &&
insert_before->result_id() == component) {
// This constitutes trying to use an id right before it is defined. The
// special case is needed due to an instruction always dominating itself.
return false;
}
if (!context
->GetDominatorAnalysis(
context->get_instr_block(&*insert_before)->GetParent())
->Dominates(component_inst, &*insert_before)) {
// The instruction defining the component must dominate the instruction we
// wish to insert the composite before.
return false;
}
}
return true;
}
void TransformationConstructComposite::Apply(opt::IRContext* context,
FactManager* fact_manager) const {
// Use the base and offset information from the transformation to determine
// where in the module a new instruction should be inserted.
auto insert_before_inst =
FindInstruction(message_.instruction_to_insert_before(), context);
auto destination_block = context->get_instr_block(insert_before_inst);
auto insert_before = fuzzerutil::GetIteratorForInstruction(
destination_block, insert_before_inst);
// Prepare the input operands for an OpCompositeConstruct instruction.
opt::Instruction::OperandList in_operands;
for (auto& component_id : message_.component()) {
in_operands.push_back({SPV_OPERAND_TYPE_ID, {component_id}});
}
// Insert an OpCompositeConstruct instruction.
insert_before.InsertBefore(MakeUnique<opt::Instruction>(
context, SpvOpCompositeConstruct, message_.composite_type_id(),
message_.fresh_id(), in_operands));
// Inform the fact manager that we now have new synonyms: every component of
// the composite is synonymous with the id used to construct that component.
auto composite_type =
context->get_type_mgr()->GetType(message_.composite_type_id());
uint32_t index = 0;
for (auto component : message_.component()) {
protobufs::Fact fact;
fact.mutable_id_synonym_fact()->set_id(component);
fact.mutable_id_synonym_fact()->mutable_data_descriptor()->set_object(
message_.fresh_id());
fact.mutable_id_synonym_fact()->mutable_data_descriptor()->add_index(index);
fact_manager->AddFact(fact, context);
if (composite_type->AsVector()) {
// The vector case is a bit fiddly, because one argument to a vector
// constructor can cover more than one element.
auto component_type = context->get_type_mgr()->GetType(
context->get_def_use_mgr()->GetDef(component)->type_id());
if (component_type->AsVector()) {
assert(component_type->AsVector()->element_type() ==
composite_type->AsVector()->element_type());
index += component_type->AsVector()->element_count();
} else {
assert(component_type == composite_type->AsVector()->element_type());
index++;
}
} else {
// The non-vector cases are all easy: the constructor has exactly the same
// number of arguments as the number of sub-components, so we can just
// increment the index.
index++;
}
}
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
bool TransformationConstructComposite::ComponentsForArrayConstructionAreOK(
opt::IRContext* context, const opt::analysis::Array& array_type) const {
if (array_type.length_info().words[0] !=
opt::analysis::Array::LengthInfo::kConstant) {
// We only handle constant-sized arrays.
return false;
}
if (array_type.length_info().words.size() != 2) {
// We only handle the case where the array size can be captured in a single
// word.
return false;
}
// Get the array size.
auto array_size = array_type.length_info().words[1];
if (static_cast<uint32_t>(message_.component().size()) != array_size) {
// The number of components must match the array size.
return false;
}
// Check that each component is the result id of an instruction whose type is
// the array's element type.
for (auto component_id : message_.component()) {
auto inst = context->get_def_use_mgr()->GetDef(component_id);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type != array_type.element_type()) {
// The component's type does not match the array's element type.
return false;
}
}
return true;
}
bool TransformationConstructComposite::ComponentsForMatrixConstructionAreOK(
opt::IRContext* context, const opt::analysis::Matrix& matrix_type) const {
if (static_cast<uint32_t>(message_.component().size()) !=
matrix_type.element_count()) {
// The number of components must match the number of columns of the matrix.
return false;
}
// Check that each component is the result id of an instruction whose type is
// the matrix's column type.
for (auto component_id : message_.component()) {
auto inst = context->get_def_use_mgr()->GetDef(component_id);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type != matrix_type.element_type()) {
// The component's type does not match the matrix's column type.
return false;
}
}
return true;
}
bool TransformationConstructComposite::ComponentsForStructConstructionAreOK(
opt::IRContext* context, const opt::analysis::Struct& struct_type) const {
if (static_cast<uint32_t>(message_.component().size()) !=
struct_type.element_types().size()) {
// The number of components must match the number of fields of the struct.
return false;
}
// Check that each component is the result id of an instruction those type
// matches the associated field type.
for (uint32_t field_index = 0;
field_index < struct_type.element_types().size(); field_index++) {
auto inst =
context->get_def_use_mgr()->GetDef(message_.component()[field_index]);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type != struct_type.element_types()[field_index]) {
// The component's type does not match the corresponding field type.
return false;
}
}
return true;
}
bool TransformationConstructComposite::ComponentsForVectorConstructionAreOK(
opt::IRContext* context, const opt::analysis::Vector& vector_type) const {
uint32_t base_element_count = 0;
auto element_type = vector_type.element_type();
for (auto& component_id : message_.component()) {
auto inst = context->get_def_use_mgr()->GetDef(component_id);
if (inst == nullptr || !inst->type_id()) {
// The component does not correspond to an instruction with a result
// type.
return false;
}
auto component_type = context->get_type_mgr()->GetType(inst->type_id());
assert(component_type);
if (component_type == element_type) {
base_element_count++;
} else if (component_type->AsVector() &&
component_type->AsVector()->element_type() == element_type) {
base_element_count += component_type->AsVector()->element_count();
} else {
// The component was not appropriate; e.g. no type corresponding to the
// given id was found, or the type that was found was not compatible
// with the vector being constructed.
return false;
}
}
// The number of components provided (when vector components are flattened
// out) needs to match the length of the vector being constructed.
return base_element_count == vector_type.element_count();
}
protobufs::Transformation TransformationConstructComposite::ToMessage() const {
protobufs::Transformation result;
*result.mutable_construct_composite() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools