procedure that swaps the bytes (low/high) of a Word variable
Asked Answered
D

5

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;
Diplo answered 27/2, 2011 at 15:9 Comment(1)
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?Oh
S
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;
Stempien answered 27/2, 2011 at 17:41 Comment(5)
@Altar: "Need" is a very strong word, especially since you always can do procedure Proc(var w: word) begin w := Func(w); end;.Freespoken
But surely this isn't faster than the compiler's Swap function?Freespoken
Not faster but not every Delphi implements Swap.Stempien
@gabr-The ReverseDWord is not working anymore on Win64. Any idea how to fix it?Diplo
@Altar - I've edited the answer to include X64 support.Stempien
M
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;
Mumbletypeg answered 27/2, 2011 at 15:33 Comment(3)
Alternatively, you could just use ECX or EDX, which you don't need to preserve.Clotho
I see you beat me with a few seconds. I will remove my (almost identical) answer. And +1, of course.Freespoken
@Andreas: I am sorry. And +1 for your detailed answer.Mumbletypeg
H
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;
Hewlett answered 28/2, 2011 at 1:1 Comment(3)
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. :)Scanlon
@Scanlon 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...Hewlett
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. :)Scanlon
F
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;
Freespoken answered 27/2, 2011 at 15:20 Comment(4)
Hi Andreas. Why do you say ASM will not be available under Delphi 64?Diplo
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: #4052103Freespoken
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.Sonar
Thanks a lot Andreas for that link!Diplo
F
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.

Fellow answered 20/6, 2011 at 6:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.