Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-04 09:20:03

0001 /*
0002     pybind11/detail/argument_vector.h: small_vector-like containers to
0003     avoid heap allocation of arguments during function call dispatch.
0004 
0005     Copyright (c) Meta Platforms, Inc. and affiliates.
0006 
0007     All rights reserved. Use of this source code is governed by a
0008     BSD-style license that can be found in the LICENSE file.
0009 */
0010 
0011 #pragma once
0012 
0013 #include <pybind11/pytypes.h>
0014 
0015 #include "common.h"
0016 
0017 #include <algorithm>
0018 #include <array>
0019 #include <cstddef>
0020 #include <cstdint>
0021 #include <cstring>
0022 #include <iterator>
0023 #include <type_traits>
0024 #include <utility>
0025 #include <vector>
0026 
0027 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
0028 
0029 PYBIND11_WARNING_DISABLE_MSVC(4127)
0030 
0031 PYBIND11_NAMESPACE_BEGIN(detail)
0032 
0033 // Shared implementation utility for our small_vector-like containers.
0034 // We support C++11 and C++14, so we cannot use
0035 // std::variant. Union with the tag packed next to the inline
0036 // array's size is smaller anyway, allowing 1 extra handle of
0037 // inline storage for free. Compare the layouts (1 line per
0038 // size_t/void*, assuming a 64-bit machine):
0039 // With variant, total is N + 2 for N >= 2:
0040 // - variant tag (cannot be packed with the array size)
0041 // - array size (or first pointer of 3 in std::vector)
0042 // - N pointers of inline storage (or 2 remaining pointers of std::vector)
0043 // Custom union, total is N + 1 for N >= 3:
0044 // - variant tag & array size if applicable
0045 // - N pointers of inline storage (or 3 pointers of std::vector)
0046 //
0047 // NOTE: this is a low-level representational convenience; the two
0048 // use cases of this union are materially different and in particular
0049 // have different semantics for inline_array::size. All that is being
0050 // shared is the memory management behavior.
0051 template <typename ArrayT, std::size_t InlineSize, typename VectorT = ArrayT>
0052 union inline_array_or_vector {
0053     struct inline_array {
0054         bool is_inline = true;
0055         std::uint32_t size = 0;
0056         std::array<ArrayT, InlineSize> arr;
0057     };
0058     struct heap_vector {
0059         bool is_inline = false;
0060         std::vector<VectorT> vec;
0061 
0062         heap_vector() = default;
0063         heap_vector(std::size_t count, VectorT value) : vec(count, value) {}
0064     };
0065 
0066     inline_array iarray;
0067     heap_vector hvector;
0068 
0069     inline_array_or_vector() : iarray() {}
0070 
0071     ~inline_array_or_vector() {
0072         if (is_inline()) {
0073             iarray.~inline_array();
0074         } else {
0075             hvector.~heap_vector();
0076         }
0077     }
0078 
0079     // Disable copy ctor and assignment.
0080     inline_array_or_vector(const inline_array_or_vector &) = delete;
0081     inline_array_or_vector &operator=(const inline_array_or_vector &) = delete;
0082 
0083     inline_array_or_vector(inline_array_or_vector &&rhs) noexcept {
0084         if (rhs.is_inline()) {
0085             new (&iarray) inline_array(std::move(rhs.iarray));
0086         } else {
0087             new (&hvector) heap_vector(std::move(rhs.hvector));
0088         }
0089         assert(is_inline() == rhs.is_inline());
0090     }
0091 
0092     inline_array_or_vector &operator=(inline_array_or_vector &&rhs) noexcept {
0093         if (this == &rhs) {
0094             return *this;
0095         }
0096 
0097         if (is_inline()) {
0098             iarray.~inline_array();
0099         } else {
0100             hvector.~heap_vector();
0101         }
0102 
0103         if (rhs.is_inline()) {
0104             new (&iarray) inline_array(std::move(rhs.iarray));
0105         } else {
0106             new (&hvector) heap_vector(std::move(rhs.hvector));
0107         }
0108         return *this;
0109     }
0110 
0111     bool is_inline() const {
0112         // It is undefined behavior to access the inactive member of a
0113         // union directly. However, it is well-defined to reinterpret_cast any
0114         // pointer into a pointer to char and examine it as an array
0115         // of bytes. See
0116         // https://dev-discuss.pytorch.org/t/unionizing-for-profit-how-to-exploit-the-power-of-unions-in-c/444#the-memcpy-loophole-4
0117         bool result = false;
0118         static_assert(offsetof(inline_array, is_inline) == 0,
0119                       "untagged union implementation relies on this");
0120         static_assert(offsetof(heap_vector, is_inline) == 0,
0121                       "untagged union implementation relies on this");
0122         std::memcpy(&result, reinterpret_cast<const char *>(this), sizeof(bool));
0123         return result;
0124     }
0125 };
0126 
0127 template <typename T, std::size_t InlineSize>
0128 struct small_vector {
0129 public:
0130     small_vector() = default;
0131 
0132     // Disable copy ctor and assignment.
0133     small_vector(const small_vector &) = delete;
0134     small_vector &operator=(const small_vector &) = delete;
0135     small_vector(small_vector &&) noexcept = default;
0136     small_vector &operator=(small_vector &&) noexcept = default;
0137 
0138     std::size_t size() const {
0139         if (is_inline()) {
0140             return m_repr.iarray.size;
0141         }
0142         return m_repr.hvector.vec.size();
0143     }
0144 
0145     T const *data() const {
0146         if (is_inline()) {
0147             return m_repr.iarray.arr.data();
0148         }
0149         return m_repr.hvector.vec.data();
0150     }
0151 
0152     T &operator[](std::size_t idx) {
0153         assert(idx < size());
0154         if (is_inline()) {
0155             return m_repr.iarray.arr[idx];
0156         }
0157         return m_repr.hvector.vec[idx];
0158     }
0159 
0160     T const &operator[](std::size_t idx) const {
0161         assert(idx < size());
0162         if (is_inline()) {
0163             return m_repr.iarray.arr[idx];
0164         }
0165         return m_repr.hvector.vec[idx];
0166     }
0167 
0168     void push_back(const T &x) { emplace_back(x); }
0169 
0170     void push_back(T &&x) { emplace_back(std::move(x)); }
0171 
0172     template <typename... Args>
0173     void emplace_back(Args &&...x) {
0174         if (is_inline()) {
0175             auto &ha = m_repr.iarray;
0176             if (ha.size == InlineSize) {
0177                 move_to_heap_vector_with_reserved_size(InlineSize + 1);
0178                 m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
0179             } else {
0180                 ha.arr[ha.size++] = T(std::forward<Args>(x)...);
0181             }
0182         } else {
0183             m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
0184         }
0185     }
0186 
0187     void reserve(std::size_t sz) {
0188         if (is_inline()) {
0189             if (sz > InlineSize) {
0190                 move_to_heap_vector_with_reserved_size(sz);
0191             }
0192         } else {
0193             reserve_slow_path(sz);
0194         }
0195     }
0196 
0197 private:
0198     using repr_type = inline_array_or_vector<T, InlineSize>;
0199     repr_type m_repr;
0200 
0201     PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
0202         assert(is_inline());
0203         auto &ha = m_repr.iarray;
0204         using heap_vector = typename repr_type::heap_vector;
0205         heap_vector hv;
0206         hv.vec.reserve(reserved_size);
0207         static_assert(std::is_nothrow_move_constructible<T>::value,
0208                       "this conversion is not exception safe");
0209         static_assert(std::is_nothrow_move_constructible<heap_vector>::value,
0210                       "this conversion is not exception safe");
0211         std::move(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
0212         new (&m_repr.hvector) heap_vector(std::move(hv));
0213     }
0214 
0215     PYBIND11_NOINLINE void reserve_slow_path(std::size_t sz) { m_repr.hvector.vec.reserve(sz); }
0216 
0217     bool is_inline() const { return m_repr.is_inline(); }
0218 };
0219 
0220 // Container to avoid heap allocation for kRequestedInlineSize or fewer booleans.
0221 template <std::size_t kRequestedInlineSize>
0222 struct small_vector<bool, kRequestedInlineSize> {
0223 private:
0224 public:
0225     small_vector() = default;
0226 
0227     // Disable copy ctor and assignment.
0228     small_vector(const small_vector &) = delete;
0229     small_vector &operator=(const small_vector &) = delete;
0230     small_vector(small_vector &&) noexcept = default;
0231     small_vector &operator=(small_vector &&) noexcept = default;
0232 
0233     small_vector(std::size_t count, bool value) {
0234         if (count > kInlineSize) {
0235             new (&m_repr.hvector) typename repr_type::heap_vector(count, value);
0236         } else {
0237             auto &inline_arr = m_repr.iarray;
0238             inline_arr.arr.fill(value ? static_cast<std::size_t>(-1) : 0);
0239             inline_arr.size = static_cast<decltype(inline_arr.size)>(count);
0240         }
0241     }
0242 
0243     std::size_t size() const {
0244         if (is_inline()) {
0245             return m_repr.iarray.size;
0246         }
0247         return m_repr.hvector.vec.size();
0248     }
0249 
0250     void reserve(std::size_t sz) {
0251         if (is_inline()) {
0252             if (sz > kInlineSize) {
0253                 move_to_heap_vector_with_reserved_size(sz);
0254             }
0255         } else {
0256             m_repr.hvector.vec.reserve(sz);
0257         }
0258     }
0259 
0260     bool operator[](std::size_t idx) const {
0261         if (is_inline()) {
0262             return inline_index(idx);
0263         }
0264         assert(idx < m_repr.hvector.vec.size());
0265         return m_repr.hvector.vec[idx];
0266     }
0267 
0268     void push_back(bool b) {
0269         if (is_inline()) {
0270             auto &ha = m_repr.iarray;
0271             if (ha.size == kInlineSize) {
0272                 move_to_heap_vector_with_reserved_size(kInlineSize + 1);
0273                 push_back_slow_path(b);
0274             } else {
0275                 assert(ha.size < kInlineSize);
0276                 const auto wbi = word_and_bit_index(ha.size++);
0277                 assert(wbi.word < kWords);
0278                 assert(wbi.bit < kBitsPerWord);
0279                 if (b) {
0280                     ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
0281                 } else {
0282                     ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
0283                 }
0284                 assert(operator[](ha.size - 1) == b);
0285             }
0286         } else {
0287             push_back_slow_path(b);
0288         }
0289     }
0290 
0291     void set(std::size_t idx, bool value = true) {
0292         if (is_inline()) {
0293             auto &ha = m_repr.iarray;
0294             assert(ha.size < kInlineSize);
0295             const auto wbi = word_and_bit_index(idx);
0296             assert(wbi.word < kWords);
0297             assert(wbi.bit < kBitsPerWord);
0298             if (value) {
0299                 ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
0300             } else {
0301                 ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
0302             }
0303         } else {
0304             m_repr.hvector.vec[idx] = value;
0305         }
0306     }
0307 
0308     void swap(small_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
0309 
0310 private:
0311     struct WordAndBitIndex {
0312         std::size_t word;
0313         std::size_t bit;
0314     };
0315 
0316     static WordAndBitIndex word_and_bit_index(std::size_t idx) {
0317         return WordAndBitIndex{idx / kBitsPerWord, idx % kBitsPerWord};
0318     }
0319 
0320     bool inline_index(std::size_t idx) const {
0321         const auto wbi = word_and_bit_index(idx);
0322         assert(wbi.word < kWords);
0323         assert(wbi.bit < kBitsPerWord);
0324         return m_repr.iarray.arr[wbi.word] & (static_cast<std::size_t>(1) << wbi.bit);
0325     }
0326 
0327     PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
0328         auto &inline_arr = m_repr.iarray;
0329         using heap_vector = typename repr_type::heap_vector;
0330         heap_vector hv;
0331         hv.vec.reserve(reserved_size);
0332         for (std::size_t ii = 0; ii < inline_arr.size; ++ii) {
0333             hv.vec.push_back(inline_index(ii));
0334         }
0335         new (&m_repr.hvector) heap_vector(std::move(hv));
0336     }
0337 
0338     PYBIND11_NOINLINE void push_back_slow_path(bool b) { m_repr.hvector.vec.push_back(b); }
0339 
0340     static constexpr auto kBitsPerWord = 8 * sizeof(std::size_t);
0341     static constexpr auto kWords = (kRequestedInlineSize + kBitsPerWord - 1) / kBitsPerWord;
0342     static constexpr auto kInlineSize = kWords * kBitsPerWord;
0343 
0344     using repr_type = inline_array_or_vector<std::size_t, kWords, bool>;
0345     repr_type m_repr;
0346 
0347     bool is_inline() const { return m_repr.is_inline(); }
0348 };
0349 
0350 // Container to avoid heap allocation for N or fewer arguments.
0351 template <size_t N>
0352 using argument_vector = small_vector<handle, N>;
0353 
0354 // Container to avoid heap allocation for N or fewer booleans.
0355 template <size_t N>
0356 using args_convert_vector = small_vector<bool, N>;
0357 
0358 /// A small_vector of PyObject* that holds references and releases them on destruction.
0359 /// This provides explicit ownership semantics without relying on py::object's
0360 /// destructor, and avoids the need for reinterpret_cast when passing to vectorcall.
0361 template <std::size_t InlineSize>
0362 class ref_small_vector {
0363 public:
0364     ref_small_vector() = default;
0365 
0366     ~ref_small_vector() {
0367         for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
0368             Py_XDECREF(m_ptrs[i]);
0369         }
0370     }
0371 
0372     // Disable copy (prevent accidental double-decref)
0373     ref_small_vector(const ref_small_vector &) = delete;
0374     ref_small_vector &operator=(const ref_small_vector &) = delete;
0375 
0376     // Move is allowed
0377     ref_small_vector(ref_small_vector &&other) noexcept : m_ptrs(std::move(other.m_ptrs)) {
0378         // other.m_ptrs is now empty, so its destructor won't decref anything
0379     }
0380 
0381     ref_small_vector &operator=(ref_small_vector &&other) noexcept {
0382         if (this != &other) {
0383             // Decref our current contents
0384             for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
0385                 Py_XDECREF(m_ptrs[i]);
0386             }
0387             m_ptrs = std::move(other.m_ptrs);
0388         }
0389         return *this;
0390     }
0391 
0392     /// Add a pointer, taking ownership (no incref, will decref on destruction)
0393     void push_back_steal(PyObject *p) { m_ptrs.push_back(p); }
0394 
0395     /// Add a pointer, borrowing (increfs now, will decref on destruction)
0396     void push_back_borrow(PyObject *p) {
0397         Py_XINCREF(p);
0398         m_ptrs.push_back(p);
0399     }
0400 
0401     /// Add a null pointer (for PY_VECTORCALL_ARGUMENTS_OFFSET slot)
0402     void push_back_null() { m_ptrs.push_back(nullptr); }
0403 
0404     void reserve(std::size_t sz) { m_ptrs.reserve(sz); }
0405 
0406     std::size_t size() const { return m_ptrs.size(); }
0407 
0408     PyObject *operator[](std::size_t idx) const { return m_ptrs[idx]; }
0409 
0410     PyObject *const *data() const { return m_ptrs.data(); }
0411 
0412 private:
0413     small_vector<PyObject *, InlineSize> m_ptrs;
0414 };
0415 
0416 PYBIND11_NAMESPACE_END(detail)
0417 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)