| Autor: | Wojciech Muła |
|---|---|
| Dodany: | 28.02.2002 |
| Aktualizacja: | 9.08.2007 |
Contents
W artykule zostaną pokazane asemblerowe implementacje niektórych funkcji zadeklarowanych w pliku nagłówkowym string.h.
UWAGA: na początku września 2007 opublikowałem bibliotekę see2string, która zawiera niektóre funkcje ze string.h zaimplementowane z użyciem rozkazów SSE2. Oczywiście zastosowałem w nich metody opisane w tym artykule.
Funkcja stwierdza czy dwa łańcuchy znaków, o tej samej długości są takie same.
segment .text
; edi, esi - wskaźniki na łańcuchy
; ecx - długość łańcuchów
; eax - wynik
memcmp:
mov ebx, ecx ; liczba bajtów które nie zmieszczą się
and ebx, 0x7 ; w rejestrze MMX
shr ecx, 3 ; ecx/=3;
jz .compare_rest ; jeśli zero porównanie MMX zostaje pominięte
.compare_mmx:
movq mm0, [edi] ; takie same różne (przykład)
pcmpeqd mm0, [esi] ; |ffffffff|ffffffff| |ffffffff|00000000|
psrlq mm0, 16 ; |0000ffff|ffffffff| |0000ffff|ffff0000|
movd eax, mm0 ; |ffffffff| |ffff0000|
cmp eax, -1 ;
jne .diff
add edi, 8
add esi, 8
dec ecx
jnz .compare_mmx
.compare_rest:
or ebx, ebx ; jeśli nie jest potrzebne dodatkowe porównanie
jz .equal ; łańcuchy są identyczne
movq mm0, [edi]
movq mm1, [edi]
pand mm0, [mask + ebx*8 - 8] ; wyzeruj dane spoza końca łańcucha
pand mm1, [mask + ebx*8 - 8]
pcmpeqd mm0, mm1
psrlq mm0, 16
movd eax, mm0
cmp eax, -1
sete eax ; eax = 1 gdy równe
ret
segment .data
mask db 0xff,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 ; 1
db 0xff,0xff,0x00,0x00, 0x00,0x00,0x00,0x00 ; 2
db 0xff,0xff,0xff,0x00, 0x00,0x00,0x00,0x00 ; 3
db 0xff,0xff,0xff,0xff, 0x00,0x00,0x00,0x00 ; 4
db 0xff,0xff,0xff,0xff, 0xff,0x00,0x00,0x00 ; 5
db 0xff,0xff,0xff,0xff, 0xff,0xff,0x00,0x00 ; 6
db 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00 ; 7
segment .text
; edi, esi - wskaźniki na łańcuchy
; ecx - długość łańcuchów
; eax - wynik
strncmp:
mov ebx, ecx
shr ecx, 2 ; ecx/=4 - ilość dword'ów
jz .compare_rest
.compare4b:
mov edx, [edi]
cmp edx, [esi]
jne .diff
add edi, 4
add esi, 4
dec ecx
jnz .compare4b
.compare_rest:
and ebx, 0x3 ; ebx - pozostałe bajty
jz .equal
mov ebx, [mask + ebx*4 - 4]
mov eax, [edi] ; pobierz 4 bajty
mov ecx, [esi] ;
and eax, ebx ; zamaskuj bajty pobrane
and ecx, ebx ; spoza łańcuchów
cmp eax, ecx ; porównanie
sete eax
segment .data
mask db 0xff,0x00,0x00,0x00
db 0xff,0xff,0x00,0x00
db 0xff,0xff,0xff,0x00
Poniższa funkcja porównuje dwa łańcuchy. Zwracany wynik jest zgodny z konwencją funkcji strcmp znanej z C.
; wejście: esi (A), edi (B) - porównywane łańcuchy
; wyjście: eax
; modyfikowane:
; MMX: mm0, mm1, mm2, mm6, mm7
; x86: ebx
strcmp:
pxor mm7, mm7 ; mm7 = packed_byte(0x00)
xor ecx, ecx ; ecx = 0
.compare:
movq mm0, [esi+ecx]
movq mm1, [edi+ecx]
add ecx, 8
pcmpeqb mm1, mm0 ; 0x00 na pozycji gdzie różnią się oba łańcuchy
pcmpeqb mm0, mm7 ; 0xff na pozycji '\0' w łańcuchu A
pandn mm0, mm1 ; połącz wyniki - 0x00 gdzie łańcuchy różne/zero w A
pmovmskb eax, mm0 ; i zamień na maskę bitową
cmp al, 0xff ; gdy którykolwiek bit jest wyzerowany, świadczy o
jz .compare ; napotkaniu końca A lub różniącego je bajtu
not eax
bsf eax, eax ; pozycja pierwszego ustawionego bitu
add ecx, eax
movzx ebx, byte [esi+ecx] ; pobierz
movzx eax, byte [edi+ecx] ; znaki
sub eax, ebx ; i oblicz różnicę
ret
Funkcja oblicza długość łańcucha znaków zakończonego zerem, z powodzeniem może być wykorzystana jako strchr, wystarczy wypełnić mm1 szukanym znakiem.
; edi - pierwszy znak
; ecx - wynik: długość łańcucha
mmx_strlen:
pxor mm1, mm1 ; packed_byte(0x00)
xor ecx, ecx
.count: ; np.
movq mm0, [edi] ; mm0 = | 0 |'e'| 0 |'b'|'m'|'e'|'s'|'a'|
pcmpeqb mm0, mm1 ; mm0 = | ff| 0 | ff| 0 | 0 | 0 | 0 | 0 |
pmovmskb eax, mm0 ; eax = 10100000b (0xa0)
test al, al
jnz .founded ; w wyniku są ustawione bity, znaleziono kończące zero
; w przeciwny przypadku...
add edi, 8 ; następne 8 bajtów
add ecx, 8 ; zwiększ o 8 długość
jmp .count
.founded:
bsf eax, eax ; znajdź pierwszy ustawiony bit
add ecx, eax
ret
; edi - pierwszy znak
; al - szukany znak
; edi - wynik (wskaźnik, NULL gdy znak nie znaleziony)
mmx_strlen:
pxor mm7, mm7 ; packed_byte(0x00)
movzx eax, al
imul eax, 0x01010101
movd mm6, eax
punpcklbw mm6, mm6 ; |AL|AL|AL|AL|AL|AL|AL|AL|
.count: ; np. AL = 'e'
movq mm0, [edi] ; mm0 = | 0 |'e'| 0 |'b'|'m'|'e'|'s'|'a'|
movq mm1, mm0
pcmpeqb mm0, mm7 ; mm0 = | ff| 0 | ff| 0 | 0 | 0 | 0 | 0 | -- \0
pcmpeqb mm1, mm6 ; mm1 = | 0 | ff| 0 | 0 | 0 | ff| 0 | 0 | -- AL
por mm0, mm1 ; mm0 = | ff| ff| ff| 0 | 0 | ff| 0 | 0 | -- AL & \0
pmovmskb eax, mm0 ; eax = 11100100b (0xa0)
test al, al
jnz .founded ; w wyniku są ustawione bity, znaleziono
; kończące zero lub szukany znak;
; w przeciwny przypadku...
add edi, 8 ; następne 8 bajtów
jmp .count
.founded:
bsf eax, eax ; znajdź pierwszy ustawiony bit
add edi, eax ; edi - adres albo \0 albo szukany znak
cmp [edi], 0 ; edi == 0?
jz .NULL
ret ; nie, edi wskazuje na szukany znak
.NULL
xor edi, edi ; tak, nie znaleziono znaku, edi = NULL
ret
Pierwsze procesory (Pentium MMX, Pentium II) posiadające rozszerzenie MMX nie miały wielu obecnie dostępnych instrukcji, w tym intensywnie wykorzystywaną w wymienionych funkcjach PMOVMSKB — można ją z powodzeniem zastąpić czteroma dodatkowymi rozkazami:
; mm0 -- maska bajtowa ; |aaaaaaaa|bbbbbbbb|cccccccc|dddddddd|eeeeeeee|ffffffff|gggggggg|hhhhhhhh|
psrlq mm0, 4 ; |0000aaaa|aaaabbbb|bbbbcccc|ccccdddd|ddddeeee|eeeeffff|ffffgggg|gggghhhh|
pand mm0, [maska_0x0011001100110011] ; |00000000|000a000b|00000000|000c000d|00000000|000e000f|00000000|000g000h|
packsswb mm0, mm0 ; |000a000b|000c000d|000e000f|000g000h|000a000b|000c000d|000e000f|000g000h|
; ustawione bity są umieszczone
; na początku każdej tetrady
movd eax, mm0
bsf/bsr eax, eax ; czyli wynikiem jest index*4, a stąd:
shr eax, 2 ; eax = index
Funkcje wykorzystujące zarówno PMOVMSKB jak i opisany wyżej zamiennik są dostępne w pliku mmx_string.c (kompilacja w GCC).
Kody małych liter ASCII różnią się od dużych tylko jednym, piątym bitem.
| A..Z | 0x41..0x5a |
| a..z | 0x61..0x7a |
; wejście/wyjście - al litera
tolower:
cmp al, 0x41 ;
jb .skip ;
cmp al, 0x5a ;
ja .skip ; sprawdź zakres
xor al, 0x20 ; zaneguj 5-ty bit, jeśli to duża litera
.skip:
ret
Kod bez rozgałęzień:
tolower:
cmp al, 0x41
setnb bl ; (al >= 0x41) ? bl=1 : bl=0;
cmp al, 0x5a
setna bh ; (al <= 0x5a) ? bh=1 : bh=0;
and bl, bh
shl bl, 5
xor al, bl ; wyzeruj 5-ty bit jeśli (al>=41h) & (al<=5ah)
W przypadku funkcji toupper wystarczy zmienić sprawdzany zakres.
Interesujące może być sprawdzenie czy bajt należy do przedziału (w kodzie wydzielone ***). Poniższa funkcja asemblerowa jest odpowiednikiem funkcji strupr, ale tak jak wcześniej wystarczy zmienić sprawdzany zakres aby uzyskać strlwr.
segment .text
; wejście: edi - wskaźnik na łańcuch 8 znaków
; mm3 = packed_byte(0x00)
; mm4 = packed_byte(0x20)
simple_strupr:
mov mm0, [edi] ; wprowadź 8 znaków
;***
mov mm1, mm0 ; kopiuj
mov mm2, mm0 ;
psubusb mm1, [mmx_0x40] ; wszystkie bajty mniejsze od 0x41
psubusb mm2, [mmx_0x5a] ; i 0x5a zostaną wyzerowane
pcmpeqb mm1, mm3 ; mm1 = (bajt < 'A') ? 0xff : 0x00
pcmpeqb mm2, mm3 ; mm2 = (bajt <= 'Z') ? 0xff : 0x00
pandn mm1, mm2 ; mm1 = (bajt >= 'A' && bajt <= 'Z') ? 0xff : 0x00
pand mm1, mm4 ; dla odpowiednich bajtów przypisz maskę
;***
pxor mm0, mm1 ; zanegowanie 5-go bitu - małe litery
movq [edi], mm0
ret
segment .data
mmx_0x40 dd 0x40404040, 0x40404040
mmx_0x5a dd 0x5a5a5a5a, 0x5a5a5a5a
Oto używany algorytm (dzięki uprzejmości Dale Moora).
char * strstr (const char *s1, const char *s2) {
int j = 0;
while (1) {
if (!s2[j])
return (char *)s1;
if (!s1[j])
return NULL;
if (s1[j] == s2[j])
j++;
else
j = 0,
s1++;
}
}
_strstr:
push esi ; s1
push edi ; s2
push ecx ; j
xor ecx, 0 ; j = 0
.loop:
mov al, byte ptr [esi+ecx] ; al = s1[j]
mov ah, byte ptr [edi+ecx] ; ah = s2[j]
or ah, ah ; if (!s2[j]) return (char *)s1;
jnz .1
lea eax, [esi+ecx]
jmp .exit
.1:
or al, al ; if (!s1[j]) return NULL
jnz .2
mov eax, NULL
jmp .exit
.2:
sub al, ah ; if (s1[j] == s2[j])
jnz @else
.then:
inc ecx ; j++
jmp @loop
.else:
xor ecx, ecx ; j=0, s1++
inc esi
jmp .loop
.exit:
pop ecx
pop edi
pop esi
ret
_strstr:
push esi ; s1
push edi ; s2
push ecx ; j
push ebx
push edx
xor ecx, 0 ; j = 0
.loop:
mov al, byte ptr [esi+ecx] ; al = s1[j]
mov ah, byte ptr [edi+ecx] ; ah = s2[j]
or ah, ah ; if (!s2[j]) return (char *)s1;
jz .exit
or al, al ; if (!s1[j]) return NULL
jz .exit
mov edx, esi
mov ebx, ecx
inc edx ; edx = s1+1
inc ebx ; ebx = j+1
cmp al, ah ; eq = (s1[j] == s2[j])
mov ecx, 0 ; j = 0
cmove ecx, ebx ; if (eq) j = ebx
cmovne esi, edx ; if (!eq) s1++
jmp .loop
.exit:
add esi, ecx ; esi = &s1[j]
mov edi, NULL ; edi = NULL
or al, al ; ZF = al==0
cmovz eax, edi
cmovnz eax, esi
pop edx
pop ebx
pop ecx
pop edi
pop esi
ret