spirv-fuzz: Transformation to add a new function to a module (#3114)
This adds a large transformation that can add a new function to a
SPIR-V module. This paves the way for donation of code from one
module to another.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 1b1da9b..bc7d453 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -55,6 +55,7 @@
fuzzer_util.h
id_use_descriptor.h
instruction_descriptor.h
+ instruction_message.h
protobufs/spirvfuzz_protobufs.h
pseudo_random_generator.h
random_generator.h
@@ -66,6 +67,7 @@
transformation_add_constant_scalar.h
transformation_add_dead_break.h
transformation_add_dead_continue.h
+ transformation_add_function.h
transformation_add_global_undef.h
transformation_add_global_variable.h
transformation_add_no_contraction_decoration.h
@@ -121,6 +123,7 @@
fuzzer_util.cpp
id_use_descriptor.cpp
instruction_descriptor.cpp
+ instruction_message.cpp
pseudo_random_generator.cpp
random_generator.cpp
replayer.cpp
@@ -131,6 +134,7 @@
transformation_add_constant_scalar.cpp
transformation_add_dead_break.cpp
transformation_add_dead_continue.cpp
+ transformation_add_function.cpp
transformation_add_global_undef.cpp
transformation_add_global_variable.cpp
transformation_add_no_contraction_decoration.cpp
diff --git a/source/fuzz/instruction_message.cpp b/source/fuzz/instruction_message.cpp
new file mode 100644
index 0000000..b217a21
--- /dev/null
+++ b/source/fuzz/instruction_message.cpp
@@ -0,0 +1,69 @@
+// 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/instruction_message.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+protobufs::Instruction MakeInstructionMessage(
+ SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
+ const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>&
+ input_operands) {
+ protobufs::Instruction result;
+ result.set_opcode(opcode);
+ result.set_result_type_id(result_type_id);
+ result.set_result_id(result_id);
+ for (auto& operand : input_operands) {
+ auto operand_message = result.add_input_operand();
+ operand_message->set_operand_type(operand.first);
+ for (auto operand_word : operand.second) {
+ operand_message->add_operand_data(operand_word);
+ }
+ }
+ return result;
+}
+
+std::unique_ptr<opt::Instruction> InstructionFromMessage(
+ opt::IRContext* ir_context,
+ const protobufs::Instruction& instruction_message) {
+ // First, update the module's id bound with respect to the new instruction,
+ // if it has a result id.
+ if (instruction_message.result_id()) {
+ fuzzerutil::UpdateModuleIdBound(ir_context,
+ instruction_message.result_id());
+ }
+ // Now create a sequence of input operands from the input operand data in the
+ // protobuf message.
+ opt::Instruction::OperandList in_operands;
+ for (auto& operand_message : instruction_message.input_operand()) {
+ opt::Operand::OperandData operand_data;
+ for (auto& word : operand_message.operand_data()) {
+ operand_data.push_back(word);
+ }
+ in_operands.push_back(
+ {static_cast<spv_operand_type_t>(operand_message.operand_type()),
+ operand_data});
+ }
+ // Create and return the instruction.
+ return MakeUnique<opt::Instruction>(
+ ir_context, static_cast<SpvOp>(instruction_message.opcode()),
+ instruction_message.result_type_id(), instruction_message.result_id(),
+ in_operands);
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/instruction_message.h b/source/fuzz/instruction_message.h
new file mode 100644
index 0000000..ed339aa
--- /dev/null
+++ b/source/fuzz/instruction_message.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef SOURCE_FUZZ_INSTRUCTION_MESSAGE_H_
+#define SOURCE_FUZZ_INSTRUCTION_MESSAGE_H_
+
+#include <memory>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Creates an Instruction protobuf message from its component parts.
+protobufs::Instruction MakeInstructionMessage(
+ SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
+ const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>&
+ input_operands);
+
+// Creates and returns an opt::Instruction from protobuf message
+// |instruction_message|, relative to |ir_context|. In the process, the module
+// id bound associated with |ir_context| is updated to be at least as large as
+// the result id (if any) associated with the new instruction.
+std::unique_ptr<opt::Instruction> InstructionFromMessage(
+ opt::IRContext* ir_context,
+ const protobufs::Instruction& instruction_message);
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_INSTRUCTION_MESSAGE_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index f57486c..dbd1fb8 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -126,6 +126,37 @@
}
+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;
}
@@ -210,6 +241,7 @@
TransformationAddConstantComposite add_constant_composite = 30;
TransformationAddGlobalVariable add_global_variable = 31;
TransformationAddGlobalUndef add_global_undef = 32;
+ TransformationAddFunction add_function = 33;
// Add additional option using the next available number.
}
}
@@ -297,6 +329,15 @@
}
+message TransformationAddFunction {
+
+ // Adds a SPIR-V function to the module.
+
+ // The series of instructions that comprise the function.
+ repeated Instruction instruction = 1;
+
+}
+
message TransformationAddGlobalUndef {
// Adds an undefined value of a given type to the module at global scope.
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index aa886b9..1489f85 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -21,6 +21,7 @@
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_dead_break.h"
#include "source/fuzz/transformation_add_dead_continue.h"
+#include "source/fuzz/transformation_add_function.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_global_variable.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
@@ -72,6 +73,8 @@
case protobufs::Transformation::TransformationCase::kAddDeadContinue:
return MakeUnique<TransformationAddDeadContinue>(
message.add_dead_continue());
+ case protobufs::Transformation::TransformationCase::kAddFunction:
+ return MakeUnique<TransformationAddFunction>(message.add_function());
case protobufs::Transformation::TransformationCase::kAddGlobalUndef:
return MakeUnique<TransformationAddGlobalUndef>(
message.add_global_undef());
diff --git a/source/fuzz/transformation_add_function.cpp b/source/fuzz/transformation_add_function.cpp
new file mode 100644
index 0000000..5e53961
--- /dev/null
+++ b/source/fuzz/transformation_add_function.cpp
@@ -0,0 +1,156 @@
+// 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_add_function.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_message.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddFunction::TransformationAddFunction(
+ const spvtools::fuzz::protobufs::TransformationAddFunction& message)
+ : message_(message) {}
+
+TransformationAddFunction::TransformationAddFunction(
+ const std::vector<protobufs::Instruction>& instructions) {
+ for (auto& instruction : instructions) {
+ *message_.add_instruction() = instruction;
+ }
+}
+
+bool TransformationAddFunction::IsApplicable(
+ opt::IRContext* context,
+ const spvtools::fuzz::FactManager& /*unused*/) const {
+ // Because checking all the conditions for a function to be valid is a big
+ // job that the SPIR-V validator can already do, a "try it and see" approach
+ // is taken here.
+
+ // We first clone the current module, so that we can try adding the new
+ // function without risking wrecking |context|.
+ auto cloned_module = fuzzerutil::CloneIRContext(context);
+
+ // We try to add a function to the cloned module, which may fail if
+ // |message_.instruction| is not sufficiently well-formed.
+ if (!TryToAddFunction(cloned_module.get())) {
+ return false;
+ }
+ // Having managed to add the new function to the cloned module, we ascertain
+ // whether the cloned module is still valid. If it is, the transformation is
+ // applicable.
+ return fuzzerutil::IsValid(cloned_module.get());
+}
+
+void TransformationAddFunction::Apply(
+ opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+ auto success = TryToAddFunction(context);
+ assert(success && "The function should be successfully added.");
+ (void)(success); // Keep release builds happy (otherwise they may complain
+ // that |success| is not used).
+ context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddFunction::ToMessage() const {
+ protobufs::Transformation result;
+ *result.mutable_add_function() = message_;
+ return result;
+}
+
+bool TransformationAddFunction::TryToAddFunction(
+ opt::IRContext* context) const {
+ // This function returns false if |message_.instruction| was not well-formed
+ // enough to actually create a function and add it to |context|.
+
+ // A function must have at least some instructions.
+ if (message_.instruction().empty()) {
+ return false;
+ }
+
+ // A function must start with OpFunction.
+ auto function_begin = message_.instruction(0);
+ if (function_begin.opcode() != SpvOpFunction) {
+ return false;
+ }
+
+ // Make a function, headed by the OpFunction instruction.
+ std::unique_ptr<opt::Function> new_function = MakeUnique<opt::Function>(
+ InstructionFromMessage(context, function_begin));
+
+ // Keeps track of which instruction protobuf message we are currently
+ // considering.
+ uint32_t instruction_index = 1;
+ const auto num_instructions =
+ static_cast<uint32_t>(message_.instruction().size());
+
+ // Iterate through all function parameter instructions, adding parameters to
+ // the new function.
+ while (instruction_index < num_instructions &&
+ message_.instruction(instruction_index).opcode() ==
+ SpvOpFunctionParameter) {
+ new_function->AddParameter(InstructionFromMessage(
+ context, message_.instruction(instruction_index)));
+ instruction_index++;
+ }
+
+ // After the parameters, there needs to be a label.
+ if (instruction_index == num_instructions ||
+ message_.instruction(instruction_index).opcode() != SpvOpLabel) {
+ return false;
+ }
+
+ // Iterate through the instructions block by block until the end of the
+ // function is reached.
+ while (instruction_index < num_instructions &&
+ message_.instruction(instruction_index).opcode() != SpvOpFunctionEnd) {
+ // Invariant: we should always be at a label instruction at this point.
+ assert(message_.instruction(instruction_index).opcode() == SpvOpLabel);
+
+ // Make a basic block using the label instruction, with the new function
+ // as its parent.
+ std::unique_ptr<opt::BasicBlock> block =
+ MakeUnique<opt::BasicBlock>(InstructionFromMessage(
+ context, message_.instruction(instruction_index)));
+ block->SetParent(new_function.get());
+
+ // Consider successive instructions until we hit another label or the end
+ // of the function, adding each such instruction to the block.
+ instruction_index++;
+ while (instruction_index < num_instructions &&
+ message_.instruction(instruction_index).opcode() !=
+ SpvOpFunctionEnd &&
+ message_.instruction(instruction_index).opcode() != SpvOpLabel) {
+ block->AddInstruction(InstructionFromMessage(
+ context, message_.instruction(instruction_index)));
+ instruction_index++;
+ }
+ // Add the block to the new function.
+ new_function->AddBasicBlock(std::move(block));
+ }
+ // Having considered all the blocks, we should be at the last instruction and
+ // it needs to be OpFunctionEnd.
+ if (instruction_index != num_instructions - 1 ||
+ message_.instruction(instruction_index).opcode() != SpvOpFunctionEnd) {
+ return false;
+ }
+ // Set the function's final instruction, add the function to the module and
+ // report success.
+ new_function->SetFunctionEnd(
+ InstructionFromMessage(context, message_.instruction(instruction_index)));
+ context->AddFunction(std::move(new_function));
+ return true;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_add_function.h b/source/fuzz/transformation_add_function.h
new file mode 100644
index 0000000..fee2732
--- /dev/null
+++ b/source/fuzz/transformation_add_function.h
@@ -0,0 +1,70 @@
+// 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.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddFunction : public Transformation {
+ public:
+ explicit TransformationAddFunction(
+ const protobufs::TransformationAddFunction& message);
+
+ explicit TransformationAddFunction(
+ const std::vector<protobufs::Instruction>& instructions);
+
+ // - |message_.instruction| must correspond to a sufficiently well-formed
+ // sequence of instructions that a function can be created from them
+ // - Adding the created function to the module must lead to a valid module.
+ bool IsApplicable(opt::IRContext* context,
+ const FactManager& fact_manager) const override;
+
+ // Adds the function defined by |message_.instruction| to the module
+ void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ private:
+ // Attempts to create a function from the series of instructions in
+ // |message_.instruction| and add it to |context|. Returns false if this is
+ // not possible due to the messages not respecting the basic structure of a
+ // function, e.g. if there is no OpFunction instruction or no blocks; in this
+ // case |context| is left in an indeterminate state.
+ //
+ // Otherwise returns true. Whether |context| is valid after addition of the
+ // function depends on the contents of |message_.instruction|.
+ //
+ // Intended usage:
+ // - Perform a dry run of this method on a clone of a module, and use
+ // the validator to check whether the resulting module is valid. Working
+ // on a clone means it does not matter if the function fails to be cleanly
+ // added, or leads to an invalid module.
+ // - If the dry run succeeds, run the method on the real module of interest,
+ // to add the function.
+ bool TryToAddFunction(opt::IRContext* context) const;
+
+ protobufs::TransformationAddFunction message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_
diff --git a/test/fuzz/transformation_add_function_test.cpp b/test/fuzz/transformation_add_function_test.cpp
new file mode 100644
index 0000000..66130be
--- /dev/null
+++ b/test/fuzz/transformation_add_function_test.cpp
@@ -0,0 +1,447 @@
+// 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_add_function.h"
+#include "source/fuzz/instruction_message.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddFunctionTest, BasicTest) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFloat 32
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %8 %7 %9
+ %18 = OpConstant %8 0
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %37 = OpConstant %6 1
+ %42 = OpTypePointer Private %8
+ %43 = OpVariable %42 Private
+ %47 = OpConstant %8 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+
+ TransformationAddFunction transformation1(std::vector<protobufs::Instruction>(
+ {MakeInstructionMessage(
+ SpvOpFunction, 8, 13,
+ {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+ {SPV_OPERAND_TYPE_ID, {10}}}),
+ MakeInstructionMessage(SpvOpFunctionParameter, 7, 11, {}),
+ MakeInstructionMessage(SpvOpFunctionParameter, 9, 12, {}),
+ MakeInstructionMessage(SpvOpLabel, 0, 14, {}),
+ MakeInstructionMessage(
+ SpvOpVariable, 9, 17,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}),
+ MakeInstructionMessage(
+ SpvOpVariable, 7, 19,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {17}}, {SPV_OPERAND_TYPE_ID, {18}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {19}}, {SPV_OPERAND_TYPE_ID, {20}}}),
+ MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {21}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 21, {}),
+ MakeInstructionMessage(
+ SpvOpLoopMerge, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {23}},
+ {SPV_OPERAND_TYPE_ID, {24}},
+ {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}),
+ MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {25}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 25, {}),
+ MakeInstructionMessage(SpvOpLoad, 6, 26, {{SPV_OPERAND_TYPE_ID, {19}}}),
+ MakeInstructionMessage(SpvOpLoad, 6, 27, {{SPV_OPERAND_TYPE_ID, {11}}}),
+ MakeInstructionMessage(
+ SpvOpSLessThan, 28, 29,
+ {{SPV_OPERAND_TYPE_ID, {26}}, {SPV_OPERAND_TYPE_ID, {27}}}),
+ MakeInstructionMessage(SpvOpBranchConditional, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {29}},
+ {SPV_OPERAND_TYPE_ID, {22}},
+ {SPV_OPERAND_TYPE_ID, {23}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 22, {}),
+ MakeInstructionMessage(SpvOpLoad, 8, 30, {{SPV_OPERAND_TYPE_ID, {12}}}),
+ MakeInstructionMessage(SpvOpLoad, 6, 31, {{SPV_OPERAND_TYPE_ID, {19}}}),
+ MakeInstructionMessage(SpvOpConvertSToF, 8, 32,
+ {{SPV_OPERAND_TYPE_ID, {31}}}),
+ MakeInstructionMessage(
+ SpvOpFMul, 8, 33,
+ {{SPV_OPERAND_TYPE_ID, {30}}, {SPV_OPERAND_TYPE_ID, {32}}}),
+ MakeInstructionMessage(SpvOpLoad, 8, 34, {{SPV_OPERAND_TYPE_ID, {17}}}),
+ MakeInstructionMessage(
+ SpvOpFAdd, 8, 35,
+ {{SPV_OPERAND_TYPE_ID, {34}}, {SPV_OPERAND_TYPE_ID, {33}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {17}}, {SPV_OPERAND_TYPE_ID, {35}}}),
+ MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {24}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 24, {}),
+ MakeInstructionMessage(SpvOpLoad, 6, 36, {{SPV_OPERAND_TYPE_ID, {19}}}),
+ MakeInstructionMessage(
+ SpvOpIAdd, 6, 38,
+ {{SPV_OPERAND_TYPE_ID, {36}}, {SPV_OPERAND_TYPE_ID, {37}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {19}}, {SPV_OPERAND_TYPE_ID, {38}}}),
+ MakeInstructionMessage(SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {21}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 23, {}),
+ MakeInstructionMessage(SpvOpLoad, 8, 39, {{SPV_OPERAND_TYPE_ID, {17}}}),
+ MakeInstructionMessage(SpvOpReturnValue, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {39}}}),
+ MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})}));
+
+ ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+ transformation1.Apply(context.get(), &fact_manager);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformation1 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFloat 32
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %8 %7 %9
+ %18 = OpConstant %8 0
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %37 = OpConstant %6 1
+ %42 = OpTypePointer Private %8
+ %43 = OpVariable %42 Private
+ %47 = OpConstant %8 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %13 = OpFunction %8 None %10
+ %11 = OpFunctionParameter %7
+ %12 = OpFunctionParameter %9
+ %14 = OpLabel
+ %17 = OpVariable %9 Function
+ %19 = OpVariable %7 Function
+ OpStore %17 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpLoad %6 %19
+ %27 = OpLoad %6 %11
+ %29 = OpSLessThan %28 %26 %27
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ %30 = OpLoad %8 %12
+ %31 = OpLoad %6 %19
+ %32 = OpConvertSToF %8 %31
+ %33 = OpFMul %8 %30 %32
+ %34 = OpLoad %8 %17
+ %35 = OpFAdd %8 %34 %33
+ OpStore %17 %35
+ OpBranch %24
+ %24 = OpLabel
+ %36 = OpLoad %6 %19
+ %38 = OpIAdd %6 %36 %37
+ OpStore %19 %38
+ OpBranch %21
+ %23 = OpLabel
+ %39 = OpLoad %8 %17
+ OpReturnValue %39
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, after_transformation1, context.get()));
+
+ TransformationAddFunction transformation2(std::vector<protobufs::Instruction>(
+ {MakeInstructionMessage(
+ SpvOpFunction, 2, 15,
+ {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+ {SPV_OPERAND_TYPE_ID, {3}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 16, {}),
+ MakeInstructionMessage(
+ SpvOpVariable, 7, 44,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}),
+ MakeInstructionMessage(
+ SpvOpVariable, 9, 45,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}),
+ MakeInstructionMessage(
+ SpvOpVariable, 7, 48,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}),
+ MakeInstructionMessage(
+ SpvOpVariable, 9, 49,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {44}}, {SPV_OPERAND_TYPE_ID, {20}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {45}}, {SPV_OPERAND_TYPE_ID, {18}}}),
+ MakeInstructionMessage(SpvOpFunctionCall, 8, 46,
+ {{SPV_OPERAND_TYPE_ID, {13}},
+ {SPV_OPERAND_TYPE_ID, {44}},
+ {SPV_OPERAND_TYPE_ID, {45}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {48}}, {SPV_OPERAND_TYPE_ID, {37}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {49}}, {SPV_OPERAND_TYPE_ID, {47}}}),
+ MakeInstructionMessage(SpvOpFunctionCall, 8, 50,
+ {{SPV_OPERAND_TYPE_ID, {13}},
+ {SPV_OPERAND_TYPE_ID, {48}},
+ {SPV_OPERAND_TYPE_ID, {49}}}),
+ MakeInstructionMessage(
+ SpvOpFAdd, 8, 51,
+ {{SPV_OPERAND_TYPE_ID, {46}}, {SPV_OPERAND_TYPE_ID, {50}}}),
+ MakeInstructionMessage(
+ SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {43}}, {SPV_OPERAND_TYPE_ID, {51}}}),
+ MakeInstructionMessage(SpvOpReturn, 0, 0, {}),
+ MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})}));
+
+ ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+ transformation2.Apply(context.get(), &fact_manager);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformation2 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFloat 32
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %8 %7 %9
+ %18 = OpConstant %8 0
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %37 = OpConstant %6 1
+ %42 = OpTypePointer Private %8
+ %43 = OpVariable %42 Private
+ %47 = OpConstant %8 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %13 = OpFunction %8 None %10
+ %11 = OpFunctionParameter %7
+ %12 = OpFunctionParameter %9
+ %14 = OpLabel
+ %17 = OpVariable %9 Function
+ %19 = OpVariable %7 Function
+ OpStore %17 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpLoad %6 %19
+ %27 = OpLoad %6 %11
+ %29 = OpSLessThan %28 %26 %27
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ %30 = OpLoad %8 %12
+ %31 = OpLoad %6 %19
+ %32 = OpConvertSToF %8 %31
+ %33 = OpFMul %8 %30 %32
+ %34 = OpLoad %8 %17
+ %35 = OpFAdd %8 %34 %33
+ OpStore %17 %35
+ OpBranch %24
+ %24 = OpLabel
+ %36 = OpLoad %6 %19
+ %38 = OpIAdd %6 %36 %37
+ OpStore %19 %38
+ OpBranch %21
+ %23 = OpLabel
+ %39 = OpLoad %8 %17
+ OpReturnValue %39
+ OpFunctionEnd
+ %15 = OpFunction %2 None %3
+ %16 = OpLabel
+ %44 = OpVariable %7 Function
+ %45 = OpVariable %9 Function
+ %48 = OpVariable %7 Function
+ %49 = OpVariable %9 Function
+ OpStore %44 %20
+ OpStore %45 %18
+ %46 = OpFunctionCall %8 %13 %44 %45
+ OpStore %48 %37
+ OpStore %49 %47
+ %50 = OpFunctionCall %8 %13 %48 %49
+ %51 = OpFAdd %8 %46 %50
+ OpStore %43 %51
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, after_transformation2, context.get()));
+}
+
+TEST(TransformationAddFunctionTest, InapplicableTransformations) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFloat 32
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %8 %7 %9
+ %18 = OpConstant %8 0
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %37 = OpConstant %6 1
+ %42 = OpTypePointer Private %8
+ %43 = OpVariable %42 Private
+ %47 = OpConstant %8 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %13 = OpFunction %8 None %10
+ %11 = OpFunctionParameter %7
+ %12 = OpFunctionParameter %9
+ %14 = OpLabel
+ %17 = OpVariable %9 Function
+ %19 = OpVariable %7 Function
+ OpStore %17 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpLoad %6 %19
+ %27 = OpLoad %6 %11
+ %29 = OpSLessThan %28 %26 %27
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ %30 = OpLoad %8 %12
+ %31 = OpLoad %6 %19
+ %32 = OpConvertSToF %8 %31
+ %33 = OpFMul %8 %30 %32
+ %34 = OpLoad %8 %17
+ %35 = OpFAdd %8 %34 %33
+ OpStore %17 %35
+ OpBranch %24
+ %24 = OpLabel
+ %36 = OpLoad %6 %19
+ %38 = OpIAdd %6 %36 %37
+ OpStore %19 %38
+ OpBranch %21
+ %23 = OpLabel
+ %39 = OpLoad %8 %17
+ OpReturnValue %39
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+
+ // No instructions
+ ASSERT_FALSE(
+ TransformationAddFunction(std::vector<protobufs::Instruction>({}))
+ .IsApplicable(context.get(), fact_manager));
+
+ // No function begin
+ ASSERT_FALSE(
+ TransformationAddFunction(
+ std::vector<protobufs::Instruction>(
+ {MakeInstructionMessage(SpvOpFunctionParameter, 7, 11, {}),
+ MakeInstructionMessage(SpvOpFunctionParameter, 9, 12, {}),
+ MakeInstructionMessage(SpvOpLabel, 0, 14, {})}))
+ .IsApplicable(context.get(), fact_manager));
+
+ // No OpLabel
+ ASSERT_FALSE(
+ TransformationAddFunction(
+ std::vector<protobufs::Instruction>(
+ {MakeInstructionMessage(SpvOpFunction, 8, 13,
+ {{SPV_OPERAND_TYPE_FUNCTION_CONTROL,
+ {SpvFunctionControlMaskNone}},
+ {SPV_OPERAND_TYPE_ID, {10}}}),
+ MakeInstructionMessage(SpvOpReturnValue, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {39}}}),
+ MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})}))
+ .IsApplicable(context.get(), fact_manager));
+
+ // Abrupt end of instructions
+ ASSERT_FALSE(TransformationAddFunction(
+ std::vector<protobufs::Instruction>({MakeInstructionMessage(
+ SpvOpFunction, 8, 13,
+ {{SPV_OPERAND_TYPE_FUNCTION_CONTROL,
+ {SpvFunctionControlMaskNone}},
+ {SPV_OPERAND_TYPE_ID, {10}}})}))
+ .IsApplicable(context.get(), fact_manager));
+
+ // No function end
+ ASSERT_FALSE(
+ TransformationAddFunction(
+ std::vector<protobufs::Instruction>(
+ {MakeInstructionMessage(SpvOpFunction, 8, 13,
+ {{SPV_OPERAND_TYPE_FUNCTION_CONTROL,
+ {SpvFunctionControlMaskNone}},
+ {SPV_OPERAND_TYPE_ID, {10}}}),
+ MakeInstructionMessage(SpvOpLabel, 0, 14, {}),
+ MakeInstructionMessage(SpvOpReturnValue, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {39}}})}))
+ .IsApplicable(context.get(), fact_manager));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools