backend: i386 어셈블리를 내고 Windows 11 실행 파일을 만든다
fec --emit-asm -> wasm -> wlink -> .exe. 툴체인은 고정된 Open Watcom 그대로다. 레지스터 할당기가 없다. 임시값마다 스택 슬롯을 주고, 명령마다 피연산자를 고정 레지스터로 읽어 계산하고 다시 저장한다. 느린 코드지만 명백히 옳은 코드이고, 옳은 것이 먼저다. 나중에 할당기를 끼워도 나머지는 모른다 -- 임시값이 어디 사는지만 바뀐다. 런타임 fec/rt/start.asm 은 진입 스텁과 fe_trap 이다. trap 은 이유와 파일과 줄을 stderr 에 쓰고 3으로 끝낸다. 처음으로 Ferro 프로그램이 실행됐다: 1..10 합 -> 55 (7*6-2)/4 -> 10 루프+호출+분기 -> 1 tests/build.py 가 컴파일하고 링크하고 돌린다.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
; Ferro runtime: process entry and the trap handler.
|
||||
;
|
||||
; The entry point calls the program's `main` and hands its result to
|
||||
; ExitProcess, so a Ferro program is an ordinary console executable.
|
||||
; `fe_trap` prints where the program stopped and why, then exits 3.
|
||||
|
||||
.386
|
||||
.model flat
|
||||
|
||||
extern _ExitProcess@4 : near
|
||||
extern _GetStdHandle@4 : near
|
||||
extern _WriteFile@20 : near
|
||||
extern fe_main_ : near
|
||||
|
||||
_DATA segment dword public 'DATA'
|
||||
|
||||
reasons dd offset r_bounds, offset r_overflow, offset r_divide
|
||||
dd offset r_unreach, offset r_explicit
|
||||
r_bounds db 'index out of bounds',0
|
||||
r_overflow db 'integer overflow',0
|
||||
r_divide db 'divide by zero',0
|
||||
r_unreach db 'reached unreachable code',0
|
||||
r_explicit db 'trap',0
|
||||
r_unknown db 'trap',0
|
||||
prefix db 'ferro: ',0
|
||||
at_word db ' at ',0
|
||||
colon db ':',0
|
||||
newline db 13,10,0
|
||||
numbuf db 16 dup(0)
|
||||
written dd 0
|
||||
|
||||
_DATA ends
|
||||
|
||||
_TEXT segment dword public 'CODE'
|
||||
|
||||
; write_cstr(esi = pointer to a NUL-terminated string) -> void
|
||||
write_cstr proc near
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
push ebx
|
||||
push esi
|
||||
push edi
|
||||
mov edi, esi
|
||||
xor ecx, ecx
|
||||
count_loop:
|
||||
cmp byte ptr [edi], 0
|
||||
je count_done
|
||||
inc edi
|
||||
inc ecx
|
||||
jmp count_loop
|
||||
count_done:
|
||||
test ecx, ecx
|
||||
je write_done
|
||||
push -11 ; STD_ERROR_HANDLE
|
||||
call _GetStdHandle@4
|
||||
push 0 ; lpOverlapped
|
||||
push offset written
|
||||
push ecx
|
||||
push esi
|
||||
push eax
|
||||
call _WriteFile@20
|
||||
write_done:
|
||||
pop edi
|
||||
pop esi
|
||||
pop ebx
|
||||
mov esp, ebp
|
||||
pop ebp
|
||||
ret
|
||||
write_cstr endp
|
||||
|
||||
; write_uint(eax = value) -> void
|
||||
write_uint proc near
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
push ebx
|
||||
mov edi, offset numbuf + 15
|
||||
mov byte ptr [edi], 0
|
||||
mov ebx, 10
|
||||
digit_loop:
|
||||
xor edx, edx
|
||||
div ebx
|
||||
add dl, '0'
|
||||
dec edi
|
||||
mov [edi], dl
|
||||
test eax, eax
|
||||
jnz digit_loop
|
||||
mov esi, edi
|
||||
call write_cstr
|
||||
pop ebx
|
||||
mov esp, ebp
|
||||
pop ebp
|
||||
ret
|
||||
write_uint endp
|
||||
|
||||
; fe_trap(reason, file, line) -- cdecl, never returns
|
||||
public fe_trap
|
||||
fe_trap proc near
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
mov esi, offset prefix
|
||||
call write_cstr
|
||||
mov eax, [ebp+8] ; reason
|
||||
cmp eax, 5
|
||||
jb reason_ok
|
||||
mov esi, offset r_unknown
|
||||
jmp reason_write
|
||||
reason_ok:
|
||||
mov esi, [reasons + eax*4]
|
||||
reason_write:
|
||||
call write_cstr
|
||||
mov esi, offset at_word
|
||||
call write_cstr
|
||||
mov esi, [ebp+12] ; file
|
||||
call write_cstr
|
||||
mov esi, offset colon
|
||||
call write_cstr
|
||||
mov eax, [ebp+16] ; line
|
||||
call write_uint
|
||||
mov esi, offset newline
|
||||
call write_cstr
|
||||
push 3
|
||||
call _ExitProcess@4
|
||||
fe_trap endp
|
||||
|
||||
public fe_start_
|
||||
fe_start_ proc near
|
||||
call fe_main_
|
||||
push eax
|
||||
call _ExitProcess@4
|
||||
fe_start_ endp
|
||||
|
||||
_TEXT ends
|
||||
|
||||
end fe_start_
|
||||
+14
-5
@@ -2,6 +2,7 @@
|
||||
#include "check.h"
|
||||
#include "resolve.h"
|
||||
#include "lower.h"
|
||||
#include "x86.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -17,7 +18,7 @@ static char *read_file(const char *name, unsigned long *size)
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
puts("usage: fec [--dump-tokens|--dump-ast|--check|--dump-ir] file.fe [--no-checks]");
|
||||
puts("usage: fec [--dump-tokens|--dump-ast|--check|--dump-ir|--emit-asm] file.fe [-o out.asm] [--no-checks]");
|
||||
}
|
||||
|
||||
static void dump_tokens(const char *src, unsigned long n, const char *file,
|
||||
@@ -38,8 +39,9 @@ static void dump_tokens(const char *src, unsigned long n, const char *file,
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i,dump=0,dump_tok=0,check_only=0,no_checks=0,dump_ir=0;
|
||||
int i,dump=0,dump_tok=0,check_only=0,no_checks=0,dump_ir=0,emit_asm=0;
|
||||
const char *file=0;
|
||||
const char *out_path=0;
|
||||
unsigned long n;
|
||||
char *src;
|
||||
FeDiags d;
|
||||
@@ -53,13 +55,15 @@ int main(int argc, char **argv)
|
||||
else if(strcmp(argv[i],"--dump-tokens")==0) dump_tok=1;
|
||||
else if(strcmp(argv[i],"--check")==0) check_only=1;
|
||||
else if(strcmp(argv[i],"--dump-ir")==0) dump_ir=1;
|
||||
else if(strcmp(argv[i],"--emit-asm")==0) emit_asm=1;
|
||||
else if(strcmp(argv[i],"-o")==0 && i+1<argc) out_path=argv[++i];
|
||||
else if(strcmp(argv[i],"--no-checks")==0) no_checks=1;
|
||||
else if(strncmp(argv[i],"--target=",9)==0 || strncmp(argv[i],"--model=",8)==0 || strcmp(argv[i],"--strip-error-names")==0) { }
|
||||
else if(argv[i][0]!='-') file=argv[i];
|
||||
else if(strcmp(argv[i],"--help")==0){usage();return 0;}
|
||||
else {fprintf(fe_diag_stream(),"fec: unknown option %s\n",argv[i]);return 2;}
|
||||
}
|
||||
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)+(dump_ir?1:0)>1){
|
||||
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)+(dump_ir?1:0)+(emit_asm?1:0)>1){
|
||||
fprintf(fe_diag_stream(),"fec: choose only one output mode\n");
|
||||
return 2;
|
||||
}
|
||||
@@ -94,11 +98,16 @@ int main(int argc, char **argv)
|
||||
if(ok){
|
||||
fe_check_init(&check,&build,&d,pointer_bits,no_checks);
|
||||
if(!fe_check_program(&check)) ok=0;
|
||||
if(ok && dump_ir){
|
||||
if(ok && (dump_ir||emit_asm)){
|
||||
FeIrModule ir;
|
||||
fe_ir_module_init(&ir);
|
||||
if(!fe_lower_program(&check,&ir)) ok=0;
|
||||
else fe_ir_dump(&ir,stdout);
|
||||
else if(dump_ir) fe_ir_dump(&ir,stdout);
|
||||
else {
|
||||
FILE *o=out_path?fopen(out_path,"w"):stdout;
|
||||
if(!o){fprintf(fe_diag_stream(),"fec: cannot write %s\n",out_path);ok=0;}
|
||||
else { fe_x86_emit(&ir,o); if(out_path) fclose(o); }
|
||||
}
|
||||
fe_ir_module_destroy(&ir);
|
||||
}
|
||||
fe_check_destroy(&check);
|
||||
|
||||
@@ -5,6 +5,7 @@ void fe_ir_module_init(FeIrModule *m)
|
||||
{
|
||||
fe_arena_init(&m->arena, 16384);
|
||||
m->unit_file = "";
|
||||
m->entry_main = 0;
|
||||
m->funcs = 0;
|
||||
m->last_func = 0;
|
||||
m->globals = 0;
|
||||
|
||||
@@ -133,6 +133,9 @@ typedef struct FeIrGlobal {
|
||||
typedef struct FeIrModule {
|
||||
FeArena arena;
|
||||
const char *unit_file; /* the one file-name string a unit's traps share */
|
||||
/* The entry unit's `main`, if it has one. The runtime's start stub
|
||||
calls a fixed name, so the generator emits a jump to this one. */
|
||||
const char *entry_main;
|
||||
FeIrFunc *funcs;
|
||||
FeIrFunc *last_func;
|
||||
FeIrGlobal *globals;
|
||||
|
||||
+6
-1
@@ -582,7 +582,12 @@ int fe_lower_program(FeCheck *c, FeIrModule *out)
|
||||
c->types.unit_name = unit->name[0] ? unit->name : "unit";
|
||||
if (!out->unit_file || !out->unit_file[0]) out->unit_file = unit->path;
|
||||
for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next)
|
||||
if (n->kind == FE_N_FN && n->c) lower_fn(&L, n);
|
||||
if (n->kind == FE_N_FN && n->c) {
|
||||
lower_fn(&L, n);
|
||||
/* The entry unit is the one the build was rooted at. */
|
||||
if (u == 0 && n->text && !strcmp(n->text, "main"))
|
||||
out->entry_main = n->cname;
|
||||
}
|
||||
}
|
||||
return !L.failed;
|
||||
}
|
||||
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
#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 == FE_IR_VOID
|
||||
? FE_IR_I32 : v->type), addr, reg_of(v->type == FE_IR_VOID
|
||||
? FE_IR_I32 : 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) {
|
||||
fprintf(out, "public %s\n%s label byte\n", g->name, g->name);
|
||||
fprintf(out, " db %lu dup(0)\n", g->size ? g->size : 1UL);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef FE_X86_H
|
||||
#define FE_X86_H
|
||||
|
||||
#include "ir.h"
|
||||
|
||||
/* IR to i386 assembly, in the syntax Open Watcom's `wasm` accepts.
|
||||
|
||||
There is no register allocator. Every temporary gets a stack slot, and every
|
||||
instruction loads its operands into fixed registers, computes, and stores
|
||||
the result back. That is slow code and obviously correct code, and correct
|
||||
comes first: a register allocator can be dropped in later without the rest
|
||||
of the compiler noticing, because it only changes where a temporary lives. */
|
||||
|
||||
void fe_x86_emit(const FeIrModule *m, FILE *out);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user