
This commit adds a bunch of passes, the most interesting of which is a pass that merges blocks together, and a pass that places blocks that flow into each other next to each other, and a very simply pass that removes duplicate basic blocks. Note that this does not remove the jump at the end of each block in that pass to avoid scope creep in the passes.
165 lines
5.7 KiB
C++
165 lines
5.7 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 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;
|
|
|
|
{
|
|
InstructionStreamIterator it { entry.key->instruction_stream() };
|
|
auto& first_instruction = *it;
|
|
if (first_instruction.is_terminator()) {
|
|
if (first_instruction.type() == Instruction::Type::Jump) {
|
|
auto 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_entry = inverted_cfg.get(*entry.value.begin()); cfg_entry.has_value()) {
|
|
auto& predecssor_entry = *cfg_entry;
|
|
if (predecssor_entry.size() != 1)
|
|
continue;
|
|
}
|
|
|
|
// The two blocks are safe to merge.
|
|
blocks_to_merge.set(entry.key);
|
|
}
|
|
|
|
for (auto& entry : blocks_to_replace) {
|
|
auto 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 current_block = *it;
|
|
blocks_to_merge.remove(it);
|
|
Vector<BasicBlock const*> successors { current_block };
|
|
for (;;) {
|
|
auto last = successors.last();
|
|
auto entry = cfg.get(last);
|
|
if (!entry.has_value())
|
|
break;
|
|
auto& successor = *entry->begin();
|
|
successors.append(successor);
|
|
auto it = blocks_to_merge.find(successor);
|
|
if (it == blocks_to_merge.end())
|
|
break;
|
|
blocks_to_merge.remove(it);
|
|
}
|
|
|
|
auto blocks_to_merge_copy = blocks_to_merge;
|
|
for (auto& last : blocks_to_merge) {
|
|
auto entry = cfg.get(last);
|
|
if (!entry.has_value())
|
|
continue;
|
|
auto successor = *entry->begin();
|
|
if (auto it = successors.find(successor); !it.is_end()) {
|
|
successors.insert(it.index(), last);
|
|
blocks_to_merge_copy.remove(last);
|
|
}
|
|
}
|
|
|
|
blocks_to_merge = move(blocks_to_merge_copy);
|
|
|
|
size_t size = 0;
|
|
StringBuilder builder;
|
|
builder.append("merge");
|
|
for (auto& entry : successors) {
|
|
size += entry->size();
|
|
builder.append('.');
|
|
builder.append(entry->name());
|
|
}
|
|
|
|
auto new_block = BasicBlock::create(builder.build(), size);
|
|
auto& block = *new_block;
|
|
|
|
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() };
|
|
size_t copy_end = 0;
|
|
while (!it.at_end()) {
|
|
auto& instruction = *it;
|
|
++it;
|
|
if (instruction.is_terminator() && last_successor_index != i)
|
|
break;
|
|
copy_end = it.offset();
|
|
}
|
|
__builtin_memcpy(block.next_slot(), entry->instruction_stream().data(), copy_end);
|
|
block.grow(copy_end);
|
|
}
|
|
|
|
auto first_successor_position = replace_blocks(successors, *new_block);
|
|
VERIFY(first_successor_position.has_value());
|
|
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();
|
|
}
|
|
|
|
}
|