SPIR-V 目标用户指南

简介

SPIR-V 目标为 官方 SPIR-V 规范中描述的 SPIR-V 二进制格式提供代码生成。

用法

SPIR-V 后端可以从 LLVM 的静态编译器 (llc) 或 Clang 调用,允许开发人员将 LLVM 中间语言 (IL) 文件或 OpenCL 内核源直接编译为 SPIR-V。本节概述了利用 SPIR-V 后端用于不同目的的各种命令的用法。

静态编译器命令

  1. 基本 SPIR-V 编译 命令:llc -mtriple=spirv32-unknown-unknown input.ll -o output.spvt 描述:此命令将 LLVM IL 文件 (input.ll) 编译为 32 位架构的 SPIR-V 二进制文件 (output.spvt)。

  2. 使用扩展和优化的编译 命令:llc -O1 -mtriple=spirv64-unknown-unknown –spirv-ext=+SPV_INTEL_arbitrary_precision_integers input.ll -o output.spvt 描述:使用 (-O1) 优化将 LLVM IL 文件编译为 SPIR-V,目标为 64 位架构。它启用了 SPV_INTEL_arbitrary_precision_integers 扩展。

  3. 编译时支持实验性的 NonSemantic.Shader.DebugInfo.100 命令:llc –spv-emit-nonsemantic-debug-info –spirv-ext=+SPV_KHR_non_semantic_info input.ll -o output.spvt 描述:使用额外的 NonSemantic.Shader.DebugInfo.100 指令将 LLVM IL 文件编译为 SPIR-V。它启用了必需的 SPV_KHR_non_semantic_info 扩展。

  4. SPIR-V 二进制生成 命令:llc -O0 -mtriple=spirv64-unknown-unknown -filetype=obj input.ll -o output.spvt 描述:从 LLVM 模块生成 SPIR-V 对象文件 (output.spvt),目标为 64 位 SPIR-V 架构,不进行优化。

Clang 命令

  1. SPIR-V 生成 命令:clang –target=spirv64 input.cl 描述:直接从 OpenCL 内核源文件 (input.cl) 生成 SPIR-V 文件。

编译器选项

目标三元组

对于交叉编译到 SPIR-V,请使用选项

-target <架构><子架构>-<供应商>-<操作系统>-<环境>

来指定目标三元组

表 120 SPIR-V 架构

架构

描述

spirv32

具有 32 位指针宽度的 SPIR-V。

spirv64

具有 64 位指针宽度的 SPIR-V。

spirv

具有逻辑内存布局的 SPIR-V。

表 121 SPIR-V 子架构

子架构

描述

<空>

SPIR-V 版本由后端根据输入推断。

v1.0

SPIR-V 版本 1.0。

v1.1

SPIR-V 版本 1.1。

v1.2

SPIR-V 版本 1.2。

v1.3

SPIR-V 版本 1.3。

v1.4

SPIR-V 版本 1.4。

v1.5

SPIR-V 版本 1.5。

v1.6

SPIR-V 版本 1.6。

表 122 SPIR-V 供应商

供应商

描述

<空>/unknown

不带任何供应商特定设置的通用 SPIR-V 目标。

amd

AMDGCN SPIR-V 目标,支持特定于目标的内置函数和 ASM,旨在供 AMDGCN 工具链使用。

表 123 操作系统

操作系统

描述

<空>/unknown

默认为 OpenCL 运行时。

vulkan

Vulkan 着色器运行时。

vulkan1.2

Vulkan 1.2 运行时,对应于 SPIR-V 1.5。

vulkan1.3

Vulkan 1.3 运行时,对应于 SPIR-V 1.6。

amdhsa

AMDHSA 运行时,旨在用于 HSA 兼容运行时,对应于 SPIR-V 1.6。

表 124 SPIR-V 环境

环境

描述

<空>/unknown

OpenCL 环境或由后端根据输入推断。

示例

-target spirv64v1.0 可用于编译具有 64 位指针宽度的 SPIR-V 版本 1.0。

-target spirv64-amd-amdhsa 可用于编译具有 64 位指针宽度的 AMDGCN 风味的 SPIR-V。

扩展

SPIR-V 后端支持各种 扩展,这些扩展启用或增强了超出核心 SPIR-V 规范的功能。可以使用 -spirv-extensions 选项后跟您希望启用的扩展的名称来启用这些扩展。以下是支持的 SPIR-V 扩展列表,按扩展名称的字母顺序排序

表 125 支持的 SPIR-V 扩展

扩展名称

描述

SPV_EXT_arithmetic_fence

添加一条指令,以防止其参数与其包含的表达式之间的快速数学优化。

SPV_EXT_demote_to_helper_invocation

添加一条指令,将片段着色器调用降级为辅助调用。

SPV_EXT_optnone

为函数控制掩码添加 OptNoneEXT 值,指示不优化函数的请求。

SPV_EXT_shader_atomic_float16_add

扩展 SPV_EXT_shader_atomic_float_add 扩展以支持原子地加到内存中的 16 位浮点数。

SPV_EXT_shader_atomic_float_add

在浮点数上添加原子加法指令。

SPV_EXT_shader_atomic_float_min_max

在浮点数上添加原子最小值和最大值指令。

SPV_INTEL_arbitrary_precision_integers

允许生成任意宽度的整数类型。

SPV_INTEL_bindless_images

添加指令以将无符号整数句柄转换为图像、采样器和采样图像。

SPV_INTEL_bfloat16_conversion

添加指令以在单精度 32 位浮点值和 16 位 bfloat16 值之间进行转换。

SPV_INTEL_cache_controls

允许将缓存控制信息应用于内存访问指令。

SPV_INTEL_float_controls2

添加执行模式和修饰符以控制浮点计算。

SPV_INTEL_function_pointers

允许转换函数指针。

SPV_INTEL_inline_assembly

允许使用内联汇编。

SPV_INTEL_global_variable_host_access

添加可以应用于全局(模块范围)变量的修饰符。

SPV_INTEL_global_variable_fpga_decorations

添加可以应用于全局(模块范围)变量的修饰符,以帮助为 FPGA 设备生成代码。

SPV_INTEL_media_block_io

添加额外的子组块读取和写入功能,允许应用程序灵活地指定要从 2D 图像读取或写入的块的宽度和高度。

SPV_INTEL_memory_access_aliasing

添加指令和修饰符以指定内存访问别名,类似于 alias.scope 和 noalias LLVM 元数据。

SPV_INTEL_optnone

为函数控制掩码添加 OptNoneINTEL 值,指示不优化函数的请求。

SPV_INTEL_split_barrier

添加 SPIR-V 指令以将控制屏障拆分为两个独立的操作:第一个指示调用已“到达”屏障但应继续执行,第二个指示调用应“等待”其他调用到达屏障后再进一步执行。

SPV_INTEL_subgroups

允许子组中的工作项共享数据,而无需使用本地内存和工作组屏障,并利用专用硬件从图像或缓冲区加载和存储数据块。

SPV_INTEL_usm_storage_classes

引入了两个新的存储类,它们是 CrossWorkgroup 存储类的子类,提供额外的可以启用优化的信息。

SPV_INTEL_variable_length_array

允许分配本地数组,其元素数量在编译时未知。

SPV_INTEL_joint_matrix

在 SPV_KHR_cooperative_matrix 扩展之上添加了一些矩阵功能,例如矩阵预取、获取元素坐标和检查的加载/存储/构造指令、张量浮点 32 和 bfloat 类型解释用于乘加指令。

SPV_KHR_bit_instructions

使 SPIR-V 模块可以使用位指令,而无需 Shader 能力。

SPV_KHR_expect_assume

向编译器提供额外信息,类似于 llvm.assume 和 llvm.expect 内联函数。

SPV_KHR_float_controls

提供新的执行模式来控制浮点计算,方法是覆盖实现对舍入模式、非规范化数、有符号零和无穷大的默认行为。

SPV_KHR_integer_dot_product

为整数向量(带有可选累加)上的点积运算添加指令。整数向量包括 8 位整数的 4 分量向量和打包到 32 位整数中的 8 位整数的 4 分量向量。

SPV_KHR_linkonce_odr

允许使用 LinkOnceODR 链接类型,该类型允许在发生链接时将函数或全局变量与同名的其他函数或全局变量合并。

SPV_KHR_no_integer_wrap_decoration

添加修饰符以指示给定指令不会导致整数环绕。

SPV_KHR_shader_clock

添加扩展 cl_khr_kernel_clock,该扩展添加了内核从计算单元提供的时钟中采样值的能力。

SPV_KHR_subgroup_rotate

添加了一条新指令,该指令允许在子组内的调用之间旋转值。

SPV_KHR_uniform_group_instructions

允许在统一控制流中支持额外的组操作。

SPV_KHR_non_semantic_info

添加声明对语义没有影响并且可以安全地从模块中删除的扩展指令集的能力。

要启用多个扩展,请用逗号分隔列出它们。例如,要启用对浮点数和任意精度整数的原子操作的支持,请使用

-spirv-ext=+SPV_EXT_shader_atomic_float_add,+SPV_INTEL_arbitrary_precision_integers

要启用所有扩展,请使用以下选项: -spirv-ext=all

要启用除指定扩展之外的所有扩展,请指定 all,后跟不允许的扩展列表。例如: -spirv-ext=all,-SPV_INTEL_arbitrary_precision_integers

LLVM IR 中 SPIR-V 的表示

SPIR-V 经过有意设计,可与各种中间表示 (IR)(包括 LLVM IR)无缝集成,从而为大多数实体提供直接映射。SPIR-V 后端的开发以与 Khronos Group SPIR-V LLVM 转换器兼容为原则。因此,SPIR-V 后端接受的输入表示与 LLVM 文档中的 SPIR-V 表示中详细描述的表示非常一致。本文档以及后续章节描述了要点,并重点介绍了此后端处理的 LLVM IR 与其他工具使用的约定之间的任何差异。

特殊类型

SPIR-V 指定了几种不透明类型。这些类型使用目标扩展类型表示,表示如下

表 126 SPIR-V 不透明类型

SPIR-V 类型

LLVM 类型名称

LLVM 类型参数

OpTypeImage

spirv.Image

采样类型、维度、深度、数组化、MS、采样、图像格式、[访问限定符]

OpTypeSampler

spirv.Sampler

(无)

OpTypeSampledImage

spirv.SampledImage

采样类型、维度、深度、数组化、MS、采样、图像格式、[访问限定符]

OpTypeEvent

spirv.Event

(无)

OpTypeDeviceEvent

spirv.DeviceEvent

(无)

OpTypeReserveId

spirv.ReserveId

(无)

OpTypeQueue

spirv.Queue

(无)

OpTypePipe

spirv.Pipe

访问限定符

OpTypePipeStorage

spirv.PipeStorage

(无)

所有整数参数的值与其在 相应的 SPIR-V 指令中的值相同。例如,OpenCL 类型 image2d_depth_ro_t 在 SPIR-V IR 中将表示为 target("spirv.Image", void, 1, 1, 0, 0, 0, 0, 0),其维度参数为 1,表示 2D。采样图像类型包括其底层图像类型的参数,因此前一种类型的采样图像的表示形式为 target("spirv.SampledImage, void, 1, 1, 0, 0, 0, 0, 0)

目标内联函数

SPIR-V 后端采用多个 LLVM IR 内联函数,这些内联函数有助于生成正确且高效的 SPIR-V 代码所需的各种低级操作。这些内联函数涵盖从类型分配和内存管理到控制流和原子操作的一系列功能。下面是 SPIR-V 后端中使用的选定内联函数的详细表,以及它们的描述和参数详细信息。

表 127 用于 SPIR-V 的 LLVM IR 内联函数

内联函数 ID

返回类型

参数类型

描述

int_spv_assign_type

[类型,元数据]

将类型与元数据关联,这对于在 SPIR-V 结构中维护类型信息至关重要。不直接发出,但内部支持类型系统。

int_spv_assign_ptr_type

[类型,元数据,整数]

类似于 int_spv_assign_type,但用于指针类型,附加整数指定存储类。支持 SPIR-V 的详细指针类型系统。不直接发出。

int_spv_assign_name

[类型,Vararg]

为类型或值分配名称,增强 SPIR-V 代码的可读性和可调试性。不直接发出,但用于元数据丰富。

int_spv_value_md

[元数据]

将一组属性(例如名称和数据类型)分配给作为关联的 llvm.fake.use 内联函数调用的参数的值。后者用作将 IRTranslator 创建的虚拟寄存器映射到原始值的方法。

int_spv_assign_decoration

[类型,元数据]

通过将修饰符与元数据关联来为值分配修饰符。不直接发出,但用于支持 LLVM IR 中的 SPIR-V 表示。

int_spv_assign_aliasing_decoration

[类型,32 位整数,元数据]

使用原始别名元数据节点,为指令分配两个内存别名修饰符之一(由第二个参数指定)。不直接发出,但用于支持 LLVM IR 中的 SPIR-V 表示。

int_spv_track_constant

类型

[类型,元数据]

跟踪 SPIR-V 模块中的常量。对于优化和减少冗余至关重要。仅供内部使用而发出。

int_spv_init_global

[类型,类型]

初始化全局变量,这是确保 SPIR-V 中正确的全局状态管理的必要步骤。仅供内部使用而发出。

int_spv_unref_global

[类型]

通过将全局变量标记为未引用来管理全局变量的生命周期,从而启用与全局变量使用相关的优化。仅供内部使用而发出。

int_spv_gep

指针

[布尔值,类型,Vararg]

计算聚合类型的子元素的地址。对于访问数组元素和结构字段至关重要。支持以通用方式有条件地寻址元素。

int_spv_load

32 位整数

[指针,16 位整数,8 位整数]

从内存位置加载值。附加整数指定内存访问和对齐详细信息,这对于确保正确和高效的内存操作至关重要。

int_spv_store

[类型,指针,16 位整数,8 位整数]

将值存储到内存位置。与 int_spv_load 类似,它包括内存访问和对齐的规范,这对于内存操作至关重要。

int_spv_extractv

类型

[32 位整数,Vararg]

从向量中提取值,允许在 SPIR-V 中进行向量操作。启用向量分量的操作。

int_spv_insertv

32 位整数

[32 位整数,类型,Vararg]

将值插入向量。作为 int_spv_extractv 的补充,它有助于向量的构造和操作。

int_spv_extractelt

类型

[类型,任何整数]

基于索引从聚合类型中提取元素。对于数组和向量上的操作至关重要。

int_spv_insertelt

类型

[类型,类型,任何整数]

将元素插入指定索引处的聚合类型。允许构建和修改数组和向量。

int_spv_const_composite

类型

[Vararg]

从给定元素构造复合类型。从单个组件创建数组、结构和向量的关键。

int_spv_bitcast

类型

[类型]

在类型之间执行按位转换。对于不更改位表示的类型转换至关重要。

int_spv_ptrcast

类型

[类型,元数据,整数]

在不同类型之间转换指针。类似于 int_spv_bitcast,但专门用于指针,同时考虑了 SPIR-V 的严格类型系统。

int_spv_switch

[类型,Vararg]

根据值实现多路分支。启用复杂的控制流结构,类似于高级语言中的 switch 语句。

int_spv_cmpxchg

32 位整数

[类型,Vararg]

执行原子比较和交换操作。对于计算着色器中的同步和并发控制至关重要。

int_spv_unreachable

[]

标记代码中永远不应到达的点,通过指示无法访问的代码路径来启用优化。

int_spv_alloca

类型

[]

在堆栈上分配内存。函数中局部变量存储的基础。

int_spv_alloca_array

类型

[任何整数]

在堆栈上分配数组。扩展 int_spv_alloca 以支持数组分配,这对于临时数组至关重要。

int_spv_undef

32 位整数

[]

生成未定义的值。对于优化和指示未初始化的变量很有用。

int_spv_inline_asm

[元数据,元数据,Vararg]

通过创建元数据并保留原始参数,将内联汇编功能与内联汇编调用实例关联。不直接发出,但用于支持 LLVM IR 中的 SPIR-V 表示。

int_spv_assume

[1 位整数]

向优化器提供有关可以对程序状态进行的假设的提示。提高优化潜力。

int_spv_expect

任何整数类型

[类型,类型]

通过指示预期的分支路径来指导分支预测。通过优化常见代码路径来提高性能。

int_spv_thread_id

32 位整数

[32 位整数]

检索工作组内的线程 ID。对于识别并行计算操作中的执行上下文至关重要。

int_spv_create_handle

指针

[8 位整数]

为图形或计算资源创建资源句柄。有助于着色器中资源的管理和使用。

int_spv_resource_handlefrombinding

spirv.Image

[32 位整数集,32 位整数绑定,32 位整数 arraySize,32 位整数索引,bool isUniformIndex]

返回给定集合和绑定的资源的句柄。如果 arraySize > 1,则绑定表示给定大小的资源数组,并返回给定索引处的资源的句柄。如果索引可能不统一,则必须将 isUniformIndex 设置为 true。

int_spv_typeBufferLoad

标量或向量

[spirv.Image ImageBuffer,32 位整数坐标]

从给定坐标处的 Vulkan 图像缓冲区加载值。假定图像缓冲区数据存储为 4 元素向量。如果返回类型是标量,则返回向量的第一个元素。如果返回类型是 n 元素向量,则返回 4 元素向量的前 n 个元素。

int_spv_resource_store_typedbuffer

void

[spirv.Image Image,32 位整数坐标,vec4 数据]

将数据存储到给定坐标处的图像缓冲区。数据必须是 4 元素向量。

内置函数

以下部分重点介绍了 LLVM IR 中 SPIR-V 内置函数的表示,重点介绍了在 LLVM 中没有直接对应项的内置函数。

作为函数调用的指令

没有直接 LLVM 对应项的 SPIR-V 内置函数表示为 LLVM 函数调用。这些函数称为 SPIR-V 内置函数,遵循带有 SPIR-V 特定扩展的 IA64 命名修饰方案。在某些情况下支持解析对内置函数的非命名修饰调用,但未经过广泛测试。通用格式为

__spirv_{OpCodeName}{_OptionalPostfixes}

其中 {OpCodeName} 是 SPIR-V 操作码名称,不带 “Op” 前缀,{OptionalPostfixes} 是修饰符特定的后缀(如果有)。命名修饰和后缀允许在 LLVM 框架内表示 SPIR-V 的丰富指令集。

扩展指令集

SPIR-V 定义了几个用于附加功能的扩展指令集,例如特定于 OpenCL 的操作。在 LLVM IR 中,这些由对命名修饰的内置函数的函数调用表示,并根据环境选择。例如

acos_f32

表示来自 float32 输入的 OpenCL 扩展指令集中的 acos 函数。

内置变量

SPIR-V 内置变量提供对特殊硬件或执行模型属性的访问,映射到 LLVM 函数调用或 LLVM 全局变量。表示形式遵循命名约定

__spirv_BuiltIn{VariableName}

例如,SPIR-V 内置函数 GlobalInvocationId 在 LLVM IR 中可作为 __spirv_BuiltInGlobalInvocationId 访问。

向量加载和存储内置函数

SPIR-V 加载和存储向量的功能在 LLVM IR 中使用模仿 SPIR-V 指令的函数表示。这些内置函数处理 LLVM 的本机指令不直接支持的情况,从而可以对内存操作进行细粒度控制。

原子操作

SPIR-V 的原子操作,特别是那些对浮点数据进行操作的原子操作,在 LLVM IR 中使用相应的函数调用表示。这些内置函数确保 LLVM 可能没有直接支持的操作中的原子性,这对于并行执行和同步至关重要。

图像操作

SPIR-V 提供对图像和采样器操作的广泛支持,LLVM 通过对内置函数的函数调用来表示这些操作。这些包括图像读取、写入和查询,允许详细操作图像数据和参数。

组和子组操作

对于工作组和子组操作,LLVM 使用函数调用来表示 SPIR-V 的基于组的指令。这些内置函数有助于组同步、数据共享和集体操作,这对于高效的并行计算至关重要。