전역은 정적 저장소다. 초기값이 컴파일타임 상수면 이미지에 박고 아니면 0이다. 문자열 리터럴은 바이트를 이미지에 두고 포인터와 길이를 값으로 만든다. 같은 글자는 같은 저장소를 쓴다 -- 읽기 전용이라 공유가 공짜다. defers 프로그램이 defer 순서를 실행으로 고정한다. 등록 역순이고, 이른 return 과 끝까지 간 경로 양쪽 다 돈다.
394 lines
14 KiB
C
394 lines
14 KiB
C
#include "x86.h"
|
|
#include <string.h>
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* i386 code generation
|
|
*
|
|
* The frame, from EBP downwards:
|
|
*
|
|
* [ebp + 8 + 4k] incoming argument k
|
|
* [ebp + 4] return address
|
|
* [ebp] saved ebp
|
|
* [ebp - ...] parameters, copied in from the argument area
|
|
* [ebp - ...] locals
|
|
* [ebp - ...] one slot per temporary
|
|
*
|
|
* Parameters are copied into the frame rather than read in place so that a
|
|
* parameter and a local are the same thing to everything below.
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
typedef struct Frame {
|
|
const FeIrFunc *f;
|
|
long *local_off; /* [ebp + off] for each local */
|
|
long temp_base; /* first temporary slot */
|
|
long size; /* bytes to subtract from esp */
|
|
} Frame;
|
|
|
|
static long align_up(long v, long a)
|
|
{
|
|
long r = v % a;
|
|
return r ? v + a - r : v;
|
|
}
|
|
|
|
static unsigned long slot_bytes(const FeIrLocal *l)
|
|
{
|
|
switch (l->type) {
|
|
case FE_IR_I8: return 1;
|
|
case FE_IR_I16: return 2;
|
|
case FE_IR_I32: return 4;
|
|
case FE_IR_PTR: return 4;
|
|
case FE_IR_MEM: return l->size ? l->size : 1;
|
|
default: return 4;
|
|
}
|
|
}
|
|
|
|
/* Every temporary is four bytes: a temporary only ever holds something that
|
|
fits in a register, and narrower values are kept zero- or sign-extended. */
|
|
#define TEMP_SLOT 4L
|
|
|
|
static void frame_layout(Frame *fr, const FeIrFunc *f, long *storage)
|
|
{
|
|
unsigned i;
|
|
long off = 0;
|
|
fr->f = f;
|
|
fr->local_off = storage;
|
|
for (i = 0; i < f->local_count; ++i) {
|
|
unsigned long size = slot_bytes(&f->locals[i]);
|
|
long a = (long)f->locals[i].align;
|
|
if (a < 1) a = 1;
|
|
if (a > 4) a = 4;
|
|
off = align_up(off + (long)size, a);
|
|
storage[i] = -off;
|
|
}
|
|
off = align_up(off, 4);
|
|
fr->temp_base = -off;
|
|
off += (long)f->temp_count * TEMP_SLOT;
|
|
fr->size = align_up(off, 4);
|
|
}
|
|
|
|
static long temp_off(const Frame *fr, unsigned t)
|
|
{
|
|
return fr->temp_base - (long)(t + 1) * TEMP_SLOT;
|
|
}
|
|
|
|
static const char *word_of(FeIrType t)
|
|
{
|
|
switch (t) {
|
|
case FE_IR_I8: return "byte ptr";
|
|
case FE_IR_I16: return "word ptr";
|
|
default: return "dword ptr";
|
|
}
|
|
}
|
|
|
|
static const char *reg_of(FeIrType t, int which)
|
|
{
|
|
/* which: 0 -> a, 1 -> c, 2 -> d */
|
|
switch (t) {
|
|
case FE_IR_I8: return which == 0 ? "al" : which == 1 ? "cl" : "dl";
|
|
case FE_IR_I16: return which == 0 ? "ax" : which == 1 ? "cx" : "dx";
|
|
default: return which == 0 ? "eax" : which == 1 ? "ecx" : "edx";
|
|
}
|
|
}
|
|
|
|
/* Write the effective address of a place into `buf`. A place is a base plus a
|
|
constant, and the only base that is not already an address is a temporary,
|
|
which holds a pointer. */
|
|
static void place_addr(const Frame *fr, const FeIrPlace *p, char *buf)
|
|
{
|
|
switch (p->base) {
|
|
case FE_PLACE_LOCAL:
|
|
sprintf(buf, "[ebp%+ld]", fr->local_off[p->index] + p->offset);
|
|
break;
|
|
case FE_PLACE_GLOBAL:
|
|
if (p->offset) sprintf(buf, "[%s%+ld]", p->name, p->offset);
|
|
else sprintf(buf, "[%s]", p->name);
|
|
break;
|
|
case FE_PLACE_TEMP:
|
|
sprintf(buf, "[edx%+ld]", p->offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* A temporary-based place needs its pointer in a register first. */
|
|
static void load_place_base(const Frame *fr, const FeIrPlace *p, FILE *out)
|
|
{
|
|
if (p->base != FE_PLACE_TEMP) return;
|
|
fprintf(out, " mov edx, [ebp%+ld]\n", temp_off(fr, p->index));
|
|
}
|
|
|
|
static void load_temp(const Frame *fr, unsigned t, const char *reg, FILE *out)
|
|
{
|
|
fprintf(out, " mov %s, [ebp%+ld]\n", reg, temp_off(fr, t));
|
|
}
|
|
|
|
static void store_temp(const Frame *fr, unsigned t, const char *reg, FILE *out)
|
|
{
|
|
fprintf(out, " mov [ebp%+ld], %s\n", temp_off(fr, t), reg);
|
|
}
|
|
|
|
static const char *cmp_set(FeIrOp op, int is_unsigned)
|
|
{
|
|
switch (op) {
|
|
case FE_IR_EQ: return "sete";
|
|
case FE_IR_NE: return "setne";
|
|
case FE_IR_LT: return is_unsigned ? "setb" : "setl";
|
|
case FE_IR_LE: return is_unsigned ? "setbe" : "setle";
|
|
case FE_IR_GT: return is_unsigned ? "seta" : "setg";
|
|
case FE_IR_GE: return is_unsigned ? "setae" : "setge";
|
|
default: return "sete";
|
|
}
|
|
}
|
|
|
|
static void emit_binary(const Frame *fr, const FeIrValue *v, FILE *out)
|
|
{
|
|
int is_cmp = v->op >= FE_IR_EQ && v->op <= FE_IR_GE;
|
|
FeIrType t = is_cmp ? (FeIrType)v->imm : v->type;
|
|
const char *a = reg_of(t, 0);
|
|
const char *c = reg_of(t, 1);
|
|
load_temp(fr, v->a, "eax", out);
|
|
load_temp(fr, v->b, "ecx", out);
|
|
if (is_cmp) {
|
|
fprintf(out, " cmp %s, %s\n", a, c);
|
|
fprintf(out, " %s al\n", cmp_set(v->op, v->is_unsigned));
|
|
fprintf(out, " movzx eax, al\n");
|
|
store_temp(fr, v->dest, "eax", out);
|
|
return;
|
|
}
|
|
switch (v->op) {
|
|
case FE_IR_ADD: fprintf(out, " add %s, %s\n", a, c); break;
|
|
case FE_IR_SUB: fprintf(out, " sub %s, %s\n", a, c); break;
|
|
case FE_IR_MUL: fprintf(out, " imul %s, %s\n", a, c); break;
|
|
case FE_IR_AND: fprintf(out, " and %s, %s\n", a, c); break;
|
|
case FE_IR_OR: fprintf(out, " or %s, %s\n", a, c); break;
|
|
case FE_IR_XOR: fprintf(out, " xor %s, %s\n", a, c); break;
|
|
case FE_IR_SHL: fprintf(out, " shl %s, cl\n", a); break;
|
|
case FE_IR_SHR:
|
|
fprintf(out, " %s %s, cl\n",
|
|
v->is_unsigned ? "shr" : "sar", a);
|
|
break;
|
|
case FE_IR_DIV:
|
|
case FE_IR_MOD:
|
|
/* The divide instructions use edx:eax, so the operands have to be
|
|
widened to 32 bits whatever the declared width is. */
|
|
if (v->is_unsigned) fprintf(out, " xor edx, edx\n");
|
|
else fprintf(out, " cdq\n");
|
|
fprintf(out, " %s ecx\n", v->is_unsigned ? "div " : "idiv");
|
|
if (v->op == FE_IR_MOD) fprintf(out, " mov eax, edx\n");
|
|
break;
|
|
default: break;
|
|
}
|
|
store_temp(fr, v->dest, "eax", out);
|
|
}
|
|
|
|
static void emit_value(const Frame *fr, const FeIrValue *v, FILE *out)
|
|
{
|
|
char addr[128];
|
|
unsigned i;
|
|
switch (v->op) {
|
|
case FE_IR_CONST:
|
|
fprintf(out, " mov eax, %ld\n", v->imm);
|
|
store_temp(fr, v->dest, "eax", out);
|
|
break;
|
|
case FE_IR_LOAD:
|
|
load_place_base(fr, &v->place, out);
|
|
place_addr(fr, &v->place, addr);
|
|
if (v->type == FE_IR_I8)
|
|
fprintf(out, " movzx eax, byte ptr %s\n", addr);
|
|
else if (v->type == FE_IR_I16)
|
|
fprintf(out, " movzx eax, word ptr %s\n", addr);
|
|
else
|
|
fprintf(out, " mov eax, dword ptr %s\n", addr);
|
|
store_temp(fr, v->dest, "eax", out);
|
|
break;
|
|
case FE_IR_STORE:
|
|
load_place_base(fr, &v->place, out);
|
|
place_addr(fr, &v->place, addr);
|
|
load_temp(fr, v->a, "eax", out);
|
|
fprintf(out, " mov %s %s, %s\n", word_of(v->type), addr,
|
|
reg_of(v->type, 0));
|
|
break;
|
|
case FE_IR_ADDR:
|
|
load_place_base(fr, &v->place, out);
|
|
place_addr(fr, &v->place, addr);
|
|
fprintf(out, " lea eax, %s\n", addr);
|
|
store_temp(fr, v->dest, "eax", out);
|
|
break;
|
|
case FE_IR_CAST:
|
|
load_temp(fr, v->a, "eax", out);
|
|
/* Narrowing is free once everything is kept in a 32-bit slot; widening
|
|
has to say whether the top bits are copies of the sign. */
|
|
if (v->type == FE_IR_I8)
|
|
fprintf(out, " %s eax, al\n",
|
|
v->is_unsigned ? "movzx" : "movsx");
|
|
else if (v->type == FE_IR_I16)
|
|
fprintf(out, " %s eax, ax\n",
|
|
v->is_unsigned ? "movzx" : "movsx");
|
|
store_temp(fr, v->dest, "eax", out);
|
|
break;
|
|
case FE_IR_CALL:
|
|
/* cdecl: arguments pushed right to left, the caller pops them. */
|
|
for (i = v->arg_count; i > 0; --i) {
|
|
load_temp(fr, v->args[i - 1], "eax", out);
|
|
fprintf(out, " push eax\n");
|
|
}
|
|
fprintf(out, " call %s\n", v->callee);
|
|
if (v->arg_count)
|
|
fprintf(out, " add esp, %u\n", v->arg_count * 4U);
|
|
if (v->has_dest) store_temp(fr, v->dest, "eax", out);
|
|
break;
|
|
case FE_IR_COPY: {
|
|
char dst[128];
|
|
char src[128];
|
|
/* The source base and the destination base both want edx, so a
|
|
temporary-based place is resolved into esi or edi first. */
|
|
if (v->place2.base == FE_PLACE_TEMP) {
|
|
load_temp(fr, v->place2.index, "esi", out);
|
|
sprintf(src, "[esi%+ld]", v->place2.offset);
|
|
} else {
|
|
place_addr(fr, &v->place2, src);
|
|
}
|
|
if (v->place.base == FE_PLACE_TEMP) {
|
|
load_temp(fr, v->place.index, "edi", out);
|
|
sprintf(dst, "[edi%+ld]", v->place.offset);
|
|
} else {
|
|
place_addr(fr, &v->place, dst);
|
|
}
|
|
fprintf(out, " lea esi, %s\n", src);
|
|
fprintf(out, " lea edi, %s\n", dst);
|
|
fprintf(out, " mov ecx, %ld\n", v->imm);
|
|
fprintf(out, " cld\n");
|
|
fprintf(out, " rep movsb\n");
|
|
break;
|
|
}
|
|
default:
|
|
emit_binary(fr, v, out);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out)
|
|
{
|
|
Frame fr;
|
|
long storage[512];
|
|
const FeIrBlock *b;
|
|
const FeIrValue *v;
|
|
unsigned i;
|
|
long arg = 8;
|
|
if (f->is_extern || !f->first) return;
|
|
if (f->local_count > 512) return;
|
|
frame_layout(&fr, f, storage);
|
|
|
|
fprintf(out, "\npublic %s\n", f->name);
|
|
fprintf(out, "%s proc near\n", f->name);
|
|
fprintf(out, " push ebp\n");
|
|
fprintf(out, " mov ebp, esp\n");
|
|
if (fr.size) fprintf(out, " sub esp, %ld\n", fr.size);
|
|
fprintf(out, " push esi\n push edi\n");
|
|
/* Copy the incoming arguments into the frame. */
|
|
for (i = 0; i < f->param_count; ++i) {
|
|
fprintf(out, " mov eax, [ebp+%ld]\n", arg);
|
|
fprintf(out, " mov %s [ebp%+ld], %s\n",
|
|
word_of(f->locals[i].type), storage[i],
|
|
reg_of(f->locals[i].type, 0));
|
|
arg += 4;
|
|
}
|
|
|
|
for (b = f->first; b; b = b->next) {
|
|
fprintf(out, "L%s_%u:\n", f->name, b->id);
|
|
for (v = b->first; v; v = v->next) emit_value(&fr, v, out);
|
|
switch (b->term) {
|
|
case FE_IR_JMP:
|
|
fprintf(out, " jmp L%s_%u\n", f->name, b->target);
|
|
break;
|
|
case FE_IR_BR:
|
|
load_temp(&fr, b->cond, "eax", out);
|
|
fprintf(out, " test eax, eax\n");
|
|
fprintf(out, " jnz L%s_%u\n", f->name, b->target);
|
|
fprintf(out, " jmp L%s_%u\n", f->name, b->target_else);
|
|
break;
|
|
case FE_IR_RET:
|
|
if (b->has_ret_value) load_temp(&fr, b->ret_value, "eax", out);
|
|
fprintf(out, " pop edi\n pop esi\n");
|
|
fprintf(out, " mov esp, ebp\n pop ebp\n");
|
|
fprintf(out, " ret\n");
|
|
break;
|
|
case FE_IR_TRAP:
|
|
fprintf(out, " push %lu\n", b->trap_line);
|
|
fprintf(out, " push offset FE_UNIT_FILE\n");
|
|
fprintf(out, " push %u\n", (unsigned)b->trap);
|
|
fprintf(out, " call fe_trap\n");
|
|
fprintf(out, " add esp, 12\n");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(out, "%s endp\n", f->name);
|
|
(void)m;
|
|
}
|
|
|
|
static void emit_string(const char *s, FILE *out)
|
|
{
|
|
int in = 0;
|
|
fputs(" db ", out);
|
|
for (; s && *s; ++s) {
|
|
unsigned char c = (unsigned char)*s;
|
|
if (c >= 32 && c < 127 && c != '\'' && c != '"') {
|
|
if (!in) { fputc('\'', out); in = 1; }
|
|
fputc(c, out);
|
|
} else {
|
|
if (in) { fputs("',", out); in = 0; }
|
|
fprintf(out, "%u,", c);
|
|
}
|
|
}
|
|
if (in) fputc('\'', out);
|
|
else fputc('0', out);
|
|
if (in) fputs(",0", out);
|
|
fputc('\n', out);
|
|
}
|
|
|
|
void fe_x86_emit(const FeIrModule *m, FILE *out)
|
|
{
|
|
const FeIrFunc *f;
|
|
const FeIrGlobal *g;
|
|
int any_trap = 0;
|
|
const FeIrBlock *b;
|
|
|
|
for (f = m->funcs; f && !any_trap; f = f->next)
|
|
for (b = f->first; b; b = b->next)
|
|
if (b->term == FE_IR_TRAP) { any_trap = 1; break; }
|
|
|
|
fputs(".386\n.model flat\n\n", out);
|
|
for (f = m->funcs; f; f = f->next)
|
|
if (f->is_extern || !f->first)
|
|
fprintf(out, "extern %s : near\n", f->name);
|
|
if (any_trap) fputs("extern fe_trap : near\n", out);
|
|
|
|
fputs("\n_DATA segment dword public 'DATA'\n", out);
|
|
if (any_trap) {
|
|
fputs("public FE_UNIT_FILE\nFE_UNIT_FILE label byte\n", out);
|
|
emit_string(m->unit_file, out);
|
|
}
|
|
for (g = m->globals; g; g = g->next) {
|
|
unsigned long i;
|
|
fprintf(out, "public %s\n%s label byte\n", g->name, g->name);
|
|
if (!g->init) {
|
|
fprintf(out, " db %lu dup(0)\n", g->size ? g->size : 1UL);
|
|
continue;
|
|
}
|
|
for (i = 0; i < g->size; ++i) {
|
|
if (i % 16 == 0) fputs(" db ", out);
|
|
fprintf(out, "%u%s", g->init[i],
|
|
(i + 1 == g->size || (i % 16) == 15) ? "\n" : ",");
|
|
}
|
|
if (!g->size) fputs(" db 0\n", out);
|
|
}
|
|
fputs("_DATA ends\n", out);
|
|
|
|
fputs("\n_TEXT segment dword public 'CODE'\n", out);
|
|
for (f = m->funcs; f; f = f->next) emit_func(m, f, out);
|
|
/* The runtime's entry stub calls one fixed name, so point it here. */
|
|
if (m->entry_main)
|
|
fprintf(out, "\npublic fe_main_\nfe_main_ proc near\n"
|
|
" jmp %s\nfe_main_ endp\n", m->entry_main);
|
|
fputs("\n_TEXT ends\n\nend\n", out);
|
|
}
|