diff --git a/mozjs-sys/Cargo.toml b/mozjs-sys/Cargo.toml index 1afa0ac63d..b1b7323c99 100644 --- a/mozjs-sys/Cargo.toml +++ b/mozjs-sys/Cargo.toml @@ -2,7 +2,7 @@ name = "mozjs_sys" description = "System crate for the Mozilla SpiderMonkey JavaScript engine." repository.workspace = true -version = "0.128.0-9" +version = "0.128.0-10" authors = ["Mozilla"] links = "mozjs" build = "build.rs" diff --git a/mozjs-sys/src/jsapi.cpp b/mozjs-sys/src/jsapi.cpp index 5d5c4184b2..9b1430c613 100644 --- a/mozjs-sys/src/jsapi.cpp +++ b/mozjs-sys/src/jsapi.cpp @@ -40,6 +40,7 @@ #include "js/Utility.h" #include "js/Warnings.h" #include "js/WasmModule.h" +#include "js/experimental/CompileScript.h" #include "js/experimental/JSStencil.h" #include "js/experimental/JitInfo.h" #include "js/experimental/TypedData.h" @@ -68,6 +69,16 @@ JS::OwningCompileOptions* JS_NewOwningCompileOptions(JSContext* cx) { return result; } +JS::OwningCompileOptions* OwningCompileOptions_for_fc( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& rhs) { + JS::OwningCompileOptions* oco = new JS::OwningCompileOptions( + JS::OwningCompileOptions::ForFrontendContext()); + if (!oco->copy(fc, rhs)) { + return nullptr; + } + return oco; +} + void DeleteOwningCompileOptions(JS::OwningCompileOptions* opts) { delete opts; } JS::shadow::Zone* JS_AsShadowZone(JS::Zone* zone) { diff --git a/mozjs/src/lib.rs b/mozjs/src/lib.rs index a23996a2bf..61d607452c 100644 --- a/mozjs/src/lib.rs +++ b/mozjs/src/lib.rs @@ -49,6 +49,7 @@ pub mod error; pub mod gc; pub mod panic; pub mod typedarray; +pub mod offthread; pub use crate::consts::*; pub use mozjs_sys::glue; diff --git a/mozjs/src/offthread.rs b/mozjs/src/offthread.rs new file mode 100644 index 0000000000..7cec203ebd --- /dev/null +++ b/mozjs/src/offthread.rs @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::jsapi::JS::{ + CompileGlobalScriptToStencil2, DestroyFrontendContext, FrontendContext as RawFrontendContext, + NewFrontendContext, ReadOnlyCompileOptions, +}; +use crate::rust::{transform_str_to_source_text, OwningCompileOptionsWrapper, Stencil}; +use std::ops::Deref; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; + +pub struct FrontendContext(*mut RawFrontendContext); + +unsafe impl Send for FrontendContext {} + +impl FrontendContext { + pub fn new() -> Self { + Self(unsafe { NewFrontendContext() }) + } +} + +impl Deref for FrontendContext { + type Target = *mut RawFrontendContext; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for FrontendContext { + fn drop(&mut self) { + unsafe { DestroyFrontendContext(self.0) } + } +} + +pub struct OffThreadToken(JoinHandle>); + +impl OffThreadToken { + /// Obtains result + /// + /// Blocks until completion + pub fn finish(self) -> Option { + self.0.join().ok().flatten() + } +} + +/// Creates a new thread and starts compilation there +/// +/// Callback receives stencil that can either consumed or returned +pub fn compile_to_stencil_offthread( + options: *const ReadOnlyCompileOptions, + source: Arc, + callback: F, +) -> OffThreadToken +where + F: FnOnce(Stencil) -> Option + Send + 'static, +{ + let fc = FrontendContext::new(); + let options = OwningCompileOptionsWrapper::new_for_fc(&fc, options); + OffThreadToken( + thread::Builder::new() + .name("OffThread Compile".to_string()) + .spawn(move || { + callback(unsafe { + Stencil::from_raw(CompileGlobalScriptToStencil2( + *fc, + options.read_only(), + &mut transform_str_to_source_text(&source) as *mut _, + )) + }) + }) + .unwrap(), + ) +} diff --git a/mozjs/src/rust.rs b/mozjs/src/rust.rs index 3cf6a8bb75..3f493870a8 100644 --- a/mozjs/src/rust.rs +++ b/mozjs/src/rust.rs @@ -41,8 +41,13 @@ use crate::jsapi::HandleObjectVector as RawHandleObjectVector; use crate::jsapi::HandleValue as RawHandleValue; use crate::jsapi::JS_AddExtraGCRootsTracer; use crate::jsapi::MutableHandleIdVector as RawMutableHandleIdVector; +use crate::jsapi::OwningCompileOptions_for_fc; use crate::jsapi::{already_AddRefed, jsid}; use crate::jsapi::{BuildStackString, CaptureCurrentStack, StackFormat}; +use crate::jsapi::{ + DeleteOwningCompileOptions, OwningCompileOptions, PersistentRootedObjectVector, + ReadOnlyCompileOptions, RootingContext, +}; use crate::jsapi::{Evaluate2, HandleValueArray, StencilRelease}; use crate::jsapi::{InitSelfHostedCode, IsWindowSlow}; use crate::jsapi::{ @@ -56,11 +61,11 @@ use crate::jsapi::{JS_DefineFunctions, JS_DefineProperties, JS_DestroyContext, J use crate::jsapi::{JS_EnumerateStandardClasses, JS_GetRuntime, JS_GlobalObjectTraceHook}; use crate::jsapi::{JS_MayResolveStandardClass, JS_NewContext, JS_ResolveStandardClass}; use crate::jsapi::{JS_StackCapture_AllFrames, JS_StackCapture_MaxFrames}; -use crate::jsapi::{PersistentRootedObjectVector, ReadOnlyCompileOptions, RootingContext}; use crate::jsapi::{SetWarningReporter, SourceText, ToBooleanSlow}; use crate::jsapi::{ToInt32Slow, ToInt64Slow, ToNumberSlow, ToStringSlow, ToUint16Slow}; use crate::jsapi::{ToUint32Slow, ToUint64Slow, ToWindowProxyIfWindowSlow}; use crate::jsval::ObjectValue; +use crate::offthread::FrontendContext; use crate::panic::maybe_resume_unwind; use lazy_static::lazy_static; use log::{debug, warn}; @@ -470,6 +475,30 @@ impl Drop for RootedObjectVectorWrapper { } } +pub struct OwningCompileOptionsWrapper { + pub ptr: *mut OwningCompileOptions, +} + +impl OwningCompileOptionsWrapper { + pub fn new_for_fc(fc: &FrontendContext, options: *const ReadOnlyCompileOptions) -> Self { + Self { + ptr: unsafe { OwningCompileOptions_for_fc(**fc, options) }, + } + } + + pub fn read_only(&self) -> &ReadOnlyCompileOptions { + unsafe { &(*self.ptr)._base } + } +} + +unsafe impl Send for OwningCompileOptionsWrapper {} + +impl Drop for OwningCompileOptionsWrapper { + fn drop(&mut self) { + unsafe { DeleteOwningCompileOptions(self.ptr) } + } +} + pub struct CompileOptionsWrapper { pub ptr: *mut ReadOnlyCompileOptions, } @@ -493,8 +522,7 @@ pub struct Stencil { inner: already_AddRefed, } -/*unsafe impl Send for Stencil {} -unsafe impl Sync for Stencil {}*/ +unsafe impl Send for Stencil {} impl Drop for Stencil { fn drop(&mut self) { @@ -519,6 +547,10 @@ impl Stencil { pub fn is_null(&self) -> bool { self.inner.mRawPtr.is_null() } + + pub unsafe fn from_raw(inner: already_AddRefed) -> Self { + Self { inner } + } } // ___________________________________________________________________________ diff --git a/mozjs/tests/offthread.rs b/mozjs/tests/offthread.rs new file mode 100644 index 0000000000..7ed9b84aea --- /dev/null +++ b/mozjs/tests/offthread.rs @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::ptr; +use std::sync::mpsc::channel; +use std::sync::Arc; + +use mozjs::jsapi::{ + InstantiateGlobalStencil, InstantiateOptions, JSAutoRealm, JS_NewGlobalObject, + OnNewGlobalHookOption, +}; +use mozjs::jsval::UndefinedValue; +use mozjs::offthread::compile_to_stencil_offthread; +use mozjs::rooted; +use mozjs::rust::{ + wrappers::JS_ExecuteScript, CompileOptionsWrapper, JSEngine, RealmOptions, Runtime, + SIMPLE_GLOBAL_CLASS, +}; + +#[test] +fn offthread() { + let engine = JSEngine::init().unwrap(); + let runtime = Runtime::new(engine.handle()); + let context = runtime.cx(); + let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook; + let c_option = RealmOptions::default(); + + unsafe { + rooted!(in(context) let global = JS_NewGlobalObject( + context, + &SIMPLE_GLOBAL_CLASS, + ptr::null_mut(), + h_option, + &*c_option, + )); + + let _ac = JSAutoRealm::new(context, global.get()); + + let src = Arc::new("1 + 1".to_string()); + let options = CompileOptionsWrapper::new(context, "", 1); + let options_ptr = options.ptr as *const _; + let (sender, receiver) = channel(); + let offthread_token = compile_to_stencil_offthread(options_ptr, src, move |stencil| { + sender.send(stencil).unwrap(); + None + }); + + let stencil = receiver.recv().unwrap(); + + assert!(offthread_token.finish().is_none()); + + let options = InstantiateOptions { + skipFilenameValidation: false, + hideScriptFromDebugger: false, + deferDebugMetadata: false, + }; + rooted!(in(context) let script = InstantiateGlobalStencil( + context, + &options, + *stencil, + ptr::null_mut(), + )); + + rooted!(in(context) let mut rval = UndefinedValue()); + let result = JS_ExecuteScript(context, script.handle(), rval.handle_mut()); + assert!(result); + assert_eq!(rval.get().to_int32(), 2); + } +}