Skip to content

add @memmove builtin #22605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -5149,6 +5149,23 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}std.crypto.secureZero{#endsyntax#}</p>
{#header_close#}

{#header_open|@memmove#}
<pre>{#syntax#}@memmove(dest, source) void{#endsyntax#}</pre>
<p>This function copies bytes from one region of memory to another, but unlike
{#link|@memcpy#} the regions may overlap.</p>
<p>{#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.</p>
<p>{#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.</p>
<p>The {#syntax#}source{#endsyntax#} element type must have the same in-memory
representation as the {#syntax#}dest{#endsyntax#} element type.</p>
<p>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.</p>
{#header_close#}

{#header_open|@min#}
<pre>{#syntax#}@min(...) T{#endsyntax#}</pre>
<p>
Expand Down
4 changes: 4 additions & 0 deletions lib/std/debug.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
5 changes: 5 additions & 0 deletions lib/std/debug/no_panic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ pub fn memcpyAlias() noreturn {
@trap();
}

pub fn memmoveLenMismatch() noreturn {
@branchHint(.cold);
@trap();
}

pub fn noreturnReturned() noreturn {
@branchHint(.cold);
@trap();
Expand Down
4 changes: 4 additions & 0 deletions lib/std/debug/simple_panic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
2 changes: 2 additions & 0 deletions lib/std/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,15 @@ 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;
}

/// 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
Expand Down
8 changes: 8 additions & 0 deletions lib/std/zig/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]),
Expand Down
2 changes: 1 addition & 1 deletion lib/std/zig/AstRlAnnotate.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions lib/std/zig/BuiltinFn.zig
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub const Tag = enum {
max,
memcpy,
memset,
memmove,
min,
wasm_memory_size,
wasm_memory_grow,
Expand Down Expand Up @@ -641,6 +642,13 @@ pub const list = list: {
.param_count = 2,
},
},
.{
"@memmove",
.{
.tag = .memmove,
.param_count = 2,
},
},
.{
"@min",
.{
Expand Down
7 changes: 7 additions & 0 deletions lib/std/zig/Zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1272,6 +1275,7 @@ pub const Inst = struct {
.max,
.memcpy,
.memset,
.memmove,
.min,
.c_import,
.@"resume",
Expand Down Expand Up @@ -1355,6 +1359,7 @@ pub const Inst = struct {
.set_runtime_safety,
.memcpy,
.memset,
.memmove,
.check_comptime_control_flow,
.@"defer",
.defer_err_code,
Expand Down Expand Up @@ -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,

Expand Down Expand Up @@ -4291,6 +4297,7 @@ fn findTrackableInner(
.mul_add,
.memcpy,
.memset,
.memmove,
.min,
.max,
.alloc,
Expand Down
30 changes: 30 additions & 0 deletions lib/std/zig/llvm/Builder.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions lib/zig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 ================= */

Expand Down
14 changes: 14 additions & 0 deletions src/Air.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/Air/types_resolved.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/Liveness.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 });
Expand Down
1 change: 1 addition & 0 deletions src/Liveness/Verify.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Loading
Loading