| // 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; |
| } |
| 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 |