Commit 8ebfdc14f6

Andrew Kelley <andrew@ziglang.org>
2021-04-02 07:34:40
stage2: implement structs in the frontend
New ZIR instructions: * struct_decl_packed * struct_decl_extern New TZIR instruction: struct_field_ptr Introduce `Module.Struct`. It uses `Value` to store default values and abi alignments. Implemented Sema.analyzeStructFieldPtr and zirStructDecl. Some stuff I changed from `@panic("TODO")` to `log.warn("TODO")`. It's becoming more clear that we need the lazy value mechanism soon; Type is becoming unruly, and some of these functions have too much logic given that they don't have any context for memory management or error reporting.
1 parent c66b481
src/AstGen.zig
@@ -1322,6 +1322,8 @@ fn blockExprStmts(
                         .switch_capture_else_ref,
                         .struct_init_empty,
                         .struct_decl,
+                        .struct_decl_packed,
+                        .struct_decl_extern,
                         .union_decl,
                         .enum_decl,
                         .opaque_decl,
@@ -1789,8 +1791,13 @@ fn containerDecl(
 
     switch (token_tags[container_decl.ast.main_token]) {
         .keyword_struct => {
+            const tag = if (container_decl.layout_token) |t| switch (token_tags[t]) {
+                .keyword_packed => zir.Inst.Tag.struct_decl_packed,
+                .keyword_extern => zir.Inst.Tag.struct_decl_extern,
+                else => unreachable,
+            } else zir.Inst.Tag.struct_decl;
             if (container_decl.ast.members.len == 0) {
-                const result = try gz.addPlNode(.struct_decl, node, zir.Inst.StructDecl{
+                const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{
                     .fields_len = 0,
                 });
                 return rvalue(gz, scope, rl, result, node);
@@ -1856,7 +1863,7 @@ fn containerDecl(
             const empty_slot_count = 16 - ((member_index - 1) % 16);
             cur_bit_bag >>= @intCast(u5, empty_slot_count * 2);
 
-            const result = try gz.addPlNode(.struct_decl, node, zir.Inst.StructDecl{
+            const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{
                 .fields_len = @intCast(u32, container_decl.ast.members.len),
             });
             try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
src/codegen.zig
@@ -910,6 +910,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .ret => return self.genRet(inst.castTag(.ret).?),
                 .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
                 .store => return self.genStore(inst.castTag(.store).?),
+                .struct_field_ptr => return self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
                 .sub => return self.genSub(inst.castTag(.sub).?),
                 .subwrap => return self.genSubWrap(inst.castTag(.subwrap).?),
                 .switchbr => return self.genSwitch(inst.castTag(.switchbr).?),
@@ -1403,6 +1404,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return .none;
         }
 
+        fn genStructFieldPtr(self: *Self, inst: *ir.Inst.StructFieldPtr) !MCValue {
+            return self.fail(inst.base.src, "TODO implement codegen struct_field_ptr", .{});
+        }
+
         fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
             if (inst.base.isUnused())
src/ir.zig
@@ -137,6 +137,8 @@ pub const Inst = struct {
         wrap_errunion_err,
         xor,
         switchbr,
+        /// Given a pointer to a struct and a field index, returns a pointer to the field.
+        struct_field_ptr,
 
         pub fn Type(tag: Tag) type {
             return switch (tag) {
@@ -204,6 +206,7 @@ pub const Inst = struct {
                 .constant => Constant,
                 .loop => Loop,
                 .varptr => VarPtr,
+                .struct_field_ptr => StructFieldPtr,
                 .switchbr => SwitchBr,
                 .dbg_stmt => DbgStmt,
             };
@@ -552,6 +555,27 @@ pub const Inst = struct {
         }
     };
 
+    pub const StructFieldPtr = struct {
+        pub const base_tag = Tag.struct_field_ptr;
+
+        base: Inst,
+        struct_ptr: *Inst,
+        field_index: usize,
+
+        pub fn operandCount(self: *const StructFieldPtr) usize {
+            return 1;
+        }
+        pub fn getOperand(self: *const StructFieldPtr, index: usize) ?*Inst {
+            var i = index;
+
+            if (i < 1)
+                return self.struct_ptr;
+            i -= 1;
+
+            return null;
+        }
+    };
+
     pub const SwitchBr = struct {
         pub const base_tag = Tag.switchbr;
 
@@ -794,6 +818,10 @@ const DumpTzir = struct {
                         try dtz.findConst(arg);
                     }
                 },
+                .struct_field_ptr => {
+                    const struct_field_ptr = inst.castTag(.struct_field_ptr).?;
+                    try dtz.findConst(struct_field_ptr.struct_ptr);
+                },
 
                 // TODO fill out this debug printing
                 .assembly,
@@ -1067,6 +1095,18 @@ const DumpTzir = struct {
                     }
                 },
 
+                .struct_field_ptr => {
+                    const struct_field_ptr = inst.castTag(.struct_field_ptr).?;
+                    const kinky = try dtz.writeInst(writer, struct_field_ptr.struct_ptr);
+                    if (kinky != null) {
+                        try writer.print("{d}) // Instruction does not dominate all uses!\n", .{
+                            struct_field_ptr.field_index,
+                        });
+                    } else {
+                        try writer.print("{d})\n", .{struct_field_ptr.field_index});
+                    }
+                },
+
                 // TODO fill out this debug printing
                 .assembly,
                 .constant,
src/Module.zig
@@ -356,6 +356,25 @@ pub const ErrorSet = struct {
     names_ptr: [*]const []const u8,
 };
 
+/// Represents the data that a struct declaration provides.
+pub const Struct = struct {
+    owner_decl: *Decl,
+    /// Set of field names in declaration order.
+    fields: std.StringArrayHashMapUnmanaged(Field),
+    /// Represents the declarations inside this struct.
+    container: Scope.Container,
+
+    /// Offset from Decl node index, points to the struct AST node.
+    node_offset: i32,
+
+    pub const Field = struct {
+        ty: Type,
+        abi_align: Value,
+        /// Uses `unreachable_value` to indicate no default.
+        default_val: Value,
+    };
+};
+
 /// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
 /// Extern functions do not have this data structure; they are represented by
 /// the `Decl` only, with a `Value` tag of `extern_fn`.
@@ -822,6 +841,7 @@ pub const Scope = struct {
             try block.instructions.append(block.sema.gpa, &inst.base);
             return &inst.base;
         }
+
         pub fn addBr(
             scope_block: *Scope.Block,
             src: LazySrcLoc,
@@ -920,6 +940,27 @@ pub const Scope = struct {
             try block.instructions.append(block.sema.gpa, &inst.base);
             return &inst.base;
         }
+
+        pub fn addStructFieldPtr(
+            block: *Scope.Block,
+            src: LazySrcLoc,
+            ty: Type,
+            struct_ptr: *ir.Inst,
+            field_index: u32,
+        ) !*ir.Inst {
+            const inst = try block.sema.arena.create(ir.Inst.StructFieldPtr);
+            inst.* = .{
+                .base = .{
+                    .tag = .struct_field_ptr,
+                    .ty = ty,
+                    .src = src,
+                },
+                .struct_ptr = struct_ptr,
+                .field_index = field_index,
+            };
+            try block.instructions.append(block.sema.gpa, &inst.base);
+            return &inst.base;
+        }
     };
 
     /// This is a temporary structure; references to it are valid only
src/Sema.zig
@@ -261,7 +261,9 @@ pub fn analyzeBody(
             .xor => try sema.zirBitwise(block, inst, .xor),
             .struct_init_empty => try sema.zirStructInitEmpty(block, inst),
 
-            .struct_decl => try sema.zirStructDecl(block, inst),
+            .struct_decl => try sema.zirStructDecl(block, inst, .Auto),
+            .struct_decl_packed => try sema.zirStructDecl(block, inst, .Packed),
+            .struct_decl_extern => try sema.zirStructDecl(block, inst, .Extern),
             .enum_decl => try sema.zirEnumDecl(block, inst),
             .union_decl => try sema.zirUnionDecl(block, inst),
             .opaque_decl => try sema.zirOpaqueDecl(block, inst),
@@ -520,21 +522,98 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) In
     return sema.mod.fail(&block.base, sema.src, "TODO implement zirCoerceResultPtr", .{});
 }
 
-fn zirStructDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirStructDecl(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: zir.Inst.Index,
+    layout: std.builtin.TypeInfo.ContainerLayout,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const gpa = sema.gpa;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
-    const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+    const extra = sema.code.extraData(zir.Inst.StructDecl, inst_data.payload_index);
+    const fields_len = extra.data.fields_len;
+    const bit_bags_count = std.math.divCeil(usize, fields_len, 16) catch unreachable;
+
+    var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+    errdefer new_decl_arena.deinit();
+
+    var fields_map: std.StringArrayHashMapUnmanaged(Module.Struct.Field) = .{};
+    try fields_map.ensureCapacity(&new_decl_arena.allocator, fields_len);
 
-    return sema.mod.fail(&block.base, sema.src, "TODO implement zirStructDecl", .{});
+    {
+        var field_index: usize = extra.end + bit_bags_count;
+        var bit_bag_index: usize = extra.end;
+        var cur_bit_bag: u32 = undefined;
+        var field_i: u32 = 0;
+        while (field_i < fields_len) : (field_i += 1) {
+            if (field_i % 16 == 0) {
+                cur_bit_bag = sema.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 field_name_zir = sema.code.nullTerminatedString(sema.code.extra[field_index]);
+            field_index += 1;
+            const field_type_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[field_index]);
+            field_index += 1;
+
+            // This string needs to outlive the ZIR code.
+            const field_name = try new_decl_arena.allocator.dupe(u8, field_name_zir);
+            // 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.
+            const field_ty = try sema.resolveType(block, src, field_type_ref);
+
+            const gop = fields_map.getOrPutAssumeCapacity(field_name);
+            assert(!gop.found_existing);
+            gop.entry.value = .{
+                .ty = field_ty,
+                .abi_align = Value.initTag(.abi_align_default),
+                .default_val = Value.initTag(.unreachable_value),
+            };
+
+            if (has_align) {
+                const align_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[field_index]);
+                field_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.entry.value.abi_align = (try sema.resolveInstConst(block, src, align_ref)).val;
+            }
+            if (has_default) {
+                const default_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[field_index]);
+                field_index += 1;
+                // 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.
+                gop.entry.value.default_val = (try sema.resolveInstConst(block, src, default_ref)).val;
+            }
+        }
+    }
 
-    //const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
-    //    .ty = decl_ty,
-    //    .val = decl_val,
-    //});
-    //return sema.analyzeDeclVal(block, src, new_decl);
+    const struct_obj = try new_decl_arena.allocator.create(Module.Struct);
+    const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj);
+    struct_obj.* = .{
+        .owner_decl = sema.owner_decl,
+        .fields = fields_map,
+        .node_offset = inst_data.src_node,
+        .container = .{
+            .ty = struct_ty,
+            .file_scope = block.getFileScope(),
+        },
+    };
+    const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
+        .ty = Type.initTag(.type),
+        .val = try Value.Tag.ty.create(gpa, struct_ty),
+    });
+    return sema.analyzeDeclVal(block, src, new_decl);
 }
 
 fn zirEnumDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@@ -763,7 +842,7 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Ind
     const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
     const instrs = sema.code.extra[extra.end..][0..extra.data.body_len];
 
-    return sema.mod.fail(&block.base, src, "TODO implement zirValidateStructInitPtr", .{});
+    log.warn("TODO implement zirValidateStructInitPtr (compile errors for missing/dupe fields)", .{});
 }
 
 fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
@@ -4141,11 +4220,40 @@ fn namedFieldPtr(
                 else => return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{child_type}),
             }
         },
+        .Struct => return sema.analyzeStructFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
         else => {},
     }
     return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty});
 }
 
+fn analyzeStructFieldPtr(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    struct_ptr: *Inst,
+    field_name: []const u8,
+    field_name_src: LazySrcLoc,
+    elem_ty: Type,
+) InnerError!*Inst {
+    const mod = sema.mod;
+    const arena = sema.arena;
+    assert(elem_ty.zigTypeTag() == .Struct);
+
+    const struct_obj = elem_ty.castTag(.@"struct").?.data;
+
+    const field_index = struct_obj.fields.getIndex(field_name) orelse {
+        // TODO note: struct S declared here
+        return mod.fail(&block.base, field_name_src, "no field named '{s}' in struct '{}'", .{
+            field_name, elem_ty,
+        });
+    };
+    const field = struct_obj.fields.entries.items[field_index].value;
+    const ptr_field_ty = try mod.simplePtrType(arena, field.ty, true, .One);
+    // TODO comptime field access
+    try sema.requireRuntimeBlock(block, src);
+    return block.addStructFieldPtr(src, ptr_field_ty, struct_ptr, @intCast(u32, field_index));
+}
+
 fn elemPtr(
     sema: *Sema,
     block: *Scope.Block,
src/type.zig
@@ -4,6 +4,7 @@ const assert = std.debug.assert;
 const Allocator = std.mem.Allocator;
 const Target = std.Target;
 const Module = @import("Module.zig");
+const log = std.log.scoped(.Type);
 
 /// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication.
 /// It's important for this type to be small.
@@ -94,6 +95,7 @@ pub const Type = extern union {
 
             .empty_struct => return .Struct,
             .empty_struct_literal => return .Struct,
+            .@"struct" => return .Struct,
 
             .var_args_param => unreachable, // can be any type
         }
@@ -611,7 +613,7 @@ pub const Type = extern union {
             .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
             .error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
             .empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope),
-
+            .@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct),
             .@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque),
         }
     }
@@ -675,6 +677,7 @@ pub const Type = extern union {
                 .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"),
 
                 .empty_struct, .empty_struct_literal => return out_stream.writeAll("struct {}"),
+                .@"struct" => return out_stream.writeAll("(struct)"),
                 .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"),
                 .const_slice_u8 => return out_stream.writeAll("[]const u8"),
                 .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"),
@@ -940,6 +943,18 @@ pub const Type = extern union {
             .error_set,
             .error_set_single,
             => true,
+
+            .@"struct" => {
+                // TODO introduce lazy value mechanism
+                const struct_obj = self.castTag(.@"struct").?.data;
+                for (struct_obj.fields.entries.items) |entry| {
+                    if (entry.value.ty.hasCodeGenBits())
+                        return true;
+                } else {
+                    return false;
+                }
+            },
+
             // TODO lazy types
             .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0,
             .array_u8 => self.arrayLen() != 0,
@@ -1100,6 +1115,10 @@ pub const Type = extern union {
                 @panic("TODO abiAlignment error union");
             },
 
+            .@"struct" => {
+                @panic("TODO abiAlignment struct");
+            },
+
             .c_void,
             .void,
             .type,
@@ -1144,6 +1163,10 @@ pub const Type = extern union {
             .@"opaque" => unreachable,
             .var_args_param => unreachable,
 
+            .@"struct" => {
+                @panic("TODO abiSize struct");
+            },
+
             .u8,
             .i8,
             .bool,
@@ -1316,6 +1339,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .@"opaque",
@@ -1393,6 +1417,7 @@ pub const Type = extern union {
             .empty_struct,
             .empty_struct_literal,
             .@"opaque",
+            .@"struct",
             .var_args_param,
             => unreachable,
 
@@ -1487,6 +1512,7 @@ pub const Type = extern union {
             .empty_struct_literal,
             .inferred_alloc_const,
             .inferred_alloc_mut,
+            .@"struct",
             .@"opaque",
             .var_args_param,
             => false,
@@ -1564,6 +1590,7 @@ pub const Type = extern union {
             .empty_struct_literal,
             .inferred_alloc_const,
             .inferred_alloc_mut,
+            .@"struct",
             .@"opaque",
             .var_args_param,
             => false,
@@ -1650,6 +1677,7 @@ pub const Type = extern union {
             .empty_struct_literal,
             .inferred_alloc_const,
             .inferred_alloc_mut,
+            .@"struct",
             .@"opaque",
             .var_args_param,
             => false,
@@ -1731,6 +1759,7 @@ pub const Type = extern union {
             .empty_struct_literal,
             .inferred_alloc_const,
             .inferred_alloc_mut,
+            .@"struct",
             .@"opaque",
             .var_args_param,
             => false,
@@ -1792,7 +1821,11 @@ pub const Type = extern union {
             .ErrorUnion => ty = ty.errorUnionChild(),
 
             .Fn => @panic("TODO fn isValidVarType"),
-            .Struct => @panic("TODO struct isValidVarType"),
+            .Struct => {
+                // TODO this is not always correct; introduce lazy value mechanism
+                // and here we need to force a resolve of "type requires comptime".
+                return true;
+            },
             .Union => @panic("TODO union isValidVarType"),
         };
     }
@@ -1850,6 +1883,7 @@ pub const Type = extern union {
             .anyerror_void_error_union => unreachable,
             .error_set => unreachable,
             .error_set_single => unreachable,
+            .@"struct" => unreachable,
             .empty_struct => unreachable,
             .empty_struct_literal => unreachable,
             .inferred_alloc_const => unreachable,
@@ -1999,6 +2033,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2070,6 +2105,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2156,6 +2192,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2238,6 +2275,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2306,6 +2344,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2402,6 +2441,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2519,6 +2559,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2602,6 +2643,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2684,6 +2726,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2766,6 +2809,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2845,6 +2889,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -2924,6 +2969,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -3003,6 +3049,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -3070,6 +3117,11 @@ pub const Type = extern union {
             .var_args_param,
             => return null,
 
+            .@"struct" => {
+                log.warn("TODO implement Type.onePossibleValue for structs", .{});
+                return null;
+            },
+
             .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value),
             .void => return Value.initTag(.void_value),
             .noreturn => return Value.initTag(.unreachable_value),
@@ -3172,6 +3224,7 @@ pub const Type = extern union {
             .anyerror_void_error_union,
             .error_set,
             .error_set_single,
+            .@"struct",
             .empty_struct,
             .empty_struct_literal,
             .inferred_alloc_const,
@@ -3269,6 +3322,7 @@ pub const Type = extern union {
             .empty_struct_literal,
             => unreachable,
 
+            .@"struct" => &self.castTag(.@"struct").?.data.container,
             .empty_struct => self.castTag(.empty_struct).?.data,
             .@"opaque" => &self.castTag(.@"opaque").?.data,
         };
@@ -3421,6 +3475,7 @@ pub const Type = extern union {
         error_set_single,
         empty_struct,
         @"opaque",
+        @"struct",
 
         pub const last_no_payload_tag = Tag.inferred_alloc_const;
         pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -3506,6 +3561,7 @@ pub const Type = extern union {
                 .error_union => Payload.ErrorUnion,
                 .error_set_single => Payload.Name,
                 .@"opaque" => Payload.Opaque,
+                .@"struct" => Payload.Struct,
                 .empty_struct => Payload.ContainerScope,
             };
         }
@@ -3638,6 +3694,11 @@ pub const Type = extern union {
             base: Payload = .{ .tag = .@"opaque" },
             data: Module.Scope.Container,
         };
+
+        pub const Struct = struct {
+            base: Payload = .{ .tag = .@"struct" },
+            data: *Module.Struct,
+        };
     };
 };
 
src/value.zig
@@ -74,6 +74,7 @@ pub const Value = extern union {
         bool_true,
         bool_false,
 
+        abi_align_default,
         empty_struct_value,
         empty_array, // See last_no_payload_tag below.
         // After this, the tag requires a payload.
@@ -165,6 +166,7 @@ pub const Value = extern union {
                 .null_value,
                 .bool_true,
                 .bool_false,
+                .abi_align_default,
                 => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"),
 
                 .int_big_positive,
@@ -320,6 +322,7 @@ pub const Value = extern union {
             .bool_true,
             .bool_false,
             .empty_struct_value,
+            .abi_align_default,
             => unreachable,
 
             .ty => {
@@ -464,8 +467,8 @@ pub const Value = extern union {
             .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
             .const_slice_u8_type => return out_stream.writeAll("[]const u8"),
             .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
+            .abi_align_default => return out_stream.writeAll("(default ABI alignment)"),
 
-            // TODO this should print `NAME{}`
             .empty_struct_value => return out_stream.writeAll("struct {}{}"),
             .null_value => return out_stream.writeAll("null"),
             .undef => return out_stream.writeAll("undefined"),
@@ -627,6 +630,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
         };
     }
@@ -699,6 +703,7 @@ pub const Value = extern union {
             .@"error",
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .undef => unreachable,
@@ -786,6 +791,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .undef => unreachable,
@@ -873,6 +879,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .undef => unreachable,
@@ -988,6 +995,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .zero,
@@ -1079,6 +1087,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .zero,
@@ -1239,6 +1248,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .zero,
@@ -1317,6 +1327,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .zero,
@@ -1452,6 +1463,7 @@ pub const Value = extern union {
             .const_slice_u8_type,
             .enum_literal_type,
             .ty,
+            .abi_align_default,
             => {
                 // Directly return Type.hash, toType can only fail for .int_type.
                 var allocator = std.heap.FixedBufferAllocator.init(&[_]u8{});
@@ -1632,6 +1644,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .ref_val => self.castTag(.ref_val).?.data,
@@ -1719,6 +1732,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .inferred_alloc,
+            .abi_align_default,
             => unreachable,
 
             .empty_array => unreachable, // out of bounds array index
@@ -1822,6 +1836,7 @@ pub const Value = extern union {
             .@"error",
             .error_union,
             .empty_struct_value,
+            .abi_align_default,
             => false,
 
             .undef => unreachable,
@@ -1903,6 +1918,7 @@ pub const Value = extern union {
             .void_value,
             .enum_literal,
             .empty_struct_value,
+            .abi_align_default,
             => null,
 
             .error_union => {
@@ -2009,6 +2025,7 @@ pub const Value = extern union {
             .error_union,
             .empty_struct_value,
             .null_value,
+            .abi_align_default,
             => false,
 
             .undef => unreachable,
src/zir.zig
@@ -274,6 +274,10 @@ pub const Inst = struct {
         /// the field types, defaults, and alignments.
         /// Uses the `pl_node` union field. Payload is `StructDecl`.
         struct_decl,
+        /// Same as `struct_decl`, except has the `packed` layout.
+        struct_decl_packed,
+        /// Same as `struct_decl`, except has the `extern` layout.
+        struct_decl_extern,
         /// A union type definition. Contains references to ZIR instructions for
         /// the field types and optional type tag expression.
         /// Uses the `pl_node` union field. Payload is `UnionDecl`.
@@ -707,6 +711,8 @@ pub const Inst = struct {
                 .coerce_result_ptr,
                 .@"const",
                 .struct_decl,
+                .struct_decl_packed,
+                .struct_decl_extern,
                 .union_decl,
                 .enum_decl,
                 .opaque_decl,
@@ -1655,7 +1661,10 @@ const Writer = struct {
             .condbr_inline,
             => try self.writePlNodeCondBr(stream, inst),
 
-            .struct_decl => try self.writeStructDecl(stream, inst),
+            .struct_decl,
+            .struct_decl_packed,
+            .struct_decl_extern,
+            => try self.writeStructDecl(stream, inst),
 
             .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none),
             .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),