Add support for SPV_KHR_non_semantic_info (#3110)

Add support for SPV_KHR_non_semantic_info

This entails a couple of changes:

- Allowing unknown OpExtInstImport that begin with the prefix `NonSemantic.`
- Allowing OpExtInst that reference any of those sets to contain unknown
  ext inst instruction numbers, and assume the format is always a series of IDs
  as guaranteed by the extension.
- Allowing those OpExtInst to appear in the types/variables/constants section.
- Not stripping OpString in the --strip-debug pass, since it may be referenced
  by these non-semantic OpExtInsts.
- Stripping them instead in the --strip-reflect pass.

* Add adjacency validation of non-semantic OpExtInst

- We validate and test that OpExtInst cannot appear before or between
  OpPhi instructions, or before/between OpFunctionParameter
  instructions.

* Change non-semantic extinst type to single value

* Add helper function spvExtInstIsNonSemantic() which will check if the extinst
  set is non-semantic or not, either the unknown generic value or any future
  recognised non-semantic set.

* Add test of a complex non-semantic extinst

* Use DefUseManager in StripDebugInfoPass to strip some OpStrings

* Any OpString used by a non-semantic instruction cannot be stripped, all others
  can so we search for uses to see if each string can be removed.
* We only do this if the non-semantic debug info extension is enabled, otherwise
  all strings can be trivially removed.

* Silence -Winconsistent-missing-override in protobufs
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 3190f4b..8bde13c 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -108,6 +108,9 @@
   set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
   set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
   if (IS_DIRECTORY ${PROTOBUF_DIR})
+    if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
+      add_definitions(-Wno-inconsistent-missing-override)
+    endif()
     add_subdirectory(${PROTOBUF_DIR} EXCLUDE_FROM_ALL)
   else()
     message(FATAL_ERROR
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 2f4c7d6..723de05 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -249,6 +249,11 @@
   SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT,
   SPV_EXT_INST_TYPE_DEBUGINFO,
 
+  // Multiple distinct extended instruction set types could return this
+  // value, if they are prefixed with NonSemantic. and are otherwise
+  // unrecognised
+  SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN,
+
   SPV_FORCE_32_BIT_ENUM(spv_ext_inst_type_t)
 } spv_ext_inst_type_t;
 
diff --git a/source/binary.cpp b/source/binary.cpp
index 1d31283..8229e53 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -477,9 +477,22 @@
       assert(SpvOpExtInst == opcode);
       assert(inst->ext_inst_type != SPV_EXT_INST_TYPE_NONE);
       spv_ext_inst_desc ext_inst;
-      if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst))
-        return diagnostic() << "Invalid extended instruction number: " << word;
-      spvPushOperandTypes(ext_inst->operandTypes, expected_operands);
+      if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst) ==
+          SPV_SUCCESS) {
+        // if we know about this ext inst, push the expected operands
+        spvPushOperandTypes(ext_inst->operandTypes, expected_operands);
+      } else {
+        // if we don't know this extended instruction and the set isn't
+        // non-semantic, we cannot process further
+        if (!spvExtInstIsNonSemantic(inst->ext_inst_type)) {
+          return diagnostic()
+                 << "Invalid extended instruction number: " << word;
+        } else {
+          // for non-semantic instruction sets, we know the form of all such
+          // extended instructions contains a series of IDs as parameters
+          expected_operands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID);
+        }
+      }
     } break;
 
     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
diff --git a/source/disassemble.cpp b/source/disassemble.cpp
index c116f50..2ba0d3d 100644
--- a/source/disassemble.cpp
+++ b/source/disassemble.cpp
@@ -217,10 +217,18 @@
       break;
     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
       spv_ext_inst_desc ext_inst;
-      if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst))
-        assert(false && "should have caught this earlier");
       SetRed();
-      stream_ << ext_inst->name;
+      if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
+          SPV_SUCCESS) {
+        stream_ << ext_inst->name;
+      } else {
+        if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
+          assert(false && "should have caught this earlier");
+        } else {
+          // for non-semantic instruction sets we can just print the number
+          stream_ << word;
+        }
+      }
     } break;
     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
       spv_opcode_desc opcode_desc;
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 0499e23..6ac5756 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -116,9 +116,21 @@
   if (!strcmp("DebugInfo", name)) {
     return SPV_EXT_INST_TYPE_DEBUGINFO;
   }
+  // ensure to add any known non-semantic extended instruction sets
+  // above this point, and update spvExtInstIsNonSemantic()
+  if (!strncmp("NonSemantic.", name, 12)) {
+    return SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN;
+  }
   return SPV_EXT_INST_TYPE_NONE;
 }
 
+bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) {
+  if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN) {
+    return true;
+  }
+  return false;
+}
+
 spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table,
                                        const spv_ext_inst_type_t type,
                                        const char* name,
diff --git a/source/ext_inst.h b/source/ext_inst.h
index a821cc2..b42d82b 100644
--- a/source/ext_inst.h
+++ b/source/ext_inst.h
@@ -21,6 +21,9 @@
 // Gets the type of the extended instruction set with the specified name.
 spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name);
 
+// Returns true if the extended instruction set is non-semantic
+bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type);
+
 // Finds the named extented instruction of the given type in the given extended
 // instruction table. On success, returns SPV_SUCCESS and writes a handle of
 // the instruction entry into *entry.
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index c68b3e2..e21e680 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "source/ext_inst.h"
 #include "source/opt/log.h"
 #include "source/opt/reflect.h"
 #include "source/util/make_unique.h"
@@ -113,11 +114,14 @@
       } else if (IsTypeInst(opcode)) {
         module_->AddType(std::move(spv_inst));
       } else if (IsConstantInst(opcode) || opcode == SpvOpVariable ||
-                 opcode == SpvOpUndef) {
+                 opcode == SpvOpUndef ||
+                 (opcode == SpvOpExtInst &&
+                  spvExtInstIsNonSemantic(inst->ext_inst_type))) {
         module_->AddGlobalValue(std::move(spv_inst));
       } else {
         Errorf(consumer_, src, loc,
-               "Unhandled inst type (opcode: %d) found outside function definition.",
+               "Unhandled inst type (opcode: %d) found outside function "
+               "definition.",
                opcode);
         return false;
       }
diff --git a/source/opt/strip_debug_info_pass.cpp b/source/opt/strip_debug_info_pass.cpp
index 9e7fad0..936c966 100644
--- a/source/opt/strip_debug_info_pass.cpp
+++ b/source/opt/strip_debug_info_pass.cpp
@@ -19,12 +19,60 @@
 namespace opt {
 
 Pass::Status StripDebugInfoPass::Process() {
-  bool modified = !context()->debugs1().empty() ||
-                  !context()->debugs2().empty() ||
-                  !context()->debugs3().empty();
+  bool uses_non_semantic_info = false;
+  for (auto& inst : context()->module()->extensions()) {
+    const char* ext_name =
+        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
+    if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+      uses_non_semantic_info = true;
+    }
+  }
 
   std::vector<Instruction*> to_kill;
-  for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg);
+
+  // if we use non-semantic info, it may reference OpString. Do a more
+  // expensive pass checking the uses of the OpString to see if any are
+  // OpExtInst on a non-semantic instruction set. If we're not using the
+  // extension then we can do a simpler pass and kill all debug1 instructions
+  if (uses_non_semantic_info) {
+    for (auto& inst : context()->module()->debugs1()) {
+      switch (inst.opcode()) {
+        case SpvOpString: {
+          analysis::DefUseManager* def_use = context()->get_def_use_mgr();
+
+          // see if this string is used anywhere by a non-semantic instruction
+          bool no_nonsemantic_use =
+              def_use->WhileEachUser(&inst, [def_use](Instruction* use) {
+                if (use->opcode() == SpvOpExtInst) {
+                  auto ext_inst_set =
+                      def_use->GetDef(use->GetSingleWordInOperand(0u));
+                  const char* extension_name = reinterpret_cast<const char*>(
+                      &ext_inst_set->GetInOperand(0).words[0]);
+                  if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) {
+                    // found a non-semantic use, return false as we cannot
+                    // remove this OpString
+                    return false;
+                  }
+                }
+
+                // other instructions can't be a non-semantic use
+                return true;
+              });
+
+          if (no_nonsemantic_use) to_kill.push_back(&inst);
+
+          break;
+        }
+
+        default:
+          to_kill.push_back(&inst);
+          break;
+      }
+    }
+  } else {
+    for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg);
+  }
+
   for (auto& dbg : context()->debugs2()) to_kill.push_back(&dbg);
   for (auto& dbg : context()->debugs3()) to_kill.push_back(&dbg);
 
@@ -38,8 +86,11 @@
               return false;
             });
 
+  bool modified = !to_kill.empty();
+
   for (auto* inst : to_kill) context()->KillInst(inst);
 
+  // clear OpLine information
   context()->module()->ForEachInst([&modified](Instruction* inst) {
     modified |= !inst->dbg_line_insts().empty();
     inst->dbg_line_insts().clear();
diff --git a/source/opt/strip_reflect_info_pass.cpp b/source/opt/strip_reflect_info_pass.cpp
index 984073f..c231ead 100644
--- a/source/opt/strip_reflect_info_pass.cpp
+++ b/source/opt/strip_reflect_info_pass.cpp
@@ -67,9 +67,54 @@
     } else if (!other_uses_for_decorate_string &&
                0 == std::strcmp(ext_name, "SPV_GOOGLE_decorate_string")) {
       to_remove.push_back(&inst);
+    } else if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+      to_remove.push_back(&inst);
     }
   }
 
+  // clear all debug data now if it hasn't been cleared already, to remove any
+  // remaining OpString that may have been referenced by non-semantic extinsts
+  for (auto& dbg : context()->debugs1()) to_remove.push_back(&dbg);
+  for (auto& dbg : context()->debugs2()) to_remove.push_back(&dbg);
+  for (auto& dbg : context()->debugs3()) to_remove.push_back(&dbg);
+
+  // remove any extended inst imports that are non semantic
+  std::unordered_set<uint32_t> non_semantic_sets;
+  for (auto& inst : context()->module()->ext_inst_imports()) {
+    assert(inst.opcode() == SpvOpExtInstImport &&
+           "Expecting an import of an extension's instruction set.");
+    const char* extension_name =
+        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
+    if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) {
+      non_semantic_sets.insert(inst.result_id());
+      to_remove.push_back(&inst);
+    }
+  }
+
+  // if we removed some non-semantic sets, then iterate over the instructions in
+  // the module to remove any OpExtInst that referenced those sets
+  if (!non_semantic_sets.empty()) {
+    context()->module()->ForEachInst(
+        [&non_semantic_sets, &to_remove](Instruction* inst) {
+          if (inst->opcode() == SpvOpExtInst) {
+            if (non_semantic_sets.find(inst->GetSingleWordInOperand(0)) !=
+                non_semantic_sets.end()) {
+              to_remove.push_back(inst);
+            }
+          }
+        });
+  }
+
+  // OpName must come first, since they may refer to other debug instructions.
+  // If they are after the instructions that refer to, then they will be killed
+  // when that instruction is killed, which will lead to a double kill.
+  std::sort(to_remove.begin(), to_remove.end(),
+            [](Instruction* lhs, Instruction* rhs) -> bool {
+              if (lhs->opcode() == SpvOpName && rhs->opcode() != SpvOpName)
+                return true;
+              return false;
+            });
+
   for (auto* inst : to_remove) {
     modified = true;
     context()->KillInst(inst);
diff --git a/source/text.cpp b/source/text.cpp
index d88d4f7..fb475d8 100644
--- a/source/text.cpp
+++ b/source/text.cpp
@@ -242,14 +242,37 @@
       // The assembler accepts the symbolic name for an extended instruction,
       // and emits its corresponding number.
       spv_ext_inst_desc extInst;
-      if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst)) {
-        return context->diagnostic()
-               << "Invalid extended instruction name '" << textValue << "'.";
-      }
-      spvInstructionAddWord(pInst, extInst->ext_inst);
+      if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst) ==
+          SPV_SUCCESS) {
+        // if we know about this extended instruction, push the numeric value
+        spvInstructionAddWord(pInst, extInst->ext_inst);
 
-      // Prepare to parse the operands for the extended instructions.
-      spvPushOperandTypes(extInst->operandTypes, pExpectedOperands);
+        // Prepare to parse the operands for the extended instructions.
+        spvPushOperandTypes(extInst->operandTypes, pExpectedOperands);
+      } else {
+        // if we don't know this extended instruction and the set isn't
+        // non-semantic, we cannot process further
+        if (!spvExtInstIsNonSemantic(pInst->extInstType)) {
+          return context->diagnostic()
+                 << "Invalid extended instruction name '" << textValue << "'.";
+        } else {
+          // for non-semantic instruction sets, as long as the text name is an
+          // integer value we can encode it since we know the form of all such
+          // extended instructions
+          spv_literal_t extInstValue;
+          if (spvTextToLiteral(textValue, &extInstValue) ||
+              extInstValue.type != SPV_LITERAL_TYPE_UINT_32) {
+            return context->diagnostic()
+                   << "Couldn't translate unknown extended instruction name '"
+                   << textValue << "' to unsigned integer.";
+          }
+
+          spvInstructionAddWord(pInst, extInstValue.value.u32);
+
+          // opcode contains an unknown number of IDs.
+          pExpectedOperands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID);
+        }
+      }
     } break;
 
     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
diff --git a/source/val/instruction.h b/source/val/instruction.h
index 1fa855f..e30bfbc 100644
--- a/source/val/instruction.h
+++ b/source/val/instruction.h
@@ -21,6 +21,7 @@
 #include <utility>
 #include <vector>
 
+#include "source/ext_inst.h"
 #include "source/table.h"
 #include "spirv-tools/libspirv.h"
 
@@ -85,6 +86,11 @@
     return inst_.ext_inst_type;
   }
 
+  bool IsNonSemantic() const {
+    return opcode() == SpvOp::SpvOpExtInst &&
+           spvExtInstIsNonSemantic(inst_.ext_inst_type);
+  }
+
   // Casts the words belonging to the operand under |index| to |T| and returns.
   template <typename T>
   T GetOperandAs(size_t index) const {
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 2695280..df38f1b 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -286,7 +286,8 @@
     auto use = pair.first;
     if (use->opcode() != SpvOpDecorate && use->opcode() != SpvOpGroupDecorate &&
         use->opcode() != SpvOpGroupMemberDecorate &&
-        use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId) {
+        use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId &&
+        !use->IsNonSemantic()) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "Result id of OpDecorationGroup can only "
              << "be targeted by OpName, OpGroupDecorate, "
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index d513a25..3b44833 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -1275,6 +1275,7 @@
     const auto store = use.first;
     if (store->opcode() == SpvOpFConvert) continue;
     if (spvOpcodeIsDebug(store->opcode())) continue;
+    if (store->IsNonSemantic()) continue;
     if (spvOpcodeIsDecoration(store->opcode())) continue;
     if (store->opcode() != SpvOpStore) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index 1a64605..070cc4c 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -61,8 +61,8 @@
 
 spv_result_t ValidateExtInstImport(ValidationState_t& _,
                                    const Instruction* inst) {
+  const auto name_id = 1;
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    const auto name_id = 1;
     const std::string name(reinterpret_cast<const char*>(
         inst->words().data() + inst->operands()[name_id].offset));
     if (name != "GLSL.std.450") {
@@ -72,6 +72,16 @@
     }
   }
 
+  if (!_.HasExtension(kSPV_KHR_non_semantic_info)) {
+    const std::string name(reinterpret_cast<const char*>(
+        inst->words().data() + inst->operands()[name_id].offset));
+    if (name.find("NonSemantic.") == 0) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "NonSemantic extended instruction sets cannot be declared "
+                "without SPV_KHR_non_semantic_info.";
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_function.cpp b/source/val/validate_function.cpp
index b983194..f995ab3 100644
--- a/source/val/validate_function.cpp
+++ b/source/val/validate_function.cpp
@@ -87,7 +87,8 @@
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
     if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
-        acceptable.end()) {
+            acceptable.end() &&
+        !use->IsNonSemantic()) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
              << "Invalid use of function result id " << _.getIdName(inst->id())
              << ".";
diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp
index d80e1b8..7406330 100644
--- a/source/val/validate_id.cpp
+++ b/source/val/validate_id.cpp
@@ -167,7 +167,8 @@
           const auto opcode = inst->opcode();
           if (spvOpcodeGeneratesType(def->opcode()) &&
               !spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) &&
-              !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction &&
+              !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) &&
+              opcode != SpvOpFunction &&
               opcode != SpvOpCooperativeMatrixLengthNV &&
               !(opcode == SpvOpSpecConstantOp &&
                 inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
@@ -175,7 +176,7 @@
                    << "Operand " << _.getIdName(operand_word)
                    << " cannot be a type";
           } else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) &&
-                     !spvOpcodeIsDebug(opcode) &&
+                     !spvOpcodeIsDebug(opcode) && !inst->IsNonSemantic() &&
                      !spvOpcodeIsDecoration(opcode) &&
                      !spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi &&
                      opcode != SpvOpExtInst && opcode != SpvOpExtInstImport &&
@@ -187,6 +188,11 @@
             return _.diag(SPV_ERROR_INVALID_ID, inst)
                    << "Operand " << _.getIdName(operand_word)
                    << " requires a type";
+          } else if (def->IsNonSemantic() && !inst->IsNonSemantic()) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Operand " << _.getIdName(operand_word)
+                   << " in semantic instruction cannot be a non-semantic "
+                      "instruction";
           } else {
             ret = SPV_SUCCESS;
           }
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index 53c2835..259befe 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -34,6 +34,30 @@
 // checked.
 spv_result_t ModuleScopedInstructions(ValidationState_t& _,
                                       const Instruction* inst, SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpExtInst:
+      if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
+        // non-semantic extinst opcodes are allowed beginning in the types
+        // section, but since they must name a return type they cannot be the
+        // first instruction in the types section. Therefore check that we are
+        // already in it.
+        if (_.current_layout_section() < kLayoutTypes) {
+          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                 << "Non-semantic OpExtInst must not appear before types "
+                 << "section";
+        }
+      } else {
+        // otherwise they must be used in a block
+        if (_.current_layout_section() < kLayoutFunctionDefinitions) {
+          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                 << spvOpcodeString(opcode) << " must appear in a block";
+        }
+      }
+      break;
+    default:
+      break;
+  }
+
   while (_.IsOpcodeInCurrentLayoutSection(opcode) == false) {
     _.ProgressToNextLayoutSectionOrder();
 
@@ -144,6 +168,29 @@
         }
         break;
 
+      case SpvOpExtInst:
+        if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
+          // non-semantic extinst opcodes are allowed beginning in the types
+          // section, but must either be placed outside a function declaration,
+          // or inside a block.
+          if (_.current_layout_section() < kLayoutTypes) {
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << "Non-semantic OpExtInst must not appear before types "
+                   << "section";
+          } else if (_.in_function_body() && _.in_block() == false) {
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << "Non-semantic OpExtInst within function definition must "
+                      "appear in a block";
+          }
+        } else {
+          // otherwise they must be used in a block
+          if (_.in_block() == false) {
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << spvOpcodeString(opcode) << " must appear in a block";
+          }
+        }
+        break;
+
       default:
         if (_.current_layout_section() == kLayoutFunctionDeclarations &&
             _.in_function_body()) {
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 1f171cf..5924c69 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -536,7 +536,7 @@
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
     if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) &&
-        !spvOpcodeIsDecoration(use->opcode())) {
+        !use->IsNonSemantic() && !spvOpcodeIsDecoration(use->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
              << "Invalid use of function type result id "
              << _.getIdName(inst->id()) << ".";
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 20eaf88..51aebbe 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -93,6 +93,9 @@
         case SpvOpLine:
         case SpvOpNoLine:
         case SpvOpUndef:
+        // SpvOpExtInst is only allowed here for certain extended instruction
+        // sets. This will be checked separately
+        case SpvOpExtInst:
           out = true;
           break;
         default: break;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 3dca430..e6857e0 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -102,6 +102,7 @@
   enum_set_test.cpp
   ext_inst.debuginfo_test.cpp
   ext_inst.glsl_test.cpp
+  ext_inst.non_semantic_test.cpp
   ext_inst.opencl_test.cpp
   fix_word_test.cpp
   generator_magic_number_test.cpp
diff --git a/test/ext_inst.non_semantic_test.cpp b/test/ext_inst.non_semantic_test.cpp
new file mode 100644
index 0000000..870684e
--- /dev/null
+++ b/test/ext_inst.non_semantic_test.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+//
+// 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.
+
+// Assembler tests for non-semantic extended instructions
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+
+using ::testing::Eq;
+
+namespace spvtools {
+namespace {
+
+using NonSemanticRoundTripTest = RoundTripTest;
+using NonSemanticTextToBinaryTest = spvtest::TextToBinaryTest;
+
+TEST_F(NonSemanticRoundTripTest, NonSemanticInsts) {
+  std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
+%2 = OpTypeVoid
+%3 = OpExtInst %2 %1 132384681 %2
+%4 = OpTypeInt 32 0
+%5 = OpConstant %4 123
+%6 = OpString "Test string"
+%7 = OpExtInst %4 %1 82198732 %5 %6
+%8 = OpExtInstImport "NonSemantic.Testing.AnotherUnknownExtInstSet"
+%9 = OpExtInst %4 %8 613874321 %7 %5 %6
+)";
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_0);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(NonSemanticTextToBinaryTest, InvalidExtInstSetName) {
+  std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic_Testing_ExtInst"
+)";
+
+  EXPECT_THAT(
+      CompileFailure(spirv),
+      Eq("Invalid extended instruction import 'NonSemantic_Testing_ExtInst'"));
+}
+
+TEST_F(NonSemanticTextToBinaryTest, NonSemanticIntParameter) {
+  std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
+%2 = OpTypeVoid
+%3 = OpExtInst %2 %1 1 99999
+)";
+
+  EXPECT_THAT(CompileFailure(spirv), Eq("Expected id to start with %."));
+}
+
+TEST_F(NonSemanticTextToBinaryTest, NonSemanticFloatParameter) {
+  std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
+%2 = OpTypeVoid
+%3 = OpExtInst %2 %1 1 3.141592
+)";
+
+  EXPECT_THAT(CompileFailure(spirv), Eq("Expected id to start with %."));
+}
+
+TEST_F(NonSemanticTextToBinaryTest, NonSemanticStringParameter) {
+  std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
+%2 = OpTypeVoid
+%3 = OpExtInst %2 %1 1 "foobar"
+)";
+
+  EXPECT_THAT(CompileFailure(spirv), Eq("Expected id to start with %."));
+}
+
+}  // namespace
+}  // namespace spvtools
diff --git a/test/opt/strip_debug_info_test.cpp b/test/opt/strip_debug_info_test.cpp
index 2f2ff46..088bba9 100644
--- a/test/opt/strip_debug_info_test.cpp
+++ b/test/opt/strip_debug_info_test.cpp
@@ -152,6 +152,53 @@
                                             /* do_validation */ true);
 }
 
+TEST_F(StripDebugStringTest, OpStringRemovedWithNonSemantic) {
+  std::vector<const char*> input{
+      // clang-format off
+                     "OpCapability Shader",
+                     "OpExtension \"SPV_KHR_non_semantic_info\"",
+                "%1 = OpExtInstImport \"NonSemantic.Testing.Set\"",
+                     "OpMemoryModel Logical GLSL450",
+                     "OpEntryPoint Vertex %2 \"main\"",
+                // this string is not referenced, should be removed fully
+                "%3 = OpString \"minimal.vert\"",
+                     "OpName %3 \"bob\"",
+                // this string is referenced and cannot be removed,
+                // but the name should be
+                "%4 = OpString \"secondary.inc\"",
+                     "OpName %4 \"sue\"",
+             "%void = OpTypeVoid",
+                "%6 = OpTypeFunction %void",
+                "%2 = OpFunction %void None %6",
+                "%7 = OpLabel",
+                "%8 = OpExtInst %void %1 5 %4",
+                     "OpReturn",
+                     "OpFunctionEnd",
+      // clang-format on
+  };
+  std::vector<const char*> output{
+      // clang-format off
+                     "OpCapability Shader",
+                     "OpExtension \"SPV_KHR_non_semantic_info\"",
+                "%1 = OpExtInstImport \"NonSemantic.Testing.Set\"",
+                     "OpMemoryModel Logical GLSL450",
+                     "OpEntryPoint Vertex %2 \"main\"",
+                "%4 = OpString \"secondary.inc\"",
+             "%void = OpTypeVoid",
+                "%6 = OpTypeFunction %void",
+                "%2 = OpFunction %void None %6",
+                "%7 = OpLabel",
+                "%8 = OpExtInst %void %1 5 %4",
+                     "OpReturn",
+                     "OpFunctionEnd",
+      // clang-format on
+  };
+  SinglePassRunAndCheck<StripDebugInfoPass>(JoinAllInsts(input),
+                                            JoinAllInsts(output),
+                                            /* skip_nop = */ false,
+                                            /* do_validation */ true);
+}
+
 using StripDebugInfoTest = PassTest<::testing::TestWithParam<const char*>>;
 
 TEST_P(StripDebugInfoTest, Kind) {
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index d4bfe1d..138e711 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -70,6 +70,7 @@
        val_memory_test.cpp
        val_misc_test.cpp
        val_modes_test.cpp
+       val_non_semantic_test.cpp
        val_non_uniform_test.cpp
        val_opencl_test.cpp
        val_primitives_test.cpp
diff --git a/test/val/val_adjacency_test.cpp b/test/val/val_adjacency_test.cpp
index e61c03d..0b09de0 100644
--- a/test/val/val_adjacency_test.cpp
+++ b/test/val/val_adjacency_test.cpp
@@ -315,6 +315,243 @@
                         "non-OpPhi instructions"));
 }
 
+TEST_F(ValidateAdjacency, NonSemanticBeforeOpPhiBad) {
+  const std::string body = R"(
+OpSelectionMerge %end_label None
+OpBranchConditional %true %true_label %false_label
+%true_label = OpLabel
+OpBranch %end_label
+%false_label = OpLabel
+OpBranch %end_label
+%end_label = OpLabel
+%dummy = OpExtInst %void %extinst 123 %int_1
+%result = OpPhi %bool %true %true_label %false %false_label
+)";
+
+  const std::string extra = R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra));
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpPhi must appear within a non-entry block before all "
+                        "non-OpPhi instructions"));
+}
+
+TEST_F(ValidateAdjacency, NonSemanticBetweenOpPhiBad) {
+  const std::string body = R"(
+OpSelectionMerge %end_label None
+OpBranchConditional %true %true_label %false_label
+%true_label = OpLabel
+OpBranch %end_label
+%false_label = OpLabel
+OpBranch %end_label
+%end_label = OpLabel
+%result1 = OpPhi %bool %true %true_label %false %false_label
+%dummy = OpExtInst %void %extinst 123 %int_1
+%result2 = OpPhi %bool %true %true_label %false %false_label
+)";
+
+  const std::string extra = R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra));
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpPhi must appear within a non-entry block before all "
+                        "non-OpPhi instructions"));
+}
+
+TEST_F(ValidateAdjacency, NonSemanticAfterOpPhiGood) {
+  const std::string body = R"(
+OpSelectionMerge %end_label None
+OpBranchConditional %true %true_label %false_label
+%true_label = OpLabel
+OpBranch %end_label
+%false_label = OpLabel
+OpBranch %end_label
+%end_label = OpLabel
+OpLine %string 0 0
+%result = OpPhi %bool %true %true_label %false %false_label
+%dummy = OpExtInst %void %extinst 123 %int_1
+)";
+
+  const std::string extra = R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra));
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAdjacency, NonSemanticBeforeOpFunctionParameterBad) {
+  const std::string body = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%string = OpString ""
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%zero = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%func = OpTypeFunction %void
+%func_int = OpTypePointer Function %int
+%paramfunc_type = OpTypeFunction %void %int %int
+
+%paramfunc = OpFunction %void None %paramfunc_type
+%dummy = OpExtInst %void %extinst 123 %int_1
+%a = OpFunctionParameter %int
+%b = OpFunctionParameter %int
+%paramfunc_entry = OpLabel
+OpReturn
+OpFunctionEnd
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body);
+  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Non-semantic OpExtInst within function definition "
+                        "must appear in a block"));
+}
+
+TEST_F(ValidateAdjacency, NonSemanticBetweenOpFunctionParameterBad) {
+  const std::string body = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%string = OpString ""
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%zero = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%func = OpTypeFunction %void
+%func_int = OpTypePointer Function %int
+%paramfunc_type = OpTypeFunction %void %int %int
+
+%paramfunc = OpFunction %void None %paramfunc_type
+%a = OpFunctionParameter %int
+%dummy = OpExtInst %void %extinst 123 %int_1
+%b = OpFunctionParameter %int
+%paramfunc_entry = OpLabel
+OpReturn
+OpFunctionEnd
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body);
+  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Non-semantic OpExtInst within function definition "
+                        "must appear in a block"));
+}
+
+TEST_F(ValidateAdjacency, NonSemanticAfterOpFunctionParameterGood) {
+  const std::string body = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%string = OpString ""
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%zero = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%func = OpTypeFunction %void
+%func_int = OpTypePointer Function %int
+%paramfunc_type = OpTypeFunction %void %int %int
+
+%paramfunc = OpFunction %void None %paramfunc_type
+%a = OpFunctionParameter %int
+%b = OpFunctionParameter %int
+%paramfunc_entry = OpLabel
+%dummy = OpExtInst %void %extinst 123 %int_1
+OpReturn
+OpFunctionEnd
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAdjacency, NonSemanticBetweenFunctionsGood) {
+  const std::string body = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%string = OpString ""
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%zero = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%func = OpTypeFunction %void
+%func_int = OpTypePointer Function %int
+%paramfunc_type = OpTypeFunction %void %int %int
+
+%paramfunc = OpFunction %void None %paramfunc_type
+%a = OpFunctionParameter %int
+%b = OpFunctionParameter %int
+%paramfunc_entry = OpLabel
+OpReturn
+OpFunctionEnd
+
+%dummy = OpExtInst %void %extinst 123 %int_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateAdjacency, OpVariableInFunctionGood) {
   const std::string body = R"(
 OpLine %string 1 1
diff --git a/test/val/val_non_semantic_test.cpp b/test/val/val_non_semantic_test.cpp
new file mode 100644
index 0000000..b80bb1a
--- /dev/null
+++ b/test/val/val_non_semantic_test.cpp
@@ -0,0 +1,195 @@
+// 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.
+
+// Validation tests for non-semantic instructions
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+struct TestResult {
+  TestResult(spv_result_t in_validation_result = SPV_SUCCESS,
+             const char* in_error_str = nullptr,
+             const char* in_error_str2 = nullptr)
+      : validation_result(in_validation_result),
+        error_str(in_error_str),
+        error_str2(in_error_str2) {}
+  spv_result_t validation_result;
+  const char* error_str;
+  const char* error_str2;
+};
+
+using ::testing::Combine;
+using ::testing::HasSubstr;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+using ValidateNonSemanticGenerated = spvtest::ValidateBase<
+    std::tuple<bool, bool, const char*, const char*, TestResult>>;
+using ValidateNonSemanticString = spvtest::ValidateBase<bool>;
+
+CodeGenerator GetNonSemanticCodeGenerator(const bool declare_ext,
+                                          const bool declare_extinst,
+                                          const char* const global_extinsts,
+                                          const char* const function_extinsts) {
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
+
+  if (declare_ext) {
+    generator.extensions_ += "OpExtension \"SPV_KHR_non_semantic_info\"\n";
+  }
+  if (declare_extinst) {
+    generator.extensions_ +=
+        "%extinst = OpExtInstImport \"NonSemantic.Testing.Set\"\n";
+  }
+
+  generator.after_types_ = global_extinsts;
+
+  generator.before_types_ = "%decorate_group = OpDecorationGroup";
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = "Vertex";
+
+  entry_point.body = R"(
+)";
+  entry_point.body += function_extinsts;
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  return generator;
+}
+
+TEST_P(ValidateNonSemanticGenerated, InTest) {
+  const bool declare_ext = std::get<0>(GetParam());
+  const bool declare_extinst = std::get<1>(GetParam());
+  const char* const global_extinsts = std::get<2>(GetParam());
+  const char* const function_extinsts = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetNonSemanticCodeGenerator(
+      declare_ext, declare_extinst, global_extinsts, function_extinsts);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(),
+                testing::ContainsRegex(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(),
+                testing::ContainsRegex(test_result.error_str2));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(OnlyOpExtension, ValidateNonSemanticGenerated,
+                         Combine(Values(true), Values(false), Values(""),
+                                 Values(""), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    MissingOpExtension, ValidateNonSemanticGenerated,
+    Combine(Values(false), Values(true), Values(""), Values(""),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "NonSemantic extended instruction sets cannot be declared "
+                "without SPV_KHR_non_semantic_info."))));
+
+INSTANTIATE_TEST_SUITE_P(NoExtInst, ValidateNonSemanticGenerated,
+                         Combine(Values(true), Values(true), Values(""),
+                                 Values(""), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SimpleGlobalExtInst, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true),
+            Values("%result = OpExtInst %void %extinst 123 %i32"), Values(""),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    ComplexGlobalExtInst, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true),
+            Values("%result = OpExtInst %void %extinst  123 %i32 %u32_2 "
+                   "%f32vec4_1234 %u32_0"),
+            Values(""), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SimpleFunctionLevelExtInst, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true), Values(""),
+            Values("%result = OpExtInst %void %extinst 123 %i32"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    FunctionTypeReference, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true),
+            Values("%result = OpExtInst %void %extinst 123 %func"), Values(""),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    EntryPointReference, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true), Values(""),
+            Values("%result = OpExtInst %void %extinst 123 %main"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    DecorationGroupReference, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true), Values(""),
+            Values("%result = OpExtInst %void %extinst 123 %decorate_group"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    UnknownIDReference, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true),
+            Values("%result = OpExtInst %void %extinst 123 %undefined_id"),
+            Values(""),
+            Values(TestResult(SPV_ERROR_INVALID_ID,
+                              "ID .* has not been defined"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    NonSemanticUseInSemantic, ValidateNonSemanticGenerated,
+    Combine(Values(true), Values(true),
+            Values("%result = OpExtInst %f32 %extinst 123 %i32\n"
+                   "%invalid = OpConstantComposite %f32vec2 %f32_0 %result"),
+            Values(""),
+            Values(TestResult(SPV_ERROR_INVALID_ID,
+                              "in semantic instruction cannot be a "
+                              "non-semantic instruction"))));
+
+TEST_F(ValidateNonSemanticString, InvalidSectionOpExtInst) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+%test = OpExtInst %void %extinst 4 %void
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+
+  // there's no specific error for using an OpExtInst too early, it requires a
+  // type so by definition any use of a type in it will be an undefined ID
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ID 2[%2] has not been defined"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 4364e3e..0ff937a 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -467,7 +467,8 @@
   printf(R"(
   --strip-reflect
                Remove all reflection information.  For now, this covers
-               reflection information defined by SPV_GOOGLE_hlsl_functionality1.)");
+               reflection information defined by SPV_GOOGLE_hlsl_functionality1
+               and SPV_KHR_non_semantic_info)");
   printf(R"(
   --target-env=<env>
                Set the target environment. Without this flag the target
diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py
index ed24bd0..f6c671e 100755
--- a/utils/generate_grammar_tables.py
+++ b/utils/generate_grammar_tables.py
@@ -30,6 +30,7 @@
 SPV_AMD_gpu_shader_half_float
 SPV_AMD_gpu_shader_int16
 SPV_AMD_shader_trinary_minmax
+SPV_KHR_non_semantic_info
 """