<commit_before>// Copyright 2012 <NAME> <<EMAIL>>
// This program is free software licensed under the MIT license.

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "8cc.h"

static char *REGS[] = {"rdi", "rsi", "rdx", "rcx", "r8", "r9"};
static int TAB = 8;
static List *functions = &EMPTY_LIST;
static char *lbreak;
static char *lcontinue;
static char *lswitch;
static int stackpos;
static int numgp;
static int numfp;

static void emit_expr(Node *node);
static void emit_decl_init(List *inits, int off);
static void emit_data(Node *v, int off, int depth);

#define REGAREA_SIZE 304

#define emit(...)        emitf(__LINE__, "\t" __VA_ARGS__)
#define emit_noindent(...)  emitf(__LINE__, __VA_ARGS__)

#ifdef __GNUC__
#define SAVE                                                            \
    int save_hook __attribute__((unused, cleanup(pop_function)));       \
    list_push(functions, (void *)__func__)

static void pop_function(void *ignore) {
    list_pop(functions);
}
#else
#define SAVE
#endif

static char *get_caller_list(void) {
    String *s = make_string();
    for (Iter *i = list_iter(functions); !iter_end(i);) {
        string_appendf(s, "%s", iter_next(i));
        if (!iter_end(i))
            string_appendf(s, " -> ");
    }
    return get_cstring(s);
}

static void emitf(int line, char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int col = vprintf(fmt, args);
    va_end(args);

    for (char *p = fmt; *p; p++)
        if (*p == '\t')
            col += TAB - 1;
    int space = (28 - col) > 0 ? (30 - col) : 2;
    printf("%*c %s:%d\n", space, '#', get_caller_list(), line);
}

static char *get_int_reg(Ctype *ctype, char r) {
    assert(r == 'a' || r == 'c');
    switch (ctype->size) {
    case 1: return (r == 'a') ? "al" : "cl";
    case 2: return (r == 'a') ? "ax" : "cx";
    case 4: return (r == 'a') ? "eax" : "ecx";
    case 8: return (r == 'a') ? "rax" : "rcx";
    default:
        error("Unknown data size: %s: %d", c2s(ctype), ctype->size);
    }
}

static char *get_load_inst(Ctype *ctype) {
    switch (ctype->size) {
    case 1: return "movsbq";
    case 2: return "movswq";
    case 4: return "movslq";
    case 8: return "mov";
    default:
        error("Unknown data size: %s: %d", c2s(ctype), ctype->size);
    }
}

static void push_xmm(int reg) {
    SAVE;
    emit("sub $8, %%rsp");
    emit("movsd %%xmm%d, (%%rsp)", reg);
    stackpos += 8;
}

static void pop_xmm(int reg) {
    SAVE;
    emit("movsd (%%rsp), %%xmm%d", reg);
    emit("add $8, %%rsp");
    stackpos -= 8;
    assert(stackpos >= 0);
}

static void push(char *reg) {
    SAVE;
    emit("push %%%s", reg);
    stackpos += 8;
}

static void pop(char *reg) {
    SAVE;
    emit("pop %%%s", reg);
    stackpos -= 8;
    assert(stackpos >= 0);
}

static void emit_gload(Ctype *ctype, char *label, int off) {
    SAVE;
    if (ctype->type == CTYPE_ARRAY) {
        if (off)
            emit("lea %s+%d(%%rip), %%rax", label, off);
        else
            emit("lea %s(%%rip), %%rax", label);
        return;
    }
    char *inst = get_load_inst(ctype);
    emit("%s %s+%d(%%rip), %%rax", inst, label, off);
}

static void emit_toint(Ctype *ctype) {
    SAVE;
    if (ctype->type == CTYPE_FLOAT)
        emit("cvttss2si %%xmm0, %%eax");
    else if (ctype->type == CTYPE_FLOAT)
        emit("cvttsd2si %%xmm0, %%eax");
}

static void emit_lload(Ctype *ctype, char *base, int off) {
    SAVE;
    if (ctype->type == CTYPE_ARRAY) {
        emit("lea %d(%%%s), %%rax", off, base);
    } else if (ctype->type == CTYPE_FLOAT) {
        emit("movss %d(%%%s), %%xmm0", off, base);
    } else if (ctype->type == CTYPE_DOUBLE || ctype->type == CTYPE_LDOUBLE) {
        emit("movsd %d(%%%s), %%xmm0", off, base);
    } else {
        char *inst = get_load_inst(ctype);
        emit("%s %d(%%%s), %%rax", inst, off, base);
    }
}

static void maybe_convert_bool(Ctype *ctype) {
    if (ctype->type == CTYPE_BOOL) {
        emit("test %%rax, %%rax");
        emit("setne %%al");
    }
}

static void emit_gsave(char *varname, Ctype *ctype, int off) {
    SAVE;
    assert(ctype->type != CTYPE_ARRAY);
    maybe_convert_bool(ctype);
    char *reg = get_int_reg(ctype, 'a');
    if (off)
        emit("mov %%%s, %s+%d(%%rip)", reg, varname, off);
    else
        emit("mov %%%s, %s(%%rip)", reg, varname);
}

static void emit_lsave(Ctype *ctype, int off) {
    SAVE;
    if (ctype->type == CTYPE_FLOAT) {
        emit("movss %%xmm0, %d(%%rbp)", off);
    } else if (ctype->type == CTYPE_DOUBLE) {
        emit("movsd %%xmm0, %d(%%rbp)", off);
    } else {
        maybe_convert_bool(ctype);
        char *reg = get_int_reg(ctype, 'a');
        emit("mov %%%s, %d(%%rbp)", reg, off);
    }
}

static void emit_assign_deref_int(Ctype *ctype, int off) {
    SAVE;
    emit("mov (%%rsp), %%rcx");
    char *reg = get_int_reg(ctype, 'c');
    if (off)
        emit("mov %%%s, %d(%%rax)", reg, off);
    else
        emit("mov %%%s, (%%rax)", reg);
    pop("rax");
}

static void emit_assign_deref(Node *var) {
    SAVE;
    push("rax");
    emit_expr(var->operand);
    emit_assign_deref_int(var->operand->ctype->ptr, 0);
}

static void emit_pointer_arith(char op, Node *left, Node *right) {
    SAVE;
    emit_expr(left);
    push("rax");
    emit_expr(right);
    int size = left->ctype->ptr->size;
    if (size > 1)
        emit("imul $%d, %%rax", size);
    emit("mov %%rax, %%rcx");
    pop("rax");
    emit("add %%rcx, %%rax");
}

static void emit_zero_filler(int start, int end) {
    for (; start <= end - 4; start += 4)
        emit("movl $0, %d(%%rbp)", start);
    for (; start < end; start++)
        emit("movb $0, %d(%%rbp)", start);
}

static void ensure_lvar_init(Node *node) {
    assert(node->type == AST_LVAR);
    if (node->lvarinit)
        emit_decl_init(node->lvarinit, node->loff);
    node->lvarinit = NULL;
}

static void emit_assign_struct_ref(Node *struc, Ctype *field, int off) {
    SAVE;
    switch (struc->type) {
    case AST_LVAR:
        ensure_lvar_init(struc);
        emit_lsave(field, struc->loff + field->offset + off);
        break;
    case AST_GVAR:
        emit_gsave(struc->varname, field, field->offset + off);
        break;
    case AST_STRUCT_REF:
        emit_assign_struct_ref(struc->struc, field, off + struc->ctype->offset);
        break;
    case AST_DEREF:
        push("rax");
        emit_expr(struc->operand);
        emit_assign_deref_int(field, field->offset + off);
        break;
    default:
        error("internal error: %s", a2s(struc));
    }
}

static void emit_load_struct_ref(Node *struc, Ctype *field, int off) {
    SAVE;
    switch (struc->type) {
    case AST_LVAR:
        ensure_lvar_init(struc);
        emit_lload(field, "rbp", struc->loff + field->offset + off);
        break;
    case AST_GVAR:
        emit_gload(field, struc->varname, field->offset + off);
        break;
    case AST_STRUCT_REF:
        emit_load_struct_ref(struc->struc, field, struc->ctype->offset + off);
        break;
    case AST_DEREF:
        emit_expr(struc->operand);
        emit_lload(field, "rax", field->offset + off);
        break;
    default:
        error("internal error: %s", a2s(struc));
    }
}

static void emit_store(Node *var) {
    SAVE;
    switch (var->type) {
    case AST_DEREF: emit_assign_deref(var); break;
    case AST_STRUCT_REF: emit_assign_struct_ref(var->struc, var->ctype, 0); break;
    case AST_LVAR:
        ensure_lvar_init(var);
        emit_lsave(var->ctype, var->loff);
        break;
    case AST_GVAR: emit_gsave(var->varname, var->ctype, 0); break;
    default: error("internal error");
    }
}

static void emit_to_bool(Ctype *ctype) {
    SAVE;
    if (is_flotype(ctype)) {
        push_xmm(1);
        emit("xorpd %%xmm1, %%xmm1");
        emit("%s %%xmm1, %%xmm0", (ctype->type == CTYPE_FLOAT) ? "ucomiss" : "ucomisd");
        emit("setne %%al");
        pop_xmm(1);
    } else {
        emit("cmp $0, %%rax");
        emit("setne %%al");
    }
    emit("movzb %%al, %%eax");
}

static void emit_comp(char *inst, Node *node) {
    SAVE;
    if (is_flotype(node->left->ctype)) {
        emit_expr(node->left);
        push_xmm(0);
        emit_expr(node->right);
        pop_xmm(1);
        if (node->left->ctype->type == CTYPE_FLOAT)
            emit("ucomiss %%xmm0, %%xmm1");
        else
            emit("ucomisd %%xmm0, %%xmm1");
    } else {
        emit_expr(node->left);
        push("rax");
        emit_expr(node->right);
        pop("rcx");
        emit("cmp %%rax, %%rcx");
    }
    emit("%s %%al", inst);
    emit("movzb %%al, %%eax");
}

static void emit_binop_int_arith(Node *node) {
    SAVE;
    char *op = NULL;
    switch (node->type) {
    case '+': op = "add"; break;
    case '-': op = "sub"; break;
    case '*': op = "imul"; break;
    case '^': op = "xor"; break;
    case OP_SAL: op = "sal"; break;
    case OP_SAR: op = "sar"; break;
    case OP_SHR: op = "shr"; break;
    case '/': case '%': break;
    default: error("invalid operator '%d'", node->type);
    }
    emit_expr(node->left);
    push("rax");
    emit_expr(node->right);
    emit("mov %%rax, %%rcx");
    pop("rax");
    if (node->type == '/' || node->type == '%') {
        emit("cqto");
        emit("idiv %%rcx");
        if (node->type == '%')
            emit("mov %%edx, %%eax");
    } else if (node->type == OP_SAL || node->type == OP_SAR || node->type == OP_SHR) {
        emit("%s %%cl, %%%s", op, get_int_reg(node->left->ctype, 'a'));
    } else {
        emit("%s %%rcx, %rax", op);
    }
}

static void emit_binop_float_arith(Node *node) {
    SAVE;
    char *op;
    bool isdouble = (node->ctype->type == CTYPE_DOUBLE);
    switch (node->type) {
    case '+': op = (isdouble ? "addsd" : "addss"); break;
    case '-': op = (isdouble ? "subsd" : "subss"); break;
    case '*': op = (isdouble ? "mulsd" : "mulss"); break;
    case '/': op = (isdouble ? "divsd" : "divss"); break;
    default: error("invalid operator '%d'", node->type);
    }
    emit_expr(node->left);
    push_xmm(0);
    emit_expr(node->right);
    emit("%s %%xmm0, %%xmm1", (isdouble ? "movsd" : "movss"));
    pop_xmm(0);
    emit("%s %%xmm1, %%xmm0", op);
}

static void emit_load_convert(Ctype *to, Ctype *from) {
    SAVE;
    if (is_inttype(from) && to->type == CTYPE_FLOAT)
        emit("cvtsi2ss %%eax, %%xmm0");
    else if (is_inttype(from) && to->type == CTYPE_DOUBLE)
        emit("cvtsi2sd %%eax, %%xmm0");
    else if (from->type == CTYPE_FLOAT && to->type == CTYPE_DOUBLE)
        emit("cvtps2pd %%xmm0, %%xmm0");
    else if (from->type == CTYPE_DOUBLE && to->type == CTYPE_FLOAT)
        emit("cvtpd2ps %%xmm0, %%xmm0");
    else if (to->type == CTYPE_BOOL)
        emit_to_bool(from);
    else if (is_inttype(to))
        emit_toint(from);
}

static void emit_ret(void) {
    SAVE;
    emit("leave");
    emit("ret");
}

static void emit_binop(Node *node) {
    SAVE;
    if (node->ctype->type == CTYPE_PTR) {
        emit_pointer_arith(node->type, node->left, node->right);
        return;
    }
    switch (node->type) {
    case '<': emit_comp("setl", node); return;
    case '>': emit_comp("setg", node); return;
    case OP_EQ: emit_comp("sete", node); return;
    case OP_GE: emit_comp("setge", node); return;
    case OP_LE: emit_comp("setle", node); return;
    case OP_NE: emit_comp("setne", node); return;
    }
    if (is_inttype(node->ctype))
        emit_binop_int_arith(node);
    else if (is_flotype(node->ctype))
        emit_binop_float_arith(node);
    else
        error("internal error");
}

static void emit_save_literal(Node *node, Ctype *totype, int off) {
    switch (totype->type) {
    case CTYPE_BOOL:  emit("movb $%d, %d(%%rbp)", !!node->ival, off); break;
    case CTYPE_CHAR:  emit("movb $%d, %d(%%rbp)", node->ival, off); break;
    case CTYPE_SHORT: emit("movw $%d, %d(%%rbp)", node->ival, off); break;
    case CTYPE_INT:   emit("movl $%d, %d(%%rbp)", node->ival, off); break;
    case CTYPE_LONG:
    case CTYPE_LLONG:
    case CTYPE_PTR: {
        unsigned long ival = node->ival;
        emit("movl $%lu, %d(%%rbp)", ival & ((1L << 32) - 1), off);
        emit("movl $%lu, %d(%%rbp)", ival >> 32, off + 4);
        break;
    }
    case CTYPE_FLOAT: {
        float fval = node->fval;
        int *p = (int *)&fval;
        emit("movl $%u, %d(%%rbp)", *p, off);
        break;
    }
    case CTYPE_DOUBLE: {
        long *p = (long *)&node->fval;
        emit("movl $%lu, %d(%%rbp)", *p & ((1L << 32) - 1), off);
        emit("movl $%lu, %d(%%rbp)", *p >> 32, off + 4);
        break;
    }
    default:
        error("internal error: <%s> <%s> <%d>", a2s(node), c2s(totype), off);
    }
}

static void emit_addr(Node *node) {
    switch (node->type) {
    case AST_LVAR:
        ensure_lvar_init(node);
        emit("lea %d(%%rbp), %%rax", node->loff);
        break;
    case AST_GVAR:
        emit("lea %s(%%rip), %%rax", node->glabel);
        break;
    case AST_DEREF:
        emit_expr(node->operand);
        break;
    case AST_STRUCT_REF:
        emit_addr(node->struc);
        emit("add $%d, %%rax", node->ctype->offset);
        break;
    default:
        error("internal error: %s", a2s(node));
    }
}

static void emit_copy_struct(Node *left, Node *right) {
    push("rcx");
    push("r11");
    emit_addr(right);
    emit("mov %%rax, %%rcx");
    emit_addr(left);
    int i = 0;
    for (; i < left->ctype->size; i += 8) {
        emit("movq %d(%%rcx), %%r11", i);
        emit("movq %%r11, %d(%%rax)", i);
    }
    for (; i < left->ctype->size; i += 4) {
        emit("movl %d(%%rcx), %%r11", i);
        emit("movl %%r11, %d(%%rax)", i);
    }
    for (; i < left->ctype->size; i++) {
        emit("movb %d(%%rcx), %%r11", i);
        emit("movb %%r11, %d(%%rax)", i);
    }
    pop("r11");
    pop("rcx");
}

static void emit_decl_init(List *inits, int off) {
    Iter *iter = list_iter(inits);
    while (!iter_end(iter)) {
        Node *node = iter_next(iter);
        assert(node->type == AST_INIT);
        if (node->initval->type == AST_LITERAL) {
            emit_save_literal(node->initval, node->totype, node->initoff + off);
        } else {
            emit_expr(node->initval);
            emit_lsave(node->totype, node->initoff + off);
        }
    }
}

static void emit_uminus(Node *node) {
    emit_expr(node->operand);
    if (is_flotype(node->ctype)) {
        push_xmm(1);
        emit("xorpd %%xmm1, %%xmm1");
        emit("%s %%xmm1, %%xmm0", (node->ctype->type == CTYPE_DOUBLE ? "subsd" : "subss"));
        pop_xmm(1);
    } else {
        emit("neg %%rax");
    }
}

static void emit_pre_inc_dec(Node *node, char *op) {
    emit_expr(node->operand);
    emit("%s $1, %%rax", op);
    emit_store(node->operand);
}

static void emit_post_inc_dec(Node *node, char *op) {
    SAVE;
    emit_expr(node->operand);
    push("rax");
    emit("%s $1, %%rax", op);
    emit_store(node->operand);
    pop("rax");
}

static List *get_arg_types(List *params, List *args) {
    List *r = make_list();
    Iter *i = list_iter(params);
    Iter *j = list_iter(args);
    while (!iter_end(i)) {
        Node *v = iter_next(i);
        Ctype *ptype = iter_next(j);
        list_push(r, ptype ? ptype : v->ctype);
    }
    return r;
}

static void set_reg_nums(List *args) {
    numgp = numfp = 0;
    for (Iter *i = list_iter(args); !iter_end(i);) {
        Node *arg = iter_next(i);
        if (is_flotype(arg->ctype))
            numfp++;
        else
            numgp++;
    }
}

static void emit_je(char *label) {
    emit("test %%rax, %%rax");
    emit("je %s", label);
}

static void emit_label(char *label) {
    emit("%s:", label);
}

static void emit_jmp(char *label) {
    emit("jmp %s", label);
}

static void emit_literal(Node *node) {
    SAVE;
    switch (node->ctype->type) {
    case CTYPE_BOOL:
    case CTYPE_CHAR:
        emit("mov $%d, %%rax", node->ival);
        break;
    case CTYPE_INT:
        emit("mov $%d, %%rax", node->ival);
        break;
    case CTYPE_LONG:
    case CTYPE_LLONG: {
        emit("mov $%lu, %%rax", node->ival);
        break;
    }
    case CTYPE_FLOAT: {
        if (!node->flabel) {
            node->flabel = make_label();
            float fval = node->fval;
            int *p = (int *)&fval;
            emit_noindent(".data");
            emit_label(node->flabel);
            emit(".long %d", *p);
            emit_noindent(".text");
        }
        emit("movss %s(%%rip), %%xmm0", node->flabel);
        break;
    }
    case CTYPE_DOUBLE:
    case CTYPE_LDOUBLE: {
        if (!node->flabel) {
            node->flabel = make_label();
            int *fval = (int *)&node->fval;
            emit_noindent(".data");
            emit_label(node->flabel);
            emit(".long %d", fval[0]);
            emit(".long %d", fval[1]);
            emit_noindent(".text");
        }
        emit("movsd %s(%%rip), %%xmm0", node->flabel);
        break;
    }
    default:
        error("internal error");
    }
}

static void emit_literal_string(Node *node) {
    SAVE;
    if (!node->slabel) {
        node->slabel = make_label();
        emit_noindent(".data");
        emit_label(node->slabel);
        emit(".string \"%s\"", quote_cstring(node->sval));
        emit_noindent(".text");
    }
    emit("lea %s(%%rip), %%rax", node->slabel);
}

static void emit_lvar(Node *node) {
    SAVE;
    ensure_lvar_init(node);
    emit_lload(node->ctype, "rbp", node->loff);
}

static void emit_gvar(Node *node) {
    SAVE;
    emit_gload(node->ctype, node->glabel, 0);
}

static void emit_func_call(Node *node) {
    SAVE;
    bool isptr = (node->type == AST_FUNCPTR_CALL);
    int ireg = 0;
    int xreg = 0;
    List *argtypes = isptr
        ? get_arg_types(node->args, node->fptr->ctype->ptr->params)
        : get_arg_types(node->args, node->ftype->params);
    for (Iter *i = list_iter(argtypes); !iter_end(i);) {
        if (is_flotype(iter_next(i))) {
            if (xreg > 0) push_xmm(xreg);
            xreg++;
        } else {
            push(REGS[ireg++]);
        }
    }
    if (isptr) {
        emit_expr(node->fptr);
        push("rax");
    }
    {
        Iter *i = list_iter(node->args);
        Iter *j = list_iter(argtypes);
        while (!iter_end(i)) {
            Node *v = iter_next(i);
            emit_expr(v);
            Ctype *ptype = iter_next(j);
            if (is_flotype(ptype))
                push_xmm(0);
            else
                push("rax");
        }
    }
    int ir = ireg;
    int xr = xreg;
    for (Iter *i = list_iter(list_reverse(argtypes)); !iter_end(i);) {
        if (is_flotype(iter_next(i)))
            pop_xmm(--xr);
        else
            pop(REGS[--ir]);
    }
    if (isptr) pop("rbx");
    emit("mov $%d, %%eax", xreg);
    if (stackpos % 16)
        emit("sub $8, %%rsp");
    if (isptr) {
        emit("call *%%rbx");
    } else {
        emit("call %s", node->fname);
    }<commit_msg>Set # of flonum args to %al only if function has varargs.<commit_after>// Copyright 2012 <NAME> <<EMAIL>>
// This program is free software licensed under the MIT license.

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "8cc.h"

static char *REGS[] = {"rdi", "rsi", "rdx", "rcx", "r8", "r9"};
static int TAB = 8;
static List *functions = &EMPTY_LIST;
static char *lbreak;
static char *lcontinue;
static char *lswitch;
static int stackpos;
static int numgp;
static int numfp;

static void emit_expr(Node *node);
static void emit_decl_init(List *inits, int off);
static void emit_data(Node *v, int off, int depth);

#define REGAREA_SIZE 304

#define emit(...)        emitf(__LINE__, "\t" __VA_ARGS__)
#define emit_noindent(...)  emitf(__LINE__, __VA_ARGS__)

#ifdef __GNUC__
#define SAVE                                                            \
    int save_hook __attribute__((unused, cleanup(pop_function)));       \
    list_push(functions, (void *)__func__)

static void pop_function(void *ignore) {
    list_pop(functions);
}
#else
#define SAVE
#endif

static char *get_caller_list(void) {
    String *s = make_string();
    for (Iter *i = list_iter(functions); !iter_end(i);) {
        string_appendf(s, "%s", iter_next(i));
        if (!iter_end(i))
            string_appendf(s, " -> ");
    }
    return get_cstring(s);
}

static void emitf(int line, char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    int col = vprintf(fmt, args);
    va_end(args);

    for (char *p = fmt; *p; p++)
        if (*p == '\t')
            col += TAB - 1;
    int space = (28 - col) > 0 ? (30 - col) : 2;
    printf("%*c %s:%d\n", space, '#', get_caller_list(), line);
}

static char *get_int_reg(Ctype *ctype, char r) {
    assert(r == 'a' || r == 'c');
    switch (ctype->size) {
    case 1: return (r == 'a') ? "al" : "cl";
    case 2: return (r == 'a') ? "ax" : "cx";
    case 4: return (r == 'a') ? "eax" : "ecx";
    case 8: return (r == 'a') ? "rax" : "rcx";
    default:
        error("Unknown data size: %s: %d", c2s(ctype), ctype->size);
    }
}

static char *get_load_inst(Ctype *ctype) {
    switch (ctype->size) {
    case 1: return "movsbq";
    case 2: return "movswq";
    case 4: return "movslq";
    case 8: return "mov";
    default:
        error("Unknown data size: %s: %d", c2s(ctype), ctype->size);
    }
}

static void push_xmm(int reg) {
    SAVE;
    emit("sub $8, %%rsp");
    emit("movsd %%xmm%d, (%%rsp)", reg);
    stackpos += 8;
}

static void pop_xmm(int reg) {
    SAVE;
    emit("movsd (%%rsp), %%xmm%d", reg);
    emit("add $8, %%rsp");
    stackpos -= 8;
    assert(stackpos >= 0);
}

static void push(char *reg) {
    SAVE;
    emit("push %%%s", reg);
    stackpos += 8;
}

static void pop(char *reg) {
    SAVE;
    emit("pop %%%s", reg);
    stackpos -= 8;
    assert(stackpos >= 0);
}

static void emit_gload(Ctype *ctype, char *label, int off) {
    SAVE;
    if (ctype->type == CTYPE_ARRAY) {
        if (off)
            emit("lea %s+%d(%%rip), %%rax", label, off);
        else
            emit("lea %s(%%rip), %%rax", label);
        return;
    }
    char *inst = get_load_inst(ctype);
    emit("%s %s+%d(%%rip), %%rax", inst, label, off);
}

static void emit_toint(Ctype *ctype) {
    SAVE;
    if (ctype->type == CTYPE_FLOAT)
        emit("cvttss2si %%xmm0, %%eax");
    else if (ctype->type == CTYPE_FLOAT)
        emit("cvttsd2si %%xmm0, %%eax");
}

static void emit_lload(Ctype *ctype, char *base, int off) {
    SAVE;
    if (ctype->type == CTYPE_ARRAY) {
        emit("lea %d(%%%s), %%rax", off, base);
    } else if (ctype->type == CTYPE_FLOAT) {
        emit("movss %d(%%%s), %%xmm0", off, base);
    } else if (ctype->type == CTYPE_DOUBLE || ctype->type == CTYPE_LDOUBLE) {
        emit("movsd %d(%%%s), %%xmm0", off, base);
    } else {
        char *inst = get_load_inst(ctype);
        emit("%s %d(%%%s), %%rax", inst, off, base);
    }
}

static void maybe_convert_bool(Ctype *ctype) {
    if (ctype->type == CTYPE_BOOL) {
        emit("test %%rax, %%rax");
        emit("setne %%al");
    }
}

static void emit_gsave(char *varname, Ctype *ctype, int off) {
    SAVE;
    assert(ctype->type != CTYPE_ARRAY);
    maybe_convert_bool(ctype);
    char *reg = get_int_reg(ctype, 'a');
    if (off)
        emit("mov %%%s, %s+%d(%%rip)", reg, varname, off);
    else
        emit("mov %%%s, %s(%%rip)", reg, varname);
}

static void emit_lsave(Ctype *ctype, int off) {
    SAVE;
    if (ctype->type == CTYPE_FLOAT) {
        emit("movss %%xmm0, %d(%%rbp)", off);
    } else if (ctype->type == CTYPE_DOUBLE) {
        emit("movsd %%xmm0, %d(%%rbp)", off);
    } else {
        maybe_convert_bool(ctype);
        char *reg = get_int_reg(ctype, 'a');
        emit("mov %%%s, %d(%%rbp)", reg, off);
    }
}

static void emit_assign_deref_int(Ctype *ctype, int off) {
    SAVE;
    emit("mov (%%rsp), %%rcx");
    char *reg = get_int_reg(ctype, 'c');
    if (off)
        emit("mov %%%s, %d(%%rax)", reg, off);
    else
        emit("mov %%%s, (%%rax)", reg);
    pop("rax");
}

static void emit_assign_deref(Node *var) {
    SAVE;
    push("rax");
    emit_expr(var->operand);
    emit_assign_deref_int(var->operand->ctype->ptr, 0);
}

static void emit_pointer_arith(char op, Node *left, Node *right) {
    SAVE;
    emit_expr(left);
    push("rax");
    emit_expr(right);
    int size = left->ctype->ptr->size;
    if (size > 1)
        emit("imul $%d, %%rax", size);
    emit("mov %%rax, %%rcx");
    pop("rax");
    emit("add %%rcx, %%rax");
}

static void emit_zero_filler(int start, int end) {
    for (; start <= end - 4; start += 4)
        emit("movl $0, %d(%%rbp)", start);
    for (; start < end; start++)
        emit("movb $0, %d(%%rbp)", start);
}

static void ensure_lvar_init(Node *node) {
    assert(node->type == AST_LVAR);
    if (node->lvarinit)
        emit_decl_init(node->lvarinit, node->loff);
    node->lvarinit = NULL;
}

static void emit_assign_struct_ref(Node *struc, Ctype *field, int off) {
    SAVE;
    switch (struc->type) {
    case AST_LVAR:
        ensure_lvar_init(struc);
        emit_lsave(field, struc->loff + field->offset + off);
        break;
    case AST_GVAR:
        emit_gsave(struc->varname, field, field->offset + off);
        break;
    case AST_STRUCT_REF:
        emit_assign_struct_ref(struc->struc, field, off + struc->ctype->offset);
        break;
    case AST_DEREF:
        push("rax");
        emit_expr(struc->operand);
        emit_assign_deref_int(field, field->offset + off);
        break;
    default:
        error("internal error: %s", a2s(struc));
    }
}

static void emit_load_struct_ref(Node *struc, Ctype *field, int off) {
    SAVE;
    switch (struc->type) {
    case AST_LVAR:
        ensure_lvar_init(struc);
        emit_lload(field, "rbp", struc->loff + field->offset + off);
        break;
    case AST_GVAR:
        emit_gload(field, struc->varname, field->offset + off);
        break;
    case AST_STRUCT_REF:
        emit_load_struct_ref(struc->struc, field, struc->ctype->offset + off);
        break;
    case AST_DEREF:
        emit_expr(struc->operand);
        emit_lload(field, "rax", field->offset + off);
        break;
    default:
        error("internal error: %s", a2s(struc));
    }
}

static void emit_store(Node *var) {
    SAVE;
    switch (var->type) {
    case AST_DEREF: emit_assign_deref(var); break;
    case AST_STRUCT_REF: emit_assign_struct_ref(var->struc, var->ctype, 0); break;
    case AST_LVAR:
        ensure_lvar_init(var);
        emit_lsave(var->ctype, var->loff);
        break;
    case AST_GVAR: emit_gsave(var->varname, var->ctype, 0); break;
    default: error("internal error");
    }
}

static void emit_to_bool(Ctype *ctype) {
    SAVE;
    if (is_flotype(ctype)) {
        push_xmm(1);
        emit("xorpd %%xmm1, %%xmm1");
        emit("%s %%xmm1, %%xmm0", (ctype->type == CTYPE_FLOAT) ? "ucomiss" : "ucomisd");
        emit("setne %%al");
        pop_xmm(1);
    } else {
        emit("cmp $0, %%rax");
        emit("setne %%al");
    }
    emit("movzb %%al, %%eax");
}

static void emit_comp(char *inst, Node *node) {
    SAVE;
    if (is_flotype(node->left->ctype)) {
        emit_expr(node->left);
        push_xmm(0);
        emit_expr(node->right);
        pop_xmm(1);
        if (node->left->ctype->type == CTYPE_FLOAT)
            emit("ucomiss %%xmm0, %%xmm1");
        else
            emit("ucomisd %%xmm0, %%xmm1");
    } else {
        emit_expr(node->left);
        push("rax");
        emit_expr(node->right);
        pop("rcx");
        emit("cmp %%rax, %%rcx");
    }
    emit("%s %%al", inst);
    emit("movzb %%al, %%eax");
}

static void emit_binop_int_arith(Node *node) {
    SAVE;
    char *op = NULL;
    switch (node->type) {
    case '+': op = "add"; break;
    case '-': op = "sub"; break;
    case '*': op = "imul"; break;
    case '^': op = "xor"; break;
    case OP_SAL: op = "sal"; break;
    case OP_SAR: op = "sar"; break;
    case OP_SHR: op = "shr"; break;
    case '/': case '%': break;
    default: error("invalid operator '%d'", node->type);
    }
    emit_expr(node->left);
    push("rax");
    emit_expr(node->right);
    emit("mov %%rax, %%rcx");
    pop("rax");
    if (node->type == '/' || node->type == '%') {
        emit("cqto");
        emit("idiv %%rcx");
        if (node->type == '%')
            emit("mov %%edx, %%eax");
    } else if (node->type == OP_SAL || node->type == OP_SAR || node->type == OP_SHR) {
        emit("%s %%cl, %%%s", op, get_int_reg(node->left->ctype, 'a'));
    } else {
        emit("%s %%rcx, %rax", op);
    }
}

static void emit_binop_float_arith(Node *node) {
    SAVE;
    char *op;
    bool isdouble = (node->ctype->type == CTYPE_DOUBLE);
    switch (node->type) {
    case '+': op = (isdouble ? "addsd" : "addss"); break;
    case '-': op = (isdouble ? "subsd" : "subss"); break;
    case '*': op = (isdouble ? "mulsd" : "mulss"); break;
    case '/': op = (isdouble ? "divsd" : "divss"); break;
    default: error("invalid operator '%d'", node->type);
    }
    emit_expr(node->left);
    push_xmm(0);
    emit_expr(node->right);
    emit("%s %%xmm0, %%xmm1", (isdouble ? "movsd" : "movss"));
    pop_xmm(0);
    emit("%s %%xmm1, %%xmm0", op);
}

static void emit_load_convert(Ctype *to, Ctype *from) {
    SAVE;
    if (is_inttype(from) && to->type == CTYPE_FLOAT)
        emit("cvtsi2ss %%eax, %%xmm0");
    else if (is_inttype(from) && to->type == CTYPE_DOUBLE)
        emit("cvtsi2sd %%eax, %%xmm0");
    else if (from->type == CTYPE_FLOAT && to->type == CTYPE_DOUBLE)
        emit("cvtps2pd %%xmm0, %%xmm0");
    else if (from->type == CTYPE_DOUBLE && to->type == CTYPE_FLOAT)
        emit("cvtpd2ps %%xmm0, %%xmm0");
    else if (to->type == CTYPE_BOOL)
        emit_to_bool(from);
    else if (is_inttype(to))
        emit_toint(from);
}

static void emit_ret(void) {
    SAVE;
    emit("leave");
    emit("ret");
}

static void emit_binop(Node *node) {
    SAVE;
    if (node->ctype->type == CTYPE_PTR) {
        emit_pointer_arith(node->type, node->left, node->right);
        return;
    }
    switch (node->type) {
    case '<': emit_comp("setl", node); return;
    case '>': emit_comp("setg", node); return;
    case OP_EQ: emit_comp("sete", node); return;
    case OP_GE: emit_comp("setge", node); return;
    case OP_LE: emit_comp("setle", node); return;
    case OP_NE: emit_comp("setne", node); return;
    }
    if (is_inttype(node->ctype))
        emit_binop_int_arith(node);
    else if (is_flotype(node->ctype))
        emit_binop_float_arith(node);
    else
        error("internal error");
}

static void emit_save_literal(Node *node, Ctype *totype, int off) {
    switch (totype->type) {
    case CTYPE_BOOL:  emit("movb $%d, %d(%%rbp)", !!node->ival, off); break;
    case CTYPE_CHAR:  emit("movb $%d, %d(%%rbp)", node->ival, off); break;
    case CTYPE_SHORT: emit("movw $%d, %d(%%rbp)", node->ival, off); break;
    case CTYPE_INT:   emit("movl $%d, %d(%%rbp)", node->ival, off); break;
    case CTYPE_LONG:
    case CTYPE_LLONG:
    case CTYPE_PTR: {
        unsigned long ival = node->ival;
        emit("movl $%lu, %d(%%rbp)", ival & ((1L << 32) - 1), off);
        emit("movl $%lu, %d(%%rbp)", ival >> 32, off + 4);
        break;
    }
    case CTYPE_FLOAT: {
        float fval = node->fval;
        int *p = (int *)&fval;
        emit("movl $%u, %d(%%rbp)", *p, off);
        break;
    }
    case CTYPE_DOUBLE: {
        long *p = (long *)&node->fval;
        emit("movl $%lu, %d(%%rbp)", *p & ((1L << 32) - 1), off);
        emit("movl $%lu, %d(%%rbp)", *p >> 32, off + 4);
        break;
    }
    default:
        error("internal error: <%s> <%s> <%d>", a2s(node), c2s(totype), off);
    }
}

static void emit_addr(Node *node) {
    switch (node->type) {
    case AST_LVAR:
        ensure_lvar_init(node);
        emit("lea %d(%%rbp), %%rax", node->loff);
        break;
    case AST_GVAR:
        emit("lea %s(%%rip), %%rax", node->glabel);
        break;
    case AST_DEREF:
        emit_expr(node->operand);
        break;
    case AST_STRUCT_REF:
        emit_addr(node->struc);
        emit("add $%d, %%rax", node->ctype->offset);
        break;
    default:
        error("internal error: %s", a2s(node));
    }
}

static void emit_copy_struct(Node *left, Node *right) {
    push("rcx");
    push("r11");
    emit_addr(right);
    emit("mov %%rax, %%rcx");
    emit_addr(left);
    int i = 0;
    for (; i < left->ctype->size; i += 8) {
        emit("movq %d(%%rcx), %%r11", i);
        emit("movq %%r11, %d(%%rax)", i);
    }
    for (; i < left->ctype->size; i += 4) {
        emit("movl %d(%%rcx), %%r11", i);
        emit("movl %%r11, %d(%%rax)", i);
    }
    for (; i < left->ctype->size; i++) {
        emit("movb %d(%%rcx), %%r11", i);
        emit("movb %%r11, %d(%%rax)", i);
    }
    pop("r11");
    pop("rcx");
}

static void emit_decl_init(List *inits, int off) {
    Iter *iter = list_iter(inits);
    while (!iter_end(iter)) {
        Node *node = iter_next(iter);
        assert(node->type == AST_INIT);
        if (node->initval->type == AST_LITERAL) {
            emit_save_literal(node->initval, node->totype, node->initoff + off);
        } else {
            emit_expr(node->initval);
            emit_lsave(node->totype, node->initoff + off);
        }
    }
}

static void emit_uminus(Node *node) {
    emit_expr(node->operand);
    if (is_flotype(node->ctype)) {
        push_xmm(1);
        emit("xorpd %%xmm1, %%xmm1");
        emit("%s %%xmm1, %%xmm0", (node->ctype->type == CTYPE_DOUBLE ? "subsd" : "subss"));
        pop_xmm(1);
    } else {
        emit("neg %%rax");
    }
}

static void emit_pre_inc_dec(Node *node, char *op) {
    emit_expr(node->operand);
    emit("%s $1, %%rax", op);
    emit_store(node->operand);
}

static void emit_post_inc_dec(Node *node, char *op) {
    SAVE;
    emit_expr(node->operand);
    push("rax");
    emit("%s $1, %%rax", op);
    emit_store(node->operand);
    pop("rax");
}

static List *get_arg_types(List *params, List *args) {
    List *r = make_list();
    Iter *i = list_iter(params);
    Iter *j = list_iter(args);
    while (!iter_end(i)) {
        Node *v = iter_next(i);
        Ctype *ptype = iter_next(j);
        list_push(r, ptype ? ptype : v->ctype);
    }
    return r;
}

static void set_reg_nums(List *args) {
    numgp = numfp = 0;
    for (Iter *i = list_iter(args); !iter_end(i);) {
        Node *arg = iter_next(i);
        if (is_flotype(arg->ctype))
            numfp++;
        else
            numgp++;
    }
}

static void emit_je(char *label) {
    emit("test %%rax, %%rax");
    emit("je %s", label);
}

static void emit_label(char *label) {
    emit("%s:", label);
}

static void emit_jmp(char *label) {
    emit("jmp %s", label);
}

static void emit_literal(Node *node) {
    SAVE;
    switch (node->ctype->type) {
    case CTYPE_BOOL:
    case CTYPE_CHAR:
        emit("mov $%d, %%rax", node->ival);
        break;
    case CTYPE_INT:
        emit("mov $%d, %%rax", node->ival);
        break;
    case CTYPE_LONG:
    case CTYPE_LLONG: {
        emit("mov $%lu, %%rax", node->ival);
        break;
    }
    case CTYPE_FLOAT: {
        if (!node->flabel) {
            node->flabel = make_label();
            float fval = node->fval;
            int *p = (int *)&fval;
            emit_noindent(".data");
            emit_label(node->flabel);
            emit(".long %d", *p);
            emit_noindent(".text");
        }
        emit("movss %s(%%rip), %%xmm0", node->flabel);
        break;
    }
    case CTYPE_DOUBLE:
    case CTYPE_LDOUBLE: {
        if (!node->flabel) {
            node->flabel = make_label();
            int *fval = (int *)&node->fval;
            emit_noindent(".data");
            emit_label(node->flabel);
            emit(".long %d", fval[0]);
            emit(".long %d", fval[1]);
            emit_noindent(".text");
        }
        emit("movsd %s(%%rip), %%xmm0", node->flabel);
        break;
    }
    default:
        error("internal error");
    }
}

static void emit_literal_string(Node *node) {
    SAVE;
    if (!node->slabel) {
        node->slabel = make_label();
        emit_noindent(".data");
        emit_label(node->slabel);
        emit(".string \"%s\"", quote_cstring(node->sval));
        emit_noindent(".text");
    }
    emit("lea %s(%%rip), %%rax", node->slabel);
}

static void emit_lvar(Node *node) {
    SAVE;
    ensure_lvar_init(node);
    emit_lload(node->ctype, "rbp", node->loff);
}

static void emit_gvar(Node *node) {
    SAVE;
    emit_gload(node->ctype, node->glabel, 0);
}

static void emit_func_call(Node *node) {
    SAVE;
    bool isptr = (node->type == AST_FUNCPTR_CALL);
    Ctype *ftype = isptr ? node->fptr->ctype->ptr : node->ftype;
    int ireg = 0;
    int xreg = 0;
    List *argtypes = get_arg_types(node->args, ftype->params);
    for (Iter *i = list_iter(argtypes); !iter_end(i);) {
        if (is_flotype(iter_next(i))) {
            if (xreg > 0) push_xmm(xreg);
            xreg++;
        } else {
            push(REGS[ireg++]);
        }
    }
    if (isptr) {
        emit_expr(node->fptr);
        push("rax");
    }
    {
        Iter *i = list_iter(node->args);
        Iter *j = list_iter(argtypes);
        while (!iter_end(i)) {
            Node *v = iter_next(i);
            emit_expr(v);
            Ctype *ptype = iter_next(j);
            if (is_flotype(ptype))
                push_xmm(0);
            else
                push("rax");
        }
    }
    int ir = ireg;
    int xr = xreg;
    for (Iter *i = list_iter(list_reverse(argtypes)); !iter_end(i);) {
        if (is_flotype(iter_next(i)))
            pop_xmm(--xr);
        else
            pop(REGS[--ir]);
    }
    if (isptr) pop("rbx");
    if (ftype->hasva)
        emit("mov $%d, %%eax", xreg);
    if (stackpos % 16)
        emit("sub $8, %%rsp");
    if (isptr) {
        emit("call *%%rbx");
    } else {
        emit("call %s", node->fname);
    }
{'fraction_non_alphanumeric': 0.18932769779421477, 'fraction_numerical': 0.0073966450931184785, 'mean_word_length': 2.4912846998063265, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 388, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3409883', 'n_tokens_mistral': 14702, 'n_tokens_neox': 14046, 'n_words': 3190}