Browse Source

Decouple `Context` from `ByteCompiler` (#3829)

* Decouple GlobalDeclarationInstantiation from Context

* Decouple EvalDeclarationInstantiation from Context

* Remove `Context` from `ByteCompiler`

* Fix typo

* Apply review
pull/3832/head
Haled Odat 8 months ago committed by GitHub
parent
commit
6ee208fc5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 40
      core/engine/src/builtins/eval/mod.rs
  2. 2
      core/engine/src/builtins/function/mod.rs
  3. 2
      core/engine/src/builtins/json/mod.rs
  4. 10
      core/engine/src/bytecompiler/class.rs
  5. 707
      core/engine/src/bytecompiler/declarations.rs
  6. 7
      core/engine/src/bytecompiler/function.rs
  7. 36
      core/engine/src/bytecompiler/mod.rs
  8. 2
      core/engine/src/module/source.rs
  9. 2
      core/engine/src/module/synthetic.rs
  10. 21
      core/engine/src/script.rs
  11. 21
      core/engine/src/vm/code_block.rs
  12. 16
      core/engine/src/vm/flowgraph/mod.rs
  13. 38
      core/engine/src/vm/opcode/control_flow/throw.rs
  14. 53
      core/engine/src/vm/opcode/define/mod.rs
  15. 220
      core/engine/src/vm/opcode/global.rs
  16. 62
      core/engine/src/vm/opcode/mod.rs

40
core/engine/src/builtins/eval/mod.rs

@ -9,11 +9,13 @@
//! [spec]: https://tc39.es/ecma262/#sec-eval-x
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
use std::rc::Rc;
use crate::{
builtins::{function::OrdinaryFunction, BuiltInObject},
bytecompiler::ByteCompiler,
bytecompiler::{eval_declaration_instantiation_context, ByteCompiler},
context::intrinsics::Intrinsics,
environments::Environment,
environments::{CompileTimeEnvironment, Environment},
error::JsNativeError,
js_string,
object::JsObject,
@ -229,24 +231,48 @@ impl Eval {
let var_environment = context.vm.environments.outer_function_environment();
let mut var_env = var_environment.compile_env();
let lex_env = context.vm.environments.current_compile_environment();
let lex_env = Rc::new(CompileTimeEnvironment::new(lex_env, strict));
let mut annex_b_function_names = Vec::new();
eval_declaration_instantiation_context(
&mut annex_b_function_names,
&body,
strict,
if strict { &lex_env } else { &var_env },
&lex_env,
context,
)?;
let mut compiler = ByteCompiler::new(
js_string!("<main>"),
body.strict(),
false,
var_env.clone(),
context.vm.environments.current_compile_environment(),
context,
lex_env.clone(),
context.interner_mut(),
);
let env_index = compiler.push_compile_environment(strict);
compiler.current_open_environments_count += 1;
let env_index = compiler.constants.len() as u32;
compiler
.constants
.push(crate::vm::Constant::CompileTimeEnvironment(lex_env.clone()));
compiler.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
let lex_env = compiler.lexical_environment.clone();
if strict {
var_env = lex_env.clone();
compiler.variable_environment = lex_env.clone();
}
compiler.eval_declaration_instantiation(&body, strict, &var_env, &lex_env)?;
#[cfg(feature = "annex-b")]
{
compiler.annex_b_function_names = annex_b_function_names;
}
compiler.eval_declaration_instantiation(&body, strict, &var_env, &lex_env);
compiler.compile_statement_list(body.statements(), true, false);
let code_block = Gc::new(compiler.finish());

2
core/engine/src/builtins/function/mod.rs

@ -624,7 +624,7 @@ impl BuiltInFunctionObject {
&body,
context.realm().environment().compile_env(),
context.realm().environment().compile_env(),
context,
context.interner_mut(),
);
let environments = context.vm.environments.pop_to_global();

2
core/engine/src/builtins/json/mod.rs

@ -118,7 +118,7 @@ impl Json {
true,
context.realm().environment().compile_env(),
context.realm().environment().compile_env(),
context,
context.interner_mut(),
);
compiler.compile_statement_list(script.statements(), true, false);
Gc::new(compiler.finish())

10
core/engine/src/bytecompiler/class.rs

@ -54,7 +54,7 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
compiler.code_block_flags |= CodeBlockFlags::IS_CLASS_CONSTRUCTOR;
@ -287,7 +287,7 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
// Function environment
@ -315,7 +315,7 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
let _ = field_compiler.push_compile_environment(true);
if let Some(node) = field {
@ -353,7 +353,7 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
let _ = field_compiler.push_compile_environment(true);
if let Some(node) = field {
@ -387,7 +387,7 @@ impl ByteCompiler<'_> {
false,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
let _ = compiler.push_compile_environment(true);

707
core/engine/src/bytecompiler/declarations.rs

@ -3,11 +3,12 @@ use std::rc::Rc;
use crate::{
bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, NodeKind},
environments::CompileTimeEnvironment,
vm::{create_function_object_fast, BindingOpcode, Opcode},
JsNativeError, JsResult,
vm::{BindingOpcode, Opcode},
Context, JsNativeError, JsResult,
};
use boa_ast::{
declaration::{Binding, LexicalDeclaration, VariableList},
expression::Identifier,
function::{FormalParameterList, FunctionBody},
operations::{
all_private_identifiers_valid, bound_names, lexically_declared_names,
@ -24,6 +25,361 @@ use boa_ast::operations::annex_b_function_declarations_names;
use super::{Operand, ToJsString};
/// `GlobalDeclarationInstantiation ( script, env )`
///
/// This diverges from the specification by separating the context from the compilation process.
/// Many steps are skipped that are done during bytecode compilation.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
#[cfg(not(feature = "annex-b"))]
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::ptr_arg)]
pub(crate) fn global_declaration_instantiation_context(
_annex_b_function_names: &mut Vec<Identifier>,
_script: &Script,
_env: &Rc<CompileTimeEnvironment>,
_context: &mut Context,
) -> JsResult<()> {
Ok(())
}
/// `GlobalDeclarationInstantiation ( script, env )`
///
/// This diverges from the specification by separating the context from the compilation process.
/// Many steps are skipped that are done during bytecode compilation.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
#[cfg(feature = "annex-b")]
pub(crate) fn global_declaration_instantiation_context(
annex_b_function_names: &mut Vec<Identifier>,
script: &Script,
env: &Rc<CompileTimeEnvironment>,
context: &mut Context,
) -> JsResult<()> {
// SKIP: 1. Let lexNames be the LexicallyDeclaredNames of script.
// SKIP: 2. Let varNames be the VarDeclaredNames of script.
// SKIP: 3. For each element name of lexNames, do
// SKIP: 4. For each element name of varNames, do
// 5. Let varDeclarations be the VarScopedDeclarations of script.
// Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations.
let var_declarations = var_scoped_declarations(script);
// SKIP: 6. Let functionsToInitialize be a new empty List.
// 7. Let declaredFunctionNames be a new empty List.
let mut declared_function_names = Vec::new();
// 8. For each element d of varDeclarations, in reverse List order, do
for declaration in var_declarations.iter().rev() {
// a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
// a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
let name = match declaration {
VarScopedDeclaration::Function(f) => f.name(),
VarScopedDeclaration::Generator(f) => f.name(),
VarScopedDeclaration::AsyncFunction(f) => f.name(),
VarScopedDeclaration::AsyncGenerator(f) => f.name(),
VarScopedDeclaration::VariableDeclaration(_) => {
continue;
}
};
// a.iii. Let fn be the sole element of the BoundNames of d.
let name = name.expect("function declaration must have a name");
// a.iv. If declaredFunctionNames does not contain fn, then
if !declared_function_names.contains(&name) {
// SKIP: 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
// SKIP: 2. If fnDefinable is false, throw a TypeError exception.
// 3. Append fn to declaredFunctionNames.
declared_function_names.push(name);
// SKIP: 4. Insert d as the first element of functionsToInitialize.
}
}
// // 9. Let declaredVarNames be a new empty List.
let mut declared_var_names = Vec::new();
// 10. For each element d of varDeclarations, do
// a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
for declaration in var_declarations {
let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else {
continue;
};
// i. For each String vn of the BoundNames of d, do
for name in bound_names(&declaration) {
// 1. If declaredFunctionNames does not contain vn, then
if !declared_function_names.contains(&name) {
// SKIP: a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
// SKIP: b. If vnDefinable is false, throw a TypeError exception.
// c. If declaredVarNames does not contain vn, then
if !declared_var_names.contains(&name) {
// i. Append vn to declaredVarNames.
declared_var_names.push(name);
}
}
}
}
// 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
// that cause abnormal terminations in some of the following steps.
// 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// 12. Perform the following steps:
// a. Let strict be IsStrict of script.
// b. If strict is false, then
if !script.strict() {
let lex_names = 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) {
let f_string = f.to_js_string(context.interner());
// a. If env.HasLexicalDeclaration(F) is false, then
if !env.has_lex_binding(&f_string) {
// i. Let fnDefinable be ? env.CanDeclareGlobalVar(F).
let fn_definable = context.can_declare_global_function(&f_string)?;
// 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).
context.create_global_var_binding(f_string, 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.
annex_b_function_names.push(f);
}
}
}
}
}
// SKIP: 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// SKIP: 14. Let privateEnv be null.
// SKIP: 15. For each element d of lexDeclarations, do
// SKIP: 16. For each Parse Node f of functionsToInitialize, do
// SKIP: 17. For each String vn of declaredVarNames, do
// 18. Return unused.
Ok(())
}
/// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )`
///
/// This diverges from the specification by separating the context from the compilation process.
/// Many steps are skipped that are done during bytecode compilation.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
pub(crate) fn eval_declaration_instantiation_context(
#[allow(unused, clippy::ptr_arg)] annex_b_function_names: &mut Vec<Identifier>,
body: &Script,
#[allow(unused)] strict: bool,
#[allow(unused)] var_env: &Rc<CompileTimeEnvironment>,
#[allow(unused)] lex_env: &Rc<CompileTimeEnvironment>,
context: &mut Context,
) -> JsResult<()> {
// SKIP: 3. If strict is false, then
// 4. Let privateIdentifiers be a new empty List.
// 5. Let pointer be privateEnv.
// 6. Repeat, while pointer is not null,
// a. For each Private Name binding of pointer.[[Names]], do
// i. If privateIdentifiers does not contain binding.[[Description]],
// append binding.[[Description]] to privateIdentifiers.
// b. Set pointer to pointer.[[OuterPrivateEnvironment]].
let private_identifiers = context.vm.environments.private_name_descriptions();
let private_identifiers = private_identifiers
.into_iter()
.map(|ident| {
context
.interner()
.get(ident.as_slice())
.expect("string should be in interner")
})
.collect();
// 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception.
if !all_private_identifiers_valid(body, private_identifiers) {
return Err(JsNativeError::syntax()
.with_message("invalid private identifier")
.into());
}
// 2. Let varDeclarations be the VarScopedDeclarations of body.
#[cfg(feature = "annex-b")]
let var_declarations = var_scoped_declarations(body);
// SKIP: 8. Let functionsToInitialize be a new empty List.
// 9. Let declaredFunctionNames be a new empty List.
#[cfg(feature = "annex-b")]
let mut declared_function_names = Vec::new();
// 10. For each element d of varDeclarations, in reverse List order, do
#[cfg(feature = "annex-b")]
for declaration in var_declarations.iter().rev() {
// a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
// a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
let name = match &declaration {
VarScopedDeclaration::Function(f) => f.name(),
VarScopedDeclaration::Generator(f) => f.name(),
VarScopedDeclaration::AsyncFunction(f) => f.name(),
VarScopedDeclaration::AsyncGenerator(f) => f.name(),
VarScopedDeclaration::VariableDeclaration(_) => {
continue;
}
};
// a.iii. Let fn be the sole element of the BoundNames of d.
let name = name.expect("function declaration must have a name");
// a.iv. If declaredFunctionNames does not contain fn, then
if !declared_function_names.contains(&name) {
// SKIP: 1. If varEnv is a Global Environment Record, then
// 2. Append fn to declaredFunctionNames.
declared_function_names.push(name);
// SKIP: 3. Insert d as the first element of functionsToInitialize.
}
}
// 11. NOTE: Annex B.3.2.3 adds additional steps at this point.
// 11. If strict is false, then
#[cfg(feature = "annex-b")]
if !strict {
let lexically_declared_names = 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.
let mut binding_exists = false;
// 2. Let thisEnv be lexEnv.
let mut this_env = lex_env.clone();
// 3. Assert: The following loop will terminate.
// 4. Repeat, while thisEnv is not varEnv,
while this_env.environment_index() != lex_env.environment_index() {
let f = f.to_js_string(context.interner());
// a. If thisEnv is not an Object Environment Record, then
// i. If ! thisEnv.HasBinding(F) is true, then
if this_env.has_binding(&f) {
// i. Let bindingExists be true.
binding_exists = true;
break;
}
// b. Set thisEnv to thisEnv.[[OuterEnv]].
if let Some(outer) = this_env.outer() {
this_env = outer;
} else {
break;
}
}
// 5. If bindingExists is false and varEnv is a Global Environment Record, then
let fn_definable = if !binding_exists && var_env.is_global() {
let f = f.to_js_string(context.interner());
// a. If varEnv.HasLexicalDeclaration(F) is false, then
// b. Else,
if var_env.has_lex_binding(&f) {
// i. Let fnDefinable be false.
false
} else {
// i. Let fnDefinable be ? varEnv.CanDeclareGlobalVar(F).
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 {
// a. If declaredFunctionOrVarNames does not contain F, then
if !declared_function_names.contains(&f) {
// i. If varEnv is a Global Environment Record, then
if var_env.is_global() {
let f = f.to_js_string(context.interner());
// i. Perform ? varEnv.CreateGlobalVarBinding(F, true).
context.create_global_var_binding(f, true)?;
}
// SKIP: ii. Else,
// SKIP: iii. Append F to declaredFunctionOrVarNames.
}
// 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.
annex_b_function_names.push(f);
}
}
}
}
// SKIP: 12. Let declaredVarNames be a new empty List.
// SKIP: 13. For each element d of varDeclarations, do
// SKIP: 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a
// Global Environment Record and the global object is a Proxy exotic object.
// SKIP: 15. Let lexDeclarations be the LexicallyScopedDeclarations of body.
// SKIP: 16. For each element d of lexDeclarations, do
// SKIP: 17. For each Parse Node f of functionsToInitialize, do
// SKIP: 18. For each String vn of declaredVarNames, do
// 19. Return unused.
Ok(())
}
impl ByteCompiler<'_> {
/// `GlobalDeclarationInstantiation ( script, env )`
///
@ -35,7 +391,7 @@ impl ByteCompiler<'_> {
&mut self,
script: &Script,
env: &Rc<CompileTimeEnvironment>,
) -> JsResult<()> {
) {
// 1. Let lexNames be the LexicallyDeclaredNames of script.
let lex_names = lexically_declared_names(script);
@ -44,47 +400,34 @@ impl ByteCompiler<'_> {
// 3. For each element name of lexNames, do
for name in lex_names {
let name = self
.context
.interner()
.resolve_expect(name.sym())
.utf16()
.into();
let name = name.to_js_string(self.interner());
// Note: Our implementation differs from the spec here.
// a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception.
// b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if env.has_binding(&name) {
return Err(JsNativeError::syntax()
.with_message("duplicate lexical declaration")
.into());
self.emit_syntax_error("duplicate lexical declaration");
return;
}
// c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name).
let has_restricted_global = self.context.has_restricted_global_property(&name)?;
let index = self.get_or_insert_string(name);
self.emit_with_varying_operand(Opcode::HasRestrictedGlobalProperty, index);
// d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
if has_restricted_global {
return Err(JsNativeError::syntax()
.with_message("cannot redefine non-configurable global property")
.into());
}
let exit = self.jump_if_false();
self.emit_syntax_error("cannot redefine non-configurable global property");
self.patch_jump(exit);
}
// 4. For each element name of varNames, do
for name in var_names {
let name = self
.context
.interner()
.resolve_expect(name.sym())
.utf16()
.into();
let name = name.to_js_string(self.interner());
// a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if env.has_lex_binding(&name) {
return Err(JsNativeError::syntax()
.with_message("duplicate lexical declaration")
.into());
self.emit_syntax_error("duplicate lexical declaration");
return;
}
}
@ -119,16 +462,13 @@ impl ByteCompiler<'_> {
// a.iv. If declaredFunctionNames does not contain fn, then
if !declared_function_names.contains(&name) {
// 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
let fn_definable = self
.context
.can_declare_global_function(&name.to_js_string(self.interner()))?;
let index = self.get_or_insert_name(name);
self.emit_with_varying_operand(Opcode::CanDeclareGlobalFunction, index);
// 2. If fnDefinable is false, throw a TypeError exception.
if !fn_definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global function")
.into());
}
let exit = self.jump_if_true();
self.emit_type_error("cannot declare global function");
self.patch_jump(exit);
// 3. Append fn to declaredFunctionNames.
declared_function_names.push(name);
@ -155,16 +495,13 @@ impl ByteCompiler<'_> {
// 1. If declaredFunctionNames does not contain vn, then
if !declared_function_names.contains(&name) {
// a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
let definable = self
.context
.can_declare_global_var(&name.to_js_string(self.interner()))?;
let index = self.get_or_insert_name(name);
self.emit_with_varying_operand(Opcode::CanDeclareGlobalVar, index);
// b. If vnDefinable is false, throw a TypeError exception.
if !definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global variable")
.into());
}
let exit = self.jump_if_true();
self.emit_type_error("cannot declare global variable");
self.patch_jump(exit);
// c. If declaredVarNames does not contain vn, then
if !declared_var_names.contains(&name) {
@ -175,60 +512,15 @@ impl ByteCompiler<'_> {
}
}
// 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object.
// NOTE: These steps depend on the global object are done before bytecode compilation.
//
// SKIP: 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
// that cause abnormal terminations in some of the following steps.
// 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// 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 = 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) {
let f_string = self.resolve_identifier_expect(f);
// a. If env.HasLexicalDeclaration(F) is false, then
if !env.has_lex_binding(&f_string) {
// i. Let fnDefinable be ? env.CanDeclareGlobalVar(F).
let fn_definable = self.context.can_declare_global_function(&f_string)?;
// 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_string, 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);
}
}
}
}
}
// SKIP: 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// SKIP: 12. Perform the following steps:
// SKIP: a. Let strict be IsStrict of script.
// SKIP: b. If strict is false, then
// 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// 14. Let privateEnv be null.
@ -298,30 +590,34 @@ impl ByteCompiler<'_> {
body,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
// Ensures global functions are printed when generating the global flowgraph.
let _ = self.push_function_to_constants(code.clone());
let function_index = self.push_function_to_constants(code.clone());
// b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
let function = create_function_object_fast(code, self.context);
self.emit_with_varying_operand(Opcode::GetFunction, function_index);
// c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
let name = name.to_js_string(self.interner());
self.context
.create_global_function_binding(name, function, false)?;
let name_index = self.get_or_insert_name(name);
self.emit(
Opcode::CreateGlobalFunctionBinding,
&[Operand::Bool(false), Operand::Varying(name_index)],
);
}
// 17. For each String vn of declaredVarNames, do
for var in declared_var_names {
// a. Perform ? env.CreateGlobalVarBinding(vn, false).
let var = var.to_js_string(self.interner());
self.context.create_global_var_binding(var, false)?;
let index = self.get_or_insert_name(var);
self.emit(
Opcode::CreateGlobalVarBinding,
&[Operand::Bool(false), Operand::Varying(index)],
);
}
// 18. Return unused.
Ok(())
}
/// `BlockDeclarationInstantiation ( code, env )`
@ -416,7 +712,7 @@ impl ByteCompiler<'_> {
strict: bool,
var_env: &Rc<CompileTimeEnvironment>,
lex_env: &Rc<CompileTimeEnvironment>,
) -> JsResult<()> {
) {
// 2. Let varDeclarations be the VarScopedDeclarations of body.
let var_declarations = var_scoped_declarations(body);
@ -434,9 +730,8 @@ impl ByteCompiler<'_> {
// 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
// 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration.
if var_env.has_lex_binding(&name) {
return Err(JsNativeError::syntax()
.with_message("duplicate lexical declaration")
.into());
self.emit_syntax_error("duplicate lexical declaration");
return;
}
}
}
@ -452,19 +747,15 @@ impl ByteCompiler<'_> {
// declaration so it doesn't need to be checked for var/let hoisting conflicts.
// 2. For each element name of varNames, do
for name in &var_names {
let name = self
.context
.interner()
.resolve_expect(name.sym())
.utf16()
.into();
let name = self.interner().resolve_expect(name.sym()).utf16().into();
// a. If ! thisEnv.HasBinding(name) is true, then
if this_env.has_binding(&name) {
// i. Throw a SyntaxError exception.
// ii. NOTE: Annex B.3.4 defines alternate semantics for the above step.
let msg = format!("variable declaration {} in eval function already exists as a lexical variable", name.to_std_string_escaped());
return Err(JsNativeError::syntax().with_message(msg).into());
self.emit_syntax_error(&msg);
return;
}
// b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration.
}
@ -478,30 +769,17 @@ impl ByteCompiler<'_> {
}
}
// 4. Let privateIdentifiers be a new empty List.
// 5. Let pointer be privateEnv.
// 6. Repeat, while pointer is not null,
// a. For each Private Name binding of pointer.[[Names]], do
// i. If privateIdentifiers does not contain binding.[[Description]],
// append binding.[[Description]] to privateIdentifiers.
// b. Set pointer to pointer.[[OuterPrivateEnvironment]].
let private_identifiers = self.context.vm.environments.private_name_descriptions();
let private_identifiers = private_identifiers
.into_iter()
.map(|ident| {
self.context
.interner()
.get(ident.as_slice())
.expect("string should be in interner")
})
.collect();
// 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception.
if !all_private_identifiers_valid(body, private_identifiers) {
return Err(JsNativeError::syntax()
.with_message("invalid private identifier")
.into());
}
// NOTE: These steps depend on the current environment state are done before bytecode compilation,
// in `eval_declaration_instantiation_context`.
//
// SKIP: 4. Let privateIdentifiers be a new empty List.
// SKIP: 5. Let pointer be privateEnv.
// SKIP: 6. Repeat, while pointer is not null,
// a. For each Private Name binding of pointer.[[Names]], do
// i. If privateIdentifiers does not contain binding.[[Description]],
// append binding.[[Description]] to privateIdentifiers.
// b. Set pointer to pointer.[[OuterPrivateEnvironment]].
// SKIP: 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception.
// 8. Let functionsToInitialize be a new empty List.
let mut functions_to_initialize = Vec::new();
@ -531,17 +809,15 @@ impl ByteCompiler<'_> {
if !declared_function_names.contains(&name) {
// 1. If varEnv is a Global Environment Record, then
if var_env.is_global() {
let name = name.to_js_string(self.interner());
let index = self.get_or_insert_name(name);
// a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn).
let fn_definable = self.context.can_declare_global_function(&name)?;
self.emit_with_varying_operand(Opcode::CanDeclareGlobalFunction, index);
// b. If fnDefinable is false, throw a TypeError exception.
if !fn_definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global function")
.into());
}
let exit = self.jump_if_true();
self.emit_type_error("cannot declare global function");
self.patch_jump(exit);
}
// 2. Append fn to declaredFunctionNames.
@ -558,105 +834,20 @@ impl ByteCompiler<'_> {
// 11. If strict is false, then
#[cfg(feature = "annex-b")]
if !strict {
let lexically_declared_names = 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.
let mut binding_exists = false;
// 2. Let thisEnv be lexEnv.
let mut this_env = lex_env.clone();
// 3. Assert: The following loop will terminate.
// 4. Repeat, while thisEnv is not varEnv,
while this_env.environment_index() != lex_env.environment_index() {
let f = f.to_js_string(self.interner());
// a. If thisEnv is not an Object Environment Record, then
// i. If ! thisEnv.HasBinding(F) is true, then
if this_env.has_binding(&f) {
// i. Let bindingExists be true.
binding_exists = true;
break;
}
// b. Set thisEnv to thisEnv.[[OuterEnv]].
if let Some(outer) = this_env.outer() {
this_env = outer;
} else {
break;
}
}
// 5. If bindingExists is false and varEnv is a Global Environment Record, then
let fn_definable = if !binding_exists && var_env.is_global() {
let f = f.to_js_string(self.interner());
// a. If varEnv.HasLexicalDeclaration(F) is false, then
// b. Else,
if self.variable_environment.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_env.is_global() {
let f = f.to_js_string(self.interner());
// i. Perform ? varEnv.CreateGlobalVarBinding(F, true).
self.context.create_global_var_binding(f, true)?;
}
// ii. Else,
else {
let f = f.to_js_string(self.interner());
// i. Let bindingExists be ! varEnv.HasBinding(F).
// ii. If bindingExists is false, then
if !var_env.has_binding(&f) {
// i. Perform ! varEnv.CreateMutableBinding(F, true).
// ii. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = var_env.create_mutable_binding(f, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit_with_varying_operand(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);
// NOTE: This diviates from the specification, we split the first part of defining the annex-b names
// in `eval_declaration_instantiation_context`, because it depends on the context.
if !var_env.is_global() {
for name in self.annex_b_function_names.clone() {
let f = name.to_js_string(self.interner());
// i. Let bindingExists be ! varEnv.HasBinding(F).
// ii. If bindingExists is false, then
if !var_env.has_binding(&f) {
// i. Perform ! varEnv.CreateMutableBinding(F, true).
// ii. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = var_env.create_mutable_binding(f, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit_with_varying_operand(Opcode::DefInitVar, index);
}
}
}
@ -678,17 +869,15 @@ impl ByteCompiler<'_> {
if !declared_function_names.contains(&name) {
// a. If varEnv is a Global Environment Record, then
if var_env.is_global() {
let name = name.to_js_string(self.interner());
let index = self.get_or_insert_name(name);
// i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn).
let vn_definable = self.context.can_declare_global_var(&name)?;
self.emit_with_varying_operand(Opcode::CanDeclareGlobalVar, index);
// ii. If vnDefinable is false, throw a TypeError exception.
if !vn_definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global variable")
.into());
}
let exit = self.jump_if_true();
self.emit_type_error("cannot declare global function");
self.patch_jump(exit);
}
// b. If declaredVarNames does not contain vn, then
@ -769,7 +958,7 @@ impl ByteCompiler<'_> {
body,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
// c. If varEnv is a Global Environment Record, then
@ -818,15 +1007,20 @@ impl ByteCompiler<'_> {
// 18. For each String vn of declaredVarNames, do
for name in declared_var_names {
let name = name.to_js_string(self.interner());
// a. If varEnv is a Global Environment Record, then
if var_env.is_global() {
let index = self.get_or_insert_name(name);
// i. Perform ? varEnv.CreateGlobalVarBinding(vn, true).
self.context.create_global_var_binding(name, true)?;
self.emit(
Opcode::CreateGlobalVarBinding,
&[Operand::Bool(true), Operand::Varying(index)],
);
}
// b. Else,
else {
let name = name.to_js_string(self.interner());
// i. Let bindingExists be ! varEnv.HasBinding(vn).
let binding_exists = var_env.has_binding(&name);
@ -844,7 +1038,6 @@ impl ByteCompiler<'_> {
}
// 19. Return unused.
Ok(())
}
/// `FunctionDeclarationInstantiation ( func, argumentsList )`

7
core/engine/src/bytecompiler/function.rs

@ -6,10 +6,11 @@ use crate::{
environments::CompileTimeEnvironment,
js_string,
vm::{CodeBlock, CodeBlockFlags, Opcode},
Context, JsString,
JsString,
};
use boa_ast::function::{FormalParameterList, FunctionBody};
use boa_gc::Gc;
use boa_interner::Interner;
/// `FunctionCompiler` is used to compile AST functions to bytecode.
#[derive(Debug, Clone)]
@ -91,7 +92,7 @@ impl FunctionCompiler {
body: &FunctionBody,
variable_environment: Rc<CompileTimeEnvironment>,
lexical_environment: Rc<CompileTimeEnvironment>,
context: &mut Context,
interner: &mut Interner,
) -> Gc<CodeBlock> {
self.strict = self.strict || body.strict();
@ -103,7 +104,7 @@ impl FunctionCompiler {
false,
variable_environment,
lexical_environment,
context,
interner,
);
compiler.length = length;
compiler

36
core/engine/src/bytecompiler/mod.rs

@ -21,7 +21,7 @@ use crate::{
BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler,
InlineCache, Opcode, VaryingOperandKind,
},
Context, JsBigInt, JsString,
JsBigInt, JsString,
};
use boa_ast::{
declaration::{Binding, LexicalDeclaration, VarDeclaration},
@ -41,10 +41,13 @@ use boa_ast::{
use boa_gc::Gc;
use boa_interner::{Interner, Sym};
use rustc_hash::FxHashMap;
use thin_vec::ThinVec;
pub(crate) use declarations::{
eval_declaration_instantiation_context, global_declaration_instantiation_context,
};
pub(crate) use function::FunctionCompiler;
pub(crate) use jump_control::JumpControlInfo;
use thin_vec::ThinVec;
pub(crate) trait ToJsString {
fn to_js_string(&self, interner: &Interner) -> JsString;
@ -277,7 +280,7 @@ pub struct ByteCompiler<'ctx> {
/// The current lexical environment.
pub(crate) lexical_environment: Rc<CompileTimeEnvironment>,
current_open_environments_count: u32,
pub(crate) current_open_environments_count: u32,
current_stack_value_count: u32,
pub(crate) code_block_flags: CodeBlockFlags,
handlers: ThinVec<Handler>,
@ -293,11 +296,10 @@ pub struct ByteCompiler<'ctx> {
pub(crate) async_handler: Option<u32>,
json_parse: bool,
// TODO: remove when we separate scripts from the context
pub(crate) context: &'ctx mut Context,
pub(crate) interner: &'ctx mut Interner,
#[cfg(feature = "annex-b")]
annex_b_function_names: Vec<Identifier>,
pub(crate) annex_b_function_names: Vec<Identifier>,
}
impl<'ctx> ByteCompiler<'ctx> {
@ -313,8 +315,7 @@ impl<'ctx> ByteCompiler<'ctx> {
json_parse: bool,
variable_environment: Rc<CompileTimeEnvironment>,
lexical_environment: Rc<CompileTimeEnvironment>,
// TODO: remove when we separate scripts from the context
context: &'ctx mut Context,
interner: &'ctx mut Interner,
) -> ByteCompiler<'ctx> {
let mut code_block_flags = CodeBlockFlags::empty();
code_block_flags.set(CodeBlockFlags::STRICT, strict);
@ -343,7 +344,7 @@ impl<'ctx> ByteCompiler<'ctx> {
json_parse,
variable_environment,
lexical_environment,
context,
interner,
#[cfg(feature = "annex-b")]
annex_b_function_names: Vec::new(),
@ -367,7 +368,7 @@ impl<'ctx> ByteCompiler<'ctx> {
}
pub(crate) fn interner(&self) -> &Interner {
self.context.interner()
self.interner
}
fn get_or_insert_literal(&mut self, literal: Literal) -> u32 {
@ -568,6 +569,15 @@ impl<'ctx> ByteCompiler<'ctx> {
self.emit_with_varying_operand(Opcode::SetPropertyByName, ic_index);
}
fn emit_type_error(&mut self, message: &str) {
let error_msg = self.get_or_insert_literal(Literal::String(js_string!(message)));
self.emit_with_varying_operand(Opcode::ThrowNewTypeError, error_msg);
}
fn emit_syntax_error(&mut self, message: &str) {
let error_msg = self.get_or_insert_literal(Literal::String(js_string!(message)));
self.emit_with_varying_operand(Opcode::ThrowNewSyntaxError, error_msg);
}
fn emit_u64(&mut self, value: u64) {
self.bytecode.extend(value.to_ne_bytes());
}
@ -1308,7 +1318,7 @@ impl<'ctx> ByteCompiler<'ctx> {
body,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
self.push_function_to_constants(code)
@ -1383,7 +1393,7 @@ impl<'ctx> ByteCompiler<'ctx> {
body,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
let index = self.push_function_to_constants(code);
@ -1430,7 +1440,7 @@ impl<'ctx> ByteCompiler<'ctx> {
body,
self.variable_environment.clone(),
self.lexical_environment.clone(),
self.context,
self.interner,
);
let index = self.push_function_to_constants(code);

2
core/engine/src/module/source.rs

@ -1432,7 +1432,7 @@ impl SourceTextModule {
false,
env.clone(),
env.clone(),
context,
context.interner_mut(),
);
compiler.code_block_flags |= CodeBlockFlags::IS_ASYNC;

2
core/engine/src/module/synthetic.rs

@ -286,7 +286,7 @@ impl SyntheticModule {
false,
module_compile_env.clone(),
module_compile_env.clone(),
context,
context.interner_mut(),
);
// 4. For each String exportName in module.[[ExportNames]], do

21
core/engine/src/script.rs

@ -17,7 +17,7 @@ use boa_parser::{source::ReadChar, Parser, Source};
use boa_profiler::Profiler;
use crate::{
bytecompiler::ByteCompiler,
bytecompiler::{global_declaration_instantiation_context, ByteCompiler},
js_string,
realm::Realm,
vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock},
@ -119,19 +119,34 @@ impl Script {
let _timer = Profiler::global().start_event("Script compilation", "Main");
let mut annex_b_function_names = Vec::new();
global_declaration_instantiation_context(
&mut annex_b_function_names,
&self.inner.source,
&self.inner.realm.environment().compile_env(),
context,
)?;
let mut compiler = ByteCompiler::new(
js_string!("<main>"),
self.inner.source.strict(),
false,
self.inner.realm.environment().compile_env(),
self.inner.realm.environment().compile_env(),
context,
context.interner_mut(),
);
#[cfg(feature = "annex-b")]
{
compiler.annex_b_function_names = annex_b_function_names;
}
// TODO: move to `Script::evaluate` to make this operation infallible.
compiler.global_declaration_instantiation(
&self.inner.source,
&self.inner.realm.environment().compile_env(),
)?;
);
compiler.compile_statement_list(self.inner.source.statements(), true, false);
let cb = Gc::new(compiler.finish());

21
core/engine/src/vm/code_block.rs

@ -385,7 +385,11 @@ impl CodeBlock {
Instruction::PushFloat { value } => ryu_js::Buffer::new().format(*value).to_string(),
Instruction::PushDouble { value } => ryu_js::Buffer::new().format(*value).to_string(),
Instruction::PushLiteral { index }
| Instruction::ThrowNewTypeError { message: index } => index.value().to_string(),
| Instruction::ThrowNewTypeError { message: index }
| Instruction::ThrowNewSyntaxError { message: index }
| Instruction::HasRestrictedGlobalProperty { index }
| Instruction::CanDeclareGlobalFunction { index }
| Instruction::CanDeclareGlobalVar { index } => index.value().to_string(),
Instruction::PushRegExp {
pattern_index: source_index,
flags_index: flag_index,
@ -526,11 +530,15 @@ impl CodeBlock {
format!("done: {done}")
}
Instruction::CreateGlobalFunctionBinding {
name_index,
index,
configurable,
}
| Instruction::CreateGlobalVarBinding {
index,
configurable,
} => {
let name = self
.constant_string(name_index.value() as usize)
.constant_string(index.value() as usize)
.to_std_string_escaped();
format!("name: {name}, configurable: {configurable}")
}
@ -712,12 +720,7 @@ impl CodeBlock {
| Instruction::Reserved51
| Instruction::Reserved52
| Instruction::Reserved53
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58
| Instruction::Reserved59 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved54 => unreachable!("Reserved opcodes are unrechable"),
}
}
}

16
core/engine/src/vm/flowgraph/mod.rs

@ -72,7 +72,11 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Instruction::PushLiteral { .. } | Instruction::PushRegExp { .. } => {
Instruction::PushLiteral { .. }
| Instruction::PushRegExp { .. }
| Instruction::HasRestrictedGlobalProperty { .. }
| Instruction::CanDeclareGlobalFunction { .. }
| Instruction::CanDeclareGlobalVar { .. } => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
@ -281,7 +285,7 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Instruction::ThrowNewTypeError { .. } => {
Instruction::ThrowNewTypeError { .. } | Instruction::ThrowNewSyntaxError { .. } => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
graph.add_edge(
@ -449,6 +453,7 @@ impl CodeBlock {
| Instruction::CreateMappedArgumentsObject
| Instruction::CreateUnmappedArgumentsObject
| Instruction::CreateGlobalFunctionBinding { .. }
| Instruction::CreateGlobalVarBinding { .. }
| Instruction::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
@ -511,12 +516,7 @@ impl CodeBlock {
| Instruction::Reserved51
| Instruction::Reserved52
| Instruction::Reserved53
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58
| Instruction::Reserved59 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved54 => unreachable!("Reserved opcodes are unrechable"),
}
}

38
core/engine/src/vm/opcode/control_flow/throw.rs

@ -151,3 +151,41 @@ impl Operation for ThrowNewTypeError {
Self::operation(context, index)
}
}
/// `ThrowNewSyntaxError` implements the Opcode Operation for `Opcode::ThrowNewSyntaxError`
///
/// Operation:
/// - Throws a `SyntaxError` exception.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ThrowNewSyntaxError;
impl ThrowNewSyntaxError {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let msg = context.vm.frame().code_block().constant_string(index);
let msg = msg
.to_std_string()
.expect("throw message must be an ASCII string");
Err(JsNativeError::syntax().with_message(msg).into())
}
}
impl Operation for ThrowNewSyntaxError {
const NAME: &'static str = "ThrowNewSyntaxError";
const INSTRUCTION: &'static str = "INST - ThrowNewSyntaxError";
const COST: u8 = 2;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}

53
core/engine/src/vm/opcode/define/mod.rs

@ -137,56 +137,3 @@ impl Operation for PutLexicalValue {
Self::operation(context, index as usize)
}
}
/// `CreateGlobalFunctionBinding` implements the Opcode Operation for `Opcode::CreateGlobalFunctionBinding`
///
/// Operation:
/// - Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateGlobalFunctionBinding;
impl CreateGlobalFunctionBinding {
#[allow(clippy::unnecessary_wraps)]
fn operation(
context: &mut Context,
index: usize,
configurable: bool,
) -> JsResult<CompletionType> {
let name = context.vm.frame().code_block().constant_string(index);
let value = context.vm.pop();
let function = value
.as_object()
.expect("valeu should be an function")
.clone();
context.create_global_function_binding(name, function, configurable)?;
Ok(CompletionType::Normal)
}
}
impl Operation for CreateGlobalFunctionBinding {
const NAME: &'static str = "CreateGlobalFunctionBinding";
const INSTRUCTION: &'static str = "INST - CreateGlobalFunctionBinding";
const COST: u8 = 2;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index, configurable)
}
}

220
core/engine/src/vm/opcode/global.rs

@ -0,0 +1,220 @@
use crate::{vm::CompletionType, Context, JsResult};
use super::Operation;
/// `HasRestrictedGlobalProperty` implements the Opcode Operation for `Opcode::HasRestrictedGlobalProperty`
///
/// Operation:
/// - Performs [`HasRestrictedGlobalProperty ( N )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
#[derive(Debug, Clone, Copy)]
pub(crate) struct HasRestrictedGlobalProperty;
impl HasRestrictedGlobalProperty {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let name = &context.vm.frame().code_block().constant_string(index);
let value = context.has_restricted_global_property(name)?;
context.vm.push(value);
Ok(CompletionType::Normal)
}
}
impl Operation for HasRestrictedGlobalProperty {
const NAME: &'static str = "HasRestrictedGlobalProperty";
const INSTRUCTION: &'static str = "INST - HasRestrictedGlobalProperty";
const COST: u8 = 4;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}
/// `CanDeclareGlobalFunction` implements the Opcode Operation for `Opcode::CanDeclareGlobalFunction`
///
/// Operation:
/// - Performs [`CanDeclareGlobalFunction ( N )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction
#[derive(Debug, Clone, Copy)]
pub(crate) struct CanDeclareGlobalFunction;
impl CanDeclareGlobalFunction {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let name = &context.vm.frame().code_block().constant_string(index);
let value = context.can_declare_global_function(name)?;
context.vm.push(value);
Ok(CompletionType::Normal)
}
}
impl Operation for CanDeclareGlobalFunction {
const NAME: &'static str = "CanDeclareGlobalFunction";
const INSTRUCTION: &'static str = "INST - CanDeclareGlobalFunction";
const COST: u8 = 4;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}
/// `CanDeclareGlobalVar` implements the Opcode Operation for `Opcode::CanDeclareGlobalVar`
///
/// Operation:
/// - Performs [`CanDeclareGlobalVar ( N )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar
#[derive(Debug, Clone, Copy)]
pub(crate) struct CanDeclareGlobalVar;
impl CanDeclareGlobalVar {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let name = &context.vm.frame().code_block().constant_string(index);
let value = context.can_declare_global_var(name)?;
context.vm.push(value);
Ok(CompletionType::Normal)
}
}
impl Operation for CanDeclareGlobalVar {
const NAME: &'static str = "CanDeclareGlobalVar";
const INSTRUCTION: &'static str = "INST - CanDeclareGlobalVar";
const COST: u8 = 4;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}
/// `CreateGlobalFunctionBinding` implements the Opcode Operation for `Opcode::CreateGlobalFunctionBinding`
///
/// Operation:
/// - Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateGlobalFunctionBinding;
impl CreateGlobalFunctionBinding {
#[allow(clippy::unnecessary_wraps)]
fn operation(
context: &mut Context,
index: usize,
configurable: bool,
) -> JsResult<CompletionType> {
let name = context.vm.frame().code_block().constant_string(index);
let value = context.vm.pop();
let function = value
.as_object()
.expect("valeu should be an function")
.clone();
context.create_global_function_binding(name, function, configurable)?;
Ok(CompletionType::Normal)
}
}
impl Operation for CreateGlobalFunctionBinding {
const NAME: &'static str = "CreateGlobalFunctionBinding";
const INSTRUCTION: &'static str = "INST - CreateGlobalFunctionBinding";
const COST: u8 = 2;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index, configurable)
}
}
/// `CreateGlobalVarBinding` implements the Opcode Operation for `Opcode::CreateGlobalVarBinding`
///
/// Operation:
/// - Performs [`CreateGlobalVarBinding ( N, V, D )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateGlobalVarBinding;
impl CreateGlobalVarBinding {
#[allow(clippy::unnecessary_wraps)]
fn operation(
context: &mut Context,
index: usize,
configurable: bool,
) -> JsResult<CompletionType> {
let name = context.vm.frame().code_block().constant_string(index);
context.create_global_var_binding(name, configurable)?;
Ok(CompletionType::Normal)
}
}
impl Operation for CreateGlobalVarBinding {
const NAME: &'static str = "CreateGlobalVarBinding";
const INSTRUCTION: &'static str = "INST - CreateGlobalVarBinding";
const COST: u8 = 2;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index, configurable)
}
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let configurable = context.vm.read::<u8>() != 0;
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index, configurable)
}
}

62
core/engine/src/vm/opcode/mod.rs

@ -17,6 +17,7 @@ mod dup;
mod environment;
mod generator;
mod get;
mod global;
mod iteration;
mod meta;
mod modifier;
@ -60,6 +61,8 @@ pub(crate) use generator::*;
#[doc(inline)]
pub(crate) use get::*;
#[doc(inline)]
pub(crate) use global::*;
#[doc(inline)]
pub(crate) use iteration::*;
#[doc(inline)]
pub(crate) use meta::*;
@ -1590,6 +1593,13 @@ generate_opcodes! {
/// Stack: **=>**
ThrowNewTypeError { message: VaryingOperand },
/// Throw a new `SyntaxError` exception
///
/// Operands: message: u32
///
/// Stack: **=>**
ThrowNewSyntaxError { message: VaryingOperand },
/// Pops value converts it to boolean and pushes it back.
///
/// Operands:
@ -2070,14 +2080,50 @@ generate_opcodes! {
/// Stack: **=>** `arguments`
CreateUnmappedArgumentsObject,
/// Performs [`HasRestrictedGlobalProperty ( N )`][spec]
///
/// Operands: `index`: u32
///
/// Stack: **=>**
///
/// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
HasRestrictedGlobalProperty { index: VaryingOperand },
/// Performs [`CanDeclareGlobalFunction ( N )`][spec]
///
/// Operands: `index`: u32
///
/// Stack: **=>**
///
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction
CanDeclareGlobalFunction { index: VaryingOperand },
/// Performs [`CanDeclareGlobalVar ( N )`][spec]
///
/// Operands: `index`: u32
///
/// Stack: **=>**
///
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar
CanDeclareGlobalVar { index: VaryingOperand },
/// Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec]
///
/// Operands: configurable: `bool`, `name_index`: `VaryingOperand`
/// Operands: configurable: `bool`, `index`: `VaryingOperand`
///
/// Stack: `value` **=>**
/// Stack: `function` **=>**
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
CreateGlobalFunctionBinding { configurable: bool, name_index: VaryingOperand },
CreateGlobalFunctionBinding { configurable: bool, index: VaryingOperand },
/// Performs [`CreateGlobalVarBinding ( N, V, D )`][spec]
///
/// Operands: configurable: `bool`, `index`: `VaryingOperand`
///
/// Stack: **=>**
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding
CreateGlobalVarBinding { configurable: bool, index: VaryingOperand },
/// No-operation instruction, does nothing.
///
@ -2208,16 +2254,6 @@ generate_opcodes! {
Reserved53 => Reserved,
/// Reserved [`Opcode`].
Reserved54 => Reserved,
/// Reserved [`Opcode`].
Reserved55 => Reserved,
/// Reserved [`Opcode`].
Reserved56 => Reserved,
/// Reserved [`Opcode`].
Reserved57 => Reserved,
/// Reserved [`Opcode`].
Reserved58 => Reserved,
/// Reserved [`Opcode`].
Reserved59 => Reserved,
}
/// Specific opcodes for bindings.

Loading…
Cancel
Save