diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-01-28 02:49:58 +0000 |
|---|---|---|
| committer | Matthew Lugg <mlugg@mlugg.co.uk> | 2025-01-29 06:35:22 +0000 |
| commit | 71d16106ad76bb31bc4e17dc37f8d8b5498a12dd (patch) | |
| tree | f8db4c1ed772e3188447afad05156dd6d1c664fc /src/Sema.zig | |
| parent | 97ccf3504f0b4787b86fa882312aba01ab980121 (diff) | |
| download | zig-71d16106ad76bb31bc4e17dc37f8d8b5498a12dd.tar.gz zig-71d16106ad76bb31bc4e17dc37f8d8b5498a12dd.zip | |
Sema: `@memcpy` changes
* The langspec definition of `@memcpy` has been changed so that the
source and destination element types must be in-memory coercible,
allowing all such calls to be raw copying operations, not actually
applying any coercions.
* Implement aliasing check for comptime `@memcpy`; a compile error will
now be emitted if the arguments alias.
* Implement more efficient comptime `@memcpy` by loading and storing a
whole array at once, similar to how `@memset` is implemented.
Diffstat (limited to 'src/Sema.zig')
| -rw-r--r-- | src/Sema.zig | 118 |
1 files changed, 66 insertions, 52 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index 72b05d078d..f38a82659c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -25793,7 +25793,6 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void const src_len = try indexablePtrLenOrNone(sema, block, src_src, src_ptr); const pt = sema.pt; const zcu = pt.zcu; - const target = zcu.getTarget(); if (dest_ty.isConstPtr(zcu)) { return sema.fail(block, dest_src, "cannot memcpy to constant pointer", .{}); @@ -25814,6 +25813,30 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void return sema.failWithOwnedErrorMsg(block, msg); } + const dest_elem_ty = dest_ty.indexablePtrElem(zcu); + const src_elem_ty = src_ty.indexablePtrElem(zcu); + + const imc = try sema.coerceInMemoryAllowed( + block, + dest_elem_ty, + src_elem_ty, + false, + zcu.getTarget(), + dest_src, + src_src, + null, + ); + if (imc != .ok) return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg( + src, + "pointer element type '{}' cannot coerce into element type '{}'", + .{ src_elem_ty.fmt(pt), dest_elem_ty.fmt(pt) }, + ); + errdefer msg.destroy(sema.gpa); + try imc.report(sema, src, msg); + break :msg msg; + }); + var len_val: ?Value = null; if (dest_len != .none and src_len != .none) check: { @@ -25855,61 +25878,52 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void } } - const runtime_src = if (try sema.resolveDefinedValue(block, dest_src, dest_ptr)) |dest_ptr_val| rs: { + 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; + + const raw_dest_ptr = if (dest_ty.isSlice(zcu)) dest_ptr_val.slicePtr(zcu) else dest_ptr_val; + const raw_src_ptr = if (src_ty.isSlice(zcu)) src_ptr_val.slicePtr(zcu) else src_ptr_val; + + const len_u64 = try len_val.?.toUnsignedIntSema(pt); + + if (Value.doPointersOverlap( + raw_src_ptr, + raw_dest_ptr, + len_u64, + zcu, + )) return sema.fail(block, src, "'@memcpy' arguments alias", .{}); + if (!sema.isComptimeMutablePtr(dest_ptr_val)) break :rs dest_src; - if (try sema.resolveDefinedValue(block, src_src, src_ptr)) |_| { - const len_u64 = try len_val.?.toUnsignedIntSema(pt); - const len = try sema.usizeCast(block, dest_src, len_u64); - for (0..len) |i| { - const elem_index = try pt.intRef(Type.usize, i); - const dest_elem_ptr = try sema.elemPtrOneLayerOnly( - block, - src, - dest_ptr, - elem_index, - src, - true, // init - false, // oob_safety - ); - const src_elem_ptr = try sema.elemPtrOneLayerOnly( - block, - src, - src_ptr, - elem_index, - src, - false, // init - false, // oob_safety - ); - const uncoerced_elem = try sema.analyzeLoad(block, src, src_elem_ptr, src_src); - try sema.storePtr2( - block, - src, - dest_elem_ptr, - dest_src, - uncoerced_elem, - src_src, - .store, - ); - } - return; - } else break :rs src_src; - } else dest_src; - // If in-memory coercion is not allowed, explode this memcpy call into a - // for loop that copies element-wise. - // Likewise if this is an iterable rather than a pointer, do the same - // lowering. The AIR instruction requires pointers with element types of - // equal ABI size. + // Because comptime pointer access is a somewhat expensive operation, we implement @memcpy + // as one load and store of an array, rather than N loads and stores of individual elements. - if (dest_ty.zigTypeTag(zcu) != .pointer or src_ty.zigTypeTag(zcu) != .pointer) { - return sema.fail(block, src, "TODO: lower @memcpy to a for loop because the source or destination iterable is a tuple", .{}); - } + const array_ty = try pt.arrayType(.{ + .child = dest_elem_ty.toIntern(), + .len = len_u64, + }); - const dest_elem_ty = dest_ty.elemType2(zcu); - const src_elem_ty = src_ty.elemType2(zcu); - if (.ok != try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, true, target, dest_src, src_src, null)) { - return sema.fail(block, src, "TODO: lower @memcpy to a for loop because the element types have different ABI sizes", .{}); - } + const dest_array_ptr_ty = try pt.ptrType(info: { + var info = dest_ty.ptrInfo(zcu); + info.flags.size = .one; + info.child = array_ty.toIntern(); + break :info info; + }); + const src_array_ptr_ty = try pt.ptrType(info: { + var info = src_ty.ptrInfo(zcu); + info.flags.size = .one; + info.child = array_ty.toIntern(); + break :info info; + }); + + const coerced_dest_ptr = try pt.getCoerced(raw_dest_ptr, dest_array_ptr_ty); + const coerced_src_ptr = try pt.getCoerced(raw_src_ptr, src_array_ptr_ty); + + const array_val = try sema.pointerDeref(block, src_src, coerced_src_ptr, src_array_ptr_ty) orelse break :rs src_src; + try sema.storePtrVal(block, dest_src, coerced_dest_ptr, array_val, array_ty); + return; + }; // If the length is comptime-known, then upgrade src and destination types // into pointer-to-array. At this point we know they are both pointers |
