Discussion:
Little question about the "First Pass"
(too old to reply)
J. Gareth Moreton
2018-08-08 20:44:55 UTC
Permalink
Hi everyone,
So I'm making progress with my pure function implementation, or something
passing as an early alpha prototype.  I do have a question though... when
the "firstpass" routines are called, have all the procedures been compiled
into nodes yet? I'm still trying to work out the best time to evaluate
function purity, partly due to constructs such as this:

interface

function PureFunc1(Input: Integer): Boolean;
function PureFunc2(Input: Integer): Boolean;

implementation

function PureFunc1(Input: Integer): Boolean; pure;
begin
  Result := PureFunc2(Input * Input); { "pure" directive for PureFunc2
hasn't been seen yet }
end;

function PureFunc2(Input: Integer): Boolean; pure;
begin  { Do something complicated but deterministic! }
end;

****

I could probably find the answer after some long research, but I'm trying
to avoid wasting time unnecessarily.  I would guess that the evaluation
should occur at a similar time to when inline functions are expanded, where
they are fully-defined and known to be inlinable.

Gareth aka. Kit
Sven Barth via fpc-devel
2018-08-09 09:15:14 UTC
Permalink
Post by J. Gareth Moreton
Hi everyone,
So I'm making progress with my pure function implementation, or something
passing as an early alpha prototype. I do have a question though... when
the "firstpass" routines are called, have all the procedures been compiled
into nodes yet? I'm still trying to work out the best time to evaluate
interface
function PureFunc1(Input: Integer): Boolean;
function PureFunc2(Input: Integer): Boolean;
implementation
function PureFunc1(Input: Integer): Boolean; pure;
begin
Result := PureFunc2(Input * Input); { "pure" directive for PureFunc2
hasn't been seen yet }
end;
function PureFunc2(Input: Integer): Boolean; pure;
begin
{ Do something complicated but deterministic! }
end;
****
I could probably find the answer after some long research, but I'm trying
to avoid wasting time unnecessarily. I would guess that the evaluation
should occur at a similar time to when inline functions are expanded, where
they are fully-defined and known to be inlinable.
First of such modifiers should only be allowed in the interface section,
not the implementation section as they would essentially be a change of the
routine's signature (doesn't matter that it isn't part of the routine's
mangled name) and thus a change of the unit's interface which in turn would
require a recompilation of units using that unit.

To your question itself: once a non-nested routine body is parsed it's code
is generated which includes both the first and second pass. For nested
routines the same is done once the outermost routine as been parsed (but
before that one's first pass).

Regards,
Sven
Sven Barth via fpc-devel
2018-08-10 05:45:43 UTC
Permalink
Gareth sent me the following two mails in private, but they were ment
Thanks Sven. Normally I would agree with
"pure" only belonging in the interface
section, but it causes problems when you
try to put a pure function inside the main
program block, as the entire thing is
considered equivalent to implementation.
Also, inline is allowed in the
implementation section, and the two follow
similar rules in regards to their calls
being modified.
Just to clarify, "pure" doesn't change
anything in regards to the parameter types
or how a call with variable arguments is
handled. The raw signature shouldn't
change. It's an optimisation hint. At
least it is in a perfect world!
I asked the question because I stumbled across something interesting. 
I've been using the following set of functions to see how the compiler
handles things that are a bit out of order (using 'inline' as a
program PureTest;
function PureMax(const a: Double; const b: Double): Double; forward;
procedure TestFunc;
begin
  WriteLn(PureMax(2.0, 3.0));
end;
function PureMax(const a: Double; const b: Double): Double; inline;
begin
  if (a > b) then
    Result := a
  else
    Result := b;
end;
begin
  TestFunc;
end.
****
Turns out, to my surprise, after analysing the nodes and the
disassembly, that "PureMax" is not inlined inside the TestFunc
routine.  I haven't tested units yet, but would a similar situation
occur if a function is not defined as inline in the "interface"
section and only the "implementation" section (unless the caller
appears after said function in the source file)?
Either way, I'm leaning towards making the new "pure" directive an
interface-only directive as Sven suggested, as that will make things a
lot easier if a pure function is used as part of a constant
definition.  After all, "pure" would have a bit more of a notable
effect than "inline" because it dictates where the function can and
cannot be used.
I hadn't thought that I need to explain that, but apparantly you haven't
yet reached that far in your research of the compiler, so:
- of course I only mean that it's an interface-only modifier if there
*is* an interface section; routines that are - inside a unit - only
declared inside the implementation section are obviously exempt from
this (thus also for the program file)
- again I wrote that I don't mean that "pure" modifies the parameters,
but it modifies the meta data (most importantly checksum) of the
interface section when you add a modifier flag inside the implementation
section. The compiler first handles the interface sections of units and
might then compile interface sections or even implementation sections of
other units depending on the dependencies between the units. If a flag
only appears in the implementation section than this messes up this
whole thing as the other units would need to be recompiled. This is
already a mess with "inline" so we don't need to do that with "pure" as
well, especially as we don't need to be Delphi compatible here.
- yes, it is correct that the compiler's capability to inline depends on
the order of the routine definitions. The compiler generates the code
routine by routine so if the routine's body has not yet been encountered
then it can't be inlined. (When using generic specializations it
triggeres the generation of the specialized routine bodies before
generating the routine to avoid "inline" not working for generics) For
"pure" we can even go so far as to say that a routine body *must* be
available to be able to call it inside another "pure" routine.
Everything else would be an error.

I hope this clears things up a bit.

Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepasca

Loading...