Compare commits

...

11 Commits

22 changed files with 776 additions and 495 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
build/ build/
.cache/

View File

@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR)
project(pl VERSION 0.0.1 LANGUAGES C CXX) project(pl VERSION 0.0.1 LANGUAGES C CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(SOURCES set(SOURCES
src/main.cpp src/main.cpp

32
TODO.md
View File

@@ -1,6 +1,33 @@
0. Define how many physical regs are available, which are caller-saved and callee-saved # Milestones
1. Implement if-else and while statements
## Clean codebase structure
0. Prelude classes like FileIO, strings null-termination etc.
1. Diagnostic helper class for logging
2. Remove "void" valuehandle class and use it as ValueHandle::Type instead
3. Function multiple arguments support
4. Implement if-else and while statements
## Types support in language
## Stack based code generation
0. Stack based codegen, i.e. for fasm x86_64
## IR Optimizations aka const folding, deadcode elimination
## Register allocation
## First beta assembly code generation with registers
## ...
-------------------------------------------------
# References for IR optimizations
2. Find out what use[B] and def[B] are for each block 2. Find out what use[B] and def[B] are for each block
3. Define successors and predecessors of each block and make them accessible 3. Define successors and predecessors of each block and make them accessible
4. Compute liveIn[B] and liveOut[B] for each block 4. Compute liveIn[B] and liveOut[B] for each block
@@ -19,6 +46,7 @@
- Spill to stack slots - Spill to stack slots
- Add “CALL clobbers regs” rule - Add “CALL clobbers regs” rule
# References for register allocation
function's reserved registers: function's reserved registers:
EAX, ECX, EDX EAX, ECX, EDX

View File

@@ -1,4 +1,5 @@
extern putchar extern putchar
extern iadd
fn hello() { fn hello() {
local h = 72 local h = 72
@@ -14,5 +15,6 @@ fn hello() {
fn main() { fn main() {
hello() hello()
putchar(3 * 3 + 1) local newline = iadd(3*3, 1)
putchar(newline)
} }

View File

@@ -1,17 +1,21 @@
// 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 #pragma once
#include <cstddef> #include "ir/op.hpp"
#include <cstdarg> #include "prelude/linkedlist.hpp"
#include "prelude/string.hpp" #include "prelude/string.hpp"
#include "ir/ir.hpp"
class CodeGenerator class CodeGenerator
{ {
public: public:
virtual ~CodeGenerator() {}; CodeGenerator() = default;
virtual ~CodeGenerator() {}
virtual void Generate(const char* filename, View<IR::Op*> ops) = 0; public:
virtual bool Generate(const DoubleLinkedList<IR::Op*>* ops) = 0;
StringView GetOutput() { return output().view(); }
protected:
StringBuilder& output() { return m_output; }
private:
StringBuilder m_output;
}; };

View File

@@ -1,180 +0,0 @@
#pragma once
#include "codegen/codegen.hpp"
#include "ir/slot.hpp"
#include "ir/value.hpp"
class StackFasmX86_64Generator : public CodeGenerator
{
public:
~StackFasmX86_64Generator() override = default;
private:
StringView GetTempAddr(IR::ValueHandle *reg)
{
return std::move((StringBuilder() << reg->Format()).view());
}
StringView GetSlotAddr(const IR::IRSlot &slot)
{
switch (slot.GetType())
{
case IR::IRSlot::Type::REGISTRY:
{
StringBuilder sb;
sb.AppendFormat("r%d", slot.GetSlot() + 12); // 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->body().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);
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;
};

View File

@@ -0,0 +1,247 @@
#pragma once
#include "codegen/codegen.hpp"
#include "ir/op.hpp"
#include "ir/ops.hpp"
#include "ir/value.hpp"
#include "prelude/linkedlist.hpp"
#include "prelude/string.hpp"
#include <unordered_map>
class FasmX86_64Generator : public CodeGenerator
{
public:
FasmX86_64Generator() = default;
public:
bool Generate(const DoubleLinkedList<IR::Op*>* ops) override
{
m_ops = ops;
output().Extend("format ELF64\n");
output().Extend("section '.text' executable\n");
for (m_current = m_ops->Begin(); m_current != nullptr; node_next())
{
GenerateStatement();
}
return true;
}
private:
void GenerateExtern()
{
auto extrn = current<IR::ExternOp>();
// TODO: instead of __<symbol, use some random UID or hash string
// for safety and collision considerations
output().AppendFormat("extrn '%s' as __%s\n", extrn->symbol().c_str(), extrn->symbol().c_str());
output().AppendFormat("%s = PLT __%s\n", extrn->symbol().c_str(), extrn->symbol().c_str());
}
void GenerateFunction()
{
auto fn = current<IR::FnOp>();
m_slots.clear();
m_stackCounter = 0;
output().AppendFormat("public %s\n", fn->name().c_str());
output().AppendFormat("%s:\n", fn->name().c_str());
output().Extend(" push rbp\n");
output().Extend(" mov rbp, rsp\n");
auto fnNode = node<IR::Op>();
auto ops = m_ops;
m_ops = &fn->body().ops();
for (m_current = m_ops->Begin(); m_current != nullptr; node_next())
{
GenerateStatement();
}
m_ops = ops;
m_current = fnNode;
output().Extend(" leave\n");
output().Extend(" ret\n");
}
void GenerateCall()
{
auto call = current<IR::CallOp>();
// TODO: support stack spilled arguments
assert(call->args().size < 7 && "stack arguments not supported yet");
const char *regs[6] = {"edi", "esi", "edx", "ecx","e8", "e9"};
for (size_t i = 0; i < call->args().size; ++i)
{
auto arg = call->args().data[i];
if (arg->HasId()) {
auto sp = EnsureSlot(arg);
// TODO:
auto size = "dword";
output().AppendFormat(" mov %s, %s [rbp-%d]\n", regs[i], size, sp);
} else {
output().AppendFormat(" mov %s, %d\n", regs[i], reinterpret_cast<IR::ConstantInt*>(arg)->GetValue());
}
}
output().AppendFormat(" call %s\n", call->callee().c_str());
auto sp = EnsureSlot(call->result());
auto size = "dword";
output().AppendFormat(" mov %s [rbp-%d], eax\n", size, sp);
}
void GenerateAllocate()
{
auto totalAllocSize = 0;
while (current<IR::Op>()->GetType() == IR::OpType::ALLOCATE)
{
auto alloc = current<IR::AllocateOp>();
// TODO: support other types
assert(alloc->Type()->kind == IR::ValueHandle::Type::Kind::Int);
// TODO: dynamic size + alignment
auto allocSize = 4;
totalAllocSize += allocSize;
m_stackCounter += allocSize;
m_slots.insert(std::make_pair(alloc->result()->GetId(), m_stackCounter));
if (seek<IR::Op>() && seek<IR::Op>()->get()->GetType() == IR::OpType::ALLOCATE) node_next();
else break;
};
output().AppendFormat(" sub rsp, %d\n", totalAllocSize);
}
void GenerateStore()
{
auto store = current<IR::StoreOp>();
auto sp = EnsureSlot(store->dst());
// TODO: support other types
assert(store->src()->GetType()->kind == IR::ValueHandle::Type::Kind::Int);
if (!store->src()->HasId()) {
auto value = reinterpret_cast<const IR::ConstantInt*>(store->src());
auto size = "dword";
output().AppendFormat(" mov %s [rbp-%d], %d\n", size, sp, value->GetValue());
} else {
auto ssp = EnsureSlot(store->src());
auto size = "dword";
output().AppendFormat(" mov eax, %s [rbp-%d]\n", size, ssp);
output().AppendFormat(" mov %s [rbp-%d], eax\n", size, sp);
}
}
void GenerateLoad()
{
auto load = current<IR::LoadOp>();
auto sp = EnsureSlot(load->Ptr());
// TODO: support other types
auto size = "dword";
output().AppendFormat(" mov eax, %s [rbp-%d]\n", size, sp);
sp = EnsureSlot(load->result());
output().AppendFormat(" mov dword [rbp-%d], eax\n", sp);
}
void GenerateMath()
{
auto math = current<IR::MathOp>();
StringBuilder sb;
switch(math->GetType())
{
case IR::OpType::ADD:
sb << "add";
break;
case IR::OpType::SUB:
sb << "sub";
break;
case IR::OpType::MUL:
sb << "imul";
break;
case IR::OpType::DIV:
sb << "div";
break;
default: assert(false && "unreachable or not implemented");
}
auto op = sb.view();
// TODO:
auto size = "dword";
if (!math->lhs()->HasId()) {
output().AppendFormat(" mov eax, %d\n", reinterpret_cast<IR::ConstantInt*>(math->lhs())->GetValue());
} else {
auto lsp = EnsureSlot(math->lhs());
output().AppendFormat(" mov eax, %s [rbp-%d]\n", size, lsp);
}
if (!math->rhs()->HasId()) {
output().AppendFormat(" %s eax, %d\n", op.c_str(), reinterpret_cast<IR::ConstantInt*>(math->rhs())->GetValue());
} else {
auto lsp = EnsureSlot(math->rhs());
output().AppendFormat(" %s eax, %s [rbp-%d]\n", op.c_str(), size, lsp);
}
auto sp = EnsureSlot(math->result());
output().AppendFormat(" mov %s [rbp-%d], eax\n", size, sp);
}
void GenerateStatement()
{
auto op = current<IR::Op>();
switch(op->GetType())
{
case IR::OpType::EXTERN:
return GenerateExtern();
case IR::OpType::FN:
return GenerateFunction();
case IR::OpType::CALL:
return GenerateCall();
case IR::OpType::ALLOCATE:
return GenerateAllocate();
case IR::OpType::STORE:
return GenerateStore();
case IR::OpType::LOAD:
return GenerateLoad();
case IR::OpType::ADD:
case IR::OpType::SUB:
case IR::OpType::MUL:
case IR::OpType::DIV:
return GenerateMath();
// TODO:
default: output().AppendFormat(" ; %d not implemented\n", op->GetType());
}
}
private:
uint32_t EnsureSlot(const IR::ValueHandle* value)
{
assert(value->HasId());
auto stackPointer = m_slots.find(value->GetId());
if (stackPointer != m_slots.end())
{
return stackPointer->second;
}
// TODO: dynamic size based on type
auto allocSize = 4;
output().AppendFormat(" sub rsp, %d\n", allocSize);
m_stackCounter += allocSize;
m_slots.insert(std::make_pair(value->GetId(), m_stackCounter));
return m_stackCounter;
}
private:
template<typename T>
ListNode<T*>* node() { return reinterpret_cast<ListNode<T*>*>(m_current); }
template<typename T>
ListNode<T*>* seek() { return m_current && m_current->next ? reinterpret_cast<ListNode<T*>*>(m_current->next) : nullptr; }
void node_next() { assert(m_current); m_current = m_current->next; }
template<typename T>
T* current() const { return reinterpret_cast<T*>(m_current->value); }
private:
const DoubleLinkedList<IR::Op*>* m_ops;
ListNode<IR::Op*>* m_current;
std::unordered_map<uint32_t, uint32_t> m_slots;
uint32_t m_stackCounter = 0;
};

View File

@@ -1,61 +0,0 @@
#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;
};

View File

@@ -1,35 +1,34 @@
#pragma once #pragma once
#include "ir/op.hpp" #include "ir/op.hpp"
#include "prelude/linkedlist.hpp"
namespace IR namespace IR
{ {
using OpView = View<Op*>;
using BlockID = unsigned int; using BlockID = unsigned int;
class Block class Block
{ {
public: public:
Block(BlockID id, OpView&& ops) Block(BlockID id, DoubleLinkedList<Op*> ops)
: m_id(id), m_ops(ops) {} : m_id(id), m_ops(ops) {}
public: public:
const OpView& ops() const { return m_ops; } DoubleLinkedList<Op*>& ops() { return m_ops; }
public: public:
StringView Format(int indent) const StringView Format(int indent) const
{ {
StringBuilder sb; StringBuilder sb;
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb.AppendFormat("b%d:\n", m_id); sb.AppendFormat("b%d:\n", m_id);
for (size_t i = 0; i < m_ops.size; ++i) for (ListNode<Op*>* cur = m_ops.Begin(); cur != nullptr; cur = cur->next)
{ {
sb << m_ops.data[i]->Format(indent + 2) << '\n'; sb << cur->value->Format(indent + 2) << '\n';
} }
return sb.view(); return sb.view();
} }
private: private:
BlockID m_id; BlockID m_id;
OpView m_ops; DoubleLinkedList<Op*> m_ops;
}; };
} // namespace IR } // namespace IR

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include <unordered_map> #include <unordered_map>
#include "parser/nodes.hpp"
#include "prelude/error.hpp"
#include "prelude/linkedlist.hpp"
#include "prelude/string.hpp" #include "prelude/string.hpp"
#include "parser/ast.hpp"
#include "ir/slot.hpp"
#include "ir/allocator.hpp"
#include "ir/value.hpp" #include "ir/value.hpp"
#include "ir/ops.hpp" #include "ir/ops.hpp"
@@ -13,8 +13,8 @@ namespace IR
class IRBuilder class IRBuilder
{ {
public: public:
IRBuilder(const Node *root) IRBuilder(const StringView &filename, const Node *root)
: m_root(root), m_ops(new OpBuilder()) {} : m_root(root), m_ops(new DoubleLinkedList<Op*>), m_filename(filename) {}
public: public:
// TODO: support other literals // TODO: support other literals
@@ -28,36 +28,36 @@ namespace IR
{ {
auto value = ParseExpression(varDecl->value()); auto value = ParseExpression(varDecl->value());
// TODO: gather type information from var decl signature, aka local <int> v = 0; // TODO: gather type information from var decl signature, aka local <int> v = 0;
auto dst = AllocateNamed<Pointer>(); auto dst = AllocateNamed<Pointer>(value->GetType());
m_ops->Push(new AllocateOp(dst, value->GetType())); m_ops->Append(m_ops->New(new AllocateOp(dst, value->GetType())));
m_ops->Push(new StoreOp(value, reinterpret_cast<Pointer *>(dst))); m_ops->Append(m_ops->New(new StoreOp(value, reinterpret_cast<Pointer *>(dst))));
m_locals.insert(std::make_pair(varDecl->name(), reinterpret_cast<Pointer *>(dst))); m_locals.insert(std::make_pair(varDecl->name(), reinterpret_cast<Pointer *>(dst)));
} }
ValueHandle *ParseVariable(const VariableNode *var) ValueHandle *ParseVariable(const VariableNode *var)
{ {
// auto dst = AllocateValue();
// m_ops->Push(new LoadOp(dst, var->name()));
if (m_locals.find(var->name()) == m_locals.end()) if (m_locals.find(var->name()) == m_locals.end())
{ {
// TODO: throw proper error // TODO: pass line:offset when Node will have them
assert(0 && "ERROR: variable does not exist"); ErrorLogger::Raise(Error::CompileError(m_filename, StringView::FromFormat("use of undefined variable '%s'", var->name().c_str())));
assert(false);
} }
return reinterpret_cast<ValueHandle *>(m_locals[var->name()]); auto dst = AllocateNamed<Instruction>(m_locals[var->name()]->GetValueType());
m_ops->Append(m_ops->New(new LoadOp(dst, m_locals[var->name()])));
return reinterpret_cast<ValueHandle *>(dst);
} }
ValueHandle *ParseFnCall(const FnCallNode *fn) ValueHandle *ParseFnCall(const FnCallNode *fn)
{ {
// TODO: support multiple args auto args = ValueBuilder();
auto argRegs = ValueBuilder(); for (size_t i = 0; i < fn->args().size; ++i)
if (fn->arg() != nullptr)
{ {
auto arg = ParseExpression(fn->arg()); auto arg = ParseExpression(fn->args().data[i]);
argRegs.Push(arg); args.Push(arg);
} }
// TODO: gather return type of the function // TODO: gather return type of the function
auto dst = AllocateUnnamed<Void>(); auto dst = AllocateNamed<Instruction>(new ValueHandle::Type {ValueHandle::Type::Kind::Int});
m_ops->Push(new CallOp(dst, fn->name(), argRegs.view())); m_ops->Append(m_ops->New(new CallOp(dst, fn->name(), args.view())));
return dst; return dst;
} }
@@ -77,7 +77,7 @@ namespace IR
} }
assert(0 && "unreachable"); assert(0 && "unreachable");
return reinterpret_cast<ValueHandle *>(new Void(0)); return nullptr;
} }
ValueHandle *ParseExpression(const Node *expression) ValueHandle *ParseExpression(const Node *expression)
@@ -93,16 +93,16 @@ namespace IR
switch (expr->op()) switch (expr->op())
{ {
case ExpressionNode::Operator::Plus: case ExpressionNode::Operator::Plus:
m_ops->Push(new MathOp(dst, lhs, rhs, OpType::ADD)); m_ops->Append(m_ops->New(new MathOp(dst, lhs, rhs, OpType::ADD)));
break; break;
case ExpressionNode::Operator::Multiply: case ExpressionNode::Operator::Multiply:
m_ops->Push(new MathOp(dst, lhs, rhs, OpType::MUL)); m_ops->Append(m_ops->New(new MathOp(dst, lhs, rhs, OpType::MUL)));
break; break;
case ExpressionNode::Operator::Minus: case ExpressionNode::Operator::Minus:
m_ops->Push(new MathOp(dst, lhs, rhs, OpType::SUB)); m_ops->Append(m_ops->New(new MathOp(dst, lhs, rhs, OpType::SUB)));
break; break;
case ExpressionNode::Operator::Divide: case ExpressionNode::Operator::Divide:
m_ops->Push(new MathOp(dst, lhs, rhs, OpType::DIV)); m_ops->Append(m_ops->New(new MathOp(dst, lhs, rhs, OpType::DIV)));
break; break;
default: default:
assert(0 && "unreachable"); assert(0 && "unreachable");
@@ -131,12 +131,12 @@ namespace IR
} }
} }
auto ops = EndBlock(); auto ops = EndBlock();
auto block = Block(m_block_counter++, std::move(ops->view())); auto block = Block(m_block_counter++, *ops);
operator delete(ops); operator delete(ops);
return block; return block;
} }
OpView Build() DoubleLinkedList<Op*>* Build()
{ {
assert(m_root->GetType() == NodeType::Program && "root should be a program"); assert(m_root->GetType() == NodeType::Program && "root should be a program");
auto program = reinterpret_cast<const ProgramNode *>(m_root); auto program = reinterpret_cast<const ProgramNode *>(m_root);
@@ -144,31 +144,30 @@ namespace IR
// Externs // Externs
for (auto &extrn : program->externs()) for (auto &extrn : program->externs())
{ {
m_ops->Push(new ExternOp(extrn->symbol())); m_ops->Append(m_ops->New(new ExternOp(extrn->symbol())));
} }
// Functions // Functions
for (auto &fn : program->funcs()) for (auto &fn : program->funcs())
{ {
auto block = ParseBlock(fn->body()); auto block = ParseBlock(fn->body());
m_ops->Push(new FnOp(fn->name(), fn->params(), std::move(block))); m_ops->Append(m_ops->New(new FnOp(fn->name(), fn->params(), std::move(block))));
} }
return OpView(m_ops->data, m_ops->size); return m_ops;
} }
public: public:
// TODO: think about safety (copying m_ops->data before giving) const DoubleLinkedList<Op*>* ops() const { return m_ops; }
OpView ops() const { return OpView(m_ops->data, m_ops->size); }
private: private:
void StartBlock() void StartBlock()
{ {
m_containers.Push(m_ops); m_containers.Push(m_ops);
m_ops = new OpBuilder(); m_ops = new DoubleLinkedList<Op*>;
} }
OpBuilder *EndBlock() DoubleLinkedList<Op*> *EndBlock()
{ {
assert(m_containers.size > 0 && "containers stack is empty"); assert(m_containers.size > 0 && "containers stack is empty");
auto current = m_ops; auto current = m_ops;
@@ -192,13 +191,15 @@ namespace IR
private: private:
const Node *m_root = nullptr; const Node *m_root = nullptr;
StringView m_filename;
OpBuilder *m_ops = nullptr; DoubleLinkedList<Op*> *m_ops = nullptr;
unsigned int m_value_counter = 0; unsigned int m_value_counter = 0;
unsigned int m_block_counter = 0; unsigned int m_block_counter = 0;
std::unordered_map<StringView, Pointer *> m_locals; std::unordered_map<StringView, Pointer *> m_locals;
Builder<OpBuilder *> m_containers; Builder<DoubleLinkedList<Op*>*> m_containers;
}; };
} // namespace IR } // namespace IR

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "prelude/string.hpp" #include "prelude/string.hpp"
#include "parser/nodes.hpp"
#include "ir/value.hpp" #include "ir/value.hpp"
#include "ir/op.hpp" #include "ir/op.hpp"
#include "ir/block.hpp" #include "ir/block.hpp"
@@ -21,7 +20,7 @@ namespace IR
{ {
StringBuilder sb; StringBuilder sb;
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << "EXTRN " << m_symbol.c_str(); sb << "extern " << m_symbol.c_str();
return sb.view(); return sb.view();
} }
@@ -32,7 +31,7 @@ namespace IR
StringView m_symbol; StringView m_symbol;
}; };
// TODO: Make function return value (i.e. inhreit the OpValued class instead) // TODO: Make function return value (i.e. inherit the OpValued class instead)
class FnOp : public Op class FnOp : public Op
{ {
public: public:
@@ -46,14 +45,14 @@ namespace IR
{ {
StringBuilder sb; StringBuilder sb;
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << "LABEL " << m_name.c_str() << ':' << '\n'; sb << "label " << m_name.c_str() << ':' << '\n';
sb << m_body.Format(indent); sb << m_body.Format(indent);
return sb.view(); return sb.view();
} }
public: public:
const StringView &name() const { return m_name; } const StringView &name() const { return m_name; }
const Block &body() const { return m_body; } Block &body() { return m_body; }
const View<StringView> &params() const { return m_params; } const View<StringView> &params() const { return m_params; }
private: private:
@@ -79,43 +78,22 @@ namespace IR
{ {
StringBuilder sb; StringBuilder sb;
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << result()->Format() << " = ALLOCATE " << m_typ->Format(); sb << result()->Format() << " = allocate " << m_typ->Format();
return sb.view(); return sb.view();
} }
public:
const ValueHandle::Type *Type() const { return m_typ; }
private: private:
ValueHandle::Type *m_typ; ValueHandle::Type *m_typ;
}; };
// class LoadConstOp : public OpValued
// {
// public:
// LoadConstOp(ValueHandle *dest, long value)
// : OpValued(dest), m_value(value) {}
// ~LoadConstOp() {}
// OP_TYPE(LOAD_CONST)
// public:
// StringView Format(int indent) const override
// {
// StringBuilder sb;
// sb.AppendIndent(indent);
// sb << result()->Format() << " = LOAD_CONST " << m_value;
// return sb.view();
// }
// public:
// long value() const { return m_value; }
// private:
// long m_value;
// };
class LoadOp : public OpValued class LoadOp : public OpValued
{ {
public: public:
LoadOp(ValueHandle *dest, StringView addr) LoadOp(ValueHandle *dest, Pointer *ptr)
: OpValued(dest), m_addr(addr) {} : OpValued(dest), m_ptr(ptr) {}
~LoadOp() {} ~LoadOp() {}
OP_TYPE(LOAD) OP_TYPE(LOAD)
@@ -124,15 +102,15 @@ namespace IR
{ {
StringBuilder sb; StringBuilder sb;
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << result()->Format() << " = LOAD \"" << m_addr.c_str() << "\""; sb << result()->Format() << " = load " << result()->GetType()->Format() << ", " << m_ptr->Format();
return sb.view(); return sb.view();
} }
public: public:
const StringView &addr() const { return m_addr; } const Pointer* Ptr() const { return m_ptr; }
private: private:
StringView m_addr; Pointer* m_ptr;
}; };
class StoreOp : public Op class StoreOp : public Op
@@ -148,7 +126,7 @@ namespace IR
{ {
StringBuilder sb; StringBuilder sb;
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << "STORE " << src()->Format() << ", " << dst()->Format(); sb << "store " << src()->Format() << ", " << dst()->Format();
return sb.view(); return sb.view();
} }
@@ -176,13 +154,14 @@ namespace IR
switch (m_typ) switch (m_typ)
{ {
case OpType::ADD: case OpType::ADD:
return StringView("ADD"); return StringView("add");
case OpType::MUL: case OpType::MUL:
return StringView("MUL"); return StringView("mul");
case OpType::SUB: case OpType::SUB:
return StringView("SUB"); return StringView("sub");
case OpType::DIV: case OpType::DIV:
return StringView("DIV"); return StringView("div");
default: break;
} }
assert(false && "unreachable"); assert(false && "unreachable");
@@ -222,10 +201,10 @@ namespace IR
for (size_t i = 0; i < m_args.size; ++i) for (size_t i = 0; i < m_args.size; ++i)
{ {
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << "PARAM " << m_args.data[i]->Format() << '\n'; sb << "param " << m_args.data[i]->Format() << '\n';
} }
sb.AppendIndent(indent); sb.AppendIndent(indent);
sb << result()->Format() << " = CALL " << m_callee.c_str(); sb << result()->Format() << " = call " << m_callee.c_str();
return sb.view(); return sb.view();
} }

34
include/ir/optimize.hpp Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "ir/op.hpp"
#include "prelude/linkedlist.hpp"
namespace IR {
class Optimizer
{
public:
virtual bool Apply(DoubleLinkedList<Op*>& ops) = 0;
};
class AllocOptimizer : public Optimizer
{
public:
bool Apply(DoubleLinkedList<Op*>& ops) override
{
for (ListNode<Op*>* cur = ops.Begin(); cur != nullptr; )
{
auto op = cur->value;
if (op->GetType() == OpType::ALLOCATE)
{
auto tmp = cur;
cur = cur->next;
ops.SpliceFront(tmp);
} else {
cur = cur->next;
}
}
return true;
}
};
}

View File

@@ -1,48 +0,0 @@
#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

View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include <cstdint>
#include "prelude/string.hpp" #include "prelude/string.hpp"
namespace IR namespace IR
@@ -104,8 +105,8 @@ namespace IR
class Pointer : public ValueHandle class Pointer : public ValueHandle
{ {
public: public:
Pointer(unsigned int id) Pointer(unsigned int id, const Type* value_typ)
: ValueHandle(id) : ValueHandle(id), m_value_typ(new Type(value_typ))
{ {
m_type = new Type{Type::Kind::Ptr}; m_type = new Type{Type::Kind::Ptr};
} }
@@ -120,30 +121,12 @@ namespace IR
return sb.view(); return sb.view();
} }
private:
Type *m_type;
};
// TODO: Remove void value and use void type only
class Void : public ValueHandle
{
public: public:
Void(unsigned int id) const Type *GetValueType() const { return m_value_typ; }
: ValueHandle(id)
{
m_type = new Type{Type::Kind::Void};
}
const Type *GetType() const override { return m_type; }
public:
virtual StringView Format() const override
{
return m_type->Format();
}
private: private:
Type *m_type; Type *m_type;
Type *m_value_typ;
}; };
class Instruction : public ValueHandle class Instruction : public ValueHandle

View File

@@ -1,9 +1,8 @@
#pragma once #pragma once
#include <cstring>
#include <cassert> #include <cassert>
#include <vector>
#include "parser/lexer.hpp" #include "parser/lexer.hpp"
#include "parser/nodes.hpp" #include "parser/nodes.hpp"
#include "prelude/error.hpp"
class AstParser class AstParser
{ {
@@ -46,14 +45,15 @@ public:
// m_lexer->NextExpect(TokenType::Id); // m_lexer->NextExpect(TokenType::Id);
// char* name = strdup(m_lexer->token().string); // char* name = strdup(m_lexer->token().string);
m_lexer->NextExpect('('); m_lexer->NextExpect('(');
Node* arg = nullptr; Builder<Node*> args;
// TODO: support multiple arguments while (m_lexer->seek_token()->token != ')')
if (m_lexer->seek_token()->token != ')')
{ {
arg = ParseExpression(); auto arg = ParseExpression();
args.Push(arg);
if (m_lexer->seek_token()->token == ',') assert(m_lexer->NextToken());
} }
m_lexer->NextExpect(')'); m_lexer->NextExpect(')');
return new FnCallNode(name, arg); return new FnCallNode(name, std::move(args.view()));
} }
Node* ParseFactor() Node* ParseFactor()
@@ -80,8 +80,8 @@ public:
return new VariableNode(name); return new VariableNode(name);
} }
default: default:
fprintf(stderr, "%s:%d:%d: ERROR: unexpected token while parsing %ld\n", m_lexer->filename(), token->line_number, token->offset_start, token->token); // fprintf(stderr, "%s:%d:%d: ERROR: unexpected token while parsing %ld\n", m_lexer->filename(), token->line_number, token->offset_start, token->token);
Exit(1); ErrorLogger::Raise(Error::ParseError(m_lexer->filename(), StringView::FromFormat("unexpected token while parsing '%d'", token->token), token->line_number, token->offset_start));
break; break;
} }
@@ -96,7 +96,7 @@ public:
{ {
m_lexer->NextToken(); m_lexer->NextToken();
ExpressionNode::Operator eop; ExpressionNode::Operator eop;
assert((int)ExpressionNode::Operator::COUNT_OPERATORS == 4 && "some operators may not be handled"); assert(static_cast<size_t>(ExpressionNode::Operator::COUNT_OPERATORS) == 4 && "some operators may not be handled");
switch((char)op->token) switch((char)op->token)
{ {
case '/': case '/':
@@ -124,7 +124,7 @@ public:
{ {
m_lexer->NextToken(); m_lexer->NextToken();
ExpressionNode::Operator eop; ExpressionNode::Operator eop;
assert((int)ExpressionNode::Operator::COUNT_OPERATORS == 4 && "some operators may not be handled"); assert(static_cast<size_t>(ExpressionNode::Operator::COUNT_OPERATORS) == 4 && "some operators may not be handled");
switch((char)op->token) switch((char)op->token)
{ {
case '+': case '+':
@@ -157,8 +157,7 @@ public:
Node* ParseStatement() Node* ParseStatement()
{ {
auto token = m_lexer->seek_token(); auto token = m_lexer->seek_token();
// TODO: proper error handling assert(token != nullptr);
assert(token != nullptr && "next token should be available");
switch(token->token) switch(token->token)
{ {
case TokenType::Local: return ParseVarDecl(); case TokenType::Local: return ParseVarDecl();
@@ -178,13 +177,13 @@ public:
auto token = m_lexer->token(); auto token = m_lexer->token();
switch(token.token) switch(token.token)
{ {
case TokenType::Extern: program->PushExtern(ParseExtern()); break;
case TokenType::Fn: program->PushFunction(ParseFnDecl()); break; case TokenType::Fn: program->PushFunction(ParseFnDecl()); break;
case TokenType::Extern: program->PushExtern(ParseExtern()); break;
default: { default: {
fprintf(stderr, "%s:%d:%d: ERROR: unexpected token while parsing %ld\n", m_lexer->filename(), token.line_number, token.offset_start, token.token); ErrorLogger::Raise(Error::ParseError(m_lexer->filename(), StringView::FromFormat("unexpected token while parsing '%d'", token.token), token.line_number, token.offset_start));
Exit(1); assert(false);
break;
} }
break;
} }
} }

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include <iostream>
#include <cstring> #include <cstring>
#include <cctype> #include <cctype>
@@ -48,13 +47,13 @@ inline bool operator!=(char c, TokenType t)
struct Token struct Token
{ {
TokenType token; TokenType token = TokenType::Unknown;
long int_number; long int_number = 0;
// null-terminated // null-terminated
StringView string; StringView string;
long line_number; long line_number = 0;
long offset_start; long offset_start = 0;
long offset_end; long offset_end = 0;
public: public:
Token(TokenType t) : token(t) {} Token(TokenType t) : token(t) {}
Token(TokenType t, long lnumber, long soffset, long eoffset) Token(TokenType t, long lnumber, long soffset, long eoffset)
@@ -74,7 +73,7 @@ public:
auto lnl = m_last_newline; auto lnl = m_last_newline;
if (!NextToken()) if (!NextToken())
{ {
return nullptr; return new Token(TokenType::Eof, m_line, m_pos - m_last_newline, m_pos - m_last_newline);
} }
auto seeked = m_token; auto seeked = m_token;
m_token = s; m_token = s;
@@ -93,66 +92,98 @@ public:
public: public:
bool NextToken() bool NextToken()
{ {
if (m_pos >= m_code.size || m_code.data[m_pos] == '\0') auto len = m_code.len();
auto peek = [&]() -> char {
return (m_pos < len) ? m_code.data[m_pos] : '\0';
};
auto advance = [&]() -> char {
return (m_pos < len) ? m_code.data[m_pos++] : '\0';
};
// IMPORTANT: >= not >
if (m_pos >= len)
{ {
m_token = Token(TokenType::Eof); m_token = Token(TokenType::Eof);
return false; return false;
} }
char c = m_code.data[m_pos++]; char c = advance();
while(std::isspace(c)) { // skip whitespace safely
while (c != '\0' && std::isspace((unsigned char)c))
{
if (c == '\n') if (c == '\n')
{ {
m_line++; m_line++;
m_last_newline = m_pos; m_last_newline = m_pos;
} }
c = m_code.data[m_pos++];
if (m_pos >= len) // reached real EOF while skipping whitespace
{
m_token = Token(TokenType::Eof);
return false;
} }
if (std::isalpha(c) != 0 || c == '_') c = advance();
}
if (c == '\0' || m_pos > len) // paranoia guard
{
m_token = Token(TokenType::Eof);
return false;
}
// identifier
if (std::isalpha((unsigned char)c) || c == '_')
{ {
StringBuilder s; StringBuilder s;
long offset_start = m_pos - m_last_newline; long offset_start = m_pos - m_last_newline - 1; // -1 because we already consumed c
s.Push(c); s.Push(c);
// id
while (std::isalpha(m_code.data[m_pos]) != 0 || m_code.data[m_pos] == '_') // NOTE: usually identifiers allow digits after first char; add isdigit if you want
while (true)
{ {
s.Push(m_code.data[m_pos++]); char p = peek();
if (!(std::isalpha((unsigned char)p) || p == '_'))
break;
s.Push(advance());
} }
s.Push('\0'); s.Push('\0');
m_token = Token(TokenType::Id, m_line, offset_start, m_pos - m_last_newline); m_token = Token(TokenType::Id, m_line, offset_start, m_pos - m_last_newline);
m_token.string = s.view(); m_token.string = s.view();
if (strcmp("extern", m_token.string.c_str()) == 0) if (strcmp("extern", m_token.string.c_str()) == 0) m_token.token = TokenType::Extern;
{ else if (strcmp("fn", m_token.string.c_str()) == 0) m_token.token = TokenType::Fn;
m_token.token = TokenType::Extern; else if (strcmp("local", m_token.string.c_str()) == 0) m_token.token = TokenType::Local;
}
if (strcmp("fn", m_token.string.c_str()) == 0)
{
m_token.token = TokenType::Fn;
}
if (strcmp("local", m_token.string.c_str()) == 0)
{
m_token.token = TokenType::Local;
}
return true; return true;
} }
if (std::isdigit(c) != 0) // integer (hex supported)
if (std::isdigit((unsigned char)c))
{ {
StringBuilder s; StringBuilder s;
long offset_start = m_pos - m_last_newline; long offset_start = m_pos - m_last_newline - 1;
bool hex = c == '0' && m_code.data[m_pos] == 'x';
bool hex = (c == '0' && peek() == 'x');
s.Push(c); s.Push(c);
// integer (could be hex)
while (std::isdigit(m_code.data[m_pos]) != 0 || (hex && std::isalpha(m_code.data[m_pos]) != 0)) if (hex) s.Push(advance()); // consume 'x'
while (true)
{ {
s.Push(m_code.data[m_pos++]); char p = peek();
if (std::isdigit((unsigned char)p) ||
(hex && std::isxdigit((unsigned char)p)))
{
s.Push(advance());
} }
else break;
}
s.Push('\0'); s.Push('\0');
m_token = Token(TokenType::IntLiteral, m_line, offset_start, m_pos - m_last_newline); m_token = Token(TokenType::IntLiteral, m_line, offset_start, m_pos - m_last_newline);
m_token.int_number = std::strtol(s.data, nullptr, hex ? 16 : 10); m_token.int_number = std::strtol(s.data, nullptr, hex ? 16 : 10);
@@ -160,7 +191,8 @@ public:
return true; return true;
} }
m_token = Token((TokenType)c, m_line, m_pos - m_last_newline, m_pos - m_last_newline + 1); // single-char token fallback
m_token = Token((TokenType)c, m_line, m_pos - m_last_newline - 1, m_pos - m_last_newline);
return true; return true;
} }

View File

@@ -144,20 +144,17 @@ class FnCallNode : public Node
{ {
public: public:
// TODO: support multiple arguments // TODO: support multiple arguments
FnCallNode(const StringView& name, Node* arg) FnCallNode(const StringView& name, View<Node*>&& arg)
: m_name(name), m_arg(arg) {} : m_name(name), m_args(arg) {}
~FnCallNode() override { ~FnCallNode() override = default;
delete m_arg;
}
NODE_TYPE(FnCall) NODE_TYPE(FnCall)
public: public:
const StringView& name() const { return m_name; } const StringView& name() const { return m_name; }
// TODO: support multiple args const View<Node*>& args() const { return m_args; }
const Node* arg() const { return m_arg; }
private: private:
StringView m_name; StringView m_name;
Node* m_arg; View<Node*> m_args;
}; };
class VariableNode : public Node class VariableNode : public Node

53
include/prelude/error.hpp Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include "prelude/string.hpp"
#include <print>
struct Error
{
enum class Type
{
PARSE = 0,
TYPE,
COMPILE,
COUNT_ERROR_TYPES,
};
Type type;
StringView message;
StringView filename;
long line = 0;
long offset = 0;
public:
static Error CompileError(StringView filename, StringView message, long line = 0, long offset = 0)
{
return Error{Type::COMPILE, message, filename, line, offset};
}
static Error ParseError(StringView filename, StringView message, long line = 0, long offset = 0)
{
return Error{Type::PARSE, message, filename, line, offset};
}
static Error TypeError(StringView filename, StringView message, long line = 0, long offset = 0)
{
return Error{Type::TYPE, message, filename, line, offset};
}
};
constexpr const char* ErrorTypeNames[] = {
"ParseError",
"TypeError",
"CompileError",
};
class ErrorLogger
{
public:
static void Raise(const Error &error)
{
StringBuilder sb;
sb.AppendFormat("%s:%zu:%zu %s: %s", error.filename.c_str(), error.line, error.offset, ErrorTypeNames[static_cast<size_t>(error.type)], error.message.c_str());
std::println("{}", sb.c_str());
std::exit(1);
}
};

71
include/prelude/file.hpp Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include "prelude/string.hpp"
#include <cstdio>
class File
{
public:
enum class Mode
{
WRITE,
READ,
};
private:
File(FILE* handle, Mode mode)
: m_handle(handle), m_mode(mode) {}
public:
static File Open(const StringView& filename, Mode mode)
{
StringBuilder sb;
switch(mode)
{
case Mode::WRITE:
sb.Append('w');
break;
case Mode::READ:
sb.Append('r');
break;
}
FILE* handle = fopen(filename.c_str(), sb.c_str());
return File(handle, mode);
}
private:
inline bool Writable() const { return m_mode == File::Mode::WRITE; }
inline bool Readable() const { return m_mode == File::Mode::READ; }
public:
bool Write(const StringView& content)
{
assert(Writable() && "attempt to write to a file in read only mode");
if (fwrite(content.c_str(), sizeof(char), content.len(), m_handle) != content.len())
{
return false;
}
return true;
}
public:
bool ReadAll(StringBuilder& sb)
{
assert(Readable() && "attempt to read from a file in write only mode");
if (fseek(m_handle, 0, SEEK_END) != 0) return false;
auto size = ftell(m_handle);
if (size == -1) return false;
sb.ensure_extra(size);
size_t wrote = fread(sb.data, sizeof(char), size, m_handle);
if (wrote == 0) return false;
sb.size = wrote;
return true;
}
private:
FILE* m_handle;
Mode m_mode;
};

View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstddef>
#include "prelude/string.hpp"
template<typename T>
struct ListNode
{
T value;
ListNode* prev = nullptr;
ListNode* next = nullptr;
public:
T& get() noexcept { return value; }
const T& get() const noexcept { return value; }
};
template<typename T>
class DoubleLinkedList {
public:
DoubleLinkedList() = default;
~DoubleLinkedList() = default;
public:
static DoubleLinkedList<T> FromView(const View<T> &view)
{
auto list = DoubleLinkedList<T>();
for (size_t i = 0; i < view.size; ++i)
{
list.Append(new ListNode<T>(view.data[i]));
}
return list;
}
View<T> ToView()
{
Builder<T> b;
for(ListNode<T>* cur = Begin(); cur != nullptr; cur = cur->next)
{
b.Push(cur->value);
}
return b.view();
}
public:
ListNode<T>* New(T value) const { return new ListNode<T>(value); }
public:
void Append(ListNode<T>* node)
{
if (IsEmpty())
{
node->next = nullptr;
node->prev = nullptr;
tail = node;
head = node;
} else {
head->next = node;
node->prev = head;
node->next = nullptr;
head = node;
}
m_size++;
}
void Prepend(ListNode<T>* node)
{
if (IsEmpty())
{
Append(node);
} else {
tail->prev = node;
node->next = tail;
node->prev = nullptr;
tail = node;
m_size++;
}
}
void SpliceFront(ListNode<T>* node)
{
if (m_size == 1 && tail == node && head == node) return;
assert((node->next || node->prev) && "node should be inside of the linked list");
if (node == tail) return;
if (node == head) {
node->prev->next = nullptr;
head = node->prev;
} else {
node->prev->next = node->next;
node->next->prev = node->prev;
}
m_size--;
Prepend(node);
}
bool IsEmpty() const { return m_size == 0; }
ListNode<T>* Begin() const { return tail; }
ListNode<T>* End() const { return head; }
private:
ListNode<T>* head = nullptr;
ListNode<T>* tail = nullptr;
size_t m_size = 0;
};

View File

@@ -67,9 +67,6 @@ public:
const T* end() const { return data + size; } const T* end() const { return data + size; }
}; };
// using StringView = View<char>;
class StringView final : public View<char> class StringView final : public View<char>
{ {
public: public:
@@ -104,6 +101,32 @@ public:
} }
} }
static StringView FromFormat(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
va_list args2;
va_copy(args2, args);
int len = std::vsnprintf(nullptr, 0, fmt, args2);
va_end(args2);
if (len < 0) {
va_end(args);
return StringView();
}
char* str = new char[len + 1];
std::vsnprintf(str, len + 1, fmt, args);
va_end(args);
return StringView(str);
}
size_t len() const { return strlen(data); }
const char* c_str() const { return data; } const char* c_str() const { return data; }
}; };

View File

@@ -1,11 +1,15 @@
#include <iostream>
#include <fstream> #include <fstream>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <print> #include <print>
#include "codegen/targets/fasm_x86_64_linux.hpp"
#include "ir/op.hpp"
#include "ir/optimize.hpp"
#include "parser/lexer.hpp" #include "parser/lexer.hpp"
#include "parser/ast.hpp" #include "parser/ast.hpp"
#include "ir/ir.hpp" #include "ir/ir.hpp"
#include "prelude/file.hpp"
#include "prelude/string.hpp"
// #include "codegen/fasm_stack.hpp" // #include "codegen/fasm_stack.hpp"
@@ -53,29 +57,38 @@ int main(int argc, char **argv)
auto program = parser.Parse(); auto program = parser.Parse();
IR::IRBuilder irBuilder(program); IR::IRBuilder irBuilder(filename, program);
auto ops = irBuilder.Build(); auto ops = irBuilder.Build();
for (size_t i = 0; i < ops.size; ++i) for (auto cur = ops->Begin(); cur != nullptr; cur = cur->next)
{ {
printf("%s\n", ops.data[i]->Format(0).c_str()); auto op = cur->value;
if (op->GetType() == IR::OpType::FN)
{
auto fn = reinterpret_cast<IR::FnOp*>(op);
auto allocaoptimz = IR::AllocOptimizer();
printf("fn <%s>: status = %d\n", fn->name().c_str(), allocaoptimz.Apply(fn->body().ops()));
}
} }
// StackFasmX86_64Generator gen; for (auto cur = ops->Begin(); cur != nullptr; cur = cur->next)
{
auto op = cur->value;
printf("%s\n", op->Format(0).c_str());
}
// gen.Generate(filename, ops); FasmX86_64Generator gen;
// StringView output = gen.GetOutput(); gen.Generate(ops);
// FILE *file = fopen("out.asm", "w"); auto output = File::Open("example.asm", File::Mode::WRITE);
if (!output.Write(gen.GetOutput()))
// fwrite(output.c_str(), output.size - 1, sizeof(char), file); {
fprintf(stderr, "ERROR: Failed to write IR to a file");
// fclose(file); }
std::println("OK");
// system("fasm out.asm");
// system("gcc -o out out.o");
return 0; return 0;
} }