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 */ 41static void printip(struct interpass *pole); 42 43struct 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 */ 53static 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 66static SLIST_HEAD(, istat) ipole = { NULL, &ipole.q_forw }; 67static 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 76int isinlining; 77int inlnodecnt, inlstatcnt; 78 79#define SZSI sizeof(struct istat) 80#define ialloc() memset(permalloc(SZSI), 0, SZSI); inlstatcnt++ 81 82static void 83tcnt(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 95static struct istat * 96findfun(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 106static void 107refnode(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 119void 120inline_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 */ 137void 138inline_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 */ 180void 181inline_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 */ 210void 211inline_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 236static void 237puto(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 */ 295void 296inline_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 316static void 317printip(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 358static int toff; 359 360static NODE * 361mnode(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 381static void 382rtmps(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 */ 396NODE * 397inlinetree(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 531void 532inline_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