first try of registry allocation
This commit is contained in:
61
include/ir/allocator.hpp
Normal file
61
include/ir/allocator.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "prelude/string.hpp"
|
||||
#include "ir/slot.hpp"
|
||||
|
||||
#define AVAILABLE_REGISTERS 4
|
||||
#define AVAILABLE_STACK_SIZE 4096
|
||||
|
||||
template<typename Slot, typename SlotAddr = const StringView&>
|
||||
class Allocator
|
||||
{
|
||||
public:
|
||||
virtual ~Allocator() {};
|
||||
public:
|
||||
virtual const Slot& Allocate(SlotAddr addr) = 0;
|
||||
virtual const Slot& Resolve(SlotAddr addr) = 0;
|
||||
};
|
||||
|
||||
class SlotAllocator : public Allocator<IR::IRSlot>
|
||||
{
|
||||
public:
|
||||
SlotAllocator() = default;
|
||||
~SlotAllocator() = default;
|
||||
public:
|
||||
const IR::IRSlot& Allocate(const StringView& addr) override
|
||||
{
|
||||
if (m_regs < AVAILABLE_REGISTERS)
|
||||
{
|
||||
m_slots.Push(IR::IRSlot(addr, m_regs++, IR::IRSlot::Type::REGISTRY));
|
||||
return m_slots.data[m_slots.size - 1];
|
||||
}
|
||||
if (m_offset_counter + 8 <= AVAILABLE_STACK_SIZE)
|
||||
{
|
||||
m_offset_counter += 8;
|
||||
m_slots.Push(IR::IRSlot(addr, m_offset_counter, IR::IRSlot::Type::STACK));
|
||||
return m_slots.data[m_slots.size - 1];
|
||||
}
|
||||
// TODO: proper error handling (stack overflow etc.)
|
||||
assert(0 && "failed to allocate local");
|
||||
}
|
||||
|
||||
const IR::IRSlot& Resolve(const StringView& addr) override
|
||||
{
|
||||
for (size_t i = 0; i < m_slots.size; ++i)
|
||||
{
|
||||
if (m_slots.data[i].GetAddr() == addr)
|
||||
{
|
||||
return m_slots.data[i];
|
||||
}
|
||||
}
|
||||
|
||||
assert(0 && "could not resolve stack offset for specified address");
|
||||
}
|
||||
public:
|
||||
View<IR::IRSlot> slots() const { return m_slots.view(); }
|
||||
public:
|
||||
unsigned int GetStackSize() const { return m_offset_counter; }
|
||||
public:
|
||||
int m_regs = 0;
|
||||
unsigned int m_offset_counter = 0;
|
||||
Builder<IR::IRSlot> m_slots;
|
||||
};
|
||||
359
include/ir/ir.hpp
Normal file
359
include/ir/ir.hpp
Normal file
@@ -0,0 +1,359 @@
|
||||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include "prelude/string.hpp"
|
||||
#include "ir/slot.hpp"
|
||||
#include "parser/ast.hpp"
|
||||
#include "ir/allocator.hpp"
|
||||
|
||||
namespace IR
|
||||
{
|
||||
|
||||
enum class OpType
|
||||
{
|
||||
EXTERN = 0,
|
||||
FN,
|
||||
LOAD_CONST,
|
||||
LOAD,
|
||||
STORE,
|
||||
ADD,
|
||||
CALL,
|
||||
COUNT_OPS,
|
||||
};
|
||||
|
||||
#define OP_TYPE(x) \
|
||||
OpType GetType() const override { return OpType::x; }
|
||||
|
||||
using Tmp = int;
|
||||
|
||||
using TmpView = View<Tmp>;
|
||||
using TmpBuilder = Builder<Tmp>;
|
||||
|
||||
class Op
|
||||
{
|
||||
public:
|
||||
virtual OpType GetType() const = 0;
|
||||
virtual ~Op() {}
|
||||
|
||||
virtual StringView Format(int indent) const = 0;
|
||||
};
|
||||
|
||||
using OpView = View<Op*>;
|
||||
using OpBuilder = Builder<Op*>;
|
||||
|
||||
class Valued
|
||||
{
|
||||
public:
|
||||
Valued(Tmp dest)
|
||||
: m_dest(dest) {}
|
||||
~Valued() = default;
|
||||
public:
|
||||
Tmp result() const { return m_dest; }
|
||||
private:
|
||||
Tmp m_dest;
|
||||
};
|
||||
|
||||
class ExternOp : public Op
|
||||
{
|
||||
public:
|
||||
ExternOp(StringView symbol)
|
||||
: m_symbol(symbol) {}
|
||||
~ExternOp() {}
|
||||
|
||||
OP_TYPE(EXTERN)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << "EXTRN " << m_symbol.c_str();
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
const StringView& symbol() const { return m_symbol; }
|
||||
private:
|
||||
StringView m_symbol;
|
||||
};
|
||||
|
||||
class FnOp : public Op
|
||||
{
|
||||
public:
|
||||
FnOp(StringView name, const CompoundNode* body, const View<StringView>& params);
|
||||
~FnOp() {}
|
||||
|
||||
OP_TYPE(FN)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << "LABEL " << m_name.c_str() << ':' << '\n';
|
||||
for (size_t i = 0; i < m_ops.size; ++i)
|
||||
{
|
||||
sb << m_ops.data[i]->Format(indent + 2) << '\n';
|
||||
}
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
const StringView& name() const { return m_name; }
|
||||
const OpView& ops() const { return m_ops; }
|
||||
const View<StringView>& params() const { return m_params; }
|
||||
private:
|
||||
StringView m_name;
|
||||
OpView m_ops;
|
||||
View<Tmp> m_slots;
|
||||
View<StringView> m_params;
|
||||
};
|
||||
|
||||
class LoadConstOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
LoadConstOp(Tmp dest, long value)
|
||||
: Valued(dest), m_value(value) {}
|
||||
~LoadConstOp() {}
|
||||
|
||||
OP_TYPE(LOAD_CONST)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << 't' << result() << " = LOAD_CONST " << m_value;
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
long value() const { return m_value; }
|
||||
private:
|
||||
long m_value;
|
||||
};
|
||||
|
||||
class LoadOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
LoadOp(Tmp dest, StringView addr)
|
||||
: Valued(dest), m_addr(addr) {}
|
||||
~LoadOp() {}
|
||||
|
||||
OP_TYPE(LOAD)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << 't' << result() << " = LOAD \"" << m_addr.c_str() << "\"";
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
const StringView& addr() const { return m_addr; }
|
||||
private:
|
||||
StringView m_addr;
|
||||
};
|
||||
|
||||
class StoreOp : public Op
|
||||
{
|
||||
public:
|
||||
StoreOp(StringView addr, Tmp src)
|
||||
: m_addr(addr), m_src(src) {}
|
||||
~StoreOp() {}
|
||||
|
||||
OP_TYPE(STORE)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << "STORE \"" << m_addr.c_str() << "\", t" << m_src;
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
const StringView& addr() const { return m_addr; }
|
||||
Tmp src() const { return m_src; }
|
||||
private:
|
||||
StringView m_addr;
|
||||
Tmp m_src;
|
||||
};
|
||||
|
||||
class AddOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
AddOp(Tmp dest, Tmp lhs, Tmp rhs)
|
||||
: Valued(dest), m_lhs(lhs), m_rhs(rhs) {}
|
||||
~AddOp() {}
|
||||
|
||||
OP_TYPE(ADD)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << 't' << result() << " = ADD " << m_lhs << ", " << m_rhs;
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
Tmp lhs() const { return m_lhs; }
|
||||
Tmp rhs() const { return m_rhs; }
|
||||
private:
|
||||
Tmp m_lhs;
|
||||
Tmp m_rhs;
|
||||
};
|
||||
|
||||
class CallOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
CallOp(Tmp dest, StringView callee, TmpView args)
|
||||
: Valued(dest), m_callee(callee), m_args(args) {}
|
||||
~CallOp() {}
|
||||
|
||||
OP_TYPE(CALL)
|
||||
public:
|
||||
StringView Format(int indent) const override
|
||||
{
|
||||
StringBuilder sb;
|
||||
for (size_t i = 0; i < m_args.size; ++i)
|
||||
{
|
||||
sb.AppendIndent(indent);
|
||||
sb << "PARAM t" << m_args.data[i] << '\n';
|
||||
}
|
||||
sb.AppendIndent(indent);
|
||||
sb << 't' << result() << " = CALL " << m_callee.c_str();
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
const StringView& callee() const { return m_callee; }
|
||||
const TmpView& args() const { return m_args; }
|
||||
private:
|
||||
StringView m_callee;
|
||||
TmpView m_args;
|
||||
};
|
||||
|
||||
class IRBuilder
|
||||
{
|
||||
public:
|
||||
IRBuilder(const Node* root)
|
||||
: m_root(root) {}
|
||||
public:
|
||||
// TODO: support other literals
|
||||
Tmp ParseIntLiteral(const IntLiteralNode* literal)
|
||||
{
|
||||
auto dst = AllocateRegister();
|
||||
m_ops.Push(new LoadConstOp(dst, literal->integer()));
|
||||
return dst;
|
||||
}
|
||||
|
||||
Tmp ParseVariable(const VariableNode* var)
|
||||
{
|
||||
// auto dst = AllocateRegister();
|
||||
// m_ops.Push(new LoadOp(dst, var->name()));
|
||||
if (m_locals.find(var->name()) == m_locals.end())
|
||||
{
|
||||
// TODO: throw proper error
|
||||
assert(0 && "ERROR: variable does not exist");
|
||||
}
|
||||
return m_locals[var->name()];
|
||||
}
|
||||
|
||||
Tmp ParseFnCall(const FnCallNode* fn)
|
||||
{
|
||||
// TODO: support multiple args
|
||||
auto argRegs = TmpBuilder();
|
||||
if (fn->arg() != nullptr)
|
||||
{
|
||||
auto arg = ParseExpression(fn->arg());
|
||||
argRegs.Push(arg);
|
||||
}
|
||||
auto dst = AllocateRegister();
|
||||
m_ops.Push(new CallOp(dst, fn->name(), argRegs.view()));
|
||||
return dst;
|
||||
}
|
||||
|
||||
Tmp ParseFactor(const Node* factor)
|
||||
{
|
||||
switch(factor->GetType())
|
||||
{
|
||||
case NodeType::IntLiteral: return ParseIntLiteral(reinterpret_cast<const IntLiteralNode*>(factor));
|
||||
case NodeType::Variable: return ParseVariable(reinterpret_cast<const VariableNode*>(factor));
|
||||
case NodeType::FnCall: return ParseFnCall(reinterpret_cast<const FnCallNode*>(factor));
|
||||
default: assert(0 && "some factor may not be handled"); break;
|
||||
}
|
||||
|
||||
assert(0 && "unreachable");
|
||||
return Tmp();
|
||||
}
|
||||
|
||||
Tmp ParseExpression(const Node* expression)
|
||||
{
|
||||
if (expression->GetType() == NodeType::Expression)
|
||||
{
|
||||
auto expr = reinterpret_cast<const ExpressionNode*>(expression);
|
||||
auto lhs = ParseExpression(expr->left());
|
||||
auto rhs = ParseExpression(expr->right());
|
||||
auto dst = AllocateRegister();
|
||||
|
||||
assert(4 == static_cast<int>(ExpressionNode::Operator::COUNT_OPERATORS) && "some operators may not be handled");
|
||||
switch (expr->op())
|
||||
{
|
||||
case ExpressionNode::Operator::Plus: m_ops.Push(new AddOp(dst, lhs, rhs)); break;
|
||||
default: assert(0 && "TODO: implement other operations"); break;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
return ParseFactor(expression);
|
||||
}
|
||||
|
||||
void ParseVarDecl(const VarDeclNode* varDecl)
|
||||
{
|
||||
auto value = ParseExpression(varDecl->value());
|
||||
// m_ops.Push(new StoreOp(varDecl->name(), value));
|
||||
m_locals.insert(std::make_pair(varDecl->name(), value));
|
||||
}
|
||||
|
||||
void ParseBlock(const CompoundNode* compound)
|
||||
{
|
||||
for (auto &statement : *compound)
|
||||
{
|
||||
switch(statement->GetType())
|
||||
{
|
||||
case NodeType::VarDecl: ParseVarDecl(reinterpret_cast<VarDeclNode*>(statement)); continue;
|
||||
default: ParseExpression(statement); continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OpView Build()
|
||||
{
|
||||
assert(m_root->GetType() == NodeType::Program && "root should be a program");
|
||||
auto program = reinterpret_cast<const ProgramNode*>(m_root);
|
||||
|
||||
// Externs
|
||||
for (auto &extrn : program->externs())
|
||||
{
|
||||
m_ops.Push(new ExternOp(extrn->symbol()));
|
||||
}
|
||||
|
||||
// Functions
|
||||
for (auto &fn : program->funcs())
|
||||
{
|
||||
m_ops.Push(new FnOp(fn->name(), fn->body(), fn->params()));
|
||||
}
|
||||
|
||||
return OpView(m_ops.data, m_ops.size);
|
||||
}
|
||||
public:
|
||||
// TODO: think about safety (copying m_ops.data before giving)
|
||||
OpView ops() const { return OpView(m_ops.data, m_ops.size); }
|
||||
private:
|
||||
Tmp AllocateRegister()
|
||||
{
|
||||
return m_tmp_counter++;
|
||||
}
|
||||
private:
|
||||
OpBuilder m_ops;
|
||||
const Node* m_root = nullptr;
|
||||
|
||||
unsigned int m_tmp_counter = 0;
|
||||
|
||||
std::unordered_map<StringView, Tmp> m_locals;
|
||||
};
|
||||
|
||||
} // namespace IR
|
||||
48
include/ir/slot.hpp
Normal file
48
include/ir/slot.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include "prelude/string.hpp"
|
||||
|
||||
namespace IR
|
||||
{
|
||||
|
||||
struct IRSlot
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
STACK,
|
||||
REGISTRY,
|
||||
};
|
||||
public:
|
||||
IRSlot(StringView addr, unsigned int slot, Type slotType) : m_addr(addr), m_slot(slot), m_slotType(slotType) {}
|
||||
IRSlot() = default;
|
||||
public:
|
||||
Type GetType() const { return m_slotType; }
|
||||
const StringView& GetAddr() const { return m_addr; }
|
||||
unsigned int GetSlot() const { return m_slot; }
|
||||
public:
|
||||
StringView Format() const {
|
||||
StringBuilder sb;
|
||||
switch(GetType())
|
||||
{
|
||||
case Type::REGISTRY:
|
||||
sb << "r" << GetSlot();
|
||||
break;
|
||||
case Type::STACK:
|
||||
sb << "s[" << GetSlot() << "]";
|
||||
break;
|
||||
default:
|
||||
sb << "(UNKNOWN_SLOT_TYPE)";
|
||||
break;
|
||||
}
|
||||
return sb.view();
|
||||
}
|
||||
private:
|
||||
StringView m_addr;
|
||||
unsigned int m_slot;
|
||||
Type m_slotType;
|
||||
};
|
||||
|
||||
using IRSlotBuilder = Builder<IRSlot>;
|
||||
using IRSlotView = View<IRSlot>;
|
||||
|
||||
} // namespace IR
|
||||
Reference in New Issue
Block a user