GenerateCFG.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /*
  2. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Bytecode/PassManager.h>
  7. namespace JS::Bytecode::Passes {
  8. struct UnwindFrame {
  9. BasicBlock const* handler;
  10. BasicBlock const* finalizer;
  11. };
  12. static HashTable<BasicBlock const*> seen_blocks;
  13. static Vector<UnwindFrame*> unwind_frames;
  14. static void generate_cfg_for_block(BasicBlock const& current_block, PassPipelineExecutable executable)
  15. {
  16. seen_blocks.set(&current_block);
  17. auto enter_label = [&](Label const& label, auto& entering_block) {
  18. executable.cfg->ensure(&entering_block).set(&label.block());
  19. executable.inverted_cfg->ensure(&label.block()).set(&entering_block);
  20. // The finalizer of an unwind context is handled separately
  21. if (!seen_blocks.contains(&label.block()) && unwind_frames.last()->finalizer != &current_block)
  22. generate_cfg_for_block(label.block(), executable);
  23. };
  24. if (auto const* handler = unwind_frames.last()->handler)
  25. enter_label(Label { *handler }, current_block);
  26. for (InstructionStreamIterator it { current_block.instruction_stream() }; !it.at_end(); ++it) {
  27. auto const& instruction = *it;
  28. if (instruction.type() == Instruction::Type::LeaveUnwindContext) {
  29. if (unwind_frames.last()->finalizer && unwind_frames.last()->finalizer != &current_block)
  30. dbgln("FIXME: Popping finalizer from the unwind context from outside the finalizer");
  31. unwind_frames.take_last();
  32. }
  33. if (!instruction.is_terminator())
  34. continue;
  35. using enum Instruction::Type;
  36. switch (instruction.type()) {
  37. case Jump: {
  38. auto true_target = *static_cast<Op::Jump const&>(instruction).true_target();
  39. enter_label(true_target, current_block);
  40. return;
  41. }
  42. case JumpConditional:
  43. case JumpNullish:
  44. case JumpUndefined: {
  45. // FIXME: Can we avoid this copy
  46. // Note: We might partially unwind here, so we need to make a copy of
  47. // the current context to assure that the falsy code path has the same one
  48. auto saved_context = unwind_frames;
  49. auto true_target = *static_cast<Op::Jump const&>(instruction).true_target();
  50. enter_label(true_target, current_block);
  51. unwind_frames = move(saved_context);
  52. auto false_target = *static_cast<Op::Jump const&>(instruction).false_target();
  53. enter_label(false_target, current_block);
  54. return;
  55. }
  56. case Yield: {
  57. auto continuation = static_cast<Op::Yield const&>(instruction).continuation();
  58. if (continuation.has_value()) {
  59. executable.exported_blocks->set(&continuation->block());
  60. enter_label(*continuation, current_block);
  61. } else if (auto const* finalizer = unwind_frames.is_empty() ? nullptr : unwind_frames.last()->finalizer) {
  62. enter_label(Label { *finalizer }, current_block);
  63. }
  64. return;
  65. }
  66. case EnterUnwindContext: {
  67. auto entry_point = static_cast<Op::EnterUnwindContext const&>(instruction).entry_point();
  68. auto handler_target = static_cast<Op::EnterUnwindContext const&>(instruction).handler_target();
  69. auto finalizer_target = static_cast<Op::EnterUnwindContext const&>(instruction).finalizer_target();
  70. // We keep the frame alive here on the stack, to save some allocation size
  71. UnwindFrame frame { nullptr, finalizer_target.has_value() ? &finalizer_target->block() : nullptr };
  72. unwind_frames.append(&frame);
  73. if (handler_target.has_value()) {
  74. // We might pop from this context while in the handler, so we
  75. // need to save it and return it to its rightful place
  76. // FIXME: We can relax this when we are stricter about entering finally statements
  77. auto saved_context = unwind_frames;
  78. enter_label(*handler_target, current_block);
  79. unwind_frames = move(saved_context);
  80. frame.handler = &handler_target->block();
  81. }
  82. {
  83. // We might pop from this context while in the handler, so we
  84. // need to save it and return it to its rightful place
  85. // FIXME: We can relax this when we are stricter about entering finally statements
  86. auto saved_context = unwind_frames;
  87. enter_label(entry_point, current_block);
  88. unwind_frames = move(saved_context);
  89. }
  90. frame.handler = nullptr;
  91. if (finalizer_target.has_value()) {
  92. auto saved_context = unwind_frames;
  93. enter_label(*finalizer_target, current_block);
  94. }
  95. return;
  96. }
  97. case ContinuePendingUnwind: {
  98. auto resume_target = static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target();
  99. enter_label(resume_target, current_block);
  100. return;
  101. }
  102. case Throw:
  103. // Note: We technically register that we enter the handler in the prelude,
  104. // but lets be correct and mark it again,
  105. // this will be useful once we have more info on which instruction can
  106. // actually fail
  107. if (auto const* handler = unwind_frames.last()->finalizer)
  108. enter_label(Label { *handler }, current_block);
  109. else if (auto const* finalizer = unwind_frames.last()->finalizer)
  110. enter_label(Label { *finalizer }, current_block);
  111. return;
  112. case Return:
  113. if (auto const* finalizer = unwind_frames.last()->finalizer)
  114. enter_label(Label { *finalizer }, current_block);
  115. return;
  116. case ScheduleJump:
  117. enter_label(Label { *unwind_frames.last()->finalizer }, current_block);
  118. enter_label(static_cast<Op::ScheduleJump const&>(instruction).target(), *unwind_frames.last()->finalizer);
  119. return;
  120. default:
  121. dbgln("Unhandled terminator instruction: `{}`", instruction.to_deprecated_string(executable.executable));
  122. VERIFY_NOT_REACHED();
  123. };
  124. }
  125. // We have left the block, but not through a designated terminator,
  126. // so before we return, we need to check if we still need to go through a finalizer
  127. if (auto const* finalizer = unwind_frames.last()->finalizer)
  128. enter_label(Label { *finalizer }, current_block);
  129. }
  130. void GenerateCFG::perform(PassPipelineExecutable& executable)
  131. {
  132. started();
  133. executable.cfg = HashMap<BasicBlock const*, HashTable<BasicBlock const*>> {};
  134. executable.inverted_cfg = HashMap<BasicBlock const*, HashTable<BasicBlock const*>> {};
  135. executable.exported_blocks = HashTable<BasicBlock const*> {};
  136. seen_blocks.clear();
  137. unwind_frames.clear();
  138. UnwindFrame top_level_frame = {};
  139. unwind_frames.append(&top_level_frame);
  140. generate_cfg_for_block(*executable.executable.basic_blocks.first(), executable);
  141. finished();
  142. }
  143. }