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