D3D12: Fix order of Tint transforms

FirstIndexOffset must happen after BindingRemapper otherwise some
invalid AST is produced after FirstIndexOffset.

This was found running a CTS test on Windows:
validation,vertex_state:vertex_shader_type_matches_attribute_format

Also adds a regression test.

Bug: dawn:1014

Change-Id: Icbe4b3adb5e5844ffc8435e0e67056c7dff23970
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/59281
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
index d2f0ed5..265725e 100644
--- a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
@@ -251,13 +251,19 @@
         if (GetDevice()->IsRobustnessEnabled()) {
             transformManager.Add<tint::transform::BoundArrayAccessors>();
         }
+        transformManager.Add<tint::transform::BindingRemapper>();
+
+        // The FirstIndexOffset transform must be done after the BindingRemapper because it assumes
+        // that the register space has already flattened (and uses the next register). Otherwise
+        // intermediate ASTs can be produced where the extra registers conflict with one of the
+        // user-declared bind points.
         if (stage == SingleShaderStage::Vertex) {
             transformManager.Add<tint::transform::FirstIndexOffset>();
             transformInputs.Add<tint::transform::FirstIndexOffset::BindingPoint>(
                 layout->GetFirstIndexOffsetShaderRegister(),
                 layout->GetFirstIndexOffsetRegisterSpace());
         }
-        transformManager.Add<tint::transform::BindingRemapper>();
+
         transformManager.Add<tint::transform::Renamer>();
 
         if (GetDevice()->IsToggleEnabled(Toggle::DisableSymbolRenaming)) {
diff --git a/src/tests/end2end/ShaderTests.cpp b/src/tests/end2end/ShaderTests.cpp
index 4fb95b3..5c53bd5 100644
--- a/src/tests/end2end/ShaderTests.cpp
+++ b/src/tests/end2end/ShaderTests.cpp
@@ -322,6 +322,50 @@
     ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, shader.c_str()));
 }
 
+// This is a regression test for an issue caused by the FirstIndexOffset transfrom being done before
+// the BindingRemapper, causing an intermediate AST to be invalid (and fail the overall
+// compilation).
+TEST_P(ShaderTests, FirstIndexOffsetRegisterConflictInHLSLTransforms) {
+    // TODO(crbug.com/dawn/658): Crashes on bots because there are two entrypoints in the shader.
+    DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
+
+    const char* shader = R"(
+// Dumped WGSL:
+
+struct Inputs {
+  [[location(1)]] attrib1 : u32;
+  // The extra register added to handle base_vertex for vertex_index conflicts with [1]
+  [[builtin(vertex_index)]] vertexIndex: u32;
+};
+
+// [1] a binding point that conflicts with the regitster
+[[block]] struct S1 { data : array<vec4<u32>, 20>; };
+[[group(0), binding(1)]] var<uniform> providedData1 : S1;
+
+[[stage(vertex)]] fn vsMain(input : Inputs) -> [[builtin(position)]] vec4<f32> {
+  ignore(providedData1.data[input.vertexIndex][0]);
+  return vec4<f32>();
+}
+
+[[stage(fragment)]] fn fsMain() -> [[location(0)]] vec4<f32> {
+  return vec4<f32>();
+}
+    )";
+    auto module = utils::CreateShaderModule(device, shader);
+
+    utils::ComboRenderPipelineDescriptor rpDesc;
+    rpDesc.vertex.module = module;
+    rpDesc.vertex.entryPoint = "vsMain";
+    rpDesc.cFragment.module = module;
+    rpDesc.cFragment.entryPoint = "fsMain";
+    rpDesc.vertex.bufferCount = 1;
+    rpDesc.cBuffers[0].attributeCount = 1;
+    rpDesc.cBuffers[0].arrayStride = 16;
+    rpDesc.cAttributes[0].shaderLocation = 1;
+    rpDesc.cAttributes[0].format = wgpu::VertexFormat::Uint8x2;
+    device.CreateRenderPipeline(&rpDesc);
+}
+
 DAWN_INSTANTIATE_TEST(ShaderTests,
                       D3D12Backend(),
                       MetalBackend(),