Skip to content

Commit c02c1d4

Browse files
committed
Change YIELD/YIELD_FROM to do not increment opline (#15328)
YIELD and YIELD_FROM increment opline before returning, but in most places we need the opline to point to the YIELD and YIELD_FROM. Here I change YIELD / YIELD_FROM to not increment opline. This simplifies the code and fixes GH-15275 in a better way. Closes GH-15328
1 parent 60a055f commit c02c1d4

7 files changed

+40
-162
lines changed

UPGRADING.INTERNALS

+5
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,11 @@ PHP 8.4 INTERNALS UPGRADE NOTES
360360
that provide a frameless handler (search for usages of the
361361
ZEND_FRAMELESS_FUNCTION macro).
362362

363+
* CREATE_GENERATOR now initializes the generator with opline pointing to the
364+
CREATE_GENERATOR op (before, opline was set to the next op).
365+
366+
* YIELD and YIELD_FROM do not increment the opline anymore.
367+
363368
========================
364369
5. SAPI changes
365370
========================

Zend/zend_execute.c

+10-29
Original file line numberDiff line numberDiff line change
@@ -4711,19 +4711,7 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data,
47114711

47124712
ZEND_API ZEND_ATTRIBUTE_DEPRECATED HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer)
47134713
{
4714-
bool suspended_by_yield = false;
4715-
4716-
if (Z_TYPE_INFO(EX(This)) & ZEND_CALL_GENERATOR) {
4717-
ZEND_ASSERT(EX(return_value));
4718-
4719-
/* The generator object is stored in EX(return_value) */
4720-
zend_generator *generator = (zend_generator*) EX(return_value);
4721-
ZEND_ASSERT(execute_data == generator->execute_data);
4722-
4723-
suspended_by_yield = !(generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING);
4724-
}
4725-
4726-
return zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, suspended_by_yield);
4714+
return zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, false);
47274715
}
47284716

47294717
ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer, bool suspended_by_yield)
@@ -4761,27 +4749,20 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d
47614749
zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params);
47624750
}
47634751

4752+
uint32_t op_num;
4753+
if (UNEXPECTED(execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION)) {
4754+
op_num = EG(opline_before_exception) - op_array->opcodes;
4755+
} else {
4756+
op_num = execute_data->opline - op_array->opcodes;
4757+
}
4758+
ZEND_ASSERT(op_num < op_array->last);
4759+
47644760
if (call) {
4765-
uint32_t op_num;
4766-
if (UNEXPECTED(execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION)) {
4767-
op_num = EG(opline_before_exception) - op_array->opcodes;
4768-
} else {
4769-
op_num = execute_data->opline - op_array->opcodes;
4770-
}
4771-
ZEND_ASSERT(op_num < op_array->last);
4772-
if (suspended_by_yield) {
4773-
/* When the execution was suspended by yield, EX(opline) points to
4774-
* next opline to execute. Otherwise, it points to the opline that
4775-
* suspended execution. */
4776-
op_num--;
4777-
ZEND_ASSERT(EX(func)->op_array.opcodes[op_num].opcode == ZEND_YIELD
4778-
|| EX(func)->op_array.opcodes[op_num].opcode == ZEND_YIELD_FROM);
4779-
}
47804761
zend_unfinished_calls_gc(execute_data, call, op_num, gc_buffer);
47814762
}
47824763

47834764
if (execute_data->opline != op_array->opcodes) {
4784-
uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1;
4765+
uint32_t i;
47854766
for (i = 0; i < op_array->last_live_range; i++) {
47864767
const zend_live_range *range = &op_array->live_range[i];
47874768
if (range->start > op_num) {

Zend/zend_generators.c

+16-24
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ static void zend_generator_cleanup_unfinished_execution(
116116
{
117117
zend_op_array *op_array = &execute_data->func->op_array;
118118
if (execute_data->opline != op_array->opcodes) {
119-
/* -1 required because we want the last run opcode, not the next to-be-run one. */
120-
uint32_t op_num = execute_data->opline - op_array->opcodes - 1;
119+
uint32_t op_num = execute_data->opline - op_array->opcodes;
121120

122121
if (UNEXPECTED(generator->frozen_call_stack)) {
123122
/* Temporarily restore generator->execute_data if it has been NULLed out already. */
@@ -299,9 +298,7 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
299298
return;
300299
}
301300

302-
/* -1 required because we want the last run opcode, not the
303-
* next to-be-run one. */
304-
op_num = ex->opline - ex->func->op_array.opcodes - 1;
301+
op_num = ex->opline - ex->func->op_array.opcodes;
305302
try_catch_offset = -1;
306303

307304
/* Find the innermost try/catch that we are inside of. */
@@ -331,7 +328,8 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
331328
Z_OBJ_P(fast_call) = NULL;
332329
Z_OPLINE_NUM_P(fast_call) = (uint32_t)-1;
333330

334-
ex->opline = &ex->func->op_array.opcodes[try_catch->finally_op];
331+
/* -1 because zend_generator_resume() will increment it */
332+
ex->opline = &ex->func->op_array.opcodes[try_catch->finally_op] - 1;
335333
generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
336334
zend_generator_resume(generator);
337335

@@ -516,9 +514,6 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
516514
/* Throw the exception in the context of the generator. Decrementing the opline
517515
* to pretend the exception happened during the YIELD opcode. */
518516
EG(current_execute_data) = generator->execute_data;
519-
generator->execute_data->opline--;
520-
ZEND_ASSERT(generator->execute_data->opline->opcode == ZEND_YIELD
521-
|| generator->execute_data->opline->opcode == ZEND_YIELD_FROM);
522517
generator->execute_data->prev_execute_data = original_execute_data;
523518

524519
if (exception) {
@@ -527,8 +522,6 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
527522
zend_rethrow_exception(EG(current_execute_data));
528523
}
529524

530-
generator->execute_data->opline++;
531-
532525
/* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */
533526
if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) {
534527
zval_ptr_dtor(&generator->values);
@@ -621,7 +614,7 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator
621614
zend_generator_remove_child(&new_root_parent->node, new_root);
622615

623616
if (EXPECTED(EG(exception) == NULL) && EXPECTED((OBJ_FLAGS(&generator->std) & IS_OBJ_DESTRUCTOR_CALLED) == 0)) {
624-
zend_op *yield_from = (zend_op *) new_root->execute_data->opline - 1;
617+
zend_op *yield_from = (zend_op *) new_root->execute_data->opline;
625618

626619
if (yield_from->opcode == ZEND_YIELD_FROM) {
627620
if (Z_ISUNDEF(new_root_parent->retval)) {
@@ -636,8 +629,6 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator
636629
generator->execute_fake.prev_execute_data = original_execute_data;
637630
}
638631

639-
/* ZEND_YIELD(_FROM) already advance, so decrement opline to throw from correct place */
640-
new_root->execute_data->opline--;
641632
zend_throw_exception(zend_ce_ClosedGeneratorException, "Generator yielded from aborted, no return value available", 0);
642633

643634
EG(current_execute_data) = original_execute_data;
@@ -815,15 +806,6 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
815806
generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
816807
return;
817808
}
818-
if (UNEXPECTED(EG(exception))) {
819-
/* Decrementing opline_before_exception to pretend the exception
820-
* happened during the YIELD_FROM opcode. */
821-
if (generator->execute_data) {
822-
ZEND_ASSERT(generator->execute_data->opline == EG(exception_op));
823-
ZEND_ASSERT((EG(opline_before_exception)-1)->opcode == ZEND_YIELD_FROM);
824-
EG(opline_before_exception)--;
825-
}
826-
}
827809
/* If there are no more delegated values, resume the generator
828810
* after the "yield from" expression. */
829811
}
@@ -834,6 +816,16 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
834816
}
835817

836818
/* Resume execution */
819+
ZEND_ASSERT(generator->execute_data->opline->opcode == ZEND_GENERATOR_CREATE
820+
|| generator->execute_data->opline->opcode == ZEND_YIELD
821+
|| generator->execute_data->opline->opcode == ZEND_YIELD_FROM
822+
/* opline points to EG(exception_op), which is a sequence of
823+
* ZEND_HANDLE_EXCEPTION ops, so the following increment is safe */
824+
|| generator->execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION
825+
/* opline points to the start of a finally block minus one op to
826+
* account for the following increment */
827+
|| (generator->flags & ZEND_GENERATOR_FORCED_CLOSE));
828+
generator->execute_data->opline++;
837829
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
838830
if (!ZEND_OBSERVER_ENABLED) {
839831
zend_execute_ex(generator->execute_data);
@@ -880,7 +872,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
880872
}
881873

882874
/* yield from was used, try another resume. */
883-
if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) {
875+
if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM))) {
884876
generator = zend_generator_get_current(orig_generator);
885877
goto try_again;
886878
}

Zend/zend_vm_def.h

+1-9
Original file line numberDiff line numberDiff line change
@@ -4609,7 +4609,7 @@ ZEND_VM_HANDLER(139, ZEND_GENERATOR_CREATE, ANY, ANY)
46094609
generator->execute_fake.prev_execute_data = NULL;
46104610
ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator);
46114611

4612-
gen_execute_data->opline = opline + 1;
4612+
gen_execute_data->opline = opline;
46134613
/* EX(return_value) keeps pointer to zend_object (not a real zval) */
46144614
gen_execute_data->return_value = (zval*)generator;
46154615
call_info = Z_TYPE_INFO(EX(This));
@@ -8416,10 +8416,6 @@ ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMPVAR|CV|UNUSED
84168416
generator->send_target = NULL;
84178417
}
84188418

8419-
/* We increment to the next op, so we are at the correct position when the
8420-
* generator is resumed. */
8421-
ZEND_VM_INC_OPCODE();
8422-
84238419
/* The GOTO VM uses a local opline variable. We need to set the opline
84248420
* variable in execute_data so we don't resume at an old position. */
84258421
SAVE_OPLINE();
@@ -8522,10 +8518,6 @@ ZEND_VM_C_LABEL(yield_from_try_again):
85228518
/* This generator has no send target (though the generator we delegate to might have one) */
85238519
generator->send_target = NULL;
85248520

8525-
/* We increment to the next op, so we are at the correct position when the
8526-
* generator is resumed. */
8527-
ZEND_VM_INC_OPCODE();
8528-
85298521
/* The GOTO VM uses a local opline variable. We need to set the opline
85308522
* variable in execute_data so we don't resume at an old position. */
85318523
SAVE_OPLINE();

0 commit comments

Comments
 (0)