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

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

Lab 1 - 6502 Assembly Language