mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 22:17:42 +00:00
LibJS: More properly implement scoping rules in bytecode codegen
Now we emit CreateVariable and SetVariable with the appropriate initialization/environment modes, much closer to the spec. This makes a whole lot of things like let/const variables, function and variable hoisting and some other things work :^)
This commit is contained in:
parent
c7e6b65fd2
commit
1bbfaf8627
12 changed files with 503 additions and 38 deletions
|
@ -28,29 +28,263 @@ Bytecode::CodeGenerationErrorOr<void> ASTNode::generate_bytecode(Bytecode::Gener
|
||||||
|
|
||||||
Bytecode::CodeGenerationErrorOr<void> ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
|
Bytecode::CodeGenerationErrorOr<void> ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
{
|
{
|
||||||
// FIXME: This is an ad-hoc fix but should be done as the spec says in
|
Optional<Bytecode::CodeGenerationError> maybe_error;
|
||||||
// {Global, Block, Function, Eval}DeclarationInstantiation.
|
size_t pushed_scope_count = 0;
|
||||||
for (auto& function : m_functions_hoistable_with_annexB_extension) {
|
auto const failing_completion = Completion(Completion::Type::Throw, {}, {});
|
||||||
generator.emit<Bytecode::Op::NewFunction>(function);
|
|
||||||
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(function.name()));
|
if (is<BlockStatement>(*this) || is<SwitchStatement>(*this)) {
|
||||||
|
// Perform the steps of BlockDeclarationInstantiation.
|
||||||
|
if (has_lexical_declarations()) {
|
||||||
|
generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Block);
|
||||||
|
pushed_scope_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr<void> {
|
||||||
|
auto is_constant_declaration = declaration.is_constant_declaration();
|
||||||
|
declaration.for_each_bound_name([&](auto const& name) {
|
||||||
|
auto index = generator.intern_identifier(name);
|
||||||
|
if (is_constant_declaration || !generator.has_binding(index)) {
|
||||||
|
generator.register_binding(index);
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_constant_declaration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is<FunctionDeclaration>(declaration)) {
|
||||||
|
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
|
||||||
|
auto const& name = function_declaration.name();
|
||||||
|
auto index = generator.intern_identifier(name);
|
||||||
|
generator.emit<Bytecode::Op::NewFunction>(function_declaration);
|
||||||
|
generator.emit<Bytecode::Op::SetVariable>(index, Bytecode::Op::SetVariable::InitializationMode::Initialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
} else if (is<Program>(*this)) {
|
||||||
|
// Perform the steps of GlobalDeclarationInstantiation.
|
||||||
|
generator.begin_variable_scope(Bytecode::Generator::BindingMode::Global, Bytecode::Generator::SurroundingScopeKind::Global);
|
||||||
|
pushed_scope_count++;
|
||||||
|
|
||||||
|
// 1. Let lexNames be the LexicallyDeclaredNames of script.
|
||||||
|
// 2. Let varNames be the VarDeclaredNames of script.
|
||||||
|
// 3. For each element name of lexNames, do
|
||||||
|
(void)for_each_lexically_declared_name([&](auto const& name) -> ThrowCompletionOr<void> {
|
||||||
|
auto identifier = generator.intern_identifier(name);
|
||||||
|
// a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception.
|
||||||
|
// b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
|
||||||
|
if (generator.has_binding(identifier)) {
|
||||||
|
// FIXME: Throw an actual SyntaxError instance.
|
||||||
|
generator.emit<Bytecode::Op::NewString>(generator.intern_string(String::formatted("SyntaxError: toplevel variable already declared: {}", name)));
|
||||||
|
generator.emit<Bytecode::Op::Throw>();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: c. If hasRestrictedGlobalProperty is true, throw a SyntaxError exception.
|
||||||
|
// d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. For each element name of varNames, do
|
||||||
|
(void)for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr<void> {
|
||||||
|
auto identifier = generator.intern_identifier(name);
|
||||||
|
// a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
|
||||||
|
if (generator.has_binding(identifier)) {
|
||||||
|
// FIXME: Throw an actual SyntaxError instance.
|
||||||
|
generator.emit<Bytecode::Op::NewString>(generator.intern_string(String::formatted("SyntaxError: toplevel variable already declared: {}", name)));
|
||||||
|
generator.emit<Bytecode::Op::Throw>();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Let varDeclarations be the VarScopedDeclarations of script.
|
||||||
|
// 6. Let functionsToInitialize be a new empty List.
|
||||||
|
Vector<FunctionDeclaration const&> functions_to_initialize;
|
||||||
|
|
||||||
|
// 7. Let declaredFunctionNames be a new empty List.
|
||||||
|
HashTable<FlyString> declared_function_names;
|
||||||
|
|
||||||
|
// 8. For each element d of varDeclarations, in reverse List order, do
|
||||||
|
(void)for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr<void> {
|
||||||
|
// a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then
|
||||||
|
// i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
|
||||||
|
// Note: This is checked in for_each_var_function_declaration_in_reverse_order.
|
||||||
|
// ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
|
||||||
|
// iii. Let fn be the sole element of the BoundNames of d.
|
||||||
|
|
||||||
|
// iv. If fn is not an element of declaredFunctionNames, then
|
||||||
|
if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// FIXME: 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
|
||||||
|
// FIXME: 2. If fnDefinable is false, throw a TypeError exception.
|
||||||
|
|
||||||
|
// 3. Append fn to declaredFunctionNames.
|
||||||
|
// Note: Already done in step iv. above.
|
||||||
|
|
||||||
|
// 4. Insert d as the first element of functionsToInitialize.
|
||||||
|
functions_to_initialize.prepend(function);
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 9. Let declaredVarNames be a new empty List.
|
||||||
|
HashTable<FlyString> declared_var_names;
|
||||||
|
|
||||||
|
// 10. For each element d of varDeclarations, do
|
||||||
|
(void)for_each_var_scoped_variable_declaration([&](Declaration const& declaration) {
|
||||||
|
// a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
|
||||||
|
// Note: This is done in for_each_var_scoped_variable_declaration.
|
||||||
|
|
||||||
|
// i. For each String vn of the BoundNames of d, do
|
||||||
|
return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr<void> {
|
||||||
|
// 1. If vn is not an element of declaredFunctionNames, then
|
||||||
|
if (declared_function_names.contains(name))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// FIXME: a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
|
||||||
|
// FIXME: b. If vnDefinable is false, throw a TypeError exception.
|
||||||
|
|
||||||
|
// c. If vn is not an element of declaredVarNames, then
|
||||||
|
// i. Append vn to declaredVarNames.
|
||||||
|
declared_var_names.set(name);
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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. Let strict be IsStrict of script.
|
||||||
|
// 13. If strict is false, then
|
||||||
|
if (!verify_cast<Program>(*this).is_strict_mode()) {
|
||||||
|
// 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 script, do
|
||||||
|
(void)for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
|
||||||
|
// i. Let F be StringValue of the BindingIdentifier of f.
|
||||||
|
auto& function_name = function_declaration.name();
|
||||||
|
|
||||||
|
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for script, then
|
||||||
|
// Note: This step is already performed during parsing and for_each_function_hoistable_with_annexB_extension so this always passes here.
|
||||||
|
|
||||||
|
// 1. If env.HasLexicalDeclaration(F) is false, then
|
||||||
|
auto index = generator.intern_identifier(function_name);
|
||||||
|
if (generator.has_binding(index, Bytecode::Generator::BindingMode::Lexical))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// FIXME: a. Let fnDefinable be ? env.CanDeclareGlobalVar(F).
|
||||||
|
// b. If fnDefinable is true, then
|
||||||
|
// 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(function_name) && !declared_var_names.contains(function_name)) {
|
||||||
|
// i. Perform ? env.CreateGlobalVarBinding(F, false).
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Var, false);
|
||||||
|
|
||||||
|
// ii. Append F to declaredFunctionOrVarNames.
|
||||||
|
declared_function_names.set(function_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 NormalCompletion(empty).
|
||||||
|
function_declaration.set_should_do_additional_annexB_steps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15. For each element d of lexDeclarations, do
|
||||||
|
(void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr<void> {
|
||||||
|
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
|
||||||
|
// b. For each element dn of the BoundNames of d, do
|
||||||
|
return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr<void> {
|
||||||
|
auto identifier = generator.intern_identifier(name);
|
||||||
|
// i. If IsConstantDeclaration of d is true, then
|
||||||
|
generator.register_binding(identifier);
|
||||||
|
if (declaration.is_constant_declaration()) {
|
||||||
|
// 1. Perform ? env.CreateImmutableBinding(dn, true).
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(identifier, Bytecode::Op::EnvironmentMode::Lexical, true);
|
||||||
|
} else {
|
||||||
|
// ii. Else,
|
||||||
|
// 1. Perform ? env.CreateMutableBinding(dn, false).
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 16. For each Parse Node f of functionsToInitialize, do
|
||||||
|
for (auto& function_declaration : functions_to_initialize) {
|
||||||
|
// FIXME: Do this more correctly.
|
||||||
|
// a. Let fn be the sole element of the BoundNames of f.
|
||||||
|
// b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
|
||||||
|
generator.emit<Bytecode::Op::NewFunction>(function_declaration);
|
||||||
|
|
||||||
|
// c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
|
||||||
|
auto const& name = function_declaration.name();
|
||||||
|
auto index = generator.intern_identifier(name);
|
||||||
|
if (!generator.has_binding(index)) {
|
||||||
|
generator.register_binding(index, Bytecode::Generator::BindingMode::Var);
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||||
|
}
|
||||||
|
generator.emit<Bytecode::Op::SetVariable>(index, Bytecode::Op::SetVariable::InitializationMode::Initialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 17. For each String vn of declaredVarNames, do
|
||||||
|
// a. Perform ? env.CreateGlobalVarBinding(vn, false).
|
||||||
|
for (auto& var_name : declared_var_names)
|
||||||
|
generator.register_binding(generator.intern_identifier(var_name), Bytecode::Generator::BindingMode::Var);
|
||||||
|
} else {
|
||||||
|
// Perform the steps of FunctionDeclarationInstantiation.
|
||||||
|
generator.begin_variable_scope(Bytecode::Generator::BindingMode::Var, Bytecode::Generator::SurroundingScopeKind::Function);
|
||||||
|
pushed_scope_count++;
|
||||||
|
if (has_lexical_declarations()) {
|
||||||
|
generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Function);
|
||||||
|
pushed_scope_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Implement this boi correctly.
|
||||||
|
(void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr<void> {
|
||||||
|
auto is_constant_declaration = declaration.is_constant_declaration();
|
||||||
|
declaration.for_each_bound_name([&](auto const& name) {
|
||||||
|
auto index = generator.intern_identifier(name);
|
||||||
|
if (is_constant_declaration || !generator.has_binding(index)) {
|
||||||
|
generator.register_binding(index);
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_constant_declaration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is<FunctionDeclaration>(declaration)) {
|
||||||
|
auto& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
|
||||||
|
if (auto result = function_declaration.generate_bytecode(generator); result.is_error()) {
|
||||||
|
maybe_error = result.release_error();
|
||||||
|
// To make `for_each_lexically_scoped_declaration` happy.
|
||||||
|
return failing_completion;
|
||||||
|
}
|
||||||
|
auto const& name = function_declaration.name();
|
||||||
|
auto index = generator.intern_identifier(name);
|
||||||
|
if (!generator.has_binding(index)) {
|
||||||
|
generator.register_binding(index);
|
||||||
|
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||||
|
}
|
||||||
|
generator.emit<Bytecode::Op::SetVariable>(index, Bytecode::Op::SetVariable::InitializationMode::InitializeOrSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HashTable<FlyString> functions_initialized;
|
if (maybe_error.has_value())
|
||||||
for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) {
|
return maybe_error.release_value();
|
||||||
if (functions_initialized.set(function.name()) != AK::HashSetResult::InsertedNewEntry)
|
|
||||||
return;
|
|
||||||
|
|
||||||
generator.emit<Bytecode::Op::NewFunction>(function);
|
|
||||||
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(function.name()));
|
|
||||||
});
|
|
||||||
|
|
||||||
// FIXME: Register lexical and variable scope declarations
|
|
||||||
for (auto& child : children()) {
|
for (auto& child : children()) {
|
||||||
TRY(child.generate_bytecode(generator));
|
TRY(child.generate_bytecode(generator));
|
||||||
if (generator.is_current_block_terminated())
|
if (generator.is_current_block_terminated())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pushed_scope_count; ++i)
|
||||||
|
generator.end_variable_scope();
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -609,8 +843,13 @@ Bytecode::CodeGenerationErrorOr<void> MemberExpression::generate_bytecode(Byteco
|
||||||
return generator.emit_load_from_reference(*this);
|
return generator.emit_load_from_reference(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bytecode::CodeGenerationErrorOr<void> FunctionDeclaration::generate_bytecode(Bytecode::Generator&) const
|
Bytecode::CodeGenerationErrorOr<void> FunctionDeclaration::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
{
|
{
|
||||||
|
if (m_is_hoisted) {
|
||||||
|
auto index = generator.intern_identifier(name());
|
||||||
|
generator.emit<Bytecode::Op::GetVariable>(index);
|
||||||
|
generator.emit<Bytecode::Op::SetVariable>(index, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Var);
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,11 @@ namespace JS::Bytecode {
|
||||||
|
|
||||||
class Generator {
|
class Generator {
|
||||||
public:
|
public:
|
||||||
|
enum class SurroundingScopeKind {
|
||||||
|
Global,
|
||||||
|
Function,
|
||||||
|
Block,
|
||||||
|
};
|
||||||
static CodeGenerationErrorOr<NonnullOwnPtr<Executable>> generate(ASTNode const&, FunctionKind = FunctionKind::Normal);
|
static CodeGenerationErrorOr<NonnullOwnPtr<Executable>> generate(ASTNode const&, FunctionKind = FunctionKind::Normal);
|
||||||
|
|
||||||
Register allocate_register();
|
Register allocate_register();
|
||||||
|
@ -117,6 +122,55 @@ public:
|
||||||
bool is_in_generator_function() const { return m_enclosing_function_kind == FunctionKind::Generator; }
|
bool is_in_generator_function() const { return m_enclosing_function_kind == FunctionKind::Generator; }
|
||||||
bool is_in_async_function() const { return m_enclosing_function_kind == FunctionKind::Async; }
|
bool is_in_async_function() const { return m_enclosing_function_kind == FunctionKind::Async; }
|
||||||
|
|
||||||
|
enum class BindingMode {
|
||||||
|
Lexical,
|
||||||
|
Var,
|
||||||
|
Global,
|
||||||
|
};
|
||||||
|
struct LexicalScope {
|
||||||
|
SurroundingScopeKind kind;
|
||||||
|
BindingMode mode;
|
||||||
|
HashTable<IdentifierTableIndex> known_bindings;
|
||||||
|
};
|
||||||
|
|
||||||
|
void register_binding(IdentifierTableIndex identifier, BindingMode mode = BindingMode::Lexical)
|
||||||
|
{
|
||||||
|
m_variable_scopes.last_matching([&](auto& x) { return x.mode == BindingMode::Global || x.mode == mode; })->known_bindings.set(identifier);
|
||||||
|
}
|
||||||
|
bool has_binding(IdentifierTableIndex identifier, Optional<BindingMode> const& specific_binding_mode = {})
|
||||||
|
{
|
||||||
|
for (auto index = m_variable_scopes.size(); index > 0; --index) {
|
||||||
|
auto& scope = m_variable_scopes[index - 1];
|
||||||
|
|
||||||
|
if (scope.mode != BindingMode::Global && specific_binding_mode.value_or(scope.mode) != scope.mode)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (scope.known_bindings.contains(identifier))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void begin_variable_scope(BindingMode mode = BindingMode::Lexical, SurroundingScopeKind kind = SurroundingScopeKind::Block)
|
||||||
|
{
|
||||||
|
m_variable_scopes.append({ kind, mode, {} });
|
||||||
|
if (mode != BindingMode::Global) {
|
||||||
|
emit<Bytecode::Op::CreateEnvironment>(
|
||||||
|
mode == BindingMode::Lexical
|
||||||
|
? Bytecode::Op::EnvironmentMode::Lexical
|
||||||
|
: Bytecode::Op::EnvironmentMode::Var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void end_variable_scope()
|
||||||
|
{
|
||||||
|
auto mode = m_variable_scopes.take_last().mode;
|
||||||
|
if (mode != BindingMode::Global && !m_current_basic_block->is_terminated()) {
|
||||||
|
emit<Bytecode::Op::LeaveEnvironment>(
|
||||||
|
mode == BindingMode::Lexical
|
||||||
|
? Bytecode::Op::EnvironmentMode::Lexical
|
||||||
|
: Bytecode::Op::EnvironmentMode::Var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Generator();
|
Generator();
|
||||||
~Generator();
|
~Generator();
|
||||||
|
@ -134,6 +188,7 @@ private:
|
||||||
FunctionKind m_enclosing_function_kind { FunctionKind::Normal };
|
FunctionKind m_enclosing_function_kind { FunctionKind::Normal };
|
||||||
Vector<Label> m_continuable_scopes;
|
Vector<Label> m_continuable_scopes;
|
||||||
Vector<Label> m_breakable_scopes;
|
Vector<Label> m_breakable_scopes;
|
||||||
|
Vector<LexicalScope> m_variable_scopes;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
O(ConcatString) \
|
O(ConcatString) \
|
||||||
O(ContinuePendingUnwind) \
|
O(ContinuePendingUnwind) \
|
||||||
O(CopyObjectExcludingProperties) \
|
O(CopyObjectExcludingProperties) \
|
||||||
|
O(CreateEnvironment) \
|
||||||
|
O(CreateVariable) \
|
||||||
O(Decrement) \
|
O(Decrement) \
|
||||||
O(Div) \
|
O(Div) \
|
||||||
O(EnterUnwindContext) \
|
O(EnterUnwindContext) \
|
||||||
|
@ -42,6 +44,7 @@
|
||||||
O(JumpConditional) \
|
O(JumpConditional) \
|
||||||
O(JumpNullish) \
|
O(JumpNullish) \
|
||||||
O(JumpUndefined) \
|
O(JumpUndefined) \
|
||||||
|
O(LeaveEnvironment) \
|
||||||
O(LeaveUnwindContext) \
|
O(LeaveUnwindContext) \
|
||||||
O(LeftShift) \
|
O(LeftShift) \
|
||||||
O(LessThan) \
|
O(LessThan) \
|
||||||
|
|
|
@ -67,7 +67,7 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& e
|
||||||
if (!m_manually_entered_frames.is_empty() && m_manually_entered_frames.last()) {
|
if (!m_manually_entered_frames.is_empty() && m_manually_entered_frames.last()) {
|
||||||
m_register_windows.append(make<RegisterWindow>(m_register_windows.last()));
|
m_register_windows.append(make<RegisterWindow>(m_register_windows.last()));
|
||||||
} else {
|
} else {
|
||||||
m_register_windows.append(make<RegisterWindow>());
|
m_register_windows.append(make<RegisterWindow>(MarkedVector<Value>(vm().heap()), MarkedVector<Environment*>(vm().heap()), MarkedVector<Environment*>(vm().heap())));
|
||||||
}
|
}
|
||||||
|
|
||||||
registers().resize(executable.number_of_registers);
|
registers().resize(executable.number_of_registers);
|
||||||
|
@ -150,7 +150,7 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& e
|
||||||
|
|
||||||
// NOTE: The return value from a called function is put into $0 in the caller context.
|
// NOTE: The return value from a called function is put into $0 in the caller context.
|
||||||
if (!m_register_windows.is_empty())
|
if (!m_register_windows.is_empty())
|
||||||
m_register_windows.last()[0] = return_value;
|
m_register_windows.last().registers[0] = return_value;
|
||||||
|
|
||||||
// At this point we may have already run any queued promise jobs via on_call_stack_emptied,
|
// At this point we may have already run any queued promise jobs via on_call_stack_emptied,
|
||||||
// in which case this is a no-op.
|
// in which case this is a no-op.
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
|
|
||||||
namespace JS::Bytecode {
|
namespace JS::Bytecode {
|
||||||
|
|
||||||
using RegisterWindow = Vector<Value>;
|
struct RegisterWindow {
|
||||||
|
MarkedVector<Value> registers;
|
||||||
|
MarkedVector<Environment*> saved_lexical_environments;
|
||||||
|
MarkedVector<Environment*> saved_variable_environments;
|
||||||
|
};
|
||||||
|
|
||||||
class Interpreter {
|
class Interpreter {
|
||||||
public:
|
public:
|
||||||
|
@ -48,6 +52,9 @@ public:
|
||||||
Value& reg(Register const& r) { return registers()[r.index()]; }
|
Value& reg(Register const& r) { return registers()[r.index()]; }
|
||||||
[[nodiscard]] RegisterWindow snapshot_frame() const { return m_register_windows.last(); }
|
[[nodiscard]] RegisterWindow snapshot_frame() const { return m_register_windows.last(); }
|
||||||
|
|
||||||
|
auto& saved_lexical_environment_stack() { return m_register_windows.last().saved_lexical_environments; }
|
||||||
|
auto& saved_variable_environment_stack() { return m_register_windows.last().saved_variable_environments; }
|
||||||
|
|
||||||
void enter_frame(RegisterWindow const& frame)
|
void enter_frame(RegisterWindow const& frame)
|
||||||
{
|
{
|
||||||
m_manually_entered_frames.append(true);
|
m_manually_entered_frames.append(true);
|
||||||
|
@ -82,7 +89,7 @@ public:
|
||||||
VM::InterpreterExecutionScope ast_interpreter_scope();
|
VM::InterpreterExecutionScope ast_interpreter_scope();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RegisterWindow& registers() { return m_register_windows.last(); }
|
MarkedVector<Value>& registers() { return m_register_windows.last().registers; }
|
||||||
|
|
||||||
static AK::Array<OwnPtr<PassManager>, static_cast<UnderlyingType<Interpreter::OptimizationLevel>>(Interpreter::OptimizationLevel::__Count)> s_optimization_pipelines;
|
static AK::Array<OwnPtr<PassManager>, static_cast<UnderlyingType<Interpreter::OptimizationLevel>>(Interpreter::OptimizationLevel::__Count)> s_optimization_pipelines;
|
||||||
|
|
||||||
|
|
|
@ -260,12 +260,63 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> CreateEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
auto make_and_swap_envs = [&](auto*& old_environment) {
|
||||||
|
Environment* environment = new_declarative_environment(*old_environment);
|
||||||
|
swap(old_environment, environment);
|
||||||
|
return environment;
|
||||||
|
};
|
||||||
|
if (m_mode == EnvironmentMode::Lexical)
|
||||||
|
interpreter.saved_lexical_environment_stack().append(make_and_swap_envs(interpreter.vm().running_execution_context().lexical_environment));
|
||||||
|
else if (m_mode == EnvironmentMode::Var)
|
||||||
|
interpreter.saved_variable_environment_stack().append(make_and_swap_envs(interpreter.vm().running_execution_context().variable_environment));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> CreateVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
auto& vm = interpreter.vm();
|
||||||
|
auto const& name = interpreter.current_executable().get_identifier(m_identifier);
|
||||||
|
|
||||||
|
if (m_mode == EnvironmentMode::Lexical) {
|
||||||
|
// Note: This is papering over an issue where "FunctionDeclarationInstantiation" creates these bindings for us.
|
||||||
|
// Instead of crashing in there, we'll just raise an exception here.
|
||||||
|
if (TRY(vm.lexical_environment()->has_binding(name)))
|
||||||
|
return vm.throw_completion<InternalError>(interpreter.global_object(), String::formatted("Lexical environment already has binding '{}'", name));
|
||||||
|
|
||||||
|
if (m_is_immutable)
|
||||||
|
vm.lexical_environment()->create_immutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
|
||||||
|
else
|
||||||
|
vm.lexical_environment()->create_mutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
|
||||||
|
} else {
|
||||||
|
if (m_is_immutable)
|
||||||
|
vm.variable_environment()->create_immutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
|
||||||
|
else
|
||||||
|
vm.variable_environment()->create_mutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
ThrowCompletionOr<void> SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
ThrowCompletionOr<void> SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
auto& vm = interpreter.vm();
|
auto& vm = interpreter.vm();
|
||||||
auto reference = TRY(vm.resolve_binding(interpreter.current_executable().get_identifier(m_identifier)));
|
auto const& name = interpreter.current_executable().get_identifier(m_identifier);
|
||||||
|
auto environment = m_mode == EnvironmentMode::Lexical ? vm.running_execution_context().lexical_environment : vm.running_execution_context().variable_environment;
|
||||||
TRY(reference.put_value(interpreter.global_object(), interpreter.accumulator()));
|
auto reference = TRY(vm.resolve_binding(name, environment));
|
||||||
|
switch (m_initialization_mode) {
|
||||||
|
case InitializationMode::Initialize:
|
||||||
|
TRY(reference.initialize_referenced_binding(interpreter.global_object(), interpreter.accumulator()));
|
||||||
|
break;
|
||||||
|
case InitializationMode::Set:
|
||||||
|
TRY(reference.put_value(interpreter.global_object(), interpreter.accumulator()));
|
||||||
|
break;
|
||||||
|
case InitializationMode::InitializeOrSet:
|
||||||
|
VERIFY(reference.is_environment_reference());
|
||||||
|
VERIFY(reference.base_environment().is_declarative_environment());
|
||||||
|
TRY(static_cast<DeclarativeEnvironment&>(reference.base_environment()).initialize_or_set_mutable_binding(interpreter.global_object(), name, interpreter.accumulator()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,6 +491,15 @@ void FinishUnwind::replace_references_impl(BasicBlock const& from, BasicBlock co
|
||||||
m_next_target = Label { to };
|
m_next_target = Label { to };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> LeaveEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
if (m_mode == EnvironmentMode::Lexical)
|
||||||
|
interpreter.vm().running_execution_context().lexical_environment = interpreter.saved_lexical_environment_stack().take_last();
|
||||||
|
if (m_mode == EnvironmentMode::Var)
|
||||||
|
interpreter.vm().running_execution_context().variable_environment = interpreter.saved_variable_environment_stack().take_last();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
ThrowCompletionOr<void> LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
|
ThrowCompletionOr<void> LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
interpreter.leave_unwind_context();
|
interpreter.leave_unwind_context();
|
||||||
|
@ -628,9 +688,27 @@ String GetVariable::to_string_impl(Bytecode::Executable const& executable) const
|
||||||
return String::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
|
return String::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String CreateEnvironment::to_string_impl(Bytecode::Executable const&) const
|
||||||
|
{
|
||||||
|
auto mode_string = m_mode == EnvironmentMode::Lexical
|
||||||
|
? "Lexical"
|
||||||
|
: "Variable";
|
||||||
|
return String::formatted("CreateEnvironment mode:{}", mode_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
String CreateVariable::to_string_impl(Bytecode::Executable const& executable) const
|
||||||
|
{
|
||||||
|
auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable";
|
||||||
|
return String::formatted("CreateVariable env:{} immutable:{} {} ({})", mode_string, m_is_immutable, m_identifier, executable.identifier_table->get(m_identifier));
|
||||||
|
}
|
||||||
|
|
||||||
String SetVariable::to_string_impl(Bytecode::Executable const& executable) const
|
String SetVariable::to_string_impl(Bytecode::Executable const& executable) const
|
||||||
{
|
{
|
||||||
return String::formatted("SetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
|
auto initialization_mode_name = m_initialization_mode == InitializationMode ::Initialize ? "Initialize"
|
||||||
|
: m_initialization_mode == InitializationMode::Set ? "Set"
|
||||||
|
: "InitializeOrSet";
|
||||||
|
auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable";
|
||||||
|
return String::formatted("SetVariable env:{} init:{} {} ({})", mode_string, initialization_mode_name, m_identifier, executable.identifier_table->get(m_identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
String PutById::to_string_impl(Bytecode::Executable const& executable) const
|
String PutById::to_string_impl(Bytecode::Executable const& executable) const
|
||||||
|
@ -729,6 +807,14 @@ String FinishUnwind::to_string_impl(const Bytecode::Executable&) const
|
||||||
return String::formatted("FinishUnwind next:{}", m_next_target);
|
return String::formatted("FinishUnwind next:{}", m_next_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String LeaveEnvironment::to_string_impl(Bytecode::Executable const&) const
|
||||||
|
{
|
||||||
|
auto mode_string = m_mode == EnvironmentMode::Lexical
|
||||||
|
? "Lexical"
|
||||||
|
: "Variable";
|
||||||
|
return String::formatted("LeaveEnvironment env:{}", mode_string);
|
||||||
|
}
|
||||||
|
|
||||||
String LeaveUnwindContext::to_string_impl(Bytecode::Executable const&) const
|
String LeaveUnwindContext::to_string_impl(Bytecode::Executable const&) const
|
||||||
{
|
{
|
||||||
return "LeaveUnwindContext";
|
return "LeaveUnwindContext";
|
||||||
|
|
|
@ -282,11 +282,34 @@ private:
|
||||||
Register m_lhs;
|
Register m_lhs;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SetVariable final : public Instruction {
|
enum class EnvironmentMode {
|
||||||
|
Lexical,
|
||||||
|
Var,
|
||||||
|
};
|
||||||
|
|
||||||
|
class CreateEnvironment final : public Instruction {
|
||||||
public:
|
public:
|
||||||
explicit SetVariable(IdentifierTableIndex identifier)
|
explicit CreateEnvironment(EnvironmentMode mode)
|
||||||
: Instruction(Type::SetVariable)
|
: Instruction(Type::CreateEnvironment)
|
||||||
|
, m_mode(mode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||||
|
String to_string_impl(Bytecode::Executable const&) const;
|
||||||
|
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EnvironmentMode m_mode { EnvironmentMode::Lexical };
|
||||||
|
};
|
||||||
|
|
||||||
|
class CreateVariable final : public Instruction {
|
||||||
|
public:
|
||||||
|
explicit CreateVariable(IdentifierTableIndex identifier, EnvironmentMode mode, bool is_immutable)
|
||||||
|
: Instruction(Type::CreateVariable)
|
||||||
, m_identifier(identifier)
|
, m_identifier(identifier)
|
||||||
|
, m_mode(mode)
|
||||||
|
, m_is_immutable(is_immutable)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +319,33 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IdentifierTableIndex m_identifier;
|
IdentifierTableIndex m_identifier;
|
||||||
|
EnvironmentMode m_mode;
|
||||||
|
bool m_is_immutable { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
class SetVariable final : public Instruction {
|
||||||
|
public:
|
||||||
|
enum class InitializationMode {
|
||||||
|
Initialize,
|
||||||
|
Set,
|
||||||
|
InitializeOrSet,
|
||||||
|
};
|
||||||
|
explicit SetVariable(IdentifierTableIndex identifier, InitializationMode initialization_mode = InitializationMode::Set, EnvironmentMode mode = EnvironmentMode::Lexical)
|
||||||
|
: Instruction(Type::SetVariable)
|
||||||
|
, m_identifier(identifier)
|
||||||
|
, m_mode(mode)
|
||||||
|
, m_initialization_mode(initialization_mode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||||
|
String to_string_impl(Bytecode::Executable const&) const;
|
||||||
|
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IdentifierTableIndex m_identifier;
|
||||||
|
EnvironmentMode m_mode;
|
||||||
|
InitializationMode m_initialization_mode { InitializationMode::Set };
|
||||||
};
|
};
|
||||||
|
|
||||||
class GetVariable final : public Instruction {
|
class GetVariable final : public Instruction {
|
||||||
|
@ -599,6 +649,22 @@ private:
|
||||||
Optional<Label> m_finalizer_target;
|
Optional<Label> m_finalizer_target;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LeaveEnvironment final : public Instruction {
|
||||||
|
public:
|
||||||
|
LeaveEnvironment(EnvironmentMode mode)
|
||||||
|
: Instruction(Type::LeaveEnvironment)
|
||||||
|
, m_mode(mode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||||
|
String to_string_impl(Bytecode::Executable const&) const;
|
||||||
|
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EnvironmentMode m_mode { EnvironmentMode::Lexical };
|
||||||
|
};
|
||||||
|
|
||||||
class LeaveUnwindContext final : public Instruction {
|
class LeaveUnwindContext final : public Instruction {
|
||||||
public:
|
public:
|
||||||
LeaveUnwindContext()
|
LeaveUnwindContext()
|
||||||
|
|
|
@ -196,15 +196,21 @@ ThrowCompletionOr<bool> DeclarativeEnvironment::delete_binding(GlobalObject&, Fl
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeclarativeEnvironment::initialize_or_set_mutable_binding(Badge<ScopeNode>, GlobalObject& global_object, FlyString const& name, Value value)
|
ThrowCompletionOr<void> DeclarativeEnvironment::initialize_or_set_mutable_binding(GlobalObject& global_object, FlyString const& name, Value value)
|
||||||
{
|
{
|
||||||
auto it = m_names.find(name);
|
auto it = m_names.find(name);
|
||||||
VERIFY(it != m_names.end());
|
VERIFY(it != m_names.end());
|
||||||
auto& binding = m_bindings[it->value];
|
auto& binding = m_bindings[it->value];
|
||||||
if (!binding.initialized)
|
if (!binding.initialized)
|
||||||
MUST(initialize_binding(global_object, name, value));
|
TRY(initialize_binding(global_object, name, value));
|
||||||
else
|
else
|
||||||
MUST(set_mutable_binding(global_object, name, value, false));
|
TRY(set_mutable_binding(global_object, name, value, false));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclarativeEnvironment::initialize_or_set_mutable_binding(Badge<ScopeNode>, GlobalObject& global_object, FlyString const& name, Value value)
|
||||||
|
{
|
||||||
|
MUST(initialize_or_set_mutable_binding(global_object, name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<String> DeclarativeEnvironment::bindings() const
|
Vector<String> DeclarativeEnvironment::bindings() const
|
||||||
|
|
|
@ -31,6 +31,7 @@ public:
|
||||||
virtual ThrowCompletionOr<bool> delete_binding(GlobalObject&, FlyString const& name) override;
|
virtual ThrowCompletionOr<bool> delete_binding(GlobalObject&, FlyString const& name) override;
|
||||||
|
|
||||||
void initialize_or_set_mutable_binding(Badge<ScopeNode>, GlobalObject& global_object, FlyString const& name, Value value);
|
void initialize_or_set_mutable_binding(Badge<ScopeNode>, GlobalObject& global_object, FlyString const& name, Value value);
|
||||||
|
ThrowCompletionOr<void> initialize_or_set_mutable_binding(GlobalObject& global_object, FlyString const& name, Value value);
|
||||||
|
|
||||||
// This is not a method defined in the spec! Do not use this in any LibJS (or other spec related) code.
|
// This is not a method defined in the spec! Do not use this in any LibJS (or other spec related) code.
|
||||||
[[nodiscard]] Vector<String> bindings() const;
|
[[nodiscard]] Vector<String> bindings() const;
|
||||||
|
|
|
@ -542,14 +542,16 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
|
||||||
if (!scope_body)
|
if (!scope_body)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
if (!Bytecode::Interpreter::current()) {
|
||||||
declaration.for_each_bound_name([&](auto const& name) {
|
scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||||
if (declaration.is_constant_declaration())
|
declaration.for_each_bound_name([&](auto const& name) {
|
||||||
MUST(lex_environment->create_immutable_binding(global_object(), name, true));
|
if (declaration.is_constant_declaration())
|
||||||
else
|
MUST(lex_environment->create_immutable_binding(global_object(), name, true));
|
||||||
MUST(lex_environment->create_mutable_binding(global_object(), name, false));
|
else
|
||||||
|
MUST(lex_environment->create_mutable_binding(global_object(), name, false));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
auto* private_environment = callee_context.private_environment;
|
auto* private_environment = callee_context.private_environment;
|
||||||
for (auto& declaration : functions_to_initialize) {
|
for (auto& declaration : functions_to_initialize) {
|
||||||
|
|
|
@ -97,7 +97,7 @@ ThrowCompletionOr<Value> GeneratorObject::next_impl(VM& vm, GlobalObject& global
|
||||||
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
||||||
|
|
||||||
// Restore the snapshot registers
|
// Restore the snapshot registers
|
||||||
bytecode_interpreter->enter_frame(m_frame);
|
bytecode_interpreter->enter_frame(*m_frame);
|
||||||
|
|
||||||
// Temporarily switch to the captured execution context
|
// Temporarily switch to the captured execution context
|
||||||
TRY(vm.push_execution_context(m_execution_context, global_object));
|
TRY(vm.push_execution_context(m_execution_context, global_object));
|
||||||
|
|
|
@ -29,7 +29,7 @@ private:
|
||||||
ExecutionContext m_execution_context;
|
ExecutionContext m_execution_context;
|
||||||
ECMAScriptFunctionObject* m_generating_function { nullptr };
|
ECMAScriptFunctionObject* m_generating_function { nullptr };
|
||||||
Value m_previous_value;
|
Value m_previous_value;
|
||||||
Bytecode::RegisterWindow m_frame;
|
Optional<Bytecode::RegisterWindow> m_frame;
|
||||||
bool m_done { false };
|
bool m_done { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue