New helpers for writing dump() methods

It's happened to all of us. You're sitting there, debugging the compiler, and you dig a pointer out of the bowels of some horrible data structure. Then you go to dump it, and lldb gives you the dreaded:

error: Couldn't lookup symbols:
  swift::Whatever::dump() const

Curses, foiled again!

What happened, of course, is that clang optimized away the dump() method because nothing called it. And nothing called it, of course, because the dump() method doesn't exist to be called by code in the compiler—it exists to be called by Swift contributors in the debugger.

To avoid this, you need to add LLVM_ATTRIBUTE_USED to the method. In many parts of the compiler, the convention is also to use LLDB_ATTRIBUTE_DEPRECATED so that leaving a temporary dump() method call in the compiler produces a warning. But contributors frequently forget to do one or both of these things.

This finally happened to me one too many times on Tuesday, so I've added a new header and set of macros to the compiler. If we all use them consistently, we can get rid of this annoyance without having to copy code around perfectly every time.

What to do

If you want to declare a dump() method, please include swift/Basic/Debug.h and then declare your method like this:

class MyClass {
  ...
  SWIFT_DEBUG_DUMP;
};

Then define it as you normally would, with void MyClass::dump() const {...}.

The macro translates to void dump() const, but with the extra attributes added to ensure that it's usable in the debugger and not accidentally used in the rest of the compiler.

If you need a little more flexibility, there are two variants of this macro you can use instead. The first lets you declare a dumping method with a different name, with parameters, or both:

class MyClass {
  ...
  SWIFT_DEBUG_DUMPER(dumpRef());
  SWIFT_DEBUG_DUMPER(dump(SourceManager *SM));
};

And the second is a more general one which just adds the attributes to any method, in case you want to write something else that should only be called from the debugger:

class MyClass {
  ...
  SWIFT_DEBUG_HELPER(bool hasName(const char *name) const);
};

(Theoretically the SWIFT_DEBUG_HELPER() form is the only one we really need, but I want to make the most common cases as easy to write as possible so that people use the macros instead of declaring them on their own.)

Remember that these methods will not be removed from release builds; if you want to do that, you'll still need to use #ifndef NDEBUG as usual.

Unfinished business

So far, I've only modified the compiler frontend (i.e. components that primarily work on the Syntax or AST representations) to fully adopt these macros.

I haven't modified SIL-handling phases because they frequently call dump() methods from permanent debug-logging code. I think we should change this code to instead use these methods' dump(raw_ostream &) or print(raw_ostream &) companions, which are meant to be called by other compiler code; this would not only allow us to extend SWIFT_DEBUG_DUMP to this part of the compiler, it would also keep us from accidentally mixing output to llvm::errs() and llvm::dbgs(), which this code does pretty often. But I don't want to make a wholesale change without input from folks who work on these parts of the compiler.

I also haven't changed components like reflection or the runtime to use these macros. These are components where we actually might want to compile out all debugging code; I think they might need a different solution.

Thoughts?

5 Likes
Terms of Service

Privacy Policy

Cookie Policy