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/Interpreter/RemoteJITUtils.h" |
14 | |
15 | #include "clang/Basic/Diagnostic.h" |
16 | #include "clang/Basic/Version.h" |
17 | #include "clang/Config/config.h" |
18 | #include "clang/Frontend/CompilerInstance.h" |
19 | #include "clang/Frontend/FrontendDiagnostic.h" |
20 | #include "clang/Interpreter/CodeCompletion.h" |
21 | #include "clang/Interpreter/Interpreter.h" |
22 | #include "clang/Lex/Preprocessor.h" |
23 | #include "clang/Sema/Sema.h" |
24 | |
25 | #include "llvm/ExecutionEngine/Orc/LLJIT.h" |
26 | #include "llvm/LineEditor/LineEditor.h" |
27 | #include "llvm/Support/CommandLine.h" |
28 | #include "llvm/Support/ManagedStatic.h" // llvm_shutdown |
29 | #include "llvm/Support/Signals.h" |
30 | #include "llvm/Support/TargetSelect.h" |
31 | #include "llvm/TargetParser/Host.h" |
32 | #include <optional> |
33 | |
34 | #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" |
35 | |
36 | // Disable LSan for this test. |
37 | // FIXME: Re-enable once we can assume GCC 13.2 or higher. |
38 | // https://llvm.org/github.com/llvm/llvm-project/issues/67586. |
39 | #if LLVM_ADDRESS_SANITIZER_BUILD || LLVM_HWADDRESS_SANITIZER_BUILD |
40 | #include <sanitizer/lsan_interface.h> |
41 | LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; } |
42 | #endif |
43 | |
44 | #define DEBUG_TYPE "clang-repl" |
45 | |
46 | static llvm::cl::opt<bool> CudaEnabled("cuda" , llvm::cl::Hidden); |
47 | static llvm::cl::opt<std::string> CudaPath("cuda-path" , llvm::cl::Hidden); |
48 | static llvm::cl::opt<std::string> OffloadArch("offload-arch" , llvm::cl::Hidden); |
49 | static llvm::cl::OptionCategory OOPCategory("Out-of-process Execution Options" ); |
50 | static llvm::cl::opt<std::string> SlabAllocateSizeString( |
51 | "slab-allocate" , |
52 | llvm::cl::desc("Allocate from a slab of the given size " |
53 | "(allowable suffixes: Kb, Mb, Gb. default = " |
54 | "Kb)" ), |
55 | llvm::cl::init(Val: "" ), llvm::cl::cat(OOPCategory)); |
56 | static llvm::cl::opt<std::string> |
57 | OOPExecutor("oop-executor" , |
58 | llvm::cl::desc("Launch an out-of-process executor to run code" ), |
59 | llvm::cl::init(Val: "" ), llvm::cl::ValueOptional, |
60 | llvm::cl::cat(OOPCategory)); |
61 | static llvm::cl::opt<std::string> OOPExecutorConnect( |
62 | "oop-executor-connect" , |
63 | llvm::cl::desc( |
64 | "Connect to an out-of-process executor through a TCP socket" ), |
65 | llvm::cl::value_desc("<hostname>:<port>" )); |
66 | static llvm::cl::opt<std::string> |
67 | OrcRuntimePath("orc-runtime" , llvm::cl::desc("Path to the ORC runtime" ), |
68 | llvm::cl::init(Val: "" ), llvm::cl::ValueOptional, |
69 | llvm::cl::cat(OOPCategory)); |
70 | static llvm::cl::opt<bool> UseSharedMemory( |
71 | "use-shared-memory" , |
72 | llvm::cl::desc("Use shared memory to transfer generated code and data" ), |
73 | llvm::cl::init(Val: false), llvm::cl::cat(OOPCategory)); |
74 | static llvm::cl::list<std::string> |
75 | ClangArgs("Xcc" , |
76 | llvm::cl::desc("Argument to pass to the CompilerInvocation" ), |
77 | llvm::cl::CommaSeparated); |
78 | static llvm::cl::opt<bool> OptHostSupportsJit("host-supports-jit" , |
79 | llvm::cl::Hidden); |
80 | static llvm::cl::list<std::string> OptInputs(llvm::cl::Positional, |
81 | llvm::cl::desc("[code to run]" )); |
82 | |
83 | static llvm::Error sanitizeOopArguments(const char *ArgV0) { |
84 | // Only one of -oop-executor and -oop-executor-connect can be used. |
85 | if (!!OOPExecutor.getNumOccurrences() && |
86 | !!OOPExecutorConnect.getNumOccurrences()) |
87 | return llvm::make_error<llvm::StringError>( |
88 | Args: "Only one of -" + OOPExecutor.ArgStr + " and -" + |
89 | OOPExecutorConnect.ArgStr + " can be specified" , |
90 | Args: llvm::inconvertibleErrorCode()); |
91 | |
92 | llvm::Triple SystemTriple(llvm::sys::getProcessTriple()); |
93 | // TODO: Remove once out-of-process execution support is implemented for |
94 | // non-Unix platforms. |
95 | if ((!SystemTriple.isOSBinFormatELF() && |
96 | !SystemTriple.isOSBinFormatMachO()) && |
97 | (OOPExecutor.getNumOccurrences() || |
98 | OOPExecutorConnect.getNumOccurrences())) |
99 | return llvm::make_error<llvm::StringError>( |
100 | Args: "Out-of-process execution is only supported on Unix platforms" , |
101 | Args: llvm::inconvertibleErrorCode()); |
102 | |
103 | // If -slab-allocate is passed, check that we're not trying to use it in |
104 | // -oop-executor or -oop-executor-connect mode. |
105 | // |
106 | // FIXME: Remove once we enable remote slab allocation. |
107 | if (SlabAllocateSizeString != "" ) { |
108 | if (OOPExecutor.getNumOccurrences() || |
109 | OOPExecutorConnect.getNumOccurrences()) |
110 | return llvm::make_error<llvm::StringError>( |
111 | Args: "-slab-allocate cannot be used with -oop-executor or " |
112 | "-oop-executor-connect" , |
113 | Args: llvm::inconvertibleErrorCode()); |
114 | } |
115 | |
116 | // Out-of-process executors require the ORC runtime. |
117 | if (OrcRuntimePath.empty() && (OOPExecutor.getNumOccurrences() || |
118 | OOPExecutorConnect.getNumOccurrences())) { |
119 | llvm::SmallString<256> BasePath(llvm::sys::fs::getMainExecutable( |
120 | argv0: ArgV0, MainExecAddr: reinterpret_cast<void *>(&sanitizeOopArguments))); |
121 | llvm::sys::path::remove_filename(path&: BasePath); // Remove clang-repl filename. |
122 | llvm::sys::path::remove_filename(path&: BasePath); // Remove ./bin directory. |
123 | llvm::sys::path::append(path&: BasePath, CLANG_INSTALL_LIBDIR_BASENAME, b: "clang" , |
124 | CLANG_VERSION_MAJOR_STRING); |
125 | if (SystemTriple.isOSBinFormatELF()) |
126 | OrcRuntimePath = |
127 | BasePath.str().str() + "/lib/x86_64-unknown-linux-gnu/liborc_rt.a" ; |
128 | else if (SystemTriple.isOSBinFormatMachO()) |
129 | OrcRuntimePath = BasePath.str().str() + "/lib/darwin/liborc_rt_osx.a" ; |
130 | else |
131 | return llvm::make_error<llvm::StringError>( |
132 | Args: "Out-of-process execution is not supported on non-unix platforms" , |
133 | Args: llvm::inconvertibleErrorCode()); |
134 | } |
135 | |
136 | // If -oop-executor was used but no value was specified then use a sensible |
137 | // default. |
138 | if (!!OOPExecutor.getNumOccurrences() && OOPExecutor.empty()) { |
139 | llvm::SmallString<256> OOPExecutorPath(llvm::sys::fs::getMainExecutable( |
140 | argv0: ArgV0, MainExecAddr: reinterpret_cast<void *>(&sanitizeOopArguments))); |
141 | llvm::sys::path::remove_filename(path&: OOPExecutorPath); |
142 | llvm::sys::path::append(path&: OOPExecutorPath, a: "llvm-jitlink-executor" ); |
143 | OOPExecutor = OOPExecutorPath.str().str(); |
144 | } |
145 | |
146 | return llvm::Error::success(); |
147 | } |
148 | |
149 | static void LLVMErrorHandler(void *UserData, const char *Message, |
150 | bool GenCrashDiag) { |
151 | auto &Diags = *static_cast<clang::DiagnosticsEngine *>(UserData); |
152 | |
153 | Diags.Report(DiagID: clang::diag::err_fe_error_backend) << Message; |
154 | |
155 | // Run the interrupt handlers to make sure any special cleanups get done, in |
156 | // particular that we remove files registered with RemoveFileOnSignal. |
157 | llvm::sys::RunInterruptHandlers(); |
158 | |
159 | // We cannot recover from llvm errors. When reporting a fatal error, exit |
160 | // with status 70 to generate crash diagnostics. For BSD systems this is |
161 | // defined as an internal software error. Otherwise, exit with status 1. |
162 | |
163 | exit(status: GenCrashDiag ? 70 : 1); |
164 | } |
165 | |
166 | // If we are running with -verify a reported has to be returned as unsuccess. |
167 | // This is relevant especially for the test suite. |
168 | static int checkDiagErrors(const clang::CompilerInstance *CI, bool HasError) { |
169 | unsigned Errs = CI->getDiagnostics().getClient()->getNumErrors(); |
170 | if (CI->getDiagnosticOpts().VerifyDiagnostics) { |
171 | // If there was an error that came from the verifier we must return 1 as |
172 | // an exit code for the process. This will make the test fail as expected. |
173 | clang::DiagnosticConsumer *Client = CI->getDiagnostics().getClient(); |
174 | Client->EndSourceFile(); |
175 | Errs = Client->getNumErrors(); |
176 | |
177 | // The interpreter expects BeginSourceFile/EndSourceFiles to be balanced. |
178 | Client->BeginSourceFile(LangOpts: CI->getLangOpts(), PP: &CI->getPreprocessor()); |
179 | } |
180 | return (Errs || HasError) ? EXIT_FAILURE : EXIT_SUCCESS; |
181 | } |
182 | |
183 | struct ReplListCompleter { |
184 | clang::IncrementalCompilerBuilder &CB; |
185 | clang::Interpreter &MainInterp; |
186 | ReplListCompleter(clang::IncrementalCompilerBuilder &CB, |
187 | clang::Interpreter &Interp) |
188 | : CB(CB), MainInterp(Interp){}; |
189 | |
190 | std::vector<llvm::LineEditor::Completion> operator()(llvm::StringRef Buffer, |
191 | size_t Pos) const; |
192 | std::vector<llvm::LineEditor::Completion> |
193 | operator()(llvm::StringRef Buffer, size_t Pos, llvm::Error &ErrRes) const; |
194 | }; |
195 | |
196 | std::vector<llvm::LineEditor::Completion> |
197 | ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos) const { |
198 | auto Err = llvm::Error::success(); |
199 | auto res = (*this)(Buffer, Pos, Err); |
200 | if (Err) |
201 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: " ); |
202 | return res; |
203 | } |
204 | |
205 | std::vector<llvm::LineEditor::Completion> |
206 | ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos, |
207 | llvm::Error &ErrRes) const { |
208 | std::vector<llvm::LineEditor::Completion> Comps; |
209 | std::vector<std::string> Results; |
210 | |
211 | auto CI = CB.CreateCpp(); |
212 | if (auto Err = CI.takeError()) { |
213 | ErrRes = std::move(Err); |
214 | return {}; |
215 | } |
216 | |
217 | size_t Lines = |
218 | std::count(first: Buffer.begin(), last: std::next(x: Buffer.begin(), n: Pos), value: '\n') + 1; |
219 | auto Interp = clang::Interpreter::create(CI: std::move(*CI)); |
220 | |
221 | if (auto Err = Interp.takeError()) { |
222 | // log the error and returns an empty vector; |
223 | ErrRes = std::move(Err); |
224 | |
225 | return {}; |
226 | } |
227 | auto *MainCI = (*Interp)->getCompilerInstance(); |
228 | auto CC = clang::ReplCodeCompleter(); |
229 | CC.codeComplete(InterpCI: MainCI, Content: Buffer, Line: Lines, Col: Pos + 1, |
230 | ParentCI: MainInterp.getCompilerInstance(), CCResults&: Results); |
231 | for (auto c : Results) { |
232 | if (c.find(str: CC.Prefix) == 0) |
233 | Comps.push_back( |
234 | x: llvm::LineEditor::Completion(c.substr(pos: CC.Prefix.size()), c)); |
235 | } |
236 | return Comps; |
237 | } |
238 | |
239 | llvm::ExitOnError ExitOnErr; |
240 | int main(int argc, const char **argv) { |
241 | llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
242 | |
243 | ExitOnErr.setBanner("clang-repl: " ); |
244 | llvm::cl::ParseCommandLineOptions(argc, argv); |
245 | |
246 | llvm::llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. |
247 | |
248 | std::vector<const char *> ClangArgv(ClangArgs.size()); |
249 | std::transform(first: ClangArgs.begin(), last: ClangArgs.end(), result: ClangArgv.begin(), |
250 | unary_op: [](const std::string &s) -> const char * { return s.data(); }); |
251 | // Initialize all targets (required for device offloading) |
252 | llvm::InitializeAllTargetInfos(); |
253 | llvm::InitializeAllTargets(); |
254 | llvm::InitializeAllTargetMCs(); |
255 | llvm::InitializeAllAsmPrinters(); |
256 | llvm::InitializeAllAsmParsers(); |
257 | |
258 | if (OptHostSupportsJit) { |
259 | auto J = llvm::orc::LLJITBuilder().create(); |
260 | if (J) |
261 | llvm::outs() << "true\n" ; |
262 | else { |
263 | llvm::consumeError(Err: J.takeError()); |
264 | llvm::outs() << "false\n" ; |
265 | } |
266 | return 0; |
267 | } |
268 | |
269 | clang::IncrementalCompilerBuilder CB; |
270 | CB.SetCompilerArgs(ClangArgv); |
271 | |
272 | std::unique_ptr<clang::CompilerInstance> DeviceCI; |
273 | if (CudaEnabled) { |
274 | if (!CudaPath.empty()) |
275 | CB.SetCudaSDK(CudaPath); |
276 | |
277 | if (OffloadArch.empty()) { |
278 | OffloadArch = "sm_35" ; |
279 | } |
280 | CB.SetOffloadArch(OffloadArch); |
281 | |
282 | DeviceCI = ExitOnErr(CB.CreateCudaDevice()); |
283 | } |
284 | |
285 | ExitOnErr(sanitizeOopArguments(ArgV0: argv[0])); |
286 | |
287 | std::unique_ptr<llvm::orc::ExecutorProcessControl> EPC; |
288 | if (OOPExecutor.getNumOccurrences()) { |
289 | // Launch an out-of-process executor locally in a child process. |
290 | EPC = ExitOnErr( |
291 | launchExecutor(ExecutablePath: OOPExecutor, UseSharedMemory, SlabAllocateSizeString)); |
292 | } else if (OOPExecutorConnect.getNumOccurrences()) { |
293 | EPC = ExitOnErr(connectTCPSocket(NetworkAddress: OOPExecutorConnect, UseSharedMemory, |
294 | SlabAllocateSizeString)); |
295 | } |
296 | |
297 | std::unique_ptr<llvm::orc::LLJITBuilder> JB; |
298 | if (EPC) { |
299 | CB.SetTargetTriple(EPC->getTargetTriple().getTriple()); |
300 | JB = ExitOnErr( |
301 | clang::Interpreter::createLLJITBuilder(EPC: std::move(EPC), OrcRuntimePath)); |
302 | } |
303 | |
304 | // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It |
305 | // can replace the boilerplate code for creation of the compiler instance. |
306 | std::unique_ptr<clang::CompilerInstance> CI; |
307 | if (CudaEnabled) { |
308 | CI = ExitOnErr(CB.CreateCudaHost()); |
309 | } else { |
310 | CI = ExitOnErr(CB.CreateCpp()); |
311 | } |
312 | |
313 | // Set an error handler, so that any LLVM backend diagnostics go through our |
314 | // error handler. |
315 | llvm::install_fatal_error_handler(handler: LLVMErrorHandler, |
316 | user_data: static_cast<void *>(&CI->getDiagnostics())); |
317 | |
318 | // Load any requested plugins. |
319 | CI->LoadRequestedPlugins(); |
320 | if (CudaEnabled) |
321 | DeviceCI->LoadRequestedPlugins(); |
322 | |
323 | std::unique_ptr<clang::Interpreter> Interp; |
324 | |
325 | if (CudaEnabled) { |
326 | Interp = ExitOnErr( |
327 | clang::Interpreter::createWithCUDA(CI: std::move(CI), DCI: std::move(DeviceCI))); |
328 | |
329 | if (CudaPath.empty()) { |
330 | ExitOnErr(Interp->LoadDynamicLibrary(name: "libcudart.so" )); |
331 | } else { |
332 | auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so" ; |
333 | ExitOnErr(Interp->LoadDynamicLibrary(name: CudaRuntimeLibPath.c_str())); |
334 | } |
335 | } else if (JB) { |
336 | Interp = |
337 | ExitOnErr(clang::Interpreter::create(CI: std::move(CI), JITBuilder: std::move(JB))); |
338 | } else |
339 | Interp = ExitOnErr(clang::Interpreter::create(CI: std::move(CI))); |
340 | |
341 | bool HasError = false; |
342 | |
343 | for (const std::string &input : OptInputs) { |
344 | if (auto Err = Interp->ParseAndExecute(Code: input)) { |
345 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: " ); |
346 | HasError = true; |
347 | } |
348 | } |
349 | |
350 | if (OptInputs.empty()) { |
351 | llvm::LineEditor LE("clang-repl" ); |
352 | std::string Input; |
353 | LE.setListCompleter(ReplListCompleter(CB, *Interp)); |
354 | while (std::optional<std::string> Line = LE.readLine()) { |
355 | llvm::StringRef L = *Line; |
356 | L = L.trim(); |
357 | if (L.ends_with(Suffix: "\\" )) { |
358 | Input += L.drop_back(N: 1); |
359 | // If it is a preprocessor directive, new lines matter. |
360 | if (L.starts_with(Prefix: '#')) |
361 | Input += "\n" ; |
362 | LE.setPrompt("clang-repl... " ); |
363 | continue; |
364 | } |
365 | |
366 | Input += L; |
367 | if (Input == R"(%quit)" ) { |
368 | break; |
369 | } |
370 | if (Input == R"(%undo)" ) { |
371 | if (auto Err = Interp->Undo()) |
372 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: " ); |
373 | } else if (Input.rfind(s: "%lib " , pos: 0) == 0) { |
374 | if (auto Err = Interp->LoadDynamicLibrary(name: Input.data() + 5)) |
375 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: " ); |
376 | } else if (auto Err = Interp->ParseAndExecute(Code: Input)) { |
377 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: " ); |
378 | } |
379 | |
380 | Input = "" ; |
381 | LE.setPrompt("clang-repl> " ); |
382 | } |
383 | } |
384 | |
385 | // Our error handler depends on the Diagnostics object, which we're |
386 | // potentially about to delete. Uninstall the handler now so that any |
387 | // later errors use the default handling behavior instead. |
388 | llvm::remove_fatal_error_handler(); |
389 | |
390 | return checkDiagErrors(CI: Interp->getCompilerInstance(), HasError); |
391 | } |
392 | |