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>
41LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; }
42#endif
43
44#define DEBUG_TYPE "clang-repl"
45
46static llvm::cl::opt<bool> CudaEnabled("cuda", llvm::cl::Hidden);
47static llvm::cl::opt<std::string> CudaPath("cuda-path", llvm::cl::Hidden);
48static llvm::cl::opt<std::string> OffloadArch("offload-arch", llvm::cl::Hidden);
49static llvm::cl::OptionCategory OOPCategory("Out-of-process Execution Options");
50static 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));
56static 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));
61static 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>"));
66static 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));
70static 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));
74static llvm::cl::list<std::string>
75 ClangArgs("Xcc",
76 llvm::cl::desc("Argument to pass to the CompilerInvocation"),
77 llvm::cl::CommaSeparated);
78static llvm::cl::opt<bool> OptHostSupportsJit("host-supports-jit",
79 llvm::cl::Hidden);
80static llvm::cl::list<std::string> OptInputs(llvm::cl::Positional,
81 llvm::cl::desc("[code to run]"));
82
83static 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
149static 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.
168static 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
183struct 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
196std::vector<llvm::LineEditor::Completion>
197ReplListCompleter::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
205std::vector<llvm::LineEditor::Completion>
206ReplListCompleter::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
239llvm::ExitOnError ExitOnErr;
240int 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