Jump to content
Mahdi Safsafi

Delphi inline and function evaluation

Recommended Posts

Hello guys,

I always thought that the Delphi compiler takes the inlined function and TRIES to insert it (without making a call instruction) where the call to that function occurs. However for some circumstances it can do better ! it TRIES to evaluate the function (just like constexpr in c++) !

Here is an example :

program Console1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

function Max(a, b: Integer): Integer; inline;
begin
  if (a > b) then
    Result := a
  else
    Result := b;
end;

var
  a: Integer = 5;
  b: Integer = 9;
  i: Integer;

begin
  Writeln('--------------------------------');
  i := Max(5, 9); // evaluated
  Writeln(Format('max=%d', [i]));
  i := Max(a, b); // not evaluated
  Writeln(Format('max=%d', [i]));
  Writeln('--------------------------------');
  Readln;
end.

After compiling the above example (x64, Release mode), the compiler generates the following code:


//------------------------------------------------------ Delphi ------------------------------------------------------------
0000000000428510 | 48:8B0D 59880000         | mov rcx,qword ptr ds:[430D70]           |
0000000000428517 | 48:8D15 6E010000         | lea rdx,qword ptr ds:[42868C]           | rdx:EntryPoint, 000000000042868C:L"--------------------------------"
000000000042851E | E8 2DEFFDFF              | call <console1.sub_407450>              |
0000000000428523 | 48:89C1                  | mov rcx,rax                             | rax:EntryPoint
0000000000428526 | E8 85F0FDFF              | call <console1.sub_4075B0>              |
000000000042852B | E8 60D4FDFF              | call <console1.sub_405990>              |
0000000000428530 | B8 09000000              | mov eax,9                               | eax:EntryPoint, 9:'\t'
0000000000428535 | 8905 3D420100            | mov dword ptr ds:[43C778],eax           | eax:EntryPoint
000000000042853B | 8B05 37420100            | mov eax,dword ptr ds:[43C778]           | eax:EntryPoint
0000000000428541 | 8945 28                  | mov dword ptr ss:[rbp+28],eax           | eax:EntryPoint
0000000000428544 | C645 30 00               | mov byte ptr ss:[rbp+30],0              |
0000000000428548 | 48:8D4D 38               | lea rcx,qword ptr ss:[rbp+38]           |
000000000042854C | 48:8D15 89010000         | lea rdx,qword ptr ds:[<sub_4286DC>]     | rdx:EntryPoint, 00000000004286DC:L"max=%d"
0000000000428553 | 4C:8D45 28               | lea r8,qword ptr ss:[rbp+28]            |
0000000000428557 | 4D:33C9                  | xor r9,r9                               | r9:EntryPoint
000000000042855A | E8 C16AFFFF              | call <console1.sub_41F020>              |
000000000042855F | 48:8B0D 0A880000         | mov rcx,qword ptr ds:[430D70]           |
0000000000428566 | 48:8B55 38               | mov rdx,qword ptr ss:[rbp+38]           | rdx:EntryPoint
000000000042856A | E8 E1EEFDFF              | call <console1.sub_407450>              |
000000000042856F | 48:89C1                  | mov rcx,rax                             | rax:EntryPoint
0000000000428572 | E8 39F0FDFF              | call <console1.sub_4075B0>              |
0000000000428577 | E8 14D4FDFF              | call <console1.sub_405990>              |
000000000042857C | 8B05 72860000            | mov eax,dword ptr ds:[430BF4]           | eax:EntryPoint
0000000000428582 | 3B05 70860000            | cmp eax,dword ptr ds:[430BF8]           | eax:EntryPoint
0000000000428588 | 7E 08                    | jle console1.428592                     |
000000000042858A | 8B05 64860000            | mov eax,dword ptr ds:[430BF4]           | eax:EntryPoint
0000000000428590 | EB 06                    | jmp console1.428598                     |
0000000000428592 | 8B05 60860000            | mov eax,dword ptr ds:[430BF8]           | eax:EntryPoint
0000000000428598 | 8905 DA410100            | mov dword ptr ds:[43C778],eax           | eax:EntryPoint
000000000042859E | 8B05 D4410100            | mov eax,dword ptr ds:[43C778]           | eax:EntryPoint
00000000004285A4 | 8945 28                  | mov dword ptr ss:[rbp+28],eax           | eax:EntryPoint
00000000004285A7 | C645 30 00               | mov byte ptr ss:[rbp+30],0              |
00000000004285AB | 48:8D4D 20               | lea rcx,qword ptr ss:[rbp+20]           |
00000000004285AF | 48:8D15 26010000         | lea rdx,qword ptr ds:[<sub_4286DC>]     | rdx:EntryPoint, 00000000004286DC:L"max=%d"
00000000004285B6 | 4C:8D45 28               | lea r8,qword ptr ss:[rbp+28]            |
00000000004285BA | 4D:33C9                  | xor r9,r9                               | r9:EntryPoint
00000000004285BD | E8 5E6AFFFF              | call <console1.sub_41F020>              |
00000000004285C2 | 48:8B0D A7870000         | mov rcx,qword ptr ds:[430D70]           |
00000000004285C9 | 48:8B55 20               | mov rdx,qword ptr ss:[rbp+20]           | rdx:EntryPoint
00000000004285CD | E8 7EEEFDFF              | call <console1.sub_407450>              |
00000000004285D2 | 48:89C1                  | mov rcx,rax                             | rax:EntryPoint
00000000004285D5 | E8 D6EFFDFF              | call <console1.sub_4075B0>              |
00000000004285DA | E8 B1D3FDFF              | call <console1.sub_405990>              |
00000000004285DF | 48:8B0D 8A870000         | mov rcx,qword ptr ds:[430D70]           |
00000000004285E6 | 48:8D15 9F000000         | lea rdx,qword ptr ds:[42868C]           | rdx:EntryPoint, 000000000042868C:L"--------------------------------"
00000000004285ED | E8 5EEEFDFF              | call <console1.sub_407450>              |
00000000004285F2 | 48:89C1                  | mov rcx,rax                             | rax:EntryPoint
00000000004285F5 | E8 B6EFFDFF              | call <console1.sub_4075B0>              |
00000000004285FA | E8 91D3FDFF              | call <console1.sub_405990>              |
00000000004285FF | 48:8B0D 9A880000         | mov rcx,qword ptr ds:[430EA0]           |
0000000000428606 | E8 45E6FDFF              | call <console1.sub_406C50>              |
000000000042860B | E8 80D3FDFF              | call <console1.sub_405990>              |
0000000000428610 | 90                       | nop                                     |

You can see that the compiler was able to evaluate the first expression "i := Max(5, 9);" and just generated one single instruction "mov eax, 9".

For the second call "i := Max(a, b);" the compiler didn't make any evaluation and it just inlined the function.

This is really impressive ! the first call was completely evaluated at the compiling time.

I also tested the above example with MSVC and the result was quite different (I was expecting that before running my debugger)

#include "pch.h"
#include <iostream>

inline int max(int a, int b) {
	return a > b ? a : b;
}

constexpr int max2(int a, int b) {
	return a > b ? a : b;
}

int a = 5;
int b = 9;
int i;
int main()
{
	printf("--------------------------------\n");


	i = max(5, 9);    // evaluated
	printf("max=%d\n", i);
	i = max(a, b);    // evaluated
	printf("max=%d\n", i);
	i = max2(5, 9);   // evaluated
	printf("max=%d\n", i);
	i = max2(a, b);   // evaluated
	printf("max=%d\n", i);
	printf("--------------------------------\n");
}
//------------------------------------------------------ CPP ------------------------------------------------------------
00007FF7DB431070 | 48:83EC 28               | sub rsp,28                              | consoleapplication4.cpp:19
00007FF7DB431074 | 48:8D0D 85110000         | lea rcx,qword ptr ds:[7FF7DB432200]     | consoleapplication4.cpp:20, 00007FF7DB432200:"--------------------------------\n"
00007FF7DB43107B | E8 90FFFFFF              | call <consoleapplication4.printf>       |
00007FF7DB431080 | BA 09000000              | mov edx,9                               | consoleapplication4.cpp:24, 9:'\t'
00007FF7DB431085 | C705 91250000 09000000   | mov dword ptr ds:[<i>],9                | 9:'\t'
00007FF7DB43108F | 48:8D0D 92110000         | lea rcx,qword ptr ds:[7FF7DB432228]     | 00007FF7DB432228:"max=%d\n"
00007FF7DB431096 | E8 75FFFFFF              | call <consoleapplication4.printf>       |
00007FF7DB43109B | BA 09000000              | mov edx,9                               | consoleapplication4.cpp:26, 9:'\t'
00007FF7DB4310A0 | C705 76250000 09000000   | mov dword ptr ds:[<i>],9                | 9:'\t'
00007FF7DB4310AA | 48:8D0D 77110000         | lea rcx,qword ptr ds:[7FF7DB432228]     | 00007FF7DB432228:"max=%d\n"
00007FF7DB4310B1 | E8 5AFFFFFF              | call <consoleapplication4.printf>       |
00007FF7DB4310B6 | BA 09000000              | mov edx,9                               | consoleapplication4.cpp:28, 9:'\t'
00007FF7DB4310BB | C705 5B250000 09000000   | mov dword ptr ds:[<i>],9                | 9:'\t'
00007FF7DB4310C5 | 48:8D0D 5C110000         | lea rcx,qword ptr ds:[7FF7DB432228]     | 00007FF7DB432228:"max=%d\n"
00007FF7DB4310CC | E8 3FFFFFFF              | call <consoleapplication4.printf>       |
00007FF7DB4310D1 | BA 09000000              | mov edx,9                               | consoleapplication4.cpp:30, 9:'\t'
00007FF7DB4310D6 | C705 40250000 09000000   | mov dword ptr ds:[<i>],9                | 9:'\t'
00007FF7DB4310E0 | 48:8D0D 41110000         | lea rcx,qword ptr ds:[7FF7DB432228]     | 00007FF7DB432228:"max=%d\n"
00007FF7DB4310E7 | E8 24FFFFFF              | call <consoleapplication4.printf>       |
00007FF7DB4310EC | 48:8D0D 0D110000         | lea rcx,qword ptr ds:[7FF7DB432200]     | consoleapplication4.cpp:31, 00007FF7DB432200:"--------------------------------\n"
00007FF7DB4310F3 | E8 18FFFFFF              | call <consoleapplication4.printf>       |
00007FF7DB4310F8 | 33C0                     | xor eax,eax                             | consoleapplication4.cpp:32
00007FF7DB4310FA | 48:83C4 28               | add rsp,28                              |
00007FF7DB4310FE | C3                       | ret                                     |

Note that MSVC also generated a very short routine compared to what Delphi generated!

Edited by Mahdi Safsafi

Share this post


Link to post
10 minutes ago, David Heffernan said:

C++ compilers are generally far better at optimisation than any Delphi compilers 

of course ! But you know : the MSVC's output didn't impressed me since I was expecting to get such result(especially with the constexpr specifier). However, Delphi did ! I never thought that it can do function evaluation on the fly. 

Share this post


Link to post
On 3/3/2019 at 1:55 PM, Mahdi Safsafi said:

I never thought that it can do function evaluation on the fly. 

Why does that surprise you? ANY compiler worth its salt will perform optimizations, especially when dealing with inline functions, and would be smart enough to recognize when basic arithmetic operations are being performed on compile-time literals and just output code for the result of those operations at compile-time instead of generating code to perform those operations at runtime.

 

"5 > 9" is ALWAYS false, so the compiler can just output False instead of outputting code to compare 5 to 9.  And "if False Then" will ALWAYS skip the IF block and jump to the ELSE block. so the compiler can ignore any of the code in the IF block.  As such, your call to Max(5, 9) gets optimized to simply "Result := 9" when inlined, and so the compiler can simply replace the entire call to Max(5, 9) with just the literal 9.

Edited by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×