Coverage Report

Created: 2025-08-28 06:48

/src/hermes/lib/Utils/Dumper.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#include <cctype>
9
#include <string>
10
11
#include "llvh/ADT/DenseSet.h"
12
#include "llvh/ADT/SmallVector.h"
13
#include "llvh/Support/Casting.h"
14
#include "llvh/Support/ErrorHandling.h"
15
#include "llvh/Support/GraphWriter.h"
16
#include "llvh/Support/raw_ostream.h"
17
18
#include "hermes/AST/Context.h"
19
#include "hermes/FrontEndDefs/Builtins.h"
20
#include "hermes/IR/CFG.h"
21
#include "hermes/IR/IR.h"
22
#include "hermes/IR/IRVisitor.h"
23
#include "hermes/IR/Instrs.h"
24
#include "hermes/Optimizer/Wasm/WasmIntrinsics.h"
25
#include "hermes/Support/Statistic.h"
26
#include "hermes/Utils/Dumper.h"
27
28
using namespace hermes;
29
30
using llvh::dyn_cast;
31
using llvh::isa;
32
33
namespace hermes {
34
35
0
std::string IRPrinter::escapeStr(llvh::StringRef name) {
36
0
  std::string s = name.str();
37
0
  std::string out;
38
0
  out += getQuoteSign();
39
0
  for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) {
40
0
    unsigned char c = *i;
41
0
    if (std::isprint(c) && c != '\\' && c != '"') {
42
0
      out += c;
43
0
    } else {
44
0
      out += "\\\\";
45
0
      switch (c) {
46
0
        case '"':
47
0
          out += "\\\"";
48
0
          break;
49
0
        case '\\':
50
0
          out += "\\\\";
51
0
          break;
52
0
        case '\t':
53
0
          out += 't';
54
0
          break;
55
0
        case '\r':
56
0
          out += 'r';
57
0
          break;
58
0
        case '\n':
59
0
          out += 'n';
60
0
          break;
61
0
        default:
62
0
          char const *const hexdig = "0123456789ABCDEF";
63
0
          out += 'x';
64
0
          out += hexdig[c >> 4];
65
0
          out += hexdig[c & 0xF];
66
0
      }
67
0
    }
68
0
  }
69
0
  out += getQuoteSign();
70
0
  return out;
71
0
}
72
73
0
std::string IRPrinter::quoteStr(llvh::StringRef name) {
74
0
  if (name.count(" ") || name.empty()) {
75
0
    return getQuoteSign() + name.str() + getQuoteSign();
76
0
  }
77
0
  return name.str();
78
0
}
79
80
0
void InstructionNamer::clear() {
81
0
  Counter = 0;
82
0
  InstrMap.clear();
83
0
}
84
0
unsigned InstructionNamer::getNumber(Value *T) {
85
0
  auto It = InstrMap.find(T);
86
0
  if (It != InstrMap.end()) {
87
0
    return It->second;
88
0
  }
89
0
  InstrMap[T] = Counter;
90
0
  return Counter++;
91
0
}
92
93
0
void IRPrinter::printTypeLabel(Type T) {
94
  // We don't print type annotations for unknown types.
95
0
  if (T.isAnyType())
96
0
    return;
97
0
  os << " : " << T;
98
0
}
99
100
0
void IRPrinter::printValueLabel(Instruction *I, Value *V, unsigned opIndex) {
101
0
  auto &ctx = I->getContext();
102
0
  if (isa<CallBuiltinInst>(I) && opIndex == CallInst::CalleeIdx) {
103
0
    os << "["
104
0
       << getBuiltinMethodName(cast<CallBuiltinInst>(I)->getBuiltinIndex())
105
0
       << "]";
106
#ifdef HERMES_RUN_WASM
107
  } else if (isa<CallIntrinsicInst>(I) && opIndex == 0) {
108
    os << "["
109
       << getWasmIntrinsicsName(
110
              cast<CallIntrinsicInst>(I)->getIntrinsicsIndex())
111
       << "]";
112
#endif
113
0
  } else if (isa<GetBuiltinClosureInst>(I) && opIndex == 0) {
114
0
    os << "["
115
0
       << getBuiltinMethodName(
116
0
              cast<GetBuiltinClosureInst>(I)->getBuiltinIndex())
117
0
       << "]";
118
0
  } else if (auto LBI = dyn_cast<LiteralBigInt>(V)) {
119
0
    os << LBI->getValue()->str();
120
0
  } else if (auto LS = dyn_cast<LiteralString>(V)) {
121
0
    os << escapeStr(ctx.toString(LS->getValue()));
122
0
  } else if (auto LB = dyn_cast<LiteralBool>(V)) {
123
0
    os << (LB->getValue() ? "true" : "false");
124
0
  } else if (auto LN = dyn_cast<LiteralNumber>(V)) {
125
0
    const auto Num = LN->getValue();
126
0
    if (Num == 0 && std::signbit(Num)) {
127
      // Ensure we output -0 correctly
128
0
      os << "-0";
129
0
    } else {
130
0
      char buf[NUMBER_TO_STRING_BUF_SIZE];
131
0
      numberToString(LN->getValue(), buf, sizeof(buf));
132
0
      os << buf;
133
0
    }
134
0
  } else if (isa<LiteralEmpty>(V)) {
135
0
    os << "empty";
136
0
  } else if (isa<LiteralNull>(V)) {
137
0
    os << "null";
138
0
  } else if (isa<LiteralUndefined>(V)) {
139
0
    os << "undefined";
140
0
  } else if (isa<GlobalObject>(V)) {
141
0
    os << "globalObject";
142
0
  } else if (isa<EmptySentinel>(V)) {
143
0
    os << "empty";
144
0
  } else if (isa<Instruction>(V)) {
145
0
    os << "%" << InstNamer.getNumber(V);
146
0
  } else if (isa<BasicBlock>(V)) {
147
0
    os << "%BB" << BBNamer.getNumber(V);
148
0
  } else if (auto L = dyn_cast<Label>(V)) {
149
0
    auto Name = L->get();
150
0
    os << "$" << quoteStr(ctx.toString(Name));
151
0
  } else if (auto P = dyn_cast<Parameter>(V)) {
152
0
    auto Name = P->getName();
153
0
    os << "%" << ctx.toString(Name);
154
0
  } else if (auto F = dyn_cast<Function>(V)) {
155
0
    os << "%";
156
0
    printFunctionName(F, PrintFunctionParams::No);
157
0
  } else if (auto S = dyn_cast<ScopeDesc>(V)) {
158
0
    os << "%";
159
0
    printScopeLabel(S);
160
0
  } else if (auto VR = dyn_cast<Variable>(V)) {
161
0
    os << "[";
162
0
    printVariableName(VR);
163
0
    if (I->getParent()->getParent() != VR->getParent()->getFunction()) {
164
0
      llvh::StringRef scopeName =
165
0
          VR->getParent()->getFunction()->getInternalNameStr();
166
0
      os << "@" << quoteStr(scopeName);
167
0
    }
168
0
    os << "]";
169
0
  } else {
170
0
    llvm_unreachable("Invalid value");
171
0
  }
172
173
0
  printTypeLabel(V->getType());
174
0
}
175
176
0
void IRPrinter::printFunctionHeader(Function *F) {
177
0
  std::string defKindStr = F->getDefinitionKindStr(false);
178
179
0
  os << defKindStr << " ";
180
0
  printFunctionName(F, PrintFunctionParams::Yes);
181
0
  printTypeLabel(F->getType());
182
0
}
183
184
0
void IRPrinter::printFunctionVariables(Function *F) {
185
0
  bool hasGlobals = false;
186
0
  if (F->isGlobalScope()) {
187
0
    auto &Ctx = F->getContext();
188
0
    bool first2 = true;
189
0
    for (auto *GP : F->getParent()->getGlobalProperties()) {
190
0
      if (!GP->isDeclared())
191
0
        continue;
192
0
      if (first2) {
193
0
        hasGlobals = true;
194
0
        os << "globals = [";
195
0
      } else {
196
0
        os << ", ";
197
0
      }
198
0
      os << Ctx.toString(GP->getName()->getValue());
199
0
      first2 = false;
200
0
    }
201
0
    if (!first2)
202
0
      os << "]";
203
0
  }
204
205
0
  bool printNewLine = hasGlobals;
206
0
  F->forEachScope([&](ScopeDesc *S) {
207
0
    if (printNewLine) {
208
0
      os << "\n";
209
0
    }
210
0
    printNewLine = true;
211
0
    printScopeLabel(S);
212
0
    os << " = [";
213
0
    bool first = true;
214
0
    for (auto V : S->getVariables()) {
215
0
      if (!first) {
216
0
        os << ", ";
217
0
      }
218
0
      printVariableName(V);
219
0
      printTypeLabel(V->getType());
220
0
      first = false;
221
0
    }
222
0
    os << "]";
223
0
  });
224
0
}
225
226
0
void IRPrinter::printInstructionDestination(Instruction *I) {
227
0
  os << "%" << InstNamer.getNumber(I);
228
0
}
229
230
0
void IRPrinter::printInstruction(Instruction *I) {
231
0
  printInstructionDestination(I);
232
0
  os << " = ";
233
0
  os << I->getName();
234
235
0
  bool first = true;
236
237
0
  if (auto *binop = dyn_cast<BinaryOperatorInst>(I)) {
238
0
    os << " '" << binop->getOperatorStr() << "'";
239
0
    first = false;
240
0
  } else if (auto *cmpbr = dyn_cast<CompareBranchInst>(I)) {
241
0
    os << " '" << cmpbr->getOperatorStr() << "'";
242
0
    first = false;
243
0
  } else if (auto *unop = dyn_cast<UnaryOperatorInst>(I)) {
244
0
    os << " '" << unop->getOperatorStr() << "'";
245
0
    first = false;
246
0
  }
247
248
0
  for (int i = 0, e = I->getNumOperands(); i < e; i++) {
249
0
    Value *O = I->getOperand(i);
250
0
    os << (first ? " " : ", ");
251
0
    printValueLabel(I, O, i);
252
0
    first = false;
253
0
  }
254
255
0
  auto codeGenOpts = I->getContext().getCodeGenerationSettings();
256
0
  const char *prefix = " // ";
257
258
0
  if (codeGenOpts.dumpTextifiedCallee) {
259
0
    auto &ctx = I->getParent()->getParent()->getContext();
260
0
    if (auto call = llvh::dyn_cast<CallInst>(I)) {
261
0
      if (LiteralString *textifiedCallee = call->getTextifiedCallee()) {
262
0
        os << prefix << "textified callee: "
263
0
           << escapeStr(ctx.toString(textifiedCallee->getValue()));
264
0
        prefix = ", ";
265
0
      }
266
0
    }
267
0
  }
268
0
  if (codeGenOpts.dumpSourceLevelScope) {
269
0
    if (auto *originalScope = I->getSourceLevelScope()) {
270
0
      os << prefix << "scope: ";
271
0
      printScopeLabel(originalScope);
272
0
      prefix = ", ";
273
0
    }
274
0
  }
275
276
  // Print the use list if there is any user for the instruction.
277
0
  if (!codeGenOpts.dumpUseList || I->getUsers().empty())
278
0
    return;
279
280
0
  llvh::DenseSet<Instruction *> Visited;
281
0
  os << prefix << "users:";
282
0
  for (auto &U : I->getUsers()) {
283
0
    auto *II = cast<Instruction>(U);
284
0
    assert(II && "Expecting user to be an Instruction");
285
0
    if (Visited.find(II) != Visited.end())
286
0
      continue;
287
0
    Visited.insert(II);
288
0
    os << " %" << InstNamer.getNumber(II);
289
0
  }
290
0
}
291
292
0
void IRPrinter::printSourceLocation(SMLoc loc) {
293
0
  SourceErrorManager::SourceCoords coords;
294
0
  if (!sm_.findBufferLineAndLoc(loc, coords))
295
0
    return;
296
297
0
  os << sm_.getSourceUrl(coords.bufId) << ":" << coords.line << ":"
298
0
     << coords.col;
299
0
}
300
301
0
void IRPrinter::printSourceLocation(SMRange rng) {
302
0
  SourceErrorManager::SourceCoords start, end;
303
0
  if (!sm_.findBufferLineAndLoc(rng.Start, start) ||
304
0
      !sm_.findBufferLineAndLoc(rng.End, end))
305
0
    return;
306
307
0
  os << "[" << sm_.getSourceUrl(start.bufId) << ":" << start.line << ":"
308
0
     << start.col << " ... " << sm_.getSourceUrl(end.bufId) << ":" << end.line
309
0
     << ":" << end.col << ")";
310
0
}
311
312
0
void IRPrinter::printScopeLabel(ScopeDesc *S) {
313
0
  os << "S{";
314
0
  printFunctionName(S->getFunction(), PrintFunctionParams::No);
315
0
  printScopeRange(S, S->getFunction()->getFunctionScopeDesc());
316
0
  os << "}";
317
0
}
318
319
0
void IRPrinter::printScope(ScopeDesc *S) {
320
0
  os << "#" << ScopeNamer.getNumber(S);
321
0
}
322
323
0
void IRPrinter::printScopeRange(ScopeDesc *Start, ScopeDesc *End) {
324
0
  if (Start != End) {
325
0
    printScopeRange(Start->getParent(), End);
326
0
    printScope(Start);
327
0
  }
328
0
}
329
330
0
void IRPrinter::printScopeChain(ScopeDesc *S) {
331
0
  if (S && S->getParent()) {
332
0
    printScope(S->getParent());
333
0
  }
334
0
  printScope(S);
335
0
}
336
337
void IRPrinter::printFunctionName(
338
    Function *F,
339
0
    PrintFunctionParams printFunctionParams) {
340
0
  auto &ctx = F->getContext();
341
0
  os << quoteStr(ctx.toString(F->getInternalName()));
342
0
  printScopeChain(F->getFunctionScopeDesc()->getParent());
343
0
  os << "(";
344
0
  if (printFunctionParams != PrintFunctionParams::No) {
345
0
    bool first = true;
346
0
    for (auto P : F->getParameters()) {
347
0
      if (!first) {
348
0
        os << ", ";
349
0
      }
350
0
      os << ctx.toString(P->getName());
351
0
      printTypeLabel(P->getType());
352
0
      first = false;
353
0
    }
354
0
  }
355
0
  os << ")";
356
0
  printScope(F->getFunctionScopeDesc());
357
0
}
358
359
0
void IRPrinter::printVariableName(Variable *V) {
360
0
  ScopeDesc *VS = V->getParent();
361
0
  auto &ctx = VS->getFunction()->getContext();
362
0
  os << ctx.toString(V->getName());
363
0
  printScope(VS);
364
0
}
365
366
0
void IRPrinter::visitModule(const Module &M) {
367
0
  ScopeNamer.clear();
368
0
  visitScope(*M.getInitialScope());
369
370
  // Use IRVisitor dispatch to visit each individual function.
371
0
  for (auto &F : M)
372
0
    visit(F);
373
0
}
374
375
0
void IRPrinter::visitScope(const ScopeDesc &S) {
376
0
  ScopeNamer.getNumber(const_cast<ScopeDesc *>(&S));
377
0
  for (ScopeDesc *inner : S.getInnerScopes()) {
378
0
    visitScope(*inner);
379
0
  }
380
0
}
381
382
0
void IRPrinter::visitFunction(const Function &F) {
383
0
  auto *UF = const_cast<Function *>(&F);
384
0
  os.indent(Indent);
385
0
  BBNamer.clear();
386
0
  InstNamer.clear();
387
  // Number all instructions sequentially.
388
0
  for (auto &BB : *UF)
389
0
    for (auto &I : BB) {
390
0
      InstNamer.getNumber(&I);
391
0
    }
392
393
0
  printFunctionHeader(UF);
394
0
  os << "\n";
395
0
  printFunctionVariables(UF);
396
0
  os << "\n";
397
398
0
  auto codeGenOpts = F.getContext().getCodeGenerationSettings();
399
0
  if (codeGenOpts.dumpSourceLocation) {
400
0
    os << "source location: ";
401
0
    printSourceLocation(F.getSourceRange());
402
0
    os << "\n";
403
0
  }
404
405
  // Use IRVisitor dispatch to visit the basic blocks.
406
0
  for (auto &BB : F) {
407
0
    visit(BB);
408
0
  }
409
410
0
  os.indent(Indent);
411
0
  os << "function_end" << "\n";
412
0
  os << "\n";
413
0
}
414
415
0
void IRPrinter::visitBasicBlock(const BasicBlock &BB) {
416
0
  auto *UBB = const_cast<BasicBlock *>(&BB);
417
0
  os.indent(Indent);
418
0
  os << "%BB" << BBNamer.getNumber(UBB) << ":\n";
419
0
  Indent += 2;
420
421
  // Use IRVisitor dispatch to visit the instructions.
422
0
  for (auto &I : BB) {
423
0
    visit(I);
424
0
  }
425
426
0
  Indent -= 2;
427
0
}
428
429
0
void IRPrinter::visitInstruction(const Instruction &I) {
430
0
  auto *UII = const_cast<Instruction *>(&I);
431
0
  auto codeGenOpts = I.getContext().getCodeGenerationSettings();
432
0
  if (codeGenOpts.dumpSourceLocation) {
433
0
    os << "; ";
434
0
    printSourceLocation(UII->getLocation());
435
0
    os << "\n";
436
0
  }
437
0
  os.indent(Indent);
438
0
  printInstruction(UII);
439
0
  os << "\n";
440
0
}
441
442
} // namespace hermes
443
444
namespace {
445
446
/// This class prints Functions into dotty graphs. This struct inherits the
447
/// IRVisitor and reimplement the visitFunction and visitBasicBlock function.
448
struct DottyPrinter : public IRVisitor<DottyPrinter, void> {
449
  llvh::raw_ostream &os;
450
  llvh::SmallVector<std::pair<std::string, std::string>, 4> Edges;
451
  IRPrinter Printer;
452
453
  explicit DottyPrinter(
454
      Context &ctx,
455
      llvh::raw_ostream &ost,
456
      llvh::StringRef Title)
457
0
      : os(ost), Printer(ctx, ost, /* escape output */ true) {
458
0
    os << " digraph g {\n graph [ rankdir = \"TD\" ];\n";
459
0
    os << "labelloc=\"t\"; ";
460
0
    os << " node [ fontsize = \"16\" shape = \"record\" ]; edge [ ];\n";
461
0
  }
462
463
0
  ~DottyPrinter() {
464
0
    os << "\n";
465
466
    // Print the edges between the blocks.
467
0
    for (auto T : Edges) {
468
0
      os << "" << T.first << " ->";
469
0
      os << "" << T.second << ";\n";
470
0
    }
471
0
    os << "\n}\n";
472
0
  }
473
474
  /// Convert pointers into unique textual ids.
475
0
  static std::string toString(BasicBlock *ptr) {
476
0
    auto Num = (size_t)ptr;
477
0
    return std::to_string(Num);
478
0
  }
479
480
  /// Reimplement the visitFunction in IRVisitor.
481
0
  void visitFunction(const Function &F) {
482
0
    os << "label=\"";
483
0
    Printer.printFunctionHeader(const_cast<Function *>(&F));
484
0
    os << "\";\n";
485
486
    // Pre-assign every instruction a number, by doing this we avoid
487
    // assigning non-consecutive numbers to consecutive instructions if we
488
    // are dumping user list. Its also not a bad practice to initialize the
489
    // InstNamer table before we dump all the instructions.
490
0
    for (auto &BB : F) {
491
0
      for (auto &II : BB) {
492
0
        (void)Printer.InstNamer.getNumber(const_cast<Instruction *>(&II));
493
0
      }
494
0
    }
495
496
    // Use IRVisitor dispatch to visit the basic blocks.
497
0
    for (auto &BB : F) {
498
0
      visit(BB);
499
0
    }
500
0
  }
501
502
  /// Reimplement the visitBasicBlock in the IRVisitor.
503
  /// Visit all of the basic blocks in the program and render them into dotty
504
  /// records. Save edges between basic blocks and print them when we finish
505
  /// visiting all of the blocks in the program.
506
0
  void visitBasicBlock(const BasicBlock &V) {
507
0
    auto *BB = const_cast<BasicBlock *>(&V);
508
0
    os << "\"" << toString(BB) << "\"";
509
0
    os << "[ label = \"{ ";
510
511
0
    os << " { <self> | <head> \\<\\<%BB" << Printer.BBNamer.getNumber(BB)
512
0
       << "\\>\\> | } ";
513
514
0
    int counter = 0;
515
516
    // For each instruction in the basic block:
517
0
    for (auto &I : *BB) {
518
0
      counter++;
519
0
      os << " | ";
520
521
      // Print the instruction.
522
0
      os << "<L" << counter << ">";
523
0
      Printer.printInstruction(&I);
524
0
    }
525
526
0
    for (auto I = succ_begin(BB), E = succ_end(BB); I != E; ++I) {
527
0
      auto From = toString(BB) + ":L" + std::to_string(counter);
528
0
      Edges.push_back({From, toString(*I) + (*I == BB ? ":self" : ":head")});
529
0
    }
530
531
    // Emit the record:
532
0
    os << "}\" shape = \"record\" ]; \n";
533
0
  }
534
};
535
536
} // anonymous namespace
537
538
0
void hermes::viewGraph(Function *F) {
539
0
#ifndef NDEBUG
540
0
  auto &Ctx = F->getContext();
541
0
  llvh::StringRef Name = Ctx.toString(F->getInternalName());
542
0
  int FD;
543
  // Windows can't always handle long paths, so limit the length of the name.
544
0
  std::string N = Name.str();
545
0
  N = N.substr(0, std::min<std::size_t>(N.size(), 140));
546
0
  std::string Filename = llvh::createGraphFilename(N, FD);
547
0
  {
548
0
    llvh::raw_fd_ostream O(FD, /*shouldClose=*/true);
549
550
0
    if (FD == -1) {
551
0
      llvh::errs() << "error opening file '" << Filename << "' for writing!\n";
552
0
      return;
553
0
    }
554
555
0
    DottyPrinter D(F->getContext(), O, Name);
556
0
    D.visitFunction(*F);
557
0
  }
558
559
0
  llvh::DisplayGraph(Filename);
560
0
  llvh::errs() << " done. \n";
561
0
#endif
562
0
}