diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/modules.rs | 145 | ||||
-rw-r--r-- | core/runtime.rs | 257 |
2 files changed, 379 insertions, 23 deletions
diff --git a/core/modules.rs b/core/modules.rs index 8a79143f0..7c1b74732 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -14,6 +14,8 @@ use futures::stream::Stream; use futures::stream::StreamFuture; use futures::stream::TryStreamExt; use log::debug; +use serde::Deserialize; +use serde::Serialize; use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; @@ -157,7 +159,7 @@ fn json_module_evaluation_steps<'a>( /// how to interpret the module; it is only used to validate /// the module against an import assertion (if one is present /// in the import statement). -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ModuleType { JavaScript, Json, @@ -720,7 +722,7 @@ impl Stream for RecursiveModuleLoad { } } -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub(crate) enum AssertedModuleType { JavaScriptOrWasm, Json, @@ -748,12 +750,13 @@ impl std::fmt::Display for AssertedModuleType { /// Usually executable (`JavaScriptOrWasm`) is used, except when an /// import assertions explicitly constrains an import to JSON, in /// which case this will have a `AssertedModuleType::Json`. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub(crate) struct ModuleRequest { pub specifier: ModuleSpecifier, pub asserted_module_type: AssertedModuleType, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub(crate) struct ModuleInfo { #[allow(unused)] pub id: ModuleId, @@ -765,7 +768,8 @@ pub(crate) struct ModuleInfo { } /// A symbolic module entity. -enum SymbolicModule { +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub(crate) enum SymbolicModule { /// This module is an alias to another module. /// This is useful such that multiple names could point to /// the same underlying module (particularly due to redirects). @@ -783,12 +787,12 @@ pub(crate) enum ModuleError { /// A collection of JS modules. pub(crate) struct ModuleMap { // Handling of specifiers and v8 objects - ids_by_handle: HashMap<v8::Global<v8::Module>, ModuleId>, + pub(crate) ids_by_handle: HashMap<v8::Global<v8::Module>, ModuleId>, pub handles_by_id: HashMap<ModuleId, v8::Global<v8::Module>>, pub info: HashMap<ModuleId, ModuleInfo>, - by_name: HashMap<(String, AssertedModuleType), SymbolicModule>, - next_module_id: ModuleId, - next_load_id: ModuleLoadId, + pub(crate) by_name: HashMap<(String, AssertedModuleType), SymbolicModule>, + pub(crate) next_module_id: ModuleId, + pub(crate) next_load_id: ModuleLoadId, // Handling of futures for loading module sources pub loader: Rc<dyn ModuleLoader>, @@ -806,6 +810,131 @@ pub(crate) struct ModuleMap { } impl ModuleMap { + pub fn serialize_for_snapshotting( + &self, + scope: &mut v8::HandleScope, + ) -> (v8::Global<v8::Object>, Vec<v8::Global<v8::Module>>) { + let obj = v8::Object::new(scope); + + let next_module_id_str = v8::String::new(scope, "next_module_id").unwrap(); + let next_module_id = v8::Integer::new(scope, self.next_module_id); + obj.set(scope, next_module_id_str.into(), next_module_id.into()); + + let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap(); + let next_load_id = v8::Integer::new(scope, self.next_load_id); + obj.set(scope, next_load_id_str.into(), next_load_id.into()); + + let info_obj = v8::Object::new(scope); + for (key, value) in self.info.clone().into_iter() { + let key_val = v8::Integer::new(scope, key); + let module_info = serde_v8::to_v8(scope, value).unwrap(); + info_obj.set(scope, key_val.into(), module_info); + } + let info_str = v8::String::new(scope, "info").unwrap(); + obj.set(scope, info_str.into(), info_obj.into()); + + let by_name_triples: Vec<(String, AssertedModuleType, SymbolicModule)> = + self + .by_name + .clone() + .into_iter() + .map(|el| (el.0 .0, el.0 .1, el.1)) + .collect(); + let by_name_array = serde_v8::to_v8(scope, by_name_triples).unwrap(); + let by_name_str = v8::String::new(scope, "by_name").unwrap(); + obj.set(scope, by_name_str.into(), by_name_array); + + let obj_global = v8::Global::new(scope, obj); + + let mut handles_and_ids: Vec<(ModuleId, v8::Global<v8::Module>)> = + self.handles_by_id.clone().into_iter().collect(); + handles_and_ids.sort_by_key(|(id, _)| *id); + let handles: Vec<v8::Global<v8::Module>> = handles_and_ids + .into_iter() + .map(|(_, handle)| handle) + .collect(); + (obj_global, handles) + } + + pub fn update_with_snapshot_data( + &mut self, + scope: &mut v8::HandleScope, + data: v8::Global<v8::Object>, + module_handles: Vec<v8::Global<v8::Module>>, + ) { + let local_data: v8::Local<v8::Object> = v8::Local::new(scope, data); + + { + let next_module_id_str = + v8::String::new(scope, "next_module_id").unwrap(); + let next_module_id = + local_data.get(scope, next_module_id_str.into()).unwrap(); + assert!(next_module_id.is_int32()); + let integer = next_module_id.to_integer(scope).unwrap(); + let val = integer.int32_value(scope).unwrap(); + self.next_module_id = val; + } + + { + let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap(); + let next_load_id = + local_data.get(scope, next_load_id_str.into()).unwrap(); + assert!(next_load_id.is_int32()); + let integer = next_load_id.to_integer(scope).unwrap(); + let val = integer.int32_value(scope).unwrap(); + self.next_load_id = val; + } + + { + let mut info = HashMap::new(); + let info_str = v8::String::new(scope, "info").unwrap(); + let info_data: v8::Local<v8::Object> = local_data + .get(scope, info_str.into()) + .unwrap() + .try_into() + .unwrap(); + let keys = info_data + .get_own_property_names(scope, v8::GetPropertyNamesArgs::default()) + .unwrap(); + let keys_len = keys.length(); + + for i in 0..keys_len { + let key = keys.get_index(scope, i).unwrap(); + let key_val = key.to_integer(scope).unwrap(); + let key_int = key_val.int32_value(scope).unwrap(); + let value = info_data.get(scope, key).unwrap(); + let module_info = serde_v8::from_v8(scope, value).unwrap(); + info.insert(key_int, module_info); + } + self.info = info; + } + + { + let by_name_str = v8::String::new(scope, "by_name").unwrap(); + let by_name_data = local_data.get(scope, by_name_str.into()).unwrap(); + let by_name_deser: Vec<(String, AssertedModuleType, SymbolicModule)> = + serde_v8::from_v8(scope, by_name_data).unwrap(); + self.by_name = by_name_deser + .into_iter() + .map(|(name, module_type, symbolic_module)| { + ((name, module_type), symbolic_module) + }) + .collect(); + } + + self.ids_by_handle = module_handles + .iter() + .enumerate() + .map(|(index, handle)| (handle.clone(), (index + 1) as i32)) + .collect(); + + self.handles_by_id = module_handles + .iter() + .enumerate() + .map(|(index, handle)| ((index + 1) as i32, handle.clone())) + .collect(); + } + pub(crate) fn new( loader: Rc<dyn ModuleLoader>, op_state: Rc<RefCell<OpState>>, diff --git a/core/runtime.rs b/core/runtime.rs index bd85e1a88..918049013 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -351,7 +351,8 @@ impl JsRuntime { DENO_INIT.call_once(move || v8_init(v8_platform, options.will_snapshot)); // Add builtins extension - if options.startup_snapshot.is_none() { + let has_startup_snapshot = options.startup_snapshot.is_some(); + if !has_startup_snapshot { options .extensions_with_js .insert(0, crate::ops_builtin::init_builtins()); @@ -423,6 +424,62 @@ impl JsRuntime { // 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![]; + + fn get_context_data( + scope: &mut v8::HandleScope<()>, + context: v8::Local<v8::Context>, + ) -> (Vec<v8::Global<v8::Module>>, v8::Global<v8::Object>) { + fn data_error_to_panic(err: v8::DataError) -> ! { + match err { + v8::DataError::BadType { actual, expected } => { + panic!( + "Invalid type for snapshot data: expected {}, got {}", + expected, actual + ); + } + v8::DataError::NoData { expected } => { + panic!("No data for snapshot data: expected {}", expected); + } + } + } + + let mut module_handles = vec![]; + 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::Object>(0) { + Ok(val) => { + let next_module_id = { + let next_module_id_str = + v8::String::new(&mut scope, "next_module_id").unwrap(); + let next_module_id = + val.get(&mut scope, next_module_id_str.into()).unwrap(); + assert!(next_module_id.is_int32()); + let integer = next_module_id.to_integer(&mut scope).unwrap(); + integer.int32_value(&mut scope).unwrap() + }; + let no_of_modules = next_module_id - 1; + + for i in 1..=no_of_modules { + 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) = @@ -468,6 +525,14 @@ impl JsRuntime { 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); + } + global_context = v8::Global::new(scope, context); scope.set_default_context(context); } @@ -510,6 +575,13 @@ impl JsRuntime { 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); + } + global_context = v8::Global::new(scope, context); } @@ -544,7 +616,7 @@ impl JsRuntime { state.inspector = inspector; state .known_realms - .push(v8::Weak::new(&mut isolate, global_context)); + .push(v8::Weak::new(&mut isolate, &global_context)); } isolate.set_data( Self::STATE_DATA_OFFSET, @@ -552,6 +624,16 @@ impl JsRuntime { ); let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state))); + if let Some(module_map_data) = module_map_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, + ); + } isolate.set_data( Self::MODULE_MAP_DATA_OFFSET, Rc::into_raw(module_map_rc.clone()) as *mut c_void, @@ -911,15 +993,37 @@ impl JsRuntime { } } - self.state.borrow_mut().global_realm.take(); self.state.borrow_mut().inspector.take(); + // 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 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); + } + } + // Drop existing ModuleMap to drop v8::Global handles { - self.module_map.take(); let v8_isolate = self.v8_isolate(); 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 { @@ -2605,10 +2709,13 @@ pub mod tests { use super::*; use crate::error::custom_error; use crate::error::AnyError; + use crate::modules::AssertedModuleType; + use crate::modules::ModuleInfo; use crate::modules::ModuleSource; use crate::modules::ModuleSourceFuture; use crate::modules::ModuleType; use crate::modules::ResolutionKind; + use crate::modules::SymbolicModule; use crate::ZeroCopyBuf; use deno_ops::op; use futures::future::lazy; @@ -2618,6 +2725,7 @@ pub mod tests { use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; + // deno_ops macros generate code assuming deno_core in scope. mod deno_core { pub use crate::*; @@ -3422,8 +3530,6 @@ pub mod tests { referrer: &str, _kind: ResolutionKind, ) -> Result<ModuleSpecifier, Error> { - assert_eq!(specifier, "file:///main.js"); - assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } @@ -3434,29 +3540,150 @@ pub mod tests { _maybe_referrer: Option<ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin<Box<ModuleSourceFuture>> { + eprintln!("load() should not be called"); unreachable!() } } - let loader = std::rc::Rc::new(ModsLoader::default()); + fn create_module( + runtime: &mut JsRuntime, + i: usize, + main: bool, + ) -> ModuleInfo { + let specifier = crate::resolve_url(&format!("file:///{i}.js")).unwrap(); + let prev = i - 1; + let source_code = format!( + r#" + import {{ f{prev} }} from "file:///{prev}.js"; + export function f{i}() {{ return f{prev}() }} + "# + ); + + let id = if main { + futures::executor::block_on( + runtime.load_main_module(&specifier, Some(source_code)), + ) + .unwrap() + } else { + futures::executor::block_on( + runtime.load_side_module(&specifier, Some(source_code)), + ) + .unwrap() + }; + assert_eq!(i + 1, id as usize); + + let _ = runtime.mod_evaluate(id); + futures::executor::block_on(runtime.run_event_loop(false)).unwrap(); + + ModuleInfo { + id, + main, + name: specifier.to_string(), + requests: vec![crate::modules::ModuleRequest { + specifier: crate::resolve_url(&format!("file:///{prev}.js")).unwrap(), + asserted_module_type: AssertedModuleType::JavaScriptOrWasm, + }], + module_type: ModuleType::JavaScript, + } + } + + fn assert_module_map(runtime: &mut JsRuntime, modules: &Vec<ModuleInfo>) { + let module_map_rc = runtime.get_module_map(); + let module_map = module_map_rc.borrow(); + assert_eq!(module_map.ids_by_handle.len(), modules.len()); + assert_eq!(module_map.handles_by_id.len(), modules.len()); + assert_eq!(module_map.info.len(), modules.len()); + assert_eq!(module_map.by_name.len(), modules.len()); + + assert_eq!(module_map.next_module_id, (modules.len() + 1) as ModuleId); + assert_eq!(module_map.next_load_id, (modules.len() + 1) as ModuleId); + + let ids_by_handle = module_map.ids_by_handle.values().collect::<Vec<_>>(); + + for info in modules { + assert!(ids_by_handle.contains(&&info.id)); + assert!(module_map.handles_by_id.contains_key(&info.id)); + assert_eq!(module_map.info.get(&info.id).unwrap(), info); + assert_eq!( + module_map + .by_name + .get(&(info.name.clone(), AssertedModuleType::JavaScriptOrWasm)) + .unwrap(), + &SymbolicModule::Mod(info.id) + ); + } + } + + let loader = Rc::new(ModsLoader::default()); let mut runtime = JsRuntime::new(RuntimeOptions { - module_loader: Some(loader), + module_loader: Some(loader.clone()), will_snapshot: true, ..Default::default() }); - let specifier = crate::resolve_url("file:///main.js").unwrap(); - let source_code = "Deno.core.print('hello\\n')".to_string(); - - let module_id = futures::executor::block_on( - runtime.load_main_module(&specifier, Some(source_code)), + let specifier = crate::resolve_url("file:///0.js").unwrap(); + let source_code = + r#"export function f0() { return "hello world" }"#.to_string(); + let id = futures::executor::block_on( + runtime.load_side_module(&specifier, Some(source_code)), ) .unwrap(); - let _ = runtime.mod_evaluate(module_id); + let _ = runtime.mod_evaluate(id); futures::executor::block_on(runtime.run_event_loop(false)).unwrap(); - let _snapshot = runtime.snapshot(); + let mut modules = vec![]; + modules.push(ModuleInfo { + id, + main: false, + name: specifier.to_string(), + requests: vec![], + module_type: ModuleType::JavaScript, + }); + + modules.extend((1..200).map(|i| create_module(&mut runtime, i, false))); + + assert_module_map(&mut runtime, &modules); + + let snapshot = runtime.snapshot(); + + let mut runtime2 = JsRuntime::new(RuntimeOptions { + module_loader: Some(loader.clone()), + will_snapshot: true, + startup_snapshot: Some(Snapshot::JustCreated(snapshot)), + ..Default::default() + }); + + assert_module_map(&mut runtime2, &modules); + + modules.extend((200..400).map(|i| create_module(&mut runtime2, i, false))); + modules.push(create_module(&mut runtime2, 400, true)); + + assert_module_map(&mut runtime2, &modules); + + let snapshot2 = runtime2.snapshot(); + + let mut runtime3 = JsRuntime::new(RuntimeOptions { + module_loader: Some(loader), + startup_snapshot: Some(Snapshot::JustCreated(snapshot2)), + ..Default::default() + }); + + assert_module_map(&mut runtime3, &modules); + + let source_code = r#"(async () => { + const mod = await import("file:///400.js"); + return mod.f400(); + })();"# + .to_string(); + let val = runtime3.execute_script(".", &source_code).unwrap(); + let val = futures::executor::block_on(runtime3.resolve_value(val)).unwrap(); + { + let scope = &mut runtime3.handle_scope(); + let value = v8::Local::new(scope, val); + let str_ = value.to_string(scope).unwrap().to_rust_string_lossy(scope); + assert_eq!(str_, "hello world"); + } } #[test] |