Programmeerimiskeel Assembler

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],...

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 äärmiselt 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.