// 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_replace_constant_with_uniform.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/uniform_buffer_element_descriptor.h" namespace spvtools { namespace fuzz { TransformationReplaceConstantWithUniform:: TransformationReplaceConstantWithUniform( protobufs::TransformationReplaceConstantWithUniform message) : message_(std::move(message)) {} TransformationReplaceConstantWithUniform:: TransformationReplaceConstantWithUniform( protobufs::IdUseDescriptor id_use, protobufs::UniformBufferElementDescriptor uniform_descriptor, uint32_t fresh_id_for_access_chain, uint32_t fresh_id_for_load) { *message_.mutable_id_use_descriptor() = std::move(id_use); *message_.mutable_uniform_descriptor() = std::move(uniform_descriptor); message_.set_fresh_id_for_access_chain(fresh_id_for_access_chain); message_.set_fresh_id_for_load(fresh_id_for_load); } std::unique_ptr TransformationReplaceConstantWithUniform::MakeAccessChainInstruction( spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const { // The input operands for the access chain. opt::Instruction::OperandList operands_for_access_chain; opt::Instruction* uniform_variable = FindUniformVariable(message_.uniform_descriptor(), ir_context, false); // The first input operand is the id of the uniform variable. operands_for_access_chain.push_back( {SPV_OPERAND_TYPE_ID, {uniform_variable->result_id()}}); // The other input operands are the ids of the constants used to index into // the uniform. The uniform buffer descriptor specifies a series of literals; // for each we find the id of the instruction that defines it, and add these // instruction ids as operands. opt::analysis::Integer int_type(32, true); auto registered_int_type = ir_context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger(); auto int_type_id = ir_context->get_type_mgr()->GetId(&int_type); for (auto index : message_.uniform_descriptor().index()) { opt::analysis::IntConstant int_constant(registered_int_type, {index}); auto constant_id = ir_context->get_constant_mgr()->FindDeclaredConstant( &int_constant, int_type_id); operands_for_access_chain.push_back({SPV_OPERAND_TYPE_ID, {constant_id}}); } // The type id for the access chain is a uniform pointer with base type // matching the given constant id type. auto type_and_pointer_type = ir_context->get_type_mgr()->GetTypeAndPointerType( constant_type_id, spv::StorageClass::Uniform); assert(type_and_pointer_type.first != nullptr); assert(type_and_pointer_type.second != nullptr); auto pointer_to_uniform_constant_type_id = ir_context->get_type_mgr()->GetId(type_and_pointer_type.second.get()); return MakeUnique( ir_context, spv::Op::OpAccessChain, pointer_to_uniform_constant_type_id, message_.fresh_id_for_access_chain(), operands_for_access_chain); } std::unique_ptr TransformationReplaceConstantWithUniform::MakeLoadInstruction( spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const { opt::Instruction::OperandList operands_for_load = { {SPV_OPERAND_TYPE_ID, {message_.fresh_id_for_access_chain()}}}; return MakeUnique( ir_context, spv::Op::OpLoad, constant_type_id, message_.fresh_id_for_load(), operands_for_load); } opt::Instruction* TransformationReplaceConstantWithUniform::GetInsertBeforeInstruction( opt::IRContext* ir_context) const { auto* result = FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); if (!result) { return nullptr; } // The use might be in an OpPhi instruction. if (result->opcode() == spv::Op::OpPhi) { // OpPhi instructions must be the first instructions in a block. Thus, we // can't insert above the OpPhi instruction. Given the predecessor block // that corresponds to the id use, get the last instruction in that block // above which we can insert OpAccessChain and OpLoad. return fuzzerutil::GetLastInsertBeforeInstruction( ir_context, result->GetSingleWordInOperand( message_.id_use_descriptor().in_operand_index() + 1), spv::Op::OpLoad); } // The only operand that we could've replaced in the OpBranchConditional is // the condition id. But that operand has a boolean type and uniform variables // can't store booleans (see the spec on OpTypeBool). Thus, |result| can't be // an OpBranchConditional. assert(result->opcode() != spv::Op::OpBranchConditional && "OpBranchConditional has no operands to replace"); assert( fuzzerutil::CanInsertOpcodeBeforeInstruction(spv::Op::OpLoad, result) && "We should be able to insert OpLoad and OpAccessChain at this point"); return result; } bool TransformationReplaceConstantWithUniform::IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const { // The following is really an invariant of the transformation rather than // merely a requirement of the precondition. We check it here since we cannot // check it in the message_ constructor. assert(message_.fresh_id_for_access_chain() != message_.fresh_id_for_load() && "Fresh ids for access chain and load result cannot be the same."); // The ids for the access chain and load instructions must both be fresh. if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id_for_access_chain())) { return false; } if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id_for_load())) { return false; } // The id specified in the id use descriptor must be that of a declared scalar // constant. auto declared_constant = ir_context->get_constant_mgr()->FindDeclaredConstant( message_.id_use_descriptor().id_of_interest()); if (!declared_constant) { return false; } if (!declared_constant->AsScalarConstant()) { return false; } // The fact manager needs to believe that the uniform data element described // by the uniform buffer element descriptor will hold a scalar value. auto constant_id_associated_with_uniform = transformation_context.GetFactManager()->GetConstantFromUniformDescriptor( message_.uniform_descriptor()); if (!constant_id_associated_with_uniform) { return false; } auto constant_associated_with_uniform = ir_context->get_constant_mgr()->FindDeclaredConstant( constant_id_associated_with_uniform); assert(constant_associated_with_uniform && "The constant should be present in the module."); if (!constant_associated_with_uniform->AsScalarConstant()) { return false; } // The types and values of the scalar value held in the id specified by the id // use descriptor and in the uniform data element specified by the uniform // buffer element descriptor need to match on both type and value. if (!declared_constant->type()->IsSame( constant_associated_with_uniform->type())) { return false; } if (declared_constant->AsScalarConstant()->words() != constant_associated_with_uniform->AsScalarConstant()->words()) { return false; } // The id use descriptor must identify some instruction with respect to the // module. auto instruction_using_constant = FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); if (!instruction_using_constant) { return false; } // The use must not be a variable initializer; these are required to be // constants, so it would be illegal to replace one with a uniform access. if (instruction_using_constant->opcode() == spv::Op::OpVariable) { return false; } // The module needs to have a uniform pointer type suitable for indexing into // the uniform variable, i.e. matching the type of the constant we wish to // replace with a uniform. opt::analysis::Pointer pointer_to_type_of_constant( declared_constant->type(), spv::StorageClass::Uniform); if (!ir_context->get_type_mgr()->GetId(&pointer_to_type_of_constant)) { return false; } // In order to index into the uniform, the module has got to contain the int32 // type, plus an OpConstant for each of the indices of interest. opt::analysis::Integer int_type(32, true); if (!ir_context->get_type_mgr()->GetId(&int_type)) { return false; } auto registered_int_type = ir_context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger(); auto int_type_id = ir_context->get_type_mgr()->GetId(&int_type); for (auto index : message_.uniform_descriptor().index()) { opt::analysis::IntConstant int_constant(registered_int_type, {index}); if (!ir_context->get_constant_mgr()->FindDeclaredConstant(&int_constant, int_type_id)) { return false; } } // Once all checks are completed, we should be able to safely insert // OpAccessChain and OpLoad into the module. assert(GetInsertBeforeInstruction(ir_context) && "There must exist an instruction that we can use to insert " "OpAccessChain and OpLoad above"); return true; } void TransformationReplaceConstantWithUniform::Apply( spvtools::opt::IRContext* ir_context, TransformationContext* /*unused*/) const { // Get the instruction that contains the id use we wish to replace. auto* instruction_containing_constant_use = FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); assert(instruction_containing_constant_use && "Precondition requires that the id use can be found."); assert(instruction_containing_constant_use->GetSingleWordInOperand( message_.id_use_descriptor().in_operand_index()) == message_.id_use_descriptor().id_of_interest() && "Does not appear to be a usage of the desired id."); // The id of the type for the constant whose use we wish to replace. auto constant_type_id = ir_context->get_def_use_mgr() ->GetDef(message_.id_use_descriptor().id_of_interest()) ->type_id(); // Get an instruction that will be used to insert OpAccessChain and OpLoad. auto* insert_before_inst = GetInsertBeforeInstruction(ir_context); assert(insert_before_inst && "There must exist an insertion point for OpAccessChain and OpLoad"); opt::BasicBlock* enclosing_block = ir_context->get_instr_block(insert_before_inst); // Add an access chain instruction to target the uniform element. auto access_chain_instruction = MakeAccessChainInstruction(ir_context, constant_type_id); auto access_chain_instruction_ptr = access_chain_instruction.get(); insert_before_inst->InsertBefore(std::move(access_chain_instruction)); ir_context->get_def_use_mgr()->AnalyzeInstDefUse( access_chain_instruction_ptr); ir_context->set_instr_block(access_chain_instruction_ptr, enclosing_block); // Add a load from this access chain. auto load_instruction = MakeLoadInstruction(ir_context, constant_type_id); auto load_instruction_ptr = load_instruction.get(); insert_before_inst->InsertBefore(std::move(load_instruction)); ir_context->get_def_use_mgr()->AnalyzeInstDefUse(load_instruction_ptr); ir_context->set_instr_block(load_instruction_ptr, enclosing_block); // Adjust the instruction containing the usage of the constant so that this // usage refers instead to the result of the load. instruction_containing_constant_use->SetInOperand( message_.id_use_descriptor().in_operand_index(), {message_.fresh_id_for_load()}); ir_context->get_def_use_mgr()->EraseUseRecordsOfOperandIds( instruction_containing_constant_use); ir_context->get_def_use_mgr()->AnalyzeInstUse( instruction_containing_constant_use); // Update the module id bound to reflect the new instructions. fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id_for_load()); fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id_for_access_chain()); } protobufs::Transformation TransformationReplaceConstantWithUniform::ToMessage() const { protobufs::Transformation result; *result.mutable_replace_constant_with_uniform() = message_; return result; } std::unordered_set TransformationReplaceConstantWithUniform::GetFreshIds() const { return {message_.fresh_id_for_access_chain(), message_.fresh_id_for_load()}; } } // namespace fuzz } // namespace spvtools