LibJS: Split Call Instruction by CallType

Instead of branching on the `CallType` at runtime in a hot
instruction like `Call`, lets emit separate instructions
for each call type during codegen.
This commit is contained in:
Jonne Ransijn 2024-10-31 22:47:30 +01:00 committed by Andreas Kling
parent 80f0900565
commit 641c549463
Notes: github-actions[bot] 2024-11-02 10:16:48 +00:00
4 changed files with 291 additions and 39 deletions

View file

@ -1712,9 +1712,11 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
Optional<ScopedOperand> original_callee;
auto this_value = generator.add_constant(js_undefined());
Bytecode::Op::CallType call_type = Bytecode::Op::CallType::Call;
if (is<NewExpression>(this)) {
original_callee = TRY(m_callee->generate_bytecode(generator)).value();
call_type = Bytecode::Op::CallType::Construct;
} else if (is<MemberExpression>(*m_callee)) {
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
auto base_and_value = TRY(get_base_and_value_from_member_expression(generator, member_expression));
@ -1733,6 +1735,9 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
// NOTE: If the identifier refers to a known "local" or "global", we know it can't be
// a `with` binding, so we can skip this.
auto& identifier = static_cast<Identifier const&>(*m_callee);
if (identifier.string() == "eval"sv) {
call_type = Bytecode::Op::CallType::DirectEval;
}
if (identifier.is_local()) {
auto local = generator.local(identifier.local_variable_index());
if (!generator.is_local_initialized(local.operand().index())) {
@ -1758,15 +1763,6 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
// to avoid overwriting it while evaluating arguments.
auto callee = generator.copy_if_needed_to_preserve_evaluation_order(original_callee.value());
Bytecode::Op::CallType call_type;
if (is<NewExpression>(*this)) {
call_type = Bytecode::Op::CallType::Construct;
} else if (m_callee->is_identifier() && static_cast<Identifier const&>(*m_callee).string() == "eval"sv) {
call_type = Bytecode::Op::CallType::DirectEval;
} else {
call_type = Bytecode::Op::CallType::Call;
}
Optional<Bytecode::StringTableIndex> expression_string_index;
if (auto expression_string = this->expression_string(); expression_string.has_value())
expression_string_index = generator.intern_string(expression_string.release_value());
@ -1784,15 +1780,41 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
auto argument_value = TRY(argument.value->generate_bytecode(generator)).value();
argument_operands.append(generator.copy_if_needed_to_preserve_evaluation_order(argument_value));
}
generator.emit_with_extra_operand_slots<Bytecode::Op::Call>(
argument_operands.size(),
call_type,
dst,
callee,
this_value,
argument_operands,
expression_string_index,
builtin);
if (builtin.has_value()) {
VERIFY(call_type == Op::CallType::Call);
generator.emit_with_extra_operand_slots<Bytecode::Op::CallBuiltin>(
argument_operands.size(),
dst,
callee,
this_value,
argument_operands,
builtin.value(),
expression_string_index);
} else if (call_type == Op::CallType::Construct) {
generator.emit_with_extra_operand_slots<Bytecode::Op::CallConstruct>(
argument_operands.size(),
dst,
callee,
this_value,
argument_operands,
expression_string_index);
} else if (call_type == Op::CallType::DirectEval) {
generator.emit_with_extra_operand_slots<Bytecode::Op::CallDirectEval>(
argument_operands.size(),
dst,
callee,
this_value,
argument_operands,
expression_string_index);
} else {
generator.emit_with_extra_operand_slots<Bytecode::Op::Call>(
argument_operands.size(),
dst,
callee,
this_value,
argument_operands,
expression_string_index);
}
}
return dst;

View file

@ -25,6 +25,9 @@
O(BitwiseXor) \
O(BlockDeclarationInstantiation) \
O(Call) \
O(CallBuiltin) \
O(CallConstruct) \
O(CallDirectEval) \
O(CallWithArgumentArray) \
O(Catch) \
O(ConcatString) \

View file

@ -569,6 +569,9 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
HANDLE_INSTRUCTION(BitwiseXor);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(BlockDeclarationInstantiation);
HANDLE_INSTRUCTION(Call);
HANDLE_INSTRUCTION(CallBuiltin);
HANDLE_INSTRUCTION(CallConstruct);
HANDLE_INSTRUCTION(CallDirectEval);
HANDLE_INSTRUCTION(CallWithArgumentArray);
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Catch);
HANDLE_INSTRUCTION(ConcatString);
@ -2535,20 +2538,57 @@ ThrowCompletionOr<void> Call::execute_impl(Bytecode::Interpreter& interpreter) c
{
auto callee = interpreter.get(m_callee);
TRY(throw_if_needed_for_call(interpreter, callee, call_type(), expression_string()));
TRY(throw_if_needed_for_call(interpreter, callee, CallType::Call, expression_string()));
auto argument_values = interpreter.allocate_argument_values(m_argument_count);
for (size_t i = 0; i < m_argument_count; ++i)
argument_values[i] = interpreter.get(m_arguments[i]);
interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), CallType::Call, callee, argument_values)));
return {};
}
ThrowCompletionOr<void> CallConstruct::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto callee = interpreter.get(m_callee);
TRY(throw_if_needed_for_call(interpreter, callee, CallType::Construct, expression_string()));
auto argument_values = interpreter.allocate_argument_values(m_argument_count);
for (size_t i = 0; i < m_argument_count; ++i)
argument_values[i] = interpreter.get(m_arguments[i]);
interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), CallType::Construct, callee, argument_values)));
return {};
}
ThrowCompletionOr<void> CallDirectEval::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto callee = interpreter.get(m_callee);
TRY(throw_if_needed_for_call(interpreter, callee, CallType::DirectEval, expression_string()));
auto argument_values = interpreter.allocate_argument_values(m_argument_count);
for (size_t i = 0; i < m_argument_count; ++i)
argument_values[i] = interpreter.get(m_arguments[i]);
interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), CallType::DirectEval, callee, argument_values)));
return {};
}
ThrowCompletionOr<void> CallBuiltin::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto callee = interpreter.get(m_callee);
TRY(throw_if_needed_for_call(interpreter, callee, CallType::Call, expression_string()));
if (m_argument_count == Bytecode::builtin_argument_count(m_builtin) && callee.is_object() && interpreter.realm().get_builtin_value(m_builtin) == &callee.as_object()) {
interpreter.set(dst(), TRY(dispatch_builtin_call(interpreter, m_builtin, { m_arguments, m_argument_count })));
if (m_builtin.has_value()
&& m_argument_count == Bytecode::builtin_argument_count(m_builtin.value())
&& callee.is_object()
&& interpreter.realm().get_builtin_value(m_builtin.value()) == &callee.as_object()) {
interpreter.set(dst(), TRY(dispatch_builtin_call(interpreter, m_builtin.value(), { m_arguments, m_argument_count })));
return {};
}
auto argument_values = interpreter.allocate_argument_values(m_argument_count);
for (size_t i = 0; i < m_argument_count; ++i)
argument_values[i] = interpreter.get(m_arguments[i]);
interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), call_type(), callee, argument_values)));
interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), CallType::Call, callee, argument_values)));
return {};
}
@ -3289,21 +3329,67 @@ static StringView call_type_to_string(CallType type)
ByteString Call::to_byte_string_impl(Bytecode::Executable const& executable) const
{
auto type = call_type_to_string(m_type);
StringBuilder builder;
builder.appendff("Call{} {}, {}, {}, "sv,
type,
builder.appendff("Call {}, {}, {}, "sv,
format_operand("dst"sv, m_dst, executable),
format_operand("callee"sv, m_callee, executable),
format_operand("this"sv, m_this_value, executable));
builder.append(format_operand_list("args"sv, { m_arguments, m_argument_count }, executable));
if (m_builtin.has_value()) {
builder.appendff(", (builtin:{})", m_builtin.value());
if (m_expression_string.has_value()) {
builder.appendff(", `{}`", executable.get_string(m_expression_string.value()));
}
return builder.to_byte_string();
}
ByteString CallConstruct::to_byte_string_impl(Bytecode::Executable const& executable) const
{
StringBuilder builder;
builder.appendff("CallConstruct {}, {}, {}, "sv,
format_operand("dst"sv, m_dst, executable),
format_operand("callee"sv, m_callee, executable),
format_operand("this"sv, m_this_value, executable));
builder.append(format_operand_list("args"sv, { m_arguments, m_argument_count }, executable));
if (m_expression_string.has_value()) {
builder.appendff(", `{}`", executable.get_string(m_expression_string.value()));
}
return builder.to_byte_string();
}
ByteString CallDirectEval::to_byte_string_impl(Bytecode::Executable const& executable) const
{
StringBuilder builder;
builder.appendff("CallDirectEval {}, {}, {}, "sv,
format_operand("dst"sv, m_dst, executable),
format_operand("callee"sv, m_callee, executable),
format_operand("this"sv, m_this_value, executable));
builder.append(format_operand_list("args"sv, { m_arguments, m_argument_count }, executable));
if (m_expression_string.has_value()) {
builder.appendff(", `{}`", executable.get_string(m_expression_string.value()));
}
return builder.to_byte_string();
}
ByteString CallBuiltin::to_byte_string_impl(Bytecode::Executable const& executable) const
{
StringBuilder builder;
builder.appendff("CallBuiltin {}, {}, {}, "sv,
format_operand("dst"sv, m_dst, executable),
format_operand("callee"sv, m_callee, executable),
format_operand("this"sv, m_this_value, executable));
builder.append(format_operand_list("args"sv, { m_arguments, m_argument_count }, executable));
builder.appendff(", (builtin:{})", m_builtin);
if (m_expression_string.has_value()) {
builder.appendff(", `{}`", executable.get_string(m_expression_string.value()));
}

View file

@ -1726,14 +1726,12 @@ class Call final : public Instruction {
public:
static constexpr bool IsVariableLength = true;
Call(CallType type, Operand dst, Operand callee, Operand this_value, ReadonlySpan<ScopedOperand> arguments, Optional<StringTableIndex> expression_string = {}, Optional<Builtin> builtin = {})
Call(Operand dst, Operand callee, Operand this_value, ReadonlySpan<ScopedOperand> arguments, Optional<StringTableIndex> expression_string = {})
: Instruction(Type::Call)
, m_dst(dst)
, m_callee(callee)
, m_this_value(this_value)
, m_argument_count(arguments.size())
, m_type(type)
, m_builtin(builtin)
, m_expression_string(expression_string)
{
for (size_t i = 0; i < arguments.size(); ++i)
@ -1745,7 +1743,6 @@ public:
return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * m_argument_count);
}
CallType call_type() const { return m_type; }
Operand dst() const { return m_dst; }
Operand callee() const { return m_callee; }
Operand this_value() const { return m_this_value; }
@ -1753,8 +1750,6 @@ public:
u32 argument_count() const { return m_argument_count; }
Optional<Builtin> const& builtin() const { return m_builtin; }
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
void visit_operands_impl(Function<void(Operand&)> visitor)
@ -1771,8 +1766,154 @@ private:
Operand m_callee;
Operand m_this_value;
u32 m_argument_count { 0 };
CallType m_type;
Optional<Builtin> m_builtin;
Optional<StringTableIndex> m_expression_string;
Operand m_arguments[];
};
class CallBuiltin final : public Instruction {
public:
static constexpr bool IsVariableLength = true;
CallBuiltin(Operand dst, Operand callee, Operand this_value, ReadonlySpan<ScopedOperand> arguments, Builtin builtin, Optional<StringTableIndex> expression_string = {})
: Instruction(Type::CallBuiltin)
, m_dst(dst)
, m_callee(callee)
, m_this_value(this_value)
, m_argument_count(arguments.size())
, m_builtin(builtin)
, m_expression_string(expression_string)
{
for (size_t i = 0; i < arguments.size(); ++i)
m_arguments[i] = arguments[i];
}
size_t length_impl() const
{
return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * m_argument_count);
}
Operand dst() const { return m_dst; }
Operand callee() const { return m_callee; }
Operand this_value() const { return m_this_value; }
Optional<StringTableIndex> const& expression_string() const { return m_expression_string; }
u32 argument_count() const { return m_argument_count; }
Builtin const& builtin() const { return m_builtin; }
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
void visit_operands_impl(Function<void(Operand&)> visitor)
{
visitor(m_dst);
visitor(m_callee);
visitor(m_this_value);
for (size_t i = 0; i < m_argument_count; i++)
visitor(m_arguments[i]);
}
private:
Operand m_dst;
Operand m_callee;
Operand m_this_value;
u32 m_argument_count { 0 };
Builtin m_builtin;
Optional<StringTableIndex> m_expression_string;
Operand m_arguments[];
};
class CallConstruct final : public Instruction {
public:
static constexpr bool IsVariableLength = true;
CallConstruct(Operand dst, Operand callee, Operand this_value, ReadonlySpan<ScopedOperand> arguments, Optional<StringTableIndex> expression_string = {})
: Instruction(Type::CallConstruct)
, m_dst(dst)
, m_callee(callee)
, m_this_value(this_value)
, m_argument_count(arguments.size())
, m_expression_string(expression_string)
{
for (size_t i = 0; i < arguments.size(); ++i)
m_arguments[i] = arguments[i];
}
size_t length_impl() const
{
return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * m_argument_count);
}
Operand dst() const { return m_dst; }
Operand callee() const { return m_callee; }
Operand this_value() const { return m_this_value; }
Optional<StringTableIndex> const& expression_string() const { return m_expression_string; }
u32 argument_count() const { return m_argument_count; }
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
void visit_operands_impl(Function<void(Operand&)> visitor)
{
visitor(m_dst);
visitor(m_callee);
visitor(m_this_value);
for (size_t i = 0; i < m_argument_count; i++)
visitor(m_arguments[i]);
}
private:
Operand m_dst;
Operand m_callee;
Operand m_this_value;
u32 m_argument_count { 0 };
Optional<StringTableIndex> m_expression_string;
Operand m_arguments[];
};
class CallDirectEval final : public Instruction {
public:
static constexpr bool IsVariableLength = true;
CallDirectEval(Operand dst, Operand callee, Operand this_value, ReadonlySpan<ScopedOperand> arguments, Optional<StringTableIndex> expression_string = {})
: Instruction(Type::CallDirectEval)
, m_dst(dst)
, m_callee(callee)
, m_this_value(this_value)
, m_argument_count(arguments.size())
, m_expression_string(expression_string)
{
for (size_t i = 0; i < arguments.size(); ++i)
m_arguments[i] = arguments[i];
}
size_t length_impl() const
{
return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * m_argument_count);
}
Operand dst() const { return m_dst; }
Operand callee() const { return m_callee; }
Operand this_value() const { return m_this_value; }
Optional<StringTableIndex> const& expression_string() const { return m_expression_string; }
u32 argument_count() const { return m_argument_count; }
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
void visit_operands_impl(Function<void(Operand&)> visitor)
{
visitor(m_dst);
visitor(m_callee);
visitor(m_this_value);
for (size_t i = 0; i < m_argument_count; i++)
visitor(m_arguments[i]);
}
private:
Operand m_dst;
Operand m_callee;
Operand m_this_value;
u32 m_argument_count { 0 };
Optional<StringTableIndex> m_expression_string;
Operand m_arguments[];
};