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

2013/01/14 12:00am

LLVM 3.1 のソースコードから、llvm::Module の IR 出力用関数のソースコードを読んだ。

/// 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;

実装は lib/VMCore/Module.cpp ではなく、他の IR 出力用関数とともに lib/VMCore/AsmWriter.cpp にまとめられている。まずは 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

AssemblyWriterlib/VMCore/AsmWriter.cpp の無名ネームスペースに定義されているクラスであり、llvm 内のさまざまな型をストリームに出力する関数を提供している。AssemblyWriter::printModule のコードをざっと眺めてみれば、やっていることの想像はつくだろう。

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

同じく lib/VMCore/AsmWriter.cpp の最後の方に定義されている llvm::Module::dump はどうなっているだろうか。

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

llvm::Module::print を呼んでいるだけだ。

では、第一引数に渡している dbgs 関数の戻り値はなんだろうか?

llvm::dbgs

llvm::dbgs の宣言は include/llvm/Support/Debug.h にある。デバッグ用の出力ストリーム (raw_ostream) を返す関数だ。

/// 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();

実装は lib/Support/Debug.cpp で定義されており、NDEBUG が宣言されているか (非デバッグ・モード) 宣言されていないか (デバッグ・モード) で実装が切り替わるようになっている。

// 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

非デバッグ・モードのとき dbgs() は単に標準エラー出力への raw_ostream を返す。

#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;
}

デバッグ・モードの場合の実装も見てみよう。

/// 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;
}

デバッグ・モードの場合は、標準エラー出力をラップした circular_raw_ostream を返している。static な構造体のコンストラクタで初期化することで、 シグナル・ハンドラなども設定しているのが興味深い。