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 68932438 [CI][ORCJIT] Source manylinux LLVM from a release, link 
libstdc++ dynamically (#688)
68932438 is described below

commit 68932438dc809b1199f9ac80dc458b73b4e7ec7d
Author: Yaxing Cai <[email protected]>
AuthorDate: Wed Jul 29 14:02:43 2026 +0800

    [CI][ORCJIT] Source manylinux LLVM from a release, link libstdc++ 
dynamically (#688)
    
    ## Motivation
    
    The orcjit addon links against conda-forge's LLVM, which is built with a
    newer GCC whose libstdc++ carries `GLIBCXX_3.4.29+` symbols. Those
    exceed the `manylinux_2_28` floor this project standardized on in #675,
    which is why the addon has carried a
    `-static-libstdc++`/`-static-libgcc` workaround since the original addon
    PR (#254).
    
    This builds LLVM from source inside the `manylinux_2_28` image instead,
    publishes the install prefix as a release asset, and has CI download it.
    Because that toolchain already sits at the manylinux ABI floor, the
    workaround is no longer needed.
    
    ## What changes
    
    * **LLVM acquisition (Linux).** `/opt/llvm` is obtained by downloading
    `llvm-<version>-linux-<arch>.tar.zst` and extracting it, instead of
    being built or installed via conda. macOS/Windows keep the conda-forge
    binaries (no GLIBCXX floor); their `actions/cache` step is split out so
    their install gating is unchanged. Everything downstream is untouched —
    same `/opt/llvm`, same `LLVM_PREFIX`, same bind-mount into the build
    container.
    * **Drop `-static-libstdc++`/`-static-libgcc`,** so the addon no longer
    bakes in a private copy of the C++ runtime.
    
    The by-name `--exclude-libs` list is deliberately **kept** rather than
    collapsed to `--exclude-libs,ALL`. I tried that; it fails. The embedded
    `liborc_rt.a` is linked by the JIT at run time and resolves its C++
    runtime through the process, including archive-only helpers such as
    `_ZSt28__throw_bad_array_new_lengthv` that `libstdc++.so` does not
    export — `ALL` localizes those and the ORC platform fails to materialize
    (`Failed to materialize symbols: { (<Platform>, ...) }`). That holds
    whether libstdc++ is static or dynamic, so the comment there is
    corrected rather than the flag changed.
    
    ## Results
    
    Full CI green on all four platforms. The repaired wheel names are the
    ABI evidence — auditwheel tags them:
    
    ```
    
apache_tvm_ffi_orcjit-0.1.1-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
    ```
    
    i.e. the prefix stays under the floor with libstdc++ linked dynamically.
    The addon's exported dynamic symbols drop from ~965 to ~30, since it no
    longer re-exports a private libstdc++ that could interpose with a host's
    own copy (e.g. PyTorch's bundled LLVM), and the C++ runtime references
    (`_Znwm`, `__cxa_throw`, `__gxx_personality_v0`) are now `UND`, served
    by the process. Tests: 89 passed x86_64 / 87 passed aarch64, plus the
    quick-start C and C++ examples.
    
    Prebuilt LLVM is produced out-of-band, so this also removes a ~50 minute
    from-source build from the CI path; the Linux orcjit jobs now spend a
    few minutes downloading.
    
    ## Notes for reviewers
    
    Two things worth flagging that are not visible in the diff:
    
    1. **Artifact provenance.** Linux LLVM now comes from a release asset in
    `mlc-ai/package`, which is outside the ASF org. The asset is currently a
    **pre-release**, and the workflow that produces it lives on an unmerged
    branch there. That side should land (and ideally be promoted out of
    pre-release) before this is merged — hence the draft status. The
    download source is parameterized via the `llvm_release_repo` input if
    you would prefer it mirrored elsewhere.
    2. **`publish_orcjit_wheel.yml` inherits this.** It calls the same
    composite action without passing `llvm_version`/`llvm_release_repo`, so
    published PyPI wheels will also start sourcing LLVM from that release.
    This is a supply-chain change to a published artifact that the diff does
    not visibly touch.
    
    Builds on #675 (`manylinux_2_28` standardization), which is what makes
    the floor the right target.
---
 .github/actions/build-orcjit-wheel/action.yml | 63 ++++++++++++++++++++++-----
 .github/workflows/ci_test.yml                 | 13 ++++++
 addons/tvm_ffi_orcjit/CMakeLists.txt          | 17 +++++---
 3 files changed, 76 insertions(+), 17 deletions(-)

diff --git a/.github/actions/build-orcjit-wheel/action.yml 
b/.github/actions/build-orcjit-wheel/action.yml
index bc2a2ad5..01ff4c2a 100644
--- a/.github/actions/build-orcjit-wheel/action.yml
+++ b/.github/actions/build-orcjit-wheel/action.yml
@@ -30,6 +30,17 @@ inputs:
   checkout_ref:
     description: "Branch, tag, or SHA to check out before building"
     required: true
+  llvm_version:
+    description: "LLVM version (selects the release tag / conda pin)"
+    default: "22.1.0"
+    required: false
+  llvm_release_repo:
+    description: >
+      GitHub repo hosting the prebuilt LLVM release assets (Linux only). The
+      asset llvm-<version>-linux-<arch>.tar.zst is downloaded from the
+      llvm-<version> tag. Point at a fork to validate before the canonical 
repo.
+    default: "mlc-ai/package"
+    required: false
 
 runs:
   using: "composite"
@@ -43,17 +54,44 @@ runs:
     - uses: ./.github/actions/detect-env-vars
       id: env_vars
 
-    # ---- Cache LLVM prefix ----
-    - name: Cache LLVM
+    # ---- Acquire LLVM prefix ----
+    # Linux: downloaded as a prebuilt tarball from a GitHub Release (built from
+    #   source with the manylinux gcc-toolset so the ABI stays under the floor)
+    #   and extracted to /opt/llvm. Permanent storage -- no actions/cache 
lifetime.
+    # macOS/Windows: no GLIBCXX floor -- keep the conda-forge binaries, cached.
+    - name: Cache LLVM (macOS/Windows)
+      if: runner.os != 'Linux'
       uses: actions/cache@v4
       id: llvm-cache
       with:
         path: ${{ runner.os == 'Windows' && 'C:/opt/llvm' || '/opt/llvm' }}
-        key: llvm-22.1.0-${{ runner.os }}-${{ inputs.arch }}-v3
+        key: llvm-${{ inputs.llvm_version }}-${{ runner.os }}-${{ inputs.arch 
}}-v4
 
-    # ---- Install LLVM via conda (cache miss only) ----
+    - name: Download prebuilt LLVM (Linux)
+      if: runner.os == 'Linux'
+      shell: bash
+      run: |
+        set -euo pipefail
+        asset="llvm-${{ inputs.llvm_version }}-linux-${{ inputs.arch 
}}.tar.zst"
+        base="https://github.com/${{ inputs.llvm_release_repo 
}}/releases/download"
+        url="${base}/llvm-${{ inputs.llvm_version }}/${asset}"
+        echo "Downloading $url"
+        curl -fL --retry 5 --retry-all-errors -o "$RUNNER_TEMP/$asset" "$url"
+        sudo mkdir -p /opt/llvm
+        sudo tar --zstd -xf "$RUNNER_TEMP/$asset" -C /opt/llvm
+        sudo chown -R "$(id -u):$(id -g)" /opt/llvm
+
+    - name: Verify LLVM prefix (Linux)
+      if: runner.os == 'Linux'
+      shell: bash
+      run: |
+        test -x /opt/llvm/bin/llvm-config \
+          || { echo "::error::LLVM prefix missing; the release download must 
succeed first"; exit 1; }
+        /opt/llvm/bin/llvm-config --version
+
+    # ---- Install LLVM via conda (macOS/Windows, cache miss only) ----
     - name: Setup conda
-      if: steps.llvm-cache.outputs.cache-hit != 'true'
+      if: steps.llvm-cache.outputs.cache-hit != 'true' && runner.os != 'Linux'
       uses: 
conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167  # 
v3.3.0
       continue-on-error: true
       id: conda1
@@ -61,7 +99,7 @@ runs:
         miniforge-version: latest
 
     - name: Setup conda (retry with tar.bz2)
-      if: steps.llvm-cache.outputs.cache-hit != 'true' && steps.conda1.outcome 
== 'failure'
+      if: steps.llvm-cache.outputs.cache-hit != 'true' && runner.os != 'Linux' 
&& steps.conda1.outcome == 'failure'
       uses: 
conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167  # 
v3.3.0
       with:
         miniforge-version: latest
@@ -72,19 +110,20 @@ runs:
       shell: bash
       run: sudo mkdir -p /opt/llvm && sudo chown -R $(whoami) /opt/llvm
 
-    - name: Install LLVM (Unix)
-      if: steps.llvm-cache.outputs.cache-hit != 'true' && runner.os != 
'Windows'
+    - name: Install LLVM (macOS)
+      if: steps.llvm-cache.outputs.cache-hit != 'true' && runner.os == 'macOS'
       shell: bash -l {0}
       run: |
         conda create -q -p /opt/llvm -c conda-forge \
-          llvmdev=22.1.0 clangdev=22.1.0 compiler-rt=22.1.0 zlib zstd-static \
+          llvmdev=${{ inputs.llvm_version }} clangdev=${{ inputs.llvm_version 
}} \
+          compiler-rt=${{ inputs.llvm_version }} zlib zstd-static \
           -y
 
     - name: Install LLVM (Windows)
       if: steps.llvm-cache.outputs.cache-hit != 'true' && runner.os == 
'Windows'
       shell: cmd /C call {0}
       run: |
-        conda create -q -p C:\opt\llvm -c conda-forge llvmdev=22.1.0 zlib 
zstd-static -y
+        conda create -q -p C:\opt\llvm -c conda-forge llvmdev=${{ 
inputs.llvm_version }} zlib zstd-static -y
 
     # ---- Build and test wheels ----
     - name: Build and test wheels
@@ -97,6 +136,10 @@ runs:
         CIBW_ARCHS_LINUX: ${{ inputs.arch }}
         CIBW_ARCHS_MACOS: ${{ inputs.arch }}
         CIBW_ARCHS_WINDOWS: ${{ inputs.arch }}
+        # Link inside manylinux_2_28 -- the same image family the downloaded 
LLVM
+        # was built in (gcc-toolset-14). An older image couldn't consume its 
libs.
+        CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
+        CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
         CIBW_BUILD_VERBOSITY: 1
         CMAKE_BUILD_PARALLEL_LEVEL: ${{ steps.env_vars.outputs.cpu_count }}
         CIBW_ENVIRONMENT: LLVM_PREFIX=/opt/llvm
diff --git a/.github/workflows/ci_test.yml b/.github/workflows/ci_test.yml
index 6be07081..ab0486de 100644
--- a/.github/workflows/ci_test.yml
+++ b/.github/workflows/ci_test.yml
@@ -209,6 +209,11 @@ jobs:
         run: |
           cargo test
 
+  # Build + test the orcjit wheel. On Linux the action downloads a prebuilt 
LLVM
+  # tarball from a GitHub Release (built from source under the manylinux
+  # gcc-toolset so the ABI stays under the floor); macOS/Windows install LLVM 
via
+  # conda inside the action. The LLVM release is produced out-of-band (rarely, 
on
+  # version bump) by mlc-ai/package's build_llvm workflow -- not on every CI 
run.
   orcjit:
     needs: [lint, prepare]
     if: >
@@ -217,6 +222,12 @@ jobs:
       needs.prepare.outputs.should_skip_ci_docs_only != 'true'
     name: orcjit ${{ matrix.os }} (${{ matrix.arch }})
     runs-on: ${{ matrix.os }}
+    env:
+      LLVM_VERSION: "22.1.0"
+      # 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.
+      LLVM_RELEASE_REPO: "mlc-ai/package"
     strategy:
       fail-fast: false
       matrix:
@@ -237,6 +248,8 @@ jobs:
           arch: ${{ matrix.arch }}
           build: ${{ matrix.build }}
           checkout_ref: ${{ github.sha }}
+          llvm_version: ${{ env.LLVM_VERSION }}
+          llvm_release_repo: ${{ env.LLVM_RELEASE_REPO }}
 
       - uses: actions/upload-artifact@v4
         with:
diff --git a/addons/tvm_ffi_orcjit/CMakeLists.txt 
b/addons/tvm_ffi_orcjit/CMakeLists.txt
index bdb2952f..cda2c9c4 100644
--- a/addons/tvm_ffi_orcjit/CMakeLists.txt
+++ b/addons/tvm_ffi_orcjit/CMakeLists.txt
@@ -148,17 +148,20 @@ if (APPLE)
             $<TARGET_FILE:tvm_ffi_orcjit>
     COMMENT "Fixing libc++ rpath to use system library"
   )
-elseif (UNIX AND NOT WIN32)
-  # Static libstdc++/libgcc: the conda LLVM toolchain's libstdc++ is newer 
than the manylinux ABI
-  # floor, so linking it dynamically would import too-recent GLIBCXX_* symbols 
and fail auditwheel.
-  target_link_options(tvm_ffi_orcjit PRIVATE -static-libstdc++ -static-libgcc)
 endif ()
 
+# libstdc++/libgcc are linked DYNAMICALLY on Linux. The prebuilt LLVM this 
addon links against is
+# built inside the manylinux image with its gcc-toolset, so the toolchain 
already sits at the
+# manylinux ABI floor and no too-recent GLIBCXX_* symbol is imported -- the 
-static-libstdc++
+# workaround the older conda-forge LLVM required is no longer needed.
+
 # Hide symbols pulled from static archives so they are not re-exported and 
cannot interpose with a
 # host process's own copies (e.g. PyTorch's bundled LLVM). Exclude 
LLVM/zlib/zstd by name -- NOT
-# --exclude-libs,ALL, which would also hide the static libstdc++ symbols 
(operator new, __throw_*,
-# ...) that liborc_rt and JIT'd C++ code resolve from this .so at run time. 
The LLVM list is derived
-# from llvm-config so it tracks the toolchain version.
+# --exclude-libs,ALL. The embedded liborc_rt.a is linked by the JIT at run 
time and resolves its C++
+# runtime through the process (dlsym), including archive-only helpers such as
+# _ZSt28__throw_bad_array_new_lengthv that libstdc++.so does not export; ALL 
localizes those and the
+# ORC platform then fails to materialize. This holds whether libstdc++ is 
static or dynamic. The
+# LLVM list is derived from llvm-config so it tracks the toolchain version.
 if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android|FreeBSD|NetBSD|OpenBSD" AND 
CMAKE_CXX_COMPILER_ID
                                                                          
MATCHES "GNU|Clang"
 )

Reply via email to