From 0942bf73c90eabf87d0ca965b50beb0fd9a8fc8c Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 24 Oct 2021 16:34:42 +0200 Subject: [PATCH 1/4] stage2: improve slicing * Allow slicing many- and c-pointers. * Allow comptime pointer arithmetic on undefined values. * Return the correct type for slicing of slices. --- src/Sema.zig | 47 ++++++++++++++++++++++++++------- test/behavior/slice.zig | 48 ++++++++++++++++++++++++++++++++++ test/behavior/slice_stage1.zig | 48 ---------------------------------- 3 files changed, 85 insertions(+), 58 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index a87e0739e8..d290ea8ec0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7989,11 +7989,16 @@ fn analyzePtrArithmetic( const offset = try sema.coerce(block, Type.usize, uncasted_offset, offset_src); // TODO adjust the return type according to alignment and other factors const runtime_src = rs: { - if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { - if (try sema.resolveDefinedValue(block, offset_src, offset)) |offset_val| { + if (try sema.resolveMaybeUndefVal(block, ptr_src, ptr)) |ptr_val| { + if (try sema.resolveMaybeUndefVal(block, offset_src, offset)) |offset_val| { const ptr_ty = sema.typeOf(ptr); - const offset_int = offset_val.toUnsignedInt(); const new_ptr_ty = ptr_ty; // TODO modify alignment + + if (ptr_val.isUndef() or offset_val.isUndef()) { + return sema.addConstUndef(new_ptr_ty); + } + + const offset_int = offset_val.toUnsignedInt(); if (ptr_val.getUnsignedInt()) |addr| { const target = sema.mod.getTarget(); const elem_ty = ptr_ty.childType(); @@ -13206,8 +13211,8 @@ fn analyzeSlice( var elem_ty = ptr_ptr_child_ty.childType(); switch (ptr_ptr_child_ty.zigTypeTag()) { .Array => {}, - .Pointer => { - if (ptr_ptr_child_ty.isSinglePointer()) { + .Pointer => switch (ptr_ptr_child_ty.ptrSize()) { + .One => { const double_child_ty = ptr_ptr_child_ty.childType(); if (double_child_ty.zigTypeTag() == .Array) { ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src); @@ -13217,10 +13222,23 @@ fn analyzeSlice( } else { return sema.fail(block, ptr_src, "slice of single-item pointer", .{}); } - } + }, + .Many, .C => { + ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src); + slice_ty = ptr_ptr_child_ty; + array_ty = ptr_ptr_child_ty; + elem_ty = ptr_ptr_child_ty.childType(); + }, + .Slice => { + ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src); + slice_ty = ptr_ptr_child_ty; + array_ty = ptr_ptr_child_ty; + elem_ty = ptr_ptr_child_ty.childType(); + }, }, else => return sema.fail(block, ptr_src, "slice of non-array type '{}'", .{ptr_ptr_child_ty}), } + const ptr = if (slice_ty.isSlice()) try sema.analyzeSlicePtr(block, src, ptr_or_slice, slice_ty, ptr_src) else @@ -13252,7 +13270,6 @@ fn analyzeSlice( const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src); - const opt_new_ptr_val = try sema.resolveDefinedValue(block, ptr_src, new_ptr); const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len); const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data; @@ -13276,11 +13293,21 @@ fn analyzeSlice( .size = .One, }); - if (opt_new_ptr_val) |new_ptr_val| { - return sema.addConstant(return_ty, new_ptr_val); - } else { + const opt_new_ptr_val = try sema.resolveMaybeUndefVal(block, ptr_src, new_ptr); + const new_ptr_val = opt_new_ptr_val orelse { return block.addBitCast(return_ty, new_ptr); + }; + + if (!new_ptr_val.isUndef()) { + return sema.addConstant(return_ty, new_ptr_val); } + + // Special case: @as([]i32, undefined)[x..x] + if (new_len_int == 0) { + return sema.addConstUndef(return_ty); + } + + return sema.fail(block, ptr_src, "non-zero length slice of undefined pointer", .{}); } const return_ty = try Type.ptr(sema.arena, .{ diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 19c9e7e773..dfe2b39297 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -109,3 +109,51 @@ test "slice of type" { } } } + +test "generic malloc free" { + const a = memAlloc(u8, 10) catch unreachable; + memFree(u8, a); +} +var some_mem: [100]u8 = undefined; +fn memAlloc(comptime T: type, n: usize) anyerror![]T { + return @ptrCast([*]T, &some_mem[0])[0..n]; +} +fn memFree(comptime T: type, memory: []T) void { + _ = memory; +} + +test "slice of hardcoded address to pointer" { + const S = struct { + fn doTheTest() !void { + const pointer = @intToPtr([*]u8, 0x04)[0..2]; + comptime try expect(@TypeOf(pointer) == *[2]u8); + const slice: []const u8 = pointer; + try expect(@ptrToInt(slice.ptr) == 4); + try expect(slice.len == 2); + } + }; + + try S.doTheTest(); +} + +test "comptime slice of pointer preserves comptime var" { + comptime { + var buff: [10]u8 = undefined; + var a = @ptrCast([*]u8, &buff); + a[0..1][0] = 1; + try expect(buff[0..][0..][0] == 1); + } +} + +test "comptime pointer cast array and then slice" { + const array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; + + const ptrA: [*]const u8 = @ptrCast([*]const u8, &array); + const sliceA: []const u8 = ptrA[0..2]; + + const ptrB: [*]const u8 = &array; + const sliceB: []const u8 = ptrB[0..2]; + + try expect(sliceA[1] == 2); + try expect(sliceB[1] == 2); +} diff --git a/test/behavior/slice_stage1.zig b/test/behavior/slice_stage1.zig index cc472a4ff6..e587c5fb53 100644 --- a/test/behavior/slice_stage1.zig +++ b/test/behavior/slice_stage1.zig @@ -25,18 +25,6 @@ test "slice string literal has correct type" { comptime try expect(@TypeOf(array[runtime_zero..]) == []const i32); } -test "generic malloc free" { - const a = memAlloc(u8, 10) catch unreachable; - memFree(u8, a); -} -var some_mem: [100]u8 = undefined; -fn memAlloc(comptime T: type, n: usize) anyerror![]T { - return @ptrCast([*]T, &some_mem[0])[0..n]; -} -fn memFree(comptime T: type, memory: []T) void { - _ = memory; -} - test "result location zero sized array inside struct field implicit cast to slice" { const E = struct { entries: []u32, @@ -307,20 +295,6 @@ test "slice syntax resulting in pointer-to-array" { comptime try S.doTheTest(); } -test "slice of hardcoded address to pointer" { - const S = struct { - fn doTheTest() !void { - const pointer = @intToPtr([*]u8, 0x04)[0..2]; - comptime try expect(@TypeOf(pointer) == *[2]u8); - const slice: []const u8 = pointer; - try expect(@ptrToInt(slice.ptr) == 4); - try expect(slice.len == 2); - } - }; - - try S.doTheTest(); -} - test "type coercion of pointer to anon struct literal to pointer to slice" { const S = struct { const U = union { @@ -352,15 +326,6 @@ test "type coercion of pointer to anon struct literal to pointer to slice" { comptime try S.doTheTest(); } -test "comptime slice of pointer preserves comptime var" { - comptime { - var buff: [10]u8 = undefined; - var a = @ptrCast([*]u8, &buff); - a[0..1][0] = 1; - try expect(buff[0..][0..][0] == 1); - } -} - test "array concat of slices gives slice" { comptime { var a: []const u8 = "aoeu"; @@ -370,19 +335,6 @@ test "array concat of slices gives slice" { } } -test "comptime pointer cast array and then slice" { - const array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; - - const ptrA: [*]const u8 = @ptrCast([*]const u8, &array); - const sliceA: []const u8 = ptrA[0..2]; - - const ptrB: [*]const u8 = &array; - const sliceB: []const u8 = ptrB[0..2]; - - try expect(sliceA[1] == 2); - try expect(sliceB[1] == 2); -} - test "slice bounds in comptime concatenation" { const bs = comptime blk: { const b = "........1........"; From 7062c8a8865bbd2fb8181b579da552295cd68e6a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 24 Oct 2021 20:37:09 +0200 Subject: [PATCH 2/4] stage2: comptime slice of pointer to hardcoded address --- src/Sema.zig | 7 ++++++- test/behavior.zig | 1 + test/behavior/slice_stage2.zig | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/behavior/slice_stage2.zig diff --git a/src/Sema.zig b/src/Sema.zig index d290ea8ec0..eaa99a42a7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8001,7 +8001,12 @@ fn analyzePtrArithmetic( const offset_int = offset_val.toUnsignedInt(); if (ptr_val.getUnsignedInt()) |addr| { const target = sema.mod.getTarget(); - const elem_ty = ptr_ty.childType(); + const ptr_child_ty = ptr_ty.childType(); + const elem_ty = if (ptr_ty.isSinglePointer() and ptr_child_ty.zigTypeTag() == .Array) + ptr_child_ty.childType() + else + ptr_child_ty; + const elem_size = elem_ty.abiSize(target); const new_addr = switch (air_tag) { .ptr_add => addr + elem_size * offset_int, diff --git a/test/behavior.zig b/test/behavior.zig index 05e05d51fc..42e3e7f07d 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -65,6 +65,7 @@ test { if (builtin.zig_is_stage2) { // When all comptime_memory.zig tests pass, #9646 can be closed. // _ = @import("behavior/comptime_memory.zig"); + _ = @import("behavior/slice_stage2.zig"); } else { _ = @import("behavior/align_stage1.zig"); _ = @import("behavior/alignof.zig"); diff --git a/test/behavior/slice_stage2.zig b/test/behavior/slice_stage2.zig new file mode 100644 index 0000000000..360527e8ba --- /dev/null +++ b/test/behavior/slice_stage2.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const x = @intToPtr([*]i32, 0x1000)[0..0x500]; +const y = x[0x100..]; +test "compile time slice of pointer to hard coded address" { + try expect(@ptrToInt(x) == 0x1000); + try expect(x.len == 0x500); + + try expect(@ptrToInt(y) == 0x1400); + try expect(y.len == 0x400); +} From 4eb7b28700b23d8465a36e364e60394b2a1da41b Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 25 Oct 2021 03:41:23 +0200 Subject: [PATCH 3/4] stage2: generate correct constants for zero-sized arrays --- src/codegen/llvm.zig | 6 +++--- test/behavior/slice.zig | 9 +++++++++ test/behavior/slice_stage1.zig | 9 --------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 80f2d8b653..646633a90d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -762,7 +762,7 @@ pub const DeclGen = struct { } const llvm_addrspace = dg.llvmAddressSpace(t.ptrAddressSpace()); const elem_ty = t.childType(); - const llvm_elem_ty = if (elem_ty.hasCodeGenBits()) + const llvm_elem_ty = if (elem_ty.hasCodeGenBits() or elem_ty.zigTypeTag() == .Array) try dg.llvmType(elem_ty) else dg.context.intType(8); @@ -1475,7 +1475,7 @@ pub const DeclGen = struct { } const llvm_type = try self.llvmType(tv.ty); - if (!tv.ty.childType().hasCodeGenBits()) { + if (!tv.ty.childType().hasCodeGenBits() or !decl.ty.hasCodeGenBits()) { return self.lowerPtrToVoid(tv.ty); } @@ -1497,7 +1497,7 @@ pub const DeclGen = struct { // for non-optional pointers. We also need to respect the alignment, even though // the address will never be dereferenced. const llvm_usize = try dg.llvmType(Type.usize); - const llvm_ptr_ty = dg.context.intType(8).pointerType(0); + const llvm_ptr_ty = try dg.llvmType(ptr_ty); if (alignment != 0) { return llvm_usize.constInt(alignment, .False).constIntToPtr(llvm_ptr_ty); } diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index dfe2b39297..0332cff802 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -157,3 +157,12 @@ test "comptime pointer cast array and then slice" { try expect(sliceA[1] == 2); try expect(sliceB[1] == 2); } + +test "slicing zero length array" { + const s1 = ""[0..]; + const s2 = ([_]u32{})[0..]; + try expect(s1.len == 0); + try expect(s2.len == 0); + try expect(mem.eql(u8, s1, "")); + try expect(mem.eql(u32, s2, &[_]u32{})); +} diff --git a/test/behavior/slice_stage1.zig b/test/behavior/slice_stage1.zig index e587c5fb53..5bbadc3cc6 100644 --- a/test/behavior/slice_stage1.zig +++ b/test/behavior/slice_stage1.zig @@ -4,15 +4,6 @@ const expectEqualSlices = std.testing.expectEqualSlices; const expectEqual = std.testing.expectEqual; const mem = std.mem; -test "slicing zero length array" { - const s1 = ""[0..]; - const s2 = ([_]u32{})[0..]; - try expect(s1.len == 0); - try expect(s2.len == 0); - try expect(mem.eql(u8, s1, "")); - try expect(mem.eql(u32, s2, &[_]u32{})); -} - test "slice string literal has correct type" { comptime { try expect(@TypeOf("aoeu"[0..]) == *const [4:0]u8); From 21bf3b80666c14c9b2a2e1ec984a6b4bb23a5bb7 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 26 Oct 2021 00:51:46 +0200 Subject: [PATCH 4/4] stage2: runtime c pointer null comparison --- src/codegen/llvm.zig | 11 ++++++----- src/type.zig | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 646633a90d..1a4bada192 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2450,6 +2450,12 @@ pub const FuncGen = struct { const operand = try self.resolveInst(un_op); const operand_ty = self.air.typeOf(un_op); const optional_ty = if (operand_is_ptr) operand_ty.childType() else operand_ty; + if (optional_ty.isPtrLikeOptional()) { + const optional_llvm_ty = try self.dg.llvmType(optional_ty); + const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand; + return self.builder.buildICmp(pred, loaded, optional_llvm_ty.constNull(), ""); + } + var buf: Type.Payload.ElemType = undefined; const payload_ty = optional_ty.optionalChild(&buf); if (!payload_ty.hasCodeGenBits()) { @@ -2459,11 +2465,6 @@ pub const FuncGen = struct { return operand; } } - if (optional_ty.isPtrLikeOptional()) { - const optional_llvm_ty = try self.dg.llvmType(optional_ty); - const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand; - return self.builder.buildICmp(pred, loaded, optional_llvm_ty.constNull(), ""); - } if (operand_is_ptr or isByRef(optional_ty)) { const index_type = self.context.intType(32); diff --git a/src/type.zig b/src/type.zig index e77780d9ca..7ac235e68e 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2347,11 +2347,13 @@ pub const Type = extern union { } } - /// Asserts that the type is an optional + /// Asserts that the type is an optional or a pointer that can be null. pub fn isPtrLikeOptional(self: Type) bool { switch (self.tag()) { .optional_single_const_pointer, .optional_single_mut_pointer, + .c_const_pointer, + .c_mut_pointer, => return true, .optional => { @@ -2367,6 +2369,8 @@ pub const Type = extern union { .Many, .One => return !info.@"allowzero", } }, + + .pointer => return self.castTag(.pointer).?.data.size == .C, else => unreachable, } }