gollvm: emit debug meta-data for global variables

Fill in debug meta-data for global variables. This required
reworking the DIBuildHelper class, which previously was active
only during control flow generation for a function, and is now
a module-scope entity.

Change-Id: I15d82e0fd862ff976c385eb09b53bfc42347ca8d
Reviewed-on: https://go-review.googlesource.com/46419
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/llvm-gofrontend/go-llvm-dibuildhelper.cpp b/llvm-gofrontend/go-llvm-dibuildhelper.cpp
index 5632c14..8d5ae17 100644
--- a/llvm-gofrontend/go-llvm-dibuildhelper.cpp
+++ b/llvm-gofrontend/go-llvm-dibuildhelper.cpp
@@ -24,23 +24,70 @@
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/Support/FileSystem.h"
 
-DIBuildHelper::DIBuildHelper(Bnode *topnode,
+DIBuildHelper::DIBuildHelper(llvm::Module *module,
                              TypeManager *typemanager,
-                             Llvm_linemap *linemap,
-                             llvm::DIBuilder &builder,
-                             llvm::DIScope *moduleScope,
-                             llvm::BasicBlock *entryBlock)
-    : typemanager_(typemanager), linemap_(linemap),
-      dibuilder_(builder), moduleScope_(moduleScope),
-      topblock_(topnode->castToBblock()),
-      entryBlock_(entryBlock), known_locations_(0)
+                             Llvm_linemap *linemap)
+    : module_(module), typemanager_(typemanager), linemap_(linemap), moduleScope_(nullptr),
+      dibuilder_(new llvm::DIBuilder(*module)), topblock_(nullptr),
+      entryBlock_(nullptr), known_locations_(0)
 {
-  pushDIScope(moduleScope);
 }
 
-void DIBuildHelper::beginFunction(llvm::DIScope *scope, Bfunction *function)
+void DIBuildHelper::createCompileUnitIfNeeded()
 {
+  if (moduleScope_)
+    return;
+
+  // Create compile unit
+  llvm::SmallString<256> currentDir;
+  llvm::sys::fs::current_path(currentDir);
+  auto primaryFile =
+      dibuilder_->createFile(linemap_->get_initial_file(), currentDir);
+  bool isOptimized = true;
+  std::string compileFlags; // FIXME
+  unsigned runtimeVersion = 0; // not sure what would be for Go
+  moduleScope_ =
+      dibuilder_->createCompileUnit(llvm::dwarf::DW_LANG_Go, primaryFile,
+                                    "llvm-goparse", isOptimized,
+                                    compileFlags, runtimeVersion);
+  pushDIScope(moduleScope_);
+}
+
+void DIBuildHelper::finalize()
+{
+  dibuilder_->finalize();
+}
+
+void DIBuildHelper::processGlobal(Bvariable *v, bool isExported)
+{
+  createCompileUnitIfNeeded();
+
+  llvm::DIFile *vfile = diFileFromLocation(v->location());
+  unsigned vline = linemap()->location_line(v->location());
+  llvm::DIType *vdit = typemanager()->buildDIType(v->btype(), *this);
+  bool isLocalToUnit = !isExported;
+  dibuilder().createGlobalVariableExpression(moduleScope_,
+                                             v->name(), v->name(),
+                                             vfile, vline, vdit,
+                                             isLocalToUnit);
+}
+
+void DIBuildHelper::beginFunction(Bfunction *function,
+                                  Bnode *topnode,
+                                  llvm::BasicBlock *entryBlock)
+{
+  createCompileUnitIfNeeded();
+
+  assert(entryBlock_ == nullptr);
+  assert(entryBlock != nullptr);
+  entryBlock_ = entryBlock;
+
+  assert(topblock_ == nullptr);
+  assert(topnode->castToBblock());
+  topblock_ = topnode->castToBblock();
+
   known_locations_ = 0;
 
   // Create proper DIType for function
@@ -56,11 +103,10 @@
   unsigned scopeLine = fcnLine; // FIXME -- determine correct value here
   llvm::DIFile *difile = diFileFromLocation(function->location());
   auto difunc =
-      dibuilder().createFunction(scope, function->name(), function->asmName(),
+      dibuilder().createFunction(moduleScope(), function->name(), function->asmName(),
                                  difile, fcnLine, dst, isLocalToUnit,
                                  isDefinition, scopeLine);
   pushDIScope(difunc);
-
 }
 
 void DIBuildHelper::processVarsInBLock(const std::vector<Bvariable*> &vars,
@@ -139,6 +185,13 @@
 
   // Done with this scope
   popDIScope();
+  assert(diScopeStack_.size() == 1);
+
+  // Clean up
+  entryBlock_ = nullptr;
+  topblock_ = nullptr;
+  known_locations_ = 0;
+  declared_.clear();
 }
 
 // Front end tends to declare more blocks than strictly required for
@@ -210,12 +263,6 @@
   llvm::StringRef locfilename = llvm::sys::path::filename(locfile);
   if (linemap()->is_predeclared(location))
     locdir = "";
-#if 0
-  llvm::SmallString<256> currentDir;
-  llvm::sys::fs::current_path(currentDir);
-  if (locdir == "" || locdir == ".")
-    locdir = currentDir;
-#endif
   return dibuilder().createFile(locfilename, locdir);
 }
 
diff --git a/llvm-gofrontend/go-llvm-dibuildhelper.h b/llvm-gofrontend/go-llvm-dibuildhelper.h
index 14484ce..776797f 100644
--- a/llvm-gofrontend/go-llvm-dibuildhelper.h
+++ b/llvm-gofrontend/go-llvm-dibuildhelper.h
@@ -29,6 +29,7 @@
 class DIType;
 class DebugLoc;
 class Instruction;
+class Module;
 class Type;
 }
 
@@ -47,19 +48,20 @@
 
 class DIBuildHelper {
  public:
-  DIBuildHelper(Bnode *topNode,
+  DIBuildHelper(llvm::Module *module,
                 TypeManager *typemanager,
-                Llvm_linemap *linemap,
-                llvm::DIBuilder &builder,
-                llvm::DIScope *moduleScope,
-                llvm::BasicBlock *entryBlock);
+                Llvm_linemap *linemap);
 
-  void beginFunction(llvm::DIScope *scope, Bfunction *function);
+  void processGlobal(Bvariable *gvar, bool isExported);
+
+  void beginFunction(Bfunction *function, Bnode *topnode, llvm::BasicBlock *entryBlock);
   void endFunction(Bfunction *function);
 
   void beginLexicalBlock(Bblock *block);
   void endLexicalBlock(Bblock *block);
 
+  void finalize();
+
   llvm::DIFile *diFileFromLocation(Location location);
 
   void processExprInst(Bexpression *expr, llvm::Instruction *inst);
@@ -75,7 +77,7 @@
   void pushDIScope(llvm::DIScope *);
 
   // Various getters
-  llvm::DIBuilder &dibuilder() { return dibuilder_; }
+  llvm::DIBuilder &dibuilder() { return *dibuilder_.get(); }
   Llvm_linemap *linemap() { return linemap_; }
   TypeManager *typemanager() { return typemanager_; }
 
@@ -88,20 +90,23 @@
   }
 
  private:
+  llvm::Module *module_;
   TypeManager *typemanager_;
   Llvm_linemap *linemap_;
-  llvm::DIBuilder &dibuilder_;
   llvm::DIScope *moduleScope_;
-  Bblock *topblock_;
+  std::unique_ptr<llvm::DIBuilder> dibuilder_;
   std::vector<llvm::DIScope*> diScopeStack_;
   std::unordered_map<Btype *, llvm::DIType*> typeCache_;
   std::unordered_map<llvm::DIType *, llvm::DIType*> typeReplacements_;
+
+  // The following items are specific to the current function we're visiting.
   std::unordered_set<Bvariable *> declared_;
+  Bblock *topblock_;
   llvm::BasicBlock *entryBlock_;
   unsigned known_locations_;
 
-
  private:
+  void createCompileUnitIfNeeded();
   llvm::DebugLoc debugLocFromLocation(Location location);
   void insertVarDecl(Bvariable *var, llvm::DILocalVariable *dilv);
   bool interestingBlock(Bblock *block);
diff --git a/llvm-gofrontend/go-llvm.cpp b/llvm-gofrontend/go-llvm.cpp
index 1603103..d9eb8a8 100644
--- a/llvm-gofrontend/go-llvm.cpp
+++ b/llvm-gofrontend/go-llvm.cpp
@@ -38,7 +38,6 @@
 #include "llvm/IR/Type.h"
 #include "llvm/IR/Value.h"
 #include "llvm/IR/Verifier.h"
-#include "llvm/Support/FileSystem.h"
 
 Llvm_backend::Llvm_backend(llvm::LLVMContext &context,
                            llvm::Module *module,
@@ -48,7 +47,6 @@
     , module_(module)
     , datalayout_(module ? &module->getDataLayout() : nullptr)
     , nbuilder_(this)
-    , diCompileUnit_(nullptr)
     , linemap_(linemap)
     , addressSpace_(0)
     , traceLevel_(0)
@@ -111,6 +109,9 @@
 
   // Initialize machinery for builtins
   builtinTable_->defineAllBuiltins();
+
+  if (createDebugMetaData_)
+    dibuildhelper_.reset(new DIBuildHelper(module_, typeManager(), linemap_));
 }
 
 Llvm_backend::~Llvm_backend() {
@@ -120,34 +121,6 @@
     delete bfcn;
 }
 
-llvm::DICompileUnit *Llvm_backend::getDICompUnit()
-{
-  if (!createDebugMetaData_)
-    return nullptr;
-
-  if (diCompileUnit_ || !createDebugMetaData_)
-    return diCompileUnit_;
-
-  // Create debug info builder
-  assert(dibuilder_.get() == nullptr);
-  dibuilder_.reset(new llvm::DIBuilder(*module_));
-
-  // Create compile unit
-  llvm::SmallString<256> currentDir;
-  llvm::sys::fs::current_path(currentDir);
-  auto primaryFile =
-      dibuilder_->createFile(linemap_->get_initial_file(), currentDir);
-  bool isOptimized = true;
-  std::string compileFlags; // FIXME
-  unsigned runtimeVersion = 0; // not sure what would be for Go
-  diCompileUnit_ =
-      dibuilder_->createCompileUnit(llvm::dwarf::DW_LANG_Go, primaryFile,
-                                    "llvm-goparse", isOptimized,
-                                    compileFlags, runtimeVersion);
-
-  return diCompileUnit_;
-}
-
 TypeManager *Llvm_backend::typeManager() const {
   const TypeManager *tm = this;
   return const_cast<TypeManager*>(tm);
@@ -707,7 +680,7 @@
   Bvariable *rv = makeModuleVar(type, ctag, "", Location(),
                                 MV_Constant, MV_DefaultSection,
                                 MV_NotInComdat, MV_DefaultVisibility,
-                                MV_NotExternallyInitialized,
+                                MV_NotExternallyInitialized, MV_SkipDebug,
                                 llvm::GlobalValue::PrivateLinkage,
                                 conval, 0);
   assert(llvm::isa<llvm::GlobalVariable>(rv->value()));
@@ -1134,7 +1107,7 @@
       makeModuleVar(makeAuxType(scon->getType()),
                     "", "", Location(), MV_Constant, MV_DefaultSection,
                     MV_NotInComdat, MV_DefaultVisibility,
-                    MV_NotExternallyInitialized,
+                    MV_NotExternallyInitialized, MV_SkipDebug,
                     llvm::GlobalValue::PrivateLinkage, scon, 1);
   llvm::Constant *varval = llvm::cast<llvm::Constant>(svar->value());
   llvm::Constant *bitcast =
@@ -3016,6 +2989,7 @@
                             ModVarComdat inComdat,
                             ModVarVis isHiddenVisibility,
                             ModVarExtInit isExtInit,
+                            ModVarGenDebug genDebug,
                             llvm::GlobalValue::LinkageTypes linkage,
                             llvm::Constant *initializer,
                             unsigned alignment)
@@ -3034,8 +3008,6 @@
   // variable.
   assert(inUniqueSection == MV_DefaultSection);
 
-  // FIXME: add DIGlobalVariable to debug info for this variable
-
   llvm::Constant *init =
       (isExtInit == MV_ExternallyInitialized ? nullptr :
        llvm::Constant::getNullValue(btype->type()));
@@ -3063,6 +3035,10 @@
       new Bvariable(btype, location, gname, GlobalVar, addressTaken, glob);
   assert(valueVarMap_.find(bv->value()) == valueVarMap_.end());
   valueVarMap_[bv->value()] = bv;
+  if (genDebug == MV_GenDebug && dibuildhelper() && !errorCount_) {
+    bool exported = (isHiddenVisibility != MV_HiddenVisibility);
+    dibuildhelper()->processGlobal(bv, exported);
+  }
   return bv;
 }
 
@@ -3087,7 +3063,7 @@
   Bvariable *gvar =
       makeModuleVar(btype, var_name, asm_name, location,
                     MV_NonConstant, inUniqSec, MV_NotInComdat,
-                    varVis, extInit, linkage, nullptr);
+                    varVis, extInit, MV_GenDebug, linkage, nullptr);
   return gvar;
 }
 
@@ -3213,7 +3189,8 @@
   Bvariable *gvar =
       makeModuleVar(btype, name, asm_name, Location(),
                     isConst, inUniqSec, inComdat,
-                    varVis, extInit, linkage, nullptr, alignment);
+                    varVis, extInit, MV_SkipDebug, linkage,
+                    nullptr, alignment);
   return gvar;
 }
 
@@ -3271,7 +3248,7 @@
   Bvariable *gvar =
       makeModuleVar(btype, name, asm_name, location,
                     MV_Constant, inUniqueSec, inComdat,
-                    varVis, extInit, linkage, nullptr);
+                    varVis, extInit, MV_SkipDebug, linkage, nullptr);
   return gvar;
 }
 
@@ -3490,8 +3467,8 @@
 class GenBlocks {
 public:
   GenBlocks(llvm::LLVMContext &context, Llvm_backend *be,
-            Bfunction *function, Bnode *topNode, llvm::DIScope *scope,
-            bool createDebugMetadata, llvm::BasicBlock *entryBlock);
+            Bfunction *function, Bnode *topNode,
+            DIBuildHelper *diBuildHelper, llvm::BasicBlock *entryBlock);
 
   llvm::BasicBlock *walk(Bnode *node, llvm::BasicBlock *curblock);
   void finishFunction(llvm::BasicBlock *entry);
@@ -3519,15 +3496,14 @@
   std::pair<llvm::Instruction*, llvm::BasicBlock *>
   postProcessInst(llvm::Instruction *inst,
                   llvm::BasicBlock *curblock);
-  llvm::DIBuilder &dibuilder() { return be_->dibuilder(); }
-  DIBuildHelper &dibuildhelper() { return *dibuildhelper_.get(); }
+  DIBuildHelper *dibuildhelper() const { return dibuildhelper_; }
   Llvm_linemap *linemap() { return be_->linemap(); }
 
  private:
   llvm::LLVMContext &context_;
   Llvm_backend *be_;
   Bfunction *function_;
-  std::unique_ptr<DIBuildHelper> dibuildhelper_;
+  DIBuildHelper *dibuildhelper_;
   std::map<LabelId, llvm::BasicBlock *> labelmap_;
   std::vector<llvm::BasicBlock*> padBlockStack_;
   std::set<llvm::AllocaInst *> temporariesDiscovered_;
@@ -3535,37 +3511,27 @@
   llvm::BasicBlock* finallyBlock_;
   Bstatement *cachedReturn_;
   bool emitOrphanedCode_;
-  bool createDebugMetaData_;
 };
 
 GenBlocks::GenBlocks(llvm::LLVMContext &context,
                      Llvm_backend *be,
                      Bfunction *function,
                      Bnode *topNode,
-                     llvm::DIScope *scope,
-                     bool createDebugMetadata,
+                     DIBuildHelper *dibuildhelper,
                      llvm::BasicBlock *entryBlock)
     : context_(context), be_(be), function_(function),
-      dibuildhelper_(nullptr), finallyBlock_(nullptr),
-      cachedReturn_(nullptr), emitOrphanedCode_(false),
-      createDebugMetaData_(createDebugMetadata)
+      dibuildhelper_(dibuildhelper), finallyBlock_(nullptr),
+      cachedReturn_(nullptr), emitOrphanedCode_(false)
 {
-  if (createDebugMetaData_) {
-    dibuildhelper_.reset(new DIBuildHelper(topNode,
-                                           be->typeManager(),
-                                           be->linemap(),
-                                           be->dibuilder(),
-                                           be->getDICompUnit(),
-                                           entryBlock));
-    dibuildhelper().beginFunction(scope, function);
-  }
+  if (dibuildhelper_)
+    dibuildhelper_->beginFunction(function, topNode, entryBlock);
 }
 
 void GenBlocks::finishFunction(llvm::BasicBlock *entry)
 {
   function_->fixupProlog(entry, newTemporaries_);
-  if (createDebugMetaData_)
-    dibuildhelper().endFunction(function_);
+  if (dibuildhelper_)
+    dibuildhelper_->endFunction(function_);
 }
 
 llvm::BasicBlock *GenBlocks::mkLLVMBlock(const std::string &name,
@@ -3680,8 +3646,8 @@
     }
     auto pair = postProcessInst(originst, curblock);
     auto inst = pair.first;
-    if (createDebugMetaData_)
-      dibuildhelper().processExprInst(expr, inst);
+    if (dibuildhelper_)
+      dibuildhelper_->processExprInst(expr, inst);
     curblock->getInstList().push_back(inst);
     curblock = pair.second;
   }
@@ -4015,12 +3981,12 @@
     }
     case N_BlockStmt: {
       Bblock *bblock = stmt->castToBblock();
-      if (createDebugMetaData_)
-        dibuildhelper().beginLexicalBlock(bblock);
+      if (dibuildhelper_)
+        dibuildhelper_->beginLexicalBlock(bblock);
       for (auto &st : stmt->getChildStmts())
         curblock = walk(st, curblock);
-      if (createDebugMetaData_)
-        dibuildhelper().endLexicalBlock(bblock);
+      if (dibuildhelper_)
+        dibuildhelper_->endLexicalBlock(bblock);
       break;
     }
     case N_IfStmt: {
@@ -4126,11 +4092,13 @@
   llvm::BasicBlock *entryBlock = genEntryBlock(function);
 
   // Avoid debug meta-generation if errors seen
-  bool dodebug = (createDebugMetaData_ && errorCount_ == 0);
+  DIBuildHelper *dibh = nullptr;
+  if (createDebugMetaData_ && errorCount_ == 0)
+    dibh = dibuildhelper();
 
   // Walk the code statements
   GenBlocks gb(context_, this, function, code_stmt,
-               getDICompUnit(), dodebug, entryBlock);
+               dibh, entryBlock);
   llvm::BasicBlock *block = gb.walk(code_stmt, entryBlock);
   gb.finishFunction(entryBlock);
 
@@ -4190,8 +4158,8 @@
 void Llvm_backend::finalizeExportData()
 {
   // Calling this here, for lack of a better spot
-  if (dibuilder_.get())
-    dibuilder_->finalize();
+  if (errorCount_ == 0 && dibuildhelper())
+    dibuildhelper_->finalize();
 
   assert(! exportDataFinalized_);
   exportDataFinalized_ = true;
diff --git a/llvm-gofrontend/go-llvm.h b/llvm-gofrontend/go-llvm.h
index 52ae2b9..86d877c 100644
--- a/llvm-gofrontend/go-llvm.h
+++ b/llvm-gofrontend/go-llvm.h
@@ -339,8 +339,8 @@
   // Type manager functionality
   TypeManager *typeManager() const;
 
-  // DI builder
-  llvm::DIBuilder &dibuilder() { return *dibuilder_.get(); }
+  // DI build helper. Will be NULL if debug meta-data generation disabled.
+  DIBuildHelper *dibuildhelper() { return dibuildhelper_.get(); }
 
   // Bnode builder
   BnodeBuilder &nodeBuilder() { return nbuilder_; }
@@ -417,6 +417,7 @@
   enum ModVarComdat { MV_InComdat, MV_NotInComdat };
   enum ModVarVis { MV_HiddenVisibility, MV_DefaultVisibility };
   enum ModVarExtInit { MV_ExternallyInitialized, MV_NotExternallyInitialized };
+  enum ModVarGenDebug { MV_GenDebug, MV_SkipDebug };
 
   // Make a module-scope variable (static, global, or external).
   Bvariable *makeModuleVar(Btype *btype,
@@ -428,6 +429,7 @@
                            ModVarComdat inComdat,
                            ModVarVis isHiddenVisibility,
                            ModVarExtInit isExtInit,
+                           ModVarGenDebug genDebug,
                            llvm::GlobalValue::LinkageTypes linkage,
                            llvm::Constant *initializer,
                            unsigned alignmentInBytes = 0);
@@ -666,11 +668,8 @@
   // Builder for constructing Bexpressions and Bstatements.
   BnodeBuilder nbuilder_;
 
-  // Debug info builder
-  std::unique_ptr<llvm::DIBuilder> dibuilder_;
-
-  // Root debug meta-data scope for compilation unit
-  llvm::DICompileUnit *diCompileUnit_;
+  // Debug info builder.
+  std::unique_ptr<DIBuildHelper> dibuildhelper_;
 
   // Linemap to use. If client did not supply a linemap during
   // construction, then ownLinemap_ is filled in.
diff --git a/unittests/BackendCore/BackendDebugEmit.cpp b/unittests/BackendCore/BackendDebugEmit.cpp
index d9c8ad1..0fc3fcf 100644
--- a/unittests/BackendCore/BackendDebugEmit.cpp
+++ b/unittests/BackendCore/BackendDebugEmit.cpp
@@ -130,4 +130,29 @@
     std::cerr << fdump;
 }
 
+TEST(BackendVarTests, TestGlobalVarDebugEmit) {
+  FcnTestHarness h("foo");
+  Llvm_backend *be = h.be();
+  Location loc = h.loc();
+
+  Btype *bi32t = be->integer_type(false, 32);
+  Bvariable *g1 =
+      be->global_variable("_bar", "bar", bi32t,
+                          true,         /* is_external */
+                          false,        /* is_hidden */
+                          false,        /* unique_section */
+                          loc);
+  be->global_variable_set_init(g1, mkInt32Const(be, 101));
+
+  bool broken = h.finish(PreserveDebugInfo);
+  EXPECT_FALSE(broken && "Module failed to verify.");
+
+  // This is a long way from verifying that the debug meta-data is in fact
+  // completely correct, but at least it checks that the global
+  // wasn't skipped.
+  bool ok = h.expectModuleDumpContains("!DIGlobalVariable(name: \"bar\",");
+  EXPECT_TRUE(ok);
+
+}
+
 }
diff --git a/unittests/BackendCore/TestUtils.cpp b/unittests/BackendCore/TestUtils.cpp
index 8a65ee2..da06c14 100644
--- a/unittests/BackendCore/TestUtils.cpp
+++ b/unittests/BackendCore/TestUtils.cpp
@@ -703,6 +703,15 @@
   return equal;
 }
 
+bool FcnTestHarness::expectModuleDumpContains(const std::string &expected)
+{
+  std::string res;
+  llvm::raw_string_ostream os(res);
+  be()->module().print(os, nullptr);
+  std::string actual(trimsp(os.str()));
+  return containstokens(actual, expected);
+}
+
 bool FcnTestHarness::expectRepr(Bnode *node, const std::string &expected)
 {
   std::string reason;
@@ -715,21 +724,24 @@
 
 bool FcnTestHarness::finish(DebugDisposition whatToDoWithDebugInfo)
 {
-  // Emit a label def for the pending block if needed
-  Bstatement *bst = be()->block_statement(curBlock_);
-  if (nextLabel_) {
-    Bstatement *ldef = be()->label_definition_statement(nextLabel_);
-    bst = be()->compound_statement(ldef, bst);
+  if (func_) {
+
+    // Emit a label def for the pending block if needed
+    Bstatement *bst = be()->block_statement(curBlock_);
+    if (nextLabel_) {
+      Bstatement *ldef = be()->label_definition_statement(nextLabel_);
+      bst = be()->compound_statement(ldef, bst);
+    }
+
+    // Add current block as stmt to entry block
+    addStmtToBlock(be(), entryBlock_, bst);
+
+    // Set function body
+    be()->function_set_body(func_, entryBlock_);
+
   }
-
-  // Add current block as stmt to entry block
-  addStmtToBlock(be(), entryBlock_, bst);
-
-  // Set function body
-  be()->function_set_body(func_, entryBlock_);
-
-  // Finalize export data. This has the side effect of finalizing
-  // debug meta-data, which we need to do before invoking the verifier.
+    // Finalize export data. This has the side effect of finalizing
+    // debug meta-data, which we need to do before invoking the verifier.
   be()->finalizeExportData();
 
   // Strip debug info now if requested
diff --git a/unittests/BackendCore/TestUtils.h b/unittests/BackendCore/TestUtils.h
index 2a5e193..85cf0e3 100644
--- a/unittests/BackendCore/TestUtils.h
+++ b/unittests/BackendCore/TestUtils.h
@@ -229,6 +229,12 @@
   // and emit diagnostics if not.
   bool expectRepr(Bnode *node, const std::string &expected);
 
+  // Verify that a dump of the module contains the specified token sequence
+  // somewhere within it. To be used only if the various methods above
+  // are inadequate, since obviously there is more potential for
+  // trouble here.
+  bool expectModuleDumpContains(const std::string &expected);
+
   //
   // Finish function:
   // - attach current block to function