miodrag
miodrag

Reputation: 99

Inline asm (32) emulation of move (copy memory) command

I have two two-dimensional arrays with dynamic sizes (guess that's the proper wording). I copy the content of first one into the other using:

    dest:=copy(src,0,4*x*y);
    // src,dest:array of array of longint; x,y:longint;
    // setlength(both arrays,x,y);  //x and y are max 15 bit positive!

It works. However I'm unable to reproduce this in asm. I tried the following variations to no avail... Could someone enlighten me...

    MOV ESI,src; MOV EDI,dest; MOV EBX,y; MOV EAX,x; MUL EBX; 
    PUSH DS; POP ES; MOV ECX,EAX; CLD; REP MOVSD;

Also tried with LEA (didn't expect that to work since it should fetch the pointer address not the array address), no workie, and tried with:

    p1:=@src[0,0]; p2:=@dest[0,0]; //being no-type pointers
    MOV ESI,p1; MOV EDI,p2... (the same asm)

Hints pls? Btw it's delphi 6. The error is, of course, access violation.

Upvotes: 0

Views: 1355

Answers (2)

Margaret Bloom
Margaret Bloom

Reputation: 44076

Dynamic Arrays differ from Static Arrays, especially when it comes to multi-dimensionality.

Refer to this reference for internal formats.

The point is that an Array Of Array Of LongInt of dimensions X and Y (in this order!) is a pointer to an array of X pointers that point to an array of Y LongInts.

Since it seems, from your comments, that you have already allocated the space for all elements in Dest, I assume you want to do a Deep Copy.

Here a sample program, where the assembly as been made as simple as possible for the sake of clarity.

Program Test;

Var Src, Dest: Array Of Array Of LongInt;
    X, Y, I, J: Integer;
Begin

   X := 4;
   Y := 2;

   setLength(Src, X, Y);
   setLength(Dest, X, Y);

   For I := 0 To X-1 Do
      For J := 0 To Y-1 Do
         Src[I,J] := I*Y + J;


   {$ASMMODE intel}
   Asm
       cld                         ;Be sure movsd increments the registers
       mov esi, DWORD PTR [Src]    ;Src pointer
       mov edi, DWORD PTR [Dest]   ;Dest pointer

       mov ecx, DWORD PTR [X]      ;Repeat for X times
                                   ;The number of elements in Src

      @_copy:
       push esi                    ;Save these for later
       push edi 
       push ecx 

       mov ecx, DWORD PTR [Y]      ;Repeat for Y times
                                   ;The number of element in a Src[i] array
       mov edi, DWORD PTR [edi]    ;Get the pointer to the Dest[i] array
       mov esi, DWORD PTR [esi]    ;Get the pointer to the Src[i] array
       rep movsd                   ;Copy sub array

       pop ecx                     ;Restore
       pop edi 
       pop esi 

       add esi, 04h                ;Go from Src[i] to Src[i+1]
       add edi, 04h                ;Go from Dest[i] to Dest[i+1]
      loop @_copy

   End;


   For I := 0 To X-1 Do
      Begin
         WriteLn();

         For J := 0 To Y-1 Do
            Begin
               Write(' ');
               Write(Dest[I,J]);
            End;
      End;
End.

Note 1 This source code is intended to be compile with freepascal.
Donation of Spare Time(TM) for downloading and installing Delphi are welcome!

Note 2 This source code is for illustration purpose only, it is pretty obvious, it has already been stated above, but somehow not everybody got it.
If the OP wanted a fast way to copy the array, they should have stated so.

Note 3 I don't save the clobbered registers, this is bad practice, my bad; I forgot, as there are no subroutines, no optimizations and no reason for the compiler to pass data in the registers between the two fors.
This is left as an exercise to the reader.

Upvotes: 0

Johan
Johan

Reputation: 76617

This is really a two-fold three-fold question.

  1. What's the structure of a dynamic array.

  2. Which instructions in asm will copy the array.

  3. I'm throwing random assembler at the CPU, why doesn't it work?

Structure of a dynamic array
See: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Internal_Data_Formats To quote:

Dynamic Array Types

On the 32-bit platform, a dynamic-array variable occupies 4 bytes of memory (and 8 bytes on 64-bit) that contain a pointer to the dynamically allocated array. When the variable is empty (uninitialized) or holds a zero-length array, the pointer is nil and no dynamic memory is associated with the variable. For a nonempty array, the variable points to a dynamically allocated block of memory that contains the array in addition to a 32-bit (64-bit on Win64) length indicator and a 32-bit reference count. The table below shows the layout of a dynamic-array memory block.

Dynamic array memory layout (32-bit and 64-bit)

Offset 32-bit    -8       -4       0 
Offset 64-bit   -12       -8       0
contents      refcount  count    start of data   

So the dynamic array variable is a pointer to the middle of the above structure.

How do I access this in asm

Let's assume the array holds records of type TMyRec

you'll need to run this code for every inner array in the outer array to do the deep copy. I leave this as an exercise for the reader. (you can do the other part in pascal).

type
  TDynArr: array of TMyRec;

procedure SlowButBasicMove(const Source: TDynArr; var dest);
asm
  //insert register pushes, see below.
  mov esi,Source          //esi = pointer to source data
  mov edi,Dest            //edi = pointer to dest
  sub esi,8               
  mov ebx,[esi]           //ebx = refcount (just in case)
  mov ecx,[esi+4]         //ecx = element count
  mov edx,SizeOf(TMyRec)  //anywhere from 1 to zillions
  mul ecx,edx             //==ecx=number of bytes in array.
  //// now we can start moving 
  xor ebx,ebx             //ebx =0
  add eax,8               //eax = @data
  @loop:
  mov eax,[esi+ebx]       //Get data from source
  mov [edi+ebx],esi       //copy it to dest
  add ebx,4               //4 bytes at a time
  cmp ebx,ecx             //is ebx> number of bytes?
  jle loop 
  //Done copying.
  //insert register pops, see below
end;

That's the copy done, however in order for the system not to crash, you need to save and restore the non volatile registers (all but EAX, ECX, EDX), see: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Program_Control

push ebx
push esi
push edi
--- insert code shown above
//restore non-volatile registers
pop edi
pop esi
pop ebx  //note the restoring must happen in the reverse order of the push.

See the Jeff Dunteman's book assembly step by step if you're completely new to asm.

You will get access violations if:

  • you try to read from a wrong address.
  • you try to write to a wrong adress.
  • you read past the end of the array.
  • you write to memory you haven't claimed before using GetMem or whatever means.
  • if you write past the end of your buffer.
  • if you do not restore all non-volatile registers

Remember you're directly dealing with the CPU. Delphi will not assist you in any way.

Really fast code will use some form of SSE to move 16bytes per instruction in an unrolled loop, see the above mentioned fastcode for examples of optimized assembler.

Random assembler
In assembler you need to know exactly what you're what to do, how and what the CPU does.
Set a breakpoint and run your code. Press ctrl + alt + C and behold the CPU-debug window.
This will allow you to see the code Delphi generates.
You can single step through the code to see what the CPU does.
see: http://www.plantation-productions.com/Webster/index.html
For more reading.

Upvotes: 2

Related Questions