diff --git a/compiler-rt/lib/orc/CMakeLists.txt b/compiler-rt/lib/orc/CMakeLists.txt --- a/compiler-rt/lib/orc/CMakeLists.txt +++ b/compiler-rt/lib/orc/CMakeLists.txt @@ -9,6 +9,7 @@ macho_platform.cpp elfnix_platform.cpp run_program_wrapper.cpp + dlfcn_wrapper.cpp ) # Implementation files for all ORC architectures. diff --git a/compiler-rt/lib/orc/dlfcn_wrapper.cpp b/compiler-rt/lib/orc/dlfcn_wrapper.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/orc/dlfcn_wrapper.cpp @@ -0,0 +1,52 @@ +//===- dlfcn_wrapper.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 +// +//===----------------------------------------------------------------------===// +// +// This file is a part of the ORC runtime support library. +// +//===----------------------------------------------------------------------===// + +#include "adt.h" +#include "common.h" +#include "wrapper_function_utils.h" + +#include + +using namespace __orc_rt; + +extern "C" const char *__orc_rt_jit_dlerror(); +extern "C" void *__orc_rt_jit_dlopen(const char *path, int mode); +extern "C" int __orc_rt_jit_dlclose(void *dso_handle); + +ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult +__orc_rt_jit_dlerror_wrapper(const char *ArgData, size_t ArgSize) { + return WrapperFunction::handle( + ArgData, ArgSize, + []() { return std::string(__orc_rt_jit_dlerror()); }) + .release(); +} + +ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult +__orc_rt_jit_dlopen_wrapper(const char *ArgData, size_t ArgSize) { + return WrapperFunction::handle( + ArgData, ArgSize, + [](const std::string &Path, int32_t mode) { + return ExecutorAddr::fromPtr( + __orc_rt_jit_dlopen(Path.c_str(), mode)); + }) + .release(); +} + +ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult +__orc_rt_jit_dlclose_wrapper(const char *ArgData, size_t ArgSize) { + return WrapperFunction::handle( + ArgData, ArgSize, + [](ExecutorAddr &DSOHandle) { + return __orc_rt_jit_dlclose(DSOHandle.toPtr()); + }) + .release(); +} diff --git a/compiler-rt/test/orc/TestCases/Darwin/x86-64/lljit-ehframe.cpp b/compiler-rt/test/orc/TestCases/Darwin/x86-64/lljit-ehframe.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/orc/TestCases/Darwin/x86-64/lljit-ehframe.cpp @@ -0,0 +1,15 @@ +// RUN: %clangxx -fPIC -emit-llvm -c -o %t %s +// RUN: %lli_orc_jitlink -relocation-model=pic %t | FileCheck %s + +// CHECK: catch + +#include + +int main(int argc, char *argv[]) { + try { + throw 0; + } catch (int X) { + puts("catch"); + } + return 0; +} diff --git a/compiler-rt/test/orc/TestCases/Darwin/x86-64/lljit-initialize-deinitialize.ll b/compiler-rt/test/orc/TestCases/Darwin/x86-64/lljit-initialize-deinitialize.ll new file mode 100644 --- /dev/null +++ b/compiler-rt/test/orc/TestCases/Darwin/x86-64/lljit-initialize-deinitialize.ll @@ -0,0 +1,32 @@ +; RUN: %lli_orc_jitlink -relocation-model=pic %s | FileCheck %s + +; CHECK: constructor +; CHECK-NEXT: main +; CHECK-NEXT: destructor + +@__dso_handle = external hidden global i8 +@.str = private unnamed_addr constant [5 x i8] c"main\00", align 1 +@.str.1 = private unnamed_addr constant [12 x i8] c"constructor\00", align 1 +@.str.2 = private unnamed_addr constant [11 x i8] c"destructor\00", align 1 +@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @constructor, i8* null }] + +define dso_local void @destructor(i8* %0) { + %2 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([11 x i8], [11 x i8]* @.str.2, i64 0, i64 0)) + ret void +} + +declare i32 @__cxa_atexit(void (i8*)*, i8*, i8*) + +; Function Attrs: nofree norecurse nounwind uwtable +define dso_local i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2 { + %3 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([5 x i8], [5 x i8]* @.str, i64 0, i64 0)) + ret i32 0 +} + +declare i32 @puts(i8* nocapture readonly) + +define internal void @constructor() { + %1 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([12 x i8], [12 x i8]* @.str.1, i64 0, i64 0)) #5 + %2 = tail call i32 @__cxa_atexit(void (i8*)* @destructor, i8* null, i8* nonnull @__dso_handle) #5 + ret void +} diff --git a/compiler-rt/test/orc/TestCases/FreeBSD/x86-64/lljit-ehframe.cpp b/compiler-rt/test/orc/TestCases/FreeBSD/x86-64/lljit-ehframe.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/orc/TestCases/FreeBSD/x86-64/lljit-ehframe.cpp @@ -0,0 +1,15 @@ +// RUN: %clangxx -fPIC -emit-llvm -c -o %t %s +// RUN: %lli_orc_jitlink -relocation-model=pic %t | FileCheck %s + +// CHECK: catch + +#include + +int main(int argc, char *argv[]) { + try { + throw 0; + } catch (int X) { + puts("catch"); + } + return 0; +} diff --git a/compiler-rt/test/orc/TestCases/FreeBSD/x86-64/lljit-initialize-deinitialize.ll b/compiler-rt/test/orc/TestCases/FreeBSD/x86-64/lljit-initialize-deinitialize.ll new file mode 100644 --- /dev/null +++ b/compiler-rt/test/orc/TestCases/FreeBSD/x86-64/lljit-initialize-deinitialize.ll @@ -0,0 +1,32 @@ +; RUN: %lli_orc_jitlink %s | FileCheck %s + +; CHECK: constructor +; CHECK-NEXT: main +; CHECK-NEXT: destructor + +@__dso_handle = external hidden global i8 +@.str = private unnamed_addr constant [5 x i8] c"main\00", align 1 +@.str.1 = private unnamed_addr constant [12 x i8] c"constructor\00", align 1 +@.str.2 = private unnamed_addr constant [11 x i8] c"destructor\00", align 1 +@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @constructor, i8* null }] + +define dso_local void @destructor(i8* %0) { + %2 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([11 x i8], [11 x i8]* @.str.2, i64 0, i64 0)) + ret void +} + +declare i32 @__cxa_atexit(void (i8*)*, i8*, i8*) + +; Function Attrs: nofree norecurse nounwind uwtable +define dso_local i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2 { + %3 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([5 x i8], [5 x i8]* @.str, i64 0, i64 0)) + ret i32 0 +} + +declare i32 @puts(i8* nocapture readonly) + +define internal void @constructor() { + %1 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([12 x i8], [12 x i8]* @.str.1, i64 0, i64 0)) #5 + %2 = tail call i32 @__cxa_atexit(void (i8*)* @destructor, i8* null, i8* nonnull @__dso_handle) #5 + ret void +} diff --git a/compiler-rt/test/orc/TestCases/Linux/x86-64/lljit-ehframe.cpp b/compiler-rt/test/orc/TestCases/Linux/x86-64/lljit-ehframe.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/orc/TestCases/Linux/x86-64/lljit-ehframe.cpp @@ -0,0 +1,15 @@ +// RUN: %clangxx -fPIC -emit-llvm -c -o %t %s +// RUN: %lli_orc_jitlink -relocation-model=pic %t | FileCheck %s + +// CHECK: catch + +#include + +int main(int argc, char *argv[]) { + try { + throw 0; + } catch (int X) { + puts("catch"); + } + return 0; +} diff --git a/compiler-rt/test/orc/TestCases/Linux/x86-64/lljit-initialize-deinitialize.ll b/compiler-rt/test/orc/TestCases/Linux/x86-64/lljit-initialize-deinitialize.ll new file mode 100644 --- /dev/null +++ b/compiler-rt/test/orc/TestCases/Linux/x86-64/lljit-initialize-deinitialize.ll @@ -0,0 +1,32 @@ +; RUN: %lli_orc_jitlink %s | FileCheck %s + +; CHECK: constructor +; CHECK-NEXT: main +; CHECK-NEXT: destructor + +@__dso_handle = external hidden global i8 +@.str = private unnamed_addr constant [5 x i8] c"main\00", align 1 +@.str.1 = private unnamed_addr constant [12 x i8] c"constructor\00", align 1 +@.str.2 = private unnamed_addr constant [11 x i8] c"destructor\00", align 1 +@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @constructor, i8* null }] + +define dso_local void @destructor(i8* %0) { + %2 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([11 x i8], [11 x i8]* @.str.2, i64 0, i64 0)) + ret void +} + +declare i32 @__cxa_atexit(void (i8*)*, i8*, i8*) + +; Function Attrs: nofree norecurse nounwind uwtable +define dso_local i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2 { + %3 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([5 x i8], [5 x i8]* @.str, i64 0, i64 0)) + ret i32 0 +} + +declare i32 @puts(i8* nocapture readonly) + +define internal void @constructor() { + %1 = tail call i32 @puts(i8* nonnull dereferenceable(1) getelementptr inbounds ([12 x i8], [12 x i8]* @.str.1, i64 0, i64 0)) #5 + %2 = tail call i32 @__cxa_atexit(void (i8*)* @destructor, i8* null, i8* nonnull @__dso_handle) #5 + ret void +} diff --git a/compiler-rt/test/orc/lit.cfg.py b/compiler-rt/test/orc/lit.cfg.py --- a/compiler-rt/test/orc/lit.cfg.py +++ b/compiler-rt/test/orc/lit.cfg.py @@ -13,6 +13,7 @@ # Assume that llvm-jitlink is in the config.llvm_tools_dir. llvm_jitlink = os.path.join(config.llvm_tools_dir, 'llvm-jitlink') +lli = os.path.join(config.llvm_tools_dir, 'lli') if config.host_os == 'Darwin': orc_rt_path = '%s/libclang_rt.orc_osx.a' % config.compiler_rt_libdir else: @@ -30,9 +31,11 @@ build_invocation(config.cxx_mode_flags + [config.target_cflags]))) config.substitutions.append( ('%llvm_jitlink', (llvm_jitlink + ' -orc-runtime=' + orc_rt_path))) +config.substitutions.append( + ('%lli_orc_jitlink', (lli + ' -jit-kind=orc -jit-linker=jitlink -orc-runtime=' + orc_rt_path))) # Default test suffixes. -config.suffixes = ['.c', '.cpp', '.S'] +config.suffixes = ['.c', '.cpp', '.S', '.ll'] # Exclude Inputs directories. config.excludes = ['Inputs'] diff --git a/llvm/include/llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h b/llvm/include/llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h --- a/llvm/include/llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h @@ -125,7 +125,7 @@ /// Set TargetOptions. /// /// Note: This operation will overwrite any previously configured options, - /// including EmulatedTLS and ExplicitEmulatedTLS which + /// including EmulatedTLS, ExplicitEmulatedTLS, and UseInitArray which /// the JITTargetMachineBuilder sets by default. Clients are responsible /// for re-enabling these overwritten options. JITTargetMachineBuilder &setOptions(TargetOptions Options) { diff --git a/llvm/lib/ExecutionEngine/Orc/ELFNixPlatform.cpp b/llvm/lib/ExecutionEngine/Orc/ELFNixPlatform.cpp --- a/llvm/lib/ExecutionEngine/Orc/ELFNixPlatform.cpp +++ b/llvm/lib/ExecutionEngine/Orc/ELFNixPlatform.cpp @@ -263,6 +263,10 @@ static const std::pair StandardRuntimeUtilityAliases[] = { {"__orc_rt_run_program", "__orc_rt_elfnix_run_program"}, + {"__orc_rt_jit_dlerror", "__orc_rt_elfnix_jit_dlerror"}, + {"__orc_rt_jit_dlopen", "__orc_rt_elfnix_jit_dlopen"}, + {"__orc_rt_jit_dlclose", "__orc_rt_elfnix_jit_dlclose"}, + {"__orc_rt_jit_dlsym", "__orc_rt_elfnix_jit_dlsym"}, {"__orc_rt_log_error", "__orc_rt_log_error_to_stderr"}}; return ArrayRef>( diff --git a/llvm/lib/ExecutionEngine/Orc/JITTargetMachineBuilder.cpp b/llvm/lib/ExecutionEngine/Orc/JITTargetMachineBuilder.cpp --- a/llvm/lib/ExecutionEngine/Orc/JITTargetMachineBuilder.cpp +++ b/llvm/lib/ExecutionEngine/Orc/JITTargetMachineBuilder.cpp @@ -19,6 +19,7 @@ : TT(std::move(TT)) { Options.EmulatedTLS = true; Options.ExplicitEmulatedTLS = true; + Options.UseInitArray = true; } Expected JITTargetMachineBuilder::detectHost() { diff --git a/llvm/lib/ExecutionEngine/Orc/MachOPlatform.cpp b/llvm/lib/ExecutionEngine/Orc/MachOPlatform.cpp --- a/llvm/lib/ExecutionEngine/Orc/MachOPlatform.cpp +++ b/llvm/lib/ExecutionEngine/Orc/MachOPlatform.cpp @@ -302,6 +302,10 @@ static const std::pair StandardRuntimeUtilityAliases[] = { {"___orc_rt_run_program", "___orc_rt_macho_run_program"}, + {"___orc_rt_jit_dlerror", "___orc_rt_macho_jit_dlerror"}, + {"___orc_rt_jit_dlopen", "___orc_rt_macho_jit_dlopen"}, + {"___orc_rt_jit_dlclose", "___orc_rt_macho_jit_dlclose"}, + {"___orc_rt_jit_dlsym", "___orc_rt_macho_jit_dlsym"}, {"___orc_rt_log_error", "___orc_rt_log_error_to_stderr"}}; return ArrayRef>( diff --git a/llvm/tools/lli/lli.cpp b/llvm/tools/lli/lli.cpp --- a/llvm/tools/lli/lli.cpp +++ b/llvm/tools/lli/lli.cpp @@ -28,12 +28,15 @@ #include "llvm/ExecutionEngine/ObjectCache.h" #include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" #include "llvm/ExecutionEngine/Orc/DebugUtils.h" +#include "llvm/ExecutionEngine/Orc/ELFNixPlatform.h" #include "llvm/ExecutionEngine/Orc/EPCDebugObjectRegistrar.h" +#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" #include "llvm/ExecutionEngine/Orc/EPCEHFrameRegistrar.h" #include "llvm/ExecutionEngine/Orc/EPCGenericRTDyldMemoryManager.h" #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" #include "llvm/ExecutionEngine/Orc/LLJIT.h" +#include "llvm/ExecutionEngine/Orc/MachOPlatform.h" #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" #include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h" #include "llvm/ExecutionEngine/Orc/SymbolStringPool.h" @@ -120,6 +123,9 @@ "RuntimeDyld"), clEnumValN(JITLinkerKind::JITLink, "jitlink", "Orc-specific linker"))); + cl::opt OrcRuntime("orc-runtime", + cl::desc("Use ORC runtime from given path"), + cl::init("")); cl::opt LazyJITCompileThreads("compile-threads", @@ -230,13 +236,15 @@ cl::desc("Do not resolve lli process symbols in JIT'd code"), cl::init(false)); - enum class LLJITPlatform { Inactive, DetectHost, GenericIR }; + enum class LLJITPlatform { Inactive, DetectHost, ORC, GenericIR }; cl::opt Platform("lljit-platform", cl::desc("Platform to use with LLJIT"), cl::init(LLJITPlatform::DetectHost), cl::values(clEnumValN(LLJITPlatform::DetectHost, "DetectHost", "Select based on JIT target triple"), + clEnumValN(LLJITPlatform::ORC, "ORC", + "Use ORCPlatform with the ORC runtime"), clEnumValN(LLJITPlatform::GenericIR, "GenericIR", "Use LLJITGenericIRPlatform"), clEnumValN(LLJITPlatform::Inactive, "Inactive", @@ -365,6 +373,53 @@ } }; +class ORCPlatformSupport : public orc::LLJIT::PlatformSupport { +public: + ORCPlatformSupport(orc::LLJIT &J) : J(J) {} + + Error initialize(orc::JITDylib &JD) override { + using llvm::orc::shared::SPSExecutorAddr; + using llvm::orc::shared::SPSString; + using SPSDLOpenSig = SPSExecutorAddr(SPSString, int32_t); + enum dlopen_mode : int32_t { + ORC_RT_RTLD_LAZY = 0x1, + ORC_RT_RTLD_NOW = 0x2, + ORC_RT_RTLD_LOCAL = 0x4, + ORC_RT_RTLD_GLOBAL = 0x8 + }; + + if (auto WrapperAddr = J.lookup("__orc_rt_jit_dlopen_wrapper")) { + return J.getExecutionSession().callSPSWrapper( + *WrapperAddr, DSOHandles[&JD], JD.getName(), + int32_t(ORC_RT_RTLD_LAZY)); + } else + return WrapperAddr.takeError(); + } + + Error deinitialize(orc::JITDylib &JD) override { + using llvm::orc::shared::SPSExecutorAddr; + using SPSDLCloseSig = int32_t(SPSExecutorAddr); + + if (auto WrapperAddr = J.lookup("__orc_rt_jit_dlclose_wrapper")) { + int32_t result; + auto E = J.getExecutionSession().callSPSWrapper( + *WrapperAddr, result, DSOHandles[&JD]); + if (E) + return E; + else if (result) + return make_error("dlclose failed", + inconvertibleErrorCode()); + DSOHandles.erase(&JD); + } else + return WrapperAddr.takeError(); + return Error::success(); + } + +private: + orc::LLJIT &J; + DenseMap DSOHandles; +}; + // On Mingw and Cygwin, an external symbol named '__main' is called from the // generated 'main' function to allow static initialization. To avoid linking // problems with remote targets (because lli's remote target support does not @@ -904,21 +959,29 @@ } // Set up LLJIT platform. - { - LLJITPlatform P = Platform; - if (P == LLJITPlatform::DetectHost) + LLJITPlatform P = Platform; + if (P == LLJITPlatform::DetectHost) { + if (JITLinker == JITLinkerKind::JITLink && !OrcRuntime.empty() && + (TT->isOSBinFormatMachO() || TT->isOSBinFormatELF())) + P = LLJITPlatform::ORC; + else P = LLJITPlatform::GenericIR; - - switch (P) { - case LLJITPlatform::GenericIR: - // Nothing to do: LLJITBuilder will use this by default. - break; - case LLJITPlatform::Inactive: - Builder.setPlatformSetUp(orc::setUpInactivePlatform); - break; - default: - llvm_unreachable("Unrecognized platform value"); - } + } + switch (P) { + case LLJITPlatform::ORC: + Builder.setPlatformSetUp([](llvm::orc::LLJIT &J) -> llvm::Error { + J.setPlatformSupport(std::make_unique(J)); + return Error::success(); + }); + break; + case LLJITPlatform::GenericIR: + // Nothing to do: LLJITBuilder will use this by default. + break; + case LLJITPlatform::Inactive: + Builder.setPlatformSetUp(orc::setUpInactivePlatform); + break; + default: + llvm_unreachable("Unrecognized platform value"); } std::unique_ptr EPC = nullptr; @@ -926,13 +989,15 @@ EPC = ExitOnErr(orc::SelfExecutorProcessControl::Create( std::make_shared())); - Builder.setObjectLinkingLayerCreator([&EPC](orc::ExecutionSession &ES, - const Triple &) { + Builder.setObjectLinkingLayerCreator([&EPC, &P](orc::ExecutionSession &ES, + const Triple &TT) { auto L = std::make_unique(ES, EPC->getMemMgr()); - L->addPlugin(std::make_unique( - ES, ExitOnErr(orc::EPCEHFrameRegistrar::Create(ES)))); - L->addPlugin(std::make_unique( - ES, ExitOnErr(orc::createJITLoaderGDBRegistrar(ES)))); + if (P != LLJITPlatform::ORC) { + L->addPlugin(std::make_unique( + ES, ExitOnErr(orc::EPCEHFrameRegistrar::Create(ES)))); + L->addPlugin(std::make_unique( + ES, ExitOnErr(orc::createJITLoaderGDBRegistrar(ES)))); + } return L; }); } @@ -979,6 +1044,31 @@ std::make_unique(GenerateBuiltinFunctions, Mangle)); + if (P == LLJITPlatform::ORC) { + if (auto *OLL = llvm::dyn_cast(ObjLayer)) { + auto &ES = J->getExecutionSession(); + if (TT->isOSBinFormatMachO()) { + if (auto P = llvm::orc::MachOPlatform::Create( + ES, *OLL, J->getMainJITDylib(), OrcRuntime.c_str())) + ES.setPlatform(std::move(*P)); + else + ExitOnErr(P.takeError()); + } else if (TT->isOSBinFormatELF()) { + if (auto P = llvm::orc::ELFNixPlatform::Create( + ES, *OLL, J->getMainJITDylib(), OrcRuntime.c_str())) + ES.setPlatform(std::move(*P)); + else + ExitOnErr(P.takeError()); + } else { + errs() << "No ORC platform support\n"; + exit(1); + } + } else { + errs() << "ORC platform requires JITLink\n"; + exit(1); + } + } + // Regular modules are greedy: They materialize as a whole and trigger // materialization for all required symbols recursively. Lazy modules go // through partitioning and they replace outgoing calls with reexport stubs