diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 856f41090a..e6c524da7c 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1874,16 +1874,18 @@ func (ctxt *Link) hostlink() { // Since GCC version 11, the 64-bit version of GCC starting files // are now suffixed by "_64". Even under "-maix64" multilib directory // "crtcxa.o" is 32-bit. - crtcxa := getPathFile("crtcxa_64.o") - if !filepath.IsAbs(crtcxa) { - crtcxa = getPathFile("crtcxa.o") - } - crtdbase := getPathFile("crtdbase_64.o") - if !filepath.IsAbs(crtdbase) { - crtdbase = getPathFile("crtdbase.o") + if ctxt.BuildMode != BuildModeCShared { + crtcxa := getPathFile("crtcxa_64.o") + if !filepath.IsAbs(crtcxa) { + crtcxa = getPathFile("crtcxa.o") + } + crtdbase := getPathFile("crtdbase_64.o") + if !filepath.IsAbs(crtdbase) { + crtdbase = getPathFile("crtdbase.o") + } + argv = append(argv, crtcxa) + argv = append(argv, crtdbase) } - argv = append(argv, crtcxa) - argv = append(argv, crtdbase) } if ctxt.linkShared { diff --git a/src/runtime/asm_ppc64x.s b/src/runtime/asm_ppc64x.s index 5d46c4a1b1..aed390061a 100644 --- a/src/runtime/asm_ppc64x.s +++ b/src/runtime/asm_ppc64x.s @@ -35,10 +35,19 @@ TEXT _rt0_ppc64x_lib(SB),NOSPLIT|NOFRAME,$0 // See runtime/cgo/gcc_aix_ppc64.c MOVBZ runtime·isarchive(SB), R3 // Check buildmode = c-archive CMP $0, R3 - BNE continue_init + BNE check_guard MOVBZ runtime·islibrary(SB), R3 // Check buildmode = c-shared CMP $0, R3 - BEQ done // If neither, exit + BEQ done // If neither c-archive nor c-shared, exit +check_guard: + // One-shot guard: only initialize once. Without this, every C→Go + // callback re-enters this path and spawns a new OS thread via + // _cgo_sys_thread_create, leaking a goroutine and heap memory per call. + MOVBZ runtime·aixLibraryInited(SB), R3 + CMP $0, R3 + BNE done // Already initialized — bail out immediately + MOVD $1, R3 + MOVB R3, runtime·aixLibraryInited(SB) continue_init: #endif @@ -844,7 +853,13 @@ loadg: CMP R3, $0 BEQ nocgo #endif +#ifdef GOOS_aix + // On AIX c-shared, the C runtime may overwrite the tls_g slot between + // callbacks. Use the pthread-key fallback to recover g when tls_g is nil. + BL runtime·load_g_from_pthread(SB) +#else BL runtime·load_g(SB) +#endif nocgo: // If g is nil, Go did not create the current thread, diff --git a/src/runtime/cgo.go b/src/runtime/cgo.go index 60f2403d73..85989ace00 100644 --- a/src/runtime/cgo.go +++ b/src/runtime/cgo.go @@ -21,6 +21,7 @@ import "unsafe" //go:linkname _cgo_yield _cgo_yield //go:linkname _cgo_pthread_key_created _cgo_pthread_key_created //go:linkname _cgo_bindm _cgo_bindm +//go:linkname _cgo_get_g _cgo_get_g //go:linkname _cgo_getstackbound _cgo_getstackbound var ( @@ -35,6 +36,7 @@ var ( _cgo_yield unsafe.Pointer _cgo_pthread_key_created unsafe.Pointer _cgo_bindm unsafe.Pointer + _cgo_get_g unsafe.Pointer _cgo_getstackbound unsafe.Pointer ) diff --git a/src/runtime/cgo/callbacks.go b/src/runtime/cgo/callbacks.go index 986f61914f..612be8b21b 100644 --- a/src/runtime/cgo/callbacks.go +++ b/src/runtime/cgo/callbacks.go @@ -107,6 +107,15 @@ var _set_crosscall2 = set_crosscall2 var x_cgo_bindm byte var _cgo_bindm = &x_cgo_bindm +// Retrieve the g pointer stored by x_cgo_bindm for the current thread. +// Used on AIX c-shared where tls_g is reset by the C runtime between callbacks. + +//go:cgo_import_static x_cgo_get_g +//go:linkname x_cgo_get_g x_cgo_get_g +//go:linkname _cgo_get_g _cgo_get_g +var x_cgo_get_g byte +var _cgo_get_g = &x_cgo_get_g + // Notifies that the runtime has been initialized. // // We currently block at every CGO entry point (via _cgo_wait_runtime_init_done) diff --git a/src/runtime/cgo/gcc_libinit.c b/src/runtime/cgo/gcc_libinit.c index 05998fadf8..51e817e852 100644 --- a/src/runtime/cgo/gcc_libinit.c +++ b/src/runtime/cgo/gcc_libinit.c @@ -135,6 +135,17 @@ void x_cgo_bindm(void* g) { pthread_setspecific(pthread_g, g); } +// x_cgo_get_g retrieves the g pointer previously stored by x_cgo_bindm for +// this thread. Used on AIX in c-shared mode where the Go TLS slot (tls_g) +// can be overwritten by the C runtime between callbacks, but the pthread +// specific value survives intact. +void* x_cgo_get_g(void) { + if (x_cgo_pthread_key_created == 0) { + return NULL; + } + return pthread_getspecific(pthread_g); +} + void x_cgo_notify_runtime_init_done(void* dummy __attribute__ ((unused))) { pthread_mutex_lock(&runtime_init_mu); diff --git a/src/runtime/os_aix.go b/src/runtime/os_aix.go index 4bb8576f42..0ef73941f1 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -155,6 +155,13 @@ func newosproc0(stacksize uintptr, fn *funcDescriptor) { } +// aixLibraryInited is set to 1 the first time _rt0_ppc64x_lib runs the +// initialization path (c-archive or c-shared buildmode). All subsequent +// entries into _rt0_ppc64x_lib must bail out immediately to prevent +// spawning a new OS thread and goroutine on every C→Go callback. +// Checked in asm_ppc64x.s before libpreinit / _cgo_sys_thread_create. +var aixLibraryInited uint8 + // Called to do synchronous initialization of Go code built with // -buildmode=c-archive or -buildmode=c-shared. // None of the Go runtime is initialized. diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 005c875cbf..19d6f3d4e3 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -2481,7 +2481,11 @@ func needm(signal bool) { func needAndBindM() { needm(false) - if _cgo_pthread_key_created != nil && *(*uintptr)(_cgo_pthread_key_created) != 0 { + keyCreated := uintptr(0) + if _cgo_pthread_key_created != nil { + keyCreated = *(*uintptr)(_cgo_pthread_key_created) + } + if keyCreated != 0 { cgoBindM() } } diff --git a/src/runtime/stubs_ppc64x.go b/src/runtime/stubs_ppc64x.go index dbc82c8453..4637e8cce7 100644 --- a/src/runtime/stubs_ppc64x.go +++ b/src/runtime/stubs_ppc64x.go @@ -13,6 +13,10 @@ func load_g() func save_g() func reginit() +// load_g_from_pthread recovers g via pthread_getspecific on AIX c-shared, +// where the C runtime resets tls_g between callbacks. AIX only. +func load_g_from_pthread() + //go:noescape func asmcgocall_no_g(fn, arg unsafe.Pointer) diff --git a/src/runtime/sys_aix_ppc64.s b/src/runtime/sys_aix_ppc64.s index 2b2b4090c4..2006d3cbdc 100644 --- a/src/runtime/sys_aix_ppc64.s +++ b/src/runtime/sys_aix_ppc64.s @@ -342,3 +342,74 @@ TEXT runtime·callCgoNoG(SB),NOSPLIT,$256-64 MOVD 40(R1), R2 // Restore Go's TOC MOVD R3, ret+56(FP) // Return value RET + +// load_g_from_pthread recovers g for this thread by calling x_cgo_get_g +// (pthread_getspecific) when the AIX C runtime has reset tls_g between +// c-shared callbacks. Used from cgocallback on AIX only. +// +// The pthread fallback is only needed in c-shared mode. For regular binaries +// and c-archive, tls_g works normally via load_g — falling back to +// pthread_getspecific would return nil (the key was never set) and falsely +// treat the calling M as unbound, triggering needm and blocking on +// main_init_done. Guard with islibrary at runtime. +// +// We never access tls_g directly here — that symbol is declared TLSBSS only +// in tls_ppc64x.s; the assembler compiling this file would not know its type, +// causing R_ADDR instead of R_POWER_TLS_IE. For non-c-shared we BL to +// load_g (defined in tls_ppc64x.s) which handles TLS correctly. +// +// On entry: no g register loaded. Uses R3/R12 as scratch. +// On exit: g register set (may still be nil if not yet bound). +// Frame: 48-byte AIX fixed frame = $48. +TEXT runtime·load_g_from_pthread(SB),NOSPLIT,$48-0 + // Only use the pthread fallback in c-shared mode. + // In all other build modes, tls_g is reliable — use load_g directly. + MOVBZ runtime·islibrary(SB), R3 + CMP R3, $0 + BEQ load_g_std // not c-shared: delegate to normal load_g + + // c-shared path: tls_g has been reset by the AIX C runtime. + // Recover g via pthread_getspecific(pthread_g). + // + // _cgo_get_g is a Go unsafe.Pointer variable (cgo.go) whose value at + // runtime is &x_cgo_get_g — the address of the C function descriptor + // (gcc_libinit.c). Calling it returns pthread_getspecific(pthread_g). + // + // Call convention with GO_PPC64X_HAS_FUNCDESC: + // R12 = descriptor address + // MOVD 8(R12), R2 → TOC ptr + // MOVD (R12), R12 → code ptr + // MOVD R12, CTR; BL (CTR) + MOVD _cgo_get_g(SB), R12 // R12 = &x_cgo_get_g = C descriptor address + CMP R12, $0 + BEQ load_g_done // cgo not linked in + + MOVD LR, R0 + MOVD R0, 16(R1) // save LR (AIX ABI fixed-frame slot) + MOVD R2, 40(R1) // save Go TOC + + MOVD 8(R12), R2 // TOC pointer from descriptor + MOVD (R12), R12 // code pointer from descriptor + MOVD R12, CTR + BL (CTR) // R3 = x_cgo_get_g() = pthread_getspecific(pthread_g) + + MOVD 40(R1), R2 // restore Go TOC + MOVD 16(R1), R0 // restore LR + MOVD R0, LR + XOR R0, R0 // restore R0=0 (Go ABI invariant) + + // R3 = g0 stored by cgoBindM, or nil if this thread is not yet bound. + MOVD R3, g + RET + +load_g_std: + // Not c-shared: tls_g is reliable. Call the standard load_g and return. + // Use BL+RET rather than a tail-call BR: load_g_from_pthread has a $48 + // frame (needed for the c-shared path), and a tail-branch from a framed + // function produces incorrect LR/SP state on AIX FUNCDESC targets. + BL runtime·load_g(SB) + // fall through + +load_g_done: + RET +