Reduce memory usage by using bitsets instead of array of bytes.

This commit is contained in:
Dmitry Stogov 2015-08-06 15:41:50 +03:00
parent 6d681876ee
commit 90cb3bb7de
3 changed files with 256 additions and 62 deletions

185
Zend/zend_bitset.h Normal file
View File

@ -0,0 +1,185 @@
/*
+----------------------------------------------------------------------+
| Zend OPcache JIT |
+----------------------------------------------------------------------+
| Copyright (c) 1998-2014 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov <dmitry@zend.com> |
+----------------------------------------------------------------------+
*/
/* $Id:$ */
#ifndef _ZEND_BITSET_H_
#define _ZEND_BITSET_H_
typedef zend_ulong *zend_bitset;
#define ZEND_BITSET_ELM_SIZE sizeof(zend_ulong)
#if SIZEOF_ZEND_LONG == 4
# define ZEND_BITSET_ELM_NUM(n) ((n) >> 5)
# define ZEND_BITSET_BIT_NUM(n) ((zend_ulong)(n) & Z_UL(0x1f))
#elif SIZEOF_ZEND_LONG == 8
# define ZEND_BITSET_ELM_NUM(n) ((n) >> 6)
# define ZEND_BITSET_BIT_NUM(n) ((zend_ulong)(n) & Z_UL(0x3f))
#else
# define ZEND_BITSET_ELM_NUM(n) ((n) / (sizeof(zend_long) * 8))
# define ZEND_BITSET_BIT_NUM(n) ((n) % (sizeof(zend_long) * 8))
#endif
/* Returns the number of zend_ulong words needed to store a bitset that is N
bits long. */
static inline uint32_t zend_bitset_len(uint32_t n)
{
return (n + ((sizeof(zend_long) * 8) - 1)) / (sizeof(zend_long) * 8);
}
#define ZEND_BITSET_ALLOCA(n, use_heap) \
(zend_bitset)do_alloca((n) * ZEND_BITSET_ELM_SIZE, use_heap)
static inline zend_bool zend_bitset_in(zend_bitset set, uint32_t n)
{
return (set[ZEND_BITSET_ELM_NUM(n)] & (Z_UL(1) << ZEND_BITSET_BIT_NUM(n))) != Z_UL(0);
}
static inline void zend_bitset_incl(zend_bitset set, uint32_t n)
{
set[ZEND_BITSET_ELM_NUM(n)] |= Z_UL(1) << ZEND_BITSET_BIT_NUM(n);
}
static inline void zend_bitset_excl(zend_bitset set, uint32_t n)
{
set[ZEND_BITSET_ELM_NUM(n)] &= ~(Z_UL(1) << ZEND_BITSET_BIT_NUM(n));
}
static inline void zend_bitset_clear(zend_bitset set, uint32_t len)
{
memset(set, 0, len * ZEND_BITSET_ELM_SIZE);
}
static inline int zend_bitset_empty(zend_bitset set, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
if (set[i]) {
return 0;
}
}
return 1;
}
static inline void zend_bitset_fill(zend_bitset set, uint32_t len)
{
memset(set, 0xff, len * ZEND_BITSET_ELM_SIZE);
}
static inline zend_bool zend_bitset_equal(zend_bitset set1, zend_bitset set2, uint32_t len)
{
return memcmp(set1, set2, len * ZEND_BITSET_ELM_SIZE) == 0;
}
static inline void zend_bitset_copy(zend_bitset set1, zend_bitset set2, uint32_t len)
{
memcpy(set1, set2, len * ZEND_BITSET_ELM_SIZE);
}
static inline void zend_bitset_intersection(zend_bitset set1, zend_bitset set2, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
set1[i] &= set2[i];
}
}
static inline void zend_bitset_union(zend_bitset set1, zend_bitset set2, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
set1[i] |= set2[i];
}
}
static inline void zend_bitset_difference(zend_bitset set1, zend_bitset set2, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
set1[i] = set1[i] & ~set2[i];
}
}
static inline void zend_bitset_union_with_intersection(zend_bitset set1, zend_bitset set2, zend_bitset set3, zend_bitset set4, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
set1[i] = set2[i] | (set3[i] & set4[i]);
}
}
static inline void zend_bitset_union_with_difference(zend_bitset set1, zend_bitset set2, zend_bitset set3, zend_bitset set4, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
set1[i] = set2[i] | (set3[i] & ~set4[i]);
}
}
static inline int zend_bitset_first(zend_bitset set, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i++) {
if (set[i]) {
int j = ZEND_BITSET_ELM_SIZE * 8 * i;
zend_ulong x = set[i];
while ((x & Z_UL(1)) == 0) {
x = x >> Z_UL(1);
j++;
}
return j;
}
}
return -1; /* empty set */
}
static inline int zend_bitset_last(zend_bitset set, uint32_t len)
{
uint32_t i = len;
while (i > 0) {
i--;
if (set[i]) {
int j = ZEND_BITSET_ELM_SIZE * 8 * i - 1;
zend_ulong x = set[i];
while (x != Z_UL(0)) {
x = x >> Z_UL(1);
j++;
}
return j;
}
}
return -1; /* empty set */
}
#endif /* _ZEND_BITSET_H_ */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode: t
* End:
*/

View File

@ -26,6 +26,7 @@
#include "zend_constants.h"
#include "zend_execute.h"
#include "zend_vm.h"
#include "zend_bitset.h"
#define DEBUG_BLOCKPASS 0
@ -601,7 +602,7 @@ static void strip_nop(zend_code_block *block, zend_optimizer_ctx *ctx)
block->len = new_end - block->start_opline;
}
static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, char *used_ext, zend_cfg *cfg, zend_optimizer_ctx *ctx)
static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_optimizer_ctx *ctx)
{
zend_op *opline = block->start_opline;
zend_op *end, *last_op = NULL;
@ -799,7 +800,7 @@ static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array,
opline->opcode == ZEND_JMPZNZ) &&
ZEND_OP1_TYPE(opline) == IS_TMP_VAR &&
VAR_SOURCE(opline->op1) != NULL &&
!used_ext[VAR_NUM(ZEND_OP1(opline).var)] &&
!zend_bitset_in(used_ext, VAR_NUM(ZEND_OP1(opline).var)) &&
VAR_SOURCE(opline->op1)->opcode == ZEND_BOOL_NOT) {
/* T = BOOL_NOT(X) + JMPZ(T) -> NOP, JMPNZ(X) */
zend_op *src = VAR_SOURCE(opline->op1);
@ -874,7 +875,7 @@ static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array,
opline->opcode == ZEND_JMPZNZ) &&
(ZEND_OP1_TYPE(opline) & (IS_TMP_VAR|IS_VAR)) &&
VAR_SOURCE(opline->op1) != NULL &&
(!used_ext[VAR_NUM(ZEND_OP1(opline).var)] ||
(!zend_bitset_in(used_ext, VAR_NUM(ZEND_OP1(opline).var)) ||
((ZEND_RESULT_TYPE(opline) & (IS_TMP_VAR|IS_VAR)) &&
ZEND_RESULT(opline).var == ZEND_OP1(opline).var)) &&
(VAR_SOURCE(opline->op1)->opcode == ZEND_BOOL ||
@ -1112,7 +1113,7 @@ static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array,
VAR_SOURCE(opline->op1)->opcode == ZEND_IS_NOT_IDENTICAL ||
VAR_SOURCE(opline->op1)->opcode == ZEND_ISSET_ISEMPTY_VAR ||
VAR_SOURCE(opline->op1)->opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ) &&
!used_ext[VAR_NUM(ZEND_OP1(opline).var)]) {
!zend_bitset_in(used_ext, VAR_NUM(ZEND_OP1(opline).var))) {
/* T = IS_SMALLER(X, Y), T1 = BOOL(T) => T = IS_SMALLER(X, Y), T1 = QM_ASSIGN(T) */
zend_op *src = VAR_SOURCE(opline->op1);
COPY_NODE(src->result, opline->result);
@ -1771,21 +1772,22 @@ next_target_znz:
#define T_USAGE(op) do { \
if ((op ## _type & (IS_VAR | IS_TMP_VAR)) && \
!defined_here[VAR_NUM(op.var)] && !used_ext[VAR_NUM(op.var)]) { \
used_ext[VAR_NUM(op.var)] = 1; \
!zend_bitset_in(defined_here, VAR_NUM(op.var)) && !zend_bitset_in(used_ext, VAR_NUM(op.var))) { \
zend_bitset_incl(used_ext, VAR_NUM(op.var)); \
} \
} while (0)
#define NEVER_USED(op) ((op ## _type & (IS_VAR | IS_TMP_VAR)) && !usage[VAR_NUM(op.var)]) /* !used_ext[op.var] && */
#define NEVER_USED(op) ((op ## _type & (IS_VAR | IS_TMP_VAR)) && !zend_bitset_in(usage, VAR_NUM(op.var))) /* !zend_bitset_in(used_ext, op.var) && */
#define RES_NEVER_USED(opline) (opline->result_type == IS_UNUSED || NEVER_USED(opline->result))
/* Find a set of variables which are used outside of the block where they are
* defined. We won't apply some optimization patterns for such variables. */
static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *used_ext, zend_optimizer_ctx *ctx)
static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx)
{
zend_code_block *next_block = block->next;
char *usage;
char *defined_here;
uint32_t bitset_len;
zend_bitset usage;
zend_bitset defined_here;
void *checkpoint;
if (op_array->T == 0) {
@ -1794,9 +1796,10 @@ static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *
}
checkpoint = zend_arena_checkpoint(ctx->arena);
usage = zend_arena_alloc(&ctx->arena, op_array->last_var + op_array->T);
memset(usage, 0, op_array->last_var + op_array->T);
defined_here = zend_arena_alloc(&ctx->arena, op_array->last_var + op_array->T);
bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
zend_bitset_clear(usage, bitset_len);
defined_here = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
while (next_block) {
zend_op *opline = next_block->start_opline;
@ -1806,26 +1809,26 @@ static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *
next_block = next_block->next;
continue;
}
memset(defined_here, 0, op_array->last_var + op_array->T);
zend_bitset_clear(defined_here, bitset_len);
while (opline<end) {
T_USAGE(opline->op1);
if (opline->op2_type & (IS_VAR | IS_TMP_VAR)) {
if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) {
/* these opcode use the op2 as result */
defined_here[VAR_NUM(ZEND_OP2(opline).var)] = 1;
zend_bitset_incl(defined_here, VAR_NUM(ZEND_OP2(opline).var));
} else {
T_USAGE(opline->op2);
}
}
if (RESULT_USED(opline)) {
if (!defined_here[VAR_NUM(ZEND_RESULT(opline).var)] && !used_ext[VAR_NUM(ZEND_RESULT(opline).var)] &&
if (!zend_bitset_in(defined_here, VAR_NUM(ZEND_RESULT(opline).var)) && !zend_bitset_in(used_ext, VAR_NUM(ZEND_RESULT(opline).var)) &&
opline->opcode == ZEND_ADD_ARRAY_ELEMENT) {
/* these opcode use the result as argument */
used_ext[VAR_NUM(ZEND_RESULT(opline).var)] = 1;
zend_bitset_incl(used_ext, VAR_NUM(ZEND_RESULT(opline).var));
}
defined_here[VAR_NUM(ZEND_RESULT(opline).var)] = 1;
zend_bitset_incl(defined_here, VAR_NUM(ZEND_RESULT(opline).var));
}
opline++;
}
@ -1836,7 +1839,7 @@ static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *
{
int i;
for (i = op_array->last_var; i< op_array->T; i++) {
fprintf(stderr, "T%d: %c\n", i, used_ext[i] + '0');
fprintf(stderr, "T%d: %c\n", i, zend_bitset_in(used_ext, i) + '0');
}
}
#endif
@ -1849,7 +1852,7 @@ static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *
continue;
}
memcpy(usage, used_ext, op_array->last_var + op_array->T);
zend_bitset_copy(usage, used_ext, bitset_len);
while (opline >= block->start_opline) {
/* usage checks */
@ -1898,25 +1901,25 @@ static void zend_t_usage(zend_code_block *block, zend_op_array *op_array, char *
if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) {
if (ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_TMP_VAR) {
usage[VAR_NUM(ZEND_RESULT(opline).var)] = 1;
zend_bitset_incl(usage, VAR_NUM(ZEND_RESULT(opline).var));
}
} else {
if (RESULT_USED(opline)) {
usage[VAR_NUM(ZEND_RESULT(opline).var)] = 0;
zend_bitset_excl(usage, VAR_NUM(ZEND_RESULT(opline).var));
}
}
if (ZEND_OP1_TYPE(opline) == IS_VAR || ZEND_OP1_TYPE(opline) == IS_TMP_VAR) {
usage[VAR_NUM(ZEND_OP1(opline).var)] = 1;
zend_bitset_incl(usage, VAR_NUM(ZEND_OP1(opline).var));
}
if (ZEND_OP2_TYPE(opline) == IS_VAR || ZEND_OP2_TYPE(opline) == IS_TMP_VAR) {
usage[VAR_NUM(ZEND_OP2(opline).var)] = 1;
zend_bitset_incl(usage, VAR_NUM(ZEND_OP2(opline).var));
}
if ((ZEND_RESULT_TYPE(opline) & IS_VAR) &&
(ZEND_RESULT_TYPE(opline) & EXT_TYPE_UNUSED) &&
usage[VAR_NUM(ZEND_RESULT(opline).var)]) {
zend_bitset_in(usage, VAR_NUM(ZEND_RESULT(opline).var))) {
ZEND_RESULT_TYPE(opline) &= ~EXT_TYPE_UNUSED;
}
@ -1935,7 +1938,8 @@ void optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
zend_cfg cfg;
zend_code_block *cur_block;
int pass;
char *usage;
uint32_t bitset_len;
zend_bitset usage;
void *checkpoint;
#if DEBUG_BLOCKPASS
@ -1957,17 +1961,19 @@ void optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
zend_rebuild_access_path(&cfg, op_array, 0, ctx);
/* full rebuild here to produce correct sources! */
if (op_array->last_var || op_array->T) {
bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
cfg.Tsource = zend_arena_calloc(&ctx->arena, op_array->last_var + op_array->T, sizeof(zend_op *));
cfg.same_t = zend_arena_alloc(&ctx->arena, op_array->last_var + op_array->T);
usage = zend_arena_alloc(&ctx->arena, op_array->last_var + op_array->T);
usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
} else {
bitset_len = 0;
cfg.Tsource = NULL;
cfg.same_t = NULL;
usage = NULL;
}
for (pass = 0; pass < PASSES; pass++) {
/* Compute data dependencies */
memset(usage, 0, op_array->last_var + op_array->T);
zend_bitset_clear(usage, bitset_len);
zend_t_usage(cfg.blocks, op_array, usage, ctx);
/* optimize each basic block separately */
@ -1990,7 +1996,7 @@ void optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
zend_rebuild_access_path(&cfg, op_array, 1, ctx);
}
memset(usage, 0, op_array->last_var + op_array->T);
zend_bitset_clear(usage, bitset_len);
zend_t_usage(cfg.blocks, op_array, usage, ctx);
assemble_code_blocks(&cfg, op_array);

View File

@ -26,25 +26,27 @@
#include "zend_constants.h"
#include "zend_execute.h"
#include "zend_vm.h"
#include "zend_bitset.h"
#define GET_AVAILABLE_T() \
for (i = 0; i < T; i++) { \
if (!taken_T[i]) { \
break; \
} \
} \
taken_T[i] = 1; \
if (i > max) { \
max = i; \
#define GET_AVAILABLE_T() \
for (i = 0; i < T; i++) { \
if (!zend_bitset_in(taken_T, i)) { \
break; \
} \
} \
zend_bitset_incl(taken_T, i); \
if (i > max) { \
max = i; \
}
void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx)
{
int T = op_array->T;
int offset = op_array->last_var;
char *taken_T; /* T index in use */
uint32_t bitset_len;
zend_bitset taken_T; /* T index in use */
zend_op **start_of_T; /* opline where T is first used */
char *valid_T; /* Is the map_T valid */
zend_bitset valid_T; /* Is the map_T valid */
int *map_T; /* Map's the T to its new index */
zend_op *opline, *end;
int currT;
@ -53,9 +55,10 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
int var_to_free = -1;
void *checkpoint = zend_arena_checkpoint(ctx->arena);
taken_T = (char *) zend_arena_alloc(&ctx->arena, T);
bitset_len = zend_bitset_len(T);
taken_T = (zend_bitset) zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
start_of_T = (zend_op **) zend_arena_alloc(&ctx->arena, T * sizeof(zend_op *));
valid_T = (char *) zend_arena_alloc(&ctx->arena, T);
valid_T = (zend_bitset) zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
map_T = (int *) zend_arena_alloc(&ctx->arena, T * sizeof(int));
end = op_array->opcodes;
@ -69,8 +72,8 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
opline--;
}
memset(valid_T, 0, T);
memset(taken_T, 0, T);
zend_bitset_clear(valid_T, bitset_len);
zend_bitset_clear(taken_T, bitset_len);
end = op_array->opcodes;
opline = &op_array->opcodes[op_array->last - 1];
@ -83,24 +86,24 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
int var;
var = max;
while (var >= 0 && !taken_T[var]) {
while (var >= 0 && !zend_bitset_in(taken_T, var)) {
var--;
}
max = MAX(max, var + num);
var = var + 1;
map_T[currT] = var;
valid_T[currT] = 1;
taken_T[var] = 1;
zend_bitset_incl(valid_T, currT);
zend_bitset_incl(taken_T, var);
ZEND_OP1(opline).var = NUM_VAR(var + offset);
while (num > 1) {
num--;
taken_T[var + num] = 1;
zend_bitset_incl(taken_T, var + num);
}
} else {
if (!valid_T[currT]) {
if (!zend_bitset_in(valid_T, currT)) {
GET_AVAILABLE_T();
map_T[currT] = i;
valid_T[currT] = 1;
zend_bitset_incl(valid_T, currT);
}
ZEND_OP1(opline).var = NUM_VAR(map_T[currT] + offset);
}
@ -115,10 +118,10 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) {
currT = VAR_NUM(ZEND_OP2(opline).var) - offset;
if (!valid_T[currT]) {
if (!zend_bitset_in(valid_T, currT)) {
GET_AVAILABLE_T();
map_T[currT] = i;
valid_T[currT] = 1;
zend_bitset_incl(valid_T, currT);
}
ZEND_OP2(opline).var = NUM_VAR(map_T[currT] + offset);
}
@ -127,10 +130,10 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
opline->opcode == ZEND_DECLARE_ANON_INHERITED_CLASS ||
opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) {
currT = VAR_NUM(opline->extended_value) - offset;
if (!valid_T[currT]) {
if (!zend_bitset_in(valid_T, currT)) {
GET_AVAILABLE_T();
map_T[currT] = i;
valid_T[currT] = 1;
zend_bitset_incl(valid_T, currT);
}
opline->extended_value = NUM_VAR(map_T[currT] + offset);
}
@ -142,21 +145,21 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
currT = VAR_NUM(ZEND_OP2(opline + 1).var) - offset;
GET_AVAILABLE_T();
map_T[currT] = i;
valid_T[currT] = 1;
taken_T[i] = 0;
zend_bitset_incl(valid_T, currT);
zend_bitset_excl(taken_T, i);
ZEND_OP2(opline + 1).var = NUM_VAR(i + offset);
var_to_free = i;
}
if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) {
currT = VAR_NUM(ZEND_RESULT(opline).var) - offset;
if (valid_T[currT]) {
if (zend_bitset_in(valid_T, currT)) {
if (start_of_T[currT] == opline) {
/* ZEND_FAST_CALL can not share temporary var with others
* since the fast_var could also be set by ZEND_HANDLE_EXCEPTION
* which could be ahead of it */
if (opline->opcode != ZEND_FAST_CALL) {
taken_T[map_T[currT]] = 0;
zend_bitset_excl(taken_T, map_T[currT]);
}
}
ZEND_RESULT(opline).var = NUM_VAR(map_T[currT] + offset);
@ -165,7 +168,7 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
while (num > 1) {
num--;
taken_T[map_T[currT]+num] = 0;
zend_bitset_excl(taken_T, map_T[currT]+num);
}
}
}
@ -173,18 +176,18 @@ void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *c
GET_AVAILABLE_T();
if (RESULT_UNUSED(opline)) {
taken_T[i] = 0;
zend_bitset_excl(taken_T, i);
} else {
/* Code which gets here is using a wrongly built opcode such as RECV() */
map_T[currT] = i;
valid_T[currT] = 1;
zend_bitset_incl(valid_T, currT);
}
ZEND_RESULT(opline).var = NUM_VAR(i + offset);
}
}
if (var_to_free >= 0) {
taken_T[var_to_free] = 0;
zend_bitset_excl(taken_T, var_to_free);
var_to_free = -1;
}