Chapel Debugger (Student Project)

Greetings Chapel Developers!

We are Computer Science students at Western Washington University working on a project to improve the debugging experience for Chapel. One of our goals is to provide a generic debugging method which may be called from our debugger with GDB to print out Chapel variables. To do this, we’re attempting to modify the compiler so as to instantiate a generic method for different types. However it seems like our functions are getting pruned away and do not actually get generated. Below are some snippets from what we’re working on:

Here is the definition of the debug function we are attempting to instantiate. The pragma is being used to link it with the well known functions. The implementation is elided as there are many chapel types writeln does not work on by default (c_string, c_fn_ptr, c_file, etc).

pragma "debug print"
proc chpl_debug_print(param x) {
// TODO writeln(x);

We are instantiating the chpl_debug_print procedure for each type ts in the program with the following code within compiler/resolution/functionResolution.resolve():

forv_Vec(TypeSymbol, ts, gTypeSymbols) {
    if (ts->isKnownToBeGeneric() || strcmp(ts->cname, "void") == 0 || strcmp(ts->cname, "nothing") == 0 || strcmp(ts->cname, "_ref_void") == 0) 
    SymbolMap subs;
    subs.put(gChplDebugPrint->getFormal(1), paramMap.get(ts));
    FnSymbol* debugFn = instantiateWithoutCall(gChplDebugPrint, subs);

If we run this code before the pruneResolvedTree pass, the generated debug instances are pruned since they are unused, while if we run this after the prune pass, gChplDebugPrint is null since (we believe) it has been pruned.

Our immediate question is: How can we ensure our function is instantiated (and ultimately generated) and does not get pruned away?

-- Paul, Aedan, Sakari, Tom

1 Like

Hi all,

As a first plan of attack, have you tried using the --break-on-remove-id flag in conjunction with a debugger? I would try that and aim to identify the path in pruneResolvedTree which leads to the instantiations being removed.

Try to get the ID of one or more instantiations using a debugger, perhaps by breaking at the end of that loop body. You can use the command nv in the debugger on the AST node (the debugFn variable in this case) to print it with ID numbers (assuming you launched chpl with --lldb or --gdb).

Brainstorming a potential solution: I think it would be perfectly reasonable to consider functions marked with FLAG_DEBUG_PRINT as roots and thus always reachable. This is similar to what we do for functions marked with FLAG_EXPORT.

I suspect you need to check for your pragma in the loop in removeUnusedFunctions. I might be able to be more help if you provide a git repository reproducing the issue you are running into.

1 Like

Hi Paul, Aedan, Sakari, and Tom —

Catching up on this, David's comment:

made me realize that beyond simply instantiating the function, you will probably need to have the compiler resolve it so that the body of the function is capable of being code-generated. This makes me wonder whether it would be a viable approach to have your loop attach FLAG_EXPORT to the instantiated versions of the routine (which is actually accurate, since you're effectively asking for it to support external calls). And then if your loop is prior to the resolveExports() step, you'll automatically have them get resolved. And, because they're exports, they shouldn't be pruned from the tree either. So attaching that flag could resolve both issues neatly without any further special cases.