summaryrefslogtreecommitdiff
path: root/lib/Transforms/Instrumentation/DebugIR.cpp
blob: 62a9b8abd8fb7106b26a4aca629f0b36d199edc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
//===--- DebugIR.cpp - Transform debug metadata to allow debugging IR -----===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// A Module transform pass that emits a succinct version of the IR and replaces
// the source file metadata to allow debuggers to step through the IR.
//
// The location where the IR file is emitted is the same as the directory
// operand of the !llvm.dbg.cu metadata node present in the input module. The
// file name is constructed from the original file name by stripping the
// extension and replacing it with "-debug.ll" or the Postfix string specified
// at construction.
//
// FIXME: instead of replacing debug metadata, additional metadata should be
// used to point capable debuggers to the IR file without destroying the
// mapping to the original source file.
//
// FIXME: this pass should not depend on the existance of debug metadata in
// the module as it does now. Instead, it should use DIBuilder to create the
// required metadata.
//
//===----------------------------------------------------------------------===//

#include <string>

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/DebugInfo.h"
#include "llvm/DIBuilder.h"
#include "llvm/IR/AsmWriter.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Instrumentation.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;

namespace {

/// Returns true if Node's name contains the string "llvm.dbg"
bool isDebugNamedMetadata(const NamedMDNode *Node) {
  return Node->getName().str().find("llvm.dbg") != std::string::npos;
}

/// Returns true if Inst is a call to llvm.dbg.value or llvm.dbg.declare
bool isDebugIntrinsic(const IntrinsicInst *Inst) {
  Intrinsic::ID id = Inst->getIntrinsicID();
  return id == Intrinsic::dbg_value || id == Intrinsic::dbg_declare;
}

/// An AssemblyWriter which generates a succinct representation of the module
/// (without debug intrinsics or metadata) suitable for debugging. As IR
/// instructions are printed, !dbg metadata nodes are added (or updated)
/// to point to the corresponding line in the generated IR file instead
/// of the original source file. The input module must have been constructed
/// with debug metadata (i.e. clang -g).
class IRDebugInfoHelper : public llvm::AssemblyWriter {
  /// Directory of debug metadata
  const DebugInfoFinder &Finder;

  /// Flags to control the verbosity of the generated IR file
  bool hideDebugIntrinsics;
  bool hideDebugMetadata;

  /// Set to track metadata nodes to be printed (used only when
  /// printDebugMetadata == false)
  SmallSet<const MDNode *, 4> NonDebugNodes;

public:
  IRDebugInfoHelper(
      formatted_raw_ostream &o, const Module *M,
      AssemblyAnnotationWriter *AAW, const DebugInfoFinder &Finder,
      bool hideDebugIntrinsics = true, bool hideDebugMetadata = true)
      : AssemblyWriter(o, M, AAW), Finder(Finder),
        hideDebugIntrinsics(hideDebugIntrinsics),
        hideDebugMetadata(hideDebugMetadata) {}

private:
  virtual void printInstruction(const Instruction &I) {
    DebugLoc Loc(I.getDebugLoc());

    if (hideDebugMetadata)
      removeDebugMetadata(const_cast<Instruction &>(I));

    AssemblyWriter::printInstruction(I);
    Out.flush();
    // Adjust line number by 1 because we have not yet printed the \n
    unsigned Line = Out.getLine() + 1;

    DebugLoc NewLoc;
    if (!Loc.isUnknown())
      // I had a previous debug location: re-use the DebugLoc
      NewLoc = DebugLoc::get(Line, /* FIXME: support columns */ 0,
                             Loc.getScope(I.getContext()),
                             Loc.getInlinedAt(I.getContext()));
    else if (MDNode *scope = findFunctionMD(I.getParent()->getParent()))
      // I had no previous debug location, but M has some debug information
      NewLoc = DebugLoc::get(Line, 0, scope, /*FIXME: inlined instructions*/ 0);
    else
      // Neither I nor M has any debug information -- nothing to do here.
      // FIXME: support debugging of undecorated IR (generated by clang without
      //        the -g option)
      return;

    if (hideDebugMetadata)
      saveNonDebugMetadata(I);

    addDebugLocation(const_cast<Instruction &>(I), NewLoc);
  }

  virtual void printInstructionLine(const Instruction &I) {
    if (hideDebugIntrinsics)
      if (const IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I))
        if (isDebugIntrinsic(II))
          return;
    AssemblyWriter::printInstructionLine(I);
  }

  virtual void writeMDNode(unsigned Slot, const MDNode *Node) {
    if (hideDebugMetadata == false || isDebugMetadata(Node) == false)
      AssemblyWriter::writeMDNode(Slot, Node);
  }

  virtual void printNamedMDNode(const NamedMDNode *NMD) {
    if (hideDebugMetadata == false || isDebugNamedMetadata(NMD) == false)
      AssemblyWriter::printNamedMDNode(NMD);
  }

  /// Returns the MDNode that corresponds with F
  MDNode *findFunctionMD(const Function *F) {
    for (DebugInfoFinder::iterator i = Finder.subprogram_begin(),
                                   e = Finder.subprogram_end();
         i != e; ++i) {
      DISubprogram S(*i);
      if (S.getFunction() == F)
        return *i;
    }
    // cannot find F -- likely means there is no debug information
    return 0;
  }

  /// Saves all non-debug metadata attached to I
  void saveNonDebugMetadata(const Instruction &I) {
    typedef SmallVector<std::pair<unsigned, MDNode *>, 4> MDNodeVector;
    MDNodeVector Others;
    I.getAllMetadataOtherThanDebugLoc(Others);
    for (MDNodeVector::iterator i = Others.begin(), e = Others.end(); i != e;
         ++i)
      NonDebugNodes.insert(i->second);
  }

  /// Returns true if Node was not saved as non-debug metadata with
  /// saveNonDebugMetadata(), false otherwise.
  bool isDebugMetadata(const MDNode *Node) {
    return NonDebugNodes.count(Node) == 0;
  }

  void removeDebugMetadata(Instruction &I) {
    if (I.getMetadata(LLVMContext::MD_dbg))
      I.setMetadata(LLVMContext::MD_dbg, 0);
  }

  void addDebugLocation(Instruction &I, DebugLoc Loc) {
    MDNode *MD = Loc.getAsMDNode(I.getContext());
    I.setMetadata(LLVMContext::MD_dbg, MD);
  }
};

class DebugIR : public ModulePass {
  std::string Postfix;
  std::string Filename;
  DebugInfoFinder Finder;

public:
  static char ID;

  DebugIR() : ModulePass(ID), Postfix("-debug.ll") {}

  /// Customize the postfix string used to replace the extension of the
  /// original filename that appears in the !llvm.dbg.cu metadata node.
  DebugIR(StringRef postfix) : ModulePass(ID), Postfix(postfix) {}

private:
  // Modify the filename embedded in the Compilation-Unit debug information of M
  bool replaceFilename(Module &M) {
    bool changed = false;

    // Sanity check -- if llvm.dbg.cu node exists, the DebugInfoFinder
    // better have found at least one CU!
    if (M.getNamedMetadata("llvm.dbg.cu"))
      assert(Finder.compile_unit_count() > 0 &&
             "Found no compile units but llvm.dbg.cu node exists");

    for (DebugInfoFinder::iterator i = Finder.compile_unit_begin(),
                                   e = Finder.compile_unit_end();
         i != e; ++i) {
      DICompileUnit CU(*i);
      Filename = CU.getFilename();

      // Replace extension with postfix
      size_t dot = Filename.find_last_of(".");
      if (dot != std::string::npos)
        Filename.erase(dot);
      Filename += Postfix;

      CU.setFilename(Filename, M.getContext());
      changed = true;
    }
    return changed;
  }

  void writeAndUpdateDebugIRFile(Module *M) {
    std::string error;
    tool_output_file OutFile(Filename.c_str(), error);
    OutFile.keep();
    formatted_raw_ostream OS;
    OS.setStream(OutFile.os(), false);

    IRDebugInfoHelper W(OS, M, 0, Finder);
    W.printModule(M);
  }

  bool runOnModule(Module &M) {
    Finder.processModule(M);
    bool changed = replaceFilename(M);
    if (changed)
      writeAndUpdateDebugIRFile(&M);
    return changed;
  }
};

} // anonymous namespace

char DebugIR::ID = 0;
INITIALIZE_PASS(DebugIR, "debug-ir", "Enable debugging IR", false, false)
    ModulePass *llvm::createDebugIRPass(StringRef FilenamePostfix) {
  return new DebugIR(FilenamePostfix);
}