From 6ee208fc5db104ae07bc3b013f5d04bb3e19ceb2 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Sun, 28 Apr 2024 13:37:15 +0200 Subject: [PATCH] Decouple `Context` from `ByteCompiler` (#3829) * Decouple GlobalDeclarationInstantiation from Context * Decouple EvalDeclarationInstantiation from Context * Remove `Context` from `ByteCompiler` * Fix typo * Apply review --- core/engine/src/builtins/eval/mod.rs | 40 +- core/engine/src/builtins/function/mod.rs | 2 +- core/engine/src/builtins/json/mod.rs | 2 +- core/engine/src/bytecompiler/class.rs | 10 +- core/engine/src/bytecompiler/declarations.rs | 707 +++++++++++------- core/engine/src/bytecompiler/function.rs | 7 +- core/engine/src/bytecompiler/mod.rs | 36 +- core/engine/src/module/source.rs | 2 +- core/engine/src/module/synthetic.rs | 2 +- core/engine/src/script.rs | 21 +- core/engine/src/vm/code_block.rs | 21 +- core/engine/src/vm/flowgraph/mod.rs | 16 +- .../src/vm/opcode/control_flow/throw.rs | 38 + core/engine/src/vm/opcode/define/mod.rs | 53 -- core/engine/src/vm/opcode/global.rs | 220 ++++++ core/engine/src/vm/opcode/mod.rs | 62 +- 16 files changed, 864 insertions(+), 375 deletions(-) create mode 100644 core/engine/src/vm/opcode/global.rs diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 3007340c23..d76435e5a5 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/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!("
"), 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()); diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index 0d1604fcba..cdb7143c65 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/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(); diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 817fecf40d..3ba82ce549 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/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()) diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index 27e134595b..ca43318bd6 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/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); diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index f60eb2d667..16de09bcff 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/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, + _script: &Script, + _env: &Rc, + _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, + script: &Script, + env: &Rc, + 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, + body: &Script, + #[allow(unused)] strict: bool, + #[allow(unused)] var_env: &Rc, + #[allow(unused)] lex_env: &Rc, + 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, - ) -> 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, lex_env: &Rc, - ) -> 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 )` diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index 1d57083624..2a09a497b9 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/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, lexical_environment: Rc, - context: &mut Context, + interner: &mut Interner, ) -> Gc { self.strict = self.strict || body.strict(); @@ -103,7 +104,7 @@ impl FunctionCompiler { false, variable_environment, lexical_environment, - context, + interner, ); compiler.length = length; compiler diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 13b8867882..4b64f6774e 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/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, - 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, @@ -293,11 +296,10 @@ pub struct ByteCompiler<'ctx> { pub(crate) async_handler: Option, 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, + pub(crate) annex_b_function_names: Vec, } impl<'ctx> ByteCompiler<'ctx> { @@ -313,8 +315,7 @@ impl<'ctx> ByteCompiler<'ctx> { json_parse: bool, variable_environment: Rc, lexical_environment: Rc, - // 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); diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index c36b11966a..edc1757db8 100644 --- a/core/engine/src/module/source.rs +++ b/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; diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index fa151d468f..734b1e9dd9 100644 --- a/core/engine/src/module/synthetic.rs +++ b/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 diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index bb34e98188..3351ae4492 100644 --- a/core/engine/src/script.rs +++ b/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!("
"), 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()); diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 502f658c85..2a486da098 100644 --- a/core/engine/src/vm/code_block.rs +++ b/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"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 2db51c7b4d..273053d15b 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/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"), } } diff --git a/core/engine/src/vm/opcode/control_flow/throw.rs b/core/engine/src/vm/opcode/control_flow/throw.rs index bde9b6288f..9261f29feb 100644 --- a/core/engine/src/vm/opcode/control_flow/throw.rs +++ b/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 { + 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 { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } +} diff --git a/core/engine/src/vm/opcode/define/mod.rs b/core/engine/src/vm/opcode/define/mod.rs index 0b6465f856..5c21d2b5ce 100644 --- a/core/engine/src/vm/opcode/define/mod.rs +++ b/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 { - 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 { - let configurable = context.vm.read::() != 0; - let index = context.vm.read::() as usize; - Self::operation(context, index, configurable) - } - - fn execute_with_u16_operands(context: &mut Context) -> JsResult { - let configurable = context.vm.read::() != 0; - let index = context.vm.read::() as usize; - Self::operation(context, index, configurable) - } - - fn execute_with_u32_operands(context: &mut Context) -> JsResult { - let configurable = context.vm.read::() != 0; - let index = context.vm.read::() as usize; - Self::operation(context, index, configurable) - } -} diff --git a/core/engine/src/vm/opcode/global.rs b/core/engine/src/vm/opcode/global.rs new file mode 100644 index 0000000000..49666dc437 --- /dev/null +++ b/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 { + 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 { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() 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 { + 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 { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() 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 { + 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 { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() 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 { + 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 { + let configurable = context.vm.read::() != 0; + let index = context.vm.read::() as usize; + Self::operation(context, index, configurable) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let configurable = context.vm.read::() != 0; + let index = context.vm.read::() as usize; + Self::operation(context, index, configurable) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let configurable = context.vm.read::() != 0; + let index = context.vm.read::() 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 { + 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 { + let configurable = context.vm.read::() != 0; + let index = context.vm.read::() as usize; + Self::operation(context, index, configurable) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let configurable = context.vm.read::() != 0; + let index = context.vm.read::() as usize; + Self::operation(context, index, configurable) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let configurable = context.vm.read::() != 0; + let index = context.vm.read::() as usize; + Self::operation(context, index, configurable) + } +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 4458b78607..3f54a0080f 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/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.