-
Notifications
You must be signed in to change notification settings - Fork 8k
Tailcall VM interrupt crash #21922
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
base: PHP-8.5
Are you sure you want to change the base?
Tailcall VM interrupt crash #21922
Changes from all commits
7c26c21
914efeb
57d406a
0c31be1
f9fd505
6af994d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1594,6 +1594,13 @@ function gen_halt_handler($f, $kind) { | |
| out($f,"}\n\n"); | ||
| } | ||
|
|
||
| function gen_interrupt_func($f, $kind, $spec) { | ||
| out($f, "static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS) {\n"); | ||
| out($f,"\tSAVE_OPLINE();\n"); | ||
| out($f,"\tZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); | ||
| out($f, "}\n"); | ||
| } | ||
|
|
||
| function extra_spec_name($extra_spec) { | ||
| global $prefix; | ||
|
|
||
|
|
@@ -1808,6 +1815,7 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) | |
| case ZEND_VM_KIND_TAILCALL: | ||
| gen_null_handler($f, $kind); | ||
| gen_halt_handler($f, $kind); | ||
| gen_interrupt_func($f, $kind, $spec); | ||
| break; | ||
| case ZEND_VM_KIND_SWITCH: | ||
| out($f,"default: ZEND_NULL_LABEL:\n"); | ||
|
|
@@ -1845,7 +1853,7 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) | |
| out($f, "#pragma push_macro(\"ZEND_VM_INTERRUPT\")\n"); | ||
| out($f, "#undef ZEND_VM_INTERRUPT\n"); | ||
| out($f, "#define ZEND_VM_CONTINUE(handler) return opline\n"); | ||
| out($f, "#define ZEND_VM_INTERRUPT() return zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)\n"); | ||
| out($f, "# define ZEND_VM_INTERRUPT() SAVE_OPLINE(); return &call_interrupt_op;\n"); | ||
| out($f, $delayed_helpers); | ||
| out($f, "#pragma pop_macro(\"ZEND_VM_INTERRUPT\")\n"); | ||
| out($f, "#pragma pop_macro(\"ZEND_VM_CONTINUE\")\n"); | ||
|
|
@@ -1901,6 +1909,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) | |
| out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_HYBRID || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n\n"); | ||
| out($f,"static zend_vm_opcode_handler_func_t const * zend_opcode_handler_funcs;\n"); | ||
| out($f,"#endif\n"); | ||
| out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n\n"); | ||
| out($f,"static const zend_op call_interrupt_op;\n"); | ||
| out($f,"#endif\n\n"); | ||
| } | ||
| out($f,"#if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC\n"); | ||
| out($f,"static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op);\n"); | ||
|
|
@@ -2139,9 +2150,10 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) | |
| out($f," ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \\\n"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the alternative would be returning instead of tail-calling here. But that might be slower.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any thoughts @arnaud-lb ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this would work. Possibly this may result in worse branch prediction as we revert to the main loop's central dispatch point. Another alternative would be to make I've benchmarked the 3 approaches: Results: The Symfony results do not make sense to me, but these are stable. Approach 3 seems better overall, speed-wise, assuming that we don't find issues with it.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uh, that's a nice approach! Love it. I don't see any issues, that approach will never have &call_interrupt_op show up in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've pulled in your changes in the 3rd approach to let CI run while I review it in more dept (first glance looks good). |
||
| out($f," } while (0)\n"); | ||
| out($f,"# define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE()\n"); | ||
| out($f,"# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); | ||
| out($f,"# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); | ||
| out($f,"\n"); | ||
| out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS);\n"); | ||
| out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS);\n"); | ||
| out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_NULL_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); | ||
| out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_HALT_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); | ||
| out($f,"static zend_never_inline const zend_op *ZEND_OPCODE_HANDLER_CCONV zend_leave_helper_SPEC_TAILCALL(zend_execute_data *ex, const zend_op *opline);\n"); | ||
|
|
@@ -2152,6 +2164,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) | |
| out($f,"static const zend_op call_leave_op = {\n"); | ||
| out($f," .handler = zend_leave_helper_SPEC_TAILCALL,\n"); | ||
| out($f,"};\n"); | ||
| out($f,"static const zend_op call_interrupt_op = {\n"); | ||
| out($f," .handler = zend_interrupt_helper_SPEC_TAILCALL,\n"); | ||
| out($f,"};\n"); | ||
| out($f,"\n"); | ||
|
|
||
| gen_executor_code($f, $spec, ZEND_VM_KIND_TAILCALL, $m[1]); | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --TEST-- | ||
| Observer: VM interrupt during tailcall helper dispatch | ||
| --DESCRIPTION-- | ||
| This exercises a VM interrupt raised while an opcode handler dispatches to an | ||
| extra-argument helper. On the tailcall VM, the helper may return an opline | ||
| tagged with ZEND_VM_ENTER_BIT; treating that tagged value as a zend_op * before | ||
| tailcalling the next handler can crash. | ||
| --EXTENSIONS-- | ||
| zend_test | ||
| --INI-- | ||
| opcache.jit=0 | ||
| zend_test.observer.set_vm_interrupt_on_begin=1 | ||
| --FILE-- | ||
| <?php | ||
| function trigger(VmInterruptComparable $left, VmInterruptComparable $right): object | ||
| { | ||
| if ($left < $right) { | ||
| return new Exception(); | ||
| } | ||
| return new stdClass(); | ||
| } | ||
|
|
||
| echo get_class(trigger(new VmInterruptComparable(2), new VmInterruptComparable(1))), "\n"; | ||
| ?> | ||
| --EXPECT-- | ||
| stdClass |
Uh oh!
There was an error while loading. Please reload this page.
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.
This seems equivalent to
return zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU). The advantage of this form (return zend_interrupt_TAILCALL(...)) is that it reduces code size slightly compared to an inline SAVE_OPLINE() + return.