// 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 UInt32Pair {

  // A pair of uint32s; useful for defining mappings.

  uint32 first = 1;

  uint32 second = 2;

}

message InstructionDescriptor {

  // Describes an instruction in some block of a function with respect to a
  // base instruction.

  // The id of an instruction after which the instruction being described is
  // believed to be located.  It might be the using instruction itself.
  uint32 base_instruction_result_id = 1;

  // The opcode for the instruction being described.
  uint32 target_instruction_opcode = 2;

  // The number of matching opcodes to skip over when searching from the base
  // instruction to the instruction being described.
  uint32 num_opcodes_to_ignore = 3;

}

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
  //   - enclosing_instruction = (
  //         base_instruction_result_id = 50,
  //         target_instruction_opcode = OpStore
  //         num_opcodes_to_ignore = 7
  //     )
  //   - in_operand_index = 1
  // 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 input operand index at which the use is expected.
  InstructionDescriptor enclosing_instruction = 2;

  uint32 in_operand_index = 3;

}

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 InstructionOperand {

  // Represents an operand to a SPIR-V instruction.

  // The type of the operand.
  uint32 operand_type = 1;

  // The data associated with the operand.  For most operands (e.g. ids,
  // storage classes and literals) this will be a single word.
  repeated uint32 operand_data = 2;

}

message Instruction {

  // Represents a SPIR-V instruction.

  // The instruction's opcode (e.g. OpLabel).
  uint32 opcode = 1;

  // The id of the instruction's result type; 0 if there is no result type.
  uint32 result_type_id = 2;

  // The id of the instruction's result; 0 if there is no result.
  uint32 result_id = 3;

  // Zero or more input operands.
  repeated InstructionOperand input_operand = 4;

}

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;
    FactDataSynonym data_synonym_fact = 2;
    FactBlockIsDead block_is_dead_fact = 3;
    FactFunctionIsLivesafe function_is_livesafe_fact = 4;
    FactPointeeValueIsIrrelevant pointee_value_is_irrelevant_fact = 5;
    FactIdEquation id_equation_fact = 6;
    FactIdIsIrrelevant id_is_irrelevant = 7;
  }
}

// Keep fact message types in alphabetical order:

message FactBlockIsDead {

  // Records the fact that a block is guaranteed to be dynamically unreachable.
  // This is useful because it informs the fuzzer that rather arbitrary changes
  // can be made to this block.

  uint32 block_id = 1;

}

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 FactDataSynonym {

  // Records the fact that the data held in two data descriptors are guaranteed
  // to be equal.  spirv-fuzz can use this to replace uses of one piece of data
  // with a known-to-be-equal piece of data.

  // Data descriptors guaranteed to hold identical data.
  DataDescriptor data1 = 1;

  DataDescriptor data2 = 2;

}

message FactFunctionIsLivesafe {

  // Records the fact that a function is guaranteed to be "livesafe", meaning
  // that it will not make out-of-bounds accesses, does not contain reachable
  // OpKill or OpUnreachable instructions, does not contain loops that will
  // execute for large numbers of iterations, and only invokes other livesafe
  // functions.

  uint32 function_id = 1;

}

message FactIdEquation {

  // Records the fact that the equation:
  //
  // lhs_id = opcode rhs_id[0] rhs_id[1] ... rhs_id[N-1]
  //
  // holds; e.g. that the equation:
  //
  // %12 = OpIAdd %13 %14
  //
  // holds in the case where lhs_id is 12, rhs_id is [13, 14], and the opcode is
  // OpIAdd.

  // The left-hand-side of the equation.
  uint32 lhs_id = 1;

  // A SPIR-V opcode, from a restricted set of instructions for which equation
  // facts make sense.
  uint32 opcode = 2;

  // The operands to the right-hand-side of the equation.
  repeated uint32 rhs_id = 3;

}

message FactIdIsIrrelevant {

  // Records a fact that |result_id| is irrelevant (i.e. it's usage doesn't
  // change the semantics of the module). This implies that a use of this id
  // can later be replaced with some other id of the same type, or the
  // definition of |result_id| can be changed so that it yields a different value.

  // An irrelevant id.
  uint32 result_id = 1;

}

message FactPointeeValueIsIrrelevant {

  // Records the fact that value of the data pointed to by a pointer id does
  // not influence the observable behaviour of the module.  This means that
  // arbitrary stores can be made through the pointer, and that nothing can be
  // guaranteed about the values that are loaded via the pointer.

  // A result id of pointer type
  uint32 pointer_id = 1;

}

message AccessChainClampingInfo {

  // When making a function livesafe it is necessary to clamp the indices that
  // occur as operands to access chain instructions so that they are guaranteed
  // to be in bounds.  This message type allows an access chain instruction to
  // have an associated sequence of ids that are reserved for comparing an
  // access chain index with a bound (e.g. an array size), and selecting
  // between the access chain index (if it is within bounds) and the bound (if
  // it is not).
  //
  // This allows turning an instruction of the form:
  //
  // %result = OpAccessChain %type %object ... %index ...
  //
  // into:
  //
  // %t1 = OpULessThanEqual %bool %index %bound_minus_one
  // %t2 = OpSelect %int_type %t1 %index %bound_minus_one
  // %result = OpAccessChain %type %object ... %t2 ...

  // The result id of an OpAccessChain or OpInBoundsAccessChain instruction.
  uint32 access_chain_id = 1;

  // A series of pairs of fresh ids, one per access chain index, for the results
  // of a compare instruction and a select instruction, serving the roles of %t1
  // and %t2 in the above example.
  repeated UInt32Pair compare_and_select_ids = 2;

}

message LoopLimiterInfo {

  // Structure capturing the information required to manipulate a loop limiter
  // at a loop header.

  // The header for the loop.
  uint32 loop_header_id = 1;

  // A fresh id into which the loop limiter's current value can be loaded.
  uint32 load_id = 2;

  // A fresh id that can be used to increment the loaded value by 1.
  uint32 increment_id = 3;

  // A fresh id that can be used to compare the loaded value with the loop
  // limit.
  uint32 compare_id = 4;

  // A fresh id that can be used to compute the conjunction or disjunction of
  // an original loop exit condition with |compare_id|, if the loop's back edge
  // block can conditionally exit the loop.
  uint32 logical_op_id = 5;

  // A sequence of ids suitable for extending OpPhi instructions of the loop
  // merge block if it did not previously have an incoming edge from the loop
  // back edge block.
  repeated uint32 phi_id = 6;

}

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;
    TransformationReplaceIdWithSynonym replace_id_with_synonym = 13;
    TransformationSetSelectionControl set_selection_control = 14;
    TransformationCompositeConstruct composite_construct = 15;
    TransformationSetLoopControl set_loop_control = 16;
    TransformationSetFunctionControl set_function_control = 17;
    TransformationAddNoContractionDecoration add_no_contraction_decoration = 18;
    TransformationSetMemoryOperandsMask set_memory_operands_mask = 19;
    TransformationCompositeExtract composite_extract = 20;
    TransformationVectorShuffle vector_shuffle = 21;
    TransformationOutlineFunction outline_function = 22;
    TransformationMergeBlocks merge_blocks = 23;
    TransformationAddTypeVector add_type_vector = 24;
    TransformationAddTypeArray add_type_array = 25;
    TransformationAddTypeMatrix add_type_matrix = 26;
    TransformationAddTypeStruct add_type_struct = 27;
    TransformationAddTypeFunction add_type_function = 28;
    TransformationAddConstantComposite add_constant_composite = 29;
    TransformationAddGlobalVariable add_global_variable = 30;
    TransformationAddGlobalUndef add_global_undef = 31;
    TransformationAddFunction add_function = 32;
    TransformationAddDeadBlock add_dead_block = 33;
    TransformationAddLocalVariable add_local_variable = 34;
    TransformationLoad load = 35;
    TransformationStore store = 36;
    TransformationFunctionCall function_call = 37;
    TransformationAccessChain access_chain = 38;
    TransformationEquationInstruction equation_instruction = 39;
    TransformationSwapCommutableOperands swap_commutable_operands = 40;
    TransformationPermuteFunctionParameters permute_function_parameters = 41;
    TransformationToggleAccessChainInstruction toggle_access_chain_instruction = 42;
    TransformationAddConstantNull add_constant_null = 43;
    TransformationComputeDataSynonymFactClosure compute_data_synonym_fact_closure = 44;
    TransformationAdjustBranchWeights adjust_branch_weights = 45;
    TransformationPushIdThroughVariable push_id_through_variable = 46;
    TransformationAddSpecConstantOp add_spec_constant_op = 47;
    TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 48;
    TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 49;
    TransformationPermutePhiOperands permute_phi_operands = 50;
    TransformationAddParameter add_parameter = 51;
    TransformationAddCopyMemory add_copy_memory = 52;
    TransformationInvertComparisonOperator invert_comparison_operator = 53;
    TransformationAddImageSampleUnusedComponents add_image_sample_unused_components = 54;
    TransformationReplaceParameterWithGlobal replace_parameter_with_global = 55;
    TransformationRecordSynonymousConstants record_synonymous_constants = 56;
    TransformationAddSynonym add_synonym = 57;
    TransformationAddRelaxedDecoration add_relaxed_decoration = 58;
    TransformationReplaceParamsWithStruct replace_params_with_struct = 59;
    TransformationReplaceCopyObjectWithStoreLoad replace_copy_object_with_store_load = 60;
    TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61;
    TransformationReplaceLoadStoreWithCopyMemory replace_load_store_with_copy_memory = 62;
    TransformationAddLoopPreheader add_loop_preheader = 63;
    TransformationMoveInstructionDown move_instruction_down = 64;
    // Add additional option using the next available number.
  }
}

// Keep transformation message types in alphabetical order:

message TransformationAccessChain {

  // Adds an access chain instruction based on a given pointer and indices.

  // When accessing a struct, the corresponding indices must be 32-bit integer constants.
  // For any other composite, the indices can be any 32-bit integer, and the transformation
  // adds two instructions for each such index to clamp it to the bound, as follows:
  //
  // %t1 = OpULessThanEqual %bool %index %bound_minus_one
  // %t2 = OpSelect %int_type %t1 %index %bound_minus_one

  // Result id for the access chain
  uint32 fresh_id = 1;

  // The pointer from which the access chain starts
  uint32 pointer_id = 2;

  // Zero or more access chain indices
  repeated uint32 index_id = 3;

  // A descriptor for an instruction in a block before which the new
  // OpAccessChain instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 4;

  // Additional fresh ids, required to clamp index variables. A pair is needed
  // for each access to a non-struct composite.
  repeated UInt32Pair fresh_ids_for_clamping = 5;

}

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.
  // Also, creates an IdIsIrrelevant fact about |fresh_id| if |is_irrelevant| is true.

  uint32 fresh_id = 1;
  bool is_true = 2;

  // If the constant should be marked as irrelevant.
  bool is_irrelevant = 3;

}

message TransformationAddConstantComposite {

  // Adds a constant of the given composite type to the module.
  // Also, creates an IdIsIrrelevant fact about |fresh_id| if
  // |is_irrelevant| is true.

  // Fresh id for the composite
  uint32 fresh_id = 1;

  // A composite type id
  uint32 type_id = 2;

  // Constituent ids for the composite
  repeated uint32 constituent_id = 3;

  // If the constant should be marked as irrelevant.
  bool is_irrelevant = 4;

}

message TransformationAddConstantNull {

  // Adds a null constant.

  // Id for the constant
  uint32 fresh_id = 1;

  // Type of the constant
  uint32 type_id = 2;

}

message TransformationAddConstantScalar {

  // Adds a constant of the given scalar type.
  // Also, creates an IdIsIrrelevant fact about
  // |fresh_id| if |is_irrelevant| is true.

  // 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;

  // If the constant should be marked as irrelevant.
  bool is_irrelevant = 4;

}

message TransformationAddCopyMemory {

  // Adds an OpCopyMemory instruction into the module.
  // Creates either a global or a local variable (based on
  // |storage_class| field) to copy the target into.

  // OpCopyMemory will be inserted before this instruction.
  InstructionDescriptor instruction_descriptor = 1;

  // Fresh id to copy memory into.
  uint32 fresh_id = 2;

  // Source to copy memory from.
  uint32 source_id = 3;

  // Storage class for the target variable. Can be either Function or Private.
  uint32 storage_class = 4;

  // Result id for the variable's initializer operand. Its type must be equal to
  // variable's pointee type.
  uint32 initializer_id = 5;

}

message TransformationAddDeadBlock {

  // Adds a new block to the module that is statically reachable from an
  // existing block, but dynamically unreachable.

  // Fresh id for the dead block
  uint32 fresh_id = 1;

  // Id of an existing block terminated with OpBranch, such that this OpBranch
  // can be replaced with an OpBranchConditional to its exiting successor or
  // the dead block
  uint32 existing_block = 2;

  // Determines whether the condition associated with the OpBranchConditional
  // is true or false
  bool condition_value = 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 TransformationAddFunction {

  // Adds a SPIR-V function to the module.

  // The series of instructions that comprise the function.
  repeated Instruction instruction = 1;

  // True if and only if the given function should be made livesafe (see
  // FactFunctionIsLivesafe for definition).
  bool is_livesafe = 2;

  // Fresh id for a new variable that will serve as a "loop limiter" for the
  // function; only relevant if |is_livesafe| holds.
  uint32 loop_limiter_variable_id = 3;

  // Id of an existing unsigned integer constant providing the maximum value
  // that the loop limiter can reach before the loop is broken from; only
  // relevant if |is_livesafe| holds.
  uint32 loop_limit_constant_id = 4;

  // Fresh ids for each loop in the function that allow the loop limiter to be
  // manipulated; only relevant if |is_livesafe| holds.
  repeated LoopLimiterInfo loop_limiter_info = 5;

  // Id of an existing global value with the same return type as the function
  // that can be used to replace OpKill and OpReachable instructions with
  // ReturnValue instructions.  Ignored if the function has void return type.
  uint32 kill_unreachable_return_value_id = 6;

  // A mapping (represented as a sequence) from every access chain result id in
  // the function to the ids required to clamp its indices to ensure they are in
  // bounds.
  repeated AccessChainClampingInfo access_chain_clamping_info = 7;

}

message TransformationAddGlobalUndef {

  // Adds an undefined value of a given type to the module at global scope.

  // Fresh id for the undefined value
  uint32 fresh_id = 1;

  // The type of the undefined value
  uint32 type_id = 2;

}

message TransformationAddGlobalVariable {

  // Adds a global variable of the given type to the module, with Private or
  // Workgroup storage class, and optionally (for the Private case) with an
  // initializer.

  // Fresh id for the global variable
  uint32 fresh_id = 1;

  // The type of the global variable
  uint32 type_id = 2;

  uint32 storage_class = 3;

  // Initial value of the variable
  uint32 initializer_id = 4;

  // True if and only if the behaviour of the module should not depend on the
  // value of the variable, in which case stores to the variable can be
  // performed in an arbitrary fashion.
  bool value_is_irrelevant = 5;

}

message TransformationAddImageSampleUnusedComponents {

  // A transformation that adds unused components to an image sample coordinate.

  // An vector id with the original coordinate and the unused components.
  uint32 coordinate_with_unused_components_id = 1;

  // A descriptor for an image sample instruction.
  InstructionDescriptor instruction_descriptor = 2;

}

message TransformationAddLocalVariable {

  // Adds a local variable of the given type (which must be a pointer with
  // Function storage class) to the given function, initialized to the given
  // id.

  // Fresh id for the local variable
  uint32 fresh_id = 1;

  // The type of the local variable
  uint32 type_id = 2;

  // The id of the function to which the local variable should be added
  uint32 function_id = 3;

  // Initial value of the variable
  uint32 initializer_id = 4;

  // True if and only if the behaviour of the module should not depend on the
  // value of the variable, in which case stores to the variable can be
  // performed in an arbitrary fashion.
  bool value_is_irrelevant = 5;

}

message TransformationAddLoopPreheader {

  // A transformation that adds a loop preheader block before the given loop header.

  // The id of the loop header block
  uint32 loop_header_block = 1;

  // A fresh id for the preheader block
  uint32 fresh_id = 2;

  // Fresh ids for splitting the OpPhi instructions in the header.
  // A new OpPhi instruction in the preheader is needed for each OpPhi instruction in the header,
  // if the header has more than one predecessor outside of the loop.
  // This allows turning instructions of the form:
  //
  //   %loop_header_block = OpLabel
  //                 %id1 = OpPhi %type %val1 %pred1_id %val2 %pred2_id %val3 %backedge_block_id
  //
  // into:
  //            %fresh_id = OpLabel
  //             %phi_id1 = OpPhi %type %val1 %pred1_id %val2 %pred2_id
  //                        OpBranch %header_id
  //   %loop_header_block = OpLabel
  //                 %id1 = OpPhi %type %phi_id1 %fresh_id %val3 %backedge_block_id
  repeated uint32 phi_id = 3;

}

message TransformationAddNoContractionDecoration {

  // Applies OpDecorate NoContraction to the given result id

  // Result id to be decorated
  uint32 result_id = 1;

}

message TransformationAddParameter {

  // Adds a new parameter into the function.

  // Result id of the function to add parameters to.
  uint32 function_id = 1;

  // Fresh id for a new parameter.
  uint32 parameter_fresh_id = 2;

  // Result id of the instruction, used to initializer new parameter
  // in function calls. Type id of that instruction is the type id of the parameter.
  // It may not be OpTypeVoid.
  uint32 initializer_id = 3;

  // A fresh id for a new function type. This might not be used
  // if a required function type already exists or if we can change
  // the old function type.
  uint32 function_type_fresh_id = 4;

}

message TransformationAddRelaxedDecoration {

  // Applies OpDecorate RelaxedPrecision to the given result id

  // Result id to be decorated
  uint32 result_id = 1;

}

message TransformationAddSpecConstantOp {

  // Adds OpSpecConstantOp into the module.

  // Result id for the new instruction.
  uint32 fresh_id = 1;

  // Type id for the new instruction.
  uint32 type_id = 2;

  // Opcode operand of the OpSpecConstantOp instruction.
  uint32 opcode = 3;

  // Operands of the |opcode| instruction.
  repeated InstructionOperand operand = 4;

}

message TransformationAddSynonym {

  // Adds a |synonymous_instruction| before |insert_before| instruction with
  // and creates a fact that |result_id| and the result id of |synonymous_instruction|
  // are synonymous.

  // Result id of the first synonym.
  uint32 result_id = 1;

  // Type of the synonym to apply. Some types might produce instructions
  // with commutative operands. Such types do not specify the order of the
  // operands since we have a special transformation to swap commutable operands.
  //
  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3499):
  //  Consider adding more types here.
  enum SynonymType {
    // New synonym is derived by adding zero to the |result_id|.
    ADD_ZERO = 0;

    // New synonym is derived by subtracting zero from the |result_id|.
    SUB_ZERO = 1;

    // New synonym is derived by multiplying |result_id| by one.
    MUL_ONE = 2;

    // New synonym is derived by applying OpCopyObject instruction to |result_id|.
    COPY_OBJECT = 3;

    // New synonym is derived by applying OpLogicalOr to |result_id| with the second
    // operand being 'false'.
    LOGICAL_OR = 4;

    // New synonym is derived by applying OpLogicalAnd to |result_id| with the second
    // operand being 'true'.
    LOGICAL_AND = 5;
  }

  // Type of the synonym to create. See SynonymType for more details.
  SynonymType synonym_type = 2;

  // Fresh result id for a created synonym.
  uint32 synonym_fresh_id = 3;

  // An instruction to insert a new synonym before.
  InstructionDescriptor insert_before = 4;

}

message TransformationAddTypeArray {

  // Adds an array type of the given element type and size to the module

  // Fresh id for the array type
  uint32 fresh_id = 1;

  // The array's element type
  uint32 element_type_id = 2;

  // The array's size
  uint32 size_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 TransformationAddTypeFunction {

  // Adds a function type to the module

  // Fresh id for the function type
  uint32 fresh_id = 1;

  // The function's return type
  uint32 return_type_id = 2;

  // The function's argument types
  repeated uint32 argument_type_id = 3;

}

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 TransformationAddTypeMatrix {

  // Adds a matrix type to the module

  // Fresh id for the matrix type
  uint32 fresh_id = 1;

  // The matrix's column type, which must be a floating-point vector (as per
  // the "data rules" in the SPIR-V specification).
  uint32 column_type_id = 2;

  // The matrix's column count
  uint32 column_count = 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 TransformationAddTypeStruct {

  // Adds a struct type to the module

  // Fresh id for the struct type
  uint32 fresh_id = 1;

  // The struct's member types
  repeated uint32 member_type_id = 3;

}

message TransformationAddTypeVector {

  // Adds a vector type to the module

  // Fresh id for the vector type
  uint32 fresh_id = 1;

  // The vector's component type
  uint32 component_type_id = 2;

  // The vector's component count
  uint32 component_count = 3;

}

message TransformationAdjustBranchWeights {

  // A transformation that adjusts the branch weights
  // of a branch conditional instruction.

  // A descriptor for a branch conditional instruction.
  InstructionDescriptor instruction_descriptor = 1;

  // Branch weights of a branch conditional instruction.
  UInt32Pair branch_weights = 2;

}

message TransformationCompositeConstruct {

  // A transformation that introduces an OpCompositeConstruct instruction to
  // make a composite object.

  // Id of the type of the composite that is to be constructed
  uint32 composite_type_id = 1;

  // Ids of the objects that will form the components of the composite
  repeated uint32 component = 2;

  // A descriptor for an instruction in a block before which the new
  // OpCompositeConstruct instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 3;

  // A fresh id for the composite object
  uint32 fresh_id = 4;

}

message TransformationCompositeExtract {

  // A transformation that adds an instruction to extract an element from a
  // composite.

  // A descriptor for an instruction in a block before which the new
  // OpCompositeExtract instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 1;

  // Result id for the extract operation.
  uint32 fresh_id = 2;

  // Id of the composite from which data is to be extracted.
  uint32 composite_id = 3;

  // Indices that indicate which part of the composite should be extracted.
  repeated uint32 index = 4;

}

message TransformationComputeDataSynonymFactClosure {

  // A transformation that impacts the fact manager only, forcing a computation
  // of the closure of data synonym facts, so that e.g. if the components of
  // vectors v and w are known to be pairwise synonymous, it is deduced that v
  // and w are themselves synonymous.

  // When searching equivalence classes for implied facts, equivalence classes
  // larger than this size will be skipped.
  uint32 maximum_equivalence_class_size = 1;

}

message TransformationEquationInstruction {

  // A transformation that adds an instruction to the module that defines an
  // equation between its result id and input operand ids, such that the
  // equation is guaranteed to hold at any program point where all ids involved
  // are available (i.e. at any program point dominated by the instruction).

  // The result id of the new instruction
  uint32 fresh_id = 1;

  // The instruction's opcode
  uint32 opcode = 2;

  // The input operands to the instruction
  repeated uint32 in_operand_id = 3;

  // A descriptor for an instruction in a block before which the new
  // instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 4;

}

message TransformationFunctionCall {

  // A transformation that introduces an OpFunctionCall instruction.  The call
  // must not make the module's call graph cyclic.  Beyond that, if the call
  // is in a dead block it can be to any function with arbitrary suitably-typed
  // arguments; otherwise it must be to a livesafe function, with injected
  // variables as pointer arguments and arbitrary non-pointer arguments.

  // A fresh id for the result of the call
  uint32 fresh_id = 1;

  // Id of the function to be called
  uint32 callee_id = 2;

  // Ids for arguments to the function
  repeated uint32 argument_id = 3;

  // A descriptor for an instruction in a block before which the new
  // OpFunctionCall instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 4;

}

message TransformationInvertComparisonOperator {

  // For some instruction with result id |operator_id| that
  // represents a binary comparison operator (e.g. <, >, <=), this transformation
  // will replace that instruction's result id with |fresh_id|,
  // invert the opcode (< will become >=) and insert OpLogicalNot
  // instruction with result id |operator_id| below.

  // Result id of the instruction to invert.
  uint32 operator_id = 1;

  // Fresh id that will be used by the operator after the inversion.
  uint32 fresh_id = 2;

}

message TransformationLoad {

  // Transformation that adds an OpLoad instruction from a pointer into an id.

  // The result of the load instruction
  uint32 fresh_id = 1;

  // The pointer to be loaded from
  uint32 pointer_id = 2;

  // A descriptor for an instruction in a block before which the new OpLoad
  // instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 3;

}

message TransformationMergeBlocks {

  // A transformation that merges a block with its predecessor.

  // The id of the block that is to be merged with its predecessor; the merged
  // block will have the *predecessor's* id.
  uint32 block_id = 1;

}

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 TransformationMoveInstructionDown {

  // Swaps |instruction| with the next instruction in the block.

  // The instruction to move down.
  InstructionDescriptor instruction = 1;

}

message TransformationOutlineFunction {

  // A transformation that outlines a single-entry single-exit region of a
  // control flow graph into a separate function, and replaces the region with
  // a call to that function.

  // Id of the entry block of the single-entry single-exit region to be outlined
  uint32 entry_block = 1;

  // Id of the exit block of the single-entry single-exit region to be outlined
  uint32 exit_block = 2;

  // Id of a struct that will store the return values of the new function
  uint32 new_function_struct_return_type_id = 3;

  // A fresh id for the type of the outlined function
  uint32 new_function_type_id = 4;

  // A fresh id for the outlined function itself
  uint32 new_function_id = 5;

  // A fresh id to represent the block in the outlined function that represents
  // the first block of the outlined region.
  uint32 new_function_region_entry_block = 6;

  // A fresh id for the result of the OpFunctionCall instruction that will call
  // the outlined function
  uint32 new_caller_result_id = 7;

  // A fresh id to capture the return value of the outlined function - the
  // argument to OpReturn
  uint32 new_callee_result_id = 8;

  // Ids defined outside the region and used inside the region will become
  // parameters to the outlined function.  This is a mapping from used ids to
  // fresh parameter ids.
  repeated UInt32Pair input_id_to_fresh_id = 9;

  // Ids defined inside the region and used outside the region will become
  // fresh ids defined by the outlined function, which get copied into the
  // function's struct return value and then copied into their destination ids
  // by the caller.  This is a mapping from original ids to corresponding fresh
  // ids.
  repeated UInt32Pair output_id_to_fresh_id = 10;

}

message TransformationPermuteFunctionParameters {

  // A transformation that, given a non-entry-point function taking n
  // parameters and a permutation of the set [0, n-1]:
  //   - Introduces a new function type that is the same as the original
  //     function's type but with the order of arguments permuted
  //     (only if it doesn't already exist)
  //   - Changes the type of the function to this type
  //   - Adjusts all calls to the function so that their arguments are permuted

  // Function, whose parameters will be permuted
  uint32 function_id = 1;

  // Fresh id for a new type of the function. This might not be used
  // if a required function type already exists or if we can change
  // the old function type.
  uint32 function_type_fresh_id = 2;

  // An array of size |n|, where |n| is a number of arguments to a function
  // with |function_id|. For each i: 0 <= permutation[i] < n.
  //
  // i-th element of this array contains a position for an i-th
  // function's argument (i.e. i-th argument will be permutation[i]-th
  // after running this transformation)
  repeated uint32 permutation = 3;

}

message TransformationPermutePhiOperands {

  // Permutes operands of some OpPhi instruction.

  // Result id of the instruction to apply the transformation to.
  uint32 result_id = 1;

  // A sequence of numbers in the range [0, n/2 - 1] where |n| is the number
  // of operands of the OpPhi instruction with |result_id|.
  repeated uint32 permutation = 2;

}

message TransformationPushIdThroughVariable {

  // A transformation that makes |value_synonym_id| and |value_id| to be
  // synonymous by storing |value_id| into |variable_id| and
  // loading |variable_id| to |value_synonym_id|.

  // The value to be stored.
  uint32 value_id = 1;

  // A fresh id for the result of the load instruction.
  uint32 value_synonym_id = 2;

  // A fresh id for the variable to be stored to.
  uint32 variable_id = 3;

  // Constant to initialize the variable from.
  uint32 initializer_id = 4;

  // The variable storage class (global or local).
  uint32 variable_storage_class = 5;

  // A descriptor for an instruction which the new OpStore
  // and OpLoad instructions might be inserted before.
  InstructionDescriptor instruction_descriptor = 6;

}

message TransformationRecordSynonymousConstants {

  // A transformation that, given the IDs to two synonymous constants,
  // records the fact that they are synonymous. The module is not changed.
  // Two constants are synonymous if:
  // - they have the same type (ignoring the presence of integer sign)
  // - they have the same opcode (one of OpConstant, OpConstantTrue,
  //   OpConstantFalse, OpConstantNull)
  // - they have the same value
  // If the types are the same, OpConstantNull is equivalent to
  // OpConstantFalse or OpConstant with value zero.

  // The id of a constant
  uint32 constant1_id = 1;

  // The id of the synonym
  uint32 constant2_id = 2;

}

message TransformationReplaceParameterWithGlobal {

  // Removes parameter with result id |parameter_id| from its function
  // and creates a global variable to pass its value to the function instead.

  // Fresh id for a new function type. This might not be used if a required
  // function type already exists or if we can change the old function type.
  uint32 function_type_fresh_id = 2;

  // Result id of the OpFunctionParameter instruction to remove.
  uint32 parameter_id = 3;

  // Fresh id of a global variable used to pass parameter's value to the function.
  uint32 global_variable_fresh_id = 4;

}

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 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 TransformationReplaceCopyMemoryWithLoadStore {

   // A transformation that replaces instructions OpCopyMemory with loading
   // the source variable to an intermediate value and storing this value into the
   // target variable of the original OpCopyMemory instruction.

   // The intermediate value.
   uint32 fresh_id = 1;

   // The instruction descriptor to OpCopyMemory. It is necessary, because
   // OpCopyMemory doesn't have a result id.
   InstructionDescriptor copy_memory_instruction_descriptor = 2;
}

message TransformationReplaceCopyObjectWithStoreLoad {

  // A transformation that replaces instruction OpCopyObject with
  // storing into a new variable and immediately loading from this
  // variable to |result_id| of the original OpCopyObject instruction.

  // The result id of initial OpCopyObject instruction
  uint32 copy_object_result_id = 1;

  // A fresh id for the variable to be stored to.
  uint32 fresh_variable_id = 2;

  // The variable storage class (Function or Private).
  uint32 variable_storage_class = 3;

  // Constant to initialize the variable with.
  uint32 variable_initializer_id = 4;
}

message TransformationReplaceIdWithSynonym {

  // Replaces a use of an id with an id that is known to be synonymous, e.g.
  // because it was obtained via applying OpCopyObject

  // The id use that is to be replaced
  IdUseDescriptor id_use_descriptor = 1;

  // The synonymous id
  uint32 synonymous_id = 2;

}

message TransformationReplaceLinearAlgebraInstruction {

  // Replaces a linear algebra instruction with its
  // mathematical definition.

  // The fresh ids needed to apply the transformation.
  repeated uint32 fresh_ids = 1;

  // A descriptor for a linear algebra instruction.
  InstructionDescriptor instruction_descriptor = 2;

}

message TransformationReplaceLoadStoreWithCopyMemory {
  // A transformation that takes a pair of instruction descriptors
  // to OpLoad and OpStore that have the same intermediate value
  // and replaces the OpStore with an equivalent OpCopyMemory.

  // The instruction descriptor to OpLoad
  InstructionDescriptor load_instruction_descriptor = 1;

  // The instruction descriptor to OpStore
  InstructionDescriptor store_instruction_descriptor = 2;
}

message TransformationReplaceParamsWithStruct {

  // Replaces parameters of the function with a struct containing
  // values of those parameters.

  // Result ids of parameters to replace.
  repeated uint32 parameter_id = 1;

  // Fresh id for a new function type. This might be unused if the required type
  // already exists in the module or if we can change the old type.
  uint32 fresh_function_type_id = 2;

  // Fresh id for a new struct function parameter to be used as a replacement.
  uint32 fresh_parameter_id = 3;

  // Fresh ids for struct objects containing values of replaced parameters.
  // This field contains a fresh id for at least every result id of a relevant
  // OpFunctionCall instruction.
  repeated UInt32Pair caller_id_to_fresh_composite_id = 4;

}

message TransformationSetFunctionControl {

  // A transformation that sets the function control operand of an OpFunction
  // instruction.

  // The result id of an OpFunction instruction
  uint32 function_id = 1;

  // The value to which the 'function control' operand should be set.
  uint32 function_control = 2;

}

message TransformationSetLoopControl {

  // A transformation that sets the loop control operand of an OpLoopMerge
  // instruction.

  // The id of a basic block that should contain OpLoopMerge
  uint32 block_id = 1;

  // The value to which the 'loop control' operand should be set.
  // This must be a legal loop control mask.
  uint32 loop_control = 2;

  // Provides a peel count value for the loop.  Used if and only if the
  // PeelCount bit is set.  Must be zero if the PeelCount bit is not set (can
  // still be zero if this bit is set).
  uint32 peel_count = 3;

  // Provides a partial count value for the loop.  Used if and only if the
  // PartialCount bit is set.  Must be zero if the PartialCount bit is not set
  // (can still be zero if this bit is set).
  uint32 partial_count = 4;

}

message TransformationSetMemoryOperandsMask {

  // A transformation that sets the memory operands mask of a memory access
  // instruction.

  // A descriptor for a memory access instruction, e.g. an OpLoad
  InstructionDescriptor memory_access_instruction = 1;

  // A mask of memory operands to be applied to the instruction.  It must be the
  // same as the original mask, except that Volatile can be added, and
  // Nontemporal can be added or removed.
  uint32 memory_operands_mask = 2;

  // Some memory access instructions allow more than one mask to be specified;
  // this field indicates which mask should be set
  uint32 memory_operands_mask_index = 3;

}

message TransformationSetSelectionControl {

  // A transformation that sets the selection control operand of an
  // OpSelectionMerge instruction.

  // The id of a basic block that should contain OpSelectionMerge
  uint32 block_id = 1;

  // The value to which the 'selection control' operand should be set.
  // Although technically 'selection control' is a literal mask that can be
  // some combination of 'None', 'Flatten' and 'DontFlatten', the combination
  // 'Flatten | DontFlatten' does not make sense and is not allowed here.
  uint32 selection_control = 2;

}

message TransformationSplitBlock {

  // A transformation that splits a basic block into two basic blocks

  // A descriptor for an instruction such that the block containing the
  // described instruction should be split right before the instruction.
  InstructionDescriptor instruction_to_split_before = 1;

  // 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 = 2;

}

message TransformationStore {

  // Transformation that adds an OpStore instruction of an id to a pointer.

  // The pointer to be stored to
  uint32 pointer_id = 1;

  // The value to be stored
  uint32 value_id = 2;

  // A descriptor for an instruction in a block before which the new OpStore
  // instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 3;

}

message TransformationSwapCommutableOperands {

  // A transformation that swaps the operands of a commutative instruction.

  // A descriptor for a commutative instruction
  InstructionDescriptor instruction_descriptor = 1;

}

message TransformationSwapConditionalBranchOperands {

  // Swaps label ids in OpBranchConditional instruction.
  // Additionally, inverts the guard and swaps branch weights
  // if present.

  // Descriptor of the instruction to swap operands of.
  InstructionDescriptor instruction_descriptor = 1;

  // Fresh result id for the OpLogicalNot instruction, used
  // to invert the guard.
  uint32 fresh_id = 2;

}

message TransformationToggleAccessChainInstruction {

  // A transformation that toggles an access chain instruction.

  // A descriptor for an access chain instruction
  InstructionDescriptor instruction_descriptor = 1;

}

message TransformationVectorShuffle {

  // A transformation that adds a vector shuffle instruction.

  // A descriptor for an instruction in a block before which the new
  // OpVectorShuffle instruction should be inserted
  InstructionDescriptor instruction_to_insert_before = 1;

  // Result id for the shuffle operation.
  uint32 fresh_id = 2;

  // Id of the first vector operand.
  uint32 vector1 = 3;

  // Id of the second vector operand.
  uint32 vector2 = 4;

  // Indices that indicate which components of the input vectors should be used.
  repeated uint32 component = 5;

}
