diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 034cab04..78b32fab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,10 @@ jobs: components: clippy - name: Cargo clippy run: cargo clippy --all --all-targets --features full-async,bindgen + - name: Check no_std feature sets + run: | + cargo check -p rquickjs --no-default-features + cargo check -p rquickjs --no-default-features --features full-nostd msrv: # Check to see if rquickjs builds on minimal supported Rust version. diff --git a/Cargo.toml b/Cargo.toml index c0c83d2d..6422ffb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/DelSkayn/rquickjs.git" [workspace] +exclude = ["examples/zephyr"] members = [ "sys", "core", @@ -33,8 +34,8 @@ rquickjs-sys = { version = "0.11.0", path = "sys", default-features = false } rquickjs = { version = "0.11.0", path = "./", default-features = false } [dependencies] -indexmap-rs = { package = "indexmap", version = "2", optional = true } -either-rs = { package = "either", version = "1", optional = true } +indexmap-rs = { package = "indexmap", version = "2", optional = true, default-features = false } +either-rs = { package = "either", version = "1", optional = true, default-features = false } rquickjs-core = { workspace = true } rquickjs-macro = { workspace = true, optional = true } @@ -60,6 +61,23 @@ full-wasi = ["std", "chrono", "loader", "either", "indexmap", "macro", "phf"] # A version of full designed for wasm32-unknown-unknown and similar hosts full-wasm = ["full-wasi"] +# Portable feature set for no_std hosts with a global allocator. +full-nostd = [ + "chrono", + "loader", + "either", + "indexmap", + "macro", + "phf", + "half", + "futures", + "parallel", + "rust-alloc", +] + +# Build QuickJS against Zephyr's C headers and configuration. +zephyr = ["full-nostd", "bindgen", "rquickjs-core/zephyr"] + # Almost all features excluding "parallel" full-async = ["full", "futures"] diff --git a/README.md b/README.md index 852fc257..1d737d20 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,35 @@ See below for a list of supported platforms. | wasm32-wasip1 | ✅ | ✅ | ✅ | | wasm32-wasip2 | ✅ | ✅ | ✅ | | wasm32-unknown-unknown | ✅ | ✅* | ✅ | +| thumbv7m-none-eabi (Zephyr) | ❌ | ✅** | ✅ | | other | ❌ | ❌ | Unknown | \* `wasm32-unknown-unknown` is validated with workspace builds and a dedicated smoke example compile. Runtime embedding still depends on the host providing the generated wasm imports. +\** Zephyr is validated on the `mps2/an385` QEMU board. Bindings are generated during the Zephyr build. + +## `no_std` and Zephyr + +Disable default features to use rquickjs with `no_std + alloc`. The `full-nostd` feature enables the +portable high-level APIs, including async/futures support, macros, in-memory module loaders, +`parallel`, and the Rust global allocator. The default feature set continues to use `std`. + +```toml +rquickjs = { version = "0.11", default-features = false, features = ["full-nostd"] } +``` + +For Zephyr, use the `zephyr` feature. It also enables bindgen and the C integration required to use +Zephyr's generated configuration, libc, threading primitives, and compiler toolchain. + +```toml +rquickjs = { path = "../rquickjs", default-features = false, features = ["zephyr"] } +``` + +The [Zephyr example](examples/zephyr/README.md) runs two Zephyr threads against one runtime and +checks promises, the pending-job queue, SharedArrayBuffer, and 8/16/32/64-bit Atomics on QEMU. +Filesystem and dynamic-library module loading remain host facilities; use in-memory or custom +resolvers and loaders on embedded targets. + ## WebAssembly notes For browser-style or freestanding wasm hosts, prefer the `full-wasm` or `full-async-wasm` feature sets: diff --git a/core/Cargo.toml b/core/Cargo.toml index ef49c867..a5502852 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,16 +15,17 @@ features = ["full-async", "doc-cfg"] [dependencies] rquickjs-sys = { workspace = true } -hashbrown = { version = "0.17" } -phf = { version = "0.13", optional = true } -indexmap = { version = "2", optional = true } -either = { version = "1", optional = true } +hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"] } +phf = { version = "0.13", optional = true, default-features = false } +indexmap = { version = "2", optional = true, default-features = false } +either = { version = "1", optional = true, default-features = false } async-lock = { version = "3", optional = true, default-features = false } -chrono = { version = "0.4", optional = true } +chrono = { version = "0.4", optional = true, default-features = false, features = ["alloc"] } relative-path = { version = "2.0", optional = true, default-features = false, features = [ "alloc", ] } -half = { version = "2.7.1", optional = true } +half = { version = "2.7.1", optional = true, default-features = false } +spin = { version = "0.10", optional = true, default-features = false, features = ["mutex", "spin_mutex"] } [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] dlopen2 = { version = "0.8", optional = true } @@ -44,7 +45,7 @@ trybuild = "1" [features] default = ["std"] -std = ["relative-path?/std"] +std = ["relative-path?/std", "chrono?/clock"] # Almost all features excluding "parallel" and support for async runtimes full = ["std", "chrono", "loader", "dyn-load", "either", "indexmap", "half"] @@ -52,6 +53,9 @@ full = ["std", "chrono", "loader", "dyn-load", "either", "indexmap", "half"] # A version of full designed for wasm hosts without a native filesystem or dynamic loading. full-wasm = ["std", "chrono", "loader", "either", "indexmap", "half"] +# Portable feature set for no_std hosts with a global allocator. +full-nostd = ["chrono", "loader", "either", "indexmap", "half", "futures", "parallel", "rust-alloc"] + # Almost all features excluding "parallel" full-async = ["full", "futures"] @@ -64,9 +68,10 @@ chrono = ["dep:chrono"] # Use bindgen to generate bindings at compile-type # otherwise bundled bindings will be used bindgen = ["rquickjs-sys/bindgen"] +zephyr = ["rquickjs-sys/zephyr"] # Enable support of parallel execution -parallel = ["std", "tokio/rt-multi-thread"] +parallel = ["dep:spin", "tokio/rt-multi-thread"] # Enable user-defined module loader support loader = ["relative-path"] diff --git a/core/src/context/async.rs b/core/src/context/async.rs index fa5dd5f9..32bbdc2e 100644 --- a/core/src/context/async.rs +++ b/core/src/context/async.rs @@ -3,6 +3,8 @@ use super::{ owner::{ContextOwner, DropContext}, ContextBuilder, Intrinsic, }; +#[cfg(feature = "parallel")] +use crate::runtime::DropContextPtr; use crate::{markers::ParallelSend, qjs, runtime::AsyncRuntime, Ctx, Error, Result}; use core::{mem, ptr::NonNull}; @@ -100,9 +102,7 @@ impl DropContext for AsyncRuntime { } #[cfg(feature = "parallel")] { - self.drop_send - .send(ctx) - .expect("runtime should be alive while contexts life"); + self.drop_queue.lock().push(DropContextPtr(ctx)); return; } } diff --git a/core/src/context/owner.rs b/core/src/context/owner.rs index 25517996..7e5c3126 100644 --- a/core/src/context/owner.rs +++ b/core/src/context/owner.rs @@ -3,7 +3,16 @@ use core::ptr::NonNull; use crate::qjs; #[cfg(feature = "parallel")] -use std::sync::Arc; +use alloc::sync::Arc; + +#[cfg(feature = "parallel")] +struct ParallelContext(NonNull); + +// Access to the context is serialized by the owning runtime lock. +#[cfg(feature = "parallel")] +unsafe impl Send for ParallelContext {} +#[cfg(feature = "parallel")] +unsafe impl Sync for ParallelContext {} /// Trait to specify how to drop a context once it goes out of scope. /// Implemented on Runtime and AsyncRuntime. @@ -19,7 +28,7 @@ pub(crate) struct ContextOwner { #[cfg(not(feature = "parallel"))] ctx: NonNull, #[cfg(feature = "parallel")] - pub(crate) ctx: Arc>, + ctx: Arc, pub(crate) rt: R, } @@ -31,7 +40,7 @@ impl ContextOwner { #[cfg(feature = "parallel")] pub(crate) unsafe fn new(ctx: NonNull, rt: R) -> Self { Self { - ctx: Arc::new(ctx), + ctx: Arc::new(ParallelContext(ctx)), rt, } } @@ -43,7 +52,7 @@ impl ContextOwner { #[cfg(feature = "parallel")] pub(crate) fn ctx(&self) -> NonNull { - *self.ctx + self.ctx.0 } pub(crate) fn rt(&self) -> &R { diff --git a/core/src/runtime.rs b/core/src/runtime.rs index dd09a061..d5991b6a 100644 --- a/core/src/runtime.rs +++ b/core/src/runtime.rs @@ -19,6 +19,8 @@ use alloc::boxed::Box; pub use base::{Runtime, WeakRuntime}; pub use userdata::{UserDataError, UserDataGuard}; +#[cfg(all(feature = "futures", feature = "parallel"))] +pub(crate) use r#async::DropContextPtr; #[cfg(feature = "futures")] pub(crate) use r#async::InnerRuntime; #[cfg(feature = "futures")] diff --git a/core/src/runtime/async.rs b/core/src/runtime/async.rs index 52949cea..95daf5b5 100644 --- a/core/src/runtime/async.rs +++ b/core/src/runtime/async.rs @@ -7,9 +7,6 @@ use core::{ptr::NonNull, result::Result as StdResult, task::Poll}; #[cfg(feature = "std")] use std::println; -#[cfg(feature = "parallel")] -use std::sync::mpsc::{self, Receiver, Sender}; - use async_lock::Mutex; use super::{ @@ -21,22 +18,35 @@ use crate::allocator::Allocator; use crate::loader::{Loader, Resolver}; #[cfg(feature = "parallel")] use crate::util::{AssertSendFuture, AssertSyncFuture}; +#[cfg(feature = "parallel")] +use crate::Mut; use crate::{ context::AsyncContext, qjs, result::AsyncJobException, util::ManualPoll, Ctx, Exception, Result, }; +#[cfg(feature = "parallel")] +#[derive(Debug)] +pub(crate) struct DropContextPtr(pub(crate) NonNull); + +// Context destruction is deferred until the runtime lock is held. +#[cfg(feature = "parallel")] +unsafe impl Send for DropContextPtr {} + +#[cfg(feature = "parallel")] +type DropQueue = Arc>>; + #[derive(Debug)] pub(crate) struct InnerRuntime { pub runtime: RawRuntime, #[cfg(feature = "parallel")] - pub drop_recv: Receiver>, + pub drop_queue: DropQueue, } impl InnerRuntime { pub fn drop_pending(&self) { #[cfg(feature = "parallel")] - while let Ok(x) = self.drop_recv.try_recv() { - unsafe { qjs::JS_FreeContext(x.as_ptr()) } + for ptr in self.drop_queue.lock().drain(..) { + unsafe { qjs::JS_FreeContext(ptr.0.as_ptr()) } } } } @@ -58,7 +68,7 @@ unsafe impl Send for InnerRuntime {} pub struct AsyncWeakRuntime { inner: Weak>, #[cfg(feature = "parallel")] - drop_send: Sender>, + drop_queue: DropQueue, } impl AsyncWeakRuntime { @@ -66,7 +76,7 @@ impl AsyncWeakRuntime { self.inner.upgrade().map(|inner| AsyncRuntime { inner, #[cfg(feature = "parallel")] - drop_send: self.drop_send.clone(), + drop_queue: self.drop_queue.clone(), }) } } @@ -78,7 +88,7 @@ pub struct AsyncRuntime { // use Arc instead of Ref so we can use OwnedLock pub(crate) inner: Arc>, #[cfg(feature = "parallel")] - pub(crate) drop_send: Sender>, + pub(crate) drop_queue: DropQueue, } // Since all functions which use runtime are behind a mutex @@ -110,16 +120,16 @@ impl AsyncRuntime { let runtime = unsafe { RawRuntime::new(opaque) }?; #[cfg(feature = "parallel")] - let (drop_send, drop_recv) = mpsc::channel(); + let drop_queue = Arc::new(Mut::new(Vec::new())); Ok(Self { inner: Arc::new(Mutex::new(InnerRuntime { runtime, #[cfg(feature = "parallel")] - drop_recv, + drop_queue: drop_queue.clone(), })), #[cfg(feature = "parallel")] - drop_send, + drop_queue, }) } @@ -136,16 +146,16 @@ impl AsyncRuntime { let runtime = unsafe { RawRuntime::new_with_allocator(opaque, allocator) }?; #[cfg(feature = "parallel")] - let (drop_send, drop_recv) = mpsc::channel(); + let drop_queue = Arc::new(Mut::new(Vec::new())); Ok(Self { inner: Arc::new(Mutex::new(InnerRuntime { runtime, #[cfg(feature = "parallel")] - drop_recv, + drop_queue: drop_queue.clone(), })), #[cfg(feature = "parallel")] - drop_send, + drop_queue, }) } @@ -154,7 +164,7 @@ impl AsyncRuntime { AsyncWeakRuntime { inner: Arc::downgrade(&self.inner), #[cfg(feature = "parallel")] - drop_send: self.drop_send.clone(), + drop_queue: self.drop_queue.clone(), } } @@ -192,6 +202,16 @@ impl AsyncRuntime { } } + /// Configure whether JavaScript Atomics operations may block this thread. + /// + /// This must be enabled before using `Atomics.wait`. + #[inline] + pub async fn set_can_block(&self, can_block: bool) { + unsafe { + self.inner.lock().await.runtime.set_can_block(can_block); + } + } + /// Set the module loader #[cfg(feature = "loader")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))] diff --git a/core/src/runtime/base.rs b/core/src/runtime/base.rs index b4b72e76..9bcb44ee 100644 --- a/core/src/runtime/base.rs +++ b/core/src/runtime/base.rs @@ -92,6 +92,16 @@ impl Runtime { } } + /// Configure whether JavaScript Atomics operations may block this thread. + /// + /// This must be enabled before using `Atomics.wait`. + #[inline] + pub fn set_can_block(&self, can_block: bool) { + unsafe { + self.inner.lock().set_can_block(can_block); + } + } + /// Set the module loader #[cfg(feature = "loader")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))] @@ -218,6 +228,24 @@ mod test { rt.run_gc(); } + #[test] + #[cfg_attr(target_os = "wasi", ignore = "Atomics.wait cannot block on WASI")] + fn atomics_wait_can_be_enabled() { + let rt = Runtime::new().unwrap(); + rt.set_can_block(true); + let ctx = crate::Context::full(&rt).unwrap(); + ctx.with(|ctx| { + let result: String = ctx + .eval( + r#"Atomics.wait( + new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1 + )"#, + ) + .unwrap(); + assert_eq!(result, "timed-out"); + }); + } + #[test] fn set_max_stack_size_large_values() { let rt = Runtime::new().unwrap(); diff --git a/core/src/runtime/raw.rs b/core/src/runtime/raw.rs index 2b0cb42d..ad24ad3b 100644 --- a/core/src/runtime/raw.rs +++ b/core/src/runtime/raw.rs @@ -422,6 +422,11 @@ impl RawRuntime { self.get_opaque().set_interrupt_handler(handler); } + /// Allow or reject blocking Atomics operations in this runtime. + pub unsafe fn set_can_block(&mut self, can_block: bool) { + qjs::JS_SetCanBlock(self.rt.as_ptr(), can_block); + } + fn add_dump_flags(rt: *mut rquickjs_sys::JSRuntime) { unsafe { qjs::JS_SetDumpFlags(rt, build_dump_flags()); diff --git a/core/src/safe_ref.rs b/core/src/safe_ref.rs index fdd5fc95..aa69a178 100644 --- a/core/src/safe_ref.rs +++ b/core/src/safe_ref.rs @@ -1,9 +1,12 @@ #[cfg(not(feature = "parallel"))] use core::cell::RefCell as Cell; -#[cfg(feature = "parallel")] +#[cfg(all(feature = "parallel", feature = "std"))] use std::sync::Mutex as Cell; +#[cfg(all(feature = "parallel", not(feature = "std")))] +use spin::Mutex as Cell; + #[cfg(not(feature = "parallel"))] pub use core::cell::RefMut as Lock; @@ -11,9 +14,16 @@ pub use core::cell::RefMut as Lock; pub use alloc::rc::{Rc as Ref, Weak}; #[cfg(feature = "parallel")] -pub use std::sync::{Arc as Ref, MutexGuard as Lock, Weak}; +pub use alloc::sync::{Arc as Ref, Weak}; + +#[cfg(all(feature = "parallel", feature = "std"))] +pub use std::sync::MutexGuard as Lock; + +#[cfg(all(feature = "parallel", not(feature = "std")))] +pub use spin::MutexGuard as Lock; #[repr(transparent)] +#[derive(Debug)] pub struct Mut(Cell); impl Mut { @@ -35,10 +45,15 @@ impl Mut { self.0.borrow_mut() } - #[cfg(feature = "parallel")] + #[cfg(all(feature = "parallel", feature = "std"))] { self.0.lock().unwrap() } + + #[cfg(all(feature = "parallel", not(feature = "std")))] + { + self.0.lock() + } } pub fn try_lock(&self) -> Option> { @@ -47,9 +62,27 @@ impl Mut { self.0.try_borrow_mut().ok() } - #[cfg(feature = "parallel")] + #[cfg(all(feature = "parallel", feature = "std"))] { - self.0.lock().ok() + self.0.try_lock().ok() } + + #[cfg(all(feature = "parallel", not(feature = "std")))] + { + self.0.try_lock() + } + } +} + +#[cfg(test)] +mod tests { + use super::Mut; + + #[test] + fn try_lock_returns_none_while_locked() { + let value = Mut::new(0); + let _guard = value.lock(); + + assert!(value.try_lock().is_none()); } } diff --git a/core/src/value/convert/from.rs b/core/src/value/convert/from.rs index 0848c848..b59683a8 100644 --- a/core/src/value/convert/from.rs +++ b/core/src/value/convert/from.rs @@ -429,6 +429,10 @@ macro_rules! chrono_from_js_impls { chrono_from_js_impls! { Utc; +} + +#[cfg(feature = "std")] +chrono_from_js_impls! { Local; } diff --git a/examples/zephyr/CMakeLists.txt b/examples/zephyr/CMakeLists.txt new file mode 100644 index 00000000..9fd59e77 --- /dev/null +++ b/examples/zephyr/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(rquickjs_zephyr) + +rust_cargo_application() diff --git a/examples/zephyr/Cargo.toml b/examples/zephyr/Cargo.toml new file mode 100644 index 00000000..037c42ef --- /dev/null +++ b/examples/zephyr/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rustapp" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["staticlib"] + +[dependencies] +rquickjs = { path = "../..", default-features = false, features = ["zephyr"] } +zephyr = "0.1.0" diff --git a/examples/zephyr/README.md b/examples/zephyr/README.md new file mode 100644 index 00000000..f8f42ab0 --- /dev/null +++ b/examples/zephyr/README.md @@ -0,0 +1,19 @@ +# Zephyr example + +This firmware validates rquickjs and QuickJS on a `no_std + alloc` Zephyr host. It exercises two +Zephyr threads sharing a `parallel` runtime, promises and the job queue, SharedArrayBuffer, 8/16/32 +bit Atomics, 64-bit BigInt Atomics, and timed `Atomics.wait`. + +Zephyr 4.4.2 and SDK 1.0.1 were used for the initial port. The `mps2/an385` QEMU board provides the +same Cortex-M3 Rust target as `qemu_cortex_m3`, with enough RAM and flash for the engine. + +```sh +export RQUICKJS_DIR="$(git rev-parse --show-toplevel)" +export ZEPHYR_WORKSPACE=/path/to/zephyrproject +source "$ZEPHYR_WORKSPACE/env.sh" +west build -b mps2/an385 "$RQUICKJS_DIR/examples/zephyr" \ + -d "$ZEPHYR_WORKSPACE/build/rquickjs-zephyr" +west build -d "$ZEPHYR_WORKSPACE/build/rquickjs-zephyr" -t run +``` + +Exit QEMU with Ctrl+A, then X. diff --git a/examples/zephyr/app.overlay b/examples/zephyr/app.overlay new file mode 100644 index 00000000..08449b10 --- /dev/null +++ b/examples/zephyr/app.overlay @@ -0,0 +1,13 @@ +/ { + aliases { + /delete-property/ led0; + /delete-property/ led1; + /delete-property/ sw0; + /delete-property/ sw1; + }; +}; + +/delete-node/ &led_0; +/delete-node/ &led_1; +/delete-node/ &user_button_0; +/delete-node/ &user_button_1; diff --git a/examples/zephyr/prj.conf b/examples/zephyr/prj.conf new file mode 100644 index 00000000..6a2b03fe --- /dev/null +++ b/examples/zephyr/prj.conf @@ -0,0 +1,14 @@ +CONFIG_RUST=y +CONFIG_RUST_ALLOC=y + +CONFIG_MAIN_STACK_SIZE=65536 +CONFIG_HEAP_MEM_POOL_SIZE=3145728 + +CONFIG_POSIX_API=y +CONFIG_POSIX_THREAD_THREADS_MAX=8 +CONFIG_MAX_PTHREAD_MUTEX_COUNT=16 +CONFIG_MAX_PTHREAD_COND_COUNT=16 +CONFIG_POSIX_THREAD_ATTR_STACKSIZE=y + +CONFIG_LOG=y +CONFIG_LOG_BACKEND_RTT=n diff --git a/examples/zephyr/src/lib.rs b/examples/zephyr/src/lib.rs new file mode 100644 index 00000000..ae0eeb3e --- /dev/null +++ b/examples/zephyr/src/lib.rs @@ -0,0 +1,92 @@ +#![no_std] + +extern crate alloc; + +use rquickjs::{Context, Runtime}; +use zephyr::printkln; + +const WORKER_STACK_SIZE: usize = 65536; + +fn run_engine(rt: &Runtime) -> rquickjs::Result { + printkln!("rquickjs: create context"); + rt.set_can_block(true); + let ctx = Context::full(rt)?; + + ctx.with(|ctx| { + ctx.eval::<(), _>( + r#" + globalThis.promiseResult = 0; + Promise.resolve(40).then(value => { globalThis.promiseResult = value + 2; }); + "#, + ) + })?; + printkln!("rquickjs: promise queued"); + + while rt.is_job_pending() { + rt.execute_pending_job() + .map_err(|_| rquickjs::Error::Exception)?; + } + printkln!("rquickjs: jobs drained"); + + ctx.with(|ctx| { + let result = ctx.eval( + r#" + (() => { + const sab = new SharedArrayBuffer(32); + const u8 = new Uint8Array(sab); + const i16 = new Int16Array(sab); + const i32 = new Int32Array(sab); + const i64 = new BigInt64Array(sab); + + if (Atomics.add(i32, 0, 40) !== 0) return -1; + if (Atomics.compareExchange(i32, 0, 40, 42) !== 40) return -2; + if (Atomics.load(i32, 0) !== 42) return -3; + if (Atomics.wait(i32, 1, 0, 1) !== "timed-out") return -4; + Atomics.store(i64, 1, 7n); + if (Atomics.load(i64, 1) !== 7n) return -5; + if (Atomics.exchange(u8, 16, 5) !== 0) return -6; + if (Atomics.or(u8, 16, 2) !== 5) return -7; + if (Atomics.and(u8, 16, 6) !== 7) return -8; + if (Atomics.store(i16, 9, 9) !== 9) return -9; + if (Atomics.sub(i16, 9, 2) !== 9) return -10; + if (Atomics.xor(i16, 9, 5) !== 7) return -11; + if (Atomics.add(i64, 1, 2n) !== 7n) return -12; + if (Atomics.load(i64, 1) !== 9n) return -13; + if (Atomics.notify(i32, 1, 1) !== 0) return -14; + Atomics.pause(); + return globalThis.promiseResult; + })() + "#, + ); + if result.is_err() { + printkln!("rquickjs: JavaScript exception: {:?}", ctx.catch()); + } + result + }) +} + +#[zephyr::thread(stack_size = WORKER_STACK_SIZE, pool_size = 2)] +fn worker(id: usize, runtime: Runtime) { + printkln!("rquickjs worker {}: start", id); + match run_engine(&runtime) { + Ok(42) => printkln!("rquickjs worker {}: PASS", id), + Ok(value) => panic!("rquickjs worker {} returned {}", id, value), + Err(error) => panic!("rquickjs worker {} failed: {:?}", id, error), + } +} + +#[no_mangle] +pub extern "C" fn rust_main() { + printkln!("rquickjs: create runtime"); + let runtime = Runtime::new().expect("create QuickJS runtime"); + printkln!("rquickjs: runtime ready"); + + let first = worker(1, runtime.clone()).start(); + printkln!("rquickjs: first worker started"); + let second = worker(2, runtime).start(); + printkln!("rquickjs: second worker started"); + first.join().expect("join first rquickjs worker"); + second.join().expect("join second rquickjs worker"); + + printkln!("rquickjs Zephyr no_std + alloc: PASS"); +} diff --git a/sys/Cargo.toml b/sys/Cargo.toml index 1b992fac..c30b1e39 100644 --- a/sys/Cargo.toml +++ b/sys/Cargo.toml @@ -24,6 +24,7 @@ optional = true [features] bindgen = ["bindgen-rs"] +zephyr = ["bindgen"] # Debug logging logging = ["pretty_env_logger"] diff --git a/sys/build.rs b/sys/build.rs index 22e6f19a..1c72cd38 100644 --- a/sys/build.rs +++ b/sys/build.rs @@ -111,12 +111,17 @@ fn main() { "dump-promise", "dump-read-object", "disable-assertions", + "zephyr", ]; for feature in &features { println!("cargo:rerun-if-env-changed={}", feature_to_cargo(feature)); } println!("cargo:rerun-if-env-changed=CARGO_CFG_SANITIZE"); + println!("cargo:rerun-if-env-changed=INCLUDE_DIRS"); + println!("cargo:rerun-if-env-changed=INCLUDE_DEFINES"); + println!("cargo:rerun-if-env-changed=BINARY_DIR_INCLUDE_GENERATED"); + println!("cargo:rerun-if-env-changed=ZEPHYR_SDK_INSTALL_DIR"); let src_dir = Path::new("quickjs"); @@ -160,14 +165,13 @@ fn main() { let is_wasi = target_os == "wasi"; let is_wasm_unknown = target_arch == "wasm32" && target_os == "unknown" && target_family == "wasm"; + let is_zephyr = env::var_os("CARGO_FEATURE_ZEPHYR").is_some(); let mut builder = cc::Build::new(); - builder - .extra_warnings(false) - .flag_if_supported("-Wno-implicit-const-int-float-conversion") - //.flag("-Wno-array-bounds") - //.flag("-Wno-format-truncation") - ; + builder.extra_warnings(false); + if !is_zephyr { + builder.flag_if_supported("-Wno-implicit-const-int-float-conversion"); + } match env::var("CARGO_CFG_SANITIZE").as_deref() { Ok("address") => { @@ -194,6 +198,62 @@ fn main() { let mut bindgen_cflags = vec![]; + if is_zephyr { + defines.push(("__ZEPHYR__".into(), Some("1"))); + + let generated_include = PathBuf::from( + env::var("BINARY_DIR_INCLUDE_GENERATED").expect( + "the zephyr feature requires BINARY_DIR_INCLUDE_GENERATED from rust_cargo_application()", + ), + ); + let autoconf = generated_include.join("autoconf.h"); + builder.flag("-imacros").flag(&autoconf); + bindgen_cflags.push("-imacros".into()); + bindgen_cflags.push(autoconf.display().to_string()); + + let include_dirs = env::var("INCLUDE_DIRS") + .expect("the zephyr feature requires INCLUDE_DIRS from rust_cargo_application()") + .split([' ', ';']) + .filter(|value| !value.is_empty()) + .map(PathBuf::from) + .collect::>(); + for include_dir in include_dirs { + builder.include(&include_dir); + bindgen_cflags.push(format!("-I{}", include_dir.display())); + } + + let include_defines = env::var("INCLUDE_DEFINES") + .expect("the zephyr feature requires INCLUDE_DEFINES from rust_cargo_application()"); + for definition in include_defines + .split([' ', ';']) + .filter(|value| !value.is_empty()) + { + let (name, value) = definition + .split_once('=') + .map_or((definition, None), |(name, value)| (name, Some(value))); + builder.define(name, value); + bindgen_cflags.push(match value { + Some(value) => format!("-D{name}={value}"), + None => format!("-D{name}"), + }); + } + + if target_arch == "arm" { + if let Some(sdk_dir) = env::var_os("ZEPHYR_SDK_INSTALL_DIR") { + let toolchain = PathBuf::from(sdk_dir).join("gnu/arm-zephyr-eabi/bin"); + builder.compiler(toolchain.join("arm-zephyr-eabi-gcc")); + builder.archiver(toolchain.join("arm-zephyr-eabi-ar")); + + let libc_include = toolchain + .parent() + .expect("Zephyr ARM toolchain bin directory has no parent") + .join("arm-zephyr-eabi/include"); + builder.include(&libc_include); + bindgen_cflags.push(format!("-I{}", libc_include.display())); + } + } + } + if target_os == "windows" { if target_env == "msvc" { env::set_var( diff --git a/sys/quickjs b/sys/quickjs index f23aa4db..836f07d0 160000 --- a/sys/quickjs +++ b/sys/quickjs @@ -1 +1 @@ -Subproject commit f23aa4db6749ddb36867bd05f982bd57d286d346 +Subproject commit 836f07d01f997b45cd2ed146fb88ffc5709348c6