blob: 28af854906e6580d9e1be61dbd5641d1e9039fea [file] [log] [blame]
// 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_add_parameters.h"
#include <source/spirv_constant.h>
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationAddParameters::TransformationAddParameters(
const protobufs::TransformationAddParameters& message)
: message_(message) {}
TransformationAddParameters::TransformationAddParameters(
uint32_t function_id, uint32_t new_type_id,
const std::vector<uint32_t>& new_parameter_type,
const std::vector<uint32_t>& new_parameter_id,
const std::vector<uint32_t>& constant_id) {
message_.set_function_id(function_id);
message_.set_new_type_id(new_type_id);
for (auto id : new_parameter_type) {
message_.add_new_parameter_type(id);
}
for (auto id : new_parameter_id) {
message_.add_new_parameter_id(id);
}
for (auto id : constant_id) {
message_.add_constant_id(id);
}
}
bool TransformationAddParameters::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// Check that function exists
const auto* function =
fuzzerutil::FindFunction(ir_context, message_.function_id());
if (!function ||
fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
return false;
}
// Validate new parameters.
const auto& new_type_ids = message_.new_parameter_type();
const auto& new_parameter_ids = message_.new_parameter_id();
const auto& constant_ids = message_.constant_id();
// All three vectors must have the same size.
if (new_type_ids.size() != new_parameter_ids.size() ||
new_type_ids.size() != constant_ids.size()) {
return false;
}
// Vectors must have at least one component.
if (new_type_ids.empty()) {
return false;
}
// Check that type ids exist in the module and are not OpTypeVoid.
for (auto id : new_type_ids) {
const auto* type = ir_context->get_type_mgr()->GetType(id);
if (!type || type->AsVoid()) {
return false;
}
}
// Check that all parameter ids are fresh.
for (auto id : new_parameter_ids) {
if (!fuzzerutil::IsFreshId(ir_context, id)) {
return false;
}
}
// Check that constants exist and have valid type.
for (int i = 0, n = constant_ids.size(); i < n; ++i) {
const auto* inst = ir_context->get_def_use_mgr()->GetDef(constant_ids[i]);
if (!inst || inst->type_id() != new_type_ids[i]) {
return false;
}
}
// Validate new function type.
const auto* old_type_inst = fuzzerutil::GetFunctionType(ir_context, function);
const auto* new_type_inst =
ir_context->get_def_use_mgr()->GetDef(message_.new_type_id());
// Both types must exist.
assert(old_type_inst && old_type_inst->opcode() == SpvOpTypeFunction);
if (!new_type_inst || new_type_inst->opcode() != SpvOpTypeFunction) {
return false;
}
auto num_old_parameters = old_type_inst->NumInOperands();
auto num_new_parameters = new_type_ids.size();
// New function type has been added to the module which means that it's valid.
// Thus, we don't need to check whether the limit on the number of arguments
// is satisfied.
// New type = old type + new parameters.
if (new_type_inst->NumInOperands() !=
num_old_parameters + num_new_parameters) {
return false;
}
// Check that old parameters and the return type are preserved.
for (uint32_t i = 0; i < num_old_parameters; ++i) {
if (new_type_inst->GetSingleWordInOperand(i) !=
old_type_inst->GetSingleWordInOperand(i)) {
return false;
}
}
// Check that new parameters have been appended.
for (int i = 0; i < num_new_parameters; ++i) {
if (new_type_inst->GetSingleWordInOperand(i + num_old_parameters) !=
new_type_ids[i]) {
return false;
}
}
return true;
}
void TransformationAddParameters::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
// Retrieve all data from the message
auto function_id = message_.function_id();
const auto& new_parameter_type = message_.new_parameter_type();
const auto& new_parameter_id = message_.new_parameter_id();
const auto& constant_id = message_.constant_id();
// Find the function that will be transformed
auto* function = fuzzerutil::FindFunction(ir_context, function_id);
assert(function && "Can't find the function");
// Change function's type
function->DefInst().SetInOperand(1, {message_.new_type_id()});
// Add new parameters to the function.
for (int i = 0, n = new_parameter_id.size(); i < n; ++i) {
function->AddParameter(MakeUnique<opt::Instruction>(
ir_context, SpvOpFunctionParameter, new_parameter_type[i],
new_parameter_id[i], opt::Instruction::OperandList()));
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
// Add an PointeeValueIsIrrelevant fact if the parameter is a pointer.
}
// Fix all OpFunctionCall instructions.
ir_context->get_def_use_mgr()->ForEachUser(
&function->DefInst(),
[function_id, &constant_id](opt::Instruction* call) {
if (call->opcode() != SpvOpFunctionCall ||
call->GetSingleWordInOperand(0) != function_id) {
return;
}
for (auto id : constant_id) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
// it would be good to mark this usage of |id| as irrelevant, so that
// we can perform some interesting transformations on it later.
call->AddOperand({SPV_OPERAND_TYPE_ID, {id}});
}
});
// Update module's id bound. We can safely dereference the result of
// max_element since |new_parameter_id| is guaranteed to have elements.
fuzzerutil::UpdateModuleIdBound(
ir_context,
*std::max_element(new_parameter_id.begin(), new_parameter_id.end()));
// Make sure our changes are analyzed.
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
}
protobufs::Transformation TransformationAddParameters::ToMessage() const {
protobufs::Transformation result;
*result.mutable_add_parameters() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools