Compare commits

..

7 Commits

15 changed files with 979 additions and 138 deletions
+1
View File
@@ -17,6 +17,7 @@ Checks: >
-readability-redundant-access-specifiers,
-readability-use-anyofallof,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-performance-enum-size,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
+1 -1
View File
@@ -24,7 +24,7 @@ struct lexer_error {
std::string message;
};
using token_r = furlang::result<token, lexer_error>;
using token_r = furlang::result<lexer_error, token>;
class lexer {
public:
+4 -4
View File
@@ -19,7 +19,7 @@ token_r lexer::next_token() {
// TODO: Add support for single-line comments
// TODO: Add support for multi-line comments
if (m_cursor >= m_content.size()) return token_r{ lexer_error{ lexer_error::EndOfFile, location() } };
if (m_cursor >= m_content.size()) return token_r::error(lexer_error{ lexer_error::EndOfFile, location() });
// TODO: Add support for negative integers (I am positive thanks to stasiu :v:)
// TODO: Add support for hexadecimal and binary numeric literals
@@ -93,9 +93,9 @@ token_r lexer::next_token() {
case '.': ++m_cursor; return { token::Dot };
case ':': ++m_cursor; return { token::Colon };
default:
return token_r{
lexer_error{ lexer_error::UnknownCharacter, location(), "Unknown character '"s + m_content[m_cursor] + "'" }
};
return token_r::error(lexer_error{ lexer_error::UnknownCharacter,
location(),
"Unknown character '"s + m_content[m_cursor] + "'" });
}
}
+2 -3
View File
@@ -10,7 +10,6 @@ target_link_libraries(furc PRIVATE libfurc)
include(GoogleTest)
file(GLOB_RECURSE FURC_TESTS "test/**.cpp")
add_executable(furc_tests ${FURC_TESTS})
add_executable(furc_tests "test/ssa.cpp")
target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main)
# gtest_discover_tests(furc_tests)
gtest_discover_tests(furc_tests)
+34 -1
View File
@@ -9,6 +9,7 @@
#include <initializer_list>
#include <optional>
#include <stack>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
@@ -55,6 +56,9 @@ struct ir_operand {
value_u(std::uint16_t variable)
: variable(variable) {}
value_u(register_s reg)
: reg(reg) {}
value_u(std::uint64_t first, std::uint64_t second)
: blockPair({ first, second }) {}
@@ -65,6 +69,30 @@ struct ir_operand {
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_u, Args...>>>
ir_operand(type_e type, Args&&... args)
: type(type), value(std::forward<Args>(args)...) {}
static ir_operand reg(std::uint64_t name, std::uint64_t ver) {
return { Register, value_u::register_s{ name, ver } };
}
bool operator==(const ir_operand& rhs) const {
if (type != rhs.type) return false;
switch (type) {
case Integer: return value.integer == rhs.value.integer;
case Register: return value.reg.name == rhs.value.reg.name && value.reg.ver == rhs.value.reg.ver;
case Variable: return value.variable == rhs.value.variable;
case Global: return value.global == rhs.value.global;
case Function: return value.function == rhs.value.function;
case Block: return value.block == rhs.value.block;
case BlockPair:
return value.blockPair.first == rhs.value.blockPair.first &&
value.blockPair.second == rhs.value.blockPair.second;
case PhiPair:
return value.phiPair.block == rhs.value.phiPair.block &&
value.phiPair.reg.name == rhs.value.phiPair.reg.name &&
value.phiPair.reg.ver == rhs.value.phiPair.reg.ver;
}
throw std::runtime_error("unreachable");
}
};
struct ir_type {
@@ -139,6 +167,10 @@ struct ir_instruction {
default: return false;
}
}
bool operator==(const ir_instruction& rhs) const {
return type == rhs.type && destination == rhs.destination && sources == rhs.sources;
}
};
struct ir_basic_block {
@@ -225,9 +257,10 @@ struct ir_function : ir_scope {
std::vector<ir_basic_block> blocks;
std::uint64_t regCount = 0;
std::uint64_t varCount = 0;
const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) final {
return variables[name] = arena.allocate<ir_function_variable>(type, regCount++);
return variables[name] = arena.allocate<ir_function_variable>(type, varCount++);
}
static ir_function from_name(std::string&& name) {
+87 -3
View File
@@ -3,13 +3,97 @@
#include "furc/middle/ir.hpp"
#include <cassert>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc {
class ssa {
ssa() = delete;
public:
static void process(ir_module& mod);
static void destruct(ir_module& mod);
struct cfg_block {
std::unordered_set<std::uint64_t> preds;
std::unordered_set<std::uint64_t> sucs;
};
struct ssa_block {
std::size_t order = 0;
std::uint64_t idom = -1;
std::unordered_set<std::uint64_t> children; // Children of the block in dominator tree
// Dominance Frontiers
std::unordered_set<std::uint64_t> df;
};
struct register_info {
std::unordered_set<std::uint64_t> sites; // Definition Sites
};
public:
ssa(ir_function& func) {
m_registers.resize(func.regCount);
compute_cfg(func.blocks, m_cfgBlocks);
collect_registers(func.blocks, m_registers, m_globals);
std::vector<std::uint64_t> order;
compute_rpo(m_cfgBlocks, m_ssaBlocks, order);
build_dtree(m_cfgBlocks, m_ssaBlocks, order);
compute_dfrontiers(m_cfgBlocks, m_ssaBlocks);
ssaification(func.blocks, m_cfgBlocks, m_ssaBlocks, m_registers, m_globals);
rename(func.blocks, func.regCount, m_cfgBlocks, m_ssaBlocks, order);
}
public:
static void compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks);
static void collect_registers(const std::vector<ir_basic_block>& irBlocks,
std::vector<register_info>& registers,
std::unordered_set<std::uint64_t>& globals);
static void build_dtree(const std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
const std::vector<std::size_t>& order);
static void compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks);
static void compute_rpo(std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
std::vector<std::size_t>& order);
static void ssaification(std::vector<ir_basic_block>& irBlocks,
const std::vector<cfg_block>& cfgBlocks,
const std::vector<ssa_block>& ssaBlocks,
const std::vector<register_info>& registers,
const std::unordered_set<std::uint64_t>& globals);
static void rename(std::vector<ir_basic_block>& irBlocks,
std::size_t regCount,
const std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
const std::vector<std::uint64_t>& order);
private:
static void rename_rec(std::vector<std::uint64_t>& counters,
std::vector<std::stack<std::uint64_t>>& stacks,
std::vector<ir_basic_block>& irBlocks,
const std::vector<cfg_block>& cfgBlocks,
const std::vector<ssa_block>& ssaBlocks,
std::size_t blockIdx);
private:
static void rpo_dfs(std::unordered_set<std::size_t>& visited,
std::vector<std::size_t>& order,
std::size_t block,
const std::vector<cfg_block>& blocks);
static std::size_t intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2);
private:
std::vector<cfg_block> m_cfgBlocks;
std::vector<ssa_block> m_ssaBlocks;
std::vector<register_info> m_registers;
std::unordered_set<std::uint64_t> m_globals;
};
} // namespace furc
+7 -1
View File
@@ -1,3 +1,5 @@
#ifndef LIBFURC
#include "furc/front/lexer.hpp"
#include "furc/front/parser.hpp"
#include "furc/middle/ir.hpp"
@@ -17,7 +19,11 @@ int main(void) {
furc::lexer lexer = { "<AK>", content };
furc::parser parser = { std::move(lexer), arena };
furc::ir_module irModule = furc::ir_generator::generate(parser.parse());
furc::ssa::process(irModule);
for (auto& func : irModule.functions) {
furc::ssa ssa(*func);
}
return 0;
}
#endif // LIBFURC
+232 -119
View File
@@ -12,168 +12,140 @@
#include <algorithm>
#include <cstddef>
#include <limits>
#include <stack>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc {
namespace {
void ssa::compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks) {
cfgBlocks.resize(irBlocks.size());
struct block_info {
std::size_t order = 0;
std::unordered_set<std::size_t> preds;
std::unordered_set<std::size_t> sucs;
std::size_t idom = 0;
// Dominance Frontiers
std::unordered_set<std::size_t> df;
};
struct register_info {
std::unordered_set<std::size_t> sites; // Definition Sites
};
void rpo_dfs(std::unordered_set<std::size_t>& visited,
std::vector<std::size_t>& order,
std::size_t block,
std::vector<block_info>& blocks) {
visited.insert(block);
for (auto succ : blocks[block].sucs) {
if (visited.find(succ) != visited.end()) continue;
rpo_dfs(visited, order, succ, blocks);
}
order.push_back(block);
}
void compute_rpo(std::vector<block_info>& blocks, std::vector<std::size_t>& order) {
std::unordered_set<std::size_t> visited;
if (!blocks.empty()) rpo_dfs(visited, order, 0, blocks);
std::reverse(order.begin(), order.begin());
for (std::size_t i = 0; i < order.size(); ++i) {
blocks[order[i]].order = i;
}
}
std::size_t intersect(std::vector<block_info>& blocks, std::size_t b1, std::size_t b2) {
std::size_t finger1 = b1;
std::size_t finger2 = b2;
while (finger1 != finger2) {
while (finger1 < finger2)
finger1 = blocks[finger1].idom;
while (finger2 < finger1)
finger2 = blocks[finger2].idom;
}
return finger1;
}
void process_function(ir_function& func) {
std::vector<block_info> blocks(func.blocks.size());
std::vector<register_info> registers(func.regCount);
std::unordered_set<std::uint64_t> nonLocals;
// 1. Compute CFG
for (std::size_t i = 0; i < func.blocks.size(); ++i) {
const auto& block = func.blocks[i];
for (std::size_t i = 0; i < irBlocks.size(); ++i) {
const auto& block = irBlocks[i];
if (block.instructions.empty()) continue;
for (const auto& instr : block.instructions) {
for (const auto& op : instr.sources) {
if (op.type != ir_operand::Register) continue;
const auto& reg = registers[op.value.reg.name];
if (reg.sites.find(i) != reg.sites.end()) continue;
nonLocals.insert(op.value.reg.name);
}
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
registers[instr.destination->value.reg.name].sites.insert(i);
}
const auto& termInstr = block.instructions.back();
switch (termInstr.type) {
case ir_instruction::Branch: {
const auto& dst = termInstr.destination.value();
if (dst.type != ir_operand::Block) throw std::runtime_error("invalid operand");
blocks[dst.value.block].preds.insert(i);
blocks[i].sucs.insert(dst.value.block);
assert(dst.type == ir_operand::Block);
cfgBlocks[dst.value.block].preds.insert(i);
cfgBlocks[i].sucs.insert(dst.value.block);
} break;
case ir_instruction::BranchCond: {
const auto& dst = termInstr.destination.value();
if (dst.type != ir_operand::BlockPair) throw std::runtime_error("invalid operand");
blocks[dst.value.blockPair.first].preds.insert(i);
blocks[dst.value.blockPair.second].preds.insert(i);
blocks[i].preds.insert(dst.value.blockPair.first);
blocks[i].preds.insert(dst.value.blockPair.second);
assert(dst.type == ir_operand::BlockPair);
cfgBlocks[dst.value.blockPair.first].preds.insert(i);
cfgBlocks[dst.value.blockPair.second].preds.insert(i);
cfgBlocks[i].sucs.insert(dst.value.blockPair.first);
cfgBlocks[i].sucs.insert(dst.value.blockPair.second);
} break;
default: break;
}
}
}
// 2. Computing dominance tree
std::vector<std::size_t> order;
order.reserve(blocks.size());
compute_rpo(blocks, order);
void ssa::collect_registers(const std::vector<ir_basic_block>& irBlocks,
std::vector<register_info>& registers,
std::unordered_set<std::uint64_t>& globals) {
for (std::size_t i = 0; i < irBlocks.size(); ++i) {
const auto& block = irBlocks[i];
for (const auto& instr : block.instructions) {
for (const auto& src : instr.sources) {
if (src.type != ir_operand::Register) continue;
const auto& reg = registers.at(src.value.reg.name);
if (reg.sites.find(i) != reg.sites.end()) continue;
globals.insert(src.value.reg.name);
}
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
registers[instr.destination->value.reg.name].sites.insert(i);
}
}
}
blocks[order.front()].idom = order.front();
void ssa::build_dtree(const std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
const std::vector<std::size_t>& order) {
ssaBlocks[order.front()].idom = order.front();
bool changed = true;
while (changed) {
changed = false;
for (auto it = order.begin() + 1; it != order.end(); ++it) {
static constexpr std::uint64_t INVALID = std::numeric_limits<std::uint64_t>::max();
for (std::size_t i = 1; i < order.size(); ++i) {
auto& block = blocks[order[i]];
std::size_t newIdom = -1;
bool found = false;
for (auto pred : block.preds) {
if (blocks[pred].idom == -1) continue;
newIdom = found ? intersect(blocks, pred, newIdom) : pred;
std::uint64_t newIdom = -1;
bool found = false;
for (std::uint64_t pred : cfgBlocks[*it].preds) {
if (ssaBlocks[pred].idom == INVALID) continue;
newIdom = found ? intersect(ssaBlocks, pred, newIdom) : pred;
found = true;
}
if (block.idom != newIdom) {
block.idom = newIdom;
changed = true;
if (ssaBlocks[*it].idom != newIdom) {
ssaBlocks[*it].idom = newIdom;
changed = true;
}
}
}
}
// 3. Computing Dominance Frontiers
for (std::size_t j = 0; j < blocks.size(); ++j) {
const auto& join = blocks[j];
if (join.preds.size() < 2) continue;
for (std::size_t runner : join.preds) {
while (runner != join.idom) {
blocks[runner].df.insert(j);
runner = blocks[runner].idom;
void ssa::compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks) {
for (std::uint64_t i = 0; i < ssaBlocks.size(); ++i) {
if (cfgBlocks[i].preds.size() < 2) continue;
const auto& cfgBlock = cfgBlocks[i];
auto& ssaBlock = ssaBlocks[i];
for (std::uint64_t worker : cfgBlock.preds) {
while (worker != ssaBlock.idom) {
ssaBlocks[worker].df.insert(i);
worker = ssaBlocks[worker].idom;
}
}
}
}
// 4. Inserting Phi-nodes (Semi-Pruned SSA form)
std::vector<std::size_t> worklist;
void ssa::compute_rpo(std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
std::vector<std::size_t>& order) {
std::unordered_set<std::size_t> visited;
if (!cfgBlocks.empty()) rpo_dfs(visited, order, 0, cfgBlocks);
std::reverse(order.begin(), order.end());
ssaBlocks.resize(cfgBlocks.size());
for (std::size_t i = 0; i < order.size(); ++i) {
ssaBlocks[order[i]].order = i;
}
}
for (std::size_t i = 0; i < registers.size(); ++i) {
void ssa::ssaification(std::vector<ir_basic_block>& irBlocks,
const std::vector<cfg_block>& cfgBlocks,
const std::vector<ssa_block>& ssaBlocks,
const std::vector<register_info>& registers,
const std::unordered_set<std::uint64_t>& globals) {
std::vector<std::uint64_t> worklist;
for (std::uint64_t i = 0; i < registers.size(); ++i) {
const auto& reg = registers[i];
if (reg.sites.size() < 2 || nonLocals.find(i) == nonLocals.end()) continue;
if (reg.sites.size() < 2 || globals.find(i) == globals.end()) continue;
worklist.insert(worklist.end(), reg.sites.begin(), reg.sites.end());
std::unordered_set<std::size_t> done;
std::unordered_set<std::uint64_t> done;
while (!worklist.empty()) {
const auto blockIdx = worklist.back();
worklist.pop_back();
for (auto frontier : blocks[blockIdx].df) {
for (auto frontier : ssaBlocks[blockIdx].df) {
if (done.find(frontier) != done.end()) continue;
done.insert(frontier);
auto& target = func.blocks[frontier];
ir_instruction instr = { ir_instruction::Phi };
for (const auto& pred : blocks[frontier].preds)
auto& target = irBlocks[frontier];
ir_instruction instr = { ir_instruction::Phi, ir_operand{ ir_operand::Register, i } };
for (const auto& pred : cfgBlocks[frontier].preds)
instr.sources.emplace_back(ir_operand::PhiPair, i, pred);
target.instructions.emplace(target.instructions.begin(), std::move(instr));
if (reg.sites.find(frontier) == reg.sites.end()) worklist.push_back(frontier);
@@ -182,13 +154,154 @@ void process_function(ir_function& func) {
}
}
} // namespace
void ssa::rename(std::vector<ir_basic_block>& irBlocks,
std::size_t regCount,
const std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
const std::vector<std::uint64_t>& order) {
std::vector<std::uint64_t> counters;
std::vector<std::stack<std::uint64_t>> stacks;
void ssa::process(ir_module& mod) {
for (auto* func : mod.functions)
process_function(*func);
counters.resize(regCount);
stacks.resize(regCount);
for (auto it = order.begin() + 1; it != order.end(); ++it) {
std::uint64_t parent = ssaBlocks[*it].idom;
if (parent != std::numeric_limits<std::uint64_t>::max()) ssaBlocks[parent].children.emplace(*it);
}
rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, order.front());
}
void ssa::destruct(ir_module& mod) {}
void ssa::rename_rec(std::vector<std::uint64_t>& counters,
std::vector<std::stack<std::uint64_t>>& stacks,
std::vector<ir_basic_block>& irBlocks,
const std::vector<cfg_block>& cfgBlocks,
const std::vector<ssa_block>& ssaBlocks,
std::size_t blockIdx) {
std::unordered_map<std::uint64_t, std::size_t> pushed;
auto& block = irBlocks[blockIdx];
for (auto& instr : block.instructions) {
if (instr.type == ir_instruction::Phi) {
auto reg = instr.destination->value.reg.name;
stacks[reg].push(instr.destination->value.reg.ver = counters[reg]++);
++pushed[reg];
continue;
}
for (auto& op : instr.sources) {
if (op.type != ir_operand::Register) continue;
auto reg = op.value.reg.name;
op.value.reg.ver = stacks[reg].top();
}
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
auto reg = instr.destination->value.reg.name;
stacks[reg].push(instr.destination->value.reg.ver = counters[reg]++);
++pushed[reg];
}
for (auto succIdx : cfgBlocks[blockIdx].sucs) {
auto& succ = irBlocks[succIdx];
for (auto& instr : succ.instructions) {
if (instr.type != ir_instruction::Phi) break;
for (auto& op : instr.sources) {
if (op.value.phiPair.block != blockIdx) continue;
op.value.phiPair.reg.ver = stacks[op.value.phiPair.reg.name].top();
}
}
}
for (std::uint64_t child : ssaBlocks[blockIdx].children)
rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, child);
for (auto [reg, count] : pushed)
while ((count--) > 0)
stacks[reg].pop();
}
void ssa::rpo_dfs(std::unordered_set<std::size_t>& visited,
std::vector<std::size_t>& order,
std::size_t block,
const std::vector<cfg_block>& blocks) {
visited.insert(block);
for (auto succ : blocks[block].sucs) {
if (visited.find(succ) != visited.end()) continue;
rpo_dfs(visited, order, succ, blocks);
}
order.push_back(block);
}
std::size_t ssa::intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2) {
while (b1 != b2) {
while (m_blocks[b1].order > m_blocks[b2].order)
b1 = m_blocks[b1].idom;
while (m_blocks[b2].order > m_blocks[b1].order)
b2 = m_blocks[b2].idom;
}
return b1;
}
// // 5. Renaming
// std::vector<std::uint64_t> counters;
// std::vector<std::stack<std::uint64_t>> stacks;
//
// counters.resize(func.regCount);
// stacks.resize(func.regCount);
//
// for (std::size_t i = 1; i < order.size(); ++i) {
// std::size_t parent = blocks[order[i]].idom;
// if (parent != std::numeric_limits<std::size_t>::max()) blocks[parent].children.emplace(order[i]);
// }
//
// auto rename = [&counters, &stacks, &blocks, &func](auto& self, std::size_t blockIdx) -> void {
// std::unordered_map<std::size_t, std::size_t> pushed;
//
// auto& block = func.blocks[blockIdx];
// for (auto& instr : block.instructions) {
// if (instr.type == ir_instruction::Phi) {
// auto reg = instr.destination->value.reg.name;
// auto idx = counters[reg]++;
// instr.destination->value.reg.ver = idx;
// stacks[reg].push(idx);
// ++pushed[reg];
// continue;
// }
//
// for (auto& op : instr.sources) {
// if (op.type != ir_operand::Register) continue;
// auto reg = op.value.reg.name;
// op.value.reg.ver = stacks[reg].top();
// }
//
// if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
// auto reg = instr.destination->value.reg.name;
// auto idx = counters[reg]++;
// instr.destination->value.reg.ver = idx;
// stacks[reg].push(idx);
// ++pushed[reg];
// }
//
// for (auto succIdx : blocks[blockIdx].sucs) {
// auto& succ = func.blocks[succIdx];
// for (auto& instr : succ.instructions) {
// if (instr.type != ir_instruction::Phi) break;
// for (auto& op : instr.sources) {
// if (op.value.phiPair.block != blockIdx) continue;
// op.value.phiPair.reg.ver = stacks[op.value.phiPair.reg.name].top();
// }
// }
// }
//
// for (std::size_t child : blocks[blockIdx].children)
// self(self, child);
//
// for (auto [reg, count] : pushed)
// while (count--)
// stacks[reg].pop();
// };
// rename(rename, order.front());
// }
} // namespace furc
+141
View File
@@ -0,0 +1,141 @@
// NOLINTBEGIN(readability-identifier-naming)
#include "gtest/gtest.h"
#include <furc/middle/ir.hpp>
#include <furc/middle/ssa.hpp>
#include <unordered_set>
#include <vector>
namespace furc::test {
class SsaCfg : public testing::Test {
public:
SsaCfg()
: p_context(&p_function) {}
protected:
void compute() { ssa::compute_cfg(p_function.blocks, p_cfgBlocks); }
protected:
std::vector<ssa::cfg_block> p_cfgBlocks;
ir_function p_function;
ir_context p_context;
};
TEST_F(SsaCfg, Diamond) {
ir_operand null = { ir_operand::Integer, static_cast<std::uint64_t>(0) };
// Entry
p_context.terminate(null, 1, 2);
p_context.new_next();
// A
p_context.terminate(3);
p_context.new_next();
// B
p_context.terminate(3);
p_context.new_next();
// C
p_context.terminate(4);
p_context.new_next();
// Exit
p_context.terminate();
compute();
EXPECT_EQ(p_cfgBlocks[0].sucs.size(), 2);
EXPECT_NE(p_cfgBlocks[0].sucs.find(1), p_cfgBlocks[0].sucs.end());
EXPECT_NE(p_cfgBlocks[0].sucs.find(2), p_cfgBlocks[0].sucs.end());
EXPECT_EQ(p_cfgBlocks[3].preds, p_cfgBlocks[0].sucs);
EXPECT_EQ(p_cfgBlocks[3].sucs.size(), 1);
EXPECT_NE(p_cfgBlocks[3].sucs.find(4), p_cfgBlocks[3].sucs.end());
}
class SsaDom : public testing::Test {
public:
SsaDom()
: p_context(&p_function) {}
protected:
void compute() {
ssa::compute_cfg(p_function.blocks, p_cfgBlocks);
p_ssaRegisters.resize(p_function.regCount);
ssa::collect_registers(p_function.blocks, p_ssaRegisters, p_ssaGlobals);
std::vector<std::uint64_t> order;
ssa::compute_rpo(p_cfgBlocks, p_ssaBlocks, order);
ssa::build_dtree(p_cfgBlocks, p_ssaBlocks, order);
ssa::compute_dfrontiers(p_cfgBlocks, p_ssaBlocks);
ssa::ssaification(p_function.blocks, p_cfgBlocks, p_ssaBlocks, p_ssaRegisters, p_ssaGlobals);
ssa::rename(p_function.blocks, p_function.regCount, p_cfgBlocks, p_ssaBlocks, order);
}
protected:
std::vector<ssa::cfg_block> p_cfgBlocks;
std::vector<ssa::ssa_block> p_ssaBlocks;
std::vector<ssa::register_info> p_ssaRegisters;
std::unordered_set<std::uint64_t> p_ssaGlobals;
ir_function p_function;
ir_context p_context;
};
TEST_F(SsaDom, Diamond) {
ir_operand null = { ir_operand::Integer, static_cast<std::uint64_t>(0) };
// Entry
p_context.terminate(null, 1, 2);
p_context.new_next();
// A
p_context.add_instr(ir_instruction{ ir_instruction::Move,
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
{ null } });
p_context.terminate(3);
p_context.new_next();
// B
p_context.add_instr(ir_instruction{ ir_instruction::Move,
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
{ null } });
p_context.terminate(3);
p_context.new_next();
// C
p_context.add_instr(ir_instruction{ ir_instruction::Add,
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
{ ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) } } });
p_context.new_next();
// Exit
p_context.terminate();
p_function.regCount = 1;
compute();
EXPECT_TRUE(p_ssaBlocks[0].df.empty());
EXPECT_EQ(p_ssaBlocks[1].idom, 0);
EXPECT_EQ(p_ssaBlocks[1].df.size(), 1);
EXPECT_NE(p_ssaBlocks[1].df.find(3), p_ssaBlocks[1].df.end());
EXPECT_EQ(p_ssaBlocks[2].idom, 0);
EXPECT_EQ(p_ssaBlocks[2].df.size(), 1);
EXPECT_NE(p_ssaBlocks[2].df.find(3), p_ssaBlocks[2].df.end());
EXPECT_EQ(p_ssaBlocks[3].idom, 0);
EXPECT_TRUE(p_ssaBlocks[3].df.empty());
EXPECT_EQ(p_ssaBlocks[4].idom, 3);
EXPECT_TRUE(p_ssaBlocks[4].df.empty());
// Renaming
EXPECT_EQ(p_function.blocks[1].instructions.front(),
(ir_instruction{ ir_instruction::Move, ir_operand::reg(0, 3), { null } }));
EXPECT_EQ(p_function.blocks[2].instructions.front(),
(ir_instruction{ ir_instruction::Move, ir_operand::reg(0, 2), { null } }));
EXPECT_EQ(p_function.blocks[3].instructions.front(),
(ir_instruction{ ir_instruction::Phi,
ir_operand::reg(0, 0),
{ ir_operand{ ir_operand::PhiPair, ir_operand::value_u::register_s{ 0, 2 }, 2 },
ir_operand{ ir_operand::PhiPair, ir_operand::value_u::register_s{ 0, 3 }, 1 } } }));
}
} // namespace furc::test
// NOLINTEND(readability-identifier-naming)
+51 -5
View File
@@ -2,7 +2,9 @@
#define FURLANG_RESULT_HPP
#include <exception>
#include <optional>
#include <ostream>
#include <type_traits>
#include <utility>
namespace furlang {
@@ -43,6 +45,8 @@ public:
const char* what() const noexcept override { return "bad result access"; }
};
struct error_tag {};
/**
* @brief Result.
*
@@ -51,7 +55,7 @@ public:
* @tparam R Value type.
* @tparam E Error type.
*/
template <typename R, typename E>
template <typename E, typename R = void>
class result {
public:
using value_type = std::remove_reference_t<R>; /**< Value type. */
@@ -63,6 +67,10 @@ public:
using error_reference = error_type&; /**< Error reference type. */
using error_const_reference = const error_type&; /**< Error const reference type. */
public:
template <typename Other>
result(const result<E, Other>& error)
: result(error_tag{}, error.error()) {}
/**
* @brief Construct a new result.
*
@@ -82,7 +90,7 @@ public:
*
* @param args Variadic arguments to construct the value with.
*/
template <typename... Args>
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_type, Args...>>>
result(Args&&... args) {
new (&m_value.result) value_type(std::forward<Args>(args)...);
}
@@ -92,7 +100,7 @@ public:
*
* @param error Error to copy.
*/
explicit result(const error_type& error)
result(error_tag tag, const error_type& error)
: m_error(true) {
new (&m_value.error) error_type(error);
}
@@ -102,7 +110,7 @@ public:
*
* @param error Error to move.
*/
explicit result(error_type&& error)
result(error_tag tag, error_type&& error)
: m_error(true) {
new (&m_value.error) error_type(std::move(error));
}
@@ -166,6 +174,16 @@ public:
}
return *this;
}
public:
template <typename ResultFwd, typename = std::enable_if_t<std::is_constructible_v<R, ResultFwd>>>
static result ok(ResultFwd&& value) {
return { std::forward<ResultFwd>(value) };
}
template <typename ErrorFwd, typename = std::enable_if_t<std::is_constructible_v<E, ErrorFwd>>>
static result error(ErrorFwd&& value) {
return { error_tag{}, std::forward<ErrorFwd>(value) };
}
public:
/**
* @brief Checks if this result contains a value.
@@ -364,6 +382,34 @@ private:
bool m_error = false;
};
template <typename E>
class result<E, void> {
public:
using value_type = std::remove_reference_t<E>;
using reference = value_type&;
using const_reference = const value_type&;
public:
result() = default;
result(const value_type& value)
: m_error(true), m_value(value) {}
result(value_type&& value)
: m_error(true), m_value(std::move(value)) {}
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_type, Args...>>>
result(Args&&... args)
: m_error(true), m_value(std::forward<Args>(args)...) {}
public:
bool has_value() const { return !m_error; }
bool has_error() const { return m_error; }
const value_type& error() const { return *m_value; }
private:
std::optional<value_type> m_value;
bool m_error = false;
};
} // namespace furlang
#endif // FURLANG_RESULT_HPP
#endif // FURLANG_RESULT_HPP
@@ -0,0 +1,58 @@
#ifndef FURLANG_SERIALIZATION_CODEC_HPP
#define FURLANG_SERIALIZATION_CODEC_HPP
#include "furlang/result.hpp"
#include "furlang/serialization/io.hpp"
#include <type_traits>
#include <utility>
namespace furlang {
namespace serialization {
template <typename Codec, typename T>
using codec_encode_result_t = decltype(std::declval<Codec>().encode(std::declval<writer&>(), std::declval<const T&>()));
template <typename Codec, typename T>
using codec_decode_result_t = decltype(std::declval<Codec>().decode(std::declval<reader&>()));
template <typename Codec, typename T, typename = void>
struct is_codec : std::false_type {};
template <typename Codec, typename T>
struct is_codec<Codec, T, std::void_t<codec_encode_result_t<Codec, T>, codec_decode_result_t<Codec, T>>>
: std::true_type {};
template <typename Codec, typename T>
constexpr bool is_codec_v = is_codec<Codec, T>::value;
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
result<error> encode(Codec& codec, writer& writer, const T& value) {
return codec.encode(writer, value);
}
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
result<error, T> decode(Codec& codec, reader& reader) {
return codec.decode(reader);
}
template <typename T, typename = void>
class codec;
template <typename T>
struct codec<T, std::enable_if_t<std::is_integral_v<T>>> {
result<error> encode(writer& writer, const T& value) { return writer.write_int(value); }
result<error, T> decode(reader& reader) { return reader.read_int(T{}); }
};
template <>
struct codec<std::string> {
result<error> encode(writer& writer, const std::string& value) { return writer.write_string(value); }
result<error, std::string> decode(reader& reader) { return reader.read_string(); }
};
} // namespace serialization
} // namespace furlang
#endif // FURLANG_SERIALIZATION_CODEC_HPP
@@ -0,0 +1,33 @@
#ifndef FURLANG_SERIALIZATION_ERROR_HPP
#define FURLANG_SERIALIZATION_ERROR_HPP
#include <cstddef>
#include <string>
namespace furlang {
namespace serialization {
enum class error_code {
EndOfFile,
InvalidData,
InvalidTag,
InvalidVersion,
IntegerOverflow,
SizeLimit,
DuplicateId,
UnknownId,
TypeMismatch,
Unsupported,
};
struct error {
error_code code;
std::string message;
std::size_t offset = 0;
};
} // namespace serialization
} // namespace furlang
#endif // FURLANG_SERIALIZATION_ERROR_HPP
@@ -0,0 +1,190 @@
#ifndef FURLANG_SERIALIZATION_IO_HPP
#define FURLANG_SERIALIZATION_IO_HPP
#include "furlang/result.hpp"
#include "furlang/serialization/error.hpp"
#include <cstdint>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
namespace furlang {
namespace serialization {
class writer {
public:
writer() = default;
virtual ~writer() = default;
writer(writer&&) noexcept = default;
writer& operator=(writer&&) noexcept = default;
writer(const writer&) = default;
writer& operator=(const writer&) = default;
public:
virtual result<error> write_s8(std::int8_t value) = 0;
virtual result<error> write_u8(std::uint8_t value) = 0;
virtual result<error> write_s16(std::int16_t value) = 0;
virtual result<error> write_u16(std::uint16_t value) = 0;
virtual result<error> write_s32(std::int32_t value) = 0;
virtual result<error> write_u32(std::uint32_t value) = 0;
virtual result<error> write_s64(std::int64_t value) = 0;
virtual result<error> write_u64(std::uint64_t value) = 0;
result<error> write_int(std::int8_t value) { return write_s8(value); }
result<error> write_int(std::uint8_t value) { return write_u8(value); }
result<error> write_int(std::int16_t value) { return write_s16(value); }
result<error> write_int(std::uint16_t value) { return write_u16(value); }
result<error> write_int(std::int32_t value) { return write_s32(value); }
result<error> write_int(std::uint32_t value) { return write_u32(value); }
result<error> write_int(std::int64_t value) { return write_s64(value); }
result<error> write_int(std::uint64_t value) { return write_u64(value); }
virtual result<error> write_string(const char* string) = 0;
virtual result<error> write_string(std::string_view string) = 0;
virtual result<error> write_string(const std::string& string) = 0;
};
class reader {
public:
reader() = default;
virtual ~reader() = default;
reader(reader&&) noexcept = default;
reader& operator=(reader&&) noexcept = default;
reader(const reader&) = default;
reader& operator=(const reader&) = default;
public:
virtual result<error, std::int8_t> read_s8() = 0;
virtual result<error, std::uint8_t> read_u8() = 0;
virtual result<error, std::int16_t> read_s16() = 0;
virtual result<error, std::uint16_t> read_u16() = 0;
virtual result<error, std::int32_t> read_s32() = 0;
virtual result<error, std::uint32_t> read_u32() = 0;
virtual result<error, std::int64_t> read_s64() = 0;
virtual result<error, std::uint64_t> read_u64() = 0;
result<error, std::int8_t> read_int(std::int8_t) { return read_s8(); }
result<error, std::uint8_t> read_int(std::uint8_t) { return read_u8(); }
result<error, std::int16_t> read_int(std::int16_t) { return read_s16(); }
result<error, std::uint16_t> read_int(std::uint16_t) { return read_u16(); }
result<error, std::int32_t> read_int(std::int32_t) { return read_s32(); }
result<error, std::uint32_t> read_int(std::uint32_t) { return read_u32(); }
result<error, std::int64_t> read_int(std::int64_t) { return read_s64(); }
result<error, std::uint64_t> read_int(std::uint64_t) { return read_u64(); }
virtual result<error, std::string> read_string() = 0;
virtual std::size_t offset() const = 0;
};
enum class endianness {
Little = 0,
Big = 1,
};
class byte_writer : public writer {
public:
byte_writer(endianness endianness = endianness::Big)
: m_endianness(endianness) {}
public:
result<error> write_s8(std::int8_t value) override;
result<error> write_u8(std::uint8_t value) override;
result<error> write_s16(std::int16_t value) override;
result<error> write_u16(std::uint16_t value) override;
result<error> write_s32(std::int32_t value) override;
result<error> write_u32(std::uint32_t value) override;
result<error> write_s64(std::int64_t value) override;
result<error> write_u64(std::uint64_t value) override;
result<error> write_string(const char* string) override;
result<error> write_string(std::string_view string) override;
result<error> write_string(const std::string& string) override;
private:
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
result<error> write_integral_le(T value) {
return write_integral_le(value, std::make_index_sequence<sizeof(T)>{});
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
result<error> write_integral_le(T value, std::index_sequence<I...>) {
auto usig = static_cast<std::make_unsigned_t<T>>(value);
(m_bytes.push_back(usig >> (I * 8)), ...);
return {};
}
private:
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void write_integral_be(T value) {
write_integral_be(value, std::make_index_sequence<sizeof(T)>{});
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
void write_integral_be(T value, std::index_sequence<I...>) {
auto usig = static_cast<std::make_unsigned_t<T>>(value);
(m_bytes.push_back(usig >> ((sizeof(T) - 1 - I) * 8)), ...);
}
private:
endianness m_endianness;
std::vector<std::uint8_t> m_bytes;
};
class byte_reader : public reader {
public:
byte_reader(const std::uint8_t* bytes, std::size_t length, endianness endianness = endianness::Big)
: m_endianness(endianness), m_bytes(bytes), m_length(length) {}
public:
result<error, std::int8_t> read_s8() override;
result<error, std::uint8_t> read_u8() override;
result<error, std::int16_t> read_s16() override;
result<error, std::uint16_t> read_u16() override;
result<error, std::int32_t> read_s32() override;
result<error, std::uint32_t> read_u32() override;
result<error, std::int64_t> read_s64() override;
result<error, std::uint64_t> read_u64() override;
result<error, std::string> read_string() override;
std::size_t offset() const override;
private:
result<error, std::uint8_t> read_byte() {
if (m_offset >= m_length)
return result<error, std::uint8_t>::error(error{ error_code::EndOfFile, "", m_offset });
return { m_bytes[m_offset++] };
}
private:
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
result<error, T> read_integral_le() {
using U = std::make_unsigned_t<T>;
U usig = 0;
for (std::size_t i = 0; i < sizeof(T); ++i) {
auto res = read_byte();
if (res.has_error()) return res;
usig |= static_cast<U>(res.value()) << (i * 8);
}
return { static_cast<T>(usig) };
}
private:
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
result<error, T> read_integral_be() {
using U = std::make_unsigned_t<T>;
U usig = 0;
for (std::size_t i = 0; i < sizeof(T); ++i) {
auto res = read_byte();
if (res.has_error()) return res;
usig |= static_cast<U>(res.value()) << ((sizeof(T) - 1 - i) * 8);
}
return { static_cast<T>(usig) };
}
private:
endianness m_endianness;
const std::uint8_t* m_bytes;
std::size_t m_length;
std::size_t m_offset = 0;
};
} // namespace serialization
} // namespace furlang
#endif // FURLANG_SERIALIZATION_IO_HPP
+137
View File
@@ -0,0 +1,137 @@
#include "furlang/serialization/io.hpp"
#include <cstring>
namespace furlang::serialization {
result<error> byte_writer::write_s8(std::int8_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_u8(std::uint8_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_s16(std::int16_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_u16(std::uint16_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_s32(std::int32_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_u32(std::uint32_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_s64(std::int64_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_u64(std::uint64_t value) {
if (m_endianness == endianness::Little)
write_integral_le(value);
else
write_integral_be(value);
return {};
}
result<error> byte_writer::write_string(const char* string) {
write_u16(std::strlen(string));
m_bytes.insert(m_bytes.end(), string, string + std::strlen(string));
return {};
}
result<error> byte_writer::write_string(std::string_view string) {
write_u16(string.length());
m_bytes.insert(m_bytes.end(), string.begin(), string.end());
return {};
}
result<error> byte_writer::write_string(const std::string& string) {
write_u16(string.length());
m_bytes.insert(m_bytes.end(), string.begin(), string.end());
return {};
}
result<error, std::int8_t> byte_reader::read_s8() {
return m_endianness == endianness::Little ? read_integral_le<std::int8_t>() : read_integral_be<std::int8_t>();
}
result<error, std::uint8_t> byte_reader::read_u8() {
return m_endianness == endianness::Little ? read_integral_le<std::uint8_t>() : read_integral_be<std::uint8_t>();
}
result<error, std::int16_t> byte_reader::read_s16() {
return m_endianness == endianness::Little ? read_integral_le<std::int16_t>() : read_integral_be<std::int16_t>();
}
result<error, std::uint16_t> byte_reader::read_u16() {
return m_endianness == endianness::Little ? read_integral_le<std::uint16_t>() : read_integral_be<std::uint16_t>();
}
result<error, std::int32_t> byte_reader::read_s32() {
return m_endianness == endianness::Little ? read_integral_le<std::int32_t>() : read_integral_be<std::int32_t>();
}
result<error, std::uint32_t> byte_reader::read_u32() {
return m_endianness == endianness::Little ? read_integral_le<std::uint32_t>() : read_integral_be<std::uint32_t>();
}
result<error, std::int64_t> byte_reader::read_s64() {
return m_endianness == endianness::Little ? read_integral_le<std::int64_t>() : read_integral_be<std::int64_t>();
}
result<error, std::uint64_t> byte_reader::read_u64() {
return m_endianness == endianness::Little ? read_integral_le<std::uint64_t>() : read_integral_be<std::uint64_t>();
}
result<error, std::string> byte_reader::read_string() {
std::string str;
auto length = read_u16();
if (length.has_error()) return length;
str.resize(length.value());
if (m_offset + str.length() >= m_length)
return result<error, std::string>::error(error{ error_code::EndOfFile, "", m_length });
str.assign(reinterpret_cast<const char*>(m_bytes) + m_offset, str.length());
m_offset += str.length();
return str;
}
std::size_t byte_reader::offset() const {
return m_offset;
}
} // namespace furlang::serialization
+1 -1
View File
@@ -13,4 +13,4 @@ include(GoogleTest)
file(GLOB_RECURSE FURVM_TESTS "test/**.cpp")
add_executable(furvm_tests ${FURVM_TESTS})
target_link_libraries(furvm_tests PRIVATE libfurvm GTest::gtest_main)
gtest_discover_tests(furvm_tests)
gtest_discover_tests(furvm_tests)