Reinstate "[clang-repl] Re-implement clang-interpreter as a test case."

Original commit message: "
  Original commit message: "
    Original commit message: "
       Original commit message:"
         The current infrastructure in lib/Interpreter has a tool, clang-repl, very
         similar to clang-interpreter which also allows incremental compilation.

         This patch moves clang-interpreter as a test case and drops it as conditionally
         built example as we already have clang-repl in place.
       "

       This patch also ignores ppc due to missing weak symbol for __gxx_personality_v0
       which may be a feature request for the jit infrastructure. Also, adds a missing
       build system dependency to the orc jit.
    "

    Additionally, this patch defines a custom exception type and thus avoids the
    requirement to include header <exception>, making it easier to deploy across
    systems without standard location of the c++ headers.
  "

  This patch also works around PR49692 and finds a way to use llvm::consumeError
  in rtti mode.
"

This patch also checks if stl is built with rtti.

Differential revision: https://reviews.llvm.org/D107049
This commit is contained in:
Vassil Vassilev 2021-10-25 17:52:20 +00:00
parent d7fbad0dcf
commit c24a58081b
16 changed files with 194 additions and 168 deletions

View File

@ -54,11 +54,6 @@ tree in terms of conformance to :doc:`ClangFormat` as of: October 09, 2021 17:11
- `1`
- `0`
- :good:`100%`
* - clang/examples/clang-interpreter
- `1`
- `0`
- `1`
- :none:`0%`
* - clang/examples/PrintFunctionNames
- `1`
- `0`

View File

@ -3,7 +3,6 @@ if(NOT CLANG_BUILD_EXAMPLES)
set(EXCLUDE_FROM_ALL ON)
endif()
add_subdirectory(clang-interpreter)
add_subdirectory(PrintFunctionNames)
add_subdirectory(AnnotateFunctions)
add_subdirectory(Attribute)

View File

@ -1,93 +0,0 @@
set(LLVM_LINK_COMPONENTS
Core
ExecutionEngine
MC
MCJIT
Object
OrcJit
Option
RuntimeDyld
Support
native
)
add_clang_executable(clang-interpreter
main.cpp
)
add_dependencies(clang-interpreter
clang-resource-headers
)
clang_target_link_libraries(clang-interpreter
PRIVATE
clangBasic
clangCodeGen
clangDriver
clangFrontend
clangSerialization
)
export_executable_symbols(clang-interpreter)
if (MSVC)
# Is this a CMake bug that even with export_executable_symbols, Windows
# needs to explictly export the type_info vtable
set_property(TARGET clang-interpreter
APPEND_STRING PROPERTY LINK_FLAGS " /EXPORT:??_7type_info@@6B@")
endif()
function(clang_enable_exceptions TARGET)
# Really have to jump through hoops to enable exception handling independent
# of how LLVM is being built.
if (NOT LLVM_REQUIRES_EH AND NOT LLVM_REQUIRES_RTTI)
if (MSVC)
# /EHs to allow throwing from extern "C"
set(excptnExceptions_ON "/D _HAS_EXCEPTIONS=1 /EHs /wd4714")
set(excptnExceptions_OFF "/D _HAS_EXCEPTIONS=0 /EHs-c-")
set(excptnRTTI_ON "/GR")
set(excptnRTTI_OFF "/GR-")
set(excptnEHRTTIRegEx "(/EHs(-c-?)|_HAS_EXCEPTIONS=(0|1))")
else()
set(excptnExceptions_ON "-fexceptions")
set(excptnExceptions_OFF "-fno-exceptions")
set(excptnRTTI_ON "-frtti")
set(excptnRTTI_OFF "-fno-rtti")
set(excptnEHRTTIRegEx "-f(exceptions|no-exceptions)")
endif()
if (LLVM_REQUIRES_EH)
set(excptnExceptions_DFLT ${excptnExceptions_ON})
else()
set(excptnExceptions_DFLT ${excptnExceptions_OFF})
endif()
if (LLVM_REQUIRES_RTTI)
set(excptnRTTI_DFLT ${excptnRTTI_ON})
else()
set(excptnRTTI_DFLT ${excptnRTTI_OFF})
endif()
# Strip the exception & rtti flags from the target
get_property(addedFlags TARGET ${TARGET} PROPERTY COMPILE_FLAGS)
string(REGEX REPLACE ${excptnEHRTTIRegEx} "" editedFlags "${addedFlags}")
string(REPLACE ${excptnRTTI_OFF} "" editedFlags "${editedFlags}")
set_property(TARGET ${TARGET} PROPERTY COMPILE_FLAGS "${editedFlags}")
get_property(addedFlags TARGET ${TARGET} PROPERTY COMPILE_DEFINITIONS)
string(REGEX REPLACE ${excptnEHRTTIRegEx} "" editedFlags "${addedFlags}")
string(REPLACE ${excptnRTTI_OFF} "" editedFlags "${editedFlags}")
set_property(TARGET ${TARGET} PROPERTY COMPILE_DEFINITIONS "${editedFlags}")
# Re-add the exception & rtti flags from LLVM
set_property(SOURCE main.cpp APPEND_STRING PROPERTY COMPILE_FLAGS
" ${excptnExceptions_DFLT} ${excptnRTTI_DFLT} ")
set_property(SOURCE Manager.cpp APPEND_STRING PROPERTY COMPILE_FLAGS
" ${excptnExceptions_DFLT} ${excptnRTTI_DFLT} ")
# Invoke with exceptions & rtti
set_property(SOURCE Invoke.cpp APPEND_STRING PROPERTY COMPILE_FLAGS
" ${excptnExceptions_ON} ${excptnRTTI_ON} ")
endif()
endfunction(clang_enable_exceptions)
clang_enable_exceptions(clang-interpreter)

View File

@ -1,20 +0,0 @@
This is an example of Clang based interpreter, for executing standalone C/C++
programs.
It demonstrates the following features:
1. Parsing standard compiler command line arguments using the Driver library.
2. Constructing a Clang compiler instance, using the appropriate arguments
derived in step #1.
3. Invoking the Clang compiler to lex, parse, syntax check, and then generate
LLVM code.
4. Use the LLVM JIT functionality to execute the final module.
5. Intercepting a Win64 library call to allow throwing and catching exceptions
in and from the JIT.
The implementation has many limitations and is not designed to be a full fledged
interpreter. It is designed to demonstrate a simple but functional use of the
Clang compiler libraries.

View File

@ -1,33 +0,0 @@
//===-- examples/clang-interpreter/Test.cxx - Clang C Interpreter Example -===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// Example throwing in and from the JIT (particularly on Win64).
//
// ./bin/clang-interpreter <src>/tools/clang/examples/clang-interpreter/Test.cxx
#include <stdexcept>
#include <stdio.h>
static void ThrowerAnError(const char* Name) {
throw std::runtime_error(Name);
}
int main(int argc, const char** argv) {
for (int I = 0; I < argc; ++I)
printf("arg[%d]='%s'\n", I, argv[I]);
try {
ThrowerAnError("In JIT");
} catch (const std::exception& E) {
printf("Caught: '%s'\n", E.what());
} catch (...) {
printf("Unknown exception\n");
}
ThrowerAnError("From JIT");
return 0;
}

View File

@ -16,6 +16,7 @@
#include "clang/Interpreter/PartialTranslationUnit.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
#include "llvm/Support/Error.h"
#include <memory>
@ -65,6 +66,8 @@ public:
return Execute(*PTU);
return llvm::Error::success();
}
llvm::Expected<llvm::JITTargetAddress>
getSymbolAddress(llvm::StringRef UnmangledName) const;
};
} // namespace clang

View File

@ -60,4 +60,12 @@ llvm::Error IncrementalExecutor::runCtors() const {
return Jit->initialize(Jit->getMainJITDylib());
}
llvm::Expected<llvm::JITTargetAddress>
IncrementalExecutor::getSymbolAddress(llvm::StringRef UnmangledName) const {
auto Sym = Jit->lookup(UnmangledName);
if (!Sym)
return Sym.takeError();
return Sym->getAddress();
}
} // end namespace clang

View File

@ -41,6 +41,8 @@ public:
llvm::Error addModule(std::unique_ptr<llvm::Module> M);
llvm::Error runCtors() const;
llvm::Expected<llvm::JITTargetAddress>
getSymbolAddress(llvm::StringRef UnmangledName) const;
};
} // end namespace clang

View File

@ -36,8 +36,7 @@
using namespace clang;
// FIXME: Figure out how to unify with namespace init_convenience from
// tools/clang-import-test/clang-import-test.cpp and
// examples/clang-interpreter/main.cpp
// tools/clang-import-test/clang-import-test.cpp
namespace {
/// Retrieves the clang CC1 specific flags out of the compilation's jobs.
/// \returns NULL on error.
@ -222,3 +221,13 @@ llvm::Error Interpreter::Execute(PartialTranslationUnit &T) {
return llvm::Error::success();
}
llvm::Expected<llvm::JITTargetAddress>
Interpreter::getSymbolAddress(llvm::StringRef UnmangledName) const {
if (!IncrExecutor)
return llvm::make_error<llvm::StringError>("Operation failed. "
"No execution engine",
std::error_code());
return IncrExecutor->getSymbolAddress(UnmangledName);
}

View File

@ -96,7 +96,6 @@ if (CLANG_BUILD_EXAMPLES)
Attribute
AnnotateFunctions
CallSuperAttr
clang-interpreter
PluginsOrder
PrintFunctionNames
)

View File

@ -1,10 +0,0 @@
// RUN: clang-interpreter %s | FileCheck %s
// REQUIRES: native, examples
int printf(const char *, ...);
int main() {
// CHECK: {{Hello world!}}
printf("Hello world!\n");
return 0;
}

View File

@ -71,7 +71,6 @@ tools = [
if config.clang_examples:
config.available_features.add('examples')
tools.append('clang-interpreter')
def have_host_jit_support():
clang_repl_exe = lit.util.which('clang-repl', config.clang_tools_dir)

View File

@ -1,5 +1,8 @@
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
Core
OrcJIT
Support
)
add_clang_unittest(ClangReplInterpreterTests
@ -12,3 +15,8 @@ target_link_libraries(ClangReplInterpreterTests PUBLIC
clangInterpreter
clangFrontend
)
# Exceptions on Windows are not yet supported.
if(NOT WIN32)
add_subdirectory(ExceptionTests)
endif()

View File

@ -0,0 +1,24 @@
# The interpreter can throw an exception from user input. The test binary needs
# to be compiled with exception support to catch the thrown exception.
set(LLVM_REQUIRES_EH ON)
set(LLVM_REQUIRES_RTTI ON)
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
Core
OrcJIT
Support
)
add_clang_unittest(ClangReplInterpreterExceptionTests
InterpreterExceptionTest.cpp
)
llvm_update_compile_flags(ClangReplInterpreterExceptionTests)
target_link_libraries(ClangReplInterpreterExceptionTests PUBLIC
clangAST
clangBasic
clangInterpreter
clangFrontend
)
add_dependencies(ClangReplInterpreterExceptionTests clang-resource-headers)

View File

@ -0,0 +1,138 @@
//===- unittests/Interpreter/InterpreterExceptionTest.cpp -----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Unit tests for Clang's Interpreter library.
//
//===----------------------------------------------------------------------===//
#include "clang/Interpreter/Interpreter.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclGroup.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/Version.h"
#include "clang/Config/config.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm-c/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using namespace clang;
namespace {
using Args = std::vector<const char *>;
static std::unique_ptr<Interpreter>
createInterpreter(const Args &ExtraArgs = {},
DiagnosticConsumer *Client = nullptr) {
Args ClangArgs = {"-Xclang", "-emit-llvm-only"};
ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end());
auto CI = cantFail(clang::IncrementalCompilerBuilder::create(ClangArgs));
if (Client)
CI->getDiagnostics().setClient(Client, /*ShouldOwnClient=*/false);
return cantFail(clang::Interpreter::create(std::move(CI)));
}
TEST(InterpreterTest, CatchException) {
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
{
auto J = llvm::orc::LLJITBuilder().create();
if (!J) {
// The platform does not support JITs.
// Using llvm::consumeError will require typeinfo for ErrorInfoBase, we
// can avoid that by going via the C interface.
LLVMConsumeError(llvm::wrap(J.takeError()));
return;
}
}
#define Stringify(s) Stringifyx(s)
#define Stringifyx(s) #s
// We define a custom exception to avoid #include-ing the <exception> header
// which would require this test to know about the libstdc++ location.
// its own header file.
#define CUSTOM_EXCEPTION \
struct custom_exception { \
custom_exception(const char *Msg) : Message(Msg) {} \
const char *Message; \
};
CUSTOM_EXCEPTION;
std::string ExceptionCode = Stringify(CUSTOM_EXCEPTION);
ExceptionCode +=
R"(
extern "C" int printf(const char*, ...);
static void ThrowerAnError(const char* Name) {
throw custom_exception(Name);
}
extern "C" int throw_exception() {
try {
ThrowerAnError("To be caught in JIT");
} catch (const custom_exception& E) {
printf("Caught: '%s'\n", E.Message);
} catch (...) {
printf("Unknown exception\n");
}
ThrowerAnError("To be caught in binary");
return 0;
}
)";
std::unique_ptr<Interpreter> Interp = createInterpreter();
// FIXME: Re-enable the excluded target triples.
const clang::CompilerInstance *CI = Interp->getCompilerInstance();
const llvm::Triple &Triple = CI->getASTContext().getTargetInfo().getTriple();
// FIXME: PPC fails due to `Symbols not found: [DW.ref.__gxx_personality_v0]`
// The current understanding is that the JIT should emit this symbol if it was
// not (eg. the way passing clang -fPIC does it).
if (Triple.isPPC())
return;
// FIXME: ARM fails due to `Not implemented relocation type!`
if (Triple.isARM())
return;
// FIXME: libunwind on darwin is broken, see PR49692.
if (Triple.isOSDarwin() && (Triple.getArch() == llvm::Triple::aarch64 ||
Triple.getArch() == llvm::Triple::aarch64_32))
return;
// Check if platform does not support exceptions.
{
// Force the creation of an incremental executor to call getSymbolAddress.
llvm::cantFail(Interp->ParseAndExecute(""));
auto Sym = Interp->getSymbolAddress("__cxa_throw");
if (!Sym) {
LLVMConsumeError(llvm::wrap(Sym.takeError()));
return;
}
}
llvm::cantFail(Interp->ParseAndExecute(ExceptionCode));
testing::internal::CaptureStdout();
auto ThrowException =
(int (*)())llvm::cantFail(Interp->getSymbolAddress("throw_exception"));
EXPECT_ANY_THROW(ThrowException());
std::string CapturedStdOut = testing::internal::GetCapturedStdout();
EXPECT_EQ(CapturedStdOut, "Caught: 'To be caught in JIT'\n");
llvm::llvm_shutdown();
}
} // end anonymous namespace

View File

@ -17,8 +17,6 @@
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "llvm/ADT/ArrayRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"