← index
MasterX_System/compiler/ETL/codegen.hpp
Source: MasterX_System/compiler/ETL/codegen.hpp
#ifndef _CODEGEN_H_

#define _CODEGEN_H_


#include <unordered_map>

#include <unordered_set>

#include <stack>

#include <sstream>

#include <vector>

#include <string>

#include <iostream>

#include <algorithm>

#include <regex>

#include <set>

#include "ir.hpp"

#include "symbol.hpp"

#include "clib.hpp"


namespace codegen {
    
    enum class ReturnType {
        INTEGER,
        POINTER,
        VOID, 
        FLOAT,
        DOUBLE,
    };

    enum class VariableType {
        STRING_CONST,
        NUMERIC_CONST,
        VAR,
        VAR_STRING,
        POINTER,
    };

    struct VariableInfo {
        VariableType type;
        bool isAllocated;
        std::string vname, text;
        VariableInfo(VariableType t, bool alloc = false, const std::string& name = "", const std::string &txt = "")
            : type(t), isAllocated(alloc), vname(name), text(txt) {}
        VariableInfo() : type(VariableType::VAR), isAllocated(false), vname(""), text("") {} 
    };

    class CodeEmitter {
    public:
        CodeEmitter(symbol::SymbolTable &symbolTable, std::unordered_map<std::string, int> &functionVarCount)
            : table(symbolTable), functionLocalVarCount(functionVarCount), currentStackOffset{}, maxStackUsage{} {}

        std::string emit(const ir::IRCode &code) {
            std::ostringstream output;
            collectLiteralsAndConstants(code);
            analyzeTempVarUsage(code);
            emitDataSection(output);
            bool has_init = false;
            for(auto &it : code) {
                if(it.type == ir::InstructionType::LABEL && it.dest == "init") {
                    has_init = true;
                    break;
                }
            }
            curFunction = "main";
            if(has_init == true) {
                emitPreamble(output);
                emitCallInit(output);
            } else {
#ifdef __APPLE__

                output << ".section __TEXT,__text\n";
#else

                output << ".section .text\n";
#endif

            }
            emitCode(code, output);
#ifdef __linux__

            output << ".section .note.GNU-stack,\"\",@progbits\n";
#endif

            return applyPeephole(output);
        }

    private:
        symbol::SymbolTable &table;
        symbol::SymbolTable local;
        std::unordered_map<std::string, int> &functionLocalVarCount;
        std::unordered_map<std::string, int> currentStackOffset;
        std::unordered_map<std::string, int> maxStackUsage;
        std::unordered_map<std::string, std::unordered_map<std::string, int>> variableOffsets;
        std::unordered_map<std::string, std::unordered_map<std::string, std::string>> stringLiterals;
        std::unordered_map<std::string, std::unordered_map<std::string, std::string>> numericConstants;
        std::unordered_map<std::string, std::unordered_map<std::string, std::string>> valueLocations;
        std::unordered_map<std::string, std::unordered_map<std::string, int>> valueToStackOffset;
        std::unordered_map<std::string, int> tempVarCountPerFunction;
        std::unordered_map<std::string, std::unordered_set<std::string>> allocatedMemory;  
        std::unordered_map<std::string, std::unordered_map<std::string, VariableInfo>> variableInfo;
        std::unordered_map<std::string, std::set<std::string>> ownedMemory;
        std::string curFunction;

        void collectLiteralsAndConstants(const ir::IRCode &code) {
        
           static int counter = 1;

           for (const auto &instr : code) {

                if(instr.type == ir::InstructionType::LABEL) {
                    curFunction = instr.dest;
                    continue;
                }

                if (instr.type == ir::InstructionType::LOAD_CONST || instr.type == ir::InstructionType::SET_CONST) {
                    if (instr.op1[0] == '\"') {
                        std::string label =  instr.dest;
                        stringLiterals[curFunction][instr.op1] = label;
                        variableInfo[curFunction][instr.dest] = VariableInfo(VariableType::STRING_CONST, false, label, instr.op1);
                        local.enter(label);
                        auto it = local.lookup(label);
                        if(it.has_value()) {
                            symbol::Symbol *s = it.value();
                            s->name = label;
                            s->value = instr.op1;
                        }
                    } else {
                        std::string label = instr.dest;
                        if(numericConstants[curFunction].find(instr.op1) == numericConstants[curFunction].end()) {
                            numericConstants[curFunction][instr.op1] = label;
                            variableInfo[curFunction][instr.dest] = VariableInfo(VariableType::NUMERIC_CONST, false, label, instr.op1);
                            local.enter(label);
                            auto it = local.lookup(label);
                            if(it.has_value()) {
                                symbol::Symbol *s = it.value();
                                 s->name = label;
                                s->value = instr.op1;
                            }
                        }
                    }
                }
            }
        }

        void analyzeTempVarUsage(const ir::IRCode &code) {
            std::unordered_map<std::string, int> tempVarIndices;
            std::string currentFunction;

            for (const auto &instr : code) {
                if (instr.type == ir::InstructionType::LABEL) {
                    currentFunction = instr.dest;
                    tempVarCountPerFunction[currentFunction] = 0;
                    maxStackUsage[currentFunction] = 0;
                    tempVarIndices.clear();
                }

                if (instr.type == ir::InstructionType::CALL || instr.type == ir::InstructionType::CONCAT || instr.type == ir::InstructionType::ASSIGN || instr.dest[0] == 't' || instr.type == ir::InstructionType::LOAD_CONST || instr.type == ir::InstructionType::LOAD_VAR) {
                    if (tempVarIndices.find(instr.dest) == tempVarIndices.end()) {
                        int index = tempVarCountPerFunction[currentFunction]++;
                        tempVarIndices[instr.dest] = index;
                        maxStackUsage[currentFunction] += 8;
                    }
                } 
            }

            for (auto &entry : maxStackUsage) {
                entry.second = ((entry.second + 15) / 16) * 16;
            }
        }

        void emitDataSection(std::ostringstream &output) {
#ifdef __APPLE__

output << ".section __TEXT,__cstring\n";
#else

output << ".section .data\n";
#endif

            for(auto &func : variableInfo) {
                for(const auto &v : func.second) {
                    if(v.second.type == VariableType::NUMERIC_CONST) {
                        //output << v.second.vname << ": .quad " << v.second.text << "\n";

                    } else if(v.second.type == VariableType::STRING_CONST) {
                        output << v.second.vname << ": .asciz " << ir::escapeString(v.second.text) << "\n";
                    }
                }
            }
        }

        void emitPreamble(std::ostringstream &output) {
#ifdef __APPLE__

            output << ".section __TEXT,__text\n";
            output << ".globl _start\n";
            output << "_start:\n";
            output << "    xor %rbp, %rbp\n";
            output << "    movq %rsp, %rdi\n";
            output << "    andq $-16, %rsp\n";
            output << "    subq $8, %rsp \n";
            output << "    call _main\n";
            output << "    movq %rax, %rdi\n";
            output << "    movl $0, %edi\n";
            output << "    call _exit\n\n";
            output << ".globl _main\n";
            output << "_main:\n";
#else

            output << ".section .text\n";
            output << ".globl main\n";
            output << "main:\n";

#endif

            output << "    pushq %rbp\n";
            output << "    movq %rsp, %rbp\n";
            output << "    subq $16, %rsp\n";
            curFunction = "main";
        }

        void emitCallInit(std::ostringstream &output) {
#ifdef __APPLE__

            output << "    call _init\n";
#else

            output << "    call init\n";
#endif

            output << "    movq $0, %rax\n";
            output << "    leave\n";
            output << "    ret\n";
            
        }

        void emitFunctionPrologue(std::ostringstream &output, const std::string &functionName) {
            output << "    pushq %rbp\n";
            output << "    movq %rsp, %rbp\n";

            int stackSpace = maxStackUsage[functionName] + 64; 
            if ((stackSpace % 16) != 0) {
                stackSpace += 8; 
            }

           if (stackSpace > 0) {
                output << "    subq $" << stackSpace << ", %rsp\n";
            }

            output << "    movq $0, %rcx\n";
        }

        void emitFunctionEpilogue(std::ostringstream &output) {
            output << "    leave\n";
            output << "    ret\n";
            
        }

        void emitCode(const ir::IRCode &code, std::ostringstream &output) {
            std::string currentFunction;

            for (const auto &instr : code) {
                if (instr.type == ir::InstructionType::LABEL) {
                    currentFunction = instr.dest;
                }

                switch (instr.type) {
                    case ir::InstructionType::ADD:
                        emitBinaryOp(output, instr, "addq");
                        break;
                    case ir::InstructionType::SUB:
                        emitBinaryOp(output, instr, "subq");
                        break;
                    case ir::InstructionType::MUL:
                        emitBinaryOp(output, instr, "imulq");
                        break;
                    case ir::InstructionType::DIV:
                        emitDiv(output, instr);
                        break;
                    case ir::InstructionType::ASSIGN:
                        emitAssign(output, instr);
                        break;
                    case ir::InstructionType::LOAD_CONST:
                        emitLoadConst(output, instr);
                        break;
                    case ir::InstructionType::LOAD_VAR:
                        emitLoadVar(output, instr);
                        break;
                    case ir::InstructionType::NEG:
                        emitNeg(output, instr);
                        break;
                    case ir::InstructionType::NOT:
                        emitNot(output, instr);
                        break;
                    case ir::InstructionType::CALL:
                        emitCall(output, instr);
                        break;
                    case ir::InstructionType::LABEL:
                        emitLabel(output, instr);
                        break;
                    case ir::InstructionType::RETURN:
                        emitReturn(output, instr);
                        break;
                    case ir::InstructionType::CONCAT:
                        emitConcat(output, instr);
                        break;
                    case ir::InstructionType::PARAM:
                        emitParam(output, instr);
                        break;
                    case ir::InstructionType::PARAM_STRING:
                        emitParamString(output, instr);
                        break;
                    case ir::InstructionType::DEF_PARAM:
                        emitDefParam(output, instr);
                        break;
                    case ir::InstructionType::DEF_PARAM_STRING:
                        emitDefParamString(output, instr);
                        break;
                    case ir::InstructionType::DEFINE:
                        break;
                    case ir::InstructionType::MOD:
                        emitMod(output, instr);
                        break;
                    case ir::InstructionType::AND:
                        emitAnd(output, instr);
                        break;
                    case ir::InstructionType::XOR:
                        emitXor(output, instr);
                        break;
                    case ir::InstructionType::OR:
                        emitOr(output, instr);
                        break;
                    case ir::InstructionType::LSHIFT:
                        emitLShift(output, instr); 
                        break;
                    case ir::InstructionType::RSHIFT:
                        emitRShift(output, instr);
                        break; 
                    case ir::InstructionType::EQ:  
                        emitEq(output, instr);
                        break;
                    case ir::InstructionType::NEQ: 
                        emitNeq(output, instr);
                        break;
                    case ir::InstructionType::LT:  
                        emitLt(output, instr);
                        break;
                    case ir::InstructionType::LE:  
                        emitLe(output, instr);
                        break;
                    case ir::InstructionType::GT:  
                        emitGt(output, instr);
                        break;
                    case ir::InstructionType::GE:  
                        emitGe(output, instr);
                        break;
                    case ir::InstructionType::LOGICAL_AND:  
                        emitLogicalAnd(output, instr);
                        break;
                    case ir::InstructionType::LOGICAL_OR:  
                        emitLogicalOr(output, instr);
                        break;
                    case ir::InstructionType::SUB_LABEL:
                        emitSubLabel(output, instr);
                        break;
                    case ir::InstructionType::JUMP:
                        emitJump(output, instr);
                        break;
                    case ir::InstructionType::SET:
                        emitSet(output, instr);
                        break;  
                    case ir::InstructionType::SET_CONST:
                        emitSetConst(output, instr);
                        break;
                    case ir::InstructionType::LOGICAL_NOT:
                        emitLogicalNot(output, instr);
                        break;
                    case ir::InstructionType::PARAM_POINTER:
                        emitParamPointer(output, instr);
                        break;
                    case ir::InstructionType::DEF_PARAM_POINTER:
                        emitDefParamPointer(output, instr);
                        break;
                    default:
                        std::cerr << "Unsupported IR Instruction: " << instr.toString() << std::endl;
                        break;
                }
            }
        }


        void emitLogicalNot(std::ostringstream &output, const ir::IRInstruction &instr) {
            loadToRegister(output, instr.op1, "%rax");
            output << "    cmpq $0, %rax\n";
            output << "    sete %al\n";
            output << "    movzbq %al, %rbx\n";
            storeToTemp(output, instr.dest, "%rbx");
        }

        void emitSet(std::ostringstream &output, const ir::IRInstruction &instr) {
            variableInfo[curFunction][instr.dest].type = variableInfo[curFunction][instr.op1].type;
            auto val = table.lookup(instr.dest);
            if(val.has_value()) {
                auto loc = table.lookup(instr.op1);
                if(loc.has_value()) {
                    val.value()->vtype = loc.value()->vtype;
                }
                auto val = ownedMemory[curFunction].find(instr.op1);
                if(val != ownedMemory[curFunction].end()) {
                    ownedMemory[curFunction].erase(val);
                    ownedMemory[curFunction].insert(instr.dest);
                }
                output << "# load " << instr.op1 << " to " << instr.dest << "\n";
                output << "    movq "<< getOperand(instr.op1) << ", %rcx\n";
                output << "    movq " << "%rcx, " << getOperand(instr.dest)<< "\n";
            }
        }

        void emitSetConst(std::ostringstream &output, const ir::IRInstruction &instr) {
            auto loc = table.lookup(instr.dest);
            if (instr.op1[0] == '\"') {
                std::string label = stringLiterals[curFunction][instr.op1];
                variableInfo[curFunction][instr.dest].type = VariableType::STRING_CONST;
                if(loc.has_value()) {
                    loc.value()->vtype = ast::VarType::STRING;
                    loc.value()->value = instr.op1;
                }
                output << "    movq " << getOperand(label) << ", %rcx\n";
                output << "    movq %rcx, " << getOperand(instr.dest) << "\n";
            } else {
                std::string label = numericConstants[curFunction][instr.op1];
                variableInfo[curFunction][instr.dest].type = VariableType::NUMERIC_CONST;
                if(loc.has_value()) {
                    loc.value()->vtype = ast::VarType::NUMBER;
                    loc.value()->value = instr.op1;
                }
                output << "    movq $" << instr.op1 << ", %rcx # here\n";
                output << "    movq %rcx, " << getOperand(instr.dest) << "\n";
            } 
           
        }

        void emitSubLabel(std::ostringstream &output, const ir::IRInstruction &instr) {
            output << instr.dest << ": \n";
        }

        void emitJump(std::ostringstream &output, const ir::IRInstruction &instr) {
            if (instr.op1.empty() && instr.op2.empty()) {
                output << "    jmp " << instr.dest << "\n";
            } else {
                loadToRegister(output, instr.op1, "%rax");
                output << "    cmpq $0, %rax\n";
                output << "    je " << instr.dest << "\n";
            }
        }

        void emitLogicalAnd(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if (it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rsi");
            output << "    cmpq $0, %rsi\n";
            output << "    setne %al\n";
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq $0, %rdi\n";
            output << "    setne %cl\n";
            output << "    andb %al, %cl\n";
            output << "    movzbq %cl, %rax\n"; 
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitLogicalOr(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if (it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rdi");
            output << "    cmpq $0, %rdi\n";
            output << "    setne %al\n";
            loadToRegister(output, instr.op2, "%rdx");
            output << "    cmpq $0, %rdx\n";
            output << "    setne %cl\n";
            output << "    orb %al, %cl\n";
            output << "    movzbq %cl, %rdx\n"; 
            storeToTemp(output, instr.dest, "%rdx");
        }
        
        void emitEq(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if (it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rsi");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq %rdi, %rsi\n";
            output << "    sete %cl\n";
            output << "    movzbq %cl, %rdx\n";
            storeToTemp(output, instr.dest, "%rdx");
        }

        void emitNeq(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq %rdi, %rax\n";  
            output << "    setne %cl\n";         
            output << "    movzbq %cl, %rdx\n"; 
            storeToTemp(output, instr.dest, "%rdx");
        }

        void emitLt(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq %rdi, %rax\n";  
            output << "    setl %cl\n";         
            output << "    movzbq %cl, %rdx\n"; 
            storeToTemp(output, instr.dest, "%rdx");
        }

        void emitLe(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq %rdi, %rax\n";  
            output << "    setle %cl\n";         
            output << "    movzbq %cl, %rdx\n"; 
            storeToTemp(output, instr.dest, "%rdx");
        }

        void emitGt(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq %rdi, %rax\n";  
            output << "    setg %cl\n";         
            output << "    movzbq %cl, %rdx\n"; 
            storeToTemp(output, instr.dest, "%rdx");

        }

        void emitGe(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    cmpq %rdi, %rax\n";  
            output << "    setge %cl\n";         
            output << "    movzbq %cl, %rdx\n"; 
            storeToTemp(output, instr.dest, "%rdx");
        }

        void emitAnd(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    andq %rdi, %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitOr(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    orq %rdi, %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitXor(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rdi");
            output << "    xorq %rdi, %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitLShift(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rcx");
            output << "    salq %cl, %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitRShift(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            loadToRegister(output, instr.op2, "%rcx");
            output << "    sarq %cl, %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitMod(std::ostringstream &output, const ir::IRInstruction &instr) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            output << "    cqto\n";          
            loadToRegister(output, instr.op2, "%rdi");
            output << "    idivq %rdi\n";
            storeToTemp(output, instr.dest, "%rdx");
        }

        std::vector<std::string> cargs;
        int paramIndex = 0; 

        void emitDefParam(std::ostringstream &stream, const ir::IRInstruction &instr) {
        
                static std::vector<std::pair<std::string, int>> paramLocations = {
                {"%rdi", -8},  
                {"%rsi", -16}, 
                {"%rdx", -24}, 
                {"%rcx", -32}, 
                {"%r8", -40},  
                {"%r9", -48}   
            };

            if(curFunction == "main") return;

            if (paramIndex < paramLocations.size()) {
                std::string reg = paramLocations[paramIndex].first;
                int offset = paramLocations[paramIndex].second;
                paramIndex++;
                table.enter(instr.dest);
                auto it = table.lookup(instr.dest);
                variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            } else {
                std::cerr << "ETL Error: More parameters than registers available.\n";
                exit(EXIT_FAILURE);
            }
        }

        void emitDefParamString(std::ostringstream &stream, const ir::IRInstruction &instr) {
            static std::vector<std::pair<std::string, int>> paramLocations = {
                {"%rdi", -8},  
                {"%rsi", -16}, 
                {"%rdx", -24}, 
                {"%rcx", -32}, 
                {"%r8", -40},  
                {"%r9", -48}   
            };
            if(curFunction == "main") return;

            if (paramIndex < paramLocations.size()) {
                std::string reg = paramLocations[paramIndex].first;
                int offset = paramLocations[paramIndex].second;
                paramIndex++;
                table.enter(instr.dest);
                auto it = table.lookup(instr.dest);
                if(it.has_value()) {
                    it.value()->vtype = ast::VarType::STRING;
                }
                variableInfo[curFunction][instr.dest].type = VariableType::VAR_STRING;
            } else {
                std::cerr << "ETL Error: More parameters than registers available.\n";
                exit(EXIT_FAILURE);
            }
        }

        
        void emitParamPointer(std::ostringstream &output, const ir::IRInstruction  &instr) {
            static std::vector<std::pair<std::string, int>> paramLocations = {
                {"%rdi", -8},  
                {"%rsi", -16}, 
                {"%rdx", -24}, 
                {"%rcx", -32}, 
                {"%r8", -40},  
                {"%r9", -48}   
            };

            if(curFunction == "main") return;

            if (paramIndex < paramLocations.size()) {
                std::string reg = paramLocations[paramIndex].first;
                int offset = paramLocations[paramIndex].second;
                storeToTemp(output, instr.dest, reg);
                paramIndex++;
                table.enter(instr.dest);
                auto it = table.lookup(instr.dest);
                if(it.has_value()) {
                    it.value()->vtype = ast::VarType::POINTER;
                }
                variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            } else {
                std::cerr << "ETL Error: More parameters than registers available.\n";
                exit(EXIT_FAILURE);
            }
        }

        void emitDefParamPointer(std::ostringstream &output, const ir::IRInstruction &instr) {
            static std::vector<std::pair<std::string, int>> paramLocations = {
                {"%rdi", -8},  
                {"%rsi", -16}, 
                {"%rdx", -24}, 
                {"%rcx", -32}, 
                {"%r8", -40},  
                {"%r9", -48}   
            };

            if(curFunction == "main") return;

            if (paramIndex < paramLocations.size()) {
                std::string reg = paramLocations[paramIndex].first;
                int offset = paramLocations[paramIndex].second;
                paramIndex++;
                table.enter(instr.dest);
                auto it = table.lookup(instr.dest);
                if(it.has_value()) {
                    it.value()->vtype = ast::VarType::POINTER;
                }
                variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            } else {
                std::cerr << "ETL Error: More parameters than registers available.\n";
                exit(EXIT_FAILURE);
            }
        }

        void emitParam(std::ostringstream &stream, const ir::IRInstruction &instr) {
            static std::vector<std::pair<std::string, int>> paramLocations = {
                {"%rdi", -8},  
                {"%rsi", -16}, 
                {"%rdx", -24}, 
                {"%rcx", -32}, 
                {"%r8", -40},  
                {"%r9", -48}   
            };

            if(curFunction == "main") return;

            if (paramIndex < paramLocations.size()) {
                std::string reg = paramLocations[paramIndex].first;
                int offset = paramLocations[paramIndex].second;
                storeToTemp(stream, instr.dest, reg);
                paramIndex++;
                table.enter(instr.dest);
                auto it = table.lookup(instr.dest);
                variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            } else {
                std::cerr << "ETL Error: More parameters than registers available.\n";
                exit(EXIT_FAILURE);
            }
        }

        void emitParamString(std::ostringstream &output, const ir::IRInstruction &instr) {
            static std::vector<std::pair<std::string, int>> paramLocations = {
                {"%rdi", -8},  
                {"%rsi", -16}, 
                {"%rdx", -24}, 
                {"%rcx", -32}, 
                {"%r8", -40},  
                {"%r9", -48}   
            };
            if(curFunction == "main") return;

            if (paramIndex < paramLocations.size()) {
                std::string reg = paramLocations[paramIndex].first;
                int offset = paramLocations[paramIndex].second;
                storeToTemp(output, instr.dest, reg);
                paramIndex++;
                table.enter(instr.dest);
                auto it = table.lookup(instr.dest);
                if(it.has_value()) {
                    it.value()->vtype = ast::VarType::STRING;
                }
                variableInfo[curFunction][instr.dest].type = VariableType::VAR_STRING;
            } else {
                std::cerr << "ETL Error: More parameters than registers available.\n";
                exit(EXIT_FAILURE);
            }
        }
        
        void emitLoadConst(std::ostringstream &output, const ir::IRInstruction &instr) {

            table.enter(instr.dest);
            auto loc = table.lookup(instr.dest);


            if (instr.op1[0] == '\"') {
                std::string label = stringLiterals[curFunction][instr.op1];
                output << "    leaq " << label << "(%rip), %rax\n";
                variableInfo[curFunction][instr.dest].type = VariableType::STRING_CONST;
                if(loc.has_value()) {
                    loc.value()->vtype = ast::VarType::STRING;
                }
            } else {
                std::string label = numericConstants[curFunction][instr.op1];
                output << "    movq " << "$" << instr.op1 << ", %rax\n";
                variableInfo[curFunction][instr.dest].type = VariableType::NUMERIC_CONST;
                if(loc.has_value()) {
                    loc.value()->vtype = ast::VarType::NUMBER;
                }
            } 
            storeToTemp(output, instr.dest, "%rax");
        }
        
        void emitConcat(std::ostringstream &output, const ir::IRInstruction &instr) {
            auto op1_it = table.lookup(instr.op1);
            auto op2_it = table.lookup(instr.op2);
            std::string prefix;
        #ifdef __APPLE__
            prefix = "_";
        #endif
            output << "    movq $0, %rcx\n";
            storeToTemp(output, "counter", "%rcx");
            auto addStringLength = [&](const std::string &op, auto &op_it) {
                if (variableInfo[curFunction][op].type == VariableType::STRING_CONST) {
                    if (!variableInfo[curFunction][op].text.empty() && variableInfo[curFunction][op].text[0] == '\"') {
                        auto len = variableInfo[curFunction][op].text.length() + 1;
                        output << "    addq $" << len << ", " << getOperand("counter") << "\n";
                    } else {
                        loadToRegister(output, op, "%rdi");
                        output << "    call " << prefix << "strlen #" << op << "\n";
                        output << "    addq %rax, " << getOperand("counter") << "\n";
                    }
                } else if (variableInfo[curFunction][op].type == VariableType::VAR_STRING ||
                        (op_it.has_value() && op_it.value()->vtype == ast::VarType::STRING)) {
                    loadToRegister(output, op, "%rdi");
                    output << "    call " << prefix << "strlen # " << op << "\n";
                    output << "    addq %rax, " << getOperand("counter") << "\n";
                }
            };
            addStringLength(instr.op1, op1_it);
            addStringLength(instr.op2, op2_it);
            output << "    addq $1, " << getOperand("counter") << "\n";
            output << "    movq $" << sizeof(char) << ", %rsi\n";
            output << "    xorq %rax, %rax\n";
            loadToRegister(output, "counter", "%rdi");
            output << "    call " << prefix << "calloc\n";
            output << "    movq %rax, %rdi\n";         
            storeToTemp(output, instr.dest, "%rdi");
            loadToRegister(output, instr.op1, "%rsi");
            output << "    call " << prefix << "strcpy\n";
            loadToRegister(output, instr.op2, "%rsi");
            output << "    call " << prefix << "strcat\n";
            allocatedMemory[curFunction].insert(instr.dest);
            
            variableInfo[curFunction][instr.dest].type = VariableType::VAR_STRING;
            table.enter(instr.dest);
            if (auto it = table.lookup(instr.dest); it.has_value()) {
                it.value()->vtype = ast::VarType::STRING;
            }
        }


        void emitBinaryOp(std::ostringstream &output, const ir::IRInstruction &instr, const std::string &op) {
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            loadToRegister(output, instr.op1, "%rax");
            output << "    " << op << " " << getOperand(instr.op2) << ", %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitDiv(std::ostringstream &output, const ir::IRInstruction &instr) {
            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
            table.enter(instr.dest);
            auto it = table.lookup(instr.dest);
            if(it.has_value()) {
                it.value()->vtype = ast::VarType::NUMBER;
            }
            loadToRegister(output, instr.op1, "%rax");
            output << "    cqto\n";
            output << "    idivq " << getOperand(instr.op2) << "\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitAssign(std::ostringstream &output, const ir::IRInstruction &instr) {
            variableInfo[curFunction][instr.dest].type = variableInfo[curFunction][instr.op1].type;
            table.enter(instr.dest);
            auto val = table.lookup(instr.dest);
            if(val.has_value()) {
                auto loc = table.lookup(instr.op1);
                if(loc.has_value()) {
                    val.value()->vtype = loc.value()->vtype;
                }
                auto val = ownedMemory[curFunction].find(instr.op1);
                if(val != ownedMemory[curFunction].end()) {
                    ownedMemory[curFunction].erase(val);
                    ownedMemory[curFunction].insert(instr.dest);
                }
            }
            loadToRegister(output, instr.op1, "%rax");
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitLoadVar(std::ostringstream &output, const ir::IRInstruction &instr) {
            loadToRegister(output, instr.op1, "%rax");
            storeToTemp(output, instr.dest, "%rax");
            table.enter(instr.op1);
            table.enter(instr.dest);
            auto it = table.lookup(instr.op1);
            if(it.has_value()) {
                symbol::Symbol *s = it.value();
                auto src = table.lookup(instr.dest);
                if(src.has_value()) {
                    symbol::Symbol *v = src.value();
                    v->vtype = s->vtype;
                    variableInfo[curFunction][instr.dest].type = VariableType::VAR_STRING;
                   
                }
            }
        }

        void emitNeg(std::ostringstream &output, const ir::IRInstruction &instr) {
            loadToRegister(output, instr.op1, "%rax");
            output << "    negq %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        void emitNot(std::ostringstream &output, const ir::IRInstruction &instr) {
            loadToRegister(output, instr.op1, "%rax");
            output << "    notq %rax\n";
            storeToTemp(output, instr.dest, "%rax");
        }

        std::string lastFunctionCall;
        std::string lastFunctionCallDest;


        bool checkArgumentTypes(const std::string& functionName, const std::vector<ast::VarType>& providedArgs, const std::vector<ast::VarType>& expectedArgs) {
            if (providedArgs.size() != expectedArgs.size()) {
                std::cerr << "Error: Function '" << functionName << "' expected " << expectedArgs.size() << " arguments, but got " << providedArgs.size() << ".\n";
                return false;
            }

            for (size_t i = 0; i < providedArgs.size(); ++i) {
                ast::VarType actual = providedArgs[i];
                ast::VarType expected = expectedArgs[i];

                //a pointer is a number

               
               if(actual == ast::VarType::NUMBER && expected == ast::VarType::POINTER) 
                    continue;
                if(actual == ast::VarType::POINTER && expected == ast::VarType::NUMBER)
                    continue;
                
                if (actual != expected) {
                    std::cerr << "Error: Type mismatch for argument " << i << " in function '" << functionName
                            << "'. Expected type: " << ast::VarString[static_cast<int>(expected)]
                            << ", Actual type: " << ast::VarString[static_cast<int>(actual)] << ".\n";
                    return false;
                }
            }

            return true;
        }
        void checkFunctionArguments(const std::string& functionName, const std::vector<std::string>& args) {
                std::vector<ast::VarType> providedArgs;
                for (const auto& arg : args) {
                    auto argIt = table.lookup(arg);
                    if (argIt.has_value()) {
                        providedArgs.push_back(argIt.value()->vtype);
                    } else {
                        std::cerr << "Error: Argument '" << arg << "' not found in symbol table.\n";
                        exit(EXIT_FAILURE);
                    }
                }

                auto localFunc = table.lookupFunc(functionName);
                if (localFunc.has_value()) {
                    const auto& expectedArgs = localFunc.value()->argTypes;
                    if (!checkArgumentTypes(functionName, providedArgs, expectedArgs)) {
                        std::cerr << "Error: Argument type check failed for function '" << functionName << "'.\n";
                        exit(EXIT_FAILURE);
                    }
                } else {
                    if(functionName != "printf" && functionName != "sprintf") {
                        std::cerr << "Error: Function '" << functionName << "' is not defined.\n";
                        exit(EXIT_FAILURE);
                    }
                }
        }


        void emitCall(std::ostringstream &output, const ir::IRInstruction &instr) {
            static const std::vector<std::string> argumentRegisters = {"%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9"};
            size_t numArgs = instr.args.size();

            for (size_t i = 0; i < numArgs && i < argumentRegisters.size(); ++i) {
                loadToRegister(output, instr.args[i], argumentRegisters[i]);
            }

            checkFunctionArguments(instr.functionName, instr.args);

            lastFunctionCall = instr.functionName;
            lastFunctionCallDest = instr.dest;
            output << "    movq $0, %rax\n"; 
#ifdef __APPLE__

            output << "    call " << "_" << instr.functionName << "\n";
#else

            output << "    call " << instr.functionName << "\n";
#endif

            storeToTemp(output, instr.dest, "%rax");
            table.enter(instr.dest);
            if(variableInfo[curFunction][instr.dest].type == VariableType::VAR_STRING) {
                ownedMemory[curFunction].insert(instr.dest);
            }

            

            if(instr.functionName == "str") {
                output << "    addq $22, " << getOperand("counter") << "\n";
            }
            auto fn = clib::clibrary.find(instr.functionName);
            
            if (fn != clib::clibrary.end()) {

                auto dest_rt = table.lookup(instr.dest);

                switch (fn->second.return_type) {
                    case clib::VarType::POINTER:
                        variableInfo[curFunction][instr.dest].type = VariableType::VAR_STRING;
                        if(dest_rt.has_value()) {
                            dest_rt.value()->vtype = ast::VarType::STRING;
                        }
                        break;
                    case clib::VarType::INTEGER:
                        variableInfo[curFunction][instr.dest].type = VariableType::VAR;
                        if(dest_rt.has_value()) {
                            dest_rt.value()->vtype = ast::VarType::NUMBER;
                        }
                        break;
                    default:
                        std::cerr << "ETL: Return type not supported yet.\n";
                        exit(EXIT_FAILURE);
                        break;
                }

                if (instr.args.size() != fn->second.args.size() && instr.functionName != "printf" && instr.functionName != "sprintf") {
                    std::cerr << "ETL: Fatal, incorrect number of arguments for: " << instr.functionName << "\n";
                    exit(EXIT_FAILURE);
                }

                if (instr.functionName != "printf" && instr.functionName != "sprintf") {
                    for (size_t i = 0; i < fn->second.args.size(); ++i) {
                        ast::VarType actualVarType , expectedType;

                        auto one = instr.args.at(i);
                        auto t1 = table.lookup(one);
                        if(t1.has_value()) {
                            actualVarType = t1.value()->vtype;
                          
                        }

                        auto type = fn->second.args.at(i);
                        switch(type) {
                            case clib::VarType::POINTER:
                            expectedType = ast::VarType::STRING;
                            break;
                            case clib::VarType::INTEGER:
                            expectedType = ast::VarType::NUMBER;
                            break;
                            default:
                                break;
                        }
                       
                        if (actualVarType != expectedType) {
                            std::cerr << "ETL: Type mismatch for argument " << i << " " << static_cast<int>(actualVarType) << ":" << static_cast<int>(expectedType) << " " << instr.args.at(i) << " in function " << instr.functionName << "\n";
                            exit(EXIT_FAILURE);
                        }
                    }
                }

                if (fn->second.allocated || fn->first == "str") {
                    if (allocatedMemory[curFunction].find(instr.dest) == allocatedMemory[curFunction].end()) {
                        allocatedMemory[curFunction].insert(instr.dest);
                    }
                }
                if(fn->first == "str") {
                    table.enter(instr.dest);
                    auto it = table.lookup(instr.dest);
                    if(it.has_value()) {
                        it.value()->vtype = ast::VarType::STRING;
                    }
                }
            } else {
                auto f = table.lookupFunc(instr.functionName);
                if(f.has_value()) {
                    if(f.value()->num_args != instr.args.size()) {
                        std::cerr << "ETL: Error function argument count doesn't match call to: " << instr.functionName << "\n";
                        exit(EXIT_FAILURE);
                    }
                    ast::VarType returnType = f.value()->vtype;
                    table.enter(instr.dest);
                    auto loc = table.lookup(instr.dest);
                    if(loc.has_value()) {
                        loc.value()->name = instr.dest;
                        loc.value()->vtype = returnType;
                    }

                       switch (returnType) {
                        case ast::VarType::STRING:
                                if(variableInfo[instr.functionName][instr.transfer_var].type == VariableType::VAR_STRING) {
                                    variableInfo[curFunction][instr.dest].type = VariableType::VAR_STRING;
                                    allocatedMemory[curFunction].insert(instr.dest);
                                }
                            break;
                        case ast::VarType::NUMBER:
                            variableInfo[curFunction][instr.dest].type = VariableType::VAR;
                            break;
                        case ast::VarType::POINTER:
                             variableInfo[curFunction][instr.dest].type = VariableType::POINTER;
                             break;
                        default:
                            std::cerr << "ETL: Unsupported return type for local function " << instr.functionName << "\n";
                            exit(EXIT_FAILURE);
                    }
                }
            }

        

            cargs.clear();
        }

        void emitLabel(std::ostringstream &output, const ir::IRInstruction &instr) {
#ifdef __APPLE__

            output << ".globl _" << instr.dest << "\n";
            output << "_" << instr.dest << ":\n";
#else

            output << ".globl " << instr.dest << "\n";
            output << instr.dest << ":\n";
#endif

            curFunction = instr.dest;
            local.enterScope(curFunction);
            paramIndex = 0;
            if (instr.dest != "main") {
                emitFunctionPrologue(output, instr.dest);
            }
            
        }

        void emitReturn(std::ostringstream &output, const ir::IRInstruction &instr) {

            if(variableInfo[curFunction][instr.dest].type == VariableType::VAR_STRING) {
                auto val = allocatedMemory[curFunction].find(instr.dest);
                if(val != allocatedMemory[curFunction].end()) {
                    allocatedMemory[curFunction].erase(val);
                }
            } 

            std::string prefix;
#ifdef __APPLE__

            prefix = "_";
#endif


            
            /*
            for (const auto &var : allocatedMemory[curFunction]) {
                if(variableInfo[curFunction][var].type == VariableType::VAR_STRING) {
                    loadToRegister(output, var, "%rdi");
                    output << "    call " << prefix << "free # local variable: "<<var<<"\n";
                }
            } 

            for(const auto &var : ownedMemory[curFunction]) {
                    loadToRegister(output, var, "%rdi");
                    output << "    call " << prefix << "free # ownership transfer: "<<var<<"\n";
            }*/


            if (!instr.dest.empty()) {
                loadToRegister(output, instr.dest, "%rax");
            } else {
                output << "    movq $0, %rax\n";
            }
            
            emitFunctionEpilogue(output);
            local.exitScope();
        }

        void loadToRegister(std::ostringstream &output, const std::string &operand, const std::string &reg) {
            if (operand[0] == '$' || operand[0] == '%') {
                output << "    movq " << operand << ", " << reg << " # " << operand << "," << reg << "\n";
            } 
            else if(isdigit(operand[0])) {
                output << "    movq " << "$" << operand << ", " << reg << " # operand -> reg \n";
            } 
            else {
                int offset = getVariableOffset(operand);
                output << "    movq " << offset << "(%rbp), " << reg << " # " << operand << " # " << operand << "\n";
            }
        }

        void storeToTemp(std::ostringstream &output, const std::string &temp, const std::string &reg) {
            if (valueLocations[curFunction][temp] == reg) {
                return; 
            }
            int offset = getVariableOffset(temp);
            valueToStackOffset[curFunction][temp] = offset;
            output << "    movq " << reg << ", " << offset << "(%rbp)\n";
            valueLocations[curFunction][temp] = reg;
        }

        int getVariableOffset(const std::string &varName) {
            if (variableOffsets[curFunction].find(varName) == variableOffsets[curFunction].end()) {
                currentStackOffset[curFunction] -= 8;
                variableOffsets[curFunction][varName] = currentStackOffset[curFunction];
                //maxStackUsage[curFunction] = std::min(maxStackUsage[curFunction], currentStackOffset[curFunction]);

            }
            return variableOffsets[curFunction][varName];
        }

        std::string getOperand(const std::string &operand) {
            if (operand.empty() || operand[0] == '$' || operand[0] == '%') {
                return operand;
            }
            int offset = getVariableOffset(operand);
            return std::to_string(offset) + "(%rbp)";
        }

        std::string optimizeRedundantLoadStore(const std::string &assemblyCode) {
            std::istringstream input(assemblyCode);
            std::ostringstream output;
            std::string line;
            std::string lastStoreLine;
            std::string lastStoreLocation;
            std::string lastStoreRegister;
            bool lastWasStore = false;

            std::regex movqRegex(R"(movq\s+([^\s,]+)\s*,\s*([^\s]+))");

            while (std::getline(input, line)) {
                std::smatch match;
                if (std::regex_search(line, match, movqRegex)) {
                    std::string src = match[1];
                    std::string dest = match[2];

                    if (lastWasStore) {
                        if (src == lastStoreLocation && dest == lastStoreRegister) {
                            lastWasStore = false; 
                            continue;
                        }
                    }

                    if (dest.find("(%rbp)") != std::string::npos) {
                        lastStoreLine = line;
                        lastStoreLocation = dest;
                        lastStoreRegister = src;
                        lastWasStore = true;
                    } else {
                        lastWasStore = false;
                    }
                } else {
                    lastWasStore = false;
                }

                output << line << "\n";
            }
            return output.str();
        }

        std::string applyPeephole(std::ostringstream &output) {
            return optimizeRedundantLoadStore(output.str());
        }
    };

}

#endif