Skip to content

Fix external PHP extension ABI exports#4108

Open
chubes4 wants to merge 9 commits into
WordPress:trunkfrom
chubes4:fix/4107-external-extension-abi
Open

Fix external PHP extension ABI exports#4108
chubes4 wants to merge 9 commits into
WordPress:trunkfrom
chubes4:fix/4107-external-extension-abi

Conversation

@chubes4

@chubes4 chubes4 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • derive the public PHP extension ABI from the matching libphp.a while retaining MAIN_MODULE=2
  • normalize phpize headers so external extensions use stable _emalloc instead of build-specific _emalloc_<size> symbols
  • make the extension compiler resolve PHP patch releases from the same canonical version data as runtime builds
  • add a Docker-backed PHP 8.3 fixture that compiles a real side module and verifies every env and GOT.* import resolves against the matching runtime or module

Closes #4107.

Why

The runtime previously generated its retained export list only from bundled extensions. @php-wasm/compile-extension could therefore produce a valid external side module that imported public PHP APIs removed from the matching main module by dead-code elimination. Startup then failed late with TypeError: resolved is not a function.

The extension compiler also duplicated PHP patch-version constants. That map drifted to PHP 8.4.20 while the runtime used canonical PHP 8.4.23, undermining matched-header ABI proof. Both paths now consume supported-php-versions.mjs; the published bundle embeds that canonical data and does not gain a workspace-relative runtime dependency.

This keeps the MAIN_MODULE=2 optimization while making the generic public PHP extension ABI available to externally compiled modules. Archive member labels emitted by llvm-nm are filtered before generating Emscripten and Wasm linker export lists.

How to test

  1. Check out this branch on a machine with Docker and Buildx.
  2. Run npm ci.
  3. Run npm exec nx -- run php-wasm-compile-extension:lint.
  4. Run npm exec nx -- run php-wasm-compile-extension:test.
  5. Run npm exec nx -- run php-wasm-compile-extension:typecheck.
  6. Run npm exec nx -- run php-wasm-compile-extension:test-external-extension-abi.
  7. Confirm the final target reports success after building both the PHP 8.3 JSPI runtime and the external phpize side module.

Verification

  • Lint passed.
  • Unit tests passed: 7 files, 38 tests.
  • Typecheck passed.
  • Docker-backed external ABI integration passed with ABI_TEST_EXIT=0.
  • PHP 8.3 JSPI Wasm size changed from 22,839,234 bytes on trunk to 23,251,406 bytes on this branch: +412,172 bytes (+1.80%).
  • Homeboy independently adopted the patch, promoted the three changed version-alignment files, and reran the 38 tests plus both TypeScript typechecks successfully.

Compatibility

The supported PHP minor-version list and public package API are unchanged. Extension builds now follow the canonical runtime patch release instead of a stale duplicate, so rebuilding may produce a side module against newer patch-level headers. Published package bundles embed the canonical version data; no extension path or workspace-relative runtime dependency is introduced.

AI assistance

  • AI assistance: Yes
  • Tool(s): OpenCode with OpenAI GPT-5.6 Sol and Homeboy
  • Used for: Diagnosed the external side-module ABI and patch-version drift, drafted the implementation and integration coverage, and iterated on real PHP.wasm and control-plane verification failures. Chris reviewed and owns the change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR fixes the external PHP extension ABI surface for PHP.wasm by deriving exports from the matching libphp.a, normalizing phpize headers to ensure stable allocator symbols, and adding a Docker-backed integration test that compiles a real external extension and verifies its imports resolve.

Changes:

  • Generate retained export lists from libphp.a (in addition to bundled extensions) to preserve the public extension ABI while keeping MAIN_MODULE=2.
  • Patch installed PHP headers in the extension build image to avoid build-specific _emalloc_<size> symbol references.
  • Add a Docker-backed fixture and Nx target to compile and validate an external side module against the matching runtime exports.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/php-wasm/compile/php/Dockerfile Derives ABI export lists from libphp.a via llvm-nm to retain extension ABI under MAIN_MODULE=2.
packages/php-wasm/compile-extension/tests/test-external-extension-abi.sh Adds an integration script that builds a runtime + external module and checks unresolved imports.
packages/php-wasm/compile-extension/tests/fixtures/external-abi/external_abi.c Adds a real external extension that exercises key API/allocator symbols.
packages/php-wasm/compile-extension/tests/fixtures/external-abi/config.m4 Adds phpize config for the external ABI fixture.
packages/php-wasm/compile-extension/project.json Adds an Nx target to run the external ABI integration test.
packages/php-wasm/compile-extension/docker/Dockerfile.ext Patches installed PHP headers to disable constant-size emalloc() specialization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/php-wasm/compile/php/Dockerfile
Comment thread packages/php-wasm/compile/php/Dockerfile Outdated
Comment thread packages/php-wasm/compile-extension/tests/test-external-extension-abi.sh Outdated
Comment thread packages/php-wasm/compile-extension/docker/Dockerfile.ext Outdated
@chubes4
chubes4 requested a review from Copilot July 18, 2026 01:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Comment on lines +2194 to +2206
RUN set -eux; \
/root/emsdk/upstream/bin/llvm-nm \
--defined-only \
--extern-only \
--format=just-symbols \
/root/lib/libphp.a \
| grep -v '^[[:space:]]*$' \
| grep -v ':$' \
> /root/.PHP_EXTENSION_ABI_EXPORTS; \
sed 's/^/_/' /root/.PHP_EXTENSION_ABI_EXPORTS \
>> /root/.JS_ABI_EXPORTS; \
cat /root/.PHP_EXTENSION_ABI_EXPORTS \
>> /root/.WASM_ABI_EXPORTS

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 202bc37. The filtered llvm-nm symbols now pass through LC_ALL=C sort -u before either export list is generated.

Comment on lines +54 to +58
RUN /root/replace.sh \
's/#include "zend_alloc_sizes.h"/#include "zend_alloc_sizes.h"\n#undef HAVE_BUILTIN_CONSTANT_P/' \
/usr/local/include/php/Zend/zend_alloc.h && \
grep -Fqx '#undef HAVE_BUILTIN_CONSTANT_P' \
/usr/local/include/php/Zend/zend_alloc.h

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 202bc37. The Docker layer now checks for the exact #undef first, applies the replacement only when absent, and retains the final verification.

Comment on lines +4 to +6
ROOT_DIR="$(git rev-parse --show-toplevel)"
WORK_DIR="${ROOT_DIR}/tmp/external-extension-abi"
PHP_VERSION="${PHP_VERSION:-8.3}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 202bc37. Each integration run now uses a unique mktemp -d directory under the repository temp root, with the existing exit trap scoped to that path.

Comment on lines +13 to +15
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
trap 'rm -rf "$WORK_DIR"' EXIT

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 202bc37 by replacing the fixed work directory with a unique mktemp -d path and retaining cleanup through the scoped trap.

@chubes4

chubes4 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@mho22 Review follow-up is ready in 202bc37ec: ABI symbols are deterministically sorted/deduplicated, the header patch is idempotent, and the Docker integration uses a unique temporary directory. Lint, typecheck, shell syntax, and diff checks pass; fresh CI is underway. This remains the external-extension ABI dependency for the proven PHP 8.4 Sodium/WPCOM test stack.

@chubes4

chubes4 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Downstream full-stack validation completed against this PR plus #4146 using https://github.com/chubes4/wordpress-playground/tree/proof/bet12-clean-worker-abi.

The combined build loaded the external Sodium extension and ran clean PHP workers against managed MariaDB. Real focused WPCOM PHPUnit execution passed 25 CHATGPT-19 media tests across the MCP media-create integration and image/audio upload transport suites.

Downstream PR: Automattic/wp-codebox#1938

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

compile-extension artifacts can require symbols omitted from MAIN_MODULE=2 runtimes

2 participants