# 代码生成工作流
代码生成工作流说明
# 代码生成工程
代码生成工作流可以总结为如下流程:
# 生成可执行文件工作流
演示对于 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/: 其他外部动态库依赖目录