From fe8045060bc7a6f79d82aac9a472ae804f751cfa Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:56:45 +0200 Subject: [PATCH 1/2] offthread Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- mozjs-sys/Cargo.toml | 2 +- mozjs-sys/src/jsapi.cpp | 11 ++++++ mozjs/src/lib.rs | 1 + mozjs/src/offthread.rs | 75 ++++++++++++++++++++++++++++++++++++++++ mozjs/src/rust.rs | 38 ++++++++++++++++++-- mozjs/tests/offthread.rs | 70 +++++++++++++++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 mozjs/src/offthread.rs create mode 100644 mozjs/tests/offthread.rs diff --git a/mozjs-sys/Cargo.toml b/mozjs-sys/Cargo.toml index 1afa0ac63d6..b1b7323c99e 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 5d5c4184b27..9b1430c6137 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 a23996a2bfd..448f9252df5 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 00000000000..7cec203ebdb --- /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 3cf6a8bb752..3f493870a88 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 00000000000..7ed9b84aea3 --- /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); + } +} From 5cb4b557b05b4a43e7f3e70200c8a024b7a255ca Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:47:07 +0200 Subject: [PATCH 2/2] STACK_SIZE Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- mozjs/src/offthread.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mozjs/src/offthread.rs b/mozjs/src/offthread.rs index 7cec203ebdb..ef6c1d26311 100644 --- a/mozjs/src/offthread.rs +++ b/mozjs/src/offthread.rs @@ -6,11 +6,22 @@ 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 {} @@ -19,6 +30,12 @@ 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 { @@ -61,7 +78,9 @@ where 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,