使用 LLVM 框架创建有效的编译器,第 2 部分
使用
clang 预处理 C/C++ 代码
本系列的其他文章
查看 使用
LLVM 框架创建有效的编译器 系列中的更多文章。
使用 LLVM 框架创建一个工作编译器,第 1 部分 探讨了
LLVM 中间表示 (IR)。您手动创建了一个 “Hello World” 测试程序;了解了 LLVM 的一些细微差别(如类型转换);并使用 LLVM 应用程序编程接口 (API) 创建了相同的程序。在这一过程中,您还了解到一些 LLVM 工具,如llc
和 lli
,并了解了如何使用
llvm-gcc 为您发出 LLVM IR。本文是系列文章的第二篇也是最后一篇,探讨了可以与 LLVM 结合使用的其他一些炫酷功能。具体而言,本文将介绍代码测试,即向生成的最终可执行的代码添加信息。本文还简单介绍了 clang,这是 LLVM 的前端,用于支持 C
、C++
和
Objective-C。您可以使用 clang API 对 C/C++
代码进行预处理并生成一个抽象语法树
(AST)。
LLVM 阶段
LLVM 以其提供的优化特性而著名。优化被实现为阶段 (pass)(有关 LLVM 阶段的高级功能的细节,请参见 参考资料)。这里需要注意的是
LLVM 为您提供了使用最少量的代码创建实用阶段 (utility pass) 的功能。例如,如果不希望使用 “hello” 作为函数名称的开头,那么可以使用一个实用阶段来实现这个目的。
了解 LLVM opt 工具
从 opt
的手册页中可以看到,“opt
命令是模块化的
LLVM 优化器和分析器”。一旦您的代码支持定制阶段,您将使用 opt
把代码编译为一个共享库并对其进行加载。如果您的
LLVM 安装进展顺利,那么 opt
应该已经位于您的系统中。opt
命令接受
LLVM IR(扩展名为 .ll)和 LLVM 位码格式(扩展名为 .bc),可以生成 LLVM IR 或位码格式的输出。下面展示了如何使用 opt
加载您的定制共享库:
tintin# opt –load=mycustom_pass.so –help –S
还需注意,从命令行运行 opt
会生成一个 LLVM 将要执行的阶段的细目清单。对
–helphelp
使用 load
选项将生成一条帮助消息,其中包括有关定制阶段的信息。
创建定制的 LLVM 阶段
您需要在 Pass.h 文件中声明 LLVM 阶段,该文件在我的系统中被安装到 /usr/include/llvm 下。该文件将各个阶段的接口定义为 Pass
类的一部分。各个阶段的类型都从 Pass
中派生,也在该文件中进行了声明。阶段类型包括:
BasicBlockPass
类。用于实现本地优化,优化通常每次针对一个基本块或指令运行FunctionPass
类。用于全局优化,每次执行一个功能ModulePass
类。用于执行任何非结构化的过程间优化
由于您打算创建一个阶段,该阶段拒绝任何以 “Hello ” 开头的函数名,因此需要通过从 FunctionPass
派生来创建自己的阶段。从
Pass.h 中复制 清单 1 中的代码。
清单 1. 覆盖 FunctionPass 中的 runOnFunction 类
Class FunctionPass : public Pass {
/// explicit FunctionPass(char &pid) : Pass(PT_Function, pid) {}
/// runOnFunction - Virtual method overridden by subclasses to do the
/// per-function processing of the pass.
///
virtual bool runOnFunction(Function &F) = 0;
/// …
};
同样,BasicBlockPass
类声明了一个 runOnBasicBlock
,而 ModulePass
类声明了 runOnModule
纯虚拟方法。子类需要为虚拟方法提供一个定义。
返回到 清单 1 中的 runOnFunction
方法,您将看到输出为类型 Function
的对象。深入钻研
/usr/include/llvm/Function.h 文件,就会很容易发现 LLVM 使用 Function
类封装了一个 C/C++
函数的功能。而 Function
派生自
Value.h 中定义的 Value
类,并支持 getName
方法。清单
2 显示了代码。
清单 2. 创建一个定制 LLVM 阶段
#include "llvm/Pass.h"
#include "llvm/Function.h"
class TestClass : public llvm::FunctionPass {
public:
virtual bool runOnFunction(llvm::Function &F)
{
if (F.getName().startswith("hello"))
{
std::cout << "Function name starts with hello\n";
}
return false;
}
};
清单 2 中的代码遗漏了两个重要的细节:
FunctionPass
构造函数需要一个char
,用于在
LLVM 内部使用。LLVM 使用char
的地址,因此您可以使用任何内容对它进行初始化。- 您需要通过某种方式让 LLVM 系统理解您所创建的类是一个新阶段。这正是
RegisterPass
LLVM
模板发挥作用的地方。您在 PassSupport.h 头文件中声明了RegisterPass
模板;该文件包含在
Pass.h 中,因此无需额外的标头。
清单 3 展示了完整的代码。
清单 3. 注册 LLVM Function 阶段
class TestClass : public llvm::FunctionPass
{
public:
TestClass() : llvm::FunctionPass(TestClass::ID) { }
virtual bool runOnFunction(llvm::Function &F) {
if (F.getName().startswith("hello")) {
std::cout << "Function name starts with hello\n";
}
return false;
}
static char ID; // could be a global too
};
char TestClass::ID = 'a';
static llvm::RegisterPass<TestClass> global_("test_llvm", "test llvm", false, false);
RegisterPass
模板中的参数 template
是将要在命令行中与 opt
一起使用的阶段的名称。也就是说,您现在所需做的就是在 清单
3 中的代码之外创建一个共享库,然后运行 opt
来加载该库,之后是使用 RegisterPass
注册的命令的名称(在本例中为 test_llvm
),最后是一个位码文件,您的定制阶段将在该文件中与其他阶段一起运行。清单
4 中概述了这些步骤。
清单 4. 运行定制阶段
bash$ g++ -c pass.cpp -I/usr/local/include `llvm-config --cxxflags`
bash$ g++ -shared -o pass.so pass.o -L/usr/local/lib `llvm-config --ldflags -libs`
bash$ opt -load=./pass.so –test_llvm < test.bc
现在让我们了解另一个工具(LLVM 后端的前端):clang。
clang 简介
开始之前的注意事项
Clang 目前正在开发阶段,和相同规模的任何项目一样,项目文档通常要在代码基完成之后才能就绪。因此,最好的方法是查看开发人员邮件列表(参见 参考资料 中的链接)。您可能希望构建并安装
clang 源,为此,您需要执行 clang 起步指南(参见 参考资料)中的说明。注意,要安装到默认的系统文件夹,您需要在构建完成后发出make
命令。本文后面的内容将假设 clang 标头和库分别位于类似于 /usr/local/include 和 /usr/local/lib 的系统文件夹中。
install
LLVM 拥有自己的前端:名为 clang 的一种工具(恰如其分)。Clang 是一种功能强大的C/C++
/Objective-C
编译器,其编译速度可以媲美甚至超过 GNU Compiler Collection (GCC) 工具(参见 参考资料 中的链接,获取更多信息)。更重要的是,clang
拥有一个可修改的代码基,可以轻松实现定制扩展。与在 使用 LLVM
框架创建一个工作编译器,第 1 部分 中对定制插件使用 LLVM 后端 API 的方式非常类似,本文将对 LLVM 前端使用该 API 并开发一些小的应用程序来实现预处理和解析功能。
常见的 clang 类
您需要熟悉一些最常见的 clang 类:
CompilerInstance
Preprocessor
FileManager
SourceManager
DiagnosticsEngine
LangOptions
TargetInfo
ASTConsumer
Sema
ParseAST
也许是最重要的
clang 方法。
稍后将详细介绍 ParseAST
方法。
要实现所有实用的用途,考虑使用适当的 CompilerInstance
编译器。它提供了接口,管理对
AST 的访问,对输入源进行预处理,而且维护目标信息。典型的应用程序需要创建 CompilerInstance
对象来完成有用的功能。清单
5 展示了 CompilerInstance.h 头文件的大致内容。
清单 5. CompilerInstance 类
class CompilerInstance : public ModuleLoader {
/// The options used in this compiler instance.
llvm::IntrusiveRefCntPtr<CompilerInvocation> Invocation;
/// The diagnostics engine instance.
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diagnostics;
/// The target being compiled for.
llvm::IntrusiveRefCntPtr<TargetInfo> Target;
/// The file manager.
llvm::IntrusiveRefCntPtr<FileManager> FileMgr;
/// The source manager.
llvm::IntrusiveRefCntPtr<SourceManager> SourceMgr;
/// The preprocessor.
llvm::IntrusiveRefCntPtr<Preprocessor> PP;
/// The AST context.
llvm::IntrusiveRefCntPtr<ASTContext> Context;
/// The AST consumer.
OwningPtr<ASTConsumer> Consumer;
/// \brief The semantic analysis object.
OwningPtr<Sema> TheSema;
//… the list continues
};
预处理 C 文件
在 clang 中,至少可以使用两种方法创建一个预处理器对象:
- 直接实例化一个
Preprocessor
对象 - 使用
CompilerInstance
类创建一个Preprocessor
对象
让我们首先使用后一种方法。
使用 Helper 和实用工具类实现预处理功能
单独使用 Preprocessor
不会有太大的帮助:您需要 FileManager
和 SourceManager
类来读取文件并跟踪源位置,实现故障诊断。FileManager
类支持文件系统查找、文件系统缓存和目录搜索。查看 FileEntry
类,它为一个源文件定义了
clang 抽象。清单 6 提供了
FileManager.h 头文件的一个摘要。
清单 6. clang FileManager 类
class FileManager : public llvm::RefCountedBase<FileManager> {
FileSystemOptions FileSystemOpts;
/// \brief The virtual directories that we have allocated. For each
/// virtual file (e.g. foo/bar/baz.cpp), we add all of its parent
/// directories (foo/ and foo/bar/) here.
SmallVector<DirectoryEntry*, 4> VirtualDirectoryEntries;
/// \brief The virtual files that we have allocated.
SmallVector<FileEntry*, 4> VirtualFileEntries;
/// NextFileUID - Each FileEntry we create is assigned a unique ID #.
unsigned NextFileUID;
// Statistics.
unsigned NumDirLookups, NumFileLookups;
unsigned NumDirCacheMisses, NumFileCacheMisses;
// …
// Caching.
OwningPtr<FileSystemStatCache> StatCache;
SourceManager
类通常用来查询 SourceLocation
对象。在
SourceManager.h 头文件中,清单
7 提供了有关 SourceLocation
对象的信息。
清单 7. 理解 SourceLocation
/// There are three different types of locations in a file: a spelling
/// location, an expansion location, and a presumed location.
///
/// Given an example of:
/// #define min(x, y) x < y ? x : y
///
/// and then later on a use of min:
/// #line 17
/// return min(a, b);
///
/// The expansion location is the line in the source code where the macro
/// was expanded (the return statement), the spelling location is the
/// location in the source where the macro was originally defined,
/// and the presumed location is where the line directive states that
/// the line is 17, or any other line.
很明显,SourceManager
取决于底层的 FileManager
;事实上,SourceManager
类构造函数接受一个 FileManager
类作为输入参数。最后,您需要跟踪处理源代码时可能出现的错误并进行报告。您可以使用 DiagnosticsEngine
类完成这项工作。和 Preprocessor
一样,您有两个选择:
- 独立创建所有必需的对象
- 使用
CompilerInstance
完成所有工作
让我们使用后一种方法。清单 8 显示了 Preprocessor
的代码;其他任何事情之前已经解释过了。
清单 8. 使用 clang API 创建一个预处理器
using namespace clang;
int main()
{
CompilerInstance ci;
ci.createDiagnostics(0,NULL); // create DiagnosticsEngine
ci.createFileManager(); // create FileManager
ci.createSourceManager(ci.getFileManager()); // create SourceManager
ci.createPreprocessor(); // create Preprocessor
const FileEntry *pFile = ci.getFileManager().getFile("hello.c");
ci.getSourceManager().createMainFileID(pFile);
ci.getPreprocessor().EnterMainSourceFile();
ci.getDiagnosticClient().BeginSourceFile(ci.getLangOpts(), &ci.getPreprocessor());
Token tok;
do {
ci.getPreprocessor().Lex(tok);
if( ci.getDiagnostics().hasErrorOccurred())
break;
ci.getPreprocessor().DumpToken(tok);
std::cerr << std::endl;
} while ( tok.isNot(clang::tok::eof));
ci.getDiagnosticClient().EndSourceFile();
}
清单 8 使用 CompilerInstance
类依次创建 DiagnosticsEngine
(ci.createDiagnostics
方法调用)和FileManager
(ci.createFileManager
和 ci.CreateSourceManager
)。使用 FileEntry
完成文件关联后,继续处理源文件中的每个令牌,直到达到文件的末尾
(EOF)。预处理器的 DumpToken
方法将把令牌转储到屏幕中。
要编译并运行 清单 8 中的代码,使用 清单
9 中的 makefile(针对您的 clang 和 LLVM 安装文件夹进行了相应调整)。主要想法是使用 llvm-config 工具提供任何必需的 LLVM(包含路径和库):您永远不应尝试将这些链接传递到 g++ 命令行。
清单 9. 用于构建预处理器代码的 Makefile
CXX := g++
RTTIFLAG := -fno-rtti
CXXFLAGS := $(shell llvm-config --cxxflags) $(RTTIFLAG)
LLVMLDFLAGS := $(shell llvm-config --ldflags --libs)
DDD := $(shell echo $(LLVMLDFLAGS))
SOURCES = main.cpp
OBJECTS = $(SOURCES:.cpp=.o)
EXES = $(OBJECTS:.o=)
CLANGLIBS = \
-L /usr/local/lib \
-lclangFrontend \
-lclangParse \
-lclangSema \
-lclangAnalysis \
-lclangAST \
-lclangLex \
-lclangBasic \
-lclangDriver \
-lclangSerialization \
-lLLVMMC \
-lLLVMSupport \
all: $(OBJECTS) $(EXES)
%: %.o
$(CXX) -o $@ $< $(CLANGLIBS) $(LLVMLDFLAGS)
编译并运行以上代码后,您应当获得 清单
10 中的输出。
清单 10. 运行清单 7 中的代码时发生崩溃
Assertion failed: (Target && "Compiler instance has no target!"),
function getTarget, file
/Users/Arpan/llvm/tools/clang/lib/Frontend/../..
/include/clang/Frontend/CompilerInstance.h,
line 294.
Abort trap: 6
在这里,您遗漏了 CompilerInstance
设置的最后一部分:即编译代码所针对的目标平台。这里是 TargetInfo
和 TargetOptions
类发挥作用的地方。根据
clang 标头 TargetInfo.h,TargetInfo
类存储有关代码生成的目标系统的所需信息,并且必须在编译或预处理之前创建。和预期的一样,TargetInfo
包含有关整数和浮动宽度、对齐等信息。清单
11 提供了 TargetInfo.h 头文件的摘要。
清单 11. Clang TargetInfo 类
class TargetInfo : public llvm::RefCountedBase<TargetInfo> {
llvm::Triple Triple;
protected:
bool BigEndian;
unsigned char PointerWidth, PointerAlign;
unsigned char IntWidth, IntAlign;
unsigned char HalfWidth, HalfAlign;
unsigned char FloatWidth, FloatAlign;
unsigned char DoubleWidth, DoubleAlign;
unsigned char LongDoubleWidth, LongDoubleAlign;
// …
TargetInfo
类使用两个参数实现初始化:DiagnosticsEngine
和 TargetOptions
。在这两个参数中,对于当前平台,后者必须将 Triple
字符串设置为相应的值。LLVM
此时将发挥作用。清单 12 显示了对 清单
9 所附加的可以使预处理器工作的内容。
清单 12. 为编译器设置目标选项
int main()
{
CompilerInstance ci;
ci.createDiagnostics(0,NULL);
// create TargetOptions
TargetOptions to;
to.Triple = llvm::sys::getDefaultTargetTriple();
// create TargetInfo
TargetInfo *pti = TargetInfo::CreateTargetInfo(ci.getDiagnostics(), to);
ci.setTarget(pti);
// rest of the code same as in Listing 9…
ci.createFileManager();
// …
就这么简单。运行代码并观察简单的 hello.c 测试的输出:
#include <stdio.h>
int main() { printf("hello world!\n"); }
清单 13 展示了部分预处理器输出。
清单 13. 预处理器输出(部分)
typedef 'typedef'
struct 'struct'
identifier '__va_list_tag'
l_brace '{'
unsigned 'unsigned'
identifier 'gp_offset'
semi ';'
unsigned 'unsigned'
identifier 'fp_offset'
semi ';'
void 'void'
star '*'
identifier 'overflow_arg_area'
semi ';'
void 'void'
star '*'
identifier 'reg_save_area'
semi ';'
r_brace '}'
identifier '__va_list_tag'
semi ';' identifier '__va_list_tag'
identifier '__builtin_va_list'
l_square '['
numeric_constant '1'
r_square ']'
semi ';'
手动创建一个 Preprocessor 对象
clang 库的其中一个优点,就是您可以通过多种方法实现相同的效果。在本节中,您将创建一个 Preprocessor
对象,但是不需要直接向CompilerInstance
发出请求。从
Preprocessor.h 头文件中,清单
14 显示了 Preprocessor
的构造函数。
清单 14. 构造一个 Preprocessor 对象
Preprocessor(DiagnosticsEngine &diags, LangOptions &opts,
const TargetInfo *target,
SourceManager &SM, HeaderSearch &Headers,
ModuleLoader &TheModuleLoader,
IdentifierInfoLookup *IILookup = 0,
bool OwnsHeaderSearch = false,
bool DelayInitialization = false);
查看该构造函数,显然,要想让这个预处理器工作,您还需要创建 6 个不同的对象。您已经了解了 DiagnosticsEngine
、TargetInfo
和SourceManager
。CompilerInstance
派生自 ModuleLoader
。因此您必须创建两个新的对象,一个用于 LangOptions
,另一个用于HeaderSearch
。LangOptions
类使您编译一组 C/C++
方言,包括 C99
、C11
和 C++0x
。参考
LangOptions.h 和 LangOptions.def 标头,获取更多信息。最后,HeaderSearch
类存储目录的 std::vector
,用于在其他对象中搜索功能。清单
15 显示了 Preprocessor
的代码。
清单 15. 手动创建的预处理器
using namespace clang;
int main() {
DiagnosticOptions diagnosticOptions;
TextDiagnosticPrinter *printer =
new TextDiagnosticPrinter(llvm::outs(), diagnosticOptions);
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIDs;
DiagnosticsEngine diagnostics(diagIDs, printer);
LangOptions langOpts;
clang::TargetOptions to;
to.Triple = llvm::sys::getDefaultTargetTriple();
TargetInfo *pti = TargetInfo::CreateTargetInfo(diagnostics, to);
FileSystemOptions fsopts;
FileManager fileManager(fsopts);
SourceManager sourceManager(diagnostics, fileManager);
HeaderSearch headerSearch(fileManager, diagnostics, langOpts, pti);
CompilerInstance ci;
Preprocessor preprocessor(diagnostics, langOpts, pti,
sourceManager, headerSearch, ci);
const FileEntry *pFile = fileManager.getFile("test.c");
sourceManager.createMainFileID(pFile);
preprocessor.EnterMainSourceFile();
printer->BeginSourceFile(langOpts, &preprocessor);
// … similar to Listing 8 here on
}
对于 清单 15 中的代码,需要注意以下几点:
- 您没有初始化
HeaderSearch
并使它指向任何特定的目录。但是您应当这样做。 - clang API 要求在堆 (heap) 上分配
TextDiagnosticPrinter
。在栈
(stack) 上分配会引起崩溃。 - 您还不能处理掉
CompilerInstance
。总之是因为您正在使用CompilerInstance
,那么为什么还要费心去手动创建它而不是更舒适地使用
clang API 呢?
语言选择:C++
您目前为止一直使用的是 C
测试代码:那么使用一些 C++
代码如何?向 清单
15 中的代码添加 langOpts.CPlusPlus
,然后尝试使用 清单
= 1;
16 中的测试代码。
清单 16. 对预处理器使用 C++ 测试代码
template <typename T, int n>
struct s {
T array[n];
};
int main() {
s<int, 20> var;
}
清单 17 展示了程序的部分输出。
清单 17. 清单 16 中代码的部分预处理器输出
identifier 'template'
less '<'
identifier 'typename'
identifier 'T'
comma ','
int 'int'
identifier 'n'
greater '>'
struct 'struct'
identifier 's'
l_brace '{'
identifier 'T'
identifier 'array'
l_square '['
identifier 'n'
r_square ']'
semi ';'
r_brace '}'
semi ';'
int 'int'
identifier 'main'
l_paren '('
r_paren ')'
创建一个解析树
clang/Parse/ParseAST.h 中定义的 ParseAST
方法是
clang 提供的重要方法之一。以下是从 ParseAST.h 复制的一个例程声明:
void ParseAST(Preprocessor &pp, ASTConsumer *C,
ASTContext &Ctx, bool PrintStats = false,
TranslationUnitKind TUKind = TU_Complete,
CodeCompleteConsumer *CompletionConsumer = 0);
ASTConsumer
为您提供了一个抽象接口,可以从该接口进行派生。这样做非常合适,因为不同的客户端很可能通过不同的方式转储或处理
AST。您的客户端代码将派生自 ASTConsumer
。ASTContext
类存储有关类型声明的信息和其他信息。最简单的尝试就是使用
clang ASTConsumer API 在您的代码中输出一个全局变量列表。许多技术公司就全局变量在 C++
代码中的使用有非常严格的要求,这应当作为创建定制
lint 工具的出发点。清单
18 中提供了定制 consumer 的代码。
清单 18. 定制 AST consumer 类
class CustomASTConsumer : public ASTConsumer {
public:
CustomASTConsumer () : ASTConsumer() { }
virtual ~ CustomASTConsumer () { }
virtual bool HandleTopLevelDecl(DeclGroupRef decls)
{
clang::DeclGroupRef::iterator it;
for( it = decls.begin(); it != decls.end(); it++)
{
clang::VarDecl *vd = llvm::dyn_cast<clang::VarDecl>(*it);
if(vd)
std::cout << vd->getDeclName().getAsString() << std::endl;;
}
return true;
}
};
您将使用自己的版本覆盖 HandleTopLevelDecl
方法(最初在 ASTConsumer
中提供)。Clang
将全局变量列表传递给您;您对该列表进行迭代并输出变量名称。清单
19 摘录自 ASTConsumer.h,显示了客户端 consumer 代码可以覆盖的一些其他方法。
清单 19. 其他一些可以在客户端代码中覆盖的方法
/// HandleInterestingDecl - Handle the specified interesting declaration. This
/// is called by the AST reader when deserializing things that might interest
/// the consumer. The default implementation forwards to HandleTopLevelDecl.
virtual void HandleInterestingDecl(DeclGroupRef D); /// HandleTranslationUnit - This method is called when the ASTs for entire
/// translation unit have been parsed.
virtual void HandleTranslationUnit(ASTContext &Ctx) {} /// HandleTagDeclDefinition - This callback is invoked each time a TagDecl
/// (e.g. struct, union, enum, class) is completed. This allows the client to
/// hack on the type, which can occur at any point in the file (because these
/// can be defined in declspecs).
virtual void HandleTagDeclDefinition(TagDecl *D) {} /// Note that at this point it does not have a body, its body is
/// instantiated at the end of the translation unit and passed to
/// HandleTopLevelDecl.
virtual void HandleCXXImplicitFunctionInstantiation(FunctionDecl *D) {}
最后,清单 20 显示了您开发的定制
AST consumer 类的实际客户端代码。
清单 20. 使用定制 AST consumer 的客户端代码
int main() {
CompilerInstance ci;
ci.createDiagnostics(0,NULL);
TargetOptions to;
to.Triple = llvm::sys::getDefaultTargetTriple();
TargetInfo *tin = TargetInfo::CreateTargetInfo(ci.getDiagnostics(), to);
ci.setTarget(tin);
ci.createFileManager();
ci.createSourceManager(ci.getFileManager());
ci.createPreprocessor();
ci.createASTContext();
CustomASTConsumer *astConsumer = new CustomASTConsumer ();
ci.setASTConsumer(astConsumer);
const FileEntry *file = ci.getFileManager().getFile("hello.c");
ci.getSourceManager().createMainFileID(file);
ci.getDiagnosticClient().BeginSourceFile(
ci.getLangOpts(), &ci.getPreprocessor());
clang::ParseAST(ci.getPreprocessor(), astConsumer, ci.getASTContext());
ci.getDiagnosticClient().EndSourceFile();
return 0;
}
结束语
本系列的其他文章
查看 使用
LLVM 框架创建有效的编译器 系列中的其他文章。
这篇两部分的系列文章涵盖了大量内容:它探讨了 LLVM IR,提供了通过手动创建和 LLVM API 生成 IR 的方法,展示了如何为 LLVM 后端创建一个定制插件,以及解释了 LLVM 前端及其丰富的标头集。您还了解了如何使用该前端进行预处理和使用 AST。在计算史上,创建一个编译器并进行扩展,特别是针对 C++
等复杂的语言,看上去是个非常复杂的过程,但是有了
LLVM,一切都变得非常简单。文档工作是 LLVM 和 clang 需要继续加强的部分,但是在此之前,我建议尝试 VIM/doxygen 来浏览这些标头。祝您使用愉快!
参考资料
学习
- 在 使用 LLVM 框架创建一个工作编译器,第
1 部分(Arpan Sen,developerWorks,2012 年 6 月)中了解 LLVM 的基础知识。使用功能强大的 LLVM 编译器基础架构优化用任何语言编写的应用程序。构建一个定制编译器现在变得非常简单! - 了解有关 LLVM 阶段 的更多信息。
- 获取 clang 开发人员邮件列表。
- 阅读 Getting Started: Building and Running Clang,获取有关构建和安装
clang 的详细信息。 - 参加 官方 LLVM 教程,获取有关 LLVM 的出色介绍。
- 深入钻研 LLVM Programmer's Manual,这是了解 LLVM API
的必不可少的资源。 - 在 developerWorks 中国网站 Linux 技术专区,查找数百篇 how-to
文章和教程,以及下载、讨论论坛和面向 Linux 开发人员和管理员的其他丰富资源。 - developerWorks 中国网站 Web 开发专区 提供了涵盖各种基于 Web
的解决方案的文章。 - 查看 developerWorks 演示中心,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
- 在 Twitter 上关注 developerWorks。
- 随时关注 developerWorks 技术活动和网络广播。
- 访问 developerWorks Open source 专区获得丰富的
how-to 信息、工具和项目更新以及最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与
IBM 产品结合使用。
获得产品和技术
- 访问 LLVM 项目站点 并下载 最新版本。
- 从 LLVM 站点查找有关 clang 的详细信息。
- IBM 产品评估试用版软件:下载产品试用版,在线试用产品,在云环境中使用产品,或在IBM
SOA 人员沙箱 中花几个小时了解如何高效实现面向服务的架构。
使用 LLVM 框架创建有效的编译器,第 2 部分的更多相关文章
- DLPack构建跨框架的深度学习编译器
DLPack构建跨框架的深度学习编译器 Tensorflow,PyTorch和ApacheMxNet等深度学习框架提供了一个功能强大的工具包,可用于快速进行原型设计和部署深度学习模型.易用性通常是以碎 ...
- 跟着刚哥学习Spring框架--创建HelloWorld项目(一)
1.Spring框架简介 Spring是一个开源框架,Spring是在2003年兴起的一个轻量级的开源框架,由Rod johnson创建.主要对JavaBean的生命周期进行管理的轻量级框架,Spri ...
- [Xcode 实际操作]九、实用进阶-(14)使用富文本CoreText框架创建丰富多彩的文本
目录:[Swift]Xcode实际操作 本文将演示如何使用富文本CoreText框架创建丰富多彩的文本图形. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] imp ...
- 使用express框架创建服务器
上一篇写创建第一个node服务器是基于原始方法写的,express框架的作用就是省掉那些原始代码,直接调用相关的方法就可以了,开发起来简单方便. 一.package.json的配置 首先要进行pack ...
- 如何使用Python的Django框架创建自己的网站
如何使用Python的Django框架创建自己的网站 Django建站主要分四步:1.创建Django项目,2.将网页模板移植到Django项目中,3.数据交互,4.数据库 1创建Django项目 本 ...
- 构建自己的PHP框架--创建组件的机制
在之前的博客中,我们完成了基本的Model类,但是大家应该还记得,我们创建数据库的pdo实例时,是hard好的配置,并且直接hard在Model类中. 代码如下: public static func ...
- LLVM每日谈21 一些编译器和LLVM/Clang代码
作者:闪亮宁(snsn1984) 一些自己的收藏LLVM/Clang代码,而他自己写一些一点点LLVM/Clang译器的代码.在这里把这些代码库分享出来,欢迎大家交流探讨. 1.crange http ...
- CI框架 -- 创建类库
当我们使用 “类库” 这个词的时候,通常我们指的是位于 libraries 这个目录下的那些类. 接下来我们将介绍 如何在 application/libraries 目录下创建你自己的类库,和全局的 ...
- Robot Framework(十四) 扩展RobotFramework框架——创建测试库
4.1创建测试库 Robot Framework的实际测试功能由测试库提供.有许多现有的库,其中一些甚至与核心框架捆绑在一起,但仍然经常需要创建新的库.这个任务并不复杂,因为正如本章所示,Robot ...
- nodejs的express框架创建https服务器
一 openssl创建https私钥和证书 1.下载windows版openssl: http://slproweb.com/products/Win32OpenSSL.html Win64OpenS ...
随机推荐
- Android 获取当前获取焦点的组件
在Activity中,使用this.getCurrentFocus(),获取当前焦点所在的View, 再判断是否是EditText(可调整成其他组件),看个人需要再做特定的逻辑处理 String co ...
- 使用inno setup 打包Pyinstaller生成的文件夹
背景:pyinstaller 6.5.0.Inno Setup 6.2.2 1. 需要先使用pyinstaller打包,生成包括exe在内的可执行文件夹 注意:直接使用pyinstaller打包,生成 ...
- 性能、成本与 POSIX 兼容性比较: JuiceFS vs EFS vs FSx for Lustre
JuiceFS 是一款为云环境设计的分布式高性能文件系统.Amazon EFS 易于使用且可伸缩,适用于多种应用.Amazon FSx for Lustre 则是面向处理快速和大规模数据工作负载的高性 ...
- MyBatis——快速入门
MyBatis 快速入门 查询 tb_user 的所有信息 1.创建tb_user表,添加数据 create database mybatis; use mybatis; drop tab ...
- [OI] 莫比乌斯函数与莫比乌斯反演
9. 莫比乌斯函数与莫比乌斯反演 9.1 莫比乌斯函数 9.1.1 定义 设 \(\mu\) 为莫比乌斯函数,则有: \[\mu(x)=\begin{cases}1\qquad (n=1)\\ 0\q ...
- 普元中间件Primeton AppServer6.5部署SuperMap iServer
本文使用Windows环境普元中间件Primeton AppServer6.5(以下简称PAS)部署SuperMap iServer 一.部署前准备 本文使用SuperMap iServer 11.0 ...
- Hive--hbase--spark
hive创建hbase表 create external table events.hb_train( row_key string, user_id string, event_id string, ...
- o1 式开源推理链项目 g1:可基于 Llama 3.2-90b 模型
g1 简介 g1 是一个开源项目,利用 Llama 3.1 70b 模型在 Groq 硬件上实现类似 OpenAI o1 的推理链能力.项目通过精心设计的提示策略引导语言模型进行逐步推理,解决了传统语 ...
- Linux发布ASPNetCore 项目 IIS 部署
Linux系统发布 ASP.ENT Core 项目 Linux系统-CentOS7 ---基于虚拟机来安装 IP:192.168.1.97 安装教程 链接:https://pan.baidu.com/ ...
- .net 泛型 Generic
什么是泛型 就是不确定的类型