1. Petunjuk dan tips¶
Berikut ini adalah beberapa contoh penggunaan inline assembler dan beberapa informasi tentang cara mengatasi keterbatasannya. Dalam dokumen ini, istilah "fungsi assembler" mengacu pada fungsi yang dideklarasikan dalam Python dengan dekorator @micropython.asm_thumb, sedangkan "subrutin" mengacu pada kode assembler yang dipanggil dari dalam fungsi assembler.
1.1. Cabang kode dan subrutin¶
Penting untuk dipahami bahwa label bersifat lokal pada fungsi assembler. Saat ini tidak ada cara bagi subrutin yang didefinisikan dalam satu fungsi untuk dipanggil dari fungsi lain.
Untuk memanggil subrutin, instruksi bl(LABEL) digunakan. Ini memindahkan kontrol ke instruksi setelah direktif label(LABEL) dan menyimpan alamat pengembalian di link register (lr atau r14). Untuk kembali, instruksi bx(lr) digunakan, yang menyebabkan eksekusi melanjutkan dengan instruksi setelah pemanggilan subrutin. Mekanisme ini mengimplikasikan bahwa, jika subrutin akan memanggil subrutin lain, ia harus menyimpan link register sebelum pemanggilan dan memulihkannya sebelum berakhir.
Contoh berikut yang agak dibuat-buat mengilustrasikan pemanggilan fungsi. Perhatikan bahwa diperlukan di awal untuk bercabang melewati semua pemanggilan subrutin: subrutin mengakhiri eksekusi dengan bx(lr) sementara fungsi luar hanya "jatuh dari ujung" dengan gaya fungsi Python.
@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))
Contoh kode berikut mendemonstrasikan pemanggilan bersarang (rekursif): urutan Fibonacci klasik. Di sini, sebelum pemanggilan rekursif, link register disimpan bersama register lain yang diperlukan oleh logika program untuk dipertahankan.
@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. Penerusan argumen dan pengembalian¶
Fungsi assembler dapat mendukung dari nol hingga tiga argumen, yang harus (jika digunakan) diberi nama r0, r1 dan r2. Ketika kode dieksekusi, register akan diinisialisasi ke nilai-nilai tersebut.
Tipe data yang dapat diteruskan dengan cara ini adalah bilangan bulat dan alamat memori. Dengan firmware saat ini, semua nilai 32 bit yang mungkin dapat diteruskan dan dikembalikan. Jika nilai pengembalian mungkin memiliki bit paling signifikan yang diset, petunjuk tipe Python harus digunakan untuk memungkinkan MicroPython menentukan apakah nilai tersebut harus diinterpretasikan sebagai bilangan bulat bertanda atau tidak bertanda: tipenya adalah int atau uint.
@micropython.asm_thumb
def uadd(r0, r1) -> uint:
add(r0, r0, r1)
hex(uadd(0x40000000,0x40000000)) akan mengembalikan 0x80000000, mendemonstrasikan penerusan dan pengembalian bilangan bulat di mana bit 30 dan 31 berbeda.
Keterbatasan jumlah argumen dan nilai pengembalian dapat diatasi dengan menggunakan modul array yang memungkinkan sejumlah nilai dari tipe apa pun untuk diakses.
1.2.1. Beberapa argumen¶
Jika array bilangan bulat Python diteruskan sebagai argumen ke fungsi assembler, fungsi tersebut akan menerima alamat sekumpulan bilangan bulat yang berkesinambungan. Dengan demikian, beberapa argumen dapat diteruskan sebagai elemen dari satu array. Demikian pula, suatu fungsi dapat mengembalikan beberapa nilai dengan menetapkannya ke elemen array. Fungsi assembler tidak memiliki cara untuk menentukan panjang array: ini perlu diteruskan ke fungsi.
Penggunaan array ini dapat diperluas untuk memungkinkan lebih dari tiga array digunakan. Ini dilakukan menggunakan indirection: modul uctypes mendukung addressof() yang akan mengembalikan alamat array yang diteruskan sebagai argumennya. Dengan demikian Anda dapat mengisi array bilangan bulat dengan alamat array lain:
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. Tipe data non-integer¶
Ini dapat ditangani dengan menggunakan array dari tipe data yang sesuai. Misalnya, data floating point presisi tunggal dapat diproses sebagai berikut. Contoh kode ini mengambil array float dan mengganti isinya dengan kuadratnya.
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 mendukung penggunaan struktur data di luar array sederhana. Ini memungkinkan struktur data Python dipetakan ke instance bytearray yang kemudian dapat diteruskan ke fungsi assembler.
1.3. Konstanta bernama¶
Kode assembler dapat dibuat lebih mudah dibaca dan dipelihara dengan menggunakan konstanta bernama daripada mengisi kode dengan angka. Ini dapat dicapai dengan cara:
MYDATA = const(33)
@micropython.asm_thumb
def foo():
mov(r0, MYDATA)
Konstruk const() menyebabkan MicroPython menggantikan nama variabel dengan nilainya pada waktu kompilasi. Jika konstanta dideklarasikan dalam lingkup Python luar, mereka dapat dibagikan antara beberapa fungsi assembler dan dengan kode Python.
1.4. Kode assembler sebagai metode kelas¶
MicroPython meneruskan alamat instance objek sebagai argumen pertama ke metode kelas. Ini biasanya tidak berguna bagi fungsi assembler. Hal ini dapat dihindari dengan mendeklarasikan fungsi sebagai metode statis dengan cara:
class foo:
@staticmethod
@micropython.asm_thumb
def bar(r0):
add(r0, r0, r0)
1.5. Penggunaan instruksi yang tidak didukung¶
Ini dapat dikodekan menggunakan pernyataan data seperti yang ditunjukkan di bawah ini. Meskipun push() dan pop() didukung, contoh di bawah ini mengilustrasikan prinsipnya. Kode mesin yang diperlukan dapat ditemukan dalam ARM v7-M Architecture Reference Manual. Perhatikan bahwa argumen pertama dari pemanggilan data seperti
data(2, 0xe92d, 0x0f00) # push r8,r9,r10,r11
menunjukkan bahwa setiap argumen berikutnya adalah kuantitas dua byte.
1.6. Mengatasi pembatasan bilangan bulat MicroPython¶
Bilangan bulat kecil MicroPython pada port 32-bit tidak dapat menampung nilai yang bit 30 dan 31-nya berbeda (misalnya 0x80000000), sehingga rutin assembler yang menghasilkan hasil 32-bit penuh tidak dapat langsung mengembalikannya. Keterbatasan ini diatasi dengan kode berikut, yang menggunakan assembler untuk memasukkan hasil ke dalam array dan kode Python untuk memaksa hasil menjadi bilangan bulat tak bertanda presisi arbitrer.
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