Jelajahi Sumber

LibJS/Bytecode: Bring back the bytecode optimization pipeline

...minus the EliminateLoads pass, since it was not compatible with the
new bytecode format.
Andreas Kling 1 tahun lalu
induk
melakukan
5b29974bfa

+ 1 - 1
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -2510,7 +2510,7 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> TryStatement::gener
 
     auto& target_block = generator.make_block();
     generator.switch_to_basic_block(saved_block);
-    generator.emit<Bytecode::Op::EnterUnwindContext>(Bytecode::Label { target_block });
+    generator.emit<Bytecode::Op::EnterUnwindContext>(Bytecode::Label { target_block }, handler_target, finalizer_target);
     generator.start_boundary(Bytecode::Generator::BlockBoundaryType::Unwind);
     if (m_finalizer)
         generator.start_boundary(Bytecode::Generator::BlockBoundaryType::ReturnToFinally);

+ 44 - 1
Userland/Libraries/LibJS/Bytecode/BasicBlock.h

@@ -8,6 +8,7 @@
 
 #include <AK/Badge.h>
 #include <AK/String.h>
+#include <LibJS/Bytecode/Operand.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Heap/Handle.h>
 
@@ -35,7 +36,7 @@ public:
 
     void grow(size_t additional_size);
 
-    void terminate(Badge<Generator>) { m_terminated = true; }
+    void terminate(Badge<Generator>, size_t slot_offset) { terminate(slot_offset); }
     bool is_terminated() const { return m_terminated; }
 
     String const& name() const { return m_name; }
@@ -46,14 +47,56 @@ public:
     BasicBlock const* handler() const { return m_handler; }
     BasicBlock const* finalizer() const { return m_finalizer; }
 
+    Instruction const* terminator() const
+    {
+        VERIFY(m_terminated);
+        return reinterpret_cast<Instruction const*>(data() + m_terminator_offset);
+    }
+
+    template<typename OpType, typename... Args>
+    void append(u32 start_offset, u32 end_offset, Args&&... args)
+    {
+        VERIFY(!m_terminated);
+        size_t const slot_offset = size();
+        grow(sizeof(OpType));
+        void* slot = data() + slot_offset;
+        new (slot) OpType(forward<Args>(args)...);
+        if constexpr (OpType::IsTerminator)
+            terminate(slot_offset);
+        auto* op = static_cast<OpType*>(slot);
+        op->set_source_record({ start_offset, end_offset });
+    }
+
+    template<typename OpType, typename... Args>
+    void append_with_extra_operand_slots(u32 start_offset, u32 end_offset, size_t extra_operand_slots, Args&&... args)
+    {
+        VERIFY(!m_terminated);
+        size_t size_to_allocate = round_up_to_power_of_two(sizeof(OpType) + extra_operand_slots * sizeof(Operand), alignof(void*));
+        size_t slot_offset = size();
+        grow(size_to_allocate);
+        void* slot = data() + slot_offset;
+        new (slot) OpType(forward<Args>(args)...);
+        if constexpr (OpType::IsTerminator)
+            terminate(slot_offset);
+        auto* op = static_cast<OpType*>(slot);
+        op->set_source_record({ start_offset, end_offset });
+    }
+
 private:
     explicit BasicBlock(String name);
 
+    void terminate(size_t slot_offset)
+    {
+        m_terminated = true;
+        m_terminator_offset = slot_offset;
+    }
+
     Vector<u8> m_buffer;
     BasicBlock const* m_handler { nullptr };
     BasicBlock const* m_finalizer { nullptr };
     String m_name;
     bool m_terminated { false };
+    size_t m_terminator_offset { 0 };
 };
 
 }

+ 2 - 2
Userland/Libraries/LibJS/Bytecode/Generator.h

@@ -76,7 +76,7 @@ public:
         void* slot = m_current_basic_block->data() + slot_offset;
         new (slot) OpType(forward<Args>(args)...);
         if constexpr (OpType::IsTerminator)
-            m_current_basic_block->terminate({});
+            m_current_basic_block->terminate({}, slot_offset);
         auto* op = static_cast<OpType*>(slot);
         op->set_source_record({ m_current_ast_node->start_offset(), m_current_ast_node->end_offset() });
     }
@@ -92,7 +92,7 @@ public:
         void* slot = m_current_basic_block->data() + slot_offset;
         new (slot) OpType(forward<Args>(args)...);
         if constexpr (OpType::IsTerminator)
-            m_current_basic_block->terminate({});
+            m_current_basic_block->terminate({}, slot_offset);
         auto* op = static_cast<OpType*>(slot);
         op->set_source_record({ m_current_ast_node->start_offset(), m_current_ast_node->end_offset() });
     }

+ 30 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.cpp

@@ -10,6 +10,36 @@
 
 namespace JS::Bytecode {
 
+bool Instruction::is_terminator() const
+{
+#define __BYTECODE_OP(op) \
+    case Type::op:        \
+        return Op::op::IsTerminator;
+
+    switch (type()) {
+        ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
+    default:
+        VERIFY_NOT_REACHED();
+    }
+
+#undef __BYTECODE_OP
+}
+
+void Instruction::replace_references(BasicBlock const& from, BasicBlock const& to)
+{
+#define __BYTECODE_OP(op)       \
+    case Instruction::Type::op: \
+        return static_cast<Bytecode::Op::op&>(*this).replace_references_impl(from, to);
+
+    switch (type()) {
+        ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
+    default:
+        VERIFY_NOT_REACHED();
+    }
+
+#undef __BYTECODE_OP
+}
+
 void Instruction::destroy(Instruction& instruction)
 {
 #define __BYTECODE_OP(op)                        \

+ 3 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.h

@@ -131,10 +131,13 @@ public:
 #undef __BYTECODE_OP
     };
 
+    [[nodiscard]] bool is_terminator() const;
+
     Type type() const { return m_type; }
     size_t length() const { return m_length; }
     ByteString to_byte_string(Bytecode::Executable const&) const;
     ThrowCompletionOr<void> execute(Bytecode::Interpreter&) const;
+    void replace_references(BasicBlock const& from, BasicBlock const& to);
     static void destroy(Instruction&);
 
     // FIXME: Find a better way to organize this information

+ 27 - 0
Userland/Libraries/LibJS/Bytecode/Interpreter.cpp

@@ -15,6 +15,7 @@
 #include <LibJS/Bytecode/Interpreter.h>
 #include <LibJS/Bytecode/Label.h>
 #include <LibJS/Bytecode/Op.h>
+#include <LibJS/Bytecode/PassManager.h>
 #include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/BigInt.h>
@@ -532,6 +533,25 @@ void Interpreter::enter_object_environment(Object& object)
     vm().running_execution_context().lexical_environment = new_object_environment(object, true, old_environment);
 }
 
+static PassManager& optimization_pipeline()
+{
+    static auto s_optimization_pipeline = [] {
+        auto pm = make<PassManager>();
+        pm->add<Passes::GenerateCFG>();
+        pm->add<Passes::UnifySameBlocks>();
+        pm->add<Passes::GenerateCFG>();
+        pm->add<Passes::MergeBlocks>();
+        pm->add<Passes::GenerateCFG>();
+        pm->add<Passes::UnifySameBlocks>();
+        pm->add<Passes::GenerateCFG>();
+        pm->add<Passes::MergeBlocks>();
+        pm->add<Passes::GenerateCFG>();
+        pm->add<Passes::PlaceBlocks>();
+        return pm;
+    }();
+    return *s_optimization_pipeline;
+}
+
 ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ASTNode const& node, ReadonlySpan<FunctionParameter> parameters, FunctionKind kind, DeprecatedFlyString const& name)
 {
     auto executable_result = Bytecode::Generator::generate(vm, node, parameters, kind);
@@ -541,6 +561,13 @@ ThrowCompletionOr<NonnullGCPtr<Bytecode::Executable>> compile(VM& vm, ASTNode co
     auto bytecode_executable = executable_result.release_value();
     bytecode_executable->name = name;
 
+    auto& passes = optimization_pipeline();
+    passes.perform(*bytecode_executable);
+    if constexpr (JS_BYTECODE_DEBUG) {
+        dbgln("Optimisation passes took {}us", passes.elapsed());
+        dbgln("Compiled Bytecode::Block for function '{}':", name);
+    }
+
     if (Bytecode::g_dump_bytecode)
         bytecode_executable->dump();
 

+ 1 - 0
Userland/Libraries/LibJS/Bytecode/Interpreter.h

@@ -18,6 +18,7 @@
 namespace JS::Bytecode {
 
 class InstructionStreamIterator;
+class PassManager;
 
 struct CallFrame {
     static NonnullOwnPtr<CallFrame> create(size_t register_count);

+ 173 - 61
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -42,6 +42,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand src() const { return m_src; }
@@ -77,28 +78,29 @@ private:
     O(StrictlyEquals, strict_equals)                        \
     O(LeftShift, left_shift)
 
-#define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case)             \
-    class OpTitleCase final : public Instruction {                          \
-    public:                                                                 \
-        explicit OpTitleCase(Operand dst, Operand lhs, Operand rhs)         \
-            : Instruction(Type::OpTitleCase, sizeof(*this))                 \
-            , m_dst(dst)                                                    \
-            , m_lhs(lhs)                                                    \
-            , m_rhs(rhs)                                                    \
-        {                                                                   \
-        }                                                                   \
-                                                                            \
-        ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const; \
-        ByteString to_byte_string_impl(Bytecode::Executable const&) const;  \
-                                                                            \
-        Operand dst() const { return m_dst; }                               \
-        Operand lhs() const { return m_lhs; }                               \
-        Operand rhs() const { return m_rhs; }                               \
-                                                                            \
-    private:                                                                \
-        Operand m_dst;                                                      \
-        Operand m_lhs;                                                      \
-        Operand m_rhs;                                                      \
+#define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case)                \
+    class OpTitleCase final : public Instruction {                             \
+    public:                                                                    \
+        explicit OpTitleCase(Operand dst, Operand lhs, Operand rhs)            \
+            : Instruction(Type::OpTitleCase, sizeof(*this))                    \
+            , m_dst(dst)                                                       \
+            , m_lhs(lhs)                                                       \
+            , m_rhs(rhs)                                                       \
+        {                                                                      \
+        }                                                                      \
+                                                                               \
+        ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;    \
+        ByteString to_byte_string_impl(Bytecode::Executable const&) const;     \
+        void replace_references_impl(BasicBlock const&, BasicBlock const&) { } \
+                                                                               \
+        Operand dst() const { return m_dst; }                                  \
+        Operand lhs() const { return m_lhs; }                                  \
+        Operand rhs() const { return m_rhs; }                                  \
+                                                                               \
+    private:                                                                   \
+        Operand m_dst;                                                         \
+        Operand m_lhs;                                                         \
+        Operand m_rhs;                                                         \
     };
 
 JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(JS_DECLARE_COMMON_BINARY_OP)
@@ -112,25 +114,26 @@ JS_ENUMERATE_COMMON_BINARY_OPS_WITH_FAST_PATH(JS_DECLARE_COMMON_BINARY_OP)
     O(UnaryMinus, unary_minus)           \
     O(Typeof, typeof_)
 
-#define JS_DECLARE_COMMON_UNARY_OP(OpTitleCase, op_snake_case)              \
-    class OpTitleCase final : public Instruction {                          \
-    public:                                                                 \
-        OpTitleCase(Operand dst, Operand src)                               \
-            : Instruction(Type::OpTitleCase, sizeof(*this))                 \
-            , m_dst(dst)                                                    \
-            , m_src(src)                                                    \
-        {                                                                   \
-        }                                                                   \
-                                                                            \
-        ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const; \
-        ByteString to_byte_string_impl(Bytecode::Executable const&) const;  \
-                                                                            \
-        Operand dst() const { return m_dst; }                               \
-        Operand src() const { return m_src; }                               \
-                                                                            \
-    private:                                                                \
-        Operand m_dst;                                                      \
-        Operand m_src;                                                      \
+#define JS_DECLARE_COMMON_UNARY_OP(OpTitleCase, op_snake_case)                 \
+    class OpTitleCase final : public Instruction {                             \
+    public:                                                                    \
+        OpTitleCase(Operand dst, Operand src)                                  \
+            : Instruction(Type::OpTitleCase, sizeof(*this))                    \
+            , m_dst(dst)                                                       \
+            , m_src(src)                                                       \
+        {                                                                      \
+        }                                                                      \
+                                                                               \
+        ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;    \
+        ByteString to_byte_string_impl(Bytecode::Executable const&) const;     \
+        void replace_references_impl(BasicBlock const&, BasicBlock const&) { } \
+                                                                               \
+        Operand dst() const { return m_dst; }                                  \
+        Operand src() const { return m_src; }                                  \
+                                                                               \
+    private:                                                                   \
+        Operand m_dst;                                                         \
+        Operand m_src;                                                         \
     };
 
 JS_ENUMERATE_COMMON_UNARY_OPS(JS_DECLARE_COMMON_UNARY_OP)
@@ -146,6 +149,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -166,6 +170,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     StringTableIndex source_index() const { return m_source_index; }
@@ -182,25 +187,26 @@ private:
 #define JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(O) \
     O(TypeError)
 
-#define JS_DECLARE_NEW_BUILTIN_ERROR_OP(ErrorName)                          \
-    class New##ErrorName final : public Instruction {                       \
-    public:                                                                 \
-        New##ErrorName(Operand dst, StringTableIndex error_string)          \
-            : Instruction(Type::New##ErrorName, sizeof(*this))              \
-            , m_dst(dst)                                                    \
-            , m_error_string(error_string)                                  \
-        {                                                                   \
-        }                                                                   \
-                                                                            \
-        ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const; \
-        ByteString to_byte_string_impl(Bytecode::Executable const&) const;  \
-                                                                            \
-        Operand dst() const { return m_dst; }                               \
-        StringTableIndex error_string() const { return m_error_string; }    \
-                                                                            \
-    private:                                                                \
-        Operand m_dst;                                                      \
-        StringTableIndex m_error_string;                                    \
+#define JS_DECLARE_NEW_BUILTIN_ERROR_OP(ErrorName)                             \
+    class New##ErrorName final : public Instruction {                          \
+    public:                                                                    \
+        New##ErrorName(Operand dst, StringTableIndex error_string)             \
+            : Instruction(Type::New##ErrorName, sizeof(*this))                 \
+            , m_dst(dst)                                                       \
+            , m_error_string(error_string)                                     \
+        {                                                                      \
+        }                                                                      \
+                                                                               \
+        ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;    \
+        ByteString to_byte_string_impl(Bytecode::Executable const&) const;     \
+        void replace_references_impl(BasicBlock const&, BasicBlock const&) { } \
+                                                                               \
+        Operand dst() const { return m_dst; }                                  \
+        StringTableIndex error_string() const { return m_error_string; }       \
+                                                                               \
+    private:                                                                   \
+        Operand m_dst;                                                         \
+        StringTableIndex m_error_string;                                       \
     };
 
 JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(JS_DECLARE_NEW_BUILTIN_ERROR_OP)
@@ -221,6 +227,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     size_t length_impl(size_t excluded_names_count) const
     {
@@ -260,6 +267,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -306,6 +314,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     ReadonlySpan<Value> elements() const { return { m_elements, m_element_count }; }
@@ -328,6 +337,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand src() const { return m_src; }
@@ -351,6 +361,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand specifier() const { return m_specifier; }
@@ -373,6 +384,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand iterator() const { return m_iterator; }
@@ -393,6 +405,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand src() const { return m_src; }
@@ -416,6 +429,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 };
 
 class EnterObjectEnvironment final : public Instruction {
@@ -428,6 +442,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand object() const { return m_object; }
 
@@ -445,6 +460,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -466,6 +482,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     IdentifierTableIndex identifier() const { return m_identifier; }
     EnvironmentMode mode() const { return m_mode; }
@@ -499,6 +516,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     IdentifierTableIndex identifier() const { return m_identifier; }
     Operand src() const { return m_src; }
@@ -525,6 +543,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     size_t index() const { return m_index; }
     Operand dst() const { return Operand(Operand::Type::Local, m_index); }
@@ -548,6 +567,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     IdentifierTableIndex identifier() const { return m_identifier; }
     u32 cache_index() const { return m_cache_index; }
@@ -573,6 +593,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     IdentifierTableIndex identifier() const { return m_identifier; }
@@ -596,6 +617,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     IdentifierTableIndex identifier() const { return m_identifier; }
@@ -618,6 +640,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     IdentifierTableIndex identifier() const { return m_identifier; }
@@ -640,6 +663,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -667,6 +691,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -694,6 +719,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -717,6 +743,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -751,6 +778,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand base() const { return m_base; }
     IdentifierTableIndex property() const { return m_property; }
@@ -781,6 +809,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand base() const { return m_base; }
     Operand this_value() const { return m_this_value; }
@@ -811,6 +840,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand base() const { return m_base; }
     IdentifierTableIndex property() const { return m_property; }
@@ -835,6 +865,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -859,6 +890,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -884,6 +916,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -908,6 +941,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -934,6 +968,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand base() const { return m_base; }
     Operand property() const { return m_property; }
@@ -961,6 +996,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand base() const { return m_base; }
     Operand property() const { return m_property; }
@@ -988,6 +1024,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand base() const { return m_base; }
@@ -1017,6 +1054,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
 private:
     Operand m_dst;
@@ -1052,6 +1090,13 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const& from, BasicBlock const& to)
+    {
+        if (m_true_target.has_value() && &m_true_target->block() == &from)
+            m_true_target = Label { to };
+        if (m_false_target.has_value() && &m_false_target->block() == &from)
+            m_false_target = Label { to };
+    }
 
     auto& true_target() const { return m_true_target; }
     auto& false_target() const { return m_false_target; }
@@ -1151,6 +1196,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
 private:
     Operand m_dst;
@@ -1185,6 +1231,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
 private:
     Operand m_dst;
@@ -1207,6 +1254,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand arguments() const { return m_arguments; }
@@ -1231,6 +1279,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Optional<Operand> const& super_class() const { return m_super_class; }
@@ -1257,6 +1306,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     FunctionExpression const& function_node() const { return m_function_node; }
@@ -1280,6 +1330,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     ScopeNode const& scope_node() const { return m_scope_node; }
 
@@ -1299,6 +1350,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Optional<Operand> const& value() const { return m_value; }
 
@@ -1316,6 +1368,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -1334,6 +1387,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand src() const { return m_src; }
@@ -1353,6 +1407,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -1371,6 +1426,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand src() const { return m_src; }
@@ -1392,6 +1448,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand src() const { return m_src; }
 
@@ -1409,6 +1466,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand src() const { return m_src; }
 
@@ -1426,6 +1484,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand src() const { return m_src; }
 
@@ -1443,6 +1502,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand src() const { return m_src; }
 
@@ -1454,19 +1514,34 @@ class EnterUnwindContext final : public Instruction {
 public:
     constexpr static bool IsTerminator = true;
 
-    EnterUnwindContext(Label entry_point)
+    EnterUnwindContext(Label entry_point, Optional<Label> handler, Optional<Label> finalizer)
         : Instruction(Type::EnterUnwindContext, sizeof(*this))
         , m_entry_point(move(entry_point))
+        , m_handler(move(handler))
+        , m_finalizer(move(finalizer))
     {
     }
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const& from, BasicBlock const& to)
+    {
+        if (&m_entry_point.block() == &from)
+            m_entry_point = Label { to };
+        if (m_handler.has_value() && &m_handler->block() == &from)
+            m_handler = Label { to };
+        if (m_finalizer.has_value() && &m_finalizer->block() == &from)
+            m_finalizer = Label { to };
+    }
 
     auto& entry_point() const { return m_entry_point; }
+    auto& handler() const { return m_handler; }
+    auto& finalizer() const { return m_finalizer; }
 
 private:
     Label m_entry_point;
+    Optional<Label> m_handler;
+    Optional<Label> m_finalizer;
 };
 
 class ScheduleJump final : public Instruction {
@@ -1492,6 +1567,11 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const& from, BasicBlock const& to)
+    {
+        if (&m_target.block() == &from)
+            m_target = Label { to };
+    }
 
 private:
     Label m_target;
@@ -1506,6 +1586,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 };
 
 class LeaveUnwindContext final : public Instruction {
@@ -1517,6 +1598,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 };
 
 class ContinuePendingUnwind final : public Instruction {
@@ -1531,6 +1613,11 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const& from, BasicBlock const& to)
+    {
+        if (&m_resume_target.block() == &from)
+            m_resume_target = Label { to };
+    }
 
     auto& resume_target() const { return m_resume_target; }
 
@@ -1557,6 +1644,11 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const& from, BasicBlock const& to)
+    {
+        if (m_continuation_label.has_value() && &m_continuation_label->block() == &from)
+            m_continuation_label = Label { to };
+    }
 
     auto& continuation() const { return m_continuation_label; }
     Operand value() const { return m_value; }
@@ -1579,6 +1671,11 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const& from, BasicBlock const& to)
+    {
+        if (&m_continuation_label.block() == &from)
+            m_continuation_label = Label { to };
+    }
 
     auto& continuation() const { return m_continuation_label; }
     Operand argument() const { return m_argument; }
@@ -1600,6 +1697,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand iterable() const { return m_iterable; }
@@ -1622,6 +1720,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand object() const { return m_object; }
     Operand iterator_record() const { return m_iterator_record; }
@@ -1642,6 +1741,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand next_method() const { return m_next_method; }
     Operand iterator_record() const { return m_iterator_record; }
@@ -1663,6 +1763,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand object() const { return m_object; }
@@ -1685,6 +1786,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand object() const { return m_object; }
@@ -1706,6 +1808,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand iterator_record() const { return m_iterator_record; }
     Completion::Type completion_type() const { return m_completion_type; }
@@ -1729,6 +1832,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand iterator_record() const { return m_iterator_record; }
     Completion::Type completion_type() const { return m_completion_type; }
@@ -1751,6 +1855,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     Operand iterator_record() const { return m_iterator_record; }
@@ -1770,6 +1875,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -1787,6 +1893,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -1804,6 +1911,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -1821,6 +1929,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
 
@@ -1839,6 +1948,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand dst() const { return m_dst; }
     IdentifierTableIndex identifier() const { return m_identifier; }
@@ -1860,6 +1970,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
     Operand value() const { return m_value; }
 
@@ -1878,6 +1989,7 @@ public:
 
     ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
     ByteString to_byte_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 
 private:
     StringView m_text;

+ 26 - 0
Userland/Libraries/LibJS/Bytecode/Pass/DumpCFG.cpp

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/PassManager.h>
+
+namespace JS::Bytecode::Passes {
+
+void DumpCFG::perform(PassPipelineExecutable& executable)
+{
+    started();
+
+    VERIFY(executable.cfg.has_value());
+    outln(m_file, "CFG Dump for {} basic blocks:", executable.executable.basic_blocks.size());
+    for (auto& entry : executable.cfg.value()) {
+        for (auto& value : entry.value)
+            outln(m_file, "{} -> {}", entry.key->name(), value->name());
+    }
+    outln(m_file);
+
+    finished();
+}
+
+}

+ 220 - 0
Userland/Libraries/LibJS/Bytecode/Pass/GenerateCFG.cpp

@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/TemporaryChange.h>
+#include <LibJS/Bytecode/PassManager.h>
+
+namespace JS::Bytecode::Passes {
+
+struct UnwindFrame {
+    BasicBlock const* handler;
+    BasicBlock const* finalizer;
+    Vector<BasicBlock const*> finalizer_targets;
+};
+
+static HashTable<BasicBlock const*> seen_blocks;
+static Vector<UnwindFrame> unwind_frames;
+
+static BasicBlock const* next_handler_or_finalizer()
+{
+    return unwind_frames.last().handler ?: unwind_frames.last().finalizer;
+}
+
+static void generate_cfg_for_block(BasicBlock const& current_block, PassPipelineExecutable& executable)
+{
+    seen_blocks.set(&current_block);
+
+    auto enter_label = [&](Label const& label, BasicBlock const& entering_block) {
+        executable.cfg->ensure(&entering_block).set(&label.block());
+        executable.inverted_cfg->ensure(&label.block()).set(&entering_block);
+
+        // The finalizers and handlers of an unwind context are handled separately
+        if (!seen_blocks.contains(&label.block())
+            && &label.block() != unwind_frames.last().handler
+            && &label.block() != unwind_frames.last().finalizer)
+            generate_cfg_for_block(label.block(), executable);
+    };
+
+    if (auto const* block = next_handler_or_finalizer())
+        enter_label(Label { *block }, current_block);
+
+    for (InstructionStreamIterator it { current_block.instruction_stream() }; !it.at_end(); ++it) {
+        auto const& instruction = *it;
+
+        if (instruction.type() == Instruction::Type::LeaveUnwindContext) {
+            if (unwind_frames.last().finalizer && unwind_frames.last().finalizer != &current_block)
+                dbgln("FIXME: Popping finalizer from the unwind context from outside the finalizer");
+            unwind_frames.take_last();
+
+            if (auto const* block = next_handler_or_finalizer())
+                enter_label(Label { *block }, current_block);
+        }
+
+        if (!instruction.is_terminator())
+            continue;
+
+        using enum Instruction::Type;
+        switch (instruction.type()) {
+        case Jump: {
+            auto true_target = *static_cast<Op::Jump const&>(instruction).true_target();
+            enter_label(true_target, current_block);
+            return;
+        }
+        case JumpIf:
+        case JumpNullish:
+        case JumpUndefined: {
+            // FIXME: It would be nice if we could avoid this copy, if we know that the unwind context stays the same in both paths
+            //        Or with a COW capable Vector alternative
+            // Note: We might partially unwind here, so we need to make a copy of
+            //       the current context to assure that the falsy code path has the same one
+            {
+                TemporaryChange saved_context { unwind_frames, unwind_frames };
+
+                auto true_target = *static_cast<Op::Jump const&>(instruction).true_target();
+                enter_label(true_target, current_block);
+            }
+
+            auto false_target = *static_cast<Op::Jump const&>(instruction).false_target();
+            enter_label(false_target, current_block);
+            return;
+        }
+        case Yield: {
+            auto continuation = static_cast<Op::Yield const&>(instruction).continuation();
+            if (continuation.has_value()) {
+                executable.exported_blocks->set(&continuation->block());
+                enter_label(*continuation, current_block);
+            } else if (auto const* finalizer = unwind_frames.last().finalizer) {
+                enter_label(Label { *finalizer }, current_block);
+                unwind_frames.last().finalizer_targets.append(nullptr);
+            }
+            return;
+        }
+        case Await: {
+            auto const& continuation = static_cast<Op::Await const&>(instruction).continuation();
+            executable.exported_blocks->set(&continuation.block());
+            enter_label(continuation, current_block);
+            return;
+        }
+        case EnterUnwindContext: {
+            auto entry_point = static_cast<Op::EnterUnwindContext const&>(instruction).entry_point();
+            auto handler_target = static_cast<Op::EnterUnwindContext const&>(instruction).handler();
+            auto finalizer_target = static_cast<Op::EnterUnwindContext const&>(instruction).finalizer();
+
+            // We keep the frame alive here on the stack, to save some allocation size
+            UnwindFrame frame {
+                .handler = handler_target.has_value() ? &handler_target->block() : nullptr,
+                .finalizer = finalizer_target.has_value() ? &finalizer_target->block() : nullptr,
+                .finalizer_targets = {}
+            };
+
+            unwind_frames.append(frame);
+
+            {
+                // This will enter the handler and finalizer when needed.
+                TemporaryChange saved_context { unwind_frames, unwind_frames };
+                enter_label(entry_point, current_block);
+            }
+            frame.handler = nullptr;
+            if (handler_target.has_value()) {
+                // We manually generate the CFG, because we previously skiped it
+                TemporaryChange saved_context { unwind_frames, unwind_frames };
+                generate_cfg_for_block(handler_target->block(), executable);
+            }
+
+            if (finalizer_target.has_value()) {
+                // We manually generate the CFG, because we previously halted before entering it
+                generate_cfg_for_block(finalizer_target->block(), executable);
+
+                // We previously halted execution when we would enter the finalizer,
+                // So we now have to visit all possible targets
+                // This mainly affects the ScheduleJump instruction
+                for (auto const* block : frame.finalizer_targets) {
+                    if (block == nullptr) {
+                        // This signals a `return`, which we do not handle specially, so we skip
+                        continue;
+                    }
+                    if (!seen_blocks.contains(block))
+                        generate_cfg_for_block(*block, executable);
+                }
+            } else {
+                unwind_frames.take_last();
+                VERIFY(frame.finalizer_targets.is_empty());
+            }
+
+            return;
+        }
+        case ContinuePendingUnwind: {
+            auto resume_target = static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target();
+            enter_label(resume_target, current_block);
+            // Note: We already mark these possible control flow changes further up, but when we get
+            //       get better error awareness, being explicit here will be required
+            if (auto const* handler = unwind_frames.last().handler)
+                enter_label(Label { *handler }, current_block);
+            else if (auto const* finalizer = unwind_frames.last().finalizer)
+                enter_label(Label { *finalizer }, current_block);
+
+            return;
+        }
+        case Throw:
+            // Note: We technically register that we enter the handler in the prelude,
+            //       but lets be correct and mark it again,
+            //       this will be useful once we have more info on which instruction can
+            //       actually fail
+            if (auto const* handler = unwind_frames.last().handler) {
+                enter_label(Label { *handler }, current_block);
+            } else if (auto const* finalizer = unwind_frames.last().finalizer) {
+                enter_label(Label { *finalizer }, current_block);
+                // Note: This error might bubble through the finalizer to the next handler/finalizer,
+                //       This is currently marked in the general path
+            }
+            return;
+        case Return:
+            if (auto const* finalizer = unwind_frames.last().finalizer) {
+                enter_label(Label { *finalizer }, current_block);
+                unwind_frames.last().finalizer_targets.append(nullptr);
+            }
+            return;
+        case ScheduleJump: {
+            enter_label(Label { *unwind_frames.last().finalizer }, current_block);
+
+            unwind_frames.last().finalizer_targets.append(
+                &static_cast<Op::ScheduleJump const&>(instruction).target().block());
+            return;
+        }
+        case End:
+            return;
+        default:
+            dbgln("Unhandled terminator instruction: `{}`", instruction.to_byte_string(executable.executable));
+            VERIFY_NOT_REACHED();
+        };
+    }
+
+    // We have left the block, but not through a designated terminator,
+    // so before we return, we need to check if we still need to go through a finalizer
+    if (auto const* finalizer = unwind_frames.last().finalizer)
+        enter_label(Label { *finalizer }, current_block);
+}
+
+void GenerateCFG::perform(PassPipelineExecutable& executable)
+{
+    started();
+
+    executable.cfg = HashMap<BasicBlock const*, HashTable<BasicBlock const*>> {};
+    executable.inverted_cfg = HashMap<BasicBlock const*, HashTable<BasicBlock const*>> {};
+    executable.exported_blocks = HashTable<BasicBlock const*> {};
+
+    seen_blocks.clear();
+    unwind_frames.clear();
+    UnwindFrame top_level_frame = {};
+
+    unwind_frames.append(top_level_frame);
+
+    generate_cfg_for_block(*executable.executable.basic_blocks.first(), executable);
+
+    finished();
+}
+
+}

+ 177 - 0
Userland/Libraries/LibJS/Bytecode/Pass/MergeBlocks.cpp

@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/PassManager.h>
+
+namespace JS::Bytecode::Passes {
+
+void MergeBlocks::perform(PassPipelineExecutable& executable)
+{
+    started();
+
+    VERIFY(executable.cfg.has_value());
+    VERIFY(executable.inverted_cfg.has_value());
+    auto cfg = executable.cfg.release_value();
+    auto inverted_cfg = executable.inverted_cfg.release_value();
+
+    // Figure out which blocks can be merged
+    HashTable<BasicBlock const*> blocks_to_merge;
+    HashMap<BasicBlock const*, BasicBlock const*> blocks_to_replace;
+    Vector<BasicBlock const*> blocks_to_remove;
+    Vector<size_t> boundaries;
+
+    for (auto& entry : cfg) {
+        if (entry.value.size() != 1)
+            continue;
+
+        if (executable.exported_blocks->contains(*entry.value.begin()))
+            continue;
+
+        if (!entry.key->is_terminated())
+            continue;
+
+        if (entry.key->terminator()->type() != Instruction::Type::Jump)
+            continue;
+
+        {
+            InstructionStreamIterator it { entry.key->instruction_stream() };
+            auto& first_instruction = *it;
+            if (first_instruction.type() == Instruction::Type::Jump) {
+                auto const* replacing_block = &static_cast<Op::Jump const&>(first_instruction).true_target()->block();
+                if (replacing_block != entry.key) {
+                    blocks_to_replace.set(entry.key, replacing_block);
+                }
+                continue;
+            }
+        }
+
+        if (auto cfg_iter = inverted_cfg.find(*entry.value.begin()); cfg_iter != inverted_cfg.end()) {
+            auto& predecessor_entry = cfg_iter->value;
+            if (predecessor_entry.size() != 1)
+                continue;
+        }
+
+        // The two blocks are safe to merge.
+        blocks_to_merge.set(entry.key);
+    }
+
+    for (auto& entry : blocks_to_replace) {
+        auto const* replacement = entry.value;
+        for (;;) {
+            auto lookup = blocks_to_replace.get(replacement);
+            if (!lookup.has_value())
+                break;
+            if (replacement == *lookup)
+                break;
+            replacement = *lookup;
+        }
+        entry.value = replacement;
+    }
+
+    auto replace_blocks = [&](auto& blocks, auto& replacement) {
+        Optional<size_t> first_successor_position;
+        for (auto& entry : blocks) {
+            blocks_to_remove.append(entry);
+            auto it = executable.executable.basic_blocks.find_if([entry](auto& block) { return entry == block; });
+            VERIFY(!it.is_end());
+            if (!first_successor_position.has_value())
+                first_successor_position = it.index();
+        }
+        for (auto& block : executable.executable.basic_blocks) {
+            InstructionStreamIterator it { block->instruction_stream() };
+            while (!it.at_end()) {
+                auto& instruction = *it;
+                ++it;
+                for (auto& entry : blocks)
+                    const_cast<Instruction&>(instruction).replace_references(*entry, replacement);
+            }
+        }
+        return first_successor_position;
+    };
+
+    for (auto& entry : blocks_to_replace) {
+        AK::Array candidates { entry.key };
+        (void)replace_blocks(candidates, *entry.value);
+    }
+
+    while (!blocks_to_merge.is_empty()) {
+        auto it = blocks_to_merge.begin();
+        auto const* current_block = *it;
+        blocks_to_merge.remove(it);
+
+        Vector<BasicBlock const*> successors { current_block };
+        for (;;) {
+            auto const* last = successors.last();
+            auto entry = cfg.find(last);
+            if (entry == cfg.end())
+                break;
+            auto const* successor = *entry->value.begin();
+            successors.append(successor);
+
+            if (!blocks_to_merge.remove(successor))
+                break;
+        }
+
+        auto blocks_to_merge_copy = blocks_to_merge;
+        // We need to do the following multiple times, due to it not being
+        // guaranteed, that the blocks are in sequential order
+        bool did_prepend = true;
+        while (did_prepend) {
+            did_prepend = false;
+            for (auto const* last : blocks_to_merge) {
+                auto entry = cfg.find(last);
+                if (entry == cfg.end())
+                    continue;
+                auto const* successor = *entry->value.begin();
+                if (successor == successors.first()) {
+                    successors.prepend(last);
+                    blocks_to_merge_copy.remove(last);
+                    did_prepend = true;
+                }
+            }
+        }
+
+        blocks_to_merge = move(blocks_to_merge_copy);
+
+        StringBuilder builder;
+        builder.append("merge"sv);
+        for (auto& entry : successors) {
+            builder.append('.');
+            builder.append(entry->name());
+        }
+
+        auto new_block = BasicBlock::create(MUST(builder.to_string()));
+        auto& block = *new_block;
+        auto first_successor_position = replace_blocks(successors, *new_block);
+        VERIFY(first_successor_position.has_value());
+
+        size_t last_successor_index = successors.size() - 1;
+        for (size_t i = 0; i < successors.size(); ++i) {
+            auto& entry = successors[i];
+            InstructionStreamIterator it { entry->instruction_stream() };
+            while (!it.at_end()) {
+                auto& instruction = *it;
+                ++it;
+                if (instruction.is_terminator() && last_successor_index != i)
+                    break;
+                // FIXME: Use a single memcpy to copy the whole block at once.
+                auto instruction_size = instruction.length();
+                size_t slot_offset = block.size();
+                block.grow(instruction_size);
+                auto* next_slot = block.data() + slot_offset;
+                memcpy(next_slot, &instruction, instruction_size);
+            }
+        }
+
+        executable.executable.basic_blocks.insert(*first_successor_position, move(new_block));
+    }
+
+    executable.executable.basic_blocks.remove_all_matching([&blocks_to_remove](auto& candidate) { return blocks_to_remove.contains_slow(candidate.ptr()); });
+
+    finished();
+}
+
+}

+ 58 - 0
Userland/Libraries/LibJS/Bytecode/Pass/PlaceBlocks.cpp

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/PassManager.h>
+
+namespace JS::Bytecode::Passes {
+
+void PlaceBlocks::perform(PassPipelineExecutable& executable)
+{
+    started();
+
+    VERIFY(executable.cfg.has_value());
+    auto cfg = executable.cfg.release_value();
+
+    Vector<BasicBlock&> replaced_blocks;
+    HashTable<BasicBlock const*> reachable_blocks;
+
+    // Visit the blocks in CFG order
+    Function<void(BasicBlock const*)> visit = [&](auto* block) {
+        if (reachable_blocks.contains(block))
+            return;
+
+        reachable_blocks.set(block);
+        replaced_blocks.append(*const_cast<BasicBlock*>(block));
+
+        auto children = cfg.find(block);
+        if (children == cfg.end())
+            return;
+
+        for (auto& entry : children->value)
+            visit(entry);
+    };
+
+    // Make sure to visit the entry block first
+    visit(executable.executable.basic_blocks.first());
+
+    for (auto& entry : cfg)
+        visit(entry.key);
+
+    // Put the unreferenced blocks back in at the end
+    for (auto& entry : static_cast<Vector<NonnullOwnPtr<BasicBlock>>&>(executable.executable.basic_blocks)) {
+        if (reachable_blocks.contains(entry.ptr()))
+            (void)entry.leak_ptr();
+        else
+            replaced_blocks.append(*entry.leak_ptr()); // Don't try to do DCE here.
+    }
+
+    executable.executable.basic_blocks.clear();
+    for (auto& block : replaced_blocks)
+        executable.executable.basic_blocks.append(adopt_own(block));
+
+    finished();
+}
+
+}

+ 68 - 0
Userland/Libraries/LibJS/Bytecode/Pass/UnifySameBlocks.cpp

@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/PassManager.h>
+#include <string.h>
+
+namespace JS::Bytecode::Passes {
+
+void UnifySameBlocks::perform(PassPipelineExecutable& executable)
+{
+    started();
+
+    VERIFY(executable.cfg.has_value());
+    VERIFY(executable.inverted_cfg.has_value());
+    auto cfg = executable.cfg.release_value();
+    auto inverted_cfg = executable.inverted_cfg.release_value();
+
+    HashMap<BasicBlock const*, BasicBlock const*> equal_blocks;
+
+    for (size_t i = 0; i < executable.executable.basic_blocks.size(); ++i) {
+        auto& block = executable.executable.basic_blocks[i];
+        auto block_bytes = block->instruction_stream();
+        for (auto& candidate_block : executable.executable.basic_blocks.span().slice(i + 1)) {
+            if (equal_blocks.contains(&*candidate_block))
+                continue;
+            // FIXME: This can probably be relaxed a bit...
+            if (candidate_block->size() != block->size())
+                continue;
+            if (candidate_block->finalizer() != block->finalizer())
+                continue;
+            if (candidate_block->handler() != block->handler())
+                continue;
+
+            auto candidate_bytes = candidate_block->instruction_stream();
+            if (memcmp(candidate_bytes.data(), block_bytes.data(), candidate_block->size()) == 0)
+                equal_blocks.set(candidate_block.ptr(), block);
+        }
+    }
+
+    auto replace_blocks = [&](auto& match, auto& replacement) {
+        Optional<size_t> first_successor_position;
+        auto it = executable.executable.basic_blocks.find_if([match](auto& block) { return match == block; });
+        VERIFY(!it.is_end());
+        executable.executable.basic_blocks.remove(it.index());
+        if (!first_successor_position.has_value())
+            first_successor_position = it.index();
+
+        for (auto& block : executable.executable.basic_blocks) {
+            InstructionStreamIterator it { block->instruction_stream() };
+            while (!it.at_end()) {
+                auto& instruction = *it;
+                ++it;
+                const_cast<Instruction&>(instruction).replace_references(*match, replacement);
+            }
+        }
+        return first_successor_position;
+    };
+
+    for (auto& entry : equal_blocks)
+        (void)replace_blocks(entry.key, *entry.value);
+
+    finished();
+}
+
+}

+ 127 - 0
Userland/Libraries/LibJS/Bytecode/PassManager.h

@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibCore/ElapsedTimer.h>
+#include <LibJS/Bytecode/BasicBlock.h>
+#include <LibJS/Bytecode/Generator.h>
+
+namespace JS::Bytecode {
+
+struct PassPipelineExecutable {
+    Executable& executable;
+    Optional<HashMap<BasicBlock const*, HashTable<BasicBlock const*>>> cfg {};
+    Optional<HashMap<BasicBlock const*, HashTable<BasicBlock const*>>> inverted_cfg {};
+    Optional<HashTable<BasicBlock const*>> exported_blocks {};
+};
+
+class Pass {
+public:
+    Pass() = default;
+    virtual ~Pass() = default;
+
+    virtual void perform(PassPipelineExecutable&) = 0;
+    void started()
+    {
+        m_timer.start();
+    }
+    void finished()
+    {
+        m_time_difference = m_timer.elapsed_time();
+    }
+
+    u64 elapsed() const { return m_time_difference.to_microseconds(); }
+
+protected:
+    Core::ElapsedTimer m_timer;
+    Duration m_time_difference {};
+};
+
+class PassManager : public Pass {
+public:
+    PassManager() = default;
+    ~PassManager() override = default;
+
+    void add(NonnullOwnPtr<Pass> pass) { m_passes.append(move(pass)); }
+
+    template<typename PassT, typename... Args>
+    void add(Args&&... args) { m_passes.append(make<PassT>(forward<Args>(args)...)); }
+
+    void perform(Executable& executable)
+    {
+        PassPipelineExecutable pipeline_executable { executable };
+        perform(pipeline_executable);
+    }
+
+    virtual void perform(PassPipelineExecutable& executable) override
+    {
+        started();
+        for (auto& pass : m_passes)
+            pass->perform(executable);
+        finished();
+    }
+
+private:
+    Vector<NonnullOwnPtr<Pass>> m_passes;
+};
+
+namespace Passes {
+
+class GenerateCFG : public Pass {
+public:
+    GenerateCFG() = default;
+    ~GenerateCFG() override = default;
+
+private:
+    virtual void perform(PassPipelineExecutable&) override;
+};
+
+class MergeBlocks : public Pass {
+public:
+    MergeBlocks() = default;
+    ~MergeBlocks() override = default;
+
+private:
+    virtual void perform(PassPipelineExecutable&) override;
+};
+
+class PlaceBlocks : public Pass {
+public:
+    PlaceBlocks() = default;
+    ~PlaceBlocks() override = default;
+
+private:
+    virtual void perform(PassPipelineExecutable&) override;
+};
+
+class UnifySameBlocks : public Pass {
+public:
+    UnifySameBlocks() = default;
+    ~UnifySameBlocks() override = default;
+
+private:
+    virtual void perform(PassPipelineExecutable&) override;
+};
+
+class DumpCFG : public Pass {
+public:
+    DumpCFG(FILE* file)
+        : m_file(file)
+    {
+    }
+
+    ~DumpCFG() override = default;
+
+private:
+    virtual void perform(PassPipelineExecutable&) override;
+
+    FILE* m_file { nullptr };
+};
+
+}
+
+}

+ 5 - 0
Userland/Libraries/LibJS/CMakeLists.txt

@@ -9,6 +9,11 @@ set(SOURCES
     Bytecode/IdentifierTable.cpp
     Bytecode/Instruction.cpp
     Bytecode/Interpreter.cpp
+    Bytecode/Pass/DumpCFG.cpp
+    Bytecode/Pass/GenerateCFG.cpp
+    Bytecode/Pass/MergeBlocks.cpp
+    Bytecode/Pass/PlaceBlocks.cpp
+    Bytecode/Pass/UnifySameBlocks.cpp
     Bytecode/RegexTable.cpp
     Bytecode/StringTable.cpp
     Console.cpp