Skip to content

add @memmove builtin#22605

Merged
andrewrk merged 5 commits intoziglang:masterfrom
dweiller:memmove
Apr 27, 2025
Merged

add @memmove builtin#22605
andrewrk merged 5 commits intoziglang:masterfrom
dweiller:memmove

Conversation

@dweiller
Copy link
Contributor

@dweiller dweiller commented Jan 25, 2025

This PR adds a new builtin @memmove(dest: T, src: T) void. It is similar to @memcpy except it can be used for overlapping memory regions. The intended semantics are:

  • src and dest can each be a slice, pointer to array, or many-item pointer
  • at least one of src and dest must provide a length, and if they both do the lengths must be the same
  • unlike (the intended runtime, and current comptime) behaviour of@memcpy, @memmove requires the element type of src and dest to be coercible to each other in memory
  • the result of @memmove(dest, src) is that dest[0..len] contains bytes as if a element-by-element loop copied src[0..len] to an auxiliary buffer, and then a second loop copied from that buffer to dest[0..len].

This builtin is currently using up the last ZIR tag.

Related: #767

@dweiller dweiller force-pushed the memmove branch 3 times, most recently from 4e8a431 to c976221 Compare January 25, 2025 15:35
@nektro
Copy link
Contributor

nektro commented Jan 25, 2025

  • should it be @memmove(dest, src) to match order of @memcpy ?
  • does @memmove perform the same length equality assertion as @memcpy ?

@dweiller
Copy link
Contributor Author

dweiller commented Jan 25, 2025

  • should it be @memmove(dest, src) to match order of @memcpy ?

    • does @memmove perform the same length equality assertion as @memcpy ?

Whoops the order is the same, I just wrote it wrong. It has the same length checks too - will update the OP.

@alexrp
Copy link
Member

alexrp commented Jan 27, 2025

unlike (the intended runtime, and current comptime) behaviour of@memcpy, @memmove requires the element type of src and dest to be coercible to each other in memory

Just noting that we concluded in the discussion that @memcpy will be changed to only allow in-memory coercions, i.e. coerceInMemoryAllowed. So the builtins will be consistent on this point.

@dweiller dweiller force-pushed the memmove branch 2 times, most recently from 3602635 to d67f0b0 Compare January 28, 2025 05:09
@alexrp alexrp mentioned this pull request Jan 28, 2025
@alexrp
Copy link
Member

alexrp commented Jan 28, 2025

Additionally, per the @memcpy discussion, the addition of @memmove should include deprecation of std.mem.copy(Backwards,Forwards) (and later removal in 0.15.0).

@dweiller
Copy link
Contributor Author

Additionally, per the @memcpy discussion, the addition of @memmove should include deprecation of std.mem.copy(Backwards,Forwards) (and later removal in 0.15.0).

Does deprecation mean replace with @compileError, or say so in a doc comment? I see both being done in lib/std.

@alexrp
Copy link
Member

alexrp commented Jan 28, 2025

Does deprecation mean replace with @compileError, or say so in a doc comment? I see both being done in lib/std.

Doc comment.

I think it's removal that either means outright deletion, or @compileError for an additional release cycle. Not sure if we have any guidelines for when to do which. cc @andrewrk

@dweiller dweiller marked this pull request as ready for review January 29, 2025 05:49
@dweiller dweiller force-pushed the memmove branch 2 times, most recently from f8d1141 to 7f0001a Compare January 30, 2025 09:57
@alexrp alexrp added this to the 0.14.0 milestone Jan 30, 2025
@dweiller dweiller force-pushed the memmove branch 3 times, most recently from 21b518e to 99eff11 Compare February 4, 2025 04:01
@dweiller dweiller force-pushed the memmove branch 3 times, most recently from 7a4c39c to fd577a8 Compare February 25, 2025 04:28
@dweiller
Copy link
Contributor Author

I don't know what the aarch64-macos-debug CI issue is - it failed before and after a rebase for @disableIntrinsics(), but I can't really believe this PR is causing the problem.

@dweiller
Copy link
Contributor Author

Looks like the aarch64-macos-debug CI issue is unrelated, there are other PRs that are having the same failure.

@andrewrk
Copy link
Member

macho linker bug filed at #23010

@andrewrk
Copy link
Member

andy@bark ~/d/z/build-release (dweiller-memmove)> git rebase origin/master
Successfully rebased and updated refs/heads/dweiller-memmove.
andy@bark ~/d/z/build-release (dweiller-memmove)> /etc/profiles/per-user/andy/bin/fzf-tmux: line 22: tmux: commaandy@bark ~/d/z/build-release (dweiller-memmove)> git push -f git@github.com:dweiller/zig.git dweiller-memmove:memmove
Enumerating objects: 149, done.
Counting objects: 100% (149/149), done.
Delta compression using up to 32 threads
Compressing objects: 100% (82/82), done.
Writing objects: 100% (85/85), 11.15 KiB | 11.15 MiB/s, done.
Total 85 (delta 70), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (70/70), completed with 56 local objects.
To github.com:dweiller/zig.git
 ! [remote rejected]       dweiller-memmove -> memmove (permission denied)
error: failed to push some refs to 'github.com:dweiller/zig.git'

@dweiller
Copy link
Contributor Author

andy@bark ~/d/z/build-release (dweiller-memmove)> git rebase origin/master
Successfully rebased and updated refs/heads/dweiller-memmove.
andy@bark ~/d/z/build-release (dweiller-memmove)> /etc/profiles/per-user/andy/bin/fzf-tmux: line 22: tmux: commaandy@bark ~/d/z/build-release (dweiller-memmove)> git push -f git@github.com:dweiller/zig.git dweiller-memmove:memmove
Enumerating objects: 149, done.
Counting objects: 100% (149/149), done.
Delta compression using up to 32 threads
Compressing objects: 100% (82/82), done.
Writing objects: 100% (85/85), 11.15 KiB | 11.15 MiB/s, done.
Total 85 (delta 70), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (70/70), completed with 56 local objects.
To github.com:dweiller/zig.git
 ! [remote rejected]       dweiller-memmove -> memmove (permission denied)
error: failed to push some refs to 'github.com:dweiller/zig.git'

I've rebased onto master (c06fecd) and enabled the 'allow edits by maintainers' option so you shouldn't hit that error again.

@andrewrk
Copy link
Member

Thanks!

@andrewrk andrewrk merged commit 1b76d4c into ziglang:master Apr 27, 2025
9 checks passed
@dweiller dweiller deleted the memmove branch April 28, 2025 03:34
@matklad
Copy link
Contributor

matklad commented Sep 19, 2025

This is bikeshedy, but I do feel somewhat strongly here. While memcpy / memmove pair of names is familiar to C programmers, they are quite opaque from the first principles. I remember having hard time learning which is which back in the day, and, honestly, having learned that Zig now has @memmove, I had to double check @memcpy docs to make sure I didn't mix them up.

Given that using a wrong copy built-in can lead to subtle bugs, perhaps we can use a less legacy-inspired name here? I would prefer @memcpy / @memcpyOverlapping for example. The Overlapping suffix is a tad verbose, but @memmove is a function that you need only rarely, so this feels fine:

matklad@TigerMac ~/p/zig/src ((d6b4e191))
λ rg '@memcpy' | wc -l
     120
matklad@TigerMac ~/p/zig/src ((d6b4e191))
λ rg '@memmove' | wc -l
       3

A less bikesheddy question is whether @memcpy is the right primitive at all? I think there are three use-cases for shoveling bytes around:

  • Assigning contents of S to D, where S and D are disjoint (memcpy)
  • Inside an array, shifting subarray left or right
  • Assigning S to D where S or D may or may not overlap

Only 0.6 sure here, but it seems to me that the third use-case, where you don't know if slices overlap, is not a real use-case. Quick glance at zig source code confirms that @memmove is always used to shift a subarray of a bigger array.

So perhaps the right signature is

fn @memmove(slice: []T, by: isize) void

That is, we pass a signed shift distance:

    msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1);
    @memmove(msg.notes[1..][0..orig_notes], msg.notes[0..orig_notes]);
    msg.notes[0] = .{
        .src_loc = msg.src_loc,
        .msg = msg.msg,
    };

->

    msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1);
    @memmove(msg.notes[0..orig_notes], +1);
    msg.notes[0] = .{
        .src_loc = msg.src_loc,
        .msg = msg.msg,
    };

@matklad
Copy link
Contributor

matklad commented Sep 19, 2025

🤔 hm, the by version can't bounds check, I think we also want to pass the whole array:

fn @memmove(slice: []T, subslice: []T, by: isize) void
@memmove(msg.notes, msg.notes[0..orig_notes], +1);

EDIT: another option is to use offset instead of signed difference, that is what Rust is doing:

https://doc.rust-lang.org/stable/std/primitive.slice.html#method.copy_within

So,

fn @memmove(slice: []T, subslice: []T, to: usize) void

@dweiller
Copy link
Contributor Author

dweiller commented Sep 20, 2025

Another option could be to have a single @copy builtin that has a third parameter of type enum { no_alias, may_alias }, or enum { forwards, backwards, no_alias, may_alias }, Since builtin's can be variadic, we could have @copy(s1, s2) default to one of them (may_alias might be best).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants