Skip to content

Architecture: side-load extensions — core should not link all 75 by default #23480

Description

@PurHur

Direction set 2026-07-26: the compiler core must not include every extension by default. Extensions become separate, discoverable, side-loadable modules, and a third party must be able to add one without patching the core.

Why this is a release-blocking lever, not a nice-to-have

measurement value
ext/ vs lib/ 4,715 files vs 1,853 — extensions are ~72% of the tree
hardcoded loads in Runtime::loadCoreModules() 75, all unconditional
gen-0 spine 6,519 files ≈ lib + ext
phpc build on <?php echo "hello"; 14.8 MB binary

Runtime::loadCoreModules() instantiates all 75 modules on every run regardless of what the script touches, and everything links into every binary.

The extension boundary is the gen-0 problem. The spine that takes ~4.6 h and OOMs (#22642) is essentially lib + ext; removing extensions from the default core takes it from ~6,500 files to ~1,850 before any other optimisation. It is also the same partition a split-TU build needs (#23018/#23033 measured the spine as ~96% partitionable — only 3–4% of intra-spine class references cross a directory boundary).

Direction

  1. Discovery over hardcoding — a per-extension manifest (name, provided symbols, dependencies, capability rows) enumerated at build time, replacing the 75 constructor calls.
  2. Opt-in linking — the extension set is a property of the build (phpc.json / CLI) with a small default; --with-ext= / --without-ext=.
  3. Link what is reachable, with an explicit override for dynamic use ($fn = 'curl_init').
  4. No core → ext imports — dependencies point ext → core, never the reverse; core-level hooks live in lib/ with extensions implementing an interface.
  5. Per-extension translation units, following the helper-runtime cache model (lib/AOT/HelperRuntimeCache.php): a crash resumes at the failing unit, one edit re-emits one unit.

Boundary hazard

An unbound cross-module call lowers to __value__writeNull with no diagnostic (#579) — a silent miscompile rather than a missing-symbol error. Any work moving module edges must run with PHP_COMPILER_REPORT_EXTERNAL_STUBS=1 (#22967) and treat stub reports as failures.

OPEN: which extensions form the default set? Proposal: those the language itself leans on — standard, spl, types, ctype, hash, random — with everything else opt-in.

Done when: a script that never calls into an extension pays nothing for it — not in link time, not in binary size, not in startup — demonstrated by before/after numbers for gen-0 file count, hello-world binary size and cold phpc build wall time; and a new extension can be added by creating ext/<name>/ alone, with no core edit.

Rule: .cursor/rules/extensions-sideloaded.mdc. Plan: #23474.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions