Commit 7b8cb881df

Andrew Kelley <andrew@ziglang.org>
2021-07-24 07:23:03
stage2: improvements towards `zig test`
* There is now a main_pkg in addition to root_pkg. They are usually the same. When using `zig test`, main_pkg is the user's source file and root_pkg has the test runner. * scanDecl no longer looks for test decls outside the package being tested. honoring `--test-filter` is still TODO. * test runner main function has a void return value rather than `anyerror!void` * Sema is improved to generate better AIR for for loops on slices. * Sema: fix incorrect capacity calculation in zirBoolBr * Sema: add compile errors for trying to use slice fields as an lvalue. * Sema: fix type coercion for error unions * Sema: fix analyzeVarRef generating garbage AIR * C codegen: fix renderValue for error unions with 0 bit payload * C codegen: implement function pointer calls * CLI: fix usage text Adds 4 new AIR instructions: * slice_len, slice_ptr: to get the ptr and len fields of a slice. * slice_elem_val, ptr_slice_elem_val: to get the element value of a slice, and a pointer to a slice. AstGen gains a new functionality: * One of the unused flags of struct decls is now used to indicate structs that are known to have non-zero size based on the AST alone.
1 parent f979810
lib/std/special/test_runner.zig
@@ -21,9 +21,9 @@ fn processArgs() void {
     std.testing.zig_exe_path = args[1];
 }
 
-pub fn main() anyerror!void {
+pub fn main() void {
     if (builtin.zig_is_stage2) {
-        return main2();
+        return main2() catch @panic("test failure");
     }
     processArgs();
     const test_fn_list = builtin.test_functions;
src/codegen/c.zig
@@ -237,7 +237,8 @@ pub const DeclGen = struct {
             // This should lower to 0xaa bytes in safe modes, and for unsafe modes should
             // lower to leaving variables uninitialized (that might need to be implemented
             // outside of this function).
-            return dg.fail("TODO: C backend: implement renderValue undef", .{});
+            return writer.writeAll("{}");
+            //return dg.fail("TODO: C backend: implement renderValue undef", .{});
         }
         switch (t.zigTypeTag()) {
             .Int => {
@@ -361,18 +362,27 @@ pub const DeclGen = struct {
                 }
             },
             .ErrorSet => {
-                const payload = val.castTag(.@"error").?;
-                // error values will be #defined at the top of the file
-                return writer.print("zig_error_{s}", .{payload.data.name});
+                switch (val.tag()) {
+                    .@"error" => {
+                        const payload = val.castTag(.@"error").?;
+                        // error values will be #defined at the top of the file
+                        return writer.print("zig_error_{s}", .{payload.data.name});
+                    },
+                    else => {
+                        // In this case we are rendering an error union which has a
+                        // 0 bits payload.
+                        return writer.writeAll("0");
+                    },
+                }
             },
             .ErrorUnion => {
                 const error_type = t.errorUnionSet();
-                const payload_type = t.errorUnionChild();
-                const data = val.castTag(.error_union).?.data;
+                const payload_type = t.errorUnionPayload();
+                const sub_val = val.castTag(.error_union).?.data;
 
                 if (!payload_type.hasCodeGenBits()) {
                     // We use the error type directly as the type.
-                    return dg.renderValue(writer, error_type, data);
+                    return dg.renderValue(writer, error_type, sub_val);
                 }
 
                 try writer.writeByte('(');
@@ -383,7 +393,7 @@ pub const DeclGen = struct {
                     try dg.renderValue(
                         writer,
                         error_type,
-                        data,
+                        sub_val,
                     );
                     try writer.writeAll(" }");
                 } else {
@@ -391,7 +401,7 @@ pub const DeclGen = struct {
                     try dg.renderValue(
                         writer,
                         payload_type,
-                        data,
+                        sub_val,
                     );
                     try writer.writeAll(", .error = 0 }");
                 }
@@ -616,7 +626,7 @@ pub const DeclGen = struct {
                 if (dg.typedefs.get(t)) |some| {
                     return w.writeAll(some.name);
                 }
-                const child_type = t.errorUnionChild();
+                const child_type = t.errorUnionPayload();
                 const err_set_type = t.errorUnionSet();
 
                 if (!child_type.hasCodeGenBits()) {
@@ -926,6 +936,11 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
             .ref              => try airRef(o, inst),
             .struct_field_ptr => try airStructFieldPtr(o, inst),
             .varptr           => try airVarPtr(o, inst),
+            .slice_ptr        => try airSliceField(o, inst, ".ptr;\n"),
+            .slice_len        => try airSliceField(o, inst, ".len;\n"),
+
+            .slice_elem_val     => try airSliceElemVal(o, inst, "["),
+            .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["),
 
             .unwrap_errunion_payload     => try airUnwrapErrUnionPay(o, inst),
             .unwrap_errunion_err         => try airUnwrapErrUnionErr(o, inst),
@@ -948,6 +963,37 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
     try writer.writeAll("}");
 }
 
+fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue {
+    if (o.liveness.isUnused(inst))
+        return CValue.none;
+
+    const ty_op = o.air.instructions.items(.data)[inst].ty_op;
+    const operand = try o.resolveInst(ty_op.operand);
+    const writer = o.writer();
+    const local = try o.allocLocal(Type.initTag(.usize), .Const);
+    try writer.writeAll(" = ");
+    try o.writeCValue(writer, operand);
+    try writer.writeAll(suffix);
+    return local;
+}
+
+fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
+    if (o.liveness.isUnused(inst))
+        return CValue.none;
+
+    const bin_op = o.air.instructions.items(.data)[inst].bin_op;
+    const slice = try o.resolveInst(bin_op.lhs);
+    const index = try o.resolveInst(bin_op.rhs);
+    const writer = o.writer();
+    const local = try o.allocLocal(o.air.typeOfIndex(inst), .Const);
+    try writer.writeAll(" = ");
+    try o.writeCValue(writer, slice);
+    try writer.writeAll(prefix);
+    try o.writeCValue(writer, index);
+    try writer.writeAll("];\n");
+    return local;
+}
+
 fn airVarPtr(o: *Object, inst: Air.Inst.Index) !CValue {
     const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
     const variable = o.air.variables[ty_pl.payload];
@@ -1233,6 +1279,20 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
     const pl_op = o.air.instructions.items(.data)[inst].pl_op;
     const extra = o.air.extraData(Air.Call, pl_op.payload);
     const args = @bitCast([]const Air.Inst.Ref, o.air.extra[extra.end..][0..extra.data.args_len]);
+    const fn_ty = o.air.typeOf(pl_op.operand);
+    const ret_ty = fn_ty.fnReturnType();
+    const unused_result = o.liveness.isUnused(inst);
+    const writer = o.writer();
+
+    var result_local: CValue = .none;
+    if (unused_result) {
+        if (ret_ty.hasCodeGenBits()) {
+            try writer.print("(void)", .{});
+        }
+    } else {
+        result_local = try o.allocLocal(ret_ty, .Const);
+        try writer.writeAll(" = ");
+    }
 
     if (o.air.value(pl_op.operand)) |func_val| {
         const fn_decl = if (func_val.castTag(.extern_fn)) |extern_fn|
@@ -1242,38 +1302,26 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
         else
             unreachable;
 
-        const fn_ty = fn_decl.ty;
-        const ret_ty = fn_ty.fnReturnType();
-        const unused_result = o.liveness.isUnused(inst);
-        var result_local: CValue = .none;
+        try writer.writeAll(mem.spanZ(fn_decl.name));
+    } else {
+        const callee = try o.resolveInst(pl_op.operand);
+        try o.writeCValue(writer, callee);
+    }
 
-        const writer = o.writer();
-        if (unused_result) {
-            if (ret_ty.hasCodeGenBits()) {
-                try writer.print("(void)", .{});
-            }
-        } else {
-            result_local = try o.allocLocal(ret_ty, .Const);
-            try writer.writeAll(" = ");
+    try writer.writeAll("(");
+    for (args) |arg, i| {
+        if (i != 0) {
+            try writer.writeAll(", ");
         }
-        const fn_name = mem.spanZ(fn_decl.name);
-        try writer.print("{s}(", .{fn_name});
-        for (args) |arg, i| {
-            if (i != 0) {
-                try writer.writeAll(", ");
-            }
-            if (o.air.value(arg)) |val| {
-                try o.dg.renderValue(writer, o.air.typeOf(arg), val);
-            } else {
-                const val = try o.resolveInst(arg);
-                try o.writeCValue(writer, val);
-            }
+        if (o.air.value(arg)) |val| {
+            try o.dg.renderValue(writer, o.air.typeOf(arg), val);
+        } else {
+            const val = try o.resolveInst(arg);
+            try o.writeCValue(writer, val);
         }
-        try writer.writeAll(");\n");
-        return result_local;
-    } else {
-        return o.dg.fail("TODO: C backend: implement function pointers", .{});
     }
+    try writer.writeAll(");\n");
+    return result_local;
 }
 
 fn airDbgStmt(o: *Object, inst: Air.Inst.Index) !CValue {
@@ -1643,7 +1691,7 @@ fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
     const operand = try o.resolveInst(ty_op.operand);
     const operand_ty = o.air.typeOf(ty_op.operand);
 
-    const payload_ty = operand_ty.errorUnionChild();
+    const payload_ty = operand_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) {
         if (operand_ty.zigTypeTag() == .Pointer) {
             const local = try o.allocLocal(inst_ty, .Const);
@@ -1675,7 +1723,7 @@ fn airUnwrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue {
     const operand = try o.resolveInst(ty_op.operand);
     const operand_ty = o.air.typeOf(ty_op.operand);
 
-    const payload_ty = operand_ty.errorUnionChild();
+    const payload_ty = operand_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) {
         return CValue.none;
     }
@@ -1760,7 +1808,7 @@ fn airIsErr(
     const operand = try o.resolveInst(un_op);
     const operand_ty = o.air.typeOf(un_op);
     const local = try o.allocLocal(Type.initTag(.bool), .Const);
-    const payload_ty = operand_ty.errorUnionChild();
+    const payload_ty = operand_ty.errorUnionPayload();
     if (!payload_ty.hasCodeGenBits()) {
         try writer.print(" = {s}", .{deref_prefix});
         try o.writeCValue(writer, operand);
src/codegen/wasm.zig
@@ -646,7 +646,7 @@ pub const Context = struct {
                 } };
             },
             .ErrorUnion => {
-                const payload_type = ty.errorUnionChild();
+                const payload_type = ty.errorUnionPayload();
                 const val_type = try self.genValtype(payload_type);
 
                 // we emit the error value as the first local, and the payload as the following.
@@ -699,7 +699,7 @@ pub const Context = struct {
             .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
             .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
             .ErrorUnion => {
-                const val_type = try self.genValtype(return_type.errorUnionChild());
+                const val_type = try self.genValtype(return_type.errorUnionPayload());
 
                 // write down the amount of return values
                 try leb.writeULEB128(writer, @as(u32, 2));
@@ -1055,7 +1055,7 @@ pub const Context = struct {
             .ErrorUnion => {
                 const data = value.castTag(.error_union).?.data;
                 const error_type = ty.errorUnionSet();
-                const payload_type = ty.errorUnionChild();
+                const payload_type = ty.errorUnionPayload();
                 if (value.getError()) |_| {
                     // write the error value
                     try self.emitConstant(data, error_type);
src/stage1/stage1.cpp
@@ -126,7 +126,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) {
 
     g->main_progress_node = stage1->main_progress_node;
 
-    add_package(g, stage1->root_pkg, g->main_pkg);
+    add_package(g, stage1->main_pkg, g->main_pkg);
 
     codegen_build_object(g);
 }
src/stage1/stage1.h
@@ -176,7 +176,7 @@ struct ZigStage1 {
     size_t test_name_prefix_len;
 
     void *userdata;
-    struct ZigStage1Pkg *root_pkg;
+    struct ZigStage1Pkg *main_pkg;
     struct Stage2ProgressNode *main_progress_node;
 
     enum CodeModel code_model;
src/stage1/zig0.cpp
@@ -465,7 +465,7 @@ int main(int argc, char **argv) {
     stage1->verbose_llvm_cpu_features = verbose_llvm_cpu_features;
     stage1->emit_o_ptr = emit_bin_path;
     stage1->emit_o_len = strlen(emit_bin_path);
-    stage1->root_pkg = cur_pkg;
+    stage1->main_pkg = cur_pkg;
     stage1->err_color = color;
     stage1->link_libc = link_libc;
     stage1->link_libcpp = link_libcpp;
src/Air.zig
@@ -247,6 +247,21 @@ pub const Inst = struct {
         /// Given a pointer to a struct and a field index, returns a pointer to the field.
         /// Uses the `ty_pl` field, payload is `StructField`.
         struct_field_ptr,
+        /// Given a slice value, return the length.
+        /// Result type is always usize.
+        /// Uses the `ty_op` field.
+        slice_len,
+        /// Given a slice value, return the pointer.
+        /// Uses the `ty_op` field.
+        slice_ptr,
+        /// Given a slice value, and element index, return the element value at that index.
+        /// Result type is the element type of the slice operand.
+        /// Uses the `bin_op` field.
+        slice_elem_val,
+        /// Given a pointer to a slice, and element index, return the element value at that index.
+        /// Result type is the element type of the slice operand (2 element type operations).
+        /// Uses the `bin_op` field.
+        ptr_slice_elem_val,
 
         pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
             return switch (op) {
@@ -450,6 +465,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .unwrap_errunion_err_ptr,
         .wrap_errunion_payload,
         .wrap_errunion_err,
+        .slice_ptr,
         => return air.getRefType(datas[inst].ty_op.ty),
 
         .loop,
@@ -465,12 +481,24 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .store,
         => return Type.initTag(.void),
 
-        .ptrtoint => return Type.initTag(.usize),
+        .ptrtoint,
+        .slice_len,
+        => return Type.initTag(.usize),
 
         .call => {
             const callee_ty = air.typeOf(datas[inst].pl_op.operand);
             return callee_ty.fnReturnType();
         },
+
+        .slice_elem_val => {
+            const slice_ty = air.typeOf(datas[inst].bin_op.lhs);
+            return slice_ty.elemType();
+        },
+        .ptr_slice_elem_val => {
+            const ptr_slice_ty = air.typeOf(datas[inst].bin_op.lhs);
+            const slice_ty = ptr_slice_ty.elemType();
+            return slice_ty.elemType();
+        },
     }
 }
 
src/AstGen.zig
@@ -3470,6 +3470,7 @@ fn structDeclInner(
             .fields_len = 0,
             .body_len = 0,
             .decls_len = 0,
+            .known_has_bits = false,
         });
         return indexToRef(decl_inst);
     }
@@ -3510,6 +3511,7 @@ fn structDeclInner(
     var bit_bag = ArrayListUnmanaged(u32){};
     defer bit_bag.deinit(gpa);
 
+    var known_has_bits = false;
     var cur_bit_bag: u32 = 0;
     var field_index: usize = 0;
     for (container_decl.ast.members) |member_node| {
@@ -3657,6 +3659,8 @@ fn structDeclInner(
             try typeExpr(&block_scope, &block_scope.base, member.ast.type_expr);
         fields_data.appendAssumeCapacity(@enumToInt(field_type));
 
+        known_has_bits = known_has_bits or nodeImpliesRuntimeBits(tree, member.ast.type_expr);
+
         const have_align = member.ast.align_expr != 0;
         const have_value = member.ast.value_expr != 0;
         const is_comptime = member.comptime_token != null;
@@ -3706,6 +3710,7 @@ fn structDeclInner(
         .body_len = @intCast(u32, block_scope.instructions.items.len),
         .fields_len = @intCast(u32, field_index),
         .decls_len = @intCast(u32, wip_decls.decl_index),
+        .known_has_bits = known_has_bits,
     });
 
     try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
@@ -8150,6 +8155,189 @@ fn nodeMayEvalToError(tree: *const ast.Tree, start_node: ast.Node.Index) enum {
     }
 }
 
+fn nodeImpliesRuntimeBits(tree: *const ast.Tree, start_node: ast.Node.Index) bool {
+    const node_tags = tree.nodes.items(.tag);
+    const node_datas = tree.nodes.items(.data);
+
+    var node = start_node;
+    while (true) {
+        switch (node_tags[node]) {
+            .root,
+            .@"usingnamespace",
+            .test_decl,
+            .switch_case,
+            .switch_case_one,
+            .container_field_init,
+            .container_field_align,
+            .container_field,
+            .asm_output,
+            .asm_input,
+            .global_var_decl,
+            .local_var_decl,
+            .simple_var_decl,
+            .aligned_var_decl,
+            => unreachable,
+
+            .@"return",
+            .@"break",
+            .@"continue",
+            .bit_not,
+            .bool_not,
+            .@"defer",
+            .@"errdefer",
+            .address_of,
+            .negation,
+            .negation_wrap,
+            .@"resume",
+            .array_type,
+            .@"suspend",
+            .@"anytype",
+            .fn_decl,
+            .anyframe_literal,
+            .integer_literal,
+            .float_literal,
+            .enum_literal,
+            .string_literal,
+            .multiline_string_literal,
+            .char_literal,
+            .true_literal,
+            .false_literal,
+            .null_literal,
+            .undefined_literal,
+            .unreachable_literal,
+            .identifier,
+            .error_set_decl,
+            .container_decl,
+            .container_decl_trailing,
+            .container_decl_two,
+            .container_decl_two_trailing,
+            .container_decl_arg,
+            .container_decl_arg_trailing,
+            .tagged_union,
+            .tagged_union_trailing,
+            .tagged_union_two,
+            .tagged_union_two_trailing,
+            .tagged_union_enum_tag,
+            .tagged_union_enum_tag_trailing,
+            .@"asm",
+            .asm_simple,
+            .add,
+            .add_wrap,
+            .array_cat,
+            .array_mult,
+            .assign,
+            .assign_bit_and,
+            .assign_bit_or,
+            .assign_bit_shift_left,
+            .assign_bit_shift_right,
+            .assign_bit_xor,
+            .assign_div,
+            .assign_sub,
+            .assign_sub_wrap,
+            .assign_mod,
+            .assign_add,
+            .assign_add_wrap,
+            .assign_mul,
+            .assign_mul_wrap,
+            .bang_equal,
+            .bit_and,
+            .bit_or,
+            .bit_shift_left,
+            .bit_shift_right,
+            .bit_xor,
+            .bool_and,
+            .bool_or,
+            .div,
+            .equal_equal,
+            .error_union,
+            .greater_or_equal,
+            .greater_than,
+            .less_or_equal,
+            .less_than,
+            .merge_error_sets,
+            .mod,
+            .mul,
+            .mul_wrap,
+            .switch_range,
+            .field_access,
+            .sub,
+            .sub_wrap,
+            .slice,
+            .slice_open,
+            .slice_sentinel,
+            .deref,
+            .array_access,
+            .error_value,
+            .while_simple,
+            .while_cont,
+            .for_simple,
+            .if_simple,
+            .@"catch",
+            .@"orelse",
+            .array_init_one,
+            .array_init_one_comma,
+            .array_init_dot_two,
+            .array_init_dot_two_comma,
+            .array_init_dot,
+            .array_init_dot_comma,
+            .array_init,
+            .array_init_comma,
+            .struct_init_one,
+            .struct_init_one_comma,
+            .struct_init_dot_two,
+            .struct_init_dot_two_comma,
+            .struct_init_dot,
+            .struct_init_dot_comma,
+            .struct_init,
+            .struct_init_comma,
+            .@"while",
+            .@"if",
+            .@"for",
+            .@"switch",
+            .switch_comma,
+            .call_one,
+            .call_one_comma,
+            .async_call_one,
+            .async_call_one_comma,
+            .call,
+            .call_comma,
+            .async_call,
+            .async_call_comma,
+            .block_two,
+            .block_two_semicolon,
+            .block,
+            .block_semicolon,
+            .builtin_call,
+            .builtin_call_comma,
+            .builtin_call_two,
+            .builtin_call_two_comma,
+            => return false,
+
+            // Forward the question to the LHS sub-expression.
+            .grouped_expression,
+            .@"try",
+            .@"await",
+            .@"comptime",
+            .@"nosuspend",
+            .unwrap_optional,
+            => node = node_datas[node].lhs,
+
+            .fn_proto_simple,
+            .fn_proto_multi,
+            .fn_proto_one,
+            .fn_proto,
+            .ptr_type_aligned,
+            .ptr_type_sentinel,
+            .ptr_type,
+            .ptr_type_bit_range,
+            .optional_type,
+            .anyframe_type,
+            .array_type_sentinel,
+            => return true,
+        }
+    }
+}
+
 /// Applies `rl` semantics to `inst`. Expressions which do not do their own handling of
 /// result locations must call this function on their result.
 /// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer.
@@ -9556,6 +9744,7 @@ const GenZir = struct {
         fields_len: u32,
         decls_len: u32,
         layout: std.builtin.TypeInfo.ContainerLayout,
+        known_has_bits: bool,
     }) !void {
         const astgen = gz.astgen;
         const gpa = astgen.gpa;
@@ -9585,6 +9774,7 @@ const GenZir = struct {
                     .has_body_len = args.body_len != 0,
                     .has_fields_len = args.fields_len != 0,
                     .has_decls_len = args.decls_len != 0,
+                    .known_has_bits = args.known_has_bits,
                     .name_strategy = gz.anon_name_strategy,
                     .layout = args.layout,
                 }),
src/codegen.zig
@@ -853,6 +853,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .struct_field_ptr=> try self.airStructFieldPtr(inst),
                     .switch_br       => try self.airSwitch(inst),
                     .varptr          => try self.airVarPtr(inst),
+                    .slice_ptr       => try self.airSlicePtr(inst),
+                    .slice_len       => try self.airSliceLen(inst),
+
+                    .slice_elem_val      => try self.airSliceElemVal(inst),
+                    .ptr_slice_elem_val  => try self.airPtrSliceElemVal(inst),
 
                     .constant => unreachable, // excluded from function bodies
                     .const_ty => unreachable, // excluded from function bodies
@@ -1333,6 +1338,38 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAir(inst, result, .{ .none, .none, .none });
         }
 
+        fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
+            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement slice_ptr for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+        }
+
+        fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
+            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement slice_len for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+        }
+
+        fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement slice_elem_val for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+        }
+
+        fn airPtrSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement ptr_slice_elem_val for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+        }
+
         fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
             if (!self.liveness.operandDies(inst, op_index))
                 return false;
src/Compilation.zig
@@ -622,7 +622,7 @@ pub const InitOptions = struct {
     global_cache_directory: Directory,
     target: Target,
     root_name: []const u8,
-    root_pkg: ?*Package,
+    main_pkg: ?*Package,
     output_mode: std.builtin.OutputMode,
     thread_pool: *ThreadPool,
     dynamic_linker: ?[]const u8 = null,
@@ -826,7 +826,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         const ofmt = options.object_format orelse options.target.getObjectFormat();
 
         const use_stage1 = options.use_stage1 orelse blk: {
-            // Even though we may have no Zig code to compile (depending on `options.root_pkg`),
+            // Even though we may have no Zig code to compile (depending on `options.main_pkg`),
             // we may need to use stage1 for building compiler-rt and other dependencies.
 
             if (build_options.omit_stage2)
@@ -846,7 +846,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                 break :blk explicit;
 
             // If we have no zig code to compile, no need for LLVM.
-            if (options.root_pkg == null)
+            if (options.main_pkg == null)
                 break :blk false;
 
             // If we are outputting .c code we must use Zig backend.
@@ -929,7 +929,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             if (use_llvm) {
                 // If stage1 generates an object file, self-hosted linker is not
                 // yet sophisticated enough to handle that.
-                break :blk options.root_pkg != null;
+                break :blk options.main_pkg != null;
             }
 
             break :blk false;
@@ -1159,7 +1159,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         if (options.target.os.tag == .wasi) cache.hash.add(wasi_exec_model);
         // TODO audit this and make sure everything is in it
 
-        const module: ?*Module = if (options.root_pkg) |root_pkg| blk: {
+        const module: ?*Module = if (options.main_pkg) |main_pkg| blk: {
             // Options that are specific to zig source files, that cannot be
             // modified between incremental updates.
             var hash = cache.hash;
@@ -1169,13 +1169,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             // incremental compilation will handle it, but we do want to namespace different
             // source file names because they are likely different compilations and therefore this
             // would be likely to cause cache hits.
-            hash.addBytes(root_pkg.root_src_path);
-            hash.addOptionalBytes(root_pkg.root_src_directory.path);
+            hash.addBytes(main_pkg.root_src_path);
+            hash.addOptionalBytes(main_pkg.root_src_directory.path);
             {
                 var local_arena = std.heap.ArenaAllocator.init(gpa);
                 defer local_arena.deinit();
                 var seen_table = std.AutoHashMap(*Package, void).init(&local_arena.allocator);
-                try addPackageTableToCacheHash(&hash, &local_arena, root_pkg.table, &seen_table, .path_bytes);
+                try addPackageTableToCacheHash(&hash, &local_arena, main_pkg.table, &seen_table, .path_bytes);
             }
             hash.add(valgrind);
             hash.add(single_threaded);
@@ -1212,9 +1212,26 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             );
             errdefer std_pkg.destroy(gpa);
 
-            try root_pkg.addAndAdopt(gpa, "builtin", builtin_pkg);
-            try root_pkg.add(gpa, "root", root_pkg);
-            try root_pkg.addAndAdopt(gpa, "std", std_pkg);
+            const root_pkg = if (options.is_test) root_pkg: {
+                const test_pkg = try Package.createWithDir(
+                    gpa,
+                    options.zig_lib_directory,
+                    "std" ++ std.fs.path.sep_str ++ "special",
+                    "test_runner.zig",
+                );
+                errdefer test_pkg.destroy(gpa);
+
+                try test_pkg.add(gpa, "builtin", builtin_pkg);
+                try test_pkg.add(gpa, "root", test_pkg);
+                try test_pkg.add(gpa, "std", std_pkg);
+
+                break :root_pkg test_pkg;
+            } else main_pkg;
+            errdefer if (options.is_test) root_pkg.destroy(gpa);
+
+            try main_pkg.addAndAdopt(gpa, "builtin", builtin_pkg);
+            try main_pkg.add(gpa, "root", root_pkg);
+            try main_pkg.addAndAdopt(gpa, "std", std_pkg);
 
             try std_pkg.add(gpa, "builtin", builtin_pkg);
             try std_pkg.add(gpa, "root", root_pkg);
@@ -1258,6 +1275,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             module.* = .{
                 .gpa = gpa,
                 .comp = comp,
+                .main_pkg = main_pkg,
                 .root_pkg = root_pkg,
                 .zig_cache_artifact_directory = zig_cache_artifact_directory,
                 .global_zir_cache = global_zir_cache,
@@ -1684,7 +1702,7 @@ pub fn update(self: *Compilation) !void {
 
         // Make sure std.zig is inside the import_table. We unconditionally need
         // it for start.zig.
-        const std_pkg = module.root_pkg.table.get("std").?;
+        const std_pkg = module.main_pkg.table.get("std").?;
         _ = try module.importPkg(std_pkg);
 
         // Normally we rely on importing std to in turn import the root source file
@@ -1692,7 +1710,7 @@ pub fn update(self: *Compilation) !void {
         // so in order to run AstGen on the root source file we put it into the
         // import_table here.
         if (use_stage1) {
-            _ = try module.importPkg(module.root_pkg);
+            _ = try module.importPkg(module.main_pkg);
         }
 
         // Put a work item in for every known source file to detect if
@@ -3873,7 +3891,7 @@ fn buildOutputFromZig(
     var special_dir = try comp.zig_lib_directory.handle.openDir(special_sub, .{});
     defer special_dir.close();
 
-    var root_pkg: Package = .{
+    var main_pkg: Package = .{
         .root_src_directory = .{
             .path = special_path,
             .handle = special_dir,
@@ -3899,7 +3917,7 @@ fn buildOutputFromZig(
         .zig_lib_directory = comp.zig_lib_directory,
         .target = target,
         .root_name = root_name,
-        .root_pkg = &root_pkg,
+        .main_pkg = &main_pkg,
         .output_mode = output_mode,
         .thread_pool = comp.thread_pool,
         .libc_installation = comp.bin_file.options.libc_installation,
@@ -3969,8 +3987,8 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
     // Here we use the legacy stage1 C++ compiler to compile Zig code.
     const mod = comp.bin_file.options.module.?;
     const directory = mod.zig_cache_artifact_directory; // Just an alias to make it shorter to type.
-    const main_zig_file = try mod.root_pkg.root_src_directory.join(arena, &[_][]const u8{
-        mod.root_pkg.root_src_path,
+    const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{
+        mod.main_pkg.root_src_path,
     });
     const zig_lib_dir = comp.zig_lib_directory.path.?;
     const builtin_zig_path = try directory.join(arena, &[_][]const u8{"builtin.zig"});
@@ -4002,7 +4020,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
     _ = try man.addFile(main_zig_file, null);
     {
         var seen_table = std.AutoHashMap(*Package, void).init(&arena_allocator.allocator);
-        try addPackageTableToCacheHash(&man.hash, &arena_allocator, mod.root_pkg.table, &seen_table, .{ .files = &man });
+        try addPackageTableToCacheHash(&man.hash, &arena_allocator, mod.main_pkg.table, &seen_table, .{ .files = &man });
     }
     man.hash.add(comp.bin_file.options.valgrind);
     man.hash.add(comp.bin_file.options.single_threaded);
@@ -4045,7 +4063,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
             &prev_digest_buf,
         ) catch |err| blk: {
             log.debug("stage1 {s} new_digest={s} error: {s}", .{
-                mod.root_pkg.root_src_path,
+                mod.main_pkg.root_src_path,
                 std.fmt.fmtSliceHexLower(&digest),
                 @errorName(err),
             });
@@ -4057,7 +4075,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
                 break :hit;
 
             log.debug("stage1 {s} digest={s} match - skipping invocation", .{
-                mod.root_pkg.root_src_path,
+                mod.main_pkg.root_src_path,
                 std.fmt.fmtSliceHexLower(&digest),
             });
             var flags_bytes: [1]u8 = undefined;
@@ -4083,7 +4101,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
             return;
         }
         log.debug("stage1 {s} prev_digest={s} new_digest={s}", .{
-            mod.root_pkg.root_src_path,
+            mod.main_pkg.root_src_path,
             std.fmt.fmtSliceHexLower(prev_digest),
             std.fmt.fmtSliceHexLower(&digest),
         });
@@ -4109,7 +4127,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
 
     comp.stage1_cache_manifest = &man;
 
-    const main_pkg_path = mod.root_pkg.root_src_directory.path orelse "";
+    const main_pkg_path = mod.main_pkg.root_src_directory.path orelse "";
 
     const stage1_module = stage1.create(
         @enumToInt(comp.bin_file.options.optimize_mode),
@@ -4142,7 +4160,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
     const emit_llvm_bc_path = try stage1LocPath(arena, comp.emit_llvm_bc, directory);
     const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
     const emit_docs_path = try stage1LocPath(arena, comp.emit_docs, directory);
-    const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null);
+    const stage1_pkg = try createStage1Pkg(arena, "root", mod.main_pkg, null);
     const test_filter = comp.test_filter orelse ""[0..0];
     const test_name_prefix = comp.test_name_prefix orelse ""[0..0];
     const subsystem = if (comp.bin_file.options.subsystem) |s|
@@ -4173,7 +4191,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
         .test_name_prefix_ptr = test_name_prefix.ptr,
         .test_name_prefix_len = test_name_prefix.len,
         .userdata = @ptrToInt(comp),
-        .root_pkg = stage1_pkg,
+        .main_pkg = stage1_pkg,
         .code_model = @enumToInt(comp.bin_file.options.machine_code_model),
         .subsystem = subsystem,
         .err_color = @enumToInt(comp.color),
@@ -4239,7 +4257,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
     // means that the next invocation will have an unnecessary cache miss.
     const stage1_flags_byte = @bitCast(u8, mod.stage1_flags);
     log.debug("stage1 {s} final digest={s} flags={x}", .{
-        mod.root_pkg.root_src_path, std.fmt.fmtSliceHexLower(&digest), stage1_flags_byte,
+        mod.main_pkg.root_src_path, std.fmt.fmtSliceHexLower(&digest), stage1_flags_byte,
     });
     var digest_plus_flags: [digest.len + 2]u8 = undefined;
     digest_plus_flags[0..digest.len].* = digest;
@@ -4333,7 +4351,7 @@ pub fn build_crt_file(
         .zig_lib_directory = comp.zig_lib_directory,
         .target = target,
         .root_name = root_name,
-        .root_pkg = null,
+        .main_pkg = null,
         .output_mode = output_mode,
         .thread_pool = comp.thread_pool,
         .libc_installation = comp.bin_file.options.libc_installation,
src/glibc.zig
@@ -943,7 +943,7 @@ fn buildSharedLib(
         .zig_lib_directory = comp.zig_lib_directory,
         .target = comp.getTarget(),
         .root_name = lib.name,
-        .root_pkg = null,
+        .main_pkg = null,
         .output_mode = .Lib,
         .link_mode = .Dynamic,
         .thread_pool = comp.thread_pool,
src/libcxx.zig
@@ -169,7 +169,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
         .zig_lib_directory = comp.zig_lib_directory,
         .target = target,
         .root_name = root_name,
-        .root_pkg = null,
+        .main_pkg = null,
         .output_mode = output_mode,
         .thread_pool = comp.thread_pool,
         .libc_installation = comp.bin_file.options.libc_installation,
@@ -301,7 +301,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {
         .zig_lib_directory = comp.zig_lib_directory,
         .target = target,
         .root_name = root_name,
-        .root_pkg = null,
+        .main_pkg = null,
         .output_mode = output_mode,
         .thread_pool = comp.thread_pool,
         .libc_installation = comp.bin_file.options.libc_installation,
src/libtsan.zig
@@ -201,7 +201,7 @@ pub fn buildTsan(comp: *Compilation) !void {
         .zig_lib_directory = comp.zig_lib_directory,
         .target = target,
         .root_name = root_name,
-        .root_pkg = null,
+        .main_pkg = null,
         .output_mode = output_mode,
         .thread_pool = comp.thread_pool,
         .libc_installation = comp.bin_file.options.libc_installation,
src/libunwind.zig
@@ -101,7 +101,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {
         .zig_lib_directory = comp.zig_lib_directory,
         .target = target,
         .root_name = root_name,
-        .root_pkg = null,
+        .main_pkg = null,
         .output_mode = output_mode,
         .thread_pool = comp.thread_pool,
         .libc_installation = comp.bin_file.options.libc_installation,
src/Liveness.zig
@@ -243,6 +243,8 @@ fn analyzeInst(
         .bool_and,
         .bool_or,
         .store,
+        .slice_elem_val,
+        .ptr_slice_elem_val,
         => {
             const o = inst_datas[inst].bin_op;
             return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
@@ -273,6 +275,8 @@ fn analyzeInst(
         .unwrap_errunion_err_ptr,
         .wrap_errunion_payload,
         .wrap_errunion_err,
+        .slice_ptr,
+        .slice_len,
         => {
             const o = inst_datas[inst].ty_op;
             return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
src/main.zig
@@ -263,12 +263,12 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
 }
 
 const usage_build_generic =
-    \\Usage: zig build-exe   <options> [files]
-    \\       zig build-lib   <options> [files]
-    \\       zig build-obj   <options> [files]
-    \\       zig test        <options> [files]
-    \\       zig run         <options> [file] [-- [args]]
-    \\       zig translate-c <options> [file]
+    \\Usage: zig build-exe   [options] [files]
+    \\       zig build-lib   [options] [files]
+    \\       zig build-obj   [options] [files]
+    \\       zig test        [options] [files]
+    \\       zig run         [options] [files] [-- [args]]
+    \\       zig translate-c [options] [file]
     \\
     \\Supported file types:
     \\                    .zig    Zig source code
@@ -1915,7 +1915,7 @@ fn buildOutputType(
     };
     defer emit_docs_resolved.deinit();
 
-    const root_pkg: ?*Package = if (root_src_file) |src_path| blk: {
+    const main_pkg: ?*Package = if (root_src_file) |src_path| blk: {
         if (main_pkg_path) |p| {
             const rel_src_path = try fs.path.relative(gpa, p, src_path);
             defer gpa.free(rel_src_path);
@@ -1924,10 +1924,10 @@ fn buildOutputType(
             break :blk try Package.create(gpa, fs.path.dirname(src_path), fs.path.basename(src_path));
         }
     } else null;
-    defer if (root_pkg) |p| p.destroy(gpa);
+    defer if (main_pkg) |p| p.destroy(gpa);
 
     // Transfer packages added with --pkg-begin/--pkg-end to the root package
-    if (root_pkg) |pkg| {
+    if (main_pkg) |pkg| {
         pkg.table = pkg_tree_root.table;
         pkg_tree_root.table = .{};
     }
@@ -1980,7 +1980,7 @@ fn buildOutputType(
         if (arg_mode == .run) {
             break :l global_cache_directory;
         }
-        if (root_pkg) |pkg| {
+        if (main_pkg) |pkg| {
             const cache_dir_path = try pkg.root_src_directory.join(arena, &[_][]const u8{"zig-cache"});
             const dir = try pkg.root_src_directory.handle.makeOpenPath("zig-cache", .{});
             cleanup_local_cache_dir = dir;
@@ -2018,7 +2018,7 @@ fn buildOutputType(
         .dynamic_linker = target_info.dynamic_linker.get(),
         .sysroot = sysroot,
         .output_mode = output_mode,
-        .root_pkg = root_pkg,
+        .main_pkg = main_pkg,
         .emit_bin = emit_bin_loc,
         .emit_h = emit_h_resolved.data,
         .emit_asm = emit_asm_resolved.data,
@@ -2823,7 +2823,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
         const std_special = "std" ++ fs.path.sep_str ++ "special";
         const special_dir_path = try zig_lib_directory.join(arena, &[_][]const u8{std_special});
 
-        var root_pkg: Package = .{
+        var main_pkg: Package = .{
             .root_src_directory = .{
                 .path = special_dir_path,
                 .handle = zig_lib_directory.handle.openDir(std_special, .{}) catch |err| {
@@ -2832,7 +2832,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
             },
             .root_src_path = "build_runner.zig",
         };
-        defer root_pkg.root_src_directory.handle.close();
+        defer main_pkg.root_src_directory.handle.close();
 
         var cleanup_build_dir: ?fs.Dir = null;
         defer if (cleanup_build_dir) |*dir| dir.close();
@@ -2881,7 +2881,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
             .root_src_directory = build_directory,
             .root_src_path = build_zig_basename,
         };
-        try root_pkg.addAndAdopt(arena, "@build", &build_pkg);
+        try main_pkg.addAndAdopt(arena, "@build", &build_pkg);
 
         var global_cache_directory: Compilation.Directory = l: {
             const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);
@@ -2938,7 +2938,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
             .is_native_abi = cross_target.isNativeAbi(),
             .dynamic_linker = target_info.dynamic_linker.get(),
             .output_mode = .Exe,
-            .root_pkg = &root_pkg,
+            .main_pkg = &main_pkg,
             .emit_bin = emit_bin,
             .emit_h = null,
             .optimize_mode = .Debug,
src/Module.zig
@@ -35,8 +35,11 @@ comp: *Compilation,
 
 /// Where our incremental compilation metadata serialization will go.
 zig_cache_artifact_directory: Compilation.Directory,
-/// Pointer to externally managed resource. `null` if there is no zig file being compiled.
+/// Pointer to externally managed resource.
 root_pkg: *Package,
+/// Normally, `main_pkg` and `root_pkg` are the same. The exception is `zig test`, in which
+/// `root_pkg` is the test runner, and `main_pkg` is the user's source file which has the tests.
+main_pkg: *Package,
 
 /// Used by AstGen worker to load and store ZIR cache.
 global_zir_cache: Compilation.Directory,
@@ -598,6 +601,9 @@ pub const Struct = struct {
         layout_wip,
         have_layout,
     },
+    /// If true, definitely nonzero size at runtime. If false, resolving the fields
+    /// is necessary to determine whether it has bits at runtime.
+    known_has_bits: bool,
 
     pub const Field = struct {
         /// Uses `noreturn` to indicate `anytype`.
@@ -2048,19 +2054,22 @@ pub fn deinit(mod: *Module) void {
 
     mod.deletion_set.deinit(gpa);
 
-    // The callsite of `Compilation.create` owns the `root_pkg`, however
+    // The callsite of `Compilation.create` owns the `main_pkg`, however
     // Module owns the builtin and std packages that it adds.
-    if (mod.root_pkg.table.fetchRemove("builtin")) |kv| {
+    if (mod.main_pkg.table.fetchRemove("builtin")) |kv| {
         gpa.free(kv.key);
         kv.value.destroy(gpa);
     }
-    if (mod.root_pkg.table.fetchRemove("std")) |kv| {
+    if (mod.main_pkg.table.fetchRemove("std")) |kv| {
         gpa.free(kv.key);
         kv.value.destroy(gpa);
     }
-    if (mod.root_pkg.table.fetchRemove("root")) |kv| {
+    if (mod.main_pkg.table.fetchRemove("root")) |kv| {
         gpa.free(kv.key);
     }
+    if (mod.root_pkg != mod.main_pkg) {
+        mod.root_pkg.destroy(gpa);
+    }
 
     mod.compile_log_text.deinit(gpa);
 
@@ -2148,7 +2157,7 @@ pub fn astGenFile(mod: *Module, file: *Scope.File) !void {
 
     const stat = try source_file.stat();
 
-    const want_local_cache = file.pkg == mod.root_pkg;
+    const want_local_cache = file.pkg == mod.main_pkg;
     const digest = hash: {
         var path_hash: Cache.HashHelper = .{};
         path_hash.addBytes(build_options.version);
@@ -2792,6 +2801,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
         .zir_index = undefined, // set below
         .layout = .Auto,
         .status = .none,
+        .known_has_bits = undefined,
         .namespace = .{
             .parent = null,
             .ty = struct_ty,
@@ -3301,10 +3311,23 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
         gop.value_ptr.* = new_decl;
         // Exported decls, comptime decls, usingnamespace decls, and
         // test decls if in test mode, get analyzed.
+        const decl_pkg = namespace.file_scope.pkg;
         const want_analysis = is_exported or switch (decl_name_index) {
             0 => true, // comptime decl
-            1 => mod.comp.bin_file.options.is_test, // test decl
-            else => is_named_test and mod.comp.bin_file.options.is_test,
+            1 => blk: {
+                // test decl with no name. Skip the part where we check against
+                // the test name filter.
+                if (!mod.comp.bin_file.options.is_test) break :blk false;
+                if (decl_pkg != mod.main_pkg) break :blk false;
+                break :blk true;
+            },
+            else => blk: {
+                if (!is_named_test) break :blk false;
+                if (!mod.comp.bin_file.options.is_test) break :blk false;
+                if (decl_pkg != mod.main_pkg) break :blk false;
+                // TODO check the name against --test-filter
+                break :blk true;
+            },
         };
         if (want_analysis) {
             mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
src/musl.zig
@@ -197,7 +197,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
                 .zig_lib_directory = comp.zig_lib_directory,
                 .target = comp.getTarget(),
                 .root_name = "c",
-                .root_pkg = null,
+                .main_pkg = null,
                 .output_mode = .Lib,
                 .link_mode = .Dynamic,
                 .thread_pool = comp.thread_pool,
src/print_air.zig
@@ -124,6 +124,8 @@ const Writer = struct {
             .bool_and,
             .bool_or,
             .store,
+            .slice_elem_val,
+            .ptr_slice_elem_val,
             => try w.writeBinOp(s, inst),
 
             .is_null,
@@ -161,6 +163,8 @@ const Writer = struct {
             .unwrap_errunion_err_ptr,
             .wrap_errunion_payload,
             .wrap_errunion_err,
+            .slice_ptr,
+            .slice_len,
             => try w.writeTyOp(s, inst),
 
             .block,
src/Sema.zig
@@ -768,6 +768,8 @@ pub fn analyzeStructDecl(
     assert(extended.opcode == .struct_decl);
     const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
 
+    struct_obj.known_has_bits = small.known_has_bits;
+
     var extra_index: usize = extended.operand;
     extra_index += @boolToInt(small.has_src_node);
     extra_index += @boolToInt(small.has_body_len);
@@ -812,6 +814,7 @@ fn zirStructDecl(
         .zir_index = inst,
         .layout = small.layout,
         .status = .none,
+        .known_has_bits = undefined,
         .namespace = .{
             .parent = sema.owner_decl.namespace,
             .ty = struct_ty,
@@ -1259,8 +1262,13 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
     const array_ptr = sema.resolveInst(inst_data.operand);
+    const array_ptr_src = src;
 
     const elem_ty = sema.typeOf(array_ptr).elemType();
+    if (elem_ty.isSlice()) {
+        const slice_inst = try sema.analyzeLoad(block, src, array_ptr, array_ptr_src);
+        return sema.analyzeSliceLen(block, src, slice_inst);
+    }
     if (!elem_ty.isIndexable()) {
         const cond_src: LazySrcLoc = .{ .node_offset_for_cond = inst_data.src_node };
         const msg = msg: {
@@ -1283,7 +1291,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
         return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
     }
     const result_ptr = try sema.namedFieldPtr(block, src, array_ptr, "len", src);
-    const result_ptr_src = src;
+    const result_ptr_src = array_ptr_src;
     return sema.analyzeLoad(block, src, result_ptr, result_ptr_src);
 }
 
@@ -2928,17 +2936,15 @@ fn zirErrUnionPayload(
             return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name});
         }
         const data = val.castTag(.error_union).?.data;
-        return sema.addConstant(
-            operand_ty.castTag(.error_union).?.data.payload,
-            data,
-        );
+        const result_ty = operand_ty.errorUnionPayload();
+        return sema.addConstant(result_ty, data);
     }
     try sema.requireRuntimeBlock(block, src);
     if (safety_check and block.wantSafety()) {
         const is_non_err = try block.addUnOp(.is_err, operand);
         try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion);
     }
-    const result_ty = operand_ty.castTag(.error_union).?.data.payload;
+    const result_ty = operand_ty.errorUnionPayload();
     return block.addTyOp(.unwrap_errunion_payload, result_ty, operand);
 }
 
@@ -2961,7 +2967,8 @@ fn zirErrUnionPayloadPtr(
     if (operand_ty.elemType().zigTypeTag() != .ErrorUnion)
         return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand_ty.elemType()});
 
-    const operand_pointer_ty = try Module.simplePtrType(sema.arena, operand_ty.elemType().castTag(.error_union).?.data.payload, !operand_ty.isConstPtr(), .One);
+    const payload_ty = operand_ty.elemType().errorUnionPayload();
+    const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One);
 
     if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
         const val = try pointer_val.pointerDeref(sema.arena);
@@ -2999,7 +3006,7 @@ fn zirErrUnionCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compi
     if (operand_ty.zigTypeTag() != .ErrorUnion)
         return sema.mod.fail(&block.base, src, "expected error union type, found '{}'", .{operand_ty});
 
-    const result_ty = operand_ty.castTag(.error_union).?.data.error_set;
+    const result_ty = operand_ty.errorUnionSet();
 
     if (try sema.resolveDefinedValue(block, src, operand)) |val| {
         assert(val.getError() != null);
@@ -3025,7 +3032,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
     if (operand_ty.elemType().zigTypeTag() != .ErrorUnion)
         return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand_ty.elemType()});
 
-    const result_ty = operand_ty.elemType().castTag(.error_union).?.data.error_set;
+    const result_ty = operand_ty.elemType().errorUnionSet();
 
     if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
         const val = try pointer_val.pointerDeref(sema.arena);
@@ -3048,7 +3055,7 @@ fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
     const operand_ty = sema.typeOf(operand);
     if (operand_ty.zigTypeTag() != .ErrorUnion)
         return sema.mod.fail(&block.base, src, "expected error union type, found '{}'", .{operand_ty});
-    if (operand_ty.castTag(.error_union).?.data.payload.zigTypeTag() != .Void) {
+    if (operand_ty.errorUnionPayload().zigTypeTag() != .Void) {
         return sema.mod.fail(&block.base, src, "expression value is ignored", .{});
     }
 }
@@ -3460,14 +3467,8 @@ fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
 
     const bin_inst = sema.code.instructions.items(.data)[inst].bin;
     const array = sema.resolveInst(bin_inst.lhs);
-    const array_ty = sema.typeOf(array);
-    const array_ptr = if (array_ty.zigTypeTag() == .Pointer)
-        array
-    else
-        try sema.analyzeRef(block, sema.src, array);
     const elem_index = sema.resolveInst(bin_inst.rhs);
-    const result_ptr = try sema.elemPtr(block, sema.src, array_ptr, elem_index, sema.src);
-    return sema.analyzeLoad(block, sema.src, result_ptr, sema.src);
+    return sema.elemVal(block, sema.src, array, elem_index, sema.src);
 }
 
 fn zirElemValNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -3479,14 +3480,8 @@ fn zirElemValNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compil
     const elem_index_src: LazySrcLoc = .{ .node_offset_array_access_index = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
     const array = sema.resolveInst(extra.lhs);
-    const array_ty = sema.typeOf(array);
-    const array_ptr = if (array_ty.zigTypeTag() == .Pointer)
-        array
-    else
-        try sema.analyzeRef(block, src, array);
     const elem_index = sema.resolveInst(extra.rhs);
-    const result_ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src);
-    return sema.analyzeLoad(block, src, result_ptr, src);
+    return sema.elemVal(block, src, array, elem_index, elem_index_src);
 }
 
 fn zirElemPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -5338,7 +5333,7 @@ fn zirBoolBr(
 
     try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
         then_block.instructions.items.len + else_block.instructions.items.len +
-        @typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len);
+        @typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len + 1);
 
     const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
         .then_body_len = @intCast(u32, then_block.instructions.items.len),
@@ -6217,8 +6212,9 @@ fn zirVarExtended(
     const init_val: Value = if (small.has_init) blk: {
         const init_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
         extra_index += 1;
-        const init_tv = try sema.resolveInstConst(block, init_src, init_ref);
-        break :blk init_tv.val;
+        const init_air_inst = sema.resolveInst(init_ref);
+        break :blk (try sema.resolvePossiblyUndefinedValue(block, init_src, init_air_inst)) orelse
+            return sema.failWithNeededComptime(block, init_src);
     } else Value.initTag(.unreachable_value);
 
     if (!var_ty.isValidVarType(small.is_extern)) {
@@ -6586,7 +6582,30 @@ fn namedFieldPtr(
         },
         .Pointer => {
             const ptr_child = elem_ty.elemType();
-            switch (ptr_child.zigTypeTag()) {
+            if (ptr_child.isSlice()) {
+                if (mem.eql(u8, field_name, "ptr")) {
+                    return mod.fail(
+                        &block.base,
+                        field_name_src,
+                        "cannot obtain reference to pointer field of slice '{}'",
+                        .{elem_ty},
+                    );
+                } else if (mem.eql(u8, field_name, "len")) {
+                    return mod.fail(
+                        &block.base,
+                        field_name_src,
+                        "cannot obtain reference to length field of slice '{}'",
+                        .{elem_ty},
+                    );
+                } else {
+                    return mod.fail(
+                        &block.base,
+                        field_name_src,
+                        "no member named '{s}' in '{}'",
+                        .{ field_name, elem_ty },
+                    );
+                }
+            } else switch (ptr_child.zigTypeTag()) {
                 .Array => {
                     if (mem.eql(u8, field_name, "len")) {
                         return sema.addConstant(
@@ -6836,6 +6855,50 @@ fn elemPtr(
     return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr", .{});
 }
 
+fn elemVal(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    array_maybe_ptr: Air.Inst.Ref,
+    elem_index: Air.Inst.Ref,
+    elem_index_src: LazySrcLoc,
+) CompileError!Air.Inst.Ref {
+    const array_ptr_src = src; // TODO better source location
+    const maybe_ptr_ty = sema.typeOf(array_maybe_ptr);
+    if (maybe_ptr_ty.isSinglePointer()) {
+        const indexable_ty = maybe_ptr_ty.elemType();
+        if (indexable_ty.isSlice()) {
+            // We have a pointer to a slice and we want an element value.
+            if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) {
+                const slice = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src);
+                if (try sema.resolveDefinedValue(block, src, slice)) |slice_val| {
+                    _ = slice_val;
+                    return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{});
+                }
+                try sema.requireRuntimeBlock(block, src);
+                return block.addBinOp(.slice_elem_val, slice, elem_index);
+            }
+            try sema.requireRuntimeBlock(block, src);
+            return block.addBinOp(.ptr_slice_elem_val, array_maybe_ptr, elem_index);
+        }
+    }
+    if (maybe_ptr_ty.isSlice()) {
+        if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |slice_val| {
+            _ = slice_val;
+            return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{});
+        }
+        try sema.requireRuntimeBlock(block, src);
+        return block.addBinOp(.slice_elem_val, array_maybe_ptr, elem_index);
+    }
+
+    const array_ptr = if (maybe_ptr_ty.zigTypeTag() == .Pointer)
+        array_maybe_ptr
+    else
+        try sema.analyzeRef(block, src, array_maybe_ptr);
+    const ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src);
+    return sema.analyzeLoad(block, src, ptr, elem_index_src);
+}
+
 fn elemPtrArray(
     sema: *Sema,
     block: *Scope.Block,
@@ -6896,11 +6959,6 @@ fn coerce(
     }
     assert(inst_ty.zigTypeTag() != .Undefined);
 
-    // T to E!T or E to E!T
-    if (dest_type.tag() == .error_union) {
-        return try sema.wrapErrorUnion(block, dest_type, inst, inst_src);
-    }
-
     // comptime known number to other number
     if (try sema.coerceNum(block, dest_type, inst, inst_src)) |some|
         return some;
@@ -7028,6 +7086,10 @@ fn coerce(
                 );
             }
         },
+        .ErrorUnion => {
+            // T to E!T or E to E!T
+            return sema.wrapErrorUnion(block, dest_type, inst, inst_src);
+        },
         else => {},
     }
 
@@ -7257,16 +7319,13 @@ fn analyzeVarRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, tv: TypedVal
     const gpa = sema.gpa;
     try sema.requireRuntimeBlock(block, src);
     try sema.air_variables.append(gpa, variable);
-    const result_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
-    try sema.air_instructions.append(gpa, .{
+    return block.addInst(.{
         .tag = .varptr,
         .data = .{ .ty_pl = .{
             .ty = try sema.addType(ty),
             .payload = @intCast(u32, sema.air_variables.items.len - 1),
         } },
     });
-    try block.instructions.append(gpa, result_inst);
-    return Air.indexToRef(result_inst);
 }
 
 fn analyzeRef(
@@ -7309,6 +7368,22 @@ fn analyzeLoad(
     return block.addTyOp(.load, elem_ty, ptr);
 }
 
+fn analyzeSliceLen(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    slice_inst: Air.Inst.Ref,
+) CompileError!Air.Inst.Ref {
+    if (try sema.resolvePossiblyUndefinedValue(block, src, slice_inst)) |slice_val| {
+        if (slice_val.isUndef()) {
+            return sema.addConstUndef(Type.initTag(.usize));
+        }
+        return sema.mod.fail(&block.base, src, "TODO implement Sema analyzeSliceLen on comptime slice", .{});
+    }
+    try sema.requireRuntimeBlock(block, src);
+    return block.addTyOp(.slice_len, Type.initTag(.usize), slice_inst);
+}
+
 fn analyzeIsNull(
     sema: *Sema,
     block: *Scope.Block,
@@ -7645,27 +7720,28 @@ fn wrapErrorUnion(
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
     const inst_ty = sema.typeOf(inst);
-    const err_union = dest_type.castTag(.error_union).?;
+    const dest_err_set_ty = dest_type.errorUnionSet();
+    const dest_payload_ty = dest_type.errorUnionPayload();
     if (try sema.resolvePossiblyUndefinedValue(block, inst_src, inst)) |val| {
         if (inst_ty.zigTypeTag() != .ErrorSet) {
-            _ = try sema.coerce(block, err_union.data.payload, inst, inst_src);
-        } else switch (err_union.data.error_set.tag()) {
+            _ = try sema.coerce(block, dest_payload_ty, inst, inst_src);
+        } else switch (dest_err_set_ty.tag()) {
             .anyerror => {},
             .error_set_single => {
                 const expected_name = val.castTag(.@"error").?.data.name;
-                const n = err_union.data.error_set.castTag(.error_set_single).?.data;
+                const n = dest_err_set_ty.castTag(.error_set_single).?.data;
                 if (!mem.eql(u8, expected_name, n)) {
                     return sema.mod.fail(
                         &block.base,
                         inst_src,
                         "expected type '{}', found type '{}'",
-                        .{ err_union.data.error_set, inst_ty },
+                        .{ dest_err_set_ty, inst_ty },
                     );
                 }
             },
             .error_set => {
                 const expected_name = val.castTag(.@"error").?.data.name;
-                const error_set = err_union.data.error_set.castTag(.error_set).?.data;
+                const error_set = dest_err_set_ty.castTag(.error_set).?.data;
                 const names = error_set.names_ptr[0..error_set.names_len];
                 // TODO this is O(N). I'm putting off solving this until we solve inferred
                 // error sets at the same time.
@@ -7677,19 +7753,19 @@ fn wrapErrorUnion(
                         &block.base,
                         inst_src,
                         "expected type '{}', found type '{}'",
-                        .{ err_union.data.error_set, inst_ty },
+                        .{ dest_err_set_ty, inst_ty },
                     );
                 }
             },
             .error_set_inferred => {
                 const expected_name = val.castTag(.@"error").?.data.name;
-                const map = &err_union.data.error_set.castTag(.error_set_inferred).?.data.map;
+                const map = &dest_err_set_ty.castTag(.error_set_inferred).?.data.map;
                 if (!map.contains(expected_name)) {
                     return sema.mod.fail(
                         &block.base,
                         inst_src,
                         "expected type '{}', found type '{}'",
-                        .{ err_union.data.error_set, inst_ty },
+                        .{ dest_err_set_ty, inst_ty },
                     );
                 }
             },
@@ -7704,10 +7780,10 @@ fn wrapErrorUnion(
 
     // we are coercing from E to E!T
     if (inst_ty.zigTypeTag() == .ErrorSet) {
-        var coerced = try sema.coerce(block, err_union.data.error_set, inst, inst_src);
+        var coerced = try sema.coerce(block, dest_err_set_ty, inst, inst_src);
         return block.addTyOp(.wrap_errunion_err, dest_type, coerced);
     } else {
-        var coerced = try sema.coerce(block, err_union.data.payload, inst, inst_src);
+        var coerced = try sema.coerce(block, dest_payload_ty, inst, inst_src);
         return block.addTyOp(.wrap_errunion_payload, dest_type, coerced);
     }
 }
@@ -7857,7 +7933,7 @@ fn getBuiltin(
     name: []const u8,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    const std_pkg = mod.root_pkg.table.get("std").?;
+    const std_pkg = mod.main_pkg.table.get("std").?;
     const std_file = (mod.importPkg(std_pkg) catch unreachable).file;
     const opt_builtin_inst = try sema.analyzeNamespaceLookup(
         block,
src/stage1.zig
@@ -107,7 +107,7 @@ pub const Module = extern struct {
     test_name_prefix_ptr: [*]const u8,
     test_name_prefix_len: usize,
     userdata: usize,
-    root_pkg: *Pkg,
+    main_pkg: *Pkg,
     main_progress_node: ?*std.Progress.Node,
     code_model: CodeModel,
     subsystem: TargetSubsystem,
src/test.zig
@@ -848,11 +848,11 @@ pub const TestContext = struct {
             .path = local_cache_path,
         };
 
-        var root_pkg: Package = .{
+        var main_pkg: Package = .{
             .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
             .root_src_path = tmp_src_path,
         };
-        defer root_pkg.table.deinit(allocator);
+        defer main_pkg.table.deinit(allocator);
 
         const bin_name = try std.zig.binNameAlloc(arena, .{
             .root_name = "test_case",
@@ -896,7 +896,7 @@ pub const TestContext = struct {
             .optimize_mode = case.optimize_mode,
             .emit_bin = emit_bin,
             .emit_h = emit_h,
-            .root_pkg = &root_pkg,
+            .main_pkg = &main_pkg,
             .keep_source_files_loaded = true,
             .object_format = case.object_format,
             .is_native_os = case.target.isNativeOs(),
src/type.zig
@@ -525,9 +525,19 @@ pub const Type = extern union {
                 const b_data = b.castTag(.error_union).?.data;
                 return a_data.error_set.eql(b_data.error_set) and a_data.payload.eql(b_data.payload);
             },
+            .ErrorSet => {
+                const a_is_anyerror = a.tag() == .anyerror;
+                const b_is_anyerror = b.tag() == .anyerror;
+
+                if (a_is_anyerror and b_is_anyerror) return true;
+                if (a_is_anyerror or b_is_anyerror) return false;
+
+                std.debug.panic("TODO implement Type equality comparison of {} and {}", .{
+                    a.tag(), b.tag(),
+                });
+            },
             .Opaque,
             .Float,
-            .ErrorSet,
             .BoundFn,
             .Frame,
             => std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }),
@@ -1190,6 +1200,9 @@ pub const Type = extern union {
             .@"struct" => {
                 // TODO introduce lazy value mechanism
                 const struct_obj = self.castTag(.@"struct").?.data;
+                if (struct_obj.known_has_bits) {
+                    return true;
+                }
                 assert(struct_obj.status == .have_field_types or
                     struct_obj.status == .layout_wip or
                     struct_obj.status == .have_layout);
@@ -1645,7 +1658,7 @@ pub const Type = extern union {
                 } else if (!payload.payload.hasCodeGenBits()) {
                     return payload.error_set.abiSize(target);
                 }
-                @panic("TODO abiSize error union");
+                std.debug.panic("TODO abiSize error union {}", .{self});
             },
         };
     }
@@ -2038,7 +2051,7 @@ pub const Type = extern union {
                 return ty.optionalChild(&buf).isValidVarType(is_extern);
             },
             .Pointer, .Array, .Vector => ty = ty.elemType(),
-            .ErrorUnion => ty = ty.errorUnionChild(),
+            .ErrorUnion => ty = ty.errorUnionPayload(),
 
             .Fn => @panic("TODO fn isValidVarType"),
             .Struct => {
@@ -2119,13 +2132,10 @@ pub const Type = extern union {
     }
 
     /// Asserts that the type is an error union.
-    pub fn errorUnionChild(self: Type) Type {
+    pub fn errorUnionPayload(self: Type) Type {
         return switch (self.tag()) {
-            .anyerror_void_error_union => Type.initTag(.anyerror),
-            .error_union => {
-                const payload = self.castTag(.error_union).?;
-                return payload.data.payload;
-            },
+            .anyerror_void_error_union => Type.initTag(.void),
+            .error_union => self.castTag(.error_union).?.data.payload,
             else => unreachable,
         };
     }
@@ -2133,10 +2143,7 @@ pub const Type = extern union {
     pub fn errorUnionSet(self: Type) Type {
         return switch (self.tag()) {
             .anyerror_void_error_union => Type.initTag(.anyerror),
-            .error_union => {
-                const payload = self.castTag(.error_union).?;
-                return payload.data.error_set;
-            },
+            .error_union => self.castTag(.error_union).?.data.error_set,
             else => unreachable,
         };
     }
src/Zir.zig
@@ -2462,9 +2462,10 @@ pub const Inst = struct {
             has_body_len: bool,
             has_fields_len: bool,
             has_decls_len: bool,
+            known_has_bits: bool,
             name_strategy: NameStrategy,
             layout: std.builtin.TypeInfo.ContainerLayout,
-            _: u8 = undefined,
+            _: u7 = undefined,
         };
     };
 
@@ -3543,6 +3544,7 @@ const Writer = struct {
             break :blk decls_len;
         } else 0;
 
+        try self.writeFlag(stream, "known_has_bits, ", small.known_has_bits);
         try stream.print("{s}, {s}, ", .{
             @tagName(small.name_strategy), @tagName(small.layout),
         });
test/behavior.zig
@@ -1,6 +1,6 @@
 const builtin = @import("builtin");
 
-comptime {
+test {
     // Tests that pass for both.
     {}