Browse Source

Fix tagged template creation (#2925)

* Fix tagged template creation

* Fix template identifier hash

* Apply suggestion

* Apply suggestion
pull/2939/head
raskad 2 years ago committed by GitHub
parent
commit
efeaa40ae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      boa_ast/src/expression/tagged_template.rs
  2. 1
      boa_engine/src/builtins/eval/mod.rs
  3. 37
      boa_engine/src/builtins/function/mod.rs
  4. 25
      boa_engine/src/bytecompiler/expression/mod.rs
  5. 12
      boa_engine/src/context/mod.rs
  6. 16
      boa_engine/src/realm.rs
  7. 7
      boa_engine/src/vm/code_block.rs
  8. 10
      boa_engine/src/vm/flowgraph/mod.rs
  9. 17
      boa_engine/src/vm/opcode/mod.rs
  10. 104
      boa_engine/src/vm/opcode/templates/mod.rs
  11. 26
      boa_parser/src/parser/cursor/mod.rs
  12. 1
      boa_parser/src/parser/expression/left_hand_side/template.rs
  13. 8
      boa_parser/src/parser/mod.rs

10
boa_ast/src/expression/tagged_template.rs

@ -21,6 +21,7 @@ pub struct TaggedTemplate {
raws: Box<[Sym]>,
cookeds: Box<[Option<Sym>]>,
exprs: Box<[Expression]>,
identifier: u64,
}
impl TaggedTemplate {
@ -33,12 +34,14 @@ impl TaggedTemplate {
raws: Box<[Sym]>,
cookeds: Box<[Option<Sym>]>,
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 {

1
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();
}

37
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(&parameters)))
.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(&parameters);
let mut parser = Parser::new(Source::from_bytes(&parameters));
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(&parameters, 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()

25
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);

12
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<StatementList, ParseError> {
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<ModuleItemList, ParseError> {
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)?;

16
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<DeclarativeEnvironment>,
global_object: JsObject,
global_this: JsObject,
template_map: GcRefCell<FxHashMap<u64, JsObject>>,
}
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<JsObject> {
self.inner.template_map.borrow().get(&site).cloned()
}
}

7
boa_engine/src/vm/code_block.rs

@ -300,6 +300,13 @@ impl CodeBlock {
*pc += size_of::<u32>();
format!("{operand1}, {operand2}")
}
Opcode::TemplateLookup | Opcode::TemplateCreate => {
let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let operand2 = self.read::<u64>(*pc);
*pc += size_of::<u64>();
format!("{operand1}, {operand2}")
}
Opcode::GeneratorAsyncDelegateResume => {
let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>();

10
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::<u32>(pc);
pc += size_of::<u32>();
let end_address = self.read::<u64>(pc);
pc += size_of::<u64>();
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::<u32>(pc);
pc += size_of::<u32>();

17
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:

104
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<CompletionType> {
let jump = context.vm.read::<u32>();
let site = context.vm.read::<u64>();
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<CompletionType> {
let count = context.vm.read::<u32>();
let site = context.vm.read::<u64>();
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)
}
}

26
boa_parser/src/parser/cursor/mod.rs

@ -36,6 +36,13 @@ pub(super) struct Cursor<R> {
/// 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<R> Cursor<R>
@ -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<K>(
&mut self,

1
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(),
));
}
_ => {

8
boa_parser/src/parser/mod.rs

@ -218,6 +218,14 @@ impl<R> 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.

Loading…
Cancel
Save