Skip to content

Commit

Permalink
change semantics of @memcpy and @memset
Browse files Browse the repository at this point in the history
Now they use slices or array pointers with any element type instead of
requiring byte pointers.

This is a breaking enhancement to the language.

The safety check for overlapping pointers will be implemented in a
future commit.

closes #14040
  • Loading branch information
andrewrk committed Apr 24, 2023
1 parent ef0a020 commit b1ad912
Show file tree
Hide file tree
Showing 33 changed files with 221 additions and 280 deletions.
50 changes: 19 additions & 31 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -8681,40 +8681,28 @@ test "integer cast panic" {
{#header_close#}

{#header_open|@memcpy#}
<pre>{#syntax#}@memcpy(noalias dest: [*]u8, noalias source: [*]const u8, byte_count: usize) void{#endsyntax#}</pre>
<p>
This function copies bytes from one region of memory to another. {#syntax#}dest{#endsyntax#} and
{#syntax#}source{#endsyntax#} are both pointers and must not overlap.
</p>
<p>
This function is a low level intrinsic with no safety mechanisms. Most code
should not use this function, instead using something like this:
</p>
<pre>{#syntax#}for (dest, source[0..byte_count]) |*d, s| d.* = s;{#endsyntax#}</pre>
<p>
The optimizer is intelligent enough to turn the above snippet into a memcpy.
</p>
<p>There is also a standard library function for this:</p>
<pre>{#syntax#}const mem = @import("std").mem;
mem.copy(u8, dest[0..byte_count], source[0..byte_count]);{#endsyntax#}</pre>
<pre>{#syntax#}@memcpy(noalias dest, noalias source) void{#endsyntax#}</pre>
<p>This function copies bytes from one region of memory to another.</p>
<p>{#syntax#}dest{#endsyntax#} must be a mutable slice, or a mutable pointer to an array.
It may have any alignment, and it may have any element type.</p>
<p>{#syntax#}source{#endsyntax#} must be an array, pointer, or a slice
with the same element type as {#syntax#}dest{#endsyntax#}. It may have
any alignment. Only {#syntax#}const{#endsyntax#} access is required. It
is sliced from 0 to the same length as
{#syntax#}dest{#endsyntax#}, triggering the same set of safety checks and
possible compile errors as
{#syntax#}source[0..dest.len]{#endsyntax#}.</p>
<p>It is illegal for {#syntax#}dest{#endsyntax#} and
{#syntax#}source[0..dest.len]{#endsyntax#} to overlap. If safety
checks are enabled, there will be a runtime check for such overlapping.</p>
{#header_close#}

{#header_open|@memset#}
<pre>{#syntax#}@memset(dest: [*]u8, c: u8, byte_count: usize) void{#endsyntax#}</pre>
<p>
This function sets a region of memory to {#syntax#}c{#endsyntax#}. {#syntax#}dest{#endsyntax#} is a pointer.
</p>
<p>
This function is a low level intrinsic with no safety mechanisms. Most
code should not use this function, instead using something like this:
</p>
<pre>{#syntax#}for (dest[0..byte_count]) |*b| b.* = c;{#endsyntax#}</pre>
<p>
The optimizer is intelligent enough to turn the above snippet into a memset.
</p>
<p>There is also a standard library function for this:</p>
<pre>{#syntax#}const mem = @import("std").mem;
mem.set(u8, dest, c);{#endsyntax#}</pre>
<pre>{#syntax#}@memset(dest, elem) void{#endsyntax#}</pre>
<p>This function sets all the elements of a memory region to {#syntax#}elem{#endsyntax#}.</p>
<p>{#syntax#}dest{#endsyntax#} must be a mutable slice or a mutable pointer to an array.
It may have any alignment, and it may have any element type.</p>
<p>{#syntax#}elem{#endsyntax#} is coerced to the element type of {#syntax#}dest{#endsyntax#}.</p>
{#header_close#}

{#header_open|@min#}
Expand Down
12 changes: 6 additions & 6 deletions lib/compiler_rt/atomics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,22 @@ fn __atomic_load(size: u32, src: [*]u8, dest: [*]u8, model: i32) callconv(.C) vo
_ = model;
var sl = spinlocks.get(@ptrToInt(src));
defer sl.release();
@memcpy(dest, src, size);
@memcpy(dest[0..size], src);
}

fn __atomic_store(size: u32, dest: [*]u8, src: [*]u8, model: i32) callconv(.C) void {
_ = model;
var sl = spinlocks.get(@ptrToInt(dest));
defer sl.release();
@memcpy(dest, src, size);
@memcpy(dest[0..size], src);
}

fn __atomic_exchange(size: u32, ptr: [*]u8, val: [*]u8, old: [*]u8, model: i32) callconv(.C) void {
_ = model;
var sl = spinlocks.get(@ptrToInt(ptr));
defer sl.release();
@memcpy(old, ptr, size);
@memcpy(ptr, val, size);
@memcpy(old[0..size], ptr);
@memcpy(ptr[0..size], val);
}

fn __atomic_compare_exchange(
Expand All @@ -155,10 +155,10 @@ fn __atomic_compare_exchange(
if (expected[i] != b) break;
} else {
// The two objects, ptr and expected, are equal
@memcpy(ptr, desired, size);
@memcpy(ptr[0..size], desired);
return 1;
}
@memcpy(expected, ptr, size);
@memcpy(expected[0..size], ptr);
return 0;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/compiler_rt/emutls.zig
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ const ObjectArray = struct {

if (control.default_value) |value| {
// default value: copy the content to newly allocated object.
@memcpy(data, @ptrCast([*]const u8, value), size);
@memcpy(data[0..size], @ptrCast([*]const u8, value));
} else {
// no default: return zeroed memory.
@memset(data, 0, size);
@memset(data[0..size], 0);
}

self.slots[index] = @ptrCast(*anyopaque, data);
Expand Down
4 changes: 2 additions & 2 deletions lib/std/array_hash_map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1893,7 +1893,7 @@ const IndexHeader = struct {
const index_size = hash_map.capacityIndexSize(new_bit_index);
const nbytes = @sizeOf(IndexHeader) + index_size * len;
const bytes = try allocator.alignedAlloc(u8, @alignOf(IndexHeader), nbytes);
@memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader));
@memset(bytes[@sizeOf(IndexHeader)..], 0xff);
const result = @ptrCast(*IndexHeader, bytes.ptr);
result.* = .{
.bit_index = new_bit_index,
Expand All @@ -1914,7 +1914,7 @@ const IndexHeader = struct {
const index_size = hash_map.capacityIndexSize(header.bit_index);
const ptr = @ptrCast([*]align(@alignOf(IndexHeader)) u8, header);
const nbytes = @sizeOf(IndexHeader) + header.length() * index_size;
@memset(ptr + @sizeOf(IndexHeader), 0xff, nbytes - @sizeOf(IndexHeader));
@memset(ptr[@sizeOf(IndexHeader)..nbytes], 0xff);
}

// Verify that the header has sufficient alignment to produce aligned arrays.
Expand Down
16 changes: 4 additions & 12 deletions lib/std/array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {

const new_memory = try allocator.alignedAlloc(T, alignment, self.items.len);
mem.copy(T, new_memory, self.items);
@memset(@ptrCast([*]u8, self.items.ptr), undefined, self.items.len * @sizeOf(T));
@memset(self.items, undefined);
self.clearAndFree();
return new_memory;
}
Expand Down Expand Up @@ -281,11 +281,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
const new_len = old_len + items.len;
assert(new_len <= self.capacity);
self.items.len = new_len;
@memcpy(
@ptrCast([*]align(@alignOf(T)) u8, self.items.ptr + old_len),
@ptrCast([*]const u8, items.ptr),
items.len * @sizeOf(T),
);
@memcpy(self.items[old_len..][0..items.len], items);
}

pub const Writer = if (T != u8)
Expand Down Expand Up @@ -601,7 +597,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ

const new_memory = try allocator.alignedAlloc(T, alignment, self.items.len);
mem.copy(T, new_memory, self.items);
@memset(@ptrCast([*]u8, self.items.ptr), undefined, self.items.len * @sizeOf(T));
@memset(self.items, undefined);
self.clearAndFree(allocator);
return new_memory;
}
Expand Down Expand Up @@ -740,11 +736,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
const new_len = old_len + items.len;
assert(new_len <= self.capacity);
self.items.len = new_len;
@memcpy(
@ptrCast([*]align(@alignOf(T)) u8, self.items.ptr + old_len),
@ptrCast([*]const u8, items.ptr),
items.len * @sizeOf(T),
);
@memcpy(self.items[old_len..][0..items.len], items);
}

pub const WriterContext = struct {
Expand Down
2 changes: 1 addition & 1 deletion lib/std/c/darwin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3670,7 +3670,7 @@ pub const MachTask = extern struct {
else => |err| return unexpectedKernError(err),
}

@memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read);
@memcpy(out_buf[0..curr_bytes_read], @intToPtr([*]const u8, vm_memory));
_ = vm_deallocate(mach_task_self(), vm_memory, curr_bytes_read);

out_buf = out_buf[curr_bytes_read..];
Expand Down
2 changes: 1 addition & 1 deletion lib/std/crypto/aes_gcm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fn AesGcm(comptime Aes: anytype) type {
acc |= (computed_tag[p] ^ tag[p]);
}
if (acc != 0) {
@memset(m.ptr, undefined, m.len);
@memset(m, undefined);
return error.AuthenticationFailed;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/std/crypto/tls/Client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
const pub_key = subject.pubKey();
if (pub_key.len > main_cert_pub_key_buf.len)
return error.CertificatePublicKeyInvalid;
@memcpy(&main_cert_pub_key_buf, pub_key.ptr, pub_key.len);
@memcpy(main_cert_pub_key_buf[0..pub_key.len], pub_key);
main_cert_pub_key_len = @intCast(@TypeOf(main_cert_pub_key_len), pub_key.len);
} else {
try prev_cert.verify(subject, now_sec);
Expand Down
6 changes: 3 additions & 3 deletions lib/std/crypto/utils.zig
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T,
/// Sets a slice to zeroes.
/// Prevents the store from being optimized out.
pub fn secureZero(comptime T: type, s: []T) void {
// NOTE: We do not use a volatile slice cast here since LLVM cannot
// see that it can be replaced by a memset.
// TODO: implement `@memset` for non-byte-sized element type in the llvm backend
//@memset(@as([]volatile T, s), 0);
const ptr = @ptrCast([*]volatile u8, s.ptr);
const length = s.len * @sizeOf(T);
@memset(ptr, 0, length);
@memset(ptr[0..length], 0);
}

test "crypto.utils.timingSafeEql" {
Expand Down
8 changes: 4 additions & 4 deletions lib/std/fifo.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub fn LinearFifo(
}
{ // set unused area to undefined
const unused = mem.sliceAsBytes(self.buf[self.count..]);
@memset(unused.ptr, undefined, unused.len);
@memset(unused, undefined);
}
}

Expand Down Expand Up @@ -182,12 +182,12 @@ pub fn LinearFifo(
const slice = self.readableSliceMut(0);
if (slice.len >= count) {
const unused = mem.sliceAsBytes(slice[0..count]);
@memset(unused.ptr, undefined, unused.len);
@memset(unused, undefined);
} else {
const unused = mem.sliceAsBytes(slice[0..]);
@memset(unused.ptr, undefined, unused.len);
@memset(unused, undefined);
const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]);
@memset(unused2.ptr, undefined, unused2.len);
@memset(unused2, undefined);
}
}
if (autoalign and self.count == count) {
Expand Down
13 changes: 4 additions & 9 deletions lib/std/hash/murmur.zig
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub const Murmur2_64 = struct {
const offset = len - rest;
if (rest > 0) {
var k1: u64 = 0;
@memcpy(@ptrCast([*]u8, &k1), @ptrCast([*]const u8, &str[@intCast(usize, offset)]), @intCast(usize, rest));
@memcpy(@ptrCast([*]u8, &k1)[0..@intCast(usize, rest)], @ptrCast([*]const u8, &str[@intCast(usize, offset)]));
if (native_endian == .Big)
k1 = @byteSwap(k1);
h1 ^= k1;
Expand Down Expand Up @@ -282,13 +282,8 @@ pub const Murmur3_32 = struct {

fn SMHasherTest(comptime hash_fn: anytype, comptime hashbits: u32) u32 {
const hashbytes = hashbits / 8;
var key: [256]u8 = undefined;
var hashes: [hashbytes * 256]u8 = undefined;
var final: [hashbytes]u8 = undefined;

@memset(@ptrCast([*]u8, &key[0]), 0, @sizeOf(@TypeOf(key)));
@memset(@ptrCast([*]u8, &hashes[0]), 0, @sizeOf(@TypeOf(hashes)));
@memset(@ptrCast([*]u8, &final[0]), 0, @sizeOf(@TypeOf(final)));
var key: [256]u8 = [1]u8{0} ** 256;
var hashes: [hashbytes * 256]u8 = [1]u8{0} ** (hashbytes * 256);

var i: u32 = 0;
while (i < 256) : (i += 1) {
Expand All @@ -297,7 +292,7 @@ fn SMHasherTest(comptime hash_fn: anytype, comptime hashbits: u32) u32 {
var h = hash_fn(key[0..i], 256 - i);
if (native_endian == .Big)
h = @byteSwap(h);
@memcpy(@ptrCast([*]u8, &hashes[i * hashbytes]), @ptrCast([*]u8, &h), hashbytes);
@memcpy(hashes[i * hashbytes..][0..hashbytes], @ptrCast([*]u8, &h));
}

return @truncate(u32, hash_fn(&hashes, 0));
Expand Down
2 changes: 1 addition & 1 deletion lib/std/hash_map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1449,7 +1449,7 @@ pub fn HashMapUnmanaged(
}

fn initMetadatas(self: *Self) void {
@memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity());
@memset(@ptrCast([*]u8, self.metadata.?)[0..@sizeOf(Metadata) * self.capacity()], 0);
}

// This counts the number of occupied slots (not counting tombstones), which is
Expand Down
7 changes: 4 additions & 3 deletions lib/std/heap/general_purpose_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size);
if (new_size_class <= size_class) {
if (old_mem.len > new_size) {
@memset(old_mem.ptr + new_size, undefined, old_mem.len - new_size);
@memset(old_mem[new_size..], undefined);
}
if (config.verbose_log) {
log.info("small resize {d} bytes at {*} to {d}", .{
Expand Down Expand Up @@ -911,7 +911,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
self.empty_buckets = bucket;
}
} else {
@memset(old_mem.ptr, undefined, old_mem.len);
@memset(old_mem, undefined);
}
if (config.safety) {
assert(self.small_allocations.remove(@ptrToInt(old_mem.ptr)));
Expand Down Expand Up @@ -1011,7 +1011,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
};
self.buckets[bucket_index] = ptr;
// Set the used bits to all zeroes
@memset(@as(*[1]u8, ptr.usedBits(0)), 0, usedBitsCount(size_class));
@memset(@as([*]u8, @as(*[1]u8, ptr.usedBits(0)))[0..usedBitsCount(size_class)], 0);
return ptr;
}
};
Expand Down Expand Up @@ -1412,3 +1412,4 @@ test "bug 9995 fix, large allocs count requested size not backing size" {
buf = try allocator.realloc(buf, 2);
try std.testing.expect(gpa.total_requested_bytes == 2);
}

8 changes: 4 additions & 4 deletions lib/std/math/big/int_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2756,7 +2756,7 @@ test "big int conversion read twos complement with padding" {

var buffer1 = try testing.allocator.alloc(u8, 16);
defer testing.allocator.free(buffer1);
@memset(buffer1.ptr, 0xaa, buffer1.len);
@memset(buffer1, 0xaa);

// writeTwosComplement:
// (1) should not write beyond buffer[0..abi_size]
Expand All @@ -2773,7 +2773,7 @@ test "big int conversion read twos complement with padding" {
a.toConst().writeTwosComplement(buffer1[0..16], .Big);
try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }));

@memset(buffer1.ptr, 0xaa, buffer1.len);
@memset(buffer1, 0xaa);
try a.set(-0x01_02030405_06070809_0a0b0c0d);
bit_count = 12 * 8 + 2;

Expand All @@ -2794,7 +2794,7 @@ test "big int write twos complement +/- zero" {

var buffer1 = try testing.allocator.alloc(u8, 16);
defer testing.allocator.free(buffer1);
@memset(buffer1.ptr, 0xaa, buffer1.len);
@memset(buffer1, 0xaa);

// Test zero

Expand All @@ -2807,7 +2807,7 @@ test "big int write twos complement +/- zero" {
m.toConst().writeTwosComplement(buffer1[0..16], .Big);
try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));

@memset(buffer1.ptr, 0xaa, buffer1.len);
@memset(buffer1, 0xaa);
m.positive = false;

// Test negative zero
Expand Down
8 changes: 4 additions & 4 deletions lib/std/mem/Allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ pub fn allocAdvancedWithRetAddr(
const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
const byte_ptr = self.rawAlloc(byte_count, log2a(a), return_address) orelse return Error.OutOfMemory;
// TODO: https://github.com/ziglang/zig/issues/4298
@memset(byte_ptr, undefined, byte_count);
@memset(byte_ptr[0..byte_count], undefined);
const byte_slice = byte_ptr[0..byte_count];
return mem.bytesAsSlice(T, @alignCast(a, byte_slice));
}
Expand Down Expand Up @@ -282,9 +282,9 @@ pub fn reallocAdvanced(

const new_mem = self.rawAlloc(byte_count, log2a(Slice.alignment), return_address) orelse
return error.OutOfMemory;
@memcpy(new_mem, old_byte_slice.ptr, @min(byte_count, old_byte_slice.len));
@memcpy(new_mem[0..@min(byte_count, old_byte_slice.len)], old_byte_slice);
// TODO https://github.com/ziglang/zig/issues/4298
@memset(old_byte_slice.ptr, undefined, old_byte_slice.len);
@memset(old_byte_slice, undefined);
self.rawFree(old_byte_slice, log2a(Slice.alignment), return_address);

return mem.bytesAsSlice(T, @alignCast(Slice.alignment, new_mem[0..byte_count]));
Expand All @@ -299,7 +299,7 @@ pub fn free(self: Allocator, memory: anytype) void {
if (bytes_len == 0) return;
const non_const_ptr = @constCast(bytes.ptr);
// TODO: https://github.com/ziglang/zig/issues/4298
@memset(non_const_ptr, undefined, bytes_len);
@memset(non_const_ptr[0..bytes_len], undefined);
self.rawFree(non_const_ptr[0..bytes_len], log2a(Slice.alignment), @returnAddress());
}

Expand Down
3 changes: 1 addition & 2 deletions lib/std/multi_array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -360,11 +360,10 @@ pub fn MultiArrayList(comptime T: type) type {
if (@sizeOf(field_info.type) != 0) {
const field = @intToEnum(Field, i);
const dest_slice = self_slice.items(field)[new_len..];
const byte_count = dest_slice.len * @sizeOf(field_info.type);
// We use memset here for more efficient codegen in safety-checked,
// valgrind-enabled builds. Otherwise the valgrind client request
// will be repeated for every element.
@memset(@ptrCast([*]u8, dest_slice.ptr), undefined, byte_count);
@memset(dest_slice, undefined);
}
}
self.len = new_len;
Expand Down
Loading

0 comments on commit b1ad912

Please sign in to comment.