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