Skip to content

runtime: PIC package composition (decoupled allocator + dynamic-linking loader + unbounded/freed host returns)#33

Merged
colinrozzi merged 1 commit into
mainfrom
pic-composition
Jul 10, 2026
Merged

runtime: PIC package composition (decoupled allocator + dynamic-linking loader + unbounded/freed host returns)#33
colinrozzi merged 1 commit into
mainfrom
pic-composition

Conversation

@colinrozzi

Copy link
Copy Markdown
Owner

PIC package composition — decoupled allocator, dynamic-linking loader, unbounded+freed host returns

The substrate for packaging code and importing it across packages: replace each package's baked-in dlmalloc with a shared, position-independent (PIC) allocator side module, and make the runtime a mini dynamic linker so an allocator + a package share one linear memory without their stacks/data colliding.

What's in it

  • pack-guest: setup_guest! links an imported-allocator shim (pack:alloc) instead of embedding dlmalloc; __import_impl frees guest-allocated host-return buffers.
  • packages/pack-alloc: PIC dlmalloc side module — real free, bounded memory.
  • runtime: PIC loader (per-instance Memory+Table, disjoint __memory_base, package __stack_pointer, GOT heap globals, __wasm_call_ctors) on both the sync path and the async/theater host-fn path; host marshalling moved off get_export("memory") to a host-owned Memory.
  • host returns: async host fns (theater's large returns) guest-allocate on the heap — UNBOUNDED — and are freed after decode via an ownership bit in the import status (1=guest-owned/free, 0=host-owned scratch). Sync fns keep the bounded fixed buffer.

Verification

  • pic_composition (10 tests): every value shape round-trips through the shared in-wasm allocator, concurrent disjoint allocation, async large-return guest-allocation, and a teeth-verified no-leak regression (async_host_fn_large_returns_do_not_leak: 60 large async returns — memory grows ~15 MiB without the free, ~0 with it; verified it fails without the free).
  • Full workspace tests, clippy -D warnings, and fmt all green.
  • Rebased cleanly onto v0.7.3; collapsed to one commit. CI now builds the PIC side-modules (from each package dir, for the .cargo/config link flags) before the test step.

⚠️ HELD — do not merge yet

This is the fleet-committing PIC feature. Two gates before merge + release:

  1. theater-dev soak test — bounded memory over thousands of calls against a long-lived actor (their adoption gate).
  2. Confirm the sync/async split — this design assumes all of theater's large returns ride the async seam (func_async*, unbounded). Sync host fns (func_typed*) keep the ~32KB fixed-buffer cap, which is PIC-layout-safe. If any theater host fn returns >32KB via a sync registration, it needs to move to async.

Merging this does not publish (version stays 0.7.3); the release/publish is a separate, gated step after the soak.

🤖 Generated with Claude Code

@colinrozzi colinrozzi force-pushed the pic-composition branch 2 times, most recently from 8abedb6 to 4d02643 Compare July 9, 2026 14:11
…ic-linking loader, unbounded+freed host returns

Replaces each package's baked-in dlmalloc with a shared, position-independent
allocator side module, and teaches the runtime to act as a mini dynamic linker
so an allocator + a package (and, ahead, package-to-package imports) share one
linear memory without their stacks/data colliding — the substrate for packaging
code and importing it across packages.

- pack-guest: setup_guest! links an imported-allocator shim (pack:alloc) instead
  of embedding dlmalloc; __import_impl frees guest-allocated host-return buffers.
- packages/pack-alloc: PIC dlmalloc side module (real free, bounded memory).
- runtime: PIC loader (per-instance Memory+Table, disjoint __memory_base, package
  __stack_pointer, GOT heap globals, GOT.mem.__data_end resolution,
  __wasm_apply_data_relocs + __wasm_call_ctors) on both the sync path and the
  async/theater host-fn path; host marshalling moved off get_export(memory) to a
  host-owned Memory.
- host returns: async host fns (theater's large returns) guest-allocate on the
  heap — UNBOUNDED — and are freed after decode via an ownership bit in the import
  status (1=guest-owned/free, 0=host-owned scratch); sync fns keep the bounded
  fixed buffer. Teeth-verified no-leak regression.

Three fixes from theater-dev's soak of a real (mesh) actor:
- Loader calls __wasm_apply_data_relocs before ctors — else stored data pointers
  (format!()'s static &str fragments) keep raw offsets and read blank.
- pack-guest 'pic' feature defines placeholder __heap_base/__data_end so PIC side
  modules link on rustc > 1.92 (which injects those exports; lld can't satisfy
  them under -shared). PIC actors enable packr-guest/pic.
- Loader resolves GOT.mem.__data_end (which real actors' dep trees import) to
  __memory_base + the module's exported offset; without it, instantiation fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@colinrozzi colinrozzi merged commit 43c3f41 into main Jul 10, 2026
2 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.

1 participant