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

// 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 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 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;
    TransformationSetSelectionControl set_selection_control = 15;
    TransformationCompositeConstruct composite_construct = 16;
    TransformationSetLoopControl set_loop_control = 17;
    TransformationSetFunctionControl set_function_control = 18;
    TransformationAddNoContractionDecoration add_no_contraction_decoration = 19;
    TransformationSetMemoryOperandsMask set_memory_operands_mask = 20;
    TransformationCompositeExtract composite_extract = 21;
    TransformationVectorShuffle vector_shuffle = 22;
    // 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 TransformationAddNoContractionDecoration {

  // Applies OpDecorate NoContraction to the given result id

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

}

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

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

  // A fresh id for the copied object
  uint32 fresh_id = 3;

}

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

}
