feat: allow extending hyperlight-js-runtime with custom native modules#49
feat: allow extending hyperlight-js-runtime with custom native modules#49simongdavies wants to merge 7 commits intohyperlight-dev:mainfrom
Conversation
d721317 to
3e43831
Compare
7dcc0a9 to
2741c45
Compare
There was a problem hiding this comment.
Pull request overview
Adds an extension mechanism to hyperlight-js-runtime so downstream crates can register custom native Rust modules and custom globals (and optionally override built-ins) without forking, and wires host-side embedding to support a custom runtime binary via HYPERLIGHT_JS_RUNTIME_PATH.
Changes:
- Introduces a custom native-module registry +
native_modules!macro and acustom_globals!macro, and updatesJsRuntime::new()init sequence to: built-in globals → custom globals → freeze. - Adds host build-time support for embedding a custom runtime binary (
HYPERLIGHT_JS_RUNTIME_PATH) and ajust test-native-modulespipeline to exercise it. - Adds fixtures, docs, and tests covering custom modules/globals, console extensibility, and post-init freezing behavior.
Reviewed changes
Copilot reviewed 19 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hyperlight-js/tests/native_modules.rs | Ignored VM integration tests validating custom modules/globals via embedded custom runtime. |
| src/hyperlight-js/build.rs | Adds HYPERLIGHT_JS_RUNTIME_PATH override for embedding a custom runtime binary. |
| src/hyperlight-js-runtime/tests/native_modules.rs | Unit/E2E tests for registry, macros, globals setup, freeze behavior, and fixture pipeline. |
| src/hyperlight-js-runtime/tests/fixtures/native_math/src/lib.rs | Fixture native module providing add/multiply. |
| src/hyperlight-js-runtime/tests/fixtures/native_math/Cargo.toml | Fixture crate manifest for native-math. |
| src/hyperlight-js-runtime/tests/fixtures/extended_runtime/src/main.rs | Fixture binary using native_modules! and custom_globals! to extend the runtime. |
| src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.toml | Fixture binary manifest depending on the runtime lib + fixture module. |
| src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.lock | Lockfile for the standalone fixture build. |
| src/hyperlight-js-runtime/src/modules/mod.rs | Implements custom module registry, lazy init symbol, unified loader, and both extension macros. |
| src/hyperlight-js-runtime/src/main.rs | Provides empty native_modules!{} / custom_globals!{} symbols for the upstream binary. |
| src/hyperlight-js-runtime/src/lib.rs | Exposes runtime as a library and adds 3-step init (setup → custom globals → freeze). |
| src/hyperlight-js-runtime/src/guest/stubs/srand.rs | Adds libc stub for srand. |
| src/hyperlight-js-runtime/src/guest/stubs/mod.rs | Registers the new/relocated libc stub modules. |
| src/hyperlight-js-runtime/src/guest/stubs/localtime.rs | Adds libc stub implementation for localtime_r. |
| src/hyperlight-js-runtime/src/guest/stubs/io.rs | Adds libc stubs for putchar/fflush. |
| src/hyperlight-js-runtime/src/guest/stubs/clock.rs | Adds libc stubs for clock_gettime/gettimeofday using host time. |
| src/hyperlight-js-runtime/src/guest/mod.rs | Moves guest entrypoint/plumbing into the library (cfg(hyperlight)). |
| src/hyperlight-js-runtime/src/globals/print.rs | Makes print writable during custom-globals phase (frozen later). |
| src/hyperlight-js-runtime/src/globals/mod.rs | Adds freeze() stage API and wires in new freeze module. |
| src/hyperlight-js-runtime/src/globals/freeze.rs | New freeze stage: freezes console object and locks down print. |
| src/hyperlight-js-runtime/src/globals/console.rs | Installs console as an extensible object (not module namespace) before freezing. |
| src/hyperlight-js-runtime/Cargo.toml | Adds [lib] target and makes the bin explicitly point to src/main.rs. |
| docs/extending-runtime.md | Adds documentation for extending runtime with custom modules/globals and embedding workflow. |
| Justfile | Adds test-native-modules pipeline and updates clean/test recipes accordingly. |
| Cargo.toml | Excludes the extended runtime fixture from the workspace. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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
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>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
75c23b9 to
48a251a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 25 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
48a251a to
5915fb7
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 25 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
5915fb7 to
d04119c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 25 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
d04119c to
5f6e309
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 25 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
5f6e309 to
ab48a82
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 25 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Copy fn pointer out of CUSTOM_MODULES lock before calling module declaration to prevent potential deadlock (spin::Mutex not re-entrant) - Move cargo:rerun-if-env-changed=HYPERLIGHT_JS_RUNTIME_PATH before the match so Cargo tracks the env var even when unset; use canonical path for rerun-if-changed - Lock down globalThis.console binding (non-writable/non-configurable) after Object.freeze to prevent handler code replacing it entirely - Fix test doc comment: built-in overrides work except for require Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
ab48a82 to
779877f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 25 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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:
native_modules!— Custom native modulesRegister Rust-implemented modules that guest JS can
import:io,crypto,consolecan be overriddenrequirecannot be overridden (panics — it's core infrastructure)#[no_mangle]+extern "Rust"linkagecustom_globals!— Custom global objectsRegister global objects (constructors, polyfills, constants) without import:
console(addwarn,error,info,debug)#[rquickjs::class]) and JS polyfills (ctx.eval()) supportedGlobals freeze
After
custom_globals!runs, built-in globals are locked down:console→Object.freeze()(no new properties, no modifications)print→ non-writable, non-configurablerequire→ already non-configurable from setup viaProperty::from()Handler code cannot tamper with any of these.
Runtime as a library
hyperlight-js-runtimenow exposes a[lib]target. Extender crates depend on it and provide a binary that links everything together. TheHYPERLIGHT_JS_RUNTIME_PATHbuild-time env var tellshyperlight-jsto embed the custom binary.Guest infrastructure (entry point, host function dispatch, libc stubs) moved from
src/main/tosrc/guest/and is provided by the lib — no copying needed.Key changes
src/hyperlight-js-runtime/src/modules/mod.rsNativeModuleLoader,register_native_module,native_modules!,custom_globals!,setup_custom_globals()src/hyperlight-js-runtime/src/lib.rssetup→custom_globals→freezesrc/hyperlight-js-runtime/src/globals/console.rssrc/hyperlight-js-runtime/src/globals/print.rssrc/hyperlight-js-runtime/src/globals/freeze.rssrc/hyperlight-js-runtime/src/globals/mod.rsfreeze()public functionsrc/hyperlight-js-runtime/src/main.rsnative_modules! {}+custom_globals! {}src/hyperlight-js-runtime/src/guest/src/main/hyperlight.rs— lib-provided guest infrasrc/hyperlight-js/build.rsHYPERLIGHT_JS_RUNTIME_PATHsupportdocs/extending-runtime.mdTests
tests/native_modules.rs: loader resolution, custom modules, built-in override,requireprotection,custom_globals!macro, console extensions, freeze behavioursrc/hyperlight-js/tests/native_modules.rs:HYPERLIGHT_JS_RUNTIME_PATHembeddingnative_math(shared lib) +extended_runtime(binary with custom modules + globals)