Skip to content

feat: allow extending hyperlight-js-runtime with custom native modules#49

Open
simongdavies wants to merge 3 commits intohyperlight-dev:mainfrom
simongdavies:extend-runtime-with-native-modules
Open

feat: allow extending hyperlight-js-runtime with custom native modules#49
simongdavies wants to merge 3 commits intohyperlight-dev:mainfrom
simongdavies:extend-runtime-with-native-modules

Conversation

@simongdavies
Copy link
Contributor

@simongdavies simongdavies commented Mar 12, 2026

Resolves #48

Overview

Adds an extension system that lets downstream crates add custom native modules, custom globals, and override built-in modules — without forking the runtime.

Architecture

Three macros, one init sequence:

JsRuntime::new()
  ├─ Step 1: globals::setup()         — install built-in globals (console, print, require)
  │                                      console + print are writable at this stage
  ├─ Step 2: custom_globals!(...)     — extender adds/extends globals (e.g. console.warn)
  ├─ Step 3: globals::freeze()        — Object.freeze(console), lock print
  └─ Module loader ready              — custom modules take priority over built-ins

native_modules! — Custom native modules

Register Rust-implemented modules that guest JS can import:

hyperlight_js_runtime::native_modules! {
    "math" => js_math,
    "io"   => js_custom_io,   // overrides built-in io
}
  • Custom modules take priority over built-ins with the same name
  • io, crypto, console can be overridden
  • require cannot be overridden (panics — it's core infrastructure)
  • Lazy initialization via #[no_mangle] + extern "Rust" linkage

custom_globals! — Custom global objects

Register global objects (constructors, polyfills, constants) without import:

fn setup_text_encoding(ctx: &rquickjs::Ctx<'_>) -> rquickjs::Result<()> {
    ctx.globals().set("TextEncoder", TextEncoder::constructor(ctx)?)?;
    Ok(())
}

hyperlight_js_runtime::custom_globals! {
    setup_text_encoding,
}
  • Runs after built-in globals, before freeze
  • Can extend console (add warn, error, info, debug)
  • Both Rust classes (#[rquickjs::class]) and JS polyfills (ctx.eval()) supported

Globals freeze

After custom_globals! runs, built-in globals are locked down:

  • consoleObject.freeze() (no new properties, no modifications)
  • print → non-writable, non-configurable
  • require → already non-configurable from setup via Property::from()

Handler code cannot tamper with any of these.

Runtime as a library

hyperlight-js-runtime now exposes a [lib] target. Extender crates depend on it and provide a binary that links everything together. The HYPERLIGHT_JS_RUNTIME_PATH build-time env var tells hyperlight-js to embed the custom binary.

Guest infrastructure (entry point, host function dispatch, libc stubs) moved from src/main/ to src/guest/ and is provided by the lib — no copying needed.

Key changes

File Change
src/hyperlight-js-runtime/src/modules/mod.rs NativeModuleLoader, register_native_module, native_modules!, custom_globals!, setup_custom_globals()
src/hyperlight-js-runtime/src/lib.rs 3-step init: setupcustom_globalsfreeze
src/hyperlight-js-runtime/src/globals/console.rs Console as extensible Object (not frozen module namespace)
src/hyperlight-js-runtime/src/globals/print.rs Print as writable global (frozen after custom_globals)
src/hyperlight-js-runtime/src/globals/freeze.rs New — freezes console + print after custom_globals
src/hyperlight-js-runtime/src/globals/mod.rs Added freeze() public function
src/hyperlight-js-runtime/src/main.rs Empty native_modules! {} + custom_globals! {}
src/hyperlight-js-runtime/src/guest/ Moved from src/main/hyperlight.rs — lib-provided guest infra
src/hyperlight-js/build.rs HYPERLIGHT_JS_RUNTIME_PATH support
docs/extending-runtime.md Full guide: quick start, native CLI testing, API reference, custom globals

Tests

  • 22 unit tests in tests/native_modules.rs: loader resolution, custom modules, built-in override, require protection, custom_globals! macro, console extensions, freeze behaviour
  • 6 full-pipeline E2E tests: build fixture binary → run handler.js → verify output (custom modules, builtins + custom together, console.log, custom globals, globals + modules combined)
  • 3 host-side integration tests in src/hyperlight-js/tests/native_modules.rs: HYPERLIGHT_JS_RUNTIME_PATH embedding
  • Fixture crates: native_math (shared lib) + extended_runtime (binary with custom modules + globals)

@simongdavies simongdavies added the kind/enhancement New feature or improvement label Mar 12, 2026
@simongdavies simongdavies force-pushed the extend-runtime-with-native-modules branch 8 times, most recently from acaa72b to d721317 Compare March 14, 2026 08:56
Resolves hyperlight-dev#48

Add a registration-based system for extending the JS runtime with custom
native (Rust-implemented) modules that run inside the Hyperlight guest VM.

Key changes:

- hyperlight-js-runtime/Cargo.toml: Add [lib] target so the runtime can
  be used as a library dependency by extender crates.

- modules/mod.rs: Add global CUSTOM_MODULES registry with
  register_native_module() and builtin_module_names(). NativeModuleLoader
  checks custom registry first, falls back to built-in modules. Panics
  if custom module name conflicts with a built-in.

- native_modules! macro: Generates init_native_modules() (#[no_mangle])
  that registers custom modules. Called automatically via spin::Once on
  first NativeModuleLoader access — no explicit init needed.

- guest/mod.rs: Move hyperlight guest entry point (hyperlight_main,
  guest_dispatch_function, Host impl, stubs) from main/hyperlight.rs
  into the lib behind cfg(hyperlight). Extender binaries get all guest
  infrastructure for free by depending on the lib.

- hyperlight-js/build.rs: Add HYPERLIGHT_JS_RUNTIME_PATH env var override
  to embed custom runtime binaries instead of the default.

- host.rs: Restore original Host trait only (no FsHost extraction).

Testing:
- 13 unit/integration tests in hyperlight-js-runtime (loader, registry,
  macro, override prevention, E2E with native CLI fixture binary)
- 3 Hyperlight VM integration tests in hyperlight-js (#[ignore], run via
  just test-native-modules)
- Extended runtime fixture crate with shared native_math module
- just test-native-modules recipe for full hyperlight pipeline

Docs:
- docs/extending-runtime.md with quick start, host-side usage, native
  testing, API reference, and architecture diagram
@simongdavies simongdavies force-pushed the extend-runtime-with-native-modules branch from d721317 to 3e43831 Compare March 14, 2026 10:12
Add custom_globals! macro alongside native_modules! to allow extender
crates to register global JS objects (TextEncoder, TextDecoder,
polyfills, constants) without modifying hyperlight-js-runtime.

- New custom_globals! macro in modules/mod.rs (same #[no_mangle] + extern
- setup_custom_globals() bridge called in JsRuntime::new() after
  built-in globals
- Default empty custom_globals! {} in base runtime main.rs
- Test fixture with globalThis.CUSTOM_GLOBAL_TEST = 42
- 6 new tests: unit e2e + full pipeline (standalone, coexist with
  builtins, combined with native modules)
- Documentation in docs/extending-runtime.md
- Allow overriding built-in modules (io, crypto, console) via
  native_modules! — custom modules take priority over built-ins.
  The require module is protected and cannot be overridden.
- Make console and print globals writable during init so
  custom_globals! can extend them (e.g. add console.warn/error).
- Freeze console (Object.freeze) and print (non-writable) after
  custom_globals! runs — handler code cannot tamper with them.
- 3-step init in JsRuntime::new: setup → custom_globals → freeze.
- New globals/freeze.rs module for post-init lockdown.
- Tests: require override rejection, console extension via
  custom_globals, freeze verification, console.log after freeze.
- Updated docs/extending-runtime.md with override rules.

Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/enhancement New feature or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow extending hyperlight-js-runtime with custom native modules

1 participant