Embedded reflection (#612)

Fixes #592

Changes the descriptor map to be implemented as a non-semantic extended instruction set embedded directly in the SPIR-V.

* remove -descriptormap
* added a new tool clspv-reflection to dump the old style descriptor map
  * legacy tests use this instead of -descriptormap where they checked the descriptor results
* remove DescriptorMap.{cpp,h}
* add Sampler.h as a replacement for the sampler enums in descriptor map
* docs update
* new header  built for clspv reflection extended instruction set
diff --git a/tools/reflection/ReflectionParser.cpp b/tools/reflection/ReflectionParser.cpp
new file mode 100644
index 0000000..07202c8
--- /dev/null
+++ b/tools/reflection/ReflectionParser.cpp
@@ -0,0 +1,345 @@
+// Copyright 2020 The Clspv Authors. All rights reserved.
+//
+// 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 <cassert>
+#include <ostream>
+#include <unordered_map>
+
+#include "spirv-tools/libspirv.hpp"
+#include "spirv/unified1/spirv.hpp"
+
+#include "clspv/ArgKind.h"
+#include "clspv/PushConstant.h"
+#include "clspv/Sampler.h"
+#include "clspv/SpecConstant.h"
+#include "clspv/spirv_reflection.hpp"
+
+#include "ReflectionParser.h"
+
+namespace {
+class ReflectionParser {
+public:
+  ReflectionParser(std::ostream *ostr) : str(ostr) {}
+
+  // Parses |inst| and emits descriptor map entries as necessary.
+  spv_result_t ParseInstruction(const spv_parsed_instruction_t *inst);
+
+private:
+  // Converts the extended instruction to ArgKind.
+  clspv::ArgKind GetArgKindFromExtInst(uint32_t value);
+
+  // Converts the extended instruction to PushConstant.
+  clspv::PushConstant GetPushConstantFromExtInst(uint32_t value);
+
+  // Descriptor map output stream.
+  std::ostream *str;
+
+  // Tracks OpTypeInt 32 0 result id.
+  uint32_t int_id = 0;
+
+  // String mappings. Includes OpString value to result id, Kernel name to
+  // result id and argument name to result id.
+  std::unordered_map<uint32_t, std::string> strings;
+
+  // Maps u32 constant result ids to their values.
+  std::unordered_map<uint32_t, uint32_t> constants;
+};
+
+spv_result_t ParseInstruction(void *user_data,
+                              const spv_parsed_instruction_t *inst) {
+  ReflectionParser *parser = reinterpret_cast<ReflectionParser *>(user_data);
+  return parser->ParseInstruction(inst);
+}
+
+clspv::ArgKind ReflectionParser::GetArgKindFromExtInst(uint32_t value) {
+  clspv::reflection::ExtInst ext_inst =
+      static_cast<clspv::reflection::ExtInst>(value);
+  switch (ext_inst) {
+  case clspv::reflection::ExtInstArgumentStorageBuffer:
+  case clspv::reflection::ExtInstConstantDataStorageBuffer:
+    return clspv::ArgKind::Buffer;
+  case clspv::reflection::ExtInstArgumentUniform:
+  case clspv::reflection::ExtInstConstantDataUniform:
+    return clspv::ArgKind::BufferUBO;
+  case clspv::reflection::ExtInstArgumentPodStorageBuffer:
+    return clspv::ArgKind::Pod;
+  case clspv::reflection::ExtInstArgumentPodUniform:
+    return clspv::ArgKind::PodUBO;
+  case clspv::reflection::ExtInstArgumentPodPushConstant:
+    return clspv::ArgKind::PodPushConstant;
+  case clspv::reflection::ExtInstArgumentSampledImage:
+    return clspv::ArgKind::ReadOnlyImage;
+  case clspv::reflection::ExtInstArgumentStorageImage:
+    return clspv::ArgKind::WriteOnlyImage;
+  case clspv::reflection::ExtInstArgumentSampler:
+    return clspv::ArgKind::Sampler;
+  case clspv::reflection::ExtInstArgumentWorkgroup:
+    return clspv::ArgKind::Local;
+  default:
+    assert(false && "Unexpected extended instruction");
+    return clspv::ArgKind::Buffer;
+  }
+}
+
+clspv::PushConstant
+ReflectionParser::GetPushConstantFromExtInst(uint32_t value) {
+  clspv::reflection::ExtInst ext_inst =
+      static_cast<clspv::reflection::ExtInst>(value);
+  switch (ext_inst) {
+  case clspv::reflection::ExtInstPushConstantGlobalOffset:
+    return clspv::PushConstant::GlobalOffset;
+  case clspv::reflection::ExtInstPushConstantEnqueuedLocalSize:
+    return clspv::PushConstant::EnqueuedLocalSize;
+  case clspv::reflection::ExtInstPushConstantGlobalSize:
+    return clspv::PushConstant::GlobalSize;
+  case clspv::reflection::ExtInstPushConstantRegionOffset:
+    return clspv::PushConstant::RegionOffset;
+  case clspv::reflection::ExtInstPushConstantNumWorkgroups:
+    return clspv::PushConstant::NumWorkgroups;
+  case clspv::reflection::ExtInstPushConstantRegionGroupOffset:
+    return clspv::PushConstant::RegionGroupOffset;
+  default:
+    assert(false && "Unexpected push constant");
+    return clspv::PushConstant::KernelArgument;
+  }
+}
+
+spv_result_t
+ReflectionParser::ParseInstruction(const spv_parsed_instruction_t *inst) {
+  switch (inst->opcode) {
+  case spv::OpTypeInt:
+    if (inst->words[inst->operands[1].offset] == 32 &&
+        inst->words[inst->operands[2].offset] == 0) {
+      int_id = inst->result_id;
+    }
+    break;
+  case spv::OpConstant:
+    if (inst->words[inst->operands[0].offset] == int_id) {
+      uint32_t value = inst->words[inst->operands[2].offset];
+      constants[inst->result_id] = value;
+    }
+    break;
+  case spv::OpString: {
+    std::string value =
+        reinterpret_cast<const char *>(inst->words + inst->operands[1].offset);
+    strings[inst->result_id] = value;
+    break;
+  }
+  case spv::OpExtInst:
+    if (inst->ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) {
+      // Reflection specific instruction.
+      auto ext_inst = inst->words[inst->operands[3].offset];
+      switch (ext_inst) {
+      case clspv::reflection::ExtInstKernel: {
+        // Record the name and emit a kernel_decl entry.
+        const auto &name = strings[inst->words[inst->operands[5].offset]];
+        strings[inst->result_id] = name;
+        *str << "kernel_decl," << name << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstArgumentInfo: {
+        // Record the argument name.
+        const auto &name = strings[inst->words[inst->operands[4].offset]];
+        strings[inst->result_id] = name;
+        break;
+      }
+      case clspv::reflection::ExtInstArgumentStorageBuffer:
+      case clspv::reflection::ExtInstArgumentUniform:
+      case clspv::reflection::ExtInstArgumentSampledImage:
+      case clspv::reflection::ExtInstArgumentStorageImage:
+      case clspv::reflection::ExtInstArgumentSampler: {
+        // Emit an argument entry. Descriptor set and binding, no size.
+        auto kernel_id = inst->words[inst->operands[4].offset];
+        auto ordinal_id = inst->words[inst->operands[5].offset];
+        auto ds_id = inst->words[inst->operands[6].offset];
+        auto binding_id = inst->words[inst->operands[7].offset];
+        std::string arg_name;
+        if (inst->num_operands == 9) {
+          arg_name = strings[inst->words[inst->operands[8].offset]];
+        }
+        auto kind = GetArgKindFromExtInst(ext_inst);
+        *str << "kernel," << strings[kernel_id] << ",arg," << arg_name
+             << ",argOrdinal," << constants[ordinal_id] << ",descriptorSet,"
+             << constants[ds_id] << ",binding," << constants[binding_id]
+             << ",offset,0,argKind," << clspv::GetArgKindName(kind) << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstArgumentPodStorageBuffer:
+      case clspv::reflection::ExtInstArgumentPodUniform: {
+        // Emit an argument entry. Descriptor set, binding and size.
+        auto kernel_id = inst->words[inst->operands[4].offset];
+        auto ordinal_id = inst->words[inst->operands[5].offset];
+        auto ds_id = inst->words[inst->operands[6].offset];
+        auto binding_id = inst->words[inst->operands[7].offset];
+        auto offset_id = inst->words[inst->operands[8].offset];
+        auto size_id = inst->words[inst->operands[9].offset];
+        std::string arg_name;
+        if (inst->num_operands == 11) {
+          arg_name = strings[inst->words[inst->operands[10].offset]];
+        }
+        auto kind = GetArgKindFromExtInst(ext_inst);
+        *str << "kernel," << strings[kernel_id] << ",arg," << arg_name
+             << ",argOrdinal," << constants[ordinal_id] << ",descriptorSet,"
+             << constants[ds_id] << ",binding," << constants[binding_id]
+             << ",offset," << constants[offset_id] << ",argKind,"
+             << clspv::GetArgKindName(kind) << ",argSize," << constants[size_id]
+             << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstArgumentPodPushConstant: {
+        // Emit an argument entry. No descriptor set or binding, but has
+        // size.
+        auto kernel_id = inst->words[inst->operands[4].offset];
+        auto ordinal_id = inst->words[inst->operands[5].offset];
+        auto offset_id = inst->words[inst->operands[6].offset];
+        auto size_id = inst->words[inst->operands[7].offset];
+        std::string arg_name;
+        if (inst->num_operands == 9) {
+          arg_name = strings[inst->words[inst->operands[8].offset]];
+        }
+        auto kind = GetArgKindFromExtInst(ext_inst);
+        *str << "kernel," << strings[kernel_id] << ",arg," << arg_name
+             << ",argOrdinal," << constants[ordinal_id] << ",offset,"
+             << constants[offset_id] << ",argKind,"
+             << clspv::GetArgKindName(kind) << ",argSize," << constants[size_id]
+             << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstArgumentWorkgroup: {
+        // Emit an argument entry. No descriptor set or binding, but has
+        // spec id and size.
+        auto kernel_id = inst->words[inst->operands[4].offset];
+        auto ordinal_id = inst->words[inst->operands[5].offset];
+        auto spec_id = inst->words[inst->operands[6].offset];
+        auto size_id = inst->words[inst->operands[7].offset];
+        std::string arg_name;
+        if (inst->num_operands == 9) {
+          arg_name = strings[inst->words[inst->operands[8].offset]];
+        }
+        auto kind = GetArgKindFromExtInst(ext_inst);
+        *str << "kernel," << strings[kernel_id] << ",arg," << arg_name
+             << ",argOrdinal," << constants[ordinal_id] << ",argKind,"
+             << clspv::GetArgKindName(kind) << ",arrayElemSize,"
+             << constants[size_id] << ",arrayNumElemSpecId,"
+             << constants[spec_id] << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstConstantDataStorageBuffer:
+      case clspv::reflection::ExtInstConstantDataUniform: {
+        // Emit constant data entry.
+        auto ds_id = inst->words[inst->operands[4].offset];
+        auto binding_id = inst->words[inst->operands[5].offset];
+        auto data_id = inst->words[inst->operands[6].offset];
+        auto kind = GetArgKindFromExtInst(ext_inst);
+        *str << "constant,descriptorSet," << constants[ds_id] << ",binding,"
+             << constants[binding_id] << ",kind," << clspv::GetArgKindName(kind)
+             << ",hexbytes," << strings[data_id] << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstSpecConstantWorkgroupSize: {
+        // WorkgroupSize is emitted as three separate entries.
+        auto x_id = inst->words[inst->operands[4].offset];
+        auto y_id = inst->words[inst->operands[5].offset];
+        auto z_id = inst->words[inst->operands[6].offset];
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kWorkgroupSizeX)
+             << ",spec_id," << constants[x_id] << "\n";
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kWorkgroupSizeY)
+             << ",spec_id," << constants[y_id] << "\n";
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kWorkgroupSizeZ)
+             << ",spec_id," << constants[z_id] << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstSpecConstantGlobalOffset: {
+        // GlobalOffset is emitted as three separate entries.
+        auto x_id = inst->words[inst->operands[4].offset];
+        auto y_id = inst->words[inst->operands[5].offset];
+        auto z_id = inst->words[inst->operands[6].offset];
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kGlobalOffsetX)
+             << ",spec_id," << constants[x_id] << "\n";
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kGlobalOffsetY)
+             << ",spec_id," << constants[y_id] << "\n";
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kGlobalOffsetZ)
+             << ",spec_id," << constants[z_id] << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstSpecConstantWorkDim: {
+        auto dim_id = inst->words[inst->operands[4].offset];
+        *str << "spec_constant,"
+             << clspv::GetSpecConstantName(clspv::SpecConstant::kWorkDim)
+             << ",spec_id," << constants[dim_id] << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstPushConstantGlobalOffset:
+      case clspv::reflection::ExtInstPushConstantEnqueuedLocalSize:
+      case clspv::reflection::ExtInstPushConstantGlobalSize:
+      case clspv::reflection::ExtInstPushConstantRegionOffset:
+      case clspv::reflection::ExtInstPushConstantNumWorkgroups:
+      case clspv::reflection::ExtInstPushConstantRegionGroupOffset: {
+        auto offset_id = inst->words[inst->operands[4].offset];
+        auto size_id = inst->words[inst->operands[5].offset];
+        auto kind = GetPushConstantFromExtInst(ext_inst);
+        *str << "pushconstant,name," << clspv::GetPushConstantName(kind)
+             << ",offset," << constants[offset_id] << ",size,"
+             << constants[size_id] << "\n";
+        break;
+      }
+      case clspv::reflection::ExtInstLiteralSampler: {
+        auto ds_id = inst->words[inst->operands[4].offset];
+        auto binding_id = inst->words[inst->operands[5].offset];
+        auto mask_id = inst->words[inst->operands[6].offset];
+        auto mask = constants[mask_id];
+        *str << "sampler," << mask << ",samplerExpr,\""
+             << clspv::GetSamplerCoordsName(mask) << "|"
+             << clspv::GetSamplerAddressingModeName(mask) << "|"
+             << clspv::GetSamplerFilteringModeName(mask) << "\",descriptorSet,"
+             << constants[ds_id] << ",binding," << constants[binding_id]
+             << "\n";
+        break;
+      }
+      default:
+        break;
+      }
+      break;
+    }
+  default:
+    break;
+  }
+
+  return SPV_SUCCESS;
+}
+} // namespace
+
+namespace clspv {
+
+bool ParseReflection(const std::vector<uint32_t> &binary, spv_target_env env,
+                     std::ostream *str) {
+  ReflectionParser parser(str);
+  auto MessageConsumer = [](spv_message_level_t, const char *,
+                            const spv_position_t, const char *) {};
+  spvtools::Context context(env);
+  context.SetMessageConsumer(MessageConsumer);
+
+  spv_result_t result =
+      spvBinaryParse(context.CContext(), &parser, binary.data(), binary.size(),
+                     nullptr, ParseInstruction, nullptr);
+
+  return result == SPV_SUCCESS;
+}
+} // namespace clspv