跳转至

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. 总结

核心回答

  1. 你的 vector_add 程序没有经过 SPIR-V
  2. 因为使用了 clCreateProgramWithSource()
  3. 直接路径:OpenCL C → LLVM IR → 机器码
  4. 什么时候使用 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(保护知识产权)
  • 高性能场景:考虑特化常量优化