Code Reading: Dump and Print Function in llvm::Module Class

2013/01/14 12:00am

It’s time for code reading. Today, I read the functions dump() and print() which dump IR code and are implemented in llvm::Module which I had been curious about it for a while. Notice, the referenced code base is LLVM 3.1.

/// Print the module to an output stream with an optional
/// AssemblyAnnotationWriter.
void print(raw_ostream &OS, AssemblyAnnotationWriter *AAW) const;

/// Dump the module to stderr (for debugging).
void dump() const;

Both functions are instance methods of llvm::Module, but the implementation code is in lib/VMCore/AsmWriter.cpp which also contains other debug functions, not in lib/VMCore/Module.cpp. First of all, I should set about reading the function llvm::Module::print.

void Module::print(raw_ostream &ROS, AssemblyAnnotationWriter *AAW) const {
  SlotTracker SlotTable(this);
  formatted_raw_ostream OS(ROS);
  AssemblyWriter W(OS, SlotTable, this, AAW);
  W.printModule(this);
}

llvm::Module::print

The class AssemblyWriter is defined in unnamed namespace in lib/VMCore/AsmWriter.cpp. It can print various LLVM types in a stream. Take a quick look at the function, then you can get the overall gist.

void AssemblyWriter::printModule(const Module *M) {
  if (!M->getModuleIdentifier().empty() &&
      // Don't print the ID if it will start a new line (which would
      // require a comment char before it).
      M->getModuleIdentifier().find('\n') == std::string::npos)
    Out << "; ModuleID = '" << M->getModuleIdentifier() << "'\n";
...
}

llvm::Module::dump

Next, let’s look at the function llvm::Module::dump also defined in lib/VMCore/AsmWriter.cpp.

// Module::dump() - Allow printing of Modules from the debugger.
void Module::dump() const { print(dbgs(), 0); }

Easy enough. It just call the llvm::Module::print with arguments:

So, what about a return value of dbgs()?

llvm::dbgs

The declaration of llvm::dbgs is in the file include/llvm/Support/Debug.h. It’s a function which returns an output stream for debugging.

/// dbgs() - This returns a reference to a raw_ostream for debugging
/// messages.  If debugging is disabled it returns errs().  Use it
/// like: dbgs() << "foo" << "bar";
raw_ostream &dbgs();

The function implementation is in the file lib/Support/Debug.cpp. It switches its implementation whether NDEBUG is undefined or not.

// All Debug.h functionality is a no-op in NDEBUG mode.
#ifndef NDEBUG
/// dbgs - Return a circular-buffered debug stream.
raw_ostream &llvm::dbgs() {
...
}
...
#else
// Avoid "has no symbols" warning.
namespace llvm {
  /// dbgs - Return errs().
  raw_ostream &dbgs() {
    return errs();
  }
}

#endif

When NDEBUG is defined (no debug mode), dbgs() function just returns a raw_ostream instance which points to standard error.

#ifndef STDERR_FILENO
# define STDERR_FILENO 2
#endif
...
/// errs() - This returns a reference to a raw_ostream for standard error.
/// Use it like: errs() << "foo" << "bar";
raw_ostream &llvm::errs() {
  // Set standard error to be unbuffered by default.
  static raw_fd_ostream S(STDERR_FILENO, false, true);
  return S;
}

But the opposite has a more complicated implementation.

/// dbgs - Return a circular-buffered debug stream.
raw_ostream &llvm::dbgs() {
  // Do one-time initialization in a thread-safe way.
  static struct dbgstream {
    circular_raw_ostream strm;

    dbgstream() :
        strm(errs(), "*** Debug Log Output ***\n",
             (!EnableDebugBuffering || !DebugFlag) ? 0 : DebugBufferSize) {
      if (EnableDebugBuffering && DebugFlag && DebugBufferSize != 0)
        // TODO: Add a handler for SIGUSER1-type signals so the user can
        // force a debug dump.
        sys::AddSignalHandler(&debug_user_sig_handler, 0);
      // Otherwise we've already set the debug stream buffer size to
      // zero, disabling buffering so it will output directly to errs().
    }
  } thestrm;

  return thestrm.strm;
}

It returns a circular_raw_ostream which internally uses standard error stream. The stream is initialized in the constructor of a struct dbgstream. The struct is declared with static, so it is statically allocated and its constructor is invoked exactly once. The constructor does not initialize a stream but also set a signal handler.