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,
             )

Reply via email to