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 
5 //! The script runtime contains common traits and structs commonly used by the
6 //! script thread, the dom, and the worker threads.
7 
8 use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
9 use dom::bindings::refcounted::{LiveDOMReferences, trace_refcounted_objects};
10 use dom::bindings::root::trace_roots;
11 use dom::bindings::settings_stack;
12 use dom::bindings::trace::{JSTraceable, trace_traceables};
13 use dom::bindings::utils::DOM_CALLBACKS;
14 use dom::globalscope::GlobalScope;
15 use js::glue::CollectServoSizes;
16 use js::jsapi::{DisableIncrementalGC, GCDescription, GCProgress, HandleObject};
17 use js::jsapi::{JSContext, JS_GetRuntime, JSRuntime, JSTracer, SetDOMCallbacks, SetGCSliceCallback};
18 use js::jsapi::{JSGCInvocationKind, JSGCStatus, JS_AddExtraGCRootsTracer, JS_SetGCCallback};
19 use js::jsapi::{JSGCMode, JSGCParamKey, JS_SetGCParameter, JS_SetGlobalJitCompilerOption};
20 use js::jsapi::{JSJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled};
21 use js::jsapi::{JSObject, RuntimeOptionsRef, SetPreserveWrapperCallback, SetEnqueuePromiseJobCallback};
22 use js::panic::wrap_panic;
23 use js::rust::Runtime as RustRuntime;
24 use microtask::{EnqueuedPromiseCallback, Microtask};
25 use msg::constellation_msg::PipelineId;
26 use profile_traits::mem::{Report, ReportKind, ReportsChan};
27 use script_thread::trace_thread;
28 use servo_config::opts;
29 use servo_config::prefs::PREFS;
30 use std::cell::Cell;
31 use std::fmt;
32 use std::io::{Write, stdout};
33 use std::ops::Deref;
34 use std::os;
35 use std::os::raw::c_void;
36 use std::panic::AssertUnwindSafe;
37 use std::ptr;
38 use style::thread_state::{self, ThreadState};
39 use task::TaskBox;
40 use time::{Tm, now};
41 
42 /// Common messages used to control the event loops in both the script and the worker
43 pub enum CommonScriptMsg {
44     /// Requests that the script thread measure its memory usage. The results are sent back via the
45     /// supplied channel.
46     CollectReports(ReportsChan),
47     /// Generic message that encapsulates event handling.
48     Task(ScriptThreadEventCategory, Box<TaskBox>, Option<PipelineId>),
49 }
50 
51 impl fmt::Debug for CommonScriptMsg {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result52     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53         match *self {
54             CommonScriptMsg::CollectReports(_) => write!(f, "CollectReports(...)"),
55             CommonScriptMsg::Task(ref category, ref task, _) => {
56                 f.debug_tuple("Task").field(category).field(task).finish()
57             },
58         }
59     }
60 }
61 
62 /// A cloneable interface for communicating with an event loop.
63 pub trait ScriptChan: JSTraceable {
64     /// Send a message to the associated event loop.
send(&self, msg: CommonScriptMsg) -> Result<(), ()>65     fn send(&self, msg: CommonScriptMsg) -> Result<(), ()>;
66     /// Clone this handle.
clone(&self) -> Box<ScriptChan + Send>67     fn clone(&self) -> Box<ScriptChan + Send>;
68 }
69 
70 #[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
71 pub enum ScriptThreadEventCategory {
72     AttachLayout,
73     ConstellationMsg,
74     DevtoolsMsg,
75     DocumentEvent,
76     DomEvent,
77     FileRead,
78     FormPlannedNavigation,
79     ImageCacheMsg,
80     InputEvent,
81     NetworkEvent,
82     Resize,
83     ScriptEvent,
84     SetScrollState,
85     SetViewport,
86     StylesheetLoad,
87     TimerEvent,
88     UpdateReplacedElement,
89     WebSocketEvent,
90     WorkerEvent,
91     WorkletEvent,
92     ServiceWorkerEvent,
93     EnterFullscreen,
94     ExitFullscreen,
95     WebVREvent,
96     PerformanceTimelineTask,
97 }
98 
99 /// An interface for receiving ScriptMsg values in an event loop. Used for synchronous DOM
100 /// APIs that need to abstract over multiple kinds of event loops (worker/main thread) with
101 /// different Receiver interfaces.
102 pub trait ScriptPort {
recv(&self) -> Result<CommonScriptMsg, ()>103     fn recv(&self) -> Result<CommonScriptMsg, ()>;
104 }
105 
106 /// SM callback for promise job resolution. Adds a promise callback to the current
107 /// global's microtask queue.
108 #[allow(unsafe_code)]
enqueue_job(cx: *mut JSContext, job: HandleObject, _allocation_site: HandleObject, _data: *mut c_void) -> bool109 unsafe extern "C" fn enqueue_job(cx: *mut JSContext,
110                                  job: HandleObject,
111                                  _allocation_site: HandleObject,
112                                  _data: *mut c_void) -> bool {
113     wrap_panic(AssertUnwindSafe(|| {
114         //XXXjdm - use a different global now?
115         let global = GlobalScope::from_object(job.get());
116         let pipeline = global.pipeline_id();
117         global.enqueue_microtask(Microtask::Promise(EnqueuedPromiseCallback {
118             callback: PromiseJobCallback::new(cx, job.get()),
119             pipeline: pipeline,
120         }));
121         true
122     }), false)
123 }
124 
125 #[derive(JSTraceable)]
126 pub struct Runtime(RustRuntime);
127 
128 impl Drop for Runtime {
drop(&mut self)129     fn drop(&mut self) {
130         THREAD_ACTIVE.with(|t| t.set(false));
131     }
132 }
133 
134 impl Deref for Runtime {
135     type Target = RustRuntime;
deref(&self) -> &RustRuntime136     fn deref(&self) -> &RustRuntime {
137         &self.0
138     }
139 }
140 
141 #[allow(unsafe_code)]
new_rt_and_cx() -> Runtime142 pub unsafe fn new_rt_and_cx() -> Runtime {
143     LiveDOMReferences::initialize();
144     let runtime = RustRuntime::new().unwrap();
145 
146     JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_rust_roots), ptr::null_mut());
147 
148     // Needed for debug assertions about whether GC is running.
149     if cfg!(debug_assertions) {
150         JS_SetGCCallback(runtime.rt(), Some(debug_gc_callback), ptr::null_mut());
151     }
152 
153     if opts::get().gc_profile {
154         SetGCSliceCallback(runtime.rt(), Some(gc_slice_callback));
155     }
156 
157     unsafe extern "C" fn empty_wrapper_callback(_: *mut JSContext, _: *mut JSObject) -> bool { true }
158     SetDOMCallbacks(runtime.rt(), &DOM_CALLBACKS);
159     SetPreserveWrapperCallback(runtime.rt(), Some(empty_wrapper_callback));
160     // Pre barriers aren't working correctly at the moment
161     DisableIncrementalGC(runtime.rt());
162 
163     SetEnqueuePromiseJobCallback(runtime.rt(), Some(enqueue_job), ptr::null_mut());
164 
165     set_gc_zeal_options(runtime.rt());
166 
167     // Enable or disable the JITs.
168     let rt_opts = &mut *RuntimeOptionsRef(runtime.rt());
169     if let Some(val) = PREFS.get("js.baseline.enabled").as_boolean() {
170         rt_opts.set_baseline_(val);
171     }
172     if let Some(val) = PREFS.get("js.ion.enabled").as_boolean() {
173         rt_opts.set_ion_(val);
174     }
175     if let Some(val) = PREFS.get("js.asmjs.enabled").as_boolean() {
176         rt_opts.set_asmJS_(val);
177     }
178     if let Some(val) = PREFS.get("js.strict.enabled").as_boolean() {
179         rt_opts.set_extraWarnings_(val);
180     }
181     // TODO: handle js.strict.debug.enabled
182     // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey)
183     if let Some(val) = PREFS.get("js.native_regexp.enabled").as_boolean() {
184         rt_opts.set_nativeRegExp_(val);
185     }
186     if let Some(val) = PREFS.get("js.parallel_parsing.enabled").as_boolean() {
187         JS_SetParallelParsingEnabled(runtime.rt(), val);
188     }
189     if let Some(val) = PREFS.get("js.offthread_compilation_enabled").as_boolean() {
190         JS_SetOffthreadIonCompilationEnabled(runtime.rt(), val);
191     }
192     if let Some(val) = PREFS.get("js.baseline.unsafe_eager_compilation.enabled").as_boolean() {
193         let trigger: i32 = if val {
194             0
195         } else {
196             -1
197         };
198         JS_SetGlobalJitCompilerOption(runtime.rt(),
199                                       JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
200                                       trigger as u32);
201     }
202     if let Some(val) = PREFS.get("js.ion.unsafe_eager_compilation.enabled").as_boolean() {
203         let trigger: i64 = if val {
204             0
205         } else {
206             -1
207         };
208         JS_SetGlobalJitCompilerOption(runtime.rt(),
209                                       JSJitCompilerOption::JSJITCOMPILER_ION_WARMUP_TRIGGER,
210                                       trigger as u32);
211     }
212     // TODO: handle js.discard_system_source.enabled
213     // TODO: handle js.asyncstack.enabled (needs new Spidermonkey)
214     // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey)
215     // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey)
216     if let Some(val) = PREFS.get("js.werror.enabled").as_boolean() {
217         rt_opts.set_werror_(val);
218     }
219     // TODO: handle js.shared_memory.enabled
220     if let Some(val) = PREFS.get("js.mem.high_water_mark").as_i64() {
221         JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MAX_MALLOC_BYTES, val as u32 * 1024 * 1024);
222     }
223     if let Some(val) = PREFS.get("js.mem.max").as_i64() {
224         let max = if val <= 0 || val >= 0x1000 {
225             -1
226         } else {
227             val * 1024 * 1024
228         };
229         JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MAX_BYTES, max as u32);
230     }
231     // NOTE: This is disabled above, so enabling it here will do nothing for now.
232     if let Some(val) = PREFS.get("js.mem.gc.incremental.enabled").as_boolean() {
233         let compartment = if let Some(val) = PREFS.get("js.mem.gc.per_compartment.enabled").as_boolean() {
234             val
235         } else {
236             false
237         };
238         let mode = if val {
239             JSGCMode::JSGC_MODE_INCREMENTAL
240         } else if compartment {
241             JSGCMode::JSGC_MODE_COMPARTMENT
242         } else {
243             JSGCMode::JSGC_MODE_GLOBAL
244         };
245         JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MODE, mode as u32);
246     }
247     if let Some(val) = PREFS.get("js.mem.gc.incremental.slice_ms").as_i64() {
248         if val >= 0 && val < 100000 {
249             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_SLICE_TIME_BUDGET, val as u32);
250         }
251     }
252     if let Some(val) = PREFS.get("js.mem.gc.compacting.enabled").as_boolean() {
253         JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_COMPACTING_ENABLED, val as u32);
254     }
255     if let Some(val) = PREFS.get("js.mem.gc.high_frequency_time_limit_ms").as_i64() {
256         if val >= 0 && val < 10000 {
257             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32);
258         }
259     }
260     if let Some(val) = PREFS.get("js.mem.gc.dynamic_mark_slice.enabled").as_boolean() {
261         JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_DYNAMIC_MARK_SLICE, val as u32);
262     }
263     if let Some(val) = PREFS.get("js.mem.gc.dynamic_heap_growth.enabled").as_boolean() {
264         JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_DYNAMIC_HEAP_GROWTH, val as u32);
265     }
266     if let Some(val) = PREFS.get("js.mem.gc.low_frequency_heap_growth").as_i64() {
267         if val >= 0 && val < 10000 {
268             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32);
269         }
270     }
271     if let Some(val) = PREFS.get("js.mem.gc.high_frequency_heap_growth_min").as_i64() {
272         if val >= 0 && val < 10000 {
273             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN, val as u32);
274         }
275     }
276     if let Some(val) = PREFS.get("js.mem.gc.high_frequency_heap_growth_max").as_i64() {
277         if val >= 0 && val < 10000 {
278             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX, val as u32);
279         }
280     }
281     if let Some(val) = PREFS.get("js.mem.gc.high_frequency_low_limit_mb").as_i64() {
282         if val >= 0 && val < 10000 {
283             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_LOW_LIMIT, val as u32);
284         }
285     }
286     if let Some(val) = PREFS.get("js.mem.gc.high_frequency_high_limit_mb").as_i64() {
287         if val >= 0 && val < 10000 {
288             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_HIGH_LIMIT, val as u32);
289         }
290     }
291     if let Some(val) = PREFS.get("js.mem.gc.allocation_threshold_mb").as_i64() {
292         if val >= 0 && val < 10000 {
293             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_ALLOCATION_THRESHOLD, val as u32);
294         }
295     }
296     if let Some(val) = PREFS.get("js.mem.gc.decommit_threshold_mb").as_i64() {
297         if val >= 0 && val < 10000 {
298             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_DECOMMIT_THRESHOLD, val as u32);
299         }
300     }
301     if let Some(val) = PREFS.get("js.mem.gc.empty_chunk_count_min").as_i64() {
302         if val >= 0 && val < 10000 {
303             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32);
304         }
305     }
306     if let Some(val) = PREFS.get("js.mem.gc.empty_chunk_count_max").as_i64() {
307         if val >= 0 && val < 10000 {
308             JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MAX_EMPTY_CHUNK_COUNT, val as u32);
309         }
310     }
311 
312     Runtime(runtime)
313 }
314 
315 #[allow(unsafe_code)]
get_reports(cx: *mut JSContext, path_seg: String) -> Vec<Report>316 pub fn get_reports(cx: *mut JSContext, path_seg: String) -> Vec<Report> {
317     let mut reports = vec![];
318 
319     unsafe {
320         let rt = JS_GetRuntime(cx);
321         let mut stats = ::std::mem::zeroed();
322         if CollectServoSizes(rt, &mut stats) {
323             let mut report = |mut path_suffix, kind, size| {
324                 let mut path = path![path_seg, "js"];
325                 path.append(&mut path_suffix);
326                 reports.push(Report {
327                     path: path,
328                     kind: kind,
329                     size: size as usize,
330                 })
331             };
332 
333             // A note about possibly confusing terminology: the JS GC "heap" is allocated via
334             // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use
335             // `ExplicitNonHeapSize` as its kind.
336 
337             report(path!["gc-heap", "used"],
338                    ReportKind::ExplicitNonHeapSize,
339                    stats.gcHeapUsed);
340 
341             report(path!["gc-heap", "unused"],
342                    ReportKind::ExplicitNonHeapSize,
343                    stats.gcHeapUnused);
344 
345             report(path!["gc-heap", "admin"],
346                    ReportKind::ExplicitNonHeapSize,
347                    stats.gcHeapAdmin);
348 
349             report(path!["gc-heap", "decommitted"],
350                    ReportKind::ExplicitNonHeapSize,
351                    stats.gcHeapDecommitted);
352 
353             // SpiderMonkey uses the system heap, not jemalloc.
354             report(path!["malloc-heap"],
355                    ReportKind::ExplicitSystemHeapSize,
356                    stats.mallocHeap);
357 
358             report(path!["non-heap"],
359                    ReportKind::ExplicitNonHeapSize,
360                    stats.nonHeap);
361         }
362     }
363     reports
364 }
365 
366 thread_local!(static GC_CYCLE_START: Cell<Option<Tm>> = Cell::new(None));
367 thread_local!(static GC_SLICE_START: Cell<Option<Tm>> = Cell::new(None));
368 
369 #[allow(unsafe_code)]
gc_slice_callback(_rt: *mut JSRuntime, progress: GCProgress, desc: *const GCDescription)370 unsafe extern "C" fn gc_slice_callback(_rt: *mut JSRuntime, progress: GCProgress, desc: *const GCDescription) {
371     match progress {
372         GCProgress::GC_CYCLE_BEGIN => {
373             GC_CYCLE_START.with(|start| {
374                 start.set(Some(now()));
375                 println!("GC cycle began");
376             })
377         },
378         GCProgress::GC_SLICE_BEGIN => {
379             GC_SLICE_START.with(|start| {
380                 start.set(Some(now()));
381                 println!("GC slice began");
382             })
383         },
384         GCProgress::GC_SLICE_END => {
385             GC_SLICE_START.with(|start| {
386                 let dur = now() - start.get().unwrap();
387                 start.set(None);
388                 println!("GC slice ended: duration={}", dur);
389             })
390         },
391         GCProgress::GC_CYCLE_END => {
392             GC_CYCLE_START.with(|start| {
393                 let dur = now() - start.get().unwrap();
394                 start.set(None);
395                 println!("GC cycle ended: duration={}", dur);
396             })
397         },
398     };
399     if !desc.is_null() {
400         let desc: &GCDescription = &*desc;
401         let invocation_kind = match desc.invocationKind_ {
402             JSGCInvocationKind::GC_NORMAL => "GC_NORMAL",
403             JSGCInvocationKind::GC_SHRINK => "GC_SHRINK",
404         };
405         println!("  isCompartment={}, invocation_kind={}", desc.isCompartment_, invocation_kind);
406     }
407     let _ = stdout().flush();
408 }
409 
410 #[allow(unsafe_code)]
debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus, _data: *mut os::raw::c_void)411 unsafe extern "C" fn debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus, _data: *mut os::raw::c_void) {
412     match status {
413         JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC),
414         JSGCStatus::JSGC_END   => thread_state::exit(ThreadState::IN_GC),
415     }
416 }
417 
418 thread_local!(
419     static THREAD_ACTIVE: Cell<bool> = Cell::new(true);
420 );
421 
422 #[allow(unsafe_code)]
trace_rust_roots(tr: *mut JSTracer, _data: *mut os::raw::c_void)423 unsafe extern fn trace_rust_roots(tr: *mut JSTracer, _data: *mut os::raw::c_void) {
424     if !THREAD_ACTIVE.with(|t| t.get()) {
425         return;
426     }
427     debug!("starting custom root handler");
428     trace_thread(tr);
429     trace_traceables(tr);
430     trace_roots(tr);
431     trace_refcounted_objects(tr);
432     settings_stack::trace(tr);
433     debug!("done custom root handler");
434 }
435 
436 #[allow(unsafe_code)]
437 #[cfg(feature = "debugmozjs")]
set_gc_zeal_options(rt: *mut JSRuntime)438 unsafe fn set_gc_zeal_options(rt: *mut JSRuntime) {
439     use js::jsapi::{JS_DEFAULT_ZEAL_FREQ, JS_SetGCZeal};
440 
441     let level = match PREFS.get("js.mem.gc.zeal.level").as_i64() {
442         Some(level @ 0...14) => level as u8,
443         _ => return,
444     };
445     let frequency = match PREFS.get("js.mem.gc.zeal.frequency").as_i64() {
446         Some(frequency) if frequency >= 0 => frequency as u32,
447         _ => JS_DEFAULT_ZEAL_FREQ,
448     };
449     JS_SetGCZeal(rt, level, frequency);
450 }
451 
452 #[allow(unsafe_code)]
453 #[cfg(not(feature = "debugmozjs"))]
set_gc_zeal_options(_: *mut JSRuntime)454 unsafe fn set_gc_zeal_options(_: *mut JSRuntime) {}
455