# 代码生成工作流


代码生成工作流说明

# 代码生成工程

代码生成工作流可以总结为如下流程:

# 生成可执行文件工作流

演示对于 Julia 代码生成可执行文件的工作流。

# 新建 Julia 工程

打开 Syslab,打开工作文件夹,此处演示以 Test 文件夹为例:

序号 名称 说明
终端创建选择 用于创建新终端;选择不同终端环境
代码生成编译环境 用于 Syslab 代码生成

如果是第一次使用 scc 命令行工具,可以在终端中使用 scc -h 验证配置是否正确。

# 编写 Julia 脚本

新建 main.jl 输入以下代码,此代码主要为演示工作流使用:

global x = 5

function draw_shapes()
    # 绘制菱形
    println("\n菱形:")
    for i in 1:x
        println(" "^(x - i) * "* "^(i))
    end
    for i in 4:-1:1
        println(" "^(x - i) * "* "^(i))
    end
end

draw_shapes()

在 Syslab 中运行我们的函数:

# 编译配置

接下来我们对此 main.jl 代码进行代码生成。

# 确定编译模式

此处演示生成可执行文件,将 main.jl 生成为 main.exe ,预期执行生成的 main.exe 二进制文件可得到与 Julia 代码运行类似的输出。

提示

  • 对于打印结果,SyslabCC 编译产物仅关注表意清楚,但不追求与 Julia 程序的打印结果保持一致
  • 对于计算结果,SyslabCC 编译产物与 Julia 程序保持一致

# 入口函数定义

scc 命令行工具默认为生成可执行文件模式,也可通过 --mode app 指定。指定可执行模式生成的 Julia 代码必须包括 main() 函数定义。

提示

@isdefined(SyslabCC) 在使用 scc 编译时会返回 true,在 Julia 编程环境中运行时返回 false ,故我们可以用这个判断语句约束代码作用环境。

因此我们修改 main.jl 为如下形式,这样不影响代码生成,也便于在 Julia 编程环境中调试 main() 函数:

global x = 5

function draw_shapes()
    # 绘制菱形
    println("\n菱形:")
    for i in 1:x
        println(" "^(x - i) * "* "^(i))
    end
    for i in 4:-1:1
        println(" "^(x - i) * "* "^(i))
    end
end

function main()
    draw_shapes()
end

@static @isdefined(SyslabCC) || main()

# 配置编译选项

预期在当前目录生成 main.exe ,故我们设置编译命令为:

# Windows 下在可执行文件生成模式时,代码生成会自动为输出补充 .exe 后缀
scc main.jl -o main --bundle --static-mingw
# Linux 命令
# scc main.jl -o main --bundle

# 代码生成

输入以上命令,进行代码生成。

但此处 global 变量不支持代码生成,因为 global 变量可以随时修改类型,导致代码生成无法推断其具体类型。

此时编译器会报错,提示此处存在非 const 的全局变量:

...
Compiler Error(4): non-constant global variable `x`
in the body of typeof(draw_shapes), with () as its arguments type
    File <path>\main.jl, line 6, in Main.draw_shapes

in the body of typeof(main), with () as its arguments type
    File <path>\main.jl, line 15, in Main.main

# 验证产物

由于代码生成失败,输出路径下不会有代码生成产物,接下来我们调试迭代 main.jl 代码,以支持代码生成。

# 调试迭代

根据 Compiler Error 报错信息,我们定位到错误来源于 draw_shapes 函数中第 6 行调用的 x ,其中x为非 const 全局变量。

我们需要将 x 定义为 const 全局变量以支持代码生成,可以将 main.jl 中的原代码修改为:

const x = 5

function draw_shapes()
    # 绘制菱形
    println("\n菱形:")
    for i in 1:x
        println(" "^(x - i) * "* "^(i))
    end
    for i in (x-1):-1:1
        println(" "^(x - i) * "* "^(i))
    end
end

function main()
    draw_shapes()
end

@static @isdefined(SyslabCC) || main()

如果需要定义可变的 const 全局变量,请参见使用 Ref 定义可变的 const 全局变量

此时并不需要修改编译选项,我们继续使用如下命令编译。

scc main.jl -o main --bundle --static-mingw

提示

--static-mingw 选项

此选项用于静态链接 MinGW 运行时,以避免 Windows 程序在其他机器下缺少动态链接库的问题。

可以观察到 main.exe 等产物被生成,生成的文件结构如下:

    .syslabcc-cache/
    lib/
    bdwgc.dll
    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

可以在当前目录测试可执行文件:

# 部署

接下来可以将可执行文件转移至生成产物部署环境中应用。由于 Julia 代码生成产物依赖外部动态库,需确保使用 --bundle 编译选项,将所有需要的动态库依赖收集至 lib 目录下,以便部署生成产物到没有这些外部库的环境中。

使用 --bundle 选项将打包自包含版本的代码生成产物,动态库依赖将被自动收集到 lib 目录中:

接下来将生成产物打包转移至部署环境中,即完成部署。

需要打包的生成产物有:

  • main.exe: 目标可执行文件
  • bdwgc.dll: 目标可执行文件的核心依赖
  • lib/: 其他外部动态库依赖目录

# 生成动态库工作流

演示对于 Julia 代码生成动态库的工作流。

# 新建 Julia 工程

打开 Syslab,打开文件夹,此处演示打开 Test 文件夹:

序号 名称 说明
终端创建选择 用于创建新终端;选择不同终端环境
代码生成编译环境 用于 Syslab 代码生成

如果是第一次使用 scc 命令行工具,可以在终端中使用 scc -h 验证配置是否正确。

# 编写 Julia 脚本

此处演示使用蒙特卡洛方法计算 ,接下来我们将编写 monte_carlo_pi_serial 函数,并将其导出为动态库,然后使用 Python 调用生成的动态库来测试。

新建 build.jl 输入以下代码,此代码主要为演示工作流使用:

using Random
using TyMath

const _mrng = MT19937ar()

function darts_in_circle(n, rng=_mrng)
    inside = 0
    for i in 1:n
        # 随机生成点
        if rand(rng)^2 + rand(rng)^2 < 1
            inside += 1
        end
    end
    return inside
end

function monte_carlo_pi_serial(n)
    return 4 * darts_in_circle(n) / n
end

monte_carlo_pi_serial(1000000)

我们测试此代码:

# 编译配置

接下来,我们希望能将 monte_carlo_pi_serial 函数导出为动态库中函数。

# 确定编译模式

此处演示生成动态库,将 build.jl 生成为 libmonte_carlo_pi.dll,预期生成的动态库可被 Python 调用验证。

# 入口函数定义

动态库导出函数格式的形同 SyslabCC.static_compile("导出函数名", 待导出函数, (参数1类型, 参数2类型, ...)),在生成的动态库中导出函数名和导出函数及其类型组合对应。

build.jl 修改为以下代码:

using Random
using TyMath

const _mrng = MT19937ar()

function darts_in_circle(n, rng=_mrng)
    inside = 0
    for i in 1:n
        # 随机生成点
        if rand(rng)^2 + rand(rng)^2 < 1
            inside += 1
        end
    end
    return inside
end

function monte_carlo_pi_serial(n)
    return 4 * darts_in_circle(n) / n
end

@static if @isdefined(SyslabCC)
    SyslabCC.static_compile("monte_carlo_pi_serial", monte_carlo_pi_serial, (Int64,))
end

提示

@isdefined(SyslabCC) 在使用 scc 编译时返回 true,在 Julia 编程环境中运行时为 false 。我们可以用这个判断语句约束代码作用环境。

# 配置编译选项

预期在当前目录生成 libmonte_carlo_pi.dll ,故我们设置编译命令为:

scc build.jl -o libmonte_carlo_pi.dll --bundle
# Linux 命令:
# scc build.jl -o libmonte_carlo_pi.so --bundle

# 代码生成

输入以上命令,进行代码生成。

# 验证产物

可以看到动态库和其它编译产物被成功生成:

    .syslabcc-cache/
    lib/
    bdwgc.dll
    build.jl
    libmonte_carlo_pi.dll
    libmonte_carlo_pi.lib
    syslabcrt-dylib.lib
    syslabcrt-io.lib

Linux 平台上生成产物后缀会有一定差异:

...
    .syslabcc-cache/
    lib/
    libbdwgc.so
    build.jl
    libmonte_carlo_pi.so
    libmonte_carlo_pi.lib
    libsyslabcrt-dylib.a
    libsyslabcrt-io.a
...

产物正常生成而不需要调试迭代修改代码,接下来我们使用 Python 脚本验证动态库。

在同级文件夹下创建如下 Python 脚本 test.py,调用动态库函数进行计算:

import ctypes
import platform
import os
import pathlib

if platform.system() == "Windows":
    # Windows 下加载动态库
    lib_path = pathlib.Path(os.path.join(os.path.dirname(__file__), "libmonte_carlo_pi.dll")).absolute()
    lib = ctypes.cdll.LoadLibrary(str(lib_path))
else:
    # Linux 下加载动态库:
    lib_path = pathlib.Path(os.path.join(os.path.dirname(__file__), "libmonte_carlo_pi.so")).absolute()
    lib = ctypes.cdll.LoadLibrary(str(lib_path))

# 定义函数签名
lib.monte_carlo_pi_serial.argtypes = (ctypes.c_int64,)
lib.monte_carlo_pi_serial.restype = ctypes.c_double

# 调用函数并输出结果
n = 1000000
result = lib.monte_carlo_pi_serial(n)
print("Monte Carlo Pi (Serial) =", result)

运行 Python 脚本,可以看到动态库正常被调用。

# 部署

接下来可以将动态库转移至生成产物部署环境中应用。由于 Julia 代码生成产物依赖外部动态库,在代码生成时确保使用 --bundle 编译选项,将所有需要的动态库依赖收集至 lib 目录下,以便部署生成产物到没有这些外部库的环境中。

接下来将生成产物打包转移至部署环境中,即完成部署。

需要打包的生成产物有:

  • libmonte_carlo_pi.dll: 目标动态链接库
  • bdwgc.dll: 目标动态链接库的核心依赖
  • lib/: 其他外部动态库依赖目录