blob: cf630f34843186ad834dcb390b738b9039e34afb [file] [log] [blame]
// 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;
}