|
| 1 | +// This code is part of Qiskit. |
| 2 | +// |
| 3 | +// (C) Copyright IBM 2026 |
| 4 | +// |
| 5 | +// This code is licensed under the Apache License, Version 2.0. You may |
| 6 | +// obtain a copy of this license in the LICENSE.txt file in the root directory |
| 7 | +// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0. |
| 8 | +// |
| 9 | +// Any modifications or derivative works of this code must retain this |
| 10 | +// copyright notice, and modified files need to carry a notice indicating |
| 11 | +// that they have been altered from the originals. |
| 12 | + |
| 13 | +use std::sync::LazyLock; |
| 14 | + |
| 15 | +// This has to be `pub(crate)` so that the `export_fn` macro can find it, but nothing in here is |
| 16 | +// supposed to actually be used other than as internal implementation details. |
| 17 | +#[doc(hidden)] |
| 18 | +pub(crate) mod inner { |
| 19 | + #[derive(Copy, Clone, Debug)] |
| 20 | + pub struct ExportedFunctionPartial { |
| 21 | + pub name: &'static str, |
| 22 | + #[cfg(feature = "addr")] |
| 23 | + pub addr: usize, |
| 24 | + } |
| 25 | + |
| 26 | + pub fn last_element(path: &str) -> &str { |
| 27 | + path.rfind(":") |
| 28 | + .map(|index| path.split_at(index + 1).1) |
| 29 | + .unwrap_or(path) |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +/// An exported C API function, along with a slot to place it in a function-pointer lookup table. |
| 34 | +#[derive(Copy, Clone, Debug)] |
| 35 | +pub struct ExportedFunction { |
| 36 | + /// The name of the function. |
| 37 | + pub name: &'static str, |
| 38 | + /// Which slot the function pointer should be assigned to, in its appropriate table. |
| 39 | + pub slot: usize, |
| 40 | + /// A pointer to the function, type erased to a pointer-width integer. |
| 41 | + /// |
| 42 | + /// In general, these derive from a type that looks like |
| 43 | + /// ``` |
| 44 | + /// unsafe extern "C" fn(T0, T1, ...) -> TRet |
| 45 | + /// ``` |
| 46 | + /// for some number of arguments and some (maybe void) return type. |
| 47 | + #[cfg(feature = "addr")] |
| 48 | + pub addr: usize, |
| 49 | +} |
| 50 | + |
| 51 | +/// Maximum number of children of any single [`ExportedFunctions`] object. |
| 52 | +/// |
| 53 | +/// Upping this causes more static memory use, but it shouldn't be too onerous. You can nest |
| 54 | +/// [`ExportedFunctions`] objects at any depth without trouble. |
| 55 | +pub const MAX_CHILDREN: usize = 8; |
| 56 | + |
| 57 | +/// A compile-time list of exported functions, including potential subgroups of functions. |
| 58 | +/// |
| 59 | +/// When creating one of these, you almost certainly want to assign it to a `static` variable; all |
| 60 | +/// the data it is supposed to represent is static |
| 61 | +pub struct ExportedFunctions { |
| 62 | + /// The amount of space reserved for the leaves. It is a panic to reserve less space than |
| 63 | + /// required, but it's fine (and encouraged) to reserve as much space as you think you'll expand |
| 64 | + /// to, in any given set of [ExportedFunctions]. |
| 65 | + leaves_reserve: usize, |
| 66 | + /// The calculated total length of reserved space (though there may be internal gaps that aren't |
| 67 | + /// technically reserved within it). This is calculated at compile time, mostly for the |
| 68 | + /// purposes of causing compile-time errors of this crate if the requested reservations don't |
| 69 | + /// fit together properly. |
| 70 | + len: usize, |
| 71 | + /// The leaf functions owned by this set of [ExportedFunctions]. This has to be constructed |
| 72 | + /// lazily because the function-pointer values can't (in general) be calculated until the |
| 73 | + /// compiled artifact is loaded into a process's memory space. |
| 74 | + /// |
| 75 | + /// This shouldn't be used directly; use [Self::get_leaves] to build it to ensure the `assert` |
| 76 | + /// code is called too. |
| 77 | + leaves: LazyLock<Vec<Option<inner::ExportedFunctionPartial>>>, |
| 78 | + /// The offsets and references to each child owned by this object. The funky static-sized array |
| 79 | + /// of maybe-uninitialized references is to make this all work at compile time. The array is |
| 80 | + /// guaranteed to be zero or more `Some` values and all the remainder are `None`. |
| 81 | + children: [Option<(usize, &'static ExportedFunctions)>; MAX_CHILDREN], |
| 82 | +} |
| 83 | +impl ExportedFunctions { |
| 84 | + /// Create a new (lazy) list of exported functions. |
| 85 | + /// |
| 86 | + /// The first argument is how much space to reserve for the leaf nodes. It must be at least as |
| 87 | + /// large as the vector of leaves, or this will panic when trying to access the functions. The |
| 88 | + /// second is a non-capturing closure that produces a vector of items defined by `export_fn` |
| 89 | + /// (or `None`). |
| 90 | + /// |
| 91 | + /// The second argument has to be a lazy closure because the addresses of functions generally |
| 92 | + /// aren't set until the fully compiled binary has been loaded up into a process; they can't be |
| 93 | + /// set at compile time. |
| 94 | + /// |
| 95 | + /// You can then append children with [`add_child`][Self::add_child]. If you don't need any |
| 96 | + /// leaf functions, use [`empty`][Self::empty]. |
| 97 | + pub const fn leaves( |
| 98 | + reserve: usize, |
| 99 | + slots: fn() -> Vec<Option<inner::ExportedFunctionPartial>>, |
| 100 | + ) -> Self { |
| 101 | + Self { |
| 102 | + leaves_reserve: reserve, |
| 103 | + len: reserve, |
| 104 | + leaves: LazyLock::new(slots), |
| 105 | + children: [None; MAX_CHILDREN], |
| 106 | + } |
| 107 | + } |
| 108 | + /// Create a new empty list of exported functions. |
| 109 | + /// |
| 110 | + /// You can then append children with [`add_child`][Self::add_child]. |
| 111 | + pub const fn empty() -> Self { |
| 112 | + Self::leaves(0, Vec::new) |
| 113 | + } |
| 114 | + |
| 115 | + /// Add a group of exported functions as a child of this set. |
| 116 | + /// |
| 117 | + /// You must add children in offset order, or the compile-time checks on validity will fail. |
| 118 | + /// |
| 119 | + /// # Panics |
| 120 | + /// |
| 121 | + /// If there are already [`MAX_CHILDREN`] children attached to this set of functions, or if the |
| 122 | + /// base `offset` is less than the maximum current reservation. |
| 123 | + pub const fn add_child(mut self, offset: usize, fns: &'static ExportedFunctions) -> Self { |
| 124 | + if offset < self.len { |
| 125 | + panic!("offset is less than previously reserved space; don't fill in holes"); |
| 126 | + } |
| 127 | + let mut i = 0; |
| 128 | + while self.children[i].is_some() { |
| 129 | + i += 1; |
| 130 | + if i == MAX_CHILDREN { |
| 131 | + // We'd panic even without this catch, but this just makes sure the dev sees a |
| 132 | + // clearer message about what's gone wrong. |
| 133 | + panic!("too many children; consider using deeper nesting"); |
| 134 | + } |
| 135 | + } |
| 136 | + // There isn't actually a value to throw away here, but we had to do this little dance with |
| 137 | + // the iteration and `replace` to keep things safely `const` |
| 138 | + self.children[i].replace((offset, fns)); |
| 139 | + self.len = offset + fns.len; |
| 140 | + self |
| 141 | + } |
| 142 | + |
| 143 | + /// The total length of the reservation |
| 144 | + pub fn len(&self) -> usize { |
| 145 | + self.len |
| 146 | + } |
| 147 | + pub fn is_empty(&self) -> bool { |
| 148 | + self.len == 0 |
| 149 | + } |
| 150 | + |
| 151 | + #[inline] |
| 152 | + fn get_leaves(&self) -> &[Option<inner::ExportedFunctionPartial>] { |
| 153 | + let slots = &self.leaves; |
| 154 | + assert!(slots.len() <= self.leaves_reserve); |
| 155 | + slots |
| 156 | + } |
| 157 | + |
| 158 | + /// Iterate through all the exported functions, filling in their complete slot information from |
| 159 | + /// a base offset. |
| 160 | + /// |
| 161 | + /// The order of iteration is not defined with respect to the slots; they are not guaranteed to |
| 162 | + /// be in sorted order. |
| 163 | + /// |
| 164 | + /// Requiring a `'static` lifetime on `self` is mostly just laziness in defining this (it lets |
| 165 | + /// us safely do it with iterator combinators rather than producing a custom "walker" class |
| 166 | + /// while avoiding recursive types), but one that shouldn't actually affect use of this, since |
| 167 | + /// all [`ExportedFunctions`] objects are expected to be defined as `static`s. |
| 168 | + pub fn exports(&'static self, offset: usize) -> Box<dyn Iterator<Item = ExportedFunction>> { |
| 169 | + Box::new( |
| 170 | + self.get_leaves() |
| 171 | + .iter() |
| 172 | + .enumerate() |
| 173 | + .filter_map(move |(i, func)| { |
| 174 | + func.as_ref().map(move |func| ExportedFunction { |
| 175 | + name: func.name, |
| 176 | + slot: offset + i, |
| 177 | + #[cfg(feature = "addr")] |
| 178 | + addr: func.addr, |
| 179 | + }) |
| 180 | + }) |
| 181 | + .chain( |
| 182 | + self.children |
| 183 | + .iter() |
| 184 | + .filter_map(move |funcs| { |
| 185 | + funcs |
| 186 | + .as_ref() |
| 187 | + .map(move |(inner, funcs)| funcs.exports(offset + inner)) |
| 188 | + }) |
| 189 | + .flatten(), |
| 190 | + ), |
| 191 | + ) |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +/// Create an entry in an `ExportedFunctions` table. |
| 196 | +/// |
| 197 | +/// The first argument to the macro is the path to export, which should resolve to some object |
| 198 | +/// declared like |
| 199 | +/// ``` |
| 200 | +/// #[unsafe(no_mangle)] |
| 201 | +/// pub unsafe extern "C" fn qk_my_function() {} |
| 202 | +/// ``` |
| 203 | +/// (or just `pub extern` - the `unsafe` is not important). |
| 204 | +/// |
| 205 | +/// If the function is only defined when certain features are active, you can follow the path with a |
| 206 | +/// comma-separated list of `feature = "my-feature"` items, such as |
| 207 | +/// ``` |
| 208 | +/// export_fn!(path::to::qk_my_function, feature = "python_binding", feature = "cool_stuff"); |
| 209 | +/// ``` |
| 210 | +macro_rules! export_fn { |
| 211 | + ($fn:path) => { |
| 212 | + Some($crate::impl_::inner::ExportedFunctionPartial { |
| 213 | + name: $crate::impl_::inner::last_element(stringify!($fn)), |
| 214 | + #[cfg(feature = "addr")] |
| 215 | + addr: ($fn as *const ()).addr(), |
| 216 | + }) |
| 217 | + }; |
| 218 | + ($fn:path, $(feature = $feat:tt),+) => {{ |
| 219 | + #[cfg(all($(feature = $feat),+))] |
| 220 | + let out = $crate::impl_::export_fn!($fn); |
| 221 | + #[cfg(not(all($(feature = $feat),+)))] |
| 222 | + let out = None::<$crate::impl_::inner::ExportedFunctionPartial>; |
| 223 | + out |
| 224 | + }}; |
| 225 | +} |
| 226 | +pub(crate) use export_fn; |
| 227 | + |
| 228 | +/// Helper module to made exports easier. This should contain everything that modules need to |
| 229 | +/// define their exports. |
| 230 | +pub(crate) mod prelude { |
| 231 | + pub(crate) use super::{ExportedFunctions, export_fn}; |
| 232 | +} |
0 commit comments