如果RSS导入的格式有问题,请查看原文。
这回介绍一下怎么写一个GCC Plugin。上次提到了用gcc -print-file-name=plugin检测gcc是否支持plugin,如果支持的话,则相应的开发需要的头文件就在返回目录的/include下。其中最重要的一个就是gcc-plugin.h,plugin所用到的一些结构声明和注册函数等包含在这个头文件里。
在plugin文件中,我们必须声明一个int型的名字为plugin_is_GPL_compatible的全局变量,用来表示这个插件是在GPL或在于GPL兼容的协议下开发的。否则在编译插件的时候,就会报错。
GCC在使用插件的时候会调用plugin_init()这个函数,用来注册plugin,因此plugin_init()函数也就是所有plugin起始的函数,不懂的话,可以理解结为main()函数。这个函数的原型如下:
int
plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version);
第一个参数比较有用,这个结构体里包含的信息有plugin的名字以及在编译时,用户传给这个插件的参数。第二个参数就是一些描述gcc版本等信息的结构,我个人认为用处不大。
gcc调用plugin_init()时,还未开始正式的编译工作,所以这里主要进行一下初始化的工作就可以了,当然最重要的就是注册callback函数。注册callback函数需要使用register_callback()函数,原型如下:
extern void
register_callback (const char *plugin_name,
int event,
plugin_callback_func callback,
void *user_data);
这个函数的意义就是当发生event事件的时候,执行plugin_name这个插件中的callback函数,并且用户可以在注册时传入自定义数据user_data,并且在时间发生时使用这些数据,不过plugin_name貌似传什么都没关系。event可选择的值是在如下的枚举结构中定义的。
enum plugin_event
{
PLUGIN_PASS_MANAGER_SETUP,
PLUGIN_FINISH_TYPE,
PLUGIN_FINISH_UNIT,
PLUGIN_PRE_GENERICIZE,
... ...
PLUGIN_NEW_PASS,
PLUGIN_EVENT_FIRST_DYNAMIC
};
除了最后一个是一个Dummy Event以外,其他都是可以使用的事件类型,具体可以在GCC Internal Manual中或者头文件中自己查。我之后会选PLUGIN_PASS_MANAGER_SETUP,和PLUGIN_PRE_GENERICIZE介绍。
callback函数,可以看到它的类型是plugin_callback_func,这个类型的声明如下:
typedef void (*plugin_callback_func)(void *gcc_data, void *user_data);
其中gcc_data是指在GCC可能利用这个指针,给callback函数传一些数据,具体传什么是由具体的事件决定的,而user_data一般就是我们在注册的时候传入的那个数据。没用用过函数指针的话,看到上面的声明,也不用害怕,在使用的时候,声明一个如下的函数就可以做为callback函数了,我结合一个例子一起说。
static void
my_callback_func (void *gcc_data, void *user_data)
{
... ...
}
int
plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
... ...
register_callback(plugin_info->fullname, PLUGIN_PRE_GENERICIZE,
my_plugin_callback, NULL);
... ...
return 0;
}
这样,当GCC触发PLUGIN_PRE_GENERICIZE事件的时候,就会调用我们的my_plugin_callback()函数了。
下面说说PLUGIN_PRE_GENERICIZE和PLUGIN_PASS_MANAGER_SETUP事件。PLUGIN_PRE_GENERICIZE事件是每当GCC分析完一个函数获得抽象语法树(AST)后,就会被触发,同时将这个函数的AST的根节点作为gcc_data返回给callback函数。
PLUGIN_PASS_MANAGER_SETUP事件是用来在GCC的GIMPLE PASSES中或者在RTL PASSES中插入或替换一个PASS用的,至于什么是GIMPLE, 什么是PASS,以后有时间再讲,这个内容已经超过PLUGIN本身了,不过了解这个确实很有用,我这里就讲讲怎么注册PLUGIN_PASS_MANAGER_SETUP事件。
注册PLUGIN_PASS_MANAGER_SETUP事件呢需要用到register_pass_info, opt_pass这两个结构体,以及pass_positioning_ops这个枚举类型,它们的声明如下:
struct opt_pass
{
/* Optimization pass type. */
enum opt_pass_type type;
/* Terse name of the pass used as a fragment of the dump file
name. If the name starts with a star, no dump happens. */
const char *name;
/* If non-null, this pass and all sub-passes are executed only if
the function returns true. */
bool (*gate) (void);
/* This is the code to run. If null, then there should be sub-passes
otherwise this pass does nothing. The return value contains
TODOs to execute in addition to those in TODO_flags_finish. */
unsigned int (*execute) (void);
/* A list of sub-passes to run, dependent on gate predicate. */
struct opt_pass *sub;
/* Next in the list of passes to run, independent of gate predicate. */
struct opt_pass *next;
/* Static pass number, used as a fragment of the dump file name. */
int static_pass_number;
/* The timevar id associated with this pass. */
/* ??? Ideally would be dynamically assigned. */
timevar_id_t tv_id;
/* Sets of properties input and output from this pass. */
unsigned int properties_required;
unsigned int properties_provided;
unsigned int properties_destroyed;
/* Flags indicating common sets things to do before and after. */
unsigned int todo_flags_start;
unsigned int todo_flags_finish;
};
enum pass_positioning_ops
{
PASS_POS_INSERT_AFTER, /* Insert after the reference pass. */
PASS_POS_INSERT_BEFORE, /* Insert before the reference pass. */
PASS_POS_REPLACE /* Replace the reference pass. */
};
struct register_pass_info
{
struct opt_pass *pass; /* New pass to register. */
const char *reference_pass_name; /* Name of the reference pass for hooking
up the new pass. */
int ref_pass_instance_number; /* Insert the pass at the specified
instance number of the reference pass.
Do it for every instance if it is 0. */
enum pass_positioning_ops pos_op; /* how to insert the new pass. */
};
opt_pass这个结构体就是用来描述要插入或者替换其他pass的pass的一些基本信息: type用来描述pass的类型,一共有4种,包括GIMPLE_PASS, RTL_PASS, SIMPLE_IPA_PASS, 和IPA_PASS。我个人目前使用过GIMPLE_PASS这种类型。name就是这种pass的名字,用来在GCC使用-fdump-tree-*开关[1]编译程序时,这个pass的dump文件的后缀,如果名字最前名是一个“*”号,在dump时就不会dump这个pass执行后的信息。gate和execute这两个函数指针一个用来判断是否需要执行这个pass,一个用来执行pass。properties_required参数可以填上PROP_any以使这个pass可以看到所有的gimple信息,而在cfg这个pass之后可以使用PROP_cfg来获得control flow graph的信息。当然,PROP_any和PROP_cfg可以使用“|”连接赋值给properties_required。 todo_flags_finish顾名思义就是在执行完这个pass以后需要执行什么额外的操作,可以用的值有很多,我用到的就是TODO_verify_stmts和TODO_dump_func,源码和手册里都没解释这两个参数,我猜,顾名思义吧,第一个就是执行完之后检查所有的语句是否合法,以及在使用-fdump-tree-*时dump这个pass处理完后,程序的信息。这些值都可以用或”|”连接后赋给todo_flags_finish。其他的参数,我基本都没有用到,一般不是填NULL就是填0,就可以了,大家如有需要,就看看源码里的注释,也许会能找到解释。
register_pass_info这个结构体有四个参数:pass就是上面说的pass结构体实例的地址;reference_pass_name和pos_op用来说明这个新的pass是放在叫reference_pass_name这个pass的前面一个处理呢,还是后面一个处理呢,还是替换它,而决定操作方法的就是pass_positioning_ops这个枚举类型,也就是pos_op变量了;ref_pass_instance_number我一般填0,具体的解释是大家可以看源码注释,我虽然能看懂字面意思,不过没试过其他值,不是很理解。
大家可能说,我怎么知道有哪些pass,他们的名字又是什么呢?这个只有在源码里找了,在passes.c文件中,大家搜NEXT字段,注意是大写。然后就可以搜到NEXT_PASS这个宏,这个就是gcc内部注册各种pass的地方,而它的参数就是上面说的opt_pass结构体及其变型了,当然也就可以找到各个pass的名字。
最后,在注册的时候,在callback函数处填0或NULL,而在user_data处把register_pass_info的实例的地址填上就可以了。可能会有人问,没有callback函数我们怎么处理事件呢?还记得在opt_pass结构里的gate和execute函数指针吗?
给一个例子如下:
static unsigned int
my_handler(void);
struct gimple_opt_pass my_gimple_pass =
{
{
GIMPLE_PASS,
"my_gimple_pass_name",
NULL,
my_handler,
NULL,
NULL,
0,
0,
PROP_gimple_any | PROP_cfg,
0,
0,
0,
TODO_verify_stmts | TODO_dump_func
}
};
int
plugin_init(struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
const char *plugin_name = plugin_info->full_name;
struct register_pass_info rp_info;
rp_info.pass = &my_gimple_pass.pass;
rp_info.pos_op = PASS_POS_INSERT_AFTER;
rp_info.ref_pass_instance_number = 0;
rp_info.reference_pass_name = "cfg";
register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, 0, &rp_info);
return 0;
}
其中:
struct gimple_opt_pass
{
struct opt_pass pass;
};
这样就可以在cfg这个pass后插入我的my_gimple_pass,当执行到这个pass的时候,会调用my_handler()。
最后,我想说GCC Internal Manual上并没有描述每个事件会返回什么样的gcc_data,我是在源代码里自己找到的。怎么找呢?gcc里,除了GIMPLE_PASS_MANAGE_SETUP外,都是使用invoke_plugin_callbacks()函数调用的callback,所以我们到gcc的源码的目录下,使用如下命令就可以找到所有的事件触发的地方,包括会返回什么参数等。
find ./ -maxdepth 1 -regex ".*\.c" -exec grep -H -n "invoke_plugin_callbacks" {} \;
今天先说到这,以后的话,再说就说GCC里的东西,GCC PLUGIN差不多也就这些内容了。
注:
[1]-fdump-tree-*开关中的”*”只是一个占位符,大家可以按照需要,填入要dump的pass的名称,或者填入all,代表dump所以可以dump的信息,例如:
gcc -fdump-tree-all -o fibonacci fibonacci.c fibonacci_main.c