URL
https://opencores.org/ocsvn/eco32/eco32/trunk
Subversion Repositories eco32
[/] [eco32/] [trunk/] [fp/] [investigate/] [dag.c] - Rev 239
Go to most recent revision | Compare with Previous | Blame | View Log
#include "c.h" static char rcsid[] = "$Id: dag.c,v 1.1 2002/08/28 23:12:42 drh Exp $"; #define iscall(op) (generic(op) == CALL \ || IR->mulops_calls \ && (generic(op)==DIV||generic(op)==MOD||generic(op)==MUL) \ && ( optype(op)==U || optype(op)==I)) static Node forest; static struct dag { struct node node; struct dag *hlink; } *buckets[16]; int nodecount; static Tree firstarg; int assignargs = 1; int prunetemps = -1; static Node *tail; static int depth = 0; static Node replace(Node); static Node prune(Node); static Node asgnnode(Symbol, Node); static struct dag *dagnode(int, Node, Node, Symbol); static Symbol equated(Symbol); static void fixup(Node); static void labelnode(int); static void list(Node); static void killnodes(Symbol); static Node node(int, Node, Node, Symbol); static void printdag1(Node, int, int); static void printnode(Node, int, int); static void reset(void); static Node tmpnode(Node); static void typestab(Symbol, void *); static Node undag(Node); static Node visit(Node, int); static void unlist(void); void walk(Tree tp, int tlab, int flab) { listnodes(tp, tlab, flab); if (forest) { Node list = forest->link; forest->link = NULL; if (!IR->wants_dag && errcnt == 0) list = undag(list); code(Gen)->u.forest = list; forest = NULL; } reset(); deallocate(STMT); } static Node node(int op, Node l, Node r, Symbol sym) { int i; struct dag *p; i = (opindex(op)^((unsigned long)sym>>2))&(NELEMS(buckets)-1); for (p = buckets[i]; p; p = p->hlink) if (p->node.op == op && p->node.syms[0] == sym && p->node.kids[0] == l && p->node.kids[1] == r) return &p->node; p = dagnode(op, l, r, sym); p->hlink = buckets[i]; buckets[i] = p; ++nodecount; return &p->node; } static struct dag *dagnode(int op, Node l, Node r, Symbol sym) { struct dag *p; NEW0(p, FUNC); p->node.op = op; if ((p->node.kids[0] = l) != NULL) ++l->count; if ((p->node.kids[1] = r) != NULL) ++r->count; p->node.syms[0] = sym; return p; } Node newnode(int op, Node l, Node r, Symbol sym) { return &dagnode(op, l, r, sym)->node; } static void killnodes(Symbol p) { int i; struct dag **q; for (i = 0; i < NELEMS(buckets); i++) for (q = &buckets[i]; *q; ) if (generic((*q)->node.op) == INDIR && (!isaddrop((*q)->node.kids[0]->op) || (*q)->node.kids[0]->syms[0] == p)) { *q = (*q)->hlink; --nodecount; } else q = &(*q)->hlink; } static void reset(void) { if (nodecount > 0) memset(buckets, 0, sizeof buckets); nodecount = 0; } Node listnodes(Tree tp, int tlab, int flab) { Node p = NULL, l, r; int op; assert(tlab || flab || tlab == 0 && flab == 0); if (tp == NULL) return NULL; if (tp->node) return tp->node; if (isarray(tp->type)) op = tp->op + sizeop(voidptype->size); else op = tp->op + sizeop(tp->type->size); switch (generic(tp->op)) { case AND: { if (depth++ == 0) reset(); if (flab) { listnodes(tp->kids[0], 0, flab); listnodes(tp->kids[1], 0, flab); } else { listnodes(tp->kids[0], 0, flab = genlabel(1)); listnodes(tp->kids[1], tlab, 0); labelnode(flab); } depth--; } break; case OR: { if (depth++ == 0) reset(); if (tlab) { listnodes(tp->kids[0], tlab, 0); listnodes(tp->kids[1], tlab, 0); } else { tlab = genlabel(1); listnodes(tp->kids[0], tlab, 0); listnodes(tp->kids[1], 0, flab); labelnode(tlab); } depth--; } break; case NOT: { return listnodes(tp->kids[0], flab, tlab); } case COND: { Tree q = tp->kids[1]; assert(tlab == 0 && flab == 0); if (tp->u.sym) addlocal(tp->u.sym); flab = genlabel(2); listnodes(tp->kids[0], 0, flab); assert(q && q->op == RIGHT); reset(); listnodes(q->kids[0], 0, 0); if (forest->op == LABEL+V) { equatelab(forest->syms[0], findlabel(flab + 1)); unlist(); } list(jump(flab + 1)); labelnode(flab); listnodes(q->kids[1], 0, 0); if (forest->op == LABEL+V) { equatelab(forest->syms[0], findlabel(flab + 1)); unlist(); } labelnode(flab + 1); if (tp->u.sym) p = listnodes(idtree(tp->u.sym), 0, 0); } break; case CNST: { Type ty = unqual(tp->type); assert(ty->u.sym); if (tlab || flab) { assert(ty == inttype); if (tlab && tp->u.v.i != 0) list(jump(tlab)); else if (flab && tp->u.v.i == 0) list(jump(flab)); } else if (ty->u.sym->addressed) p = listnodes(cvtconst(tp), 0, 0); else p = node(op, NULL, NULL, constant(ty, tp->u.v)); } break; case RIGHT: { if ( tp->kids[0] && tp->kids[1] && generic(tp->kids[1]->op) == ASGN && (generic(tp->kids[0]->op) == INDIR && tp->kids[0]->kids[0] == tp->kids[1]->kids[0] || (tp->kids[0]->op == FIELD && tp->kids[0] == tp->kids[1]->kids[0]))) { assert(tlab == 0 && flab == 0); if (generic(tp->kids[0]->op) == INDIR) { p = listnodes(tp->kids[0], 0, 0); list(p); listnodes(tp->kids[1], 0, 0); } else { assert(generic(tp->kids[0]->kids[0]->op) == INDIR); list(listnodes(tp->kids[0]->kids[0], 0, 0)); p = listnodes(tp->kids[0], 0, 0); listnodes(tp->kids[1], 0, 0); } } else if (tp->kids[1]) { listnodes(tp->kids[0], 0, 0); p = listnodes(tp->kids[1], tlab, flab); } else p = listnodes(tp->kids[0], tlab, flab); } break; case JUMP: { assert(tlab == 0 && flab == 0); assert(tp->u.sym == 0); assert(tp->kids[0]); l = listnodes(tp->kids[0], 0, 0); list(newnode(JUMP+V, l, NULL, NULL)); reset(); } break; case CALL: { Tree save = firstarg; firstarg = NULL; assert(tlab == 0 && flab == 0); if (tp->op == CALL+B && !IR->wants_callb) { Tree arg0 = tree(ARG+P, tp->kids[1]->type, tp->kids[1], NULL); if (IR->left_to_right) firstarg = arg0; l = listnodes(tp->kids[0], 0, 0); if (!IR->left_to_right || firstarg) { firstarg = NULL; listnodes(arg0, 0, 0); } p = newnode(CALL+V, l, NULL, NULL); } else { l = listnodes(tp->kids[0], 0, 0); r = listnodes(tp->kids[1], 0, 0); p = newnode(tp->op == CALL+B ? tp->op : op, l, r, NULL); } NEW0(p->syms[0], FUNC); assert(isptr(tp->kids[0]->type)); assert(isfunc(tp->kids[0]->type->type)); p->syms[0]->type = tp->kids[0]->type->type; list(p); reset(); cfunc->u.f.ncalls++; firstarg = save; } break; case ARG: { assert(tlab == 0 && flab == 0); if (IR->left_to_right) listnodes(tp->kids[1], 0, 0); if (firstarg) { Tree arg = firstarg; firstarg = NULL; listnodes(arg, 0, 0); } l = listnodes(tp->kids[0], 0, 0); list(newnode(tp->op == ARG+B ? tp->op : op, l, NULL, NULL)); forest->syms[0] = intconst(tp->type->size); forest->syms[1] = intconst(tp->type->align); if (!IR->left_to_right) listnodes(tp->kids[1], 0, 0); } break; case EQ: case NE: case GT: case GE: case LE: case LT: { assert(tp->u.sym == 0); assert(errcnt || tlab || flab); l = listnodes(tp->kids[0], 0, 0); r = listnodes(tp->kids[1], 0, 0); assert(errcnt || opkind(l->op) == opkind(r->op)); assert(errcnt || optype(op) == optype(l->op)); if (tlab) assert(flab == 0), list(newnode(generic(tp->op) + opkind(l->op), l, r, findlabel(tlab))); else if (flab) { switch (generic(tp->op)) { case EQ: op = NE; break; case NE: op = EQ; break; case GT: op = LE; break; case LT: op = GE; break; case GE: op = LT; break; case LE: op = GT; break; default: assert(0); } list(newnode(op + opkind(l->op), l, r, findlabel(flab))); } if (forest && forest->syms[0]) forest->syms[0]->ref++; } break; case ASGN: { assert(tlab == 0 && flab == 0); if (tp->kids[0]->op == FIELD) { Tree x = tp->kids[0]->kids[0]; Field f = tp->kids[0]->u.field; assert(generic(x->op) == INDIR); reset(); l = listnodes(lvalue(x), 0, 0); if (fieldsize(f) < 8*f->type->size) { unsigned int fmask = fieldmask(f); unsigned int mask = fmask<<fieldright(f); Tree q = tp->kids[1]; if (q->op == CNST+I && q->u.v.i == 0 || q->op == CNST+U && q->u.v.u == 0) q = bittree(BAND, x, cnsttree(unsignedtype, (unsigned long)~mask)); else if (q->op == CNST+I && (q->u.v.i&fmask) == fmask || q->op == CNST+U && (q->u.v.u&fmask) == fmask) q = bittree(BOR, x, cnsttree(unsignedtype, (unsigned long)mask)); else { listnodes(q, 0, 0); q = bittree(BOR, bittree(BAND, rvalue(lvalue(x)), cnsttree(unsignedtype, (unsigned long)~mask)), bittree(BAND, shtree(LSH, cast(q, unsignedtype), cnsttree(unsignedtype, (unsigned long)fieldright(f))), cnsttree(unsignedtype, (unsigned long)mask))); } r = listnodes(q, 0, 0); op = ASGN + ttob(q->type); } else { r = listnodes(tp->kids[1], 0, 0); op = ASGN + ttob(tp->kids[1]->type); } } else { l = listnodes(tp->kids[0], 0, 0); r = listnodes(tp->kids[1], 0, 0); } list(newnode(tp->op == ASGN+B ? tp->op : op, l, r, NULL)); forest->syms[0] = intconst(tp->kids[1]->type->size); forest->syms[1] = intconst(tp->kids[1]->type->align); if (isaddrop(tp->kids[0]->op) && !tp->kids[0]->u.sym->computed) killnodes(tp->kids[0]->u.sym); else reset(); p = listnodes(tp->kids[1], 0, 0); } break; case BOR: case BAND: case BXOR: case ADD: case SUB: case RSH: case LSH: { assert(tlab == 0 && flab == 0); l = listnodes(tp->kids[0], 0, 0); r = listnodes(tp->kids[1], 0, 0); p = node(op, l, r, NULL); } break; case DIV: case MUL: case MOD: { assert(tlab == 0 && flab == 0); l = listnodes(tp->kids[0], 0, 0); r = listnodes(tp->kids[1], 0, 0); p = node(op, l, r, NULL); if (IR->mulops_calls && isint(tp->type)) { list(p); cfunc->u.f.ncalls++; } } break; case RET: { assert(tlab == 0 && flab == 0); l = listnodes(tp->kids[0], 0, 0); list(newnode(op, l, NULL, NULL)); } break; case CVF: case CVI: case CVP: case CVU: { assert(tlab == 0 && flab == 0); assert(optype(tp->kids[0]->op) != optype(tp->op) || tp->kids[0]->type->size != tp->type->size); l = listnodes(tp->kids[0], 0, 0); p = node(op, l, NULL, intconst(tp->kids[0]->type->size)); } break; case BCOM: case NEG: { assert(tlab == 0 && flab == 0); l = listnodes(tp->kids[0], 0, 0); p = node(op, l, NULL, NULL); } break; case INDIR: { Type ty = tp->kids[0]->type; assert(tlab == 0 && flab == 0); l = listnodes(tp->kids[0], 0, 0); if (isptr(ty)) ty = unqual(ty)->type; if (isvolatile(ty) || (isstruct(ty) && unqual(ty)->u.sym->u.s.vfields)) p = newnode(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); else p = node(tp->op == INDIR+B ? tp->op : op, l, NULL, NULL); } break; case FIELD: { Tree q = tp->kids[0]; if (tp->type == inttype) { long n = fieldleft(tp->u.field); q = shtree(RSH, shtree(LSH, q, cnsttree(inttype, n)), cnsttree(inttype, n + fieldright(tp->u.field))); } else if (fieldsize(tp->u.field) < 8*tp->u.field->type->size) q = bittree(BAND, shtree(RSH, q, cnsttree(inttype, (long)fieldright(tp->u.field))), cnsttree(unsignedtype, (unsigned long)fieldmask(tp->u.field))); assert(tlab == 0 && flab == 0); p = listnodes(q, 0, 0); } break; case ADDRG: case ADDRF: { assert(tlab == 0 && flab == 0); p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); } break; case ADDRL: { assert(tlab == 0 && flab == 0); if (tp->u.sym->generated) addlocal(tp->u.sym); p = node(tp->op + sizeop(voidptype->size), NULL, NULL, tp->u.sym); } break; default:assert(0); } tp->node = p; return p; } static void list(Node p) { if (p && p->link == NULL) { if (forest) { p->link = forest->link; forest->link = p; } else p->link = p; forest = p; } } static void labelnode(int lab) { assert(lab); if (forest && forest->op == LABEL+V) equatelab(findlabel(lab), forest->syms[0]); else list(newnode(LABEL+V, NULL, NULL, findlabel(lab))); reset(); } static void unlist(void) { Node p; assert(forest); assert(forest != forest->link); p = forest->link; while (p->link != forest) p = p->link; p->link = forest->link; forest = p; } Tree cvtconst(Tree p) { Symbol q = constant(p->type, p->u.v); Tree e; if (q->u.c.loc == NULL) q->u.c.loc = genident(STATIC, p->type, GLOBAL); if (isarray(p->type)) { e = simplify(ADDRG, atop(p->type), NULL, NULL); e->u.sym = q->u.c.loc; } else e = idtree(q->u.c.loc); return e; } void gencode(Symbol caller[], Symbol callee[]) { Code cp; Coordinate save; if (prunetemps == -1) prunetemps = !IR->wants_dag; save = src; if (assignargs) { int i; Symbol p, q; cp = codehead.next->next; codelist = codehead.next; for (i = 0; (p = callee[i]) != NULL && (q = caller[i]) != NULL; i++) if (p->sclass != q->sclass || p->type != q->type) walk(asgn(p, idtree(q)), 0, 0); codelist->next = cp; cp->prev = codelist; } if (glevel && IR->stabsym) { int i; Symbol p, q; for (i = 0; (p = callee[i]) != NULL && (q = caller[i]) != NULL; i++) { (*IR->stabsym)(p); if (p->sclass != q->sclass || p->type != q->type) (*IR->stabsym)(q); } swtoseg(CODE); } cp = codehead.next; for ( ; errcnt <= 0 && cp; cp = cp->next) switch (cp->kind) { case Address: assert(IR->address); (*IR->address)(cp->u.addr.sym, cp->u.addr.base, cp->u.addr.offset); break; case Blockbeg: { Symbol *p = cp->u.block.locals; (*IR->blockbeg)(&cp->u.block.x); for ( ; *p; p++) if ((*p)->ref != 0.0) (*IR->local)(*p); else if (glevel) (*IR->local)(*p); } break; case Blockend: (*IR->blockend)(&cp->u.begin->u.block.x); break; case Defpoint: src = cp->u.point.src; break; case Gen: case Jump: case Label: if (prunetemps) cp->u.forest = prune(cp->u.forest); fixup(cp->u.forest); cp->u.forest = (*IR->gen)(cp->u.forest); break; case Local: (*IR->local)(cp->u.var); break; case Switch: break; default: assert(0); } src = save; } static void fixup(Node p) { for ( ; p; p = p->link) switch (generic(p->op)) { case JUMP: if (specific(p->kids[0]->op) == ADDRG+P) p->kids[0]->syms[0] = equated(p->kids[0]->syms[0]); break; case LABEL: assert(p->syms[0] == equated(p->syms[0])); break; case EQ: case GE: case GT: case LE: case LT: case NE: assert(p->syms[0]); p->syms[0] = equated(p->syms[0]); } } static Symbol equated(Symbol p) { { Symbol q; for (q = p->u.l.equatedto; q; q = q->u.l.equatedto) assert(p != q); } while (p->u.l.equatedto) p = p->u.l.equatedto; return p; } void emitcode(void) { Code cp; Coordinate save; save = src; cp = codehead.next; for ( ; errcnt <= 0 && cp; cp = cp->next) switch (cp->kind) { case Address: break; case Blockbeg: if (glevel && IR->stabblock) { (*IR->stabblock)('{', cp->u.block.level - LOCAL, cp->u.block.locals); swtoseg(CODE); } break; case Blockend: if (glevel && IR->stabblock) { Code bp = cp->u.begin; foreach(bp->u.block.identifiers, bp->u.block.level, typestab, NULL); foreach(bp->u.block.types, bp->u.block.level, typestab, NULL); (*IR->stabblock)('}', bp->u.block.level - LOCAL, bp->u.block.locals); swtoseg(CODE); } break; case Defpoint: src = cp->u.point.src; if (glevel > 0 && IR->stabline) { (*IR->stabline)(&cp->u.point.src); swtoseg(CODE); } break; case Gen: case Jump: case Label: if (cp->u.forest) (*IR->emit)(cp->u.forest); break; case Local: if (glevel && IR->stabsym) { (*IR->stabsym)(cp->u.var); swtoseg(CODE); } break; case Switch: { int i; defglobal(cp->u.swtch.table, LIT); (*IR->defaddress)(equated(cp->u.swtch.labels[0])); for (i = 1; i < cp->u.swtch.size; i++) { long k = cp->u.swtch.values[i-1]; while (++k < cp->u.swtch.values[i]) assert(k < LONG_MAX), (*IR->defaddress)(equated(cp->u.swtch.deflab)); (*IR->defaddress)(equated(cp->u.swtch.labels[i])); } swtoseg(CODE); } break; default: assert(0); } src = save; } static Node undag(Node forest) { Node p; tail = &forest; for (p = forest; p; p = p->link) if (generic(p->op) == INDIR) { assert(p->count >= 1); visit(p, 1); if (p->syms[2]) { assert(p->syms[2]->u.t.cse); p->syms[2]->u.t.cse = NULL; addlocal(p->syms[2]); } } else if (iscall(p->op) && p->count >= 1) visit(p, 1); else { assert(p->count == 0), visit(p, 1); *tail = p; tail = &p->link; } *tail = NULL; return forest; } static Node replace(Node p) { if (p && ( generic(p->op) == INDIR && generic(p->kids[0]->op) == ADDRL && p->kids[0]->syms[0]->temporary && p->kids[0]->syms[0]->u.t.replace)) { p = p->kids[0]->syms[0]->u.t.cse; if (generic(p->op) == INDIR && isaddrop(p->kids[0]->op)) p = newnode(p->op, newnode(p->kids[0]->op, NULL, NULL, p->kids[0]->syms[0]), NULL, NULL); else if (generic(p->op) == ADDRG) p = newnode(p->op, NULL, NULL, p->syms[0]); else assert(0); p->count = 1; } else if (p) { p->kids[0] = replace(p->kids[0]); p->kids[1] = replace(p->kids[1]); } return p; } static Node prune(Node forest) { Node p, *tail = &forest; int count = 0; for (p = forest; p; p = p->link) { if (count > 0) { p->kids[0] = replace(p->kids[0]); p->kids[1] = replace(p->kids[1]); } if (( generic(p->op) == ASGN && generic(p->kids[0]->op) == ADDRL && p->kids[0]->syms[0]->temporary && p->kids[0]->syms[0]->u.t.cse == p->kids[1])) { Symbol tmp = p->kids[0]->syms[0]; if (!tmp->defined) (*IR->local)(tmp); tmp->defined = 1; if (( generic(p->kids[1]->op) == INDIR && isaddrop(p->kids[1]->kids[0]->op) && p->kids[1]->kids[0]->syms[0]->sclass == REGISTER) || (( generic(p->kids[1]->op) == INDIR && isaddrop(p->kids[1]->kids[0]->op)) && tmp->sclass == AUTO) || (generic(p->kids[1]->op) == ADDRG && tmp->sclass == AUTO)) { tmp->u.t.replace = 1; count++; continue; /* and omit the assignment */ } } /* keep the assignment and other roots */ *tail = p; tail = &(*tail)->link; } assert(*tail == NULL); return forest; } static Node visit(Node p, int listed) { if (p) if (p->syms[2]) p = tmpnode(p); else if (p->count <= 1 && !iscall(p->op) || p->count == 0 && iscall(p->op)) { p->kids[0] = visit(p->kids[0], 0); p->kids[1] = visit(p->kids[1], 0); } else if (specific(p->op) == ADDRL+P || specific(p->op) == ADDRF+P) { assert(!listed); p = newnode(p->op, NULL, NULL, p->syms[0]); p->count = 1; } else if (p->op == INDIR+B) { p = newnode(p->op, p->kids[0], NULL, NULL); p->count = 1; p->kids[0] = visit(p->kids[0], 0); p->kids[1] = visit(p->kids[1], 0); } else { p->kids[0] = visit(p->kids[0], 0); p->kids[1] = visit(p->kids[1], 0); p->syms[2] = temporary(REGISTER, btot(p->op, opsize(p->op))); assert(!p->syms[2]->defined); p->syms[2]->ref = 1; p->syms[2]->u.t.cse = p; *tail = asgnnode(p->syms[2], p); tail = &(*tail)->link; if (!listed) p = tmpnode(p); }; return p; } static Node tmpnode(Node p) { Symbol tmp = p->syms[2]; assert(tmp); if (--p->count == 0) p->syms[2] = NULL; p = newnode(INDIR + ttob(tmp->type), newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), NULL, NULL); p->count = 1; return p; } static Node asgnnode(Symbol tmp, Node p) { p = newnode(ASGN + ttob(tmp->type), newnode(ADDRL + ttob(voidptype), NULL, NULL, tmp), p, NULL); p->syms[0] = intconst(tmp->type->size); p->syms[1] = intconst(tmp->type->align); return p; } /* printdag - print dag p on fd, or the node list if p == 0 */ void printdag(Node p, int fd) { FILE *f = fd == 1 ? stdout : stderr; printed(0); if (p == 0) { if ((p = forest) != NULL) do { p = p->link; printdag1(p, fd, 0); } while (p != forest); } else if (*printed(nodeid((Tree)p))) fprint(f, "node'%d printed above\n", nodeid((Tree)p)); else printdag1(p, fd, 0); } /* printdag1 - recursively print dag p */ static void printdag1(Node p, int fd, int lev) { int id, i; if (p == 0 || *printed(id = nodeid((Tree)p))) return; *printed(id) = 1; for (i = 0; i < NELEMS(p->kids); i++) printdag1(p->kids[i], fd, lev + 1); printnode(p, fd, lev); } /* printnode - print fields of dag p */ static void printnode(Node p, int fd, int lev) { if (p) { FILE *f = fd == 1 ? stdout : stderr; int i, id = nodeid((Tree)p); fprint(f, "%c%d%s", lev == 0 ? '\'' : '#', id, &" "[id < 10 ? 0 : id < 100 ? 1 : 2]); fprint(f, "%s count=%d", opname(p->op), p->count); for (i = 0; i < NELEMS(p->kids) && p->kids[i]; i++) fprint(f, " #%d", nodeid((Tree)p->kids[i])); if (generic(p->op) == CALL && p->syms[0] && p->syms[0]->type) fprint(f, " {%t}", p->syms[0]->type); else for (i = 0; i < NELEMS(p->syms) && p->syms[i]; i++) if (p->syms[i]->name) fprint(f, " %s", p->syms[i]->name); else fprint(f, " %p", p->syms[i]); fprint(f, "\n"); } } /* typestab - emit stab entries for p */ static void typestab(Symbol p, void *cl) { if (!isfunc(p->type) && (p->sclass == EXTERN || p->sclass == STATIC) && IR->stabsym) (*IR->stabsym)(p); else if ((p->sclass == TYPEDEF || p->sclass == 0) && IR->stabtype) (*IR->stabtype)(p); }
Go to most recent revision | Compare with Previous | Blame | View Log