1
0
mirror of https://github.com/php/php-src.git synced 2026-04-29 11:13:36 +02:00
Commit Graph

2397 Commits

Author SHA1 Message Date
Arnaud Le Blanc 73b98a3858 TAILCALL VM
Introduce the TAILCALL VM, a more efficient variant of the CALL VM:

 * Each opcode handler tailcalls the next opcode handler directly instead of
   returning to the interpreter loop. This eliminates call and interpreter loop
   overhead.
 * Opcode handlers use the preserve_none calling convention to eliminate
   register saving overhead.
 * preserve_none uses non-volatile registers for its first arguments, so
   execute_data and opline are usually kept in these registers and no code is
   required to forward them to the next handlers.

Generated machine code is similar to a direct-threaded VM with register pinning,
like the HYBRID VM.

JIT+TAILCALL VM also benefits from this compared to JIT+CALL VM:

 * JIT uses the registers of the execute_data and opline args as fixed regs,
   eliminating the need to move them in prologue.
 * Traces exit by tailcalling the next handler. No code is needed to forward
   execute_data and opline.
 * No register saving/restoring in epilogue/prologue.

The TAILCALL VM is used when the HYBRID VM is not supported, and the compiler
supports the musttail and preserve_none attributes: The HYBRID VM is used when
compiling with GCC, the TAILCALL VM when compiling with Clang>=19 on x86_64 or
aarch64, and the CALL VM otherwise.

This makes binaries built with Clang>=19 as fast as binaries built with GCC.
Before, these were considerably slower (by 2.8% to 44% depending on benchmark,
and by 5% to 77% before 76d7c616bb).

Closes GH-17849
Closes GH-18720
2025-08-22 18:05:52 +02:00
Arnaud Le Blanc 504a633780 Merge branch 'PHP-8.4'
* PHP-8.4:
  Fit JIT variable not stored before YIELD
2025-08-19 15:54:46 +02:00
Arnaud Le Blanc bc05bfe7c5 Fit JIT variable not stored before YIELD
JIT doesn't recognize that variables may be used after returning from a
trace due to YIELD, so some effects may never be stored to memory.

YIELD ops terminate trace recordings with ZEND_JIT_TRACE_STOP_RETURN, and are
handled mostly like RETURN. Here I change zend_jit_trace_execute() so that
YIELD terminates recordings with ZEND_JIT_TRACE_STOP_INTERPRETER instead,
to ensure that we recognize that variables may be used after returning from
the trace due to YIELD.

Fixes GH-19493
Closes GH-19515
2025-08-19 15:49:29 +02:00
Dmitry Stogov 290c9aef56 Eliminate useless spill stores (#19467) 2025-08-13 13:46:14 +03:00
Dmitry Stogov 07a9c25c71 Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix Nightly workflow Symfony assertion (ir_ra.c:326: ir_fix_live_range: Assertion `ival && p->start == old_start' failed) (#19458)
2025-08-12 11:59:26 +03:00
Dmitry Stogov 47f9f3a3f6 Fix Nightly workflow Symfony assertion (ir_ra.c:326: ir_fix_live_range: Assertion `ival && p->start == old_start' failed) (#19458) 2025-08-12 11:59:08 +03:00
Arnaud Le Blanc 3ddbad9589 Allocate a fast thread-safe-resource id for opcache
Closes GH-19347
2025-08-06 18:02:43 +02:00
Dmitry Stogov ef98a6e723 Merge branch 'PHP-8.4'
* PHP-8.4:
  Update IR
2025-08-04 17:26:46 +03:00
Dmitry Stogov ac1cd9c26e Update IR
IR commit: 6e2aea0ebfef2c741ebec30c57aa492df0d4e319
2025-08-04 17:26:24 +03:00
Arnaud Le Blanc 9aa8e8825f Remove zend_jit_vm_kind (#19299)
JIT used to not have a compile-time dependency on VM kind, such that a single
build of opcache could work with different VM kinds at runtime. This has been broken
over time and would be difficult to restore. Additionally, as opcache is now
built-in, this would not be useful anymore.

Remove the zend_jit_vm_kind variable.
2025-08-04 15:52:09 +02:00
Niels Dossche 15990de89e Refactor op array loops in JIT (#19335)
Reuse the helper zend_foreach_op_array() that we move to the
zend_optimizer.h header to be usable in opcache.
Note that applying this to other op_array loops is not easy because they either:
- start from EG(persistent_classes_count)
- or only apply to classes
2025-07-31 22:10:06 +02:00
Niels Dossche 0591defd6f Merge branch 'PHP-8.4'
* PHP-8.4:
  Remove dynamic defs from property hooks
  Add missing hooks JIT restart code
2025-07-31 20:22:20 +02:00
Niels Dossche 9ce51dad8b Add missing hooks JIT restart code
Closes GH-19207.
2025-07-31 20:21:40 +02:00
Niels Dossche 16b2fc41a3 Merge branch 'PHP-8.4'
* PHP-8.4:
  Reset global pointers to prevent use-after-free
2025-07-30 09:23:38 +02:00
Niels Dossche 6fda0a5617 Merge branch 'PHP-8.3' into PHP-8.4
* PHP-8.3:
  Reset global pointers to prevent use-after-free
2025-07-30 09:23:33 +02:00
Niels Dossche be9f1d3d56 Merge branch 'PHP-8.2' into PHP-8.3
* PHP-8.2:
  Reset global pointers to prevent use-after-free
2025-07-30 09:23:12 +02:00
Niels Dossche 7016ad558b Merge branch 'PHP-8.1' into PHP-8.2
* PHP-8.1:
  Reset global pointers to prevent use-after-free
2025-07-30 09:22:50 +02:00
Florian Engelhardt 3aaa8d3526 Reset global pointers to prevent use-after-free
Closes GH-19212.
2025-07-30 09:22:15 +02:00
Arnaud Le Blanc 7b4c14dc10 Make OPcache non-optional
This removes the --enable-opcache/--disable-opcache configure switch. OPcache
is now always builtin. The default value of opcache.enable and
opcache.enable_cli is unchanged.

RFC: https://wiki.php.net/rfc/make_opcache_required

Closes GH-18961.

Co-authored-by: Tim Düsterhus <tim@tideways-gmbh.com>
2025-07-27 09:40:22 +02:00
Arnaud Le Blanc 73b1ebfa20 Fix linker failure when building Opcache statically
We use linker relocations to fetch the TLS index and offset of _tsrm_ls_cache.
When building Opcache statically, linkers may attempt to optimize that into a
more efficient code sequence (relaxing from "General Dynamic" to "Local Exec"
model [1]). Unfortunately, linkers will fail, rather than ignore our
relocations, when they don't recognize the exact code sequence they are
expecting.

This results in errors as reported by GH-15074:

    TLS transition from R_X86_64_TLSGD to R_X86_64_GOTTPOFF against
    `_tsrm_ls_cache' at 0x12fc3 in section `.text' failed"

Here I take a different approach:

 * Emit the exact full code sequence expected by linkers
 * Extract the TLS index/offset by inspecting the linked ASM code, rather than
   executing it (execution would give us the thread-local address).
 * We detect when the code was relaxed, in which case we can extract the TCB
   offset instead.
 * This is done in a conservative way so that if the linker did something we
   didn't expect, we fallback to a safer (but slower) mechanism.

One additional benefit of that is we are now able to use the Local Exec model in
more cases, in JIT'ed code. This makes non-glibc builds faster in these cases.

Closes GH-18939.

Related RFC: https://wiki.php.net/rfc/make_opcache_required.

[1] https://www.akkadia.org/drepper/tls.pdf
2025-07-26 16:43:41 +02:00
Arnaud Le Blanc 7f7b3cdb90 Introduce zend_vm_opcode_handler_t / zend_vm_opcode_handler_func_t
This reduces the chances of confusion between opcode handlers used by the
VM, and opcode handler functions used for tracing or debugging. Depending
on the VM, zend_vm_opcode_handler_t may not be a function. For instance in
the HYBRID VM this is a label pointer.

Closes GH-19006
2025-07-26 13:20:59 +02:00
Dmitry Stogov 2beb44a80b Merge branch 'PHP-8.4'
* PHP-8.4:
  Revert "Update IR"
2025-07-14 14:28:55 +03:00
Dmitry Stogov 9abb0fb0c4 Revert "Update IR"
This reverts commit e8ae27bf8a.

Something wrong in irrducable loops habdling that causes ir_find_loop()
to stuck. See https://github.com/php/php-src/issues/19104
2025-07-14 14:27:05 +03:00
Dmitry Stogov dd69b65638 Merge branch 'PHP-8.4'
* PHP-8.4:
  Update IR
2025-07-07 14:03:36 +03:00
Dmitry Stogov e8ae27bf8a Update IR
IR commit: af6dc83bcd91c3123f40efcdcbeba8794b9b2abf
2025-07-07 14:03:11 +03:00
Niels Dossche 4a98b36416 Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-18898: SEGV zend_jit_op_array_hot with property hooks and preloading
2025-06-30 18:38:30 +02:00
Niels Dossche 53f2aa93ae Fix GH-18898: SEGV zend_jit_op_array_hot with property hooks and preloading
Property hooks were not handled for JIT+trait+preloading.
Split the existing functions that handle op arrays, and add iterations
for property hooks.

Closes GH-18923.
2025-06-30 18:38:11 +02:00
Niels Dossche 6eed02bacc Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-18899: JIT function crash when emitting undefined variable warning and opline is not set yet
2025-06-23 20:10:27 +02:00
Niels Dossche 56c4ddfaf6 Fix GH-18899: JIT function crash when emitting undefined variable warning and opline is not set yet
The crash happens because EX(opline) is attempted to be accessed but
it's not set yet.

Closes GH-18904.
2025-06-23 20:10:09 +02:00
Niels Dossche 9b7252b8bd Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-18639: Internal class aliases can break preloading + JIT
2025-06-23 20:01:49 +02:00
Niels Dossche ee2c0d7e7f Merge branch 'PHP-8.3' into PHP-8.4
* PHP-8.3:
  Fix GH-18639: Internal class aliases can break preloading + JIT
2025-06-23 20:01:40 +02:00
Niels Dossche 8e731ca622 Fix GH-18639: Internal class aliases can break preloading + JIT
ZEND_FUNC_INFO() can not be used on internal CE's. If preloading makes a
CE that's an alias of an internal class, the invalid access happens when
setting the FUNC_INFO.

While we could check the class type to be of user code, we can just skip
aliases altogether anyway which may be faster.

Closes GH-18915.
2025-06-23 20:01:15 +02:00
Niels Dossche 4c7220322b Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-18743: Incompatibility in Inline TLS Assembly on Alpine 3.22
2025-06-09 11:41:08 +02:00
Niels Dossche 7a0beb4867 Merge branch 'PHP-8.3' into PHP-8.4
* PHP-8.3:
  Fix GH-18743: Incompatibility in Inline TLS Assembly on Alpine 3.22
2025-06-09 11:41:02 +02:00
Niels Dossche b3c8afe272 Fix GH-18743: Incompatibility in Inline TLS Assembly on Alpine 3.22
GAS started checking the relocation for tlsgd: it must use the %rdi
register. However, the inline assembly now uses %rax instead.
Fix it by changing the "=a" output register to "=D".
Source: https://github.com/bminor/binutils-gdb/blob/ec181e1710e37007a8d95c284609bfaa5868d086/gas/config/tc-i386.c#L6793

gottpoff is unaffected.

Closes GH-18779.
2025-06-09 11:39:34 +02:00
Dmitry Stogov baeb981746 Merge branch 'PHP-8.4'
* PHP-8.4:
  Update IR
2025-06-02 09:24:10 +03:00
Dmitry Stogov 81593cfc6a Update IR
IR commit: e4343be0082897510c40a1b57baff427c6858878
2025-06-02 09:23:39 +03:00
Arnaud Le Blanc 16ca097ef2 Do not exit to VM when setting undefined prop in constructor
JIT'ed ASSIGN_OBJ expressions will exit to VM when the prop is undef. However,
in a constructor it's very likely the case. Therefore most traces with `new`
expressions will exit to VM.

Here I ensure that we don't need to exit to VM when it's likely that the
prop will be undef.

In the function JIT we compile a slow path to handle such properties,
but not in the tracing JIT, assumingly to reduce code size. Here I enable
compilation of the slow path in the tracing JIT when it's likely the prop
will be undef. Quite conveniently we already record the prop type during
tracing, so I use that to make the decision.

This results in a 1.20% wall time improvement on the symfony demo benchmark
with 20 warmup requests.

Closes GH-18576
2025-05-19 12:42:11 +02:00
Arnaud Le Blanc 1de16c7f15 Merge branch 'PHP-8.4'
* PHP-8.4:
  Snapshotted poly_func / poly_this may be spilled
2025-05-14 12:30:36 +02:00
Arnaud Le Blanc 18276a8b42 Snapshotted poly_func / poly_this may be spilled
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.

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

 - In zend_jit_snapshot_handler, keep track of the C stack offset
   of the spilled register, in a way similar to how stack variables.
 - 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.

Closes GH-18408
2025-05-14 12:27:57 +02:00
Ilija Tovilo 59056937bf Fix use-of-uninitialized-value with exception on deprecated const access
Closes GH-18478
2025-05-02 11:57:16 +02:00
Niels Dossche 39a56a1687 Fix opcode length of ZEND_DECLARE_ATTRIBUTED_CONST in JIT-IR component (#18457)
Introduced in 3f03f7ed.
2025-04-30 08:17:56 +02:00
DanielEScherzer 3f03f7ed3d [RFC] Add support for attributes on compile-time constants
https://wiki.php.net/rfc/attributes-on-constants
2025-04-29 11:53:09 -07:00
Arnaud Le Blanc 2fea4efa8f Fix merge error (#18453) 2025-04-29 17:06:59 +02:00
Arnaud Le Blanc 3737e0aa6f Merge branch 'PHP-8.4'
* PHP-8.4:
  JIT: Check exception on exit
2025-04-29 10:55:38 +02:00
Arnaud Le Blanc 978c01ce15 JIT: Check exception on exit
Add a new exit flag (ZEND_JIT_EXIT_CHECK_EXCEPTION) that enables exception
checking during exit/deoptimization.

We already checked for exceptions during exit/deoptimization, but only when
ZEND_JIT_EXIT_FREE_OP1 or ZEND_JIT_EXIT_FREE_OP2 were set (presumably to
handle exceptions thrown during dtor). The new flag makes it possible to request
it explicitly.

This also fixes two issues in zend_jit_trace_exit():

- By returning 1, we were telling the caller (zend_jit_trace_exit_stub()) to
  execute the original op handler of EG(current_execute_data)->opline, but in
  reality we want to execute EX(opline), which should be EG(exception_op).

- EX(opline) is set to the value of %r15 in zend_jit_trace_exit_stub() before
  calling zend_jit_trace_exit(), but this may be the address of a
  zend_execute_data when the register is being reused to cache EX(call).

Fixes GH-18262
Closes GH-18297
2025-04-29 10:55:31 +02:00
Arnaud Le Blanc 76d7c616bb Pass opline as argument to opcode handlers in CALL VM
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
2025-04-15 18:51:54 +02:00
Arnaud Le Blanc 49891d89fc Merge branch 'PHP-8.4'
* PHP-8.4:
  Save opline in zend_jit_hot_func()
2025-04-15 14:11:53 +02:00
Florian Engelhardt 061b46e09d Save opline in zend_jit_hot_func()
Closes GH-18289
2025-04-15 14:11:32 +02:00
Niels Dossche 4a12a9f3e9 Fix GH-18294: assertion failure zend_jit_ir.c
The JIT helper `zend_jit_assign_op_to_typed_ref` expects a `zval*` as an
argument, so we have to store to the stack if OP1_DATA(=op3) is in a
register.

Closes GH-18299.
2025-04-11 17:54:19 +02:00