]> granicus.if.org Git - php/commitdiff
Use main VM stack for generators. Only single call frame for generator itself is...
authorDmitry Stogov <dmitry@zend.com>
Wed, 11 May 2016 21:44:18 +0000 (00:44 +0300)
committerDmitry Stogov <dmitry@zend.com>
Wed, 11 May 2016 21:44:18 +0000 (00:44 +0300)
Zend/zend_execute.c
Zend/zend_execute.h
Zend/zend_generators.c
Zend/zend_generators.h

index bd5ccf6c3723bc226bcf31e15d2ef81cd70f0803..bb27463c6cfae42cdec11a9a91ade6e2c47c13f8 100644 (file)
@@ -2343,31 +2343,18 @@ ZEND_API zend_execute_data *zend_create_generator_execute_data(zend_execute_data
         * though this behavior would be suboptimal, because the (rather large)
         * structure would have to be copied back and forth every time execution is
         * suspended or resumed. That's why for generators the execution context
-        * is allocated using a separate VM stack, thus allowing to save and
-        * restore it simply by replacing a pointer.
+        * is allocated using a separate VM stack frame.
         */
        zend_execute_data *execute_data;
        uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
-       size_t stack_size = (ZEND_CALL_FRAME_SLOT + MAX(op_array->last_var + op_array->T, num_args)) * sizeof(zval);
-       uint32_t call_info;
-
-       EG(vm_stack) = zend_vm_stack_new_page(
-               EXPECTED(stack_size < ZEND_VM_STACK_FREE_PAGE_SIZE(1)) ?
-                       ZEND_VM_STACK_PAGE_SIZE(1) :
-                       ZEND_VM_STACK_PAGE_ALIGNED_SIZE(1, stack_size),
-               NULL);
-       EG(vm_stack_top) = EG(vm_stack)->top;
-       EG(vm_stack_end) = EG(vm_stack)->end;
+       uint32_t used_stack = (ZEND_CALL_FRAME_SLOT + num_args + op_array->last_var + op_array->T - MIN(op_array->num_args, num_args)) * sizeof(zval);
 
-       call_info = ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED | (ZEND_CALL_INFO(call) & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS));
-       execute_data = zend_vm_stack_push_call_frame(
-               call_info,
-               (zend_function*)op_array,
-               num_args,
-               Z_TYPE(call->This) != IS_OBJECT ? Z_CE(call->This) : NULL,
-               Z_TYPE(call->This) == IS_OBJECT ? Z_OBJ(call->This) : NULL);
+       execute_data = (zend_execute_data*)emalloc(used_stack);
+       ZEND_SET_CALL_INFO(execute_data, Z_TYPE(call->This) == IS_OBJECT, ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED | (ZEND_CALL_INFO(call) & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)));
+       EX(func) = (zend_function*)op_array;
+       Z_OBJ(EX(This)) = Z_OBJ(call->This);
+       ZEND_CALL_NUM_ARGS(execute_data) = num_args;
        EX(prev_execute_data) = NULL;
-       EX_NUM_ARGS() = num_args;
 
        /* copy arguments */
        if (num_args > 0) {
index 24071719a2f6f3feeefc7eb9fd5bf2a0f5190834..9cca85c4cf5274c24af401d1c247982f88943157 100644 (file)
@@ -260,6 +260,11 @@ static zend_always_inline void zend_vm_stack_free_call_frame_ex(uint32_t call_in
        ZEND_ASSERT_VM_STACK_GLOBAL;
 
        if (UNEXPECTED(call_info & ZEND_CALL_ALLOCATED)) {
+               if (UNEXPECTED(call != (zend_execute_data*)ZEND_VM_STACK_ELEMENTS(EG(vm_stack)))) {
+                       /* This is a generator's stack frame */
+                       efree(call);
+                       return;
+               }
                zend_vm_stack p = EG(vm_stack);
 
                zend_vm_stack prev = p->prev;
index b4dfeba1dac8cce179947903a43f32a878563742..5f8a6b2ce49f622b1a4193ebef7cbed5afa89ffd 100644 (file)
@@ -31,6 +31,71 @@ static zend_object_handlers zend_generator_handlers;
 
 static zend_object *zend_generator_create(zend_class_entry *class_type);
 
+static void zend_restore_call_stack(zend_generator *generator) /* {{{ */
+{
+       zend_execute_data *call, *new_call, *prev_call = NULL;
+
+       call = generator->frozen_call_stack;
+       do {
+               new_call = zend_vm_stack_push_call_frame(
+                       (ZEND_CALL_INFO(call) & ~ZEND_CALL_ALLOCATED),
+                       call->func,
+                       ZEND_CALL_NUM_ARGS(call),
+                       (Z_TYPE(call->This) == IS_UNDEF) ?
+                               (zend_class_entry*)Z_OBJ(call->This) : NULL,
+                       (Z_TYPE(call->This) != IS_UNDEF) ?
+                               Z_OBJ(call->This) : NULL);
+               memcpy(((zval*)new_call) + ZEND_CALL_FRAME_SLOT, ((zval*)call) + ZEND_CALL_FRAME_SLOT, ZEND_CALL_NUM_ARGS(call) * sizeof(zval));
+               new_call->prev_execute_data = prev_call;
+               prev_call = new_call;
+
+               call = call->prev_execute_data;
+       } while (call);
+       generator->execute_data->call = prev_call;
+       efree(generator->frozen_call_stack);
+       generator->frozen_call_stack = NULL;
+}
+/* }}} */
+
+static zend_execute_data* zend_freeze_call_stack(zend_execute_data *execute_data) /* {{{ */
+{
+       size_t used_stack;
+       zend_execute_data *call, *new_call, *prev_call = NULL;
+       zval *stack;
+
+       /* calculate required stack size */
+       used_stack = 0;
+       call = EX(call);
+       do {
+               used_stack += ZEND_CALL_FRAME_SLOT + ZEND_CALL_NUM_ARGS(call);
+               call = call->prev_execute_data;
+       } while (call);
+
+       stack = emalloc(used_stack * sizeof(zval));
+
+       /* save stack, linking frames in reverse order */
+       call = EX(call);
+       do {
+               size_t frame_size = ZEND_CALL_FRAME_SLOT + ZEND_CALL_NUM_ARGS(call);
+
+               new_call = (zend_execute_data*)(stack + used_stack - frame_size);
+               memcpy(new_call, call, frame_size * sizeof(zval));
+               used_stack -= frame_size;
+               new_call->prev_execute_data = prev_call;
+               prev_call = new_call;
+
+               new_call = call->prev_execute_data;
+               zend_vm_stack_free_call_frame(call);
+               call = new_call;
+       } while (call);
+
+       execute_data->call = NULL;
+       ZEND_ASSERT(prev_call == (zend_execute_data*)stack);
+
+       return prev_call;
+}
+/* }}} */
+
 static void zend_generator_cleanup_unfinished_execution(
                zend_generator *generator, uint32_t catch_op_num) /* {{{ */
 {
@@ -40,21 +105,10 @@ static void zend_generator_cleanup_unfinished_execution(
                /* -1 required because we want the last run opcode, not the next to-be-run one. */
                uint32_t op_num = execute_data->opline - execute_data->func->op_array.opcodes - 1;
 
-               /* There may be calls to zend_vm_stack_free_call_frame(), which modifies the VM stack
-                * globals, so need to load/restore those. */
-               zend_vm_stack original_stack = EG(vm_stack);
-               original_stack->top = EG(vm_stack_top);
-               EG(vm_stack_top) = generator->stack->top;
-               EG(vm_stack_end) = generator->stack->end;
-               EG(vm_stack) = generator->stack;
-
-               zend_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
-
-               generator->stack = EG(vm_stack);
-               generator->stack->top = EG(vm_stack_top);
-               EG(vm_stack_top) = original_stack->top;
-               EG(vm_stack_end) = original_stack->end;
-               EG(vm_stack) = original_stack;
+               if (UNEXPECTED(generator->frozen_call_stack)) {
+                       zend_restore_call_stack(generator);
+               }
+               zend_cleanup_unfinished_execution(execute_data, op_num, 0);
        }
 }
 /* }}} */
@@ -100,7 +154,7 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished
                        generator->gc_buffer = NULL;
                }
 
-               efree(generator->stack);
+               efree(generator->execute_data);
                generator->execute_data = NULL;
        }
 }
@@ -331,9 +385,6 @@ ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array
        zend_generator *generator;
        zend_execute_data *current_execute_data;
        zend_execute_data *execute_data;
-       zend_vm_stack current_stack = EG(vm_stack);
-
-       current_stack->top = EG(vm_stack_top);
 
        /* Create new execution context. We have to back up and restore  EG(current_execute_data) here. */
        current_execute_data = EG(current_execute_data);
@@ -350,11 +401,7 @@ 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);
        generator->execute_data = execute_data;
-       generator->stack = EG(vm_stack);
-       generator->stack->top = EG(vm_stack_top);
-       EG(vm_stack_top) = current_stack->top;
-       EG(vm_stack_end) = current_stack->end;
-       EG(vm_stack) = current_stack;
+       generator->frozen_call_stack = NULL;
 
        /* EX(return_value) keeps pointer to zend_object (not a real zval) */
        execute_data->return_value = (zval*)generator;
@@ -765,14 +812,9 @@ try_again:
        {
                /* Backup executor globals */
                zend_execute_data *original_execute_data = EG(current_execute_data);
-               zend_vm_stack original_stack = EG(vm_stack);
-               original_stack->top = EG(vm_stack_top);
 
                /* Set executor globals */
                EG(current_execute_data) = generator->execute_data;
-               EG(vm_stack_top) = generator->stack->top;
-               EG(vm_stack_end) = generator->stack->end;
-               EG(vm_stack) = generator->stack;
 
                /* We want the backtrace to look as if the generator function was
                 * called from whatever method we are current running (e.g. next()).
@@ -786,22 +828,25 @@ try_again:
                        orig_generator->execute_fake.prev_execute_data = original_execute_data;
                }
 
+               if (UNEXPECTED(generator->frozen_call_stack)) {
+                       /* Restore frozen call-stack */
+                       zend_restore_call_stack(generator);
+               }
+
                /* Resume execution */
                generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
                zend_execute_ex(generator->execute_data);
                generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
 
-               /* Unlink generator call_frame from the caller and backup vm_stack_top */
-               if (EXPECTED(generator->execute_data)) {
-                       generator->stack = EG(vm_stack);
-                       generator->stack->top = EG(vm_stack_top);
+               generator->frozen_call_stack = NULL;
+               if (EXPECTED(generator->execute_data) &&
+                   UNEXPECTED(generator->execute_data->call)) {
+                       /* Frize call-stack */
+                       generator->frozen_call_stack = zend_freeze_call_stack(generator->execute_data);
                }
 
                /* Restore executor globals */
                EG(current_execute_data) = original_execute_data;
-               EG(vm_stack_top) = original_stack->top;
-               EG(vm_stack_end) = original_stack->end;
-               EG(vm_stack) = original_stack;
 
                /* If an exception was thrown in the generator we have to internally
                 * rethrow it in the parent scope.
index 95c5147a93a8271205bbdad38346732af11c0487..4e6241fc02e87027fba64b44be90ef729e6b8e66 100644 (file)
@@ -62,8 +62,8 @@ struct _zend_generator {
        /* The suspended execution context. */
        zend_execute_data *execute_data;
 
-       /* The separate stack used by generator */
-       zend_vm_stack stack;
+       /* Frozen call stack for "yield" used in context of other calls */
+       zend_execute_data *frozen_call_stack;
 
        /* Current value */
        zval value;