Fix GH-23628: Tracing JIT reads undefined property slots of lazy proxies - #1
Fix GH-23628: Tracing JIT reads undefined property slots of lazy proxies#1lisachenko wants to merge 1 commit into
Conversation
…roxies A lazy proxy keeps its own property slots IS_UNDEF|IS_PROP_LAZY even after it has been initialized, and the object handlers forward every property access to the real instance. The tracing JIT was not aware of this in two places: 1. When the recorded trace contained a FETCH_OBJ_R/IS/W on a known property whose slot was IS_UNDEF, the known-offset fast path was still compiled. For a lazy proxy this path never succeeds, and it deoptimized on every execution. Use the generic code path (that falls back to the object handlers for undefined slots) when the slot was IS_UNDEF at recording time. This also covers uninitialized and unset properties. 2. During deoptimization of a failed result type guard after FETCH_OBJ_IS, an IS_UNDEF slot was turned into NULL, assuming an undefined property. For a slot flagged IS_PROP_LAZY the fetch has to be forwarded to the real instance instead, so re-execute the opline in the VM, the same way it is already done for FETCH_OBJ_R. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECekMqERF8jnqBxo1cXe3V
|
CI status on This failure is not this PR's:
No fix exists to port for that test. I will re-run the failed job once the workflow run has finished (a re-run is refused while the other suites are still running). Generated by Claude Code |
|
Re-run result for It exercises the same built-in web server I'm not spending another re-run on it; the failure is outside this PR's scope and cannot be made robust from a PHP-8.5 based branch. Generated by Claude Code |
Fixes php#23628 (see also goaop/framework bug report).
Problem
A lazy proxy keeps its own property slots
IS_UNDEF | IS_PROP_LAZYeven after it has been initialized; the object handlers forward each property access to the real instance. The tracing JIT was not aware of this:ext/opcache/jit/zend_jit_ir.c,zend_jit_fetch_obj()– when aFETCH_OBJ_R/IS/Won a known property was recorded with anIS_UNDEFslot (val(undef)in the trace), the known-offset fast path was compiled anyway. For a lazy proxy that path never succeeds and deoptimized on every execution. The fix uses the generic code path (run-time cache +read_property/get_property_ptr_ptrfallback for undefined slots) when the slot was undefined at recording time. This also covers uninitialized/unset properties, and it mirrors whatzend_jit_assign_obj()already does forprop_type == IS_UNDEF.ext/opcache/jit/zend_jit_trace.c,zend_jit_trace_exit()– when a result type guard afterFETCH_OBJ_ISfailed on anIS_UNDEFslot, deoptimization turned the result intoNULL, assuming an undefined property. That is what madeisset($this->map['start']['next'])returnfalsestarting at the iteration where the trace kicked in. For a slot flaggedIS_PROP_LAZYthe opline is now re-executed in the VM (same mechanism already used forFETCH_OBJ_R), so the read is forwarded to the real instance. Plain undefined properties keep the previousNULLshortcut.Tests
ext/opcache/tests/jit/gh23628_001.phpt– proxy from the start (isset on nested dim,??, plain read, write,++) and a lazy ghost. Exercises fix 1.ext/opcache/tests/jit/gh23628_002.phpt– trace recorded on a regular object, then executed on a proxy. Exercises fix 2 (deoptimization path).Both tests fail on the unpatched build (
isset false at N) and pass with the patch.Verified locally (debug build, x86_64):
ext/opcache/tests/jit+Zend/tests/lazy_objects: 708 passed, 0 failed.Zend/tests,ext/opcache/tests,ext/reflection/testswithopcache.jit=tracingforced andjit_hot_loop/func/return/side_exit=1: 6588 passed, 0 failed.1000under tracing JIT, and the compiled trace no longer contains a deoptimizing exit on the property fetch.🤖 Generated with Claude Code
https://claude.ai/code/session_01ECekMqERF8jnqBxo1cXe3V
Generated by Claude Code