Fix AArch64 JIT encoding of invalid logical immediates

dasm_imm13() accepted some non-encodable constants and returned a bogus
encoding, so AND/OR/XOR with them computed a wrong result
(0x5555555555555554) or emitted an illegal instruction (0x200000003).
It also negated INT64_MIN in signed arithmetic. Backport the rewritten
dasm_imm13() from DynASM (LuaJIT a9981441), already in master via the
IR update.

Closes GH-24663.
This commit is contained in:
Jorg Sowa committed 2026-10-27 22:37:01 +01:00
1 parent ef873f64f3
commit aa6e3a84e8
8 files changed
+148 -31

No files matched your search

+1
View File
@@ -161,6 +161,7 @@ PHP NEWS
. Call orig_post_shutdown_cb(). (ndossche)
. Fixed JIT segfault on switch over a string reference holding a
non-string value. (jorgsowa)
. Fixed AArch64 JIT encoding of invalid logical immediates. (jorgsowa)
- PDO:
. Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.
+33 -31
View File
@@ -175,44 +175,46 @@ static int dasm_imm12(unsigned int n)
return -1;
}
static int dasm_ffs(unsigned long long x)
#if defined(__GNUC__) || defined(__clang__)
#define dasm_ctz(x) __builtin_ctzll(x)
#define dasm_clz(x) __builtin_clzll(x)
#else
static int dasm_ctz(unsigned long long x)
{
int n = -1;
while (x) { x >>= 1; n++; }
int n = 0;
x = ~x;
while (x & 1) { x >>= 1; n++; }
return n;
}
static int dasm_clz(unsigned long long x)
{
int n = 0;
x = ~x;
while ((long long)x < 0) { x <<= 1; n++; }
return n;
}
#endif
static unsigned long long dasm_ror(unsigned long long x, int n)
{
return (x << (-n & 63)) | (x >> n);
}
static int dasm_imm13(int lo, int hi)
{
int inv = 0, w = 64, s = 0xfff, xa, xb;
/* Thanks to: https://dougallj.wordpress.com/2021/10/30/ */
int rot, ones, size, immr, imms;
unsigned long long n = (((unsigned long long)hi) << 32) | (unsigned int)lo;
unsigned long long m = 1ULL, a, b, c;
if (n & 1) { n = ~n; inv = 1; }
a = n & (unsigned long long)-(long long)n;
b = (n+a)&(unsigned long long)-(long long)(n+a);
c = (n+a-b)&(unsigned long long)-(long long)(n+a-b);
xa = dasm_ffs(a); xb = dasm_ffs(b);
if (c) {
w = dasm_ffs(c) - xa;
if (w == 32) m = 0x0000000100000001UL;
else if (w == 16) m = 0x0001000100010001UL;
else if (w == 8) m = 0x0101010101010101UL;
else if (w == 4) m = 0x1111111111111111UL;
else if (w == 2) m = 0x5555555555555555UL;
else return -1;
s = (-2*w & 0x3f) - 1;
} else if (!a) {
return -1;
} else if (xb == -1) {
xb = 64;
}
if ((b-a) * m != n) return -1;
if (inv) {
return ((w - xb) << 6) | (s+w+xa-xb);
} else {
return ((w - xa) << 6) | (s+xb-xa);
}
return -1;
if ((n+1u) <= 1u) return -1; /* Neither all-zero nor all-ones are allowed. */
rot = (n & (n+1u)) ? dasm_ctz(n & (n+1u)) : 64;
n = dasm_ror(n, rot & 63);
ones = dasm_ctz(~n);
size = dasm_clz(n) + ones;
if (dasm_ror(n, size & 63) != n) return -1; /* Non-repeating? */
immr = -rot & (size - 1);
imms = (-(size << 1) | (ones - 1)) & 63;
return ((immr | (size & 64)) << 6) | imms;
}
/* Pass 1: Store actions and args, link branches/labels, estimate offsets. */
+19
View File
@@ -0,0 +1,19 @@
--TEST--
JIT BW_AND: 003 (non-encodable AArch64 logical immediate, wrong result)
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=function
opcache.jit_buffer_size=64M
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
--FILE--
<?php
function foo($x) { return $x & 0x5555555555555554; }
var_dump(foo(-1));
?>
--EXPECT--
int(6148914691236517204)
+19
View File
@@ -0,0 +1,19 @@
--TEST--
JIT BW_AND: 004 (non-encodable AArch64 logical immediate, illegal instruction)
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=function
opcache.jit_buffer_size=64M
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
--FILE--
<?php
function foo($x) { return $x & 0x200000003; }
var_dump(foo(-1));
?>
--EXPECT--
int(8589934595)
+19
View File
@@ -0,0 +1,19 @@
--TEST--
JIT BW_OR: 001 (non-encodable AArch64 logical immediate, wrong result)
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=function
opcache.jit_buffer_size=64M
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
--FILE--
<?php
function foo($x) { return $x | 0x5555555555555554; }
var_dump(foo(0));
?>
--EXPECT--
int(6148914691236517204)
+19
View File
@@ -0,0 +1,19 @@
--TEST--
JIT BW_OR: 002 (non-encodable AArch64 logical immediate, illegal instruction)
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=function
opcache.jit_buffer_size=64M
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
--FILE--
<?php
function foo($x) { return $x | 0x200000003; }
var_dump(foo(0));
?>
--EXPECT--
int(8589934595)
+19
View File
@@ -0,0 +1,19 @@
--TEST--
JIT XOR: 004 (non-encodable AArch64 logical immediate, wrong result)
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=function
opcache.jit_buffer_size=64M
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
--FILE--
<?php
function foo($x) { return $x ^ 0x5555555555555554; }
var_dump(foo(0));
?>
--EXPECT--
int(6148914691236517204)
+19
View File
@@ -0,0 +1,19 @@
--TEST--
JIT XOR: 005 (non-encodable AArch64 logical immediate, illegal instruction)
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_update_protection=0
opcache.jit=function
opcache.jit_buffer_size=64M
--EXTENSIONS--
opcache
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
--FILE--
<?php
function foo($x) { return $x ^ 0x200000003; }
var_dump(foo(0));
?>
--EXPECT--
int(8589934595)