Compare commits

..

5 Commits

Author SHA1 Message Date
589586db51 feat: migrate from OpView to DoubleLinkedList<Op*> + multiple
funciton arguments
2026-01-06 16:39:51 +01:00
806e20d9b1 feat: fasm stack codegen beta 2026-01-05 19:36:48 +01:00
2312129148 first codegen prototype 2026-01-04 20:19:24 +01:00
1c04a058d7 fix: lexer out of bounds 2026-01-03 15:03:45 +01:00
629b65e151 save current lexer impl 2026-01-03 15:02:26 +01:00
15 changed files with 405 additions and 421 deletions

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,66 +0,0 @@
#pragma once
#include "prelude/string.hpp"
#include "codegen/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,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

@@ -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

@@ -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

@@ -10,8 +10,8 @@ using BlockID = unsigned int;
class Block class Block
{ {
public: public:
Block(BlockID id, const OpView& ops) Block(BlockID id, DoubleLinkedList<Op*> ops)
: m_id(id), m_ops(DoubleLinkedList<Op*>::FromView(ops)) {} : m_id(id), m_ops(ops) {}
public: public:
DoubleLinkedList<Op*>& ops() { return m_ops; } DoubleLinkedList<Op*>& ops() { return m_ops; }
public: public:

View File

@@ -2,6 +2,7 @@
#include <unordered_map> #include <unordered_map>
#include "parser/nodes.hpp" #include "parser/nodes.hpp"
#include "prelude/error.hpp" #include "prelude/error.hpp"
#include "prelude/linkedlist.hpp"
#include "prelude/string.hpp" #include "prelude/string.hpp"
#include "ir/value.hpp" #include "ir/value.hpp"
#include "ir/ops.hpp" #include "ir/ops.hpp"
@@ -13,7 +14,7 @@ namespace IR
{ {
public: public:
IRBuilder(const StringView &filename, const Node *root) IRBuilder(const StringView &filename, const Node *root)
: m_root(root), m_ops(new OpBuilder()), m_filename(filename) {} : m_root(root), m_ops(new DoubleLinkedList<Op*>), m_filename(filename) {}
public: public:
// TODO: support other literals // TODO: support other literals
@@ -28,8 +29,8 @@ 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>(value->GetType()); 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)));
} }
@@ -42,22 +43,21 @@ namespace IR
assert(false); assert(false);
} }
auto dst = AllocateNamed<Instruction>(m_locals[var->name()]->GetValueType()); auto dst = AllocateNamed<Instruction>(m_locals[var->name()]->GetValueType());
m_ops->Push(new LoadOp(dst, m_locals[var->name()])); m_ops->Append(m_ops->New(new LoadOp(dst, m_locals[var->name()])));
return reinterpret_cast<ValueHandle *>(dst); 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 = AllocateNamed<Instruction>(new ValueHandle::Type {ValueHandle::Type::Kind::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;
} }
@@ -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,30 +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:
OpView ops() const { return OpView(m_ops->data, m_ops->size); } const DoubleLinkedList<Op*>* ops() const { return m_ops; }
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;
@@ -193,13 +193,13 @@ namespace IR
const Node *m_root = nullptr; const Node *m_root = nullptr;
StringView m_filename; 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

@@ -82,6 +82,9 @@ namespace IR
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;
}; };
@@ -103,6 +106,9 @@ namespace IR
return sb.view(); return sb.view();
} }
public:
const Pointer* Ptr() const { return m_ptr; }
private: private:
Pointer* m_ptr; Pointer* m_ptr;
}; };

View File

@@ -45,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,7 +81,7 @@ public:
} }
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);
ErrorLogger::Raise(Error::ParseError(m_lexer->filename(), StringView::FromFormat("unexpected token while parsing '%c'", token->token), token->line_number, token->offset_start)); ErrorLogger::Raise(Error::ParseError(m_lexer->filename(), StringView::FromFormat("unexpected token while parsing '%d'", token->token), token->line_number, token->offset_start));
break; break;
} }
@@ -179,7 +180,7 @@ public:
case TokenType::Fn: program->PushFunction(ParseFnDecl()); break; case TokenType::Fn: program->PushFunction(ParseFnDecl()); break;
case TokenType::Extern: program->PushExtern(ParseExtern()); break; case TokenType::Extern: program->PushExtern(ParseExtern()); break;
default: { default: {
ErrorLogger::Raise(Error::ParseError(m_lexer->filename(), StringView::FromFormat("unexpected token while parsing '%c'", token.token), token.line_number, token.offset_start)); ErrorLogger::Raise(Error::ParseError(m_lexer->filename(), StringView::FromFormat("unexpected token while parsing '%d'", token.token), token.line_number, token.offset_start));
assert(false); assert(false);
} }
break; break;

View File

@@ -47,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)
@@ -73,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;
@@ -92,72 +92,98 @@ public:
public: public:
bool NextToken() bool NextToken()
{ {
// if (m_pos >= m_code.len()) auto len = m_code.len();
// {
// m_token = Token(TokenType::Eof);
// return false;
// }
char c = m_code.data[m_pos++]; auto peek = [&]() -> char {
return (m_pos < len) ? m_code.data[m_pos] : '\0';
};
while(std::isspace(c)) { auto advance = [&]() -> char {
if (c == '\n') return (m_pos < len) ? m_code.data[m_pos++] : '\0';
{ };
m_line++;
m_last_newline = m_pos;
}
c = m_code.data[m_pos++];
}
if (m_pos >= m_code.len()) // IMPORTANT: >= not >
if (m_pos >= len)
{ {
m_token = Token(TokenType::Eof); m_token = Token(TokenType::Eof);
return false; return false;
} }
if (std::isalpha(c) != 0 || c == '_') char c = advance();
// skip whitespace safely
while (c != '\0' && std::isspace((unsigned char)c))
{
if (c == '\n')
{
m_line++;
m_last_newline = m_pos;
}
if (m_pos >= len) // reached real EOF while skipping whitespace
{
m_token = Token(TokenType::Eof);
return false;
}
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);
@@ -165,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

View File

@@ -8,12 +8,16 @@ struct ListNode
T value; T value;
ListNode* prev = nullptr; ListNode* prev = nullptr;
ListNode* next = nullptr; ListNode* next = nullptr;
public:
T& get() noexcept { return value; }
const T& get() const noexcept { return value; }
}; };
template<typename T> template<typename T>
class DoubleLinkedList { class DoubleLinkedList {
public: public:
DoubleLinkedList() = default; DoubleLinkedList() = default;
~DoubleLinkedList() = default;
public: public:
static DoubleLinkedList<T> FromView(const View<T> &view) static DoubleLinkedList<T> FromView(const View<T> &view)
@@ -26,7 +30,6 @@ public:
return list; return list;
} }
public:
View<T> ToView() View<T> ToView()
{ {
Builder<T> b; Builder<T> b;
@@ -37,6 +40,9 @@ public:
return b.view(); return b.view();
} }
public:
ListNode<T>* New(T value) const { return new ListNode<T>(value); }
public: public:
void Append(ListNode<T>* node) void Append(ListNode<T>* node)
{ {

View File

@@ -2,6 +2,7 @@
#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/op.hpp"
#include "ir/optimize.hpp" #include "ir/optimize.hpp"
#include "parser/lexer.hpp" #include "parser/lexer.hpp"
@@ -60,9 +61,9 @@ int main(int argc, char **argv)
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)
{ {
auto op = ops.data[i]; auto op = cur->value;
if (op->GetType() == IR::OpType::FN) if (op->GetType() == IR::OpType::FN)
{ {
auto fn = reinterpret_cast<IR::FnOp*>(op); auto fn = reinterpret_cast<IR::FnOp*>(op);
@@ -72,35 +73,22 @@ int main(int argc, char **argv)
} }
} }
StringBuilder sb; for (auto cur = ops->Begin(); cur != nullptr; cur = cur->next)
for (size_t i = 0; i < ops.size; ++i)
{ {
sb.AppendFormat("%s\n", ops.data[i]->Format(0).c_str()); auto op = cur->value;
printf("%s\n", op->Format(0).c_str());
} }
printf("%s\n", sb.c_str()); FasmX86_64Generator gen;
auto output = File::Open("example.ll", File::Mode::WRITE); gen.Generate(ops);
if (!output.Write(sb.view()))
auto output = File::Open("example.asm", File::Mode::WRITE);
if (!output.Write(gen.GetOutput()))
{ {
fprintf(stderr, "ERROR: Failed to write IR to a file"); fprintf(stderr, "ERROR: Failed to write IR to a file");
} }
std::println("OK"); std::println("OK");
// StackFasmX86_64Generator gen;
// gen.Generate(filename, ops);
// StringView output = gen.GetOutput();
// FILE *file = fopen("out.asm", "w");
// fwrite(output.c_str(), output.size, sizeof(char), file);
// fclose(file);
// system("fasm out.asm");
// system("gcc -o out out.o");
return 0; return 0;
} }