// 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. // This file is specifically named spvtools_fuzz.proto so that the string // 'spvtools_fuzz' appears in the names of global-scope symbols that protoc // generates when targeting C++. This is to reduce the potential for name // clashes with other globally-scoped symbols. syntax = "proto3"; package spvtools.fuzz.protobufs; message IdUseDescriptor { // Describes a use of an id as an input operand to an instruction in some block // of a function. // Example: // - id_of_interest = 42 // - target_instruction_opcode = OpStore // - in_operand_index = 1 // - base_instruction_result_id = 50 // - num_opcodes_to_ignore = 7 // represents a use of id 42 as input operand 1 to an OpStore instruction, // such that the OpStore instruction can be found in the same basic block as // the instruction with result id 50, and in particular is the 8th OpStore // instruction found from instruction 50 onwards (i.e. 7 OpStore // instructions are skipped). // An id that we would like to be able to find a use of. uint32 id_of_interest = 1; // The opcode for the instruction that uses the id. uint32 target_instruction_opcode = 2; // The input operand index at which the use is expected. uint32 in_operand_index = 3; // The id of an instruction after which the instruction that contains the use // is believed to occur; it might be the using instruction itself. uint32 base_instruction_result_id = 4; // The number of matching opcodes to skip over when searching for the using // instruction from the base instruction. uint32 num_opcodes_to_ignore = 5; } message DataDescriptor { // Represents a data element that can be accessed from an id, by walking the // type hierarchy via a sequence of 0 or more indices. // // Very similar to a UniformBufferElementDescriptor, except that a // DataDescriptor is rooted at the id of a scalar or composite. // The object being accessed - a scalar or composite uint32 object = 1; // 0 or more indices, used to index into a composite object repeated uint32 index = 2; } message UniformBufferElementDescriptor { // Represents a data element inside a uniform buffer. The element is // specified via (a) the result id of a uniform variable in which the element // is contained, and (b) a series of indices that need to be followed to get // to the element (via fields and array/vector indices). // // Example: suppose there is a uniform variable with descriptor set 7 and // binding 9, and that the uniform variable has the following type (using // GLSL-like syntax): // // struct S { // float f; // vec3 g; // int4 h[10]; // }; // // Then: // - (7, 9, [0]) describes the 'f' field. // - (7, 9, [1,1]) describes the y component of the 'g' field. // - (7, 9, [2,7,3]) describes the w component of element 7 of the 'h' field // The descriptor set and binding associated with a uniform variable. uint32 descriptor_set = 1; uint32 binding = 2; // An ordered sequence of indices through composite structures in the // uniform buffer. repeated uint32 index = 3; } message FactSequence { repeated Fact fact = 1; } message Fact { oneof fact { // Order the fact options by numeric id (rather than alphabetically). FactConstantUniform constant_uniform_fact = 1; FactIdSynonym id_synonym_fact = 2; } } // Keep fact message types in alphabetical order: message FactConstantUniform { // Records the fact that a uniform buffer element is guaranteed to be equal // to a particular constant value. spirv-fuzz can use such guarantees to // obfuscate code, e.g. to manufacture an expression that will (due to the // guarantee) evaluate to a particular value at runtime but in a manner that // cannot be predicted at compile-time. // An element of a uniform buffer UniformBufferElementDescriptor uniform_buffer_element_descriptor = 1; // The words of the associated constant repeated uint32 constant_word = 2; } message FactIdSynonym { // Records the fact that the data held in an id is guaranteed to be equal to // the data held in a data descriptor. spirv-fuzz can use this to replace // uses of the id with references to the data described by the data // descriptor. // An id uint32 id = 1; // A data descriptor guaranteed to hold a value identical to that held by the // id DataDescriptor data_descriptor = 2; } message TransformationSequence { repeated Transformation transformation = 1; } message Transformation { oneof transformation { // Order the transformation options by numeric id (rather than // alphabetically). TransformationMoveBlockDown move_block_down = 1; TransformationSplitBlock split_block = 2; TransformationAddConstantBoolean add_constant_boolean = 3; TransformationAddConstantScalar add_constant_scalar = 4; TransformationAddTypeBoolean add_type_boolean = 5; TransformationAddTypeFloat add_type_float = 6; TransformationAddTypeInt add_type_int = 7; TransformationAddDeadBreak add_dead_break = 8; TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9; TransformationAddTypePointer add_type_pointer = 10; TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11; TransformationAddDeadContinue add_dead_continue = 12; TransformationCopyObject copy_object = 13; TransformationReplaceIdWithSynonym replace_id_with_synonym = 14; // Add additional option using the next available number. } } // Keep transformation message types in alphabetical order: message TransformationAddConstantBoolean { // Supports adding the constants true and false to a module, which may be // necessary in order to enable other transformations if they are not present. uint32 fresh_id = 1; bool is_true = 2; } message TransformationAddConstantScalar { // Adds a constant of the given scalar type // Id for the constant uint32 fresh_id = 1; // Id for the scalar type of the constant uint32 type_id = 2; // Value of the constant repeated uint32 word = 3; } message TransformationAddDeadBreak { // A transformation that turns a basic block that unconditionally branches to // its successor into a block that potentially breaks out of a structured // control flow construct, but in such a manner that the break cannot actually // be taken. // The block to break from uint32 from_block = 1; // The merge block to break to uint32 to_block = 2; // Determines whether the break condition is true or false bool break_condition_value = 3; // A sequence of ids suitable for extending OpPhi instructions as a result of // the new break edge repeated uint32 phi_id = 4; } message TransformationAddDeadContinue { // A transformation that turns a basic block appearing in a loop and that // unconditionally branches to its successor into a block that potentially // branches to the continue target of the loop, but in such a manner that the // continue branch cannot actually be taken. // The block to continue from uint32 from_block = 1; // Determines whether the continue condition is true or false bool continue_condition_value = 2; // A sequence of ids suitable for extending OpPhi instructions as a result of // the new break edge repeated uint32 phi_id = 3; } message TransformationAddTypeBoolean { // Adds OpTypeBool to the module // Id to be used for the type uint32 fresh_id = 1; } message TransformationAddTypeFloat { // Adds OpTypeFloat to the module with the given width // Id to be used for the type uint32 fresh_id = 1; // Floating-point width uint32 width = 2; } message TransformationAddTypeInt { // Adds OpTypeInt to the module with the given width and signedness // Id to be used for the type uint32 fresh_id = 1; // Integer width uint32 width = 2; // True if and only if this is a signed type bool is_signed = 3; } message TransformationAddTypePointer { // Adds OpTypePointer to the module, with the given storage class and base // type // Id to be used for the type uint32 fresh_id = 1; // Pointer storage class uint32 storage_class = 2; // Id of the base type for the pointer uint32 base_type_id = 3; } message TransformationCopyObject { // A transformation that introduces an OpCopyObject instruction to make a // copy of an object. // Id of the object to be copied uint32 object = 1; // The id of an instruction in a block uint32 base_instruction_id = 2; // An offset, such that OpCopyObject instruction should be inserted right // before the instruction |offset| instructions after |base_instruction_id| uint32 offset = 3; // A fresh id for the copied object uint32 fresh_id = 4; } message TransformationMoveBlockDown { // A transformation that moves a basic block to be one position lower in // program order. // The id of the block to move down. uint32 block_id = 1; } message TransformationReplaceBooleanConstantWithConstantBinary { // A transformation to capture replacing a use of a boolean constant with // binary operation on two constant values // A descriptor for the boolean constant id we would like to replace IdUseDescriptor id_use_descriptor = 1; // Id for the constant to be used on the LHS of the comparision uint32 lhs_id = 2; // Id for the constant to be used on the RHS of the comparision uint32 rhs_id = 3; // Opcode for binary operator uint32 opcode = 4; // Id that will store the result of the binary operation instruction uint32 fresh_id_for_binary_operation = 5; } message TransformationReplaceConstantWithUniform { // Replaces a use of a constant id with the the result of a load from an // element of uniform buffer known to hold the same value as the constant // A descriptor for the id we would like to replace IdUseDescriptor id_use_descriptor = 1; // Uniform descriptor to identify which uniform value to choose UniformBufferElementDescriptor uniform_descriptor = 2; // Id that will store the result of an access chain uint32 fresh_id_for_access_chain = 3; // Id that will store the result of a load uint32 fresh_id_for_load = 4; } message TransformationReplaceIdWithSynonym { // Replaces an id use with something known to be synonymous with that id use, // e.g. because it was obtained via applying OpCopyObject // Identifies the id use that is to be replaced IdUseDescriptor id_use_descriptor = 1; // Identifies the data with which the id use is expected to be synonymous DataDescriptor data_descriptor = 2; // In the case that a temporary is required to express the synonym (e.g. to // obtain an element of a vector, provides a fresh id for the temporary; // should be set to 0 if no temporary is required uint32 fresh_id_for_temporary = 3; } message TransformationSplitBlock { // A transformation that splits a basic block into two basic blocks // The result id of an instruction uint32 base_instruction_id = 1; // An offset, such that the block containing |base_instruction_id| should be // split right before the instruction |offset| instructions after // |base_instruction_id| uint32 offset = 2; // An id that must not yet be used by the module to which this transformation // is applied. Rather than having the transformation choose a suitable id on // application, we require the id to be given upfront in order to facilitate // reducing fuzzed shaders by removing transformations. The reason is that // future transformations may refer to the fresh id introduced by this // transformation, and if we end up changing what that id is, due to removing // earlier transformations, it may inhibit later transformations from // applying. uint32 fresh_id = 3; }