Skip to content

Add hooks for statically linking extensions into the WASI build - #87

Open
bendytree wants to merge 2 commits into
electric-sql:REL_17_5_WASM-pglitefrom
bendytree:feat/wasi-static-extension-hooks
Open

Add hooks for statically linking extensions into the WASI build#87
bendytree wants to merge 2 commits into
electric-sql:REL_17_5_WASM-pglitefrom
bendytree:feat/wasi-static-extension-hooks

Conversation

@bendytree

Copy link
Copy Markdown

The WASI flavor has no dynamic loading; its dlopen shim resolves symbols from a hardcoded table covering only dict_snowball and plpgsql, so extensions can't be added without editing the shim.

This PR adds two overridable (weak) hooks to sdk_port-wasi-dlfcn.c:

  • pglite_extra_dlsym(symbol) — consulted when the built-in table misses, so a linked object can supply extension symbols (e.g. a table generated from llvm-nm over the extension's static archive);
  • pglite_extra_dlopen(filename) — called on every dlopen, so that object can run the extension's (renamed) _PG_init.

…and passes ${EXTRA_PG_LIBS:-} at the end of the pglite.wasi link line in pglite-wasm/build.sh, so a build can append static extension archives without patching the script. The weak defaults keep the stock build's behavior unchanged.

I use this to link pgvector 0.8.0 into pglite.wasi (renaming _PG_init/Pg_magic_func via -D to avoid colliding with plpgsql's statically linked copies, plus a 327-entry generated dlsym table). CREATE EXTENSION vector, the vector type, distance operators, and HNSW/IVFFlat index builds all work — verified under node/wazero WASI hosts and in a native wasm2c iOS embedding (https://github.com/bendytree/pglite-ios), where the full build recipe lives in module/.

Note: includes the commit from #86 (the dlopen fix) as its base, since both touch dlopen().

Joshua Wright added 2 commits August 26, 2026 10:31
dltab was a tentative one-element array ("dict_t* dltab[]") indexed past
its end, and slot 0 was never assigned: the first library was stored at
dltab[1] while the lookup loop read dltab[0..dltab_index). Every dlopen
lookup therefore dereferenced a NULL dict, i.e. guest address 0.

That happens to be harmless while address 0 holds zeros (the dict's len
field reads as 0), which is why the socket-file transport never noticed.
With the CMA wire channel, live request bytes sit at the bottom of linear
memory, so the NULL-dict read picks up garbage lengths and entry pointers
and traps — reproducible as a crash on the first CREATE EXTENSION issued
over CMA.

Make dltab a real fixed-size array, keep every slot initialized, check
the existing-library branch against index >= 0 (slot 0 is valid), and
return stable 1-based handles.
The WASI flavor has no dynamic loading; its dlopen shim resolves symbols
from a hardcoded table covering only dict_snowball and plpgsql, so
extensions like pgvector cannot be added without editing the shim.

Add two overridable (weak) hooks:

- pglite_extra_dlsym(symbol): consulted when the built-in table misses,
  letting a linked object supply extension symbols (e.g. a table
  generated from llvm-nm over the extension's static library);
- pglite_extra_dlopen(filename): called for every dlopen, letting the
  object run the extension's renamed _PG_init.

Also pass ${EXTRA_PG_LIBS:-} at the end of the pglite.wasi link line so
a build can append static extension archives without patching build.sh.
The weak defaults keep the stock build byte-identical in behavior.

Used to link pgvector 0.8.0 (with a 327-entry generated dlsym table)
into pglite.wasi; CREATE EXTENSION vector, the vector type, distance
operators, and HNSW/IVFFlat index builds all work, verified natively on
iOS/macOS and under wazero/node WASI hosts.
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.

1 participant