Programmeerimiskeel Assembler

Assambler keel on teise põlvkonna programmeerimiskeel. Assembler on programmeerimise süsteem, mis koosneb programmeerimise keelest ja translaatorist. Assembler on peaaegu üksüheses vastavuses masinkoodiga – assembler programmi reale vastab tavaliselt üks pesa masinkoodis kirja pandud programmis. Assembler aitab mõista masina olemust, seda keelt on vaja eeskätt translaatori kirjutajale. Kui masinkoodis programmeerimisel toimus kogu programmeerimine kahendkoodis ja selle lugemine ning silumine oli selle tõttu väga keeruline, siis assembler-keeles programmeerimisel on käsud inimesele sobivamal kujul. Kogu programmeerimine on üsna sarnane masinkoodis programmeerimisega, sest käsud on samad, mis masinkoodis (ainult teisel kujul, sõnadena)

 Assemblerikeele ehitus 
Assemblerkeel asendab instruktsioonide kahendkoodis oleva kuju (nt 11110111xx100xxx, kus x-id tähistavad tegurite asukohta arvuti mälus) mnemoonilise sümboliga (eelneva lihtsam kuju MUL xx, xxx). Seega on assemblertranslaatori töö suhteliselt kerge – ta peab jaotama mälu andmetele ja instruktsioonidele ning teisendama iga sümboli ning muutuja masinkoodi. Lõpuks koondab ta saadud koodi valitud väljundformaati. Väljunditeks võivad olla kas käitus- või objektkoodifailid. 80x86 seerial töötavate operatsioonisüsteemide täitmisfailidest on tuntuim vast DOS-, OS/2- ja Windowsi keskkondades kasutatav EXE-laiendiga fail (EXEcutable – käivitatav). Nende üks esimestest ülesannetest käivitudes käivituskeskkonna ülesseadmine, mis võtab muidugi nii aega kui mäluruumi.

 Assemblerprogrammi lähtekood 
Assemblerprogrammi lähtekood on instruktsioonide jada, mida vastavalt antud juhenditele täidetakse, korratakse ja vahele jäetakse. Peale mõnetäheliste sümbolitega tähistatud käskude tunnevad translaatoreid ka direktiive – tihti erinevaid juhtnööre, mida translaator kasutab programmist mingi aimu saamiseks ja keerukamate lahenduste loomiseks.
Direktiivide ülesannete hulka kuuluvad näiteks programmitüübi määramine (16- või 32-bitised instruktsioonid), avalike funktsioonide ja muutujate deklareerimine ning koodi ja muutujate joondamine mälus.
Lähtekood peab objektkoodi- või täitmisfailiks teisendamiseks olema digitaalkujul ja tekstivormingus, s.t peab olema suvalise tekstiredaktoriga töödeldav ja suvalise translaatori poolt loetav.
Ühel koodireal võivad antud järjekorras olla pealdis, instruktsioon, argumendid ja kommentaar. Loomulikult ei pea ühel real kõiki nelja olema, aga loomulik on, et kui real on instruktsioon, siis on seal ka argumendid ja neid viimaseid ei saa omakorda olla ilma instruktsioonita. Direktiiv peab olema eraldi real ja direktiiviga samal real tohib olla ainult kommentaar.
Pealdis on nimi, mis mingile programmireale antakse, et oleks võimalik programmi sees ühest osast teise "hüppeid" teha. Instruktsioon ja operand(id) moodustavad käsu, mida protsessor täidab ning kommentaaridega on võimalik programmide mõistetavust võimaliku uuendamis- või ümberkirjutamisaja saabudes programmi käikudest aru saada. Kommenteerimist loetakse ka hea programmeerimisstiili üheks koostisosaks. 

 Assemblerprogrammi loomine 
Assemblerprogrammi loomine algab lähteteksti kirjutamisest. Lähteteksti võib kirjutada suvalise tekstiredaktoriga, mis suudab salvestada puhast ASCII-teksti. Enne lähtekoodi kirjutamisega alustamist võiks suurema projekti puhul teha paberile plokkskeem, mis määraks programmi kõik funktsioonid ja nende omavahelised seosed.
Assemblertranslaatorid on käsureal töötavad programmid. Programmi käivitades tuleb programmifaili nime järele kirjutada assembleeritava lähtekoodi tekstifaili nimi ja n-ö "võtmed" – juhendid transleerimiseks. nende juhendite hulka kuuluvad veaotsingu põhjalikkuse määramine, väljundformaadi valik ja muud kasulikud võimalused.
On olemas eraldi programme – IDE-sid, mis sisaldavad peale tekstiredaktori veel ka võimalust lihtsamalt valida eelpoolmainitud "võtmeid". Vastava täheühendi kirjutamise asemel saab soovitud tingimused valida graafilisest menüüst. Tegeliku translaatori saab välja kutsuda klaviatuuril klahvikombinatsiooni vajutades. Lähtekood transleeritakse ja kui tulemiks on programm, siis see käivitatakse.
Faili salvestamisel võiks jälgida väljakujunenud tava muuta faili laiend sisule vastavaks. DOS-keskkonnas võiks selleks olla ASM (ASseMbler). Linuxi või mõne mu keskkonna all võib translaator omada enda kindlat laiendit. GNU Assembler (GAS) tunnistab näiteks faililaiendina S-i. Mõnel pool levib NASM-i lähtekoodi NSM-laiendiga varustamine, et seda teiste DOS-translaatorite omast eristada.
Programmi kirjutamisega alustades tuleks kõigepealt määrata sihttranslaator ja uurida selle omadusi ja võimalikke direktiive, mida tuleks kasutada. Programmi alguses tuleks need paika panna, sest hiljem võib üldjoonte muutmine kaasa tuua segadust.
Iga programm vajab andmeid. Teistest keeltest erineb assembler selle poolest, et alamäluga (1 MB) piirdudes võib olla vajalik kõikide muutujate deklareerimine koos segmentidega, kus nad asuvad. See on samuti eri tootjatel erinev. Kaitstud reþiimis olles toimub kõik teise loogika järgi, siis on kogu mälu vaadeldav ühe suure tükina ja adresseerimine on täiesti lineaarne. Kasutatakse ka segmente, kuigi lineaarsete aadresside ja reaalreþiimi segmentide kombineerimine on üks tihe vigade allikas.
Muutujate defineerimiseks on kasutusel nn. pseudo-instruktsioonid ehk käsud, millel masinkoodis vastet pole, kuid lähtekoodis asuvad nad samas kohas kus instruktsioon (sealt ka nimi). Muutujadeklaratsiooni üldkuju on järgmine:
[muutuja nimi] andmetüüp [väärtus],[väärtus],...

 Omistamine ja võrdlemine 
CMP – võrdle (ing. k. compare – võrdlema)
Süntaks: CMP operand, operand
Kirjeldus: Võrdleb esimest operandi teisega ja analoogselt lahutamistehtele seab vastavalt tulemusele lipuregistri bitid. Instruktsiooni kasutatakse koos tingimushüppega (Jtt), mis vastavalt lippude seisule otsustab, kas hüpe teostada või mitte 

 Näide:
 cmp eax, 100 ;võrdleb EAX-registri väärtust arvuga sada

 MOV – omista, kopeeri (ing. k. MOVe –liigutama)

Süntaks: MOV sihtoperand, lähteoperand

Kirjeldus: Kopeerib lähteoperandi sisu sihtoperandi. Lähteoperand võib olla register, muutuja või konstant. Sihtoperand võib olla kas register või muutuja. Operandid peavad olema võrdse suurusega. Instruktsiooni ei saa kasutada CS-registriga.

 Näide:
 mov eax, 27 ;registri EAX väärtuseks saab 27
 mov arv, 0x0F00F ;muutuja arv väärtuseks saab 0x0F00F

 Aritmeetika 

ADD – liitmistehe (ing. k. add – liitma)
Süntaks: ADD sihtoperand, lähteoperand
Kirjeldus: Liidab mõlemad operandid ja talletab tulemuse sihtoperandi. Sihtoperand võib olla register või muutuja. Lähteoperand võib olla nii register ja muutuja kui ka konstantväärtus, mille programmeerija annab programmi kirjutades. ADD ei tee vahet märgiga ja märgita operandidel. Selle asemel seab ta lipuregistri bitid OF, CF ja SF. (Nende lippude seletus oli toodud peatükis 1.3.).

 Näited:
 add eax, ebx ;liidab EAX-i ja EBX-i ja kirjutab tulemuse EAX-i
 add eax, 3 ;liidab EAX-ile 3 ja kirjutab tulemuse EAX-i
DEC – vähenda ühe võrra (ing. k. DECrement – vähendama)
Süntaks: DEC operand

Kirjeldus: Vähendab operandi väärtust ühe võrra. Operand võib olla register või muutuja.

 Näide:
 dec eax ;vähendab registri EAX väärtust ühe võrra
 DIV – märgita jagamine (ing. k. DIVision – jagamine)
Süntaks: DIV operand

Kirjeldus: Jagab märgita väärtuse registris AX või registripaaris DX:AX / EDX:EAX (vastavalt registrite väärtustele) operandina antud jagajaga ja talletab tulemuse ühte registritest (või nende paaridest) AX, DX:AX või EDX:EAX. Jagaja võib olla register, muutuja või konstant.

 Näide:
 div bx ;jagab AX-i BX-ga ja kirjutab tulemuse AX-i
 div 7 ;jagab AX-i seitsmega ja kirjutab tulemuse AX-i
 INC – suurenda väärtust ühe võrra (ing. k. INCrement – suurendama)
Süntaks: INC operand

Kirjeldus: Suurendab operandi väärtust ühe võrra. Operand võib olla kas register või muutuja.

 Näide:
 inc ebx ;suurendab registri EBX väärtust ühe võrra
 inc 8 ;translaator annab veateate, sest konstanti argumendina kasutada ei tohtinud
 MUL – korrutamine (ing. k .MULtiplication – korrutamine)
Süntaks: MUL operand

Kirjeldus: Korrutab märgita väärtuse registris AX või EAX (vastavalt registrite väärtustele) operandina antud teise teguriga ja talletab tulemuse ühte registritest (või nende paaridest) AX, DX:AX või EDX:EAX. Operand võib olla register, muutuja või konstant.

 Näide:
 mul 5 ; korrutab AX-i väärtuse viiega
 mul ebx ; korrutab AX-i väärtuse EBX-i väärtusega.
 SUB – lahutamine (ing. k. SUBtract –lahutama)
Süntaks: SUB sihtoperand, lähteoperand

Kirjeldus: Lahutab lähteoperandi sihtoperandist ja talletab tulemuse sihtoperandi. Sihtoperandiks võib olla nii register kui muutuja. Lähteoperand võib olla register, muutuja ja konstant. Vastavalt tulemusele seatakse lipuregistri bitid OF, SF ja CF.
 Näide:
 sub eax, 99 ;lahutab registri EAX väärtusest 99
 sub muutuja, ecx ;lahutab muutuja väärtusest ECX-registri väärtuse

 Loogikatehted 

AND – konjunktsioon (ing. k. AND – ja)
Süntaks: AND sihtoperand, lähteoperand
Kirjeldus: Rakendab kahe operandi peal konjunktsioonitehet ja kirjutab tulemuse sihtoperandi. Lähteoperand võib olla register või muutuja. Sihtoperandi puhul lisandub konstandi variant. Tehe toimub bitthaaval – kui nii siht- kui lähteoperandi vastavad bitid on ühed, siis on ka tulemuses vastav bitt üks, teiste kombinatsioonide puhul on tulemuse vastav bitt null.

 Näited:
and al, 11000110b ;registri EAX madalaimat baiti võrreldakse kahendarvu 11000110b-ga ja tulemus kirjutatakse AL-i
OR – disjunktsioon (ing. k. OR – või)
Süntaks: OR sihtoperand, lähteoperand
Rakendab kahe operandi peal disjunktsioonitehet ja kirjutab tulemuse sihtoperandi. Lähteoperand võib olla register või muutuja. Sihtoperandi puhul lisandub konstandi variant. Tehe toimub bitthaaval – kui siht- või lähteoperandi vastavatest bittidest vähemalt üks on üks, siis on ka tulemuses vastav bitt üks, kui mõlemas operandis vastav bitt null, on tulemuse vastav bitt samuti null.

 Näited:
or ax, 0x0f ;
registri AX bitte võrreldakse kuueteistkümnendarvu bittidega ja vastavalt tulemustele kirjutatakse AX-registrisse tulemus. 

 Assemblerkeele programmeerimisstiil 
Assembleri lähtetekst on juba sündinult väheloetav, sest ülesanded jagatakse mitmeks alamtegevuseks, mida kõrgkeelte funktsioonid iseseisvalt läbivad. Selliste tegevuste hulka kuuluvad lähteandmete lugemine, tulemuse sihtmuutjasse või -registrisse kirjutamine ja arvude vormingu ühtlustamine.

BASIC-keeles kirjutatud programmirida

Minutid = Päevad * 1440

on lihtsalt mõistetav, minutite arvu saamiseks korrutatakse päevade arv 1440-ga. Assemblerkeeles näeks seda sama ülesannet täitev 
programm välja selline:
 mov eax, Päevad
 mov ebx, 1440
 mul ebx
 mov Minutid, eax

Mõistetavuse kadumisega kaasneb dokumentatsiooni ja selgituste vajalikkus. Kommenteerimata programm ei ole hea ka siis kui ta hästi ja kiiresti töötab. 
Hea stiili näitajaks on ka muutujate arusaadavad nimed. Kui keegi viljeleb programmeerimispraktikat, mis on täis avaldisi nagu
x = (y – k) / (z ^ 2 – r * 4)
siis varem või hiljem lõpeb see programmi ümberkirjutamisega nullist. Kui programm loendab näiteks õigete vastuste hulka, siis nende arvu hoidev muutuja peaks kandma nime õiged_vastused (mitte lihtsalt V) ja jagamisel tekkiv jääk peaks kandma nime jääk (mitte J). On väidetud, et iga ebakorrektselt nimetatud muutuja raiskab 10 minutit programmeerija aega. Suurte projektide puhul kaasneb valesti toimides aja kadu koodi pideva ülelugemise tõttu.
Tunnustatud praktika kasutab muutujanimedena allkriipsuga ühendatud sõnu näiteks vanim_õpilane, leitud_vastus või x_koordinaat. Peale allkriipsu ja tähemärkide on lubatud kasutada ka numbreid ja muid märke, viimaste hulk on eri translaatoritel erinev.

 Optimeerimine 
Assemblerkeelt kasutades on võimalik tulemusena saadavat koodi baithaaval paika sättida. Mõni instruktsioonikuju võib võtta vähem ruumi kui teine ja teha ära sama töö. Näiteks
 mov esp, 4
 mov esp, byte 4

teevad sisuliselt ära sama töö, aga masinkoodis on esimese pikkus 4 baiti ja teisel üks. See pole küll väga suur võit, ent palju selliseid kiirenemisi eriti tsükli sees võivad anda märgatava jõudluse kasvu. Samuti on olemas instruktsioone, mis loomult täidetakse kiiremini kui teised. Näiteks kasutavad paljud programmeerijad registri nullimiseks
 mov eax, 0
asemel instruktsiooni
 xor eax, eax
mis esiteks on kiirem ja teiseks lisab programmi lähtetekstile veelgi seda müstilisust, mis nagunii järjest kasvama kipub ja programmeerijate vastu austust tõstab.

Üldine praktika soovitab igal võimalusel eelistada EAX-registrit teistele, sest seda registrit kasutades on paljud instruktsioonid baidi jagu lühemad. Andmete lugemiseks soovitatakse kasutada DS-registrit ja pinumälu jaoks ESP-d. Muidugi on mugavam kopeerida ESP väärtus EBP-sse ja sellega opereerida, kuid vahel võib EBP kasulik olla muudel eesmärkidel.
Instruktsiooni LEA saab väga edukalt kasutada kolme/neljaliikmeliste tehete arvutamiseks ja seda kiiremini kui seda teeks "tavaline" lahendus. Näiteks
 lea eax, [ebx+C*D+E]
on oluliselt kiirem kui
 mov ecx, C
 mov eax, ebx
 mul ecx, D
 add eax, E
 add eax, ebx
LEA võib see-eest väga kahjulik olla instruktsioonide konveiertöötlusele, mida Inteli protsessorid alates 80486-st toetavad. Konveiertöötluse põhimõtted on üldjoontes järgmised:

Instruktsiooni täitmist võib väga lihtsustatult jagada viide etappi:

1) Instruktsiooni ja konstantandmete lugemine
2) Instruktsiooni dekodeerimine ja konstantide paigutamine siseregistritesse
3) Teiste operandide aadresside arvutamine ja nende mällu laadimine
4) Instruktsiooni täitmine ja tulemuste siseregistritesse kirjutamine
5) Tulemuse mäluoperandi kirjutamine

Iga etapp vajab täitmiseks vähemalt ühte protsessoritakti, välja arvatud esimene ja viies, sest mäluoperatsioonid võtavad reeglina rohkem aega. Iga etapi täitmiseks on protsessoris eraldi üksus, samuti on kogu tööd koordineeriv kontrollkeskus. Kuna kõik üksused on iseseisvad, omades oma mäluala, siis saab instruktsiooni täita konveiermeetodil (nagu tehases). Põhimõte on lihtne: üksus saab eelmiselt osaliselt töödeldud instruktsiooni, annab oma panuse ja siis saadab pisut "rohkem" täidetud instruktsiooni järgmisele üksusele, eelmiselt uut oodates.

Sellise lahenduse kasutegur ilmneb kõige rohkem järgmises lihtsustatud näites: olgu meil programm, millel on viis käsku, A, B, C, D, ja E. Kui need järjest konveierile paigutada tekib situatsioon, et instruktsioon A vajab täitmiseks viis takti, samas kui iga järgmine, olles ühe takti maas, vajab vaid ühte! Niimoodi kuluks nende instruktsioonide täitmiseks 12 takti. Konveierita protsessor oleks kulutanud 5 * 8 = 40. Programmeerija osavus seisneb siin instruktsioonide järjestamises nii, et ükski kombinatsioon ei tekita konveieri tühjenemist.

Muidugi ei saavutata sellist kasutegurit sugugi nii kergelt. On olemas hulk sündmusi, mis kutsuvad esile konveieri tühjenemise. Ükski instruktsioon ei tohi operandina kasutada registrit või mäluaadressi, mis on kasutusel mõnel eelneval konveieri astmel. Selline juhus tühjendab konveieri täielikult ja järgmine instruktsioon saab esimesele etapile minna alles siis kui eelmine on oma andmed töödelnud ja tulemused registritesse / mällu tagasi kirjutanud.

Vanemates protsessorites kutsusid kõik hüpped esile konveieri tühjenemise, uuemates on erilised, mis üritavad oletada, kuhu hüpe võiks minna ja säästavad aega. Siiski öeldakse, et mida vähem ja lähemale hüpata, seda parem. Inteli seeriast on protsessorid alates Pentiumist superskalaarsed (mitme konveieriga) ja hüppeennustusega.

Eelmainitud LEA-instruktsiooni halvim juht võib esile kutsuda koguni kaks kuni kolm konveieri tühjenemist, kuid sellega ikkagi saavutada märkimisväärset edu.

 Kirjandus 
 Delorie Software, http://www.delorie.com/
 e-Teatmik: IT ja sidetehnika seletav sõnaraamat. http://www.vallaste.ee
 Indrek Zolk, Riistvara ja tehniline dokumentatsioon, 2008
 Intel Secrets Web Site, http://www.x86.org/ Robert R. Collins - PC Processor Guide by x86.org
 http://math.ut.ee/ktv/1995/pro.keeled/index.html
 http://materjalid.tmk.edu.ee/heikki_eljas/EUCIP/arendus/331_programmeerimiskeelte_tbid__funktsionaalsed_protseduursed_objektorienteeritud_keeled.html
 Zack Smith. The 8086 / 80286 / 80386 / 80486 Instruction Set.