LibJS: Cache JS-to-JS inline call eligibility

Store whether a function can participate in JS-to-JS inline calls on
SharedFunctionInstanceData instead of recomputing the function kind,
class-constructor bit, and bytecode availability at each fast-path
call site.
This commit is contained in:
Andreas Kling 2026-04-13 13:43:55 +02:00 committed by Andreas Kling
parent 4ad800b594
commit df0fdee2a0
8 changed files with 46 additions and 12 deletions

View file

@ -867,15 +867,15 @@ i64 asm_try_inline_call(VM* vm, u32 pc)
if (!is<ECMAScriptFunctionObject>(callee_object))
return 1;
auto& callee_function = static_cast<ECMAScriptFunctionObject&>(callee_object);
if (callee_function.kind() != FunctionKind::Normal
|| callee_function.is_class_constructor()
|| !callee_function.bytecode_executable())
if (!callee_function.can_inline_call())
return 1;
auto& callee_executable = callee_function.inline_call_executable();
u32 return_pc = pc + insn.length();
auto* callee_context = vm->push_inline_frame(
callee_function, *callee_function.bytecode_executable(),
callee_function, callee_executable,
insn.arguments(), return_pc, insn.dst().raw(),
vm->get(insn.this_value()), nullptr, false);

View file

@ -323,15 +323,15 @@ NEVER_INLINE bool VM::try_inline_call(Instruction const& insn, u32 current_pc)
if (!is<ECMAScriptFunctionObject>(callee_object))
return false;
auto& callee_function = static_cast<ECMAScriptFunctionObject&>(callee_object);
if (callee_function.kind() != FunctionKind::Normal
|| callee_function.is_class_constructor()
|| !callee_function.bytecode_executable())
if (!callee_function.can_inline_call())
return false;
auto& callee_executable = callee_function.inline_call_executable();
u32 return_pc = current_pc + instruction.length();
auto* callee_context = push_inline_frame(
callee_function, *callee_function.bytecode_executable(),
callee_function, callee_executable,
instruction.arguments(), return_pc, instruction.dst().raw(),
get(instruction.this_value()), nullptr, false);

View file

@ -139,10 +139,11 @@ void ECMAScriptFunctionObject::initialize(Realm& realm)
void ECMAScriptFunctionObject::get_stack_frame_info(size_t& registers_and_locals_count, ReadonlySpan<Value>& constants, size_t& argument_count)
{
auto& executable = shared_data().m_executable;
auto executable = shared_data().m_executable;
if (!executable) {
auto rust_executable = RustIntegration::compile_function(vm(), *m_shared_data, false);
VERIFY(rust_executable);
m_shared_data->set_executable(rust_executable);
executable = rust_executable;
executable->name = m_shared_data->m_name;
if (Bytecode::g_dump_bytecode)

View file

@ -62,9 +62,15 @@ public:
Utf16FlyString const& name() const { return shared_data().m_name; }
void set_name(Utf16FlyString const& name);
void set_is_class_constructor() { const_cast<SharedFunctionInstanceData&>(shared_data()).m_is_class_constructor = true; }
void set_is_class_constructor() { const_cast<SharedFunctionInstanceData&>(shared_data()).set_is_class_constructor(); }
auto& bytecode_executable() const { return shared_data().m_executable; }
[[nodiscard]] bool can_inline_call() const { return shared_data().can_inline_call(); }
[[nodiscard]] Bytecode::Executable& inline_call_executable() const
{
VERIFY(can_inline_call());
return *shared_data().m_executable;
}
Environment* environment() { return m_environment; }
virtual Realm* realm() const override { return &shape().realm(); }

View file

@ -95,10 +95,11 @@ ThrowCompletionOr<Value> NativeJavaScriptBackedFunction::call()
Bytecode::Executable& NativeJavaScriptBackedFunction::bytecode_executable()
{
auto& executable = m_shared_function_instance_data->m_executable;
auto executable = m_shared_function_instance_data->m_executable;
if (!executable) {
auto rust_executable = RustIntegration::compile_function(vm(), *m_shared_function_instance_data, true);
VERIFY(rust_executable);
m_shared_function_instance_data->set_executable(rust_executable);
executable = rust_executable;
executable->name = m_shared_function_instance_data->m_name;
if (Bytecode::g_dump_bytecode)

View file

@ -39,6 +39,8 @@ SharedFunctionInstanceData::SharedFunctionInstanceData(
m_this_mode = ThisMode::Strict;
else
m_this_mode = ThisMode::Global;
update_can_inline_call();
}
void SharedFunctionInstanceData::visit_edges(Visitor& visitor)
@ -52,6 +54,18 @@ void SharedFunctionInstanceData::visit_edges(Visitor& visitor)
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::finalize()
{
Base::finalize();
@ -69,4 +83,9 @@ void SharedFunctionInstanceData::clear_compile_inputs()
m_rust_function_ast = nullptr;
}
void SharedFunctionInstanceData::update_can_inline_call()
{
m_can_inline_call = m_executable && m_kind == FunctionKind::Normal && !m_is_class_constructor;
}
}

View file

@ -63,6 +63,10 @@ public:
Vector<Utf16FlyString> parameter_names_for_mapped_arguments,
void* rust_function_ast);
void set_executable(GC::Ptr<Bytecode::Executable>);
void set_is_class_constructor();
[[nodiscard]] bool can_inline_call() const { return m_can_inline_call; }
mutable GC::Ptr<Bytecode::Executable> m_executable;
Utf16FlyString m_name;
@ -141,6 +145,9 @@ public:
private:
virtual void visit_edges(Visitor&) override;
void update_can_inline_call();
bool m_can_inline_call { false };
};
}

View file

@ -493,7 +493,7 @@ Optional<Result<ModuleResult, Vector<ParserError>>> compile_parsed_module(Parsed
builder.result.tla_shared_data->m_is_module_wrapper = true;
builder.result.tla_shared_data->m_uses_this = true;
builder.result.tla_shared_data->m_function_environment_needed = true;
builder.result.tla_shared_data->m_executable = tla_exec;
builder.result.tla_shared_data->set_executable(tla_exec);
} else {
builder.result.executable = static_cast<Bytecode::Executable*>(exec_ptr);
}