Commit 4a28c1d5c3

Andrew Kelley <andrew@ziglang.org>
2022-07-12 07:34:03
stage2: lower each struct field type, align, init separately
Previously, struct types, alignment values, and initialization expressions were all lowered into the same ZIR body, which caused false positive "depends on itself" errors when the initialization expression depended on the size of the struct. This also uses ResultLoc.coerced_ty for struct field alignment and initialization values. The resulting ZIR encoding ends up being roughly the same, neither smaller nor larger than previously. Closes #12029
1 parent 3708e26
src/AstGen.zig
@@ -4148,7 +4148,6 @@ fn structDeclInner(
             .src_node = node,
             .layout = layout,
             .fields_len = 0,
-            .body_len = 0,
             .decls_len = 0,
             .known_non_opv = false,
             .known_comptime_only = false,
@@ -4192,6 +4191,19 @@ fn structDeclInner(
     var wip_members = try WipMembers.init(gpa, &astgen.scratch, decl_count, field_count, bits_per_field, max_field_size);
     defer wip_members.deinit();
 
+    // We will use the scratch buffer, starting here, for the bodies:
+    //    bodies: { // for every fields_len
+    //        field_type_body_inst: Inst, // for each field_type_body_len
+    //        align_body_inst: Inst, // for each align_body_len
+    //        init_body_inst: Inst, // for each init_body_len
+    //    }
+    // Note that the scratch buffer is simultaneously being used by WipMembers, however
+    // it will not access any elements beyond this point in the ArrayList. It also
+    // accesses via the ArrayList items field so it can handle the scratch buffer being
+    // reallocated.
+    // No defer needed here because it is handled by `wip_members.deinit()` above.
+    const bodies_start = astgen.scratch.items.len;
+
     var known_non_opv = false;
     var known_comptime_only = false;
     for (container_decl.ast.members) |member_node| {
@@ -4203,20 +4215,18 @@ fn structDeclInner(
         const field_name = try astgen.identAsString(member.ast.name_token);
         wip_members.appendToField(field_name);
 
+        const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
+        wip_members.appendToField(doc_comment_index);
+
         if (member.ast.type_expr == 0) {
             return astgen.failTok(member.ast.name_token, "struct field missing type", .{});
         }
 
         const field_type = try typeExpr(&block_scope, &namespace.base, member.ast.type_expr);
-        wip_members.appendToField(@enumToInt(field_type));
-
-        const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
-        wip_members.appendToField(doc_comment_index);
-
+        const have_type_body = !block_scope.isEmpty();
         const have_align = member.ast.align_expr != 0;
         const have_value = member.ast.value_expr != 0;
         const is_comptime = member.comptime_token != null;
-        const unused = false;
 
         if (!is_comptime) {
             known_non_opv = known_non_opv or
@@ -4224,36 +4234,59 @@ fn structDeclInner(
             known_comptime_only = known_comptime_only or
                 nodeImpliesComptimeOnly(tree, member.ast.type_expr);
         }
-        wip_members.nextField(bits_per_field, .{ have_align, have_value, is_comptime, unused });
+        wip_members.nextField(bits_per_field, .{ have_align, have_value, is_comptime, have_type_body });
+
+        if (have_type_body) {
+            if (!block_scope.endsWithNoReturn()) {
+                _ = try block_scope.addBreak(.break_inline, decl_inst, field_type);
+            }
+            const body = block_scope.instructionsSlice();
+            const old_scratch_len = astgen.scratch.items.len;
+            try astgen.scratch.ensureUnusedCapacity(gpa, countBodyLenAfterFixups(astgen, body));
+            appendBodyWithFixupsArrayList(astgen, &astgen.scratch, body);
+            wip_members.appendToField(@intCast(u32, astgen.scratch.items.len - old_scratch_len));
+            block_scope.instructions.items.len = block_scope.instructions_top;
+        } else {
+            wip_members.appendToField(@enumToInt(field_type));
+        }
 
         if (have_align) {
             if (layout == .Packed) {
                 try astgen.appendErrorNode(member.ast.align_expr, "unable to override alignment of packed struct fields", .{});
             }
-            const align_inst = try expr(&block_scope, &namespace.base, align_rl, member.ast.align_expr);
-            wip_members.appendToField(@enumToInt(align_inst));
+            const align_ref = try expr(&block_scope, &namespace.base, coerced_align_rl, member.ast.align_expr);
+            if (!block_scope.endsWithNoReturn()) {
+                _ = try block_scope.addBreak(.break_inline, decl_inst, align_ref);
+            }
+            const body = block_scope.instructionsSlice();
+            const old_scratch_len = astgen.scratch.items.len;
+            try astgen.scratch.ensureUnusedCapacity(gpa, countBodyLenAfterFixups(astgen, body));
+            appendBodyWithFixupsArrayList(astgen, &astgen.scratch, body);
+            wip_members.appendToField(@intCast(u32, astgen.scratch.items.len - old_scratch_len));
+            block_scope.instructions.items.len = block_scope.instructions_top;
         }
+
         if (have_value) {
-            const rl: ResultLoc = if (field_type == .none) .none else .{ .ty = field_type };
+            const rl: ResultLoc = if (field_type == .none) .none else .{ .coerced_ty = field_type };
 
             const default_inst = try expr(&block_scope, &namespace.base, rl, member.ast.value_expr);
-            wip_members.appendToField(@enumToInt(default_inst));
+            if (!block_scope.endsWithNoReturn()) {
+                _ = try block_scope.addBreak(.break_inline, decl_inst, default_inst);
+            }
+            const body = block_scope.instructionsSlice();
+            const old_scratch_len = astgen.scratch.items.len;
+            try astgen.scratch.ensureUnusedCapacity(gpa, countBodyLenAfterFixups(astgen, body));
+            appendBodyWithFixupsArrayList(astgen, &astgen.scratch, body);
+            wip_members.appendToField(@intCast(u32, astgen.scratch.items.len - old_scratch_len));
+            block_scope.instructions.items.len = block_scope.instructions_top;
         } else if (member.comptime_token) |comptime_token| {
             return astgen.failTok(comptime_token, "comptime field without default initialization value", .{});
         }
     }
 
-    if (!block_scope.isEmpty()) {
-        _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
-    }
-
-    const body = block_scope.instructionsSlice();
-    const body_len = astgen.countBodyLenAfterFixups(body);
-
     try gz.setStruct(decl_inst, .{
         .src_node = node,
         .layout = layout,
-        .body_len = body_len,
         .fields_len = field_count,
         .decls_len = decl_count,
         .known_non_opv = known_non_opv,
@@ -4263,10 +4296,11 @@ fn structDeclInner(
     wip_members.finishBits(bits_per_field);
     const decls_slice = wip_members.declsSlice();
     const fields_slice = wip_members.fieldsSlice();
-    try astgen.extra.ensureUnusedCapacity(gpa, decls_slice.len + body_len + fields_slice.len);
+    const bodies_slice = astgen.scratch.items[bodies_start..];
+    try astgen.extra.ensureUnusedCapacity(gpa, decls_slice.len + fields_slice.len + bodies_slice.len);
     astgen.extra.appendSliceAssumeCapacity(decls_slice);
-    astgen.appendBodyWithFixups(body);
     astgen.extra.appendSliceAssumeCapacity(fields_slice);
+    astgen.extra.appendSliceAssumeCapacity(bodies_slice);
 
     block_scope.unstack();
     try gz.addNamespaceCaptures(&namespace);
@@ -10981,7 +11015,6 @@ const GenZir = struct {
 
     fn setStruct(gz: *GenZir, inst: Zir.Inst.Index, args: struct {
         src_node: Ast.Node.Index,
-        body_len: u32,
         fields_len: u32,
         decls_len: u32,
         layout: std.builtin.Type.ContainerLayout,
@@ -10998,9 +11031,6 @@ const GenZir = struct {
             const node_offset = gz.nodeIndexToRelative(args.src_node);
             astgen.extra.appendAssumeCapacity(@bitCast(u32, node_offset));
         }
-        if (args.body_len != 0) {
-            astgen.extra.appendAssumeCapacity(args.body_len);
-        }
         if (args.fields_len != 0) {
             astgen.extra.appendAssumeCapacity(args.fields_len);
         }
@@ -11013,7 +11043,6 @@ const GenZir = struct {
                 .opcode = .struct_decl,
                 .small = @bitCast(u16, Zir.Inst.StructDecl.Small{
                     .has_src_node = args.src_node != 0,
-                    .has_body_len = args.body_len != 0,
                     .has_fields_len = args.fields_len != 0,
                     .has_decls_len = args.decls_len != 0,
                     .known_non_opv = args.known_non_opv,
src/Module.zig
@@ -916,13 +916,14 @@ pub const Struct = struct {
     /// one possible value.
     known_non_opv: bool,
     requires_comptime: PropertyBoolean = .unknown,
+    have_field_inits: bool = false,
 
     pub const Fields = std.StringArrayHashMapUnmanaged(Field);
 
     /// The `Type` and `Value` memory is owned by the arena of the Struct's owner_decl.
     pub const Field = struct {
         /// Uses `noreturn` to indicate `anytype`.
-        /// undefined until `status` is `have_field_types` or `have_layout`.
+        /// undefined until `status` is >= `have_field_types`.
         ty: Type,
         /// Uses `unreachable_value` to indicate no default.
         default_val: Value,
src/print_zir.zig
@@ -1227,12 +1227,6 @@ const Writer = struct {
             break :blk src_node;
         } else null;
 
-        const body_len = if (small.has_body_len) blk: {
-            const body_len = self.code.extra[extra_index];
-            extra_index += 1;
-            break :blk body_len;
-        } else 0;
-
         const fields_len = if (small.has_fields_len) blk: {
             const fields_len = self.code.extra[extra_index];
             extra_index += 1;
@@ -1262,71 +1256,114 @@ const Writer = struct {
             try stream.writeAll("}, ");
         }
 
-        const body = self.code.extra[extra_index..][0..body_len];
-        extra_index += body.len;
-
         if (fields_len == 0) {
-            assert(body.len == 0);
             try stream.writeAll("{}, {})");
         } else {
-            const prev_parent_decl_node = self.parent_decl_node;
-            if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
-            try self.writeBracedDecl(stream, body);
-            try stream.writeAll(", {\n");
-
-            self.indent += 2;
             const bits_per_field = 4;
             const fields_per_u32 = 32 / bits_per_field;
             const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
-            var bit_bag_index: usize = extra_index;
-            extra_index += bit_bags_count;
-            var cur_bit_bag: u32 = undefined;
-            var field_i: u32 = 0;
-            while (field_i < fields_len) : (field_i += 1) {
-                if (field_i % fields_per_u32 == 0) {
-                    cur_bit_bag = self.code.extra[bit_bag_index];
-                    bit_bag_index += 1;
-                }
-                const has_align = @truncate(u1, cur_bit_bag) != 0;
-                cur_bit_bag >>= 1;
-                const has_default = @truncate(u1, cur_bit_bag) != 0;
-                cur_bit_bag >>= 1;
-                const is_comptime = @truncate(u1, cur_bit_bag) != 0;
-                cur_bit_bag >>= 1;
-                const unused = @truncate(u1, cur_bit_bag) != 0;
-                cur_bit_bag >>= 1;
+            const Field = struct {
+                doc_comment_index: u32,
+                type_len: u32 = 0,
+                align_len: u32 = 0,
+                init_len: u32 = 0,
+                field_type: Zir.Inst.Ref = .none,
+                name: u32,
+                is_comptime: bool,
+            };
+            const fields = try self.arena.alloc(Field, fields_len);
+            {
+                var bit_bag_index: usize = extra_index;
+                extra_index += bit_bags_count;
+                var cur_bit_bag: u32 = undefined;
+                var field_i: u32 = 0;
+                while (field_i < fields_len) : (field_i += 1) {
+                    if (field_i % fields_per_u32 == 0) {
+                        cur_bit_bag = self.code.extra[bit_bag_index];
+                        bit_bag_index += 1;
+                    }
+                    const has_align = @truncate(u1, cur_bit_bag) != 0;
+                    cur_bit_bag >>= 1;
+                    const has_default = @truncate(u1, cur_bit_bag) != 0;
+                    cur_bit_bag >>= 1;
+                    const is_comptime = @truncate(u1, cur_bit_bag) != 0;
+                    cur_bit_bag >>= 1;
+                    const has_type_body = @truncate(u1, cur_bit_bag) != 0;
+                    cur_bit_bag >>= 1;
+
+                    const field_name = self.code.extra[extra_index];
+                    extra_index += 1;
+                    const doc_comment_index = self.code.extra[extra_index];
+                    extra_index += 1;
 
-                _ = unused;
+                    fields[field_i] = .{
+                        .doc_comment_index = doc_comment_index,
+                        .is_comptime = is_comptime,
+                        .name = field_name,
+                    };
 
-                const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]);
-                extra_index += 1;
-                const field_type = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
-                extra_index += 1;
-                const doc_comment_index = self.code.extra[extra_index];
-                extra_index += 1;
+                    if (has_type_body) {
+                        fields[field_i].type_len = self.code.extra[extra_index];
+                    } else {
+                        fields[field_i].field_type = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
+                    }
+                    extra_index += 1;
 
-                try self.writeDocComment(stream, doc_comment_index);
+                    if (has_align) {
+                        fields[field_i].align_len = self.code.extra[extra_index];
+                        extra_index += 1;
+                    }
+
+                    if (has_default) {
+                        fields[field_i].init_len = self.code.extra[extra_index];
+                        extra_index += 1;
+                    }
+                }
+            }
+
+            const prev_parent_decl_node = self.parent_decl_node;
+            if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
+            try stream.writeAll("{\n");
+            self.indent += 2;
 
+            for (fields) |field| {
+                const field_name = self.code.nullTerminatedString(field.name);
+
+                try self.writeDocComment(stream, field.doc_comment_index);
                 try stream.writeByteNTimes(' ', self.indent);
-                try self.writeFlag(stream, "comptime ", is_comptime);
+                try self.writeFlag(stream, "comptime ", field.is_comptime);
                 try stream.print("{}: ", .{std.zig.fmtId(field_name)});
-                try self.writeInstRef(stream, field_type);
+                if (field.field_type != .none) {
+                    try self.writeInstRef(stream, field.field_type);
+                }
 
-                if (has_align) {
-                    const align_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
-                    extra_index += 1;
+                if (field.type_len > 0) {
+                    const body = self.code.extra[extra_index..][0..field.type_len];
+                    extra_index += body.len;
+                    self.indent += 2;
+                    try self.writeBracedDecl(stream, body);
+                    self.indent -= 2;
+                }
 
+                if (field.align_len > 0) {
+                    const body = self.code.extra[extra_index..][0..field.align_len];
+                    extra_index += body.len;
+                    self.indent += 2;
                     try stream.writeAll(" align(");
-                    try self.writeInstRef(stream, align_ref);
+                    try self.writeBracedDecl(stream, body);
                     try stream.writeAll(")");
+                    self.indent -= 2;
                 }
-                if (has_default) {
-                    const default_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
-                    extra_index += 1;
 
+                if (field.init_len > 0) {
+                    const body = self.code.extra[extra_index..][0..field.init_len];
+                    extra_index += body.len;
+                    self.indent += 2;
                     try stream.writeAll(" = ");
-                    try self.writeInstRef(stream, default_ref);
+                    try self.writeBracedDecl(stream, body);
+                    self.indent -= 2;
                 }
+
                 try stream.writeAll(",\n");
             }
 
src/Sema.zig
@@ -1862,13 +1862,15 @@ fn failWithOwnedErrorMsg(sema: *Sema, block: *Block, err_msg: *Module.ErrorMsg)
     return error.AnalysisFail;
 }
 
-pub fn resolveAlign(
+const align_ty = Type.u29;
+
+fn analyzeAsAlign(
     sema: *Sema,
     block: *Block,
     src: LazySrcLoc,
-    zir_ref: Zir.Inst.Ref,
+    air_ref: Air.Inst.Ref,
 ) !u32 {
-    const alignment_big = try sema.resolveInt(block, src, zir_ref, Type.initTag(.u29));
+    const alignment_big = try sema.analyzeAsInt(block, src, air_ref, align_ty);
     const alignment = @intCast(u32, alignment_big); // We coerce to u16 in the prev line.
     if (alignment == 0) return sema.fail(block, src, "alignment must be >= 1", .{});
     if (!std.math.isPowerOfTwo(alignment)) {
@@ -1879,6 +1881,16 @@ pub fn resolveAlign(
     return alignment;
 }
 
+pub fn resolveAlign(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    zir_ref: Zir.Inst.Ref,
+) !u32 {
+    const air_ref = try sema.resolveInst(zir_ref);
+    return analyzeAsAlign(sema, block, src, air_ref);
+}
+
 fn resolveInt(
     sema: *Sema,
     block: *Block,
@@ -1886,8 +1898,18 @@ fn resolveInt(
     zir_ref: Zir.Inst.Ref,
     dest_ty: Type,
 ) !u64 {
-    const air_inst = try sema.resolveInst(zir_ref);
-    const coerced = try sema.coerce(block, dest_ty, air_inst, src);
+    const air_ref = try sema.resolveInst(zir_ref);
+    return analyzeAsInt(sema, block, src, air_ref, dest_ty);
+}
+
+fn analyzeAsInt(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    air_ref: Air.Inst.Ref,
+    dest_ty: Type,
+) !u64 {
+    const coerced = try sema.coerce(block, dest_ty, air_ref, src);
     const val = try sema.resolveConstValue(block, src, coerced);
     const target = sema.mod.getTarget();
     return (try val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?;
@@ -2097,7 +2119,6 @@ pub fn analyzeStructDecl(
 
     var extra_index: usize = extended.operand;
     extra_index += @boolToInt(small.has_src_node);
-    extra_index += @boolToInt(small.has_body_len);
     extra_index += @boolToInt(small.has_fields_len);
     const decls_len = if (small.has_decls_len) blk: {
         const decls_len = sema.code.extra[extra_index];
@@ -24858,12 +24879,6 @@ fn resolveTypeFieldsStruct(
 
     struct_obj.status = .field_types_wip;
     try semaStructFields(sema.mod, struct_obj);
-
-    if (struct_obj.fields.count() == 0) {
-        struct_obj.status = .have_layout;
-    } else {
-        struct_obj.status = .have_field_types;
-    }
 }
 
 fn resolveTypeFieldsUnion(
@@ -24954,13 +24969,7 @@ fn resolveInferredErrorSetTy(
     }
 }
 
-fn semaStructFields(
-    mod: *Module,
-    struct_obj: *Module.Struct,
-) CompileError!void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
+fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void {
     const gpa = mod.gpa;
     const decl_index = struct_obj.owner_decl;
     const zir = struct_obj.namespace.file_scope.zir;
@@ -24972,12 +24981,6 @@ fn semaStructFields(
     const src = LazySrcLoc.nodeOffset(struct_obj.node_offset);
     extra_index += @boolToInt(small.has_src_node);
 
-    const body_len = if (small.has_body_len) blk: {
-        const body_len = zir.extra[extra_index];
-        extra_index += 1;
-        break :blk body_len;
-    } else 0;
-
     const fields_len = if (small.has_fields_len) blk: {
         const fields_len = zir.extra[extra_index];
         extra_index += 1;
@@ -24995,12 +24998,10 @@ fn semaStructFields(
     while (decls_it.next()) |_| {}
     extra_index = decls_it.extra_index;
 
-    const body = zir.extra[extra_index..][0..body_len];
     if (fields_len == 0) {
-        assert(body.len == 0);
+        struct_obj.status = .have_layout;
         return;
     }
-    extra_index += body.len;
 
     const decl = mod.declPtr(decl_index);
     var decl_arena = decl.value_arena.?.promote(gpa);
@@ -25042,106 +25043,150 @@ fn semaStructFields(
         block_scope.params.deinit(gpa);
     }
 
-    if (body.len != 0) {
-        try sema.analyzeBody(&block_scope, body);
-    }
+    try struct_obj.fields.ensureTotalCapacity(decl_arena_allocator, fields_len);
 
-    try wip_captures.finalize();
+    const Field = struct {
+        type_body_len: u32 = 0,
+        align_body_len: u32 = 0,
+        init_body_len: u32 = 0,
+        type_ref: Air.Inst.Ref = .none,
+    };
+    const fields = try sema.arena.alloc(Field, fields_len);
+    var any_inits = false;
 
-    try struct_obj.fields.ensureTotalCapacity(decl_arena_allocator, fields_len);
+    {
+        const bits_per_field = 4;
+        const fields_per_u32 = 32 / bits_per_field;
+        const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
+        const flags_index = extra_index;
+        var bit_bag_index: usize = flags_index;
+        extra_index += bit_bags_count;
+        var cur_bit_bag: u32 = undefined;
+        var field_i: u32 = 0;
+        while (field_i < fields_len) : (field_i += 1) {
+            if (field_i % fields_per_u32 == 0) {
+                cur_bit_bag = zir.extra[bit_bag_index];
+                bit_bag_index += 1;
+            }
+            const has_align = @truncate(u1, cur_bit_bag) != 0;
+            cur_bit_bag >>= 1;
+            const has_init = @truncate(u1, cur_bit_bag) != 0;
+            cur_bit_bag >>= 1;
+            const is_comptime = @truncate(u1, cur_bit_bag) != 0;
+            cur_bit_bag >>= 1;
+            const has_type_body = @truncate(u1, cur_bit_bag) != 0;
+            cur_bit_bag >>= 1;
+
+            const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
+            extra_index += 1;
+            extra_index += 1; // doc_comment
 
-    const bits_per_field = 4;
-    const fields_per_u32 = 32 / bits_per_field;
-    const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
-    var bit_bag_index: usize = extra_index;
-    extra_index += bit_bags_count;
-    var cur_bit_bag: u32 = undefined;
-    var field_i: u32 = 0;
-    while (field_i < fields_len) : (field_i += 1) {
-        if (field_i % fields_per_u32 == 0) {
-            cur_bit_bag = zir.extra[bit_bag_index];
-            bit_bag_index += 1;
-        }
-        const has_align = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        const has_default = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        const is_comptime = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        const unused = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
+            fields[field_i] = .{};
 
-        _ = unused;
+            if (has_type_body) {
+                fields[field_i].type_body_len = zir.extra[extra_index];
+            } else {
+                fields[field_i].type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+            }
+            extra_index += 1;
 
-        const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
-        extra_index += 1;
-        const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
-        extra_index += 1;
+            // This string needs to outlive the ZIR code.
+            const field_name = try decl_arena_allocator.dupe(u8, field_name_zir);
 
-        // doc_comment
-        extra_index += 1;
+            const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
+            if (gop.found_existing) {
+                const msg = msg: {
+                    const tree = try sema.getAstTree(&block_scope);
+                    const field_src = enumFieldSrcLoc(decl, tree.*, struct_obj.node_offset, field_i);
+                    const msg = try sema.errMsg(&block_scope, field_src, "duplicate struct field: '{s}'", .{field_name});
+                    errdefer msg.destroy(gpa);
 
-        // This string needs to outlive the ZIR code.
-        const field_name = try decl_arena_allocator.dupe(u8, field_name_zir);
-        const field_ty: Type = if (field_type_ref == .none)
-            Type.initTag(.noreturn)
-        else
-            // TODO: if we need to report an error here, use a source location
-            // that points to this type expression rather than the struct.
-            // But only resolve the source location if we need to emit a compile error.
-            try sema.resolveType(&block_scope, src, field_type_ref);
+                    const prev_field_index = struct_obj.fields.getIndex(field_name).?;
+                    const prev_field_src = enumFieldSrcLoc(decl, tree.*, struct_obj.node_offset, prev_field_index);
+                    try sema.mod.errNoteNonLazy(prev_field_src.toSrcLoc(decl), msg, "other field here", .{});
+                    try sema.errNote(&block_scope, src, msg, "struct declared here", .{});
+                    break :msg msg;
+                };
+                return sema.failWithOwnedErrorMsg(&block_scope, msg);
+            }
+            gop.value_ptr.* = .{
+                .ty = Type.initTag(.noreturn),
+                .abi_align = 0,
+                .default_val = Value.initTag(.unreachable_value),
+                .is_comptime = is_comptime,
+                .offset = undefined,
+            };
+
+            if (has_align) {
+                fields[field_i].align_body_len = zir.extra[extra_index];
+                extra_index += 1;
+            }
+            if (has_init) {
+                fields[field_i].init_body_len = zir.extra[extra_index];
+                extra_index += 1;
+                any_inits = true;
+            }
+        }
+    }
+
+    // Next we do only types and alignments, saving the inits for a second pass,
+    // so that init values may depend on type layout.
+    const bodies_index = extra_index;
 
+    for (fields) |zir_field, i| {
         // TODO emit compile errors for invalid field types
         // such as arrays and pointers inside packed structs.
-
+        const field_ty: Type = ty: {
+            if (zir_field.type_ref != .none) {
+                // TODO: if we need to report an error here, use a source location
+                // that points to this type expression rather than the struct.
+                // But only resolve the source location if we need to emit a compile error.
+                break :ty try sema.resolveType(&block_scope, src, zir_field.type_ref);
+            }
+            assert(zir_field.type_body_len != 0);
+            const body = zir.extra[extra_index..][0..zir_field.type_body_len];
+            extra_index += body.len;
+            const ty_ref = try sema.resolveBody(&block_scope, body, struct_obj.zir_index);
+            break :ty try sema.analyzeAsType(&block_scope, src, ty_ref);
+        };
         if (field_ty.tag() == .generic_poison) {
             return error.GenericPoison;
         }
 
-        const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
-        if (gop.found_existing) {
-            const msg = msg: {
-                const tree = try sema.getAstTree(&block_scope);
-                const field_src = enumFieldSrcLoc(decl, tree.*, struct_obj.node_offset, field_i);
-                const msg = try sema.errMsg(&block_scope, field_src, "duplicate struct field: '{s}'", .{field_name});
-                errdefer msg.destroy(gpa);
+        const field = &struct_obj.fields.values()[i];
+        field.ty = try field_ty.copy(decl_arena_allocator);
 
-                const prev_field_index = struct_obj.fields.getIndex(field_name).?;
-                const prev_field_src = enumFieldSrcLoc(decl, tree.*, struct_obj.node_offset, prev_field_index);
-                try sema.mod.errNoteNonLazy(prev_field_src.toSrcLoc(decl), msg, "other field here", .{});
-                try sema.errNote(&block_scope, src, msg, "struct declared here", .{});
-                break :msg msg;
-            };
-            return sema.failWithOwnedErrorMsg(&block_scope, msg);
+        if (zir_field.align_body_len > 0) {
+            const body = zir.extra[extra_index..][0..zir_field.align_body_len];
+            extra_index += body.len;
+            const align_ref = try sema.resolveBody(&block_scope, body, struct_obj.zir_index);
+            field.abi_align = try sema.analyzeAsAlign(&block_scope, src, align_ref);
         }
-        gop.value_ptr.* = .{
-            .ty = try field_ty.copy(decl_arena_allocator),
-            .abi_align = 0,
-            .default_val = Value.initTag(.unreachable_value),
-            .is_comptime = is_comptime,
-            .offset = undefined,
-        };
 
-        if (has_align) {
-            const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
-            extra_index += 1;
-            // TODO: if we need to report an error here, use a source location
-            // that points to this alignment expression rather than the struct.
-            // But only resolve the source location if we need to emit a compile error.
-            gop.value_ptr.abi_align = try sema.resolveAlign(&block_scope, src, align_ref);
-        }
-        if (has_default) {
-            const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
-            extra_index += 1;
-            const default_inst = try sema.resolveInst(default_ref);
-            // TODO: if we need to report an error here, use a source location
-            // that points to this default value expression rather than the struct.
-            // But only resolve the source location if we need to emit a compile error.
-            const default_val = (try sema.resolveMaybeUndefVal(&block_scope, src, default_inst)) orelse
-                return sema.failWithNeededComptime(&block_scope, src);
-            gop.value_ptr.default_val = try default_val.copy(decl_arena_allocator);
+        extra_index += zir_field.init_body_len;
+    }
+
+    struct_obj.status = .have_field_types;
+
+    if (any_inits) {
+        extra_index = bodies_index;
+        for (fields) |zir_field, i| {
+            extra_index += zir_field.type_body_len;
+            extra_index += zir_field.align_body_len;
+            if (zir_field.init_body_len > 0) {
+                const body = zir.extra[extra_index..][0..zir_field.init_body_len];
+                extra_index += body.len;
+                const init = try sema.resolveBody(&block_scope, body, struct_obj.zir_index);
+                const field = &struct_obj.fields.values()[i];
+                const coerced = try sema.coerce(&block_scope, field.ty, init, src);
+                const default_val = (try sema.resolveMaybeUndefVal(&block_scope, src, coerced)) orelse
+                    return sema.failWithNeededComptime(&block_scope, src);
+                field.default_val = try default_val.copy(decl_arena_allocator);
+            }
         }
     }
+
+    struct_obj.have_field_inits = true;
 }
 
 fn semaUnionFields(block: *Block, mod: *Module, union_obj: *Module.Union) CompileError!void {
src/Zir.zig
@@ -3093,16 +3093,15 @@ pub const Inst = struct {
 
     /// Trailing:
     /// 0. src_node: i32, // if has_src_node
-    /// 1. body_len: u32, // if has_body_len
-    /// 2. fields_len: u32, // if has_fields_len
-    /// 3. decls_len: u32, // if has_decls_len
-    /// 4. decl_bits: u32 // for every 8 decls
+    /// 1. fields_len: u32, // if has_fields_len
+    /// 2. decls_len: u32, // if has_decls_len
+    /// 3. decl_bits: u32 // for every 8 decls
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding decl is pub
     ///      0b00X0: whether corresponding decl is exported
     ///      0b0X00: whether corresponding decl has an align expression
     ///      0bX000: whether corresponding decl has a linksection or an address space expression
-    /// 5. decl: { // for every decls_len
+    /// 4. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
     ///        name: u32, // null terminated string index
@@ -3120,32 +3119,35 @@ pub const Inst = struct {
     ///            address_space: Ref,
     ///        }
     ///    }
-    /// 6. inst: Index // for every body_len
-    /// 7. flags: u32 // for every 8 fields
+    /// 5. flags: u32 // for every 8 fields
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding field has an align expression
     ///      0b00X0: whether corresponding field has a default expression
     ///      0b0X00: whether corresponding field is comptime
-    ///      0bX000: unused
-    /// 8. fields: { // for every fields_len
+    ///      0bX000: whether corresponding field has a type expression
+    /// 6. fields: { // for every fields_len
     ///        field_name: u32,
-    ///        field_type: Ref,
-    ///        - if none, means `anytype`.
     ///        doc_comment: u32, // 0 if no doc comment
-    ///        align: Ref, // if corresponding bit is set
-    ///        default_value: Ref, // if corresponding bit is set
+    ///        field_type: Ref, // if corresponding bit is not set. none means anytype.
+    ///        field_type_body_len: u32, // if corresponding bit is set
+    ///        align_body_len: u32, // if corresponding bit is set
+    ///        init_body_len: u32, // if corresponding bit is set
+    ///    }
+    /// 7. bodies: { // for every fields_len
+    ///        field_type_body_inst: Inst, // for each field_type_body_len
+    ///        align_body_inst: Inst, // for each align_body_len
+    ///        init_body_inst: Inst, // for each init_body_len
     ///    }
     pub const StructDecl = struct {
         pub const Small = packed struct {
             has_src_node: bool,
-            has_body_len: bool,
             has_fields_len: bool,
             has_decls_len: bool,
             known_non_opv: bool,
             known_comptime_only: bool,
             name_strategy: NameStrategy,
             layout: std.builtin.Type.ContainerLayout,
-            _: u6 = undefined,
+            _: u7 = undefined,
         };
     };
 
@@ -3594,7 +3596,6 @@ pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator {
                     const small = @bitCast(Inst.StructDecl.Small, extended.small);
                     var extra_index: usize = extended.operand;
                     extra_index += @boolToInt(small.has_src_node);
-                    extra_index += @boolToInt(small.has_body_len);
                     extra_index += @boolToInt(small.has_fields_len);
                     const decls_len = if (small.has_decls_len) decls_len: {
                         const decls_len = zir.extra[extra_index];
test/behavior/struct.zig
@@ -1358,3 +1358,17 @@ test "store to comptime field" {
         s.a.a = 1;
     }
 }
+
+test "struct field init value is size of the struct" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+
+    const namespace = struct {
+        const S = extern struct {
+            size: u8 = @sizeOf(S),
+            blah: u16,
+        };
+    };
+    var s: namespace.S = .{ .blah = 1234 };
+    try expect(s.size == 4);
+}