8

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;
PhiS
  • 4,540
  • 25
  • 35
Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • 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 Feb 27 '11 at 15:16

5 Answers5

11

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;
gabr
  • 26,580
  • 9
  • 75
  • 141
8

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;
kludg
  • 27,213
  • 5
  • 67
  • 118
7

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;
  • 2
    `absolute` is around here not just for more than a decade but for more than Delphi itself, I remember using it in Turbo Pascal 6 or 7! Not sure about the version, tough, and hoping my memory is not playing mi a joke with this. :) – jachguate Mar 02 '11 at 00:15
  • @jachguate you are right, the absolute keyword was used for fast video memory access way way way back, I remember a friend of mine talking to me about this a few years ago, however I only used it in Delphi... –  Mar 03 '11 at 21:55
  • I used pointers to access video memory that old days... but absolute was very handy to use different variables to access the same address without type-casting, like in your example. :) – jachguate Mar 03 '11 at 22:33
5

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;
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • Hi Andreas. Why do you say ASM will not be available under Delphi 64? – Gabriel Feb 27 '11 at 16:26
  • 1
    An Embarcadero employee, Barry Kelly, wrote about that. First he wrote "Almost certainly no built-in assembler, but possibly support for linking in object files assembled with something else." but now I see he has edited his text: http://stackoverflow.com/questions/4051603/how-should-i-prepare-my-32-bit-delphi-programs-for-an-eventual-64-bit-compiler/4052431#4052431 – Andreas Rejbrand Feb 27 '11 at 16:32
  • 2
    FWIW Allen Bauer has posted a screenshot of the x64 inline assembler. It's not confirmed yet but seems quite possible to be in the product. – David Heffernan Feb 27 '11 at 23:41
  • Thanks a lot Andreas for that link! – Gabriel Mar 01 '11 at 11:03
1

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.

PhiS
  • 4,540
  • 25
  • 35