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 {...@@ -1227,16 +1227,14 @@ const LinuxThreadImpl = struct {
1227 switch (target.cpu.arch) {1227 switch (target.cpu.arch) {
1228 .x86 => asm volatile (1228 .x86 => asm volatile (
1229 \\ movl $91, %%eax # SYS_munmap1229 \\ movl $91, %%eax # SYS_munmap
1230 \\ movl %[ptr], %%ebx
1231 \\ movl %[len], %%ecx
1232 \\ int $1281230 \\ int $128
1233 \\ movl $1, %%eax # SYS_exit1231 \\ movl $1, %%eax # SYS_exit
1234 \\ movl $0, %%ebx1232 \\ movl $0, %%ebx
1235 \\ int $1281233 \\ int $128
1236 :1234 :
1237 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1235 : [ptr] "{ebx}" (@intFromPtr(self.mapped.ptr)),
1238 [len] "r" (self.mapped.len),1236 [len] "{ecx}" (self.mapped.len),
1239 : .{ .memory = true }),1237 ),
1240 .x86_64 => asm volatile (switch (target.abi) {1238 .x86_64 => asm volatile (switch (target.abi) {
1241 .gnux32, .muslx32 =>1239 .gnux32, .muslx32 =>
1242 \\ movl $0x4000000b, %%eax # SYS_munmap1240 \\ movl $0x4000000b, %%eax # SYS_munmap
...@@ -1259,88 +1257,74 @@ const LinuxThreadImpl = struct {...@@ -1259,88 +1257,74 @@ const LinuxThreadImpl = struct {
1259 ),1257 ),
1260 .arm, .armeb, .thumb, .thumbeb => asm volatile (1258 .arm, .armeb, .thumb, .thumbeb => asm volatile (
1261 \\ mov r7, #91 // SYS_munmap1259 \\ mov r7, #91 // SYS_munmap
1262 \\ mov r0, %[ptr]
1263 \\ mov r1, %[len]
1264 \\ svc 01260 \\ svc 0
1265 \\ mov r7, #1 // SYS_exit1261 \\ mov r7, #1 // SYS_exit
1266 \\ mov r0, #01262 \\ mov r0, #0
1267 \\ svc 01263 \\ svc 0
1268 :1264 :
1269 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1265 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1270 [len] "r" (self.mapped.len),1266 [len] "{r1}" (self.mapped.len),
1271 : .{ .memory = true }),1267 ),
1272 .aarch64, .aarch64_be => asm volatile (1268 .aarch64, .aarch64_be => asm volatile (
1273 \\ mov x8, #215 // SYS_munmap1269 \\ mov x8, #215 // SYS_munmap
1274 \\ mov x0, %[ptr]
1275 \\ mov x1, %[len]
1276 \\ svc 01270 \\ svc 0
1277 \\ mov x8, #93 // SYS_exit1271 \\ mov x8, #93 // SYS_exit
1278 \\ mov x0, #01272 \\ mov x0, #0
1279 \\ svc 01273 \\ svc 0
1280 :1274 :
1281 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1275 : [ptr] "{x0}" (@intFromPtr(self.mapped.ptr)),
1282 [len] "r" (self.mapped.len),1276 [len] "{x1}" (self.mapped.len),
1283 : .{ .memory = true }),1277 ),
1284 .alpha => asm volatile (1278 .alpha => asm volatile (
1285 \\ ldi $0, 73 # SYS_munmap1279 \\ ldi $0, 73 # SYS_munmap
1286 \\ mov %[ptr], $16
1287 \\ mov %[len], $17
1288 \\ callsys1280 \\ callsys
1289 \\ ldi $0, 1 # SYS_exit1281 \\ ldi $0, 1 # SYS_exit
1290 \\ ldi $16, 01282 \\ ldi $16, 0
1291 \\ callsys1283 \\ callsys
1292 :1284 :
1293 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1285 : [ptr] "{r16}" (@intFromPtr(self.mapped.ptr)),
1294 [len] "r" (self.mapped.len),1286 [len] "{r17}" (self.mapped.len),
1295 : .{ .memory = true }),1287 ),
1296 .hexagon => asm volatile (1288 .hexagon => asm volatile (
1297 \\ r6 = #215 // SYS_munmap1289 \\ r6 = #215 // SYS_munmap
1298 \\ r0 = %[ptr]
1299 \\ r1 = %[len]
1300 \\ trap0(#1)1290 \\ trap0(#1)
1301 \\ r6 = #93 // SYS_exit1291 \\ r6 = #93 // SYS_exit
1302 \\ r0 = #01292 \\ r0 = #0
1303 \\ trap0(#1)1293 \\ trap0(#1)
1304 :1294 :
1305 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1295 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1306 [len] "r" (self.mapped.len),1296 [len] "{r1}" (self.mapped.len),
1307 : .{ .memory = true }),1297 ),
1308 .hppa => asm volatile (1298 .hppa => asm volatile (
1309 \\ ldi 91, %%r20 /* SYS_munmap */1299 \\ ldi 91, %%r20 /* SYS_munmap */
1310 \\ copy %[ptr], %%r26
1311 \\ copy %[len], %%r25
1312 \\ ble 0x100(%%sr2, %%r0)1300 \\ ble 0x100(%%sr2, %%r0)
1313 \\ ldi 1, %%r20 /* SYS_exit */1301 \\ ldi 1, %%r20 /* SYS_exit */
1314 \\ ldi 0, %%r261302 \\ ldi 0, %%r26
1315 \\ ble 0x100(%%sr2, %%r0)1303 \\ ble 0x100(%%sr2, %%r0)
1316 :1304 :
1317 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1305 : [ptr] "{r26}" (@intFromPtr(self.mapped.ptr)),
1318 [len] "r" (self.mapped.len),1306 [len] "{r25}" (self.mapped.len),
1319 : .{ .memory = true }),1307 ),
1320 .m68k => asm volatile (1308 .m68k => asm volatile (
1321 \\ move.l #91, %%d0 // SYS_munmap1309 \\ move.l #91, %%d0 // SYS_munmap
1322 \\ move.l %[ptr], %%d1
1323 \\ move.l %[len], %%d2
1324 \\ trap #01310 \\ trap #0
1325 \\ move.l #1, %%d0 // SYS_exit1311 \\ move.l #1, %%d0 // SYS_exit
1326 \\ move.l #0, %%d11312 \\ move.l #0, %%d1
1327 \\ trap #01313 \\ trap #0
1328 :1314 :
1329 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1315 : [ptr] "{d1}" (@intFromPtr(self.mapped.ptr)),
1330 [len] "r" (self.mapped.len),1316 [len] "{d2}" (self.mapped.len),
1331 : .{ .memory = true }),1317 ),
1332 .microblaze, .microblazeel => asm volatile (1318 .microblaze, .microblazeel => asm volatile (
1333 \\ ori r12, r0, 91 # SYS_munmap1319 \\ ori r12, r0, 91 # SYS_munmap
1334 \\ ori r5, %[ptr], 0
1335 \\ ori r6, %[len], 0
1336 \\ brki r14, 0x81320 \\ brki r14, 0x8
1337 \\ ori r12, r0, 1 # SYS_exit1321 \\ ori r12, r0, 1 # SYS_exit
1338 \\ or r5, r0, r01322 \\ or r5, r0, r0
1339 \\ brki r14, 0x81323 \\ brki r14, 0x8
1340 :1324 :
1341 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1325 : [ptr] "{r5}" (@intFromPtr(self.mapped.ptr)),
1342 [len] "r" (self.mapped.len),1326 [len] "{r6}" (self.mapped.len),
1343 : .{ .memory = true }),1327 ),
1344 // We set `sp` to the address of the current function as a workaround for a Linux1328 // We set `sp` to the address of the current function as a workaround for a Linux
1345 // kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid.1329 // kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid.
1346 // The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in1330 // The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in
...@@ -1348,21 +1332,17 @@ const LinuxThreadImpl = struct {...@@ -1348,21 +1332,17 @@ const LinuxThreadImpl = struct {
1348 .mips, .mipsel => asm volatile (1332 .mips, .mipsel => asm volatile (
1349 \\ move $sp, $t91333 \\ move $sp, $t9
1350 \\ li $v0, 4091 # SYS_munmap1334 \\ li $v0, 4091 # SYS_munmap
1351 \\ move $a0, %[ptr]
1352 \\ move $a1, %[len]
1353 \\ syscall1335 \\ syscall
1354 \\ li $v0, 4001 # SYS_exit1336 \\ li $v0, 4001 # SYS_exit
1355 \\ li $a0, 01337 \\ li $a0, 0
1356 \\ syscall1338 \\ syscall
1357 :1339 :
1358 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1340 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
1359 [len] "r" (self.mapped.len),1341 [len] "{$5}" (self.mapped.len),
1360 : .{ .memory = true }),1342 ),
1361 .mips64, .mips64el => asm volatile (switch (target.abi) {1343 .mips64, .mips64el => asm volatile (switch (target.abi) {
1362 .gnuabin32, .muslabin32 =>1344 .gnuabin32, .muslabin32 =>
1363 \\ li $v0, 6011 # SYS_munmap1345 \\ li $v0, 6011 # SYS_munmap
1364 \\ move $a0, %[ptr]
1365 \\ move $a1, %[len]
1366 \\ syscall1346 \\ syscall
1367 \\ li $v0, 6058 # SYS_exit1347 \\ li $v0, 6058 # SYS_exit
1368 \\ li $a0, 01348 \\ li $a0, 0
...@@ -1370,8 +1350,6 @@ const LinuxThreadImpl = struct {...@@ -1370,8 +1350,6 @@ const LinuxThreadImpl = struct {
1370 ,1350 ,
1371 else =>1351 else =>
1372 \\ li $v0, 5011 # SYS_munmap1352 \\ li $v0, 5011 # SYS_munmap
1373 \\ move $a0, %[ptr]
1374 \\ move $a1, %[len]
1375 \\ syscall1353 \\ syscall
1376 \\ li $v0, 5058 # SYS_exit1354 \\ li $v0, 5058 # SYS_exit
1377 \\ li $a0, 01355 \\ li $a0, 0
...@@ -1379,60 +1357,50 @@ const LinuxThreadImpl = struct {...@@ -1379,60 +1357,50 @@ const LinuxThreadImpl = struct {
1379 ,1357 ,
1380 }1358 }
1381 :1359 :
1382 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1360 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
1383 [len] "r" (self.mapped.len),1361 [len] "{$5}" (self.mapped.len),
1384 : .{ .memory = true }),1362 ),
1385 .or1k => asm volatile (1363 .or1k => asm volatile (
1386 \\ l.ori r11, r0, 215 # SYS_munmap1364 \\ l.ori r11, r0, 215 # SYS_munmap
1387 \\ l.ori r3, %[ptr]
1388 \\ l.ori r4, %[len]
1389 \\ l.sys 11365 \\ l.sys 1
1390 \\ l.ori r11, r0, 93 # SYS_exit1366 \\ l.ori r11, r0, 93 # SYS_exit
1391 \\ l.ori r3, r0, r01367 \\ l.ori r3, r0, r0
1392 \\ l.sys 11368 \\ l.sys 1
1393 :1369 :
1394 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1370 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
1395 [len] "r" (self.mapped.len),1371 [len] "{r4}" (self.mapped.len),
1396 : .{ .memory = true }),1372 ),
1397 .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (1373 .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
1398 \\ li 0, 91 # SYS_munmap1374 \\ li 0, 91 # SYS_munmap
1399 \\ mr 3, %[ptr]
1400 \\ mr 4, %[len]
1401 \\ sc1375 \\ sc
1402 \\ li 0, 1 # SYS_exit1376 \\ li 0, 1 # SYS_exit
1403 \\ li 3, 01377 \\ li 3, 0
1404 \\ sc1378 \\ sc
1405 \\ blr1379 \\ blr
1406 :1380 :
1407 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1381 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
1408 [len] "r" (self.mapped.len),1382 [len] "{r4}" (self.mapped.len),
1409 : .{ .memory = true }),1383 ),
1410 .riscv32, .riscv64 => asm volatile (1384 .riscv32, .riscv64 => asm volatile (
1411 \\ li a7, 215 # SYS_munmap1385 \\ li a7, 215 # SYS_munmap
1412 \\ mv a0, %[ptr]
1413 \\ mv a1, %[len]
1414 \\ ecall1386 \\ ecall
1415 \\ li a7, 93 # SYS_exit1387 \\ li a7, 93 # SYS_exit
1416 \\ mv a0, zero1388 \\ mv a0, zero
1417 \\ ecall1389 \\ ecall
1418 :1390 :
1419 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1391 : [ptr] "{a0}" (@intFromPtr(self.mapped.ptr)),
1420 [len] "r" (self.mapped.len),1392 [len] "{a1}" (self.mapped.len),
1421 : .{ .memory = true }),1393 ),
1422 .s390x => asm volatile (1394 .s390x => asm volatile (
1423 \\ lgr %%r2, %[ptr]
1424 \\ lgr %%r3, %[len]
1425 \\ svc 91 # SYS_munmap1395 \\ svc 91 # SYS_munmap
1426 \\ lghi %%r2, 01396 \\ lghi %%r2, 0
1427 \\ svc 1 # SYS_exit1397 \\ svc 1 # SYS_exit
1428 :1398 :
1429 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1399 : [ptr] "{r2}" (@intFromPtr(self.mapped.ptr)),
1430 [len] "r" (self.mapped.len),1400 [len] "{r3}" (self.mapped.len),
1431 : .{ .memory = true }),1401 ),
1432 .sh, .sheb => asm volatile (1402 .sh, .sheb => asm volatile (
1433 \\ mov #91, r3 ! SYS_munmap1403 \\ mov #91, r3 ! SYS_munmap
1434 \\ mov %[ptr], r4
1435 \\ mov %[len], r5
1436 \\ trapa #311404 \\ trapa #31
1437 \\ or r0, r01405 \\ or r0, r0
1438 \\ or r0, r01406 \\ or r0, r0
...@@ -1448,9 +1416,9 @@ const LinuxThreadImpl = struct {...@@ -1448,9 +1416,9 @@ const LinuxThreadImpl = struct {
1448 \\ or r0, r01416 \\ or r0, r0
1449 \\ or r0, r01417 \\ or r0, r0
1450 :1418 :
1451 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1419 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
1452 [len] "r" (self.mapped.len),1420 [len] "{r5}" (self.mapped.len),
1453 : .{ .memory = true }),1421 ),
1454 .sparc => asm volatile (1422 .sparc => asm volatile (
1455 \\ # See sparc64 comments below.1423 \\ # See sparc64 comments below.
1456 \\ 1:1424 \\ 1:
...@@ -1460,17 +1428,17 @@ const LinuxThreadImpl = struct {...@@ -1460,17 +1428,17 @@ const LinuxThreadImpl = struct {
1460 \\ ba 1b1428 \\ ba 1b
1461 \\ restore1429 \\ restore
1462 \\ 2:1430 \\ 2:
1431 \\ mov %%g1, %%o0 // ptr
1432 \\ mov %%g2, %%o1 // len
1463 \\ mov 73, %%g1 // SYS_munmap1433 \\ mov 73, %%g1 // SYS_munmap
1464 \\ mov %[ptr], %%o0
1465 \\ mov %[len], %%o1
1466 \\ t 0x3 # ST_FLUSH_WINDOWS1434 \\ t 0x3 # ST_FLUSH_WINDOWS
1467 \\ t 0x101435 \\ t 0x10
1468 \\ mov 1, %%g1 // SYS_exit1436 \\ mov 1, %%g1 // SYS_exit
1469 \\ mov 0, %%o01437 \\ mov 0, %%o0
1470 \\ t 0x101438 \\ t 0x10
1471 :1439 :
1472 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1440 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1473 [len] "r" (self.mapped.len),1441 [len] "{g2}" (self.mapped.len),
1474 : .{ .memory = true }),1442 : .{ .memory = true }),
1475 .sparc64 => asm volatile (1443 .sparc64 => asm volatile (
1476 \\ # SPARCs really don't like it when active stack frames1444 \\ # SPARCs really don't like it when active stack frames
...@@ -1484,9 +1452,9 @@ const LinuxThreadImpl = struct {...@@ -1484,9 +1452,9 @@ const LinuxThreadImpl = struct {
1484 \\ ba 1b1452 \\ ba 1b
1485 \\ restore1453 \\ restore
1486 \\ 2:1454 \\ 2:
1455 \\ mov %%g1, %%o0 // ptr
1456 \\ mov %%g2, %%o1 // len
1487 \\ mov 73, %%g1 // SYS_munmap1457 \\ mov 73, %%g1 // SYS_munmap
1488 \\ mov %[ptr], %%o0
1489 \\ mov %[len], %%o1
1490 \\ # Flush register window contents to prevent background1458 \\ # Flush register window contents to prevent background
1491 \\ # memory access before unmapping the stack.1459 \\ # memory access before unmapping the stack.
1492 \\ flushw1460 \\ flushw
...@@ -1495,20 +1463,18 @@ const LinuxThreadImpl = struct {...@@ -1495,20 +1463,18 @@ const LinuxThreadImpl = struct {
1495 \\ mov 0, %%o01463 \\ mov 0, %%o0
1496 \\ t 0x6d1464 \\ t 0x6d
1497 :1465 :
1498 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1466 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1499 [len] "r" (self.mapped.len),1467 [len] "{g2}" (self.mapped.len),
1500 : .{ .memory = true }),1468 : .{ .memory = true }),
1501 .loongarch32, .loongarch64 => asm volatile (1469 .loongarch32, .loongarch64 => asm volatile (
1502 \\ or $a0, $zero, %[ptr]
1503 \\ or $a1, $zero, %[len]
1504 \\ ori $a7, $zero, 215 # SYS_munmap1470 \\ ori $a7, $zero, 215 # SYS_munmap
1505 \\ syscall 0 # call munmap1471 \\ syscall 0 # call munmap
1506 \\ ori $a0, $zero, 01472 \\ ori $a0, $zero, 0
1507 \\ ori $a7, $zero, 93 # SYS_exit1473 \\ ori $a7, $zero, 93 # SYS_exit
1508 \\ syscall 0 # call exit1474 \\ syscall 0 # call exit
1509 :1475 :
1510 : [ptr] "r" (@intFromPtr(self.mapped.ptr)),1476 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
1511 [len] "r" (self.mapped.len),1477 [len] "{r5}" (self.mapped.len),
1512 : .{ .memory = true }),1478 : .{ .memory = true }),
1513 else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),1479 else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
1514 }1480 }
lib/std/http/test.zig-1
...@@ -1164,7 +1164,6 @@ fn createTestServer(io: Io, S: type) !*TestServer {...@@ -1164,7 +1164,6 @@ fn createTestServer(io: Io, S: type) !*TestServer {
11641164
1165test "redirect to different connection" {1165test "redirect to different connection" {
1166 if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/1718791166 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
1169 const io = std.testing.io;1168 const io = std.testing.io;
1170 const test_server_new = try createTestServer(io, struct {1169 const test_server_new = try createTestServer(io, struct {