Merge pull request #22708 from mlugg/memcpy-alias-zero-bit

Sema: skip aliasing check and runtime operation for `@memcpy` of zero-bit type
This commit is contained in:
Matthew Lugg 2025-02-01 15:47:06 +00:00 committed by GitHub
commit c225b780e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 58 additions and 2 deletions

View File

@ -24581,8 +24581,12 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
const len = try sema.usizeCast(block, src, dest_ty.arrayLen(zcu));
// `len == 0` because `[0:s]T` always has a comptime-known splat.
if (!dest_ty.hasRuntimeBits(zcu) or len == 0) {
if (try sema.typeHasOnePossibleValue(dest_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
// We also need this case because `[0:s]T` is not OPV.
if (len == 0) {
const empty_aggregate = try pt.intern(.{ .aggregate = .{
.ty = dest_ty.toIntern(),
.storage = .{ .elems = &.{} },
@ -25924,6 +25928,22 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
}
}
zero_bit: {
const src_comptime = try src_elem_ty.comptimeOnlySema(pt);
const dest_comptime = try dest_elem_ty.comptimeOnlySema(pt);
assert(src_comptime == dest_comptime); // IMC
if (src_comptime) break :zero_bit;
const src_has_bits = try src_elem_ty.hasRuntimeBitsIgnoreComptimeSema(pt);
const dest_has_bits = try dest_elem_ty.hasRuntimeBitsIgnoreComptimeSema(pt);
assert(src_has_bits == dest_has_bits); // IMC
if (src_has_bits) break :zero_bit;
// The element type is zero-bit. We've done all validation (aside from the aliasing check,
// which we must skip) so we're done.
return;
}
const runtime_src = rs: {
const dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr) orelse break :rs dest_src;
const src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr) orelse break :rs src_src;

View File

@ -452,6 +452,13 @@ pub fn hasRuntimeBitsIgnoreComptime(ty: Type, zcu: *const Zcu) bool {
return hasRuntimeBitsInner(ty, true, .eager, zcu, {}) catch unreachable;
}
pub fn hasRuntimeBitsIgnoreComptimeSema(ty: Type, pt: Zcu.PerThread) SemaError!bool {
return hasRuntimeBitsInner(ty, true, .sema, pt.zcu, pt.tid) catch |err| switch (err) {
error.NeedLazy => unreachable, // this would require a resolve strat of lazy
else => |e| return e,
};
}
/// true if and only if the type takes up space in memory at runtime.
/// There are two reasons a type will return false:
/// * the type is a comptime-only type. For example, the type `type` itself.

View File

@ -1,6 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
const assert = std.debug.assert;
test "memcpy and memset intrinsics" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
@ -99,3 +100,31 @@ comptime {
s.set("hello");
if (!std.mem.eql(u8, s.buffer[0..5], "hello")) @compileError("bad");
}
test "@memcpy comptime-only type" {
const in: [4]type = .{ u8, u16, u32, u64 };
comptime var out: [4]type = undefined;
@memcpy(&out, &in);
comptime assert(out[0] == u8);
comptime assert(out[1] == u16);
comptime assert(out[2] == u32);
comptime assert(out[3] == u64);
}
test "@memcpy zero-bit type with aliasing" {
const S = struct {
fn doTheTest() void {
var buf: [3]void = @splat({});
const slice: []void = &buf;
// These two pointers are the same, but it's still not considered aliasing because
// the input and output slices both correspond to zero bits of memory.
@memcpy(slice, slice);
comptime assert(buf[0] == {});
comptime assert(buf[1] == {});
comptime assert(buf[2] == {});
}
};
S.doTheTest();
comptime S.doTheTest();
}