Implement using spvc in ExtractSpirvInfo

BUG=dawn:291

Change-Id: I6c12c1874afb9b2c60e326f5c80230fba5fa748c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15000
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index dfa9acf..1cb3f21 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -66,6 +66,75 @@
             }
         }
 
+        wgpu::TextureViewDimension ToWGPUTextureViewDimension(
+            shaderc_spvc_texture_view_dimension dim) {
+            switch (dim) {
+                case shaderc_spvc_texture_view_dimension_undefined:
+                    return wgpu::TextureViewDimension::Undefined;
+                case shaderc_spvc_texture_view_dimension_e1D:
+                    return wgpu::TextureViewDimension::e1D;
+                case shaderc_spvc_texture_view_dimension_e2D:
+                    return wgpu::TextureViewDimension::e2D;
+                case shaderc_spvc_texture_view_dimension_e2D_array:
+                    return wgpu::TextureViewDimension::e2DArray;
+                case shaderc_spvc_texture_view_dimension_cube:
+                    return wgpu::TextureViewDimension::Cube;
+                case shaderc_spvc_texture_view_dimension_cube_array:
+                    return wgpu::TextureViewDimension::CubeArray;
+                case shaderc_spvc_texture_view_dimension_e3D:
+                    return wgpu::TextureViewDimension::e3D;
+            }
+            UNREACHABLE();
+        }
+
+        Format::Type ToDawnFormatType(shaderc_spvc_texture_format_type type) {
+            switch (type) {
+                case shaderc_spvc_texture_format_type_float:
+                    return Format::Type::Float;
+                case shaderc_spvc_texture_format_type_sint:
+                    return Format::Type::Sint;
+                case shaderc_spvc_texture_format_type_uint:
+                    return Format::Type::Uint;
+                case shaderc_spvc_texture_format_type_other:
+                    return Format::Type::Other;
+            }
+            UNREACHABLE();
+        }
+
+        wgpu::BindingType ToWGPUBindingType(shaderc_spvc_binding_type type) {
+            switch (type) {
+                case shaderc_spvc_binding_type_uniform_buffer:
+                    return wgpu::BindingType::UniformBuffer;
+                case shaderc_spvc_binding_type_storage_buffer:
+                    return wgpu::BindingType::StorageBuffer;
+                case shaderc_spvc_binding_type_readonly_storage_buffer:
+                    return wgpu::BindingType::ReadonlyStorageBuffer;
+                case shaderc_spvc_binding_type_sampler:
+                    return wgpu::BindingType::Sampler;
+                case shaderc_spvc_binding_type_sampled_texture:
+                    return wgpu::BindingType::SampledTexture;
+                case shaderc_spvc_binding_type_storage_texture:
+                    return wgpu::BindingType::StorageTexture;
+            }
+            UNREACHABLE();
+        }
+
+        // TODO(rharrison): Convert this to ResultOrError once ExtractSPIRVInfo turns an error
+        SingleShaderStage ToSingleShaderStage(shaderc_spvc_execution_model execution_model) {
+            switch (execution_model) {
+                case shaderc_spvc_execution_model_vertex:
+                    return SingleShaderStage::Vertex;
+                case shaderc_spvc_execution_model_fragment:
+                    return SingleShaderStage::Fragment;
+                case shaderc_spvc_execution_model_glcompute:
+                    return SingleShaderStage::Compute;
+                default:
+                    // This will be converted to an error return when the whole
+                    // calling stack is changed to passing errors.
+                    UNREACHABLE();
+                    return SingleShaderStage::Vertex;
+            }
+        }
     }  // anonymous namespace
 
     MaybeError ValidateShaderModuleDescriptor(DeviceBase*,
@@ -131,8 +200,159 @@
 
     void ShaderModuleBase::ExtractSpirvInfo(const spirv_cross::Compiler& compiler) {
         ASSERT(!IsError());
+        if (GetDevice()->IsToggleEnabled(Toggle::UseSpvc)) {
+            ExtractSpirvInfoWithSpvc(compiler);
+        } else {
+            ExtractSpirvInfoWithSpirvCross(compiler);
+        }
+    }
 
-        DeviceBase* device = GetDevice();
+    void ShaderModuleBase::ExtractSpirvInfoWithSpvc(const spirv_cross::Compiler& compiler) {
+        shaderc_spvc_execution_model execution_model;
+        if (!CheckSpvcSuccess(mSpvcContext.GetExecutionModel(&execution_model),
+                              "Unable to get execution model for shader.")) {
+            return;
+        }
+
+        mExecutionModel = ToSingleShaderStage(execution_model);
+
+        size_t push_constant_buffers_count;
+        if (!CheckSpvcSuccess(mSpvcContext.GetPushConstantBufferCount(&push_constant_buffers_count),
+                              "Unable to get push constant buffer count for shader.")) {
+            return;
+        }
+
+        // TODO(rharrison): This should be handled by spirv-val pass in spvc,
+        // but need to confirm.
+        if (push_constant_buffers_count > 0) {
+            GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                     "Push constants aren't supported.");
+            return;
+        }
+
+        // Fill in bindingInfo with the SPIRV bindings
+        auto ExtractResourcesBinding = [this](std::vector<shaderc_spvc_binding_info> bindings) {
+            for (const auto& binding : bindings) {
+                if (binding.binding >= kMaxBindingsPerGroup || binding.set >= kMaxBindGroups) {
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Binding over limits in the SPIRV");
+                    continue;
+                }
+
+                BindingInfo* info = &mBindingInfo[binding.set][binding.binding];
+                *info = {};
+                info->used = true;
+                info->id = binding.id;
+                info->base_type_id = binding.base_type_id;
+                if (binding.binding_type == shaderc_spvc_binding_type_sampled_texture) {
+                    info->multisampled = binding.multisampled;
+                    info->textureDimension = ToWGPUTextureViewDimension(binding.texture_dimension);
+                    info->textureComponentType = ToDawnFormatType(binding.texture_component_type);
+                }
+                info->type = ToWGPUBindingType(binding.binding_type);
+            }
+        };
+
+        std::vector<shaderc_spvc_binding_info> resource_bindings;
+        if (!CheckSpvcSuccess(mSpvcContext.GetBindingInfo(
+                                  shaderc_spvc_shader_resource_uniform_buffers,
+                                  shaderc_spvc_binding_type_uniform_buffer, &resource_bindings),
+                              "Unable to get binding info for uniform buffers from shader")) {
+            return;
+        }
+        ExtractResourcesBinding(resource_bindings);
+
+        if (!CheckSpvcSuccess(mSpvcContext.GetBindingInfo(
+                                  shaderc_spvc_shader_resource_separate_images,
+                                  shaderc_spvc_binding_type_sampled_texture, &resource_bindings),
+                              "Unable to get binding info for sampled textures from shader")) {
+            return;
+        }
+        ExtractResourcesBinding(resource_bindings);
+
+        if (!CheckSpvcSuccess(
+                mSpvcContext.GetBindingInfo(shaderc_spvc_shader_resource_separate_samplers,
+                                            shaderc_spvc_binding_type_sampler, &resource_bindings),
+                "Unable to get binding info for samples from shader")) {
+            return;
+        }
+        ExtractResourcesBinding(resource_bindings);
+
+        if (!CheckSpvcSuccess(mSpvcContext.GetBindingInfo(
+                                  shaderc_spvc_shader_resource_storage_buffers,
+                                  shaderc_spvc_binding_type_storage_buffer, &resource_bindings),
+                              "Unable to get binding info for storage buffers from shader")) {
+            return;
+        }
+        ExtractResourcesBinding(resource_bindings);
+
+        std::vector<shaderc_spvc_resource_location_info> input_stage_locations;
+        if (!CheckSpvcSuccess(mSpvcContext.GetInputStageLocationInfo(&input_stage_locations),
+                              "Unable to get input stage location information from shader")) {
+            input_stage_locations.clear();
+            return;
+        }
+
+        for (const auto& input : input_stage_locations) {
+            if (mExecutionModel == SingleShaderStage::Vertex) {
+                if (input.location >= kMaxVertexAttributes) {
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Attribute location over limits in the SPIRV");
+                    return;
+                }
+                mUsedVertexAttributes.set(input.location);
+            } else if (mExecutionModel == SingleShaderStage::Fragment) {
+                // Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives
+                // them all the location 0, causing a compile error.
+                if (!input.has_location) {
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Need location qualifier on fragment input");
+                    return;
+                }
+            }
+        }
+
+        std::vector<shaderc_spvc_resource_location_info> output_stage_locations;
+        if (!CheckSpvcSuccess(mSpvcContext.GetOutputStageLocationInfo(&output_stage_locations),
+                              "Unable to get output stage location information from shader")) {
+            output_stage_locations.clear();
+            return;
+        }
+
+        for (const auto& output : output_stage_locations) {
+            if (mExecutionModel == SingleShaderStage::Vertex) {
+                // Without a location qualifier on vertex outputs, spirv_cross::CompilerMSL
+                // gives them all the location 0, causing a compile error.
+                if (!output.has_location) {
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Need location qualifier on vertex output");
+                    return;
+                }
+            } else if (mExecutionModel == SingleShaderStage::Fragment) {
+                if (output.location >= kMaxColorAttachments) {
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Fragment output location over limits in the SPIRV");
+                    return;
+                }
+            }
+        }
+
+        if (mExecutionModel == SingleShaderStage::Fragment) {
+            std::vector<shaderc_spvc_resource_type_info> output_types;
+            if (!CheckSpvcSuccess(mSpvcContext.GetOutputStageTypeInfo(&output_types),
+                                  "Unable to get output stage type information from shader")) {
+                output_stage_locations.clear();
+                return;
+            }
+
+            for (const auto& output : output_types) {
+                ASSERT(output.type != shaderc_spvc_texture_format_type_other);
+                mFragmentOutputFormatBaseTypes[output.location] = ToDawnFormatType(output.type);
+            }
+        }
+    }
+
+    void ShaderModuleBase::ExtractSpirvInfoWithSpirvCross(const spirv_cross::Compiler& compiler) {
         // TODO(cwallez@chromium.org): make errors here creation errors
         // currently errors here do not prevent the shadermodule from being used
         const auto& resources = compiler.get_shader_resources();
@@ -157,58 +377,59 @@
         }
 
         // Fill in bindingInfo with the SPIRV bindings
-        auto ExtractResourcesBinding = [this](const spirv_cross::SmallVector<spirv_cross::Resource>&
-                                                  resources,
-                                              const spirv_cross::Compiler& compiler,
-                                              wgpu::BindingType bindingType) {
-            for (const auto& resource : resources) {
-                ASSERT(compiler.get_decoration_bitset(resource.id).get(spv::DecorationBinding));
-                ASSERT(
-                    compiler.get_decoration_bitset(resource.id).get(spv::DecorationDescriptorSet));
+        auto ExtractResourcesBinding =
+            [this](const spirv_cross::SmallVector<spirv_cross::Resource>& resources,
+                   const spirv_cross::Compiler& compiler, wgpu::BindingType bindingType) {
+                for (const auto& resource : resources) {
+                    ASSERT(compiler.get_decoration_bitset(resource.id).get(spv::DecorationBinding));
+                    ASSERT(compiler.get_decoration_bitset(resource.id)
+                               .get(spv::DecorationDescriptorSet));
 
-                uint32_t binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
-                uint32_t set = compiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
+                    uint32_t binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
+                    uint32_t set =
+                        compiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
 
-                if (binding >= kMaxBindingsPerGroup || set >= kMaxBindGroups) {
-                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
-                                             "Binding over limits in the SPIRV");
-                    continue;
+                    if (binding >= kMaxBindingsPerGroup || set >= kMaxBindGroups) {
+                        GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                                 "Binding over limits in the SPIRV");
+                        continue;
+                    }
+
+                    BindingInfo* info = &mBindingInfo[set][binding];
+                    *info = {};
+                    info->used = true;
+                    info->id = resource.id;
+                    info->base_type_id = resource.base_type_id;
+                    switch (bindingType) {
+                        case wgpu::BindingType::SampledTexture: {
+                            spirv_cross::SPIRType::ImageType imageType =
+                                compiler.get_type(info->base_type_id).image;
+                            spirv_cross::SPIRType::BaseType textureComponentType =
+                                compiler.get_type(imageType.type).basetype;
+
+                            info->multisampled = imageType.ms;
+                            info->textureDimension =
+                                SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed);
+                            info->textureComponentType =
+                                SpirvCrossBaseTypeToFormatType(textureComponentType);
+                            info->type = bindingType;
+                        } break;
+                        case wgpu::BindingType::StorageBuffer: {
+                            // Differentiate between readonly storage bindings and writable ones
+                            // based on the NonWritable decoration
+                            spirv_cross::Bitset flags =
+                                compiler.get_buffer_block_flags(resource.id);
+                            if (flags.get(spv::DecorationNonWritable)) {
+                                info->type = wgpu::BindingType::ReadonlyStorageBuffer;
+                            } else {
+                                info->type = wgpu::BindingType::StorageBuffer;
+                            }
+                        } break;
+                        default:
+                            info->type = bindingType;
+                    }
                 }
-
-                BindingInfo* info = &mBindingInfo[set][binding];
-                *info = {};
-                info->used = true;
-                info->id = resource.id;
-                info->base_type_id = resource.base_type_id;
-                switch (bindingType) {
-                    case wgpu::BindingType::SampledTexture: {
-                        spirv_cross::SPIRType::ImageType imageType =
-                            compiler.get_type(info->base_type_id).image;
-                        spirv_cross::SPIRType::BaseType textureComponentType =
-                            compiler.get_type(imageType.type).basetype;
-
-                        info->multisampled = imageType.ms;
-                        info->textureDimension =
-                            SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed);
-                        info->textureComponentType =
-                            SpirvCrossBaseTypeToFormatType(textureComponentType);
-                        info->type = bindingType;
-                    } break;
-                    case wgpu::BindingType::StorageBuffer: {
-                        // Differentiate between readonly storage bindings and writable ones based
-                        // on the NonWritable decoration
-                        spirv_cross::Bitset flags = compiler.get_buffer_block_flags(resource.id);
-                        if (flags.get(spv::DecorationNonWritable)) {
-                            info->type = wgpu::BindingType::ReadonlyStorageBuffer;
-                        } else {
-                            info->type = wgpu::BindingType::StorageBuffer;
-                        }
-                    } break;
-                    default:
-                        info->type = bindingType;
-                }
-            }
-        };
+            };
 
         ExtractResourcesBinding(resources.uniform_buffers, compiler,
                                 wgpu::BindingType::UniformBuffer);
@@ -225,32 +446,32 @@
                 uint32_t location = compiler.get_decoration(attrib.id, spv::DecorationLocation);
 
                 if (location >= kMaxVertexAttributes) {
-                    device->HandleError(wgpu::ErrorType::Validation,
-                                        "Attribute location over limits in the SPIRV");
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Attribute location over limits in the SPIRV");
                     return;
                 }
 
                 mUsedVertexAttributes.set(location);
             }
 
-            // Without a location qualifier on vertex outputs, spirv_cross::CompilerMSL gives them
-            // all the location 0, causing a compile error.
+            // Without a location qualifier on vertex outputs, spirv_cross::CompilerMSL gives
+            // them all the location 0, causing a compile error.
             for (const auto& attrib : resources.stage_outputs) {
                 if (!compiler.get_decoration_bitset(attrib.id).get(spv::DecorationLocation)) {
-                    device->HandleError(wgpu::ErrorType::Validation,
-                                        "Need location qualifier on vertex output");
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Need location qualifier on vertex output");
                     return;
                 }
             }
         }
 
         if (mExecutionModel == SingleShaderStage::Fragment) {
-            // Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives them
-            // all the location 0, causing a compile error.
+            // Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives
+            // them all the location 0, causing a compile error.
             for (const auto& attrib : resources.stage_inputs) {
                 if (!compiler.get_decoration_bitset(attrib.id).get(spv::DecorationLocation)) {
-                    device->HandleError(wgpu::ErrorType::Validation,
-                                        "Need location qualifier on fragment input");
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Need location qualifier on fragment input");
                     return;
                 }
             }
@@ -261,8 +482,8 @@
                 uint32_t location =
                     compiler.get_decoration(fragmentOutput.id, spv::DecorationLocation);
                 if (location >= kMaxColorAttachments) {
-                    device->HandleError(wgpu::ErrorType::Validation,
-                                        "Fragment output location over limits in the SPIRV");
+                    GetDevice()->HandleError(wgpu::ErrorType::Validation,
+                                             "Fragment output location over limits in the SPIRV");
                     return;
                 }
 
@@ -379,4 +600,12 @@
         return a->mCode == b->mCode;
     }
 
+    bool ShaderModuleBase::CheckSpvcSuccess(shaderc_spvc_status status, const char* error_msg) {
+        if (status != shaderc_spvc_status_success) {
+            GetDevice()->HandleError(wgpu::ErrorType::Validation, error_msg);
+            return false;
+        }
+        return true;
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index da69c4d..9a6bccc 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -89,6 +89,13 @@
 
         bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout) const;
 
+        // Different implementations reflection into the shader depending on
+        // whether using spvc, or directly accessing spirv-cross.
+        void ExtractSpirvInfoWithSpvc(const spirv_cross::Compiler& compiler);
+        void ExtractSpirvInfoWithSpirvCross(const spirv_cross::Compiler& compiler);
+
+        bool CheckSpvcSuccess(shaderc_spvc_status status, const char* error_msg);
+
         // TODO(cwallez@chromium.org): The code is only stored for deduplication. We could maybe
         // store a cryptographic hash of the code instead?
         std::vector<uint32_t> mCode;