Я продолжаю работать над проектом универсального тулкита разработки программ для 8-битных микроконтроллеров.

Моя цель — создать единый ООП-язык (в стиле Java) с унифицированным API и HAL для 8-битных МК (AVR, STM8, PIC).

Хорошо подходящий слоган: «Написано один раз — работает на большинстве МК» (конечно, если это вообще возможно).

Сейчас я разрабатываю методику выделения памяти для примитивов внутри метода и его блоков (условные операторы, циклы и т. п.).

Основные два подхода:

- Динамическое выделение памяти на основе битовой карты блоков памяти.

- Выделение памяти непосредственно в стеке.

Похоже, в итоге я приду к выводу, что первый вариант нужен для хранения полей класса, а второй — для локальных переменных метода.

Судя по всему (подсказка от DeepSeek), всем известный язык C статически выделяет блок в стеке при входе в функцию, при этом:

- выделяет полный объем для хранения всех примитивов (даже тех, которые объявлены в блоках и могут не использоваться);

- не умеет и не позволяет освобождать выделенную память до завершения функции;

- использует дополнительный код для выделения и освобождения блока в стеке.

Теперь я понимаю, почему разработчики на C для МК с малыми ресурсами используют глобальные переменные.

Представьте, сколько памяти потребуется, если в программе будет огромное дерево условий, внутри которого разная логика и разные переменные.

Это действительно огромная трата RAM.

Разрабатывая своё решение, я планирую использовать выделение памяти в стеке только для переменных метода (без учёта блоков), а для каждого блока — отдельную процедуру выделения памяти.

Это позволит существенно экономить RAM.

Но, к сожалению, увеличит размер прошивки, так как для каждого блока потребуется около 16 слов для 16-битного SP и около 5 слов для 8-битного SP (вероятно для 16 бит я вынесу данный код в процедуру, уменя есть подобное решение в core5277).

И это необходимо для высокоуровневого ООП-языка.

Я также планирую добавить опцию в компилятор, позволяющую выбрать, что экономить: RAM или FLASH+CPU.

При выборе FLASH+CPU память будет выделяться статически, как в C.


В общем, проблема C ясна:

Для каждой функции выделяется блок в стеке под все переменные (даже неиспользуемые), и освобождается он только при завершении функции.

В ассемблере же:
Программист сам решает, как использовать память при входе в функцию.
Часто память вообще не выделяется — многие значения хранятся в регистрах.
То, что не помещается в регистры, можно сохранить одной инструкцией PUSH и восстановить POP.
Можно закрепить ячейку RAM за функцией и использовать её без дополнительных накладных расходов.
Даже если использовать аналогичные C-подобные макросы, программист, скорее всего, будет выделять память для конкретных блоков, а не резервировать максимальный объём для всей функции.
При этом он будет эффективно использовать регистры.

Вот такая огромная разница в оптимизации между ассемблером и C.

К сожалению, я тоже не смогу добиться максимальной оптимизации (как в ассемблере) в своём компиляторе.

НО! В моём тулките это не так критично, как могло бы быть.
Дело в том, что множество низкоуровневых алгоритмов будет реализовано и оптимизировано на ассемблере.
Драйверы также будут на ассемблере и будут использовать эти алгоритмы.
Язык высокого уровня будет их активно применять (часть — прозрачно через кодогенератор компилятора, часть — через явные вызовы).

GitHub проекта: https://github.com/w5277c/vm5277

Визитка проекта: http://vm5277.ru/

P.S. Прошло 3 недели, за две из них я набросал AVR-ассемблер (не полностью, но его функционала и гибкости достаточно для текущих нужд тулкита).

Конечно, я был бы рад использовать avra, но он содержит ошибки, из-за которых не получается собирать мои проекты.

P.P.S. Я сменил лицензию с GPLv3-or-later на Apache-2.0. Судя по всему, это допустимо, так как на данный момент я являюсь автором всего кода в проекте.