Quentin Carbonneaux: 1 specialized branch compilation 4 files changed, 79 insertions(+), 32 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~mcf/cproc/patches/53742/mbox | git am -3Learn more about email & git
Hello, This patch is a minor refactor of code generation for branch conditions. Until now branch conditions were treated just like any other expression. With the present patch, and its associated modest increase in complexity, branch conditions are compiled in a context where the two targets (taken / not taken) are available. This leads to slightly simpler qbe IL. The change I was most concerned about is the different compilation of TLNOT. Rather than turning it into a comparison with zero early, we keep it in the IL and defer to funcjnz later on. I tested it with pointers and double arguments and the compilation seems to behave alright. The correctness argument I found is that the code path I introduce will have convert() called as part of funcjnz(), this same function was called in complex cases as a result of the EXPRCAST being generated when turning TLNOT into `== 0` (TEQL) in expr.c. cproc bootstraps fine with the patch. Coremark sees a 12% improvement when using qbe 1.2 as backend. --- cc.h | 1 + expr.c | 6 +++- qbe.c | 87 ++++++++++++++++++++++++++++++++++++++++++++-------------- stmt.c | 17 ++++-------- 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/cc.h b/cc.h index cb53b0d..3a56ea9 100644 --- a/cc.h +++ b/cc.h @@ -559,6 +559,7 @@ struct func *mkfunc(struct decl *, char *, struct type *, struct scope *); void delfunc(struct func *); struct type *functype(struct func *); void funclabel(struct func *, struct block *); +void funcbranch(struct func *, struct expr *, struct block *, struct block *); struct value *funcexpr(struct func *, struct expr *); void funcjmp(struct func *, struct block *); void funcjnz(struct func *, struct value *, struct type *, struct block *, struct block *); diff --git a/expr.c b/expr.c index aef6fe9..0bd2158 100644 --- a/expr.c +++ b/expr.c @@ -156,6 +156,10 @@ mkunaryexpr(enum tokenkind op, struct expr *base) expr->op = op; } return decay(expr); + case TLNOT: + expr = mkexpr(EXPRUNARY, &typeint, base); + expr->op = TLNOT; + return expr; } /* other unary operators get compiled as equivalent binary ones */ fatal("internal error: unknown unary operator %d", op); @@ -1069,7 +1073,7 @@ unaryexpr(struct scope *s) e = castexpr(s); if (!(e->type->prop & PROPSCALAR)) error(&tok.loc, "operator '!' must have scalar operand"); - e = mkbinaryexpr(&tok.loc, TEQL, e, mkconstexpr(&typeint, 0)); + e = mkunaryexpr(TLNOT, e); break; case TSIZEOF: case TALIGNOF: diff --git a/qbe.c b/qbe.c index 33d6b71..7951409 100644 --- a/qbe.c +++ b/qbe.c @@ -694,6 +694,69 @@ funclval(struct func *f, struct expr *e) return lval; } +void +funcbranch(struct func *f, struct expr *e, struct block *bt, struct block *bf) +{ + struct value *v; + struct block *b; + + /* Maybe we we could do something for EXPRCOND as well. */ + switch (e->kind) { + case EXPRUNARY: + if (e->op == TLNOT) { + funcbranch(f, e->base, bf, bt); + return; + } + break; + case EXPRBINARY: + if (e->op == TLOR || e->op == TLAND) { + if (e->op == TLOR) { + b = mkblock("logic_or"); + funcbranch(f, e->u.binary.l, bt, b); + } else { + b = mkblock("logic_and"); + funcbranch(f, e->u.binary.l, b, bf); + } + funclabel(f, b); + funcbranch(f, e->u.binary.r, bt, bf); + return; + } + break; + case EXPRCOMMA: + for (e = e->base; e->next; e = e->next) + funcexpr(f, e); + funcbranch(f, e, bt, bf); + return; + } + v = funcexpr(f, e); + funcjnz(f, v, e->type, bt, bf); +} + +static struct value * +funclogical(struct func *f, struct expr *e) +{ + struct block *b[3]; + + b[0] = mkblock("logic_true"); + b[1] = mkblock("logic_false"); + b[2] = mkblock("logic_join"); + + funcbranch(f, e, b[0], b[1]); + funclabel(f, b[0]); + funcjmp(f, b[2]); + funclabel(f, b[1]); + + b[2]->phi.blk[0] = b[0]; + b[2]->phi.val[0] = mkintconst(1); + b[2]->phi.blk[1] = b[1]; + b[2]->phi.val[1] = mkintconst(0); + functemp(f, &b[2]->phi.res); + b[2]->phi.class = 'w'; + funclabel(f, b[2]); + + return &b[2]->phi.res; +} + struct value * funcexpr(struct func *f, struct expr *e) { @@ -769,6 +832,8 @@ funcexpr(struct func *f, struct expr *e) return v; case EXPRUNARY: switch (e->op) { + case TLNOT: + return funclogical(f, e); case TBAND: lval = funclval(f, e->base); return lval.addr; @@ -787,28 +852,10 @@ funcexpr(struct func *f, struct expr *e) l = funcexpr(f, e->base); return convert(f, e->type, e->base->type, l); case EXPRBINARY: - l = funcexpr(f, e->u.binary.l); if (e->op == TLOR || e->op == TLAND) { - b[0] = mkblock("logic_right"); - b[1] = mkblock("logic_join"); - t = e->u.binary.l->type; - if (e->op == TLOR) { - funcjnz(f, l, t, b[1], b[0]); - b[1]->phi.val[0] = mkintconst(1); - } else { - funcjnz(f, l, t, b[0], b[1]); - b[1]->phi.val[0] = mkintconst(0); - } - b[1]->phi.blk[0] = f->end; - funclabel(f, b[0]); - r = funcexpr(f, e->u.binary.r); - b[1]->phi.val[1] = convert(f, &typebool, e->u.binary.r->type, r); - b[1]->phi.blk[1] = f->end; - funclabel(f, b[1]); - functemp(f, &b[1]->phi.res); - b[1]->phi.class = 'w'; - return &b[1]->phi.res; + return funclogical(f, e); } + l = funcexpr(f, e->u.binary.l); r = funcexpr(f, e->u.binary.r); t = e->u.binary.l->type; if (t->kind == TYPEPOINTER) diff --git a/stmt.c b/stmt.c index ff163bf..9b96f0d 100644 --- a/stmt.c +++ b/stmt.c @@ -104,13 +104,11 @@ stmt(struct func *f, struct scope *s) t = e->type; if (!(t->prop & PROPSCALAR)) error(&tok.loc, "controlling expression of if statement must have scalar type"); - v = funcexpr(f, e); - delexpr(e); - expect(TRPAREN, "after expression"); - b[0] = mkblock("if_true"); b[1] = mkblock("if_false"); - funcjnz(f, v, t, b[0], b[1]); + funcbranch(f, e, b[0], b[1]); + delexpr(e); + expect(TRPAREN, "after expression"); funclabel(f, b[0]); s = mkscope(s); @@ -181,8 +179,7 @@ stmt(struct func *f, struct scope *s) b[2] = mkblock("while_join"); funclabel(f, b[0]); - v = funcexpr(f, e); - funcjnz(f, v, t, b[1], b[2]); + funcbranch(f, e, b[1], b[2]); funclabel(f, b[1]); s = mkscope(s); s->continuelabel = b[0]; @@ -217,8 +214,7 @@ stmt(struct func *f, struct scope *s) error(&tok.loc, "controlling expression of loop must have scalar type"); expect(TRPAREN, "after expression"); - v = funcexpr(f, e); - funcjnz(f, v, t, b[0], b[2]); + funcbranch(f, e, b[0], b[2]); funclabel(f, b[2]); s = delscope(s); expect(TSEMICOLON, "after 'do' statement"); @@ -247,8 +243,7 @@ stmt(struct func *f, struct scope *s) t = e->type; if (!(t->prop & PROPSCALAR)) error(&tok.loc, "controlling expression of loop must have scalar type"); - v = funcexpr(f, e); - funcjnz(f, v, t, b[1], b[3]); + funcbranch(f, e, b[1], b[3]); delexpr(e); } expect(TSEMICOLON, NULL); -- 2.44.0