The LLVM Project 博客

LLVM 项目新闻和来自战壕的细节

新的 Clang _ExtInt 特性提供了精确位宽整数类型

作者: Erich Keane,英特尔公司编译器前端工程师

本月初,我终于提交了一个补丁,在近两年半的设计和实现之后,实现了扩展整数类型类 _ExtInt。这些类型允许开发人员使用自定义宽度整数,例如 13 位有符号整数。此补丁目前旨在跟踪 N2472,这是一个正在被 ISO WG14 C 语言委员会积极考虑的提案。我们认为这些类型将对 Clang 的许多下游用户非常有用,并为 LLVM 的强大整数类型类提供了一个语言接口。

动机

LLVM-IR 能够表示位宽从 1 到 16,777,215((1<<24)-1) 的整数,但 C 语言仅限于几种二的幂大小。从历史上看,这些类型对几乎所有编程架构都足够了,因为整数的二的幂表示很方便且实用。

最近,称为高级综合编译器 (HLS) 的现场可编程门阵列 (FPGA) 工具变得实用且功能强大,足以使用通用编程语言生成它们。这些工具使用 C 或 C++ 代码并生成 FPGA 使用的晶体管布局。然而,一旦程序员在这些工具中获得经验,他们发现标准 C 整数类型对于两个主要原因来说非常浪费。

首先,大多数情况下,程序员没有使用整数类型的全部宽度。很少有人使用 16、32 或 64 位整数表示的全部位。在传统 CPU 上,这不是什么大问题,因为硬件已经到位,所以从未设置的位不会带来任何成本。另一方面,在 FPGA 上,逻辑门是一种极其宝贵的资源,HLS 编译器不应该被要求在它们只需要一小部分的情况下浪费大量二进制幂整数的位!虽然优化器能够删除一些这些宽度,但绝大多数这些硬件需要被发出。

其次,C 语言要求小于 int 的整数在 'int' 类型上的运算中被提升。这进一步复杂了硬件生成,因为提升到 int 既昂贵又往往会坚持使用整个语句的运算。这些提升通常具有语义意义,因此在不改变源代码意义的情况下,简单地省略它们是不可能的。更糟糕的是,auto 的激增导致用户代码导致更大的整数大小在整个程序中非常流行。

结果是 FPGA/HLS 程序比程序员需要的更大,也可能比他们预期的更大。更糟糕的是,程序员没有办法表达他们不需要标准整数类型全部宽度的意图。

使用 _ExtInt 语言特性

被接受并提交到 LLVM 的补丁通过提供 _ExtInt 类型类解决了上述问题中的大部分问题。这些类型直接转换为相应的 LLVM-IR 整数类型。 _ExtInt 关键字是一个类型说明符(如 int),它接受一个必需的整型常量表达式参数,表示要使用的位数。更简洁地说:_ExtInt(7) 是一个使用 7 位的有符号整数类型。因为它是一个类型说明符,所以它也可以与 signedunsigned 组合使用,以更改值的符号(以及溢出行为!)。因此,"unsigned _ExtInt(9) foo;" 声明了一个变量 foo,它是一个无符号整数类型,占用 9 位,在 LLVM-IR 中表示为 i9

所实现的 _ExtInt 类型不参与任何隐式转换或整数提升,因此对其执行的所有数学运算都发生在适当的位宽上。WG14 文件建议将整数提升到最大的类型(即,添加 _ExtInt(5)_ExtInt(6) 将导致 _ExtInt(6)),但是实现不允许这样做,并且 _ExtInt(5) + _ExtInt(6) 将导致编译器错误。这样做是为了确保在 WG14 更改论文设计的情况下,我们能够在不破坏现有程序的情况下实现它。同时,可以通过显式强制转换解决此问题:(_ExtInt(6))AnExtInt5 + AnExtInt6static_cast<ExtInt(6)>(AnExtInt5) + AnExtInt6.

此外,对于 C++,clang 支持将位宽参数设置为一个依赖表达式,因此以下内容是合法的
template<size_t WidthA, size_t WidthB>
  _ExtInt(WidthA + WidthB) lossless_mul(_ExtInt(WidthA) a, _ExtInt(WidthB) b) {
  return static_cast<
_ExtInt(WidthA + WidthB)>(a) 
       * static_cast<_ExtInt(WidthA + WidthB)>(b);


我们预计这种能力和这些类型将导致一些非常有用的代码片段,包括对 256 位、512 位或更大整数的新颖使用,以及对 8 位和 16 位整数的使用,这些整数适合那些负担不起提升的人。例如,现在可以轻松地实现一个扩展整数类型结构,它对所有运算都能够证明无损,也就是说,添加两个 6 位值将导致一个 7 位值。

为了与 C 语言一致,包含标准类型的表达式仍然遵循整数提升和转换规则。所有小于 int 的类型都将被提升,然后运算将发生在最大类型上。当您添加一个 short 和一个 _ExtInt(15) 时,这可能会令人惊讶,结果将是 int。但是,这最终与 C 语言规范最一致。

此外,在谈到转换时,这些类型会'输给'相同大小或更大的 C 标准类型。因此,将一个 int 添加到一个 _ExtInt(32) 将导致一个 int。但是,一个 int 和一个 _ExtInt(33) 将是后者。这是为了保留 C 整数语义。

历史

如前所述,此特性已经酝酿已久!事实上,这可能是为了达到这一目标而完成的第四个实现。此外,这还远远没有结束,我们非常希望在 WG14 标准委员会接受此特性后,将有更多的扩展和功能可以使用。

2017 年秋季,我被我公司 FPGA 团队联系到,要实现此特性,该团队遇到了上述问题。他们尝试了一种使用一些巧妙的解析使这些看起来像模板的解决方案,并在整个编译器中广泛地实现了它们。由于我担心这些类型在类型和模板系统中的灵活性和可用性,我们选择将这些类型作为一种类型属性来实现,并在具有争议性的名称 Arbitrary Precision Int(拼写为 __ap_int)下实现。这个拼写受到 GCC 和 Clang 中向量类型实现的很大影响。

然后,我们能够在一个结构中包装一组类型定义(或依赖的 __ap_int 类型),该结构提供了我们希望公开的 C 和 C++ 接口。由于这当时是一个专有实现,因此它被保留在我们下游的实现中,在那里它接受了广泛的测试和使用。

大约一年后(距离今天还有一年多!),我被授权将我们的实现贡献给开源 LLVM 社区!我决定对实现进行重大重构,以便更好地融入 Clang 类型系统,并将它上传到 审核。这个(现在是第三个!)此特性的实现是通过 RFC 和代码审查同时提出的。

虽然它的有用性立即得到承认,但它被 Clang 代码所有者拒绝,原因有两个:首先,拼写被认为不可取,其次,它是一个纯粹的扩展,没有标准化。这开始了近一年的努力,旨在提出一个标准提案,该提案将更好地定义和描述此特性,并提出一个更符合标准语言的拼写。

感谢 Richard Smith 的宝贵反馈和投入,我的同事 Melanie Blower、Tommy Hoffner 和我自己能够为标准化提出拼写 _ExtInt。此外,此特性在今年年初再次重新实现,并最终被接受并提交!

标准化文件(N2472)在今年春季的 WG14 ISO C 语言委员会会议上发表,并获得了几乎一致的支持。我们预计将有一个更新版本的论文,其中包含准备在下一次 WG14 会议上使用的措辞,我们希望它能获得足够的支持,被纳入该语言。

未来的扩展

虽然在 Clang 中提交的特性非常有用,但它还可以进一步扩展。我们希望在 WG14 给出其方向和实施方面的指导后,实现一些未来的扩展。

首先,我们相信特殊的整数提升/转换规则(省略到 int 的自动提升,而是提供在最大类型上的运算)既非常有用又强大。虽然我们从 WG14 收到了积极的鼓励,但我们希望我们提供的措辞文件既能以支持所有常见用途的方式阐明机制和定义。

其次,我们想选择一个允许为 C 语言指定类型的 printf/scanf 说明符。这是 WG14 讨论的主题,也得到了强烈的鼓励。我们打算提出一个好的表示,然后在主要实现中实现它。

最后,许多人建议实现一种拼写这种类型字面量的方法。这有两个重要原因:首先,它允许在不使用强制转换的情况下在表达式中使用字面量,而不会违反提升规则。其次,它提供了一种拼写大于 UINTMAX_MAX 的整数字面量的方法,这对于初始化这些类型的大版本可能有用。虽然拼写尚未确定,但我们打算使用以下方式:1234X 将导致一个值为 1234 的整数字面量,表示为 _ExtInt(11),这是能够存储此值的最小类型。

但是,如果没有上述整数提升/转换规则,此特性就没有那么有用。此外,我们希望与 C 语言委员会选择的内容保持一致。一旦我们收到关于此类型拼写和语法的积极指导,我们期待提供实现。

结论

最后,我们鼓励您尝试使用它并向我和我的提案合著者以及 C 委员会本身提供反馈!我们认为这是一个非常有用的特性,并且希望获得尽可能多的用户体验。如果您有任何问题或疑虑,请随时与我和我的合著者联系!

-Erich Keane,英特尔公司