1
0
mirror of https://github.com/golang/go synced 2024-10-04 22:21:22 -06:00
go/src/cmd/gc/gen.c

832 lines
14 KiB
C
Raw Normal View History

// 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;
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 %N", 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:
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:
cmd/gc: contiguous loop layout Drop expecttaken function in favor of extra argument to gbranch and bgen. Mark loop condition as likely to be true, so that loops are generated inline. The main benefit here is contiguous code when trying to read the generated assembly. It has only minor effects on the timing, and they mostly cancel the minor effects that aligning function entry points had. One exception: both changes made Fannkuch faster. Compared to before CL 6244066 (before aligned functions) benchmark old ns/op new ns/op delta BenchmarkBinaryTree17 4222117400 4201958800 -0.48% BenchmarkFannkuch11 3462631800 3215908600 -7.13% BenchmarkGobDecode 20887622 20899164 +0.06% BenchmarkGobEncode 9548772 9439083 -1.15% BenchmarkGzip 151687 152060 +0.25% BenchmarkGunzip 8742 8711 -0.35% BenchmarkJSONEncode 62730560 62686700 -0.07% BenchmarkJSONDecode 252569180 252368960 -0.08% BenchmarkMandelbrot200 5267599 5252531 -0.29% BenchmarkRevcomp25M 980813500 985248400 +0.45% BenchmarkTemplate 361259100 357414680 -1.06% Compared to tip (aligned functions): benchmark old ns/op new ns/op delta BenchmarkBinaryTree17 4140739800 4201958800 +1.48% BenchmarkFannkuch11 3259914400 3215908600 -1.35% BenchmarkGobDecode 20620222 20899164 +1.35% BenchmarkGobEncode 9384886 9439083 +0.58% BenchmarkGzip 150333 152060 +1.15% BenchmarkGunzip 8741 8711 -0.34% BenchmarkJSONEncode 65210990 62686700 -3.87% BenchmarkJSONDecode 249394860 252368960 +1.19% BenchmarkMandelbrot200 5273394 5252531 -0.40% BenchmarkRevcomp25M 996013800 985248400 -1.08% BenchmarkTemplate 360620840 357414680 -0.89% R=ken2 CC=golang-dev https://golang.org/cl/6245069
2012-05-30 16:07:39 -06:00
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:
cmd/gc: contiguous loop layout Drop expecttaken function in favor of extra argument to gbranch and bgen. Mark loop condition as likely to be true, so that loops are generated inline. The main benefit here is contiguous code when trying to read the generated assembly. It has only minor effects on the timing, and they mostly cancel the minor effects that aligning function entry points had. One exception: both changes made Fannkuch faster. Compared to before CL 6244066 (before aligned functions) benchmark old ns/op new ns/op delta BenchmarkBinaryTree17 4222117400 4201958800 -0.48% BenchmarkFannkuch11 3462631800 3215908600 -7.13% BenchmarkGobDecode 20887622 20899164 +0.06% BenchmarkGobEncode 9548772 9439083 -1.15% BenchmarkGzip 151687 152060 +0.25% BenchmarkGunzip 8742 8711 -0.35% BenchmarkJSONEncode 62730560 62686700 -0.07% BenchmarkJSONDecode 252569180 252368960 -0.08% BenchmarkMandelbrot200 5267599 5252531 -0.29% BenchmarkRevcomp25M 980813500 985248400 +0.45% BenchmarkTemplate 361259100 357414680 -1.06% Compared to tip (aligned functions): benchmark old ns/op new ns/op delta BenchmarkBinaryTree17 4140739800 4201958800 +1.48% BenchmarkFannkuch11 3259914400 3215908600 -1.35% BenchmarkGobDecode 20620222 20899164 +1.35% BenchmarkGobEncode 9384886 9439083 +0.58% BenchmarkGzip 150333 152060 +1.15% BenchmarkGunzip 8741 8711 -0.34% BenchmarkJSONEncode 65210990 62686700 -3.87% BenchmarkJSONDecode 249394860 252368960 +1.19% BenchmarkMandelbrot200 5273394 5252531 -0.40% BenchmarkRevcomp25M 996013800 985248400 -1.08% BenchmarkTemplate 360620840 357414680 -0.89% R=ken2 CC=golang-dev https://golang.org/cl/6245069
2012-05-30 16:07:39 -06:00
bgen(n->ntest, 0, 0, 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:
cgen_ret(n);
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 *l;
// generate a rewrite for method call
// (p.f)(...) goes to (f)(p,...)
l = n->left;
if(l->op != ODOTMETH)
fatal("cgen_callmeth: not dotmethod: %N");
n->op = OCALLFUNC;
n->left = n->left->right;
n->left->type = l->type;
if(n->left->op == ONAME)
n->left->class = PFUNC;
cgen_call(n, 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.
* nothing to do for on-stack automatics,
* but might 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;
// special enough to just evaluate
default:
tempname(&tmp, nr->type);
cgen_as(&tmp, nr);
gused(&tmp);
}
}
/*
* generate assignment:
* nl = nr
* nr == N means zero nl.
*/
void
cgen_as(Node *nl, Node *nr)
{
Node nc;
Type *tl;
int iszer;
if(debug['g']) {
dump("cgen_as", nl);
dump("cgen_as = ", nr);
}
if(nl == N || isblank(nl)) {
cgen_discard(nr);
return;
}
iszer = 0;
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)) {
clearfat(nl);
goto ret;
}
/* invent a "zero" for the rhs */
iszer = 1;
nr = &nc;
memset(nr, 0, sizeof(*nr));
switch(simtype[tl->etype]) {
default:
fatal("cgen_as: tl %T", tl);
break;
case TINT8:
case TUINT8:
case TINT16:
case TUINT16:
case TINT32:
case TUINT32:
case TINT64:
case TUINT64:
nr->val.u.xval = mal(sizeof(*nr->val.u.xval));
mpmovecfix(nr->val.u.xval, 0);
nr->val.ctype = CTINT;
break;
case TFLOAT32:
case TFLOAT64:
nr->val.u.fval = mal(sizeof(*nr->val.u.fval));
mpmovecflt(nr->val.u.fval, 0.0);
nr->val.ctype = CTFLT;
break;
case TBOOL:
nr->val.u.bval = 0;
nr->val.ctype = CTBOOL;
break;
case TPTR32:
case TPTR64:
nr->val.ctype = CTNIL;
break;
case TCOMPLEX64:
case TCOMPLEX128:
nr->val.u.cval = mal(sizeof(*nr->val.u.cval));
mpmovecflt(&nr->val.u.cval->real, 0.0);
mpmovecflt(&nr->val.u.cval->imag, 0.0);
break;
}
nr->op = OLITERAL;
nr->type = tl;
nr->addable = 1;
ullmancalc(nr);
}
tl = nl->type;
if(tl == T)
return;
cgen(nr, nl);
if(iszer && nl->addable)
gused(nl);
ret:
;
}
/*
* gather series of offsets
* >=0 is direct addressed field
* <0 is pointer to next field (+1)
*/
int
dotoffset(Node *n, int *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;
}