MicroPython:Основы/Язык MicroPython и его реализация/Ассемблерная вставка для архитектур Thumb2/Советы по использованию ассемблерных вставок: различия между версиями
Myagkij (обсуждение | вклад) Нет описания правки |
Нет описания правки |
||
Строка 15: | Строка 15: | ||
Пример ниже вам вряд ли пригодится на практике, но он иллюстрирует, как происходит вызов функции. Обратите внимание, что вам сначала нужно организовать переходы для всех подпрограмм: выполнение подпрограмм заканчивается командой '''bx(lr)''', тогда как внешняя функция заканчивается ''«на полуслове»'' на манер [[Python-функций]]. | Пример ниже вам вряд ли пригодится на практике, но он иллюстрирует, как происходит вызов функции. Обратите внимание, что вам сначала нужно организовать переходы для всех подпрограмм: выполнение подпрограмм заканчивается командой '''bx(lr)''', тогда как внешняя функция заканчивается ''«на полуслове»'' на манер [[Python-функций]]. | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
@micropython.asm_thumb | @micropython.asm_thumb | ||
def quad(r0): | def quad(r0): | ||
Строка 31: | Строка 31: | ||
Код ниже демонстрирует, как сделать вложенную (рекурсивную) функцию на примере классической последовательности Фибоначчи. Здесь до вызова рекурсивной функции сохраняется не только регистр связи, но и прочие регистры, необходимые для программы. | Код ниже демонстрирует, как сделать вложенную (рекурсивную) функцию на примере классической последовательности Фибоначчи. Здесь до вызова рекурсивной функции сохраняется не только регистр связи, но и прочие регистры, необходимые для программы. | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
@micropython.asm_thumb | @micropython.asm_thumb | ||
def fib(r0): | def fib(r0): | ||
Строка 62: | Строка 62: | ||
К типам данных, которые можно передать таким образом, относятся целые числа и адреса памяти. Текущая прошивка поддерживает передачу/возврат всего диапазона 32-битных значений. Если у возвращаемого значения самый старший бит задан на единицу, нужно воспользоваться [[Python]]’овской аннотацией типов, чтобы [[MicroPython]] мог понять, как интерпретировать это значение – как знаковое ([[int]]) или беззнаковое ([[uint]]) целое число. | К типам данных, которые можно передать таким образом, относятся целые числа и адреса памяти. Текущая прошивка поддерживает передачу/возврат всего диапазона 32-битных значений. Если у возвращаемого значения самый старший бит задан на единицу, нужно воспользоваться [[Python]]’овской аннотацией типов, чтобы [[MicroPython]] мог понять, как интерпретировать это значение – как знаковое ([[int]]) или беззнаковое ([[uint]]) целое число. | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
@micropython.asm_thumb | @micropython.asm_thumb | ||
def uadd(r0, r1) -> uint: | def uadd(r0, r1) -> uint: | ||
Строка 80: | Строка 80: | ||
Благодаря ей вы можете сохранить в массив целых чисел адреса других массивов. | Благодаря ей вы можете сохранить в массив целых чисел адреса других массивов. | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
from uctypes import addressof | from uctypes import addressof | ||
@micropython.asm_thumb | @micropython.asm_thumb | ||
Строка 99: | Строка 99: | ||
Нецелочисленные типы данных можно обрабатывать при помощи массивов соответствующего типа. Пример обработки чисел с плавающей точкой одинарной точности показан ниже. В нем содержимое массива чисел с плавающей точкой заменяется на квадраты этих чисел. | Нецелочисленные типы данных можно обрабатывать при помощи массивов соответствующего типа. Пример обработки чисел с плавающей точкой одинарной точности показан ниже. В нем содержимое массива чисел с плавающей точкой заменяется на квадраты этих чисел. | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
from array import array | from array import array | ||
Строка 123: | Строка 123: | ||
Ассемблерный код не обязан быть просто нагромождением чисел – его можно сделать более читаемым и удобным в сопровождении при помощи именованных констант. Это делается, к примеру, следующим образом: | Ассемблерный код не обязан быть просто нагромождением чисел – его можно сделать более читаемым и удобным в сопровождении при помощи именованных констант. Это делается, к примеру, следующим образом: | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
MYDATA = const(33) | MYDATA = const(33) | ||
Строка 137: | Строка 137: | ||
[[MicroPython]] передает адрес экземпляра объекта в первом аргументе метода класса. В ассемблерной функции он обычно не используется. Обойти это можно, объявив эту функцию как статический метод – вот так: | [[MicroPython]] передает адрес экземпляра объекта в первом аргументе метода класса. В ассемблерной функции он обычно не используется. Обойти это можно, объявив эту функцию как статический метод – вот так: | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
class foo: | class foo: | ||
@staticmethod | @staticmethod | ||
Строка 151: | Строка 151: | ||
Необходимый машинный код можно найти в справочнике по архитектуре ''ARM v7-M''. Помните, что первый аргумент в команде '''data()''' отвечает за размер всех последующих аргументов: | Необходимый машинный код можно найти в справочнике по архитектуре ''ARM v7-M''. Помните, что первый аргумент в команде '''data()''' отвечает за размер всех последующих аргументов: | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
data(2, 0xe92d, 0x0f00) # сохраняем r8,r9,r10,r11 в стек | data(2, 0xe92d, 0x0f00) # сохраняем r8,r9,r10,r11 в стек | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Строка 161: | Строка 161: | ||
Чип [[Pyboard]] оснащен [[CRC-генератор]]ом, но его использование проблемно для [[MicroPython]]. В [[MicroPython]] поддерживается возврат всего диапазона 32-битных чисел, но у малых целых чисел в битах 30 и 31 не может быть разных значений. Обойти это ограничение можно при помощи кода ниже – в нем используется ассемблер, чтобы поместить результат в массив, и [[Python]]-код, чтобы сделать принудительную трансформацию в беззнаковые числа произвольной точности. | Чип [[Pyboard]] оснащен [[CRC-генератор]]ом, но его использование проблемно для [[MicroPython]]. В [[MicroPython]] поддерживается возврат всего диапазона 32-битных чисел, но у малых целых чисел в битах 30 и 31 не может быть разных значений. Обойти это ограничение можно при помощи кода ниже – в нем используется ассемблер, чтобы поместить результат в массив, и [[Python]]-код, чтобы сделать принудительную трансформацию в беззнаковые числа произвольной точности. | ||
<syntaxhighlight lang="python | <syntaxhighlight lang="python"> | ||
from array import array | from array import array | ||
import stm | import stm |
Текущая версия от 18:17, 14 мая 2023
Советы по использованию ассемблерных вставок[1]
В этой статье мы рассмотрим несколько примеров использования ассемблерных вставок и расскажем о том, как обойти некоторые ограничения, связанные с их использованием. Под термином «ассемблерная функция» будет подразумеваться функция, объявленная в Python при помощи декоратора @micropython.asm_thumb, а под термином «подпрограмма» – ассемблерный код, вызванный из ассемблерной функции.
Условные переходы и подпрограммы
Важно понимать, что метки – это локальные элементы ассемблерной функции. В данный момент в MicroPython нет возможности задать подпрограмму в одной функции, чтобы потом вызвать ее в другой.
Вызов подпрограммы осуществляется с помощью команды 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))
Передача и возврат аргументов
В ассемблерной функции может быть не более трех аргументов (или не быть вообще), и они должны быть (если используются) названы 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. Он позволяет получить доступ к любому количеству значений любого типа.
Использование нескольких аргументов
Если в ассемблерной функции в качестве аргумента задан Python’овский массив целых чисел, эта функция также получит адрес группы смежных целых чисел. Следовательно, несколько аргументов можно передать в качестве элементов одного массива. Аналогично функция может вернуть несколько значений, присвоив их к массиву элементов. У ассемблерных функций нет инструментария для определения размера массива, поэтому вам придется передать функции и эту информацию тоже.
Если необходимо, вы можете воспользоваться более чем тремя массивами. Вам в этом поможет косвенная адресация: в модуле uctypes есть функция addressof(), которая возвращает адрес массива, переданного ей в качестве аргумента.
Благодаря ей вы можете сохранить в массив целых чисел адреса других массивов.
from uctypes import addressof
@micropython.asm_thumb
def getindirect(r0):
ldr(r0, [r0, 0]) # адрес массива,
# загруженный из переданного массива
ldr(r0, [r0, 4]) # возвращаем 1-ый элемент массива (24)
def testindirect():
a = array.array('i',[23, 24])
b = array.array('i',[0,0])
b[0] = addressof(a)
print(getindirect(b))
Нецелочисленные типы данных
Нецелочисленные типы данных можно обрабатывать при помощи массивов соответствующего типа. Пример обработки чисел с плавающей точкой одинарной точности показан ниже. В нем содержимое массива чисел с плавающей точкой заменяется на квадраты этих чисел.
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 поддерживается использование не только обычных массивов, но и других структур данных. Это позволяет реализовать в экземпляре bytearray структуру данных из Python, а затем передать ее ассемблерной функции.
Именованные константы
Ассемблерный код не обязан быть просто нагромождением чисел – его можно сделать более читаемым и удобным в сопровождении при помощи именованных констант. Это делается, к примеру, следующим образом:
MYDATA = const(33)
@micropython.asm_thumb
def foo():
mov(r0, MYDATA)
Здесь конструкция const() в фазе компиляции заставляет MicroPython заменить название переменной на ее числовое значение. Если константа объявлена во внешней области видимости Python, ее можно одновременно использовать к Python-коде и разных ассемблерных функциях.
Ассемблерный код как метод класса
MicroPython передает адрес экземпляра объекта в первом аргументе метода класса. В ассемблерной функции он обычно не используется. Обойти это можно, объявив эту функцию как статический метод – вот так:
class foo:
@staticmethod
@micropython.asm_thumb
def bar(r0):
add(r0, r0, r0)
Использование неподдерживаемых команд
Их можно создать при помощи ассемблерной директивы data() как в примере ниже – в нем создается команда, работающая по принципу команды push() (в MicroPython поддерживается команда push(), а цель примера ниже – чисто иллюстративная).
Необходимый машинный код можно найти в справочнике по архитектуре ARM v7-M. Помните, что первый аргумент в команде data() отвечает за размер всех последующих аргументов:
data(2, 0xe92d, 0x0f00) # сохраняем r8,r9,r10,r11 в стек
В примере выше это «2», а это значит, что размер всех последующих аргументов будет 2 байта.
Обход ограничения MicroPython, связанного с целыми числами
Чип Pyboard оснащен CRC-генератором, но его использование проблемно для MicroPython. В MicroPython поддерживается возврат всего диапазона 32-битных чисел, но у малых целых чисел в битах 30 и 31 не может быть разных значений. Обойти это ограничение можно при помощи кода ниже – в нем используется ассемблер, чтобы поместить результат в массив, и Python-код, чтобы сделать принудительную трансформацию в беззнаковые числа произвольной точности.
from array import array
import stm
def enable_crc():
stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x1000
def reset_crc():
stm.mem32[stm.CRC+stm.CRC_CR] = 1
@micropython.asm_thumb
def getval(r0, r1):
movwt(r3, stm.CRC + stm.CRC_DR)
str(r1, [r3, 0])
ldr(r2, [r3, 0])
str(r2, [r0, 0])
def getcrc(value):
a = array('i', [0])
getval(a, value)
return a[0] & 0xffffffff # принудительная трансформация
# в произвольную точность
enable_crc()
reset_crc()
for x in range(20):
print(hex(getcrc(0)))