Project - Stage 1: Create a Basic GCC Pass
Project - Stage 1: Create a Basic GCC Pass
In this project stage 1, I am creating and adding a basic GCC pass into the GCC compiler.
What is a pass?
Based on my understanding, a pass is a set of operations or rules that transform the source code during compilation, optimizing it for better efficiency so that the compiler can convert the code into binary files.
However, before diving into more complex passes that optimize the code, we need to first understand how to create a basic pass. To start, I will create a simple pass that performs the following operations:
- Iterates through the code being compiled
- Prints the name of every function being compiled
- Prints a count of the number of basic blocks in each function
- Prints a count of the number of GIMPLE statements in each function.
Before Creating a pass
Creating a pass
cd ~/gcc/gcc
#include "config.h" #include "system.h" #include "coretypes.h" #include "tree.h" #include "tree-pass.h" #include "cgraph.h" #include "function.h" #include "basic-block.h" #include "gimple.h" #include "gimple-iterator.h" #include "cfg.h" namespace{ const pass_data pass_data_project = { GIMPLE_PASS, /* type */ "pass_project", /* name */ OPTGROUP_NONE, /* optinfo_flags */ TV_NONE, /* tv_id */ PROP_cfg, /* properties_required */ 0, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ 0, /* todo_flags_finish */ }; // Pass class class pass_project : public gimple_opt_pass { public: // Constructor pass_project(gcc::context* ctxt) : gimple_opt_pass(pass_data_project, ctxt){}; unsigned int execute(function* func) override{ struct cgraph_node* node; int func_cnt = 0; // Iterate functions FOR_EACH_FUNCTION(node){ // Iterate basic blocks of the function int bb_cnt = 0, gimple_stmt_cnt = 0; basic_block bb; FOR_EACH_BB_FN(bb, func){ bb_cnt++; // Iterate GIMPLE statements in the basic block for(gimple_stmt_iterator gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)){ gimple_stmt_cnt++; } } if(dump_file){ fprintf(dump_file, "=== Function %d Name '%s' ===\n" "=== Number of Basic Blocks: %d ===\n" "=== Number of GIMPLE statements: %d ===\n\n", ++func_cnt, node->name(), bb_cnt, gimple_stmt_cnt); } } if(dump_file){ fprintf(dump_file, "\n\n### End diagnostics, start regular dump of current gimple ###\n\n\n"); } return 0; } }; } //Custom pass creation function gimple_opt_pass* make_pass_project(gcc::context* ctxt){ return new pass_project(ctxt); }
const pass_data pass_data_project = { GIMPLE_PASS, /* type */ "pass_project", /* name */ OPTGROUP_NONE, /* optinfo_flags */ TV_NONE, /* tv_id */ PROP_cfg, /* properties_required */ 0, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ 0, /* todo_flags_finish */ };
// Pass class class pass_project : public gimple_opt_pass { public: // Constructor pass_project(gcc::context* ctxt) : gimple_opt_pass(pass_data_project, ctxt){}; unsigned int execute(function* func) override{ struct cgraph_node* node; int func_cnt = 0; // Iterate functions FOR_EACH_FUNCTION(node){ // Iterate basic blocks of the function int bb_cnt = 0, gimple_stmt_cnt = 0; basic_block bb; FOR_EACH_BB_FN(bb, func){ bb_cnt++; // Iterate GIMPLE statements in the basic block for(gimple_stmt_iterator gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)){ gimple_stmt_cnt++; } } if(dump_file){ fprintf(dump_file, "=== Function %d Name '%s' ===\n" "=== Number of Basic Blocks: %d ===\n" "=== Number of GIMPLE statements: %d ===\n\n", ++func_cnt, node->name(), bb_cnt, gimple_stmt_cnt); } } if(dump_file){ fprintf(dump_file, "\n\n### End diagnostics, start regular dump of current gimple ###\n\n\n"); } return 0; } };
//Custom pass creation function gimple_opt_pass* make_pass_project(gcc::context* ctxt){ return new pass_project(ctxt); }
Inserting the custom pass into the GCC building sequence
passes.def
NEXT_PASS (pass_project);
Makefile.in
~/gcc/configure --prefix=$HOME/gcc-test-001
pass_project.o \
vi Makefile.in
/OBJS =
tree-pass.h
tree-pass.h acts as a registry header that lists all the passes, ensuring the GCC pass manager can recognize and work with them. We declare our pass in this header to make it visible and available to the pass manager. Therefore, we need to insert our pass inside the struct register_pass_info. Since our pass is a GIMPLE pass, I inserted it at the end of the GIMPLE passes, just before the IPA passes. To insert our pass, I follow the declaration format:extern gimple_opt_pass *make_pass_project (gcc::context *ctxt);
rm Makefile ~/gcc/configure --prefix=$HOME/gcc-test-001
time make -j$(nproc) |& tee build.logThis command is the same as the one we used when initially building GCC. It records the build time and saves the full build log to a file named build.log.
less build.log
make[1]: *** [Makefile:14721: configure-c++tools] Error 1
make[1]: *** [Makefile:4029: configure-fixincludes] Error 1
mkdir ~/gcc-build-001 cd ~/gcc-build-001 ~/gcc/configure --prefix=$HOME/gcc-test-001 time make -j 24 |& tee build.log
Then, we need to install the build by:
make install
Testing result
PATH=$HOME/gcc-test-001/bin:$PATH
which gcc
For example, I created a C program file with the following code:
#include <stdio.h>
char* getName();
int main(){
printf("Hello, I am %s\n", getName());
return 0;
}
char* getName(){
return "Wing Ho Chau";
}
gcc -Wall -g -fdump-tree-pass_project hello.c -o hello
cat hello.c.019t.pass_project
We can see that the function names, the number of basic blocks, and the number of GIMPLE statements are printed out as expected. This confirms that we have successfully created our custom pass and integrated it into the GCC build.
Reflection
I found Project Stage 1 quite challenging because there wasn't enough documentation, and I was unsure about which header files to include in my pass. There are so many header files, and it was difficult to know which ones are necessary for my pass to work. To figure it out, I decided to look at other passes to see which headers they included. It became a trial-and-error process where I tried to build GCC, encountered errors, and then reviewed the errors to understand which headers were missing.
Although this process was frustrating at times, I learned a lot. I now understand that a pass is a set of operations or rules that transform the source code during compilation to make it more efficient. This allows the compiler to convert the code into binary files. I also learned that when I make significant changes to the build system, I need to rebuild GCC from scratch. This experience has helped me become more familiar with how GCC works and how to troubleshoot issues during the build process.
Comments
Post a Comment