View Issue Details

IDProjectCategoryView StatusLast Update
0037282FPCCompilerpublic2020-06-30 16:18
ReporterSerge Anvarov Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformWindows 
Product Version3.0.4 
Summary0037282: Passing a variable by reference to an empty (inline) procedure makes it no longer placed in a register
DescriptionWhen the compiler sees a call to another procedure, it stops placing the variable in the register, even if it doesn't affect the result.
In the example below, if you uncomment a dummy procedure call, optimization will stop, as you can see from the measurement results - almost 7 times slower (tested on Win64).
Steps To Reproduce{$APPTYPE CONSOLE}
{$MODE OBJFPC}

uses SysUtils;

procedure Dummy(var Value: Int64); inline;
begin
end;

procedure Test;
var
  Value: Int64;
  Ticks: QWord;
  i: SizeInt;
begin
  Ticks := GetTickCount64;
  Value := 1;
  for i := 1 to 1000*1000*1000 do
    Inc(Value);
  WriteLn(GetTickCount64 - Ticks);
  //Dummy(Value);
end;

begin
  Test;
  ReadLn;
end.
TagsNo tags attached.
Fixed in Revision
FPCOldBugId
FPCTarget-
Attached Files

Activities

Jonas Maebe

2020-06-28 17:59

manager   ~0123655

Last edited: 2020-06-28 19:26

View 2 revisions

It's not just "a call to another procedure", it's a call to a procedure that takes the address of the "Value" variable (to be able to pass it by reference). Taking the address of a variable generally makes it impossible to put it in a register, since it's impossible to take the address of a register.

You are of course correct that it _is_ possible in this case, and for multiple reasons:
1) the call happens after the loop, so in theory the variable could be kept in a register until then, and only then be placed in memory. However, FPC only has a context-insensitive version of the "address has been taken" analysis (i.e., it looks at all uses of the variable in the entire procedure, and uses the result of that for optimising the variable everywhere in the procedure), and it doesn't have SSA (so it cannot split the variable into two different variables, one of which can be kept in a register and one which is stored in memory).
2) the inlined procedure does not actually use the passed variable. This might be handled by an optimisation that records unused parameters (it already exists, but is only used for parentfp parameters currently iirc).

Florian

2020-06-29 21:57

administrator   ~0123671

Please submit a real world example, then we can discuss if it is fixable or not. This artificial example can be fixed easily but I doubt that the chosen fix will have any effect on real world code but wasting only compilation time.

Sven Barth

2020-06-30 16:18

manager   ~0123683

@Jonas: but shouldn't inlining get rid of most of these "call semantics" to allow for better optimizations?

@Florian: How about this?

{$APPTYPE CONSOLE}
{$MODE OBJFPC}

uses SysUtils;

procedure Dummy(var Value: Int64); inline;
begin
  Inc(Value);
end;

procedure Test;
var
  Value: Int64;
  Ticks: QWord;
  i: SizeInt;
begin
  Ticks := GetTickCount64;
  Value := 1;
  for i := 1 to 1000*1000*1000 do
    Dummy(Value);
  WriteLn(GetTickCount64 - Ticks);
end;

begin
  Test;
  ReadLn;
end.


After inlining - in my opinion - this code should behave like the original example with Value being placed in a register.

Issue History

Date Modified Username Field Change
2020-06-28 17:44 Serge Anvarov New Issue
2020-06-28 17:59 Jonas Maebe Note Added: 0123655
2020-06-28 18:01 Jonas Maebe Summary Optimization by the compiler using registers sometime wrong => Passing a variable by reference to an empty (inline) procedure makes it no longer placed in a register
2020-06-28 18:01 Jonas Maebe FPCTarget => -
2020-06-28 19:26 Jonas Maebe Note Edited: 0123655 View Revisions
2020-06-29 21:57 Florian Note Added: 0123671
2020-06-30 16:18 Sven Barth Note Added: 0123683