zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit cf75cad899069d67d50e6d08160b998b7cba16dc (tree)
parent 4334dbb9a5ddfa58a06f3ef7759927e2cb38e65b
Author: Martin Wickham <spexguy070@gmail.com>
Date:   Mon, 30 Aug 2021 13:01:37 -0500

Add comptime memory tests

Diffstat:
Mtest/behavior.zig | 2++
Atest/behavior/comptime_memory.zig | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 319 insertions(+), 0 deletions(-)

diff --git a/test/behavior.zig b/test/behavior.zig @@ -89,6 +89,8 @@ test { _ = @import("behavior/byval_arg_var.zig"); _ = @import("behavior/call.zig"); _ = @import("behavior/cast_stage1.zig"); + // When these tests pass, #9646 can be closed. + // _ = @import("behavior/comptime_memory.zig"); _ = @import("behavior/const_slice_child.zig"); _ = @import("behavior/defer.zig"); _ = @import("behavior/enum.zig"); diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig @@ -0,0 +1,317 @@ +const endian = @import("builtin").cpu.arch.endian(); +const testing = @import("std").testing; +const ptr_size = @sizeOf(usize); + +test "type pun signed and unsigned as single pointer" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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" { + 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]); + } + } + } +}