first try of registry allocation
This commit is contained in:
@@ -14,5 +14,5 @@ fn hello() {
|
||||
|
||||
fn main() {
|
||||
hello()
|
||||
putchar(10)
|
||||
putchar(9 + 1)
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
|
||||
// TODO: store all of values, allocated registers, stack offsets in single allocator
|
||||
// to be able to find out which kind a specific temp register represent
|
||||
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include "string.hpp"
|
||||
#include "ir.hpp"
|
||||
|
||||
template<typename Slot>
|
||||
class Allocator
|
||||
{
|
||||
public:
|
||||
virtual ~Allocator() {};
|
||||
public:
|
||||
virtual const Slot& Allocate(const StringView& addr) = 0;
|
||||
virtual const Slot& Resolve(const StringView& addr) = 0;
|
||||
};
|
||||
|
||||
struct StackSlot
|
||||
{
|
||||
size_t offset;
|
||||
StringView addr;
|
||||
};
|
||||
|
||||
class StackAllocator : public Allocator<StackSlot>
|
||||
{
|
||||
public:
|
||||
StackAllocator() = default;
|
||||
~StackAllocator() override = default;
|
||||
public:
|
||||
const StackSlot& Allocate(const StringView& addr)
|
||||
{
|
||||
m_offset_counter += 4;
|
||||
m_slots.Push(StackSlot { m_offset_counter, addr });
|
||||
return m_slots.data[m_slots.size - 1];
|
||||
}
|
||||
|
||||
const StackSlot& Resolve(const StringView& addr)
|
||||
{
|
||||
for (size_t i = 0; i < m_slots.size; ++i)
|
||||
{
|
||||
if (strcmp(m_slots.data[i].addr.c_str(), addr.c_str()) == 0)
|
||||
{
|
||||
return m_slots.data[i];
|
||||
}
|
||||
}
|
||||
|
||||
assert(0 && "could not resolve stack offset for specified address");
|
||||
}
|
||||
private:
|
||||
size_t m_offset_counter = 0;
|
||||
Builder<StackSlot> m_slots;
|
||||
};
|
||||
|
||||
// struct ConstSlot
|
||||
// {
|
||||
// long value;
|
||||
// StringView addr;
|
||||
// };
|
||||
|
||||
// class ConstAllocator : public Allocator<ConstSlot>
|
||||
// {
|
||||
// public:
|
||||
// ConstAllocator() = default;
|
||||
// ~ConstAllocator() override = default;
|
||||
// public:
|
||||
// const ConstSlot& Allocate(const StringView& addr)
|
||||
// {
|
||||
// m_slots.Push(ConstSlot { 0, addr });
|
||||
// return m_slots.data[m_slots.size - 1];
|
||||
// }
|
||||
|
||||
// const ConstSlot& StoreValue(const StringView& addr, long value)
|
||||
// {
|
||||
// for (size_t i = 0; i < m_slots.size; ++i)
|
||||
// {
|
||||
// if (strcmp(m_slots.data[i].addr.c_str(), addr.c_str()) == 0)
|
||||
// {
|
||||
// m_slots.data[i].value = value;
|
||||
// return m_slots.data[i];
|
||||
// }
|
||||
// }
|
||||
|
||||
// assert(0 && "could not resolve const under specified address");
|
||||
// }
|
||||
|
||||
// const ConstSlot& Resolve(const StringView& addr)
|
||||
// {
|
||||
// for (size_t i = 0; i < m_slots.size; ++i)
|
||||
// {
|
||||
// if (strcmp(m_slots.data[i].addr.c_str(), addr.c_str()) == 0)
|
||||
// {
|
||||
// return m_slots.data[i];
|
||||
// }
|
||||
// }
|
||||
|
||||
// assert(0 && "could not resolve const under specified address");
|
||||
// }
|
||||
// private:
|
||||
// Builder<ConstSlot> m_slots;
|
||||
// };
|
||||
|
||||
// struct RegisterSlot
|
||||
// {
|
||||
// const StringView& reg;
|
||||
// StringView addr;
|
||||
// };
|
||||
|
||||
// class RegisterAllocator : public Allocator<RegisterSlot>
|
||||
// {
|
||||
// public:
|
||||
// RegisterAllocator()
|
||||
// {
|
||||
// m_regs.Push(std::move(StringView("eax")));
|
||||
// m_regs.Push(std::move(StringView("ecx")));
|
||||
// }
|
||||
// ~RegisterAllocator() override = default;
|
||||
// public:
|
||||
// const RegisterSlot& Allocate(const StringView& addr)
|
||||
// {
|
||||
// assert(m_slots.size < m_regs.size && "no space available for allocating to register");
|
||||
// m_slots.Push(RegisterSlot { m_regs.data[m_slots.size], addr });
|
||||
// return m_slots.data[m_slots.size - 1];
|
||||
// }
|
||||
|
||||
// const RegisterSlot& Resolve(const StringView& addr)
|
||||
// {
|
||||
// for (size_t i = 0; i < m_slots.size; ++i)
|
||||
// {
|
||||
// if (strcmp(m_slots.data[i].addr.c_str(), addr.c_str()) == 0)
|
||||
// {
|
||||
// return m_slots.data[i];
|
||||
// }
|
||||
// }
|
||||
|
||||
// assert(0 && "could not resolve register for specified address");
|
||||
// }
|
||||
|
||||
// void Clear()
|
||||
// {
|
||||
// m_slots.size = 0;
|
||||
// }
|
||||
// private:
|
||||
// Builder<RegisterSlot> m_slots;
|
||||
// Builder<StringView> m_regs;
|
||||
// };
|
||||
|
||||
class CodeGenerator
|
||||
{
|
||||
public:
|
||||
virtual ~CodeGenerator() {};
|
||||
|
||||
virtual void Generate(const char* filename, View<IR::Op*> ops) = 0;
|
||||
};
|
||||
17
include/codegen/codegen.hpp
Normal file
17
include/codegen/codegen.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
// TODO: store all of values, allocated registers, stack offsets in single allocator
|
||||
// to be able to find out which kind a specific temp register represent
|
||||
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdarg>
|
||||
#include "prelude/string.hpp"
|
||||
#include "ir/ir.hpp"
|
||||
|
||||
class CodeGenerator
|
||||
{
|
||||
public:
|
||||
virtual ~CodeGenerator() {};
|
||||
|
||||
virtual void Generate(const char* filename, View<IR::Op*> ops) = 0;
|
||||
};
|
||||
170
include/codegen/fasm_stack.hpp
Normal file
170
include/codegen/fasm_stack.hpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include "codegen/codegen.hpp"
|
||||
#include "ir/slot.hpp"
|
||||
|
||||
class StackFasmX86_64Generator : public CodeGenerator
|
||||
{
|
||||
public:
|
||||
~StackFasmX86_64Generator() override = default;
|
||||
private:
|
||||
StringView GetTempAddr(IR::Tmp reg)
|
||||
{
|
||||
return std::move((StringBuilder() << 't' << reg).view());
|
||||
}
|
||||
|
||||
StringView GetSlotAddr(const IR::IRSlot& slot)
|
||||
{
|
||||
switch (slot.GetType())
|
||||
{
|
||||
case IR::IRSlot::Type::REGISTRY:
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendFormat("r%d", slot.GetSlot() + 10); // for r10, r11, r12 etc.
|
||||
return sb.view();
|
||||
} break;
|
||||
case IR::IRSlot::Type::STACK:
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendFormat("[rbp-%d]", slot.GetSlot()); // for r10, r11, r12 etc.
|
||||
return sb.view();
|
||||
} break;
|
||||
default:
|
||||
assert(0 && "TODO: either unreachable or handle properly");
|
||||
}
|
||||
}
|
||||
private:
|
||||
void GenerateOp(const IR::Op *op)
|
||||
{
|
||||
switch (op->GetType())
|
||||
{
|
||||
case IR::OpType::EXTERN:
|
||||
{
|
||||
auto extrn = reinterpret_cast<const IR::ExternOp *>(op);
|
||||
auto symbol = extrn->symbol();
|
||||
appendf("extrn '%s' as __%s\n", symbol.c_str(), symbol.c_str());
|
||||
appendf("%s = PLT __%s\n", symbol.c_str(), symbol.c_str());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::FN:
|
||||
{
|
||||
m_allocator = new SlotAllocator();
|
||||
|
||||
auto fn = reinterpret_cast<const IR::FnOp *>(op);
|
||||
auto name = fn->name();
|
||||
appendf("public %s\n", name.c_str());
|
||||
appendf("%s:\n", name.c_str());
|
||||
appendf("push rbp\n");
|
||||
appendf("mov rbp, rsp\n");
|
||||
|
||||
StringBuilder fnOutput;
|
||||
StringBuilder* backup = m_output;
|
||||
m_output = &fnOutput;
|
||||
|
||||
if (fn->params().size > 0)
|
||||
{
|
||||
// TODO: support multiple parameters
|
||||
auto param_slot = m_allocator->Allocate(fn->params().data[0]);
|
||||
appendf("mov %s, rdi\n", GetSlotAddr(param_slot).c_str());
|
||||
}
|
||||
for(auto &fOp : fn->ops())
|
||||
{
|
||||
GenerateOp(fOp);
|
||||
}
|
||||
|
||||
int stackSize = m_allocator->GetStackSize();
|
||||
m_output = backup;
|
||||
appendf("sub rsp, %d\n", stackSize);
|
||||
*m_output << fnOutput.c_str();
|
||||
|
||||
appendf("leave\nret\n");
|
||||
|
||||
m_allocator = nullptr;
|
||||
}
|
||||
break;
|
||||
case IR::OpType::CALL:
|
||||
{
|
||||
auto call = reinterpret_cast<const IR::CallOp *>(op);
|
||||
// TODO: support several arguments
|
||||
if (call->args().size == 1)
|
||||
{
|
||||
auto slot = m_allocator->Resolve(GetTempAddr(call->args().data[0]));
|
||||
appendf("mov rdi, %s\n", GetSlotAddr(slot).c_str());
|
||||
}
|
||||
appendf("call %s\n", call->callee().c_str());
|
||||
auto result_slot = m_allocator->Allocate(GetTempAddr(call->result()));
|
||||
appendf("mov %s, rax\n", GetSlotAddr(result_slot).c_str());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::LOAD_CONST:
|
||||
{
|
||||
auto lc = reinterpret_cast<const IR::LoadConstOp *>(op);
|
||||
auto addr = GetTempAddr(lc->result());
|
||||
auto slot = m_allocator->Allocate(addr);
|
||||
appendf("mov %s, %ld\n", GetSlotAddr(slot).c_str(), lc->value());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::STORE:
|
||||
{
|
||||
auto s = reinterpret_cast<const IR::StoreOp *>(op);
|
||||
appendf("; DEBUG: resolving stack slot at %s\n", s->addr().c_str());
|
||||
auto slot = m_allocator->Allocate(s->addr());
|
||||
auto value_slot = m_allocator->Resolve(GetTempAddr(s->src()));
|
||||
appendf("mov rax, %s\n", GetSlotAddr(value_slot).c_str());
|
||||
appendf("mov %s, rax\n", GetSlotAddr(slot).c_str());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::LOAD:
|
||||
{
|
||||
auto l = reinterpret_cast<const IR::LoadOp *>(op);
|
||||
auto value_slot = m_allocator->Allocate(GetTempAddr(l->result()));
|
||||
auto variable_slot = m_allocator->Resolve(l->addr());
|
||||
appendf("mov rax, %s\n", GetSlotAddr(variable_slot).c_str());
|
||||
appendf("mov %s, rax\n", GetSlotAddr(value_slot).c_str());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::ADD:
|
||||
{
|
||||
auto expr = reinterpret_cast<const IR::AddOp *>(op);
|
||||
auto lhs_slot = m_allocator->Resolve(GetTempAddr(expr->lhs()));
|
||||
appendf("mov rax, %s\n", GetSlotAddr(lhs_slot).c_str());
|
||||
auto rhs_slot = m_allocator->Resolve(GetTempAddr(expr->rhs()));
|
||||
appendf("add rax, %s\n", GetSlotAddr(rhs_slot).c_str());
|
||||
auto result_slot = m_allocator->Allocate(GetTempAddr(expr->result()));
|
||||
appendf("mov %s, rax\n", GetSlotAddr(result_slot).c_str());
|
||||
}
|
||||
break;
|
||||
default: appendf("; NOT HANDLED\n; %s\n", op->Format(0).c_str()); break;
|
||||
}
|
||||
}
|
||||
|
||||
void appendf(const char * fmt, ...)
|
||||
{
|
||||
assert(m_output != nullptr && "nowhere to write");
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
m_output->VAppendFormat(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
public:
|
||||
StringView GetOutput() { return m_output->view(); }
|
||||
private:
|
||||
StringBuilder* m_output = nullptr;
|
||||
public:
|
||||
void Generate(const char* filename, View<IR::Op*> ops) override
|
||||
{
|
||||
m_output = new StringBuilder();
|
||||
appendf("; fasm x86_64 linux generated assembly using pl\n");
|
||||
appendf("format ELF64\n");
|
||||
appendf("section '.text' executable\n");
|
||||
|
||||
for (auto& op : ops)
|
||||
{
|
||||
GenerateOp(op);
|
||||
}
|
||||
}
|
||||
public:
|
||||
// StackAllocator* m_stack = nullptr;
|
||||
// TODO: handle sub-blocks
|
||||
SlotAllocator* m_allocator = nullptr;
|
||||
};
|
||||
@@ -1,140 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "codegen.hpp"
|
||||
|
||||
class StackFasmX86_64Generator : public CodeGenerator
|
||||
{
|
||||
public:
|
||||
~StackFasmX86_64Generator() override = default;
|
||||
private:
|
||||
int GetStackSize(const IR::OpView ops)
|
||||
{
|
||||
int stackSize = 0;
|
||||
for (auto &op : ops)
|
||||
{
|
||||
switch (op->GetType())
|
||||
{
|
||||
case IR::OpType::STORE:
|
||||
case IR::OpType::LOAD:
|
||||
case IR::OpType::LOAD_CONST:
|
||||
case IR::OpType::ADD:
|
||||
case IR::OpType::CALL:
|
||||
stackSize += 4;
|
||||
}
|
||||
}
|
||||
return stackSize;
|
||||
}
|
||||
|
||||
StringView GetTempAddr(IR::Reg reg)
|
||||
{
|
||||
return std::move((StringBuilder() << 't' << reg).view());
|
||||
}
|
||||
private:
|
||||
void GenerateOp(const IR::Op *op)
|
||||
{
|
||||
switch (op->GetType())
|
||||
{
|
||||
case IR::OpType::EXTERN:
|
||||
{
|
||||
auto extrn = reinterpret_cast<const IR::ExternOp *>(op);
|
||||
auto symbol = extrn->symbol();
|
||||
printf("extrn '%s' as __%s\n", symbol.c_str(), symbol.c_str());
|
||||
printf("%s = PLT __%s\n", symbol.c_str(), symbol.c_str());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::FN:
|
||||
{
|
||||
auto fn = reinterpret_cast<const IR::FnOp *>(op);
|
||||
auto name = fn->name();
|
||||
printf("public %s\n", name.c_str());
|
||||
printf("%s:\n", name.c_str());
|
||||
printf("push rbp\n");
|
||||
printf("mov rbp, rsp\n");
|
||||
m_stack = new StackAllocator();
|
||||
int stackSize = GetStackSize(fn->ops());
|
||||
printf("sub rsp, %d\n", stackSize);
|
||||
if (fn->params().size > 0)
|
||||
{
|
||||
auto param_slot = m_stack->Allocate(fn->params().data[0]);
|
||||
printf("mov [rbp-%d], edi\n", param_slot.offset);
|
||||
}
|
||||
for(auto &fOp : fn->ops())
|
||||
{
|
||||
GenerateOp(fOp);
|
||||
}
|
||||
operator delete(m_stack);
|
||||
m_stack = nullptr;
|
||||
printf("leave\nret\n");
|
||||
}
|
||||
break;
|
||||
case IR::OpType::CALL:
|
||||
{
|
||||
auto call = reinterpret_cast<const IR::CallOp *>(op);
|
||||
// TODO: support several arguments
|
||||
if (call->args().size == 1)
|
||||
{
|
||||
auto slot = m_stack->Resolve(GetTempAddr(call->args().data[0]));
|
||||
printf("mov edi, [rbp-%d]\n", slot.offset);
|
||||
}
|
||||
printf("call %s\n", call->callee().c_str());
|
||||
auto result_slot = m_stack->Allocate(GetTempAddr(call->result()));
|
||||
printf("mov dword [rbp-%d], eax\n", result_slot.offset);
|
||||
}
|
||||
break;
|
||||
case IR::OpType::LOAD_CONST:
|
||||
{
|
||||
auto lc = reinterpret_cast<const IR::LoadConstOp *>(op);
|
||||
auto addr = GetTempAddr(lc->result());
|
||||
auto slot = m_stack->Allocate(addr);
|
||||
printf("mov dword [rbp-%d], %ld\n", slot.offset, lc->value());
|
||||
}
|
||||
break;
|
||||
case IR::OpType::STORE:
|
||||
{
|
||||
auto s = reinterpret_cast<const IR::StoreOp *>(op);
|
||||
printf("; DEBUG: resolving stack slot at %s\n", s->addr().c_str());
|
||||
auto slot = m_stack->Allocate(s->addr());
|
||||
auto value_slot = m_stack->Resolve(GetTempAddr(s->src()));
|
||||
printf("mov eax, [rbp-%d]\n", value_slot.offset);
|
||||
printf("mov dword [rbp-%d], eax\n", slot.offset);
|
||||
}
|
||||
break;
|
||||
case IR::OpType::LOAD:
|
||||
{
|
||||
auto l = reinterpret_cast<const IR::LoadOp *>(op);
|
||||
auto value_slot = m_stack->Allocate(GetTempAddr(l->result()));
|
||||
auto variable_slot = m_stack->Resolve(l->addr());
|
||||
printf("mov eax, [rbp-%d]\n", variable_slot.offset);
|
||||
printf("mov dword [rbp-%d], eax\n", value_slot.offset);
|
||||
}
|
||||
break;
|
||||
case IR::OpType::ADD:
|
||||
{
|
||||
auto expr = reinterpret_cast<const IR::AddOp *>(op);
|
||||
auto lhs_slot = m_stack->Resolve(GetTempAddr(expr->lhs()));
|
||||
printf("mov eax, [rbp-%d]\n", lhs_slot.offset);
|
||||
auto rhs_slot = m_stack->Resolve(GetTempAddr(expr->rhs()));
|
||||
printf("add eax, [rbp-%d]\n", rhs_slot.offset);
|
||||
auto result_slot = m_stack->Allocate(GetTempAddr(expr->result()));
|
||||
printf("mov dword [rbp-%d], eax\n", result_slot.offset);
|
||||
}
|
||||
break;
|
||||
default: printf("; NOT HANDLED\n; %s\n", op->Format(0).c_str()); break;
|
||||
}
|
||||
}
|
||||
public:
|
||||
void Generate(const char* filename, View<IR::Op*> ops) override
|
||||
{
|
||||
printf("; fasm x86_64 linux generated assembly using pl\n");
|
||||
printf("format ELF64\n");
|
||||
printf("section '.text' executable\n");
|
||||
|
||||
for (auto& op : ops)
|
||||
{
|
||||
GenerateOp(op);
|
||||
}
|
||||
}
|
||||
public:
|
||||
// TODO: handle sub-blocks
|
||||
StackAllocator* m_stack = nullptr;
|
||||
};
|
||||
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;
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include "string.hpp"
|
||||
#include "ast.hpp"
|
||||
#include <unordered_map>
|
||||
#include "prelude/string.hpp"
|
||||
#include "ir/slot.hpp"
|
||||
#include "parser/ast.hpp"
|
||||
#include "ir/allocator.hpp"
|
||||
|
||||
namespace IR
|
||||
{
|
||||
@@ -20,10 +23,10 @@ enum class OpType
|
||||
#define OP_TYPE(x) \
|
||||
OpType GetType() const override { return OpType::x; }
|
||||
|
||||
using Reg = int;
|
||||
using Tmp = int;
|
||||
|
||||
using RegBuilder = Builder<Reg>;
|
||||
using RegView = View<Reg>;
|
||||
using TmpView = View<Tmp>;
|
||||
using TmpBuilder = Builder<Tmp>;
|
||||
|
||||
class Op
|
||||
{
|
||||
@@ -40,13 +43,13 @@ using OpBuilder = Builder<Op*>;
|
||||
class Valued
|
||||
{
|
||||
public:
|
||||
Valued(Reg dest)
|
||||
Valued(Tmp dest)
|
||||
: m_dest(dest) {}
|
||||
~Valued() = default;
|
||||
public:
|
||||
Reg result() const { return m_dest; }
|
||||
Tmp result() const { return m_dest; }
|
||||
private:
|
||||
Reg m_dest;
|
||||
Tmp m_dest;
|
||||
};
|
||||
|
||||
class ExternOp : public Op
|
||||
@@ -97,13 +100,14 @@ public:
|
||||
private:
|
||||
StringView m_name;
|
||||
OpView m_ops;
|
||||
View<Tmp> m_slots;
|
||||
View<StringView> m_params;
|
||||
};
|
||||
|
||||
class LoadConstOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
LoadConstOp(Reg dest, long value)
|
||||
LoadConstOp(Tmp dest, long value)
|
||||
: Valued(dest), m_value(value) {}
|
||||
~LoadConstOp() {}
|
||||
|
||||
@@ -125,7 +129,7 @@ private:
|
||||
class LoadOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
LoadOp(Reg dest, StringView addr)
|
||||
LoadOp(Tmp dest, StringView addr)
|
||||
: Valued(dest), m_addr(addr) {}
|
||||
~LoadOp() {}
|
||||
|
||||
@@ -147,7 +151,7 @@ private:
|
||||
class StoreOp : public Op
|
||||
{
|
||||
public:
|
||||
StoreOp(StringView addr, Reg src)
|
||||
StoreOp(StringView addr, Tmp src)
|
||||
: m_addr(addr), m_src(src) {}
|
||||
~StoreOp() {}
|
||||
|
||||
@@ -162,16 +166,16 @@ public:
|
||||
}
|
||||
public:
|
||||
const StringView& addr() const { return m_addr; }
|
||||
Reg src() const { return m_src; }
|
||||
Tmp src() const { return m_src; }
|
||||
private:
|
||||
StringView m_addr;
|
||||
Reg m_src;
|
||||
Tmp m_src;
|
||||
};
|
||||
|
||||
class AddOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
AddOp(Reg dest, Reg lhs, Reg rhs)
|
||||
AddOp(Tmp dest, Tmp lhs, Tmp rhs)
|
||||
: Valued(dest), m_lhs(lhs), m_rhs(rhs) {}
|
||||
~AddOp() {}
|
||||
|
||||
@@ -181,21 +185,21 @@ public:
|
||||
{
|
||||
StringBuilder sb;
|
||||
sb.AppendIndent(indent);
|
||||
sb << 't' << result() << " = ADD t" << m_lhs << ", t" << m_rhs;
|
||||
sb << 't' << result() << " = ADD " << m_lhs << ", " << m_rhs;
|
||||
return sb.view();
|
||||
}
|
||||
public:
|
||||
Reg lhs() const { return m_lhs; }
|
||||
Reg rhs() const { return m_rhs; }
|
||||
Tmp lhs() const { return m_lhs; }
|
||||
Tmp rhs() const { return m_rhs; }
|
||||
private:
|
||||
Reg m_lhs;
|
||||
Reg m_rhs;
|
||||
Tmp m_lhs;
|
||||
Tmp m_rhs;
|
||||
};
|
||||
|
||||
class CallOp : public Op, public Valued
|
||||
{
|
||||
public:
|
||||
CallOp(Reg dest, StringView callee, RegView args)
|
||||
CallOp(Tmp dest, StringView callee, TmpView args)
|
||||
: Valued(dest), m_callee(callee), m_args(args) {}
|
||||
~CallOp() {}
|
||||
|
||||
@@ -215,10 +219,10 @@ public:
|
||||
}
|
||||
public:
|
||||
const StringView& callee() const { return m_callee; }
|
||||
const RegView& args() const { return m_args; }
|
||||
const TmpView& args() const { return m_args; }
|
||||
private:
|
||||
StringView m_callee;
|
||||
RegView m_args;
|
||||
TmpView m_args;
|
||||
};
|
||||
|
||||
class IRBuilder
|
||||
@@ -228,24 +232,29 @@ public:
|
||||
: m_root(root) {}
|
||||
public:
|
||||
// TODO: support other literals
|
||||
Reg ParseIntLiteral(const IntLiteralNode* literal)
|
||||
Tmp ParseIntLiteral(const IntLiteralNode* literal)
|
||||
{
|
||||
auto dst = AllocateRegister();
|
||||
m_ops.Push(new LoadConstOp(dst, literal->integer()));
|
||||
return dst;
|
||||
}
|
||||
|
||||
Reg ParseVariable(const VariableNode* var)
|
||||
Tmp ParseVariable(const VariableNode* var)
|
||||
{
|
||||
auto dst = AllocateRegister();
|
||||
m_ops.Push(new LoadOp(dst, var->name()));
|
||||
return dst;
|
||||
// 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()];
|
||||
}
|
||||
|
||||
Reg ParseFnCall(const FnCallNode* fn)
|
||||
Tmp ParseFnCall(const FnCallNode* fn)
|
||||
{
|
||||
// TODO: support multiple args
|
||||
auto argRegs = RegBuilder();
|
||||
auto argRegs = TmpBuilder();
|
||||
if (fn->arg() != nullptr)
|
||||
{
|
||||
auto arg = ParseExpression(fn->arg());
|
||||
@@ -256,7 +265,7 @@ public:
|
||||
return dst;
|
||||
}
|
||||
|
||||
Reg ParseFactor(const Node* factor)
|
||||
Tmp ParseFactor(const Node* factor)
|
||||
{
|
||||
switch(factor->GetType())
|
||||
{
|
||||
@@ -267,10 +276,10 @@ public:
|
||||
}
|
||||
|
||||
assert(0 && "unreachable");
|
||||
return -1;
|
||||
return Tmp();
|
||||
}
|
||||
|
||||
Reg ParseExpression(const Node* expression)
|
||||
Tmp ParseExpression(const Node* expression)
|
||||
{
|
||||
if (expression->GetType() == NodeType::Expression)
|
||||
{
|
||||
@@ -295,7 +304,8 @@ public:
|
||||
void ParseVarDecl(const VarDeclNode* varDecl)
|
||||
{
|
||||
auto value = ParseExpression(varDecl->value());
|
||||
m_ops.Push(new StoreOp(varDecl->name(), value));
|
||||
// m_ops.Push(new StoreOp(varDecl->name(), value));
|
||||
m_locals.insert(std::make_pair(varDecl->name(), value));
|
||||
}
|
||||
|
||||
void ParseBlock(const CompoundNode* compound)
|
||||
@@ -333,15 +343,17 @@ public:
|
||||
// TODO: think about safety (copying m_ops.data before giving)
|
||||
OpView ops() const { return OpView(m_ops.data, m_ops.size); }
|
||||
private:
|
||||
Reg AllocateRegister()
|
||||
Tmp AllocateRegister()
|
||||
{
|
||||
return m_reg_counter++;
|
||||
return m_tmp_counter++;
|
||||
}
|
||||
private:
|
||||
OpBuilder m_ops;
|
||||
const Node* m_root = nullptr;
|
||||
|
||||
Reg m_reg_counter = 0;
|
||||
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
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "lexer.hpp"
|
||||
#include "parser/lexer.hpp"
|
||||
|
||||
enum class NodeType
|
||||
{
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
|
||||
#include "string.hpp"
|
||||
#include "prelude/string.hpp"
|
||||
|
||||
enum class TokenType
|
||||
{
|
||||
@@ -110,13 +110,13 @@ public:
|
||||
c = m_code.data[m_pos++];
|
||||
}
|
||||
|
||||
if (std::isalpha(c) != 0)
|
||||
if (std::isalpha(c) != 0 || c == '_')
|
||||
{
|
||||
StringBuilder s;
|
||||
long offset_start = m_pos - m_last_newline;
|
||||
s.Push(c);
|
||||
// id
|
||||
while (std::isalpha(m_code.data[m_pos]) != 0)
|
||||
while (std::isalpha(m_code.data[m_pos]) != 0 || m_code.data[m_pos] == '_')
|
||||
{
|
||||
s.Push(m_code.data[m_pos++]);
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
|
||||
template<typename T>
|
||||
struct View
|
||||
@@ -105,6 +107,48 @@ public:
|
||||
const char* c_str() const { return data; }
|
||||
};
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<StringView>
|
||||
{
|
||||
size_t operator()(const StringView& sv) const noexcept
|
||||
{
|
||||
// FNV-1a hash (simple, fast, good enough)
|
||||
constexpr size_t fnv_offset = 14695981039346656037ull;
|
||||
constexpr size_t fnv_prime = 1099511628211ull;
|
||||
|
||||
size_t hash = fnv_offset;
|
||||
|
||||
// Exclude the null terminator if present
|
||||
size_t len = (sv.size > 0 && sv.data[sv.size - 1] == '\0')
|
||||
? sv.size - 1
|
||||
: sv.size;
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
hash ^= static_cast<unsigned char>(sv.data[i]);
|
||||
hash *= fnv_prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline bool operator==(const StringView& a, const StringView& b)
|
||||
{
|
||||
// Sizes include null terminator; logical string length is size-1
|
||||
if (a.size != b.size) return false;
|
||||
if (a.size == 0) return true;
|
||||
return std::memcmp(a.data, b.data, a.size) == 0;
|
||||
}
|
||||
|
||||
inline bool operator!=(const StringView& a, const StringView& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct Builder
|
||||
{
|
||||
@@ -217,6 +261,52 @@ public:
|
||||
size += indent;
|
||||
}
|
||||
|
||||
void VAppendFormat(const char* fmt, va_list args)
|
||||
{
|
||||
// Make a copy because vsnprintf consumes the va_list
|
||||
va_list argsCopy;
|
||||
va_copy(argsCopy, args);
|
||||
|
||||
int len = std::vsnprintf(nullptr, 0, fmt, argsCopy);
|
||||
va_end(argsCopy);
|
||||
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
this->grow(this->size + len + 1);
|
||||
|
||||
std::vsnprintf(this->data + this->size, len + 1, fmt, args);
|
||||
this->size += len;
|
||||
}
|
||||
|
||||
|
||||
void AppendFormat(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
// 1) compute length (excluding terminator)
|
||||
va_list argsCopy;
|
||||
va_copy(argsCopy, args);
|
||||
int len = std::vsnprintf(nullptr, 0, fmt, argsCopy);
|
||||
va_end(argsCopy);
|
||||
|
||||
if (len <= 0)
|
||||
{
|
||||
va_end(args);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) reserve space
|
||||
this->grow(this->size + len + 1);
|
||||
|
||||
// 3) write formatted string
|
||||
std::vsnprintf(this->data + this->size, len + 1, fmt, args);
|
||||
this->size += len;
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Return a null-terminated string pointer owned by builder
|
||||
const char* c_str()
|
||||
{
|
||||
@@ -272,4 +362,12 @@ public:
|
||||
Extend(buf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringBuilder& operator<<(unsigned int v)
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%d", v);
|
||||
Extend(buf);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "ir.hpp"
|
||||
#include "ir/ir.hpp"
|
||||
|
||||
IR::FnOp::FnOp(StringView name, const CompoundNode* body, const View<StringView>& params)
|
||||
: m_name(name), m_params(params)
|
||||
{
|
||||
IRBuilder ir(body); // Now IRBuilder is complete → OK
|
||||
ir.ParseBlock(body);
|
||||
m_ops = ir.ops();
|
||||
m_ops = std::move(ir.ops());
|
||||
}
|
||||
24
src/main.cpp
24
src/main.cpp
@@ -1,11 +1,11 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <print>
|
||||
#include "lexer.hpp"
|
||||
#include "ast.hpp"
|
||||
#include "ir.hpp"
|
||||
#include "parser/lexer.hpp"
|
||||
#include "parser/ast.hpp"
|
||||
#include "ir/ir.hpp"
|
||||
|
||||
#include "codegens/fasm_stack.hpp"
|
||||
#include "codegen/fasm_stack.hpp"
|
||||
|
||||
void dump_tokens(const char* filename, Lexer* lexer)
|
||||
{
|
||||
@@ -50,16 +50,16 @@ int main(int argc, char** argv)
|
||||
|
||||
auto ops = irBuilder.Build();
|
||||
|
||||
// printf("; ------ IR ------\n");
|
||||
// for (size_t i = 0; i < ops.size; ++i)
|
||||
// {
|
||||
// printf("; %s\n", ops.data[i]->Format(0).c_str());
|
||||
// }
|
||||
// printf("; ------ IR ------\n");
|
||||
for (size_t i = 0; i < ops.size; ++i)
|
||||
{
|
||||
printf("%s\n", ops.data[i]->Format(0).c_str());
|
||||
}
|
||||
|
||||
StackFasmX86_64Generator gen;
|
||||
// StackFasmX86_64Generator gen;
|
||||
|
||||
gen.Generate(filename, ops);
|
||||
// gen.Generate(filename, ops);
|
||||
|
||||
// printf("%s\n", gen.GetOutput().c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user