[tint] Add VectorizeScalarMatrixConstructors

Several target languages are missing these constructors. Use it from
the SPIR-V backend.

Fixed: tint:2078
Change-Id: I908360d464c9d5eaa324cf8302780ccc2a73d9d9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/158204
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index 44a3f6a..c5b1f51 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -54,6 +54,7 @@
     "robustness.cc",
     "shader_io.cc",
     "std140.cc",
+    "vectorize_scalar_matrix_constructors.cc",
     "zero_init_workgroup_memory.cc",
   ],
   hdrs = [
@@ -72,6 +73,7 @@
     "robustness.h",
     "shader_io.h",
     "std140.h",
+    "vectorize_scalar_matrix_constructors.h",
     "zero_init_workgroup_memory.h",
   ],
   deps = [
@@ -118,6 +120,7 @@
     "preserve_padding_test.cc",
     "robustness_test.cc",
     "std140_test.cc",
+    "vectorize_scalar_matrix_constructors_test.cc",
     "zero_init_workgroup_memory_test.cc",
   ] + select({
     "//conditions:default": [],
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index ed4ae4b3..40c0852 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -69,6 +69,8 @@
   lang/core/ir/transform/shader_io.h
   lang/core/ir/transform/std140.cc
   lang/core/ir/transform/std140.h
+  lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
+  lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
   lang/core/ir/transform/zero_init_workgroup_memory.cc
   lang/core/ir/transform/zero_init_workgroup_memory.h
 )
@@ -116,6 +118,7 @@
   lang/core/ir/transform/preserve_padding_test.cc
   lang/core/ir/transform/robustness_test.cc
   lang/core/ir/transform/std140_test.cc
+  lang/core/ir/transform/vectorize_scalar_matrix_constructors_test.cc
   lang/core/ir/transform/zero_init_workgroup_memory_test.cc
 )
 
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 91305e4..f4fb43e 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -74,6 +74,8 @@
     "shader_io.h",
     "std140.cc",
     "std140.h",
+    "vectorize_scalar_matrix_constructors.cc",
+    "vectorize_scalar_matrix_constructors.h",
     "zero_init_workgroup_memory.cc",
     "zero_init_workgroup_memory.h",
   ]
@@ -118,6 +120,7 @@
       "preserve_padding_test.cc",
       "robustness_test.cc",
       "std140_test.cc",
+      "vectorize_scalar_matrix_constructors_test.cc",
       "zero_init_workgroup_memory_test.cc",
     ]
     deps = [
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
new file mode 100644
index 0000000..68e365a
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
@@ -0,0 +1,109 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::core::ir::transform {
+
+namespace {
+
+/// PIMPL state for the transform.
+struct State {
+    /// The IR module.
+    Module& ir;
+
+    /// The IR builder.
+    Builder b{ir};
+
+    /// The type manager.
+    core::type::Manager& ty{ir.Types()};
+
+    /// Process the module.
+    void Process() {
+        // Find and replace matrix constructors that take scalar operands.
+        Vector<Construct*, 8> worklist;
+        for (auto inst : ir.instructions.Objects()) {
+            if (auto* construct = inst->As<Construct>(); construct && construct->Alive()) {
+                if (construct->Result()->Type()->As<type::Matrix>()) {
+                    if (construct->Operands().Length() > 0 &&
+                        construct->Operands()[0]->Type()->Is<type::Scalar>()) {
+                        b.InsertBefore(construct, [&] {  //
+                            ReplaceConstructor(construct);
+                        });
+                    }
+                }
+            }
+        }
+    }
+
+    /// Replace a matrix construct instruction.
+    /// @param construct the instruction to replace
+    void ReplaceConstructor(Construct* construct) {
+        auto* mat = construct->Result()->Type()->As<type::Matrix>();
+        auto* col = mat->ColumnType();
+        const auto& scalars = construct->Operands();
+
+        // Collect consecutive scalars into column vectors.
+        Vector<Value*, 4> columns;
+        for (uint32_t c = 0; c < mat->columns(); c++) {
+            Vector<Value*, 4> values;
+            for (uint32_t r = 0; r < col->Width(); r++) {
+                values.Push(scalars[c * col->Width() + r]);
+            }
+            columns.Push(b.Construct(col, std::move(values))->Result());
+        }
+
+        // Construct the matrix from the column vectors and replace the original instruction.
+        auto* replacement = b.Construct(mat, std::move(columns))->Result();
+        construct->Result()->ReplaceAllUsesWith(replacement);
+        construct->Destroy();
+    }
+};
+
+}  // namespace
+
+Result<SuccessType> VectorizeScalarMatrixConstructors(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "VectorizeScalarMatrixConstructors transform");
+    if (!result) {
+        return result;
+    }
+
+    State{ir}.Process();
+
+    return Success;
+}
+
+}  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
new file mode 100644
index 0000000..ff4ef93
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
@@ -0,0 +1,49 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// VectorizeScalarMatrixConstructors is a transform that replaces construct instructions that
+/// produce matrices from scalar operands to construct individual columns first.
+///
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> VectorizeScalarMatrixConstructors(Module& module);
+
+}  // namespace tint::core::ir::transform
+
+#endif  // SRC_TINT_LANG_CORE_IR_TRANSFORM_VECTORIZE_SCALAR_MATRIX_CONSTRUCTORS_H_
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_test.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_test.cc
new file mode 100644
index 0000000..4f42888
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_test.cc
@@ -0,0 +1,576 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+#include "src/tint/lang/core/type/matrix.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+using IR_VectorizeScalarMatrixConstructorsTest = TransformTest;
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, NoModify_NoOperands) {
+    auto* mat = ty.mat3x3<f32>();
+    auto* func = b.Function("foo", mat);
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func():mat3x3<f32> -> %b1 {
+  %b1 = block {
+    %2:mat3x3<f32> = construct
+    ret %2
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, NoModify_Identity) {
+    auto* mat = ty.mat3x3<f32>();
+    auto* value = b.FunctionParam("value", mat);
+    auto* func = b.Function("foo", mat);
+    func->SetParams({value});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, value);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%value:mat3x3<f32>):mat3x3<f32> -> %b1 {
+  %b1 = block {
+    %3:mat3x3<f32> = construct %value
+    ret %3
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, NoModify_Vectors) {
+    auto* mat = ty.mat3x3<f32>();
+    auto* v1 = b.FunctionParam("v1", mat->ColumnType());
+    auto* v2 = b.FunctionParam("v2", mat->ColumnType());
+    auto* v3 = b.FunctionParam("v3", mat->ColumnType());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:vec3<f32>, %v2:vec3<f32>, %v3:vec3<f32>):mat3x3<f32> -> %b1 {
+  %b1 = block {
+    %5:mat3x3<f32> = construct %v1, %v2, %v3
+    ret %5
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat2x2) {
+    auto* mat = ty.mat2x2<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32):mat2x2<f32> -> %b1 {
+  %b1 = block {
+    %6:mat2x2<f32> = construct %v1, %v2, %v3, %v4
+    ret %6
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32):mat2x2<f32> -> %b1 {
+  %b1 = block {
+    %6:vec2<f32> = construct %v1, %v2
+    %7:vec2<f32> = construct %v3, %v4
+    %8:mat2x2<f32> = construct %6, %7
+    ret %8
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat2x3) {
+    auto* mat = ty.mat2x3<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32):mat2x3<f32> -> %b1 {
+  %b1 = block {
+    %8:mat2x3<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6
+    ret %8
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32):mat2x3<f32> -> %b1 {
+  %b1 = block {
+    %8:vec3<f32> = construct %v1, %v2, %v3
+    %9:vec3<f32> = construct %v4, %v5, %v6
+    %10:mat2x3<f32> = construct %8, %9
+    ret %10
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat2x4) {
+    auto* mat = ty.mat2x4<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* v7 = b.FunctionParam("v7", ty.f32());
+    auto* v8 = b.FunctionParam("v8", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32):mat2x4<f32> -> %b1 {
+  %b1 = block {
+    %10:mat2x4<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8
+    ret %10
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32):mat2x4<f32> -> %b1 {
+  %b1 = block {
+    %10:vec4<f32> = construct %v1, %v2, %v3, %v4
+    %11:vec4<f32> = construct %v5, %v6, %v7, %v8
+    %12:mat2x4<f32> = construct %10, %11
+    ret %12
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat3x2) {
+    auto* mat = ty.mat3x2<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32):mat3x2<f32> -> %b1 {
+  %b1 = block {
+    %8:mat3x2<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6
+    ret %8
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32):mat3x2<f32> -> %b1 {
+  %b1 = block {
+    %8:vec2<f32> = construct %v1, %v2
+    %9:vec2<f32> = construct %v3, %v4
+    %10:vec2<f32> = construct %v5, %v6
+    %11:mat3x2<f32> = construct %8, %9, %10
+    ret %11
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat3x3) {
+    auto* mat = ty.mat3x3<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* v7 = b.FunctionParam("v7", ty.f32());
+    auto* v8 = b.FunctionParam("v8", ty.f32());
+    auto* v9 = b.FunctionParam("v9", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8, v9});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8, v9);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32):mat3x3<f32> -> %b1 {
+  %b1 = block {
+    %11:mat3x3<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8, %v9
+    ret %11
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32):mat3x3<f32> -> %b1 {
+  %b1 = block {
+    %11:vec3<f32> = construct %v1, %v2, %v3
+    %12:vec3<f32> = construct %v4, %v5, %v6
+    %13:vec3<f32> = construct %v7, %v8, %v9
+    %14:mat3x3<f32> = construct %11, %12, %13
+    ret %14
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat3x4) {
+    auto* mat = ty.mat3x4<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* v7 = b.FunctionParam("v7", ty.f32());
+    auto* v8 = b.FunctionParam("v8", ty.f32());
+    auto* v9 = b.FunctionParam("v9", ty.f32());
+    auto* v10 = b.FunctionParam("v10", ty.f32());
+    auto* v11 = b.FunctionParam("v11", ty.f32());
+    auto* v12 = b.FunctionParam("v12", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32, %v10:f32, %v11:f32, %v12:f32):mat3x4<f32> -> %b1 {
+  %b1 = block {
+    %14:mat3x4<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8, %v9, %v10, %v11, %v12
+    ret %14
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32, %v10:f32, %v11:f32, %v12:f32):mat3x4<f32> -> %b1 {
+  %b1 = block {
+    %14:vec4<f32> = construct %v1, %v2, %v3, %v4
+    %15:vec4<f32> = construct %v5, %v6, %v7, %v8
+    %16:vec4<f32> = construct %v9, %v10, %v11, %v12
+    %17:mat3x4<f32> = construct %14, %15, %16
+    ret %17
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat4x2) {
+    auto* mat = ty.mat4x2<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* v7 = b.FunctionParam("v7", ty.f32());
+    auto* v8 = b.FunctionParam("v8", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32):mat4x2<f32> -> %b1 {
+  %b1 = block {
+    %10:mat4x2<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8
+    ret %10
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32):mat4x2<f32> -> %b1 {
+  %b1 = block {
+    %10:vec2<f32> = construct %v1, %v2
+    %11:vec2<f32> = construct %v3, %v4
+    %12:vec2<f32> = construct %v5, %v6
+    %13:vec2<f32> = construct %v7, %v8
+    %14:mat4x2<f32> = construct %10, %11, %12, %13
+    ret %14
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat4x3) {
+    auto* mat = ty.mat4x3<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* v7 = b.FunctionParam("v7", ty.f32());
+    auto* v8 = b.FunctionParam("v8", ty.f32());
+    auto* v9 = b.FunctionParam("v9", ty.f32());
+    auto* v10 = b.FunctionParam("v10", ty.f32());
+    auto* v11 = b.FunctionParam("v11", ty.f32());
+    auto* v12 = b.FunctionParam("v12", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32, %v10:f32, %v11:f32, %v12:f32):mat4x3<f32> -> %b1 {
+  %b1 = block {
+    %14:mat4x3<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8, %v9, %v10, %v11, %v12
+    ret %14
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32, %v10:f32, %v11:f32, %v12:f32):mat4x3<f32> -> %b1 {
+  %b1 = block {
+    %14:vec3<f32> = construct %v1, %v2, %v3
+    %15:vec3<f32> = construct %v4, %v5, %v6
+    %16:vec3<f32> = construct %v7, %v8, %v9
+    %17:vec3<f32> = construct %v10, %v11, %v12
+    %18:mat4x3<f32> = construct %14, %15, %16, %17
+    ret %18
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat4x4) {
+    auto* mat = ty.mat4x4<f32>();
+    auto* v1 = b.FunctionParam("v1", ty.f32());
+    auto* v2 = b.FunctionParam("v2", ty.f32());
+    auto* v3 = b.FunctionParam("v3", ty.f32());
+    auto* v4 = b.FunctionParam("v4", ty.f32());
+    auto* v5 = b.FunctionParam("v5", ty.f32());
+    auto* v6 = b.FunctionParam("v6", ty.f32());
+    auto* v7 = b.FunctionParam("v7", ty.f32());
+    auto* v8 = b.FunctionParam("v8", ty.f32());
+    auto* v9 = b.FunctionParam("v9", ty.f32());
+    auto* v10 = b.FunctionParam("v10", ty.f32());
+    auto* v11 = b.FunctionParam("v11", ty.f32());
+    auto* v12 = b.FunctionParam("v12", ty.f32());
+    auto* v13 = b.FunctionParam("v13", ty.f32());
+    auto* v14 = b.FunctionParam("v14", ty.f32());
+    auto* v15 = b.FunctionParam("v15", ty.f32());
+    auto* v16 = b.FunctionParam("v16", ty.f32());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16});
+    b.Append(func->Block(), [&] {
+        auto* construct =
+            b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32, %v10:f32, %v11:f32, %v12:f32, %v13:f32, %v14:f32, %v15:f32, %v16:f32):mat4x4<f32> -> %b1 {
+  %b1 = block {
+    %18:mat4x4<f32> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8, %v9, %v10, %v11, %v12, %v13, %v14, %v15, %v16
+    ret %18
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f32, %v2:f32, %v3:f32, %v4:f32, %v5:f32, %v6:f32, %v7:f32, %v8:f32, %v9:f32, %v10:f32, %v11:f32, %v12:f32, %v13:f32, %v14:f32, %v15:f32, %v16:f32):mat4x4<f32> -> %b1 {
+  %b1 = block {
+    %18:vec4<f32> = construct %v1, %v2, %v3, %v4
+    %19:vec4<f32> = construct %v5, %v6, %v7, %v8
+    %20:vec4<f32> = construct %v9, %v10, %v11, %v12
+    %21:vec4<f32> = construct %v13, %v14, %v15, %v16
+    %22:mat4x4<f32> = construct %18, %19, %20, %21
+    ret %22
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_VectorizeScalarMatrixConstructorsTest, Mat3x3_F16) {
+    auto* mat = ty.mat3x3<f16>();
+    auto* v1 = b.FunctionParam("v1", ty.f16());
+    auto* v2 = b.FunctionParam("v2", ty.f16());
+    auto* v3 = b.FunctionParam("v3", ty.f16());
+    auto* v4 = b.FunctionParam("v4", ty.f16());
+    auto* v5 = b.FunctionParam("v5", ty.f16());
+    auto* v6 = b.FunctionParam("v6", ty.f16());
+    auto* v7 = b.FunctionParam("v7", ty.f16());
+    auto* v8 = b.FunctionParam("v8", ty.f16());
+    auto* v9 = b.FunctionParam("v9", ty.f16());
+    auto* func = b.Function("foo", mat);
+    func->SetParams({v1, v2, v3, v4, v5, v6, v7, v8, v9});
+    b.Append(func->Block(), [&] {
+        auto* construct = b.Construct(mat, v1, v2, v3, v4, v5, v6, v7, v8, v9);
+        b.Return(func, construct->Result());
+    });
+
+    auto* src = R"(
+%foo = func(%v1:f16, %v2:f16, %v3:f16, %v4:f16, %v5:f16, %v6:f16, %v7:f16, %v8:f16, %v9:f16):mat3x3<f16> -> %b1 {
+  %b1 = block {
+    %11:mat3x3<f16> = construct %v1, %v2, %v3, %v4, %v5, %v6, %v7, %v8, %v9
+    ret %11
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%v1:f16, %v2:f16, %v3:f16, %v4:f16, %v5:f16, %v6:f16, %v7:f16, %v8:f16, %v9:f16):mat3x3<f16> -> %b1 {
+  %b1 = block {
+    %11:vec3<f16> = construct %v1, %v2, %v3
+    %12:vec3<f16> = construct %v4, %v5, %v6
+    %13:vec3<f16> = construct %v7, %v8, %v9
+    %14:mat3x3<f16> = construct %11, %12, %13
+    ret %14
+  }
+}
+)";
+
+    Run(VectorizeScalarMatrixConstructors);
+
+    EXPECT_EQ(expect, str());
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 5c6a9d7..a5b0bc4 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -43,6 +43,7 @@
 #include "src/tint/lang/core/ir/transform/preserve_padding.h"
 #include "src/tint/lang/core/ir/transform/robustness.h"
 #include "src/tint/lang/core/ir/transform/std140.h"
+#include "src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h"
 #include "src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h"
 #include "src/tint/lang/spirv/writer/common/option_builder.h"
 #include "src/tint/lang/spirv/writer/raise/builtin_polyfill.h"
@@ -125,6 +126,7 @@
     RUN_TRANSFORM(core::ir::transform::AddEmptyEntryPoint, module);
     RUN_TRANSFORM(core::ir::transform::Bgra8UnormPolyfill, module);
     RUN_TRANSFORM(core::ir::transform::BlockDecoratedStructs, module);
+    RUN_TRANSFORM(core::ir::transform::VectorizeScalarMatrixConstructors, module);
 
     // CombineAccessInstructions must come after DirectVariableAccess and BlockDecoratedStructs.
     // We run this transform as some Qualcomm drivers struggle with partial access chains that