authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-01 09:47:01+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-01 09:48:18+00:00
log149031204c0c804a66571a751b259da95886640d
tree4c36c2b86776a5c0116e7212bc31dd1f4bfe3f78
parentd97441d37ef08813187f1b44ec29612619104585
signaturelock-open Commit is signed but in an unrecognized format.

Sema: skip aliasing check and runtime operation for `@memcpy` of zero-bit type

This check isn't valid in such cases, because the source and destination pointers both refer to zero bits of memory, meaning they effectively never alias. Resolves: #21655

3 files changed, 52 insertions(+), 0 deletions(-)

src/Sema.zig+16
......@@ -25928,6 +25928,22 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
2592825928 }
2592925929 }
2593025930
25931 zero_bit: {
25932 const src_comptime = try src_elem_ty.comptimeOnlySema(pt);
25933 const dest_comptime = try dest_elem_ty.comptimeOnlySema(pt);
25934 assert(src_comptime == dest_comptime); // IMC
25935 if (src_comptime) break :zero_bit;
25936
25937 const src_has_bits = try src_elem_ty.hasRuntimeBitsIgnoreComptimeSema(pt);
25938 const dest_has_bits = try dest_elem_ty.hasRuntimeBitsIgnoreComptimeSema(pt);
25939 assert(src_has_bits == dest_has_bits); // IMC
25940 if (src_has_bits) break :zero_bit;
25941
25942 // The element type is zero-bit. We've done all validation (aside from the aliasing check,
25943 // which we must skip) so we're done.
25944 return;
25945 }
25946
2593125947 const runtime_src = rs: {
2593225948 const dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr) orelse break :rs dest_src;
2593325949 const src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr) orelse break :rs src_src;
src/Type.zig+7
......@@ -452,6 +452,13 @@ pub fn hasRuntimeBitsIgnoreComptime(ty: Type, zcu: *const Zcu) bool {
452452 return hasRuntimeBitsInner(ty, true, .eager, zcu, {}) catch unreachable;
453453}
454454
455pub fn hasRuntimeBitsIgnoreComptimeSema(ty: Type, pt: Zcu.PerThread) SemaError!bool {
456 return hasRuntimeBitsInner(ty, true, .sema, pt.zcu, pt.tid) catch |err| switch (err) {
457 error.NeedLazy => unreachable, // this would require a resolve strat of lazy
458 else => |e| return e,
459 };
460}
461
455462/// true if and only if the type takes up space in memory at runtime.
456463/// There are two reasons a type will return false:
457464/// * the type is a comptime-only type. For example, the type `type` itself.
test/behavior/memcpy.zig+29
......@@ -1,6 +1,7 @@
11const std = @import("std");
22const builtin = @import("builtin");
33const expect = std.testing.expect;
4const assert = std.debug.assert;
45
56test "memcpy and memset intrinsics" {
67 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
......@@ -99,3 +100,31 @@ comptime {
99100 s.set("hello");
100101 if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad");
101102}
103
104test "@memcpy comptime-only type" {
105 const in: [4]type = .{ u8, u16, u32, u64 };
106 comptime var out: [4]type = undefined;
107 @memcpy(&out, &in);
108
109 comptime assert(out[0] == u8);
110 comptime assert(out[1] == u16);
111 comptime assert(out[2] == u32);
112 comptime assert(out[3] == u64);
113}
114
115test "@memcpy zero-bit type with aliasing" {
116 const S = struct {
117 fn doTheTest() void {
118 var buf: [3]void = @splat({});
119 const slice: []void = &buf;
120 // These two pointers are the same, but it's still not considered aliasing because
121 // the input and output slices both correspond to zero bits of memory.
122 @memcpy(slice, slice);
123 comptime assert(buf[0] == {});
124 comptime assert(buf[1] == {});
125 comptime assert(buf[2] == {});
126 }
127 };
128 S.doTheTest();
129 comptime S.doTheTest();
130}