1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 07:25:07 +00:00
serenity/Userland/Libraries/LibJS/Bytecode/Pass/GenerateCFG.cpp
Hendiadyoin1 5181957da5 LibJS: VERIFY on unknown terminator opcodes in GenerateCFG
If we mess up in here it could break promises later optimization stages
assume, so rather than having wrong results, make it scream as load as
it can.
2023-02-26 19:40:09 +01:00

107 lines
4 KiB
C++

/*
* 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 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*> {};
Vector<InstructionStreamIterator> iterators;
Vector<BasicBlock const&> entered_blocks;
HashTable<BasicBlock const*> seen_blocks;
auto enter_label = [&](auto const& label, auto& entering_block, bool exported = false) {
auto& entry = executable.cfg->ensure(&entering_block);
entry.set(&label->block());
auto& inverse_entry = executable.inverted_cfg->ensure(&label->block());
inverse_entry.set(&entering_block);
if (exported)
executable.exported_blocks->set(&label->block());
if (!seen_blocks.contains(&label->block())) {
seen_blocks.set(&label->block());
entered_blocks.append(label->block());
iterators.empend(label->block().instruction_stream());
}
};
seen_blocks.set(&executable.executable.basic_blocks.first());
entered_blocks.append(executable.executable.basic_blocks.first());
iterators.empend(executable.executable.basic_blocks.first().instruction_stream());
while (!entered_blocks.is_empty()) {
if (iterators.last().at_end()) {
entered_blocks.take_last();
iterators.take_last();
continue;
}
auto const& instruction = *iterators.last();
++iterators.last();
if (!instruction.is_terminator())
continue;
auto const& current_block = entered_blocks.last();
using enum Instruction::Type;
switch (instruction.type()) {
case Jump: {
auto const& true_target = static_cast<Op::Jump const&>(instruction).true_target();
enter_label(true_target, current_block);
continue;
}
case JumpConditional:
case JumpNullish:
case JumpUndefined: {
auto const& true_target = static_cast<Op::Jump const&>(instruction).true_target();
enter_label(true_target, current_block);
auto const& false_target = static_cast<Op::Jump const&>(instruction).false_target();
enter_label(false_target, current_block);
continue;
}
case Yield: {
auto const& continuation = static_cast<Op::Yield const&>(instruction).continuation();
if (continuation.has_value())
enter_label(continuation, current_block, true);
continue;
}
case EnterUnwindContext: {
auto const& entry_point = static_cast<Op::EnterUnwindContext const&>(instruction).entry_point();
auto const& handler_target = static_cast<Op::EnterUnwindContext const&>(instruction).handler_target();
auto const& finalizer_target = static_cast<Op::EnterUnwindContext const&>(instruction).finalizer_target();
enter_label(&entry_point, current_block);
if (handler_target.has_value())
enter_label(handler_target, current_block);
if (finalizer_target.has_value())
enter_label(finalizer_target, current_block);
continue;
}
case ContinuePendingUnwind: {
auto const& resume_target = static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target();
enter_label(&resume_target, current_block);
continue;
}
case Throw:
case Return:
iterators.take_last();
entered_blocks.take_last();
continue;
default:
dbgln("Unhandled terminator instruction: `{}`", instruction.to_deprecated_string(executable.executable));
VERIFY_NOT_REACHED();
};
}
finished();
}
}