Page 1 of 2

Assembler Routines for beginners...

Posted: Fri May 19, 2006 11:01 am
by Michael Vogel
I'm interested to find a small collection of tiny procedures, written in assembler to learn from it. I've searched around in the forum, but because of the differences of PB4 to older versions, it's not easy to find the "right" way for programming such snippets...

The most important target is to be able to write (reallly small) functions which could be usefull sometimes, like

* Bit operations: SetBit ClrBit CheckBit
* Math functions: Min(), Max(), Int(), Frac()

So if anyone can put here some nice code parts (working with PB4), that would be cool!

Posted: Fri May 19, 2006 1:17 pm
by Psychophanta
I have a good collection:

some small pieces:

Code: Select all

Procedure.f RoundASM(num.f)
  !fld dword[p.v_num]
  !frndint
  ProcedureReturn
EndProcedure

Code: Select all

Procedure.l rol12(n.l,b.l); <- rotate left the bits in a 12bit value
  !mov ecx,12
  !xor edx,edx
  !mov eax,dword[p.v_b]
  !div ecx
  !mov ecx,edx
  !mov ax,word[p.v_n]
  !mov bx,ax
  !shl bx,4
  !shld ax,bx,cl
  !and ax,$0fff
  ProcedureReturn
EndProcedure
Procedure.l ror12(n.l,b.l); <- rotate right the bits in a 12bit value
  !mov ecx,12
  !xor edx,edx
  !mov eax,dword[p.v_b]
  !div ecx
  !mov ecx,edx
  !add ecx,4
  !mov ax,word[p.v_n]
  !mov bx,ax
  !shl ax,4
  !shrd ax,bx,cl
  !and ax,$0fff
  ProcedureReturn
EndProcedure
Procedure.l rol24(n.l,b.l); <- rotate left the bits in a 24bit value
  !mov ecx,24
  !xor edx,edx
  !mov eax,dword[p.v_b]
  !div ecx
  !mov ecx,edx
  !mov eax,dword[p.v_n]
  !mov ebx,eax
  !shl ebx,8
  !shld eax,ebx,cl
  !and eax,$00ffffff
  ProcedureReturn
EndProcedure
Procedure.l ror24(n.l,b.l); <- rotate right the bits in a 24bit value
  !mov ecx,24
  !xor edx,edx
  !mov eax,dword[p.v_b]
  !div ecx
  !mov ecx,edx
  !add ecx,8
  !mov eax,dword[p.v_n]
  !mov ebx,eax
  !shl eax,8
  !shrd eax,ebx,cl
  !and eax,$00ffffff
  ProcedureReturn
EndProcedure

Code: Select all

Procedure.d ATan2(y.d,x.d)
  !fld qword[p.v_y]
  !fld qword[p.v_x]
  !fpatan
  ProcedureReturn
EndProcedure

Code: Select all

Procedure.l CountChars(a.s,s.b)
  !mov edi,dword[p.v_a]    ;pointer to the first character in string (first function parameter)
  ;firstly we must find the lenght of the string:
  !;cld              ;clear DF (Direction Flag). (normally not necessary; cleared by default)
  !xor ebx,ebx  ;init counter to NULL
  !mov al,bl       ;set NULL character in AL register
  !mov ecx,ebx    ;lets set 4294967295 ($FFFFFFFF) characters maximum
  !dec ecx
  !repnz scasb    ;repeat comparing AL CPU register content with byte[edi] 
  !jecxz near _CountCharsgo    ;if NULL byte is not found within those 4294967295 characters then exit returning 0 
  !not ecx     ;else, some adjusts. Now we have the lenght at ecx register
  !mov edi,dword[p.v_a]     ;point again to the first character in string (first function parameter)
  !mov al,byte[p.v_s]    ;al=character to find
  !@@:REPNZ scasb   ;repeat comparing AL CPU register content with byte[edi]
  !jecxz near _CountCharsgo     ;until ecx value is reached
  !inc ebx       ;or a match is found
  !jmp near @r       ;continue comparing next character
  !_CountCharsgo:MOV eax,ebx   ;output the matches counter
  ProcedureReturn
EndProcedure

Posted: Fri May 19, 2006 1:52 pm
by Dare2
These appear to be a bit of a duplication of Pyschophanta's stuff, but may:

A: Be useful
B: Get some feedback from others that is useful to us both. :)

Code: Select all

Procedure LogicalShR(a,b)
 MOV eax,a
 MOV ecx,b
 SHR eax,cl
 ProcedureReturn
EndProcedure
Procedure LogicalShL(a,b)
 MOV eax,a
 MOV ecx,b
 SHL eax,cl
 ProcedureReturn
EndProcedure
Procedure LogicalRoR(a,b)
 MOV eax,a
 MOV ecx,b
 ROR eax,cl
 ProcedureReturn
EndProcedure
Procedure LogicalRoL(a,b)
 MOV eax,a
 MOV ecx,b
 ROL eax,cl
 ProcedureReturn
EndProcedure

v1=$01 : Debug Hex(v1) : v1 = LogicalRoR(v1,1) : Debug Hex(v1)
Requires inline ASM turned on.

Posted: Fri May 19, 2006 2:35 pm
by Dare2
Some more, again smarter minds will have better stuff:

Code: Select all

Procedure SetBit(a,b)  ; Set bit 'b' of 'a' (bit 0 is rightmost bit)
 MOV eax,1
 MOV ecx,b
 SHL eax,cl
 OR eax,a
 ProcedureReturn
EndProcedure

Procedure ClrBit(a,b)  ; Clear bit 'b' of 'a' (bit 0 is rightmost bit)
 MOV eax,a
 MOV ecx,b
 ROR eax,cl
 AND al,$FE
 ROL eax,cl
 ProcedureReturn
EndProcedure

Procedure GetBit(a,b)  ; Get bit 'b' of 'a' (bit 0 is rightmost bit)
 MOV eax,1
 MOV ecx,b
 SHL eax,cl
 AND eax,a
 SHR eax,cl
 ProcedureReturn
EndProcedure

For i = 7 To 0 Step -1
  Debug RSet(Bin(SetBit($0,i)),8,"0")
Next
Debug "---"

For i = 7 To 0 Step -1
  Debug RSet(Bin(ClrBit($0FF,i)),8,"0")
Next
Debug "---"

For i = 7 To 0 Step -1
  w.s + Str(GetBit($55,i))
Next
Debug w

Posted: Fri May 19, 2006 3:50 pm
by Pupil

Code: Select all

Procedure.l Min(a.l, b.l)
	MOV eax, a
	MOV ebx, b
	XOr edx, edx
	CMP eax, ebx
	SETLE dl
	SUB edx, 1
	And ebx, edx
	NOT edx
	And eax, edx
	Or eax, ebx
	ProcedureReturn
EndProcedure

Procedure.l Max(a.l, b.l)
	MOV eax, a
	MOV ebx, b
	XOr edx, edx
	CMP eax, ebx
	SETLE dl
	SUB edx, 1
	And eax, edx
	NOT edx
	And ebx, edx
	Or eax, ebx
	ProcedureReturn
EndProcedure

Procedure.l SetBit(a.l, bit.l)
	MOV eax, a
	MOV edx, bit
	BTS eax, edx
	ProcedureReturn
EndProcedure

Procedure.l ClearBit(a.l, bit.l)
	MOV eax, a
	MOV edx, bit
	BTC eax, edx
	ProcedureReturn
EndProcedure

Procedure.l GetBit(a.l, bit.l)
	XOr eax, eax
	MOV edx, bit
	BT  a, edx
	SETC al
	ProcedureReturn
EndProcedure

Posted: Sat May 20, 2006 8:25 pm
by dagcrack
Those are some slow min/max routines you've posted :!: (no sarcasm, just saying this after a few benchmarks I did with my own routines).

Posted: Sat May 20, 2006 8:47 pm
by dioxin

Code: Select all

'MIN
!mov eax,a
!mov ecx,b
!cmp eax,ecx
!cmovg eax,ecx
'min value is now in eax
 
'MAX
!mov eax,a
!mov ecx,b
!cmp ecx,eax
!cmovg eax,ecx
'max value is now in eax
but note that the CMOV instruction only came in with the PentiumPro so not all Pentiums have it.

Posted: Sun May 21, 2006 1:25 am
by rsts
dagcrack wrote:Those are some slow min/max routines you've posted :!: (no sarcasm, just saying this after a few benchmarks I did with my own routines).
Care to share yours so we can learn from them? :)

cheers

Posted: Sun May 21, 2006 4:04 am
by Dare2
Come on dagcrack, stop being a voyeur at a nudist beach. :) Show us what you've got!

Or keep everything zipped up. :) (no sarcasm <- and just saying that because it seems to mean that put-downs suddenly become acceptable behaviour)

Posted: Sun May 21, 2006 11:55 am
by Pupil
Actually i had not benchmarked the min max routines. My intention with the routines was to skip conditional jumps and hopefully avoid having the cpu flush the pipeline because of jump prediction misses. However this seemed a futile effort as even the following code will execute faster :)

Code: Select all

Procedure.l Min(a.l, b.l)
  If a < b
    Procedurereturn a
  Else
    Procedurereturn b
EndProcedure

Posted: Sun May 21, 2006 12:46 pm
by dioxin
Pupil,
avoiding jumps is a useful technique but not when replacing one jump takes up so much extra code.

The simple way to do a MIN or MAX is:

Code: Select all

	
!mov eax,a
!mov ecx,b
!cmp eax,ecx
!jg skip
!mov eax,ecx

skip:
eax now contains the result

Comparing it to your code:

Code: Select all

you've replaced this:
!jg skip
!mov eax,ecx

with this:
   XOr edx, edx 

   SETLE dl 
   SUB edx, 1 
   And ebx, edx 
   NOT edx 
   And eax, edx 
   Or eax, ebx 
So you replaced a short 2 opcode/4 byte sequence with a 7 opcode/16 byte sequence.
Also, if you look at the code, almost every line is directly dependant on the result of the previous line (edx is used in 6 of the 7 lines) so the CPU is forced to execute every line in sequence, it has no opportunity to do things in parallel.

If you need to avoid the CMOV instruction (as not all CPUs have it) then here is a clip from the Athlon Optimisation manual on how to do MIN:

Code: Select all

Example 4  Unsigned integer min function (z = x < y ? x : y):
MOV EAX, [x] ;load x
MOV EBX, [y] ;load y
SUB EAX, EBX ;x < y ? CF : NC ; x - y
SBB ECX, ECX ;x < y ? 0xffffffff : 0
AND ECX, EAX ;x < y ? x - y : 0
ADD ECX, EBX ;x < y ? x - y + y : y
MOV [z], ECX ;x < y ? x : y
This one replaces a 2 opcode/4 byte sequence with a 4 opcode/8 bytes sequence to avoid the jump. Still not as good as using CMOV but in cases where the data is random (and therefore the CPU's jump prediction is only 50% effective) it can beat the version with a jump.

Paul.

Posted: Sun May 21, 2006 12:49 pm
by thefool
a real pb procedure of Dioxin's code:

Code: Select all

Procedure.l Dioxi_Min(a.l, b.l)
!mov eax,dword[p.v_a]
!mov ecx,dword[p.v_b]
!cmp eax,ecx
!cmovg eax,ecx 

ProcedureReturn

EndProcedure
and this is the fastest method i've seen. Also one of the simplest :)

Posted: Sun May 21, 2006 1:18 pm
by dioxin
TheFool,
ONE of the simplest? You mean there's another, equally simple method??

I do often wonder about the usefulness of placing such short code snippets in a procedure. The procedure overheads will dwarf the time taken by the code.

Paul.

Posted: Sun May 21, 2006 1:43 pm
by thefool
dioxin wrote:TheFool,
ONE of the simplest? You mean there's another, equally simple method??

I do often wonder about the usefulness of placing such short code snippets in a procedure. The procedure overheads will dwarf the time taken by the code.

Paul.
Perhaps it IS the simplest :)

True, dioxin. You can always make a macro of it :)

Posted: Sun May 21, 2006 1:51 pm
by dioxin
As it happens, depending on circumstances, MMX/SSE stuff can make it simpler and quicker but unless you're already using MMX/SSE then the overhead outweighs the gain.