Search code examples
arraysdelphiindexingbytebuffercompiler-bug

Delphi XE byte array index


I use simple circular buffer like this

var
  Values: array [byte] of single;
  ptr: byte;

In this test example

for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;

I expect to have set to 1 first 5 values and last 5 values, but XE4 compiller produce incorrect code, its using 32bit pointer math to calculate array index:

for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
005B94BB C645FB00         mov byte ptr [ebp-$05],$00
005B94BF 33C0             xor eax,eax
005B94C1 8A45FB           mov al,[ebp-$05]
005B94C4 C78485E0FBFFFF0000803F mov [ebp+eax*4-$0420],$3f800000
005B94CF FE45FB           inc byte ptr [ebp-$05]
005B94D2 807DFB0B         cmp byte ptr [ebp-$05],$0b
005B94D6 75E7             jnz $005b94bf

Is it my wrong code and whats proper way to operate byte indexes?


Solution

  • The question is:

    Is a wrap expected within the Byte() cast?

    Lets compare the disassembly with overflow checking on/off.

    {$Q+}
    Project71.dpr.21: for ptr:= 0 to 10 do Values[Byte(ptr-5)]:= 1;
    0041D568 33DB             xor ebx,ebx
    0041D56A 0FB6C3           movzx eax,bl
    0041D56D 83E805           sub eax,$05
    0041D570 7105             jno $0041d577
    0041D572 E82D8DFEFF       call @IntOver
    0041D577 0FB6C0           movzx eax,al
    0041D57A C704870000803F   mov [edi+eax*4],$3f800000
    0041D581 43               inc ebx
    0041D582 80FB0B           cmp bl,$0b
    0041D585 75E3             jnz $0041d56a
    
    {$Q-}
    Project71.dpr.21: for ptr:= 0 to 10 do Values[Byte(ptr-5)]:= 1;
    0041D566 B30B             mov bl,$0b
    0041D568 B808584200       mov eax,$00425808
    0041D56D C7000000803F     mov [eax],$3f800000
    0041D573 83C004           add eax,$04
    0041D576 FECB             dec bl
    0041D578 75F3             jnz $0041d56d
    

    With {$Q+} the wraps works, while with {$Q-} the wrap does not work and the compiler does not generate a range error for the wrong array indexing when {$R+} is set.

    So, to me the conclusion is: Since the range check on does not generate a run-time error for an array index out of bounds, a wrap is expected.

    This is further proved by the fact that a wrap is done when overflow checking is on.


    This should be reported as a bug in the compiler.

    Done: https://quality.embarcadero.com/browse/RSP-15527 "Type cast fail within array indexing"


    Note: a workaround is given by @Rudy in his answer.


    Addendum:

    Following code:

    for ptr:= 0 to 10 do WriteLn(Byte(ptr-5));
    

    generates:

    251
    252
    253
    254
    255
    0
    1
    2
    3
    4
    5
    

    for all combinations of range/overflow checking.

    Likewise Values[Byte(-1)] := 1; assigns 1 to Values[255] for all compiler options.


    The documentation for Value Typecasts says:

    The resulting value is obtained by converting the expression in parentheses. This may involve truncation or extension if the size of the specified type differs from that of the expression. The expression's sign is always preserved.