← index
MXVM/src/icode_gen.cpp
Source: MXVM/src/icode_gen.cpp
#include "mxvm/icode.hpp"

#include <algorithm>
#include <unordered_set>

namespace mxvm {

    static int error_label_count = 0;

    std::string Program::getPlatformSymbolName(const std::string &name) {
        if (platform == Platform::DARWIN) {
            if (!name.empty() && name[0] != '_')
                return "_" + name;
            else
                return name;
        }
        return name;
    }

    void Program::sysv_analyzeRegAlloc(bool uses_std_module) {
        sysv_reg_vars.clear();
        sysv_reg_save_order.clear();

        // %rbx is reserved for stack alignment scratch
        // %rsi, %rdi are caller-saved on System V (not available)
        std::vector<std::string> available_regs = {"%r14", "%r15"};
        if (!uses_std_module) {
            available_regs.push_back("%r12");
            available_regs.push_back("%r13");
        }

        std::unordered_map<std::string, int> usage_count;
        auto countOp = [&](const Operand &op) {
            if (!op.op.empty() && isVariable(op.op)) {
                Variable &v = getVariable(op.op);
                if (v.type == VarType::VAR_INTEGER && !v.is_global) {
                    usage_count[op.op]++;
                }
            }
        };

        for (const auto &instr : inc) {
            countOp(instr.op1);
            countOp(instr.op2);
            countOp(instr.op3);
            for (const auto &vop : instr.vop)
                countOp(vop);
        }

        std::vector<std::pair<std::string, int>> sorted_vars(usage_count.begin(), usage_count.end());
        std::sort(sorted_vars.begin(), sorted_vars.end(),
                  [](const auto &a, const auto &b) { return a.second > b.second; });

        size_t n = std::min(available_regs.size(), sorted_vars.size());
        for (size_t i = 0; i < n; ++i) {
            if (sorted_vars[i].second >= 2) {
                sysv_reg_vars[sorted_vars[i].first] = available_regs[i];
                sysv_reg_save_order.push_back(available_regs[i]);
            }
        }
    }

    void Program::sysv_emitFlushRegs(std::ostream &out) {
        for (const auto &[var, reg] : sysv_reg_vars) {
            out << "\tmovq " << reg << ", " << getMangledName(var) << "(%rip)\n";
        }
    }

    void Program::sysv_emitReloadRegs(std::ostream &out) {
        for (const auto &[var, reg] : sysv_reg_vars) {
            out << "\tmovq " << getMangledName(var) << "(%rip), " << reg << "\n";
        }
    }

    void Program::sysv_emitSaveRegs(std::ostream &out) {
        for (const auto &reg : sysv_reg_save_order) {
            out << "\tpushq " << reg << "\n";
        }
    }

    void Program::sysv_emitRestoreRegs(std::ostream &out) {
        for (auto it = sysv_reg_save_order.rbegin(); it != sysv_reg_save_order.rend(); ++it) {
            out << "\tpopq " << *it << "\n";
        }
    }

    void Program::sysv_emitStoreVar(std::ostream &out, const std::string &srcReg, const Operand &dest) {
        auto it = sysv_reg_vars.find(dest.op);
        if (it != sysv_reg_vars.end()) {
            if (srcReg != it->second)
                out << "\tmovq " << srcReg << ", " << it->second << "\n";
        } else {
            out << "\tmovq " << srcReg << ", " << getMangledName(dest) << "(%rip)\n";
        }
    }

    void Program::sysv_emitStoreVarImm(std::ostream &out, const std::string &imm, const Operand &dest) {
        auto it = sysv_reg_vars.find(dest.op);
        if (it != sysv_reg_vars.end()) {
            out << "\tmovq $" << imm << ", " << it->second << "\n";
        } else {
            out << "\tmovq $" << imm << ", " << getMangledName(dest) << "(%rip)\n";
        }
    }

    void Program::sysv_emitLoadVar(std::ostream &out, const std::string &dstReg, const Operand &src) {
        auto it = sysv_reg_vars.find(src.op);
        if (it != sysv_reg_vars.end()) {
            if (dstReg != it->second)
                out << "\tmovq " << it->second << ", " << dstReg << "\n";
        } else {
            out << "\tmovq " << getMangledName(src) << "(%rip), " << dstReg << "\n";
        }
    }

    void Program::generateCode(const Platform &platform, bool obj, std::ostream &out) {

        if (platform == Platform::WINX64) {
            x64_generateCode(platform, obj, out);
            return;
        }

        this->add_standard();
        std::unordered_map<int, std::string> labels_;
        for (auto &l : labels) {
            labels_[l.second.first] = l.first;
        }

        bool uses_std_module = false;
        for (const auto &e : external) {
            if (e.mod == "std") {
                uses_std_module = true;
                break;
            }
        }

        if (platform == Platform::DARWIN)
            out << ".section __DATA, __data\n";
        else
            out << ".section .data\n";

        std::vector<std::string> var_names;
        for (auto &v : vars) {
            var_names.push_back(v.first);
        }
        std::sort(var_names.begin(), var_names.end());

        for (auto &v : var_names) {
            const std::string var_out_name = getPlatformSymbolName(getMangledName(v));
            auto varx = getVariable(v);
            if (varx.type == VarType::VAR_INTEGER) {
                out << "\t" << var_out_name << ": .quad " << vars[v].var_value.int_value << "\n";
                continue;
            }
            if (varx.type == VarType::VAR_FLOAT) {
                out << "\t" << var_out_name << ": .double " << vars[v].var_value.float_value << "\n";
                continue;
            }
            if (varx.type == VarType::VAR_BYTE) {
                out << "\t" << var_out_name << ": .byte " << vars[v].var_value.int_value << "\n";
            }
        }
        for (auto &v : var_names) {
            auto varx = getVariable(v);
        }

        if (platform == Platform::LINUX) {
            out << "\t.extern " << getPlatformSymbolName("stderr") << "\n";
            out << "\t.extern " << getPlatformSymbolName("stdin") << "\n";
            out << "\t.extern " << getPlatformSymbolName("stdout") << "\n";
        }

        for (auto &v : var_names) {
            auto varx = getVariable(v);
            if (varx.type == VarType::VAR_STRING && varx.var_value.buffer_size == 0) {
                const std::string var_out_name = getPlatformSymbolName(getMangledName(v));
                out << "\t" << var_out_name << ": .asciz " << "\"" << escapeNewLines(vars[v].var_value.str_value) << "\"\n";
                continue;
            }
        }

        if (base != nullptr) {

            for (auto &v : var_names) {
                auto varx = getVariable(v);
                auto t = varx.type;
                if (varx.is_global) {
                    switch (t) {
                    case VarType::VAR_BYTE:
                    case VarType::VAR_FLOAT:
                    case VarType::VAR_INTEGER:
                        out << "\t.global " << getMangledName(v) << "\n";
                        break;
                    default:
                        break;
                    }
                }
            }
        }

        if (platform == Platform::DARWIN)
            out << ".section __DATA,__bss\n";
        else
            out << ".section .bss\n";

        for (auto &v : var_names) {
            const std::string var_out_name = getPlatformSymbolName(getMangledName(v));
            auto varx = getVariable(v);
            if (varx.type == VarType::VAR_POINTER) {
                out << "\t.comm " << var_out_name << ", 8\n";
            } else if (varx.type == VarType::VAR_STRING && varx.var_value.buffer_size > 0) {
                out << "\t.comm " << var_out_name << ", " << varx.var_value.buffer_size << "\n";
            }
        }
        if (object) {
            for (auto &v : var_names) {
                auto varx = getVariable(v);
                if (varx.is_global && (varx.type == VarType::VAR_STRING || varx.type == VarType::VAR_POINTER)) {
                    out << "\t.global " << getMangledName(v) << "\n";
                }
            }
        }
        if (platform == Platform::DARWIN)
            out << ".section __TEXT, __text\n";
        else
            out << ".section .text\n";

        for (auto &lbl : labels) {
            if (lbl.second.second == true) {
                out << "\t.global " << name + "_" + lbl.first << "\n";
            }
        }
        std::sort(external.begin(), external.end(), [](const ExternalFunction &a, const ExternalFunction &b) {
            if (a.mod == b.mod)
                return a.name < b.name;
            return a.mod < b.mod;
        });

        for (auto &e : external) {
            if (e.module == true)
                out << "\t.extern " << getPlatformSymbolName(e.name) << "\n";
            else
                out << "\t.extern " << getPlatformSymbolName(e.mod + "_" + e.name) << "\n";
        }

        sysv_analyzeRegAlloc(uses_std_module);

        if (!this->object) {
            out << "\t.p2align 4, 0x90\n";
            out << ".global " << "main" << "\n";
            out << "main" << ":\n";
            out << "\tpush %rbp\n";
            out << "\tmov %rsp, %rbp\n";
            out << "\tpush %rbx\n";
            if (uses_std_module) {
                out << "\tpush %r12\n";
                out << "\tpush %r13\n";
                out << "\tsub $8, %rsp\n";
                out << "\tmov %rdi, %r12\n";
                out << "\tmov %rsi, %r13\n";
                out << "\tmovq %rsp, %rbx\n";
                out << "\tandq $-16, %rsp\n";
                out << "\tcall " << getPlatformSymbolName("set_program_args") << "\n";
                out << "\tmovq %rbx, %rsp\n";
            } else {
                out << "\tsub $8, %rsp\n";
            }
            sysv_emitSaveRegs(out);
            sysv_emitReloadRegs(out);
        }

        bool done_found = false;

        for (size_t i = 0; i < inc.size(); ++i) {
            const Instruction &instr = inc[i];
            for (auto l : labels) {
                if (l.second.first == i && l.second.second) {
                    out << "\t.p2align 4, 0x90\n";
                    out << getPlatformSymbolName(name + "_" + l.first) << ":\n";
                    out << "\tpush %rbp\n";
                    out << "\tmov %rsp, %rbp\n";
                    out << "\tpush %rbx\n";
                    out << "\tsub $8, %rsp\n";
                    sysv_emitSaveRegs(out);
                    sysv_emitReloadRegs(out);
                    if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
                        Operand rax_op;
                        rax_op.op = "rax";
                        sysv_emitStoreVar(out, "%rax", rax_op);
                    }
                    break;
                }
            }
            for (auto l : labels) {
                if (l.second.first == i && !l.second.second) {
                    out << "." << l.first << ":\n";
                }
            }
            if (instr.instruction == DONE)
                done_found = true;
            generateInstruction(out, instr);
        }

#ifndef __EMSCRIPTEN__
        if (this->object == false && done_found == false)
            throw mx::Exception("Program missing done to signal completion.\n");
#endif
        if (platform == Platform::LINUX)
            out << "\n\n\n.section .note.GNU-stack,\"\",@progbits\n\n";

        std::string mainFunc = " Object";
        if (root_name == name)
            mainFunc = " Program";
        std::cout << Col("MXVM: Compiled: ", mx::Color::BRIGHT_BLUE) << name << ".s" << mainFunc << Col(" platform: ", mx::Color::BRIGHT_CYAN) << ((platform == Platform::LINUX) ? "Linux" : "macOS") << "\n";
    }

    void Program::generateInstruction(std::ostream &out, const Instruction &i) {
        if (i.instruction < JMP || i.instruction > JNS) {
            last_cmp_type = CMP_NONE;
        }
        switch (i.instruction) {
        case ADD:
            gen_arth(out, "add", i);
            break;
        case SUB:
            gen_arth(out, "sub", i);
            break;
        case MUL:
            gen_arth(out, "mul", i);
            break;
        case PRINT:
            gen_print(out, i);
            break;
        case EXIT:
            gen_exit(out, i);
            break;
        case MOV:
            gen_mov(out, i);
            break;
        case JMP:
        case JE:
        case JNE:
        case JL:
        case JLE:
        case JG:
        case JGE:
        case JZ:
        case JNZ:
        case JA:
        case JB:
            gen_jmp(out, i);
            break;
        case CMP:
            gen_cmp(out, i);
            break;
        case ALLOC:
            gen_alloc(out, i);
            break;
        case FREE:
            gen_free(out, i);
            break;
        case LOAD:
            gen_load(out, i);
            break;
        case STORE:
            gen_store(out, i);
            break;
        case RET:
            gen_ret(out, i);
            break;
        case CALL:
            gen_call(out, i);
            break;
        case DONE:
            gen_done(out, i);
            break;
        case AND:
            gen_bitop(out, "and", i);
            break;
        case OR:
            gen_bitop(out, "or", i);
            break;
        case XOR:
            gen_bitop(out, "xor", i);
            break;
        case NOT:
            gen_not(out, i);
            break;
        case DIV:
            gen_div(out, i);
            break;
        case MOD:
            gen_mod(out, i);
            break;
        case PUSH:
            gen_push(out, i);
            break;
        case POP:
            gen_pop(out, i);
            break;
        case STACK_LOAD:
            gen_stack_load(out, i);
            break;
        case STACK_STORE:
            gen_stack_store(out, i);
            break;
        case STACK_SUB:
            gen_stack_sub(out, i);
            break;
        case GETLINE:
            gen_getline(out, i);
            break;
        case TO_INT:
            gen_to_int(out, i);
            break;
        case TO_FLOAT:
            gen_to_float(out, i);
            break;
        case INVOKE:
            gen_invoke(out, i);
            break;
        case RETURN:
            gen_return(out, i);
            break;
        case NEG:
            gen_neg(out, i);
            break;
        case FCMP:
            gen_fcmp(out, i);
            break;
        case JAE:
            gen_jae(out, i);
            break;
        case JBE:
            gen_jbe(out, i);
            break;
        case JC:
            gen_jc(out, i);
            break;
        case JNC:
            gen_jnc(out, i);
            break;
        case JP:
            gen_jp(out, i);
            break;
        case JNP:
            gen_jnp(out, i);
            break;
        case JO:
            gen_jo(out, i);
            break;
        case JNO:
            gen_jno(out, i);
            break;
        case JS:
            gen_js(out, i);
            break;
        case JNS:
            gen_jns(out, i);
            break;
        default:
            throw mx::Exception("Invalid or unsupported instruction: " + std::to_string(static_cast<unsigned int>(i.instruction)));
        }
    }
    void Program::gen_done(std::ostream &out, const Instruction &i) {
        bool uses_std_module = false;
        for (const auto &e : external) {
            if (e.mod == "std") {
                uses_std_module = true;
                break;
            }
        }

        if (uses_std_module && !this->object) {
            sysv_emitFlushRegs(out);
            out << "\tmovq %rsp, %rbx\n";
            out << "\tandq $-16, %rsp\n";
            out << "\tcall " << getPlatformSymbolName("free_program_args") << "\n";
            out << "\tmovq %rbx, %rsp\n";
        } else {
            sysv_emitFlushRegs(out);
        }

        out << "\txor %eax, %eax\n";
        sysv_emitRestoreRegs(out);
        if (!this->object && uses_std_module) {
            out << "\tmovq -24(%rbp), %r13\n";
            out << "\tmovq -16(%rbp), %r12\n";
        }
        out << "\tmovq -8(%rbp), %rbx\n";
        out << "\tleave\n";
        out << "\tret\n";
    }

    void Program::gen_ret(std::ostream &out, const Instruction &i) {
        sysv_emitFlushRegs(out);
        if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
            Operand rax_op;
            rax_op.op = "rax";
            sysv_emitLoadVar(out, "%rax", rax_op);
        }
        sysv_emitRestoreRegs(out);
        out << "\tmovq -8(%rbp), %rbx\n";
        out << "\tleave\n";
        out << "\tret\n";
    }

    void Program::gen_return(std::ostream &out, const Instruction &i) {
        if (isVariable(i.op1.op)) {
            Variable &v = getVariable(i.op1.op);
            if (this->last_call_returns_owned_ptr) {
                v.var_value.owns = true;
            }
            switch (v.type) {
            case VarType::VAR_FLOAT:
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                break;
            default:
                sysv_emitStoreVar(out, "%rax", i.op1);
            }

        } else {
            throw mx::Exception("return: " + i.op1.op + " requires variable\n");
        }
    }

    void Program::gen_neg(std::ostream &out, const Instruction &i) {
        if (isVariable(i.op1.op)) {
            Variable &v = getVariable(i.op1.op);
            if (v.type == VarType::VAR_INTEGER) {
                generateLoadVar(out, v.type, "%rcx", i.op1);
                out << "\tnegq %rcx\n";
                sysv_emitStoreVar(out, "%rcx", i.op1);
            } else if (v.type == VarType::VAR_FLOAT) {
                generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
                out << "\txorpd %xmm1, %xmm1\n";
                out << "\tsubsd %xmm0, %xmm1\n";
                out << "\tmovsd %xmm1, " << getMangledName(i.op1) << "(%rip)\n";
            } else {
                throw mx::Exception("neg requires float or integer.");
            }
        } else {
            throw mx::Exception("neg requires variable");
        }
    }

    void Program::gen_invoke(std::ostream &out, const Instruction &i) {
        if (i.op1.op.empty()) {
            throw mx::Exception("invoke instruction requires operand of instruction name");
        }
        std::vector<Operand> op;
        op.push_back(i.op1);
        if (!i.op2.op.empty())
            op.push_back(i.op2);
        if (!i.op3.op.empty())
            op.push_back(i.op3);
        for (const auto &vx : i.vop) {
            if (!vx.op.empty())
                op.push_back(vx);
        }
        generateInvokeCall(out, op);
    }

    void Program::generateInvokeCall(std::ostream &out, std::vector<Operand> &op) {
        if (op.empty() || op[0].op.empty()) {
            throw mx::Exception("invoke instruction requires operand of instruction name");
        }
        std::string name = op[0].op;
        std::vector<Operand> args;
        for (size_t i = 1; i < op.size(); ++i) {
            if (!op[i].op.empty())
                args.push_back(op[i]);
        }

        if (isFunctionReturningOwnedPtr(name)) {
            this->last_call_returns_owned_ptr = true;
        } else {
            this->last_call_returns_owned_ptr = false;
        }
        generateFunctionCall(out, name, args);
    }

    void Program::generateFunctionCall(std::ostream &out, const std::string &name, std::vector<Operand> &op) {
        const std::vector<Operand> &args = op;
        xmm_offset = 0;
        size_t stack_args = (args.size() > 6) ? (args.size() - 6) : 0;

        out << "\tmovq %rsp, %rbx\n";
        out << "\tandq $-16, %rsp\n";

        if (stack_args) {
            size_t stack_bytes = stack_args * 8;
            size_t aligned = (stack_bytes + 15) & ~(size_t)15;
            out << "\tsubq $" << aligned << ", %rsp\n";
            for (size_t idx = 6, slot = 0; idx < args.size(); ++idx, ++slot) {
                generateLoadVar(out, VarType::VAR_INTEGER, "%rax", args[idx]);
                out << "\tmovq %rax, " << (slot * 8) << "(%rsp)\n";
            }
        }

        int reg_count = 0;
        for (size_t z = 0; z < args.size() && reg_count < 6; ++z, ++reg_count) {
            generateLoadVar(out, reg_count, args[z]);
        }

        out << "\txor %eax, %eax\n";
        out << "\tcall " << getPlatformSymbolName(name) << "\n";
        out << "\tmovq %rbx, %rsp\n";
    }

    void Program::gen_call(std::ostream &out, const Instruction &i) {
        if (isFunctionValid(i.op1.op)) {
            sysv_emitFlushRegs(out);
            if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
                Operand rax_op;
                rax_op.op = "rax";
                sysv_emitLoadVar(out, "%rax", rax_op);
            }
            out << "\tmovq %rsp, %rbx\n";
            out << "\tandq $-16, %rsp\n";
            out << "\tcall " << getPlatformSymbolName(getMangledName(i.op1)) << "\n";
            out << "\tmovq %rbx, %rsp\n";
            if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
                Operand rax_op;
                rax_op.op = "rax";
                out << "\tmovq %rax, " << getMangledName(rax_op) << "(%rip)\n";
            }
            sysv_emitReloadRegs(out);
        } else {
            throw mx::Exception("Function " + i.op1.op + " not found!");
        }
    }

    void Program::gen_alloc(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("ALLOC destination must be a variable");
        }

        Variable &v = getVariable(i.op1.op);
        if (v.type != VarType::VAR_POINTER) {
            throw mx::Exception("ALLOC destination must be a pointer\n");
        }

        if (!i.op2.op.empty()) {
            if (isVariable(i.op2.op)) {
                sysv_emitLoadVar(out, "%rsi", i.op2);
            } else {
                out << "\tmovq $" << i.op2.op << ", %rsi\n";
            }
        } else {
            out << "\tmovq $8, %rsi\n";
        }

        if (!i.op3.op.empty()) {
            if (isVariable(i.op3.op)) {
                sysv_emitLoadVar(out, "%rdi", i.op3);
            } else if (i.op3.type == OperandType::OP_CONSTANT) {
                out << "\tmovq $" << i.op3.op << ", %rdi\n";
            } else {
                throw mx::Exception("ALLOC: invalid count operand " + i.op3.op);
            }
        } else {
            out << "\tmovq $1, %rdi\n";
        }
        out << "\tmovq %rsp, %rbx\n";
        out << "\tandq $-16, %rsp\n";
        out << "\tcall " << getPlatformSymbolName("calloc") << "\n";
        out << "\tmovq %rbx, %rsp\n";
        out << "\ttest %rax, %rax\n";
        out << "\tjz .alloc_failed_" << error_label_count << "\n";
        out << "\tmovq %rax, " << getMangledName(i.op1) << "(%rip)\n";
        out << "\tjmp .alloc_done_" << error_label_count << "\n";
        out << ".alloc_failed_" << error_label_count << ":\n";
        out << "\tmovq $0, " << getMangledName(i.op1) << "(%rip)\n";
        out << ".alloc_done_" << error_label_count << ":\n";
        error_label_count++;
        v.var_value.owns = true;
    }

    void Program::gen_load(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("LOAD destination: " + i.op1.op + " must be a variable");
        }
        Variable &dest = getVariable(i.op1.op);

        if (!isVariable(i.op2.op)) {
            throw mx::Exception("LOAD source: " + i.op2.op + " must be a pointer variable");
        }
        Variable &ptrVar = getVariable(i.op2.op);

        if (ptrVar.type == VarType::VAR_POINTER) {
            out << "\tmovq " << getMangledName(i.op2) << "(%rip), %rax\n";
        } else if (ptrVar.type == VarType::VAR_STRING) {
            out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rax\n";
        } else {
            throw mx::Exception("LOAD source must be a pointer or string buffer: " + i.op2.op);
        }

        if (!i.op3.op.empty()) {
            if (isVariable(i.op3.op)) {
                sysv_emitLoadVar(out, "%rcx", i.op3);
            } else {
                out << "\tmovq $" << i.op3.op << ", %rcx\n";
            }
        } else {
            out << "\txorq %rcx, %rcx\n";
        }

        if (!i.vop.empty() && !i.vop[0].op.empty()) {
            if (isVariable(i.vop[0].op)) {
                sysv_emitLoadVar(out, "%rdx", i.vop[0]);
                out << "\timulq %rdx, %rcx\n";
                out << "\taddq %rcx, %rax\n";

                switch (dest.type) {
                case VarType::VAR_INTEGER:
                case VarType::VAR_POINTER:
                case VarType::VAR_EXTERN:
                    out << "\tmovq (%rax), %rdx\n";
                    sysv_emitStoreVar(out, "%rdx", i.op1);
                    break;
                case VarType::VAR_FLOAT:
                    out << "\tmovsd (%rax), %xmm0\n";
                    out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                    break;
                case VarType::VAR_BYTE:
                    out << "\tmovzbq (%rax), %rdx\n";
                    out << "\tmovq %rdx, " << getMangledName(i.op1) << "(%rip)\n";
                    break;
                default:
                    throw mx::Exception("LOAD: unsupported destination type");
                }
            } else {
                size_t stride = static_cast<size_t>(std::stoll(i.vop[0].op, nullptr, 0));

                if (stride == 1 || stride == 2 || stride == 4 || stride == 8) {
                    switch (dest.type) {
                    case VarType::VAR_INTEGER:
                    case VarType::VAR_POINTER:
                    case VarType::VAR_EXTERN:
                        out << "\tmovq (%rax,%rcx," << stride << "), %rdx\n";
                        sysv_emitStoreVar(out, "%rdx", i.op1);
                        break;
                    case VarType::VAR_FLOAT:
                        out << "\tmovsd (%rax,%rcx," << stride << "), %xmm0\n";
                        out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                        break;
                    case VarType::VAR_BYTE:
                        out << "\tmovzbq (%rax,%rcx," << stride << "), %rdx\n";
                        out << "\tmovq %rdx, " << getMangledName(i.op1) << "(%rip)\n";
                        break;
                    default:
                        throw mx::Exception("LOAD: unsupported destination type");
                    }
                } else {
                    out << "\timulq $" << stride << ", %rcx\n";
                    out << "\taddq %rcx, %rax\n";

                    switch (dest.type) {
                    case VarType::VAR_INTEGER:
                    case VarType::VAR_POINTER:
                    case VarType::VAR_EXTERN:
                        out << "\tmovq (%rax), %rdx\n";
                        sysv_emitStoreVar(out, "%rdx", i.op1);
                        break;
                    case VarType::VAR_FLOAT:
                        out << "\tmovsd (%rax), %xmm0\n";
                        out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                        break;
                    case VarType::VAR_BYTE:
                        out << "\tmovzbq (%rax), %rdx\n";
                        out << "\tmovq %rdx, " << getMangledName(i.op1) << "(%rip)\n";
                        break;
                    default:
                        throw mx::Exception("LOAD: unsupported destination type");
                    }
                }
            }
        } else {
            switch (dest.type) {
            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN:
                out << "\tmovq (%rax,%rcx,8), %rdx\n";
                sysv_emitStoreVar(out, "%rdx", i.op1);
                break;
            case VarType::VAR_FLOAT:
                out << "\tmovsd (%rax,%rcx,8), %xmm0\n";
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                break;
            case VarType::VAR_BYTE:
                out << "\tmovzbq (%rax,%rcx,8), %rdx\n";
                out << "\tmovq %rdx, " << getMangledName(i.op1) << "(%rip)\n";
                break;
            default:
                throw mx::Exception("LOAD: unsupported destination type");
            }
        }
    }

    void Program::gen_store(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op2.op)) {
            throw mx::Exception("STORE destination must be a pointer variable: " + i.op2.op);
        }
        Variable &ptrVar = getVariable(i.op2.op);

        if (ptrVar.type == VarType::VAR_POINTER) {
            out << "\tmovq " << getMangledName(i.op2) << "(%rip), %rax\n";
        } else if (ptrVar.type == VarType::VAR_STRING && ptrVar.var_value.buffer_size > 0) {
            out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rax\n";
        } else {
            throw mx::Exception("STORE must target pointer or string buffer");
        }

        if (!i.op3.op.empty()) {
            if (isVariable(i.op3.op)) {
                sysv_emitLoadVar(out, "%rcx", i.op3);
            } else {
                out << "\tmovq $" << i.op3.op << ", %rcx\n";
            }
        } else {
            out << "\txorq %rcx, %rcx\n";
        }

        if (isVariable(i.op1.op)) {
            Variable &src = getVariable(i.op1.op);
            switch (src.type) {
            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN:
                sysv_emitLoadVar(out, "%rdx", i.op1);
                break;
            case VarType::VAR_STRING:
                out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rdx\n";
                break;
            case VarType::VAR_BYTE:
                out << "\tmovzbq " << getMangledName(i.op1) << "(%rip), %rdx\n";
                break;
            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getMangledName(i.op1) << "(%rip), %xmm0\n";
                break;
            default:
                throw mx::Exception("STORE: unsupported source type");
            }
        } else {
            out << "\tmovq $" << i.op1.op << ", %rdx\n";
        }

        if (!i.vop.empty() && !i.vop[0].op.empty()) {
            if (isVariable(i.vop[0].op)) {
                sysv_emitLoadVar(out, "%r8", i.vop[0]);
                out << "\timulq %r8, %rcx\n";
                out << "\taddq %rcx, %rax\n";

                if (isVariable(i.op1.op)) {
                    Variable &src = getVariable(i.op1.op);
                    if (src.type == VarType::VAR_FLOAT) {
                        out << "\tmovsd %xmm0, (%rax)\n";
                    } else if (src.type == VarType::VAR_BYTE) {
                        out << "\tmovb %dl, (%rax)\n";
                    } else {
                        out << "\tmovq %rdx, (%rax)\n";
                    }
                } else {
                    out << "\tmovq %rdx, (%rax)\n";
                }
            } else {
                size_t stride = static_cast<size_t>(std::stoll(i.vop[0].op, nullptr, 0));

                if (stride == 1 || stride == 2 || stride == 4 || stride == 8) {
                    if (isVariable(i.op1.op)) {
                        Variable &src = getVariable(i.op1.op);
                        if (src.type == VarType::VAR_FLOAT) {
                            out << "\tmovsd %xmm0, (%rax,%rcx," << stride << ")\n";
                        } else if (src.type == VarType::VAR_BYTE) {
                            out << "\tmovb %dl, (%rax,%rcx," << stride << ")\n";
                        } else {
                            out << "\tmovq %rdx, (%rax,%rcx," << stride << ")\n";
                        }
                    } else {
                        out << "\tmovq %rdx, (%rax,%rcx," << stride << ")\n";
                    }
                } else {
                    out << "\timulq $" << stride << ", %rcx\n";
                    out << "\taddq %rcx, %rax\n";

                    if (isVariable(i.op1.op)) {
                        Variable &src = getVariable(i.op1.op);
                        if (src.type == VarType::VAR_FLOAT) {
                            out << "\tmovsd %xmm0, (%rax)\n";
                        } else if (src.type == VarType::VAR_BYTE) {
                            out << "\tmovb %dl, (%rax)\n";
                        } else {
                            out << "\tmovq %rdx, (%rax)\n";
                        }
                    } else {
                        out << "\tmovq %rdx, (%rax)\n";
                    }
                }
            }
        } else {
            if (isVariable(i.op1.op)) {
                Variable &src = getVariable(i.op1.op);
                if (src.type == VarType::VAR_FLOAT) {
                    out << "\tmovsd %xmm0, (%rax,%rcx,8)\n";
                } else if (src.type == VarType::VAR_BYTE) {
                    out << "\tmovb %dl, (%rax,%rcx,8)\n";
                } else {
                    out << "\tmovq %rdx, (%rax,%rcx,8)\n";
                }
            } else {
                out << "\tmovq %rdx, (%rax,%rcx,8)\n";
            }
        }
    }

    void Program::gen_free(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("FREE argument must be a variable");
        }
        Variable &v = getVariable(i.op1.op);
        if (v.type != VarType::VAR_POINTER) {
            throw mx::Exception("FREE argument must be a pointer");
        }
        if (v.var_value.owns != true) {
            throw mx::Exception("FREE argument must be owned by program");
        }

        out << "\tmovq " << getMangledName(i.op1) << "(%rip), %rdi\n";
        out << "\ttest %rdi, %rdi\n";
        out << "\tjz 1f\n";
        out << "\tmovq %rsp, %rbx\n";
        out << "\tandq $-16, %rsp\n";
        out << "\tcall " << getPlatformSymbolName("free") << "\n";
        out << "\tmovq %rbx, %rsp\n";
        out << "\tmovq $0, " << getMangledName(i.op1) << "(%rip)\n";
        out << "1:\n";
        v.var_value.owns = false;
    }

    void Program::gen_to_int(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("to_int requires destination variable (dest int)");
        }
        if (i.op2.op.empty()) {
            throw mx::Exception("to_int requires source operand");
        }

        Variable &dest = getVariable(i.op1.op);
        if (dest.type != VarType::VAR_INTEGER) {
            throw mx::Exception("to_int: first argument must be an integer variable");
        }

        if (isVariable(i.op2.op)) {
            Variable &src = getVariable(i.op2.op);
            switch (src.type) {
            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN: {
                auto ra_src = sysv_reg_vars.find(i.op2.op);
                if (ra_src != sysv_reg_vars.end()) {
                    out << "\tmovq " << ra_src->second << ", %rax\n";
                } else {
                    out << "\tmovq " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %rax\n";
                }
                sysv_emitStoreVar(out, "%rax", i.op1);
                break;
            }

            case VarType::VAR_BYTE:
                out << "\tmovzbq " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %rax\n";
                sysv_emitStoreVar(out, "%rax", i.op1);
                break;

            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %xmm0\n";
                out << "\tcvttsd2si %xmm0, %rax\n";
                sysv_emitStoreVar(out, "%rax", i.op1);
                break;

            case VarType::VAR_STRING:
                out << "\tleaq " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %rdi\n";
                out << "\tmovq %rsp, %rbx\n";
                out << "\tandq $-16, %rsp\n";
                out << "\tcall " << getPlatformSymbolName("atol") << "\n";
                out << "\tmovq %rbx, %rsp\n";
                sysv_emitStoreVar(out, "%rax", i.op1);
                break;

            default:
                throw mx::Exception("to_int: unsupported source variable type");
            }
        } else if (i.op2.type == OperandType::OP_CONSTANT) {
            out << "\tmovq $" << i.op2.op << ", %rax\n";
            sysv_emitStoreVar(out, "%rax", i.op1);
        } else {
            throw mx::Exception("to_int: unsupported source operand");
        }
    }
    void Program::gen_to_float(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("to_float requires destination variable (dest float)");
        }
        if (i.op2.op.empty()) {
            throw mx::Exception("to_float requires source operand");
        }

        Variable &dest = getVariable(i.op1.op);
        if (dest.type != VarType::VAR_FLOAT) {
            throw mx::Exception("to_float: first argument must be a float variable");
        }

        if (isVariable(i.op2.op)) {
            Variable &src = getVariable(i.op2.op);
            switch (src.type) {
            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %xmm0\n";
                out << "\tmovsd %xmm0, " << getPlatformSymbolName(getMangledName(i.op1)) << "(%rip)\n";
                break;

            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN: {
                auto ra_src = sysv_reg_vars.find(i.op2.op);
                if (ra_src != sysv_reg_vars.end()) {
                    out << "\tmovq " << ra_src->second << ", %rax\n";
                } else {
                    out << "\tmovq " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %rax\n";
                }
                out << "\tcvtsi2sd %rax, %xmm0\n";
                out << "\tmovsd %xmm0, " << getPlatformSymbolName(getMangledName(i.op1)) << "(%rip)\n";
                break;
            }

            case VarType::VAR_BYTE:
                out << "\tmovzbq " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %rax\n";
                out << "\tcvtsi2sd %rax, %xmm0\n";
                out << "\tmovsd %xmm0, " << getPlatformSymbolName(getMangledName(i.op1)) << "(%rip)\n";
                break;

            case VarType::VAR_STRING:
                out << "\tleaq " << getPlatformSymbolName(getMangledName(i.op2)) << "(%rip), %rdi\n";
                out << "\tmovq %rsp, %rbx\n";
                out << "\tandq $-16, %rsp\n";
                out << "\tcall " << getPlatformSymbolName("atof") << "\n";
                out << "\tmovq %rbx, %rsp\n";
                out << "\tmovsd %xmm0, " << getPlatformSymbolName(getMangledName(i.op1)) << "(%rip)\n";
                break;

            default:
                throw mx::Exception("to_float: unsupported source variable type");
            }
        } else if (i.op2.type == OperandType::OP_CONSTANT) {
            out << "\tmovq $" << i.op2.op << ", %rax\n";
            out << "\tcvtsi2sd %rax, %xmm0\n";
            out << "\tmovsd %xmm0, " << getPlatformSymbolName(getMangledName(i.op1)) << "(%rip)\n";
        } else {
            throw mx::Exception("to_float: unsupported source operand");
        }
    }

    void Program::gen_div(std::ostream &out, const Instruction &i) {
        if (i.op3.op.empty()) {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
                    out << "\tcmpq $0, %rcx\n";
                    out << "\tje 1f\n";
                    out << "\tcqto\n";
                    out << "\tidivq %rcx\n";
                    out << "\tjmp 2f\n";
                    out << "1:\n";
                    out << "\txor %eax, %eax\n";
                    out << "2:\n";
                    sysv_emitStoreVar(out, "%rax", i.op1);
                } else if (v.type == VarType::VAR_FLOAT) {
                    generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
                    generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op2);
                    out << "\txorpd %xmm2, %xmm2\n";
                    out << "\tucomisd %xmm2, %xmm1\n";
                    out << "\tje 1f\n";
                    out << "\tdivsd %xmm1, %xmm0\n";
                    out << "\tjmp 2f\n";
                    out << "1:\n";
                    out << "\txorpd %xmm0, %xmm0\n";
                    out << "2:\n";
                    out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    throw mx::Exception("DIV: unsupported variable type");
                }
            } else {
                throw mx::Exception("DIV: first argument must be a variable");
            }
        } else {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op3);
                    out << "\tcmpq $0, %rcx\n";
                    out << "\tje 1f\n";
                    out << "\tcqto\n";
                    out << "\tidivq %rcx\n";
                    out << "\tjmp 2f\n";
                    out << "1:\n";
                    out << "\txor %eax, %eax\n";
                    out << "2:\n";
                    sysv_emitStoreVar(out, "%rax", i.op1);
                } else if (v.type == VarType::VAR_FLOAT) {
                    generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op2);
                    generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op3);
                    out << "\txorpd %xmm2, %xmm2\n";
                    out << "\tucomisd %xmm2, %xmm1\n";
                    out << "\tje 1f\n";
                    out << "\tdivsd %xmm1, %xmm0\n";
                    out << "\tjmp 2f\n";
                    out << "1:\n";
                    out << "\txorpd %xmm0, %xmm0\n";
                    out << "2:\n";
                    out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    throw mx::Exception("DIV: unsupported variable type");
                }
            } else {
                throw mx::Exception("DIV: first argument must be a variable");
            }
        }
    }

    void Program::gen_mod(std::ostream &out, const Instruction &i) {
        if (i.op3.op.empty()) {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
                    out << "\tcmpq $0, %rcx\n";
                    out << "\tje 1f\n";
                    out << "\tcqto\n";
                    out << "\tidivq %rcx\n";
                    out << "\tjmp 2f\n";
                    out << "1:\n";
                    out << "\txor %edx, %edx\n";
                    out << "2:\n";
                    sysv_emitStoreVar(out, "%rdx", i.op1);
                } else {
                    throw mx::Exception("MOD only supports integer variables");
                }
            } else {
                throw mx::Exception("MOD: first argument must be a variable");
            }
        } else {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op3);
                    out << "\tcmpq $0, %rcx\n";
                    out << "\tje 1f\n";
                    out << "\tcqto\n";
                    out << "\tidivq %rcx\n";
                    out << "\tjmp 2f\n";
                    out << "1:\n";
                    out << "\txor %edx, %edx\n";
                    out << "2:\n";
                    sysv_emitStoreVar(out, "%rdx", i.op1);
                } else {
                    throw mx::Exception("MOD only supports integer variables");
                }
            } else {
                throw mx::Exception("MOD: first argument must be a variable");
            }
        }
    }

    void Program::gen_getline(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("GETLINE destination must be a variable");
        }
        Variable &dest = getVariable(i.op1.op);
        if (dest.type != VarType::VAR_STRING || dest.var_value.buffer_size == 0) {
            throw mx::Exception("GETLINE destination must be a string buffer variable");
        }
        static int over_count = 0;
        out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rdi\n";
        out << "\tmovq $" << dest.var_value.buffer_size << ", %rsi\n";
        out << "\tmovq " << getPlatformSymbolName("stdin") << "(%rip), %rdx\n";
        out << "\tmovq %rsp, %rbx\n";
        out << "\tandq $-16, %rsp\n";
        out << "\tcall " << getPlatformSymbolName("fgets") << "\n";
        out << "\tmovq %rbx, %rsp\n";
        out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rdi\n";
        out << "\tmovq %rsp, %rbx\n";
        out << "\tandq $-16, %rsp\n";
        out << "\tcall " << getPlatformSymbolName("strlen") << "\n";
        out << "\tmovq %rbx, %rsp\n";
        out << "\tmovq %rax, %rcx\n";
        out << "\tcmp $0, %rax\n";
        out << "\tje .over" << over_count << "\n";
        out << "\tsub $1, %rcx\n";
        out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rdi\n";
        out << "\tmovb $0, (%rdi, %rcx, 1)\n";
        out << ".over" << over_count << ": \n";
        over_count++;
    }

    void Program::gen_bitop(std::ostream &out, const std::string &opc, const Instruction &i) {
        if (i.op3.op.empty()) {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
                    out << "\t" << opc << "q %rcx, %rax\n";
                    sysv_emitStoreVar(out, "%rax", i.op1);
                } else {
                    throw mx::Exception("Bitwise operations only supported for integer variables");
                }
            } else {
                throw mx::Exception("First argument of bitop instruction must be a variable.");
            }
        } else {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
                    generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op3);
                    out << "\t" << opc << "q %rcx, %rax\n";
                    sysv_emitStoreVar(out, "%rax", i.op1);
                } else {
                    throw mx::Exception("Bitwise operations only supported for integer variables");
                }
            } else {
                throw mx::Exception("First argument of bitop instruction must be a variable.");
            }
        }
    }

    void Program::gen_not(std::ostream &out, const Instruction &i) {
        if (!i.op1.op.empty()) {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type != VarType::VAR_INTEGER) {
                    throw mx::Exception("NOT instruction expect integer variable");
                }
                generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                out << "\ttestq %rax, %rax\n";
                out << "\tsete %al\n";
                out << "\tmovzbq %al, %rax\n";
                sysv_emitStoreVar(out, "%rax", i.op1);
            } else {
                throw mx::Exception("NOT instruction requires variable");
            }
        } else {
            throw mx::Exception("NOT instruction requires operand");
        }
    }

    void Program::gen_push(std::ostream &out, const Instruction &i) {
        if (isVariable(i.op1.op)) {
            Variable &v = getVariable(i.op1.op);
            if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER || v.type == VarType::VAR_EXTERN) {
                sysv_emitLoadVar(out, "%rax", i.op1);
                out << "\tpushq %rax\n";
            } else if (v.type == VarType::VAR_STRING) {
                out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rax\n";
                out << "\tpushq %rax\n";
            } else {
                throw mx::Exception("PUSH only supports integer or pointer variables");
            }
        } else if (i.op1.type == OperandType::OP_CONSTANT) {
            out << "\tmovq $" << i.op1.op << ", %rax\n";
            out << "\tpushq %rax\n";
        } else {
            throw mx::Exception("PUSH argument must be a variable or integer constant");
        }
    }

    void Program::gen_pop(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("POP destination must be a variable");
        }
        Variable &v = getVariable(i.op1.op);
        if (v.type == VarType::VAR_INTEGER) {
            out << "\tpopq %rax\n";
            sysv_emitStoreVar(out, "%rax", i.op1);
        } else if (v.type == VarType::VAR_POINTER || v.type == VarType::VAR_EXTERN) {
            out << "\tpopq %rax\n";
            out << "\tmovq %rax, " << getMangledName(i.op1) << "(%rip)\n";
        } else {
            throw mx::Exception("POP only supports integer or pointer variables");
        }
    }

    void Program::gen_stack_load(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("stack_load, requires first argument to be a variable.");
        }
        Variable &dest = getVariable(i.op1.op);
        if (i.op2.op.empty()) {
            throw mx::Exception("stack_load, requires second argument");
        }
        generateLoadVar(out, dest.type, "%rax", i.op2);
        out << "\tmovq (%rsp, %rax, 8), " << "%rcx\n";
        sysv_emitStoreVar(out, "%rcx", i.op1);
    }

    void Program::gen_stack_store(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("stack_store, requires first argument to be a variable.");
        }
        Variable &dest = getVariable(i.op1.op);
        if (i.op2.op.empty()) {
            throw mx::Exception("stack_store, requires second argument");
        }
        generateLoadVar(out, dest.type, "%rax", i.op2);
        sysv_emitLoadVar(out, "%rcx", i.op1);
        out << "\tmovq %rcx, (%rsp, %rax, 8)\n";
    }

    void Program::gen_stack_sub(std::ostream &out, const Instruction &i) {
        if (!i.op1.op.empty()) {
            if (isVariable(i.op1.op)) {
                sysv_emitLoadVar(out, "%rcx", i.op1);
            } else {
                out << "\tmovq $" << i.op1.op << ", %rcx\n";
            }
        } else {
            out << "\tmovq $1, %rcx\n";
        }
        out << "\tshl $3, %rcx\n";
        out << "\taddq %rcx, %rsp\n";
    }

    int Program::generateLoadVar(std::ostream &out, int r, const Operand &op) {
        int count = 0;

        if (isVariable(op.op)) {
            if (op.type != OperandType::OP_VARIABLE) {
                throw mx::Exception("Operand expected variable instead I found: " + op.op);
            }
            Variable &v = getVariable(op.op);

            std::string reg = getRegisterByIndex(r, v.type);
            switch (v.type) {
            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN: {
                auto ra = sysv_reg_vars.find(op.op);
                if (ra != sysv_reg_vars.end()) {
                    if (ra->second != reg)
                        out << "\tmovq " << ra->second << ", " << reg << "\n";
                } else {
                    out << "\tmovq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                }
                count = 0;
                break;
            }
            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                count = 1;
                break;
            case VarType::VAR_STRING:
                count = 0;
                out << "\tleaq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                break;
            case VarType::VAR_BYTE:
                count = 0;
                out << "\tmovzbq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                break;
            default:
                break;
            }
        } else {
            if (op.type == OperandType::OP_CONSTANT) {
                out << "\tmovq $" << op.op << ", " << getRegisterByIndex(r, VarType::VAR_INTEGER) << "\n";
            }
        }
        return count;
    }

    int Program::generateLoadVar(std::ostream &out, VarType reqType, std::string reg, const Operand &op) {
        int count = 0;
        if (isVariable(op.op)) {
            if (op.type != OperandType::OP_VARIABLE) {
                throw mx::Exception("Operand expected variable instead I found: " + op.op);
            }
            Variable &v = getVariable(op.op);
            if (reqType == VarType::VAR_FLOAT) {
                if (v.type == VarType::VAR_FLOAT) {
                    out << "\tmovsd " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                    count = 1;
                } else if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER || v.type == VarType::VAR_EXTERN) {
                    auto ra = sysv_reg_vars.find(op.op);
                    if (ra != sysv_reg_vars.end()) {
                        out << "\tmovq " << ra->second << ", %rax\n";
                    } else {
                        out << "\tmovq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), %rax\n";
                    }
                    out << "\tcvtsi2sd %rax, " << reg << "\n";
                    count = 1;
                } else if (v.type == VarType::VAR_BYTE) {
                    out << "\tmovzbq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), %rax\n";
                    out << "\tcvtsi2sd %rax, " << reg << "\n";
                    count = 1;
                } else if (v.type == VarType::VAR_STRING) {
                    out << "\tleaq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), %rdi\n";
                    out << "\tmovq %rsp, %rbx\n";
                    out << "\tandq $-16, %rsp\n";
                    out << "\tcall " << getPlatformSymbolName("atof") << "\n";
                    out << "\tmovq %rbx, %rsp\n";
                    out << "\tmovsd %xmm0, " << reg << "\n";
                    count = 1;
                } else {
                    throw mx::Exception("LoadVar: unsupported source type for float");
                }
                return count;
            }

            if (reqType == VarType::VAR_INTEGER || reqType == VarType::VAR_POINTER || reqType == VarType::VAR_EXTERN) {
                if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER || v.type == VarType::VAR_EXTERN) {
                    auto ra = sysv_reg_vars.find(op.op);
                    if (ra != sysv_reg_vars.end()) {
                        if (ra->second != reg)
                            out << "\tmovq " << ra->second << ", " << reg << "\n";
                    } else {
                        out << "\tmovq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                    }
                } else if (v.type == VarType::VAR_BYTE) {
                    out << "\tmovzbq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                } else if (v.type == VarType::VAR_FLOAT) {
                    out << "\tmovsd " << getPlatformSymbolName(getMangledName(op)) << "(%rip), %xmm0\n";
                    out << "\tcvttsd2si %xmm0, " << reg << "\n";
                } else if (v.type == VarType::VAR_STRING) {
                    out << "\tleaq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), %rdi\n";
                    out << "\tmovq %rsp, %rbx\n";
                    out << "\tandq $-16, %rsp\n";
                    out << "\tcall " << getPlatformSymbolName("atol") << "\n";
                    out << "\tmovq %rbx, %rsp\n";
                    out << "\tmovq %rax, " << reg << "\n";
                } else {
                    throw mx::Exception("LoadVar: unsupported source type for integer");
                }
                return count;
            }

            if (reqType == VarType::VAR_STRING) {
                if (v.type == VarType::VAR_STRING) {
                    out << "\tleaq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                } else if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER || v.type == VarType::VAR_EXTERN) {
                    auto ra = sysv_reg_vars.find(op.op);
                    if (ra != sysv_reg_vars.end()) {
                        if (ra->second != reg)
                            out << "\tmovq " << ra->second << ", " << reg << "\n";
                    } else {
                        out << "\tmovq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                    }
                } else {
                    throw mx::Exception("LoadVar: unsupported source type for string");
                }
                return count;
            }

            switch (v.type) {
            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN: {
                auto ra = sysv_reg_vars.find(op.op);
                if (ra != sysv_reg_vars.end()) {
                    if (ra->second != reg)
                        out << "\tmovq " << ra->second << ", " << reg << "\n";
                } else {
                    out << "\tmovq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                }
                count = 0;
                break;
            }
            case VarType::VAR_BYTE:
                out << "\tmovzbq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                break;
            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                count = 1;
                break;
            case VarType::VAR_STRING:
                out << "\tleaq " << getPlatformSymbolName(getMangledName(op)) << "(%rip), " << reg << "\n";
                break;
            default:
                break;
            }
        } else {
            if (op.type == OperandType::OP_CONSTANT) {
                if (reqType == VarType::VAR_INTEGER || reqType == VarType::VAR_POINTER || reqType == VarType::VAR_EXTERN) {
                    out << "\tmovq $" << op.op << ", " << reg << "\n";
                } else if (reqType == VarType::VAR_FLOAT) {
                    out << "\tmovq $" << op.op << ", %rax\n";
                    out << "\tcvtsi2sd %rax, " << reg << "\n";
                    count = 1;
                } else if (reqType == VarType::VAR_STRING) {
                    out << "\tmovq $" << op.op << ", " << reg << "\n";
                } else {
                    out << "\tmovq $" << op.op << ", " << reg << "\n";
                }
            }
        }
        return count;
    }

    std::string Program::getRegisterByIndex(int index, VarType type) {
        if (index < 6 && (type == VarType::VAR_INTEGER || type == VarType::VAR_STRING || type == VarType::VAR_POINTER || type == VarType::VAR_EXTERN || type == VarType::VAR_BYTE)) {
            static const char *reg[] = {"%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9"};
            return reg[index];
        } else if (index < 9 && type == VarType::VAR_FLOAT) {
            return "%xmm" + std::to_string(xmm_offset++);
        }
        return "[stack]";
    }

    void Program::gen_print(std::ostream &out, const Instruction &i) {
        xmm_offset = 0;
        out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rdi\n";
        std::vector<Operand> args;
        if (!i.op2.op.empty())
            args.push_back(i.op2);
        if (!i.op3.op.empty())
            args.push_back(i.op3);
        for (const auto &v : i.vop) {
            if (!v.op.empty())
                args.push_back(v);
        }

        int total = 0;
        const size_t stack_args = (args.size() > 5) ? (args.size() - 5) : 0;

        out << "\tmovq %rsp, %rbx\n";
        out << "\tandq $-16, %rsp\n";

        if (stack_args) {
            size_t stack_bytes = stack_args * 8;
            size_t aligned = (stack_bytes + 15) & ~(size_t)15;
            out << "\tsubq $" << aligned << ", %rsp\n";
            size_t slot = 0;
            for (size_t idx = 6; idx <= args.size(); ++idx, ++slot) {
                size_t aidx = idx - 1;
                if (isVariable(args[aidx].op)) {
                    Variable &v = getVariable(args[aidx].op);
                    if (v.type == VarType::VAR_FLOAT) {
                        generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", args[aidx]);
                        out << "\tmovsd %xmm0, " << (slot * 8) << "(%rsp)\n";
                    } else {
                        generateLoadVar(out, v.type, "%rax", args[aidx]);
                        out << "\tmovq %rax, " << (slot * 8) << "(%rsp)\n";
                    }
                } else {
                    out << "\tmovq $" << args[aidx].op << ", " << (slot * 8) << "(%rsp)\n";
                }
            }
        }

        int reg_count = 1;
        for (size_t z = 0; z < args.size() && reg_count < 6; ++z, ++reg_count) {
            total += generateLoadVar(out, reg_count, args[z]);
        }
        if (total == 0) {
            out << "\txor %eax, %eax\n";
        } else {
            out << "\tmovb $" << total << ", %al\n";
        }
        out << "\tcall " << getPlatformSymbolName("printf") << "\n";
        out << "\tmovq %rbx, %rsp\n";
    }

    void Program::gen_jmp(std::ostream &out, const Instruction &i) {
        if (!i.op1.op.empty()) {
            auto pos = labels.find(i.op1.op);
            if (pos == labels.end()) {
                throw mx::Exception("Jump instruction must have valid label: " + i.op1.op);
            }
            const char *mnem = nullptr;
            switch (i.instruction) {
            case JMP:
                mnem = "jmp";
                break;
            case JE:
                mnem = "je";
                break;
            case JNE:
                mnem = "jne";
                break;
            case JZ:
                mnem = "jz";
                break;
            case JNZ:
                mnem = "jnz";
                break;

            case JL:
                mnem = (last_cmp_type == CMP_FLOAT) ? "jb" : "jl";
                break;
            case JLE:
                mnem = (last_cmp_type == CMP_FLOAT) ? "jbe" : "jle";
                break;
            case JG:
                mnem = (last_cmp_type == CMP_FLOAT) ? "ja" : "jg";
                break;
            case JGE:
                mnem = (last_cmp_type == CMP_FLOAT) ? "jae" : "jge";
                break;
            case JA:
                mnem = "ja";
                break;
            case JB:
                mnem = "jb";
                break;
            default:
                throw mx::Exception("Invalid jump opcode for gen_jmp");
            }
            out << "\t" << mnem << " ." << i.op1.op << "\n";
        } else {
            throw mx::Exception("Jump instruction must have label: " + i.op1.op);
        }
    }

    void Program::gen_cmp(std::ostream &out, const Instruction &i) {
        if (i.op2.op.empty()) {
            throw mx::Exception("CMP requires two operands");
        }

        VarType type1 = VarType::VAR_INTEGER;
        VarType type2 = VarType::VAR_INTEGER;
        if (isVariable(i.op1.op)) {
            type1 = getVariable(i.op1.op).type;
        }
        if (isVariable(i.op2.op)) {
            type2 = getVariable(i.op2.op).type;
        }

        if (type1 == VarType::VAR_FLOAT || type2 == VarType::VAR_FLOAT) {
            generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
            generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op2);
            out << "\tcomisd %xmm1, %xmm0\n";
            last_cmp_type = CMP_FLOAT;

        } else if (type1 == VarType::VAR_POINTER && (type2 == VarType::VAR_INTEGER || type2 == VarType::VAR_BYTE)) {
            generateLoadVar(out, type1, "%rax", i.op1);
            generateLoadVar(out, type2, "%rcx", i.op2);
            out << "\tcmpq %rcx, %rax\n";
            last_cmp_type = CMP_INTEGER;
        } else if (!isVariable(i.op2.op) && i.op2.type == OperandType::OP_CONSTANT && isVariable(i.op1.op)) {
            auto ra = sysv_reg_vars.find(i.op1.op);
            if (ra != sysv_reg_vars.end()) {
                out << "\tcmpq $" << i.op2.op << ", " << ra->second << "\n";
            } else {
                out << "\tcmpq $" << i.op2.op << ", " << getMangledName(i.op1) << "(%rip)\n";
            }
            last_cmp_type = CMP_INTEGER;
        } else {
            generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
            generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
            out << "\tcmpq %rcx, %rax\n";
            last_cmp_type = CMP_INTEGER;
        }
    }

    void Program::gen_fcmp(std::ostream &out, const Instruction &i) {
        if (i.op2.op.empty()) {
            throw mx::Exception("FCMP requires two operands");
        }

        generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
        generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op2);
        out << "\tcomisd %xmm1, %xmm0\n";
        last_cmp_type = CMP_FLOAT;
    }

    void Program::gen_jae(std::ostream &out, const Instruction &i) {
        out << "\tjae ." << i.op1.op << "\n";
    }

    void Program::gen_jbe(std::ostream &out, const Instruction &i) {
        out << "\tjbe ." << i.op1.op << "\n";
    }

    void Program::gen_jc(std::ostream &out, const Instruction &i) {
        out << "\tjc ." << i.op1.op << "\n";
    }

    void Program::gen_jnc(std::ostream &out, const Instruction &i) {
        out << "\tjnc ." << i.op1.op << "\n";
    }

    void Program::gen_jp(std::ostream &out, const Instruction &i) {
        out << "\tjp ." << i.op1.op << "\n";
    }

    void Program::gen_jnp(std::ostream &out, const Instruction &i) {
        out << "\tjnp ." << i.op1.op << "\n";
    }

    void Program::gen_jo(std::ostream &out, const Instruction &i) {
        out << "\tjo ." << i.op1.op << "\n";
    }

    void Program::gen_jno(std::ostream &out, const Instruction &i) {
        out << "\tjno ." << i.op1.op << "\n";
    }

    void Program::gen_js(std::ostream &out, const Instruction &i) {
        out << "\tjs ." << i.op1.op << "\n";
    }

    void Program::gen_jns(std::ostream &out, const Instruction &i) {
        out << "\tjns ." << i.op1.op << "\n";
    }

    void Program::gen_mov(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op)) {
            throw mx::Exception("MOV destination must be a variable: " + i.op1.op);
        }
        Variable &dest = getVariable(i.op1.op);

        if (isVariable(i.op2.op)) {
            Variable &src = getVariable(i.op2.op);
            if (dest.type == VarType::VAR_FLOAT && src.type == VarType::VAR_FLOAT) {
                out << "\tmovsd " << getMangledName(i.op2) << "(%rip), %xmm0\n";
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
            } else if (dest.type == VarType::VAR_FLOAT && src.type != VarType::VAR_FLOAT) {
                if (src.type == VarType::VAR_BYTE) {
                    out << "\tmovzbq " << getMangledName(i.op2) << "(%rip), %rax\n";
                    out << "\tcvtsi2sd %rax, %xmm0\n";
                } else if (src.type == VarType::VAR_STRING) {
                    out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rdi\n";
                    out << "\tmovq %rsp, %rbx\n";
                    out << "\tandq $-16, %rsp\n";
                    out << "\tcall " << getPlatformSymbolName("atof") << "\n";
                    out << "\tmovq %rbx, %rsp\n";
                } else {
                    sysv_emitLoadVar(out, "%rax", i.op2);
                    out << "\tcvtsi2sd %rax, %xmm0\n";
                }
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
            } else if (dest.type != VarType::VAR_FLOAT && src.type == VarType::VAR_FLOAT) {
                out << "\tmovsd " << getMangledName(i.op2) << "(%rip), %xmm0\n";
                out << "\tcvttsd2si %xmm0, %rax\n";
                if (dest.type == VarType::VAR_BYTE) {
                    out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    sysv_emitStoreVar(out, "%rax", i.op1);
                }
            } else if (dest.type == VarType::VAR_POINTER && src.type == VarType::VAR_STRING) {
                out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rax\n";
                out << "\tmovq %rax, " << getMangledName(i.op1) << "(%rip)\n";
            } else if (dest.type == VarType::VAR_INTEGER && src.type == VarType::VAR_STRING) {
                out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rdi\n";
                out << "\tmovq %rsp, %rbx\n";
                out << "\tandq $-16, %rsp\n";
                out << "\tcall " << getPlatformSymbolName("atol") << "\n";
                out << "\tmovq %rbx, %rsp\n";
                sysv_emitStoreVar(out, "%rax", i.op1);
            } else if (dest.type == VarType::VAR_BYTE && src.type != VarType::VAR_BYTE) {
                if (src.type == VarType::VAR_FLOAT) {
                    out << "\tmovsd " << getMangledName(i.op2) << "(%rip), %xmm0\n";
                    out << "\tcvttsd2si %xmm0, %rax\n";
                    out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    sysv_emitLoadVar(out, "%rax", i.op2);
                    out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                }
            } else if (dest.type != VarType::VAR_BYTE && src.type == VarType::VAR_BYTE) {
                out << "\tmovzbq " << getMangledName(i.op2) << "(%rip), %rax\n";
                if (dest.type == VarType::VAR_FLOAT) {
                    out << "\tcvtsi2sd %rax, %xmm0\n";
                    out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    sysv_emitStoreVar(out, "%rax", i.op1);
                }
            } else {
                switch (dest.type) {
                case VarType::VAR_INTEGER:
                case VarType::VAR_POINTER:
                case VarType::VAR_EXTERN:
                    sysv_emitLoadVar(out, "%rax", i.op2);
                    sysv_emitStoreVar(out, "%rax", i.op1);
                    break;
                case VarType::VAR_BYTE:
                    out << "\tmovb " << getMangledName(i.op2) << "(%rip), %al\n";
                    out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                    break;
                case VarType::VAR_STRING:
                    out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rax\n";
                    out << "\tmovq %rax, " << getMangledName(i.op1) << "(%rip)\n";
                    break;
                default:
                    throw mx::Exception("MOV: unsupported destination type");
                }
            }
        } else {
            if (i.op2.type == OperandType::OP_CONSTANT) {
                if (dest.type == VarType::VAR_FLOAT) {
                    double val = std::stod(i.op2.op);
                    uint64_t bits;
                    std::memcpy(&bits, &val, sizeof(bits));
                    out << "\tmovq $" << bits << ", %rax\n";
                    out << "\tmovq %rax, " << getMangledName(i.op1) << "(%rip)\n";
                } else if (dest.type == VarType::VAR_BYTE) {
                    out << "\tmovb $" << i.op2.op << ", " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    sysv_emitStoreVarImm(out, i.op2.op, i.op1);
                }
            } else {
                throw mx::Exception("MOV: unsupported source operand type");
            }
        }
    }

    void Program::gen_arth(std::ostream &out, std::string arth, const Instruction &i) {
        auto emitIntegerOp = [&](const Operand &lhs, const Operand &rhs, const Operand &dest) {
            if (arth == "mul")
                arth = "imul";
            if (!isVariable(rhs.op) && rhs.type == OperandType::OP_CONSTANT && arth != "imul" && lhs.op == dest.op && isVariable(lhs.op)) {
                auto ra = sysv_reg_vars.find(lhs.op);
                if (ra != sysv_reg_vars.end()) {
                    out << "\t" << arth << "q $" << rhs.op << ", " << ra->second << "\n";
                } else {
                    out << "\t" << arth << "q $" << rhs.op << ", " << getMangledName(lhs) << "(%rip)\n";
                }
                return;
            }
            generateLoadVar(out, VarType::VAR_INTEGER, "%r10", lhs);
            if (!isVariable(rhs.op) && rhs.type == OperandType::OP_CONSTANT) {
                out << "\t" << arth << "q $" << rhs.op << ", %r10\n";
            } else {
                generateLoadVar(out, VarType::VAR_INTEGER, "%r11", rhs);
                out << "\t" << arth << "q %r11, %r10\n";
            }
            sysv_emitStoreVar(out, "%r10", dest);
        };

        if (i.op3.op.empty()) {
            if (!isVariable(i.op1.op)) {
                throw mx::Exception("First argument of arithmetic must be a variable: " + i.op1.op);
            }
            Variable &v = getVariable(i.op1.op);

            if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER) {
                emitIntegerOp(i.op1, i.op2, i.op1);
            } else if (v.type == VarType::VAR_FLOAT) {
                generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
                generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op2);
                out << "\t" << arth << "sd %xmm1, %xmm0\n";
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
            } else {
                throw mx::Exception("Unsupported variable type for arithmetic");
            }
        } else {
            if (!isVariable(i.op1.op)) {
                throw mx::Exception("First argument of arithmetic must be a variable: " + i.op1.op);
            }
            Variable &v = getVariable(i.op1.op);

            if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER) {
                emitIntegerOp(i.op2, i.op3, i.op1);
            } else if (v.type == VarType::VAR_FLOAT) {
                generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op2);
                generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op3);
                out << "\t" << arth << "sd %xmm1, %xmm0\n";
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
            } else {
                throw mx::Exception("Unsupported variable type for arithmetic");
            }
        }
    }

    void Program::gen_exit(std::ostream &out, const Instruction &i) {
        bool uses_std_module = false;
        for (const auto &e : external) {
            if (e.mod == "std") {
                uses_std_module = true;
                break;
            }
        }
        if (uses_std_module) {
            out << "\tmovq %rsp, %rbx\n";
            out << "\tandq $-16, %rsp\n";
            out << "\tcall " << getPlatformSymbolName("free_program_args") << "\n";
            out << "\tmovq %rbx, %rsp\n";
        }

        if (!i.op1.op.empty()) {
            std::vector<Operand> opz;
            opz.push_back(i.op1);
            generateFunctionCall(out, "exit", opz);
        } else {
            throw mx::Exception("exit requires argument.\n");
        }
    }
} // namespace mxvm