📌 Find My Source Code Here: GitHub

Hello everyone, welcome back!

This is a continuation of my Project 01 blog post. There were some errors in Project Stage 01 so we will be fixing that.

You can find the first part here: Link.

📌 Install Error?

So my issue was that I could not make install after running the make command in the directory. This is the error:

Error Code

This error is vague but it seems to generally mean that

  • Makefile was corrupted or was not completed
  • Configure didn't finish properly
  • Wrong directory

In this case, i think that the first point makes the most sense to be the issue.

📌 Build Error?

So when I check the build.log to investigate, I saw a BUNCH of code lines that says undefined reference to .... Example of a part of the log:

Error

This definitely means that make was not completed successfully, had an error at one point and all the error cascaded.

If I check config.log, I can also find some 'missing' files.

📌 Basic Code

This is the changed code, I used for my basic tree-kzaw.cc

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
#include "gimple.h"
#include "tree-pass.h"
#include "ssa.h"
#include "tree-pretty-print.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
#include "internal-fn.h"
#include "gimple-pretty-print.h"

// Added headers:
#include "gimple-ssa.h"
#include "cgraph.h"
#include "attribs.h"
#include "pretty-print.h"
#include "tree-inline.h"
#include "intl.h"

// Test pass

namespace {

const pass_data pass_data_kzaw =
{
  GIMPLE_PASS, /* type */
  "kzaw", /* name */
  OPTGROUP_NONE, /* optinfo_flags */
  TV_TREE_NRV, /* tv_id */
  PROP_cfg , /* properties_required */
  0, /* properties_provided */
  0, /* properties_destroyed */
  0, /* todo_flags_start */
  0, /* todo_flags_finish */
};

class pass_kzaw : public gimple_opt_pass
{
public:
  pass_kzaw (gcc::context *ctxt)
    : gimple_opt_pass (pass_data_kzaw, ctxt)
  {}

  /* opt_pass methods: */
  bool gate (function *) final override {
      return 1;
  }

  unsigned int execute (function *) final override;

};

unsigned int
pass_kzaw::execute (function *) {
    struct cgraph_node *node;
    int func_cnt = 0;

    FOR_EACH_FUNCTION (node)
      {
        if (dump_file)
        {
            fprintf(dump_file, "=== Function %d Name '%s' ===\n", ++func_cnt, node->name() );
        }
      }

    if (dump_file)
    {
        fprintf(dump_file, "\n\n#### End kzaw diagnostics, start regular dump of current gimple ####\n\n\n");
    }

    return 0;  // Moved return statement outside the if block to ensure it always returns
}

} // anonymous namespace

// Factory function that creates an instance of the pass
gimple_opt_pass *
make_pass_kzaw (gcc::context *ctxt)
{
  return new pass_kzaw (ctxt);
}

📌 What did I do?

After trying at the hour for many many hours, I decided to rebuild the whole gcc from scratch. Yes, this would take long but it was the only viable option as the logs were too big of a size and complicated to decipher.

I deleted the ~/git/gcc and rebuilt the entire thing again.

However, I realized there are very important details that you will need to consider for creating your own GCC pass.

✅ Get the Names Right

The main error you could face is getting and putting the names. This is very IMPORTANT! For example, taking the naming convention from my code:

  • tree-name.cc: This is the name of your pass definition file. You will put your logic in here. When you add a .o to OBJS in Makefile.in, you are using the filename so it will be tree-name.o.
  • pass_name: This will typically be the name of your pass itself! Not to be mistaken with the file name. This is what you are using in passes.def where you will define a NEXT_PASS(pass_name).
  • make_pass_name: This is the name of function within your file. It will be used in the tree-pass.h header file so use it when declaring the function within the header.

✅ Get Directories Right

Specifically, please be carefully with where you are calling the commands from. They have to be very specific. Here are important ones:

  • Configure command: The configure command (~/git/gcc/configure --prefix=$HOME/gcc-test-001) need to be called from your build directory. (~/gcc-build-001/)
  • GCC subdirectory: Creating a pass with regards to gcc source code must be in ~/git/gcc/gcc.
  • Makefile remove: When you change Makefile.in for the first time, and you need to remove the initial Makefile. Make sure that you are in the ~/gcc-build-001/gcc subdirectory!

If everything goes and attention is detailed, the install will be successful!

Now let's move on 😄


📌 Test it Out

We have confirmation that our gcc is installed. Now we will need to test it out.

test.c file

Make a new test.c file:

#include 

void function1() {
    printf("I'm function1!\n");
}

void function2() {
    printf("I'm function2!\n");
}

void function3() {
    printf("I'm function3!\n");
}

int main() {
    function1(); 
    function2(); 
    function3();
    return 0;
}

Makefile

To make and run our new test.c file, we will need to add a new Makefile.

BINARIES=test
CCFLAGS=-g -O0 -fno-builtin -fdump-tree-kzaw

all: ${BINARIES}

test: test.c
    $$HOME/gcc-test-001/bin/gcc ${CCFLAGS} -o test test.c

clean:
    rm ${BINARIES} *.o || true

Things to note:

  • Add -fdump-tree-kzaw to check our dump file. (The dump file name is from our pass file name)
  • Make sure to configure our local gcc, not the server gcc. We define the path from our test directory.

Run command:

make test

Now check your dumpfile, you will see something like this which means it is working:

Output

📌 Updating the Code

Now that our basic code is working, it is time to edit it so that it can count both the basic blocks and GIMPLE statements!

The goal is to traverse basic blocks and statements within a function, while the old code traverses functions in the entire program.

The main portion of the code we will be editing is the execute() function.

First, we'll modify the function signature to actually use the function *fun parameter.

unsigned int
pass_kzaw::execute (function *fun)

Next, we need to declare variables for going through basic blocks and counting both blocks and statements.

basic_block block;
int block_count = 0, statement_count = 0;

Instead of traversing all functions in the program with FOR_EACH_FUNCTION, we now use FOR_EACH_BB_FN to iterate through all basic blocks in the current function.

// Traverse basic blocks in the current function
FOR_EACH_BB_FN (block, fun)
{
  block_count++;
  if (dump_file)
  {
    fprintf (dump_file, "===== Basic block count: %d =====\n", block_count);
  }
...

For each basic block, we add a nested loop using gimple_stmt_iterator to traverse all statements within that block,

// For each basic block, traverse statements
  for (gimple_stmt_iterator gsi = gsi_start_bb (block); !gsi_end_p (gsi); gsi_next (&gsi))
  {
    gimple *g = gsi_stmt (gsi);
    statement_count++;
    if (dump_file)
    {
      fprintf (dump_file, "----- Statement count: %d -----\n", statement_count);
      print_gimple_stmt (dump_file, g, 0, TDF_VOPS|TDF_MEMSYMS);
    }
  }
}

Finally, we add summary statistics that report the total number of basic blocks and statements encountered.

// Print summary information
if (dump_file)
{
  fprintf (dump_file, "------------------------------------\n");
  fprintf (dump_file, "Total Basic Blocks: %d\n", block_count);
  fprintf (dump_file, "Total Gimple Statements: %d\n", statement_count);
  fprintf (dump_file, "------------------------------------\n\n");
}

return 0;

Completed execute() Code:

unsigned int
pass_kzaw::execute (function *fun)
{
  basic_block block;
  int block_count = 0, statement_count = 0;

  // Traverse basic blocks in the current function
  FOR_EACH_BB_FN (block, fun)
  {
    block_count++;
    if (dump_file)
    {
      fprintf (dump_file, "===== Basic block count: %d =====\n", block_count);
    }

    // For each basic block, traverse statements
    for (gimple_stmt_iterator gsi = gsi_start_bb (block); !gsi_end_p (gsi); gsi_next (&gsi))
    {
      gimple *g = gsi_stmt (gsi);
      statement_count++;
      if (dump_file)
      {
        fprintf (dump_file, "----- Statement count: %d -----\n", statement_count);
        print_gimple_stmt (dump_file, g, 0, TDF_VOPS|TDF_MEMSYMS);
      }
    }
  }

  // Print summary information
  if (dump_file)
  {
    fprintf (dump_file, "------------------------------------\n");
    fprintf (dump_file, "Total Basic Blocks: %d\n", block_count);
    fprintf (dump_file, "Total Gimple Statements: %d\n", statement_count);
    fprintf (dump_file, "------------------------------------\n\n");
  }

  return 0;
}

Once you have finished editing the tree-name.cc file and the logic, please rebuild the gcc in your build directory using make.

Note that we don't have to do all of these Makefile.in stuff as it is only done the first time you add a pass.

When you go to your test file, make and run again, this will be the output!

Dump File Output:

;; Function function1 (function1, funcdef_no=0, decl_uid=2335, cgraph_uid=1, symbol_order=0)

===== Basic block count: 1 =====
----- Statement count: 1 -----
# .MEM_2 = VDEF <.MEM_1(D)>
printf ("I\'m function1!\n");
----- Statement count: 2 -----
# VUSE <.MEM_2>
return;
------------------------------------
Total Basic Blocks: 1
Total Gimple Statements: 2
------------------------------------

void function1 ()
{
  <bb 2> :
  printf ("I\'m function1!\n");
  return;

}



;; Function function2 (function2, funcdef_no=1, decl_uid=2337, cgraph_uid=2, symbol_order=1)

===== Basic block count: 1 =====
----- Statement count: 1 -----
# .MEM_2 = VDEF <.MEM_1(D)>
printf ("I\'m function2!\n");
----- Statement count: 2 -----
# VUSE <.MEM_2>
return;
------------------------------------
Total Basic Blocks: 1
Total Gimple Statements: 2
------------------------------------

void function2 ()
{
  <bb 2> :
  printf ("I\'m function2!\n");
  return;

}



;; Function function3 (function3, funcdef_no=2, decl_uid=2339, cgraph_uid=3, symbol_order=2)

===== Basic block count: 1 =====
----- Statement count: 1 -----
# .MEM_2 = VDEF <.MEM_1(D)>
printf ("I\'m function3!\n");
----- Statement count: 2 -----
# VUSE <.MEM_2>
return;
------------------------------------
Total Basic Blocks: 1
Total Gimple Statements: 2
------------------------------------

void function3 ()
{
  <bb 2> :
  printf ("I\'m function3!\n");
  return;

}



;; Function main (main, funcdef_no=3, decl_uid=2341, cgraph_uid=4, symbol_order=3)

===== Basic block count: 1 =====
----- Statement count: 1 -----
# .MEM_2 = VDEF <.MEM_1(D)>
function1 ();
----- Statement count: 2 -----
# .MEM_3 = VDEF <.MEM_2>
function2 ();
----- Statement count: 3 -----
# .MEM_4 = VDEF <.MEM_3>
function3 ();
----- Statement count: 4 -----
_5 = 0;
===== Basic block count: 2 =====
----- Statement count: 5 -----
<L0>:
----- Statement count: 6 -----
# VUSE <.MEM_4>
return _5;
------------------------------------
Total Basic Blocks: 2
Total Gimple Statements: 6
------------------------------------

int main ()
{
  int D.2344;
  int _5;

  <bb 2> :
  function1 ();
  function2 ();
  function3 ();
  _5 = 0;

  <bb 3> :
<L0>:
  return _5;

}

This code can be run both in x86 and aarch64 servers!
That brings us to the end of Project Stage 01! 😄


Conclusion

This stage took me a few tries to get running, but that made me understand all the more about creating gcc passes. Even a simple thing such as creating a pass can take so long if you are not being cautious about your file names, function names and directories.

All my learnings from my errors are condensed into a section at the start of this post. Please make sure to check it out so that you don't repeat my mistakes.

I will see you in Project Stage 02. Please stay safe!