You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Tracing JIT: segfault on $f($arr[k]) when $f alternates between by-value and by-reference functions #24438
Warning: Undefined array key 1 in reduced.php on line 5
Warning: Undefined array key 0 in reduced.php on line 5
php: Zend/zend_operators.c:1016: zval_get_long_func: Assertion `0' failed.
Release build: the same two warnings, then SIGSEGV (exit 139).
With opcache.jit=off the script only prints the two warnings and exits normally, so I expected the same under the JIT. jit_max_polymorphic_calls=0 is not required: with the default value, a variant with five callees (four by-value, one by-ref) and a defined $arr = [5, 6] crashes the same way. The hot counters of 1 are needed to form the traces in a short script.
It reproduces at 9253b03 (latest upstream when checked): the debug build hits the assertion above, the release build segfaults (rc 139).
Looks like the megamorphic INIT_DYNAMIC_CALL (call->func unknown) makes zend_jit_trace.c skip the specialised FETCH_DIM_FUNC_ARG, but zend_jit_send_var (zend_jit_ir.c, ZEND_SEND_FUNC_ARG) still compiles the by-ref branch using the recorded type of the fetched value, which does not include MAY_BE_INDIRECT. ZEND_FETCH_OBJ_FUNC_ARG has a zend_jit_func_arg_by_ref_guard for this case and the DIM variant does not. This is from reading jit_debug trace output, not confirmed in a debugger. Open PR #18392 (reworks EX(call) handling in the call/send path) may be related; I did not test it, its diff does not apply on the latest commit.
I can send a patch if that helps.
Found with an LLM-based testing tool; I used Claude to reduce it and look for the cause.
i'll first try reproducing the crash on current PHP builds with tracing JIT enabled and compare the behavior with JIT disabled
then i'll inspect the generated traces around FETCH_DIM_FUNC_ARG and ZEND_SEND_FUNC_ARG, particularly how the by-reference case handles MAY_BE_INDIRECT and whether the missing guard is actually responsible for the crash
i'll also check whether #18392 affects the same path before attempting a fix
have you already started working on a patch? if so i'd be happy to compare findings or help test it rather than duplicate the work
quick update, i managed to reproduce this on php 8.5.13-dev with tracing jit enabled. the same input runs normally with jit disabled, but the tracing jit hits the zval_get_long_func() assertion.
after looking through the trace output, i noticed that a side trace starts directly at SEND_FUNC_ARG with an indirect operand, but its tssa type mask is missing MAY_BE_INDIRECT. this seems to come from the temporary fallback in zend_jit_trace_build_tssa(), which doesn't include that bit when regular ssa information isn't available. zend_jit_send_ref() relies on it to emit the indirect dereference, so the mismatch looks like the actual issue rather than a missing dim guard.
i've tested a one-line change that adds MAY_BE_INDIRECT to that fallback, along with a phpt that checks the by-value/by-reference behavior and the resulting array values. on the same php revision and debug build configuration, the original fails the new test with the assertion, while the patched version passes with the expected output. seven related jit tests also pass with jit both enabled and disabled.
i'm checking the broader opcache jit tests now before opening a pr, but the results so far look promising.
another update, i've finished validating the candidate fix.
the issue appears to come from the fallback type information used when a side trace starts with a live-in temporary. it doesn't include MAY_BE_INDIRECT, so when the trace resumes directly at SEND_FUNC_ARG, zend_jit_send_ref() can miss the indirect dereference.
i added MAY_BE_INDIRECT to that fallback and wrote a phpt checking the actual by-value/by-reference behavior. i rebuilt both versions from the same php 8.5 revision and configuration. the original fails the test with the assertion, while the patched version passes with the expected array values. both pass with jit disabled.
the full ext/opcache/tests/jit/ directory also passed on the patched build: 515 passes, no failures, and 10 skips due to missing extensions or platform requirements. the original reproducer now completes with exactly the same warnings as the interpreter, and additional checks for existing references and copy-on-write behavior also matched.
i also confirmed the original issue reproduces on an existing php 8.4 debug build, although i haven't tested the patch on 8.4 yet.
i haven't found any regressions in the tested configurations, so i'll prepare the pr for review.
the issue appears to be caused by the side-trace fallback type mask missing MAY_BE_INDIRECT, which can prevent zend_jit_send_ref() from correctly handling an indirect argument.
the fix is a one-line change with a regression test. i confirmed that the original implementation fails the test while the patched version passes. the full jit test directory also completed with 515 passes, no failures and 10 expected skips.
i've targeted PHP-8.4 since the original issue reproduces there as well.
thanks for the detailed reproducer, it was really helpful in tracking this down. feedback on the pr is welcome!
Activity
aasul commentedon Oct 25, 2026
i'd like to look into this
i'll first try reproducing the crash on current PHP builds with tracing JIT enabled and compare the behavior with JIT disabled
then i'll inspect the generated traces around
FETCH_DIM_FUNC_ARGandZEND_SEND_FUNC_ARG, particularly how the by-reference case handlesMAY_BE_INDIRECTand whether the missing guard is actually responsible for the crashi'll also check whether #18392 affects the same path before attempting a fix
have you already started working on a patch? if so i'd be happy to compare findings or help test it rather than duplicate the work
i'll share anything useful i find here
aasul commentedon Oct 25, 2026
quick update, i managed to reproduce this on php 8.5.13-dev with tracing jit enabled. the same input runs normally with jit disabled, but the tracing jit hits the
zval_get_long_func()assertion.after looking through the trace output, i noticed that a side trace starts directly at
SEND_FUNC_ARGwith an indirect operand, but its tssa type mask is missingMAY_BE_INDIRECT. this seems to come from the temporary fallback inzend_jit_trace_build_tssa(), which doesn't include that bit when regular ssa information isn't available.zend_jit_send_ref()relies on it to emit the indirect dereference, so the mismatch looks like the actual issue rather than a missing dim guard.i've tested a one-line change that adds
MAY_BE_INDIRECTto that fallback, along with a phpt that checks the by-value/by-reference behavior and the resulting array values. on the same php revision and debug build configuration, the original fails the new test with the assertion, while the patched version passes with the expected output. seven related jit tests also pass with jit both enabled and disabled.i'm checking the broader opcache jit tests now before opening a pr, but the results so far look promising.
aasul commentedon Oct 25, 2026
another update, i've finished validating the candidate fix.
the issue appears to come from the fallback type information used when a side trace starts with a live-in temporary. it doesn't include
MAY_BE_INDIRECT, so when the trace resumes directly atSEND_FUNC_ARG,zend_jit_send_ref()can miss the indirect dereference.i added
MAY_BE_INDIRECTto that fallback and wrote a phpt checking the actual by-value/by-reference behavior. i rebuilt both versions from the same php 8.5 revision and configuration. the original fails the test with the assertion, while the patched version passes with the expected array values. both pass with jit disabled.the full
ext/opcache/tests/jit/directory also passed on the patched build: 515 passes, no failures, and 10 skips due to missing extensions or platform requirements. the original reproducer now completes with exactly the same warnings as the interpreter, and additional checks for existing references and copy-on-write behavior also matched.i also confirmed the original issue reproduces on an existing php 8.4 debug build, although i haven't tested the patch on 8.4 yet.
i haven't found any regressions in the tested configurations, so i'll prepare the pr for review.
aasul commentedon Oct 25, 2026
i've opened a pr for this now: #24699
the issue appears to be caused by the side-trace fallback type mask missing
MAY_BE_INDIRECT, which can preventzend_jit_send_ref()from correctly handling an indirect argument.the fix is a one-line change with a regression test. i confirmed that the original implementation fails the test while the patched version passes. the full jit test directory also completed with 515 passes, no failures and 10 expected skips.
i've targeted
PHP-8.4since the original issue reproduces there as well.thanks for the detailed reproducer, it was really helpful in tracking this down. feedback on the pr is welcome!
ndossche commentedon Oct 25, 2026
Thanks for investigating aasul, I'll have a look.
Fix phpGH-24438: Preserve indirect types in JIT side traces
Fix phpGH-24438: Preserve indirect types in JIT side traces
Merge branch 'PHP-8.4' into PHP-8.5
Merge branch 'PHP-8.5' into PHP-8.6
Merge branch 'PHP-8.6'