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