ladybird/Libraries/LibJS/Runtime/SharedFunctionInstanceData.cpp
Andreas Kling 7c19719946 LibJS: Avoid repeated lazy source decoding
Function.prototype.toString() can ask for the same cached function
source text repeatedly after a script was materialized from the
bytecode cache. In that path SourceCode still owns only the encoded
source bytes, so every request decoded the requested source range
again. Large ASCII bundles made that path expensive enough to stall
Speedometer 2.1's Ember debug test.

Keep the byte-backed SourceCode representation lazy, but let ASCII
UTF-8 source ranges slice the source bytes directly. For other ASCII
byte-backed encodings, first ask the decoder to prove that the source
bytes map to the same UTF-16 code units at the same positions.

Cache extracted function source text on shared function data after the
first request so repeated toString() calls do not keep going back to
SourceCode.

Add bytecode cache coverage for toString() on lazy UTF-8 source bytes,
keep malformed UTF-8 and UTF-16 edge cases on the decoder path, and
cover PDFDocEncoding bytes that are not identity-mapped.
2026-05-19 17:58:08 +02:00

162 lines
5.9 KiB
C++

/*
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/RustIntegration.h>
#include <LibJS/SourceCode.h>
namespace JS {
GC_DEFINE_ALLOCATOR(SharedFunctionInstanceData);
SharedFunctionInstanceData::SharedFunctionInstanceData(
VM&,
FunctionKind kind,
Utf16FlyString name,
i32 function_length,
u32 formal_parameter_count,
bool strict,
bool is_arrow_function,
bool has_simple_parameter_list,
Vector<Utf16FlyString> parameter_names_for_mapped_arguments,
void* rust_function_ast)
: m_name(move(name))
, m_function_length(function_length)
, m_formal_parameter_count(formal_parameter_count)
, m_parameter_names_for_mapped_arguments(move(parameter_names_for_mapped_arguments))
, m_kind(kind)
, m_strict(strict)
, m_is_arrow_function(is_arrow_function)
, m_has_simple_parameter_list(has_simple_parameter_list)
, m_rust_function_ast(rust_function_ast)
, m_use_rust_compilation(true)
{
if (m_is_arrow_function)
m_this_mode = ThisMode::Lexical;
else if (m_strict)
m_this_mode = ThisMode::Strict;
else
m_this_mode = ThisMode::Global;
update_can_inline_call();
}
void SharedFunctionInstanceData::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_executable);
visitor.visit(m_function_environment_shape);
visitor.visit(m_var_environment_shape);
for (auto& function : m_functions_to_initialize)
visitor.visit(function.shared_data);
m_class_field_initializer_name.visit([&](PropertyKey const& key) { key.visit_edges(visitor); }, [](auto&) {});
}
size_t SharedFunctionInstanceData::external_memory_size() const
{
size_t size = utf16_string_external_memory_size(m_source_text_owner);
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_local_variables_names));
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_parameter_names_for_mapped_arguments));
size = saturating_add_external_memory_size(size, hash_map_external_memory_size(m_parameter_names));
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_functions_to_initialize));
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_var_names_to_initialize_binding));
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_function_names_to_initialize_binding));
size = saturating_add_external_memory_size(size, vector_external_memory_size(m_lexical_bindings));
return size;
}
Utf16String SharedFunctionInstanceData::source_text() const
{
if (!m_source_text_owner.is_empty())
return m_source_text_owner;
if (!m_source_code)
return {};
auto old_external_memory_size = utf16_string_external_memory_size(m_source_text_owner);
m_source_text_owner = m_source_code->source_text_from_offsets(m_source_text_offset, m_source_text_length);
auto new_external_memory_size = utf16_string_external_memory_size(m_source_text_owner);
if (new_external_memory_size > old_external_memory_size)
heap().did_allocate_external_memory(new_external_memory_size - old_external_memory_size);
return m_source_text_owner;
}
void SharedFunctionInstanceData::set_source_text(Utf16View source_text)
{
m_source_text_owner = Utf16String::from_utf16(source_text);
m_source_code = nullptr;
m_source_text_offset = 0;
m_source_text_length = 0;
}
void SharedFunctionInstanceData::set_source_text_range(SourceCode const& source_code, size_t source_text_offset, size_t source_text_length)
{
m_source_text_owner = {};
m_source_code = &source_code;
m_source_text_offset = source_text_offset;
m_source_text_length = source_text_length;
}
SharedFunctionInstanceData::~SharedFunctionInstanceData() = default;
void SharedFunctionInstanceData::set_executable(GC::Ptr<Bytecode::Executable> executable)
{
m_executable = executable;
update_can_inline_call();
}
void SharedFunctionInstanceData::set_is_class_constructor()
{
m_is_class_constructor = true;
update_can_inline_call();
}
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_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)
m_asm_call_metadata |= asm_call_metadata_strict;
}
void SharedFunctionInstanceData::finalize()
{
Base::finalize();
RustIntegration::free_function_ast(m_rust_function_ast);
m_rust_function_ast = nullptr;
RustIntegration::free_cached_bytecode_executable(m_cached_bytecode_executable);
m_cached_bytecode_executable = nullptr;
RustIntegration::free_precompiled_bytecode_executable(m_precompiled_bytecode_executable);
m_precompiled_bytecode_executable = nullptr;
}
void SharedFunctionInstanceData::clear_compile_inputs()
{
VERIFY(m_executable);
m_functions_to_initialize.clear();
m_var_names_to_initialize_binding.clear();
m_lexical_bindings.clear();
RustIntegration::free_function_ast(m_rust_function_ast);
m_rust_function_ast = nullptr;
RustIntegration::free_cached_bytecode_executable(m_cached_bytecode_executable);
m_cached_bytecode_executable = nullptr;
RustIntegration::free_precompiled_bytecode_executable(m_precompiled_bytecode_executable);
m_precompiled_bytecode_executable = nullptr;
}
void SharedFunctionInstanceData::update_can_inline_call()
{
m_can_inline_call = m_executable && m_kind == FunctionKind::Normal && !m_is_class_constructor;
update_asm_call_metadata();
}
}