views:

258

answers:

3

I found that Delphi 5 generates invalid assembly code in specific cases. I can't understand in what cases in general. The example below produces access violation since a very strange optimization occurs. For a byte in a record or array Delphi generates push dword [...], pop ebx, mov .., bl that works correctly if there are data after this byte (we need at least three to push dword correctly), but fails if the data is inaccessible. I emulated the strict boundaries here with win32 Virtual* functions

Specifically the error occurs when the last byte from the block accessed inside FeedBytesToClass procedure. And if I try to change something like using data array instead of object property of remove actionFlag variable, Delphi generates correct assembly instructions.

const
  BlockSize = 4096;

type
  TSomeClass = class
  private
    fBytes: PByteArray;
  public
    property Bytes: PByteArray read fBytes;
    constructor Create;
    destructor  Destroy;override;
  end;

constructor TSomeClass.Create;
begin
  inherited Create;
  GetMem(fBytes, BlockSize);
end;

destructor TSomeClass.Destroy;
begin
  FreeMem(fBytes);
  inherited;
end;

procedure FeedBytesToClass(SrcDataBytes: PByteArray; Count: integer);
var
  j: integer;
  Ofs: integer;
  actionFlag: boolean;
  AClass: TSomeClass;
begin
  AClass:=TSomeClass.Create;
  try
    actionFlag:=true;

    for j:=0 to Count-1 do
    begin
      Ofs:=j;
      if actionFlag then
      begin
        AClass.Bytes[Ofs]:=SrcDataBytes[j];
      end;
    end;
  finally
    AClass.Free;
  end;
end;

procedure TForm31.Button1Click(Sender: TObject);
var
  SrcDataBytes: PByteArray;
begin
  SrcDataBytes:=VirtualAlloc(Nil, BlockSize, MEM_COMMIT, PAGE_READWRITE);
  try
    if VirtualLock(SrcDataBytes, BlockSize) then
      try
        FeedBytesToClass(SrcDataBytes, BlockSize);
      finally
        VirtualUnLock(SrcDataBytes, BlockSize);
      end;
  finally
    VirtualFree(SrcDataBytes, MEM_DECOMMIT, BlockSize);
  end;
end;

Initially the error occured when I used access to RGB data of bitmap bits, but the code there is too complex so I narrowed it to this fragment.

So the question is what is here so specific that makes Delphi produce push,pop,mov optimization. I need to know this in order to avoid such side effects in general.

+3  A: 

Upgrade. Delphi 5 was released 10 years ago.

Bruce McGee
Bruce, some people (like me) still use Delphi 5 to maintain a legacy applications.
smok1
Consider it a tongue in cheek remark.
Bruce McGee
+3  A: 

Dunno exactly what the problem is as your question is rather elusive... but a few remarks:

  • Why are you using GetMem instead of New, especially with a blocksize too small for ByteArray.
    I don't have D5 but in D2007:
    PByteArray = ^TByteArray;
    TByteArray = array[0..32767] of Byte;

  • Try using FastMM4 for memory allocation. Its easier to see what's going on and to find problems.

BTW, in D2007 the asm generated by the compiler is different of what Paul-Jan posted:

Unit7.pas.67: AClass.Bytes[Ofs]:=SrcDataBytes[j];
        mov ebx,[ebp-$04]
        movzx ebx,[ebx+eax]
        push ebx
        mov ebx,[ebp-$08]
        mov ebx,[ebx+$04]
        lea esi,[ebx+esi]
        pop ebx
        mov [esi],bl
Unit7.pas.69: end;
        inc eax
François
Hmm, it's interesting that such code was generated in D2007 since Paul-Jan said that probably in D6 the bug was fixed. It looks like Delphi still tries to access data as dword
Maksee
+8  A: 

Ouch, painful issue indeed. The presence of the constant actionFlag (combined with the constant count of a multiple of 4) triggers the push/pop style of handling the data. For those interested in the actual assembler, (manually typed over as back in the days the cpu view didn't offer copy/paste):

AClass.Bytes[Ofs] := SrcDataBytes[j];
  mov exc,[ebp-$04]
  push dword ptr [ecx+eax]   <- ouch
  mov ecx,[ebp-$08]
  mov ecx,[exc+04]
  lea esi,[exc+esi]
  pop ecx
  mov [esi],cl
end;
  inc eax

It does this 4096 times. I checked, and Delphi 6 doesn't have this behaviour. I think we can safely assume it is fixed in any later version as well.

As a workaround, I'd suggest simply adding {$O-}/{$O+} to the method. I wouldn't dive into the exact forensics too much, as the conditions that trigger Delphi to perform this erroneous optimization seem to be rare, and the Delphi version is indeed rather ancient.

Constant flags wouldn't normally be part of the inner loop, and I suspect your count would normally be dynamic as well. However, you've run into this issue with production code, so perhaps it is not as rare as it seems to be. All I can say is that I have never run into it, and we write 90% of our production code in Delphi 5. Perhaps it's just the safety of the default memory allocation.

Paul-Jan
Paul-Jan, as I see from the François post, D2007 still has tries to access the data as dword. Are you sure about Delphi 6?
Maksee