1 //===--- tools/clang-repl/ClangRepl.cpp - clang-repl - the Clang REPL -----===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 //  This file implements a REPL tool on top of clang.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/Basic/Diagnostic.h"
14 #include "clang/Frontend/CompilerInstance.h"
15 #include "clang/Frontend/FrontendDiagnostic.h"
16 #include "clang/Interpreter/Interpreter.h"
17 
18 #include "llvm/ExecutionEngine/Orc/LLJIT.h"
19 #include "llvm/LineEditor/LineEditor.h"
20 #include "llvm/Support/CommandLine.h"
21 #include "llvm/Support/ManagedStatic.h" // llvm_shutdown
22 #include "llvm/Support/Signals.h"
23 #include "llvm/Support/TargetSelect.h"
24 #include <optional>
25 
26 static llvm::cl::opt<bool> CudaEnabled("cuda", llvm::cl::Hidden);
27 static llvm::cl::opt<std::string> CudaPath("cuda-path", llvm::cl::Hidden);
28 static llvm::cl::opt<std::string> OffloadArch("offload-arch", llvm::cl::Hidden);
29 
30 static llvm::cl::list<std::string>
31     ClangArgs("Xcc",
32               llvm::cl::desc("Argument to pass to the CompilerInvocation"),
33               llvm::cl::CommaSeparated);
34 static llvm::cl::opt<bool> OptHostSupportsJit("host-supports-jit",
35                                               llvm::cl::Hidden);
36 static llvm::cl::list<std::string> OptInputs(llvm::cl::Positional,
37                                              llvm::cl::desc("[code to run]"));
38 
39 static void LLVMErrorHandler(void *UserData, const char *Message,
40                              bool GenCrashDiag) {
41   auto &Diags = *static_cast<clang::DiagnosticsEngine *>(UserData);
42 
43   Diags.Report(clang::diag::err_fe_error_backend) << Message;
44 
45   // Run the interrupt handlers to make sure any special cleanups get done, in
46   // particular that we remove files registered with RemoveFileOnSignal.
47   llvm::sys::RunInterruptHandlers();
48 
49   // We cannot recover from llvm errors.  When reporting a fatal error, exit
50   // with status 70 to generate crash diagnostics.  For BSD systems this is
51   // defined as an internal software error. Otherwise, exit with status 1.
52 
53   exit(GenCrashDiag ? 70 : 1);
54 }
55 
56 // If we are running with -verify a reported has to be returned as unsuccess.
57 // This is relevant especially for the test suite.
58 static int checkDiagErrors(const clang::CompilerInstance *CI, bool HasError) {
59   unsigned Errs = CI->getDiagnostics().getClient()->getNumErrors();
60   if (CI->getDiagnosticOpts().VerifyDiagnostics) {
61     // If there was an error that came from the verifier we must return 1 as
62     // an exit code for the process. This will make the test fail as expected.
63     clang::DiagnosticConsumer *Client = CI->getDiagnostics().getClient();
64     Client->EndSourceFile();
65     Errs = Client->getNumErrors();
66 
67     // The interpreter expects BeginSourceFile/EndSourceFiles to be balanced.
68     Client->BeginSourceFile(CI->getLangOpts(), &CI->getPreprocessor());
69   }
70   return (Errs || HasError) ? EXIT_FAILURE : EXIT_SUCCESS;
71 }
72 
73 llvm::ExitOnError ExitOnErr;
74 int main(int argc, const char **argv) {
75   ExitOnErr.setBanner("clang-repl: ");
76   llvm::cl::ParseCommandLineOptions(argc, argv);
77 
78   llvm::llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
79 
80   std::vector<const char *> ClangArgv(ClangArgs.size());
81   std::transform(ClangArgs.begin(), ClangArgs.end(), ClangArgv.begin(),
82                  [](const std::string &s) -> const char * { return s.data(); });
83   // Initialize all targets (required for device offloading)
84   llvm::InitializeAllTargetInfos();
85   llvm::InitializeAllTargets();
86   llvm::InitializeAllTargetMCs();
87   llvm::InitializeAllAsmPrinters();
88 
89   if (OptHostSupportsJit) {
90     auto J = llvm::orc::LLJITBuilder().create();
91     if (J)
92       llvm::outs() << "true\n";
93     else {
94       llvm::consumeError(J.takeError());
95       llvm::outs() << "false\n";
96     }
97     return 0;
98   }
99 
100   clang::IncrementalCompilerBuilder CB;
101   CB.SetCompilerArgs(ClangArgv);
102 
103   std::unique_ptr<clang::CompilerInstance> DeviceCI;
104   if (CudaEnabled) {
105     if (!CudaPath.empty())
106       CB.SetCudaSDK(CudaPath);
107 
108     if (OffloadArch.empty()) {
109       OffloadArch = "sm_35";
110     }
111     CB.SetOffloadArch(OffloadArch);
112 
113     DeviceCI = ExitOnErr(CB.CreateCudaDevice());
114   }
115 
116   // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It
117   // can replace the boilerplate code for creation of the compiler instance.
118   std::unique_ptr<clang::CompilerInstance> CI;
119   if (CudaEnabled) {
120     CI = ExitOnErr(CB.CreateCudaHost());
121   } else {
122     CI = ExitOnErr(CB.CreateCpp());
123   }
124 
125   // Set an error handler, so that any LLVM backend diagnostics go through our
126   // error handler.
127   llvm::install_fatal_error_handler(LLVMErrorHandler,
128                                     static_cast<void *>(&CI->getDiagnostics()));
129 
130   // Load any requested plugins.
131   CI->LoadRequestedPlugins();
132   if (CudaEnabled)
133     DeviceCI->LoadRequestedPlugins();
134 
135   std::unique_ptr<clang::Interpreter> Interp;
136   if (CudaEnabled) {
137     Interp = ExitOnErr(
138         clang::Interpreter::createWithCUDA(std::move(CI), std::move(DeviceCI)));
139 
140     if (CudaPath.empty()) {
141       ExitOnErr(Interp->LoadDynamicLibrary("libcudart.so"));
142     } else {
143       auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so";
144       ExitOnErr(Interp->LoadDynamicLibrary(CudaRuntimeLibPath.c_str()));
145     }
146   } else
147     Interp = ExitOnErr(clang::Interpreter::create(std::move(CI)));
148 
149   for (const std::string &input : OptInputs) {
150     if (auto Err = Interp->ParseAndExecute(input))
151       llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
152   }
153 
154   bool HasError = false;
155 
156   if (OptInputs.empty()) {
157     llvm::LineEditor LE("clang-repl");
158     // FIXME: Add LE.setListCompleter
159     std::string Input;
160     while (std::optional<std::string> Line = LE.readLine()) {
161       llvm::StringRef L = *Line;
162       L = L.trim();
163       if (L.endswith("\\")) {
164         // FIXME: Support #ifdef X \ ...
165         Input += L.drop_back(1);
166         LE.setPrompt("clang-repl...   ");
167         continue;
168       }
169 
170       Input += L;
171 
172       if (Input == R"(%quit)") {
173         break;
174       } else if (Input == R"(%undo)") {
175         if (auto Err = Interp->Undo()) {
176           llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
177           HasError = true;
178         }
179       } else if (Input.rfind("%lib ", 0) == 0) {
180         if (auto Err = Interp->LoadDynamicLibrary(Input.data() + 5)) {
181           llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
182           HasError = true;
183         }
184       } else if (auto Err = Interp->ParseAndExecute(Input)) {
185         llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
186         HasError = true;
187       }
188 
189       Input = "";
190       LE.setPrompt("clang-repl> ");
191     }
192   }
193 
194   // Our error handler depends on the Diagnostics object, which we're
195   // potentially about to delete. Uninstall the handler now so that any
196   // later errors use the default handling behavior instead.
197   llvm::remove_fatal_error_handler();
198 
199   return checkDiagErrors(Interp->getCompilerInstance(), HasError);
200 }
201