1/*	Id: inline.c,v 1.6 2015/11/24 17:30:20 ragge Exp 	*/
2/*	$NetBSD: inline.c,v 1.1.1.4 2016/02/09 20:28:59 plunky Exp $	*/
3/*
4 * Copyright (c) 2003, 2008 Anders Magnusson (ragge@ludd.luth.se).
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28
29#include "pass1.h"
30
31#include <stdarg.h>
32
33/*
34 * Simple description of how the inlining works:
35 * A function found with the keyword "inline" is always saved.
36 * If it also has the keyword "extern" it is written out thereafter.
37 * If it has the keyword "static" it will be written out if it is referenced.
38 * inlining will only be done if -xinline is given, and only if it is
39 * possible to inline the function.
40 */
41static void printip(struct interpass *pole);
42
43struct ntds {
44	int temp;
45	TWORD type;
46	union dimfun *df;
47	struct attr *attr;
48};
49
50/*
51 * ilink from ipole points to the next struct in the list of functions.
52 */
53static struct istat {
54	SLIST_ENTRY(istat) link;
55	struct symtab *sp;
56	int flags;
57#define	CANINL	1	/* function is possible to inline */
58#define	WRITTEN	2	/* function is written out */
59#define	REFD	4	/* Referenced but not yet written out */
60	struct ntds *nt;/* Array of arg temp type data */
61	int nargs;	/* number of args in array */
62	int retval;	/* number of return temporary, if any */
63	struct interpass shead;
64} *cifun;
65
66static SLIST_HEAD(, istat) ipole = { NULL, &ipole.q_forw };
67static int nlabs;
68
69#define	IP_REF	(MAXIP+1)
70#ifdef PCC_DEBUG
71#define	SDEBUG(x)	if (sdebug) printf x
72#else
73#define	SDEBUG(x)
74#endif
75
76int isinlining;
77int inlnodecnt, inlstatcnt;
78
79#define	SZSI	sizeof(struct istat)
80#define	ialloc() memset(permalloc(SZSI), 0, SZSI); inlstatcnt++
81
82static void
83tcnt(NODE *p, void *arg)
84{
85	inlnodecnt++;
86	if (nlabs > 1 && (p->n_op == REG || p->n_op == OREG) &&
87	    regno(p) == FPREG)
88		SLIST_FIRST(&ipole)->flags &= ~CANINL; /* no stack refs */
89	if (p->n_op == NAME || p->n_op == ICON)
90		p->n_sp = NULL; /* let symtabs be freed for inline funcs */
91	if (ndebug)
92		printf("locking node %p\n", p);
93}
94
95static struct istat *
96findfun(struct symtab *sp)
97{
98	struct istat *is;
99
100	SLIST_FOREACH(is, &ipole, link)
101		if (is->sp == sp)
102			return is;
103	return NULL;
104}
105
106static void
107refnode(struct symtab *sp)
108{
109	struct interpass *ip;
110
111	SDEBUG(("refnode(%s)\n", sp->sname));
112
113	ip = permalloc(sizeof(*ip));
114	ip->type = IP_REF;
115	ip->ip_name = (char *)sp;
116	inline_addarg(ip);
117}
118
119void
120inline_addarg(struct interpass *ip)
121{
122	extern NODE *cftnod;
123
124	SDEBUG(("inline_addarg(%p)\n", ip));
125	DLIST_INSERT_BEFORE(&cifun->shead, ip, qelem);
126	if (ip->type == IP_DEFLAB)
127		nlabs++;
128	if (ip->type == IP_NODE)
129		walkf(ip->ip_node, tcnt, 0); /* Count as saved */
130	if (cftnod)
131		cifun->retval = regno(cftnod);
132}
133
134/*
135 * Called to setup for inlining of a new function.
136 */
137void
138inline_start(struct symtab *sp)
139{
140	struct istat *is;
141
142	SDEBUG(("inline_start(\"%s\")\n", sp->sname));
143
144	if (isinlining)
145		cerror("already inlining function");
146
147	if ((is = findfun(sp)) != 0) {
148		if (!DLIST_ISEMPTY(&is->shead, qelem))
149			uerror("inline function already defined");
150	} else {
151		is = ialloc();
152		is->sp = sp;
153		SLIST_INSERT_FIRST(&ipole, is, link);
154		DLIST_INIT(&is->shead, qelem);
155	}
156	cifun = is;
157	nlabs = 0;
158	isinlining++;
159}
160
161/*
162 * End of an inline function. In C99 an inline function declared "extern"
163 * should also have external linkage and are therefore printed out.
164 *
165 * Gcc inline syntax is a mess, see matrix below on emitting functions:
166 *		    without extern
167 *	-std=		-	gnu89	gnu99
168 *	gcc 3.3.5:	ja	ja	ja
169 *	gcc 4.1.3:	ja	ja	ja
170 *	gcc 4.3.1	ja	ja	nej
171 *
172 *		     with extern
173 *	gcc 3.3.5:	nej	nej	nej
174 *	gcc 4.1.3:	nej	nej	nej
175 *	gcc 4.3.1	nej	nej	ja
176 *
177 * The attribute gnu_inline sets gnu89 behaviour.
178 * Since pcc mimics gcc 4.3.1 that is the behaviour we emulate.
179 */
180void
181inline_end(void)
182{
183	struct symtab *sp = cifun->sp;
184
185	SDEBUG(("inline_end()\n"));
186
187	if (sdebug)printip(&cifun->shead);
188	isinlining = 0;
189
190#ifdef GCC_COMPAT
191	if (sp->sclass != STATIC &&
192	    (attr_find(sp->sap, GCC_ATYP_GNU_INLINE) || xgnu89)) {
193		if (sp->sclass == EXTDEF)
194			sp->sclass = 0;
195		else
196			sp->sclass = EXTDEF;
197	}
198#endif
199	if (sp->sclass == EXTDEF) {
200		cifun->flags |= REFD;
201		inline_prtout();
202	}
203}
204
205/*
206 * Called when an inline function is found, to be sure that it will
207 * be written out.
208 * The function may not be defined when inline_ref() is called.
209 */
210void
211inline_ref(struct symtab *sp)
212{
213	struct istat *w;
214
215	SDEBUG(("inline_ref(\"%s\")\n", sp->sname));
216	if (sp->sclass == SNULL)
217		return; /* only inline, no references */
218	if (isinlining) {
219		refnode(sp);
220	} else {
221		SLIST_FOREACH(w,&ipole, link) {
222			if (w->sp != sp)
223				continue;
224			w->flags |= REFD;
225			return;
226		}
227		/* function not yet defined, print out when found */
228		w = ialloc();
229		w->sp = sp;
230		w->flags |= REFD;
231		SLIST_INSERT_FIRST(&ipole, w, link);
232		DLIST_INIT(&w->shead, qelem);
233	}
234}
235
236static void
237puto(struct istat *w)
238{
239	struct interpass_prolog *ipp, *epp, *pp;
240	struct interpass *ip, *nip;
241	extern int crslab;
242	int lbloff = 0;
243
244	/* Copy the saved function and print it out */
245	ipp = 0; /* XXX data flow analysis */
246	DLIST_FOREACH(ip, &w->shead, qelem) {
247		switch (ip->type) {
248		case IP_EPILOG:
249		case IP_PROLOG:
250			if (ip->type == IP_PROLOG) {
251				ipp = (struct interpass_prolog *)ip;
252				/* fix label offsets */
253				lbloff = crslab - ipp->ip_lblnum;
254			} else {
255				epp = (struct interpass_prolog *)ip;
256				crslab += (epp->ip_lblnum - ipp->ip_lblnum);
257			}
258			pp = tmpalloc(sizeof(struct interpass_prolog));
259			memcpy(pp, ip, sizeof(struct interpass_prolog));
260			pp->ip_lblnum += lbloff;
261#ifdef PCC_DEBUG
262			if (ip->type == IP_EPILOG && crslab != pp->ip_lblnum)
263				cerror("puto: %d != %d", crslab, pp->ip_lblnum);
264#endif
265			pass2_compile((struct interpass *)pp);
266			break;
267
268		case IP_REF:
269			inline_ref((struct symtab *)ip->ip_name);
270			break;
271
272		default:
273			nip = tmpalloc(sizeof(struct interpass));
274			*nip = *ip;
275			if (nip->type == IP_NODE) {
276				NODE *p;
277
278				p = nip->ip_node = ccopy(nip->ip_node);
279				if (p->n_op == GOTO)
280					glval(p->n_left) += lbloff;
281				else if (p->n_op == CBRANCH)
282					glval(p->n_right) += lbloff;
283			} else if (nip->type == IP_DEFLAB)
284				nip->ip_lbl += lbloff;
285			pass2_compile(nip);
286			break;
287		}
288	}
289	w->flags |= WRITTEN;
290}
291
292/*
293 * printout functions that are referenced.
294 */
295void
296inline_prtout(void)
297{
298	struct istat *w;
299	int gotone = 0;
300
301	SLIST_FOREACH(w, &ipole, link) {
302		if ((w->flags & (REFD|WRITTEN)) == REFD &&
303		    !DLIST_ISEMPTY(&w->shead, qelem)) {
304			locctr(PROG, w->sp);
305			defloc(w->sp);
306			puto(w);
307			w->flags |= WRITTEN;
308			gotone++;
309		}
310	}
311	if (gotone)
312		inline_prtout();
313}
314
315#if 1
316static void
317printip(struct interpass *pole)
318{
319	static char *foo[] = {
320	   0, "NODE", "PROLOG", "STKOFF", "EPILOG", "DEFLAB", "DEFNAM", "ASM" };
321	struct interpass *ip;
322	struct interpass_prolog *ipplg, *epplg;
323
324	DLIST_FOREACH(ip, pole, qelem) {
325		if (ip->type > MAXIP)
326			printf("IP(%d) (%p): ", ip->type, ip);
327		else
328			printf("%s (%p): ", foo[ip->type], ip);
329		switch (ip->type) {
330		case IP_NODE: printf("\n");
331#ifdef PCC_DEBUG
332			fwalk(ip->ip_node, eprint, 0); break;
333#endif
334		case IP_PROLOG:
335			ipplg = (struct interpass_prolog *)ip;
336			printf("%s %s regs %lx autos %d mintemp %d minlbl %d\n",
337			    ipplg->ipp_name, ipplg->ipp_vis ? "(local)" : "",
338			    (long)ipplg->ipp_regs[0], ipplg->ipp_autos,
339			    ipplg->ip_tmpnum, ipplg->ip_lblnum);
340			break;
341		case IP_EPILOG:
342			epplg = (struct interpass_prolog *)ip;
343			printf("%s %s regs %lx autos %d mintemp %d minlbl %d\n",
344			    epplg->ipp_name, epplg->ipp_vis ? "(local)" : "",
345			    (long)epplg->ipp_regs[0], epplg->ipp_autos,
346			    epplg->ip_tmpnum, epplg->ip_lblnum);
347			break;
348		case IP_DEFLAB: printf(LABFMT "\n", ip->ip_lbl); break;
349		case IP_DEFNAM: printf("\n"); break;
350		case IP_ASM: printf("%s", ip->ip_asm); break;
351		default:
352			break;
353		}
354	}
355}
356#endif
357
358static int toff;
359
360static NODE *
361mnode(struct ntds *nt, NODE *p)
362{
363	NODE *q;
364	int num = nt->temp + toff;
365
366	if (p->n_op == CM) {
367		q = p->n_right;
368		q = tempnode(num, nt->type, nt->df, nt->attr);
369		nt--;
370		p->n_right = buildtree(ASSIGN, q, p->n_right);
371		p->n_left = mnode(nt, p->n_left);
372		p->n_op = COMOP;
373	} else {
374		p = pconvert(p);
375		q = tempnode(num, nt->type, nt->df, nt->attr);
376		p = buildtree(ASSIGN, q, p);
377	}
378	return p;
379}
380
381static void
382rtmps(NODE *p, void *arg)
383{
384	if (p->n_op == TEMP)
385		regno(p) += toff;
386}
387
388/*
389 * Inline a function. Returns the return value.
390 * There are two major things that must be converted when
391 * inlining a function:
392 * - Label numbers must be updated with an offset.
393 * - The stack block must be relocated (add to REG or OREG).
394 * - Temporaries should be updated (but no must)
395 */
396NODE *
397inlinetree(struct symtab *sp, NODE *f, NODE *ap)
398{
399	extern int crslab, tvaloff;
400	struct istat *is = findfun(sp);
401	struct interpass *ip, *ipf, *ipl;
402	int lmin, l0, l1, l2, gainl;
403	NODE *p, *rp;
404
405	if (is == NULL || nerrors) {
406		inline_ref(sp); /* prototype of not yet declared inline ftn */
407		return NIL;
408	}
409
410	SDEBUG(("inlinetree(%p,%p) OK %d\n", f, ap, is->flags & CANINL));
411
412#ifdef GCC_COMPAT
413	gainl = attr_find(sp->sap, GCC_ATYP_ALW_INL) != NULL;
414#else
415	gainl = 0;
416#endif
417
418	if ((is->flags & CANINL) == 0 && gainl)
419		werror("cannot inline but always_inline");
420
421	if ((is->flags & CANINL) == 0 || (xinline == 0 && gainl == 0)) {
422		if (is->sp->sclass == STATIC || is->sp->sclass == USTATIC)
423			inline_ref(sp);
424		return NIL;
425	}
426
427	if (isinlining && cifun->sp == sp) {
428		/* Do not try to inline ourselves */
429		inline_ref(sp);
430		return NIL;
431	}
432
433#ifdef mach_i386
434	if (kflag) {
435		is->flags |= REFD; /* if static inline, emit */
436		return NIL; /* XXX cannot handle hidden ebx arg */
437	}
438#endif
439
440	/* emit jumps to surround inline function */
441	branch(l0 = getlab());
442	plabel(l1 = getlab());
443	l2 = getlab();
444	SDEBUG(("branch labels %d,%d,%d\n", l0, l1, l2));
445
446	ipf = DLIST_NEXT(&is->shead, qelem); /* prolog */
447	ipl = DLIST_PREV(&is->shead, qelem); /* epilog */
448
449	/* Fix label & temp offsets */
450#define	IPP(x) ((struct interpass_prolog *)x)
451	SDEBUG(("pre-offsets crslab %d tvaloff %d\n", crslab, tvaloff));
452	lmin = crslab - IPP(ipf)->ip_lblnum;
453	crslab += (IPP(ipl)->ip_lblnum - IPP(ipf)->ip_lblnum) + 1;
454	toff = tvaloff - IPP(ipf)->ip_tmpnum;
455	tvaloff += (IPP(ipl)->ip_tmpnum - IPP(ipf)->ip_tmpnum) + 1;
456	SDEBUG(("offsets crslab %d lmin %d tvaloff %d toff %d\n",
457	    crslab, lmin, tvaloff, toff));
458
459	/* traverse until first real label */
460	ipf = DLIST_NEXT(ipf, qelem);
461	do
462		ipf = DLIST_NEXT(ipf, qelem);
463	while (ipf->type != IP_DEFLAB);
464
465	/* traverse backwards to last label */
466	do
467		ipl = DLIST_PREV(ipl, qelem);
468	while (ipl->type != IP_DEFLAB);
469
470	/* So, walk over all statements and emit them */
471	for (ip = ipf; ip != ipl; ip = DLIST_NEXT(ip, qelem)) {
472		switch (ip->type) {
473		case IP_NODE:
474			p = ccopy(ip->ip_node);
475			if (p->n_op == GOTO)
476				glval(p->n_left) += lmin;
477			else if (p->n_op == CBRANCH)
478				glval(p->n_right) += lmin;
479			walkf(p, rtmps, 0);
480#ifdef PCC_DEBUG
481			if (sdebug) {
482				printf("converted node\n");
483				fwalk(ip->ip_node, eprint, 0);
484				fwalk(p, eprint, 0);
485			}
486#endif
487			send_passt(IP_NODE, p);
488			break;
489
490		case IP_DEFLAB:
491			SDEBUG(("converted label %d to %d\n",
492			    ip->ip_lbl, ip->ip_lbl + lmin));
493			send_passt(IP_DEFLAB, ip->ip_lbl + lmin);
494			break;
495
496		case IP_ASM:
497			send_passt(IP_ASM, ip->ip_asm);
498			break;
499
500		case IP_REF:
501			inline_ref((struct symtab *)ip->ip_name);
502			break;
503
504		default:
505			cerror("bad inline stmt %d", ip->type);
506		}
507	}
508	SDEBUG(("last label %d to %d\n", ip->ip_lbl, ip->ip_lbl + lmin));
509	send_passt(IP_DEFLAB, ip->ip_lbl + lmin);
510
511	branch(l2);
512	plabel(l0);
513
514	rp = block(GOTO, bcon(l1), NIL, INT, 0, 0);
515	if (is->retval)
516		p = tempnode(is->retval + toff, DECREF(sp->stype),
517		    sp->sdf, sp->sap);
518	else
519		p = bcon(0);
520	rp = buildtree(COMOP, rp, p);
521
522	if (is->nargs) {
523		p = mnode(&is->nt[is->nargs-1], ap);
524		rp = buildtree(COMOP, p, rp);
525	}
526
527	tfree(f);
528	return rp;
529}
530
531void
532inline_args(struct symtab **sp, int nargs)
533{
534	union arglist *al;
535	struct istat *cf;
536	TWORD t;
537	int i;
538
539	SDEBUG(("inline_args\n"));
540	cf = cifun;
541	/*
542	 * First handle arguments.  We currently do not inline anything if:
543	 * - function has varargs
544	 * - function args are volatile, checked if no temp node is asg'd.
545	 */
546	/* XXX - this is ugly, invent something better */
547	if (cf->sp->sdf->dfun == NULL)
548		return; /* no prototype */
549	for (al = cf->sp->sdf->dfun; al->type != TNULL; al++) {
550		t = al->type;
551		if (t == TELLIPSIS)
552			return; /* cannot inline */
553		if (ISSOU(BTYPE(t)))
554			al++;
555		for (; t > BTMASK; t = DECREF(t))
556			if (ISARY(t) || ISFTN(t))
557				al++;
558	}
559
560	if (nargs) {
561		for (i = 0; i < nargs; i++)
562			if ((sp[i]->sflags & STNODE) == 0)
563				return; /* not temporary */
564		cf->nt = permalloc(sizeof(struct ntds)*nargs);
565		for (i = 0; i < nargs; i++) {
566			cf->nt[i].temp = sp[i]->soffset;
567			cf->nt[i].type = sp[i]->stype;
568			cf->nt[i].df = sp[i]->sdf;
569			cf->nt[i].attr = sp[i]->sap;
570		}
571	}
572	cf->nargs = nargs;
573	cf->flags |= CANINL;
574}
575