天天看点

Clang:一个面向LLVM的C语言家族前端

Clang:一个用于 LLVM 的前端

      • 1. 特性和目标
        • 1.1 终端用户特性
          • 1.1.1 快速编译和低内存使用
          • 1.1.2 富有表现力的诊断
          • 1.1.3 GCC的兼容性
        • 1.2 实用程序和应用程序
          • 1.2.1 基于库的架构
          • 1.2.2 支持不同的 Clients
          • 1.2.3 与 IDEs 的集成
          • 1.2.4 使用 LLVM 'BSD' 许可证
        • 1.3 内部设计与实现
          • 1.3.1 一个真实的,高质量的编译器
          • 1.3.2 一个简单且可编程的代码库
          • 1.3.3 一个用于 C、Objective C、C++ 和 Objective C++ 的统一解析器
          • 1.3.4 符合 C/C++/Objective C 及其变体
      • 2. 为什么?
      • 3. 当前的状态
      • 4. 参与进来吧!

Clang

项目为 LLVM 项目提供了 C 语言家族(C、C++、Objective C/C++、OpenCL、CUDA 和 RenderScript)中的语言的语言前端和工具基础结构。提供了一个 GCC 兼容的编译器驱动程序

(clang)

和一个 MSVC 兼容的编译器驱动程序

(clang-cl.exe)

。您可以在今天 获取并构建源代码。

1. 特性和目标

该项目的一些目标包括:

终端用户的特性:

  • 快速编译和低内存使用
  • 富有表现力的诊断(例子)
  • GCC 的兼容性

实用程序和应用程序:

  • 基于模块库的体系结构
  • 支持不同的客户端(重构、静态分析、代码生成等)
  • 允许与 IDEs 紧密集成
  • 使用 LLVM ‘BSD’ 许可证

内部设计与实现:

  • 一个真实的,高质量的编译器
  • 一个简单且可编程的代码库
  • 一个用于 C、Objective C、C++ 和 Objective C++ 的统一解析器
  • 符合 C/C++/ObjC 及其变体

1.1 终端用户特性

1.1.1 快速编译和低内存使用

clang 的主要工作重点是使其快速、轻便和可伸缩。clang基于库的体系结构使它可以直接分析栈的每一层的时间和成本,并且驱动程序有许多用于性能分析的选项。许多详细的基准可以在网上找到。

编译时性能很重要,但是当使用clang作为API时,内存的使用往往更重要:代码占用的内存越少,一次可以放入内存的代码就越多(例如,对于整个程序分析工具很有用)。

除了在批处理模式中与GCC进行正面对抗时效率更高之外,clang还使用基于库的架构构建,这使得对clang进行调整并使用它构建新工具变得相对容易。这意味着常常可以应用开箱即用的思想和新技术以各种方式改进编译。

1.1.2 富有表现力的诊断

除了快速和功能,我们的目标是使Clang非常友好的用户。就命令行编译器而言,这基本上可以归结为使编译器生成的诊断(错误和警告消息)尽可能有用。我们有几种方法可以做到这一点,但最重要的是准确地指出程序中的错误,突出显示相关信息,使其一目了然,并使措辞尽可能清晰。

下面是一个简单的例子,说明了典型的GCC和Clang诊断之间的区别:

$ gcc-4.9 -fsyntax-only t.c
  t.c: In function 'int f(int, int)':
  t.c:7:39: error: invalid operands to binary + (have 'int' and 'struct A')
     return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);
                                         ^
  $ clang -fsyntax-only t.c
  t.c:7:39: error: invalid operands to binary expression ('int' and 'struct A')
    return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);
                         ~~~~~~~~~~~~~~ ^ ~~~~~
           

在这里,您可以看到甚至不需要查看原始源代码就可以根据Clang错误理解出了什么问题:因为Clang打印了一个插入符号,所以您确切地知道它所抱怨的是哪个。范围信息突出了加号的左边和右边,这使编译器所谈论的内容一目了然,这对于涉及优先级问题和许多其他情况的情况非常有用。

Clang诊断学非常完善,具有许多特性。有关更多信息和示例,请参见富有表现力的诊断页面。

1.1.3 GCC的兼容性

GCC目前是标准的开源编译器,它通常会编译大量的代码。GCC支持大量的扩展和特性(其中许多是没有文档说明的),为了构建,许多代码和头文件都依赖于这些特性。

虽然能够忽略这些扩展并将重点放在严格实现语言标准上是很好的,但是语用学迫使我们支持使用最多的GCC扩展。许多用户只想编译他们的代码,他们不关心它是否是C99。

如上所述,所有扩展都被显式地识别为扩展诊断,并使用扩展诊断进行标记,扩展诊断可以映射到警告、错误或忽略。

1.2 实用程序和应用程序

1.2.1 基于库的架构

Clang

的一个主要设计概念是使用基于库的体系结构。在这个设计中,前端的各个部分可以被清晰地划分为单独的库,然后可以根据不同的需求和用途混合使用。此外,基于库的方法鼓励良好的接口,并使新开发人员更容易参与其中(因为他们只需要了解全局的一小部分)。

“世界需要更好的编译器工具,这些工具是作为库构建的。这个设计点允许以新颖的方式重用工具。然而,仅仅将工具构建为库是不够的:它们必须具有清晰的 APIs,尽可能彼此解耦,并且易于修改/扩展。这就需要清晰的分层、良好的设计、以及保持库独立于任何特定的客户端。”

目前,

Clang

分为以下库和工具:

  • libsupport

    —— 基本支持库,来自 LLVM。
  • libsystem

    —— 系统抽象库,来自 LLVM。
  • libbasic

    —— 诊断、源代码定位、源代码缓冲区抽象、输入源文件的文件系统缓存。
  • libast

    —— 提供类来表示 C AST、C 类型系统、内置函数和用于分析和操作 AST 的各种帮助程序(visitors、很棒的 printers 等)。
  • liblex

    —— 词法分析和预处理、标识符哈希表、编译指示处理、tokens 和宏展开。
  • libparse

    —— 解析。这个库调用客户端提供的粗粒度“Actions”(例如 libsema 构建 ASTs),但对 ASTs 或其他客户端特定的数据结构一无所知。
  • libsema

    —— 语义分析。这提供了一组解析器 actions 来为程序构建一个标准化的 AST。
  • libcodegen

    —— 将 AST 降低到 LLVM IR 以进行优化和代码生成。
  • librewrite

    —— 文本缓冲区的编辑(对于代码重写转换非常重要,比如重构)。
  • libanalysis

    —— 静态分析支持。
  • clang

    —— 一个驱动程序,各级库的客户端。

作为这个基于库的设计的强大功能的一个例子…… 如果您想要构建一个预处理器(preprocessor),您可以使用 Basic 库和 Lexer 库。如果您想要构建一个索引器(indexer),您可以使用前两个库,并添加 Parser 库和一些用于索引的 actions。如果您想要构建一个重构、静态分析或源代码到源代码的编译器工具,那么您可以添加 AST building 和 semantic analyzer 库。

有关各种 Clang 库的底层实现细节的更多信息,请参阅 clang 内部手册。

1.2.2 支持不同的 Clients

Clang 的设计和构建有许多宏伟的计划,以便我们可以如何使用它。驱动因素是我们每天都在使用 C 和 C++,而且由于缺乏可用的好工具,我们不得不为此付出代价。我们相信 C 和 C++ 工具的生态系统已经被这些语言的源代码的解析和表示的困难程度显著地限制了,我们的目标是在 Clang 中解决这个问题。

这个目标的问题是不同的 clients 有非常不同的需求。考虑代码生成(code generation),例如:一个用于代码生成解析的简单前端必须分析代码的有效性,并以某种中间形式发出代码,以便传递给优化器或后端。由于有效性分析和代码生成在很大程度上可以动态完成,所以并不要求前端为代码中的所有表达式和语句构建一个完整的 AST。TCC 和 GCC 是编译器的例子,它们要么不构建真正的 AST(在前一种情况下),要么构建简化的 AST(在后一种情况下),因为它们主要关注 codegen。

另一方面,一些 clients(比如重构) 需要关于原始源代码的非常详细的信息,并且需要一个完整的 AST 来描述它。重构需要关于宏扩展、每个 paren 表达式 ‘(((x)))’ vs ‘x’ 的位置、完整的位置信息等等的信息。此外,重构希望查看整个程序,以确保正在进行的转换是安全的。要使它高效并正确地运行,需要大量的工程和算法工作,而这些工作对于一个简单的静态编译器来说是不必要的。

Clang 解决方案的美妙之处在于它不限制您如何使用它。特别地,可以使用 clang preprocessor 和 parser 构建一个非常快速、轻量级的实时代码生成器(类似于TCC),它根本不构建 AST。作为一个中间步骤,clang 支持使用当前 AST 生成和语义分析代码,并在代码生成后为每个函数释放AST。最后,clang 提供了对构建和保留成熟的 ASTs 的支持,甚至支持将它们写入磁盘。

使用清晰而简单的 APIs 设计库允许在 clients 中确定这些高级策略决策,而不是在实现这些库时强制采用“一种真正的方法”。做对一件事很难,而且我们并不总是第一次就能做对,但是当我们意识到自己犯了错误时,我们就能解决任何问题。

1.2.3 与 IDEs 的集成

我们相信,集成开发环境(IDE)是一个很好的方法,可以将开发难题的各个部分组合在一起,并使 clang 能够在这样的环境中很好地工作。IDE 的主要优点是,它们通常在整个项目中具有可视性,并且是长生命周期的进程,而独立编译器工具通常在项目中的每个单独文件上调用,因此范围有限。

这种差异有很多含义,但一个重要的含义与效率和缓存有关:在一个项目中跨不同文件共享一个地址空间,意味着可以使用智能缓存和其他技术来显著减少分析/编译时间。

IDE 和批处理编译器的另一个区别是,它们对前端的要求常常非常不同:它们依赖于高性能来提供“快速”的体验,因此非常需要“增量编译”、“模糊解析”等技术。最后,IDE 通常与代码生成有非常不同的需求,通常需要仅使用 codegen 的前端就可以丢弃的信息。Clang 是专门为捕获这些信息而设计和构建的。

1.2.4 使用 LLVM ‘BSD’ 许可证

我们积极地打算将 Clang(以及整个 LLVM) 用于商业项目,不仅作为独立的编译器,而且作为嵌入到专有应用程序中的库。BSD许可是实现这一点的最简单方法。我们认为,许可鼓励贡献者选择源代码并使用它,并且相信,如果这些个人和组织不想永远维护一个fork(当涉及到合并时,这是费时且昂贵的),那么他们将贡献自己的工作。此外,现在没有人靠编译器赚钱,但许多人需要它们来实现更大的目标:每个人一起工作是有意义的。

有关 LLVM/Clang 许可证的更多信息,请参见 LLVM 许可证描述。

1.3 内部设计与实现

1.3.1 一个真实的,高质量的编译器

Clang 是由经验丰富的编译器开发人员设计和构建的,他们对现有的开源编译器所存在的问题越来越感到沮丧。Clang 经过精心设计和构建,为新一代 C/C++/Objective C 开发工具的开发打下了基础,我们希望它的目标是产品质量。

成为一个高质量的产品编译器意味着很多事情:它意味着高性能、可靠性和(相对)没有bug,并且最终被广泛的人使用和依赖。虽然我们仍处于早期发展阶段,但我们坚信这将成为现实。

1.3.2 一个简单且可编程的代码库

我们的目标是使任何对编译器有基本了解并具有 C/C++/Objective C 语言工作知识的人都能够理解和扩展 clang 源代码库。这在很大程度上是因为我们决定让 AST 镜像尽可能地接近这些语言:您有友好的 if 语句、for 语句、圆括号表达式、结构体、union等等,所有这些都以简单而明确的方式表示。

除了简单的设计之外,我们还通过对源代码进行良好的注释,包括在适当的地方引用语言标准,并为简单性而设计代码,从而使源代码变得可访问。除此之外,clang 还提供了一组 AST 转储程序、printers 和可视化工具,可以方便地将代码放入其中并查看代码是如何表示的。

1.3.3 一个用于 C、Objective C、C++ 和 Objective C++ 的统一解析器

Clang 是“C语言家族前端”,这意味着我们打算支持 C 家族中最受欢迎的成员。我们确信,这类语言的正确解析技术是手工构建的递归下降解析器。由于它是普通的 C++ 代码,递归下降使新开发人员非常容易理解代码,它很容易支持 C/C++ 所需的特殊规则和其他奇怪的技巧,并使它能够直接实现出色的诊断和错误恢复。

我们认为,在一个统一的解析器中实现 C/C++/Objective C 比维护一个单独的 C 和 C++ 解析器更容易维护和发展最终结果。

1.3.4 符合 C/C++/Objective C 及其变体

当你开始着手实现一种语言时,你会发现这种语言的工作原理和大多数人理解它的工作原理之间存在着巨大的差距。这个差距是一个普通程序员和一个(可怕的?超自然?)“语言律师”,通晓语言的来由,能轻松掌握标准语言。

在实践中,与语言保持一致意味着我们的目标是支持完整的语言,包括黑暗和尘土飞扬的角落(如 trigraphs、预处理器 arcana、C99 VLAs 等)。我们支持扩展超出标准正式允许,我们努力显式地调用这个代码中,发出警告(默认是禁用的,但可以被映射到警告或错误),允许您使用 Clang 在“严格”模式,如果你愿意的话。

我们还打算支持这些语言的“方言”,如 C89、K&R C、C++'03、Objective-C 2 等。

2. 为什么?

开发新的前端是出于对编译器的需求,它允许更好的诊断、更好地与ide集成、与商业产品兼容的许可证,以及易于开发和维护的灵活编译器。所有这些都是开始一个新的前端工作的动机,可以满足这些需求。

有关Clang与其他编译器之间的更详细比较,请参见 Clang 比较页面。

3. 当前的状态

当以X86-32、X86-64和ARM为目标时,Clang被认为是一个高质量的 C、Objective C、C++ 和 Objective C++ 编译器(其他目标可能有一些警告,但通常很容易修复)。例如,Clang在生产中用于构建性能关键的软件,如 Chrome 或Firefox。

如果您正在寻找源代码分析或源代码到源代码转换工具,Clang可能是一个很好的解决方案。Clang支持c++ 11、c++ 14和c++ 17,更多信息请参见C++状态页。

4. 参与进来吧!

首先获取代码、构建代码、并使用它。这将向你展示我们今天可以做的事情,并让你有第一手的“Clang体验”:希望它能与你产生“共鸣”。)

一旦你这样做了,请考虑加入 Clang 社区。Clang 开发人员包括许多具有不同背景的志愿者。如果您对Clang的开发感兴趣,那么注册一个邮件列表是了解项目如何工作的好方法。