1. تلميحات ونصائح

فيما يلي بعض الأمثلة على استخدام المُجمّع السطري وبعض المعلومات حول كيفية التغلب على قيوده. في هذا المستند يشير مصطلح "دالة المُجمّع" إلى دالة معرّفة في Python باستخدام المُزخرِف @micropython.asm_thumb، بينما يشير "الروتين الفرعي" إلى شفرة المُجمّع التي تُستدعى من داخل دالة المُجمّع.

1.1. تفرّعات الشفرة والروتينات الفرعية

من المهم إدراك أن التسميات محلية لدالة المُجمّع. لا توجد حاليًا طريقة لاستدعاء روتين فرعي معرّف في دالة ما من دالة أخرى.

لاستدعاء روتين فرعي تُصدَر التعليمة bl(LABEL). وهذا ينقل التحكم إلى التعليمة التي تلي توجيه label(LABEL) ويخزّن عنوان العودة في سجل الرابط (lr أو r14). وللعودة تُصدَر التعليمة bx(lr) التي تتسبب في متابعة التنفيذ من التعليمة التي تلي استدعاء الروتين الفرعي. وتعني هذه الآلية أنه إذا كان روتين فرعي سيستدعي آخر، فيجب عليه حفظ سجل الرابط قبل الاستدعاء واستعادته قبل الانتهاء.

يوضّح المثال التالي المُصطنع نوعًا ما استدعاء دالة. لاحظ أنه من الضروري في البداية التفرّع حول جميع استدعاءات الروتينات الفرعية: تنهي الروتينات الفرعية التنفيذ بـ bx(lr) بينما "تتساقط الدالة الخارجية ببساطة من النهاية" على نمط دوال 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))

يوضّح مثال الشفرة التالي استدعاءً متداخلًا (تعاوديًا): متتالية فيبوناتشي الكلاسيكية. هنا، قبل الاستدعاء التعاودي، يُحفظ سجل الرابط مع السجلات الأخرى التي يتطلب منطق البرنامج الحفاظ عليها.

@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. تمرير الوسائط والعودة

يمكن لدوال المُجمّع أن تدعم من صفر إلى ثلاثة وسائط، والتي يجب (إذا استُخدمت) أن تُسمى r0 و r1 و r2. عندما تُنفّذ الشفرة ستُهيَّأ السجلات بتلك القيم.

أنواع البيانات التي يمكن تمريرها بهذه الطريقة هي الأعداد الصحيحة وعناوين الذاكرة. مع البرنامج الثابت الحالي يمكن تمرير وإرجاع جميع القيم الممكنة ذات 32 بت. إذا كانت قيمة الإرجاع قد يكون فيها البت الأكثر دلالة مضبوطًا، فينبغي استخدام تلميح نوع في Python لتمكين MicroPython من تحديد ما إذا كان ينبغي تفسير القيمة كعدد صحيح ذي إشارة أو بدون إشارة: الأنواع هي int أو uint.

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

سيُرجع hex(uadd(0x40000000,0x40000000)) القيمة 0x80000000، مما يوضّح تمرير وإرجاع الأعداد الصحيحة حيث يختلف البتان 30 و 31.

يمكن التغلب على القيود المفروضة على عدد الوسائط وقيم الإرجاع بواسطة وحدة array التي تتيح الوصول إلى أي عدد من القيم من أي نوع.

1.2.1. وسائط متعددة

إذا مُرِّر مصفوفة Python من الأعداد الصحيحة كوسيط إلى دالة مُجمّع، فستتلقى الدالة عنوان مجموعة متجاورة من الأعداد الصحيحة. وبالتالي يمكن تمرير وسائط متعددة كعناصر لمصفوفة واحدة. وبالمثل يمكن لدالة أن تُرجع قيمًا متعددة بإسنادها إلى عناصر المصفوفة. لا تملك دوال المُجمّع وسيلة لتحديد طول المصفوفة: سيلزم تمرير هذا الطول إلى الدالة.

يمكن توسيع استخدام المصفوفات هذا لإتاحة استخدام أكثر من ثلاث مصفوفات. ويتم ذلك باستخدام التوجيه غير المباشر: تدعم وحدة uctypes الدالة addressof() التي تُرجع عنوان مصفوفة مُمرَّرة كوسيط لها. وبالتالي يمكنك ملء مصفوفة أعداد صحيحة بعناوين مصفوفات أخرى:

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. أنواع البيانات غير الصحيحة

يمكن التعامل مع هذه بواسطة مصفوفات من نوع البيانات المناسب. على سبيل المثال، يمكن معالجة بيانات الفاصلة العائمة أحادية الدقة على النحو التالي. يأخذ مثال الشفرة هذا مصفوفة من أعداد الفاصلة العائمة ويستبدل محتوياتها بمربعاتها.

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 استخدام بنى البيانات بما يتجاوز المصفوفات البسيطة. فهي تمكّن من تعيين بنية بيانات Python إلى نسخة من bytearray يمكن بعد ذلك تمريرها إلى دالة المُجمّع.

1.3. الثوابت المُسمّاة

يمكن جعل شفرة المُجمّع أكثر قابلية للقراءة والصيانة باستخدام ثوابت مُسمّاة بدلًا من ملء الشفرة بالأرقام. ويمكن تحقيق ذلك على النحو التالي:

MYDATA = const(33)

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

تتسبب البنية const() في أن يستبدل MicroPython اسم المتغير بقيمته في وقت التجميع. إذا أُعلنت الثوابت في نطاق Python خارجي فيمكن مشاركتها بين دوال مُجمّع متعددة ومع شفرة Python.

1.4. شفرة المُجمّع كدوال صنف

يمرّر MicroPython عنوان نسخة الكائن كأول وسيط لدوال الصنف. وهذا عادةً قليل الفائدة لدالة مُجمّع. ويمكن تجنّبه بإعلان الدالة كدالة ساكنة على النحو التالي:

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

1.5. استخدام التعليمات غير المدعومة

يمكن ترميز هذه باستخدام عبارة data كما هو موضح أدناه. وبينما تُدعم push() و pop() فإن المثال أدناه يوضّح المبدأ. يمكن العثور على شفرة الآلة اللازمة في دليل مرجع معمارية ARM v7-M. لاحظ أن الوسيط الأول لاستدعاءات data مثل

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

يشير إلى أن كل وسيط لاحق هو كمية من بايتين.

1.6. التغلب على قيد الأعداد الصحيحة في MicroPython

لا يمكن للأعداد الصحيحة الصغيرة في MicroPython على المنافذ ذات 32 بت أن تحمل قيمة يختلف فيها البتان 30 و 31 (على سبيل المثال 0x80000000)، لذا فإن روتين المُجمّع الذي ينتج نتيجة 32 بت كاملة لا يمكنه ببساطة إرجاعها مباشرة. يتم التغلب على هذا القيد بالشفرة التالية، التي تستخدم المُجمّع لوضع النتيجة في مصفوفة وشفرة Python لإكراه النتيجة على أن تصبح عددًا صحيحًا بدون إشارة وبدقة عشوائية.

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