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>
34LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; }
35#endif
36
37static llvm::cl::opt<bool> CudaEnabled("cuda", llvm::cl::Hidden);
38static llvm::cl::opt<std::string> CudaPath("cuda-path", llvm::cl::Hidden);
39static llvm::cl::opt<std::string> OffloadArch("offload-arch", llvm::cl::Hidden);
40
41static llvm::cl::list<std::string>
42 ClangArgs("Xcc",
43 llvm::cl::desc("Argument to pass to the CompilerInvocation"),
44 llvm::cl::CommaSeparated);
45static llvm::cl::opt<bool> OptHostSupportsJit("host-supports-jit",
46 llvm::cl::Hidden);
47static llvm::cl::list<std::string> OptInputs(llvm::cl::Positional,
48 llvm::cl::desc("[code to run]"));
49
50static void LLVMErrorHandler(void *UserData, const char *Message,
51 bool GenCrashDiag) {
52 auto &Diags = *static_cast<clang::DiagnosticsEngine *>(UserData);
53
54 Diags.Report(DiagID: 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(status: 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.
69static 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(LangOpts: CI->getLangOpts(), PP: &CI->getPreprocessor());
80 }
81 return (Errs || HasError) ? EXIT_FAILURE : EXIT_SUCCESS;
82}
83
84struct 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
97std::vector<llvm::LineEditor::Completion>
98ReplListCompleter::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(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: ");
103 return res;
104}
105
106std::vector<llvm::LineEditor::Completion>
107ReplListCompleter::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(first: Buffer.begin(), last: std::next(x: Buffer.begin(), n: Pos), value: '\n') + 1;
120 auto Interp = clang::Interpreter::create(CI: 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(InterpCI: MainCI, Content: Buffer, Line: Lines, Col: Pos + 1,
131 ParentCI: MainInterp.getCompilerInstance(), CCResults&: Results);
132 for (auto c : Results) {
133 if (c.find(str: CC.Prefix) == 0)
134 Comps.push_back(
135 x: llvm::LineEditor::Completion(c.substr(pos: CC.Prefix.size()), c));
136 }
137 return Comps;
138}
139
140llvm::ExitOnError ExitOnErr;
141int main(int argc, const char **argv) {
142 ExitOnErr.setBanner("clang-repl: ");
143 llvm::cl::ParseCommandLineOptions(argc, argv);
144
145 llvm::llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
146
147 std::vector<const char *> ClangArgv(ClangArgs.size());
148 std::transform(first: ClangArgs.begin(), last: ClangArgs.end(), result: ClangArgv.begin(),
149 unary_op: [](const std::string &s) -> const char * { return s.data(); });
150 // Initialize all targets (required for device offloading)
151 llvm::InitializeAllTargetInfos();
152 llvm::InitializeAllTargets();
153 llvm::InitializeAllTargetMCs();
154 llvm::InitializeAllAsmPrinters();
155 llvm::InitializeAllAsmParsers();
156
157 if (OptHostSupportsJit) {
158 auto J = llvm::orc::LLJITBuilder().create();
159 if (J)
160 llvm::outs() << "true\n";
161 else {
162 llvm::consumeError(Err: J.takeError());
163 llvm::outs() << "false\n";
164 }
165 return 0;
166 }
167
168 clang::IncrementalCompilerBuilder CB;
169 CB.SetCompilerArgs(ClangArgv);
170
171 std::unique_ptr<clang::CompilerInstance> DeviceCI;
172 if (CudaEnabled) {
173 if (!CudaPath.empty())
174 CB.SetCudaSDK(CudaPath);
175
176 if (OffloadArch.empty()) {
177 OffloadArch = "sm_35";
178 }
179 CB.SetOffloadArch(OffloadArch);
180
181 DeviceCI = ExitOnErr(CB.CreateCudaDevice());
182 }
183
184 // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It
185 // can replace the boilerplate code for creation of the compiler instance.
186 std::unique_ptr<clang::CompilerInstance> CI;
187 if (CudaEnabled) {
188 CI = ExitOnErr(CB.CreateCudaHost());
189 } else {
190 CI = ExitOnErr(CB.CreateCpp());
191 }
192
193 // Set an error handler, so that any LLVM backend diagnostics go through our
194 // error handler.
195 llvm::install_fatal_error_handler(handler: LLVMErrorHandler,
196 user_data: static_cast<void *>(&CI->getDiagnostics()));
197
198 // Load any requested plugins.
199 CI->LoadRequestedPlugins();
200 if (CudaEnabled)
201 DeviceCI->LoadRequestedPlugins();
202
203 std::unique_ptr<clang::Interpreter> Interp;
204
205 if (CudaEnabled) {
206 Interp = ExitOnErr(
207 clang::Interpreter::createWithCUDA(CI: std::move(CI), DCI: std::move(DeviceCI)));
208
209 if (CudaPath.empty()) {
210 ExitOnErr(Interp->LoadDynamicLibrary(name: "libcudart.so"));
211 } else {
212 auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so";
213 ExitOnErr(Interp->LoadDynamicLibrary(name: CudaRuntimeLibPath.c_str()));
214 }
215 } else
216 Interp = ExitOnErr(clang::Interpreter::create(CI: std::move(CI)));
217
218 bool HasError = false;
219
220 for (const std::string &input : OptInputs) {
221 if (auto Err = Interp->ParseAndExecute(Code: input)) {
222 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: ");
223 HasError = true;
224 }
225 }
226
227 if (OptInputs.empty()) {
228 llvm::LineEditor LE("clang-repl");
229 std::string Input;
230 LE.setListCompleter(ReplListCompleter(CB, *Interp));
231 while (std::optional<std::string> Line = LE.readLine()) {
232 llvm::StringRef L = *Line;
233 L = L.trim();
234 if (L.ends_with(Suffix: "\\")) {
235 // FIXME: Support #ifdef X \ ...
236 Input += L.drop_back(N: 1);
237 LE.setPrompt("clang-repl... ");
238 continue;
239 }
240
241 Input += L;
242 if (Input == R"(%quit)") {
243 break;
244 }
245 if (Input == R"(%undo)") {
246 if (auto Err = Interp->Undo())
247 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: ");
248 } else if (Input.rfind(s: "%lib ", pos: 0) == 0) {
249 if (auto Err = Interp->LoadDynamicLibrary(name: Input.data() + 5))
250 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: ");
251 } else if (auto Err = Interp->ParseAndExecute(Code: Input)) {
252 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), ErrorBanner: "error: ");
253 }
254
255 Input = "";
256 LE.setPrompt("clang-repl> ");
257 }
258 }
259
260 // Our error handler depends on the Diagnostics object, which we're
261 // potentially about to delete. Uninstall the handler now so that any
262 // later errors use the default handling behavior instead.
263 llvm::remove_fatal_error_handler();
264
265 return checkDiagErrors(CI: Interp->getCompilerInstance(), HasError);
266}
267