前言
GCC中包含了从-O0到-O3以及-Og,-Os和-Ofast各种级别的优化,并提供了上百多个可独立控制的优化标志。每个优化级别都启用了优化标志的子集,并在启用调试信息生成对可执行代码没有任何影响。但是确定何时运行pass是一个复杂的过程,每一个pass都有一个门函数(gate function), 它根据优化级别和标志来决定是否运行该pass。
GCC中如何添加一个优化pass的操作,可以参考《GCC - GIMPLE IR 学习一》。如何通过优化选项控制我新添加的优化pass?在回答这个问题之前,我们需要先了解GCC中优化选项相关的一些知识,GCC中除了各种级别中包含的选项,还有一部分是通过选项独立控制它是否执行的,查看每个-O
级别上包含的优化选项可以使用gcc -O3 -Q --help=optimizers
命令查看。通过独立控制的选项可以通过fdump-tree-选项简称
来查看,例如,查看-ftree-loop-distribution
选项pass信息,使用gcc -O3 test.c -ftree-loop-distribution -fdump-tree-ldist
命令,查看-prefetch-loop-arrays
选项的pass信息,使用gcc -O3 test.c -fprefetch-loop-arrays -fdump-tree-aprefetch
命令。也可使用-fdump-tree-all
、-fdump-rtl-all
或者-fdump-ipa-all
打印出所有中间运行信息。
1. GCC8.2.0 中重要的pass文件
1. opts.c文件
在gcc/opts.c文件中主要进行一些默认选项的分配管理以及完成后的处理等操作。其中的default_options_table
数组会根据优化级别设置启用标志,如添加-O
级别上的选项可按照其中已有的选项要求就行添加,并在相关函数中配置其flag。default_options_table
数组内容如下:
static const struct default_options default_options_table[] =
{
/* -O1 optimizations. */
{ OPT_LEVELS_1_PLUS, OPT_fdefer_pop, NULL, 1 },
#if DELAY_SLOTS
{ OPT_LEVELS_1_PLUS, OPT_fdelayed_branch, NULL, 1 },
#endif
{ OPT_LEVELS_1_PLUS, OPT_fguess_branch_probability, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fcprop_registers, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fforward_propagate, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_fif_conversion, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_fif_conversion2, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fipa_pure_const, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fipa_reference, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fipa_profile, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fmerge_constants, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_freorder_blocks, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fshrink_wrap, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fsplit_wide_types, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_ccp, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_ftree_bit_ccp, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_coalesce_vars, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_dce, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_dominator_opts, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_dse, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_ter, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_ftree_sra, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_fre, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_copy_prop, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_sink, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_ch, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fcombine_stack_adjustments, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fcompare_elim, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_slsr, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_fbranch_count_reg, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_fmove_loop_invariants, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_ftree_pta, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_fssa_phiopt, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_ftree_builtin_call_dce, NULL, 1 },
{ OPT_LEVELS_1_PLUS, OPT_fomit_frame_pointer, NULL, 1 },
/* -O2 optimizations. */
{ OPT_LEVELS_2_PLUS, OPT_finline_small_functions, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_findirect_inlining, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fpartial_inlining, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fthread_jumps, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcrossjumping, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_foptimize_sibling_calls, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcse_follow_jumps, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fgcse, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fexpensive_optimizations, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_frerun_cse_after_loop, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcaller_saves, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fpeephole2, NULL, 1 },
#ifdef INSN_SCHEDULING
/* Only run the pre-regalloc scheduling pass if optimizing for speed. */
{ OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_fschedule_insns, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fschedule_insns2, NULL, 1 },
#endif
{ OPT_LEVELS_2_PLUS, OPT_fstrict_aliasing, NULL, 1 },
{ OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_freorder_blocks_algorithm_, NULL,
REORDER_BLOCKS_ALGORITHM_STC },
{ OPT_LEVELS_2_PLUS, OPT_freorder_functions, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_ftree_vrp, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fcode_hoisting, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_ftree_pre, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_ftree_switch_conversion, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fipa_cp, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fipa_bit_cp, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fipa_vrp, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fdevirtualize, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fdevirtualize_speculatively, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fipa_sra, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_falign_loops, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_falign_jumps, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_falign_labels, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_falign_functions, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_ftree_tail_merge, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fvect_cost_model_, NULL, VECT_COST_MODEL_CHEAP },
{ OPT_LEVELS_2_PLUS_SPEED_ONLY, OPT_foptimize_strlen, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fhoist_adjacent_loads, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fipa_icf, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fisolate_erroneous_paths_dereference, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fipa_ra, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_flra_remat, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_fstore_merging, NULL, 1 },
/* -O3 optimizations. */
{ OPT_LEVELS_3_PLUS, OPT_ftree_loop_distribute_patterns, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_ftree_loop_distribution, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_floop_interchange, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fpredictive_commoning, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fsplit_paths, NULL, 1 },
/* Inlining of functions reducing size is a good idea with -Os
regardless of them being declared inline. */
{ OPT_LEVELS_3_PLUS_AND_SIZE, OPT_finline_functions, NULL, 1 },
{ OPT_LEVELS_1_PLUS_NOT_DEBUG, OPT_finline_functions_called_once, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fsplit_loops, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_funswitch_loops, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_floop_unroll_and_jam, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fgcse_after_reload, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_ftree_loop_vectorize, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_ftree_slp_vectorize, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fvect_cost_model_, NULL, VECT_COST_MODEL_DYNAMIC },
{ OPT_LEVELS_3_PLUS, OPT_fipa_cp_clone, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_ftree_partial_pre, NULL, 1 },
{ OPT_LEVELS_3_PLUS, OPT_fpeel_loops, NULL, 1 },
/* -Ofast adds optimizations to -O3. */
{ OPT_LEVELS_FAST, OPT_ffast_math, NULL, 1 },
{ OPT_LEVELS_NONE, 0, NULL, 0 }
};
但是某些标志默认情况下是通过其初始化程序启用的,例如 gcc/common.opt,根据其他条件,有些也被强制启用或禁用。也就是说一个pass的启用或关闭是由众多条件构成的,只有满足这些条件,这个pass才会被执行。
2. common.opt文件
这个文件描述了所有公共的命令行选项和内部标志,这里面包含pass启用或关闭的执行条件以及所有选项的名称。也就是说这个文件是定义优化选项名称的地方,对于一些需要flag的选项,其flag标记值也在此文件中定义和初始化。
文件变量定义部分内容如下:
文件中选项声明部分内容如下:
3. toplev.c文件
这是cc1的顶层文件,它解析命令args,打开文件以正确的顺序调用各种过程,并计算每个过程所用的时间。错误消息和malloc的低级接口也在这里处理。其中process_options函数用来处理已解析的选项,选项执行的前提条件在此通过flag设置,以fprefetch-loop-arrays
选项在此文件中的判断条件为例,其内容如下:
4. passes.def文件
GCC编译过程中的所有优化pass的执行顺序在passes.def中描述,但具体每一个pass是关闭还是启用,gcc会根据标记在passes.c文件中判断。passes.def中选项执行顺序的描述如下所示:
5. tree-pass.h文件
该文件主要包含GIMPLE_PASS、RTL_PASS、SIMPLE_IPA_PASS、IPA_PASS四类优化pass的定义和具体参数描述,和pass的元数据(pass_data)定义和描述,以及所有pass的注册管理操作。
注册新的pass,需要填写register_pass_info或指定各个参数。通过操作符new 来分配pass对象,并且通过pass manager来获取pass对象的所有权。我们新建pass时只需要按照如下信息注册便可:
2. 其它相关pass文件
opts-global.c中在启动时初始化与全局选项相关的设置。解析命令行选项并设置默认标志值,并进行最少的选项处理等操作。
passes.c中通过enable_pass或disable_pass函数启用或关闭arg指定的pass,还包括dump pass以及很多用于pass debuging相关实现的操作。
3. 选项控制pass操作
讲完前面铺垫,到此正式进入正题,如何通过选项控制pass,主要分为以下三步:
1. 定义pass中的gate函数执行条件
我们新建的pass中包含的pass_data,其结构主要包含的信息如下:
- type:pass属于四种pass中的哪一种。
- name:pass的简短名称,用作dump file时打印出来的名称,例如:使用
fdump-tree-all
打印出来的aprefetch
名称结尾的文件代表fprefet-loop-arrays
这个优化pass。 - ptinfo_flags:控制选项所属的组别,取值包含:
OPTGROUP_NONE、OPTGROUP_IPA、OPTGROUP_LOOP、OPTGROUP_INLINE、OPTGROUP_OMP、OPTGROUP_VEC、OPTGROUP_OTHER、OPTGROUP_ALL
- tv_id:与该pass关联的timevar id。在timevar.def文件中描述。
pass所在的class中有两个重要函数gate
和execute
,pass的执行条件由gate
函数的参数控制,该函数默认返回true,flag标记可由自己控制。pass的执行内容在execute
函数中执行。以aprefetch
pass的源码文件tree-ssa-loop-prefetch.c
为例,其内容如下:
2. 控制参数的定义和选项声明
在common.opt文件中定义控制变量,flag_xxxx,并在Driver中声明优化选项,以及对选项做出描述。
3. 解析选项和命令参数
在toplev.c文件中通过process_options函数解析命令参数,并在此添加选项执行的前提条件以及选项使用过程中的警告信息提示等。
一句话概括以上操作便是:创建pass的时候,在gate函数中设置函数的执行条件,在tree-pass.h中注册pass,在common.opt和passes.def中添加pass和选项信息,toplev.c中设置flag参数,控制pass执行。
以上是我个人的一个非常粗浅的认识,如有不对的地方,欢迎大家指出!