11#! /usr/bin/env bash
22
33# Build script for Flow PHP Interactive Playground
4- # Builds PHP 8.5.8
4+ # Builds PHP 8.5.8 as 64-bit wasm (see WASM64_MODE below) with a forced mmap Opcache shared-memory
5+ # backend (see php_cv_shm_mmap_anon below).
56
67set -xeu
78
@@ -15,6 +16,13 @@ echo "=== Build started at $(date) ==="
1516PHP_VERSION=8.5.8
1617PHP_PATH=php-$PHP_VERSION
1718
19+ # MEMORY64=2 is wasm64 for clang/lld lowered to wasm32 by Binaryen, so PHP gets an 8-byte zend_long
20+ # (pack()'s q/Q/J/P, which Floe and sortBy() need) without requiring Memory64 in visitors' browsers.
21+ # Set via EMCC_CFLAGS, not CFLAGS, which is exported below -- after the dependencies are built.
22+ WASM64_MODE=" -sMEMORY64=2"
23+ EM_TARGET=wasm64-emscripten
24+ export EMCC_CFLAGS=" $WASM64_MODE "
25+
1826echo " Build libxml2 for WebAssembly"
1927LIBXML2_VERSION=2.11.4
2028LIBXML2_DIR=libxml2-$LIBXML2_VERSION
@@ -48,9 +56,8 @@ if [ ! -d "$LIBXML2_DIR" ]; then
4856fi
4957
5058echo " Build libpg_query for WebAssembly"
51- # Single source of truth is the pg_query extension's own pin, so the playground and the CLI can
52- # never disagree about which PostgreSQL grammar parses. build.sh already consumes that directory's
53- # ext/ sources below, so this adds no coupling that did not already exist.
59+ # Read from the pg_query extension's own pin, so the playground and the CLI cannot disagree about
60+ # which PostgreSQL grammar parses.
5461LIBPG_QUERY_VERSION=$( sed -n ' s/^LIBPG_QUERY_VERSION := //p' \
5562 " $PROJECT_ROOT /../src/extension/pg-query-ext/Makefile" )
5663
@@ -59,27 +66,22 @@ if [ -z "$LIBPG_QUERY_VERSION" ]; then
5966 exit 1
6067fi
6168
62- # Version in the directory name, like libxml2/libzip/php above: a bumped pin then misses the
63- # guard below and re-clones, instead of silently reusing the previous version's checkout.
64- # Note this tracks a moving branch, so an upstream branch move still needs a manual rm -rf.
69+ # Version in the directory name so a bumped pin re-clones instead of reusing the old checkout.
70+ # This tracks a moving branch, so an upstream branch move still needs a manual rm -rf.
6571LIBPG_QUERY_DIR=libpg_query-$LIBPG_QUERY_VERSION
6672LIBPG_QUERY_INSTALL_DIR=" $PROJECT_ROOT /$LIBPG_QUERY_DIR "
6773
68- # Guard on the built archive rather than the directory, like libzip below: a build that fails
69- # part way then leaves a tree that a directory check would skip, and the missing archive would
70- # only surface much later at link time.
74+ # Guard on the built archive, not the directory: a part-way failure leaves a tree that a directory
75+ # check would skip, and the missing archive would only surface at link time.
7176if [ ! -f " $LIBPG_QUERY_INSTALL_DIR /libpg_query.a" ]; then
7277 rm -rf " $LIBPG_QUERY_DIR "
7378 git clone --depth=1 --branch=$LIBPG_QUERY_VERSION \
7479 https://github.com/pganalyze/libpg_query.git " $LIBPG_QUERY_DIR "
7580 cd $LIBPG_QUERY_DIR
7681
77- # Build with Emscripten - override CC, AR and ARFLAGS.
78- # libpg_query 18 keeps AR and ARFLAGS separate (Makefile:71-72), unlike 17's combined
79- # AR := $(AR) rs, so folding the flags into AR gives `emar rcs rs` -- two operations, which
80- # llvm-ar rejects. Both have to come from the command line: the Makefile's `?=` never fires,
81- # because GNU make predefines ARFLAGS=-rv and `?=` only assigns when the origin is `undefined`.
82- # `rcs` rather than the default `-rv` so the archive still gets its symbol index.
82+ # libpg_query 18 keeps AR and ARFLAGS separate, so folding the flags into AR gives `emar rcs rs`,
83+ # which llvm-ar rejects. Both must come from the command line -- make predefines ARFLAGS, so the
84+ # Makefile's `?=` never fires.
8385 emmake make build -j$( nproc) CC=emcc AR=emar ARFLAGS=rcs
8486
8587 cd $PROJECT_ROOT
@@ -95,15 +97,16 @@ if [ ! -f "$LIBZIP_INSTALL_DIR/lib/libzip.a" ]; then
9597 # First, ensure Emscripten's zlib port is built by triggering a compile
9698 # This downloads and builds zlib to the Emscripten cache
9799 echo " int main(){return 0;}" > /tmp/zlib_test.c
98- # -flto must match CFLAGS below: emcc caches a separate port build per variant, and this
99- # script links the lto/ one. Without it only the non-LTO libz.a is produced and the check
100- # after this block fails.
100+ # -flto must match CFLAGS below: emcc caches a separate port build per variant and this links
101+ # the lto/ one.
101102 emcc -flto -sUSE_ZLIB=1 /tmp/zlib_test.c -o /tmp/zlib_test.js 2> /dev/null || true
102103 rm -f /tmp/zlib_test.c /tmp/zlib_test.js /tmp/zlib_test.wasm
103104
104105 # Get Emscripten cache path and locate zlib
105106 EM_CACHE=$( em-config CACHE)
106- ZLIB_LIBRARY=" $EM_CACHE /sysroot/lib/wasm32-emscripten/lto/libz.a"
107+ # Per-target path: hardcoding wasm32 would find a real file of the wrong architecture, which the
108+ # guard below cannot detect.
109+ ZLIB_LIBRARY=" $EM_CACHE /sysroot/lib/$EM_TARGET /lto/libz.a"
107110 ZLIB_INCLUDE_DIR=" $EM_CACHE /sysroot/include"
108111
109112 echo " Using zlib from Emscripten cache:"
@@ -121,11 +124,11 @@ if [ ! -f "$LIBZIP_INSTALL_DIR/lib/libzip.a" ]; then
121124 tar xf $LIBZIP_DIR .tar.xz
122125 cd $LIBZIP_DIR
123126
127+ # CMakeCache.txt pins the resolved zlib path and target, so a tree left from another target has
128+ # to be cleared rather than reconfigured.
129+ rm -rf build
124130 mkdir -p build && cd build
125131
126- # Configure libzip for WebAssembly using CMake
127- # Provide explicit paths to Emscripten's zlib (from its ports system)
128- # Disable encryption and optional compression to minimize dependencies
129132 emcmake cmake .. \
130133 -DCMAKE_INSTALL_PREFIX=$LIBZIP_INSTALL_DIR \
131134 -DZLIB_LIBRARY=$ZLIB_LIBRARY \
161164echo " Patch php-src"
162165# Re-runs reuse an already extracted $PHP_PATH, so this has to be idempotent: a cleanly applying
163166# reverse patch means it is already in place. Anything else is a real failure and set -e ends here.
164- PHP_PATCH=" $PROJECT_ROOT /patches/php-8.5-opcache-unistd.patch"
165167cd " $PHP_PATH "
166168
167- if patch -p1 --reverse --dry-run --force --silent < " $PHP_PATCH " > /dev/null 2>&1 ; then
168- echo " already applied: $( basename " $PHP_PATCH " ) "
169- else
170- patch -p1 --forward < " $PHP_PATCH "
171- fi
169+ for PHP_PATCH in " $PROJECT_ROOT " /patches/* .patch; do
170+ if patch -p1 --reverse --dry-run --force --silent < " $PHP_PATCH " > /dev/null 2>&1 ; then
171+ echo " already applied: $( basename " $PHP_PATCH " ) "
172+ else
173+ patch -p1 --forward < " $PHP_PATCH "
174+ fi
175+ done
172176
173177cd " $PROJECT_ROOT "
174178
@@ -191,11 +195,10 @@ cp -r "$SNAPPY_EXT_DIR" "$SNAPPY_EXT_DST"
191195
192196echo " Configure PHP"
193197
194- # Use -Oz for size optimization instead of -O3 for speed
195198# -DHAVE_REALLOCARRAY=1: emcc's link probe for reallocarray() fails while its sysroot header still
196- # declares the symbol , so main/php_glob.c (always built since 8.5) compiles its own static copy and
197- # clashes. See https://github.com/php/php-src/issues/19152 .
198- export CFLAGS=" -Oz -flto -fPIC -g0 -DZEND_MM_ERROR=0 - DHAVE_REALLOCARRAY=1 -I$LIBXML2_INSTALL_DIR /include/libxml2 -I$LIBPG_QUERY_INSTALL_DIR -I$LIBPG_QUERY_INSTALL_DIR /src -I$LIBZIP_INSTALL_DIR /include -sUSE_ZLIB=1"
199+ # declares it , so main/php_glob.c compiles a clashing static copy (php-src#19152).
200+ # ZEND_MM_ERROR is deliberately NOT 0 -- it gates the messages that named the chunk-alignment leak .
201+ export CFLAGS=" -Oz -flto -fPIC -g0 -DHAVE_REALLOCARRAY=1 -I$LIBXML2_INSTALL_DIR /include/libxml2 -I$LIBPG_QUERY_INSTALL_DIR -I$LIBPG_QUERY_INSTALL_DIR /src -I$LIBZIP_INSTALL_DIR /include -sUSE_ZLIB=1"
199202export CXXFLAGS=" -Oz -flto -fPIC -g0 -std=c++11 -sUSE_ZLIB=1"
200203export LDFLAGS=" -L$LIBXML2_INSTALL_DIR /lib -L$LIBPG_QUERY_INSTALL_DIR -L$LIBZIP_INSTALL_DIR /lib -sUSE_ZLIB=1"
201204
@@ -207,13 +210,7 @@ export LIBZIP_LIBS="-L$LIBZIP_INSTALL_DIR/lib -lzip"
207210
208211cd $PHP_PATH
209212
210- # Configure with extensions required by Flow PHP
211- # - bcmath: required by flow-php/parquet
212- # - libxml: required as base for XML extensions
213- # - xml, dom, xmlreader, xmlwriter: required by flow-php/etl-adapter-xml
214- # - phar, mbstring: essential PHP extensions
215- # - iconv: required by symfony/polyfill-mbstring
216- # - zip: required by flow-php/etl-adapter-excel (XLSX files are ZIP archives)
213+ # The extension set and the reason for each is in documentation/contributing/wasm.md.
217214
218215# Fix permissions for build scripts
219216chmod +x buildconf build/config-stubs build/shtool 2> /dev/null || true
@@ -223,13 +220,14 @@ bash ./buildconf --force
223220
224221set +e
225222
226- # --disable-opcache-jit is NOT redundant next to --disable-all. Since php-src 7b4c14dc1016 opcache
227- # is non-optional and --disable-all cannot reach it, but its JIT is still gated on $host_cpu -- and
228- # emconfigure passes no --host, so config.guess reports the BUILD machine. On any host autoconf
229- # calls JIT-capable (x86_64, aarch64, ...) emcc would be handed jit/ir/*.c and dynasm for the host
230- # ISA to compile into a wasm32 binary. Same class of workaround as --disable-fiber-asm below.
231- # --disable-huge-code-pages is pointless under wasm and matches the other PHP-to-wasm builds.
232- # Both pass [no] as their 5th PHP_ARG_ENABLE arg, so neither is affected by --disable-all.
223+ # Opcache's shared-memory probes all call fork(), which Emscripten lacks, so configure would leave
224+ # Opcache permanently inert. Only the fork() half really fails -- Emscripten's mmap() serves
225+ # anonymous MAP_SHARED from linear memory and its fcntl() reports the locks taken, which is enough.
226+ export php_cv_shm_mmap_anon=yes
227+
228+ # --disable-opcache-jit and --disable-huge-code-pages are NOT redundant next to --disable-all: both
229+ # pass [no] as their 5th PHP_ARG_ENABLE arg, so --disable-all cannot reach either. Without the JIT
230+ # flag emcc is handed dynasm for the build machine's ISA, since emconfigure passes no --host.
233231emconfigure ./configure \
234232 --disable-all \
235233 --disable-opcache-jit \
@@ -290,9 +288,9 @@ echo "Compile pib_eval wrapper"
290288emcc $CFLAGS -I . -I Zend -I main -I TSRM/ ../pib_eval.c -c -o pib_eval.o
291289
292290echo " Link everything together"
293- emcc $CFLAGS $LDFLAGS \
291+ emcc $CFLAGS $LDFLAGS $WASM64_MODE \
294292 -s ENVIRONMENT=web \
295- -s EXPORTED_FUNCTIONS=' ["_pib_eval", "_pib_force_exit", "_php_embed_init", "_zend_eval_string", "_php_embed_shutdown "]' \
293+ -s EXPORTED_FUNCTIONS=' ["_pib_eval", "_pib_force_exit", "_pib_heap_bytes "]' \
296294 -s EXPORTED_RUNTIME_METHODS=' ["ccall","FS","UTF8ToString","lengthBytesUTF8","stringToUTF8","getValue","setValue","ENV"]' \
297295 -s MODULARIZE=1 \
298296 -s EXPORT_NAME=" 'PHP'" \
0 commit comments