Squashed 'third_party/SPIRV-Tools/' changes from 0174dd11f..e9dc2c8ce

e9dc2c8ce Roll external/googletest/ ebedaa18c..2f2e72bae (4 commits) (#5099)
7823b8ff4 build: allow update_build to generate fake version (#5098)
cac9a5a3e Fix null pointer in FoldInsertWithConstants. (#5093)
fd1e650cf Validate decoration of structs with RuntimeArray (#5094)
589076373 instrument: Clean up generation code (#5090)
d8759a140 build: fix bazel build for c++17 (#5097)
1a49b5218 Remove vs2017 no longer being run (#5095)
0994ca45b Add C interface for Optimizer (#5030)
8a0fe779e Roll external/re2/ 8afcf7fcc..b025c6a3a (1 commit) (#5091)
b230a7c7d Validate operand type before operating on it (#5092)
fcfc3c580 build: stop parsing CHANGES file. (#5067)

git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: e9dc2c8ce12df55514546511abd19afdf6980cc7
Change-Id: I590a161a8cdb6e5f45611dafce14172cda3fd342
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 0000000..5b3d13f
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1 @@
+build --cxxopt=-std=c++17
diff --git a/.bazelversion b/.bazelversion
index 28cbf7c..0062ac9 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-5.0.0
\ No newline at end of file
+5.0.0
diff --git a/Android.mk b/Android.mk
index a4e7615..8a70206 100644
--- a/Android.mk
+++ b/Android.mk
@@ -311,9 +311,9 @@
 $(call generate-file-dir,$(1)/dummy_filename)
 $(1)/build-version.inc: \
         $(LOCAL_PATH)/utils/update_build_version.py \
-        $(LOCAL_PATH)/CHANGES
+        $(LOCAL_PATH)
 		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/update_build_version.py \
-		                $(LOCAL_PATH)/CHANGES $(1)/build-version.inc
+		                $(LOCAL_PATH) $(1)/build-version.inc
 		@echo "[$(TARGET_ARCH_ABI)] Generate       : build-version.inc <= CHANGES"
 $(LOCAL_PATH)/source/software_version.cpp: $(1)/build-version.inc
 endef
diff --git a/BUILD.bazel b/BUILD.bazel
index 255d4e7..71399b2 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -104,11 +104,11 @@
 
 genrule(
     name = "build_version_inc",
-    srcs = ["CHANGES"],
     outs = ["build-version.inc"],
-    cmd = "SOURCE_DATE_EPOCH=0 $(location :update_build_version) $(location CHANGES) $(location build-version.inc)",
-    cmd_bat = "set SOURCE_DATE_EPOCH=0  && $(location :update_build_version) $(location CHANGES) $(location build-version.inc)",
+    cmd = "SOURCE_DATE_EPOCH=0 $(location :update_build_version) $(RULEDIR) $(location build-version.inc)",
+    cmd_bat = "set SOURCE_DATE_EPOCH=0  && $(location :update_build_version) $(RULEDIR) $(location build-version.inc)",
     exec_tools = [":update_build_version"],
+    local = True,
 )
 
 # Libraries
diff --git a/BUILD.gn b/BUILD.gn
index 862cd95..ee3743b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -262,12 +262,12 @@
 action("spvtools_build_version") {
   script = "utils/update_build_version.py"
 
-  changes_file = "CHANGES"
+  repo_path = "."
   inc_file = "${target_gen_dir}/build-version.inc"
 
   outputs = [ inc_file ]
   args = [
-    rebase_path(changes_file, root_build_dir),
+    rebase_path(repo_path, root_build_dir),
     rebase_path(inc_file, root_build_dir),
   ]
 }
diff --git a/DEPS b/DEPS
index f1866ea..c6be512 100644
--- a/DEPS
+++ b/DEPS
@@ -5,14 +5,12 @@
 
   'effcee_revision': 'c7b4db79f340f7a9981e8a484f6d5785e24242d1',
 
-  # Pin to the last version of googletest that supports C++11.
-  # Anything later requires C++14
-  'googletest_revision': 'v1.12.0',
+  'googletest_revision': '2f2e72bae991138cedd0e3d06a115022736cd568',
 
   # Use protobufs before they gained the dependency on abseil
   'protobuf_revision': 'v3.13.0.1',
 
-  're2_revision': '8afcf7fcc481692197e33612446d69e8f5777c54',
+  're2_revision': 'b025c6a3ae05995660e3b882eb3277f4399ced1a',
   'spirv_headers_revision': 'aa331ab0ffcb3a67021caa1a0c1c9017712f2f31',
 }
 
diff --git a/build_defs.bzl b/build_defs.bzl
index 3a69de5..4d6f15c 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -4,13 +4,10 @@
     "-DSPIRV_CHECK_CONTEXT",
     "-DSPIRV_COLOR_TERMINAL",
 ] + select({
-    # On Windows, assume MSVC.
-    # C++14 is the default in VisualStudio 2017.
     "@platforms//os:windows": [],
     "//conditions:default": [
         "-DSPIRV_LINUX",
         "-DSPIRV_TIMER_ENABLED",
-        "-std=c++11",
         "-fvisibility=hidden",
         "-fno-exceptions",
         "-fno-rtti",
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index b549efb..84a7726 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -441,6 +441,8 @@
 
 typedef struct spv_fuzzer_options_t spv_fuzzer_options_t;
 
+typedef struct spv_optimizer_t spv_optimizer_t;
+
 // Type Definitions
 
 typedef spv_const_binary_t* spv_const_binary;
@@ -900,6 +902,63 @@
     const size_t num_words, spv_parsed_header_fn_t parse_header,
     spv_parsed_instruction_fn_t parse_instruction, spv_diagnostic* diagnostic);
 
+// The optimizer interface.
+
+// A pointer to a function that accepts a log message from an optimizer.
+typedef void (*spv_message_consumer)(
+    spv_message_level_t, const char*, const spv_position_t*, const char*);
+
+// Creates and returns an optimizer object.  This object must be passed to
+// optimizer APIs below and is valid until passed to spvOptimizerDestroy.
+SPIRV_TOOLS_EXPORT spv_optimizer_t* spvOptimizerCreate(spv_target_env env);
+
+// Destroys the given optimizer object.
+SPIRV_TOOLS_EXPORT void spvOptimizerDestroy(spv_optimizer_t* optimizer);
+
+// Sets an spv_message_consumer on an optimizer object.
+SPIRV_TOOLS_EXPORT void spvOptimizerSetMessageConsumer(
+    spv_optimizer_t* optimizer, spv_message_consumer consumer);
+
+// Registers passes that attempt to legalize the generated code.
+SPIRV_TOOLS_EXPORT void spvOptimizerRegisterLegalizationPasses(
+    spv_optimizer_t* optimizer);
+
+// Registers passes that attempt to improve performance of generated code.
+SPIRV_TOOLS_EXPORT void spvOptimizerRegisterPerformancePasses(
+    spv_optimizer_t* optimizer);
+
+// Registers passes that attempt to improve the size of generated code.
+SPIRV_TOOLS_EXPORT void spvOptimizerRegisterSizePasses(
+    spv_optimizer_t* optimizer);
+
+// Registers a pass specified by a flag in an optimizer object.
+SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassFromFlag(
+    spv_optimizer_t* optimizer, const char* flag);
+
+// Registers passes specified by length number of flags in an optimizer object.
+SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassesFromFlags(
+    spv_optimizer_t* optimizer, const char** flags, const size_t flag_count);
+
+// Optimizes the SPIR-V code of size |word_count| pointed to by |binary| and
+// returns an optimized spv_binary in |optimized_binary|.
+//
+// Returns SPV_SUCCESS on successful optimization, whether or not the module is
+// modified.  Returns an SPV_ERROR_* if the module fails to validate or if
+// errors occur when processing using any of the registered passes.  In that
+// case, no further passes are executed and the |optimized_binary| contents may
+// be invalid.
+//
+// By default, the binary is validated before any transforms are performed,
+// and optionally after each transform.  Validation uses SPIR-V spec rules
+// for the SPIR-V version named in the binary's header (at word offset 1).
+// Additionally, if the target environment is a client API (such as
+// Vulkan 1.1), then validate for that client API version, to the extent
+// that it is verifiable from data in the binary itself, or from the
+// validator options set on the optimizer options.
+SPIRV_TOOLS_EXPORT spv_result_t spvOptimizerRun(
+    spv_optimizer_t* optimizer, const uint32_t* binary, const size_t word_count,
+    spv_binary* optimized_binary, const spv_optimizer_options options);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/kokoro/windows-msvc-2017-debug/build.bat b/kokoro/windows-msvc-2017-debug/build.bat
deleted file mode 100644
index 25783a9..0000000
--- a/kokoro/windows-msvc-2017-debug/build.bat
+++ /dev/null
@@ -1,23 +0,0 @@
-:: Copyright (c) 2018 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.
-::
-:: Windows Build Script.
-
-@echo on
-
-:: Find out the directory of the common build script.
-set SCRIPT_DIR=%~dp0
-
-:: Call with correct parameter
-call %SCRIPT_DIR%\..\scripts\windows\build.bat Debug 2017
diff --git a/kokoro/windows-msvc-2017-debug/continuous.cfg b/kokoro/windows-msvc-2017-debug/continuous.cfg
deleted file mode 100644
index 25c5e11..0000000
--- a/kokoro/windows-msvc-2017-debug/continuous.cfg
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (c) 2018 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.
-
-# Continuous build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-debug/build.bat"
-
-action {
-  define_artifacts {
-    regex: "install.zip"
-  }
-}
diff --git a/kokoro/windows-msvc-2017-debug/presubmit.cfg b/kokoro/windows-msvc-2017-debug/presubmit.cfg
deleted file mode 100644
index a7a553a..0000000
--- a/kokoro/windows-msvc-2017-debug/presubmit.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (c) 2018 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.
-
-# Presubmit build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-debug/build.bat"
diff --git a/kokoro/windows-msvc-2017-release-bazel/build.bat b/kokoro/windows-msvc-2017-release-bazel/build.bat
deleted file mode 100644
index c1945e2..0000000
--- a/kokoro/windows-msvc-2017-release-bazel/build.bat
+++ /dev/null
@@ -1,57 +0,0 @@
-:: 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.
-::
-:: Windows Build Script.
-
-@echo on
-
-set SRC=%cd%\github\SPIRV-Tools
-
-:: Force usage of python 3.6
-set PATH=C:\python36;%PATH%
-
-:: Get dependencies
-cd %SRC%
-git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone https://github.com/google/googletest          external/googletest
-cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd ..
-git clone --depth=1 https://github.com/google/effcee              external/effcee
-git clone --depth=1 https://github.com/google/re2                 external/re2
-
-:: REM Install Bazel.
-wget -q https://github.com/bazelbuild/bazel/releases/download/5.0.0/bazel-5.0.0-windows-x86_64.zip
-unzip -q bazel-5.0.0-windows-x86_64.zip
-
-:: Set up MSVC
-call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
-set BAZEL_VC=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC
-
-:: #########################################
-:: Start building.
-:: #########################################
-echo "Build everything... %DATE% %TIME%"
-bazel.exe build :all
-if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
-echo "Build Completed %DATE% %TIME%"
-
-:: ##############
-:: Run the tests
-:: ##############
-echo "Running Tests... %DATE% %TIME%"
-bazel.exe test :all
-if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
-echo "Tests Completed %DATE% %TIME%"
-
-exit /b 0
-
diff --git a/kokoro/windows-msvc-2017-release-bazel/continuous.cfg b/kokoro/windows-msvc-2017-release-bazel/continuous.cfg
deleted file mode 100644
index f2387a6..0000000
--- a/kokoro/windows-msvc-2017-release-bazel/continuous.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.
-
-# Continuous build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-release-bazel/build.bat"
diff --git a/kokoro/windows-msvc-2017-release-bazel/presubmit.cfg b/kokoro/windows-msvc-2017-release-bazel/presubmit.cfg
deleted file mode 100644
index 13394b4..0000000
--- a/kokoro/windows-msvc-2017-release-bazel/presubmit.cfg
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.
-
-# Presubmit build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-release-bazel/build.bat"
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index acfa0c1..b5924f5 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -195,14 +195,11 @@
   ${spirv-tools_BINARY_DIR}/build-version.inc)
 set(SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR
   ${spirv-tools_SOURCE_DIR}/utils/update_build_version.py)
-set(SPIRV_TOOLS_CHANGES_FILE
-  ${spirv-tools_SOURCE_DIR}/CHANGES)
 add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC}
    COMMAND ${PYTHON_EXECUTABLE}
            ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR}
-           ${SPIRV_TOOLS_CHANGES_FILE} ${SPIRV_TOOLS_BUILD_VERSION_INC}
+           ${spirv-tools_SOURCE_DIR} ${SPIRV_TOOLS_BUILD_VERSION_INC}
    DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR}
-           ${SPIRV_TOOLS_CHANGES_FILE}
    COMMENT "Update build-version.inc in the SPIRV-Tools build directory (if necessary).")
 # Convenience target for standalone generation of the build-version.inc file.
 # This is not required for any dependence chain.
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index 14f2208..516c34b 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -145,12 +145,17 @@
       if (composite->AsNullConstant()) {
         // Make new composite so it can be inserted in the index with the
         // non-null value
-        const auto new_composite = const_mgr->GetNullCompositeConstant(type);
-        // Keep track of any indexes along the way to last index
-        if (i != final_index) {
-          chain.push_back(new_composite);
+        if (const auto new_composite =
+                const_mgr->GetNullCompositeConstant(type)) {
+          // Keep track of any indexes along the way to last index
+          if (i != final_index) {
+            chain.push_back(new_composite);
+          }
+          components = new_composite->AsCompositeConstant()->GetComponents();
+        } else {
+          // Unsupported input type (such as structs)
+          return nullptr;
         }
-        components = new_composite->AsCompositeConstant()->GetComponents();
       } else {
         // Keep track of any indexes along the way to last index
         if (i != final_index) {
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index ad581e1..cd712f0 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -497,8 +497,8 @@
     if (sum_id == 0)
       sum_id = curr_offset_id;
     else {
-      Instruction* sum_inst = builder->AddBinaryOp(GetUintId(), spv::Op::OpIAdd,
-                                                   sum_id, curr_offset_id);
+      Instruction* sum_inst =
+          builder->AddIAdd(GetUintId(), sum_id, curr_offset_id);
       sum_id = sum_inst->result_id();
     }
     ++ac_in_idx;
@@ -507,8 +507,7 @@
   uint32_t bsize = ByteSize(curr_ty_id, matrix_stride, col_major, in_matrix);
   uint32_t last = bsize - 1;
   uint32_t last_id = builder->GetUintConstantId(last);
-  Instruction* sum_inst =
-      builder->AddBinaryOp(GetUintId(), spv::Op::OpIAdd, sum_id, last_id);
+  Instruction* sum_inst = builder->AddIAdd(GetUintId(), sum_id, last_id);
   return sum_inst->result_id();
 }
 
diff --git a/source/opt/inst_buff_addr_check_pass.cpp b/source/opt/inst_buff_addr_check_pass.cpp
index 97d25f3..c18f91d 100644
--- a/source/opt/inst_buff_addr_check_pass.cpp
+++ b/source/opt/inst_buff_addr_check_pass.cpp
@@ -257,9 +257,7 @@
     uint32_t hdr_blk_id = TakeNextId();
     // Branch to search loop header
     std::unique_ptr<Instruction> hdr_blk_label(NewLabel(hdr_blk_id));
-    (void)builder.AddInstruction(MakeUnique<Instruction>(
-        context(), spv::Op::OpBranch, 0, 0,
-        std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {hdr_blk_id}}}));
+    (void)builder.AddBranch(hdr_blk_id);
     input_func->AddBasicBlock(std::move(first_blk_ptr));
     // Linear search loop header block
     // TODO(greg-lunarg): Implement binary search
@@ -293,17 +291,10 @@
     uint32_t bound_test_blk_id = TakeNextId();
     std::unique_ptr<Instruction> bound_test_blk_label(
         NewLabel(bound_test_blk_id));
-    (void)builder.AddInstruction(MakeUnique<Instruction>(
-        context(), spv::Op::OpLoopMerge, 0, 0,
-        std::initializer_list<Operand>{
-            {SPV_OPERAND_TYPE_ID, {bound_test_blk_id}},
-            {SPV_OPERAND_TYPE_ID, {cont_blk_id}},
-            {SPV_OPERAND_TYPE_LITERAL_INTEGER,
-             {uint32_t(spv::LoopControlMask::MaskNone)}}}));
+    (void)builder.AddLoopMerge(bound_test_blk_id, cont_blk_id,
+                               uint32_t(spv::LoopControlMask::MaskNone));
     // Branch to continue/work block
-    (void)builder.AddInstruction(MakeUnique<Instruction>(
-        context(), spv::Op::OpBranch, 0, 0,
-        std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {cont_blk_id}}}));
+    (void)builder.AddBranch(cont_blk_id);
     input_func->AddBasicBlock(std::move(hdr_blk_ptr));
     // Continue/Work Block. Read next buffer pointer and break if greater
     // than ref_ptr arg.
@@ -386,10 +377,8 @@
         GetBoolId(), spv::Op::OpULessThanEqual, ref_end_inst->result_id(),
         len_load_inst->result_id());
     // Return test result
-    (void)builder.AddInstruction(MakeUnique<Instruction>(
-        context(), spv::Op::OpReturnValue, 0, 0,
-        std::initializer_list<Operand>{
-            {SPV_OPERAND_TYPE_ID, {len_test_inst->result_id()}}}));
+    (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
+                             len_test_inst->result_id());
     // Close block
     input_func->AddBasicBlock(std::move(bound_test_blk_ptr));
     // Close function and add function to module
@@ -422,10 +411,8 @@
   uint32_t ref_len = GetTypeLength(ref_ptr_ty_inst->GetSingleWordInOperand(1));
   uint32_t ref_len_id = builder->GetUintConstantId(ref_len);
   // Gen call to search and test function
-  const std::vector<uint32_t> args = {GetSearchAndTestFuncId(), *ref_uptr_id,
-                                      ref_len_id};
-  Instruction* call_inst =
-      builder->AddNaryOp(GetBoolId(), spv::Op::OpFunctionCall, args);
+  Instruction* call_inst = builder->AddFunctionCall(
+      GetBoolId(), GetSearchAndTestFuncId(), {*ref_uptr_id, ref_len_id});
   uint32_t retval = call_inst->result_id();
   return retval;
 }
diff --git a/source/opt/inst_debug_printf_pass.cpp b/source/opt/inst_debug_printf_pass.cpp
index 151b94c..4f97277 100644
--- a/source/opt/inst_debug_printf_pass.cpp
+++ b/source/opt/inst_debug_printf_pass.cpp
@@ -34,8 +34,8 @@
       const analysis::Type* c_ty = v_ty->element_type();
       uint32_t c_ty_id = type_mgr->GetId(c_ty);
       for (uint32_t c = 0; c < v_ty->element_count(); ++c) {
-        Instruction* c_inst = builder->AddIdLiteralOp(
-            c_ty_id, spv::Op::OpCompositeExtract, val_inst->result_id(), c);
+        Instruction* c_inst =
+            builder->AddCompositeExtract(c_ty_id, val_inst->result_id(), {c});
         GenOutputValues(c_inst, val_ids, builder);
       }
       return;
@@ -44,9 +44,8 @@
       // Select between uint32 zero or one
       uint32_t zero_id = builder->GetUintConstantId(0);
       uint32_t one_id = builder->GetUintConstantId(1);
-      Instruction* sel_inst =
-          builder->AddTernaryOp(GetUintId(), spv::Op::OpSelect,
-                                val_inst->result_id(), one_id, zero_id);
+      Instruction* sel_inst = builder->AddSelect(
+          GetUintId(), val_inst->result_id(), one_id, zero_id);
       val_ids->push_back(sel_inst->result_id());
       return;
     }
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index 441d943..c6e4050 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -54,7 +54,6 @@
 
 void InstrumentPass::MovePostludeCode(
     UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
-  // new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
   // Move contents of original ref block.
   for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
        cii = ref_block_itr->begin()) {
@@ -77,21 +76,62 @@
 }
 
 std::unique_ptr<Instruction> InstrumentPass::NewLabel(uint32_t label_id) {
-  std::unique_ptr<Instruction> newLabel(
-      new Instruction(context(), spv::Op::OpLabel, 0, label_id, {}));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*newLabel);
-  return newLabel;
+  auto new_label =
+      MakeUnique<Instruction>(context(), spv::Op::OpLabel, 0, label_id,
+                              std::initializer_list<Operand>{});
+  get_def_use_mgr()->AnalyzeInstDefUse(&*new_label);
+  return new_label;
+}
+
+std::unique_ptr<Function> InstrumentPass::StartFunction(
+    uint32_t func_id, const analysis::Type* return_type,
+    const std::vector<const analysis::Type*>& param_types) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::Function* func_type = GetFunction(return_type, param_types);
+
+  const std::vector<Operand> operands{
+      {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+       {uint32_t(spv::FunctionControlMask::MaskNone)}},
+      {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_mgr->GetId(func_type)}},
+  };
+  auto func_inst =
+      MakeUnique<Instruction>(context(), spv::Op::OpFunction,
+                              type_mgr->GetId(return_type), func_id, operands);
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+  return MakeUnique<Function>(std::move(func_inst));
+}
+
+std::unique_ptr<Instruction> InstrumentPass::EndFunction() {
+  auto end = MakeUnique<Instruction>(context(), spv::Op::OpFunctionEnd, 0, 0,
+                                     std::initializer_list<Operand>{});
+  get_def_use_mgr()->AnalyzeInstDefUse(end.get());
+  return end;
+}
+
+std::vector<uint32_t> InstrumentPass::AddParameters(
+    Function& func, const std::vector<const analysis::Type*>& param_types) {
+  std::vector<uint32_t> param_ids;
+  param_ids.reserve(param_types.size());
+  for (const analysis::Type* param : param_types) {
+    uint32_t pid = TakeNextId();
+    param_ids.push_back(pid);
+    auto param_inst =
+        MakeUnique<Instruction>(context(), spv::Op::OpFunctionParameter,
+                                context()->get_type_mgr()->GetId(param), pid,
+                                std::initializer_list<Operand>{});
+    get_def_use_mgr()->AnalyzeInstDefUse(param_inst.get());
+    func.AddParameter(std::move(param_inst));
+  }
+  return param_ids;
 }
 
 std::unique_ptr<Instruction> InstrumentPass::NewName(
     uint32_t id, const std::string& name_str) {
-  std::unique_ptr<Instruction> new_name(new Instruction(
+  return MakeUnique<Instruction>(
       context(), spv::Op::OpName, 0, 0,
       std::initializer_list<Operand>{
           {SPV_OPERAND_TYPE_ID, {id}},
-          {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}));
-
-  return new_name;
+          {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
 }
 
 std::unique_ptr<Instruction> InstrumentPass::NewGlobalName(
@@ -118,14 +158,12 @@
 
 std::unique_ptr<Instruction> InstrumentPass::NewMemberName(
     uint32_t id, uint32_t member_index, const std::string& name_str) {
-  std::unique_ptr<Instruction> new_name(new Instruction(
+  return MakeUnique<Instruction>(
       context(), spv::Op::OpMemberName, 0, 0,
       std::initializer_list<Operand>{
           {SPV_OPERAND_TYPE_ID, {id}},
           {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}},
-          {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}));
-
-  return new_name;
+          {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
 }
 
 uint32_t InstrumentPass::Gen32BitCvtCode(uint32_t val_id,
@@ -167,17 +205,15 @@
   // Cast value to 32-bit unsigned if necessary
   uint32_t val_id = GenUintCastCode(field_value_id, builder);
   // Store value
-  Instruction* data_idx_inst =
-      builder->AddBinaryOp(GetUintId(), spv::Op::OpIAdd, base_offset_id,
-                           builder->GetUintConstantId(field_offset));
+  Instruction* data_idx_inst = builder->AddIAdd(
+      GetUintId(), base_offset_id, builder->GetUintConstantId(field_offset));
   uint32_t buf_id = GetOutputBufferId();
   uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
-  Instruction* achain_inst =
-      builder->AddTernaryOp(buf_uint_ptr_id, spv::Op::OpAccessChain, buf_id,
-                            builder->GetUintConstantId(kDebugOutputDataOffset),
-                            data_idx_inst->result_id());
-  (void)builder->AddBinaryOp(0, spv::Op::OpStore, achain_inst->result_id(),
-                             val_id);
+  Instruction* achain_inst = builder->AddAccessChain(
+      buf_uint_ptr_id, buf_id,
+      {builder->GetUintConstantId(kDebugOutputDataOffset),
+       data_idx_inst->result_id()});
+  (void)builder->AddStore(achain_inst->result_id(), val_id);
 }
 
 void InstrumentPass::GenCommonStreamWriteCode(uint32_t record_sz,
@@ -202,8 +238,8 @@
 void InstrumentPass::GenFragCoordEltDebugOutputCode(
     uint32_t base_offset_id, uint32_t uint_frag_coord_id, uint32_t element,
     InstructionBuilder* builder) {
-  Instruction* element_val_inst = builder->AddIdLiteralOp(
-      GetUintId(), spv::Op::OpCompositeExtract, uint_frag_coord_id, element);
+  Instruction* element_val_inst =
+      builder->AddCompositeExtract(GetUintId(), uint_frag_coord_id, {element});
   GenDebugOutputFieldCode(base_offset_id, kInstFragOutFragCoordX + element,
                           element_val_inst->result_id(), builder);
 }
@@ -212,8 +248,7 @@
                                     InstructionBuilder* builder) {
   Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
   uint32_t type_id = GetPointeeTypeId(var_inst);
-  Instruction* load_inst =
-      builder->AddUnaryOp(type_id, spv::Op::OpLoad, var_id);
+  Instruction* load_inst = builder->AddLoad(type_id, var_id);
   return load_inst->result_id();
 }
 
@@ -249,12 +284,12 @@
       uint32_t load_id = GenVarLoad(context()->GetBuiltinInputVarId(uint32_t(
                                         spv::BuiltIn::GlobalInvocationId)),
                                     builder);
-      Instruction* x_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, load_id, 0);
-      Instruction* y_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, load_id, 1);
-      Instruction* z_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, load_id, 2);
+      Instruction* x_inst =
+          builder->AddCompositeExtract(GetUintId(), load_id, {0});
+      Instruction* y_inst =
+          builder->AddCompositeExtract(GetUintId(), load_id, {1});
+      Instruction* z_inst =
+          builder->AddCompositeExtract(GetUintId(), load_id, {2});
       GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdX,
                               x_inst->result_id(), builder);
       GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdY,
@@ -291,10 +326,10 @@
       Instruction* uvec3_cast_inst =
           builder->AddUnaryOp(GetVec3UintId(), spv::Op::OpBitcast, load_id);
       uint32_t uvec3_cast_id = uvec3_cast_inst->result_id();
-      Instruction* u_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, uvec3_cast_id, 0);
-      Instruction* v_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, uvec3_cast_id, 1);
+      Instruction* u_inst =
+          builder->AddCompositeExtract(GetUintId(), uvec3_cast_id, {0});
+      Instruction* v_inst =
+          builder->AddCompositeExtract(GetUintId(), uvec3_cast_id, {1});
       GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordU,
                               u_inst->result_id(), builder);
       GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordV,
@@ -302,8 +337,8 @@
     } break;
     case spv::ExecutionModel::Fragment: {
       // Load FragCoord and convert to Uint
-      Instruction* frag_coord_inst = builder->AddUnaryOp(
-          GetVec4FloatId(), spv::Op::OpLoad,
+      Instruction* frag_coord_inst = builder->AddLoad(
+          GetVec4FloatId(),
           context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::FragCoord)));
       Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
           GetVec4UintId(), spv::Op::OpBitcast, frag_coord_inst->result_id());
@@ -321,12 +356,12 @@
       uint32_t launch_id = GenVarLoad(
           context()->GetBuiltinInputVarId(uint32_t(spv::BuiltIn::LaunchIdNV)),
           builder);
-      Instruction* x_launch_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, launch_id, 0);
-      Instruction* y_launch_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, launch_id, 1);
-      Instruction* z_launch_inst = builder->AddIdLiteralOp(
-          GetUintId(), spv::Op::OpCompositeExtract, launch_id, 2);
+      Instruction* x_launch_inst =
+          builder->AddCompositeExtract(GetUintId(), launch_id, {0});
+      Instruction* y_launch_inst =
+          builder->AddCompositeExtract(GetUintId(), launch_id, {1});
+      Instruction* z_launch_inst =
+          builder->AddCompositeExtract(GetUintId(), launch_id, {2});
       GenDebugOutputFieldCode(base_offset_id, kInstRayTracingOutLaunchIdX,
                               x_launch_inst->result_id(), builder);
       GenDebugOutputFieldCode(base_offset_id, kInstRayTracingOutLaunchIdY,
@@ -344,11 +379,10 @@
   // Call debug output function. Pass func_idx, instruction_idx and
   // validation ids as args.
   uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
-  uint32_t output_func_id = GetStreamWriteFunctionId(stage_idx, val_id_cnt);
-  std::vector<uint32_t> args = {output_func_id,
-                                builder->GetUintConstantId(instruction_idx)};
+  std::vector<uint32_t> args = {builder->GetUintConstantId(instruction_idx)};
   (void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
-  (void)builder->AddNaryOp(GetVoidId(), spv::Op::OpFunctionCall, args);
+  (void)builder->AddFunctionCall(
+      GetVoidId(), GetStreamWriteFunctionId(stage_idx, val_id_cnt), args);
 }
 
 bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
@@ -360,33 +394,38 @@
 }
 
 uint32_t InstrumentPass::GenDebugDirectRead(
-    const std::vector<uint32_t>& offset_ids, InstructionBuilder* ref_builder) {
+    const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
   // Call debug input function. Pass func_idx and offset ids as args.
-  uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
-  uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
-  std::vector<uint32_t> args = {input_func_id};
-  (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
+  const uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
+  const uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
+  return GenReadFunctionCall(input_func_id, offset_ids, builder);
+}
+
+uint32_t InstrumentPass::GenReadFunctionCall(
+    uint32_t func_id, const std::vector<uint32_t>& func_call_args,
+    InstructionBuilder* ref_builder) {
   // If optimizing direct reads and the call has already been generated,
   // use its result
   if (opt_direct_reads_) {
-    uint32_t res_id = call2id_[args];
+    uint32_t res_id = call2id_[func_call_args];
     if (res_id != 0) return res_id;
   }
-  // If the offsets are all constants, the call can be moved to the first block
-  // of the function where its result can be reused. One example where this is
-  // profitable is for uniform buffer references, of which there are often many.
+  // If the function arguments are all constants, the call can be moved to the
+  // first block of the function where its result can be reused. One example
+  // where this is profitable is for uniform buffer references, of which there
+  // are often many.
   InstructionBuilder builder(ref_builder->GetContext(),
                              &*ref_builder->GetInsertPoint(),
                              ref_builder->GetPreservedAnalysis());
-  bool insert_in_first_block = opt_direct_reads_ && AllConstant(offset_ids);
+  bool insert_in_first_block = opt_direct_reads_ && AllConstant(func_call_args);
   if (insert_in_first_block) {
     Instruction* insert_before = &*curr_func_->begin()->tail();
     builder.SetInsertPoint(insert_before);
   }
   uint32_t res_id =
-      builder.AddNaryOp(GetUintId(), spv::Op::OpFunctionCall, args)
+      builder.AddFunctionCall(GetUintId(), func_id, func_call_args)
           ->result_id();
-  if (insert_in_first_block) call2id_[args] = res_id;
+  if (insert_in_first_block) call2id_[func_call_args] = res_id;
   return res_id;
 }
 
@@ -502,32 +541,61 @@
   return 0;
 }
 
-analysis::Type* InstrumentPass::GetUintXRuntimeArrayType(
-    uint32_t width, analysis::Type** rarr_ty) {
+analysis::Integer* InstrumentPass::GetInteger(uint32_t width, bool is_signed) {
+  analysis::Integer i(width, is_signed);
+  analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&i);
+  assert(type && type->AsInteger());
+  return type->AsInteger();
+}
+
+analysis::Struct* InstrumentPass::GetStruct(
+    const std::vector<const analysis::Type*>& fields) {
+  analysis::Struct s(fields);
+  analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&s);
+  assert(type && type->AsStruct());
+  return type->AsStruct();
+}
+
+analysis::RuntimeArray* InstrumentPass::GetRuntimeArray(
+    const analysis::Type* element) {
+  analysis::RuntimeArray r(element);
+  analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&r);
+  assert(type && type->AsRuntimeArray());
+  return type->AsRuntimeArray();
+}
+
+analysis::Function* InstrumentPass::GetFunction(
+    const analysis::Type* return_val,
+    const std::vector<const analysis::Type*>& args) {
+  analysis::Function func(return_val, args);
+  analysis::Type* type = context()->get_type_mgr()->GetRegisteredType(&func);
+  assert(type && type->AsFunction());
+  return type->AsFunction();
+}
+
+analysis::RuntimeArray* InstrumentPass::GetUintXRuntimeArrayType(
+    uint32_t width, analysis::RuntimeArray** rarr_ty) {
   if (*rarr_ty == nullptr) {
-    analysis::DecorationManager* deco_mgr = get_decoration_mgr();
-    analysis::TypeManager* type_mgr = context()->get_type_mgr();
-    analysis::Integer uint_ty(width, false);
-    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
-    analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
-    *rarr_ty = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
-    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(*rarr_ty);
+    *rarr_ty = GetRuntimeArray(GetInteger(width, false));
+    uint32_t uint_arr_ty_id =
+        context()->get_type_mgr()->GetTypeInstruction(*rarr_ty);
     // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
     // a block, and will therefore be decorated with an ArrayStride. Therefore
     // the undecorated type returned here will not be pre-existing and can
     // safely be decorated. Since this type is now decorated, it is out of
     // sync with the TypeManager and therefore the TypeManager must be
     // invalidated after this pass.
-    assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
+    assert(get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
            "used RuntimeArray type returned");
-    deco_mgr->AddDecorationVal(
+    get_decoration_mgr()->AddDecorationVal(
         uint_arr_ty_id, uint32_t(spv::Decoration::ArrayStride), width / 8u);
   }
   return *rarr_ty;
 }
 
-analysis::Type* InstrumentPass::GetUintRuntimeArrayType(uint32_t width) {
-  analysis::Type** rarr_ty =
+analysis::RuntimeArray* InstrumentPass::GetUintRuntimeArrayType(
+    uint32_t width) {
+  analysis::RuntimeArray** rarr_ty =
       (width == 64) ? &uint64_rarr_ty_ : &uint32_rarr_ty_;
   return GetUintXRuntimeArrayType(width, rarr_ty);
 }
@@ -546,11 +614,10 @@
     // If not created yet, create one
     analysis::DecorationManager* deco_mgr = get_decoration_mgr();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
-    analysis::Type* reg_uint_rarr_ty = GetUintRuntimeArrayType(32);
-    analysis::Integer uint_ty(32, false);
-    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
-    analysis::Struct buf_ty({reg_uint_ty, reg_uint_ty, reg_uint_rarr_ty});
-    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    analysis::RuntimeArray* reg_uint_rarr_ty = GetUintRuntimeArrayType(32);
+    analysis::Integer* reg_uint_ty = GetInteger(32, false);
+    analysis::Type* reg_buf_ty =
+        GetStruct({reg_uint_ty, reg_uint_ty, reg_uint_rarr_ty});
     uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
     // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
     // must be a block, and will therefore be decorated with Block. Therefore
@@ -604,8 +671,7 @@
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
     uint32_t width = (validation_id_ == kInstValidationIdBuffAddr) ? 64u : 32u;
     analysis::Type* reg_uint_rarr_ty = GetUintRuntimeArrayType(width);
-    analysis::Struct buf_ty({reg_uint_rarr_ty});
-    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    analysis::Struct* reg_buf_ty = GetStruct({reg_uint_rarr_ty});
     uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
     // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
     // must be a block, and will therefore be decorated with Block. Therefore
@@ -747,37 +813,17 @@
     // Create function
     param2output_func_id_[param_cnt] = TakeNextId();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
-    std::vector<const analysis::Type*> param_types;
-    for (uint32_t c = 0; c < param_cnt; ++c)
-      param_types.push_back(type_mgr->GetType(GetUintId()));
-    analysis::Function func_ty(type_mgr->GetType(GetVoidId()), param_types);
-    analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
-    std::unique_ptr<Instruction> func_inst(
-        new Instruction(get_module()->context(), spv::Op::OpFunction,
-                        GetVoidId(), param2output_func_id_[param_cnt],
-                        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
-                          {uint32_t(spv::FunctionControlMask::MaskNone)}},
-                         {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-                          {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
-    get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
-    std::unique_ptr<Function> output_func =
-        MakeUnique<Function>(std::move(func_inst));
-    // Add parameters
-    std::vector<uint32_t> param_vec;
-    for (uint32_t c = 0; c < param_cnt; ++c) {
-      uint32_t pid = TakeNextId();
-      param_vec.push_back(pid);
-      std::unique_ptr<Instruction> param_inst(
-          new Instruction(get_module()->context(), spv::Op::OpFunctionParameter,
-                          GetUintId(), pid, {}));
-      get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
-      output_func->AddParameter(std::move(param_inst));
-    }
+
+    const std::vector<const analysis::Type*> param_types(param_cnt,
+                                                         GetInteger(32, false));
+    std::unique_ptr<Function> output_func = StartFunction(
+        param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types);
+
+    std::vector<uint32_t> param_ids = AddParameters(*output_func, param_types);
+
     // Create first block
-    uint32_t test_blk_id = TakeNextId();
-    std::unique_ptr<Instruction> test_label(NewLabel(test_blk_id));
-    std::unique_ptr<BasicBlock> new_blk_ptr =
-        MakeUnique<BasicBlock>(std::move(test_label));
+    auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
+
     InstructionBuilder builder(
         context(), &*new_blk_ptr,
         IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
@@ -786,9 +832,9 @@
     uint32_t obuf_record_sz = val_spec_offset + val_spec_param_cnt;
     uint32_t buf_id = GetOutputBufferId();
     uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
-    Instruction* obuf_curr_sz_ac_inst =
-        builder.AddBinaryOp(buf_uint_ptr_id, spv::Op::OpAccessChain, buf_id,
-                            builder.GetUintConstantId(kDebugOutputSizeOffset));
+    Instruction* obuf_curr_sz_ac_inst = builder.AddAccessChain(
+        buf_uint_ptr_id, buf_id,
+        {builder.GetUintConstantId(kDebugOutputSizeOffset)});
     // Fetch the current debug buffer written size atomically, adding the
     // size of the record to be written.
     uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
@@ -802,8 +848,8 @@
     uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
     // Compute new written size
     Instruction* obuf_new_sz_inst =
-        builder.AddBinaryOp(GetUintId(), spv::Op::OpIAdd, obuf_curr_sz_id,
-                            builder.GetUintConstantId(obuf_record_sz));
+        builder.AddIAdd(GetUintId(), obuf_curr_sz_id,
+                        builder.GetUintConstantId(obuf_record_sz));
     // Fetch the data bound
     Instruction* obuf_bnd_inst =
         builder.AddIdLiteralOp(GetUintId(), spv::Op::OpArrayLength,
@@ -825,13 +871,13 @@
     new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
     builder.SetInsertPoint(&*new_blk_ptr);
     // Generate common and stage-specific debug record members
-    GenCommonStreamWriteCode(obuf_record_sz, param_vec[kInstCommonParamInstIdx],
+    GenCommonStreamWriteCode(obuf_record_sz, param_ids[kInstCommonParamInstIdx],
                              stage_idx, obuf_curr_sz_id, &builder);
     GenStageStreamWriteCode(stage_idx, obuf_curr_sz_id, &builder);
     // Gen writes of validation specific data
     for (uint32_t i = 0; i < val_spec_param_cnt; ++i) {
       GenDebugOutputFieldCode(obuf_curr_sz_id, val_spec_offset + i,
-                              param_vec[kInstCommonParamCnt + i], &builder);
+                              param_ids[kInstCommonParamCnt + i], &builder);
     }
     // Close write block and gen merge block
     (void)builder.AddBranch(merge_blk_id);
@@ -840,11 +886,9 @@
     builder.SetInsertPoint(&*new_blk_ptr);
     // Close merge block and function and add function to module
     (void)builder.AddNullaryOp(0, spv::Op::OpReturn);
+
     output_func->AddBasicBlock(std::move(new_blk_ptr));
-    std::unique_ptr<Instruction> func_end_inst(new Instruction(
-        get_module()->context(), spv::Op::OpFunctionEnd, 0, 0, {}));
-    get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
-    output_func->SetFunctionEnd(std::move(func_end_inst));
+    output_func->SetFunctionEnd(EndFunction());
     context()->AddFunction(std::move(output_func));
 
     std::string name("stream_write_");
@@ -861,77 +905,48 @@
   if (func_id != 0) return func_id;
   // Create input function for param_cnt.
   func_id = TakeNextId();
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
-  std::vector<const analysis::Type*> param_types;
-  for (uint32_t c = 0; c < param_cnt; ++c)
-    param_types.push_back(type_mgr->GetType(GetUintId()));
-  uint32_t ibuf_type_id = GetInputBufferTypeId();
-  analysis::Function func_ty(type_mgr->GetType(ibuf_type_id), param_types);
-  analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
-  std::unique_ptr<Instruction> func_inst(new Instruction(
-      get_module()->context(), spv::Op::OpFunction, ibuf_type_id, func_id,
-      {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
-        {uint32_t(spv::FunctionControlMask::MaskNone)}},
-       {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-        {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+  analysis::Integer* uint_type = GetInteger(32, false);
+  std::vector<const analysis::Type*> param_types(param_cnt, uint_type);
+
   std::unique_ptr<Function> input_func =
-      MakeUnique<Function>(std::move(func_inst));
-  // Add parameters
-  std::vector<uint32_t> param_vec;
-  for (uint32_t c = 0; c < param_cnt; ++c) {
-    uint32_t pid = TakeNextId();
-    param_vec.push_back(pid);
-    std::unique_ptr<Instruction> param_inst(
-        new Instruction(get_module()->context(), spv::Op::OpFunctionParameter,
-                        GetUintId(), pid, {}));
-    get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
-    input_func->AddParameter(std::move(param_inst));
-  }
+      StartFunction(func_id, uint_type, param_types);
+  std::vector<uint32_t> param_ids = AddParameters(*input_func, param_types);
+
   // Create block
-  uint32_t blk_id = TakeNextId();
-  std::unique_ptr<Instruction> blk_label(NewLabel(blk_id));
-  std::unique_ptr<BasicBlock> new_blk_ptr =
-      MakeUnique<BasicBlock>(std::move(blk_label));
+  auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
   InstructionBuilder builder(
       context(), &*new_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   // For each offset parameter, generate new offset with parameter, adding last
   // loaded value if it exists, and load value from input buffer at new offset.
   // Return last loaded value.
+  uint32_t ibuf_type_id = GetInputBufferTypeId();
   uint32_t buf_id = GetInputBufferId();
   uint32_t buf_ptr_id = GetInputBufferPtrId();
   uint32_t last_value_id = 0;
   for (uint32_t p = 0; p < param_cnt; ++p) {
     uint32_t offset_id;
     if (p == 0) {
-      offset_id = param_vec[0];
+      offset_id = param_ids[0];
     } else {
       if (ibuf_type_id != GetUintId()) {
-        Instruction* ucvt_inst =
-            builder.AddUnaryOp(GetUintId(), spv::Op::OpUConvert, last_value_id);
-        last_value_id = ucvt_inst->result_id();
+        last_value_id =
+            builder.AddUnaryOp(GetUintId(), spv::Op::OpUConvert, last_value_id)
+                ->result_id();
       }
-      Instruction* offset_inst = builder.AddBinaryOp(
-          GetUintId(), spv::Op::OpIAdd, last_value_id, param_vec[p]);
-      offset_id = offset_inst->result_id();
+      offset_id = builder.AddIAdd(GetUintId(), last_value_id, param_ids[p])
+                      ->result_id();
     }
-    Instruction* ac_inst = builder.AddTernaryOp(
-        buf_ptr_id, spv::Op::OpAccessChain, buf_id,
-        builder.GetUintConstantId(kDebugInputDataOffset), offset_id);
-    Instruction* load_inst =
-        builder.AddUnaryOp(ibuf_type_id, spv::Op::OpLoad, ac_inst->result_id());
-    last_value_id = load_inst->result_id();
+    Instruction* ac_inst = builder.AddAccessChain(
+        buf_ptr_id, buf_id,
+        {builder.GetUintConstantId(kDebugInputDataOffset), offset_id});
+    last_value_id =
+        builder.AddLoad(ibuf_type_id, ac_inst->result_id())->result_id();
   }
-  (void)builder.AddInstruction(MakeUnique<Instruction>(
-      context(), spv::Op::OpReturnValue, 0, 0,
-      std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {last_value_id}}}));
+  (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, last_value_id);
   // Close block and function and add function to module
   input_func->AddBasicBlock(std::move(new_blk_ptr));
-  std::unique_ptr<Instruction> func_end_inst(new Instruction(
-      get_module()->context(), spv::Op::OpFunctionEnd, 0, 0, {}));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
-  input_func->SetFunctionEnd(std::move(func_end_inst));
+  input_func->SetFunctionEnd(EndFunction());
   context()->AddFunction(std::move(input_func));
 
   std::string name("direct_read_");
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index e98ba88..1311929 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -214,6 +214,10 @@
   uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
                               InstructionBuilder* builder);
 
+  uint32_t GenReadFunctionCall(uint32_t func_id,
+                               const std::vector<uint32_t>& args,
+                               InstructionBuilder* builder);
+
   // Generate code to convert integer |value_id| to 32bit, if needed. Return
   // an id to the 32bit equivalent.
   uint32_t Gen32BitCvtCode(uint32_t value_id, InstructionBuilder* builder);
@@ -222,6 +226,15 @@
   // Return an id to the Uint equivalent.
   uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
 
+  std::unique_ptr<Function> StartFunction(
+      uint32_t func_id, const analysis::Type* return_type,
+      const std::vector<const analysis::Type*>& param_types);
+
+  std::vector<uint32_t> AddParameters(
+      Function& func, const std::vector<const analysis::Type*>& param_types);
+
+  std::unique_ptr<Instruction> EndFunction();
+
   // Return new label.
   std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
 
@@ -253,12 +266,20 @@
   // Return id for void type
   uint32_t GetVoidId();
 
-  // Return pointer to type for runtime array of uint
-  analysis::Type* GetUintXRuntimeArrayType(uint32_t width,
-                                           analysis::Type** rarr_ty);
+  // Get registered type structures
+  analysis::Integer* GetInteger(uint32_t width, bool is_signed);
+  analysis::Struct* GetStruct(const std::vector<const analysis::Type*>& fields);
+  analysis::RuntimeArray* GetRuntimeArray(const analysis::Type* element);
+  analysis::Function* GetFunction(
+      const analysis::Type* return_val,
+      const std::vector<const analysis::Type*>& args);
 
   // Return pointer to type for runtime array of uint
-  analysis::Type* GetUintRuntimeArrayType(uint32_t width);
+  analysis::RuntimeArray* GetUintXRuntimeArrayType(
+      uint32_t width, analysis::RuntimeArray** rarr_ty);
+
+  // Return pointer to type for runtime array of uint
+  analysis::RuntimeArray* GetUintRuntimeArrayType(uint32_t width);
 
   // Return id for buffer uint type
   uint32_t GetOutputBufferPtrId();
@@ -448,10 +469,10 @@
   bool storage_buffer_ext_defined_;
 
   // runtime array of uint type
-  analysis::Type* uint64_rarr_ty_;
+  analysis::RuntimeArray* uint64_rarr_ty_;
 
   // runtime array of uint type
-  analysis::Type* uint32_rarr_ty_;
+  analysis::RuntimeArray* uint32_rarr_ty_;
 
   // Pre-instrumentation same-block insts
   std::unordered_map<uint32_t, Instruction*> same_block_pre_;
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index be0daeb..cbc4b82 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -1065,3 +1065,95 @@
       MakeUnique<opt::FixFuncCallArgumentsPass>());
 }
 }  // namespace spvtools
+
+extern "C" {
+
+SPIRV_TOOLS_EXPORT spv_optimizer_t* spvOptimizerCreate(spv_target_env env) {
+  return reinterpret_cast<spv_optimizer_t*>(new spvtools::Optimizer(env));
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerDestroy(spv_optimizer_t* optimizer) {
+  delete reinterpret_cast<spvtools::Optimizer*>(optimizer);
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerSetMessageConsumer(
+    spv_optimizer_t* optimizer, spv_message_consumer consumer) {
+  reinterpret_cast<spvtools::Optimizer*>(optimizer)->
+      SetMessageConsumer(
+          [consumer](spv_message_level_t level, const char* source,
+                     const spv_position_t& position, const char* message) {
+            return consumer(level, source, &position, message);
+          });
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerRegisterLegalizationPasses(
+    spv_optimizer_t* optimizer) {
+  reinterpret_cast<spvtools::Optimizer*>(optimizer)->
+      RegisterLegalizationPasses();
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerRegisterPerformancePasses(
+    spv_optimizer_t* optimizer) {
+  reinterpret_cast<spvtools::Optimizer*>(optimizer)->
+      RegisterPerformancePasses();
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerRegisterSizePasses(
+    spv_optimizer_t* optimizer) {
+  reinterpret_cast<spvtools::Optimizer*>(optimizer)->RegisterSizePasses();
+}
+
+SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassFromFlag(
+    spv_optimizer_t* optimizer, const char* flag)
+{
+  return reinterpret_cast<spvtools::Optimizer*>(optimizer)->
+      RegisterPassFromFlag(flag);
+}
+
+SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassesFromFlags(
+    spv_optimizer_t* optimizer, const char** flags, const size_t flag_count) {
+  std::vector<std::string> opt_flags;
+  for (uint32_t i = 0; i < flag_count; i++) {
+    opt_flags.emplace_back(flags[i]);
+  }
+
+  return reinterpret_cast<spvtools::Optimizer*>(optimizer)->
+      RegisterPassesFromFlags(opt_flags);
+}
+
+SPIRV_TOOLS_EXPORT
+spv_result_t spvOptimizerRun(spv_optimizer_t* optimizer,
+                             const uint32_t* binary,
+                             const size_t word_count,
+                             spv_binary* optimized_binary,
+                             const spv_optimizer_options options) {
+  std::vector<uint32_t> optimized;
+
+  if (!reinterpret_cast<spvtools::Optimizer*>(optimizer)->
+      Run(binary, word_count, &optimized, options)) {
+    return SPV_ERROR_INTERNAL;
+  }
+
+  auto result_binary = new spv_binary_t();
+  if (!result_binary) {
+      *optimized_binary = nullptr;
+      return SPV_ERROR_OUT_OF_MEMORY;
+  }
+
+  result_binary->code = new uint32_t[optimized.size()];
+  if (!result_binary->code) {
+      delete result_binary;
+      *optimized_binary = nullptr;
+      return SPV_ERROR_OUT_OF_MEMORY;
+  }
+  result_binary->wordCount = optimized.size();
+
+  memcpy(result_binary->code, optimized.data(),
+         optimized.size() * sizeof(uint32_t));
+
+  *optimized_binary = result_binary;
+
+  return SPV_SUCCESS;
+}
+
+}  // extern "C"
diff --git a/source/val/validate_bitwise.cpp b/source/val/validate_bitwise.cpp
index 87c9556..6ab1fae 100644
--- a/source/val/validate_bitwise.cpp
+++ b/source/val/validate_bitwise.cpp
@@ -206,13 +206,14 @@
                << spvOpcodeString(opcode);
 
       const uint32_t base_type = _.GetOperandTypeId(inst, 2);
-      const uint32_t base_dimension = _.GetDimension(base_type);
-      const uint32_t result_dimension = _.GetDimension(result_type);
 
       if (spv_result_t error = ValidateBaseType(_, inst, base_type)) {
         return error;
       }
 
+      const uint32_t base_dimension = _.GetDimension(base_type);
+      const uint32_t result_dimension = _.GetDimension(result_type);
+
       if (base_dimension != result_dimension)
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected Base dimension to be equal to Result Type "
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index e7adab8..430d819 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -349,6 +349,15 @@
                << ", OpTypeRuntimeArray must only be used for the last member "
                   "of an OpTypeStruct";
       }
+
+      if (!_.HasDecoration(inst->id(), spv::Decoration::Block) &&
+          !_.HasDecoration(inst->id(), spv::Decoration::BufferBlock)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << _.VkErrorID(4680)
+               << spvLogStringForEnv(_.context()->target_env)
+               << ", OpTypeStruct containing an OpTypeRuntimeArray "
+               << "must be decorated with Block or BufferBlock.";
+      }
     }
   }
 
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index af24e65..3b2d384 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -21,6 +21,7 @@
        analyze_live_input_test.cpp
        assembly_builder_test.cpp
        block_merge_test.cpp
+       c_interface_test.cpp
        ccp_test.cpp
        cfg_cleanup_test.cpp
        cfg_test.cpp
diff --git a/test/opt/c_interface_test.cpp b/test/opt/c_interface_test.cpp
new file mode 100644
index 0000000..a172525
--- /dev/null
+++ b/test/opt/c_interface_test.cpp
@@ -0,0 +1,534 @@
+// Copyright (c) 2023 Nintendo
+//
+// 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 <string>
+#include <iostream>
+
+#include "gtest/gtest.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace {
+
+TEST(OptimizerCInterface, DefaultConsumerWithValidationNoPassesForInvalidInput) {
+  const uint32_t spirv[] = {
+      0xDEADFEED, // Invalid Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x01000000, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  // Do not register any passes
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, true);
+
+  spv_binary binary = nullptr;
+  EXPECT_NE(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_EQ(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, SpecifyConsumerWithValidationNoPassesForInvalidInput) {
+  const uint32_t spirv[] = {
+      0xDEADFEED, // Invalid Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x01000000, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  spvOptimizerSetMessageConsumer(
+      optimizer,
+      [](spv_message_level_t, const char*, const spv_position_t*,
+         const char* message) {
+        std::cout << message << std::endl;
+      });
+
+  // Do not register any passes
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, true);
+
+  testing::internal::CaptureStdout();
+
+  spv_binary binary = nullptr;
+  EXPECT_NE(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_EQ(binary, nullptr);
+
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_STRNE(output.c_str(), "");
+
+  spvOptimizerOptionsDestroy(options);
+
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerWithValidationNoPassesForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000001, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  // Do not register any passes
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, true);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Should remain unchanged
+  EXPECT_EQ(binary->wordCount, sizeof(spirv) / sizeof(uint32_t));
+  EXPECT_EQ(memcmp(binary->code, spirv, sizeof(spirv) / sizeof(uint32_t)), 0);
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerNoPassesForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000003, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001, // GLSL450
+      0x00040015, // OpTypeInt
+      0x00000001, // %1
+      0x00000020, // 32 Bits
+      0x00000000, // Unsigned
+      0x0004002B, // OpConstant
+      0x00000001, // %1
+      0x00000002, // %2
+      0x00000001  // 1
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  // Do not register any passes
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, true);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Should remain unchanged
+  EXPECT_EQ(binary->wordCount, sizeof(spirv) / sizeof(uint32_t));
+  EXPECT_EQ(memcmp(binary->code, spirv, sizeof(spirv) / sizeof(uint32_t)), 0);
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerLegalizationPassesForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000003, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001, // GLSL450
+      0x00040015, // OpTypeInt
+      0x00000001, // %1
+      0x00000020, // 32 Bits
+      0x00000000, // Unsigned
+      0x0004002B, // OpConstant
+      0x00000001, // %1
+      0x00000002, // %2
+      0x00000001  // 1
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  spvOptimizerRegisterLegalizationPasses(optimizer);
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, false);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Only check that SPV_SUCCESS is returned, do not verify output
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerPerformancePassesForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000003, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001, // GLSL450
+      0x00040015, // OpTypeInt
+      0x00000001, // %1
+      0x00000020, // 32 Bits
+      0x00000000, // Unsigned
+      0x0004002B, // OpConstant
+      0x00000001, // %1
+      0x00000002, // %2
+      0x00000001  // 1
+  };
+  const uint32_t expected_spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000001, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  spvOptimizerRegisterPerformancePasses(optimizer);
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, false);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Unreferenced OpTypeInt and OpConstant should be removed
+  EXPECT_EQ(binary->wordCount, sizeof(expected_spirv) / sizeof(uint32_t));
+  EXPECT_EQ(memcmp(binary->code, expected_spirv,
+                   sizeof(expected_spirv) / sizeof(uint32_t)), 0);
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerSizePassesForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000003, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001, // GLSL450
+      0x00040015, // OpTypeInt
+      0x00000001, // %1
+      0x00000020, // 32 Bits
+      0x00000000, // Unsigned
+      0x0004002B, // OpConstant
+      0x00000001, // %1
+      0x00000002, // %2
+      0x00000001  // 1
+  };
+  const uint32_t expected_spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000001, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  spvOptimizerRegisterSizePasses(optimizer);
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, false);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Unreferenced OpTypeInt and OpConstant should be removed
+  EXPECT_EQ(binary->wordCount, sizeof(expected_spirv) / sizeof(uint32_t));
+  EXPECT_EQ(memcmp(binary->code, expected_spirv,
+                   sizeof(expected_spirv) / sizeof(uint32_t)), 0);
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerPassFromFlagForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000003, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001, // GLSL450
+      0x00040015, // OpTypeInt
+      0x00000001, // %1
+      0x00000020, // 32 Bits
+      0x00000000, // Unsigned
+      0x0004002B, // OpConstant
+      0x00000001, // %1
+      0x00000002, // %2
+      0x00000001  // 1
+  };
+  const uint32_t expected_spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000001, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  EXPECT_TRUE(spvOptimizerRegisterPassFromFlag(
+      optimizer, "--eliminate-dead-code-aggressive"));
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, false);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Unreferenced OpTypeInt and OpConstant should be removed
+  EXPECT_EQ(binary->wordCount, sizeof(expected_spirv) / sizeof(uint32_t));
+  EXPECT_EQ(memcmp(binary->code, expected_spirv,
+                   sizeof(expected_spirv) / sizeof(uint32_t)), 0);
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerPassesFromFlagsForValidInput) {
+  const uint32_t spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000003, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001, // GLSL450
+      0x00040015, // OpTypeInt
+      0x00000001, // %1
+      0x00000020, // 32 Bits
+      0x00000000, // Unsigned
+      0x0004002B, // OpConstant
+      0x00000001, // %1
+      0x00000002, // %2
+      0x00000001  // 1
+  };
+  const uint32_t expected_spirv[] = {
+      0x07230203, // Magic
+      0x00010100, // Version 1.1
+      0x00000000, // No Generator
+      0x00000001, // Bound
+      0x00000000, // Schema
+      0x00020011, // OpCapability
+      0x00000001, // Shader
+      0x00020011, // OpCapability
+      0x00000005, // Linkage
+      0x0003000E, // OpMemoryModel
+      0x00000000, // Logical
+      0x00000001  // GLSL450
+  };
+
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  const char* flags[2] = {
+      "--eliminate-dead-const",
+      "--eliminate-dead-code-aggressive"
+  };
+
+  EXPECT_TRUE(spvOptimizerRegisterPassesFromFlags(
+      optimizer, flags, sizeof(flags) / sizeof(const char*)));
+
+  auto options = spvOptimizerOptionsCreate();
+  ASSERT_NE(options, nullptr);
+  spvOptimizerOptionsSetRunValidator(options, false);
+
+  spv_binary binary = nullptr;
+  EXPECT_EQ(SPV_SUCCESS,
+            spvOptimizerRun(optimizer, spirv, sizeof(spirv) / sizeof(uint32_t),
+                            &binary, options));
+  ASSERT_NE(binary, nullptr);
+
+  spvOptimizerOptionsDestroy(options);
+
+  // Unreferenced OpTypeInt and OpConstant should be removed
+  EXPECT_EQ(binary->wordCount, sizeof(expected_spirv) / sizeof(uint32_t));
+  EXPECT_EQ(memcmp(binary->code, expected_spirv,
+                   sizeof(expected_spirv) / sizeof(uint32_t)), 0);
+
+  spvBinaryDestroy(binary);
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerInvalidPassFromFlag) {
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  EXPECT_FALSE(spvOptimizerRegisterPassFromFlag(
+      optimizer, "--this-is-not-a-valid-pass"));
+
+  spvOptimizerDestroy(optimizer);
+}
+
+TEST(OptimizerCInterface, DefaultConsumerInvalidPassesFromFlags) {
+  auto optimizer = spvOptimizerCreate(SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_NE(optimizer, nullptr);
+
+  const char* flags[2] = {
+      "--eliminate-dead-const",
+      "--this-is-not-a-valid-pass"
+  };
+
+  EXPECT_FALSE(spvOptimizerRegisterPassesFromFlags(
+      optimizer, flags, sizeof(flags) / sizeof(const char*)));
+
+  spvOptimizerDestroy(optimizer);
+}
+
+}  // namespace
+}  // namespace spvtools
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 06b91f3..cc9bcd6 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -7363,7 +7363,16 @@
             "%5 = OpCompositeConstruct %v2int %3 %4\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        5, true)
+        5, true),
+    // Test case 16: Don't fold when type cannot be deduced to a constant.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%4 = OpCompositeInsert %struct_v2int_int_int %int_1 %struct_v2int_int_int_null 2\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        4, false)
 ));
 
 INSTANTIATE_TEST_SUITE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index 3600d0d..d450511 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -1040,10 +1040,6 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-; CHECK: uint_0 = OpConstant %uint 0
-; CHECK: bool = OpTypeBool
-; CHECK: %41 = OpTypeFunction %void %uint %uint %uint %uint
-; CHECK: _runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
 ; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
@@ -2385,14 +2381,7 @@
 %_ptr_Input_float = OpTypePointer Input %float
 %b = OpVariable %_ptr_Input_float Input
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %uint = OpTypeInt 32 0
-; CHECK: %26 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-; CHECK: %48 = OpTypeFunction %void %uint %uint %uint %uint
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ; CHECK: %v4uint = OpTypeVector %uint 4
 ; CHECK: %102 = OpTypeFunction %uint %uint %uint %uint %uint
@@ -2508,10 +2497,7 @@
 %nu_ii = OpVariable %_ptr_Input_int Input
 %int_0 = OpConstant %int 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %bool = OpTypeBool
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ; CHECK: %v4float = OpTypeVector %float 4
 ; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
 ; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
@@ -2637,11 +2623,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: %v3uint = OpTypeVector %uint 3
 ; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
 ; CHECK: %gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
@@ -2797,12 +2779,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %34 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 ; CHECK: [[null_uint:%\w+]] = OpConstantNull %uint
 )";
@@ -2954,12 +2931,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %34 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: [[launch_id]] = OpVariable %_ptr_Input_v3uint Input
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 ; CHECK: [[null_uint:%\w+]] = OpConstantNull %uint
@@ -3112,12 +3084,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %34 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: [[launch_id]] = OpVariable %_ptr_Input_v3uint Input
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 ; CHECK: [[null_uint:%\w+]] = OpConstantNull %uint
@@ -3270,12 +3237,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %34 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: [[launch_id]] = OpVariable %_ptr_Input_v3uint Input
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 ; CHECK: [[null_uint:%\w+]] = OpConstantNull %uint
@@ -3428,12 +3390,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %34 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: [[launch_id]] = OpVariable %_ptr_Input_v3uint Input
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 ; CHECK: [[null_uint:%\w+]] = OpConstantNull %uint
@@ -3586,12 +3543,7 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-; CHECK: %34 = OpTypeFunction %uint %uint %uint
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-; CHECK: %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 ; CHECK: [[null_uint:%\w+]] = OpConstantNull %uint
 )";
@@ -3777,8 +3729,6 @@
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %outColor = OpVariable %_ptr_Output_v4float Output
 %float_0 = OpConstant %float 0
-; CHECK: %bool = OpTypeBool
-; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
 ; CHECK: [[null_v4float:%\w+]] = OpConstantNull %v4float
 )" + kInputGlobals + R"(
@@ -3963,11 +3913,7 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
- ;CHECK:         %122 = OpTypeFunction %uint %uint %uint %uint
- ;CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
- )" + kInputGlobals + R"(
- ;CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
- )" + kOutputGlobals + R"(
+ )" + kInputGlobals + kOutputGlobals + R"(
  ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
  ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
  ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -4154,12 +4100,7 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-;CHECK:        %105 = OpTypeFunction %uint %uint %uint %uint
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-;CHECK:       %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -4296,12 +4237,7 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-;CHECK:        %104 = OpTypeFunction %uint %uint %uint %uint %uint
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-;CHECK:       %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -4436,10 +4372,7 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-;CHECK:       %bool = OpTypeBool
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:        [[null_v4float:%\w+]] = OpConstantNull %v4float
@@ -4620,12 +4553,7 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-;CHECK:         %61 = OpTypeFunction %uint %uint %uint %uint
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-;CHECK:       %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:        [[null_v2float:%\w+]] = OpConstantNull %v2float
@@ -4750,12 +4678,7 @@
     %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
  %a_position = OpVariable %_ptr_Input_v4float Input
-;CHECK:         %37 = OpTypeFunction %uint %uint %uint %uint
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-;CHECK:       %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ;CHECK:%_ptr_Input_uint = OpTypePointer Input %uint
 ;CHECK:%gl_VertexIndex = OpVariable %_ptr_Input_uint Input
 ;CHECK:%gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
@@ -4873,12 +4796,7 @@
     %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
  %a_position = OpVariable %_ptr_Input_v4float Input
-;CHECK:         %37 = OpTypeFunction %uint %uint %uint %uint
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-;CHECK:       %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ;CHECK:%_ptr_Input_uint = OpTypePointer Input %uint
 ;CHECK:%gl_VertexIndex = OpVariable %_ptr_Input_uint Input
 ;CHECK:%gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
@@ -5003,12 +4921,7 @@
     %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
  %a_position = OpVariable %_ptr_Input_v4float Input
-;CHECK:         %46 = OpTypeFunction %uint %uint %uint %uint
-;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-)" + kInputGlobals + R"(
-;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-;CHECK:       %bool = OpTypeBool
-)" + kOutputGlobals + R"(
+)" + kInputGlobals + kOutputGlobals + R"(
 ;CHECK:%_ptr_Input_uint = OpTypePointer Input %uint
 ;CHECK:%gl_VertexIndex = OpVariable %_ptr_Input_uint Input
 ;CHECK:%gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
@@ -5202,7 +5115,7 @@
                      %x = OpVariable %_ptr_Output_v4float Output
 ;CHECK:           %uint = OpTypeInt 32 0
 ;CHECK:           %bool = OpTypeBool
-;CHECK:             %34 = OpTypeFunction %void %uint %uint %uint %uint %uint
+;CHECK:        {{%\w+}} = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
 ;CHECK:    %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
diff --git a/test/opt/inst_buff_addr_check_test.cpp b/test/opt/inst_buff_addr_check_test.cpp
index b08f7b0..4a56f60 100644
--- a/test/opt/inst_buff_addr_check_test.cpp
+++ b/test/opt/inst_buff_addr_check_test.cpp
@@ -272,7 +272,7 @@
 ; CHECK: %_runtimearr_ulong = OpTypeRuntimeArray %ulong
 )" + kInputGlobals + R"(
 ; CHECK: %_ptr_StorageBuffer_ulong = OpTypePointer StorageBuffer %ulong
-; CHECK: %70 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: {{%\w+}} = OpTypeFunction %void %uint %uint %uint %uint
 ; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
 ; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
@@ -524,7 +524,7 @@
 %ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
 ; CHECK: %47 = OpTypeFunction %bool %ulong %uint
 )" + kInputGlobals + R"(
-; CHECK: %90 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: {{%\w+}} = OpTypeFunction %void %uint %uint %uint %uint
 )" + kOutputGlobals + R"(
 ; CHECK: %143 = OpConstantNull %Test_0
 )";
diff --git a/test/opt/inst_debug_printf_test.cpp b/test/opt/inst_debug_printf_test.cpp
index 6a4cbdd..4031480 100644
--- a/test/opt/inst_debug_printf_test.cpp
+++ b/test/opt/inst_debug_printf_test.cpp
@@ -111,7 +111,7 @@
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %4 = OpVariable %_ptr_Output_v4float Output
 ; CHECK: %uint = OpTypeInt 32 0
-; CHECK: %38 = OpTypeFunction %void %uint %uint %uint %uint %uint %uint
+; CHECK: [[func_type:%\w+]] = OpTypeFunction %void %uint %uint %uint %uint %uint %uint
 ; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
 )" + kOutputGlobals + R"(
 ; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
@@ -149,7 +149,7 @@
 )";
 
   const std::string output_func = R"(
-; CHECK: %inst_printf_stream_write_6 = OpFunction %void None %38
+; CHECK: %inst_printf_stream_write_6 = OpFunction %void None [[func_type]]
 ; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
 ; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
 ; CHECK: [[param_3:%\w+]] = OpFunctionParameter %uint
diff --git a/test/val/val_bitwise_test.cpp b/test/val/val_bitwise_test.cpp
index bebaa84..b849e7b 100644
--- a/test/val/val_bitwise_test.cpp
+++ b/test/val/val_bitwise_test.cpp
@@ -643,6 +643,32 @@
               HasSubstr("Expected 32-bit int type for Base operand: BitCount"));
 }
 
+TEST_F(ValidateBitwise, OpBitCountPointer) {
+  const std::string body = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Function %int
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%var = OpVariable %ptr_int Function
+%count = OpBitCount %int %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected int scalar or vector type for Base operand: BitCount"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 04d373a..4f90e6b 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -5281,6 +5281,37 @@
           "rules: member 1 at offset 1 is not aligned to 4"));
 }
 
+TEST_F(ValidateDecorations, VulkanStructWithoutDecorationWithRuntimeArray) {
+  std::string str = R"(
+              OpCapability Shader
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %uint_t %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Vulkan, OpTypeStruct containing an OpTypeRuntimeArray "
+                        "must be decorated with Block or BufferBlock."));
+}
+
 TEST_F(ValidateDecorations, EmptyStructAtNonZeroOffsetGood) {
   const std::string spirv = R"(
 OpCapability Shader
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index d0735dc..d575318 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -2475,6 +2475,7 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
+OpDecorate %struct_t Block
 %uint_t = OpTypeInt 32 0
 %array_t = OpTypeRuntimeArray %uint_t
 %struct_t = OpTypeStruct %array_t
@@ -2498,7 +2499,7 @@
           "For Vulkan, OpTypeStruct variables containing OpTypeRuntimeArray "
           "must have storage class of StorageBuffer, PhysicalStorageBuffer, or "
           "Uniform.\n  %6 = "
-          "OpVariable %_ptr_Workgroup__struct_4 Workgroup\n"));
+          "OpVariable %_ptr_Workgroup__struct_2 Workgroup\n"));
 }
 
 TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructWithoutBlockBad) {
@@ -2507,6 +2508,7 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
+OpDecorate %struct_t BufferBlock
 %uint_t = OpTypeInt 32 0
 %array_t = OpTypeRuntimeArray %uint_t
 %struct_t = OpTypeStruct %array_t
@@ -2529,7 +2531,7 @@
                         "OpTypeRuntimeArray must be decorated with Block if it "
                         "has storage class StorageBuffer or "
                         "PhysicalStorageBuffer.\n  %6 = OpVariable "
-                        "%_ptr_StorageBuffer__struct_4 StorageBuffer\n"));
+                        "%_ptr_StorageBuffer__struct_2 StorageBuffer\n"));
 }
 
 TEST_F(ValidateMemory, VulkanRTAInsideUniformStructGood) {
@@ -2564,6 +2566,7 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
+OpDecorate %struct_t Block
 %uint_t = OpTypeInt 32 0
 %array_t = OpTypeRuntimeArray %uint_t
 %struct_t = OpTypeStruct %array_t
@@ -2585,7 +2588,7 @@
               HasSubstr("For Vulkan, an OpTypeStruct variable containing an "
                         "OpTypeRuntimeArray must be decorated with BufferBlock "
                         "if it has storage class Uniform.\n  %6 = OpVariable "
-                        "%_ptr_Uniform__struct_4 Uniform\n"));
+                        "%_ptr_Uniform__struct_2 Uniform\n"));
 }
 
 TEST_F(ValidateMemory, VulkanRTAInsideRTABad) {
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index aa647af..e3e74bc 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -41,8 +41,9 @@
            'Alastair F. Donaldson',
            'Mostafa Ashraf',
            'Shiyu Liu',
-           'ZHOU He']
-CURRENT_YEAR = 2022
+           'ZHOU He',
+           'Nintendo']
+CURRENT_YEAR = 2023
 
 FIRST_YEAR = 2014
 FINAL_YEAR = CURRENT_YEAR + 5
diff --git a/utils/roll_deps.sh b/utils/roll_deps.sh
index bf6d693..6289c94 100755
--- a/utils/roll_deps.sh
+++ b/utils/roll_deps.sh
@@ -31,7 +31,7 @@
 
 # We are not rolling google test for now. The latest version requires C++14.
 dependencies=("external/effcee/"
-#             "external/googletest/")
+              "external/googletest/"
               "external/re2/"
               "external/spirv-headers/")
 
diff --git a/utils/update_build_version.py b/utils/update_build_version.py
index b1c7b21..ec475ea 100755
--- a/utils/update_build_version.py
+++ b/utils/update_build_version.py
@@ -17,16 +17,13 @@
 # Updates an output file with version info unless the new content is the same
 # as the existing content.
 #
-# Args: <changes-file> <output-file>
+# Args: <repo-path> <output-file>
 #
 # The output file will contain a line of text consisting of two C source syntax
 # string literals separated by a comma:
-#  - The software version deduced from the given CHANGES file.
+#  - The software version deduced from the last release tag.
 #  - A longer string with the project name, the software version number, and
-#    git commit information for the CHANGES file's directory.  The commit
-#    information is the output of "git describe" if that succeeds, or "git
-#    rev-parse HEAD" if that succeeds, or otherwise a message containing the
-#    phrase "unknown hash".
+#    git commit information for this release.
 # The string contents are escaped as necessary.
 
 import datetime
@@ -39,6 +36,13 @@
 import sys
 import time
 
+# Regex to match the SPIR-V version tag.
+# Example of matching tags:
+#  - v2020.1
+#  - v2020.1-dev
+#  - v2020.1.rc1
+VERSION_REGEX = re.compile(r'^v(\d+)\.(\d+)(-dev|rc\d+)?$')
+
 # Format of the output generated by this script. Example:
 # "v2023.1", "SPIRV-Tools v2023.1 0fc5526f2b01a0cc89192c10cf8bef77f1007a62, 2023-01-18T14:51:49"
 OUTPUT_FORMAT = '"{version_tag}", "SPIRV-Tools {version_tag} {description}"\n'
@@ -79,38 +83,66 @@
         return False, None
     return p.returncode == 0, stdout
 
-def deduce_software_version(changes_file):
-    """Returns a tuple (success, software version number) parsed from the
-    given CHANGES file.
+def deduce_last_release(repo_path):
+    """Returns a software version number parsed from git tags."""
 
-    Success is set to True if the software version could be deduced.
-    Software version is undefined if success if False.
-    Function expects the CHANGES file to describes most recent versions first.
-    """
+    success, tag_list = command_output(['git', 'tag', '--sort=-v:refname'], repo_path)
+    if not success:
+      return False, None
 
-    # Match the first well-formed version-and-date line
-    # Allow trailing whitespace in the checked-out source code has
-    # unexpected carriage returns on a linefeed-only system such as
-    # Linux.
-    pattern = re.compile(r'^(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d\s*$')
-    with open(changes_file, mode='r') as f:
-        for line in f.readlines():
-            match = pattern.match(line)
-            if match:
-                return True, match.group(1)
+    latest_version_tag = None
+    for tag in tag_list.decode().splitlines():
+      if VERSION_REGEX.match(tag):
+        latest_version_tag = tag
+        break
+
+    if latest_version_tag is None:
+      logging.error("No tag matching version regex matching.")
+      return False, None
+    return True, latest_version_tag
+
+def get_last_release_tuple(repo_path):
+  success, version = deduce_last_release(repo_path)
+  if not success:
     return False, None
 
+  m = VERSION_REGEX.match(version)
+  if len(m.groups()) != 3:
+    return False, None
+  return True, (int(m.groups()[0]), int(m.groups()[1]))
 
-def describe(repo_path):
+def deduce_current_release(repo_path):
+  status, version_tuple = get_last_release_tuple(repo_path)
+  if not status:
+    return False, None
+
+  last_release_tag = "v{}.{}-dev".format(*version_tuple)
+  success, tag_list = command_output(['git', 'tag', '--contains'], repo_path)
+  if success:
+    if last_release_tag in set(tag_list.decode().splitlines()):
+      return True, last_release_tag
+  else:
+    logging.warning("Could not check tags for commit. Assuming -dev version.")
+
+  now_year = datetime.datetime.now().year
+  if version_tuple[0] == now_year:
+    version_tuple =  (now_year, version_tuple[1] + 1)
+  else:
+    version_tuple = (now_year, 1)
+
+  return True, "v{}.{}-dev".format(*version_tuple)
+
+def get_description_for_head(repo_path):
     """Returns a string describing the current Git HEAD version as descriptively
-    as possible.
-
-    Runs 'git describe', or alternately 'git rev-parse HEAD', in directory.  If
-    successful, returns the output; otherwise returns 'unknown hash, <date>'."""
+    as possible, in order of priority:
+      - git describe output
+      - git rev-parse HEAD output
+      - "unknown-hash, <date>"
+    """
 
     success, output = command_output(['git', 'describe'], repo_path)
     if not success:
-      output = command_output(['git', 'rev-parse', 'HEAD'], repo_path)
+      success, output = command_output(['git', 'rev-parse', 'HEAD'], repo_path)
 
     if success:
       # decode() is needed here for Python3 compatibility. In Python2,
@@ -126,9 +158,12 @@
     # reproducible builds, allow the builder to override the wall
     # clock time with environment variable SOURCE_DATE_EPOCH
     # containing a (presumably) fixed timestamp.
-    timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
-    iso_date = datetime.datetime.utcfromtimestamp(timestamp).isoformat()
-    return "unknown hash, {}".format(iso_date)
+    if 'SOURCE_DATE_EPOCH' in os.environ:
+      timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
+      iso_date = datetime.datetime.utcfromtimestamp(timestamp).isoformat()
+    else:
+      iso_date = datetime.datetime.now().isoformat()
+    return "unknown_hash, {}".format(iso_date)
 
 def main():
     FORMAT = '%(asctime)s %(message)s'
@@ -137,16 +172,15 @@
         logging.error("usage: {} <repo-path> <output-file>".format(sys.argv[0]))
         sys.exit(1)
 
-    changes_file_path = os.path.realpath(sys.argv[1])
+    repo_path = os.path.realpath(sys.argv[1])
     output_file_path = sys.argv[2]
 
-    success, version = deduce_software_version(changes_file_path)
+    success, version = deduce_current_release(repo_path)
     if not success:
-      logging.error("Could not deduce latest release version from {}.".format(changes_file_path))
-      sys.exit(1)
+      logging.warning("Could not deduce latest release version from history.")
+      version = "unknown_version"
 
-    repo_path = os.path.dirname(changes_file_path)
-    description = describe(repo_path)
+    description = get_description_for_head(repo_path)
     content = OUTPUT_FORMAT.format(version_tag=version, description=description)
 
     # Escape file content.