LibJS: Avoid function envs for lexical-this arrows
Track whether a function needs environment-backed this resolution
separately from whether it needs to allocate its own function
environment. Arrow functions that only capture lexical this can now
resolve through the outer environment without allocating an empty
function environment for every call.
Keep the asm Call path conservative by routing functions that still need
lexical-this resolution through the C++ inline-call helper, so the call
receiver is not cached as the arrow function's this value.
Microbenchmark:
function makeLexicalThisArrow() {
return () => this.value;
}
let object = { value: 1, makeLexicalThisArrow };
let fn = object.makeLexicalThisArrow();
for (let i = 0; i < 20_000_000; ++i)
fn();
Measured with the same Release build toggling this patch:
baseline: 1069.2 ms mean over 12 runs
optimized: 501.2 ms mean over 12 runs
speedup: 2.13 times faster
This commit is contained in:
parent
7f0ae9bd4c
commit
2171563daf
11 changed files with 41 additions and 21 deletions
|
|
@ -2022,9 +2022,9 @@ handler Call
|
|||
load64 shared_data, [callee, ECMASCRIPT_FUNCTION_OBJECT_SHARED_DATA]
|
||||
load_pair64 exec_ptr, meta, [shared_data, SHARED_FUNCTION_INSTANCE_DATA_EXECUTABLE], [shared_data, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA]
|
||||
branch_bits_clear meta, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_CAN_INLINE_CALL, .call_slow
|
||||
# NewFunctionEnvironment() allocates and has to stay out of the pure asm
|
||||
# path, but we still preserve inline-call semantics via .call_interp_inline.
|
||||
branch_bits_set meta, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_FUNCTION_ENVIRONMENT_NEEDED, .call_interp_inline
|
||||
# NewFunctionEnvironment() allocation and lexical-this resolution both use
|
||||
# the C++ helper, instead of the pure asm path.
|
||||
branch_bits_set meta, SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_NEEDS_ENVIRONMENT_OR_THIS_VALUE_RESOLUTION, .call_interp_inline
|
||||
|
||||
# Bind this without allocations. Sloppy primitive this-values still need
|
||||
# ToObject(), so they use the C++ inline-frame helper.
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ int main()
|
|||
EMIT_OFFSET(SHARED_FUNCTION_INSTANCE_DATA_USES_THIS, SharedFunctionInstanceData, m_uses_this);
|
||||
EMIT_OFFSET(SHARED_FUNCTION_INSTANCE_DATA_CAN_INLINE_CALL, SharedFunctionInstanceData, m_can_inline_call);
|
||||
outln("const SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_CAN_INLINE_CALL = {}", SharedFunctionInstanceData::asm_call_metadata_can_inline_call);
|
||||
outln("const SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_FUNCTION_ENVIRONMENT_NEEDED = {}", SharedFunctionInstanceData::asm_call_metadata_function_environment_needed);
|
||||
outln("const SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_NEEDS_ENVIRONMENT_OR_THIS_VALUE_RESOLUTION = {}", SharedFunctionInstanceData::asm_call_metadata_needs_environment_or_this_value_resolution);
|
||||
outln("const SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_USES_THIS = {}", SharedFunctionInstanceData::asm_call_metadata_uses_this);
|
||||
outln("const SHARED_FUNCTION_INSTANCE_DATA_ASM_CALL_METADATA_STRICT = {}", SharedFunctionInstanceData::asm_call_metadata_strict);
|
||||
|
||||
|
|
|
|||
|
|
@ -2728,11 +2728,14 @@ ThrowCompletionOr<void> ResolveThisBinding::execute_impl(VM& vm) const
|
|||
// OPTIMIZATION: Because the value of 'this' cannot be reassigned during a function execution, it's
|
||||
// resolved once and then saved for subsequent use.
|
||||
auto& running_execution_context = vm.running_execution_context();
|
||||
if (auto function = running_execution_context.function; function && is<ECMAScriptFunctionObject>(*function) && !static_cast<ECMAScriptFunctionObject&>(*function).allocates_function_environment()) {
|
||||
cached_this_value = running_execution_context.this_value.value();
|
||||
} else {
|
||||
cached_this_value = TRY(vm.resolve_this_binding());
|
||||
if (auto function = running_execution_context.function; function && is<ECMAScriptFunctionObject>(*function)) {
|
||||
auto& ecmascript_function = static_cast<ECMAScriptFunctionObject&>(*function);
|
||||
if (!ecmascript_function.allocates_function_environment() && !ecmascript_function.this_value_needs_environment_resolution()) {
|
||||
cached_this_value = running_execution_context.this_value.value();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
cached_this_value = TRY(vm.resolve_this_binding());
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ public:
|
|||
[[nodiscard]] bool is_arrow_function() const { return shared_data().m_is_arrow_function; }
|
||||
[[nodiscard]] bool is_class_constructor() const { return shared_data().m_is_class_constructor; }
|
||||
[[nodiscard]] bool uses_this() const { return shared_data().m_uses_this; }
|
||||
[[nodiscard]] bool this_value_needs_environment_resolution() const
|
||||
{
|
||||
return shared_data().m_this_value_needs_environment_resolution;
|
||||
}
|
||||
[[nodiscard]] i32 function_length() const { return shared_data().m_function_length; }
|
||||
|
||||
Object* home_object() const { return m_home_object; }
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ void SharedFunctionInstanceData::update_asm_call_metadata()
|
|||
m_asm_call_metadata = m_formal_parameter_count;
|
||||
if (m_can_inline_call)
|
||||
m_asm_call_metadata |= asm_call_metadata_can_inline_call;
|
||||
if (m_function_environment_needed)
|
||||
m_asm_call_metadata |= asm_call_metadata_function_environment_needed;
|
||||
if (m_function_environment_needed || m_this_value_needs_environment_resolution)
|
||||
m_asm_call_metadata |= asm_call_metadata_needs_environment_or_this_value_resolution;
|
||||
if (m_uses_this)
|
||||
m_asm_call_metadata |= asm_call_metadata_uses_this;
|
||||
if (m_strict)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class JS_API SharedFunctionInstanceData final : public GC::Cell {
|
|||
|
||||
public:
|
||||
static constexpr u64 asm_call_metadata_can_inline_call = 1ull << 32;
|
||||
static constexpr u64 asm_call_metadata_function_environment_needed = 1ull << 33;
|
||||
static constexpr u64 asm_call_metadata_needs_environment_or_this_value_resolution = 1ull << 33;
|
||||
static constexpr u64 asm_call_metadata_uses_this = 1ull << 34;
|
||||
static constexpr u64 asm_call_metadata_strict = 1ull << 35;
|
||||
|
||||
|
|
@ -123,6 +123,7 @@ public:
|
|||
Vector<FunctionToInitialize> m_functions_to_initialize;
|
||||
bool m_arguments_object_needed { false };
|
||||
bool m_function_environment_needed { false };
|
||||
bool m_this_value_needs_environment_resolution { false };
|
||||
bool m_uses_this { false };
|
||||
Vector<VarBinding> m_var_names_to_initialize_binding;
|
||||
Vector<Utf16FlyString> m_function_names_to_initialize_binding;
|
||||
|
|
|
|||
|
|
@ -121,9 +121,9 @@ fn generate_expression_inner(
|
|||
|
||||
// === This ===
|
||||
ExpressionKind::This => {
|
||||
// OPTIMIZATION: When function_environment_needed is false, the `this`
|
||||
// value is inherited from the outer function and already in the register.
|
||||
if generator.function_environment_needed {
|
||||
// OPTIMIZATION: When this_value_needs_environment_resolution is false,
|
||||
// `this` is already cached in the function's this register.
|
||||
if generator.this_value_needs_environment_resolution {
|
||||
emit_resolve_this_if_needed(generator);
|
||||
}
|
||||
Some(generator.this_value())
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@ unsafe extern "C" {
|
|||
sfd_ptr: *mut c_void,
|
||||
executable_ptr: *mut c_void,
|
||||
uses_this: bool,
|
||||
this_value_needs_environment_resolution: bool,
|
||||
function_environment_needed: bool,
|
||||
function_environment_bindings_count: usize,
|
||||
might_need_arguments_object: bool,
|
||||
|
|
@ -456,6 +457,7 @@ unsafe fn materialize_shared_function_data(
|
|||
sfd_ptr,
|
||||
executable_ptr,
|
||||
precompiled.metadata.uses_this,
|
||||
precompiled.metadata.this_value_needs_environment_resolution,
|
||||
precompiled.metadata.function_environment_needed,
|
||||
precompiled.metadata.function_environment_bindings_count,
|
||||
precompiled.metadata.might_need_arguments,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ pub struct PendingSharedFunctionData {
|
|||
/// Metadata computed from scope analysis for a SharedFunctionInstanceData.
|
||||
pub struct FunctionSfdMetadata {
|
||||
pub uses_this: bool,
|
||||
pub this_value_needs_environment_resolution: bool,
|
||||
pub function_environment_needed: bool,
|
||||
pub function_environment_bindings_count: usize,
|
||||
pub var_environment_bindings_count: usize,
|
||||
|
|
@ -235,7 +236,7 @@ pub struct Generator {
|
|||
|
||||
// --- Codegen state ---
|
||||
pub strict: bool,
|
||||
pub function_environment_needed: bool,
|
||||
pub this_value_needs_environment_resolution: bool,
|
||||
pub enclosing_function_kind: FunctionKind,
|
||||
pub local_variables: Vec<LocalVariable>,
|
||||
pub initialized_locals: Vec<bool>,
|
||||
|
|
@ -382,7 +383,7 @@ impl Generator {
|
|||
next_object_shape_cache: 0,
|
||||
next_object_property_iterator_cache: 0,
|
||||
strict: false,
|
||||
function_environment_needed: true,
|
||||
this_value_needs_environment_resolution: true,
|
||||
enclosing_function_kind: FunctionKind::Normal,
|
||||
local_variables: Vec::new(),
|
||||
initialized_locals: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -2228,13 +2228,13 @@ fn compile_function_payload_to_bytecode(
|
|||
_ => None,
|
||||
};
|
||||
|
||||
// Compute SFD metadata before codegen so the generator can use
|
||||
// function_environment_needed to optimize `this` access.
|
||||
// Compute SFD metadata before codegen so the generator can optimize
|
||||
// direct `this` access when it does not need environment resolution.
|
||||
let sfd_metadata = compute_sfd_metadata(&function_data);
|
||||
|
||||
let mut generator = bytecode::generator::Generator::new();
|
||||
generator.strict = function_data.is_strict_mode;
|
||||
generator.function_environment_needed = sfd_metadata.function_environment_needed;
|
||||
generator.this_value_needs_environment_resolution = sfd_metadata.this_value_needs_environment_resolution;
|
||||
generator.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled;
|
||||
generator.function_table = payload.function_table;
|
||||
generator.source_len = source_len;
|
||||
|
|
@ -2502,15 +2502,17 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato
|
|||
}
|
||||
}
|
||||
|
||||
let this_value_needs_environment_resolution = bsi.uses_this_from_env;
|
||||
let function_environment_needed = arguments_object_needs_binding
|
||||
|| function_environment_bindings_count > 0
|
||||
|| var_environment_bindings_count > 0
|
||||
|| lex_environment_bindings_count > 0
|
||||
|| bsi.uses_this_from_env
|
||||
|| (!is_arrow && bsi.uses_this_from_env)
|
||||
|| bsi.contains_eval;
|
||||
|
||||
bytecode::generator::FunctionSfdMetadata {
|
||||
uses_this: bsi.uses_this,
|
||||
this_value_needs_environment_resolution,
|
||||
function_environment_needed,
|
||||
function_environment_bindings_count,
|
||||
var_environment_bindings_count,
|
||||
|
|
@ -2528,6 +2530,7 @@ unsafe fn write_sfd_metadata(sfd_ptr: *mut c_void, metadata: &bytecode::generato
|
|||
rust_sfd_set_metadata(
|
||||
sfd_ptr,
|
||||
metadata.uses_this,
|
||||
metadata.this_value_needs_environment_resolution,
|
||||
metadata.function_environment_needed,
|
||||
metadata.function_environment_bindings_count,
|
||||
metadata.might_need_arguments,
|
||||
|
|
@ -2627,6 +2630,7 @@ unsafe extern "C" {
|
|||
fn rust_sfd_set_metadata(
|
||||
sfd_ptr: *mut c_void,
|
||||
uses_this: bool,
|
||||
this_value_needs_environment_resolution: bool,
|
||||
function_environment_needed: bool,
|
||||
function_environment_bindings_count: usize,
|
||||
might_need_arguments_object: bool,
|
||||
|
|
|
|||
|
|
@ -976,7 +976,8 @@ extern "C" void* rust_create_sfd(
|
|||
|
||||
// Set parsing insights that must be available before lazy compilation.
|
||||
shared->m_uses_this = data->uses_this;
|
||||
if (data->uses_this_from_environment)
|
||||
shared->m_this_value_needs_environment_resolution = data->uses_this_from_environment;
|
||||
if (data->uses_this_from_environment && !data->is_arrow)
|
||||
shared->m_function_environment_needed = true;
|
||||
shared->update_asm_call_metadata();
|
||||
|
||||
|
|
@ -993,6 +994,7 @@ extern "C" void* rust_create_sfd(
|
|||
extern "C" void rust_sfd_set_metadata(
|
||||
void* sfd_ptr,
|
||||
bool uses_this,
|
||||
bool this_value_needs_environment_resolution,
|
||||
bool function_environment_needed,
|
||||
size_t function_environment_bindings_count,
|
||||
bool might_need_arguments_object,
|
||||
|
|
@ -1000,6 +1002,7 @@ extern "C" void rust_sfd_set_metadata(
|
|||
{
|
||||
auto& shared = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
|
||||
shared.m_uses_this = uses_this;
|
||||
shared.m_this_value_needs_environment_resolution = this_value_needs_environment_resolution;
|
||||
shared.m_function_environment_needed = function_environment_needed;
|
||||
shared.update_asm_call_metadata();
|
||||
shared.m_function_environment_bindings_count = function_environment_bindings_count;
|
||||
|
|
@ -1026,6 +1029,7 @@ extern "C" void rust_sfd_set_precompiled_executable(
|
|||
void* sfd_ptr,
|
||||
void* executable_ptr,
|
||||
bool uses_this,
|
||||
bool this_value_needs_environment_resolution,
|
||||
bool function_environment_needed,
|
||||
size_t function_environment_bindings_count,
|
||||
bool might_need_arguments_object,
|
||||
|
|
@ -1035,6 +1039,7 @@ extern "C" void rust_sfd_set_precompiled_executable(
|
|||
auto& executable = *static_cast<JS::Bytecode::Executable*>(executable_ptr);
|
||||
|
||||
shared.m_uses_this = uses_this;
|
||||
shared.m_this_value_needs_environment_resolution = this_value_needs_environment_resolution;
|
||||
shared.m_function_environment_needed = function_environment_needed;
|
||||
shared.m_function_environment_bindings_count = function_environment_bindings_count;
|
||||
shared.m_might_need_arguments_object = might_need_arguments_object;
|
||||
|
|
|
|||
Loading…
Reference in a new issue