feat: fasm stack code generator
This commit is contained in:
35
hello.asm
35
hello.asm
@@ -1,35 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -153,139 +153,3 @@ public:
|
|||||||
|
|
||||||
virtual void Generate(const char* filename, View<IR::Op*> ops) = 0;
|
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)
|
|
||||||
{
|
|
||||||
switch (op->GetType())
|
|
||||||
{
|
|
||||||
case IR::OpType::STORE:
|
|
||||||
case IR::OpType::LOAD:
|
|
||||||
case IR::OpType::LOAD_CONST:
|
|
||||||
case IR::OpType::ADD:
|
|
||||||
case IR::OpType::CALL:
|
|
||||||
stackSize += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stackSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringView GetTempAddr(IR::Reg reg)
|
|
||||||
{
|
|
||||||
return std::move((StringBuilder() << 't' << reg).view());
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
void GenerateOp(const IR::Op *op)
|
|
||||||
{
|
|
||||||
switch (op->GetType())
|
|
||||||
{
|
|
||||||
case IR::OpType::EXTERN:
|
|
||||||
{
|
|
||||||
auto extrn = reinterpret_cast<const IR::ExternOp *>(op);
|
|
||||||
auto symbol = extrn->symbol();
|
|
||||||
printf("extrn '%s' as __%s\n", symbol.c_str(), symbol.c_str());
|
|
||||||
printf("%s = PLT __%s\n", symbol.c_str(), symbol.c_str());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IR::OpType::FN:
|
|
||||||
{
|
|
||||||
auto fn = reinterpret_cast<const IR::FnOp *>(op);
|
|
||||||
auto name = fn->name();
|
|
||||||
printf("public %s\n", name.c_str());
|
|
||||||
printf("%s:\n", name.c_str());
|
|
||||||
printf("push rbp\n");
|
|
||||||
printf("mov rbp, rsp\n");
|
|
||||||
m_stack = new StackAllocator();
|
|
||||||
int stackSize = GetStackSize(fn->ops());
|
|
||||||
printf("sub rsp, %d\n", stackSize);
|
|
||||||
if (fn->params().size > 0)
|
|
||||||
{
|
|
||||||
auto param_slot = m_stack->Allocate(fn->params().data[0]);
|
|
||||||
printf("mov [rbp-%d], edi\n", param_slot.offset);
|
|
||||||
}
|
|
||||||
for(auto &fOp : fn->ops())
|
|
||||||
{
|
|
||||||
GenerateOp(fOp);
|
|
||||||
}
|
|
||||||
m_stack = nullptr;
|
|
||||||
printf("leave\nret\n");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IR::OpType::CALL:
|
|
||||||
{
|
|
||||||
auto call = reinterpret_cast<const IR::CallOp *>(op);
|
|
||||||
// TODO: support several arguments
|
|
||||||
if (call->args().size == 1)
|
|
||||||
{
|
|
||||||
auto slot = m_stack->Resolve(GetTempAddr(call->args().data[0]));
|
|
||||||
printf("mov edi, [rbp-%d]\n", slot.offset);
|
|
||||||
}
|
|
||||||
printf("call %s\n", call->callee().c_str());
|
|
||||||
auto result_slot = m_stack->Allocate(GetTempAddr(call->result()));
|
|
||||||
printf("mov dword [rbp-%d], eax\n", result_slot.offset);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IR::OpType::LOAD_CONST:
|
|
||||||
{
|
|
||||||
auto lc = reinterpret_cast<const IR::LoadConstOp *>(op);
|
|
||||||
auto addr = GetTempAddr(lc->result());
|
|
||||||
auto slot = m_stack->Allocate(addr);
|
|
||||||
printf("mov dword [rbp-%d], %ld\n", slot.offset, lc->value());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IR::OpType::STORE:
|
|
||||||
{
|
|
||||||
auto s = reinterpret_cast<const IR::StoreOp *>(op);
|
|
||||||
printf("; DEBUG: resolving stack slot at %s\n", s->addr().c_str());
|
|
||||||
auto slot = m_stack->Allocate(s->addr());
|
|
||||||
auto value_slot = m_stack->Resolve(GetTempAddr(s->src()));
|
|
||||||
printf("mov eax, [rbp-%d]\n", value_slot.offset);
|
|
||||||
printf("mov dword [rbp-%d], eax\n", slot.offset);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IR::OpType::LOAD:
|
|
||||||
{
|
|
||||||
auto l = reinterpret_cast<const IR::LoadOp *>(op);
|
|
||||||
auto value_slot = m_stack->Allocate(GetTempAddr(l->result()));
|
|
||||||
auto variable_slot = m_stack->Resolve(l->addr());
|
|
||||||
printf("mov eax, [rbp-%d]\n", variable_slot.offset);
|
|
||||||
printf("mov dword [rbp-%d], eax\n", value_slot.offset);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IR::OpType::ADD:
|
|
||||||
{
|
|
||||||
auto expr = reinterpret_cast<const IR::AddOp *>(op);
|
|
||||||
auto lhs_slot = m_stack->Resolve(GetTempAddr(expr->lhs()));
|
|
||||||
printf("mov eax, [rbp-%d]\n", lhs_slot.offset);
|
|
||||||
auto rhs_slot = m_stack->Resolve(GetTempAddr(expr->rhs()));
|
|
||||||
printf("add eax, [rbp-%d]\n", rhs_slot.offset);
|
|
||||||
auto result_slot = m_stack->Allocate(GetTempAddr(expr->result()));
|
|
||||||
printf("mov dword [rbp-%d], eax\n", result_slot.offset);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: printf("; NOT HANDLED\n; %s\n", op->Format(0).c_str()); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
void Generate(const char* filename, View<IR::Op*> ops) override
|
|
||||||
{
|
|
||||||
printf("; fasm x86_64 linux generated assembly using pl\n");
|
|
||||||
printf("format ELF64\n");
|
|
||||||
printf("section '.text' executable\n");
|
|
||||||
|
|
||||||
for (auto& op : ops)
|
|
||||||
{
|
|
||||||
GenerateOp(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
// TODO: handle sub-blocks
|
|
||||||
StackAllocator* m_stack = nullptr;
|
|
||||||
};
|
|
||||||
|
|||||||
140
include/codegens/fasm_stack.hpp
Normal file
140
include/codegens/fasm_stack.hpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "codegen.hpp"
|
||||||
|
|
||||||
|
class StackFasmX86_64Generator : public CodeGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~StackFasmX86_64Generator() override = default;
|
||||||
|
private:
|
||||||
|
int GetStackSize(const IR::OpView ops)
|
||||||
|
{
|
||||||
|
int stackSize = 0;
|
||||||
|
for (auto &op : ops)
|
||||||
|
{
|
||||||
|
switch (op->GetType())
|
||||||
|
{
|
||||||
|
case IR::OpType::STORE:
|
||||||
|
case IR::OpType::LOAD:
|
||||||
|
case IR::OpType::LOAD_CONST:
|
||||||
|
case IR::OpType::ADD:
|
||||||
|
case IR::OpType::CALL:
|
||||||
|
stackSize += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stackSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringView GetTempAddr(IR::Reg reg)
|
||||||
|
{
|
||||||
|
return std::move((StringBuilder() << 't' << reg).view());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void GenerateOp(const IR::Op *op)
|
||||||
|
{
|
||||||
|
switch (op->GetType())
|
||||||
|
{
|
||||||
|
case IR::OpType::EXTERN:
|
||||||
|
{
|
||||||
|
auto extrn = reinterpret_cast<const IR::ExternOp *>(op);
|
||||||
|
auto symbol = extrn->symbol();
|
||||||
|
printf("extrn '%s' as __%s\n", symbol.c_str(), symbol.c_str());
|
||||||
|
printf("%s = PLT __%s\n", symbol.c_str(), symbol.c_str());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::OpType::FN:
|
||||||
|
{
|
||||||
|
auto fn = reinterpret_cast<const IR::FnOp *>(op);
|
||||||
|
auto name = fn->name();
|
||||||
|
printf("public %s\n", name.c_str());
|
||||||
|
printf("%s:\n", name.c_str());
|
||||||
|
printf("push rbp\n");
|
||||||
|
printf("mov rbp, rsp\n");
|
||||||
|
m_stack = new StackAllocator();
|
||||||
|
int stackSize = GetStackSize(fn->ops());
|
||||||
|
printf("sub rsp, %d\n", stackSize);
|
||||||
|
if (fn->params().size > 0)
|
||||||
|
{
|
||||||
|
auto param_slot = m_stack->Allocate(fn->params().data[0]);
|
||||||
|
printf("mov [rbp-%d], edi\n", param_slot.offset);
|
||||||
|
}
|
||||||
|
for(auto &fOp : fn->ops())
|
||||||
|
{
|
||||||
|
GenerateOp(fOp);
|
||||||
|
}
|
||||||
|
operator delete(m_stack);
|
||||||
|
m_stack = nullptr;
|
||||||
|
printf("leave\nret\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::OpType::CALL:
|
||||||
|
{
|
||||||
|
auto call = reinterpret_cast<const IR::CallOp *>(op);
|
||||||
|
// TODO: support several arguments
|
||||||
|
if (call->args().size == 1)
|
||||||
|
{
|
||||||
|
auto slot = m_stack->Resolve(GetTempAddr(call->args().data[0]));
|
||||||
|
printf("mov edi, [rbp-%d]\n", slot.offset);
|
||||||
|
}
|
||||||
|
printf("call %s\n", call->callee().c_str());
|
||||||
|
auto result_slot = m_stack->Allocate(GetTempAddr(call->result()));
|
||||||
|
printf("mov dword [rbp-%d], eax\n", result_slot.offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::OpType::LOAD_CONST:
|
||||||
|
{
|
||||||
|
auto lc = reinterpret_cast<const IR::LoadConstOp *>(op);
|
||||||
|
auto addr = GetTempAddr(lc->result());
|
||||||
|
auto slot = m_stack->Allocate(addr);
|
||||||
|
printf("mov dword [rbp-%d], %ld\n", slot.offset, lc->value());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::OpType::STORE:
|
||||||
|
{
|
||||||
|
auto s = reinterpret_cast<const IR::StoreOp *>(op);
|
||||||
|
printf("; DEBUG: resolving stack slot at %s\n", s->addr().c_str());
|
||||||
|
auto slot = m_stack->Allocate(s->addr());
|
||||||
|
auto value_slot = m_stack->Resolve(GetTempAddr(s->src()));
|
||||||
|
printf("mov eax, [rbp-%d]\n", value_slot.offset);
|
||||||
|
printf("mov dword [rbp-%d], eax\n", slot.offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::OpType::LOAD:
|
||||||
|
{
|
||||||
|
auto l = reinterpret_cast<const IR::LoadOp *>(op);
|
||||||
|
auto value_slot = m_stack->Allocate(GetTempAddr(l->result()));
|
||||||
|
auto variable_slot = m_stack->Resolve(l->addr());
|
||||||
|
printf("mov eax, [rbp-%d]\n", variable_slot.offset);
|
||||||
|
printf("mov dword [rbp-%d], eax\n", value_slot.offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IR::OpType::ADD:
|
||||||
|
{
|
||||||
|
auto expr = reinterpret_cast<const IR::AddOp *>(op);
|
||||||
|
auto lhs_slot = m_stack->Resolve(GetTempAddr(expr->lhs()));
|
||||||
|
printf("mov eax, [rbp-%d]\n", lhs_slot.offset);
|
||||||
|
auto rhs_slot = m_stack->Resolve(GetTempAddr(expr->rhs()));
|
||||||
|
printf("add eax, [rbp-%d]\n", rhs_slot.offset);
|
||||||
|
auto result_slot = m_stack->Allocate(GetTempAddr(expr->result()));
|
||||||
|
printf("mov dword [rbp-%d], eax\n", result_slot.offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: printf("; NOT HANDLED\n; %s\n", op->Format(0).c_str()); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
void Generate(const char* filename, View<IR::Op*> ops) override
|
||||||
|
{
|
||||||
|
printf("; fasm x86_64 linux generated assembly using pl\n");
|
||||||
|
printf("format ELF64\n");
|
||||||
|
printf("section '.text' executable\n");
|
||||||
|
|
||||||
|
for (auto& op : ops)
|
||||||
|
{
|
||||||
|
GenerateOp(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
// TODO: handle sub-blocks
|
||||||
|
StackAllocator* m_stack = nullptr;
|
||||||
|
};
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
#include "lexer.hpp"
|
#include "lexer.hpp"
|
||||||
#include "ast.hpp"
|
#include "ast.hpp"
|
||||||
#include "ir.hpp"
|
#include "ir.hpp"
|
||||||
#include "codegen.hpp"
|
|
||||||
|
#include "codegens/fasm_stack.hpp"
|
||||||
|
|
||||||
void dump_tokens(const char* filename, Lexer* lexer)
|
void dump_tokens(const char* filename, Lexer* lexer)
|
||||||
{
|
{
|
||||||
|
|||||||
39
test.asm
39
test.asm
@@ -1,39 +0,0 @@
|
|||||||
format ELF64
|
|
||||||
|
|
||||||
section '.text' executable
|
|
||||||
|
|
||||||
extrn 'putchar' as __putchar
|
|
||||||
putchar = PLT __putchar
|
|
||||||
|
|
||||||
public main
|
|
||||||
|
|
||||||
main:
|
|
||||||
; 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
|
|
||||||
|
|
||||||
; function return (SysV: rax contains return value from putchar)
|
|
||||||
leave
|
|
||||||
ret
|
|
||||||
Reference in New Issue
Block a user