diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 7ff8038f398..96e2c08f141 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -656,7 +656,7 @@ impl<'a> CanvasData<'a> { ideographic_baseline, alphabetic_baseline, hanging_baseline, - } = match font.get_baseline() { + } = match font.baseline() { Some(baseline) => baseline, None => FontBaseline { hanging_baseline: ascent * HANGING_BASELINE_DEFAULT, diff --git a/components/fonts/font.rs b/components/fonts/font.rs index 44ca3a8c4b9..678c10cd776 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -543,10 +543,10 @@ impl Font { self.handle.typographic_bounds(glyph_id) } - #[allow(unsafe_code)] - pub fn get_baseline(&self) -> Option { + /// Get the [`FontBaseline`] for this font. + pub fn baseline(&self) -> Option { let this = self as *const Font; - unsafe { self.shaper.get_or_init(|| Shaper::new(this)).get_baseline() } + self.shaper.get_or_init(|| Shaper::new(this)).baseline() } } diff --git a/components/fonts/shaper.rs b/components/fonts/shaper.rs index 0a815144a51..3425fff257c 100644 --- a/components/fonts/shaper.rs +++ b/components/fonts/shaper.rs @@ -619,39 +619,41 @@ impl Shaper { advance } - pub unsafe fn get_baseline(&self) -> Option { - (*self.font).table_for_tag(BASE)?; + pub fn baseline(&self) -> Option { + unsafe { (*self.font).table_for_tag(BASE)? }; let mut hanging_baseline = 0; let mut alphabetic_baseline = 0; let mut ideographic_baseline = 0; - hb_ot_layout_get_baseline( - self.hb_font, - HB_OT_LAYOUT_BASELINE_TAG_ROMAN, - HB_DIRECTION_LTR, - HB_OT_TAG_DEFAULT_SCRIPT, - HB_OT_TAG_DEFAULT_LANGUAGE, - &mut alphabetic_baseline as *mut _, - ); + unsafe { + hb_ot_layout_get_baseline( + self.hb_font, + HB_OT_LAYOUT_BASELINE_TAG_ROMAN, + HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &mut alphabetic_baseline as *mut _, + ); - hb_ot_layout_get_baseline( - self.hb_font, - HB_OT_LAYOUT_BASELINE_TAG_HANGING, - HB_DIRECTION_LTR, - HB_OT_TAG_DEFAULT_SCRIPT, - HB_OT_TAG_DEFAULT_LANGUAGE, - &mut hanging_baseline as *mut _, - ); + hb_ot_layout_get_baseline( + self.hb_font, + HB_OT_LAYOUT_BASELINE_TAG_HANGING, + HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &mut hanging_baseline as *mut _, + ); - hb_ot_layout_get_baseline( - self.hb_font, - HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, - HB_DIRECTION_LTR, - HB_OT_TAG_DEFAULT_SCRIPT, - HB_OT_TAG_DEFAULT_LANGUAGE, - &mut ideographic_baseline as *mut _, - ); + hb_ot_layout_get_baseline( + self.hb_font, + HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, + HB_DIRECTION_LTR, + HB_OT_TAG_DEFAULT_SCRIPT, + HB_OT_TAG_DEFAULT_LANGUAGE, + &mut ideographic_baseline as *mut _, + ); + } Some(FontBaseline { ideographic_baseline: Shaper::fixed_to_float(ideographic_baseline) as f32, diff --git a/components/script/dom/bindings/reflector.rs b/components/script/dom/bindings/reflector.rs index 261a317e360..9d3d27141ef 100644 --- a/components/script/dom/bindings/reflector.rs +++ b/components/script/dom/bindings/reflector.rs @@ -76,6 +76,10 @@ impl Reflector { } /// Initialize the reflector. (May be called only once.) + /// + /// # Safety + /// + /// The provided [`JSObject`] pointer must point to a valid [`JSObject`]. pub unsafe fn set_jsobject(&self, object: *mut JSObject) { assert!(self.object.get().is_null()); assert!(!object.is_null()); @@ -123,6 +127,10 @@ impl DomObject for Reflector { /// A trait to initialize the `Reflector` for a DOM object. pub trait MutDomObject: DomObject { /// Initializes the Reflector + /// + /// # Safety + /// + /// The provided [`JSObject`] pointer must point to a valid [`JSObject`]. unsafe fn init_reflector(&self, obj: *mut JSObject); } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 544aaa229fc..06ec5b92d89 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -69,8 +69,19 @@ use crate::script_thread::IncompleteParserContexts; use crate::task::TaskBox; /// A trait to allow tracing only DOM sub-objects. +/// +/// # Safety +/// +/// This trait is unsafe; if it is implemented incorrectly, the GC may end up collecting objects +/// that are still reachable. pub unsafe trait CustomTraceable { /// Trace `self`. + /// + /// # Safety + /// + /// The `JSTracer` argument must point to a valid `JSTracer` in memory. In addition, + /// implementors of this method must ensure that all active objects are properly traced + /// or else the garbage collector may end up collecting objects that are still reachable. unsafe fn trace(&self, trc: *mut JSTracer); } diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 6550671a3d3..4b0b064bb35 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -55,8 +55,8 @@ use crate::fetch::load_whole_resource; use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent; use crate::script_runtime::{ - new_child_runtime, CanGc, CommonScriptMsg, JSContext as SafeJSContext, Runtime, ScriptChan, - ScriptPort, ThreadSafeJSContext, + CanGc, CommonScriptMsg, JSContext as SafeJSContext, Runtime, ScriptChan, ScriptPort, + ThreadSafeJSContext, }; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use crate::task_source::networking::NetworkingTaskSource; @@ -381,7 +381,7 @@ impl DedicatedWorkerGlobalScope { }), pipeline_id, ); - new_child_runtime(parent, Some(task_source)) + Runtime::new_with_parent(Some(parent), Some(task_source)) }; let context_for_interrupt = runtime.thread_safe_js_context(); diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 651d383337a..65975c5755b 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -45,8 +45,7 @@ use crate::dom::workerglobalscope::WorkerGlobalScope; use crate::fetch::load_whole_resource; use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; use crate::script_runtime::{ - new_rt_and_cx, CanGc, CommonScriptMsg, JSContext as SafeJSContext, Runtime, ScriptChan, - ThreadSafeJSContext, + CanGc, CommonScriptMsg, JSContext as SafeJSContext, Runtime, ScriptChan, ThreadSafeJSContext, }; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use crate::task_source::TaskSourceName; @@ -310,7 +309,7 @@ impl ServiceWorkerGlobalScope { .name(format!("SW:{}", script_url.debug_compact())) .spawn(move || { thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER); - let runtime = new_rt_and_cx(None); + let runtime = Runtime::new(None); let context_for_interrupt = runtime.thread_safe_js_context(); let _ = context_sender.send(context_for_interrupt); diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 91d21feb945..2e77790cdcf 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -54,9 +54,7 @@ use crate::dom::workerlocation::WorkerLocation; use crate::dom::workernavigator::WorkerNavigator; use crate::fetch; use crate::realms::{enter_realm, InRealm}; -use crate::script_runtime::{ - get_reports, CanGc, CommonScriptMsg, JSContext, Runtime, ScriptChan, ScriptPort, -}; +use crate::script_runtime::{CanGc, CommonScriptMsg, JSContext, Runtime, ScriptChan, ScriptPort}; use crate::task::TaskCanceller; use crate::task_source::dom_manipulation::DOMManipulationTaskSource; use crate::task_source::file_reading::FileReadingTaskSource; @@ -539,8 +537,7 @@ impl WorkerGlobalScope { CommonScriptMsg::Task(_, task, _, _) => task.run_box(), CommonScriptMsg::CollectReports(reports_chan) => { let cx = self.get_cx(); - let path_seg = format!("url({})", self.get_url()); - let reports = unsafe { get_reports(*cx, path_seg) }; + let reports = cx.get_reports(format!("url({})", self.get_url())); reports_chan.send(reports); }, } diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs index d2ee1ef965c..a796bb98071 100644 --- a/components/script/dom/worklet.rs +++ b/components/script/dom/worklet.rs @@ -49,7 +49,7 @@ use crate::dom::workletglobalscope::{ }; use crate::fetch::load_whole_resource; use crate::realms::InRealm; -use crate::script_runtime::{new_rt_and_cx, CommonScriptMsg, Runtime, ScriptThreadEventCategory}; +use crate::script_runtime::{CommonScriptMsg, Runtime, ScriptThreadEventCategory}; use crate::script_thread::{MainThreadScriptMsg, ScriptThread}; use crate::task::TaskBox; use crate::task_source::TaskSourceName; @@ -490,7 +490,7 @@ impl WorkletThread { global_init: init.global_init, global_scopes: HashMap::new(), control_buffer: None, - runtime: new_rt_and_cx(None), + runtime: Runtime::new(None), should_gc: false, gc_threshold: MIN_GC_THRESHOLD, }); diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index 4ecd3ca2de1..05349205c82 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -89,12 +89,24 @@ impl<'dom> ServoLayoutElement<'dom> { self.as_node().style_data() } + /// Unset the snapshot flags on the underlying DOM object for this element. + /// + /// # Safety + /// + /// This function accesses and modifies the underlying DOM object and should + /// not be used by more than a single thread at once. pub unsafe fn unset_snapshot_flags(&self) { self.as_node() .node .set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false); } + /// Unset the snapshot flags on the underlying DOM object for this element. + /// + /// # Safety + /// + /// This function accesses and modifies the underlying DOM object and should + /// not be used by more than a single thread at once. pub unsafe fn set_has_snapshot(&self) { self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true); } diff --git a/components/script/layout_dom/node.rs b/components/script/layout_dom/node.rs index efb05a2f121..0b2b939cda4 100644 --- a/components/script/layout_dom/node.rs +++ b/components/script/layout_dom/node.rs @@ -76,6 +76,11 @@ impl<'dom> ServoLayoutNode<'dom> { ServoLayoutNode { node: n } } + /// Create a new [`ServoLayoutNode`] for this given [`TrustedNodeAddress`]. + /// + /// # Safety + /// + /// The address pointed to by `address` should point to a valid node in memory. pub unsafe fn new(address: &TrustedNodeAddress) -> Self { ServoLayoutNode::from_layout_js(LayoutDom::from_trusted_node_address(*address)) } diff --git a/components/script/layout_dom/shadow_root.rs b/components/script/layout_dom/shadow_root.rs index 9137e863972..0763e150eff 100644 --- a/components/script/layout_dom/shadow_root.rs +++ b/components/script/layout_dom/shadow_root.rs @@ -48,6 +48,12 @@ impl<'dom> ServoShadowRoot<'dom> { ServoShadowRoot { shadow_root } } + /// Flush the stylesheets for the underlying shadow root. + /// + /// # Safety + /// + /// This modifies a DOM object, so should care should be taken that only one + /// thread has a reference to this object. pub unsafe fn flush_stylesheets( &self, stylist: &mut Stylist, diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index aeee9c1553b..0e3228ac73c 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -448,6 +448,258 @@ pub struct Runtime { } impl Runtime { + /// Create a new runtime, optionally with the given [`NetworkingTaskSource`]. + /// + /// # Safety + /// + /// If panicking does not abort the program, any threads with child runtimes will continue + /// executing after the thread with the parent runtime panics, but they will be in an + /// invalid and undefined state. + /// + /// This, like many calls to SpiderMoney API, is unsafe. + #[allow(unsafe_code)] + pub(crate) fn new(networking_task_source: Option) -> Runtime { + unsafe { Self::new_with_parent(None, networking_task_source) } + } + + /// Create a new runtime, optionally with the given [`ParentRuntime`] and [`NetworkingTaskSource`]. + /// + /// # Safety + /// + /// If panicking does not abort the program, any threads with child runtimes will continue + /// executing after the thread with the parent runtime panics, but they will be in an + /// invalid and undefined state. + /// + /// The `parent` pointer in the [`ParentRuntime`] argument must point to a valid object in memory. + /// + /// This, like many calls to the SpiderMoney API, is unsafe. + #[allow(unsafe_code)] + pub(crate) unsafe fn new_with_parent( + parent: Option, + networking_task_source: Option, + ) -> Runtime { + LiveDOMReferences::initialize(); + let (cx, runtime) = if let Some(parent) = parent { + let runtime = RustRuntime::create_with_parent(parent); + let cx = runtime.cx(); + (cx, runtime) + } else { + let runtime = RustRuntime::new(JS_ENGINE.lock().unwrap().as_ref().unwrap().clone()); + (runtime.cx(), runtime) + }; + + JS_AddExtraGCRootsTracer(cx, Some(trace_rust_roots), ptr::null_mut()); + + JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS); + + JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal)); + JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal)); + + // Needed for debug assertions about whether GC is running. + if cfg!(debug_assertions) { + JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut()); + } + + if opts::get().debug.gc_profile { + SetGCSliceCallback(cx, Some(gc_slice_callback)); + } + + unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool { + true + } + unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool { + // fixme: return true when the Drop impl for a DOM object has been invoked + false + } + SetDOMCallbacks(cx, &DOM_CALLBACKS); + SetPreserveWrapperCallbacks( + cx, + Some(empty_wrapper_callback), + Some(empty_has_released_callback), + ); + // Pre barriers aren't working correctly at the moment + DisableIncrementalGC(cx); + + unsafe extern "C" fn dispatch_to_event_loop( + closure: *mut c_void, + dispatchable: *mut JSRunnable, + ) -> bool { + let networking_task_src: &NetworkingTaskSource = + &*(closure as *mut NetworkingTaskSource); + let runnable = Runnable(dispatchable); + let task = task!(dispatch_to_event_loop_message: move || { + runnable.run(RustRuntime::get(), Dispatchable_MaybeShuttingDown::NotShuttingDown); + }); + + networking_task_src.queue_unconditionally(task).is_ok() + } + + let mut networking_task_src_ptr = std::ptr::null_mut(); + if let Some(source) = networking_task_source { + networking_task_src_ptr = Box::into_raw(Box::new(source)); + InitDispatchToEventLoop( + cx, + Some(dispatch_to_event_loop), + networking_task_src_ptr as *mut c_void, + ); + } + + InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error)); + + let microtask_queue = Rc::new(MicrotaskQueue::default()); + let job_queue = CreateJobQueue( + &JOB_QUEUE_TRAPS, + &*microtask_queue as *const _ as *const c_void, + ); + SetJobQueue(cx, job_queue); + SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut()); + + EnsureModuleHooksInitialized(runtime.rt()); + + set_gc_zeal_options(cx); + + // Enable or disable the JITs. + let cx_opts = &mut *ContextOptionsRef(cx); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE, + pref!(js.baseline_interpreter.enabled) as u32, + ); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE, + pref!(js.baseline_jit.enabled) as u32, + ); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE, + pref!(js.ion.enabled) as u32, + ); + cx_opts.compileOptions_.asmJSOption_ = if pref!(js.asmjs.enabled) { + AsmJSOption::Enabled + } else { + AsmJSOption::DisabledByAsmJSPref + }; + let wasm_enabled = pref!(js.wasm.enabled); + cx_opts.set_wasm_(wasm_enabled); + if wasm_enabled { + // If WASM is enabled without setting the buildIdOp, + // initializing a module will report an out of memory error. + // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458 + SetProcessBuildIdOp(Some(servo_build_id)); + } + cx_opts.set_wasmBaseline_(pref!(js.wasm.baseline.enabled)); + cx_opts.set_wasmIon_(pref!(js.wasm.ion.enabled)); + // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey) + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE, + pref!(js.native_regex.enabled) as u32, + ); + JS_SetParallelParsingEnabled(cx, pref!(js.parallel_parsing.enabled)); + JS_SetOffthreadIonCompilationEnabled(cx, pref!(js.offthread_compilation.enabled)); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, + if pref!(js.baseline_jit.unsafe_eager_compilation.enabled) { + 0 + } else { + u32::MAX + }, + ); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER, + if pref!(js.ion.unsafe_eager_compilation.enabled) { + 0 + } else { + u32::MAX + }, + ); + // TODO: handle js.discard_system_source.enabled + // TODO: handle js.asyncstack.enabled (needs new Spidermonkey) + // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey) + // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey) + // TODO: handle js.shared_memory.enabled + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_MAX_BYTES, + in_range(pref!(js.mem.max), 1, 0x100) + .map(|val| (val * 1024 * 1024) as u32) + .unwrap_or(u32::MAX), + ); + // NOTE: This is disabled above, so enabling it here will do nothing for now. + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED, + pref!(js.mem.gc.incremental.enabled) as u32, + ); + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED, + pref!(js.mem.gc.per_zone.enabled) as u32, + ); + if let Some(val) = in_range(pref!(js.mem.gc.incremental.slice_ms), 0, 100_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32); + } + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_COMPACTING_ENABLED, + pref!(js.mem.gc.compacting.enabled) as u32, + ); + + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_time_limit_ms), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32); + } + if let Some(val) = in_range(pref!(js.mem.gc.low_frequency_heap_growth), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32); + } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_heap_growth_min), 0, 10_000) { + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH, + val as u32, + ); + } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_heap_growth_max), 0, 10_000) { + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH, + val as u32, + ); + } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_low_limit_mb), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32); + } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_high_limit_mb), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32); + } + /*if let Some(val) = in_range(pref!(js.mem.gc.allocation_threshold_factor), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_NON_INCREMENTAL_FACTOR, val as u32); + }*/ + /* + // JSGC_SMALL_HEAP_INCREMENTAL_LIMIT + pref("javascript.options.mem.gc_small_heap_incremental_limit", 140); + + // JSGC_LARGE_HEAP_INCREMENTAL_LIMIT + pref("javascript.options.mem.gc_large_heap_incremental_limit", 110); + */ + if let Some(val) = in_range(pref!(js.mem.gc.empty_chunk_count_min), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32); + } + if let Some(val) = in_range(pref!(js.mem.gc.empty_chunk_count_max), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_MAX_EMPTY_CHUNK_COUNT, val as u32); + } + + Runtime { + rt: runtime, + microtask_queue, + job_queue, + networking_task_src: (!networking_task_src_ptr.is_null()) + .then(|| Box::from_raw(networking_task_src_ptr)), + } + } + pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext { self.rt.thread_safe_js_context() } @@ -495,245 +747,6 @@ impl Drop for JSEngineSetup { static JS_ENGINE: Mutex> = Mutex::new(None); -#[allow(unsafe_code)] -pub unsafe fn new_child_runtime( - parent: ParentRuntime, - networking_task_source: Option, -) -> Runtime { - new_rt_and_cx_with_parent(Some(parent), networking_task_source) -} - -#[allow(unsafe_code)] -pub fn new_rt_and_cx(networking_task_source: Option) -> Runtime { - unsafe { new_rt_and_cx_with_parent(None, networking_task_source) } -} - -#[allow(unsafe_code)] -unsafe fn new_rt_and_cx_with_parent( - parent: Option, - networking_task_source: Option, -) -> Runtime { - LiveDOMReferences::initialize(); - let (cx, runtime) = if let Some(parent) = parent { - let runtime = RustRuntime::create_with_parent(parent); - let cx = runtime.cx(); - (cx, runtime) - } else { - let runtime = RustRuntime::new(JS_ENGINE.lock().unwrap().as_ref().unwrap().clone()); - (runtime.cx(), runtime) - }; - - JS_AddExtraGCRootsTracer(cx, Some(trace_rust_roots), ptr::null_mut()); - - JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS); - - JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal)); - JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal)); - - // Needed for debug assertions about whether GC is running. - if cfg!(debug_assertions) { - JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut()); - } - - if opts::get().debug.gc_profile { - SetGCSliceCallback(cx, Some(gc_slice_callback)); - } - - unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool { - true - } - unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool { - // fixme: return true when the Drop impl for a DOM object has been invoked - false - } - SetDOMCallbacks(cx, &DOM_CALLBACKS); - SetPreserveWrapperCallbacks( - cx, - Some(empty_wrapper_callback), - Some(empty_has_released_callback), - ); - // Pre barriers aren't working correctly at the moment - DisableIncrementalGC(cx); - - unsafe extern "C" fn dispatch_to_event_loop( - closure: *mut c_void, - dispatchable: *mut JSRunnable, - ) -> bool { - let networking_task_src: &NetworkingTaskSource = &*(closure as *mut NetworkingTaskSource); - let runnable = Runnable(dispatchable); - let task = task!(dispatch_to_event_loop_message: move || { - runnable.run(RustRuntime::get(), Dispatchable_MaybeShuttingDown::NotShuttingDown); - }); - - networking_task_src.queue_unconditionally(task).is_ok() - } - - let mut networking_task_src_ptr = std::ptr::null_mut(); - if let Some(source) = networking_task_source { - networking_task_src_ptr = Box::into_raw(Box::new(source)); - InitDispatchToEventLoop( - cx, - Some(dispatch_to_event_loop), - networking_task_src_ptr as *mut c_void, - ); - } - - InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error)); - - let microtask_queue = Rc::new(MicrotaskQueue::default()); - let job_queue = CreateJobQueue( - &JOB_QUEUE_TRAPS, - &*microtask_queue as *const _ as *const c_void, - ); - SetJobQueue(cx, job_queue); - SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut()); - - EnsureModuleHooksInitialized(runtime.rt()); - - set_gc_zeal_options(cx); - - // Enable or disable the JITs. - let cx_opts = &mut *ContextOptionsRef(cx); - JS_SetGlobalJitCompilerOption( - cx, - JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE, - pref!(js.baseline_interpreter.enabled) as u32, - ); - JS_SetGlobalJitCompilerOption( - cx, - JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE, - pref!(js.baseline_jit.enabled) as u32, - ); - JS_SetGlobalJitCompilerOption( - cx, - JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE, - pref!(js.ion.enabled) as u32, - ); - cx_opts.compileOptions_.asmJSOption_ = if pref!(js.asmjs.enabled) { - AsmJSOption::Enabled - } else { - AsmJSOption::DisabledByAsmJSPref - }; - let wasm_enabled = pref!(js.wasm.enabled); - cx_opts.set_wasm_(wasm_enabled); - if wasm_enabled { - // If WASM is enabled without setting the buildIdOp, - // initializing a module will report an out of memory error. - // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458 - SetProcessBuildIdOp(Some(servo_build_id)); - } - cx_opts.set_wasmBaseline_(pref!(js.wasm.baseline.enabled)); - cx_opts.set_wasmIon_(pref!(js.wasm.ion.enabled)); - // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey) - JS_SetGlobalJitCompilerOption( - cx, - JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE, - pref!(js.native_regex.enabled) as u32, - ); - JS_SetParallelParsingEnabled(cx, pref!(js.parallel_parsing.enabled)); - JS_SetOffthreadIonCompilationEnabled(cx, pref!(js.offthread_compilation.enabled)); - JS_SetGlobalJitCompilerOption( - cx, - JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, - if pref!(js.baseline_jit.unsafe_eager_compilation.enabled) { - 0 - } else { - u32::MAX - }, - ); - JS_SetGlobalJitCompilerOption( - cx, - JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER, - if pref!(js.ion.unsafe_eager_compilation.enabled) { - 0 - } else { - u32::MAX - }, - ); - // TODO: handle js.discard_system_source.enabled - // TODO: handle js.asyncstack.enabled (needs new Spidermonkey) - // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey) - // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey) - // TODO: handle js.shared_memory.enabled - JS_SetGCParameter( - cx, - JSGCParamKey::JSGC_MAX_BYTES, - in_range(pref!(js.mem.max), 1, 0x100) - .map(|val| (val * 1024 * 1024) as u32) - .unwrap_or(u32::MAX), - ); - // NOTE: This is disabled above, so enabling it here will do nothing for now. - JS_SetGCParameter( - cx, - JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED, - pref!(js.mem.gc.incremental.enabled) as u32, - ); - JS_SetGCParameter( - cx, - JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED, - pref!(js.mem.gc.per_zone.enabled) as u32, - ); - if let Some(val) = in_range(pref!(js.mem.gc.incremental.slice_ms), 0, 100_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32); - } - JS_SetGCParameter( - cx, - JSGCParamKey::JSGC_COMPACTING_ENABLED, - pref!(js.mem.gc.compacting.enabled) as u32, - ); - - if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_time_limit_ms), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32); - } - if let Some(val) = in_range(pref!(js.mem.gc.low_frequency_heap_growth), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32); - } - if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_heap_growth_min), 0, 10_000) { - JS_SetGCParameter( - cx, - JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH, - val as u32, - ); - } - if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_heap_growth_max), 0, 10_000) { - JS_SetGCParameter( - cx, - JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH, - val as u32, - ); - } - if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_low_limit_mb), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32); - } - if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_high_limit_mb), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32); - } - /*if let Some(val) = in_range(pref!(js.mem.gc.allocation_threshold_factor), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_NON_INCREMENTAL_FACTOR, val as u32); - }*/ - /* - // JSGC_SMALL_HEAP_INCREMENTAL_LIMIT - pref("javascript.options.mem.gc_small_heap_incremental_limit", 140); - - // JSGC_LARGE_HEAP_INCREMENTAL_LIMIT - pref("javascript.options.mem.gc_large_heap_incremental_limit", 110); - */ - if let Some(val) = in_range(pref!(js.mem.gc.empty_chunk_count_min), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32); - } - if let Some(val) = in_range(pref!(js.mem.gc.empty_chunk_count_max), 0, 10_000) { - JS_SetGCParameter(cx, JSGCParamKey::JSGC_MAX_EMPTY_CHUNK_COUNT, val as u32); - } - - Runtime { - rt: runtime, - microtask_queue, - job_queue, - networking_task_src: (!networking_task_src_ptr.is_null()) - .then(|| Box::from_raw(networking_task_src_ptr)), - } -} - fn in_range(val: T, min: T, max: T) -> Option { if val < min || val >= max { None @@ -758,63 +771,6 @@ unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize { } } -#[allow(unsafe_code)] -pub unsafe fn get_reports(cx: *mut RawJSContext, path_seg: String) -> Vec { - let mut reports = vec![]; - - let mut stats = ::std::mem::zeroed(); - if CollectServoSizes(cx, &mut stats, Some(get_size)) { - let mut report = |mut path_suffix, kind, size| { - let mut path = path![path_seg, "js"]; - path.append(&mut path_suffix); - reports.push(Report { path, kind, size }) - }; - - // A note about possibly confusing terminology: the JS GC "heap" is allocated via - // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use - // `ExplicitNonHeapSize` as its kind. - - report( - path!["gc-heap", "used"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapUsed, - ); - - report( - path!["gc-heap", "unused"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapUnused, - ); - - report( - path!["gc-heap", "admin"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapAdmin, - ); - - report( - path!["gc-heap", "decommitted"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapDecommitted, - ); - - // SpiderMonkey uses the system heap, not jemalloc. - report( - path!["malloc-heap"], - ReportKind::ExplicitSystemHeapSize, - stats.mallocHeap, - ); - - report( - path!["non-heap"], - ReportKind::ExplicitNonHeapSize, - stats.nonHeap, - ); - } - - reports -} - thread_local!(static GC_CYCLE_START: Cell> = const { Cell::new(None) }); thread_local!(static GC_SLICE_START: Cell> = const { Cell::new(None) }); @@ -923,9 +879,73 @@ pub struct JSContext(*mut RawJSContext); #[allow(unsafe_code)] impl JSContext { - pub unsafe fn from_ptr(raw_js_context: *mut RawJSContext) -> Self { + /// Create a new [`JSContext`] object from the given raw pointer. + /// + /// # Safety + /// + /// The `RawJSContext` argument must point to a valid `RawJSContext` in memory. + pub(crate) unsafe fn from_ptr(raw_js_context: *mut RawJSContext) -> Self { JSContext(raw_js_context) } + + #[allow(unsafe_code)] + pub(crate) fn get_reports(&self, path_seg: String) -> Vec { + let stats = unsafe { + let mut stats = ::std::mem::zeroed(); + if !CollectServoSizes(self.0, &mut stats, Some(get_size)) { + return vec![]; + } + stats + }; + + let mut reports = vec![]; + let mut report = |mut path_suffix, kind, size| { + let mut path = path![path_seg, "js"]; + path.append(&mut path_suffix); + reports.push(Report { path, kind, size }) + }; + + // A note about possibly confusing terminology: the JS GC "heap" is allocated via + // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use + // `ExplicitNonHeapSize` as its kind. + report( + path!["gc-heap", "used"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapUsed, + ); + + report( + path!["gc-heap", "unused"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapUnused, + ); + + report( + path!["gc-heap", "admin"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapAdmin, + ); + + report( + path!["gc-heap", "decommitted"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapDecommitted, + ); + + // SpiderMonkey uses the system heap, not jemalloc. + report( + path!["malloc-heap"], + ReportKind::ExplicitSystemHeapSize, + stats.mallocHeap, + ); + + report( + path!["non-heap"], + ReportKind::ExplicitNonHeapSize, + stats.nonHeap, + ); + reports + } } #[allow(unsafe_code)] diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index ae765b9768d..c0dd80b141b 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -145,8 +145,8 @@ use crate::microtask::{Microtask, MicrotaskQueue}; use crate::realms::enter_realm; use crate::script_module::ScriptFetchOptions; use crate::script_runtime::{ - get_reports, new_rt_and_cx, CanGc, CommonScriptMsg, JSContext, Runtime, ScriptChan, ScriptPort, - ScriptThreadEventCategory, ThreadSafeJSContext, + CanGc, CommonScriptMsg, JSContext, Runtime, ScriptChan, ScriptPort, ScriptThreadEventCategory, + ThreadSafeJSContext, }; use crate::task_manager::TaskManager; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; @@ -170,6 +170,11 @@ pub type ImageCacheMsg = (PipelineId, PendingImageResponse); thread_local!(static SCRIPT_THREAD_ROOT: Cell> = const { Cell::new(None) }); +/// # Safety +/// +/// The `JSTracer` argument must point to a valid `JSTracer` in memory. In addition, +/// implementors of this method must ensure that all active objects are properly traced +/// or else the garbage collector may end up collecting objects that are still reachable. pub unsafe fn trace_thread(tr: *mut JSTracer) { SCRIPT_THREAD_ROOT.with(|root| { if let Some(script_thread) = root.get() { @@ -1291,7 +1296,7 @@ impl ScriptThread { let boxed_script_sender = Box::new(MainThreadScriptChan(chan.clone())); - let runtime = new_rt_and_cx(Some(NetworkingTaskSource( + let runtime = Runtime::new(Some(NetworkingTaskSource( boxed_script_sender.clone(), state.id, ))); @@ -2944,11 +2949,8 @@ impl ScriptThread { fn collect_reports(&self, reports_chan: ReportsChan) { let documents = self.documents.borrow(); let urls = itertools::join(documents.iter().map(|(_, d)| d.url().to_string()), ", "); - let path_seg = format!("url({})", urls); - - let mut reports = vec![]; - reports.extend(unsafe { get_reports(*self.get_cx(), path_seg) }); + let mut reports = self.get_cx().get_reports(format!("url({})", urls)); for (_, document) in documents.iter() { document.window().layout().collect_reports(&mut reports); }