oden/oden-js/src/runtime.rs
John Doty c96a1a4979 [quickjs][oden][oden-js] Source maps
The worst support but it should cost very little if nobody is using
them (psst we're using them)
2023-09-21 01:51:55 -05:00

377 lines
12 KiB
Rust

use crate::{
module::loader::{DefaultModuleLoader, ModuleLoader, ModuleSource},
promise::{PromiseEvent, PromiseHandle},
throw_error, Atom, AtomRef, ContextRef, DefaultRejectedPromiseTracker, Promise,
RejectedPromiseTracker, Result, Value, ValueRef,
};
use oden_js_sys as sys;
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::ffi::CStr;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
// NOTE: This is DEEPLY unsafe, but the runtime lifetime dominates the
// lifetime of all of this stuff, so we really *can* hold on to them.
struct PromiseEntry {
context: *mut sys::JSContext,
resolve: sys::JSValue,
reject: sys::JSValue,
}
impl PromiseEntry {
// NOTE: Takes ownership of resolve and reject.
fn new(context: *mut sys::JSContext, resolve: sys::JSValue, reject: sys::JSValue) -> Self {
PromiseEntry {
context: unsafe { sys::JS_DupContext(context) },
resolve,
reject,
}
}
}
impl Drop for PromiseEntry {
fn drop(&mut self) {
unsafe {
sys::JS_FreeValue(self.context, self.resolve);
sys::JS_FreeValue(self.context, self.reject);
sys::JS_FreeContext(self.context);
}
}
}
struct PrivateState {
refs: u64,
loader: Arc<RefCell<Box<dyn ModuleLoader>>>,
promise_send: Sender<(PromiseHandle, PromiseEvent)>,
promise_recv: Receiver<(PromiseHandle, PromiseEvent)>,
promise_table: HashMap<PromiseHandle, PromiseEntry>, // !
rejection_tracker: Arc<Box<dyn RejectedPromiseTracker>>,
}
impl PrivateState {
pub fn new() -> Box<RefCell<Self>> {
let (send, recv) = channel();
Box::new(RefCell::new(PrivateState {
refs: 1,
loader: Arc::new(RefCell::new(Box::new(DefaultModuleLoader::new()))),
promise_send: send,
promise_recv: recv,
promise_table: HashMap::new(),
rejection_tracker: Arc::new(Box::new(DefaultRejectedPromiseTracker::new())),
}))
}
pub unsafe fn from_rt_mut(rt: *mut sys::JSRuntime) -> RefMut<'static, PrivateState> {
unsafe {
let ptr = sys::JS_GetRuntimeOpaque(rt) as *const RefCell<PrivateState>;
ptr.as_ref()
.expect("We already know this runtime is one of ours!")
.borrow_mut()
}
}
unsafe extern "C" fn module_loader(
ctx: *mut sys::JSContext,
path: *const i8,
opaque: *mut std::os::raw::c_void,
) -> *mut sys::JSModuleDef {
let path = match CStr::from_ptr(path).to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let loader = unsafe {
let ptr = opaque as *const RefCell<PrivateState>;
ptr.as_ref()
.expect("We already know this runtime is one of ours!")
.borrow()
.loader
.clone()
};
let context = ContextRef::from_raw(ctx);
let result = {
let mut loader = loader.borrow_mut();
loader.load(&context, path)
};
match result {
Ok(ModuleSource::Native(native)) => native.module,
Ok(ModuleSource::JavaScript(js)) => match context.eval_module(&js, path) {
Ok(v) => v.module,
Err(e) => {
throw_error(&context, e);
std::ptr::null_mut()
}
},
Err(e) => {
throw_error(&context, e);
std::ptr::null_mut()
}
}
}
unsafe extern "C" fn source_mapper(
ctx: *mut sys::JSContext,
opaque: *mut std::os::raw::c_void,
file: u32,
line: i32,
pfile: *mut u32,
pline: *mut i32,
) {
let ctx = ContextRef::from_raw(ctx);
let file = AtomRef::from_raw(file, &ctx);
let loader = unsafe {
let ptr = opaque as *const RefCell<PrivateState>;
ptr.as_ref()
.expect("We already know this runtime is one of ours!")
.borrow()
.loader
.clone()
};
let result = {
let loader = loader.borrow();
loader.map_source(&ctx, &file, line)
};
match result {
Ok((file, line)) => {
*pfile = Atom::consume(file);
*pline = line;
}
Err(e) => {
throw_error(&ctx, e);
*pfile = file.atom;
*pline = line;
}
}
}
unsafe extern "C" fn promise_rejection_tracker(
ctx: *mut sys::JSContext,
promise: sys::JSValue,
reason: sys::JSValue,
is_handled: i32,
opaque: *mut std::os::raw::c_void,
) {
let ctx = ContextRef::from_raw(ctx);
let promise = ValueRef::from_raw(promise);
let reason = ValueRef::from_raw(reason);
let is_handled = is_handled != 0;
let handler = unsafe {
let ptr = opaque as *const RefCell<PrivateState>;
ptr.as_ref()
.expect("We already know this runtime is one of ours!")
.borrow()
.rejection_tracker
.clone()
};
handler.on_rejected_promise(&ctx, &promise, &reason, is_handled);
}
}
#[derive(Debug)]
pub struct Runtime {
pub(crate) rt: *mut sys::JSRuntime,
}
impl Runtime {
pub fn new() -> Runtime {
let state = PrivateState::new();
let rt = unsafe {
let rt = sys::JS_NewRuntime();
let state = Box::into_raw(state) as *mut _;
sys::JS_SetRuntimeOpaque(rt, state);
sys::JS_SetModuleLoaderFunc(rt, None, Some(PrivateState::module_loader), state);
sys::JS_SetHostPromiseRejectionTracker(
rt,
Some(PrivateState::promise_rejection_tracker),
state,
);
rt
};
Runtime { rt }
}
pub(crate) fn from_raw(rt: *mut sys::JSRuntime) -> Self {
let mut state = unsafe { PrivateState::from_rt_mut(rt) };
state.refs += 1;
Runtime { rt }
}
pub fn set_memory_limit(&mut self, limit: usize) {
unsafe {
sys::JS_SetMemoryLimit(self.rt, limit);
}
}
pub fn set_gc_threshold(&mut self, threshold: usize) {
unsafe {
sys::JS_SetGCThreshold(self.rt, threshold);
}
}
/// Pass in 0 to disable the maximum size check.
pub fn set_max_stack_size(&mut self, max_stack: usize) {
unsafe {
sys::JS_SetMaxStackSize(self.rt, max_stack);
}
}
/// Set the handler for loading modules. By default this is an instance
/// of `DefaultModuleLoader`.
pub fn set_module_loader<T>(&mut self, loader: T)
where
T: ModuleLoader + 'static,
{
let mut state = unsafe { PrivateState::from_rt_mut(self.rt) };
if loader.support_source_map() {
unsafe {
let opaque = sys::JS_GetRuntimeOpaque(self.rt);
sys::JS_SetSourceMapFunc(self.rt, Some(PrivateState::source_mapper), opaque);
}
} else {
unsafe {
sys::JS_SetSourceMapFunc(self.rt, None, std::ptr::null_mut());
}
}
state.loader = Arc::new(RefCell::new(Box::new(loader)));
}
/// Set a tracker to be notified whenever a promise is rejected. By
/// default, this is an instance of `DefaultRejectedPromiseHandler`.
pub fn set_rejected_promise_tracker<T>(&mut self, tracker: T)
where
T: RejectedPromiseTracker + 'static,
{
let mut state = unsafe { PrivateState::from_rt_mut(self.rt) };
state.rejection_tracker = Arc::new(Box::new(tracker));
}
pub fn run_gc(&mut self) {
unsafe {
sys::JS_RunGC(self.rt);
}
}
/// Construct a new promise.
pub fn new_promise(&self, context: &ContextRef) -> Result<(Value, Promise)> {
unsafe {
let mut state = PrivateState::from_rt_mut(self.rt);
let mut resolving_funcs: [sys::JSValue; 2] =
[sys::JS_MakeUndefined(), sys::JS_MakeUndefined()];
let val = sys::JS_NewPromiseCapability(
context.ctx,
&mut resolving_funcs as *mut sys::JSValue,
);
if sys::JS_ValueGetTag(val) == sys::JS_TAG_EXCEPTION {
return Err(context.exception_error());
}
let handle = PromiseHandle::new();
state.promise_table.insert(
handle,
PromiseEntry::new(context.ctx, resolving_funcs[0], resolving_funcs[1]),
);
let promise = Promise::new(handle, state.promise_send.clone());
drop(state); // Need to drop state so that the value constructor can addref.
Ok((Value::from_raw(val, context), promise))
}
}
/// Process all pending async jobs. This includes all promise resolutions.
fn process_promise_completions(&self) {
let mut promises = vec![];
// First, gather all the completed promises into a temporary vector
// so that we only need to borrow our mutable state for a short
// period of time.
let mut state = unsafe { PrivateState::from_rt_mut(self.rt) };
while let Ok((handle, evt)) = state.promise_recv.try_recv() {
if let Some(entry) = state.promise_table.remove(&handle) {
promises.push((entry, evt));
}
}
drop(state); // Don't need our internal state anymore.
// Nowe we can complete all the promises.
for (entry, evt) in promises {
let ctx = ContextRef::from_raw(entry.context);
let (callback, value) = match evt {
PromiseEvent::Resolved(v) => (entry.resolve, v),
PromiseEvent::Rejected(v) => (entry.reject, v),
};
// Convert the result into a JS value, which we can only
// really do while we are on this thread.
let value = value(&ctx).expect("Should be able to convert promise result to value");
// Call the particular callback and make sure it doesn't throw.
ctx.check_exception(unsafe {
let mut args = [value.val];
sys::JS_Call(
entry.context,
callback,
sys::JS_MakeUndefined(),
1,
args.as_mut_ptr(),
)
})
.expect("Exception thrown by promise callback");
}
}
/// Process all pending async jobs. This includes all promise resolutions.
pub fn process_all_jobs(&self) -> Result<()> {
self.process_promise_completions();
loop {
let mut ctx1: *mut sys::JSContext = std::ptr::null_mut();
let err = unsafe { sys::JS_ExecutePendingJob(self.rt, &mut ctx1) };
if err == 0 {
break;
} else if err < 0 {
return Err(ContextRef::from_raw(ctx1).exception_error());
}
}
Ok(())
}
}
impl Clone for Runtime {
fn clone(&self) -> Self {
Runtime::from_raw(self.rt)
}
}
impl Drop for Runtime {
fn drop(&mut self) {
let should_free = {
let mut state = unsafe {
let ptr = sys::JS_GetRuntimeOpaque(self.rt) as *const RefCell<PrivateState>;
ptr.as_ref()
.expect("We already know this runtime is one of ours!")
.borrow_mut()
};
state.refs -= 1;
state.refs == 0
};
if should_free {
unsafe {
let opaque = sys::JS_GetRuntimeOpaque(self.rt);
sys::JS_RunGC(self.rt);
sys::JS_FreeRuntime(self.rt);
if !opaque.is_null() {
// Just let the system drop it here.
let _ = Box::from_raw(opaque as *mut RefCell<PrivateState>);
}
}
}
}
}