xref: /netbsd-src/external/bsd/pcc/dist/pcc/cc/cxxcom/inline.c (revision 411dcbec990c8aa9c57d3bd2f4bcacadec0b1ab5)
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  */
41 static void printip(struct interpass *pole);
42 
43 struct 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  */
53 static 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 
66 static SLIST_HEAD(, istat) ipole = { NULL, &ipole.q_forw };
67 static 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 
76 int isinlining;
77 int inlnodecnt, inlstatcnt;
78 
79 #define	SZSI	sizeof(struct istat)
80 #define	ialloc() memset(permalloc(SZSI), 0, SZSI); inlstatcnt++
81 
82 static void
tcnt(NODE * p,void * arg)83 tcnt(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 
95 static struct istat *
findfun(struct symtab * sp)96 findfun(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 
106 static void
refnode(struct symtab * sp)107 refnode(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 
119 void
inline_addarg(struct interpass * ip)120 inline_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  */
137 void
inline_start(struct symtab * sp)138 inline_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  */
180 void
inline_end(void)181 inline_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  */
210 void
inline_ref(struct symtab * sp)211 inline_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 
236 static void
puto(struct istat * w)237 puto(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  */
295 void
inline_prtout(void)296 inline_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
316 static void
printip(struct interpass * pole)317 printip(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 
358 static int toff;
359 
360 static NODE *
mnode(struct ntds * nt,NODE * p)361 mnode(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 
381 static void
rtmps(NODE * p,void * arg)382 rtmps(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  */
396 NODE *
inlinetree(struct symtab * sp,NODE * f,NODE * ap)397 inlinetree(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 
531 void
inline_args(struct symtab ** sp,int nargs)532 inline_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