From: Dmitriy Vyukov Date: Tue, 2 Oct 2012 06:05:46 +0000 (+0400) Subject: race: gc changes X-Git-Tag: go1.1rc2~2275 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=041fc8bf96993d7405d938c7f4ad0b6ec474a91a;p=gostls13.git race: gc changes This is the first part of a bigger change that adds data race detection feature: https://golang.org/cl/6456044 This change makes gc compiler instrument memory accesses when supplied with -b flag. R=rsc, nigeltao, lvd CC=golang-dev https://golang.org/cl/6497074 --- diff --git a/src/cmd/gc/builtin.c b/src/cmd/gc/builtin.c index 6354fa22c8..1313b3d162 100644 --- a/src/cmd/gc/builtin.c +++ b/src/cmd/gc/builtin.c @@ -104,6 +104,10 @@ char *runtimeimport = "func @\"\".int64tofloat64(? int64) (? float64)\n" "func @\"\".uint64tofloat64(? uint64) (? float64)\n" "func @\"\".complex128div(@\"\".num complex128, @\"\".den complex128) (@\"\".quo complex128)\n" + "func @\"\".racefuncenter()\n" + "func @\"\".racefuncexit()\n" + "func @\"\".raceread(? uintptr)\n" + "func @\"\".racewrite(? uintptr)\n" "\n" "$$\n"; char *unsafeimport = diff --git a/src/cmd/gc/doc.go b/src/cmd/gc/doc.go index 8d8f8967b7..fad1427339 100644 --- a/src/cmd/gc/doc.go +++ b/src/cmd/gc/doc.go @@ -54,6 +54,8 @@ Flags: disallow importing packages not marked as safe -V print the compiler version + -b + compile with race detection enabled There are also a number of debugging flags; run the command with no arguments to get a usage message. diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h index 326ede3063..31fae5b19b 100644 --- a/src/cmd/gc/go.h +++ b/src/cmd/gc/go.h @@ -838,6 +838,7 @@ EXTERN Pkg* builtinpkg; // fake package for builtins EXTERN Pkg* gostringpkg; // fake pkg for Go strings EXTERN Pkg* itabpkg; // fake pkg for itab cache EXTERN Pkg* runtimepkg; // package runtime +EXTERN Pkg* racepkg; // package runtime/race EXTERN Pkg* stringpkg; // fake package for C strings EXTERN Pkg* typepkg; // fake package for runtime type info EXTERN Pkg* weaktypepkg; // weak references to runtime type info @@ -1442,3 +1443,8 @@ void zname(Biobuf *b, Sym *s, int t); #pragma varargck type "V" Val* #pragma varargck type "Y" char* #pragma varargck type "Z" Strlit* + +/* + * racewalk.c + */ +void racewalk(Node *fn); diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c index 3ef3fcc746..6bf00161b7 100644 --- a/src/cmd/gc/lex.c +++ b/src/cmd/gc/lex.c @@ -264,7 +264,12 @@ main(int argc, char *argv[]) print("%cg version %s%s%s\n", thechar, getgoversion(), *p ? " " : "", p); exits(0); } ARGEND - + + if(debug['b']) { + racepkg = mkpkg(strlit("runtime/race")); + racepkg->name = "race"; + } + // enable inlining. for now: // default: inlining on. (debug['l'] == 1) // -l: inlining off (debug['l'] == 0) @@ -530,7 +535,7 @@ static int findpkg(Strlit *name) { Idir *p; - char *q; + char *q, *race; if(islocalname(name)) { if(safemode) @@ -568,10 +573,13 @@ findpkg(Strlit *name) return 1; } if(goroot != nil) { - snprint(namebuf, sizeof(namebuf), "%s/pkg/%s_%s/%Z.a", goroot, goos, goarch, name); + race = ""; + if(debug['b']) + race = "_race"; + snprint(namebuf, sizeof(namebuf), "%s/pkg/%s_%s%s/%Z.a", goroot, goos, goarch, race, name); if(access(namebuf, 0) >= 0) return 1; - snprint(namebuf, sizeof(namebuf), "%s/pkg/%s_%s/%Z.%c", goroot, goos, goarch, name, thechar); + snprint(namebuf, sizeof(namebuf), "%s/pkg/%s_%s%s/%Z.%c", goroot, goos, goarch, race, name, thechar); if(access(namebuf, 0) >= 0) return 1; } diff --git a/src/cmd/gc/pgen.c b/src/cmd/gc/pgen.c index f2b75d61b6..46b763bf09 100644 --- a/src/cmd/gc/pgen.c +++ b/src/cmd/gc/pgen.c @@ -63,6 +63,10 @@ compile(Node *fn) walk(curfn); if(nerrors != 0) goto ret; + if(debug['b']) + racewalk(curfn); + if(nerrors != 0) + goto ret; continpc = P; breakpc = P; diff --git a/src/cmd/gc/racewalk.c b/src/cmd/gc/racewalk.c new file mode 100644 index 0000000000..6f8597a161 --- /dev/null +++ b/src/cmd/gc/racewalk.c @@ -0,0 +1,431 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The racewalk pass modifies the code tree for the function as follows: +// +// 1. It inserts a call to racefuncenter at the beginning of each function. +// 2. It inserts a call to racefuncexit at the end of each function. +// 3. It inserts a call to raceread before each memory read. +// 4. It inserts a call to racewrite before each memory write. +// +// The rewriting is not yet complete. Certain nodes are not rewritten +// but should be. + +#include +#include +#include "go.h" +#include "opnames.h" + +//TODO: do not instrument initialization as writes: +// a := make([]int, 10) + +static void racewalklist(NodeList *l, NodeList **init); +static void racewalknode(Node **np, NodeList **init, int wr, int skip); +static void callinstr(Node *n, NodeList **init, int wr, int skip); +static Node* uintptraddr(Node *n); +static Node* basenod(Node *n); + +static const char *omitPkgs[] = {"runtime", "runtime/race", "sync", "sync/atomic"}; + +void +racewalk(Node *fn) +{ + int i; + Node *nd; + char s[1024]; + + if(myimportpath) { + for(i=0; ienter = list(fn->enter, nd); + nd = mkcall("racefuncexit", T, nil); + fn->exit = list(fn->exit, nd); // works fine if (!fn->exit) + racewalklist(curfn->nbody, nil); + + if(debug['W']) { + snprint(s, sizeof(s), "after racewalk %S", curfn->nname->sym); + dumplist(s, curfn->nbody); + } +} + +static void +racewalklist(NodeList *l, NodeList **init) +{ + NodeList *instr; + + for(; l; l = l->next) { + instr = nil; + racewalknode(&l->n, &instr, 0, 0); + if(init == nil) + l->n->ninit = concat(l->n->ninit, instr); + else + *init = concat(*init, instr); + } +} + +// walkexpr and walkstmt combined +// walks the tree and adds calls to the +// instrumentation code to top-level (statement) nodes' init +static void +racewalknode(Node **np, NodeList **init, int wr, int skip) +{ + Node *n, *n1; + + n = *np; + + if(n == N) + return; + if(0) + print("op=%s, left=[ %N ], right=[ %N ], right's type=%T, n's type=%T, n's class=%d\n", + opnames[n->op], n->left, n->right, n->right ? n->right->type : nil, n->type, n->class); + setlineno(n); + + switch(n->op) { + default: + fatal("racewalk: unknown node type %O", n->op); + + case OASOP: + case OAS: + case OAS2: + case OAS2DOTTYPE: + case OAS2RECV: + case OAS2FUNC: + case OAS2MAPR: + racewalklist(n->ninit, init); + racewalknode(&n->left, init, 1, 0); + racewalknode(&n->right, init, 0, 0); + goto ret; + + case OBLOCK: + // leads to crashes. + //racewalklist(n->list, nil); + goto ret; + + case ODEFER: + racewalknode(&n->left, init, 0, 0); + goto ret; + + case OFOR: + racewalklist(n->ninit, nil); + if(n->ntest != N) + racewalklist(n->ntest->ninit, nil); + racewalknode(&n->nincr, init, wr, 0); + racewalklist(n->nbody, nil); + goto ret; + + case OIF: + racewalklist(n->ninit, nil); + racewalknode(&n->ntest, &n->ninit, wr, 0); + racewalklist(n->nbody, nil); + racewalklist(n->nelse, nil); + goto ret; + + case OPROC: + racewalknode(&n->left, init, 0, 0); + goto ret; + + case OCALLINTER: + racewalknode(&n->left, init, 0, 0); + racewalklist(n->list, init); + goto ret; + + case OCALLFUNC: + racewalknode(&n->left, init, 0, 0); + racewalklist(n->ninit, init); + racewalklist(n->list, init); + goto ret; + + case OCALLMETH: + racewalklist(n->list, init); + goto ret; + + case ORETURN: + racewalklist(n->list, nil); + goto ret; + + case OSELECT: + // n->nlist is nil by now because this code + // is running after walkselect + racewalklist(n->nbody, nil); + goto ret; + + case OSWITCH: + racewalklist(n->ninit, nil); + if(n->ntest->op == OTYPESW) + // don't bother, we have static typization + return; + racewalknode(&n->ntest, &n->ninit, 0, 0); + racewalklist(n->nbody, nil); + goto ret; + + case OEMPTY: + racewalklist(n->ninit, nil); + goto ret; + + case ONOT: + case OMINUS: + case OPLUS: + case OREAL: + case OIMAG: + racewalknode(&n->left, init, wr, 0); + goto ret; + + case ODOTINTER: + racewalknode(&n->left, init, 0, 0); + goto ret; + + case ODOT: + callinstr(n, init, wr, skip); + racewalknode(&n->left, init, 0, 1); + goto ret; + + case ODOTPTR: // dst = (*x).f with implicit *; otherwise it's ODOT+OIND + callinstr(n, init, wr, skip); + racewalknode(&n->left, init, 0, 0); + goto ret; + + case OIND: // *p + callinstr(n, init, wr, skip); + racewalknode(&n->left, init, 0, 0); + goto ret; + + case OLEN: + case OCAP: + racewalknode(&n->left, init, 0, 0); + if(istype(n->left->type, TMAP)) { + // crashes on len(m[0]) or len(f()) + USED(&n1); + /* + n1 = nod(OADDR, n->left, N); + n1 = conv(n1, types[TUNSAFEPTR]); + n1 = conv(n1, ptrto(ptrto(types[TINT8]))); + n1 = nod(OIND, n1, N); + n1 = nod(OIND, n1, N); + typecheck(&n1, Erv); + callinstr(n1, init, 0, skip); + */ + } + goto ret; + + case OLSH: + case ORSH: + case OAND: + case OANDNOT: + case OOR: + case OXOR: + case OSUB: + case OMUL: + case OEQ: + case ONE: + case OLT: + case OLE: + case OGE: + case OGT: + case OADD: + case OCOMPLEX: + racewalknode(&n->left, init, wr, 0); + racewalknode(&n->right, init, wr, 0); + goto ret; + + case OANDAND: + case OOROR: + racewalknode(&n->left, init, wr, 0); + // It requires more complex tree transformation, + // because we don't know whether it will be executed or not. + //racewalknode(&n->right, init, wr, 0); + goto ret; + + case ONAME: + callinstr(n, init, wr, skip); + goto ret; + + case OCONV: + racewalknode(&n->left, init, wr, 0); + goto ret; + + case OCONVNOP: + racewalknode(&n->left, init, wr, 0); + goto ret; + + case ODIV: + case OMOD: + // TODO(dvyukov): add a test for this + racewalknode(&n->left, init, wr, 0); + racewalknode(&n->right, init, wr, 0); + goto ret; + + case OINDEX: + if(n->left->type->etype != TSTRING) + callinstr(n, init, wr, skip); + if(!isfixedarray(n->left->type)) + racewalknode(&n->left, init, 0, 0); + racewalknode(&n->right, init, 0, 0); + goto ret; + + case OSLICE: + case OSLICEARR: + // Seems to only lead to double instrumentation. + //racewalklist(n->ninit, init); + //racewalknode(&n->left, init, 0, 0); + //racewalklist(n->list, init); + goto ret; + + case OADDR: + racewalknode(&n->left, init, 0, 1); + goto ret; + + // should not appear in AST by now + case OSEND: + case ORECV: + case OCLOSE: + case ONEW: + case OXCASE: + case OXFALL: + case OCASE: + case OPANIC: + case ORECOVER: + yyerror("racewalk: %O must be lowered by now", n->op); + goto ret; + + // does not require instrumentation + case OINDEXMAP: // implemented in runtime + case OPRINT: // don't bother instrumenting it + case OPRINTN: // don't bother instrumenting it + goto ret; + + // unimplemented + case OCMPSTR: + case OADDSTR: + case OSLICESTR: + case OAPPEND: + case OCOPY: + case OMAKECHAN: + case OMAKEMAP: + case OMAKESLICE: + case ORUNESTR: + case OARRAYBYTESTR: + case OARRAYRUNESTR: + case OSTRARRAYBYTE: + case OSTRARRAYRUNE: + case OCMPIFACE: + case OARRAYLIT: + case OMAPLIT: + case OSTRUCTLIT: + case OCLOSURE: + case ODOTTYPE: + case ODOTTYPE2: + case OCONVIFACE: + case OCALL: + case OBREAK: + case ODCL: + case OCONTINUE: + case OFALL: + case OGOTO: + case OLABEL: + case ODCLCONST: + case ODCLTYPE: + case OLITERAL: + case ORANGE: + case OTYPE: + case ONONAME: + case OINDREG: + case OCOM: + case ODOTMETH: + case OEFACE: + case OITAB: + case OEXTEND: + case OHMUL: + case OLROT: + case ORROTC: + goto ret; + } + +ret: + *np = n; +} + +static void +callinstr(Node *n, NodeList **init, int wr, int skip) +{ + Node *f, *b; + Type *t, *t1; + int class; + + //print("callinstr for %N [ %s ] etype=%d class=%d\n", + // n, opnames[n->op], n->type ? n->type->etype : -1, n->class); + + if(skip || n->type == T || n->type->etype >= TIDEAL) + return; + t = n->type; + if(n->op == ONAME) { + if(n->sym != S) { + if(n->sym->name != nil) { + if(strncmp(n->sym->name, "_", sizeof("_")-1) == 0) + return; + if(strncmp(n->sym->name, "autotmp_", sizeof("autotmp_")-1) == 0) + return; + if(strncmp(n->sym->name, "statictmp_", sizeof("statictmp_")-1) == 0) + return; + } + } + } + if (t->etype == TSTRUCT) { + for(t1=t->type; t1; t1=t1->down) { + if(t1->sym && strncmp(t1->sym->name, "_", sizeof("_")-1)) { + n = treecopy(n); + f = nod(OXDOT, n, newname(t1->sym)); + typecheck(&f, Erv); + callinstr(f, init, wr, 0); + } + } + return; + } + + b = basenod(n); + class = b->class; + // BUG: we _may_ want to instrument PAUTO sometimes + // e.g. if we've got a local variable/method receiver + // that has got a pointer inside. Whether it points to + // the heap or not is impossible to know at compile time + if((class&PHEAP) || class == PPARAMREF || class == PEXTERN + || b->type->etype == TARRAY || b->op == ODOTPTR || b->op == OIND || b->op == OXDOT) { + n = treecopy(n); + f = mkcall(wr ? "racewrite" : "raceread", T, nil, uintptraddr(n)); + //typecheck(&f, Etop); + *init = list(*init, f); + } +} + +static Node* +uintptraddr(Node *n) +{ + Node *r; + + r = nod(OADDR, n, N); + r = conv(r, types[TUNSAFEPTR]); + r = conv(r, types[TUINTPTR]); + return r; +} + +static Node* +basenod(Node *n) +{ + for(;;) { + if(n->op == ODOT || n->op == OPAREN) { + n = n->left; + continue; + } + if(n->op == OINDEX) { + n = n->left; + continue; + } + break; + } + return n; +} diff --git a/src/cmd/gc/reflect.c b/src/cmd/gc/reflect.c index 52f77b86f7..ad1d8d8606 100644 --- a/src/cmd/gc/reflect.c +++ b/src/cmd/gc/reflect.c @@ -934,6 +934,8 @@ dumptypestructs(void) // add paths for runtime and main, which 6l imports implicitly. dimportpath(runtimepkg); + if(debug['b']) + dimportpath(racepkg); dimportpath(mkpkg(strlit("main"))); } } diff --git a/src/cmd/gc/runtime.go b/src/cmd/gc/runtime.go index 28c6b44bc6..cfabbb174b 100644 --- a/src/cmd/gc/runtime.go +++ b/src/cmd/gc/runtime.go @@ -138,3 +138,9 @@ func int64tofloat64(int64) float64 func uint64tofloat64(uint64) float64 func complex128div(num complex128, den complex128) (quo complex128) + +// race detection +func racefuncenter() +func racefuncexit() +func raceread(uintptr) +func racewrite(uintptr)