summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Zend/zend_ast.h1
-rw-r--r--Zend/zend_compile.c39
-rw-r--r--Zend/zend_execute.c10
-rw-r--r--Zend/zend_generators.c102
-rw-r--r--Zend/zend_generators.h15
-rw-r--r--Zend/zend_language_parser.y1
-rw-r--r--Zend/zend_vm_def.h93
-rw-r--r--Zend/zend_vm_execute.h296
-rw-r--r--Zend/zend_vm_opcodes.c3
-rw-r--r--Zend/zend_vm_opcodes.h1
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