1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 23:34:47 -05:00

refactor(core): store v8::Global<v8::Context> in an Rc (#18749)

Alternative to https://github.com/denoland/deno/pull/18726.

This was suggested by @piscisaureus. It's a bit ugly, but it does the
work and makes cloning `JsRealm` very cheap, while not requiring 
invasive changes.

Also managed to remove some vector and `v8::Global` clones which yields
about 5% improvement in the "async_ops_deferred.js" benchmark.

This PR:
```
time 1689 ms rate 592066
time 1722 ms rate 580720
time 1629 ms rate 613873
time 1578 ms rate 633713
time 1585 ms rate 630914
time 1574 ms rate 635324
```

`main` branch:
```
time 1687 ms rate 592768
time 1676 ms rate 596658
time 1651 ms rate 605693
time 1652 ms rate 605326
time 1638 ms rate 610500
```
This commit is contained in:
Bartek Iwańczuk 2023-04-19 00:52:12 +02:00 committed by GitHub
parent edca01c35e
commit 40e157c005
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 72 deletions

View file

@ -210,15 +210,18 @@ impl JsStackFrame {
// V8's column numbers are 0-based, we want 1-based.
let c = message.get_start_column() as i64 + 1;
let state_rc = JsRuntime::state(scope);
let state = &mut *state_rc.borrow_mut();
if let Some(source_map_getter) = &state.source_map_getter {
let (f, l, c) = apply_source_map(
f,
l,
c,
&mut state.source_map_cache,
source_map_getter.as_ref(),
);
let (getter, cache) = {
let state = state_rc.borrow();
(
state.source_map_getter.clone(),
state.source_map_cache.clone(),
)
};
if let Some(source_map_getter) = getter {
let mut cache = cache.borrow_mut();
let (f, l, c) =
apply_source_map(f, l, c, &mut cache, &**source_map_getter);
Some(JsStackFrame::from_location(Some(f), Some(l), Some(c)))
} else {
Some(JsStackFrame::from_location(Some(f), Some(l), Some(c)))
@ -280,8 +283,15 @@ impl JsError {
}
{
let state_rc = JsRuntime::state(scope);
let state = &mut *state_rc.borrow_mut();
if let Some(source_map_getter) = &state.source_map_getter {
let (getter, cache) = {
let state = state_rc.borrow();
(
state.source_map_getter.clone(),
state.source_map_cache.clone(),
)
};
if let Some(source_map_getter) = getter {
let mut cache = cache.borrow_mut();
for (i, frame) in frames.iter().enumerate() {
if let (Some(file_name), Some(line_number)) =
(&frame.file_name, frame.line_number)
@ -290,8 +300,8 @@ impl JsError {
source_line = get_source_line(
file_name,
line_number,
&mut state.source_map_cache,
source_map_getter.as_ref(),
&mut cache,
&**source_map_getter,
);
source_line_frame_index = Some(i);
break;
@ -405,8 +415,16 @@ impl JsError {
}
{
let state_rc = JsRuntime::state(scope);
let state = &mut *state_rc.borrow_mut();
if let Some(source_map_getter) = &state.source_map_getter {
let (getter, cache) = {
let state = state_rc.borrow();
(
state.source_map_getter.clone(),
state.source_map_cache.clone(),
)
};
if let Some(source_map_getter) = getter {
let mut cache = cache.borrow_mut();
for (i, frame) in frames.iter().enumerate() {
if let (Some(file_name), Some(line_number)) =
(&frame.file_name, frame.line_number)
@ -415,8 +433,8 @@ impl JsError {
source_line = get_source_line(
file_name,
line_number,
&mut state.source_map_cache,
source_map_getter.as_ref(),
&mut cache,
&**source_map_getter,
);
source_line_frame_index = Some(i);
break;

View file

@ -8,7 +8,7 @@ use crate::error::JsError;
use crate::ops_builtin::WasmStreamingResource;
use crate::resolve_url;
use crate::serde_v8::from_v8;
use crate::source_map::apply_source_map as apply_source_map_;
use crate::source_map::apply_source_map;
use crate::JsRealm;
use crate::JsRuntime;
use crate::ZeroCopyBuf;
@ -17,6 +17,7 @@ use deno_ops::op;
use serde::Deserialize;
use serde::Serialize;
use std::cell::RefCell;
use std::rc::Rc;
use v8::ValueDeserializerHelper;
use v8::ValueSerializerHelper;
@ -59,8 +60,8 @@ fn op_set_promise_reject_callback<'a>(
let old = context_state_rc
.borrow_mut()
.js_promise_reject_cb
.replace(cb);
let old = old.map(|v| v8::Local::new(scope, v));
.replace(Rc::new(cb));
let old = old.map(|v| v8::Local::new(scope, &*v));
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
}
@ -633,7 +634,7 @@ fn op_set_wasm_streaming_callback(
if context_state.js_wasm_streaming_cb.is_some() {
return Err(type_error("op_set_wasm_streaming_callback already called"));
}
context_state.js_wasm_streaming_cb = Some(cb);
context_state.js_wasm_streaming_cb = Some(Rc::new(cb));
scope.set_wasm_streaming_callback(|scope, arg, wasm_streaming| {
let (cb_handle, streaming_rid) = {
@ -755,15 +756,23 @@ fn op_apply_source_map(
location: Location,
) -> Result<Location, Error> {
let state_rc = JsRuntime::state(scope);
let state = &mut *state_rc.borrow_mut();
if let Some(source_map_getter) = &state.source_map_getter {
let (getter, cache) = {
let state = state_rc.borrow();
(
state.source_map_getter.clone(),
state.source_map_cache.clone(),
)
};
if let Some(source_map_getter) = getter {
let mut cache = cache.borrow_mut();
let mut location = location;
let (f, l, c) = apply_source_map_(
let (f, l, c) = apply_source_map(
location.file_name,
location.line_number.into(),
location.column_number.into(),
&mut state.source_map_cache,
source_map_getter.as_ref(),
&mut cache,
&**source_map_getter,
);
location.file_name = f;
location.line_number = l as u32;
@ -788,14 +797,14 @@ fn op_set_format_exception_callback<'a>(
let old = context_state_rc
.borrow_mut()
.js_format_exception_cb
.replace(cb);
let old = old.map(|v| v8::Local::new(scope, v));
.replace(Rc::new(cb));
let old = old.map(|v| v8::Local::new(scope, &*v));
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
}
#[op(v8)]
fn op_event_loop_has_more_work(scope: &mut v8::HandleScope) -> bool {
JsRuntime::event_loop_pending_state_from_isolate(scope).is_pending()
JsRuntime::event_loop_pending_state_from_scope(scope).is_pending()
}
#[op(v8)]

View file

@ -8,21 +8,43 @@ use anyhow::Error;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::BuildHasherDefault;
use std::hash::Hasher;
use std::marker::PhantomData;
use std::option::Option;
use std::rc::Rc;
use v8::HandleScope;
use v8::Local;
// Hasher used for `unrefed_ops`. Since these are rolling i32, there's no
// need to actually hash them.
#[derive(Default)]
pub(crate) struct IdentityHasher(u64, PhantomData<i32>);
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!()
}
}
#[derive(Default)]
pub(crate) struct ContextState {
pub(crate) js_event_loop_tick_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_build_custom_error_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_format_exception_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_event_loop_tick_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_build_custom_error_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_promise_reject_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_format_exception_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_wasm_streaming_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) pending_promise_rejections:
HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>,
pub(crate) unrefed_ops: HashSet<i32>,
pub(crate) unrefed_ops: HashSet<i32, BuildHasherDefault<IdentityHasher>>,
// We don't explicitly re-read this prop but need the slice to live alongside
// the context
pub(crate) op_ctxs: Box<[OpCtx]>,
@ -73,16 +95,18 @@ pub(crate) struct ContextState {
/// keep the underlying V8 context alive even if it would have otherwise been
/// garbage collected.
#[derive(Clone)]
pub struct JsRealm(v8::Global<v8::Context>);
pub struct JsRealm(Rc<v8::Global<v8::Context>>);
impl JsRealm {
pub fn new(context: v8::Global<v8::Context>) -> Self {
JsRealm(context)
JsRealm(Rc::new(context))
}
#[inline(always)]
pub fn context(&self) -> &v8::Global<v8::Context> {
&self.0
}
#[inline(always)]
pub(crate) fn state(
&self,
isolate: &mut v8::Isolate,
@ -95,6 +119,7 @@ impl JsRealm {
.clone()
}
#[inline(always)]
pub(crate) fn state_from_scope(
scope: &mut v8::HandleScope,
) -> Rc<RefCell<ContextState>> {
@ -106,11 +131,12 @@ impl JsRealm {
}
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
#[inline(always)]
pub fn handle_scope<'s>(
&self,
isolate: &'s mut v8::Isolate,
) -> v8::HandleScope<'s> {
v8::HandleScope::with_context(isolate, &self.0)
v8::HandleScope::with_context(isolate, &*self.0)
}
/// For info on the [`v8::Isolate`] parameter, check [`JsRealm#panics`].
@ -212,12 +238,36 @@ impl JsRealm {
}
// TODO(andreubotella): `mod_evaluate`, `load_main_module`, `load_side_module`
}
pub struct JsRealmLocal<'s>(v8::Local<'s, v8::Context>);
impl<'s> JsRealmLocal<'s> {
pub fn new(context: v8::Local<'s, v8::Context>) -> Self {
JsRealmLocal(context)
}
#[inline(always)]
pub fn context(&self) -> v8::Local<v8::Context> {
self.0
}
#[inline(always)]
pub(crate) fn state(
&self,
isolate: &mut v8::Isolate,
) -> Rc<RefCell<ContextState>> {
self
.context()
.get_slot::<Rc<RefCell<ContextState>>>(isolate)
.unwrap()
.clone()
}
pub(crate) fn check_promise_rejections(
&self,
isolate: &mut v8::Isolate,
scope: &mut v8::HandleScope,
) -> Result<(), Error> {
let context_state_rc = self.state(isolate);
let context_state_rc = self.state(scope);
let mut context_state = context_state_rc.borrow_mut();
if context_state.pending_promise_rejections.is_empty() {
@ -238,7 +288,6 @@ impl JsRealm {
.unwrap();
drop(context_state);
let scope = &mut self.handle_scope(isolate);
let exception = v8::Local::new(scope, handle);
exception_to_err_result(scope, exception, true)
}

View file

@ -20,6 +20,7 @@ use crate::op_void_sync;
use crate::ops::*;
use crate::realm::ContextState;
use crate::realm::JsRealm;
use crate::realm::JsRealmLocal;
use crate::snapshot_util;
use crate::source_map::SourceMapCache;
use crate::source_map::SourceMapGetter;
@ -164,8 +165,8 @@ pub struct JsRuntimeState {
/// A counter used to delay our dynamic import deadlock detection by one spin
/// of the event loop.
dyn_module_evaluate_idle_counter: u32,
pub(crate) source_map_getter: Option<Box<dyn SourceMapGetter>>,
pub(crate) source_map_cache: SourceMapCache,
pub(crate) source_map_getter: Option<Rc<Box<dyn SourceMapGetter>>>,
pub(crate) source_map_cache: Rc<RefCell<SourceMapCache>>,
pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>,
pub(crate) have_unpolled_ops: bool,
pub(crate) op_state: Rc<RefCell<OpState>>,
@ -341,7 +342,7 @@ impl JsRuntime {
pending_mod_evaluate: None,
dyn_module_evaluate_idle_counter: 0,
has_tick_scheduled: false,
source_map_getter: options.source_map_getter,
source_map_getter: options.source_map_getter.map(Rc::new),
source_map_cache: Default::default(),
pending_ops: FuturesUnordered::new(),
shared_array_buffer_store: options.shared_array_buffer_store,
@ -871,10 +872,12 @@ impl JsRuntime {
// Put global handles in the realm's ContextState
let state_rc = realm.state(self.v8_isolate());
let mut state = state_rc.borrow_mut();
state.js_event_loop_tick_cb.replace(event_loop_tick_cb);
state
.js_event_loop_tick_cb
.replace(Rc::new(event_loop_tick_cb));
state
.js_build_custom_error_cb
.replace(build_custom_error_cb);
.replace(Rc::new(build_custom_error_cb));
}
/// Returns the runtime's op state, which can be used to maintain ops
@ -972,27 +975,26 @@ impl JsRuntime {
Self::drop_state_and_module_map(v8_isolate);
}
self.state.borrow_mut().global_realm.take();
// Drop other v8::Global handles before snapshotting
{
for weak_context in &self.state.clone().borrow().known_realms {
let v8_isolate = self.v8_isolate();
if let Some(context) = weak_context.to_global(v8_isolate) {
let realm = JsRealm::new(context.clone());
let realm_state_rc = realm.state(v8_isolate);
let scope = &mut self.handle_scope();
if let Some(context) = weak_context.to_local(scope) {
let realm = JsRealmLocal::new(context);
let realm_state_rc = realm.state(scope);
let mut realm_state = realm_state_rc.borrow_mut();
std::mem::take(&mut realm_state.js_event_loop_tick_cb);
std::mem::take(&mut realm_state.js_build_custom_error_cb);
std::mem::take(&mut realm_state.js_promise_reject_cb);
std::mem::take(&mut realm_state.js_format_exception_cb);
std::mem::take(&mut realm_state.js_wasm_streaming_cb);
context.open(v8_isolate).clear_all_slots(v8_isolate);
context.clear_all_slots(scope);
}
}
let mut state = self.state.borrow_mut();
state.known_realms.clear();
state.global_realm.take();
}
let snapshot_creator = self.v8_isolate.take().unwrap();
@ -1335,21 +1337,26 @@ impl JsRuntime {
}
fn event_loop_pending_state(&mut self) -> EventLoopPendingState {
let isolate = self.v8_isolate.as_mut().unwrap();
let mut scope = v8::HandleScope::new(isolate);
EventLoopPendingState::new(
self.v8_isolate.as_mut().unwrap(),
&mut scope,
&mut self.state.borrow_mut(),
&self.module_map.as_ref().unwrap().borrow(),
)
}
pub(crate) fn event_loop_pending_state_from_isolate(
isolate: &mut v8::Isolate,
pub(crate) fn event_loop_pending_state_from_scope(
scope: &mut v8::HandleScope,
) -> EventLoopPendingState {
EventLoopPendingState::new(
isolate,
&mut Self::state(isolate).borrow_mut(),
&Self::module_map(isolate).borrow(),
)
let state = Self::state(scope);
let module_map = Self::module_map(scope);
let state = EventLoopPendingState::new(
scope,
&mut state.borrow_mut(),
&module_map.borrow(),
);
state
}
}
@ -1416,15 +1423,21 @@ pub(crate) struct EventLoopPendingState {
}
impl EventLoopPendingState {
pub fn new(
isolate: &mut v8::Isolate,
scope: &mut v8::HandleScope<()>,
state: &mut JsRuntimeState,
module_map: &ModuleMap,
) -> EventLoopPendingState {
let mut num_unrefed_ops = 0;
if state.known_realms.len() == 1 {
let realm = state.global_realm.as_ref().unwrap();
num_unrefed_ops += realm.state(scope).borrow().unrefed_ops.len();
} else {
for weak_context in &state.known_realms {
if let Some(context) = weak_context.to_global(isolate) {
let realm = JsRealm::new(context);
num_unrefed_ops += realm.state(isolate).borrow().unrefed_ops.len();
if let Some(context) = weak_context.to_local(scope) {
let realm = JsRealmLocal::new(context);
num_unrefed_ops += realm.state(scope).borrow().unrefed_ops.len();
}
}
}
@ -1435,7 +1448,7 @@ impl EventLoopPendingState {
.pending_dyn_mod_evaluate
.is_empty(),
has_pending_module_evaluation: state.pending_mod_evaluate.is_some(),
has_pending_background_tasks: isolate.has_pending_background_tasks(),
has_pending_background_tasks: scope.has_pending_background_tasks(),
has_tick_scheduled: state.has_tick_scheduled,
}
}
@ -2176,11 +2189,12 @@ impl JsRuntime {
}
fn check_promise_rejections(&mut self) -> Result<(), Error> {
let known_realms = self.state.borrow().known_realms.clone();
let isolate = self.v8_isolate();
for weak_context in known_realms {
if let Some(context) = weak_context.to_global(isolate) {
JsRealm::new(context).check_promise_rejections(isolate)?;
let state = self.state.clone();
let scope = &mut self.handle_scope();
let state = state.borrow();
for weak_context in &state.known_realms {
if let Some(context) = weak_context.to_local(scope) {
JsRealmLocal::new(context).check_promise_rejections(scope)?;
}
}
Ok(())