diff options
-rw-r--r-- | Zend/zend_ast.h | 1 | ||||
-rw-r--r-- | Zend/zend_compile.c | 39 | ||||
-rw-r--r-- | Zend/zend_execute.c | 10 | ||||
-rw-r--r-- | Zend/zend_generators.c | 102 | ||||
-rw-r--r-- | Zend/zend_generators.h | 15 | ||||
-rw-r--r-- | Zend/zend_language_parser.y | 1 | ||||
-rw-r--r-- | Zend/zend_vm_def.h | 93 | ||||
-rw-r--r-- | Zend/zend_vm_execute.h | 296 | ||||
-rw-r--r-- | Zend/zend_vm_opcodes.c | 3 | ||||
-rw-r--r-- | Zend/zend_vm_opcodes.h | 1 |
10 files changed, 541 insertions, 20 deletions
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index a9d58428ec..8ab3c4c10f 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -84,6 +84,7 @@ enum _zend_ast_kind { ZEND_AST_PRE_DEC, ZEND_AST_POST_INC, ZEND_AST_POST_DEC, + ZEND_AST_YIELD_FROM, ZEND_AST_GLOBAL, ZEND_AST_UNSET, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 36055258f6..f1b5c54948 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5524,16 +5524,8 @@ void zend_compile_exit(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ -void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */ +static void zend_mark_function_as_generator() /* {{{ */ { - zend_ast *value_ast = ast->child[0]; - zend_ast *key_ast = ast->child[1]; - - znode value_node, key_node; - znode *value_node_ptr = NULL, *key_node_ptr = NULL; - zend_op *opline; - zend_bool returns_by_ref = (CG(active_op_array)->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; - if (!CG(active_op_array)->function_name) { zend_error_noreturn(E_COMPILE_ERROR, "The \"yield\" expression can only be used inside a function"); @@ -5555,6 +5547,20 @@ void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */ } CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR; +} +/* }}} */ + +void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */ +{ + zend_ast *value_ast = ast->child[0]; + zend_ast *key_ast = ast->child[1]; + + znode value_node, key_node; + znode *value_node_ptr = NULL, *key_node_ptr = NULL; + zend_op *opline; + zend_bool returns_by_ref = (CG(active_op_array)->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; + + zend_mark_function_as_generator(); if (key_ast) { zend_compile_expr(&key_node, key_ast); @@ -5578,6 +5584,18 @@ void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ +void zend_compile_yield_from(znode *result, zend_ast *ast) /* {{{ */ +{ + zend_ast *expr_ast = ast->child[0]; + znode expr_node; + + zend_mark_function_as_generator(); + + zend_compile_expr(&expr_node, expr_ast); + zend_emit_op_tmp(result, ZEND_YIELD_FROM, &expr_node, NULL); +} +/* }}} */ + void zend_compile_instanceof(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *obj_ast = ast->child[0]; @@ -6404,6 +6422,9 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_YIELD: zend_compile_yield(result, ast); return; + case ZEND_AST_YIELD_FROM: + zend_compile_yield_from(result, ast); + return; case ZEND_AST_INSTANCEOF: zend_compile_instanceof(result, ast); return; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 539b1e4dd7..e42952ee91 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2022,6 +2022,16 @@ static zend_always_inline void zend_vm_stack_extend_call_frame(zend_execute_data } /* }}} */ +static zend_always_inline zend_generator *zend_get_running_generator(zend_execute_data *execute_data) /* {{{ */ +{ + /* The generator object is stored in EX(return_value) */ + zend_generator *generator = (zend_generator *) EX(return_value); + /* However control may currently be delegated to another generator. + * That's the one we're interested in. */ + return generator->current_generator; +} +/* }}} */ + #define ZEND_VM_NEXT_OPCODE() \ CHECK_SYMBOL_TABLES() \ ZEND_VM_INC_OPCODE(); \ diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 7ebbca1030..8421d1fc7c 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -94,6 +94,11 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished ZVAL_UNDEF(&generator->key); } + if (Z_TYPE(generator->values) != IS_UNDEF) { + zval_ptr_dtor(&generator->values); + ZVAL_UNDEF(&generator->values); + } + if (generator->execute_data) { zend_execute_data *execute_data = generator->execute_data; zend_op_array *op_array = &execute_data->func->op_array; @@ -209,6 +214,9 @@ static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ * generator->largest_used_integer_key = -1; ZVAL_UNDEF(&generator->retval); + ZVAL_UNDEF(&generator->values); + + zend_ptr_stack_init(&generator->generator_stack); zend_object_std_init(&generator->std, class_type); generator->std.handlers = &zend_generator_handlers; @@ -283,6 +291,9 @@ ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array EG(vm_stack_end) = current_stack->end; EG(vm_stack) = current_stack; + /* By default we obviously execute the generator itself. */ + generator->current_generator = generator; + /* EX(return_value) keeps pointer to zend_object (not a real zval) */ execute_data->return_value = (zval*)generator; } @@ -296,6 +307,82 @@ static zend_function *zend_generator_get_constructor(zend_object *object) /* {{{ } /* }}} */ +static int zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */ +{ + zval *value; + if (Z_TYPE(generator->values) == IS_ARRAY) { + HashTable *ht = Z_ARR(generator->values); + HashPosition pos = Z_FE_POS(generator->values); + + Bucket *p; + do { + if (UNEXPECTED(pos >= ht->nNumUsed)) { + /* Reached end of array */ + goto failure; + } + + p = &ht->arData[pos]; + value = &p->val; + if (Z_TYPE_P(value) == IS_INDIRECT) { + value = Z_INDIRECT_P(value); + } + pos++; + } while (Z_ISUNDEF_P(value)); + + zval_ptr_dtor(&generator->value); + ZVAL_COPY(&generator->value, value); + + zval_ptr_dtor(&generator->key); + if (p->key) { + ZVAL_STR_COPY(&generator->key, p->key); + } else { + ZVAL_LONG(&generator->key, p->h); + } + + Z_FE_POS(generator->values) = pos; + } else { + zend_object_iterator *iter = (zend_object_iterator *) Z_OBJ(generator->values); + + if (++iter->index > 0) { + iter->funcs->move_forward(iter); + if (UNEXPECTED(EG(exception) != NULL)) { + goto failure; + } + } + + if (iter->funcs->valid(iter) == FAILURE) { + /* reached end of iteration */ + goto failure; + } + + value = iter->funcs->get_current_data(iter); + if (UNEXPECTED(EG(exception) != NULL || !value)) { + goto failure; + } + + zval_ptr_dtor(&generator->value); + ZVAL_COPY(&generator->value, value); + + zval_ptr_dtor(&generator->key); + if (iter->funcs->get_current_key) { + iter->funcs->get_current_key(iter, &generator->key); + if (UNEXPECTED(EG(exception) != NULL)) { + ZVAL_UNDEF(&generator->key); + goto failure; + } + } else { + ZVAL_LONG(&generator->key, iter->index); + } + } + return SUCCESS; + +failure: + zval_ptr_dtor(&generator->values); + ZVAL_UNDEF(&generator->values); + return FAILURE; +} +/* }}} */ + ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ { /* The generator is already closed, thus can't resume */ @@ -307,6 +394,16 @@ ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ zend_error(E_ERROR, "Cannot resume an already running generator"); } +try_again: + if (!Z_ISUNDEF(generator->values)) { + if (zend_generator_get_next_delegated_value(generator) == SUCCESS) { + return; + } + /* If there are no more deletegated values, resume the generator + * at the "yield *" expression. */ + // TODO: Handle exceptions + } + /* Drop the AT_FIRST_YIELD flag */ generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; @@ -353,6 +450,11 @@ ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ if (UNEXPECTED(EG(exception) != NULL)) { zend_throw_exception_internal(NULL); } + + /* Yield-from was used, try another resume. */ + if (!Z_ISUNDEF(generator->values)) { + goto try_again; + } } } /* }}} */ diff --git a/Zend/zend_generators.h b/Zend/zend_generators.h index 2e70bc5a2f..42f890e78f 100644 --- a/Zend/zend_generators.h +++ b/Zend/zend_generators.h @@ -42,11 +42,26 @@ typedef struct _zend_generator { zval key; /* Return value */ zval retval; + /* Variable to put sent value into */ zval *send_target; /* Largest used integer key for auto-incrementing keys */ zend_long largest_used_integer_key; + /* Values specified by "yield *" to yield from this generator. + * This is only used for arrays or non-generator Traversables. + * This zval also uses the u2 structure in the same way as + * by-value foreach. */ + zval values; + + /* Generator that is currently yielding values. This will differ + * from the surrounding structure if "yield *" is used on a generator. */ + struct _zend_generator *current_generator; + + /* Stack of waiting generators when multiple "yield *" expressions + * are nested. */ + zend_ptr_stack generator_stack; + /* ZEND_GENERATOR_* flags */ zend_uchar flags; } zend_generator; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 009d7bae0b..65ccbab3bb 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -872,6 +872,7 @@ expr_without_variable: | T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); } | T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); } | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); } + | T_YIELD '*' expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $3); } | function returns_ref '(' parameter_list ')' lexical_vars return_type backup_doc_comment '{' inner_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $8, diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 76d7f4774c..62ac7641ce 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3082,8 +3082,7 @@ ZEND_VM_HANDLER(161, ZEND_GENERATOR_RETURN, CONST|TMP|VAR|CV, ANY) zval *retval; zend_free_op free_op1; - /* The generator object is stored in EX(return_value) */ - zend_generator *generator = (zend_generator *) EX(return_value); + zend_generator *generator = zend_get_running_generator(execute_data); SAVE_OPLINE(); retval = GET_OP1_ZVAL_PTR(BP_VAR_R); @@ -6110,8 +6109,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op_num]); ZEND_VM_CONTINUE(); } else if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) { - /* The generator object is stored in EX(return_value) */ - zend_generator *generator = (zend_generator *) EX(return_value); + zend_generator *generator = zend_get_running_generator(execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { @@ -6144,8 +6142,7 @@ ZEND_VM_HANDLER(150, ZEND_USER_OPCODE, ANY, ANY) ZEND_VM_CONTINUE(); case ZEND_USER_OPCODE_RETURN: if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) { - /* The generator object is stored in EX(return_value) */ - zend_generator *generator = (zend_generator *) EX(return_value); + zend_generator *generator = zend_get_running_generator(execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { @@ -6243,8 +6240,7 @@ ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSE { USE_OPLINE - /* The generator object is stored in EX(return_value) */ - zend_generator *generator = (zend_generator *) EX(return_value); + zend_generator *generator = zend_get_running_generator(execute_data); if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) { zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator"); @@ -6371,6 +6367,84 @@ ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSE ZEND_VM_RETURN(); } +ZEND_VM_HANDLER(170, ZEND_YIELD_FROM, CONST|TMP|VAR|CV, ANY) +{ + USE_OPLINE + + /* The generator object is stored in EX(return_value) */ + zend_generator *generator = (zend_generator *) EX(return_value); + + zval *val; + zend_free_op free_op1; + + SAVE_OPLINE(); + val = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); + if (Z_TYPE_P(val) == IS_ARRAY) { + ZVAL_COPY_VALUE(&generator->values, val); + if (OP1_TYPE != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(val)) { + Z_ADDREF_P(val); + } + Z_FE_POS(generator->values) = 0; + + FREE_OP1_IF_VAR(); + } else if (OP1_TYPE != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) { + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce == zend_ce_generator) { + zend_generator *new_gen = (zend_generator *) Z_OBJ_P(val); + + zend_ptr_stack_push(&generator->generator_stack, generator->current_generator); + generator->current_generator = new_gen; + + if (OP1_TYPE != IS_TMP_VAR) { + Z_ADDREF_P(val); + } + FREE_OP1_IF_VAR(); + } else { + zend_object_iterator *iter = ce->get_iterator(ce, val, 0); + FREE_OP1(); + + if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) { + if (!EG(exception)) { + zend_throw_exception_ex(NULL, 0, + "Object of type %s did not create an Iterator", ce->name->val); + } + zend_throw_exception_internal(NULL); + HANDLE_EXCEPTION(); + } + + iter->index = 0; + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (UNEXPECTED(EG(exception) != NULL)) { + OBJ_RELEASE(&iter->std); + HANDLE_EXCEPTION(); + } + } + + ZVAL_OBJ(&generator->values, &iter->std); + } + } else { + // TODO: Should be an engine exception + zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0); + HANDLE_EXCEPTION(); + } + + if (RETURN_VALUE_USED(opline)) { + // TODO + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + /* We increment to the next op, so we are at the correct position when the + * generator is resumed. */ + ZEND_VM_INC_OPCODE(); + + /* The GOTO VM uses a local opline variable. We need to set the opline + * variable in execute_data so we don't resume at an old position. */ + SAVE_OPLINE(); + + ZEND_VM_RETURN(); +} + ZEND_VM_HANDLER(159, ZEND_DISCARD_EXCEPTION, ANY, ANY) { USE_OPLINE @@ -6431,8 +6505,7 @@ ZEND_VM_HANDLER(163, ZEND_FAST_RET, ANY, ANY) ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[opline->op2.opline_num]); ZEND_VM_CONTINUE(); } else if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) { - /* The generator object is stored in EX(return_value) */ - zend_generator *generator = (zend_generator *) EX(return_value); + zend_generator *generator = zend_get_running_generator(execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 8e7094fed7..db617a6bd8 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3485,6 +3485,73 @@ static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ ZEND_VM_NEXT_OPCODE(); } +static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + /* The generator object is stored in EX(return_value) */ + zend_generator *generator = (zend_generator *) EX(return_value); + + zval *val; + + + SAVE_OPLINE(); + val = EX_CONSTANT(opline->op1); + if (Z_TYPE_P(val) == IS_ARRAY) { + ZVAL_COPY_VALUE(&generator->values, val); + if (IS_CONST != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(val)) { + Z_ADDREF_P(val); + } + Z_FE_POS(generator->values) = 0; + + } else if (IS_CONST != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) { + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce == zend_ce_generator) { + + } else { + zend_object_iterator *iter = ce->get_iterator(ce, val, 0); + + if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) { + if (!EG(exception)) { + zend_throw_exception_ex(NULL, 0, + "Object of type %s did not create an Iterator", ce->name->val); + } + zend_throw_exception_internal(NULL); + HANDLE_EXCEPTION(); + } + + iter->index = 0; + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (UNEXPECTED(EG(exception) != NULL)) { + OBJ_RELEASE(&iter->std); + HANDLE_EXCEPTION(); + } + } + + ZVAL_OBJ(&generator->values, &iter->std); + } + } else { + // TODO: Should be an engine exception + zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0); + HANDLE_EXCEPTION(); + } + + if (RETURN_VALUE_USED(opline)) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + /* We increment to the next op, so we are at the correct position when the + * generator is resumed. */ + ZEND_VM_INC_OPCODE(); + + /* The GOTO VM uses a local opline variable. We need to set the opline + * variable in execute_data so we don't resume at an old position. */ + SAVE_OPLINE(); + + ZEND_VM_RETURN(); +} + static int ZEND_FASTCALL ZEND_STRLEN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -9500,6 +9567,74 @@ static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_AR ZEND_VM_NEXT_OPCODE(); } +static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + /* The generator object is stored in EX(return_value) */ + zend_generator *generator = (zend_generator *) EX(return_value); + + zval *val; + zend_free_op free_op1; + + SAVE_OPLINE(); + val = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1); + if (Z_TYPE_P(val) == IS_ARRAY) { + ZVAL_COPY_VALUE(&generator->values, val); + if (IS_TMP_VAR != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(val)) { + Z_ADDREF_P(val); + } + Z_FE_POS(generator->values) = 0; + + } else if (IS_TMP_VAR != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) { + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce == zend_ce_generator) { + + } else { + zend_object_iterator *iter = ce->get_iterator(ce, val, 0); + zval_ptr_dtor_nogc(free_op1); + + if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) { + if (!EG(exception)) { + zend_throw_exception_ex(NULL, 0, + "Object of type %s did not create an Iterator", ce->name->val); + } + zend_throw_exception_internal(NULL); + HANDLE_EXCEPTION(); + } + + iter->index = 0; + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (UNEXPECTED(EG(exception) != NULL)) { + OBJ_RELEASE(&iter->std); + HANDLE_EXCEPTION(); + } + } + + ZVAL_OBJ(&generator->values, &iter->std); + } + } else { + // TODO: Should be an engine exception + zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0); + HANDLE_EXCEPTION(); + } + + if (RETURN_VALUE_USED(opline)) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + /* We increment to the next op, so we are at the correct position when the + * generator is resumed. */ + ZEND_VM_INC_OPCODE(); + + /* The GOTO VM uses a local opline variable. We need to set the opline + * variable in execute_data so we don't resume at an old position. */ + SAVE_OPLINE(); + + ZEND_VM_RETURN(); +} + static int ZEND_FASTCALL ZEND_TYPE_CHECK_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -12768,6 +12903,75 @@ static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_AR ZEND_VM_NEXT_OPCODE(); } +static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + /* The generator object is stored in EX(return_value) */ + zend_generator *generator = (zend_generator *) EX(return_value); + + zval *val; + zend_free_op free_op1; + + SAVE_OPLINE(); + val = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1); + if (Z_TYPE_P(val) == IS_ARRAY) { + ZVAL_COPY_VALUE(&generator->values, val); + if (IS_VAR != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(val)) { + Z_ADDREF_P(val); + } + Z_FE_POS(generator->values) = 0; + + zval_ptr_dtor_nogc(free_op1); + } else if (IS_VAR != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) { + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce == zend_ce_generator) { + + } else { + zend_object_iterator *iter = ce->get_iterator(ce, val, 0); + zval_ptr_dtor_nogc(free_op1); + + if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) { + if (!EG(exception)) { + zend_throw_exception_ex(NULL, 0, + "Object of type %s did not create an Iterator", ce->name->val); + } + zend_throw_exception_internal(NULL); + HANDLE_EXCEPTION(); + } + + iter->index = 0; + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (UNEXPECTED(EG(exception) != NULL)) { + OBJ_RELEASE(&iter->std); + HANDLE_EXCEPTION(); + } + } + + ZVAL_OBJ(&generator->values, &iter->std); + } + } else { + // TODO: Should be an engine exception + zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0); + HANDLE_EXCEPTION(); + } + + if (RETURN_VALUE_USED(opline)) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + /* We increment to the next op, so we are at the correct position when the + * generator is resumed. */ + ZEND_VM_INC_OPCODE(); + + /* The GOTO VM uses a local opline variable. We need to set the opline + * variable in execute_data so we don't resume at an old position. */ + SAVE_OPLINE(); + + ZEND_VM_RETURN(); +} + static int ZEND_FASTCALL ZEND_TYPE_CHECK_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -24950,6 +25154,73 @@ static int ZEND_FASTCALL ZEND_QM_ASSIGN_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARG ZEND_VM_NEXT_OPCODE(); } +static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + + /* The generator object is stored in EX(return_value) */ + zend_generator *generator = (zend_generator *) EX(return_value); + + zval *val; + + + SAVE_OPLINE(); + val = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var); + if (Z_TYPE_P(val) == IS_ARRAY) { + ZVAL_COPY_VALUE(&generator->values, val); + if (IS_CV != IS_TMP_VAR && Z_OPT_REFCOUNTED_P(val)) { + Z_ADDREF_P(val); + } + Z_FE_POS(generator->values) = 0; + + } else if (IS_CV != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) { + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce == zend_ce_generator) { + + } else { + zend_object_iterator *iter = ce->get_iterator(ce, val, 0); + + if (UNEXPECTED(!iter) || UNEXPECTED(EG(exception))) { + if (!EG(exception)) { + zend_throw_exception_ex(NULL, 0, + "Object of type %s did not create an Iterator", ce->name->val); + } + zend_throw_exception_internal(NULL); + HANDLE_EXCEPTION(); + } + + iter->index = 0; + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (UNEXPECTED(EG(exception) != NULL)) { + OBJ_RELEASE(&iter->std); + HANDLE_EXCEPTION(); + } + } + + ZVAL_OBJ(&generator->values, &iter->std); + } + } else { + // TODO: Should be an engine exception + zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0); + HANDLE_EXCEPTION(); + } + + if (RETURN_VALUE_USED(opline)) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + + /* We increment to the next op, so we are at the correct position when the + * generator is resumed. */ + ZEND_VM_INC_OPCODE(); + + /* The GOTO VM uses a local opline variable. We need to set the opline + * variable in execute_data so we don't resume at an old position. */ + SAVE_OPLINE(); + + ZEND_VM_RETURN(); +} + static int ZEND_FASTCALL ZEND_STRLEN_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -41208,6 +41479,31 @@ void zend_init_opcodes_handlers(void) ZEND_COALESCE_SPEC_CV_HANDLER, ZEND_COALESCE_SPEC_CV_HANDLER, ZEND_COALESCE_SPEC_CV_HANDLER, + ZEND_YIELD_FROM_SPEC_CONST_HANDLER, + ZEND_YIELD_FROM_SPEC_CONST_HANDLER, + ZEND_YIELD_FROM_SPEC_CONST_HANDLER, + ZEND_YIELD_FROM_SPEC_CONST_HANDLER, + ZEND_YIELD_FROM_SPEC_CONST_HANDLER, + ZEND_YIELD_FROM_SPEC_TMP_HANDLER, + ZEND_YIELD_FROM_SPEC_TMP_HANDLER, + ZEND_YIELD_FROM_SPEC_TMP_HANDLER, + ZEND_YIELD_FROM_SPEC_TMP_HANDLER, + ZEND_YIELD_FROM_SPEC_TMP_HANDLER, + ZEND_YIELD_FROM_SPEC_VAR_HANDLER, + ZEND_YIELD_FROM_SPEC_VAR_HANDLER, + ZEND_YIELD_FROM_SPEC_VAR_HANDLER, + ZEND_YIELD_FROM_SPEC_VAR_HANDLER, + ZEND_YIELD_FROM_SPEC_VAR_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_YIELD_FROM_SPEC_CV_HANDLER, + ZEND_YIELD_FROM_SPEC_CV_HANDLER, + ZEND_YIELD_FROM_SPEC_CV_HANDLER, + ZEND_YIELD_FROM_SPEC_CV_HANDLER, + ZEND_YIELD_FROM_SPEC_CV_HANDLER, ZEND_NULL_HANDLER }; zend_opcode_handlers = (opcode_handler_t*)labels; diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index d7c5db6966..a6d7ecedc4 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -21,7 +21,7 @@ #include <stdio.h> #include <zend.h> -const char *zend_vm_opcodes_map[170] = { +const char *zend_vm_opcodes_map[171] = { "ZEND_NOP", "ZEND_ADD", "ZEND_SUB", @@ -192,6 +192,7 @@ const char *zend_vm_opcodes_map[170] = { "ZEND_ASSIGN_POW", "ZEND_BIND_GLOBAL", "ZEND_COALESCE", + "ZEND_YIELD_FROM", }; ZEND_API const char* zend_get_opcode_name(zend_uchar opcode) { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index aa4afbd984..0bb6dd345d 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -185,5 +185,6 @@ END_EXTERN_C() #define ZEND_ASSIGN_POW 167 #define ZEND_BIND_GLOBAL 168 #define ZEND_COALESCE 169 +#define ZEND_YIELD_FROM 170 #endif |