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