diff options
author | Bob Weinand <bobwei9@hotmail.com> | 2015-04-14 17:58:58 +0200 |
---|---|---|
committer | Bob Weinand <bobwei9@hotmail.com> | 2015-04-14 17:58:58 +0200 |
commit | f3e124d58dc4627eb89625a7c264cb009130f725 (patch) | |
tree | c256b36decc201ef6eb780b4ff80f5a97c7b5b42 /Zend/zend_generators.c | |
parent | 4f07330e798a4e7b12bcc1811c3c71d17b71f22c (diff) | |
parent | b4a142ab974747be2fd03d055b30c63286a687fd (diff) | |
download | php-git-f3e124d58dc4627eb89625a7c264cb009130f725.tar.gz |
Merge branch 'coroutineDelegation' of https://github.com/bwoebi/php-src
Diffstat (limited to 'Zend/zend_generators.c')
-rw-r--r-- | Zend/zend_generators.c | 481 |
1 files changed, 444 insertions, 37 deletions
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index bb9f0f07d3..8976a0fcc4 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -13,6 +13,7 @@ | license@zend.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Nikita Popov <nikic@php.net> | + | Bob Weinand <bobwei9@hotmail.com> | +----------------------------------------------------------------------+ */ @@ -25,6 +26,7 @@ #include "zend_generators.h" ZEND_API zend_class_entry *zend_ce_generator; +ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException; static zend_object_handlers zend_generator_handlers; static zend_object *zend_generator_create(zend_class_entry *class_type); @@ -94,6 +96,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; @@ -185,7 +192,14 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */ zend_generator_close(generator, 0); - zval_ptr_dtor(&generator->retval); + if (!Z_ISUNDEF(generator->retval)) { + zval_ptr_dtor(&generator->retval); + } + + if (generator->node.children > 4) { + zend_hash_destroy(&generator->node.child.ht); + } + zend_object_std_dtor(&generator->std); if (generator->iterator) { @@ -205,6 +219,12 @@ 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); + + /* By default we have a tree of only one node */ + generator->node.parent = NULL; + generator->node.children = 0; + generator->node.ptr.root = generator; zend_object_std_init(&generator->std, class_type); generator->std.handlers = &zend_generator_handlers; @@ -237,7 +257,6 @@ ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array /* Save execution context in generator object. */ generator = (zend_generator *) Z_OBJ_P(return_value); - execute_data->prev_execute_data = NULL; generator->execute_data = execute_data; generator->stack = EG(vm_stack); generator->stack->top = EG(vm_stack_top); @@ -247,6 +266,9 @@ ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array /* EX(return_value) keeps pointer to zend_object (not a real zval) */ execute_data->return_value = (zval*)generator; + + memset(&generator->execute_fake, 0, sizeof(zend_execute_data)); + Z_OBJ(generator->execute_fake.This) = (zend_object *) generator; } /* }}} */ @@ -258,20 +280,369 @@ static zend_function *zend_generator_get_constructor(zend_object *object) /* {{{ } /* }}} */ -ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ +ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_data *ptr) +{ + if (!ptr->func && ptr->prev_execute_data && Z_OBJ(ptr->This)) { + if (Z_OBJCE(ptr->This) == zend_ce_generator) { + zend_generator *generator = (zend_generator *) Z_OBJ(ptr->This); + zend_generator *root = (generator->node.children < 1 ? generator : generator->node.ptr.leaf)->node.ptr.root; + zend_execute_data *prev = ptr->prev_execute_data; + if (generator->node.parent != root) { + do { + generator->execute_data->prev_execute_data = prev; + prev = generator->execute_data; + generator = generator->node.parent; + } while (generator->node.parent != root); + } + generator->execute_data->prev_execute_data = prev; + ptr = generator->execute_data; + } + } + return ptr; +} + +static void zend_generator_throw_exception(zend_generator *generator, zval *exception) +{ + /* Throw the exception in the context of the generator */ + zend_execute_data *original_execute_data = EG(current_execute_data); + EG(current_execute_data) = generator->execute_data; + if (exception) { + zend_throw_exception_object(exception); + } else { + zend_throw_exception_internal(NULL); + } + EG(current_execute_data) = original_execute_data; +} + +static zend_generator *zend_generator_get_child(zend_generator_node *node, zend_generator *leaf) +{ + switch (node->children) { + case 0: + return NULL; + case 1: + return node->child.array[0].child; + +#define ZEND_GEN_GET_CHILD(x) \ + if (node->child.array[x].leaf == leaf) { \ + return node->child.array[x].child; \ + } + case 4: + ZEND_GEN_GET_CHILD(3) + case 3: + ZEND_GEN_GET_CHILD(2) + case 2: + ZEND_GEN_GET_CHILD(1) + ZEND_GEN_GET_CHILD(0) + ZEND_ASSERT(0); // we never should have no matching child + } + + return zend_hash_index_find_ptr(&node->child.ht, (zend_ulong) leaf); +} + +static zend_generator_node *zend_generator_search_multi_children_node(zend_generator_node *node) +{ + while (node->children == 1) { + node = &node->child.array[0].child->node; + } + return node->children > 1 ? node : NULL; +} + +static void zend_generator_add_single_child(zend_generator_node *node, zend_generator *child, zend_generator *leaf) +{ + if (node->children < 4) { + node->child.array[node->children].leaf = leaf; + node->child.array[node->children].child = child; + } else if (node->children > 4) { + zend_hash_index_add_ptr(&node->child.ht, (zend_ulong) leaf, child); + } else { + struct { + zend_generator *leaf; + zend_generator *child; + } array[4]; + int i; + + memcpy(&array, &node->child.array, sizeof(array)); + zend_hash_init(&node->child.ht, 5, sigh, NULL, 0); + for (i = 0; i < 4; i++) { + zend_hash_index_add_ptr(&node->child.ht, (zend_ulong) array[i].leaf, array[i].child); + } + zend_hash_index_add_ptr(&node->child.ht, (zend_ulong) leaf, child); + } + + node->children++; +} + +static void zend_generator_merge_child_nodes(zend_generator_node *dest, zend_generator_node *src, zend_generator *child) +{ + if (src->children <= 4) { + int i = src->children; + while (i--) { + zend_generator_add_single_child(dest, child, src->child.array[i].leaf); + } + } else { + zend_ulong leaf; + ZEND_HASH_FOREACH_NUM_KEY(&src->child.ht, leaf) { + zend_generator_add_single_child(dest, child, (zend_generator *) leaf); + } ZEND_HASH_FOREACH_END(); + } +} + +static void zend_generator_add_child(zend_generator *generator, zend_generator *child) +{ + zend_generator *leaf = child->node.children ? child->node.ptr.leaf : child; + zend_generator_node *multi_children_node; + zend_bool was_leaf = generator->node.children == 0; + + if (was_leaf) { + zend_generator *next = generator->node.parent; + leaf->node.ptr.root = generator->node.ptr.root; + generator->node.ptr.leaf = leaf; + + while (next) { + if (next->node.children > 1) { + if (next->node.children > 4) { + zend_generator *child = zend_hash_index_find_ptr(&next->node.child.ht, (zend_ulong) generator); + zend_hash_index_del(&next->node.child.ht, (zend_ulong) generator); + zend_hash_index_add_ptr(&next->node.child.ht, (zend_ulong) leaf, child); + } else { + switch (next->node.children) { +#define ZEND_GEN_UPDATE_CHILD(x) \ + if (next->node.child.array[x].leaf == generator) { \ + next->node.child.array[x].leaf = leaf; \ + break; \ + } + case 4: + ZEND_GEN_UPDATE_CHILD(3) + case 3: + ZEND_GEN_UPDATE_CHILD(2) + case 2: + ZEND_GEN_UPDATE_CHILD(1) + ZEND_GEN_UPDATE_CHILD(0) + ZEND_ASSERT(0); // we never should have no matching child + } + } + } + + next->node.ptr.leaf = leaf; + next = next->node.parent; + } + + zend_generator_add_single_child(&generator->node, child, leaf); + } else if (generator->node.children == 1) { + multi_children_node = zend_generator_search_multi_children_node(&generator->node); + if (multi_children_node) { + generator->node.children = 0; + zend_generator_merge_child_nodes(&generator->node, multi_children_node, generator->node.child.array[0].child); + } + } + + if (!was_leaf) { + multi_children_node = zend_generator_search_multi_children_node(&child->node); + } else { + multi_children_node = (zend_generator_node *) 0x1; + } + + { + zend_generator *parent = generator->node.parent, *cur = generator; + + if (multi_children_node > (zend_generator_node *) 0x1) { + zend_generator_merge_child_nodes(&generator->node, multi_children_node, child); + } else { + zend_generator_add_single_child(&generator->node, child, leaf); + } + while (parent) { + if (parent->node.children > 1) { + if (multi_children_node == (zend_generator_node *) 0x1) { + multi_children_node = zend_generator_search_multi_children_node(&child->node); + } + if (multi_children_node) { + zend_generator_merge_child_nodes(&parent->node, multi_children_node, cur); + } else { + zend_generator_add_single_child(&parent->node, cur, leaf); + } + } + cur = parent; + parent = parent->node.parent; + } + } +} + +void zend_generator_yield_from(zend_generator *this, zend_generator *from) +{ + zend_generator_add_child(from, this); + + this->node.parent = from; +} + +ZEND_API zend_generator *zend_generator_get_current(zend_generator *generator) +{ + zend_generator *leaf; + zend_generator *root; + + if (generator->node.parent == NULL) { + /* we're not in yield from mode */ + return generator; + } + + leaf = generator->node.children ? generator->node.ptr.leaf : generator; + root = leaf->node.ptr.root; + + if (root->execute_data && root->node.parent == NULL) { + /* generator still running */ + return root; + } + + while (!root->execute_data && root != generator) { + /* generator at the root had stopped */ + root = zend_generator_get_child(&root->node, leaf); + } + + if (root->node.parent) { + if (root->node.parent->execute_data == NULL) { + if (EXPECTED(EG(exception) == NULL)) { + zend_op *yield_from = (zend_op *) root->execute_data->opline - 1; + + if (yield_from->opcode == ZEND_YIELD_FROM && !(yield_from->result_type & EXT_TYPE_UNUSED)) { + if (Z_ISUNDEF(root->node.parent->retval)) { + /* Throw the exception in the context of the generator */ + zend_execute_data *original_execute_data = EG(current_execute_data); + EG(current_execute_data) = root->execute_data; + + if (root == generator) { + root->execute_data->prev_execute_data = original_execute_data; + } else { + root->execute_data->prev_execute_data = &generator->execute_fake; + generator->execute_fake.prev_execute_data = original_execute_data; + } + + zend_throw_exception(zend_ce_ClosedGeneratorException, "Generator yielded from aborted, no return value available", 0); + + EG(current_execute_data) = original_execute_data; + } else { + ZVAL_COPY(ZEND_CALL_VAR(root->execute_data, yield_from->result.var), &root->node.parent->retval); + } + } + } + + root->node.parent = NULL; + } else { + do { + root = root->node.parent; + } while (root->node.parent); + } + } + + return leaf->node.ptr.root = root; +} + +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 *orig_generator) /* {{{ */ { + zend_generator *generator; + /* The generator is already closed, thus can't resume */ - if (!generator->execute_data) { + if (!orig_generator->execute_data) { return; } + generator = zend_generator_get_current(orig_generator); + +try_again: if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) { zend_error(E_EXCEPTION | E_ERROR, "Cannot resume an already running generator"); return; } + 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 + * after the "yield from" expression. */ + } + + if ((orig_generator->flags & ZEND_GENERATOR_DO_INIT) && !Z_ISUNDEF(generator->value)) { + /* We must not advance Generator if we yield from a Generator being currently run */ + return; + } + /* Drop the AT_FIRST_YIELD flag */ - generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; + orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; { /* Backup executor globals */ @@ -290,7 +661,14 @@ ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ /* We want the backtrace to look as if the generator function was * called from whatever method we are current running (e.g. next()). * So we have to link generator call frame with caller call frame. */ - generator->execute_data->prev_execute_data = original_execute_data; + if (generator == orig_generator) { + generator->execute_data->prev_execute_data = original_execute_data; + } else { + /* We need some execute_data placeholder in stacktrace to be replaced + * by the real stack trace when needed */ + generator->execute_data->prev_execute_data = &orig_generator->execute_fake; + orig_generator->execute_fake.prev_execute_data = original_execute_data; + } /* Resume execution */ generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; @@ -301,7 +679,6 @@ ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ if (generator->execute_data) { generator->stack = EG(vm_stack); generator->stack->top = EG(vm_stack_top); - generator->execute_data->prev_execute_data = NULL; } /* Restore executor globals */ @@ -312,9 +689,25 @@ ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ EG(vm_stack) = original_stack; /* If an exception was thrown in the generator we have to internally - * rethrow it in the parent scope. */ + * rethrow it in the parent scope. + * In case we did yield from, the Exception must be rethrown into + * its calling frame (see above in if (check_yield_from). */ if (UNEXPECTED(EG(exception) != NULL)) { - zend_throw_exception_internal(NULL); + zend_generator_close(generator, 0); + + if (generator == orig_generator) { + zend_throw_exception_internal(NULL); + } else { + generator = zend_generator_get_current(orig_generator); + zend_generator_throw_exception(generator, NULL); + goto try_again; + } + } + + /* yield from was used, try another resume. */ + if ((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM)) { + generator = zend_generator_get_current(orig_generator); + goto try_again; } } } @@ -322,8 +715,10 @@ ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */ static void zend_generator_ensure_initialized(zend_generator *generator) /* {{{ */ { - if (generator->execute_data && Z_TYPE(generator->value) == IS_UNDEF) { + if (generator->execute_data && Z_TYPE(generator->value) == IS_UNDEF && generator->node.parent == NULL) { + generator->flags |= ZEND_GENERATOR_DO_INIT; zend_generator_resume(generator); + generator->flags &= ~ZEND_GENERATOR_DO_INIT; generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD; } } @@ -369,7 +764,9 @@ ZEND_METHOD(Generator, valid) zend_generator_ensure_initialized(generator); - RETURN_BOOL(Z_TYPE(generator->value) != IS_UNDEF); + zend_generator_get_current(generator); + + RETURN_BOOL(Z_TYPE(generator->value) != IS_UNDEF || generator->node.parent != NULL); } /* }}} */ @@ -377,7 +774,7 @@ ZEND_METHOD(Generator, valid) * Get the current value */ ZEND_METHOD(Generator, current) { - zend_generator *generator; + zend_generator *generator, *root; if (zend_parse_parameters_none() == FAILURE) { return; @@ -387,8 +784,9 @@ ZEND_METHOD(Generator, current) zend_generator_ensure_initialized(generator); - if (Z_TYPE(generator->value) != IS_UNDEF) { - RETURN_ZVAL_FAST(&generator->value); + root = zend_generator_get_current(generator); + if (Z_TYPE(root->value) != IS_UNDEF) { + RETURN_ZVAL_FAST(&root->value); } } /* }}} */ @@ -397,7 +795,7 @@ ZEND_METHOD(Generator, current) * Get the current key */ ZEND_METHOD(Generator, key) { - zend_generator *generator; + zend_generator *generator, *root; if (zend_parse_parameters_none() == FAILURE) { return; @@ -407,8 +805,9 @@ ZEND_METHOD(Generator, key) zend_generator_ensure_initialized(generator); - if (Z_TYPE(generator->key) != IS_UNDEF) { - RETURN_ZVAL_FAST(&generator->key); + root = zend_generator_get_current(generator); + if (Z_TYPE(root->key) != IS_UNDEF) { + RETURN_ZVAL_FAST(&root->key); } } /* }}} */ @@ -436,7 +835,7 @@ ZEND_METHOD(Generator, next) ZEND_METHOD(Generator, send) { zval *value; - zend_generator *generator; + zend_generator *generator, *root; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) { return; @@ -451,16 +850,18 @@ ZEND_METHOD(Generator, send) return; } + root = zend_generator_get_current(generator); /* Put sent value in the target VAR slot, if it is used */ - if (generator->send_target) { - if (Z_REFCOUNTED_P(generator->send_target)) Z_DELREF_P(generator->send_target); - ZVAL_COPY(generator->send_target, value); + if (root->send_target) { + if (Z_REFCOUNTED_P(root->send_target)) Z_DELREF_P(root->send_target); + ZVAL_COPY(root->send_target, value); } zend_generator_resume(generator); - if (Z_TYPE(generator->value) != IS_UNDEF) { - RETURN_ZVAL_FAST(&generator->value); + root = zend_generator_get_current(generator); + if (Z_TYPE(root->value) != IS_UNDEF) { + RETURN_ZVAL_FAST(&root->value); } } /* }}} */ @@ -483,18 +884,15 @@ ZEND_METHOD(Generator, throw) zend_generator_ensure_initialized(generator); if (generator->execute_data) { - /* Throw the exception in the context of the generator */ - zend_execute_data *current_execute_data = EG(current_execute_data); - EG(current_execute_data) = generator->execute_data; + zend_generator *root = zend_generator_get_current(generator); - zend_throw_exception_object(&exception_copy); - - EG(current_execute_data) = current_execute_data; + zend_generator_throw_exception(root, &exception_copy); zend_generator_resume(generator); - if (Z_TYPE(generator->value) != IS_UNDEF) { - RETURN_ZVAL_FAST(&generator->value); + root = zend_generator_get_current(generator); + if (Z_TYPE(root->value) != IS_UNDEF) { + RETURN_ZVAL_FAST(&root->value); } } else { /* If the generator is already closed throw the exception in the @@ -565,28 +963,34 @@ static int zend_generator_iterator_valid(zend_object_iterator *iterator) /* {{{ zend_generator_ensure_initialized(generator); - return Z_TYPE(generator->value) != IS_UNDEF ? SUCCESS : FAILURE; + zend_generator_get_current(generator); + + return Z_TYPE(generator->value) != IS_UNDEF || generator->node.parent != NULL ? SUCCESS : FAILURE; } /* }}} */ static zval *zend_generator_iterator_get_data(zend_object_iterator *iterator) /* {{{ */ { - zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data); + zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data), *root; zend_generator_ensure_initialized(generator); - return &generator->value; + root = zend_generator_get_current(generator); + + return &root->value; } /* }}} */ static void zend_generator_iterator_get_key(zend_object_iterator *iterator, zval *key) /* {{{ */ { - zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data); + zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data), *root; zend_generator_ensure_initialized(generator); - if (Z_TYPE(generator->key) != IS_UNDEF) { - ZVAL_ZVAL(key, &generator->key, 1, 0); + root = zend_generator_get_current(generator); + + if (Z_TYPE(root->key) != IS_UNDEF) { + ZVAL_ZVAL(key, &root->key, 1, 0); } else { ZVAL_NULL(key); } @@ -691,6 +1095,9 @@ void zend_register_generator_ce(void) /* {{{ */ zend_generator_handlers.dtor_obj = zend_generator_dtor_storage; zend_generator_handlers.clone_obj = NULL; zend_generator_handlers.get_constructor = zend_generator_get_constructor; + + INIT_CLASS_ENTRY(ce, "ClosedGeneratorException", NULL); + zend_ce_ClosedGeneratorException = zend_register_internal_class_ex(&ce, zend_exception_get_default()); } /* }}} */ |