mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 04:25:13 +00:00
LibJS/Bytecode: Add Call opcode for fixed-argument-count calls
This avoids the overhead of allocating a new Array on every function call, saving a substantial amount of time and avoiding GC thrash. This patch only makes use of Op::Call in CallExpression. There are other places we should codegen this op. We should also do the same for super expression calls. ~5% speed-up on Kraken/stanford-crypto-ccm.js
This commit is contained in:
parent
7eb87dec9f
commit
c37b204ce1
Notes:
sideshowbarker
2024-07-17 23:02:37 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/c37b204ce1 Pull-request: https://github.com/SerenityOS/serenity/pull/19756 Reviewed-by: https://github.com/Hendiadyoin1
5 changed files with 135 additions and 41 deletions
|
@ -1463,8 +1463,6 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
|
|||
generator.emit<Bytecode::Op::Store>(callee_reg);
|
||||
}
|
||||
|
||||
TRY(arguments_to_array_for_call(generator, arguments()));
|
||||
|
||||
Bytecode::Op::CallType call_type;
|
||||
if (is<NewExpression>(*this)) {
|
||||
call_type = Bytecode::Op::CallType::Construct;
|
||||
|
@ -1478,7 +1476,26 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
|
|||
if (auto expression_string = this->expression_string(); expression_string.has_value())
|
||||
expression_string_index = generator.intern_string(expression_string.release_value());
|
||||
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(call_type, callee_reg, this_reg, expression_string_index);
|
||||
bool has_spread = any_of(arguments(), [](auto& argument) { return argument.is_spread; });
|
||||
|
||||
if (has_spread) {
|
||||
TRY(arguments_to_array_for_call(generator, arguments()));
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(call_type, callee_reg, this_reg, expression_string_index);
|
||||
} else {
|
||||
Optional<Bytecode::Register> first_argument_reg {};
|
||||
for (size_t i = 0; i < arguments().size(); ++i) {
|
||||
auto reg = generator.allocate_register();
|
||||
if (!first_argument_reg.has_value())
|
||||
first_argument_reg = reg;
|
||||
}
|
||||
u32 register_offset = 0;
|
||||
for (auto const& argument : arguments()) {
|
||||
TRY(argument.value->generate_bytecode(generator));
|
||||
generator.emit<Bytecode::Op::Store>(Bytecode::Register { first_argument_reg.value().index() + register_offset });
|
||||
register_offset += 1;
|
||||
}
|
||||
generator.emit<Bytecode::Op::Call>(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
O(BitwiseOr) \
|
||||
O(BitwiseXor) \
|
||||
O(BlockDeclarationInstantiation) \
|
||||
O(Call) \
|
||||
O(CallWithArgumentArray) \
|
||||
O(ConcatString) \
|
||||
O(ContinuePendingUnwind) \
|
||||
|
|
|
@ -694,42 +694,39 @@ static MarkedVector<Value> argument_list_evaluation(Bytecode::Interpreter& inter
|
|||
return argument_values;
|
||||
}
|
||||
|
||||
Completion CallWithArgumentArray::throw_type_error_for_callee(Bytecode::Interpreter& interpreter, StringView callee_type) const
|
||||
static Completion throw_type_error_for_callee(Bytecode::Interpreter& interpreter, auto& call, StringView callee_type)
|
||||
{
|
||||
auto& vm = interpreter.vm();
|
||||
auto callee = interpreter.reg(m_callee);
|
||||
auto callee = interpreter.reg(call.callee());
|
||||
|
||||
if (m_expression_string.has_value())
|
||||
return vm.throw_completion<TypeError>(ErrorType::IsNotAEvaluatedFrom, TRY_OR_THROW_OOM(vm, callee.to_string_without_side_effects()), callee_type, interpreter.current_executable().get_string(m_expression_string->value()));
|
||||
if (call.expression_string().has_value())
|
||||
return vm.throw_completion<TypeError>(ErrorType::IsNotAEvaluatedFrom, TRY_OR_THROW_OOM(vm, callee.to_string_without_side_effects()), callee_type, interpreter.current_executable().get_string(call.expression_string()->value()));
|
||||
|
||||
return vm.throw_completion<TypeError>(ErrorType::IsNotA, TRY_OR_THROW_OOM(vm, callee.to_string_without_side_effects()), callee_type);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
static ThrowCompletionOr<void> throw_if_needed_for_call(Interpreter& interpreter, auto& call, Value callee)
|
||||
{
|
||||
if (call.call_type() == CallType::Call && !callee.is_function())
|
||||
return throw_type_error_for_callee(interpreter, call, "function"sv);
|
||||
if (call.call_type() == CallType::Construct && !callee.is_constructor())
|
||||
return throw_type_error_for_callee(interpreter, call, "constructor"sv);
|
||||
return {};
|
||||
}
|
||||
|
||||
static ThrowCompletionOr<void> perform_call(Interpreter& interpreter, auto& call, Value callee, MarkedVector<Value> argument_values)
|
||||
{
|
||||
auto& vm = interpreter.vm();
|
||||
|
||||
auto callee = interpreter.reg(m_callee);
|
||||
|
||||
if (m_type == CallType::Call && !callee.is_function())
|
||||
return throw_type_error_for_callee(interpreter, "function"sv);
|
||||
if (m_type == CallType::Construct && !callee.is_constructor())
|
||||
return throw_type_error_for_callee(interpreter, "constructor"sv);
|
||||
|
||||
auto this_value = interpreter.reg(call.this_value());
|
||||
auto& function = callee.as_function();
|
||||
|
||||
auto this_value = interpreter.reg(m_this_value);
|
||||
|
||||
auto argument_values = argument_list_evaluation(interpreter);
|
||||
|
||||
Value return_value;
|
||||
if (m_type == CallType::DirectEval) {
|
||||
if (call.call_type() == CallType::DirectEval) {
|
||||
if (callee == interpreter.realm().intrinsics().eval_function())
|
||||
return_value = TRY(perform_eval(vm, !argument_values.is_empty() ? argument_values[0].value_or(JS::js_undefined()) : js_undefined(), vm.in_strict_mode() ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct));
|
||||
else
|
||||
return_value = TRY(call(vm, function, this_value, move(argument_values)));
|
||||
} else if (m_type == CallType::Call)
|
||||
return_value = TRY(call(vm, function, this_value, move(argument_values)));
|
||||
return_value = TRY(JS::call(vm, function, this_value, move(argument_values)));
|
||||
} else if (call.call_type() == CallType::Call)
|
||||
return_value = TRY(JS::call(vm, function, this_value, move(argument_values)));
|
||||
else
|
||||
return_value = TRY(construct(vm, function, move(argument_values)));
|
||||
|
||||
|
@ -737,6 +734,29 @@ ThrowCompletionOr<void> CallWithArgumentArray::execute_impl(Bytecode::Interprete
|
|||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> Call::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto& vm = interpreter.vm();
|
||||
auto callee = interpreter.reg(m_callee);
|
||||
|
||||
TRY(throw_if_needed_for_call(interpreter, *this, callee));
|
||||
|
||||
MarkedVector<Value> argument_values(vm.heap());
|
||||
argument_values.ensure_capacity(m_argument_count);
|
||||
for (u32 i = 0; i < m_argument_count; ++i) {
|
||||
argument_values.unchecked_append(interpreter.reg(Register { m_first_argument.index() + i }));
|
||||
}
|
||||
return perform_call(interpreter, *this, callee, move(argument_values));
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto callee = interpreter.reg(m_callee);
|
||||
TRY(throw_if_needed_for_call(interpreter, *this, callee));
|
||||
auto argument_values = argument_list_evaluation(interpreter);
|
||||
return perform_call(interpreter, *this, callee, move(argument_values));
|
||||
}
|
||||
|
||||
// 13.3.7.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
|
||||
ThrowCompletionOr<void> SuperCallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
|
@ -896,6 +916,18 @@ void CopyObjectExcludingProperties::replace_references_impl(Register from, Regis
|
|||
}
|
||||
}
|
||||
|
||||
void Call::replace_references_impl(Register from, Register to)
|
||||
{
|
||||
if (m_callee == from)
|
||||
m_callee = to;
|
||||
|
||||
if (m_this_value == from)
|
||||
m_this_value = to;
|
||||
|
||||
if (m_first_argument == from)
|
||||
m_first_argument = to;
|
||||
}
|
||||
|
||||
void CallWithArgumentArray::replace_references_impl(Register from, Register to)
|
||||
{
|
||||
if (m_callee == from)
|
||||
|
@ -1383,24 +1415,32 @@ DeprecatedString JumpUndefined::to_deprecated_string_impl(Bytecode::Executable c
|
|||
return DeprecatedString::formatted("JumpUndefined undefined:{} not undefined:{}", true_string, false_string);
|
||||
}
|
||||
|
||||
static StringView call_type_to_string(CallType type)
|
||||
{
|
||||
switch (type) {
|
||||
case CallType::Call:
|
||||
return ""sv;
|
||||
case CallType::Construct:
|
||||
return " (Construct)"sv;
|
||||
case CallType::DirectEval:
|
||||
return " (DirectEval)"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
DeprecatedString Call::to_deprecated_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
auto type = call_type_to_string(m_type);
|
||||
if (m_expression_string.has_value())
|
||||
return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{} ({})", type, m_callee, m_this_value, m_first_argument, executable.get_string(m_expression_string.value()));
|
||||
return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{}", type, m_callee, m_first_argument, m_this_value);
|
||||
}
|
||||
|
||||
DeprecatedString CallWithArgumentArray::to_deprecated_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
StringView type;
|
||||
switch (m_type) {
|
||||
case CallType::Call:
|
||||
type = ""sv;
|
||||
break;
|
||||
case CallType::Construct:
|
||||
type = " (Construct)"sv;
|
||||
break;
|
||||
case CallType::DirectEval:
|
||||
type = " (DirectEval)"sv;
|
||||
break;
|
||||
}
|
||||
|
||||
auto type = call_type_to_string(m_type);
|
||||
if (m_expression_string.has_value())
|
||||
return DeprecatedString::formatted("CallWithArgumentArray{} callee:{}, this:{}, arguments:[...acc] ({})", type, m_callee, m_this_value, executable.get_string(m_expression_string.value()));
|
||||
|
||||
return DeprecatedString::formatted("CallWithArgumentArray{} callee:{}, this:{}, arguments:[...acc]", type, m_callee, m_this_value);
|
||||
}
|
||||
|
||||
|
|
|
@ -776,6 +776,38 @@ enum class CallType {
|
|||
DirectEval,
|
||||
};
|
||||
|
||||
class Call final : public Instruction {
|
||||
public:
|
||||
Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional<StringTableIndex> expression_string = {})
|
||||
: Instruction(Type::Call)
|
||||
, m_callee(callee)
|
||||
, m_this_value(this_value)
|
||||
, m_first_argument(first_argument)
|
||||
, m_argument_count(argument_count)
|
||||
, m_type(type)
|
||||
, m_expression_string(expression_string)
|
||||
{
|
||||
}
|
||||
|
||||
CallType call_type() const { return m_type; }
|
||||
Register callee() const { return m_callee; }
|
||||
Register this_value() const { return m_this_value; }
|
||||
Optional<StringTableIndex> const& expression_string() const { return m_expression_string; }
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
|
||||
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||
void replace_references_impl(Register, Register);
|
||||
|
||||
private:
|
||||
Register m_callee;
|
||||
Register m_this_value;
|
||||
Register m_first_argument;
|
||||
u32 m_argument_count { 0 };
|
||||
CallType m_type;
|
||||
Optional<StringTableIndex> m_expression_string;
|
||||
};
|
||||
|
||||
class CallWithArgumentArray final : public Instruction {
|
||||
public:
|
||||
CallWithArgumentArray(CallType type, Register callee, Register this_value, Optional<StringTableIndex> expression_string = {})
|
||||
|
@ -787,13 +819,16 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
CallType call_type() const { return m_type; }
|
||||
Register callee() const { return m_callee; }
|
||||
Register this_value() const { return m_this_value; }
|
||||
Optional<StringTableIndex> const& expression_string() const { return m_expression_string; }
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
|
||||
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||
void replace_references_impl(Register, Register);
|
||||
|
||||
Completion throw_type_error_for_callee(Bytecode::Interpreter&, StringView callee_type) const;
|
||||
|
||||
private:
|
||||
Register m_callee;
|
||||
Register m_this_value;
|
||||
|
|
|
@ -124,6 +124,7 @@ static NonnullOwnPtr<BasicBlock> eliminate_loads(BasicBlock const& block, size_t
|
|||
// Attribute accesses (`a.o` or `a[o]`) may result in calls to getters or setters
|
||||
// or may trigger proxies
|
||||
// So these are treated like calls
|
||||
case Call:
|
||||
case CallWithArgumentArray:
|
||||
// Calls, especially to local functions and eval, may poison visible and
|
||||
// cached variables, hence we need to clear the lookup cache after emitting them
|
||||
|
|
Loading…
Add table
Reference in a new issue