1use anyhow::Error;
4use serde::Deserialize;
5use serde::Serialize;
6use std::collections::HashMap;
7use std::path::Path;
8use std::path::PathBuf;
9use std::rc::Rc;
10use std::time::Instant;
11
12use crate::modules::ModuleMapSnapshotData;
13use crate::Extension;
14use crate::JsRuntimeForSnapshot;
15use crate::RuntimeOptions;
16
17use super::ExtensionTranspiler;
18
19pub type WithRuntimeCb = dyn Fn(&mut JsRuntimeForSnapshot);
20
21pub type SnapshotDataId = u32;
22
23const ULEN: usize = std::mem::size_of::<usize>();
25
26pub(crate) struct V8Snapshot(pub(crate) &'static [u8]);
29
30pub(crate) fn deconstruct(
31 slice: &'static [u8],
32) -> (V8Snapshot, SerializableSnapshotSidecarData) {
33 let len =
34 usize::from_le_bytes(slice[slice.len() - ULEN..].try_into().unwrap());
35 let data = SerializableSnapshotSidecarData::from_slice(
36 &slice[len..slice.len() - ULEN],
37 );
38 (V8Snapshot(&slice[0..len]), data)
39}
40
41pub(crate) fn serialize(
42 v8_data: v8::StartupData,
43 sidecar_data: SerializableSnapshotSidecarData,
44) -> Box<[u8]> {
45 let v8_data_len = v8_data.len();
46 let sidecar_data = sidecar_data.into_bytes();
47 let mut data = Vec::with_capacity(v8_data_len + sidecar_data.len() + ULEN);
48
49 data.extend_from_slice(&v8_data);
51 data.extend_from_slice(&sidecar_data);
52 data.extend_from_slice(&v8_data_len.to_le_bytes());
53
54 data.into_boxed_slice()
55}
56
57#[derive(Default)]
58pub struct SnapshotLoadDataStore {
59 data: Vec<Option<v8::Global<v8::Data>>>,
60}
61
62impl SnapshotLoadDataStore {
63 pub fn get<'s, T>(
64 &mut self,
65 scope: &mut v8::HandleScope<'s>,
66 id: SnapshotDataId,
67 ) -> v8::Global<T>
68 where
69 v8::Local<'s, T>: TryFrom<v8::Local<'s, v8::Data>>,
70 {
71 let Some(data) = self.data.get_mut(id as usize) else {
72 panic!(
73 "Attempted to read snapshot data out of range: {id} (of {})",
74 self.data.len()
75 );
76 };
77 let Some(data) = data.take() else {
78 panic!("Attempted to read the snapshot data at index {id} twice");
79 };
80 let local = v8::Local::new(scope, data);
81 let local = v8::Local::<T>::try_from(local).unwrap_or_else(|_| {
82 panic!(
83 "Invalid data type at index {id}, expected '{}'",
84 std::any::type_name::<T>()
85 )
86 });
87 v8::Global::new(scope, local)
88 }
89}
90
91#[derive(Default)]
92pub struct SnapshotStoreDataStore {
93 data: Vec<v8::Global<v8::Data>>,
94}
95
96impl SnapshotStoreDataStore {
97 pub fn register<T>(&mut self, global: v8::Global<T>) -> SnapshotDataId
98 where
99 for<'s> v8::Local<'s, v8::Data>: From<v8::Local<'s, T>>,
100 {
101 let id = self.data.len();
102 unsafe {
105 self.data.push(std::mem::transmute(global));
106 }
107 id as _
108 }
109}
110
111pub struct CreateSnapshotOptions {
112 pub cargo_manifest_dir: &'static str,
113 pub startup_snapshot: Option<&'static [u8]>,
114 pub skip_op_registration: bool,
115 pub extensions: Vec<Extension>,
116 pub extension_transpiler: Option<Rc<ExtensionTranspiler>>,
117 pub with_runtime_cb: Option<Box<WithRuntimeCb>>,
118}
119
120pub struct CreateSnapshotOutput {
121 pub files_loaded_during_snapshot: Vec<PathBuf>,
124 pub output: Box<[u8]>,
125}
126
127#[must_use = "The files listed by create_snapshot should be printed as 'cargo:rerun-if-changed' lines"]
128pub fn create_snapshot(
129 create_snapshot_options: CreateSnapshotOptions,
130 warmup_script: Option<&'static str>,
131) -> Result<CreateSnapshotOutput, Error> {
132 let mut mark = Instant::now();
133 println!("Creating a snapshot...",);
134
135 let warmup_exts = warmup_script.map(|_| {
137 create_snapshot_options
138 .extensions
139 .iter()
140 .map(|e| e.for_warmup())
141 .collect::<Vec<_>>()
142 });
143
144 let mut js_runtime = JsRuntimeForSnapshot::new(RuntimeOptions {
145 startup_snapshot: create_snapshot_options.startup_snapshot,
146 extensions: create_snapshot_options.extensions,
147 extension_transpiler: create_snapshot_options.extension_transpiler,
148 skip_op_registration: create_snapshot_options.skip_op_registration,
149 ..Default::default()
150 });
151
152 println!("JsRuntimeForSnapshot prepared, took {:#?}", mark.elapsed(),);
153 mark = Instant::now();
154
155 let files_loaded_during_snapshot = js_runtime
156 .files_loaded_from_fs_during_snapshot()
157 .iter()
158 .map(PathBuf::from)
159 .collect::<Vec<_>>();
160
161 if let Some(ref with_runtime_cb) = create_snapshot_options.with_runtime_cb {
162 with_runtime_cb(&mut js_runtime);
163 }
164
165 let mut snapshot = js_runtime.snapshot();
166 if let Some(warmup_script) = warmup_script {
167 let leaked_snapshot = Box::leak(snapshot);
168 let warmup_exts = warmup_exts.unwrap();
169
170 let mut js_runtime = JsRuntimeForSnapshot::new(RuntimeOptions {
176 startup_snapshot: Some(leaked_snapshot),
177 extensions: warmup_exts,
178 skip_op_registration: true,
179 ..Default::default()
180 });
181
182 if let Some(with_runtime_cb) = create_snapshot_options.with_runtime_cb {
183 with_runtime_cb(&mut js_runtime);
184 }
185
186 js_runtime.execute_script("warmup", warmup_script)?;
187
188 snapshot = js_runtime.snapshot();
189 }
190
191 println!(
192 "Snapshot size: {}, took {:#?}",
193 snapshot.len(),
194 mark.elapsed(),
195 );
196 mark = Instant::now();
197
198 println!(
199 "Snapshot written, took: {:#?}",
200 Instant::now().saturating_duration_since(mark),
201 );
202
203 Ok(CreateSnapshotOutput {
204 files_loaded_during_snapshot,
205 output: snapshot,
206 })
207}
208
209pub type FilterFn = Box<dyn Fn(&PathBuf) -> bool>;
210
211pub fn get_js_files(
212 cargo_manifest_dir: &'static str,
213 directory: &str,
214 filter: Option<FilterFn>,
215) -> Vec<PathBuf> {
216 let manifest_dir = Path::new(cargo_manifest_dir);
217 let mut js_files = std::fs::read_dir(directory)
218 .unwrap()
219 .map(|dir_entry| {
220 let file = dir_entry.unwrap();
221 manifest_dir.join(file.path())
222 })
223 .filter(|path| {
224 path.extension().unwrap_or_default() == "js"
225 && filter.as_ref().map(|filter| filter(path)).unwrap_or(true)
226 })
227 .collect::<Vec<PathBuf>>();
228 js_files.sort();
229 js_files
230}
231
232#[derive(Serialize, Deserialize)]
235pub(crate) struct SnapshottedData<'snapshot> {
236 pub js_handled_promise_rejection_cb: Option<u32>,
237 pub module_map_data: ModuleMapSnapshotData,
238 pub externals_count: u32,
239 pub extension_count: usize,
240 pub op_count: usize,
241 pub source_count: usize,
242 pub addl_refs_count: usize,
243 #[serde(borrow)]
244 pub ext_source_maps: HashMap<String, &'snapshot [u8]>,
245 #[serde(borrow)]
246 pub external_strings: Vec<&'snapshot [u8]>,
247}
248
249#[derive(Serialize, Deserialize)]
253pub(crate) struct SerializableSnapshotSidecarData<'snapshot> {
254 data_count: u32,
255 #[serde(borrow)]
256 pub snapshot_data: SnapshottedData<'snapshot>,
257}
258
259impl<'snapshot> SerializableSnapshotSidecarData<'snapshot> {
260 fn from_slice(slice: &'snapshot [u8]) -> Self {
261 bincode::deserialize(slice).expect("Failed to deserialize snapshot data")
262 }
263
264 fn into_bytes(self) -> Box<[u8]> {
265 bincode::serialize(&self).unwrap().into_boxed_slice()
266 }
267}
268
269pub(crate) fn load_snapshotted_data_from_snapshot<'snapshot>(
272 scope: &mut v8::HandleScope<()>,
273 context: v8::Local<v8::Context>,
274 raw_data: SerializableSnapshotSidecarData<'snapshot>,
275) -> (SnapshottedData<'snapshot>, SnapshotLoadDataStore) {
276 let scope = &mut v8::ContextScope::new(scope, context);
277 let mut data = SnapshotLoadDataStore::default();
278 for i in 0..raw_data.data_count {
279 let item = scope
280 .get_context_data_from_snapshot_once::<v8::Data>(i as usize)
281 .unwrap();
282 let item = v8::Global::new(scope, item);
283 data.data.push(Some(item));
284 }
285
286 (raw_data.snapshot_data, data)
287}
288
289pub(crate) fn store_snapshotted_data_for_snapshot<'snapshot>(
292 scope: &mut v8::HandleScope,
293 context: v8::Global<v8::Context>,
294 snapshotted_data: SnapshottedData<'snapshot>,
295 data_store: SnapshotStoreDataStore,
296) -> SerializableSnapshotSidecarData<'snapshot> {
297 let context = v8::Local::new(scope, context);
298 let raw_snapshot_data = SerializableSnapshotSidecarData {
299 data_count: data_store.data.len() as _,
300 snapshot_data: snapshotted_data,
301 };
302
303 for data in data_store.data {
304 let data = v8::Local::new(scope, data);
305 scope.add_context_data(context, data);
306 }
307
308 raw_snapshot_data
309}
310
311pub(crate) fn create_snapshot_creator(
313 external_refs: &'static v8::ExternalReferences,
314 maybe_startup_snapshot: Option<V8Snapshot>,
315) -> v8::OwnedIsolate {
316 if let Some(snapshot) = maybe_startup_snapshot {
317 v8::Isolate::snapshot_creator_from_existing_snapshot(
318 snapshot.0,
319 Some(external_refs),
320 )
321 } else {
322 v8::Isolate::snapshot_creator(Some(external_refs))
323 }
324}