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 }