Commit 0cd361219c

Andrew Kelley <andrew@ziglang.org>
2021-08-21 00:23:55
stage2: field type expressions support referencing locals
The big change in this commit is making `semaDecl` resolve the fields if the Decl ends up being a struct or union. It needs to do this while the `Sema` is still in scope, because it will have the resolved AIR instructions that the field type expressions possibly reference. We do this after the decl is populated and set to `complete` so that a `Decl` may reference itself. Everything else is fixes and improvements to make the test suite pass again after making this change. * New AIR instruction: `ptr_elem_ptr` - Implemented for LLVM backend * New Type tag: `type_info` which represents `std.builtin.TypeInfo`. It is used by AstGen for the operand type of `@Type`. * ZIR instruction `set_float_mode` uses `coerced_ty` to avoid superfluous `as` instruction on operand. * ZIR instruction `Type` uses `coerced_ty` to properly handle result location type of operand. * Fix two instances of `enum_nonexhaustive` Value Tag not handled properly - it should generally be handled the same as `enum_full`. * Fix struct and union field resolution not copying Type and Value objects into its Decl arena. * Fix enum tag value resolution discarding the ZIR=>AIR instruction map for the child Sema, when they still needed to be accessed. * Fix `zirResolveInferredAlloc` use-after-free in the AIR instructions data array. * Fix `elemPtrArray` not respecting const/mutable attribute of pointer in the result type. * Fix LLVM backend crashing when `updateDeclExports` is called before `updateDecl`/`updateFunc` (which is, according to the API, perfectly legal for the frontend to do). * Fix LLVM backend handling element pointer of pointer-to-array. It needed another index in the GEP otherwise LLVM saw the wrong type. * Fix LLVM test cases not returning 0 from main, causing test failures. Fixes a regression introduced in 6a5094872f10acc629543cc7f10533b438d0283a. * Implement comptime shift-right. * Implement `@Type` for integers and `@TypeInfo` for integers. * Implement union initialization syntax. * Implement `zirFieldType` for unions. * Implement `elemPtrArray` for a runtime-known operand. * Make `zirLog2IntType` support RHS of shift being `comptime_int`. In this case it returns `comptime_int`. The motivating test case for this commit was originally: ```zig test "example" { var l: List(10) = undefined; l.array[1] = 1; } fn List(comptime L: usize) type { var T = u8; return struct { array: [L]T, }; } ``` However I changed it to: ```zig test "example" { var l: List = undefined; l.array[1] = 1; } const List = blk: { const T = [10]u8; break :blk struct { array: T, }; }; ``` Which ended up being a similar, smaller problem. The former test case will require a similar solution in the implementation of comptime function calls - checking if the result of the function call is a struct or union, and using the child `Sema` before it is destroyed to resolve the fields.
1 parent 2f1abd9
lib/std/builtin.zig
@@ -237,7 +237,7 @@ pub const TypeInfo = union(enum) {
         /// This field is an optional type.
         /// The type of the sentinel is the element type of the pointer, which is
         /// the value of the `child` field in this struct. However there is no way
-        /// to refer to that type here, so we use `var`.
+        /// to refer to that type here, so we use `anytype`.
         sentinel: anytype,
 
         /// This data structure is used by the Zig language code generation and
src/codegen/c.zig
@@ -913,6 +913,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
 
             .ptr_elem_val       => try airPtrElemVal(o, inst, "["),
             .ptr_ptr_elem_val   => try airPtrElemVal(o, inst, "[0]["),
+            .ptr_elem_ptr       => try airPtrElemPtr(o, inst),
             .slice_elem_val     => try airSliceElemVal(o, inst, "["),
             .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["),
 
@@ -960,6 +961,13 @@ fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
     return o.dg.fail("TODO: C backend: airPtrElemVal", .{});
 }
 
+fn airPtrElemPtr(o: *Object, inst: Air.Inst.Index) !CValue {
+    if (o.liveness.isUnused(inst))
+        return CValue.none;
+
+    return o.dg.fail("TODO: C backend: airPtrElemPtr", .{});
+}
+
 fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
     const is_volatile = false; // TODO
     if (!is_volatile and o.liveness.isUnused(inst))
src/codegen/llvm.zig
@@ -432,6 +432,8 @@ pub const Object = struct {
             },
             else => |e| return e,
         };
+        const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
+        try self.updateDeclExports(module, decl, decl_exports);
     }
 
     pub fn updateDeclExports(
@@ -440,7 +442,9 @@ pub const Object = struct {
         decl: *const Module.Decl,
         exports: []const *Module.Export,
     ) !void {
-        const llvm_fn = self.llvm_module.getNamedFunction(decl.name).?;
+        // If the module does not already have the function, we ignore this function call
+        // because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`.
+        const llvm_fn = self.llvm_module.getNamedFunction(decl.name) orelse return;
         const is_extern = decl.val.tag() == .extern_fn;
         if (is_extern or exports.len != 0) {
             llvm_fn.setLinkage(.External);
@@ -1041,6 +1045,7 @@ pub const FuncGen = struct {
                 .slice_elem_val     => try self.airSliceElemVal(inst),
                 .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst),
                 .ptr_elem_val       => try self.airPtrElemVal(inst),
+                .ptr_elem_ptr       => try self.airPtrElemPtr(inst),
                 .ptr_ptr_elem_val   => try self.airPtrPtrElemVal(inst),
 
                 .optional_payload     => try self.airOptionalPayload(inst, false),
@@ -1296,11 +1301,35 @@ pub const FuncGen = struct {
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const base_ptr = try self.resolveInst(bin_op.lhs);
         const rhs = try self.resolveInst(bin_op.rhs);
-        const indices: [1]*const llvm.Value = .{rhs};
-        const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+        const ptr = if (self.air.typeOf(bin_op.lhs).isSinglePointer()) ptr: {
+            // If this is a single-item pointer to an array, we need another index in the GEP.
+            const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
+            break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+        } else ptr: {
+            const indices: [1]*const llvm.Value = .{rhs};
+            break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+        };
         return self.builder.buildLoad(ptr, "");
     }
 
+    fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
+        const base_ptr = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        if (self.air.typeOf(bin_op.lhs).isSinglePointer()) {
+            // If this is a single-item pointer to an array, we need another index in the GEP.
+            const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
+            return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+        } else {
+            const indices: [1]*const llvm.Value = .{rhs};
+            return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+        }
+    }
+
     fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         const is_volatile = false; // TODO
         if (!is_volatile and self.liveness.isUnused(inst))
src/Air.zig
@@ -286,6 +286,10 @@ pub const Inst = struct {
         /// Result type is the element type of the pointer operand.
         /// Uses the `bin_op` field.
         ptr_elem_val,
+        /// Given a pointer value, and element index, return the element pointer at that index.
+        /// Result type is pointer to the element type of the pointer operand.
+        /// Uses the `ty_pl` field with payload `Bin`.
+        ptr_elem_ptr,
         /// Given a pointer to a pointer, and element index, return the element value of the inner
         /// pointer at that index.
         /// Result type is the element type of the inner pointer operand.
@@ -410,6 +414,11 @@ pub const StructField = struct {
     field_index: u32,
 };
 
+pub const Bin = struct {
+    lhs: Inst.Ref,
+    rhs: Inst.Ref,
+};
+
 /// Trailing:
 /// 0. `Inst.Ref` for every outputs_len
 /// 1. `Inst.Ref` for every inputs_len
@@ -482,6 +491,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .constant,
         .struct_field_ptr,
         .struct_field_val,
+        .ptr_elem_ptr,
         => return air.getRefType(datas[inst].ty_pl.ty),
 
         .not,
@@ -527,8 +537,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         },
 
         .slice_elem_val, .ptr_elem_val => {
-            const slice_ty = air.typeOf(datas[inst].bin_op.lhs);
-            return slice_ty.elemType();
+            const ptr_ty = air.typeOf(datas[inst].bin_op.lhs);
+            return ptr_ty.elemType();
         },
         .ptr_slice_elem_val, .ptr_ptr_elem_val => {
             const outer_ptr_ty = air.typeOf(datas[inst].bin_op.lhs);
src/AstGen.zig
@@ -7102,38 +7102,38 @@ fn builtinCall(
         .bit_size_of => return simpleUnOpType(gz, scope, rl, node, params[0], .bit_size_of),
         .align_of    => return simpleUnOpType(gz, scope, rl, node, params[0], .align_of),
 
-        .ptr_to_int            => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .ptr_to_int),
-        .error_to_int          => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .error_to_int),
-        .int_to_error          => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type },            params[0], .int_to_error),
-        .compile_error         => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error),
-        .set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u32_type },            params[0], .set_eval_branch_quota),
-        .enum_to_int           => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .enum_to_int),
-        .bool_to_int           => return simpleUnOp(gz, scope, rl, node, bool_rl,                         params[0], .bool_to_int),
-        .embed_file            => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .embed_file),
-        .error_name            => return simpleUnOp(gz, scope, rl, node, .{ .ty = .anyerror_type },       params[0], .error_name),
-        .panic                 => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .panic),
-        .set_align_stack       => return simpleUnOp(gz, scope, rl, node, align_rl,                        params[0], .set_align_stack),
-        .set_cold              => return simpleUnOp(gz, scope, rl, node, bool_rl,                         params[0], .set_cold),
-        .set_float_mode        => return simpleUnOp(gz, scope, rl, node, .{ .ty = .float_mode_type },     params[0], .set_float_mode),
-        .set_runtime_safety    => return simpleUnOp(gz, scope, rl, node, bool_rl,                         params[0], .set_runtime_safety),
-        .sqrt                  => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .sqrt),
-        .sin                   => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .sin),
-        .cos                   => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .cos),
-        .exp                   => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .exp),
-        .exp2                  => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .exp2),
-        .log                   => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .log),
-        .log2                  => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .log2),
-        .log10                 => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .log10),
-        .fabs                  => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .fabs),
-        .floor                 => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .floor),
-        .ceil                  => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .ceil),
-        .trunc                 => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .trunc),
-        .round                 => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .round),
-        .tag_name              => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .tag_name),
-        .Type                  => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .reify),
-        .type_name             => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .type_name),
-        .Frame                 => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .frame_type),
-        .frame_size            => return simpleUnOp(gz, scope, rl, node, .none,                           params[0], .frame_size),
+        .ptr_to_int            => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .ptr_to_int),
+        .error_to_int          => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .error_to_int),
+        .int_to_error          => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type },                params[0], .int_to_error),
+        .compile_error         => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type },     params[0], .compile_error),
+        .set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u32_type },                params[0], .set_eval_branch_quota),
+        .enum_to_int           => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .enum_to_int),
+        .bool_to_int           => return simpleUnOp(gz, scope, rl, node, bool_rl,                             params[0], .bool_to_int),
+        .embed_file            => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type },     params[0], .embed_file),
+        .error_name            => return simpleUnOp(gz, scope, rl, node, .{ .ty = .anyerror_type },           params[0], .error_name),
+        .panic                 => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type },     params[0], .panic),
+        .set_align_stack       => return simpleUnOp(gz, scope, rl, node, align_rl,                            params[0], .set_align_stack),
+        .set_cold              => return simpleUnOp(gz, scope, rl, node, bool_rl,                             params[0], .set_cold),
+        .set_float_mode        => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .float_mode_type }, params[0], .set_float_mode),
+        .set_runtime_safety    => return simpleUnOp(gz, scope, rl, node, bool_rl,                             params[0], .set_runtime_safety),
+        .sqrt                  => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .sqrt),
+        .sin                   => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .sin),
+        .cos                   => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .cos),
+        .exp                   => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .exp),
+        .exp2                  => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .exp2),
+        .log                   => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .log),
+        .log2                  => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .log2),
+        .log10                 => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .log10),
+        .fabs                  => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .fabs),
+        .floor                 => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .floor),
+        .ceil                  => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .ceil),
+        .trunc                 => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .trunc),
+        .round                 => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .round),
+        .tag_name              => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .tag_name),
+        .Type                  => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .type_info_type },  params[0], .reify),
+        .type_name             => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .type_name),
+        .Frame                 => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .frame_type),
+        .frame_size            => return simpleUnOp(gz, scope, rl, node, .none,                               params[0], .frame_size),
 
         .float_to_int => return typeCast(gz, scope, rl, node, params[0], params[1], .float_to_int),
         .int_to_float => return typeCast(gz, scope, rl, node, params[0], params[1], .int_to_float),
src/codegen.zig
@@ -862,6 +862,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .slice_elem_val      => try self.airSliceElemVal(inst),
                     .ptr_slice_elem_val  => try self.airPtrSliceElemVal(inst),
                     .ptr_elem_val        => try self.airPtrElemVal(inst),
+                    .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
                     .ptr_ptr_elem_val    => try self.airPtrPtrElemVal(inst),
 
                     .constant => unreachable, // excluded from function bodies
@@ -1419,6 +1420,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
         }
 
+        fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
+            const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+            const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement ptr_elem_ptr for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
+        }
+
         fn airPtrPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
             const is_volatile = false; // TODO
             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
src/Liveness.zig
@@ -330,6 +330,10 @@ fn analyzeInst(
             const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
             return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none });
         },
+        .ptr_elem_ptr => {
+            const extra = a.air.extraData(Air.Bin, inst_datas[inst].ty_pl.payload).data;
+            return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none });
+        },
         .br => {
             const br = inst_datas[inst].br;
             return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });
src/Module.zig
@@ -554,8 +554,8 @@ pub const Decl = struct {
                 assert(struct_obj.owner_decl == decl);
                 return &struct_obj.namespace;
             },
-            .enum_full => {
-                const enum_obj = ty.castTag(.enum_full).?.data;
+            .enum_full, .enum_nonexhaustive => {
+                const enum_obj = ty.cast(Type.Payload.EnumFull).?.data;
                 assert(enum_obj.owner_decl == decl);
                 return &enum_obj.namespace;
             },
@@ -660,6 +660,7 @@ pub const Struct = struct {
     /// is necessary to determine whether it has bits at runtime.
     known_has_bits: bool,
 
+    /// 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`.
@@ -3091,6 +3092,9 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
         if (linksection_ref == .none) break :blk Value.initTag(.null_value);
         break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val;
     };
+    // Note this resolves the type of the Decl, not the value; if this Decl
+    // is a struct, for example, this resolves `type` (which needs no resolution),
+    // not the struct itself.
     try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty);
 
     // We need the memory for the Type to go into the arena for the Decl
@@ -3193,6 +3197,15 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
         if (type_changed and mod.emit_h != null) {
             try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
         }
+    } else if (decl_tv.ty.zigTypeTag() == .Type) {
+        // In case this Decl is a struct or union, we need to resolve the fields
+        // while we still have the `Sema` in scope, so that the field type expressions
+        // can use the resolved AIR instructions that they possibly reference.
+        // We do this after the decl is populated and set to `complete` so that a `Decl`
+        // may reference itself.
+        var buffer: Value.ToTypeBuffer = undefined;
+        const ty = decl.val.toType(&buffer);
+        try sema.resolveDeclFields(&block_scope, src, ty);
     }
 
     if (decl.is_exported) {
@@ -4450,309 +4463,6 @@ pub const PeerTypeCandidateSrc = union(enum) {
     }
 };
 
-pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const gpa = mod.gpa;
-    const zir = struct_obj.owner_decl.namespace.file_scope.zir;
-    const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
-    assert(extended.opcode == .struct_decl);
-    const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
-    var extra_index: usize = extended.operand;
-
-    const src: LazySrcLoc = .{ .node_offset = 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;
-        break :blk fields_len;
-    } else 0;
-
-    const decls_len = if (small.has_decls_len) decls_len: {
-        const decls_len = zir.extra[extra_index];
-        extra_index += 1;
-        break :decls_len decls_len;
-    } else 0;
-
-    // Skip over decls.
-    var decls_it = zir.declIteratorInner(extra_index, decls_len);
-    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);
-        return;
-    }
-    extra_index += body.len;
-
-    var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa);
-    defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state;
-
-    try struct_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
-
-    // We create a block for the field type instructions because they
-    // may need to reference Decls from inside the struct namespace.
-    // Within the field type, default value, and alignment expressions, the "owner decl"
-    // should be the struct itself. Thus we need a new Sema.
-    var sema: Sema = .{
-        .mod = mod,
-        .gpa = gpa,
-        .arena = &decl_arena.allocator,
-        .code = zir,
-        .owner_decl = struct_obj.owner_decl,
-        .namespace = &struct_obj.namespace,
-        .owner_func = null,
-        .func = null,
-        .fn_ret_ty = Type.initTag(.void),
-    };
-    defer sema.deinit();
-
-    var block: Scope.Block = .{
-        .parent = null,
-        .sema = &sema,
-        .src_decl = struct_obj.owner_decl,
-        .instructions = .{},
-        .inlining = null,
-        .is_comptime = true,
-    };
-    defer assert(block.instructions.items.len == 0); // should all be comptime instructions
-
-    if (body.len != 0) {
-        _ = try sema.analyzeBody(&block, body);
-    }
-
-    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;
-
-        _ = unused;
-
-        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);
-        if (field_type_ref == .none) {
-            return mod.fail(&block.base, src, "TODO: implement anytype struct field", .{});
-        }
-        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, src, field_type_ref);
-
-        const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
-        assert(!gop.found_existing);
-        gop.value_ptr.* = .{
-            .ty = field_ty,
-            .abi_align = Value.initTag(.abi_align_default),
-            .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.resolveInstConst(&block, src, align_ref)).val;
-        }
-        if (has_default) {
-            const default_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 default value expression rather than the struct.
-            // But only resolve the source location if we need to emit a compile error.
-            gop.value_ptr.default_val = (try sema.resolveInstConst(&block, src, default_ref)).val;
-        }
-    }
-}
-
-pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const gpa = mod.gpa;
-    const zir = union_obj.owner_decl.namespace.file_scope.zir;
-    const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
-    assert(extended.opcode == .union_decl);
-    const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
-    var extra_index: usize = extended.operand;
-
-    const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
-    extra_index += @boolToInt(small.has_src_node);
-
-    if (small.has_tag_type) {
-        extra_index += 1;
-    }
-
-    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;
-        break :blk fields_len;
-    } else 0;
-
-    const decls_len = if (small.has_decls_len) decls_len: {
-        const decls_len = zir.extra[extra_index];
-        extra_index += 1;
-        break :decls_len decls_len;
-    } else 0;
-
-    // Skip over decls.
-    var decls_it = zir.declIteratorInner(extra_index, decls_len);
-    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);
-        return;
-    }
-    extra_index += body.len;
-
-    var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa);
-    defer union_obj.owner_decl.value_arena.?.* = decl_arena.state;
-
-    try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
-
-    // We create a block for the field type instructions because they
-    // may need to reference Decls from inside the struct namespace.
-    // Within the field type, default value, and alignment expressions, the "owner decl"
-    // should be the struct itself. Thus we need a new Sema.
-    var sema: Sema = .{
-        .mod = mod,
-        .gpa = gpa,
-        .arena = &decl_arena.allocator,
-        .code = zir,
-        .owner_decl = union_obj.owner_decl,
-        .namespace = &union_obj.namespace,
-        .owner_func = null,
-        .func = null,
-        .fn_ret_ty = Type.initTag(.void),
-    };
-    defer sema.deinit();
-
-    var block: Scope.Block = .{
-        .parent = null,
-        .sema = &sema,
-        .src_decl = union_obj.owner_decl,
-        .instructions = .{},
-        .inlining = null,
-        .is_comptime = true,
-    };
-    defer assert(block.instructions.items.len == 0); // should all be comptime instructions
-
-    if (body.len != 0) {
-        _ = try sema.analyzeBody(&block, body);
-    }
-
-    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_type = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        const has_align = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        const has_tag = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        const unused = @truncate(u1, cur_bit_bag) != 0;
-        cur_bit_bag >>= 1;
-        _ = unused;
-
-        const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
-        extra_index += 1;
-
-        const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
-            const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
-            extra_index += 1;
-            break :blk field_type_ref;
-        } else .none;
-
-        const align_ref: Zir.Inst.Ref = if (has_align) blk: {
-            const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
-            extra_index += 1;
-            break :blk align_ref;
-        } else .none;
-
-        if (has_tag) {
-            extra_index += 1;
-        }
-
-        // 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(.void)
-        else
-            // TODO: if we need to report an error here, use a source location
-            // that points to this type expression rather than the union.
-            // But only resolve the source location if we need to emit a compile error.
-            try sema.resolveType(&block, src, field_type_ref);
-
-        const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
-        assert(!gop.found_existing);
-        gop.value_ptr.* = .{
-            .ty = field_ty,
-            .abi_align = Value.initTag(.abi_align_default),
-        };
-
-        if (align_ref != .none) {
-            // 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.resolveInstConst(&block, src, align_ref)).val;
-        }
-    }
-
-    // TODO resolve the union tag_type_ref
-}
-
 /// Called from `performAllTheWork`, after all AstGen workers have finished,
 /// and before the main semantic analysis loop begins.
 pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
src/print_air.zig
@@ -175,6 +175,7 @@ const Writer = struct {
             .loop,
             => try w.writeBlock(s, inst),
 
+            .ptr_elem_ptr => try w.writePtrElemPtr(s, inst),
             .struct_field_ptr => try w.writeStructField(s, inst),
             .struct_field_val => try w.writeStructField(s, inst),
             .constant => try w.writeConstant(s, inst),
@@ -239,10 +240,19 @@ const Writer = struct {
 
     fn writeStructField(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
         const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
-        const extra = w.air.extraData(Air.StructField, ty_pl.payload);
+        const extra = w.air.extraData(Air.StructField, ty_pl.payload).data;
 
-        try w.writeOperand(s, inst, 0, extra.data.struct_operand);
-        try s.print(", {d}", .{extra.data.field_index});
+        try w.writeOperand(s, inst, 0, extra.struct_operand);
+        try s.print(", {d}", .{extra.field_index});
+    }
+
+    fn writePtrElemPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+        const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
+        const extra = w.air.extraData(Air.Bin, ty_pl.payload).data;
+
+        try w.writeOperand(s, inst, 0, extra.lhs);
+        try s.writeAll(", ");
+        try w.writeOperand(s, inst, 0, extra.rhs);
     }
 
     fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
src/Sema.zig
@@ -1032,25 +1032,27 @@ fn zirEnumDecl(
         // We create a block for the field type instructions because they
         // may need to reference Decls from inside the enum namespace.
         // Within the field type, default value, and alignment expressions, the "owner decl"
-        // should be the enum itself. Thus we need a new Sema.
-        var enum_sema: Sema = .{
-            .mod = mod,
-            .gpa = gpa,
-            .arena = &new_decl_arena.allocator,
-            .code = sema.code,
-            .inst_map = sema.inst_map,
-            .owner_decl = new_decl,
-            .namespace = &enum_obj.namespace,
-            .owner_func = null,
-            .func = null,
-            .fn_ret_ty = Type.initTag(.void),
-            .branch_quota = sema.branch_quota,
-            .branch_count = sema.branch_count,
-        };
+        // should be the enum itself.
+
+        const prev_owner_decl = sema.owner_decl;
+        sema.owner_decl = new_decl;
+        defer sema.owner_decl = prev_owner_decl;
+
+        const prev_namespace = sema.namespace;
+        sema.namespace = &enum_obj.namespace;
+        defer sema.namespace = prev_namespace;
+
+        const prev_owner_func = sema.owner_func;
+        sema.owner_func = null;
+        defer sema.owner_func = prev_owner_func;
+
+        const prev_func = sema.func;
+        sema.func = null;
+        defer sema.func = prev_func;
 
         var enum_block: Scope.Block = .{
             .parent = null,
-            .sema = &enum_sema,
+            .sema = sema,
             .src_decl = new_decl,
             .instructions = .{},
             .inlining = null,
@@ -1059,11 +1061,8 @@ fn zirEnumDecl(
         defer assert(enum_block.instructions.items.len == 0); // should all be comptime instructions
 
         if (body.len != 0) {
-            _ = try enum_sema.analyzeBody(&enum_block, body);
+            _ = try sema.analyzeBody(&enum_block, body);
         }
-
-        sema.branch_count = enum_sema.branch_count;
-        sema.branch_quota = enum_sema.branch_quota;
     }
     var bit_bag_index: usize = body_end;
     var cur_bit_bag: u32 = undefined;
@@ -1466,8 +1465,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
     const ptr = sema.resolveInst(inst_data.operand);
     const ptr_inst = Air.refToIndex(ptr).?;
     assert(sema.air_instructions.items(.tag)[ptr_inst] == .constant);
-    const air_datas = sema.air_instructions.items(.data);
-    const value_index = air_datas[ptr_inst].ty_pl.payload;
+    const value_index = sema.air_instructions.items(.data)[ptr_inst].ty_pl.payload;
     const ptr_val = sema.air_values.items[value_index];
     const var_is_mut = switch (sema.typeOf(ptr).tag()) {
         .inferred_alloc_const => false,
@@ -1481,7 +1479,8 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
 
         const final_elem_ty = try decl.ty.copy(sema.arena);
         const final_ptr_ty = try Module.simplePtrType(sema.arena, final_elem_ty, true, .One);
-        air_datas[ptr_inst].ty_pl.ty = try sema.addType(final_ptr_ty);
+        const final_ptr_ty_inst = try sema.addType(final_ptr_ty);
+        sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst;
 
         if (var_is_mut) {
             sema.air_values.items[value_index] = try Value.Tag.decl_ref_mut.create(sema.arena, .{
@@ -5329,10 +5328,16 @@ fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
 
     if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| {
         if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
+            const lhs_ty = sema.typeOf(lhs);
             if (lhs_val.isUndef() or rhs_val.isUndef()) {
-                return sema.addConstUndef(sema.typeOf(lhs));
+                return sema.addConstUndef(lhs_ty);
+            }
+            // If rhs is 0, return lhs without doing any calculations.
+            if (rhs_val.compareWithZero(.eq)) {
+                return sema.addConstant(lhs_ty, lhs_val);
             }
-            return sema.mod.fail(&block.base, src, "TODO implement comptime shr", .{});
+            const val = try lhs_val.shr(rhs_val, sema.arena);
+            return sema.addConstant(lhs_ty, val);
         }
     }
 
@@ -6008,6 +6013,28 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
                 }),
             );
         },
+        .Int => {
+            const info = ty.intInfo(target);
+            const field_values = try sema.arena.alloc(Value, 2);
+            // signedness: Signedness,
+            field_values[0] = try Value.Tag.enum_field_index.create(
+                sema.arena,
+                @enumToInt(info.signedness),
+            );
+            // bits: comptime_int,
+            field_values[1] = try Value.Tag.int_u64.create(sema.arena, info.bits);
+
+            return sema.addConstant(
+                type_info_ty,
+                try Value.Tag.@"union".create(sema.arena, .{
+                    .tag = try Value.Tag.enum_field_index.create(
+                        sema.arena,
+                        @enumToInt(@typeInfo(std.builtin.TypeInfo).Union.tag_type.?.Int),
+                    ),
+                    .val = try Value.Tag.@"struct".create(sema.arena, field_values.ptr),
+                }),
+            );
+        },
         else => |t| return sema.mod.fail(&block.base, src, "TODO: implement zirTypeInfo for {s}", .{
             @tagName(t),
         }),
@@ -6047,20 +6074,24 @@ fn zirLog2IntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compil
 }
 
 fn log2IntType(sema: *Sema, block: *Scope.Block, operand: Type, src: LazySrcLoc) CompileError!Air.Inst.Ref {
-    if (operand.zigTypeTag() != .Int) return sema.mod.fail(
-        &block.base,
-        src,
-        "bit shifting operation expected integer type, found '{}'",
-        .{operand},
-    );
-
-    var count: u16 = 0;
-    var s = operand.bitSize(sema.mod.getTarget()) - 1;
-    while (s != 0) : (s >>= 1) {
-        count += 1;
+    switch (operand.zigTypeTag()) {
+        .ComptimeInt => return Air.Inst.Ref.comptime_int_type,
+        .Int => {
+            var count: u16 = 0;
+            var s = operand.bitSize(sema.mod.getTarget()) - 1;
+            while (s != 0) : (s >>= 1) {
+                count += 1;
+            }
+            const res = try Module.makeIntType(sema.arena, .unsigned, count);
+            return sema.addType(res);
+        },
+        else => return sema.mod.fail(
+            &block.base,
+            src,
+            "bit shifting operation expected integer type, found '{}'",
+            .{operand},
+        ),
     }
-    const res = try Module.makeIntType(sema.arena, .unsigned, count);
-    return sema.addType(res);
 }
 
 fn zirTypeofPeer(
@@ -6517,99 +6548,134 @@ fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref:
     const first_field_type_data = zir_datas[first_item.field_type].pl_node;
     const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data;
     const unresolved_struct_type = try sema.resolveType(block, src, first_field_type_extra.container_type);
-    const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
-    const struct_obj = struct_ty.castTag(.@"struct").?.data;
-
-    // Maps field index to field_type index of where it was already initialized.
-    // For making sure all fields are accounted for and no fields are duplicated.
-    const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count());
-    defer gpa.free(found_fields);
-    mem.set(Zir.Inst.Index, found_fields, 0);
-
-    // The init values to use for the struct instance.
-    const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count());
-    defer gpa.free(field_inits);
+    const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
+
+    if (resolved_ty.castTag(.@"struct")) |struct_payload| {
+        const struct_obj = struct_payload.data;
+
+        // Maps field index to field_type index of where it was already initialized.
+        // For making sure all fields are accounted for and no fields are duplicated.
+        const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count());
+        defer gpa.free(found_fields);
+        mem.set(Zir.Inst.Index, found_fields, 0);
+
+        // The init values to use for the struct instance.
+        const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count());
+        defer gpa.free(field_inits);
+
+        var field_i: u32 = 0;
+        var extra_index = extra.end;
+
+        while (field_i < extra.data.fields_len) : (field_i += 1) {
+            const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index);
+            extra_index = item.end;
+
+            const field_type_data = zir_datas[item.data.field_type].pl_node;
+            const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
+            const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
+            const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
+            const field_index = struct_obj.fields.getIndex(field_name) orelse
+                return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name);
+            if (found_fields[field_index] != 0) {
+                const other_field_type = found_fields[field_index];
+                const other_field_type_data = zir_datas[other_field_type].pl_node;
+                const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node };
+                const msg = msg: {
+                    const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{});
+                    errdefer msg.destroy(gpa);
+                    try mod.errNote(&block.base, other_field_src, msg, "other field here", .{});
+                    break :msg msg;
+                };
+                return mod.failWithOwnedErrorMsg(&block.base, msg);
+            }
+            found_fields[field_index] = item.data.field_type;
+            field_inits[field_index] = sema.resolveInst(item.data.init);
+        }
 
-    var field_i: u32 = 0;
-    var extra_index = extra.end;
+        var root_msg: ?*Module.ErrorMsg = null;
 
-    while (field_i < extra.data.fields_len) : (field_i += 1) {
-        const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index);
-        extra_index = item.end;
+        for (found_fields) |field_type_inst, i| {
+            if (field_type_inst != 0) continue;
 
-        const field_type_data = zir_datas[item.data.field_type].pl_node;
-        const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
-        const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
-        const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
-        const field_index = struct_obj.fields.getIndex(field_name) orelse
-            return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name);
-        if (found_fields[field_index] != 0) {
-            const other_field_type = found_fields[field_index];
-            const other_field_type_data = zir_datas[other_field_type].pl_node;
-            const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node };
-            const msg = msg: {
-                const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{});
-                errdefer msg.destroy(gpa);
-                try mod.errNote(&block.base, other_field_src, msg, "other field here", .{});
-                break :msg msg;
-            };
+            // Check if the field has a default init.
+            const field = struct_obj.fields.values()[i];
+            if (field.default_val.tag() == .unreachable_value) {
+                const field_name = struct_obj.fields.keys()[i];
+                const template = "missing struct field: {s}";
+                const args = .{field_name};
+                if (root_msg) |msg| {
+                    try mod.errNote(&block.base, src, msg, template, args);
+                } else {
+                    root_msg = try mod.errMsg(&block.base, src, template, args);
+                }
+            } else {
+                field_inits[i] = try sema.addConstant(field.ty, field.default_val);
+            }
+        }
+        if (root_msg) |msg| {
+            const fqn = try struct_obj.getFullyQualifiedName(gpa);
+            defer gpa.free(fqn);
+            try mod.errNoteNonLazy(
+                struct_obj.srcLoc(),
+                msg,
+                "struct '{s}' declared here",
+                .{fqn},
+            );
             return mod.failWithOwnedErrorMsg(&block.base, msg);
         }
-        found_fields[field_index] = item.data.field_type;
-        field_inits[field_index] = sema.resolveInst(item.data.init);
-    }
 
-    var root_msg: ?*Module.ErrorMsg = null;
+        if (is_ref) {
+            return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true", .{});
+        }
 
-    for (found_fields) |field_type_inst, i| {
-        if (field_type_inst != 0) continue;
-
-        // Check if the field has a default init.
-        const field = struct_obj.fields.values()[i];
-        if (field.default_val.tag() == .unreachable_value) {
-            const field_name = struct_obj.fields.keys()[i];
-            const template = "missing struct field: {s}";
-            const args = .{field_name};
-            if (root_msg) |msg| {
-                try mod.errNote(&block.base, src, msg, template, args);
-            } else {
-                root_msg = try mod.errMsg(&block.base, src, template, args);
+        const is_comptime = for (field_inits) |field_init| {
+            if (!(try sema.isComptimeKnown(block, src, field_init))) {
+                break false;
             }
-        } else {
-            field_inits[i] = try sema.addConstant(field.ty, field.default_val);
+        } else true;
+
+        if (is_comptime) {
+            const values = try sema.arena.alloc(Value, field_inits.len);
+            for (field_inits) |field_init, i| {
+                values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?;
+            }
+            return sema.addConstant(resolved_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr));
         }
-    }
-    if (root_msg) |msg| {
-        const fqn = try struct_obj.getFullyQualifiedName(gpa);
-        defer gpa.free(fqn);
-        try mod.errNoteNonLazy(
-            struct_obj.srcLoc(),
-            msg,
-            "struct '{s}' declared here",
-            .{fqn},
-        );
-        return mod.failWithOwnedErrorMsg(&block.base, msg);
-    }
 
-    if (is_ref) {
-        return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true", .{});
-    }
+        return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{});
+    } else if (resolved_ty.cast(Type.Payload.Union)) |union_payload| {
+        const union_obj = union_payload.data;
 
-    const is_comptime = for (field_inits) |field_init| {
-        if (!(try sema.isComptimeKnown(block, src, field_init))) {
-            break false;
+        if (extra.data.fields_len != 1) {
+            return sema.mod.fail(&block.base, src, "union initialization expects exactly one field", .{});
         }
-    } else true;
 
-    if (is_comptime) {
-        const values = try sema.arena.alloc(Value, field_inits.len);
-        for (field_inits) |field_init, i| {
-            values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?;
+        const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end);
+
+        const field_type_data = zir_datas[item.data.field_type].pl_node;
+        const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
+        const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
+        const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
+        const field_index = union_obj.fields.getIndex(field_name) orelse
+            return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
+
+        if (is_ref) {
+            return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true union", .{});
         }
-        return sema.addConstant(struct_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr));
-    }
 
-    return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{});
+        const init_inst = sema.resolveInst(item.data.init);
+        if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| {
+            return sema.addConstant(
+                resolved_ty,
+                try Value.Tag.@"union".create(sema.arena, .{
+                    .tag = try Value.Tag.int_u64.create(sema.arena, field_index),
+                    .val = val,
+                }),
+            );
+        }
+        return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known union values", .{});
+    }
+    unreachable;
 }
 
 fn zirStructInitAnon(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref {
@@ -6647,17 +6713,25 @@ fn zirFieldType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
     const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
     const src = inst_data.src();
     const field_name = sema.code.nullTerminatedString(extra.name_start);
-    const unresolved_struct_type = try sema.resolveType(block, src, extra.container_type);
-    if (unresolved_struct_type.zigTypeTag() != .Struct) {
-        return sema.mod.fail(&block.base, src, "expected struct; found '{}'", .{
-            unresolved_struct_type,
-        });
+    const unresolved_ty = try sema.resolveType(block, src, extra.container_type);
+    const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_ty);
+    switch (resolved_ty.zigTypeTag()) {
+        .Struct => {
+            const struct_obj = resolved_ty.castTag(.@"struct").?.data;
+            const field = struct_obj.fields.get(field_name) orelse
+                return sema.failWithBadFieldAccess(block, struct_obj, src, field_name);
+            return sema.addType(field.ty);
+        },
+        .Union => {
+            const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
+            const field = union_obj.fields.get(field_name) orelse
+                return sema.failWithBadUnionFieldAccess(block, union_obj, src, field_name);
+            return sema.addType(field.ty);
+        },
+        else => return sema.mod.fail(&block.base, src, "expected struct or union; found '{}'", .{
+            resolved_ty,
+        }),
     }
-    const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
-    const struct_obj = struct_ty.castTag(.@"struct").?.data;
-    const field = struct_obj.fields.get(field_name) orelse
-        return sema.failWithBadFieldAccess(block, struct_obj, src, field_name);
-    return sema.addType(field.ty);
 }
 
 fn zirErrorReturnTrace(
@@ -6732,7 +6806,54 @@ fn zirTagName(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
 fn zirReify(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
-    return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify", .{});
+    const type_info_ty = try sema.getBuiltinType(block, src, "TypeInfo");
+    const uncasted_operand = sema.resolveInst(inst_data.operand);
+    const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+    const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
+    const val = try sema.resolveConstValue(block, operand_src, type_info);
+    const union_val = val.cast(Value.Payload.Union).?.data;
+    const TypeInfoTag = std.meta.Tag(std.builtin.TypeInfo);
+    const tag_index = @intCast(std.meta.Tag(TypeInfoTag), union_val.tag.toUnsignedInt());
+    switch (@intToEnum(std.builtin.TypeId, tag_index)) {
+        .Type => return Air.Inst.Ref.type_type,
+        .Void => return Air.Inst.Ref.void_type,
+        .Bool => return Air.Inst.Ref.bool_type,
+        .NoReturn => return Air.Inst.Ref.noreturn_type,
+        .Int => {
+            const struct_val = union_val.val.castTag(.@"struct").?.data;
+            // TODO use reflection instead of magic numbers here
+            const signedness_val = struct_val[0];
+            const bits_val = struct_val[1];
+
+            const signedness = signedness_val.toEnum(std.builtin.Signedness);
+            const bits = @intCast(u16, bits_val.toUnsignedInt());
+            const ty = switch (signedness) {
+                .signed => try Type.Tag.int_signed.create(sema.arena, bits),
+                .unsigned => try Type.Tag.int_unsigned.create(sema.arena, bits),
+            };
+            return sema.addType(ty);
+        },
+        .Float => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Float", .{}),
+        .Pointer => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Pointer", .{}),
+        .Array => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Array", .{}),
+        .Struct => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Struct", .{}),
+        .ComptimeFloat => return Air.Inst.Ref.comptime_float_type,
+        .ComptimeInt => return Air.Inst.Ref.comptime_int_type,
+        .Undefined => return Air.Inst.Ref.undefined_type,
+        .Null => return Air.Inst.Ref.null_type,
+        .Optional => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Optional", .{}),
+        .ErrorUnion => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for ErrorUnion", .{}),
+        .ErrorSet => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for ErrorSet", .{}),
+        .Enum => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Enum", .{}),
+        .Union => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Union", .{}),
+        .Fn => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Fn", .{}),
+        .BoundFn => @panic("TODO delete BoundFn from the language"),
+        .Opaque => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Opaque", .{}),
+        .Frame => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Frame", .{}),
+        .AnyFrame => return Air.Inst.Ref.anyframe_type,
+        .Vector => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Vector", .{}),
+        .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
+    }
 }
 
 fn zirTypeName(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -8152,24 +8273,35 @@ fn elemPtrArray(
     elem_index: Air.Inst.Ref,
     elem_index_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
+    const array_ptr_ty = sema.typeOf(array_ptr);
+    const pointee_type = array_ptr_ty.elemType().elemType();
+    const result_ty = if (array_ptr_ty.ptrIsMutable())
+        try Type.Tag.single_mut_pointer.create(sema.arena, pointee_type)
+    else
+        try Type.Tag.single_const_pointer.create(sema.arena, pointee_type);
+
     if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| {
-        if (try sema.resolveDefinedValue(block, src, elem_index)) |index_val| {
+        if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| {
             // Both array pointer and index are compile-time known.
             const index_u64 = index_val.toUnsignedInt();
             // @intCast here because it would have been impossible to construct a value that
             // required a larger index.
             const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64));
-            const pointee_type = sema.typeOf(array_ptr).elemType().elemType();
-
-            return sema.addConstant(
-                try Type.Tag.single_const_pointer.create(sema.arena, pointee_type),
-                elem_ptr,
-            );
+            return sema.addConstant(result_ty, elem_ptr);
         }
     }
-    _ = elem_index;
-    _ = elem_index_src;
-    return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr for arrays", .{});
+    // TODO safety check for array bounds
+    try sema.requireRuntimeBlock(block, src);
+    return block.addInst(.{
+        .tag = .ptr_elem_ptr,
+        .data = .{ .ty_pl = .{
+            .ty = try sema.addType(result_ty),
+            .payload = try sema.addExtra(Air.Bin{
+                .lhs = array_ptr,
+                .rhs = elem_index,
+            }),
+        } },
+    });
 }
 
 fn coerce(
@@ -9177,22 +9309,62 @@ pub fn resolveTypeLayout(
     }
 }
 
-fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type {
+/// `sema` and `block` are expected to be the same ones used for the `Decl`.
+pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void {
     switch (ty.tag()) {
         .@"struct" => {
             const struct_obj = ty.castTag(.@"struct").?.data;
+            if (struct_obj.owner_decl.namespace != sema.owner_decl.namespace) return;
             switch (struct_obj.status) {
                 .none => {},
                 .field_types_wip => {
                     return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty});
                 },
-                .have_field_types, .have_layout, .layout_wip => return ty,
+                .have_field_types, .have_layout, .layout_wip => return,
             }
+            const prev_namespace = sema.namespace;
+            sema.namespace = &struct_obj.namespace;
+            defer sema.namespace = prev_namespace;
+
             struct_obj.status = .field_types_wip;
-            try sema.mod.analyzeStructFields(struct_obj);
+            try sema.analyzeStructFields(block, struct_obj);
             struct_obj.status = .have_field_types;
-            return ty;
         },
+        .@"union", .union_tagged => {
+            const union_obj = ty.cast(Type.Payload.Union).?.data;
+            if (union_obj.owner_decl.namespace != sema.owner_decl.namespace) return;
+            switch (union_obj.status) {
+                .none => {},
+                .field_types_wip => {
+                    return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ty});
+                },
+                .have_field_types, .have_layout, .layout_wip => return,
+            }
+            const prev_namespace = sema.namespace;
+            sema.namespace = &union_obj.namespace;
+            defer sema.namespace = prev_namespace;
+
+            union_obj.status = .field_types_wip;
+            try sema.analyzeUnionFields(block, union_obj);
+            union_obj.status = .have_field_types;
+        },
+        else => return,
+    }
+}
+
+fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type {
+    switch (ty.tag()) {
+        .@"struct" => {
+            const struct_obj = ty.castTag(.@"struct").?.data;
+            switch (struct_obj.status) {
+                .none => unreachable,
+                .field_types_wip => {
+                    return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty});
+                },
+                .have_field_types, .have_layout, .layout_wip => return ty,
+            }
+        },
+        .type_info => return sema.resolveBuiltinTypeFields(block, src, "TypeInfo"),
         .extern_options => return sema.resolveBuiltinTypeFields(block, src, "ExternOptions"),
         .export_options => return sema.resolveBuiltinTypeFields(block, src, "ExportOptions"),
         .atomic_ordering => return sema.resolveBuiltinTypeFields(block, src, "AtomicOrdering"),
@@ -9205,18 +9377,12 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type
         .@"union", .union_tagged => {
             const union_obj = ty.cast(Type.Payload.Union).?.data;
             switch (union_obj.status) {
-                .none => {},
+                .none => unreachable,
                 .field_types_wip => {
-                    return sema.mod.fail(&block.base, src, "union {} depends on itself", .{
-                        ty,
-                    });
+                    return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ty});
                 },
                 .have_field_types, .have_layout, .layout_wip => return ty,
             }
-            union_obj.status = .field_types_wip;
-            try sema.mod.analyzeUnionFields(union_obj);
-            union_obj.status = .have_field_types;
-            return ty;
         },
         else => return ty,
     }
@@ -9232,6 +9398,265 @@ fn resolveBuiltinTypeFields(
     return sema.resolveTypeFields(block, src, resolved_ty);
 }
 
+fn analyzeStructFields(
+    sema: *Sema,
+    block: *Scope.Block,
+    struct_obj: *Module.Struct,
+) CompileError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const gpa = sema.gpa;
+    const zir = sema.code;
+    const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
+    assert(extended.opcode == .struct_decl);
+    const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = .{ .node_offset = 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;
+        break :blk fields_len;
+    } else 0;
+
+    const decls_len = if (small.has_decls_len) decls_len: {
+        const decls_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :decls_len decls_len;
+    } else 0;
+
+    // Skip over decls.
+    var decls_it = zir.declIteratorInner(extra_index, decls_len);
+    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);
+        return;
+    }
+    extra_index += body.len;
+
+    var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa);
+    defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state;
+
+    try struct_obj.fields.ensureTotalCapacity(&decl_arena.allocator, fields_len);
+
+    if (body.len != 0) {
+        _ = try sema.analyzeBody(block, body);
+    }
+
+    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;
+
+        _ = unused;
+
+        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);
+        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, src, field_type_ref);
+
+        const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
+        assert(!gop.found_existing);
+        gop.value_ptr.* = .{
+            .ty = try field_ty.copy(&decl_arena.allocator),
+            .abi_align = Value.initTag(.abi_align_default),
+            .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.
+            const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val;
+            gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator);
+        }
+        if (has_default) {
+            const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+            extra_index += 1;
+            const default_inst = 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, src, default_inst)) orelse
+                return sema.failWithNeededComptime(block, src);
+            gop.value_ptr.default_val = try default_val.copy(&decl_arena.allocator);
+        }
+    }
+}
+
+fn analyzeUnionFields(
+    sema: *Sema,
+    block: *Scope.Block,
+    union_obj: *Module.Union,
+) CompileError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const gpa = sema.gpa;
+    const zir = sema.code;
+    const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
+    assert(extended.opcode == .union_decl);
+    const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
+    extra_index += @boolToInt(small.has_src_node);
+
+    if (small.has_tag_type) {
+        extra_index += 1;
+    }
+
+    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;
+        break :blk fields_len;
+    } else 0;
+
+    const decls_len = if (small.has_decls_len) decls_len: {
+        const decls_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :decls_len decls_len;
+    } else 0;
+
+    // Skip over decls.
+    var decls_it = zir.declIteratorInner(extra_index, decls_len);
+    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);
+        return;
+    }
+    extra_index += body.len;
+
+    var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa);
+    defer union_obj.owner_decl.value_arena.?.* = decl_arena.state;
+
+    try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
+
+    if (body.len != 0) {
+        _ = try sema.analyzeBody(block, body);
+    }
+
+    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_type = @truncate(u1, cur_bit_bag) != 0;
+        cur_bit_bag >>= 1;
+        const has_align = @truncate(u1, cur_bit_bag) != 0;
+        cur_bit_bag >>= 1;
+        const has_tag = @truncate(u1, cur_bit_bag) != 0;
+        cur_bit_bag >>= 1;
+        const unused = @truncate(u1, cur_bit_bag) != 0;
+        cur_bit_bag >>= 1;
+        _ = unused;
+
+        const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
+        extra_index += 1;
+
+        const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
+            const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+            extra_index += 1;
+            break :blk field_type_ref;
+        } else .none;
+
+        const align_ref: Zir.Inst.Ref = if (has_align) blk: {
+            const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+            extra_index += 1;
+            break :blk align_ref;
+        } else .none;
+
+        if (has_tag) {
+            extra_index += 1;
+        }
+
+        // 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(.void)
+        else
+            // TODO: if we need to report an error here, use a source location
+            // that points to this type expression rather than the union.
+            // But only resolve the source location if we need to emit a compile error.
+            try sema.resolveType(block, src, field_type_ref);
+
+        const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
+        assert(!gop.found_existing);
+        gop.value_ptr.* = .{
+            .ty = try field_ty.copy(&decl_arena.allocator),
+            .abi_align = Value.initTag(.abi_align_default),
+        };
+
+        if (align_ref != .none) {
+            // 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.
+            const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val;
+            gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator);
+        }
+    }
+
+    // TODO resolve the union tag_type_ref
+}
+
 fn getBuiltin(
     sema: *Sema,
     block: *Scope.Block,
@@ -9344,6 +9769,7 @@ fn typeHasOnePossibleValue(
         .call_options,
         .export_options,
         .extern_options,
+        .type_info,
         .@"anyframe",
         .anyframe_T,
         .many_const_pointer,
@@ -9528,6 +9954,7 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref {
         .call_options => return .call_options_type,
         .export_options => return .export_options_type,
         .extern_options => return .extern_options_type,
+        .type_info => return .type_info_type,
         .manyptr_u8 => return .manyptr_u8_type,
         .manyptr_const_u8 => return .manyptr_const_u8_type,
         .fn_noreturn_no_args => return .fn_noreturn_no_args_type,
src/type.zig
@@ -133,6 +133,7 @@ pub const Type = extern union {
 
             .@"union",
             .union_tagged,
+            .type_info,
             => return .Union,
 
             .var_args_param => unreachable, // can be any type
@@ -248,6 +249,30 @@ pub const Type = extern union {
         };
     }
 
+    pub fn ptrIsMutable(ty: Type) bool {
+        return switch (ty.tag()) {
+            .single_const_pointer_to_comptime_int,
+            .const_slice_u8,
+            .single_const_pointer,
+            .many_const_pointer,
+            .manyptr_const_u8,
+            .c_const_pointer,
+            .const_slice,
+            => false,
+
+            .single_mut_pointer,
+            .many_mut_pointer,
+            .manyptr_u8,
+            .c_mut_pointer,
+            .mut_slice,
+            => true,
+
+            .pointer => ty.castTag(.pointer).?.data.mutable,
+
+            else => unreachable,
+        };
+    }
+
     pub fn ptrInfo(self: Type) Payload.Pointer {
         switch (self.tag()) {
             .single_const_pointer_to_comptime_int => return .{ .data = .{
@@ -717,6 +742,7 @@ pub const Type = extern union {
             .call_options,
             .export_options,
             .extern_options,
+            .type_info,
             .@"anyframe",
             .generic_poison,
             => unreachable,
@@ -928,6 +954,7 @@ pub const Type = extern union {
                 .call_options => return writer.writeAll("std.builtin.CallOptions"),
                 .export_options => return writer.writeAll("std.builtin.ExportOptions"),
                 .extern_options => return writer.writeAll("std.builtin.ExternOptions"),
+                .type_info => return writer.writeAll("std.builtin.TypeInfo"),
                 .function => {
                     const payload = ty.castTag(.function).?.data;
                     try writer.writeAll("fn(");
@@ -1178,6 +1205,7 @@ pub const Type = extern union {
             .comptime_int,
             .comptime_float,
             .enum_literal,
+            .type_info,
             => true,
 
             .var_args_param => unreachable,
@@ -1269,6 +1297,7 @@ pub const Type = extern union {
             .call_options => return Value.initTag(.call_options_type),
             .export_options => return Value.initTag(.export_options_type),
             .extern_options => return Value.initTag(.extern_options_type),
+            .type_info => return Value.initTag(.type_info_type),
             .inferred_alloc_const => unreachable,
             .inferred_alloc_mut => unreachable,
             else => return Value.Tag.ty.create(allocator, self),
@@ -1409,6 +1438,7 @@ pub const Type = extern union {
             .empty_struct,
             .empty_struct_literal,
             .@"opaque",
+            .type_info,
             => false,
 
             .inferred_alloc_const => unreachable,
@@ -1636,6 +1666,7 @@ pub const Type = extern union {
             .inferred_alloc_mut,
             .@"opaque",
             .var_args_param,
+            .type_info,
             => unreachable,
 
             .generic_poison => unreachable,
@@ -1667,6 +1698,7 @@ pub const Type = extern union {
             .@"opaque" => unreachable,
             .var_args_param => unreachable,
             .generic_poison => unreachable,
+            .type_info => unreachable,
 
             .@"struct" => {
                 const s = self.castTag(.@"struct").?.data;
@@ -1978,6 +2010,7 @@ pub const Type = extern union {
             .call_options,
             .export_options,
             .extern_options,
+            .type_info,
             => @panic("TODO at some point we gotta resolve builtin types"),
         };
     }
@@ -2691,6 +2724,7 @@ pub const Type = extern union {
             .call_options,
             .export_options,
             .extern_options,
+            .type_info,
             .@"anyframe",
             .anyframe_T,
             .many_const_pointer,
@@ -2778,6 +2812,7 @@ pub const Type = extern union {
         return switch (self.tag()) {
             .@"struct" => &self.castTag(.@"struct").?.data.namespace,
             .enum_full => &self.castTag(.enum_full).?.data.namespace,
+            .enum_nonexhaustive => &self.castTag(.enum_nonexhaustive).?.data.namespace,
             .empty_struct => self.castTag(.empty_struct).?.data,
             .@"opaque" => &self.castTag(.@"opaque").?.data,
             .@"union" => &self.castTag(.@"union").?.data.namespace,
@@ -3022,6 +3057,7 @@ pub const Type = extern union {
             .call_options,
             .export_options,
             .extern_options,
+            .type_info,
             => @panic("TODO resolve std.builtin types"),
             else => unreachable,
         }
@@ -3058,6 +3094,7 @@ pub const Type = extern union {
             .call_options,
             .export_options,
             .extern_options,
+            .type_info,
             => @panic("TODO resolve std.builtin types"),
             else => unreachable,
         }
@@ -3167,6 +3204,7 @@ pub const Type = extern union {
         call_options,
         export_options,
         extern_options,
+        type_info,
         manyptr_u8,
         manyptr_const_u8,
         fn_noreturn_no_args,
@@ -3289,6 +3327,7 @@ pub const Type = extern union {
                 .call_options,
                 .export_options,
                 .extern_options,
+                .type_info,
                 .@"anyframe",
                 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
 
src/value.zig
@@ -68,6 +68,7 @@ pub const Value = extern union {
         call_options_type,
         export_options_type,
         extern_options_type,
+        type_info_type,
         manyptr_u8_type,
         manyptr_const_u8_type,
         fn_noreturn_no_args_type,
@@ -221,6 +222,7 @@ pub const Value = extern union {
                 .call_options_type,
                 .export_options_type,
                 .extern_options_type,
+                .type_info_type,
                 .generic_poison,
                 => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"),
 
@@ -402,6 +404,7 @@ pub const Value = extern union {
             .call_options_type,
             .export_options_type,
             .extern_options_type,
+            .type_info_type,
             .generic_poison,
             => unreachable,
 
@@ -585,6 +588,7 @@ pub const Value = extern union {
             .call_options_type => return out_stream.writeAll("std.builtin.CallOptions"),
             .export_options_type => return out_stream.writeAll("std.builtin.ExportOptions"),
             .extern_options_type => return out_stream.writeAll("std.builtin.ExternOptions"),
+            .type_info_type => return out_stream.writeAll("std.builtin.TypeInfo"),
             .abi_align_default => return out_stream.writeAll("(default ABI alignment)"),
 
             .empty_struct_value => return out_stream.writeAll("struct {}{}"),
@@ -743,6 +747,7 @@ pub const Value = extern union {
             .call_options_type => Type.initTag(.call_options),
             .export_options_type => Type.initTag(.export_options),
             .extern_options_type => Type.initTag(.extern_options),
+            .type_info_type => Type.initTag(.type_info),
 
             .int_type => {
                 const payload = self.castTag(.int_type).?.data;
@@ -1514,6 +1519,31 @@ pub const Value = extern union {
         return Tag.int_u64.create(arena, truncated);
     }
 
+    pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+        // TODO is this a performance issue? maybe we should try the operation without
+        // resorting to BigInt first.
+        var lhs_space: Value.BigIntSpace = undefined;
+        const lhs_bigint = lhs.toBigInt(&lhs_space);
+        const shift = rhs.toUnsignedInt();
+        const limbs = try allocator.alloc(
+            std.math.big.Limb,
+            lhs_bigint.limbs.len - (shift / (@sizeOf(std.math.big.Limb) * 8)),
+        );
+        var result_bigint = BigIntMutable{
+            .limbs = limbs,
+            .positive = undefined,
+            .len = undefined,
+        };
+        result_bigint.shiftRight(lhs_bigint, shift);
+        const result_limbs = result_bigint.limbs[0..result_bigint.len];
+
+        if (result_bigint.positive) {
+            return Value.Tag.int_big_positive.create(allocator, result_limbs);
+        } else {
+            return Value.Tag.int_big_negative.create(allocator, result_limbs);
+        }
+    }
+
     pub fn floatAdd(
         lhs: Value,
         rhs: Value,
src/Zir.zig
@@ -687,14 +687,14 @@ pub const Inst = struct {
         /// A struct literal with a specified type, with no fields.
         /// Uses the `un_node` field.
         struct_init_empty,
-        /// Given a struct, union, or enum, and a field name as a string index,
+        /// Given a struct or union, and a field name as a string index,
         /// returns the field type. Uses the `pl_node` field. Payload is `FieldType`.
         field_type,
-        /// Given a struct, union, or enum, and a field name as a Ref,
+        /// Given a struct or union, and a field name as a Ref,
         /// returns the field type. Uses the `pl_node` field. Payload is `FieldTypeRef`.
         field_type_ref,
-        /// Finalizes a typed struct initialization, performs validation, and returns the
-        /// struct value.
+        /// Finalizes a typed struct or union initialization, performs validation, and returns the
+        /// struct or union value.
         /// Uses the `pl_node` field. Payload is `StructInit`.
         struct_init,
         /// Struct initialization syntax, make the result a pointer.
@@ -1703,6 +1703,7 @@ pub const Inst = struct {
         call_options_type,
         export_options_type,
         extern_options_type,
+        type_info_type,
         manyptr_u8_type,
         manyptr_const_u8_type,
         fn_noreturn_no_args_type,
@@ -1973,6 +1974,10 @@ pub const Inst = struct {
                 .ty = Type.initTag(.type),
                 .val = Value.initTag(.extern_options_type),
             },
+            .type_info_type = .{
+                .ty = Type.initTag(.type),
+                .val = Value.initTag(.type_info_type),
+            },
 
             .undef = .{
                 .ty = Type.initTag(.@"undefined"),
test/behavior/array.zig
@@ -3,487 +3,3 @@ const testing = std.testing;
 const mem = std.mem;
 const expect = testing.expect;
 const expectEqual = testing.expectEqual;
-
-test "arrays" {
-    var array: [5]u32 = undefined;
-
-    var i: u32 = 0;
-    while (i < 5) {
-        array[i] = i + 1;
-        i = array[i];
-    }
-
-    i = 0;
-    var accumulator = @as(u32, 0);
-    while (i < 5) {
-        accumulator += array[i];
-
-        i += 1;
-    }
-
-    try expect(accumulator == 15);
-    try expect(getArrayLen(&array) == 5);
-}
-fn getArrayLen(a: []const u32) usize {
-    return a.len;
-}
-
-test "array with sentinels" {
-    const S = struct {
-        fn doTheTest(is_ct: bool) !void {
-            if (is_ct) {
-                var zero_sized: [0:0xde]u8 = [_:0xde]u8{};
-                // Disabled at runtime because of
-                // https://github.com/ziglang/zig/issues/4372
-                try expectEqual(@as(u8, 0xde), zero_sized[0]);
-                var reinterpreted = @ptrCast(*[1]u8, &zero_sized);
-                try expectEqual(@as(u8, 0xde), reinterpreted[0]);
-            }
-            var arr: [3:0x55]u8 = undefined;
-            // Make sure the sentinel pointer is pointing after the last element
-            if (!is_ct) {
-                const sentinel_ptr = @ptrToInt(&arr[3]);
-                const last_elem_ptr = @ptrToInt(&arr[2]);
-                try expectEqual(@as(usize, 1), sentinel_ptr - last_elem_ptr);
-            }
-            // Make sure the sentinel is writeable
-            arr[3] = 0x55;
-        }
-    };
-
-    try S.doTheTest(false);
-    comptime try S.doTheTest(true);
-}
-
-test "void arrays" {
-    var array: [4]void = undefined;
-    array[0] = void{};
-    array[1] = array[2];
-    try expect(@sizeOf(@TypeOf(array)) == 0);
-    try expect(array.len == 4);
-}
-
-test "array literal" {
-    const hex_mult = [_]u16{
-        4096,
-        256,
-        16,
-        1,
-    };
-
-    try expect(hex_mult.len == 4);
-    try expect(hex_mult[1] == 256);
-}
-
-test "array dot len const expr" {
-    try expect(comptime x: {
-        break :x some_array.len == 4;
-    });
-}
-
-const ArrayDotLenConstExpr = struct {
-    y: [some_array.len]u8,
-};
-const some_array = [_]u8{
-    0,
-    1,
-    2,
-    3,
-};
-
-test "nested arrays" {
-    const array_of_strings = [_][]const u8{
-        "hello",
-        "this",
-        "is",
-        "my",
-        "thing",
-    };
-    for (array_of_strings) |s, i| {
-        if (i == 0) try expect(mem.eql(u8, s, "hello"));
-        if (i == 1) try expect(mem.eql(u8, s, "this"));
-        if (i == 2) try expect(mem.eql(u8, s, "is"));
-        if (i == 3) try expect(mem.eql(u8, s, "my"));
-        if (i == 4) try expect(mem.eql(u8, s, "thing"));
-    }
-}
-
-var s_array: [8]Sub = undefined;
-const Sub = struct {
-    b: u8,
-};
-const Str = struct {
-    a: []Sub,
-};
-test "set global var array via slice embedded in struct" {
-    var s = Str{ .a = s_array[0..] };
-
-    s.a[0].b = 1;
-    s.a[1].b = 2;
-    s.a[2].b = 3;
-
-    try expect(s_array[0].b == 1);
-    try expect(s_array[1].b == 2);
-    try expect(s_array[2].b == 3);
-}
-
-test "array literal with specified size" {
-    var array = [2]u8{
-        1,
-        2,
-    };
-    try expect(array[0] == 1);
-    try expect(array[1] == 2);
-}
-
-test "array len field" {
-    var arr = [4]u8{ 0, 0, 0, 0 };
-    var ptr = &arr;
-    try expect(arr.len == 4);
-    comptime try expect(arr.len == 4);
-    try expect(ptr.len == 4);
-    comptime try expect(ptr.len == 4);
-}
-
-test "single-item pointer to array indexing and slicing" {
-    try testSingleItemPtrArrayIndexSlice();
-    comptime try testSingleItemPtrArrayIndexSlice();
-}
-
-fn testSingleItemPtrArrayIndexSlice() !void {
-    {
-        var array: [4]u8 = "aaaa".*;
-        doSomeMangling(&array);
-        try expect(mem.eql(u8, "azya", &array));
-    }
-    {
-        var array = "aaaa".*;
-        doSomeMangling(&array);
-        try expect(mem.eql(u8, "azya", &array));
-    }
-}
-
-fn doSomeMangling(array: *[4]u8) void {
-    array[1] = 'z';
-    array[2..3][0] = 'y';
-}
-
-test "implicit cast single-item pointer" {
-    try testImplicitCastSingleItemPtr();
-    comptime try testImplicitCastSingleItemPtr();
-}
-
-fn testImplicitCastSingleItemPtr() !void {
-    var byte: u8 = 100;
-    const slice = @as(*[1]u8, &byte)[0..];
-    slice[0] += 1;
-    try expect(byte == 101);
-}
-
-fn testArrayByValAtComptime(b: [2]u8) u8 {
-    return b[0];
-}
-
-test "comptime evalutating function that takes array by value" {
-    const arr = [_]u8{ 0, 1 };
-    _ = comptime testArrayByValAtComptime(arr);
-    _ = comptime testArrayByValAtComptime(arr);
-}
-
-test "implicit comptime in array type size" {
-    var arr: [plusOne(10)]bool = undefined;
-    try expect(arr.len == 11);
-}
-
-fn plusOne(x: u32) u32 {
-    return x + 1;
-}
-
-test "runtime initialize array elem and then implicit cast to slice" {
-    var two: i32 = 2;
-    const x: []const i32 = &[_]i32{two};
-    try expect(x[0] == 2);
-}
-
-test "array literal as argument to function" {
-    const S = struct {
-        fn entry(two: i32) !void {
-            try foo(&[_]i32{
-                1,
-                2,
-                3,
-            });
-            try foo(&[_]i32{
-                1,
-                two,
-                3,
-            });
-            try foo2(true, &[_]i32{
-                1,
-                2,
-                3,
-            });
-            try foo2(true, &[_]i32{
-                1,
-                two,
-                3,
-            });
-        }
-        fn foo(x: []const i32) !void {
-            try expect(x[0] == 1);
-            try expect(x[1] == 2);
-            try expect(x[2] == 3);
-        }
-        fn foo2(trash: bool, x: []const i32) !void {
-            try expect(trash);
-            try expect(x[0] == 1);
-            try expect(x[1] == 2);
-            try expect(x[2] == 3);
-        }
-    };
-    try S.entry(2);
-    comptime try S.entry(2);
-}
-
-test "double nested array to const slice cast in array literal" {
-    const S = struct {
-        fn entry(two: i32) !void {
-            const cases = [_][]const []const i32{
-                &[_][]const i32{&[_]i32{1}},
-                &[_][]const i32{&[_]i32{ 2, 3 }},
-                &[_][]const i32{
-                    &[_]i32{4},
-                    &[_]i32{ 5, 6, 7 },
-                },
-            };
-            try check(&cases);
-
-            const cases2 = [_][]const i32{
-                &[_]i32{1},
-                &[_]i32{ two, 3 },
-            };
-            try expect(cases2.len == 2);
-            try expect(cases2[0].len == 1);
-            try expect(cases2[0][0] == 1);
-            try expect(cases2[1].len == 2);
-            try expect(cases2[1][0] == 2);
-            try expect(cases2[1][1] == 3);
-
-            const cases3 = [_][]const []const i32{
-                &[_][]const i32{&[_]i32{1}},
-                &[_][]const i32{&[_]i32{ two, 3 }},
-                &[_][]const i32{
-                    &[_]i32{4},
-                    &[_]i32{ 5, 6, 7 },
-                },
-            };
-            try check(&cases3);
-        }
-
-        fn check(cases: []const []const []const i32) !void {
-            try expect(cases.len == 3);
-            try expect(cases[0].len == 1);
-            try expect(cases[0][0].len == 1);
-            try expect(cases[0][0][0] == 1);
-            try expect(cases[1].len == 1);
-            try expect(cases[1][0].len == 2);
-            try expect(cases[1][0][0] == 2);
-            try expect(cases[1][0][1] == 3);
-            try expect(cases[2].len == 2);
-            try expect(cases[2][0].len == 1);
-            try expect(cases[2][0][0] == 4);
-            try expect(cases[2][1].len == 3);
-            try expect(cases[2][1][0] == 5);
-            try expect(cases[2][1][1] == 6);
-            try expect(cases[2][1][2] == 7);
-        }
-    };
-    try S.entry(2);
-    comptime try S.entry(2);
-}
-
-test "read/write through global variable array of struct fields initialized via array mult" {
-    const S = struct {
-        fn doTheTest() !void {
-            try expect(storage[0].term == 1);
-            storage[0] = MyStruct{ .term = 123 };
-            try expect(storage[0].term == 123);
-        }
-
-        pub const MyStruct = struct {
-            term: usize,
-        };
-
-        var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
-    };
-    try S.doTheTest();
-}
-
-test "implicit cast zero sized array ptr to slice" {
-    {
-        var b = "".*;
-        const c: []const u8 = &b;
-        try expect(c.len == 0);
-    }
-    {
-        var b: [0]u8 = "".*;
-        const c: []const u8 = &b;
-        try expect(c.len == 0);
-    }
-}
-
-test "anonymous list literal syntax" {
-    const S = struct {
-        fn doTheTest() !void {
-            var array: [4]u8 = .{ 1, 2, 3, 4 };
-            try expect(array[0] == 1);
-            try expect(array[1] == 2);
-            try expect(array[2] == 3);
-            try expect(array[3] == 4);
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "anonymous literal in array" {
-    const S = struct {
-        const Foo = struct {
-            a: usize = 2,
-            b: usize = 4,
-        };
-        fn doTheTest() !void {
-            var array: [2]Foo = .{
-                .{ .a = 3 },
-                .{ .b = 3 },
-            };
-            try expect(array[0].a == 3);
-            try expect(array[0].b == 4);
-            try expect(array[1].a == 2);
-            try expect(array[1].b == 3);
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "access the null element of a null terminated array" {
-    const S = struct {
-        fn doTheTest() !void {
-            var array: [4:0]u8 = .{ 'a', 'o', 'e', 'u' };
-            try expect(array[4] == 0);
-            var len: usize = 4;
-            try expect(array[len] == 0);
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "type deduction for array subscript expression" {
-    const S = struct {
-        fn doTheTest() !void {
-            var array = [_]u8{ 0x55, 0xAA };
-            var v0 = true;
-            try expectEqual(@as(u8, 0xAA), array[if (v0) 1 else 0]);
-            var v1 = false;
-            try expectEqual(@as(u8, 0x55), array[if (v1) 1 else 0]);
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "sentinel element count towards the ABI size calculation" {
-    const S = struct {
-        fn doTheTest() !void {
-            const T = packed struct {
-                fill_pre: u8 = 0x55,
-                data: [0:0]u8 = undefined,
-                fill_post: u8 = 0xAA,
-            };
-            var x = T{};
-            var as_slice = mem.asBytes(&x);
-            try expectEqual(@as(usize, 3), as_slice.len);
-            try expectEqual(@as(u8, 0x55), as_slice[0]);
-            try expectEqual(@as(u8, 0xAA), as_slice[2]);
-        }
-    };
-
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "zero-sized array with recursive type definition" {
-    const U = struct {
-        fn foo(comptime T: type, comptime n: usize) type {
-            return struct {
-                s: [n]T,
-                x: usize = n,
-            };
-        }
-    };
-
-    const S = struct {
-        list: U.foo(@This(), 0),
-    };
-
-    var t: S = .{ .list = .{ .s = undefined } };
-    try expectEqual(@as(usize, 0), t.list.x);
-}
-
-test "type coercion of anon struct literal to array" {
-    const S = struct {
-        const U = union {
-            a: u32,
-            b: bool,
-            c: []const u8,
-        };
-
-        fn doTheTest() !void {
-            var x1: u8 = 42;
-            const t1 = .{ x1, 56, 54 };
-            var arr1: [3]u8 = t1;
-            try expect(arr1[0] == 42);
-            try expect(arr1[1] == 56);
-            try expect(arr1[2] == 54);
-
-            var x2: U = .{ .a = 42 };
-            const t2 = .{ x2, .{ .b = true }, .{ .c = "hello" } };
-            var arr2: [3]U = t2;
-            try expect(arr2[0].a == 42);
-            try expect(arr2[1].b == true);
-            try expect(mem.eql(u8, arr2[2].c, "hello"));
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "type coercion of pointer to anon struct literal to pointer to array" {
-    const S = struct {
-        const U = union {
-            a: u32,
-            b: bool,
-            c: []const u8,
-        };
-
-        fn doTheTest() !void {
-            var x1: u8 = 42;
-            const t1 = &.{ x1, 56, 54 };
-            var arr1: *const [3]u8 = t1;
-            try expect(arr1[0] == 42);
-            try expect(arr1[1] == 56);
-            try expect(arr1[2] == 54);
-
-            var x2: U = .{ .a = 42 };
-            const t2 = &.{ x2, .{ .b = true }, .{ .c = "hello" } };
-            var arr2: *const [3]U = t2;
-            try expect(arr2[0].a == 42);
-            try expect(arr2[1].b == true);
-            try expect(mem.eql(u8, arr2[2].c, "hello"));
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
test/behavior/array_stage1.zig
@@ -0,0 +1,489 @@
+const std = @import("std");
+const testing = std.testing;
+const mem = std.mem;
+const expect = testing.expect;
+const expectEqual = testing.expectEqual;
+
+test "arrays" {
+    var array: [5]u32 = undefined;
+
+    var i: u32 = 0;
+    while (i < 5) {
+        array[i] = i + 1;
+        i = array[i];
+    }
+
+    i = 0;
+    var accumulator = @as(u32, 0);
+    while (i < 5) {
+        accumulator += array[i];
+
+        i += 1;
+    }
+
+    try expect(accumulator == 15);
+    try expect(getArrayLen(&array) == 5);
+}
+fn getArrayLen(a: []const u32) usize {
+    return a.len;
+}
+
+test "array with sentinels" {
+    const S = struct {
+        fn doTheTest(is_ct: bool) !void {
+            if (is_ct) {
+                var zero_sized: [0:0xde]u8 = [_:0xde]u8{};
+                // Disabled at runtime because of
+                // https://github.com/ziglang/zig/issues/4372
+                try expectEqual(@as(u8, 0xde), zero_sized[0]);
+                var reinterpreted = @ptrCast(*[1]u8, &zero_sized);
+                try expectEqual(@as(u8, 0xde), reinterpreted[0]);
+            }
+            var arr: [3:0x55]u8 = undefined;
+            // Make sure the sentinel pointer is pointing after the last element
+            if (!is_ct) {
+                const sentinel_ptr = @ptrToInt(&arr[3]);
+                const last_elem_ptr = @ptrToInt(&arr[2]);
+                try expectEqual(@as(usize, 1), sentinel_ptr - last_elem_ptr);
+            }
+            // Make sure the sentinel is writeable
+            arr[3] = 0x55;
+        }
+    };
+
+    try S.doTheTest(false);
+    comptime try S.doTheTest(true);
+}
+
+test "void arrays" {
+    var array: [4]void = undefined;
+    array[0] = void{};
+    array[1] = array[2];
+    try expect(@sizeOf(@TypeOf(array)) == 0);
+    try expect(array.len == 4);
+}
+
+test "array literal" {
+    const hex_mult = [_]u16{
+        4096,
+        256,
+        16,
+        1,
+    };
+
+    try expect(hex_mult.len == 4);
+    try expect(hex_mult[1] == 256);
+}
+
+test "array dot len const expr" {
+    try expect(comptime x: {
+        break :x some_array.len == 4;
+    });
+}
+
+const ArrayDotLenConstExpr = struct {
+    y: [some_array.len]u8,
+};
+const some_array = [_]u8{
+    0,
+    1,
+    2,
+    3,
+};
+
+test "nested arrays" {
+    const array_of_strings = [_][]const u8{
+        "hello",
+        "this",
+        "is",
+        "my",
+        "thing",
+    };
+    for (array_of_strings) |s, i| {
+        if (i == 0) try expect(mem.eql(u8, s, "hello"));
+        if (i == 1) try expect(mem.eql(u8, s, "this"));
+        if (i == 2) try expect(mem.eql(u8, s, "is"));
+        if (i == 3) try expect(mem.eql(u8, s, "my"));
+        if (i == 4) try expect(mem.eql(u8, s, "thing"));
+    }
+}
+
+var s_array: [8]Sub = undefined;
+const Sub = struct {
+    b: u8,
+};
+const Str = struct {
+    a: []Sub,
+};
+test "set global var array via slice embedded in struct" {
+    var s = Str{ .a = s_array[0..] };
+
+    s.a[0].b = 1;
+    s.a[1].b = 2;
+    s.a[2].b = 3;
+
+    try expect(s_array[0].b == 1);
+    try expect(s_array[1].b == 2);
+    try expect(s_array[2].b == 3);
+}
+
+test "array literal with specified size" {
+    var array = [2]u8{
+        1,
+        2,
+    };
+    try expect(array[0] == 1);
+    try expect(array[1] == 2);
+}
+
+test "array len field" {
+    var arr = [4]u8{ 0, 0, 0, 0 };
+    var ptr = &arr;
+    try expect(arr.len == 4);
+    comptime try expect(arr.len == 4);
+    try expect(ptr.len == 4);
+    comptime try expect(ptr.len == 4);
+}
+
+test "single-item pointer to array indexing and slicing" {
+    try testSingleItemPtrArrayIndexSlice();
+    comptime try testSingleItemPtrArrayIndexSlice();
+}
+
+fn testSingleItemPtrArrayIndexSlice() !void {
+    {
+        var array: [4]u8 = "aaaa".*;
+        doSomeMangling(&array);
+        try expect(mem.eql(u8, "azya", &array));
+    }
+    {
+        var array = "aaaa".*;
+        doSomeMangling(&array);
+        try expect(mem.eql(u8, "azya", &array));
+    }
+}
+
+fn doSomeMangling(array: *[4]u8) void {
+    array[1] = 'z';
+    array[2..3][0] = 'y';
+}
+
+test "implicit cast single-item pointer" {
+    try testImplicitCastSingleItemPtr();
+    comptime try testImplicitCastSingleItemPtr();
+}
+
+fn testImplicitCastSingleItemPtr() !void {
+    var byte: u8 = 100;
+    const slice = @as(*[1]u8, &byte)[0..];
+    slice[0] += 1;
+    try expect(byte == 101);
+}
+
+fn testArrayByValAtComptime(b: [2]u8) u8 {
+    return b[0];
+}
+
+test "comptime evalutating function that takes array by value" {
+    const arr = [_]u8{ 0, 1 };
+    _ = comptime testArrayByValAtComptime(arr);
+    _ = comptime testArrayByValAtComptime(arr);
+}
+
+test "implicit comptime in array type size" {
+    var arr: [plusOne(10)]bool = undefined;
+    try expect(arr.len == 11);
+}
+
+fn plusOne(x: u32) u32 {
+    return x + 1;
+}
+
+test "runtime initialize array elem and then implicit cast to slice" {
+    var two: i32 = 2;
+    const x: []const i32 = &[_]i32{two};
+    try expect(x[0] == 2);
+}
+
+test "array literal as argument to function" {
+    const S = struct {
+        fn entry(two: i32) !void {
+            try foo(&[_]i32{
+                1,
+                2,
+                3,
+            });
+            try foo(&[_]i32{
+                1,
+                two,
+                3,
+            });
+            try foo2(true, &[_]i32{
+                1,
+                2,
+                3,
+            });
+            try foo2(true, &[_]i32{
+                1,
+                two,
+                3,
+            });
+        }
+        fn foo(x: []const i32) !void {
+            try expect(x[0] == 1);
+            try expect(x[1] == 2);
+            try expect(x[2] == 3);
+        }
+        fn foo2(trash: bool, x: []const i32) !void {
+            try expect(trash);
+            try expect(x[0] == 1);
+            try expect(x[1] == 2);
+            try expect(x[2] == 3);
+        }
+    };
+    try S.entry(2);
+    comptime try S.entry(2);
+}
+
+test "double nested array to const slice cast in array literal" {
+    const S = struct {
+        fn entry(two: i32) !void {
+            const cases = [_][]const []const i32{
+                &[_][]const i32{&[_]i32{1}},
+                &[_][]const i32{&[_]i32{ 2, 3 }},
+                &[_][]const i32{
+                    &[_]i32{4},
+                    &[_]i32{ 5, 6, 7 },
+                },
+            };
+            try check(&cases);
+
+            const cases2 = [_][]const i32{
+                &[_]i32{1},
+                &[_]i32{ two, 3 },
+            };
+            try expect(cases2.len == 2);
+            try expect(cases2[0].len == 1);
+            try expect(cases2[0][0] == 1);
+            try expect(cases2[1].len == 2);
+            try expect(cases2[1][0] == 2);
+            try expect(cases2[1][1] == 3);
+
+            const cases3 = [_][]const []const i32{
+                &[_][]const i32{&[_]i32{1}},
+                &[_][]const i32{&[_]i32{ two, 3 }},
+                &[_][]const i32{
+                    &[_]i32{4},
+                    &[_]i32{ 5, 6, 7 },
+                },
+            };
+            try check(&cases3);
+        }
+
+        fn check(cases: []const []const []const i32) !void {
+            try expect(cases.len == 3);
+            try expect(cases[0].len == 1);
+            try expect(cases[0][0].len == 1);
+            try expect(cases[0][0][0] == 1);
+            try expect(cases[1].len == 1);
+            try expect(cases[1][0].len == 2);
+            try expect(cases[1][0][0] == 2);
+            try expect(cases[1][0][1] == 3);
+            try expect(cases[2].len == 2);
+            try expect(cases[2][0].len == 1);
+            try expect(cases[2][0][0] == 4);
+            try expect(cases[2][1].len == 3);
+            try expect(cases[2][1][0] == 5);
+            try expect(cases[2][1][1] == 6);
+            try expect(cases[2][1][2] == 7);
+        }
+    };
+    try S.entry(2);
+    comptime try S.entry(2);
+}
+
+test "read/write through global variable array of struct fields initialized via array mult" {
+    const S = struct {
+        fn doTheTest() !void {
+            try expect(storage[0].term == 1);
+            storage[0] = MyStruct{ .term = 123 };
+            try expect(storage[0].term == 123);
+        }
+
+        pub const MyStruct = struct {
+            term: usize,
+        };
+
+        var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
+    };
+    try S.doTheTest();
+}
+
+test "implicit cast zero sized array ptr to slice" {
+    {
+        var b = "".*;
+        const c: []const u8 = &b;
+        try expect(c.len == 0);
+    }
+    {
+        var b: [0]u8 = "".*;
+        const c: []const u8 = &b;
+        try expect(c.len == 0);
+    }
+}
+
+test "anonymous list literal syntax" {
+    const S = struct {
+        fn doTheTest() !void {
+            var array: [4]u8 = .{ 1, 2, 3, 4 };
+            try expect(array[0] == 1);
+            try expect(array[1] == 2);
+            try expect(array[2] == 3);
+            try expect(array[3] == 4);
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "anonymous literal in array" {
+    const S = struct {
+        const Foo = struct {
+            a: usize = 2,
+            b: usize = 4,
+        };
+        fn doTheTest() !void {
+            var array: [2]Foo = .{
+                .{ .a = 3 },
+                .{ .b = 3 },
+            };
+            try expect(array[0].a == 3);
+            try expect(array[0].b == 4);
+            try expect(array[1].a == 2);
+            try expect(array[1].b == 3);
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "access the null element of a null terminated array" {
+    const S = struct {
+        fn doTheTest() !void {
+            var array: [4:0]u8 = .{ 'a', 'o', 'e', 'u' };
+            try expect(array[4] == 0);
+            var len: usize = 4;
+            try expect(array[len] == 0);
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "type deduction for array subscript expression" {
+    const S = struct {
+        fn doTheTest() !void {
+            var array = [_]u8{ 0x55, 0xAA };
+            var v0 = true;
+            try expectEqual(@as(u8, 0xAA), array[if (v0) 1 else 0]);
+            var v1 = false;
+            try expectEqual(@as(u8, 0x55), array[if (v1) 1 else 0]);
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "sentinel element count towards the ABI size calculation" {
+    const S = struct {
+        fn doTheTest() !void {
+            const T = packed struct {
+                fill_pre: u8 = 0x55,
+                data: [0:0]u8 = undefined,
+                fill_post: u8 = 0xAA,
+            };
+            var x = T{};
+            var as_slice = mem.asBytes(&x);
+            try expectEqual(@as(usize, 3), as_slice.len);
+            try expectEqual(@as(u8, 0x55), as_slice[0]);
+            try expectEqual(@as(u8, 0xAA), as_slice[2]);
+        }
+    };
+
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "zero-sized array with recursive type definition" {
+    const U = struct {
+        fn foo(comptime T: type, comptime n: usize) type {
+            return struct {
+                s: [n]T,
+                x: usize = n,
+            };
+        }
+    };
+
+    const S = struct {
+        list: U.foo(@This(), 0),
+    };
+
+    var t: S = .{ .list = .{ .s = undefined } };
+    try expectEqual(@as(usize, 0), t.list.x);
+}
+
+test "type coercion of anon struct literal to array" {
+    const S = struct {
+        const U = union {
+            a: u32,
+            b: bool,
+            c: []const u8,
+        };
+
+        fn doTheTest() !void {
+            var x1: u8 = 42;
+            const t1 = .{ x1, 56, 54 };
+            var arr1: [3]u8 = t1;
+            try expect(arr1[0] == 42);
+            try expect(arr1[1] == 56);
+            try expect(arr1[2] == 54);
+
+            var x2: U = .{ .a = 42 };
+            const t2 = .{ x2, .{ .b = true }, .{ .c = "hello" } };
+            var arr2: [3]U = t2;
+            try expect(arr2[0].a == 42);
+            try expect(arr2[1].b == true);
+            try expect(mem.eql(u8, arr2[2].c, "hello"));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "type coercion of pointer to anon struct literal to pointer to array" {
+    const S = struct {
+        const U = union {
+            a: u32,
+            b: bool,
+            c: []const u8,
+        };
+
+        fn doTheTest() !void {
+            var x1: u8 = 42;
+            const t1 = &.{ x1, 56, 54 };
+            var arr1: *const [3]u8 = t1;
+            try expect(arr1[0] == 42);
+            try expect(arr1[1] == 56);
+            try expect(arr1[2] == 54);
+
+            var x2: U = .{ .a = 42 };
+            const t2 = &.{ x2, .{ .b = true }, .{ .c = "hello" } };
+            var arr2: *const [3]U = t2;
+            try expect(arr2[0].a == 42);
+            try expect(arr2[1].b == true);
+            try expect(mem.eql(u8, arr2[2].c, "hello"));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
test/behavior/eval.zig
@@ -130,3 +130,21 @@ test "no undeclared identifier error in unanalyzed branches" {
         lol_this_doesnt_exist = nonsense;
     }
 }
+
+test "a type constructed in a global expression" {
+    var l: List = undefined;
+    l.array[0] = 10;
+    l.array[1] = 11;
+    l.array[2] = 12;
+    const ptr = @ptrCast([*]u8, &l.array);
+    try expect(ptr[0] == 10);
+    try expect(ptr[1] == 11);
+    try expect(ptr[2] == 12);
+}
+
+const List = blk: {
+    const T = [10]u8;
+    break :blk struct {
+        array: T,
+    };
+};
test/stage2/llvm.zig
@@ -32,18 +32,20 @@ pub fn addCases(ctx: *TestContext) !void {
         var case = ctx.exeUsingLlvmBackend("shift right + left", linux_x64);
 
         case.addCompareOutput(
-            \\pub export fn main() void {
+            \\pub export fn main() c_int {
             \\    var i: u32 = 16;
             \\    assert(i >> 1, 8);
+            \\    return 0;
             \\}
             \\fn assert(a: u32, b: u32) void {
             \\    if (a != b) unreachable;
             \\}
         , "");
         case.addCompareOutput(
-            \\pub export fn main() void {
+            \\pub export fn main() c_int {
             \\    var i: u32 = 16;
             \\    assert(i << 1, 32);
+            \\    return 0;
             \\}
             \\fn assert(a: u32, b: u32) void {
             \\    if (a != b) unreachable;
test/behavior.zig
@@ -9,12 +9,13 @@ test {
     _ = @import("behavior/pointers.zig");
     _ = @import("behavior/if.zig");
     _ = @import("behavior/cast.zig");
+    _ = @import("behavior/array.zig");
 
     if (!builtin.zig_is_stage2) {
         // Tests that only pass for stage1.
         _ = @import("behavior/align.zig");
         _ = @import("behavior/alignof.zig");
-        _ = @import("behavior/array.zig");
+        _ = @import("behavior/array_stage1.zig");
         if (builtin.os.tag != .wasi) {
             _ = @import("behavior/asm.zig");
             _ = @import("behavior/async_fn.zig");