1 /** 2 * Function inliner. 3 * 4 * This is meant to replace the previous inliner, which inlined the front end AST. 5 * This inlines based on the intermediate code, after it is optimized, 6 * which is simpler and presumably can inline more functions. 7 * It does not yet have full functionality, 8 * - it does not inline expressions with string literals in them, as these get turned into 9 * local symbols which cannot be referenced from another object file 10 * - exception handling code for Win32 is not inlined 11 * - it does not give warnings for failed attempts at inlining pragma(inline, true) functions 12 * - it can only inline functions that have already been compiled 13 * - it cannot inline statements 14 * 15 * Compiler implementation of the 16 * $(LINK2 https://www.dlang.org, D programming language). 17 * 18 * Copyright: Copyright (C) 2022-2023 by The D Language Foundation, All Rights Reserved 19 * Some parts based on an inliner from the Digital Mars C compiler. 20 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 21 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 22 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/backend/inliner.d, backend/inliner.d) 23 */ 24 25 // C++ specific routines 26 27 module dmd.backend.inliner; 28 29 version (MARS) 30 { 31 32 import core.stdc.stdio; 33 import core.stdc.ctype; 34 import core.stdc.string; 35 import core.stdc.stdlib; 36 37 import dmd.backend.cdef; 38 import dmd.backend.cc; 39 import dmd.backend.el; 40 import dmd.backend.global; 41 import dmd.backend.oper; 42 import dmd.backend.symtab; 43 import dmd.backend.ty; 44 import dmd.backend.type; 45 46 import dmd.backend.barray; 47 import dmd.backend.dlist; 48 49 nothrow: 50 51 private enum log = false; 52 private enum log2 = false; 53 54 /********************************** 55 * Determine if function can be inline'd. 56 * Params: 57 * sfunc = function to check 58 * Returns: 59 * true if sfunc can be inline'd. 60 */ 61 62 bool canInlineFunction(Symbol *sfunc) 63 { 64 if (log) printf("canInlineFunction(%s)\n",sfunc.Sident.ptr); 65 auto f = sfunc.Sfunc; 66 auto t = sfunc.Stype; 67 assert(f && tyfunc(t.Tty)); 68 69 bool result = false; 70 if (!(config.flags & CFGnoinlines) && /* if inlining is turned on */ 71 f.Fflags & Finline && 72 /* Cannot inline varargs or unprototyped functions */ 73 (t.Tflags & (TFfixed | TFprototype)) == (TFfixed | TFprototype) && 74 !(t.Tty & mTYimport) // do not inline imported functions 75 ) 76 { 77 auto b = f.Fstartblock; 78 if (!b) 79 return false; 80 if (config.ehmethod == EHmethod.EH_WIN32 && !(f.Fflags3 & Feh_none)) 81 return false; // not working properly, so don't inline it 82 83 static if (1) // enable for the moment 84 while (b.BC == BCgoto && b.Bnext == b.nthSucc(0) && canInlineExpression(b.Belem)) 85 b = b.Bnext; 86 87 switch (b.BC) 88 { case BCret: 89 if (tybasic(t.Tnext.Tty) != TYvoid 90 && !(f.Fflags & (Fctor | Fdtor | Finvariant)) 91 ) 92 { // Message about no return value 93 // should already have been generated 94 break; 95 } 96 goto case BCretexp; 97 98 case BCretexp: 99 if (b.Belem) 100 { 101 result = canInlineExpression(b.Belem); 102 if (log && !result) printf("not inlining function %s\n", sfunc.Sident.ptr); 103 } 104 break; 105 106 default: 107 break; 108 } 109 110 foreach (s; f.Flocsym[]) 111 { 112 assert(s); 113 if (s.Sclass == SC.bprel) 114 return false; 115 } 116 } 117 if (!result) 118 f.Fflags &= ~Finline; 119 if (log) printf("returns: %d\n",result); 120 return result; 121 } 122 123 /************************** 124 * Examine all of the function calls in sfunc, and inline-expand 125 * any that can be. 126 * Params: 127 * sfunc = function to scan 128 */ 129 130 void scanForInlines(Symbol *sfunc) 131 { 132 if (log) printf("scanForInlines(%s)\n",prettyident(sfunc)); 133 //symbol_debug(sfunc); 134 func_t* f = sfunc.Sfunc; 135 assert(f && tyfunc(sfunc.Stype.Tty)); 136 // BUG: flag not set right in dmd 137 if (1 || f.Fflags3 & Fdoinline) // if any inline functions called 138 { 139 f.Fflags |= Finlinenest; 140 foreach (b; BlockRange(startblock)) 141 if (b.Belem) 142 { 143 //elem_print(b.Belem); 144 b.Belem = scanExpressionForInlines(b.Belem); 145 } 146 if (eecontext.EEelem) 147 { 148 const marksi = globsym.length; 149 eecontext.EEelem = scanExpressionForInlines(eecontext.EEelem); 150 eecontext_convs(marksi); 151 } 152 f.Fflags &= ~Finlinenest; 153 } 154 } 155 156 /************************************************* private *********************************/ 157 158 private: 159 160 /**************************************** 161 * Can this expression be inlined? 162 * Params: 163 * e = expression 164 * Returns: 165 * true if it can be inlined 166 */ 167 bool canInlineExpression(elem* e) 168 { 169 if (!e) 170 return true; 171 while (1) 172 { 173 if (OTleaf(e.Eoper)) 174 { 175 if (e.Eoper == OPvar || e.Eoper == OPrelconst) 176 { 177 /* Statics cannot be accessed from a different object file, 178 * so the reference will fail. 179 */ 180 if (e.EV.Vsym.Sclass == SC.locstat || e.EV.Vsym.Sclass == SC.static_) 181 { 182 if (log) printf("not inlining due to %s\n", e.EV.Vsym.Sident.ptr); 183 return false; 184 } 185 } 186 else if (e.Eoper == OPasm) 187 return false; 188 return true; 189 } 190 else if (OTunary(e.Eoper)) 191 { 192 e = e.EV.E1; 193 continue; 194 } 195 else 196 { 197 if (!canInlineExpression(e.EV.E1)) 198 return false; 199 e = e.EV.E2; 200 continue; 201 } 202 } 203 } 204 205 206 /********************************************* 207 * Walk the elems, looking for function calls we can inline. 208 * Params: 209 * e = expression tree to walk 210 * Returns: 211 * replacement tree 212 */ 213 elem* scanExpressionForInlines(elem *e) 214 { 215 //printf("scanExpressionForInlines(%p)\n",e); 216 const op = e.Eoper; 217 if (OTbinary(op)) 218 { 219 e.EV.E1 = scanExpressionForInlines(e.EV.E1); 220 e.EV.E2 = scanExpressionForInlines(e.EV.E2); 221 if (op == OPcall) 222 e = tryInliningCall(e); 223 } 224 else if (OTunary(op)) 225 { 226 if (op == OPstrctor) // never happens in MARS 227 { 228 elem* e1 = e.EV.E1; 229 while (e1.Eoper == OPcomma) 230 { 231 e1.EV.E1 = scanExpressionForInlines(e1.EV.E1); 232 e1 = e1.EV.E2; 233 } 234 if (e1.Eoper == OPcall && e1.EV.E1.Eoper == OPvar) 235 { // Never inline expand this function 236 237 // But do expand templates 238 Symbol* s = e1.EV.E1.EV.Vsym; 239 if (tyfunc(s.ty())) 240 { 241 // This function might be an inline template function that was 242 // never parsed. If so, parse it now. 243 if (s.Sfunc.Fbody) 244 { 245 //n2_instantiate_memfunc(s); 246 } 247 } 248 } 249 else 250 e1.EV.E1 = scanExpressionForInlines(e1.EV.E1); 251 e1.EV.E2 = scanExpressionForInlines(e1.EV.E2); 252 } 253 else 254 { 255 e.EV.E1 = scanExpressionForInlines(e.EV.E1); 256 if (op == OPucall) 257 { 258 e = tryInliningCall(e); 259 } 260 } 261 } 262 else /* leaf */ 263 { 264 // If deferred allocation of variable, allocate it now. 265 // The deferred allocations are done by cpp_initctor(). 266 if (0 && CPP && 267 (op == OPvar || op == OPrelconst)) 268 { 269 Symbol* s = e.EV.Vsym; 270 if (s.Sclass == SC.auto_ && 271 s.Ssymnum == SYMIDX.max) 272 { //dbg_printf("Deferred allocation of %p\n",s); 273 symbol_add(s); 274 275 if (tybasic(s.Stype.Tty) == TYstruct && 276 s.Stype.Ttag.Sstruct.Sdtor && 277 !(s.Sflags & SFLnodtor)) 278 { 279 //enum DTORmostderived = 4; 280 //elem* eptr = el_ptr(s); 281 //elem* edtor = cpp_destructor(s.Stype,eptr,null,DTORmostderived); 282 //assert(edtor); 283 //edtor = scanExpressionForInlines(edtor); 284 //cpp_stidtors.push(edtor); 285 } 286 } 287 if (tyfunc(s.ty())) 288 { 289 // This function might be an inline template function that was 290 // never parsed. If so, parse it now. 291 if (s.Sfunc.Fbody) 292 { 293 //n2_instantiate_memfunc(s); 294 } 295 } 296 } 297 } 298 return e; 299 } 300 301 /********************************** 302 * Inline-expand a function call if it can be. 303 * Params: 304 * e = OPcall or OPucall elem 305 * Returns: 306 * replacement tree. 307 */ 308 309 private elem* tryInliningCall(elem *e) 310 { 311 //elem_debug(e); 312 assert(e && (e.Eoper == OPcall || e.Eoper == OPucall)); 313 314 if (e.EV.E1.Eoper != OPvar) 315 return e; 316 317 // This is an explicit function call (not through a pointer) 318 Symbol* sfunc = e.EV.E1.EV.Vsym; 319 if (log) printf("tryInliningCall: %s, class = %d\n", prettyident(sfunc),sfunc.Sclass); 320 321 // sfunc may not be a function due to user's clever casting 322 if (!tyfunc(sfunc.Stype.Tty)) 323 return e; 324 325 /* If forward referencing an inline function, we'll have to 326 * write out the function when it eventually is defined 327 */ 328 if (!sfunc.Sfunc) // this can happen for rtlsym functions 329 { 330 } 331 else if (sfunc.Sfunc.Fstartblock == null) 332 { } //nwc_mustwrite(sfunc); 333 else 334 { func_t *f = sfunc.Sfunc; 335 336 /* Check to see if we inline expand the function, or queue */ 337 /* it to be output. */ 338 if ((f.Fflags & (Finline | Finlinenest)) == Finline) 339 e = inlineCall(e,sfunc); 340 else 341 { } //queue_func(sfunc); 342 } 343 344 return e; 345 } 346 347 /********************************** 348 * Inline expand a function call. 349 * Params: 350 * e = the OPcall or OPucall that calls sfunc, this gets free'd 351 * sfunc = function being called that gets inlined 352 * Returns: 353 * the expression replacing the function call 354 */ 355 356 private elem* inlineCall(elem *e,Symbol *sfunc) 357 { 358 if (debugc) 359 printf("inline %s\n", prettyident(sfunc)); 360 if (log) printf("inlineCall(e = %p, func %p = '%s')\n", e, sfunc, prettyident(sfunc)); 361 if (log2) { printf("before:\n"); elem_print(e); } 362 //symbol_debug(sfunc); 363 assert(e.Eoper == OPcall || e.Eoper == OPucall); 364 func_t* f = sfunc.Sfunc; 365 366 // Declare all of sfunc's local symbols as symbols in globsym 367 const sistart = globsym.length; // where func's local symbols start 368 foreach (s; f.Flocsym[]) 369 { 370 assert(s); 371 //if (!s) 372 // continue; 373 //symbol_debug(s); 374 auto sc = s.Sclass; 375 switch (sc) 376 { 377 case SC.parameter: 378 case SC.fastpar: 379 case SC.shadowreg: 380 sc = SC.auto_; 381 goto L1; 382 case SC.regpar: 383 sc = SC.register; 384 goto L1; 385 case SC.register: 386 case SC.auto_: 387 case SC.pseudo: 388 L1: 389 { 390 //printf(" new symbol %s\n", s.Sident.ptr); 391 Symbol* snew = symbol_copy(s); 392 snew.Sclass = sc; 393 snew.Sfl = FLauto; 394 snew.Sflags |= SFLfree; 395 snew.Srange = null; 396 s.Sflags |= SFLreplace; 397 if (sc == SC.pseudo) 398 { 399 snew.Sfl = FLpseudo; 400 snew.Sreglsw = s.Sreglsw; 401 } 402 s.Ssymnum = symbol_add(snew); 403 break; 404 } 405 case SC.global: 406 case SC.static_: 407 break; 408 default: 409 //fprintf(stderr, "Sclass = %d\n", sc); 410 symbol_print(s); 411 assert(0); 412 } 413 } 414 415 static if (0) 416 foreach (i, s; globsym[]) 417 { 418 if (i == sistart) 419 printf("---\n"); 420 printf("[%d] %s %s\n", cast(int)i, s.Sident.ptr, tym_str(s.Stype.Tty)); 421 } 422 423 /* Create duplicate of function elems 424 */ 425 elem* ec; 426 for (block* b = f.Fstartblock; b; b = b.Bnext) 427 { 428 ec = el_combine(ec, el_copytree(b.Belem)); 429 } 430 431 /* Walk the copied tree, replacing references to the old 432 * variables with references to the new 433 */ 434 if (ec) 435 { 436 adjustExpression(ec); 437 if (config.flags3 & CFG3eh && 438 (eecontext.EEin || 439 f.Fflags3 & Fmark || // if mark/release around function expansion 440 f.Fflags & Fctor)) 441 { 442 elem* em = el_calloc(); 443 em.Eoper = OPmark; 444 //el_settype(em,tstypes[TYvoid]); 445 ec = el_bin(OPinfo,ec.Ety,em,ec); 446 } 447 } 448 449 /* Initialize the parameter variables with the argument list 450 */ 451 if (e.Eoper == OPcall) 452 { 453 elem* eargs = initializeParamsWithArgs(e.EV.E2, sistart, globsym.length); 454 ec = el_combine(eargs,ec); 455 } 456 457 if (ec) 458 { 459 ec.Esrcpos = e.Esrcpos; // save line information 460 f.Fflags |= Finlinenest; // prevent recursive inlining 461 ec = scanExpressionForInlines(ec); // look for more cases 462 f.Fflags &= ~Finlinenest; 463 } 464 else 465 ec = el_long(TYint,0); 466 el_free(e); // dump function call 467 if (log2) { printf("after:\n"); elem_print(ec); } 468 return ec; 469 } 470 471 /**************************** 472 * Evaluate the argument list, putting in initialization statements to the 473 * local parameters. If there are more arguments than parameters, 474 * evaluate the remaining arguments for side effects only. 475 * Params: 476 * eargs = argument tree 477 * sistart = starting index in globsym[] of the inlined function's parameters 478 * Returns: 479 * expression representing the argument list 480 */ 481 482 private elem* initializeParamsWithArgs(elem* eargs, SYMIDX sistart, SYMIDX siend) 483 { 484 /* Create args[] and fill it with the arguments 485 */ 486 const nargs = el_nparams(eargs); 487 assert(nargs < size_t.max / (2 * (elem *).sizeof)); // conservative overflow check 488 elem*[] args = (cast(elem **)malloc(nargs * (elem *).sizeof))[0 .. nargs]; 489 elem **tmp = args.ptr; 490 el_paramArray(&tmp, eargs); 491 492 elem* ecopy; 493 494 auto si = sistart; 495 for (size_t n = args.length; n; --n) 496 { 497 elem* e = args[n - 1]; 498 499 if (e.Eoper == OPstrpar) 500 e = e.EV.E1; 501 502 /* Look for and return next parameter Symbol 503 */ 504 Symbol* nextSymbol(ref SYMIDX si) 505 { 506 while (1) 507 { 508 if (si == siend) 509 return null; 510 511 Symbol* s = globsym[si]; 512 ++si; 513 // SCregpar was turned into SCregister, SCparameter to SCauto 514 if (s.Sclass == SC.register || s.Sclass == SC.auto_) 515 return s; 516 } 517 } 518 519 Symbol *s = nextSymbol(si); 520 if (!s) 521 { 522 ecopy = el_combine(el_copytree(e), ecopy); // for ... arguments 523 continue; 524 } 525 526 //printf("Param[%d] %s %s\n", cast(int)cast(int)si, s.Sident.ptr, tym_str(s.Stype.Tty)); 527 //elem_print(e); 528 if (e.Eoper == OPstrctor) 529 { 530 ecopy = el_combine(el_copytree(e.EV.E1), ecopy); // skip the OPstrctor 531 e = ecopy; 532 //while (e.Eoper == OPcomma) 533 // e = e.EV.E2; 534 debug 535 { 536 if (e.Eoper != OPcall && e.Eoper != OPcond) 537 elem_print(e); 538 } 539 assert(e.Eoper == OPcall || e.Eoper == OPcond || e.Eoper == OPinfo); 540 //exp2_setstrthis(e,s,0,ecopy.ET); 541 continue; 542 } 543 544 /* s is the parameter, e is the argument, s = e 545 */ 546 const szs = type_size(s.Stype); 547 const sze = getSize(e); 548 549 if (szs * 2 == sze && szs == REGSIZE()) // s got SROA'd into 2 slices 550 { 551 if (log) printf("detected slice with %s\n", s.Sident.ptr); 552 auto s2 = nextSymbol(si); 553 if (!s2) { symbol_print(s); elem_print(e); assert(0); } 554 assert(szs == type_size(s2.Stype)); 555 const ty = s.Stype.Tty; 556 557 elem* ex; 558 e = el_copytree(e); // copy argument 559 if (e.Eoper != OPvar) 560 { 561 elem* ec = exp2_copytotemp(e); 562 e = ec.EV.E2; 563 ex = ec.EV.E1; 564 ec.EV.E1 = null; 565 ec.EV.E2 = null; 566 el_free(ec); 567 e.EV.Vsym.Sfl = FLauto; 568 } 569 assert(e.Eoper == OPvar); 570 elem* e2 = el_copytree(e); 571 e.EV.Voffset += 0; 572 e2.EV.Voffset += szs; 573 e.Ety = ty; 574 e2.Ety = ty; 575 elem* elo = el_bin(OPeq, ty, el_var(s), e); 576 elem* ehi = el_bin(OPeq, ty, el_var(s2), e2); 577 if (tybasic(ty) == TYstruct || tybasic(ty) == TYarray) 578 { 579 elo.Eoper = OPstreq; 580 ehi.Eoper = OPstreq; 581 elo.ET = s.Stype; 582 ehi.ET = s.Stype; 583 } 584 ex = el_combine(ex, elo); 585 ex = el_combine(ex, ehi); 586 587 ecopy = el_combine(ex, ecopy); 588 continue; 589 } 590 591 if (sze * 2 == szs && szs == 2 * REGSIZE() && n >= 2) 592 { 593 /* This happens when elparam() splits an OPpair into 594 * two OPparams. Try to reverse this here 595 */ 596 elem* e2 = args[--n - 1]; 597 assert(getSize(e2) == sze); 598 e = el_bin(OPpair, s.Stype.Tty, e, e2); 599 } 600 601 // s = e; 602 elem* evar = el_var(s); 603 elem* ex = el_copytree(e); 604 auto ty = tybasic(ex.Ety); 605 if (szs == 3) 606 { 607 ty = TYstruct; 608 } 609 else if (szs < sze && sze == 4) 610 { 611 // e got promoted to int 612 ex = el_una(OP32_16, TYshort, ex); 613 ty = TYshort; 614 if (szs == 1) 615 { 616 ex = el_una(OP16_8, TYchar, ex); 617 ty = TYchar; 618 } 619 } 620 evar.Ety = ty; 621 auto eeq = el_bin(OPeq,ty,evar,ex); 622 // If struct copy 623 if (tybasic(eeq.Ety) == TYstruct || tybasic(eeq.Ety) == TYarray) 624 { 625 eeq.Eoper = OPstreq; 626 eeq.ET = s.Stype; 627 } 628 //el_settype(evar,ecopy.ET); 629 630 ecopy = el_combine(eeq, ecopy); 631 continue; 632 } 633 free(args.ptr); 634 return ecopy; 635 } 636 637 /********************************* 638 * Replace references to old symbols with references to copied symbols. 639 */ 640 641 private void adjustExpression(elem *e) 642 { 643 while (1) 644 { 645 assert(e); 646 //elem_debug(e); 647 //dbg_printf("adjustExpression(%p) ",e);WROP(e.Eoper);dbg_printf("\n"); 648 // the debugger falls over on debugging inlines 649 if (configv.addlinenumbers) 650 e.Esrcpos.Slinnum = 0; // suppress debug info for inlines 651 if (!OTleaf(e.Eoper)) 652 { 653 if (OTbinary(e.Eoper)) 654 adjustExpression(e.EV.E2); 655 else 656 assert(!e.EV.E2); 657 e = e.EV.E1; 658 } 659 else 660 { 661 if (e.Eoper == OPvar || e.Eoper == OPrelconst) 662 { 663 Symbol *s = e.EV.Vsym; 664 665 if (s.Sflags & SFLreplace) 666 { 667 e.EV.Vsym = globsym[s.Ssymnum]; 668 //printf(" replacing %p %s\n", e, s.Sident.ptr); 669 } 670 } 671 break; 672 } 673 } 674 } 675 676 /****************************************** 677 * Get size of an elem e. 678 */ 679 private int getSize(const(elem)* e) 680 { 681 int sz = tysize(e.Ety); 682 if (sz == -1 && e.ET && (tybasic(e.Ety) == TYstruct || tybasic(e.Ety) == TYarray)) 683 sz = cast(int)type_size(e.ET); 684 return sz; 685 } 686 687 }