zig/test/behavior/comptime_memory.zig
Andrew Kelley b34f994c0b stage2: type system treats fn ptr and body separately
This commit updates stage2 to enforce the property that the syntax
`fn()void` is a function *body* not a *pointer*. To get a pointer, the
syntax `*const fn()void` is required.

ZIR puts function alignment into the func instruction rather than the
decl because this way it makes it into function types. LLVM backend
respects function alignments.

Struct and Union have methods `fieldSrcLoc` to help look up source
locations of their fields. These trigger full loading, tokenization, and
parsing of source files, so should only be called once it is confirmed
that an error message needs to be printed.

There are some nice new error hints for explaining why a type is
required to be comptime, particularly for structs that contain function
body types.

`Type.requiresComptime` is now moved into Sema because it can fail and
might need to trigger field type resolution. Comptime pointer loading
takes into account types that do not have a well-defined memory layout
and does not try to compute a byte offset for them.

`fn()void` syntax no longer secretly makes a pointer. You get a function
body type, which requires comptime. However a pointer to a function body
can be runtime known (obviously).

Compile errors that report "expected pointer, found ..." are factored
out into convenience functions `checkPtrOperand` and `checkPtrType` and
have a note about function pointers.

Implemented `Value.hash` for functions, enum literals, and undefined values.

stage1 is not updated to this (yet?), so some workarounds and disabled
tests are needed to keep everything working. Should we update stage1 to
these new type semantics? Yes probably because I don't want to add too
much conditional compilation logic in the std lib for the different
backends.
2022-01-24 21:47:53 -07:00

415 lines
14 KiB
Zig

const builtin = @import("builtin");
const endian = builtin.cpu.arch.endian();
const testing = @import("std").testing;
const ptr_size = @sizeOf(usize);
test "type pun signed and unsigned as single pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast(*i32, &x);
y.* = -1;
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
}
}
test "type pun signed and unsigned as many pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast([*]i32, &x);
y[0] = -1;
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
}
}
test "type pun signed and unsigned as array pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast(*[1]i32, &x);
y[0] = -1;
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
}
}
test "type pun signed and unsigned as offset many pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
var y = @ptrCast([*]i32, &x);
y -= 10;
y[10] = -1;
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
}
}
test "type pun signed and unsigned as array pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast([*]i32, &x) - 10;
const z: *[15]i32 = y[0..15];
z[10] = -1;
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
}
}
test "type pun value and struct" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const StructOfU32 = extern struct { x: u32 };
var inst: StructOfU32 = .{ .x = 0 };
@ptrCast(*i32, &inst.x).* = -1;
try testing.expectEqual(@as(u32, 0xFFFFFFFF), inst.x);
@ptrCast(*i32, &inst).* = -2;
try testing.expectEqual(@as(u32, 0xFFFFFFFE), inst.x);
}
}
fn bigToNativeEndian(comptime T: type, v: T) T {
return if (endian == .Big) v else @byteSwap(T, v);
}
test "type pun endianness" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const StructOfBytes = extern struct { x: [4]u8 };
var inst: StructOfBytes = .{ .x = [4]u8{ 0, 0, 0, 0 } };
const structPtr = @ptrCast(*align(1) u32, &inst);
const arrayPtr = @ptrCast(*align(1) u32, &inst.x);
inst.x[0] = 0xFE;
inst.x[2] = 0xBE;
try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), structPtr.*);
try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), arrayPtr.*);
structPtr.* = bigToNativeEndian(u32, 0xDEADF00D);
try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), structPtr.*);
try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), arrayPtr.*);
try testing.expectEqual(@as(u8, 0xDE), inst.x[0]);
try testing.expectEqual(@as(u8, 0xAD), inst.x[1]);
try testing.expectEqual(@as(u8, 0xF0), inst.x[2]);
try testing.expectEqual(@as(u8, 0x0D), inst.x[3]);
}
}
const Bits = packed struct {
// Note: This struct has only single byte words so it
// doesn't need to be byte swapped.
p0: u1,
p1: u4,
p2: u3,
p3: u2,
p4: u6,
p5: u8,
p6: u7,
p7: u1,
};
const ShuffledBits = packed struct {
p1: u4,
p3: u2,
p7: u1,
p0: u1,
p5: u8,
p2: u3,
p6: u7,
p4: u6,
};
fn shuffle(ptr: usize, comptime From: type, comptime To: type) usize {
if (@sizeOf(From) != @sizeOf(To))
@compileError("Mismatched sizes! " ++ @typeName(From) ++ " and " ++ @typeName(To) ++ " must have the same size!");
const array_len = @divExact(ptr_size, @sizeOf(From));
var result: usize = 0;
const pSource = @ptrCast(*align(1) const [array_len]From, &ptr);
const pResult = @ptrCast(*align(1) [array_len]To, &result);
var i: usize = 0;
while (i < array_len) : (i += 1) {
inline for (@typeInfo(To).Struct.fields) |f| {
@field(pResult[i], f.name) = @field(pSource[i], f.name);
}
}
return result;
}
fn doTypePunBitsTest(as_bits: *Bits) !void {
const as_u32 = @ptrCast(*align(1) u32, as_bits);
const as_bytes = @ptrCast(*[4]u8, as_bits);
as_u32.* = bigToNativeEndian(u32, 0xB0A7DEED);
try testing.expectEqual(@as(u1, 0x00), as_bits.p0);
try testing.expectEqual(@as(u4, 0x08), as_bits.p1);
try testing.expectEqual(@as(u3, 0x05), as_bits.p2);
try testing.expectEqual(@as(u2, 0x03), as_bits.p3);
try testing.expectEqual(@as(u6, 0x29), as_bits.p4);
try testing.expectEqual(@as(u8, 0xDE), as_bits.p5);
try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
try testing.expectEqual(@as(u1, 0x01), as_bits.p7);
as_bits.p6 = 0x2D;
as_bits.p1 = 0x0F;
try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAD), as_u32.*);
// clobbering one bit doesn't clobber the word
as_bits.p7 = undefined;
try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
// even when read as a whole
const u = as_u32.*;
_ = u; // u is undefined
try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
// or if a field which shares the byte is modified
as_bits.p6 = 0x6D;
try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
// but overwriting the undefined will clear it
as_bytes[3] = 0xAF;
try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAF), as_u32.*);
}
test "type pun bits" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var v: u32 = undefined;
try doTypePunBitsTest(@ptrCast(*Bits, &v));
}
}
const imports = struct {
var global_u32: u32 = 0;
};
// Make sure lazy values work on their own, before getting into more complex tests
test "basic pointer preservation" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const lazy_address = @ptrToInt(&imports.global_u32);
try testing.expectEqual(@ptrToInt(&imports.global_u32), lazy_address);
try testing.expectEqual(&imports.global_u32, @intToPtr(*u32, lazy_address));
}
}
test "byte copy preserves linker value" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
const ct_value = comptime blk: {
const lazy = &imports.global_u32;
var result: *u32 = undefined;
const pSource = @ptrCast(*const [ptr_size]u8, &lazy);
const pResult = @ptrCast(*[ptr_size]u8, &result);
var i: usize = 0;
while (i < ptr_size) : (i += 1) {
pResult[i] = pSource[i];
try testing.expectEqual(pSource[i], pResult[i]);
}
try testing.expectEqual(&imports.global_u32, result);
break :blk result;
};
try testing.expectEqual(&imports.global_u32, ct_value);
}
test "unordered byte copy preserves linker value" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
const ct_value = comptime blk: {
const lazy = &imports.global_u32;
var result: *u32 = undefined;
const pSource = @ptrCast(*const [ptr_size]u8, &lazy);
const pResult = @ptrCast(*[ptr_size]u8, &result);
if (ptr_size > 8) @compileError("This array needs to be expanded for platform with very big pointers");
const shuffled_indices = [_]usize{ 4, 5, 2, 6, 1, 3, 0, 7 };
for (shuffled_indices) |i| {
pResult[i] = pSource[i];
try testing.expectEqual(pSource[i], pResult[i]);
}
try testing.expectEqual(&imports.global_u32, result);
break :blk result;
};
try testing.expectEqual(&imports.global_u32, ct_value);
}
test "shuffle chunks of linker value" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
const lazy_address = @ptrToInt(&imports.global_u32);
const shuffled1_rt = shuffle(lazy_address, Bits, ShuffledBits);
const unshuffled1_rt = shuffle(shuffled1_rt, ShuffledBits, Bits);
try testing.expectEqual(lazy_address, unshuffled1_rt);
const shuffled1_ct = comptime shuffle(lazy_address, Bits, ShuffledBits);
const shuffled1_ct_2 = comptime shuffle(lazy_address, Bits, ShuffledBits);
comptime try testing.expectEqual(shuffled1_ct, shuffled1_ct_2);
const unshuffled1_ct = comptime shuffle(shuffled1_ct, ShuffledBits, Bits);
comptime try testing.expectEqual(lazy_address, unshuffled1_ct);
try testing.expectEqual(shuffled1_ct, shuffled1_rt);
}
test "dance on linker values" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var arr: [2]usize = undefined;
arr[0] = @ptrToInt(&imports.global_u32);
arr[1] = @ptrToInt(&imports.global_u32);
const weird_ptr = @ptrCast([*]Bits, @ptrCast([*]u8, &arr) + @sizeOf(usize) - 3);
try doTypePunBitsTest(&weird_ptr[0]);
if (ptr_size > @sizeOf(Bits))
try doTypePunBitsTest(&weird_ptr[1]);
var arr_bytes = @ptrCast(*[2][ptr_size]u8, &arr);
var rebuilt_bytes: [ptr_size]u8 = undefined;
var i: usize = 0;
while (i < ptr_size - 3) : (i += 1) {
rebuilt_bytes[i] = arr_bytes[0][i];
}
while (i < ptr_size) : (i += 1) {
rebuilt_bytes[i] = arr_bytes[1][i];
}
try testing.expectEqual(&imports.global_u32, @intToPtr(*u32, @bitCast(usize, rebuilt_bytes)));
}
}
test "offset array ptr by element size" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const VirtualStruct = struct { x: u32 };
var arr: [4]VirtualStruct = .{
.{ .x = bigToNativeEndian(u32, 0x0004080c) },
.{ .x = bigToNativeEndian(u32, 0x0105090d) },
.{ .x = bigToNativeEndian(u32, 0x02060a0e) },
.{ .x = bigToNativeEndian(u32, 0x03070b0f) },
};
const address = @ptrToInt(&arr);
try testing.expectEqual(@ptrToInt(&arr[0]), address);
try testing.expectEqual(@ptrToInt(&arr[0]) + 10, address + 10);
try testing.expectEqual(@ptrToInt(&arr[1]), address + @sizeOf(VirtualStruct));
try testing.expectEqual(@ptrToInt(&arr[2]), address + 2 * @sizeOf(VirtualStruct));
try testing.expectEqual(@ptrToInt(&arr[3]), address + @sizeOf(VirtualStruct) * 3);
const secondElement = @intToPtr(*VirtualStruct, @ptrToInt(&arr[0]) + 2 * @sizeOf(VirtualStruct));
try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), secondElement.x);
}
}
test "offset instance by field size" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const VirtualStruct = struct { x: u32, y: u32, z: u32, w: u32 };
var inst = VirtualStruct{ .x = 0, .y = 1, .z = 2, .w = 3 };
var ptr = @ptrToInt(&inst);
ptr -= 4;
ptr += @offsetOf(VirtualStruct, "x");
try testing.expectEqual(@as(u32, 0), @intToPtr([*]u32, ptr)[1]);
ptr -= @offsetOf(VirtualStruct, "x");
ptr += @offsetOf(VirtualStruct, "y");
try testing.expectEqual(@as(u32, 1), @intToPtr([*]u32, ptr)[1]);
ptr = ptr - @offsetOf(VirtualStruct, "y") + @offsetOf(VirtualStruct, "z");
try testing.expectEqual(@as(u32, 2), @intToPtr([*]u32, ptr)[1]);
ptr = @ptrToInt(&inst.z) - 4 - @offsetOf(VirtualStruct, "z");
ptr += @offsetOf(VirtualStruct, "w");
try testing.expectEqual(@as(u32, 3), @intToPtr(*u32, ptr + 4).*);
}
}
test "offset field ptr by enclosing array element size" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const VirtualStruct = struct { x: u32 };
var arr: [4]VirtualStruct = .{
.{ .x = bigToNativeEndian(u32, 0x0004080c) },
.{ .x = bigToNativeEndian(u32, 0x0105090d) },
.{ .x = bigToNativeEndian(u32, 0x02060a0e) },
.{ .x = bigToNativeEndian(u32, 0x03070b0f) },
};
var i: usize = 0;
while (i < 4) : (i += 1) {
var ptr: [*]u8 = @ptrCast([*]u8, &arr[0]);
ptr += i;
ptr += @offsetOf(VirtualStruct, "x");
var j: usize = 0;
while (j < 4) : (j += 1) {
const base = ptr + j * @sizeOf(VirtualStruct);
try testing.expectEqual(@intCast(u8, i * 4 + j), base[0]);
}
}
}
}