支持库¶
摘要¶
本文档提供了一些关于 LLVM 支持库的详细信息,该库位于源代码的 lib/Support
和 include/llvm/Support
目录下。该库的目的是使 LLVM 免受少数 LLVM 需要操作系统提供的服务的操作系统差异的影响。LLVM 的大部分代码都是使用标准 C++ 的可移植性特性编写的。但是,在少数几个领域,需要使用系统相关的功能,而支持库则是这些系统调用的包装器。
通过集中 LLVM 对操作系统接口的使用,我们可以使 LLVM 工具链和运行时库更容易移植到新的平台,因为(理论上)只需要移植 lib/Support
。该库还可以使 LLVM 的其余部分免受 #ifdef 使用和特定操作系统的特殊情况的影响。此类用法将替换为对 include/llvm/Support
中提供的接口的简单调用。
请注意,支持库并非旨在成为一个完整的操作系统包装器(例如自适应通信环境 (ACE) 或 Apache 可移植运行时 (APR)),而只是提供支持 LLVM 所需的功能。
支持库最初被称为系统库,由 Reid Spencer 编写,他基于源自可扩展编程系统 (XPS) 的类似工作制定了设计。许多人帮助完成了这项工作;特别是 Jeff Cohen 和 Henrik Bach 参与了 Win32 移植。
保持 LLVM 的可移植性¶
为了保持 LLVM 的可移植性,LLVM 开发人员应该遵守与支持库相关的一套可移植性规则。遵守这些规则应该有助于支持库实现其目标,即保护 LLVM 免受操作系统接口变化的影响,并以高效的方式实现这一目标。以下部分定义了实现此目标所需的规则。
不要包含系统头文件¶
除了 lib/Support
之外,任何 LLVM 源代码都不应直接 #include
系统头文件。在开发 lib/Support
的过程中,已经小心地从 LLVM 中删除了所有此类 #includes
。具体来说,这意味着像“unistd.h
”、“windows.h
”、“stdio.h
”和“string.h
”这样的头文件被禁止包含在 lib/Support
实现之外的 LLVM 源代码中。
要获得系统相关的功能,应使用 include/llvm/Support
中找到的现有系统接口。如果合适的接口不可用,则应将其添加到 include/llvm/Support
中,并在 lib/Support
中为所有支持的平台实现它。
不要公开系统头文件¶
支持库必须保护 LLVM 免受**所有**系统头文件的影响。要获得系统级功能,LLVM 源代码必须 #include "llvm/Support/Thing.h"
且仅此而已。这意味着 Thing.h
不能公开任何系统头文件。这可以防止 LLVM 意外使用系统特定功能,并且仅允许通过 lib/Support
接口使用它。
使用标准 C 头文件¶
**标准** C 头文件(以“c”开头)允许通过 lib/Support
接口公开。这些头文件及其声明的内容被认为是平台无关的。LLVM 源文件可以直接包含它们,或者通过 lib/Support
接口包含它们。
使用标准 C++ 头文件¶
来自标准 C++ 库和标准模板库的**标准** C++ 头文件可以通过 lib/Support
接口公开。这些头文件及其声明的内容被认为是平台无关的。LLVM 源文件可以包含它们,或者通过 lib/Support
接口包含它们。
高级接口¶
lib/Support
接口中指定的入口点必须旨在完成 LLVM 所需的一些合理的高级任务。我们不想仅仅包装每个操作系统调用。最好是包装几个始终与 LLVM 结合使用的操作系统调用。
例如,考虑执行程序、等待其完成并返回其结果代码需要什么。在 Unix 上,这涉及以下操作系统调用:getenv
、fork
、execve
和 wait
。对于 lib/Support
来说,正确的方法是提供一个函数,例如 ExecuteProgramAndWait
,它完全实现了该功能。我们不希望的是对所涉及的操作系统调用的包装器。
操作系统调用与支持库的接口之间**不能**存在一对一的关系。任何此类接口函数都将是可疑的。
没有未使用的功能¶
lib/Support
接口中指定的任何功能都不应被 LLVM 实际使用。我们在这里不是编写一个通用的操作系统包装器,而只是满足 LLVM 的需求。而且,LLVM 的需求并不多。此设计目标旨在使 lib/Support
接口保持简洁易懂,这应该有助于其实际使用和采用。
没有重复的实现¶
给定平台的函数实现必须且只能编写一次。这意味着如果这些操作系统可以共享相同的实现,则可以将函数的实现应用于多个操作系统。此规则适用于给定操作系统类(例如 Unix、Win32)支持的操作系统集。
没有虚方法¶
LLVM 可以非常频繁地调用支持库接口。为了使这些调用尽可能高效,我们不鼓励使用虚方法。没有必要使用继承来实现差异,这只会增加复杂性。#include
机制可以很好地工作。
没有公开的函数¶
系统库定义的任何函数(即不是由 lib/Support
定义的)都不应通过 lib/Support
接口公开,即使该函数的头文件未公开也是如此。这可以防止无意中使用系统特定功能。
例如,stat
系统调用因其提供的数据存在差异而臭名昭著。lib/Support
不得声明 stat
也不得允许其被声明。相反,它应该提供自己的接口来发现有关文件和目录的信息。这些接口可以使用 stat
来实现,但这严格来说是一个实现细节。支持库提供的接口必须在所有平台上实现(即使那些没有 stat
的平台)。
没有公开的数据¶
系统库定义的任何数据(即不是由 lib/Support
定义的)都不应通过 lib/Support
接口公开,即使该函数的头文件未公开也是如此。与函数一样,这可以防止无意中使用可能并非所有平台都存在的数据。
最大限度地减少软错误¶
操作系统接口通常会为可能出错的每一件小事提供错误结果。在几乎所有情况下,您可以将这些错误结果分为两组:正常/好/软和异常/坏/硬。也就是说,一些错误只是信息,例如“文件未找到”、“权限不足”等,而其他错误则要困难得多,例如“空间不足”、“磁盘扇区错误”或“系统调用中断”。我们将第一组称为“软”错误,第二组称为“硬”错误。
lib/Support
必须始终尝试最大限度地减少软错误。这是一个设计要求,因为软错误的最小化会影响接口的粒度和性质。一般来说,如果您发现自己想要抛出软错误,则必须检查接口的粒度,因为您可能正在尝试实现一些级别太低的错误。经验法则是提供**无法**失败的接口函数,除非遇到硬错误。
举一个简单的例子,假设我们要添加一个“OpenFileForWriting
”函数。对于许多操作系统,如果文件不存在,尝试打开文件将产生错误。但是,lib/Support
不应在发生这种情况时简单地抛出该错误,因为这是一个软错误。问题在于接口函数 OpenFileForWriting
的级别太低。它应该是 OpenOrCreateFileForWriting
。在软“不存在”错误的情况下,此函数将创建它,然后将其打开以进行写入。
这个设计原则需要在lib/Support
中维护,因为它避免了软错误处理在 LLVM 其余部分的传播。硬错误通常会导致 LLVM 工具终止,所以不要犹豫地抛出它们。
经验法则
不要抛出软错误,只抛出硬错误。
如果你想抛出一个软错误,重新考虑一下接口。
在内部处理最常见的正常/良好/软错误情况,这样 LLVM 的其他部分就不必处理了。
无抛出规范¶
任何lib/Support
接口函数都不能声明 C++ throw()
规范。此要求确保编译器不会将额外的异常处理代码插入接口函数中。这是一个性能考虑因素:lib/Support
函数位于许多调用链的底部,因此可能被频繁调用。我们需要它们尽可能高效。但是,系统库中的任何例程都不应该实际抛出异常。
代码组织¶
Support 库接口的实现按其操作系统的一般类别进行分离。目前仅定义了 Unix 和 Win32 类,但可以为其他操作系统分类添加更多类。为了区分要编译哪个实现,lib/Support
中的代码使用了LLVM_ON_UNIX
和_WIN32
#defines
。每个lib/Support
中的源文件,在实现通用(与操作系统无关)功能后,需要使用一组#if defined(LLVM_ON_XYZ)
指令包含正确的实现。例如,如果我们有lib/Support/Path.cpp
,我们期望在该文件中看到
#if defined(LLVM_ON_UNIX)
#include "Unix/Path.inc"
#endif
#if defined(_WIN32)
#include "Windows/Path.inc"
#endif
在lib/Support/Unix/Path.inc
中的实现应该处理所有 Unix 变体。在lib/Support/Windows/Path.inc
中的实现应该处理所有 Windows 变体。这样做的目的是快速增加将提供实现的操作系统基本类别。给定平台的具体细节仍然必须通过使用#ifdef
来确定。
一致的语义¶
lib/Support
接口的实现可能在不同平台之间差异很大。只要接口函数的最终结果相同,这就可以。例如,创建目录的函数在所有操作系统上都非常简单。另一方面,System V IPC 甚至并非所有平台都支持。lib/Support
不应该“支持”System V IPC,而应该提供一个用于进程间通信基本概念的接口。如果可用,实现可以使用 System V IPC 或命名管道,或者任何可以有效地完成给定操作系统工作的方案。在所有情况下,接口和实现都必须在语义上保持一致。