From efeaa40ae247dda10d51675a7b8192f0a7a7ad4f Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Wed, 17 May 2023 22:40:09 +0200 Subject: [PATCH] Fix tagged template creation (#2925) * Fix tagged template creation * Fix template identifier hash * Apply suggestion * Apply suggestion --- boa_ast/src/expression/tagged_template.rs | 10 ++ boa_engine/src/builtins/eval/mod.rs | 1 + boa_engine/src/builtins/function/mod.rs | 37 ++++--- boa_engine/src/bytecompiler/expression/mod.rs | 25 ++--- boa_engine/src/context/mod.rs | 12 ++ boa_engine/src/realm.rs | 16 ++- boa_engine/src/vm/code_block.rs | 7 ++ boa_engine/src/vm/flowgraph/mod.rs | 10 ++ boa_engine/src/vm/opcode/mod.rs | 17 +++ boa_engine/src/vm/opcode/templates/mod.rs | 104 ++++++++++++++++++ boa_parser/src/parser/cursor/mod.rs | 26 +++++ .../expression/left_hand_side/template.rs | 1 + boa_parser/src/parser/mod.rs | 8 ++ 13 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 boa_engine/src/vm/opcode/templates/mod.rs diff --git a/boa_ast/src/expression/tagged_template.rs b/boa_ast/src/expression/tagged_template.rs index c138d74160c..b463ce5724a 100644 --- a/boa_ast/src/expression/tagged_template.rs +++ b/boa_ast/src/expression/tagged_template.rs @@ -21,6 +21,7 @@ pub struct TaggedTemplate { raws: Box<[Sym]>, cookeds: Box<[Option]>, exprs: Box<[Expression]>, + identifier: u64, } impl TaggedTemplate { @@ -33,12 +34,14 @@ impl TaggedTemplate { raws: Box<[Sym]>, cookeds: Box<[Option]>, exprs: Box<[Expression]>, + identifier: u64, ) -> Self { Self { tag: tag.into(), raws, cookeds, exprs, + identifier, } } @@ -69,6 +72,13 @@ impl TaggedTemplate { pub const fn exprs(&self) -> &[Expression] { &self.exprs } + + /// Gets the unique identifier of the template. + #[inline] + #[must_use] + pub const fn identifier(&self) -> u64 { + self.identifier + } } impl ToInternedString for TaggedTemplate { diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index bb27226edc8..05d01ae258a 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -112,6 +112,7 @@ impl Eval { // c. If script Contains ScriptBody is false, return undefined. // d. Let body be the ScriptBody of script. let mut parser = Parser::new(Source::from_bytes(&x)); + parser.set_identifier(context.next_parser_identifier()); if strict { parser.set_strict(); } diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 8343ff3ebc7..6b30c913e58 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -586,17 +586,22 @@ impl BuiltInFunctionObject { let parameters = parameters.join(utf16!(",")); // TODO: make parser generic to u32 iterators - let parameters = - match Parser::new(Source::from_bytes(&String::from_utf16_lossy(¶meters))) - .parse_formal_parameters(context.interner_mut(), generator, r#async) - { - Ok(parameters) => parameters, - Err(e) => { - return Err(JsNativeError::syntax() - .with_message(format!("failed to parse function parameters: {e}")) - .into()) - } - }; + let parameters = String::from_utf16_lossy(¶meters); + let mut parser = Parser::new(Source::from_bytes(¶meters)); + parser.set_identifier(context.next_parser_identifier()); + + let parameters = match parser.parse_formal_parameters( + context.interner_mut(), + generator, + r#async, + ) { + Ok(parameters) => parameters, + Err(e) => { + return Err(JsNativeError::syntax() + .with_message(format!("failed to parse function parameters: {e}")) + .into()) + } + }; if generator && contains(¶meters, ContainsSymbol::YieldExpression) { return Err(JsNativeError::syntax().with_message( @@ -626,11 +631,11 @@ impl BuiltInFunctionObject { let body = b"\n".chain(body_arg.as_bytes()).chain(b"\n".as_slice()); // TODO: make parser generic to u32 iterators - let body = match Parser::new(Source::from_reader(body, None)).parse_function_body( - context.interner_mut(), - generator, - r#async, - ) { + let mut parser = Parser::new(Source::from_reader(body, None)); + parser.set_identifier(context.next_parser_identifier()); + + let body = match parser.parse_function_body(context.interner_mut(), generator, r#async) + { Ok(statement_list) => statement_list, Err(e) => { return Err(JsNativeError::syntax() diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index 40681b44085..2203c95d4c9 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -17,7 +17,6 @@ use boa_ast::{ }, Expression, }; -use boa_interner::Sym; impl ByteCompiler<'_, '_> { fn compile_literal(&mut self, lit: &AstLiteral, use_expr: bool) { @@ -255,8 +254,13 @@ impl ByteCompiler<'_, '_> { } } - self.emit_opcode(Opcode::PushNewArray); - for cooked in template.cookeds() { + let site = template.identifier(); + let count = template.cookeds().len() as u32; + + let jump_label = self.emit_opcode_with_operand(Opcode::TemplateLookup); + self.emit_u64(site); + + for (cooked, raw) in template.cookeds().iter().zip(template.raws()) { if let Some(cooked) = cooked { self.emit_push_literal(Literal::String( self.interner().resolve_expect(*cooked).into_common(false), @@ -264,22 +268,15 @@ impl ByteCompiler<'_, '_> { } else { self.emit_opcode(Opcode::PushUndefined); } - self.emit_opcode(Opcode::PushValueToArray); - } - self.emit_opcode(Opcode::Dup); - self.emit_opcode(Opcode::Dup); - - self.emit_opcode(Opcode::PushNewArray); - for raw in template.raws() { self.emit_push_literal(Literal::String( self.interner().resolve_expect(*raw).into_common(false), )); - self.emit_opcode(Opcode::PushValueToArray); } - let index = self.get_or_insert_name(Sym::RAW.into()); - self.emit(Opcode::SetPropertyByName, &[index]); - self.emit(Opcode::Pop, &[]); + self.emit(Opcode::TemplateCreate, &[count]); + self.emit_u64(site); + + self.patch_jump(jump_label); for expr in template.exprs() { self.compile_expr(expr, true); diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index caecfd72f7e..9280c9eee0f 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -112,6 +112,9 @@ pub struct Context<'host> { optimizer_options: OptimizerOptions, root_shape: SharedShape, + + /// Unique identifier for each parser instance used during the context lifetime. + parser_identifier: u32, } impl std::fmt::Debug for Context<'_> { @@ -230,6 +233,7 @@ impl<'host> Context<'host> { ) -> Result { let _timer = Profiler::global().start_event("Script parsing", "Main"); let mut parser = Parser::new(src); + parser.set_identifier(self.next_parser_identifier()); if self.strict { parser.set_strict(); } @@ -247,6 +251,7 @@ impl<'host> Context<'host> { ) -> Result { let _timer = Profiler::global().start_event("Module parsing", "Main"); let mut parser = Parser::new(src); + parser.set_identifier(self.next_parser_identifier()); parser.parse_module(&mut self.interner) } @@ -656,6 +661,12 @@ impl Context<'_> { std::mem::swap(&mut self.realm, realm); } + /// Increment and get the parser identifier. + pub(crate) fn next_parser_identifier(&mut self) -> u32 { + self.parser_identifier += 1; + self.parser_identifier + } + /// `CanDeclareGlobalFunction ( N )` /// /// More information: @@ -1025,6 +1036,7 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> { }), optimizer_options: OptimizerOptions::OPTIMIZE_ALL, root_shape, + parser_identifier: 0, }; builtins::set_default_global_bindings(&mut context)?; diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index 349d7de7668..ee2a871c6ea 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -6,15 +6,15 @@ //! //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. -use std::fmt; - use crate::{ context::{intrinsics::Intrinsics, HostHooks}, environments::DeclarativeEnvironment, object::{shape::shared_shape::SharedShape, JsObject}, }; -use boa_gc::{Finalize, Gc, Trace}; +use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_profiler::Profiler; +use rustc_hash::FxHashMap; +use std::fmt; /// Representation of a Realm. /// @@ -49,6 +49,7 @@ struct Inner { environment: Gc, global_object: JsObject, global_this: JsObject, + template_map: GcRefCell>, } impl Realm { @@ -70,6 +71,7 @@ impl Realm { environment, global_object, global_this, + template_map: GcRefCell::new(FxHashMap::default()), }), }; @@ -110,4 +112,12 @@ impl Realm { bindings.resize(binding_number, None); } } + + pub(crate) fn push_template(&self, site: u64, template: JsObject) { + self.inner.template_map.borrow_mut().insert(site, template); + } + + pub(crate) fn lookup_template(&self, site: u64) -> Option { + self.inner.template_map.borrow().get(&site).cloned() + } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 6731a1285df..452c0dd972b 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -300,6 +300,13 @@ impl CodeBlock { *pc += size_of::(); format!("{operand1}, {operand2}") } + Opcode::TemplateLookup | Opcode::TemplateCreate => { + let operand1 = self.read::(*pc); + *pc += size_of::(); + let operand2 = self.read::(*pc); + *pc += size_of::(); + format!("{operand1}, {operand2}") + } Opcode::GeneratorAsyncDelegateResume => { let operand1 = self.read::(*pc); *pc += size_of::(); diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index f78a9ba90c0..eb70abc6ca0 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -148,6 +148,16 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } + Opcode::TemplateLookup | Opcode::TemplateCreate => { + let start_address = self.read::(pc); + pc += size_of::(); + let end_address = self.read::(pc); + pc += size_of::(); + + let label = format!("{opcode_str} {start_address}, {end_address}"); + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red); + graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); + } Opcode::Break => { let jump_operand = self.read::(pc); pc += size_of::(); diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index 612b63032eb..0a57ec89852 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -27,6 +27,7 @@ mod rest_parameter; mod set; mod swap; mod switch; +mod templates; mod to; mod unary_ops; mod value; @@ -79,6 +80,8 @@ pub(crate) use swap::*; #[doc(inline)] pub(crate) use switch::*; #[doc(inline)] +pub(crate) use templates::*; +#[doc(inline)] pub(crate) use to::*; #[doc(inline)] pub(crate) use unary_ops::*; @@ -1602,6 +1605,20 @@ generate_impl! { /// Stack: value **=>** is_object IsObject, + /// Lookup if a tagged template object is cached and skip the creation if it is. + /// + /// Operands: jump: `u32`, site: `u64` + /// + /// Stack: **=>** template (if cached) + TemplateLookup, + + /// Create a new tagged template object and cache it. + /// + /// Operands: count: `u32`, site: `u64` + /// + /// Stack: count * (cooked_value, raw_value) **=>** template + TemplateCreate, + /// No-operation instruction, does nothing. /// /// Operands: diff --git a/boa_engine/src/vm/opcode/templates/mod.rs b/boa_engine/src/vm/opcode/templates/mod.rs new file mode 100644 index 00000000000..4e193d4ffa2 --- /dev/null +++ b/boa_engine/src/vm/opcode/templates/mod.rs @@ -0,0 +1,104 @@ +use crate::{ + builtins::array::Array, + object::IntegrityLevel, + property::PropertyDescriptor, + vm::{opcode::Operation, CompletionType}, + Context, JsResult, +}; +use boa_macros::utf16; + +/// `TemplateLookup` implements the Opcode Operation for `Opcode::TemplateLookup` +/// +/// Operation: +/// - Lookup if a tagged template object is cached and skip the creation if it is. +#[derive(Debug, Clone, Copy)] +pub(crate) struct TemplateLookup; + +impl Operation for TemplateLookup { + const NAME: &'static str = "TemplateLookup"; + const INSTRUCTION: &'static str = "INST - TemplateLookup"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let jump = context.vm.read::(); + let site = context.vm.read::(); + + if let Some(template) = context.realm().lookup_template(site) { + context.vm.push(template); + context.vm.frame_mut().pc = jump as usize; + } + + Ok(CompletionType::Normal) + } +} + +/// `TemplateCreate` implements the Opcode Operation for `Opcode::TemplateCreate` +/// +/// Operation: +/// - Create a new tagged template object and cache it. +#[derive(Debug, Clone, Copy)] +pub(crate) struct TemplateCreate; + +impl Operation for TemplateCreate { + const NAME: &'static str = "TemplateCreate"; + const INSTRUCTION: &'static str = "INST - TemplateCreate"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let count = context.vm.read::(); + let site = context.vm.read::(); + + let template = + Array::array_create(count.into(), None, context).expect("cannot fail per spec"); + let raw_obj = + Array::array_create(count.into(), None, context).expect("cannot fail per spec"); + + for index in (0..count).rev() { + let raw_value = context.vm.pop(); + let cooked_value = context.vm.pop(); + template + .define_property_or_throw( + index, + PropertyDescriptor::builder() + .value(cooked_value) + .writable(false) + .enumerable(true) + .configurable(false), + context, + ) + .expect("should not fail on new array"); + raw_obj + .define_property_or_throw( + index, + PropertyDescriptor::builder() + .value(raw_value) + .writable(false) + .enumerable(true) + .configurable(false), + context, + ) + .expect("should not fail on new array"); + } + + raw_obj + .set_integrity_level(IntegrityLevel::Frozen, context) + .expect("should never fail per spec"); + template + .define_property_or_throw( + utf16!("raw"), + PropertyDescriptor::builder() + .value(raw_obj) + .writable(false) + .enumerable(false) + .configurable(false), + context, + ) + .expect("should never fail per spec"); + template + .set_integrity_level(IntegrityLevel::Frozen, context) + .expect("should never fail per spec"); + + context.realm().push_template(site, template.clone()); + + context.vm.push(template); + Ok(CompletionType::Normal) + } +} diff --git a/boa_parser/src/parser/cursor/mod.rs b/boa_parser/src/parser/cursor/mod.rs index 12162270f3b..0b91a3cb060 100644 --- a/boa_parser/src/parser/cursor/mod.rs +++ b/boa_parser/src/parser/cursor/mod.rs @@ -36,6 +36,13 @@ pub(super) struct Cursor { /// Indicate if the cursor is used in `JSON.parse`. json_parse: bool, + + /// A unique identifier for each parser instance. + /// This is used to generate unique identifiers tagged template literals. + identifier: u32, + + /// Tracks the number of tagged templates that are currently being parsed. + tagged_templates_count: u32, } impl Cursor @@ -50,6 +57,8 @@ where private_environment_root_index: 0, arrow: false, json_parse: false, + identifier: 0, + tagged_templates_count: 0, } } @@ -169,6 +178,23 @@ where self.private_environment_nested_index != 0 } + /// Set the identifier of the cursor. + #[inline] + pub(super) fn set_identifier(&mut self, identifier: u32) { + self.identifier = identifier; + } + + /// Get the identifier for a tagged template. + #[inline] + pub(super) fn tagged_template_identifier(&mut self) -> u64 { + self.tagged_templates_count += 1; + + let identifier = u64::from(self.identifier); + let count = u64::from(self.tagged_templates_count); + + (count << 32) | identifier + } + /// Returns an error if the next token is not of kind `kind`. pub(super) fn expect( &mut self, diff --git a/boa_parser/src/parser/expression/left_hand_side/template.rs b/boa_parser/src/parser/expression/left_hand_side/template.rs index fc1f09081f2..ffd353fe59a 100644 --- a/boa_parser/src/parser/expression/left_hand_side/template.rs +++ b/boa_parser/src/parser/expression/left_hand_side/template.rs @@ -84,6 +84,7 @@ where raws.into_boxed_slice(), cookeds.into_boxed_slice(), exprs.into_boxed_slice(), + cursor.tagged_template_identifier(), )); } _ => { diff --git a/boa_parser/src/parser/mod.rs b/boa_parser/src/parser/mod.rs index 5e18af8a7c5..1e33bdf3156 100644 --- a/boa_parser/src/parser/mod.rs +++ b/boa_parser/src/parser/mod.rs @@ -218,6 +218,14 @@ impl Parser<'_, R> { { self.cursor.set_json_parse(true); } + + /// Set the unique identifier for the parser. + pub fn set_identifier(&mut self, identifier: u32) + where + R: Read, + { + self.cursor.set_identifier(identifier); + } } /// Parses a full script.