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