It is currently Thu Nov 21, 2019 11:26 pm

All times are UTC + 1 hour




Post new topic Reply to topic  [ 2 posts ] 
Author Message
 Post subject: [Include] Endianness swapper using assembly
PostPosted: Tue Jun 11, 2019 4:27 am 
Offline
New User
New User
User avatar

Joined: Wed Oct 04, 2017 5:59 pm
Posts: 3
Hi,

I was recently playing around with some file formats where I needed to swap endianness, and I noticed that there was only one solution for longs posted here by djes so I made an include that supports every integer types.
A nibble swapper for bytes and ascii types is also included.

Code:
;{- Code Header
; ==- Basic Info -================================
;         Name: Endianness.pbi
;      Version: 1.0.2
;      Authors: Herwin Bozet & djes
;  Create date: 9 June 2019, 21:04:51
;
;  Description: A set of procedure that allow you to swap the endianness of a variable easily.
;
; ==- Compatibility -=============================
;  Compiler version: PureBasic 5.60-5.62 (x86 & x64) (Other versions untested)
;  Operating system: Windows 10 (Other platforms untested)
;
; ==- Credits -===================================
;  djes: Original EndianSwapW(Number.l) procedure
;        https://www.purebasic.fr/english/viewtopic.php?f=19&t=17427
;
; ==- Links -=====================================
;   Github: https://github.com/aziascreations/PB-Utils
;
; ==- Documentation -=============================
;  See each procedures for a link to the relevant x86/x64 ASM instruction(s).
;
;}


; FIXME: IMPORTANT NOTICES !!!
; A second procedure was specially made for and .u (and .a) variables to avoid any potential problem that could happen
;  if you use "EndianSwapW" when declaring an implicitely typed variable.
; The compiler might think that you want to use a "Word" and not an "Unsigned Word, aka. Unicode", even if
;  the value returned be these procedure is exactly the same bit-wise.
; If your variable is explicitely typed, it shouldn't be a problem, but you should still use the correct one.
;
; Each of the procedures in this include uses the RAX register and its parts (EAX, AX, AL, AH).
; And in the case of "EndianSwapQ(...)" on x86, the EDX register is used.
; Keep this in mind if you call them while using inline ASM !


;
;- Compiler Options
;{

; The following line is only used to see if it works with it and for debugging.
;EnableExplicit

;}


;
;- Macros
;{

Macro EndianSwap(Number)
   CompilerSelect TypeOf(Number)
      CompilerCase #PB_Word
         EndianSwapW(Number)
      CompilerCase #PB_Unicode
         EndianSwapU(Number)
      CompilerCase #PB_Long
         EndianSwapL(Number)
      CompilerCase #PB_Integer
         EndianSwapI(Number)
      CompilerCase #PB_Quad
         EndianSwapQ(Number)
      CompilerDefault
         CompilerError "Unsupported value type given in '+EndianSwap(Number)' !"
   CompilerEndSelect
EndMacro

;}


;
;- Procedures
;{

;-> 1 Byte (B/A) (Nibble inverters)

; ROL r8/m8 -> Similar to a shift, but the bits loop
; https://www.aldeid.com/wiki/X86-assembly/Instructions/rol
Procedure.b NibbleSwapB(Number.b)
   EnableASM
      ROL Number, 4
   DisableASM
   
   ProcedureReturn Number
EndProcedure

Procedure.a NibbleSwapA(Number.a)
   EnableASM
      ROL Number, 4
   DisableASM
   
   ProcedureReturn Number
EndProcedure


;-> 2 Bytes (W/U)

; XCHG r8, r8 -> Where both r8 are parts of r16 -> (ax = al+ah)
; See: https://c9x.me/x86/html/file_module_x86_id_328.html
Procedure.w EndianSwapW(Number.w)
   EnableASM
      MOV ax,Number
      XCHG al,ah
      MOV Number,ax
   DisableASM
   
   ProcedureReturn Number
EndProcedure

Procedure.u EndianSwapU(Number.u)
   EnableASM
      MOV ax,Number
      XCHG al,ah
      MOV Number,ax
   DisableASM
   
   ProcedureReturn Number
EndProcedure


;-> 4 Bytes (L)

; Original code for "EndianSwapL(...)" by djes on the PureBasic forum.
; See: https://www.purebasic.fr/english/viewtopic.php?f=19&t=17427

; Improvement(s) made:
; * EAX Register returned directly -> MAY save a couple of cycles if the compiler doesn't optimize it already.
;                                     It does, it goes from ~687-690ms/1M calls to ~635ms/1M calls.

; BSWAP r32 -> Does the operation on the register itself.
; See: https://www.felixcloutier.com/x86/bswap
Procedure.l EndianSwapL(Number.l)
   EnableASM
      MOV eax,Number
      BSWAP eax
      ProcedureReturn
   DisableASM
EndProcedure


;-> 4-8 Bytes (I)

; EndianSwapI(Number.i)
; Had to be separated based on the CPU arch because its size varies based on it, and also because BSWAP uses
;  the register size to know what to do.
; And since x64 has 64bit registers while x86 doesn't, they have to be different.
CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
   
   ; BSWAP r32 -> Does the operation on the register itself.
   ; See: https://www.felixcloutier.com/x86/bswap
   Procedure.i EndianSwapI(Number.i)
      EnableASM
         MOV eax, Number
         BSWAP eax
         ProcedureReturn
      DisableASM
   EndProcedure
   
CompilerElseIf #PB_Compiler_Processor = #PB_Processor_x64
   
   ; BSWAP r64 -> Does the operation on the register itself.
   ; See: https://www.felixcloutier.com/x86/bswap
   Procedure.i EndianSwapI(Number.i)
      EnableASM
         MOV rax, Number
         BSWAP rax
         MOV Number, rax
      DisableASM
      
      ProcedureReturn Number
   EndProcedure
   
CompilerElse
   CompilerWarning "Unsupported CPU Architecture in Endianness.pbi for EndianSwapI(...) !"
CompilerEndIf


;-> 8 Bytes (Q)

; MOVBE could have been used, but the compiler kept giving a syntax error for some reason :/
; See: https://www.felixcloutier.com/x86/movbe
; Same thing with MOVSS and other SSE2 MOV ops with xmm regs.

; EndianSwapQ(Number.q)
; Had to be separated based on the CPU arch because x86 doesn't have an easy way of dealing with a 64bit variable
;  in its registers.
CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
   
   ; Note: The xmm registers should be able to hold a quad on both x86 and x64, it might be a good idea to check it.
   
   ; Note: The stack could have technically been used to leave EDX untouched, but when I used it,
   ;        the "ProcedureReturn" kept throwing an "Invalid memory access" error.
   
   ; BSWAP r32 -> Does the operation on the register itself.
   ; See: https://www.felixcloutier.com/x86/bswap
   Procedure.q EndianSwapQ(Number.q)
      EnableASM
         MOV eax, dword [p.v_Number]
         MOV edx, dword [p.v_Number+4]
         
          BSWAP eax
          BSWAP edx
          
          MOV dword [p.v_Number+4], eax
          MOV dword [p.v_Number], edx
      DisableASM
      
      ProcedureReturn Number
   EndProcedure
   
CompilerElseIf #PB_Compiler_Processor = #PB_Processor_x64
   
   ; BSWAP r64 -> Does the operation on the register itself.
   ; See: https://www.felixcloutier.com/x86/bswap
   Procedure.q EndianSwapQ(Number.q)
      EnableASM
         MOV rdx, Number
         BSWAP rdx
         MOV Number, rdx
      DisableASM
      
      ProcedureReturn Number
   EndProcedure
   
CompilerElse
   CompilerWarning "Unsupported CPU Architecture in Endianness.pbi for EndianSwapQ(...) !"
CompilerEndIf

;}


;
;- Tests
;{

CompilerIf #PB_Compiler_IsMainFile
   Define VarB.b = $B5
   Debug "Byte:"
   Debug "0x"+RSet(Hex(VarB, #PB_Byte), 2, "0")+" -> 0x"+RSet(Hex(NibbleSwapB(VarB), #PB_Byte), 2, "0")
   Debug Str(VarB)+" -> "+Str(NibbleSwapB(VarB))
   Debug ""
   
   Define VarA.b = $B5
   Debug "Ascii:"
   Debug "0x"+RSet(Hex(VarA, #PB_Ascii), 2, "0")+" -> 0x"+RSet(Hex(NibbleSwapA(VarA), #PB_Ascii), 2, "0")
   Debug StrU(VarA, #PB_Ascii)+" -> "+StrU(NibbleSwapA(VarA), #PB_Ascii)
   Debug ""
   
   Define VarW.w = $B903
   Debug "Word:"
   Debug "0x"+RSet(Hex(VarW, #PB_Word), 4, "0")+" -> 0x"+RSet(Hex(EndianSwapW(VarW), #PB_Word), 4, "0")
   Debug Str(VarW)+" -> "+Str(EndianSwapW(VarW))
   Debug ""
   
   Define VarU.w = $B903
   Debug "Unicode:"
   Debug "0x"+RSet(Hex(VarU, #PB_Unicode), 4, "0")+" -> 0x"+RSet(Hex(EndianSwapU(VarU), #PB_Unicode), 4, "0")
   Debug StrU(VarU, #PB_Unicode)+" -> "+StrU(EndianSwapU(VarU), #PB_Unicode)
   Debug ""
   
   Define VarL.l = $E530A6F0
   Debug "Long:"
   Debug "0x"+RSet(Hex(VarL, #PB_Long), 8, "0")+" -> 0x"+RSet(Hex(EndianSwapL(VarL), #PB_Long), 8, "0")
   Debug Str(VarL)+" -> "+Str(EndianSwapL(VarL))
   Debug ""
   
   Debug "Integer:"
   CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
      Define VarI.i = $E530A6F0
      
      Debug "> 32 bits"
      Debug "0x"+RSet(Hex(VarI, #PB_Long), 8, "0")+" -> 0x"+RSet(Hex(EndianSwapL(VarI), #PB_Long), 8, "0")
      Debug Str(VarI)+" -> "+Str(EndianSwapL(VarI))
      Debug "> 64 bits"
      Debug "Use a 64 bits compiler or EndianSwapQ(...)"
      
   CompilerElseIf #PB_Compiler_Processor = #PB_Processor_x64
      Define VarI.i = $D34A096BD100F590
      
      Debug "> 32 bits"
      Debug "Use a 32 bits compiler or EndianSwapL(...)"
      Debug "> 64 bits"
      Debug "0x"+RSet(Hex(VarI, #PB_Quad), 16, "0")+" -> 0x"+RSet(Hex(EndianSwapI(VarI), #PB_Quad), 16, "0")
      Debug Str(VarI)+" -> "+Str(EndianSwapI(VarI))
      
   CompilerElse
      CompilerWarning "> Unsupported CPU Architecture for both x86 and x64."
   CompilerEndIf
   Debug ""
      
   Debug "Quad:"
   Define VarQ.q = $D34A096BD100F590
   CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
      
      Debug "> 32 bits method (x86 fallback)"
      Debug "0x"+RSet(Hex(VarQ, #PB_Quad), 16, "0")+" -> 0x"+RSet(Hex(EndianSwapQ(VarQ), #PB_Quad), 16, "0")
      Debug Str(VarQ)+" -> "+Str(EndianSwapQ(VarQ))
      Debug "> 64 bits method (???)"
      Debug "Use a 64 bits compiler"
      
   CompilerElseIf #PB_Compiler_Processor = #PB_Processor_x64
      
      Debug "> 32 bits method (x86 fallback)"
      Debug "Use a 32 bits compiler"
      Debug "> 64 bits method (???)"
      Debug "0x"+RSet(Hex(VarQ, #PB_Quad), 16, "0")+" -> 0x"+RSet(Hex(EndianSwapQ(VarQ), #PB_Quad), 16, "0")
      Debug Str(VarQ)+" -> "+Str(EndianSwapQ(VarQ))
      
   CompilerElse
      CompilerWarning "> Unsupported CPU Architecture for both x86 and x64."
   CompilerEndIf
CompilerEndIf

;}


A few improvements can still be made by directly returning variables, but they should only save a couple of cycles at best.


Top
 Profile  
Reply with quote  
 Post subject: Re: [Include] Endianness swapper using assembly
PostPosted: Tue Jun 11, 2019 5:37 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Sun Apr 12, 2009 6:27 am
Posts: 3458
Hi
Did not tested it yet
But I like this kind of staff
Thanks

_________________
Egypt my love


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 2 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 19 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye