1use std::cell::{Cell, RefCell, RefMut};
8use std::collections::HashMap;
9use std::rc::Rc;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicBool, Ordering};
12
13use oxide_core::Handle;
14use oxide_core::host::Host;
15use oxide_core::vm::module::ModuleTables;
16use oxide_core::{MemoryReport, NativeCx, RuntimeOptions};
17
18use super::RuntimeError;
19use super::context::Context;
20use super::module::ModuleLoader;
21use super::ops::View;
22use super::property::{
23 CompleteOrdinaryPropertyDescriptor, OrdinaryPropertyDescriptor, PropertyKey, WellKnownSymbol,
24};
25use super::value::{CallableRef, ObjectRef, Slot, SymbolRef, Value};
26use crate::engine::code::debug::DebugInfoMode;
27use crate::engine::value::JsString;
28
29#[derive(Clone, Debug)]
35pub struct InterruptHandle {
36 flag: Arc<AtomicBool>,
37}
38
39impl InterruptHandle {
40 pub fn interrupt(&self) {
41 self.flag.store(true, Ordering::Relaxed);
42 }
43
44 pub fn clear(&self) {
45 self.flag.store(false, Ordering::Relaxed);
46 }
47
48 pub fn is_set(&self) -> bool {
49 self.flag.load(Ordering::Relaxed)
50 }
51}
52
53#[derive(Clone)]
55pub struct Runtime {
56 pub(crate) shared: Rc<Shared>,
57}
58
59pub(crate) enum Preloaded {
62 Compiled {
63 function: ObjectRef,
64 tables: ModuleTables,
65 import_meta: Vec<(String, Value)>,
66 },
67 Synthetic(Vec<(String, Value)>),
68}
69
70pub(crate) struct Shared {
71 pub(crate) inner: RefCell<Inner>,
72 released: RefCell<Vec<Handle>>,
75 pub(crate) poisoned: Cell<bool>,
77 interrupt: Arc<AtomicBool>,
79 pub(crate) loader: RefCell<Option<Box<dyn ModuleLoader>>>,
80 pub(crate) modules: RefCell<HashMap<String, Preloaded>>,
81 pub(crate) debug_info: Cell<DebugInfoMode>,
82 turns: Cell<u32>,
84}
85
86impl Shared {
87 pub(crate) fn release(&self, handle: Handle) {
90 match self.inner.try_borrow_mut() {
91 Ok(mut inner) => inner.core.release(handle),
92 Err(_) => self.released.borrow_mut().push(handle),
93 }
94 }
95
96 pub(crate) fn drain_released(&self, core: &mut NativeCx<'_>) {
98 let released = std::mem::take(&mut *self.released.borrow_mut());
99 for handle in released {
100 core.release(handle);
101 }
102 }
103}
104
105pub(crate) struct Inner {
106 pub(crate) core: oxide_core::Runtime,
107}
108
109const NATIVE_RESERVE: usize = if cfg!(debug_assertions) {
112 256 * 1024
113} else {
114 64 * 1024
115};
116
117thread_local! {
118 static CURRENT: RefCell<Vec<Rc<Shared>>> = const { RefCell::new(Vec::new()) };
121}
122
123pub(crate) fn current() -> Option<Rc<Shared>> {
125 CURRENT.with(|current| current.borrow().last().cloned())
126}
127
128pub(crate) fn current_debug_info() -> Option<DebugInfoMode> {
129 current().map(|shared| shared.debug_info.get())
130}
131
132pub(crate) struct Entry<'a> {
134 pub(crate) inner: RefMut<'a, Inner>,
135 pub(crate) shared: &'a Rc<Shared>,
136}
137
138impl Drop for Entry<'_> {
139 fn drop(&mut self) {
140 CURRENT.with(|current| current.borrow_mut().pop());
141 }
142}
143
144impl Entry<'_> {
145 pub(crate) fn view(&mut self) -> View<'_> {
147 View::new(self.inner.core.native_cx(), self.shared)
148 }
149}
150
151impl std::ops::Deref for Entry<'_> {
152 type Target = Inner;
153
154 fn deref(&self) -> &Inner {
155 &self.inner
156 }
157}
158
159impl std::ops::DerefMut for Entry<'_> {
160 fn deref_mut(&mut self) -> &mut Inner {
161 &mut self.inner
162 }
163}
164
165pub(crate) fn slot(shared: &Rc<Shared>, handle: Handle, value: oxide_core::Value) -> Rc<Slot> {
167 Rc::new(Slot {
168 shared: Rc::downgrade(shared),
169 handle,
170 value,
171 })
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub enum PendingJobOutcome {
177 NoJob,
179 Executed,
181}
182
183impl PendingJobOutcome {
184 pub const fn executed(self) -> bool {
185 matches!(self, Self::Executed)
186 }
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
191pub enum PromiseState {
192 Pending,
193 Fulfilled,
194 Rejected,
195}
196
197#[derive(Clone, Debug, PartialEq)]
199pub struct PromiseSnapshot {
200 state: PromiseState,
201 result: Value,
202}
203
204impl PromiseSnapshot {
205 pub(crate) fn new(state: PromiseState, result: Value) -> Self {
206 PromiseSnapshot { state, result }
207 }
208
209 pub fn try_clone(&self) -> Result<Self, RuntimeError> {
210 Ok(self.clone())
211 }
212
213 pub const fn state(&self) -> PromiseState {
214 self.state
215 }
216
217 pub const fn result(&self) -> &Value {
219 &self.result
220 }
221}
222
223#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
225pub enum GcPolicy {
226 #[default]
229 Automatic,
230 Manual,
232}
233
234pub use oxide_core::heap::gc::GcStats;
236
237pub use oxide_core::host::SystemHost;
239
240impl Default for Runtime {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246impl Runtime {
247 pub fn new() -> Self {
249 Self::with_options(RuntimeOptions::default())
250 }
251
252 pub fn with_options(options: RuntimeOptions) -> Self {
253 Self::with_host(SystemHost, options)
254 }
255
256 pub fn with_host(host: impl Host + 'static, options: RuntimeOptions) -> Self {
258 let mut core = oxide_core::Runtime::new(options)
259 .expect("the initial realm fits any heap cap the options allow");
260 core.set_host(Box::new(host));
261 core.set_compiler(super::compile::compile_for_core);
262 core.set_module_host(oxide_core::vm::module::ModuleHost {
263 normalize: super::module::normalize_shim,
264 load: super::module::load_shim,
265 });
266 let interrupt = core.native_cx().interrupt_flag();
267 Runtime {
268 shared: Rc::new(Shared {
269 interrupt,
270 inner: RefCell::new(Inner { core }),
271 released: RefCell::new(Vec::new()),
272 poisoned: Cell::new(false),
273 loader: RefCell::new(None),
274 modules: RefCell::new(HashMap::new()),
275 debug_info: Cell::new(DebugInfoMode::Full),
276 turns: Cell::new(0),
277 }),
278 }
279 }
280
281 pub fn new_with_host_services(host: impl Host + 'static) -> Self {
284 Self::with_host(host, RuntimeOptions::default())
285 }
286
287 pub(crate) fn enter(&self) -> Result<Entry<'_>, RuntimeError> {
289 if self.shared.poisoned.get() {
290 return Err(RuntimeError::Poisoned);
291 }
292 let mut inner = self
293 .shared
294 .inner
295 .try_borrow_mut()
296 .map_err(|_| RuntimeError::Reentered)?;
297 self.shared.drain_released(&mut inner.core.native_cx());
298 if CURRENT.with(|current| current.borrow().is_empty()) {
299 inner.core.native_cx().clear_interrupted();
300 let (available, declared) = crate::engine::compiler::stack_guard::available_stack();
306 let budget = available
307 .saturating_sub(available / 8)
308 .saturating_sub(NATIVE_RESERVE)
309 .max(available / 2);
310 let budget = if declared {
311 budget
312 } else {
313 budget.min(oxide_core::vm::NATIVE_STACK_BUDGET)
314 };
315 inner.core.set_native_stack_budget(budget);
316 }
317 CURRENT.with(|current| current.borrow_mut().push(Rc::clone(&self.shared)));
318 Ok(Entry {
319 inner,
320 shared: &self.shared,
321 })
322 }
323
324 pub fn context(&self) -> Context {
326 Context {
327 runtime: self.clone(),
328 }
329 }
330
331 pub fn new_context(&self) -> Result<Context, RuntimeError> {
334 Ok(self.context())
335 }
336
337 pub fn is_same_runtime(&self, other: &Self) -> bool {
339 Rc::ptr_eq(&self.shared, &other.shared)
340 }
341
342 pub fn interrupt_handle(&self) -> InterruptHandle {
344 InterruptHandle {
345 flag: Arc::clone(&self.shared.interrupt),
346 }
347 }
348
349 pub fn set_can_block(&self, can_block: bool) -> Result<(), RuntimeError> {
351 self.enter()?.core.set_can_block(can_block);
352 Ok(())
353 }
354
355 pub fn set_debug_info_mode(&self, mode: DebugInfoMode) -> Result<(), RuntimeError> {
357 self.shared.debug_info.set(mode);
358 Ok(())
359 }
360
361 pub fn debug_info_mode(&self) -> Result<DebugInfoMode, RuntimeError> {
362 Ok(self.shared.debug_info.get())
363 }
364
365 pub fn set_module_loader(
367 &self,
368 loader: impl ModuleLoader + 'static,
369 ) -> Result<(), RuntimeError> {
370 let mut slot = self
371 .shared
372 .loader
373 .try_borrow_mut()
374 .map_err(|_| RuntimeError::Reentered)?;
375 *slot = Some(Box::new(loader));
376 Ok(())
377 }
378
379 pub fn is_job_pending(&self) -> Result<bool, RuntimeError> {
383 Ok(self.enter()?.core.has_pending_jobs())
384 }
385
386 pub fn run_pending_job(&self) -> Result<PendingJobOutcome, RuntimeError> {
391 if self.shared.turns.get() > 0 {
392 return Err(RuntimeError::Invariant(
393 "a job was dispatched inside an execution turn",
394 ));
395 }
396 let mut entry = self.enter()?;
397 let result = entry.inner.core.run_pending_job();
398 match entry.view().complete_with(result)? {
399 true => Ok(PendingJobOutcome::Executed),
400 false => Ok(PendingJobOutcome::NoJob),
401 }
402 }
403
404 pub fn execute_pending_job(&self) -> Result<PendingJobOutcome, RuntimeError> {
406 self.run_pending_job()
407 }
408
409 pub fn run_jobs(&self) -> Result<(), RuntimeError> {
411 while self.run_pending_job()?.executed() {}
412 Ok(())
413 }
414
415 pub fn with_execution_turn<T>(
419 &self,
420 body: impl FnOnce() -> Result<T, RuntimeError>,
421 ) -> Result<T, RuntimeError> {
422 self.shared.turns.set(self.shared.turns.get() + 1);
423 let result = body();
424 let turns = self.shared.turns.get() - 1;
425 self.shared.turns.set(turns);
426 if turns == 0 {
427 self.enter()?.core.clear_kept_objects();
428 }
429 result
430 }
431
432 pub fn promise_snapshot(
434 &self,
435 promise: &ObjectRef,
436 ) -> Result<Option<PromiseSnapshot>, RuntimeError> {
437 self.enter()?.view().promise_snapshot(promise)
438 }
439
440 pub fn set_track_rejections(&self, track: bool) -> Result<(), RuntimeError> {
443 self.enter()?.core.set_track_rejections(track);
444 Ok(())
445 }
446
447 pub fn take_unhandled_rejections(&self) -> Result<Vec<(ObjectRef, Value)>, RuntimeError> {
450 let mut entry = self.enter()?;
451 let promises = entry.inner.core.unhandled_rejections().to_vec();
452 let mut rejections = Vec::with_capacity(promises.len());
453 let mut view = entry.view();
454 for promise in promises {
455 let reason = view
456 .core
457 .promise_state(promise)
458 .map_or(oxide_core::Value::UNDEFINED, |(_, reason)| reason);
459 let promise = view.lift(promise)?;
460 let reason = view.lift(reason)?;
461 if let Value::Object(promise) = promise {
462 rejections.push((promise, reason));
463 }
464 }
465 drop(view);
466 entry.inner.core.clear_unhandled_rejections();
467 Ok(rejections)
468 }
469
470 pub fn run_gc(&self) -> Result<GcStats, RuntimeError> {
474 let mut entry = self.enter()?;
475 entry.core.run_gc();
476 Ok(entry.core.heap().gc().stats)
477 }
478
479 pub fn gc_stats(&self) -> Result<GcStats, RuntimeError> {
480 Ok(self.enter()?.core.heap().gc().stats)
481 }
482
483 pub fn memory_report(&self) -> Result<MemoryReport, RuntimeError> {
485 Ok(self.enter()?.core.memory_report())
486 }
487
488 pub fn set_gc_policy(&self, policy: GcPolicy) -> Result<(), RuntimeError> {
491 self.enter()?
492 .core
493 .heap_mut()
494 .set_gc_manual(policy == GcPolicy::Manual);
495 Ok(())
496 }
497
498 pub fn gc_policy(&self) -> Result<GcPolicy, RuntimeError> {
499 Ok(if self.enter()?.core.heap().gc().is_manual() {
500 GcPolicy::Manual
501 } else {
502 GcPolicy::Automatic
503 })
504 }
505
506 pub fn set_gc_stress(&self, interval: Option<u32>) -> Result<(), RuntimeError> {
510 let mut entry = self.enter()?;
511 #[cfg(feature = "gc-stress")]
512 {
513 let gc = entry.core.heap_mut().gc_mut();
514 gc.stress = interval.is_some_and(|interval| interval > 0);
515 gc.stress_interval = interval.unwrap_or(1).max(1);
516 Ok(())
517 }
518 #[cfg(not(feature = "gc-stress"))]
519 {
520 let _ = &mut entry;
521 match interval {
522 Some(_) => Err(RuntimeError::Unsupported(
523 "stress collections in this build",
524 )),
525 None => Ok(()),
526 }
527 }
528 }
529
530 pub const GC_STRESS_AVAILABLE: bool = cfg!(feature = "gc-stress");
532
533 pub const IC_STATS_AVAILABLE: bool = cfg!(feature = "ic-stats");
536
537 pub fn set_record_gc_pauses(&self, record: bool) -> Result<(), RuntimeError> {
539 self.enter()?.core.heap_mut().gc_mut().record_pauses = record;
540 Ok(())
541 }
542
543 pub fn gc_report(&self) -> Result<String, RuntimeError> {
546 let entry = self.enter()?;
547 let gc = entry.core.heap().gc();
548 let stats = gc.stats;
549 let mut pauses = gc.pauses.clone();
550 pauses.sort();
551 let percentile = |p: usize| {
552 pauses
553 .get((pauses.len() * p / 100).min(pauses.len().saturating_sub(1)))
554 .map_or(0.0, |pause| pause.as_secs_f64() * 1e3)
555 };
556 Ok(format!(
557 "gc: {} collections, total pause {:.1} ms, p50 {:.3} ms, p99 {:.3} ms, max {:.3} ms, \
558 live {} KiB, reserved {} KiB\n",
559 stats.collections,
560 stats.total_pause.as_secs_f64() * 1e3,
561 percentile(50),
562 percentile(99),
563 stats.max_pause.as_secs_f64() * 1e3,
564 stats.live_bytes / 1024,
565 entry.core.heap().bytes() / 1024,
566 ))
567 }
568
569 pub fn ic_report(&self, worst: usize) -> Result<Option<String>, RuntimeError> {
571 Ok(self.enter()?.core.ic_report(worst))
572 }
573
574 pub fn disassemble(&self, source: &[u8], filename: &str) -> Result<String, RuntimeError> {
576 let mut entry = self.enter()?;
577 let debug_info = self.shared.debug_info.get();
578 let lowered = {
579 let mut compiler = entry.inner.core.compiler();
580 super::compile::compile_script(&mut compiler, source, filename, debug_info)
581 };
582 let lowered = match lowered {
583 Ok(lowered) => lowered,
584 Err(super::compile::ScriptCompileError::Front(error)) => {
585 return Err(RuntimeError::Compile(error));
586 }
587 Err(super::compile::ScriptCompileError::Lower(error)) => {
588 return Err(RuntimeError::Internal(error.to_string()));
589 }
590 };
591 let mut text = String::new();
592 for unit in entry.core.units().tree(lowered.root) {
593 text.push_str(&entry.core.disassemble(unit));
594 text.push('\n');
595 }
596 Ok(text)
597 }
598
599 pub fn intern_property_key(&self, text: &str) -> Result<PropertyKey, RuntimeError> {
602 Ok(PropertyKey::from_js_string(JsString::try_from_utf8(text)?))
603 }
604
605 pub fn intern_property_key_js_string(
606 &self,
607 text: &JsString,
608 ) -> Result<PropertyKey, RuntimeError> {
609 Ok(PropertyKey::from_js_string(text.clone()))
610 }
611
612 pub fn property_key_to_js_string(&self, key: &PropertyKey) -> Result<JsString, RuntimeError> {
615 match key.as_symbol() {
616 Some(symbol) => Ok(self
617 .symbol_description(symbol)?
618 .unwrap_or_else(|| JsString::from_static(""))),
619 None => key
620 .to_js_string()
621 .ok_or(RuntimeError::Invariant("a string key")),
622 }
623 }
624
625 pub fn new_symbol(&self, description: Option<JsString>) -> Result<SymbolRef, RuntimeError> {
626 self.enter()?.view().new_symbol(description)
627 }
628
629 pub fn symbol_description(&self, symbol: &SymbolRef) -> Result<Option<JsString>, RuntimeError> {
630 self.enter()?.view().symbol_description(symbol)
631 }
632
633 pub fn well_known_symbol(&self, symbol: WellKnownSymbol) -> Result<SymbolRef, RuntimeError> {
634 self.enter()?.view().well_known_symbol(symbol)
635 }
636
637 pub fn get_prototype_of(&self, object: &ObjectRef) -> Result<Option<ObjectRef>, RuntimeError> {
640 self.enter()?.view().get_prototype_of(object)
641 }
642
643 pub fn set_prototype_of(
644 &self,
645 object: &ObjectRef,
646 prototype: Option<&ObjectRef>,
647 ) -> Result<bool, RuntimeError> {
648 self.enter()?.view().set_prototype_of(object, prototype)
649 }
650
651 pub fn as_callable(&self, object: &ObjectRef) -> Result<Option<CallableRef>, RuntimeError> {
653 self.enter()?.view().as_callable(object)
654 }
655
656 pub fn is_constructor(&self, object: &ObjectRef) -> Result<bool, RuntimeError> {
657 self.enter()?.view().is_constructor(object)
658 }
659
660 pub fn is_array_object(&self, object: &ObjectRef) -> Result<bool, RuntimeError> {
662 self.enter()?.view().is_array_object(object)
663 }
664
665 pub fn is_error_object(&self, object: &ObjectRef) -> Result<bool, RuntimeError> {
666 self.enter()?.view().is_error_object(object)
667 }
668
669 pub fn define_own_property(
670 &self,
671 object: &ObjectRef,
672 key: &PropertyKey,
673 descriptor: &OrdinaryPropertyDescriptor,
674 ) -> Result<bool, RuntimeError> {
675 self.context().define_own_property(object, key, descriptor)
676 }
677
678 pub fn get_own_property(
679 &self,
680 object: &ObjectRef,
681 key: &PropertyKey,
682 ) -> Result<Option<CompleteOrdinaryPropertyDescriptor>, RuntimeError> {
683 self.context().get_own_property(object, key)
684 }
685
686 pub fn delete_property(
687 &self,
688 object: &ObjectRef,
689 key: &PropertyKey,
690 ) -> Result<bool, RuntimeError> {
691 self.enter()?.view().delete_property(object, key)
692 }
693
694 pub fn has_property(
695 &self,
696 object: &ObjectRef,
697 key: &PropertyKey,
698 ) -> Result<bool, RuntimeError> {
699 self.enter()?.view().has_property(object, key)
700 }
701
702 pub fn has_own_property(
703 &self,
704 object: &ObjectRef,
705 key: &PropertyKey,
706 ) -> Result<bool, RuntimeError> {
707 self.enter()?.view().has_own_property(object, key)
708 }
709
710 pub fn own_property_keys(&self, object: &ObjectRef) -> Result<Vec<PropertyKey>, RuntimeError> {
711 self.enter()?.view().own_property_keys(object)
712 }
713
714 pub fn type_of(&self, value: &Value) -> Result<String, RuntimeError> {
716 self.enter()?.view().type_of(value)
717 }
718
719 pub fn print_value(&self, value: &Value) -> Result<String, RuntimeError> {
722 self.enter()?.view().print_value(value)
723 }
724
725 pub fn qjs_print_value_bytes(&self, value: &Value) -> Result<Vec<u8>, RuntimeError> {
729 self.enter()?.view().qjs_print_value_bytes(value)
730 }
731}