Skip to content

Commit

Permalink
Create plugin-api crate and move most of core's logic into it (#795)
Browse files Browse the repository at this point in the history
* Create plugin-api crate and move most of core's logic into it

* Add rust to example usage

Co-authored-by: Saúl Cabrera <[email protected]>

* Move dependency out of workspace

---------

Co-authored-by: Saúl Cabrera <[email protected]>
  • Loading branch information
jeffcharles and saulecabrera authored Oct 31, 2024
1 parent dbb3c32 commit d4d6f07
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 193 deletions.
11 changes: 9 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"crates/javy",
"crates/core",
"crates/cli",
"crates/plugin-api",
"crates/test-macros",
"crates/runner",
"fuzz",
Expand All @@ -21,7 +22,6 @@ wasmtime = "19"
wasmtime-wasi = "19"
wasi-common = "19"
anyhow = "1.0"
once_cell = "1.20"
javy = { path = "crates/javy", version = "3.0.2-alpha.1" }
tempfile = "3.13.0"
uuid = { version = "1.10", features = ["v4"] }
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ docs:
test-javy:
CARGO_TARGET_WASM32_WASIP1_RUNNER="wasmtime --dir=." cargo hack test --package=javy --target=wasm32-wasip1 --each-feature -- --nocapture

test-plugin-api:
CARGO_TARGET_WASM32_WASIP1_RUNNER="wasmtime --dir=." cargo hack test --package=javy-plugin-api --target=wasm32-wasip1 --each-feature -- --nocapture

test-core:
CARGO_TARGET_WASM32_WASIP1_RUNNER="wasmtime" cargo test --package=javy-core --target=wasm32-wasip1 -- --nocapture

Expand All @@ -44,14 +47,18 @@ test-wpt:
npm install --prefix wpt
npm test --prefix wpt

tests: test-javy test-core test-runner test-cli test-wpt
tests: test-javy test-plugin-api test-core test-runner test-cli test-wpt

fmt: fmt-javy fmt-core fmt-cli
fmt: fmt-javy fmt-plugin-api fmt-core fmt-cli

fmt-javy:
cargo fmt --package=javy -- --check
cargo clippy --package=javy --target=wasm32-wasip1 --all-targets -- -D warnings

fmt-plugin-api:
cargo fmt --package=javy-plugin-api -- --check
cargo clippy --package=javy-plugin-api --target=wasm32-wasip1 --all-targets -- -D warnings

fmt-core:
cargo fmt --package=javy-core -- --check
cargo clippy --package=javy-core --target=wasm32-wasip1 --all-targets -- -D warnings
Expand Down
5 changes: 2 additions & 3 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ crate-type = ["cdylib"]

[dependencies]
anyhow = { workspace = true }
javy = { workspace = true, features = ["export_alloc_fns", "json"] }
once_cell = { workspace = true }
javy-plugin-api = { path = "../plugin-api", features = ["json"] }
serde = { workspace = true }
serde_json = { workspace = true }

[features]
experimental_event_loop = []
experimental_event_loop = ["javy-plugin-api/experimental_event_loop"]
82 changes: 0 additions & 82 deletions crates/core/src/execution.rs

This file was deleted.

105 changes: 6 additions & 99 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
use anyhow::anyhow;
use javy::Config;
use javy::Runtime;
use namespace::import_namespace;
use once_cell::sync::OnceCell;
use javy_plugin_api::import_namespace;
use javy_plugin_api::javy::Config;
use shared_config::SharedConfig;
use std::io;
use std::io::Read;
use std::slice;
use std::str;

mod execution;
mod namespace;
mod shared_config;

const FUNCTION_MODULE_NAME: &str = "function.mjs";

static mut COMPILE_SRC_RET_AREA: [u32; 2] = [0; 2];

static mut RUNTIME: OnceCell<Runtime> = OnceCell::new();

import_namespace!("javy_quickjs_provider_v3");

/// Used by Wizer to preinitialize the module.
Expand Down Expand Up @@ -48,99 +36,18 @@ pub extern "C" fn initialize_runtime() {
shared_config.apply_to_config(&mut config);
}

let runtime = Runtime::new(config).unwrap();
unsafe {
RUNTIME.take(); // Allow re-initializing.
RUNTIME
.set(runtime)
// `unwrap` requires error `T` to implement `Debug` but `set`
// returns the `javy::Runtime` on error and `javy::Runtime` does not
// implement `Debug`.
.map_err(|_| anyhow!("Could not pre-initialize javy::Runtime"))
.unwrap();
};
}

/// Compiles JS source code to QuickJS bytecode.
///
/// Returns a pointer to a buffer containing a 32-bit pointer to the bytecode byte array and the
/// u32 length of the bytecode byte array.
///
/// # Arguments
///
/// * `js_src_ptr` - A pointer to the start of a byte array containing UTF-8 JS source code
/// * `js_src_len` - The length of the byte array containing JS source code
///
/// # Safety
///
/// * `js_src_ptr` must reference a valid array of unsigned bytes of `js_src_len` length
#[export_name = "compile_src"]
pub unsafe extern "C" fn compile_src(js_src_ptr: *const u8, js_src_len: usize) -> *const u32 {
// Use initialized runtime when compiling because certain runtime
// configurations can cause different bytecode to be emitted.
//
// For example, given the following JS:
// ```
// function foo() {
// "use math"
// 1234 % 32
// }
// ```
//
// Setting `config.bignum_extension` to `true` will produce different
// bytecode than if it were set to `false`.
let runtime = unsafe { RUNTIME.get().unwrap() };
let js_src = str::from_utf8(slice::from_raw_parts(js_src_ptr, js_src_len)).unwrap();

let bytecode = runtime
.compile_to_bytecode(FUNCTION_MODULE_NAME, js_src)
.unwrap();

// We need the bytecode buffer to live longer than this function so it can be read from memory
let len = bytecode.len();
let bytecode_ptr = Box::leak(bytecode.into_boxed_slice()).as_ptr();
COMPILE_SRC_RET_AREA[0] = bytecode_ptr as u32;
COMPILE_SRC_RET_AREA[1] = len.try_into().unwrap();
COMPILE_SRC_RET_AREA.as_ptr()
javy_plugin_api::initialize_runtime(config, |runtime| runtime).unwrap();
}

/// Evaluates QuickJS bytecode
///
/// # Safety
///
/// * `bytecode_ptr` must reference a valid array of unsigned bytes of `bytecode_len` length
// This will be removed as soon as we stop emitting calls to it in dynamically
// linked modules.
#[export_name = "eval_bytecode"]
pub unsafe extern "C" fn eval_bytecode(bytecode_ptr: *const u8, bytecode_len: usize) {
let runtime = RUNTIME.get().unwrap();
let bytecode = slice::from_raw_parts(bytecode_ptr, bytecode_len);
execution::run_bytecode(runtime, bytecode, None);
}

/// Evaluates QuickJS bytecode and optionally invokes exported JS function with
/// name.
///
/// # Safety
///
/// * `bytecode_ptr` must reference a valid array of bytes of `bytecode_len`
/// length.
/// * If `fn_name_ptr` is not 0, it must reference a UTF-8 string with
/// `fn_name_len` byte length.
#[export_name = "invoke"]
pub unsafe extern "C" fn invoke(
bytecode_ptr: *const u8,
bytecode_len: usize,
fn_name_ptr: *const u8,
fn_name_len: usize,
) {
let runtime = RUNTIME.get().unwrap();
let bytecode = slice::from_raw_parts(bytecode_ptr, bytecode_len);
let fn_name = if !fn_name_ptr.is_null() && fn_name_len != 0 {
Some(str::from_utf8_unchecked(slice::from_raw_parts(
fn_name_ptr,
fn_name_len,
)))
} else {
None
};
execution::run_bytecode(runtime, bytecode, fn_name);
javy_plugin_api::run_bytecode(bytecode, None);
}
11 changes: 11 additions & 0 deletions crates/plugin-api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

Initial release
18 changes: 18 additions & 0 deletions crates/plugin-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "javy-plugin-api"
version = "1.0.0-alpha.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
description = "APIs for Javy plugins"
homepage = "https://github.com/bytecodealliance/javy/tree/main/crates/javy-plugin-api"
repository = "https://github.com/bytecodealliance/javy/tree/main/crates/javy-plugin-api"
categories = ["wasm"]

[dependencies]
anyhow = { workspace = true }
javy = { workspace = true, features = ["export_alloc_fns"] }

[features]
experimental_event_loop = []
json = ["javy/json"]
37 changes: 37 additions & 0 deletions crates/plugin-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<div align="center">
<h1><code>javy-plugin-api</code></h1>
<p>
<strong>A crate for creating Javy plugins</strong>
</p>
<p>
<a href="https://docs.rs/javy-plugin-api"><img src="https://docs.rs/javy-plugin-api/badge.svg" alt="Documentation Status" /></a>
<a href="https://crates.io/crates/javy-plugin-api"><img src="https://img.shields.io/crates/v/javy-plugin-api.svg" alt="crates.io status" /></a>
</p>
</div>

Refer to the [crate level documentation](https://docs.rs/javy-plugin-api) to learn more.

Example usage:

```rust
use javy_plugin_api::import_namespace;
use javy_plugin_api::javy::Config;

// Dynamically linked modules will use `my_javy_plugin_v1` as the import
// namespace.
import_namespace!("my_javy_plugin_v1");

#[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).unwrap();
}
```

## Publishing to crates.io

To publish this crate to crates.io, run `./publish.sh`.
5 changes: 5 additions & 0 deletions crates/plugin-api/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

set -e

cargo publish --target=wasm32-wasip1
Loading

0 comments on commit d4d6f07

Please sign in to comment.