LLVM 项目博客

LLVM 项目新闻和来自一线的消息

将 LLVM 值映射到对应的源代码级表达式,GSoC'23 项目

大家好,我叫 Shivam,我参与了 2023 年版的 LLVM 基金会 GSoC,并参与了一个有趣的项目将 LLVM 值映射到对应的源代码级表达式

项目范围

程序员经常依靠编译器生成的备注和分析报告来提高代码效率。虽然编译器擅长在这些生成的报文中包含源代码位置(如行号和列号),但如果这些报告还包含相应的源代码级表达式,则会更有优势。LLVM 实现目前采用了一组有限的内在函数来建立 LLVM 程序元素和源代码级表达式之间的联系。本项目的目的是利用这些内在函数中嵌入的数据来生成与 LLVM 值相对应的源代码表达式。优化程序中内存访问对于实现最佳应用程序性能至关重要。具体来说,我们的目标是利用编译器分析消息来详细说明与 LLVM 加载/存储指针值相关的源代码级内存访问,这些值会阻碍编译器优化。例如,这些信息可用于识别阻碍向量化的内存访问依赖关系。

预期结果是提供一个接口,该接口在 LLVM 转换管道中的任何位置获取一个 LLVM 值,并返回一个与等效源代码级表达式相对应的字符串。我们特别感兴趣的是使用此接口将加载/存储指令中使用的地址映射到等效的源代码级内存引用。

我们做了什么

该项目的核心成果是开发了一个在 LLVM 中间表示 (IR) 上运行的分析流程。该分析流程识别加载和存储指令,然后进行递归遍历以构建代表等效源代码级内存引用的源代码表达式。这是通过利用 LLVM IR 中可用的元数据和调试内在函数来实现的。此流程已集成到循环向量化框架中,这是迈向实际应用的重要一步。除了实现之外,还开发了一套全面的测试来确保分析流程的准确性和预期行为。分析流程位于 llvm/lib/Analysis/SourceExpressionAnalysis.cpp

实现概述

调试元数据处理

该实现有效地处理与指令相关的调试元数据。它利用调试值和声明指令来检索变量名称,然后使用这些名称来构建源代码表达式。这使得能够准确地将 LLVM 值映射到其对应的源代码级表达式。

指令类型处理

该实现涵盖了各种指令类型,包括二元运算符、GetElementPtr、符号扩展指令、LoadInst 和 StoreInst。这种全面的覆盖确保了可以将大量 LLVM 指令转换为有意义的源代码级表达式。

类型和标签处理

该实现利用来自 DIType 的类型信息来确定类型标签,这有助于构建准确的源代码级表达式。不同类型将得到适当的处理,从而提高生成的表达式的保真度。

表达式构建

该实现使用提供的 LLVM 指令构建源代码级表达式。它将操作数名称、运算符符号和其他相关组件组合在一起,以创建与原始源代码非常相似的表达式。

LoadInst 和 StoreInst 处理

该实现有效地处理 LoadInst 和 StoreInst 指令。它会生成加载和存储值的源代码表达式,同时考虑指令操作数及其相关的调试元数据。

映射存储

SourceExpressionsMap 有效地存储了为各种 LLVM 值生成的源代码表达式。这种存储机制有助于避免冗余计算,并确保整个分析过程中结果一致。

但是,需要注意的是,生成的源代码表达式采用 C/C++ 风格。考虑到不同的源代码语言及其特性超出了此初始尝试的范围。除了为将 LLVM 值转换为源代码级表达式而开发单独的分析流程之外,还通过将此流程与循环向量化器集成来进一步增强该实现。这种集成允许在循环向量化过程的上下文中报告依赖源指针和目标指针的源代码表达式。此功能为开发人员提供了宝贵的见解,有助于他们理解内存访问模式并促进优化。

现状

该项目已成功交付为加载和存储指令生成源代码表达式的核心功能,涵盖了数组和指针内存引用。虽然最初尝试处理结构体等复杂结构,但该方面目前不在项目范围内。

结构体在 LLVM 中间表示 (IR) 中的复杂表示方式带来了独特的挑战。虽然该项目确实尝试过在最初阶段包含对处理结构体的基本支持,但嵌套结构的复杂性带来了很大的困难。因此,我们在准确提取结构体及其复杂组合的源代码表达式方面遇到了障碍。

代码尚未合并,我们仍然需要其他社区成员对补丁进行审查,拉取请求现在可以在 Github 上追踪到将 LLVM 值映射到源代码级表达式

让我们看看分析流程如何能够为循环中的内存依赖关系提供有用的源代码级表达式。

//test.c

void test_backward_dep(int n, int *A) {
  for (int i = 1; i <= n-3; i += 3) {
    A[i] = A[i-1];
    A[i+1] = A[i+3];
  }
}

使用以下命令生成 LLVM 文件 (*.ll)

clang -O3 -S -g emitllvm test.c

(假设生成了 test.ll 文件)

使用以下 clang 命令编译并发出与循环向量化相关的备注以及源代码表达式

opt -report-source-expr=true -passes='function(loop-vectorize,require<access-info>)' -disable-output -pass-remarks-analysis=loop-vectorize test.ll

注意:引入了命令行选项 ReportSourceExpr 来控制源代码表达式的报告。此选项允许用户切换为加载/存储指针报告源代码表达式。通过将此选项设置为 true (-report-source-expr=true),开发人员可以接收有关与依赖源指针和目标指针相关的源代码表达式的更多信息,从而提高优化报告的质量和深度。

输出备注

remark: test.c:3:12: loop not vectorized: unsafe dependent memory operations in the loop. Use #pragma loop distribute(enable) to allow loop distribution to attempt to isolate the offending operation into a separate loop 
Dependence source: &A[(i + -3)] Dependence destination: &A[(i + 1)]

此输出包含有关由于不安全的依赖内存操作而未被向量化的循环的信息。而对我们来说最有意思的是 Dependence SourceDestination 源代码表达式。

此快速演示展示了如何将分析集成到编译过程中,以提供有关内存访问模式及其对循环向量化的影响的宝贵见解。

挑战和学习

项目期间遇到的一个挑战是集成对结构体等复杂结构的支持。由于这些结构在 LLVM IR 中的复杂性,因此需要专门的处理。但是,这揭示了与 LLVM 的 IR 和调试元数据成功交互所需的深入理解。该项目是一段有趣的旅程,它允许我们深入探索 LLVM IR,并对优化备注和元数据有实际的理解。此外,使用循环向量化器让我们了解了其功能以及与自定义分析的集成。总的来说,该项目是我成为 LLVM 社区积极贡献者的垫脚石。它提供了宝贵的学习机会和对编译器优化和 LLVM 架构的实际见解。

未来工作

• Handling Structs and Complex Data Types
• Support for Other LLVM Instructions
• Accurately build the source expression  when the Optimizations alters the source level data in the IR rigorously. 
• Possible integration with LLVM debugger.
• Support multiple source languages, we would need to define mappings from LLVM constructs to constructs in each target language. 

结束语

我真的很想感谢 LLVM 基金会以及我的导师 Karthik Senthil 和 Satish Guggila 在整个项目中的指导。能够参与这个项目对我来说是一次很棒的体验。我希望我能够继续活跃在 LLVM 和编译器领域。有关此项目的更多详细信息,请参阅此最终报告。欢迎您随时联系我,我的邮箱地址是 physhivam@gmail.com,我们可以讨论此补丁或其他任何问题。