0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 20:25:12 -05:00

refactor(core): op initialization and snapshot creator (#18221)

This PR cleans up APIs related to snapshot creation and how ops are
initialized.

Prerequisite for #18080

---------

Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
Bartek Iwańczuk 2023-03-16 09:27:16 -04:00 committed by GitHub
parent b9d9cd17c9
commit b258763558
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 222 additions and 231 deletions

View file

@ -4,7 +4,6 @@ use std::option::Option;
use std::os::raw::c_void;
use log::debug;
use v8::fast_api::FastFunction;
use v8::MapFnTo;
use crate::error::is_instance_of_error;
@ -16,13 +15,13 @@ use crate::modules::ImportAssertionsKind;
use crate::modules::ModuleMap;
use crate::modules::ResolutionKind;
use crate::ops::OpCtx;
use crate::runtime::SnapshotOptions;
use crate::snapshot_util::SnapshotOptions;
use crate::JsRealm;
use crate::JsRuntime;
pub fn external_references(
pub(crate) fn external_references(
ops: &[OpCtx],
snapshot_loaded: bool,
snapshot_options: SnapshotOptions,
) -> v8::ExternalReferences {
let mut references = vec![
v8::ExternalReference {
@ -45,7 +44,7 @@ pub fn external_references(
references.push(v8::ExternalReference {
function: ctx.decl.v8_fn_ptr,
});
if snapshot_loaded {
if !snapshot_options.will_snapshot() {
if let Some(fast_fn) = &ctx.decl.fast_fn {
references.push(v8::ExternalReference {
pointer: fast_fn.function() as _,
@ -99,13 +98,11 @@ pub fn module_origin<'a>(
)
}
pub fn initialize_context<'s>(
pub(crate) fn initialize_context<'s>(
scope: &mut v8::HandleScope<'s, ()>,
op_ctxs: &[OpCtx],
snapshot_options: SnapshotOptions,
) -> v8::Local<'s, v8::Context> {
let scope = &mut v8::EscapableHandleScope::new(scope);
let context = v8::Context::new(scope);
let global = context.global(scope);
@ -136,8 +133,10 @@ pub fn initialize_context<'s>(
.expect("Deno.core.ops to exist")
.try_into()
.unwrap();
initialize_ops(scope, ops_obj, op_ctxs, snapshot_options);
return scope.escape(context);
for ctx in op_ctxs {
add_op_to_deno_core_ops(scope, ops_obj, ctx, snapshot_options);
}
return context;
}
// global.Deno = { core: { } };
@ -160,47 +159,14 @@ pub fn initialize_context<'s>(
// Bind functions to Deno.core.ops.*
let ops_obj = v8::Object::new(scope);
core_obj.set(scope, ops_str.into(), ops_obj.into());
initialize_ops(scope, ops_obj, op_ctxs, snapshot_options);
scope.escape(context)
}
fn initialize_ops(
scope: &mut v8::HandleScope,
ops_obj: v8::Local<v8::Object>,
op_ctxs: &[OpCtx],
snapshot_options: SnapshotOptions,
) {
for ctx in op_ctxs {
let ctx_ptr = ctx as *const OpCtx as *const c_void;
// If this is a fast op, we don't want it to be in the snapshot.
// Only initialize once snapshot is loaded.
if ctx.decl.fast_fn.is_some() && snapshot_options.loaded() {
set_func_raw(
scope,
ops_obj,
ctx.decl.name,
ctx.decl.v8_fn_ptr,
ctx_ptr,
&ctx.decl.fast_fn,
snapshot_options,
);
} else {
set_func_raw(
scope,
ops_obj,
ctx.decl.name,
ctx.decl.v8_fn_ptr,
ctx_ptr,
&None,
snapshot_options,
);
}
add_op_to_deno_core_ops(scope, ops_obj, ctx, snapshot_options);
}
context
}
pub fn set_func(
fn set_func(
scope: &mut v8::HandleScope<'_>,
obj: v8::Local<v8::Object>,
name: &'static str,
@ -213,28 +179,33 @@ pub fn set_func(
obj.set(scope, key.into(), val.into());
}
// Register a raw v8::FunctionCallback
// with some external data.
pub fn set_func_raw(
fn add_op_to_deno_core_ops(
scope: &mut v8::HandleScope<'_>,
obj: v8::Local<v8::Object>,
name: &'static str,
callback: v8::FunctionCallback,
external_data: *const c_void,
fast_function: &Option<Box<dyn FastFunction>>,
op_ctx: &OpCtx,
snapshot_options: SnapshotOptions,
) {
let op_ctx_ptr = op_ctx as *const OpCtx as *const c_void;
let key =
v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
let external = v8::External::new(scope, external_data as *mut c_void);
let builder =
v8::FunctionTemplate::builder_raw(callback).data(external.into());
let templ = if let Some(fast_function) = fast_function {
v8::String::new_external_onebyte_static(scope, op_ctx.decl.name.as_bytes())
.unwrap();
let external = v8::External::new(scope, op_ctx_ptr as *mut c_void);
let builder = v8::FunctionTemplate::builder_raw(op_ctx.decl.v8_fn_ptr)
.data(external.into());
// TODO(bartlomieju): this should be cleaned up once we update Fast Calls API
// If this is a fast op, we don't want it to be in the snapshot.
// Only initialize once snapshot is loaded.
let maybe_fast_fn =
if op_ctx.decl.fast_fn.is_some() && snapshot_options.loaded() {
&op_ctx.decl.fast_fn
} else {
&None
};
let templ = if let Some(fast_function) = maybe_fast_fn {
// Don't initialize fast ops when snapshotting, the external references count mismatch.
if matches!(
snapshot_options,
SnapshotOptions::Load | SnapshotOptions::None
) {
if !snapshot_options.will_snapshot() {
// TODO(@littledivy): Support fast api overloads in ops.
builder.build_fast(scope, &**fast_function, None)
} else {

View file

@ -6,6 +6,7 @@ use crate::extensions::ExtensionFileSource;
use crate::module_specifier::ModuleSpecifier;
use crate::resolve_import;
use crate::resolve_url;
use crate::snapshot_util::SnapshottedData;
use crate::JsRuntime;
use crate::OpState;
use anyhow::Error;
@ -1030,7 +1031,7 @@ impl ModuleMap {
pub fn serialize_for_snapshotting(
&self,
scope: &mut v8::HandleScope,
) -> (v8::Global<v8::Array>, Vec<v8::Global<v8::Module>>) {
) -> SnapshottedData {
let array = v8::Array::new(scope, 3);
let next_load_id = v8::Integer::new(scope, self.next_load_id);
@ -1105,16 +1106,19 @@ impl ModuleMap {
let array_global = v8::Global::new(scope, array);
let handles = self.handles.clone();
(array_global, handles)
SnapshottedData {
module_map_data: array_global,
module_handles: handles,
}
}
pub fn update_with_snapshot_data(
pub fn update_with_snapshotted_data(
&mut self,
scope: &mut v8::HandleScope,
data: v8::Global<v8::Array>,
module_handles: Vec<v8::Global<v8::Module>>,
snapshotted_data: SnapshottedData,
) {
let local_data: v8::Local<v8::Array> = v8::Local::new(scope, data);
let local_data: v8::Local<v8::Array> =
v8::Local::new(scope, snapshotted_data.module_map_data);
{
let next_load_id = local_data.get_index(scope, 0).unwrap();
@ -1258,7 +1262,7 @@ impl ModuleMap {
self.by_name = by_name;
}
self.handles = module_handles;
self.handles = snapshotted_data.module_handles;
}
pub(crate) fn new(

View file

@ -17,6 +17,7 @@ use crate::modules::ModuleMap;
use crate::op_void_async;
use crate::op_void_sync;
use crate::ops::*;
use crate::snapshot_util;
use crate::source_map::SourceMapCache;
use crate::source_map::SourceMapGetter;
use crate::Extension;
@ -86,7 +87,7 @@ pub struct JsRuntime {
// This is an Option<OwnedIsolate> instead of just OwnedIsolate to workaround
// a safety issue with SnapshotCreator. See JsRuntime::drop.
v8_isolate: Option<v8::OwnedIsolate>,
snapshot_options: SnapshotOptions,
snapshot_options: snapshot_util::SnapshotOptions,
allocations: IsolateAllocations,
extensions: Vec<Extension>,
event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
@ -298,32 +299,6 @@ pub struct RuntimeOptions {
pub is_main: bool,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum SnapshotOptions {
Load,
CreateFromExisting,
Create,
None,
}
impl SnapshotOptions {
pub fn loaded(&self) -> bool {
matches!(
self,
SnapshotOptions::Load | SnapshotOptions::CreateFromExisting
)
}
fn from_bools(snapshot_loaded: bool, will_snapshot: bool) -> Self {
match (snapshot_loaded, will_snapshot) {
(true, true) => SnapshotOptions::CreateFromExisting,
(false, true) => SnapshotOptions::Create,
(true, false) => SnapshotOptions::Load,
(false, false) => SnapshotOptions::None,
}
}
}
impl Drop for JsRuntime {
fn drop(&mut self) {
if let Some(v8_isolate) = self.v8_isolate.as_mut() {
@ -410,113 +385,30 @@ impl JsRuntime {
.collect::<Vec<_>>()
.into_boxed_slice();
let refs = bindings::external_references(&op_ctxs, !options.will_snapshot);
let snapshot_options = snapshot_util::SnapshotOptions::from_bools(
options.startup_snapshot.is_some(),
options.will_snapshot,
);
let refs = bindings::external_references(&op_ctxs, snapshot_options);
// V8 takes ownership of external_references.
let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs));
let global_context;
let mut module_map_data = None;
let mut module_handles = vec![];
let mut maybe_snapshotted_data = None;
fn get_context_data(
scope: &mut v8::HandleScope<()>,
context: v8::Local<v8::Context>,
) -> (Vec<v8::Global<v8::Module>>, v8::Global<v8::Array>) {
fn data_error_to_panic(err: v8::DataError) -> ! {
match err {
v8::DataError::BadType { actual, expected } => {
panic!(
"Invalid type for snapshot data: expected {expected}, got {actual}"
);
}
v8::DataError::NoData { expected } => {
panic!("No data for snapshot data: expected {expected}");
}
}
}
let mut scope = v8::ContextScope::new(scope, context);
// The 0th element is the module map itself, followed by X number of module
// handles. We need to deserialize the "next_module_id" field from the
// map to see how many module handles we expect.
match scope.get_context_data_from_snapshot_once::<v8::Array>(0) {
Ok(val) => {
let next_module_id = {
let info_data: v8::Local<v8::Array> =
val.get_index(&mut scope, 1).unwrap().try_into().unwrap();
info_data.length()
};
// Over allocate so executing a few scripts doesn't have to resize this vec.
let mut module_handles =
Vec::with_capacity(next_module_id as usize + 16);
for i in 1..=next_module_id {
match scope
.get_context_data_from_snapshot_once::<v8::Module>(i as usize)
{
Ok(val) => {
let module_global = v8::Global::new(&mut scope, val);
module_handles.push(module_global);
}
Err(err) => data_error_to_panic(err),
}
}
(module_handles, v8::Global::new(&mut scope, val))
}
Err(err) => data_error_to_panic(err),
}
}
let (mut isolate, snapshot_options) = if options.will_snapshot {
let (snapshot_creator, snapshot_loaded) =
if let Some(snapshot) = options.startup_snapshot {
(
match snapshot {
Snapshot::Static(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
data,
Some(refs),
)
}
Snapshot::JustCreated(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
data,
Some(refs),
)
}
Snapshot::Boxed(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
data,
Some(refs),
)
}
},
true,
)
} else {
(v8::Isolate::snapshot_creator(Some(refs)), false)
};
let snapshot_options =
SnapshotOptions::from_bools(snapshot_loaded, options.will_snapshot);
let (mut isolate, snapshot_options) = if snapshot_options.will_snapshot() {
let snapshot_creator =
snapshot_util::create_snapshot_creator(refs, options.startup_snapshot);
let mut isolate = JsRuntime::setup_isolate(snapshot_creator);
{
// SAFETY: this is first use of `isolate_ptr` so we are sure we're
// not overwriting an existing pointer.
isolate = unsafe {
isolate_ptr.write(isolate);
isolate_ptr.read()
};
let scope = &mut v8::HandleScope::new(&mut isolate);
let context =
bindings::initialize_context(scope, &op_ctxs, snapshot_options);
// Get module map data from the snapshot
if has_startup_snapshot {
let context_data = get_context_data(scope, context);
module_handles = context_data.0;
module_map_data = Some(context_data.1);
maybe_snapshotted_data =
Some(snapshot_util::get_snapshotted_data(scope, context));
}
global_context = v8::Global::new(scope, context);
@ -534,38 +426,26 @@ impl JsRuntime {
)
})
.external_references(&**refs);
let snapshot_loaded = if let Some(snapshot) = options.startup_snapshot {
if let Some(snapshot) = options.startup_snapshot {
params = match snapshot {
Snapshot::Static(data) => params.snapshot_blob(data),
Snapshot::JustCreated(data) => params.snapshot_blob(data),
Snapshot::Boxed(data) => params.snapshot_blob(data),
};
true
} else {
false
};
let snapshot_options =
SnapshotOptions::from_bools(snapshot_loaded, options.will_snapshot);
}
let isolate = v8::Isolate::new(params);
let mut isolate = JsRuntime::setup_isolate(isolate);
{
// SAFETY: this is first use of `isolate_ptr` so we are sure we're
// not overwriting an existing pointer.
isolate = unsafe {
isolate_ptr.write(isolate);
isolate_ptr.read()
};
let scope = &mut v8::HandleScope::new(&mut isolate);
let context =
bindings::initialize_context(scope, &op_ctxs, snapshot_options);
// Get module map data from the snapshot
if has_startup_snapshot {
let context_data = get_context_data(scope, context);
module_handles = context_data.0;
module_map_data = Some(context_data.1);
maybe_snapshotted_data =
Some(snapshot_util::get_snapshotted_data(scope, context));
}
global_context = v8::Global::new(scope, context);
@ -574,6 +454,12 @@ impl JsRuntime {
(isolate, snapshot_options)
};
// SAFETY: this is first use of `isolate_ptr` so we are sure we're
// not overwriting an existing pointer.
isolate = unsafe {
isolate_ptr.write(isolate);
isolate_ptr.read()
};
global_context.open(&mut isolate).set_slot(
&mut isolate,
Rc::new(RefCell::new(ContextState {
@ -593,7 +479,7 @@ impl JsRuntime {
None
};
let loader = if snapshot_options != SnapshotOptions::Load {
let loader = if snapshot_options != snapshot_util::SnapshotOptions::Load {
let esm_sources = options
.extensions
.iter()
@ -604,7 +490,7 @@ impl JsRuntime {
.collect::<Vec<ExtensionFileSource>>();
#[cfg(feature = "include_js_files_for_snapshotting")]
if snapshot_options != SnapshotOptions::None {
if snapshot_options != snapshot_util::SnapshotOptions::None {
for source in &esm_sources {
use crate::ExtensionFileSourceCode;
if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
@ -642,17 +528,13 @@ impl JsRuntime {
let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(
loader,
op_state,
snapshot_options == SnapshotOptions::Load,
snapshot_options == snapshot_util::SnapshotOptions::Load,
)));
if let Some(module_map_data) = module_map_data {
if let Some(snapshotted_data) = maybe_snapshotted_data {
let scope =
&mut v8::HandleScope::with_context(&mut isolate, global_context);
let mut module_map = module_map_rc.borrow_mut();
module_map.update_with_snapshot_data(
scope,
module_map_data,
module_handles,
);
module_map.update_with_snapshotted_data(scope, snapshotted_data);
}
isolate.set_data(
Self::MODULE_MAP_DATA_OFFSET,
@ -1061,23 +943,19 @@ impl JsRuntime {
// Serialize the module map and store its data in the snapshot.
{
let module_map_rc = self.module_map.take().unwrap();
let module_map = module_map_rc.borrow();
let (module_map_data, module_handles) =
module_map.serialize_for_snapshotting(&mut self.handle_scope());
let snapshotted_data = {
let module_map_rc = self.module_map.take().unwrap();
let module_map = module_map_rc.borrow();
module_map.serialize_for_snapshotting(&mut self.handle_scope())
};
let context = self.global_context();
let mut scope = self.handle_scope();
let local_context = v8::Local::new(&mut scope, context);
let local_data = v8::Local::new(&mut scope, module_map_data);
let offset = scope.add_context_data(local_context, local_data);
assert_eq!(offset, 0);
for (index, handle) in module_handles.into_iter().enumerate() {
let module_handle = v8::Local::new(&mut scope, handle);
let offset = scope.add_context_data(local_context, module_handle);
assert_eq!(offset, index + 1);
}
snapshot_util::set_snapshotted_data(
&mut scope,
context,
snapshotted_data,
);
}
// Drop existing ModuleMap to drop v8::Global handles

View file

@ -107,3 +107,141 @@ pub fn get_js_files(
js_files.sort();
js_files
}
fn data_error_to_panic(err: v8::DataError) -> ! {
match err {
v8::DataError::BadType { actual, expected } => {
panic!(
"Invalid type for snapshot data: expected {expected}, got {actual}"
);
}
v8::DataError::NoData { expected } => {
panic!("No data for snapshot data: expected {expected}");
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum SnapshotOptions {
Load,
CreateFromExisting,
Create,
None,
}
impl SnapshotOptions {
pub fn loaded(&self) -> bool {
matches!(self, Self::Load | Self::CreateFromExisting)
}
pub fn will_snapshot(&self) -> bool {
matches!(self, Self::Create | Self::CreateFromExisting)
}
pub fn from_bools(snapshot_loaded: bool, will_snapshot: bool) -> Self {
match (snapshot_loaded, will_snapshot) {
(true, true) => Self::CreateFromExisting,
(false, true) => Self::Create,
(true, false) => Self::Load,
(false, false) => Self::None,
}
}
}
pub(crate) struct SnapshottedData {
pub module_map_data: v8::Global<v8::Array>,
pub module_handles: Vec<v8::Global<v8::Module>>,
}
static MODULE_MAP_CONTEXT_DATA_INDEX: usize = 0;
pub(crate) fn get_snapshotted_data(
scope: &mut v8::HandleScope<()>,
context: v8::Local<v8::Context>,
) -> SnapshottedData {
let mut scope = v8::ContextScope::new(scope, context);
// The 0th element is the module map itself, followed by X number of module
// handles. We need to deserialize the "next_module_id" field from the
// map to see how many module handles we expect.
let result = scope.get_context_data_from_snapshot_once::<v8::Array>(
MODULE_MAP_CONTEXT_DATA_INDEX,
);
let val = match result {
Ok(v) => v,
Err(err) => data_error_to_panic(err),
};
let next_module_id = {
let info_data: v8::Local<v8::Array> =
val.get_index(&mut scope, 1).unwrap().try_into().unwrap();
info_data.length()
};
// Over allocate so executing a few scripts doesn't have to resize this vec.
let mut module_handles = Vec::with_capacity(next_module_id as usize + 16);
for i in 1..=next_module_id {
match scope.get_context_data_from_snapshot_once::<v8::Module>(i as usize) {
Ok(val) => {
let module_global = v8::Global::new(&mut scope, val);
module_handles.push(module_global);
}
Err(err) => data_error_to_panic(err),
}
}
SnapshottedData {
module_map_data: v8::Global::new(&mut scope, val),
module_handles,
}
}
pub(crate) fn set_snapshotted_data(
scope: &mut v8::HandleScope<()>,
context: v8::Global<v8::Context>,
snapshotted_data: SnapshottedData,
) {
let local_context = v8::Local::new(scope, context);
let local_data = v8::Local::new(scope, snapshotted_data.module_map_data);
let offset = scope.add_context_data(local_context, local_data);
assert_eq!(offset, MODULE_MAP_CONTEXT_DATA_INDEX);
for (index, handle) in snapshotted_data.module_handles.into_iter().enumerate()
{
let module_handle = v8::Local::new(scope, handle);
let offset = scope.add_context_data(local_context, module_handle);
assert_eq!(offset, index + 1);
}
}
/// Returns an isolate set up for snapshotting.
pub(crate) fn create_snapshot_creator(
external_refs: &'static v8::ExternalReferences,
maybe_startup_snapshot: Option<Snapshot>,
) -> v8::OwnedIsolate {
if let Some(snapshot) = maybe_startup_snapshot {
match snapshot {
Snapshot::Static(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
data,
Some(external_refs),
)
}
Snapshot::JustCreated(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
data,
Some(external_refs),
)
}
Snapshot::Boxed(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
data,
Some(external_refs),
)
}
}
} else {
v8::Isolate::snapshot_creator(Some(external_refs))
}
}