This is an automated email from the ASF dual-hosted git repository.
cyx-6 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 42455855 [BUILD] Upgrade ORC JIT to LLVM 23.1.1 (#810)
42455855 is described below
commit 424558557140a6eb07e1e1ee4dd81f60574e1281
Author: Yaxing Cai <[email protected]>
AuthorDate: Thu Sep 24 00:50:22 2026 +0800
[BUILD] Upgrade ORC JIT to LLVM 23.1.1 (#810)
Builds on the ORCJIT loading and lifecycle hardening merged in #809.
## Why
Upgrade the ORC JIT addon from LLVM 22.1.0 to LLVM 23.1.1 and use the
upstream fixes to reduce local compatibility code.
The implementation was derived from a direct comparison of
`llvmorg-22.1.0` and `llvmorg-23.1.1`:
- LLJIT now owns its `JITLinkMemoryManager`; the object-layer creator
receives that manager by reference.
- dynamic-library generators now use LLJIT's `DylibManager`.
- LLVM 23 ELFNixPlatform registers and runs ELF fini sections, so Linux
can use `LLJIT::initialize` / `deinitialize` instead of the local
init/fini plugin.
- LLVM 23 fixes the x86-64 GOTPCRELX relaxation to use signed
PC-relative reachability and `BranchPCRel32`, so the local
`GOTPCRELXFixPlugin` is deleted.
The source comparison also showed that GenericLLVMIRPlatform was already
LLJIT's default in LLVM 22.1.0. This PR therefore preserves generic
platform support on macOS and Windows, including unwind registration,
rather than treating it as an LLVM 23 behavior change.
## Changes
- pin builds and CI to LLVM 23.1.1;
- adapt the LLJIT memory-manager, object-layer, and dylib-generator
APIs;
- move Linux lifecycle handling to LLVM 23 ELFNixPlatform;
- keep batched slab deallocation in reverse allocation order, matching
LLVM's in-process manager;
- remove the upstreamed GOTPCRELX workaround;
- retain the existing Mach-O and COFF lifecycle adapters only where
native platforms are not yet usable.
## Validation
- macOS arm64, LLVM 23.1.1: `136 passed, 54 skipped`.
- repository pre-commit suite: all hooks passed.
- Linux x86-64, Linux aarch64, Windows, and clean wheel validation are
delegated to CI.
---
.github/actions/build-orcjit-wheel/action.yml | 2 +-
.github/workflows/ci_test.yml | 2 +-
addons/tvm_ffi_orcjit/CMakeLists.txt | 30 ++--
addons/tvm_ffi_orcjit/ORCJIT_PRIMER.md | 41 +++---
addons/tvm_ffi_orcjit/README.md | 17 ++-
.../python/tvm_ffi_orcjit/session.py | 7 +-
.../tvm_ffi_orcjit/src/ffi/llvm_patches/README.md | 11 --
.../src/ffi/llvm_patches/gotpcrelx_fix.cc | 162 ---------------------
.../src/ffi/llvm_patches/gotpcrelx_fix.h | 92 ------------
.../src/ffi/llvm_patches/init_fini_plugin.cc | 64 +-------
.../src/ffi/llvm_patches/init_fini_plugin.h | 52 +++----
.../src/ffi/llvm_patches/macho_cxa_atexit_shim.cc | 12 +-
.../src/ffi/llvm_patches/macho_cxa_atexit_shim.h | 18 +--
addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc | 49 +++++--
addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h | 3 +
.../src/ffi/orcjit_memory_manager.cc | 8 +-
.../tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.h | 10 +-
addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc | 98 +++++++------
addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h | 28 ++--
.../tests/sources/cc/test_ctor_dtor.cc | 8 +
addons/tvm_ffi_orcjit/tests/test_basic.py | 10 ++
.../tests/test_library_visibility.py | 6 +
.../tests/test_session_load_module.py | 2 +-
include/tvm/ffi/base_details.h | 1 +
24 files changed, 234 insertions(+), 499 deletions(-)
diff --git a/.github/actions/build-orcjit-wheel/action.yml
b/.github/actions/build-orcjit-wheel/action.yml
index 01ff4c2a..d3d6195c 100644
--- a/.github/actions/build-orcjit-wheel/action.yml
+++ b/.github/actions/build-orcjit-wheel/action.yml
@@ -32,7 +32,7 @@ inputs:
required: true
llvm_version:
description: "LLVM version (selects the release tag / conda pin)"
- default: "22.1.0"
+ default: "23.1.1"
required: false
llvm_release_repo:
description: >
diff --git a/.github/workflows/ci_test.yml b/.github/workflows/ci_test.yml
index 7d3d6a2e..2db75128 100644
--- a/.github/workflows/ci_test.yml
+++ b/.github/workflows/ci_test.yml
@@ -236,7 +236,7 @@ jobs:
name: orcjit ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ${{ matrix.os }}
env:
- LLVM_VERSION: "22.1.0"
+ LLVM_VERSION: "23.1.1"
# Repo hosting the prebuilt LLVM release assets consumed on Linux. Built
# out-of-band from source in a manylinux image so the ABI stays under the
# manylinux_2_28 floor; see that repo's build_llvm workflow.
diff --git a/addons/tvm_ffi_orcjit/CMakeLists.txt
b/addons/tvm_ffi_orcjit/CMakeLists.txt
index 9ced5a16..4edf342e 100644
--- a/addons/tvm_ffi_orcjit/CMakeLists.txt
+++ b/addons/tvm_ffi_orcjit/CMakeLists.txt
@@ -22,8 +22,8 @@ endif ()
# ---- Find packages ----
find_package(LLVM REQUIRED CONFIG)
-if (LLVM_PACKAGE_VERSION VERSION_LESS 22.0)
- message(FATAL_ERROR "tvm_ffi_orcjit requires LLVM 22 or newer; found
${LLVM_PACKAGE_VERSION}")
+if (LLVM_PACKAGE_VERSION VERSION_LESS 23.1.1)
+ message(FATAL_ERROR "tvm_ffi_orcjit requires LLVM 23.1.1 or newer; found
${LLVM_PACKAGE_VERSION}")
endif ()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION} in ${LLVM_DIR}")
@@ -41,17 +41,21 @@ find_package(tvm_ffi CONFIG REQUIRED)
# ---- Build shared library ----
add_library(
- tvm_ffi_orcjit SHARED
- src/ffi/orcjit_session.cc
- src/ffi/orcjit_dylib.cc
- src/ffi/orcjit_memory_manager.cc
- src/ffi/orcjit_slab.cc
- src/ffi/llvm_patches/gotpcrelx_fix.cc
- src/ffi/llvm_patches/init_fini_plugin.cc
- src/ffi/llvm_patches/macho_cxa_atexit_shim.cc
- src/ffi/llvm_patches/win_coff_pdata_strip.cc
- src/ffi/llvm_patches/win_dll_import_generator.cc
+ tvm_ffi_orcjit SHARED src/ffi/orcjit_session.cc src/ffi/orcjit_dylib.cc
+ src/ffi/orcjit_memory_manager.cc src/ffi/orcjit_slab.cc
)
+if (APPLE)
+ target_sources(
+ tvm_ffi_orcjit PRIVATE src/ffi/llvm_patches/init_fini_plugin.cc
+ src/ffi/llvm_patches/macho_cxa_atexit_shim.cc
+ )
+elseif (WIN32)
+ target_sources(
+ tvm_ffi_orcjit
+ PRIVATE src/ffi/llvm_patches/init_fini_plugin.cc
src/ffi/llvm_patches/win_coff_pdata_strip.cc
+ src/ffi/llvm_patches/win_dll_import_generator.cc
+ )
+endif ()
set_target_properties(
tvm_ffi_orcjit PROPERTIES CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
@@ -206,7 +210,7 @@ if (NOT APPLE AND NOT WIN32)
endif ()
endif ()
if (NOT ORC_RT_PATH)
- message(WARNING "Could not find liborc_rt. ORC runtime features will be
disabled.")
+ message(WARNING "Could not find liborc_rt. Linux ORC platform lifecycle
will be disabled.")
endif ()
endif ()
diff --git a/addons/tvm_ffi_orcjit/ORCJIT_PRIMER.md
b/addons/tvm_ffi_orcjit/ORCJIT_PRIMER.md
index 9ab675ee..2a19b31b 100644
--- a/addons/tvm_ffi_orcjit/ORCJIT_PRIMER.md
+++ b/addons/tvm_ffi_orcjit/ORCJIT_PRIMER.md
@@ -252,7 +252,9 @@ addObjectFile(buffer)
5. **Finalizes** by writing machine code into the allocated pages and marking
them
executable.
-The pass pipeline is where `tvm_ffi_orcjit`'s `InitFiniPlugin` does its work.
+The pass pipeline is where the addon's platform adapters and relocation fixes
do
+their work. `InitFiniPlugin` is attached only on macOS and Windows; Linux uses
+LLVM's upstream `ELFNixPlatform` lifecycle.
### 3.2 Symbol Lookup and Resolution
@@ -323,18 +325,22 @@ for the host OS and loads the ORC runtime from a given
path.
The addon takes a different approach on each platform:
-- **macOS**: the addon deliberately does not configure an ORC platform. It uses
- `InitFiniPlugin` for `__mod_init_func` / `__mod_term_func` and a per-dylib
- `__cxa_atexit` shim. This avoids a compact-unwind address-delta failure in
the
- current `MachOPlatform` integration. The `orc_rt` argument is ignored.
+- **macOS**: the addon retains LLJIT's generic platform support for unwind
+ registration, but does not configure `MachOPlatform`. It uses
`InitFiniPlugin`
+ for `__mod_init_func` / `__mod_term_func` and a per-dylib `__cxa_atexit`
shim.
+ This avoids a compact-unwind address-delta failure in the current
+ `MachOPlatform` integration. The `orc_rt` argument is ignored.
- **Windows**: `COFFPlatform` is skipped entirely because it requires MSVC CRT
symbols
(`_CxxThrowException`, RTTI vtables, iostream objects) that are not
resolvable in
- the JIT context. Instead, `InitFiniPlugin` manually handles `.CRT$XC*` /
`.CRT$XT*`
- init/fini sections. The `orc_rt` argument is ignored.
+ the JIT context. LLJIT's generic platform support plus `InitFiniPlugin`
+ manually handles `.CRT$XC*` / `.CRT$XT*` init/fini sections. The `orc_rt`
+ argument is ignored.
- **Linux**: the default embedded `liborc_rt` configures
`ExecutorNativePlatform`
(and therefore `ELFNixPlatform`). A custom archive may be supplied by path
or bytes,
- and `None` disables the platform. `InitFiniPlugin` still collects and
invokes ELF
- init/fini arrays to work around current upstream behavior.
+ and `None` disables the platform. On LLVM 23.1.1+, `LLJIT::initialize` and
+ `LLJIT::deinitialize` use the upstream platform for correctly ordered ELF
+ constructors, destructors, and `__cxa_atexit` handling. The explicit `None`
+ mode is intended for plain C-ABI objects that do not require that lifecycle.
---
@@ -373,17 +379,16 @@ between separate `load_module` results.
Several addon-specific pieces sit in this pipeline:
-- **`InitFiniPlugin`** — a JITLink pass plugin that keeps init/fini sections
- (`.init_array`/`.ctors`/`.fini_array`/`.dtors`, `__mod_init_func`,
`.CRT$XC*`)
- live, then collects their function pointers after fixup. The addon runs them
- in priority order at first lookup and at teardown, replacing the ORC
- platform's initializer machinery. See `llvm_patches/init_fini_plugin.h`.
+- **Linux ELF lifecycle** — LLVM 23.1.1+ `ELFNixPlatform` keeps and orders ELF
+ init/fini sections; the addon calls `LLJIT::initialize` after context wiring
+ and `LLJIT::deinitialize` before removing the JITDylib.
+- **`InitFiniPlugin` (macOS/Windows)** — keeps the Mach-O or COFF lifecycle
+ sections live, collects their function pointers after fixup, and runs them at
+ initialization/teardown while those native ORC platforms remain unsuitable.
- **Linux slab-pool memory manager** — reserves contiguous virtual-address
regions,
separates executable from non-executable allocations, recycles freed
regions, and
grows by adding slabs. This keeps 32-bit PC-relative relocations in range
and offers
explicit reclamation through `clear_free_slabs()`.
-- **GOTPCRELX correction (Linux/x86-64)** — repairs or reverses unsafe JITLink
- relaxations before fixup.
- **Windows DLL import stubs** — `DLLImportDefinitionGenerator` resolves
`__imp_XXX` references to host-DLL functions by emitting JIT-memory pointer +
trampoline stubs, keeping `PCRel32` fixups within ±2 GB of the JIT code. See
@@ -429,12 +434,12 @@ TVM_FFI_DLL_EXPORT_TYPED_FUNC(add, add_impl);
| --- | --- | --- |
| Object file | Container of machine code, data, symbols, relocations | Input
to `session.load_module()` |
| Relocation | Recipe to patch a code address at link/JIT time | Applied by
JITLink |
-| `.init_array` / `.ctors` | Array of C++ constructor pointers in ELF objects
| Collected by `InitFiniPlugin` |
+| `.init_array` / `.ctors` | Array of C++ constructor pointers in ELF objects
| Managed by LLVM 23.1.1+ `ELFNixPlatform` |
| `ExecutionSession` | Root of the ORC JIT environment |
`ORCJITExecutionSessionObj` |
| `LLJIT` | High-level ORC JIT wrapper | Stored in
`ORCJITExecutionSessionObj::jit_` |
| `JITDylib` | Symbol namespace / virtual shared library |
`ORCJITDynamicLibraryObj::dylib_` |
| `JITLink` | LLVM's JIT-aware linker | Used inside `ObjectLinkingLayer` |
-| JITLink pass pipeline | Pre-prune → post-alloc → post-fixup hooks | Where
`InitFiniPlugin` runs |
+| JITLink pass pipeline | Pre-prune → post-alloc → post-fixup hooks | Platform
adapters and relocation fixes |
| Slab pool | Contiguous, growable Linux JIT memory arena |
`SlabPoolMemoryManager` / `Slab` |
| `DefinitionGenerator` | Fallback symbol provider |
`DLLImportDefinitionGenerator` (Win) |
| Link order | Search path across JITDylibs for symbol resolution | LLJIT
default (Main → Platform → ProcessSymbols) |
diff --git a/addons/tvm_ffi_orcjit/README.md b/addons/tvm_ffi_orcjit/README.md
index 88ea74ff..4e5988d6 100644
--- a/addons/tvm_ffi_orcjit/README.md
+++ b/addons/tvm_ffi_orcjit/README.md
@@ -65,7 +65,7 @@ pip install apache-tvm-ffi apache-tvm_ffi_orcjit
#### Prerequisites
- Python 3.10+, CMake 3.20+, C++17 compiler
-- LLVM 22+ development libraries (`llvmdev`, `llvm-config`)
+- LLVM 23.1.1+ development libraries (`llvmdev`, `llvm-config`)
- Static `zlib` and `zstd` libraries (in the same prefix as LLVM)
#### Install LLVM via conda-forge
@@ -74,14 +74,14 @@ The easiest way to get all dependencies is via conda-forge:
```bash
conda create -p /opt/llvm -c conda-forge \
- llvmdev=22.1.0 clangdev=22.1.0 compiler-rt=22.1.0 zlib zstd-static -y
+ llvmdev=23.1.1 clangdev=23.1.1 compiler-rt=23.1.1 zlib zstd-static -y
export LLVM_PREFIX=/opt/llvm
```
On Windows:
```cmd
-conda create -p C:\opt\llvm -c conda-forge llvmdev=22.1.0 zlib zstd-static -y
+conda create -p C:\opt\llvm -c conda-forge llvmdev=23.1.1 zlib zstd-static -y
set LLVM_PREFIX=C:\opt\llvm
```
@@ -196,10 +196,11 @@ Compile: `clang -O2 -c -o example.o example.c`
- **LLJIT**: Built on LLVM's ORC JIT v2 with `ObjectLinkingLayer` (JITLink) for
all platforms.
-- **InitFiniPlugin**: Custom `ObjectLinkingLayer::Plugin` that collects
function
- pointers from init/fini sections (ELF
`.init_array`/`.ctors`/`.fini_array`/`.dtors`,
- Mach-O `__mod_init_func`/`__mod_term_func`, COFF `.CRT$XC*`/`.CRT$XT*`) and
- runs them in priority order at symbol lookup / library teardown.
+- **ELF Lifecycle** (Linux): LLVM 23.1.1+ `ELFNixPlatform` runs ordered
+ constructors and destructors through `LLJIT::initialize` / `deinitialize`.
+- **InitFiniPlugin** (macOS/Windows): A local `ObjectLinkingLayer::Plugin`
+ handles Mach-O `__mod_init_func`/`__mod_term_func` and COFF
+ `.CRT$XC*`/`.CRT$XT*` while those ORC platforms remain unsuitable.
- **DLL Import Stubs** (Windows): Custom `DefinitionGenerator` that resolves
host
process symbols from all loaded DLLs and creates `__imp_*` pointer stubs in
JIT memory, keeping all fixups within PCRel32 range.
@@ -260,7 +261,7 @@ checks (`__security_cookie`) which are CRT symbols the JIT
cannot resolve.
### LLVM version mismatch
-The package requires LLVM 22+. Set `LLVM_PREFIX` to the LLVM install prefix:
+The package requires LLVM 23.1.1+. Set `LLVM_PREFIX` to the LLVM install
prefix:
```bash
export LLVM_PREFIX=/path/to/llvm
diff --git a/addons/tvm_ffi_orcjit/python/tvm_ffi_orcjit/session.py
b/addons/tvm_ffi_orcjit/python/tvm_ffi_orcjit/session.py
index bc1c1c3a..ed345099 100644
--- a/addons/tvm_ffi_orcjit/python/tvm_ffi_orcjit/session.py
+++ b/addons/tvm_ffi_orcjit/python/tvm_ffi_orcjit/session.py
@@ -106,13 +106,14 @@ class ExecutionSession(Object):
Parameters
----------
orc_rt : str or Path or bytes or None
- Which ORC runtime to install. Linux/ELF only — ignored on macOS and
- Windows, which never configure an ORC platform.
+ Which native ORC runtime to install. Linux/ELF only — ignored on
+ macOS and Windows, which retain LLJIT's generic platform support.
- ``"auto"`` (default): the runtime embedded in this extension.
- a path (``str`` or ``Path``): a custom liborc_rt archive on disk.
- ``bytes``: a custom liborc_rt archive held in memory.
- - ``None``: no ORC platform at all.
+ - ``None``: no ORC platform or ELF constructor/destructor
lifecycle;
+ intended for plain C-ABI objects that do not require either.
A custom runtime (path or bytes) must match the LLVM/compiler-rt
this
extension was built against; ``"auto"`` is almost always what you
want.
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/README.md
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/README.md
index 2b43c8a4..54614a16 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/README.md
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/README.md
@@ -38,17 +38,6 @@ Each patch file opens with a fixed-shape header describing:
## Index
-- **GOTPCRELX relaxation** (`gotpcrelx_fix.{h,cc}`)
- LLVM issue: TBD — issue not yet filed.
- Upstream status: open.
- Remove when: LLVM floor bumps past the release that contains the fix.
-
-- **ELF init/fini** (Linux branch of `init_fini_plugin.{h,cc}`)
- LLVM issue:
[llvm/llvm-project#175981](https://github.com/llvm/llvm-project/issues/175981).
- Upstream status: merged; included in LLVM 23.1.1, not LLVM 22.1.0.
- Remove when: the LLVM floor reaches 23 and the addon switches its lifecycle
- calls to the upstream `ELFNixPlatform` path.
-
- **COFF ctor/dtor** (Windows branch of `init_fini_plugin.{h,cc}`)
LLVM issue: COFFPlatform stalled.
Upstream status: stalled 2+ years.
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/gotpcrelx_fix.cc
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/gotpcrelx_fix.cc
deleted file mode 100644
index 7f5593cb..00000000
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/gotpcrelx_fix.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*!
- * \file gotpcrelx_fix.cc
- * \brief LLVM JITLink GOTPCRELX relaxation bug workaround (x86_64).
- *
- * See gotpcrelx_fix.h for the trigger, symptom, and removal procedure.
- */
-#include "gotpcrelx_fix.h"
-
-#if defined(__linux__) && (defined(__x86_64__) || defined(_M_X64))
-
-#include <llvm/ADT/DenseMap.h>
-#include <llvm/ExecutionEngine/JITLink/JITLink.h>
-#include <llvm/ExecutionEngine/JITLink/x86_64.h>
-#include <llvm/Support/Error.h>
-#include <llvm/Support/MathExtras.h>
-
-#include <cstdint>
-#include <string>
-
-namespace tvm {
-namespace ffi {
-namespace orcjit {
-
-namespace {
-
-/*! \brief Correct broken GOTPCRELX relaxations produced by
- * optimizeGOTAndStubAccesses().
- *
- * Strategy:
- * 1. Build target-symbol → GOT-entry-symbol map (O(B+S) up front).
- * 2. For every Pointer32 edge whose preceding bytes are 67 e8
- * (relaxed call) or e9 (relaxed jmp):
- * - If the target is reachable via a signed 32-bit PC-relative
- * displacement, change the edge to BranchPCRel32.
- * - Otherwise revert the relaxation: restore the original
- * indirect-call/jmp opcode bytes (ff 15 / ff 25), retarget
- * the edge to the GOT entry, and use PCRel32 with addend 0
- * (JITLink normalises GOTPCRELX addends to 0).
- */
-llvm::Error fixBrokenGOTPCRELXRelaxation(llvm::jitlink::LinkGraph& G) {
- using namespace llvm::jitlink;
- // Build block → first symbol at offset 0 (for GOT entry symbol lookup).
- llvm::DenseMap<Block*, Symbol*> BlockToSym;
- for (auto* Sym : G.defined_symbols()) {
- if (Sym->getOffset() == 0 && !BlockToSym.count(&Sym->getBlock())) {
- BlockToSym[&Sym->getBlock()] = Sym;
- }
- }
-
- // Build target symbol → GOT entry symbol map.
- // GOT entries are pointer-sized blocks with exactly one Pointer64 edge.
- llvm::DenseMap<Symbol*, Symbol*> SymToGOTSym;
- for (auto* B : G.blocks()) {
- if (B->getSize() != G.getPointerSize()) continue;
- if (B->edges_size() != 1) continue;
- auto& E = *B->edges().begin();
- if (E.getKind() == x86_64::Pointer64) {
- auto It = BlockToSym.find(B);
- if (It != BlockToSym.end()) {
- SymToGOTSym[&E.getTarget()] = It->second;
- }
- }
- }
-
- for (auto* B : G.blocks()) {
- for (auto& E : B->edges()) {
- if (E.getKind() != x86_64::Pointer32) continue;
- if (E.getOffset() < 2) continue;
-
- auto MutableContent = B->getMutableContent(G);
- auto* FixupData = reinterpret_cast<uint8_t*>(MutableContent.data()) +
E.getOffset();
- uint8_t Prev2 = FixupData[-2];
- uint8_t Prev1 = FixupData[-1];
-
- bool isRelaxedCall = (Prev2 == 0x67 && Prev1 == 0xe8);
- bool isRelaxedJmp = (Prev1 == 0xe9);
- if (!isRelaxedCall && !isRelaxedJmp) continue;
-
- // Check if PC-relative displacement would fit.
- auto TargetAddr = E.getTarget().getAddress();
- auto FixupAddr = B->getFixupAddress(E);
- int64_t Displacement = TargetAddr.getValue() - (FixupAddr.getValue() +
4) + E.getAddend();
- if (llvm::isInt<32>(Displacement)) {
- E.setKind(x86_64::BranchPCRel32);
- continue;
- }
-
- // Distance doesn't fit — revert to indirect call/jmp through GOT.
- auto It = SymToGOTSym.find(&E.getTarget());
- if (It == SymToGOTSym.end()) {
- return llvm::make_error<llvm::StringError>(
- "Cannot revert GOTPCRELX relaxation: no GOT entry for " +
- (E.getTarget().hasName() ?
std::string(*E.getTarget().getName())
- : std::string("<anon>")),
- llvm::inconvertibleErrorCode());
- }
-
- Symbol* GOTSym = It->second;
- if (isRelaxedCall) {
- // Restore: 67 e8 → ff 15 (call *[rip+disp32])
- FixupData[-2] = 0xff;
- FixupData[-1] = 0x15;
- } else {
- // Restore: e9 XX XX XX XX 90 → ff 25 XX XX XX XX
- FixupData[-1] = 0xff;
- FixupData[0] = 0x25;
- // For jmp, the optimization shifted offset by -1; shift back.
- E.setOffset(E.getOffset() + 1);
- }
- E.setKind(x86_64::PCRel32);
- E.setTarget(*GOTSym);
- E.setAddend(0);
- }
- }
- return llvm::Error::success();
-}
-
-} // namespace
-
-void
GOTPCRELXFixPlugin::modifyPassConfig(llvm::orc::MaterializationResponsibility&
MR,
- llvm::jitlink::LinkGraph& G,
- llvm::jitlink::PassConfiguration&
Config) {
- Config.PreFixupPasses.emplace_back(fixBrokenGOTPCRELXRelaxation);
-}
-
-llvm::Error
GOTPCRELXFixPlugin::notifyFailed(llvm::orc::MaterializationResponsibility& MR) {
- return llvm::Error::success();
-}
-
-llvm::Error GOTPCRELXFixPlugin::notifyRemovingResources(llvm::orc::JITDylib&
JD,
- llvm::orc::ResourceKey
K) {
- return llvm::Error::success();
-}
-
-void GOTPCRELXFixPlugin::notifyTransferringResources(llvm::orc::JITDylib& JD,
- llvm::orc::ResourceKey
DstKey,
- llvm::orc::ResourceKey
SrcKey) {}
-
-} // namespace orcjit
-} // namespace ffi
-} // namespace tvm
-
-#endif // __linux__ && __x86_64__
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/gotpcrelx_fix.h
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/gotpcrelx_fix.h
deleted file mode 100644
index 2880ca62..00000000
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/gotpcrelx_fix.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*!
- * \file gotpcrelx_fix.h
- * \brief LLVM JITLink GOTPCRELX relaxation bug workaround (x86_64).
- *
- * LLVM issue: not yet filed, internal TODO.
- * Affected versions: observed on LLVM 20.x and 21.x; likely all versions
- * since `optimizeGOTAndStubAccesses` landed in
- * JITLink/x86_64.cpp.
- * Trigger: x86_64 JITLink with external symbols whose resolved addresses
- * fit in uint32 (e.g. libc PLT entries in a non-PIE executable,
- * or any low-VA process symbol) while JIT code is at high
- * addresses (as produced by our arena memory manager).
- * Symptom: `optimizeGOTAndStubAccesses` relaxes
- * `call *foo@GOTPCREL(%rip)` (ff 15)
- * into
- * `addr32 call foo` (67 e8)
- * and sets the edge kind to `Pointer32` (absolute 32-bit). But
- * `call rel32` is always PC-relative, so the absolute fixup
- * produces a garbage displacement. Result: SIGSEGV at JIT
- * execution or during ORC-runtime teardown.
- *
- * The `GOTPCRELXFixPlugin` registers a PreFixupPass that runs *after*
- * `optimizeGOTAndStubAccesses`, detects `Pointer32` edges preceded by
- * `67 e8` / `e9` bytes, and either converts them to `BranchPCRel32` (if
- * the PC-relative displacement fits in int32) or reverts the relaxation
- * to an indirect call/jmp through the GOT (`ff 15` / `ff 25`, edge kind
- * `PCRel32`, addend 0).
- *
- * ## Removal
- *
- * When the upstream fix lands and the project's minimum LLVM version
- * bumps past the first release containing it, delete this file and
- * remove:
- * - the `#include "llvm_patches/gotpcrelx_fix.h"` in orcjit_session.cc
- * - the `OLL->addPlugin(std::make_unique<GOTPCRELXFixPlugin>())` call
- * inside the `setObjectLinkingLayerCreator` lambda in orcjit_session.cc.
- * - the `llvm_patches/gotpcrelx_fix.cc` entry in
- * addons/tvm_ffi_orcjit/CMakeLists.txt.
- */
-#ifndef TVM_FFI_ORCJIT_LLVM_PATCHES_GOTPCRELX_FIX_H_
-#define TVM_FFI_ORCJIT_LLVM_PATCHES_GOTPCRELX_FIX_H_
-
-#if defined(__linux__) && (defined(__x86_64__) || defined(_M_X64))
-
-#include <llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h>
-
-namespace tvm {
-namespace ffi {
-namespace orcjit {
-
-/*! \brief PreFixupPass plugin that corrects broken GOTPCRELX
- * relaxations produced by JITLink's `optimizeGOTAndStubAccesses`.
- *
- * See the file-level docstring above for the trigger, symptom, and
- * removal procedure.
- */
-class GOTPCRELXFixPlugin : public llvm::orc::ObjectLinkingLayer::Plugin {
- public:
- void modifyPassConfig(llvm::orc::MaterializationResponsibility& MR,
llvm::jitlink::LinkGraph& G,
- llvm::jitlink::PassConfiguration& Config) override;
- llvm::Error notifyFailed(llvm::orc::MaterializationResponsibility& MR)
override;
- llvm::Error notifyRemovingResources(llvm::orc::JITDylib& JD,
llvm::orc::ResourceKey K) override;
- void notifyTransferringResources(llvm::orc::JITDylib& JD,
llvm::orc::ResourceKey DstKey,
- llvm::orc::ResourceKey SrcKey) override;
-};
-
-} // namespace orcjit
-} // namespace ffi
-} // namespace tvm
-
-#endif // __linux__ && __x86_64__
-
-#endif // TVM_FFI_ORCJIT_LLVM_PATCHES_GOTPCRELX_FIX_H_
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.cc
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.cc
index 9d44ffb3..8d00be46 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.cc
@@ -19,7 +19,7 @@
/*!
* \file init_fini_plugin.cc
- * \brief Init/fini section handling for ELF, MachO, and COFF JIT objects.
+ * \brief Init/fini section handling for Mach-O and COFF JIT objects.
*
* See init_fini_plugin.h for the trigger, symptom, and removal
* procedure for each platform.
@@ -46,12 +46,9 @@ void
InitFiniPlugin::modifyPassConfig(llvm::orc::MaterializationResponsibility&
Config.PrePrunePasses.emplace_back([](llvm::jitlink::LinkGraph& G) {
for (auto& Section : G.sections()) {
auto section_name = Section.getName();
- // ELF: .init_array*, .fini_array*, .ctors*, .dtors*
// Mach-O: __DATA,__mod_init_func, __DATA,__mod_term_func
// COFF: .CRT$XC* (ctors), .CRT$XT* (dtors)
- if (section_name.starts_with(".init_array") ||
section_name.starts_with(".fini_array") ||
- section_name.starts_with(".ctors") ||
section_name.starts_with(".dtors") ||
- section_name == "__DATA,__mod_init_func" || section_name ==
"__DATA,__mod_term_func" ||
+ if (section_name == "__DATA,__mod_init_func" || section_name ==
"__DATA,__mod_term_func" ||
section_name.starts_with(".CRT$XC") ||
section_name.starts_with(".CRT$XT")) {
for (auto* Block : Section.blocks()) {
bool has_live_sym = false;
@@ -120,20 +117,14 @@ void
InitFiniPlugin::modifyPassConfig(llvm::orc::MaterializationResponsibility&
return llvm::Error::success();
});
#endif
- // After fixups, read resolved function pointers from all init/fini data
sections.
- // Handles ELF (.init_array, .ctors, .fini_array, .dtors),
- // Mach-O (__DATA,__mod_init_func, __DATA,__mod_term_func),
- // and COFF (.CRT$XC*, .CRT$XT*) section conventions.
+ // After fixups, read resolved function pointers from Mach-O
+ // (__DATA,__mod_init_func / __DATA,__mod_term_func) and COFF
+ // (.CRT$XC* / .CRT$XT*) lifecycle sections.
Config.PostFixupPasses.emplace_back([this,
&jit_dylib](llvm::jitlink::LinkGraph& G) {
using Entry = ORCJITExecutionSessionObj::InitFiniEntry;
for (auto& Sec : G.sections()) {
auto section_name = Sec.getName();
- // --- ELF sections ---
- bool is_init_array = section_name.starts_with(".init_array");
- bool is_ctors = section_name.starts_with(".ctors");
- bool is_fini_array = section_name.starts_with(".fini_array");
- bool is_dtors = section_name.starts_with(".dtors");
// --- Mach-O sections ---
bool is_mod_init = (section_name == "__DATA,__mod_init_func");
bool is_mod_term = (section_name == "__DATA,__mod_term_func");
@@ -141,59 +132,21 @@ void
InitFiniPlugin::modifyPassConfig(llvm::orc::MaterializationResponsibility&
bool is_crt_xc = section_name.starts_with(".CRT$XC");
bool is_crt_xt = section_name.starts_with(".CRT$XT");
- if (!is_init_array && !is_ctors && !is_fini_array && !is_dtors &&
!is_mod_init &&
- !is_mod_term && !is_crt_xc && !is_crt_xt)
- continue;
+ if (!is_mod_init && !is_mod_term && !is_crt_xc && !is_crt_xt) continue;
int priority = 0;
- Entry::Section sec;
bool is_init;
- // ELF default priority for sections without a numeric suffix is 65535.
- // Lower priority numbers run first for .init_array; .fini_array and
.ctors
- // negate so that higher-numbered entries run first (reverse order).
- if (is_init_array) {
- if (section_name.consume_front(".init_array.")) {
- section_name.getAsInteger(10, priority);
- } else {
- priority = 65535;
- }
- sec = Entry::Section::kInitArray;
- is_init = true;
- } else if (is_ctors) {
- if (section_name.consume_front(".ctors.") &&
!section_name.getAsInteger(10, priority)) {
- priority = -priority;
- }
- sec = Entry::Section::kCtors;
- is_init = true;
- } else if (is_fini_array) {
- if (section_name.consume_front(".fini_array.") &&
- !section_name.getAsInteger(10, priority)) {
- priority = -priority;
- } else {
- priority = -65535;
- }
- sec = Entry::Section::kFiniArray;
- is_init = false;
- } else if (is_dtors) {
- if (section_name.consume_front(".dtors.")) {
- section_name.getAsInteger(10, priority);
- }
- sec = Entry::Section::kDtors;
- is_init = false;
- } else if (is_mod_init) {
+ if (is_mod_init) {
// Mach-O __mod_init_func: no priority system, treated as init_array
- sec = Entry::Section::kInitArray;
is_init = true;
} else if (is_mod_term) {
// Mach-O __mod_term_func: no priority system, treated as fini_array
- sec = Entry::Section::kFiniArray;
is_init = false;
} else if (is_crt_xc) {
// COFF .CRT$XC[suffix]: C++ constructors, sorted alphabetically by
suffix.
// Convert suffix to integer priority that preserves alphabetical
ordering.
// E.g., .CRT$XCA → 'A'*100000=6500000, .CRT$XCU → 'U'*100000=8500000,
// .CRT$XCT00200 → 'T'*100000+200=8400200
- sec = Entry::Section::kInitArray;
is_init = true;
auto suffix = section_name.substr(7); // after ".CRT$XC"
if (!suffix.empty()) {
@@ -206,7 +159,6 @@ void
InitFiniPlugin::modifyPassConfig(llvm::orc::MaterializationResponsibility&
}
} else {
// COFF .CRT$XT[suffix]: C++ destructors, same suffix-to-priority
scheme.
- sec = Entry::Section::kFiniArray;
is_init = false;
auto suffix = section_name.substr(7); // after ".CRT$XT"
if (!suffix.empty()) {
@@ -226,7 +178,7 @@ void
InitFiniPlugin::modifyPassConfig(llvm::orc::MaterializationResponsibility&
uint64_t FnAddr = 0;
memcpy(&FnAddr, Content.data() + Offset, PtrSize);
if (FnAddr != 0) {
- Entry entry{llvm::orc::ExecutorAddr(FnAddr), sec, priority};
+ Entry entry{llvm::orc::ExecutorAddr(FnAddr), priority};
if (is_init) {
session_->AddPendingInitializer(&jit_dylib, entry);
} else {
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.h
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.h
index ee01a628..14015ec6 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/init_fini_plugin.h
@@ -19,14 +19,13 @@
/*!
* \file init_fini_plugin.h
- * \brief Init/fini section handling for ELF, MachO, and COFF JIT objects.
+ * \brief Init/fini section handling for Mach-O and COFF JIT objects.
*
- * Emulates the missing/broken/bypassed LLVM ORC platform support for
- * init/fini sections on all three host platforms. Collects function
- * pointers from `.init_array` / `.fini_array` / `.ctors` / `.dtors`
- * (ELF), `__DATA,__mod_init_func` / `__DATA,__mod_term_func` (MachO),
- * and `.CRT$XC*` / `.CRT$XT*` (COFF); ties them to the containing
- * `JITDylib`; and runs them in priority order through
+ * Emulates the bypassed LLVM ORC platform support for init/fini sections on
+ * macOS and Windows. Collects function pointers from
+ * `__DATA,__mod_init_func` / `__DATA,__mod_term_func` (Mach-O) and
+ * `.CRT$XC*` / `.CRT$XT*` (COFF), ties them to the containing `JITDylib`,
+ * and runs them in priority order through
* `ORCJITExecutionSessionObj::Run{Pending{Init,De}initializers}`.
*
* On Windows the plugin additionally patches `__ImageBase` (set to the
@@ -36,38 +35,21 @@
* `RtlAddFunctionTable` anyway). Those pieces also disappear once
* `COFFPlatform` becomes usable.
*
- * Trigger: any JIT module on any platform containing constructors,
- * destructors, or `__attribute__((constructor))` /
- * MSVC `#pragma init_seg` equivalents.
+ * Trigger: a Mach-O or COFF JIT module containing constructors, destructors,
+ * or MSVC `#pragma init_seg` equivalents.
* Symptom without the patch:
- * - ELF: constructors/destructors never run (`ELFNixPlatform`
- * enumerates but does not invoke them before
- * llvm/llvm-project#175981).
- * - MachO: we skip `MachOPlatform` entirely to sidestep the
- * compact-unwind 32-bit delta bug (see `orcjit_session.cc`), so no
- * platform runs init/fini; this plugin is the only mechanism.
+ * - MachO: we skip `MachOPlatform` to sidestep the compact-unwind 32-bit
+ * delta bug (see `orcjit_session.cc`); LLJIT's generic support does not
+ * provide the native Mach-O object lifecycle, so this plugin does.
* - COFF: relocation overflow / unresolved-SEH crashes
* (`COFFPlatform` is not hooked up because its MSVC CRT symbol
* requirements cannot be satisfied).
*
- * ## Removal — Linux
- *
- * LLVM issue: https://github.com/llvm/llvm-project/pull/175981
- * The upstream fix is included in LLVM 23.1.1 but not LLVM 22.1.0, the
- * addon's current CI baseline. When the project's minimum LLVM version
- * reaches 23 and its lifecycle calls use `ELFNixPlatform`, delete the ELF
- * handling path from this file. Concretely:
- * - Remove the ELF-section branches (`.init_array`, `.ctors`,
- * `.fini_array`, `.dtors`) from `InitFiniPlugin::modifyPassConfig`.
- * - If no platform still needs this plugin, delete this file outright
- * and follow the checklist in `llvm_patches/README.md`.
- *
* ## Removal — macOS
*
- * Tied to re-enabling `MachOPlatform`. That requires the compact-unwind
- * per-graph `dso_base` fix (see `fix-machoplatform-libunwind-dso-base.patch`
- * in the repo root) to land in our LLVM. Until then we skip MachOPlatform
- * and this plugin handles `__mod_init_func` / `__mod_term_func`. When
+ * Tied to re-enabling `MachOPlatform`. That requires a compact-unwind
+ * per-graph `dso_base` fix to land in LLVM. Until then this plugin handles
+ * `__mod_init_func` / `__mod_term_func`. When
* MachOPlatform is re-enabled, delete the MachO-section branches from
* `InitFiniPlugin::modifyPassConfig` and drop the macOS side of the
* `addPlugin` call in `orcjit_session.cc`.
@@ -93,10 +75,10 @@ namespace tvm {
namespace ffi {
namespace orcjit {
-/*! \brief Init/fini section collector and runner for ELF, MachO, and COFF.
+/*! \brief Init/fini section collector and runner for Mach-O and COFF.
*
- * See the file-level docstring above for the three-platform strategy
- * and the removal procedure for each platform.
+ * See the file-level docstring above for the two-platform strategy and the
+ * removal procedure for each platform.
*/
class InitFiniPlugin : public llvm::orc::ObjectLinkingLayer::Plugin {
// Store a raw pointer to avoid a reference cycle:
diff --git
a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.cc
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.cc
index f2cd4824..4ecc228f 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.cc
@@ -43,14 +43,10 @@ namespace {
// Each CxaAtexitRecordsScope saves the previous pointer and restores it
// on exit, so nested scopes compose correctly across re-entrant init/fini.
//
-// We cannot override ___dso_handle (LLJIT's Platform has already defined
-// it in every user JITDylib), so we don't rely on the `dso_handle` arg
-// passed to the shim. Instead, each ORCJITDynamicLibraryObj publishes its
-// own records vector via this TLS slot, scoped around any JIT entry point
-// that may run ctors / dtors. The shim pushes (fn, arg) into the
-// TLS-pointed vector, or silently drops if no scope is active (which
-// would be a stray call from outside any JIT execution — acceptable
-// degradation).
+// We cannot override ___dso_handle (LLJIT's platform has already defined it in
+// every user JITDylib), so we do not rely on the dso_handle argument passed to
+// the shim. Each ORCJITDynamicLibraryObj publishes its own records vector via
+// this TLS slot while JIT code may run constructors or destructors.
thread_local CxaAtexitRecords* g_active_cxa_records = nullptr;
extern "C" int tvm_ffi_cxa_atexit_shim(void (*fn)(void*), void* arg,
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.h
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.h
index f215e209..f95f6334 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/macho_cxa_atexit_shim.h
@@ -21,20 +21,18 @@
* \file macho_cxa_atexit_shim.h
* \brief Per-JITDylib `__cxa_atexit` interposer for macOS JIT.
*
- * We skip `MachOPlatform` entirely on macOS to sidestep the
+ * We skip the native `MachOPlatform` on macOS to sidestep the
* compact-unwind 32-bit-delta bug in JITLink's `CompactUnwindSupport`
- * (see the analysis in orcjit_session.cc and
- * fix-machoplatform-libunwind-dso-base.patch at the repo root). With
- * no Platform in the picture, clang-lowered
+ * (see the analysis in orcjit_session.cc). Without that native platform,
+ * clang-lowered
* `__attribute__((destructor))` and C++ global dtors — which register
* through `__cxa_atexit(fn, arg, &__dso_handle)` during init — would
* fall through to libSystem's `___cxa_atexit`, orphaning those
* callbacks from our drop-time drain.
*
* This shim:
- * 1. Installs an absolute symbol for `___cxa_atexit` on each user
- * JITDylib that points at our own capture function
- * (`InstallCxaAtexitShim`).
+ * 1. Installs an absolute symbol for `___cxa_atexit` on each user JITDylib
+ * that points at our capture function (`InstallCxaAtexitShim`).
* 2. Publishes the owning dylib's `CxaAtexitRecords` vector in TLS
* (`CxaAtexitRecordsScope`) so the capture function knows where
* to push `(fn, arg)` pairs.
@@ -97,9 +95,9 @@ class CxaAtexitRecordsScope {
*
* Must be called once per user JITDylib, before any JIT code on that dylib
* materializes. Placing the definition on the dylib itself (rather than
- * injecting into the link order) ensures it wins over `<Platform>`'s
- * libSystem fallback — JITDylib::define-time symbols are searched before
- * the link order.
+ * injecting into the link order) ensures the shim wins over libSystem's
+ * fallback — JITDylib::define-time symbols are searched before the link
+ * order.
*/
llvm::Error InstallCxaAtexitShim(llvm::orc::ExecutionSession& ES,
llvm::orc::JITDylib& jd);
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
index b0925214..2bfb2d17 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.cc
@@ -193,22 +193,32 @@
ORCJITDynamicLibraryObj::ORCJITDynamicLibraryObj(ORCJITExecutionSession session,
}
ORCJITDynamicLibraryObj::~ORCJITDynamicLibraryObj() {
- // Step 1: run this dylib's static destructors. Drain the entries under the
- // lock but run them released — a JIT'd dtor may re-enter the session on this
- // thread, which the plain mutex could not survive while held.
- std::vector<ORCJITExecutionSessionObj::InitFiniEntry> deinit;
+#ifdef __linux__
+ if (session_->has_orc_platform_) {
+ if (platform_initialized_) {
+ // LLVM 23's ELFNixPlatform now implements ordered deinitialization. This
+ // also drains __cxa_atexit registrations through liborc_rt.
+ if (auto err = jit_->deinitialize(GetJITDylib()))
llvm::consumeError(std::move(err));
+ }
+ } else
+#endif
{
- std::lock_guard<std::mutex> lock(session_->mutex_);
- deinit = session_->DrainPendingDeinitializers(GetJITDylib());
+ // Mach-O and COFF use the local lifecycle adapter. Drain under the lock,
+ // then run released so a JIT'd dtor may re-enter the session.
+ std::vector<ORCJITExecutionSessionObj::InitFiniEntry> deinit;
+ {
+ std::lock_guard<std::mutex> lock(session_->mutex_);
+ deinit = session_->DrainPendingDeinitializers(GetJITDylib());
+ }
+ ORCJITExecutionSessionObj::RunInitFiniEntries(deinit);
}
- ORCJITExecutionSessionObj::RunInitFiniEntries(deinit);
#ifdef __APPLE__
// Drain per-dylib __cxa_atexit registrations (LIFO) captured during init;
see
// llvm_patches/macho_cxa_atexit_shim.h.
DrainCxaAtexit(cxa_atexit_records_);
#endif
- // Step 2: remove the JITDylib, releasing its JIT memory via the memory
- // manager's deallocate() (see orcjit_session.cc — no Platform teardown
here).
+ // Remove the JITDylib after platform/local lifecycle teardown, releasing its
+ // JIT memory through the memory manager's deallocate path.
{
std::lock_guard<std::mutex> lock(session_->mutex_);
session_->RemoveDylib(dylib_);
@@ -395,8 +405,8 @@ void* ORCJITDynamicLibraryObj::GetSymbol(const String&
name, bool run_initialize
void ORCJITDynamicLibraryObj::InitContextSymbols() {
// Called once from Finalize before the dylib is published, so no guard is
// needed. Resolve every context slot without running initializers, populate
- // the slots, then run the collected initializers. Constructors can therefore
- // safely use the library context on their first instruction.
+ // the slots, then invoke the upstream Linux platform or the local macOS/COFF
+ // adapter. Constructors can safely use the context on their first
instruction.
if (void** ctx_addr =
reinterpret_cast<void**>(GetSymbol(symbol::tvm_ffi_library_ctx, false))) {
*ctx_addr = this;
}
@@ -405,6 +415,23 @@ void ORCJITDynamicLibraryObj::InitContextSymbols() {
*ctx_addr = symbol;
}
});
+#ifdef __linux__
+ if (session_->has_orc_platform_) {
+ // LLVM's platform call may execute arbitrary JIT constructors. Hold the
+ // per-dylib gate, but not the session mutex, so same-thread callbacks can
+ // re-enter while other threads cannot observe partially initialized code.
+ BeginInitializerRun();
+ try {
+ TVM_FFI_ORCJIT_LLVM_CALL(jit_->initialize(GetJITDylib()));
+ platform_initialized_ = true;
+ } catch (...) {
+ EndInitializerRun();
+ throw;
+ }
+ EndInitializerRun();
+ return;
+ }
+#endif
RunPendingInitializers();
}
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
index 370dffdd..aa3eeca0 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_dylib.h
@@ -151,6 +151,9 @@ class ORCJITDynamicLibraryObj : public ModuleObj {
/*! \brief Whether Finalize has run; guards against double-finalizing. */
bool finalized_{false};
+ /*! \brief Whether LLVM's platform initialization completed successfully. */
+ bool platform_initialized_{false};
+
// Constructors run without the session mutex to permit re-entry. These
// fields prevent a second thread from observing callable code before the
// first thread completes initialization; same-thread nested lookup remains
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.cc
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.cc
index 279d5478..0b755e51 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.cc
@@ -139,8 +139,12 @@ void SlabPoolMemoryManager::allocate(const
llvm::jitlink::JITLinkDylib* /*JD*/,
void SlabPoolMemoryManager::deallocate(std::vector<FinalizedAlloc> Allocs,
OnDeallocatedFunction OnDeallocated) {
Error DeallocErr = Error::success();
- for (auto& Alloc : Allocs) {
- auto* FA = Alloc.release().toPtr<FinalizedAllocInfo*>();
+ // Match LLVM's InProcessMemoryManager: tear down a batch in reverse
+ // allocation order. Platform allocations register the JITDylib header
+ // before user graphs register their init/fini sections, so forward teardown
+ // would deregister the header while later deallocation actions still need
it.
+ for (auto It = Allocs.rbegin(); It != Allocs.rend(); ++It) {
+ auto* FA = It->release().toPtr<FinalizedAllocInfo*>();
FA->owner->deallocateOne(FA, DeallocErr);
delete FA;
}
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.h
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.h
index 6ec89707..5e7125af 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_memory_manager.h
@@ -35,14 +35,10 @@
* reclaimed or until the pool (and its enclosing session) is
* destroyed. Individual graphs are deallocated via
* `FA->owner->deallocateOne(...)`, returning bytes to the slab's free
- * list. Drained slabs can be returned to the OS via
- * `clearFreeSlabs()`.
+ * list. Batched allocations are released in reverse allocation order so
+ * platform metadata outlives the user graphs that refer to it. Drained slabs
+ * can be returned to the OS via `clearFreeSlabs()`.
*
- * ## GOTPCRELX relaxation workaround
- *
- * See `llvm_patches/gotpcrelx_fix.cc`. The plugin is added per-session
- * to the `ObjectLinkingLayer` alongside this memory manager and is
- * orthogonal to pool growth.
*/
#ifndef TVM_FFI_ORCJIT_ORCJIT_MEMORY_MANAGER_H_
#define TVM_FFI_ORCJIT_ORCJIT_MEMORY_MANAGER_H_
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
index bb56eee4..d525367d 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
@@ -55,10 +55,9 @@
#include "orcjit_memory_manager.h"
#include "orcjit_utils.h"
-#if defined(__linux__) && (defined(__x86_64__) || defined(_M_X64))
-#include "llvm_patches/gotpcrelx_fix.h"
-#endif
+#if defined(__APPLE__) || defined(_WIN32)
#include "llvm_patches/init_fini_plugin.h"
+#endif
#ifdef __APPLE__
#include "llvm_patches/macho_cxa_atexit_shim.h"
#endif
@@ -113,6 +112,7 @@ const char* GetAddonCxxRuntimeName() {
#endif
}
+#if defined(__linux__) && defined(__GLIBCXX__)
void* GetCxxRuntimeHandle(llvm::StringRef runtime_path) {
// Keep one process-lifetime local handle per compiler-selected runtime. This
// avoids incrementing the dlopen reference count on every load_module call.
@@ -132,7 +132,6 @@ void* GetCxxRuntimeHandle(llvm::StringRef runtime_path) {
return handle;
}
-#if defined(__linux__) && defined(__GLIBCXX__)
class LibStdCxxNonsharedGenerator final : public
llvm::orc::DefinitionGenerator {
public:
LibStdCxxNonsharedGenerator(
@@ -164,29 +163,39 @@ class LibStdCxxNonsharedGenerator final : public
llvm::orc::DefinitionGenerator
#endif
// Install ExecutorNativePlatform per the `orc_rt` selector (see the ctor doc).
-// A no-op except on Linux/ELF: macOS skips the platform (compact-unwind bug)
-// and Windows never wires up COFFPlatform, so both ignore the selector.
-void SetUpOrcPlatform(llvm::orc::LLJITBuilder& builder,
+// A no-op off Linux/ELF: those targets retain LLJIT's generic platform support
+// and use the local lifecycle adapter below.
+bool SetUpOrcPlatform(llvm::orc::LLJITBuilder& builder,
const Optional<Variant<String, Bytes>>& orc_rt) {
#if defined(__APPLE__) || defined(_WIN32)
(void)builder;
(void)orc_rt;
+ return false;
#else
- if (!orc_rt.has_value()) return; // None -> no platform
+ if (!orc_rt.has_value()) {
+ // Explicitly honor `None`: LLJIT otherwise installs its generic platform.
+ builder.setPlatformSetUp(llvm::orc::setUpInactivePlatform);
+ return false;
+ }
const Variant<String, Bytes>& sel = orc_rt.value();
if (auto opt_path = sel.as<String>()) {
const String& path = *opt_path;
- if (path.empty()) { // "auto" -> embedded (or nothing if compiled out)
+ if (path.empty()) { // "auto" -> embedded
#ifdef TVM_FFI_ORCJIT_EMBED_ORC_RT
builder.setPlatformSetUp(llvm::orc::ExecutorNativePlatform(GetEmbeddedOrcRuntimeBuffer()));
+ return true;
+#else
+ return false;
#endif
} else {
builder.setPlatformSetUp(llvm::orc::ExecutorNativePlatform(path.operator
std::string()));
+ return true;
}
} else { // Bytes: ExecutorNativePlatform takes ownership of the copy.
const Bytes& bytes = sel.get<Bytes>();
builder.setPlatformSetUp(llvm::orc::ExecutorNativePlatform(llvm::MemoryBuffer::getMemBufferCopy(
llvm::StringRef(bytes.data(), bytes.size()), "liborc_rt.a")));
+ return true;
}
#endif
}
@@ -218,9 +227,9 @@ ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const
Optional<Variant<Stri
// `capacityForFootprint` (>= slab_size) and mmap errors propagate.
//
// LLJIT auto-configures ObjectLinkingLayer (JITLink) on x86_64 and aarch64
- // Linux (see LLJITBuilderState::prepareForConstruction). We override
- // the layer creator to pass our memory manager. macOS/Windows are gated
- // off pending testing. (The historical "MachOPlatform teardown crashes
+ // Linux (see LLJITBuilderState::prepareForConstruction). We replace its
+ // memory manager with our slab pool. macOS/Windows are gated off pending
+ // testing. (The historical "MachOPlatform teardown crashes
// with the arena" concern is moot now that we skip MachOPlatform below,
// but enabling the slab on macOS still needs a validation pass.)
#ifdef __linux__
@@ -236,21 +245,23 @@
ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const Optional<Variant<Stri
} else {
slab_size = SlabPoolMemoryManager::kDefaultSlabSize;
}
- memory_manager_ = std::make_unique<SlabPoolMemoryManager>(page_size,
slab_size);
+ pending_memory_manager_ =
std::make_unique<SlabPoolMemoryManager>(page_size, slab_size);
+ memory_manager_ = pending_memory_manager_.get();
}
#endif
auto setup_builder = [this](llvm::orc::LLJITBuilder& builder) {
#ifdef __linux__
if (memory_manager_) {
+ builder.setMemoryManagerCreator(
+ [this](llvm::orc::ExecutionSession&)
+ ->
llvm::Expected<std::unique_ptr<llvm::jitlink::JITLinkMemoryManager>> {
+ return std::move(pending_memory_manager_);
+ });
builder.setObjectLinkingLayerCreator(
- [this](llvm::orc::ExecutionSession& ES)
+ [](llvm::orc::ExecutionSession& ES,
llvm::jitlink::JITLinkMemoryManager& memory_manager)
-> llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>> {
- auto OLL = std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
*memory_manager_);
-#if defined(__x86_64__) || defined(_M_X64)
- OLL->addPlugin(std::make_unique<GOTPCRELXFixPlugin>());
-#endif
- return OLL;
+ return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
memory_manager);
});
} // if (memory_manager_)
#elif defined(__APPLE__) || defined(_WIN32)
@@ -260,9 +271,9 @@ ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const
Optional<Variant<Stri
// Windows: LLJIT defaults to RTDyld; we need JITLink for InitFiniPlugin
// and DLLImportDefinitionGenerator.
builder.setObjectLinkingLayerCreator(
- [](llvm::orc::ExecutionSession& ES)
+ [](llvm::orc::ExecutionSession& ES,
llvm::jitlink::JITLinkMemoryManager& memory_manager)
-> llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>> {
- return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES);
+ return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
memory_manager);
});
#endif
#if (defined(__linux__) && defined(__GLIBCXX__)) || defined(__APPLE__)
@@ -278,14 +289,10 @@
ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const Optional<Variant<Stri
// cannot see its C++ runtime dependency. Search that dependency through
// a private handle instead of promoting the addon (and its statically
// linked LLVM) into the process-global namespace.
- void* cxx_runtime = GetCxxRuntimeHandle(GetAddonCxxRuntimeName());
- if (!cxx_runtime) {
- const char* error = dlerror();
- return llvm::make_error<llvm::StringError>(error ? error : "failed to
open the C++ runtime",
-
llvm::inconvertibleErrorCode());
- }
-
process_symbols->addGenerator(std::make_unique<llvm::orc::EPCDynamicLibrarySearchGenerator>(
- J.getExecutionSession(),
llvm::orc::ExecutorAddr::fromPtr(cxx_runtime)));
+ auto cxx_runtime_generator =
llvm::orc::EPCDynamicLibrarySearchGenerator::Load(
+ J.getExecutionSession(), J.getDylibMgr(), GetAddonCxxRuntimeName());
+ if (!cxx_runtime_generator) return cxx_runtime_generator.takeError();
+ process_symbols->addGenerator(std::move(*cxx_runtime_generator));
#if defined(__linux__) && defined(__GLIBCXX__)
// GCC's libstdc++.so linker script may satisfy this helper from
@@ -319,15 +326,15 @@
ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const Optional<Variant<Stri
};
auto builder = llvm::orc::LLJITBuilder();
- // Configure the ORC platform from `orc_rt` (a no-op off Linux/ELF; see
- // SetUpOrcPlatform). macOS in particular must skip ExecutorNativePlatform /
+ // Configure the native ORC platform from `orc_rt` (a no-op off Linux/ELF;
+ // see SetUpOrcPlatform). macOS must skip ExecutorNativePlatform /
// MachOPlatform to sidestep the compact-unwind 32-bit-delta bug in JITLink's
// CompactUnwindSupport (personality delta against a per-JITDylib header base
// wraps `uint64_t` and fails `isUInt<32>` when a later user graph mmaps
below
- // the header; see the repo-root fix-machoplatform-libunwind-dso-base.patch).
- // InitFiniPlugin below handles __mod_init_func / __mod_term_func instead;
- // tradeoff: no C++ exception unwinding across JIT frames on macOS.
- SetUpOrcPlatform(builder, orc_rt);
+ // the header).
+ // InitFiniPlugin below handles __mod_init_func / __mod_term_func; LLJIT's
+ // generic platform still registers unwind information for JIT frames.
+ has_orc_platform_ = SetUpOrcPlatform(builder, orc_rt);
setup_builder(builder);
jit_ = TVM_FFI_ORCJIT_LLVM_CALL(builder.create());
#ifdef _WIN32
@@ -335,13 +342,13 @@
ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const Optional<Variant<Stri
// building. See llvm_patches/win_coff_pdata_strip.h for the rationale.
jit_->getObjTransformLayer().setTransform(&StripCoffPdataXdata);
#endif
- // Use our custom InitFiniPlugin on every platform for init/fini section
- // collection and priority-ordered execution (ELF .init_array/.fini_array,
- // MachO __mod_init_func/__mod_term_func, COFF .CRT$XC*/.CRT$XT*). See
- // llvm_patches/init_fini_plugin.h for per-platform removal criteria.
+#if defined(__APPLE__) || defined(_WIN32)
+ // Mach-O and COFF still need the local lifecycle adapter. Linux uses LLVM
+ // 23's ELFNixPlatform initialize/deinitialize path instead.
auto& objlayer = jit_->getObjLinkingLayer();
static_cast<llvm::orc::ObjectLinkingLayer&>(objlayer).addPlugin(
std::make_unique<InitFiniPlugin>(this));
+#endif
#ifdef _WIN32
// On Windows, the default process-symbol generator only searches the main
// exe module via GetProcAddress(GetModuleHandle(NULL), ...). Add a
@@ -419,8 +426,10 @@ ORCJITDynamicLibrary
ORCJITExecutionSessionObj::CreateDynamicLibrary(
if (it == cxx_runtime_dylibs_.end()) {
std::string runtime_name = "<C++ runtime " +
std::to_string(cxx_runtime_dylibs_.size()) + ">";
auto& runtime_dylib =
jit_->getExecutionSession().createBareJITDylib(std::move(runtime_name));
-
runtime_dylib.addGenerator(std::make_unique<llvm::orc::EPCDynamicLibrarySearchGenerator>(
- jit_->getExecutionSession(),
llvm::orc::ExecutorAddr::fromPtr(cxx_runtime)));
+ auto runtime_generator =
+
TVM_FFI_ORCJIT_LLVM_CALL(llvm::orc::EPCDynamicLibrarySearchGenerator::Load(
+ jit_->getExecutionSession(), jit_->getDylibMgr(),
runtime_path.c_str()));
+ runtime_dylib.addGenerator(std::move(runtime_generator));
it = cxx_runtime_dylibs_.emplace(std::move(runtime_path),
&runtime_dylib).first;
}
cxx_runtime_dylib = it->second;
@@ -462,9 +471,9 @@ ORCJITDynamicLibrary
ORCJITExecutionSessionObj::CreateDynamicLibrary(
#endif
#ifdef __APPLE__
- // Inject ___cxa_atexit on the user JITDylib so it wins over <Platform>'s
- // fallback (which resolves to libSystem's and would orphan dtors from
- // our drop-time drain). See llvm_patches/macho_cxa_atexit_shim.h.
+ // Inject ___cxa_atexit on the user JITDylib so it wins over the generic
+ // platform's libSystem fallback, which would orphan dtors from our drop-time
+ // drain. See llvm_patches/macho_cxa_atexit_shim.h.
TVM_FFI_ORCJIT_LLVM_CALL(InstallCxaAtexitShim(jit_->getExecutionSession(),
jit_dylib));
#endif
@@ -500,7 +509,6 @@ std::vector<ORCJITExecutionSessionObj::InitFiniEntry>
DrainSorted(
pending.erase(it);
llvm::sort(entries, [](const ORCJITExecutionSessionObj::InitFiniEntry& a,
const ORCJITExecutionSessionObj::InitFiniEntry& b) {
- if (a.section != b.section) return static_cast<int>(a.section) <
static_cast<int>(b.section);
return a.priority < b.priority;
});
}
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
index b08b7c61..ada37fe3 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
@@ -60,13 +60,13 @@ class ORCJITExecutionSessionObj : public Object {
/*!
* \brief Construct a session, selecting the ORC runtime.
*
- * \param orc_rt Runtime selector (Linux/ELF only; ignored on macOS/Windows,
- * which never configure an ORC platform):
+ * \param orc_rt Native runtime selector (Linux/ELF only; ignored on
+ * macOS/Windows, which retain LLJIT's generic platform support):
* - empty \c String (default, "auto"): the runtime embedded in this
- * extension (or no platform if the embed was compiled out);
+ * extension, or no native platform if embedding was unavailable;
* - non-empty \c String: a custom liborc_rt archive on disk;
* - \c Bytes: a custom liborc_rt archive held in memory;
- * - \c nullopt: no ORC platform at all.
+ * - \c nullopt: no ORC platform or ELF init/fini lifecycle.
* A custom runtime must match the LLVM this extension was built
against.
* \param slab_size_bytes Per-slab capacity for the JIT memory arena.
*/
@@ -144,14 +144,7 @@ class ORCJITExecutionSessionObj : public Object {
Object);
struct InitFiniEntry {
- enum class Section {
- kInitArray = 0,
- kCtors = 1,
- kDtors = 2,
- kFiniArray = 3,
- };
llvm::orc::ExecutorAddr address;
- Section section;
int priority;
};
@@ -181,8 +174,8 @@ class ORCJITExecutionSessionObj : public Object {
* memory and dropping it from the session's dylib list.
*
* Invoked by \c ORCJITDynamicLibraryObj's destructor after any required
- * static-destructor sequence (drained via \c DrainPendingDeinitializers on
- * Linux/Windows) has completed. The caller must ensure no
+ * static-destructor sequence (upstream ELFNixPlatform on Linux, or the local
+ * adapter on macOS/Windows) has completed. The caller must ensure no
* further use of the \c JITDylib* after this call — it becomes "Closed" and
* its address may be reused by a subsequent \c createJITDylib.
*
@@ -207,11 +200,16 @@ class ORCJITExecutionSessionObj : public Object {
// entries under it. See the locking discipline on mutex_.
friend class ORCJITDynamicLibraryObj;
- /*! \brief Slab-pool memory manager — must be declared before jit_ for
destruction order */
- std::unique_ptr<SlabPoolMemoryManager> memory_manager_;
+ /*! \brief Slab manager staged for transfer into LLJIT during construction.
*/
+ std::unique_ptr<SlabPoolMemoryManager> pending_memory_manager_;
+ /*! \brief Non-owning view of LLJIT's slab manager, used by ClearFreeSlabs.
*/
+ SlabPoolMemoryManager* memory_manager_{nullptr};
/*! \brief The LLVM ORC JIT instance */
std::unique_ptr<llvm::orc::LLJIT> jit_;
+ /*! \brief Whether Linux has an ExecutorNativePlatform backed by liborc_rt.
*/
+ bool has_orc_platform_{false};
+
/*! \brief Counter for auto-generating library names */
int dylib_counter_{0};
diff --git a/addons/tvm_ffi_orcjit/tests/sources/cc/test_ctor_dtor.cc
b/addons/tvm_ffi_orcjit/tests/sources/cc/test_ctor_dtor.cc
index 35af0af1..2d3845be 100644
--- a/addons/tvm_ffi_orcjit/tests/sources/cc/test_ctor_dtor.cc
+++ b/addons/tvm_ffi_orcjit/tests/sources/cc/test_ctor_dtor.cc
@@ -26,6 +26,14 @@ using namespace tvm::ffi;
using ctor_t = void (*)();
using dtor_t = void (*)();
+// Exercises compiler-generated __cxa_atexit registration. ELFNixPlatform owns
+// this lifecycle on Linux; the addon's scoped shim owns it on macOS.
+struct GlobalLifecycle {
+ GlobalLifecycle() { PUTS_LOG("<cxx_ctor>"); }
+ ~GlobalLifecycle() { PUTS_LOG("<cxx_dtor>"); }
+};
+static GlobalLifecycle global_lifecycle;
+
// __attribute__((constructor/destructor)) works on both ELF and Mach-O:
// ELF → .init_array / .fini_array
// Mach-O → __DATA,__mod_init_func / __cxa_atexit
diff --git a/addons/tvm_ffi_orcjit/tests/test_basic.py
b/addons/tvm_ffi_orcjit/tests/test_basic.py
index 570e4151..b2f82700 100644
--- a/addons/tvm_ffi_orcjit/tests/test_basic.py
+++ b/addons/tvm_ffi_orcjit/tests/test_basic.py
@@ -442,6 +442,8 @@ def test_ctor_dtor(v: Variant) -> None:
log += x
mod = load(v.ctor_dtor_obj())
+ if sys.platform == "linux":
+ assert log, "ELFNixPlatform did not run constructors during load"
mod.get_function(v.fn("main"))()
del mod
@@ -449,6 +451,10 @@ def test_ctor_dtor(v: Variant) -> None:
pre = log[:main_idx]
post = log[main_idx:]
+ if v.subdir.startswith("cc"):
+ assert "<cxx_ctor>" in pre, f"C++ global constructor did not run:
{log!r}"
+ assert "<cxx_dtor>" in post, f"C++ __cxa_atexit destructor did not
run: {log!r}"
+
if sys.platform == "win32":
# Windows (all compilers): COFF .CRT$XC* constructors + .CRT$XT*
terminators.
# All Windows compilers (MSVC, clang-cl, and LLVM Clang targeting MSVC
ABI)
@@ -481,6 +487,10 @@ def test_ctor_dtor(v: Variant) -> None:
assert "<dtors>" not in log
[email protected](
+ sys.platform == "linux",
+ reason="ELFNixPlatform completes initialization before load_module
returns",
+)
def test_concurrent_first_lookup_waits_for_initializers() -> None:
"""A second thread cannot call newly materialized code before init
completes."""
ctor_entered = threading.Event()
diff --git a/addons/tvm_ffi_orcjit/tests/test_library_visibility.py
b/addons/tvm_ffi_orcjit/tests/test_library_visibility.py
index bd8833aa..3950557a 100644
--- a/addons/tvm_ffi_orcjit/tests/test_library_visibility.py
+++ b/addons/tvm_ffi_orcjit/tests/test_library_visibility.py
@@ -46,13 +46,19 @@ def test_addon_exports_only_initializer() -> None:
lib_path = tvm_ffi_orcjit._lib_path
if platform.system() == "Darwin":
command = [nm, "-gjU", str(lib_path)]
+ # LLVM's PrettyStackTrace object marks this hidden symbol with
+ # REFERENCED_DYNAMICALLY, which makes Apple ld export it even when an
+ # exported-symbols list is present.
+ allowed_runtime_exports = {"___crashreporter_info__"}
expected = {"_TVMFFIOrcJITInitialize"}
else:
command = [nm, "-D", "--defined-only", "--format=posix", str(lib_path)]
+ allowed_runtime_exports = set()
expected = {"TVMFFIOrcJITInitialize"}
output = subprocess.run(command, check=True, capture_output=True,
text=True).stdout
exported = {line.split()[0].split("@@", 1)[0] for line in
output.splitlines() if line.strip()}
+ exported -= allowed_runtime_exports
assert exported == expected
diff --git a/addons/tvm_ffi_orcjit/tests/test_session_load_module.py
b/addons/tvm_ffi_orcjit/tests/test_session_load_module.py
index 562ede31..59c59f4b 100644
--- a/addons/tvm_ffi_orcjit/tests/test_session_load_module.py
+++ b/addons/tvm_ffi_orcjit/tests/test_session_load_module.py
@@ -140,7 +140,7 @@ def test_orc_rt_in_memory_bytes() -> None:
def test_orc_rt_none_no_platform() -> None:
- """orc_rt=None runs with no ORC platform; C-ABI objects still load."""
+ """orc_rt=None skips the ELF platform; plain C-ABI objects still load."""
mod = ExecutionSession(orc_rt=None).load_module(obj("c/test_funcs"))
assert mod.test_add(8, 9) == 17
diff --git a/include/tvm/ffi/base_details.h b/include/tvm/ffi/base_details.h
index 4c2226d8..ac24c9ef 100644
--- a/include/tvm/ffi/base_details.h
+++ b/include/tvm/ffi/base_details.h
@@ -29,6 +29,7 @@
#include <tvm/ffi/endian.h>
#include <cstddef>
+#include <type_traits>
#include <utility>
#if defined(_MSC_VER)