Skip to content

Commit 886197b

Browse files
committed
quadrants: port to riscv64
Genesis-AI's fork of taichi: a scikit-build-core C++ compiler with an LLVM JIT CPU backend. Compared with the parked taichi entry, two of taichi's three walls are gone here: quadrants targets LLVM 22.1.0 (opaque pointers; RuntimeDyldELF has real RISC-V relocation handling since LLVM 20, confirmed directly against llvmorg-22.1.0 source: 42 riscv hits, a real resolveRISCVRelocation()) and finds clang via clang-22/21/20. The remaining wall is quadrants having no riscv64 CPU arch and no riscv64 LLVM to build against: - patches/quadrants/1.3.3/0001 adds a riscv64 CPU arch alongside x64/arm64 everywhere they're handled (CXXFlags, the arch enum, rhi/arch.cpp, and four scattered "is this a CPU arch" checks in the LLVM codegen/transforms). - 0002/0003 patch the CI build scripts: no riscv64 LLVM/sccache release exists to download, so setup_llvm() expects LLVM_DIR to already point at the LLVM we build ourselves, setup_sccache() no-ops (ccache from the manylinux image covers it instead), CUDA/ AMDGPU/Vulkan (no riscv64 backend for any of them) are turned off, and the dev dependency group is trimmed to what the build actually needs (full --group dev pulls pyright, which has no riscv64 wheel or sdist). build-quadrants.yml builds LLVM+clang 22.1.0 from source once (RISCV target only, matching build-libclang.yml's from-source precedent but also linking OrcJIT/ExecutionEngine/RuntimeDyld, which libclang.so doesn't need), then reuses it across the cp310/cp311/cp312/cp313 matrix - mirroring upstream's own manylinux_wheel.yml scripts (1_prerequisites.sh/2_build.sh/3_check.sh) almost verbatim, pointed at our own LLVM instead of their quadrants-sdk-builds releases. GCC (not our built clang) still compiles the bulk of the project, same as upstream; clang only compiles the LLVM JIT's runtime.cpp bitcode, and the main library links the static LLVM libraries GCC-side, which is an ordinary cross-compiler-ABI link on Linux. No gpl_sources job: the build doesn't statically link libstdc++ into the wheel (that flag is MinGW-only in QuadrantsCXXFlags.cmake), and glibc-based manylinux auditwheel doesn't bundle it either.
1 parent fc529c9 commit 886197b

5 files changed

Lines changed: 535 additions & 0 deletions
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# This workflow is based on: https://github.com/Genesis-Embodied-AI/quadrants/blob/v1.3.3/.github/workflows/manylinux_wheel.yml
5+
name: Build quadrants wheels (riscv64)
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
version:
11+
description: 'Version glob to (re)build; empty builds every version of docs/packages/quadrants.yaml not released yet'
12+
required: false
13+
default: ''
14+
pull_request:
15+
branches: [main]
16+
paths:
17+
- '.github/workflows/build-quadrants.yml'
18+
- 'docs/packages/quadrants.yaml'
19+
- 'patches/quadrants/**'
20+
push:
21+
branches: [main]
22+
paths:
23+
- '.github/workflows/build-quadrants.yml'
24+
- 'docs/packages/quadrants.yaml'
25+
- 'patches/quadrants/**'
26+
27+
concurrency:
28+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
29+
cancel-in-progress: true
30+
31+
permissions:
32+
contents: read
33+
34+
env:
35+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
36+
LLVM_VERSION: 22.1.0
37+
38+
jobs:
39+
setup:
40+
uses: $/.github/workflows/_setup.yml
41+
with:
42+
package: quadrants
43+
version: ${{ inputs.version }}
44+
45+
build_llvm:
46+
needs: [setup]
47+
if: needs.setup.outputs.versions != '[]'
48+
name: Build LLVM ${{ env.LLVM_VERSION }} for quadrants (riscv64)
49+
runs-on: ubuntu-24.04-riscv
50+
container: ${{ env.MANYLINUX_RISCV64_IMAGE }}
51+
timeout-minutes: 1440 # full LLVM+clang compile from source
52+
53+
steps:
54+
# quadrants-sdk-builds (download_llvm.py's own release bucket) ships no riscv64 LLVM;
55+
# build LLVM+clang 22.1.0 ourselves with only the RISCV target and the components
56+
# quadrants's QuadrantsCore.cmake links (Core/ExecutionEngine/OrcJIT/RuntimeDyld/...).
57+
# No CUDA/AMDGPU/Vulkan backend exists for riscv64 (patches/quadrants disables them), so
58+
# NVPTX/AMDGPU targets are left out too.
59+
- name: Build and install LLVM+clang
60+
run: |
61+
set -euxo pipefail
62+
dnf -y install ninja-build lld
63+
curl -fsSL -o llvm-project.src.tar.xz \
64+
"https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz"
65+
tar xf llvm-project.src.tar.xz
66+
cmake -S "llvm-project-${LLVM_VERSION}.src/llvm" -B build -G Ninja \
67+
-DLLVM_ENABLE_PROJECTS=clang \
68+
-DLLVM_TARGETS_TO_BUILD=RISCV \
69+
-DBUILD_SHARED_LIBS=OFF \
70+
-DLLVM_ENABLE_ZLIB=OFF \
71+
-DLLVM_ENABLE_ZSTD=OFF \
72+
-DLLVM_ENABLE_TERMINFO=OFF \
73+
-DLLVM_ENABLE_LIBXML2=OFF \
74+
-DLLVM_INCLUDE_TESTS=OFF \
75+
-DLLVM_INCLUDE_EXAMPLES=OFF \
76+
-DLLVM_INCLUDE_BENCHMARKS=OFF \
77+
-DLLVM_USE_LINKER=lld \
78+
-DLLVM_PARALLEL_LINK_JOBS=1 \
79+
-DCMAKE_BUILD_TYPE=Release \
80+
-DCMAKE_INSTALL_PREFIX="$(pwd)/llvm-install"
81+
cmake --build build --target install -j"$(nproc)"
82+
tar czf llvm-install.tar.gz llvm-install
83+
84+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
85+
with:
86+
name: quadrants-llvm-${{ env.LLVM_VERSION }}-riscv64
87+
path: llvm-install.tar.gz
88+
if-no-files-found: error
89+
retention-days: 1
90+
91+
build_wheels:
92+
needs: [setup, build_llvm]
93+
if: needs.setup.outputs.versions != '[]'
94+
strategy:
95+
fail-fast: false
96+
matrix:
97+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
98+
python: ["cp310", "cp311", "cp312", "cp313"]
99+
name: Build quadrants ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
100+
runs-on: ubuntu-24.04-riscv
101+
container: ${{ env.MANYLINUX_RISCV64_IMAGE }}
102+
timeout-minutes: 360
103+
104+
env:
105+
QUADRANTS_VERSION: ${{ matrix.version }}
106+
PIP_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
107+
108+
steps:
109+
- name: Checkout quadrants v${{ env.QUADRANTS_VERSION }}
110+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
111+
with:
112+
repository: Genesis-Embodied-AI/quadrants
113+
ref: v${{ env.QUADRANTS_VERSION }}
114+
submodules: recursive
115+
persist-credentials: false
116+
117+
- name: Checkout python-wheels
118+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
119+
with:
120+
path: python-wheels
121+
persist-credentials: false
122+
123+
- name: Patch quadrants source
124+
run: git apply python-wheels/patches/quadrants/${{ env.QUADRANTS_VERSION }}/*.patch
125+
126+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
127+
with:
128+
name: quadrants-llvm-${{ env.LLVM_VERSION }}-riscv64
129+
130+
- name: Unpack LLVM, select the interpreter
131+
run: |
132+
tar xzf llvm-install.tar.gz
133+
echo "LLVM_DIR=$(pwd)/llvm-install" >> "$GITHUB_ENV"
134+
echo "/opt/python/${{ matrix.python }}-${{ matrix.python }}/bin" >> "$GITHUB_PATH"
135+
136+
- name: Build and check wheel
137+
run: |
138+
bash .github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh
139+
bash .github/workflows/scripts_new/manylinux_wheel/2_build.sh
140+
bash .github/workflows/scripts_new/manylinux_wheel/3_check.sh
141+
142+
- name: Smoke-test wheel
143+
run: |
144+
rm -f dist/*.whl
145+
cp wheelhouse/*.whl dist/
146+
bash .github/workflows/scripts_new/manylinux_wheel/4_install_wheel.sh
147+
148+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
149+
with:
150+
name: quadrants-${{ matrix.version }}-${{ matrix.python }}-manylinux_riscv64
151+
path: wheelhouse/*.whl
152+
if-no-files-found: error
153+
154+
publish:
155+
name: Publish quadrants ${{ matrix.version }}
156+
needs: [setup, build_wheels]
157+
if: needs.setup.outputs.versions != '[]'
158+
strategy:
159+
fail-fast: false
160+
matrix:
161+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
162+
permissions:
163+
contents: write
164+
pull-requests: write
165+
uses: $/.github/workflows/_publish-wheel.yml
166+
secrets:
167+
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
168+
with:
169+
artifact-pattern: quadrants-${{ matrix.version }}-*-manylinux_riscv64

‎docs/packages/quadrants.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package-name: quadrants
2+
source-code: https://github.com/Genesis-Embodied-AI/quadrants
3+
license: Apache-2.0
4+
versions:
5+
- version: 1.3.3
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: RISE Project CI <ci@riseproject.dev>
3+
Date: Thu, 15 Oct 2026 00:00:00 +0000
4+
Subject: [PATCH] Add a riscv64 CPU arch
5+
6+
Quadrants' CPU-arch enum (quadrants/inc/archs.inc.h) only lists x64 and
7+
arm64; cmake/QuadrantsCXXFlags.cmake FATAL_ERRORs on any other
8+
CMAKE_SYSTEM_PROCESSOR, and rhi/arch.cpp's host_arch()/
9+
default_simd_width()/arch_is_cpu()/arch_uses_llvm() hit
10+
RHI_NOT_IMPLEMENTED or silently exclude riscv64 from the CPU-backend
11+
checks scattered through the LLVM codegen (program/extension.cpp,
12+
runtime/llvm/llvm_context.cpp, runtime/llvm/adstack_lazy_claim/
13+
bound_eval.cpp, transforms/make_mesh_block_local.cpp). Add a riscv64
14+
case everywhere x64/arm64 are handled as CPU archs, so the LLVM CPU
15+
JIT backend (orc::RTDyldObjectLinkingLayer on Linux) is reachable on
16+
riscv64. default_simd_width() uses 4, matching arm64's conservative
17+
choice since RV64GC has no fixed-width SIMD without RVV. The nanobind
18+
enum binding in quadrants/python/export_lang.cpp regenerates from
19+
archs.inc.h via the same PER_ARCH macro, so quadrants.riscv64 needs no
20+
separate binding.
21+
22+
Upstream-Status: To upstream
23+
24+
Signed-off-by: RISE Project CI <ci@riseproject.dev>
25+
---
26+
cmake/QuadrantsCXXFlags.cmake | 5 ++++-
27+
quadrants/inc/archs.inc.h | 1 +
28+
quadrants/program/extension.cpp | 3 +++
29+
quadrants/rhi/arch.cpp | 11 +++++++++--
30+
.../runtime/llvm/adstack_lazy_claim/bound_eval.cpp | 2 +-
31+
quadrants/runtime/llvm/llvm_context.cpp | 2 +-
32+
quadrants/transforms/make_mesh_block_local.cpp | 6 +++---
33+
7 files changed, 22 insertions(+), 8 deletions(-)
34+
35+
diff --git a/cmake/QuadrantsCXXFlags.cmake b/cmake/QuadrantsCXXFlags.cmake
36+
index e4eb937..c78fbab 100644
37+
--- a/cmake/QuadrantsCXXFlags.cmake
38+
+++ b/cmake/QuadrantsCXXFlags.cmake
39+
@@ -132,12 +132,15 @@ elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR "${CMAKE_SYSTEM_PROCES
40+
elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86")
41+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQD_ARCH_x86")
42+
set(ARCH "x86")
43+
+elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64")
44+
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQD_ARCH_riscv64")
45+
+ set(ARCH "riscv64")
46+
else()
47+
message(FATAL_ERROR "Unknown processor type ${CMAKE_SYSTEM_PROCESSOR}")
48+
endif()
49+
set(HOST_ARCH ${ARCH} CACHE INTERNAL "Host arch")
50+
51+
-if (LINUX AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
52+
+if (LINUX AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|riscv64)$")
53+
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -latomic")
54+
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -latomic")
55+
endif()
56+
diff --git a/quadrants/inc/archs.inc.h b/quadrants/inc/archs.inc.h
57+
index 919673a..7b60fa6 100644
58+
--- a/quadrants/inc/archs.inc.h
59+
+++ b/quadrants/inc/archs.inc.h
60+
@@ -3,6 +3,7 @@
61+
// CPU archs
62+
PER_ARCH(x64) // a.k.a. AMD64/x86_64
63+
PER_ARCH(arm64) // a.k.a. Aarch64, WIP
64+
+PER_ARCH(riscv64) // RV64GC
65+
PER_ARCH(js) // Javascript, N/A
66+
PER_ARCH(python) // Python
67+
68+
diff --git a/quadrants/program/extension.cpp b/quadrants/program/extension.cpp
69+
index 9a51bc2..90cf6cc 100644
70+
--- a/quadrants/program/extension.cpp
71+
+++ b/quadrants/program/extension.cpp
72+
@@ -13,6 +13,9 @@ bool is_extension_supported(Arch arch, Extension ext) {
73+
{Arch::arm64,
74+
{Extension::sparse, Extension::quant, Extension::quant_basic, Extension::data64, Extension::adstack,
75+
Extension::assertion, Extension::mesh}},
76+
+ {Arch::riscv64,
77+
+ {Extension::sparse, Extension::quant, Extension::quant_basic, Extension::data64, Extension::adstack,
78+
+ Extension::assertion, Extension::mesh}},
79+
{Arch::cuda,
80+
{Extension::sparse, Extension::quant, Extension::quant_basic, Extension::data64, Extension::adstack,
81+
Extension::bls, Extension::assertion, Extension::mesh}},
82+
diff --git a/quadrants/rhi/arch.cpp b/quadrants/rhi/arch.cpp
83+
index b9a4822..db48549 100644
84+
--- a/quadrants/rhi/arch.cpp
85+
+++ b/quadrants/rhi/arch.cpp
86+
@@ -39,7 +39,8 @@ Arch arch_from_name(const std::string &arch_name) {
87+
88+
// Assuming a processor is either a CPU or a GPU. DSP/TPUs not considered.
89+
bool arch_is_cpu(Arch arch) {
90+
- if (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::js || arch == Arch::python) {
91+
+ if (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::riscv64 || arch == Arch::js ||
92+
+ arch == Arch::python) {
93+
return true;
94+
} else {
95+
return false;
96+
@@ -59,7 +60,8 @@ bool arch_is_metal(Arch arch) {
97+
}
98+
99+
bool arch_uses_llvm(Arch arch) {
100+
- return (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::cuda || arch == Arch::amdgpu);
101+
+ return (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::riscv64 || arch == Arch::cuda ||
102+
+ arch == Arch::amdgpu);
103+
}
104+
105+
bool arch_is_gpu(Arch arch) {
106+
@@ -76,6 +78,9 @@ Arch host_arch() {
107+
#endif
108+
#if defined(QD_ARCH_ARM)
109+
return Arch::arm64;
110+
+#endif
111+
+#if defined(QD_ARCH_riscv64)
112+
+ return Arch::riscv64;
113+
#endif
114+
RHI_NOT_IMPLEMENTED
115+
}
116+
@@ -91,6 +96,8 @@ int default_simd_width(Arch arch) {
117+
return 32;
118+
} else if (arch == Arch::arm64) {
119+
return 4;
120+
+ } else if (arch == Arch::riscv64) {
121+
+ return 4;
122+
} else {
123+
RHI_NOT_IMPLEMENTED;
124+
return -1;
125+
diff --git a/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp b/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp
126+
index 0b62054..4849e62 100644
127+
--- a/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp
128+
+++ b/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp
129+
@@ -38,7 +38,7 @@ uint32_t LlvmRuntimeExecutor::publish_per_task_bound_count_cpu(std::size_t task_
130+
// Default to UINT32_MAX (no clamp); only override on a successful host evaluation. The codegen-emitted bounds clamp
131+
// at the float LCA-block claim site stays inert when the slot holds UINT32_MAX, so this fall-through is a no-op that
132+
// preserves the existing behaviour.
133+
- if (config_.arch != Arch::x64 && config_.arch != Arch::arm64) {
134+
+ if (config_.arch != Arch::x64 && config_.arch != Arch::arm64 && config_.arch != Arch::riscv64) {
135+
return std::numeric_limits<uint32_t>::max();
136+
}
137+
if (!ad_stack.bound_expr.has_value()) {
138+
diff --git a/quadrants/runtime/llvm/llvm_context.cpp b/quadrants/runtime/llvm/llvm_context.cpp
139+
index d116503..ef7070c 100644
140+
--- a/quadrants/runtime/llvm/llvm_context.cpp
141+
+++ b/quadrants/runtime/llvm/llvm_context.cpp
142+
@@ -725,7 +725,7 @@ std::unique_ptr<llvm::Module> QuadrantsLLVMContext::module_from_file(const std::
143+
}
144+
145+
// CPU-specific intrinsics
146+
- if (arch_ == Arch::x64 || arch_ == Arch::arm64) {
147+
+ if (arch_ == Arch::x64 || arch_ == Arch::arm64 || arch_ == Arch::riscv64) {
148+
auto func = module->getFunction("cpu_clock_i64");
149+
if (func) {
150+
func->deleteBody();
151+
diff --git a/quadrants/transforms/make_mesh_block_local.cpp b/quadrants/transforms/make_mesh_block_local.cpp
152+
index 69d713f..60724b0 100644
153+
--- a/quadrants/transforms/make_mesh_block_local.cpp
154+
+++ b/quadrants/transforms/make_mesh_block_local.cpp
155+
@@ -110,7 +110,7 @@ void MakeMeshBlockLocal::replace_global_ptrs(SNode *snode) {
156+
}
157+
158+
// in the cpu backend, atomic op in body block could be demoted to non-atomic
159+
- if (config_.arch != Arch::x64 && config_.arch != Arch::arm64) {
160+
+ if (config_.arch != Arch::x64 && config_.arch != Arch::arm64 && config_.arch != Arch::riscv64) {
161+
return;
162+
}
163+
std::vector<AtomicOpStmt *> atomic_ops;
164+
@@ -144,7 +144,7 @@ Stmt *MakeMeshBlockLocal::create_xlogue(Stmt *start_val,
165+
Stmt *idx = block_->push_back<AllocaStmt>(mapping_data_type_);
166+
[[maybe_unused]] Stmt *init_val = block_->push_back<LocalStoreStmt>(idx, start_val);
167+
Stmt *block_dim_val;
168+
- if (config_.arch == Arch::x64 || config_.arch == Arch::arm64) {
169+
+ if (config_.arch == Arch::x64 || config_.arch == Arch::arm64 || config_.arch == Arch::riscv64) {
170+
block_dim_val = block_->push_back<ConstStmt>(TypedConstant(1));
171+
} else {
172+
block_dim_val = block_->push_back<ConstStmt>(TypedConstant{offload_->block_dim});
173+
@@ -272,7 +272,7 @@ void MakeMeshBlockLocal::fetch_mapping(
174+
std::function<Stmt *(Block * /*block*/, Stmt * /*idx_val*/)>)> mapping_callback_handler,
175+
std::function<void(Block *body, Stmt *idx_val, Stmt *mapping_val)> attr_callback_handler) {
176+
Stmt *thread_idx_stmt;
177+
- if (config_.arch == Arch::x64 || config_.arch == Arch::arm64) {
178+
+ if (config_.arch == Arch::x64 || config_.arch == Arch::arm64 || config_.arch == Arch::riscv64) {
179+
thread_idx_stmt = block_->push_back<ConstStmt>(TypedConstant(0));
180+
} else {
181+
thread_idx_stmt = block_->push_back<LoopLinearIndexStmt>(offload_); // Equivalent to CUDA threadIdx

0 commit comments

Comments
 (0)