spirv-fuzz: Add fuzzer pass to change function controls (#2951)

A new pass that allows the fuzzer to change the 'function control'
operand of OpFunction instructions.

Fixes #2939.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index c711d05..f913933 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -38,6 +38,7 @@
         fuzzer_pass_add_dead_breaks.h
         fuzzer_pass_add_dead_continues.h
         fuzzer_pass_add_useful_constructs.h
+        fuzzer_pass_adjust_function_controls.h
         fuzzer_pass_adjust_loop_controls.h
         fuzzer_pass_adjust_selection_controls.h
         fuzzer_pass_apply_id_synonyms.h
@@ -68,6 +69,7 @@
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_id_with_synonym.h
+        transformation_set_function_control.h
         transformation_set_loop_control.h
         transformation_set_selection_control.h
         transformation_split_block.h
@@ -83,6 +85,7 @@
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_dead_continues.cpp
         fuzzer_pass_add_useful_constructs.cpp
+        fuzzer_pass_adjust_function_controls.cpp
         fuzzer_pass_adjust_loop_controls.cpp
         fuzzer_pass_adjust_selection_controls.cpp
         fuzzer_pass_apply_id_synonyms.cpp
@@ -112,6 +115,7 @@
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_id_with_synonym.cpp
+        transformation_set_function_control.cpp
         transformation_set_loop_control.cpp
         transformation_set_selection_control.cpp
         transformation_split_block.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index a034c0f..7dd8509 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -23,6 +23,7 @@
 #include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
 #include "source/fuzz/fuzzer_pass_add_dead_continues.h"
 #include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
 #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
 #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
 #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
@@ -170,16 +171,18 @@
 
   // Now apply some passes that it does not make sense to apply repeatedly,
   // as they do not unlock other passes.
-  if (fuzzer_context.ChooseEven()) {
-    FuzzerPassAdjustLoopControls(ir_context.get(), &fact_manager,
-                                 &fuzzer_context, transformation_sequence_out)
-        .Apply();
-  }
-  if (fuzzer_context.ChooseEven()) {
-    FuzzerPassAdjustSelectionControls(ir_context.get(), &fact_manager,
-                                      &fuzzer_context,
-                                      transformation_sequence_out)
-        .Apply();
+  std::vector<std::unique_ptr<FuzzerPass>> final_passes;
+  MaybeAddPass<FuzzerPassAdjustFunctionControls>(&passes, ir_context.get(),
+                                                 &fact_manager, &fuzzer_context,
+                                                 transformation_sequence_out);
+  MaybeAddPass<FuzzerPassAdjustLoopControls>(&passes, ir_context.get(),
+                                             &fact_manager, &fuzzer_context,
+                                             transformation_sequence_out);
+  MaybeAddPass<FuzzerPassAdjustSelectionControls>(
+      &passes, ir_context.get(), &fact_manager, &fuzzer_context,
+      transformation_sequence_out);
+  for (auto& pass : final_passes) {
+    pass->Apply();
   }
 
   // Encode the module as a binary.
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 5a49e5e..c720e4c 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -25,6 +25,8 @@
 
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfAdjustingFunctionControl = {20,
+                                                                         70};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingLoopControl = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
                                                                           90};
@@ -62,6 +64,8 @@
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
   chance_of_adding_dead_continue_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+  chance_of_adjusting_function_control_ =
+      ChooseBetweenMinAndMax(kChanceOfAdjustingFunctionControl);
   chance_of_adjusting_loop_control_ =
       ChooseBetweenMinAndMax(kChanceOfAdjustingLoopControl);
   chance_of_adjusting_selection_control_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 5e1b1d2..fd84a1b 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -62,6 +62,9 @@
   uint32_t GetChanceOfAddingDeadContinue() {
     return chance_of_adding_dead_continue_;
   }
+  uint32_t GetChanceOfAdjustingFunctionControl() {
+    return chance_of_adjusting_function_control_;
+  }
   uint32_t GetChanceOfAdjustingLoopControl() {
     return chance_of_adjusting_loop_control_;
   }
@@ -103,6 +106,7 @@
   // Keep them in alphabetical order.
   uint32_t chance_of_adding_dead_break_;
   uint32_t chance_of_adding_dead_continue_;
+  uint32_t chance_of_adjusting_function_control_;
   uint32_t chance_of_adjusting_loop_control_;
   uint32_t chance_of_adjusting_selection_control_;
   uint32_t chance_of_constructing_composite_;
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.cpp b/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
new file mode 100644
index 0000000..ab5ecf6
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
@@ -0,0 +1,73 @@
+// 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/fuzzer_pass_adjust_function_controls.h"
+
+#include "source/fuzz/transformation_set_function_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAdjustFunctionControls::FuzzerPassAdjustFunctionControls(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+
+FuzzerPassAdjustFunctionControls::~FuzzerPassAdjustFunctionControls() = default;
+
+void FuzzerPassAdjustFunctionControls::Apply() {
+  // Consider every function in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    // Randomly decide whether to adjust this function's controls.
+    if (GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfAdjustingFunctionControl())) {
+      // Grab the function control mask for the function in its present form.
+      uint32_t existing_function_control_mask =
+          function.DefInst().GetSingleWordInOperand(0);
+
+      // For the new mask, we first randomly select one of three basic masks:
+      // None, Inline or DontInline.  These are always valid (and are mutually
+      // exclusive).
+      std::vector<uint32_t> basic_function_control_masks = {
+          SpvFunctionControlMaskNone, SpvFunctionControlInlineMask,
+          SpvFunctionControlDontInlineMask};
+      uint32_t new_function_control_mask =
+          basic_function_control_masks[GetFuzzerContext()->RandomIndex(
+              basic_function_control_masks)];
+
+      // We now consider the Pure and Const mask bits.  If these are already
+      // set on the function then it's OK to keep them, but also interesting
+      // to consider dropping them, so we decide randomly in each case.
+      for (auto mask_bit :
+           {SpvFunctionControlPureMask, SpvFunctionControlConstMask}) {
+        if ((existing_function_control_mask & mask_bit) &&
+            GetFuzzerContext()->ChooseEven()) {
+          new_function_control_mask |= mask_bit;
+        }
+      }
+
+      // Create and add a transformation.
+      TransformationSetFunctionControl transformation(
+          function.DefInst().result_id(), new_function_control_mask);
+      assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+             "Transformation should be applicable by construction.");
+      transformation.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() = transformation.ToMessage();
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.h b/source/fuzz/fuzzer_pass_adjust_function_controls.h
new file mode 100644
index 0000000..02d3600
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.h
@@ -0,0 +1,39 @@
+// 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_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that adjusts the function controls on OpFunction instructions.
+class FuzzerPassAdjustFunctionControls : public FuzzerPass {
+ public:
+  FuzzerPassAdjustFunctionControls(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAdjustFunctionControls() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 80bb4df..f1bf5e3 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -177,6 +177,7 @@
     TransformationSetSelectionControl set_selection_control = 15;
     TransformationConstructComposite construct_composite = 16;
     TransformationSetLoopControl set_loop_control = 17;
+    TransformationSetFunctionControl set_function_control = 18;
     // Add additional option using the next available number.
   }
 }
@@ -412,6 +413,19 @@
   uint32 fresh_id_for_temporary = 3;
 }
 
+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
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 8b93b6a..d5dd01f 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,6 +30,7 @@
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
+#include "source/fuzz/transformation_set_function_control.h"
 #include "source/fuzz/transformation_set_loop_control.h"
 #include "source/fuzz/transformation_set_selection_control.h"
 #include "source/fuzz/transformation_split_block.h"
@@ -82,6 +83,9 @@
     case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
       return MakeUnique<TransformationReplaceIdWithSynonym>(
           message.replace_id_with_synonym());
+    case protobufs::Transformation::TransformationCase::kSetFunctionControl:
+      return MakeUnique<TransformationSetFunctionControl>(
+          message.set_function_control());
     case protobufs::Transformation::TransformationCase::kSetLoopControl:
       return MakeUnique<TransformationSetLoopControl>(
           message.set_loop_control());
diff --git a/source/fuzz/transformation_set_function_control.cpp b/source/fuzz/transformation_set_function_control.cpp
new file mode 100644
index 0000000..d2b61f1
--- /dev/null
+++ b/source/fuzz/transformation_set_function_control.cpp
@@ -0,0 +1,100 @@
+// 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_set_function_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSetFunctionControl::TransformationSetFunctionControl(
+    const spvtools::fuzz::protobufs::TransformationSetFunctionControl& message)
+    : message_(message) {}
+
+TransformationSetFunctionControl::TransformationSetFunctionControl(
+    uint32_t function_id, uint32_t function_control) {
+  message_.set_function_id(function_id);
+  message_.set_function_control(function_control);
+}
+
+bool TransformationSetFunctionControl::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  opt::Instruction* function_def_instruction =
+      FindFunctionDefInstruction(context);
+  if (!function_def_instruction) {
+    // The given function id does not correspond to any function.
+    return false;
+  }
+  uint32_t existing_function_control_mask =
+      function_def_instruction->GetSingleWordInOperand(0);
+
+  // Check (via an assertion) that function control mask doesn't have any bad
+  // bits set.
+  uint32_t acceptable_function_control_bits =
+      SpvFunctionControlInlineMask | SpvFunctionControlDontInlineMask |
+      SpvFunctionControlPureMask | SpvFunctionControlConstMask;
+  // The following is to keep release-mode compilers happy as this variable is
+  // only used in an assertion.
+  (void)(acceptable_function_control_bits);
+  assert(!(message_.function_control() & ~acceptable_function_control_bits) &&
+         "Nonsensical loop control bits were found.");
+
+  // Check (via an assertion) that function control mask does not have both
+  // Inline and DontInline bits set.
+  assert(!((message_.function_control() & SpvFunctionControlInlineMask) &&
+           (message_.function_control() & SpvFunctionControlDontInlineMask)) &&
+         "It is not OK to set both the 'Inline' and 'DontInline' bits of a "
+         "function control mask");
+
+  // Check that Const and Pure are only present if they were present on the
+  // original function
+  for (auto mask_bit :
+       {SpvFunctionControlPureMask, SpvFunctionControlConstMask}) {
+    if ((message_.function_control() & mask_bit) &&
+        !(existing_function_control_mask & mask_bit)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationSetFunctionControl::Apply(opt::IRContext* context,
+                                             FactManager* /*unused*/) const {
+  opt::Instruction* function_def_instruction =
+      FindFunctionDefInstruction(context);
+  function_def_instruction->SetInOperand(0, {message_.function_control()});
+}
+
+protobufs::Transformation TransformationSetFunctionControl::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_set_function_control() = message_;
+  return result;
+}
+
+opt::Instruction* TransformationSetFunctionControl ::FindFunctionDefInstruction(
+    opt::IRContext* context) const {
+  // Look through all functions for a function whose defining instruction's
+  // result id matches |message_.function_id|, returning the defining
+  // instruction if found.
+  for (auto& function : *context->module()) {
+    if (function.DefInst().result_id() == message_.function_id()) {
+      return &function.DefInst();
+    }
+  }
+  // A nullptr result indicates that no match was found.
+  return nullptr;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_set_function_control.h b/source/fuzz/transformation_set_function_control.h
new file mode 100644
index 0000000..0526bb9
--- /dev/null
+++ b/source/fuzz/transformation_set_function_control.h
@@ -0,0 +1,58 @@
+// 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_SET_FUNCTION_CONTROL_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SET_FUNCTION_CONTROL_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 TransformationSetFunctionControl : public Transformation {
+ public:
+  explicit TransformationSetFunctionControl(
+      const protobufs::TransformationSetFunctionControl& message);
+
+  TransformationSetFunctionControl(uint32_t function_id,
+                                   uint32_t function_control);
+
+  // - |message_.function_id| must be the result id of an OpFunction
+  //   instruction.
+  // - |message_.function_control| must be a function control mask that sets
+  //   at most one of 'Inline' or 'DontInline', and that may not contain 'Pure'
+  //   (respectively 'Const') unless the existing function control mask contains
+  //   'Pure' (respectively 'Const').
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // The function control operand of instruction |message_.function_id| is
+  // over-written with |message_.function_control|.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  opt::Instruction* FindFunctionDefInstruction(opt::IRContext* context) const;
+
+  protobufs::TransformationSetFunctionControl message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SET_FUNCTION_CONTROL_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index f4405e8..4343d21 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -34,6 +34,7 @@
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
           transformation_replace_id_with_synonym_test.cpp
+          transformation_set_function_control_test.cpp
           transformation_set_loop_control_test.cpp
           transformation_set_selection_control_test.cpp
           transformation_split_block_test.cpp
diff --git a/test/fuzz/transformation_set_function_control_test.cpp b/test/fuzz/transformation_set_function_control_test.cpp
new file mode 100644
index 0000000..536e965
--- /dev/null
+++ b/test/fuzz/transformation_set_function_control_test.cpp
@@ -0,0 +1,251 @@
+// 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_set_function_control.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSetFunctionControlTest, VariousScenarios) {
+  // This is a simple transformation; this test captures the important things
+  // to check for.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %54
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %11 "foo(i1;i1;"
+               OpName %9 "a"
+               OpName %10 "b"
+               OpName %13 "bar("
+               OpName %17 "baz(i1;"
+               OpName %16 "x"
+               OpName %21 "boo(i1;i1;"
+               OpName %19 "a"
+               OpName %20 "b"
+               OpName %29 "g"
+               OpName %42 "param"
+               OpName %44 "param"
+               OpName %45 "param"
+               OpName %48 "param"
+               OpName %49 "param"
+               OpName %54 "color"
+               OpDecorate %54 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7 %7
+         %15 = OpTypeFunction %6 %7
+         %28 = OpTypePointer Private %6
+         %29 = OpVariable %28 Private
+         %30 = OpConstant %6 2
+         %31 = OpConstant %6 5
+         %51 = OpTypeFloat 32
+         %52 = OpTypeVector %51 4
+         %53 = OpTypePointer Output %52
+         %54 = OpVariable %53 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %42 = OpVariable %7 Function
+         %44 = OpVariable %7 Function
+         %45 = OpVariable %7 Function
+         %48 = OpVariable %7 Function
+         %49 = OpVariable %7 Function
+         %41 = OpFunctionCall %2 %13
+               OpStore %42 %30
+         %43 = OpFunctionCall %6 %17 %42
+               OpStore %44 %31
+         %46 = OpLoad %6 %29
+               OpStore %45 %46
+         %47 = OpFunctionCall %6 %21 %44 %45
+               OpStore %48 %43
+               OpStore %49 %47
+         %50 = OpFunctionCall %6 %11 %48 %49
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %6 Const %8
+          %9 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %7
+         %12 = OpLabel
+         %23 = OpLoad %6 %9
+         %24 = OpLoad %6 %10
+         %25 = OpIAdd %6 %23 %24
+               OpReturnValue %25
+               OpFunctionEnd
+         %13 = OpFunction %2 Inline %3
+         %14 = OpLabel
+               OpStore %29 %30
+               OpReturn
+               OpFunctionEnd
+         %17 = OpFunction %6 Pure|DontInline %15
+         %16 = OpFunctionParameter %7
+         %18 = OpLabel
+         %32 = OpLoad %6 %16
+         %33 = OpIAdd %6 %31 %32
+               OpReturnValue %33
+               OpFunctionEnd
+         %21 = OpFunction %6 DontInline %8
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %7
+         %22 = OpLabel
+         %36 = OpLoad %6 %19
+         %37 = OpLoad %6 %20
+         %38 = OpIMul %6 %36 %37
+               OpReturnValue %38
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // %36 is not a function
+  ASSERT_FALSE(TransformationSetFunctionControl(36, SpvFunctionControlMaskNone)
+                   .IsApplicable(context.get(), fact_manager));
+  // Cannot add the Pure function control to %4 as it did not already have it
+  ASSERT_FALSE(TransformationSetFunctionControl(4, SpvFunctionControlPureMask)
+                   .IsApplicable(context.get(), fact_manager));
+  // Cannot add the Const function control to %21 as it did not already
+  // have it
+  ASSERT_FALSE(TransformationSetFunctionControl(21, SpvFunctionControlConstMask)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Set to None, removing Const
+  TransformationSetFunctionControl transformation1(11,
+                                                   SpvFunctionControlMaskNone);
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+
+  // Set to Inline; silly to do it on an entry point, but it is allowed
+  TransformationSetFunctionControl transformation2(
+      4, SpvFunctionControlInlineMask);
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+
+  // Set to Pure, removing DontInline
+  TransformationSetFunctionControl transformation3(17,
+                                                   SpvFunctionControlPureMask);
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+
+  // Change from Inline to DontInline
+  TransformationSetFunctionControl transformation4(
+      13, SpvFunctionControlDontInlineMask);
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %54
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %11 "foo(i1;i1;"
+               OpName %9 "a"
+               OpName %10 "b"
+               OpName %13 "bar("
+               OpName %17 "baz(i1;"
+               OpName %16 "x"
+               OpName %21 "boo(i1;i1;"
+               OpName %19 "a"
+               OpName %20 "b"
+               OpName %29 "g"
+               OpName %42 "param"
+               OpName %44 "param"
+               OpName %45 "param"
+               OpName %48 "param"
+               OpName %49 "param"
+               OpName %54 "color"
+               OpDecorate %54 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7 %7
+         %15 = OpTypeFunction %6 %7
+         %28 = OpTypePointer Private %6
+         %29 = OpVariable %28 Private
+         %30 = OpConstant %6 2
+         %31 = OpConstant %6 5
+         %51 = OpTypeFloat 32
+         %52 = OpTypeVector %51 4
+         %53 = OpTypePointer Output %52
+         %54 = OpVariable %53 Output
+          %4 = OpFunction %2 Inline %3
+          %5 = OpLabel
+         %42 = OpVariable %7 Function
+         %44 = OpVariable %7 Function
+         %45 = OpVariable %7 Function
+         %48 = OpVariable %7 Function
+         %49 = OpVariable %7 Function
+         %41 = OpFunctionCall %2 %13
+               OpStore %42 %30
+         %43 = OpFunctionCall %6 %17 %42
+               OpStore %44 %31
+         %46 = OpLoad %6 %29
+               OpStore %45 %46
+         %47 = OpFunctionCall %6 %21 %44 %45
+               OpStore %48 %43
+               OpStore %49 %47
+         %50 = OpFunctionCall %6 %11 %48 %49
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %7
+         %12 = OpLabel
+         %23 = OpLoad %6 %9
+         %24 = OpLoad %6 %10
+         %25 = OpIAdd %6 %23 %24
+               OpReturnValue %25
+               OpFunctionEnd
+         %13 = OpFunction %2 DontInline %3
+         %14 = OpLabel
+               OpStore %29 %30
+               OpReturn
+               OpFunctionEnd
+         %17 = OpFunction %6 Pure %15
+         %16 = OpFunctionParameter %7
+         %18 = OpLabel
+         %32 = OpLoad %6 %16
+         %33 = OpIAdd %6 %31 %32
+               OpReturnValue %33
+               OpFunctionEnd
+         %21 = OpFunction %6 DontInline %8
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %7
+         %22 = OpLabel
+         %36 = OpLoad %6 %19
+         %37 = OpLoad %6 %20
+         %38 = OpIMul %6 %36 %37
+               OpReturnValue %38
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools