Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2018-07

Compile time functions

20 messages · 6 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Martok

Hi all,

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. Let me share my proof-of-concept (which may very well become a 'poof-of-concept'...). You can find my current working branch here <https://github.com/martok/freepascal/compare/master...dev_inlining>.

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.
As an extension, I then propose a new intrinsic:

    function ConstExpr(X: AnyType): AnyType;
    If the expression X can be reduced to a constant, the node simplifies to
    that constant.
    Otherwise, fail compilation with E3203 Illegal Expression.

ConstExpr is basically pass-through for most expressions, but it allows us to "call" functions /at parse time/, as long as these functions are pure and the arguments are constant at the point of declaration.

Let me illustrate with an example:

    program tinline3;
      function sinc(x: Real): Real; inline;
      begin
        if X = 0 then
          Result:= 1
        else
          Result:= sin(x) / x;
      end;
    const
      s1 = constexpr(sinc(0.3));
      s2 = constexpr(sinc(0));
    var
      u, v: Real;
    begin
      u:= s1;
      u:= s2;
    end.

This gets processed into the node tree (right at parse time):

    *******************************************************************************
    after parsing
    $main; Register;
    *******************************************************************************
    (blockn, resultdef = $void = "untyped", pos = (16,1), loc = LOC_INVALID,
    expectloc = LOC_INVALID, flags = [], cmplx = 4
       (statementn, resultdef = <nil>, pos = (17,9), loc = LOC_INVALID,
    expectloc = LOC_INVALID, flags = [], cmplx = 4
          (assignn, resultdef = $void = "untyped", pos = (17,3), loc =
    LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx = 2
             (loadn, resultdef = Real = "Double", pos = (17,3), loc =
    LOC_INVALID, expectloc = LOC_INVALID, flags = [nf_write], cmplx = 1
                nil
                symbol = U
             )
             (realconstn, resultdef = Real = "Double", pos = (17,7), loc =
    LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx = 2
                value =  9.85067355537798561294E-0001
             )
          )
       )
       (statementn, resultdef = <nil>, pos = (18,9), loc = LOC_INVALID,
    expectloc = LOC_INVALID, flags = [], cmplx = 2
          (assignn, resultdef = $void = "untyped", pos = (18,3), loc =
    LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx = 2
             (loadn, resultdef = Real = "Double", pos = (18,3), loc =
    LOC_INVALID, expectloc = LOC_INVALID, flags = [nf_write], cmplx = 1
                nil
                symbol = U
             )
             (realconstn, resultdef = Real = "Double", pos = (18,7), loc =
    LOC_INVALID, expectloc = LOC_INVALID, flags = [], cmplx = 2
                value =  1.00000000000000000000E+0000
             )
          )
       )
    )

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).
This is already really useful for the most common application of macros with parameters in C: calculating constants (like #define _IOC(inout,group,num,len) (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num))).

There are currently a few issues:

1. I extended the const propagation optimisation pass a bit. It had restrictions on inlinen and realconstn that probably had a reason, but I can't see it...
2. calln now does an automatic internal optconstpropagate 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.
3. 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.
4. The tricky part (same goes for pure functions) is actually finding out if parameters of an inlined function are constants. 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...

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.

What do you think?

Regards,
Martok

Reply by email

Sven Barth
to Martok

Martok schrieb am Fr., 20. Juli 2018, 23:20:

What do you think?

The main problem is that not all functions that would be eligible for your approach are also declared as inline thus their node trees would not be stored inside the PPU and thus you could not work with them. Additional benefit of the "pure" modifier: the compiler can check once the method has been parsed whether it's pure or not and can thus error out in the latter case.

Regards,
Sven

Reply by email

R0b0t1
to Sven Barth
Sven Barth wrote 13 lines
Martok schrieb am Fr., 20. Juli 2018, 23:20: > > > What do you think? The main problem is that not all functions that would be eligible for your approach are also declared as inline thus their node trees would not be stored inside the PPU and thus you could not work with them. Additional benefit of the "pure" modifier: the compiler can check once the method has been parsed whether it's pure or not and can thus error out in the latter case.

How does the existing constprop optimization relate to the pure modifier? I apologize if this was already covered.

Reply by email

Martok
to Sven Barth: Martok schrieb am Fr., 20. Juli 2018, 23:20: The main problem is that not all functions…
Sven Barth wrote
The main problem is that not all functions that would be eligible for your approach are also declared as inline thus their node trees would not be stored inside the PPU and thus you could not work with them.

I'm not sure I understand. Isn't the same true, with a new feature? The full node tree must be available, which basically means "has inlineinfo". Doesn't it?

Additional benefit of the "pure" modifier: the compiler can check once the method has been parsed whether it's pure or not and can thus error out in the latter case.

That is true, it is a more explicit declaration of intent. Maybe the codegen of pure can simply be implemented as generating inline info, but always replacing the calls with the simplified version. If it is already known that a function is pure, what I did with constexpr() would basically be guaranteed to work.

Regards, Martok

Reply by email

Sven Barth
to Martok

Martok schrieb am Sa., 21. Juli 2018, 17:12:

Show quoted text 11 lines
Am 21.07.2018 um 02:08 schrieb Sven Barth via fpc-devel: > The main problem is that not all functions that would be eligible for your > approach are also declared as inline thus their node trees would not be stored > inside the PPU and thus you could not work with them. I'm not sure I understand. Isn't the same true, with a new feature? The full node tree must be available, which basically means "has inlineinfo". Doesn't it?

Correct.

Show quoted text 14 lines
> Additional benefit of the "pure" modifier: the compiler can check once the > method has been parsed whether it's pure or not and can thus error out in the > latter case. That is true, it is a more explicit declaration of intent. Maybe the codegen of pure can simply be implemented as generating inline info, but always replacing the calls with the simplified version. If it is already known that a function is pure, what I did with constexpr() would basically be guaranteed to work.

Pure functions would still have an ordinary generated body as there is no reason that their address shouldn't be able to be taken (otherwise one needs to declare wrappers for them). Other than that I personally favor the explicit declaration of intent for such functions without the need for a ConstExpr intrinsic.

Regards,
Sven

Reply by email

J. Gareth Moreton

An interesting read. If you can collapse an inline function into a single assignment in most situations, then brilliant! Definitely keep that optimisation in.

While there are many similarities and prerequisites between an inline function and a pure function, and many simple mathematical functions could easily carry both modifiers, they are not exactly the same. Pure functions cannot be evaluated at compile time if one of the arguments is a variable (or at the very least, not deterministic), but it might be inlined, while a function with recursion can't be reliably inlined, but might still be pure (e.g. factorial, assuming you don't rewrite it to use a for-loop), and not all recursive functions can be rewritten to use simpler loops (e.g. the infamous Ackermann function). It is true though... proper constant propagation would cover most situations, even when an inline function calls another inline function such as Max, although there might still be issues in exceptional conditions (e.g. division by zero or trying to calculate tan(pi/2), for example).

The other point with a "pure" modifier is that, assuming the compiler determines that the function is indeed pure, it would permit their assignment to a constant. True, you could replace the constant with the function call whenever it appears, but that's not exactly good coding practice. And that's sort of the other reason for the "pure" modifier. If you release a library of functions that a third-party then looks at, with no background knowledge of its inner workings, the appearance of the "pure" modifier would quickly clue them in in regards to the functions' intent.

The final issue is that while a function might be pure, you might not want to inline it because of its complexity, especially if it's called multiple times with variable arguments. The simplest example I can think of is the two-argument binomial coefficient:

function Binomial(n, r: Cardinal): Cardinal; pure;
begin
  if (r > n) then
    Result := 0
  else if (r = n) then
    Result := 1 { Faster than calculating the factorials }
  else
    Result := Factorial(n) div (Factorial(r) * Factorial(n - r));
end;

The alternative versions either require recursion, or for exceptionally large values of n and r, calculating the integral that defines the gamma function.

Gareth aka. Kit

On Sat 21/07/18 18:51 , Sven Barth via fpc-devel sent:

Show quoted text 45 lines
Martok schrieb am Sa., 21. Juli 2018, 17:12: Am 21.07.2018 um 02:08 schrieb Sven Barth via fpc-devel: > The main problem is that not all functions that would be eligible for your > approach are also declared as inline thus their node trees would not be stored > inside the PPU and thus you could not work with them. I'm not sure I understand. Isn't the same true, with a new feature? The full node tree must be available, which basically means "has inlineinfo". Doesn't it? Correct.  > Additional benefit of the "pure" modifier: the compiler can check once the > method has been parsed whether it's pure or not and can thus error out in the > latter case. That is true, it is a more explicit declaration of intent. Maybe the codegen of pure can simply be implemented as generating inline info, but always replacing the calls with the simplified version. If it is already known that a function is pure, what I did with constexpr() would basically be guaranteed to work. Pure functions would still have an ordinary generated body as there is no reason that their address shouldn't be able to be taken (otherwise one needs to declare wrappers for them). Other than that I personally favor the explicit declaration of intent for such functions without the need for a ConstExpr intrinsic.  Regards, Sven  _______________________________________________ fpc-devel maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel [1] Links: ------ [1] http://secureweb.fast.net.uk/parse.php?redirect=http://lists.freepascal.org /cgi-bin/mailman/listinfo/fpc-devel

Reply by email

J. Gareth Moreton
 Sorry for that mess of a formatting on the code sample, Here is a much
nicer version!
 function Binomial(n, r: Cardinal): Cardinal; pure;begin
   if (r > n) then
     Result := 0
   else if (r = n) then
     Result := 1 { Faster than calculating the factorials }
   else
     Result := Factorial(n) div (Factorial(r) * Factorial(n - r));
 end;

Gareth aka. Kit

On Sun 22/07/18 05:03 , "J. Gareth Moreton"
sent:
An interesting read. If you can collapse an inline function into a single assignment in most situations, then brilliant! Definitely keep that optimisation in.

While there are many similarities and prerequisites between an inline function
and a pure function, and many simple mathematical functions could easily carry
both modifiers, they are not exactly the same. Pure functions cannot be evaluated at compile time if one of the arguments is a variable (or at the very
least, not deterministic), but it might be inlined, while a function with recursion can't be reliably inlined, but might still be pure (e.g. factorial,
assuming you don't rewrite it to use a for-loop), and not all recursive functions
can be rewritten to use simpler loops (e.g. the infamous Ackermann function). It
is true though... proper constant propagation would cover most situations, even
when an inline function calls another inline function such as Max, although there
might still be issues in exceptional conditions (e.g. division by zero or trying
to calculate tan(pi/2), for example).

The other point with a "pure" modifier is that, assuming the compiler determines
that the function is indeed pure, it would permit their assignment to a constant.
True, you could replace the constant with the function call whenever it appears,
but that's not exactly good coding practice. And that's sort of the other reason
for the "pure" modifier. If you release a library of functions that a third-party then looks at, with no background knowledge of its inner workings,
the appearance of the "pure" modifier would quickly clue them in in regards to
the functions' intent.

The final issue is that while a function might be pure, you might not want to
inline it because of its complexity, especially if it's called multiple times
with variable arguments. The simplest example I can think of is the two-argument
binomial coefficient:

 function Binomial(n, r: Cardinal): Cardinal; pure;
 begin
 if (r > n) then
 Result := 0
 else if (r = n) then
 Result := 1 { Faster than calculating the factorials }
 else
 Result := Factorial(n) div (Factorial(r) * Factorial(n - r));
 end;

The alternative versions either require recursion, or for exceptionally large
values of n and r, calculating the integral that defines the gamma function.

Gareth aka. Kit

On Sat 21/07/18 18:51 , Sven Barth via fpc-devel
[1] sent:

Show quoted text 5 lines
Martok schrieb am Sa., 21. Juli 2018, 17:12: Am 21.07.2018 um 02:08 schrieb Sven Barth via fpc-devel: > The main problem is that not all functions that would be eligible for your > approach are also declared as inline thus their node trees would not

be

Show quoted text 23 lines
stored > inside the PPU and thus you could not work with them. I'm not sure I understand. Isn't the same true, with a new feature? The full node tree must be available, which basically means "has inlineinfo". Doesn't it? Correct.  > Additional benefit of the "pure" modifier: the compiler can check once the > method has been parsed whether it's pure or not and can thus error out in the > latter case. That is true, it is a more explicit declaration of intent. Maybe the codegen of pure can simply be implemented as generating inline info, but always replacing the calls with the simplified version. If it is already known that a function is pure, what I did with constexpr() would

basically

be guaranteed to work. Pure functions would still have an ordinary generated body as there is

no

reason that their address shouldn't be able to be taken (otherwise one needs to declare wrappers for them). Other than that I personally favor

the

Show quoted text 6 lines
explicit declaration of intent for such functions without the need for a ConstExpr intrinsic.  Regards, Sven  _______________________________________________ fpc-devel maillist - [2] http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

[3]">http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel [1]

Show quoted text 7 lines
Links: ------ [1]

http://secureweb.fast.net.uk/parse.php%3Fredirect%3Dhttp://lists.freepascal.org [4]">http://secureweb.fast.net.uk/parse.php?redirect=http://lists.freepascal.org [5]

/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

J. Gareth Moreton

To follow up on this, even if you rewrote the function to use a multiplicative formula (faster to compute overall):

 function Binomial(n, r: Cardinal): Cardinal; pure;var  c: Cardinal;
 begin
   for (r > n) then
     Result := 0
   else
     begin
       Result := 1;
       if n  r then
         begin
           Inc(n); { Only "n + 1" appears in the formula }
           for c := 1 to r do           
             Result := (Result * (n - c)) div c; { Make sure the
multiplication is evaluated first }
         end;
     end;
 end;

I'm not saying it's impossible, but that's certainly slightly harder to propagate a constant through that, even with manual programmer optimisations like with Inc(n).

Gareth aka. Kit

 On Sun 22/07/18 05:15 , "J. Gareth Moreton"
sent:
  Sorry for that mess of a formatting on the code sample, Here is a much
nicer version!
 function Binomial(n, r: Cardinal): Cardinal; pure;begin
   if (r > n) then
     Result := 0
   else if (r = n) then
     Result := 1 { Faster than calculating the factorials }
   else
     Result := Factorial(n) div (Factorial(r) * Factorial(n - r));
 end;

Gareth aka. Kit

On Sun 22/07/18 05:03 , "J. Gareth Moreton"
sent:
An interesting read. If you can collapse an inline function into a single assignment in most situations, then brilliant! Definitely keep that optimisation in.

While there are many similarities and prerequisites between an inline function
and a pure function, and many simple mathematical functions could easily carry
both modifiers, they are not exactly the same. Pure functions cannot be evaluated at compile time if one of the arguments is a variable (or at the very
least, not deterministic), but it might be inlined, while a function with recursion can't be reliably inlined, but might still be pure (e.g. factorial,
assuming you don't rewrite it to use a for-loop), and not all recursive functions
can be rewritten to use simpler loops (e.g. the infamous Ackermann function). It
is true though... proper constant propagation would cover most situations, even
when an inline function calls another inline function such as Max, although there
might still be issues in exceptional conditions (e.g. division by zero or trying
to calculate tan(pi/2), for example).

The other point with a "pure" modifier is that, assuming the compiler determines
that the function is indeed pure, it would permit their assignment to a constant.
True, you could replace the constant with the function call whenever it appears,
but that's not exactly good coding practice. And that's sort of the other reason
for the "pure" modifier. If you release a library of functions that a third-party then looks at, with no background knowledge of its inner workings,
the appearance of the "pure" modifier would quickly clue them in in regards to
the functions' intent.

The final issue is that while a function might be pure, you might not want to
inline it because of its complexity, especially if it's called multiple times
with variable arguments. The simplest example I can think of is the two-argument
binomial coefficient:

 function Binomial(n, r: Cardinal): Cardinal; pure;
 begin
 if (r > n) then
 Result := 0
 else if (r = n) then
 Result := 1 { Faster than calculating the factorials }
 else
 Result := Factorial(n) div (Factorial(r) * Factorial(n - r));
 end;

The alternative versions either require recursion, or for exceptionally large
values of n and r, calculating the integral that defines the gamma function.

Gareth aka. Kit

On Sat 21/07/18 18:51 , Sven Barth via fpc-devel
[1] sent:

Show quoted text 5 lines
Martok schrieb am Sa., 21. Juli 2018, 17:12: Am 21.07.2018 um 02:08 schrieb Sven Barth via fpc-devel: > The main problem is that not all functions that would be eligible for your > approach are also declared as inline thus their node trees would not

be

Show quoted text 23 lines
stored > inside the PPU and thus you could not work with them. I'm not sure I understand. Isn't the same true, with a new feature? The full node tree must be available, which basically means "has inlineinfo". Doesn't it? Correct.  > Additional benefit of the "pure" modifier: the compiler can check once the > method has been parsed whether it's pure or not and can thus error out in the > latter case. That is true, it is a more explicit declaration of intent. Maybe the codegen of pure can simply be implemented as generating inline info, but always replacing the calls with the simplified version. If it is already known that a function is pure, what I did with constexpr() would

basically

be guaranteed to work. Pure functions would still have an ordinary generated body as there is

no

reason that their address shouldn't be able to be taken (otherwise one needs to declare wrappers for them). Other than that I personally favor

the

Show quoted text 6 lines
explicit declaration of intent for such functions without the need for a ConstExpr intrinsic.  Regards, Sven  _______________________________________________ fpc-devel maillist - [2] http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

[3]">http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel [1]

Show quoted text 7 lines
Links: ------ [1]

http://secureweb.fast.net.uk/parse.php?redirect=http://lists.freepascal.org [5]

/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

Martok
to J. Gareth Moreton: An interesting read. If you can collapse an inline function into a single assignment in…
J. Gareth Moreton wrote
Pure functions cannot be evaluated at compile time if one of the arguments is a variable (or at the very least, not deterministic)

Do you have an idea how to prove that yet? The way I see it now, this is the only hard issue, every other component is already present somewhere in some form.

although there might still be issues in exceptional conditions (e.g. division by zero or trying to calculate tan(pi/2), for example).
Good point - what should happen in that case?
    const
      x  = Pi / 0;
    begin
      writeln(x);
    end.
That writes "+Inf".
The other point with a "pure" modifier is that, assuming the compiler determines that the function is indeed pure, it would permit their assignment to a constant.

So: on processing a call to a pure function,
if arguments are constant, emit (interpreted) result of function otherwise, emit regular call
?
What would error/warning cases be? I'd want to avoid a situation like that with the insightful, but unfixable and thus spammy "06058_N_Call to subroutine "$1" marked as inline is not inlined" message.

The final issue is that while a function might be pure, you might not want to inline it because of its complexity, especially if it's called multiple times with variable arguments.

That is very true. Should the "interpretation complexity" be limited, or should the compiler keep going and maybe run into a stack overflow?

Regards, Martok

Reply by email

J. Gareth Moreton

On Sun 22/07/18 14:57 , Martok
sent:

Am 22.07.2018 um 06:03 schrieb J. Gareth

Moreton:

> Pure functions cannot be > evaluated at compile time if one of

the

arguments is a variable (or at the very > least, not deterministic) Do you have an idea how to prove that

yet? The way I see it now, this is

the only hard issue, every other component

is already present somewhere in some

form.

This one is quite simple really. If what
you are passing into the function is
neither a constant nor a literal number,
then it is called like a regular function
with no hint or warning. If all the actual
parameters are constant, then the compiler
will attempt to evaluate it there and then
and replace the call with the result.

Show quoted text 5 lines
> although there > might still be issues in exceptional

conditions

(e.g. division by zero or trying > to calculate tan(pi/2), for example). Good point - what should happen in that

case?

Show quoted text 14 lines
const x = Pi / 0; begin writeln(x); end. That writes "+Inf".

For integer division and explicit raise
statements, I plan to return a compile-
time error. With floating point, I'm
considering returning the relevant special
representations, since there are
constructs such as "const NaN = 0.0 /
0.0;", which in the mathematical sense is
a pure function. I'm not certain what
tan(Pi/2) returns, but going by the
identity tan = sin/cos, it would be +inf.

> The other point with a "pure" modifier is that, assuming the compiler

determines

> that the function is indeed pure, it

would

permit their assignment to a constant. So: on processing a call to a pure

function,

if arguments are constant, emit

(interpreted) result of function

Show quoted text 6 lines
otherwise, emit regular call ? What would error/warning cases be? I'd

want to avoid a situation like that

with the insightful, but unfixable and thus

spammy "06058_N_Call to

subroutine "$1" marked as inline is not inlined"

message.

My plan with pure functions is that if the
compiler spots something that makes the
function impure, it returns a warning and
sets a flag to say it's ineligible, so it
won't attempt any further compile-time
evaluation. Unlike inline, which has
fairly arbitrary restrictions, the
definition of a pure function is fairly
concise and easy to understand. Triggering
the warning is usually the sign of a bug,
either in the function itself or has no
business being marked as pure (because it
reads from a file, for example). The
compiler might spot the function's
impurity during node construction or
during evaluation (the latter would
generally only happen if it suspects an
infinite loop and the like).

For the constant assignment, it will
return the error that is already thrown if
you try to assign a function result to a
constant. If the compiler only notices the
function's impurity while attempting to
evaluate the result for this assignment,
it will first throw the warning, then the
error.

> The final issue is that while a

function might

be pure, you might not want to > inline it because of its complexity,

especially

if it's called multiple times > with variable arguments. That is very true. Should the

"interpretation complexity" be

limited, or should the compiler keep going and maybe run

into a stack overflow?

Show quoted text 12 lines
-- Regards, Martok

This is actually why I keep bringing up
the Ackermann function as a test case, to
see how the compiler responds to extreme
situations, because there is no general
solution to determine if a function
actually completes (Turing's halting
problem)... then again, Ackermann's
function actually does complete - it just
takes a prohibitively long time for large
inputs.

To get around this, I'm considering a
limit of 4096 nodes that the compiler will
step through, and a stack depth limit of
64, before deciding that it's an infinite
loop or otherwise too complex to be pure,
and mark it as ineligible (and to stop the
compiler from crashing or taking longer
than the age of the Universe to build the
project). This would otherwise be the only
arbitrary restriction on pure functions
that can't easily be fixed, but you would
have to try quite hard to trigger it and
is more likely a bug such as an actual
infinite loop.

Thanks for the questions, Martok.

Gareth aka. Kit

Reply by email

J. Gareth Moreton

Another little progress update with implementing pure functions.
1. In my code (not submitted yet), the compiler now recognises and accepts the 'pure' directive.  There are a number of other directives that it can't be used with ('noreturn' being one of them) and it can't be used on object methods.
2. A compiler hint is thrown if a function is not pure, but can definitely be made pure. This is done via a new procinfo flag named "pi_pure_uncertain", which is set if any loops, jumps or calls are encountered, as well as anything that makes the function impure. If the flag is not set when the function's node tree has been built, then it's definitely a pure function. Note that the compiler won't start treating it as a pure function - it only gives the hint that it could do.
3. A compiler warning is thrown if a function marked as pure tries to access a variable whose scope falls outside of the subroutine (i.e. it's not local).  Similar warnings are thrown if the ^ and @ operators are used.

I haven't programmed in the actual compile-time evaluation yet because that requires stepping through the nodes like an interpreter.  That's my next challenge once I get the initial compilation and validation done. Some things will take a little bit of thought.  With a pure function calling another pure function, it's not always known if the callee is pure at this point (e.g. with a forward declaration and/or where the 'pure' directive only appears in the implementation section), so it will have to come back to check it later somehow, which might be difficult if the original pure function is being called before its own implementation (as part of a constant assignment, for example).

Though not tested or fully validated yet, it's intended that only floating-point and ordinal type parameters and variables are supported initially for the sake of simplicity.  Support for record types and strings will be a bit more complicated and something I'll do later, assuming there's a need for more complex types in pure functions - I can think of examples where they will, such as with "IntToStr" (when called by another pure function) with strings, and a library of routines for handling complex numbers, say (record type consisting of 2 Doubles).

Hopefully I can present something in the near future to be tested and ripped apart by other developers and SQA!

Gareth aka. Kit

Reply by email

J. Gareth Moreton

I did wonder about that, but wasn't sure how it would affect things where inheritance is concerned.  I dare ask, do you have an example of a class method that you would consider pure?

And yes, object methods can't be pure because accessing the fields of the current object (Self) is internally dereferencing a pointer .

Gareth aka. Kit

On Sat 28/07/18 18:14 , "Sven Barth" sent:
J. Gareth Moreton schrieb am Sa., 28. Juli 2018, 18:49: Another little progress update with implementing pure functions.
1. In my code (not submitted yet), the compiler now recognises and accepts the 'pure' directive.  There are a number of other directives that it can't be used with ('noreturn' being one of them) and it can't be used on object methods.
You could allow it for static class methods which don't have a Self argument and are essentially scoped ordinary routines.  Regards, Sven 

Reply by email

Sven Barth
to J. Gareth Moreton: Another little progress update with implementing pure functions. 1. In my code (not…

J. Gareth Moreton schrieb am Sa., 28. Juli
2018, 18:49:

Show quoted text 7 lines
Another little progress update with implementing pure functions. 1. In my code (not submitted yet), the compiler now recognises and accepts the 'pure' directive. There are a number of other directives that it can't be used with ('noreturn' being one of them) and it can't be used on object methods.

You could allow it for static class methods which don't have a Self argument and are essentially scoped ordinary routines.

Regards,
Sven

Reply by email

Sven Barth
to J. Gareth Moreton: I did wonder about that, but wasn't sure how it would affect things where inheritance is…

J. Gareth Moreton schrieb am Sa., 28. Juli
2018, 19:34:

I did wonder about that, but wasn't sure how it would affect things where inheritance is concerned. I dare ask, do you have an example of a class method that you would consider pure?

They don't have inheritance. If they have the modifier "static" they really are simply scoped functions or procedures. They can even be assigned to non-"of object" procedure variables.

Regards,
Sven

Reply by email

J. Gareth Moreton

Aah, right.
I'm probably being rather dumb here from not having used them before, but do you mean things like this?

 type
   TNewClass = class    function Special: Boolean; static;
 end;

I gather you can't access Self in such methods.

Gareth aka. Kit

On Sat 28/07/18 20:37 , "Sven Barth" sent:
J. Gareth Moreton schrieb am Sa., 28. Juli 2018, 19:34: I did wonder about that, but wasn't sure how it would affect things where inheritance is concerned.  I dare ask, do you have an example of a class method that you would consider pure?

They don't have inheritance. If they have the modifier "static" they really are simply scoped functions or procedures. They can even be assigned to non-"of object" procedure variables. 
Regards, Sven

Reply by email

J. Gareth Moreton

Aah, right.  Class methods I have used in past projects, but my understanding is that, in these instances, Self can still be accessed, but represents the class rather than the object, can be called without an instantiated object, and still have the issues that they can be virtual and overridden (something i'm disallowing with pure functions because of the initial complexity of determining which version of a method to call).

Can you clarify, Sven, with "static" and "class"?

Gareth aka. Kit

On Sat 28/07/18 23:11 , "Dmitry Boyarintsev"
sent:
These are known as “class” methods (rather than “static”) in object pascal.
Thanks,Dmitry

On Saturday, July 28, 2018, J. Gareth Moreton wrote: Aah, right.
I'm probably being rather dumb here from not having used them before, but do you mean things like this?

 type
   TNewClass = class    function Special: Boolean; static;
 end;

I gather you can't access Self in such methods.

Gareth aka. Kit

On Sat 28/07/18 20:37 , "Sven Barth" sent:
J. Gareth Moreton schrieb am Sa., 28. Juli 2018, 19:34: I did wonder about that, but wasn't sure how it would affect things where inheritance is concerned.  I dare ask, do you have an example of a class method that you would consider pure?

They don't have inheritance. If they have the modifier "static" they really are simply scoped functions or procedures. They can even be assigned to non-"of object" procedure variables. 
Regards, Sven

Reply by email

Dmitry Boyarintsev
to J. Gareth Moreton: Aah, right. I'm probably being rather dumb here from not having used them before, but do…

These are known as “class” methods (rather than “static”) in object pascal.

Thanks,
Dmitry

J. Gareth Moreton wrote 33 lines
Aah, right. I'm probably being rather dumb here from not having used them before, but do you mean things like this? type TNewClass = class function Special: Boolean; static; end; I gather you can't access Self in such methods. Gareth aka. Kit On Sat 28/07/18 20:37 , "Sven Barth" sent: J. Gareth Moreton schrieb am Sa., 28. Juli 2018, 19:34: > I did wonder about that, but wasn't sure how it would affect things where > inheritance is concerned. I dare ask, do you have an example of a class > method that you would consider pure? > They don't have inheritance. If they have the modifier "static" they really are simply scoped functions or procedures. They can even be assigned to non-"of object" procedure variables. Regards, Sven >

Reply by email

Thorsten Engler
to J. Gareth Moreton: Aah, right.  Class methods I have used in past projects, but my understanding is that,…
type
  TMyClass = class of TMyObject;
  TMyObject = class
    procedure InstanceMethod; //Self = TMyObject, can be virtual
    class procedure ClassMethod; //Self = TMyClass, can be virtual
    class procedure StaticClassMethod; static; //no Self, really just a global procedure in a scoped namespace
  end;
var
  o: TMyObject;
  c: TMyClass;
begin
  o.InstanceMethod; //if virtual, calls resolved based on actual class
  o.ClassMethod; //if virtual calls, resolved based on actual class
  o.StaticCalssMethod; //can’t be virtual, resolved on variable type
  TMyObject.ClassMethod; //call resolved based on given type
  TMyObject.StaticClassMethod; //call resolved based on given type
  c.ClassMethod; //if virtual, calls resolved based on actual class
  //haven’t tried if this one works, I assume it does:
  c.StaticClassMethod; //can’t be virtual, resolved on variable type
end;
Earlier message 62 lines
From: fpc-devel On Behalf Of J. Gareth Moreton Sent: Sunday, 29 July 2018 07:22 To: FPC developers' list Subject: Re: [fpc-devel] Compile time functions Aah, right. Class methods I have used in past projects, but my understanding is that, in these instances, Self can still be accessed, but represents the class rather than the object, can be called without an instantiated object, and still have the issues that they can be virtual and overridden (something i'm disallowing with pure functions because of the initial complexity of determining which version of a method to call). Can you clarify, Sven, with "static" and "class"? Gareth aka. Kit On Sat 28/07/18 23:11 , "Dmitry Boyarintsev" sent: These are known as “class” methods (rather than “static”) in object pascal. Thanks, Dmitry On Saturday, July 28, 2018, J. Gareth Moreton < <javascript:top.opencompose('','','','')> > wrote: Aah, right. I'm probably being rather dumb here from not having used them before, but do you mean things like this? type TNewClass = class function Special: Boolean; static; end; I gather you can't access Self in such methods. Gareth aka. Kit On Sat 28/07/18 20:37 , "Sven Barth" <javascript:top.opencompose('','','','')> sent: J. Gareth Moreton < > schrieb am Sa., 28. Juli 2018, 19:34: I did wonder about that, but wasn't sure how it would affect things where inheritance is concerned. I dare ask, do you have an example of a class method that you would consider pure? They don't have inheritance. If they have the modifier "static" they really are simply scoped functions or procedures. They can even be assigned to non-"of object" procedure variables. Regards, Sven

Reply by email

Sven Barth
to Thorsten Engler

Thorsten Engler schrieb am So., 29. Juli 2018,
04:40:

Show quoted text 47 lines
type TMyClass = class of TMyObject; TMyObject = class procedure InstanceMethod; //Self = TMyObject, can be virtual class procedure ClassMethod; //Self = TMyClass, can be virtual class procedure StaticClassMethod; static; //no Self, really just a global procedure in a scoped namespace end; var o: TMyObject; c: TMyClass; begin o.InstanceMethod; //if virtual, calls resolved based on actual class o.ClassMethod; //if virtual calls, resolved based on actual class o.StaticCalssMethod; //can’t be virtual, resolved on variable type TMyObject.ClassMethod; //call resolved based on given type TMyObject.StaticClassMethod; //call resolved based on given type c.ClassMethod; //if virtual, calls resolved based on actual class //haven’t tried if this one works, I assume it does: c.StaticClassMethod; //can’t be virtual, resolved on variable type end;

@Gareth: And I mean the StaticClassMethod here.

Regards,
Sven

Reply by email

J. Gareth Moreton

Thanks everyone! I'm still learning some parts of Pascal!  I'll see if I can accommodate for that.

Gareth aka. Kit

On Sun 29/07/18 08:14 , Sven Barth via fpc-devel
sent:
Thorsten Engler schrieb am So., 29. Juli 2018, 04:40:

 type
  TMyClass = class of TMyObject;

  TMyObject = class

    procedure InstanceMethod; //Self = TMyObject, can be virtual
    class procedure ClassMethod; //Self = TMyClass, can be virtual
    class procedure StaticClassMethod; static; //no Self, really just a
global procedure in a scoped namespace
   end;

 

var
  o: TMyObject;
   c: TMyClass;
begin
  o.InstanceMethod; //if virtual, calls resolved based on actual class
  o.ClassMethod; //if virtual calls, resolved based on actual class
  o.StaticCalssMethod; //can’t be virtual, resolved on variable type

 

  TMyObject.ClassMethod; //call resolved based on given type
  TMyObject.StaticClassMethod; //call resolved based on given type

 

  c.ClassMethod; //if virtual, calls resolved based on actual class

  //haven’t tried if this one works, I assume it does:

  c.StaticClassMethod; //can’t be virtual, resolved on variable type

end; @Gareth: And I mean the StaticClassMethod here.  Regards, Sven  _______________________________________________

Reply by email