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-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 628a8b64 [FEAT][ORCJIT] Add native Windows ARM64 wheels (#879)
628a8b64 is described below
commit 628a8b64e997f2c3e300899c4340819ab78bc35a
Author: Yaxing Cai <[email protected]>
AuthorDate: Mon Oct 26 21:47:59 2026 +0800
[FEAT][ORCJIT] Add native Windows ARM64 wheels (#879)
Prepare ORC JIT 0.1.3 with native Windows ARM64 wheels, using the
published `mlc-llvm-static==23.1.1.post0` SDK. Add
`windows-11-vs2026-arm` / `cp312-win_arm64` to both CI and the release
matrix, activate native Visual Studio tools, and explicitly target ARM64
when compiling clang-cl test objects.
LLVM 23.1.1 JITLink only implements COFF/x86-64 and rejects ARM64
objects. Use `RTDyldObjectLinkingLayer` on Windows ARM64, preserving
ORC's module isolation and the addon's `.CRT$XC*` / `.CRT$XT*` lifecycle
handling. RuntimeDyld supplies ARM64 relocations, DLL-import slots and
long-branch stubs; the DLL generator resolves base symbols to host
addresses. LLVM-facing C++ code follows `LLVM_ENABLE_RTTI` from the SDK
to avoid unresolved base-class typeinfo. Keep lifecycle slots indirect
until relocation finishes, and ignore null/sentinel boundary slots.
Windows continues to support C objects only.
Validation:
- macOS ARM64 editable build against the exact `23.1.1.post0` SDK
succeeds; ORC JIT tests: 136 passed, 55 skipped.
- C and C++ quick-start examples pass.
- All pre-commit hooks for changed files pass; cibuildwheel selects
`cp312-win_arm64`.
- A local C++ harness cross-compiled an ARM64 COFF object and executed
it through the new RTDyld adapter on macOS ARM64, checking direct host
calls, DLL-import slots, constructor/destructor ordering, null/sentinel
entries and teardown. This does not replace native Windows validation.
- Native Windows ARM64 validation: **105 passed, 51 skipped**; the C
quick-start passes. Both MSVC and clang-cl produce and execute ARM64
COFF objects; constructor/destructor and process-shutdown tests pass.
Built `apache_tvm_ffi_orcjit-0.1.3-py3-none-win_arm64.whl` (11.2 MB).
[Upstream native Windows ARM64
CI](https://github.com/apache/tvm-ffi/actions/runs/40298238458/job/121496554907)
passes on the PR commit. An [independent native validation run on the
fork](https://github.com/cyx-6/tvm-ffi/actions/runs/40299903473) also
passes with the same addon/core sources.
Review status: all CI checks pass, including ORC JIT wheel builds/tests
on Windows ARM64, Windows AMD64, Linux ARM64, Linux x86-64, and macOS
ARM64, plus lint, documentation, and the core test jobs.
This PR prepares the release; it does not create a tag or publish to
PyPI.
---
.github/actions/build-orcjit-wheel/action.yml | 17 ++++-
.github/workflows/ci_test.yml | 3 +-
.github/workflows/publish_orcjit_wheel.yml | 1 +
addons/tvm_ffi_orcjit/CMakeLists.txt | 15 +++-
addons/tvm_ffi_orcjit/README.md | 13 +++-
addons/tvm_ffi_orcjit/examples/quick-start/run.py | 4 +-
addons/tvm_ffi_orcjit/pyproject.toml | 4 +-
.../src/ffi/llvm_patches/win_arm64_rtdyld.cc | 89 ++++++++++++++++++++++
.../src/ffi/llvm_patches/win_arm64_rtdyld.h | 57 ++++++++++++++
.../ffi/llvm_patches/win_dll_import_generator.cc | 11 ++-
.../ffi/llvm_patches/win_dll_import_generator.h | 11 ++-
addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc | 32 +++++++-
addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h | 5 +-
addons/tvm_ffi_orcjit/tests/CMakeLists.txt | 2 +-
.../tvm_ffi_orcjit/tests/sources/c/fake_fatbin.c | 5 +-
.../tests/sources/c/test_ctor_dtor.c | 3 +
.../tests/sources/c/test_hidden_caller.c | 5 +-
.../tests/sources/c/test_hidden_helper.c | 5 +-
addons/tvm_ffi_orcjit/tests/test_basic.py | 16 ++++
addons/tvm_ffi_orcjit/tests/utils.py | 19 +++--
20 files changed, 286 insertions(+), 31 deletions(-)
diff --git a/.github/actions/build-orcjit-wheel/action.yml
b/.github/actions/build-orcjit-wheel/action.yml
index 2009fdb4..30fffa90 100644
--- a/.github/actions/build-orcjit-wheel/action.yml
+++ b/.github/actions/build-orcjit-wheel/action.yml
@@ -22,7 +22,7 @@ description: >
inputs:
arch:
- description: "Target architecture (e.g., x86_64, aarch64, arm64, AMD64)"
+ description: "Target architecture (e.g., x86_64, aarch64, arm64, AMD64,
ARM64)"
required: true
build:
description: "cibuildwheel build selector (e.g., cp312-manylinux_x86_64)"
@@ -32,7 +32,7 @@ inputs:
required: true
llvm_version:
description: "mlc-llvm-static version (keep in sync with the addon build
requirements)"
- default: "23.1.1"
+ default: "23.1.1.post0"
required: false
runs:
@@ -47,6 +47,19 @@ runs:
- uses: ./.github/actions/detect-env-vars
id: env_vars
+ - name: Activate native ARM64 Visual Studio tools
+ if: runner.os == 'Windows' && inputs.arch == 'ARM64'
+ shell: pwsh
+ run: |
+ $vsDevCmd = python .github/workflows/utils/locate_vsdevcmd_bat.py
+ $devEnv = cmd /c "`"$vsDevCmd`" -arch=arm64 -host_arch=arm64 >nul &&
set"
+ if ($LASTEXITCODE -ne 0) { throw "Failed to activate ARM64 Visual
Studio tools" }
+ foreach ($line in $devEnv) {
+ if ($line -match '^([^=]+)=(.*)$') {
+ "$($Matches[1])=$($Matches[2])" | Out-File -FilePath
$env:GITHUB_ENV -Append
+ }
+ }
+
# ---- Build and test wheels ----
- name: Build and test wheels
uses: pypa/cibuildwheel@e090b81e30c4d855ea63bf4b6e59204c09a101ae #
v4.2.1
diff --git a/.github/workflows/ci_test.yml b/.github/workflows/ci_test.yml
index bc3110cc..7a1b735f 100644
--- a/.github/workflows/ci_test.yml
+++ b/.github/workflows/ci_test.yml
@@ -235,7 +235,7 @@ jobs:
name: orcjit ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ${{ matrix.os }}
env:
- LLVM_VERSION: "23.1.1"
+ LLVM_VERSION: "23.1.1.post0"
strategy:
fail-fast: false
matrix:
@@ -244,6 +244,7 @@ jobs:
- {os: ubuntu-24.04-arm, arch: aarch64, build:
"cp312-manylinux_aarch64"}
- {os: macos-14, arch: arm64, build: "cp312-macosx_arm64"}
- {os: windows-latest, arch: AMD64, build: "cp312-win_amd64"}
+ - {os: windows-11-vs2026-arm, arch: ARM64, build: "cp312-win_arm64"}
steps:
- uses: actions/checkout@v5
diff --git a/.github/workflows/publish_orcjit_wheel.yml
b/.github/workflows/publish_orcjit_wheel.yml
index 4d33d7f1..04721a12 100644
--- a/.github/workflows/publish_orcjit_wheel.yml
+++ b/.github/workflows/publish_orcjit_wheel.yml
@@ -36,6 +36,7 @@ jobs:
- {os: ubuntu-24.04-arm, arch: aarch64, build:
"cp312-manylinux_aarch64"}
- {os: macos-14, arch: arm64, build: "cp312-macosx_arm64"}
- {os: windows-latest, arch: AMD64, build: "cp312-win_amd64"}
+ - {os: windows-11-vs2026-arm, arch: ARM64, build: "cp312-win_arm64"}
steps:
- name: Checkout repository (for local composite action)
diff --git a/addons/tvm_ffi_orcjit/CMakeLists.txt
b/addons/tvm_ffi_orcjit/CMakeLists.txt
index 7e984057..a8c04472 100644
--- a/addons/tvm_ffi_orcjit/CMakeLists.txt
+++ b/addons/tvm_ffi_orcjit/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.24)
project(
tvm_ffi_orcjit
- VERSION 0.1.2
+ VERSION 0.1.3
LANGUAGES C CXX
)
@@ -57,6 +57,10 @@ elseif (WIN32)
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
)
+ if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
+ target_sources(tvm_ffi_orcjit PRIVATE
src/ffi/llvm_patches/win_arm64_rtdyld.cc)
+ target_compile_definitions(tvm_ffi_orcjit PRIVATE
TVM_FFI_ORCJIT_WINDOWS_ARM64=1)
+ endif ()
endif ()
set_target_properties(
tvm_ffi_orcjit PROPERTIES CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
@@ -66,6 +70,15 @@ target_include_directories(
)
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND "${LLVM_DEFINITIONS}")
target_compile_definitions(tvm_ffi_orcjit PRIVATE ${LLVM_DEFINITIONS_LIST})
+# LLVM-facing subclasses must use the SDK's RTTI mode: otherwise their
typeinfo references LLVM
+# base-class typeinfo that a no-RTTI SDK does not provide.
+if (NOT LLVM_ENABLE_RTTI)
+ if (MSVC)
+ target_compile_options(tvm_ffi_orcjit PRIVATE /GR-)
+ else ()
+ target_compile_options(tvm_ffi_orcjit PRIVATE -fno-rtti)
+ endif ()
+endif ()
# Imported targets carry the static compression libraries and platform system
dependencies.
llvm_map_components_to_libnames(LLVM_LIBS Core OrcJIT Support native)
diff --git a/addons/tvm_ffi_orcjit/README.md b/addons/tvm_ffi_orcjit/README.md
index 300ca3e9..618803e8 100644
--- a/addons/tvm_ffi_orcjit/README.md
+++ b/addons/tvm_ffi_orcjit/README.md
@@ -30,7 +30,7 @@ TVM-FFI exported functions.
- **Symbol Isolation**: Separate `load_module` calls define independent symbol
namespaces, so they can define the same symbol without conflicts
- **Bounded-Range Memory**: A growable Linux slab pool keeps JIT code/data
close enough for 32-bit PC-relative relocations and supports explicit
drained-slab reclamation
- **Init/Fini Support**: Handles static constructors/destructors across ELF
(`.init_array`/`.ctors`), Mach-O (`__mod_init_func`), and COFF
(`.CRT$XC*`/`.CRT$XT*`)
-- **Cross-Platform**: Linux (x86_64, aarch64), macOS (arm64), Windows (AMD64)
+- **Cross-Platform**: Linux (x86_64, aarch64), macOS (arm64), Windows (AMD64,
ARM64)
- **Multi-Compiler**: Tested with LLVM Clang, GCC, Apple Clang, MSVC, and
clang-cl
- **TVM-FFI Integration**: Seamlessly works with TVM-FFI's stable C ABI
- **Python API**: Simple Pythonic interface for JIT compilation and execution
@@ -44,7 +44,7 @@ can be loaded and executed by the ORC JIT:
| -------- | --------- | :-: | :-: |
| Linux (x86_64, aarch64) | LLVM Clang, GCC | yes | yes |
| macOS (arm64) | LLVM Clang, Apple Clang | yes | yes |
-| Windows (AMD64) | LLVM Clang, MSVC, clang-cl | yes | no |
+| Windows (AMD64, ARM64) | LLVM Clang, MSVC, clang-cl | yes | no |
Windows is C-only across all compilers. C++ objects compiled with
`TVM_FFI_DLL_EXPORT_TYPED_FUNC` use `try`/`catch` (via
`TVM_FFI_SAFE_CALL_BEGIN/END`),
@@ -52,6 +52,11 @@ which requires Itanium exception ABI symbols
(`__cxa_begin_catch`,
`__gxx_personality_v0`, etc.) that the MSVC-built host process cannot provide.
Pure C objects using the `TVMFFISafeCallType` ABI work on all platforms.
+Windows ARM64 uses LLVM's `RTDyldObjectLinkingLayer` because LLVM 23.1.1
+does not implement COFF/AArch64 in JITLink. RuntimeDyld handles ARM64
relocations,
+long-branch stubs, and DLL-import slots; the addon preserves `.CRT$XC*` /
`.CRT$XT*`
+constructor/destructor handling. Windows AMD64 continues to use JITLink.
+
## Installation
### Install from PyPI
@@ -92,7 +97,7 @@ pip install -e .
```
For a build with `--no-build-isolation`, install the build requirements,
including
-`mlc-llvm-static==23.1.1`, into the same Python environment first.
`LLVM_PREFIX`
+`mlc-llvm-static==23.1.1.post0`, into the same Python environment first.
`LLVM_PREFIX`
and `LLVM_DIR` do not select a different SDK.
On Linux, each `load_module` call follows the same default compiler selection
@@ -227,7 +232,7 @@ tvm_ffi_orcjit/
## CI
-Runs on Linux (x86_64, aarch64), macOS (arm64), Windows (AMD64) via
+Runs on Linux (x86_64, aarch64), macOS (arm64), Windows (AMD64, ARM64) via
`cibuildwheel`. Each platform builds test objects with multiple compilers
and runs the full test suite. See the `orcjit` job in
`.github/workflows/ci_test.yml`.
diff --git a/addons/tvm_ffi_orcjit/examples/quick-start/run.py
b/addons/tvm_ffi_orcjit/examples/quick-start/run.py
index 000848cd..d2aad44a 100755
--- a/addons/tvm_ffi_orcjit/examples/quick-start/run.py
+++ b/addons/tvm_ffi_orcjit/examples/quick-start/run.py
@@ -43,8 +43,8 @@ SCRIPT_DIR = Path(__file__).resolve().parent
def _extra_cflags() -> list[str]:
"""Return extra compiler flags for the current platform."""
- machine = platform.machine()
- if machine in ("aarch64", "arm64"):
+ machine = platform.machine().lower()
+ if platform.system() != "Windows" and machine in ("aarch64", "arm64"):
return ["-mno-outline-atomics"]
return []
diff --git a/addons/tvm_ffi_orcjit/pyproject.toml
b/addons/tvm_ffi_orcjit/pyproject.toml
index 0f9f4c15..8fe93e3d 100644
--- a/addons/tvm_ffi_orcjit/pyproject.toml
+++ b/addons/tvm_ffi_orcjit/pyproject.toml
@@ -19,13 +19,13 @@
requires = [
"scikit-build-core>=0.10.0",
"apache-tvm-ffi>=0.1.14.post1",
- "mlc-llvm-static==23.1.1",
+ "mlc-llvm-static==23.1.1.post0",
]
build-backend = "scikit_build_core.build"
[project]
name = "apache-tvm_ffi_orcjit"
-version = "0.1.2"
+version = "0.1.3"
description = "Load TVM-FFI exported object files using LLVM ORC JIT v2"
readme = "README.md"
requires-python = ">=3.10"
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_arm64_rtdyld.cc
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_arm64_rtdyld.cc
new file mode 100644
index 00000000..6d7d0f8a
--- /dev/null
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_arm64_rtdyld.cc
@@ -0,0 +1,89 @@
+/*
+ * 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 win_arm64_rtdyld.cc
+ * \brief RTDyld fallback for Windows ARM64 object files.
+ *
+ * See win_arm64_rtdyld.h for the trigger, symptom and removal procedure.
+ */
+#include "win_arm64_rtdyld.h"
+
+#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
+#include <llvm/ExecutionEngine/SectionMemoryManager.h>
+
+#include <cstdint>
+#include <memory>
+
+namespace tvm {
+namespace ffi {
+namespace orcjit {
+
+std::unique_ptr<llvm::orc::ObjectLayer> CreateWindowsArm64ObjectLinkingLayer(
+ llvm::orc::ExecutionSession& execution_session, ORCJITExecutionSessionObj*
session) {
+ auto layer = std::make_unique<llvm::orc::RTDyldObjectLinkingLayer>(
+ execution_session,
+ [](const llvm::MemoryBuffer&) { return
std::make_unique<llvm::SectionMemoryManager>(); });
+ layer->setOverrideObjectFlagsWithResponsibilityFlags(true);
+ layer->setAutoClaimResponsibilityForObjectSymbols(true);
+ layer->setNotifyLoaded([session](llvm::orc::MaterializationResponsibility&
responsibility,
+ const llvm::object::ObjectFile& object,
+ const llvm::RuntimeDyld::LoadedObjectInfo&
loaded) {
+ auto& dylib = responsibility.getTargetJITDylib();
+ for (const auto& section : object.sections()) {
+ auto name = section.getName();
+ if (!name) {
+ llvm::consumeError(name.takeError());
+ continue;
+ }
+ bool is_init = name->starts_with(".CRT$XC");
+ if (!is_init && !name->starts_with(".CRT$XT")) continue;
+ auto address = loaded.getSectionLoadAddress(section);
+ if (!address) continue;
+ auto suffix = name->substr(7);
+ int priority = 0;
+ if (!suffix.empty()) {
+ priority = static_cast<int>(suffix[0]) * 100000;
+ if (suffix.size() > 1) {
+ int number = 0;
+ suffix.substr(1).getAsInteger(10, number);
+ priority += number;
+ }
+ }
+ // NotifyLoaded precedes relocation. Keep addresses of the pointer
+ // slots; RunInitFiniEntries dereferences them after materialization.
+ constexpr uint64_t kPointerSize = 8;
+ for (uint64_t offset = 0; offset + kPointerSize <= section.getSize();
+ offset += kPointerSize) {
+ ORCJITExecutionSessionObj::InitFiniEntry
entry{llvm::orc::ExecutorAddr(address + offset),
+ priority, true};
+ if (is_init) {
+ session->AddPendingInitializer(&dylib, entry);
+ } else {
+ session->AddPendingDeinitializer(&dylib, entry);
+ }
+ }
+ }
+ });
+ return layer;
+}
+
+} // namespace orcjit
+} // namespace ffi
+} // namespace tvm
diff --git a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_arm64_rtdyld.h
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_arm64_rtdyld.h
new file mode 100644
index 00000000..6917f475
--- /dev/null
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_arm64_rtdyld.h
@@ -0,0 +1,57 @@
+/*
+ * 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 win_arm64_rtdyld.h
+ * \brief RTDyld fallback for Windows ARM64 object files.
+ *
+ * LLVM 23.1.1 JITLink rejects COFF/AArch64 objects. RuntimeDyld supports
+ * their relocations, long branches and DLL-import pointer slots, so use
+ * RTDyldObjectLinkingLayer on this platform. A loaded-object callback records
+ * .CRT$XC* / .CRT$XT* entries for the session's normal init/fini drain.
+ *
+ * Trigger: loading any native Windows ARM64 COFF object.
+ * Symptom without the patch: "Unsupported target machine architecture in
+ * COFF object ...: ARM64" from JITLink.
+ *
+ * Removal: when LLVM gains a usable COFF/AArch64 JITLink backend, switch
+ * Windows ARM64 to ObjectLinkingLayer and InitFiniPlugin, add AArch64 stubs
+ * to DLLImportDefinitionGenerator, and delete this adapter.
+ */
+#ifndef TVM_FFI_ORCJIT_LLVM_PATCHES_WIN_ARM64_RTDYLD_H_
+#define TVM_FFI_ORCJIT_LLVM_PATCHES_WIN_ARM64_RTDYLD_H_
+
+#include <llvm/ExecutionEngine/Orc/Layer.h>
+
+#include <memory>
+
+#include "../orcjit_session.h"
+
+namespace tvm {
+namespace ffi {
+namespace orcjit {
+
+std::unique_ptr<llvm::orc::ObjectLayer> CreateWindowsArm64ObjectLinkingLayer(
+ llvm::orc::ExecutionSession& execution_session, ORCJITExecutionSessionObj*
session);
+
+} // namespace orcjit
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_ORCJIT_LLVM_PATCHES_WIN_ARM64_RTDYLD_H_
diff --git
a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.cc
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.cc
index 901f627f..7bfb0242 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.cc
@@ -39,6 +39,7 @@
#include <llvm/ADT/DenseMap.h>
#include <llvm/ExecutionEngine/JITLink/JITLink.h>
#include <llvm/ExecutionEngine/JITLink/x86_64.h>
+#include <llvm/ExecutionEngine/Orc/AbsoluteSymbols.h>
#include <llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h>
#include <llvm/Support/DynamicLibrary.h>
#include <llvm/Support/Error.h>
@@ -126,6 +127,14 @@ llvm::Error DLLImportDefinitionGenerator::tryToGenerate(
}
if (Resolved.empty()) return llvm::Error::success();
+ if (L_ == nullptr) {
+ llvm::orc::SymbolMap Symbols;
+ for (auto& [Name, Addr] : Resolved) {
+ Symbols[Name] = llvm::orc::ExecutorSymbolDef(Addr,
llvm::JITSymbolFlags::Exported);
+ }
+ return JD.define(llvm::orc::absoluteSymbols(std::move(Symbols)));
+ }
+
// Step 2: Build a LinkGraph with __imp_ pointers and PLT jump stubs.
auto G = std::make_unique<llvm::jitlink::LinkGraph>(
"<DLL_IMPORT_STUBS>", ES_.getSymbolStringPool(), ES_.getTargetTriple(),
@@ -149,7 +158,7 @@ llvm::Error DLLImportDefinitionGenerator::tryToGenerate(
G->addDefinedSymbol(StubBlock, 0, *InternedName, StubBlock.getSize(),
llvm::jitlink::Linkage::Strong,
llvm::jitlink::Scope::Default, true, false);
}
- return L_.add(JD, std::move(G));
+ return L_->add(JD, std::move(G));
}
} // namespace orcjit
diff --git
a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.h
b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.h
index 3b0d34c4..0c3ccbe8 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/llvm_patches/win_dll_import_generator.h
@@ -19,7 +19,7 @@
/*!
* \file win_dll_import_generator.h
- * \brief Windows DLL-import symbol generator for JIT code (COFF/x86_64).
+ * \brief Windows DLL-import symbol generator for JIT code.
*
* On Windows with the MSVC ABI, COFF objects reference DLL-imported
* symbols via `__imp_XXX` pointer stubs and direct calls. Without
@@ -37,6 +37,8 @@
* (`jmp [__imp_XXX]`) so direct calls stay in range. All stubs
* land in JIT memory, keeping every PCRel32 fixup within ±2 GB
* of the JIT arena.
+ * On ARM64, RTDyld creates its own import slots and long-branch stubs.
+ * The generator only supplies the absolute addresses of the base symbols.
*
* Trigger: any Windows x86_64 JIT graph that references a DLL-imported
* symbol (which, for MSVC-compiled objects, is effectively
@@ -70,7 +72,10 @@ namespace orcjit {
*/
class DLLImportDefinitionGenerator : public llvm::orc::DefinitionGenerator {
public:
- DLLImportDefinitionGenerator(llvm::orc::ExecutionSession& ES,
llvm::orc::ObjectLinkingLayer& L)
+ // A null layer selects absolute addresses for RTDyld, which creates its own
+ // architecture-specific import slots and branch stubs.
+ explicit DLLImportDefinitionGenerator(llvm::orc::ExecutionSession& ES,
+ llvm::orc::ObjectLinkingLayer* L =
nullptr)
: ES_(ES), L_(L) {}
llvm::Error tryToGenerate(llvm::orc::LookupState& LS, llvm::orc::LookupKind
K,
@@ -81,7 +86,7 @@ class DLLImportDefinitionGenerator : public
llvm::orc::DefinitionGenerator {
static void* FindInProcessModules(const std::string& Name);
llvm::orc::ExecutionSession& ES_;
- llvm::orc::ObjectLinkingLayer& L_;
+ llvm::orc::ObjectLinkingLayer* L_;
};
} // namespace orcjit
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
index d525367d..abc412e9 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.cc
@@ -40,6 +40,7 @@
#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <mutex>
#include <sstream>
@@ -66,6 +67,9 @@
#include "llvm_patches/win_coff_pdata_strip.h"
#include "llvm_patches/win_dll_import_generator.h"
+#ifdef TVM_FFI_ORCJIT_WINDOWS_ARM64
+#include "llvm_patches/win_arm64_rtdyld.h"
+#endif
#endif
namespace tvm {
@@ -264,6 +268,14 @@ ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const
Optional<Variant<Stri
return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES,
memory_manager);
});
} // if (memory_manager_)
+#elif defined(TVM_FFI_ORCJIT_WINDOWS_ARM64)
+ // LLVM 23's JITLink has no COFF/AArch64 backend. RTDyld supports this
+ // format and supplies its own long-branch stubs and DLL-import slots.
+ builder.setObjectLinkingLayerCreator(
+ [this](llvm::orc::ExecutionSession& ES,
llvm::jitlink::JITLinkMemoryManager&)
+ -> llvm::Expected<std::unique_ptr<llvm::orc::ObjectLayer>> {
+ return CreateWindowsArm64ObjectLinkingLayer(ES, this);
+ });
#elif defined(__APPLE__) || defined(_WIN32)
// Force ObjectLinkingLayer (JITLink) so we can attach InitFiniPlugin.
// macOS: LLJIT already defaults to JITLink for Darwin, but the explicit
@@ -337,12 +349,12 @@
ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const Optional<Variant<Stri
has_orc_platform_ = SetUpOrcPlatform(builder, orc_rt);
setup_builder(builder);
jit_ = TVM_FFI_ORCJIT_LLVM_CALL(builder.create());
-#ifdef _WIN32
+#if defined(_WIN32) && !defined(TVM_FFI_ORCJIT_WINDOWS_ARM64)
// Strip .pdata/.xdata relocations from COFF objects before JITLink graph
// building. See llvm_patches/win_coff_pdata_strip.h for the rationale.
jit_->getObjTransformLayer().setTransform(&StripCoffPdataXdata);
#endif
-#if defined(__APPLE__) || defined(_WIN32)
+#if defined(__APPLE__) || (defined(_WIN32) &&
!defined(TVM_FFI_ORCJIT_WINDOWS_ARM64))
// Mach-O and COFF still need the local lifecycle adapter. Linux uses LLVM
// 23's ELFNixPlatform initialize/deinitialize path instead.
auto& objlayer = jit_->getObjLinkingLayer();
@@ -355,9 +367,13 @@ ORCJITExecutionSessionObj::ORCJITExecutionSessionObj(const
Optional<Variant<Stri
// comprehensive generator that searches all loaded DLLs (vcruntime140,
// ucrtbase, tvm_ffi, etc.) and creates __imp_* pointer stubs.
if (auto PSG = jit_->getProcessSymbolsJITDylib()) {
+#ifdef TVM_FFI_ORCJIT_WINDOWS_ARM64
+
PSG->addGenerator(std::make_unique<DLLImportDefinitionGenerator>(jit_->getExecutionSession()));
+#else
auto& ObjLayer =
static_cast<llvm::orc::ObjectLinkingLayer&>(jit_->getObjLinkingLayer());
PSG->addGenerator(
-
std::make_unique<DLLImportDefinitionGenerator>(jit_->getExecutionSession(),
ObjLayer));
+
std::make_unique<DLLImportDefinitionGenerator>(jit_->getExecutionSession(),
&ObjLayer));
+#endif
}
#endif
}
@@ -528,7 +544,15 @@
ORCJITExecutionSessionObj::DrainPendingDeinitializers(llvm::orc::JITDylib& jit_d
void ORCJITExecutionSessionObj::RunInitFiniEntries(const
std::vector<InitFiniEntry>& entries) {
for (const auto& entry : entries) {
- entry.address.toPtr<CtorDtor>()();
+ auto address = entry.address;
+ if (entry.indirect) {
+ std::uintptr_t value;
+ std::memcpy(&value, address.toPtr<const void*>(), sizeof(value));
+ address = llvm::orc::ExecutorAddr(value);
+ }
+ if (address && address.getValue() != static_cast<std::uintptr_t>(-1)) {
+ address.toPtr<CtorDtor>()();
+ }
}
}
diff --git a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
index ada37fe3..3c9e3d0e 100644
--- a/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
+++ b/addons/tvm_ffi_orcjit/src/ffi/orcjit_session.h
@@ -146,6 +146,9 @@ class ORCJITExecutionSessionObj : public Object {
struct InitFiniEntry {
llvm::orc::ExecutorAddr address;
int priority;
+ // RTDyld reports section addresses before relocation. Read the relocated
+ // function pointer from this slot when running the entry after emission.
+ bool indirect{false};
};
/*!
@@ -164,7 +167,7 @@ class ORCJITExecutionSessionObj : public Object {
/*! \brief Run drained init/fini entries in order. Call with the lock
released. */
static void RunInitFiniEntries(const std::vector<InitFiniEntry>& entries);
- // Called by InitFiniPlugin during materialization, which the triggering
+ // Called by the lifecycle observer during materialization, which the
triggering
// GetSymbol runs while already holding mutex_ — so these do not lock.
void AddPendingInitializer(llvm::orc::JITDylib* jd, const InitFiniEntry&
entry);
void AddPendingDeinitializer(llvm::orc::JITDylib* jd, const InitFiniEntry&
entry);
diff --git a/addons/tvm_ffi_orcjit/tests/CMakeLists.txt
b/addons/tvm_ffi_orcjit/tests/CMakeLists.txt
index e5e0f338..95776bf4 100644
--- a/addons/tvm_ffi_orcjit/tests/CMakeLists.txt
+++ b/addons/tvm_ffi_orcjit/tests/CMakeLists.txt
@@ -27,7 +27,7 @@ set(TEST_OBJ_INSTALL_SUFFIX
CACHE STRING "Suffix for install subdirectory (e.g., -gcc)"
)
-if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
+if (NOT WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
add_compile_options(-mno-outline-atomics)
endif ()
diff --git a/addons/tvm_ffi_orcjit/tests/sources/c/fake_fatbin.c
b/addons/tvm_ffi_orcjit/tests/sources/c/fake_fatbin.c
index 867ac730..ffb69a73 100644
--- a/addons/tvm_ffi_orcjit/tests/sources/c/fake_fatbin.c
+++ b/addons/tvm_ffi_orcjit/tests/sources/c/fake_fatbin.c
@@ -30,7 +30,10 @@
#include <stdint.h>
#include <tvm/ffi/c_api.h>
-#ifdef __APPLE__
+#ifdef _MSC_VER
+#pragma section(".nv_fatbin", read)
+__declspec(allocate(".nv_fatbin"))
+#elif defined(__APPLE__)
__attribute__((section("__DATA,.nv_fatbin"), used))
#else
__attribute__((section(".nv_fatbin"), used))
diff --git a/addons/tvm_ffi_orcjit/tests/sources/c/test_ctor_dtor.c
b/addons/tvm_ffi_orcjit/tests/sources/c/test_ctor_dtor.c
index 5af1ea67..221073a5 100644
--- a/addons/tvm_ffi_orcjit/tests/sources/c/test_ctor_dtor.c
+++ b/addons/tvm_ffi_orcjit/tests/sources/c/test_ctor_dtor.c
@@ -123,6 +123,9 @@ __attribute__((section("__DATA,__mod_init_func"), used))
static ctor_t mod_init_
static void __cdecl crt_init_a(void) { puts_log("<crt.XCA>"); }
__declspec(allocate(".CRT$XCA")) ctor_t __tvm_test_crt_init_a = crt_init_a;
+/* CRT boundary slots may be null or all-bits-one sentinels. */
+__declspec(allocate(".CRT$XCA")) ctor_t __tvm_test_crt_init_null = NULL;
+__declspec(allocate(".CRT$XCA")) ctor_t __tvm_test_crt_init_sentinel =
(ctor_t)(intptr_t)-1;
static void __cdecl crt_init_b(void) { puts_log("<crt.XCB>"); }
__declspec(allocate(".CRT$XCB")) ctor_t __tvm_test_crt_init_b = crt_init_b;
diff --git a/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_caller.c
b/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_caller.c
index 4e332853..b1106601 100644
--- a/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_caller.c
+++ b/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_caller.c
@@ -33,7 +33,10 @@
#include <tvm/ffi/c_api.h>
/* Same hidden declaration — compiler uses ADRP+ADD to take address */
-__attribute__((visibility("hidden"))) extern int64_t hidden_helper_add(int64_t
a, int64_t b);
+#ifndef _MSC_VER
+__attribute__((visibility("hidden")))
+#endif
+extern int64_t hidden_helper_add(int64_t a, int64_t b);
typedef int64_t (*binop_t)(int64_t, int64_t);
diff --git a/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_helper.c
b/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_helper.c
index 811218e3..dc3c077e 100644
--- a/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_helper.c
+++ b/addons/tvm_ffi_orcjit/tests/sources/c/test_hidden_helper.c
@@ -30,7 +30,10 @@
#include <tvm/ffi/c_api.h>
/* Hidden visibility: caller uses ADRP+ADD instead of GOT */
-__attribute__((visibility("hidden"))) int64_t hidden_helper_add(int64_t a,
int64_t b) {
+#ifndef _MSC_VER
+__attribute__((visibility("hidden")))
+#endif
+int64_t hidden_helper_add(int64_t a, int64_t b) {
return a + b;
}
diff --git a/addons/tvm_ffi_orcjit/tests/test_basic.py
b/addons/tvm_ffi_orcjit/tests/test_basic.py
index b2f82700..a1902c00 100644
--- a/addons/tvm_ffi_orcjit/tests/test_basic.py
+++ b/addons/tvm_ffi_orcjit/tests/test_basic.py
@@ -25,6 +25,7 @@ the module tears the dylib down.
from __future__ import annotations
import gc
+import platform
import sys
import tempfile
import threading
@@ -43,6 +44,21 @@ from utils import build_test_objects
OBJ_DIR = build_test_objects()
+
[email protected](
+ sys.platform != "win32" or platform.machine().lower() not in ("arm64",
"aarch64"),
+ reason="requires native Windows ARM64",
+)
+def test_windows_arm64_native_compilers() -> None:
+ """Require real ARM64 objects from both MSVC and clang-cl in native CI."""
+ for variant in ("c", "c-msvc"):
+ path = OBJ_DIR / variant / "test_funcs.o"
+ assert path.exists(), f"{variant} compiler did not build the ARM64
test objects"
+ assert int.from_bytes(path.read_bytes()[:2], "little") == 0xAA64
+ mod = ExecutionSession().load_module(str(path))
+ assert mod.test_add(10, 20) == 30
+
+
_KNOWN_SUBDIRS = [
"cc",
"c", # default (LLVM clang)
diff --git a/addons/tvm_ffi_orcjit/tests/utils.py
b/addons/tvm_ffi_orcjit/tests/utils.py
index 2a90ff25..db3f14cc 100644
--- a/addons/tvm_ffi_orcjit/tests/utils.py
+++ b/addons/tvm_ffi_orcjit/tests/utils.py
@@ -52,15 +52,15 @@ _DEFAULT_OUT_DIR = Path(tempfile.gettempdir()) /
"tvm_ffi_orcjit_tests"
def _extra_cflags() -> list[str]:
- machine = platform.machine()
- if machine in ("aarch64", "arm64"):
+ machine = platform.machine().lower()
+ if platform.system() != "Windows" and machine in ("aarch64", "arm64"):
return ["-mno-outline-atomics"]
return []
def _extra_cuda_cflags() -> list[str]:
- machine = platform.machine()
- if machine in ("aarch64", "arm64"):
+ machine = platform.machine().lower()
+ if platform.system() != "Windows" and machine in ("aarch64", "arm64"):
return ["-Xcompiler", "-mno-outline-atomics"]
return []
@@ -214,6 +214,13 @@ def build_test_objects(out_dir: Path | None = None) ->
Path:
)
elif system == "Windows":
+ # The runner may ship an x64-hosted clang-cl. Select native ARM64
+ # explicitly rather than inheriting that compiler's default target.
+ clang_target = (
+ ["--target=aarch64-pc-windows-msvc"]
+ if platform.machine().lower() in ("arm64", "aarch64")
+ else []
+ )
llvm = _find_llvm_clang()
if llvm:
clang_cl = Path(llvm[0]).parent / "clang-cl.exe"
@@ -222,7 +229,7 @@ def build_test_objects(out_dir: Path | None = None) -> Path:
"LLVM clang-cl",
cc=str(clang_cl),
cxx=None,
- extra_cflags=["/GS-"],
+ extra_cflags=["/GS-", *clang_target],
c_outdir=out_dir / "c",
cc_outdir=None,
)
@@ -240,7 +247,7 @@ def build_test_objects(out_dir: Path | None = None) -> Path:
"clang-cl",
cc="clang-cl",
cxx=None,
- extra_cflags=["/GS-"],
+ extra_cflags=["/GS-", *clang_target],
c_outdir=out_dir / "c-clang-cl",
cc_outdir=None,
)