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.
162 lines
5.9 KiB
C++
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();
|
|
}
|
|
|
|
}
|