Project Stage 1: Create a GCC Pass

 

Introduction

I created a custom pass for the GCC compiler for Stage 1 of my SPO600 project, which examines generated code by displaying the total number of GIMPLE statements in each function, reporting the function name, and counting basic blocks. During compilation, this will aid in revealing information about the code structure.

Environment Setup

I established a three-directory structure for my GCC development environment:

bashCopy# Directory structure
~/git/gcc/         # GCC source code repository
~/gcc-build-001/   # Build directory
~/gcc-test-001/    # Installation and testing directory

I found four important files that would require editing to construct a custom pass after looking over the GCC documentation:

  1. A new source file (tree-my-pass.cc) - Containing the pass implementation

  2. passes.def - To register my pass in the compilation pipeline

  3. tree-pass.h - To declare my pass's factory function

  4. Makefile. in - To integrate my new source file into the build system

Developing the Custom Pass

Step 1: Creating the Pass Source File

First, I created a new file tree-my-pass.cc in the GCC source code directory with the following implementation:

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
#include "gimple.h"
#include "tree-pass.h"
#include "gimple-iterator.h"
#include "pass_manager.h"
#include "basic-block.h"

namespace {

const pass_data my_pass_data = {
    GIMPLE_PASS,         // Pass type: operates on GIMPLE
    "my-pass",           // Name of the pass
    OPTGROUP_NONE,       // No optimization group
    TV_NONE,             // No TV id
    0,                   // No properties required
    0,                   // No properties provided
    0,                   // No properties destroyed
    0                    // No specific flags
};

class my_pass : public gimple_opt_pass {
public:
    my_pass(gcc::context *ctxt)
        : gimple_opt_pass(my_pass_data, ctxt) { }

    // The gate method: always run this pass
    bool gate(function *) override {
        return true;
    }

    // The execute method: called for every function
    unsigned int execute(function *fun) override {
        if (dump_file) {
            fprintf(dump_file, "Processing function: %s\n", function_name(fun));

            // Count basic blocks
            int basic_block_count = 0;
            basic_block bb;
            FOR_EACH_BB_FN(bb, fun) {
                basic_block_count++;
            }
            fprintf(dump_file, "Number of basic blocks: %d\n", basic_block_count);

            // Count GIMPLE statements
            int gimple_stmt_count = 0;
            FOR_EACH_BB_FN(bb, fun) {
                for (gimple_stmt_iterator gsi = gsi_start_bb(bb);
                     !gsi_end_p(gsi);
                     gsi_next(&gsi)) {
                    gimple_stmt_count++;
                }
            }
            fprintf(dump_file, "Number of GIMPLE statements: %d\n", gimple_stmt_count);
        }
        return 0;
    }
};

} 

// Factory function to create an instance of the pass
gimple_opt_pass *
make_pass_my_pass(gcc::context *ctxt)
{
    return new my_pass(ctxt);
}

This implementation:

  • Specifies a GIMPLE pass that each function uses.

  • Counts the fundamental components in every function.

  • Counts GIMPLE statements in each function

  • Outputs the results to the dump file

Step 2: Registering the Pass in passes.def

To register my pass in the GCC pipeline, I added the following line to passes.def:

NEXT_PASS (pass_my_pass, 1)

This registers my pass with a unique name and indicates it should be executed in the compiler's pass pipeline.

Step 3: Declaring the Pass in tree-pass.h

To make my pass's factory function available, I added this declaration to tree-pass.h:

extern gimple_opt_pass *make_pass_my_pass (gcc::context *ctxt);



Step 4: Updating Makefile.in

To include my pass in the build, I added it to the list of object files in Makefile.in:

tree-my-pass.o \



I made sure to use the same indentation and trailing backslash as other entries to maintain proper Makefile syntax.

Building and Testing Process

Building GCC with My Custom Pass

I configured and built GCC with my custom pass:

~/gcc-build-001
../git/gcc/configure --prefix=$HOME/gcc-test-001 --enable-languages=c --disable-bootstrap --disable-multilib
time make -j$(nproc) |& tee rebuild.log

Testing the Custom Pass

After resolving the build issues, I tested my pass with a simple C program:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(5, 3);
    printf("Result: %d\n", result);
    return 0;
}

Compiling with my custom pass enabled:

~/gcc-test-001/bin/gcc -fdump-tree-my-pass test.c -o test

This generated a dump file test.c.234t.my-pass with output like:

Processing function: add
Number of basic blocks: 1
Number of GIMPLE statements: 1

Processing function: main
Number of basic blocks: 2
Number of GIMPLE statements: 4

Reflections and Learning

The GCC's pass management mechanism and intermediate representations have been nicely explained by this project. The way that GIMPLE statements break apart seemingly straightforward operations—a single line of C code might produce numerous GIMPLE statements—and expose the complexity concealed underneath high-level code shocked me the most. I also observed that during optimization, GCC reuses fundamental blocks. Functions that appeared to need a lot of control routes were frequently reduced to only two or three simple blocks, demonstrating how the compiler removes unnecessary code paths that we might not be aware of.

Comments

Popular posts from this blog

Project Stage 3: Enhancing GCC Pass for Multiple Function Clone Handling: Progress Update

Project Stage 1: Troubleshooting a bit - Coming soon

Lab - 1: Exploring 6502 Assembly