root/ext/opcache/Optimizer/compact_literals.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. optimizer_literal_obj_info
  2. optimizer_literal_class_info
  3. optimizer_compact_literals

/* pass 11
 * - compact literals table
 */
#if ZEND_EXTENSION_API_NO > PHP_5_3_X_API_NO

#define DEBUG_COMPACT_LITERALS 0

#define LITERAL_VALUE                        0x0100
#define LITERAL_FUNC                         0x0200
#define LITERAL_CLASS                        0x0300
#define LITERAL_CONST                        0x0400
#define LITERAL_CLASS_CONST                  0x0500
#define LITERAL_STATIC_METHOD                0x0600
#define LITERAL_STATIC_PROPERTY              0x0700
#define LITERAL_METHOD                       0x0800
#define LITERAL_PROPERTY                     0x0900

#define LITERAL_EX_CLASS                     0x4000
#define LITERAL_EX_OBJ                       0x2000
#define LITERAL_MAY_MERGE                    0x1000
#define LITERAL_KIND_MASK                    0x0f00
#define LITERAL_NUM_RELATED_MASK             0x000f
#define LITERAL_NUM_SLOTS_MASK               0x00f0
#define LITERAL_NUM_SLOTS_SHIFT              4

#define LITERAL_NUM_RELATED(info) (info & LITERAL_NUM_RELATED_MASK)
#define LITERAL_NUM_SLOTS(info)   ((info & LITERAL_NUM_SLOTS_MASK) >> LITERAL_NUM_SLOTS_SHIFT)

typedef struct _literal_info {
        zend_uint  flags; /* bitmask (see defines above) */
        union {
                int    num;   /* variable number or class name literal number */
        } u;
} literal_info;

#define LITERAL_FLAGS(kind, slots, related) \
        ((kind) | ((slots) << LITERAL_NUM_SLOTS_SHIFT) | (related))

#define LITERAL_INFO(n, kind, merge, slots, related) do { \
                info[n].flags = (((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \
        } while (0)

#define LITERAL_INFO_CLASS(n, kind, merge, slots, related, _num) do { \
                info[n].flags = (LITERAL_EX_CLASS | ((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \
                info[n].u.num = (_num); \
        } while (0)

#define LITERAL_INFO_OBJ(n, kind, merge, slots, related, _num) do { \
                info[n].flags = (LITERAL_EX_OBJ | ((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \
                info[n].u.num = (_num); \
        } while (0)

static void optimizer_literal_obj_info(literal_info   *info,
                                       zend_uchar      op_type,
                                       znode_op        op,
                                       int             constant,
                                       zend_uint       kind,
                                       zend_uint       slots,
                                       zend_uint       related,
                                       zend_op_array  *op_array)
{
        /* For now we merge only $this object properties and methods.
         * In general it's also possible to do it for any CV variable as well,
         * but it would require complex dataflow and/or type analysis.
         */
        if (Z_TYPE(op_array->literals[constant].constant) == IS_STRING &&
            op_type == IS_UNUSED) {
                LITERAL_INFO_OBJ(constant, kind, 1, slots, related, op_array->this_var);
        } else {
                LITERAL_INFO(constant, kind, 0, slots, related);
        }
}

static void optimizer_literal_class_info(literal_info   *info,
                                         zend_uchar      op_type,
                                         znode_op        op,
                                         int             constant,
                                         zend_uint       kind,
                                         zend_uint       slots,
                                         zend_uint       related,
                                         zend_op_array  *op_array)
{
        if (op_type == IS_CONST) {
                LITERAL_INFO_CLASS(constant, kind, 1, slots, related, op.constant);
        } else {
                LITERAL_INFO(constant, kind, 0, slots, related);
        }
}

static void optimizer_compact_literals(zend_op_array *op_array TSRMLS_DC)
{
        zend_op *opline, *end;
        int i, j, n, *pos, *map, cache_slots;
        ulong h;
        literal_info *info;
        int l_null = -1;
        int l_false = -1;
        int l_true = -1;
        HashTable hash;
        char *key;
        int key_len;

        if (op_array->last_literal) {
                info = (literal_info*)ecalloc(op_array->last_literal, sizeof(literal_info));

            /* Mark literals of specific types */
                opline = op_array->opcodes;
                end = opline + op_array->last;
                while (opline < end) {
                        switch (opline->opcode) {
                                case ZEND_DO_FCALL:
                                        LITERAL_INFO(opline->op1.constant, LITERAL_FUNC, 1, 1, 1);
                                        break;
                                case ZEND_INIT_FCALL_BY_NAME:
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 2);
                                        }
                                        break;
                                case ZEND_INIT_NS_FCALL_BY_NAME:
                                        LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 3);
                                        break;
                                case ZEND_INIT_METHOD_CALL:
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                optimizer_literal_obj_info(
                                                        info,
                                                        opline->op1_type,
                                                        opline->op1,
                                                        opline->op2.constant,
                                                        LITERAL_METHOD, 2, 2,
                                                        op_array);
                                        }
                                        break;
                                case ZEND_INIT_STATIC_METHOD_CALL:
                                        if (ZEND_OP1_TYPE(opline) == IS_CONST) {
                                                LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
                                        }
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                optimizer_literal_class_info(
                                                        info,
                                                        opline->op1_type,
                                                        opline->op1,
                                                        opline->op2.constant,
                                                        LITERAL_STATIC_METHOD, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 2,
                                                        op_array);
                                        }
                                        break;
                                case ZEND_CATCH:
                                        LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
                                        break;
                                case ZEND_FETCH_CONSTANT:
                                        if (ZEND_OP1_TYPE(opline) == IS_UNUSED) {
                                                if ((opline->extended_value & (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) == (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) {
                                                        LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 5);
                                                } else {
                                                        LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 3);
                                                }
                                        } else {
                                                if (ZEND_OP1_TYPE(opline) == IS_CONST) {
                                                        LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
                                                }
                                                optimizer_literal_class_info(
                                                        info,
                                                        opline->op1_type,
                                                        opline->op1,
                                                        opline->op2.constant,
                                                        LITERAL_CLASS_CONST, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 1,
                                                        op_array);
                                        }
                                        break;
                                case ZEND_FETCH_R:
                                case ZEND_FETCH_W:
                                case ZEND_FETCH_RW:
                                case ZEND_FETCH_IS:
                                case ZEND_FETCH_UNSET:
                                case ZEND_FETCH_FUNC_ARG:
                                case ZEND_UNSET_VAR:
                                case ZEND_ISSET_ISEMPTY_VAR:
                                        if (ZEND_OP2_TYPE(opline) == IS_UNUSED) {
                                                if (ZEND_OP1_TYPE(opline) == IS_CONST) {
                                                        LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1, 0, 1);
                                                }
                                        } else {
                                                if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                        LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2);
                                                }
                                                if (ZEND_OP1_TYPE(opline) == IS_CONST) {
                                                        optimizer_literal_class_info(
                                                                info,
                                                                opline->op2_type,
                                                                opline->op2,
                                                                opline->op1.constant,
                                                                LITERAL_STATIC_PROPERTY, 2, 1,
                                                                op_array);
                                                }
                                        }
                                        break;
                                case ZEND_FETCH_CLASS:
                                case ZEND_ADD_INTERFACE:
                                case ZEND_ADD_TRAIT:
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2);
                                        }
                                        break;
                                case ZEND_ASSIGN_OBJ:
                                case ZEND_FETCH_OBJ_R:
                                case ZEND_FETCH_OBJ_W:
                                case ZEND_FETCH_OBJ_RW:
                                case ZEND_FETCH_OBJ_IS:
                                case ZEND_FETCH_OBJ_UNSET:
                                case ZEND_FETCH_OBJ_FUNC_ARG:
                                case ZEND_UNSET_OBJ:
                                case ZEND_PRE_INC_OBJ:
                                case ZEND_PRE_DEC_OBJ:
                                case ZEND_POST_INC_OBJ:
                                case ZEND_POST_DEC_OBJ:
                                case ZEND_ISSET_ISEMPTY_PROP_OBJ:
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                optimizer_literal_obj_info(
                                                        info,
                                                        opline->op1_type,
                                                        opline->op1,
                                                        opline->op2.constant,
                                                        LITERAL_PROPERTY, 2, 1,
                                                        op_array);
                                        }
                                        break;
                                case ZEND_ASSIGN_ADD:
                                case ZEND_ASSIGN_SUB:
                                case ZEND_ASSIGN_MUL:
                                case ZEND_ASSIGN_DIV:
                                case ZEND_ASSIGN_MOD:
                                case ZEND_ASSIGN_SL:
                                case ZEND_ASSIGN_SR:
                                case ZEND_ASSIGN_CONCAT:
                                case ZEND_ASSIGN_BW_OR:
                                case ZEND_ASSIGN_BW_AND:
                                case ZEND_ASSIGN_BW_XOR:
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                if (opline->extended_value == ZEND_ASSIGN_OBJ) {
                                                        optimizer_literal_obj_info(
                                                                info,
                                                                opline->op1_type,
                                                                opline->op1,
                                                                opline->op2.constant,
                                                                LITERAL_PROPERTY, 2, 1,
                                                                op_array);
                                                } else {
                                                        LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1, 0, 1);
                                                }
                                        }
                                        break;
                                default:
                                        if (ZEND_OP1_TYPE(opline) == IS_CONST) {
                                                LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1, 0, 1);
                                        }
                                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                                LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1, 0, 1);
                                        }
                                        break;
                        }
                        opline++;
                }

#if DEBUG_COMPACT_LITERALS
                {
                        int i, use_copy;
                        fprintf(stderr, "File %s func %s\n", op_array->filename,
                                        op_array->function_name? op_array->function_name : "main");
                        fprintf(stderr, "Literlas table size %d\n", op_array->last_literal);

                        for (i = 0; i < op_array->last_literal; i++) {
                                zval zv = op_array->literals[i].constant;
                                zend_make_printable_zval(&op_array->literals[i].constant, &zv, &use_copy);
                                fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv));
                                if (use_copy) {
                                        zval_dtor(&zv);
                                }
                        }
                        fflush(stderr);
                }
#endif

                /* Merge equal constants */
                j = 0; cache_slots = 0;
                zend_hash_init(&hash, 16, NULL, NULL, 0);
                map = (int*)ecalloc(op_array->last_literal, sizeof(int));
                for (i = 0; i < op_array->last_literal; i++) {
                        if (!info[i].flags) {
                                /* unsed literal */
                                zval_dtor(&op_array->literals[i].constant);
                                continue;
                        }
                        switch (Z_TYPE(op_array->literals[i].constant)) {
                                case IS_NULL:
                                        if (l_null < 0) {
                                                l_null = j;
                                                if (i != j) {
                                                        op_array->literals[j] = op_array->literals[i];
                                                        info[j] = info[i];
                                                }
                                                j++;
                                        }
                                        map[i] = l_null;
                                        break;
                                case IS_BOOL:
                                        if (Z_LVAL(op_array->literals[i].constant)) {
                                                if (l_true < 0) {
                                                        l_true = j;
                                                        if (i != j) {
                                                                op_array->literals[j] = op_array->literals[i];
                                                                info[j] = info[i];
                                                        }
                                                        j++;
                                                }
                                                map[i] = l_true;
                                        } else {
                                                if (l_false < 0) {
                                                        l_false = j;
                                                        if (i != j) {
                                                                op_array->literals[j] = op_array->literals[i];
                                                                info[j] = info[i];
                                                        }
                                                        j++;
                                                }
                                                map[i] = l_false;
                                        }
                                        break;
                                case IS_LONG:
                                        if (zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i].constant), (void**)&pos) == SUCCESS) {
                                                map[i] = *pos;
                                        } else {
                                                map[i] = j;
                                                zend_hash_index_update(&hash, Z_LVAL(op_array->literals[i].constant), (void**)&j, sizeof(int), NULL);
                                                if (i != j) {
                                                        op_array->literals[j] = op_array->literals[i];
                                                        info[j] = info[i];
                                                }
                                                j++;
                                        }
                                        break;
                                case IS_DOUBLE:
                                        if (zend_hash_find(&hash, (char*)&Z_DVAL(op_array->literals[i].constant), sizeof(double), (void**)&pos) == SUCCESS) {
                                                map[i] = *pos;
                                        } else {
                                                map[i] = j;
                                                zend_hash_add(&hash, (char*)&Z_DVAL(op_array->literals[i].constant), sizeof(double), (void**)&j, sizeof(int), NULL);
                                                if (i != j) {
                                                        op_array->literals[j] = op_array->literals[i];
                                                        info[j] = info[i];
                                                }
                                                j++;
                                        }
                                        break;
                                case IS_STRING:
                                case IS_CONSTANT:
                                        if (info[i].flags & LITERAL_MAY_MERGE) {
                                                if (info[i].flags & LITERAL_EX_OBJ) {
                                                        key_len = MAX_LENGTH_OF_LONG + sizeof("->") + Z_STRLEN(op_array->literals[i].constant);
                                                        key = emalloc(key_len);
                                                        key_len = snprintf(key, key_len-1, "%d->%s", info[i].u.num, Z_STRVAL(op_array->literals[i].constant));
                                                } else if (info[i].flags & LITERAL_EX_CLASS) {
                                                        zval *class_name = &op_array->literals[(info[i].u.num < i) ? map[info[i].u.num] : info[i].u.num].constant;
                                                        key_len = Z_STRLEN_P(class_name) + sizeof("::") + Z_STRLEN(op_array->literals[i].constant);
                                                        key = emalloc(key_len);
                                                        memcpy(key, Z_STRVAL_P(class_name), Z_STRLEN_P(class_name));
                                                        memcpy(key + Z_STRLEN_P(class_name), "::", sizeof("::") - 1);
                                                        memcpy(key + Z_STRLEN_P(class_name) + sizeof("::") - 1,
                                                                Z_STRVAL(op_array->literals[i].constant),
                                                                Z_STRLEN(op_array->literals[i].constant) + 1);
                                                } else {
                                                        key = Z_STRVAL(op_array->literals[i].constant);
                                                        key_len = Z_STRLEN(op_array->literals[i].constant)+1;
                                                }
                                                h = zend_hash_func(key, key_len);
                                                h += info[i].flags;
                                        }
                                        if ((info[i].flags & LITERAL_MAY_MERGE) &&
                                                zend_hash_quick_find(&hash, key, key_len, h, (void**)&pos) == SUCCESS &&
                                                Z_TYPE(op_array->literals[i].constant) == Z_TYPE(op_array->literals[*pos].constant) &&
                                                info[i].flags == info[*pos].flags) {

                                                if (info[i].flags & (LITERAL_EX_OBJ|LITERAL_EX_CLASS)) {
                                                        efree(key);
                                                }
                                                map[i] = *pos;
                                                zval_dtor(&op_array->literals[i].constant);
                                                n = LITERAL_NUM_RELATED(info[i].flags);
                                                while (n > 1) {
                                                        i++;
                                                        zval_dtor(&op_array->literals[i].constant);
                                                        n--;
                                                }
                                        } else {
                                                map[i] = j;
                                                if (info[i].flags & LITERAL_MAY_MERGE) {
                                                        zend_hash_quick_add(&hash, key, key_len, h, (void**)&j, sizeof(int), NULL);
                                                        if (info[i].flags & (LITERAL_EX_OBJ|LITERAL_EX_CLASS)) {
                                                                efree(key);
                                                        }
                                                }
                                                if (i != j) {
                                                        op_array->literals[j] = op_array->literals[i];
                                                        info[j] = info[i];
                                                }
                                                if (!op_array->literals[j].hash_value) {
                                                        if (IS_INTERNED(Z_STRVAL(op_array->literals[j].constant))) {
                                                                op_array->literals[j].hash_value = INTERNED_HASH(Z_STRVAL(op_array->literals[j].constant));
                                                        } else {
                                                                op_array->literals[j].hash_value = zend_hash_func(Z_STRVAL(op_array->literals[j].constant), Z_STRLEN(op_array->literals[j].constant)+1);
                                                        }
                                                }
                                                if (LITERAL_NUM_SLOTS(info[i].flags)) {
                                                        op_array->literals[j].cache_slot = cache_slots;
                                                        cache_slots += LITERAL_NUM_SLOTS(info[i].flags);
                                                }
                                                j++;
                                                n = LITERAL_NUM_RELATED(info[i].flags);
                                                while (n > 1) {
                                                        i++;
                                                        if (i != j) op_array->literals[j] = op_array->literals[i];
                                                        if (!op_array->literals[j].hash_value) {
                                                                if (IS_INTERNED(Z_STRVAL(op_array->literals[j].constant))) {
                                                                        op_array->literals[j].hash_value = INTERNED_HASH(Z_STRVAL(op_array->literals[j].constant));
                                                                } else {
                                                                        op_array->literals[j].hash_value = zend_hash_func(Z_STRVAL(op_array->literals[j].constant), Z_STRLEN(op_array->literals[j].constant)+1);
                                                                }
                                                        }
                                                        j++;
                                                        n--;
                                                }
                                        }
                                        break;
                                default:
                                        /* don't merge other types */
                                        map[i] = j;
                                        if (i != j) {
                                                op_array->literals[j] = op_array->literals[i];
                                                info[j] = info[i];
                                        }
                                        j++;
                                        break;
                        }
                }
                zend_hash_destroy(&hash);
                op_array->last_literal = j;
                op_array->last_cache_slot = cache_slots;

            /* Update opcodes to use new literals table */
                opline = op_array->opcodes;
                end = opline + op_array->last;
                while (opline < end) {
                        if (ZEND_OP1_TYPE(opline) == IS_CONST) {
                                opline->op1.constant = map[opline->op1.constant];
                        }
                        if (ZEND_OP2_TYPE(opline) == IS_CONST) {
                                opline->op2.constant = map[opline->op2.constant];
                        }
                        opline++;
                }
                efree(map);
                efree(info);

#if DEBUG_COMPACT_LITERALS
                {
                        int i, use_copy;
                        fprintf(stderr, "Optimized literlas table size %d\n", op_array->last_literal);

                        for (i = 0; i < op_array->last_literal; i++) {
                                zval zv = op_array->literals[i].constant;
                                zend_make_printable_zval(&op_array->literals[i].constant, &zv, &use_copy);
                                fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv));
                                if (use_copy) {
                                        zval_dtor(&zv);
                                }
                        }
                        fflush(stderr);
                }
#endif
        }
}
#endif

/* [<][>][^][v][top][bottom][index][help] */