Skip to content

Commit 170b11d

Browse files
committed
std.Io.Threaded: fix incorrect alignment of trailing data in closure
1 parent 38d4440 commit 170b11d

File tree

2 files changed

+73
-21
lines changed

2 files changed

+73
-21
lines changed

lib/std/Io/Threaded.zig

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,8 @@ const AsyncClosure = struct {
388388
reset_event: ResetEvent,
389389
select_condition: ?*ResetEvent,
390390
context_alignment: std.mem.Alignment,
391-
result_offset: usize,
391+
result_alignment: std.mem.Alignment,
392+
result_offset_with_padding: usize,
392393

393394
const done_reset_event: *ResetEvent = @ptrFromInt(@alignOf(ResetEvent));
394395

@@ -420,12 +421,12 @@ const AsyncClosure = struct {
420421

421422
fn resultPointer(ac: *AsyncClosure) [*]u8 {
422423
const base: [*]u8 = @ptrCast(ac);
423-
return base + ac.result_offset;
424+
return @ptrFromInt(ac.result_alignment.backward(@intFromPtr(base + ac.result_offset_with_padding)));
424425
}
425426

426427
fn contextPointer(ac: *AsyncClosure) [*]u8 {
427428
const base: [*]u8 = @ptrCast(ac);
428-
return base + ac.context_alignment.forward(@sizeOf(AsyncClosure));
429+
return @ptrFromInt(ac.context_alignment.forward(@intFromPtr(base + @sizeOf(AsyncClosure))));
429430
}
430431

431432
fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void {
@@ -436,7 +437,7 @@ const AsyncClosure = struct {
436437

437438
fn free(ac: *AsyncClosure, gpa: Allocator, result_len: usize) void {
438439
const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac);
439-
gpa.free(base[0 .. ac.result_offset + result_len]);
440+
gpa.free(base[0 .. ac.result_offset_with_padding + result_len]);
440441
}
441442
};
442443

@@ -460,13 +461,14 @@ fn async(
460461
};
461462
};
462463
const gpa = t.allocator;
463-
const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
464-
const result_offset = result_alignment.forward(context_offset + context.len);
465-
const n = result_offset + result.len;
466-
const ac: *AsyncClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
464+
const closure_size_with_padding = @sizeOf(AsyncClosure) + (context_alignment.toByteUnits() -| @alignOf(AsyncClosure));
465+
const result_size_with_padding = closure_size_with_padding + context.len + (result_alignment.toByteUnits() -| context_alignment.toByteUnits());
466+
const allocated_size = result_size_with_padding + result.len;
467+
const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), allocated_size) catch {
467468
start(context.ptr, result.ptr);
468469
return null;
469-
}));
470+
};
471+
const ac: *AsyncClosure = @ptrCast(@alignCast(ac_bytes));
470472

471473
ac.* = .{
472474
.closure = .{
@@ -476,7 +478,8 @@ fn async(
476478
},
477479
.func = start,
478480
.context_alignment = context_alignment,
479-
.result_offset = result_offset,
481+
.result_alignment = result_alignment,
482+
.result_offset_with_padding = result_size_with_padding,
480483
.reset_event = .unset,
481484
.select_condition = null,
482485
};
@@ -531,10 +534,10 @@ fn concurrent(
531534
const t: *Threaded = @ptrCast(@alignCast(userdata));
532535
const cpu_count = t.cpu_count catch 1;
533536
const gpa = t.allocator;
534-
const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
535-
const result_offset = result_alignment.forward(context_offset + context.len);
536-
const n = result_offset + result_len;
537-
const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch
537+
const closure_size_with_padding = @sizeOf(AsyncClosure) + (context_alignment.toByteUnits() -| @alignOf(AsyncClosure));
538+
const result_size_with_padding = closure_size_with_padding + context.len + (result_alignment.toByteUnits() -| context_alignment.toByteUnits());
539+
const allocated_size = result_size_with_padding + result_len;
540+
const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), allocated_size) catch
538541
return error.ConcurrencyUnavailable;
539542
const ac: *AsyncClosure = @ptrCast(@alignCast(ac_bytes));
540543

@@ -546,7 +549,8 @@ fn concurrent(
546549
},
547550
.func = start,
548551
.context_alignment = context_alignment,
549-
.result_offset = result_offset,
552+
.result_alignment = result_alignment,
553+
.result_offset_with_padding = result_size_with_padding,
550554
.reset_event = .unset,
551555
.select_condition = null,
552556
};
@@ -580,6 +584,8 @@ fn concurrent(
580584
return @ptrCast(ac);
581585
}
582586

587+
/// Trailing data:
588+
/// 1. context
583589
const GroupClosure = struct {
584590
closure: Closure,
585591
t: *Threaded,
@@ -621,17 +627,13 @@ const GroupClosure = struct {
621627
gpa.free(base[0..contextEnd(gc.context_alignment, gc.context_len)]);
622628
}
623629

624-
fn contextOffset(context_alignment: std.mem.Alignment) usize {
625-
return context_alignment.forward(@sizeOf(GroupClosure));
626-
}
627-
628630
fn contextEnd(context_alignment: std.mem.Alignment, context_len: usize) usize {
629-
return contextOffset(context_alignment) + context_len;
631+
return @sizeOf(GroupClosure) + context_alignment.forward(@alignOf(GroupClosure)) + context_len;
630632
}
631633

632634
fn contextPointer(gc: *GroupClosure) [*]u8 {
633635
const base: [*]u8 = @ptrCast(gc);
634-
return base + contextOffset(gc.context_alignment);
636+
return @ptrFromInt(gc.context_alignment.forward(@intFromPtr(base + @sizeOf(GroupClosure))));
635637
}
636638

637639
const sync_is_waiting: usize = 1 << 0;

lib/std/Io/Threaded/test.zig

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,53 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {
5656
getter.await(io);
5757
putter.await(io);
5858
}
59+
60+
fn paramWithExtraAlignment(param: Align64) void {
61+
assert(param.data == 3);
62+
}
63+
64+
fn returnValueWithExtraAlignment() Align64 {
65+
return .{ .data = 5 };
66+
}
67+
68+
const Align64 = struct {
69+
data: u8 align(64),
70+
};
71+
72+
test "async closure where result or context has extra alignment" {
73+
// A fixed buffer allocator is used instead of `std.testing.allocator` to
74+
// not get memory that has better alignment than requested.
75+
var buffer: [1024]u8 align(64) = undefined;
76+
var fba: std.heap.FixedBufferAllocator = .init(buffer[1..]);
77+
78+
var threaded: std.Io.Threaded = .init(fba.allocator());
79+
defer threaded.deinit();
80+
const io = threaded.io();
81+
82+
{
83+
var future = io.async(paramWithExtraAlignment, .{.{ .data = 3 }});
84+
future.await(io);
85+
}
86+
87+
{
88+
var future = io.async(returnValueWithExtraAlignment, .{});
89+
const result = future.await(io);
90+
try std.testing.expectEqual(5, result.data);
91+
}
92+
}
93+
94+
test "group closure where context has extra alignment" {
95+
// A fixed buffer allocator is used instead of `std.testing.allocator` to
96+
// not get memory that has better alignment than requested.
97+
var buffer: [1024]u8 align(64) = undefined;
98+
var fba: std.heap.FixedBufferAllocator = .init(buffer[1..]);
99+
100+
var threaded: std.Io.Threaded = .init(fba.allocator());
101+
defer threaded.deinit();
102+
const io = threaded.io();
103+
104+
var group: std.Io.Group = .init;
105+
defer group.cancel(io);
106+
107+
group.async(io, paramWithExtraAlignment, .{.{ .data = 3 }});
108+
}

0 commit comments

Comments
 (0)