Sunday, April 11, 2021

Out parameters are just bad var parameters

After talking about const parameters last time today we take a closer look at out parameters and how they differ from var parameters.

I like the concept of out parameters as they clearly state that only the value that comes back matters and not the value that goes in. However, the Delphi implementation of out parameters has some aspects to it that can cause a decision against them and rather choose a var parameter. As last time we will look at some assembler code that shows what those differences are. Out parameters like var parameters pass a reference to the value rather than the value itself.

The code for this article will be this:
{$APPTYPE CONSOLE}
{$O+,W-}
type
  TTestType = ...;

procedure C(z: TTestType);
begin
end;

procedure A(var y: TTestType);
begin
  y := Default(TTestType);
end;

procedure B(out y: TTestType);
begin
  y := Default(TTestType);
end;

procedure Main;
var
  x: TTestType;
begin
  A(x);
  B(x);
end;
Like last time, we will inspect the code for different types for TTestType and see how it differs. Let's start with Integer - the code we will see for the calls and inside A and B are as follows:
OutParams.dpr.24: A(x);
00408FA5 8BC4             mov eax,esp
00408FA7 E8E8FFFFFF       call A
OutParams.dpr.25: B(x);
00408FAC 8BC4             mov eax,esp
00408FAE E8E9FFFFFF       call B
OutParams.dpr.12: y := Default(TTestType);
00408F94 33D2             xor edx,edx
00408F96 8910             mov [eax],edx
OutParams.dpr.13: end;
00408F98 C3               ret
OutParams.dpr.17: y := Default(TTestType);
00408F9C 33D2             xor edx,edx
00408F9E 8910             mov [eax],edx
OutParams.dpr.18: end;
00408FA0 C3               ret
Nothing really fancy here - esp is the address of the local variable x. The (unfortunately still undocumented) intrinsic function Default() takes care of setting the parameter to the default value of the given type - so in this case 0. The code for the var parameter will look exactly the same. For other types, the code will look similar but something interesting happens when we use a managed type such as string. Take a look at how this changes the code being generated:
OutParams.dpr.24: A(x);
00408FCB 8D45FC           lea eax,[ebp-$04]
00408FCE E8C1FFFFFF       call A
OutParams.dpr.25: B(x);
00408FD3 8D45FC           lea eax,[ebp-$04]
00408FD6 E8F1CEFFFF       call @UStrClr
00408FDB E8C0FFFFFF       call B
On the caller side, we now see a call to System._UStrClr which the compiler inserted here to ensure that x will be empty before being passed to A. Out parameters are always initialized before they are passed, unlike var parameters. When we will take a look at the code inside A we will see nothing unusual except a lack of optimization. eax could directly be passed to System._UStrClr - and that is not a result of using Default() but also happens when assigning '' to y:
OutParams.dpr.11: begin
00408F94 53               push ebx
00408F95 8BD8             mov ebx,eax
OutParams.dpr.12: y := Default(TTestType);
00408F97 8BC3             mov eax,ebx
00408F99 E82ECFFFFF       call @UStrClr
OutParams.dpr.13: end;
00408F9E 5B               pop ebx
00408F9F C3               ret
The interesting difference however will be obvious when we will look into B:
OutParams.dpr.16: begin
00408FA0 55               push ebp
00408FA1 8BEC             mov ebp,esp
00408FA3 53               push ebx
00408FA4 8BD8             mov ebx,eax
00408FA6 85DB             test ebx,ebx
00408FA8 7404             jz $00408fae
00408FAA 33C0             xor eax,eax
00408FAC 8903             mov [ebx],eax
OutParams.dpr.17: y := Default(TTestType);
00408FAE 8BC3             mov eax,ebx
00408FB0 E817CFFFFF       call @UStrClr
OutParams.dpr.18: end;
00408FB5 5B               pop ebx
00408FB6 5D               pop ebp
00408FB7 C3               ret
What is going on here? The first two instructions are setting up a frame pointer which is usual but would not be necessary in this case - especially since we turned off that option. The following lines basically ensure that our y parameter really is empty - again with a lack of optimization.

Why is this happening? The caller side ensured that y is empty. This code is for C++Builder compatibility! C++ does not have the concept of out parameter but just by reference parameter which equals the var parameter in Delphi. And because of that when C++ would call such a routine the value would not have been initialized. Unfortunately, at least to my knowledge, there is no option to turn this off because our code will never be called from C++. We have built an executable here in Delphi; our function is not exported nor did we make a DLL. When using out parameters you pay a price for some most likely unused feature as most if not all Delphi code you ever write will never be called from any C++ code. The impact of out parameters is so significant in some cases that in 10.4 some of them were changed to var in the RTL - take a look at most parameters of TValue in the unit System.Rtti. Because for records this overhead can be even bigger and even more when they are passed multiple levels of out parameters because every call and routine again produces this completely unnecessary overhead.

The entire concept of out parameters to me looks completely unfinished - let's for a second take a look at some C# code which also has out parameters
static void B(out int y)
{
    WriteLine(y);
}
This code will raise two errors:
Error	CS0269	Use of unassigned out parameter 'y'
Error	CS0177	The out parameter 'y' must be assigned to before control leaves the current method
And the compiler is right - since the parameter is out the value going in actually is undefined behavior - and if the compiler ensures that it cannot be used then it also does not need to do any initialization ensuring any value. That means no unnecessary work to do and no inconsistent behavior - remember unmanaged data types don't get that extra treatment, whatever value is in an int variable being passed is still in there when being passed to an out parameter.
Second, the compiler ensures setting a value to the out parameter ensuring it is not undefined when returning from this routine. Would an exception occur during the invocation of that routine and before the out parameter was set then upon returning and catching the exception the variable would still contain the value it had before - a totally sane and understandable behavior.

As for me - I will be more careful where I use out parameters and in fact, during the refactoring of the collections in Spring4D, I replaced most out parameters with var parameters. Since even without the compiler enforcing this, all code paths inside those methods eventually set the value of the parameter it will be no change in behavior for you but avoid this completely insane overhead.

Saturday, March 27, 2021

How to hide the CodeInsight status panel

The new CodeInsight status panel added in 10.4.2 can be very distracting with its busy progressbar. Unfortunately there is no builtin mechanism to hide it but doing so is very easy.

Edit: Apparently as Lachlan pointed out on the comments I totally missed the "Code Insight Activity" toggle in the Options dropdown of the Project manager toolbar:



I leave the obsolete way here for documentation - here is the code to add a toggle button to the projectmanager toolbar (adding an icon is left as an excersise to the reader):

unit ToggleLSPStatusPanel;

interface

procedure Register;

implementation

uses
  System.Classes,
  Vcl.Forms,
  Vcl.ComCtrls,
  Vcl.ExtCtrls;

var
  LSPPanel: TPanel;
  ToolButton: TToolButton;

procedure TogglePanel(instance: TObject; Sender: TObject);
begin
  LSPPanel.Visible := not LSPPanel.Visible;
end;

procedure Register;
var
  e: TNotifyEvent;
begin
  var projectManagerForm := Application.MainForm.FindComponent('ProjectManagerForm');
  var toolBar := TToolBar(projectManagerForm.FindComponent('Toolbar'));
  ToolButton := TToolButton.Create(nil);
  LSPPanel := TPanel(projectManagerForm.FindComponent('pLSPStatus'));
  ToolButton.Left := toolbar.Width;
  ToolButton.Parent := toolbar;
  TMethod(e).Data := nil;
  TMethod(e).Code := @TogglePanel;
  ToolButton.OnClick := e;
end;

initialization

finalization
  try
    ToolButton.Free;
  except 
    // simply swallow the exception when closing the IDE as 
    // it will already have destroyed the button
    // feel free to make this more robust
  end;

end.

Simply add this unit to a designtime package and install it. Enjoy!

Thursday, March 25, 2021

Introducing Delphi Uses Helper

I just wanted to share a small IDE plugin I wrote some time ago which helps adding units to your uses clause and/or navigating through code without having to rely on the faulty "Find declaration" feature.


Installation and configuration

After installation go to Tools->Options in the IDE and then to Third Party->UsesHelper (or just hit F6, type "UsesHelper" and hit Enter)

Add any directories whose source you want to have indexed (recursively) and add directories that should be excluded from the indexing - one directory per line, no extra separators. Variables are supported like they are in other places in the IDE such as library path - as you can see with the already present entry for the Delphi sources.

Under "Unit scope names" you can add those unit scopes you want to omit when adding units to the uses clause - so if you for example like me write code that has to work with versions before XE2 this might be handy to add just Classes rather than System.Classes.

If any changes were made it will automatically start indexing the source upon hitting save - indexing will only happen at that point or when you hit the button, not on starting the IDE or at any other point (I may add this at some point in the future).


Supported versions: 10.1.2, 10.2.3, 10.3.3, 10.4.2


What it does internally

Indexed are: types, consts, global variables, routines, enums - did I miss anything? Well, basically everything inside the interface section of every unit it finds.

For the parsing of the files DelphiAST is being used. Any errors that might occur are being printed out in the messages panel - I am not giving support for any source code that might not be parsed properly - if you have some issue please verify it with the demo project coming with DelphiAST and report the issue over there. Also be aware that parsing might fail if you have units that are littered with custom compiler defines as the parsing mechanism is not aware of them - it just operates with the defines that DelphiAST uses (which are those of a standard Win32 application).

Indexing may take a while - it will print out when it's done.

The index is being stored in a file per Delphi version you have the plugin installed in (the settings are also per Delphi version) that is stored in the installation directory which is %localappdata%\programs\useshelper but loaded into memory by the plugin. Additionally it will index files within the currently opened project on the fly when being invoked.

Exception: System and SysInit are not being indexed as you cannot add those units to uses clause anyway but this will avoid navigating to any identifier in those units.


Format of the index.dat

If you fancy you can write that file yourself - the format is fairly simple:

<symbolname>=<unitname>|<int32> - the upper 8bit of that number are the column and the lower 24bit are the line number


Now lets try it out

When you are in the code and have the caret on some identifier you can hit Ctrl+Shift+A - which usually invokes a similar but way worse feature of the IDE - or whatever hotkey you set this to.

You will see list of units where this identifier is found in - the match has to be exact, no fuzzy matching, no guessing. It you type TSrtingList it won't find anything I guess. (implementing any kind of more sophisticated search is not planned)

Now you can navigate with the up/down keys if there are multiple units to chose from or switch between interface/implementation with the left/right keys (it currently does not detect in which section you currently are to make a best guess).

If you then hit enter the unit will get added to the appropriate uses clause - it will get added at the end and it will in a new line - no configuration to control that behavior and I will not go into the mess of doing so.

If you hit Shift+Enter the unit you selected will be opened and you will navigate to the declaration. This works even if you have no project open which "find declaration" does not.

Yes, the dialog is modal - leaving it without doing anything only works with the Esc key at this point.

Currently moving a unit between interface and implementation is not supported.


Anyhow enjoy free stuff as some selected people and me did for quite a while now and don't pester me with feature requests. ;)


Download here

Update 26.03.2021: reuploaded the zip file as it contained old timestamps in the setup file - version did not change.

Saturday, March 20, 2021

Const parameters are an implementation detail

A parameter with a const is different from one without const in terms of a function signature. But is that actually true? Is A(x: T) really different from B(const x: T)?

Disclaimer: In this article I am focusing on the default calling convention on Windows in Delphi which is register.

We will go onto a journey through some assembler code and look into various method calls with different typed parameters both const and no const and explore their differences. Don’t worry - I will explain the assembler in detail.

I have following code for you that we will use with different types to explore:

{$APPTYPE CONSOLE}
{$O+,W-}
type
  TTestType = …;

procedure C(z: TTestType);
begin
end;

procedure A(y: TTestType);
begin
  C(y);
end;

procedure B(const y: TTestType);
begin
  C(y);
end;

procedure Main;
var
  x: TTestType;
begin
  A(x);
  B(x);
end;

We will put different types for TTestType, put a breakpoint into Main and see how the code looks like for the calls and inside of A and B. I will be using 10.4.2 for this experiment. We will ignore the warning because it does not matter for the experiment.

Let’s start with something simple: Integer

When we look into the disassembly we see the following:

Hamlet.dpr.24: A(x);
00408FA9 8BC3             mov eax,ebx
00408FAB E8E8FFFFFF       call A
Hamlet.dpr.25: B(x);
00408FB0 8BC3             mov eax,ebx
00408FB2 E8E9FFFFFF       call B

eax is the register being used for the first and only parameter we have in our routines. ebx is the register where x is being stored during the execution of Main. No difference between the two calls though – and here is how it looks inside of A and B. In case you wonder – the calls to C are just there to prevent the compiler to optimize away some code which will be important later.

Hamlet.dpr.12: C(y);
00408F98 E8F7FFFFFF       call C
Hamlet.dpr.13: end;
00408F9D C3               ret 
Hamlet.dpr.17: C(y);
00408FA0 E8EFFFFFFF       call C
Hamlet.dpr.18: end;
00408FA5 C3               ret

Also the same, the value is being kept in the register it was being passed in – eax – and C is called. We get the same identical code for various other ordinal types such as Byte, SmallInt or Cardinal, enums or sets that are up to 4 byte in size, Pointer or TObject (did someone say “not on ARC”? Well we are on 10.4.2 – I erased that from my memory already – and soon from my code as well).

Let’s explore Int64 next:

Hamlet.dpr.24: A(x);
00408FC7 FF742404         push dword ptr [esp+$04]
00408FCB FF742404         push dword ptr [esp+$04]
00408FCF E8C8FFFFFF       call A
Hamlet.dpr.25: B(x);
00408FD4 FF742404         push dword ptr [esp+$04]
00408FD8 FF742404         push dword ptr [esp+$04]
00408FDC E8CFFFFFFF       call B

And B and C:

Hamlet.dpr.11: begin
00408F9C 55               push ebp
00408F9D 8BEC             mov ebp,esp
Hamlet.dpr.12: C(y);
00408F9F FF750C           push dword ptr [ebp+$0c]
00408FA2 FF7508           push dword ptr [ebp+$08]
00408FA5 E8EAFFFFFF       call C
Hamlet.dpr.13: end;
00408FAA 5D               pop ebp
00408FAB C20800           ret $0008
Hamlet.dpr.16: begin
00408FB0 55               push ebp
00408FB1 8BEC             mov ebp,esp
Hamlet.dpr.17: C(y);
00408FB3 FF750C           push dword ptr [ebp+$0c]
00408FB6 FF7508           push dword ptr [ebp+$08]
00408FB9 E8D6FFFFFF       call C
Hamlet.dpr.18: end;
00408FBE 5D               pop ebp
00408FBF C20800           ret $0008

On 32bit an Int64 is passed on the stack as you can see – nothing fancy here but reserving a stack frame and passing on the value to C. But no difference between the two routines.

I don’t want to bore you death with walls of identical assembler code so I can tell you that for all the floating point types, enums and sets regardless their size the calls to A and B are always identical.

But now for the first difference – lets take a look at set of Byte – the biggest possible set you can have in Delphi. A set is actually a bitmask with one bit for every possible value – in this case this type is 256 bit in size or 32 byte. Like many other types that don’t fit into a register it will actually be passed by reference – this time I also show you the prologue of Main where you can see that the compiler reserved 32 byte on the stack for our x variable and that this time the address of the value is being passed to A and B which is in the esp register - the stack pointer:

Hamlet.dpr.23: begin
00408FC8 83C4E0           add esp,-$20
Hamlet.dpr.24: A(x);
00408FCB 8BC4             mov eax,esp
00408FCD E8C6FFFFFF       call A
Hamlet.dpr.25: B(x);
00408FD2 8BC4             mov eax,esp
00408FD4 E8DFFFFFFF       call B

Let’s take a look into A where we can see the first difference:

Hamlet.dpr.11: begin
00408F98 56               push esi
00408F99 57               push edi
00408F9A 83C4E0           add esp,-$20
00408F9D 8BF0             mov esi,eax
00408F9F 8D3C24           lea edi,[esp]
00408FA2 B908000000       mov ecx,$00000008
00408FA7 F3A5             rep movsd 
Hamlet.dpr.12: C(y);
00408FA9 8BC4             mov eax,esp
00408FAB E8E4FFFFFF       call C
Hamlet.dpr.13: end;
00408FB0 83C420           add esp,$20
00408FB3 5F               pop edi
00408FB4 5E               pop esi
00408FB5 C3               ret

First the non-volatile registers (that means their values need to be preserved over subroutine calls, in other words, when A returns the values in those registers must be the same as they were before A was being called) are being saved. Then we see again reserving 32byte on the stack as previously.

The next four instructions: the parameter being passed eax is stored in esi, that is the address to our value x that we passed in y, then the address of esp is loaded into edi. The rep instruction does something interesting: it repeats the movsd instruction (not to be confused with the movsd – hey it’s called complex instruction set for a reason) –moving 4 byte as often as ecx says using esi as source address and edi as destination. Long story short, it does a local copy of our set.

There we have our first difference – the compiler produces a copy of our parameter value – if the C call would not be here the compiler would actually optimize that away as it detects there is nothing to do with y. And it does so even though we just read the value and not actually modify it as we could with a non const parameter.

Finally the type many of you have been waiting for: string

Hamlet.dpr.24: A(x);
004F088B 8B45FC           mov eax,[ebp-$04]
004F088E E88DFFFFFF       call A
Hamlet.dpr.25: B(x);
004F0893 8B45FC           mov eax,[ebp-$04]
004F0896 E8CDFFFFFF       call B

Since string is a managed type – meaning the compiler inserts code that ensures its initialized as empty string and gets finalized we got quite some code in Main this time and the variable is on the stack – string is a reference type so in terms of size its just the same as a pointer fitting in eax. Just to avoid confusion – string is a reference type but the parameter is by value – the value of the string reference is passed in the eax register just like earlier for Integer and alike.

Fasten your seatbelts as we take a look into the code of A:

Hamlet.dpr.11: begin
004F0820 55               push ebp
004F0821 8BEC             mov ebp,esp
004F0823 51               push ecx
004F0824 8945FC           mov [ebp-$04],eax
004F0827 8B45FC           mov eax,[ebp-$04]
004F082A E8D994F1FF       call @UStrAddRef
004F082F 33C0             xor eax,eax
004F0831 55               push ebp
004F0832 685D084F00       push $004f085d
004F0837 64FF30           push dword ptr fs:[eax]
004F083A 648920           mov fs:[eax],esp
Hamlet.dpr.12: C(y);
004F083D 8B45FC           mov eax,[ebp-$04]
004F0840 E8AFFFFFFF       call C
Hamlet.dpr.13: end;
004F0845 33C0             xor eax,eax
004F0847 5A               pop edx
004F0848 59               pop ecx
004F0849 59               pop ecx
004F084A 648910           mov fs:[eax],edx
004F084D 6864084F00       push $004f0864
004F0852 8D45FC           lea eax,[ebp-$04]
004F0855 E8CA93F1FF       call @UStrClr
004F085A 58               pop eax
004F085B FFE0             jmp eax
004F085D E9DA89F1FF       jmp @HandleFinally
004F0862 EBEE             jmp $004f0852
004F0864 59               pop ecx
004F0865 5D               pop ebp
004F0866 C3               ret

Ok, that’s quite a lot of code but the important thing is this - the compiler basically turned the code into this:

var
  z: Pointer;
begin
  z := Pointer(y);
  System._UStrAddRef(z);
  try
    C(z);
  finally
    System._UStrClr(z);
  end;
end;

The System routine _UStrAddRef checks if the string is not empty and increases its reference count if it’s not a reference to a constant string because they have a reference count of -1 meaning they are not allocated on the heap but point to some location in your binary where the string constant is located. _UStrClr clears the passed string variable and reduces its reference count under the same conditions.

So why is that being done? The string was passed as non const causing the compiler to ensure that nothing goes wrong with the string by bumping its reference count for the time of the execution of this routine. If some code would actually modify the string the code to do that would see that the string – assuming we have one that had a reference count of at least 1 when entering the method – has a reference count of at least 2. This would trigger the copy on write mechanic and keep the string that was originally passed to the routine intact because even though a string is a reference type underneath it also has characteristics of a value type meaning that when we modify it we don’t affect anyone else that also holds a reference.

Now the code of B:

Hamlet.dpr.17: C(y);
004F0868 E887FFFFFF       call C
Hamlet.dpr.18: end;
004F086D C3               ret

That’s almost disappointing after so much code in A. But joking aside the const had a significant effect: all that reference bumping, try finally to ensure proper reference dropping in case of an exception is not necessary because the parameter being const ensures that it cannot be modified.

The code being executed in A is not the end of the world or will utterly destroy the performance of your application but is completely unnecessary most of the time. And in many cases it can indeed mean the difference for the fast execution of an algorithm. Yes, I know we can construct some terrible code that causes bugs when the parameter is const because we have a circular data dependency but let’s not talk about this – because this has already been discussed.

Similar code can be found for interfaces, dynamic arrays and managed records (both kinds, records with managed field types and the new custom managed records).

There are not many more types left – records and static arrays depending on their size are either passed by value or reference or in the fancy case of being of size 3 pushed onto the stack.

For a complete summary please refer to this table.

As we can see from the caller side a const and a non const parameter are not any different for the register calling convention. For other calling conventions however they differ – especially on non Windows platforms. As an example with the stdcall convention a record that is bigger than a pointer is being pushed onto the stack as a non const parameter while as a const parameter just its address is being pushed so basically passed by reference.

That was a rather lengthy adventure – but what did we learn?

For most code on Windows a const parameter is actually an implementation detail and does not affect the code on the caller side. But knowing that it depends on the platform and the calling convention makes it clear that as much as some would like non const and const parameters to be compatible in order to easily add some missing const throughout the RTL or VCL where it would have a benefit of removing unnecessary instructions is simply impossible. Nor could we simply assume non const parameters to behave similar to const parameters.

Using const parameters can avoid unnecessary instructions and speed up your code without any cost or side effects. There is no reason to not make your string, interface, dynamic array or record parameters const. That can be even more important if you are a library developer.

In the next article we will explore another kind of parameter: out

Monday, November 30, 2020

Extended Q&A to my DelphiCon session about Spring4D

Many of you have probably seen my session at DelphiCon (watch directly on Youtube) and the overall feedback has been quite positive - many thanks to you and I hope I could stir up some interest if it was completely new for you.

In this article I will address the questions that came up during the presentation but have not yet been answered. Please leave a comment if I missed any.


Q: When using Shared<T> can I determine how many shares are in play and therefore, if I'm processing the last running share?

A: Not without hacking into the data structure. Since you can pass a finalizer that automatically triggers when the last one goes out of scope that should not be necessary.


Q: Shared<T>, IShared<T> are really nice! What about performance?

A: While they involve a small heap allocation and atomic reference counting everything is as fast as it can be without any unnecessary overhead. In fact there is no object underneath implementing the interface but a handcrafted memory block which avoids overhead of object construction and destruction and adjustor thunks for the method calls. I will share some more details on this approach in a future blog post as I have been using this in several places now to achieve that extra bit of performance and reduce binary size especially for generics.


Q: Can the Shared<T> be used as a class field that you pass to other classes? Also can it be a function result that you keep until the end? I'm thinking about using this with an api wrapper like you said.

A: Absolutely!


Q: Can we / could we do unique pointer in Delphi?

A: If my understanding of a unique pointer is not wrong it prevents any assignment and makes sure that there is just one reference. If I am again not mistaken this is achieved in C++ by hiding/not giving an assignment/copy operator. This is not possible in Delphi. I was hoping that custom managed records get this functionality but unfortunately they didn't. So there is no way to prevent having an explicit or implicit assignment happening.


Q: What will it take to extend Spring4D's reach to FPC (and, in turn, Pas2js -- now that generics are supported) so we can enjoy Spring4D for type-safe Web application development?

A: I have looked into FPC several times over the years and while many people praise it for being the open source rescue from Delphi it still lacks things that many of us take for granted in Delphi such as anonymous methods - I don't know their current state but they are integral part of the majority of the library. Pas2js is a bit different and I would not consider it the regular FPC because TMS is pushing this to make TMSWebCore work which aims for Delphi compatibility. I am certainly interested in looking into it at some point but that will not be any time soon. I also don't want to get into some ifdef hell - I just have been happy about removing a lot that were for Delphi2010 and probably a few more for ARC soon.


Q: What font are you using in the editor? It is very nice to write code!

A: I was hoping for anyone to notice. ;) It is JetBrains Mono - you can get it here. Make sure to use those directly in the ttf folder of that zip file because the Delphi code editor does not support ligatures.


Q: Are any of the IEnumerable functions using threads behind the scenes to speed up processing. For example, complex where clauses on massive lists of objects.

A: No, there is no multithreading going on - the implementation is pretty much similar to the one you find in System.Linq in .NET. Due to the extensible nature of the library by using interfaces you can just make your own Version of this where you can go fancy. Unfortunately since we don't have interface helpers you have to invoke it like this: TMyEnumerable.MyWhere<TCustomer>(customers, otherstuff)


Q: Nullable is the same as C#, value based?

A: Yes, pretty much although in Delphi some types are supported that are not strictly value types but mostly treated like them such as strings. While it does not prevent you from using it with some T that is a reference type it does not make much sense and might behave wrong because it does not explicitly differ between null and explicitly having a typed nil being assigned.


Q: Could the Nullable be stringified and then parsed back?

A: I originally answered the question possibly wrong during the Q&A so here is another take at it: Yes, you can turn a nullable into a string and reverse. Here is some example code on how to achieve that. You need to have a conditional when parsing back the string though to either set the value or to set it as null. This is achieved with the helper for TValue from Spring.pas.


var
  n: Nullable<Integer>;
  s: string;
  v: TValue;
begin
  n := 42;
  s := TValue.From(n).ToString;
  Writeln(s);

  n := nil;
  s := TValue.From(n).ToString;
  Writeln(s);

  s := '55';
  v := TValue.From(s);
  v.TryToType(n);
  Writeln(n.HasValue);
  Writeln(n.Value);

  v := TValue.Empty;
  v.TryToType(n);
  Writeln(n.HasValue);
end.


Q: Is there any active development on the library or future plans? What news can we expect in Version 2?

A: There is constant development and I am always open for suggestions or improvements. Some things that I showed in the presentation are new in 2.0 but I will go into more details on features and some technical stories in the coming weeks. Right now the goal is to get an RC ready before the end of the year and before my Christmas holiday in Night City ;) The plans for the version after that is to finally work on some major refactoring of the DI container in order to fix some of its slowly revealing shortcomings in its architecture/implementation which will enable the implementation of some pretty exciting features. But more about that when the time comes.


Q: What about the future of the ORM part?

A: Due to lack of time and expertise in that area I put development of that part on hold for now. I explained this a bit more in detail some while ago on the mailing list (thanks google for deprecating the ability to pin topics...). It was a decision that was not easy but I am confident that it is better for the overall quality of the library as I can stay focused on its core features.


I know that many of you wish for better documentation and tutorials and that is top priority right after getting the 2.0 RC out.

Downloads: sample code - slides

Monday, September 7, 2020

Open array parameters and subranges

Open array parameters in Delphi are one of the unique language constructs in Delphi. While their syntax resembles that of a dynamic array they are a different thing. In fact under the hood they consist of two parameters, the first being the pointer to the first item and the second being the highest index.

You can pass static and dynamic arrays to an open array parameter as well as directly specify values to be passed using the square brackets. This sums up what they are but the documentation has some more details about them. There is also a detailed article about them.

Open array parameters are a great way to work with sub ranges without additionally passing start and end index or start index and length which avoids any possible defects by wrong offset calculation.

If you want to take a subrange of such an array you can use the Slice function - which however has two little deficiencies. It does not work on dynamic arrays and what this article is about: it can only return subranges that start at the beginning.

Imagine the following code - for the sake of this article we take a standard recursive mergesort implementation and try to partially implement it by using an open array.

procedure MergeSort(var values: array of Integer);
begin
  if Length(values) <= 1 then
    Exit;

  MergeSort(<left half of values>);

  MergeSort(<right half of values>);

  // actual merge left as an exercise to the reader
end;

The left half is easy as you can just use Slice:

  MergeSort(Slice(values, Length(values) div 2);

But the right side must not start at the beginning and gives us a problem. The question is not new and has already been asked on stackoverflow and shows a way how to trick the compiler into generating the correct code - keep in mind we don't want to copy parts of the array but implement an inplace merge sort here - the sort itself does not matter but just serves to demonstrate getting a subrange.

We aim for getting the best code being generated while keeping as much readability as possible - so any fancy solutions using anonymous methods are off limits. The only solution to the problem is to trick Slice in thinking it is dealing with a static array (which the questioner tried to avoid if possible). Because we need it later let's declare a type for this:

type
  TOpenArray<T> = array[0..0] of T;

As this is a generic type it can be used for any open array later - for now we are just sorting Integer. With its help we can complete the trick and outwit Slice:

  len := Length(values);
  mid := len div 2;

  MergeSort(Slice(TOpenArray<Integer>(values[mid]), len - mid));

Apart from introducing variables for the length and the middle of the array we are now taking the start of the right half (if the length is uneven the right side will be one element longer) and hardcasting this to our one-sized static array of Integer and passing the length of the right half. If you compile and run this code with some data it will eventually raise an ERangeError because the compiler assumes that this array has only one element and we want to pass more than that.

So we have to surround that code with directives to temporarily disable range checking if that is enabled - I usually write the code as follows:

  len := Length(values);
  mid := len div 2;

  {$IFOPT R+}{$R-}{$DEFINE RANGECHECKS_ON}{$ENDIF}
  MergeSort(Slice(TOpenArray<Integer>(values[mid]), len - mid));
  {$IFDEF RANGECHECKS_ON}{$R+}{$UNDEF RANGECHECKS_ON}{$ENDIF}

If you are using something such as jedi.inc you already get the RANGECHECKS_ON define and can check for that and don't have to undef - anyhow the goal is to not get this exception - of course you have to make sure that the length you are passing here is correct!

So far so good we have our Mergesort implementation and could implement the interesting part, the merging simply with an array that starts at 0 and has Length(values) elements - no tedious offset calculation. How you do that though is out of the scope for this article.

Now let's make it generic!

class procedure TArray.MergeSort<T>(var values: array of T);
var
  len, mid: Integer;
begin
  len := Length(values);
  if len <= 1 then
    Exit;

  mid := len div 2;

  MergeSort<T>(Slice(values, mid));

  {$IFOPT R+}{$R-}{$DEFINE RANGECHECKS_ON}{$ENDIF}
  MergeSort<T>(Slice(TOpenArray<T>(values[mid]), len - mid));
  {$IFDEF RANGECHECKS_ON}{$R+}{$ENDIF}

  // actual merge left as an exercise to the reader
end;

When we try to compile this code we will get an error in line 14: E2089 Invalid typecast - ugh... generics. Obviously for whatever reason the compiler here does not allow us to hardcast that one element of type T (values[mid]) into a one sized static array of type T (is that a bug given the types have an exact match? Should I report this? Let me know). Anyhow we can solve this - and that might be an indicator that the compile error is bogus:

  MergeSort<T>(Slice(TOpenArray<T>((@values[mid])^), len - mid));

Simply using the address and dereferencing satisfies the compiler to compile - and generate the correct code. Speaking of which - let's take a look at it - I am using optimization and turned off and range and overflow checking to just get the essence:

MergeSortDemo.dpr.44: begin
0041CA34 53               push ebx
0041CA35 56               push esi
0041CA36 51               push ecx
0041CA37 890424           mov [esp],eax
MergeSortDemo.dpr.45: len := Length(values);
0041CA3A 8D5A01           lea ebx,[edx+$01]
MergeSortDemo.dpr.46: if len <= 1 then
0041CA3D 83FB01           cmp ebx,$01
0041CA40 7E24             jle $0041ca66
MergeSortDemo.dpr.49: mid := len div 2;
0041CA42 8BF3             mov esi,ebx
0041CA44 D1FE             sar esi,1
0041CA46 7903             jns $0041ca4b
0041CA48 83D600           adc esi,$00
MergeSortDemo.dpr.54: MergeSort<T>(Slice(values, mid));
0041CA4B 8BD6             mov edx,esi
0041CA4D 4A               dec edx
0041CA4E 8B0424           mov eax,[esp]
0041CA51 E8DEFFFFFF       call TArray.MergeSort<System.Integer>
MergeSortDemo.dpr.59: MergeSort<T>(Slice(TOpenArray<T>((@values[mid])^), len - mid));
0041CA56 8BD3             mov edx,ebx
0041CA58 2BD6             sub edx,esi
0041CA5A 4A               dec edx
0041CA5B 8B0424           mov eax,[esp]
0041CA5E 8D04B0           lea eax,[eax+esi*4]
0041CA61 E8CEFFFFFF       call TArray.MergeSort<System.Integer>
MergeSortDemo.dpr.63: end;
0041CA66 5A               pop edx
0041CA67 5E               pop esi
0041CA68 5B               pop ebx
0041CA69 C3               ret 

Not much to complain about here - the compiler generated just the right code - sure it could have used a register to store eax in instead of using the stack and thus avoid one mov but that is what it usually does in generics. But for the important use of Slice it properly calculates the offset in the array and passes that to MergeSort.

I stated in the introduction that open array parameters are a unique language feature in Delphi - however many other languages have better support for subranges of arrays or strings - and in fact I only came to the solution being presented here because I was looking how to improve the implementation of introsort in Spring4D when I saw how .Net Core utilizes its Span<T>.

Would it be nice to have Slice work without that workaround and have a more powerful way to specify ranges on arrays? Yes and I might file another issue on QP about it - but in the meantime using open array parameters is a nice way to work with subranges of arrays without unnecessary offset calculation.

Update: I previously omitted the generic type parameter on the MergeSort call in the generic implementation which seems to work properly in Delphi 10.4.1 but causes some endless loop in the compiler in earlier versions. Updated the code to not use type inference but explicitly specify the generic type parameter.

Thursday, September 3, 2020

TestInsight 1.1.9.0 released

A new version of TestInsight is available for Delphi 10.1 and higher including the just released Delphi 10.4.1 - older Delphi versions can still use version 1.1.5.0.

Download for 1.1.9.0 (10.1 or higher) or 1.1.5.0 (XE to 10.0)


It contains mostly bugfixes (some had already been fixed in 1.1.8.0):
  • div by zero exception when trying to discover tests on DUnitX
  • truncated duration column for larger numbers: the format of the duration has been improved and you can resize the column now
  • an AV could occur in certain situations when closing the IDE with a docked TestInsight window
  • clipboard shortcuts did not work in the filter edit
  • vertical scrollbar did not have the correct size
But also some nice improvements:
  • run test at the cursor from the interface section of the unit now works (not for individual testcase attributes yet though, it executes all for that particular method) - you have to be in the line of the method declaration
  • navigating from a test result to its implementation now works if the method is implemented by a parent class