1 /*	$Id: inline.c,v 1.5 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;
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 static void
tcnt(NODE * p,void * arg)82 tcnt(NODE *p, void *arg)
83 {
84 	inlnodecnt++;
85 	if (nlabs > 1 && (p->n_op == REG || p->n_op == OREG) &&
86 	    regno(p) == FPREG)
87 		SLIST_FIRST(&ipole)->flags &= ~CANINL; /* no stack refs */
88 	if (p->n_op == NAME || p->n_op == ICON)
89 		p->n_sp = NULL; /* let symtabs be freed for inline funcs */
90 	if (ndebug)
91 		printf("locking node %p\n", p);
92 }
93 
94 static struct istat *
findfun(struct symtab * sp)95 findfun(struct symtab *sp)
96 {
97 	struct istat *is;
98 
99 	SLIST_FOREACH(is, &ipole, link)
100 		if (is->sp == sp)
101 			return is;
102 	return NULL;
103 }
104 
105 static void
refnode(struct symtab * sp)106 refnode(struct symtab *sp)
107 {
108 	struct interpass *ip;
109 
110 	SDEBUG(("refnode(%s)\n", sp->sname));
111 
112 	ip = permalloc(sizeof(*ip));
113 	ip->type = IP_REF;
114 	ip->ip_name = (char *)sp;
115 	inline_addarg(ip);
116 }
117 
118 void
inline_addarg(struct interpass * ip)119 inline_addarg(struct interpass *ip)
120 {
121 	extern NODE *cftnod;
122 
123 	SDEBUG(("inline_addarg(%p)\n", ip));
124 	DLIST_INSERT_BEFORE(&cifun->shead, ip, qelem);
125 	if (ip->type == IP_DEFLAB)
126 		nlabs++;
127 	if (ip->type == IP_NODE)
128 		walkf(ip->ip_node, tcnt, 0); /* Count as saved */
129 	if (cftnod)
130 		cifun->retval = regno(cftnod);
131 }
132 
133 /*
134  * Called to setup for inlining of a new function.
135  */
136 void
inline_start(struct symtab * sp)137 inline_start(struct symtab *sp)
138 {
139 	struct istat *is;
140 
141 	SDEBUG(("inline_start(\"%s\")\n", sp->sname));
142 
143 	if (isinlining)
144 		cerror("already inlining function");
145 
146 	if ((is = findfun(sp)) != 0) {
147 		if (!DLIST_ISEMPTY(&is->shead, qelem))
148 			uerror("inline function already defined");
149 	} else {
150 		is = ialloc();
151 		is->sp = sp;
152 		SLIST_INSERT_FIRST(&ipole, is, link);
153 		DLIST_INIT(&is->shead, qelem);
154 	}
155 	cifun = is;
156 	nlabs = 0;
157 	isinlining++;
158 }
159 
160 /*
161  * End of an inline function. In C99 an inline function declared "extern"
162  * should also have external linkage and are therefore printed out.
163  *
164  * Gcc inline syntax is a mess, see matrix below on emitting functions:
165  *		    without extern
166  *	-std=		-	gnu89	gnu99
167  *	gcc 3.3.5:	ja	ja	ja
168  *	gcc 4.1.3:	ja	ja	ja
169  *	gcc 4.3.1	ja	ja	nej
170  *
171  *		     with extern
172  *	gcc 3.3.5:	nej	nej	nej
173  *	gcc 4.1.3:	nej	nej	nej
174  *	gcc 4.3.1	nej	nej	ja
175  *
176  * The attribute gnu_inline sets gnu89 behaviour.
177  * Since pcc mimics gcc 4.3.1 that is the behaviour we emulate.
178  */
179 void
inline_end(void)180 inline_end(void)
181 {
182 	struct symtab *sp = cifun->sp;
183 
184 	SDEBUG(("inline_end()\n"));
185 
186 	if (sdebug)printip(&cifun->shead);
187 	isinlining = 0;
188 
189 #ifdef GCC_COMPAT
190 	if (sp->sclass != STATIC &&
191 	    (attr_find(sp->sap, GCC_ATYP_GNU_INLINE) || xgnu89)) {
192 		if (sp->sclass == EXTDEF)
193 			sp->sclass = 0;
194 		else
195 			sp->sclass = EXTDEF;
196 	}
197 #endif
198 	if (sp->sclass == EXTDEF) {
199 		cifun->flags |= REFD;
200 		inline_prtout();
201 	}
202 }
203 
204 /*
205  * Called when an inline function is found, to be sure that it will
206  * be written out.
207  * The function may not be defined when inline_ref() is called.
208  */
209 void
inline_ref(struct symtab * sp)210 inline_ref(struct symtab *sp)
211 {
212 	struct istat *w;
213 
214 	SDEBUG(("inline_ref(\"%s\")\n", sp->sname));
215 	if (sp->sclass == SNULL)
216 		return; /* only inline, no references */
217 	if (isinlining) {
218 		refnode(sp);
219 	} else {
220 		SLIST_FOREACH(w,&ipole, link) {
221 			if (w->sp != sp)
222 				continue;
223 			w->flags |= REFD;
224 			return;
225 		}
226 		/* function not yet defined, print out when found */
227 		w = ialloc();
228 		w->sp = sp;
229 		w->flags |= REFD;
230 		SLIST_INSERT_FIRST(&ipole, w, link);
231 		DLIST_INIT(&w->shead, qelem);
232 	}
233 }
234 
235 static void
puto(struct istat * w)236 puto(struct istat *w)
237 {
238 	struct interpass_prolog *ipp, *epp, *pp;
239 	struct interpass *ip, *nip;
240 	extern int crslab;
241 	int lbloff = 0;
242 
243 	/* Copy the saved function and print it out */
244 	ipp = 0; /* XXX data flow analysis */
245 	DLIST_FOREACH(ip, &w->shead, qelem) {
246 		switch (ip->type) {
247 		case IP_EPILOG:
248 		case IP_PROLOG:
249 			if (ip->type == IP_PROLOG) {
250 				ipp = (struct interpass_prolog *)ip;
251 				/* fix label offsets */
252 				lbloff = crslab - ipp->ip_lblnum;
253 			} else {
254 				epp = (struct interpass_prolog *)ip;
255 				crslab += (epp->ip_lblnum - ipp->ip_lblnum);
256 			}
257 			pp = tmpalloc(sizeof(struct interpass_prolog));
258 			memcpy(pp, ip, sizeof(struct interpass_prolog));
259 			pp->ip_lblnum += lbloff;
260 #ifdef PCC_DEBUG
261 			if (ip->type == IP_EPILOG && crslab != pp->ip_lblnum)
262 				cerror("puto: %d != %d", crslab, pp->ip_lblnum);
263 #endif
264 			pass2_compile((struct interpass *)pp);
265 			break;
266 
267 		case IP_REF:
268 			inline_ref((struct symtab *)ip->ip_name);
269 			break;
270 
271 		default:
272 			nip = tmpalloc(sizeof(struct interpass));
273 			*nip = *ip;
274 			if (nip->type == IP_NODE) {
275 				NODE *p;
276 
277 				p = nip->ip_node = ccopy(nip->ip_node);
278 				if (p->n_op == GOTO)
279 					p->n_left->n_lval += lbloff;
280 				else if (p->n_op == CBRANCH)
281 					p->n_right->n_lval += lbloff;
282 			} else if (nip->type == IP_DEFLAB)
283 				nip->ip_lbl += lbloff;
284 			pass2_compile(nip);
285 			break;
286 		}
287 	}
288 	w->flags |= WRITTEN;
289 }
290 
291 /*
292  * printout functions that are referenced.
293  */
294 void
inline_prtout(void)295 inline_prtout(void)
296 {
297 	struct istat *w;
298 	int gotone = 0;
299 
300 	SLIST_FOREACH(w, &ipole, link) {
301 		if ((w->flags & (REFD|WRITTEN)) == REFD &&
302 		    !DLIST_ISEMPTY(&w->shead, qelem)) {
303 			locctr(PROG, w->sp);
304 			defloc(w->sp);
305 			puto(w);
306 			w->flags |= WRITTEN;
307 			gotone++;
308 		}
309 	}
310 	if (gotone)
311 		inline_prtout();
312 }
313 
314 #if 1
315 static void
printip(struct interpass * pole)316 printip(struct interpass *pole)
317 {
318 	static char *foo[] = {
319 	   0, "NODE", "PROLOG", "STKOFF", "EPILOG", "DEFLAB", "DEFNAM", "ASM" };
320 	struct interpass *ip;
321 	struct interpass_prolog *ipplg, *epplg;
322 
323 	DLIST_FOREACH(ip, pole, qelem) {
324 		if (ip->type > MAXIP)
325 			printf("IP(%d) (%p): ", ip->type, ip);
326 		else
327 			printf("%s (%p): ", foo[ip->type], ip);
328 		switch (ip->type) {
329 		case IP_NODE: printf("\n");
330 #ifdef PCC_DEBUG
331 			fwalk(ip->ip_node, eprint, 0); break;
332 #endif
333 		case IP_PROLOG:
334 			ipplg = (struct interpass_prolog *)ip;
335 			printf("%s %s regs %lx autos %d mintemp %d minlbl %d\n",
336 			    ipplg->ipp_name, ipplg->ipp_vis ? "(local)" : "",
337 			    (long)ipplg->ipp_regs[0], ipplg->ipp_autos,
338 			    ipplg->ip_tmpnum, ipplg->ip_lblnum);
339 			break;
340 		case IP_EPILOG:
341 			epplg = (struct interpass_prolog *)ip;
342 			printf("%s %s regs %lx autos %d mintemp %d minlbl %d\n",
343 			    epplg->ipp_name, epplg->ipp_vis ? "(local)" : "",
344 			    (long)epplg->ipp_regs[0], epplg->ipp_autos,
345 			    epplg->ip_tmpnum, epplg->ip_lblnum);
346 			break;
347 		case IP_DEFLAB: printf(LABFMT "\n", ip->ip_lbl); break;
348 		case IP_DEFNAM: printf("\n"); break;
349 		case IP_ASM: printf("%s", ip->ip_asm); break;
350 		default:
351 			break;
352 		}
353 	}
354 }
355 #endif
356 
357 static int toff;
358 
359 static NODE *
mnode(struct ntds * nt,NODE * p)360 mnode(struct ntds *nt, NODE *p)
361 {
362 	NODE *q;
363 	int num = nt->temp + toff;
364 
365 	if (p->n_op == CM) {
366 		q = p->n_right;
367 		q = tempnode(num, nt->type, nt->df, nt->attr);
368 		nt--;
369 		p->n_right = buildtree(ASSIGN, q, p->n_right);
370 		p->n_left = mnode(nt, p->n_left);
371 		p->n_op = COMOP;
372 	} else {
373 		p = pconvert(p);
374 		q = tempnode(num, nt->type, nt->df, nt->attr);
375 		p = buildtree(ASSIGN, q, p);
376 	}
377 	return p;
378 }
379 
380 static void
rtmps(NODE * p,void * arg)381 rtmps(NODE *p, void *arg)
382 {
383 	if (p->n_op == TEMP)
384 		regno(p) += toff;
385 }
386 
387 /*
388  * Inline a function. Returns the return value.
389  * There are two major things that must be converted when
390  * inlining a function:
391  * - Label numbers must be updated with an offset.
392  * - The stack block must be relocated (add to REG or OREG).
393  * - Temporaries should be updated (but no must)
394  */
395 NODE *
inlinetree(struct symtab * sp,NODE * f,NODE * ap)396 inlinetree(struct symtab *sp, NODE *f, NODE *ap)
397 {
398 	extern int crslab, tvaloff;
399 	struct istat *is = findfun(sp);
400 	struct interpass *ip, *ipf, *ipl;
401 	int lmin, l0, l1, l2, gainl;
402 	NODE *p, *rp;
403 
404 	if (is == NULL || nerrors) {
405 		inline_ref(sp); /* prototype of not yet declared inline ftn */
406 		return NIL;
407 	}
408 
409 	SDEBUG(("inlinetree(%p,%p) OK %d\n", f, ap, is->flags & CANINL));
410 
411 #ifdef GCC_COMPAT
412 	gainl = attr_find(sp->sap, GCC_ATYP_ALW_INL) != NULL;
413 #else
414 	gainl = 0;
415 #endif
416 
417 	if ((is->flags & CANINL) == 0 && gainl)
418 		werror("cannot inline but always_inline");
419 
420 	if ((is->flags & CANINL) == 0 || (xinline == 0 && gainl == 0)) {
421 		if (is->sp->sclass == STATIC || is->sp->sclass == USTATIC)
422 			inline_ref(sp);
423 		return NIL;
424 	}
425 
426 	if (isinlining && cifun->sp == sp) {
427 		/* Do not try to inline ourselves */
428 		inline_ref(sp);
429 		return NIL;
430 	}
431 
432 #ifdef mach_i386
433 	if (kflag) {
434 		is->flags |= REFD; /* if static inline, emit */
435 		return NIL; /* XXX cannot handle hidden ebx arg */
436 	}
437 #endif
438 
439 	/* emit jumps to surround inline function */
440 	branch(l0 = getlab());
441 	plabel(l1 = getlab());
442 	l2 = getlab();
443 	SDEBUG(("branch labels %d,%d,%d\n", l0, l1, l2));
444 
445 	ipf = DLIST_NEXT(&is->shead, qelem); /* prolog */
446 	ipl = DLIST_PREV(&is->shead, qelem); /* epilog */
447 
448 	/* Fix label & temp offsets */
449 #define	IPP(x) ((struct interpass_prolog *)x)
450 	SDEBUG(("pre-offsets crslab %d tvaloff %d\n", crslab, tvaloff));
451 	lmin = crslab - IPP(ipf)->ip_lblnum;
452 	crslab += (IPP(ipl)->ip_lblnum - IPP(ipf)->ip_lblnum) + 1;
453 	toff = tvaloff - IPP(ipf)->ip_tmpnum;
454 	tvaloff += (IPP(ipl)->ip_tmpnum - IPP(ipf)->ip_tmpnum) + 1;
455 	SDEBUG(("offsets crslab %d lmin %d tvaloff %d toff %d\n",
456 	    crslab, lmin, tvaloff, toff));
457 
458 	/* traverse until first real label */
459 	ipf = DLIST_NEXT(ipf, qelem);
460 	do
461 		ipf = DLIST_NEXT(ipf, qelem);
462 	while (ipf->type != IP_DEFLAB);
463 
464 	/* traverse backwards to last label */
465 	do
466 		ipl = DLIST_PREV(ipl, qelem);
467 	while (ipl->type != IP_DEFLAB);
468 
469 	/* So, walk over all statements and emit them */
470 	for (ip = ipf; ip != ipl; ip = DLIST_NEXT(ip, qelem)) {
471 		switch (ip->type) {
472 		case IP_NODE:
473 			p = ccopy(ip->ip_node);
474 			if (p->n_op == GOTO)
475 				p->n_left->n_lval += lmin;
476 			else if (p->n_op == CBRANCH)
477 				p->n_right->n_lval += lmin;
478 			walkf(p, rtmps, 0);
479 #ifdef PCC_DEBUG
480 			if (sdebug) {
481 				printf("converted node\n");
482 				fwalk(ip->ip_node, eprint, 0);
483 				fwalk(p, eprint, 0);
484 			}
485 #endif
486 			send_passt(IP_NODE, p);
487 			break;
488 
489 		case IP_DEFLAB:
490 			SDEBUG(("converted label %d to %d\n",
491 			    ip->ip_lbl, ip->ip_lbl + lmin));
492 			send_passt(IP_DEFLAB, ip->ip_lbl + lmin);
493 			break;
494 
495 		case IP_ASM:
496 			send_passt(IP_ASM, ip->ip_asm);
497 			break;
498 
499 		case IP_REF:
500 			inline_ref((struct symtab *)ip->ip_name);
501 			break;
502 
503 		default:
504 			cerror("bad inline stmt %d", ip->type);
505 		}
506 	}
507 	SDEBUG(("last label %d to %d\n", ip->ip_lbl, ip->ip_lbl + lmin));
508 	send_passt(IP_DEFLAB, ip->ip_lbl + lmin);
509 
510 	branch(l2);
511 	plabel(l0);
512 
513 	rp = block(GOTO, bcon(l1), NIL, INT, 0, 0);
514 	if (is->retval)
515 		p = tempnode(is->retval + toff, DECREF(sp->stype),
516 		    sp->sdf, sp->sap);
517 	else
518 		p = bcon(0);
519 	rp = buildtree(COMOP, rp, p);
520 
521 	if (is->nargs) {
522 		p = mnode(&is->nt[is->nargs-1], ap);
523 		rp = buildtree(COMOP, p, rp);
524 	}
525 
526 	tfree(f);
527 	return rp;
528 }
529 
530 void
inline_args(struct symtab ** sp,int nargs)531 inline_args(struct symtab **sp, int nargs)
532 {
533 	union arglist *al;
534 	struct istat *cf;
535 	TWORD t;
536 	int i;
537 
538 	SDEBUG(("inline_args\n"));
539 	cf = cifun;
540 	/*
541 	 * First handle arguments.  We currently do not inline anything if:
542 	 * - function has varargs
543 	 * - function args are volatile, checked if no temp node is asg'd.
544 	 */
545 	/* XXX - this is ugly, invent something better */
546 	if (cf->sp->sdf->dfun == NULL)
547 		return; /* no prototype */
548 	for (al = cf->sp->sdf->dfun; al->type != TNULL; al++) {
549 		t = al->type;
550 		if (t == TELLIPSIS)
551 			return; /* cannot inline */
552 		if (ISSOU(BTYPE(t)))
553 			al++;
554 		for (; t > BTMASK; t = DECREF(t))
555 			if (ISARY(t) || ISFTN(t))
556 				al++;
557 	}
558 
559 	if (nargs) {
560 		for (i = 0; i < nargs; i++)
561 			if ((sp[i]->sflags & STNODE) == 0)
562 				return; /* not temporary */
563 		cf->nt = permalloc(sizeof(struct ntds)*nargs);
564 		for (i = 0; i < nargs; i++) {
565 			cf->nt[i].temp = sp[i]->soffset;
566 			cf->nt[i].type = sp[i]->stype;
567 			cf->nt[i].df = sp[i]->sdf;
568 			cf->nt[i].attr = sp[i]->sap;
569 		}
570 	}
571 	cf->nargs = nargs;
572 	cf->flags |= CANINL;
573 }
574