Sign in Join

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

Mailing lists / fpc-pascal / 2018-12

inlining functions depending on implementation only functions

18 messages · 5 people · –

Reply by email Subscribe to this list Your subscription

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

Benito van der Zander

Hi,

after updating from fpc 3.1 to fpc 3.3, I am getting a lot of "function was not inlined" warnings, e.g. when an inline function depends on a function not declared in the interface part like:

unit inlinetest;
{$mode objfpc}{$H+}
interface
uses
   Classes, SysUtils;
function strContains(const str, searched: string): boolean; inline;
implementation
function strContainsFrom(const str, searched: string; from: SizeInt):
boolean;
begin
   result:=Pos(searched, str, from) > 0;
end;
function strContains(const str, searched: string): boolean; inline;
begin
   result := strContainsFrom(str, searched, 1);
end;
end.

Is that supposed to happen?

Fpc 3.1 did not show any warning in this case (although now that I investigate it, fpc 3.1 also did not seem to inline it despite not showing the warning)

Bye,

Benito

Reply by email

Unknown author
to Benito van der Zander
Benito van der Zander wrote
Fpc 3.1 did not show any warning in this case (although now that I investigate it, fpc 3.1 also did not seem to inline it despite not showing the warning)

i think, but am not totally sure, that the addition of the warning is a recently added feature specifically to note that a function was not inlined for some reason...

NOTE: No off-list assistance is given without prior approval. *Please keep mailing list traffic on the list unless* *a signed and pre-paid contract is in effect with us.*

Reply by email

Sven Barth
to Benito van der Zander: after updating from fpc 3.1 to fpc 3.3, I am getting a lot of "function was not inlined"…

Am Sa., 29. Dez. 2018, 15:23 hat Benito van der Zander geschrieben:

Show quoted text 42 lines
Hi, after updating from fpc 3.1 to fpc 3.3, I am getting a lot of "function was not inlined" warnings, e.g. when an inline function depends on a function not declared in the interface part like: unit inlinetest; {$mode objfpc}{$H+} interface uses Classes, SysUtils; function strContains(const str, searched: string): boolean; inline; implementation function strContainsFrom(const str, searched: string; from: SizeInt): boolean; begin result:=Pos(searched, str, from) > 0; end; function strContains(const str, searched: string): boolean; inline; begin result := strContainsFrom(str, searched, 1); end; end. Is that supposed to happen? Fpc 3.1 did not show any warning in this case (although now that I investigate it, fpc 3.1 also did not seem to inline it despite not showing the warning)

Correct. FPC 3.1.1 did neither warn nor inline in this case, 3.3.1 at least warns (I think 3.2 already warns as well).

Regards,
Sven

Reply by email

Benito van der Zander
to Sven Barth

Hi,

and why is it not inlining the count and append call of this string builder? It is not using any implementation only function

unit inlinetest;
{$mode objfpc}{$H+}
interface
uses
   Classes, SysUtils,math;
type TStrBuilder = object
protected
   next, bufferend: pchar; //next empty pchar and first pos after the string
public
   buffer: pstring;
   procedure init(abuffer:pstring);
   procedure final;
   function count: SizeInt; inline;
   procedure reserveadd(delta: SizeInt);
   procedure append(c: char); inline;
   procedure append(const s: RawByteString); inline;
   procedure append(const p: pchar; const l: SizeInt); inline;
end;
implementation
procedure TStrBuilder.init(abuffer: pstring);
begin
   buffer := abuffer;
   SetLength(buffer^, 16); //use setlength to create a new string
   next := pchar(buffer^);
   bufferend := next + length(buffer^);
end;
procedure TStrBuilder.final;
begin
   if next  bufferend then begin
     setlength(buffer^, count);  // !!! Note: Call to subroutine
"function TStrBuilder.count:Int64;" marked as inline is not inlined
     next := pchar(buffer^) + length(buffer^);
     bufferend := next;
   end;
end;
function TStrBuilder.count: SizeInt;
begin
   result := next - pointer(buffer^);
end;
procedure TStrBuilder.reserveadd(delta: SizeInt);
var
   oldlen: SizeInt;
begin
   if next + delta > bufferend then begin
     oldlen := count;
     SetLength(buffer^, max(2*length(buffer^), oldlen + delta));
     next := pchar(buffer^) + oldlen;
     bufferend := pchar(buffer^) + length(buffer^);
   end;
end;
procedure TStrBuilder.append(c: char);
begin
   if next >= bufferend then reserveadd(1);
   next^ := c;
   inc(next);
end;
procedure TStrBuilder.append(const s: RawByteString);
begin
   append(pchar(pointer(s)), length(s)); // !!! inlinetest.pas(71,3)
Note: Call to subroutine "procedure TStrBuilder.append(const
p:PChar;const l:Int64);" marked as inline is not inlined
end;
procedure TStrBuilder.append(const p: pchar; const l: SizeInt); inline;
begin
   if l <= 0 then exit;
   if next + l > bufferend then reserveadd(l);
   move(p^, next^, l);
   inc(next, l);
end;
end.

Bye,
Benito

Sven Barth wrote 56 lines
Am Sa., 29. Dez. 2018, 15:23 hat Benito van der Zander < > geschrieben: Hi, after updating from fpc 3.1 to fpc 3.3, I am getting a lot of "function was not inlined" warnings, e.g. when an inline function depends on a function not declared in the interface part like: unit inlinetest; {$mode objfpc}{$H+} interface uses    Classes, SysUtils; function strContains(const str, searched: string): boolean; inline; implementation function strContainsFrom(const str, searched: string; from: SizeInt): boolean; begin    result:=Pos(searched, str, from) > 0; end; function strContains(const str, searched: string): boolean; inline; begin    result := strContainsFrom(str, searched, 1); end; end. Is that supposed to happen? Fpc 3.1 did not show any warning in this case (although now that I investigate it, fpc 3.1 also did not seem to inline it despite not showing the warning) Correct. FPC 3.1.1 did neither warn nor inline in this case, 3.3.1 at least warns (I think 3.2 already warns as well). Regards, Sven _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Jonas Maebe
to Benito van der Zander
Benito van der Zander wrote
and why is it not inlining the count and append call of this string builder? It is not using any implementation only function

Routines can only be inlined if they are called after their implementation has been parsed. FPC compiles everything in a single pass.

Jonas

Reply by email

Benito van der Zander
to Jonas Maebe

Hi Jonas,

That is reasonable. Perhaps the hint should mention that?

But why is this still not inlined::

type TStrBuilder = object
   procedure append(const p: pchar; const l: SizeInt); inline;
   procedure append(const s: RawByteString);
end;
implementation
procedure TStrBuilder.append(const p: pchar; const l: SizeInt); inline;
begin
end;
procedure TStrBuilder.append(const s: RawByteString);
begin
   append(pchar(pointer(s)), length(s)); //inlinetest.pas(24,3) Note:
Call to subroutine "procedure TStrBuilder.append(const p:PChar;const
l:Int64);" marked as inline is not inlined
end;

Best,
Benito

Jonas Maebe wrote 12 lines
On 01/01/19 22:38, Benito van der Zander wrote: > and why is it not inlining the count and append call of this string > builder? It is not using any implementation only function Routines can only be inlined if they are called after their implementation has been parsed. FPC compiles everything in a single pass. Jonas _______________________________________________ fpc-pascal maillist  -  http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Jonas Maebe
to Benito van der Zander
Benito van der Zander wrote 6 lines
procedure TStrBuilder.append(const s: RawByteString); begin append(pchar(pointer(s)), length(s)); //inlinetest.pas(24,3) Note: Call to subroutine "procedure TStrBuilder.append(const p:PChar;const l:Int64);" marked as inline is not inlined end;

The compiler does not support inlining calls with parameters that cast a managed type to an unmanaged type at this time.

Regarding the reason why something cannot be inlined: sometimes you can get more information with -vd, but not always (and even then the reason may be vague).

I would personally be in favour of removing all of the inlining hints, because they are specific to a particular compiler version and mainly cause people to waste time on premature optimisation (or on what they think is an optimisation, but in many cases slows down things due to increased instruction cache pressure). Especially in this case: there is nothing potentially wrong with such a call, nor is the method in general not inlinable, so there is no way to get rid of the hint other than by removing the inline directive altogether (or by adding a version of that routine with a different name that is not declared as "inline"). This only promotes complicating code without any potential improvement of productivity or clarity of the code/programmer intent.

Jonas

Reply by email

Benito van der Zander
to Jonas Maebe

Hi,

Show quoted text 6 lines
procedure TStrBuilder.append(const s: RawByteString); begin append(pchar(pointer(s)), length(s)); //inlinetest.pas(24,3) Note: Call to subroutine "procedure TStrBuilder.append(const p:PChar;const l:Int64);" marked as inline is not inlined end;

this seems to help

procedure TStrBuilder.append(const s: RawByteString);
var p: pchar;
begin
    p := pchar(pointer(s));
    append(p, length(s));
end;
Show quoted text 5 lines
I would personally be in favour of removing all of the inlining hints, because they are specific to a particular compiler version and mainly cause people to waste time on premature optimisation (or on what they think is an optimisation, but in many cases slows down things due to increased instruction cache pressure).

After updating from 3.1 to 3.3 I only have to look at around one thousand hints

The one-pass thing is probably the reason it now complains about all inline functions in dependency cycles, unit A uses unit B that uses unit A. Then unit A can't inline something unit B. Any way around that?

Best,
Benito

Jonas Maebe wrote 33 lines
On 2019-01-02 00:19, Benito van der Zander wrote: > procedure TStrBuilder.append(const s: RawByteString); > begin > append(pchar(pointer(s)), length(s)); //inlinetest.pas(24,3) Note: > Call to subroutine "procedure TStrBuilder.append(const p:PChar;const > l:Int64);" marked as inline is not inlined > end; The compiler does not support inlining calls with parameters that cast a managed type to an unmanaged type at this time. Regarding the reason why something cannot be inlined: sometimes you can get more information with -vd, but not always (and even then the reason may be vague). I would personally be in favour of removing all of the inlining hints, because they are specific to a particular compiler version and mainly cause people to waste time on premature optimisation (or on what they think is an optimisation, but in many cases slows down things due to increased instruction cache pressure). Especially in this case: there is nothing potentially wrong with such a call, nor is the method in general not inlinable, so there is no way to get rid of the hint other than by removing the inline directive altogether (or by adding a version of that routine with a different name that is not declared as "inline"). This only promotes complicating code without any potential improvement of productivity or clarity of the code/programmer intent. Jonas _______________________________________________ fpc-pascal maillist  - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Jonas Maebe
to Benito van der Zander
Benito van der Zander wrote
The one-pass thing is probably the reason it now complains about all inline functions in dependency cycles, unit A uses unit B that uses unit A. Then unit A can't inline something unit B. Any way around that?

Not besides breaking your dependency cycles.

Jonas

Reply by email

denisgolovan
to Jonas Maebe

07.01.2019, 00:45, "Jonas Maebe" :

Not besides breaking your dependency cycles.

Sorry, but for necro-posting, but shouldn't WPO is supposed to help to inline functions as well?

Regards, Denis Golovan

Reply by email

Jonas Maebe
to denisgolovan
denisgolovan wrote 5 lines
07.01.2019, 00:45, "Jonas Maebe" : > Not besides breaking your dependency cycles. Sorry, but for necro-posting, but shouldn't WPO is supposed to help to inline functions as well?

Not at this time (unless you use the LLVM backend). However, what you actually can do, is manually recompile all units of your program multiple times. While this won't help with inline functions called before they are parsed in those same units, it will allow inlining of function bodies in other units that were not available during the first compilation due to dependency cycles.

Jonas

Reply by email

denisgolovan
to Jonas Maebe

12.01.2019, 14:53, "Jonas Maebe" :

Not at this time (unless you use the LLVM backend).

Could you give some hints on how to enable WPO/LTO under LLVM backend? http://wiki.freepascal.org/LLVM does not seem to mention anything on that.

Show quoted text 5 lines
However, what you actually can do, is manually recompile all units of your program multiple times. While this won't help with inline functions called before they are parsed in those same units, it will allow inlining of function bodies in other units that were not available during the first compilation due to dependency cycles.

Eh.
It does not look too practical, right?
Unless there is some magical receipt for that :)

Regards, Denis Golovan

Reply by email

Jonas Maebe
to denisgolovan
denisgolovan wrote 7 lines
12.01.2019, 14:53, "Jonas Maebe" : > Not at this time (unless you use the LLVM backend). Could you give some hints on how to enable WPO/LTO under LLVM backend? http://wiki.freepascal.org/LLVM does not seem to mention anything on that.

It's not yet integrated in the compiler, so you have to do it manually right now:
a) compile everything with -al (including the RTL etc; add OPT="-a" to the make command)
b) go in all the unit directories, and assemble the files to bitcode using something like this:

for file in *.ll; do
clang -emit-llvm -O -c $file
done

c) compile your program with -a -s and do the same as in step b) for your program
d) edit the generated link.res file, and replace all references to unit files ending in ".o" with the same files ending in ".bc"

If you used a custom clang rather than the one installed by default on your system, you will also have to specify the path to the libLTO that the linker should use. You have to do this on the ld command line in ppas.sh. You can find the correct parameter by compiling a test C-program with "clang -flto -### test.c", as this will print the ld invocation to use. E.g. on macOS, it will be something like

-lto_library
/Volumes/imac/devel/clang+llvm-7.0.0-x86_64-apple-darwin/lib/libLTO.dylib"

On Linux, it's something like

-plugin
/home/jmaebe/local/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/bin/../lib/LLVMgold.so -plugin-opt=O2

(or a different optimization level)

Show quoted text 8 lines
> However, what you actually can do, is manually recompile all units of your program > multiple times. While this won't help with inline functions called > before they are parsed in those same units, it will allow inlining of > function bodies in other units that were not available during the first > compilation due to dependency cycles. Eh. It does not look too practical, right?

That's why I mentioned it's a manual process.

Jonas

Reply by email

Sven Barth
to denisgolovan: 12.01.2019, 14:53, "Jonas Maebe" : Could you give some hints on how to enable WPO/LTO…

Am Sa., 12. Jan. 2019, 13:05 hat denisgolovan
geschrieben:

Show quoted text 11 lines
> However, what you actually can do, is manually recompile all units of your program > multiple times. While this won't help with inline functions called > before they are parsed in those same units, it will allow inlining of > function bodies in other units that were not available during the first > compilation due to dependency cycles. Eh. It does not look too practical, right? Unless there is some magical receipt for that :)

With WPO you need to compile at least twice as well as the first pass only collects information and acts on it in the second pass.

Regards,
Sven

Reply by email

denisgolovan
to Jonas Maebe: It's not yet integrated in the compiler, so you have to do it manually right now: a)…

12.01.2019, 15:32, "Jonas Maebe" :

Show quoted text 33 lines
It's not yet integrated in the compiler, so you have to do it manually right now: a) compile everything with -al (including the RTL etc; add OPT="-a" to the make command) b) go in all the unit directories, and assemble the files to bitcode using something like this: for file in *.ll; do    clang -emit-llvm -O -c $file done c) compile your program with -a -s and do the same as in step b) for your program d) edit the generated link.res file, and replace all references to unit files ending in ".o" with the same files ending in ".bc" If you used a custom clang rather than the one installed by default on your system, you will also have to specify the path to the libLTO that the linker should use. You have to do this on the ld command line in ppas.sh. You can find the correct parameter by compiling a test C-program with "clang -flto -### test.c", as this will print the ld invocation to use. E.g. on macOS, it will be something like -lto_library /Volumes/imac/devel/clang+llvm-7.0.0-x86_64-apple-darwin/lib/libLTO.dylib" On Linux, it's something like -plugin /home/jmaebe/local/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/bin/../lib/LLVMgold.so -plugin-opt=O2 (or a different optimization level)

Thanks, but I expected something like rebuilding compiler and adding some option to Lazarus project :) So far it looks like it's not far away from manually recompilation of each and every module.

Regards, Denis Golovan

Reply by email

Benito van der Zander
to denisgolovan: 12.01.2019, 14:53, "Jonas Maebe" : Could you give some hints on how to enable WPO/LTO…

Hi,

 something that appears  to help is to put units in the interface uses rather than the implementation uses.

When the unit mentioned in the interface uses, uses the first unit in its implementation uses, it can inline the functions from the first unit. Although I would have expected it to do the opposite.

Unfortunately that does not help if the second unit needs to use types from the first unit in its interface

Best,
Benito

denisgolovan wrote 15 lines
12.01.2019, 14:53, "Jonas Maebe" : > Not at this time (unless you use the LLVM backend). Could you give some hints on how to enable WPO/LTO under LLVM backend? http://wiki.freepascal.org/LLVM does not seem to mention anything on that. > However, what you actually can do, is manually recompile all units of your program > multiple times. While this won't help with inline functions called > before they are parsed in those same units, it will allow inlining of > function bodies in other units that were not available during the first > compilation due to dependency cycles. Eh. It does not look too practical, right? Unless there is some magical receipt for that :)

Reply by email