This is one of the properties of Interfaces. Take this example:
ITestInterface = interface
procedure Foo;
end;
// TTest1 and TTest2 implement ITestInterface:
procedure TTest1.Foo;
begin
WriteLn('Foo');
end;
procedure TTest2.Foo;
begin
WriteLn('Bar');
end;
...
var
test: ITestInterface;
begin
if Random mod 2 = 0 then
test := TTest1.Create as ITestInterface
else
test := TTest2.Create as ITestInterface;
test.Foo;
end;
In this case the compiler can't know which of the two to inline, as it is literally random. So the first observation is that in the general case, you can't inline calls to interface functions, because the called function is runtime dependend.
The second observation we as humans are really good at is to see that this actually can be inlined:
begin
if Random mod 2 = 0 then
begin
WriteLn('Foo') // Inline TTest1.Foo
else
WriteLn('Bar'); // Inline TTest2.Foo;
end;
This is can be achived with data- and control-flow analysis. Oversimplified is the calling of an interface method nothing more than calling a function pointer inside a record. So from a dfg and cfg point of view, whats written there is:
var
fp := procedure of object;
begin
if Random mod 2 = 0 then
fp := @TTest1.Create.Foo
else
fp := @TTest2.Create.Foo;
fp();
end;
So there are two branches through this code, either the if is taken or the else is taken. An analysis of both paths shows that in the end always the function written in fp will be called, but the value of fp is soly dependend on the branch taken. So the call can be moved into the if-statement and then it can be inlined.
This is called devirtualization, it is when (through static analysis) the compiler can prove that at a given point in the cfg only a certain virtual type is possible, and can therefore elimite the function pointer call with a normal function call.
This is something we humans are naturally good at detecting. But on a formal level this is hard (in the sense of computationally hard). The number of paths grows exponentially with the number of branches (if-statements). Meaning, if you have 3 conditions upon which combination the result of the function pointer depends, you have 2^3 = 8 different paths to check.
In fact many programs, including nearly all GUI applications, have an infinite number of execution paths (e.g. I could press button 1 once, twice, three times, etc, each resulting in a new execution path). Therefore this form of optimization is not only hard, but in general even impossible.
Also the assignment and the call can be seperated by thousands of levels of indirections (jumps to other methods, setting of flags, etc.) meaning even if this would be computationally possible, you would run out of memory pretty fast.
So to summarize, inlining of interface methods is in general not possible (i.e. undecidable). But there are certain cases where devirtualization is possible. The most obvious one is your example. Only one execution path, and assignment and call are not more than 1 instruction apart.
I don't know much about the inner workings of the FPC, when I have to write performance heavy code I use C++. And for both the GCC and LLVM I'm quite sure that on optimization level 2 or greater, this would be devirtualized by them.
I don't know much about the FPC optimizer, but if it isn't capable of doing something comparable for it's native targets, there is an LLVM target (in development), so maybe take a look into that.
But always remember, as Donald E. Knuth wrote:
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.
So if it's not performance critical code and interfaces improve the readability and maintainability of your code in most cases they are worth it.
And if you run into performance issues and your profiler tells you that you are wasting a lot of Time inside your Interfaces, consider a rewrite, but if you are not at that point now, this is the last thing you should bother about