Skip to content

JIT: Snapshotted poly_func / poly_this may be spilled#18408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base:PHP-8.4
Choose a base branch
from

Conversation

arnaud-lb
Copy link
Member

@arnaud-lbarnaud-lb commented Apr 23, 2025

Polymorphic calls pass this and the function to side traces via snapshotting. However, we assume that this/func are in registers, when in fact they may be spilled.

zend_jit_trace_exit_info.{poly_func_ref,poly_this_ref} are set by zend_jit_init_method_call():

jit->trace->exit_info[exit_point].poly_func_ref=func_ref;
jit->trace->exit_info[exit_point].poly_this_ref=this_ref;

Then, after compilation, zend_jit_trace_exit_info.{poly_func_reg,poly_this_reg} are set by zend_jit_snapshot_handler() from register numbers provided by the snapshot mechanism:

t->exit_info[exit_point].poly_func_reg=reg_ops[n-1];
t->exit_info[exit_point].poly_this_reg=reg_ops[n];

However, these registers may be spilled.

This is properly handled for stack variables, but we can not use the same code for poly_func/poly_this as it's specific to stack vars / zvals.

Here I update snapshotting of poly_func/poly_this to support spilling:

  • In zend_jit_snapshot_handler, store the C stack offset of the spilled register in poly_func_ref/poly_this_ref, in a way similar to how stack variables are handled here:
    t->stack_map[t->exit_info[exit_point].stack_offset+var].ref=ref;
  • In zend_jit_start, do not pre-load the registers if they were spilled
  • In zend_jit_trace_exit / zend_jit_trace_deoptimization, load from the stack if the register was spilled
  • Store a reference to poly_func/poly_this in zend_jit_ctx so we can use that directly in the side trace

Note:

Comment on lines -627 to +632
if (t->exit_count>0
&&jit->ctx.ir_base[addr].val.u64== (uintptr_t)zend_jit_trace_get_exit_addr(t->exit_count-1)) {
exit_point=t->exit_count-1;
if (t->exit_info[exit_point].flags&ZEND_JIT_EXIT_METHOD_CALL) {
n=2;
}
exit_point = zend_jit_exit_point_by_addr((void*)(uintptr_t) jit->ctx.ir_base[addr].val.u64);
ZEND_ASSERT(exit_point != -1);
if (t->exit_info[exit_point].flags & ZEND_JIT_EXIT_METHOD_CALL) {
n = 2;
Copy link
MemberAuthor

@arnaud-lbarnaud-lbApr 23, 2025

Choose a reason for hiding this comment

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

addr is not always the one of the last exit point, e.g. when more exit points are created before an earlier exit point is actually used in a guard:

exit_addr = zend_jit_trace_get_exit_addr(exit_point); ... if (condition) { int32_t exit_point2 = zend_jit_trace_get_exit_point(jit, opline, 0); const void *exit_addr2 = zend_jit_trace_get_exit_addr(exit_point2); ... } ... ir_GUARD(..., ir_CONST_ADDR(exit_addr)); // not the last exit point 

In this case, exit_point was left initialized to 0 and we updated the wrong exit infos.

Copy link
Member

Choose a reason for hiding this comment

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

This deserves a comment

Copy link
Member

Choose a reason for hiding this comment

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

Also can't you do zend_jit_exit_point_by_addr(ptr); ?

@arnaud-lbarnaud-lb marked this pull request as ready for review April 23, 2025 17:31
@arnaud-lbarnaud-lb requested a review from dstogov as a code ownerApril 23, 2025 17:31
return rs;
}

bool zend_jit_ref_snapshot_equals(zend_jit_ref_snapshot *a, zend_jit_ref_snapshot *b)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Could be const pointers

Comment on lines -627 to +632
if (t->exit_count>0
&&jit->ctx.ir_base[addr].val.u64== (uintptr_t)zend_jit_trace_get_exit_addr(t->exit_count-1)) {
exit_point=t->exit_count-1;
if (t->exit_info[exit_point].flags&ZEND_JIT_EXIT_METHOD_CALL) {
n=2;
}
exit_point = zend_jit_exit_point_by_addr((void*)(uintptr_t) jit->ctx.ir_base[addr].val.u64);
ZEND_ASSERT(exit_point != -1);
if (t->exit_info[exit_point].flags & ZEND_JIT_EXIT_METHOD_CALL) {
n = 2;
Copy link
Member

Choose a reason for hiding this comment

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

This deserves a comment

Comment on lines -627 to +632
if (t->exit_count>0
&&jit->ctx.ir_base[addr].val.u64== (uintptr_t)zend_jit_trace_get_exit_addr(t->exit_count-1)) {
exit_point=t->exit_count-1;
if (t->exit_info[exit_point].flags&ZEND_JIT_EXIT_METHOD_CALL) {
n=2;
}
exit_point = zend_jit_exit_point_by_addr((void*)(uintptr_t) jit->ctx.ir_base[addr].val.u64);
ZEND_ASSERT(exit_point != -1);
if (t->exit_info[exit_point].flags & ZEND_JIT_EXIT_METHOD_CALL) {
n = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Also can't you do zend_jit_exit_point_by_addr(ptr); ?

Copy link
Member

@dstogovdstogov left a comment

Choose a reason for hiding this comment

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

I didn't found obvious problems.

Please verify this using community tests before merging.
I don't object against merging this into PHP-8.4.

@@ -710,6 +710,29 @@ uint32_t zend_jit_duplicate_exit_point(ir_ctx *ctx, zend_jit_trace_info *t, uint
return new_exit_point;
}

zend_jit_ref_snapshot zend_jit_resolve_ref_snapshot(ir_ctx *ctx, ir_ref snapshot_ref, ir_insn *snapshot, int op)
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer not to return "long" structures.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
3 participants
@arnaud-lb@dstogov@nielsdos
close