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..83d22412e77 --- /dev/null +++ b/boa_cli/src/debug/snapshot.rs @@ -0,0 +1,59 @@ +use std::{ + fs::{File, OpenOptions}, + io::{Read, Write}, +}; + +use boa_engine::{ + 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 { + 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 { + 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 serializer = SnapshotSerializer::new(); + + let snapshot = serializer.serialize(context).unwrap(); + + file.write_all(snapshot.bytes()).unwrap(); + file.flush().unwrap(); + + Ok(JsValue::undefined()) +} + +fn load(_: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { + // 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() +} diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index da705e54b11..7954c42ed35 100644 --- a/boa_cli/src/main.rs +++ b/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, } impl Opt { @@ -363,37 +373,60 @@ fn evaluate_files( Ok(()) } +fn get_file_as_byte_vec(filename: &Path) -> Vec { + 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 = ContextBuilder::new() - .job_queue(queue) - .module_loader(dyn_loader) - .build() - .expect("cannot fail with default global object"); - // Strict mode - context.strict(args.strict); + let mut context = if let Some(path) = &args.snapshot { + let bytes = get_file_as_byte_vec(&path); - // Add `console`. - add_runtime(&mut context); + let snapshot = Snapshot::new(bytes); - // Trace Output - context.set_trace(args.trace); + let deser_context = snapshot.deserialize().unwrap(); - if args.debug_object { - init_boa_debug_object(&mut context); - } + deser_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 - 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); + if args.debug_object { + init_boa_debug_object(&mut context); + } + + // 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() { let config = Config::builder() diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 13a31260252..31ebeb43532 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -74,6 +74,29 @@ pub struct IteratorPrototypes { segment: JsObject, } +impl crate::snapshot::Serialize for IteratorPrototypes { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.iterator.serialize(s)?; + self.async_iterator.serialize(s)?; + self.async_from_sync_iterator.serialize(s)?; + self.array.serialize(s)?; + self.set.serialize(s)?; + self.string.serialize(s)?; + self.regexp_string.serialize(s)?; + self.map.serialize(s)?; + self.for_in.serialize(s)?; + #[cfg(feature = "intl")] + { + self.segment.serialize(s)?; + } + + Ok(()) + } +} + impl IteratorPrototypes { /// Returns the `ArrayIteratorPrototype` object. #[inline] diff --git a/boa_engine/src/builtins/uri/mod.rs b/boa_engine/src/builtins/uri/mod.rs index d059fa98468..8323e0f2815 100644 --- a/boa_engine/src/builtins/uri/mod.rs +++ b/boa_engine/src/builtins/uri/mod.rs @@ -47,6 +47,27 @@ pub struct UriFunctions { encode_uri_component: JsFunction, } +impl crate::snapshot::Serialize for UriFunctions { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.decode_uri.serialize(s)?; + self.decode_uri_component.serialize(s)?; + self.encode_uri.serialize(s)?; + self.encode_uri_component.serialize(s)?; + Ok(()) + } +} + +impl crate::snapshot::Deserialize for UriFunctions { + fn deserialize( + _d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> crate::snapshot::SnapshotResult { + todo!() + } +} + impl Default for UriFunctions { fn default() -> Self { Self { diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 92d3c825d5c..2086a76661f 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -26,6 +26,18 @@ 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)?; + self.objects.serialize(s)?; + self.templates.serialize(s)?; + Ok(()) + } +} + impl Intrinsics { pub(crate) fn new(root_shape: &RootShape) -> Self { let constructors = StandardConstructors::default(); @@ -62,6 +74,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 +176,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 { @@ -814,6 +898,40 @@ pub struct IntrinsicObjects { segments_prototype: JsObject, } +impl crate::snapshot::Serialize for IntrinsicObjects { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.reflect.serialize(s)?; + self.math.serialize(s)?; + self.json.serialize(s)?; + self.throw_type_error.serialize(s)?; + self.array_prototype_values.serialize(s)?; + self.iterator_prototypes.serialize(s)?; + self.generator.serialize(s)?; + self.async_generator.serialize(s)?; + self.eval.serialize(s)?; + self.uri_functions.serialize(s)?; + self.is_finite.serialize(s)?; + self.is_nan.serialize(s)?; + self.parse_float.serialize(s)?; + self.parse_int.serialize(s)?; + #[cfg(feature = "annex-b")] + { + self.escape.serialize(s)?; + self.unescape.serialize(s)?; + } + #[cfg(feature = "intl")] + { + self.intl.serialize(s)?; + self.segments_prototype.serialize(s)?; + } + + Ok(()) + } +} + impl Default for IntrinsicObjects { fn default() -> Self { Self { @@ -1003,6 +1121,33 @@ pub(crate) struct ObjectTemplates { namespace: ObjectTemplate, } +impl crate::snapshot::Serialize for ObjectTemplates { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.iterator_result.serialize(s)?; + self.ordinary_object.serialize(s)?; + self.array.serialize(s)?; + self.number.serialize(s)?; + self.string.serialize(s)?; + self.symbol.serialize(s)?; + self.bigint.serialize(s)?; + self.boolean.serialize(s)?; + + self.unmapped_arguments.serialize(s)?; + self.mapped_arguments.serialize(s)?; + self.function_with_prototype.serialize(s)?; + self.function_prototype.serialize(s)?; + self.function.serialize(s)?; + self.async_function.serialize(s)?; + self.function_without_proto.serialize(s)?; + self.function_with_prototype_without_proto.serialize(s)?; + self.namespace.serialize(s)?; + Ok(()) + } +} + impl ObjectTemplates { pub(crate) fn new(root_shape: &RootShape, constructors: &StandardConstructors) -> Self { let root_shape = root_shape.shape(); diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 5c4ea4fc760..0b03102d9f4 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -985,3 +985,31 @@ 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.optimizer_options.serialize(s)?; + self.realm.serialize(s)?; + Ok(()) + } +} + +impl crate::snapshot::Deserialize for Context<'_> { + fn deserialize( + d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> Result { + let strict = d.read_bool()?; + let optimizer_options = OptimizerOptions::deserialize(d)?; + // let realm = Realm::deserialize(d)?; + let mut context = Context::default(); + + context.strict(strict); + context.set_optimizer_options(optimizer_options); + + Ok(context) + } +} diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 5906fa9e12b..b6cd1c17b54 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -19,6 +19,19 @@ struct CompileTimeBinding { strict: bool, } +impl crate::snapshot::Serialize for CompileTimeBinding { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.index.serialize(s)?; + self.mutable.serialize(s)?; + self.lex.serialize(s)?; + self.strict.serialize(s)?; + Ok(()) + } +} + /// A compile time environment maps bound identifiers to their binding positions. /// /// A compile time environment also indicates, if it is a function environment. @@ -30,6 +43,29 @@ pub(crate) struct CompileTimeEnvironment { function_scope: bool, } +impl crate::snapshot::Serialize for Identifier { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.sym().get().serialize(s)?; + Ok(()) + } +} + +impl crate::snapshot::Serialize for CompileTimeEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.outer.serialize(s)?; + self.environment_index.serialize(s)?; + self.bindings.serialize(s)?; + self.function_scope.serialize(s)?; + Ok(()) + } +} + // Safety: Nothing in this struct needs tracing, so this is safe. unsafe impl Trace for CompileTimeEnvironment { empty_trace!(); diff --git a/boa_engine/src/environments/runtime/declarative/function.rs b/boa_engine/src/environments/runtime/declarative/function.rs index 125c440461f..7bcbb68ec4c 100644 --- a/boa_engine/src/environments/runtime/declarative/function.rs +++ b/boa_engine/src/environments/runtime/declarative/function.rs @@ -10,6 +10,17 @@ pub(crate) struct FunctionEnvironment { slots: FunctionSlots, } +impl crate::snapshot::Serialize for FunctionEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.inner.serialize(s)?; + self.slots.serialize(s)?; + Ok(()) + } +} + impl FunctionEnvironment { /// Creates a new `FunctionEnvironment`. pub(crate) fn new(bindings: u32, poisoned: bool, with: bool, slots: FunctionSlots) -> Self { @@ -160,6 +171,22 @@ pub(crate) enum ThisBindingStatus { Initialized(JsValue), } +impl crate::snapshot::Serialize for ThisBindingStatus { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + ThisBindingStatus::Lexical => b'L'.serialize(s), + ThisBindingStatus::Uninitialized => b'U'.serialize(s), + ThisBindingStatus::Initialized(v) => { + b'I'.serialize(s)?; + v.serialize(s) + } + } + } +} + unsafe impl Trace for ThisBindingStatus { custom_trace!(this, { match this { @@ -182,6 +209,18 @@ pub(crate) struct FunctionSlots { new_target: Option, } +impl crate::snapshot::Serialize for FunctionSlots { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.this.borrow().serialize(s)?; + self.function_object.serialize(s)?; + self.new_target.serialize(s)?; + Ok(()) + } +} + impl FunctionSlots { /// Creates a new `FunctionSluts`. pub(crate) fn new( diff --git a/boa_engine/src/environments/runtime/declarative/global.rs b/boa_engine/src/environments/runtime/declarative/global.rs index 34183a51ada..5a23c60dad7 100644 --- a/boa_engine/src/environments/runtime/declarative/global.rs +++ b/boa_engine/src/environments/runtime/declarative/global.rs @@ -10,6 +10,17 @@ pub(crate) struct GlobalEnvironment { global_this: JsObject, } +impl crate::snapshot::Serialize for GlobalEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.inner.serialize(s)?; + self.global_this.serialize(s)?; + Ok(()) + } +} + impl GlobalEnvironment { /// Creates a new `GlobalEnvironment`. pub(crate) fn new(global_this: JsObject) -> Self { diff --git a/boa_engine/src/environments/runtime/declarative/lexical.rs b/boa_engine/src/environments/runtime/declarative/lexical.rs index 4969fdd8d5f..9752240bf8b 100644 --- a/boa_engine/src/environments/runtime/declarative/lexical.rs +++ b/boa_engine/src/environments/runtime/declarative/lexical.rs @@ -9,6 +9,15 @@ pub(crate) struct LexicalEnvironment { inner: PoisonableEnvironment, } +impl crate::snapshot::Serialize for LexicalEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.inner.serialize(s) + } +} + impl LexicalEnvironment { /// Creates a new `LexicalEnvironment`. pub(crate) fn new(bindings: u32, poisoned: bool, with: bool) -> Self { diff --git a/boa_engine/src/environments/runtime/declarative/mod.rs b/boa_engine/src/environments/runtime/declarative/mod.rs index 5e2a804fba4..46604720409 100644 --- a/boa_engine/src/environments/runtime/declarative/mod.rs +++ b/boa_engine/src/environments/runtime/declarative/mod.rs @@ -45,6 +45,17 @@ pub(crate) struct DeclarativeEnvironment { compile: Rc>, } +impl crate::snapshot::Serialize for DeclarativeEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.kind.serialize(s)?; + self.compile.serialize(s)?; + Ok(()) + } +} + impl DeclarativeEnvironment { /// Creates a new global `DeclarativeEnvironment`. pub(crate) fn global(global_this: JsObject) -> Self { @@ -145,6 +156,34 @@ pub(crate) enum DeclarativeEnvironmentKind { Module(ModuleEnvironment), } +impl crate::snapshot::Serialize for DeclarativeEnvironmentKind { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + DeclarativeEnvironmentKind::Lexical(env) => { + s.write_u8(b'L')?; + env.serialize(s)?; + } + DeclarativeEnvironmentKind::Global(env) => { + s.write_u8(b'G')?; + env.serialize(s)?; + } + DeclarativeEnvironmentKind::Function(env) => { + s.write_u8(b'F')?; + env.serialize(s)?; + } + DeclarativeEnvironmentKind::Module(env) => { + s.write_u8(b'M')?; + env.serialize(s)?; + } + } + + Ok(()) + } +} + impl DeclarativeEnvironmentKind { /// Unwraps the inner function environment if possible. Returns `None` otherwise. pub(crate) const fn as_function(&self) -> Option<&FunctionEnvironment> { @@ -278,6 +317,18 @@ pub(crate) struct PoisonableEnvironment { with: Cell, } +impl crate::snapshot::Serialize for PoisonableEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.bindings.borrow().serialize(s)?; + self.poisoned.serialize(s)?; + self.with.serialize(s)?; + Ok(()) + } +} + impl PoisonableEnvironment { /// Creates a new `PoisonableEnvironment`. pub(crate) fn new(bindings_count: u32, poisoned: bool, with: bool) -> Self { diff --git a/boa_engine/src/environments/runtime/declarative/module.rs b/boa_engine/src/environments/runtime/declarative/module.rs index 4ff09940026..1cb0c77e23f 100644 --- a/boa_engine/src/environments/runtime/declarative/module.rs +++ b/boa_engine/src/environments/runtime/declarative/module.rs @@ -12,6 +12,25 @@ enum BindingAccessor { Index(u32), } +impl crate::snapshot::Serialize for BindingAccessor { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + BindingAccessor::Identifier(i) => { + s.write_u8(b'I')?; + i.serialize(s)?; + } + BindingAccessor::Index(i) => { + s.write_u8(b'X')?; + i.serialize(s)?; + } + } + Ok(()) + } +} + /// An indirect reference to a binding inside an environment. #[derive(Clone, Debug, Trace, Finalize)] struct IndirectBinding { @@ -20,6 +39,17 @@ struct IndirectBinding { accessor: Cell, } +impl crate::snapshot::Serialize for IndirectBinding { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.module.serialize(s)?; + self.accessor.serialize(s)?; + Ok(()) + } +} + /// The type of binding a [`ModuleEnvironment`] can contain. #[derive(Clone, Debug, Trace, Finalize)] enum BindingType { @@ -27,6 +57,25 @@ enum BindingType { Indirect(IndirectBinding), } +impl crate::snapshot::Serialize for BindingType { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + BindingType::Direct(v) => { + s.write_u8(b'D')?; + v.serialize(s)?; + } + BindingType::Indirect(i) => { + s.write_u8(b'I')?; + i.serialize(s)?; + } + } + Ok(()) + } +} + /// A [**Module Environment Record**][spec]. /// /// Module environments allow referencing bindings inside other environments, in addition @@ -39,6 +88,16 @@ pub(crate) struct ModuleEnvironment { bindings: GcRefCell>, } +impl crate::snapshot::Serialize for ModuleEnvironment { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.bindings.borrow().serialize(s)?; + Ok(()) + } +} + impl ModuleEnvironment { /// Creates a new `LexicalEnvironment`. pub(crate) fn new(bindings: u32) -> Self { diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 1a66e7caee7..efa22867767 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -49,6 +49,15 @@ pub struct JsError { inner: Repr, } +impl crate::snapshot::Serialize for JsError { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.inner.serialize(s) + } +} + /// Internal representation of a [`JsError`]. /// /// `JsError` is represented by an opaque enum because it restricts @@ -65,6 +74,24 @@ enum Repr { Opaque(JsValue), } +impl crate::snapshot::Serialize for Repr { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + Repr::Native(v) => { + b'N'.serialize(s)?; + v.serialize(s)?; + } + Repr::Opaque(v) => { + v.serialize(s)?; + } + } + Ok(()) + } +} + /// The error type returned by the [`JsError::try_native`] method. #[derive(Debug, Clone, Error)] pub enum TryNativeError { @@ -428,6 +455,19 @@ pub struct JsNativeError { realm: Option, } +impl crate::snapshot::Serialize for JsNativeError { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.kind.serialize(s)?; + self.message.serialize(s)?; + self.cause.serialize(s)?; + self.realm.serialize(s)?; + Ok(()) + } +} + impl std::fmt::Debug for JsNativeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("JsNativeError") @@ -953,6 +993,49 @@ pub enum JsNativeErrorKind { RuntimeLimit, } +impl crate::snapshot::Serialize for JsNativeErrorKind { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + JsNativeErrorKind::Aggregate(errors) => { + b'0'.serialize(s)?; + errors.serialize(s)?; + } + JsNativeErrorKind::Error => { + b'1'.serialize(s)?; + } + JsNativeErrorKind::Eval => { + b'2'.serialize(s)?; + } + JsNativeErrorKind::Range => { + b'3'.serialize(s)?; + } + JsNativeErrorKind::Reference => { + b'4'.serialize(s)?; + } + JsNativeErrorKind::Syntax => { + b'5'.serialize(s)?; + } + JsNativeErrorKind::Type => { + b'6'.serialize(s)?; + } + JsNativeErrorKind::Uri => { + b'7'.serialize(s)?; + } + JsNativeErrorKind::RuntimeLimit => { + b'8'.serialize(s)?; + } + #[cfg(feature = "fuzz")] + JsNativeErrorKind::NoInstructionsRemain => { + b'9'.serialize(s)?; + } + } + Ok(()) + } +} + impl PartialEq for JsNativeErrorKind { fn eq(&self, other: &ErrorKind) -> bool { matches!( diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 1bd2c463463..1030727416e 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/module/mod.rs b/boa_engine/src/module/mod.rs index abd610d04ea..8533eb25933 100644 --- a/boa_engine/src/module/mod.rs +++ b/boa_engine/src/module/mod.rs @@ -269,6 +269,16 @@ pub struct Module { inner: Gc, } +impl crate::snapshot::Serialize for Module { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.inner.serialize(s)?; + Ok(()) + } +} + impl std::fmt::Debug for Module { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Module") @@ -290,6 +300,20 @@ struct Inner { host_defined: (), } +impl crate::snapshot::Serialize for Inner { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.realm.serialize(s)?; + self.environment.borrow().serialize(s)?; + self.namespace.borrow().serialize(s)?; + self.kind.serialize(s)?; + self.host_defined.serialize(s)?; + Ok(()) + } +} + /// The kind of a [`Module`]. #[derive(Debug, Trace, Finalize)] pub(crate) enum ModuleKind { @@ -300,6 +324,24 @@ pub(crate) enum ModuleKind { Synthetic, } +impl crate::snapshot::Serialize for ModuleKind { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + match self { + ModuleKind::SourceText(v) => { + s.write_u8(b'S')?; + v.serialize(s)?; + } + ModuleKind::Synthetic => { + s.write_u8(b'C')?; + } + } + Ok(()) + } +} + /// Return value of the [`Module::resolve_export`] operation. /// /// Indicates how to access a specific export in a module. diff --git a/boa_engine/src/module/source.rs b/boa_engine/src/module/source.rs index d73a26ee019..45ff459257a 100644 --- a/boa_engine/src/module/source.rs +++ b/boa_engine/src/module/source.rs @@ -93,6 +93,15 @@ enum Status { }, } +impl crate::snapshot::Serialize for Status { + fn serialize( + &self, + _s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + todo!() + } +} + // SAFETY: This must be synced with `Status` to mark any new data added that needs to be traced. // `Status` doesn't implement `Drop`, making this manual implementation safe. // @@ -251,6 +260,15 @@ pub(crate) struct SourceTextModule { inner: Gc, } +impl crate::snapshot::Serialize for SourceTextModule { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + self.inner.serialize(s) + } +} + impl std::fmt::Debug for SourceTextModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let limiter = RecursionLimiter::new(&*self.inner); @@ -279,6 +297,15 @@ struct Inner { code: ModuleCode, } +impl crate::snapshot::Serialize for Inner { + fn serialize( + &self, + _s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + todo!() + } +} + #[derive(Debug)] struct ModuleCode { has_tla: bool, 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..494703aa36a 100644 --- a/boa_engine/src/object/property_map.rs +++ b/boa_engine/src/object/property_map.rs @@ -226,6 +226,18 @@ 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> { + // FIXME: indexed properties. + self.shape.serialize(s)?; + self.storage.serialize(s)?; + Ok(()) + } +} + impl PropertyMap { /// Create a new [`PropertyMap`]. #[must_use] diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 352ca91567c..b560994f5b1 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -58,12 +58,41 @@ enum Inner { Shared(SharedShape), } +impl crate::snapshot::Serialize for Inner { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + match self { + Inner::Unique(shape) => { + b'U'.serialize(s)?; + shape.serialize(s)?; + } + Inner::Shared(shape) => { + b'S'.serialize(s)?; + shape.serialize(s)?; + } + } + Ok(()) + } +} + /// Represents the shape of an object. #[derive(Debug, Trace, Finalize, Clone)] pub struct Shape { inner: Inner, } +impl crate::snapshot::Serialize for Shape { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.inner.serialize(s)?; + Ok(()) + } +} + impl Default for Shape { #[inline] fn default() -> Self { diff --git a/boa_engine/src/object/shape/property_table.rs b/boa_engine/src/object/shape/property_table.rs index cb2d7513042..56bfbbd7930 100644 --- a/boa_engine/src/object/shape/property_table.rs +++ b/boa_engine/src/object/shape/property_table.rs @@ -14,6 +14,17 @@ pub(crate) struct PropertyTableInner { pub(crate) keys: Vec<(PropertyKey, Slot)>, } +impl crate::snapshot::Serialize for PropertyTableInner { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.map.serialize(s)?; + self.keys.serialize(s)?; + Ok(()) + } +} + impl PropertyTableInner { /// Returns all the keys, in insertion order. pub(crate) fn keys(&self) -> Vec { @@ -73,6 +84,19 @@ pub(crate) struct PropertyTable { pub(super) inner: Rc>, } +impl crate::snapshot::Serialize for PropertyTable { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + let ptr = self.inner.as_ptr() as usize; + s.reference_or(ptr, |s| { + self.inner.borrow().serialize(s)?; + Ok(()) + }) + } +} + impl PropertyTable { /// Returns the inner representation of a [`PropertyTable`]. pub(super) fn inner(&self) -> &RefCell { diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index 0eff31474f6..74681b3f429 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -49,6 +49,16 @@ bitflags! { } } +impl crate::snapshot::Serialize for ShapeFlags { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.bits().serialize(s)?; + Ok(()) + } +} + impl Default for ShapeFlags { fn default() -> Self { Self::empty() @@ -113,12 +123,36 @@ struct Inner { flags: ShapeFlags, } +impl crate::snapshot::Serialize for Inner { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.property_count.serialize(s)?; + self.prototype.serialize(s)?; + self.property_table.serialize(s)?; + self.previous.serialize(s)?; + self.transition_count.serialize(s)?; + self.flags.serialize(s)?; + Ok(()) + } +} + /// Represents a shared object shape. #[derive(Debug, Trace, Finalize, Clone)] pub struct SharedShape { inner: Gc, } +impl crate::snapshot::Serialize for SharedShape { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.inner.serialize(s) + } +} + impl SharedShape { fn property_table(&self) -> &PropertyTable { &self.inner.property_table diff --git a/boa_engine/src/object/shape/shared_shape/template.rs b/boa_engine/src/object/shape/shared_shape/template.rs index 14316cd21fb..e22e5897482 100644 --- a/boa_engine/src/object/shape/shared_shape/template.rs +++ b/boa_engine/src/object/shape/shared_shape/template.rs @@ -16,6 +16,16 @@ pub(crate) struct ObjectTemplate { shape: SharedShape, } +impl crate::snapshot::Serialize for ObjectTemplate { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.shape.serialize(s)?; + Ok(()) + } +} + impl ObjectTemplate { /// Create a new [`ObjectTemplate`] pub(crate) fn new(shape: &SharedShape) -> Self { diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index 64fb3c8f43f..ecddba0fb46 100644 --- a/boa_engine/src/object/shape/slot.rs +++ b/boa_engine/src/object/shape/slot.rs @@ -13,6 +13,16 @@ bitflags! { } } +impl crate::snapshot::Serialize for SlotAttributes { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.bits().serialize(s)?; + Ok(()) + } +} + impl SlotAttributes { pub(crate) const fn is_accessor_descriptor(self) -> bool { self.contains(Self::GET) || self.contains(Self::SET) @@ -48,6 +58,17 @@ pub(crate) struct Slot { pub(crate) attributes: SlotAttributes, } +impl crate::snapshot::Serialize for Slot { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.index.serialize(s)?; + self.attributes.serialize(s)?; + Ok(()) + } +} + impl Slot { /// Get the width of the slot. pub(crate) fn width(self) -> u32 { diff --git a/boa_engine/src/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index 68872a96c82..3c90fc5bef2 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -22,6 +22,17 @@ struct Inner { prototype: GcRefCell, } +impl crate::snapshot::Serialize for Inner { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.property_table.borrow().serialize(s)?; + self.prototype.borrow().serialize(s)?; + Ok(()) + } +} + /// Represents a [`Shape`] that is not shared with any other object. /// /// This is useful for objects that are inherently unique like, @@ -33,6 +44,16 @@ pub(crate) struct UniqueShape { inner: Gc, } +impl crate::snapshot::Serialize for UniqueShape { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + self.inner.serialize(s)?; + Ok(()) + } +} + impl UniqueShape { /// Create a new [`UniqueShape`]. pub(crate) fn new(prototype: JsPrototype, property_table: PropertyTableInner) -> Self { diff --git a/boa_engine/src/optimizer/mod.rs b/boa_engine/src/optimizer/mod.rs index e675a72a718..ce334822896 100644 --- a/boa_engine/src/optimizer/mod.rs +++ b/boa_engine/src/optimizer/mod.rs @@ -4,7 +4,10 @@ pub(crate) mod pass; pub(crate) mod walker; use self::{pass::ConstantFolding, walker::Walker}; -use crate::Context; +use crate::{ + snapshot::{Deserialize, Serialize}, + Context, +}; use bitflags::bitflags; use boa_ast::{visitor::VisitorMut, Expression, StatementList}; use std::{fmt, ops::ControlFlow}; @@ -24,6 +27,26 @@ bitflags! { } } +impl Serialize for OptimizerOptions { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + s.write_u8(self.bits())?; + Ok(()) + } +} +impl Deserialize for OptimizerOptions { + fn deserialize( + d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> Result { + let bits = d.read_u8()?; + + // TODO: handle error. + Ok(OptimizerOptions::from_bits(bits).unwrap()) + } +} + /// The action to be performed after an optimization step. #[derive(Debug)] pub(crate) enum PassAction { diff --git a/boa_engine/src/property/mod.rs b/boa_engine/src/property/mod.rs index 8adcd5a761c..f5a9985a294 100644 --- a/boa_engine/src/property/mod.rs +++ b/boa_engine/src/property/mod.rs @@ -582,6 +582,46 @@ pub enum PropertyKey { Index(u32), } +impl crate::snapshot::Serialize for PropertyKey { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> Result<(), crate::snapshot::SnapshotError> { + match self { + PropertyKey::String(v) => { + s.write_u8(0)?; + v.serialize(s)? + } + PropertyKey::Symbol(v) => { + s.write_u8(1)?; + v.serialize(s)? + } + PropertyKey::Index(v) => { + s.write_u8(2)?; + v.serialize(s)? + } + } + + Ok(()) + } +} + +impl crate::snapshot::Deserialize for PropertyKey { + fn deserialize( + d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> crate::snapshot::SnapshotResult { + let typ = u8::deserialize(d)?; + let result = match typ { + 0 => Self::String(JsString::deserialize(d)?), + 1 => Self::Symbol(JsSymbol::deserialize(d)?), + 2 => Self::Index(u32::deserialize(d)?), + _ => unreachable!("corrupted snapshot!"), + }; + + Ok(result) + } +} + /// Utility function for parsing [`PropertyKey`]. fn parse_u32_index(mut input: I) -> Option where diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index db396df201f..32c75656cb5 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -25,6 +25,27 @@ 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 crate::snapshot::Deserialize for Realm { + fn deserialize( + d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> Result { + let inner = Inner::deserialize(d)?; + Ok(Realm { + inner: Gc::new(inner), + }) + } +} + impl Eq for Realm {} impl PartialEq for Realm { @@ -54,6 +75,30 @@ 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)?; + self.global_object.serialize(s)?; + self.global_this.serialize(s)?; + self.template_map.borrow().serialize(s)?; + Ok(()) + } +} + +impl crate::snapshot::Deserialize for Inner { + fn deserialize( + _d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> Result { + // let intrinsics = Intrinsics::deserialize(d)?; + + // Ok(Inner::) + todo!() + } +} + impl Realm { /// Create a new Realm. #[inline] diff --git a/boa_engine/src/snapshot/deserializer.rs b/boa_engine/src/snapshot/deserializer.rs new file mode 100644 index 00000000000..88308688d65 --- /dev/null +++ b/boa_engine/src/snapshot/deserializer.rs @@ -0,0 +1,315 @@ +use std::hash::Hash; + +use indexmap::IndexSet; +use rustc_hash::FxHashMap; +use thin_vec::ThinVec; + +use super::{SnapshotError, SnapshotResult}; + +/// TODO: doc +pub trait Deserialize: Sized { + /// TODO: doc + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult; +} + +/// TODO: doc +pub struct SnapshotDeserializer<'snapshot> { + pub(super) bytes: &'snapshot [u8], + pub(super) index: usize, + pub(super) external_references: &'snapshot IndexSet, +} + +impl SnapshotDeserializer<'_> { + /// TODO: doc + pub fn read_bool(&mut self) -> Result { + let byte = self.read_u8()?; + assert!(byte == 0 || byte == 1); + Ok(byte == 1) + } + /// TODO: doc + pub fn read_u8(&mut self) -> Result { + let byte = self.bytes[self.index]; + self.index += 1; + Ok(byte) + } + /// TODO: doc + pub fn read_i8(&mut self) -> Result { + let byte = self.bytes[self.index]; + self.index += 1; + Ok(byte as i8) + } + + /// TODO: doc + pub fn read_u16(&mut self) -> Result { + let bytes = self.read_bytes(std::mem::size_of::())?; + let value = u16::from_le_bytes([bytes[0], bytes[1]]); + Ok(value) + } + /// TODO: doc + pub fn read_i16(&mut self) -> Result { + let value = self.read_u16()?; + Ok(value as i16) + } + + /// TODO: doc + pub fn read_u32(&mut self) -> Result { + 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 { + let value = self.read_u32()?; + Ok(value as i32) + } + + /// TODO: doc + pub fn read_f32(&mut self) -> Result { + let value = self.read_u32()?; + Ok(f32::from_bits(value)) + } + /// TODO: doc + pub fn read_f64(&mut self) -> Result { + let value = self.read_u64()?; + Ok(f64::from_bits(value)) + } + + /// TODO: doc + pub fn read_u64(&mut self) -> Result { + let bytes = self.read_bytes(std::mem::size_of::())?; + 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 { + let value = self.read_u64()?; + Ok(value as i64) + } + + /// TODO: doc + pub fn read_usize(&mut self) -> Result { + let value = self.read_u64()?; + // TODO: handle error. + Ok(usize::try_from(value).unwrap()) + } + /// TODO: doc + pub fn read_isize(&mut self) -> Result { + 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) + } +} + +impl Deserialize for bool { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_bool() + } +} + +impl Deserialize for u8 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_u8() + } +} + +impl Deserialize for i8 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_i8() + } +} + +impl Deserialize for u16 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_u16() + } +} + +impl Deserialize for i16 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_i16() + } +} + +impl Deserialize for u32 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_u32() + } +} + +impl Deserialize for i32 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_i32() + } +} + +impl Deserialize for u64 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_u64() + } +} + +impl Deserialize for i64 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_i64() + } +} + +impl Deserialize for usize { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_usize() + } +} + +impl Deserialize for isize { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_isize() + } +} + +impl Deserialize for f32 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_f32() + } +} + +impl Deserialize for f64 { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + d.read_f64() + } +} + +impl Deserialize for Option { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let is_some = bool::deserialize(d)?; + if is_some { + return Ok(Some(T::deserialize(d)?)); + } + + Ok(None) + } +} + +impl Deserialize for Result { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let is_ok = bool::deserialize(d)?; + Ok(if is_ok { + Ok(T::deserialize(d)?) + } else { + Err(E::deserialize(d)?) + }) + } +} + +impl Deserialize for Vec { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let len = usize::deserialize(d)?; + let mut values = Vec::with_capacity(len); + for _ in 0..len { + let value = T::deserialize(d)?; + values.push(value); + } + Ok(values) + } +} + +impl Deserialize for ThinVec { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let len = usize::deserialize(d)?; + let mut values = ThinVec::with_capacity(len); + for _ in 0..len { + let value = T::deserialize(d)?; + values.push(value); + } + Ok(values) + } +} + +impl Deserialize for Box { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let value = T::deserialize(d)?; + Ok(Box::new(value)) + } +} + +impl Deserialize for Box { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let len = usize::deserialize(d)?; + let bytes = d.read_bytes(len)?; + Ok(String::from_utf8(bytes.into()).unwrap().into_boxed_str()) + } +} + +impl Deserialize for () { + fn deserialize(_d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + // Deserialize nothing, zero size type. + Ok(()) + } +} + +impl Deserialize for (T1,) { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let v1 = T1::deserialize(d)?; + Ok((v1,)) + } +} + +impl Deserialize for (T1, T2) { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let v1 = T1::deserialize(d)?; + let v2 = T2::deserialize(d)?; + Ok((v1, v2)) + } +} + +impl Deserialize for (T1, T2, T3) { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let v1 = T1::deserialize(d)?; + let v2 = T2::deserialize(d)?; + let v3 = T3::deserialize(d)?; + Ok((v1, v2, v3)) + } +} + +impl Deserialize + for (T1, T2, T3, T4) +{ + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let v1 = T1::deserialize(d)?; + let v2 = T2::deserialize(d)?; + let v3 = T3::deserialize(d)?; + let v4 = T4::deserialize(d)?; + Ok((v1, v2, v3, v4)) + } +} + +impl Deserialize for FxHashMap { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + let len = usize::deserialize(d)?; + + let mut result = Self::default(); + for _ in 0..len { + let key = K::deserialize(d)?; + let value = V::deserialize(d)?; + let ret = result.insert(key, value); + + assert!(ret.is_none()); + } + Ok(result) + } +} diff --git a/boa_engine/src/snapshot/error.rs b/boa_engine/src/snapshot/error.rs new file mode 100644 index 00000000000..630651d471f --- /dev/null +++ b/boa_engine/src/snapshot/error.rs @@ -0,0 +1,28 @@ +use std::fmt::{Debug, Display}; + +/// 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) + } +} + +/// Type alias for [`Result`] return type snapshot operations. +pub type SnapshotResult = Result; diff --git a/boa_engine/src/snapshot/header.rs b/boa_engine/src/snapshot/header.rs new file mode 100644 index 00000000000..241c7330628 --- /dev/null +++ b/boa_engine/src/snapshot/header.rs @@ -0,0 +1,28 @@ +use super::{Deserialize, Serialize, SnapshotDeserializer, SnapshotResult, SnapshotSerializer}; + +/// TODO: doc +#[derive(Debug, Clone, Copy)] +pub struct Header { + pub(crate) signature: [u8; 4], + pub(crate) version: u32, + // checksum: u64, +} + +impl Serialize for Header { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_bytes(&self.signature)?; + s.write_u32(self.version)?; + Ok(()) + } +} + +impl Deserialize for Header { + fn deserialize(d: &mut SnapshotDeserializer<'_>) -> SnapshotResult { + 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 }) + } +} diff --git a/boa_engine/src/snapshot/mod.rs b/boa_engine/src/snapshot/mod.rs new file mode 100644 index 00000000000..25d0d2cba36 --- /dev/null +++ b/boa_engine/src/snapshot/mod.rs @@ -0,0 +1,60 @@ +//! TODO: doc + +#![allow(missing_debug_implementations)] +#![allow(dead_code)] + +mod deserializer; +mod error; +mod header; +mod serializer; + +pub use deserializer::*; +pub use error::*; +pub use header::*; +pub use serializer::*; + +use crate::Context; +use indexmap::IndexSet; + +/// TODO: doc +pub struct Snapshot { + bytes: Vec, + external_references: IndexSet, +} + +impl Snapshot { + /// TODO: doc + pub fn new(bytes: Vec) -> Self { + Self { + bytes, + external_references: IndexSet::default(), + } + } + + /// TODO: doc + pub fn bytes(&self) -> &[u8] { + &self.bytes + } + + /// TODO: doc + pub fn deserialize<'a>(&self) -> Result, 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) + } +} diff --git a/boa_engine/src/snapshot/serializer.rs b/boa_engine/src/snapshot/serializer.rs new file mode 100644 index 00000000000..eee403ae91e --- /dev/null +++ b/boa_engine/src/snapshot/serializer.rs @@ -0,0 +1,471 @@ +use std::{ + cell::{Cell, RefCell}, + hash::Hash, + mem::size_of, + rc::Rc, +}; + +use boa_gc::{Gc, Trace}; +use indexmap::{IndexMap, IndexSet}; +use rustc_hash::FxHashMap; +use thin_vec::ThinVec; + +use crate::{object::shape::SharedShape, Context, JsBigInt, JsObject, JsString, JsSymbol}; + +use super::{Header, Snapshot, SnapshotError, SnapshotResult}; + +/// TODO: doc +pub trait Serialize { + /// Serialize type + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()>; +} + +/// TODO: doc +struct Reference { + is_inlined: u8, + index: u32, +} + +impl Reference { + fn new(is_inlined: bool, index: u32) -> Self { + Self { + is_inlined: if is_inlined { b'I' } else { b'R' }, + index, + } + } +} + +impl Serialize for Reference { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.is_inlined.serialize(s)?; + self.index.serialize(s)?; + Ok(()) + } +} + +/// TODO: doc +pub struct SnapshotSerializer { + bytes: Vec, + objects: IndexSet, + strings: IndexMap, + symbols: IndexMap, + bigints: IndexSet, + shared_shapes: IndexMap, + + pub(crate) internal_reference: IndexMap, + external_references: IndexSet, +} + +impl SnapshotSerializer { + /// TODO: doc + pub fn new() -> Self { + Self { + bytes: Vec::new(), + objects: IndexSet::default(), + strings: IndexMap::default(), + symbols: IndexMap::default(), + bigints: IndexSet::default(), + shared_shapes: IndexMap::default(), + internal_reference: IndexMap::default(), + external_references: IndexSet::default(), + } + } + + /// Serialize the given [`Context`]. + pub fn serialize(mut self, context: &mut Context<'_>) -> Result { + // Remove any garbage objects before serialization. + boa_gc::force_collect(); + boa_gc::force_collect(); + boa_gc::force_collect(); + + // boa_gc::walk_gc_alloc_pointers(|address| { + // }); + + let header = Header { + signature: *b".boa", + version: 42, + }; + + header.serialize(&mut self)?; + context.serialize(&mut 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(&mut 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(&mut self)?; + } else { + self.write_bool(false)?; + } + } + + Ok(Snapshot { + bytes: self.bytes, + external_references: self.external_references, + }) + } + + /// TODO: doc + pub fn write_bool(&mut self, v: bool) -> SnapshotResult<()> { + Ok(self.write_u8(if v { 1 } else { 0 })?) + } + /// TODO: doc + pub fn write_u8(&mut self, v: u8) -> SnapshotResult<()> { + Ok(self.write_bytes(&[v])?) + } + /// TODO: doc + pub fn write_i8(&mut self, v: i8) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_u16(&mut self, v: u16) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i16(&mut self, v: i16) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_u32(&mut self, v: u32) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i32(&mut self, v: i32) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_f32(&mut self, v: f32) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_f64(&mut self, v: f64) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + + /// TODO: doc + pub fn write_u64(&mut self, v: u64) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i64(&mut self, v: i64) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_u128(&mut self, v: u128) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_i128(&mut self, v: i128) -> SnapshotResult<()> { + Ok(self.write_bytes(&v.to_le_bytes())?) + } + /// TODO: doc + pub fn write_usize(&mut self, v: usize) -> SnapshotResult<()> { + Ok(self.write_bytes(&(v as u64).to_le_bytes())?) + } + /// TODO: doc + pub fn write_isize(&mut self, v: isize) -> SnapshotResult<()> { + Ok(self.write_bytes(&(v as i64).to_le_bytes())?) + } + /// TODO: doc + pub fn write_string(&mut self, v: &str) -> SnapshotResult<()> { + 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]) -> SnapshotResult<()> { + self.bytes.extend_from_slice(v); + Ok(()) + } + + /// TODO: doc + pub fn reference_or(&mut self, ptr: usize, f: F) -> SnapshotResult<()> + where + F: FnOnce(&mut SnapshotSerializer) -> SnapshotResult<()>, + { + match self.internal_reference.entry(ptr) { + indexmap::map::Entry::Occupied(entry) => { + let index = *entry.get(); + Reference::new(false, index).serialize(self)?; + return Ok(()); + } + indexmap::map::Entry::Vacant(entry) => { + let index = + *entry.insert((self.bytes.len() + size_of::() + size_of::()) as u32); + Reference::new(true, index).serialize(self)?; + } + } + + f(self) + } +} + +impl Serialize for bool { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_bool(*self) + } +} + +impl Serialize for u8 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_u8(*self) + } +} + +impl Serialize for i8 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_i8(*self) + } +} + +impl Serialize for u16 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_u16(*self) + } +} + +impl Serialize for i16 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_i16(*self) + } +} + +impl Serialize for u32 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_u32(*self) + } +} + +impl Serialize for i32 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_i32(*self) + } +} + +impl Serialize for u64 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_u64(*self) + } +} + +impl Serialize for i64 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_i64(*self) + } +} + +impl Serialize for usize { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_usize(*self) + } +} + +impl Serialize for isize { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_isize(*self) + } +} + +impl Serialize for f32 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_f32(*self) + } +} + +impl Serialize for f64 { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_f64(*self) + } +} + +impl Serialize for Option { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + if let Some(value) = self { + s.write_bool(true)?; + value.serialize(s)? + } else { + s.write_bool(false)?; + } + Ok(()) + } +} + +impl Serialize for Result { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + match self { + Ok(value) => { + s.write_bool(true)?; + value.serialize(s)?; + } + Err(err) => { + s.write_bool(false)?; + err.serialize(s)?; + } + } + Ok(()) + } +} + +impl Serialize for Vec { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_usize(self.len())?; + for element in self { + element.serialize(s)?; + } + Ok(()) + } +} + +impl Serialize for ThinVec { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + s.write_usize(self.len())?; + for element in self { + element.serialize(s)?; + } + Ok(()) + } +} + +impl Serialize for Box { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + T::serialize(&self, s) + } +} + +impl Serialize for Box { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.len().serialize(s)?; + s.write_bytes(self.as_bytes())?; + Ok(()) + } +} + +impl Serialize for String { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.len().serialize(s)?; + s.write_bytes(self.as_bytes())?; + Ok(()) + } +} + +impl Serialize for () { + fn serialize(&self, _s: &mut SnapshotSerializer) -> SnapshotResult<()> { + // Serialize nothing + Ok(()) + } +} + +impl Serialize for (T1,) { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.0.serialize(s)?; + Ok(()) + } +} + +impl Serialize for (T1, T2) { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.0.serialize(s)?; + self.1.serialize(s)?; + Ok(()) + } +} + +impl Serialize for (T1, T2, T3) { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.0.serialize(s)?; + self.1.serialize(s)?; + self.2.serialize(s)?; + Ok(()) + } +} + +impl Serialize for (T1, T2, T3, T4) { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.0.serialize(s)?; + self.1.serialize(s)?; + self.2.serialize(s)?; + self.3.serialize(s)?; + Ok(()) + } +} + +impl Serialize for FxHashMap { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.len().serialize(s)?; + for (key, value) in self { + key.serialize(s)?; + value.serialize(s)?; + } + Ok(()) + } +} + +impl Serialize for JsBigInt { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + let ptr: *const _ = self.as_inner(); + s.reference_or(ptr as usize, |s| { + let (sign, bytes) = self.as_inner().to_bytes_le(); + + match sign { + num_bigint::Sign::Minus => b'-', + num_bigint::Sign::NoSign => b' ', + num_bigint::Sign::Plus => b'+', + } + .serialize(s)?; + + bytes.serialize(s) + }) + } +} + +impl Serialize for JsObject { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + let ptr: *const _ = self.inner(); + s.reference_or(ptr as usize, |s| self.inner().serialize(s)) + } +} + +impl Serialize for Gc { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + let ptr: *const _ = &*self; + s.reference_or(ptr as usize, |s| T::serialize(&*self, s)) + } +} + +impl Serialize for Rc { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + let ptr: *const _ = &*self; + s.reference_or(ptr as usize, |s| T::serialize(&*self, s)) + } +} + +impl Serialize for RefCell { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.borrow().serialize(s) + } +} + +impl Serialize for Cell { + fn serialize(&self, s: &mut SnapshotSerializer) -> SnapshotResult<()> { + self.get().serialize(s) + } +} diff --git a/boa_engine/src/string/mod.rs b/boa_engine/src/string/mod.rs index 41225895715..7e7174efc1d 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,37 @@ const DATA_OFFSET: usize = std::mem::size_of::(); /// \[u16\]'s methods. #[derive(Finalize)] pub struct JsString { - ptr: Tagged, + pub(crate) ptr: Tagged, +} + +impl crate::snapshot::Serialize for JsString { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + let addr = self.ptr.addr(); + s.reference_or(addr, |s| { + s.write_bool(self.is_static())?; + if !self.is_static() { + s.write_usize(self.len())?; + for elem in self.as_slice() { + s.write_u16(*elem)?; + } + } else { + s.write_usize(addr)?; + } + + Ok(()) + }) + } +} + +impl crate::snapshot::Deserialize for JsString { + fn deserialize( + _d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> crate::snapshot::SnapshotResult { + todo!() + } } // JsString should always be pointer sized. @@ -616,6 +646,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..046f5ba60c3 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -111,7 +111,7 @@ impl WellKnown { /// The inner representation of a JavaScript symbol. #[derive(Debug, Clone)] -struct Inner { +pub(crate) struct Inner { hash: u64, description: Option, } @@ -121,6 +121,39 @@ pub struct JsSymbol { repr: Tagged, } +impl crate::snapshot::Serialize for JsSymbol { + fn serialize( + &self, + s: &mut crate::snapshot::SnapshotSerializer, + ) -> crate::snapshot::SnapshotResult<()> { + let addr = self.repr.addr(); + s.reference_or(addr, |s| { + s.write_bool(self.repr.is_tagged())?; + if !self.repr.is_tagged() { + self.hash().serialize(s)?; + if let Some(desc) = self.description() { + s.write_bool(true)?; + desc.serialize(s)?; + } else { + s.write_bool(false)?; + } + } else { + s.write_usize(addr)?; + } + + Ok(()) + }) + } +} + +impl crate::snapshot::Deserialize for JsSymbol { + fn deserialize( + _d: &mut crate::snapshot::SnapshotDeserializer<'_>, + ) -> crate::snapshot::SnapshotResult { + todo!() + } +} + // SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe. unsafe impl Send for JsSymbol {} // 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 ba5c18c5148..89e99a53847 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -554,3 +554,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..92641aca825 --- /dev/null +++ b/docs/snapshot.md @@ -0,0 +1,22 @@ +# Snapshot File format + +This document 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. | + +## Internal Reference Map + + +## JsValue Encoding + +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.