xref: /llvm-project/clang/tools/clang-repl/ClangRepl.cpp (revision 8358437bbb5b06d9aebc2940475a5a4d86c091c9)
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/CodeCompletion.h"
17 #include "clang/Interpreter/Interpreter.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "clang/Sema/Sema.h"
20 
21 #include "llvm/ExecutionEngine/Orc/LLJIT.h"
22 #include "llvm/LineEditor/LineEditor.h"
23 #include "llvm/Support/CommandLine.h"
24 #include "llvm/Support/ManagedStatic.h" // llvm_shutdown
25 #include "llvm/Support/Signals.h"
26 #include "llvm/Support/TargetSelect.h"
27 #include <optional>
28 
29 // Disable LSan for this test.
30 // FIXME: Re-enable once we can assume GCC 13.2 or higher.
31 // https://llvm.org/github.com/llvm/llvm-project/issues/67586.
32 #if LLVM_ADDRESS_SANITIZER_BUILD || LLVM_HWADDRESS_SANITIZER_BUILD
33 #include <sanitizer/lsan_interface.h>
34 LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; }
35 #endif
36 
37 static llvm::cl::opt<bool> CudaEnabled("cuda", llvm::cl::Hidden);
38 static llvm::cl::opt<std::string> CudaPath("cuda-path", llvm::cl::Hidden);
39 static llvm::cl::opt<std::string> OffloadArch("offload-arch", llvm::cl::Hidden);
40 
41 static llvm::cl::list<std::string>
42     ClangArgs("Xcc",
43               llvm::cl::desc("Argument to pass to the CompilerInvocation"),
44               llvm::cl::CommaSeparated);
45 static llvm::cl::opt<bool> OptHostSupportsJit("host-supports-jit",
46                                               llvm::cl::Hidden);
47 static llvm::cl::list<std::string> OptInputs(llvm::cl::Positional,
48                                              llvm::cl::desc("[code to run]"));
49 
50 static void LLVMErrorHandler(void *UserData, const char *Message,
51                              bool GenCrashDiag) {
52   auto &Diags = *static_cast<clang::DiagnosticsEngine *>(UserData);
53 
54   Diags.Report(clang::diag::err_fe_error_backend) << Message;
55 
56   // Run the interrupt handlers to make sure any special cleanups get done, in
57   // particular that we remove files registered with RemoveFileOnSignal.
58   llvm::sys::RunInterruptHandlers();
59 
60   // We cannot recover from llvm errors.  When reporting a fatal error, exit
61   // with status 70 to generate crash diagnostics.  For BSD systems this is
62   // defined as an internal software error. Otherwise, exit with status 1.
63 
64   exit(GenCrashDiag ? 70 : 1);
65 }
66 
67 // If we are running with -verify a reported has to be returned as unsuccess.
68 // This is relevant especially for the test suite.
69 static int checkDiagErrors(const clang::CompilerInstance *CI, bool HasError) {
70   unsigned Errs = CI->getDiagnostics().getClient()->getNumErrors();
71   if (CI->getDiagnosticOpts().VerifyDiagnostics) {
72     // If there was an error that came from the verifier we must return 1 as
73     // an exit code for the process. This will make the test fail as expected.
74     clang::DiagnosticConsumer *Client = CI->getDiagnostics().getClient();
75     Client->EndSourceFile();
76     Errs = Client->getNumErrors();
77 
78     // The interpreter expects BeginSourceFile/EndSourceFiles to be balanced.
79     Client->BeginSourceFile(CI->getLangOpts(), &CI->getPreprocessor());
80   }
81   return (Errs || HasError) ? EXIT_FAILURE : EXIT_SUCCESS;
82 }
83 
84 struct ReplListCompleter {
85   clang::IncrementalCompilerBuilder &CB;
86   clang::Interpreter &MainInterp;
87   ReplListCompleter(clang::IncrementalCompilerBuilder &CB,
88                     clang::Interpreter &Interp)
89       : CB(CB), MainInterp(Interp){};
90 
91   std::vector<llvm::LineEditor::Completion> operator()(llvm::StringRef Buffer,
92                                                        size_t Pos) const;
93   std::vector<llvm::LineEditor::Completion>
94   operator()(llvm::StringRef Buffer, size_t Pos, llvm::Error &ErrRes) const;
95 };
96 
97 std::vector<llvm::LineEditor::Completion>
98 ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos) const {
99   auto Err = llvm::Error::success();
100   auto res = (*this)(Buffer, Pos, Err);
101   if (Err)
102     llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
103   return res;
104 }
105 
106 std::vector<llvm::LineEditor::Completion>
107 ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos,
108                               llvm::Error &ErrRes) const {
109   std::vector<llvm::LineEditor::Completion> Comps;
110   std::vector<std::string> Results;
111 
112   auto CI = CB.CreateCpp();
113   if (auto Err = CI.takeError()) {
114     ErrRes = std::move(Err);
115     return {};
116   }
117 
118   size_t Lines =
119       std::count(Buffer.begin(), std::next(Buffer.begin(), Pos), '\n') + 1;
120   auto Interp = clang::Interpreter::create(std::move(*CI));
121 
122   if (auto Err = Interp.takeError()) {
123     // log the error and returns an empty vector;
124     ErrRes = std::move(Err);
125 
126     return {};
127   }
128   auto *MainCI = (*Interp)->getCompilerInstance();
129   auto CC = clang::ReplCodeCompleter();
130   CC.codeComplete(MainCI, Buffer, Lines, Pos + 1,
131                   MainInterp.getCompilerInstance(), Results);
132   for (auto c : Results) {
133     if (c.find(CC.Prefix) == 0)
134       Comps.push_back(
135           llvm::LineEditor::Completion(c.substr(CC.Prefix.size()), c));
136   }
137   return Comps;
138 }
139 
140 llvm::ExitOnError ExitOnErr;
141 int main(int argc, const char **argv) {
142   llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
143 
144   ExitOnErr.setBanner("clang-repl: ");
145   llvm::cl::ParseCommandLineOptions(argc, argv);
146 
147   llvm::llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
148 
149   std::vector<const char *> ClangArgv(ClangArgs.size());
150   std::transform(ClangArgs.begin(), ClangArgs.end(), ClangArgv.begin(),
151                  [](const std::string &s) -> const char * { return s.data(); });
152   // Initialize all targets (required for device offloading)
153   llvm::InitializeAllTargetInfos();
154   llvm::InitializeAllTargets();
155   llvm::InitializeAllTargetMCs();
156   llvm::InitializeAllAsmPrinters();
157   llvm::InitializeAllAsmParsers();
158 
159   if (OptHostSupportsJit) {
160     auto J = llvm::orc::LLJITBuilder().create();
161     if (J)
162       llvm::outs() << "true\n";
163     else {
164       llvm::consumeError(J.takeError());
165       llvm::outs() << "false\n";
166     }
167     return 0;
168   }
169 
170   clang::IncrementalCompilerBuilder CB;
171   CB.SetCompilerArgs(ClangArgv);
172 
173   std::unique_ptr<clang::CompilerInstance> DeviceCI;
174   if (CudaEnabled) {
175     if (!CudaPath.empty())
176       CB.SetCudaSDK(CudaPath);
177 
178     if (OffloadArch.empty()) {
179       OffloadArch = "sm_35";
180     }
181     CB.SetOffloadArch(OffloadArch);
182 
183     DeviceCI = ExitOnErr(CB.CreateCudaDevice());
184   }
185 
186   // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It
187   // can replace the boilerplate code for creation of the compiler instance.
188   std::unique_ptr<clang::CompilerInstance> CI;
189   if (CudaEnabled) {
190     CI = ExitOnErr(CB.CreateCudaHost());
191   } else {
192     CI = ExitOnErr(CB.CreateCpp());
193   }
194 
195   // Set an error handler, so that any LLVM backend diagnostics go through our
196   // error handler.
197   llvm::install_fatal_error_handler(LLVMErrorHandler,
198                                     static_cast<void *>(&CI->getDiagnostics()));
199 
200   // Load any requested plugins.
201   CI->LoadRequestedPlugins();
202   if (CudaEnabled)
203     DeviceCI->LoadRequestedPlugins();
204 
205   std::unique_ptr<clang::Interpreter> Interp;
206 
207   if (CudaEnabled) {
208     Interp = ExitOnErr(
209         clang::Interpreter::createWithCUDA(std::move(CI), std::move(DeviceCI)));
210 
211     if (CudaPath.empty()) {
212       ExitOnErr(Interp->LoadDynamicLibrary("libcudart.so"));
213     } else {
214       auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so";
215       ExitOnErr(Interp->LoadDynamicLibrary(CudaRuntimeLibPath.c_str()));
216     }
217   } else
218     Interp = ExitOnErr(clang::Interpreter::create(std::move(CI)));
219 
220   bool HasError = false;
221 
222   for (const std::string &input : OptInputs) {
223     if (auto Err = Interp->ParseAndExecute(input)) {
224       llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
225       HasError = true;
226     }
227   }
228 
229   if (OptInputs.empty()) {
230     llvm::LineEditor LE("clang-repl");
231     std::string Input;
232     LE.setListCompleter(ReplListCompleter(CB, *Interp));
233     while (std::optional<std::string> Line = LE.readLine()) {
234       llvm::StringRef L = *Line;
235       L = L.trim();
236       if (L.ends_with("\\")) {
237         Input += L.drop_back(1);
238         // If it is a preprocessor directive, new lines matter.
239         if (L.starts_with('#'))
240           Input += "\n";
241         LE.setPrompt("clang-repl...   ");
242         continue;
243       }
244 
245       Input += L;
246       if (Input == R"(%quit)") {
247         break;
248       }
249       if (Input == R"(%undo)") {
250         if (auto Err = Interp->Undo())
251           llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
252       } else if (Input.rfind("%lib ", 0) == 0) {
253         if (auto Err = Interp->LoadDynamicLibrary(Input.data() + 5))
254           llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
255       } else if (auto Err = Interp->ParseAndExecute(Input)) {
256         llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
257       }
258 
259       Input = "";
260       LE.setPrompt("clang-repl> ");
261     }
262   }
263 
264   // Our error handler depends on the Diagnostics object, which we're
265   // potentially about to delete. Uninstall the handler now so that any
266   // later errors use the default handling behavior instead.
267   llvm::remove_fatal_error_handler();
268 
269   return checkDiagErrors(Interp->getCompilerInstance(), HasError);
270 }
271