lower: 검사된 AST 를 IR 로 내린다 (1차)
함수, 파라미터, 지역, 리터럴, 이름, 산술과 비교, 대입, if, while, break/continue, 호출, 참조, 필드 접근, 포인터 역참조까지. Slot 이 표현식의 결과다 -- 임시값에 든 값이거나 메모리 안의 자리다. 덩어리는 언제나 자리다. 임시값은 레지스터이고 덩어리는 거기 안 들어가기 때문이다. and/or 는 연산이 아니라 제어 흐름으로 내린다. 오른쪽을 평가하지 않아야 하는 경우가 있어서다. 블록에 terminated 플래그를 뒀다. 없으면 분기 안의 return 이 join 으로 가는 점프에 덮인다. --dump-ir 로 볼 수 있다. try/catch/defer/drop/for/배열/옵셔널/에러유니온과 제네릭 인스턴스는 아직이다.
This commit is contained in:
+12
-3
@@ -1,6 +1,7 @@
|
|||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "resolve.h"
|
#include "resolve.h"
|
||||||
|
#include "lower.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -16,7 +17,7 @@ static char *read_file(const char *name, unsigned long *size)
|
|||||||
|
|
||||||
static void usage(void)
|
static void usage(void)
|
||||||
{
|
{
|
||||||
puts("usage: fec [--dump-tokens|--dump-ast|--check] file.fe [--no-checks]");
|
puts("usage: fec [--dump-tokens|--dump-ast|--check|--dump-ir] file.fe [--no-checks]");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dump_tokens(const char *src, unsigned long n, const char *file,
|
static void dump_tokens(const char *src, unsigned long n, const char *file,
|
||||||
@@ -37,7 +38,7 @@ static void dump_tokens(const char *src, unsigned long n, const char *file,
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int i,dump=0,dump_tok=0,check_only=0,no_checks=0;
|
int i,dump=0,dump_tok=0,check_only=0,no_checks=0,dump_ir=0;
|
||||||
const char *file=0;
|
const char *file=0;
|
||||||
unsigned long n;
|
unsigned long n;
|
||||||
char *src;
|
char *src;
|
||||||
@@ -51,13 +52,14 @@ int main(int argc, char **argv)
|
|||||||
if(strcmp(argv[i],"--dump-ast")==0) dump=1;
|
if(strcmp(argv[i],"--dump-ast")==0) dump=1;
|
||||||
else if(strcmp(argv[i],"--dump-tokens")==0) dump_tok=1;
|
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],"--check")==0) check_only=1;
|
||||||
|
else if(strcmp(argv[i],"--dump-ir")==0) dump_ir=1;
|
||||||
else if(strcmp(argv[i],"--no-checks")==0) no_checks=1;
|
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(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(argv[i][0]!='-') file=argv[i];
|
||||||
else if(strcmp(argv[i],"--help")==0){usage();return 0;}
|
else if(strcmp(argv[i],"--help")==0){usage();return 0;}
|
||||||
else {fprintf(fe_diag_stream(),"fec: unknown option %s\n",argv[i]);return 2;}
|
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)>1){
|
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)+(dump_ir?1:0)>1){
|
||||||
fprintf(fe_diag_stream(),"fec: choose only one output mode\n");
|
fprintf(fe_diag_stream(),"fec: choose only one output mode\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
@@ -92,6 +94,13 @@ int main(int argc, char **argv)
|
|||||||
if(ok){
|
if(ok){
|
||||||
fe_check_init(&check,&build,&d,pointer_bits,no_checks);
|
fe_check_init(&check,&build,&d,pointer_bits,no_checks);
|
||||||
if(!fe_check_program(&check)) ok=0;
|
if(!fe_check_program(&check)) ok=0;
|
||||||
|
if(ok && dump_ir){
|
||||||
|
FeIrModule ir;
|
||||||
|
fe_ir_module_init(&ir);
|
||||||
|
if(!fe_lower_program(&check,&ir)) ok=0;
|
||||||
|
else fe_ir_dump(&ir,stdout);
|
||||||
|
fe_ir_module_destroy(&ir);
|
||||||
|
}
|
||||||
fe_check_destroy(&check);
|
fe_check_destroy(&check);
|
||||||
}
|
}
|
||||||
fe_build_destroy(&build);
|
fe_build_destroy(&build);
|
||||||
|
|||||||
@@ -215,12 +215,16 @@ void fe_ir_copy(FeIrModule *m, FeIrBlock *b, FeIrPlace dst, FeIrPlace src,
|
|||||||
|
|
||||||
void fe_ir_jmp(FeIrBlock *b, unsigned target)
|
void fe_ir_jmp(FeIrBlock *b, unsigned target)
|
||||||
{
|
{
|
||||||
|
if (b->terminated) return;
|
||||||
|
b->terminated = 1;
|
||||||
b->term = FE_IR_JMP;
|
b->term = FE_IR_JMP;
|
||||||
b->target = target;
|
b->target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f)
|
void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f)
|
||||||
{
|
{
|
||||||
|
if (b->terminated) return;
|
||||||
|
b->terminated = 1;
|
||||||
b->term = FE_IR_BR;
|
b->term = FE_IR_BR;
|
||||||
b->cond = cond;
|
b->cond = cond;
|
||||||
b->target = t;
|
b->target = t;
|
||||||
@@ -229,6 +233,8 @@ void fe_ir_br(FeIrBlock *b, unsigned cond, unsigned t, unsigned f)
|
|||||||
|
|
||||||
void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value)
|
void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value)
|
||||||
{
|
{
|
||||||
|
if (b->terminated) return;
|
||||||
|
b->terminated = 1;
|
||||||
b->term = FE_IR_RET;
|
b->term = FE_IR_RET;
|
||||||
b->ret_value = value;
|
b->ret_value = value;
|
||||||
b->has_ret_value = has_value;
|
b->has_ret_value = has_value;
|
||||||
@@ -236,6 +242,8 @@ void fe_ir_ret(FeIrBlock *b, unsigned value, int has_value)
|
|||||||
|
|
||||||
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line)
|
void fe_ir_trap(FeIrBlock *b, FeIrTrap reason, unsigned long line)
|
||||||
{
|
{
|
||||||
|
if (b->terminated) return;
|
||||||
|
b->terminated = 1;
|
||||||
b->term = FE_IR_TRAP;
|
b->term = FE_IR_TRAP;
|
||||||
b->trap = reason;
|
b->trap = reason;
|
||||||
b->trap_line = line;
|
b->trap_line = line;
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ typedef struct FeIrBlock {
|
|||||||
int has_ret_value;
|
int has_ret_value;
|
||||||
FeIrTrap trap;
|
FeIrTrap trap;
|
||||||
unsigned long trap_line;
|
unsigned long trap_line;
|
||||||
|
/* Set once a terminator is chosen. Lowering asks before appending a
|
||||||
|
jump, so a `return` inside a branch is not overwritten by the jump
|
||||||
|
to the join block. */
|
||||||
|
int terminated;
|
||||||
struct FeIrBlock *next;
|
struct FeIrBlock *next;
|
||||||
} FeIrBlock;
|
} FeIrBlock;
|
||||||
|
|
||||||
|
|||||||
+588
@@ -0,0 +1,588 @@
|
|||||||
|
#include "lower.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------- *
|
||||||
|
* Lowering
|
||||||
|
*
|
||||||
|
* One function at a time, one statement at a time. A `Slot` is what an
|
||||||
|
* expression produced: either a value already in a temporary, or a place in
|
||||||
|
* memory that a value can be read from or written to. Aggregates are always
|
||||||
|
* places -- they are never carried in a temporary, because a temporary is a
|
||||||
|
* register and an aggregate does not fit in one.
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#define LOWER_MAX_LOCALS 256
|
||||||
|
|
||||||
|
typedef struct LowerVar {
|
||||||
|
const char *cname;
|
||||||
|
unsigned local;
|
||||||
|
} LowerVar;
|
||||||
|
|
||||||
|
typedef struct Lower {
|
||||||
|
FeCheck *c;
|
||||||
|
FeIrModule *m;
|
||||||
|
FeIrFunc *fn;
|
||||||
|
FeIrBlock *b; /* the block being appended to */
|
||||||
|
FeType *ret_type;
|
||||||
|
unsigned ret_local; /* hidden result address, when returning mem */
|
||||||
|
LowerVar vars[LOWER_MAX_LOCALS];
|
||||||
|
unsigned var_count;
|
||||||
|
/* Loop targets, for break and continue. */
|
||||||
|
unsigned break_target[32];
|
||||||
|
unsigned continue_target[32];
|
||||||
|
unsigned loop_depth;
|
||||||
|
int failed;
|
||||||
|
} Lower;
|
||||||
|
|
||||||
|
typedef struct Slot {
|
||||||
|
int is_place;
|
||||||
|
unsigned temp; /* the value, when is_place is 0 */
|
||||||
|
FeIrPlace place; /* where it lives, when is_place is 1 */
|
||||||
|
FeIrType type;
|
||||||
|
unsigned long size; /* for FE_IR_MEM */
|
||||||
|
} Slot;
|
||||||
|
|
||||||
|
static Slot lower_expr(Lower *L, FeNode *n);
|
||||||
|
static void lower_stmt(Lower *L, FeNode *n);
|
||||||
|
|
||||||
|
static void fail(Lower *L, const char *why, FeNode *n)
|
||||||
|
{
|
||||||
|
if (L->failed) return;
|
||||||
|
L->failed = 1;
|
||||||
|
fprintf(fe_diag_stream(), "%s:%lu:%lu: internal: cannot lower %s\n",
|
||||||
|
n && n->loc.file ? n->loc.file : "?",
|
||||||
|
n ? n->loc.line : 0UL, n ? n->loc.col : 0UL, why);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------- types --- */
|
||||||
|
|
||||||
|
/* A Ferro type becomes what a register can hold, or a size in memory. Anything
|
||||||
|
with more than one field is memory: the backend never has to decide whether
|
||||||
|
an aggregate fits somewhere. */
|
||||||
|
static FeIrType ir_type_of(const FeType *t)
|
||||||
|
{
|
||||||
|
if (!t) return FE_IR_VOID;
|
||||||
|
switch (t->kind) {
|
||||||
|
case FE_TYPE_VOID: return FE_IR_VOID;
|
||||||
|
case FE_TYPE_BOOL:
|
||||||
|
case FE_TYPE_CHAR: return FE_IR_I8;
|
||||||
|
case FE_TYPE_INT:
|
||||||
|
if (t->bits <= 8U) return FE_IR_I8;
|
||||||
|
if (t->bits <= 16U) return FE_IR_I16;
|
||||||
|
return FE_IR_I32;
|
||||||
|
case FE_TYPE_REF: return FE_IR_PTR;
|
||||||
|
case FE_TYPE_OWNED:
|
||||||
|
/* An owned slice carries a length beside the pointer. */
|
||||||
|
return t->elem && t->elem->kind == FE_TYPE_SLICE ? FE_IR_MEM : FE_IR_PTR;
|
||||||
|
case FE_TYPE_ENUM:
|
||||||
|
/* A payload-free enum is just its tag. */
|
||||||
|
return t->variant_count && t->fields ? FE_IR_MEM :
|
||||||
|
(t->size <= 1UL ? FE_IR_I8 :
|
||||||
|
t->size <= 2UL ? FE_IR_I16 : FE_IR_I32);
|
||||||
|
default:
|
||||||
|
return FE_IR_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int enum_has_payload(const FeType *t)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
if (!t || t->kind != FE_TYPE_ENUM) return 0;
|
||||||
|
for (i = 0; i < t->variant_count; ++i)
|
||||||
|
if (t->variants[i].field_count) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FeIrType ir_type(const FeType *t)
|
||||||
|
{
|
||||||
|
if (t && t->kind == FE_TYPE_ENUM && enum_has_payload(t)) return FE_IR_MEM;
|
||||||
|
return ir_type_of(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ir_size(const FeType *t)
|
||||||
|
{
|
||||||
|
return t ? fe_type_size(t) : 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned ir_align(const FeType *t)
|
||||||
|
{
|
||||||
|
return t ? fe_type_align(t) : 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int type_is_unsigned(const FeType *t)
|
||||||
|
{
|
||||||
|
return t && t->kind == FE_TYPE_INT && t->is_unsigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------- slots --- */
|
||||||
|
|
||||||
|
static Slot slot_value(unsigned temp, FeIrType t)
|
||||||
|
{
|
||||||
|
Slot s;
|
||||||
|
s.is_place = 0; s.temp = temp; s.type = t; s.size = 0;
|
||||||
|
s.place = fe_ir_at_temp(0, 0);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Slot slot_place(FeIrPlace p, FeIrType t, unsigned long size)
|
||||||
|
{
|
||||||
|
Slot s;
|
||||||
|
s.is_place = 1; s.temp = 0; s.place = p; s.type = t; s.size = size;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Slot slot_void(void)
|
||||||
|
{
|
||||||
|
return slot_value(0, FE_IR_VOID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read a slot as a value. An aggregate has no value form, so asking for one is
|
||||||
|
a lowering bug rather than a program error. */
|
||||||
|
static unsigned as_value(Lower *L, Slot s, FeNode *n)
|
||||||
|
{
|
||||||
|
if (!s.is_place) return s.temp;
|
||||||
|
if (s.type == FE_IR_MEM) { fail(L, "an aggregate as a value", n); return 0; }
|
||||||
|
return fe_ir_load(L->m, L->b, s.type, s.place);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The address of a slot. */
|
||||||
|
static unsigned as_address(Lower *L, Slot s, FeNode *n)
|
||||||
|
{
|
||||||
|
if (!s.is_place) { fail(L, "the address of a temporary", n); return 0; }
|
||||||
|
return fe_ir_addr(L->m, L->b, s.place);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------- locals --- */
|
||||||
|
|
||||||
|
static unsigned declare_var(Lower *L, const char *cname, const FeType *t,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
unsigned local = fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t),
|
||||||
|
ir_align(t), name);
|
||||||
|
if (L->var_count < LOWER_MAX_LOCALS) {
|
||||||
|
L->vars[L->var_count].cname = cname;
|
||||||
|
L->vars[L->var_count].local = local;
|
||||||
|
++L->var_count;
|
||||||
|
}
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_var(Lower *L, const char *cname, unsigned *out)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
if (!cname) return 0;
|
||||||
|
for (i = L->var_count; i > 0; --i)
|
||||||
|
if (L->vars[i - 1].cname && strcmp(L->vars[i - 1].cname, cname) == 0) {
|
||||||
|
*out = L->vars[i - 1].local;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------- blocks --- */
|
||||||
|
|
||||||
|
static FeIrBlock *new_block(Lower *L)
|
||||||
|
{
|
||||||
|
return fe_ir_block(L->m, L->fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------- expressions --- */
|
||||||
|
|
||||||
|
static FeIrOp binary_op(const char *op, int *is_cmp)
|
||||||
|
{
|
||||||
|
*is_cmp = 0;
|
||||||
|
if (!op) return FE_IR_ADD;
|
||||||
|
if (!strcmp(op, "+") || !strcmp(op, "+%")) return FE_IR_ADD;
|
||||||
|
if (!strcmp(op, "-") || !strcmp(op, "-%")) return FE_IR_SUB;
|
||||||
|
if (!strcmp(op, "*") || !strcmp(op, "*%")) return FE_IR_MUL;
|
||||||
|
if (!strcmp(op, "/")) return FE_IR_DIV;
|
||||||
|
if (!strcmp(op, "%")) return FE_IR_MOD;
|
||||||
|
if (!strcmp(op, "&")) return FE_IR_AND;
|
||||||
|
if (!strcmp(op, "|")) return FE_IR_OR;
|
||||||
|
if (!strcmp(op, "^")) return FE_IR_XOR;
|
||||||
|
if (!strcmp(op, "<<")) return FE_IR_SHL;
|
||||||
|
if (!strcmp(op, ">>")) return FE_IR_SHR;
|
||||||
|
*is_cmp = 1;
|
||||||
|
if (!strcmp(op, "==")) return FE_IR_EQ;
|
||||||
|
if (!strcmp(op, "!=")) return FE_IR_NE;
|
||||||
|
if (!strcmp(op, "<")) return FE_IR_LT;
|
||||||
|
if (!strcmp(op, "<=")) return FE_IR_LE;
|
||||||
|
if (!strcmp(op, ">")) return FE_IR_GT;
|
||||||
|
if (!strcmp(op, ">=")) return FE_IR_GE;
|
||||||
|
*is_cmp = 0;
|
||||||
|
return FE_IR_ADD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long literal_value(FeNode *n)
|
||||||
|
{
|
||||||
|
const char *s = n->text;
|
||||||
|
long v = 0;
|
||||||
|
int neg = 0;
|
||||||
|
if (!s) return 0;
|
||||||
|
if (!strcmp(s, "true")) return 1;
|
||||||
|
if (!strcmp(s, "false")) return 0;
|
||||||
|
if (!strcmp(s, "null") || !strcmp(s, "undefined")) return 0;
|
||||||
|
if (*s == '\'') {
|
||||||
|
/* A character literal; the lexer kept the quotes. */
|
||||||
|
if (s[1] == '\\') {
|
||||||
|
switch (s[2]) {
|
||||||
|
case 'n': return 10;
|
||||||
|
case 't': return 9;
|
||||||
|
case 'r': return 13;
|
||||||
|
case '0': return 0;
|
||||||
|
default: return (long)(unsigned char)s[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (long)(unsigned char)s[1];
|
||||||
|
}
|
||||||
|
if (*s == '-') { neg = 1; ++s; }
|
||||||
|
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
|
||||||
|
s += 2;
|
||||||
|
for (; *s; ++s) {
|
||||||
|
int d = *s >= '0' && *s <= '9' ? *s - '0' :
|
||||||
|
*s >= 'a' && *s <= 'f' ? *s - 'a' + 10 :
|
||||||
|
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : -1;
|
||||||
|
if (d < 0) { if (*s == '_') continue; break; }
|
||||||
|
v = v * 16 + d;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (; *s; ++s) {
|
||||||
|
if (*s == '_') continue;
|
||||||
|
if (*s < '0' || *s > '9') break;
|
||||||
|
v = v * 10 + (*s - '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return neg ? -v : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* `and` and `or` do not evaluate the right side unless they have to, so they
|
||||||
|
are control flow rather than an operation. */
|
||||||
|
static Slot lower_logical(Lower *L, FeNode *n, int is_and)
|
||||||
|
{
|
||||||
|
unsigned result = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "logical");
|
||||||
|
FeIrBlock *rhs = new_block(L);
|
||||||
|
FeIrBlock *join = new_block(L);
|
||||||
|
FeIrBlock *entry = L->b;
|
||||||
|
unsigned left;
|
||||||
|
unsigned right;
|
||||||
|
L->b = entry;
|
||||||
|
left = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
fe_ir_store(L->m, L->b, fe_ir_at_local(result, 0), left);
|
||||||
|
if (is_and) fe_ir_br(L->b, left, rhs->id, join->id);
|
||||||
|
else fe_ir_br(L->b, left, join->id, rhs->id);
|
||||||
|
L->b = rhs;
|
||||||
|
right = as_value(L, lower_expr(L, n->b), n->b);
|
||||||
|
fe_ir_store(L->m, L->b, fe_ir_at_local(result, 0), right);
|
||||||
|
fe_ir_jmp(L->b, join->id);
|
||||||
|
L->b = join;
|
||||||
|
return slot_place(fe_ir_at_local(result, 0), FE_IR_I8, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Slot lower_call(Lower *L, FeNode *n)
|
||||||
|
{
|
||||||
|
unsigned args[16];
|
||||||
|
unsigned count = 0;
|
||||||
|
FeNode *arg;
|
||||||
|
FeType *ret = n->sem_type;
|
||||||
|
FeIrType rt = ir_type(ret);
|
||||||
|
unsigned result_local = 0;
|
||||||
|
const char *callee = n->a && n->a->cname ? n->a->cname :
|
||||||
|
(n->sem_decl && n->sem_decl->cname ?
|
||||||
|
n->sem_decl->cname : 0);
|
||||||
|
if (!callee) { fail(L, "a call with no target", n); return slot_void(); }
|
||||||
|
/* An aggregate result is written through a hidden first argument. */
|
||||||
|
if (rt == FE_IR_MEM) {
|
||||||
|
result_local = fe_ir_local(L->m, L->fn, FE_IR_MEM, ir_size(ret),
|
||||||
|
ir_align(ret), "result");
|
||||||
|
args[count++] = fe_ir_addr(L->m, L->b, fe_ir_at_local(result_local, 0));
|
||||||
|
}
|
||||||
|
for (arg = n->children; arg; arg = arg->next) {
|
||||||
|
Slot a = lower_expr(L, arg);
|
||||||
|
if (count >= 16) { fail(L, "too many arguments", n); break; }
|
||||||
|
args[count++] = a.type == FE_IR_MEM ? as_address(L, a, arg)
|
||||||
|
: as_value(L, a, arg);
|
||||||
|
}
|
||||||
|
if (rt == FE_IR_MEM) {
|
||||||
|
fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count);
|
||||||
|
return slot_place(fe_ir_at_local(result_local, 0), FE_IR_MEM,
|
||||||
|
ir_size(ret));
|
||||||
|
}
|
||||||
|
if (rt == FE_IR_VOID) {
|
||||||
|
fe_ir_call(L->m, L->b, FE_IR_VOID, callee, args, count);
|
||||||
|
return slot_void();
|
||||||
|
}
|
||||||
|
return slot_value(fe_ir_call(L->m, L->b, rt, callee, args, count), rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Slot lower_expr(Lower *L, FeNode *n)
|
||||||
|
{
|
||||||
|
FeType *t;
|
||||||
|
FeIrType it;
|
||||||
|
if (!n || L->failed) return slot_void();
|
||||||
|
t = n->sem_type;
|
||||||
|
it = ir_type(t);
|
||||||
|
switch (n->kind) {
|
||||||
|
case FE_N_LITERAL:
|
||||||
|
return slot_value(fe_ir_const(L->m, L->b,
|
||||||
|
it == FE_IR_VOID ? FE_IR_I32 : it,
|
||||||
|
literal_value(n)),
|
||||||
|
it == FE_IR_VOID ? FE_IR_I32 : it);
|
||||||
|
case FE_N_IDENT: {
|
||||||
|
unsigned local;
|
||||||
|
if (find_var(L, n->cname, &local))
|
||||||
|
return slot_place(fe_ir_at_local(local, 0), it, ir_size(t));
|
||||||
|
if (n->cname)
|
||||||
|
return slot_place(fe_ir_at_global(n->cname, 0), it, ir_size(t));
|
||||||
|
fail(L, "an unresolved name", n);
|
||||||
|
return slot_void();
|
||||||
|
}
|
||||||
|
case FE_N_BINARY: {
|
||||||
|
int is_cmp = 0;
|
||||||
|
FeIrOp op;
|
||||||
|
unsigned a;
|
||||||
|
unsigned b;
|
||||||
|
FeIrType operand;
|
||||||
|
if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or")))
|
||||||
|
return lower_logical(L, n, !strcmp(n->text, "and"));
|
||||||
|
op = binary_op(n->text, &is_cmp);
|
||||||
|
operand = ir_type(n->a ? n->a->sem_type : 0);
|
||||||
|
if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32;
|
||||||
|
a = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
b = as_value(L, lower_expr(L, n->b), n->b);
|
||||||
|
return slot_value(fe_ir_binary(L->m, L->b, op, operand, a, b,
|
||||||
|
type_is_unsigned(n->a ? n->a->sem_type
|
||||||
|
: 0)),
|
||||||
|
is_cmp ? FE_IR_I8 : operand);
|
||||||
|
}
|
||||||
|
case FE_N_UNARY:
|
||||||
|
if (n->text && !strcmp(n->text, "-")) {
|
||||||
|
unsigned zero = fe_ir_const(L->m, L->b, it, 0);
|
||||||
|
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_SUB, it, zero, v,
|
||||||
|
0), it);
|
||||||
|
}
|
||||||
|
if (n->text && !strcmp(n->text, "not")) {
|
||||||
|
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0);
|
||||||
|
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I8, v,
|
||||||
|
zero, 0), FE_IR_I8);
|
||||||
|
}
|
||||||
|
if (n->text && (!strcmp(n->text, "&") || !strcmp(n->text, "&mut"))) {
|
||||||
|
Slot inner = lower_expr(L, n->a);
|
||||||
|
return slot_value(as_address(L, inner, n->a), FE_IR_PTR);
|
||||||
|
}
|
||||||
|
fail(L, "this unary operator", n);
|
||||||
|
return slot_void();
|
||||||
|
case FE_N_MEMBER:
|
||||||
|
/* `p.^` reads through a pointer. */
|
||||||
|
if (n->text && !strcmp(n->text, ".^")) {
|
||||||
|
unsigned p = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t));
|
||||||
|
}
|
||||||
|
/* A field is a constant offset from the base. */
|
||||||
|
{
|
||||||
|
FeType *base = n->a ? n->a->sem_type : 0;
|
||||||
|
FeFieldType *field;
|
||||||
|
Slot b;
|
||||||
|
if (base && (base->kind == FE_TYPE_REF ||
|
||||||
|
base->kind == FE_TYPE_OWNED)) base = base->elem;
|
||||||
|
field = fe_type_field(base, n->b && n->b->text ? n->b->text : "");
|
||||||
|
if (!field) { fail(L, "an unresolved field", n); return slot_void(); }
|
||||||
|
b = lower_expr(L, n->a);
|
||||||
|
if (n->a->sem_type && (n->a->sem_type->kind == FE_TYPE_REF ||
|
||||||
|
n->a->sem_type->kind == FE_TYPE_OWNED)) {
|
||||||
|
unsigned p = as_value(L, b, n->a);
|
||||||
|
return slot_place(fe_ir_at_temp(p, (long)field->offset), it,
|
||||||
|
ir_size(t));
|
||||||
|
}
|
||||||
|
if (!b.is_place) { fail(L, "a field of a temporary", n); return slot_void(); }
|
||||||
|
b.place.offset += (long)field->offset;
|
||||||
|
return slot_place(b.place, it, ir_size(t));
|
||||||
|
}
|
||||||
|
case FE_N_CALL:
|
||||||
|
return lower_call(L, n);
|
||||||
|
case FE_N_EXPR:
|
||||||
|
return lower_expr(L, n->a);
|
||||||
|
default:
|
||||||
|
fail(L, "this expression", n);
|
||||||
|
return slot_void();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------- statements --- */
|
||||||
|
|
||||||
|
static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n,
|
||||||
|
unsigned long size)
|
||||||
|
{
|
||||||
|
if (value.type == FE_IR_MEM) {
|
||||||
|
if (!value.is_place) { fail(L, "an aggregate value", n); return; }
|
||||||
|
fe_ir_copy(L->m, L->b, dst, value.place, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fe_ir_store(L->m, L->b, dst, as_value(L, value, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lower_return(Lower *L, FeNode *n)
|
||||||
|
{
|
||||||
|
Slot v;
|
||||||
|
if (!n->a) { fe_ir_ret(L->b, 0, 0); return; }
|
||||||
|
v = lower_expr(L, n->a);
|
||||||
|
if (L->fn->returns_by_address) {
|
||||||
|
store_into(L, fe_ir_at_temp(L->ret_local, 0), v, n,
|
||||||
|
ir_size(L->ret_type));
|
||||||
|
fe_ir_ret(L->b, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fe_ir_ret(L->b, as_value(L, v, n->a), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lower_if(Lower *L, FeNode *n)
|
||||||
|
{
|
||||||
|
FeIrBlock *then_b = new_block(L);
|
||||||
|
FeIrBlock *else_b = n->c ? new_block(L) : 0;
|
||||||
|
FeIrBlock *join = new_block(L);
|
||||||
|
unsigned cond = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
fe_ir_br(L->b, cond, then_b->id, else_b ? else_b->id : join->id);
|
||||||
|
L->b = then_b;
|
||||||
|
lower_stmt(L, n->b);
|
||||||
|
fe_ir_jmp(L->b, join->id);
|
||||||
|
if (else_b) {
|
||||||
|
L->b = else_b;
|
||||||
|
lower_stmt(L, n->c);
|
||||||
|
fe_ir_jmp(L->b, join->id);
|
||||||
|
}
|
||||||
|
L->b = join;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lower_while(Lower *L, FeNode *n)
|
||||||
|
{
|
||||||
|
FeIrBlock *head = new_block(L);
|
||||||
|
FeIrBlock *body = new_block(L);
|
||||||
|
FeIrBlock *done = new_block(L);
|
||||||
|
unsigned cond;
|
||||||
|
fe_ir_jmp(L->b, head->id);
|
||||||
|
L->b = head;
|
||||||
|
cond = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
fe_ir_br(L->b, cond, body->id, done->id);
|
||||||
|
if (L->loop_depth < 32) {
|
||||||
|
L->break_target[L->loop_depth] = done->id;
|
||||||
|
L->continue_target[L->loop_depth] = head->id;
|
||||||
|
++L->loop_depth;
|
||||||
|
}
|
||||||
|
L->b = body;
|
||||||
|
lower_stmt(L, n->b);
|
||||||
|
fe_ir_jmp(L->b, head->id);
|
||||||
|
if (L->loop_depth) --L->loop_depth;
|
||||||
|
L->b = done;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lower_stmt(Lower *L, FeNode *n)
|
||||||
|
{
|
||||||
|
FeNode *x;
|
||||||
|
if (!n || L->failed) return;
|
||||||
|
switch (n->kind) {
|
||||||
|
case FE_N_BLOCK:
|
||||||
|
for (x = n->children; x; x = x->next) lower_stmt(L, x);
|
||||||
|
return;
|
||||||
|
case FE_N_LET:
|
||||||
|
case FE_N_VAR:
|
||||||
|
case FE_N_CONST: {
|
||||||
|
unsigned local = declare_var(L, n->cname, n->sem_type, n->text);
|
||||||
|
if (n->b) {
|
||||||
|
Slot v = lower_expr(L, n->b);
|
||||||
|
store_into(L, fe_ir_at_local(local, 0), v, n, ir_size(n->sem_type));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case FE_N_ASSIGN: {
|
||||||
|
Slot dst = lower_expr(L, n->a);
|
||||||
|
Slot v = lower_expr(L, n->b);
|
||||||
|
if (!dst.is_place) { fail(L, "an assignment to a value", n); return; }
|
||||||
|
store_into(L, dst.place, v, n, dst.size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case FE_N_EXPR_STMT:
|
||||||
|
lower_expr(L, n->a);
|
||||||
|
return;
|
||||||
|
case FE_N_RETURN:
|
||||||
|
lower_return(L, n);
|
||||||
|
return;
|
||||||
|
case FE_N_IF:
|
||||||
|
lower_if(L, n);
|
||||||
|
return;
|
||||||
|
case FE_N_WHILE:
|
||||||
|
lower_while(L, n);
|
||||||
|
return;
|
||||||
|
case FE_N_BREAK:
|
||||||
|
if (L->loop_depth) fe_ir_jmp(L->b, L->break_target[L->loop_depth - 1]);
|
||||||
|
return;
|
||||||
|
case FE_N_CONTINUE:
|
||||||
|
if (L->loop_depth)
|
||||||
|
fe_ir_jmp(L->b, L->continue_target[L->loop_depth - 1]);
|
||||||
|
return;
|
||||||
|
case FE_N_UNSAFE:
|
||||||
|
lower_stmt(L, n->a);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
fail(L, "this statement", n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ functions --- */
|
||||||
|
|
||||||
|
static void lower_fn(Lower *L, FeNode *fn)
|
||||||
|
{
|
||||||
|
FeNode *p;
|
||||||
|
FeType *ret = fn->b ? fe_type_from_ast(&L->c->types, fn->b) : 0;
|
||||||
|
FeIrFunc *f;
|
||||||
|
if (!fn->cname) return;
|
||||||
|
f = fe_ir_func(L->m, fn->cname, ir_type(ret), ir_size(ret));
|
||||||
|
if (!f) return;
|
||||||
|
L->fn = f;
|
||||||
|
L->ret_type = ret;
|
||||||
|
L->var_count = 0;
|
||||||
|
L->loop_depth = 0;
|
||||||
|
/* A hidden first parameter holds where an aggregate result goes. */
|
||||||
|
if (f->returns_by_address)
|
||||||
|
L->ret_local = fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, "result");
|
||||||
|
for (p = fn->a ? fn->a->children : 0; p; p = p->next) {
|
||||||
|
FeType *pt = fe_type_from_ast(&L->c->types, p->a);
|
||||||
|
/* An aggregate parameter arrives as an address. */
|
||||||
|
unsigned local = ir_type(pt) == FE_IR_MEM
|
||||||
|
? fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, p->text)
|
||||||
|
: fe_ir_local(L->m, f, ir_type(pt), ir_size(pt), ir_align(pt),
|
||||||
|
p->text);
|
||||||
|
if (L->var_count < LOWER_MAX_LOCALS) {
|
||||||
|
L->vars[L->var_count].cname = p->cname;
|
||||||
|
L->vars[L->var_count].local = local;
|
||||||
|
++L->var_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f->param_count = f->local_count;
|
||||||
|
L->b = fe_ir_block(L->m, f);
|
||||||
|
lower_stmt(L, fn->c);
|
||||||
|
/* A void function may just run off the end. */
|
||||||
|
fe_ir_ret(L->b, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fe_lower_program(FeCheck *c, FeIrModule *out)
|
||||||
|
{
|
||||||
|
Lower L;
|
||||||
|
unsigned u;
|
||||||
|
FeNode *n;
|
||||||
|
memset(&L, 0, sizeof L);
|
||||||
|
L.c = c;
|
||||||
|
L.m = out;
|
||||||
|
for (u = 0; u < c->build->count; ++u) {
|
||||||
|
FeUnit *unit = &c->build->units[u];
|
||||||
|
c->ast = &unit->ast;
|
||||||
|
c->unit = unit;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return !L.failed;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef FE_LOWER_H
|
||||||
|
#define FE_LOWER_H
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
#include "ir.h"
|
||||||
|
|
||||||
|
/* Turn the checked program into IR.
|
||||||
|
|
||||||
|
The checker leaves every expression with a type and every declaration with a
|
||||||
|
link-visible name; lowering reads those and produces the flat form the
|
||||||
|
backend wants. Everything Ferro-shaped is expanded here -- `try` becomes a
|
||||||
|
branch, `defer` is copied onto each exit path, an index becomes a comparison
|
||||||
|
and a trap -- so that neither the checker nor the backend has to know about
|
||||||
|
the other's world. */
|
||||||
|
|
||||||
|
int fe_lower_program(FeCheck *c, FeIrModule *out);
|
||||||
|
|
||||||
|
#endif
|
||||||
+1
-1
@@ -34,7 +34,7 @@ ROOT = Path(__file__).resolve().parent.parent
|
|||||||
FIXTURES = ROOT / "fec" / "tests"
|
FIXTURES = ROOT / "fec" / "tests"
|
||||||
WATCOM = ROOT / ".dosboxx" / "watcom"
|
WATCOM = ROOT / ".dosboxx" / "watcom"
|
||||||
SOURCES = ("arena", "diag", "lexer", "ast", "parser", "types", "m7", "own",
|
SOURCES = ("arena", "diag", "lexer", "ast", "parser", "types", "m7", "own",
|
||||||
"check", "resolve", "ir", "driver")
|
"check", "resolve", "ir", "lower", "driver")
|
||||||
# Fixtures live here until there is a code generator to run them against.
|
# Fixtures live here until there is a code generator to run them against.
|
||||||
QUARANTINE = "pending-backend"
|
QUARANTINE = "pending-backend"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user