2323static struct {
2424 pthread_mutex_t mutex ;
2525 HashTable functions ;
26- HashTable identities ;
2726#if PHP_VERSION_ID < 80200
2827 HashTable statics ;
2928#endif
@@ -57,15 +56,6 @@ typedef struct _php_parallel_cache_entry_t {
5756#endif
5857} php_parallel_cache_entry_t ;
5958
60- typedef struct _php_parallel_cache_identity_t {
61- const zend_op * opcodes ;
62- zend_string * filename ;
63- zif_handler handler ;
64- uint32_t line_start ;
65- uint32_t line_end ;
66- uint8_t type ;
67- } php_parallel_cache_identity_t ;
68-
6959/* {{{ */
7060static zend_always_inline void * php_parallel_cache_alloc (size_t size )
7161{
@@ -150,73 +140,6 @@ static zend_always_inline HashTable *php_parallel_cache_statics(HashTable *stati
150140} /* }}} */
151141#endif
152142
153- static zend_always_inline void php_parallel_cache_identities (const zend_function * source , const zend_function * cached )
154- { /* {{{ */
155- const zend_op * source_opline = source -> op_array .opcodes ;
156- const zend_op * cached_opline = cached -> op_array .opcodes ;
157- const zend_op * end = source_opline + source -> op_array .last ;
158-
159- while (source_opline < end ) {
160- if (source_opline -> opcode == ZEND_INIT_FCALL ) {
161- zend_function * function = zend_fetch_function (Z_STR_P (RT_CONSTANT (source_opline , source_opline -> op2 )));
162-
163- if (function ) {
164- php_parallel_cache_identity_t * identity = php_parallel_cache_alloc (sizeof (* identity ));
165-
166- memset (identity , 0 , sizeof (* identity ));
167- identity -> type = function -> type ;
168-
169- if (ZEND_USER_CODE (function -> type )) {
170- identity -> opcodes = function -> op_array .opcodes ;
171- identity -> filename = function -> op_array .filename
172- ? php_parallel_copy_string_interned (function -> op_array .filename )
173- : NULL ;
174- identity -> line_start = function -> op_array .line_start ;
175- identity -> line_end = function -> op_array .line_end ;
176- } else {
177- identity -> handler = function -> internal_function .handler ;
178- }
179-
180- zend_hash_index_update_ptr (& PCG (identities ), (zend_ulong )cached_opline , identity );
181- }
182- }
183-
184- source_opline ++ ;
185- cached_opline ++ ;
186- }
187- } /* }}} */
188-
189- bool php_parallel_cache_function_matches (const zend_op * opline , const zend_function * function ,
190- php_parallel_cache_function_source_t * source )
191- { /* {{{ */
192- php_parallel_cache_identity_t * identity ;
193- bool matches = false;
194-
195- pthread_mutex_lock (& PCG (mutex ));
196- identity = zend_hash_index_find_ptr (& PCG (identities ), (zend_ulong )opline );
197- pthread_mutex_unlock (& PCG (mutex ));
198-
199- if (!identity ) {
200- return false;
201- }
202-
203- source -> filename = identity -> filename ;
204- source -> line = identity -> line_start ;
205-
206- if (ZEND_USER_CODE (identity -> type )) {
207- matches =
208- ZEND_USER_CODE (function -> type ) && (identity -> opcodes == function -> op_array .opcodes ||
209- (identity -> filename && function -> op_array .filename &&
210- zend_string_equals (identity -> filename , function -> op_array .filename ) &&
211- identity -> line_start == function -> op_array .line_start &&
212- identity -> line_end == function -> op_array .line_end ));
213- } else {
214- matches = function -> type == identity -> type && function -> internal_function .handler == identity -> handler ;
215- }
216-
217- return matches ;
218- } /* }}} */
219-
220143static zend_always_inline void php_parallel_cache_type (zend_type * type )
221144{ /* {{{ */
222145 zend_type * single ;
@@ -252,7 +175,9 @@ static zend_always_inline void php_parallel_cache_type(zend_type *type)
252175static zend_op_array * php_parallel_cache_create (const zend_function * source PARALLEL_CACHE_STATICS_PARAM )
253176{
254177 zend_op_array * cached = php_parallel_cache_copy_mem ((void * )source , sizeof (zend_op_array ));
255- bool shared = cached -> refcount == NULL ;
178+ uint32_t * literal_map = NULL ;
179+ uint32_t * offset_map = NULL ;
180+ uint32_t new_last_literal = cached -> last_literal ;
256181
257182 cached -> fn_flags |= ZEND_ACC_IMMUTABLE ;
258183
@@ -285,20 +210,58 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
285210 }
286211#endif
287212
288- if (shared ) {
289- php_parallel_cache_identities (source , (zend_function * )cached );
213+ if (!cached -> refcount ) {
290214 goto _php_parallel_cached_function_return ;
291215 }
292216
293217 cached -> refcount = NULL ;
294218
295219 if (cached -> last_literal ) {
296- zval * literal = source -> op_array .literals , * end = literal + cached -> last_literal ;
297- zval * slot = php_parallel_cache_alloc (sizeof (zval ) * cached -> last_literal );
220+ zend_op * src_opline = source -> op_array .opcodes ;
221+ zend_op * src_end = src_opline + source -> op_array .last ;
222+
223+ // A map to keep track of which literals are referenced by the
224+ // `ZEND_INIT_FCALL` opcodes we found so that we can expand those later
225+ literal_map = emalloc (sizeof (uint32_t ) * cached -> last_literal );
226+ memset (literal_map , 0 , sizeof (uint32_t ) * cached -> last_literal );
227+
228+ // Search for `ZEND_INIT_FCALL` opcodes and remember the indexes for the
229+ // literals, as we are rewriting them later to `ZEND_INIT_FCALL_BY_NAME`
230+ // which requires a second, lower cased literal just in the next literal
231+ // slot.
232+ while (src_opline < src_end ) {
233+ if (src_opline -> opcode == ZEND_INIT_FCALL && src_opline -> op2_type == IS_CONST ) {
234+ uint32_t idx ;
235+ #if ZEND_USE_ABS_CONST_ADDR
236+ idx = (zval * )src_opline -> op2 .zv - source -> op_array .literals ;
237+ #else
238+ idx = ((zval * )((char * )src_opline + src_opline -> op2 .constant ) - source -> op_array .literals );
239+ #endif
240+ if (idx < cached -> last_literal ) {
241+ if (literal_map [idx ] == 0 ) {
242+ literal_map [idx ] = 1 ;
243+ new_last_literal ++ ;
244+ }
245+ }
246+ }
247+ src_opline ++ ;
248+ }
249+ }
250+
251+ if (new_last_literal ) {
252+ zval * literal = source -> op_array .literals ;
253+ zval * slot = php_parallel_cache_alloc (sizeof (zval ) * new_last_literal );
254+ uint32_t idx = 0 ;
255+
256+ offset_map = emalloc (sizeof (uint32_t ) * cached -> last_literal );
298257
299258 cached -> literals = slot ;
300259
301- while (literal < end ) {
260+ for (uint32_t i = 0 ; i < cached -> last_literal ; i ++ ) {
261+ /* Record the mapping from old literal index (i) to new literal index (idx)
262+ so we can update opcode operands later. */
263+ offset_map [i ] = idx ;
264+
302265 if (Z_TYPE_P (literal ) == IS_ARRAY ) {
303266 ZVAL_ARR (slot , php_parallel_copy_hash_persistent (Z_ARRVAL_P (literal ), php_parallel_copy_string_interned ,
304267 php_parallel_cache_copy_mem ,
@@ -310,25 +273,62 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
310273 }
311274
312275 Z_TYPE_FLAGS_P (slot ) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE );
276+
277+ /* If this literal was used by INIT_FCALL, insert its lowercased version next. */
278+ if (literal_map [i ]) {
279+ zend_string * lower = zend_string_tolower (Z_STR_P (slot ));
280+ slot ++ ;
281+ idx ++ ;
282+ ZVAL_STR (slot , php_parallel_copy_string_interned (lower ));
283+ zend_string_release (lower );
284+ Z_TYPE_FLAGS_P (slot ) &= ~(IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE );
285+ }
286+
313287 literal ++ ;
314288 slot ++ ;
289+ idx ++ ;
290+ }
291+ cached -> last_literal = new_last_literal ;
292+ }
293+
294+ if (cached -> last_var ) {
295+ zend_string * * vars = cached -> vars ;
296+ uint32_t it = 0 , end = cached -> last_var ;
297+ zend_string * * heap = php_parallel_cache_alloc (cached -> last_var * sizeof (zend_string * ));
298+
299+ while (it < end ) {
300+ heap [it ] = php_parallel_copy_string_interned (vars [it ]);
301+ it ++ ;
315302 }
303+ cached -> vars = heap ;
316304 }
317305
318306 if (cached -> last ) {
319307 zend_op * opcodes = php_parallel_cache_copy_mem (cached -> opcodes , sizeof (zend_op ) * cached -> last );
320308 zend_op * opline = opcodes , * end = opline + cached -> last ;
321309
322310 while (opline < end ) {
311+ /* Replace ZEND_INIT_FCALL with ZEND_INIT_FCALL_BY_NAME.
312+ We must clear op1_type (IS_UNUSED) and op1.var (0) to invalidate the
313+ original thread's cache slot. */
314+ if (opline -> opcode == ZEND_INIT_FCALL ) {
315+ opline -> opcode = ZEND_INIT_FCALL_BY_NAME ;
316+ opline -> op1_type = IS_UNUSED ;
317+ opline -> op1 .var = 0 ;
318+ ZEND_VM_SET_OPCODE_HANDLER (opline );
319+ }
320+
321+ /* Remap IS_CONST operands to their new locations in the expanded literal table
322+ using the offset_map we built earlier. */
323323 if (opline -> op1_type == IS_CONST ) {
324324 uint32_t idx ;
325325 zend_op * src_opline = source -> op_array .opcodes + (opline - opcodes );
326326#if ZEND_USE_ABS_CONST_ADDR
327327 idx = (zval * )src_opline -> op1 .zv - source -> op_array .literals ;
328- opline -> op1 .zv = & cached -> literals [idx ];
328+ opline -> op1 .zv = & cached -> literals [offset_map [ idx ] ];
329329#else
330330 idx = ((zval * )((char * )src_opline + src_opline -> op1 .constant ) - source -> op_array .literals );
331- opline -> op1 .constant = (char * )& cached -> literals [idx ] - (char * )opline ;
331+ opline -> op1 .constant = (char * )& cached -> literals [offset_map [ idx ] ] - (char * )opline ;
332332#endif
333333 if (opline -> opcode == ZEND_SEND_VAL || opline -> opcode == ZEND_SEND_VAL_EX ||
334334 opline -> opcode == ZEND_QM_ASSIGN ) {
@@ -340,17 +340,12 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
340340 zend_op * src_opline = source -> op_array .opcodes + (opline - opcodes );
341341#if ZEND_USE_ABS_CONST_ADDR
342342 idx = (zval * )src_opline -> op2 .zv - source -> op_array .literals ;
343- opline -> op2 .zv = & cached -> literals [idx ];
343+ opline -> op2 .zv = & cached -> literals [offset_map [ idx ] ];
344344#else
345345 idx = ((zval * )((char * )src_opline + src_opline -> op2 .constant ) - source -> op_array .literals );
346- opline -> op2 .constant = (char * )& cached -> literals [idx ] - (char * )opline ;
346+ opline -> op2 .constant = (char * )& cached -> literals [offset_map [ idx ] ] - (char * )opline ;
347347#endif
348348 }
349-
350- if (opline -> opcode == ZEND_INIT_FCALL ) {
351- Z_EXTRA_P (RT_CONSTANT (opline , opline -> op2 )) = 0 ;
352- ZEND_VM_SET_OPCODE_HANDLER (opline );
353- }
354349#if ZEND_USE_ABS_JMP_ADDR
355350 switch (opline -> opcode ) {
356351 case ZEND_JMP :
@@ -385,18 +380,12 @@ static zend_op_array *php_parallel_cache_create(const zend_function *source PARA
385380 cached -> opcodes = opcodes ;
386381 }
387382
388- php_parallel_cache_identities (source , (zend_function * )cached );
389-
390- if (cached -> last_var ) {
391- zend_string * * vars = cached -> vars ;
392- uint32_t it = 0 , end = cached -> last_var ;
393- zend_string * * heap = php_parallel_cache_alloc (cached -> last_var * sizeof (zend_string * ));
383+ if (literal_map ) {
384+ efree (literal_map );
385+ }
394386
395- while (it < end ) {
396- heap [it ] = php_parallel_copy_string_interned (vars [it ]);
397- it ++ ;
398- }
399- cached -> vars = heap ;
387+ if (offset_map ) {
388+ efree (offset_map );
400389 }
401390
402391 if (cached -> arg_info ) {
@@ -547,7 +536,6 @@ zend_function *php_parallel_cache_function(const zend_function *source)
547536PHP_MINIT_FUNCTION (PARALLEL_CACHE )
548537{
549538 zend_hash_init (& PCG (functions ), 32 , NULL , NULL , 1 );
550- zend_hash_init (& PCG (identities ), 32 , NULL , NULL , 1 );
551539#if PHP_VERSION_ID < 80200
552540 zend_hash_init (& PCG (statics ), 32 , NULL , NULL , 1 );
553541#endif
@@ -565,7 +553,6 @@ PHP_MINIT_FUNCTION(PARALLEL_CACHE)
565553PHP_MSHUTDOWN_FUNCTION (PARALLEL_CACHE )
566554{
567555 zend_hash_destroy (& PCG (functions ));
568- zend_hash_destroy (& PCG (identities ));
569556#if PHP_VERSION_ID < 80200
570557 zend_hash_destroy (& PCG (statics ));
571558#endif
0 commit comments