524 lines
13 KiB
C
524 lines
13 KiB
C
/* $Id: inline.c,v 1.37 2010/10/17 20:13:40 ragge Exp $ */
|
|
/*
|
|
* Copyright (c) 2003, 2008 Anders Magnusson (ragge@ludd.luth.se).
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#include "pass1.h"
|
|
|
|
#include <stdarg.h>
|
|
|
|
/*
|
|
* Simple description of how the inlining works:
|
|
* A function found with the keyword "inline" is always saved.
|
|
* If it also has the keyword "extern" it is written out thereafter.
|
|
* If it has the keyword "static" it will be written out if it is referenced.
|
|
* inlining will only be done if -xinline is given, and only if it is
|
|
* possible to inline the function.
|
|
*/
|
|
static void printip(struct interpass *pole);
|
|
|
|
/*
|
|
* ilink from ipole points to the next struct in the list of functions.
|
|
*/
|
|
static struct istat {
|
|
SLIST_ENTRY(istat) link;
|
|
struct symtab *sp;
|
|
int flags;
|
|
#define CANINL 1 /* function is possible to inline */
|
|
#define WRITTEN 2 /* function is written out */
|
|
#define REFD 4 /* Referenced but not yet written out */
|
|
int *args; /* Array of arg temp numbers */
|
|
int nargs; /* number of args in array */
|
|
int retval; /* number of return temporary, if any */
|
|
struct interpass shead;
|
|
} *cifun;
|
|
|
|
static SLIST_HEAD(, istat) ipole = { NULL, &ipole.q_forw };
|
|
static int nlabs;
|
|
|
|
#define IP_REF (MAXIP+1)
|
|
#ifdef PCC_DEBUG
|
|
#define SDEBUG(x) if (sdebug) printf x
|
|
#else
|
|
#define SDEBUG(x)
|
|
#endif
|
|
|
|
int isinlining;
|
|
int inlnodecnt, inlstatcnt;
|
|
|
|
#define SZSI sizeof(struct istat)
|
|
#define ialloc() memset(permalloc(SZSI), 0, SZSI); inlstatcnt++
|
|
|
|
static void
|
|
tcnt(NODE *p, void *arg)
|
|
{
|
|
inlnodecnt++;
|
|
if (nlabs > 1 && (p->n_op == REG || p->n_op == OREG) &&
|
|
regno(p) == FPREG)
|
|
SLIST_FIRST(&ipole)->flags &= ~CANINL; /* no stack refs */
|
|
if (p->n_op == NAME || p->n_op == ICON)
|
|
p->n_sp = NULL; /* let symtabs be freed for inline funcs */
|
|
if (nflag)
|
|
printf("locking node %p\n", p);
|
|
}
|
|
|
|
static struct istat *
|
|
findfun(struct symtab *sp)
|
|
{
|
|
struct istat *is;
|
|
|
|
SLIST_FOREACH(is, &ipole, link)
|
|
if (is->sp == sp)
|
|
return is;
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
refnode(struct symtab *sp)
|
|
{
|
|
struct interpass *ip;
|
|
|
|
SDEBUG(("refnode(%s)\n", sp->sname));
|
|
|
|
ip = permalloc(sizeof(*ip));
|
|
ip->type = IP_REF;
|
|
ip->ip_name = (char *)sp;
|
|
inline_addarg(ip);
|
|
}
|
|
|
|
void
|
|
inline_addarg(struct interpass *ip)
|
|
{
|
|
extern NODE *cftnod;
|
|
|
|
#if 0
|
|
SDEBUG(("inline_addarg(%p)\n", ip));
|
|
#endif
|
|
DLIST_INSERT_BEFORE(&cifun->shead, ip, qelem);
|
|
if (ip->type == IP_DEFLAB)
|
|
nlabs++;
|
|
if (ip->type == IP_NODE)
|
|
walkf(ip->ip_node, tcnt, 0); /* Count as saved */
|
|
if (cftnod)
|
|
cifun->retval = regno(cftnod);
|
|
}
|
|
|
|
/*
|
|
* Called to setup for inlining of a new function.
|
|
*/
|
|
void
|
|
inline_start(struct symtab *sp)
|
|
{
|
|
struct istat *is;
|
|
|
|
SDEBUG(("inline_start(\"%s\")\n", sp->sname));
|
|
|
|
if (isinlining)
|
|
cerror("already inlining function");
|
|
|
|
if ((is = findfun(sp)) != 0) {
|
|
if (!DLIST_ISEMPTY(&is->shead, qelem))
|
|
uerror("inline function already defined");
|
|
} else {
|
|
is = ialloc();
|
|
is->sp = sp;
|
|
SLIST_INSERT_FIRST(&ipole, is, link);
|
|
DLIST_INIT(&is->shead, qelem);
|
|
}
|
|
cifun = is;
|
|
nlabs = 0;
|
|
isinlining++;
|
|
}
|
|
|
|
/*
|
|
* End of an inline function. In C99 an inline function declared "extern"
|
|
* should also have external linkage and are therefore printed out.
|
|
* But; this is the opposite for gcc inline functions, hence special
|
|
* care must be taken to handle that specific case.
|
|
*/
|
|
void
|
|
inline_end()
|
|
{
|
|
|
|
SDEBUG(("inline_end()\n"));
|
|
|
|
if (sdebug)printip(&cifun->shead);
|
|
isinlining = 0;
|
|
|
|
if (attr_find(cifun->sp->sap, GCC_ATYP_GNU_INLINE)) {
|
|
if (cifun->sp->sclass == EXTDEF)
|
|
cifun->sp->sclass = 0;
|
|
else
|
|
cifun->sp->sclass = EXTDEF;
|
|
}
|
|
|
|
if (cifun->sp->sclass == EXTDEF) {
|
|
cifun->flags |= REFD;
|
|
inline_prtout();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when an inline function is found, to be sure that it will
|
|
* be written out.
|
|
* The function may not be defined when inline_ref() is called.
|
|
*/
|
|
void
|
|
inline_ref(struct symtab *sp)
|
|
{
|
|
struct istat *w;
|
|
|
|
SDEBUG(("inline_ref(\"%s\")\n", sp->sname));
|
|
if (sp->sclass == SNULL)
|
|
return; /* only inline, no references */
|
|
if (isinlining) {
|
|
refnode(sp);
|
|
} else {
|
|
SLIST_FOREACH(w,&ipole, link) {
|
|
if (w->sp != sp)
|
|
continue;
|
|
w->flags |= REFD;
|
|
return;
|
|
}
|
|
/* function not yet defined, print out when found */
|
|
w = ialloc();
|
|
w->sp = sp;
|
|
w->flags |= REFD;
|
|
SLIST_INSERT_FIRST(&ipole, w, link);
|
|
DLIST_INIT(&w->shead, qelem);
|
|
}
|
|
}
|
|
|
|
static void
|
|
puto(struct istat *w)
|
|
{
|
|
struct interpass_prolog *ipp, *epp, *pp;
|
|
struct interpass *ip, *nip;
|
|
extern int crslab;
|
|
int lbloff = 0;
|
|
|
|
/* Copy the saved function and print it out */
|
|
ipp = 0; /* XXX data flow analysis */
|
|
DLIST_FOREACH(ip, &w->shead, qelem) {
|
|
switch (ip->type) {
|
|
case IP_EPILOG:
|
|
case IP_PROLOG:
|
|
if (ip->type == IP_PROLOG) {
|
|
ipp = (struct interpass_prolog *)ip;
|
|
/* fix label offsets */
|
|
lbloff = crslab - ipp->ip_lblnum;
|
|
} else {
|
|
epp = (struct interpass_prolog *)ip;
|
|
crslab += (epp->ip_lblnum - ipp->ip_lblnum);
|
|
}
|
|
pp = tmpalloc(sizeof(struct interpass_prolog));
|
|
memcpy(pp, ip, sizeof(struct interpass_prolog));
|
|
pp->ip_lblnum += lbloff;
|
|
#ifdef PCC_DEBUG
|
|
if (ip->type == IP_EPILOG && crslab != pp->ip_lblnum)
|
|
cerror("puto: %d != %d", crslab, pp->ip_lblnum);
|
|
#endif
|
|
pass2_compile((struct interpass *)pp);
|
|
break;
|
|
|
|
case IP_REF:
|
|
inline_ref((struct symtab *)ip->ip_name);
|
|
break;
|
|
|
|
default:
|
|
nip = tmpalloc(sizeof(struct interpass));
|
|
*nip = *ip;
|
|
if (nip->type == IP_NODE) {
|
|
NODE *p;
|
|
|
|
p = nip->ip_node = ccopy(nip->ip_node);
|
|
if (p->n_op == GOTO)
|
|
p->n_left->n_lval += lbloff;
|
|
else if (p->n_op == CBRANCH)
|
|
p->n_right->n_lval += lbloff;
|
|
} else if (nip->type == IP_DEFLAB)
|
|
nip->ip_lbl += lbloff;
|
|
pass2_compile(nip);
|
|
break;
|
|
}
|
|
}
|
|
w->flags |= WRITTEN;
|
|
}
|
|
|
|
/*
|
|
* printout functions that are referenced.
|
|
*/
|
|
void
|
|
inline_prtout()
|
|
{
|
|
struct istat *w;
|
|
int gotone = 0;
|
|
|
|
SLIST_FOREACH(w, &ipole, link) {
|
|
if ((w->flags & (REFD|WRITTEN)) == REFD &&
|
|
!DLIST_ISEMPTY(&w->shead, qelem)) {
|
|
defloc(w->sp);
|
|
puto(w);
|
|
w->flags |= WRITTEN;
|
|
gotone++;
|
|
}
|
|
}
|
|
if (gotone)
|
|
inline_prtout();
|
|
}
|
|
|
|
#if 1
|
|
static void
|
|
printip(struct interpass *pole)
|
|
{
|
|
static char *foo[] = {
|
|
0, "NODE", "PROLOG", "STKOFF", "EPILOG", "DEFLAB", "DEFNAM", "ASM" };
|
|
struct interpass *ip;
|
|
struct interpass_prolog *ipplg, *epplg;
|
|
|
|
DLIST_FOREACH(ip, pole, qelem) {
|
|
if (ip->type > MAXIP)
|
|
printf("IP(%d) (%p): ", ip->type, ip);
|
|
else
|
|
printf("%s (%p): ", foo[ip->type], ip);
|
|
switch (ip->type) {
|
|
case IP_NODE: printf("\n");
|
|
#ifdef PCC_DEBUG
|
|
fwalk(ip->ip_node, eprint, 0); break;
|
|
#endif
|
|
case IP_PROLOG:
|
|
ipplg = (struct interpass_prolog *)ip;
|
|
printf("%s %s regs %lx autos %d mintemp %d minlbl %d\n",
|
|
ipplg->ipp_name, ipplg->ipp_vis ? "(local)" : "",
|
|
(long)ipplg->ipp_regs[0], ipplg->ipp_autos,
|
|
ipplg->ip_tmpnum, ipplg->ip_lblnum);
|
|
break;
|
|
case IP_EPILOG:
|
|
epplg = (struct interpass_prolog *)ip;
|
|
printf("%s %s regs %lx autos %d mintemp %d minlbl %d\n",
|
|
epplg->ipp_name, epplg->ipp_vis ? "(local)" : "",
|
|
(long)epplg->ipp_regs[0], epplg->ipp_autos,
|
|
epplg->ip_tmpnum, epplg->ip_lblnum);
|
|
break;
|
|
case IP_DEFLAB: printf(LABFMT "\n", ip->ip_lbl); break;
|
|
case IP_DEFNAM: printf("\n"); break;
|
|
case IP_ASM: printf("%s\n", ip->ip_asm); break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int toff;
|
|
|
|
static NODE *
|
|
mnode(int *n, NODE *p)
|
|
{
|
|
NODE *q;
|
|
int num = *n + toff;
|
|
|
|
if (p->n_op == CM) {
|
|
q = p->n_right;
|
|
q = tempnode(num, q->n_type, q->n_df, q->n_ap);
|
|
n--;
|
|
p->n_right = buildtree(ASSIGN, q, p->n_right);
|
|
p->n_left = mnode(n, p->n_left);
|
|
p->n_op = COMOP;
|
|
} else {
|
|
p = pconvert(p);
|
|
q = tempnode(num, p->n_type, p->n_df, p->n_ap);
|
|
p = buildtree(ASSIGN, q, p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
rtmps(NODE *p, void *arg)
|
|
{
|
|
if (p->n_op == TEMP)
|
|
regno(p) += toff;
|
|
}
|
|
|
|
/*
|
|
* Inline a function. Returns the return value.
|
|
* There are two major things that must be converted when
|
|
* inlining a function:
|
|
* - Label numbers must be updated with an offset.
|
|
* - The stack block must be relocated (add to REG or OREG).
|
|
* - Temporaries should be updated (but no must)
|
|
*/
|
|
NODE *
|
|
inlinetree(struct symtab *sp, NODE *f, NODE *ap)
|
|
{
|
|
extern int crslab, tvaloff;
|
|
struct istat *is = findfun(sp);
|
|
struct interpass *ip, *ipf, *ipl;
|
|
int lmin, stksz, l0, l1, l2, gainl;
|
|
OFFSZ stkoff;
|
|
NODE *p, *rp;
|
|
|
|
if (is == NULL || nerrors) {
|
|
inline_ref(sp); /* prototype of not yet declared inline ftn */
|
|
return NIL;
|
|
}
|
|
|
|
SDEBUG(("inlinetree(%p,%p) OK %d\n", f, ap, is->flags & CANINL));
|
|
|
|
gainl = attr_find(sp->sap, GCC_ATYP_ALW_INL) != NULL;
|
|
|
|
if ((is->flags & CANINL) == 0 && gainl)
|
|
werror("cannot inline but always_inline");
|
|
|
|
if ((is->flags & CANINL) == 0 || (xinline == 0 && gainl == 0)) {
|
|
if (is->sp->sclass == STATIC || is->sp->sclass == USTATIC)
|
|
inline_ref(sp);
|
|
return NIL;
|
|
}
|
|
|
|
#ifdef mach_i386
|
|
if (kflag) {
|
|
is->flags |= REFD; /* if static inline, emit */
|
|
return NIL; /* XXX cannot handle hidden ebx arg */
|
|
}
|
|
#endif
|
|
|
|
stkoff = stksz = 0;
|
|
/* emit jumps to surround inline function */
|
|
branch(l0 = getlab());
|
|
plabel(l1 = getlab());
|
|
l2 = getlab();
|
|
SDEBUG(("branch labels %d,%d,%d\n", l0, l1, l2));
|
|
|
|
ipf = DLIST_NEXT(&is->shead, qelem); /* prolog */
|
|
ipl = DLIST_PREV(&is->shead, qelem); /* epilog */
|
|
|
|
/* Fix label & temp offsets */
|
|
#define IPP(x) ((struct interpass_prolog *)x)
|
|
SDEBUG(("pre-offsets crslab %d tvaloff %d\n", crslab, tvaloff));
|
|
lmin = crslab - IPP(ipf)->ip_lblnum;
|
|
crslab += (IPP(ipl)->ip_lblnum - IPP(ipf)->ip_lblnum) + 1;
|
|
toff = tvaloff - IPP(ipf)->ip_tmpnum;
|
|
tvaloff += (IPP(ipl)->ip_tmpnum - IPP(ipf)->ip_tmpnum) + 1;
|
|
SDEBUG(("offsets crslab %d lmin %d tvaloff %d toff %d\n",
|
|
crslab, lmin, tvaloff, toff));
|
|
|
|
/* traverse until first real label */
|
|
ipf = DLIST_NEXT(ipf, qelem);
|
|
do
|
|
ipf = DLIST_NEXT(ipf, qelem);
|
|
while (ipf->type != IP_DEFLAB);
|
|
|
|
/* traverse backwards to last label */
|
|
do
|
|
ipl = DLIST_PREV(ipl, qelem);
|
|
while (ipl->type != IP_DEFLAB);
|
|
|
|
/* So, walk over all statements and emit them */
|
|
for (ip = ipf; ip != ipl; ip = DLIST_NEXT(ip, qelem)) {
|
|
switch (ip->type) {
|
|
case IP_NODE:
|
|
p = ccopy(ip->ip_node);
|
|
if (p->n_op == GOTO)
|
|
p->n_left->n_lval += lmin;
|
|
else if (p->n_op == CBRANCH)
|
|
p->n_right->n_lval += lmin;
|
|
walkf(p, rtmps, 0);
|
|
#ifdef PCC_DEBUG
|
|
if (sdebug) {
|
|
printf("converted node\n");
|
|
fwalk(ip->ip_node, eprint, 0);
|
|
fwalk(p, eprint, 0);
|
|
}
|
|
#endif
|
|
send_passt(IP_NODE, p);
|
|
break;
|
|
|
|
case IP_DEFLAB:
|
|
SDEBUG(("converted label %d to %d\n",
|
|
ip->ip_lbl, ip->ip_lbl + lmin));
|
|
send_passt(IP_DEFLAB, ip->ip_lbl + lmin);
|
|
break;
|
|
|
|
case IP_ASM:
|
|
send_passt(IP_ASM, ip->ip_asm);
|
|
break;
|
|
|
|
case IP_REF:
|
|
inline_ref((struct symtab *)ip->ip_name);
|
|
break;
|
|
|
|
default:
|
|
cerror("bad inline stmt %d", ip->type);
|
|
}
|
|
}
|
|
SDEBUG(("last label %d to %d\n", ip->ip_lbl, ip->ip_lbl + lmin));
|
|
send_passt(IP_DEFLAB, ip->ip_lbl + lmin);
|
|
|
|
branch(l2);
|
|
plabel(l0);
|
|
|
|
rp = block(GOTO, bcon(l1), NIL, INT, 0, MKAP(INT));
|
|
if (is->retval)
|
|
p = tempnode(is->retval + toff, DECREF(sp->stype),
|
|
sp->sdf, sp->sap);
|
|
else
|
|
p = bcon(0);
|
|
rp = buildtree(COMOP, rp, p);
|
|
|
|
if (is->nargs) {
|
|
p = mnode(&is->args[is->nargs-1], ap);
|
|
rp = buildtree(COMOP, p, rp);
|
|
}
|
|
|
|
tfree(f);
|
|
return rp;
|
|
}
|
|
|
|
void
|
|
inline_args(struct symtab **sp, int nargs)
|
|
{
|
|
struct istat *cf;
|
|
int i;
|
|
|
|
SDEBUG(("inline_args\n"));
|
|
cf = cifun;
|
|
/*
|
|
* First handle arguments. We currently do not inline anything if:
|
|
* - function has varargs
|
|
* - function args are volatile, checked if no temp node is asg'd.
|
|
*/
|
|
if (nargs) {
|
|
for (i = 0; i < nargs; i++)
|
|
if ((sp[i]->sflags & STNODE) == 0)
|
|
return; /* not temporary */
|
|
cf->args = permalloc(sizeof(int)*nargs);
|
|
for (i = 0; i < nargs; i++)
|
|
cf->args[i] = sp[i]->soffset;
|
|
}
|
|
cf->nargs = nargs;
|
|
cf->flags |= CANINL;
|
|
}
|