Skip to content

Commit fc4d705

Browse files
authored
[ESI][Runtime] Port Python bindings from pybind11 to nanobind (#9322)
This PR migrates the ESI Runtime Python bindings (esiaccel package) from pybind11 to nanobind for consistency with CIRCT and upstream MLIR. The migration updates binding APIs, build configuration, and dependency management while maintaining the same Python interface functionality.
1 parent 568e7e1 commit fc4d705

File tree

12 files changed

+331
-329
lines changed

12 files changed

+331
-329
lines changed

.github/workflows/esiRuntimePublish.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ jobs:
1717
fail-fast: false
1818
matrix:
1919
config:
20-
- os: ubuntu-24.04
21-
cibw_build: cp38-manylinux_x86_64
2220
- os: ubuntu-24.04
2321
cibw_build: cp39-manylinux_x86_64
2422
- os: ubuntu-24.04
@@ -29,8 +27,8 @@ jobs:
2927
cibw_build: cp312-manylinux_x86_64
3028
- os: ubuntu-24.04
3129
cibw_build: cp313-manylinux_x86_64
32-
- os: windows-2022
33-
cibw_build: cp38-win_amd64
30+
- os: ubuntu-24.04
31+
cibw_build: cp314-manylinux_x86_64
3432
- os: windows-2022
3533
cibw_build: cp39-win_amd64
3634
- os: windows-2022
@@ -41,6 +39,8 @@ jobs:
4139
cibw_build: cp312-win_amd64
4240
- os: windows-2022
4341
cibw_build: cp313-win_amd64
42+
- os: windows-2022
43+
cibw_build: cp314-win_amd64
4444

4545
steps:
4646
- name: Get CIRCT
@@ -68,7 +68,7 @@ jobs:
6868
& "${env:VCPKG_INSTALLATION_ROOT}/vcpkg" --triplet x64-windows install zlib grpc
6969
7070
- name: Install cibuildwheel
71-
run: python -m pip install cibuildwheel==2.23.3
71+
run: python -m pip install cibuildwheel==3.3.0
7272

7373
- name: Build wheels
7474
if: runner.os != 'Windows'

frontends/PyCDE/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ requires = [
88

99
# MLIR build depends.
1010
"numpy",
11-
"pybind11>=2.11,<=2.12",
12-
"nanobind==2.9.2",
11+
"nanobind>=2.2",
1312
"PyYAML",
1413

1514
# PyCDE depends

lib/Dialect/ESI/runtime/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
## themselves since it needs to be compiled for each Python version and OS then
1212
## packed together. Eventually, we'll just be distributing (lots of) binaries.
1313
##
14-
## We require Python development package and pybind11 to compile the Python API.
14+
## We require Python development package and nanobind to compile the Python API.
1515
##
1616
## ESI cosimulation requires Cap'nProto as we use it for our RPC with the
1717
## simulator. It must be fetched separately, but is optional if you don't want

lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ class Accelerator : public HWModule {
6565
Accelerator(std::optional<ModuleInfo> info,
6666
std::vector<std::unique_ptr<Instance>> children,
6767
std::vector<services::Service *> services,
68-
std::vector<std::unique_ptr<BundlePort>> &ports)
69-
: HWModule(info, std::move(children), services, ports) {}
68+
std::vector<std::unique_ptr<BundlePort>> &&ports)
69+
: HWModule(info, std::move(children), services, std::move(ports)) {}
7070
};
7171

7272
//===----------------------------------------------------------------------===//

lib/Dialect/ESI/runtime/cpp/include/esi/Design.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,15 @@ class Service;
4545

4646
/// Represents either the top level or an instance of a hardware module.
4747
class HWModule {
48+
public:
49+
HWModule(const HWModule &) = delete;
50+
HWModule &operator=(const HWModule &) = delete;
51+
4852
protected:
4953
HWModule(std::optional<ModuleInfo> info,
5054
std::vector<std::unique_ptr<Instance>> children,
5155
std::vector<services::Service *> services,
52-
std::vector<std::unique_ptr<BundlePort>> &ports);
56+
std::vector<std::unique_ptr<BundlePort>> &&ports);
5357

5458
public:
5559
virtual ~HWModule() = default;
@@ -112,11 +116,12 @@ class Instance : public HWModule {
112116
Instance(AppID id, std::optional<ModuleInfo> info,
113117
std::vector<std::unique_ptr<Instance>> children,
114118
std::vector<services::Service *> services,
115-
std::vector<std::unique_ptr<BundlePort>> &ports)
116-
: HWModule(info, std::move(children), services, ports), id(id) {}
119+
std::vector<std::unique_ptr<BundlePort>> &&ports)
120+
: HWModule(info, std::move(children), services, std::move(ports)),
121+
id(id) {}
117122

118123
/// Get the instance's ID, which it will always have.
119-
const AppID getID() const { return id; }
124+
AppID getID() const { return id; }
120125

121126
protected:
122127
const AppID id;

lib/Dialect/ESI/runtime/cpp/lib/Design.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ buildIndex(const std::vector<std::unique_ptr<BundlePort>> &ports) {
4242
HWModule::HWModule(std::optional<ModuleInfo> info,
4343
std::vector<std::unique_ptr<Instance>> children,
4444
std::vector<services::Service *> services,
45-
std::vector<std::unique_ptr<BundlePort>> &ports)
45+
std::vector<std::unique_ptr<BundlePort>> &&ports)
4646
: info(info), children(std::move(children)),
4747
childIndex(buildIndex(this->children)), services(services),
4848
ports(std::move(ports)), portIndex(buildIndex(this->ports)) {}

lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616
#include "esi/Accelerator.h"
1717
#include "esi/Services.h"
1818

19+
#if defined(__GNUC__)
1920
#pragma GCC diagnostic push
2021
#pragma GCC diagnostic ignored "-Wcovered-switch-default"
22+
#endif
2123
#include <nlohmann/json.hpp>
24+
#if defined(__GNUC__)
2225
#pragma GCC diagnostic pop
26+
#endif
2327
#include <sstream>
2428

2529
using namespace ::esi;
@@ -302,7 +306,8 @@ Manifest::Impl::buildAccelerator(AcceleratorConnection &acc) const {
302306

303307
return std::make_unique<Accelerator>(
304308
getModInfo(designJson),
305-
getChildInstances({}, acc, activeSvcs, designJson), services, ports);
309+
getChildInstances({}, acc, activeSvcs, designJson), services,
310+
std::move(ports));
306311
}
307312

308313
std::optional<ModuleInfo>
@@ -374,7 +379,7 @@ Manifest::Impl::getChildInstance(AppIDPath idPath, AcceleratorConnection &acc,
374379
auto ports = getBundlePorts(acc, idPath, activeServices, child);
375380
return std::make_unique<Instance>(parseIDChecked(child.at("appID")),
376381
getModInfo(child), std::move(children),
377-
services, ports);
382+
services, std::move(ports));
378383
}
379384

380385
services::Service *Manifest::Impl::getService(AppIDPath idPath,

lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121
#include <algorithm>
2222
#include <iostream>
2323
#include <map>
24+
#if defined(__GNUC__)
2425
#pragma GCC diagnostic push
2526
#pragma GCC diagnostic ignored "-Wcovered-switch-default"
27+
#endif
2628
#include <nlohmann/json.hpp>
29+
#if defined(__GNUC__)
2730
#pragma GCC diagnostic pop
31+
#endif
2832
#include <stdexcept>
2933
#include <string>
3034

lib/Dialect/ESI/runtime/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ requires = [
44
"setuptools_scm>=8.0",
55
"wheel",
66
"cmake>=3.31",
7-
"pybind11>=2.9",
8-
"pybind11_stubgen",
7+
"nanobind>=2.2",
98
"ninja>=1.13.0",
9+
"typing_extensions",
1010
]
1111
build-backend = "setuptools.build_meta"
1212

lib/Dialect/ESI/runtime/python/CMakeLists.txt

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,88 +13,81 @@ set(ESIPythonRuntimeSources
1313
esiaccel/cosim/verilator.py
1414
)
1515

16-
# Pybind11 is used to wrap the ESICppRuntime APIs.
16+
# Nanobind is used to wrap the ESICppRuntime APIs.
1717
find_package(Python3 COMPONENTS Interpreter Development.Module)
1818
if(Python3_FOUND)
1919
IF(MSVC)
20-
# Work around an issue with pybind11 and cmake incompatibility on Windows in debug mode.
20+
# Work around an issue with nanobind and cmake incompatibility on Windows in debug mode.
2121
set_target_properties(Python3::Module PROPERTIES
2222
MAP_IMPORTED_CONFIG_DEBUG ";RELEASE")
2323
ENDIF(MSVC)
2424

25-
if(pybind11_DIR)
26-
message(STATUS "Using explicit pybind11 cmake directory: ${pybind11_DIR} (-Dpybind11_DIR to change)")
25+
if(nanobind_DIR)
26+
message(STATUS "Using explicit nanobind cmake directory: ${nanobind_DIR} (-Dnanobind_DIR to change)")
2727
else()
28-
message(STATUS "Checking for pybind11 in python path...")
28+
message(STATUS "Checking for nanobind in python path...")
2929
execute_process(
3030
COMMAND "${Python3_EXECUTABLE}"
31-
-c "import pybind11;print(pybind11.get_cmake_dir(), end='')"
31+
-c "import nanobind;print(nanobind.cmake_dir(), end='')"
3232
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
3333
RESULT_VARIABLE STATUS
3434
OUTPUT_VARIABLE PACKAGE_DIR
3535
ERROR_QUIET)
3636
if(NOT STATUS EQUAL "0")
37-
message(FATAL_ERROR "pybind11 not found (install via 'pip install pybind11' or set pybind11_DIR)")
37+
message(FATAL_ERROR "nanobind not found (install via 'pip install nanobind' or set nanobind_DIR)")
3838
endif()
3939
message(STATUS "found (${PACKAGE_DIR})")
40-
set(pybind11_DIR "${PACKAGE_DIR}")
40+
set(nanobind_DIR "${PACKAGE_DIR}")
4141
endif()
4242

43-
# Now, find pybind11.
44-
find_package(pybind11 CONFIG)
45-
if (NOT pybind11_FOUND)
46-
message (STATUS "Could not find pybind11. Disabling Python API.")
43+
# Now, find nanobind.
44+
find_package(Python COMPONENTS Interpreter Development.Module)
45+
find_package(nanobind CONFIG)
46+
if (NOT nanobind_FOUND)
47+
message (STATUS "Could not find nanobind. Disabling Python API.")
4748
if (WHEEL_BUILD)
48-
message (FATAL_ERROR "pybind11 is required for a wheel build.")
49+
message (FATAL_ERROR "nanobind is required for a wheel build.")
4950
endif()
5051
else()
51-
# Compile Pybind11 module and copy to the correct python directory.
52-
pybind11_add_module(esiCppAccel
52+
# Compile nanobind module and copy to the correct python directory.
53+
nanobind_add_module(esiCppAccel
5354
${CMAKE_CURRENT_SOURCE_DIR}/esiaccel/esiCppAccel.cpp)
5455
target_link_libraries(esiCppAccel PRIVATE ESICppRuntime)
5556
set_target_properties(esiCppAccel PROPERTIES
5657
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/esiaccel"
5758
)
5859

59-
# Check for stubgen and generate stubs if available.
60-
find_program(STUBGEN pybind11-stubgen)
61-
if ("${STUBGEN}" STREQUAL "STUBGEN-NOTFOUND")
62-
message(STATUS "pybind11_stubgen not found. Skipping stub generation.")
60+
# Use nanobind's built-in stubgen for stub generation.
61+
if(WIN32)
62+
# I just wasted all day trying to figure out the DLL search path on
63+
# Windows both locally and in the runner. I'm done. Windows wheels
64+
# won't have a stub until somebody else figures this out.
65+
# TODO: have the patience to make this work.
66+
message(WARNING "Stub generation is not supported on Windows.")
6367
else()
64-
if(WIN32)
65-
# I just wasted all day trying to figure out the DLL search path on
66-
# Windows both locally and in the runner. I'm done. Windows wheels
67-
# won't have a stub until somebody else figures this out.
68-
# TODO: have the patience to make this work.
69-
message(WARNING "pybind11-stubgen is not supported on Windows.")
68+
set(stubgen_python_path "$ENV{PYTHONPATH}:${CMAKE_CURRENT_BINARY_DIR}")
69+
add_custom_command(
70+
TARGET esiCppAccel
71+
POST_BUILD
72+
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH="${stubgen_python_path}"
73+
"${Python3_EXECUTABLE}" -m nanobind.stubgen
74+
-m esiaccel.esiCppAccel
75+
-o "${CMAKE_CURRENT_BINARY_DIR}/esiaccel/esiCppAccel.pyi"
76+
)
77+
if (WHEEL_BUILD)
78+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/esiaccel/esiCppAccel.pyi"
79+
DESTINATION .
80+
COMPONENT ESIRuntime
81+
)
7082
else()
71-
set(stubgen_python_path "$ENV{PYTHONPATH}:${CMAKE_CURRENT_BINARY_DIR}")
72-
add_custom_command(
73-
TARGET esiCppAccel
74-
POST_BUILD
75-
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH="${stubgen_python_path}"
76-
${STUBGEN}
77-
-o "${CMAKE_CURRENT_BINARY_DIR}"
78-
esiaccel.esiCppAccel
83+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/esiaccel/esiCppAccel.pyi"
84+
DESTINATION python/esiaccel
85+
COMPONENT ESIRuntime
7986
)
80-
if (WHEEL_BUILD)
81-
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/esiaccel/esiCppAccel.pyi"
82-
DESTINATION .
83-
COMPONENT ESIRuntime
84-
)
85-
else()
86-
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/esiaccel/esiCppAccel.pyi"
87-
DESTINATION python/esiaccel
88-
COMPONENT ESIRuntime
89-
)
90-
endif()
9187
endif()
9288
endif()
9389

9490
if (WHEEL_BUILD)
95-
if ("${STUBGEN}" STREQUAL "STUBGEN-NOTFOUND")
96-
message (FATAL_ERROR "pybind11_stubgen is required for a wheel build.")
97-
endif()
9891
set_target_properties(esiCppAccel PROPERTIES
9992
INSTALL_RPATH "$ORIGIN/lib")
10093
else()
@@ -144,7 +137,7 @@ if(Python3_FOUND)
144137
endforeach()
145138

146139
# Custom target for the Python runtime just aggregates the python sources
147-
# and Pybind11 module.
140+
# and nanobind module.
148141
add_custom_target(ESIPythonRuntime
149142
DEPENDS
150143
${ESIPythonRuntimeSources}

0 commit comments

Comments
 (0)