Commit f81b2531cb

Andrew Kelley <andrew@ziglang.org>
2021-08-08 00:41:52
stage2: pass some pointer tests
* New AIR instructions: ptr_add, ptr_sub, ptr_elem_val, ptr_ptr_elem_val - See the doc comments for details. * Sema: implement runtime pointer arithmetic. * Sema: implement elem_val for many-pointers. * Sema: support coercion from `*[N:s]T` to `[*]T`. * Type: isIndexable handles many-pointers.
1 parent ade8547
src/codegen/llvm/bindings.zig
@@ -324,6 +324,9 @@ pub const Builder = opaque {
     pub const buildLoad = LLVMBuildLoad;
     extern fn LLVMBuildLoad(*const Builder, PointerVal: *const Value, Name: [*:0]const u8) *const Value;
 
+    pub const buildNeg = LLVMBuildNeg;
+    extern fn LLVMBuildNeg(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value;
+
     pub const buildNot = LLVMBuildNot;
     extern fn LLVMBuildNot(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value;
 
src/codegen/c.zig
@@ -850,19 +850,19 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
 
             // TODO use a different strategy for add that communicates to the optimizer
             // that wrapping is UB.
-            .add     => try airBinOp( o, inst, " + "),
-            .addwrap => try airWrapOp(o, inst, " + ", "addw_"),
+            .add, .ptr_add => try airBinOp( o, inst, " + "),
+            .addwrap       => try airWrapOp(o, inst, " + ", "addw_"),
             // TODO use a different strategy for sub that communicates to the optimizer
             // that wrapping is UB.
-            .sub     => try airBinOp( o, inst, " - "),
-            .subwrap => try airWrapOp(o, inst, " - ", "subw_"),
+            .sub, .ptr_sub => try airBinOp( o, inst, " - "),
+            .subwrap       => try airWrapOp(o, inst, " - ", "subw_"),
             // TODO use a different strategy for mul that communicates to the optimizer
             // that wrapping is UB.
-            .mul     => try airBinOp( o, inst, " * "),
-            .mulwrap => try airWrapOp(o, inst, " * ", "mulw_"),
+            .mul           => try airBinOp( o, inst, " * "),
+            .mulwrap       => try airWrapOp(o, inst, " * ", "mulw_"),
             // TODO use a different strategy for div that communicates to the optimizer
             // that wrapping is UB.
-            .div     => try airBinOp( o, inst, " / "),
+            .div           => try airBinOp( o, inst, " / "),
 
             .cmp_eq  => try airBinOp(o, inst, " == "),
             .cmp_gt  => try airBinOp(o, inst, " > "),
@@ -915,6 +915,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
             .slice_ptr        => try airSliceField(o, inst, ".ptr;\n"),
             .slice_len        => try airSliceField(o, inst, ".len;\n"),
 
+            .ptr_elem_val       => try airPtrElemVal(o, inst, "["),
+            .ptr_ptr_elem_val   => try airPtrElemVal(o, inst, "[0]["),
             .slice_elem_val     => try airSliceElemVal(o, inst, "["),
             .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["),
 
@@ -953,8 +955,18 @@ fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue {
     return local;
 }
 
+fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
+    const is_volatile = false; // TODO
+    if (!is_volatile and o.liveness.isUnused(inst))
+        return CValue.none;
+
+    _ = prefix;
+    return o.dg.fail("TODO: C backend: airPtrElemVal", .{});
+}
+
 fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
-    if (o.liveness.isUnused(inst))
+    const is_volatile = false; // TODO
+    if (!is_volatile and o.liveness.isUnused(inst))
         return CValue.none;
 
     const bin_op = o.air.instructions.items(.data)[inst].bin_op;
src/codegen/llvm.zig
@@ -966,6 +966,8 @@ pub const FuncGen = struct {
                 .mul     => try self.airMul(inst, false),
                 .mulwrap => try self.airMul(inst, true),
                 .div     => try self.airDiv(inst),
+                .ptr_add => try self.airPtrAdd(inst),
+                .ptr_sub => try self.airPtrSub(inst),
 
                 .bit_and, .bool_and => try self.airAnd(inst),
                 .bit_or, .bool_or   => try self.airOr(inst),
@@ -1015,6 +1017,8 @@ 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_ptr_elem_val   => try self.airPtrPtrElemVal(inst),
 
                 .optional_payload     => try self.airOptionalPayload(inst, false),
                 .optional_payload_ptr => try self.airOptionalPayload(inst, true),
@@ -1229,7 +1233,8 @@ pub const FuncGen = struct {
     }
 
     fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
-        if (self.liveness.isUnused(inst))
+        const is_volatile = false; // TODO
+        if (!is_volatile and self.liveness.isUnused(inst))
             return null;
 
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
@@ -1242,7 +1247,8 @@ pub const FuncGen = struct {
     }
 
     fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
-        if (self.liveness.isUnused(inst))
+        const is_volatile = false; // TODO
+        if (!is_volatile and self.liveness.isUnused(inst))
             return null;
 
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
@@ -1264,6 +1270,33 @@ pub const FuncGen = struct {
         return self.builder.buildLoad(ptr, "");
     }
 
+    fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const is_volatile = false; // TODO
+        if (!is_volatile and self.liveness.isUnused(inst))
+            return null;
+
+        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, "");
+        return self.builder.buildLoad(ptr, "");
+    }
+
+    fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const is_volatile = false; // TODO
+        if (!is_volatile and self.liveness.isUnused(inst))
+            return null;
+
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const base_ptr = self.builder.buildLoad(lhs, "");
+        const indices: [1]*const llvm.Value = .{rhs};
+        const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+        return self.builder.buildLoad(ptr, "");
+    }
+
     fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
@@ -1624,6 +1657,29 @@ pub const FuncGen = struct {
         return self.builder.buildUDiv(lhs, rhs, "");
     }
 
+    fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const base_ptr = try self.resolveInst(bin_op.lhs);
+        const offset = try self.resolveInst(bin_op.rhs);
+        const indices: [1]*const llvm.Value = .{offset};
+        return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+    }
+
+    fn airPtrSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const base_ptr = try self.resolveInst(bin_op.lhs);
+        const offset = try self.resolveInst(bin_op.rhs);
+        const negative_offset = self.builder.buildNeg(offset, "");
+        const indices: [1]*const llvm.Value = .{negative_offset};
+        return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
+    }
+
     fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         if (self.liveness.isUnused(inst))
             return null;
src/Air.zig
@@ -69,6 +69,18 @@ pub const Inst = struct {
         /// is the same as both operands.
         /// Uses the `bin_op` field.
         div,
+        /// Add an offset to a pointer, returning a new pointer.
+        /// The offset is in element type units, not bytes.
+        /// Wrapping is undefined behavior.
+        /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs.
+        /// Uses the `bin_op` field.
+        ptr_add,
+        /// Subtract an offset from a pointer, returning a new pointer.
+        /// The offset is in element type units, not bytes.
+        /// Wrapping is undefined behavior.
+        /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs.
+        /// Uses the `bin_op` field.
+        ptr_sub,
         /// Allocates stack local memory.
         /// Uses the `ty` field.
         alloc,
@@ -264,6 +276,15 @@ pub const Inst = struct {
         /// Result type is the element type of the slice operand (2 element type operations).
         /// Uses the `bin_op` field.
         ptr_slice_elem_val,
+        /// Given a pointer value, and element index, return the element value at that index.
+        /// Result type is the element type of the pointer operand.
+        /// Uses the `bin_op` field.
+        ptr_elem_val,
+        /// 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.
+        /// Uses the `bin_op` field.
+        ptr_ptr_elem_val,
 
         pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
             return switch (op) {
@@ -422,6 +443,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .bit_and,
         .bit_or,
         .xor,
+        .ptr_add,
+        .ptr_sub,
         => return air.typeOf(datas[inst].bin_op.lhs),
 
         .cmp_lt,
@@ -495,14 +518,14 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
             return callee_ty.fnReturnType();
         },
 
-        .slice_elem_val => {
+        .slice_elem_val, .ptr_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();
+        .ptr_slice_elem_val, .ptr_ptr_elem_val => {
+            const outer_ptr_ty = air.typeOf(datas[inst].bin_op.lhs);
+            const inner_ptr_ty = outer_ptr_ty.elemType();
+            return inner_ptr_ty.elemType();
         },
     }
 }
src/codegen.zig
@@ -802,13 +802,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
                 switch (air_tags[inst]) {
                     // zig fmt: off
-                    .add     => try self.airAdd(inst),
-                    .addwrap => try self.airAddWrap(inst),
-                    .sub     => try self.airSub(inst),
-                    .subwrap => try self.airSubWrap(inst),
-                    .mul     => try self.airMul(inst),
-                    .mulwrap => try self.airMulWrap(inst),
-                    .div     => try self.airDiv(inst),
+                    .add, .ptr_add => try self.airAdd(inst),
+                    .addwrap       => try self.airAddWrap(inst),
+                    .sub, .ptr_sub => try self.airSub(inst),
+                    .subwrap       => try self.airSubWrap(inst),
+                    .mul           => try self.airMul(inst),
+                    .mulwrap       => try self.airMulWrap(inst),
+                    .div           => try self.airDiv(inst),
 
                     .cmp_lt  => try self.airCmp(inst, .lt),
                     .cmp_lte => try self.airCmp(inst, .lte),
@@ -859,6 +859,8 @@ 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_ptr_elem_val    => try self.airPtrPtrElemVal(inst),
 
                     .constant => unreachable, // excluded from function bodies
                     .const_ty => unreachable, // excluded from function bodies
@@ -1369,21 +1371,41 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
+            const is_volatile = false; // TODO
             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
-            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+            const result: MCValue = if (!is_volatile and 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 is_volatile = false; // TODO
             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
-            const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+            const result: MCValue = if (!is_volatile and 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 airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
+            const is_volatile = false; // TODO
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement ptr_elem_val for {}", .{self.target.cpu.arch}),
+            };
+            return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.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;
+            const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) {
+                else => return self.fail("TODO implement ptr_ptr_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/Liveness.zig
@@ -231,6 +231,8 @@ fn analyzeInst(
         .mul,
         .mulwrap,
         .div,
+        .ptr_add,
+        .ptr_sub,
         .bit_and,
         .bit_or,
         .xor,
@@ -245,6 +247,8 @@ fn analyzeInst(
         .store,
         .slice_elem_val,
         .ptr_slice_elem_val,
+        .ptr_elem_val,
+        .ptr_ptr_elem_val,
         => {
             const o = inst_datas[inst].bin_op;
             return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
src/print_air.zig
@@ -109,6 +109,8 @@ const Writer = struct {
             .mul,
             .mulwrap,
             .div,
+            .ptr_add,
+            .ptr_sub,
             .bit_and,
             .bit_or,
             .xor,
@@ -123,6 +125,8 @@ const Writer = struct {
             .store,
             .slice_elem_val,
             .ptr_slice_elem_val,
+            .ptr_elem_val,
+            .ptr_ptr_elem_val,
             => try w.writeBinOp(s, inst),
 
             .is_null,
src/Sema.zig
@@ -5471,6 +5471,41 @@ fn analyzeArithmetic(
             lhs_ty, rhs_ty,
         });
     }
+    if (lhs_zig_ty_tag == .Pointer) switch (lhs_ty.ptrSize()) {
+        .One, .Slice => {},
+        .Many, .C => {
+            // Pointer arithmetic.
+            const op_src = src; // TODO better source location
+            const air_tag: Air.Inst.Tag = switch (zir_tag) {
+                .add => .ptr_add,
+                .sub => .ptr_sub,
+                else => return sema.mod.fail(
+                    &block.base,
+                    op_src,
+                    "invalid pointer arithmetic operand: '{s}''",
+                    .{@tagName(zir_tag)},
+                ),
+            };
+            // TODO if the operand is comptime-known to be negative, or is a negative int,
+            // coerce to isize instead of usize.
+            const casted_rhs = try sema.coerce(block, Type.initTag(.usize), rhs, rhs_src);
+            const runtime_src = runtime_src: {
+                if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
+                    if (try sema.resolveDefinedValue(block, rhs_src, casted_rhs)) |rhs_val| {
+                        _ = lhs_val;
+                        _ = rhs_val;
+                        return sema.mod.fail(&block.base, src, "TODO implement Sema for comptime pointer arithmetic", .{});
+                    } else {
+                        break :runtime_src rhs_src;
+                    }
+                } else {
+                    break :runtime_src lhs_src;
+                }
+            };
+            try sema.requireRuntimeBlock(block, runtime_src);
+            return block.addBinOp(air_tag, lhs, casted_rhs);
+        },
+    };
 
     const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
     const resolved_type = try sema.resolvePeerTypes(block, src, instructions);
@@ -7959,38 +7994,83 @@ fn elemVal(
 ) 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| {
+    switch (maybe_ptr_ty.zigTypeTag()) {
+        .Pointer => switch (maybe_ptr_ty.ptrSize()) {
+            .Slice => {
+                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, 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);
+                return block.addBinOp(.slice_elem_val, array_maybe_ptr, elem_index);
+            },
+            .Many, .C => {
+                if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |ptr_val| {
+                    _ = ptr_val;
+                    return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known pointer", .{});
+                }
+                try sema.requireRuntimeBlock(block, src);
+                return block.addBinOp(.ptr_elem_val, array_maybe_ptr, elem_index);
+            },
+            .One => {
+                const indexable_ty = maybe_ptr_ty.elemType();
+                switch (indexable_ty.zigTypeTag()) {
+                    .Pointer => switch (indexable_ty.ptrSize()) {
+                        .Slice => {
+                            // 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);
+                        },
+                        .Many, .C => {
+                            // We have a pointer to a pointer and we want an element value.
+                            if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) {
+                                const ptr = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src);
+                                if (try sema.resolveDefinedValue(block, src, ptr)) |ptr_val| {
+                                    _ = ptr_val;
+                                    return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known pointer", .{});
+                                }
+                                try sema.requireRuntimeBlock(block, src);
+                                return block.addBinOp(.ptr_elem_val, ptr, elem_index);
+                            }
+                            try sema.requireRuntimeBlock(block, src);
+                            return block.addBinOp(.ptr_ptr_elem_val, array_maybe_ptr, elem_index);
+                        },
+                        .One => return sema.mod.fail(
+                            &block.base,
+                            array_ptr_src,
+                            "expected pointer, found '{}'",
+                            .{indexable_ty.elemType()},
+                        ),
+                    },
+                    .Array => {
+                        const ptr = try sema.elemPtr(block, src, array_maybe_ptr, elem_index, elem_index_src);
+                        return sema.analyzeLoad(block, src, ptr, elem_index_src);
+                    },
+                    else => return sema.mod.fail(
+                        &block.base,
+                        array_ptr_src,
+                        "expected pointer, found '{}'",
+                        .{indexable_ty},
+                    ),
+                }
+            },
+        },
+        else => return sema.mod.fail(
+            &block.base,
+            array_ptr_src,
+            "expected pointer, found '{}'",
+            .{maybe_ptr_ty},
+        ),
     }
-
-    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(
@@ -8107,17 +8187,15 @@ fn coerce(
                     .Many => {
                         // *[N]T to [*]T
                         // *[N:s]T to [*:s]T
-                        const src_sentinel = array_type.sentinel();
-                        const dst_sentinel = dest_type.sentinel();
-                        if (src_sentinel == null and dst_sentinel == null)
-                            return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src);
-
-                        if (src_sentinel) |src_s| {
-                            if (dst_sentinel) |dst_s| {
-                                if (src_s.eql(dst_s, dst_elem_type)) {
+                        // *[N:s]T to [*]T
+                        if (dest_type.sentinel()) |dst_sentinel| {
+                            if (array_type.sentinel()) |src_sentinel| {
+                                if (src_sentinel.eql(dst_sentinel, dst_elem_type)) {
                                     return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src);
                                 }
                             }
+                        } else {
+                            return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src);
                         }
                     },
                     .One => {},
src/type.zig
@@ -2753,11 +2753,15 @@ pub const Type = extern union {
         };
     }
 
-    pub fn isIndexable(self: Type) bool {
-        const zig_tag = self.zigTypeTag();
-        // TODO tuples are indexable
-        return zig_tag == .Array or zig_tag == .Vector or self.isSlice() or
-            (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array);
+    pub fn isIndexable(ty: Type) bool {
+        return switch (ty.zigTypeTag()) {
+            .Array, .Vector => true,
+            .Pointer => switch (ty.ptrSize()) {
+                .Slice, .Many, .C => true,
+                .One => ty.elemType().zigTypeTag() == .Array,
+            },
+            else => false, // TODO tuples are indexable
+        };
     }
 
     /// Returns null if the type has no namespace.
test/behavior/pointers.zig
@@ -15,22 +15,6 @@ fn testDerefPtr() !void {
     try expect(x == 1235);
 }
 
-const Foo1 = struct {
-    x: void,
-};
-
-test "dereference pointer again" {
-    try testDerefPtrOneVal();
-    comptime try testDerefPtrOneVal();
-}
-
-fn testDerefPtrOneVal() !void {
-    // Foo1 satisfies the OnePossibleValueYes criteria
-    const x = &Foo1{ .x = {} };
-    const y = x.*;
-    try expect(@TypeOf(y.x) == void);
-}
-
 test "pointer arithmetic" {
     var ptr: [*]const u8 = "abcd";
 
@@ -60,288 +44,3 @@ test "double pointer parsing" {
 fn PtrOf(comptime T: type) type {
     return *T;
 }
-
-test "assigning integer to C pointer" {
-    var x: i32 = 0;
-    var ptr: [*c]u8 = 0;
-    var ptr2: [*c]u8 = x;
-    if (false) {
-        ptr;
-        ptr2;
-    }
-}
-
-test "implicit cast single item pointer to C pointer and back" {
-    var y: u8 = 11;
-    var x: [*c]u8 = &y;
-    var z: *u8 = x;
-    z.* += 1;
-    try expect(y == 12);
-}
-
-test "C pointer comparison and arithmetic" {
-    const S = struct {
-        fn doTheTest() !void {
-            var ptr1: [*c]u32 = 0;
-            var ptr2 = ptr1 + 10;
-            try expect(ptr1 == 0);
-            try expect(ptr1 >= 0);
-            try expect(ptr1 <= 0);
-            // expect(ptr1 < 1);
-            // expect(ptr1 < one);
-            // expect(1 > ptr1);
-            // expect(one > ptr1);
-            try expect(ptr1 < ptr2);
-            try expect(ptr2 > ptr1);
-            try expect(ptr2 >= 40);
-            try expect(ptr2 == 40);
-            try expect(ptr2 <= 40);
-            ptr2 -= 10;
-            try expect(ptr1 == ptr2);
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "peer type resolution with C pointers" {
-    var ptr_one: *u8 = undefined;
-    var ptr_many: [*]u8 = undefined;
-    var ptr_c: [*c]u8 = undefined;
-    var t = true;
-    var x1 = if (t) ptr_one else ptr_c;
-    var x2 = if (t) ptr_many else ptr_c;
-    var x3 = if (t) ptr_c else ptr_one;
-    var x4 = if (t) ptr_c else ptr_many;
-    try expect(@TypeOf(x1) == [*c]u8);
-    try expect(@TypeOf(x2) == [*c]u8);
-    try expect(@TypeOf(x3) == [*c]u8);
-    try expect(@TypeOf(x4) == [*c]u8);
-}
-
-test "implicit casting between C pointer and optional non-C pointer" {
-    var slice: []const u8 = "aoeu";
-    const opt_many_ptr: ?[*]const u8 = slice.ptr;
-    var ptr_opt_many_ptr = &opt_many_ptr;
-    var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr;
-    try expect(c_ptr.*.* == 'a');
-    ptr_opt_many_ptr = c_ptr;
-    try expect(ptr_opt_many_ptr.*.?[1] == 'o');
-}
-
-test "implicit cast error unions with non-optional to optional pointer" {
-    const S = struct {
-        fn doTheTest() !void {
-            try expectError(error.Fail, foo());
-        }
-        fn foo() anyerror!?*u8 {
-            return bar() orelse error.Fail;
-        }
-        fn bar() ?*u8 {
-            return null;
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "initialize const optional C pointer to null" {
-    const a: ?[*c]i32 = null;
-    try expect(a == null);
-    comptime try expect(a == null);
-}
-
-test "compare equality of optional and non-optional pointer" {
-    const a = @intToPtr(*const usize, 0x12345678);
-    const b = @intToPtr(?*usize, 0x12345678);
-    try expect(a == b);
-    try expect(b == a);
-}
-
-test "allowzero pointer and slice" {
-    var ptr = @intToPtr([*]allowzero i32, 0);
-    var opt_ptr: ?[*]allowzero i32 = ptr;
-    try expect(opt_ptr != null);
-    try expect(@ptrToInt(ptr) == 0);
-    var runtime_zero: usize = 0;
-    var slice = ptr[runtime_zero..10];
-    comptime try expect(@TypeOf(slice) == []allowzero i32);
-    try expect(@ptrToInt(&slice[5]) == 20);
-
-    comptime try expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero);
-    comptime try expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero);
-}
-
-test "assign null directly to C pointer and test null equality" {
-    var x: [*c]i32 = null;
-    try expect(x == null);
-    try expect(null == x);
-    try expect(!(x != null));
-    try expect(!(null != x));
-    if (x) |same_x| {
-        _ = same_x;
-        @panic("fail");
-    }
-    var otherx: i32 = undefined;
-    try expect((x orelse &otherx) == &otherx);
-
-    const y: [*c]i32 = null;
-    comptime try expect(y == null);
-    comptime try expect(null == y);
-    comptime try expect(!(y != null));
-    comptime try expect(!(null != y));
-    if (y) |same_y| {
-        _ = same_y;
-        @panic("fail");
-    }
-    const othery: i32 = undefined;
-    comptime try expect((y orelse &othery) == &othery);
-
-    var n: i32 = 1234;
-    var x1: [*c]i32 = &n;
-    try expect(!(x1 == null));
-    try expect(!(null == x1));
-    try expect(x1 != null);
-    try expect(null != x1);
-    try expect(x1.?.* == 1234);
-    if (x1) |same_x1| {
-        try expect(same_x1.* == 1234);
-    } else {
-        @panic("fail");
-    }
-    try expect((x1 orelse &otherx) == x1);
-
-    const nc: i32 = 1234;
-    const y1: [*c]const i32 = &nc;
-    comptime try expect(!(y1 == null));
-    comptime try expect(!(null == y1));
-    comptime try expect(y1 != null);
-    comptime try expect(null != y1);
-    comptime try expect(y1.?.* == 1234);
-    if (y1) |same_y1| {
-        try expect(same_y1.* == 1234);
-    } else {
-        @compileError("fail");
-    }
-    comptime try expect((y1 orelse &othery) == y1);
-}
-
-test "null terminated pointer" {
-    const S = struct {
-        fn doTheTest() !void {
-            var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' };
-            var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero);
-            var no_zero_ptr: [*]const u8 = zero_ptr;
-            var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr);
-            try expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello"));
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "allow any sentinel" {
-    const S = struct {
-        fn doTheTest() !void {
-            var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 };
-            var ptr: [*:std.math.minInt(i32)]i32 = &array;
-            try expect(ptr[4] == std.math.minInt(i32));
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "pointer sentinel with enums" {
-    const S = struct {
-        const Number = enum {
-            one,
-            two,
-            sentinel,
-        };
-
-        fn doTheTest() !void {
-            var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one };
-            try expect(ptr[4] == .sentinel); // TODO this should be comptime try expect, see #3731
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "pointer sentinel with optional element" {
-    const S = struct {
-        fn doTheTest() !void {
-            var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 };
-            try expect(ptr[4] == null); // TODO this should be comptime try expect, see #3731
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "pointer sentinel with +inf" {
-    const S = struct {
-        fn doTheTest() !void {
-            const inf = std.math.inf_f32;
-            var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 };
-            try expect(ptr[4] == inf); // TODO this should be comptime try expect, see #3731
-        }
-    };
-    try S.doTheTest();
-    comptime try S.doTheTest();
-}
-
-test "pointer to array at fixed address" {
-    const array = @intToPtr(*volatile [1]u32, 0x10);
-    // Silly check just to reference `array`
-    try expect(@ptrToInt(&array[0]) == 0x10);
-}
-
-test "pointer arithmetic affects the alignment" {
-    {
-        var ptr: [*]align(8) u32 = undefined;
-        var x: usize = 1;
-
-        try expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8);
-        const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4
-        try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4);
-        const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8
-        try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8);
-        const ptr3 = ptr + 0; // no-op
-        try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8);
-        const ptr4 = ptr + x; // runtime-known addend
-        try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4);
-    }
-    {
-        var ptr: [*]align(8) [3]u8 = undefined;
-        var x: usize = 1;
-
-        const ptr1 = ptr + 17; // 3 * 17 = 51
-        try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1);
-        const ptr2 = ptr + x; // runtime-known addend
-        try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1);
-        const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8
-        try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8);
-        const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4
-        try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4);
-    }
-}
-
-test "@ptrToInt on null optional at comptime" {
-    {
-        const pointer = @intToPtr(?*u8, 0x000);
-        const x = @ptrToInt(pointer);
-        _ = x;
-        comptime try expect(0 == @ptrToInt(pointer));
-    }
-    {
-        const pointer = @intToPtr(?*u8, 0xf00);
-        comptime try expect(0xf00 == @ptrToInt(pointer));
-    }
-}
-
-test "indexing array with sentinel returns correct type" {
-    var s: [:0]const u8 = "abc";
-    try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0])));
-}
test/behavior/pointers_stage1.zig
@@ -0,0 +1,305 @@
+const std = @import("std");
+const testing = std.testing;
+const expect = testing.expect;
+const expectError = testing.expectError;
+
+const Foo1 = struct {
+    x: void,
+};
+
+test "dereference pointer again" {
+    try testDerefPtrOneVal();
+    comptime try testDerefPtrOneVal();
+}
+
+fn testDerefPtrOneVal() !void {
+    // Foo1 satisfies the OnePossibleValueYes criteria
+    const x = &Foo1{ .x = {} };
+    const y = x.*;
+    try expect(@TypeOf(y.x) == void);
+}
+
+test "assigning integer to C pointer" {
+    var x: i32 = 0;
+    var ptr: [*c]u8 = 0;
+    var ptr2: [*c]u8 = x;
+    if (false) {
+        ptr;
+        ptr2;
+    }
+}
+
+test "implicit cast single item pointer to C pointer and back" {
+    var y: u8 = 11;
+    var x: [*c]u8 = &y;
+    var z: *u8 = x;
+    z.* += 1;
+    try expect(y == 12);
+}
+
+test "C pointer comparison and arithmetic" {
+    const S = struct {
+        fn doTheTest() !void {
+            var ptr1: [*c]u32 = 0;
+            var ptr2 = ptr1 + 10;
+            try expect(ptr1 == 0);
+            try expect(ptr1 >= 0);
+            try expect(ptr1 <= 0);
+            // expect(ptr1 < 1);
+            // expect(ptr1 < one);
+            // expect(1 > ptr1);
+            // expect(one > ptr1);
+            try expect(ptr1 < ptr2);
+            try expect(ptr2 > ptr1);
+            try expect(ptr2 >= 40);
+            try expect(ptr2 == 40);
+            try expect(ptr2 <= 40);
+            ptr2 -= 10;
+            try expect(ptr1 == ptr2);
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "peer type resolution with C pointers" {
+    var ptr_one: *u8 = undefined;
+    var ptr_many: [*]u8 = undefined;
+    var ptr_c: [*c]u8 = undefined;
+    var t = true;
+    var x1 = if (t) ptr_one else ptr_c;
+    var x2 = if (t) ptr_many else ptr_c;
+    var x3 = if (t) ptr_c else ptr_one;
+    var x4 = if (t) ptr_c else ptr_many;
+    try expect(@TypeOf(x1) == [*c]u8);
+    try expect(@TypeOf(x2) == [*c]u8);
+    try expect(@TypeOf(x3) == [*c]u8);
+    try expect(@TypeOf(x4) == [*c]u8);
+}
+
+test "implicit casting between C pointer and optional non-C pointer" {
+    var slice: []const u8 = "aoeu";
+    const opt_many_ptr: ?[*]const u8 = slice.ptr;
+    var ptr_opt_many_ptr = &opt_many_ptr;
+    var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr;
+    try expect(c_ptr.*.* == 'a');
+    ptr_opt_many_ptr = c_ptr;
+    try expect(ptr_opt_many_ptr.*.?[1] == 'o');
+}
+
+test "implicit cast error unions with non-optional to optional pointer" {
+    const S = struct {
+        fn doTheTest() !void {
+            try expectError(error.Fail, foo());
+        }
+        fn foo() anyerror!?*u8 {
+            return bar() orelse error.Fail;
+        }
+        fn bar() ?*u8 {
+            return null;
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "initialize const optional C pointer to null" {
+    const a: ?[*c]i32 = null;
+    try expect(a == null);
+    comptime try expect(a == null);
+}
+
+test "compare equality of optional and non-optional pointer" {
+    const a = @intToPtr(*const usize, 0x12345678);
+    const b = @intToPtr(?*usize, 0x12345678);
+    try expect(a == b);
+    try expect(b == a);
+}
+
+test "allowzero pointer and slice" {
+    var ptr = @intToPtr([*]allowzero i32, 0);
+    var opt_ptr: ?[*]allowzero i32 = ptr;
+    try expect(opt_ptr != null);
+    try expect(@ptrToInt(ptr) == 0);
+    var runtime_zero: usize = 0;
+    var slice = ptr[runtime_zero..10];
+    comptime try expect(@TypeOf(slice) == []allowzero i32);
+    try expect(@ptrToInt(&slice[5]) == 20);
+
+    comptime try expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero);
+    comptime try expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero);
+}
+
+test "assign null directly to C pointer and test null equality" {
+    var x: [*c]i32 = null;
+    try expect(x == null);
+    try expect(null == x);
+    try expect(!(x != null));
+    try expect(!(null != x));
+    if (x) |same_x| {
+        _ = same_x;
+        @panic("fail");
+    }
+    var otherx: i32 = undefined;
+    try expect((x orelse &otherx) == &otherx);
+
+    const y: [*c]i32 = null;
+    comptime try expect(y == null);
+    comptime try expect(null == y);
+    comptime try expect(!(y != null));
+    comptime try expect(!(null != y));
+    if (y) |same_y| {
+        _ = same_y;
+        @panic("fail");
+    }
+    const othery: i32 = undefined;
+    comptime try expect((y orelse &othery) == &othery);
+
+    var n: i32 = 1234;
+    var x1: [*c]i32 = &n;
+    try expect(!(x1 == null));
+    try expect(!(null == x1));
+    try expect(x1 != null);
+    try expect(null != x1);
+    try expect(x1.?.* == 1234);
+    if (x1) |same_x1| {
+        try expect(same_x1.* == 1234);
+    } else {
+        @panic("fail");
+    }
+    try expect((x1 orelse &otherx) == x1);
+
+    const nc: i32 = 1234;
+    const y1: [*c]const i32 = &nc;
+    comptime try expect(!(y1 == null));
+    comptime try expect(!(null == y1));
+    comptime try expect(y1 != null);
+    comptime try expect(null != y1);
+    comptime try expect(y1.?.* == 1234);
+    if (y1) |same_y1| {
+        try expect(same_y1.* == 1234);
+    } else {
+        @compileError("fail");
+    }
+    comptime try expect((y1 orelse &othery) == y1);
+}
+
+test "null terminated pointer" {
+    const S = struct {
+        fn doTheTest() !void {
+            var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' };
+            var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero);
+            var no_zero_ptr: [*]const u8 = zero_ptr;
+            var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr);
+            try expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello"));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "allow any sentinel" {
+    const S = struct {
+        fn doTheTest() !void {
+            var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 };
+            var ptr: [*:std.math.minInt(i32)]i32 = &array;
+            try expect(ptr[4] == std.math.minInt(i32));
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "pointer sentinel with enums" {
+    const S = struct {
+        const Number = enum {
+            one,
+            two,
+            sentinel,
+        };
+
+        fn doTheTest() !void {
+            var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one };
+            try expect(ptr[4] == .sentinel); // TODO this should be comptime try expect, see #3731
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "pointer sentinel with optional element" {
+    const S = struct {
+        fn doTheTest() !void {
+            var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 };
+            try expect(ptr[4] == null); // TODO this should be comptime try expect, see #3731
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "pointer sentinel with +inf" {
+    const S = struct {
+        fn doTheTest() !void {
+            const inf = std.math.inf_f32;
+            var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 };
+            try expect(ptr[4] == inf); // TODO this should be comptime try expect, see #3731
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
+test "pointer to array at fixed address" {
+    const array = @intToPtr(*volatile [1]u32, 0x10);
+    // Silly check just to reference `array`
+    try expect(@ptrToInt(&array[0]) == 0x10);
+}
+
+test "pointer arithmetic affects the alignment" {
+    {
+        var ptr: [*]align(8) u32 = undefined;
+        var x: usize = 1;
+
+        try expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8);
+        const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4
+        try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4);
+        const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8
+        try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8);
+        const ptr3 = ptr + 0; // no-op
+        try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8);
+        const ptr4 = ptr + x; // runtime-known addend
+        try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4);
+    }
+    {
+        var ptr: [*]align(8) [3]u8 = undefined;
+        var x: usize = 1;
+
+        const ptr1 = ptr + 17; // 3 * 17 = 51
+        try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1);
+        const ptr2 = ptr + x; // runtime-known addend
+        try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1);
+        const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8
+        try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8);
+        const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4
+        try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4);
+    }
+}
+
+test "@ptrToInt on null optional at comptime" {
+    {
+        const pointer = @intToPtr(?*u8, 0x000);
+        const x = @ptrToInt(pointer);
+        _ = x;
+        comptime try expect(0 == @ptrToInt(pointer));
+    }
+    {
+        const pointer = @intToPtr(?*u8, 0xf00);
+        comptime try expect(0xf00 == @ptrToInt(pointer));
+    }
+}
+
+test "indexing array with sentinel returns correct type" {
+    var s: [:0]const u8 = "abc";
+    try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0])));
+}
test/behavior.zig
@@ -6,6 +6,7 @@ test {
     _ = @import("behavior/basic.zig");
     _ = @import("behavior/generics.zig");
     _ = @import("behavior/eval.zig");
+    _ = @import("behavior/pointers.zig");
 
     if (!builtin.zig_is_stage2) {
         // Tests that only pass for stage1.
@@ -112,7 +113,7 @@ test {
         _ = @import("behavior/namespace_depends_on_compile_var.zig");
         _ = @import("behavior/null.zig");
         _ = @import("behavior/optional.zig");
-        _ = @import("behavior/pointers.zig");
+        _ = @import("behavior/pointers_stage1.zig");
         _ = @import("behavior/popcount.zig");
         _ = @import("behavior/ptrcast.zig");
         _ = @import("behavior/pub_enum.zig");