Compare commits

..

2 Commits

14 changed files with 1141 additions and 139 deletions

75
.vscode/settings.json vendored
View File

@ -1,6 +1,79 @@
{ {
"files.associations": { "files.associations": {
"print": "cpp", "print": "cpp",
"cctype": "cpp" "cctype": "cpp",
"new": "cpp",
"format": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"queue": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stdfloat": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"variant": "cpp"
} }
} }

View File

@ -3,6 +3,7 @@ project(pl VERSION 0.0.1 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(SOURCES set(SOURCES
src/ir.cpp
src/main.cpp src/main.cpp
) )

View File

@ -1,7 +1,15 @@
extern putchar extern putchar
fn main() { fn main() {
local a = 34 local h = 72
local b = 35 local e = 69
putchar(a + b) local l = 76
local o = 79
local nl = 10
putchar(h)
putchar(e)
putchar(l)
putchar(l)
putchar(o)
putchar(nl)
} }

35
hello.asm Normal file
View File

@ -0,0 +1,35 @@
format ELF64
section '.text' executable
extrn 'putchar' as __putchar
putchar = PLT __putchar
public main
main:
; allocate space for locals (a and b: 4 bytes each → 8 bytes total)
push rbp
mov rbp, rsp
sub rsp, 8
; local a = 34 → stored at [rbp - 4]
mov dword [rbp - 4], 34
; local b = 35 → stored at [rbp - 8]
mov dword [rbp - 8], 35
; compute a + b
mov eax, [rbp - 4]
add eax, [rbp - 8]
; call putchar(a + b)
; SysV: first integer arg → EDI
mov edi, eax
call putchar
; return 0 from main
mov eax, 0
leave
ret

View File

@ -19,6 +19,9 @@ enum class NodeType
COUNT_NODES, COUNT_NODES,
}; };
#define NODE_TYPE(x) \
NodeType GetType() const override { return NodeType::x; }
class Node class Node
{ {
public: public:
@ -26,10 +29,6 @@ public:
virtual ~Node() {} virtual ~Node() {}
}; };
#define NODE_TYPE(x) \
NodeType GetType() const override { return NodeType::x; }
class ExpressionNode : public Node class ExpressionNode : public Node
{ {
public: public:
@ -79,79 +78,15 @@ class ExternNode : public Node
{ {
public: public:
// TODO: support multiple extern symbols // TODO: support multiple extern symbols
ExternNode(char* symbol) ExternNode(StringView symbol)
: m_symbol(symbol) {} : m_symbol(symbol) {}
~ExternNode() override { ~ExternNode() override = default;
delete m_symbol;
}
NODE_TYPE(Extern) NODE_TYPE(Extern)
private:
char* m_symbol;
};
class FnDeclNode : public Node
{
public: public:
// TODO: support parameters const StringView& symbol() const { return m_symbol; }
FnDeclNode(char* name, Node* body)
: m_name(name), m_body(body) {}
~FnDeclNode() override {
delete m_name;
delete m_body;
}
NODE_TYPE(FnDecl)
private: private:
char* m_name; StringView m_symbol;
Node* m_body;
};
class FnCallNode : public Node
{
public:
// TODO: support multiple arguments
FnCallNode(char* name, Node* arg)
: m_name(name), m_arg(arg) {}
~FnCallNode() override {
delete m_name;
delete m_arg;
}
NODE_TYPE(FnCall)
private:
char* m_name;
Node* m_arg;
};
class VariableNode : public Node
{
public:
VariableNode(char* name)
: m_name(name) {}
~VariableNode() override {
delete m_name;
}
NODE_TYPE(Variable)
private:
char* m_name;
};
class VarDeclNode : public Node
{
public:
VarDeclNode(char* name, Node* value)
: m_name(name), m_value(value) {}
~VarDeclNode() override {
delete m_name;
delete m_value;
}
NODE_TYPE(VarDecl)
private:
char* m_name;
Node* m_value;
}; };
class CompoundNode : public Node class CompoundNode : public Node
@ -187,6 +122,77 @@ private:
std::vector<Node*> m_nodes; std::vector<Node*> m_nodes;
}; };
class FnDeclNode : public Node
{
public:
// TODO: support parameters
FnDeclNode(const StringView& name, CompoundNode* body)
: m_name(name), m_body(body) {}
~FnDeclNode() override {
delete m_body;
}
NODE_TYPE(FnDecl)
public:
const StringView& name() const { return m_name; }
const CompoundNode* body() const { return m_body; }
private:
StringView m_name;
CompoundNode* m_body;
};
class FnCallNode : public Node
{
public:
// TODO: support multiple arguments
FnCallNode(const StringView& name, Node* arg)
: m_name(name), m_arg(arg) {}
~FnCallNode() override {
delete m_arg;
}
NODE_TYPE(FnCall)
public:
const StringView& name() const { return m_name; }
// TODO: support multiple args
const Node* arg() const { return m_arg; }
private:
StringView m_name;
Node* m_arg;
};
class VariableNode : public Node
{
public:
VariableNode(const StringView& name)
: m_name(name) {}
~VariableNode() override = default;
NODE_TYPE(Variable)
public:
const StringView& name() const { return m_name; }
private:
StringView m_name;
};
class VarDeclNode : public Node
{
public:
VarDeclNode(const StringView& name, Node* value)
: m_name(name), m_value(value) {}
~VarDeclNode() override {
delete m_value;
}
NODE_TYPE(VarDecl)
public:
const StringView& name() const { return m_name; }
const Node* value() const { return m_value; }
private:
StringView m_name;
Node* m_value;
};
class ProgramNode : public Node class ProgramNode : public Node
{ {
public: public:
@ -203,6 +209,9 @@ public:
{ {
m_externs.push_back(extrn); m_externs.push_back(extrn);
} }
public:
const std::vector<ExternNode*> externs() const { return m_externs; }
const std::vector<FnDeclNode*> funcs() const { return m_funcs; }
private: private:
std::vector<FnDeclNode*> m_funcs; std::vector<FnDeclNode*> m_funcs;
std::vector<ExternNode*> m_externs; std::vector<ExternNode*> m_externs;
@ -225,7 +234,7 @@ public:
{ {
// Function Declaration // Function Declaration
m_lexer->NextExpect(TokenType::Id); m_lexer->NextExpect(TokenType::Id);
char *name = strdup(m_lexer->token().string); StringView name = m_lexer->token().string;
m_lexer->NextExpect('('); m_lexer->NextExpect('(');
// TODO: parse parameters // TODO: parse parameters
m_lexer->NextExpect(')'); m_lexer->NextExpect(')');
@ -239,7 +248,7 @@ public:
return new FnDeclNode(name, compound); return new FnDeclNode(name, compound);
} }
FnCallNode* ParseFnCall(char* name) FnCallNode* ParseFnCall(const StringView& name)
{ {
// m_lexer->NextExpect(TokenType::Id); // m_lexer->NextExpect(TokenType::Id);
// char* name = strdup(m_lexer->token().string); // char* name = strdup(m_lexer->token().string);
@ -264,7 +273,7 @@ public:
case TokenType::Id: // variable name or function call case TokenType::Id: // variable name or function call
{ {
m_lexer->NextExpect(TokenType::Id); m_lexer->NextExpect(TokenType::Id);
char *name = strdup(m_lexer->token().string); auto name = m_lexer->token().string;
token = m_lexer->seek_token(); token = m_lexer->seek_token();
if (token->token == '(') if (token->token == '(')
{ {
@ -341,7 +350,7 @@ public:
{ {
m_lexer->NextExpect(TokenType::Local); m_lexer->NextExpect(TokenType::Local);
m_lexer->NextExpect(TokenType::Id); m_lexer->NextExpect(TokenType::Id);
char *name = strdup(m_lexer->token().string); auto name = m_lexer->token().string;
m_lexer->NextExpect('='); m_lexer->NextExpect('=');
Node* value = ParseExpression(); Node* value = ParseExpression();
return new VarDeclNode(name, value); return new VarDeclNode(name, value);

270
include/codegen.hpp Normal file
View File

@ -0,0 +1,270 @@
#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 const under 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;
};
class StackFasmX86_64Generator : public CodeGenerator
{
public:
~StackFasmX86_64Generator() override = default;
private:
int GetStackSize(const IR::OpView ops)
{
int stackSize = 0;
for (auto &op : ops)
{
if (op->GetType() == IR::OpType::STORE) 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");
int stackSize = GetStackSize(fn->ops());
printf("sub rsp, %d\n", stackSize);
for(auto &fOp : fn->ops())
{
GenerateOp(fOp);
}
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 reg_slot = m_registers.Resolve(GetTempAddr(call->args().data[0]));
printf("mov edi, %s\n", reg_slot.reg.c_str());
}
printf("call %s\n", call->callee().c_str());
m_registers.Clear();
}
break;
case IR::OpType::LOAD_CONST:
{
auto lc = reinterpret_cast<const IR::LoadConstOp *>(op);
auto addr = GetTempAddr(lc->result());
m_consts.Allocate(addr);
m_consts.StoreValue(addr, 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 = m_consts.Resolve(GetTempAddr(s->src()));
printf("mov dword [rbp-%d], %ld\n", slot.offset, value.value);
}
break;
case IR::OpType::LOAD:
{
auto l = reinterpret_cast<const IR::LoadOp *>(op);
auto reg_slot = m_registers.Allocate(GetTempAddr(l->result()));
auto stack_slot = m_stack.Resolve(l->addr());
printf("mov %s, [rbp-%d]\n", reg_slot.reg.c_str(), stack_slot.offset);
}
break;
case IR::OpType::ADD:
{
auto expr = reinterpret_cast<const IR::AddOp *>(op);
auto lhs_slot = m_registers.Resolve(GetTempAddr(expr->lhs()));
auto rhs_slot = m_registers.Resolve(GetTempAddr(expr->rhs()));
printf("add %s, %s\n", lhs_slot.reg.c_str(), rhs_slot.reg.c_str());
m_registers.Clear();
m_registers.Allocate(GetTempAddr(expr->result()));
}
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;
ConstAllocator m_consts;
RegisterAllocator m_registers;
};

342
include/ir.hpp Normal file
View File

@ -0,0 +1,342 @@
#pragma once
#include "string.hpp"
#include "ast.hpp"
namespace IR
{
enum class OpType
{
EXTERN = 0,
FN,
LOAD_CONST,
LOAD,
STORE,
ADD,
CALL,
COUNT_OPS,
};
#define OP_TYPE(x) \
OpType GetType() const override { return OpType::x; }
using Reg = int;
using RegBuilder = Builder<Reg>;
using RegView = View<Reg>;
class Op
{
public:
virtual OpType GetType() const = 0;
virtual ~Op() {}
virtual StringView Format(int indent) const = 0;
};
using OpView = View<Op*>;
using OpBuilder = Builder<Op*>;
class Valued
{
public:
Valued(Reg dest)
: m_dest(dest) {}
~Valued() = default;
public:
Reg result() const { return m_dest; }
private:
Reg m_dest;
};
class ExternOp : public Op
{
public:
ExternOp(StringView symbol)
: m_symbol(symbol) {}
~ExternOp() {}
OP_TYPE(EXTERN)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
sb.AppendIndent(indent);
sb << "EXTRN " << m_symbol.c_str();
return sb.view();
}
public:
const StringView& symbol() const { return m_symbol; }
private:
StringView m_symbol;
};
class FnOp : public Op
{
public:
FnOp(StringView name, const CompoundNode* body);
~FnOp() {}
OP_TYPE(FN)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
sb.AppendIndent(indent);
sb << "LABEL " << m_name.c_str() << ':' << '\n';
for (size_t i = 0; i < m_ops.size; ++i)
{
sb << m_ops.data[i]->Format(indent + 2) << '\n';
}
return sb.view();
}
public:
const StringView& name() const { return m_name; }
const OpView& ops() const { return m_ops; }
private:
StringView m_name;
OpView m_ops;
};
class LoadConstOp : public Op, public Valued
{
public:
LoadConstOp(Reg dest, long value)
: Valued(dest), m_value(value) {}
~LoadConstOp() {}
OP_TYPE(LOAD_CONST)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
sb.AppendIndent(indent);
sb << 't' << result() << " = LOAD_CONST " << m_value;
return sb.view();
}
public:
long value() const { return m_value; }
private:
long m_value;
};
class LoadOp : public Op, public Valued
{
public:
LoadOp(Reg dest, StringView addr)
: Valued(dest), m_addr(addr) {}
~LoadOp() {}
OP_TYPE(LOAD)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
sb.AppendIndent(indent);
sb << 't' << result() << " = LOAD \"" << m_addr.c_str() << "\"";
return sb.view();
}
public:
const StringView& addr() const { return m_addr; }
private:
StringView m_addr;
};
class StoreOp : public Op
{
public:
StoreOp(StringView addr, Reg src)
: m_addr(addr), m_src(src) {}
~StoreOp() {}
OP_TYPE(STORE)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
sb.AppendIndent(indent);
sb << "STORE \"" << m_addr.c_str() << "\", t" << m_src;
return sb.view();
}
public:
const StringView& addr() const { return m_addr; }
Reg src() const { return m_src; }
private:
StringView m_addr;
Reg m_src;
};
class AddOp : public Op, public Valued
{
public:
AddOp(Reg dest, Reg lhs, Reg rhs)
: Valued(dest), m_lhs(lhs), m_rhs(rhs) {}
~AddOp() {}
OP_TYPE(ADD)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
sb.AppendIndent(indent);
sb << 't' << result() << " = ADD t" << m_lhs << ", t" << m_rhs;
return sb.view();
}
public:
Reg lhs() const { return m_lhs; }
Reg rhs() const { return m_rhs; }
private:
Reg m_lhs;
Reg m_rhs;
};
class CallOp : public Op, public Valued
{
public:
CallOp(Reg dest, StringView callee, RegView args)
: Valued(dest), m_callee(callee), m_args(args) {}
~CallOp() {}
OP_TYPE(CALL)
public:
StringView Format(int indent) const override
{
StringBuilder sb;
for (size_t i = 0; i < m_args.size; ++i)
{
sb.AppendIndent(indent);
sb << "PARAM t" << m_args.data[i] << '\n';
}
sb.AppendIndent(indent);
sb << 't' << result() << " = CALL " << m_callee.c_str();
return sb.view();
}
public:
const StringView& callee() const { return m_callee; }
const RegView& args() const { return m_args; }
private:
StringView m_callee;
RegView m_args;
};
class IRBuilder
{
public:
IRBuilder(const Node* root)
: m_root(root) {}
public:
// TODO: support other literals
Reg ParseIntLiteral(const IntLiteralNode* literal)
{
auto dst = AllocateRegister();
m_ops.Push(new LoadConstOp(dst, literal->integer()));
return dst;
}
Reg ParseVariable(const VariableNode* var)
{
auto dst = AllocateRegister();
m_ops.Push(new LoadOp(dst, var->name()));
return dst;
}
Reg ParseFnCall(const FnCallNode* fn)
{
// TODO: support multiple args
auto arg = ParseExpression(fn->arg());
auto argRegs = RegBuilder();
argRegs.Push(arg);
auto dst = AllocateRegister();
m_ops.Push(new CallOp(dst, fn->name(), RegView(argRegs.data, argRegs.size)));
return dst;
}
Reg ParseFactor(const Node* factor)
{
switch(factor->GetType())
{
case NodeType::IntLiteral: return ParseIntLiteral(reinterpret_cast<const IntLiteralNode*>(factor));
case NodeType::Variable: return ParseVariable(reinterpret_cast<const VariableNode*>(factor));
case NodeType::FnCall: return ParseFnCall(reinterpret_cast<const FnCallNode*>(factor));
default: assert(0 && "some factor may not be handled"); break;
}
assert(0 && "unreachable");
return -1;
}
Reg ParseExpression(const Node* expression)
{
if (expression->GetType() == NodeType::Expression)
{
auto expr = reinterpret_cast<const ExpressionNode*>(expression);
auto lhs = ParseExpression(expr->left());
auto rhs = ParseExpression(expr->right());
auto dst = AllocateRegister();
assert(4 == static_cast<int>(ExpressionNode::Operator::COUNT_OPERATORS) && "some operators may not be handled");
switch (expr->op())
{
case ExpressionNode::Operator::Plus: m_ops.Push(new AddOp(dst, lhs, rhs)); break;
default: assert(0 && "TODO: implement other operations"); break;
}
return dst;
}
return ParseFactor(expression);
}
void ParseVarDecl(const VarDeclNode* varDecl)
{
auto value = ParseExpression(varDecl->value());
m_ops.Push(new StoreOp(varDecl->name(), value));
}
void ParseBlock(const CompoundNode* compound)
{
for (auto &statement : *compound)
{
switch(statement->GetType())
{
case NodeType::VarDecl: ParseVarDecl(reinterpret_cast<VarDeclNode*>(statement)); continue;
default: ParseExpression(statement); continue;
}
}
}
OpView Build()
{
assert(m_root->GetType() == NodeType::Program && "root should be a program");
auto program = reinterpret_cast<const ProgramNode*>(m_root);
// Externs
for (auto &extrn : program->externs())
{
m_ops.Push(new ExternOp(extrn->symbol()));
}
// Functions
for (auto &fn : program->funcs())
{
m_ops.Push(new FnOp(fn->name(), fn->body()));
}
return OpView(m_ops.data, m_ops.size);
}
public:
// TODO: think about safety (copying m_ops.data before giving)
OpView ops() const { return OpView(m_ops.data, m_ops.size); }
private:
Reg AllocateRegister()
{
return m_reg_counter++;
}
private:
OpBuilder m_ops;
const Node* m_root = nullptr;
Reg m_reg_counter = 0;
};
} // namespace IR

View File

@ -51,7 +51,7 @@ struct Token
TokenType token; TokenType token;
long int_number; long int_number;
// null-terminated // null-terminated
char* string; StringView string;
long line_number; long line_number;
long offset_start; long offset_start;
long offset_end; long offset_end;
@ -90,15 +90,10 @@ public:
: m_filename(filename), m_code(code) {} : m_filename(filename), m_code(code) {}
Lexer(const Lexer&) = delete; Lexer(const Lexer&) = delete;
Lexer(Lexer&& other)
{
m_code = other.m_code;
other.m_code = StringView();
}
public: public:
bool NextToken() bool NextToken()
{ {
if (m_pos >= m_code.size) if (m_pos >= m_code.size || m_code.data[m_pos] == '\0')
{ {
m_token = Token(TokenType::Eof); m_token = Token(TokenType::Eof);
return false; return false;
@ -119,27 +114,27 @@ public:
{ {
StringBuilder s; StringBuilder s;
long offset_start = m_pos - m_last_newline; long offset_start = m_pos - m_last_newline;
s.PushChar(c); s.Push(c);
// id // id
while (std::isalpha(m_code.data[m_pos]) != 0) while (std::isalpha(m_code.data[m_pos]) != 0)
{ {
s.PushChar(m_code.data[m_pos++]); s.Push(m_code.data[m_pos++]);
} }
s.PushChar('\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.data; m_token.string = s.view();
if (strcmp("extern", m_token.string) == 0) if (strcmp("extern", m_token.string.c_str()) == 0)
{ {
m_token.token = TokenType::Extern; m_token.token = TokenType::Extern;
} }
if (strcmp("fn", m_token.string) == 0) if (strcmp("fn", m_token.string.c_str()) == 0)
{ {
m_token.token = TokenType::Fn; m_token.token = TokenType::Fn;
} }
if (strcmp("local", m_token.string) == 0) if (strcmp("local", m_token.string.c_str()) == 0)
{ {
m_token.token = TokenType::Local; m_token.token = TokenType::Local;
} }
@ -152,13 +147,13 @@ public:
StringBuilder s; StringBuilder s;
long offset_start = m_pos - m_last_newline; long offset_start = m_pos - m_last_newline;
bool hex = c == '0' && m_code.data[m_pos] == 'x'; bool hex = c == '0' && m_code.data[m_pos] == 'x';
s.PushChar(c); s.Push(c);
// integer (could be hex) // integer (could be hex)
while (std::isdigit(m_code.data[m_pos]) != 0 || (hex && std::isalpha(m_code.data[m_pos]) != 0)) while (std::isdigit(m_code.data[m_pos]) != 0 || (hex && std::isalpha(m_code.data[m_pos]) != 0))
{ {
s.PushChar(m_code.data[m_pos++]); s.Push(m_code.data[m_pos++]);
} }
s.PushChar('\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);
m_token.string = s.data; m_token.string = s.data;

View File

@ -1,47 +1,275 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <utility>
struct StringView template<typename T>
struct View
{ {
size_t size; size_t size;
const char* data; T* data; // owns its memory
public: public:
StringView() View() : size(0), data(nullptr) {}
// Deep copy constructor
View(const T* src, size_t count) : size(count)
{ {
data = nullptr; if (count == 0) { data = nullptr; return; }
size = 0;
data = static_cast<T*>(::operator new[](count * sizeof(T)));
for (size_t i = 0; i < count; ++i)
new (&data[i]) T(src[i]); // copy-construct
} }
StringView(const char* data, size_t size) // Copy constructor
View(const View& other) : View(other.data, other.size) {}
// Move constructor
View(View&& other) noexcept : size(other.size), data(other.data)
{ {
this->data = data; other.size = 0;
this->size = size; other.data = nullptr;
} }
View &operator=(const View &other)
{
if (this != &other)
{
// free old memory
for (size_t i = 0; i < size; ++i)
data[i].~T();
::operator delete[](data);
// deep copy
size = other.size;
data = static_cast<T *>(::operator new[](size * sizeof(T)));
for (size_t i = 0; i < size; ++i)
new (&data[i]) T(other.data[i]);
}
return *this;
}
// Destructor
~View()
{
for (size_t i = 0; i < size; ++i)
data[i].~T();
::operator delete[](data);
}
const T* begin() const { return data; }
const T* end() const { return data + size; }
}; };
struct StringBuilder
// using StringView = View<char>;
class StringView final : public View<char>
{ {
size_t size;
size_t capacity;
char* data;
public: public:
StringBuilder() StringView() : View<char>() {}
// Construct from C-string — deep copy, including null terminator
StringView(const char* s)
: View<char>(s, strlen(s) + 1) {}
// Construct from View<char> — ensure null termination
StringView(const View<char>& v)
: View<char>(v)
{ {
size = 0; if (size == 0 || data[size - 1] != '\0')
capacity = 10; {
data = (char*)malloc(capacity * sizeof(char)); // Reallocate and append null terminator
size_t newSize = size + 1;
char* newData = static_cast<char*>(::operator new[](newSize * sizeof(char)));
for (size_t i = 0; i < size; ++i)
newData[i] = data[i];
newData[newSize - 1] = '\0';
// destroy old
// for (size_t i = 0; i < size; ++i)
// data[i].~char();
::operator delete[](data);
data = newData;
size = newSize;
} }
private: }
void ensureSize(size_t newSize)
const char* c_str() const { return data; }
};
template<typename T>
struct Builder
{
size_t size = 0;
size_t capacity = 8;
T* data;
public:
Builder()
{
data = static_cast<T*>(::operator new[](capacity * sizeof(T)));
}
~Builder()
{
clear();
::operator delete[](data);
}
protected:
void grow(size_t newSize)
{ {
if (newSize <= capacity) return; if (newSize <= capacity) return;
capacity = capacity + (capacity / 2);
data = (char*)realloc(data, capacity * sizeof(char)); size_t newCap = capacity;
while (newCap < newSize)
newCap += newCap / 2;
T* newData = static_cast<T*>(::operator new[](newCap * sizeof(T)));
// Move-construct into the new buffer
for (size_t i = 0; i < size; ++i)
new (&newData[i]) T(std::move(data[i]));
// Destroy old items
for (size_t i = 0; i < size; ++i)
data[i].~T();
::operator delete[](data);
data = newData;
capacity = newCap;
} }
public: public:
void PushChar(char c) void Push(const T& value)
{ {
ensureSize(size + 1); grow(size + 1);
data[size++] = c; new (&data[size]) T(value);
size++;
}
void Push(T&& value)
{
grow(size + 1);
new (&data[size]) T(std::move(value));
size++;
}
// Clear Builder storage but keep capacity
void clear()
{
for (size_t i = 0; i < size; ++i)
data[i].~T();
size = 0;
}
// ALWAYS produce a deep-copied View
View<T> view() const
{
return View<T>(data, size);
}
};
// using StringBuilder = Builder<char>;
class StringBuilder final : public Builder<char>
{
public:
StringBuilder() : Builder<char>() {}
// Ensure there is room for `n` more characters (not counting terminator)
void ensure_extra(size_t n)
{
// grow size + n (but not including terminator)
this->grow(this->size + n);
}
// Append raw C string (WITHOUT copying terminator)
void Extend(const char* str)
{
if (!str) return;
size_t len = strlen(str);
ensure_extra(len);
for (size_t i = 0; i < len; ++i)
this->Push(str[i]);
}
// Append a single char
void Append(char c)
{
this->Push(c);
}
void AppendIndent(int indent) {
grow(size + indent);
memset(data + size, ' ', indent);
size += indent;
}
// Return a null-terminated string pointer owned by builder
const char* c_str()
{
// ensure space for terminator
this->grow(this->size + 1);
// add terminator (overwrite or place it at end)
if (this->size == 0 || this->data[this->size - 1] != '\0')
{
// If already ended with \0, fine
this->Push('\0');
}
return this->data;
}
// Produce a deep-copied StringView
StringView view()
{
return StringView(this->c_str());
}
// streaming operators
StringBuilder& operator<<(const char* s)
{
Extend(s);
return *this;
}
StringBuilder& operator<<(const StringView& sv)
{
Extend(sv.c_str());
return *this;
}
StringBuilder& operator<<(char c)
{
Append(c);
return *this;
}
StringBuilder& operator<<(int v)
{
char buf[32];
snprintf(buf, sizeof(buf), "%d", v);
Extend(buf);
return *this;
}
StringBuilder& operator<<(long v)
{
char buf[32];
snprintf(buf, sizeof(buf), "%ld", v);
Extend(buf);
return *this;
} }
}; };

9
src/ir.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "ir.hpp"
IR::FnOp::FnOp(StringView name, const CompoundNode* body)
: m_name(name)
{
IRBuilder ir(body); // Now IRBuilder is complete → OK
ir.ParseBlock(body);
m_ops = ir.ops();
}

View File

@ -3,6 +3,8 @@
#include <print> #include <print>
#include "lexer.hpp" #include "lexer.hpp"
#include "ast.hpp" #include "ast.hpp"
#include "ir.hpp"
#include "codegen.hpp"
void dump_tokens(const char* filename, Lexer* lexer) void dump_tokens(const char* filename, Lexer* lexer)
{ {
@ -19,9 +21,6 @@ void dump_tokens(const char* filename, Lexer* lexer)
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
for (int i = 0; i < argc; ++i) {
std::println("arg#{}: {}", i, argv[i]);
}
char* filename; char* filename;
if (argc > 1) { if (argc > 1) {
filename = (++argv)[0]; filename = (++argv)[0];
@ -40,15 +39,25 @@ int main(int argc, char** argv)
f.close(); f.close();
// std::println("{}", content); Lexer lexer(filename, StringView(content.c_str()));
Lexer lexer(filename, StringView(content.c_str(), content.size()));
// dump_tokens(filename, &lexer);
AstParser parser(&lexer); AstParser parser(&lexer);
auto program = parser.Parse(); auto program = parser.Parse();
IR::IRBuilder irBuilder(program);
auto ops = irBuilder.Build();
// printf("\n");
// for (size_t i = 0; i < ops.size; ++i)
// {
// printf("%s\n", ops.data[i]->Format(0).c_str());
// }
StackFasmX86_64Generator gen;
gen.Generate(filename, ops);
return 0; return 0;
} }

BIN
test

Binary file not shown.

View File

@ -1,16 +1,39 @@
format ELF64 format ELF64
section ".text" executable section '.text' executable
public main
extrn 'putchar' as __putchar extrn 'putchar' as __putchar
putchar = PLT __putchar putchar = PLT __putchar
public main
main: main:
mov rdi, 69 ; create stack frame
push rbp
mov rbp, rsp
sub rsp, 8 ; space for locals: a (4 bytes), b (4 bytes)
; t0 = LOAD_CONST 34
mov dword [rbp-4], 34 ; STORE "a", t0
; t1 = LOAD_CONST 35
mov dword [rbp-8], 35 ; STORE "b", t1
; t2 = LOAD "a"
mov eax, [rbp-4]
; t3 = LOAD "b"
mov ecx, [rbp-8]
; t4 = ADD t2, t3
add eax, ecx ; eax = t4
; PARAM t4 → first arg in EDI
mov edi, eax
; t5 = CALL putchar
call putchar call putchar
mov rdi, 10
call putchar ; function return (SysV: rax contains return value from putchar)
mov rax, 0 leave
ret ret

BIN
test.o

Binary file not shown.