Sema: comptime loads and stores for elem_ptr

The index is checked against actual array lengths, and now handles
coerced or casted pointers to single items.
This commit is contained in:
Andrew Kelley
2022-01-11 16:09:17 -07:00
parent 75b6637d60
commit b019a19b55
3 changed files with 146 additions and 96 deletions

View File

@@ -14085,88 +14085,112 @@ fn beginComptimePtrMutation(
.elem_ptr => {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr);
const elem_ty = parent.ty.childType();
switch (parent.val.tag()) {
.undef => {
// An array has been initialized to undefined at comptime and now we
// are for the first time setting an element. We must change the representation
// of the array from `undef` to `array`.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const array_len_including_sentinel =
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
const elems = try arena.alloc(Value, array_len_including_sentinel);
mem.set(Value, elems, Value.undef);
parent.val.* = try Value.Tag.array.create(arena, elems);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &elems[elem_ptr.index],
.ty = elem_ty,
};
},
.bytes => {
// An array is memory-optimized to store a slice of bytes, but we are about
// to modify an individual field and the representation has to change.
// If we wanted to avoid this, there would need to be special detection
// elsewhere to identify when writing a value to an array element that is stored
// using the `bytes` tag, and handle it without making a call to this function.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const bytes = parent.val.castTag(.bytes).?.data;
const dest_len = parent.ty.arrayLenIncludingSentinel();
// bytes.len may be one greater than dest_len because of the case when
// assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
assert(bytes.len >= dest_len);
const elems = try arena.alloc(Value, @intCast(usize, dest_len));
for (elems) |*elem, i| {
elem.* = try Value.Tag.int_u64.create(arena, bytes[i]);
switch (parent.ty.zigTypeTag()) {
.Array, .Vector => {
const check_len = parent.ty.arrayLenIncludingSentinel();
if (elem_ptr.index >= check_len) {
// TODO have the parent include the decl so we can say "declared here"
return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{
elem_ptr.index, check_len,
});
}
const elem_ty = parent.ty.childType();
switch (parent.val.tag()) {
.undef => {
// An array has been initialized to undefined at comptime and now we
// are for the first time setting an element. We must change the representation
// of the array from `undef` to `array`.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
parent.val.* = try Value.Tag.array.create(arena, elems);
const array_len_including_sentinel =
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
const elems = try arena.alloc(Value, array_len_including_sentinel);
mem.set(Value, elems, Value.undef);
parent.val.* = try Value.Tag.array.create(arena, elems);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &elems[elem_ptr.index],
.ty = elem_ty,
};
},
.bytes => {
// An array is memory-optimized to store a slice of bytes, but we are about
// to modify an individual field and the representation has to change.
// If we wanted to avoid this, there would need to be special detection
// elsewhere to identify when writing a value to an array element that is stored
// using the `bytes` tag, and handle it without making a call to this function.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const bytes = parent.val.castTag(.bytes).?.data;
const dest_len = parent.ty.arrayLenIncludingSentinel();
// bytes.len may be one greater than dest_len because of the case when
// assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
assert(bytes.len >= dest_len);
const elems = try arena.alloc(Value, @intCast(usize, dest_len));
for (elems) |*elem, i| {
elem.* = try Value.Tag.int_u64.create(arena, bytes[i]);
}
parent.val.* = try Value.Tag.array.create(arena, elems);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &elems[elem_ptr.index],
.ty = elem_ty,
};
},
.repeated => {
// An array is memory-optimized to store only a single element value, and
// that value is understood to be the same for the entire length of the array.
// However, now we want to modify an individual field and so the
// representation has to change. If we wanted to avoid this, there would
// need to be special detection elsewhere to identify when writing a value to an
// array element that is stored using the `repeated` tag, and handle it
// without making a call to this function.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena);
const array_len_including_sentinel =
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
const elems = try arena.alloc(Value, array_len_including_sentinel);
mem.set(Value, elems, repeated_val);
parent.val.* = try Value.Tag.array.create(arena, elems);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &elems[elem_ptr.index],
.ty = elem_ty,
};
},
.array => return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &parent.val.castTag(.array).?.data[elem_ptr.index],
.ty = elem_ty,
},
else => unreachable,
}
},
else => {
if (elem_ptr.index != 0) {
// TODO include a "declared here" note for the decl
return sema.fail(block, src, "out of bounds comptime store of index {d}", .{
elem_ptr.index,
});
}
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &elems[elem_ptr.index],
.ty = elem_ty,
.val = parent.val,
.ty = parent.ty,
};
},
.repeated => {
// An array is memory-optimized to store only a single element value, and
// that value is understood to be the same for the entire length of the array.
// However, now we want to modify an individual field and so the
// representation has to change. If we wanted to avoid this, there would
// need to be special detection elsewhere to identify when writing a value to an
// array element that is stored using the `repeated` tag, and handle it
// without making a call to this function.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena);
const array_len_including_sentinel =
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
const elems = try arena.alloc(Value, array_len_including_sentinel);
mem.set(Value, elems, repeated_val);
parent.val.* = try Value.Tag.array.create(arena, elems);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &elems[elem_ptr.index],
.ty = elem_ty,
};
},
.array => return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &parent.val.castTag(.array).?.data[elem_ptr.index],
.ty = elem_ty,
},
else => unreachable,
}
},
.field_ptr => {
@@ -14296,15 +14320,41 @@ fn beginComptimePtrLoad(
.elem_ptr => {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
const parent = try beginComptimePtrLoad(sema, block, src, elem_ptr.array_ptr);
const elem_ty = parent.ty.childType();
const elem_size = elem_ty.abiSize(target);
return ComptimePtrLoadKit{
.root_val = parent.root_val,
.val = try parent.val.elemValue(sema.arena, elem_ptr.index),
.ty = elem_ty,
.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
.is_mutable = parent.is_mutable,
};
switch (parent.ty.zigTypeTag()) {
.Array, .Vector => {
const check_len = parent.ty.arrayLenIncludingSentinel();
if (elem_ptr.index >= check_len) {
// TODO have the parent include the decl so we can say "declared here"
return sema.fail(block, src, "comptime load of index {d} out of bounds of array length {d}", .{
elem_ptr.index, check_len,
});
}
const elem_ty = parent.ty.childType();
const elem_size = elem_ty.abiSize(target);
return ComptimePtrLoadKit{
.root_val = parent.root_val,
.val = try parent.val.elemValue(sema.arena, elem_ptr.index),
.ty = elem_ty,
.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
.is_mutable = parent.is_mutable,
};
},
else => {
if (elem_ptr.index != 0) {
// TODO have the parent include the decl so we can say "declared here"
return sema.fail(block, src, "out of bounds comptime load of index {d}", .{
elem_ptr.index,
});
}
return ComptimePtrLoadKit{
.root_val = parent.root_val,
.val = parent.val,
.ty = parent.ty,
.byte_offset = parent.byte_offset,
.is_mutable = parent.is_mutable,
};
},
}
},
.field_ptr => {
const field_ptr = ptr_val.castTag(.field_ptr).?.data;

View File

@@ -33,3 +33,15 @@ test "read/write through global variable array of struct fields initialized via
};
try S.doTheTest();
}
test "implicit cast single-item pointer" {
try testImplicitCastSingleItemPtr();
comptime try testImplicitCastSingleItemPtr();
}
fn testImplicitCastSingleItemPtr() !void {
var byte: u8 = 100;
const slice = @as(*[1]u8, &byte)[0..];
slice[0] += 1;
try expect(byte == 101);
}

View File

@@ -4,18 +4,6 @@ const mem = std.mem;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
test "implicit cast single-item pointer" {
try testImplicitCastSingleItemPtr();
comptime try testImplicitCastSingleItemPtr();
}
fn testImplicitCastSingleItemPtr() !void {
var byte: u8 = 100;
const slice = @as(*[1]u8, &byte)[0..];
slice[0] += 1;
try expect(byte == 101);
}
fn testArrayByValAtComptime(b: [2]u8) u8 {
return b[0];
}