Sekalaiset Sivukartta

Pieni kurkistus VP-assemblyyn
Markus Ketola

Sakussa ja Saku-foorumilla on oltu varsin hiljaa Amiga SDK-ohjelmoinnista, joten ajattelin, että on aika hieman raottaa verhoa VP-assemblyn takana, ja tehdä pieni katsaus VP-assemblyn kiehtovaan maailmaan. Katsaus on todellakin vain pieni; vähäistä virtuaaliprosessorin ja VP-assemblyn ominaisuuksien esittelyä painottuen esimerkkilistaukseen. Useiden pienten yksityiskohtien lisäksi tässä artikkelissa ei käsitellä VP-assemblyn olio-ohjelmointiominaisuuksia, mutta ne ovat kuitenkin vähintäänkin maininnan arvoisia ominaisuuksia eli ovatpa ainakin tulleet nyt alussa mainituksi, jolloin niiden olemassaolo sentään tulee tiettäväksi.

Mitä on VP-assembly?
VP liittyy AmigaDE-maailmaan ja tulee sanoista Virtual Processor, eli VP-assembly on virtuaaliprosessorin symbolista konekieltä. Virtuaaliprosessorille (VP) tehdyt ohjelmat ovat ajettavissa sellaisenaan missä tahansa ympäristössä, missä Translator-niminen ohjelma hoitaa VP-tavukoodin kääntämisen natiiviksi koodiksi. Käytännössä Translator ei näy käyttäjälle mitenkään eli sitä ei itse ajeta kuten esim. Java-tulkki ajetaan. Translator on siis osa käyttöjärjestelmää. Virtuaaliprosessorilla on viidentyyppisiä rekistereitä: 32- ja 64-bittisiä kokonaislukurekistereitä, yksin- ja kaksinkertaisen tarkkuuden liukulukurekistereitä (32- ja 64-bittisiä) ja 32-bittisiä pointterirekistereitä. Rekisterit on nimetty alkukirjaimilla I, L, F, D ja P sekä numero-osalla alkaen nollasta. Täten esim. I0 on ensimmäinen kokonaislukureisteri ja vastaavasti F0 ensimmäinen yksinkertaisen tarkkuuden liukulukurekisteri. Mielenkiintoista on se, että rekistereiden määrää ei ole rajoitettu VP-assemblyssä muutoin kuin pinomuistin määrällä ja pinomuistin määrän ohjelmoija voi itse asettaa. Ohjelmoija voi lisäksi antaa rekistereille selväkielisiä nimiä.

Osoitusmuodoista
Ensimmäisen esiteltävä VP-assemblyn käsky on CPY. Nimi tulee sanasta copy ja se vastaa Motorolan 68K-assemblyn MOVE-käskyä vaikkakin osoitusmuodot ovat hieman erilaisia. Kuitenkin lähdeoperandi tulee ensin ja sitten kohdeoperandi samoin kuin 68K-assemblyssä. Käskyllä siis siirretään tavaraa muistiin tai muistista tai rekistereihin tai rekistereistä. Tarkastellaanpa sitten itse osoitusmuotoja. 68k-assemblyssä MOVE.L 4,D7 laittaisi osoitteen 4 sisällön D7:ään, mutta VP-assemblyssä CPY 4,I7 laittaa vakion 4 rekisteriin I7. 68K-assemblyssähän MOVE.L #4,D7 laittaisi vakion 4 rekisteriin D7. VP-assemblyssä osoitteen 4 sisällön saa siirrettyä rekisteriin I7 kirjoittamalla CPY [4],I7 eli epäsuora osoitus tehdään hakasulkujen avulla. Muut osoitusmuodot saadaan rakennettua helposti. Esim. 68K-assemblyn MOVE.L 4(A4,D7),D4 saa VP-assemblyssä muodon CPY [4+P4+I7],I4.

Makrot
Makroilla on keskeinen sija VP-assemblyssä ja niitä on kolmenlaisia: Makrokäskyt, korkean tason makrot ja käännösaikaiset makrot (assembly-time macros). Makrokäskyt soveltuvat hyvin erilaisiin matemaattisiin toimenpiteisiin, ja niitä ovatkin mm. ADD, SUB, DIV ja MUL. Lisäksi niitä on erilaisiin loogisiin operaatioihin. Esimerkki käytöstä:
    ADD 5,I2
Korkean tason makroilla saadaan lohkorakenteisten ohjelmointikielten - kuten C-kielen - ominaisuuksia VP-assemblyyn. Näitä makroja ovat mm. parit REPEAT ... UNTIL, WHILE ... ENDWHILE, IF ... ENDIF ja FOR ... NEXT. Sekä toki löytyy vielä makroja näiden rakenteiden kontrolloimiseen: BREAK ja CONTINUE. Näistä molemmista on myös ehdolliset versiot. Lisäksi on korkean tason makroja muotoiltuun tekstintulostukseen; näitä makroja ovat PRINTF, TRACEF ja KTRACE. Näiden lisäksi on korkean tason makroja mm. tietorakenteiden muistinvaraamiseen. Käännösaikaiset makrot (toivottavasti käännöskseni on kelpo) alkavat pisteellä (.) ja näillä voi ohjata ohjelman käännösprosessia. Käännösaikaisia makroja ovat .IF, .ELSEIF, .ELSE, .ENDIF, .INCLUDE, .ALIGN ja ennen kaikkea .MACRO ja .ENDM millä parilla voi määritellä omia makroja.

Esimerkkiohjelma
Ennen esimerkkiohjelmaa muutama sana VP-assembly-ohjelmista. Niitä nimittäin kutsutaan englanninkielisellä sanalla "tools" (työkalut) ja ne koostuvat ns. entblockeista (tälle en yritäkään keksiä suomennosta). Ja sitten esimerkkiohjelmaan. Ohjelma arpoo 7 lukua väliltä 0...39 kunnes käyttäjää antaa nollan. Ohjelma ei kuitenkaan ole Lotto-ohjelma, koska ohjelma ei tarkista, onko arvottu numero jo esiintynyt.
    .include 'tao'

    tool 'demo/Saku39/VPEsimerkki',VP,TF_MAIN,8192,0

    ent -:-

    qcall lib/malloc,(4:p0)

    repeat
       cpy 7,i1
       for i1
          qcall lib/rand,(-:i2)
          cpy (i2 % 39)+1,i2
          printf"%d\n",i2
       next i1

       printf"Arvotaanko uudestaan? (0=Ei, Muut numerot kuin 0=Kylla)\n"
       scanf"%d",p0
    until [p0]=0

    qcall lib/free,(p0:-)
    qcall lib/exit,(0:-)
    ret

    toolend
    .end
Katsotaanpa sitten ohjelmaa tarkemmin.
    .include 'tao'

    tool 'demo/Saku39/VPEsimerkki',VP,TF_MAIN,8192,0

    ent -:-
Ensimmäinen rivi ei kaivanne liiemmin selitystä, mutta sanottakoon, että include-tiedostot löytyvät hakemistosta lang/asm/include ja niistä löytyvät muun muassa makromäärittelyt. Seuraava rivi onkin mielenkiintoisempi. Kuten tämän kappaleen alussa totesin, VP-assembly-ohjelmat ovat työkaluja, engl. tools, ja ohjelmien alusta löytyykin tool-määrittely, missä kerrotaan, mistä ohjelma löytyy ja sitten kerrotaan, millä kielellä ohjelma on toteutettu (tässä VP). Sitten TF_MAIN-määreellä on kerrottu, että tämä työkalu on ajettava ohjelma. Tämän jälkeen tulee pinomuistin määrä ja sitten tulee globaalien muuttujien muistin määrä. ENT-direktiivi aloittaa itse ohjelman. Koska meidän tapuksessamme kyseessä on pääohjelma, ei ENT-direktiivillä ole parametreina mitään rekistereitä, joten lähde- ja output-parametrien paikalla on miinusmerkki.
    qcall lib/malloc,(4:p0)
Tämä on mielenkiintoinen rivi. C-ohjelmoijat tunnistanevat malloc-kutsun, ja totta tosiaan, se varaa muistia samoin kuin C-ohjelmissa! VP-assemblyyn onkin sisällytetty kaikki ANSI-C-funktiot sekä osa POSIX-funktioista! QCALL on makro, jolla siis kutsutaan malloc-funktiota. Lähdeparametrina välitetään 4, joka on kokonaislukutyypin (integer) koko tavuina. Output-parametrina välitetään P0, jolloin varaamamme 4 tavua löytyvät malloc-kutsun jälkeen osoitteesta, johon P0 osoittaa.
    repeat
    ...
    until [p0]=0
REPEAT ... UNTIL parissa REPEAT siis aloittaa loppuehtoisen silmukkarakenteen. Silmukkaa toistetaan kunnes UNTIL-osan ehto toteutuu. Tässä tapauksessa kunnes P0:n osoittaman muistipaikan sisältö on nolla. Mennään sitten REPEAT ... UNTIL -rakenteen sisälle:
       cpy 7,i1
       for i1
          qcall lib/rand,(-:i2)
          cpy (i2 % 39)+1,i2
          printf"%d\n",i2
       next i1
CPY-käsyllä siirretään 7 rekisteriin I1. Tämän jälkeen aloitetaan FOR-silmukka. Silmukka toistetaan FOR-sanan jälkeen tulevan rekisterin sisältämän kokonaisluvun verran. NEXT-osa vähentää rekisterin arvoa yhdellä. Sitten koodissa tulee jälleen ANSI-C-kutsu. Output-parametrina annetaan rekisteri I2, johon RAND-funktion palauttama arvo laitetaan. Tämän jälkeen jaetaan saatu luku 39:llä, ja jaon jakojäännös plus 1 talletetaan rekisteriin I2, jolloin I2:ssa on luku väliltä 0...39. PRINTF on korkean tason makro ja toimii samoin kuin C-kielen PRINTF-funktio eli tässä tapauksessa tulostaa kokonaislukurekisterissä I2 olevan arvon sekä lopuksi tekee rivinvaihdon. %d ja \n ovat siis muotoilukoodeja samoin kuin C-kielen vastaavat. Edetään koodissa SCANF-lauseeseen:
       scanf"%d",p0
Kuten C-kielessä, VP-assemblyssäkin SCANF toimii tavallaan käänteisesti PRINTF:ään nähden eli tässä tapauksessa SCANF lukee ruudulta kokonaislukuarvon, joka kerrotaan koodilla %d, ja sijoittaa arvon rekisterin P0 osoittamaan osoitteeseen. Otetaan sitten jäljellä oleva koodi käsittelyyn:
    qcall lib/free,(p0:-)
    qcall lib/exit,(0:-)

    ret

    toolend
    .end
Free-kutsu tulee jälleen ANSI-C-maailmasta. Sillä vapautetaan varattu muisti; lähdeparametrina annetaan pointterirekisteri P0, jossa meillä on varatun muistin alkuosoite. Exit-kutsukin on peräisin ANSI-C-maailmasta eli sillä poistutaan ohjelmasta ja palautetaan SHELL:iin paluukoodi. Käskyä RET, joka tulee sanasta return (paluu), ei sitten itse asiassa koskaan saavutetakaan. Mutta se on täydellisyyden vuoksi mukana; aliohjelmatapauksissa (non-primary tools) sillä palattaisiin kutsuvaan ohjelmaan. TOOLEND-makrolla lopetaan alussa TOOL-makrolla aloitettu lohko. .END-makro ei ole pakollinen. Se kertoo kääntäjälle, että koodi loppuu.

Lopuksi
Toivottavasti tämä hyvin pintapuolisesti VP-assemblyä käsittelevä artikkeli antoi jonkinlaisen kuvan VP-assemblyn mahdollisuuksista. Kenties joku tekee enemmän VP-assemblyn mahdollisuuksia esittelevän artikkelin. :) Ohessa vielä esimerkkilistaus.

Sekalaiset Sivun alkuun Sivukartta