Disclaimer: The following text contains information that applies only to Delphi 10.2.1 (as well as 10.2.2), which seem to have done something worse in inlining and RVO:
The code generated by the compiler is really different (regardless of the version of the compiler), as you can easily see when viewing the disassembly window.
Take this procedure:
procedure Main; var i: Integer; s: string; begin i := 0; s := IntToStr(i); s := i.ToString; end;
Now run this and look at the disassembly window to check the code generated by the compiler:
This is what you got with Delphi 10.1:
Project1.dpr.14: s := IntToStr(i); 00419810 8D55F8 lea edx,[ebp-$08] 00419813 8B45FC mov eax,[ebp-$04] 00419816 E80DA4FFFF call IntToStr Project1.dpr.15: s := i.ToString; 0041981B 8D55F4 lea edx,[ebp-$0c] 0041981E 8B45FC mov eax,[ebp-$04] 00419821 E802A4FFFF call IntToStr 00419826 8D45F8 lea eax,[ebp-$08] 00419829 8B55F4 mov edx,[ebp-$0c] 0041982C E843D2FEFF call @UStrLAsg
And this is what you get with 10.2.1 (as well as 10.2.2):
Project1.dpr.14: s := IntToStr(i); 00419B04 8D55F8 lea edx,[ebp-$08] 00419B07 8B45FC mov eax,[ebp-$04] 00419B0A E8C5A2FFFF call IntToStr Project1.dpr.15: s := i.ToString; 00419B0F 33C0 xor eax,eax 00419B11 55 push ebp 00419B12 68499B4100 push $00419b49 00419B17 64FF30 push dword ptr fs:[eax] 00419B1A 648920 mov fs:[eax],esp 00419B1D 8D55F4 lea edx,[ebp-$0c] 00419B20 8B45FC mov eax,[ebp-$04] 00419B23 E8ACA2FFFF call IntToStr 00419B28 8D45F8 lea eax,[ebp-$08] 00419B2B 8B55F4 mov edx,[ebp-$0c] 00419B2E E805D0FEFF call @UStrLAsg 00419B33 33C0 xor eax,eax 00419B35 5A pop edx 00419B36 59 pop ecx 00419B37 59 pop ecx 00419B38 648910 mov fs:[eax],edx 00419B3B 68509B4100 push $00419b50 00419B40 8D45F4 lea eax,[ebp-$0c] 00419B43 E8D4CCFEFF call @UStrClr 00419B48 C3 ret 00419B49 E9CEC3FEFF jmp @HandleFinally 00419B4E EBF0 jmp $00419b40
Now a million dollar question, what kind of additional instructions are there ?!
Additional instructions that you can see in both compilers are the result of the lack of so-called return value optimization. As you know, the compiler treats the results of functions that have a managed type (for example, string) as a hidden var parameter. Now, when the compiler makes an attachment, it does not eliminate this parameter and directly passes the variable s to IntToStr , as it happens with a direct call. Rather, it reserves the temporary variable that it uses to go to IntToStr , and then after that assigns this variable s (that call @UStrLAsg you see 3 lines there after calling IntToStr ).
As I mentioned above, in 10.2 or 10.2.1 there is a regression where they changed something about temporarily clearing variables immediately after the built-in call (which is additional instructions before and after).
Marked as RSP-19439 .
To be continued...