Disassembler support for OpSpecConstantOp

Document the fact that we use names for extended instructions
and OpSpecConstantOp opcode operands.
diff --git a/readme.md b/readme.md
index ba7cc54..9d9018a 100644
--- a/readme.md
+++ b/readme.md
@@ -25,6 +25,9 @@
 
 ## CHANGES (for tools hackers)
 
+* Support `OpSpecConstantOp`.
+  The opcode operand uses the opcode name, but without the `Op` prefix.
+
 2015-11-10
 * Refactored the SPIR-V binary parser:
   * The binary parser issues callbacks to a parser client.
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index 5e18f94..a437fb3 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -241,6 +241,17 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t AssemblyGrammar::lookupSpecConstantOpcode(SpvOp opcode) const {
+  const auto* last = kOpSpecConstantOpcodes + kNumOpSpecConstantOpcodes;
+  const auto* found =
+      std::find_if(kOpSpecConstantOpcodes, last,
+                   [opcode](const SpecConstantOpcodeEntry& entry) {
+                     return opcode == entry.opcode;
+                   });
+  if (found == last) return SPV_ERROR_INVALID_LOOKUP;
+  return SPV_SUCCESS;
+}
+
 spv_result_t AssemblyGrammar::parseMaskOperand(const spv_operand_type_t type,
                                                const char* textValue,
                                                uint32_t* pValue) const {
diff --git a/source/assembly_grammar.h b/source/assembly_grammar.h
index 636e952..197d8f7 100644
--- a/source/assembly_grammar.h
+++ b/source/assembly_grammar.h
@@ -78,6 +78,10 @@
   // parameter.  On failure, returns SPV_ERROR_INVALID_LOOKUP.
   spv_result_t lookupSpecConstantOpcode(const char* name, SpvOp* opcode) const;
 
+  // Returns SPV_SUCCESS if the given opcode is valid as the opcode operand
+  // to OpSpecConstantOp.
+  spv_result_t lookupSpecConstantOpcode(SpvOp opcode) const;
+
   // Parses a mask expression string for the given operand type.
   //
   // A mask expression is a sequence of one or more terms separated by '|',
diff --git a/source/binary.cpp b/source/binary.cpp
index 94e51a2..699f6b0 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -439,6 +439,26 @@
       spvPrependOperandTypes(ext_inst->operandTypes, expected_operands);
     } break;
 
+    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
+      assert(SpvOpSpecConstantOp == inst->opcode);
+      if (grammar_.lookupSpecConstantOpcode(SpvOp(word))) {
+        return diagnostic() << "Invalid " << spvOperandTypeStr(type) << ": "
+                            << word;
+      }
+      spv_opcode_desc opcode_entry = nullptr;
+      if (grammar_.lookupOpcode(SpvOp(word), &opcode_entry)) {
+        return diagnostic(SPV_ERROR_INTERNAL)
+               << "OpSpecConstant opcode table out of sync";
+      }
+      // OpSpecConstant opcodes must have a type and result. We've already
+      // processed them, so skip them when preparing to parse the other
+      // operants for the opcode.
+      assert(opcode_entry->hasType);
+      assert(opcode_entry->hasResult);
+      assert(opcode_entry->numTypes >= 2);
+      spvPrependOperandTypes(opcode_entry->operandTypes + 2, expected_operands);
+    } break;
+
     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
       // These are regular single-word literal integer operands.
diff --git a/source/disassemble.cpp b/source/disassemble.cpp
index a439b01..4ee6f35 100644
--- a/source/disassemble.cpp
+++ b/source/disassemble.cpp
@@ -183,6 +183,13 @@
       SetRed();
       stream_ << ext_inst->name;
     } break;
+    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
+      spv_opcode_desc opcode_desc;
+      if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
+        assert(false && "should have caught this earlier");
+      SetRed();
+      stream_ << opcode_desc->name;
+    } break;
     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
     case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
       SetRed();
diff --git a/syntax.md b/syntax.md
index 5cf3ef4..be3a5d5 100644
--- a/syntax.md
+++ b/syntax.md
@@ -55,6 +55,12 @@
   which is the combination of the `NotNaN`, `NotInf`, and `NSZ` flags.
 * an injected immediate integer: `!<integer>`.  See [below](#immediate).
 * an ID, e.g. `%foo`. See [below](#id).
+* the name of an extended instruction.  For example, `sqrt` in an extended
+  instruction such as `%f = OpExtInst %f32 %OpenCLImport sqrt %arg`
+* the name of an opcode for OpSpecConstantOp, but where the `Op` prefix
+  is removed.  For example, the following indicates the use of an integer
+  addition in a specialization constant computation:
+  `%sum = OpSpecConstantOp %i32 IAdd %a %b`
 
 ## ID Definitions & Usage
 <a name="id"></a>
diff --git a/test/TextToBinary.Constant.cpp b/test/TextToBinary.Constant.cpp
index ba76832..c98c7ef 100644
--- a/test/TextToBinary.Constant.cpp
+++ b/test/TextToBinary.Constant.cpp
@@ -487,6 +487,9 @@
               Eq(MakeInstruction(SpvOpSpecConstantOp,
                                  {1, 2, uint32_t(GetParam().value())},
                                  GetParam().operands())));
+
+  // Check the disassembler as well.
+  EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str());
 }
 
 // clang-format off
@@ -594,6 +597,9 @@
               Eq(MakeInstruction(SpvOpSpecConstantOp,
                                  {1, 2, uint32_t(GetParam().value()), 3, 4},
                                  GetParam().operands())));
+
+  // Check the disassembler as well.
+  EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str());
 }
 
 #define CASE(NAME) SpvOp##NAME, #NAME
@@ -629,6 +635,9 @@
               Eq(MakeInstruction(SpvOpSpecConstantOp,
                                  {1, 2, uint32_t(GetParam().value()), 3},
                                  GetParam().operands())));
+
+  // Check the disassembler as well.
+  EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str());
 }
 
 #define CASE(NAME) SpvOp##NAME, #NAME