Skip to content

Commit 89394e7

Browse files
ondrejmirtesclaude
andcommitted
turbo-ext: shared core loads on PHP 8.3-8.6 on Linux too
The core no longer references anything in the version-specific library: the pt_abi table, the module-startup registration list and the shadow plan registry moved into the new Core.cpp, and the functions Abi.cpp implements per minor became function pointers in pt_abi. The few core functions the version-specific library calls are PT_CORE_API, everything else stays hidden. PHP 8.6 broke the remaining assumptions, now read through pt_abi too: - zend_class_entry gained ce_flags2 after ce_flags, moving every later member (PT_CE(), type-checked to take a zend_class_entry only) - the ZPP_ERROR_* codes became a uint8_t enum without WRONG_COUNT and two Z_EXPECTED_* types were inserted; the parse slow paths now return the value (neutral ZPP macros, translated by Abi.cpp) - zend_is_callable, zend_call_known_function, _php_stream_free and _php_stream_read are no longer exported - zend_arg_info grew a doc_comment (PT_ARG_INFO()) and ZEND_COLD is pinned, since the older headers enable it for GCC only. bin/shared-core/gate-linux.sh builds in the CI images (GCC, or clang with GATE_IMAGE/GATE_MAKE_ARGS), link-split-linux.sh links one core from the 8.4 objects plus a 68 KB extension per minor and runs smoke.php with each: ALL OK on 8.3, 8.4, 8.5 and 8.6. The macOS scripts got a -mac suffix. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017h1GfB7vfJPgwcTHD9Fth6
1 parent b1c77b2 commit 89394e7

65 files changed

Lines changed: 887 additions & 306 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎turbo-ext/Makefile‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ CXXFLAGS += $(PGO_FLAGS)
207207
SOURCES := $(wildcard src/*.cpp) $(wildcard src/parser/*.cpp)
208208
OBJECTS := $(SOURCES:.cpp=.o)
209209

210+
# the objects alone — what bin/shared-core/gate-*.sh compare between the PHP
211+
# versions' headers
212+
objects: $(OBJECTS)
213+
.PHONY: objects
214+
210215
phpstan_turbo.so: $(OBJECTS)
211216
$(CXX) `$(PHP_CONFIG) --ldflags` -shared $(LTO_FLAGS) $(LINK_FLAGS) $(PGO_FLAGS) -o $@ $(OBJECTS)
212217
@# a shared object links with undefined symbols allowed (the engine's are
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
# compare every object's non-code section contents and all relocations
3+
# between two builds (ELF; one objdump call per object and kind)
4+
S=${SHARED_CORE_WORK_DIR:?set SHARED_CORE_WORK_DIR to a scratch directory}
5+
O=${LLVM_OBJDUMP:-/opt/homebrew/opt/llvm/bin/llvm-objdump}
6+
A=$1; B=$2
7+
norm_relocs() { $O -r "$1" | grep -v 'file format' | sed -E 's/\.L[A-Za-z]*[0-9]+/.L/g'; }
8+
data() { $O -s "$1" | awk '/^Contents of section / { keep = ($4 !~ /^\.text/ && $4 !~ /^\.comment/) } keep'; }
9+
cd "$S/$A"
10+
for o in $(find src -name '*.o' | sort); do
11+
[ "$(data $o | md5)" != "$(data $S/$B/$o | md5)" ] && echo "DATA DIFF $o"
12+
[ "$(norm_relocs $o | md5)" != "$(norm_relocs $S/$B/$o | md5)" ] && echo "RELOC DIFF $o"
13+
done
14+
true
File renamed without changes.

‎turbo-ext/bin/shared-core/compare-functions.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import hashlib, os, re, subprocess, sys
66
from concurrent.futures import ThreadPoolExecutor
77

8-
OBJDUMP = '/opt/homebrew/opt/llvm/bin/llvm-objdump'
8+
OBJDUMP = os.environ.get('LLVM_OBJDUMP', '/opt/homebrew/opt/llvm/bin/llvm-objdump')
99
func_re = re.compile(r'^<(.+)>:$')
1010
addr_re = re.compile(r'0x[0-9a-f]+ <')
1111

@@ -29,7 +29,7 @@ def flush():
2929
if cur is None:
3030
continue
3131
s = line.strip()
32-
if not s:
32+
if not s or s.startswith('Disassembly of section'):
3333
continue
3434
# branch targets print absolute section offsets; keep only the symbolic part
3535
s = addr_re.sub('<', s)
@@ -39,10 +39,15 @@ def flush():
3939
s = re.sub(r'\$_\d+', '$_', s)
4040
s = re.sub(r'\.cold\.\d+', '.cold', s)
4141
s = re.sub(r'(l_\.str|ltmp|LJTI|lJTI|l___const|lCPI)[\.\d_]+', r'\1', s)
42+
# GCC's local labels (.LC0, .LANCHOR1, .L42) and clone suffixes
43+
s = re.sub(r'\.L[A-Za-z]*\d+', '.L', s)
44+
# GCC addresses merged strings through the section of whichever
45+
# function emitted them first: section order, not content
46+
s = re.sub(r'\.rodata\.[^ +]*\.str1\.\d+(\+0x[0-9a-f]+)?', '.rodata.str', s)
4247
# relocation lines print an offset column too
4348
s = re.sub(r'^[0-9a-f]+:\s+', '', s)
4449
lines.append(s)
45-
if not s.startswith('ARM64_RELOC'):
50+
if not s.startswith(('ARM64_RELOC', 'R_AARCH64', 'R_X86_64')):
4651
n += 1
4752
flush()
4853
return funcs
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
# The Linux counterpart of gate-mac.sh: compiles turbo-ext in the CI build
3+
# images (ghcr.io/phpstan/turbo-build:gnu-php<v>, GCC 11.4) against each
4+
# PHP version's headers — LTO off, so the objects hold machine code — and
5+
# compares the results function by function.
6+
S=${SHARED_CORE_WORK_DIR:?set SHARED_CORE_WORK_DIR to a scratch directory}
7+
WT=$(cd "$(dirname "$0")/../.." && pwd)
8+
VERSIONS=${GATE_VERSIONS:-8.3 8.4 8.5 8.6}
9+
JOBS=${GATE_JOBS:-3}
10+
rm -f "$S"/lx-*.failed
11+
for v in $VERSIONS; do
12+
mkdir -p "$S/lx-$v"
13+
rsync -a --exclude '*.o' --exclude '*.so' --exclude PHP-CPP --exclude pgo --exclude version.stamp "$WT/" "$S/lx-$v/"
14+
echo "${GATE_VERSION_TXT:-dev}" > "$S/lx-$v/VERSION.txt"
15+
docker run --rm -v "$S/lx-$v:/build" -w /build "${GATE_IMAGE:-ghcr.io/phpstan/turbo-build}:gnu-php$v" \
16+
sh -c "make -k -j$JOBS LTO_FLAGS= WARN_FLAGS=\"${GATE_WARN_FLAGS:--Wall}\" ${GATE_MAKE_ARGS} ${GATE_TARGET:-objects} > build.log 2>&1" \
17+
|| { touch "$S/lx-$v.failed"; echo "BUILD FAILED $v"; grep -E ': (error|warning):' "$S/lx-$v/build.log" | head -20; } &
18+
done
19+
wait
20+
ls "$S"/lx-*.failed >/dev/null 2>&1 && exit 1
21+
first=$(echo $VERSIONS | awk '{print $1}')
22+
cd "$S" && python3 -I "$WT/bin/shared-core/compare-functions.py" $(for v in $VERSIONS; do printf 'lx-%s ' $v; done)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
# The Linux counterpart of link-split-mac.sh, on the objects gate-linux.sh
3+
# built: one core shared object linked from the $CORE_FROM objects, one thin
4+
# extension per PHP version linked against it (found next to itself through
5+
# $ORIGIN), each loaded in its version's CI image, which then runs the smoke
6+
# test with it.
7+
set -e
8+
S=${SHARED_CORE_WORK_DIR:?set SHARED_CORE_WORK_DIR to a scratch directory}
9+
REPO=$(cd "$(dirname "$0")/../../.." && pwd)
10+
VERSIONS=${GATE_VERSIONS:-8.3 8.4 8.5 8.6}
11+
CORE_FROM=${CORE_FROM:-8.4}
12+
OUT=$S/split-linux
13+
rm -rf "$OUT"; mkdir -p "$OUT"
14+
15+
SHIM='src/Abi.o src/Shadow.o src/TrustedTypes.o src/main.o src/TerminateHandler.o'
16+
# the link flags of the Makefile's Linux build; --exclude-libs keeps the
17+
# statically folded libstdc++ out of both dynamic symbol tables
18+
FLAGS='-static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL -Wl,--gc-sections -Wl,-z,relro,-z,now -Wl,-z,noexecstack'
19+
20+
docker run --rm -v "$S/lx-$CORE_FROM:/build" -v "$OUT:/out" -w /build "ghcr.io/phpstan/turbo-build:gnu-php$CORE_FROM" sh -ec "
21+
core=\$(find src -name '*.o' | grep -v -E 'src/(Abi|Shadow|TrustedTypes|main)\.o\$' | sort)
22+
g++ -shared $FLAGS -Wl,-soname,libphpstan_turbo_core.so -o /out/libphpstan_turbo_core.so \$core
23+
echo \"core exports: \$(nm -D --defined-only /out/libphpstan_turbo_core.so | wc -l)\"
24+
"
25+
26+
for v in $VERSIONS; do
27+
docker run --rm -v "$S/lx-$v:/build" -v "$OUT:/out" -v "$REPO:/repo:ro" -w /build "ghcr.io/phpstan/turbo-build:gnu-php$v" sh -ec "
28+
g++ -shared $FLAGS -Wl,-rpath,'\$ORIGIN' -o /out/phpstan_turbo-$v.so $SHIM -L/out -lphpstan_turbo_core
29+
# every extension symbol must come from the core: undefined pt_*
30+
# symbols would only fail at load time
31+
missing=\$(nm -D --undefined-only /out/phpstan_turbo-$v.so | awk '{print \$2}' | grep -E '^(_Z.*phpstanturbo|_Z.*pt_|pt_)' | while read sym; do nm -D --defined-only /out/libphpstan_turbo_core.so | awk '{print \$3}' | grep -qx \"\$sym\" || echo \$sym; done)
32+
[ -z \"\$missing\" ] || { echo \"PHP $v: undefined extension symbols: \$missing\"; exit 1; }
33+
cd /repo
34+
echo \"PHP $v: \$(php -n -d extension=/out/phpstan_turbo-$v.so -r 'echo PHP_VERSION, \" turbo=\", phpversion(\"phpstan_turbo\");')\"
35+
TURBO_DLL=/out/phpstan_turbo-$v.so php -d memory_limit=4G -d extension=/out/phpstan_turbo-$v.so turbo-ext/tests/smoke.php 2>&1 | tail -1
36+
" || echo "PHP $v: FAILED"
37+
done
38+
ls -l "$OUT"
File renamed without changes.

0 commit comments

Comments
 (0)