Any experience about debugging the VisitExpr method?

I’m using gdb to track the LowerTE pass, and it confuses me when it comes to the VisitExpr part.

I have to type s for each line, in case I step out the target line. It’s not convenient. Any advice?

For example, like this line, I want to step into it, but have skipped it for 2 times.

// /root/codes/tvm/src/relay/transforms/de_duplicate.cc:85
return WithFields(GetRef<Function>(func_node), params, VisitExpr(func_node->body),
                        VisitType(func_node->ret_type), type_params);

From the title I assume you’re asking how to step into VisitExpr. If the question is to skip the VisitExpr and break at the start of WithFields, then I would say the easiest way is to just set a breakpoint as

b WithFields and keep pressing continue until you reach that function. Note that once you’ve set the breakpoint on the WithFields function, gdb automatically tracks all functions/members named WithFields, its guaranteed to stop there if you just keep continuing.

As for stepping into the right VisitExpr, the same trick can probably be used, but an easier way might be to understand the visitor pattern hierarchy in TVM.

TLDR Just set a breakpoint in all VisitExpr_ member functions inside the surrounding class DeDupMutator

A slightly detailed explanation

At a high level, any class that wishes to traverse the AST can do it by inheriting from classes in expr_functor.cc and stmt_functor.cc. There are 2 types of visitors, which are Visitor and Mutator and the difference between them is just that Mutator allows the AST to be modified as part of its traversal.

The way this works is that the inheriting class, can override any specific VisitExpr_/VisitStmt_ functions (note that these functions are overloaded based on the type of node passed as argument) in their class, and when that particular type of node is visited in the AST, the overridden version is called, but in all other cases, the default implementations in their parent classes are called.

You can learn how visitors work in detailed from the documentation here

Anyway, in case of de_duplicate.cc, we see that they’ve defined a class DeDupMutator that inherits from TypeMutator, MixedModeMutator and PatternMutator. So for any call to VisitExpr, based on the type of the expression in question, it has to visit one of the 3 VisitExpr_ overridden functions in that class as only those are going to do anything interesting with respect to that class.

All this explanation was just to say that you can set the breakpoint in all the VisitExpr_ member functions in its surrounding class, and it will break in one of those functions.

For all other types of nodes, the parent class is just going to visit the attributes in the appropriate order and return without doing anything.

Thank you for such a detailed explanation!

I’m reading the source code about the ExprFunctor and trying to understand it.

Hi, I’m still having questions about the visitor, For example, the DeDupMutator have 3 levels of inheritance, each level defines some functions, how can I quickly from which class the VisitExpr_ method is called? thank you !

the DeDupMutator have 3 levels of inheritance

By the above statement, I assume you’re referring to the fact that DeDupMutator inherits from MixedModeMutator, which in turn inherits from ExprMutator, which finally inherits from template class ExprFunctor. In that case, the generic c++ rules of method invocation should follow, the closest base class that defines a valid VisitExpr_ for that type would be called

If you’re referring to the multiple inheritance for DeDupMutator, I would guess only one of those parent classes would define a valid VisitExpr_, and if not, the VisitExpr_ has to be explicitly called with the class name according to c++ rules again (like obj.MixedModeMutator::VisitExpr_(expr)), so we shouldn’t see that case.

Thank you! I know the c++ calling rule about the multi-level inheritance. What confuses me is how to quickly find the closest base class method. By now I’m drawing the UML graph to help me find the method to step into, but I think it’s not efficient. So I wonder if there is a faster way.

I think the easiest way in gdb is probably to still just set breakpoint on all VisitExpr_ and let gdb hit the right one. The problem is that considering the number of VisitExpr_ methods in TVM, gdb takes a while to set breakpoints in all of them. When I tried, it set the breakpoints in 3711 locations. Buf right after the breakpoints were set, just continuing jumped into the VisitExpr_ that was called.

But since the parent classes mostly don’t do anything special, I kinda ignore them and set breakpoints only in the class I’m trying to understand. The VisitExpr_ in MixedModeMutator and ExprMutator would just recursively/iteratively call VIsitExpr again on its members, and then return, so I don’t normally try to step into them.

1 Like

I tried to set breakpoints on the derived class and all its base classes, it’s a relatively fast way. It helps me to understand the mechanism of the visitor and mutator. Thank you!

1 Like