diff --git a/src/Air.zig b/src/Air.zig index d923bf0b02..391683afd5 100644 --- a/src/Air.zig +++ b/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(); }, } } diff --git a/src/Liveness.zig b/src/Liveness.zig index 4e22febc5a..48603fc7c9 100644 --- a/src/Liveness.zig +++ b/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 }); diff --git a/src/Sema.zig b/src/Sema.zig index 6c68ceaf2a..a783a48c64 100644 --- a/src/Sema.zig +++ b/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); - } + 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}, + ), } - 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( @@ -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 => {}, diff --git a/src/codegen.zig b/src/codegen.zig index 7fd51d3cd8..f5cdc518f6 100644 --- a/src/codegen.zig +++ b/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; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 22420aca45..65ad4bac8e 100644 --- a/src/codegen/c.zig +++ b/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; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4b7454d878..eccd5fa04f 100644 --- a/src/codegen/llvm.zig +++ b/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; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 096cd4a5b4..4bb8a4a18b 100644 --- a/src/codegen/llvm/bindings.zig +++ b/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; diff --git a/src/print_air.zig b/src/print_air.zig index 11f2982fc3..66490b6512 100644 --- a/src/print_air.zig +++ b/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, diff --git a/src/type.zig b/src/type.zig index 02b9fabe71..28b87a8afe 100644 --- a/src/type.zig +++ b/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. diff --git a/test/behavior.zig b/test/behavior.zig index f1c6fa3a35..936268af9c 100644 --- a/test/behavior.zig +++ b/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"); diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index bb95d3c219..4fcd78b1d6 100644 --- a/test/behavior/pointers.zig +++ b/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]))); -} diff --git a/test/behavior/pointers_stage1.zig b/test/behavior/pointers_stage1.zig new file mode 100644 index 0000000000..aea123a5c3 --- /dev/null +++ b/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]))); +}