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

Popular posts from this blog

Lab 1 - 6502 Assembly Language

SPO600 2025 Winter Project - Stage 2: GIMPLE Level Clone Analysis and Pruning (part4)