diff --git a/mozjs-sys/Cargo.toml b/mozjs-sys/Cargo.toml index 0848caa909..3c4bd8940a 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.3-0" +version = "0.128.3-1" 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..448f9252df 100644 --- a/mozjs/src/lib.rs +++ b/mozjs/src/lib.rs @@ -47,6 +47,7 @@ mod consts; pub mod conversions; pub mod error; pub mod gc; +pub mod offthread; pub mod panic; pub mod typedarray; diff --git a/mozjs/src/offthread.rs b/mozjs/src/offthread.rs new file mode 100644 index 0000000000..ef6c1d2631 --- /dev/null +++ b/mozjs/src/offthread.rs @@ -0,0 +1,94 @@ +/* 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::jsapi::{SetNativeStackQuota, ThreadStackQuotaForSize}; +use crate::rust::{transform_str_to_source_text, OwningCompileOptionsWrapper, Stencil}; +use std::ops::Deref; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; + +/// We want our default stack size limit to be approximately 2MB, to be safe for +/// JS helper tasks that can use a lot of stack, but expect most threads to use +/// much less. On Linux, however, requesting a stack of 2MB or larger risks the +/// kernel allocating an entire 2MB huge page for it on first access, which we do +/// not want. To avoid this possibility, we subtract 2 standard VM page sizes +/// from our default. +/// +/// +const STACK_SIZE: usize = 2048 * 1024 - 2 * 4096; + +pub struct FrontendContext(*mut RawFrontendContext); + +unsafe impl Send for FrontendContext {} + +impl FrontendContext { + pub fn new() -> Self { + Self(unsafe { NewFrontendContext() }) + } + + pub fn set_stack_quota(&self, size: usize) { + unsafe { + SetNativeStackQuota(self.0, ThreadStackQuotaForSize(size)); + } + } +} + +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()) + .stack_size(STACK_SIZE) + .spawn(move || { + fc.set_stack_quota(STACK_SIZE); + 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 b13e1bc1d6..ef6a972445 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::{ @@ -57,11 +62,11 @@ use crate::jsapi::{JS_EnumerateStandardClasses, JS_GetRuntime, JS_GlobalObjectTr use crate::jsapi::{JS_MayResolveStandardClass, JS_NewContext, JS_ResolveStandardClass}; use crate::jsapi::{JS_RequestInterruptCallback, JS_RequestInterruptCallbackCanWait}; 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}; @@ -515,6 +520,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, } @@ -538,8 +567,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) { @@ -564,6 +592,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); + } +}