SPRIV支持分析
1. 什么情况下会使用 SPIR-V¶
C++
路径 1: 从源代码编译 (你的例子,直接在x86上,无显卡,使用线程库来实现)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OpenCL C Source (.cl)
↓ [Clang]
LLVM IR (program.bc)
↓ [LLVM优化 + 代码生成]
Native Code (vector_add.so)
↓ [dlopen]
执行
路径 2: 从 SPIR-V 编译 (使用 clCreateProgramWithIL)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SPIR-V Binary (.spv)
↓ [llvm-spirv -r] (反向转换)
LLVM IR (program.bc)
↓ [LLVM优化 + 代码生成]
Native Code (kernel.so)
↓ [dlopen]
执行
路径 3: 离线编译工具链
━━━━━━━━━━━━━━━━━━━━━
OpenCL C Source (.cl)
↓ [clang -emit-llvm]
LLVM IR (.bc)
↓ [llvm-spirv] (正向转换)
SPIR-V Binary (.spv)
↓ [分发到不同设备]
↓ [运行时: llvm-spirv -r]
LLVM IR (.bc)
↓ [设备特定优化]
Native Code (.so)
SPIR-V 在 PoCL 中的使用场景¶
根据配置文件,你的 PoCL 支持 SPIR-V:
C++
// build/config.h
#define ENABLE_SPIR // 支持 SPIR (老版本)
#define ENABLE_SPIRV // 支持 SPIR-V
#define LLVM_SPIRV "/home/ken/workspace/SPIRV-LLVM-Translator/install/bin/llvm-spirv"
2. 使用场景¶
2.1 场景 1: 使用 clCreateProgramWithIL (OpenCL 2.1+)¶
C++
// 从 SPIR-V 二进制创建程序
const unsigned char spirv_binary[] = { /* SPIR-V 字节码 */ };
size_t spirv_size = sizeof(spirv_binary);
cl_int err;
cl_program program = clCreateProgramWithIL(
context,
spirv_binary,
spirv_size,
&err);
// 编译程序
clBuildProgram(program, 1, &device, NULL, NULL, NULL);
实现路径(clCreateProgramWithIL.c):
C++
cl_program clCreateProgramWithIL(...)
{
// 1. 验证是 SPIR-V
int is_spirv = pocl_bitcode_is_spirv_execmodel_kernel(il, length);
// 2. 保存 SPIR-V 到 program->program_il
program->program_il = malloc(length);
memcpy(program->program_il, il, length);
program->program_il_size = length;
// 3. 写入临时文件用于分析
pocl_cache_write_spirv(program_bc_spirv, il, length);
// 4. 获取特化常量信息
get_program_spec_constants(program, program_bc_spirv);
return program;
}
编译时转换(common_driver.c):
C++
static int pocl_regen_spirv_binary(cl_program program, cl_uint device_i)
{
// 使用 llvm-spirv 工具转换 SPIR-V → LLVM IR
char *args[] = {
LLVM_SPIRV, // /path/to/llvm-spirv
"--spec-const=...", // 特化常量
"--spirv-target-env=CL2.0", // 目标环境
"-r", // 反向转换 (reverse)
"-o", unlinked_program_bc, // 输出 .bc 文件
program_bc_spirv, // 输入 .spv 文件
NULL
};
pocl_run_command(args);
// 加载生成的 LLVM IR
pocl_reload_program_bc(unlinked_program_bc, program, device_i);
}
2.2 场景 2: 特化常量 (Specialization Constants)¶
SPIR-V 的一个重要特性:
C++
// OpenCL C with specialization constant
kernel void my_kernel() {
const int WORKGROUP_SIZE __attribute__((spec_constant_id = 0)) = 64;
// 使用 WORKGROUP_SIZE...
}
// 运行时设置
clSetProgramSpecializationConstant(program, 0, sizeof(int), &new_size);
实现:
C++
// 查询特化常量
llvm-spirv --spec-const-info program.spv
// 输出: Spec const id = 0, size in bytes = 4
// 应用特化常量
llvm-spirv --spec-const="0:i32:256" -r -o program.bc program.spv
2.3 场景 3: 跨平台二进制分发¶
SPIR-V 作为中间表示,允许:
C++
开发者 (x86-64) 用户设备 1 (ARM) 用户设备 2 (GPU)
↓ ↓ ↓
编译一次 llvm-spirv -r llvm-spirv -r
↓ ↓ ↓
.spv 文件 ────────→ ARM 机器码 GPU 着色器
(通用)
2.4 场景 4: Vulkan 互操作¶
C++
// Vulkan 计算着色器也使用 SPIR-V
#ifdef ENABLE_VULKAN
int is_spirv_shader =
pocl_bitcode_is_spirv_execmodel_shader(il, length);
#endif
3. 代码中的关键实现¶
3.1. SPIR-V 检测¶
C++
// lib/CL/devices/common_driver.c:625
if (program->program_il && program->program_il_size > 0)
{
// 检查是否支持 SPIR
if (!strstr(device->extensions, "cl_khr_spir")) {
return CL_LINK_PROGRAM_FAILURE;
}
// 转换 SPIR-V → LLVM IR
pocl_regen_spirv_binary(program, device_i);
}
3.2. 缓存策略¶
C++
// SPIR-V 的缓存包含特化常量
pocl_cache_create_program_cachedir(
program, device_i,
(char *)program->program_il, // SPIR-V 内容
program->program_il_size, // SPIR-V 大小
program_bc_path);
// 哈希值包含:
// - SPIR-V 内容
// - 特化常量值
// - 设备特性
// - 编译选项
4. 总结¶
核心回答¶
- 你的 vector_add 程序没有经过 SPIR-V
- 因为使用了
clCreateProgramWithSource() - 直接路径:OpenCL C → LLVM IR → 机器码
- 什么时候使用 SPIR-V?
三种主要场景¶
4.1 产品发布和知识产权保护¶
Bash
# 开发阶段 - 生成 SPIR-V
clang -cl-std=CL2.0 -emit-llvm -c kernel.cl -o kernel.bc
llvm-spirv kernel.bc -o kernel.spv
# 用户端 - 从 SPIR-V 加载
cl_program prog = clCreateProgramWithIL(context, spirv_data, size, &err);
优势:
- ✅ 隐藏源代码
- ✅ 跨平台二进制
- ✅ 更小的文件大小
4.2 场景 B: 特化常量 (Specialization Constants)¶
C++
// SPIR-V 独有特性
__attribute__((spec_constant_id = 0)) int TILE_SIZE = 16;
// 运行时动态设置
clSetProgramSpecializationConstant(prog, 0, sizeof(int), &new_size);
优势:
- ✅ 运行时优化
- ✅ 无需重新编译
- ✅ 多个配置共用一个二进制
4.3 场景 C: Vulkan 互操作¶
C++
// 同一个 SPIR-V 可用于 OpenCL 和 Vulkan
#ifdef ENABLE_VULKAN
pocl_bitcode_is_spirv_execmodel_shader(il, length);
#endif
编译路径对比图¶
C++
你的程序 (OpenCL C):
┌─────────────────┐
│ kernel.cl │ OpenCL C 源代码
└────────┬────────┘
│ clang
↓
┌─────────────────┐
│ program.bc │ LLVM IR (36 KB)
└────────┬────────┘
│ LLVM优化
↓
┌─────────────────┐
│ kernel.so │ x86-64 机器码 (14 KB)
└─────────────────┘
使用 SPIR-V 时:
┌─────────────────┐
│ kernel.cl │
└────────┬────────┘
│ clang -emit-llvm
↓
┌─────────────────┐
│ kernel.bc │
└────────┬────────┘
│ llvm-spirv
↓
┌─────────────────┐
│ kernel.spv │ SPIR-V 二进制 (可分发)
└────────┬────────┘
│ llvm-spirv -r (运行时)
↓
┌─────────────────┐
│ program.bc │ LLVM IR
└────────┬────────┘
│ LLVM后端
↓
┌─────────────────┐
│ kernel.so │ 目标设备机器码
└─────────────────┘
实用建议¶
- 开发阶段:用 OpenCL C 源代码(方便调试)
- 测试阶段:可以测试 SPIR-V 路径(验证兼容性)
- 产品发布:使用 SPIR-V(保护知识产权)
- 高性能场景:考虑特化常量优化