Skip to content

Fix GH-21368 follow-up: escape_if_undef on CALL VM#22028

Draft
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh21368-fc-func-and-call-vm
Draft

Fix GH-21368 follow-up: escape_if_undef on CALL VM#22028
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh21368-fc-func-and-call-vm

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@iliaal iliaal commented May 12, 2026

Follow-up to #21368, kept separate from #21710 per arnaud-lb's review.

Two fixes in zend_jit_escape_if_undef on PHP-8.4 CALL VM (full mechanism in the commit message): register orig_handler as IR_FUNC_ADDR instead of IR_ADDR (was tripping the FUNC_ADDR assertion in side-trace compile), and replace ir_TAILCALL_1 with ir_CALL_1 + ir_RETURN(1) so execute_ex reloads execute_data after the deopt.

gh21368_call_vm.phpt covers the CALL-VM crash. The FUNC_ADDR assertion case reproduces via Symfony HttpClient's testIgnoreErrors / testDnsError / testInlineAuth / testBadRequestBody sequence (side trace from prepareRequest():100); smaller subsets don't fire. COMMUNITY CI catches regressions.

With fix: Symfony runs 1170/1171 (1 DNS env failure), new test passes. Without: Symfony aborts at test 14 on the assertion, new test SEGVs in ZEND_DO_FCALL.

/cc @arnaud-lb: you offered to handle this; I took a first stab. Please reshape if needed.

Two issues reachable on debug Clang/CALL builds of PHP-8.4.

ir_CONST_ADDR for orig_handler trips jit_CONST_FUNC_PROTO's FUNC_ADDR
assertion when another site reuses the same handler via
ir_CONST_FC_FUNC in the same trace compile (Symfony HttpClient test
suite repro).

The ir_TAILCALL_1 to orig_handler leaves execute_ex with stale
execute_data after a frame-changing handler. PHP-8.4's CALL VM needs a
non-zero return to reload from EG(current_execute_data); the tail-call
discards that signal. gh21267.phpt with aggressive jit_hot_* ini
reproduces a crash in ZEND_DO_FCALL.

Use ir_CONST_FC_FUNC to register the handler as FUNC_ADDR, and mirror
zend_jit_tail_handler's ZEND_FUNC_RECURSIVE_DIRECTLY branch (ir_CALL_1
+ ir_RETURN(1)) so execute_ex reloads. The ir_CAST_FC_FUNC on x86
becomes dead.
@iliaal iliaal requested a review from arnaud-lb May 12, 2026 20:23
iliaal referenced this pull request May 12, 2026
…#21368)

When the JIT defers the IS_UNDEF check for FETCH_OBJ_R to the result
type guard, the deoptimization escape path dispatches to opline->handler
via the trace_escape stub. If opline->handler has been overwritten with
JIT code (e.g. a function entry trace), this creates an infinite loop.

Fix by dispatching to the original VM handler (orig_handler from the
trace extension) instead of going through the trace_escape stub. This
avoids the extra IS_UNDEF guard on every property read while correctly
handling the rare IS_UNDEF case during deoptimization.

Also set current_op_array in zend_jit_trace_exit_to_vm so that the
blacklisted exit deoptimizer can resolve orig_handler, covering the
case where side trace compilation is exhausted.

Closes GH-21368.
Copy link
Copy Markdown
Member

@arnaud-lb arnaud-lb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

Thank you!

Comment on lines 8100 to +8104
if (GCC_GLOBAL_REGS) {
ir_TAILCALL(IR_VOID, ref);
} else {
#if defined(IR_TARGET_X86)
ref = ir_CAST_FC_FUNC(ref);
#endif
ir_TAILCALL_1(IR_I32, ref, jit_FP(jit));
ir_CALL_1(IR_I32, ref, jit_FP(jit));
ir_RETURN(ir_CONST_I32(1));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When merging up, you can do something like this:

	if (GCC_GLOBAL_REGS || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) {
		ir_TAILCALL(IR_OPCODE_HANDLER_RET, ref);
	} else {
		ir_ref opline_ref = ir_CALL_2(IR_OPCODE_HANDLER_RET, ref, jit_FP(jit), jit_IP(jit));
		zend_jit_vm_enter(jit, opline_ref);
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants