Coverage Report

Created: 2025-12-14 06:09

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 (c) The PHP Group                                          |
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
   | https://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
   | Authors: Nikita Popov <nikic@php.net>                                |
16
   +----------------------------------------------------------------------+
17
*/
18
19
#include "Optimizer/zend_optimizer_internal.h"
20
#include "zend_bitset.h"
21
#include "zend_observer.h"
22
23
/* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs.
24
 * This pass does not operate on SSA form anymore. */
25
54.8k
void zend_optimizer_compact_vars(zend_op_array *op_array) {
26
54.8k
  int i;
27
28
54.8k
  ALLOCA_FLAG(use_heap1);
29
54.8k
  ALLOCA_FLAG(use_heap2);
30
54.8k
  uint32_t used_vars_len = zend_bitset_len(op_array->last_var + op_array->T);
31
54.8k
  zend_bitset used_vars = ZEND_BITSET_ALLOCA(used_vars_len, use_heap1);
32
54.8k
  uint32_t *vars_map = do_alloca((op_array->last_var + op_array->T) * sizeof(uint32_t), use_heap2);
33
54.8k
  uint32_t num_cvs, num_tmps;
34
35
  /* Determine which CVs are used */
36
54.8k
  zend_bitset_clear(used_vars, used_vars_len);
37
1.47M
  for (i = 0; i < op_array->last; i++) {
38
1.42M
    zend_op *opline = &op_array->opcodes[i];
39
1.42M
    if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
40
817k
      zend_bitset_incl(used_vars, VAR_NUM(opline->op1.var));
41
817k
    }
42
1.42M
    if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
43
320k
      zend_bitset_incl(used_vars, VAR_NUM(opline->op2.var));
44
320k
    }
45
1.42M
    if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
46
736k
      zend_bitset_incl(used_vars, VAR_NUM(opline->result.var));
47
736k
      if (opline->opcode == ZEND_ROPE_INIT) {
48
12.8k
        uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
49
103k
        while (num > 1) {
50
91.1k
          num--;
51
91.1k
          zend_bitset_incl(used_vars, VAR_NUM(opline->result.var) + num);
52
91.1k
        }
53
12.8k
      }
54
736k
    }
55
1.42M
  }
56
57
54.8k
  num_cvs = 0;
58
270k
  for (i = 0; i < op_array->last_var; i++) {
59
215k
    if (zend_bitset_in(used_vars, i)) {
60
213k
      vars_map[i] = num_cvs++;
61
213k
    } else {
62
2.13k
      vars_map[i] = (uint32_t) -1;
63
2.13k
    }
64
215k
  }
65
66
54.8k
  num_tmps = 0;
67
270k
  for (i = op_array->last_var; i < op_array->last_var + op_array->T; i++) {
68
215k
    if (zend_bitset_in(used_vars, i)) {
69
215k
      vars_map[i] = num_cvs + num_tmps++;
70
215k
    } else {
71
0
      vars_map[i] = (uint32_t) -1;
72
0
    }
73
215k
  }
74
75
54.8k
  free_alloca(used_vars, use_heap1);
76
54.8k
  if (num_cvs == op_array->last_var && num_tmps == op_array->T) {
77
53.5k
    free_alloca(vars_map, use_heap2);
78
53.5k
    return;
79
53.5k
  }
80
81
1.34k
  ZEND_ASSERT(num_cvs <= op_array->last_var);
82
1.34k
  ZEND_ASSERT(num_tmps <= op_array->T);
83
84
  /* Update CV and TMP references in opcodes */
85
59.5k
  for (i = 0; i < op_array->last; i++) {
86
58.2k
    zend_op *opline = &op_array->opcodes[i];
87
58.2k
    if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
88
41.7k
      opline->op1.var = NUM_VAR(vars_map[VAR_NUM(opline->op1.var)]);
89
41.7k
    }
90
58.2k
    if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
91
15.7k
      opline->op2.var = NUM_VAR(vars_map[VAR_NUM(opline->op2.var)]);
92
15.7k
    }
93
58.2k
    if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
94
37.6k
      opline->result.var = NUM_VAR(vars_map[VAR_NUM(opline->result.var)]);
95
37.6k
    }
96
58.2k
  }
97
98
  /* Update CV name table */
99
1.34k
  if (num_cvs != op_array->last_var) {
100
1.34k
    if (num_cvs) {
101
1.14k
      zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0);
102
8.88k
      for (i = 0; i < op_array->last_var; i++) {
103
7.73k
        if (vars_map[i] != (uint32_t) -1) {
104
5.89k
          names[vars_map[i]] = op_array->vars[i];
105
5.89k
        } else {
106
1.84k
          zend_string_release_ex(op_array->vars[i], 0);
107
1.84k
        }
108
7.73k
      }
109
1.14k
      efree(op_array->vars);
110
1.14k
      op_array->vars = names;
111
1.14k
    } else {
112
500
      for (i = 0; i < op_array->last_var; i++) {
113
297
        zend_string_release_ex(op_array->vars[i], 0);
114
297
      }
115
203
      efree(op_array->vars);
116
203
      op_array->vars = NULL;
117
203
    }
118
1.34k
    op_array->last_var = num_cvs;
119
1.34k
  }
120
121
1.34k
  op_array->T = num_tmps + ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled
122
123
  free_alloca(vars_map, use_heap2);
124
1.34k
}