☰
qiskit-bindgen 深度解析:Qiskit C API 头文件自动生成、安装与 Rust/Python FFI 绑定管线
2026/9/27 21:20:36 网站建设 项目流程
  • 科学计算

【免费下载链接】qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.

项目地址:https://gitcode.com/gh_mirrors/qi/qiskit
点击查看免费下载

qiskit-bindgen是 Qiskit 仓库中一个内部 Rust 库,负责解析qiskit-cext的 Rust 源码并自动产出可直接安装使用的 C 头文件,同时维护自定义头文件目录(include)、Rust 模板 crate(pyo3-ffi)及两者的安装逻辑。阅读本文后,你将理解 Qiskit C API(Qk*前缀符号体系)从 Rust 源码到qiskit/types.h、qiskit/funcs.h,再到 Rustqiskit-pyo3-ffi与未来 Pythonctypes绑定这条完整生成管线,并掌握其消费方qiskit-bindgen-cli的实际用法。

定位:构建与分发流程中的“头文件生成中枢”

根据 crates/bindgen/README.md 的定义,qiskit-bindgen是一个仅供构建与分发流程使用的内部库,它的核心职责是:

  • 解析qiskit-cextcrate(见 crates/cext)暴露出的公开 API,生成合适的 C 头文件,以便访问该 crate 编译出的库中的函数;
  • 统管独立头文件生成的全部逻辑,包括手写 include 文件(include目录)、Rust 模板 crate(pyo3-ffi目录)以及两者的安装逻辑;
  • 大部分繁重工作由cbindgen完成,本 crate 负责“scraping 设置逻辑”——即围绕cbindgen配置展开的定制化预处理。

该库本身不直接面向终端用户,而是设计给其消费方——二进制工具qiskit-bindgen-cli(crates/bindgen-cli)使用,未来还将被 Python 扩展构建流程qiskit-pyext(crates/pyext)复用。README 还预告了一个演进方向:未来本库可能提供对cbindgen输出的包装,将写出的函数与类型以结构化数据暴露给下游(包括 Qiskit 自己的 Python 扩展),使下游能直接基于结构化数据生成各语言的原始绑定,而不必重新解析生成的 C 头文件。

目录结构与三大核心资产

crates/bindgen/ ├── Cargo.toml # 依赖声明 ├── README.md # 定位说明 ├── include/ # ① 手写 include 文件(随头文件一起安装) │ ├── qiskit.h │ └── qiskit/ │ ├── attributes.h │ ├── complex.h │ ├── funcs_py.h │ └── version.h ├── pyo3-ffi/ # ② Rust 模板 crate(安装时复制并覆写生成文件) │ ├── Cargo.toml │ ├── Cargo.lock │ └── src/ │ ├── lib.rs # 手写部分:qk_import、declare_fn! 宏 │ └── ffi.rs # 自动生成部分:安装时由 qiskit-bindgen 覆写 └── src/ ├── lib.rs # 主入口:配置、生成、安装 ├── simple_ir.rs # 简化的 cbindgen 输出 IR └── render/ # 多语言渲染后端 ├── c.rs # C 类型渲染、函数指针 cast ├── ctypes.rs # Python ctypes 渲染(未来 Python 绑定) ├── rust.rs # Rust 渲染(pyo3-ffi 的 ffi.rs) └── mod.rs

从 crates/bindgen/Cargo.toml 可以看到它的关键依赖设计:

  • qiskit-cext-vtable(启用python_bindingfeature):提供函数指针槽位表(vtable),是生成 Rust 绑定时的槽位来源;
  • cbindgen(启用unstable_irfeature):承担 Rust → C 绑定生成的主体力;
  • hashbrown、regex、anyhow:分别用于哈希映射、文本处理与错误传播。

生成管线:从 qiskit-cext 到 Bindings

核心入口是 crates/bindgen/src/lib.rs 中的generate_bindings:

pub fn generate_bindings(cext_path: impl AsRef<Path>) -> anyhow::Result<cbindgen::Bindings> { cbindgen::Builder::new() .with_crate(cext_path) .with_config(get_config()?) .generate() .map_err(|e| e.into()) }

它把cext_path指向的 crate(即qiskit-cext)交给cbindgen,并套用get_config()生成的定制配置。get_config()(crates/bindgen/src/lib.rs)配置要点如下:

配置项取值含义
languageC输出 C 语言头文件
styleStyle::Typetypedef 风格
cpp_compattrue与 C++ 兼容
usize_is_size_ttrueRustusize映射为 Csize_t
include_versiontrue头文件中包含版本信息
includesqiskit/attributes.h所有生成文件都包含该文件,保证 Doxygen 能识别弃用属性宏
parse.parse_depstrue解析依赖 crate
parse.includeqiskit-quantum-info、qiskit-circuit、qiskit-transpiler只从这些公开 API crate 导出符号(对应常量QISKIT_PUBLIC_API_CRATES)
definesfeature = python_binding→QISKIT_C_PYTHON_INTERFACERust cfg feature 到 C#ifdef宏的映射
header带版权注释的头部文本由copyright_with_line_comments("//")生成

值得注意的细节:cbindgen的FunctionConfig被配置为使用自定义的弃用标注宏Qk_DEPRECATED_FN与Qk_DEPRECATED_FN_NOTE({})(对应 crates/bindgen/include/qiskit/attributes.h 中定义的宏),从而让生成的 C 头文件携带与手写头文件一致的弃用标注语法。

函数级属性注解:no-export与allow-duplicate

在cext源码中,函数可以通过cbindgen注解qk-vtable-rules声明特殊属性(常量CBINDGEN_ATTRIBUTE_NAME)。crates/bindgen/src/lib.rs 定义了FnAttributes结构体,支持两种取值:

  • no-export:该函数应被跳过,不出现在任何 vtable 槽位列表中;
  • allow-duplicate:允许该函数被导出到多个槽位。

fn_attrs(crates/bindgen/src/lib.rs)负责从cbindgen::ir::Function的注解列表中解析这些属性,遇到未知属性会直接报错,从而保证槽位表与导出函数集合的严格一致。

命名与导出约定:Qk前缀、重命名映射与 verbatim 导出

C 语言没有命名空间,因此 Qiskit 的 C API 采用统一前缀Qk(常量EXPORT_PREFIX)来区分符号。前缀的施加方式并非简单的“一律加前缀”,而是通过一张显式的重命名映射(EXPORT_RENAME,crates/bindgen/src/lib.rs)完成,例如:

Rust 内部名C API 导出名
CBlocksModeQkBlocksMode
CDagNeighborsQkDagNeighbors
CInstructionQkCircuitInstruction
CircuitDataQkCircuit
DAGCircuitQkDag
SparseObservableQkObs
StandardGateQkGate
ExprQkExprNode

实现上,get_config()将EXPORT_RENAME逐项拼接Qk前缀构造出cbindgen的rename映射,同时设置renaming_overrides_prefixing: true,从而让EXPORT_VERBATIM(目前只有PyObject)可以原样导出、不被加前缀。此外EnumConfig设置了prefix_with_name: true,即枚举值会带枚举名前缀(例如QkExitCode_Success这样的形式)。

头文件安装:types.h 与 funcs.h 的拆分

install_c_headers(crates/bindgen/src/lib.rs)负责把生成结果与手写文件一起安装到目标目录,关键逻辑是把 cbindgen 输出拆成两个文件:

  1. qiskit/types.h:只写类型与常量(通过临时取出bindings.functions实现)。代码中还有一个assert!(bindings.globals.is_empty()),表明目前尚未出现全局变量/常量,一旦出现需要专门处理;
  2. qiskit/funcs.h:只写函数(通过临时取出bindings.items与bindings.constants实现)。它对应“非 Python 扩展模式”下的函数声明;
  3. 随后递归复制include目录下所有.h/.hpp手写文件(manual_include_files只收集扩展名为h或hpp的文件)。

这两个生成文件的命名常量(GENERATED_FILE_TYPES = "types.h"、GENERATED_FILE_FUNCS = "funcs.h")与作用域目录常量(SCOPED_INCLUDE_DIR = "qiskit")都定义在 crates/bindgen/src/lib.rs。

手写 include 文件:伞形头与配套宏

include目录中的文件是人工维护、随生成结果一起安装的,其中qiskit.h是所有头文件的“伞形入口”(crates/bindgen/include/qiskit.h),其组织方式如下:

#if defined(QISKIT_C_PYTHON_INTERFACE) || defined(QISKIT_PYTHON_EXTENSION) #include <Python.h> #endif #include "qiskit/attributes.h" #include "qiskit/complex.h" #include "qiskit/version.h" #include "qiskit/types.h" // 由 cbindgen 生成 #if defined(QISKIT_PYTHON_EXTENSION) #include "qiskit/funcs_py.h" // 由 Qiskit 的 pyext 生成(或打桩) #else #include "qiskit/funcs.h" // 由 cbindgen 生成 #endif

这套结构体现了两个设计要点:

  • Python 感知构建与普通构建分离:当定义了QISKIT_PYTHON_EXTENSION(Python 扩展构建)时,使用funcs_py.h;否则使用funcs.h。当定义了QISKIT_C_PYTHON_INTERFACE(即 Rust 侧启用了python_bindingfeature)或QISKIT_PYTHON_EXTENSION时,才引入<Python.h>。
  • 版本信息的集中维护:crates/bindgen/include/qiskit/version.h 定义了QISKIT_VERSION_MAJOR/MINOR/PATCH、发布等级宏(QISKIT_RELEASE_LEVEL_DEV/BETA/RC/FINAL)以及QISKIT_VERSION_HEX十六进制版本编码(格式0xMMmmppls,例如 2.1.0rc1 为0x020100C1),当前版本为2.6.0-dev。

特别值得注意的是 crates/bindgen/include/qiskit/funcs_py.h:它只是一个显式报错的桩文件(#error ...)。当用户拿到的是不含 Python 扩展访问能力的分发版 C API 时,直接引用qiskit.h会得到清晰的中断错误,而不是难以排查的“文件找不到”预处理错误;完整 Python 包构建时会用真正可用的版本覆写它。弃用标注宏Qk_DEPRECATED_FN/Qk_DEPRECATED_FN_NOTE则在 crates/bindgen/include/qiskit/attributes.h 中针对 GCC/Clang(__attribute__((deprecated)))与 MSVC(__declspec(deprecated))分别定义。

Rust 模板 crate:qiskit-pyo3-ffi 的生成与安装

README 明确说明本 crate 拥有pyo3-ffi目录——一个被整体复制安装的 Rust 模板 crate,其产物qiskit-pyo3-ffi提供“通过 Python 空间qiskit包访问 Qiskit 原始 C API 的 Rust 绑定”。

安装逻辑在install_rust_pyo3_ffi(crates/bindgen/src/lib.rs):先把pyo3-ffi模板目录(通过cargo package --list枚举文件)原样复制到安装目录,然后渲染生成src/ffi.rs覆写同名文件。渲染工作由 crates/bindgen/src/render/rust.rs 完成,它针对三个 vtable(QK_FFI_CIRCUIT、QK_FFI_TRANSPILE、QK_FFI_QI,分别对应qiskit_cext_vtable::FUNCTIONS_CIRCUIT/FUNCTIONS_TRANSPILE/FUNCTIONS_QI)遍历槽位导出项,为每个函数生成一段declare_fn!(vtable[offset]; name(args) -> ret)宏调用。

对应的运行时支撑在 crates/bindgen/pyo3-ffi/src/lib.rs:

  • qk_import(py)(第 142-183 行)从 Python 模块qiskit._accelerate.capi中取出三个PyCapsule(QK_FFI_CIRCUIT等),把其中存储的函数指针表地址存入三个OnceLock<VTablePtr>静态变量,并有意识地对 capsule 引用做std::mem::forget泄漏使其“不朽”;
  • declare_fn!宏(第 195-225 行)为每个 C API 函数生成#[inline(always)]的unsafe包装函数,运行时从vtable静态量按offset取出extern "C"函数指针再间接调用;
  • 安全约定:除qk_import外的所有函数都是unsafe的,且在qk_import成功返回之前一律无效,因此通常应在 PyO3 模块初始化函数中先调用ffi::qk_import(m.py())?。

crates/bindgen/pyo3-ffi/Cargo.toml 还透露了模板 crate 的分发定位:它刻意脱离工作区([workspace]强制排除),pyo3依赖范围放宽为>=0.22,<=0.28、num-complex为0.4,便于作为独立依赖被外部 crate 使用。

简化 IR 与多语言渲染后端

由于 Qiskit 需要向多种“非 C”语言/格式输出绑定(Rust 已实现、Python ctypes 已具备、未来可能更多),crates/bindgen/src/simple_ir.rs 定义了一套比 cbindgen 输出更精简、更结构化的中间表示,只保留 Qiskit 实际用到的元素:

  • Type<T>:带指针层级(ptrs,从最内层向外排列,如*mut *const T表示为ptrs: [Const, Mut])与基础类型(内置/自定义);
  • Enum:导出名、底层整数表示(repr)与(name, literal)变体列表;
  • Struct<T>:可选字段(fields: None表示不透明 struct)、Union<T>:不可为不透明的 union;
  • Function<T>:导出名、参数(可无名)与返回类型;
  • Items<T>:上述四类对象的集合,Default实现为空集合。

render目录下的三个后端各司其职:

  • crates/bindgen/src/render/rust.rs:将 IR 渲染为 Rust 源码——枚举派生Clone, Copy, PartialEq, Eq, Hash, Debug并带#[repr(...)];透明 struct 字段全部pub且#[repr(C)];不透明 struct 采用 Rustonomicon 推荐的PhantomData<(*mut u8, PhantomPinned)>表示法;函数渲染为declare_fn!宏调用;
  • crates/bindgen/src/render/ctypes.rs:将 IR 渲染为 Pythonctypes模块源码——输出__all__、import ctypes/enum、ctypes.PyDLL(...)声明、枚举对应的class X(enum.Enum)(变体用底层ctypes构造函数如ctypes.c_int32(0)赋值)、struct 对应的class X(ctypes.Structure)(含_fields_)、函数对应的argtypes/restype赋值,并注意声明顺序(不透明 struct 最先、union 其次、透明 struct 最后)以保证 Python 自上而下执行时引用合法;它还内置了一个QkComplex64隐式定义(两个double字段re/im);
  • crates/bindgen/src/render/c.rs:C 类型渲染与“导出函数名 → C 函数指针 cast 类型”的映射(functions_as_funcptr_casts),供 ABI 校验等场景使用。

从渲染器的代码注释可以清楚看到当前的能力边界:数组类型、函数指针(在simple_ir路径中)、可变参数(VaList)都尚未支持,遇到会直接bail!报错——这些是未来扩展的空间。

消费方:qiskit-bindgen-cli 的使用方式

qiskit-bindgen-cli(crates/bindgen-cli/src/main.rs)是当前唯一消费者,用clap定义了一组子命令(cext_path统一用-c/--cext-path指定,输出目录用-o/--output-path):

子命令作用关键参数
install生成并安装 C 头文件到指定目录(内部调用generate_bindings+install_c_headers)-c <cext_path>、-o <output_path>
show-slots打印当前版本所有已占用的 vtable 槽位表示无
lint-slots校验槽位表与当前版本导出函数列表的一致性-c <cext_path>
generate-pyo3生成 Rust 模板 crate 输出(install_rust_pyo3_ffi)-c <cext_path>、-o <output_path>
check-abi校验两套槽位导出之间满足语义化版本约束(只检查符号名与偏移,不检查函数指针类型)old <旧槽位文件>、new [新槽位文件](缺省时对比当前 vtable 定义)

这组命令把“生成头文件”“生成 Rust 绑定”“ABI 兼容性检查”三个构建期需求完整覆盖,也呼应了 README 中“该库设计给其消费二进制使用”的定位。

未来演进与设计意图

README 结尾描述的演进方向(为cbindgen输出提供包装、以结构化数据暴露函数与类型)在当前代码中已有明显铺垫:simple_ir与三个render后端正是“从结构化中间表示出发、向任意语言导出”的基础设施;ctypes.rs的 Python 输出能力已经相当完整,只待接入实际构建流程。对于想要深入 Qiskit C API 生成机制的读者,建议按以下顺序研读:

  1. crates/bindgen/src/lib.rs:理解配置、生成、安装三段式主流程;
  2. crates/bindgen/src/simple_ir.rs:理解跨语言中间表示;
  3. crates/bindgen/src/render/rust.rs 与 crates/bindgen/pyo3-ffi/src/lib.rs:理解 Rust 绑定如何与 vtable 槽位对接;
  4. crates/bindgen/include/qiskit.h:理解最终交付给 C/C++ 使用者的头文件形态;
  5. crates/bindgen-cli/src/main.rs:理解整套工具链如何在构建脚本中被调用。
  • 科学计算

【免费下载链接】qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.

项目地址:https://gitcode.com/gh_mirrors/qi/qiskit
点击查看免费下载
上一篇:GitHub Readme Streak Stats测试覆盖率提升:从70%到95%的实践
下一篇:OLMo推理服务搭建:高并发API设计

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询