MicroPython:Основы/Язык MicroPython и его реализация/Ассемблерная вставка для архитектур Thumb2/Советы по использованию ассемблерных вставок: различия между версиями
Myagkij (обсуждение | вклад) (Новая страница: «{{MicroPython/Панель перехода}} {{Перевод от Сubewriter}} {{Myagkij-редактор}} =Советы по использованию ас...») |
Myagkij (обсуждение | вклад) Нет описания правки |
||
Строка 9: | Строка 9: | ||
==Условные переходы и подпрограммы== | ==Условные переходы и подпрограммы== | ||
Важно понимать, что метки – это локальные элементы ассемблерной функции. В данный момент в MicroPython нет возможности задать подпрограмму в одной функции, чтобы потом вызвать ее в другой. | Важно понимать, что метки – это локальные элементы ассемблерной функции. В данный момент в [[MicroPython]] нет возможности задать подпрограмму в одной функции, чтобы потом вызвать ее в другой. | ||
Вызов подпрограммы осуществляется с помощью команды bl(LABEL) – она передает управление команде, идущей за директивой label(LABEL), и сохраняет адрес возврата в регистр связи (LR или R14). Для возврата используется функция bx(lr) – она передает управление команде, идущей за вызовом подпрограммы. Этот механизм подразумевает, что если одна подпрограмма вызывает другую подпрограмму, то она должна перед этим сохранить адрес возврата в регистр связи, а перед завершением работы подпрограммы – прочесть его оттуда. | Вызов подпрограммы осуществляется с помощью команды '''bl(LABEL)''' – она передает управление команде, идущей за директивой '''label(LABEL)''', и сохраняет адрес возврата в регистр связи (''LR'' или ''R14''). Для возврата используется функция '''bx(lr)''' – она передает управление команде, идущей за вызовом подпрограммы. Этот механизм подразумевает, что если одна подпрограмма вызывает другую подпрограмму, то она должна перед этим сохранить адрес возврата в регистр связи, а перед завершением работы подпрограммы – прочесть его оттуда. | ||
Пример ниже вам вряд ли пригодится на практике, но он иллюстрирует, как происходит вызов функции. Обратите внимание, что вам сначала нужно организовать переходы для всех подпрограмм: выполнение подпрограмм заканчивается командой bx(lr), тогда как внешняя функция заканчивается «на полуслове» на манер Python-функций. | Пример ниже вам вряд ли пригодится на практике, но он иллюстрирует, как происходит вызов функции. Обратите внимание, что вам сначала нужно организовать переходы для всех подпрограмм: выполнение подпрограмм заканчивается командой '''bx(lr)''', тогда как внешняя функция заканчивается ''«на полуслове»'' на манер [[Python-функций]]. | ||
<syntaxhighlight lang="python" enclose="div"> | <syntaxhighlight lang="python" enclose="div"> | ||
Строка 58: | Строка 58: | ||
==Передача и возврат аргументов== | ==Передача и возврат аргументов== | ||
В ассемблерной функции может быть не более трех аргументов (или не быть вообще), и они должны быть (если используются) названы R0, R1 и R2. При выполнении кода эти регистры будут инициализированы согласно значениям, заданным пользователем. | В ассемблерной функции может быть не более трех аргументов (или не быть вообще), и они должны быть (если используются) названы ''R0'', ''R1'' и ''R2''. При выполнении кода эти регистры будут инициализированы согласно значениям, заданным пользователем. | ||
К типам данных, которые можно передать таким образом, относятся целые числа и адреса памяти. Текущая прошивка поддерживает передачу/возврат всего диапазона 32-битных значений. Если у возвращаемого значения самый старший бит задан на единицу, нужно воспользоваться | К типам данных, которые можно передать таким образом, относятся целые числа и адреса памяти. Текущая прошивка поддерживает передачу/возврат всего диапазона 32-битных значений. Если у возвращаемого значения самый старший бит задан на единицу, нужно воспользоваться [[Python]]’овской аннотацией типов, чтобы [[MicroPython]] мог понять, как интерпретировать это значение – как знаковое ([[int]]) или беззнаковое ([[uint]]) целое число. | ||
<syntaxhighlight lang="python" enclose="div"> | <syntaxhighlight lang="python" enclose="div"> | ||
Строка 68: | Строка 68: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Функция hex(uadd(0x40000000,0x40000000)) вернет «0x80000000» – это демонстрирует передачу и возвращение целых чисел, у которых отличаются 30 и 31 биты. | Функция '''hex(uadd(0x40000000,0x40000000))''' вернет ''«0x80000000»'' – это демонстрирует передачу и возвращение целых чисел, у которых отличаются ''30 и 31 биты''. | ||
Ограничение на количество аргументов и возвращаемых значений можно обойти при помощи модуля array. Он позволяет получить доступ к любому количеству значений любого типа. | Ограничение на количество аргументов и возвращаемых значений можно обойти при помощи модуля [[array]]. Он позволяет получить доступ к любому количеству значений любого типа. | ||
===Использование нескольких аргументов=== | ===Использование нескольких аргументов=== | ||
Если в ассемблерной функции в качестве аргумента задан | Если в ассемблерной функции в качестве аргумента задан [[Python]]’овский массив целых чисел, эта функция также получит адрес группы смежных целых чисел. Следовательно, несколько аргументов можно передать в качестве элементов одного массива. Аналогично функция может вернуть несколько значений, присвоив их к массиву элементов. У ассемблерных функций нет инструментария для определения размера массива, поэтому вам придется передать функции и эту информацию тоже. | ||
Если необходимо, вы можете воспользоваться более чем тремя массивами. Вам в этом поможет косвенная адресация: в модуле uctypes есть функция addressof(), которая возвращает адрес массива, переданного ей в качестве аргумента. | Если необходимо, вы можете воспользоваться более чем тремя массивами. Вам в этом поможет косвенная адресация: в модуле [[uctypes]] есть функция '''addressof()''', которая возвращает адрес массива, переданного ей в качестве аргумента. | ||
Благодаря ей вы можете сохранить в массив целых чисел адреса других массивов. | Благодаря ей вы можете сохранить в массив целых чисел адреса других массивов. | ||
Строка 117: | Строка 117: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
В модуле uctypes поддерживается использование не только обычных массивов, но и других структур данных. Это позволяет реализовать в экземпляре bytearray структуру данных из Python, а затем передать ее ассемблерной функции. | В модуле [[uctypes]] поддерживается использование не только обычных массивов, но и других структур данных. Это позволяет реализовать в экземпляре bytearray структуру данных из [[Python]], а затем передать ее ассемблерной функции. | ||
==Именованные константы== | ==Именованные константы== | ||
Строка 131: | Строка 131: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Здесь конструкция const() в фазе компиляции заставляет MicroPython заменить название переменной на ее числовое значение. Если константа объявлена во внешней области видимости Python, ее можно одновременно использовать к Python-коде и разных ассемблерных функциях. | Здесь конструкция '''const()''' в фазе компиляции заставляет [[MicroPython]] заменить название переменной на ее числовое значение. Если константа объявлена во внешней области видимости [[Python]], ее можно одновременно использовать к Python-коде и разных ассемблерных функциях. | ||
==Ассемблерный код как метод класса== | ==Ассемблерный код как метод класса== | ||
MicroPython передает адрес экземпляра объекта в первом аргументе метода класса. В ассемблерной функции он обычно не используется. Обойти это можно, объявив эту функцию как статический метод – вот так: | [[MicroPython]] передает адрес экземпляра объекта в первом аргументе метода класса. В ассемблерной функции он обычно не используется. Обойти это можно, объявив эту функцию как статический метод – вот так: | ||
<syntaxhighlight lang="python" enclose="div"> | <syntaxhighlight lang="python" enclose="div"> | ||
Строка 147: | Строка 147: | ||
==Использование неподдерживаемых команд== | ==Использование неподдерживаемых команд== | ||
Их можно создать при помощи ассемблерной директивы data() как в примере ниже – в нем создается команда, работающая по принципу команды push() (в MicroPython поддерживается команда push(), а цель примера ниже – чисто иллюстративная). | Их можно создать при помощи ассемблерной директивы '''data()''' как в примере ниже – в нем создается команда, работающая по принципу команды '''push()''' (в [[MicroPython]] поддерживается команда '''push()''', а цель примера ниже – чисто иллюстративная). | ||
Необходимый машинный код можно найти в справочнике по архитектуре ARM v7-M. Помните, что первый аргумент в команде data()отвечает за размер всех последующих аргументов: | Необходимый машинный код можно найти в справочнике по архитектуре ''ARM v7-M''. Помните, что первый аргумент в команде '''data()''' отвечает за размер всех последующих аргументов: | ||
<syntaxhighlight lang="python" enclose="div"> | <syntaxhighlight lang="python" enclose="div"> | ||
Строка 155: | Строка 155: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
В примере выше это «2», а это значит, что размер всех последующих аргументов будет 2 байта. | В примере выше это ''«2»'', а это значит, что размер всех последующих аргументов будет ''2 байта''. | ||
==Обход ограничения MicroPython, связанного с целыми числами== | ==Обход ограничения MicroPython, связанного с целыми числами== | ||
Чип Pyboard оснащен CRC- | Чип [[Pyboard]] оснащен [[CRC-генератор]]ом, но его использование проблемно для [[MicroPython]]. В [[MicroPython]] поддерживается возврат всего диапазона 32-битных чисел, но у малых целых чисел в битах 30 и 31 не может быть разных значений. Обойти это ограничение можно при помощи кода ниже – в нем используется ассемблер, чтобы поместить результат в массив, и [[Python]]-код, чтобы сделать принудительную трансформацию в беззнаковые числа произвольной точности. | ||
<syntaxhighlight lang="python" enclose="div"> | <syntaxhighlight lang="python" enclose="div"> |
Версия от 19:24, 21 августа 2020
Советы по использованию ассемблерных вставок[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)))