7
votes

I have this procedure that swaps the bytes (low/high) of a Word variable (It does the same stuff as System.Swap function). The procedure works when the compiler optimization is OFF but not when it is ON. Can anybody help me with this?

procedure SwapWord(VAR TwoBytes: word);   
asm
  Mov EBX, TwoBytes
  Mov AX, [EBX]
  XCHG AL,AH
  Mov [EBX], AX
end;
5
My first guess would be this works flawlessly, perhaps optimization led to a debugging result you didn´t expect. Could you provide us with a minimal reproduction case where this supposedly breaks?Paul-Jan

5 Answers

10
votes

Fastest:

function ReverseWord(w: word): word;
asm
   {$IFDEF CPUX64}
   mov rax, rcx
   {$ENDIF}
   xchg   al, ah
end;

In case you want to reverse DWORD too:

function ReverseDWord(dw: cardinal): cardinal;
asm
  {$IFDEF CPUX64}
  mov rax, rcx
  {$ENDIF}
  bswap eax
end;
8
votes

You can't use EBX register in ASM code without saving/restoring it. The corrected version of your code is

procedure SwapWord_Working(VAR TwoBytes: word);   
asm
  PUSH EBX     // save EBX
  Mov EBX, TwoBytes
  Mov AX, [EBX]
  XCHG AL,AH
  Mov [EBX], AX
  POP EBX     // restore EBX
end;
7
votes

I'm a bit surprised that no one mentioned the absolute "hack" which is around for more than a decade but doesn't get too much spotlight... anyways here's my two cents

function SwapWordBytes(const Value: Word): Word;
var
  // shares memory with Value parameter
  LMemValue: array[0..1] of Byte absolute Value;
  // shares memory with Result
  LMemResult: array[0..1] of Byte absolute Result;
begin
  LMemResult[0] := LMemValue[1];
  LMemResult[1] := LMemValue[0];
end;
5
votes

Have you considered using the compiler's Swap function?

procedure TForm1.FormCreate(Sender: TObject);
var
  a: word;
begin
  a := $1234;
  a := Swap(a);
  Caption := IntToHex(a, 4)
end;

If not, you don't need ASM for this (and ASM will probably not be available in 64-bit Delphi). You can just do

procedure MySwap(var a: word);
var
  tmp: byte;
begin
  tmp := PByte(@a)^;
  PByte(@a)^ := PByte(NativeUInt(@a) + sizeof(byte))^;
  PByte(NativeUInt(@a) + sizeof(byte))^ := tmp;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  a: word;
begin
  a := $123456;
  MySwap(a);
  Caption := IntToHex(a, 4)
end;

and, of course, there are "a million" variations on this theme.

procedure MySwap(var a: word);
var
  tmp: byte;
type
  PWordRec = ^TWordRec;
  TWordRec = packed record
    byte1, byte2: byte;
  end;
begin
  with PWordRec(@a)^ do
  begin
    tmp := byte1;
    byte1 := byte2;
    byte2 := tmp;
  end;
end;

and, very briefly,

procedure MySwap(var a: word);
begin
  a := word(a shl 8) + byte(a shr 8);
end;

or

procedure MySwap(var a: word);
begin
  a := lo(a) shl 8 + hi(a);
end;
1
votes

Although Serg's answer is certainly correct, as pointed out in comments to Serg's answer, it's not efficient. The fastest would clearly be the code provided in Gabr's answer, but since you explicitly want a procedure, not a function, the following would be the preferred version of Serg's routine:

procedure SwapWord_Working2 (VAR TwoBytes: word);
asm
  mov dx, [TwoBytes]  ;//[TwoBytes] = [eax] on x86 *[Note1]
  xchg dl, dh
  mov [TwoBytes], dx
end;

[Note1:] Serg's version of the function will, in all likelihood, not work for the upcoming x64 Delphi compiler. Assuming Embarcadero stick to their plan (mentioned somewhere by Allen Bauer) of using the Win64 calling convention (where @TwoBytes would be passed via RCX) the version provided in this answer should still work on x64.