cosmopolitan/third_party/chibicc/asm.c

756 lines
22 KiB
C
Raw Normal View History

/*-*- 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
2020-12-19 19:21:04 +00:00
StaticAsm *staticasms;
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"},
};
static void DecodeAsmConstraints(AsmOperand *op) {
int i;
char c;
for (i = 0;;) {
switch ((c = op->str[i++])) {
case '\0':
case ',': // alternative group
2020-12-09 12:00:48 +00:00
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;
}
}
2020-12-09 12:00:48 +00:00
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) {
2020-12-09 12:00:48 +00:00
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:
2020-12-09 12:00:48 +00:00
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;
2020-12-09 12:00:48 +00:00
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);
2020-12-09 12:00:48 +00:00
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);
}
2020-12-09 12:00:48 +00:00
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;
2020-12-09 12:00:48 +00:00
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;
2020-12-09 12:00:48 +00:00
} else if (!strcmp(s, "memory")) {
/* do nothing */
} else {
error_tok(stok, "unknown clobber register");
}
if (!EQUAL(tok, ",")) break;
2020-12-09 12:00:48 +00:00
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;
2020-12-09 12:00:48 +00:00
tok = skip(tok, '(');
a->tok = tok;
a->str = ConsumeStringLiteral(&tok, tok);
if (!EQUAL(tok, ")")) {
a->isgnu = true;
2020-12-09 12:00:48 +00:00
tok = skip(tok, ':');
tok = ParseAsmOperands(a, tok);
if (!EQUAL(tok, ")")) {
2020-12-09 12:00:48 +00:00
tok = skip(tok, ':');
tok = ParseAsmOperands(a, tok);
if (!EQUAL(tok, ")")) {
2020-12-09 12:00:48 +00:00
tok = skip(tok, ':');
tok = ParseAsmClobbers(a, tok);
}
}
}
PickAsmRegisters(a);
MarkUsedAsmOperands(a);
2020-12-09 12:00:48 +00:00
*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;
2020-12-09 12:00:48 +00:00
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;
}
}
2020-12-09 12:00:48 +00:00
} 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:
2020-12-09 12:00:48 +00:00
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);
2020-12-09 12:00:48 +00:00
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);
}