← All docs

Shared Libraries (cdylib)

Compile PHP functions into a C-callable shared library with --emit cdylib and #[Export].

--emit cdylib compiles a PHP file into a loadable shared library instead of a standalone executable. Functions marked with the #[Export] attribute become C-ABI symbols under their unmangled PHP names, so any language with a C FFI (C, Rust, Python ctypes, Go cgo, …) can dlopen the library and call into compiled PHP.

Supported on every elephc target: macOS aarch64 (.dylib), Linux aarch64 and Linux x86_64 (.so).

Building a cdylib

elephc --emit cdylib auth.php
# Linux:  auth.php -> libauth.so
# macOS:  auth.php -> libauth.dylib

The output follows the conventional lib<stem>.so / lib<stem>.dylib naming that dlopen(3) and -l linker flags expect. The aliases --emit dylib and --emit shared are accepted.

A cdylib has no main entry point and runs no top-level statements at load time — the host drives everything through the exported functions.

Exporting functions

<?php

#[Export]
function validate_token(string $token): int {
    if (strlen($token) >= 8) {
        return 0;
    }
    return 1;
}

#[Export]
function add_i64(int $a, int $b): int {
    return $a + $b;
}

// Not exported: internal helpers stay invisible to the host.
function internal_helper(): int {
    return 42;
}

Only top-level functions can be exported — methods, closures, and arrow functions are not eligible. The fully-qualified spelling #[\Elephc\Export] is also accepted.

In --emit executable mode #[Export] attributes are ignored with a warning.

v1 type marshaling

PHP typeC parameterC return
intint64_tint64_t
floatdoubledouble
boolint64_t (0 or 1)int64_t
stringconst char *ptr, size_t len (two arguments)not supported in v1
voidvoid

Signatures outside this set (arrays, objects, callables, nullables, variadic or by-reference parameters) are rejected at compile time with a diagnostic. String returns arrive in a later iteration together with host-side ownership through elephc_free.

Lifecycle entry points

Every cdylib also exports four C-callable lifecycle symbols:

int32_t     elephc_init(void);        // call once after dlopen; 0 = success
void        elephc_shutdown(void);    // call before dlclose
const char *elephc_last_error(void);  // NULL when no error is recorded
void        elephc_free(void *ptr);   // release elephc-owned pointers (v1: no-op)

In v1 these are inexpensive stubs — runtime state is zero-initialized BSS, so elephc_init reports success without extra work — but hosts should call them anyway so code keeps working as later versions add real setup and teardown.

Minimal C host

#include <dlfcn.h>
#include <stdint.h>
#include <stdio.h>

int main(void) {
    void *lib = dlopen("./libauth.so", RTLD_NOW | RTLD_LOCAL);
    int32_t (*init)(void) = dlsym(lib, "elephc_init");
    int64_t (*add)(int64_t, int64_t) = dlsym(lib, "add_i64");
    int32_t (*validate)(const char *, size_t) = dlsym(lib, "validate_token");

    init();
    printf("%lld\n", (long long)add(40, 2));            // 42
    printf("%d\n", validate("supersecret", 11));        // 0
    return 0;
}
cc -o host host.c -ldl   # -ldl needed on Linux only
./host

See examples/cdylib/ for a complete runnable demo.

Symbol visibility

On ELF targets the dynamic symbol table of the produced .so contains only the public ABI: the #[Export] trampolines and the four lifecycle entry points. Every internal symbol — runtime helpers, buffers, data-section constants, non-exported PHP functions — is emitted with hidden visibility. This keeps internal state non-preemptible, so two elephc cdylibs loaded into the same process never alias each other’s runtime, and it keeps dlsym lookups fast and unpolluted.

Internally, cdylib code generation runs in PIC mode: global data references go through the GOT (@GOTPCREL on x86_64, :got:/:got_lo12: on AArch64) so the dynamic loader can relocate the library anywhere in the address space.

v1 limitations

  • No string, array, object, callable, or nullable return values.
  • No exception propagation to the host — a PHP fatal terminates the process.
  • Single-threaded hosts only: the runtime has no locking around its heap.
  • One PHP source file per cdylib (includes/requires work as in executables).