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