1. Tippek és tanácsok

Az alábbiakban néhány példa szerepel az inline assembler használatára, valamint néhány információ arról, hogyan kerülhetők meg a korlátai. Ebben a dokumentumban az „assembler függvény” kifejezés egy olyan, Pythonban deklarált függvényre utal, amely a @micropython.asm_thumb dekorátorral van ellátva, míg az „alprogram” egy assembler függvényen belülről hívott assembler kódra utal.

1.1. Kódelágazások és alprogramok

Fontos megérteni, hogy a címkék lokálisak egy assembler függvényen belül. Jelenleg nincs mód arra, hogy egy függvényben definiált alprogramot egy másikból hívjunk meg.

Egy alprogram meghívásához a bl(LABEL) utasítást kell kiadni. Ez a vezérlést a label(LABEL) direktívát követő utasításra adja át, és a visszatérési címet a link regiszterben (lr vagy r14) tárolja. A visszatéréshez a bx(lr) utasítást kell kiadni, amely hatására a végrehajtás az alprogram-hívást követő utasítással folytatódik. Ez a mechanizmus azt jelenti, hogy ha egy alprogram egy másikat hív meg, akkor a hívás előtt el kell mentenie a link regisztert, és a befejezés előtt vissza kell állítania azt.

A következő, kissé mesterkélt példa egy függvényhívást szemléltet. Vegyük észre, hogy az elején el kell ágazni az összes alprogram-hívás körül: az alprogramok a bx(lr) utasítással fejezik be a végrehajtást, míg a külső függvény egyszerűen „lecsorog a végéig”, a Python függvények stílusában.

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

A következő kódpélda egy beágyazott (rekurzív) hívást mutat be: a klasszikus Fibonacci-sorozatot. Itt a rekurzív hívás előtt a link regiszter mentésre kerül azokkal a többi regiszterrel együtt, amelyeket a program logikája szerint meg kell őrizni.

@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. Argumentumátadás és visszatérés

Az assembler függvények nullától három argumentumig képesek argumentumokat fogadni, amelyeket (ha használjuk őket) r0, r1 és r2 névvel kell ellátni. A kód végrehajtásakor a regiszterek ezekre az értékekre lesznek inicializálva.

Az ilyen módon átadható adattípusok az egész számok és a memóriacímek. A jelenlegi firmware-rel minden lehetséges 32 bites érték átadható és visszaadható. Ha a visszatérési érték legmagasabb helyiértékű bitje beállított lehet, akkor egy Python típusjelzést kell alkalmazni, hogy a MicroPython meg tudja állapítani, az értéket előjeles vagy előjel nélküli egész számként kell-e értelmezni: a típusok az int vagy az uint.

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

A hex(uadd(0x40000000,0x40000000)) a 0x80000000 értéket adja vissza, bemutatva olyan egész számok átadását és visszaadását, ahol a 30. és a 31. bit eltér.

Az argumentumok és visszatérési értékek számára vonatkozó korlátozások az array modul segítségével küzdhetők le, amely bármilyen típusú, tetszőleges számú érték elérését teszi lehetővé.

1.2.1. Több argumentum

Ha egy egész számokból álló Python tömböt adunk át argumentumként egy assembler függvénynek, a függvény egy összefüggő egész számhalmaz címét kapja meg. Így több argumentum is átadható egyetlen tömb elemeiként. Hasonlóképpen egy függvény több értéket is visszaadhat, ha tömbelemekhez rendeli őket. Az assembler függvényeknek nincs módjuk egy tömb hosszának meghatározására: ezt át kell adni a függvénynek.

A tömbök ilyen használata kiterjeszthető úgy, hogy háromnál több tömb is használható legyen. Ez indirekcióval valósítható meg: az uctypes modul támogatja az addressof() függvényt, amely az argumentumaként átadott tömb címét adja vissza. Így egy egész számokból álló tömböt feltölthetsz más tömbök címeivel:

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. Nem egész adattípusok

Ezek a megfelelő adattípusú tömbök segítségével kezelhetők. Például az egyszeres pontosságú lebegőpontos adatok a következőképpen dolgozhatók fel. Ez a kódpélda egy float-okból álló tömböt vesz, és a tartalmát azok négyzeteivel helyettesíti.

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)

Az uctypes modul az egyszerű tömbökön túlmutató adatstruktúrák használatát is támogatja. Lehetővé teszi, hogy egy Python adatstruktúrát egy bytearray példányra képezzünk le, amely azután átadható az assembler függvénynek.

1.3. Nevesített konstansok

Az assembler kód olvashatóbbá és karbantarthatóbbá tehető nevesített konstansok használatával ahelyett, hogy számokkal árasztanánk el a kódot. Ez a következőképpen érhető el:

MYDATA = const(33)

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

A const() konstrukció hatására a MicroPython fordítási időben lecseréli a változónevet annak értékére. Ha a konstansok egy külső Python hatókörben vannak deklarálva, akkor több assembler függvény között, valamint Python kóddal is megoszthatók.

1.4. Assembler kód osztálymetódusként

A MicroPython az objektumpéldány címét adja át első argumentumként az osztálymetódusoknak. Ez egy assembler függvény számára általában csekély hasznú. Elkerülhető, ha a függvényt statikus metódusként deklaráljuk, így:

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

1.5. Nem támogatott utasítások használata

Ezek a lentebb bemutatott data utasítással kódolhatók. Bár a push() és a pop() támogatottak, az alábbi példa az elvet szemlélteti. A szükséges gépi kód az ARM v7-M Architecture Reference Manual-ban található meg. Vegyük észre, hogy az olyan data hívások első argumentuma, mint

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

azt jelzi, hogy minden további argumentum egy kétbájtos mennyiség.

1.6. A MicroPython egész szám korlátozásának leküzdése

A MicroPython kis egész számai a 32 bites portokon nem tudnak olyan értéket tárolni, amelynek a 30. és 31. bitje eltér (például 0x80000000), így egy teljes 32 bites eredményt előállító assembler rutin nem adhatja vissza azt egyszerűen közvetlenül. Ez a korlátozás a következő kóddal küzdhető le, amely assemblert használ az eredmény tömbbe helyezéséhez, és Python kódot az eredménynek tetszőleges pontosságú előjel nélküli egész számmá alakításához.

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