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 {...@@ -1135,8 +1135,9 @@ fn mainIdleEntry() callconv(.naked) void {
11351135
1136fn mainIdle(1136fn mainIdle(
1137 ev: *Evented,1137 ev: *Evented,
1138 message: *const SwitchMessage,1138 contexts: *const Io.fiber.Switch,
1139) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Io.fiber.Context)))) noreturn {1139) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Io.fiber.Context)))) noreturn {
1140 const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts);
1140 message.handle(ev);1141 message.handle(ev);
1141 ev.idle(&ev.threads.allocated[0]);1142 ev.idle(&ev.threads.allocated[0]);
1142 ev.yield(@ptrCast(&ev.main_fiber_buffer), .nothing);1143 ev.yield(@ptrCast(&ev.main_fiber_buffer), .nothing);
...@@ -1414,8 +1415,9 @@ const AsyncClosure = struct {...@@ -1414,8 +1415,9 @@ const AsyncClosure = struct {
14141415
1415 fn call(1416 fn call(
1416 closure: *AsyncClosure,1417 closure: *AsyncClosure,
1417 message: *const SwitchMessage,1418 contexts: *const Io.fiber.Switch,
1418 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {1419 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1420 const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts);
1419 const ev = closure.evented;1421 const ev = closure.evented;
1420 const fiber = closure.fiber;1422 const fiber = closure.fiber;
1421 message.handle(ev);1423 message.handle(ev);
...@@ -1779,8 +1781,9 @@ const Group = struct {...@@ -1779,8 +1781,9 @@ const Group = struct {
17791781
1780 fn call(1782 fn call(
1781 closure: *Group.AsyncClosure,1783 closure: *Group.AsyncClosure,
1782 message: *const SwitchMessage,1784 contexts: *const Io.fiber.Switch,
1783 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {1785 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1786 const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts);
1784 const ev = closure.evented;1787 const ev = closure.evented;
1785 const fiber = closure.fiber;1788 const fiber = closure.fiber;
1786 message.handle(ev);1789 message.handle(ev);