Coverage Report

Created: 2026-09-14 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/Optimizer/compact_literals.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend OPcache                                                         |
4
   +----------------------------------------------------------------------+
5
   | Copyright © The PHP Group and Contributors.                          |
6
   +----------------------------------------------------------------------+
7
   | This source file is subject to the Modified BSD License that is      |
8
   | bundled with this package in the file LICENSE, and is available      |
9
   | through the World Wide Web at <https://www.php.net/license/>.        |
10
   |                                                                      |
11
   | SPDX-License-Identifier: BSD-3-Clause                                |
12
   +----------------------------------------------------------------------+
13
   | Authors: Dmitry Stogov <dmitry@php.net>                              |
14
   |          Xinchen Hui <laruence@php.net>                              |
15
   +----------------------------------------------------------------------+
16
*/
17
18
/* pass 11
19
 * - compact literals table
20
 */
21
22
#include "Optimizer/zend_optimizer.h"
23
#include "Optimizer/zend_optimizer_internal.h"
24
#include "zend_API.h"
25
#include "zend_constants.h"
26
#include "zend_execute.h"
27
#include "zend_vm.h"
28
#include "zend_extensions.h"
29
#include "zend_partial.h"
30
31
#define DEBUG_COMPACT_LITERALS 0
32
33
2.98k
#define LITERAL_CLASS_CONST 1
34
5.75k
#define LITERAL_STATIC_METHOD 2
35
12.7k
#define LITERAL_STATIC_PROPERTY 3
36
37
typedef struct _literal_info {
38
  uint8_t num_related;
39
} literal_info;
40
41
1.18M
#define LITERAL_INFO(n, related) do { \
42
1.18M
    info[n].num_related = (related); \
43
1.18M
  } while (0)
44
45
static uint32_t add_static_slot(
46
  HashTable *hash,
47
  const zend_op_array *op_array,
48
  uint32_t op1,
49
  uint32_t op2,
50
  uint32_t kind,
51
  uint32_t *cache_size
52
13.2k
) {
53
13.2k
  uint32_t ret;
54
13.2k
  const zval *class_name = &op_array->literals[op1];
55
13.2k
  const zval *prop_name = &op_array->literals[op2];
56
57
13.2k
  zend_string *key = zend_create_member_string(Z_STR_P(class_name), Z_STR_P(prop_name));
58
13.2k
  ZSTR_H(key) = zend_string_hash_func(key);
59
13.2k
  ZSTR_H(key) += kind;
60
61
13.2k
  const zval *pos = zend_hash_find(hash, key);
62
13.2k
  if (pos) {
63
5.03k
    ret = Z_LVAL_P(pos);
64
8.21k
  } else {
65
8.21k
    zval tmp;
66
8.21k
    ret = *cache_size;
67
8.21k
    *cache_size += (kind == LITERAL_STATIC_PROPERTY ? 3 : 2) * sizeof(void *);
68
8.21k
    ZVAL_LONG(&tmp, ret);
69
8.21k
    zend_hash_add(hash, key, &tmp);
70
8.21k
  }
71
13.2k
  zend_string_release_ex(key, 0);
72
13.2k
  return ret;
73
13.2k
}
74
75
static inline void bias_key(zend_string *key, uint32_t bias)
76
212k
{
77
  /* Add a bias to the hash so we can distinguish string keys
78
   * that would otherwise be the same. */
79
212k
  ZSTR_H(key) = zend_string_hash_val(key) + bias;
80
212k
}
81
82
static zend_string *create_str_cache_key(const zval *literal, uint8_t num_related)
83
895k
{
84
895k
  ZEND_ASSERT(Z_TYPE_P(literal) == IS_STRING);
85
895k
  if (num_related == 1) {
86
696k
    return zend_string_copy(Z_STR_P(literal));
87
696k
  }
88
89
  /* Concatenate all the related literals for the cache key. */
90
199k
  zend_string *key;
91
199k
  if (num_related == 2) {
92
196k
    ZEND_ASSERT(Z_TYPE_P(literal + 1) == IS_STRING);
93
196k
    key = zend_string_concat2(
94
196k
      Z_STRVAL_P(literal), Z_STRLEN_P(literal),
95
196k
      Z_STRVAL_P(literal + 1), Z_STRLEN_P(literal + 1));
96
196k
  } else if (num_related == 3) {
97
3.10k
    ZEND_ASSERT(Z_TYPE_P(literal + 1) == IS_STRING && Z_TYPE_P(literal + 2) == IS_STRING);
98
3.10k
    key = zend_string_concat3(
99
3.10k
      Z_STRVAL_P(literal), Z_STRLEN_P(literal),
100
3.10k
      Z_STRVAL_P(literal + 1), Z_STRLEN_P(literal + 1),
101
3.10k
      Z_STRVAL_P(literal + 2), Z_STRLEN_P(literal + 2));
102
3.10k
  } else {
103
0
    ZEND_ASSERT(0 && "Currently not needed");
104
0
  }
105
106
199k
  bias_key(key, num_related - 1);
107
199k
  return key;
108
199k
}
109
110
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx)
111
115k
{
112
115k
  zend_op *opline, *end;
113
115k
  int n, *map;
114
115k
  uint32_t cache_size;
115
115k
  zval zv, *pos;
116
115k
  literal_info *info;
117
115k
  int l_null = -1;
118
115k
  int l_false = -1;
119
115k
  int l_true = -1;
120
115k
  int l_empty_arr = -1;
121
115k
  HashTable hash;
122
115k
  zend_string *key = NULL;
123
115k
  void *checkpoint = zend_arena_checkpoint(ctx->arena);
124
115k
  int *const_slot, *class_slot, *func_slot, *bind_var_slot, *property_slot, *method_slot, *jmp_slot, *assign_obj_slots;
125
126
115k
  if (op_array->last_literal) {
127
115k
    uint32_t j;
128
115k
    info = (literal_info*)zend_arena_calloc(&ctx->arena, op_array->last_literal, sizeof(literal_info));
129
130
      /* Mark literals of specific types */
131
115k
    opline = op_array->opcodes;
132
115k
    end = opline + op_array->last;
133
2.80M
    while (opline < end) {
134
2.69M
      switch (opline->opcode) {
135
0
        case ZEND_JMP_FRAMELESS:
136
0
          LITERAL_INFO(opline->op1.constant, 1);
137
0
          break;
138
7.08k
        case ZEND_INIT_FCALL_BY_NAME:
139
7.08k
          LITERAL_INFO(opline->op2.constant, 2);
140
7.08k
          break;
141
2.68k
        case ZEND_INIT_NS_FCALL_BY_NAME:
142
2.68k
          LITERAL_INFO(opline->op2.constant, 3);
143
2.68k
          break;
144
44.9k
        case ZEND_INIT_METHOD_CALL:
145
44.9k
          if (opline->op1_type == IS_CONST) {
146
162
            LITERAL_INFO(opline->op1.constant, 1);
147
162
          }
148
44.9k
          if (opline->op2_type == IS_CONST) {
149
44.4k
            LITERAL_INFO(opline->op2.constant, 2);
150
44.4k
          }
151
44.9k
          break;
152
7.51k
        case ZEND_INIT_STATIC_METHOD_CALL:
153
7.51k
          if (opline->op1_type == IS_CONST) {
154
5.87k
            LITERAL_INFO(opline->op1.constant, 2);
155
5.87k
          }
156
7.51k
          if (opline->op2_type == IS_CONST) {
157
6.84k
            LITERAL_INFO(opline->op2.constant, 2);
158
6.84k
          }
159
7.51k
          break;
160
260
        case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
161
260
          LITERAL_INFO(opline->op1.constant, 1);
162
260
          break;
163
32.7k
        case ZEND_CATCH:
164
32.7k
          LITERAL_INFO(opline->op1.constant, 2);
165
32.7k
          break;
166
26.9k
        case ZEND_FETCH_CONSTANT:
167
26.9k
          if (opline->op1.num & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
168
418
            LITERAL_INFO(opline->op2.constant, 3);
169
26.5k
          } else {
170
26.5k
            LITERAL_INFO(opline->op2.constant, 2);
171
26.5k
          }
172
26.9k
          break;
173
4.02k
        case ZEND_FETCH_CLASS_CONSTANT:
174
4.02k
          if (opline->op1_type == IS_CONST) {
175
3.03k
            LITERAL_INFO(opline->op1.constant, 2);
176
3.03k
          }
177
4.02k
          if (opline->op2_type == IS_CONST) {
178
3.90k
            LITERAL_INFO(opline->op2.constant, 1);
179
3.90k
          }
180
4.02k
          break;
181
1.71k
        case ZEND_ASSIGN_STATIC_PROP:
182
2.03k
        case ZEND_ASSIGN_STATIC_PROP_REF:
183
4.89k
        case ZEND_FETCH_STATIC_PROP_R:
184
5.46k
        case ZEND_FETCH_STATIC_PROP_W:
185
5.46k
        case ZEND_FETCH_STATIC_PROP_RW:
186
6.04k
        case ZEND_FETCH_STATIC_PROP_IS:
187
6.15k
        case ZEND_FETCH_STATIC_PROP_UNSET:
188
6.45k
        case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
189
6.48k
        case ZEND_UNSET_STATIC_PROP:
190
6.64k
        case ZEND_ISSET_ISEMPTY_STATIC_PROP:
191
6.69k
        case ZEND_PRE_INC_STATIC_PROP:
192
6.69k
        case ZEND_PRE_DEC_STATIC_PROP:
193
6.78k
        case ZEND_POST_INC_STATIC_PROP:
194
6.82k
        case ZEND_POST_DEC_STATIC_PROP:
195
7.01k
        case ZEND_ASSIGN_STATIC_PROP_OP:
196
7.01k
          if (opline->op2_type == IS_CONST) {
197
5.25k
            LITERAL_INFO(opline->op2.constant, 2);
198
5.25k
          }
199
7.01k
          if (opline->op1_type == IS_CONST) {
200
6.23k
            LITERAL_INFO(opline->op1.constant, 1);
201
6.23k
          }
202
7.01k
          break;
203
1.86k
        case ZEND_FETCH_CLASS:
204
2.38k
        case ZEND_INSTANCEOF:
205
2.38k
          if (opline->op2_type == IS_CONST) {
206
430
            LITERAL_INFO(opline->op2.constant, 2);
207
430
          }
208
2.38k
          break;
209
58.9k
        case ZEND_NEW:
210
58.9k
          if (opline->op1_type == IS_CONST) {
211
57.4k
            LITERAL_INFO(opline->op1.constant, 2);
212
57.4k
          }
213
58.9k
          break;
214
5.53k
        case ZEND_DECLARE_CLASS:
215
6.97k
        case ZEND_DECLARE_CLASS_DELAYED:
216
6.97k
          LITERAL_INFO(opline->op1.constant, 2);
217
6.97k
          if (opline->op2_type == IS_CONST) {
218
2.16k
            LITERAL_INFO(opline->op2.constant, 1);
219
2.16k
          }
220
6.97k
          break;
221
3.01k
        case ZEND_ISSET_ISEMPTY_DIM_OBJ:
222
19.5k
        case ZEND_ASSIGN_DIM:
223
21.3k
        case ZEND_UNSET_DIM:
224
35.7k
        case ZEND_FETCH_DIM_R:
225
42.3k
        case ZEND_FETCH_DIM_W:
226
43.3k
        case ZEND_FETCH_DIM_RW:
227
45.5k
        case ZEND_FETCH_DIM_IS:
228
46.3k
        case ZEND_FETCH_DIM_FUNC_ARG:
229
46.5k
        case ZEND_FETCH_DIM_UNSET:
230
47.9k
        case ZEND_FETCH_LIST_R:
231
48.3k
        case ZEND_FETCH_LIST_W:
232
51.5k
        case ZEND_ASSIGN_DIM_OP:
233
51.5k
          if (opline->op1_type == IS_CONST) {
234
709
            LITERAL_INFO(opline->op1.constant, 1);
235
709
          }
236
51.5k
          if (opline->op2_type == IS_CONST) {
237
28.7k
            if (Z_EXTRA(op_array->literals[opline->op2.constant]) == ZEND_EXTRA_VALUE) {
238
655
              LITERAL_INFO(opline->op2.constant, 2);
239
28.0k
            } else {
240
28.0k
              LITERAL_INFO(opline->op2.constant, 1);
241
28.0k
            }
242
28.7k
          }
243
51.5k
          break;
244
2.43M
        default:
245
2.43M
          if (opline->op1_type == IS_CONST) {
246
379k
            LITERAL_INFO(opline->op1.constant, 1);
247
379k
          }
248
2.43M
          if (opline->op2_type == IS_CONST) {
249
564k
            LITERAL_INFO(opline->op2.constant, 1);
250
564k
          }
251
2.43M
          break;
252
2.69M
      }
253
2.69M
      opline++;
254
2.69M
    }
255
256
#if DEBUG_COMPACT_LITERALS
257
    {
258
      fprintf(stderr, "File %s func %s\n", op_array->filename->val,
259
          op_array->function_name ? op_array->function_name->val : "main");
260
      fprintf(stderr, "Literals table size %d\n", op_array->last_literal);
261
262
      for (uint32_t i = 0; i < op_array->last_literal; i++) {
263
        zend_string *str = zval_get_string(op_array->literals + i);
264
        fprintf(stderr, "Literal %" PRIu32 ", val (%zu):%s\n", i, ZSTR_LEN(str), ZSTR_VAL(str));
265
        zend_string_release(str);
266
      }
267
      fflush(stderr);
268
    }
269
#endif
270
271
    /* Merge equal constants */
272
115k
    j = 0;
273
115k
    zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0);
274
115k
    map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int));
275
115k
    memset(map, 0, op_array->last_literal * sizeof(int));
276
1.37M
    for (uint32_t i = 0; i < op_array->last_literal; i++) {
277
1.26M
      if (!info[i].num_related) {
278
        /* unset literal */
279
76.0k
        zval_ptr_dtor_nogc(&op_array->literals[i]);
280
76.0k
        continue;
281
76.0k
      }
282
1.18M
      switch (Z_TYPE(op_array->literals[i])) {
283
47.6k
        case IS_NULL:
284
47.6k
          ZEND_ASSERT(info[i].num_related == 1);
285
47.6k
          if (l_null < 0) {
286
38.3k
            l_null = j;
287
38.3k
            if (i != j) {
288
11.5k
              op_array->literals[j] = op_array->literals[i];
289
11.5k
              info[j] = info[i];
290
11.5k
            }
291
38.3k
            j++;
292
38.3k
          }
293
47.6k
          map[i] = l_null;
294
47.6k
          break;
295
5.13k
        case IS_FALSE:
296
5.13k
          ZEND_ASSERT(info[i].num_related == 1);
297
5.13k
          if (l_false < 0) {
298
3.07k
            l_false = j;
299
3.07k
            if (i != j) {
300
1.27k
              op_array->literals[j] = op_array->literals[i];
301
1.27k
              info[j] = info[i];
302
1.27k
            }
303
3.07k
            j++;
304
3.07k
          }
305
5.13k
          map[i] = l_false;
306
5.13k
          break;
307
5.84k
        case IS_TRUE:
308
5.84k
          ZEND_ASSERT(info[i].num_related == 1);
309
5.84k
          if (l_true < 0) {
310
3.43k
            l_true = j;
311
3.43k
            if (i != j) {
312
1.37k
              op_array->literals[j] = op_array->literals[i];
313
1.37k
              info[j] = info[i];
314
1.37k
            }
315
3.43k
            j++;
316
3.43k
          }
317
5.84k
          map[i] = l_true;
318
5.84k
          break;
319
192k
        case IS_LONG:
320
192k
          if (info[i].num_related == 1) {
321
191k
            if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) {
322
61.7k
              map[i] = Z_LVAL_P(pos);
323
130k
            } else {
324
130k
              map[i] = j;
325
130k
              ZVAL_LONG(&zv, j);
326
130k
              zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv);
327
130k
              if (i != j) {
328
52.6k
                op_array->literals[j] = op_array->literals[i];
329
52.6k
                info[j] = info[i];
330
52.6k
              }
331
130k
              j++;
332
130k
            }
333
191k
          } else {
334
655
            ZEND_ASSERT(info[i].num_related == 2);
335
655
            key = zend_string_init(Z_STRVAL(op_array->literals[i+1]), Z_STRLEN(op_array->literals[i+1]), 0);
336
655
            bias_key(key, 100 + info[i].num_related - 1);
337
655
            if ((pos = zend_hash_find(&hash, key)) != NULL) {
338
278
              ZEND_ASSERT(info[Z_LVAL_P(pos)].num_related == 2);
339
278
              map[i] = Z_LVAL_P(pos);
340
278
              zval_ptr_dtor_nogc(&op_array->literals[i+1]);
341
377
            } else {
342
377
              map[i] = j;
343
377
              ZVAL_LONG(&zv, j);
344
377
              zend_hash_add_new(&hash, key, &zv);
345
377
              if (i != j) {
346
293
                op_array->literals[j] = op_array->literals[i];
347
293
                info[j] = info[i];
348
293
                op_array->literals[j+1] = op_array->literals[i+1];
349
293
                info[j+1] = info[i+1];
350
293
              }
351
377
              j += 2;
352
377
            }
353
655
            zend_string_release_ex(key, 0);
354
655
            i++;
355
655
          }
356
192k
          break;
357
192k
        case IS_DOUBLE:
358
11.9k
          ZEND_ASSERT(info[i].num_related == 1);
359
11.9k
          key = zend_string_init((char*)&Z_DVAL(op_array->literals[i]), sizeof(double), 0);
360
11.9k
          bias_key(key, 200);
361
11.9k
          if ((pos = zend_hash_find(&hash, key))) {
362
3.11k
            map[i] = Z_LVAL_P(pos);
363
8.81k
          } else {
364
8.81k
            map[i] = j;
365
8.81k
            ZVAL_LONG(&zv, j);
366
8.81k
            zend_hash_add_new(&hash, key, &zv);
367
8.81k
            if (i != j) {
368
4.70k
              op_array->literals[j] = op_array->literals[i];
369
4.70k
              info[j] = info[i];
370
4.70k
            }
371
8.81k
            j++;
372
8.81k
          }
373
11.9k
          zend_string_release_ex(key, 0);
374
11.9k
          break;
375
895k
        case IS_STRING: {
376
895k
          key = create_str_cache_key(&op_array->literals[i], info[i].num_related);
377
895k
          if ((pos = zend_hash_find(&hash, key)) != NULL) {
378
338k
            ZEND_ASSERT(Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) == IS_STRING &&
379
338k
              info[i].num_related == info[Z_LVAL_P(pos)].num_related);
380
338k
            zend_string_release_ex(key, 0);
381
338k
            map[i] = Z_LVAL_P(pos);
382
338k
            zval_ptr_dtor_nogc(&op_array->literals[i]);
383
338k
            n = info[i].num_related;
384
407k
            while (n > 1) {
385
68.4k
              i++;
386
68.4k
              zval_ptr_dtor_nogc(&op_array->literals[i]);
387
68.4k
              n--;
388
68.4k
            }
389
556k
          } else {
390
556k
            map[i] = j;
391
556k
            ZVAL_LONG(&zv, j);
392
556k
            zend_hash_add_new(&hash, key, &zv);
393
556k
            zend_string_release_ex(key, 0);
394
556k
            if (i != j) {
395
169k
              op_array->literals[j] = op_array->literals[i];
396
169k
              info[j] = info[i];
397
169k
            }
398
556k
            j++;
399
556k
            n = info[i].num_related;
400
691k
            while (n > 1) {
401
134k
              i++;
402
134k
              if (i != j) op_array->literals[j] = op_array->literals[i];
403
134k
              j++;
404
134k
              n--;
405
134k
            }
406
556k
          }
407
895k
          break;
408
895k
        }
409
895k
        case IS_ARRAY:
410
24.1k
          ZEND_ASSERT(info[i].num_related == 1);
411
24.1k
          if (zend_hash_num_elements(Z_ARRVAL(op_array->literals[i])) == 0) {
412
5.04k
            if (l_empty_arr < 0) {
413
3.86k
              l_empty_arr = j;
414
3.86k
              if (i != j) {
415
1.44k
                op_array->literals[j] = op_array->literals[i];
416
1.44k
                info[j] = info[i];
417
1.44k
              }
418
3.86k
              j++;
419
3.86k
            } else {
420
1.17k
              zval_ptr_dtor_nogc(&op_array->literals[i]);
421
1.17k
            }
422
5.04k
            map[i] = l_empty_arr;
423
5.04k
            break;
424
5.04k
          }
425
19.1k
          ZEND_FALLTHROUGH;
426
21.3k
        default:
427
          /* don't merge other types */
428
21.3k
          ZEND_ASSERT(info[i].num_related == 1);
429
21.3k
          map[i] = j;
430
21.3k
          if (i != j) {
431
4.78k
            op_array->literals[j] = op_array->literals[i];
432
4.78k
            info[j] = info[i];
433
4.78k
          }
434
21.3k
          j++;
435
21.3k
          break;
436
1.18M
      }
437
1.18M
    }
438
439
    /* Only clean "hash", as it will be reused in the loop below. */
440
115k
    zend_hash_clean(&hash);
441
115k
    op_array->last_literal = j;
442
443
115k
    const_slot = zend_arena_alloc(&ctx->arena, j * 8 * sizeof(int));
444
115k
    memset(const_slot, -1, j * 8 * sizeof(int));
445
115k
    class_slot = const_slot + j;
446
115k
    func_slot = class_slot + j;
447
115k
    bind_var_slot = func_slot + j;
448
115k
    property_slot = bind_var_slot + j;
449
115k
    method_slot = property_slot + j;
450
115k
    jmp_slot = method_slot + j;
451
115k
    assign_obj_slots = jmp_slot + j;
452
453
    /* Update opcodes to use new literals table */
454
115k
    cache_size = zend_op_array_extension_handles * sizeof(void*);
455
115k
    opline = op_array->opcodes;
456
115k
    end = opline + op_array->last;
457
2.80M
    while (opline < end) {
458
2.69M
      if (opline->op1_type == IS_CONST) {
459
492k
        opline->op1.constant = map[opline->op1.constant];
460
492k
      }
461
2.69M
      if (opline->op2_type == IS_CONST) {
462
692k
        opline->op2.constant = map[opline->op2.constant];
463
692k
      }
464
2.69M
      switch (opline->opcode) {
465
192
        case ZEND_ASSIGN_STATIC_PROP_OP:
466
192
          if (opline->op1_type == IS_CONST) {
467
            // op1 static property
468
192
            if (opline->op2_type == IS_CONST) {
469
188
              (opline+1)->extended_value = add_static_slot(&hash, op_array,
470
188
                opline->op2.constant,
471
188
                opline->op1.constant,
472
188
                LITERAL_STATIC_PROPERTY,
473
188
                &cache_size);
474
188
            } else {
475
4
              (opline+1)->extended_value = cache_size;
476
4
              cache_size += 3 * sizeof(void *);
477
4
            }
478
192
          } else if (opline->op2_type == IS_CONST) {
479
            // op2 class
480
0
            if (class_slot[opline->op2.constant] >= 0) {
481
0
              (opline+1)->extended_value = class_slot[opline->op2.constant];
482
0
            } else {
483
0
              (opline+1)->extended_value = cache_size;
484
0
              class_slot[opline->op2.constant] = cache_size;
485
0
              cache_size += sizeof(void *);
486
0
            }
487
0
          }
488
192
          break;
489
1.30k
        case ZEND_ASSIGN_OBJ_OP:
490
1.30k
          if (opline->op2_type == IS_CONST) {
491
            // op2 property
492
1.00k
            if (opline->op1_type == IS_UNUSED &&
493
229
                property_slot[opline->op2.constant] >= 0) {
494
74
              (opline+1)->extended_value = property_slot[opline->op2.constant];
495
933
            } else {
496
933
              (opline+1)->extended_value = cache_size;
497
933
              cache_size += 3 * sizeof(void *);
498
933
              if (opline->op1_type == IS_UNUSED) {
499
155
                property_slot[opline->op2.constant] = (opline+1)->extended_value;
500
155
              }
501
933
            }
502
1.00k
          }
503
1.30k
          break;
504
15.2k
        case ZEND_ASSIGN_OBJ:
505
15.2k
          if (opline->op2_type == IS_CONST) {
506
13.7k
            if (opline->op1_type == IS_UNUSED &&
507
4.97k
              assign_obj_slots[opline->op2.constant] >= 0) {
508
266
              opline->extended_value = assign_obj_slots[opline->op2.constant];
509
13.4k
            } else {
510
13.4k
              opline->extended_value = cache_size;
511
13.4k
              cache_size += 3 * sizeof(void *);
512
13.4k
              if (opline->op1_type == IS_UNUSED) {
513
4.70k
                assign_obj_slots[opline->op2.constant] = opline->extended_value;
514
4.70k
              }
515
13.4k
            }
516
13.7k
          }
517
15.2k
          break;
518
962
        case ZEND_ASSIGN_OBJ_REF:
519
55.3k
        case ZEND_FETCH_OBJ_R:
520
60.7k
        case ZEND_FETCH_OBJ_W:
521
60.9k
        case ZEND_FETCH_OBJ_RW:
522
63.3k
        case ZEND_FETCH_OBJ_IS:
523
63.6k
        case ZEND_FETCH_OBJ_UNSET:
524
64.1k
        case ZEND_FETCH_OBJ_FUNC_ARG:
525
65.4k
        case ZEND_UNSET_OBJ:
526
66.2k
        case ZEND_PRE_INC_OBJ:
527
66.6k
        case ZEND_PRE_DEC_OBJ:
528
66.9k
        case ZEND_POST_INC_OBJ:
529
67.1k
        case ZEND_POST_DEC_OBJ:
530
67.1k
          if (opline->op2_type == IS_CONST) {
531
            // op2 property
532
64.3k
            if (opline->op1_type == IS_UNUSED &&
533
6.92k
                property_slot[opline->op2.constant] >= 0) {
534
1.29k
              opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
535
63.0k
            } else {
536
63.0k
              opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
537
63.0k
              cache_size += 3 * sizeof(void *);
538
63.0k
              if (opline->op1_type == IS_UNUSED) {
539
5.62k
                property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS;
540
5.62k
              }
541
63.0k
            }
542
64.3k
          }
543
67.1k
          break;
544
1.35k
        case ZEND_ISSET_ISEMPTY_PROP_OBJ:
545
1.35k
          if (opline->op2_type == IS_CONST) {
546
            // op2 property
547
1.09k
            if (opline->op1_type == IS_UNUSED &&
548
128
                property_slot[opline->op2.constant] >= 0) {
549
12
              opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY);
550
1.07k
            } else {
551
1.07k
              opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
552
1.07k
              cache_size += 3 * sizeof(void *);
553
1.07k
              if (opline->op1_type == IS_UNUSED) {
554
116
                property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY;
555
116
              }
556
1.07k
            }
557
1.09k
          }
558
1.35k
          break;
559
137k
        case ZEND_INIT_FCALL:
560
144k
        case ZEND_INIT_FCALL_BY_NAME:
561
146k
        case ZEND_INIT_NS_FCALL_BY_NAME:
562
          // op2 func
563
146k
          if (func_slot[opline->op2.constant] >= 0) {
564
65.6k
            opline->result.num = func_slot[opline->op2.constant];
565
81.2k
          } else {
566
81.2k
            opline->result.num = cache_size;
567
81.2k
            cache_size += sizeof(void *);
568
81.2k
            func_slot[opline->op2.constant] = opline->result.num;
569
81.2k
          }
570
146k
          break;
571
44.9k
        case ZEND_INIT_METHOD_CALL:
572
44.9k
          if (opline->op2_type == IS_CONST) {
573
            // op2 method
574
44.4k
            if (opline->op1_type == IS_UNUSED &&
575
875
                method_slot[opline->op2.constant] >= 0) {
576
96
              opline->result.num = method_slot[opline->op2.constant];
577
44.3k
            } else {
578
44.3k
              opline->result.num = cache_size;
579
44.3k
              cache_size += 2 * sizeof(void *);
580
44.3k
              if (opline->op1_type == IS_UNUSED) {
581
779
                method_slot[opline->op2.constant] = opline->result.num;
582
779
              }
583
44.3k
            }
584
44.4k
          }
585
44.9k
          break;
586
7.51k
        case ZEND_INIT_STATIC_METHOD_CALL:
587
7.51k
          if (opline->op2_type == IS_CONST) {
588
            // op2 static method
589
6.84k
            if (opline->op1_type == IS_CONST) {
590
5.75k
              opline->result.num = add_static_slot(&hash, op_array,
591
5.75k
                opline->op1.constant,
592
5.75k
                opline->op2.constant,
593
5.75k
                LITERAL_STATIC_METHOD,
594
5.75k
                &cache_size);
595
5.75k
            } else {
596
1.09k
              opline->result.num = cache_size;
597
1.09k
              cache_size += 2 * sizeof(void *);
598
1.09k
            }
599
6.84k
          } else if (opline->op1_type == IS_CONST) {
600
            // op1 class
601
117
            if (class_slot[opline->op1.constant] >= 0) {
602
48
              opline->result.num = class_slot[opline->op1.constant];
603
69
            } else {
604
69
              opline->result.num = cache_size;
605
69
              cache_size += sizeof(void *);
606
69
              class_slot[opline->op1.constant] = opline->result.num;
607
69
            }
608
117
          }
609
7.51k
          break;
610
142
        case ZEND_DEFINED:
611
          // op1 const
612
142
          if (const_slot[opline->op1.constant] >= 0) {
613
58
            opline->extended_value = const_slot[opline->op1.constant];
614
84
          } else {
615
84
            opline->extended_value = cache_size;
616
84
            cache_size += sizeof(void *);
617
84
            const_slot[opline->op1.constant] = opline->extended_value;
618
84
          }
619
142
          break;
620
26.9k
        case ZEND_FETCH_CONSTANT:
621
          // op2 const
622
26.9k
          if (const_slot[opline->op2.constant] >= 0) {
623
15.1k
            opline->extended_value = const_slot[opline->op2.constant];
624
15.1k
          } else {
625
11.8k
            opline->extended_value = cache_size;
626
11.8k
            cache_size += sizeof(void *);
627
11.8k
            const_slot[opline->op2.constant] = opline->extended_value;
628
11.8k
          }
629
26.9k
          break;
630
4.02k
        case ZEND_FETCH_CLASS_CONSTANT:
631
4.02k
          if (opline->op1_type == IS_CONST
632
3.03k
            && opline->op2_type == IS_CONST
633
2.98k
            && Z_TYPE(op_array->literals[opline->op2.constant]) == IS_STRING) {
634
            // op1/op2 class_const
635
2.98k
            opline->extended_value = add_static_slot(&hash, op_array,
636
2.98k
              opline->op1.constant,
637
2.98k
              opline->op2.constant,
638
2.98k
              LITERAL_CLASS_CONST,
639
2.98k
              &cache_size);
640
2.98k
          } else {
641
1.03k
            opline->extended_value = cache_size;
642
1.03k
            cache_size += 2 * sizeof(void *);
643
1.03k
          }
644
4.02k
          break;
645
1.71k
        case ZEND_ASSIGN_STATIC_PROP:
646
2.03k
        case ZEND_ASSIGN_STATIC_PROP_REF:
647
4.89k
        case ZEND_FETCH_STATIC_PROP_R:
648
5.46k
        case ZEND_FETCH_STATIC_PROP_W:
649
5.46k
        case ZEND_FETCH_STATIC_PROP_RW:
650
6.04k
        case ZEND_FETCH_STATIC_PROP_IS:
651
6.15k
        case ZEND_FETCH_STATIC_PROP_UNSET:
652
6.45k
        case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
653
6.48k
        case ZEND_UNSET_STATIC_PROP:
654
6.64k
        case ZEND_ISSET_ISEMPTY_STATIC_PROP:
655
6.69k
        case ZEND_PRE_INC_STATIC_PROP:
656
6.69k
        case ZEND_PRE_DEC_STATIC_PROP:
657
6.78k
        case ZEND_POST_INC_STATIC_PROP:
658
6.82k
        case ZEND_POST_DEC_STATIC_PROP:
659
6.82k
          if (opline->op1_type == IS_CONST) {
660
            // op1 static property
661
6.04k
            if (opline->op2_type == IS_CONST) {
662
4.32k
              opline->extended_value = add_static_slot(&hash, op_array,
663
4.32k
                opline->op2.constant,
664
4.32k
                opline->op1.constant,
665
4.32k
                LITERAL_STATIC_PROPERTY,
666
4.32k
                &cache_size) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
667
4.32k
            } else {
668
1.72k
              opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
669
1.72k
              cache_size += 3 * sizeof(void *);
670
1.72k
            }
671
6.04k
          } else if (opline->op2_type == IS_CONST) {
672
            // op2 class
673
742
            if (class_slot[opline->op2.constant] >= 0) {
674
630
              opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
675
630
            } else {
676
112
              opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
677
112
              class_slot[opline->op2.constant] = cache_size;
678
112
              cache_size += sizeof(void *);
679
112
            }
680
742
          }
681
6.82k
          break;
682
1.86k
        case ZEND_FETCH_CLASS:
683
2.38k
        case ZEND_INSTANCEOF:
684
2.38k
          if (opline->op2_type == IS_CONST) {
685
            // op2 class
686
430
            if (class_slot[opline->op2.constant] >= 0) {
687
192
              opline->extended_value = class_slot[opline->op2.constant];
688
238
            } else {
689
238
              opline->extended_value = cache_size;
690
238
              cache_size += sizeof(void *);
691
238
              class_slot[opline->op2.constant] = opline->extended_value;
692
238
            }
693
430
          }
694
2.38k
          break;
695
58.9k
        case ZEND_NEW:
696
58.9k
          if (opline->op1_type == IS_CONST) {
697
            // op1 class
698
57.4k
            if (class_slot[opline->op1.constant] >= 0) {
699
12.4k
              opline->op2.num = class_slot[opline->op1.constant];
700
44.9k
            } else {
701
44.9k
              opline->op2.num = cache_size;
702
44.9k
              cache_size += sizeof(void *);
703
44.9k
              class_slot[opline->op1.constant] = opline->op2.num;
704
44.9k
            }
705
57.4k
          }
706
58.9k
          break;
707
32.7k
        case ZEND_CATCH:
708
32.7k
          if (opline->op1_type == IS_CONST) {
709
            // op1 class
710
32.7k
            if (class_slot[opline->op1.constant] >= 0) {
711
7.86k
              opline->extended_value = class_slot[opline->op1.constant] | (opline->extended_value & ZEND_LAST_CATCH);
712
24.8k
            } else {
713
24.8k
              opline->extended_value = cache_size | (opline->extended_value & ZEND_LAST_CATCH);
714
24.8k
              cache_size += sizeof(void *);
715
24.8k
              class_slot[opline->op1.constant] = opline->extended_value & ~ZEND_LAST_CATCH;
716
24.8k
            }
717
32.7k
          }
718
32.7k
          break;
719
1.15k
        case ZEND_BIND_GLOBAL:
720
          // op2 bind var
721
1.15k
          if (bind_var_slot[opline->op2.constant] >= 0) {
722
66
            opline->extended_value = bind_var_slot[opline->op2.constant];
723
1.08k
          } else {
724
1.08k
            opline->extended_value = cache_size;
725
1.08k
            cache_size += sizeof(void *);
726
1.08k
            bind_var_slot[opline->op2.constant] = opline->extended_value;
727
1.08k
          }
728
1.15k
          break;
729
839
        case ZEND_DECLARE_ANON_CLASS:
730
2.28k
        case ZEND_DECLARE_CLASS_DELAYED:
731
2.28k
          opline->extended_value = cache_size;
732
2.28k
          cache_size += sizeof(void *);
733
2.28k
          break;
734
0
        case ZEND_JMP_FRAMELESS:
735
          // op1 func
736
0
          if (jmp_slot[opline->op1.constant] >= 0) {
737
0
            opline->extended_value = jmp_slot[opline->op1.constant];
738
0
          } else {
739
0
            opline->extended_value = cache_size;
740
0
            cache_size += sizeof(void *);
741
0
            jmp_slot[opline->op1.constant] = opline->extended_value;
742
0
          }
743
0
          break;
744
138k
        case ZEND_SEND_VAL:
745
159k
        case ZEND_SEND_VAL_EX:
746
229k
        case ZEND_SEND_VAR:
747
243k
        case ZEND_SEND_VAR_EX:
748
243k
        case ZEND_SEND_VAR_NO_REF:
749
245k
        case ZEND_SEND_VAR_NO_REF_EX:
750
248k
        case ZEND_SEND_REF:
751
250k
        case ZEND_SEND_FUNC_ARG:
752
251k
        case ZEND_SEND_PLACEHOLDER:
753
253k
        case ZEND_CHECK_FUNC_ARG:
754
253k
          if (opline->op2_type == IS_CONST) {
755
4.41k
            opline->result.num = cache_size;
756
4.41k
            cache_size += 2 * sizeof(void *);
757
4.41k
          }
758
253k
          break;
759
1.27k
        case ZEND_CALLABLE_CONVERT:
760
11.3k
        case ZEND_DECLARE_LAMBDA_FUNCTION:
761
11.3k
          if (opline->extended_value != (uint32_t)-1) {
762
741
            opline->extended_value = cache_size;
763
741
            cache_size += sizeof(void *);
764
741
          }
765
11.3k
          break;
766
1.27k
        case ZEND_CALLABLE_CONVERT_PARTIAL:
767
1.27k
          opline->extended_value = cache_size | (opline->extended_value & ZEND_PARTIAL_FLAGS);
768
1.27k
          cache_size += 2 * sizeof(void *);
769
1.27k
          break;
770
2.69M
      }
771
2.69M
      opline++;
772
2.69M
    }
773
115k
    op_array->cache_size = cache_size;
774
115k
    zend_hash_destroy(&hash);
775
115k
    zend_arena_release(&ctx->arena, checkpoint);
776
777
115k
    if (1) {
778
115k
      opline = op_array->opcodes;
779
151k
      while (1) {
780
151k
        if (opline->opcode == ZEND_RECV_INIT) {
781
4.65k
          zval *val = &op_array->literals[opline->op2.constant];
782
783
4.65k
          if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
784
            /* Ensure zval is aligned to 8 bytes */
785
1.08k
            op_array->cache_size = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8);
786
1.08k
            Z_CACHE_SLOT_P(val) = op_array->cache_size;
787
1.08k
            op_array->cache_size += sizeof(zval);
788
1.08k
          }
789
146k
        } else if (opline->opcode != ZEND_RECV) {
790
115k
          break;
791
115k
        }
792
35.5k
        opline++;
793
35.5k
      }
794
115k
    }
795
796
#if DEBUG_COMPACT_LITERALS
797
    {
798
      fprintf(stderr, "Optimized literals table size %d\n", op_array->last_literal);
799
800
      for (uint32_t i = 0; i < op_array->last_literal; i++) {
801
        zend_string *str = zval_get_string(op_array->literals + i);
802
        fprintf(stderr, "Literal %" PRIu32 ", val (%zu):%s\n", i, ZSTR_LEN(str), ZSTR_VAL(str));
803
        zend_string_release(str);
804
      }
805
      fflush(stderr);
806
    }
807
#endif
808
115k
  }
809
115k
}