mirror of
https://github.com/php/php-src.git
synced 2025-08-16 14:08:47 +02:00
![]() This changes the signature of opcode handlers in the CALL VM so that the opline is passed directly via arguments. This reduces the number of memory operations on EX(opline), and makes the CALL VM considerably faster. Additionally, this unifies the CALL and HYBRID VMs a bit, as EX(opline) is now handled in the same way in both VMs. This is a part of GH-17849. Currently we have two VMs: * HYBRID: Used when compiling with GCC. execute_data and opline are global register variables * CALL: Used when compiling with something else. execute_data is passed as opcode handler arg, but opline is passed via execute_data->opline (EX(opline)). The Call VM looks like this: while (1) { ret = execute_data->opline->handler(execute_data); if (UNEXPECTED(ret != 0)) { if (ret > 0) { // returned by ZEND_VM_ENTER() / ZEND_VM_LEAVE() execute_data = EG(current_execute_data); } else { // returned by ZEND_VM_RETURN() return; } } } // example op handler int ZEND_INIT_FCALL_SPEC_CONST_HANDLER(zend_execute_data *execute_data) { // load opline const zend_op *opline = execute_data->opline; // instruction execution // dispatch // ZEND_VM_NEXT_OPCODE(): execute_data->opline++; return 0; // ZEND_VM_CONTINUE() } Opcode handlers return a positive value to signal that the loop must load a new execute_data from EG(current_execute_data), typically when entering or leaving a function. Here I make the following changes: * Pass opline as opcode handler argument * Return next opline from opcode handlers * ZEND_VM_ENTER / ZEND_VM_LEAVE return opline|(1<<0) to signal that execute_data must be reloaded from EG(current_execute_data) This gives us: while (1) { opline = opline->handler(execute_data, opline); if (UNEXPECTED((uintptr_t) opline & ZEND_VM_ENTER_BIT) { opline = opline & ~ZEND_VM_ENTER_BIT; if (opline != 0) { // ZEND_VM_ENTER() / ZEND_VM_LEAVE() execute_data = EG(current_execute_data); } else { // ZEND_VM_RETURN() return; } } } // example op handler const zend_op * ZEND_INIT_FCALL_SPEC_CONST_HANDLER(zend_execute_data *execute_data, const zend_op *opline) { // opline already loaded // instruction execution // dispatch // ZEND_VM_NEXT_OPCODE(): return ++opline; } bench.php is 23% faster on Linux / x86_64, 18% faster on MacOS / M1. Symfony Demo is 2.8% faster. When using the HYBRID VM, JIT'ed code stores execute_data/opline in two fixed callee-saved registers and rarely touches EX(opline), just like the VM. Since the registers are callee-saved, the JIT'ed code doesn't have to save them before calling other functions, and can assume they always contain execute_data/opline. The code also avoids saving/restoring them in prologue/epilogue, as execute_ex takes care of that (JIT'ed code is called exclusively from there). The CALL VM can now use a fixed register for execute_data/opline as well, but we can't rely on execute_ex to save the registers for us as it may use these registers itself. So we have to save/restore the two registers in JIT'ed code prologue/epilogue. Closes GH-17952 |
||
---|---|---|
.. | ||
bug73615 | ||
gh12962 | ||
basic_run.phpt | ||
breakpoints_001.phpt | ||
breakpoints_002.phpt | ||
breakpoints_003.phpt | ||
breakpoints_004.phpt | ||
breakpoints_005.phpt | ||
breakpoints_006.phpt | ||
breakpoints_007.phpt | ||
breakpoints_008.phpt | ||
breakpoints_009.phpt | ||
bug73615.phpt | ||
bug73704.phpt | ||
bug73794.phpt | ||
bug73927.phpt | ||
bug76801.phpt | ||
bug78297.phpt | ||
bug81135.phpt | ||
clean_001.phpt | ||
clear_001.phpt | ||
delimiter.phpt | ||
empty.inc | ||
exceptions_001.phpt | ||
exceptions_002.phpt | ||
exceptions_003.phpt | ||
finish_leave_001.phpt | ||
generator_run.phpt | ||
gh10715.phpt | ||
gh12675.phpt | ||
gh12962.phpt | ||
gh13681.phpt | ||
gh13827.phpt | ||
gh13931.phpt | ||
gh14553.phpt | ||
gh15208.phpt | ||
gh15210_001.phpt | ||
gh15210_002.phpt | ||
gh15268.phpt | ||
gh15901.phpt | ||
gh16174.phpt | ||
gh16181.phpt | ||
include.inc | ||
include_once_001.phpt | ||
include_once_002.phpt | ||
info_001.phpt | ||
info_002.phpt | ||
match_breakpoints_001.phpt | ||
match_breakpoints_002.phpt | ||
match_breakpoints_003.phpt | ||
match_breakpoints_004.phpt | ||
next_001.phpt | ||
normal_exit.phpt | ||
phpdbg_break_next.phpt | ||
phpdbg_get_executable_stream_wrapper.inc | ||
phpdbg_get_executable_stream_wrapper.phpt | ||
phpdbg_oplog_001.phpt | ||
phpdbg_oplog_002.phpt | ||
print_001.phpt | ||
print_002.phpt | ||
register_function.phpt | ||
register_function_leak.phpt | ||
run_001.phpt | ||
run_002.phpt | ||
set_exception_handler.phpt | ||
stdin_001.phpt | ||
stepping_001.phpt | ||
watch_001.phpt | ||
watch_002.phpt | ||
watch_003.phpt | ||
watch_004.phpt | ||
watch_005.phpt | ||
watch_006.phpt | ||
watch_007.phpt |