Oovaide Index

Viewing C++ Project Cohesion (Code Zone Diagrams)

Have you ever wanted to view hundreds or thousands of classes of a project at one time and their dependencies on each other? Traditional class diagrams can show in the tens of classes, but often times these diagrams make it difficult to understand relationships quickly.

A different way of viewing these classes is to use a circle diagram where all of the classes are a single point on the circle. The classes are grouped by directories. The dependencies are displayed as lines between the classes. The lines are colored by the provider directory.

This first example shows the small Oovaide project that uses CLang to parse C++ and create these diagrams, but what if we need to display a million line project such as LLVM? Showing lines between all classes is too much, so only lines between directories are displayed where the classes are related. The arrows can be turned on to indicate dependency direction. The following diagram is the same project as the diagram above, but only shows class dependencies between directories.

This makes it easier to see that oovaide and oovEdit are both related to oovGuiCommon, and oovCommon, but the other components are not.

Another way to view the classes is to display each directory as a separate circle diagram. This is the same information as the first diagram, but is only laid out differently.

In UML class diagrams, class methods that have a relationship that use other classes are typically not displayed. The Oovaide program allows optionally visualizing these relationhips also. It also allows hovering over the classes to see the class names.

Run the following to view these diagrams.

Viewing the LLVM project

Showing all classes for the LLVM project is pretty messy since it has over 3000 classes and somewhere near a million lines of code. I have never looked at the source for LLVM, so let's see what we can learn. The LLVM project was chosen simply because it is a large project.

Viewing just the directory relations is more manageable. It is still messy, but now we start to get a view of the whole project. To toggle classes/components in Oovaide, right click on the diagram and click on "Show All Classes"

Since this project has a directory structure that looks like this:
    include/llvm/
        Analysis
        AST
        ...
    lib/
        Analysis
        AST
        ...
    
Lets group this so that the include and lib directories are mapped together, and filter out examples, tests, tools, and utilities. To map the include and lib directories together, do the following.

There are an awful lot of targets that we are not interested in. Lets dump them. In Oovaide, right click on lib/Target and select "Hide Children" to remove them.

Now turning on all classes will show more of the cohesion between classes in the directories.

Some of the interesting aspects are:

So let's look at the MC/AsmPrinter relations in more detail. Turning on the dependency indications shows that the AsmPrinter classes are dependent on the include/llvm/MC directory and not the other way around.

In Oovaide do the following.



There are a few other directories that are not related to CodeGen/AsmPrinter such as lib/MC/MCParser, so we'll turn them off which now shows all classes related between include/llvm/MC and AsmPrinter.

There is a relationship between DIELabel and MCSymbol, so switching to a class view diagram should show this.

This diagram shows that DIELabel in AsmPrinter has a member that is an MCSymbol from the MC directory, so the AsmPrinter is truly dependent on the MC directory.

The actual DIELabel class code with comments removed looks like the following:

    class DIELabel : public DIEValue {
      const MCSymbol *Label;

    public:
      explicit DIELabel(const MCSymbol *L) : DIEValue(isLabel), Label(L) {}
      void EmitValue(AsmPrinter *AP, dwarf::Form Form) const override;
      const MCSymbol *getValue() const { return Label; }
      unsigned SizeOf(AsmPrinter *AP, dwarf::Form Form) const override;
      static bool classof(const DIEValue *L) { return L->getType() == isLabel; }

    #ifndef NDEBUG
      void print(raw_ostream &O) const override;
    #endif
    };
    
Displaying the sequence or operation diagram for the print function of DIELabel shows the following:

The print member function calls the MCSymbol::getName() method. The code looks like the following:

    void DIELabel::print(raw_ostream &O) const {
      O << "Lbl: " << Label->getName();
    }
    

This is about as far as we can drill in today. Next time, the atom.