diff options
Diffstat (limited to 'core/runtime.rs')
-rw-r--r-- | core/runtime.rs | 257 |
1 files changed, 242 insertions, 15 deletions
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] |