From f838faffe21afd2d5c177c04df8836c93e273b4b Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Fri, 17 Oct 2025 19:53:36 +0000 Subject: [PATCH] runtime: wrap procyield assembly and check for 0 procyield will currently loop infinitely if passed 0 on several platforms. This change sidesteps this bug by renaming procyield to procyieldAsm, and adding a wrapper named procyield that checks for cycles == 0. The benefit of this structure is that procyield called with a constant cycle count of 0 will be inlined and constant folded away, the expected behavior of a procyield of 0 cycles. A follow-up change will fix the assembly to not have this footgun anymore. Change-Id: I7068abfeb961bc0fa475e216836f7c0e46b38373 Reviewed-on: https://go-review.googlesource.com/c/go/+/712663 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Michael Knyszek --- src/runtime/asm_386.s | 2 +- src/runtime/asm_amd64.s | 2 +- src/runtime/asm_arm.s | 2 +- src/runtime/asm_arm64.s | 2 +- src/runtime/asm_loong64.s | 2 +- src/runtime/asm_mips64x.s | 2 +- src/runtime/asm_mipsx.s | 2 +- src/runtime/asm_ppc64x.s | 2 +- src/runtime/asm_riscv64.s | 4 ++-- src/runtime/asm_s390x.s | 2 +- src/runtime/asm_wasm.s | 2 +- src/runtime/stubs.go | 18 +++++++++++++++++- 12 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/runtime/asm_386.s b/src/runtime/asm_386.s index df32e90fda..a54ca110e5 100644 --- a/src/runtime/asm_386.s +++ b/src/runtime/asm_386.s @@ -597,7 +597,7 @@ CALLFN(·call268435456, 268435456) CALLFN(·call536870912, 536870912) CALLFN(·call1073741824, 1073741824) -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 MOVL cycles+0(FP), AX again: PAUSE diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index 525df96f00..dbf54487a7 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -815,7 +815,7 @@ CALLFN(·call268435456, 268435456) CALLFN(·call536870912, 536870912) CALLFN(·call1073741824, 1073741824) -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 MOVL cycles+0(FP), AX again: PAUSE diff --git a/src/runtime/asm_arm.s b/src/runtime/asm_arm.s index d371e80d84..9373846c74 100644 --- a/src/runtime/asm_arm.s +++ b/src/runtime/asm_arm.s @@ -839,7 +839,7 @@ TEXT runtime·memhash32(SB),NOSPLIT|NOFRAME,$0-12 TEXT runtime·memhash64(SB),NOSPLIT|NOFRAME,$0-12 JMP runtime·memhash64Fallback(SB) -TEXT runtime·procyield(SB),NOSPLIT|NOFRAME,$0 +TEXT runtime·procyieldAsm(SB),NOSPLIT|NOFRAME,$0 MOVW cycles+0(FP), R1 MOVW $0, R0 yieldloop: diff --git a/src/runtime/asm_arm64.s b/src/runtime/asm_arm64.s index a0e82ec830..94a5b9ee6c 100644 --- a/src/runtime/asm_arm64.s +++ b/src/runtime/asm_arm64.s @@ -1036,7 +1036,7 @@ aesloop: VMOV V0.D[0], R0 RET -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 MOVWU cycles+0(FP), R0 again: YIELD diff --git a/src/runtime/asm_loong64.s b/src/runtime/asm_loong64.s index 586bf89a5d..a6a5519afb 100644 --- a/src/runtime/asm_loong64.s +++ b/src/runtime/asm_loong64.s @@ -505,7 +505,7 @@ CALLFN(·call268435456, 268435456) CALLFN(·call536870912, 536870912) CALLFN(·call1073741824, 1073741824) -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 RET // Save state of caller into g->sched. diff --git a/src/runtime/asm_mips64x.s b/src/runtime/asm_mips64x.s index d4523b4a74..532eca752f 100644 --- a/src/runtime/asm_mips64x.s +++ b/src/runtime/asm_mips64x.s @@ -408,7 +408,7 @@ CALLFN(·call268435456, 268435456) CALLFN(·call536870912, 536870912) CALLFN(·call1073741824, 1073741824) -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 RET // Save state of caller into g->sched, diff --git a/src/runtime/asm_mipsx.s b/src/runtime/asm_mipsx.s index ec352f5828..2dc3f1c3ad 100644 --- a/src/runtime/asm_mipsx.s +++ b/src/runtime/asm_mipsx.s @@ -406,7 +406,7 @@ CALLFN(·call268435456, 268435456) CALLFN(·call536870912, 536870912) CALLFN(·call1073741824, 1073741824) -TEXT runtime·procyield(SB),NOSPLIT,$0-4 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-4 RET // Save state of caller into g->sched, diff --git a/src/runtime/asm_ppc64x.s b/src/runtime/asm_ppc64x.s index 127010ef97..e461764e55 100644 --- a/src/runtime/asm_ppc64x.s +++ b/src/runtime/asm_ppc64x.s @@ -612,7 +612,7 @@ CALLFN(·call268435456, 268435456) CALLFN(·call536870912, 536870912) CALLFN(·call1073741824, 1073741824) -TEXT runtime·procyield(SB),NOSPLIT|NOFRAME,$0-4 +TEXT runtime·procyieldAsm(SB),NOSPLIT|NOFRAME,$0-4 MOVW cycles+0(FP), R7 // POWER does not have a pause/yield instruction equivalent. // Instead, we can lower the program priority by setting the diff --git a/src/runtime/asm_riscv64.s b/src/runtime/asm_riscv64.s index efe6047419..5bd16181ee 100644 --- a/src/runtime/asm_riscv64.s +++ b/src/runtime/asm_riscv64.s @@ -367,8 +367,8 @@ TEXT gogo<>(SB), NOSPLIT|NOFRAME, $0 MOV gobuf_pc(T0), T0 JALR ZERO, T0 -// func procyield(cycles uint32) -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +// func procyieldAsm(cycles uint32) +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 RET // Switch to m->g0's stack, call fn(g). diff --git a/src/runtime/asm_s390x.s b/src/runtime/asm_s390x.s index 4cc1c0eb10..bb29845f58 100644 --- a/src/runtime/asm_s390x.s +++ b/src/runtime/asm_s390x.s @@ -506,7 +506,7 @@ CALLFN(·call1073741824, 1073741824) TEXT callfnMVC<>(SB),NOSPLIT|NOFRAME,$0-0 MVC $1, 0(R4), 0(R6) -TEXT runtime·procyield(SB),NOSPLIT,$0-0 +TEXT runtime·procyieldAsm(SB),NOSPLIT,$0-0 RET // Save state of caller into g->sched, diff --git a/src/runtime/asm_wasm.s b/src/runtime/asm_wasm.s index 85aa52e0f7..c46cb4ae46 100644 --- a/src/runtime/asm_wasm.s +++ b/src/runtime/asm_wasm.s @@ -200,7 +200,7 @@ TEXT runtime·asminit(SB), NOSPLIT, $0-0 TEXT ·publicationBarrier(SB), NOSPLIT, $0-0 RET -TEXT runtime·procyield(SB), NOSPLIT, $0-0 // FIXME +TEXT runtime·procyieldAsm(SB), NOSPLIT, $0-0 // FIXME RET TEXT runtime·breakpoint(SB), NOSPLIT, $0-0 diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go index 20fc1c59ad..1692283541 100644 --- a/src/runtime/stubs.go +++ b/src/runtime/stubs.go @@ -274,7 +274,23 @@ func reflectcall(stackArgsType *_type, fn, stackArgs unsafe.Pointer, stackArgsSi // See go.dev/issue/67401. // //go:linkname procyield -func procyield(cycles uint32) +//go:nosplit +func procyield(cycles uint32) { + if cycles == 0 { + return + } + procyieldAsm(cycles) +} + +// procyieldAsm is the assembly implementation of procyield. +// +// It may loop infinitely if called with cycles == 0. Prefer +// procyield, which will compile down to nothing in such cases, +// instead. +// +// FIXME: The implementation really should not loop infinitely if +// the number of cycles is 0. +func procyieldAsm(cycles uint32) type neverCallThisFunction struct{} -- 2.52.0