1. Savjeti i naputci

U nastavku su navedeni neki primjeri uporabe ugrađenog asemblera te informacije o tome kako zaobići njegova ograničenja. U ovom dokumentu pojam „asemblerska funkcija” odnosi se na funkciju deklariranu u Pythonu s dekoratorom @micropython.asm_thumb, dok se „potprogram” odnosi na asemblerski kod pozvan iz unutar asemblerske funkcije.

1.1. Grananja koda i potprogrami

Važno je razumjeti da su oznake lokalne za asemblersku funkciju. Trenutno ne postoji način da se potprogram definiran u jednoj funkciji pozove iz druge.

Za poziv potprograma izdaje se instrukcija bl(LABEL). Ona prenosi kontrolu na instrukciju koja slijedi nakon direktive label(LABEL) i pohranjuje povratnu adresu u poveznički registar (lr ili r14). Za povratak izdaje se instrukcija bx(lr) koja uzrokuje nastavak izvođenja instrukcijom koja slijedi nakon poziva potprograma. Ovaj mehanizam podrazumijeva da, ako potprogram treba pozvati drugi, mora spremiti poveznički registar prije poziva i obnoviti ga prije završetka.

Sljedeći prilično izvještačeni primjer ilustrira poziv funkcije. Imajte na umu da je na početku nužno grananjem zaobići sve pozive potprograma: potprogrami završavaju izvođenje s bx(lr) dok vanjska funkcija jednostavno „otpada s kraja” u stilu Python funkcija.

@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))

Sljedeći primjer koda demonstrira ugniježđeni (rekurzivni) poziv: klasični Fibonaccijev niz. Ovdje se, prije rekurzivnog poziva, poveznički registar sprema zajedno s drugim registrima koje logika programa zahtijeva sačuvati.

@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. Prosljeđivanje argumenata i povrat

Asemblerske funkcije mogu podržavati od nula do tri argumenta, koji (ako se koriste) moraju biti imenovani r0, r1 i r2. Kada se kod izvrši, registri će biti inicijalizirani na te vrijednosti.

Tipovi podataka koji se mogu proslijediti na ovaj način su cijeli brojevi i memorijske adrese. S trenutnim ugrađenim programom (firmware) mogu se proslijediti i vratiti sve moguće 32-bitne vrijednosti. Ako povratna vrijednost može imati postavljen najznačajniji bit, treba upotrijebiti Python savjet o tipu (type hint) kako bi se omogućilo MicroPythonu da odredi treba li vrijednost interpretirati kao predznačeni ili nepredznačeni cijeli broj: tipovi su int ili uint.

@micropython.asm_thumb
def uadd(r0, r1) -> uint:
    add(r0, r0, r1)

hex(uadd(0x40000000,0x40000000)) vratit će 0x80000000, demonstrirajući prosljeđivanje i povrat cijelih brojeva gdje se bitovi 30 i 31 razlikuju.

Ograničenja na broj argumenata i povratnih vrijednosti mogu se prevladati pomoću modula array koji omogućuje pristup bilo kojem broju vrijednosti bilo kojeg tipa.

1.2.1. Više argumenata

Ako se Python polje cijelih brojeva proslijedi kao argument asemblerskoj funkciji, funkcija će primiti adresu kontinuiranog skupa cijelih brojeva. Tako se više argumenata može proslijediti kao elementi jednog polja. Slično, funkcija može vratiti više vrijednosti dodjeljujući ih elementima polja. Asemblerske funkcije nemaju načina odrediti duljinu polja: to će se morati proslijediti funkciji.

Ova uporaba polja može se proširiti kako bi se omogućilo korištenje više od tri polja. To se postiže pomoću indirekcije: modul uctypes podržava addressof() koja vraća adresu polja proslijeđenog kao njezin argument. Tako možete popuniti polje cijelih brojeva adresama drugih polja:

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. Necijelobrojni tipovi podataka

Njima se može rukovati pomoću polja odgovarajućeg tipa podataka. Na primjer, podaci s pomičnim zarezom jednostruke preciznosti mogu se obraditi na sljedeći način. Ovaj primjer koda uzima polje brojeva s pomičnim zarezom i zamjenjuje njegov sadržaj njihovim kvadratima.

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)

Modul uctypes podržava uporabu podatkovnih struktura izvan jednostavnih polja. Omogućuje preslikavanje Python podatkovne strukture na instancu bytearray koja se zatim može proslijediti asemblerskoj funkciji.

1.3. Imenovane konstante

Asemblerski kod može se učiniti čitljivijim i lakšim za održavanje korištenjem imenovanih konstanti umjesto pretrpavanja koda brojevima. To se može postići ovako:

MYDATA = const(33)

@micropython.asm_thumb
def foo():
    mov(r0, MYDATA)

Konstrukcija const() uzrokuje da MicroPython zamijeni naziv varijable njezinom vrijednošću tijekom prevođenja. Ako se konstante deklariraju u vanjskom Python opsegu, mogu se dijeliti između više asemblerskih funkcija i s Python kodom.

1.4. Asemblerski kod kao metode klase

MicroPython prosljeđuje adresu instance objekta kao prvi argument metodama klase. To je obično od male koristi za asemblersku funkciju. Može se izbjeći deklariranjem funkcije kao statičke metode ovako:

class foo:
  @staticmethod
  @micropython.asm_thumb
  def bar(r0):
    add(r0, r0, r0)

1.5. Uporaba nepodržanih instrukcija

One se mogu kodirati pomoću naredbe data kao što je prikazano u nastavku. Iako su push() i pop() podržane, primjer u nastavku ilustrira princip. Potreban strojni kod može se pronaći u priručniku ARM v7-M Architecture Reference Manual. Imajte na umu da prvi argument poziva data kao što je

data(2, 0xe92d, 0x0f00) # push r8,r9,r10,r11

označava da je svaki sljedeći argument dvobajtna veličina.

1.6. Prevladavanje MicroPythonovog ograničenja cijelih brojeva

Mali cijeli brojevi MicroPythona na 32-bitnim portovima ne mogu sadržavati vrijednost čiji se bitovi 30 i 31 razlikuju (na primjer 0x80000000), pa asemblerski potprogram koji proizvodi puni 32-bitni rezultat ne može ga jednostavno izravno vratiti. Ovo ograničenje prevladava se sljedećim kodom, koji koristi asembler za smještanje rezultata u polje i Python kod za pretvaranje rezultata u nepredznačeni cijeli broj proizvoljne preciznosti.

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