Engee 文档
Notebook

使用代码生成器模板生成Engee函数的代码

动机

Engee允许您使用[Engee块函数]在模型中使用自定义代码(https://engee.com/helpcenter/stable/ru/base-lib-user-defined-function/engee-function.html但是,在这种情况下,代码生成将是不可能的,因为代码生成器将无法"理解"用户的代码。 为源代码生成器编写模板可以解决此问题。 作为一个例子,让我们考虑从ef_adder模型为Engee功能块编写一个模板。

AddDemo模块实现了两个输入的加法。 块输入是固定大小的向量,长度为16。 此外,我们将通过提出使用矢量计算的条件来完成任务。

我们将通过为源代码生成器编写模板来解决问题。

源代码生成器模板的解剖

源代码生成器模板分为两类:

*块的模板
*Main()函数的模板

在这个例子中,我们只处理块的模板。

块的模板有*。cgt扩展,并以类似的评论开始

``'cpp
/*!
BlockType=:EngeeFunction!AddDemo
TargetLang=:C
*/


BlockType是可以从其参数中获得的块的类型。
TargetLang是目标语言

模板代码本身如下。 模板包含目标语言的代码,用特殊注释和宏标记。 这些注释和宏包含**Julia**语言(所谓的宿主语言)中的代码。

## 矢量化加法的模板

要查看文本文件的内容,请创建一个宏:

In [ ]:
macro showfile(file::String)
    f = open(file)
    s = read(f, String);
    println(s);
    close(f)
end
Out[0]:
@showfile (macro with 1 method)

现在我们可以查看模板代码。:

In [ ]:
@showfile "add_vectors.cgt"
/*!
 BlockType = :EngeeFunction!AddDemo
 TargetLang = :C
 */

//! @Toplevel
//! sz = prod(input_size(1))
//! vector_width = convert(Int32, sz * (input(1).ty.bits)/8)
//! vType = "$(input_datatype_name(1))_v4_t"

typedef $(input_datatype_name(1)) $vType __attribute__ ((vector_size ($(vector_width))));

//! @Definitions
int vlen = $(sz);
$(vType) add1,add2,sum;
static $(output_datatype_name(1)) $(output(1))[$(prod(input_size(1)))];

//! @Step
memcpy(&add1,&$(input(1)), vlen * sizeof($(input_datatype_name(1))));
memcpy(&add2,&$(input(1)), vlen * sizeof($(input_datatype_name(1))));

sum = add1 + add2;

memcpy(&$(output(1)), &sum, vlen * sizeof($(input_datatype_name(1))));

可以看到,模板被分成使用像这样的结构的部分 //! @

这些宏提供了一种机制,用于指定将模板中的源代码插入到生成代码的相应部分中的位置。

所以,使用宏 //! @Toplevel 我们告诉代码生成器,下面的所有代码,直到下一个宏,将被放置在模型头文件的开头。

让我们注意模板的以下几行。 它们很有趣,因为它们演示了宿主语言对模板的调用。

例如,如果该行以 //! 然后这个字符串将被识别为宿主语言中的代码。:

//! vector_width=convert(Int32,prod(input_size(1))*(input(1).泰位)/8

使用这样的注释,您可以计算一个常数,以便在代码中进一步使用。:

打字,打字 $(input_datatype_name(1)) $vType__attribute__((vector_size($(vector_width))));

Чтобы встроить результаты работы некоторой функции или значение переменных в сгенерированном коде мы оборачиваем вызов функции или переменной в конструкцию вида $()

与此同时,如果有必要用宿主语言编写多行代码,那么我们可以进行多行注释。 例如:

``'茱莉亚
/*!
如果isempty(input_size(1))
sz=1;
其他
sz=input_size(1)
结束
*/


模板代码显示函数调用,如 \`output\_datatype\_name(1)\` -这些是对特殊模板函数的调用,允许您使用输入,输出和块状态的属性。

## 使用模板生成代码

为了应用我们的模板,我们需要将其文件夹添加到Engee搜索路径。:

In [ ]:
demoroot = @__DIR__
engee.addpath(demoroot)

然后,我们将使用engee命令生成代码。生成_code:

In [ ]:
modelName = "ef_adder"
engee.generate_code(joinpath(demoroot,"$(modelName).engee"),
                    joinpath(demoroot,"vector_code"),
                    target = "c"
                    )
[ Info: Generated code and artifacts: /user/work/code_generation/block_template/vector_code

我们来看看生成的代码:

In [ ]:
@showfile "vector_code/ef_adder.c"
/* Code generated by Engee
 * Model name: ef_adder.engee
 * Code generator: release-1.1.17
 * Date: Mon Jun 16 06:27:19 2025
 */

#include "ef_adder.h"

/* External inputs */
Ext_ef_adder_U ef_adder_U;

/* External outputs */
Ext_ef_adder_Y ef_adder_Y;

/* Model initialize function */
void ef_adder_init() {
	/* (no initialize code required) */
}

/* Model terminate function */
void ef_adder_term() {
	/* (no terminate code required) */
}

/* Model step function */
void ef_adder_step() {
	int vlen = 16;
double_v4_t add1,add2,sum;
static double AddDemo[16];


	
memcpy(&add1,&ef_adder_U.Inport, vlen * sizeof(double));
memcpy(&add2,&ef_adder_U.Inport, vlen * sizeof(double));

sum = add1 + add2;

memcpy(&AddDemo, &sum, vlen * sizeof(double));
	/* Outport: /Out1 incorporates:
	 *  EngeeFunction: /AddDemo
	 */
	for (int i = 0; i < 16; i++) ef_adder_Y.Out1[i] = AddDemo[i];

}

可以看出,代码从各节 @Step @Defenitions 被内置到函数中 step() 模特。 模型头文件也是如此。:

In [ ]:
@showfile "vector_code/ef_adder.h"
/* Code generated by Engee
 * Model name: ef_adder.engee
 * Code generator: release-1.1.17
 * Date: Mon Jun 16 06:27:19 2025
 */

#ifndef HEADER_ef_adder_h
#define HEADER_ef_adder_h

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <float.h>
#include <limits.h>
#include <string.h>

	typedef double double_v4_t __attribute__ ((vector_size (128)));



#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

typedef struct {
	double Inport[16];                     /* /Вход1 */
} Ext_ef_adder_U;

/* External inputs */
extern Ext_ef_adder_U ef_adder_U;

typedef struct {
	double Out1[16];                       /* /Out1 */
} Ext_ef_adder_Y;

/* External outputs */
extern Ext_ef_adder_Y ef_adder_Y;

/* Model entry point functions */
extern void ef_adder_init();
extern void ef_adder_step();
extern void ef_adder_term();

/* Here is the system hierarchy for this model:
 *
 * 'ef_adder' : '/'
 */

#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */

#endif /* HEADER_ef_adder_h */

让我们检查生成的代码是否可以在没有错误的情况下组装。:

In [ ]:
;gcc -shared -fPIC ./vector_code/ef_adder.c -I ./vector_code -march=native -O3

现在让我们确保Engee中的块和生成的代码以相同的方式工作。 为此,我们将创建加法器_ccall测试模型,并将C函数块放入其中,该函数块调用生成的代码。 然后我们将建立一个测试模型。:

image.png

对于测试,不要忘记设置C功能块并保存模型。:

In [ ]:
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)

运行模型后,我们将获得输出的值:

image.png

可以看出它们匹配,这意味着代码和模型的工作方式相同。

在工作结束时,不要忘记将搜索路径返回到原始状态。:

In [ ]:
engee.rmpath(demoroot)

结论和后续步骤

源代码生成器模板是一个功能强大的源代码自定义工具,允许您生成任何代码,包括使用编译器扩展。 但是,由于用户可以完全控制代码生成的逻辑,因此用户有责任确保模板是正确的。

示例中使用的块