authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 21:09:10+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-04 01:35:27+01:00
log0cbaaa5eb9434eb9484bae95949a4086eb472c6a
tree9906a8175864cf8f36657cb78706c3b6cd79440f
parenta0a982f0ea617fa6c74695976b5cfdfd6f9f8b33

std.Thread: stop clobbering syscall args

TL;DR: "r" considered harmful. If LLVM chose registers badly, the inline asm which cleans up a thread on Linux could, on all architectures other than x86_64, clobber either `munmap` argument with the other argument *or* with the syscall number. This would cause munmap to return EINVAL, and we would literally *never* free the thread memory, which isn't ideal. As it turns out, this was happening on MIPS, and was the cause of the failures we've recently been seeing for that target: QEMU genuinely was running out of memory (or at least, the virtualized address space was getting too fragmented to map many contiguous pages). I've therefore re-enabled a test which was disabled due to that flakiness. This bug was accidentally fixed for x86_64 back in 2022 (see 59e33b447), which probably helped it to go unnoticed for as long as it did! Resolves: https://codeberg.org/ziglang/zig/issues/30216

2 files changed, 55 insertions(+), 90 deletions(-)

lib/std/Thread.zig+55-89
......@@ -1227,16 +1227,14 @@ const LinuxThreadImpl = struct {
12271227 switch (target.cpu.arch) {
12281228 .x86 => asm volatile (
12291229 \\ movl $91, %%eax # SYS_munmap
1230 \\ movl %[ptr], %%ebx
1231 \\ movl %[len], %%ecx
12321230 \\ int $128
12331231 \\ movl $1, %%eax # SYS_exit
12341232 \\ movl $0, %%ebx
12351233 \\ int $128
12361234 :
1237 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1238 [len] "r" (self.mapped.len),
1239 : .{ .memory = true }),
1235 : [ptr] "{ebx}" (@intFromPtr(self.mapped.ptr)),
1236 [len] "{ecx}" (self.mapped.len),
1237 ),
12401238 .x86_64 => asm volatile (switch (target.abi) {
12411239 .gnux32, .muslx32 =>
12421240 \\ movl $0x4000000b, %%eax # SYS_munmap
......@@ -1259,88 +1257,74 @@ const LinuxThreadImpl = struct {
12591257 ),
12601258 .arm, .armeb, .thumb, .thumbeb => asm volatile (
12611259 \\ mov r7, #91 // SYS_munmap
1262 \\ mov r0, %[ptr]
1263 \\ mov r1, %[len]
12641260 \\ svc 0
12651261 \\ mov r7, #1 // SYS_exit
12661262 \\ mov r0, #0
12671263 \\ svc 0
12681264 :
1269 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1270 [len] "r" (self.mapped.len),
1271 : .{ .memory = true }),
1265 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1266 [len] "{r1}" (self.mapped.len),
1267 ),
12721268 .aarch64, .aarch64_be => asm volatile (
12731269 \\ mov x8, #215 // SYS_munmap
1274 \\ mov x0, %[ptr]
1275 \\ mov x1, %[len]
12761270 \\ svc 0
12771271 \\ mov x8, #93 // SYS_exit
12781272 \\ mov x0, #0
12791273 \\ svc 0
12801274 :
1281 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1282 [len] "r" (self.mapped.len),
1283 : .{ .memory = true }),
1275 : [ptr] "{x0}" (@intFromPtr(self.mapped.ptr)),
1276 [len] "{x1}" (self.mapped.len),
1277 ),
12841278 .alpha => asm volatile (
12851279 \\ ldi $0, 73 # SYS_munmap
1286 \\ mov %[ptr], $16
1287 \\ mov %[len], $17
12881280 \\ callsys
12891281 \\ ldi $0, 1 # SYS_exit
12901282 \\ ldi $16, 0
12911283 \\ callsys
12921284 :
1293 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1294 [len] "r" (self.mapped.len),
1295 : .{ .memory = true }),
1285 : [ptr] "{r16}" (@intFromPtr(self.mapped.ptr)),
1286 [len] "{r17}" (self.mapped.len),
1287 ),
12961288 .hexagon => asm volatile (
12971289 \\ r6 = #215 // SYS_munmap
1298 \\ r0 = %[ptr]
1299 \\ r1 = %[len]
13001290 \\ trap0(#1)
13011291 \\ r6 = #93 // SYS_exit
13021292 \\ r0 = #0
13031293 \\ trap0(#1)
13041294 :
1305 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1306 [len] "r" (self.mapped.len),
1307 : .{ .memory = true }),
1295 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1296 [len] "{r1}" (self.mapped.len),
1297 ),
13081298 .hppa => asm volatile (
13091299 \\ ldi 91, %%r20 /* SYS_munmap */
1310 \\ copy %[ptr], %%r26
1311 \\ copy %[len], %%r25
13121300 \\ ble 0x100(%%sr2, %%r0)
13131301 \\ ldi 1, %%r20 /* SYS_exit */
13141302 \\ ldi 0, %%r26
13151303 \\ ble 0x100(%%sr2, %%r0)
13161304 :
1317 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1318 [len] "r" (self.mapped.len),
1319 : .{ .memory = true }),
1305 : [ptr] "{r26}" (@intFromPtr(self.mapped.ptr)),
1306 [len] "{r25}" (self.mapped.len),
1307 ),
13201308 .m68k => asm volatile (
13211309 \\ move.l #91, %%d0 // SYS_munmap
1322 \\ move.l %[ptr], %%d1
1323 \\ move.l %[len], %%d2
13241310 \\ trap #0
13251311 \\ move.l #1, %%d0 // SYS_exit
13261312 \\ move.l #0, %%d1
13271313 \\ trap #0
13281314 :
1329 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1330 [len] "r" (self.mapped.len),
1331 : .{ .memory = true }),
1315 : [ptr] "{d1}" (@intFromPtr(self.mapped.ptr)),
1316 [len] "{d2}" (self.mapped.len),
1317 ),
13321318 .microblaze, .microblazeel => asm volatile (
13331319 \\ ori r12, r0, 91 # SYS_munmap
1334 \\ ori r5, %[ptr], 0
1335 \\ ori r6, %[len], 0
13361320 \\ brki r14, 0x8
13371321 \\ ori r12, r0, 1 # SYS_exit
13381322 \\ or r5, r0, r0
13391323 \\ brki r14, 0x8
13401324 :
1341 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1342 [len] "r" (self.mapped.len),
1343 : .{ .memory = true }),
1325 : [ptr] "{r5}" (@intFromPtr(self.mapped.ptr)),
1326 [len] "{r6}" (self.mapped.len),
1327 ),
13441328 // We set `sp` to the address of the current function as a workaround for a Linux
13451329 // kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid.
13461330 // The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in
......@@ -1348,21 +1332,17 @@ const LinuxThreadImpl = struct {
13481332 .mips, .mipsel => asm volatile (
13491333 \\ move $sp, $t9
13501334 \\ li $v0, 4091 # SYS_munmap
1351 \\ move $a0, %[ptr]
1352 \\ move $a1, %[len]
13531335 \\ syscall
13541336 \\ li $v0, 4001 # SYS_exit
13551337 \\ li $a0, 0
13561338 \\ syscall
13571339 :
1358 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1359 [len] "r" (self.mapped.len),
1360 : .{ .memory = true }),
1340 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
1341 [len] "{$5}" (self.mapped.len),
1342 ),
13611343 .mips64, .mips64el => asm volatile (switch (target.abi) {
13621344 .gnuabin32, .muslabin32 =>
13631345 \\ li $v0, 6011 # SYS_munmap
1364 \\ move $a0, %[ptr]
1365 \\ move $a1, %[len]
13661346 \\ syscall
13671347 \\ li $v0, 6058 # SYS_exit
13681348 \\ li $a0, 0
......@@ -1370,8 +1350,6 @@ const LinuxThreadImpl = struct {
13701350 ,
13711351 else =>
13721352 \\ li $v0, 5011 # SYS_munmap
1373 \\ move $a0, %[ptr]
1374 \\ move $a1, %[len]
13751353 \\ syscall
13761354 \\ li $v0, 5058 # SYS_exit
13771355 \\ li $a0, 0
......@@ -1379,60 +1357,50 @@ const LinuxThreadImpl = struct {
13791357 ,
13801358 }
13811359 :
1382 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1383 [len] "r" (self.mapped.len),
1384 : .{ .memory = true }),
1360 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
1361 [len] "{$5}" (self.mapped.len),
1362 ),
13851363 .or1k => asm volatile (
13861364 \\ l.ori r11, r0, 215 # SYS_munmap
1387 \\ l.ori r3, %[ptr]
1388 \\ l.ori r4, %[len]
13891365 \\ l.sys 1
13901366 \\ l.ori r11, r0, 93 # SYS_exit
13911367 \\ l.ori r3, r0, r0
13921368 \\ l.sys 1
13931369 :
1394 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1395 [len] "r" (self.mapped.len),
1396 : .{ .memory = true }),
1370 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
1371 [len] "{r4}" (self.mapped.len),
1372 ),
13971373 .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
13981374 \\ li 0, 91 # SYS_munmap
1399 \\ mr 3, %[ptr]
1400 \\ mr 4, %[len]
14011375 \\ sc
14021376 \\ li 0, 1 # SYS_exit
14031377 \\ li 3, 0
14041378 \\ sc
14051379 \\ blr
14061380 :
1407 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1408 [len] "r" (self.mapped.len),
1409 : .{ .memory = true }),
1381 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
1382 [len] "{r4}" (self.mapped.len),
1383 ),
14101384 .riscv32, .riscv64 => asm volatile (
14111385 \\ li a7, 215 # SYS_munmap
1412 \\ mv a0, %[ptr]
1413 \\ mv a1, %[len]
14141386 \\ ecall
14151387 \\ li a7, 93 # SYS_exit
14161388 \\ mv a0, zero
14171389 \\ ecall
14181390 :
1419 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1420 [len] "r" (self.mapped.len),
1421 : .{ .memory = true }),
1391 : [ptr] "{a0}" (@intFromPtr(self.mapped.ptr)),
1392 [len] "{a1}" (self.mapped.len),
1393 ),
14221394 .s390x => asm volatile (
1423 \\ lgr %%r2, %[ptr]
1424 \\ lgr %%r3, %[len]
14251395 \\ svc 91 # SYS_munmap
14261396 \\ lghi %%r2, 0
14271397 \\ svc 1 # SYS_exit
14281398 :
1429 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1430 [len] "r" (self.mapped.len),
1431 : .{ .memory = true }),
1399 : [ptr] "{r2}" (@intFromPtr(self.mapped.ptr)),
1400 [len] "{r3}" (self.mapped.len),
1401 ),
14321402 .sh, .sheb => asm volatile (
14331403 \\ mov #91, r3 ! SYS_munmap
1434 \\ mov %[ptr], r4
1435 \\ mov %[len], r5
14361404 \\ trapa #31
14371405 \\ or r0, r0
14381406 \\ or r0, r0
......@@ -1448,9 +1416,9 @@ const LinuxThreadImpl = struct {
14481416 \\ or r0, r0
14491417 \\ or r0, r0
14501418 :
1451 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1452 [len] "r" (self.mapped.len),
1453 : .{ .memory = true }),
1419 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
1420 [len] "{r5}" (self.mapped.len),
1421 ),
14541422 .sparc => asm volatile (
14551423 \\ # See sparc64 comments below.
14561424 \\ 1:
......@@ -1460,17 +1428,17 @@ const LinuxThreadImpl = struct {
14601428 \\ ba 1b
14611429 \\ restore
14621430 \\ 2:
1431 \\ mov %%g1, %%o0 // ptr
1432 \\ mov %%g2, %%o1 // len
14631433 \\ mov 73, %%g1 // SYS_munmap
1464 \\ mov %[ptr], %%o0
1465 \\ mov %[len], %%o1
14661434 \\ t 0x3 # ST_FLUSH_WINDOWS
14671435 \\ t 0x10
14681436 \\ mov 1, %%g1 // SYS_exit
14691437 \\ mov 0, %%o0
14701438 \\ t 0x10
14711439 :
1472 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1473 [len] "r" (self.mapped.len),
1440 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1441 [len] "{g2}" (self.mapped.len),
14741442 : .{ .memory = true }),
14751443 .sparc64 => asm volatile (
14761444 \\ # SPARCs really don't like it when active stack frames
......@@ -1484,9 +1452,9 @@ const LinuxThreadImpl = struct {
14841452 \\ ba 1b
14851453 \\ restore
14861454 \\ 2:
1455 \\ mov %%g1, %%o0 // ptr
1456 \\ mov %%g2, %%o1 // len
14871457 \\ mov 73, %%g1 // SYS_munmap
1488 \\ mov %[ptr], %%o0
1489 \\ mov %[len], %%o1
14901458 \\ # Flush register window contents to prevent background
14911459 \\ # memory access before unmapping the stack.
14921460 \\ flushw
......@@ -1495,20 +1463,18 @@ const LinuxThreadImpl = struct {
14951463 \\ mov 0, %%o0
14961464 \\ t 0x6d
14971465 :
1498 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1499 [len] "r" (self.mapped.len),
1466 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1467 [len] "{g2}" (self.mapped.len),
15001468 : .{ .memory = true }),
15011469 .loongarch32, .loongarch64 => asm volatile (
1502 \\ or $a0, $zero, %[ptr]
1503 \\ or $a1, $zero, %[len]
15041470 \\ ori $a7, $zero, 215 # SYS_munmap
15051471 \\ syscall 0 # call munmap
15061472 \\ ori $a0, $zero, 0
15071473 \\ ori $a7, $zero, 93 # SYS_exit
15081474 \\ syscall 0 # call exit
15091475 :
1510 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),
1511 [len] "r" (self.mapped.len),
1476 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
1477 [len] "{r5}" (self.mapped.len),
15121478 : .{ .memory = true }),
15131479 else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
15141480 }
lib/std/http/test.zig-1
......@@ -1164,7 +1164,6 @@ fn createTestServer(io: Io, S: type) !*TestServer {
11641164
11651165test "redirect to different connection" {
11661166 if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/171879
1167 if (builtin.cpu.arch.isMIPS32() and !builtin.link_libc) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30216
11681167
11691168 const io = std.testing.io;
11701169 const test_server_new = try createTestServer(io, struct {