When comparing two managed records using a inline generic class function with const parameters CopyRecord gets called on the records.

When comparing two managed records using a inline generic class function with const parameters CopyRecord gets called on the records. 
This causes the pointers inside the records to change and two (previously) equal records to not test equal. 

However.....
When creating a simple MVCE I'm unable to reproduce the behavior. 

The following code works just fine:

program RTLTestManagedRecords;
{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TCompare = class
    class function Compare(const Left, Right: T): integer; static; inline;
  end;

  TTest = class
  private
    class var F: TCompare;
  public
    class function Fast(const Left, Right: T): integer; static;
    class procedure Test(const Left, Right: T; const message: string = ''); static;
  end;

  function BinaryCompare(const Left, Right; Size: NativeInt): integer; forward;

class function TCompare.Compare(const Left, Right: T): integer;
begin
  if GetTypeKind(T) = tkRecord then case SizeOf(T) of
    0: Result:= 0;
    1: Result:= (PByte(@Left)^)- (PByte(@Right)^);
    2: Result:= (PWord(@Left)^)- (PWord(@Right)^);
    4: Result:= (integer(PCardinal(@Left)^> PCardinal(@Right)^)-
        integer(PCardinal(@Left)^< PCardinal(@Right)^));
    else Result:= BinaryCompare(Left, Right, SizeOf(T));
  end;
end;

{pointermath on}
function BinaryCompare(const Left, Right; Size: NativeInt): integer;
var
  i: integer;
  L,R: PByte;
begin
  L:=@Left;
  R:=@Right;
  for i:= 0 to Size - 1 do begin
    if L[i] <> R[i] then exit(L[i] - R[i]);
  end;
  Result:= 0;
end;
{$pointermath off}

type
  TManagedRec = record
    a: integer;
    b: string;
  end;

var
  L,R: TManagedRec;

{ TTest }

class function TTest.Fast(const Left, Right: T): integer;
begin
  Result:= F.Compare(Left, Right);
end;

class procedure TTest.Test(const Left, Right: T; const message: string);
begin
  try
    WriteLn(Format(message,[TTest.Fast(Left,Right)]));
  except
    WriteLn('Oops');
  end;
end;

begin
  L.a:= 1;
  R.a:= 2;
  L.b:= '7878787';
  R.b:= '7777777';
  TTest.Test(L,L,'Compare(L,L) = %d');
  WriteLn(Format('Compare(L,R) = %d',[TCompare.Compare(L,R)]));
  WriteLn(Format('Compare(R,L) = %d',[TCompare.Compare(R,L)]));
  WriteLn(Format('Compare(R,R) = %d',[TCompare.Compare(R,R)]));
  WriteLn(Format('Compare(L,L) = %d',[TCompare.Compare(L,L)]));
  ReadLn;
end.

When I run it inside the more complex fastdefaults code it fails because CopyRecord gets called, even though the parameters are declared as const. 
Declaring them as const [ref] does not fix the issue. 
Removing the inline fixes it, but that removes much of the benefit of the compare function. 

*Is anyone able to change/expand the above MVCE so that it bombs just like the larger example does?"

Comments

  1. The basic motto of programming is everything is always possible... provided you have enough time and/or money :)

    Interleaving, reminds me of

    10 PRINT "HELLO"
    11 REM WAS MISSING WORLD
    12 PRINT "WORLD"
    20 GOTO 10

    hehe :-)

    ReplyDelete
  2. Lars Fosdal Hundreds of compare functions? True. But the counter to that is that your way has hundreds of types with gnarly attributes. And naff runtime performance.

    ReplyDelete
  3. David Heffernan - gnarly attributes indeed.
    Performancewise, good enough for anything UI related and you only enumerate the attributes once per class.  For intense processing of large amounts of data, not good.

    ReplyDelete

Post a Comment