Commit 898ca82458

dweiller <4678790+dweiller@users.noreply.github.com>
2025-01-22 14:13:23
compiler: add @memmove builtin
1 parent b9f4406
lib/std/debug/no_panic.zig
@@ -135,6 +135,11 @@ pub fn memcpyAlias() noreturn {
     @trap();
 }
 
+pub fn memmoveLenMismatch() noreturn {
+    @branchHint(.cold);
+    @trap();
+}
+
 pub fn noreturnReturned() noreturn {
     @branchHint(.cold);
     @trap();
lib/std/debug/simple_panic.zig
@@ -128,6 +128,10 @@ pub fn memcpyAlias() noreturn {
     call("@memcpy arguments alias", null);
 }
 
+pub fn memmoveLenMismatch() noreturn {
+    call("@memmove arguments have non-equal lengths", null);
+}
+
 pub fn noreturnReturned() noreturn {
     call("'noreturn' function returned", null);
 }
lib/std/zig/llvm/Builder.zig
@@ -6125,6 +6125,36 @@ pub const WipFunction = struct {
         return value.unwrap().instruction;
     }
 
+    pub fn callMemMove(
+        self: *WipFunction,
+        dst: Value,
+        dst_align: Alignment,
+        src: Value,
+        src_align: Alignment,
+        len: Value,
+        kind: MemoryAccessKind,
+    ) Allocator.Error!Instruction.Index {
+        var dst_attrs = [_]Attribute.Index{try self.builder.attr(.{ .@"align" = dst_align })};
+        var src_attrs = [_]Attribute.Index{try self.builder.attr(.{ .@"align" = src_align })};
+        const value = try self.callIntrinsic(
+            .normal,
+            try self.builder.fnAttrs(&.{
+                .none,
+                .none,
+                try self.builder.attrs(&dst_attrs),
+                try self.builder.attrs(&src_attrs),
+            }),
+            .memmove,
+            &.{ dst.typeOfWip(self), src.typeOfWip(self), len.typeOfWip(self) },
+            &.{ dst, src, len, switch (kind) {
+                .normal => Value.false,
+                .@"volatile" => Value.true,
+            } },
+            undefined,
+        );
+        return value.unwrap().instruction;
+    }
+
     pub fn callMemSet(
         self: *WipFunction,
         dst: Value,
lib/std/zig/AstGen.zig
@@ -2919,6 +2919,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
             .set_runtime_safety,
             .memcpy,
             .memset,
+            .memmove,
             .validate_deref,
             .validate_destructure,
             .save_err_ret_index,
@@ -9717,6 +9718,13 @@ fn builtinCall(
             });
             return rvalue(gz, ri, .void_value, node);
         },
+        .memmove => {
+            _ = try gz.addPlNode(.memmove, node, Zir.Inst.Bin{
+                .lhs = try expr(gz, scope, .{ .rl = .none }, params[0]),
+                .rhs = try expr(gz, scope, .{ .rl = .none }, params[1]),
+            });
+            return rvalue(gz, ri, .void_value, node);
+        },
         .shuffle => {
             const result = try gz.addPlNode(.shuffle, node, Zir.Inst.Shuffle{
                 .elem_type = try typeExpr(gz, scope, params[0]),
lib/std/zig/AstRlAnnotate.zig
@@ -1055,7 +1055,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
             _ = try astrl.expr(args[2], block, ResultInfo.none);
             return false;
         },
-        .memcpy => {
+        .memcpy, .memmove => {
             _ = try astrl.expr(args[0], block, ResultInfo.none);
             _ = try astrl.expr(args[1], block, ResultInfo.none);
             return false;
lib/std/zig/BuiltinFn.zig
@@ -68,6 +68,7 @@ pub const Tag = enum {
     max,
     memcpy,
     memset,
+    memmove,
     min,
     wasm_memory_size,
     wasm_memory_grow,
@@ -641,6 +642,13 @@ pub const list = list: {
                 .param_count = 2,
             },
         },
+        .{
+            "@memmove",
+            .{
+                .tag = .memmove,
+                .param_count = 2,
+            },
+        },
         .{
             "@min",
             .{
lib/std/zig/Zir.zig
@@ -986,6 +986,9 @@ pub const Inst = struct {
         /// Implements the `@memset` builtin.
         /// Uses the `pl_node` union field with payload `Bin`.
         memset,
+        /// Implements the `@memmove` builtin.
+        /// Uses the `pl_node` union field with payload `Bin`.
+        memmove,
         /// Implements the `@min` builtin for 2 args.
         /// Uses the `pl_node` union field with payload `Bin`
         min,
@@ -1272,6 +1275,7 @@ pub const Inst = struct {
                 .max,
                 .memcpy,
                 .memset,
+                .memmove,
                 .min,
                 .c_import,
                 .@"resume",
@@ -1355,6 +1359,7 @@ pub const Inst = struct {
                 .set_runtime_safety,
                 .memcpy,
                 .memset,
+                .memmove,
                 .check_comptime_control_flow,
                 .@"defer",
                 .defer_err_code,
@@ -1832,6 +1837,7 @@ pub const Inst = struct {
                 .max = .pl_node,
                 .memcpy = .pl_node,
                 .memset = .pl_node,
+                .memmove = .pl_node,
                 .min = .pl_node,
                 .c_import = .pl_node,
 
@@ -4291,6 +4297,7 @@ fn findTrackableInner(
         .mul_add,
         .memcpy,
         .memset,
+        .memmove,
         .min,
         .max,
         .alloc,
lib/std/debug.zig
@@ -134,6 +134,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
             @branchHint(.cold);
             call("@memcpy arguments alias", @returnAddress());
         }
+        pub fn memmoveLenMismatch() noreturn {
+            @branchHint(.cold);
+            call("@memmove arguments have non-equal lengths", @returnAddress());
+        }
         pub fn noreturnReturned() noreturn {
             @branchHint(.cold);
             call("'noreturn' function returned", @returnAddress());
lib/zig.h
@@ -481,6 +481,7 @@
 
 zig_extern void *memcpy (void *zig_restrict, void const *zig_restrict, size_t);
 zig_extern void *memset (void *, int, size_t);
+zig_extern void *memmove (void *, void const *, size_t);
 
 /* ================ Bool and 8/16/32/64-bit Integer Support ================= */
 
src/Air/types_resolved.zig
@@ -83,6 +83,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
             .memset,
             .memset_safe,
             .memcpy,
+            .memmove,
             .atomic_store_unordered,
             .atomic_store_monotonic,
             .atomic_store_release,
src/arch/aarch64/CodeGen.zig
@@ -760,6 +760,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .atomic_rmw      => try self.airAtomicRmw(inst),
             .atomic_load     => try self.airAtomicLoad(inst),
             .memcpy          => try self.airMemcpy(inst),
+            .memmove         => try self.airMemmove(inst),
             .memset          => try self.airMemset(inst, false),
             .memset_safe     => try self.airMemset(inst, true),
             .set_union_tag   => try self.airSetUnionTag(inst),
@@ -5993,6 +5994,11 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) InnerError!void {
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
+fn airMemmove(self: *Self, inst: Air.Inst.Index) InnerError!void {
+    _ = inst;
+    return self.fail("TODO implement airMemmove for {}", .{self.target.cpu.arch});
+}
+
 fn airTagName(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const operand = try self.resolveInst(un_op);
src/arch/arm/CodeGen.zig
@@ -749,6 +749,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .atomic_rmw      => try self.airAtomicRmw(inst),
             .atomic_load     => try self.airAtomicLoad(inst),
             .memcpy          => try self.airMemcpy(inst),
+            .memmove         => try self.airMemmove(inst),
             .memset          => try self.airMemset(inst, false),
             .memset_safe     => try self.airMemset(inst, true),
             .set_union_tag   => try self.airSetUnionTag(inst),
@@ -5963,6 +5964,11 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
+fn airMemmove(self: *Self, inst: Air.Inst.Index) !void {
+    _ = inst;
+    return self.fail("TODO implement airMemmove for {}", .{self.target.cpu.arch});
+}
+
 fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const operand = try self.resolveInst(un_op);
src/arch/riscv64/CodeGen.zig
@@ -1581,6 +1581,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
             .atomic_rmw      => try func.airAtomicRmw(inst),
             .atomic_load     => try func.airAtomicLoad(inst),
             .memcpy          => try func.airMemcpy(inst),
+            .memmove         => try func.airMemmove(inst),
             .memset          => try func.airMemset(inst, false),
             .memset_safe     => try func.airMemset(inst, true),
             .set_union_tag   => try func.airSetUnionTag(inst),
@@ -7919,6 +7920,11 @@ fn airMemcpy(func: *Func, inst: Air.Inst.Index) !void {
     return func.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
+fn airMemmove(func: *Func, inst: Air.Inst.Index) !void {
+    _ = inst;
+    return func.fail("TODO implement airMemmove for riscv64", .{});
+}
+
 fn airTagName(func: *Func, inst: Air.Inst.Index) !void {
     const pt = func.pt;
 
src/arch/sparc64/CodeGen.zig
@@ -604,6 +604,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .atomic_rmw      => try self.airAtomicRmw(inst),
             .atomic_load     => try self.airAtomicLoad(inst),
             .memcpy          => @panic("TODO try self.airMemcpy(inst)"),
+            .memmove         => @panic("TODO try self.airMemmove(inst)"),
             .memset          => try self.airMemset(inst, false),
             .memset_safe     => try self.airMemset(inst, true),
             .set_union_tag   => try self.airSetUnionTag(inst),
src/arch/wasm/CodeGen.zig
@@ -2061,6 +2061,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
         .c_va_copy,
         .c_va_end,
         .c_va_start,
+        .memmove,
         => |tag| return cg.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
 
         .atomic_load => cg.airAtomicLoad(inst),
src/arch/x86_64/CodeGen.zig
@@ -89453,6 +89453,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
             .memset => try cg.airMemset(inst, false),
             .memset_safe => try cg.airMemset(inst, true),
             .memcpy => try cg.airMemcpy(inst),
+            .memmove => try cg.airMemmove(inst),
             .cmpxchg_weak, .cmpxchg_strong => try cg.airCmpxchg(inst),
             .atomic_load => try cg.airAtomicLoad(inst),
             .atomic_store_unordered => try cg.airAtomicStore(inst, .unordered),
@@ -106472,6 +106473,11 @@ fn airMemcpy(self: *CodeGen, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
+fn airMemmove(self: *CodeGen, inst: Air.Inst.Index) !void {
+    _ = inst;
+    return self.fail("TODO implement airMemmove for {}", .{self.target.cpu.arch});
+}
+
 fn airTagName(self: *CodeGen, inst: Air.Inst.Index, only_safety: bool) !void {
     const pt = self.pt;
     const zcu = pt.zcu;
src/codegen/c.zig
@@ -3349,6 +3349,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
             .memset           => try airMemset(f, inst, false),
             .memset_safe      => try airMemset(f, inst, true),
             .memcpy           => try airMemcpy(f, inst),
+            .memmove          => try airMemmove(f, inst),
             .set_union_tag    => try airSetUnionTag(f, inst),
             .get_union_tag    => try airGetUnionTag(f, inst),
             .clz              => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "clz", .bits),
@@ -6976,6 +6977,14 @@ fn airMemset(f: *Function, inst: Air.Inst.Index, safety: bool) !CValue {
 }
 
 fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue {
+    return copyOp(f, inst, .memcpy);
+}
+
+fn airMemmove(f: *Function, inst: Air.Inst.Index) !CValue {
+    return copyOp(f, inst, .memmove);
+}
+
+fn copyOp(f: *Function, inst: Air.Inst.Index, op: enum { memcpy, memmove }) !CValue {
     const pt = f.object.dg.pt;
     const zcu = pt.zcu;
     const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
@@ -6990,7 +6999,11 @@ fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue {
         try writeArrayLen(f, writer, dest_ptr, dest_ty);
         try writer.writeAll(" != 0) ");
     }
-    try writer.writeAll("memcpy(");
+    const function_paren = switch (op) {
+        .memcpy => "memcpy(",
+        .memmove => "memmove(",
+    };
+    try writer.writeAll(function_paren);
     try writeSliceOrPtr(f, writer, dest_ptr, dest_ty);
     try writer.writeAll(", ");
     try writeSliceOrPtr(f, writer, src_ptr, src_ty);
src/codegen/llvm.zig
@@ -4941,6 +4941,7 @@ pub const FuncGen = struct {
                 .memset         => try self.airMemset(inst, false),
                 .memset_safe    => try self.airMemset(inst, true),
                 .memcpy         => try self.airMemcpy(inst),
+                .memmove        => try self.airMemmove(inst),
                 .set_union_tag  => try self.airSetUnionTag(inst),
                 .get_union_tag  => try self.airGetUnionTag(inst),
                 .clz            => try self.airClzCtz(inst, .ctlz),
@@ -9927,6 +9928,32 @@ pub const FuncGen = struct {
         return .none;
     }
 
+    fn airMemmove(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+        const o = self.ng.object;
+        const pt = o.pt;
+        const zcu = pt.zcu;
+        const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
+        const dest_slice = try self.resolveInst(bin_op.lhs);
+        const dest_ptr_ty = self.typeOf(bin_op.lhs);
+        const src_slice = try self.resolveInst(bin_op.rhs);
+        const src_ptr_ty = self.typeOf(bin_op.rhs);
+        const src_ptr = try self.sliceOrArrayPtr(src_slice, src_ptr_ty);
+        const len = try self.sliceOrArrayLenInBytes(dest_slice, dest_ptr_ty);
+        const dest_ptr = try self.sliceOrArrayPtr(dest_slice, dest_ptr_ty);
+        const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or
+            dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
+
+        _ = try self.wip.callMemMove(
+            dest_ptr,
+            dest_ptr_ty.ptrAlignment(zcu).toLlvm(),
+            src_ptr,
+            src_ptr_ty.ptrAlignment(zcu).toLlvm(),
+            len,
+            access_kind,
+        );
+        return .none;
+    }
+
     fn airSetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
         const o = self.ng.object;
         const pt = o.pt;
src/codegen/spirv.zig
@@ -3344,6 +3344,7 @@ const NavGen = struct {
             .slice          => try self.airSlice(inst),
             .aggregate_init => try self.airAggregateInit(inst),
             .memcpy         => return self.airMemcpy(inst),
+            .memmove        => return self.airMemmove(inst),
 
             .slice_ptr      => try self.airSliceField(inst, 0),
             .slice_len      => try self.airSliceField(inst, 1),
@@ -4914,6 +4915,11 @@ const NavGen = struct {
         });
     }
 
+    fn airMemmove(self: *NavGen, inst: Air.Inst.Index) !void {
+        _ = inst;
+        return self.fail("TODO implement airMemcpy for spirv", .{});
+    }
+
     fn airSliceField(self: *NavGen, inst: Air.Inst.Index, field: u32) !?IdRef {
         const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
         const field_ty = self.typeOfIndex(inst);
src/Liveness/Verify.zig
@@ -267,6 +267,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
             .memset,
             .memset_safe,
             .memcpy,
+            .memmove,
             => {
                 const bin_op = data[@intFromEnum(inst)].bin_op;
                 try self.verifyInstOperands(inst, .{ bin_op.lhs, bin_op.rhs, .none });
src/Air.zig
@@ -730,6 +730,18 @@ pub const Inst = struct {
         /// source being a pointer-to-array), then it is guaranteed to be
         /// greater than zero.
         memcpy,
+        /// Given dest pointer and source pointer, copy elements from source to dest.
+        /// Dest pointer is either a slice or a pointer to array.
+        /// The dest element type may be any type.
+        /// Source pointer must have same element type as dest element type.
+        /// Dest slice may have any alignment; source pointer may have any alignment.
+        /// The two memory regions may overlap.
+        /// Result type is always void.
+        /// Uses the `bin_op` field. LHS is the dest slice. RHS is the source pointer.
+        /// If the length is compile-time known (due to the destination or
+        /// source being a pointer-to-array), then it is guaranteed to be
+        /// greater than zero.
+        memmove,
 
         /// Uses the `ty_pl` field with payload `Cmpxchg`.
         cmpxchg_weak,
@@ -1533,6 +1545,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
         .memset,
         .memset_safe,
         .memcpy,
+        .memmove,
         .set_union_tag,
         .prefetch,
         .set_err_return_trace,
@@ -1696,6 +1709,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
         .memset,
         .memset_safe,
         .memcpy,
+        .memmove,
         .cmpxchg_weak,
         .cmpxchg_strong,
         .atomic_store_unordered,
src/Liveness.zig
@@ -300,6 +300,7 @@ pub fn categorizeOperand(
         .memset,
         .memset_safe,
         .memcpy,
+        .memmove,
         => {
             const o = air_datas[@intFromEnum(inst)].bin_op;
             if (o.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .write);
@@ -936,6 +937,7 @@ fn analyzeInst(
         .memset,
         .memset_safe,
         .memcpy,
+        .memmove,
         => {
             const o = inst_datas[@intFromEnum(inst)].bin_op;
             return analyzeOperands(a, pass, data, inst, .{ o.lhs, o.rhs, .none });
src/print_air.zig
@@ -160,6 +160,7 @@ const Writer = struct {
             .cmp_gt_optimized,
             .cmp_neq_optimized,
             .memcpy,
+            .memmove,
             .memset,
             .memset_safe,
             => try w.writeBinOp(s, inst),
src/print_zir.zig
@@ -413,6 +413,7 @@ const Writer = struct {
             .min,
             .memcpy,
             .memset,
+            .memmove,
             .elem_ptr_node,
             .elem_val_node,
             .elem_ptr,
src/Sema.zig
@@ -1583,6 +1583,11 @@ fn analyzeBodyInner(
                 i += 1;
                 continue;
             },
+            .memmove => {
+                try sema.zirMemmove(block, inst);
+                i += 1;
+                continue;
+            },
             .check_comptime_control_flow => {
                 if (!block.isComptime()) {
                     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
@@ -25610,6 +25615,19 @@ fn upgradeToArrayPtr(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, len: u64) !A
 }
 
 fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
+    return sema.analyzeCopy(block, inst, .memcpy);
+}
+
+fn zirMemmove(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
+    return sema.analyzeCopy(block, inst, .memmove);
+}
+
+fn analyzeCopy(
+    sema: *Sema,
+    block: *Block,
+    inst: Zir.Inst.Index,
+    op: enum { memcpy, memmove },
+) CompileError!void {
     const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
     const src = block.nodeOffset(inst_data.src_node);
@@ -25625,12 +25643,12 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
     const zcu = pt.zcu;
 
     if (dest_ty.isConstPtr(zcu)) {
-        return sema.fail(block, dest_src, "cannot memcpy to constant pointer", .{});
+        return sema.fail(block, dest_src, "cannot {s} to constant pointer", .{@tagName(op)});
     }
 
     if (dest_len == .none and src_len == .none) {
         const msg = msg: {
-            const msg = try sema.errMsg(src, "unknown @memcpy length", .{});
+            const msg = try sema.errMsg(src, "unknown @{s} length", .{@tagName(op)});
             errdefer msg.destroy(sema.gpa);
             try sema.errNote(dest_src, msg, "destination type '{}' provides no length", .{
                 dest_ty.fmt(pt),
@@ -25676,7 +25694,7 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
             if (try sema.resolveDefinedValue(block, src_src, src_len)) |src_len_val| {
                 if (!(try sema.valuesEqual(dest_len_val, src_len_val, Type.usize))) {
                     const msg = msg: {
-                        const msg = try sema.errMsg(src, "non-matching @memcpy lengths", .{});
+                        const msg = try sema.errMsg(src, "non-matching @{s} lengths", .{@tagName(op)});
                         errdefer msg.destroy(sema.gpa);
                         try sema.errNote(dest_src, msg, "length {} here", .{
                             dest_len_val.fmtValueSema(pt, sema),
@@ -25696,7 +25714,11 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
 
         if (block.wantSafety()) {
             const ok = try block.addBinOp(.cmp_eq, dest_len, src_len);
-            try sema.addSafetyCheck(block, src, ok, .memcpy_len_mismatch);
+            const panic_id: Zcu.SimplePanicId = switch (op) {
+                .memcpy => .memcpy_len_mismatch,
+                .memmove => .memmove_len_mismatch,
+            };
+            try sema.addSafetyCheck(block, src, ok, panic_id);
         }
     } else if (dest_len != .none) {
         if (try sema.resolveDefinedValue(block, dest_src, dest_len)) |dest_len_val| {
@@ -25724,6 +25746,11 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
         return;
     }
 
+    const check_aliasing = switch (op) {
+        .memcpy => true,
+        .memmove => false,
+    };
+
     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;
@@ -25733,12 +25760,14 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
 
         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 (check_aliasing) {
+            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;
 
@@ -25810,7 +25839,7 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
     try sema.validateRuntimeValue(block, src_src, src_ptr);
 
     // Aliasing safety check.
-    if (block.wantSafety()) {
+    if (check_aliasing and block.wantSafety()) {
         const len = if (len_val) |v|
             Air.internedToRef(v.toIntern())
         else if (dest_len != .none)
@@ -25853,7 +25882,10 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
     }
 
     _ = try block.addInst(.{
-        .tag = .memcpy,
+        .tag = switch (op) {
+            .memcpy => .memcpy,
+            .memmove => .memmove,
+        },
         .data = .{ .bin_op = .{
             .lhs = new_dest_ptr,
             .rhs = new_src_ptr,
@@ -38078,6 +38110,7 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.BuiltinDecl) CompileError!Typ
         .@"panic.forLenMismatch",
         .@"panic.memcpyLenMismatch",
         .@"panic.memcpyAlias",
+        .@"panic.memmoveLenMismatch",
         .@"panic.noreturnReturned",
         => try pt.funcType(.{
             .param_types = &.{},
src/Zcu.zig
@@ -302,6 +302,7 @@ pub const BuiltinDecl = enum {
     @"panic.forLenMismatch",
     @"panic.memcpyLenMismatch",
     @"panic.memcpyAlias",
+    @"panic.memmoveLenMismatch",
     @"panic.noreturnReturned",
 
     VaList,
@@ -379,6 +380,7 @@ pub const BuiltinDecl = enum {
             .@"panic.forLenMismatch",
             .@"panic.memcpyLenMismatch",
             .@"panic.memcpyAlias",
+            .@"panic.memmoveLenMismatch",
             .@"panic.noreturnReturned",
             => .func,
         };
@@ -446,6 +448,7 @@ pub const SimplePanicId = enum {
     for_len_mismatch,
     memcpy_len_mismatch,
     memcpy_alias,
+    memmove_len_mismatch,
     noreturn_returned,
 
     pub fn toBuiltin(id: SimplePanicId) BuiltinDecl {
@@ -470,6 +473,7 @@ pub const SimplePanicId = enum {
             .for_len_mismatch           => .@"panic.forLenMismatch",
             .memcpy_len_mismatch        => .@"panic.memcpyLenMismatch",
             .memcpy_alias               => .@"panic.memcpyAlias",
+            .memmove_len_mismatch       => .@"panic.memmoveLenMismatch",
             .noreturn_returned          => .@"panic.noreturnReturned",
             // zig fmt: on
         };
test/cases/compile_errors/bad_panic_call_signature.zig
@@ -29,6 +29,7 @@ pub const panic = struct {
     pub const forLenMismatch = simple_panic.forLenMismatch;
     pub const memcpyLenMismatch = simple_panic.memcpyLenMismatch;
     pub const memcpyAlias = simple_panic.memcpyAlias;
+    pub const memmoveLenMismatch = simple_panic.memmoveLenMismatch;
     pub const noreturnReturned = simple_panic.noreturnReturned;
 };
 
test/cases/compile_errors/bad_panic_generic_signature.zig
@@ -25,6 +25,7 @@ pub const panic = struct {
     pub const forLenMismatch = simple_panic.forLenMismatch;
     pub const memcpyLenMismatch = simple_panic.memcpyLenMismatch;
     pub const memcpyAlias = simple_panic.memcpyAlias;
+    pub const memmoveLenMismatch = simple_panic.memmoveLenMismatch;
     pub const noreturnReturned = simple_panic.noreturnReturned;
 };
 
test/incremental/change_panic_handler_explicit
@@ -39,6 +39,7 @@ pub const panic = struct {
     pub const forLenMismatch = no_panic.forLenMismatch;
     pub const memcpyLenMismatch = no_panic.memcpyLenMismatch;
     pub const memcpyAlias = no_panic.memcpyAlias;
+    pub const memmoveLenMismatch = no_panic.memmoveLenMismatch;
     pub const noreturnReturned = no_panic.noreturnReturned;
 };
 fn myPanic(msg: []const u8, _: ?usize) noreturn {
@@ -86,6 +87,7 @@ pub const panic = struct {
     pub const forLenMismatch = no_panic.forLenMismatch;
     pub const memcpyLenMismatch = no_panic.memcpyLenMismatch;
     pub const memcpyAlias = no_panic.memcpyAlias;
+    pub const memmoveLenMismatch = no_panic.memmoveLenMismatch;
     pub const noreturnReturned = no_panic.noreturnReturned;
 };
 fn myPanic(msg: []const u8, _: ?usize) noreturn {
@@ -133,6 +135,7 @@ pub const panic = struct {
     pub const forLenMismatch = no_panic.forLenMismatch;
     pub const memcpyLenMismatch = no_panic.memcpyLenMismatch;
     pub const memcpyAlias = no_panic.memcpyAlias;
+    pub const memmoveLenMismatch = no_panic.memmoveLenMismatch;
     pub const noreturnReturned = no_panic.noreturnReturned;
 };
 fn myPanicNew(msg: []const u8, _: ?usize) noreturn {