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