[ir] Fix offset of struct members

When a struct member has an offset that is not automatically satisfied
by its alignment, we need to add a @size() attribute to the previous
member.

Change-Id: Ibb9588f5bbe15666c852e83f618c7c1914823599
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/249275
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index 298247c..ea5516c 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -1076,15 +1076,37 @@
         }
 
         auto n = structs_.GetOrAdd(s, [&] {
-            auto members = tint::Transform<8>(s->Members(), [&](const core::type::StructMember* m) {
+            uint32_t current_offset = 0;
+            TINT_ASSERT(s->Members().Length() > 0 && s->Members()[0]->Offset() == 0);
+
+            Vector<const ast::StructMember*, 8> members;
+            for (const auto* m : s->Members()) {
                 auto ty = Type(m->Type());
                 const auto& ir_attrs = m->Attributes();
                 Vector<const ast::Attribute*, 4> ast_attrs;
+
+                TINT_ASSERT(current_offset == m->Offset());
+
+                // If the next member requires an offset that is not automatically satisfied by
+                // its required alignment, we will need to increase the size of this member.
+                uint32_t size = m->Size();
+                if (m->Index() < s->Members().Length() - 1) {
+                    auto* next_member = s->Members()[m->Index() + 1];
+                    auto next_offset = tint::RoundUp(next_member->Align(), current_offset + size);
+                    auto next_member_required_offset = next_member->Offset();
+                    if (next_offset < next_member_required_offset) {
+                        uint32_t new_size = next_member_required_offset - current_offset;
+                        TINT_ASSERT(new_size > size);
+                        size = new_size;
+                    }
+                    current_offset = next_member_required_offset;
+                }
+
                 if (m->Type()->Align() != m->Align()) {
                     ast_attrs.Push(b.MemberAlign(u32(m->Align())));
                 }
-                if (m->Type()->Size() != m->Size()) {
-                    ast_attrs.Push(b.MemberSize(u32(m->Size())));
+                if (m->Type()->Size() != size) {
+                    ast_attrs.Push(b.MemberSize(u32(size)));
                 }
                 if (auto location = ir_attrs.location) {
                     ast_attrs.Push(b.Location(u32(*location)));
@@ -1111,8 +1133,8 @@
                 if (ir_attrs.invariant) {
                     ast_attrs.Push(b.Invariant());
                 }
-                return b.Member(m->Name().NameView(), ty, std::move(ast_attrs));
-            });
+                members.Push(b.Member(m->Name().NameView(), ty, std::move(ast_attrs)));
+            }
 
             // TODO(crbug.com/tint/1902): Emit structure attributes
             Vector<const ast::Attribute*, 2> attrs;
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index 0afbc3b..bbb5f7e 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -3414,6 +3414,36 @@
 )");
 }
 
+TEST_F(IRToProgramTest, StructMemberOffset) {
+    auto* S = ty.Struct(mod.symbols.New("S"),
+                        Vector{
+                            ty.Get<core::type::StructMember>(mod.symbols.New("a"), ty.i32(), 0u, 0u,
+                                                             4u, 4u, core::IOAttributes{}),
+                            ty.Get<core::type::StructMember>(mod.symbols.New("b"), ty.u32(), 1u,
+                                                             64u, 4u, 4u, core::IOAttributes{}),
+                            ty.Get<core::type::StructMember>(mod.symbols.New("c"), ty.f32(), 2u,
+                                                             76u, 4u, 4u, core::IOAttributes{}),
+                        });
+
+    auto* fn = b.Function("f", ty.void_());
+    auto* x = b.FunctionParam("x", S);
+    fn->SetParams({x});
+    b.Append(fn->Block(), [&] { b.Return(fn); });
+
+    EXPECT_WGSL(R"(
+struct S {
+  @size(64u)
+  a : i32,
+  @size(12u)
+  b : u32,
+  c : f32,
+}
+
+fn f(x : S) {
+}
+)");
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // chromium_internal_graphite
 ////////////////////////////////////////////////////////////////////////////////