1. İpuçları ve püf noktaları

Aşağıda satır içi assembler kullanımına dair bazı örnekler ve sınırlamalarının nasıl aşılacağına ilişkin bilgiler yer almaktadır. Bu belgede “assembler işlevi” terimi, Python’da @micropython.asm_thumb dekoratörü ile bildirilen bir işlevi ifade ederken, “alt yordam” (subroutine) bir assembler işlevi içinden çağrılan assembler kodunu ifade eder.

1.1. Kod dalları ve alt yordamlar

Etiketlerin bir assembler işlevine özgü (yerel) olduğunu kavramak önemlidir. Şu anda bir işlevde tanımlanan bir alt yordamın başka bir işlevden çağrılmasının bir yolu yoktur.

Bir alt yordamı çağırmak için bl(LABEL) komutu verilir. Bu, denetimi label(LABEL) direktifini izleyen komuta aktarır ve dönüş adresini bağlantı yazmacında (lr veya r14) saklar. Geri dönmek için bx(lr) komutu verilir; bu, yürütmenin alt yordam çağrısını izleyen komuttan devam etmesini sağlar. Bu mekanizma şu anlama gelir: Bir alt yordam başka bir yordamı çağıracaksa, çağrıdan önce bağlantı yazmacını saklamalı ve sonlanmadan önce geri yüklemelidir.

Aşağıdaki oldukça yapay örnek bir işlev çağrısını göstermektedir. Başlangıçta tüm alt yordam çağrılarının etrafından dolaşmanın gerekli olduğuna dikkat edin: alt yordamlar yürütmeyi bx(lr) ile sonlandırırken, dıştaki işlev Python işlevleri tarzında basitçe “sonundan düşer”.

@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şağıdaki kod örneği iç içe (özyinelemeli) bir çağrıyı göstermektedir: klasik Fibonacci dizisi. Burada, özyinelemeli bir çağrıdan önce, bağlantı yazmacı, program mantığının korunmasını gerektirdiği diğer yazmaçlarla birlikte saklanır.

@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. Argüman aktarma ve dönüş

Assembler işlevleri sıfırdan üçe kadar argümanı destekleyebilir; bunlar (kullanılıyorsa) r0, r1 ve r2 olarak adlandırılmalıdır. Kod yürütüldüğünde yazmaçlar bu değerlerle başlatılır.

Bu şekilde aktarılabilen veri türleri tamsayılar ve bellek adresleridir. Mevcut aygıt yazılımı (firmware) ile olası tüm 32 bitlik değerler aktarılabilir ve döndürülebilir. Dönüş değerinin en anlamlı biti ayarlanmış olabilirse, MicroPython’un değerin işaretli mi yoksa işaretsiz bir tamsayı olarak mı yorumlanacağını belirlemesini sağlamak için bir Python tür ipucu kullanılmalıdır: türler int veya uint şeklindedir.

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

hex(uadd(0x40000000,0x40000000)) 0x80000000 değerini döndürür; bu, 30. ve 31. bitlerin farklı olduğu tamsayıların aktarılmasını ve döndürülmesini gösterir.

Argüman ve dönüş değerlerinin sayısına ilişkin sınırlamalar, herhangi bir türden herhangi bir sayıda değere erişilmesini sağlayan array modülü aracılığıyla aşılabilir.

1.2.1. Birden çok argüman

Bir Python tamsayı dizisi bir assembler işlevine argüman olarak aktarılırsa, işlev bitişik bir tamsayı kümesinin adresini alır. Böylece birden çok argüman tek bir dizinin öğeleri olarak aktarılabilir. Benzer şekilde bir işlev, değerleri dizi öğelerine atayarak birden çok değer döndürebilir. Assembler işlevlerinin bir dizinin uzunluğunu belirleme yolu yoktur: bunun işleve aktarılması gerekir.

Dizilerin bu kullanımı, üçten fazla dizinin kullanılmasını sağlamak için genişletilebilir. Bu, dolaylama (indirection) kullanılarak yapılır: uctypes modülü, argüman olarak aktarılan bir dizinin adresini döndüren addressof() işlevini destekler. Böylece bir tamsayı dizisini başka dizilerin adresleriyle doldurabilirsiniz:

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. Tamsayı olmayan veri türleri

Bunlar uygun veri türündeki diziler aracılığıyla işlenebilir. Örneğin, tek duyarlıklı kayan noktalı veriler aşağıdaki gibi işlenebilir. Bu kod örneği, bir kayan nokta dizisi alır ve içeriğini bunların kareleriyle değiştirir.

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 modülü, basit dizilerin ötesinde veri yapılarının kullanımını destekler. Bir Python veri yapısının, daha sonra assembler işlevine aktarılabilen bir bytearray örneğine eşlenmesini sağlar.

1.3. Adlandırılmış sabitler

Assembler kodu, kodu sayılarla doldurmak yerine adlandırılmış sabitler kullanılarak daha okunabilir ve bakımı kolay hale getirilebilir. Bu şu şekilde yapılabilir:

MYDATA = const(33)

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

const() yapısı, MicroPython’un değişken adını derleme zamanında değeriyle değiştirmesine neden olur. Sabitler dıştaki bir Python kapsamında bildirilirse, birden çok assembler işlevi arasında ve Python koduyla paylaşılabilir.

1.4. Sınıf yöntemleri olarak assembler kodu

MicroPython, nesne örneğinin adresini sınıf yöntemlerine ilk argüman olarak aktarır. Bu, bir assembler işlevi için normalde pek işe yaramaz. İşlev statik bir yöntem olarak bildirilerek bundan kaçınılabilir:

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

1.5. Desteklenmeyen komutların kullanımı

Bunlar aşağıda gösterildiği gibi data deyimi kullanılarak kodlanabilir. push() ve pop() desteklenmesine karşın aşağıdaki örnek ilkeyi göstermektedir. Gerekli makine kodu ARM v7-M Architecture Reference Manual belgesinde bulunabilir. Şu gibi data çağrılarının ilk argümanının

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

her sonraki argümanın iki baytlık bir değer olduğunu belirttiğine dikkat edin.

1.6. MicroPython’un tamsayı kısıtlamasının aşılması

32 bitlik portlardaki MicroPython küçük tamsayıları, 30. ve 31. bitleri farklı olan bir değeri (örneğin 0x80000000) tutamaz; bu nedenle tam 32 bitlik bir sonuç üreten bir assembler yordamı bunu basitçe doğrudan döndüremez. Bu sınırlama, sonucu bir diziye koymak için assembler ve sonucu keyfi duyarlıkta işaretsiz bir tamsayıya zorlamak için Python kodu kullanan aşağıdaki kodla aşılır.

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