# 快速入门


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 用户需要保证系统中存在 gccg++ 编译器,可通过 gcc --versiong++ --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 mainscc 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" --bundleC:\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_i64add_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 8julia 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 函数加载多个文件。