Skip to content
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

Pass opline as argument to opcode handlers in CALL VM #17952

Draft
wants to merge 34 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
04bff56
Pass opline via op handler args
arnaud-lb Feb 25, 2025
1f12382
Move extra vm helper parameters after standard ones
arnaud-lb Feb 25, 2025
6ebf91b
Improve reproducibility
arnaud-lb Aug 26, 2024
a773fbe
Update JIT for new op handler signature (wip)
arnaud-lb Feb 25, 2025
5a63362
Return -1 to signal execute_data reloading
arnaud-lb Feb 27, 2025
919e1a0
Revert "Return -1 to signal execute_data reloading"
arnaud-lb Feb 27, 2025
dc1a571
Use indempotent operation to signal execute_data reloading
arnaud-lb Feb 27, 2025
46de8b2
Save registers in generated code
arnaud-lb Feb 27, 2025
841f0ea
Force reload execute_data when returning from exception
arnaud-lb Feb 28, 2025
cb83679
Cleanup
arnaud-lb Feb 28, 2025
f382249
ZEND_VM_ENTER_BIT
arnaud-lb Feb 28, 2025
fad6772
GCC build fix
arnaud-lb Feb 28, 2025
5ae328d
Generated file
arnaud-lb Feb 28, 2025
44f0a90
Build fix
arnaud-lb Feb 28, 2025
0da3e5f
Define CHAR_BITS
arnaud-lb Feb 28, 2025
a5b9f66
Support platforms in which user space addresses may have the higher b…
arnaud-lb Mar 13, 2025
e00982e
Update zend_vm_call_opcode_handler()
arnaud-lb Mar 13, 2025
42f60ba
Fix zend_runtime_jit(): Return the original opline
arnaud-lb Mar 13, 2025
64f27cc
run-tests.php: Save stdin section to a file
arnaud-lb Mar 13, 2025
91284db
Rename ZEND_OPCODE_HANDLER_ARGS_DC / ZEND_OPCODE_HANDLER_ARGS_PASSTHR…
arnaud-lb Mar 14, 2025
93317c0
Fix build
arnaud-lb Mar 14, 2025
f4f9ec5
Fix windows build
arnaud-lb Mar 14, 2025
54cd812
Fix after rebase
arnaud-lb Mar 14, 2025
ad3c920
Fix zend_jit_leave_func
arnaud-lb Mar 14, 2025
f013aa1
Remove zend_jit_set_ip_ex
arnaud-lb Mar 17, 2025
d50c5d1
Re-add/update comments
arnaud-lb Mar 17, 2025
8637220
Update zend_jit_trace_handler
arnaud-lb Mar 17, 2025
ced459a
Update zend_jit_trace_execute
arnaud-lb Mar 17, 2025
23a1cb4
Fix Zend/tests/bug70689.phpt
arnaud-lb Mar 17, 2025
d31339f
Fix x32
arnaud-lb Mar 17, 2025
0bf80cf
Optimize OR(X, PWR2) => BTS to avoid loading imm64 value in register
arnaud-lb Mar 25, 2025
5d3af10
Allow to disable ZEND_HIGH_HALF_KERNEL
arnaud-lb Mar 25, 2025
833511c
Remove jit_LOAD_IP()
arnaud-lb Apr 1, 2025
b807d6f
Update zend_jit_copy_extra_args_helper_ex
arnaud-lb Apr 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Zend/zend_portability.h
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@

#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>

#ifdef HAVE_DLFCN_H
# include <dlfcn.h>
@@ -864,4 +865,21 @@ static zend_always_inline uint64_t ZEND_BYTES_SWAP64(uint64_t u)
# define ZEND_OPCACHE_SHM_REATTACHMENT 1
#endif

#ifndef CHAR_BITS
# define CHAR_BITS 8
#endif

#ifndef UINTPTR_WIDTH
# define UINTPTR_WIDTH (CHAR_BITS * sizeof(uintptr_t))
#endif

#if ((defined(__linux__) && defined(__x86_64__)) \
|| (defined(ZEND_WIN32) && defined(_M_AMD64))) \
&& !defined(NO_ZEND_HIGH_HALF_KERNEL)
/* The kernel reserves the higher part of the address space for itself.
* Therefore, we can assume that the higher bit of user space addresses is
* never set. */
# define ZEND_HIGH_HALF_KERNEL
Copy link
Member

Choose a reason for hiding this comment

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

So I didn't like this higher half stuff because it adds more complexity, but I'm glad to see it doesn't make an improvement so this can be simplified

#endif

#endif /* ZEND_PORTABILITY_H */
3 changes: 1 addition & 2 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
@@ -5602,11 +5602,10 @@ ZEND_VM_HOT_HANDLER(199, ZEND_CHECK_UNDEF_ARGS, UNUSED, UNUSED)

ZEND_VM_COLD_HELPER(zend_missing_arg_helper, ANY, ANY)
{
#ifdef ZEND_VM_IP_GLOBAL_REG
USE_OPLINE

SAVE_OPLINE();
#endif

Comment on lines -5605 to +5608
Copy link
Member

Choose a reason for hiding this comment

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

This removes EX(opline) = opline when ZEND_VM_IP_GLOBAL_REG is defined.
This may cause output of incorrect line number in error message.
May be I miss something?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will check

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's correct.

This causes opline to be saved to EX(opline) in the CALL VM (and in the HYBRID VM, as before). Saving used to be unnecessary as EX(opline) was always up to date in the CALL VM. I believe that the #ifdef is redundant as SAVE_OPLINE() was a no-op on the CALL VM.

zend_missing_arg_error(execute_data);
HANDLE_EXCEPTION();
}
388 changes: 211 additions & 177 deletions Zend/zend_vm_execute.h

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Zend/zend_vm_execute.skl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include "Zend/zend_vm_opcodes.h"

{%DEFINES%}

#if (ZEND_VM_KIND != ZEND_VM_KIND_CALL) && (ZEND_GCC_VERSION >= 4000) && !defined(__clang__)
108 changes: 71 additions & 37 deletions Zend/zend_vm_gen.php

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Zend/zend_vm_opcodes.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions ext/opcache/jit/ir/ir_x86.dasc
Original file line number Diff line number Diff line change
@@ -1064,6 +1064,7 @@ const char *ir_reg_name(int8_t reg, ir_type type)
_(SSE_CEIL) \
_(SSE_TRUNC) \
_(SSE_NEARBYINT) \
_(OR_PWR2) \
Copy link
Member Author

@arnaud-lb arnaud-lb Mar 27, 2025

Choose a reason for hiding this comment

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

Changes in ext/opcache/jit/ir will be submitted as a proper PR on IR repository.

Here I add an optimization so that

ir_OR(x, y)

is compiled to a single bts instruction when y is a large power of two. This avoids copying y to a register first (because or doesn't accept an imm64 op2), saving an instruction and reducing code size.

So this optimizes this:

movabsq $9223372036854775808, %rcx
orq %rcx, %rax

Into this:

btsq $63, %rax

GCC does this optimization: https://godbolt.org/z/ovEWGPr3r (but not Clang).

Copy link
Member

Choose a reason for hiding this comment

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

Looks fine, but please name the rule BTS. Even better implement the similar optimization for BTR and use the common rule( e.g. BIT_OP) . I would glad to accept this for IR independently.

Copy link
Member Author

Choose a reason for hiding this comment

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

I pushed a PR here: dstogov/ir#111


#define IR_RULE_ENUM(name) IR_ ## name,

@@ -1395,6 +1396,7 @@ op2_const:
case IR_DIV_PWR2:
case IR_OP_INT:
case IR_OP_FP:
case IR_OR_PWR2:
flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG;
break;
case IR_MOD_PWR2:
@@ -2262,6 +2264,9 @@ binop_fp:
// return IR_COPY_INT;
} else if (op2_insn->val.i64 == -1) {
// -1
} else if (IR_IS_POWER_OF_TWO(op2_insn->val.u64) && op2_insn->val.u64 > (1ULL<<30)) {
/* OR(X, PWR2) => BTS */
return IR_OR_PWR2;
}
}
goto binop_int;
@@ -4280,6 +4285,26 @@ static void ir_emit_mul_div_mod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn)
uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64);

| ASM_REG_IMM_OP shr, type, def_reg, shift
} else if (insn->op == IR_OR) {
uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64);

/* bts doesn't support r/m8 first operand */
switch (ir_type_size[type]) {
default:
IR_ASSERT(0);
case 1:
case 2:
| bts Rw(def_reg), (shift & 0xffff)
break;
case 4:
| bts Rd(def_reg), shift
break;
|.if X64
|| case 8:
| bts Rq(def_reg), shift
|| break;
|.endif
}
} else {
IR_ASSERT(insn->op == IR_MOD);
uint64_t mask = ctx->ir_base[insn->op2].val.u64 - 1;
@@ -10608,6 +10633,7 @@ void *ir_emit_code(ir_ctx *ctx, size_t *size_ptr)
case IR_MUL_PWR2:
case IR_DIV_PWR2:
case IR_MOD_PWR2:
case IR_OR_PWR2:
ir_emit_mul_div_mod_pwr2(ctx, i, insn);
break;
case IR_SDIV_PWR2:
25 changes: 18 additions & 7 deletions ext/opcache/jit/zend_jit.c
Original file line number Diff line number Diff line change
@@ -98,7 +98,7 @@ static const void *zend_jit_func_trace_counter_handler = NULL;
static const void *zend_jit_ret_trace_counter_handler = NULL;
static const void *zend_jit_loop_trace_counter_handler = NULL;

static int ZEND_FASTCALL zend_runtime_jit(void);
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_runtime_jit(ZEND_OPCODE_HANDLER_ARGS);

static int zend_jit_trace_op_len(const zend_op *opline);
static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline);
@@ -2871,7 +2871,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
if (GCC_GLOBAL_REGS) {
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
} else {
ir_RETURN(ir_CONST_I32(1)); /* ZEND_VM_ENTER */
zend_jit_vm_enter(jit, jit_IP(jit));
}
ir_IF_TRUE(if_hook_enter);
}
@@ -3074,11 +3074,18 @@ static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, cons
}

/* Run-time JIT handler */
static int ZEND_FASTCALL zend_runtime_jit(void)
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_runtime_jit(ZEND_OPCODE_HANDLER_ARGS)
Copy link
Member

Choose a reason for hiding this comment

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

Ops. It looks like the previous prototype was wrong.

{
zend_execute_data *execute_data = EG(current_execute_data);
#if GCC_GLOBAL_REGS
zend_execute_data *execute_data;
zend_op *opline;
#else
const zend_op *orig_opline = opline;
#endif

execute_data = EG(current_execute_data);
zend_op_array *op_array = &EX(func)->op_array;
zend_op *opline = op_array->opcodes;
opline = op_array->opcodes;
zend_jit_op_array_extension *jit_extension;
bool do_bailout = 0;

@@ -3097,7 +3104,7 @@ static int ZEND_FASTCALL zend_runtime_jit(void)
}
}
jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array);
opline->handler = jit_extension->orig_handler;
((zend_op*)opline)->handler = jit_extension->orig_handler;

/* perform real JIT for this function */
zend_real_jit_func(op_array, NULL, NULL, ZEND_JIT_ON_FIRST_EXEC);
@@ -3116,7 +3123,11 @@ static int ZEND_FASTCALL zend_runtime_jit(void)
}

/* JIT-ed code is going to be called by VM */
return 0;
#if GCC_GLOBAL_REGS
return; // ZEND_VM_CONTINUE
#else
return orig_opline; // ZEND_VM_CONTINUE
#endif
}

void zend_jit_check_funcs(HashTable *function_table, bool is_method) {
45 changes: 30 additions & 15 deletions ext/opcache/jit/zend_jit_internal.h
Original file line number Diff line number Diff line change
@@ -21,6 +21,12 @@
#ifndef ZEND_JIT_INTERNAL_H
#define ZEND_JIT_INTERNAL_H

#include "Zend/zend_types.h"
#include "Zend/zend_compile.h"
#include "Zend/zend_constants.h"
#include "Zend/Optimizer/zend_func_info.h"
#include "Zend/Optimizer/zend_call_graph.h"

/* Address Encoding */
typedef uintptr_t zend_jit_addr;

@@ -183,17 +189,19 @@ extern const zend_op *zend_jit_halt_op;
# define ZEND_OPCODE_HANDLER_RET void
# define ZEND_OPCODE_HANDLER_ARGS EXECUTE_DATA_D
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU
# define ZEND_OPCODE_HANDLER_ARGS_DC
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC
# define ZEND_OPCODE_HANDLER_ARGS_EX
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_EX
# define ZEND_OPCODE_RETURN() return
# define ZEND_OPCODE_TAIL_CALL(handler) do { \
handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); \
return; \
} while(0)
# define ZEND_OPCODE_TAIL_CALL_EX(handler, arg) do { \
handler(arg ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC); \
handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_EX arg); \
return; \
} while(0)
# define ZEND_VM_ENTER_BIT 0
# define ZEND_VM_RETURN_VAL 0
#else
# define EXECUTE_DATA_D zend_execute_data* execute_data
# define EXECUTE_DATA_C execute_data
@@ -203,35 +211,42 @@ extern const zend_op *zend_jit_halt_op;
# define OPLINE_C opline
# define OPLINE_DC , OPLINE_D
# define OPLINE_CC , OPLINE_C
# define ZEND_OPCODE_HANDLER_RET int
# define ZEND_OPCODE_HANDLER_ARGS EXECUTE_DATA_D
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU EXECUTE_DATA_C
# define ZEND_OPCODE_HANDLER_ARGS_DC EXECUTE_DATA_DC
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC EXECUTE_DATA_CC
# define ZEND_OPCODE_RETURN() return 0
# define ZEND_OPCODE_HANDLER_RET const zend_op *
# define ZEND_OPCODE_HANDLER_ARGS EXECUTE_DATA_D OPLINE_DC
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU EXECUTE_DATA_C OPLINE_CC
# define ZEND_OPCODE_HANDLER_ARGS_EX EXECUTE_DATA_D OPLINE_DC,
# define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_EX EXECUTE_DATA_C OPLINE_CC,
# define ZEND_OPCODE_RETURN() return opline
# define ZEND_OPCODE_TAIL_CALL(handler) do { \
return handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); \
} while(0)
# define ZEND_OPCODE_TAIL_CALL_EX(handler, arg) do { \
return handler(arg ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC); \
return handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_EX arg); \
} while(0)
# ifdef ZEND_HIGH_HALF_KERNEL
# define ZEND_VM_ENTER_BIT (1ULL<<(UINTPTR_WIDTH-1))
# define ZEND_VM_RETURN_VAL 0
# else
# define ZEND_VM_ENTER_BIT 1ULL
# define ZEND_VM_RETURN_VAL ZEND_VM_ENTER_BIT
# endif
Comment on lines +226 to +232
Copy link
Member

Choose a reason for hiding this comment

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

Why do we redifine ZEND_VM_ENTER_BIT here?

Copy link
Member Author

Choose a reason for hiding this comment

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

zend_jit_internal.h has its own definition of some VM macros.

Here I define ZEND_VM_ENTER_BIT twice because of #17952 (comment). The non-ZEND_HIGH_HALF_KERNEL case is faster and more portable, so I'm going to keep only #define ZEND_VM_ENTER_BIT 1ULL.

Copy link
Member

Choose a reason for hiding this comment

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

It's also possible to "teach" IR testing the high bit checking the sign (I can do this).

Copy link
Member Author

Choose a reason for hiding this comment

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

If you do it, I would be happy to check how it affects performances

#endif

/* VM handlers */
typedef ZEND_OPCODE_HANDLER_RET (ZEND_FASTCALL *zend_vm_opcode_handler_t)(ZEND_OPCODE_HANDLER_ARGS);

/* VM helpers */
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(uint32_t call_info EXECUTE_DATA_DC);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(uint32_t call_info EXECUTE_DATA_DC);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_func_helper(EXECUTE_DATA_D);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(ZEND_OPCODE_HANDLER_ARGS_EX uint32_t call_info);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(ZEND_OPCODE_HANDLER_ARGS_EX uint32_t call_info);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_func_helper(ZEND_OPCODE_HANDLER_ARGS);

ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_profile_helper(ZEND_OPCODE_HANDLER_ARGS);

ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_func_counter_helper(ZEND_OPCODE_HANDLER_ARGS);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_loop_counter_helper(ZEND_OPCODE_HANDLER_ARGS);

void ZEND_FASTCALL zend_jit_copy_extra_args_helper(EXECUTE_DATA_D);
void ZEND_FASTCALL zend_jit_copy_extra_args_helper_no_skip_recv(EXECUTE_DATA_D);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_copy_extra_args_helper(ZEND_OPCODE_HANDLER_ARGS);
ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_copy_extra_args_helper_no_skip_recv(ZEND_OPCODE_HANDLER_ARGS);
bool ZEND_FASTCALL zend_jit_deprecated_helper(OPLINE_D);
void ZEND_FASTCALL zend_jit_undefined_long_key(EXECUTE_DATA_D);
void ZEND_FASTCALL zend_jit_undefined_long_key_ex(zend_long key EXECUTE_DATA_DC);
Loading