1. Tipy a rady

Následují některé příklady použití inline assembleru a několik informací o tom, jak obejít jeho omezení. V tomto dokumentu pojem „assembler funkce“ označuje funkci deklarovanou v Pythonu s dekorátorem @micropython.asm_thumb, zatímco „podprogram“ označuje assemblerový kód volaný zevnitř assembler funkce.

1.1. Větvení kódu a podprogramy

Je důležité si uvědomit, že návěští jsou lokální pro danou assembler funkci. V současnosti neexistuje způsob, jak by podprogram definovaný v jedné funkci mohl být volán z jiné.

K volání podprogramu se použije instrukce bl(LABEL). Ta předá řízení instrukci následující za direktivou label(LABEL) a uloží návratovou adresu do link registru (lr nebo r14). K návratu se použije instrukce bx(lr), která způsobí, že vykonávání bude pokračovat instrukcí následující za voláním podprogramu. Tento mechanismus znamená, že pokud má podprogram volat jiný, musí před voláním uložit link registr a před ukončením jej obnovit.

Následující poněkud umělý příklad ilustruje volání funkce. Všimněte si, že je na začátku nutné přeskočit všechna volání podprogramů: podprogramy ukončují vykonávání pomocí bx(lr), zatímco vnější funkce prostě „vypadne na konci“ ve stylu Python funkcí.

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

Následující příklad kódu demonstruje vnořené (rekurzivní) volání: klasickou Fibonacciho posloupnost. Zde se před rekurzivním voláním uloží link registr spolu s dalšími registry, které logika programu vyžaduje zachovat.

@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. Předávání argumentů a návrat

Assembler funkce mohou podporovat nula až tři argumenty, které (pokud se používají) musí být pojmenovány r0, r1 a r2. Když se kód vykonává, registry budou inicializovány na tyto hodnoty.

Datové typy, které lze tímto způsobem předat, jsou celá čísla a paměťové adresy. Se současným firmwarem lze předávat a vracet všech 32bitových hodnot. Pokud může mít návratová hodnota nastavený nejvýznamnější bit, měla by se použít Python type hint, aby MicroPython mohl určit, zda se hodnota má interpretovat jako celé číslo se znaménkem nebo bez znaménka: typy jsou int nebo uint.

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

hex(uadd(0x40000000,0x40000000)) vrátí 0x80000000, čímž demonstruje předávání a návrat celých čísel, kde se bity 30 a 31 liší.

Omezení počtu argumentů a návratových hodnot lze překonat pomocí modulu array, který umožňuje přístup k libovolnému počtu hodnot libovolného typu.

1.2.1. Více argumentů

Pokud je Python pole celých čísel předáno jako argument assembler funkci, funkce obdrží adresu souvislé množiny celých čísel. Tak lze předat více argumentů jako prvky jediného pole. Podobně může funkce vrátit více hodnot tak, že je přiřadí prvkům pole. Assembler funkce nemají žádný způsob, jak zjistit délku pole: tu bude třeba funkci předat.

Toto použití polí lze rozšířit tak, aby bylo možné použít více než tři pole. To se dělá pomocí nepřímého odkazování: modul uctypes podporuje addressof(), který vrátí adresu pole předaného jako jeho argument. Tak můžete naplnit pole celých čísel adresami jiných polí:

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. Neceločíselné datové typy

Ty lze zpracovat pomocí polí příslušného datového typu. Například data v plovoucí řádové čárce s jednoduchou přesností lze zpracovat následovně. Tento příklad kódu bere pole hodnot typu float a nahrazuje jeho obsah jejich druhými mocninami.

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 podporuje použití datových struktur přesahujících jednoduchá pole. Umožňuje namapovat Python datovou strukturu na instanci bytearray, kterou lze poté předat assembler funkci.

1.3. Pojmenované konstanty

Assemblerový kód lze učinit čitelnějším a snáze udržovatelným pomocí pojmenovaných konstant namísto zaplevelování kódu čísly. Toho lze dosáhnout takto:

MYDATA = const(33)

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

Konstrukce const() způsobí, že MicroPython nahradí jméno proměnné její hodnotou v době kompilace. Pokud jsou konstanty deklarovány ve vnějším Python rozsahu, mohou být sdíleny mezi více assembler funkcemi a s Python kódem.

1.4. Assemblerový kód jako metody třídy

MicroPython předává adresu instance objektu jako první argument metodám třídy. To je pro assembler funkci obvykle málo užitečné. Lze se tomu vyhnout deklarováním funkce jako statické metody takto:

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

1.5. Použití nepodporovaných instrukcí

Ty lze zakódovat pomocí příkazu data, jak je ukázáno níže. Ačkoliv jsou push() a pop() podporovány, příklad níže ilustruje princip. Potřebný strojový kód lze nalézt v dokumentu ARM v7-M Architecture Reference Manual. Všimněte si, že první argument volání data, jako například

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

naznačuje, že každý následující argument je dvoubajtová veličina.

1.6. Překonání omezení celých čísel v MicroPythonu

Malá celá čísla v MicroPythonu na 32bitových portech nemohou uchovat hodnotu, jejíž bity 30 a 31 se liší (například 0x80000000), takže assemblerový podprogram, který vyprodukuje úplný 32bitový výsledek, jej nemůže prostě přímo vrátit. Toto omezení se překonává následujícím kódem, který používá assembler k umístění výsledku do pole a Python kód k převedení výsledku na celé číslo bez znaménka s libovolnou přesností.

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