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 }