[tint][ir] Serialize struct types

Change-Id: I9564415b2dcf021b688fc915f0ab3a0a25e5be4d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/164882
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/binary/decode.cc b/src/tint/lang/core/ir/binary/decode.cc
index ca40071..14d7285 100644
--- a/src/tint/lang/core/ir/binary/decode.cc
+++ b/src/tint/lang/core/ir/binary/decode.cc
@@ -315,6 +315,8 @@
                 return CreateTypeMatrix(type_in.matrix());
             case pb::Type::KindCase::kPointer:
                 return CreateTypePointer(type_in.pointer());
+            case pb::Type::KindCase::kStruct:
+                return CreateTypeStruct(type_in.struct_());
             case pb::Type::KindCase::kArray:
                 return CreateTypeArray(type_in.array());
             case pb::Type::KindCase::kAtomic:
@@ -366,6 +368,55 @@
         return mod_out_.Types().ptr(address_space, store_ty, access);
     }
 
+    const type::Struct* CreateTypeStruct(const pb::TypeStruct& struct_in) {
+        Vector<const core::type::StructMember*, 8> members_out;
+        uint32_t offset = 0;
+        for (auto& member_in : struct_in.member()) {
+            auto symbol = mod_out_.symbols.Register(member_in.name());
+            auto* type = Type(member_in.type());
+            auto index = static_cast<uint32_t>(members_out.Length());
+            auto align = member_in.align();
+            auto size = member_in.size();
+            core::type::StructMemberAttributes attributes_out{};
+            if (member_in.has_attributes()) {
+                auto& attributes_in = member_in.attributes();
+                if (attributes_in.has_location()) {
+                    attributes_out.location = attributes_in.location();
+                }
+                if (attributes_in.has_index()) {
+                    attributes_out.index = attributes_in.index();
+                }
+                if (attributes_in.has_color()) {
+                    attributes_out.color = attributes_in.color();
+                }
+                if (attributes_in.has_builtin()) {
+                    attributes_out.builtin = BuiltinValue(attributes_in.builtin());
+                }
+                if (attributes_in.has_interpolation()) {
+                    auto& interpolation_in = attributes_in.interpolation();
+                    attributes_out.interpolation = core::Interpolation{
+                        InterpolationType(interpolation_in.type()),
+                        InterpolationSampling::kUndefined,
+                    };
+                    if (interpolation_in.has_sampling()) {
+                        attributes_out.interpolation->sampling =
+                            InterpolationSampling(interpolation_in.sampling());
+                    }
+                }
+                if (attributes_in.has_invariant()) {
+                    attributes_out.invariant = attributes_in.invariant();
+                }
+            }
+            offset = RoundUp(align, offset);
+            auto* member_out = mod_out_.Types().Get<core::type::StructMember>(
+                symbol, type, index, offset, align, size, std::move(attributes_out));
+            offset += size;
+            members_out.Push(member_out);
+        }
+        auto name = mod_out_.symbols.Register(struct_in.name());
+        return mod_out_.Types().Struct(name, std::move(members_out));
+    }
+
     const type::Array* CreateTypeArray(const pb::TypeArray& array_in) {
         auto* element = Type(array_in.element());
         uint32_t stride = static_cast<uint32_t>(array_in.stride());
@@ -576,6 +627,75 @@
                 return core::ir::BinaryOp::kAdd;
         }
     }
+
+    core::InterpolationType InterpolationType(pb::InterpolationType in) {
+        switch (in) {
+            case pb::InterpolationType::flat:
+                return core::InterpolationType::kFlat;
+            case pb::InterpolationType::linear:
+                return core::InterpolationType::kLinear;
+            case pb::InterpolationType::perspective:
+                return core::InterpolationType::kPerspective;
+            default:
+                break;
+        }
+        TINT_ICE() << "invalid InterpolationType: " << in;
+        return core::InterpolationType::kFlat;
+    }
+
+    core::InterpolationSampling InterpolationSampling(pb::InterpolationSampling in) {
+        switch (in) {
+            case pb::InterpolationSampling::center:
+                return core::InterpolationSampling::kCenter;
+            case pb::InterpolationSampling::centroid:
+                return core::InterpolationSampling::kCentroid;
+            case pb::InterpolationSampling::sample:
+                return core::InterpolationSampling::kSample;
+            default:
+                break;
+        }
+        TINT_ICE() << "invalid InterpolationSampling: " << in;
+        return core::InterpolationSampling::kCenter;
+    }
+
+    core::BuiltinValue BuiltinValue(pb::BuiltinValue in) {
+        switch (in) {
+            case pb::BuiltinValue::point_size:
+                return core::BuiltinValue::kPointSize;
+            case pb::BuiltinValue::frag_depth:
+                return core::BuiltinValue::kFragDepth;
+            case pb::BuiltinValue::front_facing:
+                return core::BuiltinValue::kFrontFacing;
+            case pb::BuiltinValue::global_invocation_id:
+                return core::BuiltinValue::kGlobalInvocationId;
+            case pb::BuiltinValue::instance_index:
+                return core::BuiltinValue::kInstanceIndex;
+            case pb::BuiltinValue::local_invocation_id:
+                return core::BuiltinValue::kLocalInvocationId;
+            case pb::BuiltinValue::local_invocation_index:
+                return core::BuiltinValue::kLocalInvocationIndex;
+            case pb::BuiltinValue::num_workgroups:
+                return core::BuiltinValue::kNumWorkgroups;
+            case pb::BuiltinValue::position:
+                return core::BuiltinValue::kPosition;
+            case pb::BuiltinValue::sample_index:
+                return core::BuiltinValue::kSampleIndex;
+            case pb::BuiltinValue::sample_mask:
+                return core::BuiltinValue::kSampleMask;
+            case pb::BuiltinValue::subgroup_invocation_id:
+                return core::BuiltinValue::kSubgroupInvocationId;
+            case pb::BuiltinValue::subgroup_size:
+                return core::BuiltinValue::kSubgroupSize;
+            case pb::BuiltinValue::vertex_index:
+                return core::BuiltinValue::kVertexIndex;
+            case pb::BuiltinValue::workgroup_id:
+                return core::BuiltinValue::kWorkgroupId;
+            default:
+                break;
+        }
+        TINT_ICE() << "invalid BuiltinValue: " << in;
+        return core::BuiltinValue::kPointSize;
+    }
 };
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/binary/encode.cc b/src/tint/lang/core/ir/binary/encode.cc
index ab18fe9..be11d5b 100644
--- a/src/tint/lang/core/ir/binary/encode.cc
+++ b/src/tint/lang/core/ir/binary/encode.cc
@@ -29,6 +29,8 @@
 
 #include <utility>
 
+#include "src/tint/lang/core/builtin_fn.h"
+#include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/core/constant/composite.h"
 #include "src/tint/lang/core/constant/scalar.h"
 #include "src/tint/lang/core/constant/splat.h"
@@ -240,6 +242,7 @@
                 [&](const core::type::Vector* v) { TypeVector(*type_out.mutable_vector(), v); },
                 [&](const core::type::Matrix* m) { TypeMatrix(*type_out.mutable_matrix(), m); },
                 [&](const core::type::Pointer* m) { TypePointer(*type_out.mutable_pointer(), m); },
+                [&](const core::type::Struct* s) { TypeStruct(*type_out.mutable_struct_(), s); },
                 [&](const core::type::Array* m) { TypeArray(*type_out.mutable_array(), m); },
                 TINT_ICE_ON_NO_MATCH);
 
@@ -265,6 +268,42 @@
         pointer_out.set_access(Access(pointer_in->Access()));
     }
 
+    void TypeStruct(pb::TypeStruct& struct_out, const core::type::Struct* struct_in) {
+        struct_out.set_name(struct_in->Name().Name());
+        for (auto* member_in : struct_in->Members()) {
+            auto& member_out = *struct_out.add_member();
+            member_out.set_name(member_in->Name().Name());
+            member_out.set_type(Type(member_in->Type()));
+            member_out.set_size(member_in->Size());
+            member_out.set_align(member_in->Align());
+
+            auto& attrs_in = member_in->Attributes();
+            if (attrs_in.location) {
+                member_out.mutable_attributes()->set_location(*attrs_in.location);
+            }
+            if (attrs_in.index) {
+                member_out.mutable_attributes()->set_index(*attrs_in.index);
+            }
+            if (attrs_in.color) {
+                member_out.mutable_attributes()->set_color(*attrs_in.color);
+            }
+            if (attrs_in.builtin) {
+                member_out.mutable_attributes()->set_builtin(BuiltinValue(*attrs_in.builtin));
+            }
+            if (auto& interpolation_in = attrs_in.interpolation) {
+                auto& interpolation_out = *member_out.mutable_attributes()->mutable_interpolation();
+                interpolation_out.set_type(InterpolationType(interpolation_in->type));
+                if (interpolation_in->sampling != InterpolationSampling::kUndefined) {
+                    interpolation_out.set_sampling(
+                        InterpolationSampling(interpolation_in->sampling));
+                }
+            }
+            if (attrs_in.invariant) {
+                member_out.mutable_attributes()->set_invariant(true);
+            }
+        }
+    }
+
     void TypeArray(pb::TypeArray& array_out, const core::type::Array* array_in) {
         array_out.set_element(Type(array_in->ElemType()));
         array_out.set_stride(array_in->Stride());
@@ -458,6 +497,75 @@
         TINT_ICE() << "invalid BinaryOp: " << in;
         return pb::BinaryOp::add_;
     }
+
+    pb::InterpolationType InterpolationType(core::InterpolationType in) {
+        switch (in) {
+            case core::InterpolationType::kFlat:
+                return pb::InterpolationType::flat;
+            case core::InterpolationType::kLinear:
+                return pb::InterpolationType::linear;
+            case core::InterpolationType::kPerspective:
+                return pb::InterpolationType::perspective;
+            default:
+                break;
+        }
+        TINT_ICE() << "invalid InterpolationType: " << in;
+        return pb::InterpolationType::flat;
+    }
+
+    pb::InterpolationSampling InterpolationSampling(core::InterpolationSampling in) {
+        switch (in) {
+            case core::InterpolationSampling::kCenter:
+                return pb::InterpolationSampling::center;
+            case core::InterpolationSampling::kCentroid:
+                return pb::InterpolationSampling::centroid;
+            case core::InterpolationSampling::kSample:
+                return pb::InterpolationSampling::sample;
+            default:
+                break;
+        }
+        TINT_ICE() << "invalid InterpolationSampling: " << in;
+        return pb::InterpolationSampling::center;
+    }
+
+    pb::BuiltinValue BuiltinValue(core::BuiltinValue in) {
+        switch (in) {
+            case core::BuiltinValue::kPointSize:
+                return pb::BuiltinValue::point_size;
+            case core::BuiltinValue::kFragDepth:
+                return pb::BuiltinValue::frag_depth;
+            case core::BuiltinValue::kFrontFacing:
+                return pb::BuiltinValue::front_facing;
+            case core::BuiltinValue::kGlobalInvocationId:
+                return pb::BuiltinValue::global_invocation_id;
+            case core::BuiltinValue::kInstanceIndex:
+                return pb::BuiltinValue::instance_index;
+            case core::BuiltinValue::kLocalInvocationId:
+                return pb::BuiltinValue::local_invocation_id;
+            case core::BuiltinValue::kLocalInvocationIndex:
+                return pb::BuiltinValue::local_invocation_index;
+            case core::BuiltinValue::kNumWorkgroups:
+                return pb::BuiltinValue::num_workgroups;
+            case core::BuiltinValue::kPosition:
+                return pb::BuiltinValue::position;
+            case core::BuiltinValue::kSampleIndex:
+                return pb::BuiltinValue::sample_index;
+            case core::BuiltinValue::kSampleMask:
+                return pb::BuiltinValue::sample_mask;
+            case core::BuiltinValue::kSubgroupInvocationId:
+                return pb::BuiltinValue::subgroup_invocation_id;
+            case core::BuiltinValue::kSubgroupSize:
+                return pb::BuiltinValue::subgroup_size;
+            case core::BuiltinValue::kVertexIndex:
+                return pb::BuiltinValue::vertex_index;
+            case core::BuiltinValue::kWorkgroupId:
+                return pb::BuiltinValue::workgroup_id;
+            default:
+                break;
+        }
+        TINT_ICE() << "invalid BuiltinValue: " << in;
+        return pb::BuiltinValue::point_size;
+    }
 };
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/binary/ir.proto b/src/tint/lang/core/ir/binary/ir.proto
index fff8d02..fc272de 100644
--- a/src/tint/lang/core/ir/binary/ir.proto
+++ b/src/tint/lang/core/ir/binary/ir.proto
@@ -48,7 +48,8 @@
         TypeMatrix matrix = 3;
         TypeArray array = 4;
         TypePointer pointer = 5;
-        uint32 atomic = 6;  // Module.types
+        TypeStruct struct = 6;
+        uint32 atomic = 7;  // Module.types
         // TODO: textures, samplers
     }
 }
@@ -86,6 +87,19 @@
     AccessControl access = 3;
 }
 
+message TypeStruct {
+    string name = 1;
+    repeated TypeStructMember member = 2;
+}
+
+message TypeStructMember {
+    string name = 1;
+    uint32 type = 2;
+    uint32 size = 3;
+    uint32 align = 4;
+    optional AttributesStructMember attributes = 5;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Values
 ////////////////////////////////////////////////////////////////////////////////
@@ -244,6 +258,23 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Attributes
+////////////////////////////////////////////////////////////////////////////////
+message AttributesStructMember {
+    optional uint32 location = 1;
+    optional uint32 index = 2;
+    optional uint32 color = 3;
+    optional BuiltinValue builtin = 4;
+    optional AttributesInterpolation interpolation = 5;
+    optional bool invariant = 6;
+}
+
+message AttributesInterpolation {
+    InterpolationType type = 1;
+    optional InterpolationSampling sampling = 2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Enums
 ////////////////////////////////////////////////////////////////////////////////
 enum AddressSpace {
@@ -286,3 +317,151 @@
     shift_left = 14;
     shift_right = 15;
 }
+
+enum InterpolationType {
+    flat = 0;
+    linear = 1;
+    perspective = 2;
+}
+
+enum InterpolationSampling {
+    center = 0;
+    centroid = 1;
+    sample = 2;
+}
+
+enum BuiltinValue {
+    point_size = 0;
+    frag_depth = 1;
+    front_facing = 2;
+    global_invocation_id = 3;
+    instance_index = 4;
+    local_invocation_id = 5;
+    local_invocation_index = 6;
+    num_workgroups = 7;
+    position = 8;
+    sample_index = 9;
+    sample_mask = 10;
+    subgroup_invocation_id = 11;
+    subgroup_size = 12;
+    vertex_index = 13;
+    workgroup_id = 14;
+}
+
+enum BuiltinFn {
+    abs = 0;
+    acos = 1;
+    acosh = 2;
+    all = 3;
+    any = 4;
+    array_length = 5;
+    asin = 6;
+    asinh = 7;
+    atan = 8;
+    atan2 = 9;
+    atanh = 10;
+    ceil = 11;
+    clamp = 12;
+    cos = 13;
+    cosh = 14;
+    count_leading_zeros = 15;
+    count_one_bits = 16;
+    count_trailing_zeros = 17;
+    cross = 18;
+    degrees = 19;
+    determinant = 20;
+    distance = 21;
+    dot = 22;
+    dot4i8_packed = 23;
+    dot4u8_packed = 24;
+    dpdx = 25;
+    dpdx_coarse = 26;
+    dpdx_fine = 27;
+    dpdy = 28;
+    dpdy_coarse = 29;
+    dpdy_fine = 30;
+    exp = 31;
+    exp2 = 32;
+    extract_bits = 33;
+    face_forward = 34;
+    first_leading_bit = 35;
+    first_trailing_bit = 36;
+    floor = 37;
+    fma = 38;
+    fract = 39;
+    frexp = 40;
+    fwidth = 41;
+    fwidth_coarse = 42;
+    fwidth_fine = 43;
+    insert_bits = 44;
+    inverse_sqrt = 45;
+    ldexp = 46;
+    length = 47;
+    log = 48;
+    log2 = 49;
+    max = 50;
+    min = 51;
+    mix = 52;
+    modf = 53;
+    normalize = 54;
+    pack2x16_float = 55;
+    pack2x16_snorm = 56;
+    pack2x16_unorm = 57;
+    pack4x8_snorm = 58;
+    pack4x8_unorm = 59;
+    pow = 60;
+    quantize_to_f16 = 61;
+    radians = 62;
+    reflect = 63;
+    refract = 64;
+    reverse_bits = 65;
+    round = 66;
+    saturate = 67;
+    select = 68;
+    sign = 69;
+    sin = 70;
+    sinh = 71;
+    smoothstep = 72;
+    sqrt = 73;
+    step = 74;
+    storage_barrier = 75;
+    tan = 76;
+    tanh = 77;
+    transpose = 78;
+    trunc = 79;
+    unpack2x16_float = 80;
+    unpack2x16_snorm = 81;
+    unpack2x16_unorm = 82;
+    unpack4x8_snorm = 83;
+    unpack4x8_unorm = 84;
+    workgroup_barrier = 85;
+    texture_barrier = 86;
+    texture_dimensions = 87;
+    texture_gather = 88;
+    texture_gather_compare = 89;
+    texture_num_layers = 90;
+    texture_num_levels = 91;
+    texture_num_samples = 92;
+    texture_sample = 93;
+    texture_sample_bias = 94;
+    texture_sample_compare = 95;
+    texture_sample_compare_level = 96;
+    texture_sample_grad = 97;
+    texture_sample_level = 98;
+    texture_sample_base_clamp_to_edge = 99;
+    texture_store = 100;
+    texture_load = 101;
+    atomic_load = 102;
+    atomic_store = 103;
+    atomic_add = 104;
+    atomic_sub = 105;
+    atomic_max = 106;
+    atomic_min = 107;
+    atomic_and = 108;
+    atomic_or = 109;
+    atomic_xor = 110;
+    atomic_exchange = 111;
+    atomic_compare_exchange_weak = 112;
+    subgroup_ballot = 113;
+    subgroup_broadcast = 114;
+}
diff --git a/src/tint/lang/core/ir/binary/roundtrip_test.cc b/src/tint/lang/core/ir/binary/roundtrip_test.cc
index 57ade4e..120eff7 100644
--- a/src/tint/lang/core/ir/binary/roundtrip_test.cc
+++ b/src/tint/lang/core/ir/binary/roundtrip_test.cc
@@ -87,6 +87,7 @@
     });
     RUN_TEST();
 }
+
 ////////////////////////////////////////////////////////////////////////////////
 // Functions
 ////////////////////////////////////////////////////////////////////////////////
@@ -123,6 +124,115 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Types
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(IRBinaryRoundtripTest, bool) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, bool>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, i32) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, i32>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, u32) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, u32>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, f32) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, f32>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, f16) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, f16>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, vec2_f32) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, vec2<f32>>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, vec3_i32) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, vec3<i32>>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, vec4_bool) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, vec4<bool>>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, mat4x2_f32) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, vec4<mat4x2<f32>>>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, mat2x4_f16) {
+    b.Append(b.ir.root_block, [&] { b.Var<private_, vec4<mat2x4<f16>>>(); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, ptr_function_f32_read_write) {
+    auto p = b.FunctionParam<ptr<function, f32, read_write>>("p");
+    auto* fn = b.Function("Function", ty.void_());
+    fn->SetParams({p});
+    b.Append(fn->Block(), [&] { b.Return(fn); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, ptr_workgroup_i32_read) {
+    auto p = b.FunctionParam<ptr<workgroup, i32, read>>("p");
+    auto* fn = b.Function("Function", ty.void_());
+    fn->SetParams({p});
+    b.Append(fn->Block(), [&] { b.Return(fn); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, struct) {
+    Vector members{
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("a"), ty.i32(), /* index */ 0u,
+                                         /* offset */ 0u, /* align */ 4u, /* size */ 4u,
+                                         type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("b"), ty.f32(), /* index */ 1u,
+                                         /* offset */ 4u, /* align */ 4u, /* size */ 32u,
+                                         type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("c"), ty.u32(), /* index */ 2u,
+                                         /* offset */ 36u, /* align */ 4u, /* size */ 4u,
+                                         type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("d"), ty.u32(), /* index */ 3u,
+                                         /* offset */ 64u, /* align */ 32u, /* size */ 4u,
+                                         type::StructMemberAttributes{}),
+    };
+    auto* S = ty.Struct(b.ir.symbols.New("S"), std::move(members));
+    b.Append(b.ir.root_block, [&] { b.Var(ty.ptr<function, read_write>(S)); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, StructMemberAttributes) {
+    type::StructMemberAttributes attrs{};
+    attrs.location = 1;
+    attrs.index = 2;
+    attrs.color = 3;
+    attrs.builtin = core::BuiltinValue::kFragDepth;
+    attrs.interpolation = core::Interpolation{
+        core::InterpolationType::kLinear,
+        core::InterpolationSampling::kCentroid,
+    };
+    attrs.invariant = true;
+    Vector members{
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("a"), ty.i32(), /* index */ 0u,
+                                         /* offset */ 0u, /* align */ 4u, /* size */ 4u, attrs),
+    };
+    auto* S = ty.Struct(b.ir.symbols.New("S"), std::move(members));
+    b.Append(b.ir.root_block, [&] { b.Var(ty.ptr<function, read_write>(S)); });
+    RUN_TEST();
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Instructions
 ////////////////////////////////////////////////////////////////////////////////
 TEST_F(IRBinaryRoundtripTest, Return) {
diff --git a/src/tint/lang/core/type/manager.cc b/src/tint/lang/core/type/manager.cc
index a3473ca..50c1a39 100644
--- a/src/tint/lang/core/type/manager.cc
+++ b/src/tint/lang/core/type/manager.cc
@@ -192,8 +192,18 @@
     return Get<core::type::Pointer>(address_space, subtype, access);
 }
 
+core::type::Struct* Manager::Struct(Symbol name, VectorRef<const StructMember*> members) {
+    uint32_t max_align = 0u;
+    for (const auto& m : members) {
+        max_align = std::max(max_align, m->Align());
+    }
+    uint32_t size = members.Back()->Offset() + members.Back()->Size();
+    return Get<core::type::Struct>(name, std::move(members), max_align,
+                                   tint::RoundUp(max_align, size), size);
+}
+
 core::type::Struct* Manager::Struct(Symbol name, VectorRef<StructMemberDesc> md) {
-    tint::Vector<const core::type::StructMember*, 4> members;
+    tint::Vector<const StructMember*, 4> members;
     uint32_t current_size = 0u;
     uint32_t max_align = 0u;
     for (const auto& m : md) {
@@ -205,8 +215,8 @@
         current_size = offset + m.type->Size();
         max_align = std::max(max_align, align);
     }
-    return Get<core::type::Struct>(name, members, max_align, tint::RoundUp(max_align, current_size),
-                                   current_size);
+    return Get<core::type::Struct>(name, std::move(members), max_align,
+                                   tint::RoundUp(max_align, current_size), current_size);
 }
 
 }  // namespace tint::core::type
diff --git a/src/tint/lang/core/type/manager.h b/src/tint/lang/core/type/manager.h
index 8f361da..d88e084 100644
--- a/src/tint/lang/core/type/manager.h
+++ b/src/tint/lang/core/type/manager.h
@@ -451,6 +451,12 @@
 
     /// Create a new structure declaration.
     /// @param name the name of the structure
+    /// @param members the list of structure members
+    /// @returns the structure type
+    core::type::Struct* Struct(Symbol name, VectorRef<const StructMember*> members);
+
+    /// Create a new structure declaration.
+    /// @param name the name of the structure
     /// @param members the list of structure member descriptors
     /// @returns the structure type
     core::type::Struct* Struct(Symbol name, VectorRef<StructMemberDesc> members);