Browse Source

Implement annexB Block-Level Function Declarations (#2910)

* Implement annexB Block-Level Function Declarations

* Apply suggestions
pull/2828/head
raskad 2 years ago committed by GitHub
parent
commit
70b0d49483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 212
      boa_ast/src/operations.rs
  2. 2
      boa_engine/src/builtins/eval/mod.rs
  3. 2
      boa_engine/src/builtins/json/mod.rs
  4. 4
      boa_engine/src/bytecompiler/class.rs
  5. 234
      boa_engine/src/bytecompiler/declarations.rs
  6. 17
      boa_engine/src/bytecompiler/env.rs
  7. 2
      boa_engine/src/bytecompiler/function.rs
  8. 46
      boa_engine/src/bytecompiler/mod.rs
  9. 2
      boa_engine/src/bytecompiler/module.rs
  10. 2
      boa_engine/src/bytecompiler/statement/block.rs
  11. 2
      boa_engine/src/bytecompiler/statement/switch.rs
  12. 2
      boa_engine/src/context/mod.rs
  13. 46
      boa_engine/src/environments/compile.rs
  14. 4
      boa_engine/src/environments/runtime.rs
  15. 17
      boa_engine/src/vm/code_block.rs
  16. 7
      boa_engine/src/vm/flowgraph/mod.rs
  17. 45
      boa_engine/src/vm/opcode/define/mod.rs
  18. 26
      boa_engine/src/vm/opcode/mod.rs

212
boa_ast/src/operations.rs

@ -9,7 +9,7 @@ use boa_interner::{Interner, Sym};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use crate::{ use crate::{
declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration, Variable}, declaration::{Binding, ExportDeclaration, ImportDeclaration, VarDeclaration, Variable},
expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield}, expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield},
function::{ function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
@ -1663,3 +1663,213 @@ impl<'ast> Visitor<'ast> for TopLevelVarScopedDeclarationsVisitor<'_> {
} }
} }
} }
/// Returns a list function declaration names that are directly contained in a statement lists
/// `Block`, `CaseClause` or `DefaultClause`.
/// If the function declaration would cause an early error it is not included in the list.
///
/// This behavior is used in the following annexB sections:
/// * [B.3.2.1 Changes to FunctionDeclarationInstantiation][spec0]
/// * [B.3.2.2 Changes to GlobalDeclarationInstantiation][spec1]
/// * [B.3.2.3 Changes to EvalDeclarationInstantiation][spec2]
///
/// [spec0]: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation
/// [spec1]: https://tc39.es/ecma262/#sec-web-compat-globaldeclarationinstantiation
/// [spec2]: https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation
#[must_use]
pub fn annex_b_function_declarations_names<'a, N>(node: &'a N) -> Vec<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut declarations = Vec::new();
AnnexBFunctionDeclarationNamesVisitor(&mut declarations).visit(node.into());
declarations
}
/// The [`Visitor`] used for [`annex_b_function_declarations_names`].
#[derive(Debug)]
struct AnnexBFunctionDeclarationNamesVisitor<'a>(&'a mut Vec<Identifier>);
impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> {
type BreakTy = Infallible;
fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
StatementListItem::Statement(node) => self.visit(node),
StatementListItem::Declaration(_) => ControlFlow::Continue(()),
}
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node {
Statement::Block(node) => self.visit(node),
Statement::If(node) => self.visit(node),
Statement::DoWhileLoop(node) => self.visit(node),
Statement::WhileLoop(node) => self.visit(node),
Statement::ForLoop(node) => self.visit(node),
Statement::ForInLoop(node) => self.visit(node),
Statement::ForOfLoop(node) => self.visit(node),
Statement::Switch(node) => self.visit(node),
Statement::Labelled(node) => self.visit(node),
Statement::Try(node) => self.visit(node),
Statement::With(node) => self.visit(node),
_ => ControlFlow::Continue(()),
}
}
fn visit_block(&mut self, node: &'ast crate::statement::Block) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement_list());
for statement in node.statement_list().statements() {
if let StatementListItem::Declaration(Declaration::Function(function)) = statement {
let name = function
.name()
.expect("function declaration must have name");
self.0.push(name);
}
}
let lexically_declared_names = lexically_declared_names_legacy(node.statement_list());
self.0
.retain(|name| !lexically_declared_names.contains(&(*name, false)));
ControlFlow::Continue(())
}
fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow<Self::BreakTy> {
for case in node.cases() {
self.visit(case);
for statement in case.body().statements() {
if let StatementListItem::Declaration(Declaration::Function(function)) = statement {
let name = function
.name()
.expect("function declaration must have name");
self.0.push(name);
}
}
}
if let Some(default) = node.default() {
self.visit(default);
for statement in default.statements() {
if let StatementListItem::Declaration(Declaration::Function(function)) = statement {
let name = function
.name()
.expect("function declaration must have name");
self.0.push(name);
}
}
}
let lexically_declared_names = lexically_declared_names_legacy(node);
self.0
.retain(|name| !lexically_declared_names.contains(&(*name, false)));
ControlFlow::Continue(())
}
fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow<Self::BreakTy> {
self.visit(node.block());
if let Some(catch) = node.catch() {
self.visit(catch.block());
if let Some(Binding::Pattern(pattern)) = catch.parameter() {
let bound_names = bound_names(pattern);
self.0.retain(|name| !bound_names.contains(name));
}
}
if let Some(finally) = node.finally() {
self.visit(finally.block());
}
ControlFlow::Continue(())
}
fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow<Self::BreakTy> {
if let Some(node) = node.else_node() {
self.visit(node);
}
self.visit(node.body())
}
fn visit_do_while_loop(
&mut self,
node: &'ast crate::statement::DoWhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body())
}
fn visit_while_loop(
&mut self,
node: &'ast crate::statement::WhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body())
}
fn visit_for_loop(
&mut self,
node: &'ast crate::statement::ForLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
if let Some(ForLoopInitializer::Lexical(node)) = node.init() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}
ControlFlow::Continue(())
}
fn visit_for_in_loop(
&mut self,
node: &'ast crate::statement::ForInLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
if let IterableLoopInitializer::Let(node) = node.initializer() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}
if let IterableLoopInitializer::Const(node) = node.initializer() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}
ControlFlow::Continue(())
}
fn visit_for_of_loop(
&mut self,
node: &'ast crate::statement::ForOfLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
if let IterableLoopInitializer::Let(node) = node.initializer() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}
if let IterableLoopInitializer::Const(node) = node.initializer() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}
ControlFlow::Continue(())
}
fn visit_labelled(
&mut self,
node: &'ast crate::statement::Labelled,
) -> ControlFlow<Self::BreakTy> {
if let LabelledItem::Statement(node) = node.item() {
self.visit(node);
}
ControlFlow::Continue(())
}
fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement())
}
}

2
boa_engine/src/builtins/eval/mod.rs

@ -234,7 +234,7 @@ impl Eval {
let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
compiler.eval_declaration_instantiation(&body, strict)?; compiler.eval_declaration_instantiation(&body, strict)?;
compiler.compile_statement_list(&body, true); compiler.compile_statement_list(&body, true, false);
let env_info = compiler.pop_compile_environment(); let env_info = compiler.pop_compile_environment();
compiler.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); compiler.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);

2
boa_engine/src/builtins/json/mod.rs

@ -123,7 +123,7 @@ impl Json {
context.realm().environment().compile_env(), context.realm().environment().compile_env(),
context, context,
); );
compiler.compile_statement_list(&statement_list, true); compiler.compile_statement_list(&statement_list, true, false);
Gc::new(compiler.finish()) Gc::new(compiler.finish())
}; };
let unfiltered = context.execute(code_block)?; let unfiltered = context.execute(code_block)?;

4
boa_engine/src/bytecompiler/class.rs

@ -47,7 +47,7 @@ impl ByteCompiler<'_, '_> {
false, false,
); );
compiler.compile_statement_list(expr.body(), false); compiler.compile_statement_list(expr.body(), false, false);
let env_info = compiler.pop_compile_environment(); let env_info = compiler.pop_compile_environment();
@ -374,7 +374,7 @@ impl ByteCompiler<'_, '_> {
false, false,
); );
compiler.compile_statement_list(statement_list, false); compiler.compile_statement_list(statement_list, false, false);
let env_info = compiler.pop_compile_environment(); let env_info = compiler.pop_compile_environment();
compiler.pop_compile_environment(); compiler.pop_compile_environment();
compiler.num_bindings = env_info.num_bindings; compiler.num_bindings = env_info.num_bindings;

234
boa_engine/src/bytecompiler/declarations.rs

@ -15,6 +15,9 @@ use boa_ast::{
}; };
use boa_interner::Sym; use boa_interner::Sym;
#[cfg(feature = "annex-b")]
use boa_ast::operations::annex_b_function_declarations_names;
impl ByteCompiler<'_, '_> { impl ByteCompiler<'_, '_> {
/// `GlobalDeclarationInstantiation ( script, env )` /// `GlobalDeclarationInstantiation ( script, env )`
/// ///
@ -151,8 +154,55 @@ impl ByteCompiler<'_, '_> {
// 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. // 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object.
// However, if the global object is a Proxy exotic object it may exhibit behaviours // However, if the global object is a Proxy exotic object it may exhibit behaviours
// that cause abnormal terminations in some of the following steps. // that cause abnormal terminations in some of the following steps.
// 12. NOTE: Annex B.3.2.2 adds additional steps at this point. // 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// TODO: Support B.3.2.2. // 12. Perform the following steps:
// a. Let strict be IsStrict of script.
// b. If strict is false, then
#[cfg(feature = "annex-b")]
if !script.strict() {
let lex_names = top_level_lexically_declared_names(script);
// i. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames.
// ii. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause,
// or DefaultClause Contained within script, do
for f in annex_b_function_declarations_names(script) {
// 1. Let F be StringValue of the BindingIdentifier of f.
// 2. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier
// would not produce any Early Errors for script, then
if !lex_names.contains(&f) {
// a. If env.HasLexicalDeclaration(F) is false, then
if !self.current_environment.borrow().has_lex_binding(f) {
// i. Let fnDefinable be ? env.CanDeclareGlobalVar(F).
let fn_definable = self.context.can_declare_global_function(f)?;
// ii. If fnDefinable is true, then
if fn_definable {
// i. NOTE: A var binding for F is only instantiated here if it is neither
// a VarDeclaredName nor the name of another FunctionDeclaration.
// ii. If declaredFunctionOrVarNames does not contain F, then
if !declared_function_names.contains(&f)
&& !declared_var_names.contains(&f)
{
// i. Perform ? env.CreateGlobalVarBinding(F, false).
self.context.create_global_var_binding(f, false)?;
// ii. Append F to declaredFunctionOrVarNames.
declared_function_names.push(f);
}
// iii. When the FunctionDeclaration f is evaluated, perform the following
// steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6:
// i. Let genv be the running execution context's VariableEnvironment.
// ii. Let benv be the running execution context's LexicalEnvironment.
// iii. Let fobj be ! benv.GetBindingValue(F, false).
// iv. Perform ? genv.SetMutableBinding(F, fobj, false).
// v. Return unused.
self.annex_b_function_names.push(f);
}
}
}
}
}
// 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// 14. Let privateEnv be null. // 14. Let privateEnv be null.
@ -253,6 +303,8 @@ impl ByteCompiler<'_, '_> {
where where
&'a N: Into<NodeRef<'a>>, &'a N: Into<NodeRef<'a>>,
{ {
let mut env = self.current_environment.borrow_mut();
// 1. Let declarations be the LexicallyScopedDeclarations of code. // 1. Let declarations be the LexicallyScopedDeclarations of code.
let declarations = lexically_scoped_declarations(block); let declarations = lexically_scoped_declarations(block);
@ -266,19 +318,28 @@ impl ByteCompiler<'_, '_> {
// a. For each element dn of the BoundNames of d, do // a. For each element dn of the BoundNames of d, do
for dn in bound_names::<'_, VariableList>(d) { for dn in bound_names::<'_, VariableList>(d) {
// 1. Perform ! env.CreateImmutableBinding(dn, true). // 1. Perform ! env.CreateImmutableBinding(dn, true).
self.create_immutable_binding(dn, true); env.create_immutable_binding(dn, true);
} }
} }
// ii. Else, // ii. Else,
else { else {
// a. For each element dn of the BoundNames of d, do // a. For each element dn of the BoundNames of d, do
for dn in bound_names::<'_, Declaration>(d) { for dn in bound_names::<'_, Declaration>(d) {
#[cfg(not(feature = "annex-b"))]
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
// TODO: Support B.3.2.6. env.create_mutable_binding(dn, false);
self.create_mutable_binding(dn, false);
#[cfg(feature = "annex-b")]
// 1. If ! env.HasBinding(dn) is false, then
if !env.has_binding(dn) {
// a. Perform ! env.CreateMutableBinding(dn, false).
env.create_mutable_binding(dn, false);
} }
} }
} }
}
drop(env);
// Note: Not sure if the spec is wrong here or if our implementation just differs too much, // Note: Not sure if the spec is wrong here or if our implementation just differs too much,
// but we need 3.a to be finished for all declarations before 3.b can be done. // but we need 3.a to be finished for all declarations before 3.b can be done.
@ -343,7 +404,8 @@ impl ByteCompiler<'_, '_> {
// c. Assert: The following loop will terminate. // c. Assert: The following loop will terminate.
// d. Repeat, while thisEnv is not varEnv, // d. Repeat, while thisEnv is not varEnv,
// i. If thisEnv is not an Object Environment Record, then // i. If thisEnv is not an Object Environment Record, then
// 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts. // 1. NOTE: The environment of with statements cannot contain any lexical
// declaration so it doesn't need to be checked for var/let hoisting conflicts.
// 2. For each element name of varNames, do // 2. For each element name of varNames, do
// a. If ! thisEnv.HasBinding(name) is true, then // a. If ! thisEnv.HasBinding(name) is true, then
// i. Throw a SyntaxError exception. // i. Throw a SyntaxError exception.
@ -420,7 +482,93 @@ impl ByteCompiler<'_, '_> {
functions_to_initialize.reverse(); functions_to_initialize.reverse();
// 11. NOTE: Annex B.3.2.3 adds additional steps at this point. // 11. NOTE: Annex B.3.2.3 adds additional steps at this point.
// TODO: Support B.3.2.3 // 11. If strict is false, then
#[cfg(feature = "annex-b")]
if !strict {
let lexically_declared_names = top_level_lexically_declared_names(body);
// a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames.
// b. For each FunctionDeclaration f that is directly contained in the StatementList
// of a Block, CaseClause, or DefaultClause Contained within body, do
for f in annex_b_function_declarations_names(body) {
// i. Let F be StringValue of the BindingIdentifier of f.
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F
// as a BindingIdentifier would not produce any Early Errors for body, then
if !lexically_declared_names.contains(&f) {
// 1. Let bindingExists be false.
// 2. Let thisEnv be lexEnv.
// 3. Assert: The following loop will terminate.
// 4. Repeat, while thisEnv is not varEnv,
// a. If thisEnv is not an Object Environment Record, then
// i. If ! thisEnv.HasBinding(F) is true, then
// i. Let bindingExists be true.
// b. Set thisEnv to thisEnv.[[OuterEnv]].
let binding_exists = self.has_binding_until_var(f);
// 5. If bindingExists is false and varEnv is a Global Environment Record, then
let fn_definable = if !binding_exists && var_environment_is_global {
// a. If varEnv.HasLexicalDeclaration(F) is false, then
// b. Else,
if self.current_environment.borrow().has_lex_binding(f) {
// i. Let fnDefinable be false.
false
} else {
// i. Let fnDefinable be ? varEnv.CanDeclareGlobalVar(F).
self.context.can_declare_global_var(f)?
}
}
// 6. Else,
else {
// a. Let fnDefinable be true.
true
};
// 7. If bindingExists is false and fnDefinable is true, then
if !binding_exists && fn_definable {
let mut function_names = Vec::new();
// a. If declaredFunctionOrVarNames does not contain F, then
if !declared_function_names.contains(&f)
//&& !var_names.contains(&f)
&& !function_names.contains(&f)
{
// i. If varEnv is a Global Environment Record, then
if var_environment_is_global {
// i. Perform ? varEnv.CreateGlobalVarBinding(F, true).
self.context.create_global_var_binding(f, true)?;
}
// ii. Else,
else {
// i. Let bindingExists be ! varEnv.HasBinding(F).
// ii. If bindingExists is false, then
if !self.has_binding(f) {
// i. Perform ! varEnv.CreateMutableBinding(F, true).
self.create_mutable_binding(f, true);
// ii. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = self.initialize_mutable_binding(f, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[index]);
}
}
// iii. Append F to declaredFunctionOrVarNames.
function_names.push(f);
}
// b. When the FunctionDeclaration f is evaluated, perform the following steps
// in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6:
// i. Let genv be the running execution context's VariableEnvironment.
// ii. Let benv be the running execution context's LexicalEnvironment.
// iii. Let fobj be ! benv.GetBindingValue(F, false).
// iv. Perform ? genv.SetMutableBinding(F, fobj, false).
// v. Return unused.
self.annex_b_function_names.push(f);
}
}
}
}
// 12. Let declaredVarNames be a new empty List. // 12. Let declaredVarNames be a new empty List.
let mut declared_var_names = Vec::new(); let mut declared_var_names = Vec::new();
@ -752,13 +900,16 @@ impl ByteCompiler<'_, '_> {
// a. If strict is true or simpleParameterList is false, then // a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList). // i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else, // b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters. // i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
// c. If strict is true, then // c. If strict is true, then
if strict { if strict {
// i. Perform ! env.CreateImmutableBinding("arguments", false). // i. Perform ! env.CreateImmutableBinding("arguments", false).
// ii. NOTE: In strict mode code early errors prevent attempting to assign to this binding, so its mutability is not observable. // ii. NOTE: In strict mode code early errors prevent attempting to assign
// to this binding, so its mutability is not observable.
self.create_immutable_binding(arguments, false); self.create_immutable_binding(arguments, false);
self.arguments_binding = Some(self.initialize_immutable_binding(arguments)); self.arguments_binding = Some(self.initialize_immutable_binding(arguments));
} }
@ -777,7 +928,7 @@ impl ByteCompiler<'_, '_> {
} }
// 23. Else, // 23. Else,
// a. Let parameterBindings be parameterNames. // a. Let parameterBindings be parameterNames.
let parameter_bindings = parameter_names; let parameter_bindings = parameter_names.clone();
// 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList). // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList).
// 25. If hasDuplicates is true, then // 25. If hasDuplicates is true, then
@ -821,8 +972,11 @@ impl ByteCompiler<'_, '_> {
// 27. If hasParameterExpressions is false, then // 27. If hasParameterExpressions is false, then
// 28. Else, // 28. Else,
if has_parameter_expressions { #[allow(unused_variables, unused_mut)]
// a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body. let mut instantiated_var_names = if has_parameter_expressions {
// a. NOTE: A separate Environment Record is needed to ensure that closures created by
// expressions in the formal parameter list do not have
// visibility of declarations in the function body.
// b. Let varEnv be NewDeclarativeEnvironment(env). // b. Let varEnv be NewDeclarativeEnvironment(env).
// c. Set the VariableEnvironment of calleeContext to varEnv. // c. Set the VariableEnvironment of calleeContext to varEnv.
self.push_compile_environment(true); self.push_compile_environment(true);
@ -865,6 +1019,8 @@ impl ByteCompiler<'_, '_> {
// the same value as the corresponding initialized parameter. // the same value as the corresponding initialized parameter.
} }
} }
instantiated_var_names
} else { } else {
// a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars. // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
// b. Let instantiatedVarNames be a copy of the List parameterBindings. // b. Let instantiatedVarNames be a copy of the List parameterBindings.
@ -889,21 +1045,67 @@ impl ByteCompiler<'_, '_> {
} }
// d. Let varEnv be env. // d. Let varEnv be env.
instantiated_var_names
}; };
// 29. NOTE: Annex B.3.2.1 adds additional steps at this point. // 29. NOTE: Annex B.3.2.1 adds additional steps at this point.
// TODO: Support B.3.2.1
// 29. If strict is false, then // 29. If strict is false, then
// a. Let lexEnv be NewDeclarativeEnvironment(varEnv). #[cfg(feature = "annex-b")]
// b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places all declarations into a new Environment Record. if !strict {
// a. For each FunctionDeclaration f that is directly contained in the StatementList
// of a Block, CaseClause, or DefaultClause, do
for f in annex_b_function_declarations_names(code) {
// i. Let F be StringValue of the BindingIdentifier of f.
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F
// as a BindingIdentifier would not produce any Early Errors
// for func and parameterNames does not contain F, then
if !lexical_names.contains(&f) && !parameter_names.contains(&f) {
// 1. NOTE: A var binding for F is only instantiated here if it is neither a
// VarDeclaredName, the name of a formal parameter, or another FunctionDeclaration.
// 2. If initializedBindings does not contain F and F is not "arguments", then
if !instantiated_var_names.contains(&f) && f != arguments {
// a. Perform ! varEnv.CreateMutableBinding(F, false).
self.create_mutable_binding(f, true);
// b. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = self.initialize_mutable_binding(f, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[index]);
// c. Append F to instantiatedVarNames.
instantiated_var_names.push(f);
}
// 3. When the FunctionDeclaration f is evaluated, perform the following steps
// in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6:
// a. Let fenv be the running execution context's VariableEnvironment.
// b. Let benv be the running execution context's LexicalEnvironment.
// c. Let fobj be ! benv.GetBindingValue(F, false).
// d. Perform ! fenv.SetMutableBinding(F, fobj, false).
// e. Return unused.
self.annex_b_function_names.push(f);
}
}
}
// 30. If strict is false, then
// 30.a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
// 30.b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical
// declarations so that a direct eval can determine whether any var scoped declarations
// introduced by the eval code conflict with pre-existing top-level lexically scoped declarations.
// This is not needed for strict functions because a strict direct eval always
// places all declarations into a new Environment Record.
// 31. Else, // 31. Else,
// a. Let lexEnv be varEnv. // a. Let lexEnv be varEnv.
// 32. Set the LexicalEnvironment of calleeContext to lexEnv. // 32. Set the LexicalEnvironment of calleeContext to lexEnv.
// 33. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
// 34. For each element d of lexDeclarations, do // 34. For each element d of lexDeclarations, do
// a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized. // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration,
// formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do // b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then // i. If IsConstantDeclaration of d is true, then
// 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).

17
boa_engine/src/bytecompiler/env.rs

@ -66,6 +66,15 @@ impl ByteCompiler<'_, '_> {
.has_binding_eval(name, strict) .has_binding_eval(name, strict)
} }
#[cfg(feature = "annex-b")]
/// Check if a binding name exists in a environment.
/// Stop when a function scope is reached.
pub(crate) fn has_binding_until_var(&self, name: Identifier) -> bool {
self.current_environment
.borrow()
.has_binding_until_var(name)
}
/// Create a mutable binding at bytecode compile time. /// Create a mutable binding at bytecode compile time.
/// This function returns a syntax error, if the binding is a redeclaration. /// This function returns a syntax error, if the binding is a redeclaration.
/// ///
@ -119,4 +128,12 @@ impl ByteCompiler<'_, '_> {
.borrow() .borrow()
.set_mutable_binding_recursive(name) .set_mutable_binding_recursive(name)
} }
#[cfg(feature = "annex-b")]
/// Return the binding locator for a set operation on an existing var binding.
pub(crate) fn set_mutable_binding_var(&self, name: Identifier) -> BindingLocator {
self.current_environment
.borrow()
.set_mutable_binding_var_recursive(name)
}
} }

2
boa_engine/src/bytecompiler/function.rs

@ -125,7 +125,7 @@ impl FunctionCompiler {
self.generator, self.generator,
); );
compiler.compile_statement_list(body, false); compiler.compile_statement_list(body, false, false);
if let Some(env_labels) = env_labels { if let Some(env_labels) = env_labels {
let env_info = compiler.pop_compile_environment(); let env_info = compiler.pop_compile_environment();

46
boa_engine/src/bytecompiler/mod.rs

@ -290,6 +290,9 @@ pub struct ByteCompiler<'ctx, 'host> {
// TODO: remove when we separate scripts from the context // TODO: remove when we separate scripts from the context
context: &'ctx mut Context<'host>, context: &'ctx mut Context<'host>,
#[cfg(feature = "annex-b")]
annex_b_function_names: Vec<Identifier>,
} }
impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
@ -337,6 +340,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
json_parse, json_parse,
current_environment, current_environment,
context, context,
#[cfg(feature = "annex-b")]
annex_b_function_names: Vec::new(),
} }
} }
@ -403,11 +409,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefVar, &[index]); self.emit(Opcode::DefVar, &[index]);
} }
BindingOpcode::Let => {
let binding = self.initialize_mutable_binding(name, false);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefLet, &[index]);
}
BindingOpcode::InitVar => { BindingOpcode::InitVar => {
let binding = if self.has_binding(name) { let binding = if self.has_binding(name) {
self.set_mutable_binding(name) self.set_mutable_binding(name)
@ -420,12 +421,12 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
BindingOpcode::InitLet => { BindingOpcode::InitLet => {
let binding = self.initialize_mutable_binding(name, false); let binding = self.initialize_mutable_binding(name, false);
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitLet, &[index]); self.emit(Opcode::PutLexicalValue, &[index]);
} }
BindingOpcode::InitConst => { BindingOpcode::InitConst => {
let binding = self.initialize_immutable_binding(name); let binding = self.initialize_immutable_binding(name);
let index = self.get_or_insert_binding(binding); let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitConst, &[index]); self.emit(Opcode::PutLexicalValue, &[index]);
} }
BindingOpcode::SetName => { BindingOpcode::SetName => {
let binding = self.set_mutable_binding(name); let binding = self.set_mutable_binding(name);
@ -742,7 +743,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
} }
/// Compile a [`StatementList`]. /// Compile a [`StatementList`].
pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool) { pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool, block: bool) {
if use_expr { if use_expr {
let expr_index = list let expr_index = list
.statements() .statements()
@ -758,11 +759,11 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
.count(); .count();
for (i, item) in list.statements().iter().enumerate() { for (i, item) in list.statements().iter().enumerate() {
self.compile_stmt_list_item(item, i + 1 == expr_index); self.compile_stmt_list_item(item, i + 1 == expr_index, block);
} }
} else { } else {
for item in list.statements() { for item in list.statements() {
self.compile_stmt_list_item(item, false); self.compile_stmt_list_item(item, false, block);
} }
} }
} }
@ -980,10 +981,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
let ident = ident; let ident = ident;
if let Some(expr) = variable.init() { if let Some(expr) = variable.init() {
self.compile_expr(expr, true); self.compile_expr(expr, true);
self.emit_binding(BindingOpcode::InitLet, *ident);
} else { } else {
self.emit_binding(BindingOpcode::Let, *ident); self.emit_opcode(Opcode::PushUndefined);
} }
self.emit_binding(BindingOpcode::InitLet, *ident);
} }
Binding::Pattern(pattern) => { Binding::Pattern(pattern) => {
if let Some(init) = variable.init() { if let Some(init) = variable.init() {
@ -1023,18 +1024,33 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
} }
/// Compile a [`StatementListItem`]. /// Compile a [`StatementListItem`].
fn compile_stmt_list_item(&mut self, item: &StatementListItem, use_expr: bool) { fn compile_stmt_list_item(&mut self, item: &StatementListItem, use_expr: bool, block: bool) {
match item { match item {
StatementListItem::Statement(stmt) => { StatementListItem::Statement(stmt) => {
self.compile_stmt(stmt, use_expr); self.compile_stmt(stmt, use_expr);
} }
StatementListItem::Declaration(decl) => self.compile_decl(decl), StatementListItem::Declaration(decl) => self.compile_decl(decl, block),
} }
} }
/// Compile a [`Declaration`]. /// Compile a [`Declaration`].
pub fn compile_decl(&mut self, decl: &Declaration) { #[allow(unused_variables)]
pub fn compile_decl(&mut self, decl: &Declaration, block: bool) {
match decl { match decl {
#[cfg(feature = "annex-b")]
Declaration::Function(function) if block => {
let name = function
.name()
.expect("function declaration must have name");
if self.annex_b_function_names.contains(&name) {
let binding = self.get_binding_value(name);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::GetName, &[index]);
let binding = self.set_mutable_binding_var(name);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[index]);
}
}
Declaration::Class(class) => self.class(class, false), Declaration::Class(class) => self.class(class, false),
Declaration::Lexical(lexical) => self.compile_lexical_decl(lexical), Declaration::Lexical(lexical) => self.compile_lexical_decl(lexical),
_ => {} _ => {}

2
boa_engine/src/bytecompiler/module.rs

@ -18,7 +18,7 @@ impl ByteCompiler<'_, '_> {
pub fn compile_module_item(&mut self, item: &ModuleItem) { pub fn compile_module_item(&mut self, item: &ModuleItem) {
match item { match item {
ModuleItem::StatementListItem(stmt) => { ModuleItem::StatementListItem(stmt) => {
self.compile_stmt_list_item(stmt, false); self.compile_stmt_list_item(stmt, false, false);
} }
_ => { _ => {
// TODO: Remove after implementing modules. // TODO: Remove after implementing modules.

2
boa_engine/src/bytecompiler/statement/block.rs

@ -8,7 +8,7 @@ impl ByteCompiler<'_, '_> {
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.block_declaration_instantiation(block); self.block_declaration_instantiation(block);
self.compile_statement_list(block.statement_list(), use_expr); self.compile_statement_list(block.statement_list(), use_expr, true);
let env_info = self.pop_compile_environment(); let env_info = self.pop_compile_environment();
self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);

2
boa_engine/src/bytecompiler/statement/switch.rs

@ -43,7 +43,7 @@ impl ByteCompiler<'_, '_> {
label label
}; };
self.patch_jump(label); self.patch_jump(label);
self.compile_statement_list(case.body(), false); self.compile_statement_list(case.body(), false, true);
} }
if !default_label_set { if !default_label_set {

2
boa_engine/src/context/mod.rs

@ -262,7 +262,7 @@ impl<'host> Context<'host> {
self, self,
); );
compiler.global_declaration_instantiation(statement_list)?; compiler.global_declaration_instantiation(statement_list)?;
compiler.compile_statement_list(statement_list, true); compiler.compile_statement_list(statement_list, true, false);
Ok(Gc::new(compiler.finish())) Ok(Gc::new(compiler.finish()))
} }

46
boa_engine/src/environments/compile.rs

@ -56,6 +56,12 @@ impl CompileTimeEnvironment {
.map_or(false, |binding| binding.lex) .map_or(false, |binding| binding.lex)
} }
#[cfg(feature = "annex-b")]
/// Check if the environment has a binding with the given name.
pub(crate) fn has_binding(&self, name: Identifier) -> bool {
self.bindings.contains_key(&name)
}
/// Checks if `name` is a lexical binding. /// Checks if `name` is a lexical binding.
pub(crate) fn is_lex_binding(&self, name: Identifier) -> bool { pub(crate) fn is_lex_binding(&self, name: Identifier) -> bool {
if let Some(binding) = self.bindings.get(&name) { if let Some(binding) = self.bindings.get(&name) {
@ -123,6 +129,23 @@ impl CompileTimeEnvironment {
} }
} }
#[cfg(feature = "annex-b")]
/// Check if a binding name exists in a environment.
/// Stop when a function scope is reached.
pub(crate) fn has_binding_until_var(&self, name: Identifier) -> bool {
if self.function_scope {
return false;
}
if self.bindings.contains_key(&name) {
return true;
}
if let Some(outer) = &self.outer {
outer.borrow().has_binding_until_var(name)
} else {
false
}
}
/// Create a mutable binding. /// Create a mutable binding.
/// ///
/// If the binding is a function scope binding and this is a declarative environment, try the outer environment. /// If the binding is a function scope binding and this is a declarative environment, try the outer environment.
@ -236,6 +259,29 @@ impl CompileTimeEnvironment {
} }
} }
#[cfg(feature = "annex-b")]
/// Return the binding locator for a set operation on an existing var binding.
pub(crate) fn set_mutable_binding_var_recursive(&self, name: Identifier) -> BindingLocator {
if !self.is_function() {
return self.outer.as_ref().map_or_else(
|| BindingLocator::global(name),
|outer| outer.borrow().set_mutable_binding_var_recursive(name),
);
}
match self.bindings.get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(binding) if binding.strict => BindingLocator::mutate_immutable(name),
Some(_) => BindingLocator::silent(name),
None => self.outer.as_ref().map_or_else(
|| BindingLocator::global(name),
|outer| outer.borrow().set_mutable_binding_var_recursive(name),
),
}
}
/// Gets the outer environment of this environment. /// Gets the outer environment of this environment.
pub(crate) fn outer(&self) -> Option<Gc<GcRefCell<Self>>> { pub(crate) fn outer(&self) -> Option<Gc<GcRefCell<Self>>> {
self.outer.clone() self.outer.clone()

4
boa_engine/src/environments/runtime.rs

@ -613,13 +613,13 @@ impl DeclarativeEnvironmentStack {
} }
} }
/// Set the value of a declarative binding. /// Set the value of a lexical binding.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the environment or binding index are out of range. /// Panics if the environment or binding index are out of range.
#[track_caller] #[track_caller]
pub(crate) fn put_declarative_value( pub(crate) fn put_lexical_value(
&mut self, &mut self,
environment_index: usize, environment_index: usize,
binding_index: usize, binding_index: usize,

17
boa_engine/src/vm/code_block.rs

@ -330,12 +330,9 @@ impl CodeBlock {
self.functions[operand as usize].length self.functions[operand as usize].length
) )
} }
Opcode::DefInitArg Opcode::DefVar
| Opcode::DefVar
| Opcode::DefInitVar | Opcode::DefInitVar
| Opcode::DefLet | Opcode::PutLexicalValue
| Opcode::DefInitLet
| Opcode::DefInitConst
| Opcode::GetName | Opcode::GetName
| Opcode::GetLocator | Opcode::GetLocator
| Opcode::GetNameAndLocator | Opcode::GetNameAndLocator
@ -977,7 +974,7 @@ impl JsObject {
context context
.vm .vm
.environments .environments
.put_declarative_value(index, 0, class_object.into()); .put_lexical_value(index, 0, class_object.into());
last_env -= 1; last_env -= 1;
} }
@ -989,7 +986,7 @@ impl JsObject {
context context
.vm .vm
.environments .environments
.put_declarative_value(index, 0, self.clone().into()); .put_lexical_value(index, 0, self.clone().into());
last_env -= 1; last_env -= 1;
} }
@ -1023,7 +1020,7 @@ impl JsObject {
context, context,
) )
}; };
context.vm.environments.put_declarative_value( context.vm.environments.put_lexical_value(
binding.environment_index(), binding.environment_index(),
binding.binding_index(), binding.binding_index(),
arguments_obj.into(), arguments_obj.into(),
@ -1245,7 +1242,7 @@ impl JsObject {
context context
.vm .vm
.environments .environments
.put_declarative_value(index, 0, self.clone().into()); .put_lexical_value(index, 0, self.clone().into());
last_env -= 1; last_env -= 1;
} }
@ -1278,7 +1275,7 @@ impl JsObject {
context, context,
) )
}; };
context.vm.environments.put_declarative_value( context.vm.environments.put_lexical_value(
binding.environment_index(), binding.environment_index(),
binding.binding_index(), binding.binding_index(),
arguments_obj.into(), arguments_obj.into(),

7
boa_engine/src/vm/flowgraph/mod.rs

@ -405,12 +405,9 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::DefInitArg Opcode::DefVar
| Opcode::DefVar
| Opcode::DefInitVar | Opcode::DefInitVar
| Opcode::DefLet | Opcode::PutLexicalValue
| Opcode::DefInitLet
| Opcode::DefInitConst
| Opcode::GetName | Opcode::GetName
| Opcode::GetLocator | Opcode::GetLocator
| Opcode::GetNameAndLocator | Opcode::GetNameAndLocator

45
boa_engine/src/vm/opcode/define/mod.rs

@ -66,47 +66,22 @@ impl Operation for DefInitVar {
} }
} }
/// `DefLet` implements the Opcode Operation for `Opcode::DefLet` /// `PutLexicalValue` implements the Opcode Operation for `Opcode::PutLexicalValue`
/// ///
/// Operation: /// Operation:
/// - Declare `let` type variable. /// - Initialize a lexical binding.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct DefLet; pub(crate) struct PutLexicalValue;
impl Operation for DefLet { impl Operation for PutLexicalValue {
const NAME: &'static str = "DefLet"; const NAME: &'static str = "PutLexicalValue";
const INSTRUCTION: &'static str = "INST - DefLet"; const INSTRUCTION: &'static str = "INST - PutLexicalValue";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
let binding_locator = context.vm.frame().code_block.bindings[index as usize];
context.vm.environments.put_declarative_value(
binding_locator.environment_index(),
binding_locator.binding_index(),
JsValue::Undefined,
);
Ok(CompletionType::Normal)
}
}
macro_rules! implement_declaratives {
($name:ident, $doc_string:literal) => {
#[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")]
#[doc= "\n"]
#[doc="Operation:\n"]
#[doc= concat!(" - ", $doc_string)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct $name;
impl Operation for $name {
const NAME: &'static str = stringify!($name);
const INSTRUCTION: &'static str = stringify!("INST - " + $name);
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>(); let index = context.vm.read::<u32>();
let value = context.vm.pop(); let value = context.vm.pop();
let binding_locator = context.vm.frame().code_block.bindings[index as usize]; let binding_locator = context.vm.frame().code_block.bindings[index as usize];
context.vm.environments.put_declarative_value( context.vm.environments.put_lexical_value(
binding_locator.environment_index(), binding_locator.environment_index(),
binding_locator.binding_index(), binding_locator.binding_index(),
value, value,
@ -114,9 +89,3 @@ macro_rules! implement_declaratives {
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
};
}
implement_declaratives!(DefInitLet, "Declare and initialize `let` type variable");
implement_declaratives!(DefInitConst, "Declare and initialize `const` type variable");
implement_declaratives!(DefInitArg, "Declare and initialize function arguments");

26
boa_engine/src/vm/opcode/mod.rs

@ -638,13 +638,6 @@ generate_impl! {
/// Stack: value **=>** (ToNumeric(value)), (value - 1) /// Stack: value **=>** (ToNumeric(value)), (value - 1)
DecPost, DecPost,
/// Declare and initialize a function argument.
///
/// Operands: name_index: `u32`
///
/// Stack: value **=>**
DefInitArg,
/// Declare `var` type variable. /// Declare `var` type variable.
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
@ -659,26 +652,12 @@ generate_impl! {
/// Stack: value **=>** /// Stack: value **=>**
DefInitVar, DefInitVar,
/// Declare `let` type variable. /// Initialize a lexical binding.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefLet,
/// Declare and initialize `let` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: value **=>**
DefInitLet,
/// Declare and initialize `const` type variable.
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value **=>** /// Stack: value **=>**
DefInitConst, PutLexicalValue,
/// Find a binding on the environment chain and push its value. /// Find a binding on the environment chain and push its value.
/// ///
@ -1633,7 +1612,6 @@ generate_impl! {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) enum BindingOpcode { pub(crate) enum BindingOpcode {
Var, Var,
Let,
InitVar, InitVar,
InitLet, InitLet,
InitConst, InitConst,

Loading…
Cancel
Save