<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=utf-8">
  </head>
  <body text="#000000" bgcolor="#FFFFFF">
    Hi all,<br>
    <br>
    inspired by Kit's concept for pure functions, I had a deeper look at
    what inline functions (and more importantly, the nodes involved in
    expanding them) can do.<br>
    Let me share my proof-of-concept (which may very well become a
    'poof-of-concept'...). You can find my current working branch <a
href="https://github.com/martok/freepascal/compare/master...dev_inlining"
      moz-do-not-send="true">here</a>.<br>
    <br>
    Instead of marking a function as 'pure' at declaration, I took
    another way: make normal inlining firstpass flexible enough that any
    "accidentally" pure function can be collapsed at the call site.<br>
    As an extension, I then propose a new intrinsic:<br>
    <blockquote><tt>function ConstExpr(X: AnyType): AnyType;<br>
      </tt>If the expression X can be reduced to a constant, the node
      simplifies to that constant.<br>
      Otherwise, fail compilation with E3203 Illegal Expression.<br>
    </blockquote>
    ConstExpr is basically pass-through for most expressions, but it
    allows us to "call" functions <i>at parse time</i>, as long as
    these functions are pure and the arguments are constant at the point
    of declaration.<br>
    <br>
    Let me illustrate with an example:<br>
    <blockquote><font size="-1"><tt>program tinline3;</tt><tt><br>
        </tt></font>
      <font size="-1"><tt><br>
        </tt><tt>
            function sinc(x: Real): Real; inline;</tt><tt><br>
        </tt><tt>
            begin</tt><tt><br>
        </tt><tt>
              if X = 0 then</tt><tt><br>
        </tt><tt>
                Result:= 1</tt><tt><br>
        </tt><tt>
              else</tt><tt><br>
        </tt><tt>
                Result:= sin(x) / x;</tt><tt><br>
        </tt><tt>
            end;</tt><tt><br>
        </tt></font>
      <font size="-1"><tt><br>
        </tt><tt>
          const</tt><tt><br>
        </tt><tt>
            s1 = constexpr(sinc(0.3));</tt><tt><br>
        </tt><tt>
            s2 = constexpr(sinc(0));</tt><tt><br>
        </tt><tt>
          var</tt><tt><br>
        </tt><tt>
            u, v: Real;</tt><tt><br>
        </tt><tt>
          begin</tt><tt><br>
        </tt><tt>
            u:= s1;</tt><tt><br>
        </tt><tt>
            u:= s2;</tt><tt><br>
        </tt><tt>
          end.</tt></font></blockquote>
    This gets processed into the node tree (right at parse time):<br>
    <blockquote><font size="-1"><tt>*******************************************************************************</tt><tt><br>
        </tt><tt>after parsing</tt><tt><br>
        </tt><tt>$main; Register;</tt><tt><br>
        </tt><tt>*******************************************************************************</tt><tt><br>
        </tt><tt>(blockn, resultdef = $void = "untyped", pos = (16,1),
          loc = LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx
          = 4</tt><tt><br>
        </tt><tt>   (statementn, resultdef = <nil>, pos = (17,9),
          loc = LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx
          = 4</tt><tt><br>
        </tt><tt>      (assignn, resultdef = $void = "untyped", pos =
          (17,3), loc = LOC_INVALID, expectloc = LOC_INVALID, flags =
          [], cmplx = 2</tt><tt><br>
        </tt><tt>         (loadn, resultdef = Real = "Double", pos =
          (17,3), loc = LOC_INVALID, expectloc = LOC_INVALID, flags =
          [nf_write], cmplx = 1</tt><tt><br>
        </tt><tt>            nil</tt><tt><br>
        </tt><tt>            symbol = U</tt><tt><br>
        </tt><tt>         )</tt><tt><br>
        </tt><tt>         (realconstn, resultdef = Real = "Double", pos
          = (17,7), loc = LOC_INVALID, expectloc = LOC_INVALID, flags =
          [], cmplx = 2</tt><tt><br>
        </tt><tt>            value =  9.85067355537798561294E-0001</tt><tt><br>
        </tt><tt>         )</tt><tt><br>
        </tt><tt>      )</tt><tt><br>
        </tt><tt><br>
        </tt><tt>   )</tt><tt><br>
        </tt><tt>   (statementn, resultdef = <nil>, pos = (18,9),
          loc = LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx
          = 2</tt><tt><br>
        </tt><tt>      (assignn, resultdef = $void = "untyped", pos =
          (18,3), loc = LOC_INVALID, expectloc = LOC_INVALID, flags =
          [], cmplx = 2</tt><tt><br>
        </tt><tt>         (loadn, resultdef = Real = "Double", pos =
          (18,3), loc = LOC_INVALID, expectloc = LOC_INVALID, flags =
          [nf_write], cmplx = 1</tt><tt><br>
        </tt><tt>            nil</tt><tt><br>
        </tt><tt>            symbol = U</tt><tt><br>
        </tt><tt>         )</tt><tt><br>
        </tt><tt>         (realconstn, resultdef = Real = "Double", pos
          = (18,7), loc = LOC_INVALID, expectloc = LOC_INVALID, flags =
          [], cmplx = 2</tt><tt><br>
        </tt><tt>            value =  1.00000000000000000000E+0000</tt><tt><br>
        </tt><tt>         )</tt><tt><br>
        </tt><tt>      )</tt><tt><br>
        </tt><tt><br>
        </tt><tt>   )</tt><tt><br>
        </tt><tt>)</tt></font><br>
    </blockquote>
    As you can see, the expressions have been simplified into (the
    correct) constants of type Double (Real for x86 is Double).
    Intermediate type conversions (including potential loss of
    FP-precision) are handled correctly (there's a sneaky
    int(0)<>real in the example).<br>
    This is already really useful for the most common application of
    macros with parameters in C: calculating constants (like <tt>#define
      _IOC(inout,group,num,len) (inout | ((len & IOCPARM_MASK)
      << 16) | ((group) << 8) | (num))</tt>).<br>
    <br>
    There are currently a few issues:<br>
    <ol>
      <li>I extended the const propagation optimisation pass a bit. It
        had restrictions on <tt>inlinen</tt> and <tt>realconstn</tt>
        that probably had a reason, but I can't see it...</li>
      <li><tt>calln</tt> now does an automatic internal <span
          class="blob-code-inner">optconstpropagate </span>pass when
        inlining. This is to carry refs to constant temps a bit further
        down. That might in fact be problematic because it could
        increase code size with multiple loads and should probably not
        be done in general. But for this PoC, that was the simplest way.</li>
      <li>procs can only be inlined if their body has been fully parsed
        before the call. This is actually kind of a problem because it
        limits usefulness for the C-style constant-macro application.</li>
      <li>The tricky part (same goes for pure functions) is actually
        finding out if parameters of an inlined function are constants.
        <span class="blob-code-inner">optconstpropagate can track that
          after the fact, but at that point the fun is already over. The
          best solution I could think of is running some very primitive
          DFA along while parsing, so that the firstpass can refer to
          that. Basically, if a symbol is known to be constant, loads of
          it could be treated as constants of its stored value for the
          purposes of simplify(). This is of course limited to only
          local symbols, but still doesn't seem very practical...<br>
        </span></li>
    </ol>
    So, it's far from perfect, but shows that there is some potential
    for metaprogramming without the need for much new complexity
    (numstat is +59-5 at the time of writing this). I suspect there is
    quite a lot that could be gained from better constant propagation in
    general: most nodes can already simplify themselves efficiently, but
    only if they know the arguments are constant.<br>
    <br>
    What do you think?<br>
    <br>
    Regards,<br>
    Martok<br>
    <br>
  </body>
</html>