• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

krakjoe / parallel / 20315935394

17 Dec 2025 08:12PM UTC coverage: 90.94% (-5.6%) from 96.535%
20315935394

push

github

web-flow
convert `ZEND_INIT_FCALL` to `ZEND_INIT_FCALL_BY_NAME` (#356)

* convert `ZEND_INIT_FCALL` to `ZEND_INIT_DYNAMIC_CALL`

* instead, switch to `ZEND_INIT_FCALL_BY_NAME` and update literals table

* Adding tests

* small fixes

6 of 54 new or added lines in 2 files covered. (11.11%)

133 existing lines in 4 files now uncovered.

2921 of 3212 relevant lines covered (90.94%)

4388.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.45
/src/cache.c
1
/*
2
  +----------------------------------------------------------------------+
3
  | parallel                                                             |
4
  +----------------------------------------------------------------------+
5
  | Copyright (c) Joe Watkins 2019-2024                                  |
6
  +----------------------------------------------------------------------+
7
  | This source file is subject to version 3.01 of the PHP license,      |
8
  | that is bundled with this package in the file LICENSE, and is        |
9
  | available through the world-wide-web at the following url:           |
10
  | http://www.php.net/license/3_01.txt                                  |
11
  | If you did not receive a copy of the PHP license and are unable to   |
12
  | obtain it through the world-wide-web, please send a note to          |
13
  | license@php.net so we can mail you a copy immediately.               |
14
  +----------------------------------------------------------------------+
15
  | Author: krakjoe                                                      |
16
  +----------------------------------------------------------------------+
17
 */
18
#ifndef HAVE_PARALLEL_CACHE
19
#define HAVE_PARALLEL_CACHE
20

21
#include "parallel.h"
22

23
static struct {
24
    pthread_mutex_t mutex;
25
    HashTable       table;
26
    struct {
27
        size_t      size;
28
        size_t      used;
29
        void       *mem;
30
        void       *block;
31
    } memory;
32
} php_parallel_cache_globals = {PTHREAD_MUTEX_INITIALIZER};
33

34
#define PCG(e) php_parallel_cache_globals.e
35
#define PCM(e) PCG(memory).e
36

37
#define PARALLEL_CACHE_CHUNK \
38
    PARALLEL_PLATFORM_ALIGNED((1024 * 1024) * 8)
39

40
/* {{{ */
41
static zend_always_inline void* php_parallel_cache_alloc(size_t size) {
1,758✔
42
    void *mem;
1,758✔
43
    size_t aligned =
1,758✔
44
        PARALLEL_PLATFORM_ALIGNED(size);
148✔
45

46
    ZEND_ASSERT(size < PARALLEL_CACHE_CHUNK);
1,758✔
47

48
    if ((PCM(used) + aligned) >= PCM(size)) {
1,758✔
49
        PCM(size) = PARALLEL_PLATFORM_ALIGNED(
×
50
            PCM(size) + PARALLEL_CACHE_CHUNK);
51
        PCM(mem) = (void*) realloc(PCM(mem), PCM(size));
×
52

53
        if (!PCM(mem)) {
×
54
            /* out of memory */
55
            return NULL;
56
        }
57

58
        PCM(block) = (void*)(((char*)PCM(mem)) + PCM(used));
×
59
    }
60

61
    mem = PCM(block);
1,758✔
62
    PCM(block) =
1,758✔
63
        (void*)(((char*)PCM(block)) + aligned);
1,758✔
64
    PCM(used) += aligned;
1,758✔
65

66
    return mem;
1,758✔
67
}
68

69
static zend_always_inline void* php_parallel_cache_copy_mem(void *source, zend_long size) {
1,758✔
70
    void *destination =
1,758✔
71
        php_parallel_cache_alloc(size);
28✔
72

73
    memcpy(destination, source, size);
1,730✔
74

75
    return destination;
1,758✔
76
} /* }}} */
77

78
static zend_always_inline HashTable* php_parallel_cache_statics(HashTable *statics) { /* {{{ */
14✔
79
    HashTable *cached = zend_hash_index_find_ptr(&PCG(table), (zend_ulong) statics);
28✔
80

81
    if (cached) {
×
82
        return cached;
83
    }
84

85
    cached = php_parallel_copy_hash_persistent(
14✔
86
                statics,
87
                php_parallel_copy_string_interned,
88
                php_parallel_cache_copy_mem);
89

90
    return zend_hash_index_update_ptr(&PCG(table), (zend_ulong) statics, cached);
14✔
91
} /* }}} */
92

UNCOV
93
static zend_always_inline void php_parallel_cache_type(zend_type *type) { /* {{{ */
×
UNCOV
94
    zend_type *single;
×
95

UNCOV
96
    if (!ZEND_TYPE_IS_SET(*type)) {
×
97
        return;
98
    }
99

UNCOV
100
    if (ZEND_TYPE_HAS_LIST(*type)) {
×
UNCOV
101
        zend_type_list *list = ZEND_TYPE_LIST(*type);
×
102

UNCOV
103
        list = php_parallel_cache_copy_mem(
×
UNCOV
104
                    list, ZEND_TYPE_LIST_SIZE(list->num_types));
×
105

UNCOV
106
        if (ZEND_TYPE_USES_ARENA(*type)) {
×
UNCOV
107
            ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT;
×
108
        }
109

UNCOV
110
        ZEND_TYPE_SET_PTR(*type, list);
×
111
    }
112

UNCOV
113
    ZEND_TYPE_FOREACH(*type, single) {
×
UNCOV
114
        if (ZEND_TYPE_HAS_NAME(*single)) {
×
UNCOV
115
            zend_string *name = ZEND_TYPE_NAME(*single);
×
116

UNCOV
117
            ZEND_TYPE_SET_PTR(
×
118
                *single,
119
                php_parallel_copy_string_interned(name));
120
        }
UNCOV
121
    } ZEND_TYPE_FOREACH_END();
×
122
} /* }}} */
123

124

125
/* {{{ */
126
static zend_op_array* php_parallel_cache_create(const zend_function *source, bool statics) {
1,610✔
127
    zend_op_array *cached = php_parallel_cache_copy_mem((void*) source, sizeof(zend_op_array));
1,610✔
128
    uint32_t *literal_map = NULL;
1,610✔
129
    uint32_t *offset_map = NULL;
1,610✔
130
    uint32_t new_last_literal = cached->last_literal;
1,610✔
131

132
    cached->fn_flags |= ZEND_ACC_IMMUTABLE;
1,610✔
133

134
    if (statics && cached->static_variables) {
1,610✔
135
        cached->static_variables =
28✔
136
            php_parallel_cache_statics(cached->static_variables);
28✔
137
    }
138

139
#if PHP_VERSION_ID >= 80200
140
    ZEND_MAP_PTR_INIT(cached->static_variables_ptr, cached->static_variables);
1,096✔
141
#else
142
    ZEND_MAP_PTR_INIT(cached->static_variables_ptr, &cached->static_variables);
514✔
143
#endif
144

145
    ZEND_MAP_PTR_INIT(cached->run_time_cache, NULL);
1,610✔
146

147
#if PHP_VERSION_ID >= 80100
148
    if (cached->num_dynamic_func_defs) {
1,370✔
149
            uint32_t it = 0;
120✔
150

151
            cached->dynamic_func_defs = php_parallel_cache_copy_mem(
240✔
152
                                            cached->dynamic_func_defs,
120✔
153
                                            sizeof(zend_op_array*) * cached->num_dynamic_func_defs);
120✔
154

155
            while (it < cached->num_dynamic_func_defs) {
260✔
156
                cached->dynamic_func_defs[it] =
280✔
157
                (zend_op_array*) php_parallel_cache_create(
140✔
158
                        (zend_function*) cached->dynamic_func_defs[it], statics);
140✔
159
            it++;
140✔
160
            }
161
    }
162
#endif
163

164
    if (!cached->refcount) {
1,610✔
165
        goto _php_parallel_cached_function_return;
1,610✔
166
    }
167

UNCOV
168
    cached->refcount  = NULL;
×
169

UNCOV
170
    if (cached->last_literal) {
×
NEW
171
        zend_op *src_opline = source->op_array.opcodes;
×
NEW
172
        zend_op *src_end    = src_opline + source->op_array.last;
×
173

174
        // A map to keep track of which literals are referenced by the
175
        // `ZEND_INIT_FCALL` opcodes we found so that we can expand those later
NEW
176
        literal_map = emalloc(sizeof(uint32_t) * cached->last_literal);
×
NEW
177
        memset(literal_map, 0, sizeof(uint32_t) * cached->last_literal);
×
178

179
        // Search for `ZEND_INIT_FCALL` opcodes and remember the indexes for the
180
        // literals, as we are rewriting them later to `ZEND_INIT_FCALL_BY_NAME`
181
        // which requires a second, lower cased literal just in the next literal
182
        // slot.
NEW
183
        while (src_opline < src_end) {
×
NEW
184
            if (src_opline->opcode == ZEND_INIT_FCALL && src_opline->op2_type == IS_CONST) {
×
NEW
185
                uint32_t idx;
×
186
#if ZEND_USE_ABS_CONST_ADDR
187
                idx = (zval*)src_opline->op2.zv - source->op_array.literals;
188
#else
NEW
189
                idx = ((zval*)((char*)src_opline + src_opline->op2.constant) - source->op_array.literals);
×
190
#endif
NEW
191
                if (idx < cached->last_literal) {
×
NEW
192
                    if (literal_map[idx] == 0) {
×
NEW
193
                        literal_map[idx] = 1;
×
NEW
194
                        new_last_literal++;
×
195
                    }
196
                }
197
            }
NEW
198
            src_opline++;
×
199
        }
200
    }
201

NEW
202
    if (new_last_literal) {
×
NEW
203
        zval     *literal = source->op_array.literals;
×
NEW
204
        zval     *slot    = php_parallel_cache_alloc(sizeof(zval) * new_last_literal);
×
NEW
205
        uint32_t  idx     = 0;
×
206

NEW
207
        offset_map = emalloc(sizeof(uint32_t) * cached->last_literal);
×
208

UNCOV
209
        cached->literals = slot;
×
210

NEW
211
        for (uint32_t i = 0; i < cached->last_literal; i++) {
×
212
            /* Record the mapping from old literal index (i) to new literal index (idx)
213
               so we can update opcode operands later. */
NEW
214
            offset_map[i] = idx;
×
215

UNCOV
216
            if (Z_TYPE_P(literal) == IS_ARRAY) {
×
UNCOV
217
                ZVAL_ARR(slot,
×
218
                    php_parallel_copy_hash_persistent(
219
                        Z_ARRVAL_P(literal),
220
                        php_parallel_copy_string_interned,
221
                        php_parallel_cache_copy_mem));
UNCOV
222
            } else if (Z_TYPE_P(literal) == IS_STRING) {
×
UNCOV
223
                ZVAL_STR(slot,
×
224
                    php_parallel_copy_string_interned(Z_STR_P(literal)));
225
            } else {
NEW
226
                *slot = *literal;
×
227
            }
228

NEW
229
            Z_TYPE_FLAGS_P(slot) &= ~(IS_TYPE_REFCOUNTED|IS_TYPE_COLLECTABLE);
×
230

231
            /* If this literal was used by INIT_FCALL, insert its lowercased version next. */
NEW
232
            if (literal_map[i]) {
×
NEW
233
                zend_string *lower = zend_string_tolower(Z_STR_P(slot));
×
NEW
234
                slot++;
×
NEW
235
                idx++;
×
NEW
236
                ZVAL_STR(slot, php_parallel_copy_string_interned(lower));
×
NEW
237
                zend_string_release(lower);
×
NEW
238
                Z_TYPE_FLAGS_P(slot) &= ~(IS_TYPE_REFCOUNTED|IS_TYPE_COLLECTABLE);
×
239
            }
240

UNCOV
241
            literal++;
×
UNCOV
242
            slot++;
×
NEW
243
            idx++;
×
244
        }
NEW
245
        cached->last_literal = new_last_literal;
×
246
    }
247

UNCOV
248
    if (cached->last_var) {
×
UNCOV
249
        zend_string **vars = cached->vars;
×
UNCOV
250
        uint32_t      it = 0,
×
UNCOV
251
                      end = cached->last_var;
×
UNCOV
252
        zend_string **heap = php_parallel_cache_alloc(cached->last_var * sizeof(zend_string*));
×
253

UNCOV
254
        while (it < end) {
×
UNCOV
255
            heap[it] =
×
UNCOV
256
                php_parallel_copy_string_interned(vars[it]);
×
UNCOV
257
            it++;
×
258
        }
UNCOV
259
        cached->vars = heap;
×
260
    }
261

UNCOV
262
    if (cached->last) {
×
UNCOV
263
        zend_op *opcodes = php_parallel_cache_copy_mem(cached->opcodes, sizeof(zend_op) * cached->last);
×
UNCOV
264
        zend_op *opline  = opcodes,
×
UNCOV
265
                *end     = opline + cached->last;
×
266

UNCOV
267
        while (opline < end) {
×
268
            /* Replace ZEND_INIT_FCALL with ZEND_INIT_FCALL_BY_NAME.
269
               We must clear op1_type (IS_UNUSED) and op1.var (0) to invalidate the
270
               original thread's cache slot. */
NEW
271
            if (opline->opcode == ZEND_INIT_FCALL) {
×
NEW
272
                opline->opcode = ZEND_INIT_FCALL_BY_NAME;
×
NEW
273
                opline->op1_type = IS_UNUSED;
×
NEW
274
                opline->op1.var = 0;
×
NEW
275
                ZEND_VM_SET_OPCODE_HANDLER(opline);
×
276
            }
277

278
            /* Remap IS_CONST operands to their new locations in the expanded literal table
279
               using the offset_map we built earlier. */
UNCOV
280
            if (opline->op1_type == IS_CONST) {
×
NEW
281
                uint32_t idx;
×
NEW
282
                zend_op *src_opline = source->op_array.opcodes + (opline - opcodes);
×
283
#if ZEND_USE_ABS_CONST_ADDR
284
                idx = (zval*)src_opline->op1.zv - source->op_array.literals;
285
                opline->op1.zv = &cached->literals[offset_map[idx]];
286
#else
NEW
287
                idx = ((zval*)((char*)src_opline + src_opline->op1.constant) - source->op_array.literals);
×
NEW
288
                opline->op1.constant = (char*)&cached->literals[offset_map[idx]] - (char*)opline;
×
289
#endif
UNCOV
290
                if (opline->opcode == ZEND_SEND_VAL
×
UNCOV
291
                 || opline->opcode == ZEND_SEND_VAL_EX
×
UNCOV
292
                 || opline->opcode == ZEND_QM_ASSIGN) {
×
UNCOV
293
                    zend_vm_set_opcode_handler_ex(opline, 0, 0, 0);
×
294
                }
295
            }
UNCOV
296
            if (opline->op2_type == IS_CONST) {
×
NEW
297
                uint32_t idx;
×
NEW
298
                zend_op *src_opline = source->op_array.opcodes + (opline - opcodes);
×
299
#if ZEND_USE_ABS_CONST_ADDR
300
                idx = (zval*)src_opline->op2.zv - source->op_array.literals;
301
                opline->op2.zv = &cached->literals[offset_map[idx]];
302
#else
NEW
303
                idx = ((zval*)((char*)src_opline + src_opline->op2.constant) - source->op_array.literals);
×
NEW
304
                opline->op2.constant = (char*)&cached->literals[offset_map[idx]] - (char*)opline;
×
305
#endif
306
            }
307
#if ZEND_USE_ABS_JMP_ADDR
308
            switch (opline->opcode) {
309
                case ZEND_JMP:
310
                case ZEND_FAST_CALL:
311
                    opline->op1.jmp_addr = &opcodes[opline->op1.jmp_addr - source->op_array.opcodes];
312
                break;
313
#if PHP_VERSION_ID < 80200
314
                case ZEND_JMPZNZ:
315
#endif
316
                case ZEND_JMPZ:
317
                case ZEND_JMPNZ:
318
                case ZEND_JMPZ_EX:
319
                case ZEND_JMPNZ_EX:
320
                case ZEND_JMP_SET:
321
                case ZEND_COALESCE:
322
                case ZEND_FE_RESET_R:
323
                case ZEND_FE_RESET_RW:
324
                case ZEND_ASSERT_CHECK:
325
                    opline->op2.jmp_addr = &opcodes[opline->op2.jmp_addr - source->op_array.opcodes];
326
                    break;
327

328
                case ZEND_CATCH:
329
                    if (!(opline->extended_value & ZEND_LAST_CATCH)) {
330
                        opline->op2.jmp_addr = &opcodes[opline->op2.jmp_addr - source->op_array.opcodes];
331
                    }
332
                    break;
333
            }
334
#endif
335

UNCOV
336
            opline++;
×
337
        }
UNCOV
338
        cached->opcodes = opcodes;
×
339
    }
340

NEW
341
    if (literal_map) {
×
NEW
342
                efree(literal_map);
×
343
        }
344

NEW
345
    if (offset_map) {
×
NEW
346
                efree(offset_map);
×
347
        }
348

UNCOV
349
    if (cached->arg_info) {
×
UNCOV
350
        zend_arg_info *it    = cached->arg_info,
×
UNCOV
351
                      *end   = it + cached->num_args,
×
352
                      *info;
353

UNCOV
354
        if (cached->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
×
UNCOV
355
            it--;
×
356
        }
UNCOV
357
        if (cached->fn_flags & ZEND_ACC_VARIADIC) {
×
UNCOV
358
            end++;
×
359
        }
360

UNCOV
361
        cached->arg_info = info = php_parallel_cache_copy_mem(it, (end - it) * sizeof(zend_arg_info));
×
362

UNCOV
363
         while (it < end) {
×
UNCOV
364
            if (info->name) {
×
UNCOV
365
                info->name =
×
UNCOV
366
                    php_parallel_copy_string_interned(it->name);
×
367
            }
368

UNCOV
369
            php_parallel_cache_type(&info->type);
×
370

UNCOV
371
            info++;
×
UNCOV
372
            it++;
×
373
        }
UNCOV
374
        if (cached->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
×
UNCOV
375
            cached->arg_info++;
×
376
        }
377
    }
378

UNCOV
379
    if (cached->try_catch_array) {
×
UNCOV
380
        cached->try_catch_array =
×
UNCOV
381
            php_parallel_cache_copy_mem(
×
UNCOV
382
                cached->try_catch_array,
×
UNCOV
383
                    sizeof(zend_try_catch_element) * cached->last_try_catch);
×
384
    }
385

UNCOV
386
    if (cached->live_range) {
×
UNCOV
387
        cached->live_range =
×
UNCOV
388
            php_parallel_cache_copy_mem(
×
UNCOV
389
                cached->live_range,
×
UNCOV
390
                sizeof(zend_live_range) * cached->last_live_range);
×
391
    }
392

UNCOV
393
    if (cached->function_name)
×
UNCOV
394
        cached->function_name =
×
UNCOV
395
            php_parallel_copy_string_interned(cached->function_name);
×
396

UNCOV
397
    if (cached->filename)
×
UNCOV
398
        cached->filename =
×
UNCOV
399
            php_parallel_copy_string_interned(cached->filename);
×
400

UNCOV
401
    if (cached->doc_comment)
×
UNCOV
402
        cached->doc_comment =
×
UNCOV
403
            php_parallel_copy_string_interned(cached->doc_comment);
×
404

UNCOV
405
_php_parallel_cached_function_return:
×
406
    return cached;
1,610✔
407
} /* }}} */
408

409
/* {{{ */
410
static zend_always_inline zend_function* php_parallel_cache_function_ex(const zend_function *source, bool statics) {
1,918✔
411
    zend_op_array *cached;
1,918✔
412

413
    pthread_mutex_lock(&PCG(mutex));
1,918✔
414

415
    if ((cached = zend_hash_index_find_ptr(&PCG(table), (zend_ulong) source->op_array.opcodes))) {
2,366✔
416
        goto _php_parallel_cached_function_return;
448✔
417
    }
418

419
    cached = php_parallel_cache_create(source, statics);
1,470✔
420

421
    zend_hash_index_add_ptr(
3,388✔
422
        &PCG(table),
423
        (zend_ulong) source->op_array.opcodes,
1,470✔
424
        cached);
425

426
_php_parallel_cached_function_return:
1,918✔
427
    pthread_mutex_unlock(&PCG(mutex));
1,918✔
428

429
    return (zend_function*) cached;
1,918✔
430
} /* }}} */
431

432
zend_function* php_parallel_cache_closure(const zend_function *source, zend_function *closure) { /* {{{ */
1,894✔
433
    zend_op_array *cache;
1,894✔
434

435
    cache =
1,894✔
436
        (zend_op_array*)
437
            php_parallel_cache_function_ex(
1,894✔
438
                (zend_function*) source, 0);
439

440
    if (!closure) {
1,894✔
441
        closure = php_parallel_copy_mem(
1,450✔
442
            cache, sizeof(zend_op_array), 1);
443
    } else {
444
        memcpy(closure, cache, sizeof(zend_op_array));
444✔
445
    }
446

447
    if (source->op_array.static_variables) {
1,894✔
448
        HashTable *statics =
900✔
449
            ZEND_MAP_PTR_GET(
450✔
450
                source->op_array.static_variables_ptr);
451

452
        if (statics) {
450✔
453
        closure->op_array.static_variables =
720✔
454
            php_parallel_copy_hash_ctor(statics, 1);
360✔
455

456
#if PHP_VERSION_ID >= 80200
457
        ZEND_MAP_PTR_INIT(
240✔
458
            closure->op_array.static_variables_ptr,
459
            closure->op_array.static_variables);
460
#else
461
        ZEND_MAP_PTR_INIT(
120✔
462
            closure->op_array.static_variables_ptr,
463
            &closure->op_array.static_variables);
464
#endif
465
        }
466
    }
467

468
#if PHP_VERSION_ID >= 80100
469
    if (source->op_array.num_dynamic_func_defs) {
1,610✔
470
        uint32_t it = 0;
130✔
471
        /* Use regular persistent memory for dynamic_func_defs array, not cache pool */
472
        closure->op_array.dynamic_func_defs = pemalloc(
130✔
473
            sizeof(zend_op_array*) * source->op_array.num_dynamic_func_defs, 1);
474
        memcpy(closure->op_array.dynamic_func_defs,
410✔
475
            source->op_array.dynamic_func_defs,
130✔
476
            sizeof(zend_op_array*) * source->op_array.num_dynamic_func_defs);
130✔
477
        while (it < source->op_array.num_dynamic_func_defs) {
280✔
478
            closure->op_array.dynamic_func_defs[it] = (zend_op_array*) php_parallel_cache_closure((zend_function*) source->op_array.dynamic_func_defs[it], NULL);
150✔
479
            it++;
150✔
480
        }
481
    }
482
#endif
483

484
    return closure;
1,894✔
485
} /* }}} */
486

487
zend_function* php_parallel_cache_function(const zend_function *source) { /* {{{ */
24✔
488
    return php_parallel_cache_function_ex(source, 1);
24✔
489
} /* }}} */
490

491
/* {{{ */
492
PHP_MINIT_FUNCTION(PARALLEL_CACHE)
2,142✔
493
{
494
    zend_hash_init(&PCG(table), 32, NULL, NULL, 1);
2,142✔
495

496
    PCM(size) = PARALLEL_CACHE_CHUNK;
2,142✔
497
    PCM(mem) =
2,142✔
498
        PCM(block) =
2,142✔
499
            malloc(PCM(size));
2,142✔
500

501
    if (!PCM(mem)) {
2,142✔
502
        /* out of memory */
503
    }
2,142✔
504

505
    return SUCCESS;
2,142✔
506
}
507

508
PHP_MSHUTDOWN_FUNCTION(PARALLEL_CACHE)
2,142✔
509
{
510
    zend_hash_destroy(&PCG(table));
2,142✔
511

512
    if (PCM(mem))
2,142✔
513
        free(PCM(mem));
2,142✔
514

515
    return SUCCESS;
2,142✔
516
} /* }}} */
517
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc