asm.md 17 KB

Assembly

Ha ezt a nyelvet választod, akkor kezd a programodat az #!asm sorral.

Példa program

#!asm

    .data
/* globális változók */
számláló:   di 123
szám:       df 3.1415
cím:        di 0x0048C
sztring:    db "valami"
tömb:       di 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
fmt:        db "a számláló %d, balshift %d\n"

    .code
/* Induláskor lefuttatandó dolgok */
setup:
  /* lokális változó (igazából nem, csak helyet foglalunk a veremben) */
  sp -4
  ret

/* Minden képkockánál lefuttatandó dolgok, 60 FPS */
loop:
  /* MEG-4 stílusú kimenet */
  pshci KEY_LSHIFT
  scall getkey
  sp 4
  pushi
  ci számláló
  ldi
  pushi
  pshci fmt
  scall printf
  sp 12
  ret

Leírás

Ez igazából nem egy programozási nyelv. Amikor valamelyik beépített nyelvet lefordítod, akkor a fordító olyan bájtkódot generál, amit aztán a CPU végrehajt. Az Assembly ezeknek a bájtkódoknak az egy-az-egybeni, ember számára is olvasható, szöveges mnemonik átirata. Két szekciója van, adat és kód, mindkettő cimkék és utasítások tetszőleges sorozatából áll. Az utasítás egy mnemonik opcionális paraméterrel.

Játszhatsz és kísérletezhetsz ezzel, ha bátornak érzed magad.

Literálok

Pontosan ugyanazok, mint a MEG-4 C literáljai.

Változók

Assemblyben nincs olyan, hogy változó. Helyette a .data kulcsszó indítja az adat szekciót, amibe csak adatokat pakolsz a db (bájt), dw (word, szó), di (integer, egészszám), df (float, lebegőpontos szám) utasítások paramétereként. Ebbe az adatfolyamba az utasítások elé rakhatsz címkéket, amik az adott adat címét fogják jelenteni. A betöltéshez a kód szekcióban, előbb be kell tölteni az akkumulátor regiszterbe egy ilyen címkét a ci utasítással, majd pedig kiadni a ldb (load, bájt betöltése), ldw (szó betöltése), ldi (egészszám betöltése) vagy ldf (lebegőpontos szám betöltése) utasítás valamelyikét. Ha az ldb vagy ldw betöltő utasításoknak nem-nulla paramétert adunk, akkor előjelesen 32 bitre kiegészítik az értéket.

Vezérlésirányítás

Minden, ami a .code kulcsszó után kerül, kód lesz. Nincs vezérlésirányítás, minden utasítás sorjában, egymás után kerül végrehajtásra; kézzel kell átállítanod a PC (program counter, programszámláló) regisztert, hogy befolyásold ezt. Ehhez a jmp (jump, ugrás), jz (jump if zero, ugrás ha nulla), jnz (jump if not zero, ugrás ha nem nulla) vagy az sw (switch, esetválasztás) utasításokat használhatod.

Függvények

Nincs olyan, hogy függvénydeklaráció. Csak megadsz egy címkét, hogy megjelöld a kód adott pontját. Hívásnál lerakod a paramétereket a verembe fordított sorrendben a pushi és pushf utasításokkal, majd a call mnemoniknak paraméterül ezt a címkét adod. A függvényen belül a paraméterek címét az adr (address, cím) utasítással lehet lekérni, aminek a paramétere a függvényparaméter sorszáma szorozva néggyel. Például adr 0 az első függvényparaméter címét rakja az akkumulátor regiszterbe, az adr 4 a másodikét. Visszatérni egy függvényből a ret utasítással lehet. A visszatérési érték az akkumulátor regiszterben adódik át, amit beállíthatsz direktben a ci (constant integer, egészszám konstans) és cf (constant float, lebegőpontos konstans) utasításokkal, illetve indirektben a popi, popf, ldb, ldw, ldi és ldf utasításokkal. Hívás után mindig a hívó fél felelőssége a paraméterek eltávolítása a veremből az sp+paraméterek száma szorozva néggyel utasítással.

Elérhető függvények

Minden MEG-4 API függvény a rendelkezésedre áll; pontosan azokkal a nevekkel, ahogy ebben a leírásban szerepelnek.

Lerakod a paramétereket a verembe fordított sorrendben, majd az scall (system call, rendszerhívás) mnemoniknak az egyik MEG-4 API funkció nevét adod paraméterül. Hívás után mindig a hívó fél felelőssége a paraméterek eltávolítása a veremből.

Mnemonikok

Mielőtt belemennénk a részletekbe, muszáj pár szót ejteni a MEG-4 CPU specifikációjáról.

A MEG-4 CPU egy 32 bites, kicsi elöl (little endian) CPU. Minden érték a memóriában úgy van tárolva, hogy az alacsonyabb helyiérték van az alacsonyabb címen. Képes műveleteket végezni 8 bites, 16 bites és 32 bites egészszámokkal (integer, előjeles és előjel nélküli is), valamint 32 bites lebegőpontos számokkal.

A memóriamodellje sík, ami azt jelenti, hogy minden adat elérhető egy egyszerű offszettel. Nincs lapozás se virtuális címfordítás, se szegmentálás, kivéve, hogy az adat- és kódszegmens hivatkozás implicit (azaz nem kell előtagot kiírni, a szegmensre hivatkozás automatikus).

Biztonsági okokból a kódszegmens és az adatszegmens el van különítve, akárcsak a hívásverem és az adatverem. Veremtúlcsordulásos támadás és egyéb buffertúlcsordulásos kód injektálás egyszerűen nem lehetséges ezen a CPU-n, ami különösen biztonságossá és hülyebiztossá teszi (továbbá a kódszeparálás nélkül lehetetlen lenne harmadik fél bájtkódját integrálni, lásd [Lua]). E tekintetben inkább Harvard architektúrájú, viszont minden másban inkább Neumann architektúrájú.

A CPU a következő regiszterekkel rendelkezik:

  • AC: akkumulátor regiszter, egészszám értékkel
  • AF: akkumulátor regiszter, lebegőpontos értékkel
  • FLG: a processzor jelzőbitjei (setup lefutott, B/K-ra illetve időzítőre várakozik, futtatás megszakítva)
  • TMR: az időzítő regiszter aktuális értéke
  • DP: adatmutató (data pointer), a felhasznált globális változó memória tetejére mutat
  • BP: bázismutató (base pointer), ez jelzi, hol van a függvényveremkeret teteje
  • SP: veremmutató (stack pointer), ez mutatja a verem alját
  • CP: hívásveremmutató (callstack pointer), a hívásverem tetejére mutat
  • PC: programszámláló (program counter), ez az éppen aktuálisan végrehajtott utasításra mutat

Az adatszegmens bájt alapú, azaz a DP, BP és az SP regiszterek 8 bites egységekre mutatnak. Az adatszegmensre a db (8 bit), dw (16 bit), di (32 bit) és df (32 bit float, lebegőpontos) menmonikokkal lehet adatokat elhelyezni. Ezeknek egy vagy akár több vesszővel elválaszott paraméterük is lehet, továbbá a db esetén sztring literál és karakter literál is használható.

A kódszegmens felbontása 32 bit, azaz ez a legkissebb címezhető egység. Emiatt a PC ilyen 32 bites egységekre mutat és nem bájtokra. A kódszegmensre a következő mnemonikokkal lehet utasításokat elhelyezni:

Mnemonik Paraméter Leírás
debug Meghívja a beépített [debuggoló]t (MEG-4 PRO alatt nop)
ret Visszatérés call hívásból (return), kivesz a hívásveremből
scall MEG-4 API funckció Rendszerhívás (system call)
call cím/kódcimke Beteszi a pozíciót a hívásverembe, majd meghívja a függvényt
jmp cím/kódcimke Ugrás a megadott címre
jz cím/kódcimke Ugrás a megadott címre, ha az akkumulátor nulla
jnz cím/kódcimke Ugrás a megadott címre, ha az akkumulátor nem nulla
js cím/kódcimke A veremből kivett értéket előjeligazítja, és ugrik, ha negatív vagy nulla
jns cím/kódcimke A veremből kivett értéket előjeligazítja, és ugrik, ha pozitív
sw szám,cím,cím0,cím1... Esetválasztás (switch, lásd alább)
ci szám/adat cimke Egészszámot helyez az akkumulátorba
cf szám Lebegőpontos számot helyez az akkumulátorba
bnd szám Ellenőrzi, hogy az akkumulátor tartalma 0 és szám közötti-e
lea szám A DP + szám címet tölti az akkumulátorba
adr szám A BP + szám címet tölti az akkumulátorba
sp szám Hozzáadja a számot az SP regiszterhez
pshci szám/adat cimke Egészszám konstanst tol az adatverembe
pshcf szám Lebegőpontos konstanst tol az adatverembe
pushi Az egészszám akkumulátor értékét az adatverembe tolja
pushf Az lebegőpontos akkumulátor értékét az adatverembe tolja
popi Kiveszi az adatverem legfelső elemét az egészszám akkumulátorba
popf Kiveszi az adatverem legfelső elemét a lebegőpontos akkumulátorba
cnvi Az adatverem legfelső elemét egészszámmá konvertálja
cnvf Az adatverem legfelső elemét lebegőpontos számmá konvertálja
ldb 0/1 Az akkumulátor által mutatott adatcímről egy bájtot tölt be (előjelesre egészít, ha nem-nulla a paraméter)
ldw 0/1 Az akkumulátor által mutatott adatcímről egy szót tölt be (előjelesre egészít, ha nem-nulla a paraméter)
ldi Az akkumulátor által mutatott adatcímről egy egészszámot tölt be
ldf Az akkumulátor által mutatott adatcímről egy lebegőpontost tölt be
stb Kiveszi a címet a veremből, és egy bájtot rak oda az akkumulátorból
stw Kiveszi a címet a veremből, és egy szót rak oda az akkumulátorból
sti Kiveszi a címet a veremből, és egy egészszámot rak oda az akkumulátorból
stf Kiveszi a címet a veremből, és egy lebegőpontost rak oda az akkumulátorból
incb szám Kiveszi a címet a veremből, és számmal növeli a bájtot a címen
incw szám Kiveszi a címet a veremből, és számmal növeli a szót a címen
inci szám Kiveszi a címet a veremből, és számmal növeli az egészszámot a címen
decb szám Kiveszi a címet a veremből, és számmal csökkenti a bájtot a címen
decw szám Kiveszi a címet a veremből, és számmal csökkenti a szót a címen
deci szám Kiveszi a címet a veremből, és számmal csökkenti az egészszámot a címen
not Logikai NEM művelet végzése az akkumulátoron
neg Bitenkénti NEM művelet végzése az akkumulátoron (negálás)
or A veremből kivett értékkel bitenkénti VAGY végzése az akkumulátoron
xor A veremből kivett értékkel KIZÁRÓ VAGY végzése az akkumulátoron
and A veremből kivett értékkel bitenkénti ÉS végzése az akkumulátoron
shl A veremből kivett értéket eltolja akkumlátornyi bittel balra, az eredmény az akkumulátorba kerül
shr A veremből kivett értéket eltolja akkumlátornyi bittel jobbra, az eredmény az akkumulátorba kerül
eq Beállítja az akkumulátort ha veremből kivett érték egyezik az akkumulátorral
ne Beállítja az akkumulátort ha veremből kivett érték nem egyezik az akkumulátorral
lts Beállítja az akkumulátort ha veremből kivett érték előjelesen kissebb
gts Beállítja az akkumulátort ha veremből kivett érték előjelesen nagyobb
les Beállítja az akkumulátort ha veremből kivett érték előjelesen kissebb vagy egyenlő
ges Beállítja az akkumulátort ha veremből kivett érték előjelesen nagyobb vagy egyenlő
ltu Beállítja az akkumulátort ha veremből kivett érték előjel nélkül kissebb
gtu Beállítja az akkumulátort ha veremből kivett érték előjel nélkül nagyobb
leu Beállítja az akkumulátort ha veremből kivett érték előjel nélkül kissebb vagy egyenlő
geu Beállítja az akkumulátort ha veremből kivett érték előjel nélkül nagyobb vagy egyenlő
ltf Beállítja az akkumulátort ha veremből kivett érték lebegőpontosan kissebb
gtf Beállítja az akkumulátort ha veremből kivett érték lebegőpontosan nagyobb
lef Beállítja az akkumulátort ha veremből kivett érték lebegőpontosan kissebb vagy egyenlő
gef Beállítja az akkumulátort ha veremből kivett érték lebegőpontosan nagyobb vagy egyenlő
addi Kivesz a veremből és hozzáadja az akkumulátort, egészszámként
subi Kivesz a veremből és kivonja belőle az akkumulátort, egészszámként
muli Kivesz a veremből és megszorozza az akkumulátorral, egészszámként
divi Kivesz a veremből és elosztja az akkumulátorral, egészszámként
modi Kivesz a veremből, elosztja és a maradék az akkumulátorba kerül, egészszámként
powi Kivesz a veremből és az akkumulátoradik hatványra emeli, egészszámként
addf Kivesz a veremből és hozzáadja az akkumulátort, lebegőpontosként
subf Kivesz a veremből és kivonja belőle az akkumulátort, lebegőpontosként
mulf Kivesz a veremből és megszorozza az akkumulátorral, lebegőpontosként
divf Kivesz a veremből és elosztja az akkumulátorral, lebegőpontosként
modf Kivesz a veremből, elosztja és a tizedes az akkumulátorba kerül, lebegőpontosként
powf Kivesz a veremből és az akkumulátoradik hatványra emeli, lebegőpontosként

Az sw mnemonik változó számú (de legalább három) paraméterrel rendelkezik. Az első paramétere egy szám, a második egy kódcimke, és a többi is mind kódcimke. Az akkumulátorból kivonja a megadott számot, majd ellenőrzi, hogy az eredmény pozitív-e és kissebb, mint a megadott cimkék száma. Ha nem, akkor a második paraméterként megadott, első cimkére ugrik. Ha igen, akkor pedig a harmadik paramétertől (második cimkétől) kezdve az akkumulátoradik cimkét veszi, és oda ugrik.

Szóval dióhéjban

sw (érték), (cimke ahová egyébként ugrik),
  (cimke ahová ugrik ha az akkumulátor egyenlő értékkel),
  (cimke ahová ugrik ha az akkumulátor egyenlő érték + 1-el),
  (cimke ahová ugrik ha az akkumulátor egyenlő érték + 2-vel),
  (cimke ahová ugrik ha az akkumulátor egyenlő érték + 3-al),
  ...
  (cimke ahová ugrik ha az akkumulátor egyenlő érték + N-el)

Összesen 256 érték címkéje lehet minden sw mnemoniknak.