
Комментарии 32
На самом деле интересная вещь. Сам тоже время от времени о подобных вещах думаю. Хочется для esp сделать какой то hot reload как в вебе. Будет время посмотрю как у вас устроено.
А компиляция быстро происходит или как обычно? На выходе файл небольшого размера? Там ведь нет библиотечного кода, только "наш"?
И ещё если я захочу его через вифи загрузить куда его положить, в отдельный раздел на флеше? Или просто в файл в разделе littlefs?
Проект ESP32_PRJ_TO_LLVM — это, по сути, стандартный ESP-IDF проект-обертка. Он нужен, чтобы компилятор (clang) видел все системные пути и хедеры (например, #include "freertos/FreeRTOS.h"). Настраивать эти пути вручную — та еще головная боль, поэтому я пришел к такому решению.
Это дает огромный плюс: вы можете сначала отладить свой код как обычное приложение, а потом скриптом собрать из него .bc файл.
Нюанс отладки: Если ваш модуль вызывает функции основной прошивки (через extern), то для локальной отладки внутри шаблона им нужны «заглушки» (тела функций). А когда собираем финальный .bc — комментируем тела и оставляем только объявления, чтобы транслятор понял, что это внешний вызов.
Компиляция в промежуточный код (.bc) и трансляция в .espb проходят очень быстро.Размер итогового файла получается компактным, так как он содержит только вашу логику. Никакого библиотечного кода (FreeRTOS, драйверы, lwIP) в него не включается — всё это уже есть в основной прошивке. Мы просто ссылаемся на системные функции через таблицу символов.
Загрузка (Wi-Fi / Файловая система)
Тут есть два пути, в зависимости от задач:
LittleFS / SPIFFS: Если файлы .espb небольшие, их можно хранить в файловой системе. Но для работы интерпретатору нужен указатель на непрерывный участок памяти. Поэтому файл из LittleFS придется целиком перегнать в ОЗУ. Исполнять код напрямую из файловой системы (побайтовым чтением) в теории можно, но это убьет производительность из-за накладных расходов SPI. И данный функционал не реализован.
Отдельный раздел (Partition): Если модули большие или нужно экономить ОЗУ, лучше писать их в Raw-раздел (через OTA API) и использовать MMAP. Это позволит исполнять код (читать опкоды) напрямую из Flash-памяти, не занимая оперативку.
вещь конечно очень нишевая за счет новой VM. имхо зря зациклились на кроссплатформенности. сложно представить где это надо, не так и сложно собрать код под несколько версий esp, да и как праивло не надо это.
из того что я думал все таки компилировать в обычный байткод. - интересующие функции (которые надо сделать горячими hot-swap) делать в основном коде ссылками на функции. тогда при заливке нового кода в IRAM можно поменять ссылки на новые функции. в коде патча ссылки должны быть сразу правильные, т.к. они делаются для конкретной версии билда.
ваш способ я пока поверхностно понял но конечно выглядит универсальнее и в теории позволит сделать настоящую ОС для ESP с онлайн маркетплейсом приложений и их запуском.
но это ваша LLVM все таки темная лошадка не понятно насколько производительность сьедает. ведь у разных ESP архитектура довольно сильно отличается и оптимизации им подходят разные.
плюс порог входа довольно высокий. как видите комментариев даже немного. кажется даже wasm неплохой вариант, видел есть проект по его портированию на esp.
но могу в чем то ошибаться пока поверхностно понял детали проекта
Тяжелая оптимизация происходит на хосте (на ПК) внутри транслятора.
В микроконтроллер прилетает уже "вылизанный" регистровый байт-код. Да, интерпретация медленнее натива но для бизнес-логики (опросить датчик, принять JSON, дернуть реле) это вообще не заметно. Мы же не биткоины майним и не видео кодируем в VM. Пока все это так.
Так же согласен, в DIY-проекте собрать бинарники несложно. Но если мы говорим о продукте, который живет годами, парк устройств становится разношерстным (тут S3, там C3...). Возможность отправить "по воздуху" (OTA) один файл обновления бизнес-логики, который гарантированно заведется на всём парке — это огромное упрощение инфраструктуры обновлений.
И да, WASM крут, но он изолирован. Чтобы из WASM дернуть xTaskCreate или кастомную функцию вашего драйвера, вам нужно написать C-обертку (binding), скомпилировать её в прошивку и зарегистрировать.
В ESPB благодаря libffi вызов происходит прозрачно. Не нужно писать "клей" для каждой функции. Это радикально снижает порог входа именно для написания кода под VM.
То, что я делаю это по сути тот же WASM, но с подвязками под libffi и без дескрипторов типов - (i*iiii)i. Когда все начинал. во главе угла стояло удобство использования и максимальная скорость выполнения кода близкая к нативной. С последним пока еще вопрос не решен, это следующий шаг- если вообще выйдет. Сначала хочу внедрить дополнительную таблицу символов без строковых имен, только доступом по индексам. Так можно открыть для .espb все, что необходимо не сильно жертвуя занимаемым местом во flash.
Вы абсолютно правы про "ОС с маркетплейсом" — это именно тот вектор, куда хочется двигаться. Чтобы приложения не зависели от версии ядра и железа.
"ОС с маркетплейсом" — это именно тот вектор, куда хочется двигаться
можеееееет нееее наааадооооо Просто что то прям хорошего результата от подобных решений маловато. В умных часах стали получаться относительно гибкие платформы, но все равно портируемость от модели к модели - очень так себе
Ну допустим у Вас все получилось. Есть интерпретатор+JIT. Все работает очень шустро, глаз радует. Сделали взаимодействие между "модулями". В каком бы направлении двигались дальше?
Предположим максимальное расширение "библиотеки модулей". А для этого ведь нонче нужен магазин сразу конечно платежи прикрутить.
Удивительно но часоделы не все идут по этому пути пока что. При этом них сильно меняется платформа внутри, выпускаются новые модели с фичами. Возможно им не хочется нести всю эту тягомотину плюс легаси. Выхлоп не очень
Спасибо! Честно говоря, пока не копал в этом направлении - сейчас голова занята другим. Все это «мысли завтрашнего дня». Какую-то инфраструктуру для удобного распространения и загрузки модулей сделать определенно стоит и то, в том случае если проект .espb вообще заинтересует и будет востребован.
Скажите. А если лично Вы напишите модуль, потратите уйму времени, сил. И захотите выложить этот модуль ну и поиметь чуток с этого дела. То как быть?
ну и поиметь чуток с этого дела
я однозначно из тех кто будет делать это исключительно под себя, и с выкладыванием исходников. и буду удивляться что это еще кому то понадобилось
часы дают интересные примеры в этом разрезе. например что запилили альтернативный сайт- магазин к официальному но без регистрации и смс. с часовыми табло довольно большой движ самодеятельности превращая их натурально в миниприложения
часы на esp32? лежат до сих пор такие, очень уж батарею негуманно используют.
в часах важно энергоэффективность.
еще проблема с часами - разные разрешения, а битмапы масштабировать не просто. в итоге все равно надо собирать разные сборки под разные разрешения.
как то даже участовал в проекте где рендеринг экрана происходит на телефоне или сервере и быстро по блютуз доставляются в часы когда надо. таким образом даже самые дешевые часы могут хоть киберпанк запускать :)
естественно есть куча ограничений изза скорости блютуз поэтому обновление адаптивное по частям и с низким фпс...
Муки выбора: почему не WASM, Lua или что то еще?
Я рассматривал стандартные решения, но по тем или иным причинам отказался от них.
И все же, почему не Lua или тот же микропитон?
Toit интересная штука. Но допустим Вам потребовалось использовать CAN. Его кстати не реализовали еще в Toit?
За протокол не отвечу, но есть драйвер для MCP2518FD CAN FD controller https://pkg.toit.io/package/github.com%2Ftoitware%2Fmcp2518fd-driver@v1.1.0
В поисках тогда перепробовал многое.
Сначала делал ставку на ELF loader (загрузку нативного кода). Хотелось получить поддержку полноценного C++ с классами. Даже модифицировал загрузчик и в целом получилось, но уперся в стену при попытке подружить его со стандартной библиотекой шаблонов (STL) и механизмом исключений(это вообще как я понял нерешаемая задача).
Смотрел и в сторону скриптовых языков. NodeMCU (Lua) оставила приятное впечатление своей модульностью: удобно собирать прошивку только из нужных компонентов. Но у Lua и MicroPython есть фундаментальный недостаток — производительность. Это интерпретаторы. Искал производительность уровня JIT-компиляции, но для ESP32 готовых решений такого класса просто не было. LuaJIT не завезли...
Самое основное: чем ближе ты к ESP-IDF, тем больше у тебя реальных возможностей. Любой скриптовый язык — это надстройка, которая неизбежно ограничивает доступ к железу и API операционной системы.
Но у Lua и MicroPython есть фундаментальный недостаток — производительность. Это интерпретаторы.
Ну просто ваше решение тоже является интерпретатором, с куда меньшим количеством вложенных человекочасов, так что можно предположить, что его производительность не будет выше. По крайней мере, без сравнения производительности отмахиваться от распространенного интерпретатора в пользу нового выглядит такой себе затеей.
В целом вы правы — код проекта молодой и требует «полировки», это факт.
Ну на счет человекочасов сложно стало судить, если учесть, что основную работу выполняют AI. Да и нет пока ни JIT ни AOT. Если у вас будет возможность и желание прогнать бенчмарки — буду только рад увидеть результаты, самому очень интересно!
не факт, учитывайте что там языки более высокоуровневые с динамической типизацией, что означает много накладных расодов на доступ к переменным, на размер памяти под любую переменную, а это всё излишняя нагрузка на кеш процессора. компилируя из строгого Си гораздо проще получить на выходе более быстрый код. но накладные расходы все равно есть и приличные.
конечно надо бы сначала тесты глянуть, иначе абстрактно. но я думаю должно быть быстрее чем Lua, MicroPython, espruino.
Доброе время суток Osmanpasha. Подскажите Вы бы каким скриптовым языком воспользовались для решения задач?
лично мне этот/подобные проекты инетерсны в разрезе разных задач. например:
обновление прошивки серийно выпускаемых изделий. пока обходимся OTA - и думаю на данном этапе это идеальное решение. патчами наверное и нет смысла обновлять, лучше полностью, это не долго и надежнее.
быстрое прототипирование. чтобы как в браузере, поменял код нажал сохранить и через пару сек выполняется новый код, особенно если работать с UI выводить чтото на дисплей будет очень полезно. ну или в сериал чтото выводить. или тестовые скетчи чтобы новую железку протестировать. вот эта ниша у меня не закрыта и данный проект в теории мог бы помочь, но пока что для сборки много телодвижений, кажется это будет еще дольше чем если просто собрать и загрузить полную прошивку. загружать в теории быстрее но собирать столько же или дольше.
у устрйоства которое делаем есть моб приложение откуда можно создать программу работы. то есть устройство обрабатывает несколько шагов с разными режимами работы, настройками и тд. моб приложение генерирует некий декларативный псевдокод и передает в устройство по нему устройство генерирует необходимые структуры объектов и дальше работает по ним. язык включает блоки для объявления переменных, простейшая арифметика, таймеры, ПИД регуляторы и тд. в общем все работет, но код специфичный и очень ограниченные конструкции.
Изначально думал использовать чтото типа Lua/wasm или скорее даже lisp и подобных ЯП или свой движок для языка похожего на них.
для такой задачи важно чтобы он был простой легкий для МК и можно было легко и быстро компилировать код на мобильном приложении. а также простая пошаговая отладка.
для такого применения данный проект бы тоже не стал использовать, т.к. компилировать сложно.
идея про маркетплейс зайдет если будет какое то популярное устройство типа флиппер зиро или как от них же проект с матричным дисплеем busybox.
проекты же типа запуска высокоуровневых языков на есп типа micropython или espruino (javascript) к сожалению прожорливые по памяти и подозреваю медленные. для есп нужно чтото более скомпилированное и низкоуровневое типа wasm или lisp. у wasm плюс в том что в него можно скомпилировать код из разных языков это очень полезно и расширяет зону применения. другое дело что при компиляции добавляется много функций конкретного языка и в итоге результат будет немало весить.
ну и я бы конечно делал ставку на опенсорс проекты.
А не расскажете, что за устройство? У той поделки с которой все у меня началось связь с мобильным приложением была на основе bluetooth и скорость передачи была так себе. Я для себя рассматривал примерно такую архитектуру: мобильное приложение в которое можно добавлять модули состоящие из выполняемых динамически "интерфейсов" (в телефоне для контроля и настройки) вкупе с подпрограммой для esp32. Подпрограмма заливается в микроконтроллер по bluetooth. В устройство можно заливать не одну подпрограмму а несколько. Соответственно подпрограммы должны быть небольшими, а основная прошивка содержать код всех основных элементов типа ПИД регуляторов и тд... Ну и с телефона управлять этим зоопарком - указывать какая подпрограмма должна выполняться, так же обновлять, удалять и тд.
Соответственно подпрограммы должны быть небольшими, а основная прошивка содержать код всех основных элементов типа ПИД регуляторов и тд..
что то это резко стало похоже на ПЛК только с bt.
Текущие результаты по ресурсам (ESP32-C3)
По итогам сборки финального отчета расклад следующий:
Flash: espb добавляет около 300 КБ по сравнению с чистой прошивкой (если не подключать дополнительные модули).
RAM: Здесь есть выбор.
Если основную функцию интерпретатора espb_call_function (с механизмом Computed Goto) поместить в IRAM (IRAM_ATTR), потребление оперативной памяти вырастает на ~35.3 КБ.
Если атрибут убрать, оверхед составляет всего ~8.3 КБ по сравнению с пустым проектом, но выполнение кода замедляется.
Конфигурация (SDK Configuration Editor)
Значения нужно подбирать индивидуально, но пока остановился на таких:
Линейная память: 64 Кб (по умолчанию).
Теневой стек: 4 Кб (по умолчанию).
IRAM для "трамплинов" libffi: 1024 байт (пока кэширования трамплинов нет и они занимают мало места, много выделять не стал).
Стеки задач: Для Main task и других задач, где выполняется espb, размеры стека нужно выставлять больше стандартных.
Тест производительности
Решил проверить скорость выполнения. В проекте ESP32_PRJ_TO_LLVM есть тест Фибоначчи.
Важный момент: атрибут inline фактически встраивает код функции, поэтому clang оптимизирует .bc файл, помещая логику теста прямо в run_performance_test. Внешние вызовы (printf, esp_cpu_get_cycle_count) идут в реальном времени, без предварительной подготовки и кэширования (это пока не реализовано).
Процесс сборки теста:
Переименовываю папку main_2 в main.
Запускаю скрипт: powershell -ExecutionPolicy Bypass -File .\get-ir-cmake.ps1 -SourceDirs "main" -EmitLL.
(Пробовал указывать -SourceDirs "main_2" напрямую, но получаю idf.py reconfigure failed. Разберусь с этим позже если получится).Компиляция занимает 10–15 секунд.
На выходе: linked_module.bc (21 340 байт) -> транслирую в linked_module.espb (2 422 байт) -> переименовываю в test.espb и кидаю в проект.
Результаты (Fibonacci 85)
Код функции (с inline):
attribute((inline))
uint64_t fibonacci_iterative(int n) {
if (n <= 1) {
return n;
}
uint64_t a = 0, b = 1;
for (int i = 2; i <= n; i++) {
uint64_t temp = a + b;
a = b;
b = temp;
}
return b;
}
С атрибутом IRAM_ATTR:
Время: 1.51 сек
Ср. время на вызов: 216 мкс
Без IRAM_ATTR (код во Flash):
Время: 1.59 сек
Ср. время на вызов: 226 мкс
(Разница есть, но не критичная для этого теста)
Нативный запуск (Native C):
Время: 0.04 сек
Ср. время на вызов: 5 мкс
Проблема с noinline
Попробовал убрать инлайнинг (noinline), чтобы честно протестировать вызов функции.
Итог печальный: Failed to call 'app_main', error: -30.
"Шарманка" не завелась. Похоже, где-то закрался баг — предстоят "веселые" вечера отладки.
С той ошибкой -30 разобрался довольно быстро.
Виновником оказался теневой стек (Shadow Stack). Увеличение его размера с дефолтных 4 КБ до 8 КБ полностью решило проблему.
Результаты бенчмарка (Fibonacci 85, noinline)
Теперь, когда вызовы проходят "по-честному", цифры немного выросли по сравнению с инлайн-версией (там было ~34.5k тактов), что логично — добавились накладные расходы на организацию вызова.
Вот расклад для функции с атрибутом attribute((noinline)):
С атрибутом IRAM_ATTR (Код в RAM):
Время выполнения: 1.73 сек.
Тактов на вызов: 39 643.
Среднее время: 247 мкс.
Комментарий: Это самый быстрый вариант для интерпретатора.
Без IRAM_ATTR (Код во Flash):
Время выполнения: 1.81 сек.
Тактов на вызов: 41 377.
Среднее время: 258 мкс.
Комментарий: Разница с IRAM составляет около 4-5%. Это плата за экономию оперативной памяти.
Нативный запуск на устройстве (Native C):
Время выполнения: 0.04 сек.
Тактов на вызов: 942.
Среднее время: 5 мкс.
Итог:
Разница между нативным кодом и интерпретатором составляет примерно 42-44 раза (942 против 39 643 тактов и 41 377).
спаисбо за тесты. чтото там рисовать на экране может быть уже критично падать фпс. хотя рисовать можно по разному. если в системе будет апи для отрисовки на чистом си, а из виртуалки только вызывать нужные методы то в целом наверное норм.
Сейчас оптимизирую. Последний тест Без IRAM_ATTR (Код во Flash) и с noinline
=== Performance Benchmark: Fibonacci(85) ===
Running 7000 iterations for averaging...
CPU @ 160 MHz
Result: 259695496911122585 (Expected: 259695496911122585)
Result is CORRECT.
Total CPU cycles for 7000 runs: 169362842
Total execution time: 1.06 seconds
Average CPU cycles per call: 24194 Average time per call: 151 microseconds
а это в 25,7 раз медленнее чем натив.
Пока не выкладывал. В работе. Думаю потихонечку приступать к JIT
Тесты не радуют, конечно не микропитон, но всё-таки медленно.
ESPB — брат WASM для ESP32