Browse Source

Implement basic deaserializer

feature/snapshot
Haled Odat 1 year ago
parent
commit
16e7d616f7
  1. 42
      boa_cli/src/debug/snapshot.rs
  2. 75
      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::{ use boa_engine::{
object::ObjectInitializer, snapshot::SnapshotSerializer, Context, JsNativeError, JsObject, object::ObjectInitializer,
JsResult, JsValue, NativeFunction, snapshot::{Snapshot, SnapshotSerializer},
Context, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
}; };
const SNAPSHOT_PATH: &str = "./snapshot.bin"; 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> { fn create(_: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
let Ok(mut file) = OpenOptions::new().write(true).create(true).open(SNAPSHOT_PATH) else { 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()); 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(); file.flush().unwrap();
Ok(JsValue::undefined()) 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 { pub(super) fn create_object(context: &mut Context<'_>) -> JsObject {
ObjectInitializer::new(context) ObjectInitializer::new(context)
.function(NativeFunction::from_fn_ptr(create), "create", 0) .function(NativeFunction::from_fn_ptr(create), "create", 0)
.function(NativeFunction::from_fn_ptr(load), "load", 1)
.build() .build()
} }

75
boa_cli/src/main.rs

@ -72,6 +72,7 @@ use boa_engine::{
optimizer::OptimizerOptions, optimizer::OptimizerOptions,
property::Attribute, property::Attribute,
script::Script, script::Script,
snapshot::Snapshot,
vm::flowgraph::{Direction, Graph}, vm::flowgraph::{Direction, Graph},
Context, JsError, JsNativeError, JsResult, Source, Context, JsError, JsNativeError, JsResult, Source,
}; };
@ -81,7 +82,13 @@ use colored::Colorize;
use debug::init_boa_debug_object; use debug::init_boa_debug_object;
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
use std::{ 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, println,
}; };
@ -168,6 +175,9 @@ struct Opt {
/// Root path from where the module resolver will try to load the modules. /// 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")] #[arg(long, short = 'r', default_value_os_t = PathBuf::from("."), requires = "mod")]
root: PathBuf, root: PathBuf,
#[arg(long)]
snapshot: Option<PathBuf>,
} }
impl Opt { impl Opt {
@ -363,37 +373,60 @@ fn evaluate_files(
Ok(()) 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> { fn main() -> Result<(), io::Error> {
let args = Opt::parse(); let args = Opt::parse();
let queue: &dyn JobQueue = &Jobs::default();
let loader = &SimpleModuleLoader::new(&args.root) let loader = &SimpleModuleLoader::new(&args.root)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; .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 dyn_loader: &dyn ModuleLoader = loader;
let mut context = ContextBuilder::new()
.job_queue(queue)
.module_loader(dyn_loader)
.build()
.expect("cannot fail with default global object");
// Strict mode let mut context = if let Some(path) = &args.snapshot {
context.strict(args.strict); let bytes = get_file_as_byte_vec(&path);
// Add `console`. let snapshot = Snapshot::new(bytes);
add_runtime(&mut context);
// Trace Output let deser_context = snapshot.deserialize().unwrap();
context.set_trace(args.trace);
if args.debug_object { deser_context
init_boa_debug_object(&mut context); } else {
} let mut context = ContextBuilder::new()
.job_queue(queue)
.module_loader(dyn_loader)
.build()
.expect("cannot fail with default global object");
// Strict mode
context.strict(args.strict);
// Add `console`.
add_runtime(&mut context);
// Trace Output
context.set_trace(args.trace);
// Configure optimizer options if args.debug_object {
let mut optimizer_options = OptimizerOptions::empty(); init_boa_debug_object(&mut context);
optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics); }
optimizer_options.set(OptimizerOptions::OPTIMIZE_ALL, args.optimize);
context.set_optimizer_options(optimizer_options); // Configure optimizer options
let mut optimizer_options = OptimizerOptions::empty();
optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics);
optimizer_options.set(OptimizerOptions::OPTIMIZE_ALL, args.optimize);
context.set_optimizer_options(optimizer_options);
context
};
if args.files.is_empty() { if args.files.is_empty() {
let config = Config::builder() let config = Config::builder()

13
boa_engine/src/context/mod.rs

@ -996,3 +996,16 @@ impl crate::snapshot::Serialize for Context<'_> {
Ok(()) 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 indexmap::{IndexMap, IndexSet};
use std::fmt::{Debug, Display}; 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 /// TODO: doc
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Header { pub struct Header {
@ -15,12 +27,6 @@ pub struct Header {
// checksum: u64, // checksum: u64,
} }
/// TODO: doc
pub trait Serialize {
/// Serialize type
fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError>;
}
impl Serialize for Header { impl Serialize for Header {
fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> {
s.write_bytes(&self.signature)?; 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 { impl Serialize for JsObject {
fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> { fn serialize(&self, s: &mut SnapshotSerializer) -> Result<(), SnapshotError> {
let value = s.objects.insert_full(self.clone()).0; let value = s.objects.insert_full(self.clone()).0;
@ -40,8 +57,149 @@ impl Serialize for JsObject {
/// TODO: doc /// TODO: doc
pub struct Snapshot { pub struct Snapshot {
header: Header,
bytes: Vec<u8>, 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 /// TODO: doc
@ -91,18 +249,12 @@ impl SnapshotSerializer {
} }
} }
/// TODO: doc
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Serialize the given [`Context`]. /// 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. // Remove any garbage objects before serialization.
boa_gc::force_collect(); boa_gc::force_collect();
// boa_gc::walk_gc_alloc_pointers(|address| { // boa_gc::walk_gc_alloc_pointers(|address| {
// }); // });
let header = Header { let header = Header {
@ -110,8 +262,8 @@ impl SnapshotSerializer {
version: 42, version: 42,
}; };
header.serialize(self)?; header.serialize(&mut self)?;
context.serialize(self)?; context.serialize(&mut self)?;
for i in 0..self.objects.len() { for i in 0..self.objects.len() {
let object = self let object = self
@ -119,7 +271,7 @@ impl SnapshotSerializer {
.get_index(i) .get_index(i)
.expect("There should be an object") .expect("There should be an object")
.clone(); .clone();
object.inner().serialize(self)?; object.inner().serialize(&mut self)?;
} }
for i in 0..self.symbols.len() { for i in 0..self.symbols.len() {
@ -132,7 +284,7 @@ impl SnapshotSerializer {
self.write_u64(hash)?; self.write_u64(hash)?;
if let Some(desc) = symbol.description() { if let Some(desc) = symbol.description() {
self.write_bool(true)?; self.write_bool(true)?;
desc.serialize(self)?; desc.serialize(&mut self)?;
} else { } else {
self.write_bool(false)?; self.write_bool(false)?;
} }
@ -146,7 +298,7 @@ impl SnapshotSerializer {
.1 .1
.clone(); .clone();
// string. // string.
string.serialize(self)?; string.serialize(&mut self)?;
self.write_bool(string.is_static())?; self.write_bool(string.is_static())?;
self.write_usize(string.len())?; self.write_usize(string.len())?;
@ -155,7 +307,10 @@ impl SnapshotSerializer {
} }
} }
Ok(()) Ok(Snapshot {
bytes: self.bytes,
external_references: self.external_references,
})
} }
/// TODO: doc /// 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 ## 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 If following the `JsValue` is an `JsString`, `JsSymbol`, `JsBigInt`, `JsObject` then the
following value will be an index into the appropriate tables. following value will be an index into the appropriate tables.

Loading…
Cancel
Save