From 2a46db3237b0d3ae81a9ea7c7e12da3addf996e8 Mon Sep 17 00:00:00 2001 From: Jeffrey Charles Date: Wed, 20 Nov 2024 09:27:58 -0500 Subject: [PATCH] Update docs for plugins (#832) * Update docs for plugins * Use headings for the plugin API * Delete accidentally added Wasm file --- docs/docs-contributing-architecture.md | 58 +++++++++---- docs/docs-using-dynamic-linking.md | 28 +++--- docs/docs-using-extending.md | 116 +++++++++++++++++++++---- 3 files changed, 155 insertions(+), 47 deletions(-) diff --git a/docs/docs-contributing-architecture.md b/docs/docs-contributing-architecture.md index 78bde09b..3beaeb3a 100644 --- a/docs/docs-contributing-architecture.md +++ b/docs/docs-contributing-architecture.md @@ -8,7 +8,8 @@ This document is intended to provide an overview of the architecture of `javy`. flowchart TD javy-cli --> wasm subgraph wasm[plugin.wasm] - javy-plugin --> javy + javy-plugin --> javy-plugin-api + javy-plugin-api --> javy javy --> rquickjs end ``` @@ -30,7 +31,7 @@ QuickJS that would be helpful, this is the place to add it. #### Example This is a contrived example of how to make a change. If I want to add -a configuuration to set a global variable called `javy_rocks` to `true`, I would +a configuration to set a global variable called `javy_rocks` to `true`, I would do the following: In `crates/javy/src/config.rs`: @@ -39,41 +40,56 @@ In `crates/javy/src/config.rs`: /// A configuration for [`Runtime`](crate::Runtime). #[derive(Debug)] pub struct Config { -+ pub(crate) set_javy_rocks: bool, ++ pub(crate) javy_rocks: bool, } impl Default for Config { /// Creates a [`Config`] with default values. fn default() -> Self { Self { -+ set_javy_rocks: false, ++ javy_rocks: false, } } } impl Config { + /// Sets `globalThis.javy_rocks` to `true`. -+ pub fn set_javy_rocks(&mut self) -> &mut Self { -+ self.set_javy_rocks = true; ++ pub fn javy_rocks(&mut self) -> &mut Self { ++ self.javy_rocks = true; + self + } } ``` +In `crates/javy/src/apis/javy_rocks.rs`: + +```rust +pub struct JavyRocks; + +impl Instrinsic for JavyRocks { + unsafe fn add_intrinsic(ctx: NonNull) { + register(Ctx::from_raw(ctx)).expect("registering Javy Rocks to succeed") + } +} + +fn register<'js>(this: Ctx<'js>) -> Result<()> { + let globals = this.globals(); + globals.set("javy_rocks", true); +} +``` + In `crates/javy/src/runtime.rs`: ```diff -+ context.with(|cx| { -+ if cfg.set_javy_rocks { -+ let globals = cx.globals(); -+ globals.set("javy_rocks", true); ++ if cfg.javy_rocks { ++ unsafe { ++ JavyRocks::add_intrinsic(ctx.as_raw()) + } -+ }); -+ ++ } ``` - -Read the `config` and call the appropriate methods on `context` to apply the +Define a new property on the `Config`, in `runtime`'s `build_from_config` add +the intrinsic if the config property is enabled, and use `context` to define the configuration. #### When to add a `cargo` feature @@ -110,9 +126,10 @@ You should gate your feature with a cargo feature if your feature/change: ### `javy-plugin` Gets compiled to `plugin.wasm` for use by the CLI and in environments for -running dynamically linked modules. This isn't intended to be used as a code -library by third parties. Contains logic for driving the `javy` crate for Wasm -modules generated by `javy-cli`. +running dynamically linked modules. This is the default plugin for Javy. +This isn't intended to be used as a code library by third parties. Defines a +an `initialize_runtime` function that uses a configuration structure to +allow the CLI to set various JS runtime configuration options. #### When to add a `cargo` feature @@ -122,6 +139,13 @@ this for the event loop because the current implementation has not been thoroughly vetted. We also need a build of Javy with event loop support to run a number of web platform tests for text encoding. +### `javy-plugin-api` + +Used by Javy plugins to provide common implementations for exports and custom +sections the plugin is expected to expose. This drives the APIs exposed by the +`javy` crate. For example, this crate adds a Wasm function export for `invoke` +to the plugin which is used for actually running the JavaScript. + ## `npm` packages ### `javy` diff --git a/docs/docs-using-dynamic-linking.md b/docs/docs-using-dynamic-linking.md index c6154c53..5680a07c 100644 --- a/docs/docs-using-dynamic-linking.md +++ b/docs/docs-using-dynamic-linking.md @@ -10,24 +10,30 @@ linked modules do not and will not execute in WebAssembly runtimes that do not meet these requirements. To successfully instantiate and run a dynamically linked Javy module, the -execution environment must provide a `javy_quickjs_provider_v` namespace for -importing that links to the exports provided by the `plugin.wasm` -module. Dynamically linked modules **cannot** be instantiated in environments -that do not provide this import. +execution environment must provide a collection of imports that match the +exports from a Javy plugin Wasm module (for example, +`canonical_abi_realloc` and `invoke`). The namespace for these imports must +match the import namespace specified by the Javy plugin Wasm module used to +generate the dynamically linked Wasm module (this is, if the plugin's import +namespace was `my_plugin_v1`, then the imports must be made available under the +module name `my_plugin_v1`). This value is available from the `import_namespace` +custom section in the Javy plugin module. You can also statically inspect the +imports of the dynamically linked Wasm module to determine the import +namespace. Dynamically linked modules **cannot** be instantiated in +environments that do not provide the required imports. Dynamically linked Javy modules are tied to QuickJS since they use QuickJS's bytecode representation. +#### Obtaining the default plugin module -#### Obtaining the plugin module +The `plugin.wasm` module is available as an asset on the Javy release you are +using. -The `plugin.wasm` module is available as an asset on the Javy -release you are using. +It can also be obtained by running `javy emit-plugin -o ` to write the +module into ``. -It can also be obtained by running `javy emit-plugin -o -` to write the module into ``. - -#### Creating and running a dynamically linked module througy the CLI +#### Creating and running a dynamically linked module through the CLI Run: diff --git a/docs/docs-using-extending.md b/docs/docs-using-extending.md index 954253a0..bc619048 100644 --- a/docs/docs-using-extending.md +++ b/docs/docs-using-extending.md @@ -1,28 +1,106 @@ # Extending If you want to use Javy for your own project, you may find that the existing -code is not sufficient since you may want to offer custom APIs or use different -branding for the CLI. The approach we'd recommend taking is to create your own -version of the `javy-cli` and `javy-plugin` crates (you could fork these if you -would like) and depend on the upstream version of the `javy` crate. You can add -your own implementations of custom JS APIs in your fork of the `javy-plugin` crate -or in a different crate that you depend on in your `javy-plugin` fork. If you find -that something is missing in the `javy` crate that you require to implement -something in your fork, we would appreciate it if you would open a GitHub issue -and consider making the change upstream instead of in your fork so all users of -the `javy` crate can benefit. +code is not sufficient since you may want to offer custom JavaScript APIs. The +approach we recommend taking is to create your own Javy plugin Wasm module. +This plugin module can then be specified when using the Javy CLI when building +Javy Wasm modules as a `-C plugin` flag when using `javy build`. + +To create your own Javy plugin Wasm module, create a new Rust project that +will compile to a library (that is, `cargo init --lib`). + +Your `Cargo.toml` should look like: + +```toml +[package] +name = "my-plugin-name" +version = "0.1.0" + +[lib] +name = "my_plugin_name" +crate-type = ["cdylib"] + +[dependencies] +javy-plugin-api = "1.0.0" +``` + +And `src/lib.rs` should look like: + +```rust +use javy_plugin_api::import_namespace; +use javy_plugin_api::javy::quickjs::prelude::Func; +use javy_plugin_api::javy::Config; + +import_namespace!("my_plugin_name"); + +#[export_name = "initialize_runtime"] +pub extern "C" fn initialize_runtime() { + let mut config = Config::default(); + config + .text_encoding(true) + .javy_stream_io(true); + + javy_plugin_api::initialize_runtime(config, |runtime| { + runtime + .context() + .with(|ctx| { + ctx.globals() + .set("isThisAPlugin", Func::from(|| "yes it is")) + }) + .unwrap(); + runtime + }) + .unwrap(); +} +``` + +You can then run `cargo build --target=wasm32-wasip1 --release` to create a +Wasm module. Then you need to run + +``` +javy init-plugin -o ` +``` + +which will validate and initialize the Javy runtime. This `javy init-plugin` +step is required for the plugin to be useable by the Javy CLI. See our documentation on [using complex data types in Wasm functions](./contributing-complex-data-types.md) for how to support Wasm functions that need to use byte arrays, strings, or structured data. -For a visual representation of how we expect forks to consume our crates: +## The full plugin API -```mermaid -flowchart TD - your-cli --> wasm - subgraph wasm[your_plugin.wasm] - your-plugin --> javy[upstream javy] - javy --> rquickjs - end -``` +This is the Wasm API the Javy CLI expects Javy plugins to expose. The +`javy-plugin-api` crate will export implementations of all required exported +functions except `initialize_runtime`. `import_namespace!` will define the +`import_namespace` custom section. + +### Exported Wasm functions + +#### `initialize_runtime() -> ()` + +This will initialize a mutable global containing the Javy runtime for use by +`compile_src` and `invoke`. + +#### `canonical_abi_realloc(orig_ptr: i32, orig_len: i32, new_ptr: i32, new_len: i32) -> ptr: i32` + +This is used to allocate memory in the plugin module. + +#### `compile_src(src_ptr: i32, src_len: i32) -> bytecode_wide_ptr: i32` + +This is used to compile JavaScript source code to QuickJS bytecode. The return +pointer points to a tuple of `(bytecode_ptr: i32, bytecode_len: i32)` in the +plugin instance's linear memory. + +#### `invoke(bytecode_ptr: i32, bytecode_len: i32, fn_name_ptr: i32, fn_name_len: i32) -> ()` + +This is used to evaluate the JavaScript code and optionally to call an exported +JS function if `fn_name_ptr` is not `0`. + +### Custom sections + +#### `import_namespace` + +Contains a UTF-8 encoded string. This is used to determine the namespace that +will be used for the Wasm imports in dynamically linked modules built with this +plugin.