Using MMX via In-line Assembler for Encryption

See the introduction to the previous program for most of our discussion of this encryption. We could not find the MMX unit in Delphi so coded an in-line assembler procedure instead. See our in-line assembler tutorial for examples of simpler operations. The PXOR command in the Convert procedure performs the XOR operation on 64 bits. The Q in the MOVQ mnemonic represents quadword (64 bits). The assembler code appears cumbersome, but since it encrypts a 4.5 MB array in about 8 ms it is not slow.

According to this page of the Webster guide to assembler programming, "The EMMS (Empty MMX Machine State) instruction restores the FPU status on the CPU so that it can begin processing FPU instructions again after an MMX instruction sequence. You should always execute the EMMS instruction once you complete some MMX sequence. Failure to do so may cause any following floating point instructions to fail."

As with the previous program, you need a large test file to test the speed of the program. We used the same file named Programs.txt and found that the speed of this program was similar. A text file is suitable for checking quickly the encryption and decryption, but you do not need to use a text file. You need to change the constants INFILE, ENCRYPTED and DECRYPTED as necessary for your tests.

program Encrypt_ASM_MMX;
  {$APPTYPE CONSOLE}
uses
  SysUtils, Classes, Windows;

type
  TMMXBytes = array of int64;
const
  INFILE = 'Programs.txt';
  ENCRYPTED = 'Encrypted.txt';
  DECRYPTED = 'Decrypted.txt';
var
  Keys : array[0 .. 3] of int64 = ($85A8FAD3C78BE29B, $81A2F6D0C28AE49C,
                                   $86A4F8D6CA82EB92, $84ABF3D9C081E296);
  MMXBytes : TMMXBytes;
  i: integer;
  FileBytes,  NumOfMMXbytes : integer;
  CurrentMMXByte : int64;

procedure FileToArray(const strSource : string;
                      var iFileBytes, iMMXbytes  : integer);
var
  fsCurrent : TFileStream;
  iBytesRead : integer;
begin
  fsCurrent := TFileStream.Create(strSource, fmOpenRead);
  iFileBytes := fsCurrent.Size;
  iMMXbytes := (iFileBytes div 8) + 1;
  SetLength(MMXBytes, iMMXbytes);
  iBytesRead := fsCurrent.Read(MMXBytes[0], iFileBytes);
  if iBytesRead <> iFileBytes then
    writeln('Error reading from file');
  fsCurrent.Destroy;
end;

procedure ArrayToFile(const strDestination : string; var arraySource : TMMXBytes;
                      const iFileBytes : integer);
var
  fsCurrent : TFileStream;
  iBytesWritten : integer;
begin
  fsCurrent := TFileStream.Create(strDestination, fmCreate);
  iBytesWritten := fsCurrent.Write(arraySource[0], iFileBytes);
  if iBytesWritten <> iFileBytes then
    writeln('Error writing to file');
  fsCurrent.Destroy;
end;

procedure Convert(var MMXBytes : TMMXBytes; const iBytes : integer);
var
  Ticks, StartTicks, i : integer;
  Key : int64;
begin
  StartTicks := GetTickCount;
  for i := 0 to NumOfMMXBytes - 1 do
    begin
      CurrentMMXByte := MMXBytes[i];
      Key := Keys[i mod 4];
      asm
        MOVQ mm0, Key;
        MOVQ mm1, CurrentMMXByte;
        PXOR mm1, mm0;
        MOVQ  CurrentMMXByte, mm1;
        EMMS;
      end;
      MMXBytes[i] := CurrentMMXByte;
    end;
  Ticks := GetTickCount - StartTicks;
  writeln('Array converted in ', Ticks, ' ms. ');
end;

begin
  FileToArray(INFILE, FileBytes, NumOfMMXBytes);
  Convert(MMXBytes, NumOfMMXBytes);
  ArrayToFile(ENCRYPTED, MMXBytes, FileBytes);
  writeln('File encrypted. Press Enter to decrypt.');
  readln;
  FileToArray(ENCRYPTED, FileBytes, NumOfMMXBytes);
  Convert(MMXBytes, NumOfMMXBytes);
  ArrayToFile(DECRYPTED, MMXBytes, FileBytes);
  writeln('File decrypted. Press Enter to exit.');
  readln;
end.
Programming - a skill for life!

How to use the MMX capabilities of Pentium processors