This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 076e83f62f [LLVM] Use external tvm-ffi ORC JIT for LLVMModule
execution (#20292)
076e83f62f is described below
commit 076e83f62f453d5be64d74a296507d68d568dbc0
Author: Yaxing Cai <[email protected]>
AuthorDate: Mon Sep 28 09:39:27 2026 +0800
[LLVM] Use external tvm-ffi ORC JIT for LLVMModule execution (#20292)
This makes `LLVMModule` execution go through the separately installed
`apache-tvm-ffi-orcjit` package instead of TVM hosting its own execution
engine.
### What changes
`LLVMModuleNode` no longer owns an `llvm::ExecutionEngine` /
`llvm::orc::LLJIT`. On first
function lookup it emits the retained module as an **object file in
memory** and transfers
the bytes to the package through the global FFI functions
`tvm_ffi_orcjit.GlobalDefaultSession` /
`tvm_ffi_orcjit.SessionLoadModule`, then forwards
`GetFunction` to the returned dylib. Because the boundary transfers an
object file, TVM and
the package do not need to be built against the same LLVM version.
This removes ~340 lines from `llvm_module.cc`, including all the
`#if TVM_LLVM_VERSION >= 210/230` LLJIT and `ObjectLinkingLayer` shims.
MCJIT is no longer
supported, so the now-dead `jit` target attribute is removed from
`target_kind.cc` and
`LLVMTargetInfo`, along with its tests.
There is no local fallback: if the package is missing, function lookup
raises a targeted
install error. Importing `tvm` stays silent so AOT-only workflows are
unaffected.
### CI
The package is a new runtime dependency, so CI has to provide it:
- **Linux (Jenkins):** installed in
`docker/install/ubuntu_install_python_package.sh` so the
~20MB wheel is baked into the CI image rather than downloaded on every
run. This
`docker/` change trips `git_change_docker.sh`, so the images rebuild for
this PR.
- **macOS / Windows (GitHub Actions):** both jobs install TVM with
`--no-deps` and would
otherwise silently skip the new dependency, so it is installed
explicitly.
Pinned to `==0.1.2` in `pyproject.toml` and everywhere CI installs it.
The tvm-ffi
submodule and Python requirement move to v0.1.14-post1, which provides
the process-lifetime
module registry required by the new addon loader. macOS CI returns to
its normal
`USE_LLVM=ON` configuration: 0.1.2 keeps its LLVM symbols local, so the
addon can coexist
with the LLVM dylib used by TVM.
### Verification
- `apache-tvm-ffi-orcjit` 0.1.2 publishes prebuilt wheels for all four
CI platforms
(manylinux x86_64/aarch64, macOS arm64, win_amd64), so no CI leg has to
build LLVM.
- On macOS arm64, installed the published 0.1.2 addon and 0.1.14.post1
core wheels in an
isolated environment. The addon's C and C++ quick-start examples both
passed.
- Loaded the compiled C and C++ object bytes through the same
five-argument global FFI call
used by `LLVMModuleNode`, then called `add(10, 20)` successfully in both
modules.
- Confirmed the macOS wheel exports only `TVMFFIOrcJITInitialize` and
compiled the new C++
FFI call against the pinned tvm-ffi headers. The macOS CI job checks the
built TVM wheel's
`Requires-Dist` pins.
The full TVM LLVM codegen test was not run locally because this checkout
has no TVM LLVM
build. CI runs `test_minimal_target_codegen_llvm.py` on macOS and
Windows and the full Linux
suite.
Co-authored-by: Tianqi Chen <[email protected]>
---
.github/workflows/main.yml | 10 +-
3rdparty/tvm-ffi | 2 +-
docker/install/ubuntu_install_python_package.sh | 4 +
docs/arch/codegen.rst | 7 +-
pyproject.toml | 3 +-
python/tvm/__init__.py | 10 +-
src/target/llvm/llvm_instance.cc | 16 -
src/target/llvm/llvm_instance.h | 6 -
src/target/llvm/llvm_module.cc | 391 ++++-------------------
src/target/target_kind.cc | 2 -
tests/python/runtime/test_runtime_module_load.py | 5 +-
tests/python/target/test_target_target.py | 7 -
tests/scripts/task_python_docs.sh | 3 +
tests/scripts/task_python_unittest.sh | 5 +
14 files changed, 105 insertions(+), 366 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 2827ed917a..7a524732cf 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -63,6 +63,10 @@ jobs:
run: |
export TVM_FFI_VERSION="$(python -c 'from importlib.metadata import
version; print(version("apache-tvm-ffi"))')"
pip install ml_dtypes
+ # --no-deps: this package requires apache-tvm-ffi>=0.1.14.post1, and
the source-built core
+ # versions as 0.1.dev1+g<sha> from the shallow checkout, which is <
0.1.0 under PEP 440.
+ # Without it pip replaces the source-matched core with a PyPI build.
+ pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
# Keep the source-matched tvm-ffi installed by the setup action.
pip install --no-deps dist/*.whl
python - <<'PY'
@@ -70,7 +74,8 @@ jobs:
from importlib.metadata import requires, version
assert version("apache-tvm-ffi") == os.environ["TVM_FFI_VERSION"]
- assert "apache-tvm-ffi>=0.1.13.post2" in requires("apache-tvm")
+ assert "apache-tvm-ffi>=0.1.14.post1" in requires("apache-tvm")
+ assert "apache-tvm-ffi-orcjit==0.1.2" in requires("apache-tvm")
PY
- name: Test
@@ -113,6 +118,9 @@ jobs:
shell: cmd /C call {0}
run: |
pip install psutil cloudpickle ml_dtypes numpy packaging scipy
tornado typing_extensions
+ rem --no-deps keeps the source-built apache-tvm-ffi (versioned below
0.1.14.post1 from the
+ rem shallow checkout) from being replaced by a PyPI build.
+ pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
- name: Test
shell: cmd /C call {0}
run: >-
diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index daf594da8e..e251c6abee 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit daf594da8e6950fdc02ae35fcf4e0ab2e59a4979
+Subproject commit e251c6abee7a111c6467f84d854be512e33e2db0
diff --git a/docker/install/ubuntu_install_python_package.sh
b/docker/install/ubuntu_install_python_package.sh
index 8be4a6a8d7..484ceabf1e 100755
--- a/docker/install/ubuntu_install_python_package.sh
+++ b/docker/install/ubuntu_install_python_package.sh
@@ -41,3 +41,7 @@ uv pip install --upgrade \
"tornado~=6.4" \
"ml_dtypes~=0.5" \
mlc-z3-static==4.16.0
+
+# Provides LLVMModule JIT execution. --no-deps keeps its apache-tvm-ffi
requirement from
+# pulling a PyPI core into site-packages; CI installs the submodule-matched
core into ./python.
+uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
diff --git a/docs/arch/codegen.rst b/docs/arch/codegen.rst
index d14e4b8a07..799568b420 100644
--- a/docs/arch/codegen.rst
+++ b/docs/arch/codegen.rst
@@ -245,7 +245,12 @@ exposes it as callable ``PackedFunc``\ s.
- How Code Is Executed
* - ``LLVMModule``
- LLVM IR (in-memory ``llvm::Module``)
- - JIT-compiled on first call (MCJIT or ORC). Function pointers cached for
subsequent calls.
+ - JIT-compiled on first call by the separately installed
``apache-tvm-ffi-orcjit`` package.
+ TVM emits an object in memory and transfers it to the package-backed
JITDylib through FFI;
+ it has no local execution engine or fallback. Install it with
+ ``pip install apache-tvm-ffi-orcjit==0.1.2``. Because this boundary
transfers an object
+ file, TVM and the package do not need to use the same LLVM version.
MCJIT is no longer
+ supported.
* - ``CUDAModule``
- PTX or cubin binary
- Loaded via CUDA driver API (``cuModuleLoad``). Kernels launched via
``cuLaunchKernel``.
diff --git a/pyproject.toml b/pyproject.toml
index 24d3594c37..e2fcc2918b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,7 +50,8 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
- "apache-tvm-ffi>=0.1.13.post2",
+ "apache-tvm-ffi>=0.1.14.post1",
+ "apache-tvm-ffi-orcjit==0.1.2",
"ml_dtypes",
"numpy",
"typing_extensions",
diff --git a/python/tvm/__init__.py b/python/tvm/__init__.py
index 77e30a9e2a..388e2180f3 100644
--- a/python/tvm/__init__.py
+++ b/python/tvm/__init__.py
@@ -19,8 +19,8 @@
"""TVM: Open Deep Learning Compiler Stack."""
import multiprocessing
-import sys
import os
+import sys
# ffi module must load first
from tvm_ffi import register_object, register_global_func, get_global_func
@@ -125,3 +125,11 @@ sys.excepthook = tvm_wrap_excepthook(sys.excepthook)
from .backend._autoload_backends import _autoload_backends
_autoload_backends()
+
+# Load the optional ORC JIT extension for its global FFI registrations. LLVM
+# function lookup reports a targeted installation error if this import fails;
+# importing TVM itself remains silent for AOT-only workflows.
+try:
+ import tvm_ffi_orcjit as _tvm_ffi_orcjit
+except Exception: # pylint: disable=broad-exception-caught
+ pass
diff --git a/src/target/llvm/llvm_instance.cc b/src/target/llvm/llvm_instance.cc
index 0b1275498c..a17e92ce8c 100644
--- a/src/target/llvm/llvm_instance.cc
+++ b/src/target/llvm/llvm_instance.cc
@@ -262,18 +262,6 @@ LLVMTargetInfo::LLVMTargetInfo(LLVMInstance& instance,
}
}
- // LLVM JIT engine options
- if (const auto& v =
-
target.Get("jit").value_or(nullptr).as_or_throw<ffi::Optional<ffi::String>>()) {
- ffi::String value = v.value();
- if ((value == "mcjit") || (value == "orcjit")) {
- jit_engine_ = value;
- } else {
- TVM_FFI_THROW(InternalError)
- << "invalid jit option " << value << " (can be `orcjit` or
`mcjit`).";
- }
- }
-
// TVM & LLVM vector width options
if (const auto& w =
target.Get("vector-width").value_or(nullptr).as_or_throw<ffi::Optional<int64_t>>())
{
@@ -585,10 +573,6 @@ std::string LLVMTargetInfo::str() const {
obj.Set(ffi::String("cl-opt"), arr);
}
- if (jit_engine_ != "orcjit") {
- obj.Set(ffi::String("jit"), ffi::String(jit_engine_));
- }
-
return std::string(ffi::json::Stringify(obj));
}
diff --git a/src/target/llvm/llvm_instance.h b/src/target/llvm/llvm_instance.h
index 0cb141de99..5d797d0141 100644
--- a/src/target/llvm/llvm_instance.h
+++ b/src/target/llvm/llvm_instance.h
@@ -236,11 +236,6 @@ class LLVMTargetInfo {
* \return `llvm::FastMathFlags` for this target
*/
llvm::FastMathFlags GetFastMathFlags() const { return fast_math_flags_; }
- /*!
- * \brief Get the LLVM JIT engine type
- * \return the type name of the JIT engine (default "orcjit" or "mcjit")
- */
- const std::string GetJITEngine() const { return jit_engine_; }
/*!
* \brief Get the TVM & LLVM vector_width
* \return number of bits for vector width
@@ -366,7 +361,6 @@ class LLVMTargetInfo {
llvm::Reloc::Model reloc_model_ = llvm::Reloc::PIC_;
llvm::CodeModel::Model code_model_ = llvm::CodeModel::Small;
std::shared_ptr<llvm::TargetMachine> target_machine_;
- std::string jit_engine_ = "orcjit";
int vector_width_{0};
};
diff --git a/src/target/llvm/llvm_module.cc b/src/target/llvm/llvm_module.cc
index a2226c5dec..952638c38e 100644
--- a/src/target/llvm/llvm_module.cc
+++ b/src/target/llvm/llvm_module.cc
@@ -26,16 +26,6 @@
#include <llvm/ADT/SmallString.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Bitcode/BitcodeWriter.h>
-#include <llvm/ExecutionEngine/ExecutionEngine.h>
-#include <llvm/ExecutionEngine/MCJIT.h>
-#include <llvm/ExecutionEngine/Orc/LLJIT.h>
-#include <llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h>
-#include <tvm/ffi/reflection/registry.h>
-#include <tvm/support/io.h>
-#if _WIN32
-#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
-#include <llvm/ExecutionEngine/SectionMemoryManager.h>
-#endif
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Intrinsics.h>
@@ -46,6 +36,8 @@
#include <llvm/IR/Module.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/FileSystem.h>
+#include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
#if TVM_LLVM_VERSION >= 180
#include <llvm/TargetParser/Host.h>
#else
@@ -54,10 +46,10 @@
#include <llvm/Support/SourceMgr.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Target/TargetMachine.h>
-#include <llvm/Target/TargetOptions.h>
#include <llvm/Transforms/Utils/Cloning.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/container/array.h>
+#include <tvm/ffi/container/variant.h>
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/string.h>
@@ -67,14 +59,10 @@
#include <tvm/target/codegen.h>
#include <tvm/target/target.h>
-#include <algorithm>
#include <memory>
-#include <mutex>
-#include <sstream>
#include <string>
#include <system_error>
#include <utility>
-#include <vector>
#include "../../runtime/file_utils.h"
#include "codegen_blob.h"
@@ -91,12 +79,12 @@ using ffi::PackedArgs;
class LLVMModuleNode final : public ffi::ModuleObj {
public:
- ~LLVMModuleNode();
-
const char* kind() const final { return "llvm"; }
ffi::Optional<ffi::Function> GetFunction(const ffi::String& name) final;
+ void ImportModule(const ffi::Module& other) final;
+
/*! \brief Get the property of the runtime module .*/
// TODO(tvm-team): Make it serializable
int GetPropertyMask() const override {
@@ -111,50 +99,22 @@ class LLVMModuleNode final : public ffi::ModuleObj {
void Init(std::unique_ptr<llvm::Module> module,
std::unique_ptr<LLVMInstance> llvm_instance);
void LoadIR(const std::string& file_name);
- bool ImplementsFunction(const ffi::String& name) final;
-
- void SetJITEngine(const std::string& jit_engine) { jit_engine_ = jit_engine;
}
-
private:
- void InitMCJIT();
- void InitORCJIT();
- bool IsCompatibleWithHost(const llvm::TargetMachine* tm) const;
- void* GetGlobalAddr(const std::string& name, const LLVMTarget& llvm_target)
const;
- void* GetFunctionAddr(const std::string& name, const LLVMTarget&
llvm_target) const;
+ void EnsureOrcJITModule();
// The LLVM scope object.
std::unique_ptr<LLVMInstance> llvm_instance_;
- // JIT lock
- std::mutex mutex_;
- // jit execution engines
- llvm::ExecutionEngine* mcjit_ee_{nullptr};
- std::unique_ptr<llvm::orc::LLJIT> orcjit_ee_{nullptr};
- // The raw pointer to the module.
- llvm::Module* module_{nullptr};
- // The unique_ptr owning the module. This becomes empty once JIT has been
initialized
- // (EngineBuilder takes ownership of the module).
- std::unique_ptr<llvm::Module> module_owning_ptr_;
+ // JIT execution is provided by the separately installed
apache-tvm-ffi-orcjit package. TVM
+ // only emits the retained LLVM module as object-file bytes and transfers
them to the package.
+ // Dropping this reference releases the package JITDylib; a returned package
Function retains
+ // that dylib independently when the function outlives this LLVMModule.
+ ffi::Optional<ffi::Module> tvm_ffi_orcjit_module_;
+ // The retained module IR used for inspection and object emission.
+ std::unique_ptr<llvm::Module> module_;
/* \brief names of the external functions declared in this module */
ffi::Array<ffi::String> function_names_;
- std::string jit_engine_;
};
-LLVMModuleNode::~LLVMModuleNode() {
- if (mcjit_ee_ != nullptr) {
- mcjit_ee_->runStaticConstructorsDestructors(true);
- delete mcjit_ee_;
- }
- if (orcjit_ee_ != nullptr) {
- auto dtors = llvm::orc::getDestructors(*module_);
- auto dtorRunner =
std::make_unique<llvm::orc::CtorDtorRunner>(orcjit_ee_->getMainJITDylib());
- dtorRunner->add(dtors);
- auto err = dtorRunner->run();
- TVM_FFI_ICHECK(!err) << llvm::toString(std::move(err));
- orcjit_ee_.reset();
- }
- module_owning_ptr_.reset();
-}
-
ffi::Optional<ffi::Function> LLVMModuleNode::GetFunction(const ffi::String&
name) {
ffi::ObjectPtr<ffi::Object> sptr_to_self =
ffi::GetObjectPtr<ffi::Object>(this);
if (name == "__tvm_is_system_module") {
@@ -181,29 +141,23 @@ ffi::Optional<ffi::Function>
LLVMModuleNode::GetFunction(const ffi::String& name
return ffi::Function(
[target_string](ffi::PackedArgs args, ffi::Any* rv) { *rv =
target_string; });
}
- TVM_FFI_ICHECK(jit_engine_.size()) << "JIT engine type is missing";
- if ((jit_engine_ == "mcjit") && (mcjit_ee_ == nullptr)) InitMCJIT();
- if ((jit_engine_ == "orcjit") && (orcjit_ee_ == nullptr)) InitORCJIT();
-
- std::lock_guard<std::mutex> lock(mutex_);
+ EnsureOrcJITModule();
+ return tvm_ffi_orcjit_module_.value()->GetFunction(name);
+}
- TVMFFISafeCallType faddr;
- With<LLVMTarget> llvm_target(*llvm_instance_,
LLVMTarget::GetTargetMetadata(*module_));
- ffi::String name_with_prefix = ffi::symbol::tvm_ffi_symbol_prefix + name;
- faddr =
reinterpret_cast<TVMFFISafeCallType>(GetFunctionAddr(name_with_prefix,
*llvm_target));
- if (faddr == nullptr) return std::nullopt;
- ffi::Module self_strong_ref = ffi::GetRef<ffi::Module>(this);
- return ffi::Function::FromPacked([faddr, self_strong_ref](ffi::PackedArgs
args, ffi::Any* rv) {
- TVM_FFI_ICHECK_LT(rv->type_index(),
ffi::TypeIndex::kTVMFFIStaticObjectBegin);
- TVM_FFI_CHECK_SAFE_CALL((*faddr)(nullptr, reinterpret_cast<const
TVMFFIAny*>(args.data()),
- args.size(),
reinterpret_cast<TVMFFIAny*>(rv)));
- });
+void LLVMModuleNode::ImportModule(const ffi::Module& other) {
+ ffi::ModuleObj::ImportModule(other);
+ if (tvm_ffi_orcjit_module_.has_value()) {
+ tvm_ffi_orcjit_module_.value()->ImportModule(other);
+ }
}
namespace {
constexpr auto llvm_open_output_flag = llvm::sys::fs::OF_None;
-std::unique_ptr<llvm::Module> CloneLLVMModule(llvm::Module* mod) { return
llvm::CloneModule(*mod); }
+std::unique_ptr<llvm::Module> CloneLLVMModule(const llvm::Module* mod) {
+ return llvm::CloneModule(*mod);
+}
#if TVM_LLVM_VERSION <= 170
constexpr auto llvm_object_file_target = llvm::CGFT_ObjectFile;
@@ -214,7 +168,7 @@ constexpr auto llvm_assembly_file_target =
llvm::CodeGenFileType::AssemblyFile;
#endif
bool LLVMAddPassesToEmitFile(llvm::TargetMachine* tm,
llvm::legacy::PassManager* pm,
- llvm::raw_fd_ostream* dest,
+ llvm::raw_pwrite_stream* dest,
decltype(llvm_object_file_target)
llvm_file_target) {
return tm->addPassesToEmitFile(*pm, *dest, nullptr, llvm_file_target);
}
@@ -242,7 +196,7 @@ void LLVMModuleNode::WriteToFile(const ffi::String&
file_name_str,
auto err = LLVMAddPassesToEmitFile(tm, &pass, &dest, llvm_file_target);
TVM_FFI_ICHECK(!err) << "Cannot emit target CGFT_ObjectFile";
- pass.run(*CloneLLVMModule(module_));
+ pass.run(*CloneLLVMModule(module_.get()));
} else if (fmt == "ll") {
module_->print(dest, nullptr);
} else if (fmt == "bc") {
@@ -332,10 +286,8 @@ void LLVMModuleNode::Init(const IRModule& mod, const
Target& target) {
cg->AddMainFunction(entry_func);
}
- module_owning_ptr_ = cg->Finish();
- module_ = module_owning_ptr_.get();
- jit_engine_ = llvm_target->GetJITEngine();
- llvm_target->SetTargetMetadata(module_);
+ module_ = cg->Finish();
+ llvm_target->SetTargetMetadata(module_.get());
module_->addModuleFlag(llvm::Module::Override, "Debug Info Version",
llvm::DEBUG_METADATA_VERSION);
@@ -351,8 +303,7 @@ void LLVMModuleNode::Init(const IRModule& mod, const
Target& target) {
void LLVMModuleNode::Init(std::unique_ptr<llvm::Module> module,
std::unique_ptr<LLVMInstance> llvm_instance) {
- module_owning_ptr_ = std::move(module);
- module_ = module_owning_ptr_.get();
+ module_ = std::move(module);
llvm_instance_ = std::move(llvm_instance);
}
@@ -362,264 +313,53 @@ void LLVMModuleNode::LoadIR(const std::string&
file_name) {
Init(std::move(module), std::move(llvm_instance));
}
-bool LLVMModuleNode::ImplementsFunction(const ffi::String& name) {
- return std::find(function_names_.begin(), function_names_.end(),
- ffi::symbol::tvm_ffi_symbol_prefix + name) !=
function_names_.end();
-}
-
-void LLVMModuleNode::InitMCJIT() {
- std::lock_guard<std::mutex> lock(mutex_);
- if (mcjit_ee_) {
+void LLVMModuleNode::EnsureOrcJITModule() {
+ if (tvm_ffi_orcjit_module_.has_value()) {
return;
}
- // MCJIT builder
- With<LLVMTarget> llvm_target(*llvm_instance_,
LLVMTarget::GetTargetMetadata(*module_));
- llvm::EngineBuilder builder(std::move(module_owning_ptr_));
+ ffi::Optional<ffi::Function> get_default_session =
+ ffi::Function::GetGlobal("tvm_ffi_orcjit.GlobalDefaultSession");
+ ffi::Optional<ffi::Function> load_module =
+ ffi::Function::GetGlobal("tvm_ffi_orcjit.SessionLoadModule");
+ TVM_FFI_CHECK(get_default_session.has_value() && load_module.has_value(),
InternalError)
+ << "LLVMModule execution requires the separately installed
apache-tvm-ffi-orcjit "
+ "package and its global execution-session functions. "
+ "Install it with `pip install apache-tvm-ffi-orcjit==0.1.2`.";
- // set options
- builder.setEngineKind(llvm::EngineKind::JIT);
-#if TVM_LLVM_VERSION <= 170
- builder.setOptLevel(llvm::CodeGenOpt::Aggressive);
-#else
- builder.setOptLevel(llvm::CodeGenOptLevel::Aggressive);
-#endif
- builder.setMCPU(llvm_target->GetCPU());
- builder.setMAttrs(llvm_target->GetTargetFeatures());
- builder.setTargetOptions(llvm_target->GetTargetOptions());
-
- // create the taget machine
- auto tm = std::unique_ptr<llvm::TargetMachine>(builder.selectTarget());
- if (!IsCompatibleWithHost(tm.get())) {
- TVM_FFI_THROW(InternalError) << "Cannot run module, architecture mismatch";
- }
-
- // data layout
- llvm::DataLayout layout(tm->createDataLayout());
- TVM_FFI_ICHECK(layout == module_->getDataLayout())
- << "Data layout mismatch between module("
- << module_->getDataLayout().getStringRepresentation() << ")"
- << " and ExecutionEngine (" << layout.getStringRepresentation() << ")";
-
- // create MCJIT
- mcjit_ee_ = builder.create(tm.release());
- TVM_FFI_ICHECK(mcjit_ee_ != nullptr) << "Failed to initialize LLVM MCJIT
engine for "
-#if TVM_LLVM_VERSION >= 210
- << module_->getTargetTriple().str();
-#else
- << module_->getTargetTriple();
-#endif
-
- VLOG(2) << "LLVM MCJIT execute " << module_->getModuleIdentifier() << " for
triple `"
- << llvm_target->GetTargetTriple() << "`"
- << " on cpu `" << llvm_target->GetCPU() << "`";
-
- // run ctors
- mcjit_ee_->runStaticConstructorsDestructors(false);
-
- if (void** ctx_addr =
-
reinterpret_cast<void**>(GetGlobalAddr(ffi::symbol::tvm_ffi_library_ctx,
*llvm_target))) {
- *ctx_addr = this;
- }
-
- ffi::Module::VisitContextSymbols([this, &llvm_target](const ffi::String&
name, void* symbol) {
- if (void** ctx_addr = reinterpret_cast<void**>(GetGlobalAddr(name,
*llvm_target))) {
- *ctx_addr = symbol;
- }
- });
- // There is a problem when a JITed function contains a call to a runtime
function.
- // The runtime function (e.g. __truncsfhf2) may not be resolved, and calling
it will
- // lead to a runtime crash.
- // Do name lookup on a symbol that doesn't exist. This will force MCJIT to
finalize
- // all loaded objects, which will resolve symbols in JITed code.
- mcjit_ee_->getFunctionAddress(
- "__some_name_that_hopefully_doesnt_exist__b49f8aaade5877eaba7583b91");
-}
-
-void LLVMModuleNode::InitORCJIT() {
- std::lock_guard<std::mutex> lock(mutex_);
- if (orcjit_ee_) {
- return;
- }
- // ORCJIT builder
With<LLVMTarget> llvm_target(*llvm_instance_,
LLVMTarget::GetTargetMetadata(*module_));
- llvm::orc::JITTargetMachineBuilder
tm_builder(llvm::Triple(llvm_target->GetTargetTriple()));
-
- // set options
- tm_builder.setCPU(llvm_target->GetCPU());
- tm_builder.setFeatures(llvm_target->GetTargetFeatureString());
- tm_builder.setOptions(llvm_target->GetTargetOptions());
-#if TVM_LLVM_VERSION <= 170
- tm_builder.setCodeGenOptLevel(llvm::CodeGenOpt::Aggressive);
-#else
- tm_builder.setCodeGenOptLevel(llvm::CodeGenOptLevel::Aggressive);
-#endif
-
- // Default is no explicit JIT code & reloc model
- // Propagate instance code & reloc for RISCV case.
- auto arch = tm_builder.getTargetTriple().getArch();
- if (arch == llvm::Triple::riscv32 || arch == llvm::Triple::riscv64) {
- tm_builder.setRelocationModel(llvm_target->GetTargetRelocModel());
- tm_builder.setCodeModel(llvm_target->GetTargetCodeModel());
- }
-
- // create the taget machine
- std::unique_ptr<llvm::TargetMachine> tm =
llvm::cantFail(tm_builder.createTargetMachine());
- if (!IsCompatibleWithHost(tm.get())) {
- TVM_FFI_THROW(InternalError) << "Cannot run module, architecture mismatch";
- }
+ llvm::TargetMachine* tm = llvm_target->GetOrCreateTargetMachine();
- // data layout
- ffi::String module_name = module_->getModuleIdentifier();
llvm::DataLayout layout(tm->createDataLayout());
TVM_FFI_ICHECK(layout == module_->getDataLayout())
<< "Data layout mismatch between module("
<< module_->getDataLayout().getStringRepresentation() << ")"
- << " and ExecutionEngine (" << layout.getStringRepresentation() << ")";
-
- // compiler
- const auto compilerBuilder = [&](const llvm::orc::JITTargetMachineBuilder&)
- ->
llvm::Expected<std::unique_ptr<llvm::orc::IRCompileLayer::IRCompiler>> {
- return std::make_unique<llvm::orc::TMOwningSimpleCompiler>(std::move(tm));
- };
-
- // linker
- const auto linkerBuilder =
-#if TVM_LLVM_VERSION >= 230
- [&](llvm::orc::ExecutionSession& session,
llvm::jitlink::JITLinkMemoryManager& mem_mgr)
- -> llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>> {
-#elif TVM_LLVM_VERSION >= 210
- [&](llvm::orc::ExecutionSession& session)
- -> llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>> {
-#else
- [&](llvm::orc::ExecutionSession& session,
- const llvm::Triple& triple) ->
std::unique_ptr<llvm::orc::ObjectLayer> {
-#endif
-#if _WIN32
-#if TVM_LLVM_VERSION >= 210
- auto GetMemMgr = [](const llvm::MemoryBuffer&) {
- return std::make_unique<llvm::SectionMemoryManager>();
- };
-#else
- auto GetMemMgr = []() { return
std::make_unique<llvm::SectionMemoryManager>(); };
-#endif
- auto ObjLinkingLayer =
- std::make_unique<llvm::orc::RTDyldObjectLinkingLayer>(session,
std::move(GetMemMgr));
-#else
-#if TVM_LLVM_VERSION >= 230
- auto ObjLinkingLayer =
std::make_unique<llvm::orc::ObjectLinkingLayer>(session, mem_mgr);
-#else
- auto ObjLinkingLayer =
std::make_unique<llvm::orc::ObjectLinkingLayer>(session);
-#endif
-#endif
-#if TVM_LLVM_VERSION >= 210
- if (tm_builder.getTargetTriple().isOSBinFormatCOFF()) {
-#else
- if (triple.isOSBinFormatCOFF()) {
-#endif
- ObjLinkingLayer->setOverrideObjectFlagsWithResponsibilityFlags(true);
- ObjLinkingLayer->setAutoClaimResponsibilityForObjectSymbols(true);
- }
-#if TVM_LLVM_VERSION >= 210
- return
llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>>(std::move(ObjLinkingLayer));
-#else
- return ObjLinkingLayer;
-#endif
- }; // NOLINT(readability/braces)
-
- // create LLJIT
- orcjit_ee_ = llvm::cantFail(llvm::orc::LLJITBuilder()
- .setDataLayout(layout)
- .setCompileFunctionCreator(compilerBuilder)
- .setObjectLinkingLayerCreator(linkerBuilder)
- .create());
-
- TVM_FFI_ICHECK(orcjit_ee_ != nullptr) << "Failed to initialize LLVM ORCJIT
engine for "
-#if TVM_LLVM_VERSION >= 210
- << module_->getTargetTriple().str();
-#else
- << module_->getTargetTriple();
-#endif
-
- // store ctors
- auto ctors = llvm::orc::getConstructors(*module_);
- llvm::orc::CtorDtorRunner ctorRunner(orcjit_ee_->getMainJITDylib());
- ctorRunner.add(ctors);
-
- // resolve system symbols (like pthread, dl, m, etc.)
- auto gen =
-
llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess(layout.getGlobalPrefix());
- TVM_FFI_ICHECK(gen) << llvm::toString(gen.takeError()) << "\n";
- orcjit_ee_->getMainJITDylib().addGenerator(std::move(gen.get()));
-
- // transfer module to a clone
- auto uctx = std::make_unique<llvm::LLVMContext>();
- auto umod = llvm::CloneModule(*(std::move(module_owning_ptr_)));
-
- // add the llvm module to run
- llvm::orc::ThreadSafeModule tsm(std::move(umod), std::move(uctx));
- auto err = orcjit_ee_->addIRModule(std::move(tsm));
- TVM_FFI_ICHECK(!err) << llvm::toString(std::move(err));
-
- VLOG(2) << "LLVM ORCJIT execute " << module_->getModuleIdentifier() << " for
triple `"
- << llvm_target->GetTargetTriple() << "`"
- << " on cpu `" << llvm_target->GetCPU() << "`";
-
- // run ctors
- err = ctorRunner.run();
- TVM_FFI_ICHECK(!err) << llvm::toString(std::move(err));
-
- if (void** ctx_addr =
-
reinterpret_cast<void**>(GetGlobalAddr(ffi::symbol::tvm_ffi_library_ctx,
*llvm_target))) {
- *ctx_addr = this;
+ << " and JIT target machine (" << layout.getStringRepresentation() <<
")";
+
+ std::unique_ptr<llvm::Module> object_module = CloneLLVMModule(module_.get());
+
+ llvm::SmallString<0> object;
+ llvm::raw_svector_ostream object_stream(object);
+ llvm::legacy::PassManager pass;
+ TVM_FFI_ICHECK(!LLVMAddPassesToEmitFile(tm, &pass, &object_stream,
llvm_object_file_target))
+ << "Cannot emit LLVM object for apache-tvm-ffi-orcjit";
+ pass.run(*object_module);
+
+ ffi::ObjectRef session =
get_default_session.value()().cast<ffi::ObjectRef>();
+ ffi::Array<ffi::Variant<ffi::String, ffi::Bytes>> objects = {
+ ffi::Bytes(object.data(), object.size())};
+ ffi::Module dylib = load_module
+ .value()(session, objects, ffi::String(""),
ffi::Optional<ffi::String>{},
+ ffi::Optional<ffi::String>{})
+ .cast<ffi::Module>();
+ for (const ffi::Any& imported_module : imports()) {
+ dylib->ImportModule(imported_module.cast<ffi::Module>());
}
- ffi::Module::VisitContextSymbols([this, &llvm_target](const ffi::String&
name, void* symbol) {
- if (void** ctx_addr = reinterpret_cast<void**>(GetGlobalAddr(name,
*llvm_target))) {
- *ctx_addr = symbol;
- }
- });
-}
-
-bool LLVMModuleNode::IsCompatibleWithHost(const llvm::TargetMachine* tm) const
{
- LLVMTargetInfo host_target(*llvm_instance_, "llvm");
- auto tm_host = host_target.GetOrCreateTargetMachine();
- if (tm_host->getTargetTriple().getArch() != tm->getTargetTriple().getArch())
{
- LOG(INFO) << "Architecture mismatch: module=" <<
tm->getTargetTriple().str()
- << " host=" << tm_host->getTargetTriple().str();
- return false;
- }
- return true;
-}
-// Get global address from execution engine.
-void* LLVMModuleNode::GetGlobalAddr(const std::string& name, const LLVMTarget&
llvm_target) const {
- // first verifies if GV exists.
- if (module_->getGlobalVariable(name) != nullptr) {
- if (jit_engine_ == "mcjit") {
- return reinterpret_cast<void*>(mcjit_ee_->getGlobalValueAddress(name));
- } else if (jit_engine_ == "orcjit") {
- auto addr = llvm::cantFail(orcjit_ee_->lookup(name)).getValue();
- return reinterpret_cast<void*>(addr);
- } else {
- TVM_FFI_THROW(InternalError) << "Either `mcjit` or `orcjit` are not
initialized.";
- }
- }
- return nullptr;
-}
+ tvm_ffi_orcjit_module_ = dylib;
-void* LLVMModuleNode::GetFunctionAddr(const std::string& name,
- const LLVMTarget& llvm_target) const {
- // first verifies if GV exists.
- if (module_->getFunction(name) != nullptr) {
- if (jit_engine_ == "mcjit") {
- return reinterpret_cast<void*>(mcjit_ee_->getFunctionAddress(name));
- } else if (jit_engine_ == "orcjit") {
- auto addr = llvm::cantFail(orcjit_ee_->lookup(name)).getValue();
- return reinterpret_cast<void*>(addr);
- } else {
- TVM_FFI_THROW(InternalError) << "Either `mcjit` or `orcjit` are not
initialized.";
- }
- }
- return nullptr;
+ VLOG(2) << "apache-tvm-ffi-orcjit execute " <<
module_->getModuleIdentifier() << " for triple `"
+ << llvm_target->GetTargetTriple() << "` on cpu `" <<
llvm_target->GetCPU()
+ << "` with features `" << llvm_target->GetTargetFeatureString() <<
"`";
}
static void LLVMReflectionRegister() {
@@ -646,7 +386,6 @@ static void LLVMReflectionRegister() {
#endif
module->setDataLayout(llvm_target->GetOrCreateTargetMachine()->createDataLayout());
n->Init(std::move(module), std::move(llvm_instance));
- n->SetJITEngine(llvm_target->GetJITEngine());
return ffi::Module(n);
})
.def("target.llvm_lookup_intrinsic_id",
@@ -763,7 +502,6 @@ static void LLVMReflectionRegister() {
.def("ffi.Module.load_from_file.ll",
[](std::string filename, std::string fmt) -> ffi::Module {
auto n = ffi::make_object<LLVMModuleNode>();
- n->SetJITEngine("orcjit");
n->LoadIR(filename);
return ffi::Module(n);
})
@@ -783,7 +521,6 @@ static void LLVMReflectionRegister() {
std::unique_ptr<llvm::Module> blob =
CodeGenBlob(data, system_lib, llvm_target.get(),
c_symbol_prefix);
n->Init(std::move(blob), std::move(llvm_instance));
- n->SetJITEngine(llvm_target->GetJITEngine());
return ffi::Module(n);
});
}
diff --git a/src/target/target_kind.cc b/src/target/target_kind.cc
index 93c66869dc..0e326f6a6f 100644
--- a/src/target/target_kind.cc
+++ b/src/target/target_kind.cc
@@ -144,8 +144,6 @@ TVM_FFI_STATIC_INIT_BLOCK() {
.def_option<int64_t>("opt-level")
// LLVM command line flags, see below
.def_option<ffi::Array<ffi::String>>("cl-opt")
- // LLVM JIT engine mcjit/orcjit
- .def_option<ffi::String>("jit")
// TVM & LLVM custom vector bit width
.def_option<int64_t>("vector-width")
.set_default_keys({"cpu"})
diff --git a/tests/python/runtime/test_runtime_module_load.py
b/tests/python/runtime/test_runtime_module_load.py
index ee5500fd1b..91479f72b3 100644
--- a/tests/python/runtime/test_runtime_module_load.py
+++ b/tests/python/runtime/test_runtime_module_load.py
@@ -45,8 +45,7 @@ print("Finish runtime checking...")
@pytest.mark.skipif(not env.has_llvm(), reason="need llvm")
[email protected]("target", ["llvm", {"kind": "llvm", "jit": "mcjit"}])
-def test_dso_module_load(target):
+def test_dso_module_load():
dtype = "int64"
temp = utils.tempdir()
@@ -65,7 +64,7 @@ def test_dso_module_load(target):
mod = tvm.IRModule.from_expr(
tvm.tirx.PrimFunc([Ab], stmt).with_attr("global_symbol", "main")
)
- m = tvm.tirx.build(mod, target=target)
+ m = tvm.tirx.build(mod, target="llvm")
for name in names:
m.write_to_file(name)
diff --git a/tests/python/target/test_target_target.py
b/tests/python/target/test_target_target.py
index 73074b72ea..aa81d3be8d 100644
--- a/tests/python/target/test_target_target.py
+++ b/tests/python/target/test_target_target.py
@@ -98,13 +98,6 @@ def test_target_llvm_options():
)
-def test_target_llvm_jit_options():
- target = tvm.target.Target({"kind": "llvm", "jit": "mcjit"})
- assert target.attrs["jit"] == "mcjit"
- target = tvm.target.Target({"kind": "llvm", "jit": "orcjit"})
- assert target.attrs["jit"] == "orcjit"
-
-
def test_target_llvm_vector_width():
target = tvm.target.Target({"kind": "llvm", "vector-width": 256})
assert target.attrs["vector-width"] == 256
diff --git a/tests/scripts/task_python_docs.sh
b/tests/scripts/task_python_docs.sh
index 11b2501b7b..e45d439929 100755
--- a/tests/scripts/task_python_docs.sh
+++ b/tests/scripts/task_python_docs.sh
@@ -132,6 +132,9 @@ find . -type f -path "*.pyc" | xargs rm -f
# setup tvm-ffi into python folder
uv pip install -v --target=python ./3rdparty/tvm-ffi/
+# LLVMModule JIT execution, needed by the tutorials this build runs.
+uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
+
cd docs
PYTHONPATH=$(pwd)/../python make htmldepoly SPHINXOPTS='-j auto' |& tee
/tmp/$$.log.txt
diff --git a/tests/scripts/task_python_unittest.sh
b/tests/scripts/task_python_unittest.sh
index 524a9f2a2e..511b3ec2a5 100755
--- a/tests/scripts/task_python_unittest.sh
+++ b/tests/scripts/task_python_unittest.sh
@@ -24,4 +24,9 @@ export PYTEST_ADDOPTS="${CI_PYTEST_ADD_OPTIONS:-}
${PYTEST_ADDOPTS:-}"
# setup tvm-ffi into python folder
uv pip install -v --target=python ./3rdparty/tvm-ffi/
+# LLVMModule JIT execution. Installed here as well as in the CI image because
a docker/
+# change does not reach the test containers in the same run (they use the
pinned tag).
+# --no-deps keeps the submodule-built tvm-ffi above from being replaced by a
PyPI build.
+uv pip install --no-deps apache-tvm-ffi-orcjit==0.1.2
+
python3 -m pytest -vvs -n auto -m "${TVM_TEST_MARKER:-not gpu}" tests/python