Skip to content

Commit 6aaf935

Browse files
committed
RFC: PHP JIT/arm64 port
Currently PHP JIT only supports x86 and x86_64 CPUs on POSIX platforms and Windows.[1] With the prevalence of PHP language and the notable growth of ARM-based servers market, we believe JIT/arm64 would be in urgent need in the near future. As an initial effort to enable PHP JIT/arm64, we (ARM) have supported the basic functionality, and (partially) implemented the compilation for several opcodes. Currently a number of simple JIT test cases from PHP test framework can be passed on ARM-based machine. There are still a lot of missing parts, such as hot loops, class/object/array operations, exception handling, etc, and we will continue working on them. In this patch, we would like to share our work with you. Any feedback would be greatly appreciated, and please let we know if anyone wants to contribute to this port. Main updates: 1. JIT backend for AArch64 A new alternative, i.e. AArch64, was added while building PHP JIT. See the updates in the following files. Note that we adopt capstone[2] for disassembly on AArch64. build/Makefile.global ext/opcache/config.m4 ext/opcache/config.w32 ext/opcache/jit/Makefile.frag ext/opcache/jit/zend_jit.c ext/opcache/jit/zend_jit_vm_helpers.c ext/opcache/jit/zend_jit_disasm_arm64.c ext/opcache/jit/zend_jit_gdb.c ext/opcache/jit/zend_jit_perf_dump.c 2. DynASM library PHP JIT uses DynASM[3] (developed for LuaJIT project) to generate native code on the fly. We added two useful but missing features, global label reference and dynamic register names, into DynASM/arm64. See the updates in files: ext/opcache/jit/dynasm/dasm_arm64.h ext/opcache/jit/dynasm/dasm_arm64.lua Note that these two features are available on DynASM/x86. 3. compilation for opcodes on AArch64 Our main work falls in the following files. ext/opcache/jit/zend_jit_arm64.h ext/opcache/jit/zend_jit_arm64.dasc ext/opcache/jit/zend_jit_internal.h Zend/zend_vm_opcodes.h * AArch64 registers and calling conventions are defined. * Instruction cache must be flushed for the JIT-ed code on AArch64. See macro JIT_CACHE_FLUSH in file 'zend_jit_internal.h'. * We have (partially) implemented the compilation for several opcodes, mainly for the function-based JIT (with opcache.jit=1203). Currently, test cases involving internal function call (e.g. var_dump), additions with integers/floating-point numbers, integer overflows and simple exception, can be supported now. See our newly added test cases under directory 'ext/opcache/tests/jit/arm64/'. * Trace counter stubs are implemented for tracing JIT (with opcache.jit=1255). See zend_jit_hybrid_trace_counter_stub() and zend_jit_hybrid_hot_trace_stub() in file 'zend_jit_arm64.dasc'. Hot functions can be recognized and compiled successfully. See the test case 'hot_func_002.phpt'. How to build and test: Our local test environment is an ARM-based server with Ubuntu 20.04 and GCC-10. We follow the building commands as shown in the readme file [4]. Note that library capstone should be installed in advance. We suggest running the JIT test cases using the following command. In our local test, 59 out of all 128 cases can be passed currently. $ make test TESTS='-d opcache.jit=1203 ext/opcache/tests/jit/' [1] https://wiki.php.net/rfc/jit [2] https://www.capstone-engine.org/ [3] https://luajit.org/dynasm.html [4] https://github.com/php/php-src
1 parent d021978 commit 6aaf935

32 files changed

+7083
-22
lines changed

Zend/zend_vm_opcodes.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
#endif
3636

3737
#if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) && !defined(__SANITIZE_ADDRESS__)
38-
# if ((defined(i386) && !defined(__PIC__)) || defined(__x86_64__) || defined(_M_X64))
38+
# if ((defined(i386) && !defined(__PIC__)) || defined(__x86_64__) || \
39+
defined(_M_X64) || defined(__aarch64__))
3940
# define ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE 16
4041
# endif
4142
#endif

build/Makefile.global

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ distclean: clean
125125
rm -f scripts/man1/phpize.1 scripts/php-config scripts/man1/php-config.1 sapi/cli/php.1 sapi/cgi/php-cgi.1 sapi/phpdbg/phpdbg.1 ext/phar/phar.1 ext/phar/phar.phar.1
126126
rm -f sapi/fpm/php-fpm.conf sapi/fpm/init.d.php-fpm sapi/fpm/php-fpm.service sapi/fpm/php-fpm.8 sapi/fpm/status.html
127127
rm -f ext/phar/phar.phar ext/phar/phar.php
128+
rm -f ext/opcache/jit/zend_jit_x86.c
129+
rm -f ext/opcache/jit/zend_jit_arm64.c
128130
if test "$(srcdir)" != "$(builddir)"; then \
129131
rm -f ext/phar/phar/phar.inc; \
130132
fi

ext/opcache/config.m4

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ if test "$PHP_OPCACHE" != "no"; then
2929

3030
if test "$PHP_OPCACHE_JIT" = "yes"; then
3131
case $host_cpu in
32-
x86*)
32+
x86*|aarch64)
3333
;;
3434
*)
3535
AC_MSG_WARN([JIT not supported by host architecture])
@@ -59,18 +59,37 @@ if test "$PHP_OPCACHE" != "no"; then
5959
case $host_alias in
6060
*x86_64-*-darwin*)
6161
DASM_FLAGS="-D X64APPLE=1 -D X64=1"
62+
DASM_ARCH="x86"
6263
;;
6364
*x86_64*)
6465
DASM_FLAGS="-D X64=1"
66+
DASM_ARCH="x86"
67+
;;
68+
*aarch64*)
69+
DASM_FLAGS="-D ARM64=1"
70+
DASM_ARCH="arm64"
6571
;;
6672
esac
73+
else
74+
DASM_ARCH="x86"
6775
fi
6876

6977
if test "$PHP_THREAD_SAFETY" = "yes"; then
7078
DASM_FLAGS="$DASM_FLAGS -D ZTS=1"
7179
fi
7280

81+
if test "$DASM_ARCH" = "arm64"; then
82+
PKG_CHECK_MODULES([CAPSTONE], [capstone >= 3.0.0],
83+
[have_capstone="yes"], [have_capstone="no"])
84+
if test "$have_capstone" = "yes"; then
85+
AC_DEFINE(HAVE_CAPSTONE, 1, [ ])
86+
PHP_EVAL_LIBLINE($CAPSTONE_LIBS, OPCACHE_SHARED_LIBADD)
87+
PHP_EVAL_INCLINE($CAPSTONE_CFLAGS)
88+
fi
89+
fi
90+
7391
PHP_SUBST(DASM_FLAGS)
92+
PHP_SUBST(DASM_ARCH)
7493

7594
AC_MSG_CHECKING(for opagent in default path)
7695
for i in /usr/local /usr; do

ext/opcache/config.w32

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ if (PHP_OPCACHE != "no") {
2525
dasm_flags += " -D ZTS=1";
2626
}
2727
DEFINE("DASM_FLAGS", dasm_flags);
28+
DEFINE("DASM_ARCH", "x86");
2829

2930
AC_DEFINE('HAVE_JIT', 1, 'Define to enable JIT');
3031
/* XXX read this dynamically */

ext/opcache/jit/Makefile.frag

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
$(builddir)/minilua: $(srcdir)/jit/dynasm/minilua.c
33
$(CC) $(srcdir)/jit/dynasm/minilua.c -lm -o $@
44

5-
$(builddir)/jit/zend_jit_x86.c: $(srcdir)/jit/zend_jit_x86.dasc $(srcdir)/jit/dynasm/*.lua $(builddir)/minilua
6-
$(builddir)/minilua $(srcdir)/jit/dynasm/dynasm.lua $(DASM_FLAGS) -o $@ $(srcdir)/jit/zend_jit_x86.dasc
5+
$(builddir)/jit/zend_jit_$(DASM_ARCH).c: $(srcdir)/jit/zend_jit_$(DASM_ARCH).dasc $(srcdir)/jit/dynasm/*.lua $(builddir)/minilua
6+
$(builddir)/minilua $(srcdir)/jit/dynasm/dynasm.lua $(DASM_FLAGS) -o $@ $(srcdir)/jit/zend_jit_$(DASM_ARCH).dasc
77

88
$(builddir)/jit/zend_jit.lo: \
9-
$(builddir)/jit/zend_jit_x86.c \
9+
$(builddir)/jit/zend_jit_$(DASM_ARCH).c \
1010
$(srcdir)/jit/zend_jit_helpers.c \
11-
$(srcdir)/jit/zend_jit_disasm_x86.c \
11+
$(srcdir)/jit/zend_jit_disasm_$(DASM_ARCH).c \
1212
$(srcdir)/jit/zend_jit_gdb.c \
1313
$(srcdir)/jit/zend_jit_perf_dump.c \
1414
$(srcdir)/jit/zend_jit_oprofile.c \

ext/opcache/jit/dynasm/dasm_arm64.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ enum {
2323
/* The following actions also have an argument. */
2424
DASM_REL_PC, DASM_LABEL_PC,
2525
DASM_IMM, DASM_IMM6, DASM_IMM12, DASM_IMM13W, DASM_IMM13X, DASM_IMML,
26+
DASM_VREG,
2627
DASM__MAX
2728
};
2829

@@ -39,6 +40,7 @@ enum {
3940
#define DASM_S_RANGE_LG 0x13000000
4041
#define DASM_S_RANGE_PC 0x14000000
4142
#define DASM_S_RANGE_REL 0x15000000
43+
#define DASM_S_RANGE_VREG 0x16000000
4244
#define DASM_S_UNDEF_LG 0x21000000
4345
#define DASM_S_UNDEF_PC 0x22000000
4446

@@ -312,13 +314,17 @@ void dasm_put(Dst_DECL, int start, ...)
312314
}
313315
case DASM_IMML: {
314316
#ifdef DASM_CHECKS
315-
int scale = (p[-2] >> 30);
317+
int scale = (ins & 0x3);
316318
CK((!(n & ((1<<scale)-1)) && (unsigned int)(n>>scale) < 4096) ||
317319
(unsigned int)(n+256) < 512, RANGE_I);
318320
#endif
319321
b[pos++] = n;
320322
break;
321323
}
324+
case DASM_VREG:
325+
CK(n < 32, RANGE_VREG);
326+
b[pos++] = n;
327+
break;
322328
}
323329
}
324330
}
@@ -348,7 +354,7 @@ int dasm_link(Dst_DECL, size_t *szp)
348354

349355
{ /* Handle globals not defined in this translation unit. */
350356
int idx;
351-
for (idx = 20; idx*sizeof(int) < D->lgsize; idx++) {
357+
for (idx = 10; idx*sizeof(int) < D->lgsize; idx++) {
352358
int n = D->lglabels[idx];
353359
/* Undefined label: Collapse rel chain and replace with marker (< 0). */
354360
while (n > 0) { int *pb = DASM_POS2PTR(D, n); n = *pb; *pb = -idx; }
@@ -377,6 +383,7 @@ int dasm_link(Dst_DECL, size_t *szp)
377383
case DASM_IMM: case DASM_IMM6: case DASM_IMM12: case DASM_IMM13W:
378384
case DASM_IMML: pos++; break;
379385
case DASM_IMM13X: pos += 2; break;
386+
case DASM_VREG: pos++; break;
380387
}
381388
}
382389
stop: (void)0;
@@ -426,6 +433,10 @@ int dasm_encode(Dst_DECL, void *buffer)
426433
ins &= 255; while ((((char *)cp - base) & ins)) *cp++ = 0xe1a00000;
427434
break;
428435
case DASM_REL_LG:
436+
if (n < 0) { /* Global label reference */
437+
n = (int)((ptrdiff_t)D->globals[-n] - (ptrdiff_t)cp + 4);
438+
goto patchrel;
439+
}
429440
CK(n >= 0, UNDEF_LG);
430441
case DASM_REL_PC:
431442
CK(n >= 0, UNDEF_PC);
@@ -467,11 +478,14 @@ int dasm_encode(Dst_DECL, void *buffer)
467478
cp[-1] |= (dasm_imm13(n, *b++) << 10);
468479
break;
469480
case DASM_IMML: {
470-
int scale = (p[-2] >> 30);
481+
int scale = (ins & 0x3);
471482
cp[-1] |= (!(n & ((1<<scale)-1)) && (unsigned int)(n>>scale) < 4096) ?
472483
((n << (10-scale)) | 0x01000000) : ((n & 511) << 12);
473484
break;
474485
}
486+
case DASM_VREG:
487+
cp[-1] |= (n & 0x1f) << (ins & 0x1f);
488+
break;
475489
default: *cp++ = ins; break;
476490
}
477491
}

ext/opcache/jit/dynasm/dasm_arm64.lua

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ local action_names = {
4040
"STOP", "SECTION", "ESC", "REL_EXT",
4141
"ALIGN", "REL_LG", "LABEL_LG",
4242
"REL_PC", "LABEL_PC", "IMM", "IMM6", "IMM12", "IMM13W", "IMM13X", "IMML",
43+
"VREG"
4344
}
4445

4546
-- Maximum number of section buffer positions for dasm_put().
@@ -246,7 +247,7 @@ local map_cond = {
246247

247248
local parse_reg_type
248249

249-
local function parse_reg(expr)
250+
local function parse_reg(expr, shift)
250251
if not expr then werror("expected register name") end
251252
local tname, ovreg = match(expr, "^([%w_]+):(@?%l%d+)$")
252253
local tp = map_type[tname or expr]
@@ -266,18 +267,29 @@ local function parse_reg(expr)
266267
elseif parse_reg_type ~= rt then
267268
werror("register size mismatch")
268269
end
269-
return r, tp
270+
return shl(r, shift or 0), tp
270271
end
271272
end
273+
-- Allow Rx(...) for dynamic register names
274+
local vrt, vreg = match(expr, "^R([xwqdshb])(%b())$")
275+
if vreg then
276+
if not parse_reg_type then
277+
parse_reg_type = vrt
278+
elseif parse_reg_type ~= vrt then
279+
werror("register size mismatch")
280+
end
281+
if shift then waction("VREG", shift, vreg) end
282+
return 0
283+
end
272284
werror("bad register name `"..expr.."'")
273285
end
274286

275287
local function parse_reg_base(expr)
276288
if expr == "sp" then return 0x3e0 end
277-
local base, tp = parse_reg(expr)
289+
local base, tp = parse_reg(expr, 5)
278290
if parse_reg_type ~= "x" then werror("bad register type") end
279291
parse_reg_type = false
280-
return shl(base, 5), tp
292+
return base, tp
281293
end
282294

283295
local parse_ctx = {}
@@ -403,7 +415,7 @@ local function parse_imm_load(imm, scale)
403415
end
404416
werror("out of range immediate `"..imm.."'")
405417
else
406-
waction("IMML", 0, imm)
418+
waction("IMML", scale, imm)
407419
return 0
408420
end
409421
end
@@ -470,7 +482,7 @@ local function parse_load(params, nparams, n, op)
470482
if reg and tailr ~= "" then
471483
local base, tp = parse_reg_base(reg)
472484
if tp then
473-
waction("IMML", 0, format(tp.ctypefmt, tailr))
485+
waction("IMML", shr(op, 30), format(tp.ctypefmt, tailr))
474486
return op + base
475487
end
476488
end
@@ -494,7 +506,7 @@ local function parse_load(params, nparams, n, op)
494506
op = op + parse_imm_load(imm, scale)
495507
else
496508
local p2b, p3b, p3s = match(p2a, "^,%s*([^,%s]*)%s*,?%s*(%S*)%s*(.*)$")
497-
op = op + shl(parse_reg(p2b), 16) + 0x00200800
509+
op = op + parse_reg(p2b, 16) + 0x00200800
498510
if parse_reg_type ~= "x" and parse_reg_type ~= "w" then
499511
werror("bad index register type")
500512
end
@@ -891,15 +903,15 @@ local function parse_template(params, template, nparams, pos)
891903
for p in gmatch(sub(template, 9), ".") do
892904
local q = params[n]
893905
if p == "D" then
894-
op = op + parse_reg(q); n = n + 1
906+
op = op + parse_reg(q, 0); n = n + 1
895907
elseif p == "N" then
896-
op = op + shl(parse_reg(q), 5); n = n + 1
908+
op = op + parse_reg(q, 5); n = n + 1
897909
elseif p == "M" then
898-
op = op + shl(parse_reg(q), 16); n = n + 1
910+
op = op + parse_reg(q, 16); n = n + 1
899911
elseif p == "A" then
900-
op = op + shl(parse_reg(q), 10); n = n + 1
912+
op = op + parse_reg(q, 10); n = n + 1
901913
elseif p == "m" then
902-
op = op + shl(parse_reg(params[n-1]), 16)
914+
op = op + parse_reg(params[n-1], 16)
903915

904916
elseif p == "p" then
905917
if q == "sp" then params[n] = "@x31" end

ext/opcache/jit/zend_jit.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@
3939
#include "Optimizer/zend_call_graph.h"
4040
#include "Optimizer/zend_dump.h"
4141

42+
#if defined(__x86_64__) || defined(i386)
4243
#include "jit/zend_jit_x86.h"
44+
#elif defined (__aarch64__)
45+
#include "jit/zend_jit_arm64.h"
46+
#else
47+
#error "JIT not supported on this platform"
48+
#endif
49+
4350
#include "jit/zend_jit_internal.h"
4451

4552
#ifdef ZTS
@@ -204,9 +211,18 @@ static bool zend_long_is_power_of_two(zend_long x)
204211
#define OP2_RANGE() OP_RANGE(ssa_op, op2)
205212
#define OP1_DATA_RANGE() OP_RANGE(ssa_op + 1, op1)
206213

214+
#if defined(__x86_64__) || defined(i386)
207215
#include "dynasm/dasm_x86.h"
216+
#elif defined(__aarch64__)
217+
#include "dynasm/dasm_arm64.h"
218+
#endif
219+
208220
#include "jit/zend_jit_helpers.c"
221+
#if defined(__x86_64__) || defined(i386)
209222
#include "jit/zend_jit_disasm_x86.c"
223+
#elif defined(__aarch64__)
224+
#include "jit/zend_jit_disasm_arm64.c"
225+
#endif
210226
#ifndef _WIN32
211227
#include "jit/zend_jit_gdb.c"
212228
#include "jit/zend_jit_perf_dump.c"
@@ -216,7 +232,11 @@ static bool zend_long_is_power_of_two(zend_long x)
216232
#endif
217233
#include "jit/zend_jit_vtune.c"
218234

235+
#if defined(__x86_64__) || defined(i386)
219236
#include "jit/zend_jit_x86.c"
237+
#elif defined(__aarch64__)
238+
#include "jit/zend_jit_arm64.c"
239+
#endif
220240

221241
#if _WIN32
222242
# include <Windows.h>
@@ -298,15 +318,32 @@ static void handle_dasm_error(int ret) {
298318
case DASM_S_RANGE_PC:
299319
fprintf(stderr, "DASM_S_RANGE_PC %d\n", ret & 0xffffffu);
300320
break;
321+
#ifdef DASM_S_RANGE_VREG
301322
case DASM_S_RANGE_VREG:
302323
fprintf(stderr, "DASM_S_RANGE_VREG\n");
303324
break;
325+
#endif
326+
#ifdef DASM_S_UNDEF_L
304327
case DASM_S_UNDEF_L:
305328
fprintf(stderr, "DASM_S_UNDEF_L\n");
306329
break;
330+
#endif
331+
#ifdef DASM_S_UNDEF_LG
332+
case DASM_S_UNDEF_LG:
333+
fprintf(stderr, "DASM_S_UNDEF_LG\n");
334+
break;
335+
#endif
336+
#ifdef DASM_S_RANGE_REL
337+
case DASM_S_RANGE_REL:
338+
fprintf(stderr, "DASM_S_RANGE_REL\n");
339+
break;
340+
#endif
307341
case DASM_S_UNDEF_PC:
308342
fprintf(stderr, "DASM_S_UNDEF_PC\n");
309343
break;
344+
default:
345+
fprintf(stderr, "DASM_S_%0x\n", ret & 0xff000000u);
346+
break;
310347
}
311348
ZEND_UNREACHABLE();
312349
}
@@ -391,6 +428,9 @@ static void *dasm_link_and_encode(dasm_State **dasm_state,
391428
entry = *dasm_ptr;
392429
*dasm_ptr = (void*)((char*)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(size, DASM_ALIGNMENT));
393430

431+
/* flush the hardware I-cache */
432+
JIT_CACHE_FLUSH(entry, entry + size);
433+
394434
if (trace_num) {
395435
zend_jit_trace_add_code(entry, size);
396436
}

0 commit comments

Comments
 (0)