Создание компилятора под разработку промышленных контроллеров и SCADA QQ1
Это пост для предпринимателей, которые периодически спрашивают о деталях моего проекта, буду скидывать его ссылку. И так же пост для всех кто интересуется разработкой. Сам люблю читать подобное. Начинаем.
Лично мое мнение. Компания разработчик ПЛК (или SCADA )без разработки своего компилятора это - разработчик "начального уровня". В таком случае , невозможно делать действительно передовые технологии, контролировать весь процесс, или иметь доступ к мельчайшему поведению программ.
Тут я расскажу, как я делал компилятор, и что выделял в первую очередь работе.
Среда исполнения и компилятор создавался для работы с MCU с 8 килобайт ОЗУ и до 4ГБ.
То есть, чтоб критически слабые системы, имели функциональность топовых ПЛК (горячая замена кода на лету, вытесняющая виртуальная многозадачность, и другое). Другими способами этого добиться, кроме как создавать свой компилятор - нет.
Я не использовал LLVM но использовал правила лексера/парсера библиотеки ANTLR. Сосредоточившись на архитектуре и алгоритмах компилятора касающиеся выполнения программы, а не обработки текстовых символов.
Сделать примитивный компилятор и машину - просто. Множество статей написано про это в интернете. Но что б я выделил в полноценном компиляторе, то есть, который может построить программу любого уровня сложности? В данном случае речь идет не о компиляции в нативный код как после СИ, а в байткод который выполняется рантаймом.
Настоящий компилятор и виртуальная машина.
Я бы выделил два ключевых момента которые нужно реализовать для полноценного компилятора:
1. Менеджер адресации данных.
2. Менеджер адресации инструкций, и управление контекстом.
Менеджер адресации данных.
Я б назвал это самым важным в работе. Любая программа работает с данными.
Если компилятор не игрушечный и не учебный - нужно реализовать поддержку сложных типов данных.
Возьмем простой пример:
INT varA=7;
INT varB=5;
LDR R0 VarA;
LDR R1 VarB;
ADD R0 R1 R0;
По сути это готовая LD математическая инструкция. Вполне можно сделать ПЛК на базе микроконтроллера с ОЗУ даже 2кб для небольшого количества LD инструкицй.
Но, что если пользователь захочет поддержку в ПЛК языка ST или LD и создавать собственные функциональные блоки, а не только стандартные?
Тут не обойтись без поддержки пользовательских данных и структур:
У нас есть сложный тип структуры внутри которого - другие структуры, внутри которых и структуры и массивы и т.д
Теперь, при встрече строки
Equipment sandwikMB670;
Компилятор должен посчитать размер всех данных внутри переменной sandwikMB670 типа Equipment , и выделить под это память. А если в программе встретится строка:
LDW R0 sandwikMB670.states[245].Point.x;
Компилятор должен все просчитать и подставить вместо имени переменной, верный адрес после компиляции:
LDW R0 #200486;
Менеджер адресации и данных должен переварить любой тип данных из "бесконечных" вложений внутри:
кощей.дуб[4][8][2].гнездо[2][5].яйцо[3].игла
А самое главное, алгоритм просчета и управления адресами, должен быть надежным и лаконичным как X^2 , который предсказуемо выдаст те данные которые ожидаешь, и не промахнется ни на байт в сторону среди миллиарда других байт. Только для работы с типами, переменными и их адресами, я завел совершенно отдельный парсер ANTLR и создал библиотеку для этой конкретной задачи независимо от компилятора в целом.
Вторая составляющая компилятора:
Менеджер адресации инструкций, и управление контекстом
Тестовые задачи, для определения эффективности и скорости решения математических или LD инструкций виртуальной машиной.
На изображении - скрин, результат трансляции моей средой 3o|||sheet программ LD / ST в промежуточный виртуальный ассемблер для своей виртуальной машины. ВМ не знает что такое - потоки, функции и прочее, она просто поочередно выполняет все команды. Задача компилятора превратить метки (например BinaryOperaty \ MathOperaty etc...) в адреса переходов. Одни метки - играют роль отдельных потоков, другие метки играют роль - функций программы. Метки которые играют роль независимых задач/ потоков - обрабатывает виртуальная моя ОС 3o|||sheet которая , которая сохраняет адреса, в свой системный массив указателей задач, а при сообщении от физического таймера - прерывает задачу, и достает следующую.
В завершение компилятор переводит объекты инструкций, к бинарному виду типа:
LDW R0 #200486; -> 11001011 00101001 00010101 01001010
В которых уже закодированы - тип инструкции, номера регистров , разные флаги и даже константы. Интерпретируются эти потоки байт - виртуальной машиной (а не физическим CPU/MCU ).
В итоге
Решив две эти задачи по управлению адресации данных, и менеджер инструкций, (ну и конечно рантайм который это воспроизводит на стороне железа) я создал собственный компилятор который может:
Создавать сложные типы данных
Утрамбовывать в памяти без пробелов
Надежно с ними работать.
Создавать сложные алгоритмы и поведения программы (рекурсии, многоуровневые переходы между функциями, передачи/возврата данных между ними
Реализовав управление контекстом, соответственно - создавать вытесняющую многозадачность , многопоточность, с полной масштабируемостью в многоядерных микроконтроллерах или CPU.
И при этом, легко и безопасно можно менять отдельные участки кода программы на лету.
И даже устанавливать (добавлять) в ПЛК - новые задачи, к уже работающим задачам/программ не останавливая ПЛК физически, как это в операционных системах Windows:
установил приложение, и оно сразу работает, или удалил его (но не злоупотреблять этим конечно, все таки это система для АСУ ТП с детерминированным, строгим по времени исполнением программ. Сборщиков мусора и фоновых задач по фрагментации памяти - нет).
А этого (менять код на лету, или устанавливать новые задачи без перепрошивки) не могут нативные ОС типа FreeRTOS. Так же можно обновлять ОС виртуального уровня - "по воздуху", и менять физические свойства ПЛК, например - включить дополнительный физический аналоговый IO - "по платной подписке" :D , это конечно шутка, но виртуальный уровень полностью может менять - физический если надо, управляя регистрами процессора и периферией.
Конечно, компилятор это еще и анализ самого кода и прочее (оптимизации) , но что касается ПЛК тут можно проще к этому относиться. Я реализовал только - удаление "мертвого кода"( да и то не по умолчанию), и предупреждения об неиспользуемых переменных и ошибках с типами. Чтоб не вышло как в старых версиях ПЛК от Siemense, когда компилятор дооптимизировался до такого что данные читал/записывал неинаисаои места( те самые "промахи" менеджера адресации данных).
Периодически буду рассказывать о других моментах проекта (например как организована виртуальная ОС и исполнение программ).
















