/* Id: inline.c,v 1.6 2015/11/24 17:30:20 ragge Exp */
/* $NetBSD: inline.c,v 1.1.1.4 2016/02/09 20:28:59 plunky 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);
struct ntds {
int temp;
TWORD type;
union dimfun *df;
struct attr *attr;
};
/*
* 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 */
struct ntds *nt;/* Array of arg temp type data */
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 (ndebug)
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;
SDEBUG(("inline_addarg(%p)\n", ip));
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.
*
* Gcc inline syntax is a mess, see matrix below on emitting functions:
* without extern
* -std= - gnu89 gnu99
* gcc 3.3.5: ja ja ja
* gcc 4.1.3: ja ja ja
* gcc 4.3.1 ja ja nej
*
* with extern
* gcc 3.3.5: nej nej nej
* gcc 4.1.3: nej nej nej
* gcc 4.3.1 nej nej ja
*
* The attribute gnu_inline sets gnu89 behaviour.
* Since pcc mimics gcc 4.3.1 that is the behaviour we emulate.
*/
void
inline_end(void)
{
struct symtab *sp = cifun->sp;
SDEBUG(("inline_end()\n"));
if (sdebug)printip(&cifun->shead);
isinlining = 0;
#ifdef GCC_COMPAT
if (sp->sclass != STATIC &&
(attr_find(sp->sap, GCC_ATYP_GNU_INLINE) || xgnu89)) {
if (sp->sclass == EXTDEF)
sp->sclass = 0;
else
sp->sclass = EXTDEF;
}
#endif
if (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)
glval(p->n_left) += lbloff;
else if (p->n_op == CBRANCH)
glval(p->n_right) += 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(void)
{
struct istat *w;
int gotone = 0;
SLIST_FOREACH(w, &ipole, link) {
if ((w->flags & (REFD|WRITTEN)) == REFD &&
!DLIST_ISEMPTY(&w->shead, qelem)) {
locctr(PROG, w->sp);
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", ip->ip_asm); break;
default:
break;
}
}
}
#endif
static int toff;
static NODE *
mnode(struct ntds *nt, NODE *p)
{
NODE *q;
int num = nt->temp + toff;
if (p->n_op == CM) {
q = p->n_right;
q = tempnode(num, nt->type, nt->df, nt->attr);
nt--;
p->n_right = buildtree(ASSIGN, q, p->n_right);
p->n_left = mnode(nt, p->n_left);
p->n_op = COMOP;
} else {
p = pconvert(p);
q = tempnode(num, nt->type, nt->df, nt->attr);
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, l0, l1, l2, gainl;
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));
#ifdef GCC_COMPAT
gainl = attr_find(sp->sap, GCC_ATYP_ALW_INL) != NULL;
#else
gainl = 0;
#endif
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;
}
if (isinlining && cifun->sp == sp) {
/* Do not try to inline ourselves */
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
/* 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)
glval(p->n_left) += lmin;
else if (p->n_op == CBRANCH)
glval(p->n_right) += 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, 0);
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->nt[is->nargs-1], ap);
rp = buildtree(COMOP, p, rp);
}
tfree(f);
return rp;
}
void
inline_args(struct symtab **sp, int nargs)
{
union arglist *al;
struct istat *cf;
TWORD t;
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.
*/
/* XXX - this is ugly, invent something better */
if (cf->sp->sdf->dfun == NULL)
return; /* no prototype */
for (al = cf->sp->sdf->dfun; al->type != TNULL; al++) {
t = al->type;
if (t == TELLIPSIS)
return; /* cannot inline */
if (ISSOU(BTYPE(t)))
al++;
for (; t > BTMASK; t = DECREF(t))
if (ISARY(t) || ISFTN(t))
al++;
}
if (nargs) {
for (i = 0; i < nargs; i++)
if ((sp[i]->sflags & STNODE) == 0)
return; /* not temporary */
cf->nt = permalloc(sizeof(struct ntds)*nargs);
for (i = 0; i < nargs; i++) {
cf->nt[i].temp = sp[i]->soffset;
cf->nt[i].type = sp[i]->stype;
cf->nt[i].df = sp[i]->sdf;
cf->nt[i].attr = sp[i]->sap;
}
}
cf->nargs = nargs;
cf->flags |= CANINL;
}