Commit 415a415381b for php.net

commit 415a415381b205c59dd3efe7585698770b492bf3
Author: Calvin Buckley <calvinb@php.net>
Date:   Fri Jul 31 16:26:47 2026 -0300

    zend_call_stack.c: AIX backend (#22906)

    Uses pthread_getthrds_np (so requires pthread linked, by default on ZTS,
    needs to be added for NTS). The stack size part is weird, but works in
    testing.

    An alternative approach is using procfs, but I didn't bother with this
    due to it not working on PASE (and the pthread approach works for the
    main thread too).

diff --git a/Zend/Zend.m4 b/Zend/Zend.m4
index 5e69a97db19..41da8344bf1 100644
--- a/Zend/Zend.m4
+++ b/Zend/Zend.m4
@@ -148,6 +148,7 @@ AC_CHECK_FUNCS(m4_normalize([
   pthread_attr_getstack
   pthread_get_stackaddr_np
   pthread_getattr_np
+  pthread_getthrds_np
   pthread_stackseg_np
   strnlen
 ]))
diff --git a/Zend/tests/stack_limit/stack_limit_010.phpt b/Zend/tests/stack_limit/stack_limit_010.phpt
index 85736423e41..9c9295c65b6 100644
--- a/Zend/tests/stack_limit/stack_limit_010.phpt
+++ b/Zend/tests/stack_limit/stack_limit_010.phpt
@@ -4,6 +4,9 @@
 zend_test
 --SKIPIF--
 <?php
+if (PHP_OS_FAMILY == "AIX") {
+    die("skip AIX adjusts top of stack (used for size) in unpredictable way");
+}
 if (!function_exists('zend_test_zend_call_stack_get')) die("skip zend_test_zend_call_stack_get() is not available");
 if (!getenv('STACK_LIMIT_DEFAULTS_CHECK')) { die('skip STACK_LIMIT_DEFAULTS_CHECK not set'); }
 ?>
diff --git a/Zend/zend_call_stack.c b/Zend/zend_call_stack.c
index aa23c2e3e2f..40f0c7e549c 100644
--- a/Zend/zend_call_stack.c
+++ b/Zend/zend_call_stack.c
@@ -36,7 +36,8 @@
 #endif /* ZEND_WIN32 */
 #if (defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)) || \
     defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || \
-    defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun)
+    defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun) || \
+    defined(_AIX)
 # include <pthread.h>
 #endif
 #if defined(__FreeBSD__) || defined(__DragonFly__)
@@ -788,6 +789,79 @@ static bool zend_call_stack_get_solaris(zend_call_stack *stack)
 }
 #endif /* defined(__sun) */

+#if defined(_AIX)
+static bool zend_call_stack_get_aix_pthread(zend_call_stack *stack)
+{
+#ifdef HAVE_PTHREAD_GETTHRDS_NP
+	pthread_t pt = pthread_self();
+	struct __pthrdsinfo thread_info = {0};
+	/*
+	 * We don't need the register buffer since we only call the function
+	 * on our own thread, and since the register buffer is only used for
+	 * suspended threads...
+	 */
+	int regsz = 0;
+
+	if (pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_ALL, &thread_info,
+				sizeof(thread_info), NULL, &regsz)) {
+		return false;
+	}
+
+	/*
+	 * These can be null in rare situations, allegedly with user-provided
+	 * stacks with pthread (according to OpenJDK)
+	 */
+	if (!(thread_info.__pi_stackend && thread_info.__pi_stackaddr)) {
+		return false;
+	}
+
+	/*
+	 * The top of the stack (stackend) is not page aligned, there's some
+	 * internal stuff above it. Thankfully, we don't need page alignment.
+	 *
+	 * The size is a little weird. The stacksize field for child threads
+	 * is smaller than subtracting the bottom (stackaddr) from the top;
+	 * it's about 0x888 to 0x1888 above stackaddr. I'm assuming it rounds
+	 * the bottom of the stack to page alignment? The main thread size is
+	 * the same as end - addr, but it is variable between systems; also
+	 * assuming there's stuff at the top of the stack that gets taken off,
+	 * regardless of maximum declared size.
+	 *
+	 * A somewhat crude diagram is available here:
+	 * https://www.ibm.com/docs/en/aix/7.2.0?topic=tuning-thread-environment-variables
+	 *
+	 * pthread->pt_stk.st_limit is __pi_stackend,
+	 * above that is internal pthread junk close to the end of page
+	 * pthread->pt_stk.st_base is __pi_stackaddr,
+	 * below that is the red zone
+	 */
+	stack->base = thread_info.__pi_stackend;
+	stack->max_size = thread_info.__pi_stackend - thread_info.__pi_stackaddr;
+	return true;
+#else
+	/* pthread likely not linked in; default NTS build behaviour */
+	return false;
+#endif
+}
+
+static bool zend_call_stack_get_aix(zend_call_stack *stack)
+{
+	/*
+	 * While we could use /proc on AIX (and the implementation basically
+	 * like the Solaris one, as the procfs is similar), it doesn't work on
+	 * PASE. The pthread API works even on the main thread, so we should
+	 * use it when we have pthread linked (always with ZTS, maybe not with
+	 * NTS builds).
+	 */
+	return zend_call_stack_get_aix_pthread(stack);
+}
+#else
+static bool zend_call_stack_get_aix(zend_call_stack *stack)
+{
+	return false;
+}
+#endif /* defined(_AIX) */
+
 /** Get the stack information for the calling thread */
 ZEND_API bool zend_call_stack_get(zend_call_stack *stack)
 {
@@ -823,6 +897,10 @@ ZEND_API bool zend_call_stack_get(zend_call_stack *stack)
 		return true;
 	}

+	if (zend_call_stack_get_aix(stack)) {
+		return true;
+	}
+
 	return false;
 }

diff --git a/Zend/zend_call_stack.h b/Zend/zend_call_stack.h
index c0b84334239..f70b9edae30 100644
--- a/Zend/zend_call_stack.h
+++ b/Zend/zend_call_stack.h
@@ -101,6 +101,32 @@ static inline size_t zend_call_stack_default_size(void)
 #ifdef __sun
 	return 8 * 4096;
 #endif
+#ifdef _AIX
+	/*
+	 * default pthread stack limit is 96 KB on 32-bit, 192 KB on 64-bit
+	 * https://www.ibm.com/docs/en/aix/7.1.0?topic=programming-threads-library-options
+	 */
+#ifdef HAVE_PTHREAD_GETTHRDS_NP /* if we have pthread linked (not in libc) */
+	if (pthread_self() != 1) {
+#ifdef __64BIT__
+		return 192 * 1024;
+#else
+		return 96 * 1024;
+#endif
+	}
+#endif
+	/*
+	 * default AIX ulimit -s value is allegedly 32 MB, default values per:
+	 * https://www.ibm.com/docs/en/aix/7.1.0?topic=u-ulimit-command
+	 * N.B.: the the file uses 512b blocks, but the command uses kilobytes
+	 * https://www.ibm.com/support/pages/ibm-aix-security-ulimit-and-ulimit-d-output-differs-value-etcsecuritylimits
+	 *
+	 * note PASE uses an unlimited stack size by default, which is capped
+	 * at the PowerPC segment size (256 MB)
+	 */
+	return 32 * 1024 * 1024;
+#endif
+

 	return 2 * 1024 * 1024;
 }