# 快速入门
Syslab 代码生成快速入门
# 环境验证
提示
二进制文件部署时无需依赖
Syslab 软件安装内置 Syslab 代码生成工具,无需额外安装。
使用 scc 编译所得的可执行文件/动态链接库,在部署时无需配置 scc 依赖环境。
# Windows 平台
Windows 用户下载 Syslab 后使用 scc -h 查看输出即可验证 scc 编译器是否配置正确。
在 Syslab 中开启新终端,输入 scc -h。
scc -h
此命令输出结构如下所示,具体选项将随版本迭代而发生更新:
Usage: scc [options] file1 file2 file3 [options]...
Syslab Compiler Collection (version x.x.x)
Options:
-o, --output : Output file name.
-d, --directory : Directory to the generated C++ project.
--mode : Output mode, either 'app' or 'shared' (default: 'app').
--arch : Target architecture, either 'x64' or 'arm64'.
--os : Target OS, either 'win' or 'linux'.
...
# Linux 平台
Linux 用户需要保证系统中存在 gcc 与 g++ 编译器,可通过 gcc --version 与 g++ --version 命令确认:
gcc --version && g++ --version
gcc (GCC) X.X.X
...
g++ (GCC) X.X.X
...
同样地,可以使用 scc -h 验证是否已安装。
提示
GCC 编译器版本建议
Syslab 代码生成工具支持包括 GCC 4.8 在内的低版本编译器,但低版本编译器不支持部分硬件指令集与多种性能优化。
建议使用 GCC 7.5.0 以上版本,以保证充分享受 Syslab 代码生成工具的性能优势。
# 可执行文件生成
scc 命令行工具的调用方式为:
scc <输入文件> [-o {输出文件路径}] [--mode shared|app] [编译选项]...
当 scc 命令未指定 --mode shared 或 --mode app 时,程序自动根据后缀选择编译模式。例如,形如 scc main.jl -o main 或 scc main.jl -o --mode app 的命令在 Linux 上输出可执行文件 main,而在 windows 上输出可执行文件 main.exe。
参考以下示例,创建 main.jl 文件:
const a = [1 2; 3 4]
const b = [5 6; 7 8]
function main()
display(a * b)
end
# 如果不处于代码生成模式,则运行 main 函数
@static @isdefined(SyslabCC) || main()
在 Julia 编程环境中,运行 main.jl 输出以下结果:
2*2 Matrix{Int64}:
19 22
43 50
新建 Syslab 终端,使用 scc 命令调用代码生成工具,将 main.jl 生成为可执行文件 main.exe :
# 此处使用相对路径以明确工作目录
# 命令行中的 -o 表示输出文件路径, --bundle 表示打包运行时依赖
scc .\main.jl -o .\main.exe --bundle
# Linux 命令:
# scc ./main.jl -o ./main --bundle
提示
--bundle 选项
Julia 在矩阵运算等操作上依赖 C/C++/Fortran 编译的动态库。
由于 Syslab 代码生成不支持反编译动态库,如果原始 Julia 代码引用动态库,生成后的 C++ 代码也将原样引用动态库。
为方便兼容原生 Julia 程序,文档将尽可能使用 --bundle 选项。该选项通知代码生成工具打包动态库依赖,并统一放置于生成目标所在目录的 lib 子文件夹。
通过 --bundle 选项生成的程序打包了运行所需依赖,因而能在其他计算机上运行,且无需安装 Syslab。
scc 命令行工具支持相对路径和绝对路径。例如,在 Windows 上可使用绝对路径形式的命令 scc "C:\Test\main.jl" -o "C:\Test\main.exe" --bundle (C:\Test\ 仅为演示目录)。
生成的文件结构如下,不同编译器后端生成的产物后缀名存在差异,具体参考代码生成副产物的后缀名:
...
.syslabcc-cache/ # 代码生成的缓存项目文件夹
lib/ # 使用 --bundle 编译选项时,会打包运行时依赖的其他动态库到该文件夹下
bdwgc.dll # 运行时依赖的 GC(垃圾回收) 动态库
syslabcrt-dylib.lib # 代码生成的中间产物,后续运行无需依赖
syslabcrt-io.lib # 代码生成的中间产物,后续运行无需依赖
main.exe # 生成的可执行文件
main.jl # 输入文件
...
Linux 平台上生成产物后缀会有一定差异:
...
.syslabcc-cache/
lib/
libbdwgc.so
libsyslabcrt-dylib.a
libsyslabcrt-io.a
main
main.jl
...
可以在当前终端中执行生成的可执行文件:
.\main.exe
# Linux 命令:
# ./main
2*2 Matrix{Int64}:
19 22
43 50
# 动态库生成
接下来使用 scc 命令行工具从 Julia 文件生成动态库,再编写 C++ 程序调用该动态库。
build.jl 文件内容:
add_num(x, y) = x + y
# 使用 SyslabCC 时导出定义函数
@static if @isdefined(SyslabCC)
# 生成动态库, 导出 C 函数 add_num_i64 让 C++ 调用
# 其中,(Int, Int) 代表函数输入参数类型
SyslabCC.static_compile(
"add_num_i64", add_num, (Int64, Int64));
# C 函数名可以自定义,此处使用 add_num_f64,表示双精度浮点数加法
SyslabCC.static_compile(
"add_num_f64", add_num, (Float64, Float64));
end
使用 scc 编译前,我们在源文件内添加 SyslabCC.static_compile() 语句,该语句类似 C++ 项目中常见的 DLL_EXPORT 宏。
SyslabCC.static_compile 编译语句一般添加在源文件末尾,该语句不影响文件正常运行和可执行文件模式的代码生成。
现在,我们使用 scc 命令生成可供 C++ 调用的动态库:
scc build.jl -o libadd.dll --bundle --experimental-gen-header
# Linux 命令:
# scc build.jl -o libadd.so --bundle --experimental-gen-header
提示
实验性特性:生成 .h 头文件
使用 --experimental-gen-header 编译选项,可以在编译动态库的同时,生成 .h 头文件。
目前该头文件仅支持在 64 位机器上使用。
此时,目录下已生成可供 C++ 调用的 libadd.dll 文件。生成的文件结构如下:
...
.syslabcc-cache/
lib/
libadd.h
bdwgc.dll
build.jl
libadd.dll
libadd.lib
syslabcrt-dylib.lib
syslabcrt-io.lib
...
Linux 平台上的生成产物后缀会有一定差异:
...
.syslabcc-cache/
lib/
libadd.h
build.jl
libadd.so
libadd.lib
libbdwgc.so
libsyslabcrt-dylib.a
libsyslabcrt-io.a
...
其中 libadd.h 中声明了导出函数的定义:
// 文件名: libadd.h
/* 省略前文... */
SYSLABCC_DLLIMPORT int64_t add_num_i64(
int64_t x,
int64_t y);
SYSLABCC_DLLIMPORT double add_num_f64(
double x,
double y);
/* 省略后文... */
可以看到,此时还生成了一些其他的动态库,如 bdwgc.dll(0.4 MB以内),为生成代码提供兼容 Julia 的垃圾回收功能。
接下来,使用以下 test_add_num.cpp 文件,调用生成动态库中的函数 add_num_i64 和 add_num_f64,以验证 build.jl 生成的动态库 libadd.dll:
#include <iostream>
#include <stdint.h>
#include "libadd.h"
int main()
{
double af = 1.2;
double bf = 2.4;
std::cout << "add_num_f64(a = 1.2, b = 2.4) = " << add_num_f64(af, bf) << std::endl;
int64_t al = 2;
int64_t bl = 4;
std::cout << "add_num_i64(a = 2, b = 4) = " << add_num_i64(al, bl) << std::endl;
return 0;
}
# 使用 g++ 编译器验证动态库
注意
Linux 上低版本 g++ 编译器可用性问题
低版本 g++ 编译器可能存在 pthread 库链接失败等问题,此时可以使用 zig c++ 替换 g++ 命令。
Syslab 已预装 zig c++ 工具。
此外,在低版本的 C++ 编译器下您可能需要为 g++ 命令添加 -lpthread -std=c++11 选项,并将 -ladd 改为 -l:./libadd。
我们推荐升级 C++ 编译器到 GCC 7.5.0 以上版本,以方便代码生成充分利用硬件指令进行加速。
您可以选择熟悉的 C++ 编译器来编译 test_add_num.cpp 源文件。
此步骤需使用上一步骤中生成的动态库 libadd。
以 g++ 编译器为例来演示,不同编译器的命令可能存在差异。
g++ test_add_num.cpp -o test.exe -I. -L. -ladd -static-libgcc -static-libstdc++ '-Wl,-allow-multiple-definition' -std=c++11
.\test.exe
# Linux 命令:
# g++ test_add_num.cpp -o test -I"." -L"." -ladd -Wl,-allow-multiple-definition,-rpath=. -lpthread -std=c++11
# ./test
# 对于旧版本 GCC(如 GCC 4.8)可能需要使用下列的选项:
# (1) 增加 -lpthread -std=c++11 等选项
# (2) 将 -ladd 改为 -l:./libadd.<平台动态库后缀>
#
# 在 Linux 上使用旧版 GCC 示例:
# g++ test_add_num.cpp -o test -I"." -L"." -l:./libadd.so -lpthread -std=c++11
# ./test
add_num_f64(a = 1.2, b = 2.4) = 3.6
add_num_i64(a = 2, b = 4) = 6
可以验证动态库成功生成并被调用。
提示
Windows 系统上可使用 Syslab 预装的 g++ 编译器
如果 Windows 系统上没有可用的 g++ 编译器,可以在 <Syslab 安装目录>\Tools\SyslabCC\mingw64\bin 目录下找到 Syslab 预装的 g++ 编译器。将该路径添加到环境变量 PATH 中,即可使用。
# C++ 项目生成
上面介绍的可执行文件生成,与动态库生成,都是基于 C++ 项目生成功能的二次封装。
C++ 项目生成是 Syslab 代码生成工具提供的核心能力。当代码生成输出可执行文件或动态库时,在当前目录的 .syslabcc-cache 下同步创建相应名字的 C++ 项目。
如果在当前工作目录下完成了本文前面的演示,则此时 .syslabcc-cache 文件夹结构如下:
.syslabcc-cache/
main/
...
src/
main.cpp
Makefile
make.jl
libadd/
...
src/
libadd.cpp
libadd.h
Makefile
make.jl
Syslab 代码生成的一个关键功能是生成 C++ 项目到指定位置,但不立刻编译它。以上面的 libadd 动态库例子为例,可使用如下命令来生成项目:
# 文件名:build.jl
add_num(x, y) = x + y
# 使用 SyslabCC 时导出定义函数
@static if @isdefined(SyslabCC)
# 生成动态库, 导出 C 函数 add_num_i64 让 C++ 调用
# 其中,(Int, Int) 代表函数输入参数类型
SyslabCC.static_compile(
"add_num_i64", add_num, (Int64, Int64));
# C 函数名可以自定义,此处使用 add_num_f64,表示双精度浮点数加法
SyslabCC.static_compile(
"add_num_f64", add_num, (Float64, Float64));
end
scc build.jl -d myproj -o myproj -c --mode shared
此时,Syslab 代码生成生成 C++ 项目到 myproj 文件夹,但不立刻编译它。myproj 文件夹结构如下:
myproj/
...
src/
myproj.cpp
Makefile
make.jl
进入 myproj/ 目录,运行 make all -j 8 或 julia make.jl all 来构建项目。
提示
关于 Makefile
对于不清楚 Makefile 的 Windows 用户:直接使用
julia make.jl all构建项目。对于正在使用 Unix 开发工具链的高级用户:推荐使用传统的 Makefile 构建系统,获得更好的并行编译与定制化构建。
构建完成后,myproj/bin 目录中包含目标动态链接库。
# Windows 下 myproj/bin 目录
myproj/
bin/
myproj.dll
myproj.lib
bdwgc.dll
syslabcrt-dylib.lib
syslabcrt-io.lib
# Linux 下 myproj/bin 目录
myproj/
bin/
myproj.so
libbdwgc.so
libsyslabcrt-dylib.a
libsyslabcrt-io.a
对于在 scc --mode app 模式下生成的 C++ 项目,完成构建将在 myproj/bin 目录中输出目标可执行文件。
# 使用 Visual Studio 验证动态库
用户也可在 Windows 平台上使用 Visual Studio 验证动态库。
此处使用 Visual Studio 2022 演示验证,创建空项目,选择路径(此处为演示用路径),项目命名为 scc_test_add:
在解决方案头文件处右击进入添加>新建项,添加 libadd.h 头文件,内容如下:
#pragma once
#ifndef ADD_NUM_H
#define ADD_NUM_H
#include <stdint.h>
// 声明整数加法函数
extern "C" __declspec(dllimport) int64_t add_num_i64(int64_t x, int64_t y);
// 声明双精度浮点数加法函数
extern "C" __declspec(dllimport) double add_num_f64(double x, double y);
#endif // ADD_NUM_H
在解决方案源文件处右击进入添加>新建项,选择 C++ 文件,添加 test_add.cpp,内容如下:
#include <iostream>
#include <stdint.h>
#include "libadd.h"
int main()
{
double af = 1.2;
double bf = 2.4;
std::cout << "add_num_f64(a = 1.2, b = 2.4) = " << add_num_f64(af, bf) << std::endl;
int64_t al = 2;
int64_t bl = 4;
std::cout << "add_num_i64(a = 2, b = 4) = " << add_num_i64(al, bl) << std::endl;
return 0;
}
接下来我们将生成产物包括动态库依赖,复制到项目路径的子文件夹 lib 中:
# 查看复制后的 lib 文件夹
# C:\Test\ 为演示目录路径
cd "C:\Test\scc_test_add\lib"
文件结构如下:
...
├── lib
├── lib
├── bdwgc.dll
├── libadd.dll
└── libadd.lib
...
然后,我们进行项目配置,单击左上角栏目中的项目,选择属性,进入链接器> 常规,添加路径 $(ProjectDir)\lib 至 附加库目录:
同时,单击链接器下的输入进入附加依赖项,添加 libadd.lib 依赖:
为了在项目编译产物执行时成功调用动态库依赖,我们可以将动态库手动复制至生成路径中(不推荐),也可以在生成事件配置中加入复制动态库的命令:
确定此配置,生成解决方案,单击开始执行按钮,可以看到动态库被调用,输出结果:
由于使用 g++ 编译器验证 C++ 调用和 Visual Studio 验证效果类似,在其余文档中我们会主要使用 g++ 来演示 C++ 调用动态库。
# 提示
对于可执行文件和动态库的代码生成,有以下两点需要注意:
- 对于可执行文件的生成:无需特意添加用于动态库生成函数的
static_compile语句,但存在static_compile语句也不影响可执行文件的生成; - 对于多个
.jl文件的代码生成:可在scc命令中传入多个.jl文件;也可仅用scc命令作用于单文件,并通过 Julia 的include函数加载多个文件。