authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2026-07-24 14:06:01+02:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-07-27 22:00:13+02:00
log1f1bee62e854ec9c75e8b49072d0c403c7f0a100
tree6ecaed99e158be08245ddd275fa24e9392e6d3e6
parentb52057f05218d13901fe576bf2d472b657e58e6f

Io.Uring: context switch depends on struct layout

During context switch in Io.Uring `fiber.contextSwitch` functions leaves `*fiber.Switch` (contexts field from SwitchMessage) in the `rpi` register. ```Zig inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage { return @fieldParentPtr("contexts", Io.fiber.contextSwitch(&message.contexts)); } ``` ```asm .x86_64 => asm volatile ( \\ movq 0(%%rsi), %%rax \\ movq 8(%%rsi), %%rcx \\ leaq 0f(%%rip), %%rdx \\ movq %%rsp, 0(%%rax) \\ movq %%rbp, 8(%%rax) \\ movq %%rdx, 16(%%rax) \\ movq 0(%%rcx), %%rsp \\ movq 8(%%rcx), %%rbp \\ jmpq *16(%%rcx) \\0: : [received_message] "={rsi}" (-> *const Switch), : [message_to_send] "{rsi}" (s), ``` That becomes second argument to the `AscynClosure.call` but there it is interpreted as `*SwitchMessage` which works because both points to the same address (contexts is first field). ```Zig fn call( closure: *AsyncClosure, message: *const SwitchMessage, ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn { ``` SwitchMessage is not packed or external struct allowing reorder, padding. Reordering fields breaks expectation that `*fiber.Switch` and `*SwitchMessage` are the same pointers.

1 files changed, 6 insertions(+), 3 deletions(-)

lib/std/Io/Uring.zig+6-3
......@@ -1135,8 +1135,9 @@ fn mainIdleEntry() callconv(.naked) void {
11351135
11361136fn mainIdle(
11371137 ev: *Evented,
1138 message: *const SwitchMessage,
1138 contexts: *const Io.fiber.Switch,
11391139) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Io.fiber.Context)))) noreturn {
1140 const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts);
11401141 message.handle(ev);
11411142 ev.idle(&ev.threads.allocated[0]);
11421143 ev.yield(@ptrCast(&ev.main_fiber_buffer), .nothing);
......@@ -1414,8 +1415,9 @@ const AsyncClosure = struct {
14141415
14151416 fn call(
14161417 closure: *AsyncClosure,
1417 message: *const SwitchMessage,
1418 contexts: *const Io.fiber.Switch,
14181419 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1420 const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts);
14191421 const ev = closure.evented;
14201422 const fiber = closure.fiber;
14211423 message.handle(ev);
......@@ -1779,8 +1781,9 @@ const Group = struct {
17791781
17801782 fn call(
17811783 closure: *Group.AsyncClosure,
1782 message: *const SwitchMessage,
1784 contexts: *const Io.fiber.Switch,
17831785 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1786 const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts);
17841787 const ev = closure.evented;
17851788 const fiber = closure.fiber;
17861789 message.handle(ev);