diff --git a/.gitignore b/.gitignore index 98608e83527..bcf3d649b82 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ chrome_profiler.json # e2e test playwright-report test-results + +# Binary snapshot file +snapshot.bin diff --git a/boa_cli/src/debug/mod.rs b/boa_cli/src/debug/mod.rs index ca1c22d0773..0c363a45d45 100644 --- a/boa_cli/src/debug/mod.rs +++ b/boa_cli/src/debug/mod.rs @@ -10,6 +10,7 @@ mod object; mod optimizer; mod realm; mod shape; +mod snapshot; fn create_boa_object(context: &mut Context<'_>) -> JsObject { let function_module = function::create_object(context); @@ -19,6 +20,7 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject { let gc_module = gc::create_object(context); let realm_module = realm::create_object(context); let limits_module = limits::create_object(context); + let snapshot_module = snapshot::create_object(context); ObjectInitializer::new(context) .property( @@ -56,6 +58,11 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject { limits_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) + .property( + "snapshot", + snapshot_module, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .build() } diff --git a/boa_cli/src/debug/snapshot.rs b/boa_cli/src/debug/snapshot.rs new file mode 100644 index 00000000000..8883ea718b5 --- /dev/null +++ b/boa_cli/src/debug/snapshot.rs @@ -0,0 +1,29 @@ +use std::{fs::OpenOptions, io::Write}; + +use boa_engine::{ + object::ObjectInitializer, snapshot::SnapshotSerializer, Context, JsNativeError, JsObject, + JsResult, JsValue, NativeFunction, +}; + +const SNAPSHOT_PATH: &str = "./snapshot.bin"; + +fn create(_: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { + let Ok(mut file) = OpenOptions::new().write(true).create(true).open(SNAPSHOT_PATH) else { + return Err(JsNativeError::error().with_message("could not create snapshot.bin file").into()); + }; + + let mut serializer = SnapshotSerializer::new(); + + serializer.serialize(context).unwrap(); + + file.write_all(serializer.bytes()).unwrap(); + file.flush().unwrap(); + + Ok(JsValue::undefined()) +} + +pub(super) fn create_object(context: &mut Context<'_>) -> JsObject { + ObjectInitializer::new(context) + .function(NativeFunction::from_fn_ptr(create), "create", 0) + .build() +} diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 92d3c825d5c..0e75d2e5ef0 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -26,6 +26,16 @@ pub struct Intrinsics { pub(super) templates: ObjectTemplates, } +impl crate::snapshot::Serialize for Intrinsics { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.constructors.serialize(s)?; + Ok(()) + } +} + impl Intrinsics { pub(crate) fn new(root_shape: &RootShape) -> Self { let constructors = StandardConstructors::default(); @@ -62,6 +72,17 @@ pub struct StandardConstructor { prototype: JsObject, } +impl crate::snapshot::Serialize for StandardConstructor { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.constructor.serialize(s)?; + self.prototype.serialize(s)?; + Ok(()) + } +} + impl Default for StandardConstructor { fn default() -> Self { Self { @@ -153,6 +174,67 @@ pub struct StandardConstructors { segmenter: StandardConstructor, } +impl crate::snapshot::Serialize for StandardConstructors { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.object.serialize(s)?; + self.proxy.serialize(s)?; + self.date.serialize(s)?; + self.function.serialize(s)?; + self.async_function.serialize(s)?; + self.generator_function.serialize(s)?; + self.async_generator_function.serialize(s)?; + self.array.serialize(s)?; + self.bigint.serialize(s)?; + self.number.serialize(s)?; + self.boolean.serialize(s)?; + self.string.serialize(s)?; + self.regexp.serialize(s)?; + self.symbol.serialize(s)?; + self.error.serialize(s)?; + self.type_error.serialize(s)?; + self.reference_error.serialize(s)?; + self.range_error.serialize(s)?; + self.syntax_error.serialize(s)?; + self.eval_error.serialize(s)?; + self.uri_error.serialize(s)?; + self.aggregate_error.serialize(s)?; + self.map.serialize(s)?; + self.set.serialize(s)?; + self.typed_array.serialize(s)?; + self.typed_int8_array.serialize(s)?; + self.typed_uint8_array.serialize(s)?; + self.typed_uint8clamped_array.serialize(s)?; + self.typed_int16_array.serialize(s)?; + self.typed_uint16_array.serialize(s)?; + self.typed_int32_array.serialize(s)?; + self.typed_uint32_array.serialize(s)?; + self.typed_bigint64_array.serialize(s)?; + self.typed_biguint64_array.serialize(s)?; + self.typed_float32_array.serialize(s)?; + self.typed_float64_array.serialize(s)?; + self.array_buffer.serialize(s)?; + self.data_view.serialize(s)?; + self.date_time_format.serialize(s)?; + self.promise.serialize(s)?; + self.weak_ref.serialize(s)?; + self.weak_map.serialize(s)?; + self.weak_set.serialize(s)?; + #[cfg(feature = "intl")] + self.collator.serialize(s)?; + #[cfg(feature = "intl")] + self.list_format.serialize(s)?; + #[cfg(feature = "intl")] + self.locale.serialize(s)?; + #[cfg(feature = "intl")] + self.segmenter.serialize(s)?; + + Ok(()) + } +} + impl Default for StandardConstructors { fn default() -> Self { Self { diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 5c4ea4fc760..c1c0b4be66f 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -985,3 +985,14 @@ where } } } + +impl crate::snapshot::Serialize for Context<'_> { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + s.write_bool(self.strict)?; + self.realm.serialize(s)?; + Ok(()) + } +} diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 1080b1708de..03f10debc28 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -147,6 +147,8 @@ mod tests; pub mod value; pub mod vm; +pub mod snapshot; + /// A convenience module that re-exports the most commonly-used Boa APIs pub mod prelude { pub use crate::{ diff --git a/boa_engine/src/object/builtins/jsfunction.rs b/boa_engine/src/object/builtins/jsfunction.rs index 522242acc69..912427d53dd 100644 --- a/boa_engine/src/object/builtins/jsfunction.rs +++ b/boa_engine/src/object/builtins/jsfunction.rs @@ -16,6 +16,16 @@ pub struct JsFunction { inner: JsObject, } +impl crate::snapshot::Serialize for JsFunction { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.inner.serialize(s)?; + Ok(()) + } +} + impl JsFunction { /// Creates a new `JsFunction` from an object, without checking if the object is callable. pub(crate) fn from_object_unchecked(object: JsObject) -> Self { diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 2b583a4e758..483ca5ee92c 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -51,6 +51,18 @@ pub struct VTableObject { vtable: &'static InternalObjectMethods, } +impl crate::snapshot::Serialize for VTableObject { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> StdResult<(), crate::snapshot::SnapshotError> { + // TODO: add internal methods to references + + self.object.borrow().serialize(s)?; + Ok(()) + } +} + impl Default for JsObject { fn default() -> Self { let data = ObjectData::ordinary(); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 072693f79a8..8863003b226 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -142,6 +142,17 @@ pub struct Object { private_elements: ThinVec<(PrivateName, PrivateElement)>, } +impl crate::snapshot::Serialize for Object { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + s.write_bool(self.extensible)?; + self.properties.serialize(s)?; + Ok(()) + } +} + impl Default for Object { fn default() -> Self { Self { diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index 9a06080a130..ae08a0a73a6 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -226,6 +226,16 @@ pub struct PropertyMap { pub(crate) storage: ObjectStorage, } +impl crate::snapshot::Serialize for PropertyMap { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.storage.serialize(s)?; + Ok(()) + } +} + impl PropertyMap { /// Create a new [`PropertyMap`]. #[must_use] diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index db396df201f..91932fd3bf1 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -25,6 +25,16 @@ pub struct Realm { inner: Gc, } +impl crate::snapshot::Serialize for Realm { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.inner.serialize(s)?; + Ok(()) + } +} + impl Eq for Realm {} impl PartialEq for Realm { @@ -54,6 +64,16 @@ struct Inner { loaded_modules: GcRefCell>, } +impl crate::snapshot::Serialize for Inner { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.intrinsics.serialize(s)?; + Ok(()) + } +} + impl Realm { /// Create a new Realm. #[inline] diff --git a/boa_engine/src/snapshot/mod.rs b/boa_engine/src/snapshot/mod.rs new file mode 100644 index 00000000000..417ebc59d1b --- /dev/null +++ b/boa_engine/src/snapshot/mod.rs @@ -0,0 +1,273 @@ +//! TODO: doc + +#![allow(missing_debug_implementations)] +#![allow(dead_code)] + +use crate::{Context, JsBigInt, JsObject, JsString, JsSymbol}; +use indexmap::{IndexMap, IndexSet}; +use std::fmt::{Debug, Display}; + +/// TODO: doc +#[derive(Debug, Clone, Copy)] +pub struct Header { + signature: [u8; 4], + version: u32, + // checksum: u64, +} + +/// TODO: doc +pub trait Serialize { + /// Serialize type + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError>; +} + +impl Serialize for Header { + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { + s.write_bytes(&self.signature)?; + s.write_u32(self.version)?; + Ok(()) + } +} + +impl Serialize for JsObject { + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { + let value = s.objects.insert_full(self.clone()).0; + + s.write_u32(value as u32)?; + Ok(()) + } +} + +/// TODO: doc +pub struct Snapshot { + header: Header, + bytes: Vec, +} + +/// TODO: doc +pub struct SnapshotSerializer { + bytes: Vec, + objects: IndexSet, + strings: IndexMap, + symbols: IndexMap, + bigints: IndexSet, + external_references: IndexSet, +} + +/// TODO: doc +#[derive(Debug)] +pub enum SnapshotError { + /// Input/output error. + /// + /// See: [`std::io::Error`]. + Io(std::io::Error), +} + +impl Display for SnapshotError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // FIXME: implement better formatting + ::fmt(self, f) + } +} + +impl std::error::Error for SnapshotError {} + +impl From for SnapshotError { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl SnapshotSerializer { + /// TODO: doc + pub fn new() -> Self { + Self { + bytes: Vec::new(), + objects: IndexSet::default(), + strings: IndexMap::default(), + symbols: IndexMap::default(), + bigints: IndexSet::default(), + external_references: IndexSet::default(), + } + } + + /// TODO: doc + pub fn bytes(&self) -> &[u8] { + &self.bytes + } + + /// Serialize the given [`Context`]. + pub fn serialize(&mut self, context: &mut Context<'_>) -> Result<(), SnapshotError> { + // Remove any garbage objects before serialization. + boa_gc::force_collect(); + + // boa_gc::walk_gc_alloc_pointers(|address| { + + // }); + + let header = Header { + signature: *b".boa", + version: 42, + }; + + header.serialize(self)?; + context.serialize(self)?; + + for i in 0..self.objects.len() { + let object = self + .objects + .get_index(i) + .expect("There should be an object") + .clone(); + object.inner().serialize(self)?; + } + + for i in 0..self.symbols.len() { + let (hash, symbol) = self + .symbols + .get_index(i) + .map(|(hash, symbol)| (*hash, symbol.clone())) + .expect("There should be an object"); + + self.write_u64(hash)?; + if let Some(desc) = symbol.description() { + self.write_bool(true)?; + desc.serialize(self)?; + } else { + self.write_bool(false)?; + } + } + + for i in 0..self.strings.len() { + let string = self + .strings + .get_index(i) + .expect("There should be an string") + .1 + .clone(); + // string. + string.serialize(self)?; + + self.write_bool(string.is_static())?; + self.write_usize(string.len())?; + for elem in string.as_slice() { + self.write_u16(*elem)?; + } + } + + Ok(()) + } + + /// TODO: doc + pub fn write_bool(&mut self, v: bool) -> Result<(), SnapshotError> { + Ok(self.write_u8(if v { 1 } else { 0 })?) + } + /// TODO: doc + pub fn write_u8(&mut self, v: u8) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&[v])?) + } + /// TODO: doc + pub fn write_i8(&mut self, v: i8) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_u16(&mut self, v: u16) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i16(&mut self, v: i16) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_u32(&mut self, v: u32) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i32(&mut self, v: i32) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_f32(&mut self, v: f32) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_f64(&mut self, v: f64) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_u64(&mut self, v: u64) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i64(&mut self, v: i64) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_u128(&mut self, v: u128) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i128(&mut self, v: i128) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_usize(&mut self, v: usize) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&(v as u64).to_le_bytes())?) + } + /// TODO: doc + pub fn write_isize(&mut self, v: isize) -> Result<(), SnapshotError> { + Ok(self.write_bytes(&(v as i64).to_le_bytes())?) + } + /// TODO: doc + pub fn write_string(&mut self, v: &str) -> Result<(), SnapshotError> { + let asb = v.as_bytes(); + self.write_usize(asb.len())?; + self.bytes.extend_from_slice(asb); + Ok(()) + } + /// TODO: doc + pub fn write_bytes(&mut self, v: &[u8]) -> Result<(), SnapshotError> { + self.bytes.extend_from_slice(v); + Ok(()) + } +} + +impl Serialize for Vec { + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { + s.write_usize(self.len())?; + for element in self { + element.serialize(s)?; + } + Ok(()) + } +} + +impl Serialize for JsString { + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { + let index = s.strings.insert_full(self.ptr.addr(), self.clone()).0; + + s.write_u32(index as u32)?; + Ok(()) + } +} + +impl Serialize for JsSymbol { + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { + let index = s.symbols.insert_full(self.hash(), self.clone()).0; + + s.write_u32(index as u32)?; + Ok(()) + } +} + +impl Serialize for JsBigInt { + fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { + let index = s.bigints.insert_full(self.clone()).0; + s.write_u32(index as u32)?; + Ok(()) + } +} diff --git a/boa_engine/src/string/mod.rs b/boa_engine/src/string/mod.rs index 41225895715..26b0b4fc8ec 100644 --- a/boa_engine/src/string/mod.rs +++ b/boa_engine/src/string/mod.rs @@ -176,7 +176,7 @@ impl CodePoint { /// The raw representation of a [`JsString`] in the heap. #[repr(C)] -struct RawJsString { +pub(crate) struct RawJsString { /// The UTF-16 length. len: usize, @@ -206,7 +206,7 @@ const DATA_OFFSET: usize = std::mem::size_of::(); /// \[u16\]'s methods. #[derive(Finalize)] pub struct JsString { - ptr: Tagged, + pub(crate) ptr: Tagged, } // JsString should always be pointer sized. @@ -616,6 +616,10 @@ impl JsString { ptr: Tagged::from_non_null(ptr), } } + + pub(crate) fn is_static(&self) -> bool { + self.ptr.is_tagged() + } } impl AsRef<[u16]> for JsString { diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index 925dd6f9e03..be7c774a419 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -111,14 +111,14 @@ impl WellKnown { /// The inner representation of a JavaScript symbol. #[derive(Debug, Clone)] -struct Inner { +pub(crate) struct Inner { hash: u64, description: Option, } /// This represents a JavaScript symbol primitive. pub struct JsSymbol { - repr: Tagged, + pub(crate) repr: Tagged, } // SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe. diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index f302f466b4a..a23e9b69ecb 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -58,6 +58,7 @@ static TWO_E_63: Lazy = Lazy::new(|| { /// A Javascript value #[derive(Finalize, Debug, Clone)] +#[repr(u8)] pub enum JsValue { /// `null` - A null value, for when a value doesn't exist. Null, @@ -79,6 +80,35 @@ pub enum JsValue { Symbol(JsSymbol), } +impl JsValue { + fn discriminant(&self) -> u8 { + // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union` + // between `repr(C)` structs, each of which has the `u8` discriminant as its first + // field, so we can read the discriminant without offsetting the pointer. + unsafe { *<*const _>::from(self).cast::() } + } +} + +impl crate::snapshot::Serialize for JsValue { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + let discriminant = self.discriminant(); + s.write_u8(discriminant)?; + match self { + JsValue::Null | JsValue::Undefined => Ok(()), + JsValue::Boolean(value) => s.write_bool(*value), + JsValue::String(string) => string.serialize(s), + JsValue::Rational(rational) => s.write_f64(*rational), + JsValue::Integer(integer) => s.write_i32(*integer), + JsValue::BigInt(bigint) => bigint.serialize(s), + JsValue::Object(object) => object.serialize(s), + JsValue::Symbol(symbol) => symbol.serialize(s), + } + } +} + unsafe impl Trace for JsValue { custom_trace! {this, { if let Self::Object(o) = this { diff --git a/boa_gc/src/lib.rs b/boa_gc/src/lib.rs index fb6e9b53fbb..c8e085dec86 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -549,3 +549,48 @@ pub fn has_weak_maps() -> bool { gc.weak_map_start.get().is_some() }) } + +/// Returns `true` is any weak maps are currently allocated. +#[cfg(test)] +#[must_use] +pub fn walk_gc_alloc_pointers(mut f: F) +where + F: FnMut(usize), +{ + BOA_GC.with(|current| { + let gc = current.borrow(); + + let mut weak_map_head = gc.weak_map_start.get(); + while let Some(node) = weak_map_head { + f(node.as_ptr() as *const u8 as usize); + + // SAFETY: + // The `Allocator` must always ensure its start node is a valid, non-null pointer that + // was allocated by `Box::from_raw(Box::new(..))`. + let unmarked_node = unsafe { node.as_ref() }; + weak_map_head = unmarked_node.next().take(); + } + + let mut strong_head = gc.strong_start.get(); + while let Some(node) = strong_head { + f(node.as_ptr() as *const u8 as usize); + + // SAFETY: + // The `Allocator` must always ensure its start node is a valid, non-null pointer that + // was allocated by `Box::from_raw(Box::new(..))`. + let unmarked_node = unsafe { node.as_ref() }; + strong_head = unmarked_node.header.next.take(); + } + + let mut eph_head = gc.weak_start.get(); + while let Some(node) = eph_head { + f(node.as_ptr() as *const u8 as usize); + + // SAFETY: + // The `Allocator` must always ensure its start node is a valid, non-null pointer that + // was allocated by `Box::from_raw(Box::new(..))`. + let unmarked_node = unsafe { node.as_ref() }; + eph_head = unmarked_node.header().next.take(); + } + }); +} diff --git a/docs/snapshot.md b/docs/snapshot.md new file mode 100644 index 00000000000..1d9c1803e25 --- /dev/null +++ b/docs/snapshot.md @@ -0,0 +1,66 @@ +# Snapshot File format + +This docoment describes the binary file format of the boa snapshot files. + +## Header + +The header composes the first part of the snapshot. + +| Field | Description | +| --------------------- | ------------------------------------------------------------------------------------------------------------ | +| signature `: [u8; 4]` | This is used to quickly check if this file is a snapshot file (`.boa`) | +| guid | Guid generated in compile time and backed into the binary, that is used to check if snapshot is compatibile. | +| checksum | Checksum that is used to check that the snapshot is not corrupted. | + +## JsString Table + +After the `Header` the table containing `JsString`s each entry contains + +| static? `: u8` | length: `: usize` | `JsString` elements `: [u16]` | +| -------------- | ----------------- | ----------------------------- | +| 0 | 5 | `'H', 'e', 'l', 'l', 'o'` | +| 1 | - | 3 | +| ... | ... | ... | + +If it's a static string then it's elements comprise the index into the `STATIC_STRING`s. + +## JsSymbol Table + +| `JsSymbol` hash `: u64` | Description (index into `JsString` table) `: usize` | +| ----------------------- | --------------------------------------------------- | +| 200 | 0 | +| ... | ... | + +## JsBigInt Table + +| Length in bytes `: u64` | Content | +| ----------------------- | ------- | +| 32 | ... | + +## Shapes (Hidden classes) Table + +### Unique Shapes + +| `[[prototype]]` `: u32` (index into `JsObject` table) | property count `: u32` | key-value pairs | +| ----------------------------------------------------- | ---------------------- | --------------- | +| | 0 | | +| | ... | | + +### Shared Shapes + +| previous `: u32` | flags | transition | +| ---------------- | ----- | ---------- | +| `MAX` (root) | ... | `x` | + +## JsObject Table + +| length | Content | +| ------ | ------- | +| 200 | ... | +| ... | ... | + +## JsValue Encoding + +type `: u8` (JsValue discriminant, Boolean, Null, etc) followed by the value if it exits. +If following the `JsValue` is an `JsString`, `JsSymbol`, `JsBigInt`, `JsObject` then the +following value will be an index into the appropriate tables.