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