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