Skip to content

Commit 65e456f

Browse files
committed
Introduce BIND_LEXICAL
This opcodes inserts a local CV into the closure static variable table. This replaces the previous mechanism of having static variables marked as LEXICAL, which perform a symtable lookup during copying. This means a) functions which contain closures no longer have to rebuild their symtable (better performance) and b) we can now track used variables in SSA.
1 parent 4440436 commit 65e456f

16 files changed

+157
-98
lines changed

Zend/zend_closures.c

+10-5
Original file line numberDiff line numberDiff line change
@@ -570,11 +570,8 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
570570
closure->func.common.prototype = (zend_function*)closure;
571571
closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
572572
if (closure->func.op_array.static_variables) {
573-
HashTable *static_variables = closure->func.op_array.static_variables;
574-
575-
ALLOC_HASHTABLE(closure->func.op_array.static_variables);
576-
zend_hash_init(closure->func.op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0);
577-
zend_hash_apply_with_arguments(static_variables, zval_copy_static_var, 1, closure->func.op_array.static_variables);
573+
closure->func.op_array.static_variables =
574+
zend_array_dup(closure->func.op_array.static_variables);
578575
}
579576
if (UNEXPECTED(!closure->func.op_array.run_time_cache)) {
580577
closure->func.op_array.run_time_cache = func->op_array.run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size);
@@ -629,6 +626,14 @@ ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_clas
629626
}
630627
/* }}} */
631628

629+
void zend_closure_bind_var(zval *closure_zv, zend_string *var_name, zval *var) /* {{{ */
630+
{
631+
zend_closure *closure = (zend_closure *) Z_OBJ_P(closure_zv);
632+
HashTable *static_variables = closure->func.op_array.static_variables;
633+
zend_hash_update(static_variables, var_name, var);
634+
}
635+
/* }}} */
636+
632637
/*
633638
* Local variables:
634639
* tab-width: 4

Zend/zend_closures.h

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
BEGIN_EXTERN_C()
2626

2727
void zend_register_closure_ce(void);
28+
void zend_closure_bind_var(zval *closure_zv, zend_string *var_name, zval *var);
2829

2930
extern ZEND_API zend_class_entry *zend_ce_closure;
3031

Zend/zend_compile.c

+31-11
Original file line numberDiff line numberDiff line change
@@ -2055,7 +2055,8 @@ static void zend_check_live_ranges(zend_op *opline) /* {{{ */
20552055
opline->opcode == ZEND_ROPE_END ||
20562056
opline->opcode == ZEND_END_SILENCE ||
20572057
opline->opcode == ZEND_FETCH_LIST ||
2058-
opline->opcode == ZEND_VERIFY_RETURN_TYPE) {
2058+
opline->opcode == ZEND_VERIFY_RETURN_TYPE ||
2059+
opline->opcode == ZEND_BIND_LEXICAL) {
20592060
/* these opcodes are handled separately */
20602061
} else {
20612062
zend_find_live_range(opline, opline->op1_type, opline->op1.var);
@@ -4893,28 +4894,44 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
48934894
}
48944895
/* }}} */
48954896

4896-
void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
4897+
static void zend_compile_closure_binding(znode *closure, zend_ast *uses_ast) /* {{{ */
48974898
{
4898-
zend_ast_list *list = zend_ast_get_list(ast);
4899+
zend_ast_list *list = zend_ast_get_list(uses_ast);
48994900
uint32_t i;
49004901

49014902
for (i = 0; i < list->children; ++i) {
4902-
zend_ast *var_ast = list->child[i];
4903-
zend_string *name = zend_ast_get_str(var_ast);
4904-
zend_bool by_ref = var_ast->attr;
4905-
zval zv;
4903+
zend_ast *var_name_ast = list->child[i];
4904+
zend_string *var_name = zend_ast_get_str(var_name_ast);
4905+
zend_bool by_ref = var_name_ast->attr;
4906+
zend_op *opline;
49064907

4907-
if (zend_string_equals_literal(name, "this")) {
4908+
if (zend_string_equals_literal(var_name, "this")) {
49084909
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use $this as lexical variable");
49094910
}
49104911

4911-
if (zend_is_auto_global(name)) {
4912+
if (zend_is_auto_global(var_name)) {
49124913
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use auto-global as lexical variable");
49134914
}
49144915

4915-
ZVAL_NULL(&zv);
4916-
Z_CONST_FLAGS(zv) = by_ref ? IS_LEXICAL_REF : IS_LEXICAL_VAR;
4916+
opline = zend_emit_op(NULL, ZEND_BIND_LEXICAL, closure, NULL);
4917+
opline->op2_type = IS_CV;
4918+
opline->op2.var = lookup_cv(CG(active_op_array), zend_string_copy(var_name));
4919+
opline->extended_value = by_ref;
4920+
}
4921+
}
4922+
/* }}} */
49174923

4924+
void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
4925+
{
4926+
zend_ast_list *list = zend_ast_get_list(ast);
4927+
uint32_t i;
4928+
4929+
for (i = 0; i < list->children; ++i) {
4930+
zend_ast *var_ast = list->child[i];
4931+
zend_bool by_ref = var_ast->attr;
4932+
4933+
zval zv;
4934+
ZVAL_NULL(&zv);
49184935
zend_compile_static_var_common(var_ast, &zv, by_ref);
49194936
}
49204937
}
@@ -5166,6 +5183,9 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
51665183
zend_begin_method_decl(op_array, decl->name, has_body);
51675184
} else {
51685185
zend_begin_func_decl(result, op_array, decl);
5186+
if (uses_ast) {
5187+
zend_compile_closure_binding(result, uses_ast);
5188+
}
51695189
}
51705190

51715191
CG(active_op_array) = op_array;

Zend/zend_compile.h

-1
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,6 @@ ZEND_API void zend_assert_valid_class_name(const zend_string *const_name);
892892
#define ZEND_FETCH_LOCAL 0x10000000
893893
#define ZEND_FETCH_STATIC 0x20000000
894894
#define ZEND_FETCH_GLOBAL_LOCK 0x40000000
895-
#define ZEND_FETCH_LEXICAL 0x50000000
896895

897896
#define ZEND_FETCH_TYPE_MASK 0x70000000
898897

Zend/zend_types.h

-2
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,6 @@ static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
412412

413413
/* zval.u1.v.const_flags */
414414
#define IS_CONSTANT_UNQUALIFIED 0x010
415-
#define IS_LEXICAL_VAR 0x020
416-
#define IS_LEXICAL_REF 0x040
417415
#define IS_CONSTANT_CLASS 0x080 /* __CLASS__ in trait */
418416
#define IS_CONSTANT_IN_NAMESPACE 0x100 /* used only in opline->extended_value */
419417

Zend/zend_variables.c

-53
Original file line numberDiff line numberDiff line change
@@ -267,59 +267,6 @@ ZEND_API void _zval_internal_ptr_dtor_wrapper(zval *zval_ptr)
267267
}
268268
#endif
269269

270-
ZEND_API int zval_copy_static_var(zval *p, int num_args, va_list args, zend_hash_key *key) /* {{{ */
271-
{
272-
zend_array *symbol_table;
273-
HashTable *target = va_arg(args, HashTable*);
274-
zend_bool is_ref;
275-
zval tmp;
276-
277-
if (Z_CONST_FLAGS_P(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) {
278-
is_ref = Z_CONST_FLAGS_P(p) & IS_LEXICAL_REF;
279-
280-
symbol_table = zend_rebuild_symbol_table();
281-
p = zend_hash_find(symbol_table, key->key);
282-
if (!p) {
283-
p = &tmp;
284-
ZVAL_NULL(&tmp);
285-
if (is_ref) {
286-
ZVAL_NEW_REF(&tmp, &tmp);
287-
zend_hash_add_new(symbol_table, key->key, &tmp);
288-
Z_ADDREF_P(p);
289-
} else {
290-
zend_error(E_NOTICE,"Undefined variable: %s", ZSTR_VAL(key->key));
291-
}
292-
} else {
293-
if (Z_TYPE_P(p) == IS_INDIRECT) {
294-
p = Z_INDIRECT_P(p);
295-
if (Z_TYPE_P(p) == IS_UNDEF) {
296-
if (!is_ref) {
297-
zend_error(E_NOTICE,"Undefined variable: %s", ZSTR_VAL(key->key));
298-
p = &tmp;
299-
ZVAL_NULL(&tmp);
300-
} else {
301-
ZVAL_NULL(p);
302-
}
303-
}
304-
}
305-
if (is_ref) {
306-
ZVAL_MAKE_REF(p);
307-
Z_ADDREF_P(p);
308-
} else if (Z_ISREF_P(p)) {
309-
ZVAL_DUP(&tmp, Z_REFVAL_P(p));
310-
p = &tmp;
311-
} else if (Z_REFCOUNTED_P(p)) {
312-
Z_ADDREF_P(p);
313-
}
314-
}
315-
} else if (Z_REFCOUNTED_P(p)) {
316-
Z_ADDREF_P(p);
317-
}
318-
zend_hash_add(target, key->key, p);
319-
return ZEND_HASH_APPLY_KEEP;
320-
}
321-
/* }}} */
322-
323270
/*
324271
* Local variables:
325272
* tab-width: 4

Zend/zend_variables.h

-2
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,6 @@ static zend_always_inline void _zval_opt_copy_ctor_no_imm(zval *zvalue ZEND_FILE
106106
}
107107
}
108108

109-
ZEND_API int zval_copy_static_var(zval *p, int num_args, va_list args, zend_hash_key *key);
110-
111109
ZEND_API size_t zend_print_variable(zval *var);
112110
ZEND_API void _zval_ptr_dtor(zval *zval_ptr ZEND_FILE_LINE_DC);
113111
ZEND_API void _zval_internal_dtor_for_ptr(zval *zvalue ZEND_FILE_LINE_DC);

Zend/zend_vm_def.h

+31
Original file line numberDiff line numberDiff line change
@@ -7997,4 +7997,35 @@ ZEND_VM_C_LABEL(call_trampoline_end):
79977997
ZEND_VM_LEAVE();
79987998
}
79997999

8000+
ZEND_VM_HANDLER(182, ZEND_BIND_LEXICAL, TMP, CV, REF)
8001+
{
8002+
USE_OPLINE
8003+
zend_free_op free_op1, free_op2;
8004+
zval *closure, *var;
8005+
zend_string *var_name;
8006+
8007+
closure = GET_OP1_ZVAL_PTR(BP_VAR_R);
8008+
if (opline->extended_value) {
8009+
/* By-ref binding */
8010+
var = GET_OP2_ZVAL_PTR(BP_VAR_W);
8011+
ZVAL_MAKE_REF(var);
8012+
Z_ADDREF_P(var);
8013+
} else {
8014+
var = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
8015+
if (UNEXPECTED(Z_ISUNDEF_P(var))) {
8016+
SAVE_OPLINE();
8017+
var = GET_OP2_UNDEF_CV(var, BP_VAR_R);
8018+
if (UNEXPECTED(EG(exception))) {
8019+
HANDLE_EXCEPTION();
8020+
}
8021+
}
8022+
ZVAL_DEREF(var);
8023+
Z_TRY_ADDREF_P(var);
8024+
}
8025+
8026+
var_name = CV_DEF_OF(EX_VAR_TO_NUM(opline->op2.var));
8027+
zend_closure_bind_var(closure, var_name, var);
8028+
ZEND_VM_NEXT_OPCODE();
8029+
}
8030+
80008031
ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA);

Zend/zend_vm_execute.h

+56
Original file line numberDiff line numberDiff line change
@@ -14210,6 +14210,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_YIELD_SPEC_TMP_CV_HANDLER(ZEND
1421014210
ZEND_VM_RETURN();
1421114211
}
1421214212

14213+
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_LEXICAL_SPEC_TMP_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
14214+
{
14215+
USE_OPLINE
14216+
zend_free_op free_op1;
14217+
zval *closure, *var;
14218+
zend_string *var_name;
14219+
14220+
closure = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1);
14221+
if (opline->extended_value) {
14222+
/* By-ref binding */
14223+
var = _get_zval_ptr_cv_BP_VAR_W(execute_data, opline->op2.var);
14224+
ZVAL_MAKE_REF(var);
14225+
Z_ADDREF_P(var);
14226+
} else {
14227+
var = _get_zval_ptr_cv_undef(execute_data, opline->op2.var);
14228+
if (UNEXPECTED(Z_ISUNDEF_P(var))) {
14229+
SAVE_OPLINE();
14230+
var = GET_OP2_UNDEF_CV(var, BP_VAR_R);
14231+
if (UNEXPECTED(EG(exception))) {
14232+
HANDLE_EXCEPTION();
14233+
}
14234+
}
14235+
ZVAL_DEREF(var);
14236+
Z_TRY_ADDREF_P(var);
14237+
}
14238+
14239+
var_name = CV_DEF_OF(EX_VAR_TO_NUM(opline->op2.var));
14240+
zend_closure_bind_var(closure, var_name, var);
14241+
ZEND_VM_NEXT_OPCODE();
14242+
}
14243+
1421314244
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_DIM_FUNC_ARG_SPEC_TMP_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
1421414245
{
1421514246
USE_OPLINE
@@ -49334,6 +49365,31 @@ void zend_init_opcodes_handlers(void)
4933449365
ZEND_NULL_HANDLER,
4933549366
ZEND_NULL_HANDLER,
4933649367
ZEND_NULL_HANDLER,
49368+
ZEND_NULL_HANDLER,
49369+
ZEND_NULL_HANDLER,
49370+
ZEND_NULL_HANDLER,
49371+
ZEND_NULL_HANDLER,
49372+
ZEND_NULL_HANDLER,
49373+
ZEND_NULL_HANDLER,
49374+
ZEND_NULL_HANDLER,
49375+
ZEND_NULL_HANDLER,
49376+
ZEND_NULL_HANDLER,
49377+
ZEND_BIND_LEXICAL_SPEC_TMP_CV_HANDLER,
49378+
ZEND_NULL_HANDLER,
49379+
ZEND_NULL_HANDLER,
49380+
ZEND_NULL_HANDLER,
49381+
ZEND_NULL_HANDLER,
49382+
ZEND_NULL_HANDLER,
49383+
ZEND_NULL_HANDLER,
49384+
ZEND_NULL_HANDLER,
49385+
ZEND_NULL_HANDLER,
49386+
ZEND_NULL_HANDLER,
49387+
ZEND_NULL_HANDLER,
49388+
ZEND_NULL_HANDLER,
49389+
ZEND_NULL_HANDLER,
49390+
ZEND_NULL_HANDLER,
49391+
ZEND_NULL_HANDLER,
49392+
ZEND_NULL_HANDLER,
4933749393
ZEND_NULL_HANDLER
4933849394
};
4933949395
zend_opcode_handlers = labels;

Zend/zend_vm_opcodes.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include <stdio.h>
2222
#include <zend.h>
2323

24-
static const char *zend_vm_opcodes_names[182] = {
24+
static const char *zend_vm_opcodes_names[183] = {
2525
"ZEND_NOP",
2626
"ZEND_ADD",
2727
"ZEND_SUB",
@@ -204,9 +204,10 @@ static const char *zend_vm_opcodes_names[182] = {
204204
"ZEND_UNSET_STATIC_PROP",
205205
"ZEND_ISSET_ISEMPTY_STATIC_PROP",
206206
"ZEND_FETCH_CLASS_CONSTANT",
207+
"ZEND_BIND_LEXICAL",
207208
};
208209

209-
static uint32_t zend_vm_opcodes_flags[182] = {
210+
static uint32_t zend_vm_opcodes_flags[183] = {
210211
0x00000000,
211212
0x00000707,
212213
0x00000707,
@@ -389,6 +390,7 @@ static uint32_t zend_vm_opcodes_flags[182] = {
389390
0x00007307,
390391
0x00027307,
391392
0x00000373,
393+
0x00100101,
392394
};
393395

394396
ZEND_API const char* zend_get_opcode_name(zend_uchar opcode) {

Zend/zend_vm_opcodes.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ END_EXTERN_C()
254254
#define ZEND_UNSET_STATIC_PROP 179
255255
#define ZEND_ISSET_ISEMPTY_STATIC_PROP 180
256256
#define ZEND_FETCH_CLASS_CONSTANT 181
257+
#define ZEND_BIND_LEXICAL 182
257258

258-
#define ZEND_VM_LAST_OPCODE 181
259+
#define ZEND_VM_LAST_OPCODE 182
259260

260261
#endif

ext/opcache/Optimizer/zend_cfg.c

-18
Original file line numberDiff line numberDiff line change
@@ -372,24 +372,6 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
372372
BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
373373
BB_START(i + 1);
374374
break;
375-
case ZEND_DECLARE_LAMBDA_FUNCTION: {
376-
//??? zend_op_array *lambda_op_array;
377-
//???
378-
//??? zv = CRT_CONSTANT(opline->op1);
379-
//??? if (ctx->main_script &&
380-
//??? (lambda_op_array = zend_hash_find_ptr(&ctx->main_script->function_table, Z_STR_P(zv))) != NULL) {
381-
//??? if (lambda_op_array->type == ZEND_USER_FUNCTION &&
382-
//??? lambda_op_array->static_variables) {
383-
//??? // FIXME: Really we should try to perform alias
384-
//??? // analysis on variables used by the closure
385-
//??? info->flags |= ZEND_FUNC_TOO_DYNAMIC;
386-
//??? }
387-
//??? } else {
388-
//??? // FIXME: how to find the lambda function?
389-
flags |= ZEND_FUNC_TOO_DYNAMIC;
390-
//??? }
391-
}
392-
break;
393375
case ZEND_UNSET_VAR:
394376
if (!(opline->extended_value & ZEND_QUICK_SET)) {
395377
flags |= ZEND_FUNC_TOO_DYNAMIC;

ext/opcache/Optimizer/zend_dfg.c

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg
9999
case ZEND_FE_RESET_RW:
100100
case ZEND_ADD_ARRAY_ELEMENT:
101101
case ZEND_INIT_ARRAY:
102+
case ZEND_BIND_LEXICAL:
102103
if (!DFG_ISSET(use, set_size, j, EX_VAR_TO_NUM(opline->op1.var))) {
103104
// FIXME: include into "use" to ...?
104105
DFG_SET(use, set_size, j, EX_VAR_TO_NUM(opline->op1.var));

ext/opcache/Optimizer/zend_dump.c

-3
Original file line numberDiff line numberDiff line change
@@ -516,9 +516,6 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *
516516
case ZEND_FETCH_GLOBAL_LOCK:
517517
fprintf(stderr, " (global+lock)");
518518
break;
519-
case ZEND_FETCH_LEXICAL:
520-
fprintf(stderr, " (lexical)");
521-
break;
522519
}
523520
}
524521
if (ZEND_VM_EXT_ISSET & flags) {

0 commit comments

Comments
 (0)