Clang 插件¶
Clang 插件允许在编译期间运行额外的用户定义操作。本文档将提供一个关于如何编写和运行 Clang 插件的基本演练。
简介¶
Clang 插件在代码上运行 FrontendActions。有关如何使用 RecursiveASTVisitor
编写 FrontendAction
,请参见 FrontendAction 教程。在本教程中,我们将演示如何编写一个简单的 clang 插件。
编写一个 PluginASTAction
¶
与编写普通的 FrontendActions
的主要区别在于,您可以处理插件命令行选项。 PluginASTAction
基类声明了一个 ParseArgs
方法,您必须在插件中实现该方法。
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string>& args) {
for (unsigned i = 0, e = args.size(); i != e; ++i) {
if (args[i] == "-some-arg") {
// Handle the command line argument.
}
}
return true;
}
注册插件¶
插件是在运行时由编译器从动态库加载的。要在库中注册插件,请使用 FrontendPluginRegistry::Add<>
static FrontendPluginRegistry::Add<MyPlugin> X("my-plugin-name", "my plugin description");
定义编译指示¶
插件还可以通过声明一个 PragmaHandler
并使用 PragmaHandlerRegistry::Add<>
注册它来定义编译指示。
// Define a pragma handler for #pragma example_pragma
class ExamplePragmaHandler : public PragmaHandler {
public:
ExamplePragmaHandler() : PragmaHandler("example_pragma") { }
void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
Token &PragmaTok) {
// Handle the pragma
}
};
static PragmaHandlerRegistry::Add<ExamplePragmaHandler> Y("example_pragma","example pragma description");
定义属性¶
插件可以通过声明一个 ParsedAttrInfo
并使用 ParsedAttrInfoRegister::Add<>
注册它来定义属性。
class ExampleAttrInfo : public ParsedAttrInfo {
public:
ExampleAttrInfo() {
Spellings.push_back({ParsedAttr::AS_GNU,"example"});
}
AttrHandling handleDeclAttribute(Sema &S, Decl *D,
const ParsedAttr &Attr) const override {
// Handle the attribute
return AttributeApplied;
}
};
static ParsedAttrInfoRegistry::Add<ExampleAttrInfo> Z("example_attr","example attribute description");
插件属性必须定义的 ParsedAttrInfo
成员是
Spellings
,必须使用属性的每个 拼写 来填充,每个拼写都包含一个属性语法以及该语法如何拼写属性名称。如果语法允许范围,则拼写必须是“scope::attr”(如果存在范围)或“::attr”(如果不存在)。
根据属性的不同,可能需要定义的 ParsedAttrInfo
成员是
NumArgs
和OptArgs
,它们设置属性的必需参数和可选参数的数量。
diagAppertainsToDecl
,它检查属性是否已在正确类型的声明上使用,如果未使用,则发出诊断。
handleDeclAttribute
,它是将属性应用于声明的函数。它负责检查属性的参数是否有效,并且通常通过向Decl
添加一个Attr
来应用属性。它返回AttributeApplied
(表示属性已成功应用)或AttributeNotApplied
(如果未应用)。
diagAppertainsToStmt
,它检查属性是否已在正确类型的语句上使用,如果未使用,则发出诊断。
handleStmtAttribute
,它是将属性应用于语句的函数。它负责检查属性的参数是否有效,并且通常通过向Stmt
添加一个Attr
来应用属性。它返回AttributeApplied
(表示属性已成功应用)或AttributeNotApplied
(如果未应用)。
diagLangOpts
,它检查属性是否允许用于当前语言模式,如果未允许,则发出诊断。
existsInTarget
,它检查属性是否允许用于给定的目标。
要查看属性插件的实际示例,请参见 Attribute.cpp 示例。
综合起来¶
让我们来看一个打印顶级函数名的插件示例。此示例已检入 clang 存储库;请查看 PrintFunctionNames.cpp 的最新版本。
运行插件¶
使用编译器驱动程序¶
Clang 驱动程序接受 -fplugin 选项来加载插件。Clang 插件可以通过 fplugin-arg-<插件名称>-<参数> 选项从编译器驱动程序命令行接收参数。使用此方法,插件名称本身不能包含连字符,但传递给插件的参数可以包含连字符。
$ export BD=/path/to/build/directory
$ make -C $BD CallSuperAttr
$ clang++ -fplugin=$BD/lib/CallSuperAttr.so \
-fplugin-arg-call_super_plugin-help \
test.cpp
如果您的插件名称包含连字符,请重命名插件或使用下面列出的 cc1 命令行选项。
使用 cc1 命令行¶
要运行插件,必须通过 -load 命令行选项加载包含插件注册表的动态库。这将加载所有已注册的插件,您可以通过指定 -plugin 选项来选择要运行的插件。可以使用 -plugin-arg-<插件名称> 传递插件的其他参数。
请注意,这些选项必须到达 clang 的 cc1 进程。有两种方法可以做到这一点:
通过使用 -cc1 选项直接调用解析过程;这样做有缺点,即不会配置默认的标题搜索路径,因此您需要在命令行上指定完整的系统路径配置。
照常使用 clang,但将所有传递给 cc1 进程的参数都加上 -Xclang 前缀。
例如,要通过 clang 在源文件上运行 print-function-names
插件,首先构建插件,然后从源代码树中使用插件调用 clang
$ export BD=/path/to/build/directory
$ (cd $BD && make PrintFunctionNames )
$ clang++ -D_GNU_SOURCE -D_DEBUG -D__STDC_CONSTANT_MACROS \
-D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -D_GNU_SOURCE \
-I$BD/tools/clang/include -Itools/clang/include -I$BD/include -Iinclude \
tools/clang/tools/clang-check/ClangCheck.cpp -fsyntax-only \
-Xclang -load -Xclang $BD/lib/PrintFunctionNames.so -Xclang \
-plugin -Xclang print-fns
另请参阅 print-function-name 插件示例的 README
使用 clang 命令行¶
在 clang 命令行上使用 -fplugin=plugin 会将插件作为参数传递给 cc1 命令行上的 -load。如果插件类实现了 getActionType
方法,则该插件将自动运行。例如,要在主 AST 操作之后自动运行插件(即与使用 -add-plugin 相同):
// Automatically run the plugin after the main AST action
PluginASTAction::ActionType getActionType() override {
return AddAfterMainAction;
}
与 -clear-ast-before-backend
的交互¶
为了降低编译器的峰值内存使用量,建议插件在主要操作(通常是代码生成)之前运行。这是因为,如果任何插件在代码生成操作之后运行,就会自动关闭 -clear-ast-before-backend
。 -clear-ast-before-backend
通过在生成 IR 之后、运行 IR 优化之前清除 Clang AST 来降低峰值内存使用量。将 CmdlineBeforeMainAction
或 AddBeforeMainAction
作为 getActionType
来运行插件,同时仍然可以从 -clear-ast-before-backend
中获益。插件必须确保不要修改 AST,否则应该在主操作之后运行。