mirror of https://github.com/boa-dev/boa.git
Haled Odat
1 year ago
17 changed files with 619 additions and 4 deletions
@ -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<JsValue> { |
||||||
|
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() |
||||||
|
} |
@ -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<u8>, |
||||||
|
} |
||||||
|
|
||||||
|
/// TODO: doc
|
||||||
|
pub struct SnapshotSerializer { |
||||||
|
bytes: Vec<u8>, |
||||||
|
objects: IndexSet<JsObject>, |
||||||
|
strings: IndexMap<usize, JsString>, |
||||||
|
symbols: IndexMap<u64, JsSymbol>, |
||||||
|
bigints: IndexSet<JsBigInt>, |
||||||
|
external_references: IndexSet<usize>, |
||||||
|
} |
||||||
|
|
||||||
|
/// 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
|
||||||
|
<Self as Debug>::fmt(self, f) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl std::error::Error for SnapshotError {} |
||||||
|
|
||||||
|
impl From<std::io::Error> 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<T: Serialize> Serialize for Vec<T> { |
||||||
|
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(()) |
||||||
|
} |
||||||
|
} |
@ -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. |
Loading…
Reference in new issue