stage2: elem_ptr needs to know if slice or direct access

This fixes one of the major issues plaguing the `std.sort` comptime tests.
The high level issue is that at comptime, we need to know whether `elem_ptr` is
being used to subslice an array-like pointer or access a child value. High-level
example:

    var x: [2][2]i32 = undefined;
    var a = &x[0]; // elem_ptr, type *[2]i32

    var y: [5]i32 = undefined;
    var b = y[1..3]; // elem_ptr, type *[2]i32

`a` is pointing directly to the 0th element of `x`. But `b` is
subslicing the 1st and 2nd element of `y`. At runtime with a well
defined memory layout, this is an inconsequential detail. At comptime,
the values aren't laid out exactly in-memory so we need to know the
difference.

This becomes an issue specifically in this case:

    var c: []i32 = a;
    var d: []i32 = b;

When converting the `*[N]T` to `[]T` we need to know what array to point
to. For runtime, its all the same. For comptime, we need to know if its
the parent array or the child value.

See the behavior tests for more details.

This commit fixes this by adding a boolean to track this on the
`elem_ptr`. We can't just immediately deref the child for `&x[0]`
because it is legal to ptrCast it to a many-pointer, do arithmetic, and
then cast it back (see behavior test) so we need to retain access to the
"parent" indexable.
This commit is contained in:
Mitchell Hashimoto
2022-03-16 12:21:48 -07:00
committed by Andrew Kelley
parent 94672dfb19
commit 418197b6c5
3 changed files with 96 additions and 2 deletions

View File

@@ -16739,7 +16739,7 @@ fn elemPtrArray(
const index_u64 = index_val.toUnsignedInt();
// @intCast here because it would have been impossible to construct a value that
// required a larger index.
const elem_ptr = try array_ptr_val.elemPtr(array_ptr_ty, sema.arena, @intCast(usize, index_u64));
const elem_ptr = try array_ptr_val.elemPtrDirect(array_ptr_ty, sema.arena, @intCast(usize, index_u64));
return sema.addConstant(result_ty, elem_ptr);
}
}

View File

@@ -505,6 +505,7 @@ pub const Value = extern union {
.array_ptr = try payload.data.array_ptr.copy(arena),
.elem_ty = try payload.data.elem_ty.copy(arena),
.index = payload.data.index,
.direct = payload.data.direct,
},
};
return Value{ .ptr_otherwise = &new_payload.base };
@@ -2402,7 +2403,11 @@ pub const Value = extern union {
.decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValueAdvanced(index, arena, buffer),
.elem_ptr => {
const data = val.castTag(.elem_ptr).?.data;
return data.array_ptr.elemValueAdvanced(index + data.index, arena, buffer);
if (!data.direct)
return data.array_ptr.elemValueAdvanced(index + data.index, arena, buffer);
const underlying = try data.array_ptr.elemValueAdvanced(data.index, arena, buffer);
return underlying.elemValueAdvanced(index, arena, buffer);
},
// The child type of arrays which have only one possible value need
@@ -2465,12 +2470,25 @@ pub const Value = extern union {
/// Returns a pointer to the element value at the index.
pub fn elemPtr(val: Value, ty: Type, arena: Allocator, index: usize) Allocator.Error!Value {
return val.elemPtrAdvanced(ty, arena, index, false);
}
/// Returns a pointer to the element value at the index. The behavior
/// of this is slightly different for comptime; the "direct" means that
/// indexing indexes the referenced child value, not the parent array.
pub fn elemPtrDirect(val: Value, ty: Type, arena: Allocator, index: usize) Allocator.Error!Value {
return val.elemPtrAdvanced(ty, arena, index, true);
}
pub fn elemPtrAdvanced(val: Value, ty: Type, arena: Allocator, index: usize, direct: bool) Allocator.Error!Value {
const elem_ty = ty.elemType2();
const ptr_val = switch (val.tag()) {
.slice => val.castTag(.slice).?.data.ptr,
else => val,
};
// If the val is already an elem ptr, then we do ptr arithmetic logic
// and just move the index.
if (ptr_val.tag() == .elem_ptr) {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
if (elem_ptr.elem_ty.eql(elem_ty)) {
@@ -2478,6 +2496,12 @@ pub const Value = extern union {
.array_ptr = elem_ptr.array_ptr,
.elem_ty = elem_ptr.elem_ty,
.index = elem_ptr.index + index,
// Retain the direct preference. This enables a direct
// elem ptr (i.e. &arr[0]) to be bitcasted to a many-pointer
// with pointer arithmetic then casted back to a single
// pointer.
.direct = elem_ptr.direct,
});
}
}
@@ -2485,6 +2509,7 @@ pub const Value = extern union {
.array_ptr = ptr_val,
.elem_ty = elem_ty,
.index = index,
.direct = direct,
});
}
@@ -4194,6 +4219,7 @@ pub const Value = extern union {
array_ptr: Value,
elem_ty: Type,
index: usize,
direct: bool,
},
};

View File

@@ -437,3 +437,71 @@ 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 "element pointer to slice" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var cases: [2][2]i32 = [_][2]i32{
[_]i32{ 0, 1 },
[_]i32{ 2, 3 },
};
const items: []i32 = &cases[0]; // *[2]i32
try testing.expect(items.len == 2);
try testing.expect(items[1] == 1);
try testing.expect(items[0] == 0);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "element pointer arithmetic to slice" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var cases: [2][2]i32 = [_][2]i32{
[_]i32{ 0, 1 },
[_]i32{ 2, 3 },
};
const elem_ptr = &cases[0]; // *[2]i32
const many = @ptrCast([*][2]i32, elem_ptr);
const many_elem = @ptrCast(*[2]i32, &many[1]);
const items: []i32 = many_elem;
try testing.expect(items.len == 2);
try testing.expect(items[1] == 3);
try testing.expect(items[0] == 2);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "array slicing to slice" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var str: [5]i32 = [_]i32{ 1, 2, 3, 4, 5 };
var sub: *[2]i32 = str[1..3];
var slice: []i32 = sub; // used to cause failures
try testing.expect(slice.len == 2);
try testing.expect(slice[0] == 2);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}