Skip to content

Commit 86b85aa

Browse files
skyrisingawesomekling
authored andcommitted
LibJS: Introduce Builtins
Builtins are functions that can be detected during bytecode generation and enable fast-paths in the JIT.
1 parent b9141d8 commit 86b85aa

File tree

13 files changed

+191
-4
lines changed

13 files changed

+191
-4
lines changed

Meta/gn/secondary/Userland/Libraries/LibJS/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ shared_library("LibJS") {
2525
"AST.cpp",
2626
"Bytecode/ASTCodegen.cpp",
2727
"Bytecode/BasicBlock.cpp",
28+
"Bytecode/Builtins.cpp",
2829
"Bytecode/CodeGenerationError.cpp",
2930
"Bytecode/CommonImplementations.cpp",
3031
"Bytecode/Executable.cpp",

Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1521,13 +1521,16 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
15211521
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
15221522
generator.emit<Bytecode::Op::Store>(this_reg);
15231523

1524+
Optional<Bytecode::Builtin> builtin;
1525+
15241526
if (is<NewExpression>(this)) {
15251527
TRY(m_callee->generate_bytecode(generator));
15261528
generator.emit<Bytecode::Op::Store>(callee_reg);
15271529
} else if (is<MemberExpression>(*m_callee)) {
15281530
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
15291531
TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg));
15301532
generator.emit<Bytecode::Op::Store>(callee_reg);
1533+
builtin = Bytecode::get_builtin(member_expression);
15311534
} else if (is<OptionalChain>(*m_callee)) {
15321535
auto& optional_chain = static_cast<OptionalChain const&>(*m_callee);
15331536
TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg));
@@ -1581,7 +1584,7 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
15811584
generator.emit<Bytecode::Op::Store>(Bytecode::Register { first_argument_reg.value().index() + register_offset });
15821585
register_offset += 1;
15831586
}
1584-
generator.emit<Bytecode::Op::Call>(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index);
1587+
generator.emit<Bytecode::Op::Call>(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index, builtin);
15851588
}
15861589

15871590
return {};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <LibJS/AST.h>
8+
#include <LibJS/Bytecode/Builtins.h>
9+
10+
namespace JS::Bytecode {
11+
12+
Optional<Builtin> get_builtin(MemberExpression const& expression)
13+
{
14+
if (expression.is_computed() || !expression.object().is_identifier() || !expression.property().is_identifier())
15+
return {};
16+
auto base_name = static_cast<Identifier const&>(expression.object()).string();
17+
auto property_name = static_cast<Identifier const&>(expression.property()).string();
18+
#define CHECK_MEMBER_BUILTIN(name, snake_case_name, base, property, ...) \
19+
if (base_name == #base##sv && property_name == #property##sv) \
20+
return Builtin::name;
21+
JS_ENUMERATE_BUILTINS(CHECK_MEMBER_BUILTIN)
22+
#undef CHECK_MEMBER_BUILTIN
23+
return {};
24+
}
25+
26+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/Format.h>
10+
#include <LibJS/Forward.h>
11+
12+
namespace JS::Bytecode {
13+
14+
// TitleCaseName, snake_case_name, base, property, argument_count
15+
#define JS_ENUMERATE_BUILTINS(O)
16+
17+
enum class Builtin {
18+
#define DEFINE_BUILTIN_ENUM(name, ...) name,
19+
JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_ENUM)
20+
#undef DEFINE_BUILTIN_ENUM
21+
__Count,
22+
};
23+
24+
static StringView builtin_name(Builtin value)
25+
{
26+
switch (value) {
27+
#define DEFINE_BUILTIN_CASE(name, snake_case_name, base, property, ...) \
28+
case Builtin::name: \
29+
return #base "." #property##sv;
30+
JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_CASE)
31+
#undef DEFINE_BUILTIN_CASE
32+
case Builtin::__Count:
33+
VERIFY_NOT_REACHED();
34+
}
35+
VERIFY_NOT_REACHED();
36+
}
37+
38+
inline size_t builtin_argument_count(Builtin value)
39+
{
40+
switch (value) {
41+
#define DEFINE_BUILTIN_CASE(name, snake_case_name, base, property, arg_count, ...) \
42+
case Builtin::name: \
43+
return arg_count;
44+
JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_CASE)
45+
#undef DEFINE_BUILTIN_CASE
46+
case Builtin::__Count:
47+
VERIFY_NOT_REACHED();
48+
}
49+
VERIFY_NOT_REACHED();
50+
}
51+
52+
Optional<Builtin> get_builtin(MemberExpression const& expression);
53+
54+
}
55+
56+
namespace AK {
57+
58+
template<>
59+
struct Formatter<JS::Bytecode::Builtin> : Formatter<StringView> {
60+
ErrorOr<void> format(FormatBuilder& builder, JS::Bytecode::Builtin value)
61+
{
62+
return Formatter<StringView>::format(builder, builtin_name(value));
63+
}
64+
};
65+
66+
}

Userland/Libraries/LibJS/Bytecode/Interpreter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,8 @@ static StringView call_type_to_string(CallType type)
15141514
DeprecatedString Call::to_deprecated_string_impl(Bytecode::Executable const& executable) const
15151515
{
15161516
auto type = call_type_to_string(m_type);
1517+
if (m_builtin.has_value())
1518+
return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{} (builtin {})", type, m_callee, m_this_value, m_first_argument, m_builtin.value());
15171519
if (m_expression_string.has_value())
15181520
return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{} ({})", type, m_callee, m_this_value, m_first_argument, executable.get_string(m_expression_string.value()));
15191521
return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{}", type, m_callee, m_first_argument, m_this_value);

Userland/Libraries/LibJS/Bytecode/Op.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <AK/StdLibExtras.h>
1212
#include <LibCrypto/BigInt/SignedBigInteger.h>
13+
#include <LibJS/Bytecode/Builtins.h>
1314
#include <LibJS/Bytecode/IdentifierTable.h>
1415
#include <LibJS/Bytecode/Instruction.h>
1516
#include <LibJS/Bytecode/Label.h>
@@ -984,14 +985,15 @@ enum class CallType {
984985

985986
class Call final : public Instruction {
986987
public:
987-
Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional<StringTableIndex> expression_string = {})
988+
Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional<StringTableIndex> expression_string = {}, Optional<Builtin> builtin = {})
988989
: Instruction(Type::Call, sizeof(*this))
989990
, m_callee(callee)
990991
, m_this_value(this_value)
991992
, m_first_argument(first_argument)
992993
, m_argument_count(argument_count)
993994
, m_type(type)
994995
, m_expression_string(expression_string)
996+
, m_builtin(builtin)
995997
{
996998
}
997999

@@ -1003,6 +1005,8 @@ class Call final : public Instruction {
10031005
Register first_argument() const { return m_first_argument; }
10041006
u32 argument_count() const { return m_argument_count; }
10051007

1008+
Optional<Builtin> const& builtin() const { return m_builtin; }
1009+
10061010
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
10071011
DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
10081012

@@ -1013,6 +1017,7 @@ class Call final : public Instruction {
10131017
u32 m_argument_count { 0 };
10141018
CallType m_type;
10151019
Optional<StringTableIndex> m_expression_string;
1020+
Optional<Builtin> m_builtin;
10161021
};
10171022

10181023
class CallWithArgumentArray final : public Instruction {

Userland/Libraries/LibJS/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ set(SOURCES
22
AST.cpp
33
Bytecode/ASTCodegen.cpp
44
Bytecode/BasicBlock.cpp
5+
Bytecode/Builtins.cpp
56
Bytecode/CodeGenerationError.cpp
67
Bytecode/CommonImplementations.cpp
78
Bytecode/Executable.cpp

Userland/Libraries/LibJS/Forward.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ class MarkedVector;
303303

304304
namespace Bytecode {
305305
class BasicBlock;
306+
enum class Builtin;
306307
class Executable;
307308
class Generator;
308309
class Instruction;

Userland/Libraries/LibJS/JIT/Compiler.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2518,9 +2518,56 @@ static Value cxx_call(VM& vm, Value callee, u32 first_argument_index, u32 argume
25182518
return TRY_OR_SET_EXCEPTION(perform_call(vm.bytecode_interpreter(), this_value, call_type, callee, move(argument_values)));
25192519
}
25202520

2521+
Assembler::Reg Compiler::argument_register(u32 index)
2522+
{
2523+
switch (index) {
2524+
case 0:
2525+
return ARG0;
2526+
case 1:
2527+
return ARG1;
2528+
case 2:
2529+
return ARG2;
2530+
case 3:
2531+
return ARG3;
2532+
case 4:
2533+
return ARG4;
2534+
case 5:
2535+
return ARG5;
2536+
}
2537+
VERIFY_NOT_REACHED();
2538+
}
2539+
25212540
void Compiler::compile_call(Bytecode::Op::Call const& op)
25222541
{
2542+
Assembler::Label slow_case {};
2543+
Assembler::Label end {};
25232544
load_vm_register(ARG1, op.callee());
2545+
if (op.call_type() == Bytecode::Op::CallType::Call && op.builtin().has_value() && op.argument_count() == Bytecode::builtin_argument_count(op.builtin().value())) {
2546+
auto builtin = op.builtin().value();
2547+
2548+
// GPR0 = vm.running_execution_context().realm;
2549+
m_assembler.mov(
2550+
Assembler::Operand::Register(GPR0),
2551+
Assembler::Operand::Mem64BaseAndOffset(RUNNING_EXECUTION_CONTEXT_BASE, ExecutionContext::realm_offset()));
2552+
2553+
// GPR0 = GPR0->m_builtins[to_underlying(builtin)]
2554+
m_assembler.mov(
2555+
Assembler::Operand::Register(GPR0),
2556+
Assembler::Operand::Mem64BaseAndOffset(GPR0, Realm::builtins_offset() + sizeof(Value) * to_underlying(builtin)));
2557+
2558+
// if (callee != GPR0) goto slow_case;
2559+
m_assembler.jump_if(
2560+
Assembler::Operand::Register(ARG1),
2561+
Assembler::Condition::NotEqualTo,
2562+
Assembler::Operand::Register(GPR0),
2563+
slow_case);
2564+
2565+
// Load arguments into ARG2, ARG3, ...
2566+
for (u32 arg = 0; arg < op.argument_count(); arg++)
2567+
load_vm_register(argument_register(arg + 2), Bytecode::Register { op.first_argument().index() + arg });
2568+
compile_builtin(builtin, slow_case, end);
2569+
}
2570+
slow_case.link(m_assembler);
25242571
m_assembler.mov(
25252572
Assembler::Operand::Register(ARG2),
25262573
Assembler::Operand::Imm(op.first_argument().index()));
@@ -2537,6 +2584,21 @@ void Compiler::compile_call(Bytecode::Op::Call const& op)
25372584
native_call((void*)cxx_call, { Assembler::Operand::Register(GPR0) });
25382585
store_accumulator(RET);
25392586
check_exception();
2587+
end.link(m_assembler);
2588+
}
2589+
2590+
void Compiler::compile_builtin(Bytecode::Builtin builtin, [[maybe_unused]] Assembler::Label& slow_case, [[maybe_unused]] Assembler::Label& end)
2591+
{
2592+
switch (builtin) {
2593+
# define DEFINE_BUILTIN_CASE(name, snake_case_name, ...) \
2594+
case Bytecode::Builtin::name: \
2595+
compile_builtin_##snake_case_name(slow_case, end); \
2596+
break;
2597+
JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_CASE)
2598+
# undef DEFINE_BUILTIN_CASE
2599+
case Bytecode::Builtin::__Count:
2600+
VERIFY_NOT_REACHED();
2601+
}
25402602
}
25412603

25422604
static Value cxx_call_with_argument_array(VM& vm, Value arguments, Value callee, Value this_value, Bytecode::Op::CallType call_type, Optional<Bytecode::StringTableIndex> const& expression_string)

Userland/Libraries/LibJS/JIT/Compiler.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <AK/Platform.h>
1111
#include <LibJIT/Assembler.h>
12+
#include <LibJS/Bytecode/Builtins.h>
1213
#include <LibJS/Bytecode/Executable.h>
1314
#include <LibJS/Bytecode/Op.h>
1415
#include <LibJS/JIT/NativeExecutable.h>
@@ -44,6 +45,8 @@ class Compiler {
4445
static constexpr auto RUNNING_EXECUTION_CONTEXT_BASE = Assembler::Reg::R15;
4546
# endif
4647

48+
static Assembler::Reg argument_register(u32);
49+
4750
# define JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(O) \
4851
O(Div, div) \
4952
O(Exp, exp) \
@@ -147,6 +150,12 @@ class Compiler {
147150
JS_ENUMERATE_IMPLEMENTED_JIT_OPS(DECLARE_COMPILE_OP)
148151
# undef DECLARE_COMPILE_OP
149152

153+
void compile_builtin(Bytecode::Builtin, Assembler::Label& slow_case, Assembler::Label& end);
154+
# define DECLARE_COMPILE_BUILTIN(name, snake_case_name, ...) \
155+
void compile_builtin_##snake_case_name(Assembler::Label& slow_case, Assembler::Label& end);
156+
JS_ENUMERATE_BUILTINS(DECLARE_COMPILE_BUILTIN)
157+
# undef DECLARE_COMPILE_BUILTIN
158+
150159
void store_vm_register(Bytecode::Register, Assembler::Reg);
151160
void load_vm_register(Assembler::Reg, Bytecode::Register);
152161

0 commit comments

Comments
 (0)