diff --git a/doc/langref.html.in b/doc/langref.html.in index 102bc8aeb9..d486a12061 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5149,6 +5149,23 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#syntax#}std.crypto.secureZero{#endsyntax#}

{#header_close#} + {#header_open|@memmove#} +
{#syntax#}@memmove(dest, source) void{#endsyntax#}
+

This function copies bytes from one region of memory to another, but unlike + {#link|@memcpy#} the regions may overlap.

+

{#syntax#}dest{#endsyntax#} must be a mutable slice, a mutable pointer to an array, or + a mutable many-item {#link|pointer|Pointers#}. It may have any + alignment, and it may have any element type.

+

{#syntax#}source{#endsyntax#} must be a slice, a pointer to + an array, or a many-item {#link|pointer|Pointers#}. It may + have any alignment, and it may have any element type.

+

The {#syntax#}source{#endsyntax#} element type must have the same in-memory + representation as the {#syntax#}dest{#endsyntax#} element type.

+

Similar to {#link|for#} loops, at least one of {#syntax#}source{#endsyntax#} and + {#syntax#}dest{#endsyntax#} must provide a length, and if two lengths are provided, + they must be equal.

+ {#header_close#} + {#header_open|@min#}
{#syntax#}@min(...) T{#endsyntax#}

diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 664999caa0..722bcc74a9 100644 --- a/lib/std/debug.zig +++ b/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()); diff --git a/lib/std/debug/no_panic.zig b/lib/std/debug/no_panic.zig index 934751b937..ccecc89a87 100644 --- a/lib/std/debug/no_panic.zig +++ b/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(); diff --git a/lib/std/debug/simple_panic.zig b/lib/std/debug/simple_panic.zig index 21016f395a..724061021f 100644 --- a/lib/std/debug/simple_panic.zig +++ b/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); } diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 4c198408b5..a6c4fe90be 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -232,6 +232,7 @@ test "Allocator alloc and remap with zero-bit type" { /// Copy all of source into dest at position 0. /// dest.len must be >= source.len. /// If the slices overlap, dest.ptr must be <= src.ptr. +/// This function is deprecated; use @memmove instead. pub fn copyForwards(comptime T: type, dest: []T, source: []const T) void { for (dest[0..source.len], source) |*d, s| d.* = s; } @@ -239,6 +240,7 @@ pub fn copyForwards(comptime T: type, dest: []T, source: []const T) void { /// Copy all of source into dest at position 0. /// dest.len must be >= source.len. /// If the slices overlap, dest.ptr must be >= src.ptr. +/// This function is deprecated; use @memmove instead. pub fn copyBackwards(comptime T: type, dest: []T, source: []const T) void { // TODO instead of manually doing this check for the whole array // and turning off runtime safety, the compiler should detect loops like diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 74fbd03adb..55bc743951 100644 --- a/lib/std/zig/AstGen.zig +++ b/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]), diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index d5fb0a8169..628574349b 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/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; diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index 1bf31cd165..818362a371 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/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", .{ diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 089bc5e2ae..d40322f51f 100644 --- a/lib/std/zig/Zir.zig +++ b/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, diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index a713830161..d8d5ff19c7 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/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, diff --git a/lib/zig.h b/lib/zig.h index 2d9e7a5626..e02aed176c 100644 --- a/lib/zig.h +++ b/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 ================= */ diff --git a/src/Air.zig b/src/Air.zig index 7afe2bfbb5..3b1dfc644a 100644 --- a/src/Air.zig +++ b/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, diff --git a/src/Air/types_resolved.zig b/src/Air/types_resolved.zig index 2144ce2316..bb3cc4c19f 100644 --- a/src/Air/types_resolved.zig +++ b/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, diff --git a/src/Liveness.zig b/src/Liveness.zig index 199e81b86b..87b63d93d5 100644 --- a/src/Liveness.zig +++ b/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 }); diff --git a/src/Liveness/Verify.zig b/src/Liveness/Verify.zig index a8cb81c51b..449dbb63fb 100644 --- a/src/Liveness/Verify.zig +++ b/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 }); diff --git a/src/Sema.zig b/src/Sema.zig index 7d7d275e9b..38b6a7f3bc 100644 --- a/src/Sema.zig +++ b/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 = &.{}, diff --git a/src/Zcu.zig b/src/Zcu.zig index f9fbe0b652..6ff8c98cf8 100644 --- a/src/Zcu.zig +++ b/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 }; diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index f7ee7eb064..1ae73602bb 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/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); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 8a1600f50a..c5c3776b80 100644 --- a/src/arch/arm/CodeGen.zig +++ b/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); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index c66de9dd30..77efade41f 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/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; diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 58cd78a7d2..5b9d1ffdf4 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/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), diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 597e1ec75b..f0e852da40 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/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), diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index ceffcb3c7a..3ba38ccd7b 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/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; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 2aee078b11..e7883b9def 100644 --- a/src/codegen/c.zig +++ b/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); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 19cf05222e..a16f681f86 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4940,6 +4940,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), @@ -9926,6 +9927,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; diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index c361090b51..f84976b2e7 100644 --- a/src/codegen/spirv.zig +++ b/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); diff --git a/src/print_air.zig b/src/print_air.zig index 73e075a0b2..3a5963f7c4 100644 --- a/src/print_air.zig +++ b/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), diff --git a/src/print_zir.zig b/src/print_zir.zig index 08e1a30368..aba093c922 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -413,6 +413,7 @@ const Writer = struct { .min, .memcpy, .memset, + .memmove, .elem_ptr_node, .elem_val_node, .elem_ptr, diff --git a/test/behavior.zig b/test/behavior.zig index aba1c663eb..5ed256dfad 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -55,6 +55,7 @@ test { _ = @import("behavior/member_func.zig"); _ = @import("behavior/memcpy.zig"); _ = @import("behavior/memset.zig"); + _ = @import("behavior/memmove.zig"); _ = @import("behavior/merge_error_sets.zig"); _ = @import("behavior/muladd.zig"); _ = @import("behavior/multiple_externs_with_conflicting_types.zig"); diff --git a/test/behavior/builtin_functions_returning_void_or_noreturn.zig b/test/behavior/builtin_functions_returning_void_or_noreturn.zig index e20e1ed9bf..8a6cb13cca 100644 --- a/test/behavior/builtin_functions_returning_void_or_noreturn.zig +++ b/test/behavior/builtin_functions_returning_void_or_noreturn.zig @@ -6,6 +6,7 @@ var x: u8 = 1; // This excludes builtin functions that return void or noreturn that cannot be tested. test { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -17,6 +18,7 @@ test { try testing.expectEqual(void, @TypeOf(@breakpoint())); try testing.expectEqual({}, @export(&x, .{ .name = "x" })); try testing.expectEqual({}, @memcpy(@as([*]u8, @ptrFromInt(1))[0..0], @as([*]u8, @ptrFromInt(1))[0..0])); + try testing.expectEqual({}, @memmove(@as([*]u8, @ptrFromInt(1))[0..0], @as([*]u8, @ptrFromInt(1))[0..0])); try testing.expectEqual({}, @memset(@as([*]u8, @ptrFromInt(1))[0..0], undefined)); try testing.expectEqual(noreturn, @TypeOf(if (true) @panic("") else {})); try testing.expectEqual({}, @prefetch(&val, .{})); diff --git a/test/behavior/memmove.zig b/test/behavior/memmove.zig new file mode 100644 index 0000000000..a29535ec9a --- /dev/null +++ b/test/behavior/memmove.zig @@ -0,0 +1,183 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const expect = std.testing.expect; + +test "memmove and memset intrinsics" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + + try testMemmoveMemset(); + try comptime testMemmoveMemset(); +} + +fn testMemmoveMemset() !void { + var foo: [20]u8 = undefined; + + @memset(foo[0..10], 'A'); + @memset(foo[10..20], 'B'); + + try expect(foo[0] == 'A'); + try expect(foo[11] == 'B'); + try expect(foo[19] == 'B'); + + @memmove(foo[10..20], foo[0..10]); + + try expect(foo[0] == 'A'); + try expect(foo[11] == 'A'); + try expect(foo[19] == 'A'); +} + +test "@memmove with both operands single-ptr-to-array, one is null-terminated" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + + try testMemmoveBothSinglePtrArrayOneIsNullTerminated(); + try comptime testMemmoveBothSinglePtrArrayOneIsNullTerminated(); +} + +fn testMemmoveBothSinglePtrArrayOneIsNullTerminated() !void { + var buf: [100]u8 = undefined; + const suffix = "hello"; + @memmove(buf[buf.len - suffix.len ..], suffix); + try expect(buf[95] == 'h'); + try expect(buf[96] == 'e'); + try expect(buf[97] == 'l'); + try expect(buf[98] == 'l'); + try expect(buf[99] == 'o'); + + const start = buf.len - suffix.len - 3; + const end = start + suffix.len; + @memmove(buf[start..end], buf[buf.len - suffix.len ..]); + try expect(buf[92] == 'h'); + try expect(buf[93] == 'e'); + try expect(buf[94] == 'l'); + try expect(buf[95] == 'l'); + try expect(buf[96] == 'o'); + try expect(buf[97] == 'l'); + try expect(buf[98] == 'l'); + try expect(buf[99] == 'o'); + + @memmove(buf[start + 2 .. end + 2], buf[start..end]); + try expect(buf[92] == 'h'); + try expect(buf[93] == 'e'); + try expect(buf[94] == 'h'); + try expect(buf[95] == 'e'); + try expect(buf[96] == 'l'); + try expect(buf[97] == 'l'); + try expect(buf[98] == 'o'); + try expect(buf[99] == 'o'); +} + +test "@memmove dest many pointer" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + + try testMemmoveDestManyPtr(); + try comptime testMemmoveDestManyPtr(); +} + +fn testMemmoveDestManyPtr() !void { + var str = "hello".*; + var buf: [8]u8 = undefined; + var len: usize = 5; + _ = &len; + @memmove(@as([*]u8, @ptrCast(&buf)), @as([*]const u8, @ptrCast(&str))[0..len]); + try expect(buf[0] == 'h'); + try expect(buf[1] == 'e'); + try expect(buf[2] == 'l'); + try expect(buf[3] == 'l'); + try expect(buf[4] == 'o'); + @memmove(buf[3..].ptr, buf[0..len]); + try expect(buf[0] == 'h'); + try expect(buf[1] == 'e'); + try expect(buf[2] == 'l'); + try expect(buf[3] == 'h'); + try expect(buf[4] == 'e'); + try expect(buf[5] == 'l'); + try expect(buf[6] == 'l'); + try expect(buf[7] == 'o'); + @memmove(buf[2..7].ptr, buf[3 .. len + 3]); + try expect(buf[0] == 'h'); + try expect(buf[1] == 'e'); + try expect(buf[2] == 'h'); + try expect(buf[3] == 'e'); + try expect(buf[4] == 'l'); + try expect(buf[5] == 'l'); + try expect(buf[6] == 'o'); + try expect(buf[7] == 'o'); +} + +test "@memmove slice" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + + try testMemmoveSlice(); + try comptime testMemmoveSlice(); +} + +fn testMemmoveSlice() !void { + var buf: [8]u8 = undefined; + const dst1: []u8 = buf[0..5]; + const dst2: []u8 = buf[3..8]; + const dst3: []u8 = buf[2..7]; + const src: []const u8 = "hello"; + @memmove(dst1, src); + try expect(buf[0] == 'h'); + try expect(buf[1] == 'e'); + try expect(buf[2] == 'l'); + try expect(buf[3] == 'l'); + try expect(buf[4] == 'o'); + @memmove(dst2, dst1); + try expect(buf[0] == 'h'); + try expect(buf[1] == 'e'); + try expect(buf[2] == 'l'); + try expect(buf[3] == 'h'); + try expect(buf[4] == 'e'); + try expect(buf[5] == 'l'); + try expect(buf[6] == 'l'); + try expect(buf[7] == 'o'); + @memmove(dst3, dst2); + try expect(buf[0] == 'h'); + try expect(buf[1] == 'e'); + try expect(buf[2] == 'h'); + try expect(buf[3] == 'e'); + try expect(buf[4] == 'l'); + try expect(buf[5] == 'l'); + try expect(buf[6] == 'o'); + try expect(buf[7] == 'o'); +} + +comptime { + const S = struct { + buffer: [8]u8 = undefined, + fn set(self: *@This(), items: []const u8) void { + @memmove(self.buffer[0..items.len], items); + @memmove(self.buffer[3..], self.buffer[0..items.len]); + @memmove(self.buffer[2 .. 2 + items.len], self.buffer[3..]); + } + }; + + var s = S{}; + s.set("hello"); + if (!std.mem.eql(u8, s.buffer[0..8], "hehelloo")) @compileError("bad"); +} diff --git a/test/cases/compile_errors/@memmove_type_mismatch.zig b/test/cases/compile_errors/@memmove_type_mismatch.zig new file mode 100644 index 0000000000..698bd9115f --- /dev/null +++ b/test/cases/compile_errors/@memmove_type_mismatch.zig @@ -0,0 +1,218 @@ +export fn foo() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []u8 = &buf; + const src: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + + @memmove(dest, src); +} + +export fn bar() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []u8 = &buf; + const src: *align(1) [8]u16 = @ptrCast(&buf); + + @memmove(dest, src); +} + +export fn baz() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []u8 = &buf; + const src: [*]align(1) u16 = @ptrCast(&buf); + + @memmove(dest, src); +} + +export fn qux() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *[8]u8 = &buf; + const src: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + + @memmove(dest, src); +} + +export fn quux() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *[8]u8 = &buf; + const src: *align(1) [8]u16 = @ptrCast(&buf); + + @memmove(dest, src); +} + +export fn quuux() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *[8]u8 = &buf; + const src: [*]align(1) u16 = @ptrCast(&buf); + + @memmove(dest, src); +} + +export fn foo2() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + const src: []u8 = &buf; + + @memmove(dest, src); +} + +export fn bar2() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *align(1) [8]u16 = @ptrCast(&buf); + const src: []u8 = &buf; + + @memmove(dest, src); +} + +export fn baz2() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: [*]align(1) u16 = @ptrCast(&buf); + const src: []u8 = &buf; + + @memmove(dest, src); +} + +export fn qux2() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + const src: *[8]u8 = &buf; + + @memmove(dest, src); +} + +export fn quux2() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *align(1) [8]u16 = @ptrCast(&buf); + const src: *[8]u8 = &buf; + + @memmove(dest, src); +} + +export fn quuux2() void { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: [*]align(1) u16 = @ptrCast(&buf); + const src: *[8]u8 = &buf; + + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []u8 = &buf; + const src: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []u8 = &buf; + const src: *align(1) [8]u16 = @ptrCast(&buf); + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []u8 = &buf; + const src: [*]align(1) u16 = @ptrCast(&buf); + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *[8]u8 = &buf; + const src: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *[8]u8 = &buf; + const src: *align(1) [8]u16 = @ptrCast(&buf); + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *[8]u8 = &buf; + const src: [*]align(1) u16 = @ptrCast(&buf); + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + const src: []u8 = &buf; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *align(1) [8]u16 = @ptrCast(&buf); + const src: []u8 = &buf; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: [*]align(1) u16 = @ptrCast(&buf); + const src: []u8 = &buf; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: []align(1) u16 = @as([*]align(1) u16, @ptrCast(&buf))[0..4]; + const src: *[8]u8 = &buf; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: *align(1) [8]u16 = @ptrCast(&buf); + const src: *[8]u8 = &buf; + @memmove(dest, src); +} + +comptime { + var buf: [8]u8 = .{ 0, 1, 2, 3, 4, 5, 6, 7 }; + const dest: [*]align(1) u16 = @ptrCast(&buf); + const src: *[8]u8 = &buf; + @memmove(dest, src); +} + +// error +// +// :6:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :6:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :14:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :14:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :22:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :22:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :30:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :30:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :38:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :38:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :46:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :46:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :54:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :62:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :70:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :78:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :86:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :94:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :101:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :101:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :108:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :108:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :115:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :115:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :122:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :122:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :129:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :129:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :136:5: error: pointer element type 'u16' cannot coerce into element type 'u8' +// :136:5: note: unsigned 8-bit int cannot represent all possible unsigned 16-bit values +// :143:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :150:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :157:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :164:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :171:5: error: pointer element type 'u8' cannot coerce into element type 'u16' +// :178:5: error: pointer element type 'u8' cannot coerce into element type 'u16' diff --git a/test/cases/compile_errors/bad_panic_call_signature.zig b/test/cases/compile_errors/bad_panic_call_signature.zig index b383ccf651..2165f178b5 100644 --- a/test/cases/compile_errors/bad_panic_call_signature.zig +++ b/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; }; diff --git a/test/cases/compile_errors/bad_panic_generic_signature.zig b/test/cases/compile_errors/bad_panic_generic_signature.zig index 92fa49c7f3..413a6b79fd 100644 --- a/test/cases/compile_errors/bad_panic_generic_signature.zig +++ b/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; }; diff --git a/test/cases/compile_errors/comptime_var_referenced_at_runtime.zig b/test/cases/compile_errors/comptime_var_referenced_at_runtime.zig index 4ededdffac..e060da33a8 100644 --- a/test/cases/compile_errors/comptime_var_referenced_at_runtime.zig +++ b/test/cases/compile_errors/comptime_var_referenced_at_runtime.zig @@ -63,6 +63,14 @@ export fn far() void { @memset(&rt, elem); } +export fn bax() void { + comptime var x: [2]u32 = undefined; + x = .{ 1, 2 }; + + var rt: [2]u32 = undefined; + @memmove(&rt, &x); +} + // error // // :5:19: error: runtime value contains reference to comptime var @@ -92,3 +100,6 @@ export fn far() void { // :63:18: error: runtime value contains reference to comptime var // :63:18: note: comptime var pointers are not available at runtime // :59:27: note: 'runtime_value' points to comptime var declared here +// :71:19: error: runtime value contains reference to comptime var +// :71:19: note: comptime var pointers are not available at runtime +// :67:30: note: 'runtime_value' points to comptime var declared here diff --git a/test/cases/compile_errors/incorrect_type_to_memset_memcpy.zig b/test/cases/compile_errors/incorrect_type_to_memset_memcpy.zig index 26b31dd506..3bc9065fa1 100644 --- a/test/cases/compile_errors/incorrect_type_to_memset_memcpy.zig +++ b/test/cases/compile_errors/incorrect_type_to_memset_memcpy.zig @@ -28,10 +28,39 @@ pub export fn memcpy_const_dest_ptr() void { var buf2: [5]u8 = .{ 1, 2, 3, 4, 5 }; @memcpy(&buf1, &buf2); } -pub export fn memset_array() void { +pub export fn memcpy_array() void { const buf: [5]u8 = .{ 1, 2, 3, 4, 5 }; @memcpy(buf, 1); } +pub export fn entry_memmove() void { + var buf: [5]u8 = .{ 1, 2, 3, 4, 5 }; + const slice: []u8 = &buf; + const a: u32 = 1234; + @memmove(slice.ptr, @as([*]const u8, @ptrCast(&a))); +} +pub export fn entry1_memmove() void { + var buf: [5]u8 = .{ 1, 2, 3, 4, 5 }; + const ptr: *u8 = &buf[0]; + @memmove(ptr, 0); +} +pub export fn non_matching_lengths_memmove() void { + var buf1: [5]u8 = .{ 1, 2, 3, 4, 5 }; + var buf2: [6]u8 = .{ 1, 2, 3, 4, 5, 6 }; + @memmove(&buf2, &buf1); +} +pub export fn memcpy_const_dest_ptr_memmove() void { + const buf1: [5]u8 = .{ 1, 2, 3, 4, 5 }; + var buf2: [5]u8 = .{ 1, 2, 3, 4, 5 }; + @memmove(&buf1, &buf2); +} +pub export fn memmove_array() void { + const buf: [5]u8 = .{ 1, 2, 3, 4, 5 }; + @memmove(buf, 1); +} +pub export fn memset_array() void { + const buf: [5]u8 = .{ 1, 2, 3, 4, 5 }; + @memset(buf, 1); +} // error // backend=stage2 @@ -51,3 +80,16 @@ pub export fn memset_array() void { // :29:13: error: cannot memcpy to constant pointer // :33:13: error: type '[5]u8' is not an indexable pointer // :33:13: note: operand must be a slice, a many pointer or a pointer to an array +// :39:5: error: unknown @memmove length +// :39:19: note: destination type '[*]u8' provides no length +// :39:25: note: source type '[*]const u8' provides no length +// :44:14: error: type '*u8' is not an indexable pointer +// :44:14: note: operand must be a slice, a many pointer or a pointer to an array +// :49:5: error: non-matching @memmove lengths +// :49:14: note: length 6 here +// :49:21: note: length 5 here +// :54:14: error: cannot memmove to constant pointer +// :58:14: error: type '[5]u8' is not an indexable pointer +// :58:14: note: operand must be a slice, a many pointer or a pointer to an array +// :62:13: error: type '[5]u8' is not an indexable pointer +// :62:13: note: operand must be a slice, a many pointer or a pointer to an array diff --git a/test/cases/safety/memcpy_alias.zig b/test/cases/safety/memcpy_alias.zig index 7371457467..f7a1a16024 100644 --- a/test/cases/safety/memcpy_alias.zig +++ b/test/cases/safety/memcpy_alias.zig @@ -12,6 +12,7 @@ pub fn main() !void { var len: usize = 5; _ = &len; @memcpy(buffer[0..len], buffer[4 .. 4 + len]); + return error.TestFailed; } // run // backend=stage2,llvm diff --git a/test/cases/safety/memcpy_len_mismatch.zig b/test/cases/safety/memcpy_len_mismatch.zig index fb6aa2e59b..cfa7f4fa77 100644 --- a/test/cases/safety/memcpy_len_mismatch.zig +++ b/test/cases/safety/memcpy_len_mismatch.zig @@ -12,6 +12,7 @@ pub fn main() !void { var len: usize = 5; _ = &len; @memcpy(buffer[0..len], buffer[len .. len + 4]); + return error.TestFailed; } // run // backend=stage2,llvm diff --git a/test/cases/safety/memmove_len_mismatch.zig b/test/cases/safety/memmove_len_mismatch.zig new file mode 100644 index 0000000000..89908a8c4e --- /dev/null +++ b/test/cases/safety/memmove_len_mismatch.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "@memmove arguments have non-equal lengths")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + var buffer = [2]u8{ 1, 2 } ** 5; + var len: usize = 5; + _ = &len; + @memmove(buffer[0..len], buffer[len .. len + 4]); + return error.TestFailed; +} +// run +// backend=llvm +// target=native diff --git a/test/incremental/change_panic_handler_explicit b/test/incremental/change_panic_handler_explicit index c23abe186c..5188208647 100644 --- a/test/incremental/change_panic_handler_explicit +++ b/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 { diff --git a/test/standalone/zerolength_check/src/main.zig b/test/standalone/zerolength_check/src/main.zig index 1cb8358c9d..2f674ab4ff 100644 --- a/test/standalone/zerolength_check/src/main.zig +++ b/test/standalone/zerolength_check/src/main.zig @@ -5,6 +5,7 @@ test { const source = foo(); @memcpy(dest, source); + @memmove(dest, source); @memset(dest, 4); @memset(dest, undefined);