use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashSet;
use std::hash::BuildHasherDefault;
use std::hash::Hasher;
use std::rc::Rc;
use std::sync::Arc;
use super::exception_state::ExceptionState;
#[cfg(test)]
use super::op_driver::OpDriver;
use crate::_ops::OpMethodDecl;
use crate::ModuleSourceCode;
use crate::SourceCodeCacheInfo;
use crate::cppgc::FunctionTemplateData;
use crate::error::CoreError;
use crate::error::CreateCodeCacheError;
use crate::error::JsError;
use crate::error::exception_to_err;
use crate::error::exception_to_err_result;
use crate::event_loop::EventLoopPhases;
use crate::module_specifier::ModuleSpecifier;
use crate::modules::IntoModuleCodeString;
use crate::modules::IntoModuleName;
use crate::modules::ModuleCodeString;
use crate::modules::ModuleId;
use crate::modules::ModuleMap;
use crate::modules::ModuleName;
use crate::modules::recursive_load::RecursiveModuleLoad;
use crate::modules::script_origin;
use crate::ops::ExternalOpsTracker;
use crate::ops::OpCtx;
use crate::reactor::DefaultReactor;
use crate::stats::RuntimeActivityTraces;
use crate::tasks::V8TaskSpawnerFactory;
use crate::uv_compat::UvLoopInner;
use crate::web_timeout::UserTimer;
pub const CONTEXT_STATE_SLOT_INDEX: i32 = 1;
pub const MODULE_MAP_SLOT_INDEX: i32 = 2;
#[derive(Default)]
pub(crate) struct IdentityHasher(u64);
impl Hasher for IdentityHasher {
fn write_i32(&mut self, i: i32) {
self.0 = i as u64;
}
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, _bytes: &[u8]) {
unreachable!()
}
}
pub(crate) type OpDriverImpl = super::op_driver::FuturesUnorderedDriver;
pub(crate) type UnrefedOps =
Rc<RefCell<HashSet<i32, BuildHasherDefault<IdentityHasher>>>>;
pub(crate) const IMM_IDX_COUNT: usize = 0;
pub(crate) const IMM_IDX_REF_COUNT: usize = 1;
pub(crate) const IMM_IDX_HAS_OUTSTANDING: usize = 2;
pub struct ContextState {
pub(crate) task_spawner_factory: Arc<V8TaskSpawnerFactory>,
pub(crate) user_timer: UserTimer<DefaultReactor>,
pub(crate) js_event_loop_tick_cb: RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) js_drain_next_tick_and_macrotasks_cb:
RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) js_handle_rejections_cb: RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) run_immediate_callbacks_cb:
RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) js_wasm_streaming_cb: RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) wasm_instance_fn: RefCell<Option<v8::Global<v8::Function>>>,
pub(crate) unrefed_ops: UnrefedOps,
pub(crate) activity_traces: RuntimeActivityTraces,
pub(crate) pending_ops: Rc<OpDriverImpl>,
pub(crate) op_ctxs: Box<[OpCtx]>,
pub(crate) op_method_decls: Vec<OpMethodDecl>,
pub(crate) methods_ctx_offset: usize,
pub(crate) isolate: Option<v8::UnsafeRawIsolatePtr>,
pub(crate) exception_state: Rc<ExceptionState>,
pub(crate) tick_info: Box<[u8; 2]>,
pub(crate) immediate_info: Box<[u32; 3]>,
pub(crate) timer_info: Box<[i32; 1]>,
pub(crate) timer_expiry: Box<[f64; 1]>,
pub(crate) active_timers:
RefCell<std::collections::HashMap<usize, (bool, bool)>>,
pub(crate) external_ops_tracker: ExternalOpsTracker,
pub(crate) ext_import_meta_proto: RefCell<Option<v8::Global<v8::Object>>>,
pub(crate) event_loop_phases: RefCell<EventLoopPhases>,
pub(crate) uv_loop_inner: Cell<Option<*const UvLoopInner>>,
pub(crate) uv_loop_ptr: Cell<Option<*mut crate::uv_compat::uv_loop_t>>,
pub(crate) immediate_check_handle:
RefCell<Option<crate::uv_compat::ImmediateCheckHandle>>,
}
impl ContextState {
pub(crate) fn has_tick_scheduled(&self) -> bool {
self.tick_info[0] != 0
}
pub(crate) fn new(
op_driver: Rc<OpDriverImpl>,
isolate_ptr: v8::UnsafeRawIsolatePtr,
op_ctxs: Box<[OpCtx]>,
op_method_decls: Vec<OpMethodDecl>,
methods_ctx_offset: usize,
external_ops_tracker: ExternalOpsTracker,
unrefed_ops: UnrefedOps,
) -> Self {
Self {
isolate: Some(isolate_ptr),
exception_state: Default::default(),
tick_info: Box::new([0u8; 2]),
immediate_info: Box::new([0u32; 3]),
js_event_loop_tick_cb: Default::default(),
js_drain_next_tick_and_macrotasks_cb: Default::default(),
js_handle_rejections_cb: Default::default(),
run_immediate_callbacks_cb: Default::default(),
js_wasm_streaming_cb: Default::default(),
wasm_instance_fn: Default::default(),
activity_traces: Default::default(),
op_ctxs,
op_method_decls,
methods_ctx_offset,
pending_ops: op_driver,
task_spawner_factory: Default::default(),
user_timer: Default::default(),
timer_info: Box::new([0i32; 1]),
timer_expiry: Box::new([0f64; 1]),
active_timers: Default::default(),
unrefed_ops,
external_ops_tracker,
ext_import_meta_proto: Default::default(),
event_loop_phases: Default::default(),
uv_loop_inner: Cell::new(None),
uv_loop_ptr: Cell::new(None),
immediate_check_handle: RefCell::new(None),
}
}
}
#[derive(Clone)]
#[repr(transparent)]
pub(crate) struct JsRealm(pub(crate) JsRealmInner);
#[derive(Clone)]
pub(crate) struct JsRealmInner {
pub(crate) context_state: Rc<ContextState>,
context: v8::Global<v8::Context>,
pub(crate) module_map: Rc<ModuleMap>,
pub(crate) function_templates: Rc<RefCell<FunctionTemplateData>>,
}
impl JsRealmInner {
pub(crate) fn new(
context_state: Rc<ContextState>,
context: v8::Global<v8::Context>,
module_map: Rc<ModuleMap>,
function_templates: Rc<RefCell<FunctionTemplateData>>,
) -> Self {
Self {
context_state,
context: context.clone(),
module_map,
function_templates,
}
}
#[inline(always)]
pub fn context(&self) -> &v8::Global<v8::Context> {
&self.context
}
#[inline(always)]
pub(crate) fn state(&self) -> Rc<ContextState> {
self.context_state.clone()
}
#[inline(always)]
pub(crate) fn module_map(&self) -> Rc<ModuleMap> {
self.module_map.clone()
}
#[inline(always)]
pub(crate) fn function_templates(&self) -> Rc<RefCell<FunctionTemplateData>> {
self.function_templates.clone()
}
pub fn destroy(self) {
let state = self.state();
let raw_ptr = self.state().isolate.unwrap();
let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(raw_ptr) };
if let Some(handle) = state.immediate_check_handle.borrow_mut().take() {
unsafe { handle.close() };
}
if let Some(loop_ptr) = state.uv_loop_ptr.get() {
unsafe {
let data = (*loop_ptr).data;
if !data.is_null() {
let raw = std::ptr::NonNull::new_unchecked(data as *mut v8::Context);
let _global = v8::Global::from_raw(&mut isolate, raw);
(*loop_ptr).data = std::ptr::null_mut();
}
}
}
v8::scope!(let scope, &mut isolate);
state.exception_state.prepare_to_destroy();
std::mem::take(&mut *state.js_event_loop_tick_cb.borrow_mut());
std::mem::take(
&mut *state.js_drain_next_tick_and_macrotasks_cb.borrow_mut(),
);
std::mem::take(&mut *state.js_handle_rejections_cb.borrow_mut());
std::mem::take(&mut *state.run_immediate_callbacks_cb.borrow_mut());
std::mem::take(&mut *state.js_wasm_streaming_cb.borrow_mut());
{
let ctx = self.context().open(scope);
unsafe {
let ctx_state =
ctx.get_aligned_pointer_from_embedder_data(CONTEXT_STATE_SLOT_INDEX);
let _ = Rc::from_raw(ctx_state as *mut ContextState);
let module_map =
ctx.get_aligned_pointer_from_embedder_data(MODULE_MAP_SLOT_INDEX);
let map = Rc::from_raw(module_map as *mut ModuleMap);
map.destroy();
ctx.set_aligned_pointer_in_embedder_data(
CONTEXT_STATE_SLOT_INDEX,
std::ptr::null_mut(),
);
ctx.set_aligned_pointer_in_embedder_data(
MODULE_MAP_SLOT_INDEX,
std::ptr::null_mut(),
);
}
ctx.clear_all_slots();
}
}
}
unsafe fn clone_rc_raw<T>(raw: *const T) -> Rc<T> {
unsafe {
Rc::increment_strong_count(raw);
Rc::from_raw(raw)
}
}
macro_rules! context_scope {
($scope: ident, $self: expr, $isolate: expr) => {
v8::scope!($scope, $isolate);
let context = v8::Local::new($scope, $self.context());
let $scope = &mut v8::ContextScope::new($scope, context);
};
}
pub(crate) use context_scope;
impl JsRealm {
pub(crate) fn new(inner: JsRealmInner) -> Self {
Self(inner)
}
#[inline(always)]
pub(crate) fn state_from_scope(scope: &mut v8::PinScope) -> Rc<ContextState> {
let context = scope.get_current_context();
unsafe {
let rc = context
.get_aligned_pointer_from_embedder_data(CONTEXT_STATE_SLOT_INDEX);
clone_rc_raw(rc as *const ContextState)
}
}
#[inline(always)]
pub(crate) fn module_map_from(scope: &mut v8::PinScope) -> Rc<ModuleMap> {
let context = scope.get_current_context();
unsafe {
let rc =
context.get_aligned_pointer_from_embedder_data(MODULE_MAP_SLOT_INDEX);
clone_rc_raw(rc as *const ModuleMap)
}
}
#[inline(always)]
pub(crate) fn exception_state_from_scope(
scope: &mut v8::PinScope,
) -> Rc<ExceptionState> {
Self::state_from_scope(scope).exception_state.clone()
}
#[cfg(test)]
#[inline(always)]
pub fn num_pending_ops(&self) -> usize {
self.0.context_state.pending_ops.len()
}
#[cfg(test)]
#[inline(always)]
pub fn num_unrefed_ops(&self) -> usize {
self.0.context_state.unrefed_ops.borrow().len()
}
#[inline(always)]
pub fn context(&self) -> &v8::Global<v8::Context> {
self.0.context()
}
pub fn execute_script(
&self,
isolate: &mut v8::Isolate,
name: impl IntoModuleName,
source_code: impl IntoModuleCodeString,
) -> Result<v8::Global<v8::Value>, Box<JsError>> {
context_scope!(scope, self, isolate);
let source = source_code.into_module_code().v8_string(scope).unwrap();
let name = name.into_module_name().v8_string(scope).unwrap();
let origin = script_origin(scope, name, false, None);
v8::tc_scope!(let tc_scope, scope);
let script = match v8::Script::compile(tc_scope, source, Some(&origin)) {
Some(script) => script,
None => {
let exception = tc_scope.exception().unwrap();
return exception_to_err_result(tc_scope, exception, false, false);
}
};
match script.run(tc_scope) {
Some(value) => {
let value_handle = v8::Global::new(tc_scope, value);
Ok(value_handle)
}
None => {
assert!(tc_scope.has_caught());
let exception = tc_scope.exception().unwrap();
exception_to_err_result(tc_scope, exception, false, false)
}
}
}
pub fn execute_script_with_cache(
&self,
isolate: &mut v8::Isolate,
name: ModuleSpecifier,
source_code: impl IntoModuleCodeString,
get_cache: &dyn Fn(
&ModuleSpecifier,
&ModuleSourceCode,
) -> SourceCodeCacheInfo,
cache_ready: &dyn Fn(ModuleSpecifier, u64, &[u8]),
) -> Result<v8::Global<v8::Value>, CoreError> {
context_scope!(scope, self, isolate);
let specifier = name.clone();
let code = source_code.into_module_code();
let source = ModuleSourceCode::String(code);
let code_cache = get_cache(&name, &source);
let ModuleSourceCode::String(source) = source else {
unreachable!()
};
let name = name.into_module_name().v8_string(scope).unwrap();
let source = source.v8_string(scope).unwrap();
let origin = script_origin(scope, name, false, None);
v8::tc_scope!(let tc_scope, scope);
let (maybe_script, maybe_code_cache_hash) =
if let Some(data) = &code_cache.data {
let mut source = v8::script_compiler::Source::new_with_cached_data(
source,
Some(&origin),
v8::CachedData::new(data),
);
let script = v8::script_compiler::compile(
tc_scope,
&mut source,
v8::script_compiler::CompileOptions::ConsumeCodeCache,
v8::script_compiler::NoCacheReason::NoReason,
);
let rejected = match source.get_cached_data() {
Some(cached_data) => cached_data.rejected(),
_ => true,
};
let maybe_code_cache_hash = if rejected {
Some(code_cache.hash) } else {
None
};
(Some(script), maybe_code_cache_hash)
} else {
(None, Some(code_cache.hash))
};
let script = maybe_script
.unwrap_or_else(|| v8::Script::compile(tc_scope, source, Some(&origin)));
let script = match script {
Some(script) => script,
None => {
let exception = tc_scope.exception().unwrap();
return Ok(exception_to_err_result(tc_scope, exception, false, false)?);
}
};
if let Some(code_cache_hash) = maybe_code_cache_hash {
let unbound_script = script.get_unbound_script(tc_scope);
let code_cache = unbound_script
.create_code_cache()
.ok_or_else(|| CreateCodeCacheError(specifier.clone()))?;
cache_ready(specifier, code_cache_hash, &code_cache);
}
match script.run(tc_scope) {
Some(value) => {
let value_handle = v8::Global::new(tc_scope, value);
Ok(value_handle)
}
None => {
assert!(tc_scope.has_caught());
let exception = tc_scope.exception().unwrap();
Ok(exception_to_err_result(tc_scope, exception, false, false)?)
}
}
}
pub fn get_module_namespace(
&self,
isolate: &mut v8::Isolate,
module_id: ModuleId,
) -> Result<v8::Global<v8::Object>, CoreError> {
context_scope!(scope, self, isolate);
self.0.module_map().get_module_namespace(scope, module_id)
}
pub(crate) fn instantiate_module(
&self,
scope: &mut v8::PinScope,
id: ModuleId,
) -> Result<(), v8::Global<v8::Value>> {
self.0.module_map().instantiate_module(scope, id)
}
pub(crate) fn modules_idle(&self) -> bool {
self.0.module_map.dyn_module_evaluate_idle_counter.get() > 1
}
pub(crate) fn increment_modules_idle(&self) {
let count = &self.0.module_map.dyn_module_evaluate_idle_counter;
count.set(count.get() + 1)
}
pub(crate) async fn load_main_es_module_from_code(
&self,
isolate: &mut v8::Isolate,
specifier: &ModuleSpecifier,
code: Option<ModuleCodeString>,
) -> Result<ModuleId, CoreError> {
let module_map_rc = self.0.module_map();
if let Some(code) = code {
context_scope!(scope, self, isolate);
module_map_rc
.new_es_module(
scope,
true,
specifier.to_string().into(),
code,
false,
None,
)
.map_err(|e| e.into_error(scope, false, false))?;
}
let root_id =
RecursiveModuleLoad::main(specifier.to_string(), module_map_rc.clone())
.await?
.run_to_completion(|load, request, source| {
context_scope!(scope, self, isolate);
load
.register_and_recurse(scope, request, source)
.map_err(|e| e.into_error(scope, false, false))
})
.await?;
context_scope!(scope, self, isolate);
self.instantiate_module(scope, root_id).map_err(|e| {
let exception = v8::Local::new(scope, e);
exception_to_err(scope, exception, false, false)
})?;
Ok(root_id)
}
pub(crate) async fn load_side_es_module_from_code(
&self,
isolate: &mut v8::Isolate,
specifier: String,
code: Option<ModuleCodeString>,
) -> Result<ModuleId, CoreError> {
let module_map_rc = self.0.module_map();
if let Some(code) = code {
let specifier = specifier.to_owned();
context_scope!(scope, self, isolate);
module_map_rc
.new_es_module(scope, false, specifier.into(), code, false, None)
.map_err(|e| e.into_error(scope, false, false))?;
}
let root_id = RecursiveModuleLoad::side(
specifier,
module_map_rc,
crate::modules::SideModuleKind::Async,
None,
)
.await?
.run_to_completion(|load, request, source| {
context_scope!(scope, self, isolate);
load
.register_and_recurse(scope, request, source)
.map_err(|e| e.into_error(scope, false, false))
})
.await?;
context_scope!(scope, self, isolate);
self.instantiate_module(scope, root_id).map_err(|e| {
let exception = v8::Local::new(scope, e);
exception_to_err(scope, exception, false, false)
})?;
Ok(root_id)
}
pub(crate) fn lazy_load_es_module_with_code(
&self,
isolate: &mut v8::Isolate,
module_specifier: ModuleName,
code: ModuleCodeString,
) -> Result<v8::Global<v8::Value>, CoreError> {
let module_map_rc = self.0.module_map();
context_scope!(scope, self, isolate);
module_map_rc.lazy_load_es_module_with_code(
scope,
module_specifier.as_str(),
code,
None,
)
}
}