mirror of
https://github.com/ziglang/zig.git
synced 2024-12-03 10:28:48 +00:00
9c3670fc93
This commit changes how we represent comptime-mutable memory (`comptime var`) in the compiler in order to implement the intended behavior that references to such memory can only exist at comptime. It does *not* clean up the representation of mutable values, improve the representation of comptime-known pointers, or fix the many bugs in the comptime pointer access code. These will be future enhancements. Comptime memory lives for the duration of a single Sema, and is not permitted to escape that one analysis, either by becoming runtime-known or by becoming comptime-known to other analyses. These restrictions mean that we can represent comptime allocations not via Decl, but with state local to Sema - specifically, the new `Sema.comptime_allocs` field. All comptime-mutable allocations, as well as any comptime-known const allocs containing references to such memory, live in here. This allows for relatively fast checking of whether a value references any comptime-mtuable memory, since we need only traverse values up to pointers: pointers to Decls can never reference comptime-mutable memory, and pointers into `Sema.comptime_allocs` always do. This change exposed some faulty pointer access logic in `Value.zig`. I've fixed the important cases, but there are some TODOs I've put in which are definitely possible to hit with sufficiently esoteric code. I plan to resolve these by auditing all direct accesses to pointers (most of them ought to use Sema to perform the pointer access!), but for now this is sufficient for all realistic code and to get tests passing. This change eliminates `Zcu.tmp_hack_arena`, instead using the Sema arena for comptime memory mutations, which is possible since comptime memory is now local to the current Sema. This change should allow `Decl` to store only an `InternPool.Index` rather than a full-blown `ty: Type, val: Value`. This commit does not perform this refactor.
540 lines
17 KiB
Zig
540 lines
17 KiB
Zig
const std = @import("std");
|
|
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" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as(*i32, @ptrCast(&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 = @as([*]i32, @ptrCast(&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 = @as(*[1]i32, @ptrCast(&x));
|
|
y[0] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as offset many pointer" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var x: u32 = 0;
|
|
var y = @as([*]i32, @ptrCast(&x));
|
|
y -= 10;
|
|
y[10] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as array pointer with pointer arithemtic" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as([*]i32, @ptrCast(&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 };
|
|
@as(*i32, @ptrCast(&inst.x)).* = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), inst.x);
|
|
@as(*i32, @ptrCast(&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(v);
|
|
}
|
|
test "type pun endianness" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
comptime {
|
|
const StructOfBytes = extern struct { x: [4]u8 };
|
|
var inst: StructOfBytes = .{ .x = [4]u8{ 0, 0, 0, 0 } };
|
|
const structPtr = @as(*align(1) u32, @ptrCast(&inst));
|
|
const arrayPtr = @as(*align(1) u32, @ptrCast(&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 = @as(*align(1) const [array_len]From, @ptrCast(&ptr));
|
|
const pResult = @as(*align(1) [array_len]To, @ptrCast(&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 = @as(*align(1) u32, @ptrCast(as_bits));
|
|
const as_bytes = @as(*[4]u8, @ptrCast(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 (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var v: u32 = undefined;
|
|
try doTypePunBitsTest(@as(*Bits, @ptrCast(&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 (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const lazy_address = @intFromPtr(&imports.global_u32);
|
|
try testing.expectEqual(@intFromPtr(&imports.global_u32), lazy_address);
|
|
try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(lazy_address)));
|
|
}
|
|
}
|
|
|
|
test "byte copy preserves linker value" {
|
|
if (true) {
|
|
// 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 = @as(*const [ptr_size]u8, @ptrCast(&lazy));
|
|
const pResult = @as(*[ptr_size]u8, @ptrCast(&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 (true) {
|
|
// 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 = @as(*const [ptr_size]u8, @ptrCast(&lazy));
|
|
const pResult = @as(*[ptr_size]u8, @ptrCast(&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 (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const lazy_address = @intFromPtr(&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);
|
|
try comptime testing.expectEqual(shuffled1_ct, shuffled1_ct_2);
|
|
const unshuffled1_ct = comptime shuffle(shuffled1_ct, ShuffledBits, Bits);
|
|
try comptime testing.expectEqual(lazy_address, unshuffled1_ct);
|
|
try testing.expectEqual(shuffled1_ct, shuffled1_rt);
|
|
}
|
|
|
|
test "dance on linker values" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var arr: [2]usize = undefined;
|
|
arr[0] = @intFromPtr(&imports.global_u32);
|
|
arr[1] = @intFromPtr(&imports.global_u32);
|
|
|
|
const weird_ptr = @as([*]Bits, @ptrCast(@as([*]u8, @ptrCast(&arr)) + @sizeOf(usize) - 3));
|
|
try doTypePunBitsTest(&weird_ptr[0]);
|
|
if (ptr_size > @sizeOf(Bits))
|
|
try doTypePunBitsTest(&weird_ptr[1]);
|
|
|
|
const arr_bytes: *[2][ptr_size]u8 = @ptrCast(&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, @as(*u32, @ptrFromInt(@as(usize, @bitCast(rebuilt_bytes)))));
|
|
}
|
|
}
|
|
|
|
test "offset array ptr by element size" {
|
|
if (true) {
|
|
// 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 = @intFromPtr(&arr);
|
|
try testing.expectEqual(@intFromPtr(&arr[0]), address);
|
|
try testing.expectEqual(@intFromPtr(&arr[0]) + 10, address + 10);
|
|
try testing.expectEqual(@intFromPtr(&arr[1]), address + @sizeOf(VirtualStruct));
|
|
try testing.expectEqual(@intFromPtr(&arr[2]), address + 2 * @sizeOf(VirtualStruct));
|
|
try testing.expectEqual(@intFromPtr(&arr[3]), address + @sizeOf(VirtualStruct) * 3);
|
|
|
|
const secondElement = @as(*VirtualStruct, @ptrFromInt(@intFromPtr(&arr[0]) + 2 * @sizeOf(VirtualStruct)));
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), secondElement.x);
|
|
}
|
|
}
|
|
|
|
test "offset instance by field size" {
|
|
if (true) {
|
|
// 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 = @intFromPtr(&inst);
|
|
ptr -= 4;
|
|
ptr += @offsetOf(VirtualStruct, "x");
|
|
try testing.expectEqual(@as(u32, 0), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr -= @offsetOf(VirtualStruct, "x");
|
|
ptr += @offsetOf(VirtualStruct, "y");
|
|
try testing.expectEqual(@as(u32, 1), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr = ptr - @offsetOf(VirtualStruct, "y") + @offsetOf(VirtualStruct, "z");
|
|
try testing.expectEqual(@as(u32, 2), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr = @intFromPtr(&inst.z) - 4 - @offsetOf(VirtualStruct, "z");
|
|
ptr += @offsetOf(VirtualStruct, "w");
|
|
try testing.expectEqual(@as(u32, 3), @as(*u32, @ptrFromInt(ptr + 4)).*);
|
|
}
|
|
}
|
|
|
|
test "offset field ptr by enclosing array element size" {
|
|
if (true) {
|
|
// 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 = @as([*]u8, @ptrCast(&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(@as(u8, @intCast(i * 4 + j)), base[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test "accessing reinterpreted memory of parent object" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
const S = extern struct {
|
|
a: f32,
|
|
b: [4]u8,
|
|
c: f32,
|
|
};
|
|
const expected = if (endian == .little) 102 else 38;
|
|
|
|
comptime {
|
|
const x = S{
|
|
.a = 1.5,
|
|
.b = [_]u8{ 1, 2, 3, 4 },
|
|
.c = 2.6,
|
|
};
|
|
const ptr = &x.b[0];
|
|
const b = @as([*c]const u8, @ptrCast(ptr))[5];
|
|
try testing.expect(b == expected);
|
|
}
|
|
}
|
|
|
|
test "bitcast packed union to integer" {
|
|
if (true) {
|
|
// https://github.com/ziglang/zig/issues/19384
|
|
return error.SkipZigTest;
|
|
}
|
|
const U = packed union {
|
|
x: u1,
|
|
y: u2,
|
|
};
|
|
|
|
comptime {
|
|
const a = U{ .x = 1 };
|
|
const b = U{ .y = 2 };
|
|
const cast_a = @as(u2, @bitCast(a));
|
|
const cast_b = @as(u2, @bitCast(b));
|
|
|
|
// truncated because the upper bit is garbage memory that we don't care about
|
|
try testing.expectEqual(@as(u1, 1), @as(u1, @truncate(cast_a)));
|
|
try testing.expectEqual(@as(u2, 2), cast_b);
|
|
}
|
|
}
|
|
|
|
test "mutate entire slice at comptime" {
|
|
comptime {
|
|
var buf: [3]u8 = undefined;
|
|
const x: [2]u8 = .{ 1, 2 }; // Avoid RLS
|
|
buf[1..3].* = x;
|
|
}
|
|
}
|
|
|
|
test "dereference undefined pointer to zero-bit type" {
|
|
const p0: *void = undefined;
|
|
try testing.expectEqual({}, p0.*);
|
|
|
|
const p1: *[0]u32 = undefined;
|
|
try testing.expect(p1.*.len == 0);
|
|
}
|
|
|
|
test "type pun extern struct" {
|
|
const S = extern struct { f: u8 };
|
|
comptime var s = S{ .f = 123 };
|
|
@as(*u8, @ptrCast(&s)).* = 72;
|
|
try testing.expectEqual(@as(u8, 72), s.f);
|
|
}
|
|
|
|
test "type pun @ptrFromInt" {
|
|
const p: *u8 = @ptrFromInt(42);
|
|
// note that expectEqual hides the bug
|
|
try testing.expect(@as(*const [*]u8, @ptrCast(&p)).* == @as([*]u8, @ptrFromInt(42)));
|
|
}
|
|
|
|
test "type pun null pointer-like optional" {
|
|
const p: ?*u8 = null;
|
|
// note that expectEqual hides the bug
|
|
try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null);
|
|
}
|
|
|
|
test "write empty array to end" {
|
|
comptime var array: [5]u8 = "hello".*;
|
|
array[5..5].* = .{};
|
|
array[5..5].* = [0]u8{};
|
|
array[5..5].* = [_]u8{};
|
|
comptime std.debug.assert(std.mem.eql(u8, "hello", &array));
|
|
}
|
|
|
|
fn doublePtrTest() !void {
|
|
var a: u32 = 0;
|
|
const ptr = &a;
|
|
const double_ptr = &ptr;
|
|
setDoublePtr(double_ptr, 1);
|
|
setDoublePtr(double_ptr, 2);
|
|
setDoublePtr(double_ptr, 1);
|
|
try std.testing.expect(a == 1);
|
|
}
|
|
fn setDoublePtr(ptr: *const *const u32, value: u32) void {
|
|
setPtr(ptr.*, value);
|
|
}
|
|
fn setPtr(ptr: *const u32, value: u32) void {
|
|
const mut_ptr: *u32 = @constCast(ptr);
|
|
mut_ptr.* = value;
|
|
}
|
|
test "double pointer can mutate comptime state" {
|
|
try comptime doublePtrTest();
|
|
}
|
|
|
|
fn GenericIntApplier(
|
|
comptime Context: type,
|
|
comptime applyFn: fn (context: Context, arg: u32) void,
|
|
) type {
|
|
return struct {
|
|
context: Context,
|
|
|
|
const Self = @This();
|
|
|
|
inline fn any(self: *const Self) IntApplier {
|
|
return .{
|
|
.context = @ptrCast(&self.context),
|
|
.applyFn = typeErasedApplyFn,
|
|
};
|
|
}
|
|
|
|
fn typeErasedApplyFn(context: *const anyopaque, arg: u32) void {
|
|
const ptr: *const Context = @alignCast(@ptrCast(context));
|
|
applyFn(ptr.*, arg);
|
|
}
|
|
};
|
|
}
|
|
const IntApplier = struct {
|
|
context: *const anyopaque,
|
|
applyFn: *const fn (context: *const anyopaque, arg: u32) void,
|
|
|
|
fn apply(ia: IntApplier, arg: u32) void {
|
|
ia.applyFn(ia.context, arg);
|
|
}
|
|
};
|
|
const Accumulator = struct {
|
|
value: u32,
|
|
|
|
const Applier = GenericIntApplier(*u32, add);
|
|
|
|
fn applier(a: *Accumulator) Applier {
|
|
return .{ .context = &a.value };
|
|
}
|
|
|
|
fn add(context: *u32, arg: u32) void {
|
|
context.* += arg;
|
|
}
|
|
};
|
|
fn fieldPtrTest() u32 {
|
|
var a: Accumulator = .{ .value = 0 };
|
|
const applier = a.applier();
|
|
applier.any().apply(1);
|
|
applier.any().apply(1);
|
|
return a.value;
|
|
}
|
|
test "pointer in aggregate field can mutate comptime state" {
|
|
try comptime std.testing.expect(fieldPtrTest() == 2);
|
|
}
|