commit 4d17328fe53b8db469fc1a1e776c053ee6027208
parent 48d7a0a9070065db6fad2f3351f148173fda1be2
Author: Brian Swetland <swetland@frotz.net>
Date: Mon, 9 Mar 2020 20:05:34 -0700
compiler: flow control! (if, else, while, break)
- fix some incorrect ordering of relop enums
- gen_branch_{fwd,back,cond}() make branch generation more uniform
- rename gen_fixups -> fixup_branches_fwd()
- add fixup_branch_fwd()
- disable fixup branch deletion for now
- while, if/else, and break parsing and codegen
- gen_branch_cond() resolves a comparison or bool
into cmp a,b -> br<cc> or cmp a,1 -> br<cc>
- test case for basic flow control
Diffstat:
4 files changed, 174 insertions(+), 19 deletions(-)
diff --git a/src/compiler.c b/src/compiler.c
@@ -75,11 +75,11 @@ char *tnames[] = {
};
// encodings for ops in Items
-enum { rEQ, rGT, rGE, rLT, rLE, rNE }; // RelOps
+enum { rEQ, rNE, rLT, rLE, rGT, rGE }; // RelOps
enum { aADD, aSUB, aIOR, aXOR }; // AddOps
enum { mMUL, mDIV, mMOD, mAND, mANN, mLSL, mLSR }; // MulOps
-u8 invert_relop_tab[6] = { rNE, rLE, rLT, rGE, rGT, rEQ };
+u8 invert_relop_tab[6] = { rNE, rEQ, rGE, rGT, rLE, rLT };
typedef struct StringRec* String;
typedef struct ObjectRec* Object;
@@ -198,7 +198,7 @@ enum { // r a b
iReg, // regno
iRegInd, // regno offset
iCond, // ccode f-chain t-chain
- iComp, // comp regno-a regno-b
+ iComp, // relop regno-a regno-b
iFunc,
};
@@ -268,10 +268,25 @@ void gen_param(Ctx ctx, u32 n, Item val);
// call func, consumes parameters and func
void gen_call(Ctx, Item func);
+// generate a forward conditional branch
+// consumes x which must be Bool or Cond
+// returns address to fixup
+u32 gen_branch_cond(Ctx ctx, Item x, bool sense);
+
+// generate a backward branch to addr
+void gen_branch_back(Ctx ctx, u32 addr);
+
+// generate an unconditional forward branch
+// returns address for later fixup
+u32 gen_branch_fwd(Ctx ctx);
+
// patches provided list of branch fixups to branch to
// the address where we will emit the next instruction
-void gen_fixups(Ctx ctx, Fixup fixup);
+void fixup_branches_fwd(Ctx ctx, Fixup list);
+// patches a single branch at addr to branch to the
+// address where we will emit the next instruction
+void fixup_branch_fwd(Ctx ctx, u32 addr);
String mkstring(Ctx ctx, const char* text, u32 len) {
String str = ctx->strtab;
@@ -701,7 +716,7 @@ void make_global(Ctx ctx, Object obj) {
// push a scope on the scope stack
// if obj is non-nil, it is the first of a list of items in that scope
-void push_scope(Ctx ctx, u32 kind, Object obj) {
+Scope push_scope(Ctx ctx, u32 kind, Object obj) {
Scope scope = malloc(sizeof(ScopeRec));
scope->kind = kind;
scope->next = ctx->scope;
@@ -710,6 +725,7 @@ void push_scope(Ctx ctx, u32 kind, Object obj) {
scope->fixups = nil;
ctx->scope = scope;
+ return scope;
// XXX lazy scopes
}
@@ -717,7 +733,7 @@ void pop_scope(Ctx ctx) {
if (ctx->scope->level == 0) {
error(ctx, "cannot pop the global scope");
}
- gen_fixups(ctx, ctx->scope->fixups);
+ fixup_branches_fwd(ctx, ctx->scope->fixups);
// XXX delete?
ctx->scope = ctx->scope->next;
}
@@ -935,37 +951,72 @@ void parse_block(Ctx ctx);
void parse_while(Ctx ctx) {
ItemRec x;
+ u32 l0_loop = ctx->pc; // for backward branch
+
parse_expr(ctx, &x);
+ u32 l1_br_false = gen_branch_cond(ctx, &x, false);
+
require(ctx, tOBRACE);
push_scope(ctx, sLoop, nil);
parse_block(ctx);
+ gen_branch_back(ctx, l0_loop);
pop_scope(ctx);
+
+ fixup_branch_fwd(ctx, l1_br_false);
}
void parse_if(Ctx ctx) {
+ Scope outer = push_scope(ctx, sBlock, nil);
+
ItemRec x;
parse_expr(ctx, &x);
+ // branch over "if" code
+ u32 l0_br_false = gen_branch_cond(ctx, &x, false);
+
+ // generate "if" code
require(ctx, tOBRACE);
push_scope(ctx, sBlock, nil);
parse_block(ctx);
pop_scope(ctx);
+
+ // branch past "else" code
+ gen_branch_fwd(ctx);
+ add_scope_fixup(ctx, outer);
+
while (ctx->tok == tELSE) {
next(ctx);
+ fixup_branch_fwd(ctx, l0_br_false);
if (ctx->tok == tIF) {
next(ctx);
parse_expr(ctx, &x);
+ // branch over "if" code
+ l0_br_false = gen_branch_cond(ctx, &x, false);
+
+ // generate "if else" code
require(ctx, tOBRACE);
push_scope(ctx, sBlock, nil);
parse_block(ctx);
pop_scope(ctx);
+
+ // branch past "else" code
+ gen_branch_fwd(ctx);
+ add_scope_fixup(ctx, outer);
} else {
+ // generate "else" code
require(ctx, tOBRACE);
push_scope(ctx, sBlock, nil);
parse_block(ctx);
pop_scope(ctx);
- break;
+
+ // close outer scope
+ pop_scope(ctx);
+ return; // no further fixups needed
}
}
+ fixup_branch_fwd(ctx, l0_br_false);
+
+ // close outer scope
+ pop_scope(ctx);
}
void parse_return(Ctx ctx) {
@@ -986,6 +1037,17 @@ void parse_return(Ctx ctx) {
gen_return(ctx, &x);
}
+void parse_break(Ctx ctx) {
+ // XXX break-to-labeled-loop support
+ require(ctx, tSEMI);
+ gen_branch_fwd(ctx);
+ Scope scope = find_scope(ctx,sLoop);
+ if (scope == nil) {
+ error(ctx, "break must be used from inside a looping construct");
+ }
+ add_scope_fixup(ctx, scope);
+}
+
void parse_block(Ctx ctx) {
while (true) {
if (ctx->tok == tCBRACE) {
@@ -994,6 +1056,9 @@ void parse_block(Ctx ctx) {
} else if (ctx->tok == tRETURN) {
next(ctx);
parse_return(ctx);
+ } else if (ctx->tok == tBREAK) {
+ next(ctx);
+ parse_return(ctx);
} else if (ctx->tok == tWHILE) {
next(ctx);
parse_while(ctx);
@@ -1203,7 +1268,7 @@ void parse_function(Ctx ctx) {
// handle definition if it is one
if (isdef) {
// patch any forward references
- gen_fixups(ctx, obj->fixups);
+ fixup_branches_fwd(ctx, obj->fixups);
// mark as defined and save entry address
obj->flags |= ofDefined;
@@ -1423,6 +1488,41 @@ void gen_store(Ctx ctx, Item val, Item var) {
}
}
+u32 gen_branch_cond(Ctx ctx, Item x, bool sense) {
+ u32 cc;
+ if (x->kind == iComp) {
+ if (sense == false) {
+ x->r = invert_relop(x->r);
+ }
+ emit_op(ctx, SUB, x->a, x->a, x->b);
+ put_reg(ctx, x->a);
+ put_reg(ctx, x->b);
+ cc = rel_op_to_cc(x->r);
+ } else if (x->type == ctx->type_bool) {
+ gen_load(ctx, x);
+ emit_opi(ctx, SUB, x->r, x->r, 1);
+ put_reg(ctx, x->r);
+ if (sense) {
+ cc = EQ;
+ } else {
+ cc = NE;
+ }
+ } else {
+ error(ctx, "conditional branch needs comparison or bool");
+ }
+ emit_bi(ctx, cc, 0);
+ return ctx->pc - 4;
+}
+
+u32 gen_branch_fwd(Ctx ctx) {
+ emit_bi(ctx, AL, 0);
+ return ctx->pc - 4;
+}
+
+void gen_branch_back(Ctx ctx, u32 addr) {
+ emit_bi(ctx, AL, (addr - ctx->pc - 4) >> 2);
+}
+
void gen_return(Ctx ctx, Item x) {
if (x->type != ctx->type_void) {
gen_load_reg(ctx, x, R0);
@@ -1581,18 +1681,30 @@ void gen_unary_op(Ctx ctx, u32 op, Item x) {
}
}
-void gen_fixups(Ctx ctx, Fixup fixup) {
- if ((fixup != nil) && (fixup->pc == ctx->pc - 4)) {
- // if the most recent branch is the
- // previously emitted instruction, we
- // can just erase it.
+// patch branch instruction at addr to
+// branch to current pc
+void fixup_branch_fwd(Ctx ctx, u32 addr) {
+#if 0
+ // disabled for now as this gets tripped up
+ // by stuff like test/1030-flow-control.src (if (n==3) ...)
+ if (addr == ctx->pc - 4) {
+ // if the branch to be patched is the
+ // instruction just previously emitted, we
+ // can simply erase it
ctx->pc -= 4;
- fixup = fixup->next;
+ fprintf(stderr, "DELETED BRANCH @ 0x%x\n", ctx->pc);
+ } else
+#endif
+ {
+ u32 off = (ctx->pc - addr - 4) >> 2;
+ u32 ins = ctx->code[addr >> 2] & 0xFF000000;
+ ctx->code[addr >> 2] = ins | (off & 0x00FFFFFF);
}
+}
+
+void fixup_branches_fwd(Ctx ctx, Fixup fixup) {
while (fixup != nil) {
- u32 off = (ctx->pc - fixup->pc - 4) >> 2;
- u32 ins = ctx->code[fixup->pc >> 2] & 0xFF000000;
- ctx->code[fixup->pc >> 2] = ins | (off & 0x00FFFFFF);
+ fixup_branch_fwd(ctx, fixup->pc);
fixup = fixup->next;
}
}
diff --git a/test/1030-flow-control.log b/test/1030-flow-control.log
@@ -0,0 +1,10 @@
+D 10101010
+D 40404040
+D 00000002
+D 00000001
+D 00000007
+D 00000006
+D 00000005
+D 00000004
+D 00000003
+X 00000001
diff --git a/test/1030-flow-control.src b/test/1030-flow-control.src
@@ -0,0 +1,33 @@
+func hex(n i32) {
+ _hexout_(n);
+}
+
+func count(n i32) {
+ while (n > 0) {
+ hex(n);
+ if (n == 3) {
+ break;
+ }
+ n = n - 1;
+ }
+}
+
+func start() i32 {
+ if (true) {
+ hex(0x10101010);
+ } else {
+ hex(0x20202020);
+ }
+
+ if (false) {
+ hex(0x30303030);
+ } else {
+ hex(0x40404040);
+ }
+
+ count(2);
+
+ count(7);
+
+ return 1;
+}
diff --git a/test/runtest.sh b/test/runtest.sh
@@ -11,7 +11,7 @@ log="${txt%.txt}.log"
msg="${txt%.txt}.msg"
gold="${src%.src}.log"
-echo "RUNTEST: $src: compiling..."
+#echo "RUNTEST: $src: compiling..."
if bin/compiler -o "$bin" -l "$lst" "$src" 2> "$msg"; then
# success!
if [[ "$txt" == *"-err"* ]]; then
@@ -19,7 +19,7 @@ if bin/compiler -o "$bin" -l "$lst" "$src" 2> "$msg"; then
echo "RUNTEST: $src: FAIL: compiler did not detect error"
echo "FAIL: $src" > "$txt"
else
- echo "RUNTEST: $src: running..."
+ #echo "RUNTEST: $src: running..."
if bin/r5e "$bin" > "$log"; then
if diff "$log" "$gold"; then
echo "RUNTEST: $src: PASS"