Генерация Си кода из Engee Function
Генерация кода для Engee Function с помощью шаблонов генератора кода¶
Мотивация¶
Engee позволяет использовать пользовательский код в моделях при помощи блока Engee Function. Однако, в этом случае генерация кода будет невозможна, так как генератор кода не сможет "понять" пользовательский код. Эту проблему решает написание шаблона для генератора исходного кода. В качестве примера рассмотрим написание шаблона для блока Engee Function из модели ef_adder.
Блок AddDemo реализует сложение двух входов. Входы блока являются векторами фиксированного размера, длиной 16. Дополнительно, дополним задачу, выдвинув условие применения векторных вычислений.
Задачу будем решать при помощи написания шаблона для генератора исходного кода.
Анатомия шаблона генератора исходного кода¶
Шаблоны генератора исходного кода делятся на два класса:
- Шаблоны для блоков
- Шаблоны функции main()
В этом примере мы касаемся только шаблонов для блоков.
Шаблон для блоков имеет расширение *.cgt и начинается с комментария вида
/*!
BlockType = :EngeeFunction!AddDemo
TargetLang = :C
*/
BlockType - это тип блока, который можно получить из его параметров TargetLang - это целевой язык
Далее следует сам код шаблона. Шаблон содержит код на целевом языке, размеченный специальными комментариями и макросами. Внутри этих комментариев и макросов содержится код на языке Julia (т.н. хост-язык).
Шаблон для векторизованного сложения¶
Чтобы посмотреть содержимое текстового файла, создадим макрос:
macro showfile(file::String)
f = open(file)
s = read(f, String);
println(s);
close(f)
end
Теперь можно рассмотреть код шаблона:
@showfile "add_vectors.cgt"
Видно, что шаблон разделен на секции при помощи конструкций вида //! @
Это - макросы, которые обеспечивают механизм указания места подстановки исходного кода из шаблона в соответствующие секции сгенерированного кода.
Так, при помощи макроса //! @Toplevel
мы указываем генератору кода, что весь код ниже до следующего макроса будет помещен в начало заголовочного файла модели.
Обратим внимание на следующие строки шаблона. Они интересны тем, что демонстрируют вызовы хост-языка для шаблона.
Например, если строка начинается с //!
, то эта строка будет распознана как код на хост языке:
//! vector_width = convert(Int32, prod(input_size(1)) * (input(1).ty.bits)/8)
С помощью такого комментария можно вычислить какую-либо константу для дальнейшего использования в коде:
typedef $(input_datatype_name(1)) $vType __attribute__ ((vector_size ($(vector_width))));
Чтобы встроить результаты работы некоторой функции или значение переменных в сгенерированном коде мы оборачиваем вызов функции или переменной в конструкцию вида $()
При этом, если необходимо написать многострочный код на хост-языке, то мы можем сделать многострочный комментарий. Например:
/*!
if isempty(input_size(1))
sz = 1;
else
sz = input_size(1)
end
*/
В коде шаблона видны вызовы функций вида `output_datatype_name(1)` - это вызовы специальных функций шаблонов, которые позволяют работать со свойствами входов, выходов и состояний блоков.
Генерация кода с использованием шаблона¶
Для того чтобы применить наш шаблон, мы должны добавить его папку в путь поиска Engee:
demoroot = @__DIR__
engee.addpath(demoroot)
Затем, сгенерируем код с помощью команды engee.generate_code:
modelName = "ef_adder"
engee.generate_code(joinpath(demoroot,"$(modelName).engee"),
joinpath(demoroot,"vector_code"),
target = "c"
)
Посмотрим на сгенерированный код:
@showfile "vector_code/ef_adder.c"
Видно, что код из секций @Step
и @Defenitions
был встроен в функцию step()
модели. Аналогично и для заголовочного файла модели:
@showfile "vector_code/ef_adder.h"
Проверим, что полученный код может быть собран без ошибок:
;gcc -shared -fPIC ./vector_code/ef_adder.c -I ./vector_code -march=native -O3
Теперь удостовремся, что блок в Engee и сгенерированный код работают одинаково. Для этого мы создадим тестовую модель adder_ccall и поместим в нее блок C Function, который вызывает сгенерированный код. Затем соберем тестовую модель:
Для тестирования не забудем настроить блок C Function и сохраним модель:
mdl = engee.load("adder_ccall.engee")
engee.set_param!("adder_ccall/C Function","SourceFiles"=>"$(demoroot)/vector_code/ef_adder.c","IncludeDirectories"=>"$(demoroot)/vector_code");
engee.save(mdl,"adder_ccall.engee";force=true)
engee.close(mdl;force=true,sync_gui=true)
После запуска модели получим значения выходов:
Видно, что они совпадают, а значит код и модель работают одинаково.
По окончанию работы, не забудем вернуть путь поиска к исходному состоянию:
engee.rmpath(demoroot)
Выводы и следующие шаги¶
Шаблоны генератора исходного кода - это мощный инструмент по кастомизации исходного кода, позволяющий генерировать любой код, вплоть до применения расширений компиляторов. Однако, так как пользователю дается полный контроль над логикой генерации кода, то на пользователя ложится ответственность по обеспечению правильности шаблона.