1. Vinkkejä ja neuvoja¶
Seuraavassa on muutamia esimerkkejä sulautetun assemblerin käytöstä sekä tietoa siitä, miten sen rajoitukset voidaan kiertää. Tässä dokumentissa termi ”assembler-funktio” viittaa Pythonissa @micropython.asm_thumb -dekoraattorilla määriteltyyn funktioon, kun taas ”aliohjelma” viittaa assembler-koodiin, jota kutsutaan assembler-funktion sisältä.
1.1. Koodihaarat ja aliohjelmat¶
On tärkeää ymmärtää, että nimiöt ovat paikallisia assembler-funktiolle. Tällä hetkellä ei ole mitään keinoa kutsua yhdessä funktiossa määriteltyä aliohjelmaa toisesta funktiosta.
Aliohjelmaa kutsutaan käskyllä bl(LABEL). Tämä siirtää suorituksen käskyyn, joka seuraa label(LABEL) -direktiiviä, ja tallentaa paluuosoitteen linkkirekisteriin (lr tai r14). Paluu tehdään käskyllä bx(lr), joka jatkaa suoritusta aliohjelmakutsua seuraavasta käskystä. Tämä mekanismi tarkoittaa, että jos aliohjelman on määrä kutsua toista aliohjelmaa, sen on tallennettava linkkirekisteri ennen kutsua ja palautettava se ennen päättymistään.
Seuraava jokseenkin keinotekoinen esimerkki havainnollistaa funktiokutsua. Huomaa, että alussa on tarpeen haarautua kaikkien aliohjelmakutsujen ympäri: aliohjelmat päättävät suorituksen käskyllä bx(lr), kun taas ulompi funktio yksinkertaisesti ”putoaa pois lopusta” Python-funktioiden tyyliin.
@micropython.asm_thumb
def quad(r0):
b(START)
label(DOUBLE)
add(r0, r0, r0)
bx(lr)
label(START)
bl(DOUBLE)
bl(DOUBLE)
print(quad(10))
Seuraava koodiesimerkki havainnollistaa sisäkkäistä (rekursiivista) kutsua: klassista Fibonaccin lukujonoa. Tässä ennen rekursiivista kutsua linkkirekisteri tallennetaan yhdessä muiden rekisterien kanssa, joiden säilymistä ohjelman logiikka edellyttää.
@micropython.asm_thumb
def fib(r0):
b(START)
label(DOFIB)
push({r1, r2, lr})
cmp(r0, 1)
ble(FIBDONE)
sub(r0, 1)
mov(r2, r0) # r2 = n -1
bl(DOFIB)
mov(r1, r0) # r1 = fib(n -1)
sub(r0, r2, 1)
bl(DOFIB) # r0 = fib(n -2)
add(r0, r0, r1)
label(FIBDONE)
pop({r1, r2, lr})
bx(lr)
label(START)
bl(DOFIB)
for n in range(10):
print(fib(n))
1.2. Argumenttien välitys ja paluuarvo¶
Assembler-funktiot voivat tukea nollasta kolmeen argumenttia, joiden (jos niitä käytetään) on oltava nimeltään r0, r1 ja r2. Kun koodi suoritetaan, rekisterit alustetaan näihin arvoihin.
Tällä tavalla välitettäviä tietotyyppejä ovat kokonaisluvut ja muistiosoitteet. Nykyisellä laiteohjelmistolla kaikki mahdolliset 32-bittiset arvot voidaan välittää ja palauttaa. Jos paluuarvon ylin bitti voi olla asetettuna, kannattaa käyttää Pythonin tyyppivihjettä, jotta MicroPython osaa määrittää, tulkitaanko arvo etumerkillisenä vai etumerkittömänä kokonaislukuna: tyypit ovat int tai uint.
@micropython.asm_thumb
def uadd(r0, r1) -> uint:
add(r0, r0, r1)
hex(uadd(0x40000000,0x40000000)) palauttaa 0x80000000, mikä osoittaa kokonaislukujen välityksen ja palautuksen tapauksessa, jossa bitit 30 ja 31 eroavat toisistaan.
Argumenttien ja paluuarvojen lukumäärän rajoitukset voidaan kiertää array -moduulin avulla, joka mahdollistaa minkä tahansa määrän minkä tahansa tyyppisiä arvoja käytettäväksi.
1.2.1. Useita argumentteja¶
Jos assembler-funktiolle välitetään argumenttina Python-taulukko kokonaislukuja, funktio saa peräkkäisten kokonaislukujoukon osoitteen. Näin useita argumentteja voidaan välittää yksittäisen taulukon alkioina. Vastaavasti funktio voi palauttaa useita arvoja sijoittamalla ne taulukon alkioihin. Assembler-funktioilla ei ole mitään keinoa määrittää taulukon pituutta: se on välitettävä funktiolle.
Tätä taulukoiden käyttöä voidaan laajentaa, jotta voidaan käyttää useampaa kuin kolmea taulukkoa. Tämä tehdään epäsuoralla osoituksella: uctypes -moduuli tukee funktiota addressof(), joka palauttaa argumenttinaan välitetyn taulukon osoitteen. Näin voit täyttää kokonaislukutaulukon muiden taulukoiden osoitteilla:
from uctypes import addressof
@micropython.asm_thumb
def getindirect(r0):
ldr(r0, [r0, 0]) # Address of array loaded from passed array
ldr(r0, [r0, 4]) # Return element 1 of indirect array (24)
def testindirect():
a = array.array('i',[23, 24])
b = array.array('i',[0,0])
b[0] = addressof(a)
print(getindirect(b))
1.2.2. Muut kuin kokonaislukutietotyypit¶
Näitä voidaan käsitellä asianmukaisen tietotyypin taulukoiden avulla. Esimerkiksi yksinkertaisen tarkkuuden liukulukudataa voidaan käsitellä seuraavasti. Tämä koodiesimerkki ottaa liukulukutaulukon ja korvaa sen sisällön niiden neliöillä.
from array import array
@micropython.asm_thumb
def square(r0, r1):
label(LOOP)
vldr(s0, [r0, 0])
vmul(s0, s0, s0)
vstr(s0, [r0, 0])
add(r0, 4)
sub(r1, 1)
bgt(LOOP)
a = array('f', (x for x in range(10)))
square(a, len(a))
print(a)
uctypes-moduuli tukee tietorakenteiden käyttöä yksinkertaisia taulukoita laajemmin. Se mahdollistaa Python-tietorakenteen kuvaamisen bytearray-instanssiin, joka voidaan sitten välittää assembler-funktiolle.
1.3. Nimetyt vakiot¶
Assembler-koodista voidaan tehdä luettavampaa ja ylläpidettävämpää käyttämällä nimettyjä vakioita sen sijaan, että koodi täytettäisiin numeroilla. Tämä voidaan saavuttaa näin:
MYDATA = const(33)
@micropython.asm_thumb
def foo():
mov(r0, MYDATA)
const() -rakenne saa MicroPythonin korvaamaan muuttujan nimen sen arvolla käännösaikana. Jos vakiot määritellään ulommassa Python-näkyvyysalueessa, niitä voidaan jakaa useiden assembler-funktioiden välillä sekä Python-koodin kanssa.
1.4. Assembler-koodi luokkametodeina¶
MicroPython välittää olioinstanssin osoitteen luokkametodien ensimmäisenä argumenttina. Tästä on yleensä vain vähän hyötyä assembler-funktiolle. Sen voi välttää määrittelemällä funktion staattiseksi metodiksi näin:
class foo:
@staticmethod
@micropython.asm_thumb
def bar(r0):
add(r0, r0, r0)
1.5. Tukemattomien käskyjen käyttö¶
Nämä voidaan koodata data-lausekkeen avulla alla esitetyllä tavalla. Vaikka push() ja pop() ovat tuettuja, alla oleva esimerkki havainnollistaa periaatteen. Tarvittava konekoodi löytyy julkaisusta ARM v7-M Architecture Reference Manual. Huomaa, että data-kutsujen, kuten alla olevan, ensimmäinen argumentti
data(2, 0xe92d, 0x0f00) # push r8,r9,r10,r11
ilmaisee, että jokainen sitä seuraava argumentti on kahden tavun suuruinen.
1.6. MicroPythonin kokonaislukurajoituksen kiertäminen¶
MicroPythonin pienet kokonaisluvut 32-bittisillä porttauksissa eivät voi sisältää arvoa, jonka bitit 30 ja 31 eroavat toisistaan (esimerkiksi 0x80000000), joten assembler-aliohjelma, joka tuottaa täyden 32-bittisen tuloksen, ei voi yksinkertaisesti palauttaa sitä suoraan. Tämä rajoitus kierretään seuraavalla koodilla, joka käyttää assembleria tuloksen tallentamiseen taulukkoon ja Python-koodia tuloksen muuntamiseen mielivaltaisen tarkkuuden etumerkittömäksi kokonaisluvuksi.
from array import array
@micropython.asm_thumb
def getval(r0):
movwt(r1, 0x80000000) # a 32-bit value whose bits 30 and 31 differ
str(r1, [r0, 0])
def get():
a = array('i', [0])
getval(a)
return a[0] & 0xffffffff # coerce to arbitrary precision
print(hex(get())) # 0x80000000