Skip to main content

mozjs/
offthread.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::glue::CompileGlobalScriptToStencil_C;
6use crate::jsapi::JS::{
7    DestroyFrontendContext, FrontendContext as RawFrontendContext,
8    InstantiationStorage as RawInstantiationStorage, NewFrontendContext, PrepareForInstantiate,
9    ReadOnlyCompileOptions,
10};
11use crate::jsapi::{SetNativeStackQuota, ThreadStackQuotaForSize};
12use crate::rust::{transform_str_to_source_text, OwningCompileOptionsWrapper, Stencil};
13
14use std::mem::MaybeUninit;
15use std::ops::Deref;
16use std::sync::Arc;
17use std::thread::{self, JoinHandle};
18
19/// We want our default stack size limit to be approximately 2MB, to be safe for
20/// JS helper tasks that can use a lot of stack, but expect most threads to use
21/// much less. On Linux, however, requesting a stack of 2MB or larger risks the
22/// kernel allocating an entire 2MB huge page for it on first access, which we do
23/// not want. To avoid this possibility, we subtract 2 standard VM page sizes
24/// from our default.
25///
26/// <https://searchfox.org/mozilla-central/rev/cb5faf5dd5176494302068c553da97b4d08aa339/xpcom/threads/TaskController.cpp#43>
27const STACK_SIZE: usize = 2048 * 1024 - 2 * 4096;
28
29pub struct FrontendContext(*mut RawFrontendContext);
30
31unsafe impl Send for FrontendContext {}
32
33impl FrontendContext {
34    pub fn new() -> Self {
35        Self(unsafe { NewFrontendContext() })
36    }
37
38    pub fn set_stack_quota(&self, size: usize) {
39        unsafe {
40            SetNativeStackQuota(self.0, ThreadStackQuotaForSize(size));
41        }
42    }
43}
44
45impl Deref for FrontendContext {
46    type Target = *mut RawFrontendContext;
47    fn deref(&self) -> &Self::Target {
48        &self.0
49    }
50}
51
52impl Drop for FrontendContext {
53    fn drop(&mut self) {
54        unsafe { DestroyFrontendContext(self.0) }
55    }
56}
57
58pub struct InstantiationStorage(RawInstantiationStorage);
59
60impl InstantiationStorage {
61    pub fn as_mut_ptr(&mut self) -> *mut RawInstantiationStorage {
62        &mut self.0
63    }
64}
65
66unsafe impl Send for InstantiationStorage {}
67
68impl Default for InstantiationStorage {
69    fn default() -> InstantiationStorage {
70        unsafe { std::mem::zeroed() }
71    }
72}
73
74pub struct CompilationResult {
75    pub stencil: Option<Stencil>,
76    pub storage: InstantiationStorage,
77    pub fc: FrontendContext,
78    pub compile_options: OwningCompileOptionsWrapper,
79}
80
81pub struct OffThreadToken(JoinHandle<Option<CompilationResult>>);
82
83impl OffThreadToken {
84    /// Obtains result
85    ///
86    /// Blocks until completion
87    pub fn finish(self) -> Option<CompilationResult> {
88        self.0.join().ok().flatten()
89    }
90}
91
92/// Creates a new thread and starts compilation there
93///
94/// Callback receives stencil that can either consumed or returned
95pub fn compile_to_stencil_offthread<F>(
96    options: *const ReadOnlyCompileOptions,
97    source: Arc<String>,
98    callback: F,
99) -> OffThreadToken
100where
101    F: FnOnce(CompilationResult) -> Option<CompilationResult> + Send + 'static,
102{
103    let fc = FrontendContext::new();
104    let options = OwningCompileOptionsWrapper::new_for_fc(&fc, options);
105    OffThreadToken(
106        thread::Builder::new()
107            .name("OffThread Compile".to_string())
108            .stack_size(STACK_SIZE)
109            .spawn(move || {
110                fc.set_stack_quota(STACK_SIZE);
111
112                let mut source = transform_str_to_source_text(&source);
113                let mut uninit_stencil = MaybeUninit::uninit();
114
115                unsafe {
116                    CompileGlobalScriptToStencil_C(
117                        *fc,
118                        options.read_only(),
119                        &mut source,
120                        uninit_stencil.as_mut_ptr(),
121                    )
122                }
123
124                let raw_stencil = unsafe { uninit_stencil.assume_init() };
125                let stencil = unsafe { Stencil::from_raw(raw_stencil) };
126
127                let mut storage = InstantiationStorage::default();
128
129                let ok = !stencil.is_null()
130                    && unsafe { PrepareForInstantiate(*fc, *stencil, storage.as_mut_ptr()) };
131
132                let stencil = if ok { Some(stencil) } else { None };
133
134                callback(CompilationResult {
135                    stencil,
136                    storage,
137                    fc,
138                    compile_options: options,
139                })
140            })
141            .unwrap(),
142    )
143}