Code Reading: Dump and Print Function in llvm::Module Class
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:
- #1 a return value of
dbgs()
asraw_ostream
- #2
0
will be interpreted as a null pointerAssemblyAnnotationWriter*
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.