Skip to content

add the extension api - #58

Merged
nvms merged 1 commit into
mainfrom
extension-api
Sep 10, 2026
Merged

add the extension api#58
nvms merged 1 commit into
mainfrom
extension-api

Conversation

@nvms

@nvms nvms commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Third-party extensions, written in C against include/zphp_extension.h, can now register functions, classes with native methods and constants, interfaces, exception classes, global constants, ini defaults, and resource types with destructors. The same source builds as a shared library (zphp --extension=PATH, or ZPHP_EXTENSION_DIR) or gets compiled into zphp (zig build -Dextension=path.c). Values cross the boundary as opaque handles, and the API is a table of function pointers handed to the extension's entry point, so extensions never link against zphp symbols and the runtime's internals can keep changing. An ABI version in the descriptor rejects mismatches at load time, as do duplicate names, conflicts with builtins, a missing entry point, and a failing module init.

A PHP call reaches an extension through the ordinary native table and a resolved trampoline. With 2M calls each in ReleaseFast, a builtin abs() takes 210ms, an extension function 227ms, and a PHP function 68ms; the trampoline is about 8ns per call.

Extensions get module, worker, and request lifecycle hooks plus a request-local and a worker-local data slot per VM. Resources are destroyed on unset, scope exit, exception unwinding, request end, and process exit. tests/extensions/run builds the demo extension with zig cc, checks every API path and every rejection, drives zphp serve for request isolation and resource cleanup over repeated requests, and with STATIC=1 builds a zphp with the demo compiled in and runs the same script. A new extensions CI job runs both.

Two fixes came out of the serve tests. Every served request saw DateTime::ATOM as an empty string and enum cases such as RoundingMode::HalfEven as null, because the per-request reset cleared the constants of builtin classes as well as user classes; builtin classes now keep their state across resets, and the serve test asserts it. And the per-VM extension state lives on the heap-allocated inline cache rather than on the VM struct: adding three fields to VM shifted field offsets and cost 36% on the closures micro and 22% on loops in a paired run, which moving them undid.

Paired micros against main (make bench-compare --micro, min of 7 interleaved runs, ms):

benchmark        base    head   ratio
array_ops        70.0    72.2   1.031
closures        123.4   126.2   1.023
fibonacci       340.0   335.9   0.988
loops           228.3   229.4   1.005
objects         123.1   122.0   0.991
string_ops       71.8    69.5   0.969
geomean                        1.001

Closes #23.

@nvms
nvms merged commit 53fbacb into main Sep 10, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add safe high-performance extension API

1 participant