cosmopolitan/third_party/chibicc/asm.c

756 lines
22 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "third_party/chibicc/chibicc.h"
#define PRECIOUS 0b1111000000101000 // bx,bp,r12-r15
static const char kGreg[4][16][5] = {
{"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", "r8b", "r9b", "r10b",
"r11b", "r12b", "r13b", "r14b", "r15b"},
{"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", "r8w", "r9w", "r10w",
"r11w", "r12w", "r13w", "r14w", "r15w"},
{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", "r8d", "r9d",
"r10d", "r11d", "r12d", "r13d", "r14d", "r15d"},
{"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15"},
};
StaticAsm *staticasms;
static void DecodeAsmConstraints(AsmOperand *op) {
int i;
char c;
for (i = 0;;) {
switch ((c = op->str[i++])) {
case '\0':
case ',': // alternative group
return; // todo: read combos
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': // reference
case '=': // output
case '+': // output and input
op->flow = c;
break;
case 'm': // memory
case 'o': // memory offsetable
op->type |= kAsmMem;
op->regmask |= 0b1111111111111111;
break;
case 'i': // int literal, c/asm constexpr, ld embedding
case 'n': // integer literal or compiler constexpr?
case 's': // integer constexpr but not literal (or known at link time?)
case 'M': // i∊[0,3] for index scaling, e.g. "mov\t(?,?,1<<%0),?"
case 'I': // i∊[0,31] 5 bits for 32-bit shifts
case 'J': // i∊[0,63] 6 bits for 64-bit shifts
case 'N': // i∊[0,255] in/out immediate byte
case 'K': // i∊[-128,127] signed byte integer
case 'Z': // i∊[0,2³²) for zero-extending
case 'L': // i∊{0xFF,0xFFFF,0xFFFFFFFF}
op->type |= kAsmImm;
break;
case 'a': // ax
op->regmask |= 0b0000000000000001;
op->type |= kAsmReg;
break;
case 'c': // cx
op->regmask |= 0b0000000000000010;
op->type |= kAsmReg;
break;
case 'd': // dx
op->regmask |= 0b0000000000000100;
op->type |= kAsmReg;
break;
case 'b': // bx
op->regmask |= 0b0000000000001000;
op->type |= kAsmReg;
break;
case 'S': // si
op->regmask |= 0b0000000001000000;
op->type |= kAsmReg;
break;
case 'D': // di
op->regmask |= 0b0000000010000000;
op->type |= kAsmReg;
break;
case 'r': // general register
op->regmask |= 0b1111111111111111;
op->type |= kAsmReg;
break;
case 'q': // greg lo-byte accessible
op->regmask |= 0b1111111111111111;
op->type |= kAsmReg;
break;
case 'Q': // greg hi-byte accessible
op->regmask |= 0b0000000000001111;
op->type |= kAsmReg;
break;
case 'U': // greg call-clobbered
op->regmask |= 0b0000111111000111;
op->type |= kAsmReg;
break;
case 'R': // greg all models
op->regmask |= 0b0000000011111111;
op->type |= kAsmReg;
break;
case 'l': // index register
op->regmask |= 0b1111111111101111;
op->type |= kAsmReg;
break;
case 'y': // mmx
op->type |= kAsmMmx;
op->regmask |= 0b0000000011111111;
break;
case 'x': // xmm
op->type |= kAsmXmm;
op->regmask |= 0b1111111111111111;
break;
case 'g': // rmi
op->type |= kAsmImm | kAsmMem | kAsmReg;
op->regmask |= 0b1111111111111111;
break;
case 'X': // anything
op->type |= kAsmImm | kAsmMem | kAsmReg | kAsmXmm | kAsmFpu | kAsmRaw;
op->regmask |= 0b1111111111111111;
op->x87mask |= 0b11111111;
break;
case 't': // %st
op->type |= kAsmFpu;
op->x87mask |= 0b00000001;
break;
case 'u': // %st(1)
op->type |= kAsmFpu;
op->x87mask |= 0b00000010;
break;
case 'f': // %st(0..7)
op->type |= kAsmFpu;
op->x87mask |= 0b11111111;
break;
case 'A': // ax+dx
error_tok(op->tok, "ax dx constraint not implemented");
case '@': // flags
if (op->flow != '=' || strlen(op->str + i) < 3 ||
(op->str[i] != 'c' || op->str[i + 1] != 'c')) {
error_tok(op->tok, "invalid flag constraint");
}
if (!is_integer(op->node->ty) || op->node->ty->size != 1) {
error_tok(op->node->tok, "invalid output flag type");
}
op->type = kAsmFlag;
op->predicate = i + 2;
return;
default:
break;
}
}
}
static bool IsLvalue(AsmOperand *op) {
switch (op->node->kind) {
case ND_VAR:
case ND_DEREF:
case ND_MEMBER:
case ND_VLA_PTR:
return true;
default:
return false;
}
}
static bool CanUseReg(Node **n) {
if ((*n)->ty->kind == TY_ARRAY) {
*n = new_cast(*n, pointer_to((*n)->ty->base));
return true;
}
return is_integer((*n)->ty) || (*n)->ty->kind == TY_PTR;
}
static bool CanUseXmm(Node *n) {
return n->ty->vector_size == 16 || n->ty->kind == TY_FLOAT ||
n->ty->kind == TY_DOUBLE || n->ty->kind == TY_PTR ||
(n->ty->kind == TY_ARRAY && n->ty->size == 16);
}
static bool CanUseMmx(Node *n) {
return n->ty->kind == TY_FLOAT || n->ty->kind == TY_DOUBLE ||
n->ty->kind == TY_PTR || (n->ty->kind == TY_ARRAY && n->ty->size == 8);
}
static int PickAsmReferenceType(AsmOperand *op, AsmOperand *ref) {
switch (ref->type) {
case kAsmImm:
case kAsmMem:
error_tok(op->tok, "bad reference");
case kAsmReg:
if (!CanUseReg(&op->node)) {
error_tok(op->tok, "expected integral expression");
}
op->regmask = 0;
return ref->type;
case kAsmXmm:
if (!CanUseXmm(op->node)) {
error_tok(op->tok, "expected xmm expression");
}
return ref->type;
case kAsmFpu:
if (op->node->ty->kind != TY_LDOUBLE) {
error_tok(op->tok, "expected long double expression");
}
op->x87mask = 0;
return ref->type;
default:
UNREACHABLE();
}
}
static int PickAsmOperandType(Asm *a, AsmOperand *op) {
if (op->flow == '=' || op->flow == '+') {
op->type &= ~kAsmImm;
if (!IsLvalue(op)) error_tok(op->tok, "lvalue required");
}
if ((op->type & kAsmImm) && is_const_expr(op->node)) {
op->val = eval2(op->node, &op->label);
return kAsmImm;
}
if ((op->type & kAsmMem) && op->node->ty->kind != TY_VOID) return kAsmMem;
if ((op->type & kAsmFpu) && op->node->ty->kind == TY_LDOUBLE) return kAsmFpu;
if ((op->type & kAsmXmm) && CanUseXmm(op->node)) return kAsmXmm;
if ((op->type & kAsmMmx) && CanUseMmx(op->node)) return kAsmMmx;
if ((op->type & kAsmReg) && CanUseReg(&op->node)) return kAsmReg;
if (op->type & kAsmFlag) return kAsmFlag;
if (op->type & kAsmRaw) return kAsmRaw;
error_tok(op->tok, "constraint mismatch");
}
static Token *ParseAsmOperand(Asm *a, AsmOperand *op, Token *tok) {
int i;
op->tok = tok;
op->str = ConsumeStringLiteral(&tok, tok);
tok = skip(tok, '(');
op->node = expr(&tok, tok);
add_type(op->node);
DecodeAsmConstraints(op);
if (isdigit(op->flow)) {
if ((i = op->flow - '0') >= a->n) error_tok(op->tok, "bad reference");
op->type = PickAsmReferenceType(op, a->ops + i);
} else {
op->type = PickAsmOperandType(a, op);
}
return skip(tok, ')');
}
static Token *ParseAsmOperands(Asm *a, Token *tok) {
if (EQUAL(tok, ":")) return tok;
for (;;) {
if (a->n == ARRAYLEN(a->ops)) {
error_tok(tok, "too many asm operands");
}
tok = ParseAsmOperand(a, &a->ops[a->n], tok);
++a->n;
if (!EQUAL(tok, ",")) break;
tok = skip(tok, ',');
}
return tok;
}
static void CouldNotAllocateRegister(AsmOperand *op) {
error_tok(op->tok, "could not allocate register");
}
static void PickAsmRegisters(Asm *a) {
int i, j, m, regset, xmmset, x87sts;
regset = 0b0000111111000111; // exclude bx,sp,bp,r12-r15
xmmset = 0b1111111111111111;
x87sts = 0b0000000011111111;
regset ^= regset & a->regclob; // don't allocate from clobber list
xmmset ^= xmmset & a->xmmclob;
x87sts ^= x87sts & a->x87clob;
for (j = 1; j <= 16; ++j) { // iterate from most to least restrictive
for (i = 0; i < a->n; ++i) {
switch (a->ops[i].type) {
case kAsmMem:
case kAsmReg:
if (!(m = a->ops[i].regmask)) break;
if (popcnt(m) != j) break;
if (!(m &= regset)) CouldNotAllocateRegister(&a->ops[i]);
regset &= ~(1 << (a->ops[i].reg = bsf(m)));
a->ops[i].regmask = 0;
break;
case kAsmXmm:
if (!(m = a->ops[i].regmask)) break;
if (!(m &= xmmset)) CouldNotAllocateRegister(&a->ops[i]);
xmmset &= ~(1 << (a->ops[i].reg = bsf(m)));
a->ops[i].regmask = 0;
break;
case kAsmFpu:
if (!(m = a->ops[i].x87mask)) break;
if (popcnt(m) != j) break;
if (!(m &= x87sts)) CouldNotAllocateRegister(&a->ops[i]);
x87sts &= ~(1 << (a->ops[i].reg = bsf(m)));
a->ops[i].x87mask = 0;
break;
default:
a->ops[i].regmask = 0;
a->ops[i].x87mask = 0;
break;
}
}
}
}
static void MarkUsedAsmOperands(Asm *a) {
char c, *p;
if (!a->isgnu) return;
for (p = a->str; (c = *p++);) {
if (c == '%') {
if (!(c = *p++)) error_tok(a->tok, "unexpected nul");
if (c == '%') continue;
if (!isdigit(c)) {
if (!(c = *p++)) error_tok(a->tok, "unexpected nul");
if (!isdigit(c)) {
error_tok(a->tok, "bad asm specifier");
}
}
a->ops[c - '0'].isused = true;
}
}
}
static int GetIndexOfRegisterName(const char *s) {
int i, j;
for (i = 0; i < 16; ++i) {
for (j = 0; j < 4; ++j) {
if (!strcmp(s, kGreg[j][i])) {
return i;
}
}
}
return -1;
}
static Token *ParseAsmClobbers(Asm *a, Token *tok) {
int i;
char *s;
Token *stok;
for (;;) {
stok = tok;
s = ConsumeStringLiteral(&tok, tok);
if (*s == '%') ++s;
if (!strcmp(s, "cc")) {
a->flagclob = true;
} else if ((i = GetIndexOfRegisterName(s)) != -1) {
a->regclob |= 1 << i;
} else if (startswith(s, "xmm") && isdigit(s[3]) &&
(!s[4] || isdigit(s[4]))) {
i = s[3] - '0';
if (s[4]) {
i *= 10;
i += s[4] - '0';
}
i &= 15;
a->xmmclob |= 1 << i;
} else if (!strcmp(s, "st")) {
a->x87clob |= 1;
} else if (startswith(s, "st(") && isdigit(s[3]) && s[4] == ')') {
i = s[3] - '0';
i &= 7;
a->x87clob |= 1 << i;
} else if (!strcmp(s, "memory")) {
/* do nothing */
} else {
error_tok(stok, "unknown clobber register");
}
if (!EQUAL(tok, ",")) break;
tok = skip(tok, ',');
}
return tok;
}
// parses ansi c11 asm statement officially defined as follows
//
// asm-stmt = "asm" ("volatile" | "inline")* "(" string-literal ")"
//
// gnu c defines a notation for inputs, outputs, and clobbers, e.g.
//
// asm("foo %1,%0"
// : "=r"(x)
// : "r"(x)
// : "cc");
//
Asm *asm_stmt(Token **rest, Token *tok) {
Asm *a = calloc(1, sizeof(Asm));
tok = tok->next;
while (EQUAL(tok, "volatile") || EQUAL(tok, "inline")) tok = tok->next;
tok = skip(tok, '(');
a->tok = tok;
a->str = ConsumeStringLiteral(&tok, tok);
if (!EQUAL(tok, ")")) {
a->isgnu = true;
tok = skip(tok, ':');
tok = ParseAsmOperands(a, tok);
if (!EQUAL(tok, ")")) {
tok = skip(tok, ':');
tok = ParseAsmOperands(a, tok);
if (!EQUAL(tok, ")")) {
tok = skip(tok, ':');
tok = ParseAsmClobbers(a, tok);
}
}
}
PickAsmRegisters(a);
MarkUsedAsmOperands(a);
*rest = skip(tok, ')');
return a;
}
static void PrintAsmConstant(AsmOperand *op) {
if (op->label) {
fprintf(output_stream, "%s%+ld", *op->label, op->val);
} else {
fprintf(output_stream, "%ld", op->val);
}
}
static void EmitAsmSpecifier(AsmOperand *op, int q, int z) {
if (!q) {
switch (op->type) {
case kAsmImm:
fputc('$', output_stream);
PrintAsmConstant(op);
break;
case kAsmMem:
fprintf(output_stream, "(%%%s)", kGreg[3][op->reg]);
break;
case kAsmReg:
fprintf(output_stream, "%%%s", kGreg[z][op->reg]);
break;
case kAsmXmm:
fprintf(output_stream, "%%xmm%d", op->reg);
break;
case kAsmFpu:
fprintf(output_stream, "%%st(%d)", op->reg);
break;
case kAsmRaw:
fprintf(output_stream, "%.*s", op->node->tok->len, op->node->tok->loc);
break;
default:
UNREACHABLE();
}
} else {
switch (q) {
case 'h': // hi byte
fprintf(output_stream, "%%%ch", "acdb"[op->reg]);
break;
case 'b': // lo byte
fprintf(output_stream, "%%%s", kGreg[0][op->reg]);
break;
case 'w': // word
fprintf(output_stream, "%%%s", kGreg[1][op->reg]);
break;
case 'k': // dword
fprintf(output_stream, "%%%s", kGreg[2][op->reg]);
break;
case 'q': // qword
fprintf(output_stream, "%%%s", kGreg[3][op->reg]);
break;
case 'z': // print suffix
fprintf(output_stream, "%c", "bwlq"[z]);
break;
case 'p': // print raw
fprintf(output_stream, "%.*s", op->node->tok->len, op->node->tok->loc);
break;
case 'a': // print address
PrintAsmConstant(op);
break;
case 'c': // print constant w/o punctuation
PrintAsmConstant(op);
break;
case 'P': // print w/ @plt
PrintAsmConstant(op);
fprintf(output_stream, "@plt");
break;
case 'l': // print label w/o punctuation
if (!op->label) {
error_tok(op->tok, "qualifier expected label");
}
fprintf(output_stream, "%s", *op->label);
break;
case 'V': // print register w/o punctuation
if (op->type != kAsmReg) {
error_tok(op->tok, "qualifier expected register");
}
fprintf(output_stream, "%s", kGreg[z][op->reg]);
break;
default:
error_tok(op->tok, "bad asm qualifier %%%`'c", q);
}
}
}
static char *HandleAsmSpecifier(Asm *a, char *p) {
int c, i, q, z;
if (!(c = *p++)) error_tok(a->tok, "unexpected nul");
if (c == '%') {
fputc('%', output_stream);
return p;
}
if (c == '=') {
fprintf(output_stream, "%d", count());
return p;
}
if (isdigit(c)) {
q = '\0';
} else {
q = c;
if (!(c = *p++)) error_tok(a->tok, "unexpected nul");
if (!isdigit(c)) {
error_tok(a->tok, "bad asm specifier at offset %d", p - a->str);
}
}
if ((i = c - '0') >= a->n) {
error_tok(a->tok, "bad asm reference at offset %d", p - a->str);
}
z = bsr(a->ops[i].node->ty->size);
if (z > 3 && a->ops[i].type == kAsmReg) {
error_tok(a->tok, "bad asm op size");
}
EmitAsmSpecifier(&a->ops[i], q, z);
return p;
}
static void EmitAsmText(Asm *a) {
char c, *p;
if (*a->str) {
if (a->isgnu) {
flushln();
fprintf(output_stream, "\t");
for (p = a->str;;) {
switch ((c = *p++)) {
case '\0':
fputc('\n', output_stream);
return;
case '%':
p = HandleAsmSpecifier(a, p);
break;
default:
fputc(c, output_stream);
break;
}
}
} else {
println("\t%s", a->str);
}
}
}
static void PushAsmInput(AsmOperand *op) {
gen_expr(op->node);
push();
}
static void PushAsmInputs(Asm *a) {
int i;
for (i = 0; i < a->n; ++i) {
if (a->ops[i].flow == '=') continue;
switch (a->ops[i].type) {
case kAsmReg:
PushAsmInput(&a->ops[i]);
break;
case kAsmMem:
if (a->ops[i].isused) {
PushAsmInput(&a->ops[i]);
}
break;
case kAsmXmm:
gen_expr(a->ops[i].node);
println("\tsub\t$16,%%rsp");
switch (a->ops[i].node->ty->kind) {
case TY_FLOAT:
case TY_DOUBLE:
println("\tmovdqu\t%%xmm0,(%%rsp)");
break;
default:
println("\tmovdqu\t(%%rax),%%xmm0");
println("\tmovdqu\t%%xmm0,(%%rsp)");
break;
}
break;
case kAsmMmx:
gen_expr(a->ops[i].node);
println("\tsub\t$8,%%rsp");
switch (a->ops[i].node->ty->kind) {
case TY_FLOAT:
case TY_DOUBLE:
println("\tmovq\t%%mm0,(%%rsp)");
break;
default:
println("\tmovq\t(%%rax),%%mm0");
println("\tmovq\t%%mm0,(%%rsp)");
break;
}
break;
case kAsmFpu: /* TODO: How does this work in non-simple case? */
gen_expr(a->ops[i].node);
println("\tsub\t$16,%%rsp");
println("\tfstpt\t(%%rsp)");
break;
default:
break;
}
}
}
static void PopAsmInput(Asm *a, AsmOperand *op) {
if (isdigit(op->flow)) {
popreg(kGreg[3][a->ops[op->flow - '0'].reg]);
} else {
popreg(kGreg[3][op->reg]);
}
}
static void PopAsmInputs(Asm *a) {
int i;
for (i = a->n; i--;) {
if (a->ops[i].flow == '=') continue;
switch (a->ops[i].type) {
case kAsmReg:
PopAsmInput(a, &a->ops[i]);
break;
case kAsmMem:
if (a->ops[i].isused) {
PopAsmInput(a, &a->ops[i]);
}
break;
case kAsmXmm:
println("\tmovdqu\t(%%rsp),%%xmm%d", a->ops[i].reg);
println("\tadd\t$16,%%rsp");
break;
case kAsmMmx:
println("\tmovq\t(%%rsp),%%mm%d", a->ops[i].reg);
println("\tadd\t$8,%%rsp");
break;
case kAsmFpu: /* TODO: How does this work in non-simple case? */
println("\tfldt\t(%%rsp)");
println("\tadd\t$16,%%rsp");
break;
default:
break;
}
}
}
static void StoreAsmOutputs(Asm *a) {
int i, z, x0, x1;
for (i = 0; i < a->n; ++i) {
if (a->ops[i].flow == '=' || a->ops[i].flow == '+') {
switch (a->ops[i].type) {
case kAsmFlag:
gen_addr(a->ops[i].node);
println("\tset%s\t(%%rax)", a->ops[i].str + a->ops[i].predicate);
break;
case kAsmReg:
z = bsr(a->ops[i].node->ty->size);
if (a->ops[i].reg) {
gen_addr(a->ops[i].node);
if (z > 3) error_tok(a->tok, "bad asm out size");
println("\tmov\t%%%s,(%%rax)", kGreg[z][a->ops[i].reg]);
} else {
println("\tpush\t%%rbx");
push();
pop("%rbx");
gen_addr(a->ops[i].node);
println("\tmov\t%%%s,(%%rax)", kGreg[z][3]);
println("\tpop\t%%rbx");
}
break;
case kAsmXmm:
gen_addr(a->ops[i].node);
switch (a->ops[i].node->ty->kind) {
case TY_FLOAT:
println("\tmovss\t%%xmm%d,(%%rax)", a->ops[i].reg);
break;
case TY_DOUBLE:
println("\tmovsd\t%%xmm%d,(%%rax)", a->ops[i].reg);
break;
default:
println("\tmovdqu\t%%xmm%d,(%%rax)", a->ops[i].reg);
break;
}
break;
case kAsmMmx:
gen_addr(a->ops[i].node);
switch (a->ops[i].node->ty->kind) {
case TY_FLOAT:
println("\tmovss\t%%mm%d,(%%rax)", a->ops[i].reg);
break;
case TY_DOUBLE:
println("\tmovsd\t%%mm%d,(%%rax)", a->ops[i].reg);
break;
default:
println("\tmovq\t%%mm%d,(%%rax)", a->ops[i].reg);
break;
}
break;
case kAsmFpu: /* TODO: How does this work in non-simple case? */
gen_addr(a->ops[i].node);
println("\tfstpt\t(%%rax)");
break;
default:
break;
}
}
}
}
static void PushClobbers(Asm *a) {
int i, regs = a->regclob & PRECIOUS;
while (regs) {
i = bsf(regs);
pushreg(kGreg[3][i]);
regs &= ~(1 << i);
}
}
static void PopClobbers(Asm *a) {
int i, regs = a->regclob & PRECIOUS;
while (regs) {
i = bsr(regs);
popreg(kGreg[3][i]);
regs &= ~(1 << i);
}
}
// generates shocking horrible code for parsed asm statement
void gen_asm(Asm *a) {
PushAsmInputs(a);
print_loc(a->tok->file->file_no, a->tok->line_no);
PopAsmInputs(a);
PushClobbers(a);
EmitAsmText(a);
StoreAsmOutputs(a);
PopClobbers(a);
}