| // Derived from Inferno utils/6c/reg.c | |
| // http://code.google.com/p/inferno-os/source/browse/utils/6c/reg.c | |
| // | |
| // Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. | |
| // Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) | |
| // Portions Copyright © 1997-1999 Vita Nuova Limited | |
| // Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) | |
| // Portions Copyright © 2004,2006 Bruce Ellis | |
| // Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) | |
| // Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others | |
| // Portions Copyright © 2009 The Go Authors. All rights reserved. | |
| // | |
| // Permission is hereby granted, free of charge, to any person obtaining a copy | |
| // of this software and associated documentation files (the "Software"), to deal | |
| // in the Software without restriction, including without limitation the rights | |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| // copies of the Software, and to permit persons to whom the Software is | |
| // furnished to do so, subject to the following conditions: | |
| // | |
| // The above copyright notice and this permission notice shall be included in | |
| // all copies or substantial portions of the Software. | |
| // | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| // THE SOFTWARE. | |
| // "Portable" optimizations. | |
| // Compiled separately for 5g, 6g, and 8g, so allowed to use gg.h, opt.h. | |
| // Must code to the intersection of the three back ends. | |
| #include <u.h> | |
| #include <libc.h> | |
| #include "gg.h" | |
| #include "opt.h" | |
| // p is a call instruction. Does the call fail to return? | |
| int | |
| noreturn(Prog *p) | |
| { | |
| Sym *s; | |
| int i; | |
| static Sym* symlist[10]; | |
| if(symlist[0] == S) { | |
| symlist[0] = pkglookup("panicindex", runtimepkg); | |
| symlist[1] = pkglookup("panicslice", runtimepkg); | |
| symlist[2] = pkglookup("throwinit", runtimepkg); | |
| symlist[3] = pkglookup("panic", runtimepkg); | |
| symlist[4] = pkglookup("panicwrap", runtimepkg); | |
| symlist[5] = pkglookup("throwreturn", runtimepkg); | |
| symlist[6] = pkglookup("selectgo", runtimepkg); | |
| symlist[7] = pkglookup("block", runtimepkg); | |
| } | |
| if(p->to.node == nil) | |
| return 0; | |
| s = p->to.node->sym; | |
| if(s == S) | |
| return 0; | |
| for(i=0; symlist[i]!=S; i++) | |
| if(s == symlist[i]) | |
| return 1; | |
| return 0; | |
| } | |
| // JMP chasing and removal. | |
| // | |
| // The code generator depends on being able to write out jump | |
| // instructions that it can jump to now but fill in later. | |
| // the linker will resolve them nicely, but they make the code | |
| // longer and more difficult to follow during debugging. | |
| // Remove them. | |
| /* what instruction does a JMP to p eventually land on? */ | |
| static Prog* | |
| chasejmp(Prog *p, int *jmploop) | |
| { | |
| int n; | |
| n = 0; | |
| while(p != P && p->as == AJMP && p->to.type == D_BRANCH) { | |
| if(++n > 10) { | |
| *jmploop = 1; | |
| break; | |
| } | |
| p = p->to.u.branch; | |
| } | |
| return p; | |
| } | |
| /* | |
| * reuse reg pointer for mark/sweep state. | |
| * leave reg==nil at end because alive==nil. | |
| */ | |
| #define alive ((void*)0) | |
| #define dead ((void*)1) | |
| /* mark all code reachable from firstp as alive */ | |
| static void | |
| mark(Prog *firstp) | |
| { | |
| Prog *p; | |
| for(p=firstp; p; p=p->link) { | |
| if(p->opt != dead) | |
| break; | |
| p->opt = alive; | |
| if(p->as != ACALL && p->to.type == D_BRANCH && p->to.u.branch) | |
| mark(p->to.u.branch); | |
| if(p->as == AJMP || p->as == ARET || p->as == AUNDEF) | |
| break; | |
| } | |
| } | |
| void | |
| fixjmp(Prog *firstp) | |
| { | |
| int jmploop; | |
| Prog *p, *last; | |
| if(debug['R'] && debug['v']) | |
| print("\nfixjmp\n"); | |
| // pass 1: resolve jump to jump, mark all code as dead. | |
| jmploop = 0; | |
| for(p=firstp; p; p=p->link) { | |
| if(debug['R'] && debug['v']) | |
| print("%P\n", p); | |
| if(p->as != ACALL && p->to.type == D_BRANCH && p->to.u.branch && p->to.u.branch->as == AJMP) { | |
| p->to.u.branch = chasejmp(p->to.u.branch, &jmploop); | |
| if(debug['R'] && debug['v']) | |
| print("->%P\n", p); | |
| } | |
| p->opt = dead; | |
| } | |
| if(debug['R'] && debug['v']) | |
| print("\n"); | |
| // pass 2: mark all reachable code alive | |
| mark(firstp); | |
| // pass 3: delete dead code (mostly JMPs). | |
| last = nil; | |
| for(p=firstp; p; p=p->link) { | |
| if(p->opt == dead) { | |
| if(p->link == P && p->as == ARET && last && last->as != ARET) { | |
| // This is the final ARET, and the code so far doesn't have one. | |
| // Let it stay. The register allocator assumes that all live code in | |
| // the function can be traversed by starting at all the RET instructions | |
| // and following predecessor links. If we remove the final RET, | |
| // this assumption will not hold in the case of an infinite loop | |
| // at the end of a function. | |
| // Keep the RET but mark it dead for the liveness analysis. | |
| p->mode = 1; | |
| } else { | |
| if(debug['R'] && debug['v']) | |
| print("del %P\n", p); | |
| continue; | |
| } | |
| } | |
| if(last) | |
| last->link = p; | |
| last = p; | |
| } | |
| last->link = P; | |
| // pass 4: elide JMP to next instruction. | |
| // only safe if there are no jumps to JMPs anymore. | |
| if(!jmploop) { | |
| last = nil; | |
| for(p=firstp; p; p=p->link) { | |
| if(p->as == AJMP && p->to.type == D_BRANCH && p->to.u.branch == p->link) { | |
| if(debug['R'] && debug['v']) | |
| print("del %P\n", p); | |
| continue; | |
| } | |
| if(last) | |
| last->link = p; | |
| last = p; | |
| } | |
| last->link = P; | |
| } | |
| if(debug['R'] && debug['v']) { | |
| print("\n"); | |
| for(p=firstp; p; p=p->link) | |
| print("%P\n", p); | |
| print("\n"); | |
| } | |
| } | |
| #undef alive | |
| #undef dead | |
| // Control flow analysis. The Flow structures hold predecessor and successor | |
| // information as well as basic loop analysis. | |
| // | |
| // graph = flowstart(firstp, sizeof(Flow)); | |
| // ... use flow graph ... | |
| // flowend(graph); // free graph | |
| // | |
| // Typical uses of the flow graph are to iterate over all the flow-relevant instructions: | |
| // | |
| // for(f = graph->start; f != nil; f = f->link) | |
| // | |
| // or, given an instruction f, to iterate over all the predecessors, which is | |
| // f->p1 and this list: | |
| // | |
| // for(f2 = f->p2; f2 != nil; f2 = f2->p2link) | |
| // | |
| // Often the Flow struct is embedded as the first field inside a larger struct S. | |
| // In that case casts are needed to convert Flow* to S* in many places but the | |
| // idea is the same. Pass sizeof(S) instead of sizeof(Flow) to flowstart. | |
| Graph* | |
| flowstart(Prog *firstp, int size) | |
| { | |
| int nf; | |
| Flow *f, *f1, *start, *last; | |
| Graph *graph; | |
| Prog *p; | |
| ProgInfo info; | |
| // Count and mark instructions to annotate. | |
| nf = 0; | |
| for(p = firstp; p != P; p = p->link) { | |
| p->opt = nil; // should be already, but just in case | |
| proginfo(&info, p); | |
| if(info.flags & Skip) | |
| continue; | |
| p->opt = (void*)1; | |
| nf++; | |
| } | |
| if(nf == 0) | |
| return nil; | |
| if(nf >= 20000) { | |
| // fatal("%S is too big (%d instructions)", curfn->nname->sym, nf); | |
| return nil; | |
| } | |
| // Allocate annotations and assign to instructions. | |
| graph = calloc(sizeof *graph + size*nf, 1); | |
| if(graph == nil) | |
| fatal("out of memory"); | |
| start = (Flow*)(graph+1); | |
| last = nil; | |
| f = start; | |
| for(p = firstp; p != P; p = p->link) { | |
| if(p->opt == nil) | |
| continue; | |
| p->opt = f; | |
| f->prog = p; | |
| if(last) | |
| last->link = f; | |
| last = f; | |
| f = (Flow*)((uchar*)f + size); | |
| } | |
| // Fill in pred/succ information. | |
| for(f = start; f != nil; f = f->link) { | |
| p = f->prog; | |
| proginfo(&info, p); | |
| if(!(info.flags & Break)) { | |
| f1 = f->link; | |
| f->s1 = f1; | |
| f1->p1 = f; | |
| } | |
| if(p->to.type == D_BRANCH) { | |
| if(p->to.u.branch == P) | |
| fatal("pnil %P", p); | |
| f1 = p->to.u.branch->opt; | |
| if(f1 == nil) | |
| fatal("fnil %P / %P", p, p->to.u.branch); | |
| if(f1 == f) { | |
| //fatal("self loop %P", p); | |
| continue; | |
| } | |
| f->s2 = f1; | |
| f->p2link = f1->p2; | |
| f1->p2 = f; | |
| } | |
| } | |
| graph->start = start; | |
| graph->num = nf; | |
| return graph; | |
| } | |
| void | |
| flowend(Graph *graph) | |
| { | |
| Flow *f; | |
| for(f = graph->start; f != nil; f = f->link) | |
| f->prog->opt = nil; | |
| free(graph); | |
| } | |
| /* | |
| * find looping structure | |
| * | |
| * 1) find reverse postordering | |
| * 2) find approximate dominators, | |
| * the actual dominators if the flow graph is reducible | |
| * otherwise, dominators plus some other non-dominators. | |
| * See Matthew S. Hecht and Jeffrey D. Ullman, | |
| * "Analysis of a Simple Algorithm for Global Data Flow Problems", | |
| * Conf. Record of ACM Symp. on Principles of Prog. Langs, Boston, Massachusetts, | |
| * Oct. 1-3, 1973, pp. 207-217. | |
| * 3) find all nodes with a predecessor dominated by the current node. | |
| * such a node is a loop head. | |
| * recursively, all preds with a greater rpo number are in the loop | |
| */ | |
| static int32 | |
| postorder(Flow *r, Flow **rpo2r, int32 n) | |
| { | |
| Flow *r1; | |
| r->rpo = 1; | |
| r1 = r->s1; | |
| if(r1 && !r1->rpo) | |
| n = postorder(r1, rpo2r, n); | |
| r1 = r->s2; | |
| if(r1 && !r1->rpo) | |
| n = postorder(r1, rpo2r, n); | |
| rpo2r[n] = r; | |
| n++; | |
| return n; | |
| } | |
| static int32 | |
| rpolca(int32 *idom, int32 rpo1, int32 rpo2) | |
| { | |
| int32 t; | |
| if(rpo1 == -1) | |
| return rpo2; | |
| while(rpo1 != rpo2){ | |
| if(rpo1 > rpo2){ | |
| t = rpo2; | |
| rpo2 = rpo1; | |
| rpo1 = t; | |
| } | |
| while(rpo1 < rpo2){ | |
| t = idom[rpo2]; | |
| if(t >= rpo2) | |
| fatal("bad idom"); | |
| rpo2 = t; | |
| } | |
| } | |
| return rpo1; | |
| } | |
| static int | |
| doms(int32 *idom, int32 r, int32 s) | |
| { | |
| while(s > r) | |
| s = idom[s]; | |
| return s == r; | |
| } | |
| static int | |
| loophead(int32 *idom, Flow *r) | |
| { | |
| int32 src; | |
| src = r->rpo; | |
| if(r->p1 != nil && doms(idom, src, r->p1->rpo)) | |
| return 1; | |
| for(r = r->p2; r != nil; r = r->p2link) | |
| if(doms(idom, src, r->rpo)) | |
| return 1; | |
| return 0; | |
| } | |
| static void | |
| loopmark(Flow **rpo2r, int32 head, Flow *r) | |
| { | |
| if(r->rpo < head || r->active == head) | |
| return; | |
| r->active = head; | |
| r->loop += LOOP; | |
| if(r->p1 != nil) | |
| loopmark(rpo2r, head, r->p1); | |
| for(r = r->p2; r != nil; r = r->p2link) | |
| loopmark(rpo2r, head, r); | |
| } | |
| void | |
| flowrpo(Graph *g) | |
| { | |
| Flow *r1; | |
| int32 i, d, me, nr, *idom; | |
| Flow **rpo2r; | |
| free(g->rpo); | |
| g->rpo = calloc(g->num*sizeof g->rpo[0], 1); | |
| idom = calloc(g->num*sizeof idom[0], 1); | |
| if(g->rpo == nil || idom == nil) | |
| fatal("out of memory"); | |
| for(r1 = g->start; r1 != nil; r1 = r1->link) | |
| r1->active = 0; | |
| rpo2r = g->rpo; | |
| d = postorder(g->start, rpo2r, 0); | |
| nr = g->num; | |
| if(d > nr) | |
| fatal("too many reg nodes %d %d", d, nr); | |
| nr = d; | |
| for(i = 0; i < nr / 2; i++) { | |
| r1 = rpo2r[i]; | |
| rpo2r[i] = rpo2r[nr - 1 - i]; | |
| rpo2r[nr - 1 - i] = r1; | |
| } | |
| for(i = 0; i < nr; i++) | |
| rpo2r[i]->rpo = i; | |
| idom[0] = 0; | |
| for(i = 0; i < nr; i++) { | |
| r1 = rpo2r[i]; | |
| me = r1->rpo; | |
| d = -1; | |
| // rpo2r[r->rpo] == r protects against considering dead code, | |
| // which has r->rpo == 0. | |
| if(r1->p1 != nil && rpo2r[r1->p1->rpo] == r1->p1 && r1->p1->rpo < me) | |
| d = r1->p1->rpo; | |
| for(r1 = r1->p2; r1 != nil; r1 = r1->p2link) | |
| if(rpo2r[r1->rpo] == r1 && r1->rpo < me) | |
| d = rpolca(idom, d, r1->rpo); | |
| idom[i] = d; | |
| } | |
| for(i = 0; i < nr; i++) { | |
| r1 = rpo2r[i]; | |
| r1->loop++; | |
| if(r1->p2 != nil && loophead(idom, r1)) | |
| loopmark(rpo2r, i, r1); | |
| } | |
| free(idom); | |
| for(r1 = g->start; r1 != nil; r1 = r1->link) | |
| r1->active = 0; | |
| } | |
| Flow* | |
| uniqp(Flow *r) | |
| { | |
| Flow *r1; | |
| r1 = r->p1; | |
| if(r1 == nil) { | |
| r1 = r->p2; | |
| if(r1 == nil || r1->p2link != nil) | |
| return nil; | |
| } else | |
| if(r->p2 != nil) | |
| return nil; | |
| return r1; | |
| } | |
| Flow* | |
| uniqs(Flow *r) | |
| { | |
| Flow *r1; | |
| r1 = r->s1; | |
| if(r1 == nil) { | |
| r1 = r->s2; | |
| if(r1 == nil) | |
| return nil; | |
| } else | |
| if(r->s2 != nil) | |
| return nil; | |
| return r1; | |
| } | |
| // The compilers assume they can generate temporary variables | |
| // as needed to preserve the right semantics or simplify code | |
| // generation and the back end will still generate good code. | |
| // This results in a large number of ephemeral temporary variables. | |
| // Merge temps with non-overlapping lifetimes and equal types using the | |
| // greedy algorithm in Poletto and Sarkar, "Linear Scan Register Allocation", | |
| // ACM TOPLAS 1999. | |
| typedef struct TempVar TempVar; | |
| typedef struct TempFlow TempFlow; | |
| struct TempVar | |
| { | |
| Node *node; | |
| TempFlow *def; // definition of temp var | |
| TempFlow *use; // use list, chained through TempFlow.uselink | |
| TempVar *freelink; // next free temp in Type.opt list | |
| TempVar *merge; // merge var with this one | |
| vlong start; // smallest Prog.pc in live range | |
| vlong end; // largest Prog.pc in live range | |
| uchar addr; // address taken - no accurate end | |
| uchar removed; // removed from program | |
| }; | |
| struct TempFlow | |
| { | |
| Flow f; | |
| TempFlow *uselink; | |
| }; | |
| static int | |
| startcmp(const void *va, const void *vb) | |
| { | |
| TempVar *a, *b; | |
| a = *(TempVar**)va; | |
| b = *(TempVar**)vb; | |
| if(a->start < b->start) | |
| return -1; | |
| if(a->start > b->start) | |
| return +1; | |
| return 0; | |
| } | |
| // Is n available for merging? | |
| static int | |
| canmerge(Node *n) | |
| { | |
| return n->class == PAUTO && strncmp(n->sym->name, "autotmp", 7) == 0; | |
| } | |
| static void mergewalk(TempVar*, TempFlow*, uint32); | |
| static void varkillwalk(TempVar*, TempFlow*, uint32); | |
| void | |
| mergetemp(Prog *firstp) | |
| { | |
| int i, j, nvar, ninuse, nfree, nkill; | |
| TempVar *var, *v, *v1, **bystart, **inuse; | |
| TempFlow *r; | |
| NodeList *l, **lp; | |
| Node *n; | |
| Prog *p, *p1; | |
| Type *t; | |
| ProgInfo info, info1; | |
| int32 gen; | |
| Graph *g; | |
| enum { Debug = 0 }; | |
| g = flowstart(firstp, sizeof(TempFlow)); | |
| if(g == nil) | |
| return; | |
| // Build list of all mergeable variables. | |
| nvar = 0; | |
| for(l = curfn->dcl; l != nil; l = l->next) | |
| if(canmerge(l->n)) | |
| nvar++; | |
| var = calloc(nvar*sizeof var[0], 1); | |
| nvar = 0; | |
| for(l = curfn->dcl; l != nil; l = l->next) { | |
| n = l->n; | |
| if(canmerge(n)) { | |
| v = &var[nvar++]; | |
| n->opt = v; | |
| v->node = n; | |
| } | |
| } | |
| // Build list of uses. | |
| // We assume that the earliest reference to a temporary is its definition. | |
| // This is not true of variables in general but our temporaries are all | |
| // single-use (that's why we have so many!). | |
| for(r = (TempFlow*)g->start; r != nil; r = (TempFlow*)r->f.link) { | |
| p = r->f.prog; | |
| proginfo(&info, p); | |
| if(p->from.node != N && p->from.node->opt && p->to.node != N && p->to.node->opt) | |
| fatal("double node %P", p); | |
| if((n = p->from.node) != N && (v = n->opt) != nil || | |
| (n = p->to.node) != N && (v = n->opt) != nil) { | |
| if(v->def == nil) | |
| v->def = r; | |
| r->uselink = v->use; | |
| v->use = r; | |
| if(n == p->from.node && (info.flags & LeftAddr)) | |
| v->addr = 1; | |
| } | |
| } | |
| if(Debug > 1) | |
| dumpit("before", g->start, 0); | |
| nkill = 0; | |
| // Special case. | |
| for(v = var; v < var+nvar; v++) { | |
| if(v->addr) | |
| continue; | |
| // Used in only one instruction, which had better be a write. | |
| if((r = v->use) != nil && r->uselink == nil) { | |
| p = r->f.prog; | |
| proginfo(&info, p); | |
| if(p->to.node == v->node && (info.flags & RightWrite) && !(info.flags & RightRead)) { | |
| p->as = ANOP; | |
| p->to = zprog.to; | |
| v->removed = 1; | |
| if(Debug) | |
| print("drop write-only %S\n", v->node->sym); | |
| } else | |
| fatal("temp used and not set: %P", p); | |
| nkill++; | |
| continue; | |
| } | |
| // Written in one instruction, read in the next, otherwise unused, | |
| // no jumps to the next instruction. Happens mainly in 386 compiler. | |
| if((r = v->use) != nil && r->f.link == &r->uselink->f && r->uselink->uselink == nil && uniqp(r->f.link) == &r->f) { | |
| p = r->f.prog; | |
| proginfo(&info, p); | |
| p1 = r->f.link->prog; | |
| proginfo(&info1, p1); | |
| enum { | |
| SizeAny = SizeB | SizeW | SizeL | SizeQ | SizeF | SizeD, | |
| }; | |
| if(p->from.node == v->node && p1->to.node == v->node && (info.flags & Move) && | |
| !((info.flags|info1.flags) & (LeftAddr|RightAddr)) && | |
| (info.flags & SizeAny) == (info1.flags & SizeAny)) { | |
| p1->from = p->from; | |
| excise(&r->f); | |
| v->removed = 1; | |
| if(Debug) | |
| print("drop immediate-use %S\n", v->node->sym); | |
| } | |
| nkill++; | |
| continue; | |
| } | |
| } | |
| // Traverse live range of each variable to set start, end. | |
| // Each flood uses a new value of gen so that we don't have | |
| // to clear all the r->f.active words after each variable. | |
| gen = 0; | |
| for(v = var; v < var+nvar; v++) { | |
| gen++; | |
| for(r = v->use; r != nil; r = r->uselink) | |
| mergewalk(v, r, gen); | |
| if(v->addr) { | |
| gen++; | |
| for(r = v->use; r != nil; r = r->uselink) | |
| varkillwalk(v, r, gen); | |
| } | |
| } | |
| // Sort variables by start. | |
| bystart = malloc(nvar*sizeof bystart[0]); | |
| for(i=0; i<nvar; i++) | |
| bystart[i] = &var[i]; | |
| qsort(bystart, nvar, sizeof bystart[0], startcmp); | |
| // List of in-use variables, sorted by end, so that the ones that | |
| // will last the longest are the earliest ones in the array. | |
| // The tail inuse[nfree:] holds no-longer-used variables. | |
| // In theory we should use a sorted tree so that insertions are | |
| // guaranteed O(log n) and then the loop is guaranteed O(n log n). | |
| // In practice, it doesn't really matter. | |
| inuse = malloc(nvar*sizeof inuse[0]); | |
| ninuse = 0; | |
| nfree = nvar; | |
| for(i=0; i<nvar; i++) { | |
| v = bystart[i]; | |
| if(v->removed) | |
| continue; | |
| // Expire no longer in use. | |
| while(ninuse > 0 && inuse[ninuse-1]->end < v->start) { | |
| v1 = inuse[--ninuse]; | |
| inuse[--nfree] = v1; | |
| } | |
| // Find old temp to reuse if possible. | |
| t = v->node->type; | |
| for(j=nfree; j<nvar; j++) { | |
| v1 = inuse[j]; | |
| // Require the types to match but also require the addrtaken bits to match. | |
| // If a variable's address is taken, that disables registerization for the individual | |
| // words of the variable (for example, the base,len,cap of a slice). | |
| // We don't want to merge a non-addressed var with an addressed one and | |
| // inhibit registerization of the former. | |
| if(eqtype(t, v1->node->type) && v->node->addrtaken == v1->node->addrtaken) { | |
| inuse[j] = inuse[nfree++]; | |
| if(v1->merge) | |
| v->merge = v1->merge; | |
| else | |
| v->merge = v1; | |
| nkill++; | |
| break; | |
| } | |
| } | |
| // Sort v into inuse. | |
| j = ninuse++; | |
| while(j > 0 && inuse[j-1]->end < v->end) { | |
| inuse[j] = inuse[j-1]; | |
| j--; | |
| } | |
| inuse[j] = v; | |
| } | |
| if(Debug) { | |
| print("%S [%d - %d]\n", curfn->nname->sym, nvar, nkill); | |
| for(v=var; v<var+nvar; v++) { | |
| print("var %#N %T %lld-%lld", v->node, v->node->type, v->start, v->end); | |
| if(v->addr) | |
| print(" addr=1"); | |
| if(v->removed) | |
| print(" dead=1"); | |
| if(v->merge) | |
| print(" merge %#N", v->merge->node); | |
| if(v->start == v->end) | |
| print(" %P", v->def->f.prog); | |
| print("\n"); | |
| } | |
| if(Debug > 1) | |
| dumpit("after", g->start, 0); | |
| } | |
| // Update node references to use merged temporaries. | |
| for(r = (TempFlow*)g->start; r != nil; r = (TempFlow*)r->f.link) { | |
| p = r->f.prog; | |
| if((n = p->from.node) != N && (v = n->opt) != nil && v->merge != nil) | |
| p->from.node = v->merge->node; | |
| if((n = p->to.node) != N && (v = n->opt) != nil && v->merge != nil) | |
| p->to.node = v->merge->node; | |
| } | |
| // Delete merged nodes from declaration list. | |
| for(lp = &curfn->dcl; (l = *lp); ) { | |
| curfn->dcl->end = l; | |
| n = l->n; | |
| v = n->opt; | |
| if(v && (v->merge || v->removed)) { | |
| *lp = l->next; | |
| continue; | |
| } | |
| lp = &l->next; | |
| } | |
| // Clear aux structures. | |
| for(v=var; v<var+nvar; v++) | |
| v->node->opt = nil; | |
| free(var); | |
| free(bystart); | |
| free(inuse); | |
| flowend(g); | |
| } | |
| static void | |
| mergewalk(TempVar *v, TempFlow *r0, uint32 gen) | |
| { | |
| Prog *p; | |
| TempFlow *r1, *r, *r2; | |
| for(r1 = r0; r1 != nil; r1 = (TempFlow*)r1->f.p1) { | |
| if(r1->f.active == gen) | |
| break; | |
| r1->f.active = gen; | |
| p = r1->f.prog; | |
| if(v->end < p->pc) | |
| v->end = p->pc; | |
| if(r1 == v->def) { | |
| v->start = p->pc; | |
| break; | |
| } | |
| } | |
| for(r = r0; r != r1; r = (TempFlow*)r->f.p1) | |
| for(r2 = (TempFlow*)r->f.p2; r2 != nil; r2 = (TempFlow*)r2->f.p2link) | |
| mergewalk(v, r2, gen); | |
| } | |
| static void | |
| varkillwalk(TempVar *v, TempFlow *r0, uint32 gen) | |
| { | |
| Prog *p; | |
| TempFlow *r1, *r; | |
| for(r1 = r0; r1 != nil; r1 = (TempFlow*)r1->f.s1) { | |
| if(r1->f.active == gen) | |
| break; | |
| r1->f.active = gen; | |
| p = r1->f.prog; | |
| if(v->end < p->pc) | |
| v->end = p->pc; | |
| if(v->start > p->pc) | |
| v->start = p->pc; | |
| if(p->as == ARET || (p->as == AVARKILL && p->to.node == v->node)) | |
| break; | |
| } | |
| for(r = r0; r != r1; r = (TempFlow*)r->f.s1) | |
| varkillwalk(v, (TempFlow*)r->f.s2, gen); | |
| } | |
| // Eliminate redundant nil pointer checks. | |
| // | |
| // The code generation pass emits a CHECKNIL for every possibly nil pointer. | |
| // This pass removes a CHECKNIL if every predecessor path has already | |
| // checked this value for nil. | |
| // | |
| // Simple backwards flood from check to definition. | |
| // Run prog loop backward from end of program to beginning to avoid quadratic | |
| // behavior removing a run of checks. | |
| // | |
| // Assume that stack variables with address not taken can be loaded multiple times | |
| // from memory without being rechecked. Other variables need to be checked on | |
| // each load. | |
| typedef struct NilVar NilVar; | |
| typedef struct NilFlow NilFlow; | |
| struct NilFlow { | |
| Flow f; | |
| int kill; | |
| }; | |
| static void nilwalkback(NilFlow *rcheck); | |
| static void nilwalkfwd(NilFlow *rcheck); | |
| void | |
| nilopt(Prog *firstp) | |
| { | |
| NilFlow *r; | |
| Prog *p; | |
| Graph *g; | |
| int ncheck, nkill; | |
| g = flowstart(firstp, sizeof(NilFlow)); | |
| if(g == nil) | |
| return; | |
| if(debug_checknil > 1 /* || strcmp(curfn->nname->sym->name, "f1") == 0 */) | |
| dumpit("nilopt", g->start, 0); | |
| ncheck = 0; | |
| nkill = 0; | |
| for(r = (NilFlow*)g->start; r != nil; r = (NilFlow*)r->f.link) { | |
| p = r->f.prog; | |
| if(p->as != ACHECKNIL || !regtyp(&p->from)) | |
| continue; | |
| ncheck++; | |
| if(stackaddr(&p->from)) { | |
| if(debug_checknil && p->lineno > 1) | |
| warnl(p->lineno, "removed nil check of SP address"); | |
| r->kill = 1; | |
| continue; | |
| } | |
| nilwalkfwd(r); | |
| if(r->kill) { | |
| if(debug_checknil && p->lineno > 1) | |
| warnl(p->lineno, "removed nil check before indirect"); | |
| continue; | |
| } | |
| nilwalkback(r); | |
| if(r->kill) { | |
| if(debug_checknil && p->lineno > 1) | |
| warnl(p->lineno, "removed repeated nil check"); | |
| continue; | |
| } | |
| } | |
| for(r = (NilFlow*)g->start; r != nil; r = (NilFlow*)r->f.link) { | |
| if(r->kill) { | |
| nkill++; | |
| excise(&r->f); | |
| } | |
| } | |
| flowend(g); | |
| if(debug_checknil > 1) | |
| print("%S: removed %d of %d nil checks\n", curfn->nname->sym, nkill, ncheck); | |
| } | |
| static void | |
| nilwalkback(NilFlow *rcheck) | |
| { | |
| Prog *p; | |
| ProgInfo info; | |
| NilFlow *r; | |
| for(r = rcheck; r != nil; r = (NilFlow*)uniqp(&r->f)) { | |
| p = r->f.prog; | |
| proginfo(&info, p); | |
| if((info.flags & RightWrite) && sameaddr(&p->to, &rcheck->f.prog->from)) { | |
| // Found initialization of value we're checking for nil. | |
| // without first finding the check, so this one is unchecked. | |
| return; | |
| } | |
| if(r != rcheck && p->as == ACHECKNIL && sameaddr(&p->from, &rcheck->f.prog->from)) { | |
| rcheck->kill = 1; | |
| return; | |
| } | |
| } | |
| // Here is a more complex version that scans backward across branches. | |
| // It assumes rcheck->kill = 1 has been set on entry, and its job is to find a reason | |
| // to keep the check (setting rcheck->kill = 0). | |
| // It doesn't handle copying of aggregates as well as I would like, | |
| // nor variables with their address taken, | |
| // and it's too subtle to turn on this late in Go 1.2. Perhaps for Go 1.3. | |
| /* | |
| for(r1 = r0; r1 != nil; r1 = (NilFlow*)r1->f.p1) { | |
| if(r1->f.active == gen) | |
| break; | |
| r1->f.active = gen; | |
| p = r1->f.prog; | |
| // If same check, stop this loop but still check | |
| // alternate predecessors up to this point. | |
| if(r1 != rcheck && p->as == ACHECKNIL && sameaddr(&p->from, &rcheck->f.prog->from)) | |
| break; | |
| proginfo(&info, p); | |
| if((info.flags & RightWrite) && sameaddr(&p->to, &rcheck->f.prog->from)) { | |
| // Found initialization of value we're checking for nil. | |
| // without first finding the check, so this one is unchecked. | |
| rcheck->kill = 0; | |
| return; | |
| } | |
| if(r1->f.p1 == nil && r1->f.p2 == nil) { | |
| print("lost pred for %P\n", rcheck->f.prog); | |
| for(r1=r0; r1!=nil; r1=(NilFlow*)r1->f.p1) { | |
| proginfo(&info, r1->f.prog); | |
| print("\t%P %d %d %D %D\n", r1->f.prog, info.flags&RightWrite, sameaddr(&r1->f.prog->to, &rcheck->f.prog->from), &r1->f.prog->to, &rcheck->f.prog->from); | |
| } | |
| fatal("lost pred trail"); | |
| } | |
| } | |
| for(r = r0; r != r1; r = (NilFlow*)r->f.p1) | |
| for(r2 = (NilFlow*)r->f.p2; r2 != nil; r2 = (NilFlow*)r2->f.p2link) | |
| nilwalkback(rcheck, r2, gen); | |
| */ | |
| } | |
| static void | |
| nilwalkfwd(NilFlow *rcheck) | |
| { | |
| NilFlow *r; | |
| Prog *p; | |
| ProgInfo info; | |
| // If the path down from rcheck dereferences the address | |
| // (possibly with a small offset) before writing to memory | |
| // and before any subsequent checks, it's okay to wait for | |
| // that implicit check. Only consider this basic block to | |
| // avoid problems like: | |
| // _ = *x // should panic | |
| // for {} // no writes but infinite loop may be considered visible | |
| for(r = (NilFlow*)uniqs(&rcheck->f); r != nil; r = (NilFlow*)uniqs(&r->f)) { | |
| p = r->f.prog; | |
| proginfo(&info, p); | |
| if((info.flags & LeftRead) && smallindir(&p->from, &rcheck->f.prog->from)) { | |
| rcheck->kill = 1; | |
| return; | |
| } | |
| if((info.flags & (RightRead|RightWrite)) && smallindir(&p->to, &rcheck->f.prog->from)) { | |
| rcheck->kill = 1; | |
| return; | |
| } | |
| // Stop if another nil check happens. | |
| if(p->as == ACHECKNIL) | |
| return; | |
| // Stop if value is lost. | |
| if((info.flags & RightWrite) && sameaddr(&p->to, &rcheck->f.prog->from)) | |
| return; | |
| // Stop if memory write. | |
| if((info.flags & RightWrite) && !regtyp(&p->to)) | |
| return; | |
| } | |
| } |