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

#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <utility>

namespace mxvm {

    static unsigned x64_sp_mod16 = 0;
    extern size_t xmm_offset;
    static int error_label_count = 0;

    std::string Program::x64_getRegisterByIndex(int index, VarType type) {
        if (type == VarType::VAR_FLOAT) {
            if (index < 4)
                return "%xmm" + std::to_string(index);
            return "[stack]";
        }
        static const char *ireg[] = {"%rcx", "%rdx", "%r8", "%r9"};
        if (index < 4)
            return ireg[index];
        return "[stack]";
    }

    static inline bool is_stdio_name(const std::string &s) {
        return s == "stdin" || s == "stdout" || s == "stderr";
    }
    static inline int stdio_index(const std::string &s) {
        return s == "stdin" ? 0 : (s == "stdout" ? 1 : 2);
    }

    void Program::x64_analyzeRegAlloc(bool uses_std_module) {
        x64_reg_vars.clear();
        x64_reg_save_order.clear();

        std::vector<std::string> available_regs = {"%rbx", "%r14", "%r15", "%rsi", "%rdi"};
        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 && !is_stdio_name(op.op)) {
                    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) {
                x64_reg_vars[sorted_vars[i].first] = available_regs[i];
                x64_reg_save_order.push_back(available_regs[i]);
            }
        }
    }

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

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

    void Program::x64_emitSaveRegs(std::ostream &out) {
        for (const auto &reg : x64_reg_save_order) {
            out << "\tpushq " << reg << "\n";
            x64_sp_mod16 ^= 8;
        }
    }

    void Program::x64_emitRestoreRegs(std::ostream &out) {
        for (auto it = x64_reg_save_order.rbegin(); it != x64_reg_save_order.rend(); ++it) {
            out << "\tpopq " << *it << "\n";
            x64_sp_mod16 ^= 8;
        }
    }

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

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

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

    size_t Program::x64_reserve_call_area(std::ostream &out, size_t spill_bytes) {
        const size_t need = 32 + spill_bytes;
        const size_t aligned_need = (need + 15) & ~15;
        out << "\tsub $" << aligned_need << ", %rsp\n";
        x64_sp_mod16 = (x64_sp_mod16 + aligned_need) % 16;
        return aligned_need;
    }

    void Program::x64_release_call_area(std::ostream &out, size_t total) {
        out << "\tadd $" << total << ", %rsp\n";
        x64_sp_mod16 = (x64_sp_mod16 + (16 - (total % 16))) % 16;
    }
    void Program::x64_emit_iob_func(std::ostream &out, int index, const std::string &dstReg) {
        out << "\tmov $" << index << ", %ecx\n";
        size_t total = x64_reserve_call_area(out, 0);
        out << "\tcall __acrt_iob_func\n";
        x64_release_call_area(out, total);
        if (dstReg != "%rax")
            out << "\tmov %rax, " << dstReg << "\n";
    }

    int Program::x64_generateLoadVar(std::ostream &out, VarType type, std::string reg, const Operand &op) {
        int count = 0;
        if (isVariable(op.op)) {
            Variable &v = getVariable(op.op);

            if ((v.type == VarType::VAR_EXTERN || v.type == VarType::VAR_POINTER) && is_stdio_name(op.op)) {
                x64_emit_iob_func(out, stdio_index(op.op), reg);
                return 0;
            }

            switch (v.type) {
            case VarType::VAR_INTEGER:
            case VarType::VAR_POINTER:
            case VarType::VAR_EXTERN: {
                auto ra = x64_reg_vars.find(op.op);
                if (ra != x64_reg_vars.end()) {
                    if (ra->second != reg)
                        out << "\tmovq " << ra->second << ", " << reg << "\n";
                } else {
                    out << "\tmovq " << getMangledName(op) << "(%rip), " << reg << "\n";
                }
                break;
            }
            case VarType::VAR_BYTE:
                out << "\tmovzbq " << getMangledName(op) << "(%rip), " << reg << "\n";
                break;
            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getMangledName(op) << "(%rip), " << reg << "\n";
                count = 1;
                break;
            case VarType::VAR_STRING:
                out << "\tleaq " << getMangledName(op) << "(%rip), " << reg << "\n";
                break;
            default:
                break;
            }
        } else {
            if (op.type == OperandType::OP_CONSTANT && type == VarType::VAR_INTEGER)
                out << "\tmovq $" << op.op << ", " << reg << "\n";
            else if (op.type == OperandType::OP_CONSTANT && type == VarType::VAR_FLOAT)
                throw mx::Exception("Constant to float not supported");
        }
        return count;
    }

    int Program::x64_generateLoadVar(std::ostream &out, int r, const Operand &op) {
        int count = 0;
        if (isVariable(op.op)) {
            Variable &v = getVariable(op.op);
            std::string reg = x64_getRegisterByIndex(r, v.type);

            if ((v.type == VarType::VAR_EXTERN || v.type == VarType::VAR_POINTER) && is_stdio_name(op.op)) {
                x64_emit_iob_func(out, stdio_index(op.op), reg);
                return 0;
            }

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

    bool isFunctionReturningOwnedPtr(const std::string &funcName) {
        static const std::unordered_set<std::string> funcs = {
            "inttostr",
            "floattostr",
        };
        return funcs.count(funcName) > 0;
    }

    void Program::x64_generateFunctionCall(std::ostream &out,
                                           const std::string &name,
                                           std::vector<Operand> &args) {
        xmm_offset = 0;
        const size_t stack_args = (args.size() > 4) ? (args.size() - 4) : 0;
        const size_t spill_bytes = stack_args * 8;
        size_t frame = x64_reserve_call_area(out, spill_bytes);

        static const char *GPR[4] = {"%rcx", "%rdx", "%r8", "%r9"};

        for (size_t i = 0; i < args.size(); ++i) {
            VarType t = VarType::VAR_INTEGER;
            if (isVariable(args[i].op))
                t = getVariable(args[i].op).type;

            if (i < 4) {
                if (t == VarType::VAR_FLOAT) {
                    std::string xr = "%xmm" + std::to_string(i);
                    x64_generateLoadVar(out, VarType::VAR_FLOAT, xr, args[i]);
                } else {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, GPR[i], args[i]);
                }
            } else {
                const size_t off = 32 + 8 * (i - 4);
                if (t == VarType::VAR_FLOAT) {
                    x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm7", args[i]);
                    out << "\tmovsd %xmm7, " << off << "(%rsp)\n";
                } else {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", args[i]);
                    out << "\tmovq %rax, " << off << "(%rsp)\n";
                }
            }
        }

        out << "\txor %eax, %eax\n";
        out << "\tcall " << name << "\n";
        x64_release_call_area(out, frame);
    }

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

        if (isFunctionReturningOwnedPtr(n)) {
            this->last_call_returns_owned_ptr = true;
        } else {
            this->last_call_returns_owned_ptr = false;
        }

        x64_generateFunctionCall(out, n, a);
    }

    void Program::x64_generateCode(const Platform &, bool, std::ostream &out) {
        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;
            }
        }

        x64_analyzeRegAlloc(uses_std_module);

        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) {
            auto varx = getVariable(v);
            const std::string nm = getMangledName(v);
            if (varx.type == VarType::VAR_INTEGER) {
                out << "\t" << nm << ": .quad " << vars[v].var_value.int_value << "\n";
                continue;
            }
            if (varx.type == VarType::VAR_FLOAT) {
                out << "\t" << nm << ": .double " << vars[v].var_value.float_value << "\n";
                continue;
            }
            if (varx.type == VarType::VAR_BYTE) {
                out << "\t" << nm << ": .byte " << vars[v].var_value.int_value << "\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 nm = getMangledName(v);
                out << "\t" << nm << ": .asciz \"" << escapeNewLines(vars[v].var_value.str_value) << "\"\n";
            }
        }

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

        out << ".section .bss\n";
        for (auto &v : var_names) {
            const std::string nm = getMangledName(v);
            auto varx = getVariable(v);
            if (varx.type == VarType::VAR_POINTER)
                out << "\t.comm " << nm << ", 8\n";
            else if (varx.type == VarType::VAR_STRING && varx.var_value.buffer_size > 0)
                out << "\t.comm " << nm << ", " << 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.globl " << getMangledName(v) << "\n";
            }
        }

        out << ".section .text\n";

        out << "\t.extern __acrt_iob_func\n";

        for (auto &lbl : labels)
            if (lbl.second.second == true)
                out << "\t.globl " << 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)
            out << "\t.extern " << (e.module ? e.name : e.mod + "_" + e.name) << "\n";

        if (!this->object) {
            out << "\t.p2align 4, 0x90\n";
            out << ".globl main\n";
            out << "main:\n";
            out << "\tpush %rbp\n";
            out << "\tmov %rsp, %rbp\n";
            x64_sp_mod16 = 0;
            if (uses_std_module) {
                out << "\tpush %r12\n";
                out << "\tpush %r13\n";
                out << "\tmov %rcx, %r12\n";
                out << "\tmov %rdx, %r13\n";
                size_t total = x64_reserve_call_area(out, 0);
                out << "\tcall set_program_args\n";
                x64_release_call_area(out, total);
            }
            x64_emitSaveRegs(out);
            x64_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 << name + "_" + l.first << ":\n";
                    out << "\tpush %rbp\n";
                    out << "\tmov %rsp, %rbp\n";
                    x64_sp_mod16 = 0;
                    x64_emitSaveRegs(out);
                    x64_emitReloadRegs(out);
                    if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
                        Operand rax_op;
                        rax_op.op = "rax";
                        x64_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;
            x64_generateInstruction(out, instr);
        }

#ifndef __EMSCRIPTEN__
        if (this->object == false && done_found == false)
            throw mx::Exception("Program missing done to signal completion.\n");
#endif

        out << "\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) << "Windows" << "\n";
    }

    void Program::x64_gen_done(std::ostream &out, const Instruction &) {
        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) {
            x64_emitFlushRegs(out);
            size_t total = x64_reserve_call_area(out, 0);
            out << "\tcall free_program_args\n";
            x64_release_call_area(out, total);
        }

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

    void Program::x64_gen_ret(std::ostream &out, const Instruction &) {
        x64_emitFlushRegs(out);
        if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
            Operand rax_op;
            rax_op.op = "rax";
            x64_emitLoadVar(out, "%rax", rax_op);
        }
        x64_emitRestoreRegs(out);
        out << "\tleave\n";
        out << "\tret\n";
    }

    void Program::x64_gen_return(std::ostream &out, const Instruction &i) {
        if (isVariable(i.op1.op)) {
            if (this->last_call_returns_owned_ptr) {
                Variable &v = getVariable(i.op1.op);
                v.var_value.owns = true;
                this->last_call_returns_owned_ptr = false;
            }
            x64_emitStoreVar(out, "%rax", i.op1);
        } else
            throw mx::Exception("return requires variable");
    }

    void Program::x64_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) {
                x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op1);
                out << "\tnegq %rcx\n";
                x64_emitStoreVar(out, "%rcx", i.op1);
            } else if (v.type == VarType::VAR_FLOAT) {
                x64_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::x64_generateInstruction(std::ostream &out, const Instruction &i) {

        if (i.instruction < JMP || i.instruction > JNS) {
            last_cmp_type = CMP_NONE;
        }
        switch (i.instruction) {
        case ADD:
            x64_gen_arth(out, "add", i);
            break;
        case SUB:
            x64_gen_arth(out, "sub", i);
            break;
        case MUL:
            x64_gen_arth(out, "mul", i);
            break;
        case PRINT:
            x64_gen_print(out, i);
            break;
        case EXIT:
            x64_gen_exit(out, i);
            break;
        case MOV:
            x64_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:
            x64_gen_jmp(out, i);
            break;
        case CMP:
            x64_gen_cmp(out, i);
            break;
        case ALLOC:
            x64_gen_alloc(out, i);
            break;
        case FREE:
            x64_gen_free(out, i);
            break;
        case LOAD:
            x64_gen_load(out, i);
            break;
        case STORE:
            x64_gen_store(out, i);
            break;
        case RET:
            x64_gen_ret(out, i);
            break;
        case CALL:
            x64_gen_call(out, i);
            break;
        case DONE:
            x64_gen_done(out, i);
            break;
        case AND:
            x64_gen_bitop(out, "and", i);
            break;
        case OR:
            x64_gen_bitop(out, "or", i);
            break;
        case XOR:
            x64_gen_bitop(out, "xor", i);
            break;
        case NOT:
            x64_gen_not(out, i);
            break;
        case DIV:
            x64_gen_div(out, i);
            break;
        case MOD:
            x64_gen_mod(out, i);
            break;
        case PUSH:
            x64_gen_push(out, i);
            break;
        case POP:
            x64_gen_pop(out, i);
            break;
        case STACK_LOAD:
            x64_gen_stack_load(out, i);
            break;
        case STACK_STORE:
            x64_gen_stack_store(out, i);
            break;
        case STACK_SUB:
            x64_gen_stack_sub(out, i);
            break;
        case GETLINE:
            x64_gen_getline(out, i);
            break;
        case TO_INT:
            x64_gen_to_int(out, i);
            break;
        case TO_FLOAT:
            x64_gen_to_float(out, i);
            break;
        case INVOKE:
            x64_gen_invoke(out, i);
            break;
        case RETURN:
            x64_gen_return(out, i);
            break;
        case NEG:
            x64_gen_neg(out, i);
            break;
        case FCMP:
            x64_gen_fcmp(out, i);
            break;
        case JAE:
            x64_gen_jae(out, i);
            break;
        case JBE:
            x64_gen_jbe(out, i);
            break;
        case JC:
            x64_gen_jc(out, i);
            break;
        case JNC:
            x64_gen_jnc(out, i);
            break;
        case JP:
            x64_gen_jp(out, i);
            break;
        case JNP:
            x64_gen_jnp(out, i);
            break;
        case JO:
            x64_gen_jo(out, i);
            break;
        case JNO:
            x64_gen_jno(out, i);
            break;
        case JS:
            x64_gen_js(out, i);
            break;
        case JNS:
            x64_gen_jns(out, i);
            break;
        default:
            throw mx::Exception("Invalid or unsupported instruction");
        }
    }

    void Program::x64_gen_invoke(std::ostream &out, const Instruction &i) {
        if (i.op1.op.empty())
            throw mx::Exception("invoke requires 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);
        x64_generateInvokeCall(out, op);
    }

    void Program::x64_gen_call(std::ostream &out, const Instruction &i) {
        if (!isFunctionValid(i.op1.op))
            throw mx::Exception("Function not found");
        x64_emitFlushRegs(out);
        if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
            Operand rax_op;
            rax_op.op = "rax";
            x64_emitLoadVar(out, "%rax", rax_op);
        }
        size_t total = x64_reserve_call_area(out, 0);
        out << "\tcall " << getMangledName(i.op1) << "\n";
        x64_release_call_area(out, total);
        if (isVariable("rax") && getVariable("rax").type == VarType::VAR_INTEGER) {
            Operand rax_op;
            rax_op.op = "rax";
            out << "\tmovq %rax, " << getMangledName(rax_op) << "(%rip)\n";
        }
        x64_emitReloadRegs(out);
    }
    void Program::x64_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");

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

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

        size_t total = x64_reserve_call_area(out, 0);
        out << "\tcall calloc\n";
        x64_release_call_area(out, total);

        out << "\ttest %rax, %rax\n";
        out << "\tjz .alloc_failed_" << error_label_count << "\n";

        out << "\tmovq %rax, " << getMangledName(i.op1) << "(%rip)\n";
        getVariable(i.op1.op).var_value.owns = true;

        out << "\tjmp .alloc_done_" << error_label_count << "\n";
        out << ".alloc_failed_" << error_label_count << ":\n";
        out << "\t# Handle allocation failure\n";
        out << "\tmovq $0, " << getMangledName(i.op1) << "(%rip)\n";
        out << ".alloc_done_" << error_label_count << ":\n";

        error_label_count++;
    }

    void Program::x64_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)
            throw mx::Exception("FREE on non-owned pointer: " + v.var_name);

        out << "\tmovq " << getMangledName(i.op1) << "(%rip), %rcx\n";
        out << "\ttest %rcx, %rcx\n";
        out << "\tjz 1f\n";
        size_t total = x64_reserve_call_area(out, 0);
        out << "\tcall free\n";
        x64_release_call_area(out, total);
        out << "\tmovq $0, " << getMangledName(i.op1) << "(%rip)\n";
        out << "1:\n";
        v.var_value.owns = false;
    }

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

        if (isVariable(i.op2.op)) {
            Variable &src = getVariable(i.op2.op);
            if (dest.type == VarType::VAR_INTEGER && src.type == VarType::VAR_FLOAT) {
                out << "\tmovsd " << getMangledName(i.op2) << "(%rip), %xmm0\n";
                out << "\tcvttsd2si %xmm0, %rax\n";
                x64_emitStoreVar(out, "%rax", i.op1);
            } else if (dest.type == VarType::VAR_FLOAT && src.type == VarType::VAR_INTEGER) {
                x64_emitLoadVar(out, "%rax", i.op2);
                out << "\tcvtsi2sd %rax, %xmm0\n";
                out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
            } else if (dest.type == src.type) {

                if (dest.type == VarType::VAR_FLOAT) {
                    x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op2);
                    out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                } else if (dest.type == VarType::VAR_BYTE) {
                    x64_generateLoadVar(out, VarType::VAR_BYTE, "%rax", i.op2);
                    out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                } else {
                    x64_generateLoadVar(out, dest.type, "%rax", i.op2);
                    x64_emitStoreVar(out, "%rax", i.op1);
                }
            } else {

                if (dest.type == VarType::VAR_FLOAT) {
                    if (src.type == VarType::VAR_INTEGER || src.type == VarType::VAR_BYTE) {
                        x64_generateLoadVar(out, src.type, "%rax", i.op2);
                        out << "\tcvtsi2sdq %rax, %xmm0\n";
                        out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
                    } else {
                        throw mx::Exception("MOV: unsupported conversion to float");
                    }
                } else if (src.type == VarType::VAR_FLOAT) {
                    if (dest.type == VarType::VAR_INTEGER || dest.type == VarType::VAR_BYTE) {
                        x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op2);
                        out << "\tcvttsd2siq %xmm0, %rax\n";
                        if (dest.type == VarType::VAR_BYTE) {
                            out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                        } else {
                            x64_emitStoreVar(out, "%rax", i.op1);
                        }
                    } else {
                        throw mx::Exception("MOV: unsupported conversion from float");
                    }
                } else if (dest.type == VarType::VAR_POINTER && src.type == VarType::VAR_STRING) {
                    out << "\tleaq " << getMangledName(i.op2.op) << "(%rip), %rax\n";
                    out << "\tmovq %rax, " << getMangledName(i.op1.op) << "(%rip)\n";
                    getVariable(i.op1.op).var_value.owns = false;
                } else {
                    x64_generateLoadVar(out, src.type, "%rax", i.op2);
                    if (dest.type == VarType::VAR_BYTE) {
                        out << "\tmovb %al, " << getMangledName(i.op1) << "(%rip)\n";
                    } else {
                        x64_emitStoreVar(out, "%rax", i.op1);
                    }
                }
            }
        } else {
            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 {
                x64_emitStoreVarImm(out, i.op2.op, i.op1);
            }
        }
    }

    void Program::x64_gen_load(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op))
            throw mx::Exception("LOAD dest must be variable");
        Variable &dest = getVariable(i.op1.op);
        if (!isVariable(i.op2.op))
            throw mx::Exception("LOAD source must be pointer variable");
        Variable &ptrVar = getVariable(i.op2.op);

        if (ptrVar.type != VarType::VAR_POINTER &&
            !(ptrVar.type == VarType::VAR_STRING && ptrVar.var_value.buffer_size > 0)) {
            throw mx::Exception("LOAD: invalid base type");
        }

        if (ptrVar.type == VarType::VAR_POINTER) {
            out << "\tmovq " << getMangledName(i.op2) << "(%rip), %rax\n";
        } else {
            out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rax\n";
        }

        out << "\ttest %rax, %rax\n";
        out << "\tjz .null_ptr_error_" << error_label_count << "\n";

        size_t base_offset = 0;
        if (!i.op3.op.empty()) {
            if (isVariable(i.op3.op)) {
                x64_emitLoadVar(out, "%rcx", i.op3);
            } else {
                base_offset = std::stoll(i.op3.op);
                out << "\tmovq $" << base_offset << ", %rcx\n";
            }
        } else {
            out << "\txorq %rcx, %rcx\n";
        }

        if (!i.vop.empty() && !i.vop[0].op.empty()) {
            out << "\taddq %rcx, %rax\n";
            if (isVariable(i.vop[0].op)) {
                x64_emitLoadVar(out, "%r8", i.vop[0]);
            } else {
                out << "\tmovq $" << i.vop[0].op << ", %r8\n";
            }

            if (i.vop.size() > 1 && !i.vop[1].op.empty()) {
                if (isVariable(i.vop[1].op)) {
                    x64_emitLoadVar(out, "%r9", i.vop[1]);
                    out << "\timulq %r9, %r8\n";
                } else {
                    size_t stride = static_cast<size_t>(std::stoll(i.vop[1].op, nullptr, 0));
                    out << "\timulq $" << stride << ", %r8\n";
                }
            }

            out << "\taddq %r8, %rax\n";
        } else {
            out << "\tshlq $3, %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";
            x64_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");
        }

        out << "\tjmp .load_done_" << error_label_count << "\n";
        out << ".null_ptr_error_" << error_label_count << ":\n";
        out << "\t# Handle null pointer error\n";
        out << ".load_done_" << error_label_count << ":\n";

        error_label_count++;
    }

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

        Variable &ptrVar = getVariable(i.op2.op);
        if (ptrVar.type != VarType::VAR_POINTER &&
            !(ptrVar.type == VarType::VAR_STRING && ptrVar.var_value.buffer_size > 0)) {
            throw mx::Exception("STORE destination must be a pointer or string buffer");
        }

        if (ptrVar.type == VarType::VAR_POINTER) {
            out << "\tmovq " << getMangledName(i.op2) << "(%rip), %rcx\n";
        } else {
            out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rcx\n";
        }

        out << "\ttest %rcx, %rcx\n";
        out << "\tjz .null_ptr_error_" << error_label_count << "\n";

        size_t base_offset = 0;
        if (!i.op3.op.empty()) {
            if (isVariable(i.op3.op)) {
                x64_emitLoadVar(out, "%rdx", i.op3);
            } else {
                base_offset = std::stoll(i.op3.op);
                out << "\tmovq $" << base_offset << ", %rdx\n";
            }
        } else {
            out << "\txorq %rdx, %rdx\n";
        }

        if (!i.vop.empty() && !i.vop[0].op.empty()) {
            out << "\taddq %rdx, %rcx\n";
            if (isVariable(i.vop[0].op)) {
                x64_emitLoadVar(out, "%r8", i.vop[0]);
            } else {
                out << "\tmovq $" << i.vop[0].op << ", %r8\n";
            }

            if (i.vop.size() > 1 && !i.vop[1].op.empty()) {
                if (isVariable(i.vop[1].op)) {
                    x64_emitLoadVar(out, "%r9", i.vop[1]);
                    out << "\timulq %r9, %r8\n";
                } else {
                    size_t stride = static_cast<size_t>(std::stoll(i.vop[1].op, nullptr, 0));
                    out << "\timulq $" << stride << ", %r8\n";
                }
            }

            out << "\taddq %r8, %rcx\n";
        } else {
            out << "\tshlq $3, %rdx\n";
            out << "\taddq %rdx, %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:
                x64_emitLoadVar(out, "%rax", i.op1);
                out << "\tmovq %rax, (%rcx)\n";
                break;
            case VarType::VAR_STRING:
                out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rax\n";
                out << "\tmovq %rax, (%rcx)\n";
                break;
            case VarType::VAR_BYTE:
                out << "\tmovzbq " << getMangledName(i.op1) << "(%rip), %rax\n";
                out << "\tmovb %al, (%rcx)\n";
                break;
            case VarType::VAR_FLOAT:
                out << "\tmovsd " << getMangledName(i.op1) << "(%rip), %xmm0\n";
                out << "\tmovsd %xmm0, (%rcx)\n";
                break;
            default:
                throw mx::Exception("STORE: unsupported source type");
            }
        } else {

            if (i.op1.op.find('.') != std::string::npos ||
                i.op1.op.find('e') != std::string::npos ||
                i.op1.op.find('E') != std::string::npos) {

                double val = std::stod(i.op1.op);
                uint64_t bits;
                memcpy(&bits, &val, sizeof(bits));
                out << "\tmovq $" << bits << ", %rax\n";
                out << "\tmovq %rax, (%rcx)\n";
            } else {

                out << "\tmovq $" << i.op1.op << ", %rax\n";
                out << "\tmovq %rax, (%rcx)\n";
            }
        }

        out << "\tjmp .store_done_" << error_label_count << "\n";
        out << ".null_ptr_error_" << error_label_count << ":\n";
        out << "\t# Handle null pointer error\n";
        out << ".store_done_" << error_label_count << ":\n";

        error_label_count++;
    }

    void Program::x64_gen_to_int(std::ostream &out, const Instruction &i) {
        out << "\tleaq " << getMangledName(i.op2) << "(%rip), %rcx\n";
        size_t total = x64_reserve_call_area(out, 0);
        out << "\tcall atol\n";
        x64_release_call_area(out, total);
        x64_emitStoreVar(out, "%rax", i.op1);
    }

    void Program::x64_gen_to_float(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op) || !isVariable(i.op2.op)) {
            throw mx::Exception("TO_FLOAT requires variable operands");
        }
        x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
        out << "\tcvtsi2sdq %rax, %xmm0\n";
        out << "\tmovsd %xmm0, " << getMangledName(i.op1) << "(%rip)\n";
    }

    void Program::x64_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) {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                    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";
                    x64_emitStoreVar(out, "%rax", i.op1);
                } else if (v.type == VarType::VAR_FLOAT) {
                    x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
                    x64_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 type");
            } else
                throw mx::Exception("DIV: first must be variable");
        } else {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op3);
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", 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";
                    x64_emitStoreVar(out, "%rax", i.op1);
                } else if (v.type == VarType::VAR_FLOAT) {
                    x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op2);
                    x64_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 type");
            } else
                throw mx::Exception("DIV: first must be variable");
        }
    }

    void Program::x64_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) {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                    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";
                    x64_emitStoreVar(out, "%rdx", i.op1);
                } else
                    throw mx::Exception("MOD int only");
            } else
                throw mx::Exception("MOD: first must be variable");
        } else {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op3);
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", 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";
                    x64_emitStoreVar(out, "%rdx", i.op1);
                } else
                    throw mx::Exception("MOD int only");
            } else
                throw mx::Exception("MOD: first must be variable");
        }
    }

    void Program::x64_gen_getline(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op))
            throw mx::Exception("GETLINE: dest must be var");
        Variable &dest = getVariable(i.op1.op);
        if (dest.type != VarType::VAR_STRING || dest.var_value.buffer_size == 0)
            throw mx::Exception("GETLINE: needs string buffer");

        out << "\txor %ecx, %ecx\n";
        size_t t0 = x64_reserve_call_area(out, 0);
        out << "\tcall __acrt_iob_func\n";
        x64_release_call_area(out, t0);
        out << "\tmov %rax, %r8\n";

        out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rcx\n";
        out << "\tmovq $" << dest.var_value.buffer_size << ", %rdx\n";
        size_t total = x64_reserve_call_area(out, 0);
        out << "\tcall fgets\n";
        x64_release_call_area(out, total);

        static size_t over_count = 0;
        out << "\ttest %rax, %rax\n";
        out << "\tje .over" << over_count << "\n";

        out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rcx\n";
        size_t tlen = x64_reserve_call_area(out, 0);
        out << "\tcall strlen\n";
        x64_release_call_area(out, tlen);

        out << "\tmov %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), %rax\n";
        out << "\tmovb $0, (%rax, %rcx, 1)\n";
        out << ".over" << over_count++ << ":\n";
    }

    void Program::x64_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) {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
                    out << "\t" << opc << "q %rcx, %rax\n";
                    x64_emitStoreVar(out, "%rax", i.op1);
                } else
                    throw mx::Exception("bitop int only");
            } else
                throw mx::Exception("bitop first must be variable");
        } else {
            if (isVariable(i.op1.op)) {
                Variable &v = getVariable(i.op1.op);
                if (v.type == VarType::VAR_INTEGER) {
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
                    x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op3);
                    out << "\t" << opc << "q %rcx, %rax\n";
                    x64_emitStoreVar(out, "%rax", i.op1);
                } else
                    throw mx::Exception("bitop int only");
            } else
                throw mx::Exception("bitop first must be variable");
        }
    }

    void Program::x64_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 int only");
                x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                out << "\ttestq %rax, %rax\n";
                out << "\tsete %al\n";
                out << "\tmovzbq %al, %rax\n";
                x64_emitStoreVar(out, "%rax", i.op1);
            } else
                throw mx::Exception("NOT requires variable");
        } else
            throw mx::Exception("NOT requires operand");
    }

    void Program::x64_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) {
                x64_emitLoadVar(out, "%rax", i.op1);
                out << "\tpushq %rax\n";
                x64_sp_mod16 ^= 8;
            } else if (v.type == VarType::VAR_STRING) {
                out << "\tleaq " << getMangledName(i.op1) << "(%rip), %rax\n";
                out << "\tpushq %rax\n";
                x64_sp_mod16 ^= 8;
            } else {
                throw mx::Exception("PUSH supports int/pointer/string");
            }
        } else if (i.op1.type == OperandType::OP_CONSTANT) {
            out << "\tmovq $" << i.op1.op << ", %rax\n";
            out << "\tpushq %rax\n";
            x64_sp_mod16 ^= 8;
        } else {
            throw mx::Exception("PUSH requires var or const");
        }
    }

    void Program::x64_gen_pop(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op))
            throw mx::Exception("POP dest must be a variable");
        Variable &v = getVariable(i.op1.op);
        if (v.type == VarType::VAR_INTEGER || v.type == VarType::VAR_POINTER || v.type == VarType::VAR_EXTERN) {
            out << "\tpopq %rax\n";
            x64_sp_mod16 ^= 8;
            x64_emitStoreVar(out, "%rax", i.op1);
        } else {
            throw mx::Exception("POP supports int/pointer");
        }
    }

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

        if (!i.op1.op.empty() && !isVariable(i.op1.op)) {
            unsigned long long n = std::stoull(i.op1.op, nullptr, 0);
            if (n & 1ull)
                x64_sp_mod16 ^= 8;
        }
    }

    void Program::x64_gen_stack_load(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op))
            throw mx::Exception("stack_load first arg variable");
        if (i.op2.op.empty())
            throw mx::Exception("stack_load requires index");
        x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
        out << "\tmovq (%rsp, %rax, 8), %rcx\n";
        x64_emitStoreVar(out, "%rcx", i.op1);
    }

    void Program::x64_gen_stack_store(std::ostream &out, const Instruction &i) {
        if (!isVariable(i.op1.op))
            throw mx::Exception("stack_store first arg variable");
        if (i.op2.op.empty())
            throw mx::Exception("stack_store requires index");
        x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
        x64_emitLoadVar(out, "%rcx", i.op1);
        out << "\tmovq %rcx, (%rsp, %rax, 8)\n";
    }

    void Program::x64_gen_print(std::ostream &out, const Instruction &i) {
        xmm_offset = 0;
        std::vector<Operand> args;
        args.push_back(i.op1);
        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);
        x64_generateFunctionCall(out, "printf", args);
    }

    void Program::x64_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 must have valid label: " + i.op1.op);
            const char *m = nullptr;
            switch (i.instruction) {
            case JMP:
                m = "jmp";
                break;
            case JE:
                m = "je";
                break;
            case JNE:
                m = "jne";
                break;
            case JL:
                m = (last_cmp_type == CMP_FLOAT) ? "jb" : "jl";
                break;
            case JLE:
                m = (last_cmp_type == CMP_FLOAT) ? "jbe" : "jle";
                break;
            case JG:
                m = (last_cmp_type == CMP_FLOAT) ? "ja" : "jg";
                break;
            case JGE:
                m = (last_cmp_type == CMP_FLOAT) ? "jae" : "jge";
                break;
            case JZ:
                m = "jz";
                break;
            case JNZ:
                m = "jnz";
                break;
            case JA:
                m = "ja";
                break;
            case JB:
                m = "jb";
                break;
            default:
                break;
            }
            out << "\t" << m << " ." << i.op1.op << "\n";
        } else
            throw mx::Exception("Jump requires label");
    }

    void Program::x64_gen_cmp(std::ostream &out, const Instruction &i) {
        if (i.op2.op.empty())
            throw mx::Exception("CMP requires two operands");
        VarType t1 = VarType::VAR_INTEGER, t2 = VarType::VAR_INTEGER;
        if (isVariable(i.op1.op))
            t1 = getVariable(i.op1.op).type;
        if (isVariable(i.op2.op))
            t2 = getVariable(i.op2.op).type;

        if (t1 == VarType::VAR_FLOAT || t2 == VarType::VAR_FLOAT) {
            if (t1 == VarType::VAR_FLOAT) {
                x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
            } else {
                x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
                out << "\tcvtsi2sdq %rax, %xmm0\n";
            }

            if (t2 == VarType::VAR_FLOAT) {
                x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm1", i.op2);
            } else {
                x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op2);
                out << "\tcvtsi2sdq %rax, %xmm1\n";
            }

            out << "\tcomisd %xmm1, %xmm0\n";
            last_cmp_type = CMP_FLOAT;
        } else if (t1 == VarType::VAR_POINTER && (t2 == VarType::VAR_INTEGER || t2 == VarType::VAR_BYTE)) {
            x64_generateLoadVar(out, t1, "%rax", i.op1);
            x64_generateLoadVar(out, t2, "%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 = x64_reg_vars.find(i.op1.op);
            if (ra != x64_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 {
            x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", i.op1);
            x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", i.op2);
            out << "\tcmpq %rcx, %rax\n";
            last_cmp_type = CMP_INTEGER;
        }
    }
    void Program::x64_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 = x64_reg_vars.find(lhs.op);
                if (ra != x64_reg_vars.end()) {
                    out << "\t" << arth << "q $" << rhs.op << ", " << ra->second << "\n";
                } else {
                    out << "\t" << arth << "q $" << rhs.op << ", " << getMangledName(lhs) << "(%rip)\n";
                }
                return;
            }
            x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rax", lhs);
            if (!isVariable(rhs.op) && rhs.type == OperandType::OP_CONSTANT) {
                out << "\t" << arth << "q $" << rhs.op << ", %rax\n";
            } else {
                x64_generateLoadVar(out, VarType::VAR_INTEGER, "%rcx", rhs);
                out << "\t" << arth << "q %rcx, %rax\n";
            }
            x64_emitStoreVar(out, "%rax", dest);
        };

        if (i.op3.op.empty()) {
            if (!isVariable(i.op1.op))
                throw mx::Exception("arth first must be variable");
            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) {
                x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op1);
                x64_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("arth unsupported type");
        } else {
            if (!isVariable(i.op1.op))
                throw mx::Exception("arth first must be variable");
            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) {
                x64_generateLoadVar(out, VarType::VAR_FLOAT, "%xmm0", i.op2);
                x64_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("arth unsupported type");
        }
    }

    void Program::x64_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 << "\t# Clean up program arguments before exit\n";
            size_t total = x64_reserve_call_area(out, 0);
            out << "\tcall free_program_args\n";
            x64_release_call_area(out, total);
        }

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

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

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

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

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

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

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

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

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

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

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

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

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