-
Notifications
You must be signed in to change notification settings - Fork 7.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix GH-15834: Segfault with hook "simple get" cache slot and minimal JIT #17909
base: PHP-8.4
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you Niels! It seems I have missed the original bug report. Dmitry should judge whether this is the correct fix.
ext/opcache/jit/zend_jit.c
Outdated
goto jit_failure; | ||
} | ||
|
||
/* If a simple hook is called, exit to the VM. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this could be omitted for certain property fetches, i.e. the ones that are known not to be polymorphic & don't contain hooks. More concretely, private
and final
properties.
ext/opcache/jit/zend_jit.c
Outdated
ir_ref if_hook_enter = ir_IF(jit_CMP_IP(jit, IR_EQ, opline + 1)); | ||
ir_IF_FALSE(if_hook_enter); | ||
if (GCC_GLOBAL_REGS) { | ||
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit))); | ||
} else { | ||
ir_RETURN(ir_CONST_I32(1)); /* ZEND_VM_ENTER */ | ||
} | ||
ir_IF_TRUE(if_hook_enter); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking for a possible hook call after each property read - looks stupid...
Previously we had magic __get
and __set
, but we didn't optimize their calls through trampolines...
I understand, this is targeted for "-O1", but this also will affect any non-constant property access.
Please check how this affect the resulting code size on typical opcache.jit=1205 and 1254 (on Wordpress and/or Symfony Demo).
Shouldn't you also do the same for FETCH_OBJ_W
and maybe others?
Tracing JIT should be also affected by this problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously we had magic __get and __set, but we didn't optimize their calls through trampolines...
Indeed, but this optimization makes get hooks as fast as getter functions. While the vast majority of public properties will not have hooks, and thus still improve performance, it was still important to us that hooks are not significantly slower than a getter function.
Shouldn't you also do the same for FETCH_OBJ_W and maybe others?
This optimization only exists for property read.
Checking for a possible hook call after each property read - looks stupid...
Maybe we can avoid this by creating a custom VM handler that is only used here, that omits the "simple get" optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check how do this work with non-constant property reads in tracing JIT. (I mean $this->{$name}
that leads to read property hook).
I guess, tracing JIT also may need to be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't have time anymore to check this today. I kinda missed that this codegen path may still be taken a lot of times.
For call VM we can improve the codegen to check the return value of the VM handler directly instead of comparing IP, that should make more compact machine code.
A custom VM specialization may help, the question would be if the trade-off seems reasonable.
I'll think a bit more about possible other solutions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rebased and I pushed a little optimization.
Unfortunately, I get an assertion failure on Symfony with jit=1205. This already happens without this patch. No influence yet on jit=1254 because I didn't yet change tracing JIT code (and didn't have time to do so yet). To reproduce, just run php bin/console cache:clear
then php bin/console cache:warmup
and then php -d opcache.jit=1205 public/index.php
For WordPress on jit=1205 I get an increase from 35164971 to 35166107 in JIT memory usage. An increase of 1136 bytes, or 1.000032305x increase. So quite a few checks added, although < 0.01% increase.
…al JIT The FETCH_OBJ_R VM handler has an optimization that directly enters into a hook if it is a simpler getter hook. This is not compatible with the minimal JIT because the minimal JIT will try to continue executing the opcodes after the FETCH_OBJ_R. To solve this, we check whether the opcode is still the expected one after the execution of the VM handler. If it is not, we know that we are going to execute a simple hook. In that case, exit to the VM.
if (opline->op2_type != IS_CONST | ||
|| Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING | ||
|| Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') { | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you move this down?
I suppose, you relay on ce
selected above in non-optimized path.
This leads to false positive build failures, because of possibly uninitialized ce
. This should be fixed.
I'm not completely sure if this code is going to be right for properties overridden with hooked ones in children.
Let we generate the code for parent class context where ce->num_hooked_props
is zero. Then we execute it in context of child that overrides the accessed property...@iluuu1994 please check this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you move this down? I suppose, you relay on
ce
selected above in non-optimized path. This leads to false positive build failures, because of possibly uninitializedce
. This should be fixed.
Indees that is the reason for moving down. I can fix build failure.
I'm not completely sure if this code is going to be right for properties overridden with hooked ones in children. Let we generate the code for parent class context where
ce->num_hooked_props
is zero. Then we execute it in context of child that overrides the accessed property...@iluuu1994 please check this
I only skip the check for final classes without hooks, so this should not be a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only skip the check for final classes without hooks, so this should not be a problem.
Right. Then this shouldn't be a problem.
did you check, if the same problem affects the tracing JIT? |
I had little time this weekend and didn't have time to check this yet. I will try to check this evening as I'm leaving for work soon. I also did not yet have time to investigate the assertion failure I mentioned in #17909 (comment) |
OK. Just let me know when this is ready for the final review. The code looks right and I don't have objections. |
The bug does not affect this case. It is because for non-constant names
When tracing JIT starts execution, it removes the php-src/ext/opcache/jit/zend_jit_vm_helpers.c Lines 863 to 865 in cd65240
Because the cache slot is not reset to NULLs, only the flag is cleared, the So to me it seems tracing JIT is not affected. I also tested this manually and it always takes the slow path. I made new memory measurements with the latest patch, and with my IR fix applied so function JIT works on Symfony:
So with the new IS_CONST check added, there is no extra overhead for the Symfony demo or WordPress homepage. Note though that in function JIT it may still generate the check, for example for a constant property name that's either not a string or starts with a '\0' byte. |
@dstogov this is ready for review please |
The FETCH_OBJ_R VM handler has an optimization that directly enters into a hook if it is a simpler getter hook. This is not compatible with the minimal JIT because the minimal JIT will try to continue executing the opcodes after the FETCH_OBJ_R.
To solve this, we check whether the opcode is still the expected one after the execution of the VM handler. If it is not, we know that we are going to execute a simple hook. In that case, exit to the VM.
Normally, I wouldn't care too much about this issue, because it is in minimal JIT. However, someone had this cause a crash to them by accident: #17767