mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 07:48:11 +00:00
LibJS: Fast non-local variable access :^)
This patch introduces the "environment coordinate" concept, which encodes the distance from a variable access to the binding it ends up resolving to. EnvironmentCoordinate has two fields: - hops: The number of hops up the lexical environment chain we have to make before getting to the resolved binding. - index: The index of the resolved binding within its declarative environment record. Whenever a variable lookup resolves somewhere inside a declarative environment, we now cache the coordinates and reuse them in subsequent lookups. This is achieved via a coordinate cache in JS::Identifier. Note that non-strict direct eval() breaks this optimization and so it will not be performed if the resolved environment has been permanently screwed by eval(). This makes variable access *significantly* faster. :^)
This commit is contained in:
parent
421845b0cd
commit
41a072bded
8 changed files with 65 additions and 12 deletions
|
@ -1005,7 +1005,20 @@ Reference Expression::to_reference(Interpreter&, GlobalObject&) const
|
||||||
|
|
||||||
Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) const
|
Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) const
|
||||||
{
|
{
|
||||||
return interpreter.vm().resolve_binding(string());
|
if (m_cached_environment_coordinate.has_value()) {
|
||||||
|
auto* environment = interpreter.vm().running_execution_context().lexical_environment;
|
||||||
|
for (size_t i = 0; i < m_cached_environment_coordinate->hops; ++i)
|
||||||
|
environment = environment->outer_environment();
|
||||||
|
VERIFY(environment);
|
||||||
|
VERIFY(environment->is_declarative_environment());
|
||||||
|
if (!environment->is_permanently_screwed_by_eval())
|
||||||
|
return Reference { *environment, string(), interpreter.vm().in_strict_mode(), m_cached_environment_coordinate };
|
||||||
|
m_cached_environment_coordinate = {};
|
||||||
|
}
|
||||||
|
auto reference = interpreter.vm().resolve_binding(string());
|
||||||
|
if (reference.environment_coordinate().has_value())
|
||||||
|
const_cast<Identifier&>(*this).m_cached_environment_coordinate = reference.environment_coordinate();
|
||||||
|
return reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& global_object) const
|
Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& global_object) const
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <AK/Variant.h>
|
#include <AK/Variant.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
|
#include <LibJS/Runtime/EnvironmentCoordinate.h>
|
||||||
#include <LibJS/Runtime/PropertyName.h>
|
#include <LibJS/Runtime/PropertyName.h>
|
||||||
#include <LibJS/Runtime/Reference.h>
|
#include <LibJS/Runtime/Reference.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
|
@ -984,6 +985,7 @@ private:
|
||||||
virtual bool is_identifier() const override { return true; }
|
virtual bool is_identifier() const override { return true; }
|
||||||
|
|
||||||
FlyString m_string;
|
FlyString m_string;
|
||||||
|
mutable Optional<EnvironmentCoordinate> m_cached_environment_coordinate;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClassMethod final : public ASTNode {
|
class ClassMethod final : public ASTNode {
|
||||||
|
|
18
Userland/Libraries/LibJS/Runtime/EnvironmentCoordinate.h
Normal file
18
Userland/Libraries/LibJS/Runtime/EnvironmentCoordinate.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibJS/Forward.h>
|
||||||
|
|
||||||
|
namespace JS {
|
||||||
|
|
||||||
|
struct EnvironmentCoordinate {
|
||||||
|
size_t hops { 0 };
|
||||||
|
size_t index { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -49,8 +49,8 @@ void Reference::put_value(GlobalObject& global_object, Value value)
|
||||||
|
|
||||||
VERIFY(m_base_type == BaseType::Environment);
|
VERIFY(m_base_type == BaseType::Environment);
|
||||||
VERIFY(m_base_environment);
|
VERIFY(m_base_environment);
|
||||||
if (m_index_in_declarative_environment.has_value())
|
if (m_environment_coordinate.has_value())
|
||||||
static_cast<DeclarativeEnvironment*>(m_base_environment)->set_mutable_binding_direct(global_object, m_index_in_declarative_environment.value(), value, m_strict);
|
static_cast<DeclarativeEnvironment*>(m_base_environment)->set_mutable_binding_direct(global_object, m_environment_coordinate->index, value, m_strict);
|
||||||
else
|
else
|
||||||
m_base_environment->set_mutable_binding(global_object, m_name.as_string(), value, m_strict);
|
m_base_environment->set_mutable_binding(global_object, m_name.as_string(), value, m_strict);
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,8 @@ Value Reference::get_value(GlobalObject& global_object) const
|
||||||
|
|
||||||
VERIFY(m_base_type == BaseType::Environment);
|
VERIFY(m_base_type == BaseType::Environment);
|
||||||
VERIFY(m_base_environment);
|
VERIFY(m_base_environment);
|
||||||
if (m_index_in_declarative_environment.has_value())
|
if (m_environment_coordinate.has_value())
|
||||||
return static_cast<DeclarativeEnvironment*>(m_base_environment)->get_binding_value_direct(global_object, m_index_in_declarative_environment.value(), m_strict);
|
return static_cast<DeclarativeEnvironment*>(m_base_environment)->get_binding_value_direct(global_object, m_environment_coordinate->index, m_strict);
|
||||||
return m_base_environment->get_binding_value(global_object, m_name.as_string(), m_strict);
|
return m_base_environment->get_binding_value(global_object, m_name.as_string(), m_strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <LibJS/Runtime/Environment.h>
|
#include <LibJS/Runtime/Environment.h>
|
||||||
|
#include <LibJS/Runtime/EnvironmentCoordinate.h>
|
||||||
#include <LibJS/Runtime/PropertyName.h>
|
#include <LibJS/Runtime/PropertyName.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
|
|
||||||
|
@ -44,12 +45,12 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference(Environment& base, FlyString referenced_name, bool strict = false, Optional<size_t> index_in_declarative_environment = {})
|
Reference(Environment& base, FlyString referenced_name, bool strict = false, Optional<EnvironmentCoordinate> environment_coordinate = {})
|
||||||
: m_base_type(BaseType::Environment)
|
: m_base_type(BaseType::Environment)
|
||||||
, m_base_environment(&base)
|
, m_base_environment(&base)
|
||||||
, m_name(move(referenced_name))
|
, m_name(move(referenced_name))
|
||||||
, m_strict(strict)
|
, m_strict(strict)
|
||||||
, m_index_in_declarative_environment(move(index_in_declarative_environment))
|
, m_environment_coordinate(move(environment_coordinate))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +120,8 @@ public:
|
||||||
|
|
||||||
bool is_valid_reference() const { return m_name.is_valid(); }
|
bool is_valid_reference() const { return m_name.is_valid(); }
|
||||||
|
|
||||||
|
Optional<EnvironmentCoordinate> environment_coordinate() const { return m_environment_coordinate; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void throw_reference_error(GlobalObject&) const;
|
void throw_reference_error(GlobalObject&) const;
|
||||||
|
|
||||||
|
@ -130,7 +133,7 @@ private:
|
||||||
PropertyName m_name;
|
PropertyName m_name;
|
||||||
Value m_this_value;
|
Value m_this_value;
|
||||||
bool m_strict { false };
|
bool m_strict { false };
|
||||||
Optional<size_t> m_index_in_declarative_environment;
|
Optional<EnvironmentCoordinate> m_environment_coordinate;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,7 +435,7 @@ ThrowCompletionOr<void> VM::iterator_binding_initialization(BindingPattern const
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference
|
// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference
|
||||||
Reference VM::get_identifier_reference(Environment* environment, FlyString name, bool strict)
|
Reference VM::get_identifier_reference(Environment* environment, FlyString name, bool strict, size_t hops)
|
||||||
{
|
{
|
||||||
// 1. If env is the value null, then
|
// 1. If env is the value null, then
|
||||||
if (!environment) {
|
if (!environment) {
|
||||||
|
@ -448,10 +448,14 @@ Reference VM::get_identifier_reference(Environment* environment, FlyString name,
|
||||||
if (exception())
|
if (exception())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
Optional<EnvironmentCoordinate> environment_coordinate;
|
||||||
|
if (index.has_value())
|
||||||
|
environment_coordinate = EnvironmentCoordinate { .hops = hops, .index = index.value() };
|
||||||
|
|
||||||
if (exists)
|
if (exists)
|
||||||
return Reference { *environment, move(name), strict, index };
|
return Reference { *environment, move(name), strict, environment_coordinate };
|
||||||
else
|
else
|
||||||
return get_identifier_reference(environment->outer_environment(), move(name), strict);
|
return get_identifier_reference(environment->outer_environment(), move(name), strict, hops + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9.4.2 ResolveBinding ( name [ , env ] ), https://tc39.es/ecma262/#sec-resolvebinding
|
// 9.4.2 ResolveBinding ( name [ , env ] ), https://tc39.es/ecma262/#sec-resolvebinding
|
||||||
|
|
|
@ -202,7 +202,7 @@ public:
|
||||||
FlyString unwind_until_label() const { return m_unwind_until_label; }
|
FlyString unwind_until_label() const { return m_unwind_until_label; }
|
||||||
|
|
||||||
Reference resolve_binding(FlyString const&, Environment* = nullptr);
|
Reference resolve_binding(FlyString const&, Environment* = nullptr);
|
||||||
Reference get_identifier_reference(Environment*, FlyString, bool strict);
|
Reference get_identifier_reference(Environment*, FlyString, bool strict, size_t hops = 0);
|
||||||
|
|
||||||
template<typename T, typename... Args>
|
template<typename T, typename... Args>
|
||||||
void throw_exception(GlobalObject& global_object, Args&&... args)
|
void throw_exception(GlobalObject& global_object, Args&&... args)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
test("basic that non-strict direct eval() prevents non-local access caching", () => {
|
||||||
|
function foo(do_eval) {
|
||||||
|
var c = 1;
|
||||||
|
function bar(do_eval) {
|
||||||
|
if (do_eval) eval("var c = 2;");
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return bar(do_eval);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(foo(false)).toBe(1);
|
||||||
|
expect(foo(true)).toBe(2);
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue