summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAli Mohammad Pur <ali.mpfard@gmail.com>2021-06-09 06:49:58 +0430
committerAndreas Kling <kling@serenityos.org>2021-06-09 09:07:29 +0200
commit01e8f0889acf9fc0c84402793ecf96859c737f9a (patch)
treed3451229cdfd0117929b2216ca91a011a34e2d9e
parentd7a25cdb82ff5326236934dd6e99e1a1b2bef3fe (diff)
downloadserenity-01e8f0889acf9fc0c84402793ecf96859c737f9a.zip
LibJS: Generate bytecode in basic blocks instead of one big block
This limits the size of each block (currently set to 1K), and gets us closer to a canonical, more easily analysable bytecode format. As a result of this, "Labels" are now simply entries to basic blocks. Since there is no more 'conditional' jump (as all jumps are always taken), JumpIf{True,False} are unified to JumpConditional, and JumpIfNullish is renamed to JumpNullish. Also fixes #7914 as a result of reimplementing the loop logic.
-rw-r--r--Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp242
-rw-r--r--Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp (renamed from Userland/Libraries/LibJS/Bytecode/Block.cpp)21
-rw-r--r--Userland/Libraries/LibJS/Bytecode/BasicBlock.h (renamed from Userland/Libraries/LibJS/Bytecode/Block.h)21
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Generator.cpp26
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Generator.h45
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Instruction.h7
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Interpreter.cpp44
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Interpreter.h11
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Label.h11
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Op.cpp57
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Op.h46
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt2
-rw-r--r--Userland/Libraries/LibJS/Forward.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp12
-rw-r--r--Userland/Libraries/LibJS/Runtime/ScriptFunction.h3
-rw-r--r--Userland/Utilities/js.cpp14
16 files changed, 391 insertions, 173 deletions
diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp
index 0c593e4367..c72fd47110 100644
--- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp
@@ -119,23 +119,43 @@ void LogicalExpression::generate_bytecode(Bytecode::Generator& generator) const
{
m_lhs->generate_bytecode(generator);
- Bytecode::Op::Jump* test_instr;
+ // lhs
+ // jump op (true) end (false) rhs
+ // rhs
+ // jump always (true) end
+ // end
+
+ auto& rhs_block = generator.make_block();
+ auto& end_block = generator.make_block();
+
switch (m_op) {
case LogicalOp::And:
- test_instr = &generator.emit<Bytecode::Op::JumpIfFalse>();
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { rhs_block },
+ Bytecode::Label { end_block });
break;
case LogicalOp::Or:
- test_instr = &generator.emit<Bytecode::Op::JumpIfTrue>();
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { end_block },
+ Bytecode::Label { rhs_block });
break;
case LogicalOp::NullishCoalescing:
- test_instr = &generator.emit<Bytecode::Op::JumpIfNotNullish>();
+ generator.emit<Bytecode::Op::JumpNullish>().set_targets(
+ Bytecode::Label { rhs_block },
+ Bytecode::Label { end_block });
break;
default:
VERIFY_NOT_REACHED();
}
+ generator.switch_to_basic_block(rhs_block);
m_rhs->generate_bytecode(generator);
- test_instr->set_target(generator.make_label());
+
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { end_block },
+ {});
+
+ generator.switch_to_basic_block(end_block);
}
void UnaryExpression::generate_bytecode(Bytecode::Generator& generator) const
@@ -279,58 +299,154 @@ void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) con
void WhileStatement::generate_bytecode(Bytecode::Generator& generator) const
{
+ // test
+ // jump if_false (true) end (false) body
+ // body
+ // jump always (true) test
+ // end
+ auto& test_block = generator.make_block();
+ auto& body_block = generator.make_block();
+ auto& end_block = generator.make_block();
+
+ // Init result register
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
- generator.begin_continuable_scope();
- auto test_label = generator.make_label();
auto result_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(result_reg);
+
+ // jump to the test block
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { test_block },
+ {});
+
+ generator.switch_to_basic_block(test_block);
m_test->generate_bytecode(generator);
- auto& test_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { body_block },
+ Bytecode::Label { end_block });
+
+ generator.switch_to_basic_block(body_block);
+ generator.begin_continuable_scope(Bytecode::Label { test_block });
m_body->generate_bytecode(generator);
- generator.emit<Bytecode::Op::Jump>(test_label);
- test_jump.set_target(generator.make_label());
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { test_block },
+ {});
generator.end_continuable_scope();
+
+ generator.switch_to_basic_block(end_block);
generator.emit<Bytecode::Op::Load>(result_reg);
}
void DoWhileStatement::generate_bytecode(Bytecode::Generator& generator) const
{
+ // jump always (true) body
+ // test
+ // jump if_false (true) end (false) body
+ // body
+ // jump always (true) test
+ // end
+ auto& test_block = generator.make_block();
+ auto& body_block = generator.make_block();
+ auto& end_block = generator.make_block();
+
+ // Init result register
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
- generator.begin_continuable_scope();
- auto head_label = generator.make_label();
- m_body->generate_bytecode(generator);
- generator.end_continuable_scope();
auto result_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(result_reg);
+
+ // jump to the body block
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { body_block },
+ {});
+
+ generator.switch_to_basic_block(test_block);
m_test->generate_bytecode(generator);
- generator.emit<Bytecode::Op::JumpIfTrue>(head_label);
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { body_block },
+ Bytecode::Label { end_block });
+
+ generator.switch_to_basic_block(body_block);
+ generator.begin_continuable_scope(Bytecode::Label { test_block });
+ m_body->generate_bytecode(generator);
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { test_block },
+ {});
+ generator.end_continuable_scope();
+
+ generator.switch_to_basic_block(end_block);
generator.emit<Bytecode::Op::Load>(result_reg);
}
void ForStatement::generate_bytecode(Bytecode::Generator& generator) const
{
- Bytecode::Op::Jump* test_jump { nullptr };
+ // init
+ // jump always (true) test
+ // test
+ // jump if_true (true) body (false) end
+ // body
+ // jump always (true) update
+ // update
+ // jump always (true) test
+ // end
+
+ // If 'test' is missing, fuse the 'test' and 'body' basic blocks
+ // If 'update' is missing, fuse the 'body' and 'update' basic blocks
+
+ Bytecode::BasicBlock* test_block_ptr { nullptr };
+ Bytecode::BasicBlock* body_block_ptr { nullptr };
+ Bytecode::BasicBlock* update_block_ptr { nullptr };
+
+ auto& end_block = generator.make_block();
if (m_init)
m_init->generate_bytecode(generator);
+ body_block_ptr = &generator.make_block();
+
+ if (m_test)
+ test_block_ptr = &generator.make_block();
+ else
+ test_block_ptr = body_block_ptr;
+
+ if (m_update)
+ update_block_ptr = &generator.make_block();
+ else
+ update_block_ptr = body_block_ptr;
+
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
- generator.begin_continuable_scope();
- auto jump_label = generator.make_label();
auto result_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(result_reg);
+
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { *test_block_ptr },
+ {});
+
if (m_test) {
+ generator.switch_to_basic_block(*test_block_ptr);
m_test->generate_bytecode(generator);
- test_jump = &generator.emit<Bytecode::Op::JumpIfFalse>();
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { *body_block_ptr },
+ Bytecode::Label { end_block });
}
+ generator.switch_to_basic_block(*body_block_ptr);
+ generator.begin_continuable_scope(Bytecode::Label { *update_block_ptr });
m_body->generate_bytecode(generator);
- if (m_update)
- m_update->generate_bytecode(generator);
- generator.emit<Bytecode::Op::Jump>(jump_label);
- if (m_test)
- test_jump->set_target(generator.make_label());
generator.end_continuable_scope();
+
+ if (m_update) {
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { *update_block_ptr },
+ {});
+
+ generator.switch_to_basic_block(*update_block_ptr);
+ m_update->generate_bytecode(generator);
+ }
+
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { *test_block_ptr },
+ {});
+
+ generator.switch_to_basic_block(end_block);
generator.emit<Bytecode::Op::Load>(result_reg);
}
@@ -408,23 +524,60 @@ void ReturnStatement::generate_bytecode(Bytecode::Generator& generator) const
void IfStatement::generate_bytecode(Bytecode::Generator& generator) const
{
+ // test
+ // jump if_true (true) true (false) false
+ // true
+ // jump always (true) end
+ // false
+ // jump always (true) end
+ // end
+
+ // If the 'false' branch doesn't exist, we're just gonna substitute it for 'end' and elide the last two entries above.
+
+ auto& true_block = generator.make_block();
+ auto& false_block = generator.make_block();
+
m_predicate->generate_bytecode(generator);
- auto& else_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { true_block },
+ Bytecode::Label { false_block });
+
+ Bytecode::Op::Jump* true_block_jump { nullptr };
+ generator.switch_to_basic_block(true_block);
m_consequent->generate_bytecode(generator);
+ if (!generator.is_current_block_terminated())
+ true_block_jump = &generator.emit<Bytecode::Op::Jump>();
+
+ generator.switch_to_basic_block(false_block);
if (m_alternate) {
- auto& if_jump = generator.emit<Bytecode::Op::Jump>();
- else_jump.set_target(generator.make_label());
+ auto& end_block = generator.make_block();
+
m_alternate->generate_bytecode(generator);
- if_jump.set_target(generator.make_label());
+ if (!generator.is_current_block_terminated())
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { end_block },
+ {});
+
+ if (true_block_jump)
+ true_block_jump->set_targets(
+ Bytecode::Label { end_block },
+ {});
+
+ generator.switch_to_basic_block(end_block);
} else {
- else_jump.set_target(generator.make_label());
+ if (true_block_jump)
+ true_block_jump->set_targets(
+ Bytecode::Label { false_block },
+ {});
}
}
void ContinueStatement::generate_bytecode(Bytecode::Generator& generator) const
{
- generator.emit<Bytecode::Op::Jump>(generator.nearest_continuable_scope());
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ generator.nearest_continuable_scope(),
+ {});
}
void DebuggerStatement::generate_bytecode(Bytecode::Generator&) const
@@ -433,16 +586,36 @@ void DebuggerStatement::generate_bytecode(Bytecode::Generator&) const
void ConditionalExpression::generate_bytecode(Bytecode::Generator& generator) const
{
+ // test
+ // jump if_true (true) true (false) false
+ // true
+ // jump always (true) end
+ // false
+ // jump always (true) end
+ // end
+
+ auto& true_block = generator.make_block();
+ auto& false_block = generator.make_block();
+ auto& end_block = generator.make_block();
+
m_test->generate_bytecode(generator);
- auto& alternate_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
+ generator.emit<Bytecode::Op::JumpConditional>().set_targets(
+ Bytecode::Label { true_block },
+ Bytecode::Label { false_block });
+ generator.switch_to_basic_block(true_block);
m_consequent->generate_bytecode(generator);
- auto& end_jump = generator.emit<Bytecode::Op::Jump>();
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { end_block },
+ {});
- alternate_jump.set_target(generator.make_label());
+ generator.switch_to_basic_block(false_block);
m_alternate->generate_bytecode(generator);
+ generator.emit<Bytecode::Op::Jump>().set_targets(
+ Bytecode::Label { end_block },
+ {});
- end_jump.set_target(generator.make_label());
+ generator.switch_to_basic_block(end_block);
}
void SequenceExpression::generate_bytecode(Bytecode::Generator& generator) const
@@ -464,5 +637,4 @@ void TemplateLiteral::generate_bytecode(Bytecode::Generator& generator) const
}
}
}
-
}
diff --git a/Userland/Libraries/LibJS/Bytecode/Block.cpp b/Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp
index ef0add31a1..072f6aa78e 100644
--- a/Userland/Libraries/LibJS/Bytecode/Block.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp
@@ -5,28 +5,29 @@
*/
#include <AK/String.h>
-#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Op.h>
#include <sys/mman.h>
namespace JS::Bytecode {
-NonnullOwnPtr<Block> Block::create()
+NonnullOwnPtr<BasicBlock> BasicBlock::create(String name)
{
- return adopt_own(*new Block);
+ return adopt_own(*new BasicBlock(move(name)));
}
-Block::Block()
+BasicBlock::BasicBlock(String name)
+ : m_name(move(name))
{
// FIXME: This is not the smartest solution ever. Find something cleverer!
// The main issue we're working around here is that we don't want pointers into the bytecode stream to become invalidated
// during code generation due to dynamic buffer resizing. Otherwise we could just use a Vector.
- m_buffer_capacity = 64 * KiB;
+ m_buffer_capacity = 1 * KiB;
m_buffer = (u8*)mmap(nullptr, m_buffer_capacity, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
VERIFY(m_buffer != MAP_FAILED);
}
-Block::~Block()
+BasicBlock::~BasicBlock()
{
Bytecode::InstructionStreamIterator it(instruction_stream());
while (!it.at_end()) {
@@ -38,7 +39,7 @@ Block::~Block()
munmap(m_buffer, m_buffer_capacity);
}
-void Block::seal()
+void BasicBlock::seal()
{
// FIXME: mprotect the instruction stream as PROT_READ
// This is currently not possible because instructions can have destructors (that clean up strings)
@@ -46,16 +47,18 @@ void Block::seal()
// It also doesn't work because instructions that have String members use RefPtr internally which must be in writable memory.
}
-void Block::dump() const
+void BasicBlock::dump() const
{
Bytecode::InstructionStreamIterator it(instruction_stream());
+ if (!m_name.is_empty())
+ warnln("{}:", m_name);
while (!it.at_end()) {
warnln("[{:4x}] {}", it.offset(), (*it).to_string());
++it;
}
}
-void Block::grow(size_t additional_size)
+void BasicBlock::grow(size_t additional_size)
{
m_buffer_size += additional_size;
VERIFY(m_buffer_size <= m_buffer_capacity);
diff --git a/Userland/Libraries/LibJS/Bytecode/Block.h b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h
index 4e81d2b1d3..0601b89a93 100644
--- a/Userland/Libraries/LibJS/Bytecode/Block.h
+++ b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h
@@ -8,6 +8,7 @@
#include <AK/Badge.h>
#include <AK/NonnullOwnPtrVector.h>
+#include <AK/String.h>
#include <LibJS/Forward.h>
namespace JS::Bytecode {
@@ -37,30 +38,32 @@ private:
size_t m_offset { 0 };
};
-class Block {
+class BasicBlock {
public:
- static NonnullOwnPtr<Block> create();
- ~Block();
+ static NonnullOwnPtr<BasicBlock> create(String name);
+ ~BasicBlock();
void seal();
void dump() const;
ReadonlyBytes instruction_stream() const { return ReadonlyBytes { m_buffer, m_buffer_size }; }
- size_t register_count() const { return m_register_count; }
-
- void set_register_count(Badge<Bytecode::Generator>, size_t count) { m_register_count = count; }
-
void* next_slot() { return m_buffer + m_buffer_size; }
void grow(size_t additional_size);
+ void terminate(Badge<Generator>) { m_is_terminated = true; }
+ bool is_terminated() const { return m_is_terminated; }
+
+ String const& name() const { return m_name; }
+
private:
- Block();
+ BasicBlock(String name);
- size_t m_register_count { 0 };
u8* m_buffer { nullptr };
size_t m_buffer_capacity { 0 };
size_t m_buffer_size { 0 };
+ bool m_is_terminated { false };
+ String m_name;
};
}
diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp
index 2d2ad73520..1bdd097312 100644
--- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp
@@ -4,9 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
-#include <AK/OwnPtr.h>
#include <LibJS/AST.h>
-#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Bytecode/Register.h>
@@ -16,30 +15,30 @@ namespace JS::Bytecode {
Generator::Generator()
{
- m_block = Block::create();
}
Generator::~Generator()
{
}
-OwnPtr<Block> Generator::generate(ASTNode const& node)
+ExecutionUnit Generator::generate(ASTNode const& node)
{
Generator generator;
+ generator.switch_to_basic_block(generator.make_block());
node.generate_bytecode(generator);
- generator.m_block->set_register_count({}, generator.m_next_register);
- generator.m_block->seal();
- return move(generator.m_block);
+ return { move(generator.m_root_basic_blocks), generator.m_next_register };
}
void Generator::grow(size_t additional_size)
{
- m_block->grow(additional_size);
+ VERIFY(m_current_basic_block);
+ m_current_basic_block->grow(additional_size);
}
void* Generator::next_slot()
{
- return m_block->next_slot();
+ VERIFY(m_current_basic_block);
+ return m_current_basic_block->next_slot();
}
Register Generator::allocate_register()
@@ -48,19 +47,14 @@ Register Generator::allocate_register()
return Register { m_next_register++ };
}
-Label Generator::make_label() const
-{
- return Label { m_block->instruction_stream().size() };
-}
-
Label Generator::nearest_continuable_scope() const
{
return m_continuable_scopes.last();
}
-void Generator::begin_continuable_scope()
+void Generator::begin_continuable_scope(Label continue_target)
{
- m_continuable_scopes.append(make_label());
+ m_continuable_scopes.append(continue_target);
}
void Generator::end_continuable_scope()
diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h
index 88bac76ebe..bf59eec66b 100644
--- a/Userland/Libraries/LibJS/Bytecode/Generator.h
+++ b/Userland/Libraries/LibJS/Bytecode/Generator.h
@@ -6,43 +6,73 @@
#pragma once
+#include <AK/NonnullOwnPtrVector.h>
#include <AK/OwnPtr.h>
+#include <AK/SinglyLinkedList.h>
+#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h>
#include <LibJS/Forward.h>
namespace JS::Bytecode {
+struct ExecutionUnit {
+ NonnullOwnPtrVector<BasicBlock> basic_blocks;
+ size_t number_of_registers { 0 };
+};
+
class Generator {
public:
- static OwnPtr<Block> generate(ASTNode const&);
+ static ExecutionUnit generate(ASTNode const&);
Register allocate_register();
template<typename OpType, typename... Args>
OpType& emit(Args&&... args)
{
+ VERIFY(!is_current_block_terminated());
void* slot = next_slot();
grow(sizeof(OpType));
new (slot) OpType(forward<Args>(args)...);
+ if constexpr (OpType::IsTerminator)
+ m_current_basic_block->terminate({});
return *static_cast<OpType*>(slot);
}
template<typename OpType, typename... Args>
OpType& emit_with_extra_register_slots(size_t extra_register_slots, Args&&... args)
{
+ VERIFY(!is_current_block_terminated());
void* slot = next_slot();
grow(sizeof(OpType) + extra_register_slots * sizeof(Register));
new (slot) OpType(forward<Args>(args)...);
+ if constexpr (OpType::IsTerminator)
+ m_current_basic_block->terminate({});
return *static_cast<OpType*>(slot);
}
- Label make_label() const;
-
- void begin_continuable_scope();
+ void begin_continuable_scope(Label continue_target);
void end_continuable_scope();
- Label nearest_continuable_scope() const;
+ [[nodiscard]] Label nearest_continuable_scope() const;
+
+ void switch_to_basic_block(BasicBlock& block)
+ {
+ m_current_basic_block = &block;
+ }
+
+ BasicBlock& make_block(String name = {})
+ {
+ if (name.is_empty())
+ name = String::number(m_next_block++);
+ m_root_basic_blocks.append(BasicBlock::create(name));
+ return m_root_basic_blocks.last();
+ }
+
+ bool is_current_block_terminated() const
+ {
+ return m_current_basic_block->is_terminated();
+ }
private:
Generator();
@@ -51,8 +81,11 @@ private:
void grow(size_t);
void* next_slot();
- OwnPtr<Block> m_block;
+ BasicBlock* m_current_basic_block { nullptr };
+ NonnullOwnPtrVector<BasicBlock> m_root_basic_blocks;
+
u32 m_next_register { 1 };
+ u32 m_next_block { 1 };
Vector<Label> m_continuable_scopes;
};
diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h
index 510c63783c..167e592ed5 100644
--- a/Userland/Libraries/LibJS/Bytecode/Instruction.h
+++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h
@@ -36,9 +36,8 @@
O(PutById) \
O(GetById) \
O(Jump) \
- O(JumpIfFalse) \
- O(JumpIfTrue) \
- O(JumpIfNotNullish) \
+ O(JumpConditional) \
+ O(JumpNullish) \
O(Call) \
O(EnterScope) \
O(Return) \
@@ -61,6 +60,8 @@ namespace JS::Bytecode {
class Instruction {
public:
+ constexpr static bool IsTerminator = false;
+
enum class Type {
#define __BYTECODE_OP(op) \
op,
diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
index 349032e0b7..770ac091da 100644
--- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
@@ -5,7 +5,7 @@
*/
#include <AK/Debug.h>
-#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/GlobalObject.h>
@@ -33,9 +33,9 @@ Interpreter::~Interpreter()
s_current = nullptr;
}
-Value Interpreter::run(Bytecode::Block const& block)
+Value Interpreter::run(ExecutionUnit const& execution_unit)
{
- dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run block {:p}", &block);
+ dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &execution_unit);
CallFrame global_call_frame;
if (vm().call_stack().is_empty()) {
@@ -50,23 +50,37 @@ Value Interpreter::run(Bytecode::Block const& block)
VERIFY(!vm().exception());
}
+ auto block = &execution_unit.basic_blocks.first();
m_register_windows.append(make<RegisterWindow>());
- registers().resize(block.register_count());
-
- Bytecode::InstructionStreamIterator pc(block.instruction_stream());
- while (!pc.at_end()) {
- auto& instruction = *pc;
- instruction.execute(*this);
- if (m_pending_jump.has_value()) {
- pc.jump(m_pending_jump.release_value());
- continue;
+ registers().resize(execution_unit.number_of_registers);
+
+ for (;;) {
+ Bytecode::InstructionStreamIterator pc(block->instruction_stream());
+ bool will_jump = false;
+ bool will_return = false;
+ while (!pc.at_end()) {
+ auto& instruction = *pc;
+ instruction.execute(*this);
+ if (m_pending_jump.has_value()) {
+ block = m_pending_jump.release_value();
+ will_jump = true;
+ break;
+ }
+ if (!m_return_value.is_empty()) {
+ will_return = true;
+ break;
+ }
+ ++pc;
}
- if (!m_return_value.is_empty())
+
+ if (will_return)
+ break;
+
+ if (pc.at_end() && !will_jump)
break;
- ++pc;
}
- dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run block {:p}", &block);
+ dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &execution_unit);
if constexpr (JS_BYTECODE_DEBUG) {
for (size_t i = 0; i < registers().size(); ++i) {
diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h
index 4611bcf0af..c140fd61f4 100644
--- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h
+++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h
@@ -6,7 +6,7 @@
#pragma once
-#include <AK/NonnullOwnPtrVector.h>
+#include "Generator.h"
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h>
#include <LibJS/Forward.h>
@@ -28,12 +28,15 @@ public:
GlobalObject& global_object() { return m_global_object; }
VM& vm() { return m_vm; }
- Value run(Bytecode::Block const&);
+ Value run(Bytecode::ExecutionUnit const&);
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
Value& reg(Register const& r) { return registers()[r.index()]; }
- void jump(Label const& label) { m_pending_jump = label.address(); }
+ void jump(Label const& label)
+ {
+ m_pending_jump = &label.block();
+ }
void do_return(Value return_value) { m_return_value = return_value; }
private:
@@ -42,7 +45,7 @@ private:
VM& m_vm;
GlobalObject& m_global_object;
NonnullOwnPtrVector<RegisterWindow> m_register_windows;
- Optional<size_t> m_pending_jump;
+ Optional<BasicBlock const*> m_pending_jump;
Value m_return_value;
};
diff --git a/Userland/Libraries/LibJS/Bytecode/Label.h b/Userland/Libraries/LibJS/Bytecode/Label.h
index 56eec29dd0..8174463f6e 100644
--- a/Userland/Libraries/LibJS/Bytecode/Label.h
+++ b/Userland/Libraries/LibJS/Bytecode/Label.h
@@ -7,20 +7,21 @@
#pragma once
#include <AK/Format.h>
+#include <LibJS/Bytecode/BasicBlock.h>
namespace JS::Bytecode {
class Label {
public:
- explicit Label(size_t address)
- : m_address(address)
+ explicit Label(BasicBlock const& block)
+ : m_block(block)
{
}
- size_t address() const { return m_address; }
+ auto& block() const { return m_block; }
private:
- size_t m_address { 0 };
+ BasicBlock const& m_block;
};
}
@@ -29,6 +30,6 @@ template<>
struct AK::Formatter<JS::Bytecode::Label> : AK::Formatter<FormatString> {
void format(FormatBuilder& builder, JS::Bytecode::Label const& value)
{
- return AK::Formatter<FormatString>::format(builder, "@{:x}", value.address());
+ return AK::Formatter<FormatString>::format(builder, "@{}", value.block().name());
}
};
diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp
index 60733de8e2..ce4903c8e2 100644
--- a/Userland/Libraries/LibJS/Bytecode/Op.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp
@@ -174,31 +174,29 @@ void PutById::execute(Bytecode::Interpreter& interpreter) const
void Jump::execute(Bytecode::Interpreter& interpreter) const
{
- interpreter.jump(*m_target);
+ interpreter.jump(*m_true_target);
}
-void JumpIfFalse::execute(Bytecode::Interpreter& interpreter) const
+void JumpConditional::execute(Bytecode::Interpreter& interpreter) const
{
- VERIFY(m_target.has_value());
- auto result = interpreter.accumulator();
- if (!result.to_boolean())
- interpreter.jump(m_target.value());
-}
-
-void JumpIfTrue::execute(Bytecode::Interpreter& interpreter) const
-{
- VERIFY(m_target.has_value());
+ VERIFY(m_true_target.has_value());
+ VERIFY(m_false_target.has_value());
auto result = interpreter.accumulator();
if (result.to_boolean())
- interpreter.jump(m_target.value());
+ interpreter.jump(m_true_target.value());
+ else
+ interpreter.jump(m_false_target.value());
}
-void JumpIfNotNullish::execute(Bytecode::Interpreter& interpreter) const
+void JumpNullish::execute(Bytecode::Interpreter& interpreter) const
{
- VERIFY(m_target.has_value());
+ VERIFY(m_true_target.has_value());
+ VERIFY(m_false_target.has_value());
auto result = interpreter.accumulator();
- if (!result.is_nullish())
- interpreter.jump(m_target.value());
+ if (result.is_nullish())
+ interpreter.jump(m_true_target.value());
+ else
+ interpreter.jump(m_false_target.value());
}
void Call::execute(Bytecode::Interpreter& interpreter) const
@@ -321,28 +319,23 @@ String GetById::to_string() const
String Jump::to_string() const
{
- return String::formatted("Jump {}", *m_target);
-}
-
-String JumpIfFalse::to_string() const
-{
- if (m_target.has_value())
- return String::formatted("JumpIfFalse target:{}", m_target.value());
- return "JumpIfFalse target:<empty>";
+ if (m_true_target.has_value())
+ return String::formatted("Jump {}", *m_true_target);
+ return String::formatted("Jump <empty>");
}
-String JumpIfTrue::to_string() const
+String JumpConditional::to_string() const
{
- if (m_target.has_value())
- return String::formatted("JumpIfTrue target:{}", m_target.value());
- return "JumpIfTrue result:{}, target:<empty>";
+ auto true_string = m_true_target.has_value() ? String::formatted("{}", *m_true_target) : "<empty>";
+ auto false_string = m_false_target.has_value() ? String::formatted("{}", *m_false_target) : "<empty>";
+ return String::formatted("JumpConditional true:{} false:{}", true_string, false_string);
}
-String JumpIfNotNullish::to_string() const
+String JumpNullish::to_string() const
{
- if (m_target.has_value())
- return String::formatted("JumpIfNotNullish target:{}", m_target.value());
- return "JumpIfNotNullish target:<empty>";
+ auto true_string = m_true_target.has_value() ? String::formatted("{}", *m_true_target) : "<empty>";
+ auto false_string = m_false_target.has_value() ? String::formatted("{}", *m_false_target) : "<empty>";
+ return String::formatted("JumpNullish null:{} nonnull:{}", true_string, false_string);
}
String Call::to_string() const
diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h
index 95bb44220f..769999e302 100644
--- a/Userland/Libraries/LibJS/Bytecode/Op.h
+++ b/Userland/Libraries/LibJS/Bytecode/Op.h
@@ -268,42 +268,40 @@ private:
class Jump : public Instruction {
public:
- explicit Jump(Type type, Optional<Label> target = {})
+ constexpr static bool IsTerminator = true;
+
+ explicit Jump(Type type, Optional<Label> taken_target = {}, Optional<Label> nontaken_target = {})
: Instruction(type)
- , m_target(move(target))
+ , m_true_target(move(taken_target))
+ , m_false_target(move(nontaken_target))
{
}
- explicit Jump(Optional<Label> target = {})
+ explicit Jump(Optional<Label> taken_target = {}, Optional<Label> nontaken_target = {})
: Instruction(Type::Jump)
- , m_target(move(target))
+ , m_true_target(move(taken_target))
+ , m_false_target(move(nontaken_target))
{
}
- void set_target(Optional<Label> target) { m_target = move(target); }
-
- void execute(Bytecode::Interpreter&) const;
- String to_string() const;
-
-protected:
- Optional<Label> m_target;
-};
-
-class JumpIfFalse final : public Jump {
-public:
- explicit JumpIfFalse(Optional<Label> target = {})
- : Jump(Type::JumpIfFalse, move(target))
+ void set_targets(Optional<Label> true_target, Optional<Label> false_target)
{
+ m_true_target = move(true_target);
+ m_false_target = move(false_target);
}
void execute(Bytecode::Interpreter&) const;
String to_string() const;
+
+protected:
+ Optional<Label> m_true_target;
+ Optional<Label> m_false_target;
};
-class JumpIfTrue : public Jump {
+class JumpConditional final : public Jump {
public:
- explicit JumpIfTrue(Optional<Label> target = {})
- : Jump(Type::JumpIfTrue, move(target))
+ explicit JumpConditional(Optional<Label> true_target = {}, Optional<Label> false_target = {})
+ : Jump(Type::JumpConditional, move(true_target), move(false_target))
{
}
@@ -311,10 +309,10 @@ public:
String to_string() const;
};
-class JumpIfNotNullish final : public Jump {
+class JumpNullish final : public Jump {
public:
- explicit JumpIfNotNullish(Optional<Label> target = {})
- : Jump(Type::JumpIfNotNullish, move(target))
+ explicit JumpNullish(Optional<Label> true_target = {}, Optional<Label> false_target = {})
+ : Jump(Type::JumpNullish, move(true_target), move(false_target))
{
}
@@ -364,6 +362,8 @@ private:
class Return final : public Instruction {
public:
+ constexpr static bool IsTerminator = true;
+
Return()
: Instruction(Type::Return)
{
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt
index a7fa1760d7..f8e359f492 100644
--- a/Userland/Libraries/LibJS/CMakeLists.txt
+++ b/Userland/Libraries/LibJS/CMakeLists.txt
@@ -1,7 +1,7 @@
set(SOURCES
AST.cpp
Bytecode/ASTCodegen.cpp
- Bytecode/Block.cpp
+ Bytecode/BasicBlock.cpp
Bytecode/Generator.cpp
Bytecode/Instruction.cpp
Bytecode/Interpreter.cpp
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
index 0a2665cc42..be4536bc91 100644
--- a/Userland/Libraries/LibJS/Forward.h
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -163,7 +163,7 @@ template<class T>
class Handle;
namespace Bytecode {
-class Block;
+class BasicBlock;
class Generator;
class Instruction;
class Interpreter;
diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
index 3a4e83f4cb..c58740b555 100644
--- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp
@@ -7,7 +7,7 @@
#include <AK/Debug.h>
#include <AK/Function.h>
#include <LibJS/AST.h>
-#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Interpreter.h>
@@ -151,15 +151,15 @@ Value ScriptFunction::execute_function_body()
if (bytecode_interpreter) {
prepare_arguments();
- if (!m_bytecode_block) {
- m_bytecode_block = Bytecode::Generator::generate(m_body);
- VERIFY(m_bytecode_block);
+ if (!m_bytecode_execution_unit.has_value()) {
+ m_bytecode_execution_unit = Bytecode::Generator::generate(m_body);
if constexpr (JS_BYTECODE_DEBUG) {
dbgln("Compiled Bytecode::Block for function '{}':", m_name);
- m_bytecode_block->dump();
+ for (auto& block : m_bytecode_execution_unit->basic_blocks)
+ block.dump();
}
}
- return bytecode_interpreter->run(*m_bytecode_block);
+ return bytecode_interpreter->run(*m_bytecode_execution_unit);
} else {
OwnPtr<Interpreter> local_interpreter;
ast_interpreter = vm.interpreter_if_exists();
diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.h b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h
index d866c78878..c114946477 100644
--- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.h
+++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h
@@ -7,6 +7,7 @@
#pragma once
#include <LibJS/AST.h>
+#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Runtime/Function.h>
namespace JS {
@@ -47,7 +48,7 @@ private:
FlyString m_name;
NonnullRefPtr<Statement> m_body;
const Vector<FunctionNode::Parameter> m_parameters;
- OwnPtr<Bytecode::Block> m_bytecode_block;
+ Optional<Bytecode::ExecutionUnit> m_bytecode_execution_unit;
ScopeObject* m_parent_scope { nullptr };
i32 m_function_length { 0 };
bool m_is_strict { false };
diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp
index c3c15ebba2..29a40e9791 100644
--- a/Userland/Utilities/js.cpp
+++ b/Userland/Utilities/js.cpp
@@ -14,7 +14,7 @@
#include <LibCore/File.h>
#include <LibCore/StandardPaths.h>
#include <LibJS/AST.h>
-#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Console.h>
@@ -494,15 +494,15 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source
program->dump(0);
if (s_dump_bytecode || s_run_bytecode) {
- auto block = JS::Bytecode::Generator::generate(*program);
- VERIFY(block);
-
- if (s_dump_bytecode)
- block->dump();
+ auto unit = JS::Bytecode::Generator::generate(*program);
+ if (s_dump_bytecode) {
+ for (auto& block : unit.basic_blocks)
+ block.dump();
+ }
if (s_run_bytecode) {
JS::Bytecode::Interpreter bytecode_interpreter(interpreter.global_object());
- bytecode_interpreter.run(*block);
+ bytecode_interpreter.run(unit);
}
return true;