Skip to content

Commit edcb6fb

Browse files
authored
memset should match the C ABI (ghostty-org#13469)
The custom memset accepted its fill value as u8 even though C callers pass int. Accept c_int and explicitly truncate it to the low byte, which is what other implementations of this do.
2 parents 6f10ddf + 20c3eae commit edcb6fb

1 file changed

Lines changed: 25 additions & 9 deletions

File tree

src/quirks_memset.zig

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,26 @@ const zva_enabled = builtin.cpu.arch == .aarch64 and
101101
/// plain vector loop measures faster. Empirically mesaured.
102102
const zva_threshold = 16384;
103103

104-
/// Matches compiler_rt's memset signature (lib/compiler_rt.zig).
105-
fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
104+
/// Matches the C memset ABI.
105+
fn memset(dest: ?[*]u8, c: c_int, len: usize) callconv(.c) ?[*]u8 {
106106
@setRuntimeSafety(false);
107107

108108
if (len == 0) return dest;
109109
const d = dest.?;
110110

111+
// Only the low byte of `c` is used. The C ABI allows a "int"
112+
// as the c parameter but this behavior of just grabbing the
113+
// low byte seems consistent across implementations.
114+
const byte: u8 = @truncate(@as(c_uint, @bitCast(c)));
115+
111116
// Large path: full-width vector stores.
112117
if (len >= vec_bytes) {
113118
// Very large zero fills: zero whole cachelines with `dc zva`
114119
// instead. Only implemented for the (universal in practice)
115120
// 64-byte block size; anything else falls through to the
116121
// vector loop.
117122
if (comptime zva_enabled) {
118-
if (len >= zva_threshold and c == 0) zva: {
123+
if (len >= zva_threshold and byte == 0) zva: {
119124
if (zvaSize() != 64) break :zva;
120125
const splat64: @Vector(64, u8) = @splat(0);
121126

@@ -148,7 +153,7 @@ fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
148153
}
149154
}
150155

151-
const splat: @Vector(vec_bytes, u8) = @splat(c);
156+
const splat: @Vector(vec_bytes, u8) = @splat(byte);
152157

153158
// Fill [0, N) where N is len rounded down to a multiple of
154159
// vec_bytes.
@@ -188,7 +193,7 @@ fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
188193
// pair of 64-byte stores covers len <= 128. Only emitted
189194
// when vec_bytes > 64 (e.g. AVX-512), otherwise the
190195
// vector loop above already handled these lengths.
191-
const splat64: @Vector(64, u8) = @splat(c);
196+
const splat64: @Vector(64, u8) = @splat(byte);
192197
d[0..64].* = splat64;
193198
d[len - 64 ..][0..64].* = splat64;
194199
return dest;
@@ -200,7 +205,7 @@ fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
200205
// second pair extends the covered prefix to [0..32] and the
201206
// covered suffix to [len-32..len], which meet or overlap in
202207
// the middle.
203-
const splat16: @Vector(16, u8) = @splat(c);
208+
const splat16: @Vector(16, u8) = @splat(byte);
204209
d[0..16].* = splat16;
205210
d[len - 16 ..][0..16].* = splat16;
206211
if (len > 32) {
@@ -211,14 +216,14 @@ fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
211216
}
212217
if (len >= 8) {
213218
// 8 <= len <= 15: one 8-byte pair (covers len <= 16).
214-
const splat8: @Vector(8, u8) = @splat(c);
219+
const splat8: @Vector(8, u8) = @splat(byte);
215220
d[0..8].* = splat8;
216221
d[len - 8 ..][0..8].* = splat8;
217222
return dest;
218223
}
219224
if (len >= 4) {
220225
// 4 <= len <= 7: one 4-byte pair (covers len <= 8).
221-
const splat4: @Vector(4, u8) = @splat(c);
226+
const splat4: @Vector(4, u8) = @splat(byte);
222227
d[0..4].* = splat4;
223228
d[len - 4 ..][0..4].* = splat4;
224229
return dest;
@@ -231,7 +236,7 @@ fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.c) ?[*]u8 {
231236
// statically known.
232237
var i: usize = 0;
233238
while (i < len) : (i += 1) {
234-
d[i] = c;
239+
d[i] = byte;
235240
asm volatile ("" ::: .{ .memory = true });
236241
}
237242
return dest;
@@ -286,6 +291,17 @@ test memset {
286291
try testing.expectEqual(0xAA, one[0]);
287292
}
288293

294+
test "memset truncates C int fill value" {
295+
const testing = std.testing;
296+
297+
var buf: [4]u8 = undefined;
298+
_ = memset(&buf, -1, buf.len);
299+
for (buf) |b| try testing.expectEqual(0xFF, b);
300+
301+
_ = memset(&buf, 0x1234, buf.len);
302+
for (buf) |b| try testing.expectEqual(0x34, b);
303+
}
304+
289305
test "memset large zero fills (dc zva path)" {
290306
const testing = std.testing;
291307
const alloc = testing.allocator;

0 commit comments

Comments
 (0)