Coverage Report

Created: 2026-06-02 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/Optimizer/compact_vars.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine, Removing unused variables                               |
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: Nikita Popov <nikic@php.net>                                |
14
   +----------------------------------------------------------------------+
15
*/
16
17
#include "Optimizer/zend_optimizer_internal.h"
18
#include "zend_bitset.h"
19
#include "zend_observer.h"
20
21
/* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs.
22
 * This pass does not operate on SSA form anymore. */
23
45.3k
void zend_optimizer_compact_vars(zend_op_array *op_array) {
24
45.3k
  int i;
25
26
45.3k
  ALLOCA_FLAG(use_heap1);
27
45.3k
  ALLOCA_FLAG(use_heap2);
28
45.3k
  uint32_t used_vars_len = zend_bitset_len(op_array->last_var + op_array->T);
29
45.3k
  zend_bitset used_vars = ZEND_BITSET_ALLOCA(used_vars_len, use_heap1);
30
45.3k
  uint32_t *vars_map = do_alloca((op_array->last_var + op_array->T) * sizeof(uint32_t), use_heap2);
31
45.3k
  uint32_t num_cvs, num_tmps;
32
33
  /* Determine which CVs are used */
34
45.3k
  zend_bitset_clear(used_vars, used_vars_len);
35
1.17M
  for (i = 0; i < op_array->last; i++) {
36
1.13M
    zend_op *opline = &op_array->opcodes[i];
37
1.13M
    if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
38
639k
      zend_bitset_incl(used_vars, VAR_NUM(opline->op1.var));
39
639k
    }
40
1.13M
    if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
41
232k
      zend_bitset_incl(used_vars, VAR_NUM(opline->op2.var));
42
232k
    }
43
1.13M
    if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
44
551k
      zend_bitset_incl(used_vars, VAR_NUM(opline->result.var));
45
551k
      if (opline->opcode == ZEND_ROPE_INIT) {
46
13.2k
        uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
47
86.6k
        while (num > 1) {
48
73.3k
          num--;
49
73.3k
          zend_bitset_incl(used_vars, VAR_NUM(opline->result.var) + num);
50
73.3k
        }
51
13.2k
      }
52
551k
    }
53
1.13M
  }
54
55
45.3k
  num_cvs = 0;
56
220k
  for (i = 0; i < op_array->last_var; i++) {
57
175k
    if (zend_bitset_in(used_vars, i)) {
58
172k
      vars_map[i] = num_cvs++;
59
172k
    } else {
60
2.43k
      vars_map[i] = (uint32_t) -1;
61
2.43k
    }
62
175k
  }
63
64
45.3k
  num_tmps = 0;
65
210k
  for (i = op_array->last_var; i < op_array->last_var + op_array->T; i++) {
66
165k
    if (zend_bitset_in(used_vars, i)) {
67
165k
      vars_map[i] = num_cvs + num_tmps++;
68
165k
    } else {
69
0
      vars_map[i] = (uint32_t) -1;
70
0
    }
71
165k
  }
72
73
45.3k
  free_alloca(used_vars, use_heap1);
74
45.3k
  if (num_cvs == op_array->last_var && num_tmps == op_array->T) {
75
43.8k
    free_alloca(vars_map, use_heap2);
76
43.8k
    return;
77
43.8k
  }
78
79
1.48k
  ZEND_ASSERT(num_cvs <= op_array->last_var);
80
1.48k
  ZEND_ASSERT(num_tmps <= op_array->T);
81
82
  /* Update CV and TMP references in opcodes */
83
65.1k
  for (i = 0; i < op_array->last; i++) {
84
63.6k
    zend_op *opline = &op_array->opcodes[i];
85
63.6k
    if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
86
45.3k
      opline->op1.var = NUM_VAR(vars_map[VAR_NUM(opline->op1.var)]);
87
45.3k
    }
88
63.6k
    if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
89
18.4k
      opline->op2.var = NUM_VAR(vars_map[VAR_NUM(opline->op2.var)]);
90
18.4k
    }
91
63.6k
    if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
92
40.2k
      opline->result.var = NUM_VAR(vars_map[VAR_NUM(opline->result.var)]);
93
40.2k
    }
94
63.6k
  }
95
96
  /* Update CV name table */
97
1.48k
  if (num_cvs != op_array->last_var) {
98
1.48k
    if (num_cvs) {
99
1.36k
      zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0);
100
11.0k
      for (i = 0; i < op_array->last_var; i++) {
101
9.69k
        if (vars_map[i] != (uint32_t) -1) {
102
7.43k
          names[vars_map[i]] = op_array->vars[i];
103
7.43k
        } else {
104
2.26k
          zend_string_release_ex(op_array->vars[i], 0);
105
2.26k
        }
106
9.69k
      }
107
1.36k
      efree(op_array->vars);
108
1.36k
      op_array->vars = names;
109
1.36k
    } else {
110
298
      for (i = 0; i < op_array->last_var; i++) {
111
175
        zend_string_release_ex(op_array->vars[i], 0);
112
175
      }
113
123
      efree(op_array->vars);
114
123
      op_array->vars = NULL;
115
123
    }
116
1.48k
    op_array->last_var = num_cvs;
117
1.48k
  }
118
119
1.48k
  op_array->T = num_tmps + ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled
120
121
  free_alloca(vars_map, use_heap2);
122
1.48k
}