ShaderModule: Add support for multiple entryPoints

Also adds validation tests that reflection data is correctly computed by
entryPoint, and end2end tests that using a shader module with multiple
entryPoints works correctly.

Bug: dawn:216
Change-Id: Id2936bb220d4480872a68624996e4c42452a507d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/28244
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn
index 85d63d1..d425345 100644
--- a/src/common/BUILD.gn
+++ b/src/common/BUILD.gn
@@ -75,6 +75,10 @@
     defines += [ "DAWN_ENABLE_BACKEND_VULKAN" ]
   }
 
+  if (dawn_enable_wgsl) {
+    defines += [ "DAWN_ENABLE_WGSL" ]
+  }
+
   if (dawn_use_x11) {
     defines += [ "DAWN_USE_X11" ]
   }
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index 4558f8c..51a6761 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -589,7 +589,6 @@
 
   if (dawn_enable_wgsl) {
     deps += [ "${dawn_tint_dir}:libtint" ]
-    defines += [ "DAWN_ENABLE_WGSL" ]
   }
 }
 
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index 3d68909..6b24b05 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -444,15 +444,15 @@
         ResultOrError<std::unique_ptr<EntryPointMetadata>> ExtractSpirvInfo(
             const DeviceBase* device,
             const spirv_cross::Compiler& compiler,
-            const char* entryPointName) {
+            const std::string& entryPointName,
+            SingleShaderStage stage) {
             std::unique_ptr<EntryPointMetadata> metadata = std::make_unique<EntryPointMetadata>();
+            metadata->stage = stage;
 
             // 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();
 
-            metadata->stage = ExecutionModelToShaderStage(compiler.get_execution_model());
-
             if (resources.push_constant_buffers.size() > 0) {
                 return DAWN_VALIDATION_ERROR("Push constants aren't supported.");
             }
@@ -585,7 +585,7 @@
                                              &metadata->bindings));
 
             // Extract the vertex attributes
-            if (metadata->stage == SingleShaderStage::Vertex) {
+            if (stage == SingleShaderStage::Vertex) {
                 for (const auto& attrib : resources.stage_inputs) {
                     if (!(compiler.get_decoration_bitset(attrib.id).get(spv::DecorationLocation))) {
                         return DAWN_VALIDATION_ERROR(
@@ -609,7 +609,7 @@
                 }
             }
 
-            if (metadata->stage == SingleShaderStage::Fragment) {
+            if (stage == SingleShaderStage::Fragment) {
                 // 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) {
@@ -645,7 +645,7 @@
                 }
             }
 
-            if (metadata->stage == SingleShaderStage::Compute) {
+            if (stage == SingleShaderStage::Compute) {
                 const spirv_cross::SPIREntryPoint& spirEntryPoint =
                     compiler.get_entry_point(entryPointName, spv::ExecutionModelGLCompute);
                 metadata->localWorkgroupSize.x = spirEntryPoint.workgroup_size.x;
@@ -773,16 +773,17 @@
 
     bool ShaderModuleBase::HasEntryPoint(const std::string& entryPoint,
                                          SingleShaderStage stage) const {
-        // TODO(dawn:216): Properly extract all entryPoints from the shader module.
-        return entryPoint == "main" && stage == mMainEntryPoint->stage;
+        auto entryPointsForNameIt = mEntryPoints.find(entryPoint);
+        if (entryPointsForNameIt == mEntryPoints.end()) {
+            return false;
+        }
+        return entryPointsForNameIt->second[stage] != nullptr;
     }
 
     const EntryPointMetadata& ShaderModuleBase::GetEntryPoint(const std::string& entryPoint,
                                                               SingleShaderStage stage) const {
-        // TODO(dawn:216): Properly extract all entryPoints from the shader module.
-        ASSERT(entryPoint == "main");
-        ASSERT(stage == mMainEntryPoint->stage);
-        return *mMainEntryPoint;
+        ASSERT(HasEntryPoint(entryPoint, stage));
+        return *mEntryPoints.at(entryPoint)[stage];
     }
 
     size_t ShaderModuleBase::HashFunc::operator()(const ShaderModuleBase* module) const {
@@ -824,14 +825,17 @@
         }
 
         spirv_cross::Compiler compiler(mSpirv);
-        DAWN_TRY_ASSIGN(mMainEntryPoint, ExtractSpirvInfo(GetDevice(), compiler, "main"));
+        for (const spirv_cross::EntryPoint& entryPoint : compiler.get_entry_points_and_stages()) {
+            SingleShaderStage stage = ExecutionModelToShaderStage(entryPoint.execution_model);
+            compiler.set_entry_point(entryPoint.name, entryPoint.execution_model);
+
+            std::unique_ptr<EntryPointMetadata> metadata;
+            DAWN_TRY_ASSIGN(metadata,
+                            ExtractSpirvInfo(GetDevice(), compiler, entryPoint.name, stage));
+            mEntryPoints[entryPoint.name][stage] = std::move(metadata);
+        }
 
         return {};
     }
 
-    SingleShaderStage ShaderModuleBase::GetMainEntryPointStageForTransition() const {
-        ASSERT(!IsError());
-        return mMainEntryPoint->stage;
-    }
-
 }  // namespace dawn_native
diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h
index aef5286..6f9f2a3 100644
--- a/src/dawn_native/ShaderModule.h
+++ b/src/dawn_native/ShaderModule.h
@@ -28,6 +28,7 @@
 
 #include <bitset>
 #include <map>
+#include <unordered_map>
 #include <vector>
 
 namespace spirv_cross {
@@ -123,11 +124,6 @@
       protected:
         MaybeError InitializeBase();
 
-        // Allows backends to get the stage for the "main" entrypoint while they are transitioned to
-        // support multiple entrypoints.
-        // TODO(dawn:216): Remove this once the transition is complete.
-        SingleShaderStage GetMainEntryPointStageForTransition() const;
-
       private:
         ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag);
 
@@ -136,7 +132,8 @@
         std::vector<uint32_t> mSpirv;
         std::string mWgsl;
 
-        std::unique_ptr<EntryPointMetadata> mMainEntryPoint;
+        // A map from [name, stage] to EntryPointMetadata.
+        std::unordered_map<std::string, PerStage<std::unique_ptr<EntryPointMetadata>>> mEntryPoints;
     };
 
 }  // namespace dawn_native
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 7817d8a..b701984 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -285,6 +285,7 @@
     "end2end/DrawIndirectTests.cpp",
     "end2end/DrawTests.cpp",
     "end2end/DynamicBufferOffsetTests.cpp",
+    "end2end/EntryPointTests.cpp",
     "end2end/FenceTests.cpp",
     "end2end/GpuMemorySynchronizationTests.cpp",
     "end2end/IndexFormatTests.cpp",
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index bc04c7c..5873da8 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -610,6 +610,14 @@
     return gTestEnv->IsDawnValidationSkipped();
 }
 
+bool DawnTestBase::HasWGSL() const {
+#ifdef DAWN_ENABLE_WGSL
+    return true;
+#else
+    return false;
+#endif
+}
+
 bool DawnTestBase::IsAsan() const {
 #if defined(ADDRESS_SANITIZER)
     return true;
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 6e2196d..2f818fb 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -250,6 +250,7 @@
     bool UsesWire() const;
     bool IsBackendValidationEnabled() const;
     bool IsDawnValidationSkipped() const;
+    bool HasWGSL() const;
 
     bool IsAsan() const;
 
diff --git a/src/tests/end2end/EntryPointTests.cpp b/src/tests/end2end/EntryPointTests.cpp
new file mode 100644
index 0000000..1347262
--- /dev/null
+++ b/src/tests/end2end/EntryPointTests.cpp
@@ -0,0 +1,189 @@
+// Copyright 2020 The Dawn Authors
+//
+// 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 "tests/DawnTest.h"
+
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+class EntryPointTests : public DawnTest {};
+
+// Test creating a render pipeline from two entryPoints in the same module.
+TEST_P(EntryPointTests, FragAndVertexSameModule) {
+    // TODO: Reenable once Tint is able to produce Vulkan 1.0 / 1.1 SPIR-V.
+    DAWN_SKIP_TEST_IF(IsVulkan());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        [[builtin position]] var<out> Position : vec4<f32>;
+        fn vertex_main() -> void {
+            Position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+            return;
+        }
+        entry_point vertex = vertex_main;
+
+        [[location 0]] var<out> outColor : vec4<f32>;
+        fn fragment_main() -> void {
+          outColor = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+          return;
+        }
+        entry_point fragment = fragment_main;
+    )");
+
+    // Create a point pipeline from the module.
+    utils::ComboRenderPipelineDescriptor desc(device);
+    desc.vertexStage.module = module;
+    desc.vertexStage.entryPoint = "vertex_main";
+    desc.cFragmentStage.module = module;
+    desc.cFragmentStage.entryPoint = "fragment_main";
+    desc.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm;
+    desc.primitiveTopology = wgpu::PrimitiveTopology::PointList;
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
+
+    // Render the point and check that it was rendered.
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+        pass.SetPipeline(pipeline);
+        pass.Draw(1);
+        pass.EndPass();
+    }
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    EXPECT_PIXEL_RGBA8_EQ(RGBA8::kRed, renderPass.color, 0, 0);
+}
+
+// Test creating a render pipeline from two entryPoints in the same module with the same name.
+TEST_P(EntryPointTests, FragAndVertexSameModuleSameName) {
+    // TODO: Reenable once Tint is able to produce Vulkan 1.0 / 1.1 SPIR-V.
+    DAWN_SKIP_TEST_IF(IsVulkan());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        [[builtin position]] var<out> Position : vec4<f32>;
+        fn vertex_main() -> void {
+            Position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+            return;
+        }
+        entry_point vertex as "main" = vertex_main;
+
+        [[location 0]] var<out> outColor : vec4<f32>;
+        fn fragment_main() -> void {
+          outColor = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+          return;
+        }
+        entry_point fragment as "main" = fragment_main;
+    )");
+
+    // Create a point pipeline from the module.
+    utils::ComboRenderPipelineDescriptor desc(device);
+    desc.vertexStage.module = module;
+    desc.vertexStage.entryPoint = "main";
+    desc.cFragmentStage.module = module;
+    desc.cFragmentStage.entryPoint = "main";
+    desc.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm;
+    desc.primitiveTopology = wgpu::PrimitiveTopology::PointList;
+    wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc);
+
+    // Render the point and check that it was rendered.
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+        pass.SetPipeline(pipeline);
+        pass.Draw(1);
+        pass.EndPass();
+    }
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    EXPECT_PIXEL_RGBA8_EQ(RGBA8::kRed, renderPass.color, 0, 0);
+}
+
+// Test creating two compute pipelines from the same module.
+TEST_P(EntryPointTests, TwoComputeInModule) {
+    // TODO: Reenable once Tint is able to produce Vulkan 1.0 / 1.1 SPIR-V.
+    DAWN_SKIP_TEST_IF(IsVulkan());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        type Data = [[block]] struct {
+            [[offset 0]] data : u32;
+        };
+        [[binding 0, set 0]] var<storage_buffer> data : Data;
+
+        fn write1() -> void {
+            data.data = 1u;
+            return;
+        }
+        fn write42() -> void {
+            data.data = 42u;
+            return;
+        }
+        entry_point compute = write1;
+        entry_point compute = write42;
+    )");
+
+    // Create both pipelines from the module.
+    wgpu::ComputePipelineDescriptor pipelineDesc;
+    pipelineDesc.computeStage.module = module;
+
+    pipelineDesc.computeStage.entryPoint = "write1";
+    wgpu::ComputePipeline write1 = device.CreateComputePipeline(&pipelineDesc);
+
+    pipelineDesc.computeStage.entryPoint = "write42";
+    wgpu::ComputePipeline write42 = device.CreateComputePipeline(&pipelineDesc);
+
+    // Create the bindGroup.
+    wgpu::BufferDescriptor bufferDesc;
+    bufferDesc.size = 4;
+    bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
+    wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
+
+    wgpu::BindGroup group =
+        utils::MakeBindGroup(device, write1.GetBindGroupLayout(0), {{0, buffer}});
+
+    // Use the first pipeline and check it wrote 1.
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.SetPipeline(write1);
+        pass.SetBindGroup(0, group);
+        pass.Dispatch(1);
+        pass.EndPass();
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER_U32_EQ(1, buffer, 0);
+    }
+
+    // Use the second pipeline and check it wrote 42.
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+        pass.SetPipeline(write42);
+        pass.SetBindGroup(0, group);
+        pass.Dispatch(42);
+        pass.EndPass();
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_BUFFER_U32_EQ(42, buffer, 0);
+    }
+}
+
+DAWN_INSTANTIATE_TEST(EntryPointTests,
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      VulkanBackend());
diff --git a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
index f4a8ff0..561a86e 100644
--- a/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
+++ b/src/tests/unittests/validation/GetBindGroupLayoutValidationTests.cpp
@@ -675,3 +675,52 @@
         EXPECT_EQ(pipeline.GetBindGroupLayout(3).Get(), emptyBindGroupLayout.Get());
     }
 }
+
+// Test that fragment output validation is for the correct entryPoint
+// TODO(dawn:216): Re-enable when we correctly reflect which bindings are used for an entryPoint.
+TEST_F(GetBindGroupLayoutTests, DISABLED_FromCorrectEntryPoint) {
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        type Data = [[block]] struct {
+            [[offset 0]] data : f32;
+        };
+        [[binding 0, set 0]] var<storage_buffer> data0 : Data;
+        [[binding 1, set 0]] var<storage_buffer> data1 : Data;
+
+        fn compute0() -> void {
+            data0.data = 0.0;
+            return;
+        }
+        fn compute1() -> void {
+            data1.data = 0.0;
+            return;
+        }
+        entry_point compute = compute0;
+        entry_point compute = compute1;
+    )");
+
+    wgpu::ComputePipelineDescriptor pipelineDesc;
+    pipelineDesc.computeStage.module = module;
+
+    // Get each entryPoint's BGL.
+    pipelineDesc.computeStage.entryPoint = "compute0";
+    wgpu::ComputePipeline pipeline0 = device.CreateComputePipeline(&pipelineDesc);
+    wgpu::BindGroupLayout bgl0 = pipeline0.GetBindGroupLayout(0);
+
+    pipelineDesc.computeStage.entryPoint = "compute1";
+    wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&pipelineDesc);
+    wgpu::BindGroupLayout bgl1 = pipeline1.GetBindGroupLayout(0);
+
+    // Create the buffer used in the bindgroups.
+    wgpu::BufferDescriptor bufferDesc;
+    bufferDesc.size = 4;
+    bufferDesc.usage = wgpu::BufferUsage::Storage;
+    wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
+
+    // Success case, the BGL matches the descriptor for the bindgroup.
+    utils::MakeBindGroup(device, bgl0, {{0, buffer}});
+    utils::MakeBindGroup(device, bgl1, {{1, buffer}});
+
+    // Error case, the BGL doesn't match the descriptor for the bindgroup.
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl0, {{1, buffer}}));
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl1, {{0, buffer}}));
+}
diff --git a/src/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/tests/unittests/validation/RenderPipelineValidationTests.cpp
index da539d2..f7ac1f5 100644
--- a/src/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -590,3 +590,209 @@
         }
     }
 }
+
+// Test that the entryPoint names must be present for the correct stage in the shader module.
+TEST_F(RenderPipelineValidationTest, EntryPointNameValidation) {
+    DAWN_SKIP_TEST_IF(!HasWGSL());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        [[builtin position]] var<out> position : vec4<f32>;
+        fn vertex_main() -> void {
+            position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+            return;
+        }
+        entry_point vertex = vertex_main;
+
+        [[location 0]] var<out> color : vec4<f32>;
+        fn fragment_main() -> void {
+            color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+            return;
+        }
+        entry_point fragment = fragment_main;
+    )");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.vertexStage.module = module;
+    descriptor.vertexStage.entryPoint = "vertex_main";
+    descriptor.cFragmentStage.module = module;
+    descriptor.cFragmentStage.entryPoint = "fragment_main";
+
+    // Success case.
+    device.CreateRenderPipeline(&descriptor);
+
+    // Test for the vertex stage entryPoint name.
+    {
+        // The entryPoint name doesn't exist in the module.
+        descriptor.vertexStage.entryPoint = "main";
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+
+        // The entryPoint name exists, but not for the correct stage.
+        descriptor.vertexStage.entryPoint = "fragment_main";
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    }
+
+    descriptor.vertexStage.entryPoint = "vertex_main";
+
+    // Test for the fragment stage entryPoint name.
+    {
+        // The entryPoint name doesn't exist in the module.
+        descriptor.cFragmentStage.entryPoint = "main";
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+
+        // The entryPoint name exists, but not for the correct stage.
+        descriptor.cFragmentStage.entryPoint = "vertex_main";
+        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    }
+}
+
+// Test that vertex attrib validation is for the correct entryPoint
+TEST_F(RenderPipelineValidationTest, VertexAttribCorrectEntryPoint) {
+    DAWN_SKIP_TEST_IF(!HasWGSL());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        [[builtin position]] var<out> position : vec4<f32>;
+        [[location 0]] var<in> attrib0 : vec4<f32>;
+        [[location 1]] var<in> attrib1 : vec4<f32>;
+
+        fn vertex0() -> void {
+            position = attrib0;
+            return;
+        }
+        fn vertex1() -> void {
+            position = attrib1;
+            return;
+        }
+
+        entry_point vertex = vertex0;
+        entry_point vertex = vertex1;
+    )");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.vertexStage.module = module;
+    descriptor.cFragmentStage.module = fsModule;
+
+    descriptor.cVertexState.vertexBufferCount = 1;
+    descriptor.cVertexState.cVertexBuffers[0].attributeCount = 1;
+    descriptor.cVertexState.cVertexBuffers[0].arrayStride = 16;
+    descriptor.cVertexState.cAttributes[0].format = wgpu::VertexFormat::Float4;
+    descriptor.cVertexState.cAttributes[0].offset = 0;
+
+    // Success cases, the attribute used by the entryPoint is declared in the pipeline.
+    descriptor.vertexStage.entryPoint = "vertex0";
+    descriptor.cVertexState.cAttributes[0].shaderLocation = 0;
+    device.CreateRenderPipeline(&descriptor);
+
+    descriptor.vertexStage.entryPoint = "vertex1";
+    descriptor.cVertexState.cAttributes[0].shaderLocation = 1;
+    device.CreateRenderPipeline(&descriptor);
+
+    // Error cases, the attribute used by the entryPoint isn't declared in the pipeline.
+    descriptor.vertexStage.entryPoint = "vertex1";
+    descriptor.cVertexState.cAttributes[0].shaderLocation = 0;
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+
+    descriptor.vertexStage.entryPoint = "vertex0";
+    descriptor.cVertexState.cAttributes[0].shaderLocation = 1;
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Test that fragment output validation is for the correct entryPoint
+TEST_F(RenderPipelineValidationTest, FragmentOutputCorrectEntryPoint) {
+    DAWN_SKIP_TEST_IF(!HasWGSL());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        [[location 0]] var<out> colorFloat : vec4<f32>;
+        [[location 0]] var<out> colorUint : vec4<u32>;
+
+        fn fragmentFloat() -> void {
+            colorFloat = vec4<f32>(0.0, 0.0, 0.0, 0.0);
+            return;
+        }
+        fn fragmentUint() -> void {
+            colorUint = vec4<u32>(0, 0, 0, 0);
+            return;
+        }
+
+        entry_point fragment = fragmentFloat;
+        entry_point fragment = fragmentUint;
+    )");
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.vertexStage.module = vsModule;
+    descriptor.cFragmentStage.module = module;
+
+    // Success case, the component type matches between the pipeline and the entryPoint
+    descriptor.cFragmentStage.entryPoint = "fragmentFloat";
+    descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Float;
+    device.CreateRenderPipeline(&descriptor);
+
+    descriptor.cFragmentStage.entryPoint = "fragmentUint";
+    descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Uint;
+    device.CreateRenderPipeline(&descriptor);
+
+    // Error case, the component type doesn't match between the pipeline and the entryPoint
+    descriptor.cFragmentStage.entryPoint = "fragmentUint";
+    descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Float;
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+
+    descriptor.cFragmentStage.entryPoint = "fragmentFloat";
+    descriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA32Uint;
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Test that fragment output validation is for the correct entryPoint
+// TODO(dawn:216): Re-enable when we correctly reflect which bindings are used for an entryPoint.
+TEST_F(RenderPipelineValidationTest, DISABLED_BindingsFromCorrectEntryPoint) {
+    DAWN_SKIP_TEST_IF(!HasWGSL());
+
+    wgpu::ShaderModule module = utils::CreateShaderModuleFromWGSL(device, R"(
+        type Uniforms = [[block]] struct {
+            [[offset 0]] data : vec4<f32>;
+        };
+        [[binding 0, set 0]] var<uniform> var0 : Uniforms;
+        [[binding 1, set 0]] var<uniform> var1 : Uniforms;
+        [[builtin position]] var<out> position : vec4<f32>;
+
+        fn vertex0() -> void {
+            position = var0.data;
+            return;
+        }
+        fn vertex1() -> void {
+            position = var1.data;
+            return;
+        }
+
+        entry_point vertex = vertex0;
+        entry_point vertex = vertex1;
+    )");
+
+    wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
+        device, {{0, wgpu::ShaderStage::Vertex, wgpu::BindingType::UniformBuffer}});
+    wgpu::PipelineLayout layout0 = utils::MakeBasicPipelineLayout(device, &bgl0);
+
+    wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
+        device, {{1, wgpu::ShaderStage::Vertex, wgpu::BindingType::UniformBuffer}});
+    wgpu::PipelineLayout layout1 = utils::MakeBasicPipelineLayout(device, &bgl1);
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    descriptor.vertexStage.module = module;
+    descriptor.cFragmentStage.module = fsModule;
+
+    // Success case, the BGL matches the bindings used by the entryPoint
+    descriptor.vertexStage.entryPoint = "vertex0";
+    descriptor.layout = layout0;
+    device.CreateRenderPipeline(&descriptor);
+
+    descriptor.vertexStage.entryPoint = "vertex1";
+    descriptor.layout = layout1;
+    device.CreateRenderPipeline(&descriptor);
+
+    // Error case, the BGL doesn't match the bindings used by the entryPoint
+    descriptor.vertexStage.entryPoint = "vertex1";
+    descriptor.layout = layout0;
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+
+    descriptor.vertexStage.entryPoint = "vertex0";
+    descriptor.layout = layout1;
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
diff --git a/src/tests/unittests/validation/ValidationTest.cpp b/src/tests/unittests/validation/ValidationTest.cpp
index 6b533b4..058fd3b 100644
--- a/src/tests/unittests/validation/ValidationTest.cpp
+++ b/src/tests/unittests/validation/ValidationTest.cpp
@@ -102,6 +102,14 @@
     device.Tick();
 }
 
+bool ValidationTest::HasWGSL() const {
+#ifdef DAWN_ENABLE_WGSL
+    return true;
+#else
+    return false;
+#endif
+}
+
 // static
 void ValidationTest::OnDeviceError(WGPUErrorType type, const char* message, void* userdata) {
     ASSERT(type != WGPUErrorType_NoError);
diff --git a/src/tests/unittests/validation/ValidationTest.h b/src/tests/unittests/validation/ValidationTest.h
index c922dd6..8468aa7 100644
--- a/src/tests/unittests/validation/ValidationTest.h
+++ b/src/tests/unittests/validation/ValidationTest.h
@@ -15,6 +15,7 @@
 #ifndef TESTS_UNITTESTS_VALIDATIONTEST_H_
 #define TESTS_UNITTESTS_VALIDATIONTEST_H_
 
+#include "common/Log.h"
 #include "dawn/webgpu_cpp.h"
 #include "dawn_native/DawnNative.h"
 #include "gtest/gtest.h"
@@ -28,6 +29,16 @@
     do {                                                        \
     } while (0)
 
+// Skip a test when the given condition is satisfied.
+#define DAWN_SKIP_TEST_IF(condition)                            \
+    do {                                                        \
+        if (condition) {                                        \
+            dawn::InfoLog() << "Test skipped: " #condition "."; \
+            GTEST_SKIP();                                       \
+            return;                                             \
+        }                                                       \
+    } while (0)
+
 class ValidationTest : public testing::Test {
   public:
     ValidationTest();
@@ -58,6 +69,8 @@
         wgpu::RenderPassColorAttachmentDescriptor mColorAttachment;
     };
 
+    bool HasWGSL() const;
+
   protected:
     wgpu::Device device;
     dawn_native::Adapter adapter;