[php-src] master: TSRM: make CG, EG, SCNG and AG compile-time offsets

henderkes via Arnaud Le Blanc <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: henderkes (henderkes)
Committer: Arnaud Le Blanc (arnaud-lb)
Date: 2026-06-29T10:51:50+02:00

Commit: https://github.com/php/php-src/commit/f785614f5164ee21128a47760d16bdb377052cc6
Raw diff: https://github.com/php/php-src/commit/f785614f5164ee21128a47760d16bdb377052cc6.diff

TSRM: make CG, EG, SCNG and AG compile-time offsets

Saves an offset load every time they're accessed

Closes GH-22287

Changed paths:
  M  NEWS
  M  TSRM/TSRM.c
  M  TSRM/TSRM.h
  M  Zend/zend.c
  M  Zend/zend_alloc.c
  M  Zend/zend_globals.h
  M  Zend/zend_globals_macros.h
  M  main/main.c


Diff:

diff --git a/NEWS b/NEWS
index a4e0e3ba4f03..91e7169f98fd 100644
--- a/NEWS
+++ b/NEWS
@@ -36,6 +36,7 @@ PHP                                                                        NEWS
   . perf: make all static extensions use TSRMG_STATIC. (henderkes)
   . Fixed bug GH-22257 (type confusion in Exception::getTraceAsString()).
     (David Carlier)
+  . TSRM: make CG, EG, SCNG and AG compile-time offsets. (henderkes)
 
 - BCMath:
   . Added NUL-byte validation to BCMath functions. (jorgsowa)
diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c
index e99993204b6f..4222e88755d6 100644
--- a/TSRM/TSRM.c
+++ b/TSRM/TSRM.c
@@ -36,12 +36,11 @@ struct _tsrm_tls_entry {
 	tsrm_tls_entry *next;
 };
 
-
 typedef struct {
 	size_t size;
 	ts_allocate_ctor ctor;
 	ts_allocate_dtor dtor;
-	size_t fast_offset;
+	ptrdiff_t fast_offset;
 	int done;
 } tsrm_resource_type;
 
@@ -58,6 +57,7 @@ static int					resource_types_table_size;
 /* Reserved space for fast globals access */
 static size_t tsrm_reserved_pos  = 0;
 static size_t tsrm_reserved_size = 0;
+static size_t tsrm_reserved_front = 0;
 
 static MUTEX_T tsmm_mutex;	  /* thread-safe memory manager mutex */
 static MUTEX_T tsrm_env_mutex; /* tsrm environ mutex */
@@ -155,6 +155,7 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb
 
 	tsrm_reserved_pos  = 0;
 	tsrm_reserved_size = 0;
+	tsrm_reserved_front = 0;
 
 	tsrm_env_mutex = tsrm_mutex_alloc();
 
@@ -205,7 +206,7 @@ TSRM_API void tsrm_shutdown(void)
 			} else {
 				free(p->storage);
 			}
-			free(p);
+			free((char *) p - tsrm_reserved_front);
 			p = next_p;
 		}
 	}
@@ -232,6 +233,7 @@ TSRM_API void tsrm_shutdown(void)
 
 	tsrm_reserved_pos  = 0;
 	tsrm_reserved_size = 0;
+	tsrm_reserved_front = 0;
 }/*}}}*/
 
 /* {{{ */
@@ -319,17 +321,20 @@ TSRM_API void tsrm_reserve(size_t size)
 }/*}}}*/
 
 
+/* Carve a fixed-offset front region out of the reserved space. It is placed
+ * before the TLS entry, so the hot globals get compile-time-constant negative
+ * offsets from the cache pointer. */
+TSRM_API void tsrm_reserve_fast_front(size_t size)
+{
+	tsrm_reserved_front = TSRM_ALIGNED_SIZE(size);
+	tsrm_reserved_size -= tsrm_reserved_front;
+}
+
+
 /* allocates a new fast thread-safe-resource id */
 TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
 {/*{{{*/
-	TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new fast resource id, %d bytes", size));
-
 	tsrm_mutex_lock(tsmm_mutex);
-
-	/* obtain a resource id */
-	*rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);
-	TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id));
-
 	size = TSRM_ALIGNED_SIZE(size);
 	if (tsrm_reserved_size - tsrm_reserved_pos < size) {
 		TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate space for fast resource"));
@@ -338,9 +343,26 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz
 		tsrm_mutex_unlock(tsmm_mutex);
 		return 0;
 	}
-
-	*offset = TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_pos;
+	ptrdiff_t fixed_offset = TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_pos;
 	tsrm_reserved_pos += size;
+	tsrm_mutex_unlock(tsmm_mutex);
+
+	return ts_allocate_fast_id_at(rsrc_id, offset, fixed_offset, size, ctor, dtor);
+}/*}}}*/
+
+
+TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
+{
+	TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new fast resource id, %d bytes", size));
+
+	tsrm_mutex_lock(tsmm_mutex);
+
+	/* obtain a resource id */
+	*rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);
+	TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id));
+
+	size = TSRM_ALIGNED_SIZE(size);
+	*offset = (size_t) fixed_offset;
 
 	/* store the new resource type in the resource sizes table */
 	if (resource_types_table_size < id_count) {
@@ -366,7 +388,7 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz
 
 	TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new resource id %d", *rsrc_id));
 	return *rsrc_id;
-}/*}}}*/
+}
 
 static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource)
 {
@@ -378,7 +400,10 @@ static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource
 static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)
 {/*{{{*/
 	TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Creating data structures for thread %x", thread_id));
-	(*thread_resources_ptr) = (tsrm_tls_entry *) malloc(TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_size);
+	/* The entry follows the fixed-offset front region.
+	 * hot globals live at negative offsets from the TLS cache pointer. */
+	char *block = (char *) malloc(tsrm_reserved_front + TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_size);
+	(*thread_resources_ptr) = (tsrm_tls_entry *) (block + tsrm_reserved_front);
 	(*thread_resources_ptr)->storage = NULL;
 	if (id_count > 0) {
 		(*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count);
@@ -487,7 +512,7 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)
 		set_thread_local_storage_resource_to(thread_resources);
 		/* Free up the old resource from the old thread instance */
 		ts_free_resources(thread_resources);
-		free(thread_resources);
+		free((char *) thread_resources - tsrm_reserved_front);
 		/* Allocate a new resource at the same point in the linked list, and relink the next pointer */
 		allocate_new_resource(last_thread_resources, thread_id);
 		thread_resources = *last_thread_resources;
@@ -529,7 +554,7 @@ void ts_free_thread(void)
 				tsrm_tls_table[hash_value] = thread_resources->next;
 			}
 			tsrm_tls_set(0);
-			free(thread_resources);
+			free((char *) thread_resources - tsrm_reserved_front);
 			break;
 		}
 		if (thread_resources->next) {
diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h
index 639b1134ddde..2e8cbddfcda7 100644
--- a/TSRM/TSRM.h
+++ b/TSRM/TSRM.h
@@ -20,6 +20,7 @@
 # include <main/php_config.h>
 #endif
 
+#include <stddef.h>
 #include <stdint.h>
 #include <stdbool.h>
 
@@ -94,6 +95,11 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate
 TSRM_API void tsrm_reserve(size_t size);
 TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor);
 
+/* Fast resources at caller-chosen, compile-time-constant offsets. The fixed
+ * front region must be reserved after tsrm_reserve() and before any fast id. */
+TSRM_API void tsrm_reserve_fast_front(size_t size);
+TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor);
+
 /* fetches the requested resource for the current thread */
 TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id);
 #define ts_resource(id)			ts_resource_ex(id, NULL)
diff --git a/Zend/zend.c b/Zend/zend.c
index f16b1a30dbbc..9411b92a2018 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -1019,9 +1019,12 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
 	zend_init_rsrc_list_dtors();
 
 #ifdef ZTS
-	ts_allocate_fast_id(&compiler_globals_id, &compiler_globals_offset, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor);
-	ts_allocate_fast_id(&executor_globals_id, &executor_globals_offset, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
-	ts_allocate_fast_id(&language_scanner_globals_id, &language_scanner_globals_offset, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL);
+	ts_allocate_fast_id_at(&compiler_globals_id, &compiler_globals_offset, ZEND_CG_OFFSET, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor);
+	ts_allocate_fast_id_at(&executor_globals_id, &executor_globals_offset, ZEND_EG_OFFSET, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
+	ts_allocate_fast_id_at(&language_scanner_globals_id, &language_scanner_globals_offset, ZEND_SCNG_OFFSET, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL);
+	ZEND_ASSERT(compiler_globals_offset == ZEND_CG_OFFSET);
+	ZEND_ASSERT(executor_globals_offset == ZEND_EG_OFFSET);
+	ZEND_ASSERT(language_scanner_globals_offset == ZEND_SCNG_OFFSET);
 	ts_allocate_fast_id(&ini_scanner_globals_id, &ini_scanner_globals_offset, sizeof(zend_ini_scanner_globals), (ts_allocate_ctor) ini_scanner_globals_ctor, NULL);
 	compiler_globals = ts_resource(compiler_globals_id);
 	executor_globals = ts_resource(executor_globals_id);
diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index 942a8b8e1309..d0f2b221b9a7 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -2614,7 +2614,8 @@ typedef struct _zend_alloc_globals {
 #ifdef ZTS
 static int alloc_globals_id;
 static size_t alloc_globals_offset;
-# define AG(v) ZEND_TSRMG_FAST(alloc_globals_offset, zend_alloc_globals *, v)
+# define ZEND_AG_OFFSET (ZEND_SCNG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_alloc_globals)))
+# define AG(v) ZEND_TSRMG_FAST(ZEND_AG_OFFSET, zend_alloc_globals *, v)
 #else
 # define AG(v) (alloc_globals.v)
 static zend_alloc_globals alloc_globals;
@@ -3335,7 +3336,8 @@ ZEND_API void start_memory_manager(void)
 #  endif
 #endif
 #ifdef ZTS
-	ts_allocate_fast_id(&alloc_globals_id, &alloc_globals_offset, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor);
+	ts_allocate_fast_id_at(&alloc_globals_id, &alloc_globals_offset, ZEND_AG_OFFSET, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor);
+	ZEND_ASSERT(alloc_globals_offset == ZEND_AG_OFFSET);
 #else
 	alloc_globals_ctor(&alloc_globals);
 #endif
diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h
index 8257df32e831..61499c0cc23d 100644
--- a/Zend/zend_globals.h
+++ b/Zend/zend_globals.h
@@ -329,6 +329,14 @@ struct _zend_executor_globals {
 	void *reserved[ZEND_MAX_RESERVED_RESOURCES];
 };
 
+#ifdef ZTS
+/* Compile-time offsets of the hot globals, in a reserved region just before the
+ * cache pointer. ZEND_AG_OFFSET is furthest, in zend_alloc.c. */
+# define ZEND_CG_OFFSET   (-(ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)))
+# define ZEND_EG_OFFSET   (ZEND_CG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)))
+# define ZEND_SCNG_OFFSET (ZEND_EG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)))
+#endif
+
 #define EG_FLAGS_INITIAL				(0)
 #define EG_FLAGS_IN_SHUTDOWN			(1<<0)
 #define EG_FLAGS_OBJECT_STORE_NO_REUSE	(1<<1)
diff --git a/Zend/zend_globals_macros.h b/Zend/zend_globals_macros.h
index bde10a0989d1..2d2948e50a86 100644
--- a/Zend/zend_globals_macros.h
+++ b/Zend/zend_globals_macros.h
@@ -30,7 +30,7 @@ BEGIN_EXTERN_C()
 
 /* Compiler */
 #ifdef ZTS
-# define CG(v) ZEND_TSRMG_FAST(compiler_globals_offset, zend_compiler_globals *, v)
+# define CG(v) ZEND_TSRMG_FAST(ZEND_CG_OFFSET, zend_compiler_globals *, v)
 #else
 # define CG(v) (compiler_globals.v)
 extern ZEND_API struct _zend_compiler_globals compiler_globals;
@@ -40,7 +40,7 @@ ZEND_API int zendparse(void);
 
 /* Executor */
 #ifdef ZTS
-# define EG(v) ZEND_TSRMG_FAST(executor_globals_offset, zend_executor_globals *, v)
+# define EG(v) ZEND_TSRMG_FAST(ZEND_EG_OFFSET, zend_executor_globals *, v)
 #else
 # define EG(v) (executor_globals.v)
 extern ZEND_API zend_executor_globals executor_globals;
@@ -48,7 +48,7 @@ extern ZEND_API zend_executor_globals executor_globals;
 
 /* Language Scanner */
 #ifdef ZTS
-# define LANG_SCNG(v) ZEND_TSRMG_FAST(language_scanner_globals_offset, zend_php_scanner_globals *, v)
+# define LANG_SCNG(v) ZEND_TSRMG_FAST(ZEND_SCNG_OFFSET, zend_php_scanner_globals *, v)
 extern ZEND_API ts_rsrc_id language_scanner_globals_id;
 extern ZEND_API size_t language_scanner_globals_offset;
 #else
diff --git a/main/main.c b/main/main.c
index afb9fd410e82..48e4a757513b 100644
--- a/main/main.c
+++ b/main/main.c
@@ -2842,6 +2842,12 @@ PHPAPI bool php_tsrm_startup_ex(int expected_threads)
 {
 	bool ret = tsrm_startup(expected_threads, 1, 0, NULL);
 	php_reserve_tsrm_memory();
+	/* Must cover the total size of every ZEND_*_OFFSET global, or the furthest underflows the block. */
+	tsrm_reserve_fast_front(
+		TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)) +
+		TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)) +
+		TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)) +
+		TSRM_ALIGNED_SIZE(zend_mm_globals_size())); // AG size, exposed through function call
 	(void)ts_resource(0);
 	return ret;
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.