Browse Source

Implement basic deaserializer

feature/snapshot
Haled Odat 1 year ago
parent
commit
16e7d616f7
  1. 42
      boa_cli/src/debug/snapshot.rs
  2. 37
      boa_cli/src/main.rs
  3. 13
      boa_engine/src/context/mod.rs
  4. 195
      boa_engine/src/snapshot/mod.rs
  5. 2
      docs/snapshot.md

42
boa_cli/src/debug/snapshot.rs

@ -1,29 +1,59 @@
use std::{fs::OpenOptions, io::Write};
use std::{
fs::{File, OpenOptions},
io::{Read, Write},
};
use boa_engine::{
object::ObjectInitializer, snapshot::SnapshotSerializer, Context, JsNativeError, JsObject,
JsResult, JsValue, NativeFunction,
object::ObjectInitializer,
snapshot::{Snapshot, SnapshotSerializer},
Context, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
};
const SNAPSHOT_PATH: &str = "./snapshot.bin";
fn get_file_as_byte_vec(filename: &str) -> Vec<u8> {
let mut f = File::open(&filename).expect("no file found");
let metadata = std::fs::metadata(&filename).expect("unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
f.read(&mut buffer).expect("buffer overflow");
buffer
}
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();
let serializer = SnapshotSerializer::new();
serializer.serialize(context).unwrap();
let snapshot = serializer.serialize(context).unwrap();
file.write_all(serializer.bytes()).unwrap();
file.write_all(snapshot.bytes()).unwrap();
file.flush().unwrap();
Ok(JsValue::undefined())
}
fn load(_: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
// let Ok(mut file) = OpenOptions::new().read(true).open(SNAPSHOT_PATH) else {
// return Err(JsNativeError::error().with_message("could not open snapshot.bin file").into());
// };
let bytes = get_file_as_byte_vec(SNAPSHOT_PATH);
let snapshot = Snapshot::new(bytes);
let deser_context = snapshot.deserialize().unwrap();
*context = deser_context;
Ok(JsValue::undefined())
}
pub(super) fn create_object(context: &mut Context<'_>) -> JsObject {
ObjectInitializer::new(context)
.function(NativeFunction::from_fn_ptr(create), "create", 0)
.function(NativeFunction::from_fn_ptr(load), "load", 1)
.build()
}

37
boa_cli/src/main.rs

@ -72,6 +72,7 @@ use boa_engine::{
optimizer::OptimizerOptions,
property::Attribute,
script::Script,
snapshot::Snapshot,
vm::flowgraph::{Direction, Graph},
Context, JsError, JsNativeError, JsResult, Source,
};
@ -81,7 +82,13 @@ use colored::Colorize;
use debug::init_boa_debug_object;
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
use std::{
cell::RefCell, collections::VecDeque, eprintln, fs::read, fs::OpenOptions, io, path::PathBuf,
cell::RefCell,
collections::VecDeque,
eprintln,
fs::OpenOptions,
fs::{read, File},
io::{self, Read},
path::{Path, PathBuf},
println,
};
@ -168,6 +175,9 @@ struct Opt {
/// Root path from where the module resolver will try to load the modules.
#[arg(long, short = 'r', default_value_os_t = PathBuf::from("."), requires = "mod")]
root: PathBuf,
#[arg(long)]
snapshot: Option<PathBuf>,
}
impl Opt {
@ -363,13 +373,33 @@ fn evaluate_files(
Ok(())
}
fn get_file_as_byte_vec(filename: &Path) -> Vec<u8> {
let mut f = File::open(&filename).expect("no file found");
let metadata = std::fs::metadata(&filename).expect("unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
f.read(&mut buffer).expect("buffer overflow");
buffer
}
fn main() -> Result<(), io::Error> {
let args = Opt::parse();
let queue: &dyn JobQueue = &Jobs::default();
let loader = &SimpleModuleLoader::new(&args.root)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let queue: &dyn JobQueue = &Jobs::default();
let dyn_loader: &dyn ModuleLoader = loader;
let mut context = if let Some(path) = &args.snapshot {
let bytes = get_file_as_byte_vec(&path);
let snapshot = Snapshot::new(bytes);
let deser_context = snapshot.deserialize().unwrap();
deser_context
} else {
let mut context = ContextBuilder::new()
.job_queue(queue)
.module_loader(dyn_loader)
@ -395,6 +425,9 @@ fn main() -> Result<(), io::Error> {
optimizer_options.set(OptimizerOptions::OPTIMIZE_ALL, args.optimize);
context.set_optimizer_options(optimizer_options);
context
};
if args.files.is_empty() {
let config = Config::builder()
.keyseq_timeout(1)

13
boa_engine/src/context/mod.rs

@ -996,3 +996,16 @@ impl crate::snapshot::Serialize for Context<'_> {
Ok(())
}
}
impl crate::snapshot::Deserialize for Context<'_> {
fn deserialize(
d: &mut crate::snapshot::SnapshotDeserializer<'_>,
) -> Result<Self, crate::snapshot::SnapshotError> {
let strict = d.read_bool()?;
let mut context = Context::default();
context.strict(strict);
Ok(context)
}
}

195
boa_engine/src/snapshot/mod.rs

@ -7,6 +7,18 @@ use crate::{Context, JsBigInt, JsObject, JsString, JsSymbol};
use indexmap::{IndexMap, IndexSet};
use std::fmt::{Debug, Display};
/// TODO: doc
pub trait Serialize {
/// Serialize type
fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError>;
}
/// TODO: doc
pub trait Deserialize: Sized {
/// TODO: doc
fn deserialize(d: &mut SnapshotDeserializer<'_>) -> Result<Self, SnapshotError>;
}
/// TODO: doc
#[derive(Debug, Clone, Copy)]
pub struct Header {
@ -15,12 +27,6 @@ pub struct Header {
// 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)?;
@ -29,6 +35,17 @@ impl Serialize for Header {
}
}
impl Deserialize for Header {
fn deserialize(d: &mut SnapshotDeserializer<'_>) -> Result<Self, SnapshotError> {
let signature = d.read_bytes(4)?;
let signature = [signature[0], signature[1], signature[2], signature[3]];
let version = d.read_u32()?;
Ok(Self { signature, version })
}
}
impl Serialize for JsObject {
fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> {
let value = s.objects.insert_full(self.clone()).0;
@ -40,8 +57,149 @@ impl Serialize for JsObject {
/// TODO: doc
pub struct Snapshot {
header: Header,
bytes: Vec<u8>,
external_references: IndexSet<usize>,
}
impl Snapshot {
/// TODO: doc
pub fn new(bytes: Vec<u8>) -> Self {
Self {
bytes,
external_references: IndexSet::default(),
}
}
/// TODO: doc
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// TODO: doc
pub fn deserialize<'a>(&self) -> Result<Context<'a>, SnapshotError> {
let mut deserializer = SnapshotDeserializer {
index: 0,
bytes: &self.bytes,
external_references: &self.external_references,
};
let header = Header::deserialize(&mut deserializer)?;
// TODO: Do error handling and snapshot integrity checks.
assert_eq!(&header.signature, b".boa");
assert_eq!(header.version, 42);
let context = Context::deserialize(&mut deserializer)?;
// Assert that all bytes are consumed.
// assert_eq!(deserializer.index, deserializer.bytes.len());
Ok(context)
}
}
/// TODO: doc
pub struct SnapshotDeserializer<'snapshot> {
bytes: &'snapshot [u8],
index: usize,
external_references: &'snapshot IndexSet<usize>,
}
impl SnapshotDeserializer<'_> {
/// TODO: doc
pub fn read_bool(&mut self) -> Result<bool, SnapshotError> {
let byte = self.read_u8()?;
assert!(byte == 0 || byte == 1);
Ok(byte == 1)
}
/// TODO: doc
pub fn read_u8(&mut self) -> Result<u8, SnapshotError> {
let byte = self.bytes[self.index];
self.index += 1;
Ok(byte)
}
/// TODO: doc
pub fn read_i8(&mut self) -> Result<i8, SnapshotError> {
let byte = self.bytes[self.index];
self.index += 1;
Ok(byte as i8)
}
/// TODO: doc
pub fn read_u16(&mut self) -> Result<u16, SnapshotError> {
let bytes = self.read_bytes(std::mem::size_of::<u16>())?;
let value = u16::from_le_bytes([bytes[0], bytes[1]]);
Ok(value)
}
/// TODO: doc
pub fn read_i16(&mut self) -> Result<i16, SnapshotError> {
let value = self.read_u16()?;
Ok(value as i16)
}
/// TODO: doc
pub fn read_u32(&mut self) -> Result<u32, SnapshotError> {
let bytes = self.read_bytes(4)?;
let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
Ok(value)
}
/// TODO: doc
pub fn read_i32(&mut self) -> Result<i32, SnapshotError> {
let value = self.read_u32()?;
Ok(value as i32)
}
/// TODO: doc
pub fn read_f32(&mut self) -> Result<f32, SnapshotError> {
let value = self.read_u32()?;
Ok(f32::from_bits(value))
}
/// TODO: doc
pub fn read_f64(&mut self) -> Result<f64, SnapshotError> {
let value = self.read_u64()?;
Ok(f64::from_bits(value))
}
/// TODO: doc
pub fn read_u64(&mut self) -> Result<u64, SnapshotError> {
let bytes = self.read_bytes(std::mem::size_of::<u64>())?;
let value = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
Ok(value)
}
/// TODO: doc
pub fn read_i64(&mut self) -> Result<i64, SnapshotError> {
let value = self.read_u64()?;
Ok(value as i64)
}
/// TODO: doc
pub fn read_usize(&mut self) -> Result<usize, SnapshotError> {
let value = self.read_u64()?;
// TODO: handle error.
Ok(usize::try_from(value).unwrap())
}
/// TODO: doc
pub fn read_isize(&mut self) -> Result<isize, SnapshotError> {
let value = self.read_usize()?;
Ok(value as isize)
}
/// TODO: doc
pub fn read_string(&mut self) -> Result<&str, SnapshotError> {
let len = self.read_usize()?;
let bytes = self.read_bytes(len)?;
// TODO: handle error
Ok(std::str::from_utf8(bytes).unwrap())
}
/// TODO: doc
pub fn read_bytes(&mut self, count: usize) -> Result<&[u8], SnapshotError> {
let index = self.index;
self.index += count;
// TODO: use .get() so we can handle the error.
let bytes = &self.bytes[index..(index + count)];
Ok(bytes)
}
}
/// TODO: doc
@ -91,18 +249,12 @@ impl SnapshotSerializer {
}
}
/// TODO: doc
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Serialize the given [`Context`].
pub fn serialize(&mut self, context: &mut Context<'_>) -> Result<(), SnapshotError> {
pub fn serialize(mut self, context: &mut Context<'_>) -> Result<Snapshot, SnapshotError> {
// Remove any garbage objects before serialization.
boa_gc::force_collect();
// boa_gc::walk_gc_alloc_pointers(|address| {
// });
let header = Header {
@ -110,8 +262,8 @@ impl SnapshotSerializer {
version: 42,
};
header.serialize(self)?;
context.serialize(self)?;
header.serialize(&mut self)?;
context.serialize(&mut self)?;
for i in 0..self.objects.len() {
let object = self
@ -119,7 +271,7 @@ impl SnapshotSerializer {
.get_index(i)
.expect("There should be an object")
.clone();
object.inner().serialize(self)?;
object.inner().serialize(&mut self)?;
}
for i in 0..self.symbols.len() {
@ -132,7 +284,7 @@ impl SnapshotSerializer {
self.write_u64(hash)?;
if let Some(desc) = symbol.description() {
self.write_bool(true)?;
desc.serialize(self)?;
desc.serialize(&mut self)?;
} else {
self.write_bool(false)?;
}
@ -146,7 +298,7 @@ impl SnapshotSerializer {
.1
.clone();
// string.
string.serialize(self)?;
string.serialize(&mut self)?;
self.write_bool(string.is_static())?;
self.write_usize(string.len())?;
@ -155,7 +307,10 @@ impl SnapshotSerializer {
}
}
Ok(())
Ok(Snapshot {
bytes: self.bytes,
external_references: self.external_references,
})
}
/// TODO: doc

2
docs/snapshot.md

@ -61,6 +61,6 @@ If it's a static string then it's elements comprise the index into the `STATIC_S
## JsValue Encoding
type `: u8` (JsValue discriminant, Boolean, Null, etc) followed by the value if it exits.
type `: u8` (JsValue discriminant, Boolean, Null, etc) followed by the value if it applied for the type (`Null` and `Undefined` does not have a value part).
If following the `JsValue` is an `JsString`, `JsSymbol`, `JsBigInt`, `JsObject` then the
following value will be an index into the appropriate tables.

Loading…
Cancel
Save