Inefficient compilation of sets

I have a codebase in Delphi that heavily uses sets. Hence my interest in Oxygene. However, I have a penchant for efficiency, so I wrote a test in Oxygene and a test in RemObjects C# uses Flag enums, and then viewed the ASM for each.

Oxygene’s code is TEN TIMES LARGER than C#'s, and has multiple calls! Below, Oxygene followed by C#:

  var s1: TSet := [SSet.sThree];
00000139  lea         rcx,[5DF945D2h] 
00000140  mov         edx,1 
00000145  call        000000005F6219F0 
0000014a  mov         qword ptr [rsp+00000098h],rax 
00000152  mov         rax,qword ptr [rsp+00000098h] 
0000015a  mov         qword ptr [rsp+000000A0h],rax 
00000162  mov         rax,qword ptr [rsp+000000A0h] 
0000016a  mov         rax,qword ptr [rax+8] 
0000016e  mov         qword ptr [rsp+000000A8h],0 
0000017a  cmp         qword ptr [rsp+000000A8h],rax 
00000182  jae         0000000000000196 
00000184  mov         rax,qword ptr [rsp+000000A8h] 
0000018c  mov         qword ptr [rsp+000000A8h],rax 
00000194  jmp         000000000000019B 
00000196  call        000000005FB09660 
0000019b  mov         rcx,qword ptr [rsp+000000A0h] 
000001a3  mov         rax,qword ptr [rsp+000000A8h] 
000001ab  mov         byte ptr [rcx+rax+10h],4 
000001b0  lea         rcx,[00049D10h] 
000001b7  call        000000005F621900 
000001bc  mov         qword ptr [rsp+000000B0h],rax 
000001c4  mov         rax,qword ptr [rsp+000000B0h] 
000001cc  mov         qword ptr [rsp+000000B8h],rax 
000001d4  mov         rdx,qword ptr [rsp+00000098h] 
000001dc  mov         rcx,qword ptr [rsp+000000B8h] 
000001e4  call        FFFFFFFFFFEDC3C8 
000001e9  mov         r11,qword ptr [rsp+000000B8h] 
000001f1  mov         qword ptr [rsp+30h],r11 
  var s2 := [SSet.sOne, SSet.sThree];
000001f6  lea         rcx,[0005391Ah] 
000001fd  mov         edx,2 
00000202  call        000000005F6219F0 
00000207  mov         qword ptr [rsp+000000C0h],rax 
0000020f  mov         rax,qword ptr [rsp+000000C0h] 
00000217  mov         qword ptr [rsp+000000C8h],rax 
0000021f  mov         rax,qword ptr [rsp+000000C8h] 
00000227  mov         rax,qword ptr [rax+8] 
0000022b  mov         qword ptr [rsp+000000D0h],0 
00000237  cmp         qword ptr [rsp+000000D0h],rax 
0000023f  jae         0000000000000253 
00000241  mov         rax,qword ptr [rsp+000000D0h] 
00000249  mov         qword ptr [rsp+000000D0h],rax 
00000251  jmp         0000000000000258 
00000253  call        000000005FB09660 
00000258  mov         rcx,qword ptr [rsp+000000C8h] 
00000260  mov         rax,qword ptr [rsp+000000D0h] 
00000268  mov         dword ptr [rcx+rax*4+10h],0 
00000270  mov         rax,qword ptr [rsp+000000C0h] 
00000278  mov         qword ptr [rsp+000000D8h],rax 
00000280  mov         rax,qword ptr [rsp+000000D8h] 
00000288  mov         rax,qword ptr [rax+8] 
0000028c  mov         qword ptr [rsp+000000E0h],1 
00000298  cmp         qword ptr [rsp+000000E0h],rax 
000002a0  jae         00000000000002B4 
000002a2  mov         rax,qword ptr [rsp+000000E0h] 
000002aa  mov         qword ptr [rsp+000000E0h],rax 
000002b2  jmp         00000000000002B9 
000002b4  call        000000005FB09660 
000002b9  mov         rcx,qword ptr [rsp+000000D8h] 
000002c1  mov         rax,qword ptr [rsp+000000E0h] 
000002c9  mov         dword ptr [rcx+rax*4+10h],2 
000002d1  mov         rax,qword ptr [rsp+000000C0h] 
000002d9  mov         qword ptr [rsp+38h],rax 
  var ix := s1 * s2;
000002de  mov         rax,qword ptr [rsp+30h] 
000002e3  mov         qword ptr [rsp+000000E8h],rax 
000002eb  mov         rcx,qword ptr [rsp+38h] 
000002f0  call        FFFFFFFFFFEDC380 
000002f5  mov         qword ptr [rsp+000000F0h],rax 
000002fd  mov         rdx,qword ptr [rsp+000000F0h] 
00000305  mov         rcx,qword ptr [rsp+000000E8h] 
0000030d  call        FFFFFFFFFFEDC388 
00000312  mov         qword ptr [rsp+000000F8h],rax 
0000031a  mov         rax,qword ptr [rsp+000000F8h] 
00000322  mov         qword ptr [rsp+40h],rax 
  
  if (SSet.sThree in ix) then
00000327  mov         rdx,qword ptr [rsp+40h] 
0000032c  mov         ecx,2 
00000331  call        FFFFFFFFFFEDC370 
00000336  mov         byte ptr [rsp+00000100h],al 
0000033d  movzx       eax,byte ptr [rsp+00000100h] 
00000345  test        eax,eax 
00000347  je          0000000000000363    

  var cs1: CSet := CSet.sThree;
00000363  mov         dword ptr [rsp+48h],3 
  var cs2: CSet := CSet.sOne or CSet.sThree;
0000036b  mov         dword ptr [rsp+4Ch],3 
  var ix1 := cs1 and cs2;
00000373  mov         ecx,dword ptr [rsp+4Ch] 
00000377  mov         eax,dword ptr [rsp+48h] 
0000037b  and         eax,ecx 
0000037d  mov         dword ptr [rsp+50h],eax 

  if (ix1 and CSet.sThree) <> 0 then
00000381  mov         eax,dword ptr [rsp+50h] 
00000385  and         eax,3 
00000388  test        eax,eax 
0000038a  je          00000000000003A6

Could someone from RemObjects comment on this please.

Oke there are two different things here. Oxygene has two “set” like structures. One is a flags enum as you use the in the C# code. The other is a set of, which is quite different. If you use flags in Oxygene like:

type
  CSet = public flags (sOne, sTwo, sThree); // values are 1,2 and 4, bits.

var cs1: CSet := CSet.sOne; 
var cs2: CSet := CSet.sOne or CSet.sThree;
var ix1 := cs1 and cs2;
 if CSet.sThree in ix1 then ...

You get the exact same code as C# would.

Sets in Oxygene use a special class with an underlying array to get the exact semantics as Delphi has, but won’t be as fast as flags. On the other hand, flags are limited to 32 bits (or 64bits when it’s a flags of Int64).

I hope that explains it. Unless there’s legacy code involved, we recommend using flags, not sets.

Thank you for your prompt reply.

So basically, one shouldn’t do it the Delphi way, but should do it the C# way, right?

* => AND
- => AND NOT
+ => OR

Considering how sets work in Delphi at the ASM level, it’s a great pity this wasn’t replicated at the MSIL level using ints like flags enums do.

hint hint :slight_smile:

you can however use in with flags. the expression

 MyFlags.A in value  

is equivalent to:

   (MyFlags.A = (value and MyFlags.A))

We did consider that back when we implemented it. The problem was that from say a C# compiler this would look really odd, since none of the metadata would be available and it would look like a plain Integer type (or fail when > int64 since .NET has no by value set type larger than 64 bits).

I noticed Dephi’s compiler uses a byte, word, int32, or multiple int32s depending on the size of the enum. If the enum has < 32 members, you could use a plain old int32 and not have an overflow error. Or int 64 for 33…64 members, Or the current class for > 64 members.

It really comes down to making the compiler do the work and be smart.

Of course, why not make it a flags enum under the hood for 1…64 members (and Delphi sets style in code), and use the MSIL the C# compiler expects? This would give full Delphi semantics from 1…64 members, and 100% standard flags enum under the hood.

Wouldn’t even take long to make the change :wink:

I’m not at all trying to tell you guys your job, but I think my idea could work and help improve your product.

Agreed it could work. I can’t promise we’ll do it, but I will have a look.

Did you come to a decision on this? :grinning:

It’s still under consideration. If you need this soonish I would suggest you use flags instead for now. we have a large set of ideas already for the upcoming releases, this idea is a good idea though. It was logged as bugs://70788: Improve sets.

bugs://70788 got closed with status fixed.