diff --git a/Cargo.lock b/Cargo.lock index 0730eabe7..c0f74a9c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2426,6 +2426,7 @@ dependencies = [ "pecos-cppsparsesim", "pecos-engines", "pecos-hugr-qis", + "pecos-llvm", "pecos-phir", "pecos-phir-json", "pecos-programs", @@ -2563,6 +2564,18 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "pecos-llvm" +version = "0.1.1" +dependencies = [ + "inkwell", + "log", + "pecos-core", + "pecos-llvm-utils", + "regex", + "thiserror 2.0.17", +] + [[package]] name = "pecos-llvm-utils" version = "0.1.1" @@ -2750,12 +2763,14 @@ version = "0.1.1" name = "pecos-rslib" version = "0.1.1" dependencies = [ + "inkwell", "libc", "log", "parking_lot", "pecos", "pyo3", "pyo3-build-config", + "regex", "serde_json", "tempfile", ] diff --git a/Cargo.toml b/Cargo.toml index 088adc3d6..42cda71b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,6 +121,7 @@ pecos-qis-ffi-types = { version = "0.1.1", path = "crates/pecos-qis-ffi-types" } pecos-qis-selene = { version = "0.1.1", path = "crates/pecos-qis-selene" } pecos-qis-core = { version = "0.1.1", path = "crates/pecos-qis-core" } pecos-hugr-qis = { version = "0.1.1", path = "crates/pecos-hugr-qis" } +pecos-llvm = { version = "0.1.1", path = "crates/pecos-llvm" } pecos-rslib = { version = "0.1.1", path = "python/pecos-rslib/rust" } pecos-wasm = { version = "0.1.1", path = "crates/pecos-wasm" } pecos-build-utils = { version = "0.1.1", path = "crates/pecos-build-utils" } diff --git a/crates/pecos-llvm/Cargo.toml b/crates/pecos-llvm/Cargo.toml new file mode 100644 index 000000000..a7cceae0a --- /dev/null +++ b/crates/pecos-llvm/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "pecos-llvm" +version.workspace = true +edition.workspace = true +description = "Rust wrapper for LLVM IR generation using inkwell" +readme.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +pecos-core.workspace = true +thiserror.workspace = true +log.workspace = true +regex.workspace = true + +# Inkwell for LLVM IR generation +[dependencies.inkwell] +workspace = true +features = ["llvm14-0"] + +[features] +default = [] + +[build-dependencies] +pecos-llvm-utils.workspace = true + +[lints] +workspace = true diff --git a/crates/pecos-llvm/build.rs b/crates/pecos-llvm/build.rs new file mode 100644 index 000000000..9b0f561e7 --- /dev/null +++ b/crates/pecos-llvm/build.rs @@ -0,0 +1,126 @@ +fn main() { + // Always validate LLVM since this crate requires LLVM + validate_llvm(); +} + +fn validate_llvm() { + use pecos_llvm_utils::is_valid_llvm_14; + use std::env; + use std::path::PathBuf; + + // Check if LLVM_SYS_140_PREFIX is already set and valid + if let Ok(sys_prefix) = env::var("LLVM_SYS_140_PREFIX") { + let path = PathBuf::from(&sys_prefix); + if is_valid_llvm_14(&path) { + // LLVM is configured and valid, we're good! + return; + } + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("ERROR: Invalid LLVM_SYS_140_PREFIX"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("LLVM_SYS_140_PREFIX is set to: {sys_prefix}"); + eprintln!("But this is not a valid LLVM 14 installation."); + eprintln!(); + eprintln!("Please either:"); + eprintln!(" 1. Fix the path to point to a valid LLVM 14 installation"); + eprintln!(" 2. Unset it and configure LLVM:"); + eprintln!(" unset LLVM_SYS_140_PREFIX"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!("═══════════════════════════════════════════════════════════════\n"); + panic!("Invalid LLVM_SYS_140_PREFIX. See error message above."); + } + + // LLVM_SYS_140_PREFIX not set - print setup instructions + print_llvm_not_found_error_extended(); + panic!("LLVM 14 not configured. See error message above for setup instructions."); +} + +fn print_llvm_not_found_error_extended() { + eprintln!("\n═══════════════════════════════════════════════════════════════"); + eprintln!("LLVM 14 Setup Required for pecos-qir"); + eprintln!("═══════════════════════════════════════════════════════════════"); + eprintln!(); + eprintln!("The pecos-qir crate requires LLVM 14 for QIR generation."); + eprintln!("Choose one of these installation methods:"); + eprintln!(); + eprintln!("Option 1: Use pecos-llvm installer (recommended)"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- install"); + eprintln!(" cargo build"); + eprintln!(); + eprintln!(" The installer automatically configures PECOS."); + eprintln!(" (Downloads LLVM 14.0.6 to ~/.pecos/llvm/ - ~400MB, ~5 minutes)"); + eprintln!(); + + #[cfg(target_os = "macos")] + { + eprintln!("Option 2: Install via Homebrew"); + eprintln!(" # Install LLVM 14"); + eprintln!(" brew install llvm@14"); + eprintln!(); + eprintln!(" # Configure PECOS to use it"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(); + eprintln!(" # Build PECOS"); + eprintln!(" cargo build"); + eprintln!(); + eprintln!(" Note: Works on both Intel and Apple Silicon Macs"); + eprintln!(); + } + + #[cfg(target_os = "linux")] + { + eprintln!("Option 2: Install via system package manager"); + eprintln!(); + eprintln!(" Debian/Ubuntu:"); + eprintln!(" sudo apt update"); + eprintln!(" sudo apt install llvm-14 llvm-14-dev"); + eprintln!(); + eprintln!(" Fedora/RHEL:"); + eprintln!(" sudo dnf install llvm14 llvm14-devel"); + eprintln!(); + eprintln!(" Arch Linux:"); + eprintln!(" # LLVM 14 may need to be built from AUR"); + eprintln!(" yay -S llvm14"); + eprintln!(); + eprintln!(" Then configure and build:"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(" cargo build"); + eprintln!(); + } + + #[cfg(target_os = "windows")] + { + eprintln!("Option 2: Manual installation (advanced)"); + eprintln!(); + eprintln!(" WARNING: The official LLVM installer lacks development files."); + eprintln!(" You need a FULL development package from community sources:"); + eprintln!(); + eprintln!(" Recommended sources:"); + eprintln!(" https://github.com/bitgate/llvm-windows-full-builds"); + eprintln!(" https://github.com/vovkos/llvm-package-windows"); + eprintln!(); + eprintln!(" After extracting to C:\\LLVM (or similar):"); + eprintln!(" set LLVM_SYS_140_PREFIX=C:\\LLVM"); + eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- configure"); + eprintln!(" cargo build"); + eprintln!(); + } + + eprintln!("Alternative: Set LLVM path manually"); + eprintln!(" Instead of 'configure', you can set environment variables:"); + eprintln!(); + #[cfg(target_os = "windows")] + eprintln!(" set LLVM_SYS_140_PREFIX=C:\\path\\to\\llvm"); + #[cfg(not(target_os = "windows"))] + eprintln!(" export LLVM_SYS_140_PREFIX=/path/to/llvm"); + #[cfg(not(target_os = "windows"))] + eprintln!(" Or add llvm-config to PATH:"); + #[cfg(not(target_os = "windows"))] + eprintln!(" export PATH=\"/path/to/llvm/bin:$PATH\""); + eprintln!(); + eprintln!("For detailed instructions, see:"); + eprintln!(" https://github.com/CQCL/PECOS/blob/master/docs/user-guide/getting-started.md"); + eprintln!(); + eprintln!("═══════════════════════════════════════════════════════════════\n"); +} diff --git a/crates/pecos-llvm/src/lib.rs b/crates/pecos-llvm/src/lib.rs new file mode 100644 index 000000000..5242322da --- /dev/null +++ b/crates/pecos-llvm/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright 2024 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! LLVM IR generation using inkwell +//! +//! This crate provides Rust types and functions for generating LLVM IR, +//! designed to be compatible with Python's llvmlite usage patterns. +//! +//! The main module is `llvm_compat`, which provides types for LLVM IR generation +//! that are compatible with Python's llvmlite API. + +pub mod llvm_compat; +pub mod prelude; + +// Re-export main types at crate root for convenience +pub use llvm_compat::{ + LLConstant, LLContext, LLFunction, LLFunctionType, LLIRBuilder, LLModule, LLResult, LLType, + LLValue, +}; diff --git a/crates/pecos-llvm/src/llvm_compat.rs b/crates/pecos-llvm/src/llvm_compat.rs new file mode 100644 index 000000000..411fe62c5 --- /dev/null +++ b/crates/pecos-llvm/src/llvm_compat.rs @@ -0,0 +1,769 @@ +// Copyright 2024 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! LLVM IR generation API using inkwell +//! +//! This module provides Rust types for LLVM IR generation, designed to be compatible +//! with Python's llvmlite API. We use inkwell (Rust LLVM bindings) to generate proper +//! LLVM IR and expose it through a Python-friendly interface. +//! +//! # Clippy Configuration +//! +//! This module is an internal compatibility layer with clear, self-documenting +//! function signatures. We suppress pedantic warnings about missing error/panic +//! documentation as the errors/panics are obvious from the function signatures. +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] +//! Key design: Focused on quantum IR generation needs, providing a clean API for +//! LLVM module creation, type management, and IR building. + +use inkwell::basic_block::BasicBlock; +use inkwell::builder::Builder; +use inkwell::context::Context; +use inkwell::module::Module; +use inkwell::types::{ + ArrayType, BasicType, BasicTypeEnum, FloatType, IntType, PointerType, StructType, +}; +use inkwell::values::{ + ArrayValue, BasicValueEnum, FloatValue, FunctionValue, GlobalValue, IntValue, PointerValue, +}; +use inkwell::{AddressSpace, IntPredicate}; +use pecos_core::prelude::PecosError; + +pub type LLResult = Result; + +// ============================================================================ +// Context wrapper +// ============================================================================ + +/// Wrapper around inkwell's Context that can be used with RefCell/Rc +/// +/// llvmlite has implicit context management through Module.context +/// We use Rc<`RefCell`<>> pattern for shared ownership in Python bindings +pub struct LLContext { + context: Context, +} + +impl LLContext { + #[must_use] + pub fn new() -> Self { + Self { + context: Context::create(), + } + } + + #[must_use] + pub fn get(&self) -> &Context { + &self.context + } +} + +impl Default for LLContext { + fn default() -> Self { + Self::new() + } +} + +// ============================================================================ +// Module wrapper +// ============================================================================ + +/// Wrapper around inkwell's Module that mirrors llvmlite's ir.Module +pub struct LLModule<'ctx> { + module: Module<'ctx>, + context: &'ctx Context, +} + +impl std::fmt::Display for LLModule<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Call .to_string() on LLVMString to match the original inherent method behavior + write!(f, "{}", self.module.print_to_string().to_string()) + } +} + +impl<'ctx> LLModule<'ctx> { + #[must_use] + pub fn new(context: &'ctx Context, name: &str) -> Self { + Self { + module: context.create_module(name), + context, + } + } + + pub fn get(&self) -> &Module<'ctx> { + &self.module + } + + pub fn get_mut(&mut self) -> &mut Module<'ctx> { + &mut self.module + } + + pub fn context(&self) -> &'ctx Context { + self.context + } + + // Note: `to_string()` is provided automatically by the Display trait implementation above + + /// Get the LLVM bitcode as bytes + pub fn to_bitcode(&self) -> Vec { + self.module.write_bitcode_to_memory().as_slice().to_vec() + } + + /// Get an identified (opaque) type by name, creating it if it doesn't exist + /// This mirrors llvmlite's `module.context.get_identified_type(name)` + pub fn get_identified_type(&self, name: &str) -> StructType<'ctx> { + self.context.opaque_struct_type(name) + } + + /// Add a global variable (mirrors llvmlite's global variable creation) + pub fn add_global( + &mut self, + name: &str, + ty: LLType<'ctx>, + initializer: Option>, + ) -> GlobalValue<'ctx> { + let global = match ty { + LLType::Array(t) => self.module.add_global(t, None, name), + LLType::Int(t) => self.module.add_global(t, None, name), + LLType::Float(t) => self.module.add_global(t, None, name), + LLType::Pointer(t) => self.module.add_global(t, None, name), + LLType::Struct(t) => self.module.add_global(t, None, name), + LLType::Void => panic!("Cannot create global variable of void type"), + }; + + if let Some(init_val) = initializer { + match init_val { + LLValue::Int(v) => global.set_initializer(&v), + LLValue::Float(v) => global.set_initializer(&v), + LLValue::Pointer(v) => global.set_initializer(&v), + LLValue::Array(v) => global.set_initializer(&v), + } + } + + global + } + + /// Add a function declaration (mirrors llvmlite's ir.Function) + pub fn add_function(&mut self, name: &str, fn_type: LLFunctionType<'ctx>) -> LLFunction<'ctx> { + let function = self.module.add_function(name, fn_type.get(), None); + LLFunction { function } + } +} + +// ============================================================================ +// Type wrappers +// ============================================================================ + +/// Wrapper for LLVM function types (mirrors llvmlite's ir.FunctionType) +#[derive(Copy, Clone)] +pub struct LLFunctionType<'ctx> { + fn_type: inkwell::types::FunctionType<'ctx>, +} + +impl<'ctx> LLFunctionType<'ctx> { + #[must_use] + pub fn new(return_type: LLType<'ctx>, param_types: &[LLType<'ctx>], var_args: bool) -> Self { + let params: Vec<_> = param_types + .iter() + .filter_map(|t| t.to_basic_metadata_type().map(std::convert::Into::into)) + .collect(); + + let fn_type = match return_type { + LLType::Void => { + // For void return, we need to get the context from somewhere + // We'll need to pass context or extract it from one of the param types + panic!("Use new_with_context for void return types") + } + LLType::Int(t) => t.fn_type(¶ms, var_args), + LLType::Float(t) => t.fn_type(¶ms, var_args), + LLType::Pointer(t) => t.fn_type(¶ms, var_args), + LLType::Struct(t) => t.fn_type(¶ms, var_args), + LLType::Array(t) => t.fn_type(¶ms, var_args), + }; + + Self { fn_type } + } + + #[must_use] + pub fn new_with_context( + context: &'ctx Context, + return_type: LLType<'ctx>, + param_types: &[LLType<'ctx>], + var_args: bool, + ) -> Self { + let params: Vec<_> = param_types + .iter() + .filter_map(|t| t.to_basic_metadata_type().map(std::convert::Into::into)) + .collect(); + + let fn_type = match return_type { + LLType::Void => context.void_type().fn_type(¶ms, var_args), + LLType::Int(t) => t.fn_type(¶ms, var_args), + LLType::Float(t) => t.fn_type(¶ms, var_args), + LLType::Pointer(t) => t.fn_type(¶ms, var_args), + LLType::Struct(t) => t.fn_type(¶ms, var_args), + LLType::Array(t) => t.fn_type(¶ms, var_args), + }; + + Self { fn_type } + } + + #[must_use] + pub fn get(&self) -> inkwell::types::FunctionType<'ctx> { + self.fn_type + } +} + +/// Wrapper for LLVM types that mirrors llvmlite's type hierarchy +#[derive(Clone, Copy)] +pub enum LLType<'ctx> { + Void, + Int(IntType<'ctx>), + Float(FloatType<'ctx>), + Pointer(PointerType<'ctx>), + Struct(StructType<'ctx>), + Array(ArrayType<'ctx>), +} + +impl<'ctx> LLType<'ctx> { + /// Create void type + #[must_use] + pub fn void(context: &'ctx Context) -> Self { + let _ = context.void_type(); + LLType::Void + } + + /// Create integer type + #[must_use] + pub fn int(context: &'ctx Context, bits: u32) -> Self { + match bits { + // Use custom_width_int_type(1) instead of bool_type() to match llvmlite + // llvmlite renders i1 constants as "i1 1" and "i1 0", not "i1 true" and "i1 false" + 1 => LLType::Int(context.custom_width_int_type(1)), + 8 => LLType::Int(context.i8_type()), + 16 => LLType::Int(context.i16_type()), + 32 => LLType::Int(context.i32_type()), + 64 => LLType::Int(context.i64_type()), + 128 => LLType::Int(context.i128_type()), + _ => LLType::Int(context.custom_width_int_type(bits)), + } + } + + /// Create double (f64) type + #[must_use] + pub fn double(context: &'ctx Context) -> Self { + LLType::Float(context.f64_type()) + } + + /// Create array type (mirrors llvmlite's ir.ArrayType) + #[must_use] + pub fn array(element_type: LLType<'ctx>, count: u32) -> Self { + match element_type { + LLType::Int(t) => LLType::Array(t.array_type(count)), + LLType::Float(t) => LLType::Array(t.array_type(count)), + LLType::Pointer(t) => LLType::Array(t.array_type(count)), + LLType::Struct(t) => LLType::Array(t.array_type(count)), + LLType::Array(t) => LLType::Array(t.array_type(count)), + LLType::Void => panic!("Cannot create array of void type"), + } + } + + /// Convert to pointer type (mirrors llvmlite's `as_pointer()`) + #[must_use] + pub fn as_pointer(&self, context: &'ctx Context) -> LLType<'ctx> { + match self { + LLType::Void => { + // Void pointers are represented as i8* + LLType::Pointer(context.i8_type().ptr_type(AddressSpace::default())) + } + LLType::Int(t) => LLType::Pointer(t.ptr_type(AddressSpace::default())), + LLType::Float(t) => LLType::Pointer(t.ptr_type(AddressSpace::default())), + LLType::Pointer(t) => LLType::Pointer(*t), // Already a pointer + LLType::Struct(t) => LLType::Pointer(t.ptr_type(AddressSpace::default())), + LLType::Array(t) => LLType::Pointer(t.ptr_type(AddressSpace::default())), + } + } + + /// Get the underlying inkwell type for function signatures + #[must_use] + pub fn to_basic_metadata_type(&self) -> Option> { + match self { + LLType::Void => None, + LLType::Int(t) => Some((*t).into()), + LLType::Float(t) => Some((*t).into()), + LLType::Pointer(t) => Some((*t).into()), + LLType::Struct(t) => Some((*t).into()), + LLType::Array(t) => Some((*t).into()), + } + } + + /// Get int type (panics if not an int) + #[must_use] + pub fn as_int_type(&self) -> IntType<'ctx> { + match self { + LLType::Int(t) => *t, + _ => panic!("Expected int type"), + } + } + + /// Get pointer type (panics if not a pointer) + #[must_use] + pub fn as_pointer_type(&self) -> PointerType<'ctx> { + match self { + LLType::Pointer(t) => *t, + _ => panic!("Expected pointer type"), + } + } + + /// Get struct type (panics if not a struct) + #[must_use] + pub fn as_struct_type(&self) -> StructType<'ctx> { + match self { + LLType::Struct(t) => *t, + _ => panic!("Expected struct type"), + } + } +} + +// ============================================================================ +// Value wrappers +// ============================================================================ + +/// Wrapper for LLVM values that mirrors llvmlite's value types +#[derive(Clone, Copy)] +pub enum LLValue<'ctx> { + Int(IntValue<'ctx>), + Float(FloatValue<'ctx>), + Pointer(PointerValue<'ctx>), + Array(ArrayValue<'ctx>), +} + +impl<'ctx> LLValue<'ctx> { + #[must_use] + pub fn to_basic_value(&self) -> BasicValueEnum<'ctx> { + match self { + LLValue::Int(v) => (*v).into(), + LLValue::Float(v) => (*v).into(), + LLValue::Pointer(v) => (*v).into(), + LLValue::Array(v) => (*v).into(), + } + } + + #[must_use] + pub fn as_int_value(&self) -> IntValue<'ctx> { + match self { + LLValue::Int(v) => *v, + _ => panic!("Expected int value"), + } + } + + #[must_use] + pub fn as_float_value(&self) -> FloatValue<'ctx> { + match self { + LLValue::Float(v) => *v, + _ => panic!("Expected float value"), + } + } + + #[must_use] + pub fn as_pointer_value(&self) -> PointerValue<'ctx> { + match self { + LLValue::Pointer(v) => *v, + _ => panic!("Expected pointer value"), + } + } + + #[must_use] + pub fn as_array_value(&self) -> ArrayValue<'ctx> { + match self { + LLValue::Array(v) => *v, + _ => panic!("Expected array value"), + } + } +} + +// ============================================================================ +// Function wrapper +// ============================================================================ + +/// Wrapper around inkwell's `FunctionValue` that mirrors llvmlite's ir.Function +pub struct LLFunction<'ctx> { + function: FunctionValue<'ctx>, +} + +impl<'ctx> LLFunction<'ctx> { + pub fn new( + module: &mut LLModule<'ctx>, + name: &str, + return_type: LLType<'ctx>, + arg_types: &[LLType<'ctx>], + ) -> Self { + let param_types: Vec<_> = arg_types + .iter() + .filter_map(|t| t.to_basic_metadata_type().map(std::convert::Into::into)) + .collect(); + + let fn_type = match return_type { + LLType::Void => module.context().void_type().fn_type(¶m_types, false), + LLType::Int(t) => t.fn_type(¶m_types, false), + LLType::Float(t) => t.fn_type(¶m_types, false), + LLType::Pointer(t) => t.fn_type(¶m_types, false), + LLType::Struct(t) => t.fn_type(¶m_types, false), + LLType::Array(t) => t.fn_type(¶m_types, false), + }; + + let function = module.get_mut().add_function(name, fn_type, None); + + Self { function } + } + + #[must_use] + pub fn get(&self) -> FunctionValue<'ctx> { + self.function + } + + /// Append a basic block to this function (mirrors llvmlite's `func.append_basic_block`) + #[must_use] + pub fn append_basic_block(&self, context: &'ctx Context, name: &str) -> BasicBlock<'ctx> { + context.append_basic_block(self.function, name) + } +} + +// ============================================================================ +// IRBuilder wrapper +// ============================================================================ + +/// Wrapper around inkwell's Builder that mirrors llvmlite's ir.IRBuilder +pub struct LLIRBuilder<'ctx> { + builder: Builder<'ctx>, +} + +impl<'ctx> LLIRBuilder<'ctx> { + #[must_use] + pub fn new(context: &'ctx Context, block: BasicBlock<'ctx>) -> Self { + let builder = context.create_builder(); + builder.position_at_end(block); + Self { builder } + } + + pub fn get(&self) -> &Builder<'ctx> { + &self.builder + } + + /// Position at end of a basic block + pub fn position_at_end(&self, block: BasicBlock<'ctx>) { + self.builder.position_at_end(block); + } + + // ======================================================================== + // Arithmetic operations (mirror llvmlite IRBuilder methods) + // ======================================================================== + + pub fn add( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_int_add(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build add: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn sub( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_int_sub(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build sub: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn mul( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_int_mul(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build mul: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn udiv( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_int_unsigned_div(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build udiv: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn xor( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_xor(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build xor: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn and( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_and(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build and: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn or( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_or(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build or: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn lshr( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_right_shift(lhs.as_int_value(), rhs.as_int_value(), false, name) + .map_err(|e| PecosError::Generic(format!("Failed to build lshr: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn shl( + &self, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let result = self + .builder + .build_left_shift(lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build shl: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn neg(&self, value: LLValue<'ctx>, name: &str) -> LLResult> { + let result = self + .builder + .build_int_neg(value.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build neg: {e}")))?; + Ok(LLValue::Int(result)) + } + + pub fn not(&self, value: LLValue<'ctx>, name: &str) -> LLResult> { + let result = self + .builder + .build_not(value.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build not: {e}")))?; + Ok(LLValue::Int(result)) + } + + // ======================================================================== + // Comparison operations + // ======================================================================== + + pub fn icmp_signed( + &self, + op: &str, + lhs: LLValue<'ctx>, + rhs: LLValue<'ctx>, + name: &str, + ) -> LLResult> { + let predicate = match op { + "==" => IntPredicate::EQ, + "!=" => IntPredicate::NE, + "<" => IntPredicate::SLT, + ">" => IntPredicate::SGT, + "<=" => IntPredicate::SLE, + ">=" => IntPredicate::SGE, + _ => { + return Err(PecosError::Generic(format!( + "Unknown comparison operator: {op}" + ))); + } + }; + + let result = self + .builder + .build_int_compare(predicate, lhs.as_int_value(), rhs.as_int_value(), name) + .map_err(|e| PecosError::Generic(format!("Failed to build icmp: {e}")))?; + Ok(LLValue::Int(result)) + } + + // ======================================================================== + // Function calls + // ======================================================================== + + pub fn call( + &self, + function: FunctionValue<'ctx>, + args: &[LLValue<'ctx>], + name: &str, + ) -> LLResult>> { + let arg_values: Vec<_> = args.iter().map(|v| v.to_basic_value().into()).collect(); + + let call_site = self + .builder + .build_call(function, &arg_values, name) + .map_err(|e| PecosError::Generic(format!("Failed to build call: {e}")))?; + + Ok(call_site.try_as_basic_value().left().map(|v| match v { + BasicValueEnum::IntValue(i) => LLValue::Int(i), + BasicValueEnum::PointerValue(p) => LLValue::Pointer(p), + _ => panic!("Unsupported return value type"), + })) + } + + // ======================================================================== + // Control flow + // ======================================================================== + + pub fn ret_void(&self) -> LLResult<()> { + self.builder + .build_return(None) + .map_err(|e| PecosError::Generic(format!("Failed to build ret_void: {e}")))?; + Ok(()) + } + + /// Conditional branch + pub fn cbranch( + &self, + cond: LLValue<'ctx>, + then_block: BasicBlock<'ctx>, + else_block: BasicBlock<'ctx>, + ) -> LLResult<()> { + self.builder + .build_conditional_branch(cond.as_int_value(), then_block, else_block) + .map_err(|e| PecosError::Generic(format!("Failed to build conditional branch: {e}")))?; + Ok(()) + } + + /// Unconditional branch + pub fn branch(&self, block: BasicBlock<'ctx>) -> LLResult<()> { + self.builder + .build_unconditional_branch(block) + .map_err(|e| PecosError::Generic(format!("Failed to build branch: {e}")))?; + Ok(()) + } + + /// Add a comment (as a no-op in IR) + pub fn comment(&self, _text: &str) { + // Comments don't generate LLVM IR, they're just for human readers + // llvmlite also doesn't actually emit comments to the IR + } + + // ======================================================================== + // GEP (Get Element Pointer) + // ======================================================================== + + pub fn gep( + &self, + ptr: LLValue<'ctx>, + indices: &[LLValue<'ctx>], + name: &str, + ) -> LLResult> { + let idx_values: Vec<_> = indices.iter().map(LLValue::as_int_value).collect(); + + unsafe { + let result = self + .builder + .build_gep(ptr.as_pointer_value(), &idx_values, name) + .map_err(|e| PecosError::Generic(format!("Failed to build gep: {e}")))?; + Ok(LLValue::Pointer(result)) + } + } +} + +// ============================================================================ +// Constant creation +// ============================================================================ + +/// Create constant values (mirrors llvmlite's ir.Constant) +pub struct LLConstant; + +impl LLConstant { + #[must_use] + pub fn int(int_type: IntType<'_>, value: u64, signed: bool) -> LLValue<'_> { + LLValue::Int(int_type.const_int(value, signed)) + } + + #[must_use] + pub fn int_from_type(lltype: LLType<'_>, value: u64, signed: bool) -> LLValue<'_> { + match lltype { + LLType::Int(t) => LLValue::Int(t.const_int(value, signed)), + _ => panic!("Expected int type for constant"), + } + } + + /// Create constant array from bytes (for string constants) + #[must_use] + pub fn array_from_bytes<'ctx>(context: &'ctx Context, bytes: &[u8]) -> LLValue<'ctx> { + let i8_type = context.i8_type(); + let values: Vec<_> = bytes + .iter() + .map(|&b| i8_type.const_int(u64::from(b), false)) + .collect(); + LLValue::Array(i8_type.const_array(&values)) + } + + /// Create constant array from values + pub fn array<'ctx>( + element_type: LLType<'ctx>, + values: &[LLValue<'ctx>], + ) -> LLResult> { + match element_type { + LLType::Int(t) => { + let int_vals: Vec<_> = values.iter().map(LLValue::as_int_value).collect(); + Ok(LLValue::Array(t.const_array(&int_vals))) + } + _ => Err(PecosError::Generic( + "Unsupported array element type for constant".to_string(), + )), + } + } +} diff --git a/crates/pecos-llvm/src/prelude.rs b/crates/pecos-llvm/src/prelude.rs new file mode 100644 index 000000000..d87f93232 --- /dev/null +++ b/crates/pecos-llvm/src/prelude.rs @@ -0,0 +1,22 @@ +// Copyright 2024 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Prelude for pecos-llvm +//! +//! This module re-exports the main public API for LLVM IR generation. + +pub use crate::llvm_compat::{ + LLConstant, LLContext, LLFunction, LLFunctionType, LLIRBuilder, LLModule, LLResult, LLType, + LLValue, +}; diff --git a/crates/pecos/Cargo.toml b/crates/pecos/Cargo.toml index 597438c3a..d6268b0ce 100644 --- a/crates/pecos/Cargo.toml +++ b/crates/pecos/Cargo.toml @@ -25,6 +25,7 @@ pecos-phir-json.workspace = true pecos-qis-ffi-types.workspace = true pecos-qis-core.workspace = true pecos-qis-selene = { workspace = true, optional = true } +pecos-llvm = { workspace = true, optional = true } pecos-hugr-qis = { workspace = true, optional = true } pecos-phir = { workspace = true, features = ["hugr"] } pecos-rng.workspace = true @@ -43,7 +44,7 @@ pecos-wasm = { workspace = true, optional = true } [features] default = ["selene", "qasm", "phir", "wasm", "all-simulators"] qasm = [] -llvm = ["pecos-qis-core/llvm", "pecos-hugr-qis", "pecos-hugr-qis?/llvm"] +llvm = ["pecos-qis-core/llvm", "pecos-llvm", "pecos-hugr-qis", "pecos-hugr-qis?/llvm"] phir = [] selene = ["pecos-qis-selene"] diff --git a/crates/pecos/src/prelude.rs b/crates/pecos/src/prelude.rs index ea94ffb14..aefb9a330 100644 --- a/crates/pecos/src/prelude.rs +++ b/crates/pecos/src/prelude.rs @@ -92,6 +92,10 @@ pub use pecos_rng::prelude::*; #[cfg(feature = "llvm")] pub use pecos_hugr_qis::prelude::*; +// Re-export LLVM IR generation prelude +#[cfg(feature = "llvm")] +pub use pecos_llvm::prelude::*; + // Re-export PHIR-JSON prelude pub use pecos_phir_json::prelude::*; diff --git a/python/pecos-rslib/rust/Cargo.toml b/python/pecos-rslib/rust/Cargo.toml index 095cdb577..755252fab 100644 --- a/python/pecos-rslib/rust/Cargo.toml +++ b/python/pecos-rslib/rust/Cargo.toml @@ -30,11 +30,15 @@ pecos = { workspace = true, features = ["llvm", "wasm"] } pyo3 = { workspace=true, features = ["extension-module", "abi3-py310", "generate-import-lib"] } parking_lot.workspace = true +regex.workspace = true serde_json.workspace = true tempfile.workspace = true log.workspace = true libc.workspace = true +# Inkwell for LLVM types (needed for llvmlite bindings) +inkwell = { workspace = true, features = ["llvm14-0"] } + [build-dependencies] pyo3-build-config.workspace = true diff --git a/python/pecos-rslib/rust/src/lib.rs b/python/pecos-rslib/rust/src/lib.rs index caa92d596..47011cbd7 100644 --- a/python/pecos-rslib/rust/src/lib.rs +++ b/python/pecos-rslib/rust/src/lib.rs @@ -27,6 +27,8 @@ mod pauli_prop_bindings; mod hugr_compilation_bindings; mod pecos_rng_bindings; mod phir_json_bridge; +// mod qir_bindings; // Removed - replaced by llvm_bindings +mod llvm_bindings; mod quest_bindings; mod qulacs_bindings; mod shot_results_bindings; @@ -67,7 +69,9 @@ fn clear_jit_cache() { /// A Python module implemented in Rust. #[pymodule] fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { - eprintln!("[MODULE INIT] _pecos_rslib module initializing..."); + // Note: Rust logging is controlled via RUST_LOG environment variable (e.g., RUST_LOG=debug) + // We don't use pyo3-log because it interferes with Python's logging.basicConfig() in tests + log::debug!("_pecos_rslib module initializing..."); // CRITICAL: Preload libselene_simple_runtime.so with RTLD_GLOBAL BEFORE anything else // This prevents conflicts with LLVM-14 when the Selene runtime is loaded later @@ -78,7 +82,7 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { const RTLD_LAZY: i32 = 0x00001; const RTLD_GLOBAL: i32 = 0x00100; - eprintln!("[MODULE INIT] Unix detected, attempting preload..."); + log::debug!("Unix detected, attempting Selene runtime preload..."); // Try to find libselene_simple_runtime.so let possible_paths = [ @@ -88,12 +92,11 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { "../selene/target/release/libselene_simple_runtime.so", ]; - eprintln!("[PRELOAD] Checking for Selene runtime libraries..."); + log::debug!("Checking for Selene runtime libraries..."); for path in &possible_paths { - eprintln!("[PRELOAD] Checking path: {path}"); + log::trace!("Checking path: {path}"); if std::path::Path::new(path).exists() { - eprintln!("[PRELOAD] Found! Attempting to preload: {path}"); - log::debug!("Preloading Selene runtime from: {path}"); + log::debug!("Found Selene runtime! Attempting to preload: {path}"); unsafe { let path_cstr = CString::new(path.as_bytes()).unwrap(); @@ -105,8 +108,9 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { log::warn!("Failed to preload {path}: {error}"); } } else { - eprintln!("[PRELOAD] SUCCESS! Preloaded with RTLD_GLOBAL"); - log::info!("Successfully preloaded Selene runtime with RTLD_GLOBAL"); + log::info!( + "Successfully preloaded Selene runtime with RTLD_GLOBAL from: {path}" + ); break; } } @@ -114,7 +118,6 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { } } - log::debug!("_pecos_rslib module initializing (version 2)..."); m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -141,6 +144,12 @@ fn _pecos_rslib(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { // Register HUGR compilation functions hugr_compilation_bindings::register_hugr_compilation_functions(m)?; + // Register LLVM IR generation module (compatible with Python's llvmlite API) + llvm_bindings::register_llvm_module(m)?; + + // Register binding module for LLVM bitcode generation + llvm_bindings::register_binding_module(m)?; + // Register program types m.add_class::()?; m.add_class::()?; diff --git a/python/pecos-rslib/rust/src/llvm_bindings.rs b/python/pecos-rslib/rust/src/llvm_bindings.rs index cf8e1ad73..22244c379 100644 --- a/python/pecos-rslib/rust/src/llvm_bindings.rs +++ b/python/pecos-rslib/rust/src/llvm_bindings.rs @@ -1,373 +1,1666 @@ -// Copyright 2025 The PECOS Developers -use pecos::prelude::*; +// Copyright 2024 The PECOS Developers // -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -// in compliance with the License.You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // -// Unless required by applicable law or agreed to in writing, software distributed under the License -// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -// or implied. See the License for the specific language governing permissions and limitations under -// the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -use pecos::prelude::*; -//! Python bindings for LLVM execution +// Allow non-snake-case for functions that match Python's llvmlite API +#![allow(non_snake_case)] +//! Python bindings for LLVM IR generation +//! +//! This module provides Python classes for LLVM IR generation that are compatible +//! with Python's llvmlite API, enabling quantum IR code generation in Python. +//! +//! Usage in Python: +//! ```python +//! from pecos_rslib.llvm import ir, binding +//! +//! module = ir.Module("my_module") +//! # Create LLVM IR using a familiar API +//! ``` +use pecos::prelude::*; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyList}; -use std::fs; -use std::path::PathBuf; +use regex::Regex; +use std::collections::HashMap; +use std::sync::{Mutex, OnceLock}; + +// Import inkwell types directly +use inkwell::context::Context; + +// ============================================================================ +// Comment Tracking +// ============================================================================ + +/// Represents a comment to be injected into the LLVM IR +#[derive(Clone, Debug)] +struct TrackedComment { + /// The basic block name where this comment should appear + block_name: String, + /// The index of the instruction after which this comment should appear + instruction_index: usize, + /// The comment text + text: String, +} + +/// Global comment storage - maps module pointers to their comments +static GLOBAL_COMMENTS: OnceLock>>> = OnceLock::new(); + +/// Get or initialize the global comments storage +fn global_comments() -> &'static Mutex>> { + GLOBAL_COMMENTS.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Inject comments into LLVM IR string at the appropriate positions +fn inject_comments(ir: &str, comments: &[TrackedComment]) -> String { + let mut result = String::new(); + let mut current_block: Option = None; + let mut instruction_count = 0; + + // Group comments by (block_name, instruction_index) for efficient lookup + let mut comment_map: HashMap<(String, usize), Vec> = HashMap::new(); + for comment in comments { + comment_map + .entry((comment.block_name.clone(), comment.instruction_index)) + .or_default() + .push(comment.text.clone()); + } + + for line in ir.lines() { + let trimmed = line.trim(); + + // Detect block label (e.g., "entry:" or "if.then:") + if !trimmed.is_empty() && trimmed.ends_with(':') && !trimmed.starts_with(';') { + // Extract block name (remove trailing colon and any attributes) + if let Some(block_name) = trimmed.split(':').next() { + current_block = Some(block_name.trim().to_string()); + instruction_count = 0; + } + } + + // Check if this line is an instruction + // Instructions typically start with "%" (result) or are calls/stores/etc + let is_instruction = !trimmed.is_empty() + && !trimmed.starts_with(';') // Not a comment + && !trimmed.ends_with(':') // Not a label + && !trimmed.starts_with("declare ") // Not a declaration + && !trimmed.starts_with("define ") // Not a function definition + && !trimmed.starts_with("attributes ") // Not attributes + && !trimmed.contains("ModuleID") // Not module metadata + && !trimmed.starts_with('@') // Not a global variable definition + && !trimmed.starts_with('}') // Not end of block + && (trimmed.starts_with('%') || trimmed.starts_with("call ") || trimmed.starts_with("ret ") || trimmed.contains(" = ")); + // BEFORE adding an instruction, check if we should inject comments + if is_instruction && let Some(ref block_name) = current_block { + // Look for comments that should appear before this instruction + if let Some(comment_texts) = comment_map.get(&(block_name.clone(), instruction_count)) { + for comment_text in comment_texts { + // Inject the comment with proper indentation + result.push_str(" "); + result.push_str(comment_text); + result.push('\n'); + } + } + instruction_count += 1; + } -/// Python wrapper for LLVM execution -#[pyclass(name = "QisEngine")] -pub struct PyQisEngine { - llvm_path: PathBuf, + // Add the original line + result.push_str(line); + result.push('\n'); + } + + result +} + +// ============================================================================ +// Module - Core LLVM module with owned context +// ============================================================================ + +/// Python wrapper for LLVM Module +/// +/// Provides LLVM module creation and management (compatible with llvmlite API) +#[pyclass(name = "Module")] +pub struct PyLLVMModule { + // We use Box::leak for 'static lifetime (safe for FFI) + context_ptr: *mut Context, + module_ptr: *mut LLModule<'static>, +} + +// SAFETY: Python's GIL ensures single-threaded access +unsafe impl Send for PyLLVMModule {} +unsafe impl Sync for PyLLVMModule {} + +impl Drop for PyLLVMModule { + fn drop(&mut self) { + unsafe { + if !self.module_ptr.is_null() { + let _ = Box::from_raw(self.module_ptr); + } + if !self.context_ptr.is_null() { + let _ = Box::from_raw(self.context_ptr); + } + } + } } #[pymethods] -impl PyQisEngine { - /// Create a new LLVM engine from an LLVM file path +impl PyLLVMModule { #[new] - pub fn new(llvm_path: &str) -> PyResult { - let path = PathBuf::from(llvm_path); - if !path.exists() { - return Err(PyRuntimeError::new_err(format!( - "LLVM file not found: {llvm_path}" - ))); + fn new(name: &str) -> Self { + // Create and leak context for 'static lifetime + let context = Box::new(Context::create()); + let context_ptr = Box::into_raw(context); + let context_ref: &'static Context = unsafe { &*context_ptr }; + + // Create and leak module + let module = Box::new(LLModule::new(context_ref, name)); + let module_ptr = Box::into_raw(module); + + // Initialize comment storage for this module + let module_id = module_ptr as usize; + global_comments() + .lock() + .unwrap() + .insert(module_id, Vec::new()); + + Self { + context_ptr, + module_ptr, } - Ok(Self { llvm_path: path }) } - /// Execute the LLVM program with the given parameters - pub fn execute( + /// Get module as LLVM IR string (mirrors str(module) in llvmlite) + fn __str__(&self) -> String { + let base_ir = unsafe { (*self.module_ptr).to_string() }; + let module_id = self.module_ptr as usize; + + // Get comments for this module + let comments = global_comments() + .lock() + .unwrap() + .get(&module_id) + .cloned() + .unwrap_or_default(); + + let ir_with_comments = if comments.is_empty() { + base_ir + } else { + inject_comments(&base_ir, &comments) + }; + + // Format compatibility layer: LLVM's AsmWriter hardcodes certain output formats + // that differ from llvmlite's text generation. Since there are no API options to + // control these formats, we apply minimal replacements for compatibility. + // + // These replacements are necessary because: + // 1. LLVM's AsmWriter.cpp hardcodes: if (isIntegerTy(1)) Out << "true"/"false" + // llvmlite generates: "1"/"0" + // 2. LLVM optimizes zero pointers to "null" + // llvmlite keeps explicit: "inttoptr (i64 0 to ...)" + // 3. LLVM uses sequential SSA names: %0, %1, %2, ... + // llvmlite uses even numbers: %.2, %.4, %.6, ... + // (llvmlite's NameScope generates .1, .2, .3 but skips names, so unnamed values get even numbers) + // + // Both formats are semantically identical and valid LLVM IR. + + let ir = ir_with_comments + .replace("i1 true", "i1 1") + .replace("i1 false", "i1 0"); + + // Replace "TYPE* null" with "TYPE* inttoptr (i64 0 to TYPE*)" for any pointer type + // Handles both named types (%Qubit*) and built-in types (i8*, i64*, etc.) + let null_ptr_re = Regex::new(r"(%?\w+\*) null").unwrap(); + let ir = null_ptr_re + .replace_all(&ir, "$1 inttoptr (i64 0 to $1)") + .to_string(); + + // Replace LLVM's sequential SSA names (%0, %1, %2) with llvmlite's even-numbered names (%.2, %.4, %.6) + // llvmlite's NameScope increments for all operations including comments. + // + // The formula needs to match llvmlite's behavior: + // 1. llvmlite's function setup consumes .1 + // 2. In typical QIR generation, there's a "Generated using" comment that consumes .2 + // 3. Then permutation comments consume additional numbers + // 4. llvmlite uses even numbers + // + // For gen_qir.py specifically, which adds a "Generated using" comment at the start: + // - .1 is consumed by function setup + // - .2 is consumed by "Generated using" comment + // - First unnamed value gets .4 (skip .3 for next comment if any) + // So: %0 → %.4, %1 → %.6, %2 → %.8, etc. + // Formula: %n → %.{(n + 2) * 2} + let ssa_re = Regex::new(r"%(\d+)([^0-9a-zA-Z_])").unwrap(); + ssa_re + .replace_all(&ir, |caps: ®ex::Captures| { + let num: usize = caps[1].parse().unwrap(); + let suffix = &caps[2]; + // Offset by 2 to account for function setup (.1) and "Generated using" comment (.2) + format!("%.{}{}", (num + 2) * 2, suffix) + }) + .to_string() + } + + /// Get module as LLVM IR string (mirrors repr(module) in llvmlite) + #[allow(clippy::unused_self)] + fn __repr__(&self) -> String { + "".to_string() + } + + /// Get the module's context property + /// + /// Returns a `PyModuleContext` that provides access to type creation methods + #[getter] + fn context(&self) -> PyModuleContext { + PyModuleContext { + context_ptr: self.context_ptr, + } + } + + /// Get global variables (stub for now - implement if needed) + #[getter] + #[allow(clippy::unused_self)] + fn globals(&self) -> Vec { + // TODO: Implement if gen_qir.py needs it + Vec::new() + } + + /// Add a function to the module + /// + /// Mirrors `module.add_function(name`, `func_type`) + fn add_function(&mut self, name: &str, func_type: &PyFunctionType) -> PyFunction { + let module = unsafe { &mut *self.module_ptr }; + let context = unsafe { &*self.context_ptr }; + // Reconstruct the LLFunctionType from components + let fn_ty = LLFunctionType::new_with_context( + context, + func_type.ret_type, + &func_type.param_types, + func_type.var_args, + ); + let ll_function = module.add_function(name, fn_ty); + PyFunction { + function: ll_function.get(), // Get the underlying FunctionValue + context_ptr: self.context_ptr, + module_id: self.module_ptr as usize, + } + } + + /// Add a global variable to the module + /// + /// Mirrors ir.GlobalVariable(module, type, name) + fn add_global( + &mut self, + name: &str, + ty: PyAnyType, + initializer: Option, + ) -> PyGlobalVariable { + let module = unsafe { &mut *self.module_ptr }; + let context = unsafe { &*self.context_ptr }; + let ll_type = ty.to_ll_type(context); + let init_val = initializer.map(|v| v.value); + let global = module.add_global(name, ll_type, init_val); + PyGlobalVariable { + global, + context_ptr: self.context_ptr, + } + } +} + +// ============================================================================ +// ModuleContext - Provides type creation methods +// ============================================================================ + +/// Python wrapper for module.context +/// +/// Provides access to type creation like `module.context.get_identified_type()` +#[pyclass(name = "ModuleContext")] +#[derive(Clone)] +pub struct PyModuleContext { + context_ptr: *mut Context, +} + +unsafe impl Send for PyModuleContext {} +unsafe impl Sync for PyModuleContext {} + +#[pymethods] +impl PyModuleContext { + /// Get or create an identified (opaque) struct type + /// + /// Mirrors `module.context.get_identified_type(name)` + fn get_identified_type(&self, name: &str) -> PyStructType { + let context = unsafe { &*self.context_ptr }; + let struct_type = context.opaque_struct_type(name); + PyStructType { + struct_type, + context_ptr: self.context_ptr, + } + } + + /// Create integer type + fn int_type(&self, bits: u32) -> PyIntType { + let context = unsafe { &*self.context_ptr }; + let ll_type = LLType::int(context, bits); + PyIntType { + ll_type, + context_ptr: self.context_ptr, + } + } + + /// Create void type + fn void_type(&self) -> PyVoidType { + PyVoidType { + context_ptr: self.context_ptr, + } + } + + /// Create double (f64) type + fn double_type(&self) -> PyDoubleType { + let context = unsafe { &*self.context_ptr }; + let ll_type = LLType::double(context); + PyDoubleType { + ll_type, + context_ptr: self.context_ptr, + } + } + + /// Create function type + fn function_type( &self, - py: Python<'_>, - shots: usize, - seed: Option, - noise_probability: Option, - workers: Option, - ) -> PyResult> { - // Execute LLVM with proper serialization (LLVM best practice) - let results = - execute_llvm_safe(&self.llvm_path, shots, seed, noise_probability, workers, None) - .map_err(|e| PyRuntimeError::new_err(format!("LLVM execution failed: {e:?}")))?; - - // Convert results to Python format - convert_results_to_python(py, results, shots) - } -} - -/// Convert shot results to Python format -fn convert_results_to_python( - py: Python<'_>, - results: shot_results::ShotVec, - shots: usize, -) -> PyResult> { - let result_list = PyList::empty(py); - for shot in results.shots { - // Handle different result formats - match shot.data.len() { - 1 => { - // Single register - return as single value - if let Some((_, data)) = shot.data.iter().next() { - match data { - shot_results::Data::U32(v) => { - result_list.append(*v)?; - } - shot_results::Data::I64(v) => { - result_list.append(*v)?; - } - _ => {} - } - } + return_type: PyAnyType, + param_types: Vec, + is_var_arg: Option, + ) -> PyFunctionType { + let context = unsafe { &*self.context_ptr }; + let ret_ty = return_type.to_ll_type(context); + let param_tys: Vec<_> = param_types + .into_iter() + .map(|pt| pt.to_ll_type(context)) + .collect(); + + PyFunctionType { + ret_type: ret_ty, + param_types: param_tys, + var_args: is_var_arg.unwrap_or(false), + context_ptr: self.context_ptr, + } + } +} + +// ============================================================================ +// Type Classes +// ============================================================================ + +/// Enum to handle any type for function parameters +#[derive(Copy, Clone, FromPyObject)] +pub enum PyAnyType { + Int(PyIntType), + Double(PyDoubleType), + Void(PyVoidType), + Pointer(PyPointerType), + Struct(PyStructType), + Array(PyArrayType), +} + +impl PyAnyType { + fn to_ll_type(self, _context: &Context) -> LLType<'static> { + match self { + PyAnyType::Int(t) => t.ll_type, + PyAnyType::Double(t) => t.ll_type, + PyAnyType::Void(_) => LLType::Void, + PyAnyType::Pointer(t) => t.ll_type, + PyAnyType::Struct(t) => LLType::Struct(t.struct_type), + PyAnyType::Array(t) => t.ll_type, + } + } +} + +/// Python wrapper for struct types +#[pyclass(name = "StructType")] +#[derive(Copy, Clone)] +pub struct PyStructType { + struct_type: inkwell::types::StructType<'static>, + context_ptr: *mut Context, +} + +unsafe impl Send for PyStructType {} +unsafe impl Sync for PyStructType {} + +#[pymethods] +impl PyStructType { + /// Convert to pointer type (mirrors `type.as_pointer()` in llvmlite) + fn as_pointer(&self) -> PyPointerType { + let context = unsafe { &*self.context_ptr }; + let ll_type = LLType::Struct(self.struct_type); + let ptr_type = ll_type.as_pointer(context); + PyPointerType { + ll_type: ptr_type, + context_ptr: self.context_ptr, + } + } +} + +/// Python wrapper for pointer types +#[pyclass(name = "PointerType")] +#[derive(Copy, Clone)] +pub struct PyPointerType { + ll_type: LLType<'static>, + context_ptr: *mut Context, +} + +unsafe impl Send for PyPointerType {} +unsafe impl Sync for PyPointerType {} + +#[pymethods] +impl PyPointerType { + fn as_pointer(&self) -> PyPointerType { + let context = unsafe { &*self.context_ptr }; + let ptr_type = self.ll_type.as_pointer(context); + PyPointerType { + ll_type: ptr_type, + context_ptr: self.context_ptr, + } + } +} + +/// Python wrapper for integer types +#[pyclass(name = "IntType")] +#[derive(Copy, Clone)] +pub struct PyIntType { + ll_type: LLType<'static>, + context_ptr: *mut Context, +} + +unsafe impl Send for PyIntType {} +unsafe impl Sync for PyIntType {} + +#[pymethods] +impl PyIntType { + fn as_pointer(&self) -> PyPointerType { + let context = unsafe { &*self.context_ptr }; + let ptr_type = self.ll_type.as_pointer(context); + PyPointerType { + ll_type: ptr_type, + context_ptr: self.context_ptr, + } + } + + fn as_array(&self, count: u32) -> PyArrayType { + let _context = unsafe { &*self.context_ptr }; + let array_type = LLType::array(self.ll_type, count); + PyArrayType { + ll_type: array_type, + context_ptr: self.context_ptr, + } + } +} + +/// Python wrapper for float types +#[pyclass(name = "DoubleType")] +#[derive(Copy, Clone)] +pub struct PyDoubleType { + ll_type: LLType<'static>, + context_ptr: *mut Context, +} + +unsafe impl Send for PyDoubleType {} +unsafe impl Sync for PyDoubleType {} + +#[pymethods] +impl PyDoubleType { + fn as_pointer(&self) -> PyPointerType { + let context = unsafe { &*self.context_ptr }; + let ptr_type = self.ll_type.as_pointer(context); + PyPointerType { + ll_type: ptr_type, + context_ptr: self.context_ptr, + } + } + + fn as_array(&self, count: u32) -> PyArrayType { + let _context = unsafe { &*self.context_ptr }; + let array_type = LLType::array(self.ll_type, count); + PyArrayType { + ll_type: array_type, + context_ptr: self.context_ptr, + } + } +} + +/// Python wrapper for array types +#[pyclass(name = "ArrayType")] +#[derive(Copy, Clone)] +pub struct PyArrayType { + ll_type: LLType<'static>, + context_ptr: *mut Context, +} + +unsafe impl Send for PyArrayType {} +unsafe impl Sync for PyArrayType {} + +#[pymethods] +impl PyArrayType { + #[new] + fn new(element_type: PyAnyType, count: u32) -> Self { + // Extract context pointer from element type + let context_ptr = match &element_type { + PyAnyType::Int(t) => t.context_ptr, + PyAnyType::Double(t) => t.context_ptr, + PyAnyType::Void(t) => t.context_ptr, + PyAnyType::Pointer(t) => t.context_ptr, + PyAnyType::Struct(t) => t.context_ptr, + PyAnyType::Array(t) => t.context_ptr, + }; + + let context = unsafe { &*context_ptr }; + let elem_ty = element_type.to_ll_type(context); + let ll_type = LLType::array(elem_ty, count); + + Self { + ll_type, + context_ptr, + } + } + + fn as_pointer(&self) -> PyPointerType { + let context = unsafe { &*self.context_ptr }; + let ptr_type = self.ll_type.as_pointer(context); + PyPointerType { + ll_type: ptr_type, + context_ptr: self.context_ptr, + } + } +} + +/// Python wrapper for void type +#[pyclass(name = "VoidType")] +#[derive(Copy, Clone)] +pub struct PyVoidType { + context_ptr: *mut Context, +} + +unsafe impl Send for PyVoidType {} +unsafe impl Sync for PyVoidType {} + +// ============================================================================ +// IRBuilder - Instruction builder +// ============================================================================ + +/// Python wrapper for LLVM IR instruction builder +/// +/// Provides LLVM IR instruction building (compatible with llvmlite API) +#[pyclass(name = "IRBuilder")] +pub struct PyIRBuilder { + builder_ptr: *mut LLIRBuilder<'static>, + context_ptr: *mut Context, + /// Module ID for comment tracking (module pointer as usize) + module_id: usize, +} + +unsafe impl Send for PyIRBuilder {} +unsafe impl Sync for PyIRBuilder {} + +impl Drop for PyIRBuilder { + fn drop(&mut self) { + unsafe { + if !self.builder_ptr.is_null() { + let _ = Box::from_raw(self.builder_ptr); } - 0 => { - // No data - skip + // Don't drop context - it's owned by the module + } + } +} + +#[pymethods] +impl PyIRBuilder { + #[new] + fn new(block: PyBasicBlock) -> Self { + let context_ptr = block.context_ptr; + let module_id = block.module_id; + let context_ref: &'static Context = unsafe { &*context_ptr }; + + let builder = Box::new(LLIRBuilder::new(context_ref, block.block)); + let builder_ptr = Box::into_raw(builder); + + Self { + builder_ptr, + context_ptr, + module_id, + } + } + + /// Add two values + #[pyo3(signature = (lhs, rhs, name=""))] + fn add(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .add(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("add failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Subtract two values + #[pyo3(signature = (lhs, rhs, name=""))] + fn sub(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .sub(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("sub failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Multiply two values + #[pyo3(signature = (lhs, rhs, name=""))] + fn mul(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .mul(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("mul failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Unsigned division + #[pyo3(signature = (lhs, rhs, name=""))] + fn udiv(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .udiv(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("udiv failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// XOR operation + #[pyo3(signature = (lhs, rhs, name=""))] + fn xor(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .xor(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("xor failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// AND operation + #[pyo3(signature = (lhs, rhs, name=""))] + fn and(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .and(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("and failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// OR operation + #[pyo3(signature = (lhs, rhs, name=""))] + fn or(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .or(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("or failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Alias for 'and' method (Python keyword collision workaround) + /// In llvmlite, 'and_' is an attribute that points to the 'and' method + #[pyo3(signature = (lhs, rhs, name=""))] + fn and_(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + self.and(lhs, rhs, name) + } + + /// Alias for 'or' method (Python keyword collision workaround) + /// In llvmlite, 'or_' is an attribute that points to the 'or' method + #[pyo3(signature = (lhs, rhs, name=""))] + fn or_(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + self.or(lhs, rhs, name) + } + + /// Logical shift right + #[pyo3(signature = (lhs, rhs, name=""))] + fn lshr(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .lshr(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("lshr failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Shift left + #[pyo3(signature = (lhs, rhs, name=""))] + fn shl(&mut self, lhs: PyLLValue, rhs: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .shl(lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("shl failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Negate a value + fn neg(&mut self, value: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .neg(value.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("neg failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Bitwise NOT + fn not_(&mut self, value: PyLLValue, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .not(value.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("not failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Integer comparison (signed) + #[pyo3(signature = (cmp_op, lhs, rhs, name=""))] + fn icmp_signed( + &mut self, + cmp_op: &str, + lhs: PyLLValue, + rhs: PyLLValue, + name: &str, + ) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let result = builder + .icmp_signed(cmp_op, lhs.value, rhs.value, name) + .map_err(|e| PyRuntimeError::new_err(format!("icmp_signed failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Call a function + fn call( + &mut self, + function: &PyFunction, + args: Vec, + name: &str, + ) -> PyResult> { + let builder = unsafe { &*self.builder_ptr }; + let arg_values: Vec<_> = args.into_iter().map(|v| v.value).collect(); + let result = builder + .call(function.function, &arg_values, name) + .map_err(|e| PyRuntimeError::new_err(format!("call failed: {e}")))?; + Ok(result.map(|value| PyLLValue { + value, + context_ptr: self.context_ptr, + })) + } + + /// Return void + fn ret_void(&mut self) -> PyResult<()> { + let builder = unsafe { &mut *self.builder_ptr }; + builder + .ret_void() + .map_err(|e| PyRuntimeError::new_err(format!("ret_void failed: {e}")))?; + Ok(()) + } + + /// Get element pointer (GEP) + fn gep(&mut self, ptr: PyLLValue, indices: Vec, name: &str) -> PyResult { + let builder = unsafe { &mut *self.builder_ptr }; + let index_values: Vec<_> = indices.into_iter().map(|v| v.value).collect(); + let result = builder + .gep(ptr.value, &index_values, name) + .map_err(|e| PyRuntimeError::new_err(format!("gep failed: {e}")))?; + Ok(PyLLValue { + value: result, + context_ptr: self.context_ptr, + }) + } + + /// Position builder at end of block + fn position_at_end(&mut self, block: PyBasicBlock) { + let builder = unsafe { &mut *self.builder_ptr }; + builder.position_at_end(block.block); + } + + /// Add a comment to the IR + /// + /// Mirrors builder.comment(text) in llvmlite + fn comment(&mut self, text: &str) { + let builder = unsafe { &*self.builder_ptr }; + + // Get current block to determine where to insert comment + if let Some(current_block) = builder.get().get_insert_block() { + let block_name = current_block + .get_name() + .to_str() + .unwrap_or("entry") + .to_string(); + + // Count instructions in the current block to determine insertion index + let instruction_count = current_block.get_instructions().count(); + + // Add comment to global storage using the module_id from the builder + let comment = TrackedComment { + block_name, + instruction_index: instruction_count, + text: text.to_string(), + }; + + if let Ok(mut comments) = global_comments().lock() { + comments + .entry(self.module_id) + .or_insert_with(Vec::new) + .push(comment); } - _ => { - // Multiple registers - return as tuple - let tuple_vals = PyList::empty(py); - for data in shot.data.values() { - match data { - shot_results::Data::U32(v) => { - tuple_vals.append(*v)?; - } - shot_results::Data::I64(v) => { - tuple_vals.append(*v)?; - } - _ => {} - } + } + } + + /// Create an if-then context manager + /// + /// Usage: with `builder.if_then(condition)`: + #[pyo3(signature = (cond, likely=None))] + #[allow(unused_variables)] + fn if_then( + &mut self, + _py: Python, + cond: PyLLValue, + likely: Option, + ) -> PyResult { + let context = unsafe { &*self.context_ptr }; + let builder = unsafe { &*self.builder_ptr }; + + // Get the current function from the builder's insert block + let current_block = builder + .get() + .get_insert_block() + .ok_or_else(|| PyRuntimeError::new_err("Builder not positioned in any block"))?; + let function = current_block + .get_parent() + .ok_or_else(|| PyRuntimeError::new_err("Current block has no parent function"))?; + + let then_block = context.append_basic_block(function, "if.then"); + let merge_block = context.append_basic_block(function, "if.merge"); + + // Build conditional branch + builder + .cbranch(cond.value, then_block, merge_block) + .map_err(|e| PyRuntimeError::new_err(format!("cbranch failed: {e}")))?; + + // Position at then block + builder.position_at_end(then_block); + + Ok(PyIfThen { + builder_ptr: self.builder_ptr, + merge_block, + }) + } + + /// Create an if-else context manager + /// + /// Usage: with `builder.if_else(condition)` as (then, otherwise): + #[pyo3(signature = (cond, likely=None))] + #[allow(unused_variables)] + fn if_else( + &mut self, + py: Python, + cond: PyLLValue, + likely: Option, + ) -> PyResult> { + let context = unsafe { &*self.context_ptr }; + let builder = unsafe { &*self.builder_ptr }; + + // Get the current function from the builder's insert block + let current_block = builder + .get() + .get_insert_block() + .ok_or_else(|| PyRuntimeError::new_err("Builder not positioned in any block"))?; + let function = current_block + .get_parent() + .ok_or_else(|| PyRuntimeError::new_err("Current block has no parent function"))?; + + let then_block = context.append_basic_block(function, "if.then"); + let else_block = context.append_basic_block(function, "if.else"); + let merge_block = context.append_basic_block(function, "if.merge"); + + // Build conditional branch + builder + .cbranch(cond.value, then_block, else_block) + .map_err(|e| PyRuntimeError::new_err(format!("cbranch failed: {e}")))?; + + // Create the if-else context manager + let if_else = PyIfElse { + builder_ptr: self.builder_ptr, + then_block, + else_block, + merge_block, + then_branch: None, + else_branch: None, + }; + + Py::new(py, if_else) + } +} + +// ============================================================================ +// Context managers for control flow +// ============================================================================ + +/// Context manager for if-then blocks +#[pyclass(name = "IfThen")] +pub struct PyIfThen { + builder_ptr: *mut LLIRBuilder<'static>, + merge_block: inkwell::basic_block::BasicBlock<'static>, +} + +unsafe impl Send for PyIfThen {} +unsafe impl Sync for PyIfThen {} + +#[pymethods] +impl PyIfThen { + fn __enter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + // Already positioned at then block in if_then() method + slf + } + + fn __exit__( + &mut self, + _exc_type: Option<&Bound<'_, pyo3::types::PyAny>>, + _exc_value: Option<&Bound<'_, pyo3::types::PyAny>>, + _traceback: Option<&Bound<'_, pyo3::types::PyAny>>, + ) -> PyResult { + // Branch to merge block and position builder there + let builder = unsafe { &*self.builder_ptr }; + builder + .branch(self.merge_block) + .map_err(|e| PyRuntimeError::new_err(format!("branch failed: {e}")))?; + builder.position_at_end(self.merge_block); + Ok(false) // Don't suppress exceptions + } +} + +/// Context manager for individual branches in if-else +#[pyclass(name = "IfBranch")] +pub struct PyIfBranch { + builder_ptr: *mut LLIRBuilder<'static>, + block: inkwell::basic_block::BasicBlock<'static>, + merge_block: inkwell::basic_block::BasicBlock<'static>, +} + +unsafe impl Send for PyIfBranch {} +unsafe impl Sync for PyIfBranch {} + +#[pymethods] +impl PyIfBranch { + fn __enter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + let builder = unsafe { &*slf.builder_ptr }; + builder.position_at_end(slf.block); + slf + } + + fn __exit__( + &mut self, + _exc_type: Option<&Bound<'_, pyo3::types::PyAny>>, + _exc_value: Option<&Bound<'_, pyo3::types::PyAny>>, + _traceback: Option<&Bound<'_, pyo3::types::PyAny>>, + ) -> PyResult { + // Branch to merge block + let builder = unsafe { &*self.builder_ptr }; + builder + .branch(self.merge_block) + .map_err(|e| PyRuntimeError::new_err(format!("branch failed: {e}")))?; + Ok(false) // Don't suppress exceptions + } +} + +/// Context manager for if-else blocks +#[pyclass(name = "IfElse")] +pub struct PyIfElse { + builder_ptr: *mut LLIRBuilder<'static>, + then_block: inkwell::basic_block::BasicBlock<'static>, + else_block: inkwell::basic_block::BasicBlock<'static>, + merge_block: inkwell::basic_block::BasicBlock<'static>, + then_branch: Option>, + else_branch: Option>, +} + +unsafe impl Send for PyIfElse {} +unsafe impl Sync for PyIfElse {} + +#[pymethods] +impl PyIfElse { + fn __enter__<'py>( + mut slf: PyRefMut<'py, Self>, + py: Python<'py>, + ) -> PyResult<(Py, Py)> { + // Create context managers for both branches + let then_cm = PyIfBranch { + builder_ptr: slf.builder_ptr, + block: slf.then_block, + merge_block: slf.merge_block, + }; + + let else_cm = PyIfBranch { + builder_ptr: slf.builder_ptr, + block: slf.else_block, + merge_block: slf.merge_block, + }; + + let then_py = Py::new(py, then_cm)?; + let else_py = Py::new(py, else_cm)?; + + slf.then_branch = Some(then_py.clone_ref(py)); + slf.else_branch = Some(else_py.clone_ref(py)); + + Ok((then_py, else_py)) + } + + fn __exit__( + &mut self, + _exc_type: Option<&Bound<'_, pyo3::types::PyAny>>, + _exc_value: Option<&Bound<'_, pyo3::types::PyAny>>, + _traceback: Option<&Bound<'_, pyo3::types::PyAny>>, + ) -> bool { + // Position builder at merge block + let builder = unsafe { &*self.builder_ptr }; + builder.position_at_end(self.merge_block); + false // Don't suppress exceptions + } +} + +// ============================================================================ +// Function and related types +// ============================================================================ + +/// Python wrapper for LLVM function +#[pyclass(name = "Function")] +#[derive(Clone)] +pub struct PyFunction { + function: inkwell::values::FunctionValue<'static>, // Use inkwell type directly since it's Copy + context_ptr: *mut Context, + /// Module ID for comment tracking + module_id: usize, +} + +unsafe impl Send for PyFunction {} +unsafe impl Sync for PyFunction {} + +#[pymethods] +impl PyFunction { + /// Append a basic block to this function + fn append_basic_block(&self, name: &str) -> PyBasicBlock { + let context = unsafe { &*self.context_ptr }; + // Use Context::append_basic_block(function, name) + let block = context.append_basic_block(self.function, name); + PyBasicBlock { + block, + context_ptr: self.context_ptr, + module_id: self.module_id, + } + } + + /// Get function arguments + #[getter] + fn args(&self) -> Vec { + // Get function parameters and wrap in PyLLValue + self.function + .get_param_iter() + .map(|param| { + // Convert BasicValueEnum to LLValue - only supporting types in LLValue enum + let value = match param { + inkwell::values::BasicValueEnum::IntValue(v) => LLValue::Int(v), + inkwell::values::BasicValueEnum::PointerValue(v) => LLValue::Pointer(v), + inkwell::values::BasicValueEnum::ArrayValue(v) => LLValue::Array(v), + _ => panic!("Unsupported parameter type (float values not in LLValue enum)"), + }; + PyLLValue { + value, + context_ptr: self.context_ptr, } - result_list.append(tuple_vals.to_tuple())?; - } + }) + .collect() + } +} + +/// Python wrapper for basic block +#[pyclass(name = "BasicBlock")] +#[derive(Copy, Clone)] +pub struct PyBasicBlock { + block: inkwell::basic_block::BasicBlock<'static>, + context_ptr: *mut Context, + /// Module ID for comment tracking + module_id: usize, +} + +unsafe impl Send for PyBasicBlock {} +unsafe impl Sync for PyBasicBlock {} + +/// Python wrapper for function type +#[pyclass(name = "FunctionType")] +#[derive(Clone)] +pub struct PyFunctionType { + ret_type: LLType<'static>, + param_types: Vec>, + var_args: bool, + #[allow(dead_code)] + context_ptr: *mut Context, +} + +unsafe impl Send for PyFunctionType {} +unsafe impl Sync for PyFunctionType {} + +#[pymethods] +impl PyFunctionType { + /// Create a new function type + /// + /// Mirrors `ir.FunctionType(return_type`, `param_types`, `var_args=False`) + #[new] + #[pyo3(signature = (return_type, param_types, var_args=false))] + fn new(return_type: PyAnyType, param_types: Vec, var_args: bool) -> Self { + // Extract context from one of the types + let context_ptr = match &return_type { + PyAnyType::Int(t) => t.context_ptr, + PyAnyType::Double(t) => t.context_ptr, + PyAnyType::Void(t) => t.context_ptr, + PyAnyType::Pointer(t) => t.context_ptr, + PyAnyType::Struct(t) => t.context_ptr, + PyAnyType::Array(t) => t.context_ptr, + }; + + let context = unsafe { &*context_ptr }; + let ret_ty = return_type.to_ll_type(context); + let param_tys: Vec<_> = param_types + .into_iter() + .map(|pt| pt.to_ll_type(context)) + .collect(); + + Self { + ret_type: ret_ty, + param_types: param_tys, + var_args, + context_ptr, } } +} - // Return a dictionary with results and metadata - let result_dict = PyDict::new(py); - result_dict.set_item("results", result_list)?; - result_dict.set_item("shots", shots)?; - result_dict.set_item("execution_successful", true)?; - - Ok(result_dict.into()) -} - -/// Simplified LLVM execution -fn execute_llvm_safe( - llvm_path: &std::path::Path, - shots: usize, - seed: Option, - noise_probability: Option, - workers: Option, - max_qubits: Option, -) -> Result { - use crate::llvm_execution_guard::LlvmExecutionGuard; - - // Create execution guard to prevent cleanup issues - let _guard = LlvmExecutionGuard::new() - .map_err(|e| pecos_core::errors::PecosError::Input(e.to_string()))?; - - // Simple reset - no complex context system - unsafe { - pecos_qis_runtime::runtime::llvm_runtime_reset(); - } - - // Set up QIS control engine for LLVM/QIR files with Selene simple runtime (default) - let selene_runtime = selene_simple_runtime() - .map_err(|e| pecos_core::errors::PecosError::Resource(format!( - "Selene simple runtime not available: {}\n\ - \n\ - The default runtime for QIS programs is Selene simple.\n\ - Please ensure Selene is built:\n\ - cd ../selene && cargo build --release\n\ - \n\ - Or explicitly specify a different runtime in your code.", e - )))?; - - log::info!("Using Selene simple runtime for QIS program"); - let classical_engine = setup_qis_engine_with_runtime(llvm_path, selene_runtime)?; - - // Create noise model - let noise_model: Box = if let Some(prob) = noise_probability { - let mut model = DepolarizingNoiseModel::new_uniform(prob); - if let Some(s) = seed { - model.set_seed(s)?; - } - Box::new(model) - } else { - Box::new(pecos_engines::noise::PassThroughNoiseModel::new()) - }; - - // Execute simulation with MonteCarloEngine directly to support max_qubits - let workers = workers.unwrap_or(1); - - // Use MonteCarloEngine directly to have control over max_qubits - let results = if let Some(max_q) = max_qubits { - // When max_qubits is specified, use the new method - pecos_engines::monte_carlo::MonteCarloEngine::run_with_noise_model_and_max_qubits( - classical_engine, - noise_model, - max_q, - shots, - workers, - seed, - )? - } else { - // When max_qubits is not specified, use a reasonable default - // For programs with loops, we need extra headroom - let static_qubits = classical_engine.num_qubits(); - // Use 3x the static count or 10, whichever is larger, to handle dynamic allocation - let default_max_qubits = std::cmp::max(static_qubits * 3, 10); - - pecos_engines::monte_carlo::MonteCarloEngine::run_with_noise_model_and_max_qubits( - classical_engine, - noise_model, - default_max_qubits, - shots, - workers, - seed, - )? - }; - - // Force another reset after execution - unsafe { - pecos_qis_runtime::runtime::llvm_runtime_reset(); - } - - // Note: HUGR bindings module is currently disabled due to symbol conflicts - - // Clean up runtime registry - pecos_qis_runtime::runtime::registry::cleanup_all_runtimes(); - - // Give the runtime a moment to clean up thread-local storage - // This prevents segfaults when running in pytest environments - std::thread::sleep(std::time::Duration::from_millis(1)); - - Ok(results) -} - -/// Direct function to execute LLVM file -#[pyfunction] -#[pyo3(name = "execute_llvm")] -#[pyo3(signature = (llvm_path, shots, seed, noise_probability, workers, max_qubits=None))] -pub fn py_execute_llvm( - py: Python<'_>, - llvm_path: &str, - shots: usize, - seed: Option, - noise_probability: Option, - workers: Option, - max_qubits: Option, -) -> PyResult> { - // Enhanced error handling removed - not needed for simplification - - // Validate LLVM file path - let path = std::path::PathBuf::from(llvm_path); - if !path.exists() { - return Err(PyRuntimeError::new_err(format!( - "LLVM file not found: {llvm_path}" - ))); - } - - // Check for pytest environment and warn about potential segfaults - if std::env::var("PYTEST_CURRENT_TEST").is_ok() { - // We're running in pytest - execution works but may segfault during cleanup - log::warn!( - "Warning: LLVM execution in pytest may segfault during cleanup (output will be produced first)" - ); +/// Python wrapper for LLVM value +#[pyclass(name = "Value")] +#[derive(Copy, Clone)] +pub struct PyLLValue { + value: LLValue<'static>, + #[allow(dead_code)] + context_ptr: *mut Context, +} - // Force clear any lingering runtime state from previous tests - unsafe { - pecos_qis_runtime::runtime::llvm_runtime_reset(); +unsafe impl Send for PyLLValue {} +unsafe impl Sync for PyLLValue {} + +#[pymethods] +impl PyLLValue { + /// Convert integer value to pointer (inttoptr instruction) + /// + /// Mirrors llvmlite's `value.inttoptr(ptr_type)` + fn inttoptr(&self, ptr_type: PyPointerType) -> PyResult { + // Verify source is an integer + let LLValue::Int(int_val) = &self.value else { + return Err(PyRuntimeError::new_err("inttoptr requires integer value")); + }; + + // Get the pointer type from PyPointerType + let LLType::Pointer(target_ptr_type) = ptr_type.ll_type else { + return Err(PyRuntimeError::new_err("Target must be a pointer type")); + }; + + // Create the inttoptr constant + let ptr_val = int_val.const_to_pointer(target_ptr_type); + + Ok(Self { + value: LLValue::Pointer(ptr_val), + context_ptr: self.context_ptr, + }) + } +} + +// ============================================================================ +// GlobalVariable - Global variable support +// ============================================================================ + +/// Python wrapper for LLVM global variables +/// +/// Provides global variable management (compatible with llvmlite API) +#[pyclass(name = "GlobalVariable")] +pub struct PyGlobalVariable { + global: inkwell::values::GlobalValue<'static>, + context_ptr: *mut Context, +} + +unsafe impl Send for PyGlobalVariable {} +unsafe impl Sync for PyGlobalVariable {} + +#[pymethods] +impl PyGlobalVariable { + /// Create a new global variable + /// + /// Mirrors ir.GlobalVariable(module, type, name) + #[new] + fn new(module: &mut PyLLVMModule, ty: PyAnyType, name: &str) -> Self { + let module_ref = unsafe { &mut *module.module_ptr }; + let context = unsafe { &*module.context_ptr }; + let ll_type = ty.to_ll_type(context); + let global = module_ref.add_global(name, ll_type, None); + Self { + global, + context_ptr: module.context_ptr, } - // Clear any interactive callbacks - pecos_qis_runtime::runtime::core_runtime::clear_interactive_callback(); } - // LLVM execution context initialization removed (was stub) + /// Set the initializer for this global variable + #[setter] + fn initializer(&mut self, value: &PyLLValue) { + match &value.value { + LLValue::Int(v) => self.global.set_initializer(v), + LLValue::Float(v) => self.global.set_initializer(v), + LLValue::Pointer(v) => self.global.set_initializer(v), + LLValue::Array(v) => self.global.set_initializer(v), + } + } + + /// Set whether this global is a constant + #[setter] + fn global_constant(&mut self, is_const: bool) { + self.global.set_constant(is_const); + } + + /// Set the linkage type + #[setter] + fn linkage(&mut self, linkage: &str) { + use inkwell::module::Linkage; + let linkage_type = match linkage { + "private" => Linkage::Private, + "internal" => Linkage::Internal, + "weak" => Linkage::WeakAny, + "common" => Linkage::Common, + _ => Linkage::External, // default (including "external") + }; + self.global.set_linkage(linkage_type); + } + + /// Get element pointer (GEP) from this global + /// + /// Mirrors global.gep(indices) in llvmlite + fn gep(&self, indices: Vec) -> PyResult { + // Convert PyLLValue indices to inkwell IntValues + let int_indices: Result, _> = indices + .into_iter() + .map(|v| match v.value { + LLValue::Int(i) => Ok(i), + _ => Err(PyRuntimeError::new_err("GEP indices must be integers")), + }) + .collect(); + let int_indices = int_indices?; - // Execute LLVM directly without error context wrapper - let results = execute_llvm_safe(&path, shots, seed, noise_probability, workers, max_qubits) - .map_err(|e| PyRuntimeError::new_err(format!("LLVM execution failed: {e}")))?; + // Use const_gep for global variables + let gep_val = unsafe { self.global.as_pointer_value().const_gep(&int_indices) }; - // Convert results to Python format - convert_results_to_python(py, results, shots) + Ok(PyLLValue { + value: LLValue::Pointer(gep_val), + context_ptr: self.context_ptr, + }) + } + + /// Get the pointer value of this global + fn as_pointer_value(&self) -> PyLLValue { + PyLLValue { + value: LLValue::Pointer(self.global.as_pointer_value()), + context_ptr: self.context_ptr, + } + } } -/// Validate LLVM format and get detailed diagnostics +// ============================================================================ +// Constant - Constant value creation +// ============================================================================ + +/// Create constant value (mirrors llvmlite's ir.Constant(type, value)) +/// +/// This is the main entry point for creating constants, matching llvmlite's API: +/// ```python +/// ir.Constant(ir.IntType(32), 5) +/// ir.Constant(ir.ArrayType(ir.IntType(8), 10), b"hello") +/// ``` #[pyfunction] -#[pyo3(name = "validate_llvm_format_detailed")] -pub fn py_validate_llvm_format(llvm_path: &str) -> PyResult> { - use pyo3::types::PyDict; +#[allow(non_snake_case)] +fn Constant(_py: Python, ty: PyAnyType, value: &Bound<'_, PyAny>) -> PyResult { + // Check type isn't void (llvmlite doesn't allow void constants) + if matches!(ty, PyAnyType::Void(_)) { + return Err(PyRuntimeError::new_err("Cannot create void constant")); + } + + // Handle different type/value combinations + match &ty { + PyAnyType::Int(int_ty) => { + // Integer constant - extract value as i64 + // Also handle Python bool (True/False) which are int subclasses + let int_value = if let Ok(val) = value.extract::() { + // Python bool: True -> 1, False -> 0 + i64::from(val) + } else if let Ok(val) = value.extract::() { + val + } else if let Ok(val) = value.extract::() { + // Allow wrapping cast - this is intentional for large unsigned values + // The value is later handled correctly via unsigned_abs() + #[allow(clippy::cast_possible_wrap)] + { + val as i64 + } + } else { + return Err(PyRuntimeError::new_err( + "Constant value must be integer or boolean for IntType", + )); + }; - let path = std::path::PathBuf::from(llvm_path); - if !path.exists() { - return Err(PyRuntimeError::new_err(format!( - "LLVM file not found: {llvm_path}" - ))); + // Create integer constant + let LLType::Int(int_type) = int_ty.ll_type else { + return Err(PyRuntimeError::new_err("Expected integer type")); + }; + let signed = int_value < 0; + let const_val = LLConstant::int(int_type, int_value.unsigned_abs(), signed); + Ok(PyLLValue { + value: const_val, + context_ptr: int_ty.context_ptr, + }) + } + + PyAnyType::Array(array_ty) => { + // Array constant - value should be bytes (most common case for gen_qir.py) + if let Ok(bytes) = value.extract::>() { + // Byte array + let context = unsafe { &*array_ty.context_ptr }; + let const_val = LLConstant::array_from_bytes(context, &bytes); + Ok(PyLLValue { + value: const_val, + context_ptr: array_ty.context_ptr, + }) + } else { + Err(PyRuntimeError::new_err( + "Constant value must be bytes for ArrayType (other array types not yet implemented)", + )) + } + } + + PyAnyType::Double(double_ty) => { + // Float/double constant + let float_value = value.extract::().map_err(|_| { + PyRuntimeError::new_err("Constant value must be float for DoubleType") + })?; + + let ll_type = double_ty.ll_type; + let const_val = match ll_type { + LLType::Float(f) => { + // Use inkwell's const_float method directly + LLValue::Float(f.const_float(float_value)) + } + _ => return Err(PyRuntimeError::new_err("Expected float type")), + }; + + Ok(PyLLValue { + value: const_val, + context_ptr: double_ty.context_ptr, + }) + } + + _ => Err(PyRuntimeError::new_err(format!( + "Constant creation not yet implemented for type: {:?}", + std::any::type_name_of_val(&ty) + ))), } +} - let llvm_content = fs::read_to_string(&path) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to read LLVM file: {e}")))?; +// ============================================================================ +// Type creation functions (module level) +// ============================================================================ - Python::attach(|py| { - let result = PyDict::new(py); +// Global context for standalone type creation (mimics llvmlite behavior) +struct GlobalContextPtr(*mut Context); +unsafe impl Send for GlobalContextPtr {} +unsafe impl Sync for GlobalContextPtr {} - // Basic format validation - if llvm_content.contains("@__quantum__") { - result.set_item("format_valid", true)?; - result.set_item("format_errors", Vec::::new())?; - } else { - result.set_item("format_valid", false)?; - result.set_item( - "format_errors", - vec!["No quantum operations found".to_string()], - )?; - } - - // Runtime issue detection (simplified - no actual validation needed) - result.set_item("runtime_warnings", Vec::::new())?; - - // LLVM statistics - let stats = PyDict::new(py); - stats.set_item("total_lines", llvm_content.lines().count())?; - stats.set_item( - "quantum_operations", - llvm_content.matches("__quantum__qis__").count(), - )?; - stats.set_item("has_entry_point", llvm_content.contains("EntryPoint"))?; - stats.set_item("has_opaque_types", llvm_content.contains("type opaque"))?; - stats.set_item( - "uses_integer_qubits", - llvm_content.contains("__quantum__qis__h__body(i64"), - )?; - stats.set_item( - "uses_pointer_qubits", - llvm_content.contains("__quantum__qis__h__body(i8*") - || llvm_content.contains("__quantum__qis__h__body(%Qubit*"), - )?; - result.set_item("statistics", stats)?; - - Ok(result.into()) - }) -} - -/// Get LLVM execution diagnostic report -/// -/// Note: This function is deprecated and always returns an empty string. -/// It is kept for backward compatibility only. +static GLOBAL_CONTEXT: OnceLock = OnceLock::new(); + +fn get_global_context() -> *mut Context { + GLOBAL_CONTEXT + .get_or_init(|| { + let context = Box::new(Context::create()); + GlobalContextPtr(Box::into_raw(context)) + }) + .0 +} + +/// Create integer type (mirrors ir.IntType(bits)) +#[pyfunction] +#[allow(non_snake_case)] +fn IntType(_py: Python, bits: u32) -> PyIntType { + let context_ptr = get_global_context(); + let context = unsafe { &*context_ptr }; + let ll_type = LLType::int(context, bits); + PyIntType { + ll_type, + context_ptr, + } +} + +/// Create void type (mirrors `ir.VoidType()`) #[pyfunction] -#[pyo3(name = "get_llvm_diagnostic_report")] -pub fn py_get_llvm_diagnostic_report() -> String { - String::new() +#[allow(non_snake_case)] +fn VoidType(_py: Python) -> PyVoidType { + let context_ptr = get_global_context(); + PyVoidType { context_ptr } } -/// Reset LLVM runtime state (simplified) +/// Create double type (mirrors `ir.DoubleType()`) #[pyfunction] -#[pyo3(name = "reset_llvm_runtime")] -pub fn py_reset_llvm_runtime() { - use std::thread; - use std::time::Duration; +#[allow(non_snake_case)] +fn DoubleType(_py: Python) -> PyDoubleType { + let context_ptr = get_global_context(); + let context = unsafe { &*context_ptr }; + let ll_type = LLType::double(context); + PyDoubleType { + ll_type, + context_ptr, + } +} + +/// Create a function (mirrors ir.Function(module, `func_type`, name=...)) +#[pyfunction] +#[pyo3(signature = (module, func_type, name))] +#[allow(non_snake_case)] +fn Function(module: &mut PyLLVMModule, func_type: &PyFunctionType, name: &str) -> PyFunction { + // This is just an alias for module.add_function() + module.add_function(name, func_type) +} + +// ============================================================================ +// Register with Python +// ============================================================================ + +/// Register LLVM IR module with Python (compatible with llvmlite API) +pub fn register_llvm_module(parent: &Bound<'_, pyo3::types::PyModule>) -> PyResult<()> { + // Create an 'ir' submodule compatible with Python's llvmlite.ir + let ir_module = pyo3::types::PyModule::new(parent.py(), "ir")?; + + // Register main module and context classes + ir_module.add_class::()?; + ir_module.add_class::()?; + + // Register type classes + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + + // Register function and builder classes + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + + // Register context manager classes for control flow + ir_module.add_class::()?; + ir_module.add_class::()?; + ir_module.add_class::()?; + + // Register type and value creation functions + ir_module.add_function(wrap_pyfunction!(IntType, &ir_module)?)?; + ir_module.add_function(wrap_pyfunction!(VoidType, &ir_module)?)?; + ir_module.add_function(wrap_pyfunction!(DoubleType, &ir_module)?)?; + ir_module.add_function(wrap_pyfunction!(Function, &ir_module)?)?; + ir_module.add_function(wrap_pyfunction!(Constant, &ir_module)?)?; + + parent.add_submodule(&ir_module)?; + Ok(()) +} + +// ============================================================================ +// llvmlite.binding module - for bitcode generation +// ============================================================================ - // Note: HUGR bindings module is currently disabled due to symbol conflicts +/// `ValueRef` for type hints (matches llvmlite.binding.ValueRef) +#[pyclass(name = "ValueRef")] +pub struct PyValueRef; - // Simple reset - no aggressive cleanup - unsafe { - pecos_qis_runtime::runtime::llvm_runtime_reset(); +#[pymethods] +impl PyValueRef { + #[new] + fn new() -> Self { + Self } +} + +/// Module reference returned by `parse_assembly` +#[pyclass(name = "ModuleRef")] +pub struct PyModuleRef { + llvm_ir: String, +} + +#[pymethods] +impl PyModuleRef { + /// Convert LLVM IR text to bitcode + fn as_bitcode(&self) -> PyResult> { + use std::io::Write; + + // Create a temporary context and module to parse the IR + let context = inkwell::context::Context::create(); + + // Write IR to a temporary file (inkwell parses files more reliably) + let mut temp_file = tempfile::NamedTempFile::new().map_err(|e| { + pyo3::exceptions::PyIOError::new_err(format!("Failed to create temp file: {e}")) + })?; + + temp_file.write_all(self.llvm_ir.as_bytes()).map_err(|e| { + pyo3::exceptions::PyIOError::new_err(format!("Failed to write temp file: {e}")) + })?; - // Clean up all runtime registry states - pecos_qis_runtime::runtime::registry::cleanup_all_runtimes(); + temp_file.flush().map_err(|e| { + pyo3::exceptions::PyIOError::new_err(format!("Failed to flush temp file: {e}")) + })?; - // Give the runtime a moment to clean up - // This helps prevent segfaults in pytest environments - thread::sleep(Duration::from_millis(10)); + // Get the path before the file is closed + let temp_path = temp_file.path().to_path_buf(); + + // Parse the LLVM IR from file + let module = inkwell::module::Module::parse_bitcode_from_path(&temp_path, &context) + .or_else(|_| { + // If bitcode parsing fails, try IR parsing + let memory_buffer = inkwell::memory_buffer::MemoryBuffer::create_from_file( + &temp_path, + ) + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to read temp file: {e}" + )) + })?; + + context.create_module_from_ir(memory_buffer).map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to parse LLVM IR: {e}" + )) + }) + })?; + + // Write module to bitcode + let bitcode_buffer = module.write_bitcode_to_memory(); + Ok(bitcode_buffer.as_slice().to_vec()) + } +} + +/// Parse LLVM assembly text into a module +#[pyfunction] +fn parse_assembly(llvm_ir: &str) -> PyModuleRef { + PyModuleRef { + llvm_ir: llvm_ir.to_string(), + } +} + +/// Shutdown LLVM (no-op for compatibility) +#[pyfunction] +fn shutdown() { + // In llvmlite, this shuts down LLVM global state + // For our Rust implementation, we don't need to do anything + // as Rust's RAII handles cleanup automatically } -/// Register LLVM Python module -pub fn register_llvm_module(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_function(wrap_pyfunction!(py_execute_llvm, m)?)?; - m.add_function(wrap_pyfunction!(py_validate_llvm_format, m)?)?; - m.add_function(wrap_pyfunction!(py_get_llvm_diagnostic_report, m)?)?; - m.add_function(wrap_pyfunction!(py_reset_llvm_runtime, m)?)?; +/// Register the binding module (mimics llvmlite.binding) +pub fn register_binding_module(parent: &Bound<'_, pyo3::types::PyModule>) -> PyResult<()> { + let binding_module = pyo3::types::PyModule::new(parent.py(), "binding")?; - // Add cleanup handlers to prevent abort on exit - m.add_function(wrap_pyfunction!( - crate::llvm_execution_guard::_mark_llvm_shutting_down, - m - )?)?; - m.add_function(wrap_pyfunction!( - crate::llvm_execution_guard::_wait_for_llvm_completion, - m - )?)?; + // Register classes + binding_module.add_class::()?; + binding_module.add_class::()?; - // Register cleanup handler on module load - crate::llvm_execution_guard::register_cleanup_handler(); + // Register functions + binding_module.add_function(wrap_pyfunction!(parse_assembly, &binding_module)?)?; + binding_module.add_function(wrap_pyfunction!(shutdown, &binding_module)?)?; + parent.add_submodule(&binding_module)?; Ok(()) } diff --git a/python/pecos-rslib/rust/src/qir_bindings.rs b/python/pecos-rslib/rust/src/qir_bindings.rs new file mode 100644 index 000000000..bf7a3eb9b --- /dev/null +++ b/python/pecos-rslib/rust/src/qir_bindings.rs @@ -0,0 +1,161 @@ +// Copyright 2024 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Python bindings for QIR generation using the Rust pecos-llvm crate + +use pecos::prelude::QirBuilder; +use pyo3::prelude::*; + +/// Python wrapper for QirBuilder +/// +/// This class provides QIR (Quantum Intermediate Representation) generation +/// functionality from Python, replacing the llvmlite dependency. +/// +/// # Example (from Python): +/// ```python +/// from pecos_rslib import QirBuilder +/// +/// builder = QirBuilder("0.1.1") +/// builder.create_qreg("q", 2) +/// builder.create_creg("c", 2, True) +/// builder.apply_gate("h", [0], []) +/// builder.apply_gate("cx", [0, 1], []) +/// builder.measure_to_bit(0, "c", 0) +/// builder.measure_to_bit(1, "c", 1) +/// ir = builder.get_output() +/// print(ir) +/// ``` +#[pyclass(name = "QirBuilder")] +pub struct PyQirBuilder { + // We use Box::leak to create a 'static reference to the context + // This is a controlled memory leak that's acceptable for these short-lived builders + builder: QirBuilder<'static>, +} + +// SAFETY: PyQirBuilder is safe to Send/Sync because: +// 1. Python's GIL ensures single-threaded access to Python objects +// 2. The LLVM context is only accessed from the Python thread that created it +// 3. We never share the builder across threads - it's owned by a Python object +unsafe impl Send for PyQirBuilder {} +unsafe impl Sync for PyQirBuilder {} + +#[pymethods] +impl PyQirBuilder { + /// Create a new QIR builder + /// + /// Args: + /// pecos_version: Version string to embed in generated IR + /// + /// Returns: + /// A new QirBuilder instance + #[new] + fn new(pecos_version: &str) -> PyResult { + let builder = QirBuilder::new_with_leaked_context(pecos_version) + .map_err(|e| PyErr::new::(e.to_string()))?; + + Ok(Self { builder }) + } + + /// Create a quantum register + /// + /// Args: + /// name: Name of the quantum register + /// size: Number of qubits in the register + fn create_qreg(&mut self, name: &str, size: usize) -> PyResult<()> { + self.builder + .create_qreg(name, size) + .map_err(|e| PyErr::new::(e.to_string())) + } + + /// Create a classical register + /// + /// Args: + /// name: Name of the classical register + /// size: Number of bits in the register + /// is_result: Whether this register contains measurement results + fn create_creg(&mut self, name: &str, size: usize, is_result: bool) -> PyResult<()> { + self.builder + .create_creg(name, size, is_result) + .map_err(|e| PyErr::new::(e.to_string())) + } + + /// Apply a quantum gate to qubits + /// + /// Args: + /// gate_name: Name of the gate (e.g., "h", "cx", "rz") + /// qubits: List of qubit indices to apply the gate to + /// params: List of gate parameters (e.g., rotation angles) + /// + /// # Example: + /// ```python + /// builder.apply_gate("h", [0], []) # Hadamard on qubit 0 + /// builder.apply_gate("cx", [0, 1], []) # CNOT from qubit 0 to 1 + /// builder.apply_gate("rz", [0], [1.57]) # RZ(π/2) on qubit 0 + /// ``` + fn apply_gate(&mut self, gate_name: &str, qubits: Vec, params: Vec) -> PyResult<()> { + self.builder + .apply_gate(gate_name, &qubits, ¶ms) + .map_err(|e| PyErr::new::(e.to_string())) + } + + /// Measure a qubit to a classical bit + /// + /// Args: + /// qubit_idx: Index of the qubit to measure + /// creg_name: Name of the classical register to store the result + /// bit_idx: Index of the bit in the classical register + fn measure_to_bit(&mut self, qubit_idx: usize, creg_name: &str, bit_idx: usize) -> PyResult<()> { + self.builder + .measure_to_bit(qubit_idx, creg_name, bit_idx) + .map_err(|e| PyErr::new::(e.to_string())) + } + + /// Get the generated LLVM IR as a string + /// + /// Returns: + /// The complete QIR LLVM IR as a string + fn get_output(&self) -> String { + self.builder.get_output() + } + + /// Get the generated LLVM bitcode as bytes + /// + /// Returns: + /// The LLVM bitcode as bytes + fn get_bitcode(&self) -> Vec { + self.builder.get_bitcode() + } + + /// Get the number of qubits allocated + #[getter] + fn qubit_count(&self) -> usize { + self.builder.qubit_count() + } + + /// Get the number of measurements performed + #[getter] + fn measure_count(&self) -> usize { + self.builder.measure_count() + } + + fn __repr__(&self) -> String { + "QirBuilder()".to_string() + } +} + +/// Register QIR functions and classes with the Python module +pub fn register_qir_module(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/python/pecos-rslib/rust/src/sim.rs b/python/pecos-rslib/rust/src/sim.rs index 181cdecad..d1ec9da3b 100644 --- a/python/pecos-rslib/rust/src/sim.rs +++ b/python/pecos-rslib/rust/src/sim.rs @@ -60,8 +60,7 @@ fn is_guppy_function(py: Python, obj: &Py) -> PyResult { #[allow(clippy::needless_pass_by_value)] // Py must be passed by value for PyO3 #[allow(clippy::too_many_lines)] // Complex function handling multiple program types pub fn sim(py: Python, program: Py) -> PyResult { - eprintln!("[SIM.RS] ========== sim() function called =========="); - log::debug!("Rust sim() function called"); + log::debug!("sim() function called"); // Check if it's a Guppy function - if so, it needs to be compiled to HUGR on Python side if is_guppy_function(py, &program)? { @@ -89,12 +88,10 @@ pub fn sim(py: Python, program: Py) -> PyResult { }) } else if let Ok(qis_prog) = program.extract::(py) { // Use the QIS control engine with Selene simple runtime (default) - eprintln!("[SIM.RS] Extracted QisProgram successfully"); - log::error!("[SIM.RS] LOG: Extracted QisProgram successfully"); + log::debug!("Extracted QisProgram successfully"); // Get Selene simple runtime - eprintln!("[SIM.RS] About to call selene_simple_runtime()"); - log::error!("[SIM.RS] LOG: About to call selene_simple_runtime()"); + log::debug!("Getting Selene simple runtime..."); let selene_runtime = selene_simple_runtime().map_err(|e| { PyErr::new::(format!( "Selene simple runtime not available: {e}\n\ @@ -105,28 +102,23 @@ pub fn sim(py: Python, program: Py) -> PyResult { )) })?; - eprintln!("[SIM.RS] Got selene_runtime, about to create Helios interface builder"); - log::info!("[SIM.RS] Creating Helios interface builder"); + log::debug!("Creating QIS engine with Helios interface..."); let helios_builder = helios_interface_builder(); - eprintln!("[SIM.RS] Created helios_builder, about to create QIS engine"); - log::info!("[SIM.RS] Creating QIS engine builder"); let builder = pecos::qis_engine(); - eprintln!("[SIM.RS] Created qis_engine, about to add runtime"); let builder = builder.runtime(selene_runtime); - eprintln!("[SIM.RS] Added runtime, about to add interface"); let builder = builder.interface(helios_builder); - eprintln!("[SIM.RS] Added interface, about to call try_program()"); - log::info!("[SIM.RS] About to call try_program()"); - eprintln!("[SIM.RS] Calling try_program() NOW..."); - let engine_builder = builder.try_program(qis_prog.inner.clone()) - .map_err(|e: PecosError| { - eprintln!("[SIM.RS] try_program() FAILED: {e}"); - PyErr::new::(format!( - "[FROM SIM.RS] Failed to load QIS program with Selene runtime and Helios interface: {e}" - )) - })?; - eprintln!("[SIM.RS] try_program() completed successfully"); - log::info!("[SIM.RS] try_program() completed successfully"); + + log::debug!("Loading QIS program into engine..."); + let engine_builder = + builder + .try_program(qis_prog.inner.clone()) + .map_err(|e: PecosError| { + log::error!("Failed to load QIS program: {e}"); + PyErr::new::(format!( + "Failed to load QIS program with Selene runtime and Helios interface: {e}" + )) + })?; + log::info!("QIS program loaded successfully"); Ok(PySimBuilder { inner: SimBuilderInner::QisControl(PyQisControlSimBuilder { engine_builder: Arc::new(Mutex::new(Some(engine_builder))), @@ -139,32 +131,31 @@ pub fn sim(py: Python, program: Py) -> PyResult { }) } else if let Ok(hugr_prog) = program.extract::(py) { // Compile HUGR to LLVM first - eprintln!("[SIM.RS] ========== HUGR program detected =========="); - eprintln!("[SIM.RS] HUGR bytes length: {}", hugr_prog.inner.hugr.len()); - log::debug!("HUGR program detected, compiling to LLVM"); + log::debug!( + "HUGR program detected (size: {} bytes), compiling to LLVM...", + hugr_prog.inner.hugr.len() + ); // Compile HUGR to LLVM IR - eprintln!("[SIM.RS] About to call compile_hugr_bytes_to_string()..."); let llvm_ir = compile_hugr_bytes_to_string(&hugr_prog.inner.hugr).map_err(|e| { - eprintln!("[SIM.RS] HUGR compilation FAILED: {e}"); + log::error!("HUGR compilation failed: {e}"); PyErr::new::(format!( "HUGR compilation failed: {e}" )) })?; - eprintln!( - "[SIM.RS] HUGR compilation succeeded, LLVM IR length: {}", + log::info!( + "HUGR compilation succeeded (LLVM IR size: {} bytes)", llvm_ir.len() ); // Create QIS program from the compiled LLVM IR - eprintln!("[SIM.RS] Creating QisProgram from LLVM IR..."); + log::debug!("Creating QIS program from compiled LLVM IR..."); let qis_prog = QisProgram::from_string(llvm_ir); - eprintln!("[SIM.RS] QisProgram created successfully"); // Get Selene simple runtime - eprintln!("[SIM.RS] Getting Selene simple runtime..."); + log::debug!("Getting Selene simple runtime..."); let selene_runtime = selene_simple_runtime().map_err(|e| { - eprintln!("[SIM.RS] Selene simple runtime FAILED: {e}"); + log::error!("Selene simple runtime not available: {e}"); PyErr::new::(format!( "Selene simple runtime not available: {e}\n\ \n\ @@ -173,24 +164,20 @@ pub fn sim(py: Python, program: Py) -> PyResult { cd ../selene && cargo build --release" )) })?; - eprintln!("[SIM.RS] Selene simple runtime created successfully"); // Use QIS control engine with Helios interface - eprintln!("[SIM.RS] Creating QIS engine builder..."); - eprintln!("[SIM.RS] Adding runtime to engine..."); - eprintln!("[SIM.RS] Adding Helios interface to engine..."); - eprintln!("[SIM.RS] About to call try_program() for HUGR..."); + log::debug!("Creating QIS engine with Helios interface for HUGR program..."); let engine_builder = pecos::qis_engine() .runtime(selene_runtime) .interface(helios_interface_builder()) .try_program(qis_prog) .map_err(|e| { - eprintln!("[SIM.RS] try_program() for HUGR FAILED: {e}"); + log::error!("Failed to load compiled HUGR program: {e}"); PyErr::new::(format!( "Failed to load compiled HUGR program: {e}" )) })?; - eprintln!("[SIM.RS] try_program() for HUGR completed successfully"); + log::info!("HUGR program loaded successfully"); Ok(PySimBuilder { inner: SimBuilderInner::QisControl(PyQisControlSimBuilder { diff --git a/python/pecos-rslib/src/pecos_rslib/__init__.py b/python/pecos-rslib/src/pecos_rslib/__init__.py index 1ecce2399..e8ae6b59c 100644 --- a/python/pecos-rslib/src/pecos_rslib/__init__.py +++ b/python/pecos-rslib/src/pecos_rslib/__init__.py @@ -32,6 +32,8 @@ ShotVec, SparseStabEngineRs, StateVecEngineRs, + binding, # llvmlite-compatible binding module for bitcode + ir, # llvmlite-compatible LLVM IR module ) from pecos_rslib.cppsparse_sim import CppSparseSimRs from pecos_rslib.rscoin_toss import CoinToss @@ -433,6 +435,9 @@ def get_compilation_backends() -> dict[str, Any]: "ByteMessageBuilder", "StateVecEngineRs", "SparseStabEngineRs", + # llvmlite-compatible modules + "ir", + "binding", # QuEST simulators "QuestStateVec", "QuestDensityMatrix", diff --git a/python/pecos-rslib/src/pecos_rslib/llvm.py b/python/pecos-rslib/src/pecos_rslib/llvm.py new file mode 100644 index 000000000..060680682 --- /dev/null +++ b/python/pecos-rslib/src/pecos_rslib/llvm.py @@ -0,0 +1,20 @@ +""" +LLVM IR generation API implemented in Rust via PyO3 and inkwell. + +This module provides a drop-in replacement for llvmlite, enabling: +- Python 3.13+ support (llvmlite doesn't support it) +- Reduced Python dependencies +- High-performance LLVM IR generation using Rust + +Usage: + from pecos_rslib.llvm import ir, binding + +This is compatible with: + from llvmlite import ir, binding + +But implemented entirely in Rust for better performance and compatibility. +""" + +from pecos_rslib._pecos_rslib import binding, ir + +__all__ = ["ir", "binding"] diff --git a/python/pecos-rslib/tests/test_llvm_binding_module.py b/python/pecos-rslib/tests/test_llvm_binding_module.py new file mode 100644 index 000000000..fb9e5d883 --- /dev/null +++ b/python/pecos-rslib/tests/test_llvm_binding_module.py @@ -0,0 +1,141 @@ +"""Test llvmlite-compatible binding module API.""" + +import pytest + + +@pytest.fixture +def simple_llvm_ir(): + """Create simple LLVM IR for testing.""" + from pecos_rslib import ir + + module = ir.Module("test_binding") + ctx = module.context + void = ctx.void_type() + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test", func_type) + entry_block = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry_block) + builder.ret_void() + + return str(module) + + +def test_import_binding_module(): + """Test that the binding module can be imported.""" + from pecos_rslib import binding + + assert binding is not None + + +def test_binding_shutdown(): + """Test binding.shutdown() (should be no-op).""" + from pecos_rslib import binding + + # Should not raise any errors + binding.shutdown() + + +def test_binding_multiple_shutdowns(): + """Test that multiple shutdown calls are safe.""" + from pecos_rslib import binding + + # Multiple calls should be safe + binding.shutdown() + binding.shutdown() + binding.shutdown() + + +def test_parse_assembly(simple_llvm_ir): + """Test binding.parse_assembly().""" + from pecos_rslib import binding + + module_ref = binding.parse_assembly(simple_llvm_ir) + assert module_ref is not None + + +def test_convert_to_bitcode(simple_llvm_ir): + """Test converting LLVM IR to bitcode.""" + from pecos_rslib import binding + + module_ref = binding.parse_assembly(simple_llvm_ir) + bitcode = module_ref.as_bitcode() + + assert isinstance(bitcode, bytes) + assert len(bitcode) > 0 + # LLVM bitcode should start with 'BC' magic bytes + assert bitcode[:2] == b"BC" + + +def test_bitcode_format(simple_llvm_ir): + """Test that generated bitcode has correct format.""" + from pecos_rslib import binding + + module_ref = binding.parse_assembly(simple_llvm_ir) + bitcode = module_ref.as_bitcode() + + # Verify it's binary data (not text) + assert isinstance(bitcode, bytes) + + # Bitcode should be reasonably sized + assert len(bitcode) > 10 # At least some header bytes + + # First two bytes should be 'BC' (0x42 0x43) + assert bitcode[0] == 0x42 # 'B' + assert bitcode[1] == 0x43 # 'C' + + +def test_value_ref(): + """Test binding.ValueRef for type hints.""" + from pecos_rslib import binding + + value_ref = binding.ValueRef() + assert value_ref is not None + + +def test_ir_and_binding_integration(simple_llvm_ir): + """Test integration between ir and binding modules.""" + from pecos_rslib import binding + + # Parse IR + module_ref = binding.parse_assembly(simple_llvm_ir) + + # Convert to bitcode + bitcode = module_ref.as_bitcode() + + # Shutdown + binding.shutdown() + + # Verify bitcode is still valid + assert len(bitcode) > 0 + assert bitcode[:2] == b"BC" + + +def test_complex_ir_to_bitcode(): + """Test converting more complex IR to bitcode.""" + from pecos_rslib import binding, ir + + # Create a more complex module + module = ir.Module("complex_test") + ctx = module.context + i32 = ctx.int_type(32) + void = ctx.void_type() + + # Add function (using void to match ret_void) + func_type = ctx.function_type(void, [i32, i32], False) + add_func = module.add_function("add", func_type) + entry_block = add_func.append_basic_block("entry") + builder = ir.IRBuilder(entry_block) + + # Build some instructions + args = add_func.args + builder.add(args[0], args[1], "sum") + builder.ret_void() + + llvm_ir = str(module) + + # Convert to bitcode + module_ref = binding.parse_assembly(llvm_ir) + bitcode = module_ref.as_bitcode() + + assert len(bitcode) > 0 + assert bitcode[:2] == b"BC" diff --git a/python/pecos-rslib/tests/test_llvm_comprehensive.py b/python/pecos-rslib/tests/test_llvm_comprehensive.py new file mode 100644 index 000000000..61069d111 --- /dev/null +++ b/python/pecos-rslib/tests/test_llvm_comprehensive.py @@ -0,0 +1,313 @@ +"""Comprehensive tests for llvmlite compatibility covering all major features.""" + +import pytest + + +@pytest.fixture +def qir_module(): + """Create a QIR-like module for testing.""" + from pecos_rslib import ir + + module = ir.Module("qir_test") + ctx = module.context + return module, ctx + + +def test_all_basic_types(qir_module): + """Test creation of all basic types used in QIR.""" + _, ctx = qir_module + + i1 = ctx.int_type(1) # Boolean + i8 = ctx.int_type(8) # Byte + i32 = ctx.int_type(32) # Int + i64 = ctx.int_type(64) # Long + double = ctx.double_type() + void = ctx.void_type() + + assert i1 is not None + assert i8 is not None + assert i32 is not None + assert i64 is not None + assert double is not None + assert void is not None + + +def test_pointer_types(qir_module): + """Test creation of pointer types.""" + _, ctx = qir_module + + i8 = ctx.int_type(8) + qubit_ptr = i8.as_pointer() # Qubit* (opaque) + result_ptr = i8.as_pointer() # Result* (opaque) + + assert qubit_ptr is not None + assert result_ptr is not None + + +def test_array_types(qir_module): + """Test creation of array types.""" + _, ctx = qir_module + + i8 = ctx.int_type(8) + array_type = i8.as_array(10) + + assert array_type is not None + + +def test_function_creation(qir_module): + """Test creating various function types.""" + module, ctx = qir_module + + void = ctx.void_type() + i8_ptr = ctx.int_type(8).as_pointer() + + # Main function + main_type = ctx.function_type(void, [], False) + main_func = module.add_function("main", main_type) + + # Quantum gate function + gate_type = ctx.function_type(void, [i8_ptr], False) + h_gate = module.add_function("__quantum__qis__h__body", gate_type) + + # Measurement function + mz_type = ctx.function_type(i8_ptr, [i8_ptr, i8_ptr], False) + mz_func = module.add_function("__quantum__qis__mz__body", mz_type) + + assert main_func is not None + assert h_gate is not None + assert mz_func is not None + + +def test_global_variables(qir_module): + """Test creating global variables with initializers.""" + from pecos_rslib import ir + + module, ctx = qir_module + + i8 = ctx.int_type(8) + array_type = i8.as_array(10) + + # Create global variable + global_var = ir.GlobalVariable(module, array_type, "global_const") + + # Create initializer (using byte array - our implementation supports bytes for arrays) + const_array = ir.Constant(array_type, b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09") + global_var.initializer = const_array + global_var.global_constant = True + global_var.linkage = "private" + + assert global_var is not None + # Note: initializer is write-only, no getter implemented + + +def test_arithmetic_operations(qir_module): + """Test all arithmetic operations.""" + from pecos_rslib import ir + + module, ctx = qir_module + + i32 = ctx.int_type(32) + void = ctx.void_type() + + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test_arith", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + + a = ir.Constant(i32, 42) + b = ir.Constant(i32, 10) + + sum_val = builder.add(a, b, "sum") + diff_val = builder.sub(sum_val, b, "diff") + prod_val = builder.mul(diff_val, ir.Constant(i32, 2), "prod") + div_val = builder.udiv(prod_val, b, "div") + + builder.ret_void() + + assert sum_val is not None + assert diff_val is not None + assert prod_val is not None + assert div_val is not None + + +def test_bitwise_operations(qir_module): + """Test all bitwise operations.""" + from pecos_rslib import ir + + module, ctx = qir_module + + i64 = ctx.int_type(64) + void = ctx.void_type() + + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test_bitwise", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + + x = ir.Constant(i64, 0xFF) + y = ir.Constant(i64, 0x0F) + + # Use getattr for Python keywords + and_val = getattr(builder, "and")(x, y, "and") + or_val = getattr(builder, "or")(x, y, "or") + xor_val = builder.xor(x, y, "xor") + shl_val = builder.shl(x, ir.Constant(i64, 2), "shl") + lshr_val = builder.lshr(x, ir.Constant(i64, 2), "lshr") + not_val = builder.not_(x, "not") + + builder.ret_void() + + assert and_val is not None + assert or_val is not None + assert xor_val is not None + assert shl_val is not None + assert lshr_val is not None + assert not_val is not None + + +def test_comparison_operations(qir_module): + """Test comparison operations.""" + from pecos_rslib import ir + + module, ctx = qir_module + + i32 = ctx.int_type(32) + void = ctx.void_type() + + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test_cmp", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + + a = ir.Constant(i32, 42) + b = ir.Constant(i32, 10) + + cmp_eq = builder.icmp_signed("==", a, b, "cmp_eq") + cmp_ne = builder.icmp_signed("!=", a, b, "cmp_ne") + cmp_gt = builder.icmp_signed(">", a, b, "cmp_gt") + cmp_lt = builder.icmp_signed("<", a, b, "cmp_lt") + + builder.ret_void() + + assert cmp_eq is not None + assert cmp_ne is not None + assert cmp_gt is not None + assert cmp_lt is not None + + +def test_control_flow(qir_module): + """Test if_then and if_else control flow.""" + from pecos_rslib import ir + + module, ctx = qir_module + + i32 = ctx.int_type(32) + void = ctx.void_type() + + func_type = ctx.function_type(void, [i32], False) + test_func = module.add_function("test_cf", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + + arg = test_func.args[0] + zero = ir.Constant(i32, 0) + + # Test if_then + cond1 = builder.icmp_signed(">", arg, zero, "cond1") + with builder.if_then(cond1): + builder.comment("if_then block") + + # Test if_else + cond2 = builder.icmp_signed("==", arg, zero, "cond2") + with builder.if_else(cond2) as (then, otherwise): + with then: + builder.comment("then block") + with otherwise: + builder.comment("else block") + + builder.ret_void() + + llvm_ir = str(module) + # Verify control flow structure is created + assert "if.then" in llvm_ir + assert "if.else" in llvm_ir + assert "if.merge" in llvm_ir + assert "br i1" in llvm_ir + + +def test_gep_operations(qir_module): + """Test GEP (Get Element Pointer) operations.""" + from pecos_rslib import ir + + module, ctx = qir_module + + i8 = ctx.int_type(8) + i32 = ctx.int_type(32) + array_type = i8.as_array(10) + + # Create global variable + global_var = ir.GlobalVariable(module, array_type, "test_array") + + # Create function to test GEP + void = ctx.void_type() + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test_gep", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + + zero = ir.Constant(i32, 0) + gep_result = global_var.gep([zero, zero]) + + builder.ret_void() + + assert gep_result is not None + + +def test_comments(qir_module): + """Test adding comments to IR.""" + from pecos_rslib import ir + + module, ctx = qir_module + + void = ctx.void_type() + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test_comments", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + + builder.comment("This is a test comment") + builder.comment("Multiple comments") + builder.ret_void() + + llvm_ir = str(module) + assert "This is a test comment" in llvm_ir + assert "Multiple comments" in llvm_ir + + +def test_end_to_end_ir_to_bitcode(qir_module): + """Test complete workflow from IR creation to bitcode generation.""" + from pecos_rslib import binding, ir + + module, ctx = qir_module + + # Create a simple function + void = ctx.void_type() + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test", func_type) + entry = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry) + builder.ret_void() + + # Get LLVM IR + llvm_ir = str(module) + assert len(llvm_ir) > 0 + + # Convert to bitcode via binding module + module_ref = binding.parse_assembly(llvm_ir) + bitcode = module_ref.as_bitcode() + + assert len(bitcode) > 0 + assert bitcode[:2] == b"BC" # LLVM bitcode magic bytes + + # Test shutdown + binding.shutdown() diff --git a/python/pecos-rslib/tests/test_llvm_control_flow.py b/python/pecos-rslib/tests/test_llvm_control_flow.py new file mode 100644 index 000000000..80f9f981b --- /dev/null +++ b/python/pecos-rslib/tests/test_llvm_control_flow.py @@ -0,0 +1,139 @@ +"""Test llvmlite-compatible control flow features (if_then, if_else).""" + +import pytest + + +@pytest.fixture +def module_with_function(): + """Create a module with a test function.""" + from pecos_rslib import ir + + module = ir.Module("control_flow_test") + ctx = module.context + i32 = ctx.int_type(32) + void = ctx.void_type() + + func_type = ctx.function_type(void, [i32], False) + test_func = module.add_function("test_func", func_type) + entry_block = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry_block) + + return module, test_func, builder, i32 + + +def test_if_then_context_manager(module_with_function): + """Test if_then context manager.""" + from pecos_rslib import ir + + module, test_func, builder, i32 = module_with_function + + # Create a condition (arg[0] > 0) + arg = test_func.args[0] + zero = ir.Constant(i32, 0) + cond = builder.icmp_signed(">", arg, zero, "cond") + + # Use if_then context manager + with builder.if_then(cond): + builder.comment("Inside if_then block") + + builder.ret_void() + + # Verify the IR contains the expected control flow structure + llvm_ir = str(module) + assert "if.then" in llvm_ir # Then block created + assert "if.merge" in llvm_ir # Merge block created + assert "br i1" in llvm_ir # Branch instruction + + +def test_if_else_context_manager(module_with_function): + """Test if_else context manager.""" + from pecos_rslib import ir + + module, test_func, builder, i32 = module_with_function + + # Create a condition (arg[0] == 0) + arg = test_func.args[0] + zero = ir.Constant(i32, 0) + cond = builder.icmp_signed("==", arg, zero, "cond") + + # Use if_else context manager + with builder.if_else(cond) as (then, otherwise): + with then: + builder.comment("Inside then branch") + with otherwise: + builder.comment("Inside else branch") + + builder.ret_void() + + # Verify the IR contains both branches + llvm_ir = str(module) + assert "if.then" in llvm_ir # Then block created + assert "if.else" in llvm_ir # Else block created + assert "if.merge" in llvm_ir # Merge block created + assert "br i1" in llvm_ir # Branch instruction + + +def test_nested_if_then(module_with_function): + """Test nested if_then blocks.""" + from pecos_rslib import ir + + module, test_func, builder, i32 = module_with_function + + # Create conditions + arg = test_func.args[0] + zero = ir.Constant(i32, 0) + ten = ir.Constant(i32, 10) + + cond1 = builder.icmp_signed(">", arg, zero, "cond1") + cond2 = builder.icmp_signed("<", arg, ten, "cond2") + + # Use nested if_then + with builder.if_then(cond1): + builder.comment("Outer if_then") + with builder.if_then(cond2): + builder.comment("Inner if_then") + + builder.ret_void() + + # Verify the IR contains nested control flow structure + llvm_ir = str(module) + # Should have multiple if.then blocks for nested structure + assert llvm_ir.count("if.then") >= 2 or "if.then1" in llvm_ir + # Should have multiple merge blocks + assert llvm_ir.count("if.merge") >= 2 or "if.merge2" in llvm_ir + + +def test_control_flow_generates_valid_ir(): + """Test that control flow generates valid LLVM IR.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + ctx = module.context + i32 = ctx.int_type(32) + void = ctx.void_type() + + func_type = ctx.function_type(void, [i32], False) + test_func = module.add_function("test", func_type) + entry_block = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry_block) + + arg = test_func.args[0] + zero = ir.Constant(i32, 0) + cond = builder.icmp_signed(">", arg, zero, "cond") + + with builder.if_else(cond) as (then, otherwise): + with then: + # Do nothing, just test structure + pass + with otherwise: + # Do nothing, just test structure + pass + + builder.ret_void() + + # Get IR and verify it's non-empty and contains expected elements + llvm_ir = str(module) + assert len(llvm_ir) > 0 + assert "define void @test(i32" in llvm_ir + assert "br i1" in llvm_ir + assert "ret void" in llvm_ir diff --git a/python/pecos-rslib/tests/test_llvm_ir_module.py b/python/pecos-rslib/tests/test_llvm_ir_module.py new file mode 100644 index 000000000..282a5bb6c --- /dev/null +++ b/python/pecos-rslib/tests/test_llvm_ir_module.py @@ -0,0 +1,117 @@ +"""Test llvmlite-compatible ir module API.""" + + +def test_import_ir_module(): + """Test that the ir module can be imported.""" + from pecos_rslib import ir + + assert ir is not None + + +def test_create_module(): + """Test creating an LLVM module.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + assert module is not None + assert repr(module) == "" + + +def test_module_context_and_types(): + """Test accessing module context and creating types.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + ctx = module.context + + # Create various types + i32 = ctx.int_type(32) + i64 = ctx.int_type(64) + void = ctx.void_type() + double = ctx.double_type() + + assert i32 is not None + assert i64 is not None + assert void is not None + assert double is not None + + +def test_create_function(): + """Test creating a function.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + ctx = module.context + i32 = ctx.int_type(32) + + # Create function type + func_type = ctx.function_type(i32, [i32, i32], False) + assert func_type is not None + + # Add function to module + add_func = module.add_function("add", func_type) + assert add_func is not None + + +def test_create_basic_block_and_builder(): + """Test creating basic blocks and IRBuilder.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + ctx = module.context + i32 = ctx.int_type(32) + + func_type = ctx.function_type(i32, [i32, i32], False) + add_func = module.add_function("add", func_type) + + # Create basic block + entry_block = add_func.append_basic_block("entry") + assert entry_block is not None + + # Create IRBuilder + builder = ir.IRBuilder(entry_block) + assert builder is not None + + +def test_build_add_instruction(): + """Test building arithmetic instructions.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + ctx = module.context + i32 = ctx.int_type(32) + + func_type = ctx.function_type(i32, [i32, i32], False) + add_func = module.add_function("add", func_type) + entry_block = add_func.append_basic_block("entry") + builder = ir.IRBuilder(entry_block) + + # Get function arguments + args = add_func.args + assert len(args) == 2 + + # Build add instruction + result = builder.add(args[0], args[1], "sum") + assert result is not None + + +def test_generate_llvm_ir(): + """Test generating LLVM IR as a string.""" + from pecos_rslib import ir + + module = ir.Module("test_module") + ctx = module.context + void = ctx.void_type() + + func_type = ctx.function_type(void, [], False) + test_func = module.add_function("test", func_type) + entry_block = test_func.append_basic_block("entry") + builder = ir.IRBuilder(entry_block) + builder.ret_void() + + # Get LLVM IR as string + llvm_ir = str(module) + assert isinstance(llvm_ir, str) + assert len(llvm_ir) > 0 + assert "define void @test()" in llvm_ir + assert "ret void" in llvm_ir diff --git a/python/quantum-pecos/pyproject.toml b/python/quantum-pecos/pyproject.toml index e828f503d..10697ffb4 100644 --- a/python/quantum-pecos/pyproject.toml +++ b/python/quantum-pecos/pyproject.toml @@ -56,9 +56,6 @@ documentation = "https://quantum-pecos.readthedocs.io" repository = "https://github.com/PECOS-packages/PECOS" [project.optional-dependencies] -qir = [ - "llvmlite==0.43.0; python_version < '3.13'" -] guppy = [ "guppylang>=0.21.0", # Install guppylang first "selene-sim~=0.2.0", # Then selene-sim (dependency of guppylang) @@ -69,7 +66,6 @@ visualization = [ all = [ "quantum-pecos[visualization]", "quantum-pecos[guppy]", - "quantum-pecos[qir]", ] # CUDA dependencies. See docs/user-guide/cuda-setup.md for detailed installation instructions diff --git a/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qir.py b/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qir.py index 2786c948e..5feb029fa 100644 --- a/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qir.py +++ b/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qir.py @@ -15,7 +15,7 @@ from collections import OrderedDict from typing import TYPE_CHECKING -from llvmlite import binding, ir +from pecos_rslib.llvm import binding, ir from pecos import __version__ from pecos.qeclib.qubit import qgate_base diff --git a/python/quantum-pecos/tests/conftest.py b/python/quantum-pecos/tests/conftest.py index c348cd61c..2dc58ee6f 100644 --- a/python/quantum-pecos/tests/conftest.py +++ b/python/quantum-pecos/tests/conftest.py @@ -11,33 +11,11 @@ """Test configuration and shared fixtures.""" -# Check if llvmlite is available -import importlib.util - # Configure matplotlib to use non-interactive backend for tests # This must be done before importing matplotlib.pyplot to avoid GUI backend issues on Windows import matplotlib as mpl -import pytest mpl.use("Agg") -HAS_LLVMLITE = importlib.util.find_spec("llvmlite") is not None - -# Decorator to skip tests that require llvmlite -skipif_no_llvmlite = pytest.mark.skipif( - not HAS_LLVMLITE, - reason="llvmlite is not installed (not available for Python >= 3.13)", -) - - -# Make skipif_no_llvmlite available to all test modules -def pytest_configure(config: pytest.Config) -> None: - """Make custom markers available globally.""" - # Register the marker - config.addinivalue_line( - "markers", - "skipif_no_llvmlite: skip test if llvmlite is not available", - ) - - # Make skipif_no_llvmlite available in the pytest namespace - pytest.skipif_no_llvmlite = skipif_no_llvmlite +# Note: llvmlite functionality is now always available via Rust (pecos_rslib.ir and pecos_rslib.binding) +# No need for conditional test skipping diff --git a/python/quantum-pecos/tests/pecos/regression/test_qasm/random_cases/test_slr_phys.py b/python/quantum-pecos/tests/pecos/regression/test_qasm/random_cases/test_slr_phys.py index 97de5b41f..44a29c6b0 100644 --- a/python/quantum-pecos/tests/pecos/regression/test_qasm/random_cases/test_slr_phys.py +++ b/python/quantum-pecos/tests/pecos/regression/test_qasm/random_cases/test_slr_phys.py @@ -89,7 +89,6 @@ def test_bell() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_bell_qir() -> None: """Test that a simple Bell prep and measure circuit can be created.""" prog: Main = Main( @@ -105,7 +104,6 @@ def test_bell_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_bell_qreg_qir() -> None: """Test that a simple Bell prep and measure circuit can be created.""" prog: Main = Main( @@ -195,7 +193,6 @@ def test_strange_program() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_control_flow_qir() -> None: """Test a program with control flow into QIR.""" prog = Main( @@ -232,7 +229,6 @@ def test_control_flow_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_plus_qir() -> None: """Test a program with addition compiling into QIR.""" prog = Main( @@ -249,7 +245,6 @@ def test_plus_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_nested_xor_qir() -> None: """Test a program with addition compiling into QIR.""" prog = Main( @@ -268,7 +263,6 @@ def test_nested_xor_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_minus_qir() -> None: """Test a program with addition compiling into QIR.""" prog = Main( @@ -285,7 +279,6 @@ def test_minus_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_steane_qir() -> None: """Test the teleportation program using the Steane code.""" # print(SlrConverter(telep("X", "X")).qir()) diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_basic_permutation.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_basic_permutation.py index bc1d38e64..5a75dfa2c 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_basic_permutation.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_basic_permutation.py @@ -134,7 +134,6 @@ def test_same_register_permutation_qasm( @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_basic_permutation_qir(basic_permutation_program: tuple) -> None: """Test basic permutation functionality in QIR generation.""" prog, _, _ = basic_permutation_program @@ -174,7 +173,6 @@ def test_basic_permutation_qir(basic_permutation_program: tuple) -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_same_register_permutation_qir( same_register_permutation_program: tuple, ) -> None: diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_complex_permutation.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_complex_permutation.py index 7d0b7d4c0..d43ef739b 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_complex_permutation.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_complex_permutation.py @@ -123,7 +123,6 @@ def test_permutation_with_conditional_qasm() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_multiple_permutations_qir() -> None: """Test multiple sequential permutations in QIR generation.""" # Create a program with multiple sequential permutations @@ -172,7 +171,6 @@ def test_multiple_permutations_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_permutation_with_conditional_qir() -> None: """Test permutation with conditional operations in QIR generation.""" # Create a program with permutation and conditional operations diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_creg_permutation.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_creg_permutation.py index 0646558f4..463d75561 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_creg_permutation.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_creg_permutation.py @@ -58,7 +58,6 @@ def test_creg_permutation_qasm() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_creg_permutation_qir() -> None: """Test permutation of whole classical registers followed by both bit and register operations in QIR.""" prog = create_creg_permutation_program() diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_permutation.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_permutation.py index 34df9f3c0..60fafab30 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_permutation.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_permutation.py @@ -71,7 +71,6 @@ def test_register_measurement_permutation_qasm( @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_individual_measurement_permutation_qir( individual_measurement_program: tuple, ) -> None: @@ -153,7 +152,6 @@ def test_individual_measurement_permutation_qir( @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_register_measurement_permutation_qir( register_measurement_program: tuple, ) -> None: diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_unrolling.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_unrolling.py index 8dc79a4c4..7fc2ce502 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_unrolling.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_measurement_unrolling.py @@ -80,7 +80,6 @@ def test_measurement_unrolling_qasm() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_measurement_unrolling_qir() -> None: """Test measurement unrolling with permutations in QIR generation.""" prog = create_measurement_unrolling_program() diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_quantum_permutation.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_quantum_permutation.py index 224f5a2c9..6bb8f3f20 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_quantum_permutation.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_quantum_permutation.py @@ -58,7 +58,6 @@ def test_quantum_permutation_qasm(quantum_permutation_program: tuple) -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_quantum_permutation_qir(quantum_permutation_program: tuple) -> None: """Test permutation with quantum gates in QIR generation.""" prog, _, _ = quantum_permutation_program @@ -110,7 +109,6 @@ def test_quantum_permutation_qir(quantum_permutation_program: tuple) -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_permutation_with_bell_circuit_qir() -> None: """Test permutation functionality with a Bell circuit in QIR generation.""" # Create a program with permutations and a Bell circuit @@ -194,7 +192,6 @@ def test_permutation_with_bell_circuit_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_comprehensive_qir_verification() -> None: """Test comprehensive verification of QIR generation with permutations.""" # Create a program with a variety of operations to test permutation effects @@ -365,7 +362,6 @@ def test_comprehensive_qir_verification() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_rotation_gates_with_permutation() -> None: """Test that permutations work correctly with rotation gates in QIR generation.""" # Create a program with rotation gates and permutations diff --git a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_register_permutation.py b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_register_permutation.py index 08a694e52..a61276237 100644 --- a/python/quantum-pecos/tests/slr/pecos/unit/slr/test_register_permutation.py +++ b/python/quantum-pecos/tests/slr/pecos/unit/slr/test_register_permutation.py @@ -118,7 +118,6 @@ def test_mixed_permutation_qasm() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_whole_register_permutation_qir() -> None: """Test permutation of whole registers in QIR generation.""" prog = create_whole_register_permutation_program() @@ -159,7 +158,6 @@ def test_whole_register_permutation_qir() -> None: @pytest.mark.optional_dependency -@pytest.skipif_no_llvmlite def test_mixed_permutation_qir() -> None: """Test mixed whole register and element permutations in QIR generation.""" prog = create_mixed_permutation_program() diff --git a/uv.lock b/uv.lock index d90d45a70..145fd08b7 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14'", @@ -603,101 +603,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/12/3e2d2ec71796e0913178478e693a06af6a3bc9f7f9cb899bf85a426d8370/coverage-7.11.1.tar.gz", hash = "sha256:b4b3a072559578129a9e863082a2972a2abd8975bc0e2ec57da96afcd6580a8a", size = 814037, upload-time = "2025-11-07T10:52:41.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/15/6d7162366ed0508686dd68a716260bb3e2686fbce9e1acb6a42fa07cbc19/coverage-7.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:057c0aedcade895c0d25c06daff00fb381dea8089434ec916e59b051e5dead68", size = 216603, upload-time = "2025-11-07T10:49:45.154Z" }, - { url = "https://files.pythonhosted.org/packages/74/87/37ad9c35a3e5376f437c20a0fb01e20c4841afbf75328eb37d66dd87242d/coverage-7.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea73d4b5a489ea60ebce592ea516089d2bee8b299fb465fdd295264da98b2480", size = 217120, upload-time = "2025-11-07T10:49:47.95Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/4a1f7f679018c189c97a48f215275fe9e31e6a4db0135aac755c08224310/coverage-7.11.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:63f837e043f7f0788c2ce8fc6bbbcc3579f123af9cb284e1334099969222ceab", size = 243865, upload-time = "2025-11-07T10:49:49.716Z" }, - { url = "https://files.pythonhosted.org/packages/38/3f/5678792f90d4c8467531a4db9b66a8929cee0c9f28a8f5fed0e94d7e1d3e/coverage-7.11.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:086764f9fa6f4fa57035ed1c2387501c57092f2159bf1be0f090f85f9042ccf2", size = 245693, upload-time = "2025-11-07T10:49:51.273Z" }, - { url = "https://files.pythonhosted.org/packages/4e/27/0e6d9d3ec92307b67eb735b451cbead5d0307dc43f6ef1faf3f280abd68b/coverage-7.11.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a30a6ba3b668227d5a6f9f6ac2d875117af20f260ddc01619487174036a5583", size = 247552, upload-time = "2025-11-07T10:49:53.826Z" }, - { url = "https://files.pythonhosted.org/packages/98/d4/5600ae43bfeb9cea2b7ea2cd6a3c5a064533cdb53696a35b7bd8e288396b/coverage-7.11.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2663b19df42932a2cd66e62783f4bbbca047853ede893d48f3271c5e12c89246", size = 244515, upload-time = "2025-11-07T10:49:55.632Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b3/73a5033b46d8193b775ed6768f05c63dc4f9402834c56d6f456cc92175bb/coverage-7.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8c6570122b2eafaa5f4b54700b6f17ee10e23c5cf4292fa9b5a00e9dc279a74", size = 245596, upload-time = "2025-11-07T10:49:58.138Z" }, - { url = "https://files.pythonhosted.org/packages/72/57/40abaeacf2a78c22983183e0d44145ef64256ab12d35635d89fe08337475/coverage-7.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2cf57b5be59d36d133c06103f50c72bfdba7c7624d68b443b16a2d2d4eb40424", size = 243605, upload-time = "2025-11-07T10:49:59.73Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a5/796f3a21bdde952568e0cadf825269c74c33ae82966e46283075e3babb80/coverage-7.11.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f3f3eb204cbe221ef9209e34341b3d0bc32f4cf3c7c4f150db571e20b9963ecd", size = 243867, upload-time = "2025-11-07T10:50:01.164Z" }, - { url = "https://files.pythonhosted.org/packages/36/0d/2071cb65945737f5d82eebcdfb7b869c56c0f521e1af4af6f6b0a80cfe62/coverage-7.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:57d36cb40ad55fe443bb2390c759c61b9fa3afc68d5446a2aaed1ad18fc92752", size = 244485, upload-time = "2025-11-07T10:50:03.086Z" }, - { url = "https://files.pythonhosted.org/packages/45/c5/599efe919c50d4069029fa59696f7ec106a70eb0e92b8a2f7a5f8afd0980/coverage-7.11.1-cp310-cp310-win32.whl", hash = "sha256:999a82a2dec9e31df7cb49a17e6b564b76fab3f9cd76788280997b5a694b8025", size = 219176, upload-time = "2025-11-07T10:50:04.432Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8c/022c91f0f0e08918991bff99bdc961a60b0585397f78e9885414c9e20f0f/coverage-7.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:d47ad0fdc96d5772fcded1a57f042a72dba893a226d3efa5802d0bfa88e3a9a1", size = 220112, upload-time = "2025-11-07T10:50:06.013Z" }, - { url = "https://files.pythonhosted.org/packages/5b/09/7d035b041643d4d99c8ea374b7f0363ebb5edf02121ea4bfddaf7f738e08/coverage-7.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f8be6327cb57e73f1933a111b31ca3e8db68eba70921244296cd9541f8405cf", size = 216729, upload-time = "2025-11-07T10:50:07.543Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d0/3b31528bb14c2dc498c09804ee4bfe3e17ca28b1de6c2e3e850c99ed2b39/coverage-7.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3386b3d974eea5b8fbc31388c2847d5b3ce783aa001048c7c13ad0e0f9f97284", size = 217232, upload-time = "2025-11-07T10:50:09.064Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1c/713bd524fec4d3d1d2813de0fad233d4ff9e3bbd9bf8f8052bb0359e0f3f/coverage-7.11.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dd5a0e53989aa0d2b94871ac9a990f7b6247c3afe49af77f8750d7bcf1e66efa", size = 247628, upload-time = "2025-11-07T10:50:10.609Z" }, - { url = "https://files.pythonhosted.org/packages/b2/05/2887d76a5e160eb1b62dc99b1f177052799c37134d38e8b208e01bd4d712/coverage-7.11.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e17d99e4a9989ccc52d672543ed9d8741d90730ba331d452793be5733b4fee58", size = 249545, upload-time = "2025-11-07T10:50:12.187Z" }, - { url = "https://files.pythonhosted.org/packages/6f/7e/bb95b8396a7c8deb0426a1261d62851b28a380a849546f730a8ee36471f7/coverage-7.11.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ece0ace8d8fc20cc29e2108d4031517c03d9e08883f10c1df16bef84d469110", size = 251658, upload-time = "2025-11-07T10:50:14.23Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/1397eaee431b43dbe2ec683401c8341d65993434d69f3a36087c5c280fb1/coverage-7.11.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:54bf4a13bfcf6f07c4b7d83970074dc2fa8b5782e8dee962f5eb4dfbc3a275ef", size = 247742, upload-time = "2025-11-07T10:50:16.001Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ea/b71c504fe7fd58495ccabe1cd4afd7e5685d563e2019ae4865cb0b44f652/coverage-7.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b48e85160795648323fc3a9d8efe11be65a033b564e1db28b53866810da6cf35", size = 249351, upload-time = "2025-11-07T10:50:17.852Z" }, - { url = "https://files.pythonhosted.org/packages/10/35/e44cb3d633cdeec7c6def511f552494a16bfa4e6cb5e916d9a0d4c98a933/coverage-7.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b77e7bb5765988a7a80463b999085cd66c6515113fc88b46910217f19ee99fe", size = 247423, upload-time = "2025-11-07T10:50:19.439Z" }, - { url = "https://files.pythonhosted.org/packages/af/88/c344ab065706a9df03b558fe4bcb9d367f92d5983f6a672c03eeb0905d39/coverage-7.11.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ce345819ddedcbe797d8ba824deeb0d55710037dfd47efd95709ab9e1b841e0c", size = 247150, upload-time = "2025-11-07T10:50:20.919Z" }, - { url = "https://files.pythonhosted.org/packages/34/5b/b0b6c986e41c6072d0c57761e648c120a34b1004f7de5b90bda5cb7542eb/coverage-7.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:abde2bd52560527124d9e6515daa1f1e3c7e820a37af63d063723867775220aa", size = 248047, upload-time = "2025-11-07T10:50:22.599Z" }, - { url = "https://files.pythonhosted.org/packages/06/2b/aa232a409b63422910e180ccd5f7083e6e41d942608f3513e617006c0253/coverage-7.11.1-cp311-cp311-win32.whl", hash = "sha256:049883a469ec823b1c9556050380e61f580d52f8abfc8be2071f3512a2bc3859", size = 219201, upload-time = "2025-11-07T10:50:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/41/d4/ec0155c883ddc43b2ff08e3b88fc846a4642a117306f8891188f217bd823/coverage-7.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:784a9fe33335296857db05b97dcb16df811418515a2355fc4811b0c2b029b4be", size = 220134, upload-time = "2025-11-07T10:50:26.035Z" }, - { url = "https://files.pythonhosted.org/packages/71/59/96dc2070a2f124e27c9b8d6e45e35d44f01b056b6eaf6793bfff40e84c4a/coverage-7.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:2bcfeb983a53f0d3ee3ebc004827723d8accb619f64bf90aff73b7703dfe14bd", size = 218807, upload-time = "2025-11-07T10:50:27.685Z" }, - { url = "https://files.pythonhosted.org/packages/0f/31/04af7e42fdb3681e4d73d37bf3f375f0488aa38d1001ee746c7dbfe09643/coverage-7.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:421e2d237dcecdefa9b77cae1aa0dfff5c495f29e053e776172457e289976311", size = 216896, upload-time = "2025-11-07T10:50:31.429Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e9/1c3628a1225bdea66295a117cd2bb1d324d9c433c40078b24d50f55448a7/coverage-7.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:08ef89c812072ecd52a862b46e131f75596475d23cc7f5a75410394341d4332f", size = 217261, upload-time = "2025-11-07T10:50:33.008Z" }, - { url = "https://files.pythonhosted.org/packages/2b/80/4d4f943da23c432b2bba8664f4eada9b19911081852e8cc89776c61d0b94/coverage-7.11.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bc6e0b2d6ed317810b4e435ffabc31b2d517d6ceb4183dfd6af4748c52d170eb", size = 248742, upload-time = "2025-11-07T10:50:34.634Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e1/c4b42f02fbb6ce08e05d7a2b26bcf5df11d3e67a3806e40415f7ab9511e7/coverage-7.11.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b59736704df8b1f8b1dafb36b16f2ef8a952e4410465634442459426bd2319ae", size = 251503, upload-time = "2025-11-07T10:50:36.501Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/3df60e88f1dabccae4994c6df4a2f23d4cd0eee27fc3ae8f0bb2e78cb538/coverage-7.11.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:843816452d8bfc4c2be72546b3b382850cb91150feaa963ec7d2b665ec9d4768", size = 252590, upload-time = "2025-11-07T10:50:38.059Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/2b9fae11361b0348c2d3612a8179d2cc8b6b245e8b14d5479c75b9f18613/coverage-7.11.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:19363046125d4a423c25d3d7c90bab3a0230932c16014198f87a6b3960c1b187", size = 249133, upload-time = "2025-11-07T10:50:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2b/e33712a8eede02762a536bdc2f89e736e0ad87bd13b35d724306585aeb54/coverage-7.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e37486aed7045c280ebdc207026bdef9267730177d929a5e25250e1f33cc125", size = 250524, upload-time = "2025-11-07T10:50:41.59Z" }, - { url = "https://files.pythonhosted.org/packages/84/c9/6181877977a0f6e46b9c93a8382b8c671769fb12df8a15be8d6091541b77/coverage-7.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c68180e67b4843674bfb1d3ec928ffcfc94081b5da959e616405eca51c23356", size = 248673, upload-time = "2025-11-07T10:50:43.153Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d6/ff26c2eb57d4dcd46c6ed136d6b04aceb7f58f48dcc500c77f7194711a6f/coverage-7.11.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cf825b60f94d1706c22d4887310db26cc3117d545ac6ad4229b4a0d718afcf9a", size = 248251, upload-time = "2025-11-07T10:50:45.069Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ff/411803f1fcb9efe00afbc96442564cc691f537541a8bde377cf1ac04e695/coverage-7.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:437149272ff0440df66044bd6ee87cbc252463754ca43cafa496cfb2f57f56dd", size = 250111, upload-time = "2025-11-07T10:50:46.701Z" }, - { url = "https://files.pythonhosted.org/packages/c1/9f/781c045e1e5f8930f8266f224318040413b60837749d2ed11883b7478c81/coverage-7.11.1-cp312-cp312-win32.whl", hash = "sha256:98ea0b8d1addfc333494c2248af367e8ecb27724a99804a18376b801f876da58", size = 219407, upload-time = "2025-11-07T10:50:48.862Z" }, - { url = "https://files.pythonhosted.org/packages/26/59/813d8eedc96a781e8a6f9c37f6ecb4326ebbffdafe2e1154ed2def468b76/coverage-7.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:7d49a473799e55a465bcadd19525977ab80031b8b86baaa622241808df4585cd", size = 220220, upload-time = "2025-11-07T10:50:51.576Z" }, - { url = "https://files.pythonhosted.org/packages/63/5f/c0905d9159d38194943a21d7d013f1c2f0c43e7d63f680ed56269728418a/coverage-7.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:0c77e5951ab176a6ccb70c6f688fca2a7ac834753ba82ee4eb741be655f30b43", size = 218856, upload-time = "2025-11-07T10:50:53.591Z" }, - { url = "https://files.pythonhosted.org/packages/f4/01/0c50c318f5e8f1a482da05d788d0ff06137803ed8fface4a1ba51e04b3ad/coverage-7.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:da9930594ca99d66eb6f613d7beba850db2f8dfa86810ee35ae24e4d5f2bb97d", size = 216920, upload-time = "2025-11-07T10:50:55.992Z" }, - { url = "https://files.pythonhosted.org/packages/20/11/9f038e6c2baea968c377ab355b0d1d0a46b5f38985691bf51164e1b78c1f/coverage-7.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc47a280dc014220b0fc6e5f55082a3f51854faf08fd9635b8a4f341c46c77d3", size = 217301, upload-time = "2025-11-07T10:50:57.609Z" }, - { url = "https://files.pythonhosted.org/packages/68/cd/9dcf93d81d0cddaa0bba90c3b4580e6f1ddf833918b816930d250cc553a4/coverage-7.11.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:74003324321bbf130939146886eddf92e48e616b5910215e79dea6edeb8ee7c8", size = 248277, upload-time = "2025-11-07T10:50:59.442Z" }, - { url = "https://files.pythonhosted.org/packages/11/f5/b2c7c494046c9c783d3cac4c812fc24d6104dd36a7a598e7dd6fea3e7927/coverage-7.11.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:211f7996265daab60a8249af4ca6641b3080769cbedcffc42cc4841118f3a305", size = 250871, upload-time = "2025-11-07T10:51:01.094Z" }, - { url = "https://files.pythonhosted.org/packages/a5/5a/b359649566954498aa17d7c98093182576d9e435ceb4ea917b3b48d56f86/coverage-7.11.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70619d194d8fea0cb028cb6bb9c85b519c7509c1d1feef1eea635183bc8ecd27", size = 252115, upload-time = "2025-11-07T10:51:03.087Z" }, - { url = "https://files.pythonhosted.org/packages/f3/17/3cef1ede3739622950f0737605353b797ec564e70c9d254521b10f4b03ba/coverage-7.11.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0208bb59d441cfa3321569040f8e455f9261256e0df776c5462a1e5a9b31e13", size = 248442, upload-time = "2025-11-07T10:51:04.888Z" }, - { url = "https://files.pythonhosted.org/packages/5f/63/d5854c47ae42d9d18855329db6bc528f5b7f4f874257edb00cf8b483f9f8/coverage-7.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:545714d8765bda1c51f8b1c96e0b497886a054471c68211e76ef49dd1468587d", size = 250253, upload-time = "2025-11-07T10:51:06.515Z" }, - { url = "https://files.pythonhosted.org/packages/48/e8/c7706f8a5358a59c18b489e7e19e83d6161b7c8bc60771f95920570c94a8/coverage-7.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d0a2b02c1e20158dd405054bcca87f91fd5b7605626aee87150819ea616edd67", size = 248217, upload-time = "2025-11-07T10:51:08.405Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c9/a2136dfb168eb09e2f6d9d6b6c986243fdc0b3866a9376adb263d3c3378b/coverage-7.11.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0f4aa986a4308a458e0fb572faa3eb3db2ea7ce294604064b25ab32b435a468", size = 248040, upload-time = "2025-11-07T10:51:10.626Z" }, - { url = "https://files.pythonhosted.org/packages/18/9a/a63991c0608ddc6adf65e6f43124951aaf36bd79f41937b028120b8268ea/coverage-7.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d51cc6687e8bbfd1e041f52baed0f979cd592242cf50bf18399a7e03afc82d88", size = 249801, upload-time = "2025-11-07T10:51:12.63Z" }, - { url = "https://files.pythonhosted.org/packages/84/19/947acf7c0c6e90e4ec3abf474133ed36d94407d07e36eafdfd3acb59fee9/coverage-7.11.1-cp313-cp313-win32.whl", hash = "sha256:1b3067db3afe6deeca2b2c9f0ec23820d5f1bd152827acfadf24de145dfc5f66", size = 219430, upload-time = "2025-11-07T10:51:14.329Z" }, - { url = "https://files.pythonhosted.org/packages/35/54/36fef7afb3884450c7b6d494fcabe2fab7c669d547c800ca30f41c1dc212/coverage-7.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:39a4c44b0cd40e3c9d89b2b7303ebd6ab9ae8a63f9e9a8c4d65a181a0b33aebe", size = 220239, upload-time = "2025-11-07T10:51:16.418Z" }, - { url = "https://files.pythonhosted.org/packages/d3/dc/7d38bb99e8e69200b7dd5de15507226bd90eac102dfc7cc891b9934cdc76/coverage-7.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:a2e3560bf82fa8169a577e054cbbc29888699526063fee26ea59ea2627fd6e73", size = 218868, upload-time = "2025-11-07T10:51:18.186Z" }, - { url = "https://files.pythonhosted.org/packages/36/c6/d1ff54fbd6bcad42dbcfd13b417e636ef84aae194353b1ef3361700f2525/coverage-7.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47a4f362a10285897ab3aa7a4b37d28213a4f2626823923613d6d7a3584dd79a", size = 217615, upload-time = "2025-11-07T10:51:21.065Z" }, - { url = "https://files.pythonhosted.org/packages/73/f9/6ed59e7cf1488d6f975e5b14ef836f5e537913523e92175135f8518a83ce/coverage-7.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0df35fa7419ef571db9dacd50b0517bc54dbfe37eb94043b5fc3540bff276acd", size = 217960, upload-time = "2025-11-07T10:51:22.797Z" }, - { url = "https://files.pythonhosted.org/packages/c4/74/2dab1dc2ebe16f074f80ae483b0f45faf278d102be703ac01b32cd85b6c3/coverage-7.11.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e1a2c621d341c9d56f7917e56fbb56be4f73fe0d0e8dae28352fb095060fd467", size = 259262, upload-time = "2025-11-07T10:51:24.467Z" }, - { url = "https://files.pythonhosted.org/packages/15/49/eccfe039663e29a50a54b0c2c8d076acd174d7ac50d018ef8a5b1c37c8dc/coverage-7.11.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c354b111be9b2234d9573d75dd30ca4e414b7659c730e477e89be4f620b3fb5", size = 261326, upload-time = "2025-11-07T10:51:26.232Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bb/2b829aa23fd5ee8318e33cc02a606eb09900921291497963adc3f06af8bb/coverage-7.11.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4589bd44698728f600233fb2881014c9b8ec86637ef454c00939e779661dbe7e", size = 263758, upload-time = "2025-11-07T10:51:27.912Z" }, - { url = "https://files.pythonhosted.org/packages/ac/03/d44c3d70e5da275caf2cad2071da6b425412fbcb1d1d5a81f1f89b45e3f1/coverage-7.11.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6956fc8754f2309131230272a7213a483a32ecbe29e2b9316d808a28f2f8ea1", size = 258444, upload-time = "2025-11-07T10:51:30.107Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c1/cf61d9f46ae088774c65dd3387a15dfbc72de90c1f6e105025e9eda19b42/coverage-7.11.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63926a97ed89dc6a087369b92dcb8b9a94cead46c08b33a7f1f4818cd8b6a3c3", size = 261335, upload-time = "2025-11-07T10:51:31.814Z" }, - { url = "https://files.pythonhosted.org/packages/95/9a/b3299bb14f11f2364d78a2b9704491b15395e757af6116694731ce4e5834/coverage-7.11.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f5311ba00c53a7fb2b293fdc1f478b7286fe2a845a7ba9cda053f6e98178f0b4", size = 258951, upload-time = "2025-11-07T10:51:33.925Z" }, - { url = "https://files.pythonhosted.org/packages/3f/a3/73cb2763e59f14ba6d8d6444b1f640a9be2242bfb59b7e50581c695db7ff/coverage-7.11.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:31bf5ffad84c974f9e72ac53493350f36b6fa396109159ec704210698f12860b", size = 257840, upload-time = "2025-11-07T10:51:36.092Z" }, - { url = "https://files.pythonhosted.org/packages/85/db/482e72589a952027e238ffa3a15f192c552e0685fd0c5220ad05b5f17d56/coverage-7.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:227ee59fbc4a8c57a7383a1d7af6ca94a78ae3beee4045f38684548a8479a65b", size = 260040, upload-time = "2025-11-07T10:51:38.277Z" }, - { url = "https://files.pythonhosted.org/packages/18/a1/b931d3ee099c2dca8e9ea56c07ae84c0f91562f7bbbcccab8c91b3474ef1/coverage-7.11.1-cp313-cp313t-win32.whl", hash = "sha256:a447d97b3ce680bb1da2e6bd822ebb71be6a1fb77ce2c2ad2fe4bd8aacec3058", size = 220102, upload-time = "2025-11-07T10:51:40.017Z" }, - { url = "https://files.pythonhosted.org/packages/9a/53/b553b7bfa6207def4918f0cb72884c844fa4c3f1566e58fbb4f34e54cdc5/coverage-7.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d6d11180437c67bde2248563a42b8e5bbf85c8df78fae13bf818ad17bfb15f02", size = 221166, upload-time = "2025-11-07T10:51:41.921Z" }, - { url = "https://files.pythonhosted.org/packages/6b/45/1c1d58b3ed585598764bd2fe41fcf60ccafe15973ad621c322ba52e22d32/coverage-7.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:1e19a4c43d612760c6f7190411fb157e2d8a6dde00c91b941d43203bd3b17f6f", size = 219439, upload-time = "2025-11-07T10:51:43.753Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c2/ac2c3417eaa4de1361036ebbc7da664242b274b2e00c4b4a1cfc7b29920b/coverage-7.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0305463c45c5f21f0396cd5028de92b1f1387e2e0756a85dd3147daa49f7a674", size = 216967, upload-time = "2025-11-07T10:51:45.55Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a3/afef455d03c468ee303f9df9a6f407e8bea64cd576fca914ff888faf52ca/coverage-7.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fa4d468d5efa1eb6e3062be8bd5f45cbf28257a37b71b969a8c1da2652dfec77", size = 217298, upload-time = "2025-11-07T10:51:47.31Z" }, - { url = "https://files.pythonhosted.org/packages/9d/59/6e2fb3fb58637001132dc32228b4fb5b332d75d12f1353cb00fe084ee0ba/coverage-7.11.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d2b2f5fc8fe383cbf2d5c77d6c4b2632ede553bc0afd0cdc910fa5390046c290", size = 248337, upload-time = "2025-11-07T10:51:49.48Z" }, - { url = "https://files.pythonhosted.org/packages/1d/5e/ce442bab963e3388658da8bde6ddbd0a15beda230afafaa25e3c487dc391/coverage-7.11.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bde6488c1ad509f4fb1a4f9960fd003d5a94adef61e226246f9699befbab3276", size = 250853, upload-time = "2025-11-07T10:51:51.215Z" }, - { url = "https://files.pythonhosted.org/packages/d1/2f/43f94557924ca9b64e09f1c3876da4eec44a05a41e27b8a639d899716c0e/coverage-7.11.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a69e0d6fa0b920fe6706a898c52955ec5bcfa7e45868215159f45fd87ea6da7c", size = 252190, upload-time = "2025-11-07T10:51:53.262Z" }, - { url = "https://files.pythonhosted.org/packages/8c/fa/a04e769b92bc5628d4bd909dcc3c8219efe5e49f462e29adc43e198ecfde/coverage-7.11.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:976e51e4a549b80e4639eda3a53e95013a14ff6ad69bb58ed604d34deb0e774c", size = 248335, upload-time = "2025-11-07T10:51:55.388Z" }, - { url = "https://files.pythonhosted.org/packages/99/d0/b98ab5d2abe425c71117a7c690ead697a0b32b83256bf0f566c726b7f77b/coverage-7.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d61fcc4d384c82971a3d9cf00d0872881f9ded19404c714d6079b7a4547e2955", size = 250209, upload-time = "2025-11-07T10:51:57.263Z" }, - { url = "https://files.pythonhosted.org/packages/9c/3f/b9c4fbd2e6d1b64098f99fb68df7f7c1b3e0a0968d24025adb24f359cdec/coverage-7.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:284c5df762b533fae3ebd764e3b81c20c1c9648d93ef34469759cb4e3dfe13d0", size = 248163, upload-time = "2025-11-07T10:51:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/08/fc/3e4d54fb6368b0628019eefd897fc271badbd025410fd5421a65fb58758f/coverage-7.11.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:bab32cb1d4ad2ac6dcc4e17eee5fa136c2a1d14ae914e4bce6c8b78273aece3c", size = 247983, upload-time = "2025-11-07T10:52:01.027Z" }, - { url = "https://files.pythonhosted.org/packages/b9/4a/a5700764a12e932b35afdddb2f59adbca289c1689455d06437f609f3ef35/coverage-7.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36f2fed9ce392ca450fb4e283900d0b41f05c8c5db674d200f471498be3ce747", size = 249646, upload-time = "2025-11-07T10:52:02.856Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2c/45ed33d9e80a1cc9b44b4bd535d44c154d3204671c65abd90ec1e99522a2/coverage-7.11.1-cp314-cp314-win32.whl", hash = "sha256:853136cecb92a5ba1cc8f61ec6ffa62ca3c88b4b386a6c835f8b833924f9a8c5", size = 219700, upload-time = "2025-11-07T10:52:05.05Z" }, - { url = "https://files.pythonhosted.org/packages/90/d7/5845597360f6434af1290118ebe114642865f45ce47e7e822d9c07b371be/coverage-7.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:77443d39143e20927259a61da0c95d55ffc31cf43086b8f0f11a92da5260d592", size = 220516, upload-time = "2025-11-07T10:52:07.259Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d0/d311a06f9cf7a48a98ffcfd0c57db0dcab6da46e75c439286a50dc648161/coverage-7.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:829acb88fa47591a64bf5197e96a931ce9d4b3634c7f81a224ba3319623cdf6c", size = 219091, upload-time = "2025-11-07T10:52:09.216Z" }, - { url = "https://files.pythonhosted.org/packages/a7/3d/c6a84da4fa9b840933045b19dd19d17b892f3f2dd1612903260291416dba/coverage-7.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2ad1fe321d9522ea14399de83e75a11fb6a8887930c3679feb383301c28070d9", size = 217700, upload-time = "2025-11-07T10:52:11.348Z" }, - { url = "https://files.pythonhosted.org/packages/94/10/a4fc5022017dd7ac682dc423849c241dfbdad31734b8f96060d84e70b587/coverage-7.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f69c332f0c3d1357c74decc9b1843fcd428cf9221bf196a20ad22aa1db3e1b6c", size = 217968, upload-time = "2025-11-07T10:52:13.203Z" }, - { url = "https://files.pythonhosted.org/packages/59/2d/a554cd98924d296de5816413280ac3b09e42a05fb248d66f8d474d321938/coverage-7.11.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:576baeea4eebde684bf6c91c01e97171c8015765c8b2cfd4022a42b899897811", size = 259334, upload-time = "2025-11-07T10:52:15.079Z" }, - { url = "https://files.pythonhosted.org/packages/05/98/d484cb659ec33958ca96b6f03438f56edc23b239d1ad0417b7a97fc1848a/coverage-7.11.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:28ad84c694fa86084cfd3c1eab4149844b8cb95bd8e5cbfc4a647f3ee2cce2b3", size = 261445, upload-time = "2025-11-07T10:52:17.134Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fa/920cba122cc28f4557c0507f8bd7c6e527ebcc537d0309186f66464a8fd9/coverage-7.11.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b1043ff958f09fc3f552c014d599f3c6b7088ba97d7bc1bd1cce8603cd75b520", size = 263858, upload-time = "2025-11-07T10:52:19.836Z" }, - { url = "https://files.pythonhosted.org/packages/2a/a0/036397bdbee0f3bd46c2e26fdfbb1a61b2140bf9059240c37b61149047fa/coverage-7.11.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6681add5060c2742dafcf29826dff1ff8eef889a3b03390daeed84361c428bd", size = 258381, upload-time = "2025-11-07T10:52:21.687Z" }, - { url = "https://files.pythonhosted.org/packages/b6/61/2533926eb8990f182eb287f4873216c8ca530cc47241144aabf46fe80abe/coverage-7.11.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:773419b225ec9a75caa1e941dd0c83a91b92c2b525269e44e6ee3e4c630607db", size = 261321, upload-time = "2025-11-07T10:52:23.612Z" }, - { url = "https://files.pythonhosted.org/packages/32/6e/618f7e203a998e4f6b8a0fa395744a416ad2adbcdc3735bc19466456718a/coverage-7.11.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a9cb272a0e0157dbb9b2fd0b201b759bd378a1a6138a16536c025c2ce4f7643b", size = 258933, upload-time = "2025-11-07T10:52:25.514Z" }, - { url = "https://files.pythonhosted.org/packages/22/40/6b1c27f772cb08a14a338647ead1254a57ee9dabbb4cacbc15df7f278741/coverage-7.11.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e09adb2a7811dc75998eef68f47599cf699e2b62eed09c9fefaeb290b3920f34", size = 257756, upload-time = "2025-11-07T10:52:27.845Z" }, - { url = "https://files.pythonhosted.org/packages/73/07/f9cd12f71307a785ea15b009c8d8cc2543e4a867bd04b8673843970b6b43/coverage-7.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1335fa8c2a2fea49924d97e1e3500cfe8d7c849f5369f26bb7559ad4259ccfab", size = 260086, upload-time = "2025-11-07T10:52:29.776Z" }, - { url = "https://files.pythonhosted.org/packages/34/02/31c5394f6f5d72a466966bcfdb61ce5a19862d452816d6ffcbb44add16ee/coverage-7.11.1-cp314-cp314t-win32.whl", hash = "sha256:4782d71d2a4fa7cef95e853b7097c8bbead4dbd0e6f9c7152a6b11a194b794db", size = 220483, upload-time = "2025-11-07T10:52:31.752Z" }, - { url = "https://files.pythonhosted.org/packages/7f/96/81e1ef5fbfd5090113a96e823dbe055e4c58d96ca73b1fb0ad9d26f9ec36/coverage-7.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:939f45e66eceb63c75e8eb8fc58bb7077c00f1a41b0e15c6ef02334a933cfe93", size = 221592, upload-time = "2025-11-07T10:52:33.724Z" }, - { url = "https://files.pythonhosted.org/packages/38/7a/a5d050de44951ac453a2046a0f3fb5471a4a557f0c914d00db27d543d94c/coverage-7.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:01c575bdbef35e3f023b50a146e9a75c53816e4f2569109458155cd2315f87d9", size = 219627, upload-time = "2025-11-07T10:52:36.285Z" }, - { url = "https://files.pythonhosted.org/packages/76/32/bd9f48c28e23b2f08946f8e83983617b00619f5538dbd7e1045fa7e88c00/coverage-7.11.1-py3-none-any.whl", hash = "sha256:0fa848acb5f1da24765cee840e1afe9232ac98a8f9431c6112c15b34e880b9e8", size = 208689, upload-time = "2025-11-07T10:52:38.646Z" }, +version = "7.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/32/e6/7c4006cf689ed7a4aa75dcf1f14acbc04e585714c220b5cc6d231096685a/coverage-7.11.2.tar.gz", hash = "sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93", size = 814849, upload-time = "2025-11-08T20:26:33.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/5b/d943b719938467d313973fd83af9c810e248fcec33165d5ab0148ab1c602/coverage-7.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1", size = 216802, upload-time = "2025-11-08T20:23:47.186Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f7/d3c096ca6a6212e8a536ae2144406d28b43e7528ff05a0bf6a5336319d0d/coverage-7.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311", size = 217317, upload-time = "2025-11-08T20:23:50.255Z" }, + { url = "https://files.pythonhosted.org/packages/10/46/d0dbafbd3604293b73a44ae9c88e339921c13f309138b31ec60b451895b9/coverage-7.11.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb", size = 244068, upload-time = "2025-11-08T20:23:51.63Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/ef8aba300f7224167c556d15852bf35d42c7af93b68f3ef82323737515e8/coverage-7.11.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9", size = 245896, upload-time = "2025-11-08T20:23:53.1Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ea/02fa537e61bc61fd111d5d9611184a354dd26bbc31e58ccd922f76404723/coverage-7.11.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f", size = 247755, upload-time = "2025-11-08T20:23:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/41/3b/6cc19074059c030e489fd5ff934aa49521a75ba6236d27badb3b4270b21c/coverage-7.11.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188", size = 244714, upload-time = "2025-11-08T20:23:56.655Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d5/b3480a0fd9c45fad37884c38ee943788ef43b64abf156b3f8e6af096c62e/coverage-7.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903", size = 245800, upload-time = "2025-11-08T20:23:58.06Z" }, + { url = "https://files.pythonhosted.org/packages/07/2a/34f1476db9c58c410193f8f0cbecdfd9931912ed07de628fdffe0dae216d/coverage-7.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7", size = 243808, upload-time = "2025-11-08T20:23:59.756Z" }, + { url = "https://files.pythonhosted.org/packages/73/fd/b43a0a4f6306a486d31cdd4166afd4dc0b08a8f072d7ab2ccc23893b6d19/coverage-7.11.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9", size = 244070, upload-time = "2025-11-08T20:24:01.281Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8c/bcbe2c9cb81ef008d05b04ebc37a3a1c65d61b61c9cf772f0ae473ddc56b/coverage-7.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b", size = 244688, upload-time = "2025-11-08T20:24:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f7/c6c276f6663a1d7e29f8cc4a5a8c76dbf834ecb74017936187146adbce9e/coverage-7.11.2-cp310-cp310-win32.whl", hash = "sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785", size = 219382, upload-time = "2025-11-08T20:24:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/4f/aa/0d07b2d567f1d005088b4afad533b4a6af48ec75f3f9071afbe5f7076cab/coverage-7.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493", size = 220319, upload-time = "2025-11-08T20:24:06.464Z" }, + { url = "https://files.pythonhosted.org/packages/89/39/326336c0adc6dc624be0edb5143dec90a9da2626335e83f6d09da120922f/coverage-7.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f", size = 216927, upload-time = "2025-11-08T20:24:08.167Z" }, + { url = "https://files.pythonhosted.org/packages/b7/68/cd1d3422fc9525827cddf62b2385f78356b88e745e90e8e512fefcc05f8f/coverage-7.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648", size = 217429, upload-time = "2025-11-08T20:24:09.939Z" }, + { url = "https://files.pythonhosted.org/packages/36/73/3f384dd79d6bbdf7fbceda3c7e0db33e148559bc18c49022c9c0c5e512c1/coverage-7.11.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534", size = 247832, upload-time = "2025-11-08T20:24:11.897Z" }, + { url = "https://files.pythonhosted.org/packages/45/3c/27839b6f343998e82f3e470397c058566c953dc71fe37e0abb953133a341/coverage-7.11.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b", size = 249749, upload-time = "2025-11-08T20:24:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/6e/51/011102c7f6902084e632128ac0f42cd3345acc543a7c9f8ce5e1a94397ef/coverage-7.11.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d", size = 251860, upload-time = "2025-11-08T20:24:15.113Z" }, + { url = "https://files.pythonhosted.org/packages/bb/4c/4622eb7aac98c2552ed8a176a6015ea8cf36a2ec75cbcfb5f2ccf100bbd6/coverage-7.11.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad", size = 247942, upload-time = "2025-11-08T20:24:16.637Z" }, + { url = "https://files.pythonhosted.org/packages/95/94/42ba12fc827fb504f8f8ec5313e46cf5582cdb9d4823e76d70ed22e88bdf/coverage-7.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd", size = 249553, upload-time = "2025-11-08T20:24:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/a3/47/2cd8014c872a3e469ffe50fbc692d02c7460e20cd701a0d6366fbef759e3/coverage-7.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582", size = 247627, upload-time = "2025-11-08T20:24:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/a9/31/e722f2c7f0f16954d13e6441a24d841174bcb1ff2421c6504c024c09c7af/coverage-7.11.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b", size = 247353, upload-time = "2025-11-08T20:24:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/d4fd26be0ce7993f0013df9788e52cd83a1adf5cfb9887bfd1b38722380e/coverage-7.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6", size = 248251, upload-time = "2025-11-08T20:24:22.724Z" }, + { url = "https://files.pythonhosted.org/packages/1c/33/003f7b5f10fae2ad7390e57a1520c46a24bd46e374b197e97050ae47751f/coverage-7.11.2-cp311-cp311-win32.whl", hash = "sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820", size = 219410, upload-time = "2025-11-08T20:24:24.15Z" }, + { url = "https://files.pythonhosted.org/packages/22/e8/5db102c57143f33a9229ecdc8d7976ad0c5d103fcd26f2b939db96789990/coverage-7.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203", size = 220342, upload-time = "2025-11-08T20:24:25.947Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b2/9908f6b4b979045c01e02a069ae5f73c16dff022c296a5e1fd756c602c6c/coverage-7.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952", size = 219014, upload-time = "2025-11-08T20:24:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/aef630a13bc974333aeb83d69765eb513f790bf4bd5b79b8036ec176de8e/coverage-7.11.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732", size = 217103, upload-time = "2025-11-08T20:24:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1f/41f144dc49c07043230ad79126a9c79236724579c43175e476e0731ddc2a/coverage-7.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4", size = 217467, upload-time = "2025-11-08T20:24:30.758Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/6fc4b47c7c8323b0326c57786858b6185668f008edc2ea626bc35fb53e28/coverage-7.11.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20", size = 248947, upload-time = "2025-11-08T20:24:32.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/03bb7b3d991259ef8d483b83560f87eb4c6d5e8889ad836d212e010d08b3/coverage-7.11.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18", size = 251707, upload-time = "2025-11-08T20:24:34.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/c32c7c76c8373978bf68bcfd87a1d265ace9c973ed9a007cada37f25948a/coverage-7.11.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2", size = 252793, upload-time = "2025-11-08T20:24:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/60/16/86582ab283bad8e137f76e97c5b75a81f547174bca9bb2eba8b7be33d8b6/coverage-7.11.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893", size = 249331, upload-time = "2025-11-08T20:24:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/24449d3e2a84bd38c1903757265cd45b6c9021ecf013f27e33155dba5ada/coverage-7.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a", size = 250728, upload-time = "2025-11-08T20:24:38.936Z" }, + { url = "https://files.pythonhosted.org/packages/86/bc/fcfe9bdda15f48ef6d78a8524837216752fe82474965d42310e6296c8bde/coverage-7.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71", size = 248877, upload-time = "2025-11-08T20:24:40.444Z" }, + { url = "https://files.pythonhosted.org/packages/51/27/58db09afcb155f41739330c521258782eefc12fe18f70d3b8e5dbc61857b/coverage-7.11.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674", size = 248455, upload-time = "2025-11-08T20:24:42.479Z" }, + { url = "https://files.pythonhosted.org/packages/24/6b/1eba5fa2b01b1aa727aa2a1c480c5f475fccecf32decae95b890cef7ee68/coverage-7.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb", size = 250316, upload-time = "2025-11-08T20:24:44.029Z" }, + { url = "https://files.pythonhosted.org/packages/08/58/46d3dcb99366c74b0478f2a58fd97e82419871a50989937e08578f9a5c5c/coverage-7.11.2-cp312-cp312-win32.whl", hash = "sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527", size = 219617, upload-time = "2025-11-08T20:24:46.086Z" }, + { url = "https://files.pythonhosted.org/packages/94/19/ab26b96a5c6fd0b5d644524997b60523b3ccbe7473a069e1385a272be238/coverage-7.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0", size = 220427, upload-time = "2025-11-08T20:24:47.809Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/948b268909f04eb2b0a55e22f1e4b3ffd472a8a398d05ebcf95c36d8b1eb/coverage-7.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc", size = 219068, upload-time = "2025-11-08T20:24:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/ec/00/57f3f8adaced9e4c74f482932e093176df7e400b4bb95dc1f3cd499511b5/coverage-7.11.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad", size = 217125, upload-time = "2025-11-08T20:24:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/ff1a55673161608c895080950cdfbb6485c95e6fa57a92d2cd1e463717b3/coverage-7.11.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3", size = 217499, upload-time = "2025-11-08T20:24:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/eaac01709ffbef291a12ca2526b6247f55ab17724e2297cc70921cd9a81f/coverage-7.11.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe", size = 248479, upload-time = "2025-11-08T20:24:54.825Z" }, + { url = "https://files.pythonhosted.org/packages/75/25/d846d2d08d182eeb30d1eba839fabdd9a3e6c710a1f187657b9c697bab23/coverage-7.11.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477", size = 251074, upload-time = "2025-11-08T20:24:56.442Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7a/34c9402ad12bce609be4be1146a7d22a7fae8e9d752684b6315cce552a65/coverage-7.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050", size = 252318, upload-time = "2025-11-08T20:24:57.987Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2f/292fe3cea4cc1c4b8fb060fa60e565ab1b3bfc67bda74bedefb24b4a2407/coverage-7.11.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33", size = 248641, upload-time = "2025-11-08T20:24:59.642Z" }, + { url = "https://files.pythonhosted.org/packages/c5/af/33ccb2aa2f43bbc330a1fccf84a396b90f2e61c00dccb7b72b2993a3c795/coverage-7.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b", size = 250457, upload-time = "2025-11-08T20:25:01.358Z" }, + { url = "https://files.pythonhosted.org/packages/bd/91/4b5b58f34e0587fbc5c1a28d644d9c20c13349c1072aea507b6e372c8f20/coverage-7.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248", size = 248421, upload-time = "2025-11-08T20:25:02.895Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d5/5c5ed220b15f490717522d241629c522fa22275549a6ccfbc96a3654b009/coverage-7.11.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c", size = 248244, upload-time = "2025-11-08T20:25:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/1e/27/504088aba40735132db838711d966e1314931ff9bddcd0e2ea6bc7e345a7/coverage-7.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148", size = 250004, upload-time = "2025-11-08T20:25:06.633Z" }, + { url = "https://files.pythonhosted.org/packages/ea/89/4d61c0ad0d39656bd5e73fe41a93a34b063c90333258e6307aadcfcdbb97/coverage-7.11.2-cp313-cp313-win32.whl", hash = "sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6", size = 219639, upload-time = "2025-11-08T20:25:08.27Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a7/a298afa025ebe7a2afd6657871a1ac2d9c49666ce00f9a35ee9df61a3bd8/coverage-7.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733", size = 220445, upload-time = "2025-11-08T20:25:09.906Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a1/1825f5eadc0a0a6ea1c6e678827e1ec8c0494dbd23270016fccfc3358fbf/coverage-7.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41", size = 219077, upload-time = "2025-11-08T20:25:11.777Z" }, + { url = "https://files.pythonhosted.org/packages/c0/61/98336c6f4545690b482e805c3a1a83fb2db4c19076307b187db3d421b5b3/coverage-7.11.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4", size = 217818, upload-time = "2025-11-08T20:25:13.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/ee/6dca6e5f1a4affba8d3224996d0e9145e6d67817da753cc436e48bb8d0e6/coverage-7.11.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c", size = 218170, upload-time = "2025-11-08T20:25:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/ec/17/9c9ca3ef09d3576027e77cf580eb599d8d655f9ca2456a26ca50c53e07e3/coverage-7.11.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b", size = 259466, upload-time = "2025-11-08T20:25:17.373Z" }, + { url = "https://files.pythonhosted.org/packages/53/96/2001a596827a0b91ba5f627f21b5ce998fa1f27d861a8f6d909f5ea663ff/coverage-7.11.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f", size = 261530, upload-time = "2025-11-08T20:25:19.085Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/fea7007035fdc3c40fcca0ab740da549ff9d38fa50b0d37cd808fbbf9683/coverage-7.11.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9", size = 263963, upload-time = "2025-11-08T20:25:21.168Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b3/7452071353441b632ebea42f6ad328a7ab592e4bc50a31f9921b41667017/coverage-7.11.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227", size = 258644, upload-time = "2025-11-08T20:25:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/6e56b1c2b3308f587508ad4b0a4cb76c8d6179fea2df148e071979b3eb77/coverage-7.11.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862", size = 261539, upload-time = "2025-11-08T20:25:25.277Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/7afeeac2a49f651318e4a83f1d5f4d3d4f4092f1d451ac4aec8069cddbdb/coverage-7.11.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5", size = 259153, upload-time = "2025-11-08T20:25:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/08f3b5c7500b2031cee74e8a01f9a5bc407f781ff6a826707563bb9dd5b7/coverage-7.11.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24", size = 258043, upload-time = "2025-11-08T20:25:30.087Z" }, + { url = "https://files.pythonhosted.org/packages/ca/49/8e080e7622bd7c82df0f8324bbe0461ed1032a638b80046f1a53a88ea3a8/coverage-7.11.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790", size = 260243, upload-time = "2025-11-08T20:25:31.722Z" }, + { url = "https://files.pythonhosted.org/packages/dc/75/da033d8589661527b4a6d30c414005467e48fbccc0f3c10898af183e14e1/coverage-7.11.2-cp313-cp313t-win32.whl", hash = "sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9", size = 220309, upload-time = "2025-11-08T20:25:33.9Z" }, + { url = "https://files.pythonhosted.org/packages/29/ef/8a477d41dbcde1f1179c13c43c9f77ee926b793fe3e5f1cf5d868a494679/coverage-7.11.2-cp313-cp313t-win_amd64.whl", hash = "sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37", size = 221374, upload-time = "2025-11-08T20:25:35.88Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a3/4c3cdd737ed1f630b821430004c2d5f1088b9bc0a7115aa5ad7c40d7d5cb/coverage-7.11.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5", size = 219648, upload-time = "2025-11-08T20:25:37.572Z" }, + { url = "https://files.pythonhosted.org/packages/52/d1/43d17c299249085d6e0df36db272899e92aa09e68e27d3e92a4cf8d9523e/coverage-7.11.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006", size = 217170, upload-time = "2025-11-08T20:25:39.254Z" }, + { url = "https://files.pythonhosted.org/packages/78/66/f21c03307079a0b7867b364af057430018a3d4a18ed1b99e1adaf5a0f305/coverage-7.11.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4", size = 217497, upload-time = "2025-11-08T20:25:41.277Z" }, + { url = "https://files.pythonhosted.org/packages/f0/dd/0a2257154c32f442fe3b4622501ab818ae4bd7cde33bd7a740630f6bd24c/coverage-7.11.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0", size = 248539, upload-time = "2025-11-08T20:25:43.349Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ca/c55ab0ee5ebfc4ab56cfc1b3585cba707342dc3f891fe19f02e07bc0c25f/coverage-7.11.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e", size = 251057, upload-time = "2025-11-08T20:25:45.083Z" }, + { url = "https://files.pythonhosted.org/packages/db/01/a149b88ebe714b76d95427d609e629446d1df5d232f4bdaec34e471da124/coverage-7.11.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599", size = 252393, upload-time = "2025-11-08T20:25:47.272Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a4/a992c805e95c46f0ac1b83782aa847030cb52bbfd8fc9015cff30f50fb9e/coverage-7.11.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8", size = 248534, upload-time = "2025-11-08T20:25:49.034Z" }, + { url = "https://files.pythonhosted.org/packages/78/01/318ed024ae245dbc76152bc016919aef69c508a5aac0e2da5de9b1efea61/coverage-7.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e", size = 250412, upload-time = "2025-11-08T20:25:51.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f9/f05c7984ef48c8d1c6c1ddb243223b344dcd8c6c0d54d359e4e325e2fa7e/coverage-7.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf", size = 248367, upload-time = "2025-11-08T20:25:53.399Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ac/461ed0dcaba0c727b760057ffa9837920d808a35274e179ff4a94f6f755a/coverage-7.11.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84", size = 248187, upload-time = "2025-11-08T20:25:55.402Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bf/8510ce8c7b1a8d682726df969e7523ee8aac23964b2c8301b8ce2400c1b4/coverage-7.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1", size = 249849, upload-time = "2025-11-08T20:25:57.186Z" }, + { url = "https://files.pythonhosted.org/packages/75/6f/ea1c8990ca35d607502c9e531f164573ea59bb6cd5cd4dc56d7cc3d1fcb5/coverage-7.11.2-cp314-cp314-win32.whl", hash = "sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38", size = 219908, upload-time = "2025-11-08T20:25:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/1e/04/a64e2a8b9b65ae84670207dc6073e3d48ee9192646440b469e9b8c335d1f/coverage-7.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1", size = 220724, upload-time = "2025-11-08T20:26:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/73/df/eb4e9f9d0d55f7ec2b55298c30931a665c2249c06e3d1d14c5a6df638c77/coverage-7.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35", size = 219296, upload-time = "2025-11-08T20:26:02.918Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b5/e9bb3b17a65fe92d1c7a2363eb5ae9893fafa578f012752ed40eee6aa3c8/coverage-7.11.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8", size = 217905, upload-time = "2025-11-08T20:26:04.633Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/1f38dd0b63a9d82fb3c9d7fbe1c9dab26ae77e5b45e801d129664e039034/coverage-7.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a", size = 218172, upload-time = "2025-11-08T20:26:06.677Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5d/2aeb513c6841270783b216478c6edc65b128c6889850c5f77568aa3a3098/coverage-7.11.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349", size = 259537, upload-time = "2025-11-08T20:26:08.481Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/ddd9b22ec1b5c69cc579b149619c354f981aaaafc072b92574f2d3d6c267/coverage-7.11.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1", size = 261648, upload-time = "2025-11-08T20:26:10.551Z" }, + { url = "https://files.pythonhosted.org/packages/29/e2/8743b7281decd3f73b964389fea18305584dd6ba96f0aff91b4880b50310/coverage-7.11.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114", size = 264061, upload-time = "2025-11-08T20:26:12.306Z" }, + { url = "https://files.pythonhosted.org/packages/00/1b/46daea7c4349c4530c62383f45148cc878845374b7a632e3ac2769b2f26a/coverage-7.11.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391", size = 258580, upload-time = "2025-11-08T20:26:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/f9b1c2d921d585dd6499e05bd71420950cac4e800f71525eb3d2690944fe/coverage-7.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73", size = 261526, upload-time = "2025-11-08T20:26:16.353Z" }, + { url = "https://files.pythonhosted.org/packages/86/7d/55acee453a71a71b08b05848d718ce6ac4559d051b4a2c407b0940aa72be/coverage-7.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914", size = 259135, upload-time = "2025-11-08T20:26:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3f/cf1e0217efdebab257eb0f487215fe02ff2b6f914cea641b2016c33358e1/coverage-7.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f", size = 257959, upload-time = "2025-11-08T20:26:19.894Z" }, + { url = "https://files.pythonhosted.org/packages/68/0e/e9be33e55346e650c3218a313e888df80418415462c63bceaf4b31e36ab5/coverage-7.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14", size = 260290, upload-time = "2025-11-08T20:26:22.05Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/9e93937c2a9bd255bb5efeff8c5df1c8322e508371f76f21a58af0e36a31/coverage-7.11.2-cp314-cp314t-win32.whl", hash = "sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34", size = 220691, upload-time = "2025-11-08T20:26:24.043Z" }, + { url = "https://files.pythonhosted.org/packages/bf/30/893b5a67e2914cf2be8e99c511b8084eaa8c0585e42d8b3cd78208f5f126/coverage-7.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731", size = 221800, upload-time = "2025-11-08T20:26:26.24Z" }, + { url = "https://files.pythonhosted.org/packages/2b/8b/6d93448c494a35000cc97d8d5d9c9b3774fa2b0c0d5be55f16877f962d71/coverage-7.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7", size = 219838, upload-time = "2025-11-08T20:26:28.479Z" }, + { url = "https://files.pythonhosted.org/packages/05/7a/99766a75c88e576f47c2d9a06416ff5d95be9b42faca5c37e1ab77c4cd1a/coverage-7.11.2-py3-none-any.whl", hash = "sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c", size = 208891, upload-time = "2025-11-08T20:26:30.739Z" }, ] [package.optional-dependencies] @@ -1838,29 +1838,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/d0/b4c959a340dd391df1f6b4c2958920f9272bc45b6b45f8af657b9377e09b/lief-0.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3dadb33cad8cf01d78a5eb12fb660ed1a06619f0baade38b606fd151e87436e2", size = 3637731, upload-time = "2025-10-25T13:15:56.644Z" }, ] -[[package]] -name = "llvmlite" -version = "0.43.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/3d/f513755f285db51ab363a53e898b85562e950f79a2e6767a364530c2f645/llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5", size = 157069, upload-time = "2024-06-13T18:09:32.641Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/ff/6ca7e98998b573b4bd6566f15c35e5c8bea829663a6df0c7aa55ab559da9/llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761", size = 31064408, upload-time = "2024-06-13T18:08:13.462Z" }, - { url = "https://files.pythonhosted.org/packages/ca/5c/a27f9257f86f0cda3f764ff21d9f4217b9f6a0d45e7a39ecfa7905f524ce/llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc", size = 28793153, upload-time = "2024-06-13T18:08:17.336Z" }, - { url = "https://files.pythonhosted.org/packages/7e/3c/4410f670ad0a911227ea2ecfcba9f672a77cf1924df5280c4562032ec32d/llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead", size = 42857276, upload-time = "2024-06-13T18:08:21.071Z" }, - { url = "https://files.pythonhosted.org/packages/c6/21/2ffbab5714e72f2483207b4a1de79b2eecd9debbf666ff4e7067bcc5c134/llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a", size = 43871781, upload-time = "2024-06-13T18:08:26.32Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/b5478037c453554a61625ef1125f7e12bb1429ae11c6376f47beba9b0179/llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed", size = 28123487, upload-time = "2024-06-13T18:08:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/95/8c/de3276d773ab6ce3ad676df5fab5aac19696b2956319d65d7dd88fb10f19/llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98", size = 31064409, upload-time = "2024-06-13T18:08:34.006Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e1/38deed89ced4cf378c61e232265cfe933ccde56ae83c901aa68b477d14b1/llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57", size = 28793149, upload-time = "2024-06-13T18:08:37.42Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b2/4429433eb2dc8379e2cb582502dca074c23837f8fd009907f78a24de4c25/llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2", size = 42857277, upload-time = "2024-06-13T18:08:40.822Z" }, - { url = "https://files.pythonhosted.org/packages/6b/99/5d00a7d671b1ba1751fc9f19d3b36f3300774c6eebe2bcdb5f6191763eb4/llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749", size = 43871781, upload-time = "2024-06-13T18:08:46.41Z" }, - { url = "https://files.pythonhosted.org/packages/20/ab/ed5ed3688c6ba4f0b8d789da19fd8e30a9cf7fc5852effe311bc5aefe73e/llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91", size = 28107433, upload-time = "2024-06-13T18:08:50.834Z" }, - { url = "https://files.pythonhosted.org/packages/0b/67/9443509e5d2b6d8587bae3ede5598fa8bd586b1c7701696663ea8af15b5b/llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7", size = 31064409, upload-time = "2024-06-13T18:08:54.375Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9c/24139d3712d2d352e300c39c0e00d167472c08b3bd350c3c33d72c88ff8d/llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7", size = 28793145, upload-time = "2024-06-13T18:08:57.953Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f1/4c205a48488e574ee9f6505d50e84370a978c90f08dab41a42d8f2c576b6/llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f", size = 42857276, upload-time = "2024-06-13T18:09:02.067Z" }, - { url = "https://files.pythonhosted.org/packages/00/5f/323c4d56e8401c50185fd0e875fcf06b71bf825a863699be1eb10aa2a9cb/llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844", size = 43871781, upload-time = "2024-06-13T18:09:06.667Z" }, - { url = "https://files.pythonhosted.org/packages/c6/94/dea10e263655ce78d777e78d904903faae39d1fc440762be4a9dc46bed49/llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9", size = 28107442, upload-time = "2024-06-13T18:09:10.709Z" }, -] - [[package]] name = "markdown" version = "3.10" @@ -2867,7 +2844,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -2876,9 +2853,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/49/7845c2d7bf6474efd8e27905b51b11e6ce411708c91e829b93f324de9929/pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15", size = 197501, upload-time = "2025-11-08T21:12:11.607Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, + { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" }, ] [[package]] @@ -3552,7 +3529,6 @@ dependencies = [ [package.optional-dependencies] all = [ { name = "guppylang" }, - { name = "llvmlite", marker = "python_full_version < '3.13'" }, { name = "plotly" }, { name = "selene-sim" }, ] @@ -3565,9 +3541,6 @@ guppy = [ { name = "guppylang" }, { name = "selene-sim" }, ] -qir = [ - { name = "llvmlite", marker = "python_full_version < '3.13'" }, -] visualization = [ { name = "plotly" }, ] @@ -3577,7 +3550,6 @@ requires-dist = [ { name = "cupy-cuda13x", marker = "python_full_version >= '3.11' and extra == 'cuda'", specifier = ">=13.0.0" }, { name = "cuquantum-python-cu13", marker = "python_full_version >= '3.11' and extra == 'cuda'", specifier = ">=25.3.0" }, { name = "guppylang", marker = "extra == 'guppy'", specifier = ">=0.21.0" }, - { name = "llvmlite", marker = "python_full_version < '3.13' and extra == 'qir'", specifier = "==0.43.0" }, { name = "matplotlib", specifier = ">=2.2.0" }, { name = "networkx", specifier = ">=2.1.0" }, { name = "numpy", specifier = ">=1.15.0" }, @@ -3586,12 +3558,11 @@ requires-dist = [ { name = "plotly", marker = "extra == 'visualization'", specifier = "~=5.9.0" }, { name = "pytket-cutensornet", marker = "python_full_version >= '3.11' and extra == 'cuda'", specifier = ">=0.12.0" }, { name = "quantum-pecos", extras = ["guppy"], marker = "extra == 'all'" }, - { name = "quantum-pecos", extras = ["qir"], marker = "extra == 'all'" }, { name = "quantum-pecos", extras = ["visualization"], marker = "extra == 'all'" }, { name = "scipy", specifier = ">=1.1.0" }, { name = "selene-sim", marker = "extra == 'guppy'", specifier = "~=0.2.0" }, ] -provides-extras = ["qir", "guppy", "visualization", "all", "cuda"] +provides-extras = ["guppy", "visualization", "all", "cuda"] [[package]] name = "qwasm"