xref: /freebsd-src/contrib/llvm-project/llvm/lib/Support/GraphWriter.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
10b57cec5SDimitry Andric //===- GraphWriter.cpp - Implements GraphWriter support routines ----------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file implements misc. GraphWriter support routines.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
120b57cec5SDimitry Andric 
130b57cec5SDimitry Andric #include "llvm/Support/GraphWriter.h"
14fe6060f1SDimitry Andric 
15fe6060f1SDimitry Andric #include "DebugOptions.h"
16fe6060f1SDimitry Andric 
170b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h"
180b57cec5SDimitry Andric #include "llvm/ADT/SmallVector.h"
190b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
200b57cec5SDimitry Andric #include "llvm/Config/config.h"
210b57cec5SDimitry Andric #include "llvm/Support/Compiler.h"
220b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h"
230b57cec5SDimitry Andric #include "llvm/Support/ErrorOr.h"
240b57cec5SDimitry Andric #include "llvm/Support/FileSystem.h"
25349cc55cSDimitry Andric #include "llvm/Support/Path.h"
260b57cec5SDimitry Andric #include "llvm/Support/Program.h"
270b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
2804eeddc0SDimitry Andric 
2904eeddc0SDimitry Andric #ifdef __APPLE__
3004eeddc0SDimitry Andric #include "llvm/Support/CommandLine.h"
31*0fca6ea1SDimitry Andric #include "llvm/Support/ManagedStatic.h"
3204eeddc0SDimitry Andric #endif
3304eeddc0SDimitry Andric 
340b57cec5SDimitry Andric #include <string>
35349cc55cSDimitry Andric #include <system_error>
360b57cec5SDimitry Andric #include <vector>
370b57cec5SDimitry Andric 
380b57cec5SDimitry Andric using namespace llvm;
390b57cec5SDimitry Andric 
40fe6060f1SDimitry Andric #ifdef __APPLE__
41fe6060f1SDimitry Andric namespace {
42fe6060f1SDimitry Andric struct CreateViewBackground {
43fe6060f1SDimitry Andric   static void *call() {
44fe6060f1SDimitry Andric     return new cl::opt<bool>("view-background", cl::Hidden,
45fe6060f1SDimitry Andric                              cl::desc("Execute graph viewer in the background. "
46fe6060f1SDimitry Andric                                       "Creates tmp file litter."));
47fe6060f1SDimitry Andric   }
48fe6060f1SDimitry Andric };
49fe6060f1SDimitry Andric } // namespace
50fe6060f1SDimitry Andric static ManagedStatic<cl::opt<bool>, CreateViewBackground> ViewBackground;
51fe6060f1SDimitry Andric void llvm::initGraphWriterOptions() { *ViewBackground; }
52fe6060f1SDimitry Andric #else
53fe6060f1SDimitry Andric void llvm::initGraphWriterOptions() {}
54fe6060f1SDimitry Andric #endif
550b57cec5SDimitry Andric 
560b57cec5SDimitry Andric std::string llvm::DOT::EscapeString(const std::string &Label) {
570b57cec5SDimitry Andric   std::string Str(Label);
580b57cec5SDimitry Andric   for (unsigned i = 0; i != Str.length(); ++i)
590b57cec5SDimitry Andric   switch (Str[i]) {
600b57cec5SDimitry Andric     case '\n':
610b57cec5SDimitry Andric       Str.insert(Str.begin()+i, '\\');  // Escape character...
620b57cec5SDimitry Andric       ++i;
630b57cec5SDimitry Andric       Str[i] = 'n';
640b57cec5SDimitry Andric       break;
650b57cec5SDimitry Andric     case '\t':
660b57cec5SDimitry Andric       Str.insert(Str.begin()+i, ' ');  // Convert to two spaces
670b57cec5SDimitry Andric       ++i;
680b57cec5SDimitry Andric       Str[i] = ' ';
690b57cec5SDimitry Andric       break;
700b57cec5SDimitry Andric     case '\\':
710b57cec5SDimitry Andric       if (i+1 != Str.length())
720b57cec5SDimitry Andric         switch (Str[i+1]) {
730b57cec5SDimitry Andric           case 'l': continue; // don't disturb \l
740b57cec5SDimitry Andric           case '|': case '{': case '}':
750b57cec5SDimitry Andric             Str.erase(Str.begin()+i); continue;
760b57cec5SDimitry Andric           default: break;
770b57cec5SDimitry Andric         }
78bdd1243dSDimitry Andric       [[fallthrough]];
790b57cec5SDimitry Andric     case '{': case '}':
800b57cec5SDimitry Andric     case '<': case '>':
810b57cec5SDimitry Andric     case '|': case '"':
820b57cec5SDimitry Andric       Str.insert(Str.begin()+i, '\\');  // Escape character...
830b57cec5SDimitry Andric       ++i;  // don't infinite loop
840b57cec5SDimitry Andric       break;
850b57cec5SDimitry Andric   }
860b57cec5SDimitry Andric   return Str;
870b57cec5SDimitry Andric }
880b57cec5SDimitry Andric 
890b57cec5SDimitry Andric /// Get a color string for this node number. Simply round-robin selects
900b57cec5SDimitry Andric /// from a reasonable number of colors.
910b57cec5SDimitry Andric StringRef llvm::DOT::getColorString(unsigned ColorNumber) {
920b57cec5SDimitry Andric   static const int NumColors = 20;
930b57cec5SDimitry Andric   static const char* Colors[NumColors] = {
940b57cec5SDimitry Andric     "aaaaaa", "aa0000", "00aa00", "aa5500", "0055ff", "aa00aa", "00aaaa",
950b57cec5SDimitry Andric     "555555", "ff5555", "55ff55", "ffff55", "5555ff", "ff55ff", "55ffff",
960b57cec5SDimitry Andric     "ffaaaa", "aaffaa", "ffffaa", "aaaaff", "ffaaff", "aaffff"};
970b57cec5SDimitry Andric   return Colors[ColorNumber % NumColors];
980b57cec5SDimitry Andric }
990b57cec5SDimitry Andric 
1005ffd83dbSDimitry Andric static std::string replaceIllegalFilenameChars(std::string Filename,
1015ffd83dbSDimitry Andric                                                const char ReplacementChar) {
102349cc55cSDimitry Andric   std::string IllegalChars =
103349cc55cSDimitry Andric       is_style_windows(sys::path::Style::native) ? "\\/:?\"<>|" : "/";
1045ffd83dbSDimitry Andric 
1055ffd83dbSDimitry Andric   for (char IllegalChar : IllegalChars) {
1065ffd83dbSDimitry Andric     std::replace(Filename.begin(), Filename.end(), IllegalChar,
1075ffd83dbSDimitry Andric                  ReplacementChar);
1085ffd83dbSDimitry Andric   }
1095ffd83dbSDimitry Andric 
1105ffd83dbSDimitry Andric   return Filename;
1115ffd83dbSDimitry Andric }
1125ffd83dbSDimitry Andric 
1130b57cec5SDimitry Andric std::string llvm::createGraphFilename(const Twine &Name, int &FD) {
1140b57cec5SDimitry Andric   FD = -1;
1150b57cec5SDimitry Andric   SmallString<128> Filename;
1165ffd83dbSDimitry Andric 
1175ffd83dbSDimitry Andric   // Windows can't always handle long paths, so limit the length of the name.
1185ffd83dbSDimitry Andric   std::string N = Name.str();
119*0fca6ea1SDimitry Andric   if (N.size() > 140)
120*0fca6ea1SDimitry Andric     N.resize(140);
1215ffd83dbSDimitry Andric 
1225ffd83dbSDimitry Andric   // Replace illegal characters in graph Filename with '_' if needed
1235ffd83dbSDimitry Andric   std::string CleansedName = replaceIllegalFilenameChars(N, '_');
1245ffd83dbSDimitry Andric 
1255ffd83dbSDimitry Andric   std::error_code EC =
1265ffd83dbSDimitry Andric       sys::fs::createTemporaryFile(CleansedName, "dot", FD, Filename);
1270b57cec5SDimitry Andric   if (EC) {
1280b57cec5SDimitry Andric     errs() << "Error: " << EC.message() << "\n";
1290b57cec5SDimitry Andric     return "";
1300b57cec5SDimitry Andric   }
1310b57cec5SDimitry Andric 
1320b57cec5SDimitry Andric   errs() << "Writing '" << Filename << "'... ";
1337a6dacacSDimitry Andric   return std::string(Filename);
1340b57cec5SDimitry Andric }
1350b57cec5SDimitry Andric 
1360b57cec5SDimitry Andric // Execute the graph viewer. Return true if there were errors.
1370b57cec5SDimitry Andric static bool ExecGraphViewer(StringRef ExecPath, std::vector<StringRef> &args,
1380b57cec5SDimitry Andric                             StringRef Filename, bool wait,
1390b57cec5SDimitry Andric                             std::string &ErrMsg) {
1400b57cec5SDimitry Andric   if (wait) {
141bdd1243dSDimitry Andric     if (sys::ExecuteAndWait(ExecPath, args, std::nullopt, {}, 0, 0, &ErrMsg)) {
1420b57cec5SDimitry Andric       errs() << "Error: " << ErrMsg << "\n";
1430b57cec5SDimitry Andric       return true;
1440b57cec5SDimitry Andric     }
1450b57cec5SDimitry Andric     sys::fs::remove(Filename);
1460b57cec5SDimitry Andric     errs() << " done. \n";
1470b57cec5SDimitry Andric   } else {
148bdd1243dSDimitry Andric     sys::ExecuteNoWait(ExecPath, args, std::nullopt, {}, 0, &ErrMsg);
1490b57cec5SDimitry Andric     errs() << "Remember to erase graph file: " << Filename << "\n";
1500b57cec5SDimitry Andric   }
1510b57cec5SDimitry Andric   return false;
1520b57cec5SDimitry Andric }
1530b57cec5SDimitry Andric 
1540b57cec5SDimitry Andric namespace {
1550b57cec5SDimitry Andric 
1560b57cec5SDimitry Andric struct GraphSession {
1570b57cec5SDimitry Andric   std::string LogBuffer;
1580b57cec5SDimitry Andric 
1590b57cec5SDimitry Andric   bool TryFindProgram(StringRef Names, std::string &ProgramPath) {
1600b57cec5SDimitry Andric     raw_string_ostream Log(LogBuffer);
1610b57cec5SDimitry Andric     SmallVector<StringRef, 8> parts;
1620b57cec5SDimitry Andric     Names.split(parts, '|');
1630b57cec5SDimitry Andric     for (auto Name : parts) {
1640b57cec5SDimitry Andric       if (ErrorOr<std::string> P = sys::findProgramByName(Name)) {
1650b57cec5SDimitry Andric         ProgramPath = *P;
1660b57cec5SDimitry Andric         return true;
1670b57cec5SDimitry Andric       }
1680b57cec5SDimitry Andric       Log << "  Tried '" << Name << "'\n";
1690b57cec5SDimitry Andric     }
1700b57cec5SDimitry Andric     return false;
1710b57cec5SDimitry Andric   }
1720b57cec5SDimitry Andric };
1730b57cec5SDimitry Andric 
1740b57cec5SDimitry Andric } // end anonymous namespace
1750b57cec5SDimitry Andric 
1760b57cec5SDimitry Andric static const char *getProgramName(GraphProgram::Name program) {
1770b57cec5SDimitry Andric   switch (program) {
1780b57cec5SDimitry Andric   case GraphProgram::DOT:
1790b57cec5SDimitry Andric     return "dot";
1800b57cec5SDimitry Andric   case GraphProgram::FDP:
1810b57cec5SDimitry Andric     return "fdp";
1820b57cec5SDimitry Andric   case GraphProgram::NEATO:
1830b57cec5SDimitry Andric     return "neato";
1840b57cec5SDimitry Andric   case GraphProgram::TWOPI:
1850b57cec5SDimitry Andric     return "twopi";
1860b57cec5SDimitry Andric   case GraphProgram::CIRCO:
1870b57cec5SDimitry Andric     return "circo";
1880b57cec5SDimitry Andric   }
1890b57cec5SDimitry Andric   llvm_unreachable("bad kind");
1900b57cec5SDimitry Andric }
1910b57cec5SDimitry Andric 
1920b57cec5SDimitry Andric bool llvm::DisplayGraph(StringRef FilenameRef, bool wait,
1930b57cec5SDimitry Andric                         GraphProgram::Name program) {
1945ffd83dbSDimitry Andric   std::string Filename = std::string(FilenameRef);
1950b57cec5SDimitry Andric   std::string ErrMsg;
1960b57cec5SDimitry Andric   std::string ViewerPath;
1970b57cec5SDimitry Andric   GraphSession S;
1980b57cec5SDimitry Andric 
1990b57cec5SDimitry Andric #ifdef __APPLE__
200fe6060f1SDimitry Andric   wait &= !*ViewBackground;
2010b57cec5SDimitry Andric   if (S.TryFindProgram("open", ViewerPath)) {
2020b57cec5SDimitry Andric     std::vector<StringRef> args;
2030b57cec5SDimitry Andric     args.push_back(ViewerPath);
2040b57cec5SDimitry Andric     if (wait)
2050b57cec5SDimitry Andric       args.push_back("-W");
2060b57cec5SDimitry Andric     args.push_back(Filename);
2070b57cec5SDimitry Andric     errs() << "Trying 'open' program... ";
2080b57cec5SDimitry Andric     if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
2090b57cec5SDimitry Andric       return false;
2100b57cec5SDimitry Andric   }
2110b57cec5SDimitry Andric #endif
2120b57cec5SDimitry Andric   if (S.TryFindProgram("xdg-open", ViewerPath)) {
2130b57cec5SDimitry Andric     std::vector<StringRef> args;
2140b57cec5SDimitry Andric     args.push_back(ViewerPath);
2150b57cec5SDimitry Andric     args.push_back(Filename);
2160b57cec5SDimitry Andric     errs() << "Trying 'xdg-open' program... ";
2170b57cec5SDimitry Andric     if (!ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg))
2180b57cec5SDimitry Andric       return false;
2190b57cec5SDimitry Andric   }
2200b57cec5SDimitry Andric 
2210b57cec5SDimitry Andric   // Graphviz
2220b57cec5SDimitry Andric   if (S.TryFindProgram("Graphviz", ViewerPath)) {
2230b57cec5SDimitry Andric     std::vector<StringRef> args;
2240b57cec5SDimitry Andric     args.push_back(ViewerPath);
2250b57cec5SDimitry Andric     args.push_back(Filename);
2260b57cec5SDimitry Andric 
2270b57cec5SDimitry Andric     errs() << "Running 'Graphviz' program... ";
2280b57cec5SDimitry Andric     return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
2290b57cec5SDimitry Andric   }
2300b57cec5SDimitry Andric 
2310b57cec5SDimitry Andric   // xdot
2320b57cec5SDimitry Andric   if (S.TryFindProgram("xdot|xdot.py", ViewerPath)) {
2330b57cec5SDimitry Andric     std::vector<StringRef> args;
2340b57cec5SDimitry Andric     args.push_back(ViewerPath);
2350b57cec5SDimitry Andric     args.push_back(Filename);
2360b57cec5SDimitry Andric 
2370b57cec5SDimitry Andric     args.push_back("-f");
2380b57cec5SDimitry Andric     args.push_back(getProgramName(program));
2390b57cec5SDimitry Andric 
2400b57cec5SDimitry Andric     errs() << "Running 'xdot.py' program... ";
2410b57cec5SDimitry Andric     return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
2420b57cec5SDimitry Andric   }
2430b57cec5SDimitry Andric 
2440b57cec5SDimitry Andric   enum ViewerKind {
2450b57cec5SDimitry Andric     VK_None,
2460b57cec5SDimitry Andric     VK_OSXOpen,
2470b57cec5SDimitry Andric     VK_XDGOpen,
2480b57cec5SDimitry Andric     VK_Ghostview,
2490b57cec5SDimitry Andric     VK_CmdStart
2500b57cec5SDimitry Andric   };
2510b57cec5SDimitry Andric   ViewerKind Viewer = VK_None;
2520b57cec5SDimitry Andric #ifdef __APPLE__
2530b57cec5SDimitry Andric   if (!Viewer && S.TryFindProgram("open", ViewerPath))
2540b57cec5SDimitry Andric     Viewer = VK_OSXOpen;
2550b57cec5SDimitry Andric #endif
2560b57cec5SDimitry Andric   if (!Viewer && S.TryFindProgram("gv", ViewerPath))
2570b57cec5SDimitry Andric     Viewer = VK_Ghostview;
2580b57cec5SDimitry Andric   if (!Viewer && S.TryFindProgram("xdg-open", ViewerPath))
2590b57cec5SDimitry Andric     Viewer = VK_XDGOpen;
2600b57cec5SDimitry Andric #ifdef _WIN32
2610b57cec5SDimitry Andric   if (!Viewer && S.TryFindProgram("cmd", ViewerPath)) {
2620b57cec5SDimitry Andric     Viewer = VK_CmdStart;
2630b57cec5SDimitry Andric   }
2640b57cec5SDimitry Andric #endif
2650b57cec5SDimitry Andric 
2660b57cec5SDimitry Andric   // PostScript or PDF graph generator + PostScript/PDF viewer
2670b57cec5SDimitry Andric   std::string GeneratorPath;
2680b57cec5SDimitry Andric   if (Viewer &&
2690b57cec5SDimitry Andric       (S.TryFindProgram(getProgramName(program), GeneratorPath) ||
2700b57cec5SDimitry Andric        S.TryFindProgram("dot|fdp|neato|twopi|circo", GeneratorPath))) {
2710b57cec5SDimitry Andric     std::string OutputFilename =
2720b57cec5SDimitry Andric         Filename + (Viewer == VK_CmdStart ? ".pdf" : ".ps");
2730b57cec5SDimitry Andric 
2740b57cec5SDimitry Andric     std::vector<StringRef> args;
2750b57cec5SDimitry Andric     args.push_back(GeneratorPath);
2760b57cec5SDimitry Andric     if (Viewer == VK_CmdStart)
2770b57cec5SDimitry Andric       args.push_back("-Tpdf");
2780b57cec5SDimitry Andric     else
2790b57cec5SDimitry Andric       args.push_back("-Tps");
2800b57cec5SDimitry Andric     args.push_back("-Nfontname=Courier");
2810b57cec5SDimitry Andric     args.push_back("-Gsize=7.5,10");
2820b57cec5SDimitry Andric     args.push_back(Filename);
2830b57cec5SDimitry Andric     args.push_back("-o");
2840b57cec5SDimitry Andric     args.push_back(OutputFilename);
2850b57cec5SDimitry Andric 
2860b57cec5SDimitry Andric     errs() << "Running '" << GeneratorPath << "' program... ";
2870b57cec5SDimitry Andric 
2880b57cec5SDimitry Andric     if (ExecGraphViewer(GeneratorPath, args, Filename, true, ErrMsg))
2890b57cec5SDimitry Andric       return true;
2900b57cec5SDimitry Andric 
2910b57cec5SDimitry Andric     // The lifetime of StartArg must include the call of ExecGraphViewer
2920b57cec5SDimitry Andric     // because the args are passed as vector of char*.
2930b57cec5SDimitry Andric     std::string StartArg;
2940b57cec5SDimitry Andric 
2950b57cec5SDimitry Andric     args.clear();
2960b57cec5SDimitry Andric     args.push_back(ViewerPath);
2970b57cec5SDimitry Andric     switch (Viewer) {
2980b57cec5SDimitry Andric     case VK_OSXOpen:
2990b57cec5SDimitry Andric       args.push_back("-W");
3000b57cec5SDimitry Andric       args.push_back(OutputFilename);
3010b57cec5SDimitry Andric       break;
3020b57cec5SDimitry Andric     case VK_XDGOpen:
3030b57cec5SDimitry Andric       wait = false;
3040b57cec5SDimitry Andric       args.push_back(OutputFilename);
3050b57cec5SDimitry Andric       break;
3060b57cec5SDimitry Andric     case VK_Ghostview:
3070b57cec5SDimitry Andric       args.push_back("--spartan");
3080b57cec5SDimitry Andric       args.push_back(OutputFilename);
3090b57cec5SDimitry Andric       break;
3100b57cec5SDimitry Andric     case VK_CmdStart:
3110b57cec5SDimitry Andric       args.push_back("/S");
3120b57cec5SDimitry Andric       args.push_back("/C");
3130b57cec5SDimitry Andric       StartArg =
3140b57cec5SDimitry Andric           (StringRef("start ") + (wait ? "/WAIT " : "") + OutputFilename).str();
3150b57cec5SDimitry Andric       args.push_back(StartArg);
3160b57cec5SDimitry Andric       break;
3170b57cec5SDimitry Andric     case VK_None:
3180b57cec5SDimitry Andric       llvm_unreachable("Invalid viewer");
3190b57cec5SDimitry Andric     }
3200b57cec5SDimitry Andric 
3210b57cec5SDimitry Andric     ErrMsg.clear();
3220b57cec5SDimitry Andric     return ExecGraphViewer(ViewerPath, args, OutputFilename, wait, ErrMsg);
3230b57cec5SDimitry Andric   }
3240b57cec5SDimitry Andric 
3250b57cec5SDimitry Andric   // dotty
3260b57cec5SDimitry Andric   if (S.TryFindProgram("dotty", ViewerPath)) {
3270b57cec5SDimitry Andric     std::vector<StringRef> args;
3280b57cec5SDimitry Andric     args.push_back(ViewerPath);
3290b57cec5SDimitry Andric     args.push_back(Filename);
3300b57cec5SDimitry Andric 
3310b57cec5SDimitry Andric // Dotty spawns another app and doesn't wait until it returns
3320b57cec5SDimitry Andric #ifdef _WIN32
3330b57cec5SDimitry Andric     wait = false;
3340b57cec5SDimitry Andric #endif
3350b57cec5SDimitry Andric     errs() << "Running 'dotty' program... ";
3360b57cec5SDimitry Andric     return ExecGraphViewer(ViewerPath, args, Filename, wait, ErrMsg);
3370b57cec5SDimitry Andric   }
3380b57cec5SDimitry Andric 
3390b57cec5SDimitry Andric   errs() << "Error: Couldn't find a usable graph viewer program:\n";
3400b57cec5SDimitry Andric   errs() << S.LogBuffer << "\n";
3410b57cec5SDimitry Andric   return true;
3420b57cec5SDimitry Andric }
343