Code generation for Engee Function using Code Generator Templates
Motivation
Engee allows you to use custom code in models using the Engee block Function. However, in this case, code generation will not be possible, as the code generator will not be able to "understand" the user's code. Writing a template for a source code generator solves this problem. As an example, let's consider writing a template for the Engee Function block from the ef_adder model.
The AddDemo block implements the addition of two inputs. The block inputs are fixed-size vectors, 16 in length. Additionally, we will complete the task by putting forward a condition for the use of vector calculations.
We will solve the problem by writing a template for the source code generator.
The anatomy of a source code generator template
The source code generator templates are divided into two classes:
- Templates for blocks
- Templates for the main() function
In this example, we are only dealing with templates for blocks.
The template for the blocks has the *.cgt extension and starts with a comment like
/*!
 BlockType = :EngeeFunction!AddDemo
 TargetLang = :C
 */
BlockType is the type of block that can be obtained from its parameters.
TargetLang is the target language
The template code itself follows. The template contains code in the target language, marked up with special comments and macros. These comments and macros contain code in the Julia language (the so-called host language).
Template for vectorized addition
To view the contents of a text file, create a macro:
macro showfile(file::String)
    f = open(file)
    s = read(f, String);
    println(s);
    close(f)
end
Now we can review the template code.:
@showfile "add_vectors.cgt"
It can be seen that the template is divided into sections using constructions like //! @
These are macros that provide a mechanism for specifying where to insert the source code from the template into the corresponding sections of the generated code.
So, using a macro //! @Toplevel we tell the code generator that all the code below, until the next macro, will be placed at the beginning of the model header file.
Let's pay attention to the following lines of the template. They are interesting because they demonstrate the calls of the host language for the template.
For example, if the line starts with //! then this string will be recognized as a code in the host language .:
//! vector_width = convert(Int32, prod(input_size(1)) * (input(1).ty.bits)/8)
Using such a comment, you can calculate a constant for further use in the code.:
typedef $(input_datatype_name(1)) $vType __attribute__ ((vector_size ($(vector_width))));
Чтобы встроить результаты работы некоторой функции или значение переменных в сгенерированном коде мы оборачиваем вызов функции или переменной в конструкцию вида $()
At the same time, if it is necessary to write multi-line code in the host language, then we can make a multi-line comment. For example:
/*! 
if isempty(input_size(1))
	sz = 1;
else
	sz = input_size(1)
end
*/
The template code shows function calls like `output_datatype_name(1)` - these are calls to special template functions that allow you to work with the properties of inputs, outputs, and block states.
Code generation using a template
In order to apply our template, we need to add its folder to the Engee search path.:
demoroot = @__DIR__
engee.addpath(demoroot)
Then, we will generate the code using the engee command.generate_code:
modelName = "ef_adder"
engee.generate_code(joinpath(demoroot,"$(modelName).engee"),
                    joinpath(demoroot,"vector_code"),
                    target = "c"
                    )
Let's look at the generated code:
@showfile "vector_code/ef_adder.c"
It can be seen that the code from the sections @Step and  @Defenitions was built into the function step() models. The same is true for the model header file.:
@showfile "vector_code/ef_adder.h"
Let's check that the received code can be assembled without errors.:
;gcc -shared -fPIC ./vector_code/ef_adder.c -I ./vector_code -march=native -O3
Now let's make sure that the block in Engee and the generated code work the same way. To do this, we will create the adder_ccall test model and put the C Function block in it, which calls the generated code. Then we'll build a test model.:
 
For testing, don't forget to set up the C Function block and save the model.:
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)
After running the model, we will get the values of the outputs:
 
It can be seen that they match, which means that the code and the model work the same way.
At the end of the work, do not forget to return the search path to its original state.:
engee.rmpath(demoroot)
Conclusions and next steps
Source Code Generator templates are a powerful source code customization tool that allows you to generate any code, including the use of compiler extensions. However, since the user is given full control over the logic of code generation, the user is responsible for ensuring that the template is correct.