SPO600 2025 Winter Project - Stage 1: Create a Basic GCC Pass (part1)
Project Overview
The goal of this project is to extend the GCC compiler by adding a custom pass that can analyze the functions being compiled. A GCC pass is a stage in the compilation process that processes intermediate representations (IR) of the source code. These passes perform various optimizations and analyses before generating the final machine code.
📌 What Will This Custom Pass Do?
In Stage 1, I am implementing a GCC pass that:
• Iterates through each function in the compiled code.
• Prints the function name being analyzed.
• Counts the number of basic blocks in the function.
• Counts the number of GIMPLE statements inside each function.
• Outputs this information to the console.
By completing this stage, I will gain hands-on experience with GCC’s internal structures, intermediate representations like GIMPLE, and how to modify GCC’s compilation pipeline.
Step 1: Setting Up the Environment
🔹 Note: I have already completed GCC setup in Lab 4. For details on obtaining, configuring, and compiling GCC, see my Lab 4 blog posts.
In this stage, I started by ensuring that my GCC source and build directories were correctly set up.
I verified this by listing my home directory:
ls -l ~/
This showed me:
• ~/git/gcc/ → GCC source directory
• ~/gcc-build-001/ → GCC build directory
• ~/gcc-test-001/ → Testing directory
Step 2: Creating a New GCC Pass (tree-my-pass.cc)
Since GCC does not automatically recognize new passes, I created a new file to define my pass.
Creating the Pass File
I navigated to the gcc/ directory inside the GCC source tree:
cd ~/git/gcc/gcc
Then, I created a new pass file:
nano tree-skim.cc
In this file, I added the following basic implementation of my custom pass:
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "tree-pass.h"
#include "pass_manager.h"
#include "context.h"
#include "diagnostic-core.h"
#include "tree.h"
#include "tree-core.h"
#include "basic-block.h"
#include "gimple.h"
#include "gimple-iterator.h"
namespace {
// Define pass metadata
const pass_data pass_data_skim = {
GIMPLE_PASS, /* type */
"skim", /* 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 */
};
// Custom GIMPLE pass class
class pass_skim : public gimple_opt_pass {
public:
// Constructor
pass_skim(gcc::context *ctxt)
: gimple_opt_pass(pass_data_skim, ctxt) {}
// Gate function - checks if function is valid
bool gate(function *fun) final override {
return fun != nullptr; // Added safety check
}
// Execute function - analyzes basic blocks and GIMPLE statements
unsigned int execute(function *fun) final override;
};
// Execute function implementation
unsigned int pass_skim::execute(function *fun) {
int bb_count = 0;
int gimple_stmt_count = 0;
if (dump_file) {
fprintf(dump_file, "=== Function: %s ===\n", function_name(fun));
}
// Iterate over basic blocks
basic_block bb;
FOR_EACH_BB_FN(bb, fun) {
if (!bb) continue; // Added safety check for null BBs
bb_count++;
int bb_gimple_count = 0;
// Iterate over GIMPLE statements
for (gimple_stmt_iterator gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
bb_gimple_count++;
}
gimple_stmt_count += bb_gimple_count;
if (dump_file) {
fprintf(dump_file, "Basic Block %d contains %d GIMPLE statements.\n", bb_count, bb_gimple_count);
}
}
if (dump_file) {
fprintf(dump_file, "Total Basic Blocks: %d\n", bb_count);
fprintf(dump_file, "Total GIMPLE Statements: %d\n", gimple_stmt_count);
}
return 0;
}
} // namespace
// Function to create pass instance
gimple_opt_pass *make_pass_skim(gcc::context *ctxt) {
return new pass_skim(ctxt);
}
To save, using Ctrl + X -> Y -> Enter.
🔹 What does this code do?
• It defines a new GCC pass named pass_skim.
• It processes one function at a time
• It counts basic blocks (logical code divisions).
• It counts GIMPLE statements (low-level IR representation).
• It logs this information to dump_file
Step 3: Registering the Pass in passes.def
In order for GCC to recognize and execute my custom pass, I need to register it in passes.def. Before doing this, I had to ensure that the function make_pass_skim is declared in the proper header file.
Modifying tree-pass.h:
I opened the tree-pass.h file:
nano tree-pass.h
To allow GCC to recognize the function, I added the following declaration in tree-pass.h:
extern gimple_opt_pass *make_pass_skim(gcc::context *ctxt);
This ensures that the pass can be correctly referenced within the GCC source.
Note: When adding a new GCC pass like make_pass_skim, its declaration should be placed in tree-pass.h. While the exact position isn’t critical, it’s best practice to place it alongside other gimple_opt_pass *make_pass_... declarations.
The image below was captured before I renamed the pass, so please note that the name may be different.
Modifying passes.def
I opened the passes.def file:
nano passes.def
After ensuring that the function is declared in the header, I opened passes.def and added:
NEXT_PASS(pass_skim);
🔹 This tells GCC to execute my_pass at the end of its compilation pipeline.
Note: Place NEXT_PASS (pass_skim); after passes that finalize lower-level transformations, such as pass_lower_resx. This ensures the pass runs when the GIMPLE representation is stable but before later optimizations that might restrict modifications. Avoid placing it too late (e.g., after POP_INSERT_PASSES()), as post-optimization phases may not allow further transformations.
Step 4: Adding tree-my-pass.o to GCC’s Build System
To make sure GCC compiles the new pass, I had to add tree-skim.o to Makefile.in.
Modifying Makefile.in
I opened Makefile.in inside the gcc/ directory:
nano Makefile.in
To quickly find where object files (.o files) are listed in Makefile.in and, inside nano, I pressed:
Ctrl + W
This opened the search function in nano, and I typed:
OBJS =
This allowed me to jump directly to the section where object files are defined.
You should place it among the existing tree-*.o files:
🔹 Why is this needed?
• Makefile.in controls which object files (.o) are compiled.
• Adding tree-my-pass.o ensures that GCC includes my pass in the final build.
I then saved and exited.
Step 5: Regenerating Makefile
Since I modified Makefile.in, I needed to ensure that the build system recognized my changes.
🔹 Why is this needed?
• GCC does not automatically detect changes to Makefile.in.
• The make process is responsible for generating necessary Makefiles for different components of the compiler.
Running make to regenerate dependencies
cd ~/gcc-build-001 // Build directory
rm -rf Makefile
../git/gcc/configure --prefix=$HOME/gcc-test-001 // Test directory
🔹 What this does:
• Ensures that my changes to Makefile.in are correctly reflected. (Configuration)
• Regenerates Makefile dependencies for all subdirectories (including gcc/).
• Avoids unnecessary reruns of configure, as the existing Makefile structure can pick up modifications.
Step 6: Rebuilding GCC
Finally, I started rebuilding GCC to include my custom pass.
time make -j20 |& tee rebuild.log
Note: Use screen if the build time is too long and prevent disconnect to ssh.
🔹 What this command does:
• time make -j20: Runs the make build process using 20 parallel jobs and measures the total execution time.
• tee rebuild.log: Saves all build output to rebuild.log for debugging.
Once finished, I verified using:
cat > test.c <<EOF #include <stdio.h> int main() { printf("Hello, GCC!\n"); return 0; } EOF
$HOME/gcc-test-001/bin/gcc test.c -o test
./test
I will summarize the troubleshooting in the next post!
Comments
Post a Comment