/* 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