(转载)Tools for Learning LLVM TableGen

[复制链接]
发表于 2025-12-31 00:50:26 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
条件

迩来在学习有关llvm的东西,此中TableGen占了一部分,以是想特意学习下TableGen干系的语法。这里找到了LLVM官网的一篇先容TableGen的博客,学习并利用呆板翻译为中文。在文章的末了也添加了一些学习TableGen的资源。
原文地点:Tools for learning LLVM TableGen
正文

TableGen 是 LLVM 项目中用于天生各种文件的一种语言,用于当手动维护编写非常困难时。
比方,它用于界说可在特定架构上利用的全部指令。信息用 TableGen 界说,我们可以根据该单个源文件天生许多东西,好比C++ 代码、文档、下令行选项等。
在LLVM第一个官方Release 版本发布之前,TableGen就存在了,约莫20年前。
如今在LLVM项目存储库中,有凌驾 1000 个TableGen源文件,总计凌驾500,000 行代码。使其成为LLVM库中第五大最盛行的语言。

随着像MLIR如许的项目也利用了TableGen,如果你正在对LLVM做贡献,你将会在某一时间遇到它。
由于 TableGen 只存在于 LLVM 中,以是就大概有一个标题。与 C++等语言差别,TableGen 没有大量的学习资源。
因此,除了到场一个新项目,您还须要学习一种新的范畴特定语言(Domain Specific Language,DSL)。你来LLVM不是为了学习DSL,你大概是来这里写编译器的。
我不能说这个标题什么时间可以办理,但环境并不像看起来那么糟糕。迩来 TableGen 工具有了很大的改进,这意味着你可以把更多的精神投入到最初把你带到 LLVM 的目标上。
对于TableGen的简短先容

假设您想体现一个架构的寄存器。我将在这里特殊利用 Arm 的AArchi64。
你可以利用TableGen形貌它:
  1. $ cat register.td
  2. class Register<int _size, string _alias=""> {
  3.   int size = _size;
  4.   string alias = _alias;
  5. }
  6. // 64 bit general purpose registers are X<N>.
  7. def X0: Register<8> {}
  8. // Some have special alternate names.
  9. def X29: Register<8, "frame pointer"> {}
  10. // Some registers omitted...
复制代码
默认环境下,TableGen的编译器 llvm-tblgen 会创建 “records”,如下所示。
  1. $ ./bin/llvm-tblgen register.td
  2. ------------- Classes -----------------
  3. class Register<int Register:_size = ?, string Register:_alias = ""> {
  4. int size = Register:_size;
  5. string alias = Register:_alias;
  6. }
  7. ------------- Defs -----------------
  8. def X0 {        // Register
  9. int size = 8;
  10. string alias = "";
  11. }
  12. def X29 {       // Register
  13. int size = 8;
  14. string alias = "frame pointer";
  15. }
复制代码
这是TableGen的中央体现IR,雷同于LLVM中的"LLVM IR"。
在利用 LLVM 时,您将选择一个 “target”,这是你想要为其天生指令的处理处罚器架构。在TableGen这里的等价物是 “backend”。这些后端不天生指令,而是为该后端的特定用例输出一种格式化的界说或数据。
比方,有一个后端天生用于搜索数据表的 C++ 代码,其他示例是 C 头文件和重结构化文本文档。

紧张利用的编译器是llvm-tblgen,但也有其他特定于LLVM中子项目标编译器。比方clang-tblgen和lldb-tblgen。唯一的区别是它们包罗的后端差别,语言是雷同的。
你可以采取你的寄存器界说并天生C++代码,以便在某种引导加载步伐中对其举行初始化。大概你还可以对其举行记录并天生该过程的图表。如果有充足的后端,你可以从雷同的TableGen源代码完成全部这些操纵。
在利用TableGen创建后端时,可以通过两种方式实现:


  • 在TableGen编译器内部用C++编写后端
  • 作为外部后端,通过TableGen的JSON输出天生所需数据:TableGen 提供 --dump-json 选项,可以将编译器内部数据以 JSON 格式输出。如许你可以利用支持 JSON 分析的任何语言(比方 Python)编写外部步伐,来读取和处理处罚这些 JSON 数据,以实现自界说的后端。
TableGen和利用TableGen构建的工具

TableGen更多的是一种头脑方式,而不是一种工具。最好的总结方式是引用文档中的一句话:
   只管 TableGen 非常通用,但它仍存在一些缺陷,而且这些缺陷已经被多次指出。总体的反馈是,固然 TableGen 答应用户创建范畴特定语言(DSL,Domain-Specific Language),但这些终极天生的语言缺乏其他 DSL 的强盛功能,这反过来使得 TableGen 文件的规模和复杂性显着增长。
同时,TableGen 答应用户通过自界说的后端,险些可以为根本概念赋予任何意义,这种机动性大概会偏离最初筹划的意图,导致 TableGen 文件难以明白,对新手尤其不友爱,以致大概变得“险恶难懂”。
  在利用 LLVM 时,你将会打仗到 TableGen 以及由 TableGen 构建的各种工具和设置,而这些通常比 TableGen 语言自己更复杂。
这就像学习 C++ 的过程中遇到了 Boost 库。有人大概会告诉你:“Boost 并不是必须的,为什么不去掉它,省些贫困呢?”但是,作为 C++ 的新手,你大概并不相识 C++ 和 Boost 之间的界限。
固然,如果你想要到场的项目中利用了 Boost,那么这种发起并没有太大资助——你依然不得不处理处罚 C++ 和 Boost。对于 LLVM 而言,TableGen 语言和利用它的后端工具是一揽子服务,你必须一起明白它们。
我之以是提到这些,是渴望你可以区分自己不明白的到底是 TableGen 自己,还是利用它构建的其他工具。清楚毕竟是哪个部分让你狐疑,将更有利于你寻求资助。
对于任何使命来说,你大概只须要明白一两个“用 TableGen 构建的工具”,而且通常不须要全面把握它们。
不要以为自己在学习 TableGen 时肯定要弄懂它的全部应用方式。这固然是大概的,但并非须要,而且险些没有人会完全夺目。相反,把精神放在那些真正让你感爱好的部分才是更明智的选择。
Compiler Explorer

我们在 Compiler Explorer 中也有 TableGen!如果一种语言不在 Compiler Explorer 中,它还算是“真实存在”吗?(固然算,不外如果你喜好的语言没有出如今此中,Compiler Explorer 有精彩的文档和友爱的维护职员)。
Compiler Explorer 是一个强盛的在线工具,支持差别语言和架构的各种编译器版本,只需在欣赏器中打开一个标签页即可访问。这个工具在学习、讲授、调试、优化等方面非常有效。本文不会详细先容它的功能,只简单提及一下 TableGen 在 Compiler Explorer 中的利用环境。
显然,llvm-tblgen(TableGen 编译工具)不会输出呆板指令(不外理论上可以有如许的后端),因此它没有编译为二进制或实行代码的选项。
默认环境下,TableGen 中的记录(记录结构)会以纯文本情势输出。你可以通过添加编译选项选择后端,大概在 “Overrides”(覆盖)菜单中选择“Action”(动作)来选择差别的后端。
须要注意的是,TableGen 的后端对源代码的内容有非常详细的要求。就像你有一个 C++ 编译器,除非代码中包罗arm_is_cool,否则它不会编译 Arm 架构的代码。
在 LLVM 堆栈中,全部所需的类都已为你设置好,但在 Compiler Explorer 中则没有。因此,如果你想在现有后端上举行实行,发起提供一些类的浅易实现,大概从 LLVM 项目堆栈中复制一些代码。你也可以利用 include/llvm/*.td 中的尺度包罗文件。
如今在 Compiler Explorer 中无法开辟一个后端,但你可以选择 JSON 后端并将天生的 JSON 复制到本地脚本中进一步处理处罚。
Compiler Explorer 也支持多文件项目(“IDE 模式”),因此你可以有自己的包罗文件。
末了,记着你可以分享 Compiler Explorer 中的示例。如果你在提问或解答有关 TableGen 的标题时,只管包罗一个 Compiler Explorer 的链接!
Jupyter Notebooks

Jupyter 可以创建交互式条记本。一个条记本是一个包罗文本、代码和代码运行结果的文档。这种方式答应你编辑代码并重新运行,以在条记本中更新结果。
这种功能非常得当做条记,大概从小的代码片断构建出大型示例。你可以将文档导出为可编辑的条记本文件,便于他人修改,也可以导出为非交互式格式,如 PDF 或 Markdown。
可以通过 TableGen Jupyter Kernel 在 Jupyter 条记本中利用 TableGen。安装阐明可以在这里找到,而详细先容可以参考我在这里的讲授。
注意:Jupyter 尚有 MLIR Kernel 和其他许多 Kernel 可用。
我们力图为 TableGen 提供与其他语言雷同的体验,以是我不会专注于怎样利用 Jupyter 条记本,而是展示我们能利用它们实现的结果。
TableGen 教程 Notebook

这个notebook 是对TableGen的先容,你可以在Github上阅读,大概下载它在Jupter中阅读。
当利用Jupyter时,你可以修改文档添加你自己的例程,大概扩展你感爱好的点。
怎样编写TableGen后端的Notebook

这个Notebook利用 Python 代替 TableGen,展示了怎样编写一个后端。
这个条记本是基于 Min-Yih Hsu 在 2021 年欧盟 LLVM 开辟者聚会会议上的演讲《怎样编写 TableGen 后端》。究竟上,该Notebook是 Min C++实现的 Python 版本。
它展示了怎样获取 llvm-tblgen 的 JSON 输出,并利用 Python 处理处罚它,天生 SQL 查询。
特殊之处在于:我们如今拥有雷同内容的多种媒介情势和多种编程语言版本。可以选择最得当自己的学习方式。
回到之条件到的“TableGen和利用TableGen构建的工具”概念,这个教程条记本是关于 TableGen 的内容,而“怎样编写后端”则属于“由 TableGen 构建的工具”。
限定

该Notebook的一个紧张限定是无法举行输出过滤。也就是说,如果你在Notebook中 include "llvm/Target/Target.td",那么会产生约莫 320,000 行输出(纵然你还没有添加任何代码)。这个输出量凌驾了默认notebook对内核的继承限度,而且当我移除该限定时,欣赏器标签页会瓦解。
在大多数环境下,这不是标题,而且可行的办理方案都有较大的权衡,因此我们不会急于推出修复。如果这个标题确实影响到了你,请在跟踪标题页面中提交反馈。
TableGen 语言服务

MLIR 项目实现了一个兼容语言服务器协议(LSP 的服务器,支持 TableGen 及 MLIR 中利用的别的两种语言。
语言服务器协议(LSP 为兼容的编辑器提供有关语言和项目结构的信息。比方:包罗文件的位置、特定范例界说的位置等。
如果你利用过兼容 LSP 的编辑器(好比 Visual Studio Code),很大概已经在不知情的环境下用过语言服务器。最常见的功能是“跳转到界说”(Go To Definition)。
LSP 答应你打开一个项目,找到想修改的代码,并直接跳转到堆栈中的其他干系部分。LLVM 项目中包罗凌驾 500,000 行的 TableGen 代码,因此有了 LSP 的资助,可以或许忽略大量无关的代码!
安装利用

你须要一个名为 tblgen-lsp-server 的服务器二进制文件,可以从得当你平台的发布包中获取,也可以自行构建。
利用以下下令自行构建:
  1. $ cmake -G Ninja <path-to>/llvm-project/llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="mlir"
  2. $ ninja tblgen-lsp-server
复制代码
运行这些下令后,tblgen-lsp-server 二进制文件会出如今 <build-dir>/bin/ 目次中。
tblgen-lsp-server 须要一个名为 tablegen_compile_commands.yml 的编译数据库文件。当你利用 CMake 设置 LLVM 时,该文件会主动天生。
这个文件的作用雷同于利用 CMAKE_EXPORT_COMPILE_COMMANDS 时天生的 compile_commands.json 文件,但这两个文件并无直接关系。
只要你当前的 llvm-project 包罗一个特定的提交,天生的编译数据库就会包罗全部启用项目中的 TableGen 文件(在该提交之前,这个文件只包罗 MLIR 项目标文件)。
比方,以下设置下令会包罗LLVM、Clang、MLIR 和 LLDB 子项目中的 TableGen 文件信息:
  1. $ cmake -G Ninja <path-to>/llvm-project/llvm -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;llvm;lldb;mlir"
复制代码
这个下令同样实用于 -DLLVM_TARGETS_TO_BUILD= 选项。如果只启用了一个目标,那么编译数据库中就只包罗与该目标干系的文件。
注意: 你不须要构建项目即可将其 TableGen 文件包罗在编译数据库中,只需完成设置步调即可。(Note: You do not need to build a project to include its TableGen files in the compilation database. Configuring is all that is needed.)
接下来,为你的编辑器设置 LSP 客户端:
Visual Studio Code:安装 MLIR扩展,并按照阐明设置扩展,以指定服务器和编译数据库的位置。
如果你正在利用其他编辑器,参考其文档相识怎样设置语言服务器。设置编译数据库路径大概须要利用服务器的下令行选项。运行tblgen-lsp-server --help 检察全部可用选项。
示例

This example assumes you have configured LLVM with the AArch64 target enabled. (It is enabled by default)


  • Open the file llvm/lib/Target/AArch64/AArch64.td.
  • Put your cursor on a use of the SubtargetFeature type.
  • In the menu bar, select “Go” then “Go to Definition”.
  • This takes you to llvm/include/llvm/Target/Target.td, where SubtargetFeature is defined.
限定

语言服务器 显现了某些 LLVM 目标(如 AArch64)利用 TableGen 时的一个反模式(anti-pattern)。
在这种模式下,你大概会在一个文件中利用某个类,但该文件既没有界说该类,也没有包罗界说该类的文件。这是由于该文件自己是筹划用来被包罗在另一个文件中的,而这个文件(即包罗它的文件)会包罗界说该类的文件。
  1. // example.td
  2. class Example {}
  3. // uses_example.td
  4. def example: Example {}
  5. // main.td
  6. include "example.td"
  7. include "uses_example.td"
复制代码


  • example.td 界说了 Example 类。
  • uses_example.td 利用了 Example 类,但没有包罗 example.td。
  • main.td 包罗了 example.td 和 uses_example.td。
  • 编译时利用的是 main.td。
  • 当你在 uses_example.td 文件中时,语言服务器无法知道 Example 类在那边界说,由于 uses_example.td 并没有直接包罗 example.td。
  • 当你在 main.td 中时,语言服务器可以或许找到 Example 类的界说,由于 main.td 包罗了 example.td 和 uses_example.td。
这种反模式的标题在于,单独检察 uses_example.td 时,它看起来像一个孤立的文件,语言服务器无法精确明白它的上下文。
Dump

printf是最好的debug工具,在TableGen划一的是dump以及它的搭档repr
  1. def op;
  2. class A {
  3.   string A = "some text";
  4.   dag X =(op op);
  5. }
  6. def a : A;
  7. dump "The Value of a is: \n" # !repr(a);
复制代码
dump会打印到stderr:
  1. def op;
  2. class A {
  3.   string A = "some text";
  4.   dag X =(op op);
  5. }
  6. def a : A;
  7. dump "The Value of a is: \n" # !repr(a);
复制代码
这个功能迩来才被添加,因此你须要迩来构建的版本,18.0版本或更高。
固然你也可以在Compiler Explorer中利用它。
Assertions

**断言(assertion)**用于查抄步伐中特定条件是否为真。一个断言由以下三部分构成:


  • assert 关键字。
  • 一个条件(通常通过 bang 操纵符之一来判断)。
  • 一条信息(堕落时体现的消息)。
如果条件为假,则天生一个编译器错误并体现提供的消息。
以下代码查抄是否实行创建一个巨微小于 0 的寄存器:
  1. class Register<int _size> {
  2.   assert !gt(_size, 0),
  3.        "Register size must be > 0, not " # _size # "." ;
  4.   int size = _size;
  5. }
  6. def X0: Register<8> {}
  7. def X1: Register<-8> {}
复制代码
在Compiler Explorer中实行。
在这个例子中:


  • 寄存器 X0:_size=8,条件 !gt(_size, 0)(C 语言中等同于 _size > 0)为真,因此不会天生错误。
  • 寄存器 X1:_size=-8,条件为假,因此会天生错误,编译器输出如下:
    1. <source>:2:11: error: assertion failed
    2.    assert !gt(_size, 0),
    3.           ^
    4. note: Register size must be > 0, not -8.
    复制代码
在学习新代码时,添加自己的断言来查抄假设会很有资助。别的,为他人利用的代码添加断言是防止误用的好方法。和文档差别,断言错误是无法忽略的,一旦条件不满意就会被立即发现。
Find In Files

将文本搜索(如 grep、ack 或“查找文件中的内容”)放在末了是由于在理想环境下,这种方法是末了的选择。但实际上,这种方法并不是最次的选择。如果你对语言语法有肯定相识,文本搜索的结果通常超出预期。
为什么我要提到这么显而易见的想法?显然,“显而易见”是主观的,而且在某些特定环境下,文本搜索的结果会更加显着。
在 LLVM 项目堆栈中,我们利用了许多 TableGen 代码。如果你想相识某个特定功能的用法,可以在凌驾 500,000 行的源代码中找到它。你大概会惊讶地发现,一个简单的查询就能找到有效的信息。
思量一下你想要查找的内容,想象一下它的源代码会是什么样子。如果是一个类,它会带有模板参数吗?那么类名反面大概会有 <。如果是错误消息,哪些部分是固定的,哪些部分是插入的模板内容?
比方,“盼望的行尾”大概是一个静态字符串,以是可以直接搜索该消息。而像 “class Foo has no attribute Bar” 如许的消息更大概是通过更换类名和属性名天生的,因此更好的搜索词大概是“has no attribute”。
LLVM 项目中也有许多编译器测试,大部分都在特定文件夹中,这些文件夹包罗了语言特性的最小示例。可以实行将搜索范围缩小到这些文件夹中。
Conclusion

学习 TableGen 并不须要让人感到畏惧。不要由于它是一个独立的 DSL 就以为它不具备你在其他喜好的编程语言中所盼望的功能。
要记着,TableGen 只是一个工具,而不是学习的终极目标。如果你可以或许通过对 TableGen 及厥后端有限但精确的明白来实现你的目标,那就充足了。根据自己的需求和爱好,学多少都可以。
除了工具支持之外,尚有一个活泼的社区,随时可以在 Discord 或论坛上解答你的标题。
如果你发现了标题或渴望做出改进,接待到场贡献,可以在 GitHub 上提交 Issue 或 Pull Request。
想想你利用的其他语言。它们是否有雷同的工具?是否应该有?这些工具大概是让你从沮丧中摆脱出来、爱上这门新语言的关键。
自已的劳绩

着实这篇博客只是从很宏观的层面讲了TableGen,但是对于我们这种新手来说,第一件事是要能读懂TableGen的语法,也就是可以看懂*.td文件。这里我保举可以把这个全文快速看一篇,然后直接去跑官方的谁人notebook,如允许以快速入门。
参考链接


  • https://github.com/llvm/llvm-project/tree/main/llvm/utils/TableGen/jupyter
  • LLVM 之后端篇(1):零根本快速入门 TableGen
  • 官方TableGen手册:https://llvm.org/docs/TableGen/ProgRef.html
  • TableGen官方页面:https://llvm.org/docs/TableGen/index.html
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表