Skip to content

Commit 521daf4

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

File tree

2 files changed

+79
-21
lines changed

2 files changed

+79
-21
lines changed

lib/std/Io/Threaded.zig

Lines changed: 29 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.forward(@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,9 @@ 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+
const result_alignment_padding = ac.result_alignment.toByteUnits() -| ac.context_alignment.toByteUnits();
441+
const allocated_size = ac.result_offset_with_padding + result_alignment_padding + result_len;
442+
gpa.free(base[0..allocated_size]);
440443
}
441444
};
442445

@@ -460,13 +463,16 @@ fn async(
460463
};
461464
};
462465
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 {
466+
const context_alignment_padding = context_alignment.toByteUnits() -| @alignOf(AsyncClosure);
467+
const context_size = context_alignment_padding + context.len;
468+
const result_alignment_padding = result_alignment.toByteUnits() -| context_alignment.toByteUnits();
469+
const result_size = result_alignment_padding + result.len;
470+
const allocated_size = @sizeOf(AsyncClosure) + context_size + result_size;
471+
const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), allocated_size) catch {
467472
start(context.ptr, result.ptr);
468473
return null;
469-
}));
474+
};
475+
const ac: *AsyncClosure = @ptrCast(@alignCast(ac_bytes));
470476

471477
ac.* = .{
472478
.closure = .{
@@ -476,7 +482,8 @@ fn async(
476482
},
477483
.func = start,
478484
.context_alignment = context_alignment,
479-
.result_offset = result_offset,
485+
.result_alignment = result_alignment,
486+
.result_offset_with_padding = @sizeOf(AsyncClosure) + context_size,
480487
.reset_event = .unset,
481488
.select_condition = null,
482489
};
@@ -531,10 +538,12 @@ fn concurrent(
531538
const t: *Threaded = @ptrCast(@alignCast(userdata));
532539
const cpu_count = t.cpu_count catch 1;
533540
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
541+
const context_alignment_padding = context_alignment.toByteUnits() -| @alignOf(AsyncClosure);
542+
const context_size = context_alignment_padding + context.len;
543+
const result_alignment_padding = result_alignment.toByteUnits() -| context_alignment.toByteUnits();
544+
const result_size = result_alignment_padding + result_len;
545+
const allocated_size = @sizeOf(AsyncClosure) + context_size + result_size;
546+
const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), allocated_size) catch
538547
return error.ConcurrencyUnavailable;
539548
const ac: *AsyncClosure = @ptrCast(@alignCast(ac_bytes));
540549

@@ -546,7 +555,8 @@ fn concurrent(
546555
},
547556
.func = start,
548557
.context_alignment = context_alignment,
549-
.result_offset = result_offset,
558+
.result_alignment = result_alignment,
559+
.result_offset_with_padding = @sizeOf(AsyncClosure) + context_size,
550560
.reset_event = .unset,
551561
.select_condition = null,
552562
};
@@ -580,6 +590,8 @@ fn concurrent(
580590
return @ptrCast(ac);
581591
}
582592

593+
/// Trailing data:
594+
/// 1. context
583595
const GroupClosure = struct {
584596
closure: Closure,
585597
t: *Threaded,
@@ -621,17 +633,13 @@ const GroupClosure = struct {
621633
gpa.free(base[0..contextEnd(gc.context_alignment, gc.context_len)]);
622634
}
623635

624-
fn contextOffset(context_alignment: std.mem.Alignment) usize {
625-
return context_alignment.forward(@sizeOf(GroupClosure));
626-
}
627-
628636
fn contextEnd(context_alignment: std.mem.Alignment, context_len: usize) usize {
629-
return contextOffset(context_alignment) + context_len;
637+
return @sizeOf(GroupClosure) + context_alignment.forward(@alignOf(GroupClosure)) + context_len;
630638
}
631639

632640
fn contextPointer(gc: *GroupClosure) [*]u8 {
633641
const base: [*]u8 = @ptrCast(gc);
634-
return base + contextOffset(gc.context_alignment);
642+
return @ptrFromInt(gc.context_alignment.forward(@intFromPtr(base + @sizeOf(GroupClosure))));
635643
}
636644

637645
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)