root/src/cmd/gc/gen.c

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

DEFINITIONS

This source file includes following definitions.
  1. sysfunc
  2. addrescapes
  3. clearlabels
  4. newlab
  5. checklabels
  6. checkgoto
  7. stmtlabel
  8. genlist
  9. gen
  10. cgen_callmeth
  11. cgen_proc
  12. cgen_dcl
  13. cgen_discard
  14. clearslim
  15. cgen_as
  16. cgen_eface
  17. cgen_slice
  18. dotoffset
  19. tempname
  20. temp

// Copyright 2009 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.

/*
 * portable half of code generator.
 * mainly statements and control flow.
 */

#include <u.h>
#include <libc.h>
#include "go.h"

static void     cgen_dcl(Node *n);
static void     cgen_proc(Node *n, int proc);
static void     checkgoto(Node*, Node*);

static Label *labellist;
static Label *lastlabel;

Node*
sysfunc(char *name)
{
        Node *n;

        n = newname(pkglookup(name, runtimepkg));
        n->class = PFUNC;
        return n;
}

/*
 * the address of n has been taken and might be used after
 * the current function returns.  mark any local vars
 * as needing to move to the heap.
 */
void
addrescapes(Node *n)
{
        char buf[100];
        Node *oldfn;

        switch(n->op) {
        default:
                // probably a type error already.
                // dump("addrescapes", n);
                break;

        case ONAME:
                if(n == nodfp)
                        break;

                // if this is a tmpname (PAUTO), it was tagged by tmpname as not escaping.
                // on PPARAM it means something different.
                if(n->class == PAUTO && n->esc == EscNever)
                        break;

                if(debug['N'] && n->esc != EscUnknown)
                        fatal("without escape analysis, only PAUTO's should have esc: %N", n);

                switch(n->class) {
                case PPARAMREF:
                        addrescapes(n->defn);
                        break;
                case PPARAM:
                case PPARAMOUT:
                        // if func param, need separate temporary
                        // to hold heap pointer.
                        // the function type has already been checked
                        // (we're in the function body)
                        // so the param already has a valid xoffset.

                        // expression to refer to stack copy
                        n->stackparam = nod(OPARAM, n, N);
                        n->stackparam->type = n->type;
                        n->stackparam->addable = 1;
                        if(n->xoffset == BADWIDTH)
                                fatal("addrescapes before param assignment");
                        n->stackparam->xoffset = n->xoffset;
                        // fallthrough

                case PAUTO:
                        n->class |= PHEAP;
                        n->addable = 0;
                        n->ullman = 2;
                        n->xoffset = 0;

                        // create stack variable to hold pointer to heap
                        oldfn = curfn;
                        curfn = n->curfn;
                        n->heapaddr = temp(ptrto(n->type));
                        snprint(buf, sizeof buf, "&%S", n->sym);
                        n->heapaddr->sym = lookup(buf);
                        n->heapaddr->orig->sym = n->heapaddr->sym;
                        if(!debug['N'])
                                n->esc = EscHeap;
                        if(debug['m'])
                                print("%L: moved to heap: %N\n", n->lineno, n);
                        curfn = oldfn;
                        break;
                }
                break;

        case OIND:
        case ODOTPTR:
                break;

        case ODOT:
        case OINDEX:
                // ODOTPTR has already been introduced,
                // so these are the non-pointer ODOT and OINDEX.
                // In &x[0], if x is a slice, then x does not
                // escape--the pointer inside x does, but that
                // is always a heap pointer anyway.
                if(!isslice(n->left->type))
                        addrescapes(n->left);
                break;
        }
}

void
clearlabels(void)
{
        Label *l;

        for(l=labellist; l!=L; l=l->link)
                l->sym->label = L;
        
        labellist = L;
        lastlabel = L;
}

static Label*
newlab(Node *n)
{
        Sym *s;
        Label *lab;
        
        s = n->left->sym;
        if((lab = s->label) == L) {
                lab = mal(sizeof(*lab));
                if(lastlabel == nil)
                        labellist = lab;
                else
                        lastlabel->link = lab;
                lastlabel = lab;
                lab->sym = s;
                s->label = lab;
        }
        
        if(n->op == OLABEL) {
                if(lab->def != N)
                        yyerror("label %S already defined at %L", s, lab->def->lineno);
                else
                        lab->def = n;
        } else
                lab->use = list(lab->use, n);

        return lab;
}

void
checklabels(void)
{
        Label *lab;
        NodeList *l;

        for(lab=labellist; lab!=L; lab=lab->link) {
                if(lab->def == N) {
                        for(l=lab->use; l; l=l->next)
                                yyerrorl(l->n->lineno, "label %S not defined", lab->sym);
                        continue;
                }
                if(lab->use == nil && !lab->used) {
                        yyerrorl(lab->def->lineno, "label %S defined and not used", lab->sym);
                        continue;
                }
                if(lab->gotopc != P)
                        fatal("label %S never resolved", lab->sym);
                for(l=lab->use; l; l=l->next)
                        checkgoto(l->n, lab->def);
        }
}

static void
checkgoto(Node *from, Node *to)
{
        int nf, nt;
        Sym *block, *dcl, *fs, *ts;
        int lno;

        if(from->sym == to->sym)
                return;

        nf = 0;
        for(fs=from->sym; fs; fs=fs->link)
                nf++;
        nt = 0;
        for(fs=to->sym; fs; fs=fs->link)
                nt++;
        fs = from->sym;
        for(; nf > nt; nf--)
                fs = fs->link;
        if(fs != to->sym) {
                lno = lineno;
                setlineno(from);

                // decide what to complain about.
                // prefer to complain about 'into block' over declarations,
                // so scan backward to find most recent block or else dcl.
                block = S;
                dcl = S;
                ts = to->sym;
                for(; nt > nf; nt--) {
                        if(ts->pkg == nil)
                                block = ts;
                        else
                                dcl = ts;
                        ts = ts->link;
                }
                while(ts != fs) {
                        if(ts->pkg == nil)
                                block = ts;
                        else
                                dcl = ts;
                        ts = ts->link;
                        fs = fs->link;
                }

                if(block)
                        yyerror("goto %S jumps into block starting at %L", from->left->sym, block->lastlineno);
                else
                        yyerror("goto %S jumps over declaration of %S at %L", from->left->sym, dcl, dcl->lastlineno);
                lineno = lno;
        }
}

static Label*
stmtlabel(Node *n)
{
        Label *lab;

        if(n->sym != S)
        if((lab = n->sym->label) != L)
        if(lab->def != N)
        if(lab->def->defn == n)
                return lab;
        return L;
}

/*
 * compile statements
 */
void
genlist(NodeList *l)
{
        for(; l; l=l->next)
                gen(l->n);
}

void
gen(Node *n)
{
        int32 lno;
        Prog *scontin, *sbreak;
        Prog *p1, *p2, *p3;
        Label *lab;
        int32 wasregalloc;

//dump("gen", n);

        lno = setlineno(n);
        wasregalloc = anyregalloc();

        if(n == N)
                goto ret;

        if(n->ninit)
                genlist(n->ninit);

        setlineno(n);

        switch(n->op) {
        default:
                fatal("gen: unknown op %+hN", n);
                break;

        case OCASE:
        case OFALL:
        case OXCASE:
        case OXFALL:
        case ODCLCONST:
        case ODCLFUNC:
        case ODCLTYPE:
                break;

        case OEMPTY:
                break;

        case OBLOCK:
                genlist(n->list);
                break;

        case OLABEL:
                if(isblanksym(n->left->sym))
                        break;
                
                lab = newlab(n);

                // if there are pending gotos, resolve them all to the current pc.
                for(p1=lab->gotopc; p1; p1=p2) {
                        p2 = unpatch(p1);
                        patch(p1, pc);
                }
                lab->gotopc = P;
                if(lab->labelpc == P)
                        lab->labelpc = pc;

                if(n->defn) {
                        switch(n->defn->op) {
                        case OFOR:
                        case OSWITCH:
                        case OSELECT:
                                // so stmtlabel can find the label
                                n->defn->sym = lab->sym;
                        }
                }
                break;

        case OGOTO:
                // if label is defined, emit jump to it.
                // otherwise save list of pending gotos in lab->gotopc.
                // the list is linked through the normal jump target field
                // to avoid a second list.  (the jumps are actually still
                // valid code, since they're just going to another goto
                // to the same label.  we'll unwind it when we learn the pc
                // of the label in the OLABEL case above.)
                lab = newlab(n);
                if(lab->labelpc != P)
                        gjmp(lab->labelpc);
                else
                        lab->gotopc = gjmp(lab->gotopc);
                break;

        case OBREAK:
                if(n->left != N) {
                        lab = n->left->sym->label;
                        if(lab == L) {
                                yyerror("break label not defined: %S", n->left->sym);
                                break;
                        }
                        lab->used = 1;
                        if(lab->breakpc == P) {
                                yyerror("invalid break label %S", n->left->sym);
                                break;
                        }
                        gjmp(lab->breakpc);
                        break;
                }
                if(breakpc == P) {
                        yyerror("break is not in a loop");
                        break;
                }
                gjmp(breakpc);
                break;

        case OCONTINUE:
                if(n->left != N) {
                        lab = n->left->sym->label;
                        if(lab == L) {
                                yyerror("continue label not defined: %S", n->left->sym);
                                break;
                        }
                        lab->used = 1;
                        if(lab->continpc == P) {
                                yyerror("invalid continue label %S", n->left->sym);
                                break;
                        }
                        gjmp(lab->continpc);
                        break;
                }
                if(continpc == P) {
                        yyerror("continue is not in a loop");
                        break;
                }
                gjmp(continpc);
                break;

        case OFOR:
                sbreak = breakpc;
                p1 = gjmp(P);                   //              goto test
                breakpc = gjmp(P);              // break:       goto done
                scontin = continpc;
                continpc = pc;

                // define break and continue labels
                if((lab = stmtlabel(n)) != L) {
                        lab->breakpc = breakpc;
                        lab->continpc = continpc;
                }
                gen(n->nincr);                          // contin:      incr
                patch(p1, pc);                          // test:
                bgen(n->ntest, 0, -1, breakpc);         //              if(!test) goto break
                genlist(n->nbody);                              //              body
                gjmp(continpc);
                patch(breakpc, pc);                     // done:
                continpc = scontin;
                breakpc = sbreak;
                if(lab) {
                        lab->breakpc = P;
                        lab->continpc = P;
                }
                break;

        case OIF:
                p1 = gjmp(P);                   //              goto test
                p2 = gjmp(P);                   // p2:          goto else
                patch(p1, pc);                          // test:
                bgen(n->ntest, 0, -n->likely, p2);              //              if(!test) goto p2
                genlist(n->nbody);                              //              then
                p3 = gjmp(P);                   //              goto done
                patch(p2, pc);                          // else:
                genlist(n->nelse);                              //              else
                patch(p3, pc);                          // done:
                break;

        case OSWITCH:
                sbreak = breakpc;
                p1 = gjmp(P);                   //              goto test
                breakpc = gjmp(P);              // break:       goto done

                // define break label
                if((lab = stmtlabel(n)) != L)
                        lab->breakpc = breakpc;

                patch(p1, pc);                          // test:
                genlist(n->nbody);                              //              switch(test) body
                patch(breakpc, pc);                     // done:
                breakpc = sbreak;
                if(lab != L)
                        lab->breakpc = P;
                break;

        case OSELECT:
                sbreak = breakpc;
                p1 = gjmp(P);                   //              goto test
                breakpc = gjmp(P);              // break:       goto done

                // define break label
                if((lab = stmtlabel(n)) != L)
                        lab->breakpc = breakpc;

                patch(p1, pc);                          // test:
                genlist(n->nbody);                              //              select() body
                patch(breakpc, pc);                     // done:
                breakpc = sbreak;
                if(lab != L)
                        lab->breakpc = P;
                break;

        case OASOP:
                cgen_asop(n);
                break;

        case ODCL:
                cgen_dcl(n->left);
                break;

        case OAS:
                if(gen_as_init(n))
                        break;
                cgen_as(n->left, n->right);
                break;

        case OCALLMETH:
                cgen_callmeth(n, 0);
                break;

        case OCALLINTER:
                cgen_callinter(n, N, 0);
                break;

        case OCALLFUNC:
                cgen_call(n, 0);
                break;

        case OPROC:
                cgen_proc(n, 1);
                break;

        case ODEFER:
                cgen_proc(n, 2);
                break;

        case ORETURN:
        case ORETJMP:
                cgen_ret(n);
                break;
        
        case OCHECKNIL:
                cgen_checknil(n->left);
                break;
        
        case OVARKILL:
                gvarkill(n->left);
                break;
        }

ret:
        if(anyregalloc() != wasregalloc) {
                dump("node", n);
                fatal("registers left allocated");
        }

        lineno = lno;
}

/*
 * generate call to non-interface method
 *      proc=0  normal call
 *      proc=1  goroutine run in new proc
 *      proc=2  defer call save away stack
 */
void
cgen_callmeth(Node *n, int proc)
{
        Node n2;
        Node *l;

        // generate a rewrite in n2 for the method call
        // (p.f)(...) goes to (f)(p,...)

        l = n->left;
        if(l->op != ODOTMETH)
                fatal("cgen_callmeth: not dotmethod: %N");

        n2 = *n;
        n2.op = OCALLFUNC;
        n2.left = l->right;
        n2.left->type = l->type;

        if(n2.left->op == ONAME)
                n2.left->class = PFUNC;
        cgen_call(&n2, proc);
}

/*
 * generate code to start new proc running call n.
 */
static void
cgen_proc(Node *n, int proc)
{
        switch(n->left->op) {
        default:
                fatal("cgen_proc: unknown call %O", n->left->op);

        case OCALLMETH:
                cgen_callmeth(n->left, proc);
                break;

        case OCALLINTER:
                cgen_callinter(n->left, N, proc);
                break;

        case OCALLFUNC:
                cgen_call(n->left, proc);
                break;
        }

}

/*
 * generate declaration.
 * have to allocate heap copy
 * for escaped variables.
 */
static void
cgen_dcl(Node *n)
{
        if(debug['g'])
                dump("\ncgen-dcl", n);
        if(n->op != ONAME) {
                dump("cgen_dcl", n);
                fatal("cgen_dcl");
        }
        if(!(n->class & PHEAP))
                return;
        if(n->alloc == nil)
                n->alloc = callnew(n->type);
        cgen_as(n->heapaddr, n->alloc);
}

/*
 * generate discard of value
 */
static void
cgen_discard(Node *nr)
{
        Node tmp;

        if(nr == N)
                return;

        switch(nr->op) {
        case ONAME:
                if(!(nr->class & PHEAP) && nr->class != PEXTERN && nr->class != PFUNC && nr->class != PPARAMREF)
                        gused(nr);
                break;

        // unary
        case OADD:
        case OAND:
        case ODIV:
        case OEQ:
        case OGE:
        case OGT:
        case OLE:
        case OLSH:
        case OLT:
        case OMOD:
        case OMUL:
        case ONE:
        case OOR:
        case ORSH:
        case OSUB:
        case OXOR:
                cgen_discard(nr->left);
                cgen_discard(nr->right);
                break;

        // binary
        case OCAP:
        case OCOM:
        case OLEN:
        case OMINUS:
        case ONOT:
        case OPLUS:
                cgen_discard(nr->left);
                break;
        
        case OIND:
                cgen_checknil(nr->left);
                break;

        // special enough to just evaluate
        default:
                tempname(&tmp, nr->type);
                cgen_as(&tmp, nr);
                gused(&tmp);
        }
}

/*
 * clearslim generates code to zero a slim node.
 */
void
clearslim(Node *n)
{
        Node z;
        Mpflt zero;

        memset(&z, 0, sizeof(z));
        z.op = OLITERAL;
        z.type = n->type;
        z.addable = 1;

        switch(simtype[n->type->etype]) {
        case TCOMPLEX64:
        case TCOMPLEX128:
                z.val.u.cval = mal(sizeof(*z.val.u.cval));
                mpmovecflt(&z.val.u.cval->real, 0.0);
                mpmovecflt(&z.val.u.cval->imag, 0.0);
                break;

        case TFLOAT32:
        case TFLOAT64:
                mpmovecflt(&zero, 0.0);
                z.val.ctype = CTFLT;
                z.val.u.fval = &zero;
                break;

        case TPTR32:
        case TPTR64:
        case TCHAN:
        case TMAP:
                z.val.ctype = CTNIL;
                break;

        case TBOOL:
                z.val.ctype = CTBOOL;
                break;

        case TINT8:
        case TINT16:
        case TINT32:
        case TINT64:
        case TUINT8:
        case TUINT16:
        case TUINT32:
        case TUINT64:
                z.val.ctype = CTINT;
                z.val.u.xval = mal(sizeof(*z.val.u.xval));
                mpmovecfix(z.val.u.xval, 0);
                break;

        default:
                fatal("clearslim called on type %T", n->type);
        }

        ullmancalc(&z);
        cgen(&z, n);
}

/*
 * generate assignment:
 *      nl = nr
 * nr == N means zero nl.
 */
void
cgen_as(Node *nl, Node *nr)
{
        Type *tl;

        if(debug['g']) {
                dump("cgen_as", nl);
                dump("cgen_as = ", nr);
        }

        while(nr != N && nr->op == OCONVNOP)
                nr = nr->left;

        if(nl == N || isblank(nl)) {
                cgen_discard(nr);
                return;
        }

        if(nr == N || isnil(nr)) {
                // externals and heaps should already be clear
                if(nr == N) {
                        if(nl->class == PEXTERN)
                                return;
                        if(nl->class & PHEAP)
                                return;
                }

                tl = nl->type;
                if(tl == T)
                        return;
                if(isfat(tl)) {
                        if(nl->op == ONAME)
                                gvardef(nl);
                        clearfat(nl);
                        return;
                }
                clearslim(nl);
                return;
        }

        tl = nl->type;
        if(tl == T)
                return;

        cgen(nr, nl);
}

/*
 * generate:
 *      res = iface{typ, data}
 * n->left is typ
 * n->right is data
 */
void
cgen_eface(Node *n, Node *res)
{
        /* 
         * the right node of an eface may contain function calls that uses res as an argument,
         * so it's important that it is done first
         */
        Node dst;
        Node *tmp;

        tmp = temp(types[tptr]);
        cgen(n->right, tmp);

        gvardef(res);

        dst = *res;
        dst.type = types[tptr];
        dst.xoffset += widthptr;
        cgen(tmp, &dst);

        dst.xoffset -= widthptr;
        cgen(n->left, &dst);
}

/*
 * generate:
 *      res = s[lo, hi];
 * n->left is s
 * n->list is (cap(s)-lo(TUINT), hi-lo(TUINT)[, lo*width(TUINTPTR)])
 * caller (cgen) guarantees res is an addable ONAME.
 *
 * called for OSLICE, OSLICE3, OSLICEARR, OSLICE3ARR, OSLICESTR.
 */
void
cgen_slice(Node *n, Node *res)
{
        Node src, dst, *cap, *len, *offs, *add, *base;

        cap = n->list->n;
        len = n->list->next->n;
        offs = N;
        if(n->list->next->next)
                offs = n->list->next->next->n;

        // evaluate base pointer first, because it is the only
        // possibly complex expression. once that is evaluated
        // and stored, updating the len and cap can be done
        // without making any calls, so without doing anything that
        // might cause preemption or garbage collection.
        // this makes the whole slice update atomic as far as the
        // garbage collector can see.
        
        base = temp(types[TUINTPTR]);

        if(isnil(n->left)) {
                tempname(&src, n->left->type);
                cgen(n->left, &src);
        } else
                src = *n->left;
        if(n->op == OSLICE || n->op == OSLICE3 || n->op == OSLICESTR)
                src.xoffset += Array_array;

        if(n->op == OSLICEARR || n->op == OSLICE3ARR) {
                if(!isptr[n->left->type->etype])
                        fatal("slicearr is supposed to work on pointer: %+N\n", n);
                cgen(&src, base);
                cgen_checknil(base);
                if(offs != N) {
                        add = nod(OADD, base, offs);
                        typecheck(&add, Erv);
                        cgen(add, base);
                }
        } else if(offs == N) {
                src.type = types[tptr];
                cgen(&src, base);
        } else {
                src.type = types[tptr];
                add = nod(OADDPTR, &src, offs);
                typecheck(&add, Erv);
                cgen(add, base);
        }
        
        // committed to the update
        gvardef(res);

        // dst.array = src.array  [ + lo *width ]
        dst = *res;
        dst.xoffset += Array_array;
        dst.type = types[tptr];
        
        cgen(base, &dst);

        // dst.len = hi [ - lo ]
        dst = *res;
        dst.xoffset += Array_nel;
        dst.type = types[simtype[TUINT]];
        cgen(len, &dst);

        if(n->op != OSLICESTR) {
                // dst.cap = cap [ - lo ]
                dst = *res;
                dst.xoffset += Array_cap;
                dst.type = types[simtype[TUINT]];
                cgen(cap, &dst);
        }
}

/*
 * gather series of offsets
 * >=0 is direct addressed field
 * <0 is pointer to next field (+1)
 */
int
dotoffset(Node *n, int64 *oary, Node **nn)
{
        int i;

        switch(n->op) {
        case ODOT:
                if(n->xoffset == BADWIDTH) {
                        dump("bad width in dotoffset", n);
                        fatal("bad width in dotoffset");
                }
                i = dotoffset(n->left, oary, nn);
                if(i > 0) {
                        if(oary[i-1] >= 0)
                                oary[i-1] += n->xoffset;
                        else
                                oary[i-1] -= n->xoffset;
                        break;
                }
                if(i < 10)
                        oary[i++] = n->xoffset;
                break;

        case ODOTPTR:
                if(n->xoffset == BADWIDTH) {
                        dump("bad width in dotoffset", n);
                        fatal("bad width in dotoffset");
                }
                i = dotoffset(n->left, oary, nn);
                if(i < 10)
                        oary[i++] = -(n->xoffset+1);
                break;

        default:
                *nn = n;
                return 0;
        }
        if(i >= 10)
                *nn = N;
        return i;
}

/*
 * make a new off the books
 */
void
tempname(Node *nn, Type *t)
{
        Node *n;
        Sym *s;

        if(curfn == N)
                fatal("no curfn for tempname");

        if(t == T) {
                yyerror("tempname called with nil type");
                t = types[TINT32];
        }

        // give each tmp a different name so that there
        // a chance to registerizer them
        snprint(namebuf, sizeof(namebuf), "autotmp_%.4d", statuniqgen);
        statuniqgen++;
        s = lookup(namebuf);
        n = nod(ONAME, N, N);
        n->sym = s;
        s->def = n;
        n->type = t;
        n->class = PAUTO;
        n->addable = 1;
        n->ullman = 1;
        n->esc = EscNever;
        n->curfn = curfn;
        curfn->dcl = list(curfn->dcl, n);

        dowidth(t);
        n->xoffset = 0;
        *nn = *n;
}

Node*
temp(Type *t)
{
        Node *n;
        
        n = nod(OXXX, N, N);
        tempname(n, t);
        n->sym->def->used = 1;
        return n->orig;
}

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