Files
doslang-mirror/fec/src/x86.c
T
coolguy 174e6c569d lower: 전역과 문자열 리터럴, 그리고 defer 실행 검증
전역은 정적 저장소다. 초기값이 컴파일타임 상수면 이미지에 박고 아니면 0이다.
문자열 리터럴은 바이트를 이미지에 두고 포인터와 길이를 값으로 만든다. 같은
글자는 같은 저장소를 쓴다 -- 읽기 전용이라 공유가 공짜다.

defers 프로그램이 defer 순서를 실행으로 고정한다. 등록 역순이고, 이른 return
과 끝까지 간 경로 양쪽 다 돈다.
2026-08-17 06:08:33 +09:00

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);
}