HLSL 支持

介绍

HLSL 支持在 Clang 代码库中积极开发中。本文档描述了项目的总体目标、指导原则,以及 HLSL 语言的一些特殊之处以及我们打算如何在 Clang 中支持它们。

项目目标

该项目的长期目标是使 Clang 能够替代 DirectXShaderCompiler (DXC),在其所有支持的使用场景中。为了实现这一目标,Clang 需要能够以高度的源代码兼容性处理大多数现有的 HLSL 程序。

非目标

HLSL AST 不需要在 DXC 和 Clang 之间兼容。我们不期望相同的代码生成或功能与 DXC 的实现或架构类似。事实上,我们明确期望在关键方面偏离 DXC 的实现。

指导原则

本文档缺少尚未最终确定的一些架构决策的详细信息。我们的首要任务是质量、可维护性和灵活性。根据社区标准,我们预计将有很高的测试覆盖率,并且我们将以长期维护为目标来设计我们的解决方案。我们还努力将对 Clang C++ 代码路径的修改限制在最小范围内,并尽可能多地共享功能。

架构方向

Clang 中的 HLSL 支持表示为 C++ 减去不支持的 C 和 C++ 功能。这与其他 Clang 语言的实现方式不同。Clang 中的大多数语言都是 C 之上的增量。

HLSL 不是一种正式或完全指定的语言,虽然我们的目标要求高度的源代码兼容性,但实现可能会有所不同,在某些情况下,我们有一些灵活性来更加或更少地允许使用。

HLSL 工作优先考虑遵循其他语言、驱动程序、运行时和目标的类似模式。具体来说,我们将尽可能地保持 HLSL 特定代码与 Clang 其余部分的分离,遵循当今 Clang 代码中使用的模式(即 ParseHLSL.cpp、SemaHLSL.cpp、CGHLSL*.cpp …)。我们将在代码简单且隔离的地方使用语言选项的内联检查,并且对于任何合理复杂度的代码,首选 HLSL 特定的实现文件。

在 HLSL 语言与 C 和 C++ 冲突的地方,我们将寻求在 HLSL 语言选项下进行最小侵入性更改。我们将努力使 HLSL 语言支持成为尽可能少的维护负担。

DXC 驱动程序

DXC 驱动程序模式将提供与 DXC 的命令行兼容性,支持 DXC 的选项和标志。DXC 驱动程序是 HLSL 特定的,将创建一个 HLSLToolchain,它将为支持目标 DirectX 和 Vulkan 提供基础。

解析器

按照其他解析器扩展的示例,HLSL 将添加一个 ParseHLSL.cpp 文件来包含 HLSL 对 Clang 解析器的特定扩展的实现。HLSL 语法与 C 和 C++ 共享大部分结构,因此我们将使用现有的 C/C++ 解析代码路径。

Sema

HLSL 的 Sema 实现也将提供一个 ExternalSemaSource。在 DXC 中,ExternalSemaSource 用于提供 HLSL 内置数据类型和内置模板的定义。Clang 已经设计为允许附加的 ExternalSemaSource 延迟完成数据类型,这对 HLSL 来说是一个巨大的性能提升。

如果在编译 HLSL 时使用预编译头,则 ExternalSemaSource 将是一个 MultiplexExternalSemaSource,它包含 ASTReader 和 -。对于在序列化 AST 中已完成的内置声明,HLSLExternalSemaSource 将重用现有声明,不会引入新的声明。如果内置类型在序列化 AST 中未完成,则 HLSLExternalSemaSource 将创建新的声明并将反序列化 decl 连接为先前的声明。

代码生成

与 OpenCL 一样,HLSL 依赖于将大量信息捕获到 IR 元数据中。手势挥动 手势挥动 手势挥动 作为设计原则,我们希望我们的 IR 尽可能地成为惯用的 Clang IR。我们将在任何地方都使用 IR 属性,并尽可能少地使用元数据。Clang 中已经实现的一个与 DXC 不同的示例是使用目标三元组来传达着色器模型版本和着色器阶段。

我们的 HLSL 代码生成实现还应着眼于生成将直接映射到 DXIL 以外的目标的 IR。虽然 IR 本身通常不可重新定位,但我们希望将 HLSL 的 Clang 代码生成实现与其他 GPU 图形目标(如 SPIR-V)以及可能的其他 GPU 甚至 CPU 目标共享。

hlsl.h

HLSL 有一组独立的函数库。这类似于 OpenCL 和 CUDA,类似于 C 的标准库。HLSL 库功能的实现方法借鉴了 OpenCL 和其他 Clang 资源头文件中使用的模式。所有 Clang 资源头文件都是源代码树中 clang/lib/Headers 下的 ClangHeaders 组件的一部分。

注意

HLSL 的复杂数据类型没有在 HLSL 的头文件中定义,因为由于缺少语言功能,这些数据类型的许多语义无法在 HLSL 中表达。无法在 HLSL 中表达的数据类型是在 HLSLExternalSemaSource 中的代码中定义的。

与 OpenCL 类似,HLSL 库功能在没有包含头文件提供声明的翻译单元中隐式声明。在 Clang 中,这是通过使 hlsl.h 成为作为 Clang 资源目录的一部分分发的隐式包含的头文件来处理的。

与 OpenCL 类似,HLSL 的隐式头文件将显式声明所有重载,并且每个重载都将映射到相应的 __builtin* 编译器内在函数,该函数在 ClangCodeGen 中处理。CUDA 使用类似的模式,尽管许多 CUDA 函数在包含的头文件中具有完整的定义,这些头文件又调用相应的 __builtin* 编译器内在函数。通过没有主体,HLSL 避免了内联器清理和内联大量小型库函数的需要。

HLSL 的隐式头文件还定义了 HLSL 的一些类型定义。这与 AVX 向量头的实现方式一致。

有些人担心这种方法可能会导致比 DXC 使用的方法更慢的编译时间,在 DXC 使用的方法中,库函数更像 Clang __builtin* 内在函数。在实际使用案例中,尚未确定解析是显著的编译时间开销,但如果需要,HLSL 隐式头文件可以编译成模块以提高性能。

此外,通过将这些函数视为函数而不是 __builtin* 编译器内在函数,语言行为更加一致,并且与用户预期一致,因为正常的重载解析规则和隐式转换按预期应用。

这种设计的特点是,由 clangd 提供支持的“转到声明”对于库函数将跳转到有效的头文件声明,并且所有重载都将是用户可读的。

HLSL 语言

HLSL 语言的文档不足,并且没有正式规范。可以在 微软网站 上找到相关文档。该语言语法与 C 和 C++ 足够相似,因此精心编写的 C 和 C++ 代码是有效的 HLSL。HLSL 与 C 和 C++ 有一些关键区别,我们需要在 Clang 中处理这些区别。

HLSL 不是 C 或 C++ 的符合标准或有效的扩展或超集。该语言在语法和语义上都与 C 和 C++ 有着关键的不兼容性。

关于 GPU 语言的旁注

由于 HLSL 是一种面向 GPU 的语言,因此 HLSL 是一种单程序多数据 (SPMD) 语言,它依赖于 GPU 硬件提供的隐式并行性。HLSL 中的一些语言功能使程序员能够利用 GPU 的并行性质,在硬件抽象的语言中。

HLSL 还禁止 C 和 C++ 中的一些功能,这些功能可能会导致灾难性的性能或在 GPU 硬件或驱动程序上不受广泛支持。例如,寄存器溢出在 GPU 上通常非常昂贵,因此 HLSL 要求所有函数在代码生成期间内联,并且不支持运行时调用约定。

指针和引用

HLSL 不支持按地址引用值。语义上,所有变量都是值类型,并且表现为值类型。HLSL 不允许使用指针解引用运算符(一元 *->),以及取地址运算符(一元 &)。虽然 HLSL 在语法中不允许使用指针和引用,但 HLSL 在 AST 中确实使用引用类型,并且我们打算在 Clang 实现中使用指针衰减。

HLSL this 关键字

HLSL 支持成员函数,并且(在 HLSL 2021 中)有限的运算符重载。在支持成员函数的情况下,HLSL 还具有一个 this 关键字。this 关键字是 HLSL 依赖于 AST 中引用的地方之一,因为 this 是一个引用。

位移

与 C 不同,HLSL 位移操作定义为使用类型大小对移位计数进行掩码操作。在 DXC 中,LLVM IR 的语义被修改以适应此变化,在 Clang 中,我们打算在 IR 中显式生成掩码。如果移位值是常量,它将被适当地进行常量折叠,在其他情况下,我们可以在 DXIL 目标中对其进行清理。

非短路逻辑运算符

在 HLSL 2018 及更早版本中,HLSL 支持对向量类型进行逻辑运算(以及三元运算符)。这种行为要求运算符不进行短路。非短路行为适用于所有数据类型,直到 HLSL 2021。在 HLSL 2021 中,逻辑运算符和三元运算符不支持向量类型,而是使用内置函数 andorselect,运算符短路与 C 行为匹配。

精确限定符

HLSL 具有 precise 限定符,其行为与 C 语言中的任何其他限定符都不一样。DXC 中对该限定符的支持存在错误,因此我们对兼容性的要求很低。

precise 限定符的作用方向与普通限定符相反。它不表示包含 precise 限定符的声明是精确的,而是表示构成声明值的运算必须是 precise。此外,precise 是一个误称:被标记为 precise 的值符合 IEEE-754 浮点数语义,并且禁止对其进行可能降低或提高精度的优化。

模板差异

HLSL 使用模板来定义内置类型和方法,但在 HLSL 2021 之前不允许用户定义模板。HLSL 还允许在所有模板参数都默认为值时省略空的模板参数列表。这在 C++ 中是一种模棱两可的语法,但 Clang 会检测这种情况并发出诊断信息。这使得 Clang 对这种情况的支持侵入性最小。

向量扩展

HLSL 使用 OpenCL 向量扩展,还为向量提供 C++ 风格的构造函数,这些构造函数不受 Clang 支持。

标准库

HLSL 不支持 C 或 C++ 标准库。与 OpenCL 一样,HLSL 描述了自己的内置类型、复杂数据类型和函数库。

不支持的 C 和 C++ 特性

HLSL 不支持 C 和 C++ 的所有特性。在 Clang 中实现 HLSL 时,使用某些 C 和 C++ 特性会在 HLSL 下产生诊断信息,而其他特性则会被支持为语言扩展。一般来说,任何可以被 DXIL 和 SPIR-V 代码生成目标支持的 C 或 C++ 特性都可以被视为 Clang HLSL 扩展。无法降低到 DXIL 或 SPIR-V 的特性必须被诊断为错误。

HLSL 不支持以下 C 特性

  • 指针

  • 引用

  • goto 或标签

  • 可变长度数组

  • _Complex_Imaginary

  • C 线程或原子操作(或 Obj-C 块)

  • union 类型 (正在为 HLSL 202x 开发)

  • C11 及更高版本中的大多数特性

HLSL 不支持以下 C++ 特性

  • RTTI

  • 异常

  • 多重继承

  • 访问说明符

  • 匿名或内联命名空间

  • newdelete 运算符及其所有形式(数组、放置等)

  • 构造函数和析构函数

  • 任何使用 virtual 关键字的情况

  • C++11 及更高版本中的大多数特性