22
SVEU ˇ CILI ˇ STE U ZAGREBU FAKULTET ELEKTROTEHNIKE I RA ˇ CUNARSTVA Zavod za elektroniku, mikroelektroniku, raˇ cunalne i inteligentne sustave Laboratorijske vje ˇ zbe ARHITEKTURE RA ˇ CUNALA 2 Programiranje u strojnom jeziku arhitekture x86 Dodatak uputama Zagreb, prosinac 2012.

Programiranje u strojnom jeziku arhitekture x86ssegvic/ar2/lab2_extra.pdf · 2020. 12. 17. · Programiranje u strojnom jeziku arhitekture x86 1 Stog, parametri, varijable, funkcije,

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

  • SVEUČILIŠTE U ZAGREBUFAKULTET ELEKTROTEHNIKE I RAČUNARSTVA

    Zavod za elektroniku, mikroelektroniku, računalne i inteligentne sustave

    Laboratorijske vježbe

    ARHITEKTURE RAČUNALA 2

    Programiranje u strojnom jeziku arhitekturex86

    Dodatak uputama

    Zagreb, prosinac 2012.

  • Programiranje u strojnom jeziku arhitekture x86

    1 Stog, parametri, varijable, funkcije, primjeri

    U ovom dodatku vježbi biti će objašnjeno kao se koristi stog prilikom poziva funkcija unutar x86arhitekture. Prije toga korisno je pročitati kratak uvod u x86 asembler (npr. ovdje ili ovdje).Poznato je da se prilikom poziva funkcije na stog pohranjuju parametri funkcije, povratna adresa,te lokalne varijable. Pogledajmo sljedeći primjer funkcije pisane u C-u:

    int primjer(int a, int b, int c)

    {

    int x,y,z;

    ...

    }

    Za funkcijski poziv:

    r = primjer(a, b, c);

    Prevoditelj će generirati sljedeći asemblerski kod:

    push c

    push b

    push a

    call primjer

    mov r, eax

    Parametri funkcije pohranjuju se na stog s desna na lijevo (a je na najmanjoj adresi)1. Imenaparametara i varijabli samo su simbolična (nisu prikazana takvim imenima i u asembleru, većmemorijskim lokacijama npr. [ebp+4], [ebp+8]). Povratna vrijednost funkcije prenosi se prekoregistra eax, te se po povratku iz potprograma preslikava u varijablu r.

    eax

    31 15 7 0

    ah al

    ax

    ebx

    31 15 7 0

    bh bl

    bx

    ecx

    31 15 7 0

    ch cl

    cx

    edx

    31 15 7 0

    dh dl

    dx

    ebp

    31 15 0

    bp

    esp

    31 15 0

    sp

    esi

    31 15 0

    si

    edi

    31 15 0

    di

    Slika 1: Registri x86 programskog modela. Registri eax, ebx, ecx i edx su registri opće namjene.Ostali registri imaju posebna značenja. Sve registre možemo koristiti u strojnom programu,no unutar funkcijskih poziva (gdje ih koristimo), moramo ih prije korǐstenja staviti na stogkako bi se očuvala njihova stara vrijednost (pohranjivanje konteksta). Jedini registri čija staravrijednost nije bitna (dakle njih ne moramo staviti na stog) su registri eax, ecx i edx.

    Uloge registara esp, ebp i eip su (eip nije prikazan na slici; takoder nije prikazan niti eflags registar.Prema njegovom imenu (eflags) lako zaključujemo da on sadrži vrijednosti zastavica procesora):

    1U vǐsim programskim jezicima parametri funkcije mogu biti složeni izrazi, koji se prije stavljanja na stog evalu-iraju. Da li će se evaluacija izvesti s desna na lijevo ili obratno, nije standardizirano i nije povezano sa redoslijedomnjihovog smještanja na stog.

    2 2. laboratorijska vježba AR2

    http://www.cs.virginia.edu/~evans/cs216/guides/x86.htmlhttp://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture

  • Programiranje u strojnom jeziku arhitekture x86

    esp je pokazivač na vrh stoga. On uvijek pokazuje na zadnji korǐsteni element na stogu (ne naprvi slobodni). Vrh stoga je memorijska lokacija sa najmanjom adresom (u trenutnom okvirustoga). Vrijednost esp može se mijenjati implicitno instrukcijama (npr. push, pop, call, ret)ili izravno instrukcijama (npr. add esp, $16, sub esp, $32).

    ebp je registar koji se koristi za referenciranje na parametre funkcije i lokalne varijable unutartrenutnog okvira stoga. Njegovu staru vrijednost uvijek pohranjujemo na stog na početkuizvodenja funkcije (cdecl prolog). Vrijednost ebp registra mijenjamo izravno.

    eip sadrži adresu sljedeće instrukcije (PC). Instrukcije skoka direktno mijenjaju njegovu vrijed-nost. Instrukcija poziva potprograma pohranjuje njegovu staru vrijednost na stog. Njegovanova vrijednost postaje adresa potprograma kojeg pozivamo.

    Instrukcija “call primjer“ se zapravo prevodi kao:

    push eip

    jmp primjer

    Pogledajmo sada funkciju main (koja poziva funkciju primjer) u C-u:

    int main(int argc, int **argv)

    {

    int r = 0;

    r = primjer(100,200,300);

    }

    Funkcija main u asembleru2 izgleda ovako (gcc):

    main:

    push ebp \\pohranimo staru vrijednost na stog

    mov ebp, esp

    sub esp, 16 \\pomaknemo vrh stoga za 16B

    mov DWORD PTR [ebp-4], 0 \\postavimo vrijednost lokalne varijable r

    mov DWORD PTR [esp+8], 300 \\postavljamo parametre (ne sa push jer je esp vec pomaknut

    )

    mov DWORD PTR [esp+4], 200

    mov DWORD PTR [esp], 100

    call primjer \\pozovemo funkciju

    mov DWORD PTR [ebp-4], eax \\povratnu vrijednost preslikamo u lokalnu varijablu

    mov eax, 0

    leave

    ret

    Možemo primijetiti kako funkcija main takoder ima cdecl prolog i epilog (leave). Takoder gccprevoditelj ne postavlja parametre funkcije na stog sa push, već odredi koliko je memorije potrebnoza lokalne varijable i parametre, te promijeni esp samo jednom. Nakon toga postavlja parametre uslobodne adrese. U našem slučaju imamo 3 parametra veličine 4 bajta. Takoder imamo 1 lokalnuvarijablu veličine 4 bajta. Zbog toga će prevoditelj pomaknuti esp za 16 bajtova (tj. napraviti subesp,$16). U ovom primjeru je slučajno broj 16 dobra vrijednost. Razlog tome je što segmenti stoga

    2Koristimo Intel sintaksu (postoji još i AT&T sintaksa). ”Čudan” simbol DWORD PTR u intel sintaksi označavaveličinu operanda (DWORD = 4B). Taj operand je nužan u instrukcijama gdje na temelju operanda nije mogućeutvrditi njegovu veličinu (npr. mov [ebp-4], 2; 2 može biti 1, 2, 4, ili 8 bajtova). U instrukcijama gdje je operandregistar taj simbol se može izostaviti (npr. mov [ebp-4], eax; eax ima 32 bita). Postoje još i BYTE PTR = 1B,WORD PTR = 2B, QWORD PTR = 8B

    AR2 2. laboratorijska vježba 3

  • Programiranje u strojnom jeziku arhitekture x86

    (tj. adrese mem. lokacija) koje funkcija zauzima trebaju (moraju) biti poravnati sa 8 bajtova 3

    (poravnati sa 8 znači da je početna adrese segmenta stoga djeljiva sa 8). Budući da je funkcija mainpočetna adresa izvodenja našeg programa, možemo pretpostaviti da je esp inicijalno bio poravnatsa 8 bajtova (prije poziva funkcije main). Pozivom funkcije main postavila se povratna adresana stog, te se u prologu sa ”push ebp” esp smanjio za 4. Kako trebamo 16 bajtova za lokalnuvarijablu te parametre, ukupni pomak bi bio 16+4+4 = 24 bajta. 24 je vǐsekratnik od 8, pa stogazaključujemo da esp ostaje poravnat sa 8. Katkad će prevoditelj u prologu funkcije generirati vrločudne instrukcije koje izgledaju ovako:

    ...

    sub esp, neki broj

    and esp, -16

    ...

    Time zapravo pomičemo esp za broj bajtova koji nam je potreban za podatke, dok sa drugominstrukcijom doljnjih 4 bita postavljamo na 0 (osiguravamo da je esp djeljiv sa 16; tada je takoderdjeljiv i sa 8). To efektivno znači smanjivanje vrijednosti esp (broj postaje manji ako mu zadnjebitove postavimo na 0), a to je upravo smjer rasta stoga. U epilogu funkcije staru vrijednost espobnavljamo iz ebp:

    ...

    mov esp, ebp

    pop ebp

    ret

    Za sada je dovoljno znati da je razlog poravnavanju memorije sa 8 bajtova korǐstenje priručnememorije i veličina linije priručne memorije (procesor priručnu memoriju puni/ažurira blokovimaod nekoliko bajtova (ne bajt po bajt), pri čemu su blokovi veličine 2n). Utjecaj veličine p.m. (injena brzina) na performansu izvodenja programa biti će tema 3. laboratorijske vježbe.

    Iako većina novijih procesora podržava segmentiranje stoga koje nije poravnato sa 8(16) bajtova,različiti operacijski sustavi to neće podržati. Neki će ispravno izvesti zadani program i javitiporuku poput (Segmentation fault), dok će drugi program iznenada prekinuti. Razlog tome su ineke instrukcije (program može sadržavati instrukcije iz vǐse različitih instrukcijskih skupova) kojeisključivo mogu pristupati podatcima čije su adrese vǐsekratnici brojeva 8 ili 16. To su obično SIMDinstrukcije. Kako bi izbjegli eventualne probleme, dobro je podatke na stogu zauzeti u blokovima po8(16) bajtova (ako zauzmemo vǐse nego što je potrebno, nije problem, program će ipak raditi brže).Tko želi saznati vǐse o ovom problemu (optimizaciji pristupa memoriji, radu priručne memorije)izuzetno dobar manual može se pronaći ovdje (manual 2 (optimizing assemply.pdf) poglavlje 11).Na istoj stranici nalazi se vǐse materijala koji obraduju optimizaciju (u asembleru i C++). Kadau programima koristimo blokove memorije (statičke ili dinamičke), dobra je optimizacijska navikada njihova početna adresa bude vǐsekratnik od 8(16).

    3Ovo vrijedi za sve 32-bitne arhitekture/operacijske sustave koji koriste x86 asemblerske kodove. Za 64-bitnearhitekture/operacijske sustave, prevoditelji će podatke poravnati sa 16 bajtova. Poravnanje sa sa 16B vrijedi uvijekna nekim operacijskim sustavima (Mac OSX i Linux).

    4 2. laboratorijska vježba AR2

    http://www.agner.org/optimize/

  • Programiranje u strojnom jeziku arhitekture x86

    Pogledajmo stanje na stogu:Relativan pomak u B Memorija

    ... ...

    -16 100 ← esp-12 200

    -8 300

    -4 0 varijabla r

    0 stari ebp ← ebp, (esp prije sub esp, $28)+4 stari eip (povratna adresa od main) (← esp prije main prologa)+8 argc

    +12 argv

    ... ...

    Na dnu stoga nalaze se parametri funkcije main (adrese +8 i +12) [u tablici kao referentnu adresu(0) uzimamo onu na koju pokazuje pokazivač na trenutni okvir stoga, dakle registar ebp. Ostaleadrese predstavljaju relativan pomak u bajtovima u odnosu na tu adresu]. Prije cdecl prologa espje pokazivao na adresu +4 (tamo se nalazi povratna adresa iz main). Zatim je ebp stavljen nastog (adresa 0). To je automatski pomaknulo i esp na adresu 0. Tada je novi ebp postao trenutniesp, te je esp smanjen za 16. Time je završio prolog funkcije main. Lokalna varijabla r nalazi sena adresi -4. Možemo uočiti standardnu organizaciju podataka na stogu (od veće adrese premamanjoj): najprije idu parametri funkcije, zatim povratna adresa i pokazivač na prijašnji okvirstoga, te lokalne varijable. Na adresama -8, -12, i -16 nalaze se parametri funkcije primjer. Ono štojoš trebamo napraviti kako bismo pokrenuli funkciju primjer je postaviti iznad njenih parametarasadržaj registra eip, te skočiti na početnu adresu od funkcije primjer. To upravo radi instrukcija“call primjer“. Slijedi funkcija primjer u C-u, te u asembleru:

    int primjer(int a, int b, int c)

    {

    int x = 1;

    int y = 2;

    int z = 3;

    return a*x+b*y+c*z;

    }

    primjer:

    push ebp \\pohranimo staru vrijednost na stog

    mov ebp, esp

    sub esp, 16 \\pomaknemo vrh stoga za 16 (3 varijable)

    mov DWORD PTR [ebp-4], 1 \\x

    mov DWORD PTR [ebp-8], 2 \\y

    mov DWORD PTR [ebp-12], 3 \\z

    mov eax, [ebp+8] \\eax = a

    mov edx, eax \\edx = eax = a

    imul edx, [ebp-4] \\edx = a*x

    mov eax, [ebp+12] \\eax = b

    imul eax, [ebp-8] \\eax = b*y

    add edx, eax \\edx = a*x + b*y

    mov eax, [ebp+16] \\eax = c

    imul eax, [ebp-12] \\eax = c*z

    lea eax, [eax+edx] \\eax = eax + edx = a*x + b*y +c*z

    leave

    AR2 2. laboratorijska vježba 5

  • Programiranje u strojnom jeziku arhitekture x86

    ret

    Stog prilikom izvodenja funkcije primjer izgleda kao u tablici ispod. Na početku funkcije izvodi secdecl prolog, pri čemu se ebp pohranjuje na stog. Novi ebp se postavlja na trenutni esp (koji sadapokazuje na zadnje pohranjeni ebp), te se esp smanjuje za 16 (imamo 3 lokalne varijable od 4 bajta).Za novu referentnu adresu okvira stoga (0) u tablici postavljena je ona na koju pokazuje trenutniregistar ebp unutar funkcije (u zagradama se nalaze stari relativni pomaci koji su se koristili uprethodnoj tablici).

    Bez obzira što esp pokazuje na praznu lokaciju (u koju može stati još jedna varijabla od 4B),smatramo to polje zauzetim.

    U ovoj vježbi, budući da koristimo x86 asembler, možemo zbog jednostavnosti uvesti pravilo da podaci na stogu

    trebaju biti poravnati sa 8 bajtova (prilikom poziva funkcija). To znači da ćemo vrijednost registra esp mijenjati u

    8*n koracima, pri čemu je n cijeli broj.

    Relativan pomak u B Memorija

    ... ...

    -16 (-40) (prazno) ← esp-12 (-36) z

    -8 (-32) y

    -4 (-28) x

    0 (-24) stari ebp ← ebp, (esp prije sub esp, $16)+4 (-20) stari eip (povratna adresa od primjer)

    +8 (-16) 100 a

    +12 (-12) 200 b

    +16 (-8) 300 c

    +20 (-4) 0 varijabla r

    +24 (0) stari ebp

    +28 (+4) stari eip (povratna adresa od main)

    +32 (+8) argc

    +36 (+12) argv

    ... ...

    6 2. laboratorijska vježba AR2

  • Programiranje u strojnom jeziku arhitekture x86

    2 2 primjera

    U nastavku se nalaze primjeri 2 funkcije u C-u i asembleru (asemblerski kod je direktan prijevodC funkcija, bez primjene optimizacija).

    Potrebno je napisati funkciju koja rekurzivno računa sumu prirodnih brojeva manjih ili jednakihn:

    int suma(int n)

    {

    if(n

  • Programiranje u strojnom jeziku arhitekture x86

    add edx, 1

    mov [eax], edx

    add DWORD PTR [ebp-4], 1

    L10:

    mov eax, [ebp-4]

    cmp eax, [ebp+12]

    setl al

    test al, al

    jne L11

    mov esp, ebp

    pop ebp

    ret

    3 x87 instrukcijski podskup

    Sa x87 označavamo podskup instrukcijskog skupa x86 koji sadrže instrukcije za operacije s broje-vima sa pomičnim zarezom. Glavna komponenta FPU (floating point) jedinice procesora je FPUstog. To je poseban stog (nalazi se na procesorskom čipu) i neovisan je o osnovnom stogu koji senalazi u RAM-u. Sastoji se od 8 elemenata duljine po 80 bita (slika 2).

    st

    79 0

    st(1)

    st(2)

    st(7)

    Slika 2: FPU stog. Može sadržavati do 8 elemenata.

    Elementi se dohvaćaju izravno preko imena st(0), st(1), . . . , st(7) (ako pǐse samo st onda je tost(0)). Mnoge instrukcije implicitno dohvaćaju elemente (u tom slučaju to su elementi st i st(1)ili samo st). Postoje posebne instrukcije koje učitavaju najčešće konstante: fldz stavlja 0.0 na vrhstoga, fld1 stavlja 1.0. Sve ostale vrijednosti potrebno je učitati iz memorije. Niti jedna aritmetičkax87 instrukcija ne izvodi operacije sa memorijskim elementima izravno (svi se prije toga morajuučitati na FPU stog). Pogledajmo:

    fld1 /*stavljamo 1 u st, tj. radimo push na stog*/

    /*prethodni st sada ce biti st(1) ...*/

    fld DWORD PTR [eax] /* ucitavamo vrijednost elementa koji se */

    /* nalazi na adresi koja je u registru eax */

    /* npr. u varijablu a (float *a) stavimo u eax */

    /* instrukcija ce u st staviti a[0]*/

    fmulp /* mnozimo elemente st i st(1) te rezultat pohranimo u st i st(1)*/

    /* zatim st maknemo sa stoga (pop), st(1) sada postaje st i sadrzi rezultat*/

    Pogledajmo sljedeći primjer u kojem želimo napisati funkciju koja skalarno množi 2 vektora zadaneduljine. C++ kod izgleda ovako:

    8 2. laboratorijska vježba AR2

  • Programiranje u strojnom jeziku arhitekture x86

    #include

    extern "C" void mnozi_float_asm(float*, float*, float*, int);

    int main()

    {

    int n = 8;

    __attribute__ ((aligned(32))) float u[8] = {1,2,3,4,5,6,7,8};

    __attribute__ ((aligned(32))) float v[8] = {-1,0,5,2,3,6,11,5};

    __attribute__ ((aligned(32))) float w[8];

    //broji pripadne elemente polja u i v duljine n (rezultat je polje w)

    mnozi_float_asm(u,v,w,n);

    for(int i = 0; i < n; ++i)

    {

    std::cout

  • Programiranje u strojnom jeziku arhitekture x86

    fld DWORD PTR [esi+eax*4] /* st = u[i] */

    fld DWORD PTR [edi+eax*4] /* st = v[i], st(1) = u[i] */

    fmulp /* st = u[i]*v[i], pop st(1) */

    fstp DWORD PTR [ecx+eax*4] /* w[i] = st, pop st */

    inc eax /* i = i + 1 */

    cmp eax, ebx /* i < n ?*/

    jne 2b /* Skok na labelu 2. Pise 2b. b dolazi od back */

    /* Sintaksno pravilo gcc prevoditelja trazi da kada */

    /* zelimo skociti na labelu koja je prije (na manjoj adresi) */

    /* dodamo b na kraju. */

    pop edi /* dohvacamo stare vrijednosti */

    pop esi

    pop ebx

    /* cdecl epilog: */

    pop ebp /* umjesto ’add esp,4, pop ebp’ moze biti ’leave’*/

    ret /* povratak iz potprograma */

    Prevodenjem programa (g++ -m32 main gcc.cpp p asm.s i njegovim izvodenjem dobivamo očekivaniispis:

    -1 0 15 8 15 36 77 40

    Za vježbu pokušajte nacrtati stanje na FPU stogu za vrijeme izvodenja prikazane funkcije. Unastavku se nalaze tablice najčešće korǐstenih x87 instrukcija.

    Tablica 1: Osnovne instrukcije za rad sa x87 stogom

    Mnemonik Operandi Značenjefinit - inicijaliziraj FPU i očisti stogfld (addr) Sadržaj *addr pokazivača push na FPU stogfld st(x) Element st(x) push na FPU stogfld1 - 1.0 push na stogfldz - 0.0 push na stogfst st(x) st(x) = stfstp st(x) st(x) = st, pop stfst (addr) *addr = stfstp (addr) *addr = st, pop stfxch - zamjeni sadržaje st(1) i stfxch st(x) zamjeni sadržaje st(x) i st

    Tablica 2: Osnovne logičke x87 instrukcije

    Mnemonik Operandi Usporedi st safcom - st(1)fcom st(x) st(x)fcom (mem) *memftst 0.0fcomp - st(1), pop stfcomp st(x) st(x), pop stfcomp (mem) *mem, pop stfcompp st(1), pop st, pop st(1)

    fstsw ax ax = FPU registar stanja. Zatim sa instrukcijamatest ili bt ispitujemo ax. Ako je rezultat usporedbest > onda su bitovi (14, 10, 8) u ax (0, 0, 0); akoje st < onda su ti bitovi (0, 0, 1); ako je st = ondasu (1, 0, 0)

    10 2. laboratorijska vježba AR2

  • Programiranje u strojnom jeziku arhitekture x86

    Tablica 3: Osnovne aritmetičke x87 instrukcije

    Mnemonik Operandi Značenjefadd(fmul) - zamjeni st i st(1) njihovom sumom (produktom)fadd(fmul) (mem) zamjeni st sa sumom (produktom) st i *memfadd(fmul) st, st(x) zamjeni st sa sumom (produktom) st i st(x)fadd(fmul) st(x), st zamjeni st(x) sa sumom (produktom) st i st(x)fadd(fmul) st(x), st zamjeni st(x) sa sumom (produktom) st i st(x), pop stfsub - zamjeni st i st(x) sa st(1)-stfsub (mem) zamjeni st sa st - *memfsub st, st(x) st-= st(x)fsub st(x), st st(x)-= stfsubp st(x), st st(x)-= st, pop stfsubr - zamjeni st i st(1) sa st-st(1)fsubr (mem) zamjeni st sa *mem-stfsubr st(x), st st(x) = st-st(x)fsubr st, st(x) st = st(x)-stfsubpr st(x), st st(x) = st-st(x), pop stfabs - st = abs(st)fchs - st = -st

    4 Vektorske instrukcije tipa SIMD

    SIMD (eng. Single Instruction Multiple Data) Instrukcije tipa SIMD omogućuju istodobno izvodenjeoperacije nad većim brojem podataka.

    Kao ilustraciju možemo naveti primjer zbrajanja dva vektora (operacija C = A + B) (slika 3).

    A7 A6 A5 A4 A3 A2 A1 A0

    +

    B7 B6 B5 B4 B3 B2 B1 B0

    =

    A7+B7 A6+B6 A5+B5 A4+B4 A3+B3 A2+B2 A1+B1 A0+B0

    Vektorsko (SIMD) zbrajanje

    A

    +

    B

    =

    A+B

    Skalarno zbrajanje

    Slika 3: Operacija zbrajanja elemenata vektora A i B na SIMD (vektorskom) procesoru i skalarnozbrajanje. Ako su latencije SIMD i skalarnih instrukcija jednake, SIMD operacija je 8 puta brža.

    Skalarna izvedba takvog zbrajanja izvodila bi zbrajanje jednog elementa vektora A sa jednim ele-mentom vektora B, te pohranjivala rezultat u odgovarajući element vekotra C. Istovijetna SIMDinstrukcija bi istovremeno zbrojila n elemenata vektora A sa n elemenata vektora B, te rezultatpohranila u odgovarajućih n elemenata vektora C. Stoga možemo reći da je SIMD, ili vektorska,instrukcija n puta brža (n predstavlja broj elemenata koji se mogu pohraniti u vektorski registarSIMD instrukcije).

    Primjetimo i jedan manji detalj: vektori podataka (A,B,C) su pohranjeni kao nizovi slijednih me-morijskih lokacija koji mogu imati proizvoljnu duljinu i koja nije nužno djeljiva sa n. Stoga akoSIMD instrukcija izvodi operaciju sa po n slijednih elemenata, ona neće moći u potpunosti izvesti tuoperaciju. Taj problem se može rješiti tako da se vektori produlje dodatnim (praznim) elementimakako bi njihova duljina postala djeljiva sa n. Operacija se zatim može izvesti i na tim dodatnim ele-mentima, no te elemente nećemo uzeti u obzir prilikom čitanja rezultata. Drugo rješenja je izvoditiSIMD operacije onoliko puta koliko je duljina vektora podataka djeljiva sa n, a za ostale elementeoperaciju izvesti skalarno. Moderni procesori koriste sljedeće SIMD instrukcijske skupove:

    AR2 2. laboratorijska vježba 11

  • Programiranje u strojnom jeziku arhitekture x86

    • SSE (eng. Streaming SIMD Extensions)

    • MMX (eng. MultiMedia Extension)

    • AVX (eng. Advanced Vector Extension)

    Na primjer, istrukcijske skup koje podržava procesor na Linux operacijskom sustavu možemo ispitatitako da ispǐsemo datoteku cpuinfo:

    $ cat /proc/cpuinfo

    Kasnije ćemo pokazati kako iste podatke dobiti korǐstenjem instrukcije cpuid unutar C programa.Na Windows operacijskom sustavu to možemo postići korǐstenjem programa CpuZ. Na primjer:procesor Intel Core 2 Duo P8600 podržava instrukcijske skupove (od ovdje spomenutih): mmx,sse, sse2, ssse3, sse4 1. Noviji procesor Intel Core i7-2600 podržava instrukcijske skupove: mmx,sse, sse2, ssse3, sse4 1, sse4 2 i avx. U nastavku ćemo ukratko reći nešto o instrukcijskim skupo-vima mmx, sse i najnovijem avx, te prikazati nekoliko instrukcija iz svakog od njih u obliku kraćihprogramskih odsječaka. Vrlo kratki asemblerski programski odsječci mogu se smjesiti zajedno saC/C++ kodom u obliku inline zapisa (ti zapisi imaju prefiks asm ili asm), no duže asem-blerske odsječke bolje je staviti u zasebnu datoteku (ima ekstenziju .s). Ukoliko se pǐse nekolikoslijednih asm zapisa postoji opasnost da optimizirajući prevoditelj promijeni njihov redoslijed pri-likom optimizacije koda te time promijeni smisao programa (bolje je pisati manje, ali cjeloviteblokove asemblerskog koda). Zbog sličnog razloga, može se pojaviti i problem sa korǐstenjem la-bela i naredbi skoka. U asemblerskim primjerima koje ćemo u nastavku pokazati poštovati ćemoto pravilo. U sljedećem podpoglavlju prikazujemo kako je programski moguće odrediti podržaneinstrukcijske skupove procesora korǐstenjem instrukcije cpuid.

    4.1 Odredivanje podržanih instrukcijskih skupova procesora instrukcijom cpuid

    Cpuid instrukcija (eng. CPU IDentification) služi za programsko odredivanje informacija o tipu ikarakteristikama procesora koji se nalazi u sustavu. Instrukcija cpuid nema parametre, već indirek-tno koristi registar eax koji se postavlja na odredenu vrijednost neposredno prije poziva instrukcijecpuid. Povratne vrijednosti instrukcije, ovisno o vrijednosti registra eax, nalazit će se u jednomod registara eax, ebx, ecx ili edx. Povratne vrijednosti interpretiraju se u ovisnosti o početnoj vri-jednosti registra eax. Na primjer, ako ispitujemo karakteristike procesora, povratne vrijednosti ćese nalaziti u registrima edx i ecx. Svaki bit tih registara predstavlja jednu karakteristiku procesorakoja je prisutna ako je on postavljen na 1 ili nije ako je postavljen na 0. Popis značenja pojedinihbitova povratnih vrijednosti registara edx i ecx nalazi se na stranicama proizvodača procesora. ZaIntel procesore, priručnici su dostupni ovdje.

    Ako postavimo registar eax = 0x00000000, instrukcija cpuid ima sljedeće povratne vrijednosti:eax će biti postavljen na maksimalni broj koji se može postaviti u registar eax prije poziva ins-trukcije cpuid, dok će registri ebx, edx i ecx sadržavati po 4 znaka kodnog imena proizvodačaprocesora. Pogledajmo primjer (ovo je gcc inline programski odsječak sa asemblerskim kodom pi-san u AT&T sintaksi. Ako ne razumijete značenje svih detalja, nije presudno; pogledajte što radi.AT&T sintaksa obrnutim redoslijedom pǐse izvorǐsni i odredǐsni registar):

    unsigned char arr[13];

    int max_broj;

    __asm__ volatile (

    12 2. laboratorijska vježba AR2

    http://www.cpuid.com/softwares/cpu-z.htmlhttp://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html

  • Programiranje u strojnom jeziku arhitekture x86

    Tablica 4: Vrijednosti registra eax prije poziva instrukcije cpuid i informacijekoje za pojedine vrijednosti dobivamo.

    Vrijednost registra eax Informacija koju daje cpuid instrukcija0x00000000 identifikator proizvodača0x00000001 bitovi značajki procesora0x00000002 opisnici priručne memorije i TLB polja0x00000003 serijski broj procesora0x80000000 najveća podržana vrijednost eax za “visoke” funkcije

    (vrijednosti koje počinju sa 0x8)0x00000001 dodatni bitovi značajki procesora0x80000002, 0x80000003 brend ime procesora0x80000004

    0x00000005 značajke L1 priručne memorije0x00000006 značajke L2 priručne memorije0x00000007 informacije o taktu, naponu i temperaturi procesora0x00000008 informacije o fizičkom i virtualnom adresnom prostoru

    "xorl %%eax, %%eax \n\t" //postavi a registar na 0x0

    "cpuid \n\t" //pozovi cpuid instrukciju

    "movl %%eax, %0 \n\t" //pohrani rezultat iz registra a

    "leal %1, %%eax \n\t" //ucitaj adresu pocetka niza

    "movl %%ebx,0(%%eax) \n\t" //pohrani prva 4 bajta (znaka)

    "movl %%edx,4(%%eax) \n\t" //sljedeca 4 bajta

    "movl %%ecx,8(%%eax) \n\t" //sljedeca 4 bajta

    "movb $0,12(%%eax) \n\t" //zavrsna 0

    : "=r"(max_broj) //povratne varijable

    : "m"(*arr) //ulazne varijable

    : "eax","ebx","ecx","edx" //koristeni registri

    );

    printf("Kod proizvodaca = %s maksimalni eax = %d\n",arr,max_broj);

    Obrazložimo ukratko značenje ovog programskog odsječka. Ovo je takozvani inline asemblerski kodkoji je dodan u datoteku sa C/C++ programom. Prilikom prevodenja prevoditelj će ga umetnuti udatoteku zajedno sa strojnim kodom ostalih elemenata vǐseg progamskog jezika. Sintaksa dopuštakorǐstenje varijabli vǐseg programskog jezika, unutar asemblerskog koda čime je omogućena jednos-tavna komunikacija sa kodom vǐseg programskog jezika. Prisjetimo se, sve varijable ili objekti kojekoristimo unutar funkcija C programa nalaze se ili na stogu ili na gomili memorijskog prostora pri-druženog procesu koji izvodi program 4. Prevoditelj prilikom prevodenja inline asemblerskog kodazamjenjuje njihovo ime adresom (ako se radi o memorijskim operandima; njih obično dohvaćamopreko pokazivača) ili vrijednošću, te ih (ovisno o instrukcijama) učitava u neki od registara proce-sora. Prikazani kod, pohraniti će 12 znakova u polje arr koje smo proslijedili kao parametar, tejednu cjelobrojnu vrijednost u varijablu max broj. Funkcija printf daje ispis:

    Kod proizvodaca = GenuineIntel maksimalni eax = 13

    Ispis će se razlikovati ovisno o procesoru kojeg imamo u sustavu.

    Informaciju o brendu procesora dobivamo tako da u registar eax upǐsemo konstantu 0x80000000

    4Postoje i registarske varijable koje se mogu nalaziti samo u registru procesora. U C/C++ programu to označavamoključnom riječ register ispred tipa varijable. Prevoditelju to služi samo kao naputak, ne i pravilo. Prevoditelj, ucilju optimizacije performanse, može i samostalno neku varijablu učiniti registarskom

    AR2 2. laboratorijska vježba 13

  • Programiranje u strojnom jeziku arhitekture x86

    te pozovemo instrukciju cpuid. Ako informacija o brendu postoji, potrebno je 3 puta pozivatiinstrukciju cpuid sa vrijednostima registra eax redom: 0x80000002, 0x80000003, 0x80000004 pričemu ćemo svaki puta kao povratnu vrijednost dobivati po 16 okteta u registrima eax, ebx, ecx iedx. Na poslijetku, ako dobivenih 48 okteta ispǐsemo kao niz znakova dobit ćemo sljedeći ispis:

    Intel(R) Core(TM) i7-2720QM CPU @ 2.20GHz

    Ispitajmo sada da li procesor koji koristimo podržava instrukcijske skupove MMX, SSE i AVX,budući da ćemo u sljedećim potpoglavljima prikazati kratke programske odsječke koji koriste ins-trukcije iz tih skupova. Prema tablici 4 u registar eax upisujemo vrijednost 1.

    int c,d;

    __asm__ volatile (

    "movl $1, %%eax \n\t" //postavi a registar na 0x1

    "cpuid \n\t" //pozovi cpuid instrukciju

    : "=c" (c), "=d"(d) //povratne varijable

    : //ulazne varijable

    :

    );

    cout

  • Programiranje u strojnom jeziku arhitekture x86

    SSE4.1 = true

    SSE4.2 = false

    AVX = false

    Vidimo da taj procesor ne podržava SIMD instrukcije iz skupa AVX. Ukoliko bismo ipak pokušaliizvesti program koji sadrži AVX instrukcije, program bi se prekinuo uz (očekivanu) poruku o grešci:Illegal instruction.

    4.2 Intrinsične funkcije

    Intrinsične funkcije (eng. intrinsic functions) su funkcije koje implementira prevoditelj izravnozamjenjujući funkcijski poziv u vǐsem programskom jeziku nizom strojnih instrukcija. Intrinsičnefunkcije se prevode kao inline funkcije pa prilikom njihovog izvodenja ne postoji funkcijski pozivkoji bi stvorio usporenje (eng. function call overhead). S obzirom da su te funkcije pisane specifičnoza zadani programski jezik, prevoditelj i arhitekturu, omogućuju bolju optimizaciju koda. Čestose nazivaju i ugradene funkcije (eng. builtin functions). Ovakve funkcije obično se koriste zaostvarivanje paralelizacije u jezicima u kojima paralelizacija ili vektorizacija nije izravno podržana.Na primjer OpenMP api (služi za ostvarivanje paralelizacije programa na vǐsejezgrenom procesoru)ostvaren je uporabom intrinsičnih funkcija.

    S obzirom da su intrinsične funkcije programeru prikazane kao funkcije vǐseg programskog jezika,programski kod će biti znatno čitljivji i imati će bolju organizaciju, te u nekim slučajevima i boljuperformansu nego inline asemlerski odsječci (zbog uključenih optimizacija). Nedostatak program-skog koda koji koristi intrinsične funkcije je smanjena prenosivost (eng. portability).

    Popis intrinsičnih funkcija za GCC prevoditelj i sve arhitekture za koje postoji GCC prevoditeljdostupan je ovdje (GCC prevoditelj ih zove ugradene funkcije): GCC.

    Za Microsoft C++ prevoditelj i arhitekture ARM, x86 i x64 popis je dostupan ovdje: MicrosoftC++.

    Za Intel C++ prevoditelj i Intel arhitekture popis se nalazi ovdje: Intel C++.

    Intrinsične funkcije uključuju mnoge aritmetičke funkcije (npr. abs, sqrt, ceil, max, log), trigono-metrijske funkcije (npr. cos, cosh, acos, atan2), funkcije za operacije nad znakovima ili nizovimaznakova (npr. iswalpha, iswdigit, iswlower, fprintf, strcat) ili funkcije za rad s memorijom (npr.malloc, memset, memcpy).

    Intrinsične funkcije definirane i za instrukcijske skupove MMX, SSE, SSE2, SSE3, SSE4, AVX,AVX2, te instrukcija za kriptiranje podataka (eng. Advanced Encryption Standard, AES). Pogle-dajmo primjer:

    __attribute__ ((aligned(32))) float a[]={1,2,3,4,5,6,7,8};

    __m256 ymm3 = _mm256_load_ps(a);

    Intrinsična funkcija mm256 load ps prevodi se kao instrukcija vmovaps (AVX instrukcija):

    movaps (%eax),ymm0

    Ta funkcija (instrukcija) učitava 8 decimalnih brojeva jednostruke preciznosti u vektorski registar.Adresa početka polja proslijeduje se kao argument funkcije (radi se o polju a, njegova adresa se

    AR2 2. laboratorijska vježba 15

    http://gcc.gnu.org/onlinedocs/gcc/Target-Builtins.html#Target-Builtinshttp://msdn.microsoft.com/en-us/library/26td21dshttp://msdn.microsoft.com/en-us/library/26td21dshttp://software.intel.com/sites/products/documentation/hpc/composerxe/en-us/2011Update/cpp/lin/intref_cls/common/intref_bk_intrinsics.htm

  • Programiranje u strojnom jeziku arhitekture x86

    u našem primjeru nalazi u registru eax). Takoder primijetimo da smo prije polja a stavili atributaligned(32). Taj atribut govori prevoditelju da polje a na stogu poravna sa 32 bajta (tj. početnaadresa polja a biti će djeljiva sa 32). To je nužno, jer instrukcija movaps zahtijeva da podaci buduporavnati sa 32 bajta. Napomenimo i to, da se atribut aligned može koristiti samo za statičkapolja (kao u našem slučaju), dok se za dinamička polja koriste naprednije funkcije za zauzimanjememorije i poravnavanje adresa (npr. mm malloc). Možemo primjetiti i to da smo u C programuvarijablu označili sa ymm3, dok će ona stvarno koristiti registar ymm0. To je zato što je u C programuniz znakova ymm3 ime varijable kojeg možemo zadati u skladu sa pravilima C jezika (npr. moglismo napisati i abc). U asembleru ymm0 je ime registra kojeg moramo označiti njegovim imenom.Prevoditelj sam odreduje koje varijable će pridružiti pojedinim registrima.

    4.3 Instrukcijski skup MMX

    Instrukcijski skup MMX (eng. Multi Media eXtensions) je proširenje standardnog Intelovog ins-trukcijskog skupa instrukcijama tipa SIMD uvedeno 1996. godine. MMX instrukcijski skup uvodi57 novih operacijskih kodova (instrukcija), 64-bitni tip podataka označen sa quadword (dvostrukadvostruka riječ), te 8 novih 64-bitnih registara ostvarenih u obliku elemenata stoga x87 FPU jedi-nice (jedinice za aritmetiku sa pomičnim zarezom) nazvanih MMX0 - MMX7. To znači da će svakapromjena elemenata stoga prilikom skalarnih operacija koje koriste brojeve s pomičnim zarezomizazvati i promjenu jednog ili dva MMX registra. Vrijedi i obratno. Svaka promjena nekog odMMX registara mijenja i stanje stoga x87 FPU jedinice. Registre MMX adresiramo direktno nji-hovim imenom (npr. MMX0, MMX3). Elemente stoga x87 FPU jedinice možemo adresirati unutarx87 instrukcije tako da navedemo njihov indeks (npr. st je 1. element stoga, st(3) je 4. elementstoga). Korǐstenje stoga u x87 instrukcijama mora biti u skladu sa dozvoljenim operacijama za radsa stogom (npr. uključena zastavica pop x87 instrukcije ukoliko je stog prazan rezultirala bi pre-kidom izvodenja programa; MMX registar ne može biti prazan). Navedimo i to da su svi elementistoga x87 FPU jedinice 80-bitni, pa su MMX registri zapravo sadržani u njima (slika 4).

    st

    79 63 31 0

    A3 A1 A0

    MMX0

    st(1)

    79 63 31 0

    A3 A1 A0

    st(7)

    79 63 31 0

    A3 A1 A0

    Slika 4: Registri instrukcijskog skupa MMX: sadrži ukupno 8 64-bitnih registara, koji su ujedno ielementi stoga x87 FPU jedinice. Svaki element stoga (oznaka st(x) ima širinu 80-bita.

    MMX registar može sadržavati:

    • 1 64-bitni cijeli broja (long ili long long),

    • 2 32-bitna cijela broja (int),

    • 4 16-bitna cijela broja (short int), te

    • 8 znakova (char).

    16 2. laboratorijska vježba AR2

  • Programiranje u strojnom jeziku arhitekture x86

    Možemo uočiti da MMX instrukcijski skup ne podržava brojeve s pomičnim zarezom kao elementesvojih registara (iako bi to mogli očekivati s obzirom da koristi registre FPU jedinice).

    Primjeri nekoliko čestih instrukcija iz instrukcijskog skupa MMX prikazano je u tablici 5.

    Kratak primjer programa koji koristi MMX instrukcije (skalarni produkt vektora a i b):

    short DotProduct2(short *a, short *b, int size)

    {

    short p, rez;

    //__m64 je ime za tip podataka koji odgovara registru mmx (64-bita)

    __m64 num3, sum;

    __m64 *ptr1, *ptr2;

    sum = _mm_setzero_si64(); //inicijaliziraj sumu na 0

    for(int i=0; i

  • Programiranje u strojnom jeziku arhitekture x86

    postiže navodenjem intrinsične funckije mm empty() (koja se prevodi u instrukciju emms) nakonsvih MMX instrukcija.

    Možemo zaključiti da su glavni nedostaci MMX instrukcijskog skupa nemogućnost istovremenogkorǐstenja FPU jedinice i MMX instrukcija, te nemogućnost rada s podacima s pomičnim zarezomkao elementima XMM registarskog skupa. To je i za očekivati s obzirom da je MMX instrukcijskiskup bio prvi skup koji je uveo SIMD instrukcije u Intel arhitekturu procesora. Danas svi modernijiprevoditelji prilikom optimizacije koriste instrukcije iz novijih SIMD instrukcijskih skupova, a tosu skupovi SSE i AVX. MMX instrukcije koriste samo starije arhitekture procesora, a u novijimasu dostupne samo zbog kompatibilnosti sa starijim programima.

    4.4 Instrukcijski skup SSE

    SSE (eng. Streaming SIMD Extensions) je sljedeće instrukcijsko proširenje x86 instrukcijskog skupa.Uvedeno je 1999. godine i do sada je imalo nekoliko dodatnih proširenja SSE2, SSE3 i SSE4. SSEkoristi 128-bitne registre bitne xmm0 - xmm7. Zadnja dva slova mnemonika instrukcije znače dali je instrukcija paralelna ili nije (Packed or Single), te preciznost instrukcije (eng. Precision).Preciznost označava veličinu podataka s kojima instukcija barata, a ona može biti: Single, Double,Word, Quadword or Binary.

    XMM registar može sadržavati:

    • 4 broja s pomičnim zarezom jednostruke preciznosti (32bit, float),

    • 2 broja s pomičnim zarezom dvostruke preciznosti (64bit, double),

    • 2 64-bitna cijela broja (long ili long long),

    • 4 32-bitna cijela broja (int),

    • 8 16-bitnih cijelih brojeva (short int), te

    • 16 znakova (char).

    XMM0

    127 95 63 31 0

    A3 A2 A1 A0

    XMM1

    127 95 63 31 0

    A3 A2 A1 A0

    XMM7

    127 95 63 31 0

    A3 A2 A1 A0

    Slika 5: Registri instrukcijskog skupa SSE: SSE instrukcijski skup ima 8 128-bitnih registara

    Nekoliko najčešćih instrukcija iz skupa SSE prikazano je u tablici 6.

    Primjer programa koji računa saxpy operaciju (eng. Single-precision real Alpha X Plus Y; y ←αx+ y) koristeći SSE intrinsične funkcije:

    18 2. laboratorijska vježba AR2

  • Programiranje u strojnom jeziku arhitekture x86

    Tablica 6: Primjeri instrukcija instrukcijskog skupa SSE

    Intrinsična funkcija Asemblerskainstrukcija

    značenje

    m128 mm cmpeq ps( m128 m1, m128 m2) cmpeqps DEST[31..0] = (m1[31..0] == m2[31..0]) ?

    0xffffffff : 0x00000000;

    ...

    DEST[127..96] = (m1[127..96] ==

    m2[127..96]) ? 0xffffffff : 0x00000000;

    m128 mm max ps( m128 m1, m128 m2) maxps DEST[31..0] = max(m1[31..0], m2[31..0]);

    ...

    DEST[127..96] = max(m1[127..96],

    m2[127..96]);

    int mm cvtss si32( m128 m1) cvtss2si DEST[31..0] = (int)m1[31..0];

    void SaxpySSE128(float *x, float *y, float alpha, int size)

    {

    __m128 xmm0 = _mm_set1_ps(alpha); //ucitaj skalar alfa

    for (int i = 0; i < size; i+=4)

    {

    __m128 xmm1 = _mm_load_ps(x+i); //ucitaj 4 elementa iz x

    __m128 xmm2 = _mm_load_ps(y+i); //ucitaj 4 elementa iz y

    xmm1 = _mm_mul_ps(xmm1, xmm0); //pomnozi x sa alfa

    xmm1 = _mm_add_ps(xmm1, xmm2); //zbroji alfa*x sa y

    _mm_store_ps(y+i, xmm1); //pohrani rezultat u y

    }

    }

    Ovo je primjer u kojem koristimo intrinsične funkcije. Kako bi te funkcije zapisali u asemblerskomkodu? (uzmite u obzir pri deklaraciji polja da ona moraju biti poravnata sa 32B, inače funkcijaneće raditi).

    4.5 Instrukcijski skup AVX

    Intel AVX (eng. Advanced Vector Extensions) instrukcijski skup sadrži instrukcije tipa SIMDi predstavlja proširenje instrukcijskih skupova MMX i SSE. Bitne značajke AVX instrukcijskogskupa su:

    • uz nove instrukcije dodano je 16 novih 256 bitnih registra YMM0-YMM15 (ostavljena jemogućnost budućeg proširenja 512 i 1024 bitnim registrima).

    • AVX instrukcije imaju mogućnost troadresnog adresiranja (SSE je omogućavao samo dvo-adresno) tako da su sada moguće instrukcije koje izvode operacije poput A = B + C (operacijene mijenjaju izvorne registre B i C).

    • neke instrukcije imaju mogućnost četveroadresnog adresiranja (omogućuju složenije operacijekoje smanjuju broj potrebnih instrukcija programa)

    • unutar istog programskog koda mogu se nalaziti i starije instrukcije iz skupova SSE i MMX

    • dodane su instrukcije koje mogu koristiti memorijske operande čije adrese nisu poravnate sa32 bajta

    AR2 2. laboratorijska vježba 19

  • Programiranje u strojnom jeziku arhitekture x86

    AVX instrukcije omogućuju operacije nad podacima sa pomičnim zarezom jednostruke (4B) i dvos-truke (8B) preciznosti. Cjelobrojne operacije nisu trenutno podržane, no biti će uvrštene u sljedećeinstrukcijsko proširenje AVX2 (prvi procesor koji će ga podržavati je procesor sljedeće Intel arhi-tekture čije je trenutno kodno ime Haswell i planirano pojavljivanje 2014. godine).

    AVX registri YMM0 - YMM15 ostvareni su kao proširenja registara XMM (slika 6). To znači da će sepostavljanjem registra XMM na zadanu vrijednost postaviti i donja polovica pripadnog registra YMM(vrijedi i obratno postavljanjem registra YMM postaviti će se i pripadni registar XMM).

    YMM0

    255 223 191 159 127 95 63 31 0

    A7 A6 A5 A4 A3 A2 A1 A0

    XMM0

    YMM1

    255 223 191 159 127 95 63 31 0

    A7 A6 A5 A4 A3 A2 A1 A0

    YMM15

    255 223 191 159 127 95 63 31 0

    A7 A6 A5 A4 A3 A2 A1 A0

    Slika 6: Registri instrukcijskog skupa AVX: AVX instrukcijski skup koristi 16 256-bitnih registara kojisu ostvareni kao proširenja registara XMM. Time je i broj XMM registara povećan na 16.

    Nekoliko tipičnih instrukcija iz skupa AVX zajedno sa pripadnim intrinsičnim funkcijama prikazanoje u tablici 7.

    Tablica 7: Primjeri instrukcija instrukcijskog skupa AVX

    Intrinsična funkcija Asemblerskainstrukcija

    značenje

    m256 mm256 add ps( m256 m1, m256 m2) vadddps Elementi polja su tipa float.DEST[31..0] = m1[31..0] + m2[31..0];

    DEST[63..32] = m1[63..32] + m2[63..32];

    ...

    DEST[255..224] = m1[255..224] +

    m2[255..224];

    m256 mm256 sqrt ps( m256 m1) vsqrtps Elementi polja su tipa float.DEST[31..0] = sqrt(m1[31..0]);

    DEST[63..32] = sqrt(m1[63..32]);

    ...

    DEST[255..224] = sqrt(m1[255..224]);

    m256d mm256 load pd(const double *a) vmovapd Elementi polja su tipa double.DEST[63..0] = a[0];

    ...

    DEST[255..192] = a[3];

    void mm256 store ps(float *a, m256 m1) vmovaps Elementi polja su tipa float.a[0] = m1[31..0]; a[1] = m1[63..32];

    ...

    a[7] = m1[255..224];

    20 2. laboratorijska vježba AR2

  • Programiranje u strojnom jeziku arhitekture x86

    5 Prevodenje programa i mogući problemi (GCC prevodioc)

    Vježbu se može napraviti korǐstenjem proizvoljne (32-bitne ili 64-bitne) distribucije Linuxa koja ima gcc, Unixa (npr.

    fakultetskom računalu Pinus), Mac OSXa ili Windowsa (sa instaliranim gcc prevodiocem [MinGW]). Takoder, ako

    radite pod Windowsima možete koristiti i virtualne linux strojeve (npr. [VmWare] i [Ubuntu]). Modifikacija

    asemblerskog koda koju je potrebno napraviti prilikom korǐstenja MSVC prevodioca dana je na početku uputa.

    U ovom dodataku uputama pokazujemo nekoliko mogućih problema koji mogu nastati korǐstenjemgcc prevodioca.

    U prilogu se nalaze datoteke main.cpp i p asm.s. U njima se nalaze glavni program i potprogramikako su opisani u uputi. Ove datoteke mogu se koristiti u a) zadatku, a mogu poslužiti i kao početakrješavanja ostalih zadataka. Program se prevodi i povezuje naredbom:

    $ g++ -g -o main main p_asm.s main.cpp

    Program pokrećemo:

    $ ./main

    Za navedene parametre funkcija, dobivamo sljedeći ispis:

    ASM: 48

    C++: 48

    Ovaj primjer se ispravno prevodi na 32-bitnom operacijskom sustavu.

    Ukoliko se prilikom prevodenja programa pojavi sljedeća greška:

    p_asm.s: Assembler messages:

    p_asm.s:10: Error: operand type mismatch for ‘push’

    p_asm.s:28: Error: operand type mismatch for ‘pop’

    Razlog je taj što se najvjerojatnije koristi 64-bitni operacijski sustav, a samim time i 64-bitni asem-bler, koji koristi drugačiju konvenciju (nije cdecl), te neispravno interpretira parametre funkcije.Takoder, 64-bitni asembler ima drugačiji instrukcijski set, 64-bitne registre i podaci na stogu suporavnati sa 16B.

    Postoje 2 rješenja: Možemo prilikom prevodenja specificirati prevoditelju da želimo prevoditi 32-bitni kod:

    To možemo učiniti dodavanjem opcije -m32 (po defaultu je 64 bita -m64)

    $ g++ -g -m32 -o main p_asm.s main.cpp

    No tada možda imamo sljedeći problem:

    /usr/bin/ld: skipping incompatible /usr/lib/gcc/x86_64-linux-gnu/

    4.4.5/libstdc++.a when searching for -lstdc++

    /usr/bin/ld: cannot find -lstdc++

    collect2: ld returned 1 exit status

    Standardne library datoteke koje trenutno imamo su 64-bitne, no trebaju nam 32-bitne. Možemoih instalirati ovako:

    AR2 2. laboratorijska vježba 21

    http://www.mingw.org/http://www.vmware.com/products/player/http://www.ubuntu.com/download/ubuntu/alternative-download

  • Programiranje u strojnom jeziku arhitekture x86

    $ sudo apt-get install g++-multilib

    Te zatim ponoviti:

    $ g++ -g -m32 -o main p_asm.s main.cpp

    i program će se uspješno prevesti.

    Ili možemo preurediti asemblerski kod funkcije koristeći arhitekturu x86 64 (promjene su značajnejer koristimo 64-bitnu konvenciju (Microsoft x64 ili AMD64), te koristimo 64-bitne registre i ins-trukcije). Za naš primjer a) funkcija u asembleru izgledati će ovako:

    /* oznaka sintakse: */

    .intel_syntax noprefix

    /* neka simbol potprogram\_asm bude vidljiv izvana: */

    .global potprogram_asm

    /*labela potprograma:*/

    potprogram_asm: /*AMD64 ABI konvencija*/

    /* u ovom slucaju prolog nam nije potreban

    jer ne koristimo lokalne varijable */

    mov rax, rdi /*rdi = a*/

    add rax, rsi /*rsi = b*/

    imul rax, rdx /*rdx = c*/

    /*rezultat je u rax*/

    ret /* povratak iz potprograma */

    Naglasak ove vježbe je na x86 arhitekturi, stoga nije nužno proučavati x64 konvencije. Ipak, akonekoga zanima može pogledati sljedeće radove/stranice:

    [1] Introduction to x64 Assembly

    [2] x86 calling conventions

    [3] The history of calling conventions, part 5: amd64

    [4] The reasons why 64-bit programs require more stack memory

    Postoje 2 konvencije (Microsoft x64 calling convention (za Windowse) i System V AMD64 ABIconvention (za Linux, BSD, Mac)). Konvencije su medusobno vrlo slične. Treba obratiti pozor-nost na način prenošenja parametara u svakoj on njih: prva prenosi 4 parametra preko registararcx/xmm0, rdx/xmm1, r8/xmm2, r9/xmm3, dok ih druga prenosi do 6 preko rdi, rsi, rdx, rcx, r8,r9, xmm0-7. Ostali parametri prenose se preko stoga.

    Napomena: Ako pokušamo program prevesti na Mac OSX operacijskom sustavu (koji je takoder64-bitan) dobiti ćemo sljedeće:

    $ g++ -m32 -g -o main p_asm.s main.cpp

    p_asm.s:5:Unknown pseudo-op: .global

    p_asm.s:5:Rest of line ignored. 1st junk character valued 112 (p).

    Program se neće uspješno prevesti jer prevoditelj ne prepoznaje operaciju .global, te kao i u Win-dowsima labela potprogram dobiva podvlaku kao prefix. Potrebno je napraviti sljedeće izmjene:

    .global potprogram_asm => .globl _potprogram_asm

    potprogram_asm: => _potprogram_asm

    22 2. laboratorijska vježba AR2

    http://software.intel.com/en-us/articles/introduction-to-x64-assembly/http://en.wikipedia.org/wiki/X86_calling_conventionshttp://blogs.msdn.com/b/oldnewthing/archive/2004/01/14/58579.aspxhttp://www.codeproject.com/Articles/86054/The-reasons-why-64-bit-programs-require-more-stack

    Stog, parametri, varijable, funkcije, primjeri2 primjerax87 instrukcijski podskupVektorske instrukcije tipa SIMDOdreivanje podržanih instrukcijskih skupova procesora instrukcijom cpuidIntrinsicne funkcijeInstrukcijski skup MMXInstrukcijski skup SSEInstrukcijski skup AVX

    Prevoenje programa i moguci problemi (GCC prevodioc)