跳转至

SPRIV demo

1. 示例程序

C++
/**
 * PoCL SPIR-V 编译路径对比示例
 * 
 * 展示三种不同的程序创建方式:
 * 1. 从 OpenCL C 源代码
 * 2. 从 LLVM IR (SPIR)
 * 3. 从 SPIR-V 二进制
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <CL/opencl.h>

// OpenCL C 内核源代码
const char *kernel_source =
"__kernel void vector_add(__global const float *A,\n"
"                         __global const float *B,\n"
"                         __global float *C,\n"
"                         const unsigned int n)\n"
"{\n"
"    int i = get_global_id(0);\n"
"    if (i < n) {\n"
"        C[i] = A[i] + B[i];\n"
"    }\n"
"}\n";

void print_compilation_path(const char *method) {
    printf("\n" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("编译方法: %s\n", method);
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}

int main(void) {
    cl_platform_id platform;
    cl_device_id device;
    cl_context context;
    cl_int err;

    // 获取平台和设备
    clGetPlatformIDs(1, &platform, NULL);
    clGetDeviceIDs(platform, CL_DEVICE_TYPE_DEFAULT, 1, &device, NULL);
    context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);

    // 检查设备扩展
    char extensions[4096];
    clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, sizeof(extensions), extensions, NULL);

    printf("\n");
    printf("═══════════════════════════════════════════════════\n");
    printf("  PoCL 编译路径对比示例\n");
    printf("═══════════════════════════════════════════════════\n");

    int supports_spir = strstr(extensions, "cl_khr_spir") != NULL;
    int supports_il = strstr(extensions, "cl_khr_il_program") != NULL;

    printf("\n设备支持情况:\n");
    printf("  ✓ OpenCL C 源代码编译\n");
    printf("  %s SPIR (LLVM IR) 二进制\n", supports_spir ? "✓" : "✗");
    printf("  %s SPIR-V 中间语言 (IL)\n", supports_il ? "✓" : "✗");

    // ================================================================
    // 方法 1: 从 OpenCL C 源代码创建 (最常用)
    // ================================================================
    print_compilation_path("方法 1: clCreateProgramWithSource (OpenCL C)");

    printf("\n编译流程:\n");
    printf("  OpenCL C Source\n");
    printf("       ↓ [Clang 前端]\n");
    printf("  LLVM IR (program.bc)\n");
    printf("       ↓ [LLVM 优化 Pass]\n");
    printf("  LLVM IR (优化后)\n");
    printf("       ↓ [LLVM 后端代码生成]\n");
    printf("  Native Code (kernel.so)\n");
    printf("       ↓ [dlopen/dlsym]\n");
    printf("  执行\n");

    cl_program program1 = clCreateProgramWithSource(
        context, 1, &kernel_source, NULL, &err);

    if (err == CL_SUCCESS) {
        printf("\n✓ 程序创建成功\n");
        printf("  - 使用 OpenCL C 源代码\n");
        printf("  - 编译时生成 LLVM IR\n");
        printf("  - 针对 x86-64 优化\n");

        // 编译
        err = clBuildProgram(program1, 1, &device, NULL, NULL, NULL);
        if (err == CL_SUCCESS) {
            printf("✓ 编译成功\n");
            printf("  - 生成的文件在: ~/.cache/pocl/kcache/\n");
            printf("  - program.bc: LLVM 位码\n");
            printf("  - kernel.so: x86-64 机器码\n");
        }
        clReleaseProgram(program1);
    }

    // ================================================================
    // 方法 2: 从 SPIR-V 创建 (OpenCL 2.1+, 如果支持)
    // ================================================================
    if (supports_il) {
        print_compilation_path("方法 2: clCreateProgramWithIL (SPIR-V)");

        printf("\n编译流程:\n");
        printf("  SPIR-V Binary (.spv)\n");
        printf("       ↓ [llvm-spirv -r 反向转换]\n");
        printf("  LLVM IR (program.bc)\n");
        printf("       ↓ [LLVM 优化 Pass]\n");
        printf("  LLVM IR (优化后)\n");
        printf("       ↓ [LLVM 后端代码生成]\n");
        printf("  Native Code (kernel.so)\n");
        printf("       ↓ [dlopen/dlsym]\n");
        printf("  执行\n");

        printf("\n注意事项:\n");
        printf("  - 需要预先生成 SPIR-V 文件\n");
        printf("  - 使用 llvm-spirv 工具转换\n");
        printf("  - 支持特化常量 (Specialization Constants)\n");
        printf("  - 适合跨平台二进制分发\n");

        printf("\n离线生成 SPIR-V 的步骤:\n");
        printf("  $ clang -cl-std=CL2.0 -emit-llvm -c kernel.cl -o kernel.bc\n");
        printf("  $ llvm-spirv kernel.bc -o kernel.spv\n");
        printf("  # 然后在程序中:\n");
        printf("  # cl_program prog = clCreateProgramWithIL(ctx, spirv_data, size, &err);\n");
    }

    // ================================================================
    // 显示实际的缓存路径
    // ================================================================
    printf("\n" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("编译产物缓存位置\n");
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    printf("\n目录结构:\n");
    printf("  ~/.cache/pocl/kcache/\n");
    printf("  └── {HASH}/                    # 程序构建哈希\n");
    printf("      ├── program.bc             # LLVM IR (从源或SPIR-V转换)\n");
    printf("      ├── program.spv            # SPIR-V (如使用IL)\n");
    printf("      ├── last_accessed          # 访问时间戳\n");
    printf("      └── {kernel_name}/         # 各个内核\n");
    printf("          └── {specialization}/  # 工作组配置\n");
    printf("              └── kernel.so      # 最终机器码\n");

    printf("\n文件格式说明:\n");
    printf("  program.bc  - LLVM 位码 (可用 llvm-dis 反汇编)\n");
    printf("  program.spv - SPIR-V 二进制 (可用 spirv-dis 反汇编)\n");
    printf("  kernel.so   - ELF 共享库 (可用 objdump 查看)\n");

    // ================================================================
    // 工具链说明
    // ================================================================
    printf("\n" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("相关工具\n");
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    printf("\nLLVM/Clang 工具:\n");
    printf("  clang        - OpenCL C 编译器\n");
    printf("  llvm-dis     - LLVM IR 反汇编器\n");
    printf("  llvm-as      - LLVM IR 汇编器\n");
    printf("  opt          - LLVM 优化器\n");
    printf("  llc          - LLVM 静态编译器\n");

    printf("\nSPIR-V 工具:\n");
    printf("  llvm-spirv   - LLVM IR ↔ SPIR-V 转换器\n");
    printf("  spirv-dis    - SPIR-V 反汇编器\n");
    printf("  spirv-as     - SPIR-V 汇编器\n");
    printf("  spirv-val    - SPIR-V 验证器\n");
    printf("  spirv-opt    - SPIR-V 优化器\n");

    printf("\n调试命令示例:\n");
    printf("  # 查看 LLVM IR\n");
    printf("  $ llvm-dis ~/.cache/pocl/kcache/.../program.bc\n");
    printf("\n");
    printf("  # 转换 LLVM IR → SPIR-V\n");
    printf("  $ llvm-spirv program.bc -o program.spv\n");
    printf("\n");
    printf("  # 转换 SPIR-V → LLVM IR\n");
    printf("  $ llvm-spirv -r program.spv -o program.bc\n");
    printf("\n");
    printf("  # 查看 SPIR-V\n");
    printf("  $ spirv-dis program.spv\n");
    printf("\n");
    printf("  # 查看内核符号\n");
    printf("  $ nm ~/.cache/pocl/kcache/.../kernel.so\n");
    printf("\n");
    printf("  # 反汇编内核代码\n");
    printf("  $ objdump -d ~/.cache/pocl/kcache/.../kernel.so\n");

    // ================================================================
    // 性能和适用场景对比
    // ================================================================
    printf("\n" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("使用场景对比\n");
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    printf("\n┌────────────┬─────────────┬─────────────┬──────────────┐\n");
    printf("│ 特性       │ OpenCL C    │ SPIR (IR)   │ SPIR-V       │\n");
    printf("├────────────┼─────────────┼─────────────┼──────────────┤\n");
    printf("│ 首次编译   │ 较慢        │ 快          │ 中等         │\n");
    printf("│ 运行时性能 │ 最佳        │ 最佳        │ 最佳         │\n");
    printf("│ 可移植性   │ 最佳        │ 一般        │ 很好         │\n");
    printf("│ 二进制大小 │ N/A         │ 中等        │ 较小         │\n");
    printf("│ 调试难度   │ 容易        │ 中等        │ 较难         │\n");
    printf("│ 知识产权   │ 源码可见    │ 部分保护    │ 较好保护     │\n");
    printf("│ 特化常量   │ ✗           │ ✗           │ ✓            │\n");
    printf("│ 跨驱动     │ ✓           │ 限制        │ ✓            │\n");
    printf("└────────────┴─────────────┴─────────────┴──────────────┘\n");

    printf("\n推荐使用场景:\n");
    printf("  OpenCL C:  开发调试、性能调优、简单应用\n");
    printf("  SPIR:      PoCL 内部使用、特定平台优化\n");
    printf("  SPIR-V:    产品发布、跨平台分发、知识产权保护\n");

    printf("\n");
    printf("═══════════════════════════════════════════════════\n");
    printf("  程序结束\n");
    printf("═══════════════════════════════════════════════════\n");
    printf("\n");

    clReleaseContext(context);
    return 0;
}

2. 输出结果

Bash
$ ./spirv_demo 

═══════════════════════════════════════════════════
  PoCL 编译路径对比示例
═══════════════════════════════════════════════════

设备支持情况:
   OpenCL C 源代码编译
   SPIR (LLVM IR) 二进制
   SPIR-V 中间语言 (IL)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
编译方法: 方法 1: clCreateProgramWithSource (OpenCL C)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

编译流程:
  OpenCL C Source
        [Clang 前端]
  LLVM IR (program.bc)
        [LLVM 优化 Pass]
  LLVM IR (优化后)
        [LLVM 后端代码生成]
  Native Code (kernel.so)
        [dlopen/dlsym]
  执行

 程序创建成功
  - 使用 OpenCL C 源代码
  - 编译时生成 LLVM IR
  - 针对 x86-64 优化
 编译成功
  - 生成的文件在: ~/.cache/pocl/kcache/
  - program.bc: LLVM 位码
  - kernel.so: x86-64 机器码

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
编译方法: 方法 2: clCreateProgramWithIL (SPIR-V)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

编译流程:
  SPIR-V Binary (.spv)
        [llvm-spirv -r 反向转换]
  LLVM IR (program.bc)
        [LLVM 优化 Pass]
  LLVM IR (优化后)
        [LLVM 后端代码生成]
  Native Code (kernel.so)
        [dlopen/dlsym]
  执行

注意事项:
  - 需要预先生成 SPIR-V 文件
  - 使用 llvm-spirv 工具转换
  - 支持特化常量 (Specialization Constants)
  - 适合跨平台二进制分发

离线生成 SPIR-V 的步骤:
  $ clang -cl-std=CL2.0 -emit-llvm -c kernel.cl -o kernel.bc
  $ llvm-spirv kernel.bc -o kernel.spv
  # 然后在程序中:
  # cl_program prog = clCreateProgramWithIL(ctx, spirv_data, size, &err);

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
编译产物缓存位置
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

目录结构:
  ~/.cache/pocl/kcache/
  └── {HASH}/                    # 程序构建哈希
      ├── program.bc             # LLVM IR (从源或SPIR-V转换)
      ├── program.spv            # SPIR-V (如使用IL)
      ├── last_accessed          # 访问时间戳
      └── {kernel_name}/         # 各个内核
          └── {specialization}/  # 工作组配置
              └── kernel.so      # 最终机器码

文件格式说明:
  program.bc  - LLVM 位码 (可用 llvm-dis 反汇编)
  program.spv - SPIR-V 二进制 (可用 spirv-dis 反汇编)
  kernel.so   - ELF 共享库 (可用 objdump 查看)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
相关工具
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

LLVM/Clang 工具:
  clang        - OpenCL C 编译器
  llvm-dis     - LLVM IR 反汇编器
  llvm-as      - LLVM IR 汇编器
  opt          - LLVM 优化器
  llc          - LLVM 静态编译器

SPIR-V 工具:
  llvm-spirv   - LLVM IR  SPIR-V 转换器
  spirv-dis    - SPIR-V 反汇编器
  spirv-as     - SPIR-V 汇编器
  spirv-val    - SPIR-V 验证器
  spirv-opt    - SPIR-V 优化器

调试命令示例:
  # 查看 LLVM IR
  $ llvm-dis ~/.cache/pocl/kcache/.../program.bc

  # 转换 LLVM IR → SPIR-V
  $ llvm-spirv program.bc -o program.spv

  # 转换 SPIR-V → LLVM IR
  $ llvm-spirv -r program.spv -o program.bc

  # 查看 SPIR-V
  $ spirv-dis program.spv

  # 查看内核符号
  $ nm ~/.cache/pocl/kcache/.../kernel.so

  # 反汇编内核代码
  $ objdump -d ~/.cache/pocl/kcache/.../kernel.so

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
使用场景对比
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌────────────┬─────────────┬─────────────┬──────────────┐
 特性        OpenCL C     SPIR (IR)    SPIR-V       ├────────────┼─────────────┼─────────────┼──────────────┤
 首次编译    较慢                    中等          运行时性能  最佳         最佳         最佳          可移植性    最佳         一般         很好          二进制大小  N/A          中等         较小          调试难度    容易         中等         较难          知识产权    源码可见     部分保护     较好保护      特化常量                                         跨驱动                  限制                     └────────────┴─────────────┴─────────────┴──────────────┘

推荐使用场景:
  OpenCL C:  开发调试、性能调优、简单应用
  SPIR:      PoCL 内部使用、特定平台优化
  SPIR-V:    产品发布、跨平台分发、知识产权保护

═══════════════════════════════════════════════════
  程序结束
═══════════════════════════════════════════════════