root/src/cmd/gc/racewalk.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ispkgin
  2. isforkfunc
  3. racewalk
  4. racewalklist
  5. racewalknode
  6. isartificial
  7. callinstr
  8. makeaddable
  9. uintptraddr
  10. basenod
  11. detachexpr
  12. foreachnode
  13. foreachlist
  14. foreach
  15. hascallspred
  16. appendinit

// 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 <u.h>
#include <libc.h>
#include "go.h"

// TODO(dvyukov): 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 int callinstr(Node **n, NodeList **init, int wr, int skip);
static Node* uintptraddr(Node *n);
static void makeaddable(Node *n);
static Node* basenod(Node *n);
static void foreach(Node *n, void(*f)(Node*, void*), void *c);
static void hascallspred(Node *n, void *c);
static void appendinit(Node **np, NodeList *init);
static Node* detachexpr(Node *n, NodeList **init);

// Do not instrument the following packages at all,
// at best instrumentation would cause infinite recursion.
static const char *omit_pkgs[] = {"runtime", "runtime/race"};
// Only insert racefuncenter/racefuncexit into the following packages.
// Memory accesses in the packages are either uninteresting or will cause false positives.
static const char *noinst_pkgs[] = {"sync", "sync/atomic"};

static int
ispkgin(const char **pkgs, int n)
{
        int i;

        if(myimportpath) {
                for(i=0; i<n; i++) {
                        if(strcmp(myimportpath, pkgs[i]) == 0)
                                return 1;
                }
        }
        return 0;
}

static int
isforkfunc(Node *fn)
{
        // Special case for syscall.forkAndExecInChild.
        // In the child, this function must not acquire any locks, because
        // they might have been locked at the time of the fork.  This means
        // no rescheduling, no malloc calls, and no new stack segments.
        // Race instrumentation does all of the above.
        return myimportpath != nil && strcmp(myimportpath, "syscall") == 0 &&
                strcmp(fn->nname->sym->name, "forkAndExecInChild") == 0;
}

void
racewalk(Node *fn)
{
        Node *nd;
        Node *nodpc;
        char s[1024];

        if(ispkgin(omit_pkgs, nelem(omit_pkgs)) || isforkfunc(fn))
                return;

        if(!ispkgin(noinst_pkgs, nelem(noinst_pkgs))) {
                racewalklist(fn->nbody, nil);
                // nothing interesting for race detector in fn->enter
                racewalklist(fn->exit, nil);
        }

        // nodpc is the PC of the caller as extracted by
        // getcallerpc. We use -widthptr(FP) for x86.
        // BUG: this will not work on arm.
        nodpc = nod(OXXX, nil, nil);
        *nodpc = *nodfp;
        nodpc->type = types[TUINTPTR];
        nodpc->xoffset = -widthptr;
        nd = mkcall("racefuncenter", T, nil, nodpc);
        fn->enter = concat(list1(nd), fn->enter);
        nd = mkcall("racefuncexit", T, nil);
        fn->exit = list(fn->exit, nd);

        if(debug['W']) {
                snprint(s, sizeof(s), "after racewalk %S", fn->nname->sym);
                dumplist(s, fn->nbody);
                snprint(s, sizeof(s), "enter %S", fn->nname->sym);
                dumplist(s, fn->enter);
                snprint(s, sizeof(s), "exit %S", fn->nname->sym);
                dumplist(s, fn->exit);
        }
}

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;
        NodeList *l;
        NodeList *fini;

        n = *np;

        if(n == N)
                return;

        if(debug['w'] > 1)
                dump("racewalk-before", n);
        setlineno(n);
        if(init == nil)
                fatal("racewalk: bad init list");
        if(init == &n->ninit) {
                // If init == &n->ninit and n->ninit is non-nil,
                // racewalknode might append it to itself.
                // nil it out and handle it separately before putting it back.
                l = n->ninit;
                n->ninit = nil;
                racewalklist(l, nil);
                racewalknode(&n, &l, wr, skip);  // recurse with nil n->ninit
                appendinit(&n, l);
                *np = n;
                return;
        }

        racewalklist(n->ninit, nil);

        switch(n->op) {
        default:
                fatal("racewalk: unknown node type %O", n->op);

        case OASOP:
        case OAS:
        case OAS2:
        case OAS2RECV:
        case OAS2FUNC:
        case OAS2MAPR:
                racewalknode(&n->left, init, 1, 0);
                racewalknode(&n->right, init, 0, 0);
                goto ret;

        case OCFUNC:
        case OVARKILL:
                // can't matter
                goto ret;

        case OBLOCK:
                if(n->list == nil)
                        goto ret;

                switch(n->list->n->op) {
                case OCALLFUNC:
                case OCALLMETH:
                case OCALLINTER:
                        // Blocks are used for multiple return function calls.
                        // x, y := f() becomes BLOCK{CALL f, AS x [SP+0], AS y [SP+n]}
                        // We don't want to instrument between the statements because it will
                        // smash the results.
                        racewalknode(&n->list->n, &n->list->n->ninit, 0, 0);
                        fini = nil;
                        racewalklist(n->list->next, &fini);
                        n->list = concat(n->list, fini);
                        break;

                default:
                        // Ordinary block, for loop initialization or inlined bodies.
                        racewalklist(n->list, nil);
                        break;
                }
                goto ret;

        case ODEFER:
                racewalknode(&n->left, init, 0, 0);
                goto ret;

        case OPROC:
                racewalknode(&n->left, init, 0, 0);
                goto ret;

        case OCALLINTER:
                racewalknode(&n->left, init, 0, 0);
                goto ret;

        case OCALLFUNC:
                racewalknode(&n->left, init, 0, 0);
                goto ret;

        case ONOT:
        case OMINUS:
        case OPLUS:
        case OREAL:
        case OIMAG:
        case OCOM:
                racewalknode(&n->left, init, wr, 0);
                goto ret;

        case ODOTINTER:
                racewalknode(&n->left, init, 0, 0);
                goto ret;

        case ODOT:
                racewalknode(&n->left, init, 0, 1);
                callinstr(&n, init, wr, skip);
                goto ret;

        case ODOTPTR: // dst = (*x).f with implicit *; otherwise it's ODOT+OIND
                racewalknode(&n->left, init, 0, 0);
                callinstr(&n, init, wr, skip);
                goto ret;

        case OIND: // *p
                racewalknode(&n->left, init, 0, 0);
                callinstr(&n, init, wr, skip);
                goto ret;

        case OSPTR:
        case OLEN:
        case OCAP:
                racewalknode(&n->left, init, 0, 0);
                if(istype(n->left->type, TMAP)) {
                        n1 = nod(OCONVNOP, n->left, N);
                        n1->type = ptrto(types[TUINT8]);
                        n1 = nod(OIND, n1, N);
                        typecheck(&n1, Erv);
                        callinstr(&n1, init, 0, skip);
                }
                goto ret;

        case OLSH:
        case ORSH:
        case OLROT:
        case OAND:
        case OANDNOT:
        case OOR:
        case OXOR:
        case OSUB:
        case OMUL:
        case OHMUL:
        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);
                // walk has ensured the node has moved to a location where
                // side effects are safe.
                // n->right may not be executed,
                // so instrumentation goes to n->right->ninit, not init.
                racewalknode(&n->right, &n->right->ninit, 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:
                racewalknode(&n->left, init, wr, 0);
                racewalknode(&n->right, init, wr, 0);
                goto ret;

        case OINDEX:
                if(!isfixedarray(n->left->type))
                        racewalknode(&n->left, init, 0, 0);
                else if(!islvalue(n->left)) {
                        // index of unaddressable array, like Map[k][i].
                        racewalknode(&n->left, init, wr, 0);
                        racewalknode(&n->right, init, 0, 0);
                        goto ret;
                }
                racewalknode(&n->right, init, 0, 0);
                if(n->left->type->etype != TSTRING)
                        callinstr(&n, init, wr, skip);
                goto ret;

        case OSLICE:
        case OSLICEARR:
        case OSLICE3:
        case OSLICE3ARR:
                // Seems to only lead to double instrumentation.
                //racewalknode(&n->left, init, 0, 0);
                goto ret;

        case OADDR:
                racewalknode(&n->left, init, 0, 1);
                goto ret;

        case OEFACE:
                racewalknode(&n->left, init, 0, 0);
                racewalknode(&n->right, init, 0, 0);
                goto ret;

        case OITAB:
                racewalknode(&n->left, init, 0, 0);
                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:
        case OCONVIFACE:
        case OCMPIFACE:
        case OMAKECHAN:
        case OMAKEMAP:
        case OMAKESLICE:
        case OCALL:
        case OCOPY:
        case OAPPEND:
        case ORUNESTR:
        case OARRAYBYTESTR:
        case OARRAYRUNESTR:
        case OSTRARRAYBYTE:
        case OSTRARRAYRUNE:
        case OINDEXMAP:  // lowered to call
        case OCMPSTR:
        case OADDSTR:
        case ODOTTYPE:
        case ODOTTYPE2:
        case OAS2DOTTYPE:
        case OCALLPART: // lowered to PTRLIT
        case OCLOSURE:  // lowered to PTRLIT
        case ORANGE:    // lowered to ordinary for loop
        case OARRAYLIT: // lowered to assignments
        case OMAPLIT:
        case OSTRUCTLIT:
                yyerror("racewalk: %O must be lowered by now", n->op);
                goto ret;

        // impossible nodes: only appear in backend.
        case ORROTC:
        case OEXTEND:
                yyerror("racewalk: %O cannot exist now", n->op);
                goto ret;

        // just do generic traversal
        case OFOR:
        case OIF:
        case OCALLMETH:
        case ORETURN:
        case ORETJMP:
        case OSWITCH:
        case OSELECT:
        case OEMPTY:
        case OBREAK:
        case OCONTINUE:
        case OFALL:
        case OGOTO:
        case OLABEL:
                goto ret;

        // does not require instrumentation
        case OPRINT:     // don't bother instrumenting it
        case OPRINTN:    // don't bother instrumenting it
        case OCHECKNIL: // always followed by a read.
        case OPARAM:     // it appears only in fn->exit to copy heap params back
        case OCLOSUREVAR:// immutable pointer to captured variable
        case ODOTMETH:   // either part of CALLMETH or CALLPART (lowered to PTRLIT)
        case OINDREG:    // at this stage, only n(SP) nodes from nodarg
        case ODCL:       // declarations (without value) cannot be races
        case ODCLCONST:
        case ODCLTYPE:
        case OTYPE:
        case ONONAME:
        case OLITERAL:
        case OSLICESTR:  // always preceded by bounds checking, avoid double instrumentation.
        case OTYPESW:    // ignored by code generation, do not instrument.
                goto ret;
        }

ret:
        if(n->op != OBLOCK)  // OBLOCK is handled above in a special way.
                racewalklist(n->list, init);
        racewalknode(&n->ntest, &n->ntest->ninit, 0, 0);
        racewalknode(&n->nincr, &n->nincr->ninit, 0, 0);
        racewalklist(n->nbody, nil);
        racewalklist(n->nelse, nil);
        racewalklist(n->rlist, nil);
        *np = n;
}

static int
isartificial(Node *n)
{
        // compiler-emitted artificial things that we do not want to instrument,
        // cant' possibly participate in a data race.
        if(n->op == ONAME && n->sym != S && n->sym->name != nil) {
                if(strcmp(n->sym->name, "_") == 0)
                        return 1;
                // autotmp's are always local
                if(strncmp(n->sym->name, "autotmp_", sizeof("autotmp_")-1) == 0)
                        return 1;
                // statictmp's are read-only
                if(strncmp(n->sym->name, "statictmp_", sizeof("statictmp_")-1) == 0)
                        return 1;
                // go.itab is accessed only by the compiler and runtime (assume safe)
                if(n->sym->pkg && n->sym->pkg->name && strcmp(n->sym->pkg->name, "go.itab") == 0)
                        return 1;
        }
        return 0;
}

static int
callinstr(Node **np, NodeList **init, int wr, int skip)
{
        Node *f, *b, *n;
        Type *t;
        int class, hascalls;

        n = *np;
        //print("callinstr for %+N [ %O ] etype=%E class=%d\n",
        //        n, n->op, n->type ? n->type->etype : -1, n->class);

        if(skip || n->type == T || n->type->etype >= TIDEAL)
                return 0;
        t = n->type;
        if(isartificial(n))
                return 0;

        b = basenod(n);
        // it skips e.g. stores to ... parameter array
        if(isartificial(b))
                return 0;
        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->op == OINDEX || b->op == ODOTPTR || b->op == OIND || b->op == OXDOT) {
                hascalls = 0;
                foreach(n, hascallspred, &hascalls);
                if(hascalls) {
                        n = detachexpr(n, init);
                        *np = n;
                }
                n = treecopy(n);
                makeaddable(n);
                if(t->etype == TSTRUCT || isfixedarray(t)) {
                        f = mkcall(wr ? "racewriterange" : "racereadrange", T, init, uintptraddr(n),
                                        nodintconst(t->width));
                } else
                        f = mkcall(wr ? "racewrite" : "raceread", T, init, uintptraddr(n));
                *init = list(*init, f);
                return 1;
        }
        return 0;
}

// makeaddable returns a node whose memory location is the
// same as n, but which is addressable in the Go language
// sense.
// This is different from functions like cheapexpr that may make
// a copy of their argument.
static void
makeaddable(Node *n)
{
        // The arguments to uintptraddr technically have an address but
        // may not be addressable in the Go sense: for example, in the case
        // of T(v).Field where T is a struct type and v is
        // an addressable value.
        switch(n->op) {
        case OINDEX:
                if(isfixedarray(n->left->type))
                        makeaddable(n->left);
                break;
        case ODOT:
        case OXDOT:
                // Turn T(v).Field into v.Field
                if(n->left->op == OCONVNOP)
                        n->left = n->left->left;
                makeaddable(n->left);
                break;
        case ODOTPTR:
        default:
                // nothing to do
                break;
        }
}

static Node*
uintptraddr(Node *n)
{
        Node *r;

        r = nod(OADDR, n, N);
        r->bounded = 1;
        r = conv(r, types[TUNSAFEPTR]);
        r = conv(r, types[TUINTPTR]);
        return r;
}

// basenod returns the simplest child node of n pointing to the same
// memory area.
static Node*
basenod(Node *n)
{
        for(;;) {
                if(n->op == ODOT || n->op == OXDOT || n->op == OCONVNOP || n->op == OCONV || n->op == OPAREN) {
                        n = n->left;
                        continue;
                }
                if(n->op == OINDEX && isfixedarray(n->type)) {
                        n = n->left;
                        continue;
                }
                break;
        }
        return n;
}

static Node*
detachexpr(Node *n, NodeList **init)
{
        Node *addr, *as, *ind, *l;

        addr = nod(OADDR, n, N);
        l = temp(ptrto(n->type));
        as = nod(OAS, l, addr);
        typecheck(&as, Etop);
        walkexpr(&as, init);
        *init = list(*init, as);
        ind = nod(OIND, l, N);
        typecheck(&ind, Erv);
        walkexpr(&ind, init);
        return ind;
}

static void
foreachnode(Node *n, void(*f)(Node*, void*), void *c)
{
        if(n)
                f(n, c);
}

static void
foreachlist(NodeList *l, void(*f)(Node*, void*), void *c)
{
        for(; l; l = l->next)
                foreachnode(l->n, f, c);
}

static void
foreach(Node *n, void(*f)(Node*, void*), void *c)
{
        foreachlist(n->ninit, f, c);
        foreachnode(n->left, f, c);
        foreachnode(n->right, f, c);
        foreachlist(n->list, f, c);
        foreachnode(n->ntest, f, c);
        foreachnode(n->nincr, f, c);
        foreachlist(n->nbody, f, c);
        foreachlist(n->nelse, f, c);
        foreachlist(n->rlist, f, c);
}

static void
hascallspred(Node *n, void *c)
{
        switch(n->op) {
        case OCALL:
        case OCALLFUNC:
        case OCALLMETH:
        case OCALLINTER:
                (*(int*)c)++;
        }
}

// appendinit is like addinit in subr.c
// but appends rather than prepends.
static void
appendinit(Node **np, NodeList *init)
{
        Node *n;

        if(init == nil)
                return;

        n = *np;
        switch(n->op) {
        case ONAME:
        case OLITERAL:
                // There may be multiple refs to this node;
                // introduce OCONVNOP to hold init list.
                n = nod(OCONVNOP, n, N);
                n->type = n->left->type;
                n->typecheck = 1;
                *np = n;
                break;
        }
        n->ninit = concat(n->ninit, init);
        n->ullman = UINF;
}


/* [<][>][^][v][top][bottom][index][help] */